@ic-reactor/mcp 0.0.0-stage → 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +108 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +77 -0
- package/.github/actions/setup-rust-wasm/action.yml +25 -0
- package/.github/dependabot.yml +107 -0
- package/.github/workflows/auth-latest.yml +67 -0
- package/.github/workflows/b3pay-site.yml +49 -0
- package/.github/workflows/ci.yml +430 -0
- package/.github/workflows/codeql.yml +41 -0
- package/.github/workflows/dependency-review.yml +68 -0
- package/.github/workflows/docs-check.yml +98 -0
- package/.github/workflows/docs.yml +265 -0
- package/.github/workflows/e2e.yml +90 -0
- package/.github/workflows/examples-published.yml +97 -0
- package/.github/workflows/release.yml +326 -0
- package/.husky/pre-commit +12 -0
- package/.prettierignore +109 -0
- package/.prettierrc +6 -0
- package/AGENTS.md +103 -0
- package/CHANGELOG.md +3268 -0
- package/CLAUDE.md +93 -0
- package/CNAME +1 -0
- package/CODE_OF_CONDUCT.md +7 -0
- package/CONTRIBUTING.md +433 -0
- package/LICENSE +21 -0
- package/README.md +2 -2
- package/SECURITY.md +74 -0
- package/b3pay-module.json +194 -0
- package/docs/.remarkrc.json +10 -0
- package/docs/README.md +64 -0
- package/docs/astro.config.mjs +205 -0
- package/docs/favicon.svg +15 -0
- package/docs/package.json +39 -0
- package/docs/public/favicon.svg +15 -0
- package/docs/public/ic-reactor-social-card.png +0 -0
- package/docs/scripts/b3pay-static.mjs +22 -0
- package/docs/scripts/check-example-docs.mjs +214 -0
- package/docs/scripts/check-links.mjs +212 -0
- package/docs/scripts/fix-dist-links.js +125 -0
- package/docs/scripts/fix-frontmatter.js +71 -0
- package/docs/scripts/remove-redundant-h1.cjs +48 -0
- package/docs/scripts/rewrite-typedoc-links.js +110 -0
- package/docs/scripts/split-index.js +69 -0
- package/docs/src/assets/ic-reactor-hero-dark.svg +23 -0
- package/docs/src/assets/ic-reactor-hero-light.svg +23 -0
- package/docs/src/assets/ic-reactor-lockup-dark.svg +15 -0
- package/docs/src/assets/ic-reactor-lockup-light.svg +15 -0
- package/docs/src/assets/ic-reactor-mark-dark.svg +14 -0
- package/docs/src/assets/ic-reactor-mark-light.svg +14 -0
- package/docs/src/assets/layers/candid-core-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/candid-core-layer-icon-light.svg +4 -0
- package/docs/src/assets/layers/candid-profile-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/candid-profile-layer-icon-light.svg +4 -0
- package/docs/src/assets/layers/ic-reactor-layer-icon-dark.svg +4 -0
- package/docs/src/assets/layers/ic-reactor-layer-icon-light.svg +4 -0
- package/docs/src/components/LibraryCards.astro +172 -0
- package/docs/src/components/LibraryIcon.astro +28 -0
- package/docs/src/components/SiteTitle.astro +241 -0
- package/docs/src/content/docs/404.mdx +19 -0
- package/docs/src/content/docs/build/agent-tools.mdx +593 -0
- package/docs/src/content/docs/build/icrc1-wallet.mdx +619 -0
- package/docs/src/content/docs/build/ssr-per-user.mdx +552 -0
- package/docs/src/content/docs/examples/canister-studio.mdx +95 -0
- package/docs/src/content/docs/examples/icrc-ledger.mdx +45 -0
- package/docs/src/content/docs/examples/index.mdx +38 -0
- package/docs/src/content/docs/examples/next-ssr.mdx +73 -0
- package/docs/src/content/docs/examples/node-agent-tool.mdx +80 -0
- package/docs/src/content/docs/examples/vite-wallet.mdx +71 -0
- package/docs/src/content/docs/guides/auth.mdx +1181 -0
- package/docs/src/content/docs/guides/client.mdx +551 -0
- package/docs/src/content/docs/guides/deploy.mdx +180 -0
- package/docs/src/content/docs/guides/errors.mdx +475 -0
- package/docs/src/content/docs/guides/getting-started.mdx +620 -0
- package/docs/src/content/docs/guides/reads.mdx +586 -0
- package/docs/src/content/docs/guides/ssr.mdx +529 -0
- package/docs/src/content/docs/guides/testing.mdx +401 -0
- package/docs/src/content/docs/guides/values.mdx +261 -0
- package/docs/src/content/docs/guides/vite-plugin.mdx +690 -0
- package/docs/src/content/docs/guides/writes.mdx +572 -0
- package/docs/src/content/docs/ic-reactor/index.mdx +167 -0
- package/docs/src/content/docs/index.mdx +65 -0
- package/docs/src/content/docs/migrating-from-3.mdx +535 -0
- package/docs/src/content/docs/packages/core.mdx +186 -0
- package/docs/src/content/docs/packages/mcp.mdx +163 -0
- package/docs/src/content/docs/packages/react.mdx +207 -0
- package/docs/src/content/docs/packages/vite-plugin.mdx +73 -0
- package/docs/src/content/docs/versioning.mdx +104 -0
- package/docs/src/content.config.ts +7 -0
- package/docs/src/styles/custom.css +189 -0
- package/docs/tsconfig.typedoc.json +26 -0
- package/docs/typedoc-entry.ts +17 -0
- package/docs/typedoc.json +37 -0
- package/e2e/Cargo.lock +646 -0
- package/e2e/Cargo.toml +3 -0
- package/e2e/README.md +101 -0
- package/e2e/frontends/build.mjs +94 -0
- package/e2e/frontends/index.html +10 -0
- package/e2e/global-setup.ts +65 -0
- package/e2e/icp.yaml +38 -0
- package/e2e/package.json +40 -0
- package/e2e/setup.ts +6 -0
- package/e2e/src/actor/Cargo.toml +14 -0
- package/e2e/src/actor/hello_actor.did +10 -0
- package/e2e/src/actor/src/lib.rs +73 -0
- package/e2e/src/calls.test.ts +132 -0
- package/e2e/src/declarations/hello_actor.envelope.json +175 -0
- package/e2e/src/declarations/hello_actor.ts +15 -0
- package/e2e/src/errors.test.ts +107 -0
- package/e2e/src/frontend-headers.test.ts +253 -0
- package/e2e/src/identity.test.ts +61 -0
- package/e2e/src/react.test.tsx +59 -0
- package/e2e/src/replica.ts +70 -0
- package/e2e/src/vite-env.test.ts +108 -0
- package/e2e/test.sh +47 -0
- package/e2e/tsconfig.json +20 -0
- package/e2e/tsconfig.typecheck.json +9 -0
- package/e2e/vitest.config.ts +22 -0
- package/eslint.config.mjs +100 -0
- package/evals/PREREGISTRATION.md +1346 -0
- package/evals/README.md +756 -0
- package/evals/conditions/thin/condition.json +8 -0
- package/evals/conditions/thin/docs/candid-core-cli.README.md +90 -0
- package/evals/conditions/thin/docs/candid-core-schema.README.md +339 -0
- package/evals/conditions/thin/package.json +25 -0
- package/evals/conditions/thin/starter/src/generated/icrc1.ts +57 -0
- package/evals/conditions/thin-guide/condition.json +10 -0
- package/evals/conditions/thin-guide/docs/llms.txt +292 -0
- package/evals/conditions/v3/condition.json +9 -0
- package/evals/conditions/v3/docs/ic-reactor-core.llms.txt +232 -0
- package/evals/conditions/v3/docs/ic-reactor-react.llms.txt +280 -0
- package/evals/conditions/v3/docs/llms-full.txt +1053 -0
- package/evals/conditions/v3/package.json +26 -0
- package/evals/conditions/v3/starter/src/declarations/icrc1.did.d.ts +95 -0
- package/evals/conditions/v3/starter/src/declarations/icrc1.did.js +140 -0
- package/evals/conditions/v4/PORTING.md +384 -0
- package/evals/conditions/v4/README.md +106 -0
- package/evals/conditions/v4/condition.json +5 -0
- package/evals/conditions/v4/docs/llms.txt +323 -0
- package/evals/conditions/v4/package.json +27 -0
- package/evals/conditions/v4/starter/src/generated/icrc1.ts +78 -0
- package/evals/conditions/v4-proto/condition.json +6 -0
- package/evals/conditions/v4-proto/docs/llms.txt +291 -0
- package/evals/conditions/v4-proto/lib/SHIM-NOTES.md +20 -0
- package/evals/conditions/v4-proto/lib/package.json +27 -0
- package/evals/conditions/v4-proto/lib/src/client.ts +471 -0
- package/evals/conditions/v4-proto/lib/src/errors.ts +214 -0
- package/evals/conditions/v4-proto/lib/src/index.ts +36 -0
- package/evals/conditions/v4-proto/lib/src/principal.ts +175 -0
- package/evals/conditions/v4-proto/lib/src/service.ts +69 -0
- package/evals/conditions/v4-proto/lib/src/types.ts +174 -0
- package/evals/conditions/v4-proto/lib/test/runtime.test.ts +170 -0
- package/evals/conditions/v4-proto/lib/test/tsconfig.json +10 -0
- package/evals/conditions/v4-proto/lib/test/types.test-d.ts +145 -0
- package/evals/conditions/v4-proto/lib/test/vitest.config.mjs +8 -0
- package/evals/conditions/v4-proto/lib/tsconfig.json +17 -0
- package/evals/conditions/v4-proto/package.json +26 -0
- package/evals/conditions/v4-proto/starter/src/generated/icrc1.service.ts +19 -0
- package/evals/conditions/v4-proto/starter/src/generated/icrc1.ts +57 -0
- package/evals/drive.mjs +1221 -0
- package/evals/gate.mjs +161 -0
- package/evals/harness/aggregate.mjs +534 -0
- package/evals/harness/aggregate.test.mjs +344 -0
- package/evals/harness/assemble.mjs +216 -0
- package/evals/harness/assemble.test.mjs +143 -0
- package/evals/harness/auth.mjs +91 -0
- package/evals/harness/check-docs.mjs +157 -0
- package/evals/harness/declarations/icrc1.did.d.ts +95 -0
- package/evals/harness/declarations/icrc1.did.js +140 -0
- package/evals/harness/drive-auth.test.mjs +584 -0
- package/evals/harness/drive.test.mjs +509 -0
- package/evals/harness/fake-auth.ts +67 -0
- package/evals/harness/fake-ledger.ts +178 -0
- package/evals/harness/gate-plan.mjs +104 -0
- package/evals/harness/gate-plan.test.mjs +243 -0
- package/evals/harness/icrc1.did +88 -0
- package/evals/harness/judge.mjs +63 -0
- package/evals/harness/judge.test.mjs +81 -0
- package/evals/harness/leak-scan.mjs +1373 -0
- package/evals/harness/leak-scan.test.mjs +1300 -0
- package/evals/harness/probes/amounts.ts +14 -0
- package/evals/harness/probes/node-tool.probe.ts +48 -0
- package/evals/harness/probes/probe.mjs +149 -0
- package/evals/harness/probes/react-wallet.probe.ts +96 -0
- package/evals/harness/public/support/auth.ts +38 -0
- package/evals/harness/public/support/fake-replica.d.ts +28 -0
- package/evals/harness/public/support/fake-replica.js +434 -0
- package/evals/harness/public/support/ledger.idl.d.ts +95 -0
- package/evals/harness/public/support/ledger.idl.js +140 -0
- package/evals/harness/public/support/world.ts +179 -0
- package/evals/harness/runs.mjs +139 -0
- package/evals/harness/sandbox.mjs +88 -0
- package/evals/harness/sandbox.test.mjs +134 -0
- package/evals/harness/ship.mjs +264 -0
- package/evals/harness/ship.test.mjs +552 -0
- package/evals/harness/world.ts +291 -0
- package/evals/package.json +26 -0
- package/evals/pnpm-lock.yaml +1537 -0
- package/evals/pnpm-workspace.yaml +12 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.json +384 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.json +404 -0
- package/evals/results/2026-09-30T14-22-05-924Z/pilot.rescanned.rescored.json +404 -0
- package/evals/results/2026-09-30T14-22-05-924Z/plan.json +240 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.json +1800 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.json +1843 -0
- package/evals/results/2026-09-30T14-22-05-924Z/summary.rescanned.rescored.json +2100 -0
- package/evals/results/2026-09-30T15-48-23-343Z/pilot.json +404 -0
- package/evals/results/2026-09-30T15-48-23-343Z/pilot.rescored.json +404 -0
- package/evals/results/2026-09-30T15-48-23-343Z/plan.json +242 -0
- package/evals/results/2026-09-30T15-48-23-343Z/summary.json +1843 -0
- package/evals/results/2026-09-30T15-48-23-343Z/summary.rescored.json +2195 -0
- package/evals/results/2026-10-03-beta1-gate/pilot.json +162 -0
- package/evals/results/2026-10-03-beta1-gate/plan.json +141 -0
- package/evals/results/2026-10-03-beta1-gate/summary.json +983 -0
- package/evals/results/2026-10-05-ga-gate/contaminated-run.json +55 -0
- package/evals/results/2026-10-05-ga-gate/pilot.json +162 -0
- package/evals/results/2026-10-05-ga-gate/plan.json +457 -0
- package/evals/results/2026-10-05-ga-gate/summary.json +1037 -0
- package/evals/results/2026-10-05-ga-gate/summary.rescanned.json +1037 -0
- package/evals/results/README.md +31 -0
- package/evals/runs-pilot.log +150 -0
- package/evals/runs-pilot2.log +150 -0
- package/evals/score.mjs +256 -0
- package/evals/setup.mjs +547 -0
- package/evals/tasks/node-tool/faulty/thin-accepts-exponent/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-accepts-exponent/src/index.ts +121 -0
- package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-anonymous-transfer/src/index.ts +116 -0
- package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-every-reject-may-have-executed/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-fetches-root-key-off-local/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-ignores-root-key/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-ignores-root-key/src/index.ts +117 -0
- package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-misclassifies-reject-codes/src/index.ts +119 -0
- package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/thin-no-nat64-cap/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/thin-number-amount/meta.json +10 -0
- package/evals/tasks/node-tool/faulty/thin-number-amount/src/index.ts +117 -0
- package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-truncates-fraction-digits/src/index.ts +120 -0
- package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/thin-unvalidated-recipient/src/index.ts +118 -0
- package/evals/tasks/node-tool/faulty/v3-retry-call-errors/meta.json +11 -0
- package/evals/tasks/node-tool/faulty/v3-retry-call-errors/src/index.ts +149 -0
- package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-anonymous-identity-sent/src/index.ts +71 -0
- package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/v4-never-may-have-executed/src/index.ts +84 -0
- package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/meta.json +9 -0
- package/evals/tasks/node-tool/faulty/v4-proto-never-may-have-executed/src/index.ts +72 -0
- package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-proto-refuses-nat64-max/src/index.ts +72 -0
- package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/meta.json +5 -0
- package/evals/tasks/node-tool/faulty/v4-refuses-nat64-max/src/index.ts +84 -0
- package/evals/tasks/node-tool/hidden/node-tool.test.ts +312 -0
- package/evals/tasks/node-tool/prompt.md +55 -0
- package/evals/tasks/node-tool/prompt.minimal.md +30 -0
- package/evals/tasks/node-tool/solutions/thin/reference/src/index.ts +118 -0
- package/evals/tasks/node-tool/solutions/thin/reference-module-scope/src/index.ts +145 -0
- package/evals/tasks/node-tool/solutions/thin-guide/reference/src/index.ts +118 -0
- package/evals/tasks/node-tool/solutions/thin-guide/reference-module-scope/src/index.ts +145 -0
- package/evals/tasks/node-tool/solutions/v3/reference/src/index.ts +140 -0
- package/evals/tasks/node-tool/solutions/v3/reference-module-scope/src/index.ts +153 -0
- package/evals/tasks/node-tool/solutions/v4/reference/src/index.ts +84 -0
- package/evals/tasks/node-tool/solutions/v4/reference-module-scope/src/index.ts +105 -0
- package/evals/tasks/node-tool/solutions/v4-proto/reference/src/index.ts +72 -0
- package/evals/tasks/node-tool/solutions/v4-proto/reference-module-scope/src/index.ts +87 -0
- package/evals/tasks/node-tool/starter/src/contract.ts +31 -0
- package/evals/tasks/node-tool/starter/src/index.ts +7 -0
- package/evals/tasks/node-tool/starter/test/smoke.test.ts +22 -0
- package/evals/tasks/node-tool/starter/tsconfig.json +17 -0
- package/evals/tasks/node-tool/task.json +63 -0
- package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-accepts-exponent/src/Wallet.tsx +216 -0
- package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-anonymous-transfer/src/Wallet.tsx +212 -0
- package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-both-auth-buttons/src/Wallet.tsx +210 -0
- package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/meta.json +8 -0
- package/evals/tasks/react-wallet/faulty/thin-constant-balance-key/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/meta.json +9 -0
- package/evals/tasks/react-wallet/faulty/thin-no-nat64-cap/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-number-balance/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-number-balance/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-transfer-as-query/src/Wallet.tsx +254 -0
- package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-truncates-fraction-digits/src/Wallet.tsx +215 -0
- package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/thin-unguarded-balance-effect/src/Wallet.tsx +214 -0
- package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v3-http-4xx-unknown/src/Wallet.tsx +224 -0
- package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v3-mutation-retry-3/src/Wallet.tsx +225 -0
- package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/meta.json +11 -0
- package/evals/tasks/react-wallet/faulty/v3-unknown-as-error/src/Wallet.tsx +182 -0
- package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-every-reject-unknown/src/Wallet.tsx +160 -0
- package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-keep-previous-data/src/Wallet.tsx +166 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-every-reject-unknown/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-keep-previous-data/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-retry-spread/src/Wallet.tsx +162 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-proto-status-not-idle/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-retry-spread/meta.json +12 -0
- package/evals/tasks/react-wallet/faulty/v4-retry-spread/src/Wallet.tsx +163 -0
- package/evals/tasks/react-wallet/faulty/v4-status-not-idle/meta.json +5 -0
- package/evals/tasks/react-wallet/faulty/v4-status-not-idle/src/Wallet.tsx +162 -0
- package/evals/tasks/react-wallet/hidden/react-wallet.test.ts +536 -0
- package/evals/tasks/react-wallet/prompt.md +88 -0
- package/evals/tasks/react-wallet/prompt.minimal.md +51 -0
- package/evals/tasks/react-wallet/solutions/thin/reference/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/solutions/thin/reference-module-scope/src/Wallet.tsx +227 -0
- package/evals/tasks/react-wallet/solutions/thin-guide/reference/src/Wallet.tsx +213 -0
- package/evals/tasks/react-wallet/solutions/thin-guide/reference-module-scope/src/Wallet.tsx +227 -0
- package/evals/tasks/react-wallet/solutions/v3/reference/src/Wallet.tsx +224 -0
- package/evals/tasks/react-wallet/solutions/v3/reference-module-scope/src/Wallet.tsx +230 -0
- package/evals/tasks/react-wallet/solutions/v4/reference/src/Wallet.tsx +160 -0
- package/evals/tasks/react-wallet/solutions/v4/reference-module-scope/src/Wallet.tsx +178 -0
- package/evals/tasks/react-wallet/solutions/v4-proto/reference/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/solutions/v4-proto/reference-module-scope/src/Wallet.tsx +161 -0
- package/evals/tasks/react-wallet/starter/src/Wallet.tsx +12 -0
- package/evals/tasks/react-wallet/starter/src/auth.ts +16 -0
- package/evals/tasks/react-wallet/starter/src/config.ts +14 -0
- package/evals/tasks/react-wallet/starter/src/ii-auth.ts +43 -0
- package/evals/tasks/react-wallet/starter/src/main.tsx +15 -0
- package/evals/tasks/react-wallet/starter/test/smoke.test.ts +34 -0
- package/evals/tasks/react-wallet/starter/tsconfig.json +18 -0
- package/evals/tasks/react-wallet/task.json +84 -0
- package/examples/canister-studio/.stackblitzrc +4 -0
- package/examples/canister-studio/README.md +183 -0
- package/examples/canister-studio/did/ckbtc_minter.did +806 -0
- package/examples/canister-studio/did/icp_ledger.did +586 -0
- package/examples/canister-studio/did/icrc1.did +88 -0
- package/examples/canister-studio/package.json +45 -0
- package/examples/canister-studio/scripts/smoke.ts +188 -0
- package/examples/canister-studio/src/calls/CacheKeyPanel.tsx +104 -0
- package/examples/canister-studio/src/calls/ConfirmWrite.tsx +265 -0
- package/examples/canister-studio/src/calls/ErrorPanel.test.tsx +113 -0
- package/examples/canister-studio/src/calls/ErrorPanel.tsx +214 -0
- package/examples/canister-studio/src/calls/MethodRunner.test.tsx +354 -0
- package/examples/canister-studio/src/calls/MethodRunner.tsx +57 -0
- package/examples/canister-studio/src/calls/ReadRunner.tsx +244 -0
- package/examples/canister-studio/src/calls/ResendPanel.tsx +165 -0
- package/examples/canister-studio/src/calls/ShareLink.tsx +59 -0
- package/examples/canister-studio/src/calls/WriteRunner.test.tsx +678 -0
- package/examples/canister-studio/src/calls/WriteRunner.tsx +670 -0
- package/examples/canister-studio/src/calls/dedupe.ts +189 -0
- package/examples/canister-studio/src/calls/error-info.ts +168 -0
- package/examples/canister-studio/src/calls/history.test.ts +284 -0
- package/examples/canister-studio/src/calls/history.ts +287 -0
- package/examples/canister-studio/src/calls/kinds.ts +68 -0
- package/examples/canister-studio/src/calls/prepare.ts +32 -0
- package/examples/canister-studio/src/calls/query-options.ts +30 -0
- package/examples/canister-studio/src/calls/read.ts +47 -0
- package/examples/canister-studio/src/calls/readback.ts +100 -0
- package/examples/canister-studio/src/calls/record.ts +123 -0
- package/examples/canister-studio/src/calls/safe-calls.test.ts +299 -0
- package/examples/canister-studio/src/calls/share.ts +46 -0
- package/examples/canister-studio/src/calls/testing/doubles.tsx +108 -0
- package/examples/canister-studio/src/calls/testing/fixtures.ts +185 -0
- package/examples/canister-studio/src/calls/useCanisterHandle.ts +35 -0
- package/examples/canister-studio/src/canisters/ckbtc_minter.envelope.json +4228 -0
- package/examples/canister-studio/src/canisters/ckbtc_minter.ts +868 -0
- package/examples/canister-studio/src/canisters/icp_ledger.envelope.json +3454 -0
- package/examples/canister-studio/src/canisters/icp_ledger.ts +584 -0
- package/examples/canister-studio/src/canisters/icrc1.envelope.json +840 -0
- package/examples/canister-studio/src/canisters/icrc1.ts +78 -0
- package/examples/canister-studio/src/components/ActionButton.tsx +32 -0
- package/examples/canister-studio/src/components/AppHeader.tsx +83 -0
- package/examples/canister-studio/src/components/AppShell.tsx +29 -0
- package/examples/canister-studio/src/components/AuthButton.tsx +79 -0
- package/examples/canister-studio/src/components/Badge.tsx +79 -0
- package/examples/canister-studio/src/components/CanisterHeader.tsx +200 -0
- package/examples/canister-studio/src/components/CanisterJump.tsx +130 -0
- package/examples/canister-studio/src/components/Card.tsx +64 -0
- package/examples/canister-studio/src/components/Code.tsx +101 -0
- package/examples/canister-studio/src/components/CopyButton.tsx +126 -0
- package/examples/canister-studio/src/components/Disclosure.tsx +34 -0
- package/examples/canister-studio/src/components/EmptyState.tsx +34 -0
- package/examples/canister-studio/src/components/MethodList.test.tsx +137 -0
- package/examples/canister-studio/src/components/MethodList.tsx +244 -0
- package/examples/canister-studio/src/components/Notice.tsx +36 -0
- package/examples/canister-studio/src/components/ProvenanceBadge.tsx +44 -0
- package/examples/canister-studio/src/components/RouteError.tsx +42 -0
- package/examples/canister-studio/src/components/SectionBoundary.tsx +76 -0
- package/examples/canister-studio/src/components/Spinner.tsx +33 -0
- package/examples/canister-studio/src/components/Tabs.tsx +96 -0
- package/examples/canister-studio/src/components/canister/CanisterLoadFailure.test.tsx +56 -0
- package/examples/canister-studio/src/components/canister/CanisterLoadFailure.tsx +165 -0
- package/examples/canister-studio/src/components/canister/CanisterNotFound.tsx +59 -0
- package/examples/canister-studio/src/components/canister/CanisterPending.tsx +33 -0
- package/examples/canister-studio/src/components/canister/MethodOverview.tsx +93 -0
- package/examples/canister-studio/src/components/canister/MethodWorkspace.test.tsx +201 -0
- package/examples/canister-studio/src/components/canister/MethodWorkspace.tsx +187 -0
- package/examples/canister-studio/src/components/canister/shared-call.test.ts +186 -0
- package/examples/canister-studio/src/components/canister/shared-call.ts +133 -0
- package/examples/canister-studio/src/components/canister/shared-link-loader.test.ts +116 -0
- package/examples/canister-studio/src/components/canister/use-canister-page.ts +44 -0
- package/examples/canister-studio/src/components/canister/workspace-key.test.ts +27 -0
- package/examples/canister-studio/src/components/canister/workspace-key.ts +36 -0
- package/examples/canister-studio/src/components/canister-id-input.ts +69 -0
- package/examples/canister-studio/src/components/canister-names.ts +11 -0
- package/examples/canister-studio/src/components/download.ts +34 -0
- package/examples/canister-studio/src/components/fallbacks.tsx +33 -0
- package/examples/canister-studio/src/components/helpers.test.ts +107 -0
- package/examples/canister-studio/src/components/history/HistoryList.tsx +284 -0
- package/examples/canister-studio/src/components/home/FeaturedLedger.tsx +149 -0
- package/examples/canister-studio/src/components/home/OpenCanister.tsx +99 -0
- package/examples/canister-studio/src/components/home/featured.test.tsx +92 -0
- package/examples/canister-studio/src/components/home/featured.ts +64 -0
- package/examples/canister-studio/src/components/interface/CandidTab.tsx +38 -0
- package/examples/canister-studio/src/components/interface/CompatReport.tsx +92 -0
- package/examples/canister-studio/src/components/interface/CompatibilityTab.tsx +190 -0
- package/examples/canister-studio/src/components/interface/ProjectTab.tsx +196 -0
- package/examples/canister-studio/src/components/interface/TypeScriptTab.tsx +114 -0
- package/examples/canister-studio/src/components/interface/tools.test.tsx +169 -0
- package/examples/canister-studio/src/components/modes.ts +31 -0
- package/examples/canister-studio/src/components/playground/MethodPicker.tsx +67 -0
- package/examples/canister-studio/src/components/playground/PlaygroundForms.tsx +112 -0
- package/examples/canister-studio/src/components/playground/PlaygroundTools.tsx +219 -0
- package/examples/canister-studio/src/components/playground/sample.ts +43 -0
- package/examples/canister-studio/src/components/playground/target.test.tsx +86 -0
- package/examples/canister-studio/src/components/primitives.test.tsx +121 -0
- package/examples/canister-studio/src/components/provenance.ts +38 -0
- package/examples/canister-studio/src/components/route-render.test.tsx +71 -0
- package/examples/canister-studio/src/components/use-task.ts +56 -0
- package/examples/canister-studio/src/components/wallet/BalancePanel.tsx +110 -0
- package/examples/canister-studio/src/components/wallet/LedgerSummary.tsx +63 -0
- package/examples/canister-studio/src/components/wallet/MinterPanel.tsx +255 -0
- package/examples/canister-studio/src/components/wallet/SendPanel.test.tsx +297 -0
- package/examples/canister-studio/src/components/wallet/SendPanel.tsx +446 -0
- package/examples/canister-studio/src/components/wallet/ledgers.test.ts +36 -0
- package/examples/canister-studio/src/components/wallet/ledgers.ts +111 -0
- package/examples/canister-studio/src/forms/JsonPanel.tsx +157 -0
- package/examples/canister-studio/src/forms/MethodForm.test.tsx +369 -0
- package/examples/canister-studio/src/forms/MethodForm.tsx +368 -0
- package/examples/canister-studio/src/forms/TypedForm.tsx +55 -0
- package/examples/canister-studio/src/forms/context.ts +62 -0
- package/examples/canister-studio/src/forms/convert.ts +131 -0
- package/examples/canister-studio/src/forms/edge.test.ts +252 -0
- package/examples/canister-studio/src/forms/engine.test.ts +358 -0
- package/examples/canister-studio/src/forms/engine.ts +1492 -0
- package/examples/canister-studio/src/forms/fields/BytesField.tsx +210 -0
- package/examples/canister-studio/src/forms/fields/Field.tsx +106 -0
- package/examples/canister-studio/src/forms/fields/FieldShell.tsx +101 -0
- package/examples/canister-studio/src/forms/fields/Group.tsx +74 -0
- package/examples/canister-studio/src/forms/fields/ListField.tsx +256 -0
- package/examples/canister-studio/src/forms/fields/Segmented.tsx +78 -0
- package/examples/canister-studio/src/forms/fields/TimestampField.tsx +108 -0
- package/examples/canister-studio/src/forms/fields/UnitsField.tsx +119 -0
- package/examples/canister-studio/src/forms/fields/composites.tsx +389 -0
- package/examples/canister-studio/src/forms/fields/leaves.tsx +350 -0
- package/examples/canister-studio/src/forms/fields.test.tsx +396 -0
- package/examples/canister-studio/src/forms/helpers.test.ts +225 -0
- package/examples/canister-studio/src/forms/hints.ts +128 -0
- package/examples/canister-studio/src/forms/json.ts +119 -0
- package/examples/canister-studio/src/forms/parsing.test.ts +217 -0
- package/examples/canister-studio/src/forms/useMethodForm.ts +251 -0
- package/examples/canister-studio/src/interface/browser.test.ts +23 -0
- package/examples/canister-studio/src/interface/browser.ts +153 -0
- package/examples/canister-studio/src/interface/cache.ts +68 -0
- package/examples/canister-studio/src/interface/cli-cache.test.ts +155 -0
- package/examples/canister-studio/src/interface/cli-cache.ts +125 -0
- package/examples/canister-studio/src/interface/compile.server.test.ts +198 -0
- package/examples/canister-studio/src/interface/compile.server.ts +262 -0
- package/examples/canister-studio/src/interface/compile.test.ts +108 -0
- package/examples/canister-studio/src/interface/compile.ts +305 -0
- package/examples/canister-studio/src/interface/docs.test.ts +75 -0
- package/examples/canister-studio/src/interface/docs.ts +100 -0
- package/examples/canister-studio/src/interface/functions.test.ts +42 -0
- package/examples/canister-studio/src/interface/functions.ts +200 -0
- package/examples/canister-studio/src/interface/live-candid.server.ts +91 -0
- package/examples/canister-studio/src/interface/live-loader.test.ts +515 -0
- package/examples/canister-studio/src/interface/live-loader.ts +364 -0
- package/examples/canister-studio/src/interface/load.test.ts +328 -0
- package/examples/canister-studio/src/interface/load.ts +249 -0
- package/examples/canister-studio/src/interface/read-candid.test.ts +185 -0
- package/examples/canister-studio/src/interface/read-candid.ts +233 -0
- package/examples/canister-studio/src/interface/server-result.ts +42 -0
- package/examples/canister-studio/src/interface/typed.ts +68 -0
- package/examples/canister-studio/src/interface/types.ts +121 -0
- package/examples/canister-studio/src/lib/format.test.ts +107 -0
- package/examples/canister-studio/src/lib/format.ts +139 -0
- package/examples/canister-studio/src/lib/hex.test.ts +43 -0
- package/examples/canister-studio/src/lib/hex.ts +33 -0
- package/examples/canister-studio/src/lib/ids.test.ts +70 -0
- package/examples/canister-studio/src/lib/ids.ts +93 -0
- package/examples/canister-studio/src/lib/reactor.test.ts +55 -0
- package/examples/canister-studio/src/lib/reactor.ts +55 -0
- package/examples/canister-studio/src/lib/registry.test.ts +74 -0
- package/examples/canister-studio/src/lib/registry.ts +238 -0
- package/examples/canister-studio/src/lib/schema.ts +148 -0
- package/examples/canister-studio/src/routeTree.gen.ts +227 -0
- package/examples/canister-studio/src/router.test.ts +290 -0
- package/examples/canister-studio/src/router.tsx +127 -0
- package/examples/canister-studio/src/routes/__root.tsx +58 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.index.tsx +172 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.interface.tsx +98 -0
- package/examples/canister-studio/src/routes/canister.$canisterId.tsx +91 -0
- package/examples/canister-studio/src/routes/history.tsx +55 -0
- package/examples/canister-studio/src/routes/index.tsx +169 -0
- package/examples/canister-studio/src/routes/playground.tsx +286 -0
- package/examples/canister-studio/src/routes/safety.tsx +57 -0
- package/examples/canister-studio/src/routes/wallet.tsx +213 -0
- package/examples/canister-studio/src/safety/LabTransferForm.tsx +84 -0
- package/examples/canister-studio/src/safety/RequestLog.tsx +79 -0
- package/examples/canister-studio/src/safety/SafetyLab.test.tsx +89 -0
- package/examples/canister-studio/src/safety/SafetyLab.tsx +140 -0
- package/examples/canister-studio/src/safety/ScenarioCard.tsx +262 -0
- package/examples/canister-studio/src/safety/lab-run.ts +283 -0
- package/examples/canister-studio/src/safety/mock-ledger.test.ts +99 -0
- package/examples/canister-studio/src/safety/mock-ledger.ts +263 -0
- package/examples/canister-studio/src/safety/scenarios.test.ts +157 -0
- package/examples/canister-studio/src/safety/scenarios.ts +924 -0
- package/examples/canister-studio/src/styles.css +3453 -0
- package/examples/canister-studio/src/styles.test.ts +46 -0
- package/examples/canister-studio/src/values/CallPreview.test.tsx +86 -0
- package/examples/canister-studio/src/values/CallPreview.tsx +148 -0
- package/examples/canister-studio/src/values/ErrValueView.tsx +66 -0
- package/examples/canister-studio/src/values/Inspector.tsx +133 -0
- package/examples/canister-studio/src/values/ReplyView.test.tsx +341 -0
- package/examples/canister-studio/src/values/ReplyView.tsx +164 -0
- package/examples/canister-studio/src/values/candid-text.test.ts +516 -0
- package/examples/canister-studio/src/values/candid-text.ts +479 -0
- package/examples/canister-studio/src/values/hints.ts +58 -0
- package/examples/canister-studio/src/values/json.test.ts +66 -0
- package/examples/canister-studio/src/values/json.ts +31 -0
- package/examples/canister-studio/src/values/leaves.tsx +309 -0
- package/examples/canister-studio/src/values/schema.test.ts +123 -0
- package/examples/canister-studio/src/values/schema.ts +78 -0
- package/examples/canister-studio/src/values/test-router.tsx +21 -0
- package/examples/canister-studio/src/values/tree.tsx +670 -0
- package/examples/canister-studio/src/values/use-sha256.ts +29 -0
- package/examples/canister-studio/tsconfig.json +20 -0
- package/examples/canister-studio/vite.config.ts +50 -0
- package/examples/canister-studio/vitest.config.ts +20 -0
- package/examples/icrc-ledger/.stackblitzrc +4 -0
- package/examples/icrc-ledger/README.md +100 -0
- package/examples/icrc-ledger/ckbtc_minter.did +12 -0
- package/examples/icrc-ledger/icp_ledger.did +147 -0
- package/examples/icrc-ledger/icrc1.did +88 -0
- package/examples/icrc-ledger/index.html +13 -0
- package/examples/icrc-ledger/package.json +40 -0
- package/examples/icrc-ledger/scripts/smoke.ts +151 -0
- package/examples/icrc-ledger/src/App.tsx +71 -0
- package/examples/icrc-ledger/src/ErrorPanel.tsx +105 -0
- package/examples/icrc-ledger/src/MainnetTab.tsx +450 -0
- package/examples/icrc-ledger/src/SandboxTab.test.tsx +379 -0
- package/examples/icrc-ledger/src/SandboxTab.tsx +761 -0
- package/examples/icrc-ledger/src/blocks.test.ts +247 -0
- package/examples/icrc-ledger/src/blocks.ts +140 -0
- package/examples/icrc-ledger/src/canisters/ckbtc_minter.envelope.json +96 -0
- package/examples/icrc-ledger/src/canisters/ckbtc_minter.ts +16 -0
- package/examples/icrc-ledger/src/canisters/icp_ledger.envelope.json +690 -0
- package/examples/icrc-ledger/src/canisters/icp_ledger.ts +223 -0
- package/examples/icrc-ledger/src/canisters/icrc1.envelope.json +840 -0
- package/examples/icrc-ledger/src/canisters/icrc1.ts +78 -0
- package/examples/icrc-ledger/src/format.test.ts +41 -0
- package/examples/icrc-ledger/src/format.ts +68 -0
- package/examples/icrc-ledger/src/ledger.ts +15 -0
- package/examples/icrc-ledger/src/main.tsx +10 -0
- package/examples/icrc-ledger/src/minter.ts +16 -0
- package/examples/icrc-ledger/src/sandbox.test.ts +464 -0
- package/examples/icrc-ledger/src/sandbox.ts +281 -0
- package/examples/icrc-ledger/src/styles.css +391 -0
- package/examples/icrc-ledger/src/transfer-form.test.ts +62 -0
- package/examples/icrc-ledger/src/transfer-form.ts +97 -0
- package/examples/icrc-ledger/tsconfig.json +19 -0
- package/examples/icrc-ledger/vite.config.ts +44 -0
- package/examples/icrc-ledger/vitest.config.ts +12 -0
- package/examples/next-ssr/.stackblitzrc +4 -0
- package/examples/next-ssr/README.md +112 -0
- package/examples/next-ssr/icrc1.did +88 -0
- package/examples/next-ssr/next.config.ts +10 -0
- package/examples/next-ssr/package.json +41 -0
- package/examples/next-ssr/scripts/smoke.ts +169 -0
- package/examples/next-ssr/src/app/account/CertifiedBalances.tsx +58 -0
- package/examples/next-ssr/src/app/account/account.test.tsx +165 -0
- package/examples/next-ssr/src/app/account/page.tsx +82 -0
- package/examples/next-ssr/src/app/api/balance/[ledger]/[principal]/route.ts +10 -0
- package/examples/next-ssr/src/app/globals.css +278 -0
- package/examples/next-ssr/src/app/layout.tsx +34 -0
- package/examples/next-ssr/src/app/page.tsx +46 -0
- package/examples/next-ssr/src/app/providers.browser.test.tsx +127 -0
- package/examples/next-ssr/src/app/providers.test.tsx +73 -0
- package/examples/next-ssr/src/app/providers.tsx +60 -0
- package/examples/next-ssr/src/canisters/icrc1.envelope.json +840 -0
- package/examples/next-ssr/src/canisters/icrc1.ts +78 -0
- package/examples/next-ssr/src/components/BalanceTable.tsx +43 -0
- package/examples/next-ssr/src/components/LedgerCard.test.tsx +96 -0
- package/examples/next-ssr/src/components/LedgerCard.tsx +230 -0
- package/examples/next-ssr/src/components/LedgerError.tsx +32 -0
- package/examples/next-ssr/src/components/LedgerSections.tsx +22 -0
- package/examples/next-ssr/src/components/MyBalances.test.tsx +253 -0
- package/examples/next-ssr/src/components/MyBalances.tsx +129 -0
- package/examples/next-ssr/src/components/SessionBadge.tsx +18 -0
- package/examples/next-ssr/src/components/hydration.test.tsx +259 -0
- package/examples/next-ssr/src/components/server-data.ts +10 -0
- package/examples/next-ssr/src/format.ts +46 -0
- package/examples/next-ssr/src/ledgers.ts +38 -0
- package/examples/next-ssr/src/server/balance-route.test.ts +151 -0
- package/examples/next-ssr/src/server/balance-route.ts +157 -0
- package/examples/next-ssr/src/server/error-summary.test.ts +34 -0
- package/examples/next-ssr/src/server/error-summary.ts +37 -0
- package/examples/next-ssr/src/server/parse-owner.test.ts +39 -0
- package/examples/next-ssr/src/server/parse-owner.ts +34 -0
- package/examples/next-ssr/src/server/prefetch-ledgers.test.ts +137 -0
- package/examples/next-ssr/src/server/prefetch-ledgers.ts +70 -0
- package/examples/next-ssr/src/server/read-balances.test.ts +103 -0
- package/examples/next-ssr/src/server/read-balances.ts +78 -0
- package/examples/next-ssr/src/server/request-client.rsc.test.tsx +110 -0
- package/examples/next-ssr/src/server/request-client.ts +24 -0
- package/examples/next-ssr/src/testing/mock-ledgers.ts +115 -0
- package/examples/next-ssr/src/testing/react-server-dom-turbopack.d.ts +15 -0
- package/examples/next-ssr/src/testing/setup.ts +10 -0
- package/examples/next-ssr/tsconfig.json +31 -0
- package/examples/next-ssr/vitest.config.ts +59 -0
- package/examples/node-agent-tool/.stackblitzrc +4 -0
- package/examples/node-agent-tool/README.md +165 -0
- package/examples/node-agent-tool/icp.yaml +11 -0
- package/examples/node-agent-tool/icrc1.did +88 -0
- package/examples/node-agent-tool/package.json +32 -0
- package/examples/node-agent-tool/src/canisters/icrc1.envelope.json +840 -0
- package/examples/node-agent-tool/src/canisters/icrc1.ts +78 -0
- package/examples/node-agent-tool/src/cli.test.ts +64 -0
- package/examples/node-agent-tool/src/cli.ts +23 -0
- package/examples/node-agent-tool/src/command-line.ts +159 -0
- package/examples/node-agent-tool/src/commands/balance.ts +73 -0
- package/examples/node-agent-tool/src/commands/demo.test.ts +51 -0
- package/examples/node-agent-tool/src/commands/demo.ts +359 -0
- package/examples/node-agent-tool/src/commands/info.ts +69 -0
- package/examples/node-agent-tool/src/commands/reads.test.ts +133 -0
- package/examples/node-agent-tool/src/commands/resend.test.ts +363 -0
- package/examples/node-agent-tool/src/commands/transfer.test.ts +313 -0
- package/examples/node-agent-tool/src/commands/transfer.ts +415 -0
- package/examples/node-agent-tool/src/commands/watch.test.ts +159 -0
- package/examples/node-agent-tool/src/commands/watch.ts +135 -0
- package/examples/node-agent-tool/src/commands/whoami.ts +36 -0
- package/examples/node-agent-tool/src/context.ts +20 -0
- package/examples/node-agent-tool/src/dispatch.ts +70 -0
- package/examples/node-agent-tool/src/failure.test.ts +59 -0
- package/examples/node-agent-tool/src/failure.ts +176 -0
- package/examples/node-agent-tool/src/identity.test.ts +242 -0
- package/examples/node-agent-tool/src/identity.ts +228 -0
- package/examples/node-agent-tool/src/input.ts +110 -0
- package/examples/node-agent-tool/src/ledgers.test.ts +74 -0
- package/examples/node-agent-tool/src/ledgers.ts +62 -0
- package/examples/node-agent-tool/src/mock-ledger.ts +211 -0
- package/examples/node-agent-tool/src/network.test.ts +128 -0
- package/examples/node-agent-tool/src/network.ts +101 -0
- package/examples/node-agent-tool/src/output.test.ts +208 -0
- package/examples/node-agent-tool/src/output.ts +163 -0
- package/examples/node-agent-tool/src/run.ts +73 -0
- package/examples/node-agent-tool/src/test-kit.ts +167 -0
- package/examples/node-agent-tool/tsconfig.json +18 -0
- package/examples/node-agent-tool/vitest.config.ts +8 -0
- package/examples/vite-wallet/.stackblitzrc +4 -0
- package/examples/vite-wallet/Cargo.lock +646 -0
- package/examples/vite-wallet/Cargo.toml +5 -0
- package/examples/vite-wallet/README.md +107 -0
- package/examples/vite-wallet/backend/Cargo.toml +12 -0
- package/examples/vite-wallet/backend/backend.did +37 -0
- package/examples/vite-wallet/backend/src/lib.rs +133 -0
- package/examples/vite-wallet/icp.yaml +44 -0
- package/examples/vite-wallet/index.html +13 -0
- package/examples/vite-wallet/ledger.did +54 -0
- package/examples/vite-wallet/package.json +46 -0
- package/examples/vite-wallet/public/.ic-assets.json5 +26 -0
- package/examples/vite-wallet/scripts/faucet-args.test.ts +30 -0
- package/examples/vite-wallet/scripts/faucet-args.ts +41 -0
- package/examples/vite-wallet/scripts/faucet.ts +41 -0
- package/examples/vite-wallet/src/AddressBook.test.tsx +191 -0
- package/examples/vite-wallet/src/AddressBook.tsx +147 -0
- package/examples/vite-wallet/src/App.tsx +53 -0
- package/examples/vite-wallet/src/Balance.test.tsx +57 -0
- package/examples/vite-wallet/src/Balance.tsx +58 -0
- package/examples/vite-wallet/src/EnvironmentPanel.test.tsx +83 -0
- package/examples/vite-wallet/src/EnvironmentPanel.tsx +103 -0
- package/examples/vite-wallet/src/ErrorNote.tsx +70 -0
- package/examples/vite-wallet/src/Profile.test.tsx +145 -0
- package/examples/vite-wallet/src/Profile.tsx +92 -0
- package/examples/vite-wallet/src/SendIcp.test.tsx +231 -0
- package/examples/vite-wallet/src/SendIcp.tsx +244 -0
- package/examples/vite-wallet/src/SendToContact.test.tsx +113 -0
- package/examples/vite-wallet/src/SendToContact.tsx +102 -0
- package/examples/vite-wallet/src/SignIn.test.tsx +106 -0
- package/examples/vite-wallet/src/SignIn.tsx +120 -0
- package/examples/vite-wallet/src/Wallet.test.tsx +89 -0
- package/examples/vite-wallet/src/account-switch.test.tsx +108 -0
- package/examples/vite-wallet/src/auth/dev-accounts.test.ts +113 -0
- package/examples/vite-wallet/src/auth/dev-accounts.ts +216 -0
- package/examples/vite-wallet/src/auth/local-page.test.ts +28 -0
- package/examples/vite-wallet/src/auth/local-page.ts +20 -0
- package/examples/vite-wallet/src/auth/wallet-auth.test.ts +159 -0
- package/examples/vite-wallet/src/auth/wallet-auth.ts +131 -0
- package/examples/vite-wallet/src/canisters/backend.envelope.json +275 -0
- package/examples/vite-wallet/src/canisters/backend.ts +58 -0
- package/examples/vite-wallet/src/canisters/ledger.envelope.json +451 -0
- package/examples/vite-wallet/src/canisters/ledger.ts +56 -0
- package/examples/vite-wallet/src/client.ts +45 -0
- package/examples/vite-wallet/src/main.tsx +18 -0
- package/examples/vite-wallet/src/optimistic-contacts.ts +113 -0
- package/examples/vite-wallet/src/refusal.ts +17 -0
- package/examples/vite-wallet/src/styles.css +282 -0
- package/examples/vite-wallet/src/test/test-wallet.tsx +322 -0
- package/examples/vite-wallet/src/token.ts +50 -0
- package/examples/vite-wallet/src/transfer.test.ts +178 -0
- package/examples/vite-wallet/src/transfer.ts +110 -0
- package/examples/vite-wallet/src/use-canisters.ts +49 -0
- package/examples/vite-wallet/tsconfig.json +19 -0
- package/examples/vite-wallet/vite.config.ts +47 -0
- package/examples/vite-wallet/vitest.config.ts +17 -0
- package/package.json +9 -4
- package/packages/core/.size-limit.js +47 -0
- package/packages/core/LICENSE +21 -0
- package/packages/core/README.md +84 -0
- package/packages/core/llms.txt +610 -0
- package/packages/core/package.json +89 -0
- package/packages/core/src/call.ts +961 -0
- package/packages/core/src/canister.ts +259 -0
- package/packages/core/src/client.ts +2061 -0
- package/packages/core/src/errors.ts +1079 -0
- package/packages/core/src/hydration.ts +239 -0
- package/packages/core/src/index.ts +23 -0
- package/packages/core/src/keys.ts +108 -0
- package/packages/core/src/management.ts +138 -0
- package/packages/core/src/network.ts +1020 -0
- package/packages/core/src/options.ts +1000 -0
- package/packages/core/src/runtime.ts +50 -0
- package/packages/core/src/testing/fake-replica.ts +1166 -0
- package/packages/core/src/testing/index.ts +16 -0
- package/packages/core/src/testing/test-auth.ts +340 -0
- package/packages/core/src/testing/test-client.ts +559 -0
- package/packages/core/src/types.ts +541 -0
- package/packages/core/src/units.ts +299 -0
- package/packages/core/src/version.ts +4 -0
- package/packages/core/tests/canister-helpers.ts +187 -0
- package/packages/core/tests/canister-id.test.ts +158 -0
- package/packages/core/tests/canister-unwrap.test.ts +94 -0
- package/packages/core/tests/canister.test-d.ts +406 -0
- package/packages/core/tests/canister.test.ts +1921 -0
- package/packages/core/tests/client-account-change.test.ts +131 -0
- package/packages/core/tests/client-agents.test.ts +402 -0
- package/packages/core/tests/client-auth-network.test.ts +719 -0
- package/packages/core/tests/client-disposed.test.ts +403 -0
- package/packages/core/tests/client-helpers.ts +136 -0
- package/packages/core/tests/client-hydration.test.ts +166 -0
- package/packages/core/tests/client-pinned.test.ts +626 -0
- package/packages/core/tests/client-session.test.ts +348 -0
- package/packages/core/tests/client.test-d.ts +181 -0
- package/packages/core/tests/client.test.ts +1162 -0
- package/packages/core/tests/encode.test.ts +171 -0
- package/packages/core/tests/env-canister-id.test.ts +666 -0
- package/packages/core/tests/env-cookie-copies.test.ts +182 -0
- package/packages/core/tests/error-guard.test.ts +222 -0
- package/packages/core/tests/errors.test-d.ts +280 -0
- package/packages/core/tests/errors.test.ts +1359 -0
- package/packages/core/tests/fixtures/README.md +28 -0
- package/packages/core/tests/fixtures/accounts.did +21 -0
- package/packages/core/tests/fixtures/accounts.envelope.json +367 -0
- package/packages/core/tests/fixtures/accounts.ts +26 -0
- package/packages/core/tests/fixtures/archive.did +31 -0
- package/packages/core/tests/fixtures/archive.envelope.json +259 -0
- package/packages/core/tests/fixtures/archive.ts +55 -0
- package/packages/core/tests/fixtures/icrc1.did +88 -0
- package/packages/core/tests/fixtures/icrc1.envelope.json +840 -0
- package/packages/core/tests/fixtures/icrc1.ts +78 -0
- package/packages/core/tests/fixtures/management.did +724 -0
- package/packages/core/tests/fixtures/management.envelope.json +4455 -0
- package/packages/core/tests/fixtures/management.ts +486 -0
- package/packages/core/tests/fixtures/shapes.did +33 -0
- package/packages/core/tests/fixtures/shapes.envelope.json +440 -0
- package/packages/core/tests/fixtures/shapes.ts +56 -0
- package/packages/core/tests/fixtures/skippable.did +15 -0
- package/packages/core/tests/fixtures/skippable.envelope.json +182 -0
- package/packages/core/tests/fixtures/skippable.ts +33 -0
- package/packages/core/tests/func.test.ts +154 -0
- package/packages/core/tests/identity-attributes.test.ts +214 -0
- package/packages/core/tests/keys.test.ts +289 -0
- package/packages/core/tests/management.test.ts +206 -0
- package/packages/core/tests/network-helpers.ts +121 -0
- package/packages/core/tests/network.test-d.ts +78 -0
- package/packages/core/tests/network.test.ts +1051 -0
- package/packages/core/tests/options.test.ts +1594 -0
- package/packages/core/tests/redirect-sign-in.test.ts +393 -0
- package/packages/core/tests/resend.test-d.ts +81 -0
- package/packages/core/tests/resend.test.ts +723 -0
- package/packages/core/tests/skip.test-d.ts +125 -0
- package/packages/core/tests/testing-entry.test.ts +73 -0
- package/packages/core/tests/testing-fake-replica.test.ts +1242 -0
- package/packages/core/tests/testing-test-auth.test-d.ts +84 -0
- package/packages/core/tests/testing-test-auth.test.ts +483 -0
- package/packages/core/tests/testing-test-client.test-d.ts +184 -0
- package/packages/core/tests/testing-test-client.test.ts +1272 -0
- package/packages/core/tests/time-realm.test.ts +63 -0
- package/packages/core/tests/time.test.ts +238 -0
- package/packages/core/tests/traps/README.md +39 -0
- package/packages/core/tests/traps/anonymous-write.test.ts +145 -0
- package/packages/core/tests/traps/classification-table.test.ts +256 -0
- package/packages/core/tests/traps/err-arm.test.ts +144 -0
- package/packages/core/tests/traps/fetch-spy.ts +39 -0
- package/packages/core/tests/traps/invalidation-after-write.test.ts +178 -0
- package/packages/core/tests/traps/ledger.ts +248 -0
- package/packages/core/tests/traps/root-key-off-local.test.ts +130 -0
- package/packages/core/tests/traps/stale-data-across-users.test.ts +173 -0
- package/packages/core/tests/traps/unresolved-name.test.ts +155 -0
- package/packages/core/tests/traps/update-as-query.test.ts +244 -0
- package/packages/core/tests/traps/update-not-resent.test.ts +126 -0
- package/packages/core/tests/traps/update-resent-when-never-delivered.test.ts +159 -0
- package/packages/core/tests/traps.test-d.ts +476 -0
- package/packages/core/tests/units.test-d.ts +118 -0
- package/packages/core/tests/units.test.ts +634 -0
- package/packages/core/tsconfig.json +19 -0
- package/packages/core/tsconfig.typecheck.json +16 -0
- package/packages/core/vitest.config.ts +13 -0
- package/packages/mcp/LICENSE +21 -0
- package/packages/mcp/README.md +189 -0
- package/packages/mcp/package.json +62 -0
- package/packages/mcp/src/candid/candid-text.test.ts +455 -0
- package/packages/mcp/src/candid/candid-text.ts +501 -0
- package/packages/mcp/src/candid/contract.test.ts +398 -0
- package/packages/mcp/src/candid/formats.ts +155 -0
- package/packages/mcp/src/candid/hex.ts +29 -0
- package/packages/mcp/src/candid/json-schema.test.ts +758 -0
- package/packages/mcp/src/candid/json-schema.ts +454 -0
- package/packages/mcp/src/candid/json.test.ts +1207 -0
- package/packages/mcp/src/candid/json.ts +1053 -0
- package/packages/mcp/src/candid/schema-walk.ts +189 -0
- package/packages/mcp/src/candid/type-text.test.ts +179 -0
- package/packages/mcp/src/candid/type-text.ts +238 -0
- package/packages/mcp/src/cli.ts +134 -0
- package/packages/mcp/src/identity.ts +188 -0
- package/packages/mcp/src/interface/cache.test.ts +48 -0
- package/packages/mcp/src/interface/cache.ts +68 -0
- package/packages/mcp/src/interface/cli-cache.test.ts +150 -0
- package/packages/mcp/src/interface/cli-cache.ts +122 -0
- package/packages/mcp/src/interface/compile-worker.test.ts +246 -0
- package/packages/mcp/src/interface/compile-worker.ts +290 -0
- package/packages/mcp/src/interface/compile.test.ts +155 -0
- package/packages/mcp/src/interface/compile.ts +281 -0
- package/packages/mcp/src/interface/docs.test.ts +105 -0
- package/packages/mcp/src/interface/docs.ts +86 -0
- package/packages/mcp/src/interface/fixtures/icrc1.did +88 -0
- package/packages/mcp/src/interface/hex.ts +8 -0
- package/packages/mcp/src/interface/ids.test.ts +71 -0
- package/packages/mcp/src/interface/ids.ts +87 -0
- package/packages/mcp/src/interface/loader.test.ts +804 -0
- package/packages/mcp/src/interface/loader.ts +519 -0
- package/packages/mcp/src/interface/read-candid.test.ts +300 -0
- package/packages/mcp/src/interface/read-candid.ts +259 -0
- package/packages/mcp/src/interface/types.ts +113 -0
- package/packages/mcp/src/network.ts +122 -0
- package/packages/mcp/src/options.ts +149 -0
- package/packages/mcp/src/server/approval.ts +159 -0
- package/packages/mcp/src/server/naming.ts +43 -0
- package/packages/mcp/src/server/results.ts +102 -0
- package/packages/mcp/src/server/server.ts +797 -0
- package/packages/mcp/src/server/tools.ts +116 -0
- package/packages/mcp/src/usage.ts +40 -0
- package/packages/mcp/tests/approval.test.ts +60 -0
- package/packages/mcp/tests/identity.test.ts +234 -0
- package/packages/mcp/tests/naming.test.ts +62 -0
- package/packages/mcp/tests/options.test.ts +140 -0
- package/packages/mcp/tests/server.test.ts +997 -0
- package/packages/mcp/tsconfig.json +19 -0
- package/packages/mcp/tsconfig.typecheck.json +10 -0
- package/packages/mcp/vitest.config.ts +10 -0
- package/packages/react/LICENSE +21 -0
- package/packages/react/README.md +328 -0
- package/packages/react/llms.txt +95 -0
- package/packages/react/package.json +95 -0
- package/packages/react/src/index.tsx +678 -0
- package/packages/react/tests/abandoned-render.test.tsx +816 -0
- package/packages/react/tests/auth.test.tsx +315 -0
- package/packages/react/tests/caller-keys.test.tsx +2334 -0
- package/packages/react/tests/client-time.test.ts +40 -0
- package/packages/react/tests/entry.test.ts +52 -0
- package/packages/react/tests/helpers.tsx +291 -0
- package/packages/react/tests/hydration.test.tsx +262 -0
- package/packages/react/tests/ownership.test.tsx +260 -0
- package/packages/react/tests/provider.server.test.tsx +169 -0
- package/packages/react/tests/query-client.test.tsx +107 -0
- package/packages/react/tests/strict-mode.test.tsx +288 -0
- package/packages/react/tests/suspense.test-d.tsx +357 -0
- package/packages/react/tests/two-query-cores.test.tsx +135 -0
- package/packages/react/tests/types.test-d.tsx +63 -0
- package/packages/react/tsconfig.json +23 -0
- package/packages/react/tsconfig.test.json +8 -0
- package/packages/react/tsconfig.typecheck.json +16 -0
- package/packages/react/vitest.config.ts +39 -0
- package/packages/vite-plugin/LICENSE +21 -0
- package/packages/vite-plugin/README.md +532 -0
- package/packages/vite-plugin/package.json +76 -0
- package/packages/vite-plugin/src/describe-value.test.ts +43 -0
- package/packages/vite-plugin/src/describe-value.ts +167 -0
- package/packages/vite-plugin/src/dev-environment.test.ts +747 -0
- package/packages/vite-plugin/src/dev-environment.ts +510 -0
- package/packages/vite-plugin/src/env.ts +431 -0
- package/packages/vite-plugin/src/fetch.ts +348 -0
- package/packages/vite-plugin/src/file-identity.ts +106 -0
- package/packages/vite-plugin/src/generate.ts +691 -0
- package/packages/vite-plugin/src/index.test.ts +1243 -0
- package/packages/vite-plugin/src/index.ts +2565 -0
- package/packages/vite-plugin/src/interface.ts +339 -0
- package/packages/vite-plugin/src/local-host.ts +34 -0
- package/packages/vite-plugin/src/options.ts +641 -0
- package/packages/vite-plugin/src/principal-text.test.ts +87 -0
- package/packages/vite-plugin/src/principal-text.ts +98 -0
- package/packages/vite-plugin/tests/close.test.ts +141 -0
- package/packages/vite-plugin/tests/environment-mode.test.ts +136 -0
- package/packages/vite-plugin/tests/fake-ic.ts +214 -0
- package/packages/vite-plugin/tests/fetch-sdk.test.ts +27 -0
- package/packages/vite-plugin/tests/fetch.test.ts +1196 -0
- package/packages/vite-plugin/tests/fixtures/fake-cli/bin.js +90 -0
- package/packages/vite-plugin/tests/fixtures/fake-cli/package.json +8 -0
- package/packages/vite-plugin/tests/fixtures/icrc1.did +88 -0
- package/packages/vite-plugin/tests/generation.test.ts +1036 -0
- package/packages/vite-plugin/tests/icp-command.test.ts +194 -0
- package/packages/vite-plugin/tests/icp-timeout.test.ts +83 -0
- package/packages/vite-plugin/tests/no-process-exit.test.ts +83 -0
- package/packages/vite-plugin/tests/on-generated.test.ts +323 -0
- package/packages/vite-plugin/tests/output-collision.test.ts +329 -0
- package/packages/vite-plugin/tests/package.test.ts +62 -0
- package/packages/vite-plugin/tests/project.test.ts +2993 -0
- package/packages/vite-plugin/tests/support.ts +339 -0
- package/packages/vite-plugin/tests/timeout.test.ts +107 -0
- package/packages/vite-plugin/tsconfig.json +23 -0
- package/packages/vite-plugin/tsconfig.typecheck.json +16 -0
- package/packages/vite-plugin/vitest.config.ts +9 -0
- package/pnpm-workspace.yaml +23 -0
- package/scripts/ai-context-files.js +72 -0
- package/scripts/assemble-docs-site.mjs +485 -0
- package/scripts/assemble-docs-site.test.mjs +568 -0
- package/scripts/build-examples.js +58 -0
- package/scripts/check-ai-context.js +963 -0
- package/scripts/check-ai-context.test.mjs +811 -0
- package/scripts/check-exports.mjs +767 -0
- package/scripts/check-exports.test.mjs +1136 -0
- package/scripts/check-snippets/ambient.d.ts +428 -0
- package/scripts/check-snippets/app/generated/backend.did +7 -0
- package/scripts/check-snippets/app/generated/backend.ts +15 -0
- package/scripts/check-snippets/app/generated/icrc1.did +88 -0
- package/scripts/check-snippets/app/generated/icrc1.ts +78 -0
- package/scripts/check-snippets/app/globals.ts +26 -0
- package/scripts/check-snippets/docs/guides/App.tsx +21 -0
- package/scripts/check-snippets/docs/guides/app/providers.tsx +23 -0
- package/scripts/check-snippets/docs/guides/auth-client.ts +47 -0
- package/scripts/check-snippets/docs/guides/canisters/backend.ts +3 -0
- package/scripts/check-snippets/docs/guides/canisters/icrc1.ts +3 -0
- package/scripts/check-snippets/docs/guides/components/LedgerCard.tsx +26 -0
- package/scripts/check-snippets/docs/guides/components/shell.tsx +11 -0
- package/scripts/check-snippets/docs/guides/components/sign-in-page.tsx +29 -0
- package/scripts/check-snippets/docs/guides/components/slow-section.tsx +5 -0
- package/scripts/check-snippets/docs/guides/generated/accounts.ts +95 -0
- package/scripts/check-snippets/docs/guides/generated/ckbtc_minter.ts +40 -0
- package/scripts/check-snippets/docs/guides/generated/icp_ledger.ts +267 -0
- package/scripts/check-snippets/docs/guides/ledger.ts +10 -0
- package/scripts/check-snippets/docs/guides/server/request-client.ts +8 -0
- package/scripts/check-snippets.mjs +993 -0
- package/scripts/check-snippets.test.mjs +338 -0
- package/scripts/check-stack.mjs +501 -0
- package/scripts/check-stack.test.mjs +800 -0
- package/scripts/clean-node-modules.js +67 -0
- package/scripts/example-projects.js +58 -0
- package/scripts/export-budget.mjs +145 -0
- package/scripts/faults.json +6749 -0
- package/scripts/lib/fault-workspace.mjs +210 -0
- package/scripts/lib/io.mjs +29 -0
- package/scripts/lib/io.test.mjs +38 -0
- package/scripts/lib/markdown-fences.mjs +56 -0
- package/scripts/lib/markdown-fences.test.mjs +84 -0
- package/scripts/lib/package-exports.mjs +54 -0
- package/scripts/lib/package-exports.test.mjs +66 -0
- package/scripts/lib/release-pr-steps.mjs +47 -0
- package/scripts/release-tag.mjs +95 -0
- package/scripts/release-tag.test.mjs +278 -0
- package/scripts/release-workflow.test.mjs +98 -0
- package/scripts/release.js +167 -0
- package/scripts/release.test.mjs +347 -0
- package/scripts/removed-v3-names.js +125 -0
- package/scripts/size-app/.size-limit.js +90 -0
- package/scripts/size-app/core.js +3 -0
- package/scripts/size-app/package.json +24 -0
- package/scripts/size-app/react.js +4 -0
- package/scripts/size-app.test.mjs +73 -0
- package/scripts/sync-ai-context-versions.js +102 -0
- package/scripts/sync-example-versions.js +151 -0
- package/scripts/test-examples-published.mjs +1585 -0
- package/scripts/test-examples-published.test.mjs +1586 -0
- package/scripts/traps/any-method-vars-not-a-list.json +5 -0
- package/scripts/traps/canister-method-unknown.json +5 -0
- package/scripts/traps/client-says-who-calls-once.json +5 -0
- package/scripts/traps/client-says-who-calls.json +5 -0
- package/scripts/traps/err-only-on-canister-err.json +14 -0
- package/scripts/traps/format-units-refuses-missing-value.json +5 -0
- package/scripts/traps/format-units-refuses-number.json +5 -0
- package/scripts/traps/format-units-unknown-option.json +5 -0
- package/scripts/traps/guard-err-is-the-methods.json +5 -0
- package/scripts/traps/guard-false-keeps-every-kind.json +5 -0
- package/scripts/traps/internals-not-exported.json +9 -0
- package/scripts/traps/network-refuses-unknown-name.json +5 -0
- package/scripts/traps/never-err-has-no-type.json +5 -0
- package/scripts/traps/options-method-unknown.json +5 -0
- package/scripts/traps/options-vars-wrong-type.json +5 -0
- package/scripts/traps/parse-units-refuses-number.json +5 -0
- package/scripts/traps/parse-units-unknown-option.json +5 -0
- package/scripts/traps/principal-is-not-plain-text.json +14 -0
- package/scripts/traps/query-options-refuses-oneway.json +5 -0
- package/scripts/traps/query-options-refuses-update.json +5 -0
- package/scripts/traps/refuse-next-unknown-option.json +5 -0
- package/scripts/traps/resend-key-reads-the-methods-arg.json +5 -0
- package/scripts/traps/resend-states-its-dedupe-key.json +5 -0
- package/scripts/traps/suspense-refuses-argument-skip.json +5 -0
- package/scripts/traps/suspense-refuses-skippable-read.json +5 -0
- package/scripts/traps/suspense-refuses-vars-skip-fits.json +5 -0
- package/scripts/traps/test-handler-method-unknown.json +5 -0
- package/scripts/traps/test-handler-nat-is-bigint.json +5 -0
- package/scripts/traps/test-handler-takes-the-domain-value.json +5 -0
- package/scripts/traps/vars-of-two-arguments-are-the-tuple.json +5 -0
- package/scripts/typecheck-examples.js +122 -0
- package/scripts/verify-faults.mjs +375 -0
- package/scripts/verify-faults.test.mjs +402 -0
- package/scripts/verify-package-artifacts.mjs +590 -0
- package/scripts/verify-peer-floors.js +399 -0
- package/scripts/verify-peer-floors.test.mjs +422 -0
- package/scripts/verify-test-fails-rules.mjs +104 -0
- package/scripts/verify-test-fails-rules.test.mjs +215 -0
- package/scripts/verify-test-fails.js +371 -0
- package/scripts/verify-traps.mjs +505 -0
- package/scripts/verify-traps.test.mjs +521 -0
- package/scripts/verify-typescript-floor.js +239 -0
- package/skill-packages/ic-reactor/SKILL.md +103 -0
- package/stack.json +8 -0
- package/tsconfig.base.json +23 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,1585 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Runs every example against the @ic-reactor/* packages published on npm, the
|
|
4
|
+
* way someone who copies the example out of this repository gets them, and the
|
|
5
|
+
* way StackBlitz installs it.
|
|
6
|
+
*
|
|
7
|
+
* The examples job of CI runs the examples inside the workspace, where
|
|
8
|
+
* `linkWorkspacePackages` links each @ic-reactor/* range to packages/*. That
|
|
9
|
+
* proves the examples against the code on the branch, not against the release:
|
|
10
|
+
* an example that uses a name the published release lacks stays green there.
|
|
11
|
+
* This script tests against one dist-tag per run (see `resolveRun`): `beta`
|
|
12
|
+
* for a prerelease and `latest` for a stable release, of the `--wait-for`
|
|
13
|
+
* version or else of the branch's own. A run without `--wait-for` whose
|
|
14
|
+
* examples all wait on the branch's own, unpublished version (a release pull
|
|
15
|
+
* request, or its merge before the tag: `awaitsRelease`) runs only the checks
|
|
16
|
+
* that need no registry (`staticFindings`) and defers to the release's run.
|
|
17
|
+
* For each example, it:
|
|
18
|
+
*
|
|
19
|
+
* 1. copies its git-tracked files to a directory outside the repository,
|
|
20
|
+
* and refuses to run if a parent of that directory holds a node_modules
|
|
21
|
+
* or a package.json (Node, Vite and vitest resolve a bare import by
|
|
22
|
+
* walking up into a parent's node_modules, so an undeclared import would
|
|
23
|
+
* be found there);
|
|
24
|
+
* 2. refuses a `workspace:`, `file:`, `link:` or `portal:` range, which no
|
|
25
|
+
* standalone install resolves, and a tracked `.npmrc` (it could point the
|
|
26
|
+
* @ic-reactor scope at another registry, for this install and on
|
|
27
|
+
* StackBlitz); requires the `typecheck`, `test` and `build` scripts; and
|
|
28
|
+
* checks each @ic-reactor/* range is satisfied by the version the run's
|
|
29
|
+
* dist-tag names (asking npm, so its own semver decides);
|
|
30
|
+
* 3. installs from registry.npmjs.org with npm (no workspace, no links, no
|
|
31
|
+
* lockfile of ours), the default registry and the @ic-reactor and
|
|
32
|
+
* @meantjs scopes all pinned to it on the command line, over any
|
|
33
|
+
* `.npmrc`, with an environment that holds nothing of the repository: no
|
|
34
|
+
* `npm_*` or `pnpm_*` variable a `pnpm run` set, and no PATH entry
|
|
35
|
+
* inside the repository;
|
|
36
|
+
* 4. checks every installed @ic-reactor/* package is a real directory, not
|
|
37
|
+
* a symlink, at exactly that version, and that npm's lockfile
|
|
38
|
+
* records it as the registry's own tarball, with the integrity the
|
|
39
|
+
* registry publishes for that version (the proof the bytes are the
|
|
40
|
+
* published ones);
|
|
41
|
+
* 5. runs the example's `typecheck`, `test` and `build` scripts, as the
|
|
42
|
+
* examples job does (a Next example gets the same `next-env.d.ts` shim
|
|
43
|
+
* `typecheck-examples.js` writes);
|
|
44
|
+
* 6. runs the `startCommand` of its `.stackblitzrc`, the command StackBlitz
|
|
45
|
+
* runs after its install, under a model of what StackBlitz's WebContainer
|
|
46
|
+
* lacks (WEBCONTAINER_MODEL: no global `Iterator`, and an
|
|
47
|
+
* AsyncLocalStorage that keeps no store past the synchronous part of
|
|
48
|
+
* `run()`): `npm run dev [-- <args>]` is started, its first page fetched
|
|
49
|
+
* with every same-origin script on it and every app module those import
|
|
50
|
+
* (a TanStack Start example's from its router and route tree as well:
|
|
51
|
+
* `startEntrySeeds`), and the server stopped (a Next example with
|
|
52
|
+
* `NEXT_TEST_WASM=1`, so Next
|
|
53
|
+
* loads the WebAssembly bindings a WebContainer loads); any other command
|
|
54
|
+
* must exit 0, a script step 5 already ran on plain Node included.
|
|
55
|
+
*
|
|
56
|
+
* Every example's tests run without a replica (each is a `createTestClient()`
|
|
57
|
+
* over an in-memory one), so nothing here starts a network. The vite-wallet
|
|
58
|
+
* example needs icp-cli's local network to run the wallet itself, which is why
|
|
59
|
+
* its `.stackblitzrc` runs its tests instead of its dev server. next-ssr's
|
|
60
|
+
* runs its tests too: Next 16 renders no page in a WebContainer (see
|
|
61
|
+
* WEBCONTAINER_MODEL).
|
|
62
|
+
*
|
|
63
|
+
* Usage: node scripts/test-examples-published.mjs [--keep] [--tmp <dir>]
|
|
64
|
+
* [--wait-for <version>] [<example> ...]
|
|
65
|
+
* --keep leave the copies on disk and print where they are
|
|
66
|
+
* --tmp <dir> make the copies under <dir> (default: the OS tmpdir)
|
|
67
|
+
* --wait-for <version> wait (up to 10 minutes) until the dist-tag the
|
|
68
|
+
* release of <version> publishes under (`beta` for a
|
|
69
|
+
* prerelease, `latest` for a stable version) of core
|
|
70
|
+
* and of every @ic-reactor/* package the examples
|
|
71
|
+
* declare names <version> (a leading "v" is
|
|
72
|
+
* dropped), and the registry serves its integrity
|
|
73
|
+
* and its tarball: the release workflow runs this
|
|
74
|
+
* right after publishing, with the tag it published.
|
|
75
|
+
* Without it, the run tests the dist-tag of the
|
|
76
|
+
* branch's version (packages/core/package.json), or
|
|
77
|
+
* defers while the examples wait on that version's
|
|
78
|
+
* release
|
|
79
|
+
* <example> a directory under examples/ (default: every one)
|
|
80
|
+
*/
|
|
81
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process"
|
|
82
|
+
import {
|
|
83
|
+
copyFileSync,
|
|
84
|
+
existsSync,
|
|
85
|
+
lstatSync,
|
|
86
|
+
mkdirSync,
|
|
87
|
+
mkdtempSync,
|
|
88
|
+
readFileSync,
|
|
89
|
+
readdirSync,
|
|
90
|
+
realpathSync,
|
|
91
|
+
rmSync,
|
|
92
|
+
writeFileSync,
|
|
93
|
+
} from "node:fs"
|
|
94
|
+
import { createServer } from "node:net"
|
|
95
|
+
import { tmpdir } from "node:os"
|
|
96
|
+
import { delimiter, dirname, join, relative, resolve, sep } from "node:path"
|
|
97
|
+
import { setTimeout as sleep } from "node:timers/promises"
|
|
98
|
+
import { fileURLToPath } from "node:url"
|
|
99
|
+
|
|
100
|
+
import { discoverExampleDirs, root as repoRoot } from "./example-projects.js"
|
|
101
|
+
|
|
102
|
+
/** Where every install and every `npm view` goes, whatever ~/.npmrc says. */
|
|
103
|
+
export const REGISTRY = "https://registry.npmjs.org/"
|
|
104
|
+
|
|
105
|
+
/** The packages this repository publishes. */
|
|
106
|
+
export const SCOPE = "@ic-reactor/"
|
|
107
|
+
|
|
108
|
+
/** A version, or a tag of one: MAJOR.MINOR.PATCH, an optional -prerelease. */
|
|
109
|
+
const RELEASE_VERSION =
|
|
110
|
+
/^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The dist-tag a run tests against.
|
|
114
|
+
*
|
|
115
|
+
* With `--wait-for`, the one the release of that version publishes under:
|
|
116
|
+
* `beta` for a prerelease (a `-` part after MAJOR.MINOR.PATCH, with or without
|
|
117
|
+
* a leading `v`), `latest` for a stable version. This mirrors the rule of
|
|
118
|
+
* `releaseFor` in scripts/release-tag.mjs, with which release.yml chooses the
|
|
119
|
+
* dist-tag it publishes under once the GA flip (#836) brings that file; the
|
|
120
|
+
* rule is repeated here rather than imported, so this script works with or
|
|
121
|
+
* without it.
|
|
122
|
+
*
|
|
123
|
+
* A run without `--wait-for` (push, pull request, weekly, manual) passes the
|
|
124
|
+
* branch's own version instead (see `resolveRun`), so it gets the tag of the
|
|
125
|
+
* branch's last release. With no value at all, `latest`.
|
|
126
|
+
*
|
|
127
|
+
* @param {string | undefined} waitFor the `--wait-for` value (or the
|
|
128
|
+
* branch's version), if any
|
|
129
|
+
* @returns {"latest" | "beta"}
|
|
130
|
+
* @throws {Error} when `waitFor` is not a version
|
|
131
|
+
*/
|
|
132
|
+
export function distTagFor(waitFor) {
|
|
133
|
+
if (waitFor === undefined) return "latest"
|
|
134
|
+
const match = RELEASE_VERSION.exec(waitFor)
|
|
135
|
+
if (!match) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`--wait-for ${waitFor} is not a release version (MAJOR.MINOR.PATCH, with an optional -prerelease and no +build).`
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
return match[4] === undefined ? "latest" : "beta"
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Range protocols that only resolve inside a workspace or on one disk. */
|
|
144
|
+
export const LOCAL_PROTOCOLS = ["workspace:", "file:", "link:", "portal:"]
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* The scopes pinned to REGISTRY on every npm command. `--registry` replaces
|
|
148
|
+
* only the default registry: a `@scope:registry=` setting in any `.npmrc`
|
|
149
|
+
* (the user's, or one in the project) still wins for its scope.
|
|
150
|
+
*/
|
|
151
|
+
export const PINNED_SCOPES = ["@ic-reactor", "@meantjs"]
|
|
152
|
+
|
|
153
|
+
/** The registry flags of every npm command this script runs. */
|
|
154
|
+
export const REGISTRY_ARGS = [
|
|
155
|
+
`--registry=${REGISTRY}`,
|
|
156
|
+
...PINNED_SCOPES.map((scope) => `--${scope}:registry=${REGISTRY}`),
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
/** The scripts every example must have: what the examples job runs. */
|
|
160
|
+
export const REQUIRED_SCRIPTS = ["typecheck", "test", "build"]
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Examples allowed to lack one of REQUIRED_SCRIPTS: { example: { script:
|
|
164
|
+
* reason } }. Empty: all five have all three.
|
|
165
|
+
*/
|
|
166
|
+
export const SCRIPT_EXCEPTIONS = {}
|
|
167
|
+
|
|
168
|
+
const DEPENDENCY_FIELDS = [
|
|
169
|
+
"dependencies",
|
|
170
|
+
"devDependencies",
|
|
171
|
+
"optionalDependencies",
|
|
172
|
+
"peerDependencies",
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
/** Same shim as typecheck-examples.js: `next build` writes the real one. */
|
|
176
|
+
const NEXT_ENV = `/// <reference types="next" />\n/// <reference types="next/image-types/global" />\n`
|
|
177
|
+
|
|
178
|
+
const MINUTE = 60_000
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* A model of what StackBlitz's WebContainer lacks, as the examples' start
|
|
182
|
+
* commands met it there (its `node --version` says v22.22.3), for a preload
|
|
183
|
+
* that `--require` puts into every Node process the start command starts:
|
|
184
|
+
*
|
|
185
|
+
* - No global `Iterator`: `typeof Iterator` is "undefined" there, though Node
|
|
186
|
+
* 22 defines it. jsdom 30.1 subclasses it, so all of vite-wallet's test
|
|
187
|
+
* files failed to start there with "ReferenceError: Iterator is not
|
|
188
|
+
* defined"; the examples pin jsdom 30.0.1.
|
|
189
|
+
* - An AsyncLocalStorage whose store is gone once `run()` returns, and so
|
|
190
|
+
* after any await: StackBlitz says WebContainer does not implement
|
|
191
|
+
* AsyncLocalStorage (stackblitz/starters#122), and next-ssr's `next dev`
|
|
192
|
+
* answered 500 there with "Invariant: Expected workStore to be
|
|
193
|
+
* initialized". Under the model `next dev` fails with the same stack, and
|
|
194
|
+
* `next build --webpack` fails at its first prerender.
|
|
195
|
+
*
|
|
196
|
+
* Under this model each of the two failures happens here as it did on
|
|
197
|
+
* StackBlitz, and what worked there (icrc-ledger's dev server, node-agent-tool's
|
|
198
|
+
* demo) still works. It is the two gaps the examples have met, not a
|
|
199
|
+
* WebContainer: a start command green under it can still meet another one.
|
|
200
|
+
*/
|
|
201
|
+
export const WEBCONTAINER_MODEL = `"use strict"
|
|
202
|
+
// Written by scripts/test-examples-published.mjs: see WEBCONTAINER_MODEL there.
|
|
203
|
+
delete globalThis.Iterator
|
|
204
|
+
class AsyncLocalStorage {
|
|
205
|
+
#store = undefined
|
|
206
|
+
getStore() {
|
|
207
|
+
return this.#store
|
|
208
|
+
}
|
|
209
|
+
run(store, fn, ...args) {
|
|
210
|
+
const outer = this.#store
|
|
211
|
+
this.#store = store
|
|
212
|
+
try {
|
|
213
|
+
return fn(...args)
|
|
214
|
+
} finally {
|
|
215
|
+
this.#store = outer
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
exit(fn, ...args) {
|
|
219
|
+
return this.run(undefined, fn, ...args)
|
|
220
|
+
}
|
|
221
|
+
enterWith(store) {
|
|
222
|
+
this.#store = store
|
|
223
|
+
}
|
|
224
|
+
disable() {
|
|
225
|
+
this.#store = undefined
|
|
226
|
+
}
|
|
227
|
+
static bind(fn) {
|
|
228
|
+
return fn
|
|
229
|
+
}
|
|
230
|
+
static snapshot() {
|
|
231
|
+
return (fn, ...args) => fn(...args)
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
Object.defineProperty(require("node:async_hooks"), "AsyncLocalStorage", {
|
|
235
|
+
value: AsyncLocalStorage,
|
|
236
|
+
writable: true,
|
|
237
|
+
enumerable: true,
|
|
238
|
+
configurable: true,
|
|
239
|
+
})
|
|
240
|
+
require("node:module").syncBuiltinESMExports()
|
|
241
|
+
`
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* `env` with the preload at `preload` (WEBCONTAINER_MODEL) added to
|
|
245
|
+
* NODE_OPTIONS, which every Node process the start command starts inherits:
|
|
246
|
+
* vitest's workers and Next's servers included.
|
|
247
|
+
*
|
|
248
|
+
* @param {NodeJS.ProcessEnv} env
|
|
249
|
+
* @param {string} preload
|
|
250
|
+
* @returns {NodeJS.ProcessEnv}
|
|
251
|
+
*/
|
|
252
|
+
export function webContainerEnv(env, preload) {
|
|
253
|
+
const option = `--require ${JSON.stringify(preload)}`
|
|
254
|
+
return {
|
|
255
|
+
...env,
|
|
256
|
+
NODE_OPTIONS: env.NODE_OPTIONS ? `${env.NODE_OPTIONS} ${option}` : option,
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Every dependency a manifest declares, with the field it is declared in.
|
|
262
|
+
*
|
|
263
|
+
* @param {Record<string, any>} manifest
|
|
264
|
+
* @returns {{ field: string, name: string, range: string }[]}
|
|
265
|
+
*/
|
|
266
|
+
export function declaredDependencies(manifest) {
|
|
267
|
+
return DEPENDENCY_FIELDS.flatMap((field) =>
|
|
268
|
+
Object.entries(manifest[field] ?? {}).map(([name, range]) => ({
|
|
269
|
+
field,
|
|
270
|
+
name,
|
|
271
|
+
range: String(range),
|
|
272
|
+
}))
|
|
273
|
+
)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* What stops this manifest from installing standalone against the version
|
|
278
|
+
* `tag` names on npm (`version`). `satisfying(name, range)` resolves with the
|
|
279
|
+
* published versions of `name` the range accepts (npm's answer, in the
|
|
280
|
+
* script).
|
|
281
|
+
*
|
|
282
|
+
* @param {Record<string, any>} manifest
|
|
283
|
+
* @param {{
|
|
284
|
+
* tag: string,
|
|
285
|
+
* version: string,
|
|
286
|
+
* satisfying: (name: string, range: string) => Promise<string[]> | string[],
|
|
287
|
+
* }} options
|
|
288
|
+
* @returns {Promise<string[]>}
|
|
289
|
+
*/
|
|
290
|
+
export async function checkManifest(manifest, { tag, version, satisfying }) {
|
|
291
|
+
const findings = []
|
|
292
|
+
for (const { field, name, range } of declaredDependencies(manifest)) {
|
|
293
|
+
const protocol = LOCAL_PROTOCOLS.find((p) => range.startsWith(p))
|
|
294
|
+
if (protocol) {
|
|
295
|
+
findings.push(
|
|
296
|
+
`${field}.${name} is "${range}": a ${protocol} range resolves only inside this repository, never in a standalone install or on StackBlitz`
|
|
297
|
+
)
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
if (!name.startsWith(SCOPE)) continue
|
|
301
|
+
const versions = await satisfying(name, range)
|
|
302
|
+
if (!versions.includes(version)) {
|
|
303
|
+
findings.push(
|
|
304
|
+
`${field}.${name} is "${range}", which ${tag} (${version}) does not satisfy` +
|
|
305
|
+
(versions.length > 0
|
|
306
|
+
? `: npm would install ${versions.at(-1)}`
|
|
307
|
+
: ": no published version satisfies it")
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return findings
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Whether the examples wait on a release that is not published yet, so that
|
|
316
|
+
* a run without `--wait-for` has nothing it can test. That is so when the
|
|
317
|
+
* branch's own version (`packages/core/package.json`, which `release.js`
|
|
318
|
+
* bumps, syncing every example to `^<version>`) is not among the registry's
|
|
319
|
+
* versions, and every @ic-reactor/* range of every example is exactly
|
|
320
|
+
* `^<version>`: a release pull request, or its merge before the tag publishes
|
|
321
|
+
* it. No published version can satisfy those ranges yet, and `release.yml`'s
|
|
322
|
+
* `examples-published` job tests the examples with `--wait-for` once the
|
|
323
|
+
* release is out. Any other range is checked as usual and fails as usual, and
|
|
324
|
+
* so does a local protocol (`workspace:`, `file:` …) on any dependency of any
|
|
325
|
+
* example, @ic-reactor or not: no deferral skips that check.
|
|
326
|
+
*
|
|
327
|
+
* @param {Record<string, any>[]} manifests the examples' package.json files
|
|
328
|
+
* @param {{ branchVersion: string | undefined, publishedVersions: string[] }} state
|
|
329
|
+
* @returns {boolean}
|
|
330
|
+
*/
|
|
331
|
+
export function awaitsRelease(manifests, { branchVersion, publishedVersions }) {
|
|
332
|
+
if (!branchVersion || publishedVersions.includes(branchVersion)) return false
|
|
333
|
+
// A local protocol on any dependency, not only an @ic-reactor one, is what
|
|
334
|
+
// checkManifest refuses without asking the registry: never defer past it.
|
|
335
|
+
const local = manifests.some((manifest) =>
|
|
336
|
+
declaredDependencies(manifest).some(({ range }) =>
|
|
337
|
+
LOCAL_PROTOCOLS.some((protocol) => range.startsWith(protocol))
|
|
338
|
+
)
|
|
339
|
+
)
|
|
340
|
+
if (local) return false
|
|
341
|
+
// Every example must wait on the release itself: one with no @ic-reactor
|
|
342
|
+
// range, or any other range, can be tested now and is not deferred.
|
|
343
|
+
return (
|
|
344
|
+
manifests.length > 0 &&
|
|
345
|
+
manifests.every((manifest) => {
|
|
346
|
+
const ranges = declaredDependencies(manifest)
|
|
347
|
+
.filter(({ name }) => name.startsWith(SCOPE))
|
|
348
|
+
.map(({ range }) => range)
|
|
349
|
+
return (
|
|
350
|
+
ranges.length > 0 &&
|
|
351
|
+
ranges.every((range) => range === `^${branchVersion}`)
|
|
352
|
+
)
|
|
353
|
+
})
|
|
354
|
+
)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* The @ic-reactor/* packages installed in `projectDir`, and what is wrong with
|
|
359
|
+
* them: one the manifest declares that is missing, a symlink (a link into a
|
|
360
|
+
* workspace, not a package from the registry), or a version other than
|
|
361
|
+
* `expectedVersion`, the one `tag` names.
|
|
362
|
+
*
|
|
363
|
+
* @param {string} projectDir
|
|
364
|
+
* @param {string[]} declared the @ic-reactor/* names the manifest declares
|
|
365
|
+
* @param {string} expectedVersion
|
|
366
|
+
* @param {string} tag the dist-tag that names `expectedVersion`
|
|
367
|
+
* @returns {{ installed: { name: string, version: string }[], findings: string[] }}
|
|
368
|
+
*/
|
|
369
|
+
export function checkInstalled(projectDir, declared, expectedVersion, tag) {
|
|
370
|
+
const scopeDir = join(projectDir, "node_modules", SCOPE.slice(0, -1))
|
|
371
|
+
const present = existsSync(scopeDir)
|
|
372
|
+
? readdirSync(scopeDir).map((entry) => `${SCOPE}${entry}`)
|
|
373
|
+
: []
|
|
374
|
+
const names = [...new Set([...declared, ...present])].sort()
|
|
375
|
+
const installed = []
|
|
376
|
+
const findings = []
|
|
377
|
+
for (const name of names) {
|
|
378
|
+
const dir = join(projectDir, "node_modules", name)
|
|
379
|
+
let stat
|
|
380
|
+
try {
|
|
381
|
+
stat = lstatSync(dir)
|
|
382
|
+
} catch {
|
|
383
|
+
findings.push(`${name} is declared but not installed`)
|
|
384
|
+
continue
|
|
385
|
+
}
|
|
386
|
+
if (stat.isSymbolicLink()) {
|
|
387
|
+
findings.push(
|
|
388
|
+
`${name} is a symlink (to ${realpathSync(dir)}), not a package installed from the registry`
|
|
389
|
+
)
|
|
390
|
+
continue
|
|
391
|
+
}
|
|
392
|
+
if (!stat.isDirectory()) {
|
|
393
|
+
findings.push(`${name} is not a directory`)
|
|
394
|
+
continue
|
|
395
|
+
}
|
|
396
|
+
let version
|
|
397
|
+
try {
|
|
398
|
+
version = JSON.parse(
|
|
399
|
+
readFileSync(join(dir, "package.json"), "utf8")
|
|
400
|
+
).version
|
|
401
|
+
} catch {
|
|
402
|
+
findings.push(`${name} has no readable package.json`)
|
|
403
|
+
continue
|
|
404
|
+
}
|
|
405
|
+
installed.push({ name, version })
|
|
406
|
+
if (version !== expectedVersion) {
|
|
407
|
+
findings.push(
|
|
408
|
+
`${name} installed at ${version}, not ${expectedVersion} (${tag})`
|
|
409
|
+
)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return { installed, findings }
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** The @ic-reactor/* entries of a lockfile's `packages`, nested ones too. */
|
|
416
|
+
export function lockedScopePackages(lock) {
|
|
417
|
+
const scope = SCOPE.slice(0, -1)
|
|
418
|
+
return Object.entries(lock?.packages ?? {})
|
|
419
|
+
.map(([path, entry]) => {
|
|
420
|
+
const match = path.match(/(?:^|\/)node_modules\/(@[^/]+\/[^/]+)$/)
|
|
421
|
+
return match && match[1].startsWith(`${scope}/`)
|
|
422
|
+
? { path, name: match[1], entry }
|
|
423
|
+
: undefined
|
|
424
|
+
})
|
|
425
|
+
.filter(Boolean)
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* What shows that an @ic-reactor/* package npm installed is not the registry's
|
|
430
|
+
* published tarball: a lockfile entry that is a link, whose `resolved` is not
|
|
431
|
+
* the tarball the registry names for that version, or whose `integrity` is not
|
|
432
|
+
* the one it publishes. Also an installed package the lockfile does not
|
|
433
|
+
* record. `published` is the registry's `dist` for each name at the version
|
|
434
|
+
* under test.
|
|
435
|
+
*
|
|
436
|
+
* @param {Record<string, any> | undefined} lock package-lock.json
|
|
437
|
+
* @param {{
|
|
438
|
+
* installed: string[],
|
|
439
|
+
* published: Record<string, { tarball: string, integrity: string }>,
|
|
440
|
+
* }} options
|
|
441
|
+
* @returns {string[]}
|
|
442
|
+
*/
|
|
443
|
+
export function checkLockfile(lock, { installed, published }) {
|
|
444
|
+
if (!lock || typeof lock.packages !== "object") {
|
|
445
|
+
return [
|
|
446
|
+
"npm wrote no package-lock.json with a `packages` map, so where the @ic-reactor/* packages came from is unproven",
|
|
447
|
+
]
|
|
448
|
+
}
|
|
449
|
+
const findings = []
|
|
450
|
+
const entries = lockedScopePackages(lock)
|
|
451
|
+
for (const name of installed) {
|
|
452
|
+
if (!entries.some((e) => e.path === `node_modules/${name}`)) {
|
|
453
|
+
findings.push(
|
|
454
|
+
`${name} is installed but package-lock.json does not record it`
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
for (const { path, name, entry } of entries) {
|
|
459
|
+
const dist = published[name]
|
|
460
|
+
if (entry.link) {
|
|
461
|
+
findings.push(
|
|
462
|
+
`${path} is a link (to ${entry.resolved}) in package-lock.json, not a tarball from the registry`
|
|
463
|
+
)
|
|
464
|
+
continue
|
|
465
|
+
}
|
|
466
|
+
if (!dist) {
|
|
467
|
+
findings.push(
|
|
468
|
+
`${path} is ${entry.version}: the registry's tarball for it was not looked up`
|
|
469
|
+
)
|
|
470
|
+
continue
|
|
471
|
+
}
|
|
472
|
+
if (entry.resolved !== dist.tarball || !dist.tarball.startsWith(REGISTRY)) {
|
|
473
|
+
findings.push(
|
|
474
|
+
`${path} was resolved from ${entry.resolved ?? "nowhere"}, not ${dist.tarball}`
|
|
475
|
+
)
|
|
476
|
+
}
|
|
477
|
+
if (entry.integrity !== dist.integrity) {
|
|
478
|
+
findings.push(
|
|
479
|
+
`${path} has integrity ${entry.integrity ?? "(none)"}, not the registry's ${dist.integrity}`
|
|
480
|
+
)
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return findings
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* The example's tracked files that change how npm resolves its packages: a
|
|
488
|
+
* `.npmrc` can point a scope at another registry, for the install here and
|
|
489
|
+
* for StackBlitz's.
|
|
490
|
+
*
|
|
491
|
+
* @param {string[]} files paths relative to the example
|
|
492
|
+
* @returns {string[]}
|
|
493
|
+
*/
|
|
494
|
+
export function trackedConfigFindings(files) {
|
|
495
|
+
return files
|
|
496
|
+
.filter((file) => file.split("/").at(-1) === ".npmrc")
|
|
497
|
+
.map(
|
|
498
|
+
(file) =>
|
|
499
|
+
`${file} is tracked: an example's .npmrc could point @ic-reactor/* at another registry, here and on StackBlitz, so none is allowed`
|
|
500
|
+
)
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Whether `scripts` defines `name` as npm runs it: a non-empty string. npm
|
|
505
|
+
* skips any other value, and `npm run <name>` then fails with "Missing script".
|
|
506
|
+
*
|
|
507
|
+
* @param {Record<string, unknown> | undefined} scripts
|
|
508
|
+
* @param {string} name
|
|
509
|
+
* @returns {boolean}
|
|
510
|
+
*/
|
|
511
|
+
export function definesScript(scripts, name) {
|
|
512
|
+
const value =
|
|
513
|
+
scripts && Object.hasOwn(scripts, name) ? scripts[name] : undefined
|
|
514
|
+
return typeof value === "string" && value.trim() !== ""
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* The required scripts an example lacks (a script is defined only as a
|
|
519
|
+
* non-empty string: `definesScript`), unless `exceptions` names them.
|
|
520
|
+
*
|
|
521
|
+
* @param {string} example
|
|
522
|
+
* @param {Record<string, string> | undefined} scripts
|
|
523
|
+
* @param {Record<string, Record<string, string>>} [exceptions]
|
|
524
|
+
* @returns {string[]}
|
|
525
|
+
*/
|
|
526
|
+
export function requiredScriptFindings(
|
|
527
|
+
example,
|
|
528
|
+
scripts,
|
|
529
|
+
exceptions = SCRIPT_EXCEPTIONS
|
|
530
|
+
) {
|
|
531
|
+
return REQUIRED_SCRIPTS.filter(
|
|
532
|
+
(script) =>
|
|
533
|
+
!definesScript(scripts, script) && !exceptions[example]?.[script]
|
|
534
|
+
).map(
|
|
535
|
+
(script) =>
|
|
536
|
+
`package.json has no "${script}" script: every example runs typecheck, test and build here (an exception goes in SCRIPT_EXCEPTIONS, with its reason)`
|
|
537
|
+
)
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* The parents of `dir` holding a node_modules or a package.json, from which
|
|
542
|
+
* Node, Vite or vitest would resolve an import the example does not declare.
|
|
543
|
+
* Walks up to the filesystem root, or to `stopAt` (included).
|
|
544
|
+
*
|
|
545
|
+
* @param {string} dir
|
|
546
|
+
* @param {{ stopAt?: string }} [options]
|
|
547
|
+
* @returns {string[]}
|
|
548
|
+
*/
|
|
549
|
+
export function ancestorFindings(dir, { stopAt } = {}) {
|
|
550
|
+
const findings = []
|
|
551
|
+
let current = resolve(dir)
|
|
552
|
+
const last = stopAt === undefined ? undefined : resolve(stopAt)
|
|
553
|
+
while (current !== last) {
|
|
554
|
+
const parent = dirname(current)
|
|
555
|
+
if (parent === current) break
|
|
556
|
+
current = parent
|
|
557
|
+
for (const entry of ["node_modules", "package.json"]) {
|
|
558
|
+
if (existsSync(join(current, entry))) {
|
|
559
|
+
findings.push(
|
|
560
|
+
`${join(current, entry)} is above the scratch directory: an import the example does not declare would resolve there (pass --tmp <dir> with no node_modules or package.json above it)`
|
|
561
|
+
)
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
return findings
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/**
|
|
569
|
+
* A child environment that holds nothing of this repository. A `pnpm run`
|
|
570
|
+
* sets `npm_*` variables (the workspace's config and package among them) and
|
|
571
|
+
* puts the repository's `node_modules/.bin` on PATH, where a binary the
|
|
572
|
+
* example failed to install would still be found.
|
|
573
|
+
*
|
|
574
|
+
* @param {NodeJS.ProcessEnv} env
|
|
575
|
+
* @param {string} root the repository root
|
|
576
|
+
* @returns {NodeJS.ProcessEnv}
|
|
577
|
+
*/
|
|
578
|
+
export function isolatedEnv(env, root) {
|
|
579
|
+
const inside = (entry) => {
|
|
580
|
+
const rel = relative(root, resolve(entry))
|
|
581
|
+
return rel === "" || (!rel.startsWith("..") && !rel.startsWith(sep))
|
|
582
|
+
}
|
|
583
|
+
const out = {}
|
|
584
|
+
for (const [key, value] of Object.entries(env)) {
|
|
585
|
+
if (value === undefined) continue
|
|
586
|
+
if (/^(npm|pnpm)_/i.test(key)) continue
|
|
587
|
+
if (["NODE_PATH", "INIT_CWD", "PNPM_SCRIPT_SRC_DIR"].includes(key)) continue
|
|
588
|
+
out[key] = value
|
|
589
|
+
}
|
|
590
|
+
out.PATH = (env.PATH ?? "")
|
|
591
|
+
.split(delimiter)
|
|
592
|
+
.filter((entry) => entry && !inside(entry))
|
|
593
|
+
.join(delimiter)
|
|
594
|
+
out.npm_config_registry = REGISTRY
|
|
595
|
+
out.npm_config_update_notifier = "false"
|
|
596
|
+
return out
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* The checks that need no registry, for one example: its tracked config files
|
|
601
|
+
* (`trackedConfigFindings`), its required scripts (`requiredScriptFindings`)
|
|
602
|
+
* and its `.stackblitzrc` start command (`stackblitzStartCommand`'s finding,
|
|
603
|
+
* and the npm script it names: `startScriptFindings`).
|
|
604
|
+
* `testExample` runs them on the copy before installing, and `deferredRun`
|
|
605
|
+
* runs them on the checkout when a run defers to the release
|
|
606
|
+
* (`awaitsRelease`), so a deferral never skips them.
|
|
607
|
+
*
|
|
608
|
+
* @param {string} name the example
|
|
609
|
+
* @param {{ files: string[], scripts: Record<string, string> | undefined, start: { finding?: string } }} example
|
|
610
|
+
* @returns {string[]}
|
|
611
|
+
*/
|
|
612
|
+
export function staticFindings(name, { files, scripts, start }) {
|
|
613
|
+
return [
|
|
614
|
+
...trackedConfigFindings(files),
|
|
615
|
+
...requiredScriptFindings(name, scripts),
|
|
616
|
+
...(start.finding ? [start.finding] : []),
|
|
617
|
+
...startScriptFindings(start.command, { files, scripts }),
|
|
618
|
+
]
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
/**
|
|
622
|
+
* A start command must be a plain npm script command, `npm run <script>`
|
|
623
|
+
* (or `npm run-script`, or a bare lifecycle command: `npm test`/`npm t`,
|
|
624
|
+
* `npm start`, `npm stop`, `npm restart`), optionally with
|
|
625
|
+
* arguments after a bare `--`, naming a script the example defines. npm's one
|
|
626
|
+
* default, `npm start` with no `start` script, runs `node server.js`, so it
|
|
627
|
+
* passes when `server.js` is tracked. Anything else is refused, npm options
|
|
628
|
+
* (`--loglevel=silent`) and other programs (`node x.js`, `npx vite`)
|
|
629
|
+
* included: this checks the command without running it, so it accepts only
|
|
630
|
+
* the form it can check (it fails closed).
|
|
631
|
+
*
|
|
632
|
+
* @param {string | undefined} command the `.stackblitzrc` start command
|
|
633
|
+
* @param {{ files: string[], scripts: Record<string, string> | undefined }} example
|
|
634
|
+
* @returns {string[]}
|
|
635
|
+
*/
|
|
636
|
+
export function startScriptFindings(command, { files, scripts }) {
|
|
637
|
+
if (!command) return []
|
|
638
|
+
const parsed = parseNpmCommand(command)
|
|
639
|
+
if (!parsed) {
|
|
640
|
+
return [
|
|
641
|
+
`.stackblitzrc's start command "${command}" is not a plain npm script command: write it as "npm run <script>" (arguments after a bare "--"), so its script can be checked`,
|
|
642
|
+
]
|
|
643
|
+
}
|
|
644
|
+
if (definesScript(scripts, parsed.script)) return []
|
|
645
|
+
if (parsed.script === "start" && files.includes("server.js")) return []
|
|
646
|
+
return [
|
|
647
|
+
`.stackblitzrc's start command "${command}" runs the npm script "${parsed.script}", which package.json does not define`,
|
|
648
|
+
]
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* The command StackBlitz runs after installing, from `.stackblitzrc`.
|
|
653
|
+
*
|
|
654
|
+
* @param {string} projectDir
|
|
655
|
+
* @returns {{ command?: string, finding?: string }}
|
|
656
|
+
*/
|
|
657
|
+
export function stackblitzStartCommand(projectDir) {
|
|
658
|
+
const file = join(projectDir, ".stackblitzrc")
|
|
659
|
+
if (!existsSync(file)) {
|
|
660
|
+
return {
|
|
661
|
+
finding:
|
|
662
|
+
".stackblitzrc is missing: StackBlitz's own choice of start command is not checked here, so each example names its own",
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
let config
|
|
666
|
+
try {
|
|
667
|
+
config = JSON.parse(readFileSync(file, "utf8"))
|
|
668
|
+
} catch (error) {
|
|
669
|
+
return { finding: `.stackblitzrc is not JSON: ${error.message}` }
|
|
670
|
+
}
|
|
671
|
+
if (typeof config.startCommand !== "string" || !config.startCommand.trim()) {
|
|
672
|
+
return { finding: ".stackblitzrc has no startCommand string" }
|
|
673
|
+
}
|
|
674
|
+
return { command: config.startCommand.trim() }
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/**
|
|
678
|
+
* npm's bare commands that run the package script of the same name: the
|
|
679
|
+
* lifecycle scripts, and `t` for `test`. Any other script runs only as
|
|
680
|
+
* `npm run <script>`: `npm dev` is an unknown command to npm.
|
|
681
|
+
*/
|
|
682
|
+
const NPM_LIFECYCLE_COMMANDS = {
|
|
683
|
+
test: "test",
|
|
684
|
+
t: "test",
|
|
685
|
+
start: "start",
|
|
686
|
+
stop: "stop",
|
|
687
|
+
restart: "restart",
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* The package script a start command runs, and the arguments it passes after
|
|
692
|
+
* `--`. `npm run <script>` (or `npm run-script`) for any script, and the bare
|
|
693
|
+
* lifecycle commands (`npm test`, `npm t`, `npm start`, `npm stop`, `npm
|
|
694
|
+
* restart`) for theirs; `npm test` and `npm run test` are the same script.
|
|
695
|
+
* A script name is any unquoted word not starting with `-` (an npm option):
|
|
696
|
+
* `npm run dev.web` names `dev.web`. Anything else, `npm dev` included, is
|
|
697
|
+
* not a script command.
|
|
698
|
+
*
|
|
699
|
+
* @param {string} command
|
|
700
|
+
* @returns {{ script: string, args: string[] } | undefined}
|
|
701
|
+
*/
|
|
702
|
+
export function parseNpmCommand(command) {
|
|
703
|
+
const match = command.match(
|
|
704
|
+
/^npm\s+(?:(run(?:-script)?)\s+)?([^\s-]\S*)(?:\s+--((?:\s+\S+)*))?\s*$/
|
|
705
|
+
)
|
|
706
|
+
if (!match) return undefined
|
|
707
|
+
const [, run, name, rest] = match
|
|
708
|
+
if (name === "run" || name === "run-script") return undefined
|
|
709
|
+
const script = run ? name : NPM_LIFECYCLE_COMMANDS[name]
|
|
710
|
+
if (!script) return undefined
|
|
711
|
+
const args = (rest ?? "").split(/\s+/).filter(Boolean)
|
|
712
|
+
return { script, args }
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* The same-origin scripts a page loads, as absolute URLs, each with whether it
|
|
717
|
+
* is an ES module (`type="module"`).
|
|
718
|
+
*
|
|
719
|
+
* @param {string} html
|
|
720
|
+
* @param {string} pageUrl
|
|
721
|
+
* @returns {{ url: string, module: boolean }[]}
|
|
722
|
+
*/
|
|
723
|
+
export function pageScripts(html, pageUrl) {
|
|
724
|
+
const origin = new URL(pageUrl).origin
|
|
725
|
+
const seen = new Set()
|
|
726
|
+
const scripts = []
|
|
727
|
+
for (const match of html.matchAll(/<script\b([^>]*)>/gi)) {
|
|
728
|
+
const src = match[1].match(/\bsrc="([^"]+)"/i)?.[1]
|
|
729
|
+
if (!src) continue
|
|
730
|
+
const url = new URL(src.replaceAll("&", "&"), pageUrl)
|
|
731
|
+
if (url.origin !== origin || seen.has(url.href)) continue
|
|
732
|
+
seen.add(url.href)
|
|
733
|
+
scripts.push({
|
|
734
|
+
url: url.href,
|
|
735
|
+
module: /\btype=["']?module\b/i.test(match[1]),
|
|
736
|
+
})
|
|
737
|
+
}
|
|
738
|
+
return scripts
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* The app modules an ES module served by a Vite dev server imports, as
|
|
743
|
+
* absolute URLs: its static `import`/`export ... from` and string `import()`
|
|
744
|
+
* specifiers on its own origin. A statement starts a line or follows a `;`:
|
|
745
|
+
* Vite joins its HMR preamble and the module's first statement onto one line
|
|
746
|
+
* (`... from "/@vite/client";import { A } from "/src/A.tsx";`). Only
|
|
747
|
+
* root-relative specifiers are taken: Vite serves every import it resolved as
|
|
748
|
+
* one (`/src/...`), so a relative one left in served code is text in a string
|
|
749
|
+
* or a comment (a code sample in a template literal), not an import.
|
|
750
|
+
* Pre-bundled dependencies (`/node_modules/`) and the dev server's own modules
|
|
751
|
+
* (`/@vite/client`, `/@id/...`, `/@fs/...`) are left out: they are not the
|
|
752
|
+
* example's code, and a dependency re-optimisation could answer them 504
|
|
753
|
+
* while it runs.
|
|
754
|
+
*
|
|
755
|
+
* @param {string} code
|
|
756
|
+
* @param {string} moduleUrl
|
|
757
|
+
* @returns {string[]}
|
|
758
|
+
*/
|
|
759
|
+
export function moduleImports(code, moduleUrl) {
|
|
760
|
+
const origin = new URL(moduleUrl).origin
|
|
761
|
+
const specifiers = [
|
|
762
|
+
...code.matchAll(
|
|
763
|
+
/(?:^|;)[ \t]*(?:import|export)[ \t\n{*][^'"`;]*?\bfrom[ \t]*["']([^"'\n]+)["']/gm
|
|
764
|
+
),
|
|
765
|
+
...code.matchAll(/(?:^|;)[ \t]*import[ \t]*["']([^"'\n]+)["']/gm),
|
|
766
|
+
...code.matchAll(/\bimport\([ \t]*["']([^"'\n]+)["'][ \t]*\)/g),
|
|
767
|
+
].map((match) => match[1])
|
|
768
|
+
const urls = new Set()
|
|
769
|
+
for (const specifier of specifiers) {
|
|
770
|
+
if (!specifier.startsWith("/")) continue
|
|
771
|
+
const url = new URL(specifier, moduleUrl)
|
|
772
|
+
if (url.origin !== origin) continue
|
|
773
|
+
if (/^\/(?:node_modules\/|@)/.test(url.pathname)) continue
|
|
774
|
+
urls.add(url.href)
|
|
775
|
+
}
|
|
776
|
+
return [...urls]
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* The app's own entry modules of a TanStack Start example, from which the walk
|
|
781
|
+
* also starts: its router (what `#tanstack-router-entry` resolves to) and the
|
|
782
|
+
* route tree TanStack Router generates. Each is seeded only when the example
|
|
783
|
+
* tracks it.
|
|
784
|
+
*/
|
|
785
|
+
export const START_ENTRY_FILES = [
|
|
786
|
+
"src/router.tsx",
|
|
787
|
+
"src/router.ts",
|
|
788
|
+
"src/router.jsx",
|
|
789
|
+
"src/router.js",
|
|
790
|
+
"src/routeTree.gen.ts",
|
|
791
|
+
]
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* The modules the walk also starts from when the page's scripts are a TanStack
|
|
795
|
+
* Start dev entry (a `/@id/virtual:tanstack-start-*` URL): that entry reaches
|
|
796
|
+
* the app only through @tanstack/react-start's own modules under `/@fs/`,
|
|
797
|
+
* which `moduleImports` leaves out, so without a seed not one module of the
|
|
798
|
+
* example's would be fetched. The seeds are START_ENTRY_FILES the example
|
|
799
|
+
* tracks, as URLs on the page's origin; a finding when it tracks none. No
|
|
800
|
+
* seeds for any other page.
|
|
801
|
+
*
|
|
802
|
+
* @param {{ url: string }[]} scripts the page's scripts (`pageScripts`)
|
|
803
|
+
* @param {string[]} files the example's tracked files, relative to it
|
|
804
|
+
* @returns {{ seeds: string[], finding?: string }}
|
|
805
|
+
*/
|
|
806
|
+
export function startEntrySeeds(scripts, files) {
|
|
807
|
+
const entry = scripts.find(({ url }) =>
|
|
808
|
+
new URL(url).pathname.startsWith("/@id/virtual:tanstack-start-")
|
|
809
|
+
)
|
|
810
|
+
if (!entry) return { seeds: [] }
|
|
811
|
+
const seeds = START_ENTRY_FILES.filter((file) => files.includes(file)).map(
|
|
812
|
+
(file) => new URL(`/${file}`, entry.url).href
|
|
813
|
+
)
|
|
814
|
+
if (seeds.length > 0) return { seeds }
|
|
815
|
+
return {
|
|
816
|
+
seeds,
|
|
817
|
+
finding: `the page's script ${entry.url} is TanStack Start's dev entry, which reaches the app only through /@fs/, and the example tracks none of ${START_ENTRY_FILES.join(", ")} to start the walk from`,
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
/** `npm view <spec> <field> --json`, from the registry; undefined on E404. */
|
|
822
|
+
function npmView(spec, field) {
|
|
823
|
+
const result = spawnSync(
|
|
824
|
+
"npm",
|
|
825
|
+
["view", spec, field, "--json", ...REGISTRY_ARGS],
|
|
826
|
+
{ encoding: "utf8", env: isolatedEnv(process.env, repoRoot) }
|
|
827
|
+
)
|
|
828
|
+
if (result.status !== 0) {
|
|
829
|
+
if (/E404/.test(result.stdout + result.stderr)) return undefined
|
|
830
|
+
throw new Error(
|
|
831
|
+
`npm view ${spec} ${field} failed: ${result.stderr || result.stdout}`
|
|
832
|
+
)
|
|
833
|
+
}
|
|
834
|
+
return result.stdout.trim() ? JSON.parse(result.stdout) : undefined
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/** The version a dist-tag names, from the registry. */
|
|
838
|
+
function distTagVersion(name, tag, view = npmView) {
|
|
839
|
+
const version = view(`${name}@${tag}`, "version")
|
|
840
|
+
if (typeof version !== "string") {
|
|
841
|
+
throw new Error(`${name} has no ${tag} dist-tag on ${REGISTRY}`)
|
|
842
|
+
}
|
|
843
|
+
return version
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/** The published versions of `name` that `range` accepts, from the registry. */
|
|
847
|
+
function npmSatisfying(name, range) {
|
|
848
|
+
const parsed = npmView(`${name}@${range}`, "version")
|
|
849
|
+
if (parsed === undefined) return []
|
|
850
|
+
return Array.isArray(parsed) ? parsed : [parsed]
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
/** The registry's tarball URL and integrity for each name at `version`. */
|
|
854
|
+
function publishedDist(names, version) {
|
|
855
|
+
const out = {}
|
|
856
|
+
for (const name of names) {
|
|
857
|
+
const dist = npmView(`${name}@${version}`, "dist")
|
|
858
|
+
if (dist?.tarball && dist?.integrity) {
|
|
859
|
+
out[name] = { tarball: dist.tarball, integrity: dist.integrity }
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
return out
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/** The example's git-tracked files, relative to it. */
|
|
866
|
+
function trackedFiles(name) {
|
|
867
|
+
const prefix = `examples/${name}`
|
|
868
|
+
return execFileSync("git", ["-C", repoRoot, "ls-files", "-z", "--", prefix], {
|
|
869
|
+
encoding: "utf8",
|
|
870
|
+
})
|
|
871
|
+
.split("\0")
|
|
872
|
+
.filter(Boolean)
|
|
873
|
+
.map((file) => relative(prefix, file))
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
/** Copies the example's git-tracked files to `dest`. */
|
|
877
|
+
function copyTracked(name, files, dest) {
|
|
878
|
+
const prefix = join(repoRoot, "examples", name)
|
|
879
|
+
for (const file of files) {
|
|
880
|
+
const source = join(prefix, file)
|
|
881
|
+
if (!existsSync(source)) continue
|
|
882
|
+
const target = join(dest, file)
|
|
883
|
+
mkdirSync(dirname(target), { recursive: true })
|
|
884
|
+
copyFileSync(source, target)
|
|
885
|
+
}
|
|
886
|
+
return files.length
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function seconds(ms) {
|
|
890
|
+
return `${(ms / 1000).toFixed(1)}s`
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
/** Runs one command with its output shown, and times it. */
|
|
894
|
+
function runStep(label, command, args, cwd, env, timeoutMs = 15 * MINUTE) {
|
|
895
|
+
console.log(`\n ▶ ${label}: ${[command, ...args].join(" ")}`)
|
|
896
|
+
const started = Date.now()
|
|
897
|
+
const result = spawnSync(command, args, {
|
|
898
|
+
cwd,
|
|
899
|
+
env,
|
|
900
|
+
stdio: "inherit",
|
|
901
|
+
timeout: timeoutMs,
|
|
902
|
+
killSignal: "SIGKILL",
|
|
903
|
+
})
|
|
904
|
+
const ms = Date.now() - started
|
|
905
|
+
const ok = result.status === 0
|
|
906
|
+
if (!ok) {
|
|
907
|
+
const why = result.error
|
|
908
|
+
? result.error.message
|
|
909
|
+
: result.signal
|
|
910
|
+
? `killed by ${result.signal}`
|
|
911
|
+
: `exit ${result.status}`
|
|
912
|
+
console.error(` ✗ ${label} failed (${why}) after ${seconds(ms)}`)
|
|
913
|
+
}
|
|
914
|
+
return { ok, ms }
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
function freePort() {
|
|
918
|
+
return new Promise((resolvePort, reject) => {
|
|
919
|
+
const server = createServer()
|
|
920
|
+
server.unref()
|
|
921
|
+
server.on("error", reject)
|
|
922
|
+
server.listen(0, () => {
|
|
923
|
+
const { port } = server.address()
|
|
924
|
+
server.close(() => resolvePort(port))
|
|
925
|
+
})
|
|
926
|
+
})
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Ends a detached child and everything it started. */
|
|
930
|
+
async function killGroup(child) {
|
|
931
|
+
if (child.exitCode !== null || child.signalCode !== null) return
|
|
932
|
+
const exited = new Promise((done) => child.once("exit", done))
|
|
933
|
+
try {
|
|
934
|
+
process.kill(-child.pid, "SIGTERM")
|
|
935
|
+
} catch {
|
|
936
|
+
return
|
|
937
|
+
}
|
|
938
|
+
const timedOut = await Promise.race([
|
|
939
|
+
exited.then(() => false),
|
|
940
|
+
sleep(5000).then(() => true),
|
|
941
|
+
])
|
|
942
|
+
if (timedOut) {
|
|
943
|
+
try {
|
|
944
|
+
process.kill(-child.pid, "SIGKILL")
|
|
945
|
+
} catch {
|
|
946
|
+
// already gone
|
|
947
|
+
}
|
|
948
|
+
await exited
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
/** At most this many app modules are fetched from one dev server. */
|
|
953
|
+
const MODULE_LIMIT = 500
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* Fetches the page's scripts and the `seeds` (`startEntrySeeds`) and, from
|
|
957
|
+
* each ES module among them, every app module it imports, transitively: a
|
|
958
|
+
* Vite dev server compiles a module only when it is requested, so a module
|
|
959
|
+
* below the entry that fails to transform (an unresolvable import, say)
|
|
960
|
+
* answers 500 only when fetched.
|
|
961
|
+
*/
|
|
962
|
+
async function loadScripts(scripts, deadline, seeds = []) {
|
|
963
|
+
const queue = [
|
|
964
|
+
...scripts.map((s) => ({ ...s, from: "the page" })),
|
|
965
|
+
...seeds.map((url) => ({
|
|
966
|
+
url,
|
|
967
|
+
module: true,
|
|
968
|
+
from: "TanStack Start's dev entry, through /@fs/ (seeded)",
|
|
969
|
+
})),
|
|
970
|
+
]
|
|
971
|
+
const seen = new Set(queue.map((s) => s.url))
|
|
972
|
+
const broken = []
|
|
973
|
+
let modules = 0
|
|
974
|
+
while (queue.length > 0) {
|
|
975
|
+
const { url, module, from } = queue.shift()
|
|
976
|
+
const res = await fetch(url, {
|
|
977
|
+
signal: AbortSignal.timeout(Math.max(1000, deadline - Date.now())),
|
|
978
|
+
})
|
|
979
|
+
const code = await res.text()
|
|
980
|
+
if (!res.ok) {
|
|
981
|
+
broken.push(`${url} (${res.status}, imported by ${from})`)
|
|
982
|
+
continue
|
|
983
|
+
}
|
|
984
|
+
if (!module) continue
|
|
985
|
+
modules++
|
|
986
|
+
for (const next of moduleImports(code, url)) {
|
|
987
|
+
if (seen.has(next) || seen.size >= MODULE_LIMIT) continue
|
|
988
|
+
seen.add(next)
|
|
989
|
+
queue.push({ url: next, module: true, from: url })
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
const app = [...seen].filter((url) =>
|
|
993
|
+
new URL(url).pathname.startsWith("/src/")
|
|
994
|
+
).length
|
|
995
|
+
return { broken, fetched: seen.size, modules, app }
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/**
|
|
999
|
+
* Starts `npm run dev -- <args> --port N` on a free port, fetches its first
|
|
1000
|
+
* page and what that page loads, and stops it. The port is passed as
|
|
1001
|
+
* `--port`, which both Vite and Next take, so a server already on the
|
|
1002
|
+
* example's own port on this machine does not fail the run. `files`, the
|
|
1003
|
+
* example's tracked files, are where a TanStack Start example's entry modules
|
|
1004
|
+
* are found (`startEntrySeeds`).
|
|
1005
|
+
*/
|
|
1006
|
+
async function smokeDevServer(
|
|
1007
|
+
cwd,
|
|
1008
|
+
env,
|
|
1009
|
+
extraArgs,
|
|
1010
|
+
files,
|
|
1011
|
+
timeoutMs = 4 * MINUTE
|
|
1012
|
+
) {
|
|
1013
|
+
const port = await freePort()
|
|
1014
|
+
const url = `http://localhost:${port}/`
|
|
1015
|
+
const args = ["run", "dev", "--", ...extraArgs, "--port", String(port)]
|
|
1016
|
+
const shown = env.NEXT_TEST_WASM ? "NEXT_TEST_WASM=1 " : ""
|
|
1017
|
+
console.log(
|
|
1018
|
+
`\n ▶ start, under the WebContainer model: ${shown}npm ${args.join(" ")}, then GET ${url}`
|
|
1019
|
+
)
|
|
1020
|
+
const started = Date.now()
|
|
1021
|
+
const child = spawn("npm", args, {
|
|
1022
|
+
cwd,
|
|
1023
|
+
env,
|
|
1024
|
+
detached: true,
|
|
1025
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
1026
|
+
})
|
|
1027
|
+
let exit
|
|
1028
|
+
child.once("exit", (code, signal) => (exit = signal ?? `exit ${code}`))
|
|
1029
|
+
let failure = "no page before the deadline"
|
|
1030
|
+
try {
|
|
1031
|
+
const deadline = started + timeoutMs
|
|
1032
|
+
while (Date.now() < deadline) {
|
|
1033
|
+
if (exit) {
|
|
1034
|
+
failure = `the dev server stopped (${exit}) before serving a page`
|
|
1035
|
+
break
|
|
1036
|
+
}
|
|
1037
|
+
let response
|
|
1038
|
+
let body
|
|
1039
|
+
try {
|
|
1040
|
+
response = await fetch(url, {
|
|
1041
|
+
signal: AbortSignal.timeout(Math.max(1000, deadline - Date.now())),
|
|
1042
|
+
})
|
|
1043
|
+
body = await response.text()
|
|
1044
|
+
} catch {
|
|
1045
|
+
// not listening yet
|
|
1046
|
+
await sleep(1000)
|
|
1047
|
+
continue
|
|
1048
|
+
}
|
|
1049
|
+
if (response.ok && /<html[\s>]/i.test(body)) {
|
|
1050
|
+
// The page alone proves little for Vite, whose index.html is static:
|
|
1051
|
+
// its scripts, and the modules they import, are what the dev server
|
|
1052
|
+
// compiles on request.
|
|
1053
|
+
const scripts = pageScripts(body, url)
|
|
1054
|
+
const { seeds, finding } = startEntrySeeds(scripts, files)
|
|
1055
|
+
if (finding) {
|
|
1056
|
+
failure = finding
|
|
1057
|
+
break
|
|
1058
|
+
}
|
|
1059
|
+
const loaded = await loadScripts(scripts, deadline, seeds)
|
|
1060
|
+
if (loaded.broken.length > 0) {
|
|
1061
|
+
failure = `what the page loads did not load: ${loaded.broken.join(", ")}`
|
|
1062
|
+
break
|
|
1063
|
+
}
|
|
1064
|
+
const ms = Date.now() - started
|
|
1065
|
+
const what =
|
|
1066
|
+
seeds.length > 0
|
|
1067
|
+
? `its ${scripts.length} script(s), TanStack Start's dev entry among them, the app's entry modules that entry reaches only through /@fs/ (${seeds.map((s) => new URL(s).pathname).join(", ")}), and the app modules they import loaded (${loaded.fetched} fetched, ${loaded.app} under /src/, ${loaded.modules} ES modules)`
|
|
1068
|
+
: `its ${scripts.length} script(s) and the app modules they import loaded (${loaded.fetched} fetched, ${loaded.modules} ES modules)`
|
|
1069
|
+
console.log(
|
|
1070
|
+
` ✓ ${url} answered ${response.status} with ${body.length} bytes of HTML; ${what}, after ${seconds(ms)}`
|
|
1071
|
+
)
|
|
1072
|
+
return { ok: true, ms }
|
|
1073
|
+
}
|
|
1074
|
+
failure = `${url} answered ${response.status}${response.ok ? " without an <html> document" : ""}`
|
|
1075
|
+
if (!response.ok) break
|
|
1076
|
+
await sleep(1000)
|
|
1077
|
+
}
|
|
1078
|
+
} catch (error) {
|
|
1079
|
+
failure = error.message
|
|
1080
|
+
} finally {
|
|
1081
|
+
await killGroup(child)
|
|
1082
|
+
}
|
|
1083
|
+
const ms = Date.now() - started
|
|
1084
|
+
console.error(` ✗ start failed: ${failure} after ${seconds(ms)}`)
|
|
1085
|
+
return { ok: false, ms }
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
/** One example, start to finish. */
|
|
1089
|
+
async function testExample(name, { tag, version, workDir, preload }) {
|
|
1090
|
+
const dir = join(workDir, name.replaceAll("/", "__"))
|
|
1091
|
+
const steps = {}
|
|
1092
|
+
const findings = []
|
|
1093
|
+
console.log(`\n━━ ${name} → ${dir}`)
|
|
1094
|
+
|
|
1095
|
+
const files = trackedFiles(name)
|
|
1096
|
+
copyTracked(name, files, dir)
|
|
1097
|
+
console.log(` copied ${files.length} tracked files`)
|
|
1098
|
+
|
|
1099
|
+
const manifest = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"))
|
|
1100
|
+
const start = stackblitzStartCommand(dir)
|
|
1101
|
+
findings.push(
|
|
1102
|
+
...staticFindings(name, { files, scripts: manifest.scripts, start })
|
|
1103
|
+
)
|
|
1104
|
+
findings.push(
|
|
1105
|
+
...(await checkManifest(manifest, {
|
|
1106
|
+
tag,
|
|
1107
|
+
version,
|
|
1108
|
+
satisfying: npmSatisfying,
|
|
1109
|
+
}))
|
|
1110
|
+
)
|
|
1111
|
+
if (findings.length > 0) return { name, steps, findings }
|
|
1112
|
+
|
|
1113
|
+
const env = isolatedEnv(process.env, repoRoot)
|
|
1114
|
+
steps.install = runStep(
|
|
1115
|
+
"install",
|
|
1116
|
+
"npm",
|
|
1117
|
+
[
|
|
1118
|
+
"install",
|
|
1119
|
+
"--no-audit",
|
|
1120
|
+
"--no-fund",
|
|
1121
|
+
"--package-lock=true",
|
|
1122
|
+
...REGISTRY_ARGS,
|
|
1123
|
+
],
|
|
1124
|
+
dir,
|
|
1125
|
+
env
|
|
1126
|
+
)
|
|
1127
|
+
if (!steps.install.ok) return { name, steps, findings }
|
|
1128
|
+
|
|
1129
|
+
const declared = declaredDependencies(manifest)
|
|
1130
|
+
.map((d) => d.name)
|
|
1131
|
+
.filter((n) => n.startsWith(SCOPE))
|
|
1132
|
+
const { installed, findings: installFindings } = checkInstalled(
|
|
1133
|
+
dir,
|
|
1134
|
+
declared,
|
|
1135
|
+
version,
|
|
1136
|
+
tag
|
|
1137
|
+
)
|
|
1138
|
+
findings.push(...installFindings)
|
|
1139
|
+
let lock
|
|
1140
|
+
try {
|
|
1141
|
+
lock = JSON.parse(readFileSync(join(dir, "package-lock.json"), "utf8"))
|
|
1142
|
+
} catch {
|
|
1143
|
+
lock = undefined
|
|
1144
|
+
}
|
|
1145
|
+
const locked = [...new Set(lockedScopePackages(lock).map((e) => e.name))]
|
|
1146
|
+
findings.push(
|
|
1147
|
+
...checkLockfile(lock, {
|
|
1148
|
+
installed: installed.map((p) => p.name),
|
|
1149
|
+
published: publishedDist(locked, version),
|
|
1150
|
+
})
|
|
1151
|
+
)
|
|
1152
|
+
console.log(
|
|
1153
|
+
` installed from ${REGISTRY} (lockfile resolved and integrity match the registry's): ` +
|
|
1154
|
+
installed.map((p) => `${p.name}@${p.version}`).join(", ")
|
|
1155
|
+
)
|
|
1156
|
+
if (findings.length > 0) return { name, steps, findings }
|
|
1157
|
+
|
|
1158
|
+
const isNext = Boolean(
|
|
1159
|
+
manifest.dependencies?.next || manifest.devDependencies?.next
|
|
1160
|
+
)
|
|
1161
|
+
if (isNext && !existsSync(join(dir, "next-env.d.ts"))) {
|
|
1162
|
+
writeFileSync(join(dir, "next-env.d.ts"), NEXT_ENV)
|
|
1163
|
+
}
|
|
1164
|
+
for (const script of REQUIRED_SCRIPTS) {
|
|
1165
|
+
if (!manifest.scripts?.[script]) continue
|
|
1166
|
+
steps[script] = runStep(script, "npm", ["run", script], dir, env)
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
// Under the model of what a WebContainer lacks, even a script step 5 ran:
|
|
1170
|
+
// green on Node says nothing about StackBlitz (vite-wallet's tests were).
|
|
1171
|
+
const modelEnv = webContainerEnv(env, preload)
|
|
1172
|
+
const parsed = parseNpmCommand(start.command)
|
|
1173
|
+
if (parsed?.script === "dev") {
|
|
1174
|
+
// A WebContainer has no native binary: Next loads its WebAssembly
|
|
1175
|
+
// bindings there, and NEXT_TEST_WASM makes it do the same here.
|
|
1176
|
+
const devEnv = isNext ? { ...modelEnv, NEXT_TEST_WASM: "1" } : modelEnv
|
|
1177
|
+
steps.start = await smokeDevServer(dir, devEnv, parsed.args, files)
|
|
1178
|
+
} else {
|
|
1179
|
+
const [command, ...args] = start.command.split(/\s+/)
|
|
1180
|
+
steps.start = runStep(
|
|
1181
|
+
"start, under the WebContainer model",
|
|
1182
|
+
command,
|
|
1183
|
+
args,
|
|
1184
|
+
dir,
|
|
1185
|
+
modelEnv,
|
|
1186
|
+
5 * MINUTE
|
|
1187
|
+
)
|
|
1188
|
+
}
|
|
1189
|
+
return { name, steps, findings }
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
function report(results, totalMs) {
|
|
1193
|
+
const columns = ["install", "typecheck", "test", "build", "start"]
|
|
1194
|
+
const cell = (step) =>
|
|
1195
|
+
!step ? "-" : `${step.ok ? "ok" : "FAIL"} ${seconds(step.ms)}`
|
|
1196
|
+
const rows = results.map((r) => {
|
|
1197
|
+
const failed =
|
|
1198
|
+
r.findings.length > 0 || Object.values(r.steps).some((s) => !s.ok)
|
|
1199
|
+
return [
|
|
1200
|
+
r.name,
|
|
1201
|
+
...columns.map((c) => cell(r.steps[c])),
|
|
1202
|
+
failed ? "FAIL" : "ok",
|
|
1203
|
+
]
|
|
1204
|
+
})
|
|
1205
|
+
const header = ["example", ...columns, "result"]
|
|
1206
|
+
const widths = header.map((h, i) =>
|
|
1207
|
+
Math.max(h.length, ...rows.map((row) => row[i].length))
|
|
1208
|
+
)
|
|
1209
|
+
const line = (row) => row.map((v, i) => v.padEnd(widths[i])).join(" ")
|
|
1210
|
+
console.log(
|
|
1211
|
+
`\n${line(header)}\n${widths.map((w) => "-".repeat(w)).join(" ")}`
|
|
1212
|
+
)
|
|
1213
|
+
for (const row of rows) console.log(line(row))
|
|
1214
|
+
for (const r of results) {
|
|
1215
|
+
for (const finding of r.findings) console.error(`\n✗ ${r.name}: ${finding}`)
|
|
1216
|
+
}
|
|
1217
|
+
console.log(`\nTotal ${seconds(totalMs)}`)
|
|
1218
|
+
return rows.some((row) => row.at(-1) === "FAIL")
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
/**
|
|
1222
|
+
* The command line: `--keep`, `--tmp <dir>`, `--wait-for <version>` and the
|
|
1223
|
+
* example names.
|
|
1224
|
+
*
|
|
1225
|
+
* @param {string[]} argv
|
|
1226
|
+
* @returns {{ keep: boolean, tmp?: string, waitFor?: string, names: string[], error?: string }}
|
|
1227
|
+
*/
|
|
1228
|
+
export function parseArgs(argv) {
|
|
1229
|
+
const out = { keep: false, names: [] }
|
|
1230
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1231
|
+
const arg = argv[i]
|
|
1232
|
+
if (arg === "--keep") out.keep = true
|
|
1233
|
+
else if (arg === "--tmp" || arg === "--wait-for") {
|
|
1234
|
+
const value = argv[++i]
|
|
1235
|
+
if (!value || value.startsWith("--")) {
|
|
1236
|
+
return { ...out, error: `${arg} needs a value` }
|
|
1237
|
+
}
|
|
1238
|
+
if (arg === "--tmp") out.tmp = value
|
|
1239
|
+
else out.waitFor = value.replace(/^v/, "")
|
|
1240
|
+
} else if (arg.startsWith("--")) {
|
|
1241
|
+
return { ...out, error: `Unknown option ${arg}` }
|
|
1242
|
+
} else out.names.push(arg)
|
|
1243
|
+
}
|
|
1244
|
+
return out
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
/**
|
|
1248
|
+
* The @ic-reactor/* packages a run reads from the registry: every one the
|
|
1249
|
+
* manifests declare, and core (whose dist-tag names the version under test).
|
|
1250
|
+
*
|
|
1251
|
+
* @param {Record<string, any>[]} manifests
|
|
1252
|
+
* @returns {string[]}
|
|
1253
|
+
*/
|
|
1254
|
+
export function scopePackageNames(manifests) {
|
|
1255
|
+
const names = new Set([`${SCOPE}core`])
|
|
1256
|
+
for (const manifest of manifests) {
|
|
1257
|
+
for (const { name } of declaredDependencies(manifest)) {
|
|
1258
|
+
if (name.startsWith(SCOPE)) names.add(name)
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
return [...names].sort()
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/**
|
|
1265
|
+
* Polls each package until its `tag` dist-tag names `version`, the registry
|
|
1266
|
+
* answers with `version`'s integrity, and it serves `version`'s tarball, for
|
|
1267
|
+
* up to `timeoutMs`. The release workflow publishes the lane's packages
|
|
1268
|
+
* (core, react, vite-plugin and mcp) one after another, so the last one can
|
|
1269
|
+
* lag the first by minutes; every package the run reads must be served before
|
|
1270
|
+
* the examples install. The tarball can lag that metadata by minutes: on
|
|
1271
|
+
* 4.1.0's release run, core's tarball answered 404 for about five minutes
|
|
1272
|
+
* after its dist-tag and integrity named 4.1.0, and every example's install
|
|
1273
|
+
* failed on it.
|
|
1274
|
+
*
|
|
1275
|
+
* `lookup(name)` resolves with what the registry serves now: the version the
|
|
1276
|
+
* dist-tag names (undefined when it has none), `version`'s integrity
|
|
1277
|
+
* (undefined when that version is not served yet), and whether `version`'s
|
|
1278
|
+
* tarball downloads (`served`).
|
|
1279
|
+
*
|
|
1280
|
+
* @param {string[]} names
|
|
1281
|
+
* @param {string} tag
|
|
1282
|
+
* @param {string} version
|
|
1283
|
+
* @param {{
|
|
1284
|
+
* lookup: (name: string) => Promise<{ tagged?: string, integrity?: string, served?: boolean }> | { tagged?: string, integrity?: string, served?: boolean },
|
|
1285
|
+
* timeoutMs?: number,
|
|
1286
|
+
* pollMs?: number,
|
|
1287
|
+
* wait?: (ms: number) => Promise<unknown>,
|
|
1288
|
+
* now?: () => number,
|
|
1289
|
+
* log?: (line: string) => void,
|
|
1290
|
+
* }} options
|
|
1291
|
+
* @returns {Promise<string>} `version`, once every package serves it
|
|
1292
|
+
*/
|
|
1293
|
+
export async function waitForPublished(
|
|
1294
|
+
names,
|
|
1295
|
+
tag,
|
|
1296
|
+
version,
|
|
1297
|
+
{
|
|
1298
|
+
lookup,
|
|
1299
|
+
timeoutMs = 10 * MINUTE,
|
|
1300
|
+
pollMs = 15_000,
|
|
1301
|
+
wait = sleep,
|
|
1302
|
+
now = Date.now,
|
|
1303
|
+
log = console.log,
|
|
1304
|
+
}
|
|
1305
|
+
) {
|
|
1306
|
+
const deadline = now() + timeoutMs
|
|
1307
|
+
for (;;) {
|
|
1308
|
+
const pending = []
|
|
1309
|
+
for (const name of names) {
|
|
1310
|
+
const { tagged, integrity, served } = await lookup(name)
|
|
1311
|
+
if (tagged !== version) {
|
|
1312
|
+
pending.push(`${name}@${tag} is ${tagged ?? "unset"}`)
|
|
1313
|
+
} else if (!integrity) {
|
|
1314
|
+
pending.push(`${name}@${version} has no dist.integrity yet`)
|
|
1315
|
+
} else if (!served) {
|
|
1316
|
+
pending.push(`${name}@${version}'s tarball is not served yet`)
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
if (pending.length === 0) return version
|
|
1320
|
+
if (now() >= deadline) {
|
|
1321
|
+
throw new Error(
|
|
1322
|
+
`Not published as ${version} after ${seconds(timeoutMs)}: ${pending.join("; ")}`
|
|
1323
|
+
)
|
|
1324
|
+
}
|
|
1325
|
+
log(`Waiting for ${version}: ${pending.join("; ")}`)
|
|
1326
|
+
await wait(pollMs)
|
|
1327
|
+
}
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/**
|
|
1331
|
+
* The HTTP status the registry answers a HEAD of `url` with now, or 0 when
|
|
1332
|
+
* the request fails.
|
|
1333
|
+
*/
|
|
1334
|
+
async function headStatus(url) {
|
|
1335
|
+
try {
|
|
1336
|
+
const response = await fetch(url, {
|
|
1337
|
+
method: "HEAD",
|
|
1338
|
+
signal: AbortSignal.timeout(30_000),
|
|
1339
|
+
})
|
|
1340
|
+
return response.status
|
|
1341
|
+
} catch {
|
|
1342
|
+
return 0
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
/**
|
|
1347
|
+
* What the registry serves for `name` now, for `waitForPublished`. The
|
|
1348
|
+
* tarball is asked for only once the dist-tag and the integrity name
|
|
1349
|
+
* `version`, at the URL the registry gives for it (`dist.tarball`), which is
|
|
1350
|
+
* the one npm's install fetches.
|
|
1351
|
+
*/
|
|
1352
|
+
async function registryState(
|
|
1353
|
+
name,
|
|
1354
|
+
tag,
|
|
1355
|
+
version,
|
|
1356
|
+
view = npmView,
|
|
1357
|
+
status = headStatus
|
|
1358
|
+
) {
|
|
1359
|
+
const tagged = view(`${name}@${tag}`, "version")
|
|
1360
|
+
const integrity =
|
|
1361
|
+
tagged === version
|
|
1362
|
+
? view(`${name}@${version}`, "dist.integrity")
|
|
1363
|
+
: undefined
|
|
1364
|
+
const tarball =
|
|
1365
|
+
typeof integrity === "string"
|
|
1366
|
+
? view(`${name}@${version}`, "dist.tarball")
|
|
1367
|
+
: undefined
|
|
1368
|
+
return {
|
|
1369
|
+
tagged: typeof tagged === "string" ? tagged : undefined,
|
|
1370
|
+
integrity: typeof integrity === "string" ? integrity : undefined,
|
|
1371
|
+
served: typeof tarball === "string" && (await status(tarball)) === 200,
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* A run's dist-tag and the version under test, or that the run defers.
|
|
1377
|
+
*
|
|
1378
|
+
* With `--wait-for` (the release's run, which never defers): the tag that
|
|
1379
|
+
* release publishes under (`distTagFor(waitFor)`), and the version is
|
|
1380
|
+
* `waitFor` once every one of `packages` serves it under that tag
|
|
1381
|
+
* (`waitForPublished`).
|
|
1382
|
+
*
|
|
1383
|
+
* Without it (push, pull request, weekly and manual runs), the branch's
|
|
1384
|
+
* version (`branchVersion`, from packages/core/package.json) decides:
|
|
1385
|
+
*
|
|
1386
|
+
* - When the examples wait on it (`awaitsRelease`: it is not among core's
|
|
1387
|
+
* published versions, every @ic-reactor/* range of every example is exactly
|
|
1388
|
+
* `^<it>`, and no dependency uses a local protocol), a release pull request
|
|
1389
|
+
* or its merge before the tag publishes it, the run defers:
|
|
1390
|
+
* `{ deferred: true, version: branchVersion }`. The caller still runs the
|
|
1391
|
+
* checks that need no registry (`deferredRun`, over `staticFindings`), and a
|
|
1392
|
+
* finding there fails the run. Core's versions are asked for only when every example pins
|
|
1393
|
+
* `^<branchVersion>`.
|
|
1394
|
+
* - Otherwise the tag is that of the branch's version, which is the version
|
|
1395
|
+
* of its last release: release.js syncs the examples to exactly that
|
|
1396
|
+
* version (sync-example-versions.js), so `latest` after a stable release
|
|
1397
|
+
* such as 4.0.0, and `beta` through a later prerelease cycle such as
|
|
1398
|
+
* 4.1.0's, whose ranges no `latest` satisfies; the version is the one that
|
|
1399
|
+
* tag names. With no branch version, `latest`.
|
|
1400
|
+
*
|
|
1401
|
+
* @param {{
|
|
1402
|
+
* waitFor?: string,
|
|
1403
|
+
* branchVersion?: string,
|
|
1404
|
+
* manifests?: Record<string, any>[],
|
|
1405
|
+
* packages?: string[],
|
|
1406
|
+
* }} run `manifests` are the examples' package.json files; `packages`, the
|
|
1407
|
+
* ones a release run waits on, default to `scopePackageNames(manifests)`
|
|
1408
|
+
* @param {{ view?: (spec: string, field: string) => unknown, status?: (url: string) => Promise<number> } & Partial<Parameters<typeof waitForPublished>[3]>} [options]
|
|
1409
|
+
* `view(spec, field)` answers as `npm view <spec> <field> --json` does
|
|
1410
|
+
* (undefined for a 404); `status(url)` with the HTTP status of a HEAD of a
|
|
1411
|
+
* tarball's URL; the rest goes to `waitForPublished`
|
|
1412
|
+
* @returns {Promise<{ tag: "latest" | "beta", version: string, deferred?: undefined } | { deferred: true, version: string }>}
|
|
1413
|
+
*/
|
|
1414
|
+
export async function resolveRun(
|
|
1415
|
+
{
|
|
1416
|
+
waitFor,
|
|
1417
|
+
branchVersion,
|
|
1418
|
+
manifests = [],
|
|
1419
|
+
packages = scopePackageNames(manifests),
|
|
1420
|
+
},
|
|
1421
|
+
{ view = npmView, status = headStatus, ...waitOptions } = {}
|
|
1422
|
+
) {
|
|
1423
|
+
if (
|
|
1424
|
+
!waitFor &&
|
|
1425
|
+
// Every example pins ^<branchVersion>: only then is npm asked whether
|
|
1426
|
+
// that version is out.
|
|
1427
|
+
awaitsRelease(manifests, { branchVersion, publishedVersions: [] }) &&
|
|
1428
|
+
awaitsRelease(manifests, {
|
|
1429
|
+
branchVersion,
|
|
1430
|
+
publishedVersions: [view(`${SCOPE}core`, "versions") ?? []].flat(),
|
|
1431
|
+
})
|
|
1432
|
+
) {
|
|
1433
|
+
return { deferred: true, version: branchVersion }
|
|
1434
|
+
}
|
|
1435
|
+
const tag = distTagFor(waitFor ?? branchVersion)
|
|
1436
|
+
const version = waitFor
|
|
1437
|
+
? await waitForPublished(packages, tag, waitFor, {
|
|
1438
|
+
...waitOptions,
|
|
1439
|
+
lookup: (name) => registryState(name, tag, waitFor, view, status),
|
|
1440
|
+
})
|
|
1441
|
+
: distTagVersion(`${SCOPE}core`, tag, view)
|
|
1442
|
+
return { tag, version }
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
/**
|
|
1446
|
+
* A deferred run (`resolveRun`'s `{ deferred: true }`): it still runs the
|
|
1447
|
+
* checks that need no registry (`staticFindings`) on each example of the
|
|
1448
|
+
* checkout, so deferring never skips them. A finding fails the run (exit 1,
|
|
1449
|
+
* reported); with none it passes (exit 0) and says what it deferred to.
|
|
1450
|
+
*
|
|
1451
|
+
* @param {string[]} names the examples
|
|
1452
|
+
* @param {{
|
|
1453
|
+
* version: string,
|
|
1454
|
+
* manifests: Record<string, any>[],
|
|
1455
|
+
* files?: (name: string) => string[],
|
|
1456
|
+
* startCommand?: (name: string) => { command?: string, finding?: string },
|
|
1457
|
+
* onFindings?: (results: { name: string, steps: {}, findings: string[] }[]) => void,
|
|
1458
|
+
* log?: (message: string) => void,
|
|
1459
|
+
* }} run `manifests` are the examples' package.json files, in `names`'
|
|
1460
|
+
* order; `files`, `startCommand`, `onFindings` and `log` default to the
|
|
1461
|
+
* checkout's tracked files, its `.stackblitzrc`, the run's report and
|
|
1462
|
+
* `console.log`
|
|
1463
|
+
* @returns {0 | 1} the run's exit code
|
|
1464
|
+
*/
|
|
1465
|
+
export function deferredRun(
|
|
1466
|
+
names,
|
|
1467
|
+
{
|
|
1468
|
+
version,
|
|
1469
|
+
manifests,
|
|
1470
|
+
files = trackedFiles,
|
|
1471
|
+
startCommand = (name) =>
|
|
1472
|
+
stackblitzStartCommand(join(repoRoot, "examples", name)),
|
|
1473
|
+
onFindings = (results) => report(results, 0),
|
|
1474
|
+
log = console.log,
|
|
1475
|
+
}
|
|
1476
|
+
) {
|
|
1477
|
+
const results = names.map((name, i) => ({
|
|
1478
|
+
name,
|
|
1479
|
+
steps: {},
|
|
1480
|
+
findings: staticFindings(name, {
|
|
1481
|
+
files: files(name),
|
|
1482
|
+
scripts: manifests[i].scripts,
|
|
1483
|
+
start: startCommand(name),
|
|
1484
|
+
}),
|
|
1485
|
+
}))
|
|
1486
|
+
if (results.some((result) => result.findings.length > 0)) {
|
|
1487
|
+
onFindings(results)
|
|
1488
|
+
return 1
|
|
1489
|
+
}
|
|
1490
|
+
log(
|
|
1491
|
+
`The examples pin ^${version}, this branch's own version, which is not on ${REGISTRY} yet: ` +
|
|
1492
|
+
`a release that has not been published. The checks that need no registry pass. release.yml's examples-published job tests them ` +
|
|
1493
|
+
`against it once it is (--wait-for v${version}). Nothing to test here.`
|
|
1494
|
+
)
|
|
1495
|
+
return 0
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
async function main(argv) {
|
|
1499
|
+
const args = parseArgs(argv)
|
|
1500
|
+
if (args.error) {
|
|
1501
|
+
console.error(args.error)
|
|
1502
|
+
return 1
|
|
1503
|
+
}
|
|
1504
|
+
const all = discoverExampleDirs()
|
|
1505
|
+
const unknown = args.names.filter((name) => !all.includes(name))
|
|
1506
|
+
if (unknown.length > 0) {
|
|
1507
|
+
console.error(
|
|
1508
|
+
`Not an example: ${unknown.join(", ")} (examples: ${all.join(", ")})`
|
|
1509
|
+
)
|
|
1510
|
+
return 1
|
|
1511
|
+
}
|
|
1512
|
+
const names = args.names.length > 0 ? args.names : all
|
|
1513
|
+
if (names.length === 0) {
|
|
1514
|
+
console.error("No examples found under examples/.")
|
|
1515
|
+
return 1
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
const readManifest = (...path) =>
|
|
1519
|
+
JSON.parse(readFileSync(join(repoRoot, ...path), "utf8"))
|
|
1520
|
+
const manifests = names.map((name) =>
|
|
1521
|
+
readManifest("examples", name, "package.json")
|
|
1522
|
+
)
|
|
1523
|
+
let run
|
|
1524
|
+
try {
|
|
1525
|
+
run = await resolveRun({
|
|
1526
|
+
waitFor: args.waitFor,
|
|
1527
|
+
branchVersion: readManifest("packages", "core", "package.json").version,
|
|
1528
|
+
manifests,
|
|
1529
|
+
})
|
|
1530
|
+
} catch (error) {
|
|
1531
|
+
console.error(error.message)
|
|
1532
|
+
return 1
|
|
1533
|
+
}
|
|
1534
|
+
if (run.deferred) {
|
|
1535
|
+
return deferredRun(names, { version: run.version, manifests })
|
|
1536
|
+
}
|
|
1537
|
+
const { tag, version } = run
|
|
1538
|
+
console.log(
|
|
1539
|
+
`${SCOPE}core@${tag} is ${version} on ${REGISTRY}; testing ${names.join(", ")}`
|
|
1540
|
+
)
|
|
1541
|
+
|
|
1542
|
+
const base = realpathSync(args.tmp ? resolve(args.tmp) : tmpdir())
|
|
1543
|
+
const workDir = realpathSync(
|
|
1544
|
+
mkdtempSync(join(base, "ic-reactor-published-examples-"))
|
|
1545
|
+
)
|
|
1546
|
+
const rel = relative(repoRoot, workDir)
|
|
1547
|
+
const refusals = []
|
|
1548
|
+
if (!rel.startsWith("..") && !rel.startsWith(sep)) {
|
|
1549
|
+
refusals.push(`The scratch directory ${workDir} is inside the repository.`)
|
|
1550
|
+
}
|
|
1551
|
+
refusals.push(...ancestorFindings(workDir))
|
|
1552
|
+
if (refusals.length > 0) {
|
|
1553
|
+
rmSync(workDir, { recursive: true, force: true })
|
|
1554
|
+
for (const refusal of refusals) console.error(refusal)
|
|
1555
|
+
return 1
|
|
1556
|
+
}
|
|
1557
|
+
const keep = args.keep
|
|
1558
|
+
const preload = join(workDir, "webcontainer-model.cjs")
|
|
1559
|
+
writeFileSync(preload, WEBCONTAINER_MODEL)
|
|
1560
|
+
|
|
1561
|
+
const started = Date.now()
|
|
1562
|
+
const results = []
|
|
1563
|
+
try {
|
|
1564
|
+
for (const name of names) {
|
|
1565
|
+
results.push(await testExample(name, { tag, version, workDir, preload }))
|
|
1566
|
+
}
|
|
1567
|
+
} finally {
|
|
1568
|
+
if (keep) console.log(`\nCopies kept in ${workDir}`)
|
|
1569
|
+
else rmSync(workDir, { recursive: true, force: true })
|
|
1570
|
+
}
|
|
1571
|
+
const failed = report(results, Date.now() - started)
|
|
1572
|
+
if (!failed) {
|
|
1573
|
+
console.log(
|
|
1574
|
+
`✅ ${results.length} example(s) green against ${SCOPE}*@${version} (${tag}) from npm`
|
|
1575
|
+
)
|
|
1576
|
+
}
|
|
1577
|
+
return failed ? 1 : 0
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
if (
|
|
1581
|
+
process.argv[1] &&
|
|
1582
|
+
resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
1583
|
+
) {
|
|
1584
|
+
process.exitCode = await main(process.argv.slice(2))
|
|
1585
|
+
}
|