From e3fb04a52f5f1f2faac59d4f720774d37f9d38ec Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 20 Jun 2026 10:47:08 +0000 Subject: [PATCH 1/8] perf(ci): run affected checks by module --- .github/workflows/check.yml | 208 ++++--- .gitignore | 3 + bun.lock | 210 +------ experiments/terminal-query-suppression.md | 4 +- package.json | 15 +- packages/api/package.json | 4 +- packages/app/linter.config.json | 33 -- packages/app/package.json | 7 +- .../tests/docker-git/changed-checks.test.ts | 187 ++++++ packages/container/linter.config.json | 33 -- packages/container/package.json | 5 +- packages/docker-git-session-sync/package.json | 2 +- packages/lib/linter.config.json | 33 -- packages/lib/package.json | 5 +- packages/terminal/linter.config.json | 33 -- packages/terminal/package.json | 7 +- .../@ton-ai-core__vibecode-linter@1.0.6.patch | 15 - scripts/changed-checks.d.mts | 59 ++ scripts/changed-checks.mjs | 555 ++++++++++++++++++ scripts/npx | 2 +- 20 files changed, 983 insertions(+), 437 deletions(-) delete mode 100644 packages/app/linter.config.json create mode 100644 packages/app/tests/docker-git/changed-checks.test.ts delete mode 100644 packages/container/linter.config.json delete mode 100644 packages/lib/linter.config.json delete mode 100644 packages/terminal/linter.config.json delete mode 100644 patches/@ton-ai-core__vibecode-linter@1.0.6.patch create mode 100644 scripts/changed-checks.d.mts create mode 100644 scripts/changed-checks.mjs diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 35247746..b6ebee50 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -54,109 +54,181 @@ jobs: - name: Dist deps prune (lint) run: bun run check:dist-deps-prune - types: - name: Types + plan-checks: + name: Plan affected checks + runs-on: ubuntu-latest + timeout-minutes: 10 + outputs: + typecheck_matrix: ${{ steps.plan.outputs.typecheck_matrix }} + typecheck_has_work: ${{ steps.plan.outputs.typecheck_has_work }} + lint_matrix: ${{ steps.plan.outputs.lint_matrix }} + lint_has_work: ${{ steps.plan.outputs.lint_has_work }} + test_matrix: ${{ steps.plan.outputs.test_matrix }} + test_has_work: ${{ steps.plan.outputs.test_has_work }} + lint_effect_matrix: ${{ steps.plan.outputs.lint_effect_matrix }} + lint_effect_has_work: ${{ steps.plan.outputs.lint_effect_has_work }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + - name: Compute affected module matrices + id: plan + shell: bash + env: + DOCKER_GIT_CHANGED_BASE: ${{ github.event.pull_request.base.sha }} + DOCKER_GIT_CHANGED_HEAD: ${{ github.sha }} + run: | + set -euo pipefail + + write_plan() { + local key="$1" + local operation="$2" + local matrix + matrix="$(node scripts/changed-checks.mjs "$operation" --matrix)" + echo "${key}_matrix=${matrix}" >> "$GITHUB_OUTPUT" + if [[ "$matrix" == '{"include":[]}' ]]; then + echo "${key}_has_work=false" >> "$GITHUB_OUTPUT" + else + echo "${key}_has_work=true" >> "$GITHUB_OUTPUT" + fi + } + + write_plan typecheck typecheck + write_plan lint lint + write_plan test test + write_plan lint_effect lint:effect + + types-modules: + name: Types (${{ matrix.label }}) + needs: plan-checks + if: needs.plan-checks.outputs.typecheck_has_work == 'true' runs-on: ubuntu-latest timeout-minutes: 10 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.plan-checks.outputs.typecheck_matrix) }} steps: - uses: actions/checkout@v6 - name: Install dependencies uses: ./.github/actions/setup - - name: Typecheck (container) - run: bun run --cwd packages/container typecheck - - name: Typecheck (terminal) - run: bun run --cwd packages/terminal typecheck - - name: Typecheck (app) - run: bun run --cwd packages/app check - - name: Typecheck (session sync) + - name: Typecheck module + run: bun run --filter "${{ matrix.packageName }}" "${{ matrix.script }}" + + types: + name: Types + needs: [plan-checks, types-modules] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check typecheck module results + env: + MODULE_RESULT: ${{ needs.types-modules.result }} run: | - if [ -f packages/docker-git-session-sync/package.json ]; then - bun run --cwd packages/docker-git-session-sync typecheck - else - echo "packages/docker-git-session-sync is not present; skipping" + if [[ "$MODULE_RESULT" == "failure" || "$MODULE_RESULT" == "cancelled" ]]; then + echo "Typecheck module job result: $MODULE_RESULT" >&2 + exit 1 fi - - name: Typecheck (lib) - run: bun run --cwd packages/lib typecheck - - name: Typecheck (api) - run: bun run --cwd packages/api typecheck + echo "Typecheck module job result: $MODULE_RESULT" - lint: - name: Lint + lint-modules: + name: Lint (${{ matrix.label }}) + needs: plan-checks + if: needs.plan-checks.outputs.lint_has_work == 'true' runs-on: ubuntu-latest timeout-minutes: 10 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.plan-checks.outputs.lint_matrix) }} steps: - uses: actions/checkout@v6 - name: Install dependencies uses: ./.github/actions/setup - - name: Lint (container) - run: bun run --cwd packages/container lint - - name: Lint (terminal) - run: bun run --cwd packages/terminal lint - - name: Lint (app) - run: bun run --cwd packages/app lint - - name: Lint (session sync) + - name: Lint module + run: bun run --filter "${{ matrix.packageName }}" "${{ matrix.script }}" + + lint: + name: Lint + needs: [plan-checks, lint-modules] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check lint module results + env: + MODULE_RESULT: ${{ needs.lint-modules.result }} run: | - if [ -f packages/docker-git-session-sync/package.json ] && \ - bun -e "const pkg=JSON.parse(await Bun.file('packages/docker-git-session-sync/package.json').text()); process.exit(pkg.scripts?.lint ? 0 : 1)"; then - bun run --cwd packages/docker-git-session-sync lint - else - echo "packages/docker-git-session-sync lint script is not present; skipping" + if [[ "$MODULE_RESULT" == "failure" || "$MODULE_RESULT" == "cancelled" ]]; then + echo "Lint module job result: $MODULE_RESULT" >&2 + exit 1 fi - - name: Lint (lib) - run: bun run --cwd packages/lib lint - - name: Lint (api) - run: bun run --cwd packages/api lint + echo "Lint module job result: $MODULE_RESULT" - test: - name: Test + test-modules: + name: Test (${{ matrix.label }}) + needs: plan-checks + if: needs.plan-checks.outputs.test_has_work == 'true' runs-on: ubuntu-latest timeout-minutes: 10 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.plan-checks.outputs.test_matrix) }} steps: - uses: actions/checkout@v6 - name: Install dependencies uses: ./.github/actions/setup - - name: Test (container) - run: bun run --cwd packages/container test - - name: Test (terminal) - run: bun run --cwd packages/terminal test - - name: Test (app) - run: bun run --cwd packages/app test - - name: Test (session sync) + - name: Test module + run: bun run --filter "${{ matrix.packageName }}" "${{ matrix.script }}" + + test: + name: Test + needs: [plan-checks, test-modules] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check test module results + env: + MODULE_RESULT: ${{ needs.test-modules.result }} run: | - if [ -f packages/docker-git-session-sync/package.json ]; then - bun run --cwd packages/docker-git-session-sync test - else - echo "packages/docker-git-session-sync is not present; skipping" + if [[ "$MODULE_RESULT" == "failure" || "$MODULE_RESULT" == "cancelled" ]]; then + echo "Test module job result: $MODULE_RESULT" >&2 + exit 1 fi - - name: Test (lib) - run: bun run --cwd packages/lib test - - name: Test (api) - run: bun run --cwd packages/api test + echo "Test module job result: $MODULE_RESULT" - lint-effect: - name: Lint Effect-TS + lint-effect-modules: + name: Lint Effect-TS (${{ matrix.label }}) + needs: plan-checks + if: needs.plan-checks.outputs.lint_effect_has_work == 'true' runs-on: ubuntu-latest timeout-minutes: 10 + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.plan-checks.outputs.lint_effect_matrix) }} steps: - uses: actions/checkout@v6 - name: Install dependencies uses: ./.github/actions/setup - - name: Lint Effect-TS (container) - run: bun run --cwd packages/container lint:effect - - name: Lint Effect-TS (terminal) - run: bun run --cwd packages/terminal lint:effect - - name: Lint Effect-TS (app) - run: bun run --cwd packages/app lint:effect - - name: Lint Effect-TS (session sync) + - name: Lint Effect-TS module + run: bun run --filter "${{ matrix.packageName }}" "${{ matrix.script }}" + + lint-effect: + name: Lint Effect-TS + needs: [plan-checks, lint-effect-modules] + if: always() + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check Effect-TS lint module results + env: + MODULE_RESULT: ${{ needs.lint-effect-modules.result }} run: | - if [ -f packages/docker-git-session-sync/package.json ] && \ - bun -e "const pkg=JSON.parse(await Bun.file('packages/docker-git-session-sync/package.json').text()); process.exit(pkg.scripts?.['lint:effect'] ? 0 : 1)"; then - bun run --cwd packages/docker-git-session-sync lint:effect - else - echo "packages/docker-git-session-sync lint:effect script is not present; skipping" + if [[ "$MODULE_RESULT" == "failure" || "$MODULE_RESULT" == "cancelled" ]]; then + echo "Effect-TS lint module job result: $MODULE_RESULT" >&2 + exit 1 fi - - name: Lint Effect-TS (lib) - run: bun run --cwd packages/lib lint:effect + echo "Effect-TS lint module job result: $MODULE_RESULT" e2e-local-package: name: E2E (Local package CLI) diff --git a/.gitignore b/.gitignore index fb60caac..b9d5dab0 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,9 @@ effect-template1/ # Node / build artifacts node_modules/ +.cache/ +.eslintcache* +packages/*/.eslintcache* dist/ build/ coverage/ diff --git a/bun.lock b/bun.lock index ac3d6c76..9ca107ea 100644 --- a/bun.lock +++ b/bun.lock @@ -43,7 +43,7 @@ }, "packages/app": { "name": "@prover-coder-ai/docker-git", - "version": "1.3.12", + "version": "1.3.13", "bin": { "docker-git": "dist/src/docker-git/main.js", }, @@ -81,7 +81,6 @@ "@eslint/js": "10.0.1", "@prover-coder-ai/docker-git-terminal": "workspace:*", "@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.26", - "@ton-ai-core/vibecode-linter": "^1.0.11", "@types/node": "^25.9.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", @@ -129,7 +128,6 @@ "@eslint/eslintrc": "3.3.5", "@eslint/js": "10.0.1", "@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.26", - "@ton-ai-core/vibecode-linter": "^1.0.11", "@types/node": "^25.9.3", "@typescript-eslint/eslint-plugin": "^8.61.1", "@typescript-eslint/parser": "^8.61.1", @@ -154,7 +152,7 @@ }, "packages/docker-git-session-sync": { "name": "@prover-coder-ai/docker-git-session-sync", - "version": "1.0.68", + "version": "1.0.69", "bin": { "docker-git-session-sync": "dist/docker-git-session-sync.js", }, @@ -209,7 +207,6 @@ "@eslint/js": "10.0.1", "@prover-coder-ai/docker-git-session-sync": "workspace:*", "@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.26", - "@ton-ai-core/vibecode-linter": "^1.0.11", "@types/node": "^25.9.3", "@typescript-eslint/eslint-plugin": "^8.61.1", "@typescript-eslint/parser": "^8.61.1", @@ -265,7 +262,6 @@ "@eslint/eslintrc": "3.3.5", "@eslint/js": "10.0.1", "@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.26", - "@ton-ai-core/vibecode-linter": "^1.0.11", "@types/node": "^25.9.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", @@ -398,8 +394,6 @@ "@changesets/write": ["@changesets/write@0.4.0", "", { "dependencies": { "@changesets/types": "6.1.0", "fs-extra": "7.0.1", "human-id": "4.1.3", "prettier": "2.8.8" } }, "sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q=="], - "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], - "@digitalbazaar/http-client": ["@digitalbazaar/http-client@4.3.0", "", { "dependencies": { "ky": "^1.14.2", "undici": "^6.23.0" } }, "sha512-6lMpxpt9BOmqHKGs9Xm6DP4LlZTBFer/ZjHvP3FcW3IaUWYIWC7dw5RFZnvw4fP57kAVcm1dp3IF+Y50qhBvAw=="], "@dprint/formatter": ["@dprint/formatter@0.4.1", "", {}, "sha512-IB/GXdlMOvi0UhQQ9mcY15Fxcrc2JPadmo6tqefCNV0bptFq7YBpggzpqYXldBXDa04CbKJ+rDwO2eNRPE2+/g=="], @@ -568,16 +562,6 @@ "@js-temporal/polyfill": ["@js-temporal/polyfill@0.5.1", "", { "dependencies": { "jsbi": "^4.3.0" } }, "sha512-hloP58zRVCRSpgDxmqCWJNlizAlUgJFqG2ypq79DCvyv9tHjRYMDOcPFjzfl/A1/YxDvRCZz8wvZvmapQnKwFQ=="], - "@jscpd/badge-reporter": ["@jscpd/badge-reporter@4.0.4", "", { "dependencies": { "badgen": "3.2.3", "colors": "1.4.0", "fs-extra": "11.3.2" } }, "sha512-I9b4MmLXPM2vo0SxSUWnNGKcA4PjQlD3GzXvFK60z43cN/EIdLbOq3FVwCL+dg2obUqGXKIzAm7EsDFTg0D+mQ=="], - - "@jscpd/core": ["@jscpd/core@4.0.4", "", { "dependencies": { "eventemitter3": "5.0.1" } }, "sha512-QGMT3iXEX1fI6lgjPH+x8eyJwhwr2KkpSF5uBpjC0Z5Xloj0yFTFLtwJT+RhxP/Ob4WYrtx2jvpKB269oIwgMQ=="], - - "@jscpd/finder": ["@jscpd/finder@4.0.4", "", { "dependencies": { "@jscpd/core": "4.0.4", "@jscpd/tokenizer": "4.0.4", "blamer": "1.0.7", "bytes": "3.1.2", "cli-table3": "0.6.5", "colors": "1.4.0", "fast-glob": "3.3.3", "fs-extra": "11.3.2", "markdown-table": "2.0.0", "pug": "3.0.3" } }, "sha512-qVUWY7Nzuvfd5OIk+n7/5CM98LmFroLqblRXAI2gDABwZrc7qS+WH2SNr0qoUq0f4OqwM+piiwKvwL/VDNn/Cg=="], - - "@jscpd/html-reporter": ["@jscpd/html-reporter@4.0.4", "", { "dependencies": { "colors": "1.4.0", "fs-extra": "11.3.2", "pug": "3.0.3" } }, "sha512-YiepyeYkeH74Kx59PJRdUdonznct0wHPFkf6FLQN+mCBoy6leAWCcOfHtcexnp+UsBFDlItG5nRdKrDSxSH+Kg=="], - - "@jscpd/tokenizer": ["@jscpd/tokenizer@4.0.4", "", { "dependencies": { "@jscpd/core": "4.0.4", "reprism": "0.0.11", "spark-md5": "3.0.2" } }, "sha512-xxYYY/qaLah/FlwogEbGIxx9CjDO+G9E6qawcy26WwrflzJb6wsnhjwdneN6Wb0RNCDsqvzY+bzG453jsin4UQ=="], - "@logtape/logtape": ["@logtape/logtape@2.1.1", "", {}, "sha512-aULbCqUQGerfOsZ3CMvcKtueKzmdchluXYUd3bIHKmOIS93fx1ko0+hyRQ4flloGZ8EiyRPydZXiy8n1J/eAQA=="], "@manypkg/find-root": ["@manypkg/find-root@1.1.0", "", { "dependencies": { "@babel/runtime": "7.28.4", "@types/node": "12.20.55", "find-up": "4.1.0", "fs-extra": "8.1.0" } }, "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA=="], @@ -720,8 +704,6 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@ton-ai-core/vibecode-linter": ["@ton-ai-core/vibecode-linter@1.0.11", "", { "dependencies": { "ajv": "8.17.1", "effect": "3.21.0", "jiti": "2.6.1", "jscpd": "4.0.8", "jscpd-sarif-reporter": "4.0.5", "loop-controls": "1.1.0", "ts-pattern": "5.9.0" }, "bin": { "vibecode-linter": "dist/bin/vibecode-linter.js" } }, "sha512-CSert5rYENM7MMvY3AcKdtBTYBnqeb2ti4CS4lNMWoDbyGqA6PmOH7/WK8+fcl6VyGJiPBTzq5Hp+1LYHUUuJA=="], - "@ts-morph/common": ["@ts-morph/common@0.29.0", "", { "dependencies": { "minimatch": "^10.0.1", "path-browserify": "^1.0.1", "tinyglobby": "^0.2.14" } }, "sha512-35oUmphHbJvQ/+UTwFNme/t2p3FoKiGJ5auTjjpNTop2dyREspirjMy82PLSC1pnDJ8ah1GU98hwpVt64YXQsg=="], "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], @@ -774,8 +756,6 @@ "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "19.2.14" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - "@types/sarif": ["@types/sarif@2.1.7", "", {}, "sha512-kRz0VEkJqWLf1LLVN4pT1cg1Z9wAuvI6L97V3m2f5B76Tg8d413ddvLBPTEHAZJlnn4XSvu0FkZtViCQGVyrXQ=="], - "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="], "@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], @@ -894,12 +874,8 @@ "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "1.0.2", "call-bind": "1.0.8", "define-properties": "1.2.1", "es-abstract": "1.24.0", "es-errors": "1.3.0", "get-intrinsic": "1.3.0", "is-array-buffer": "3.0.5" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], - "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="], - "asn1js": ["asn1js@3.0.10", "", { "dependencies": { "pvtsutils": "^1.3.6", "pvutils": "^1.1.5", "tslib": "^2.8.1" } }, "sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg=="], - "assert-never": ["assert-never@1.4.0", "", {}, "sha512-5oJg84os6NMQNl27T9LnZkvvqzvAnHu03ShCnoj6bsJwS7L8AO4lf+C/XjK/nvzEqQB744moC6V128RucQd1jA=="], - "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], "ast-types": ["ast-types@0.16.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg=="], @@ -910,10 +886,6 @@ "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "1.1.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - "babel-walk": ["babel-walk@3.0.0-canary-5", "", { "dependencies": { "@babel/types": "7.28.5" } }, "sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw=="], - - "badgen": ["badgen@3.2.3", "", {}, "sha512-svDuwkc63E/z0ky3drpUppB83s/nlgDciH9m+STwwQoWyq7yCgew1qEfJ+9axkKdNq7MskByptWUN9j1PGMwFA=="], - "balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="], "baseline-browser-mapping": ["baseline-browser-mapping@2.10.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA=="], @@ -922,8 +894,6 @@ "biome": ["@biomejs/biome@2.5.0", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.5.0", "@biomejs/cli-darwin-x64": "2.5.0", "@biomejs/cli-linux-arm64": "2.5.0", "@biomejs/cli-linux-arm64-musl": "2.5.0", "@biomejs/cli-linux-x64": "2.5.0", "@biomejs/cli-linux-x64-musl": "2.5.0", "@biomejs/cli-win32-arm64": "2.5.0", "@biomejs/cli-win32-x64": "2.5.0" }, "bin": { "biome": "bin/biome" } }, "sha512-4kURkd9hAPrdDM3C9n82ycYgx8hvQcW6MjKTEejruj8rK0N8P3OPpdy8BvI8kt3KWY4ycF5XtDOrktetEfhfuw=="], - "blamer": ["blamer@1.0.7", "", { "dependencies": { "execa": "4.1.0", "which": "2.0.2" } }, "sha512-GbBStl/EVlSWkiJQBZps3H1iARBrC7vt++Jb/TTmCNu/jZ04VW7tSN1nScbFXBUy1AN+jzeL7Zep9sbQxLhXKA=="], - "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], "brace-expansion": ["brace-expansion@5.0.6", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g=="], @@ -962,8 +932,6 @@ "character-entities-legacy": ["character-entities-legacy@1.1.4", "", {}, "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA=="], - "character-parser": ["character-parser@2.2.0", "", { "dependencies": { "is-regex": "1.2.1" } }, "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw=="], - "character-reference-invalid": ["character-reference-invalid@1.1.4", "", {}, "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg=="], "chardet": ["chardet@2.1.1", "", {}, "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ=="], @@ -974,8 +942,6 @@ "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="], - "cli-table3": ["cli-table3@0.6.5", "", { "dependencies": { "string-width": "4.2.3" }, "optionalDependencies": { "@colors/colors": "1.5.0" } }, "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ=="], - "code-block-writer": ["code-block-writer@13.0.3", "", {}, "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg=="], "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], @@ -984,14 +950,8 @@ "colorette": ["colorette@1.4.0", "", {}, "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g=="], - "colors": ["colors@1.4.0", "", {}, "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="], - - "commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="], - "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], - "constantinople": ["constantinople@4.0.1", "", { "dependencies": { "@babel/parser": "7.28.5", "@babel/types": "7.28.5" } }, "sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw=="], - "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], "core-js-compat": ["core-js-compat@3.49.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA=="], @@ -1046,8 +1006,6 @@ "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "2.0.3" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="], - "doctypes": ["doctypes@1.1.0", "", {}, "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ=="], - "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "2.3.0", "domhandler": "5.0.3", "entities": "4.5.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], @@ -1066,12 +1024,10 @@ "electron-to-chromium": ["electron-to-chromium@1.5.352", "", {}, "sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg=="], - "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "0.6.3", "whatwg-encoding": "3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], - "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], - "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "4.1.3", "strip-ansi": "6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], @@ -1144,12 +1100,8 @@ "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], - "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], - "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="], - "execa": ["execa@4.1.0", "", { "dependencies": { "cross-spawn": "7.0.6", "get-stream": "5.2.0", "human-signals": "1.1.1", "is-stream": "2.0.1", "merge-stream": "2.0.0", "npm-run-path": "4.0.1", "onetime": "5.1.2", "signal-exit": "3.0.7", "strip-final-newline": "2.0.0" } }, "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA=="], - "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "29.7.0", "jest-get-type": "29.6.3", "jest-matcher-utils": "29.7.0", "jest-message-util": "29.7.0", "jest-util": "29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="], "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], @@ -1168,8 +1120,6 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], - "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "1.1.0" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], "fdir": ["fdir@6.5.0", "", { "optionalDependencies": { "picomatch": "4.0.3" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -1214,14 +1164,10 @@ "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "1.0.1", "es-object-atoms": "1.1.1" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], - "get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "3.0.3" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="], - "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "1.0.4", "es-errors": "1.3.0", "get-intrinsic": "1.3.0" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], - "gitignore-to-glob": ["gitignore-to-glob@0.3.0", "", {}, "sha512-mk74BdnK7lIwDHnotHddx1wsjMOFIThpLY3cPNniJ/2fA/tlLzHnFxIdR+4sLOu5KGgQJdij4kjJ2RoUNnCNMA=="], - "glob": ["glob@10.5.0", "", { "dependencies": { "foreground-child": "3.3.1", "jackspeak": "3.4.3", "minimatch": "9.0.5", "minipass": "7.1.2", "package-json-from-dist": "1.0.1", "path-scurry": "1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], @@ -1262,8 +1208,6 @@ "human-id": ["human-id@4.1.3", "", { "bin": { "human-id": "dist/cli.js" } }, "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q=="], - "human-signals": ["human-signals@1.1.1", "", {}, "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="], - "iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": "2.1.2" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="], "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], @@ -1312,8 +1256,6 @@ "is-decimal": ["is-decimal@1.0.4", "", {}, "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw=="], - "is-expression": ["is-expression@4.0.0", "", { "dependencies": { "acorn": "7.4.1", "object-assign": "4.1.1" } }, "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A=="], - "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "1.0.4" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], @@ -1334,16 +1276,12 @@ "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "1.0.4", "has-tostringtag": "1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], - "is-promise": ["is-promise@2.2.2", "", {}, "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="], - "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "1.0.4", "gopd": "1.2.0", "has-tostringtag": "1.0.2", "hasown": "2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "1.0.4" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], - "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], - "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "1.0.4", "has-tostringtag": "1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], "is-subdir": ["is-subdir@1.2.0", "", { "dependencies": { "better-path-resolve": "1.0.0" } }, "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw=="], @@ -1386,8 +1324,6 @@ "js-levenshtein": ["js-levenshtein@1.1.6", "", {}, "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g=="], - "js-stringify": ["js-stringify@1.0.2", "", {}, "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g=="], - "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], @@ -1396,8 +1332,6 @@ "jscpd": ["jscpd@5.0.10", "", { "optionalDependencies": { "cpd-darwin-arm64": "5.0.10", "cpd-darwin-x64": "5.0.10", "cpd-linux-arm64-gnu": "5.0.10", "cpd-linux-x64-gnu": "5.0.10", "cpd-linux-x64-musl": "5.0.10", "cpd-windows-x64-msvc": "5.0.10" }, "bin": { "jscpd": "run-jscpd.js", "cpd": "run-jscpd.js" } }, "sha512-ieidqRmKGasve4k9YUftQRJZ2jxm6EbK6mvHSNL1sFDAnrukX7Qs4Ce+i3tUUqu3Vgd9WOhvanKpdoPG/IuZ+g=="], - "jscpd-sarif-reporter": ["jscpd-sarif-reporter@4.0.5", "", { "dependencies": { "colors": "1.4.0", "fs-extra": "11.3.2", "node-sarif-builder": "3.4.0" } }, "sha512-cD1MtUdpomUPM5C0YD0vKZmdj+Gyr0KD5Bk47yGMrPCtwtgsK+7v59OzBIUjYOL8AuxNAt6hvPFo0PH+PYJh0Q=="], - "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], @@ -1416,8 +1350,6 @@ "jsonld": ["jsonld@9.0.0", "", { "dependencies": { "@digitalbazaar/http-client": "^4.2.0", "canonicalize": "^2.1.0", "lru-cache": "^6.0.0", "rdf-canonize": "^5.0.0" } }, "sha512-pjMIdkXfC1T2wrX9B9i2uXhGdyCmgec3qgMht+TDj+S0qX3bjWMQUfL7NeqEhuRTi8G5ESzmL9uGlST7nzSEWg=="], - "jstransformer": ["jstransformer@1.0.0", "", { "dependencies": { "is-promise": "2.2.2", "promise": "7.3.1" } }, "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A=="], - "jsx-ast-utils-x": ["jsx-ast-utils-x@0.1.0", "", {}, "sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw=="], "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -1462,8 +1394,6 @@ "lodash.startcase": ["lodash.startcase@4.4.0", "", {}, "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg=="], - "loop-controls": ["loop-controls@1.1.0", "", {}, "sha512-otnxF3ngIuLecg99p7On7nJF6ws1mT2kNOiGOPFykEHQfhJtdsjcQMxM4LEHsUi3LeMrm2Ic0hFdykJcG0N1YQ=="], - "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], @@ -1472,8 +1402,6 @@ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "7.7.4" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], - "markdown-table": ["markdown-table@2.0.0", "", { "dependencies": { "repeat-string": "1.6.1" } }, "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A=="], - "marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -1482,8 +1410,6 @@ "mdast-util-to-string": ["mdast-util-to-string@2.0.0", "", {}, "sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w=="], - "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="], - "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "micromark": ["micromark@2.11.4", "", { "dependencies": { "debug": "4.4.3", "parse-entities": "2.0.0" } }, "sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA=="], @@ -1492,8 +1418,6 @@ "mime": ["mime@3.0.0", "", { "bin": { "mime": "cli.js" } }, "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A=="], - "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], - "minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="], "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], @@ -1528,16 +1452,10 @@ "node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="], - "node-sarif-builder": ["node-sarif-builder@3.4.0", "", { "dependencies": { "@types/sarif": "2.1.7", "fs-extra": "11.3.2" } }, "sha512-tGnJW6OKRii9u/b2WiUViTJS+h7Apxx17qsMUjsUeNDiMMX5ZFf8F8Fcz7PAQ6omvOxHZtvDTmOYKJQwmfpjeg=="], - "normalize-package-data": ["normalize-package-data@2.5.0", "", { "dependencies": { "hosted-git-info": "2.8.9", "resolve": "1.22.11", "semver": "5.7.2", "validate-npm-package-license": "3.0.4" } }, "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA=="], - "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "3.1.1" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="], - "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], - "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], @@ -1552,10 +1470,6 @@ "obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="], - "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1.0.2" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], - - "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="], - "openapi-typescript": ["openapi-typescript@7.13.0", "", { "dependencies": { "@redocly/openapi-core": "^1.34.6", "ansi-colors": "^4.1.3", "change-case": "^5.4.4", "parse-json": "^8.3.0", "supports-color": "^10.2.2", "yargs-parser": "^21.1.1" }, "peerDependencies": { "typescript": "^5.x" }, "bin": { "openapi-typescript": "bin/cli.js" } }, "sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ=="], "openapi-typescript-helpers": ["openapi-typescript-helpers@0.1.0", "", {}, "sha512-OKTGPthhivLw/fHz6c3OPtg72vi86qaMlqbJuVJ23qOvQ+53uw1n7HdmkJFibloF7QEjDrDkzJiOJuockM/ljw=="], @@ -1628,34 +1542,6 @@ "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "29.6.3", "ansi-styles": "5.2.0", "react-is": "18.3.1" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="], - "promise": ["promise@7.3.1", "", { "dependencies": { "asap": "2.0.6" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="], - - "pug": ["pug@3.0.3", "", { "dependencies": { "pug-code-gen": "3.0.3", "pug-filters": "4.0.0", "pug-lexer": "5.0.1", "pug-linker": "4.0.0", "pug-load": "3.0.0", "pug-parser": "6.0.0", "pug-runtime": "3.0.1", "pug-strip-comments": "2.0.0" } }, "sha512-uBi6kmc9f3SZ3PXxqcHiUZLmIXgfgWooKWXcwSGwQd2Zi5Rb0bT14+8CJjJgI8AB+nndLaNgHGrcc6bPIB665g=="], - - "pug-attrs": ["pug-attrs@3.0.0", "", { "dependencies": { "constantinople": "4.0.1", "js-stringify": "1.0.2", "pug-runtime": "3.0.1" } }, "sha512-azINV9dUtzPMFQktvTXciNAfAuVh/L/JCl0vtPCwvOA21uZrC08K/UnmrL+SXGEVc1FwzjW62+xw5S/uaLj6cA=="], - - "pug-code-gen": ["pug-code-gen@3.0.3", "", { "dependencies": { "constantinople": "4.0.1", "doctypes": "1.1.0", "js-stringify": "1.0.2", "pug-attrs": "3.0.0", "pug-error": "2.1.0", "pug-runtime": "3.0.1", "void-elements": "3.1.0", "with": "7.0.2" } }, "sha512-cYQg0JW0w32Ux+XTeZnBEeuWrAY7/HNE6TWnhiHGnnRYlCgyAUPoyh9KzCMa9WhcJlJ1AtQqpEYHc+vbCzA+Aw=="], - - "pug-error": ["pug-error@2.1.0", "", {}, "sha512-lv7sU9e5Jk8IeUheHata6/UThZ7RK2jnaaNztxfPYUY+VxZyk/ePVaNZ/vwmH8WqGvDz3LrNYt/+gA55NDg6Pg=="], - - "pug-filters": ["pug-filters@4.0.0", "", { "dependencies": { "constantinople": "4.0.1", "jstransformer": "1.0.0", "pug-error": "2.1.0", "pug-walk": "2.0.0", "resolve": "1.22.11" } }, "sha512-yeNFtq5Yxmfz0f9z2rMXGw/8/4i1cCFecw/Q7+D0V2DdtII5UvqE12VaZ2AY7ri6o5RNXiweGH79OCq+2RQU4A=="], - - "pug-lexer": ["pug-lexer@5.0.1", "", { "dependencies": { "character-parser": "2.2.0", "is-expression": "4.0.0", "pug-error": "2.1.0" } }, "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w=="], - - "pug-linker": ["pug-linker@4.0.0", "", { "dependencies": { "pug-error": "2.1.0", "pug-walk": "2.0.0" } }, "sha512-gjD1yzp0yxbQqnzBAdlhbgoJL5qIFJw78juN1NpTLt/mfPJ5VgC4BvkoD3G23qKzJtIIXBbcCt6FioLSFLOHdw=="], - - "pug-load": ["pug-load@3.0.0", "", { "dependencies": { "object-assign": "4.1.1", "pug-walk": "2.0.0" } }, "sha512-OCjTEnhLWZBvS4zni/WUMjH2YSUosnsmjGBB1An7CsKQarYSWQ0GCVyd4eQPMFJqZ8w9xgs01QdiZXKVjk92EQ=="], - - "pug-parser": ["pug-parser@6.0.0", "", { "dependencies": { "pug-error": "2.1.0", "token-stream": "1.0.0" } }, "sha512-ukiYM/9cH6Cml+AOl5kETtM9NR3WulyVP2y4HOU45DyMim1IeP/OOiyEWRr6qk5I5klpsBnbuHpwKmTx6WURnw=="], - - "pug-runtime": ["pug-runtime@3.0.1", "", {}, "sha512-L50zbvrQ35TkpHwv0G6aLSuueDRwc/97XdY8kL3tOT0FmhgG7UypU3VztfV/LATAvmUfYi4wNxSajhSAeNN+Kg=="], - - "pug-strip-comments": ["pug-strip-comments@2.0.0", "", { "dependencies": { "pug-error": "2.1.0" } }, "sha512-zo8DsDpH7eTkPHCXFeAk1xZXJbyoTfdPlNR0bK7rpOMuhBYb0f5qUVCO1xlsitYd3w5FQTK7zpNVKb3rZoUrrQ=="], - - "pug-walk": ["pug-walk@2.0.0", "", {}, "sha512-yYELe9Q5q9IQhuvqsZNwA5hfPkMJ8u92bQLIMcsMxf/VADjNtEYptU+inlufAFYcWdHlwNfZOEnOOQrZrcyJCQ=="], - - "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "1.4.5", "once": "1.4.0" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], "pure-rand": ["pure-rand@8.4.0", "", {}, "sha512-IoM8YF/jY0hiugFo/wOWqfmarlE6J0wc6fDK1PhftMk7MGhVZl88sZimmqBBFomLOCSmcCCpsfj7wXASCpvK9A=="], @@ -1696,10 +1582,6 @@ "regjsparser": ["regjsparser@0.13.2", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ=="], - "repeat-string": ["repeat-string@1.6.1", "", {}, "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w=="], - - "reprism": ["reprism@0.0.11", "", {}, "sha512-VsxDR5QxZo08M/3nRypNlScw5r3rKeSOPdU/QhDmu3Ai3BJxHn/qgfXGWQp/tAxUtzwYNo9W6997JZR0tPLZsA=="], - "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "2.16.1", "path-parse": "1.0.7", "supports-preserve-symlinks-flag": "1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], @@ -1760,8 +1642,6 @@ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - "spark-md5": ["spark-md5@3.0.2", "", {}, "sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw=="], - "spawndamnit": ["spawndamnit@3.0.1", "", { "dependencies": { "cross-spawn": "7.0.6", "signal-exit": "4.1.0" } }, "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg=="], "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "3.0.1", "spdx-license-ids": "3.0.22" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], @@ -1784,7 +1664,7 @@ "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "1.3.0", "internal-slot": "1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], - "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "8.0.0", "is-fullwidth-code-point": "3.0.0", "strip-ansi": "6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "0.2.0", "emoji-regex": "9.2.2", "strip-ansi": "7.1.2" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "8.0.0", "is-fullwidth-code-point": "3.0.0", "strip-ansi": "6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -1800,8 +1680,6 @@ "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="], - "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="], - "strip-indent": ["strip-indent@4.1.1", "", {}, "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA=="], "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], @@ -1826,8 +1704,6 @@ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], - "token-stream": ["token-stream@1.0.0", "", {}, "sha512-VSsyNPPW74RpHwR8Fc21uubwHY7wMDeJLys2IX5zJNih+OnAnaifKHo+1LHT7DAdloQ7apeaaWg8l7qnf/TnEg=="], - "toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="], "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], @@ -1836,8 +1712,6 @@ "ts-morph": ["ts-morph@28.0.0", "", { "dependencies": { "@ts-morph/common": "~0.29.0", "code-block-writer": "^13.0.3" } }, "sha512-Wp3tnZ2bzwxyTZMtgWVzXDfm7lB1Drz+y9DmmYH/L702PQhPyVrp3pkou3yIz4qjS14GY9kcpmLiOOMvl8oG1g=="], - "ts-pattern": ["ts-pattern@5.9.0", "", {}, "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg=="], - "tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="], "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "0.0.29", "json5": "1.0.2", "minimist": "1.2.8", "strip-bom": "3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="], @@ -1896,8 +1770,6 @@ "vitest": ["vitest@4.1.9", "", { "dependencies": { "@vitest/expect": "4.1.9", "@vitest/mocker": "4.1.9", "@vitest/pretty-format": "4.1.9", "@vitest/runner": "4.1.9", "@vitest/snapshot": "4.1.9", "@vitest/spy": "4.1.9", "@vitest/utils": "4.1.9", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.9", "@vitest/browser-preview": "4.1.9", "@vitest/browser-webdriverio": "4.1.9", "@vitest/coverage-istanbul": "4.1.9", "@vitest/coverage-v8": "4.1.9", "@vitest/ui": "4.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "./vitest.mjs" } }, "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ=="], - "void-elements": ["void-elements@3.1.0", "", {}, "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="], - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], @@ -1918,16 +1790,12 @@ "why-is-node-running": ["why-is-node-running@2.3.0", "", { "dependencies": { "siginfo": "2.0.0", "stackback": "0.0.2" }, "bin": { "why-is-node-running": "cli.js" } }, "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w=="], - "with": ["with@7.0.2", "", { "dependencies": { "@babel/parser": "7.28.5", "@babel/types": "7.28.5", "assert-never": "1.4.0", "babel-walk": "3.0.0-canary-5" } }, "sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w=="], - "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "6.2.3", "string-width": "5.1.2", "strip-ansi": "7.1.2" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="], "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "4.3.0", "string-width": "4.2.3", "strip-ansi": "6.0.1" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], - "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], - "ws": ["ws@8.21.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g=="], "xterm": ["xterm@5.3.0", "", {}, "sha512-8QqjlekLUFTrU6x7xck1MsPzPA571K5zNqWm0M0oroYEWVOptZ0+ubQSkQ3uxIEhcIHRujJy6emDWX4A7qyFzg=="], @@ -2006,18 +1874,10 @@ "@inquirer/external-editor/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="], - "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "0.2.0", "emoji-regex": "9.2.2", "strip-ansi": "7.1.2" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "6.2.2" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], "@jest/types/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="], - "@jscpd/badge-reporter/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "4.2.11", "jsonfile": "6.2.0", "universalify": "2.0.1" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], - - "@jscpd/finder/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "4.2.11", "jsonfile": "6.2.0", "universalify": "2.0.1" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], - - "@jscpd/html-reporter/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "4.2.11", "jsonfile": "6.2.0", "universalify": "2.0.1" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], - "@manypkg/find-root/@types/node": ["@types/node@12.20.55", "", {}, "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ=="], "@manypkg/find-root/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "5.0.0", "path-exists": "4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], @@ -2054,12 +1914,6 @@ "@rolldown/binding-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], - "@ton-ai-core/vibecode-linter/ajv": ["ajv@8.17.1", "", { "dependencies": { "fast-deep-equal": "3.1.3", "fast-uri": "3.1.0", "json-schema-traverse": "1.0.0", "require-from-string": "2.0.2" } }, "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g=="], - - "@ton-ai-core/vibecode-linter/effect": ["effect@3.21.0", "", { "dependencies": { "@standard-schema/spec": "1.1.0", "fast-check": "3.23.2" } }, "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ=="], - - "@ton-ai-core/vibecode-linter/jscpd": ["jscpd@4.0.8", "", { "dependencies": { "@jscpd/badge-reporter": "4.0.4", "@jscpd/core": "4.0.4", "@jscpd/finder": "4.0.4", "@jscpd/html-reporter": "4.0.4", "@jscpd/tokenizer": "4.0.4", "colors": "1.4.0", "commander": "5.1.0", "fs-extra": "11.3.2", "gitignore-to-glob": "0.3.0", "jscpd-sarif-reporter": "4.0.6" }, "bin": { "jscpd": "bin/jscpd" } }, "sha512-d2VNT/2Hv4dxT2/59He8Lyda4DYOxPRyRG9zBaOpTZAqJCVf2xLrBlZkT8Va6Lo9u3X2qz8Bpq4HrDi4JsrQhA=="], - "@ts-morph/common/tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], "@types/glob/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="], @@ -2112,8 +1966,6 @@ "eslint-plugin-unicorn/semver": ["semver@7.8.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], - "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "4.0.3" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "fdir/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], @@ -2128,8 +1980,6 @@ "is-builtin-module/builtin-modules": ["builtin-modules@5.0.0", "", {}, "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg=="], - "is-expression/acorn": ["acorn@7.4.1", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A=="], - "istanbul-lib-report/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], "jest-util/@types/node": ["@types/node@24.12.0", "", { "dependencies": { "undici-types": "7.16.0" } }, "sha512-GYDxsZi3ChgmckRT9HPU0WEhKLP08ev/Yfcq2AstjrDASOYCSXeyjDsHg4v5t4jOj7cyDX3vmprafKlWIG9MXQ=="], @@ -2138,16 +1988,12 @@ "jest-util/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "jscpd-sarif-reporter/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "4.2.11", "jsonfile": "6.2.0", "universalify": "2.0.1" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], - "magicast/@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="], "magicast/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "7.27.1", "@babel/helper-validator-identifier": "7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "node-sarif-builder/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "4.2.11", "jsonfile": "6.2.0", "universalify": "2.0.1" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], - "normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="], "openapi-typescript/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], @@ -2170,16 +2016,20 @@ "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="], + "string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "6.2.2" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "1.2.8" }, "bin": { "json5": "lib/cli.js" } }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="], "whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": "2.1.2" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], - "wrap-ansi/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "0.2.0", "emoji-regex": "9.2.2", "strip-ansi": "7.1.2" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="], - "wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "6.2.2" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "8.0.0", "is-fullwidth-code-point": "3.0.0", "strip-ansi": "6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + "@babel/helper-compilation-targets/browserslist/baseline-browser-mapping": ["baseline-browser-mapping@2.8.32", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw=="], "@babel/helper-compilation-targets/browserslist/caniuse-lite": ["caniuse-lite@1.0.30001759", "", {}, "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw=="], @@ -2264,24 +2114,10 @@ "@inquirer/external-editor/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], - "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], "@jest/types/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "@jscpd/badge-reporter/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "2.0.1" }, "optionalDependencies": { "graceful-fs": "4.2.11" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "@jscpd/badge-reporter/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - - "@jscpd/finder/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "2.0.1" }, "optionalDependencies": { "graceful-fs": "4.2.11" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "@jscpd/finder/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - - "@jscpd/html-reporter/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "2.0.1" }, "optionalDependencies": { "graceful-fs": "4.2.11" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "@jscpd/html-reporter/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "@manypkg/find-root/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "@napi-rs/wasm-runtime/@emnapi/core/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], @@ -2326,14 +2162,6 @@ "@redocly/openapi-core/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "1.0.2" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], - "@ton-ai-core/vibecode-linter/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], - - "@ton-ai-core/vibecode-linter/effect/fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], - - "@ton-ai-core/vibecode-linter/jscpd/fs-extra": ["fs-extra@11.3.2", "", { "dependencies": { "graceful-fs": "4.2.11", "jsonfile": "6.2.0", "universalify": "2.0.1" } }, "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A=="], - - "@ton-ai-core/vibecode-linter/jscpd/jscpd-sarif-reporter": ["jscpd-sarif-reporter@4.0.6", "", { "dependencies": { "colors": "1.4.0", "fs-extra": "11.3.2", "node-sarif-builder": "3.4.0" } }, "sha512-b9Sm3IPZ3+m8Lwa4gZa+4/LhDhlc/ZLEsLXKSOy1DANQ6kx0ueqZT+fUHWEdQ6m0o3+RIVIa7DmvLSojQD05ng=="], - "@types/glob/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], "@types/minimatch/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "4.0.4" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="], @@ -2438,21 +2266,15 @@ "jest-util/@types/node/undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - "jscpd-sarif-reporter/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "2.0.1" }, "optionalDependencies": { "graceful-fs": "4.2.11" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "jscpd-sarif-reporter/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "magicast/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], - "node-sarif-builder/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "2.0.1" }, "optionalDependencies": { "graceful-fs": "4.2.11" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "node-sarif-builder/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "read-pkg-up/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], "read-yaml-file/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "1.0.3" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], - "wrap-ansi/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -2518,12 +2340,6 @@ "@redocly/openapi-core/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], - "@ton-ai-core/vibecode-linter/effect/fast-check/pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], - - "@ton-ai-core/vibecode-linter/jscpd/fs-extra/jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "2.0.1" }, "optionalDependencies": { "graceful-fs": "4.2.11" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="], - - "@ton-ai-core/vibecode-linter/jscpd/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], - "@vitest/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.61.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.61.0", "@typescript-eslint/types": "^8.61.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-DV42F7MLJO6Rax7SK1yg43tcnEfGUrurSpSxKuVX+a3RCTzBlH3fuxprrOJXKCJGAaw82xXocikJ0uQaqwXgGA=="], "@vitest/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree/@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.61.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-O5Amvdv9ztMpxpf+vmFULGG78IE6Qwdr3bCGvqwG4nwc9H2qXkOYJJnRbRHyMkQTjv1d03olqwwwzHLMqpFePQ=="], diff --git a/experiments/terminal-query-suppression.md b/experiments/terminal-query-suppression.md index 5eb8914b..1c3a00bc 100644 --- a/experiments/terminal-query-suppression.md +++ b/experiments/terminal-query-suppression.md @@ -97,7 +97,7 @@ semantics (sub-params on these modes are vendor-specific extensions). registered, all suppressed modes consumed, all benign DEC private modes pass through, disposal closes every handler). - `bun run typecheck` — clean. -- `vibecode-linter src/web/terminal-query-suppression.ts tests/docker-git/terminal-query-suppression.test.ts` - — 0 errors, 0 warnings, 0 duplicates. +- `eslint src/web/terminal-query-suppression.ts tests/docker-git/terminal-query-suppression.test.ts` + — 0 errors, 0 warnings. - `eslint --config eslint.effect-ts-check.config.mjs ...` — clean on both files. diff --git a/package.json b/package.json index b35463f1..5eec77ca 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "api:dev": "bun run --filter @effect-template/api dev", "api:test": "bun run --filter @effect-template/api test", "api:typecheck": "bun run --filter @effect-template/api typecheck", - "check": "bun run --filter @prover-coder-ai/docker-git-session-sync check && bun run --filter @prover-coder-ai/docker-git-terminal check && bun run --filter @prover-coder-ai/docker-git-openapi check && bun run --filter @prover-coder-ai/docker-git check && bun run --filter @effect-template/lib typecheck", + "check": "bun scripts/changed-checks.mjs check", + "check:all": "bun scripts/changed-checks.mjs check --all", "check:dist-deps-prune": "bun node_modules/@prover-coder-ai/dist-deps-prune/dist/main.js scan --package ./packages/app/package.json --prune-dev true --silent", "changeset": "changeset", "changeset-publish": "bun -e \"if (!process.env.NPM_TOKEN) { console.log('Skipping publish: NPM_TOKEN is not set'); process.exit(0); }\" && changeset publish", @@ -47,13 +48,17 @@ "openapi:lint-contract": "bun run --cwd packages/api lint:openapi-contract", "web:preview": "bun run --cwd packages/app preview:web", "web:serve": "bun run --cwd packages/app serve:web", - "lint": "bun run --filter @prover-coder-ai/docker-git-terminal lint && bun run --filter @prover-coder-ai/docker-git lint && bun run --filter @effect-template/lib lint", + "lint": "bun scripts/changed-checks.mjs lint", + "lint:all": "bun scripts/changed-checks.mjs lint --all", "lint:tests": "bun run --filter @prover-coder-ai/docker-git lint:tests", - "lint:effect": "bun run --filter @prover-coder-ai/docker-git-session-sync lint:effect && bun run --filter @prover-coder-ai/docker-git-terminal lint:effect && bun run --filter @prover-coder-ai/docker-git lint:effect && bun run --filter @prover-coder-ai/docker-git-container lint:effect && bun run --filter @effect-template/lib lint:effect && bun run --filter @effect-template/api lint:effect", + "lint:effect": "bun scripts/changed-checks.mjs lint:effect", + "lint:effect:all": "bun scripts/changed-checks.mjs lint:effect --all", "effect:skill:init": "git submodule update --init --checkout third_party/effect-ts-skills", "effect:skill:check": "bun run effect:skill:init && bash .codex/skills/effect-ts-guide/scripts/run-effect-ts-check.sh packages/app/src/web/api-create-project.ts packages/app/src/web/api-database.ts packages/app/src/web/api-http.ts packages/app/src/web/api-prompts.ts packages/app/src/web/api-skills.ts packages/app/src/web/api-tasks.ts packages/openapi/src --profile strict", - "test": "bun run --filter @prover-coder-ai/docker-git-session-sync test && bun run --filter @prover-coder-ai/docker-git-terminal test && bun run --filter @prover-coder-ai/docker-git test && bun run --filter @effect-template/lib test", - "typecheck": "bun run --filter @prover-coder-ai/docker-git-session-sync typecheck && bun run --filter @prover-coder-ai/docker-git-terminal typecheck && bun run --filter @prover-coder-ai/docker-git-openapi typecheck && bun run --filter @prover-coder-ai/docker-git typecheck && bun run --filter @effect-template/lib typecheck", + "test": "bun scripts/changed-checks.mjs test", + "test:all": "bun scripts/changed-checks.mjs test --all", + "typecheck": "bun scripts/changed-checks.mjs typecheck", + "typecheck:all": "bun scripts/changed-checks.mjs typecheck --all", "start": "bun run --cwd packages/app build:docker-git && bun ./packages/app/dist/src/docker-git/main.js" }, "devDependencies": { diff --git a/packages/api/package.json b/packages/api/package.json index d2b9f357..da8bc492 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -14,8 +14,8 @@ "start": "bun dist/src/main.js", "pretypecheck": "bun run --cwd ../terminal build && bun run --cwd ../container build && bun run --cwd ../lib build", "typecheck": "tsc --noEmit -p tsconfig.json", - "lint": "eslint .", - "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .", + "lint": "eslint . --cache --cache-location .eslintcache --cache-strategy content", + "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs . --cache --cache-location .eslintcache-effect --cache-strategy content", "lint:openapi-contract": "vitest run tests/openapi.test.ts", "pretest": "bun run --cwd ../terminal build && bun run --cwd ../container build && bun run --cwd ../lib build", "test": "vitest run" diff --git a/packages/app/linter.config.json b/packages/app/linter.config.json deleted file mode 100644 index 31dc1a21..00000000 --- a/packages/app/linter.config.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "priorityLevels": [ - { - "level": 1, - "name": "Critical Compiler Errors", - "rules": [ - "ts(2835)", - "ts(2307)", - "@prover-coder-ai/suggest-members/suggest-members", - "@prover-coder-ai/suggest-members/suggest-imports", - "@prover-coder-ai/suggest-members/suggest-module-paths", - "@prover-coder-ai/suggest-members/suggest-exports", - "@prover-coder-ai/suggest-members/suggest-missing-names", - "@typescript-eslint/no-explicit-any" - ] - }, - { - "level": 2, - "name": "Critical Compiler Errors", - "rules": ["all"] - }, - { - "level": 3, - "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)", - "rules": ["max-lines-per-function", "max-lines"] - }, - { - "level": 4, - "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)", - "rules": ["complexity", "max-params", "max-depth"] - } - ] -} diff --git a/packages/app/package.json b/packages/app/package.json index 2caac76a..62b3c86f 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -24,9 +24,9 @@ "dev:web": "vite --config vite.web.config.ts", "serve:web": "bun scripts/serve-dist-web.mjs", "prelint": "bun run --cwd ../docker-git-session-sync build && bun run --cwd ../terminal build", - "lint": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH vibecode-linter src/", - "lint:tests": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH vibecode-linter tests/", - "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH eslint --config eslint.effect-ts-check.config.mjs .", + "lint": "NODE_OPTIONS=--max-old-space-size=4096 eslint src --cache --cache-location .eslintcache --cache-strategy content", + "lint:tests": "NODE_OPTIONS=--max-old-space-size=4096 eslint tests --cache --cache-location .eslintcache-tests --cache-strategy content", + "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 eslint --config eslint.effect-ts-check.config.mjs . --cache --cache-location .eslintcache-effect --cache-strategy content", "prebuild:docker-git": "bun install --cwd ../.. && bun run --cwd ../docker-git-session-sync build && bun run --cwd ../terminal build", "build:docker-git": "vite build --config vite.docker-git.config.ts", "prebuild:docker-git:reuse-install": "bun run --cwd ../docker-git-session-sync build && bun run --cwd ../terminal build", @@ -98,7 +98,6 @@ "@eslint/js": "10.0.1", "@prover-coder-ai/docker-git-terminal": "workspace:*", "@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.26", - "@ton-ai-core/vibecode-linter": "^1.0.11", "@types/node": "^25.9.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", diff --git a/packages/app/tests/docker-git/changed-checks.test.ts b/packages/app/tests/docker-git/changed-checks.test.ts new file mode 100644 index 00000000..a2da5a66 --- /dev/null +++ b/packages/app/tests/docker-git/changed-checks.test.ts @@ -0,0 +1,187 @@ +import { describe, expect, it } from "@effect/vitest" + +import { + createChangedChecksPlan, + createGithubMatrix, + type WorkspacePackage +} from "../../../../scripts/changed-checks.mjs" + +const workspacePackages: ReadonlyArray = [ + { + dependencyNames: ["@effect-template/lib", "@prover-coder-ai/docker-git-terminal"], + dir: "packages/api", + name: "@effect-template/api", + scripts: { + "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .", + lint: "eslint .", + test: "vitest run", + typecheck: "tsc --noEmit -p tsconfig.json" + } + }, + { + dependencyNames: [ + "@prover-coder-ai/docker-git-openapi", + "@prover-coder-ai/docker-git-session-sync", + "@prover-coder-ai/docker-git-terminal" + ], + dir: "packages/app", + name: "@prover-coder-ai/docker-git", + scripts: { + "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .", + "lint:tests": "eslint tests", + lint: "eslint src", + pretypecheck: "build deps", + test: "vitest run", + typecheck: "tsc --noEmit" + } + }, + { + dependencyNames: [], + dir: "packages/container", + name: "@prover-coder-ai/docker-git-container", + scripts: { + "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .", + lint: "eslint src", + test: "vitest run --passWithNoTests", + typecheck: "tsc --noEmit -p tsconfig.json" + } + }, + { + dependencyNames: [], + dir: "packages/docker-git-session-sync", + name: "@prover-coder-ai/docker-git-session-sync", + scripts: { + "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .", + test: "vitest run --passWithNoTests", + typecheck: "tsc --noEmit -p tsconfig.json" + } + }, + { + dependencyNames: ["@prover-coder-ai/docker-git-container", "@prover-coder-ai/docker-git-session-sync"], + dir: "packages/lib", + name: "@effect-template/lib", + scripts: { + "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .", + lint: "eslint src", + test: "vitest run --passWithNoTests", + typecheck: "tsc --noEmit -p tsconfig.json" + } + }, + { + dependencyNames: [], + dir: "packages/openapi", + name: "@prover-coder-ai/docker-git-openapi", + scripts: { typecheck: "tsc --noEmit -p tsconfig.json" } + }, + { + dependencyNames: [], + dir: "packages/terminal", + name: "@prover-coder-ai/docker-git-terminal", + scripts: { + "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .", + "lint:tests": "eslint tests", + lint: "eslint src", + test: "vitest run", + typecheck: "tsc --noEmit" + } + } +] + +const plan = ( + operation: "check" | "lint" | "lint:effect" | "test" | "typecheck", + changedFiles: ReadonlyArray +) => createChangedChecksPlan({ changedFiles, operation, packages: workspacePackages }) + +describe("changed-checks planner", () => { + it("skips docs-only changes", () => { + const result = plan("test", ["docs/process.md", "README.md"]) + + expect(result.mode).toBe("skip") + expect(result.commands).toEqual([]) + }) + + it("fails closed to a full run for root toolchain changes", () => { + const result = plan("test", ["bun.lock"]) + + expect(result.mode).toBe("all") + expect(result.commands.map((command) => command.packageName)).toEqual([ + "@prover-coder-ai/docker-git-container", + "@prover-coder-ai/docker-git-session-sync", + "@effect-template/lib", + "@prover-coder-ai/docker-git-terminal", + "@effect-template/api", + "@prover-coder-ai/docker-git" + ]) + }) + + it("runs normal lint only for the owning package", () => { + const result = plan("lint", ["packages/terminal/src/core/output-buffer.ts"]) + + expect(result.mode).toBe("affected") + expect(result.commands).toEqual([ + { + args: ["run", "--filter", "@prover-coder-ai/docker-git-terminal", "lint"], + command: "bun", + packageName: "@prover-coder-ai/docker-git-terminal", + phase: "lint", + serial: false, + scriptName: "lint" + } + ]) + }) + + it("adds test lint when a package test file changed", () => { + const result = plan("lint", ["packages/app/tests/docker-git/menu-create.test.ts"]) + + expect(result.commands.map((command) => command.scriptName)).toEqual(["lint", "lint:tests"]) + }) + + it("expands typecheck to transitive dependents", () => { + const result = plan("typecheck", ["packages/terminal/src/core/output-buffer.ts"]) + + expect(result.commands.map((command) => command.packageName)).toEqual([ + "@prover-coder-ai/docker-git-terminal", + "@effect-template/api", + "@prover-coder-ai/docker-git" + ]) + }) + + it("fails closed for unknown root files", () => { + const result = plan("lint:effect", [".editorconfig"]) + + expect(result.mode).toBe("all") + expect(result.commands.length).toBeGreaterThan(1) + }) + + it("marks package commands with pre-hooks as serial for local execution", () => { + const result = plan("typecheck", ["packages/app/src/web/api-http.ts"]) + + expect(result.commands.find((command) => command.packageName === "@prover-coder-ai/docker-git")).toMatchObject({ + phase: "typecheck", + serial: true, + scriptName: "typecheck" + }) + }) + + it("builds a GitHub matrix per affected package command", () => { + const result = plan("lint", [ + "packages/app/src/web/api-http.ts", + "packages/app/tests/docker-git/api-http.test.ts" + ]) + + expect(createGithubMatrix(result)).toEqual({ + include: [ + { + label: "@prover-coder-ai/docker-git lint", + packageName: "@prover-coder-ai/docker-git", + script: "lint" + }, + { + label: "@prover-coder-ai/docker-git lint:tests", + packageName: "@prover-coder-ai/docker-git", + script: "lint:tests" + } + ] + }) + }) +}) diff --git a/packages/container/linter.config.json b/packages/container/linter.config.json deleted file mode 100644 index 31dc1a21..00000000 --- a/packages/container/linter.config.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "priorityLevels": [ - { - "level": 1, - "name": "Critical Compiler Errors", - "rules": [ - "ts(2835)", - "ts(2307)", - "@prover-coder-ai/suggest-members/suggest-members", - "@prover-coder-ai/suggest-members/suggest-imports", - "@prover-coder-ai/suggest-members/suggest-module-paths", - "@prover-coder-ai/suggest-members/suggest-exports", - "@prover-coder-ai/suggest-members/suggest-missing-names", - "@typescript-eslint/no-explicit-any" - ] - }, - { - "level": 2, - "name": "Critical Compiler Errors", - "rules": ["all"] - }, - { - "level": 3, - "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)", - "rules": ["max-lines-per-function", "max-lines"] - }, - { - "level": 4, - "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)", - "rules": ["complexity", "max-params", "max-depth"] - } - ] -} diff --git a/packages/container/package.json b/packages/container/package.json index 19db56a6..cb0d2330 100644 --- a/packages/container/package.json +++ b/packages/container/package.json @@ -10,8 +10,8 @@ "scripts": { "build": "tsc -p tsconfig.json", "dev": "tsc -p tsconfig.json --watch", - "lint": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH vibecode-linter src/", - "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH eslint --config eslint.effect-ts-check.config.mjs .", + "lint": "NODE_OPTIONS=--max-old-space-size=4096 eslint src --cache --cache-location .eslintcache --cache-strategy content", + "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 eslint --config eslint.effect-ts-check.config.mjs . --cache --cache-location .eslintcache-effect --cache-strategy content", "typecheck": "tsc --noEmit -p tsconfig.json", "test": "vitest run --passWithNoTests" }, @@ -48,7 +48,6 @@ "@eslint/eslintrc": "3.3.5", "@eslint/js": "10.0.1", "@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.26", - "@ton-ai-core/vibecode-linter": "^1.0.11", "@types/node": "^25.9.3", "@typescript-eslint/eslint-plugin": "^8.61.1", "@typescript-eslint/parser": "^8.61.1", diff --git a/packages/docker-git-session-sync/package.json b/packages/docker-git-session-sync/package.json index 44aebcee..9fdeb57c 100644 --- a/packages/docker-git-session-sync/package.json +++ b/packages/docker-git-session-sync/package.json @@ -12,7 +12,7 @@ "scripts": { "build": "vite build && bun ../../scripts/mark-executable.mjs dist/docker-git-session-sync.js", "check": "bun run typecheck", - "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs .", + "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs . --cache --cache-location .eslintcache-effect --cache-strategy content", "prepack": "bun run build", "test": "vitest run --passWithNoTests", "typecheck": "tsc --noEmit -p tsconfig.json" diff --git a/packages/lib/linter.config.json b/packages/lib/linter.config.json deleted file mode 100644 index 31dc1a21..00000000 --- a/packages/lib/linter.config.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "priorityLevels": [ - { - "level": 1, - "name": "Critical Compiler Errors", - "rules": [ - "ts(2835)", - "ts(2307)", - "@prover-coder-ai/suggest-members/suggest-members", - "@prover-coder-ai/suggest-members/suggest-imports", - "@prover-coder-ai/suggest-members/suggest-module-paths", - "@prover-coder-ai/suggest-members/suggest-exports", - "@prover-coder-ai/suggest-members/suggest-missing-names", - "@typescript-eslint/no-explicit-any" - ] - }, - { - "level": 2, - "name": "Critical Compiler Errors", - "rules": ["all"] - }, - { - "level": 3, - "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)", - "rules": ["max-lines-per-function", "max-lines"] - }, - { - "level": 4, - "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)", - "rules": ["complexity", "max-params", "max-depth"] - } - ] -} diff --git a/packages/lib/package.json b/packages/lib/package.json index fe0c282c..50df55f9 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -12,8 +12,8 @@ "build": "tsc -p tsconfig.json", "dev": "tsc -p tsconfig.json --watch", "prelint": "bun run --cwd ../container build", - "lint": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH vibecode-linter src/", - "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH eslint --config eslint.effect-ts-check.config.mjs .", + "lint": "NODE_OPTIONS=--max-old-space-size=4096 eslint src --cache --cache-location .eslintcache --cache-strategy content", + "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 eslint --config eslint.effect-ts-check.config.mjs . --cache --cache-location .eslintcache-effect --cache-strategy content", "pretypecheck": "bun run --cwd ../container build", "typecheck": "tsc --noEmit -p tsconfig.json", "pretest": "bun run --cwd ../container build", @@ -65,7 +65,6 @@ "@eslint/eslintrc": "3.3.5", "@eslint/js": "10.0.1", "@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.26", - "@ton-ai-core/vibecode-linter": "^1.0.11", "@types/node": "^25.9.3", "@typescript-eslint/eslint-plugin": "^8.61.1", "@typescript-eslint/parser": "^8.61.1", diff --git a/packages/terminal/linter.config.json b/packages/terminal/linter.config.json deleted file mode 100644 index 31dc1a21..00000000 --- a/packages/terminal/linter.config.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "priorityLevels": [ - { - "level": 1, - "name": "Critical Compiler Errors", - "rules": [ - "ts(2835)", - "ts(2307)", - "@prover-coder-ai/suggest-members/suggest-members", - "@prover-coder-ai/suggest-members/suggest-imports", - "@prover-coder-ai/suggest-members/suggest-module-paths", - "@prover-coder-ai/suggest-members/suggest-exports", - "@prover-coder-ai/suggest-members/suggest-missing-names", - "@typescript-eslint/no-explicit-any" - ] - }, - { - "level": 2, - "name": "Critical Compiler Errors", - "rules": ["all"] - }, - { - "level": 3, - "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)", - "rules": ["max-lines-per-function", "max-lines"] - }, - { - "level": 4, - "name": "Critical Compiler Errors (Code must follow Clean Code and best practices)", - "rules": ["complexity", "max-params", "max-depth"] - } - ] -} diff --git a/packages/terminal/package.json b/packages/terminal/package.json index 6964a5c1..41696ac6 100644 --- a/packages/terminal/package.json +++ b/packages/terminal/package.json @@ -11,9 +11,9 @@ "build": "tsc -p tsconfig.build.json", "check": "bun run typecheck", "dev": "tsc -p tsconfig.build.json --watch", - "lint": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH vibecode-linter src/", - "lint:tests": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH vibecode-linter tests/", - "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH eslint --config eslint.effect-ts-check.config.mjs .", + "lint": "NODE_OPTIONS=--max-old-space-size=4096 eslint src --cache --cache-location .eslintcache --cache-strategy content", + "lint:tests": "NODE_OPTIONS=--max-old-space-size=4096 eslint tests --cache --cache-location .eslintcache-tests --cache-strategy content", + "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 eslint --config eslint.effect-ts-check.config.mjs . --cache --cache-location .eslintcache-effect --cache-strategy content", "test": "bun run lint:tests && vitest run", "typecheck": "tsc --noEmit" }, @@ -36,7 +36,6 @@ "@eslint/eslintrc": "3.3.5", "@eslint/js": "10.0.1", "@prover-coder-ai/eslint-plugin-suggest-members": "^0.0.26", - "@ton-ai-core/vibecode-linter": "^1.0.11", "@types/node": "^25.9.3", "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3", diff --git a/patches/@ton-ai-core__vibecode-linter@1.0.6.patch b/patches/@ton-ai-core__vibecode-linter@1.0.6.patch deleted file mode 100644 index 986a5e6b..00000000 --- a/patches/@ton-ai-core__vibecode-linter@1.0.6.patch +++ /dev/null @@ -1,15 +0,0 @@ -diff --git a/dist/shell/utils/dependencies.js b/dist/shell/utils/dependencies.js -index bd96968de92e45ef4543abb7781bd71d333c8090..47a6bf82594a74b1acefbe24c2e00b0171dc3cf6 100644 ---- a/dist/shell/utils/dependencies.js -+++ b/dist/shell/utils/dependencies.js -@@ -42,7 +42,9 @@ const DEPENDENCIES = [ - { - name: "TypeScript", - command: "tsc", -- checkCommand: "npx tsc --version", -+ // npm@11+ `npx tsc` resolves to the unrelated `tsc` package. -+ // We just need the TypeScript compiler API dependency to be present. -+ checkCommand: "node -e \"require('typescript')\"", - installCommand: "npm install", - required: true, - }, diff --git a/scripts/changed-checks.d.mts b/scripts/changed-checks.d.mts new file mode 100644 index 00000000..ef7917ef --- /dev/null +++ b/scripts/changed-checks.d.mts @@ -0,0 +1,59 @@ +export type ChangedCheckOperation = "check" | "lint" | "lint:effect" | "test" | "typecheck" + +export interface WorkspacePackage { + readonly dependencyNames: ReadonlyArray + readonly dir: string + readonly name: string + readonly scripts: Readonly> +} + +export interface ChangedCheckCommand { + readonly args: ReadonlyArray + readonly command: string + readonly packageName: string + readonly phase: string + readonly serial: boolean + readonly scriptName: string +} + +export interface ChangedChecksPlan { + readonly affectedPackages: ReadonlyArray + readonly changedFiles: ReadonlyArray + readonly commands: ReadonlyArray + readonly mode: "affected" | "all" | "skip" + readonly operation: ChangedCheckOperation + readonly ownerPackages: ReadonlyArray + readonly reason: string +} + +export interface CreateChangedChecksPlanInput { + readonly all?: boolean + readonly changedFiles: ReadonlyArray + readonly diffFailed?: boolean + readonly operation: ChangedCheckOperation + readonly packages: ReadonlyArray +} + +export interface ChangedChecksCliArgs { + readonly all: boolean + readonly base: string + readonly concurrency: number + readonly dryRun: boolean + readonly head: string + readonly matrix: boolean + readonly operation: ChangedCheckOperation +} + +export interface GithubMatrix { + readonly include: ReadonlyArray> +} + +export declare const createGithubMatrix: (plan: ChangedChecksPlan) => GithubMatrix +export declare const createChangedChecksPlan: (input: CreateChangedChecksPlanInput) => ChangedChecksPlan +export declare const loadWorkspacePackages: (rootDir?: string) => ReadonlyArray +export declare const parseChangedChecksArgs: (args: ReadonlyArray) => ChangedChecksCliArgs +export declare const runChangedChecksCli: (argv: ReadonlyArray, rootDir?: string) => Promise diff --git a/scripts/changed-checks.mjs b/scripts/changed-checks.mjs new file mode 100644 index 00000000..207aeda4 --- /dev/null +++ b/scripts/changed-checks.mjs @@ -0,0 +1,555 @@ +#!/usr/bin/env bun +// CHANGE: Add a workspace-aware affected-check runner for lint/test/typecheck. +// WHY: issue #432 asks to run checks only for changed code while failing closed for global/tooling changes. +// QUOTE(ТЗ): "настроить кеш вызова тестов и линтеров. Что бы они гонялись только на изменённый код" +// REF: issue-432 +// SOURCE: n/a +// FORMAT THEOREM: forall changed paths P: global(P) -> fullRun(P); package(P) -> affectedClosure(P); docsOnly(P) -> skip(P) +// PURITY: SHELL entrypoint with pure planner exports +// EFFECT: child_process spawn for git and check commands +// INVARIANT: unknown or uncomputable diff never skips checks +// COMPLEXITY: O(p + e + f log f) where p = packages, e = workspace dependency edges, f = changed files + +import { spawn, spawnSync } from "node:child_process" +import fs from "node:fs" +import path from "node:path" +import { pathToFileURL } from "node:url" + +const operations = new Set(["check", "lint", "lint:effect", "test", "typecheck"]) + +const fullRunExactPaths = new Set([ + ".gitignore", + "bun.lock", + "bunfig.toml", + "eslint.effect-ts-shared.mjs", + "package.json", + "tsconfig.base.json", + "tsconfig.json" +]) + +const fullRunPrefixes = [".github/", "scripts/"] + +const dependencySections = ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"] + +const commandGroups = { + check: ["typecheck", "lint", "test"], + lint: ["lint"], + "lint:effect": ["lint:effect"], + test: ["test"], + typecheck: ["typecheck"] +} + +const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value) + +const normalizePath = (value) => value.replaceAll("\\", "/").replace(/^\.\/+/u, "").replace(/\/+/gu, "/") + +const uniqueSorted = (values) => [...new Set(values)].sort((left, right) => left.localeCompare(right)) + +const shellQuote = (value) => { + if (/^[A-Za-z0-9_./:@=-]+$/u.test(value)) { + return value + } + return `'${value.replaceAll("'", "'\\''")}'` +} + +const formatCommand = (command) => [command.command, ...command.args].map(shellQuote).join(" ") + +const isDocsOnlyPath = (filePath) => + filePath === "README.md" || + filePath.startsWith("docs/") || + (filePath.endsWith(".md") && !filePath.startsWith("packages/")) + +const isFullRunPath = (filePath) => + fullRunExactPaths.has(filePath) || fullRunPrefixes.some((prefix) => filePath.startsWith(prefix)) + +const packageForPath = (packages, filePath) => + packages.find((workspacePackage) => filePath === workspacePackage.dir || filePath.startsWith(`${workspacePackage.dir}/`)) ?? + null + +const packageNamesWithScript = (packages, scriptName) => + packages.filter((workspacePackage) => workspacePackage.scripts[scriptName] !== undefined).map((workspacePackage) => + workspacePackage.name + ) + +const dependencyNameSet = (manifest) => { + const names = [] + + for (const section of dependencySections) { + const dependencies = manifest[section] + if (!isRecord(dependencies)) { + continue + } + + for (const [name, version] of Object.entries(dependencies)) { + if (typeof version === "string" && version.startsWith("workspace:")) { + names.push(name) + } + } + } + + return uniqueSorted(names) +} + +const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, "utf8")) + +export const loadWorkspacePackages = (rootDir = process.cwd()) => { + const rootPackage = readJson(path.join(rootDir, "package.json")) + const workspaces = Array.isArray(rootPackage.workspaces) ? rootPackage.workspaces : [] + const packages = [] + + for (const workspace of workspaces) { + if (typeof workspace !== "string" || workspace.includes("*")) { + continue + } + + const manifestPath = path.join(rootDir, workspace, "package.json") + if (!fs.existsSync(manifestPath)) { + continue + } + + const manifest = readJson(manifestPath) + if (typeof manifest.name !== "string" || !isRecord(manifest.scripts)) { + continue + } + + packages.push({ + dependencyNames: dependencyNameSet(manifest), + dir: normalizePath(workspace), + name: manifest.name, + scripts: Object.fromEntries( + Object.entries(manifest.scripts).filter((entry) => typeof entry[1] === "string") + ) + }) + } + + return packages +} + +const reverseDependencyMap = (packages) => { + const workspaceNames = new Set(packages.map((workspacePackage) => workspacePackage.name)) + const reverse = new Map(packages.map((workspacePackage) => [workspacePackage.name, []])) + + for (const workspacePackage of packages) { + for (const dependencyName of workspacePackage.dependencyNames) { + if (!workspaceNames.has(dependencyName)) { + continue + } + + reverse.set(dependencyName, [...(reverse.get(dependencyName) ?? []), workspacePackage.name]) + } + } + + return reverse +} + +const affectedClosure = (packages, ownerNames) => { + const reverse = reverseDependencyMap(packages) + const queue = [...ownerNames] + const seen = new Set(ownerNames) + + for (let index = 0; index < queue.length; index += 1) { + const current = queue[index] + for (const dependentName of reverse.get(current) ?? []) { + if (seen.has(dependentName)) { + continue + } + + seen.add(dependentName) + queue.push(dependentName) + } + } + + return topologicalPackageNames(packages, [...seen]) +} + +const topologicalPackageNames = (packages, selectedNames) => { + const selected = new Set(selectedNames) + const ordered = [] + const visiting = new Set() + const visited = new Set() + const byName = new Map(packages.map((workspacePackage) => [workspacePackage.name, workspacePackage])) + + const visit = (packageName) => { + if (visited.has(packageName) || visiting.has(packageName)) { + return + } + + const workspacePackage = byName.get(packageName) + if (workspacePackage === undefined) { + return + } + + visiting.add(packageName) + for (const dependencyName of workspacePackage.dependencyNames) { + if (selected.has(dependencyName)) { + visit(dependencyName) + } + } + visiting.delete(packageName) + visited.add(packageName) + ordered.push(packageName) + } + + for (const workspacePackage of packages) { + if (selected.has(workspacePackage.name)) { + visit(workspacePackage.name) + } + } + + return ordered +} + +const selectedPackageNames = (packages, scriptName, names) => { + const requested = new Set(names) + return topologicalPackageNames( + packages, + packages + .filter((workspacePackage) => requested.has(workspacePackage.name) && workspacePackage.scripts[scriptName] !== undefined) + .map((workspacePackage) => workspacePackage.name) + ) +} + +const packageScriptCommand = (workspacePackage, scriptName, phase = scriptName) => ({ + args: ["run", "--filter", workspacePackage.name, scriptName], + command: "bun", + packageName: workspacePackage.name, + phase, + serial: workspacePackage.scripts[`pre${scriptName}`] !== undefined, + scriptName +}) + +const operationPackageNames = ({ affectedNames, all, group, ownerNames, packages }) => { + if (all) { + return packageNamesWithScript(packages, group) + } + + if (group === "lint" || group === "lint:effect") { + return ownerNames + } + + return affectedNames +} + +const buildCommandsForGroup = (input) => { + const names = operationPackageNames(input) + const packageByName = new Map(input.packages.map((workspacePackage) => [workspacePackage.name, workspacePackage])) + const primaryCommands = selectedPackageNames(input.packages, input.group, names).map((packageName) => + packageScriptCommand(packageByName.get(packageName), input.group, input.group) + ) + + if (input.group !== "lint") { + return primaryCommands + } + + const testLintOwners = input.all + ? packageNamesWithScript(input.packages, "lint:tests") + : uniqueSorted(input.changedFiles.flatMap((filePath) => { + const workspacePackage = packageForPath(input.packages, filePath) + if (workspacePackage === null || !filePath.startsWith(`${workspacePackage.dir}/tests/`)) { + return [] + } + return [workspacePackage.name] + })) + + const testLintCommands = selectedPackageNames(input.packages, "lint:tests", testLintOwners).map((packageName) => + packageScriptCommand(packageByName.get(packageName), "lint:tests", "lint") + ) + + return [...primaryCommands, ...testLintCommands] +} + +const changedOwnerNames = (packages, changedFiles) => + uniqueSorted(changedFiles.flatMap((filePath) => { + const workspacePackage = packageForPath(packages, filePath) + return workspacePackage === null ? [] : [workspacePackage.name] + })) + +const unknownChangedFiles = (packages, changedFiles) => + changedFiles.filter((filePath) => !isDocsOnlyPath(filePath) && !isFullRunPath(filePath) && packageForPath(packages, filePath) === null) + +const planMode = ({ all, changedFiles, diffFailed, packages }) => { + if (all) { + return { mode: "all", reason: "explicit --all" } + } + + if (diffFailed) { + return { mode: "all", reason: "changed-file detection failed" } + } + + if (changedFiles.length === 0) { + return { mode: "skip", reason: "no changed files" } + } + + const fullRunPath = changedFiles.find(isFullRunPath) + if (fullRunPath !== undefined) { + return { mode: "all", reason: `global change: ${fullRunPath}` } + } + + const unknownPath = unknownChangedFiles(packages, changedFiles)[0] + if (unknownPath !== undefined) { + return { mode: "all", reason: `unknown change: ${unknownPath}` } + } + + if (changedOwnerNames(packages, changedFiles).length === 0) { + return { mode: "skip", reason: "docs-only changes" } + } + + return { mode: "affected", reason: "package-scoped changes" } +} + +export const createChangedChecksPlan = ({ all = false, changedFiles, diffFailed = false, operation, packages }) => { + const normalizedChangedFiles = uniqueSorted(changedFiles.map(normalizePath).filter((filePath) => filePath.length > 0)) + const modeResult = planMode({ all, changedFiles: normalizedChangedFiles, diffFailed, packages }) + const ownerNames = modeResult.mode === "all" ? packages.map((workspacePackage) => workspacePackage.name) : changedOwnerNames(packages, normalizedChangedFiles) + const affectedNames = modeResult.mode === "all" + ? packages.map((workspacePackage) => workspacePackage.name) + : affectedClosure(packages, ownerNames) + const groups = commandGroups[operation] ?? [] + + if (modeResult.mode === "skip") { + return { + affectedPackages: [], + changedFiles: normalizedChangedFiles, + commands: [], + mode: modeResult.mode, + operation, + ownerPackages: [], + reason: modeResult.reason + } + } + + const commands = groups.flatMap((group) => + buildCommandsForGroup({ + affectedNames, + all: modeResult.mode === "all", + changedFiles: normalizedChangedFiles, + group, + ownerNames, + packages + }) + ) + + return { + affectedPackages: modeResult.mode === "all" ? packageNamesInCommands(commands) : affectedNames, + changedFiles: normalizedChangedFiles, + commands, + mode: modeResult.mode, + operation, + ownerPackages: ownerNames, + reason: modeResult.reason + } +} + +const packageNamesInCommands = (commands) => uniqueSorted(commands.map((command) => command.packageName)) + +export const parseChangedChecksArgs = (args) => { + const [operation, ...rest] = args + if (!operations.has(operation)) { + throw new Error(`Usage: bun scripts/changed-checks.mjs <${[...operations].join("|")}> [--base ] [--head ] [--all] [--dry-run] [--matrix] [--concurrency ]`) + } + + const parsed = { + all: false, + base: process.env.DOCKER_GIT_CHANGED_BASE || "", + concurrency: Math.max(1, Number.parseInt(process.env.DOCKER_GIT_CHECK_CONCURRENCY || "4", 10) || 4), + dryRun: false, + head: process.env.DOCKER_GIT_CHANGED_HEAD || "HEAD", + matrix: false, + operation + } + + for (let index = 0; index < rest.length; index += 1) { + const arg = rest[index] + if (arg === "--all") { + parsed.all = true + continue + } + if (arg === "--dry-run") { + parsed.dryRun = true + continue + } + if (arg === "--matrix") { + parsed.matrix = true + continue + } + if (arg === "--concurrency") { + parsed.concurrency = Math.max(1, Number.parseInt(rest[index + 1] ?? "4", 10) || 4) + index += 1 + continue + } + if (arg === "--base") { + parsed.base = rest[index + 1] ?? "" + index += 1 + continue + } + if (arg === "--head") { + parsed.head = rest[index + 1] ?? "HEAD" + index += 1 + continue + } + + throw new Error(`Unknown argument: ${arg}`) + } + + return parsed +} + +const runCapture = (command, args, cwd) => { + const result = spawnSync(command, args, { cwd, encoding: "utf8" }) + return { + ok: result.status === 0, + stdout: result.stdout.trim() + } +} + +const lines = (text) => text.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0) + +const changedFilesFromGit = ({ base, head, rootDir }) => { + if (base.length > 0) { + const mergeBase = runCapture("git", ["merge-base", base, head], rootDir) + if (!mergeBase.ok || mergeBase.stdout.length === 0) { + return { changedFiles: [], diffFailed: true } + } + + const diff = runCapture("git", ["diff", "--name-only", "--diff-filter=ACMR", mergeBase.stdout, head, "--"], rootDir) + return diff.ok + ? { changedFiles: lines(diff.stdout), diffFailed: false } + : { changedFiles: [], diffFailed: true } + } + + if (process.env.CI === "true") { + return { changedFiles: [], diffFailed: true } + } + + const tracked = runCapture("git", ["diff", "--name-only", "--diff-filter=ACMR", "HEAD", "--"], rootDir) + const untracked = runCapture("git", ["ls-files", "--others", "--exclude-standard"], rootDir) + if (!tracked.ok || !untracked.ok) { + return { changedFiles: [], diffFailed: true } + } + + return { + changedFiles: uniqueSorted([...lines(tracked.stdout), ...lines(untracked.stdout)]), + diffFailed: false + } +} + +const printPlan = (plan) => { + console.log(`changed-checks: ${plan.operation} ${plan.mode} (${plan.reason})`) + if (plan.changedFiles.length > 0) { + console.log(`changed-checks: files ${plan.changedFiles.join(", ")}`) + } + if (plan.ownerPackages.length > 0) { + console.log(`changed-checks: owners ${plan.ownerPackages.join(", ")}`) + } + if (plan.affectedPackages.length > 0) { + console.log(`changed-checks: affected ${plan.affectedPackages.join(", ")}`) + } + if (plan.commands.length === 0) { + console.log("changed-checks: no commands to run") + return + } + + for (const command of plan.commands) { + console.log(`changed-checks: run ${formatCommand(command)}`) + } +} + +export const createGithubMatrix = (plan) => ({ + include: plan.commands.map((command) => ({ + label: `${command.packageName} ${command.scriptName}`, + packageName: command.packageName, + script: command.scriptName + })) +}) + +const runOneCommand = (command, rootDir) => + new Promise((resolve) => { + const child = spawn(command.command, command.args, { + cwd: rootDir, + env: process.env, + stdio: "inherit" + }) + + child.on("error", () => resolve(1)) + child.on("close", (code) => resolve(code ?? 1)) + }) + +const runCommandPhase = async (commands, rootDir, concurrency) => { + let next = 0 + let firstFailure = 0 + const workerCount = Math.min(concurrency, commands.length) + + await Promise.all(Array.from({ length: workerCount }, async () => { + while (next < commands.length && firstFailure === 0) { + const command = commands[next] + next += 1 + const status = await runOneCommand(command, rootDir) + if (status !== 0 && firstFailure === 0) { + firstFailure = status + } + } + })) + + return firstFailure +} + +const groupedByPhase = (commands) => { + const phases = [] + const byPhase = new Map() + + for (const command of commands) { + if (!byPhase.has(command.phase)) { + byPhase.set(command.phase, []) + phases.push(command.phase) + } + byPhase.get(command.phase).push(command) + } + + return phases.map((phase) => byPhase.get(phase)) +} + +const runCommands = async (commands, rootDir, concurrency) => { + for (const phaseCommands of groupedByPhase(commands)) { + const serialCommands = phaseCommands.filter((command) => command.serial) + const parallelCommands = phaseCommands.filter((command) => !command.serial) + const serialStatus = await runCommandPhase(serialCommands, rootDir, 1) + if (serialStatus !== 0) { + return serialStatus + } + const status = await runCommandPhase(parallelCommands, rootDir, concurrency) + if (status !== 0) { + return status + } + } + + return 0 +} + +export const runChangedChecksCli = async (argv, rootDir = process.cwd()) => { + const args = parseChangedChecksArgs(argv) + const packages = loadWorkspacePackages(rootDir) + const diff = changedFilesFromGit({ base: args.base, head: args.head, rootDir }) + const plan = createChangedChecksPlan({ + all: args.all, + changedFiles: diff.changedFiles, + diffFailed: diff.diffFailed, + operation: args.operation, + packages + }) + + if (args.matrix) { + console.log(JSON.stringify(createGithubMatrix(plan))) + return 0 + } + + printPlan(plan) + if (args.dryRun || plan.commands.length === 0) { + return 0 + } + + return await runCommands(plan.commands, rootDir, args.concurrency) +} + +if (process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href) { + process.exitCode = await runChangedChecksCli(process.argv.slice(2)) +} diff --git a/scripts/npx b/scripts/npx index 3fb65412..b9cf4fff 100755 --- a/scripts/npx +++ b/scripts/npx @@ -2,7 +2,7 @@ set -eu # CHANGE: provide a minimal npx shim for Bun-managed workspaces -# WHY: some tools (e.g. vibecode-linter) call `npx tsc` and should resolve the local workspace binary through Bun instead of downloading a similarly named package +# WHY: workspace tools that call `npx tsc` should resolve the local workspace binary through Bun instead of downloading a similarly named package # QUOTE(ТЗ): n/a # REF: issue-27 (CI/test harness) # SOURCE: n/a From 13e4f66febbf608c527087c22d7937c23d7476fc Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 20 Jun 2026 10:55:17 +0000 Subject: [PATCH 2/8] fix(ci): build module test prerequisites --- packages/api/package.json | 2 +- packages/app/package.json | 1 + packages/lib/package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/api/package.json b/packages/api/package.json index da8bc492..21a4ac1e 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -17,7 +17,7 @@ "lint": "eslint . --cache --cache-location .eslintcache --cache-strategy content", "lint:effect": "eslint --config eslint.effect-ts-check.config.mjs . --cache --cache-location .eslintcache-effect --cache-strategy content", "lint:openapi-contract": "vitest run tests/openapi.test.ts", - "pretest": "bun run --cwd ../terminal build && bun run --cwd ../container build && bun run --cwd ../lib build", + "pretest": "bun run --cwd ../terminal build && bun run --cwd ../container build && bun run --cwd ../docker-git-session-sync build && bun run --cwd ../lib build", "test": "vitest run" }, "dependencies": { diff --git a/packages/app/package.json b/packages/app/package.json index 62b3c86f..0f2b22d6 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -25,6 +25,7 @@ "serve:web": "bun scripts/serve-dist-web.mjs", "prelint": "bun run --cwd ../docker-git-session-sync build && bun run --cwd ../terminal build", "lint": "NODE_OPTIONS=--max-old-space-size=4096 eslint src --cache --cache-location .eslintcache --cache-strategy content", + "prelint:tests": "bun run --cwd ../docker-git-session-sync build && bun run --cwd ../terminal build", "lint:tests": "NODE_OPTIONS=--max-old-space-size=4096 eslint tests --cache --cache-location .eslintcache-tests --cache-strategy content", "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 eslint --config eslint.effect-ts-check.config.mjs . --cache --cache-location .eslintcache-effect --cache-strategy content", "prebuild:docker-git": "bun install --cwd ../.. && bun run --cwd ../docker-git-session-sync build && bun run --cwd ../terminal build", diff --git a/packages/lib/package.json b/packages/lib/package.json index 50df55f9..ad2cf980 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -16,7 +16,7 @@ "lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 eslint --config eslint.effect-ts-check.config.mjs . --cache --cache-location .eslintcache-effect --cache-strategy content", "pretypecheck": "bun run --cwd ../container build", "typecheck": "tsc --noEmit -p tsconfig.json", - "pretest": "bun run --cwd ../container build", + "pretest": "bun run --cwd ../container build && bun run --cwd ../docker-git-session-sync build", "test": "vitest run --passWithNoTests" }, "repository": { From e0e029e6b42da0e32b7c408d9bd7289c8a94f703 Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:16:24 +0000 Subject: [PATCH 3/8] ci(e2e): share prebuilt artifact across e2e jobs --- .github/actions/setup/action.yml | 5 + .github/workflows/check.yml | 95 ++++++++++++- .../src/docker-git/browser-frontend-launch.ts | 82 +++++++++++ .../docker-git/browser-frontend-prebuilt.ts | 39 ++++++ .../app/src/docker-git/browser-frontend.ts | 48 +------ .../tests/docker-git/browser-frontend.test.ts | 23 ++++ scripts/ci/pack-e2e-prebuilt.mjs | 128 ++++++++++++++++++ scripts/e2e/_lib.sh | 25 ++++ scripts/e2e/browser-command.sh | 3 +- 9 files changed, 394 insertions(+), 54 deletions(-) create mode 100644 packages/app/src/docker-git/browser-frontend-launch.ts create mode 100644 packages/app/src/docker-git/browser-frontend-prebuilt.ts create mode 100644 scripts/ci/pack-e2e-prebuilt.mjs diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 0887ea34..065692f4 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -9,6 +9,10 @@ inputs: description: The version of Node.js to install for compatibility/native builds required: true default: 24 + install-dependencies: + description: Whether to run bun install for the workspace + required: true + default: "true" runs: using: composite @@ -56,5 +60,6 @@ runs: shell: bash run: npm install -g node-gyp - name: Install dependencies + if: inputs.install-dependencies == 'true' shell: bash run: bun install --frozen-lockfile diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index b6ebee50..eada24d8 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -230,6 +230,28 @@ jobs: fi echo "Effect-TS lint module job result: $MODULE_RESULT" + e2e-prepare: + name: E2E prepare + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v6 + - name: Install dependencies + uses: ./.github/actions/setup + - name: Build E2E prebuilt payload + run: | + bun run --cwd packages/app build:docker-git:reuse-install + bun run --cwd packages/app build:web + - name: Pack E2E prebuilt artifact + run: | + mkdir -p artifacts + bun scripts/ci/pack-e2e-prebuilt.mjs artifacts/docker-git-e2e-prebuilt.tgz + - name: Upload E2E prebuilt artifact + uses: actions/upload-artifact@v7 + with: + name: docker-git-e2e-prebuilt + path: artifacts/docker-git-e2e-prebuilt.tgz + e2e-local-package: name: E2E (Local package CLI) runs-on: ubuntu-latest @@ -243,17 +265,28 @@ jobs: e2e-browser-command: name: E2E (Browser command) + needs: e2e-prepare runs-on: ubuntu-latest timeout-minutes: 40 env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" - DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1" + DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" + DOCKER_GIT_E2E_USE_PREBUILT_WEB: "1" steps: - uses: actions/checkout@v6 with: submodules: true - name: Install dependencies uses: ./.github/actions/setup + with: + install-dependencies: "false" + - name: Download E2E prebuilt artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-prebuilt + path: artifacts + - name: Restore E2E prebuilt artifact + run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - name: Docker info @@ -263,17 +296,27 @@ jobs: e2e-opencode: name: E2E (OpenCode) + needs: e2e-prepare runs-on: ubuntu-latest timeout-minutes: 40 env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" - DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1" + DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" steps: - uses: actions/checkout@v6 with: submodules: true - name: Install dependencies uses: ./.github/actions/setup + with: + install-dependencies: "false" + - name: Download E2E prebuilt artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-prebuilt + path: artifacts + - name: Restore E2E prebuilt artifact + run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - name: Docker info @@ -283,17 +326,27 @@ jobs: e2e-clone-cache: name: E2E (Clone cache) + needs: e2e-prepare runs-on: ubuntu-latest timeout-minutes: 40 env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" - DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1" + DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" steps: - uses: actions/checkout@v6 with: submodules: true - name: Install dependencies uses: ./.github/actions/setup + with: + install-dependencies: "false" + - name: Download E2E prebuilt artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-prebuilt + path: artifacts + - name: Restore E2E prebuilt artifact + run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - name: Docker info @@ -303,17 +356,27 @@ jobs: e2e-login-context: name: E2E (Login context) + needs: e2e-prepare runs-on: ubuntu-latest timeout-minutes: 40 env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" - DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1" + DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" steps: - uses: actions/checkout@v6 with: submodules: true - name: Install dependencies uses: ./.github/actions/setup + with: + install-dependencies: "false" + - name: Download E2E prebuilt artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-prebuilt + path: artifacts + - name: Restore E2E prebuilt artifact + run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - name: Docker info @@ -323,17 +386,27 @@ jobs: e2e-runtime-volumes-ssh: name: E2E (Runtime volumes + SSH) + needs: e2e-prepare runs-on: ubuntu-latest timeout-minutes: 60 env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" - DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1" + DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" steps: - uses: actions/checkout@v6 with: submodules: true - name: Install dependencies uses: ./.github/actions/setup + with: + install-dependencies: "false" + - name: Download E2E prebuilt artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-prebuilt + path: artifacts + - name: Restore E2E prebuilt artifact + run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - name: Docker info @@ -343,17 +416,27 @@ jobs: e2e-clone-auto-open-ssh: name: E2E (Clone auto-open SSH) + needs: e2e-prepare runs-on: ubuntu-latest timeout-minutes: 40 env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" - DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1" + DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" steps: - uses: actions/checkout@v6 with: submodules: true - name: Install dependencies uses: ./.github/actions/setup + with: + install-dependencies: "false" + - name: Download E2E prebuilt artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-prebuilt + path: artifacts + - name: Restore E2E prebuilt artifact + run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - name: Docker info diff --git a/packages/app/src/docker-git/browser-frontend-launch.ts b/packages/app/src/docker-git/browser-frontend-launch.ts new file mode 100644 index 00000000..541b9c25 --- /dev/null +++ b/packages/app/src/docker-git/browser-frontend-launch.ts @@ -0,0 +1,82 @@ +import type * as CommandExecutor from "@effect/platform/CommandExecutor" +import type { PlatformError } from "@effect/platform/Error" +import { Effect } from "effect" + +import { ensurePrebuiltBrowserFrontend, shouldUsePrebuiltBrowserFrontend } from "./browser-frontend-prebuilt.js" +import type { BrowserFrontendStartDecision } from "./browser-frontend-state.js" +import { runCommandExitCodeStreaming } from "./frontend-lib/shell/command-runner.js" +import type { ControllerBootstrapError } from "./host-errors.js" + +type BrowserFrontendLaunch = { + readonly env: Readonly> + readonly localUrl: string +} + +const browserFrontendError = (message: string): ControllerBootstrapError => ({ + _tag: "ControllerBootstrapError", + message +}) + +const copyProcessEnv = (): Readonly> => { + const env: Record = {} + for (const [key, value] of Object.entries(process.env)) { + if (value !== undefined) { + env[key] = value + } + } + return env +} + +const browserEnv = (decision: BrowserFrontendStartDecision): Readonly> => ({ + ...copyProcessEnv(), + DOCKER_GIT_API_URL: decision.apiBaseUrl, + DOCKER_GIT_WEB_HOST: decision.host, + DOCKER_GIT_WEB_PORT: decision.port, + DOCKER_GIT_WEB_REVISION: decision.webRevision, + DOCKER_GIT_WEB_STATE_PATH: decision.statePath +}) + +const ensureSuccess = ( + exitCode: number, + action: string +): Effect.Effect => + exitCode === 0 + ? Effect.void + : Effect.fail(browserFrontendError(`${action} failed with exit code ${exitCode}.`)) + +const runStreaming = ( + args: ReadonlyArray, + env: Readonly> +): Effect.Effect => + runCommandExitCodeStreaming({ args, command: "bun", cwd: process.cwd(), env }) + +// CHANGE: share the browser frontend build phase between foreground and daemon modes. +// WHY: daemon mode must not drift from foreground mode in revision, environment, or build failure semantics. +// QUOTE(ТЗ): "Run browser with support dameon mode, like a flag -d" +// REF: issue-373 +// SOURCE: n/a +// FORMAT THEOREM: forall mode in {foreground,daemon}: launch(mode) -> built_or_prebuilt(webRevision) +// PURITY: SHELL +// EFFECT: Effect +// INVARIANT: launch env is derived exactly once from BrowserFrontendStartDecision +// COMPLEXITY: O(build)/O(env) +export const buildBrowserFrontendLaunch = ( + decision: BrowserFrontendStartDecision +): Effect.Effect => + Effect.gen(function*(_) { + const env = browserEnv(decision) + const localUrl = `http://${decision.host}:${decision.port}/` + + if (shouldUsePrebuiltBrowserFrontend()) { + yield* _( + Effect.log(`Using prebuilt docker-git browser frontend ${decision.webRevision} for API ${decision.apiBaseUrl}.`) + ) + yield* _(ensurePrebuiltBrowserFrontend()) + return { env, localUrl } + } + + yield* _(Effect.log(`Building docker-git browser frontend ${decision.webRevision} for API ${decision.apiBaseUrl}.`)) + const buildExitCode = yield* _(runStreaming(["run", "--cwd", "packages/app", "build:web"], env)) + yield* _(ensureSuccess(buildExitCode, "Browser frontend build")) + return { env, localUrl } + }) diff --git a/packages/app/src/docker-git/browser-frontend-prebuilt.ts b/packages/app/src/docker-git/browser-frontend-prebuilt.ts new file mode 100644 index 00000000..25b6e1ef --- /dev/null +++ b/packages/app/src/docker-git/browser-frontend-prebuilt.ts @@ -0,0 +1,39 @@ +import type * as CommandExecutor from "@effect/platform/CommandExecutor" +import type { PlatformError } from "@effect/platform/Error" +import { Effect, pipe } from "effect" + +import { runCommandExitCode } from "./frontend-lib/shell/command-runner.js" +import type { ControllerBootstrapError } from "./host-errors.js" + +const browserFrontendError = (message: string): ControllerBootstrapError => ({ + _tag: "ControllerBootstrapError", + message +}) + +const isTruthyEnv = (value: string | undefined): boolean => + value !== undefined && ["1", "on", "true", "yes"].includes(value.trim().toLowerCase()) + +export const shouldUsePrebuiltBrowserFrontend = (): boolean => + isTruthyEnv(process.env["DOCKER_GIT_E2E_USE_PREBUILT_WEB"]) + +export const ensurePrebuiltBrowserFrontend = (): Effect.Effect< + void, + ControllerBootstrapError | PlatformError, + CommandExecutor.CommandExecutor +> => + pipe( + runCommandExitCode({ + args: ["-c", "test -f packages/app/dist-web/index.html"], + command: "sh", + cwd: process.cwd() + }), + Effect.flatMap((exitCode) => + exitCode === 0 + ? Effect.void + : Effect.fail( + browserFrontendError( + "Prebuilt browser frontend artifact is missing packages/app/dist-web/index.html." + ) + ) + ) + ) diff --git a/packages/app/src/docker-git/browser-frontend.ts b/packages/app/src/docker-git/browser-frontend.ts index 56b9798d..a473f97b 100644 --- a/packages/app/src/docker-git/browser-frontend.ts +++ b/packages/app/src/docker-git/browser-frontend.ts @@ -2,6 +2,7 @@ import type * as CommandExecutor from "@effect/platform/CommandExecutor" import type { PlatformError } from "@effect/platform/Error" import { Effect, pipe } from "effect" +import { buildBrowserFrontendLaunch } from "./browser-frontend-launch.js" import { type BrowserFrontendReuseInput, type BrowserFrontendStartDecision, @@ -32,16 +33,6 @@ const browserFrontendError = (message: string): ControllerBootstrapError => ({ message }) -const copyProcessEnv = (): Readonly> => { - const env: Record = {} - for (const [key, value] of Object.entries(process.env)) { - if (value !== undefined) { - env[key] = value - } - } - return env -} - // CHANGE: expose `docker-git browser` on all host interfaces by default // WHY: LAN clients cannot connect when the web shell is bound to loopback only // QUOTE(ТЗ): "Я хочу подключить" @@ -69,26 +60,12 @@ export interface BrowserFrontendCommandOptions { const browserFrontendForegroundOptions: BrowserFrontendCommandOptions = { daemon: false } -type BrowserFrontendLaunch = { - readonly env: Readonly> - readonly localUrl: string -} - type BrowserFrontendRunnerEffect = Effect.Effect< void, ControllerBootstrapError | PlatformError, CommandExecutor.CommandExecutor > -const browserEnv = (decision: BrowserFrontendStartDecision): Readonly> => ({ - ...copyProcessEnv(), - DOCKER_GIT_API_URL: decision.apiBaseUrl, - DOCKER_GIT_WEB_HOST: decision.host, - DOCKER_GIT_WEB_PORT: decision.port, - DOCKER_GIT_WEB_REVISION: decision.webRevision, - DOCKER_GIT_WEB_STATE_PATH: decision.statePath -}) - const runStreaming = ( args: ReadonlyArray, env: Readonly> @@ -325,29 +302,6 @@ const ensureSuccess = ( ? Effect.void : Effect.fail(browserFrontendError(`${action} failed with exit code ${exitCode}.`)) -// CHANGE: share the browser frontend build phase between foreground and daemon modes. -// WHY: daemon mode must not drift from foreground mode in revision, environment, or build failure semantics. -// QUOTE(ТЗ): "Run browser with support dameon mode, like a flag -d" -// REF: issue-373 -// SOURCE: n/a -// FORMAT THEOREM: forall mode in {foreground,daemon}: launch(mode) -> built(webRevision) -// PURITY: SHELL -// EFFECT: Effect -// INVARIANT: launch env is derived exactly once from BrowserFrontendStartDecision -// COMPLEXITY: O(build)/O(env) -const buildBrowserFrontendLaunch = ( - decision: BrowserFrontendStartDecision -): Effect.Effect => - Effect.gen(function*(_) { - const env = browserEnv(decision) - const localUrl = `http://${decision.host}:${decision.port}/` - - yield* _(Effect.log(`Building docker-git browser frontend ${decision.webRevision} for API ${decision.apiBaseUrl}.`)) - const buildExitCode = yield* _(runStreaming(["run", "--cwd", "packages/app", "build:web"], env)) - yield* _(ensureSuccess(buildExitCode, "Browser frontend build")) - return { env, localUrl } - }) - export const runBrowserFrontend = (decision: BrowserFrontendStartDecision): BrowserFrontendRunnerEffect => Effect.gen(function*(_) { const launch = yield* _(buildBrowserFrontendLaunch(decision)) diff --git a/packages/app/tests/docker-git/browser-frontend.test.ts b/packages/app/tests/docker-git/browser-frontend.test.ts index 1fb8b437..a6283baa 100644 --- a/packages/app/tests/docker-git/browser-frontend.test.ts +++ b/packages/app/tests/docker-git/browser-frontend.test.ts @@ -159,6 +159,7 @@ describe("browser frontend command", () => { yield* _( Effect.sync(() => { restoreTty() + delete process.env["DOCKER_GIT_E2E_USE_PREBUILT_WEB"] delete process.env["DOCKER_GIT_WEB_PORT"] delete process.env["DOCKER_GIT_WEB_HOST"] delete process.env["DOCKER_GIT_PROJECTS_ROOT"] @@ -181,6 +182,28 @@ describe("browser frontend command", () => { expect(runCommandExitCodeStreamingMock).toHaveBeenCalledTimes(2) })) + it.effect("uses a prebuilt web artifact when E2E requests it", () => + Effect.gen(function*(_) { + process.env["DOCKER_GIT_E2E_USE_PREBUILT_WEB"] = "1" + + yield* _(runBrowserCommandUnderTest) + + expect(runCommandExitCodeMock).toHaveBeenCalledTimes(1) + expect(runCommandExitCodeMock).toHaveBeenCalledWith( + expect.objectContaining({ + args: ["-c", "test -f packages/app/dist-web/index.html"], + command: "sh" + }) + ) + expect(runCommandExitCodeStreamingMock).toHaveBeenCalledTimes(1) + expect(runCommandExitCodeStreamingMock).toHaveBeenCalledWith( + expect.objectContaining({ + args: ["run", "--cwd", "packages/app", "serve:web"], + command: "bun" + }) + ) + })) + it.effect("prefers the reachable host API URL over a selected Docker bridge URL for the web proxy", () => Effect.gen(function*(_) { useReachableHostApiProbe() diff --git a/scripts/ci/pack-e2e-prebuilt.mjs b/scripts/ci/pack-e2e-prebuilt.mjs new file mode 100644 index 00000000..8d2d1481 --- /dev/null +++ b/scripts/ci/pack-e2e-prebuilt.mjs @@ -0,0 +1,128 @@ +#!/usr/bin/env bun +import { + existsSync, + lstatSync, + mkdtempSync, + readFileSync, + realpathSync, + readdirSync, + rmSync, + writeFileSync +} from "node:fs" +import { tmpdir } from "node:os" +import { relative, resolve, sep } from "node:path" +import { spawnSync } from "node:child_process" + +const outputPath = process.argv[2] ?? "artifacts/docker-git-e2e-prebuilt.tgz" +const repoRoot = process.cwd() +const outputAbsolutePath = resolve(repoRoot, outputPath) +const paths = new Set() +const queue = [] +const scanned = new Set() + +const toRelativePath = (path) => { + const relativePath = relative(repoRoot, path) + return relativePath.length === 0 ? "." : relativePath +} + +const fail = (message) => { + console.error(message) + process.exit(1) +} + +const addExistingPath = (path) => { + if (!existsSync(path)) { + fail(`Missing E2E artifact path: ${path}`) + } + + const relativePath = toRelativePath(resolve(repoRoot, path)) + if (!paths.has(relativePath)) { + paths.add(relativePath) + queue.push(relativePath) + } +} + +const nearestNodeModulesDir = (absolutePath) => { + const parts = absolutePath.split(sep) + for (let index = parts.length - 1; index >= 0; index -= 1) { + if (parts[index] === "node_modules") { + return parts.slice(0, index + 1).join(sep) + } + } + return null +} + +const addModulePath = (path) => { + addExistingPath(path) + + const realPath = realpathSync(path) + addExistingPath(realPath) + + const nodeModulesDir = nearestNodeModulesDir(realPath) + if (nodeModulesDir !== null) { + addExistingPath(nodeModulesDir) + } +} + +const scanSymlinks = (relativePath) => { + const absolutePath = resolve(repoRoot, relativePath) + const stat = lstatSync(absolutePath) + + if (stat.isSymbolicLink()) { + addModulePath(absolutePath) + return + } + if (!stat.isDirectory()) { + return + } + + const realDirectoryPath = realpathSync(absolutePath) + if (scanned.has(realDirectoryPath)) { + return + } + scanned.add(realDirectoryPath) + + for (const entry of readdirSync(absolutePath)) { + scanSymlinks(relative(repoRoot, resolve(absolutePath, entry))) + } +} + +const packageJson = JSON.parse(readFileSync("packages/app/package.json", "utf8")) +const runtimeDependencies = Object.keys(packageJson.dependencies ?? {}) +const extraRuntimePackages = ["ws"] + +for (const path of [ + "packages/app/dist", + "packages/app/dist-web", + "packages/app/package.json", + "packages/app/scripts/serve-dist-web.mjs", + "packages/app/scripts/serve-dist-web-routing.mjs", + "packages/docker-git-session-sync/dist", + "packages/terminal/dist" +]) { + addExistingPath(path) +} + +for (const dependency of [...runtimeDependencies, ...extraRuntimePackages]) { + addModulePath(`packages/app/node_modules/${dependency}`) +} + +for (let index = 0; index < queue.length; index += 1) { + scanSymlinks(queue[index]) +} + +const tempDir = mkdtempSync(resolve(tmpdir(), "docker-git-e2e-artifact-")) +const pathListPath = resolve(tempDir, "paths.txt") + +try { + writeFileSync(pathListPath, `${[...paths].sort().join("\n")}\n`) + const tarResult = spawnSync("tar", ["-czf", outputAbsolutePath, "-T", pathListPath], { + cwd: repoRoot, + stdio: "inherit" + }) + if (tarResult.status !== 0) { + process.exit(tarResult.status ?? 1) + } +} finally { + rmSync(tempDir, { force: true, recursive: true }) +} diff --git a/scripts/e2e/_lib.sh b/scripts/e2e/_lib.sh index 6bd3a7ba..604dcbfe 100644 --- a/scripts/e2e/_lib.sh +++ b/scripts/e2e/_lib.sh @@ -546,6 +546,23 @@ dg_build_docker_git_cli() { dg_log_duration "docker-git CLI build" "$started_at" } +dg_require_prebuilt_docker_git_cli() { + local repo_root="$1" + local required_paths=( + "$repo_root/packages/app/dist/src/docker-git/main.js" + "$repo_root/packages/docker-git-session-sync/dist/docker-git-session-sync.js" + "$repo_root/packages/terminal/dist/index.js" + ) + local path + + for path in "${required_paths[@]}"; do + if [[ ! -f "$path" ]]; then + echo "e2e: prebuilt docker-git CLI artifact is incomplete: missing $path" >&2 + return 1 + fi + done +} + dg_prepare_docker_git_cli() { local repo_root="$1" local bin_dir="$2" @@ -553,6 +570,14 @@ dg_prepare_docker_git_cli() { started_at="$(date +%s)" + dg_ensure_bun + if dg_is_truthy "${DOCKER_GIT_E2E_USE_PREBUILT_CLI:-0}"; then + dg_require_prebuilt_docker_git_cli "$repo_root" + echo "e2e: using prebuilt docker-git CLI artifact" >&2 + dg_log_duration "prepare docker-git CLI" "$started_at" + return 0 + fi + dg_prepare_bun_workspace "$repo_root" "$bin_dir" dg_build_docker_git_cli "$repo_root" dg_log_duration "prepare docker-git CLI" "$started_at" diff --git a/scripts/e2e/browser-command.sh b/scripts/e2e/browser-command.sh index ece6fe16..8b8ccafd 100755 --- a/scripts/e2e/browser-command.sh +++ b/scripts/e2e/browser-command.sh @@ -191,7 +191,8 @@ dg_ensure_docker "$E2E_BIN" dg_prepare_docker_git_cli "$REPO_ROOT" "$E2E_BIN" cd "$REPO_ROOT" -if dg_is_truthy "${DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL:-0}"; then +if dg_is_truthy "${DOCKER_GIT_E2E_USE_PREBUILT_CLI:-0}" \ + || dg_is_truthy "${DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL:-0}"; then setsid bash -lc 'bun packages/app/dist/src/docker-git/main.js browser' >"$BROWSER_LOG" 2>&1 & else setsid bash -lc 'bun run docker-git -- browser' >"$BROWSER_LOG" 2>&1 & From 0e94cec6b9e2d3f1671aad0ab333c0a17019d754 Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 20 Jun 2026 12:46:29 +0000 Subject: [PATCH 4/8] ci(e2e): reuse prebuilt controller image --- .github/workflows/check.yml | 89 ++++++++++++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index eada24d8..5ba1b2be 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -233,15 +233,36 @@ jobs: e2e-prepare: name: E2E prepare runs-on: ubuntu-latest - timeout-minutes: 15 + timeout-minutes: 30 + env: + DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" steps: - uses: actions/checkout@v6 + with: + submodules: true - name: Install dependencies uses: ./.github/actions/setup - name: Build E2E prebuilt payload run: | bun run --cwd packages/app build:docker-git:reuse-install bun run --cwd packages/app build:web + - name: Free Docker disk + uses: ./.github/actions/free-docker-disk + - name: Build E2E controller image + run: | + set -euo pipefail + + DOCKER_GIT_CONTROLLER_REV="$(bun --cwd packages/app scripts/print-controller-revision.ts "$PWD/docker-compose.yml")" + export DOCKER_GIT_CONTROLLER_REV + + echo "Building docker-git controller image revision: ${DOCKER_GIT_CONTROLLER_REV}" + docker compose --project-name docker-git -f docker-compose.yml build api + docker image inspect docker-git-api \ + -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' + + mkdir -p artifacts + docker save docker-git-api | gzip -1 > artifacts/docker-git-e2e-controller-image.tar.gz + ls -lh artifacts/docker-git-e2e-controller-image.tar.gz - name: Pack E2E prebuilt artifact run: | mkdir -p artifacts @@ -251,6 +272,12 @@ jobs: with: name: docker-git-e2e-prebuilt path: artifacts/docker-git-e2e-prebuilt.tgz + - name: Upload E2E controller image artifact + uses: actions/upload-artifact@v7 + with: + name: docker-git-e2e-controller-image + path: artifacts/docker-git-e2e-controller-image.tar.gz + compression-level: 0 e2e-local-package: name: E2E (Local package CLI) @@ -285,10 +312,20 @@ jobs: with: name: docker-git-e2e-prebuilt path: artifacts + - name: Download E2E controller image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-controller-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk + - name: Load E2E controller image + run: | + gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load + docker image inspect docker-git-api \ + -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' - name: Docker info run: docker version && docker compose version - name: Browser command startup @@ -315,10 +352,20 @@ jobs: with: name: docker-git-e2e-prebuilt path: artifacts + - name: Download E2E controller image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-controller-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk + - name: Load E2E controller image + run: | + gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load + docker image inspect docker-git-api \ + -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' - name: Docker info run: docker version && docker compose version - name: OpenCode autoconnect @@ -345,10 +392,20 @@ jobs: with: name: docker-git-e2e-prebuilt path: artifacts + - name: Download E2E controller image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-controller-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk + - name: Load E2E controller image + run: | + gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load + docker image inspect docker-git-api \ + -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' - name: Docker info run: docker version && docker compose version - name: Clone cache reuse @@ -375,10 +432,20 @@ jobs: with: name: docker-git-e2e-prebuilt path: artifacts + - name: Download E2E controller image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-controller-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk + - name: Load E2E controller image + run: | + gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load + docker image inspect docker-git-api \ + -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' - name: Docker info run: docker version && docker compose version - name: Login context notice @@ -405,10 +472,20 @@ jobs: with: name: docker-git-e2e-prebuilt path: artifacts + - name: Download E2E controller image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-controller-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk + - name: Load E2E controller image + run: | + gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load + docker image inspect docker-git-api \ + -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' - name: Docker info run: docker version && docker compose version - name: Runtime volumes + host SSH CLI @@ -435,10 +512,20 @@ jobs: with: name: docker-git-e2e-prebuilt path: artifacts + - name: Download E2E controller image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-controller-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk + - name: Load E2E controller image + run: | + gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load + docker image inspect docker-git-api \ + -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' - name: Docker info run: docker version && docker compose version - name: Clone auto-open SSH From 22d4c5887245b1fbec2526bee4d1e8caf311add1 Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:24:14 +0000 Subject: [PATCH 5/8] ci(e2e): reuse prebuilt project image --- .github/workflows/check.yml | 83 +++++++++++++- docker-compose.yml | 1 + packages/api/src/services/projects.ts | 27 ++++- packages/api/tests/projects.test.ts | 35 ++++++ packages/container/src/core/domain.ts | 1 + .../templates-entrypoint/agents-notice.ts | 7 +- .../claude-extra-config.ts | 7 +- .../src/core/templates-entrypoint/gemini.ts | 7 +- .../src/core/templates-entrypoint/grok.ts | 7 +- .../src/core/templates/docker-compose.ts | 15 +-- .../container/tests/core/templates.test.ts | 14 +++ packages/lib/src/core/domain.ts | 1 + packages/lib/src/shell/config.ts | 3 +- packages/lib/src/shell/docker-compose.ts | 17 ++- .../src/usecases/actions/create-project.ts | 3 +- .../lib/src/usecases/actions/docker-up.ts | 28 +++-- .../tests/usecases/docker-up-force.test.ts | 103 ++++++++++++------ scripts/ci/build-e2e-project-image.ts | 47 ++++++++ 18 files changed, 333 insertions(+), 73 deletions(-) create mode 100644 scripts/ci/build-e2e-project-image.ts diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5ba1b2be..5bf551ee 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -236,6 +236,7 @@ jobs: timeout-minutes: 30 env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" + DOCKER_GIT_E2E_PROJECT_IMAGE: docker-git-e2e-project:latest steps: - uses: actions/checkout@v6 with: @@ -248,6 +249,16 @@ jobs: bun run --cwd packages/app build:web - name: Free Docker disk uses: ./.github/actions/free-docker-disk + - name: Build E2E project image + run: | + set -euo pipefail + + bun run --cwd packages/lib build + bun scripts/ci/build-e2e-project-image.ts "$DOCKER_GIT_E2E_PROJECT_IMAGE" + + mkdir -p artifacts + docker save "$DOCKER_GIT_E2E_PROJECT_IMAGE" | gzip -1 > artifacts/docker-git-e2e-project-image.tar.gz + ls -lh artifacts/docker-git-e2e-project-image.tar.gz - name: Build E2E controller image run: | set -euo pipefail @@ -278,6 +289,12 @@ jobs: name: docker-git-e2e-controller-image path: artifacts/docker-git-e2e-controller-image.tar.gz compression-level: 0 + - name: Upload E2E project image artifact + uses: actions/upload-artifact@v7 + with: + name: docker-git-e2e-project-image + path: artifacts/docker-git-e2e-project-image.tar.gz + compression-level: 0 e2e-local-package: name: E2E (Local package CLI) @@ -298,6 +315,7 @@ jobs: env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" + DOCKER_GIT_E2E_PROJECT_IMAGE: docker-git-e2e-project:latest DOCKER_GIT_E2E_USE_PREBUILT_WEB: "1" steps: - uses: actions/checkout@v6 @@ -317,15 +335,23 @@ jobs: with: name: docker-git-e2e-controller-image path: artifacts + - name: Download E2E project image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-project-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - - name: Load E2E controller image + - name: Load E2E Docker images run: | gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load docker image inspect docker-git-api \ -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' + gzip -dc artifacts/docker-git-e2e-project-image.tar.gz | docker load + docker image inspect "$DOCKER_GIT_E2E_PROJECT_IMAGE" \ + -f 'project image={{.Id}}' - name: Docker info run: docker version && docker compose version - name: Browser command startup @@ -339,6 +365,7 @@ jobs: env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" + DOCKER_GIT_E2E_PROJECT_IMAGE: docker-git-e2e-project:latest steps: - uses: actions/checkout@v6 with: @@ -357,15 +384,23 @@ jobs: with: name: docker-git-e2e-controller-image path: artifacts + - name: Download E2E project image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-project-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - - name: Load E2E controller image + - name: Load E2E Docker images run: | gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load docker image inspect docker-git-api \ -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' + gzip -dc artifacts/docker-git-e2e-project-image.tar.gz | docker load + docker image inspect "$DOCKER_GIT_E2E_PROJECT_IMAGE" \ + -f 'project image={{.Id}}' - name: Docker info run: docker version && docker compose version - name: OpenCode autoconnect @@ -379,6 +414,7 @@ jobs: env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" + DOCKER_GIT_E2E_PROJECT_IMAGE: docker-git-e2e-project:latest steps: - uses: actions/checkout@v6 with: @@ -397,15 +433,23 @@ jobs: with: name: docker-git-e2e-controller-image path: artifacts + - name: Download E2E project image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-project-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - - name: Load E2E controller image + - name: Load E2E Docker images run: | gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load docker image inspect docker-git-api \ -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' + gzip -dc artifacts/docker-git-e2e-project-image.tar.gz | docker load + docker image inspect "$DOCKER_GIT_E2E_PROJECT_IMAGE" \ + -f 'project image={{.Id}}' - name: Docker info run: docker version && docker compose version - name: Clone cache reuse @@ -419,6 +463,7 @@ jobs: env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" + DOCKER_GIT_E2E_PROJECT_IMAGE: docker-git-e2e-project:latest steps: - uses: actions/checkout@v6 with: @@ -437,15 +482,23 @@ jobs: with: name: docker-git-e2e-controller-image path: artifacts + - name: Download E2E project image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-project-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - - name: Load E2E controller image + - name: Load E2E Docker images run: | gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load docker image inspect docker-git-api \ -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' + gzip -dc artifacts/docker-git-e2e-project-image.tar.gz | docker load + docker image inspect "$DOCKER_GIT_E2E_PROJECT_IMAGE" \ + -f 'project image={{.Id}}' - name: Docker info run: docker version && docker compose version - name: Login context notice @@ -459,6 +512,7 @@ jobs: env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" + DOCKER_GIT_E2E_PROJECT_IMAGE: docker-git-e2e-project:latest steps: - uses: actions/checkout@v6 with: @@ -477,15 +531,23 @@ jobs: with: name: docker-git-e2e-controller-image path: artifacts + - name: Download E2E project image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-project-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - - name: Load E2E controller image + - name: Load E2E Docker images run: | gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load docker image inspect docker-git-api \ -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' + gzip -dc artifacts/docker-git-e2e-project-image.tar.gz | docker load + docker image inspect "$DOCKER_GIT_E2E_PROJECT_IMAGE" \ + -f 'project image={{.Id}}' - name: Docker info run: docker version && docker compose version - name: Runtime volumes + host SSH CLI @@ -499,6 +561,7 @@ jobs: env: DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0" DOCKER_GIT_E2E_USE_PREBUILT_CLI: "1" + DOCKER_GIT_E2E_PROJECT_IMAGE: docker-git-e2e-project:latest steps: - uses: actions/checkout@v6 with: @@ -517,15 +580,23 @@ jobs: with: name: docker-git-e2e-controller-image path: artifacts + - name: Download E2E project image artifact + uses: actions/download-artifact@v7 + with: + name: docker-git-e2e-project-image + path: artifacts - name: Restore E2E prebuilt artifact run: tar -xzf artifacts/docker-git-e2e-prebuilt.tgz - name: Free Docker disk uses: ./.github/actions/free-docker-disk - - name: Load E2E controller image + - name: Load E2E Docker images run: | gzip -dc artifacts/docker-git-e2e-controller-image.tar.gz | docker load docker image inspect docker-git-api \ -f 'controller revision={{ index .Config.Labels "io.prover-coder-ai.docker-git.controller-rev" }}' + gzip -dc artifacts/docker-git-e2e-project-image.tar.gz | docker load + docker image inspect "$DOCKER_GIT_E2E_PROJECT_IMAGE" \ + -f 'project image={{.Id}}' - name: Docker info run: docker version && docker compose version - name: Clone auto-open SSH diff --git a/docker-compose.yml b/docker-compose.yml index 910129f8..3b0a5d3f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,6 +27,7 @@ services: DOCKER_GIT_EXCHANGE_AGENT_COMMAND: ${DOCKER_GIT_EXCHANGE_AGENT_COMMAND:-} DOCKER_GIT_EXCHANGE_AGENT_TIMEOUT_MS: ${DOCKER_GIT_EXCHANGE_AGENT_TIMEOUT_MS:-3600000} DOCKER_GIT_OUTBOX_POLLING_INTERVAL_MS: ${DOCKER_GIT_OUTBOX_POLLING_INTERVAL_MS:-5000} + DOCKER_GIT_E2E_PROJECT_IMAGE: ${DOCKER_GIT_E2E_PROJECT_IMAGE:-} ports: - "${DOCKER_GIT_API_BIND_HOST:-127.0.0.1}:${DOCKER_GIT_API_PORT:-3334}:${DOCKER_GIT_API_PORT:-3334}" dns: diff --git a/packages/api/src/services/projects.ts b/packages/api/src/services/projects.ts index 5f79f94f..2d5eca72 100644 --- a/packages/api/src/services/projects.ts +++ b/packages/api/src/services/projects.ts @@ -414,6 +414,29 @@ const withManagedAuthorizedKeysForCreate = ( } } +const e2eProjectImageEnvKey = "DOCKER_GIT_E2E_PROJECT_IMAGE" + +const resolveE2eProjectImageName = (): string | undefined => { + const imageName = process.env[e2eProjectImageEnvKey]?.trim() ?? "" + return imageName.length === 0 ? undefined : imageName +} + +const withE2eProjectImageForCreate = ( + command: LibCreateCommand +): LibCreateCommand => { + const imageName = resolveE2eProjectImageName() + return imageName === undefined + ? command + : { + ...command, + dockerComposeUpBuildMode: "reuse", + config: { + ...command.config, + imageName + } + } +} + export const seedAuthorizedKeysForCreate = ( outDir: string, authorizedKeysContents: string | undefined @@ -520,7 +543,9 @@ const prepareCreateProjectRequest = ( resolveRequestedAuthorizedKeysContents(requestAuthorizedKeysContents, request.useManagedAuthorizedKeys === true) ) - const command = withManagedAuthorizedKeysForCreate(parsedCommand, resolvedAuthorizedKeysContents) + const command = withE2eProjectImageForCreate( + withManagedAuthorizedKeysForCreate(parsedCommand, resolvedAuthorizedKeysContents) + ) yield* _(seedAuthorizedKeysForCreate(command.outDir, resolvedAuthorizedKeysContents)) yield* _(ensureGithubAuthForCreate(command.config)) diff --git a/packages/api/tests/projects.test.ts b/packages/api/tests/projects.test.ts index 5483527a..26380a28 100644 --- a/packages/api/tests/projects.test.ts +++ b/packages/api/tests/projects.test.ts @@ -444,6 +444,41 @@ describe("projects service", () => { }) ).pipe(Effect.provide(NodeContext.layer))) + it.effect("applies the E2E prebuilt project image to generated compose files", () => + withTempDir((root) => + Effect.gen(function*(_) { + const fs = yield* _(FileSystem.FileSystem) + const path = yield* _(Path.Path) + const projectsRoot = path.join(root, ".docker-git") + const projectId = path.join(projectsRoot, "test-owner", "prebuilt-image") + + yield* _( + withEnvVar( + "DOCKER_GIT_E2E_PROJECT_IMAGE", + "docker-git-e2e-project:latest", + withProjectsRoot( + projectsRoot, + withWorkingDirectory( + root, + createProjectFromRequest({ + repoUrl: "https://git.example.test/test-owner/prebuilt-image.git", + repoRef: "main", + outDir: projectId, + skipGithubAuth: true, + up: false + }) + ) + ) + ) + ) + + const compose = yield* _(fs.readFileString(path.join(projectId, "docker-compose.yml"))) + expect(compose).toContain(" image: 'docker-git-e2e-project:latest'\n") + expect(compose).toContain(" pull_policy: never\n") + expect(compose).not.toContain(" build: .\n") + }) + ).pipe(Effect.provide(NodeContext.layer))) + it.effect("refreshes the state remote before listing projects", () => withTempDir((root) => Effect.gen(function*(_) { diff --git a/packages/container/src/core/domain.ts b/packages/container/src/core/domain.ts index 41d9fda3..d21a72a5 100644 --- a/packages/container/src/core/domain.ts +++ b/packages/container/src/core/domain.ts @@ -78,6 +78,7 @@ export interface TemplateConfig { readonly agentMode?: AgentMode | undefined readonly agentAuto?: boolean | undefined readonly clonedOnHostname?: string | undefined + readonly imageName?: string | undefined } export interface ProjectConfig { diff --git a/packages/container/src/core/templates-entrypoint/agents-notice.ts b/packages/container/src/core/templates-entrypoint/agents-notice.ts index 14e9d920..965091ce 100644 --- a/packages/container/src/core/templates-entrypoint/agents-notice.ts +++ b/packages/container/src/core/templates-entrypoint/agents-notice.ts @@ -11,10 +11,10 @@ docker_git_decode_unicode_escapes() { printf "%s" "$value" fi } -PROJECT_LINE="Рабочая папка проекта (git clone): __TARGET_DIR__" -WORKSPACES_LINE="Доступные workspace пути: __TARGET_DIR__" +PROJECT_LINE="Рабочая папка проекта (git clone): $TARGET_DIR" +WORKSPACES_LINE="Доступные workspace пути: $TARGET_DIR" WORKSPACE_INFO_LINE="Контекст workspace: repository" -FOCUS_LINE="Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: __TARGET_DIR__" +FOCUS_LINE="Фокус задачи: работай только в workspace, который запрашивает пользователь. Текущий workspace: $TARGET_DIR" INTERNET_LINE="Доступ к интернету: есть. Если чего-то не знаешь — ищи в интернете или по кодовой базе." SUBAGENTS_LINE="Для решения задач обязательно используй subagents. Сам агент обязан выполнять финальную проверку, интеграцию и валидацию результата перед ответом пользователю." if [[ "$REPO_REF" == issue-* ]]; then @@ -134,4 +134,3 @@ export const renderEntrypointAgentsNotice = (config: TemplateConfig): string => entrypointAgentsNoticeTemplate .replaceAll("__CODEX_HOME__", () => config.codexHome) .replaceAll("__SSH_USER__", () => config.sshUser) - .replaceAll("__TARGET_DIR__", () => config.targetDir) diff --git a/packages/container/src/core/templates-entrypoint/claude-extra-config.ts b/packages/container/src/core/templates-entrypoint/claude-extra-config.ts index d9c4b5a5..4f350b53 100644 --- a/packages/container/src/core/templates-entrypoint/claude-extra-config.ts +++ b/packages/container/src/core/templates-entrypoint/claude-extra-config.ts @@ -52,10 +52,10 @@ CLAUDE_SYSTEM_PROMPT_OVERRIDE_FILE="${"$"}{CLAUDE_SYSTEM_PROMPT_OVERRIDE_FILE:-} CLAUDE_SYSTEM_PROMPT_OVERRIDE="${"$"}{CLAUDE_SYSTEM_PROMPT_OVERRIDE:-}" CLAUDE_DEFAULT_PROMPT_BODY="$(cat < { export const renderClaudeGlobalPromptSetup = (config: TemplateConfig): string => entrypointClaudeGlobalPromptTemplate - .replaceAll("__TARGET_DIR__", () => config.targetDir) .replaceAll("__SSH_USER__", () => config.sshUser) .replaceAll("__REPO_REF_DEFAULT__", () => escapeForDoubleQuotes(config.repoRef)) .replaceAll("__REPO_URL_DEFAULT__", () => escapeForDoubleQuotes(config.repoUrl)) diff --git a/packages/container/src/core/templates-entrypoint/gemini.ts b/packages/container/src/core/templates-entrypoint/gemini.ts index e8d35570..d8a8399c 100644 --- a/packages/container/src/core/templates-entrypoint/gemini.ts +++ b/packages/container/src/core/templates-entrypoint/gemini.ts @@ -306,10 +306,10 @@ GEMINI_SYSTEM_PROMPT_OVERRIDE_FILE="${"$"}{GEMINI_SYSTEM_PROMPT_OVERRIDE_FILE:-} GEMINI_SYSTEM_PROMPT_OVERRIDE="${"$"}{GEMINI_SYSTEM_PROMPT_OVERRIDE:-}" GEMINI_DEFAULT_PROMPT_BODY="$(cat < renderGeminiProfileSetup(config), entrypointGeminiNoticeTemplate .replaceAll("__GEMINI_HOME__", () => config.geminiHome) - .replaceAll("__TARGET_DIR__", () => config.targetDir) ].join("\n\n") diff --git a/packages/container/src/core/templates-entrypoint/grok.ts b/packages/container/src/core/templates-entrypoint/grok.ts index 86e80bcf..d6048d24 100644 --- a/packages/container/src/core/templates-entrypoint/grok.ts +++ b/packages/container/src/core/templates-entrypoint/grok.ts @@ -294,10 +294,10 @@ GROK_SYSTEM_PROMPT_OVERRIDE_FILE="${"$"}{GROK_SYSTEM_PROMPT_OVERRIDE_FILE:-}" GROK_SYSTEM_PROMPT_OVERRIDE="${"$"}{GROK_SYSTEM_PROMPT_OVERRIDE:-}" GROK_DEFAULT_PROMPT_BODY="$(cat < entrypointGrokNoticeTemplate .replaceAll("__GROK_HOME__", () => config.grokHome) .replaceAll("__SSH_USER__", () => config.sshUser) - .replaceAll("__TARGET_DIR__", () => config.targetDir) /** * Renders the Grok CLI entrypoint bootstrap for a generated project container. diff --git a/packages/container/src/core/templates/docker-compose.ts b/packages/container/src/core/templates/docker-compose.ts index a0b25547..b824af4e 100644 --- a/packages/container/src/core/templates/docker-compose.ts +++ b/packages/container/src/core/templates/docker-compose.ts @@ -49,18 +49,14 @@ type AgentEnvFragments = Pick< "maybeAgentModeEnv" | "maybeAgentAutoEnv" > -export type DockerComposeRenderOptions = { - readonly enableLocalDockerSocket: boolean -} +export type DockerComposeRenderOptions = { readonly enableLocalDockerSocket: boolean } export type ComposeResourceLimits = { readonly main: ResolvedComposeResourceLimits | undefined readonly playwright: ResolvedComposeResourceLimits | undefined } -const defaultDockerComposeRenderOptions: DockerComposeRenderOptions = { - enableLocalDockerSocket: false -} +const defaultDockerComposeRenderOptions: DockerComposeRenderOptions = { enableLocalDockerSocket: false } const sharedCodexVolumeKey = "docker_git_shared_codex" const sharedCacheVolumeKey = "docker_git_shared_cache" const bootstrapVolumeKey = "docker_git_bootstrap" @@ -128,6 +124,11 @@ const renderEnvFiles = (config: TemplateConfig): string => const optionalTrimmed = (value: string | undefined): string => value?.trim() ?? "" +const renderProjectImageSource = (imageName: string | undefined): string => + optionalTrimmed(imageName).length === 0 + ? " build: .\n" + : ` image: ${renderYamlSingleQuoted(optionalTrimmed(imageName))}\n pull_policy: never\n` + const buildAuthEnvFragments = (config: TemplateConfig): AuthEnvFragments => ({ maybeGitTokenLabelEnv: renderGitTokenLabelEnv( optionalTrimmed(config.gitTokenLabel) @@ -248,7 +249,7 @@ const renderComposeServices = ( ): string => `services: ${config.serviceName}: - build: . +${renderProjectImageSource(config.imageName)} container_name: ${config.containerName} ${renderGpu(config.gpu)}${ renderEnvFiles( diff --git a/packages/container/tests/core/templates.test.ts b/packages/container/tests/core/templates.test.ts index b3feec3b..99e1a46d 100644 --- a/packages/container/tests/core/templates.test.ts +++ b/packages/container/tests/core/templates.test.ts @@ -944,6 +944,8 @@ describe("renderDockerCompose", () => { const compose = renderDockerCompose(makeTemplateConfig()) expect(compose).toContain("name: dg-test") + expect(compose).toContain(" build: .\n") + expect(compose).not.toContain(" pull_policy: never\n") expect(compose).toContain("container_name: dg-test") expect(compose).toContain(" env_file:\n - '/workspace/.orch/env/global.env'\n - '/workspace/.orch/env/project.env'\n") expect(compose).not.toContain("restart:") @@ -957,6 +959,18 @@ describe("renderDockerCompose", () => { expect((compose.match(/\n dns:\n/g) ?? []).length).toBe(1) }) + it("renders an explicit prebuilt image without a build section", () => { + const compose = renderDockerCompose( + makeTemplateConfig({ + imageName: "docker-git-e2e-project:latest" + }) + ) + + expect(compose).toContain(" image: 'docker-git-e2e-project:latest'\n") + expect(compose).toContain(" pull_policy: never\n") + expect(compose).not.toContain(" build: .\n") + }) + it("quotes env_file paths so Windows paths and spaces remain YAML scalars", () => { const compose = renderDockerCompose( makeTemplateConfig({ diff --git a/packages/lib/src/core/domain.ts b/packages/lib/src/core/domain.ts index 0a76dd79..7c5a98ba 100644 --- a/packages/lib/src/core/domain.ts +++ b/packages/lib/src/core/domain.ts @@ -89,6 +89,7 @@ export interface CreateCommand { readonly forceEnv: boolean readonly waitForClone: boolean readonly openSsh: boolean + readonly dockerComposeUpBuildMode?: "build" | "reuse" | undefined } export interface MenuCommand { diff --git a/packages/lib/src/shell/config.ts b/packages/lib/src/shell/config.ts index d165a0a9..0b3732c2 100644 --- a/packages/lib/src/shell/config.ts +++ b/packages/lib/src/shell/config.ts @@ -87,7 +87,8 @@ const TemplateConfigInputSchema = Schema.Struct({ }), bunVersion: Schema.optional(Schema.String), pnpmVersion: Schema.optional(Schema.String), - clonedOnHostname: Schema.optional(HostnameSchema) + clonedOnHostname: Schema.optional(HostnameSchema), + imageName: Schema.optional(Schema.String) }) type DecodedProjectConfigInput = Schema.Schema.Type diff --git a/packages/lib/src/shell/docker-compose.ts b/packages/lib/src/shell/docker-compose.ts index fe2ea05b..c2a7f626 100644 --- a/packages/lib/src/shell/docker-compose.ts +++ b/packages/lib/src/shell/docker-compose.ts @@ -116,11 +116,24 @@ export const dockerComposeUpRecreateArgs: ReadonlyArray = [ "--force-recreate" ] +const dockerComposeUpRecreateArgsByMode = ( + buildMode: DockerComposeUpBuildMode +): ReadonlyArray => + buildMode === "reuse" + ? ["up", "-d", "--force-recreate"] + : dockerComposeUpRecreateArgs + export const runDockerComposeUpRecreate = ( - cwd: string + cwd: string, + options: { + readonly buildMode?: DockerComposeUpBuildMode + } = {} ): Effect.Effect => { const successExitCode = Number(ExitCode(0)) - return retryDockerComposeUp(cwd, runCompose(cwd, dockerComposeUpRecreateArgs, [successExitCode])) + return retryDockerComposeUp( + cwd, + runCompose(cwd, dockerComposeUpRecreateArgsByMode(options.buildMode ?? "build"), [successExitCode]) + ) } export const runDockerComposeDown = ( diff --git a/packages/lib/src/usecases/actions/create-project.ts b/packages/lib/src/usecases/actions/create-project.ts index 13ce76bf..af7c8aa1 100644 --- a/packages/lib/src/usecases/actions/create-project.ts +++ b/packages/lib/src/usecases/actions/create-project.ts @@ -181,7 +181,8 @@ export const runPreparedProject = ( waitForClone: command.waitForClone, waitForAgent: shouldWaitForAgent, force: command.force, - forceEnv: command.forceEnv + forceEnv: command.forceEnv, + buildMode: command.dockerComposeUpBuildMode }) ) if (command.runUp) { diff --git a/packages/lib/src/usecases/actions/docker-up.ts b/packages/lib/src/usecases/actions/docker-up.ts index 5601f77d..806a7eb6 100644 --- a/packages/lib/src/usecases/actions/docker-up.ts +++ b/packages/lib/src/usecases/actions/docker-up.ts @@ -57,6 +57,7 @@ type DockerUpOptions = { readonly waitForAgent: boolean readonly force: boolean readonly forceEnv: boolean + readonly buildMode?: "build" | "reuse" | undefined } const removeConflictingContainer = ( @@ -182,10 +183,17 @@ const runDockerComposeUpByMode = ( resolvedOutDir: string, projectConfig: CreateCommand["config"], shouldForce: boolean, - shouldForceEnv: boolean + shouldForceEnv: boolean, + buildMode: "build" | "reuse" ): Effect.Effect => Effect.gen(function*(_) { yield* _(ensureComposeNetworkReady(resolvedOutDir, projectConfig)) + const composeUpLabel = buildMode === "reuse" + ? "docker compose up -d" + : "docker compose up -d --build" + const runComposeUp = buildMode === "reuse" + ? runDockerComposeUp(resolvedOutDir, { buildMode: "reuse" }) + : runDockerComposeUp(resolvedOutDir) if (shouldForce) { yield* _(Effect.log("Force enabled: removing stale containers and wiping docker compose volumes...")) @@ -195,18 +203,18 @@ const runDockerComposeUpByMode = ( if (projectConfig.enableMcpPlaywright) { yield* _(removeConflictingContainer(resolvedOutDir, `${projectConfig.containerName}-browser`)) } - yield* _(Effect.log("Running: docker compose up -d --build")) - yield* _(runDockerComposeUp(resolvedOutDir)) + yield* _(Effect.log(`Running: ${composeUpLabel}`)) + yield* _(runComposeUp) return } yield* _(ensureSharedCodexVolumeReady(resolvedOutDir, projectConfig)) if (shouldForceEnv) { yield* _(Effect.log("Force env enabled: resetting env defaults and recreating containers (volumes preserved)...")) - yield* _(runDockerComposeUpRecreate(resolvedOutDir)) + yield* _(runDockerComposeUpRecreate(resolvedOutDir, { buildMode })) return } - yield* _(Effect.log("Running: docker compose up -d --build")) - yield* _(runDockerComposeUp(resolvedOutDir)) + yield* _(Effect.log(`Running: ${composeUpLabel}`)) + yield* _(runComposeUp) }) const ensureContainerBridgeAccess = ( @@ -251,7 +259,13 @@ export const runDockerUpIfNeeded = ( if (!options.runUp) { return } - yield* _(runDockerComposeUpByMode(resolvedOutDir, projectConfig, options.force, options.forceEnv)) + yield* _(runDockerComposeUpByMode( + resolvedOutDir, + projectConfig, + options.force, + options.forceEnv, + options.buildMode ?? "build" + )) yield* _(ensureBridgeAccess(resolvedOutDir, projectConfig)) if (options.waitForClone) { diff --git a/packages/lib/tests/usecases/docker-up-force.test.ts b/packages/lib/tests/usecases/docker-up-force.test.ts index 90681e37..035d717c 100644 --- a/packages/lib/tests/usecases/docker-up-force.test.ts +++ b/packages/lib/tests/usecases/docker-up-force.test.ts @@ -40,6 +40,11 @@ const isUp = (command: RecordedCommand): boolean => command.command === "docker" && includesArgsInOrder(command.args, ["compose", "up", "-d", "--build"]) +const isReuseUp = (command: RecordedCommand): boolean => + command.command === "docker" && + includesArgsInOrder(command.args, ["compose", "up", "-d"]) && + !command.args.includes("--build") + const isRmContainer = (name: string) => (command: RecordedCommand): boolean => command.command === "docker" && includesArgsInOrder(command.args, ["rm", "-f", name]) @@ -88,43 +93,45 @@ const makeFakeExecutor = (recorded: Array): CommandExecutor.Com return CommandExecutor.makeExecutor(start) } +const makeConfig = (resolvedOutDir: string): CreateCommand["config"] => ({ + containerName: "dg-force-test", + serviceName: "dg-force-test", + sshUser: "dev", + sshPort: 2237, + repoUrl: "https://github.com/org/repo.git", + repoRef: "main", + skipGithubAuth: false, + targetDir: "/home/dev/workspaces/org/repo", + volumeName: "dg-force-test-home", + dockerGitPath: `${resolvedOutDir}/.docker-git`, + authorizedKeysPath: "/tmp/authorized_keys", + envGlobalPath: `${resolvedOutDir}/.orch/env/global.env`, + envProjectPath: `${resolvedOutDir}/docker-git.env`, + codexAuthPath: `${resolvedOutDir}/.orch/auth/codex`, + codexSharedAuthPath: `${resolvedOutDir}/.orch/auth/codex-shared`, + codexHome: "/home/dev/.codex", + geminiAuthPath: `${resolvedOutDir}/.orch/auth/gemini`, + geminiHome: "/home/dev/.gemini", + dockerNetworkMode: "project", + dockerSharedNetworkName: "docker-git-shared", + enableMcpPlaywright: true, + gpu: "none", + bunVersion: "1.3.11", + agentMode: undefined, + agentAuto: false, + clonedOnHostname: undefined, + forkRepoUrl: undefined, + gitTokenLabel: undefined, + codexAuthLabel: undefined, + claudeAuthLabel: undefined +}) + describe("runDockerUpIfNeeded with force", () => { it.effect("wipes compose orphans, removes container, then recreates", () => Effect.gen(function*(_) { const commands: Array = [] const resolvedOutDir = "/tmp/docker-git-force-up" - const config: CreateCommand["config"] = { - containerName: "dg-force-test", - serviceName: "dg-force-test", - sshUser: "dev", - sshPort: 2237, - repoUrl: "https://github.com/org/repo.git", - repoRef: "main", - skipGithubAuth: false, - targetDir: "/home/dev/workspaces/org/repo", - volumeName: "dg-force-test-home", - dockerGitPath: `${resolvedOutDir}/.docker-git`, - authorizedKeysPath: "/tmp/authorized_keys", - envGlobalPath: `${resolvedOutDir}/.orch/env/global.env`, - envProjectPath: `${resolvedOutDir}/docker-git.env`, - codexAuthPath: `${resolvedOutDir}/.orch/auth/codex`, - codexSharedAuthPath: `${resolvedOutDir}/.orch/auth/codex-shared`, - codexHome: "/home/dev/.codex", - geminiAuthPath: `${resolvedOutDir}/.orch/auth/gemini`, - geminiHome: "/home/dev/.gemini", - dockerNetworkMode: "project", - dockerSharedNetworkName: "docker-git-shared", - enableMcpPlaywright: true, - gpu: "none", - bunVersion: "1.3.11", - agentMode: undefined, - agentAuto: false, - clonedOnHostname: undefined, - forkRepoUrl: undefined, - gitTokenLabel: undefined, - codexAuthLabel: undefined, - claudeAuthLabel: undefined - } + const config = makeConfig(resolvedOutDir) const recordedExecutor = makeFakeExecutor(commands) const result = yield* _( @@ -153,4 +160,36 @@ describe("runDockerUpIfNeeded with force", () => { expect(upIndex).toBeGreaterThan(rmBrowserIndex) }) ) + + it.effect("uses compose reuse mode after force cleanup when requested", () => + Effect.gen(function*(_) { + const commands: Array = [] + const resolvedOutDir = "/tmp/docker-git-force-up-reuse" + const config = makeConfig(resolvedOutDir) + + const recordedExecutor = makeFakeExecutor(commands) + yield* _( + runDockerUpIfNeeded(resolvedOutDir, config, { + runUp: true, + waitForClone: false, + waitForAgent: false, + force: true, + forceEnv: false, + buildMode: "reuse" + }).pipe( + Effect.provideService(CommandExecutor.CommandExecutor, recordedExecutor), + Effect.provide(NodeContext.layer) + ) + ) + + const downIndex = commands.findIndex(isDownWithRemoveOrphans) + const rmBrowserIndex = commands.findIndex(isRmContainer("dg-force-test-browser")) + const upIndex = commands.findIndex(isReuseUp) + + expect(downIndex).toBeGreaterThanOrEqual(0) + expect(rmBrowserIndex).toBeGreaterThan(downIndex) + expect(upIndex).toBeGreaterThan(rmBrowserIndex) + expect(commands.some(isUp)).toBe(false) + }) + ) }) diff --git a/scripts/ci/build-e2e-project-image.ts b/scripts/ci/build-e2e-project-image.ts new file mode 100644 index 00000000..73461763 --- /dev/null +++ b/scripts/ci/build-e2e-project-image.ts @@ -0,0 +1,47 @@ +#!/usr/bin/env bun +import { mkdtempSync, rmSync } from "node:fs" +import { tmpdir } from "node:os" +import { resolve } from "node:path" +import { spawnSync } from "node:child_process" + +import { NodeContext } from "../../packages/lib/node_modules/@effect/platform-node" +import { defaultTemplateConfig, type TemplateConfig } from "../../packages/container/dist/index.js" +import { writeProjectFiles } from "../../packages/lib/dist/shell/files.js" +import { Effect } from "../../packages/lib/node_modules/effect" + +const args = process.argv.slice(2) +const shouldDryRun = args.includes("--dry-run") +const imageName = args.find((arg) => arg !== "--dry-run") ?? "docker-git-e2e-project:latest" +const tempDir = mkdtempSync(resolve(tmpdir(), "docker-git-e2e-project-")) + +const run = (command: string, args: ReadonlyArray, cwd: string): void => { + const result = spawnSync(command, args, { cwd, stdio: "inherit" }) + if (result.status !== 0) { + process.exit(result.status ?? 1) + } +} + +const config: TemplateConfig = { + ...defaultTemplateConfig, + containerName: "docker-git-e2e-project", + serviceName: "docker-git-e2e-project", + repoUrl: "https://github.com/octocat/Hello-World.git", + repoRef: "main", + skipGithubAuth: true, + targetDir: "/home/dev", + volumeName: "docker-git-e2e-project-home" +} + +try { + await Effect.runPromise( + writeProjectFiles(tempDir, config, true).pipe(Effect.provide(NodeContext.layer)) + ) + if (shouldDryRun) { + console.log(`Prepared E2E project image context: ${tempDir}`) + } else { + run("docker", ["build", "--tag", imageName, "."], tempDir) + run("docker", ["image", "inspect", imageName, "--format", "{{.Id}}"], tempDir) + } +} finally { + rmSync(tempDir, { force: true, recursive: true }) +} From 1e24564e45f94b0e203e1e34d17d97861a929862 Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 20 Jun 2026 13:37:01 +0000 Subject: [PATCH 6/8] ci(e2e): forbid rebuild fallback for prebuilt images --- packages/lib/src/usecases/projects-up.ts | 7 +++- .../lib/tests/usecases/projects-up.test.ts | 37 +++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/lib/src/usecases/projects-up.ts b/packages/lib/src/usecases/projects-up.ts index cdc667af..189165c1 100644 --- a/packages/lib/src/usecases/projects-up.ts +++ b/packages/lib/src/usecases/projects-up.ts @@ -50,6 +50,8 @@ const syncManagedProjectFiles = ( yield* _(ensureCodexConfigFile(projectDir, template.codexAuthPath)) }) +const hasPrebuiltProjectImage = (template: TemplateConfig): boolean => (template.imageName?.trim().length ?? 0) > 0 + const claudeCliSelfHealScript = `set -eu if command -v claude >/dev/null 2>&1; then exit 0 @@ -222,7 +224,10 @@ const runProjectComposeUp = ( ? runDockerComposeUp(projectDir) : runDockerComposeUp(projectDir, { buildMode: "reuse" }).pipe( Effect.catchTag("DockerCommandError", (error) => { - if (gpuModeAfterDockerFailure(template.gpu, error.details) !== template.gpu) { + if ( + gpuModeAfterDockerFailure(template.gpu, error.details) !== template.gpu || + hasPrebuiltProjectImage(template) + ) { return Effect.fail(error) } diff --git a/packages/lib/tests/usecases/projects-up.test.ts b/packages/lib/tests/usecases/projects-up.test.ts index 22d31ec1..88c1250e 100644 --- a/packages/lib/tests/usecases/projects-up.test.ts +++ b/packages/lib/tests/usecases/projects-up.test.ts @@ -488,4 +488,41 @@ describe("runDockerComposeUpWithPortCheck", () => { expect(recorded.some((entry) => isDockerComposeUpWithBuild(entry))).toBe(false) }) ).pipe(Effect.provide(NodeContext.layer))) + + it.effect("does not rebuild when prebuilt-image reuse compose up fails", () => + withTempDir((root) => + Effect.gen(function*(_) { + const path = yield* _(Path.Path) + const outDir = path.join(root, "project") + const targetDir = "/home/dev/workspaces/org/repo" + const globalConfig = makeTemplateConfig(root, outDir, path, targetDir) + const projectConfig: TemplateConfig = { + ...makeTemplateConfig(root, outDir, path, targetDir), + imageName: "docker-git-e2e-project:latest" + } + const recorded: Array = [] + const executor = makeFakeExecutor(recorded, { failGpuComposeUp: true }) + + yield* _( + prepareProjectFiles(outDir, root, globalConfig, projectConfig, { + force: false, + forceEnv: false + }) + ) + + const result = yield* _( + runDockerComposeUpWithPortCheck(outDir, { + buildMode: "reuse", + waitForPostStart: false + }).pipe( + Effect.provideService(CommandExecutor.CommandExecutor, executor), + Effect.either + ) + ) + + expect(result._tag).toBe("Left") + expect(recorded.filter((entry) => isDockerComposeUpReuse(entry)).length).toBe(1) + expect(recorded.some((entry) => isDockerComposeUpWithBuild(entry))).toBe(false) + }) + ).pipe(Effect.provide(NodeContext.layer))) }) From 210d8fbdcc5ede68f8762781ec10a93af0cfc7e9 Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 20 Jun 2026 14:01:01 +0000 Subject: [PATCH 7/8] fix(container): refresh runtime codex resume hint --- .../src/core/templates-entrypoint/codex-resume-hint.ts | 6 ++---- packages/container/tests/core/templates.test.ts | 8 ++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/container/src/core/templates-entrypoint/codex-resume-hint.ts b/packages/container/src/core/templates-entrypoint/codex-resume-hint.ts index 08586133..bcde9356 100644 --- a/packages/container/src/core/templates-entrypoint/codex-resume-hint.ts +++ b/packages/container/src/core/templates-entrypoint/codex-resume-hint.ts @@ -12,8 +12,7 @@ const escapeForDoubleQuotes = (value: string): string => { const entrypointCodexResumeHintTemplate = `# Ensure codex resume hint is shown for interactive shells CODEX_HINT_PATH="/etc/profile.d/zz-codex-resume.sh" -if [[ ! -s "$CODEX_HINT_PATH" ]]; then - cat <<'EOF' > "$CODEX_HINT_PATH" +cat <<'EOF' > "$CODEX_HINT_PATH" docker_git_workspace_context_line() { REPO_REF_VALUE="\${REPO_REF:-__REPO_REF_DEFAULT__}" REPO_URL_VALUE="\${REPO_URL:-__REPO_URL_DEFAULT__}" @@ -83,8 +82,7 @@ if [ -n "$ZSH_VERSION" ]; then fi fi EOF - chmod 0644 "$CODEX_HINT_PATH" -fi +chmod 0644 "$CODEX_HINT_PATH" if ! grep -q "zz-codex-resume.sh" /etc/bash.bashrc 2>/dev/null; then printf "%s\\n" "if [ -f /etc/profile.d/zz-codex-resume.sh ]; then . /etc/profile.d/zz-codex-resume.sh; fi" >> /etc/bash.bashrc fi diff --git a/packages/container/tests/core/templates.test.ts b/packages/container/tests/core/templates.test.ts index 99e1a46d..1cbc538a 100644 --- a/packages/container/tests/core/templates.test.ts +++ b/packages/container/tests/core/templates.test.ts @@ -448,6 +448,14 @@ describe("renderEntrypoint clone cache", () => { expect(entrypoint).not.toContain("'+refs/merge-requests/*:refs/merge-requests/*'") }) + it("rewrites the managed Codex resume hint at container startup", () => { + const entrypoint = renderEntrypoint(makeTemplateConfig()) + + expect(entrypoint).toContain('cat <<\'EOF\' > "$CODEX_HINT_PATH"') + expect(entrypoint).toContain('chmod 0644 "$CODEX_HINT_PATH"') + expect(entrypoint).not.toContain('if [[ ! -s "$CODEX_HINT_PATH" ]]; then') + }) + it("preserves branch/tag-only clone-cache refspecs for generated configs", () => { fc.assert( fc.property(generatedTemplateConfigArbitrary, (config) => { From 3d856f4ccc0b5b9ed7eaae7b03db3e81453d9790 Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 20 Jun 2026 14:21:16 +0000 Subject: [PATCH 8/8] fix(container): export runtime project env for ssh --- .../container/src/core/templates-entrypoint.ts | 2 ++ .../src/core/templates-entrypoint/base.ts | 15 +++++++++++++++ packages/container/tests/core/templates.test.ts | 14 ++++++++++++++ 3 files changed, 31 insertions(+) diff --git a/packages/container/src/core/templates-entrypoint.ts b/packages/container/src/core/templates-entrypoint.ts index 96fdd684..68ed7cea 100644 --- a/packages/container/src/core/templates-entrypoint.ts +++ b/packages/container/src/core/templates-entrypoint.ts @@ -8,6 +8,7 @@ import { renderEntrypointHeader, renderEntrypointInputRc, renderEntrypointPackageCache, + renderEntrypointProjectRuntimeEnv, renderEntrypointSshd, renderEntrypointZshShell, renderEntrypointZshUserRc @@ -39,6 +40,7 @@ import { export const renderEntrypoint = (config: TemplateConfig): string => [ renderEntrypointHeader(config), + renderEntrypointProjectRuntimeEnv(), renderEntrypointDnsRepair(), renderEntrypointPackageCache(config), renderEntrypointDockerGitBootstrap(config), diff --git a/packages/container/src/core/templates-entrypoint/base.ts b/packages/container/src/core/templates-entrypoint/base.ts index 0ad47619..ed210b82 100644 --- a/packages/container/src/core/templates-entrypoint/base.ts +++ b/packages/container/src/core/templates-entrypoint/base.ts @@ -63,6 +63,21 @@ docker_git_upsert_ssh_env() { chown 1000:1000 "$SSH_ENV_PATH" || true }` +export const renderEntrypointProjectRuntimeEnv = (): string => + String.raw`# Publish runtime project identity into login and SSH environments. +DOCKER_GIT_PROJECT_PROFILE="/etc/profile.d/docker-git-project.sh" +{ + printf "export TARGET_DIR=%q\n" "$TARGET_DIR" + printf "export REPO_URL=%q\n" "$REPO_URL" + printf "export REPO_REF=%q\n" "$REPO_REF" + printf "export FORK_REPO_URL=%q\n" "$FORK_REPO_URL" +} > "$DOCKER_GIT_PROJECT_PROFILE" +chmod 0644 "$DOCKER_GIT_PROJECT_PROFILE" +docker_git_upsert_ssh_env "TARGET_DIR" "$TARGET_DIR" +docker_git_upsert_ssh_env "REPO_URL" "$REPO_URL" +docker_git_upsert_ssh_env "REPO_REF" "$REPO_REF" +docker_git_upsert_ssh_env "FORK_REPO_URL" "$FORK_REPO_URL"` + export const renderEntrypointPackageCache = (config: TemplateConfig): string => `# Keep package manager caches inside the project home volume PACKAGE_CACHE_ROOT="/home/${config.sshUser}/.docker-git/.cache/packages" diff --git a/packages/container/tests/core/templates.test.ts b/packages/container/tests/core/templates.test.ts index 1cbc538a..fe53d83a 100644 --- a/packages/container/tests/core/templates.test.ts +++ b/packages/container/tests/core/templates.test.ts @@ -456,6 +456,20 @@ describe("renderEntrypoint clone cache", () => { expect(entrypoint).not.toContain('if [[ ! -s "$CODEX_HINT_PATH" ]]; then') }) + it("publishes runtime project identity before Codex resume hints", () => { + const entrypoint = renderEntrypoint(makeTemplateConfig()) + + const projectProfileIndex = entrypoint.indexOf('DOCKER_GIT_PROJECT_PROFILE="/etc/profile.d/docker-git-project.sh"') + const resumeHintIndex = entrypoint.indexOf('CODEX_HINT_PATH="/etc/profile.d/zz-codex-resume.sh"') + + expect(projectProfileIndex).toBeGreaterThanOrEqual(0) + expect(resumeHintIndex).toBeGreaterThan(projectProfileIndex) + expect(entrypoint).toContain('printf "export REPO_REF=%q\\n" "$REPO_REF"') + expect(entrypoint).toContain('printf "export REPO_URL=%q\\n" "$REPO_URL"') + expect(entrypoint).toContain('docker_git_upsert_ssh_env "REPO_REF" "$REPO_REF"') + expect(entrypoint).toContain('docker_git_upsert_ssh_env "REPO_URL" "$REPO_URL"') + }) + it("preserves branch/tag-only clone-cache refspecs for generated configs", () => { fc.assert( fc.property(generatedTemplateConfigArbitrary, (config) => {