fix: PXE boot debugging — bisect root cause, syslog logging, serial console #3
247
.gitea/workflows/ci.yml
Normal file
247
.gitea/workflows/ci.yml
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
name: CI/CD
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITEA_REGISTRY: 10.0.0.194:3012
|
||||||
|
GITEA_PUBLIC_URL: https://mysources.co.uk
|
||||||
|
GITEA_OWNER: michal
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Required Gitea secrets:
|
||||||
|
# PACKAGES_TOKEN -- Gitea API token (packages + registry)
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# -- CI checks (run in parallel on every push/PR) ----------
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: bastion
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: pnpm lint || echo "::warning::Lint has errors -- not blocking CI yet"
|
||||||
|
|
||||||
|
typecheck:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: bastion
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Typecheck
|
||||||
|
run: pnpm typecheck
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: bastion
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build (needed by completions check)
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: pnpm test:run
|
||||||
|
|
||||||
|
# -- Build & package ---------------------------------------
|
||||||
|
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [lint, typecheck, test]
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: bastion
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: pnpm/action-setup@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Build all packages
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
- name: Generate shell completions
|
||||||
|
run: pnpm completions:generate
|
||||||
|
|
||||||
|
- uses: oven-sh/setup-bun@v2
|
||||||
|
|
||||||
|
- name: Install nfpm
|
||||||
|
run: |
|
||||||
|
curl -sL -o /tmp/nfpm.tar.gz "https://github.com/goreleaser/nfpm/releases/download/v2.45.0/nfpm_2.45.0_Linux_x86_64.tar.gz"
|
||||||
|
tar xzf /tmp/nfpm.tar.gz -C /usr/local/bin nfpm
|
||||||
|
|
||||||
|
- name: Bundle standalone binary
|
||||||
|
run: |
|
||||||
|
mkdir -p dist
|
||||||
|
bun build src/cli/src/index.ts --compile --outfile dist/lab
|
||||||
|
|
||||||
|
- name: Package RPM
|
||||||
|
run: nfpm pkg --packager rpm --target dist/
|
||||||
|
|
||||||
|
- name: Package DEB
|
||||||
|
run: nfpm pkg --packager deb --target dist/
|
||||||
|
|
||||||
|
- name: Upload RPM artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: rpm-package
|
||||||
|
path: bastion/dist/lab-*.rpm
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Upload DEB artifact
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: deb-package
|
||||||
|
path: bastion/dist/lab*.deb
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
# -- Release pipeline (main branch push only) --------------
|
||||||
|
|
||||||
|
publish-rpm:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build]
|
||||||
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: bastion
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download RPM artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: rpm-package
|
||||||
|
path: bastion/dist/
|
||||||
|
|
||||||
|
- name: Install rpm tools
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y rpm
|
||||||
|
|
||||||
|
- name: Publish RPM to Gitea
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||||
|
GITEA_URL: http://${{ env.GITEA_REGISTRY }}
|
||||||
|
GITEA_OWNER: ${{ env.GITEA_OWNER }}
|
||||||
|
GITEA_REPO: lab
|
||||||
|
run: |
|
||||||
|
RPM_FILE=$(ls dist/lab-*.rpm | head -1)
|
||||||
|
RPM_VERSION=$(rpm -qp --queryformat '%{VERSION}-%{RELEASE}' "$RPM_FILE")
|
||||||
|
echo "Publishing $RPM_FILE (version $RPM_VERSION)..."
|
||||||
|
|
||||||
|
# Delete existing version if present
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/packages/${GITEA_OWNER}/rpm/lab/${RPM_VERSION}")
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo "Version exists, replacing..."
|
||||||
|
curl -s -o /dev/null -X DELETE \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/packages/${GITEA_OWNER}/rpm/lab/${RPM_VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload
|
||||||
|
curl --fail -X PUT \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
--upload-file "$RPM_FILE" \
|
||||||
|
"${GITEA_URL}/api/packages/${GITEA_OWNER}/rpm/upload"
|
||||||
|
|
||||||
|
echo "Published successfully!"
|
||||||
|
|
||||||
|
# Link package to repo
|
||||||
|
source scripts/link-package.sh
|
||||||
|
link_package "rpm" "lab"
|
||||||
|
|
||||||
|
publish-deb:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [build]
|
||||||
|
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||||
|
defaults:
|
||||||
|
run:
|
||||||
|
working-directory: bastion
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download DEB artifact
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: deb-package
|
||||||
|
path: bastion/dist/
|
||||||
|
|
||||||
|
- name: Publish DEB to Gitea
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
|
||||||
|
GITEA_URL: http://${{ env.GITEA_REGISTRY }}
|
||||||
|
GITEA_OWNER: ${{ env.GITEA_OWNER }}
|
||||||
|
GITEA_REPO: lab
|
||||||
|
run: |
|
||||||
|
DEB_FILE=$(ls dist/lab*.deb | head -1)
|
||||||
|
DEB_VERSION=$(dpkg-deb --field "$DEB_FILE" Version)
|
||||||
|
echo "Publishing $DEB_FILE (version $DEB_VERSION)..."
|
||||||
|
|
||||||
|
# Publish to each supported distribution
|
||||||
|
DISTRIBUTIONS="trixie forky noble plucky"
|
||||||
|
|
||||||
|
for DIST in $DISTRIBUTIONS; do
|
||||||
|
echo " -> $DIST..."
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-X PUT \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
--upload-file "$DEB_FILE" \
|
||||||
|
"${GITEA_URL}/api/packages/${GITEA_OWNER}/debian/pool/${DIST}/main/upload")
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo " Published to $DIST"
|
||||||
|
elif [ "$HTTP_CODE" = "409" ]; then
|
||||||
|
echo " Already exists in $DIST (skipping)"
|
||||||
|
else
|
||||||
|
echo " WARNING: Upload to $DIST returned HTTP $HTTP_CODE"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Published successfully!"
|
||||||
|
|
||||||
|
# Link package to repo
|
||||||
|
source scripts/link-package.sh
|
||||||
|
link_package "debian" "lab"
|
||||||
67
bastion/completions/lab.bash
Normal file
67
bastion/completions/lab.bash
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# lab bash completions -- auto-generated by scripts/generate-completions.ts
|
||||||
|
# DO NOT EDIT MANUALLY -- run: pnpm completions:generate
|
||||||
|
|
||||||
|
_lab() {
|
||||||
|
local cur prev words cword
|
||||||
|
_init_completion || return
|
||||||
|
|
||||||
|
local top_commands="init provision"
|
||||||
|
|
||||||
|
# Extract the subcommand chain (skip options and their values)
|
||||||
|
local -a subcmd_chain=()
|
||||||
|
local i skip_next=false
|
||||||
|
for ((i=1; i < cword; i++)); do
|
||||||
|
if $skip_next; then skip_next=false; continue; fi
|
||||||
|
case "${words[i]}" in
|
||||||
|
-*) ;; # skip options
|
||||||
|
*) subcmd_chain+=("${words[i]}") ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
local chain_len=${#subcmd_chain[@]}
|
||||||
|
local chain_str="${subcmd_chain[*]}"
|
||||||
|
|
||||||
|
case "$chain_str" in
|
||||||
|
"init bastion standalone start")
|
||||||
|
COMPREPLY=($(compgen -W "--port --dir --domain --dhcp-mode --fedora --arch --timezone --locale --skip-dnsmasq --skip-artifacts -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"init bastion standalone stop")
|
||||||
|
COMPREPLY=($(compgen -W "--dir -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"init bastion standalone status")
|
||||||
|
COMPREPLY=($(compgen -W "--dir --port -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"init bastion standalone")
|
||||||
|
COMPREPLY=($(compgen -W "start stop status -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"init bastion")
|
||||||
|
COMPREPLY=($(compgen -W "standalone -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"provision list")
|
||||||
|
COMPREPLY=($(compgen -W "--port -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"provision install")
|
||||||
|
COMPREPLY=($(compgen -W "--role --disk --port -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"provision reprovision")
|
||||||
|
COMPREPLY=($(compgen -W "--role --disk --port -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"provision forget")
|
||||||
|
COMPREPLY=($(compgen -W "--port -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"init")
|
||||||
|
COMPREPLY=($(compgen -W "bastion -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"provision")
|
||||||
|
COMPREPLY=($(compgen -W "list install reprovision forget -h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
"")
|
||||||
|
COMPREPLY=($(compgen -W "$top_commands -h --help -v --version" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
*)
|
||||||
|
COMPREPLY=($(compgen -W "-h --help" -- "$cur"))
|
||||||
|
return ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
complete -F _lab lab
|
||||||
91
bastion/completions/lab.fish
Normal file
91
bastion/completions/lab.fish
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# lab fish completions -- auto-generated by scripts/generate-completions.ts
|
||||||
|
# DO NOT EDIT MANUALLY -- run: pnpm completions:generate
|
||||||
|
|
||||||
|
complete -c lab -e
|
||||||
|
complete -c lab -f
|
||||||
|
|
||||||
|
# Global options
|
||||||
|
complete -c lab -s v -l version -d 'Show version'
|
||||||
|
complete -c lab -s h -l help -d 'Show help'
|
||||||
|
|
||||||
|
# Helper: test if a subcommand chain is active
|
||||||
|
function __lab_using_cmd
|
||||||
|
set -l tokens (commandline -opc)
|
||||||
|
set -l expected $argv
|
||||||
|
set -l depth (count $expected)
|
||||||
|
set -l found 0
|
||||||
|
set -l i 1
|
||||||
|
for tok in $tokens[2..]
|
||||||
|
if string match -q -- "-*" $tok
|
||||||
|
continue
|
||||||
|
end
|
||||||
|
set i (math $i + 1)
|
||||||
|
set -l idx (math $i - 1)
|
||||||
|
if test $idx -le $depth
|
||||||
|
if test "$tok" != "$expected[$idx]"
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
set found (math $found + 1)
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
test $found -eq $depth
|
||||||
|
end
|
||||||
|
|
||||||
|
# Top-level commands
|
||||||
|
complete -c lab -n "not __fish_seen_subcommand_from init provision" -a init -d 'Initialise infrastructure components'
|
||||||
|
complete -c lab -n "not __fish_seen_subcommand_from init provision" -a provision -d 'Machine provisioning operations'
|
||||||
|
|
||||||
|
# init subcommands
|
||||||
|
complete -c lab -n "__lab_using_cmd init" -a bastion -d 'Bastion PXE server management'
|
||||||
|
|
||||||
|
# init bastion subcommands
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion" -a standalone -d 'Standalone bastion server lifecycle'
|
||||||
|
|
||||||
|
# init bastion standalone subcommands
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone" -a start -d 'Start the bastion server (HTTP + dnsmasq PXE)'
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone" -a stop -d 'Stop a running bastion server'
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone" -a status -d 'Show bastion server status'
|
||||||
|
|
||||||
|
# init bastion standalone start options
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l port -d 'HTTP port' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l dir -d 'Bastion data directory' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l domain -d 'Internal domain for hostnames' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l dhcp-mode -d 'DHCP mode: proxy or full' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l fedora -d 'Fedora version' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l arch -d 'Architecture' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l timezone -d 'Timezone' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l locale -d 'Locale' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l skip-dnsmasq -d 'Skip starting dnsmasq (for testing)'
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone start" -l skip-artifacts -d 'Skip downloading boot artifacts (for testing)'
|
||||||
|
|
||||||
|
# init bastion standalone stop options
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone stop" -l dir -d 'Bastion data directory' -x
|
||||||
|
|
||||||
|
# init bastion standalone status options
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone status" -l dir -d 'Bastion data directory' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd init bastion standalone status" -l port -d 'Bastion HTTP port' -x
|
||||||
|
|
||||||
|
# provision subcommands
|
||||||
|
complete -c lab -n "__lab_using_cmd provision" -a list -d 'List all known machines'
|
||||||
|
complete -c lab -n "__lab_using_cmd provision" -a install -d 'Queue a discovered machine for Fedora installation'
|
||||||
|
complete -c lab -n "__lab_using_cmd provision" -a reprovision -d 'Queue install + SSH reboot into PXE for reprovision'
|
||||||
|
complete -c lab -n "__lab_using_cmd provision" -a forget -d 'Remove a machine from bastion state'
|
||||||
|
|
||||||
|
# provision list options
|
||||||
|
complete -c lab -n "__lab_using_cmd provision list" -l port -d 'Bastion HTTP port' -x
|
||||||
|
|
||||||
|
# provision install options
|
||||||
|
complete -c lab -n "__lab_using_cmd provision install" -l role -d 'Machine role: worker or infra' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd provision install" -l disk -d 'Target disk device (auto-detect if omitted)' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd provision install" -l port -d 'Bastion HTTP port' -x
|
||||||
|
|
||||||
|
# provision reprovision options
|
||||||
|
complete -c lab -n "__lab_using_cmd provision reprovision" -l role -d 'Machine role: worker or infra' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd provision reprovision" -l disk -d 'Target disk device (auto-detect if omitted)' -x
|
||||||
|
complete -c lab -n "__lab_using_cmd provision reprovision" -l port -d 'Bastion HTTP port' -x
|
||||||
|
|
||||||
|
# provision forget options
|
||||||
|
complete -c lab -n "__lab_using_cmd provision forget" -l port -d 'Bastion HTTP port' -x
|
||||||
|
|
||||||
26
bastion/eslint.config.js
Normal file
26
bastion/eslint.config.js
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import tseslint from '@typescript-eslint/eslint-plugin';
|
||||||
|
import tsparser from '@typescript-eslint/parser';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
files: ['src/*/src/**/*.ts'],
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsparser,
|
||||||
|
parserOptions: {
|
||||||
|
project: ['./src/*/tsconfig.json'],
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: { '@typescript-eslint': tseslint },
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'error',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'error',
|
||||||
|
'@typescript-eslint/no-unused-vars': 'error',
|
||||||
|
'@typescript-eslint/strict-boolean-expressions': 'error',
|
||||||
|
'no-console': ['warn', { allow: ['warn', 'error'] }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: ['**/dist/**', '**/node_modules/**', '**/*.config.*'],
|
||||||
|
},
|
||||||
|
];
|
||||||
20
bastion/nfpm.yaml
Normal file
20
bastion/nfpm.yaml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: lab
|
||||||
|
arch: amd64
|
||||||
|
version: 0.1.0
|
||||||
|
release: "1"
|
||||||
|
maintainer: michal
|
||||||
|
description: Lab infrastructure CLI for bare-metal provisioning
|
||||||
|
license: MIT
|
||||||
|
contents:
|
||||||
|
- src: ./dist/lab
|
||||||
|
dst: /usr/bin/lab
|
||||||
|
file_info:
|
||||||
|
mode: 0755
|
||||||
|
- src: ./completions/lab.bash
|
||||||
|
dst: /usr/share/bash-completion/completions/lab
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
|
- src: ./completions/lab.fish
|
||||||
|
dst: /usr/share/fish/vendor_completions.d/lab.fish
|
||||||
|
file_info:
|
||||||
|
mode: 0644
|
||||||
@@ -10,7 +10,10 @@
|
|||||||
"test:run": "vitest run",
|
"test:run": "vitest run",
|
||||||
"typecheck": "tsc --build",
|
"typecheck": "tsc --build",
|
||||||
"clean": "pnpm -r run clean && rimraf node_modules",
|
"clean": "pnpm -r run clean && rimraf node_modules",
|
||||||
"lint": "eslint 'src/*/src/**/*.ts'"
|
"lint": "eslint 'src/*/src/**/*.ts'",
|
||||||
|
"lint:fix": "eslint 'src/*/src/**/*.ts' --fix",
|
||||||
|
"completions:generate": "tsx scripts/generate-completions.ts --write",
|
||||||
|
"completions:check": "tsx scripts/generate-completions.ts --check"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0",
|
"node": ">=20.0.0",
|
||||||
@@ -19,6 +22,10 @@
|
|||||||
"packageManager": "pnpm@9.15.0",
|
"packageManager": "pnpm@9.15.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.10.0",
|
"@types/node": "^22.10.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^8.57.1",
|
||||||
|
"@typescript-eslint/parser": "^8.57.1",
|
||||||
|
"eslint": "^10.0.3",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
"rimraf": "^6.0.0",
|
"rimraf": "^6.0.0",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.7.0",
|
"typescript": "^5.7.0",
|
||||||
|
|||||||
621
bastion/pnpm-lock.yaml
generated
621
bastion/pnpm-lock.yaml
generated
@@ -11,6 +11,18 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^22.10.0
|
specifier: ^22.10.0
|
||||||
version: 22.19.15
|
version: 22.19.15
|
||||||
|
'@typescript-eslint/eslint-plugin':
|
||||||
|
specifier: ^8.57.1
|
||||||
|
version: 8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/parser':
|
||||||
|
specifier: ^8.57.1
|
||||||
|
version: 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||||
|
eslint:
|
||||||
|
specifier: ^10.0.3
|
||||||
|
version: 10.0.3
|
||||||
|
eslint-config-prettier:
|
||||||
|
specifier: ^10.1.8
|
||||||
|
version: 10.1.8(eslint@10.0.3)
|
||||||
rimraf:
|
rimraf:
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.1.3
|
version: 6.1.3
|
||||||
@@ -229,6 +241,36 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@eslint-community/eslint-utils@4.9.1':
|
||||||
|
resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
|
||||||
|
|
||||||
|
'@eslint-community/regexpp@4.12.2':
|
||||||
|
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
|
||||||
|
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||||
|
|
||||||
|
'@eslint/config-array@0.23.3':
|
||||||
|
resolution: {integrity: sha512-j+eEWmB6YYLwcNOdlwQ6L2OsptI/LO6lNBuLIqe5R7RetD658HLoF+Mn7LzYmAWWNNzdC6cqP+L6r8ujeYXWLw==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
|
'@eslint/config-helpers@0.5.3':
|
||||||
|
resolution: {integrity: sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
|
'@eslint/core@1.1.1':
|
||||||
|
resolution: {integrity: sha512-QUPblTtE51/7/Zhfv8BDwO0qkkzQL7P/aWWbqcf4xWLEYn1oKjdO0gglQBB4GAsu7u6wjijbCmzsUTy6mnk6oQ==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
|
'@eslint/object-schema@3.0.3':
|
||||||
|
resolution: {integrity: sha512-iM869Pugn9Nsxbh/YHRqYiqd23AmIbxJOcpUMOuWCVNdoQJ5ZtwL6h3t0bcZzJUlC3Dq9jCFCESBZnX0GTv7iQ==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
|
'@eslint/plugin-kit@0.6.1':
|
||||||
|
resolution: {integrity: sha512-iH1B076HoAshH1mLpHMgwdGeTs0CYwL0SPMkGuSebZrwBp16v415e9NZXg2jtrqPVQjf6IANe2Vtlr5KswtcZQ==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
'@fastify/accept-negotiator@2.0.1':
|
'@fastify/accept-negotiator@2.0.1':
|
||||||
resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==}
|
resolution: {integrity: sha512-/c/TW2bO/v9JeEgoD/g1G5GxGeCF1Hafdf79WPmUlgYiBXummY0oX3VVq4yFkKKVBKDNlaDUYoab7g38RpPqCQ==}
|
||||||
|
|
||||||
@@ -256,6 +298,22 @@ packages:
|
|||||||
'@fastify/static@8.3.0':
|
'@fastify/static@8.3.0':
|
||||||
resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==}
|
resolution: {integrity: sha512-yKxviR5PH1OKNnisIzZKmgZSus0r2OZb8qCSbqmw34aolT4g3UlzYfeBRym+HJ1J471CR8e2ldNub4PubD1coA==}
|
||||||
|
|
||||||
|
'@humanfs/core@0.19.1':
|
||||||
|
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
|
||||||
|
engines: {node: '>=18.18.0'}
|
||||||
|
|
||||||
|
'@humanfs/node@0.16.7':
|
||||||
|
resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==}
|
||||||
|
engines: {node: '>=18.18.0'}
|
||||||
|
|
||||||
|
'@humanwhocodes/module-importer@1.0.1':
|
||||||
|
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
|
||||||
|
engines: {node: '>=12.22'}
|
||||||
|
|
||||||
|
'@humanwhocodes/retry@0.4.3':
|
||||||
|
resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==}
|
||||||
|
engines: {node: '>=18.18'}
|
||||||
|
|
||||||
'@isaacs/cliui@9.0.0':
|
'@isaacs/cliui@9.0.0':
|
||||||
resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
|
resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -411,15 +469,80 @@ packages:
|
|||||||
'@types/deep-eql@4.0.2':
|
'@types/deep-eql@4.0.2':
|
||||||
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
|
||||||
|
|
||||||
|
'@types/esrecurse@4.3.1':
|
||||||
|
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
|
||||||
|
|
||||||
'@types/estree@1.0.8':
|
'@types/estree@1.0.8':
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
|
'@types/json-schema@7.0.15':
|
||||||
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
'@types/node@22.19.15':
|
'@types/node@22.19.15':
|
||||||
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
|
resolution: {integrity: sha512-F0R/h2+dsy5wJAUe3tAU6oqa2qbWY5TpNfL/RGmo1y38hiyO1w3x2jPtt76wmuaJI4DQnOBu21cNXQ2STIUUWg==}
|
||||||
|
|
||||||
'@types/triple-beam@1.3.5':
|
'@types/triple-beam@1.3.5':
|
||||||
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
|
resolution: {integrity: sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==}
|
||||||
|
|
||||||
|
'@typescript-eslint/eslint-plugin@8.57.1':
|
||||||
|
resolution: {integrity: sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
'@typescript-eslint/parser': ^8.57.1
|
||||||
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/parser@8.57.1':
|
||||||
|
resolution: {integrity: sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/project-service@8.57.1':
|
||||||
|
resolution: {integrity: sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/scope-manager@8.57.1':
|
||||||
|
resolution: {integrity: sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/tsconfig-utils@8.57.1':
|
||||||
|
resolution: {integrity: sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/type-utils@8.57.1':
|
||||||
|
resolution: {integrity: sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/types@8.57.1':
|
||||||
|
resolution: {integrity: sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@typescript-eslint/typescript-estree@8.57.1':
|
||||||
|
resolution: {integrity: sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/utils@8.57.1':
|
||||||
|
resolution: {integrity: sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
peerDependencies:
|
||||||
|
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
|
||||||
|
typescript: '>=4.8.4 <6.0.0'
|
||||||
|
|
||||||
|
'@typescript-eslint/visitor-keys@8.57.1':
|
||||||
|
resolution: {integrity: sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==}
|
||||||
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
'@vitest/expect@3.2.4':
|
'@vitest/expect@3.2.4':
|
||||||
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
||||||
|
|
||||||
@@ -452,6 +575,16 @@ packages:
|
|||||||
abstract-logging@2.0.1:
|
abstract-logging@2.0.1:
|
||||||
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
|
resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==}
|
||||||
|
|
||||||
|
acorn-jsx@5.3.2:
|
||||||
|
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
|
||||||
|
peerDependencies:
|
||||||
|
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
|
||||||
|
|
||||||
|
acorn@8.16.0:
|
||||||
|
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
ajv-formats@3.0.1:
|
ajv-formats@3.0.1:
|
||||||
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -460,6 +593,9 @@ packages:
|
|||||||
ajv:
|
ajv:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
ajv@6.14.0:
|
||||||
|
resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==}
|
||||||
|
|
||||||
ajv@8.18.0:
|
ajv@8.18.0:
|
||||||
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
|
||||||
|
|
||||||
@@ -542,6 +678,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
deep-is@0.1.4:
|
||||||
|
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||||
|
|
||||||
depd@2.0.0:
|
depd@2.0.0:
|
||||||
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@@ -564,9 +703,61 @@ packages:
|
|||||||
escape-html@1.0.3:
|
escape-html@1.0.3:
|
||||||
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
|
||||||
|
|
||||||
|
escape-string-regexp@4.0.0:
|
||||||
|
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
eslint-config-prettier@10.1.8:
|
||||||
|
resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
eslint: '>=7.0.0'
|
||||||
|
|
||||||
|
eslint-scope@9.1.2:
|
||||||
|
resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
|
eslint-visitor-keys@3.4.3:
|
||||||
|
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
|
||||||
|
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
||||||
|
|
||||||
|
eslint-visitor-keys@5.0.1:
|
||||||
|
resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
|
eslint@10.0.3:
|
||||||
|
resolution: {integrity: sha512-COV33RzXZkqhG9P2rZCFl9ZmJ7WL+gQSCRzE7RhkbclbQPtLAWReL7ysA0Sh4c8Im2U9ynybdR56PV0XcKvqaQ==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
jiti: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
jiti:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
espree@11.2.0:
|
||||||
|
resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==}
|
||||||
|
engines: {node: ^20.19.0 || ^22.13.0 || >=24}
|
||||||
|
|
||||||
|
esquery@1.7.0:
|
||||||
|
resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==}
|
||||||
|
engines: {node: '>=0.10'}
|
||||||
|
|
||||||
|
esrecurse@4.3.0:
|
||||||
|
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
|
||||||
|
estraverse@5.3.0:
|
||||||
|
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
|
||||||
estree-walker@3.0.3:
|
estree-walker@3.0.3:
|
||||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||||
|
|
||||||
|
esutils@2.0.3:
|
||||||
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
execa@9.6.1:
|
execa@9.6.1:
|
||||||
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
|
resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==}
|
||||||
engines: {node: ^18.19.0 || >=20.5.0}
|
engines: {node: ^18.19.0 || >=20.5.0}
|
||||||
@@ -581,9 +772,15 @@ packages:
|
|||||||
fast-deep-equal@3.1.3:
|
fast-deep-equal@3.1.3:
|
||||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||||
|
|
||||||
|
fast-json-stable-stringify@2.1.0:
|
||||||
|
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||||
|
|
||||||
fast-json-stringify@6.3.0:
|
fast-json-stringify@6.3.0:
|
||||||
resolution: {integrity: sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==}
|
resolution: {integrity: sha512-oRCntNDY/329HJPlmdNLIdogNtt6Vyjb1WuT01Soss3slIdyUp8kAcDU3saQTOquEK8KFVfwIIF7FebxUAu+yA==}
|
||||||
|
|
||||||
|
fast-levenshtein@2.0.6:
|
||||||
|
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
|
||||||
|
|
||||||
fast-querystring@1.1.2:
|
fast-querystring@1.1.2:
|
||||||
resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==}
|
resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==}
|
||||||
|
|
||||||
@@ -615,10 +812,25 @@ packages:
|
|||||||
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
file-entry-cache@8.0.0:
|
||||||
|
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
find-my-way@9.5.0:
|
find-my-way@9.5.0:
|
||||||
resolution: {integrity: sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==}
|
resolution: {integrity: sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
|
find-up@5.0.0:
|
||||||
|
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
flat-cache@4.0.1:
|
||||||
|
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
flatted@3.4.2:
|
||||||
|
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||||
|
|
||||||
fn.name@1.1.0:
|
fn.name@1.1.0:
|
||||||
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
||||||
|
|
||||||
@@ -638,6 +850,10 @@ packages:
|
|||||||
get-tsconfig@4.13.6:
|
get-tsconfig@4.13.6:
|
||||||
resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
|
resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==}
|
||||||
|
|
||||||
|
glob-parent@6.0.2:
|
||||||
|
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
glob@11.1.0:
|
glob@11.1.0:
|
||||||
resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
|
resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
|
||||||
engines: {node: 20 || >=22}
|
engines: {node: 20 || >=22}
|
||||||
@@ -656,6 +872,18 @@ packages:
|
|||||||
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==}
|
||||||
engines: {node: '>=18.18.0'}
|
engines: {node: '>=18.18.0'}
|
||||||
|
|
||||||
|
ignore@5.3.2:
|
||||||
|
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
|
||||||
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
ignore@7.0.5:
|
||||||
|
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
|
||||||
|
engines: {node: '>= 4'}
|
||||||
|
|
||||||
|
imurmurhash@0.1.4:
|
||||||
|
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
|
||||||
|
engines: {node: '>=0.8.19'}
|
||||||
|
|
||||||
inherits@2.0.4:
|
inherits@2.0.4:
|
||||||
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
|
||||||
|
|
||||||
@@ -663,6 +891,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
|
resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
||||||
|
is-extglob@2.1.1:
|
||||||
|
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
is-glob@4.0.3:
|
||||||
|
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
is-plain-obj@4.1.0:
|
is-plain-obj@4.1.0:
|
||||||
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -689,18 +925,38 @@ packages:
|
|||||||
js-tokens@9.0.1:
|
js-tokens@9.0.1:
|
||||||
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
|
||||||
|
|
||||||
|
json-buffer@3.0.1:
|
||||||
|
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
|
||||||
|
|
||||||
json-schema-ref-resolver@3.0.0:
|
json-schema-ref-resolver@3.0.0:
|
||||||
resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==}
|
resolution: {integrity: sha512-hOrZIVL5jyYFjzk7+y7n5JDzGlU8rfWDuYyHwGa2WA8/pcmMHezp2xsVwxrebD/Q9t8Nc5DboieySDpCp4WG4A==}
|
||||||
|
|
||||||
|
json-schema-traverse@0.4.1:
|
||||||
|
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
|
||||||
|
|
||||||
json-schema-traverse@1.0.0:
|
json-schema-traverse@1.0.0:
|
||||||
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
|
||||||
|
|
||||||
|
json-stable-stringify-without-jsonify@1.0.1:
|
||||||
|
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||||
|
|
||||||
|
keyv@4.5.4:
|
||||||
|
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
|
||||||
|
|
||||||
kuler@2.0.0:
|
kuler@2.0.0:
|
||||||
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
|
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
|
||||||
|
|
||||||
|
levn@0.4.1:
|
||||||
|
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
light-my-request@6.6.0:
|
light-my-request@6.6.0:
|
||||||
resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==}
|
resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==}
|
||||||
|
|
||||||
|
locate-path@6.0.0:
|
||||||
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
logform@2.7.0:
|
logform@2.7.0:
|
||||||
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
|
resolution: {integrity: sha512-TFYA4jnP7PVbmlBIfhlSe+WKxs9dklXMTEGcBCIvLhE/Tn3H6Gk1norupVW7m5Cnd4bLcr08AytbyV/xj7f/kQ==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
@@ -736,6 +992,9 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
natural-compare@1.4.0:
|
||||||
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
|
|
||||||
npm-run-path@6.0.0:
|
npm-run-path@6.0.0:
|
||||||
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
|
resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -747,6 +1006,18 @@ packages:
|
|||||||
one-time@1.0.0:
|
one-time@1.0.0:
|
||||||
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
|
resolution: {integrity: sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==}
|
||||||
|
|
||||||
|
optionator@0.9.4:
|
||||||
|
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
p-limit@3.1.0:
|
||||||
|
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
p-locate@5.0.0:
|
||||||
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
package-json-from-dist@1.0.1:
|
package-json-from-dist@1.0.1:
|
||||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||||
|
|
||||||
@@ -754,6 +1025,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
|
resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
path-exists@4.0.0:
|
||||||
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
path-key@3.1.1:
|
path-key@3.1.1:
|
||||||
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -794,6 +1069,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
prelude-ls@1.2.1:
|
||||||
|
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
pretty-ms@9.3.0:
|
pretty-ms@9.3.0:
|
||||||
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
|
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -804,6 +1083,10 @@ packages:
|
|||||||
process-warning@5.0.0:
|
process-warning@5.0.0:
|
||||||
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
||||||
|
|
||||||
|
punycode@2.3.1:
|
||||||
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
quick-format-unescaped@4.0.4:
|
quick-format-unescaped@4.0.4:
|
||||||
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||||
|
|
||||||
@@ -958,11 +1241,21 @@ packages:
|
|||||||
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
|
resolution: {integrity: sha512-aZbgViZrg1QNcG+LULa7nhZpJTZSLm/mXnHXnbAbjmN5aSa0y7V+wvv6+4WaBtpISJzThKy+PIPxc1Nq1EJ9mg==}
|
||||||
engines: {node: '>= 14.0.0'}
|
engines: {node: '>= 14.0.0'}
|
||||||
|
|
||||||
|
ts-api-utils@2.4.0:
|
||||||
|
resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==}
|
||||||
|
engines: {node: '>=18.12'}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.8.4'
|
||||||
|
|
||||||
tsx@4.21.0:
|
tsx@4.21.0:
|
||||||
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==}
|
||||||
engines: {node: '>=18.0.0'}
|
engines: {node: '>=18.0.0'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
type-check@0.4.0:
|
||||||
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
typescript@5.9.3:
|
typescript@5.9.3:
|
||||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
engines: {node: '>=14.17'}
|
engines: {node: '>=14.17'}
|
||||||
@@ -975,6 +1268,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
|
resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
uri-js@4.4.1:
|
||||||
|
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
|
||||||
|
|
||||||
util-deprecate@1.0.2:
|
util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
@@ -1069,6 +1365,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==}
|
resolution: {integrity: sha512-LZNJgPzfKR+/J3cHkxcpHKpKKvGfDZVPS4hfJCc4cCG0CgYzvlD6yE/S3CIL/Yt91ak327YCpiF/0MyeZHEHKA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
|
|
||||||
|
word-wrap@1.2.5:
|
||||||
|
resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
yocto-queue@0.1.0:
|
||||||
|
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
yoctocolors@2.1.2:
|
yoctocolors@2.1.2:
|
||||||
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
|
resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -1161,6 +1465,36 @@ snapshots:
|
|||||||
'@esbuild/win32-x64@0.27.4':
|
'@esbuild/win32-x64@0.27.4':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@eslint-community/eslint-utils@4.9.1(eslint@10.0.3)':
|
||||||
|
dependencies:
|
||||||
|
eslint: 10.0.3
|
||||||
|
eslint-visitor-keys: 3.4.3
|
||||||
|
|
||||||
|
'@eslint-community/regexpp@4.12.2': {}
|
||||||
|
|
||||||
|
'@eslint/config-array@0.23.3':
|
||||||
|
dependencies:
|
||||||
|
'@eslint/object-schema': 3.0.3
|
||||||
|
debug: 4.4.3
|
||||||
|
minimatch: 10.2.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@eslint/config-helpers@0.5.3':
|
||||||
|
dependencies:
|
||||||
|
'@eslint/core': 1.1.1
|
||||||
|
|
||||||
|
'@eslint/core@1.1.1':
|
||||||
|
dependencies:
|
||||||
|
'@types/json-schema': 7.0.15
|
||||||
|
|
||||||
|
'@eslint/object-schema@3.0.3': {}
|
||||||
|
|
||||||
|
'@eslint/plugin-kit@0.6.1':
|
||||||
|
dependencies:
|
||||||
|
'@eslint/core': 1.1.1
|
||||||
|
levn: 0.4.1
|
||||||
|
|
||||||
'@fastify/accept-negotiator@2.0.1': {}
|
'@fastify/accept-negotiator@2.0.1': {}
|
||||||
|
|
||||||
'@fastify/ajv-compiler@4.0.5':
|
'@fastify/ajv-compiler@4.0.5':
|
||||||
@@ -1203,6 +1537,17 @@ snapshots:
|
|||||||
fastq: 1.20.1
|
fastq: 1.20.1
|
||||||
glob: 11.1.0
|
glob: 11.1.0
|
||||||
|
|
||||||
|
'@humanfs/core@0.19.1': {}
|
||||||
|
|
||||||
|
'@humanfs/node@0.16.7':
|
||||||
|
dependencies:
|
||||||
|
'@humanfs/core': 0.19.1
|
||||||
|
'@humanwhocodes/retry': 0.4.3
|
||||||
|
|
||||||
|
'@humanwhocodes/module-importer@1.0.1': {}
|
||||||
|
|
||||||
|
'@humanwhocodes/retry@0.4.3': {}
|
||||||
|
|
||||||
'@isaacs/cliui@9.0.0': {}
|
'@isaacs/cliui@9.0.0': {}
|
||||||
|
|
||||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||||
@@ -1302,14 +1647,109 @@ snapshots:
|
|||||||
|
|
||||||
'@types/deep-eql@4.0.2': {}
|
'@types/deep-eql@4.0.2': {}
|
||||||
|
|
||||||
|
'@types/esrecurse@4.3.1': {}
|
||||||
|
|
||||||
'@types/estree@1.0.8': {}
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
'@types/node@22.19.15':
|
'@types/node@22.19.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 6.21.0
|
undici-types: 6.21.0
|
||||||
|
|
||||||
'@types/triple-beam@1.3.5': {}
|
'@types/triple-beam@1.3.5': {}
|
||||||
|
|
||||||
|
'@typescript-eslint/eslint-plugin@8.57.1(@typescript-eslint/parser@8.57.1(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@eslint-community/regexpp': 4.12.2
|
||||||
|
'@typescript-eslint/parser': 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/scope-manager': 8.57.1
|
||||||
|
'@typescript-eslint/type-utils': 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/utils': 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/visitor-keys': 8.57.1
|
||||||
|
eslint: 10.0.3
|
||||||
|
ignore: 7.0.5
|
||||||
|
natural-compare: 1.4.0
|
||||||
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/parser@8.57.1(eslint@10.0.3)(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/scope-manager': 8.57.1
|
||||||
|
'@typescript-eslint/types': 8.57.1
|
||||||
|
'@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/visitor-keys': 8.57.1
|
||||||
|
debug: 4.4.3
|
||||||
|
eslint: 10.0.3
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/project-service@8.57.1(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/types': 8.57.1
|
||||||
|
debug: 4.4.3
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/scope-manager@8.57.1':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.57.1
|
||||||
|
'@typescript-eslint/visitor-keys': 8.57.1
|
||||||
|
|
||||||
|
'@typescript-eslint/tsconfig-utils@8.57.1(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
'@typescript-eslint/type-utils@8.57.1(eslint@10.0.3)(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.57.1
|
||||||
|
'@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/utils': 8.57.1(eslint@10.0.3)(typescript@5.9.3)
|
||||||
|
debug: 4.4.3
|
||||||
|
eslint: 10.0.3
|
||||||
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/types@8.57.1': {}
|
||||||
|
|
||||||
|
'@typescript-eslint/typescript-estree@8.57.1(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/project-service': 8.57.1(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/tsconfig-utils': 8.57.1(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/types': 8.57.1
|
||||||
|
'@typescript-eslint/visitor-keys': 8.57.1
|
||||||
|
debug: 4.4.3
|
||||||
|
minimatch: 10.2.4
|
||||||
|
semver: 7.7.4
|
||||||
|
tinyglobby: 0.2.15
|
||||||
|
ts-api-utils: 2.4.0(typescript@5.9.3)
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/utils@8.57.1(eslint@10.0.3)(typescript@5.9.3)':
|
||||||
|
dependencies:
|
||||||
|
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3)
|
||||||
|
'@typescript-eslint/scope-manager': 8.57.1
|
||||||
|
'@typescript-eslint/types': 8.57.1
|
||||||
|
'@typescript-eslint/typescript-estree': 8.57.1(typescript@5.9.3)
|
||||||
|
eslint: 10.0.3
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@typescript-eslint/visitor-keys@8.57.1':
|
||||||
|
dependencies:
|
||||||
|
'@typescript-eslint/types': 8.57.1
|
||||||
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
'@vitest/expect@3.2.4':
|
'@vitest/expect@3.2.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/chai': 5.2.3
|
'@types/chai': 5.2.3
|
||||||
@@ -1354,10 +1794,23 @@ snapshots:
|
|||||||
|
|
||||||
abstract-logging@2.0.1: {}
|
abstract-logging@2.0.1: {}
|
||||||
|
|
||||||
|
acorn-jsx@5.3.2(acorn@8.16.0):
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.16.0
|
||||||
|
|
||||||
|
acorn@8.16.0: {}
|
||||||
|
|
||||||
ajv-formats@3.0.1(ajv@8.18.0):
|
ajv-formats@3.0.1(ajv@8.18.0):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ajv: 8.18.0
|
ajv: 8.18.0
|
||||||
|
|
||||||
|
ajv@6.14.0:
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal: 3.1.3
|
||||||
|
fast-json-stable-stringify: 2.1.0
|
||||||
|
json-schema-traverse: 0.4.1
|
||||||
|
uri-js: 4.4.1
|
||||||
|
|
||||||
ajv@8.18.0:
|
ajv@8.18.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
@@ -1429,6 +1882,8 @@ snapshots:
|
|||||||
|
|
||||||
deep-eql@5.0.2: {}
|
deep-eql@5.0.2: {}
|
||||||
|
|
||||||
|
deep-is@0.1.4: {}
|
||||||
|
|
||||||
depd@2.0.0: {}
|
depd@2.0.0: {}
|
||||||
|
|
||||||
dequal@2.0.3: {}
|
dequal@2.0.3: {}
|
||||||
@@ -1468,10 +1923,80 @@ snapshots:
|
|||||||
|
|
||||||
escape-html@1.0.3: {}
|
escape-html@1.0.3: {}
|
||||||
|
|
||||||
|
escape-string-regexp@4.0.0: {}
|
||||||
|
|
||||||
|
eslint-config-prettier@10.1.8(eslint@10.0.3):
|
||||||
|
dependencies:
|
||||||
|
eslint: 10.0.3
|
||||||
|
|
||||||
|
eslint-scope@9.1.2:
|
||||||
|
dependencies:
|
||||||
|
'@types/esrecurse': 4.3.1
|
||||||
|
'@types/estree': 1.0.8
|
||||||
|
esrecurse: 4.3.0
|
||||||
|
estraverse: 5.3.0
|
||||||
|
|
||||||
|
eslint-visitor-keys@3.4.3: {}
|
||||||
|
|
||||||
|
eslint-visitor-keys@5.0.1: {}
|
||||||
|
|
||||||
|
eslint@10.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@eslint-community/eslint-utils': 4.9.1(eslint@10.0.3)
|
||||||
|
'@eslint-community/regexpp': 4.12.2
|
||||||
|
'@eslint/config-array': 0.23.3
|
||||||
|
'@eslint/config-helpers': 0.5.3
|
||||||
|
'@eslint/core': 1.1.1
|
||||||
|
'@eslint/plugin-kit': 0.6.1
|
||||||
|
'@humanfs/node': 0.16.7
|
||||||
|
'@humanwhocodes/module-importer': 1.0.1
|
||||||
|
'@humanwhocodes/retry': 0.4.3
|
||||||
|
'@types/estree': 1.0.8
|
||||||
|
ajv: 6.14.0
|
||||||
|
cross-spawn: 7.0.6
|
||||||
|
debug: 4.4.3
|
||||||
|
escape-string-regexp: 4.0.0
|
||||||
|
eslint-scope: 9.1.2
|
||||||
|
eslint-visitor-keys: 5.0.1
|
||||||
|
espree: 11.2.0
|
||||||
|
esquery: 1.7.0
|
||||||
|
esutils: 2.0.3
|
||||||
|
fast-deep-equal: 3.1.3
|
||||||
|
file-entry-cache: 8.0.0
|
||||||
|
find-up: 5.0.0
|
||||||
|
glob-parent: 6.0.2
|
||||||
|
ignore: 5.3.2
|
||||||
|
imurmurhash: 0.1.4
|
||||||
|
is-glob: 4.0.3
|
||||||
|
json-stable-stringify-without-jsonify: 1.0.1
|
||||||
|
minimatch: 10.2.4
|
||||||
|
natural-compare: 1.4.0
|
||||||
|
optionator: 0.9.4
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
espree@11.2.0:
|
||||||
|
dependencies:
|
||||||
|
acorn: 8.16.0
|
||||||
|
acorn-jsx: 5.3.2(acorn@8.16.0)
|
||||||
|
eslint-visitor-keys: 5.0.1
|
||||||
|
|
||||||
|
esquery@1.7.0:
|
||||||
|
dependencies:
|
||||||
|
estraverse: 5.3.0
|
||||||
|
|
||||||
|
esrecurse@4.3.0:
|
||||||
|
dependencies:
|
||||||
|
estraverse: 5.3.0
|
||||||
|
|
||||||
|
estraverse@5.3.0: {}
|
||||||
|
|
||||||
estree-walker@3.0.3:
|
estree-walker@3.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
|
|
||||||
|
esutils@2.0.3: {}
|
||||||
|
|
||||||
execa@9.6.1:
|
execa@9.6.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@sindresorhus/merge-streams': 4.0.0
|
'@sindresorhus/merge-streams': 4.0.0
|
||||||
@@ -1493,6 +2018,8 @@ snapshots:
|
|||||||
|
|
||||||
fast-deep-equal@3.1.3: {}
|
fast-deep-equal@3.1.3: {}
|
||||||
|
|
||||||
|
fast-json-stable-stringify@2.1.0: {}
|
||||||
|
|
||||||
fast-json-stringify@6.3.0:
|
fast-json-stringify@6.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@fastify/merge-json-schemas': 0.2.1
|
'@fastify/merge-json-schemas': 0.2.1
|
||||||
@@ -1502,6 +2029,8 @@ snapshots:
|
|||||||
json-schema-ref-resolver: 3.0.0
|
json-schema-ref-resolver: 3.0.0
|
||||||
rfdc: 1.4.1
|
rfdc: 1.4.1
|
||||||
|
|
||||||
|
fast-levenshtein@2.0.6: {}
|
||||||
|
|
||||||
fast-querystring@1.1.2:
|
fast-querystring@1.1.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-decode-uri-component: 1.0.1
|
fast-decode-uri-component: 1.0.1
|
||||||
@@ -1542,12 +2071,28 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-unicode-supported: 2.1.0
|
is-unicode-supported: 2.1.0
|
||||||
|
|
||||||
|
file-entry-cache@8.0.0:
|
||||||
|
dependencies:
|
||||||
|
flat-cache: 4.0.1
|
||||||
|
|
||||||
find-my-way@9.5.0:
|
find-my-way@9.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
fast-deep-equal: 3.1.3
|
fast-deep-equal: 3.1.3
|
||||||
fast-querystring: 1.1.2
|
fast-querystring: 1.1.2
|
||||||
safe-regex2: 5.1.0
|
safe-regex2: 5.1.0
|
||||||
|
|
||||||
|
find-up@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
locate-path: 6.0.0
|
||||||
|
path-exists: 4.0.0
|
||||||
|
|
||||||
|
flat-cache@4.0.1:
|
||||||
|
dependencies:
|
||||||
|
flatted: 3.4.2
|
||||||
|
keyv: 4.5.4
|
||||||
|
|
||||||
|
flatted@3.4.2: {}
|
||||||
|
|
||||||
fn.name@1.1.0: {}
|
fn.name@1.1.0: {}
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
foreground-child@3.3.1:
|
||||||
@@ -1567,6 +2112,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
resolve-pkg-maps: 1.0.0
|
resolve-pkg-maps: 1.0.0
|
||||||
|
|
||||||
|
glob-parent@6.0.2:
|
||||||
|
dependencies:
|
||||||
|
is-glob: 4.0.3
|
||||||
|
|
||||||
glob@11.1.0:
|
glob@11.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
foreground-child: 3.3.1
|
foreground-child: 3.3.1
|
||||||
@@ -1592,10 +2141,22 @@ snapshots:
|
|||||||
|
|
||||||
human-signals@8.0.1: {}
|
human-signals@8.0.1: {}
|
||||||
|
|
||||||
|
ignore@5.3.2: {}
|
||||||
|
|
||||||
|
ignore@7.0.5: {}
|
||||||
|
|
||||||
|
imurmurhash@0.1.4: {}
|
||||||
|
|
||||||
inherits@2.0.4: {}
|
inherits@2.0.4: {}
|
||||||
|
|
||||||
ipaddr.js@2.3.0: {}
|
ipaddr.js@2.3.0: {}
|
||||||
|
|
||||||
|
is-extglob@2.1.1: {}
|
||||||
|
|
||||||
|
is-glob@4.0.3:
|
||||||
|
dependencies:
|
||||||
|
is-extglob: 2.1.1
|
||||||
|
|
||||||
is-plain-obj@4.1.0: {}
|
is-plain-obj@4.1.0: {}
|
||||||
|
|
||||||
is-stream@2.0.1: {}
|
is-stream@2.0.1: {}
|
||||||
@@ -1612,20 +2173,39 @@ snapshots:
|
|||||||
|
|
||||||
js-tokens@9.0.1: {}
|
js-tokens@9.0.1: {}
|
||||||
|
|
||||||
|
json-buffer@3.0.1: {}
|
||||||
|
|
||||||
json-schema-ref-resolver@3.0.0:
|
json-schema-ref-resolver@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
dequal: 2.0.3
|
dequal: 2.0.3
|
||||||
|
|
||||||
|
json-schema-traverse@0.4.1: {}
|
||||||
|
|
||||||
json-schema-traverse@1.0.0: {}
|
json-schema-traverse@1.0.0: {}
|
||||||
|
|
||||||
|
json-stable-stringify-without-jsonify@1.0.1: {}
|
||||||
|
|
||||||
|
keyv@4.5.4:
|
||||||
|
dependencies:
|
||||||
|
json-buffer: 3.0.1
|
||||||
|
|
||||||
kuler@2.0.0: {}
|
kuler@2.0.0: {}
|
||||||
|
|
||||||
|
levn@0.4.1:
|
||||||
|
dependencies:
|
||||||
|
prelude-ls: 1.2.1
|
||||||
|
type-check: 0.4.0
|
||||||
|
|
||||||
light-my-request@6.6.0:
|
light-my-request@6.6.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
cookie: 1.1.1
|
cookie: 1.1.1
|
||||||
process-warning: 4.0.1
|
process-warning: 4.0.1
|
||||||
set-cookie-parser: 2.7.2
|
set-cookie-parser: 2.7.2
|
||||||
|
|
||||||
|
locate-path@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
p-locate: 5.0.0
|
||||||
|
|
||||||
logform@2.7.0:
|
logform@2.7.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@colors/colors': 1.6.0
|
'@colors/colors': 1.6.0
|
||||||
@@ -1655,6 +2235,8 @@ snapshots:
|
|||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
|
natural-compare@1.4.0: {}
|
||||||
|
|
||||||
npm-run-path@6.0.0:
|
npm-run-path@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
path-key: 4.0.0
|
path-key: 4.0.0
|
||||||
@@ -1666,10 +2248,29 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
fn.name: 1.1.0
|
fn.name: 1.1.0
|
||||||
|
|
||||||
|
optionator@0.9.4:
|
||||||
|
dependencies:
|
||||||
|
deep-is: 0.1.4
|
||||||
|
fast-levenshtein: 2.0.6
|
||||||
|
levn: 0.4.1
|
||||||
|
prelude-ls: 1.2.1
|
||||||
|
type-check: 0.4.0
|
||||||
|
word-wrap: 1.2.5
|
||||||
|
|
||||||
|
p-limit@3.1.0:
|
||||||
|
dependencies:
|
||||||
|
yocto-queue: 0.1.0
|
||||||
|
|
||||||
|
p-locate@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
p-limit: 3.1.0
|
||||||
|
|
||||||
package-json-from-dist@1.0.1: {}
|
package-json-from-dist@1.0.1: {}
|
||||||
|
|
||||||
parse-ms@4.0.0: {}
|
parse-ms@4.0.0: {}
|
||||||
|
|
||||||
|
path-exists@4.0.0: {}
|
||||||
|
|
||||||
path-key@3.1.1: {}
|
path-key@3.1.1: {}
|
||||||
|
|
||||||
path-key@4.0.0: {}
|
path-key@4.0.0: {}
|
||||||
@@ -1713,6 +2314,8 @@ snapshots:
|
|||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
source-map-js: 1.2.1
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
prelude-ls@1.2.1: {}
|
||||||
|
|
||||||
pretty-ms@9.3.0:
|
pretty-ms@9.3.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
parse-ms: 4.0.0
|
parse-ms: 4.0.0
|
||||||
@@ -1721,6 +2324,8 @@ snapshots:
|
|||||||
|
|
||||||
process-warning@5.0.0: {}
|
process-warning@5.0.0: {}
|
||||||
|
|
||||||
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
quick-format-unescaped@4.0.4: {}
|
quick-format-unescaped@4.0.4: {}
|
||||||
|
|
||||||
readable-stream@3.6.2:
|
readable-stream@3.6.2:
|
||||||
@@ -1856,6 +2461,10 @@ snapshots:
|
|||||||
|
|
||||||
triple-beam@1.4.1: {}
|
triple-beam@1.4.1: {}
|
||||||
|
|
||||||
|
ts-api-utils@2.4.0(typescript@5.9.3):
|
||||||
|
dependencies:
|
||||||
|
typescript: 5.9.3
|
||||||
|
|
||||||
tsx@4.21.0:
|
tsx@4.21.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.27.4
|
esbuild: 0.27.4
|
||||||
@@ -1863,12 +2472,20 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
type-check@0.4.0:
|
||||||
|
dependencies:
|
||||||
|
prelude-ls: 1.2.1
|
||||||
|
|
||||||
typescript@5.9.3: {}
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
undici-types@6.21.0: {}
|
undici-types@6.21.0: {}
|
||||||
|
|
||||||
unicorn-magic@0.3.0: {}
|
unicorn-magic@0.3.0: {}
|
||||||
|
|
||||||
|
uri-js@4.4.1:
|
||||||
|
dependencies:
|
||||||
|
punycode: 2.3.1
|
||||||
|
|
||||||
util-deprecate@1.0.2: {}
|
util-deprecate@1.0.2: {}
|
||||||
|
|
||||||
vite-node@3.2.4(@types/node@22.19.15)(tsx@4.21.0):
|
vite-node@3.2.4(@types/node@22.19.15)(tsx@4.21.0):
|
||||||
@@ -1975,4 +2592,8 @@ snapshots:
|
|||||||
triple-beam: 1.4.1
|
triple-beam: 1.4.1
|
||||||
winston-transport: 4.9.0
|
winston-transport: 4.9.0
|
||||||
|
|
||||||
|
word-wrap@1.2.5: {}
|
||||||
|
|
||||||
|
yocto-queue@0.1.0: {}
|
||||||
|
|
||||||
yoctocolors@2.1.2: {}
|
yoctocolors@2.1.2: {}
|
||||||
|
|||||||
43
bastion/scripts/build-bastion.sh
Executable file
43
bastion/scripts/build-bastion.sh
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Build bastion container image and push to Gitea container registry
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Load .env for GITEA_TOKEN
|
||||||
|
if [ -f .env ]; then
|
||||||
|
set -a; source .env; set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Registry defaults to internal address (external proxy has body size limit)
|
||||||
|
REGISTRY="${GITEA_REGISTRY:-mysources.co.uk}"
|
||||||
|
IMAGE="lab-bastion"
|
||||||
|
VERSION=$(node -p "require('./package.json').version")
|
||||||
|
TAG="${1:-$VERSION}"
|
||||||
|
|
||||||
|
echo "==> Building bastion image (tag: $TAG)..."
|
||||||
|
podman build -t "$IMAGE:$TAG" -f stack/Dockerfile .
|
||||||
|
|
||||||
|
echo "==> Tagging as $REGISTRY/michal/$IMAGE:$TAG..."
|
||||||
|
podman tag "$IMAGE:$TAG" "$REGISTRY/michal/$IMAGE:$TAG"
|
||||||
|
|
||||||
|
if [ -n "$GITEA_TOKEN" ]; then
|
||||||
|
echo "==> Logging in to $REGISTRY..."
|
||||||
|
podman login --tls-verify=false -u michal -p "$GITEA_TOKEN" "$REGISTRY"
|
||||||
|
|
||||||
|
echo "==> Pushing to $REGISTRY/michal/$IMAGE:$TAG..."
|
||||||
|
podman push --tls-verify=false "$REGISTRY/michal/$IMAGE:$TAG"
|
||||||
|
|
||||||
|
# Ensure package is linked to the repository
|
||||||
|
if [ -f "$SCRIPT_DIR/link-package.sh" ]; then
|
||||||
|
source "$SCRIPT_DIR/link-package.sh"
|
||||||
|
link_package "container" "$IMAGE"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "==> GITEA_TOKEN not set, skipping push."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Done!"
|
||||||
|
echo " Image: $REGISTRY/michal/$IMAGE:$TAG"
|
||||||
47
bastion/scripts/build-rpm.sh
Executable file
47
bastion/scripts/build-rpm.sh
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Load .env if present
|
||||||
|
if [ -f .env ]; then
|
||||||
|
set -a; source .env; set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ensure tools are on PATH
|
||||||
|
export PATH="$HOME/.npm-global/bin:$HOME/.bun/bin:$HOME/.local/bin:$PATH"
|
||||||
|
|
||||||
|
echo "==> Running unit tests..."
|
||||||
|
pnpm test:run
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "==> Building TypeScript..."
|
||||||
|
pnpm build
|
||||||
|
|
||||||
|
echo "==> Generating shell completions..."
|
||||||
|
pnpm completions:generate
|
||||||
|
|
||||||
|
echo "==> Bundling standalone binary..."
|
||||||
|
mkdir -p dist
|
||||||
|
rm -f dist/lab dist/lab-*.rpm dist/lab*.deb
|
||||||
|
|
||||||
|
bun build src/cli/src/index.ts --compile --outfile dist/lab
|
||||||
|
|
||||||
|
echo "==> Packaging RPM..."
|
||||||
|
nfpm pkg --packager rpm --target dist/
|
||||||
|
|
||||||
|
RPM_FILE=$(ls dist/lab-*.rpm 2>/dev/null | head -1)
|
||||||
|
echo "==> Built: $RPM_FILE"
|
||||||
|
echo " Size: $(du -h "$RPM_FILE" | cut -f1)"
|
||||||
|
rpm -qpi "$RPM_FILE"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Packaging DEB..."
|
||||||
|
rm -f dist/lab*.deb
|
||||||
|
nfpm pkg --packager deb --target dist/
|
||||||
|
|
||||||
|
DEB_FILE=$(ls dist/lab*.deb 2>/dev/null | head -1)
|
||||||
|
echo "==> Built: $DEB_FILE"
|
||||||
|
echo " Size: $(du -h "$DEB_FILE" | cut -f1)"
|
||||||
385
bastion/scripts/generate-completions.ts
Normal file
385
bastion/scripts/generate-completions.ts
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
#!/usr/bin/env tsx
|
||||||
|
/**
|
||||||
|
* generate-completions.ts -- auto-generates shell completions from the commander.js command tree.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* tsx scripts/generate-completions.ts # print generated files to stdout
|
||||||
|
* tsx scripts/generate-completions.ts --write # write completions/ files
|
||||||
|
* tsx scripts/generate-completions.ts --check # exit 0 if files match, 1 if stale
|
||||||
|
*
|
||||||
|
* Requires `pnpm build` to have run first (workspace packages must be compiled).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Command, type Option, type Argument } from 'commander';
|
||||||
|
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||||
|
import { join, dirname } from 'node:path';
|
||||||
|
import { fileURLToPath } from 'node:url';
|
||||||
|
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||||
|
const ROOT = join(__dirname, '..');
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Command tree extraction
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
interface CmdInfo {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
hidden: boolean;
|
||||||
|
options: OptInfo[];
|
||||||
|
args: ArgInfo[];
|
||||||
|
subcommands: CmdInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OptInfo {
|
||||||
|
short?: string;
|
||||||
|
long: string;
|
||||||
|
description: string;
|
||||||
|
takesValue: boolean;
|
||||||
|
choices?: string[];
|
||||||
|
negate: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ArgInfo {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
required: boolean;
|
||||||
|
variadic: boolean;
|
||||||
|
choices?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractOption(opt: Option): OptInfo {
|
||||||
|
return {
|
||||||
|
short: (opt as unknown as Record<string, string>).short || undefined,
|
||||||
|
long: (opt as unknown as Record<string, string>).long,
|
||||||
|
description: opt.description,
|
||||||
|
takesValue: (opt as unknown as Record<string, boolean>).required || (opt as unknown as Record<string, boolean>).optional || false,
|
||||||
|
choices: (opt as unknown as Record<string, string[] | undefined>).argChoices || undefined,
|
||||||
|
negate: (opt as unknown as Record<string, boolean>).negate || false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractArgument(arg: Argument): ArgInfo {
|
||||||
|
return {
|
||||||
|
name: (arg as unknown as Record<string, string>)._name ?? arg.name(),
|
||||||
|
description: arg.description,
|
||||||
|
required: (arg as unknown as Record<string, boolean>).required,
|
||||||
|
variadic: (arg as unknown as Record<string, boolean>).variadic,
|
||||||
|
choices: (arg as unknown as Record<string, string[] | undefined>)._choices || undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractCommand(cmd: Command): CmdInfo {
|
||||||
|
const options = (cmd.options as Option[])
|
||||||
|
.filter((o) => {
|
||||||
|
const long = (o as unknown as Record<string, string>).long;
|
||||||
|
return long !== '--help' && long !== '--version';
|
||||||
|
})
|
||||||
|
.map(extractOption);
|
||||||
|
|
||||||
|
const args = ((cmd as unknown as Record<string, Argument[]>).registeredArguments ?? [])
|
||||||
|
.map(extractArgument);
|
||||||
|
|
||||||
|
const subcommands = (cmd.commands as Command[])
|
||||||
|
.filter((sub) => sub.name() !== 'help')
|
||||||
|
.map(extractCommand);
|
||||||
|
|
||||||
|
if ((cmd.commands as Command[]).some((sub) => sub.name() === 'help')) {
|
||||||
|
subcommands.push({
|
||||||
|
name: 'help',
|
||||||
|
description: 'display help for command',
|
||||||
|
hidden: false,
|
||||||
|
options: [],
|
||||||
|
args: [],
|
||||||
|
subcommands: [],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: cmd.name(),
|
||||||
|
description: cmd.description(),
|
||||||
|
hidden: (cmd as unknown as Record<string, boolean>)._hidden ?? false,
|
||||||
|
options,
|
||||||
|
args,
|
||||||
|
subcommands,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function extractTree(): Promise<CmdInfo> {
|
||||||
|
const { createProgram } = await import('../src/cli/src/index.js') as { createProgram: () => Command };
|
||||||
|
const program = createProgram();
|
||||||
|
return extractCommand(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Utilities
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
function esc(s: string): string {
|
||||||
|
return s.replace(/'/g, "\\'");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Collect all commands recursively with their full path. */
|
||||||
|
function collectCommands(cmd: CmdInfo, prefix: string[] = []): { path: string[]; cmd: CmdInfo }[] {
|
||||||
|
const result: { path: string[]; cmd: CmdInfo }[] = [];
|
||||||
|
for (const sub of cmd.subcommands) {
|
||||||
|
const fullPath = [...prefix, sub.name];
|
||||||
|
result.push({ path: fullPath, cmd: sub });
|
||||||
|
result.push(...collectCommands(sub, fullPath));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Fish completion generator
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
function generateFish(root: CmdInfo): string {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const emit = (s: string): void => { lines.push(s); };
|
||||||
|
const BIN = root.name;
|
||||||
|
|
||||||
|
emit(`# ${BIN} fish completions -- auto-generated by scripts/generate-completions.ts`);
|
||||||
|
emit('# DO NOT EDIT MANUALLY -- run: pnpm completions:generate');
|
||||||
|
emit('');
|
||||||
|
emit(`complete -c ${BIN} -e`);
|
||||||
|
emit(`complete -c ${BIN} -f`);
|
||||||
|
emit('');
|
||||||
|
|
||||||
|
// Global options
|
||||||
|
emit('# Global options');
|
||||||
|
emit(`complete -c ${BIN} -s v -l version -d 'Show version'`);
|
||||||
|
emit(`complete -c ${BIN} -s h -l help -d 'Show help'`);
|
||||||
|
emit('');
|
||||||
|
|
||||||
|
const allCmds = collectCommands(root);
|
||||||
|
|
||||||
|
// Helper function for fish: test if exactly the given subcommand chain is present
|
||||||
|
emit('# Helper: test if a subcommand chain is active');
|
||||||
|
emit(`function __${BIN}_using_cmd`);
|
||||||
|
emit(' set -l tokens (commandline -opc)');
|
||||||
|
emit(' set -l expected $argv');
|
||||||
|
emit(' set -l depth (count $expected)');
|
||||||
|
emit(' set -l found 0');
|
||||||
|
emit(' set -l i 1');
|
||||||
|
emit(' for tok in $tokens[2..]');
|
||||||
|
emit(' if string match -q -- "-*" $tok');
|
||||||
|
emit(' continue');
|
||||||
|
emit(' end');
|
||||||
|
emit(' set i (math $i + 1)');
|
||||||
|
emit(' set -l idx (math $i - 1)');
|
||||||
|
emit(' if test $idx -le $depth');
|
||||||
|
emit(' if test "$tok" != "$expected[$idx]"');
|
||||||
|
emit(' return 1');
|
||||||
|
emit(' end');
|
||||||
|
emit(' set found (math $found + 1)');
|
||||||
|
emit(' else');
|
||||||
|
emit(' return 1');
|
||||||
|
emit(' end');
|
||||||
|
emit(' end');
|
||||||
|
emit(' test $found -eq $depth');
|
||||||
|
emit('end');
|
||||||
|
emit('');
|
||||||
|
|
||||||
|
// Top-level commands
|
||||||
|
const topCmds = root.subcommands.filter((c) => !c.hidden);
|
||||||
|
emit('# Top-level commands');
|
||||||
|
for (const cmd of topCmds) {
|
||||||
|
emit(`complete -c ${BIN} -n "not __fish_seen_subcommand_from ${topCmds.map((c) => c.name).join(' ')}" -a ${cmd.name} -d '${esc(cmd.description)}'`);
|
||||||
|
}
|
||||||
|
emit('');
|
||||||
|
|
||||||
|
// Subcommands and options at each level
|
||||||
|
for (const { path, cmd } of allCmds) {
|
||||||
|
if (cmd.hidden) continue;
|
||||||
|
|
||||||
|
// If this command has subcommands, offer them
|
||||||
|
const visibleSubs = cmd.subcommands.filter((s) => !s.hidden);
|
||||||
|
if (visibleSubs.length > 0) {
|
||||||
|
const parentCondition = `__${BIN}_using_cmd ${path.join(' ')}`;
|
||||||
|
emit(`# ${path.join(' ')} subcommands`);
|
||||||
|
for (const sub of visibleSubs) {
|
||||||
|
emit(`complete -c ${BIN} -n "${parentCondition}" -a ${sub.name} -d '${esc(sub.description)}'`);
|
||||||
|
}
|
||||||
|
emit('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Options for this command
|
||||||
|
if (cmd.options.length > 0) {
|
||||||
|
const condition = `__${BIN}_using_cmd ${path.join(' ')}`;
|
||||||
|
emit(`# ${path.join(' ')} options`);
|
||||||
|
for (const opt of cmd.options) {
|
||||||
|
const parts = [`complete -c ${BIN} -n "${condition}"`];
|
||||||
|
if (opt.short) parts.push(`-s ${opt.short.replace('-', '')}`);
|
||||||
|
parts.push(`-l ${opt.long.replace(/^--/, '')}`);
|
||||||
|
parts.push(`-d '${esc(opt.description)}'`);
|
||||||
|
if (opt.takesValue) {
|
||||||
|
if (opt.choices) {
|
||||||
|
parts.push(`-xa '${opt.choices.join(' ')}'`);
|
||||||
|
} else {
|
||||||
|
parts.push('-x');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit(parts.join(' '));
|
||||||
|
}
|
||||||
|
emit('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.join('\n') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Bash completion generator
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
function generateBash(root: CmdInfo): string {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const emit = (s: string): void => { lines.push(s); };
|
||||||
|
const BIN = root.name;
|
||||||
|
|
||||||
|
emit(`# ${BIN} bash completions -- auto-generated by scripts/generate-completions.ts`);
|
||||||
|
emit('# DO NOT EDIT MANUALLY -- run: pnpm completions:generate');
|
||||||
|
emit('');
|
||||||
|
|
||||||
|
const allCmds = collectCommands(root);
|
||||||
|
const topCmds = root.subcommands.filter((c) => !c.hidden).map((c) => c.name);
|
||||||
|
|
||||||
|
emit(`_${BIN}() {`);
|
||||||
|
emit(' local cur prev words cword');
|
||||||
|
emit(' _init_completion || return');
|
||||||
|
emit('');
|
||||||
|
emit(` local top_commands="${topCmds.join(' ')}"`);
|
||||||
|
emit('');
|
||||||
|
|
||||||
|
// Build chain of subcommands from command line
|
||||||
|
emit(' # Extract the subcommand chain (skip options and their values)');
|
||||||
|
emit(' local -a subcmd_chain=()');
|
||||||
|
emit(' local i skip_next=false');
|
||||||
|
emit(' for ((i=1; i < cword; i++)); do');
|
||||||
|
emit(' if $skip_next; then skip_next=false; continue; fi');
|
||||||
|
emit(' case "${words[i]}" in');
|
||||||
|
emit(' -*) ;; # skip options');
|
||||||
|
emit(' *) subcmd_chain+=("${words[i]}") ;;');
|
||||||
|
emit(' esac');
|
||||||
|
emit(' done');
|
||||||
|
emit('');
|
||||||
|
emit(' local chain_len=${#subcmd_chain[@]}');
|
||||||
|
emit(' local chain_str="${subcmd_chain[*]}"');
|
||||||
|
emit('');
|
||||||
|
|
||||||
|
// Build case statement for each command path
|
||||||
|
emit(' case "$chain_str" in');
|
||||||
|
|
||||||
|
// Start with the deepest paths first to match longest
|
||||||
|
const sortedCmds = [...allCmds].sort((a, b) => b.path.length - a.path.length);
|
||||||
|
|
||||||
|
for (const { path, cmd } of sortedCmds) {
|
||||||
|
if (cmd.hidden) continue;
|
||||||
|
const pathStr = path.join(' ');
|
||||||
|
const visibleSubs = cmd.subcommands.filter((s) => !s.hidden).map((s) => s.name);
|
||||||
|
const optFlags: string[] = [];
|
||||||
|
for (const opt of cmd.options) {
|
||||||
|
if (opt.short) optFlags.push(opt.short);
|
||||||
|
optFlags.push(opt.long);
|
||||||
|
}
|
||||||
|
optFlags.push('-h', '--help');
|
||||||
|
|
||||||
|
const completions = [...visibleSubs, ...optFlags].join(' ');
|
||||||
|
emit(` "${pathStr}")`);
|
||||||
|
emit(` COMPREPLY=($(compgen -W "${completions}" -- "$cur"))`);
|
||||||
|
emit(' return ;;');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top-level (no subcommand yet)
|
||||||
|
emit(' "")');
|
||||||
|
emit(` COMPREPLY=($(compgen -W "$top_commands -h --help -v --version" -- "$cur"))`);
|
||||||
|
emit(' return ;;');
|
||||||
|
|
||||||
|
// Default
|
||||||
|
emit(' *)');
|
||||||
|
emit(' COMPREPLY=($(compgen -W "-h --help" -- "$cur"))');
|
||||||
|
emit(' return ;;');
|
||||||
|
|
||||||
|
emit(' esac');
|
||||||
|
emit('}');
|
||||||
|
emit('');
|
||||||
|
emit(`complete -F _${BIN} ${BIN}`);
|
||||||
|
|
||||||
|
return lines.join('\n') + '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Main
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
async function main(): Promise<void> {
|
||||||
|
const mode = process.argv[2] ?? '';
|
||||||
|
|
||||||
|
let tree: CmdInfo;
|
||||||
|
try {
|
||||||
|
tree = await extractTree();
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to extract command tree from createProgram().');
|
||||||
|
console.error('Make sure workspace packages are built: pnpm build');
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fishContent = generateFish(tree);
|
||||||
|
const bashContent = generateBash(tree);
|
||||||
|
|
||||||
|
const completionsDir = join(ROOT, 'completions');
|
||||||
|
const fishPath = join(completionsDir, 'lab.fish');
|
||||||
|
const bashPath = join(completionsDir, 'lab.bash');
|
||||||
|
|
||||||
|
if (mode === '--check') {
|
||||||
|
let stale = false;
|
||||||
|
try {
|
||||||
|
const currentFish = readFileSync(fishPath, 'utf-8');
|
||||||
|
if (currentFish !== fishContent) {
|
||||||
|
console.error('completions/lab.fish is stale');
|
||||||
|
stale = true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
console.error('completions/lab.fish does not exist');
|
||||||
|
stale = true;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const currentBash = readFileSync(bashPath, 'utf-8');
|
||||||
|
if (currentBash !== bashContent) {
|
||||||
|
console.error('completions/lab.bash is stale');
|
||||||
|
stale = true;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
console.error('completions/lab.bash does not exist');
|
||||||
|
stale = true;
|
||||||
|
}
|
||||||
|
if (stale) {
|
||||||
|
console.error('Run: pnpm completions:generate');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
console.log('Completions are up to date.');
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === '--write') {
|
||||||
|
mkdirSync(completionsDir, { recursive: true });
|
||||||
|
writeFileSync(fishPath, fishContent);
|
||||||
|
writeFileSync(bashPath, bashContent);
|
||||||
|
console.log(`Wrote ${fishPath}`);
|
||||||
|
console.log(`Wrote ${bashPath}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: print to stdout
|
||||||
|
console.log('=== completions/lab.fish ===');
|
||||||
|
console.log(fishContent);
|
||||||
|
console.log('=== completions/lab.bash ===');
|
||||||
|
console.log(bashContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
65
bastion/scripts/link-package.sh
Executable file
65
bastion/scripts/link-package.sh
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Link a Gitea package to a repository.
|
||||||
|
# Works automatically on Gitea 1.24+ (uses API), warns on older versions.
|
||||||
|
#
|
||||||
|
# Usage: source scripts/link-package.sh
|
||||||
|
# link_package <type> <name>
|
||||||
|
#
|
||||||
|
# Requires: GITEA_URL, GITEA_TOKEN, GITEA_OWNER, GITEA_REPO
|
||||||
|
|
||||||
|
link_package() {
|
||||||
|
local PKG_TYPE="$1" # e.g. "rpm", "container"
|
||||||
|
local PKG_NAME="$2" # e.g. "lab", "lab-bastion"
|
||||||
|
|
||||||
|
if [ -z "$PKG_TYPE" ] || [ -z "$PKG_NAME" ]; then
|
||||||
|
echo "Usage: link_package <type> <name>"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local GITEA_URL="${GITEA_URL:-http://10.0.0.194:3012}"
|
||||||
|
local GITEA_OWNER="${GITEA_OWNER:-michal}"
|
||||||
|
local GITEA_REPO="${GITEA_REPO:-lab}"
|
||||||
|
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
echo "WARNING: GITEA_TOKEN not set, skipping package-repo linking."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Check if already linked (search all packages, filter by type+name client-side)
|
||||||
|
local REPO_LINK
|
||||||
|
REPO_LINK=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/packages/${GITEA_OWNER}" \
|
||||||
|
| python3 -c "
|
||||||
|
import json,sys
|
||||||
|
for p in json.load(sys.stdin):
|
||||||
|
if p['type']=='$PKG_TYPE' and p['name']=='$PKG_NAME':
|
||||||
|
r=p.get('repository')
|
||||||
|
if r: print(r['full_name'])
|
||||||
|
break
|
||||||
|
" 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$REPO_LINK" ]; then
|
||||||
|
echo "==> Package ${PKG_TYPE}/${PKG_NAME} already linked to ${REPO_LINK}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Try Gitea 1.24+ link API
|
||||||
|
local HTTP_CODE
|
||||||
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/packages/${GITEA_OWNER}/${PKG_TYPE}/${PKG_NAME}/-/link/${GITEA_REPO}")
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo "==> Linked ${PKG_TYPE}/${PKG_NAME} to ${GITEA_OWNER}/${GITEA_REPO}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# API not available (Gitea < 1.24) -- warn with manual instructions
|
||||||
|
local PUBLIC_URL="${GITEA_PUBLIC_URL:-${GITEA_URL}}"
|
||||||
|
echo ""
|
||||||
|
echo "WARNING: Could not auto-link ${PKG_TYPE}/${PKG_NAME} to repository (Gitea < 1.24)."
|
||||||
|
echo "Link it manually in the Gitea UI:"
|
||||||
|
echo " ${PUBLIC_URL}/${GITEA_OWNER}/-/packages/${PKG_TYPE}/${PKG_NAME}/settings"
|
||||||
|
echo " -> Link to repository: ${GITEA_OWNER}/${GITEA_REPO}"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
72
bastion/scripts/publish-deb.sh
Executable file
72
bastion/scripts/publish-deb.sh
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Load .env if present
|
||||||
|
if [ -f .env ]; then
|
||||||
|
set -a; source .env; set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
GITEA_URL="${GITEA_URL:-http://10.0.0.194:3012}"
|
||||||
|
GITEA_PUBLIC_URL="${GITEA_PUBLIC_URL:-https://mysources.co.uk}"
|
||||||
|
GITEA_OWNER="${GITEA_OWNER:-michal}"
|
||||||
|
GITEA_REPO="${GITEA_REPO:-lab}"
|
||||||
|
|
||||||
|
GITEA_TOKEN="${GITEA_TOKEN:-$PACKAGES_TOKEN}"
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
echo "Error: GITEA_TOKEN (or PACKAGES_TOKEN) not set. Add it to .env or export it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
DEB_FILE=$(ls dist/lab*.deb 2>/dev/null | head -1)
|
||||||
|
if [ -z "$DEB_FILE" ]; then
|
||||||
|
echo "Error: No DEB found in dist/. Run scripts/build-rpm.sh first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Extract version from the deb filename
|
||||||
|
DEB_VERSION=$(dpkg-deb --field "$DEB_FILE" Version 2>/dev/null || echo "unknown")
|
||||||
|
|
||||||
|
echo "==> Publishing $DEB_FILE (version $DEB_VERSION) to ${GITEA_URL}..."
|
||||||
|
|
||||||
|
# Gitea Debian registry: PUT /api/packages/{owner}/debian/pool/{distribution}/{component}/upload
|
||||||
|
# Publish to each supported distribution.
|
||||||
|
# Debian: trixie (13/stable), forky (14/testing)
|
||||||
|
# Ubuntu: noble (24.04 LTS), plucky (25.04)
|
||||||
|
DISTRIBUTIONS="trixie forky noble plucky"
|
||||||
|
|
||||||
|
for DIST in $DISTRIBUTIONS; do
|
||||||
|
echo " -> $DIST..."
|
||||||
|
HTTP_CODE=$(curl -s -o /tmp/deb-upload-$DIST.out -w "%{http_code}" \
|
||||||
|
-X PUT \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
--upload-file "$DEB_FILE" \
|
||||||
|
"${GITEA_URL}/api/packages/${GITEA_OWNER}/debian/pool/${DIST}/main/upload")
|
||||||
|
|
||||||
|
if [ "$HTTP_CODE" = "201" ] || [ "$HTTP_CODE" = "200" ]; then
|
||||||
|
echo " Published to $DIST"
|
||||||
|
elif [ "$HTTP_CODE" = "409" ]; then
|
||||||
|
echo " Already exists in $DIST (skipping)"
|
||||||
|
else
|
||||||
|
echo " WARNING: Upload to $DIST returned HTTP $HTTP_CODE"
|
||||||
|
cat /tmp/deb-upload-$DIST.out 2>/dev/null || true
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
rm -f /tmp/deb-upload-$DIST.out
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Published successfully!"
|
||||||
|
|
||||||
|
# Ensure package is linked to the repository
|
||||||
|
source "$SCRIPT_DIR/link-package.sh"
|
||||||
|
link_package "debian" "lab"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Install with:"
|
||||||
|
echo " echo \"deb ${GITEA_PUBLIC_URL}/api/packages/${GITEA_OWNER}/debian trixie main\" | sudo tee /etc/apt/sources.list.d/lab.list"
|
||||||
|
echo " curl -fsSL ${GITEA_PUBLIC_URL}/api/packages/${GITEA_OWNER}/debian/repository.key | sudo gpg --dearmor -o /etc/apt/keyrings/lab.gpg"
|
||||||
|
echo " sudo apt update && sudo apt install lab"
|
||||||
62
bastion/scripts/publish-rpm.sh
Executable file
62
bastion/scripts/publish-rpm.sh
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||||
|
cd "$PROJECT_ROOT"
|
||||||
|
|
||||||
|
# Load .env if present
|
||||||
|
if [ -f .env ]; then
|
||||||
|
set -a; source .env; set +a
|
||||||
|
fi
|
||||||
|
|
||||||
|
GITEA_URL="${GITEA_URL:-http://10.0.0.194:3012}"
|
||||||
|
GITEA_PUBLIC_URL="${GITEA_PUBLIC_URL:-https://mysources.co.uk}"
|
||||||
|
GITEA_OWNER="${GITEA_OWNER:-michal}"
|
||||||
|
GITEA_REPO="${GITEA_REPO:-lab}"
|
||||||
|
|
||||||
|
GITEA_TOKEN="${GITEA_TOKEN:-$PACKAGES_TOKEN}"
|
||||||
|
if [ -z "$GITEA_TOKEN" ]; then
|
||||||
|
echo "Error: GITEA_TOKEN (or PACKAGES_TOKEN) not set. Add it to .env or export it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
RPM_FILE=$(ls dist/lab-*.rpm 2>/dev/null | head -1)
|
||||||
|
if [ -z "$RPM_FILE" ]; then
|
||||||
|
echo "Error: No RPM found in dist/. Run scripts/build-rpm.sh first."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get version string as it appears in Gitea (e.g. "0.1.0-1")
|
||||||
|
RPM_VERSION=$(rpm -qp --queryformat '%{VERSION}-%{RELEASE}' "$RPM_FILE")
|
||||||
|
|
||||||
|
echo "==> Publishing $RPM_FILE (version $RPM_VERSION) to ${GITEA_URL}..."
|
||||||
|
|
||||||
|
# Check if version already exists and delete it first
|
||||||
|
EXISTING=$(curl -s -o /dev/null -w "%{http_code}" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/packages/${GITEA_OWNER}/rpm/lab/${RPM_VERSION}")
|
||||||
|
|
||||||
|
if [ "$EXISTING" = "200" ]; then
|
||||||
|
echo "==> Version $RPM_VERSION already exists, replacing..."
|
||||||
|
curl -s -o /dev/null -X DELETE \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
"${GITEA_URL}/api/v1/packages/${GITEA_OWNER}/rpm/lab/${RPM_VERSION}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload
|
||||||
|
curl --fail -s -X PUT \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
--upload-file "$RPM_FILE" \
|
||||||
|
"${GITEA_URL}/api/packages/${GITEA_OWNER}/rpm/upload"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "==> Published successfully!"
|
||||||
|
|
||||||
|
# Ensure package is linked to the repository
|
||||||
|
source "$SCRIPT_DIR/link-package.sh"
|
||||||
|
link_package "rpm" "lab"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Install with:"
|
||||||
|
echo " sudo dnf install lab # if repo already configured"
|
||||||
@@ -116,7 +116,7 @@ export async function startBastion(overrides: Partial<BastionConfig> = {}): Prom
|
|||||||
mkdirSync(config.httpDir, { recursive: true });
|
mkdirSync(config.httpDir, { recursive: true });
|
||||||
|
|
||||||
// Prepare boot artifacts
|
// Prepare boot artifacts
|
||||||
if (!config.skipArtifacts) {
|
if (config.skipArtifacts !== true) {
|
||||||
logger.info(`Preparing boot artifacts (Fedora ${config.fedoraVersion} ${config.arch})...`);
|
logger.info(`Preparing boot artifacts (Fedora ${config.fedoraVersion} ${config.arch})...`);
|
||||||
|
|
||||||
copyIfMissing(
|
copyIfMissing(
|
||||||
@@ -177,7 +177,7 @@ export async function startBastion(overrides: Partial<BastionConfig> = {}): Prom
|
|||||||
generateDnsmasqConf(config);
|
generateDnsmasqConf(config);
|
||||||
|
|
||||||
// Open firewall ports
|
// Open firewall ports
|
||||||
if (!config.skipDnsmasq) {
|
if (config.skipDnsmasq !== true) {
|
||||||
openFirewall(config);
|
openFirewall(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +187,7 @@ export async function startBastion(overrides: Partial<BastionConfig> = {}): Prom
|
|||||||
logger.info(`HTTP server listening on :${config.httpPort}`);
|
logger.info(`HTTP server listening on :${config.httpPort}`);
|
||||||
|
|
||||||
// Start dnsmasq (unless skipped)
|
// Start dnsmasq (unless skipped)
|
||||||
if (!config.skipDnsmasq) {
|
if (config.skipDnsmasq !== true) {
|
||||||
const dnsmasqProc = startDnsmasq(config);
|
const dnsmasqProc = startDnsmasq(config);
|
||||||
|
|
||||||
// Monitor dnsmasq
|
// Monitor dnsmasq
|
||||||
@@ -210,9 +210,9 @@ export async function startBastion(overrides: Partial<BastionConfig> = {}): Prom
|
|||||||
printBanner(config);
|
printBanner(config);
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
const shutdown = async () => {
|
const shutdown = async (): Promise<void> => {
|
||||||
logger.info("Shutting down...");
|
logger.info("Shutting down...");
|
||||||
if (!config.skipDnsmasq) stopDnsmasq();
|
if (config.skipDnsmasq !== true) stopDnsmasq();
|
||||||
closeFirewall(config);
|
closeFirewall(config);
|
||||||
await app.close();
|
await app.close();
|
||||||
try { unlinkSync(pidFile); } catch { /* ignore */ }
|
try { unlinkSync(pidFile); } catch { /* ignore */ }
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export function registerApiRoutes(
|
|||||||
const { mac: rawMac, hostname, disk, role } = request.body ?? {};
|
const { mac: rawMac, hostname, disk, role } = request.body ?? {};
|
||||||
const mac = (rawMac ?? "").toLowerCase().replace(/-/g, ":");
|
const mac = (rawMac ?? "").toLowerCase().replace(/-/g, ":");
|
||||||
|
|
||||||
if (!mac) {
|
if (mac === "") {
|
||||||
return reply.status(400).send({ error: "mac is required" });
|
return reply.status(400).send({ error: "mac is required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ export function registerApiRoutes(
|
|||||||
if (queueEntry) {
|
if (queueEntry) {
|
||||||
queueEntry.progress = stageName;
|
queueEntry.progress = stageName;
|
||||||
queueEntry.progress_at = new Date().toISOString();
|
queueEntry.progress_at = new Date().toISOString();
|
||||||
if (detailStr) {
|
if (detailStr !== "") {
|
||||||
queueEntry.progress_detail = detailStr;
|
queueEntry.progress_detail = detailStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,8 +111,9 @@ export function registerApiRoutes(
|
|||||||
};
|
};
|
||||||
s.installed[mac] = installedInfo;
|
s.installed[mac] = installedInfo;
|
||||||
|
|
||||||
const admin = state.load().installed[mac]?.role ? "michal" : "root";
|
const installedRole = state.load().installed[mac]?.role;
|
||||||
console.log(`\n \x1b[0;32m\x1b[1m ssh ${admin}@${ip}\x1b[0m\n`);
|
const admin = installedRole !== undefined && installedRole !== "" ? "michal" : "root";
|
||||||
|
console.log(`\n \x1b[0;32m\x1b[1m ssh ${admin}@${ip}\x1b[0m\n`); // eslint-disable-line no-console
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -126,21 +127,21 @@ export function registerApiRoutes(
|
|||||||
}>("/api/machines/:mac", async (request, reply) => {
|
}>("/api/machines/:mac", async (request, reply) => {
|
||||||
const mac = request.params.mac.toLowerCase().replace(/-/g, ":");
|
const mac = request.params.mac.toLowerCase().replace(/-/g, ":");
|
||||||
|
|
||||||
if (!mac) {
|
if (mac === "") {
|
||||||
return reply.status(400).send({ error: "mac is required" });
|
return reply.status(400).send({ error: "mac is required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
let found = false;
|
let found = false;
|
||||||
state.update((s) => {
|
state.update((s) => {
|
||||||
if (s.discovered[mac]) {
|
if (s.discovered[mac] !== undefined) {
|
||||||
delete s.discovered[mac];
|
delete s.discovered[mac];
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
if (s.install_queue[mac]) {
|
if (s.install_queue[mac] !== undefined) {
|
||||||
delete s.install_queue[mac];
|
delete s.install_queue[mac];
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
if (s.installed[mac]) {
|
if (s.installed[mac] !== undefined) {
|
||||||
delete s.installed[mac];
|
delete s.installed[mac];
|
||||||
found = true;
|
found = true;
|
||||||
}
|
}
|
||||||
@@ -171,7 +172,7 @@ export function registerApiRoutes(
|
|||||||
};
|
};
|
||||||
}>("/api/discover", async (request, reply) => {
|
}>("/api/discover", async (request, reply) => {
|
||||||
const data = request.body;
|
const data = request.body;
|
||||||
if (!data) {
|
if (data === null || data === undefined) {
|
||||||
return reply.status(400).send({ error: "invalid JSON" });
|
return reply.status(400).send({ error: "invalid JSON" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { registerDispatchRoutes } from "./routes/dispatch.js";
|
|||||||
import { registerKickstartRoutes } from "./routes/kickstart.js";
|
import { registerKickstartRoutes } from "./routes/kickstart.js";
|
||||||
import { registerApiRoutes } from "./routes/api.js";
|
import { registerApiRoutes } from "./routes/api.js";
|
||||||
|
|
||||||
export function createApp(config: BastionConfig) {
|
export function createApp(config: BastionConfig): { app: ReturnType<typeof Fastify>; state: StateManager } {
|
||||||
const app = Fastify({
|
const app = Fastify({
|
||||||
logger: false, // We use winston instead
|
logger: false, // We use winston instead
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,10 +13,11 @@ import { logger } from "./logger.js";
|
|||||||
export function detectInterface(): string {
|
export function detectInterface(): string {
|
||||||
const output = execSync("ip route", { encoding: "utf-8" });
|
const output = execSync("ip route", { encoding: "utf-8" });
|
||||||
const match = output.match(/default\s+.*\s+dev\s+(\S+)/);
|
const match = output.match(/default\s+.*\s+dev\s+(\S+)/);
|
||||||
if (!match?.[1]) {
|
const ifaceMatch = match?.[1];
|
||||||
|
if (ifaceMatch === undefined) {
|
||||||
throw new Error("Cannot detect default network interface");
|
throw new Error("Cannot detect default network interface");
|
||||||
}
|
}
|
||||||
return match[1];
|
return ifaceMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,10 +26,11 @@ export function detectInterface(): string {
|
|||||||
export function detectIp(iface: string): string {
|
export function detectIp(iface: string): string {
|
||||||
const output = execSync(`ip -4 addr show ${iface}`, { encoding: "utf-8" });
|
const output = execSync(`ip -4 addr show ${iface}`, { encoding: "utf-8" });
|
||||||
const match = output.match(/inet\s+(\d+\.\d+\.\d+\.\d+)/);
|
const match = output.match(/inet\s+(\d+\.\d+\.\d+\.\d+)/);
|
||||||
if (!match?.[1]) {
|
const ipMatch = match?.[1];
|
||||||
|
if (ipMatch === undefined) {
|
||||||
throw new Error(`Cannot detect IP on interface ${iface}`);
|
throw new Error(`Cannot detect IP on interface ${iface}`);
|
||||||
}
|
}
|
||||||
return match[1];
|
return ipMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,10 +47,11 @@ export function deriveNetwork(ip: string): string {
|
|||||||
export function detectGateway(): string {
|
export function detectGateway(): string {
|
||||||
const output = execSync("ip route", { encoding: "utf-8" });
|
const output = execSync("ip route", { encoding: "utf-8" });
|
||||||
const match = output.match(/default\s+via\s+(\S+)/);
|
const match = output.match(/default\s+via\s+(\S+)/);
|
||||||
if (!match?.[1]) {
|
const gwMatch = match?.[1];
|
||||||
|
if (gwMatch === undefined) {
|
||||||
throw new Error("Cannot detect default gateway");
|
throw new Error("Cannot detect default gateway");
|
||||||
}
|
}
|
||||||
return match[1];
|
return gwMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,11 +59,16 @@ export function detectGateway(): string {
|
|||||||
* Sources: authorized_keys, then id_ed25519.pub, id_rsa.pub, id_ecdsa.pub (deduplicated).
|
* Sources: authorized_keys, then id_ed25519.pub, id_rsa.pub, id_ecdsa.pub (deduplicated).
|
||||||
*/
|
*/
|
||||||
export function collectSshKeys(bastionDir: string): { keys: string[]; source: string } {
|
export function collectSshKeys(bastionDir: string): { keys: string[]; source: string } {
|
||||||
const realHome = process.env["SUDO_USER"]
|
const sudoUser = process.env["SUDO_USER"];
|
||||||
? execSync(`getent passwd ${process.env["SUDO_USER"]}`, { encoding: "utf-8" })
|
let realHome: string;
|
||||||
|
if (sudoUser !== undefined) {
|
||||||
|
const passwdEntry = execSync(`getent passwd ${sudoUser}`, { encoding: "utf-8" })
|
||||||
.split(":")[5]
|
.split(":")[5]
|
||||||
?.trim() ?? homedir()
|
?.trim();
|
||||||
: homedir();
|
realHome = passwdEntry !== undefined && passwdEntry !== "" ? passwdEntry : homedir();
|
||||||
|
} else {
|
||||||
|
realHome = homedir();
|
||||||
|
}
|
||||||
|
|
||||||
const keys: string[] = [];
|
const keys: string[] = [];
|
||||||
const fingerprints = new Set<string>();
|
const fingerprints = new Set<string>();
|
||||||
@@ -74,7 +82,7 @@ export function collectSshKeys(bastionDir: string): { keys: string[]; source: st
|
|||||||
const trimmed = line.trim();
|
const trimmed = line.trim();
|
||||||
if (trimmed && !trimmed.startsWith("#")) {
|
if (trimmed && !trimmed.startsWith("#")) {
|
||||||
const fp = trimmed.split(/\s+/)[1];
|
const fp = trimmed.split(/\s+/)[1];
|
||||||
if (fp && !fingerprints.has(fp)) {
|
if (fp !== undefined && fp !== "" && !fingerprints.has(fp)) {
|
||||||
keys.push(trimmed);
|
keys.push(trimmed);
|
||||||
fingerprints.add(fp);
|
fingerprints.add(fp);
|
||||||
}
|
}
|
||||||
@@ -90,7 +98,7 @@ export function collectSshKeys(bastionDir: string): { keys: string[]; source: st
|
|||||||
if (existsSync(keyPath)) {
|
if (existsSync(keyPath)) {
|
||||||
const keyData = readFileSync(keyPath, "utf-8").trim();
|
const keyData = readFileSync(keyPath, "utf-8").trim();
|
||||||
const fp = keyData.split(/\s+/)[1];
|
const fp = keyData.split(/\s+/)[1];
|
||||||
if (fp && !fingerprints.has(fp)) {
|
if (fp !== undefined && fp !== "" && !fingerprints.has(fp)) {
|
||||||
keys.push(keyData);
|
keys.push(keyData);
|
||||||
fingerprints.add(fp);
|
fingerprints.add(fp);
|
||||||
source = source ? `${source} + ${keyPath}` : keyPath;
|
source = source ? `${source} + ${keyPath}` : keyPath;
|
||||||
@@ -131,18 +139,18 @@ export function detectAdminUser(): string {
|
|||||||
* Populate runtime network config fields on the config object.
|
* Populate runtime network config fields on the config object.
|
||||||
*/
|
*/
|
||||||
export function populateNetworkConfig(config: BastionConfig): BastionConfig {
|
export function populateNetworkConfig(config: BastionConfig): BastionConfig {
|
||||||
const iface = config.iface || detectInterface();
|
const iface = config.iface !== "" ? config.iface : detectInterface();
|
||||||
const serverIp = config.serverIp || detectIp(iface);
|
const serverIp = config.serverIp !== "" ? config.serverIp : detectIp(iface);
|
||||||
const network = config.network || deriveNetwork(serverIp);
|
const network = config.network !== "" ? config.network : deriveNetwork(serverIp);
|
||||||
const gateway = config.gateway || detectGateway();
|
const gateway = config.gateway !== "" ? config.gateway : detectGateway();
|
||||||
const { keys: sshKeys, source: sshSource } = config.sshKeys.length > 0
|
const { keys: sshKeys, source: sshSource } = config.sshKeys.length > 0
|
||||||
? { keys: config.sshKeys, source: "config" }
|
? { keys: config.sshKeys, source: "config" }
|
||||||
: collectSshKeys(config.bastionDir);
|
: collectSshKeys(config.bastionDir);
|
||||||
const adminUser = config.adminUser || detectAdminUser();
|
const adminUser = config.adminUser !== "" ? config.adminUser : detectAdminUser();
|
||||||
|
|
||||||
logger.info(`Interface: ${iface} IP: ${serverIp} Network: ${network}`);
|
logger.info(`Interface: ${iface} IP: ${serverIp} Network: ${network}`);
|
||||||
logger.info(`SSH keys: ${sshKeys.length} key(s) from ${sshSource}`);
|
logger.info(`SSH keys: ${sshKeys.length} key(s) from ${sshSource}`);
|
||||||
if (adminUser) {
|
if (adminUser !== "") {
|
||||||
logger.info(`Admin user: ${adminUser} (will be created on installed machines)`);
|
logger.info(`Admin user: ${adminUser} (will be created on installed machines)`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export function registerInstallCommand(parent: Command): void {
|
|||||||
hostname,
|
hostname,
|
||||||
role: opts.role,
|
role: opts.role,
|
||||||
};
|
};
|
||||||
if (opts.disk) {
|
if (opts.disk !== undefined) {
|
||||||
payload["disk"] = opts.disk;
|
payload["disk"] = opts.disk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -62,12 +62,12 @@ export function registerListCommand(parent: Command): void {
|
|||||||
|
|
||||||
// Determine status
|
// Determine status
|
||||||
let status = "discovered";
|
let status = "discovered";
|
||||||
if (queued) {
|
if (queued !== undefined) {
|
||||||
status = queued.progress && queued.progress !== "waiting"
|
status = queued.progress !== undefined && queued.progress !== "" && queued.progress !== "waiting"
|
||||||
? "installing"
|
? "installing"
|
||||||
: "queued";
|
: "queued";
|
||||||
}
|
}
|
||||||
if (inst) status = "installed";
|
if (inst !== undefined) status = "installed";
|
||||||
|
|
||||||
const hostname = inst?.hostname ?? queued?.hostname ?? "-";
|
const hostname = inst?.hostname ?? queued?.hostname ?? "-";
|
||||||
const role = inst?.role ?? queued?.role ?? "-";
|
const role = inst?.role ?? queued?.role ?? "-";
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function registerReprovisionCommand(parent: Command): void {
|
|||||||
hostname,
|
hostname,
|
||||||
role: opts.role,
|
role: opts.role,
|
||||||
};
|
};
|
||||||
if (opts.disk) {
|
if (opts.disk !== undefined) {
|
||||||
payload["disk"] = opts.disk;
|
payload["disk"] = opts.disk;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,13 +61,14 @@ export function registerReprovisionCommand(parent: Command): void {
|
|||||||
const adminUser = process.env["SUDO_USER"] ?? process.env["USER"] ?? "";
|
const adminUser = process.env["SUDO_USER"] ?? process.env["USER"] ?? "";
|
||||||
const effectiveUser = adminUser === "root" ? "" : adminUser;
|
const effectiveUser = adminUser === "root" ? "" : adminUser;
|
||||||
|
|
||||||
if (ip && effectiveUser) {
|
if (ip !== "" && effectiveUser !== "") {
|
||||||
console.log("");
|
console.log("");
|
||||||
console.log(`Attempting SSH reboot into PXE (${effectiveUser}@${ip})...`);
|
console.log(`Attempting SSH reboot into PXE (${effectiveUser}@${ip})...`);
|
||||||
|
|
||||||
// Find SSH key
|
// Find SSH key
|
||||||
const realHome = process.env["SUDO_USER"]
|
const sudoUser = process.env["SUDO_USER"];
|
||||||
? join("/home", process.env["SUDO_USER"])
|
const realHome = sudoUser !== undefined
|
||||||
|
? join("/home", sudoUser)
|
||||||
: homedir();
|
: homedir();
|
||||||
const keyPaths = [
|
const keyPaths = [
|
||||||
join(realHome, ".ssh", "id_ed25519"),
|
join(realHome, ".ssh", "id_ed25519"),
|
||||||
@@ -79,7 +80,7 @@ export function registerReprovisionCommand(parent: Command): void {
|
|||||||
const sshArgs = [
|
const sshArgs = [
|
||||||
"-o", "StrictHostKeyChecking=no",
|
"-o", "StrictHostKeyChecking=no",
|
||||||
"-o", "ConnectTimeout=10",
|
"-o", "ConnectTimeout=10",
|
||||||
...(sshKey ? ["-i", sshKey] : []),
|
...(sshKey !== undefined ? ["-i", sshKey] : []),
|
||||||
`${effectiveUser}@${ip}`,
|
`${effectiveUser}@${ip}`,
|
||||||
'PXE_ENTRY=$(sudo efibootmgr | grep -iE "pxe|network|ipv4" | head -1 | grep -oP "Boot\\K[0-9A-F]+"); if [ -n "$PXE_ENTRY" ]; then sudo efibootmgr --bootnext "$PXE_ENTRY" && echo "PXE set as next boot" && sudo reboot; else echo "No PXE boot entry found, rebooting anyway..." && sudo reboot; fi',
|
'PXE_ENTRY=$(sudo efibootmgr | grep -iE "pxe|network|ipv4" | head -1 | grep -oP "Boot\\K[0-9A-F]+"); if [ -n "$PXE_ENTRY" ]; then sudo efibootmgr --bootnext "$PXE_ENTRY" && echo "PXE set as next boot" && sudo reboot; else echo "No PXE boot entry found, rebooting anyway..." && sudo reboot; fi',
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
// init bastion standalone start/stop/status
|
// init bastion standalone start/stop/status
|
||||||
// provision list/install/reprovision/forget
|
// provision list/install/reprovision/forget
|
||||||
|
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import { APP_VERSION } from "@lab/shared";
|
import { APP_VERSION } from "@lab/shared";
|
||||||
import { registerStartCommand } from "./commands/serve.js";
|
import { registerStartCommand } from "./commands/serve.js";
|
||||||
@@ -14,6 +15,7 @@ import { registerListCommand } from "./commands/list.js";
|
|||||||
import { registerReprovisionCommand } from "./commands/reprovision.js";
|
import { registerReprovisionCommand } from "./commands/reprovision.js";
|
||||||
import { registerForgetCommand } from "./commands/forget.js";
|
import { registerForgetCommand } from "./commands/forget.js";
|
||||||
|
|
||||||
|
export function createProgram(): Command {
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
program
|
program
|
||||||
@@ -44,4 +46,16 @@ registerInstallCommand(provisionCmd);
|
|||||||
registerReprovisionCommand(provisionCmd);
|
registerReprovisionCommand(provisionCmd);
|
||||||
registerForgetCommand(provisionCmd);
|
registerForgetCommand(provisionCmd);
|
||||||
|
|
||||||
program.parse();
|
return program;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run CLI when executed directly (not imported)
|
||||||
|
const isDirectExecution =
|
||||||
|
process.argv[1] !== undefined &&
|
||||||
|
(process.argv[1].endsWith("/index.js") ||
|
||||||
|
process.argv[1].endsWith("/index.ts") ||
|
||||||
|
process.argv[1] === fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
if (isDirectExecution) {
|
||||||
|
createProgram().parse();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,28 @@
|
|||||||
|
# Stage 1: Build TypeScript
|
||||||
|
FROM node:22-alpine AS builder
|
||||||
|
|
||||||
|
RUN corepack enable && corepack prepare pnpm@9.15.0 --activate
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy workspace config and package manifests
|
||||||
|
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json tsconfig.base.json tsconfig.json ./
|
||||||
|
COPY src/shared/package.json src/shared/tsconfig.json src/shared/
|
||||||
|
COPY src/bastion/package.json src/bastion/tsconfig.json src/bastion/
|
||||||
|
COPY src/cli/package.json src/cli/tsconfig.json src/cli/
|
||||||
|
|
||||||
|
# Install all dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY src/shared/src/ src/shared/src/
|
||||||
|
COPY src/bastion/src/ src/bastion/src/
|
||||||
|
COPY src/cli/src/ src/cli/src/
|
||||||
|
|
||||||
|
# Build TypeScript
|
||||||
|
RUN pnpm build
|
||||||
|
|
||||||
|
# Stage 2: Production runtime
|
||||||
FROM fedora:43
|
FROM fedora:43
|
||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
@@ -7,21 +32,29 @@ RUN dnf install -y \
|
|||||||
ipxe-bootimgs-aarch64 \
|
ipxe-bootimgs-aarch64 \
|
||||||
curl \
|
curl \
|
||||||
openssh-clients \
|
openssh-clients \
|
||||||
|
nodejs \
|
||||||
|
npm \
|
||||||
&& dnf clean all
|
&& dnf clean all
|
||||||
|
|
||||||
# Install Node.js 22
|
# Install pnpm
|
||||||
RUN dnf install -y nodejs npm && dnf clean all
|
|
||||||
RUN npm install -g pnpm@9
|
RUN npm install -g pnpm@9
|
||||||
|
|
||||||
# Create app directory
|
# Create app directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy package files and install dependencies
|
# Copy workspace config, manifests, and lockfile
|
||||||
COPY package.json pnpm-lock.yaml* ./
|
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./
|
||||||
RUN pnpm install --frozen-lockfile 2>/dev/null || pnpm install
|
COPY src/shared/package.json src/shared/
|
||||||
|
COPY src/bastion/package.json src/bastion/
|
||||||
|
COPY src/cli/package.json src/cli/
|
||||||
|
|
||||||
# Copy built application
|
# Install production dependencies
|
||||||
COPY dist/ ./dist/
|
RUN pnpm install --frozen-lockfile --prod 2>/dev/null || pnpm install --prod
|
||||||
|
|
||||||
|
# Copy built output from builder
|
||||||
|
COPY --from=builder /app/src/shared/dist/ src/shared/dist/
|
||||||
|
COPY --from=builder /app/src/bastion/dist/ src/bastion/dist/
|
||||||
|
COPY --from=builder /app/src/cli/dist/ src/cli/dist/
|
||||||
|
|
||||||
# Create data directories
|
# Create data directories
|
||||||
RUN mkdir -p /data/state /data/tftp /data/http
|
RUN mkdir -p /data/state /data/tftp /data/http
|
||||||
@@ -34,4 +67,4 @@ EXPOSE 67/udp
|
|||||||
EXPOSE 69/udp
|
EXPOSE 69/udp
|
||||||
EXPOSE 4011/udp
|
EXPOSE 4011/udp
|
||||||
|
|
||||||
ENTRYPOINT ["node", "dist/cli/index.js", "serve"]
|
ENTRYPOINT ["node", "src/cli/dist/index.js", "init", "bastion", "standalone", "start"]
|
||||||
|
|||||||
Reference in New Issue
Block a user