@starci/hfs 4.0.10 → 4.2.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/CHANGELOG.md +5 -1
- package/emit/operations.mjs +1 -1
- package/emit/type-schema.mjs +2 -1
- package/lint/run.mjs +65 -24
- package/package.json +1 -1
- package/report/order.mjs +2 -0
- package/runtime/config.example.yaml +194 -0
- package/runtime/engine/by-code-unit.mjs +12 -0
- package/runtime/engine/config.mjs +222 -239
- package/runtime/engine/invalid-config.mjs +5 -5
- package/runtime/engine/model-config.mjs +90 -0
- package/runtime/engine/orca-config.mjs +3 -1
- package/runtime/engine/release-config.mjs +28 -0
- package/runtime/engine/removed-vocabulary.mjs +28 -0
- package/runtime/engine/resources-config.mjs +52 -0
- package/runtime/engine/runtime-root.mjs +17 -0
- package/runtime/engine/secrets.mjs +35 -31
- package/runtime/engine/sonar-config.mjs +21 -0
- package/runtime/engine/temp-root.mjs +32 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +14 -14
- package/runtime/knowledge/hfs/peer-integrations.yaml +2 -3
- package/runtime/knowledge/hfs/rules.yaml +161 -65
- package/runtime/knowledge/hfs/slots.yaml +11 -14
- package/runtime/knowledge/patterns/be/api.yaml +7 -24
- package/runtime/knowledge/patterns/be/cli.yaml +3 -12
- package/runtime/knowledge/patterns/be/realtime.yaml +1 -14
- package/runtime/knowledge/patterns/be/webhooks.yaml +0 -19
- package/runtime/modules/kernel/failure-codes.yaml +1 -41
- package/runtime/modules/kernel/removed-vocabulary.yaml +268 -0
- package/runtime/modules/models/registry.yaml +25 -359
- package/runtime/modules/models/runtimes.yaml +126 -265
- package/runtime/modules/models/tiers.yaml +66 -0
- package/runtime/scripts/api/fs/claim-file.mjs +30 -0
- package/runtime/scripts/api/fs/ensure-temp-root.mjs +27 -0
- package/runtime/scripts/api/fs/forbidden-root.mjs +2 -1
- package/runtime/scripts/api/fs/make-temp-dir.mjs +11 -0
- package/runtime/scripts/api/fs/safe-remove.mjs +52 -35
- package/runtime/scripts/api/git/lib.mjs +34 -1
- package/runtime/scripts/api/process/resolve-real-tool.mjs +14 -8
- package/runtime/scripts/api/process/run-program.mjs +2 -1
- package/runtime/scripts/api/sops/lib.mjs +198 -120
- package/runtime/scripts/hfs/allows.mjs +3 -3
- package/runtime/scripts/hfs/architecture/ast-walks.mjs +63 -50
- package/runtime/scripts/hfs/architecture/automatic-gates.mjs +96 -0
- package/runtime/scripts/hfs/architecture/backend.mjs +261 -175
- package/runtime/scripts/hfs/architecture/background-unowned.mjs +69 -34
- package/runtime/scripts/hfs/architecture/client-reaches-server.mjs +55 -54
- package/runtime/scripts/hfs/architecture/clones.mjs +179 -106
- package/runtime/scripts/hfs/architecture/config-unread.mjs +5 -22
- package/runtime/scripts/hfs/architecture/config.mjs +95 -80
- package/runtime/scripts/hfs/architecture/connection-map.mjs +233 -141
- package/runtime/scripts/hfs/architecture/constructor-deps.mjs +15 -9
- package/runtime/scripts/hfs/architecture/context-coupling.mjs +76 -52
- package/runtime/scripts/hfs/architecture/context-map.mjs +215 -141
- package/runtime/scripts/hfs/architecture/context-owner.mjs +39 -30
- package/runtime/scripts/hfs/architecture/context-platform-tables.mjs +31 -17
- package/runtime/scripts/hfs/architecture/context-transaction.mjs +38 -25
- package/runtime/scripts/hfs/architecture/contract-fixture-guard.mjs +86 -40
- package/runtime/scripts/hfs/architecture/contracts-readonly.mjs +342 -0
- package/runtime/scripts/hfs/architecture/contracts.mjs +263 -455
- package/runtime/scripts/hfs/architecture/cross-app-duplicate.mjs +43 -34
- package/runtime/scripts/hfs/architecture/dead-exports.mjs +201 -154
- package/runtime/scripts/hfs/architecture/default-deny.mjs +118 -93
- package/runtime/scripts/hfs/architecture/doc-language.mjs +2 -1
- package/runtime/scripts/hfs/architecture/entrypoint.mjs +45 -30
- package/runtime/scripts/hfs/architecture/error-codes.mjs +19 -12
- package/runtime/scripts/hfs/architecture/error-masked.mjs +39 -26
- package/runtime/scripts/hfs/architecture/fe-slot-allows.mjs +2 -1
- package/runtime/scripts/hfs/architecture/feature-shape.mjs +26 -20
- package/runtime/scripts/hfs/architecture/framework-pinned.mjs +1 -1
- package/runtime/scripts/hfs/architecture/frontend-grammar.mjs +190 -0
- package/runtime/scripts/hfs/architecture/frontend-routing.mjs +186 -0
- package/runtime/scripts/hfs/architecture/frontend-world-render.mjs +311 -0
- package/runtime/scripts/hfs/architecture/frontend-world.mjs +328 -0
- package/runtime/scripts/hfs/architecture/frontend.mjs +109 -829
- package/runtime/scripts/hfs/architecture/hfs-graph.mjs +14 -2
- package/runtime/scripts/hfs/architecture/hfs.mjs +120 -288
- package/runtime/scripts/hfs/architecture/hooks-are-hooks.mjs +46 -26
- package/runtime/scripts/hfs/architecture/i18n-keys.mjs +114 -76
- package/runtime/scripts/hfs/architecture/index.mjs +164 -104
- package/runtime/scripts/hfs/architecture/injection-token-exported.mjs +33 -28
- package/runtime/scripts/hfs/architecture/machine-ast.mjs +180 -144
- package/runtime/scripts/hfs/architecture/managed-scripts.mjs +8 -2
- package/runtime/scripts/hfs/architecture/module-per-transport.mjs +150 -101
- package/runtime/scripts/hfs/architecture/next-data-contract.mjs +51 -43
- package/runtime/scripts/hfs/architecture/next-data-key.mjs +338 -0
- package/runtime/scripts/hfs/architecture/next-data.mjs +179 -427
- package/runtime/scripts/hfs/architecture/owners.mjs +27 -13
- package/runtime/scripts/hfs/architecture/package-shape.mjs +51 -23
- package/runtime/scripts/hfs/architecture/presentation.mjs +165 -0
- package/runtime/scripts/hfs/architecture/reachability.mjs +127 -61
- package/runtime/scripts/hfs/architecture/register-once.mjs +112 -63
- package/runtime/scripts/hfs/architecture/registration.mjs +187 -110
- package/runtime/scripts/hfs/architecture/required-files.mjs +121 -70
- package/runtime/scripts/hfs/architecture/route-files-thin.mjs +83 -58
- package/runtime/scripts/hfs/architecture/schema-owner.mjs +182 -108
- package/runtime/scripts/hfs/architecture/source-names-shape.mjs +318 -0
- package/runtime/scripts/hfs/architecture/source-names.mjs +83 -248
- package/runtime/scripts/hfs/architecture/sql-owner.mjs +175 -118
- package/runtime/scripts/hfs/architecture/sql-returning.mjs +39 -28
- package/runtime/scripts/hfs/architecture/sql-tokens.mjs +302 -172
- package/runtime/scripts/hfs/architecture/supabase-ast.mjs +1 -1
- package/runtime/scripts/hfs/architecture/supabase-be.mjs +55 -45
- package/runtime/scripts/hfs/architecture/supabase-results.mjs +182 -0
- package/runtime/scripts/hfs/architecture/supabase-tables.mjs +20 -12
- package/runtime/scripts/hfs/architecture/supabase.mjs +191 -263
- package/runtime/scripts/hfs/architecture/symbols.mjs +175 -99
- package/runtime/scripts/hfs/architecture/test-world-files.mjs +82 -41
- package/runtime/scripts/hfs/architecture/tiers.mjs +120 -69
- package/runtime/scripts/hfs/architecture/transport-owner.mjs +84 -57
- package/runtime/scripts/hfs/architecture/type-context.mjs +282 -0
- package/runtime/scripts/hfs/architecture/typescript.mjs +116 -276
- package/runtime/scripts/hfs/architecture/unit-spec-providers.mjs +84 -62
- package/runtime/scripts/hfs/architecture.mjs +3 -4
- package/runtime/scripts/hfs/catalog-once.mjs +15 -0
- package/runtime/scripts/hfs/check.mjs +63 -71
- package/runtime/scripts/hfs/coverage-scope.mjs +84 -38
- package/runtime/scripts/hfs/declaration-shape.mjs +77 -36
- package/runtime/scripts/hfs/declaration-slots.mjs +1 -1
- package/runtime/scripts/hfs/edition-slots.mjs +5 -3
- package/runtime/scripts/hfs/linear-text.mjs +30 -0
- package/runtime/scripts/hfs/manifest-shape.mjs +164 -63
- package/runtime/scripts/hfs/path-findings.mjs +58 -38
- package/runtime/scripts/hfs/pin-findings.mjs +31 -0
- package/runtime/scripts/hfs/repo-identity.mjs +21 -4
- package/runtime/scripts/hfs/rule-catalog.mjs +194 -0
- package/runtime/scripts/hfs/rule-params-shape.mjs +33 -27
- package/runtime/scripts/hfs/rules/cli.mjs +5 -1
- package/runtime/scripts/hfs/rules/contract-compat.mjs +40 -22
- package/runtime/scripts/hfs/rules/contract.mjs +35 -27
- package/runtime/scripts/hfs/rules/database-config.mjs +57 -32
- package/runtime/scripts/hfs/rules/database-migrations.mjs +52 -31
- package/runtime/scripts/hfs/rules/database-plpgsql.mjs +106 -91
- package/runtime/scripts/hfs/rules/database-sql.mjs +254 -173
- package/runtime/scripts/hfs/rules/database.mjs +36 -23
- package/runtime/scripts/hfs/rules/deps.mjs +64 -32
- package/runtime/scripts/hfs/rules/docker.mjs +113 -65
- package/runtime/scripts/hfs/rules/edition.mjs +118 -111
- package/runtime/scripts/hfs/rules/event-bus.mjs +91 -45
- package/runtime/scripts/hfs/rules/fe-contract-documents.mjs +17 -16
- package/runtime/scripts/hfs/rules/fe-no-tests.mjs +36 -16
- package/runtime/scripts/hfs/rules/frontend-tree.mjs +4 -3
- package/runtime/scripts/hfs/rules/integration-specs.mjs +75 -41
- package/runtime/scripts/hfs/rules/kinds.mjs +59 -25
- package/runtime/scripts/hfs/rules/lint-suppression.mjs +9 -3
- package/runtime/scripts/hfs/rules/monorepo.mjs +82 -45
- package/runtime/scripts/hfs/rules/peer-integrations.mjs +24 -3
- package/runtime/scripts/hfs/rules/pipeline.mjs +58 -9
- package/runtime/scripts/hfs/rules/proof-commands.mjs +14 -8
- package/runtime/scripts/hfs/rules/repo-local-checks.mjs +11 -6
- package/runtime/scripts/hfs/rules/saga.mjs +128 -61
- package/runtime/scripts/hfs/rules/secrets.mjs +4 -3
- package/runtime/scripts/hfs/rules/services.mjs +34 -16
- package/runtime/scripts/hfs/rules/stacks.mjs +21 -14
- package/runtime/scripts/hfs/rules/supabase-secrets.mjs +32 -23
- package/runtime/scripts/hfs/rules/test-topology.mjs +25 -11
- package/runtime/scripts/hfs/secret.mjs +59 -48
- package/runtime/scripts/hfs/slot-app-view.mjs +102 -0
- package/runtime/scripts/hfs/slot-classify.mjs +65 -0
- package/runtime/scripts/hfs/slot-errors.mjs +11 -0
- package/runtime/scripts/hfs/slot-imports.mjs +76 -0
- package/runtime/scripts/hfs/slot-manifest-shape.mjs +73 -0
- package/runtime/scripts/hfs/slot-match.mjs +153 -0
- package/runtime/scripts/hfs/slot-path.mjs +8 -0
- package/runtime/scripts/hfs/slot-required.mjs +76 -0
- package/runtime/scripts/hfs/slot-semantic-problems.mjs +148 -0
- package/runtime/scripts/hfs/slot-side-problems.mjs +28 -0
- package/runtime/scripts/hfs/slots.mjs +74 -695
- package/runtime/scripts/hfs/sql/pg-parse.mjs +5 -2
- package/runtime/scripts/hfs/trailing-slashes.mjs +8 -0
- package/runtime/scripts/hfs/tree.mjs +19 -14
- package/runtime/scripts/hfs/typescript-programs.mjs +6 -4
- package/runtime/scripts/hfs/view.mjs +1 -1
- package/runtime/scripts/lib/dockerfile.mjs +56 -33
- package/runtime/scripts/lib/env.mjs +10 -0
- package/runtime/scripts/lib/event-contract.mjs +17 -13
- package/runtime/scripts/lib/git.mjs +2 -2
- package/runtime/scripts/lib/graphql-contract.mjs +158 -322
- package/runtime/scripts/lib/graphql-sdl.mjs +262 -0
- package/runtime/scripts/lib/i18n.mjs +3 -2
- package/runtime/scripts/lib/in-order.mjs +71 -0
- package/runtime/scripts/lib/language.mjs +3 -3
- package/runtime/scripts/lib/list.mjs +8 -1
- package/runtime/scripts/lib/path-key.mjs +38 -5
- package/runtime/scripts/lib/pid-alive.mjs +7 -0
- package/runtime/scripts/lib/regex.mjs +1 -1
- package/runtime/scripts/lib/same-text.mjs +1 -1
- package/runtime/scripts/lib/secret-patterns.mjs +6 -2
- package/runtime/scripts/lib/sleep-sync.mjs +2 -2
- package/runtime/scripts/lib/sops-envelope.mjs +95 -48
- package/runtime/scripts/lib/stack-services.mjs +1 -2
- package/runtime/scripts/lib/ts-ast.mjs +9 -0
- package/runtime/scripts/lib/walk.mjs +6 -1
- package/runtime/scripts/lib/yaml-cached.mjs +14 -0
- package/scaffold/add-cli-lite.mjs +2 -1
- package/scaffold/add-table.mjs +2 -2
- package/scaffold/add.mjs +3 -2
- package/scaffold/app.mjs +4 -2
- package/scaffold/edition-gate.mjs +25 -25
- package/scaffold/lite-exports.mjs +2 -1
- package/scaffold/service.mjs +5 -4
- package/sync/hygiene.mjs +15 -9
- package/sync/index.mjs +7 -3
- package/templates/app/ci-workflows/github/workflows/ci.yml +4 -4
- package/templates/app/ci-workflows/github/workflows/e2e.yml +3 -3
- package/templates/app/ci-workflows/github/workflows/images.yml +3 -3
- package/templates/app/ci-workflows/sonar-steps.yml +2 -2
- package/templates/app/ci-workflows-lite/github/workflows/ci.yml +6 -6
- package/templates/app/ci-workflows-lite/github/workflows/db-deploy.yml +3 -3
- package/templates/app/ci-workflows-lite/github/workflows/images.yml +3 -3
- package/templates/app/starciwork.gitignore +1 -1
- package/templates/be/image/api/Dockerfile +1 -1
- package/templates/be/image/cli/Dockerfile +1 -1
- package/templates/be/image/worker/Dockerfile +1 -1
- package/templates/be/patterns/cli/group.cli.spec.ts.tpl +1 -1
- package/templates/be/patterns/cli/group.cli.ts.tpl +2 -2
- package/templates/be/patterns/event-bus/platform/event-runner.service.spec.ts.tpl +13 -0
- package/templates/be/patterns/event-bus/platform/event-runner.service.ts.tpl +7 -2
- package/templates/be/patterns/event-bus/platform/event.policy.ts.tpl +4 -1
- package/templates/be/patterns/event-bus/platform/kafka-event-transport.client.ts.tpl +2 -1
- package/templates/be/patterns/outbox/platform/outbox-relay.policy.ts.tpl +24 -9
- package/templates/be/patterns/queues/platform/queue-relay.service.ts.tpl +3 -2
- package/templates/be/patterns/queues/platform/queue-worker.service.ts.tpl +8 -5
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/migrate/migrate.cli.ts +2 -2
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.spec.ts +1 -1
- package/templates/be/skeleton/src/features/cli/seed/seed.cli.ts +2 -2
- package/templates/be/skeleton/src/modules/platform/database/migrate-connections.client.ts +3 -2
- package/templates/be/skeleton/src/modules/platform/database/seed-connections.client.ts +5 -4
- package/templates/be/skeleton/src/modules/platform/primitives/index.ts +1 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.spec.ts +47 -0
- package/templates/be/skeleton/src/modules/platform/primitives/sequence.policy.ts +16 -0
- package/templates/be/skeleton-lite/apps/api/Dockerfile +1 -0
- package/templates/be/skeleton-lite/src/modules/platform/primitives/index.ts +2 -0
- package/templates/fe/image/next/Dockerfile +2 -1
- package/templates/fe/skeleton/apps/app/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/apps/landing/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton/packages/__project__-i18n/src/index.ts +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/layout.tsx +1 -2
- package/templates/fe/skeleton-lite/apps/web/src/app/[locale]/page.tsx +2 -2
- package/templates/fe/skeleton-lite/apps/web/src/components/blocks/SignInForm/index.tsx +7 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/auth/write-sign-out.ts +1 -1
- package/templates/fe/skeleton-lite/apps/web/src/modules/db/validation/validation.mapper.ts +10 -2
- package/templates/fe/skeleton-lite/apps/web/src/modules/i18n/request.ts +10 -3
- package/upgrade/index.mjs +9 -2
- package/runtime/engine/admission.mjs +0 -308
- package/runtime/engine/canonical-json.mjs +0 -10
- package/runtime/engine/db/blob.mjs +0 -315
- package/runtime/engine/db/ledger-paths.mjs +0 -83
- package/runtime/engine/db/ledger.mjs +0 -1118
- package/runtime/engine/db/machine-connection.mjs +0 -135
- package/runtime/engine/db/machine-schema.mjs +0 -84
- package/runtime/engine/db/machine.mjs +0 -1367
- package/runtime/engine/db/migrations/machine/0001-init.sql +0 -924
- package/runtime/engine/db/migrations/runtime/0001-init.sql +0 -1108
- package/runtime/engine/db/provider-reservations.mjs +0 -101
- package/runtime/engine/digest.mjs +0 -16
- package/runtime/engine/refuse.mjs +0 -11
- package/runtime/modules/ops/_labels.yaml +0 -53
- package/runtime/scripts/api/node/lib.mjs +0 -14
- package/runtime/scripts/api/node/spawn-node.mjs +0 -6
- package/runtime/scripts/api/process/lib.mjs +0 -111
- package/runtime/scripts/api/process/owned-process.mjs +0 -78
- package/runtime/scripts/api/process/stop-owned-process.mjs +0 -9
- package/runtime/scripts/connectors/lib.mjs +0 -488
- package/runtime/scripts/lib/clip.mjs +0 -19
- package/runtime/scripts/lib/display-names.mjs +0 -259
- package/runtime/scripts/lib/example-refs.mjs +0 -158
- package/runtime/scripts/lib/json-schema.mjs +0 -52
- package/runtime/scripts/lib/process-identity.mjs +0 -6
- package/runtime/scripts/lib/read-yaml.mjs +0 -18
- package/runtime/scripts/lib/redact.mjs +0 -161
- package/runtime/scripts/lib/sleep.mjs +0 -7
- package/runtime/scripts/lib/source-phrases.mjs +0 -34
- package/runtime/scripts/lib/sqlite.mjs +0 -21
|
@@ -22,33 +22,50 @@ export function custodyInputType(file, override) {
|
|
|
22
22
|
return type;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
const sopsNames = (env, win, pathext) => {
|
|
26
|
+
if (!win) return ['sops'];
|
|
27
|
+
if (!pathext) return ['sops.exe', 'sops'];
|
|
28
|
+
return String(env.PATHEXT || '.EXE;.CMD;.BAT').split(';').filter(Boolean).map((ext) => `sops${ext}`);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const nestedPackageDirs = (filesystem, paths, directory) => {
|
|
32
|
+
const dirs = [];
|
|
33
|
+
try {
|
|
34
|
+
for (const nested of filesystem.readdirSync(directory, { withFileTypes: true })) {
|
|
35
|
+
if (nested.isDirectory()) dirs.push(paths.join(directory, nested.name));
|
|
36
|
+
}
|
|
37
|
+
} catch { /* unreadable package */ }
|
|
38
|
+
return dirs;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const wingetPackageDirs = (filesystem, paths, packages, wingetPackageTree) => {
|
|
42
|
+
const dirs = [];
|
|
43
|
+
try {
|
|
44
|
+
for (const entry of filesystem.readdirSync(packages)) {
|
|
45
|
+
if (!wingetPackageTree && !/sops/i.test(entry)) continue;
|
|
46
|
+
const directory = paths.join(packages, entry);
|
|
47
|
+
dirs.push(directory);
|
|
48
|
+
if (wingetPackageTree) dirs.push(...nestedPackageDirs(filesystem, paths, directory));
|
|
49
|
+
}
|
|
50
|
+
} catch { /* no WinGet packages */ }
|
|
51
|
+
return dirs;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const wingetDirs = (filesystem, paths, env, wingetPackageTree) => {
|
|
55
|
+
const dirs = [];
|
|
56
|
+
if (!env.LOCALAPPDATA) return dirs;
|
|
57
|
+
const winget = paths.join(env.LOCALAPPDATA, 'Microsoft', 'WinGet');
|
|
58
|
+
dirs.push(paths.join(winget, 'Links'), ...wingetPackageDirs(filesystem, paths, paths.join(winget, 'Packages'), wingetPackageTree));
|
|
59
|
+
return dirs;
|
|
60
|
+
};
|
|
61
|
+
|
|
25
62
|
/** Resolve SOPS from PATH and WinGet. Consumers select PATHEXT names and the full package tree for their launcher. */
|
|
26
63
|
export function resolveSops(env = process.env, { platform = process.platform, pathext = false, wingetPackageTree = false, filesystem = fs } = {}) {
|
|
27
64
|
const win = platform === 'win32';
|
|
28
65
|
const paths = win ? path.win32 : path.posix;
|
|
29
|
-
const names = win
|
|
30
|
-
? (pathext ? String(env.PATHEXT || '.EXE;.CMD;.BAT').split(';').filter(Boolean).map((ext) => `sops${ext}`) : ['sops.exe', 'sops'])
|
|
31
|
-
: ['sops'];
|
|
66
|
+
const names = sopsNames(env, win, pathext);
|
|
32
67
|
const dirs = String(env.PATH ?? '').split(win ? ';' : ':').filter(Boolean);
|
|
33
|
-
if (win
|
|
34
|
-
const winget = paths.join(env.LOCALAPPDATA, 'Microsoft', 'WinGet');
|
|
35
|
-
dirs.push(paths.join(winget, 'Links'));
|
|
36
|
-
const packages = paths.join(winget, 'Packages');
|
|
37
|
-
try {
|
|
38
|
-
for (const entry of filesystem.readdirSync(packages)) {
|
|
39
|
-
if (!wingetPackageTree && !/sops/i.test(entry)) continue;
|
|
40
|
-
const directory = paths.join(packages, entry);
|
|
41
|
-
dirs.push(directory);
|
|
42
|
-
if (wingetPackageTree) {
|
|
43
|
-
try {
|
|
44
|
-
for (const nested of filesystem.readdirSync(directory, { withFileTypes: true })) {
|
|
45
|
-
if (nested.isDirectory()) dirs.push(paths.join(directory, nested.name));
|
|
46
|
-
}
|
|
47
|
-
} catch { /* unreadable package */ }
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
} catch { /* no WinGet packages */ }
|
|
51
|
-
}
|
|
68
|
+
if (win) dirs.push(...wingetDirs(filesystem, paths, env, wingetPackageTree));
|
|
52
69
|
for (const directory of dirs) for (const name of names) {
|
|
53
70
|
const file = paths.join(directory, name);
|
|
54
71
|
try { if (filesystem.statSync(file).isFile()) return file; } catch { /* next candidate */ }
|
|
@@ -83,7 +100,7 @@ export function runSopsFile(bin, args, operation, options = {}) {
|
|
|
83
100
|
}
|
|
84
101
|
|
|
85
102
|
const SELECTED_SOPS_VERSIONS = new Set(['3.13.2', '3.13.3']);
|
|
86
|
-
const SELECTED_AGE_VERSIONS = new Set(['1.2.1', '1.3.1']);
|
|
103
|
+
export const SELECTED_AGE_VERSIONS = new Set(['1.2.1', '1.3.1']);
|
|
87
104
|
const IDENTITY_TIMEOUT_MS = 20_000;
|
|
88
105
|
const CONTROLLED_ENV = new Set(['SOPS_AGE_KEY', 'SOPS_AGE_KEY_FILE', 'SOPS_AGE_KEY_CMD', 'SOPS_AGE_SSH_PRIVATE_KEY_FILE', 'SOPS_AGE_SSH_PRIVATE_KEY_CMD', 'SOPS_AGE_RECIPIENT', 'SOPS_KEYSERVICE', 'SOPS_ENABLE_LOCAL_KEYSERVICE', 'SOPS_DECRYPTION_ORDER', 'SOPS_KMS_ARN', 'SOPS_GCP_KMS_IDS', 'SOPS_HUAWEICLOUD_KMS_IDS', 'SOPS_AZURE_KEYVAULT_URLS', 'SOPS_VAULT_URIS', 'SOPS_PGP_FP', 'SOPS_AGE_RECIPIENTS', 'HOME', 'USERPROFILE', 'APPDATA', 'XDG_CONFIG_HOME']);
|
|
89
106
|
|
|
@@ -119,7 +136,7 @@ const auditAbsent = cwd => {
|
|
|
119
136
|
try { fs.lstatSync(path.resolve(cwd ?? process.cwd(), '/etc/sops/audit.yaml')); return false; }
|
|
120
137
|
catch (error) { return error.code === 'ENOENT'; }
|
|
121
138
|
};
|
|
122
|
-
const captured = r => r
|
|
139
|
+
const captured = r => r?.status === 0 && r.signal == null && !r.error && Buffer.isBuffer(r.stdout) && Buffer.isBuffer(r.stderr);
|
|
123
140
|
const decoded = bytes => new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
124
141
|
const ownedRequest = request => {
|
|
125
142
|
if (request.operation === 'seal') {
|
|
@@ -141,115 +158,176 @@ const ownedRequest = request => {
|
|
|
141
158
|
return { inputType: flags['--input-type'], outputType: flags['--output-type'], file: args.at(-1), recipient: flags['--age'] };
|
|
142
159
|
};
|
|
143
160
|
|
|
161
|
+
/** The admitted identity key, or the unchanged refusal/failure result (admitted.key is absent then). */
|
|
162
|
+
const selectedAdmission = (selection, spec, invocation, env) => {
|
|
163
|
+
const fail = () => ({ status: null, stdout: '', stderr: '', error: selection.error });
|
|
164
|
+
if (!spec || !['binary', 'json', 'yaml', 'dotenv'].includes(spec.inputType) || !['binary', 'json', 'yaml', 'dotenv'].includes(spec.outputType)) return fail();
|
|
165
|
+
if (!invocation || typeof invocation.runProgram !== 'function' || typeof invocation.resolveRealTool !== 'function') return fail();
|
|
166
|
+
if (!['win32', 'linux', 'darwin'].includes(process.platform)) return held('unsupported-platform');
|
|
167
|
+
const key = env[selection.inlineName];
|
|
168
|
+
if (typeof key !== 'string' || !/^AGE-SECRET-KEY-1[0-9A-Z]+$/.test(key.trim())) return fail();
|
|
169
|
+
return { key };
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
const decryptArgs = (file, inputType, outputType) => ['decrypt', '--input-type', inputType, '--output-type', outputType, '--filename-override', file, '--enable-local-keyservice=true', '--decryption-order', 'age'];
|
|
173
|
+
|
|
174
|
+
const decryptSelected = (run, exe, file, input, spec, recipient) => {
|
|
175
|
+
const envelope = selectedAgeEnvelope(decoded(input), spec.inputType, recipient);
|
|
176
|
+
if (!envelope.ok) return held(envelope.reason);
|
|
177
|
+
const result = run(exe, decryptArgs(file, spec.inputType, spec.outputType), input);
|
|
178
|
+
if (!captured(result)) return held('selected-decryption-failed');
|
|
179
|
+
return { status: 0, stdout: decoded(result.stdout), stderr: '', error: null };
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const encryptSelected = (run, exe, file, input, spec, recipient) => {
|
|
183
|
+
if (spec.recipient !== recipient) return held('recipient-mismatch');
|
|
184
|
+
const args = ['encrypt', '--input-type', spec.inputType, '--output-type', spec.outputType, '--filename-override', file, '--enable-local-keyservice=true', '--age', recipient];
|
|
185
|
+
const result = run(exe, args, input);
|
|
186
|
+
if (!captured(result)) return held('selected-encryption-failed');
|
|
187
|
+
if (result.stdout.length > CREDENTIAL_FILE_MAX_BYTES) return held('document-over-budget');
|
|
188
|
+
const text = decoded(result.stdout);
|
|
189
|
+
const envelope = selectedAgeEnvelope(text, spec.outputType, recipient);
|
|
190
|
+
if (!envelope.ok) return held(envelope.reason);
|
|
191
|
+
const readback = run(exe, decryptArgs(file, spec.outputType, spec.inputType), result.stdout);
|
|
192
|
+
if (!captured(readback)) return held('selected-readback-failed');
|
|
193
|
+
return { status: 0, stdout: text, stderr: '', error: null };
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const wipeCaptures = (captures) => {
|
|
197
|
+
for (const result of captures) {
|
|
198
|
+
if (Buffer.isBuffer(result?.stdout)) result.stdout.fill(0);
|
|
199
|
+
if (Buffer.isBuffer(result?.stderr)) result.stderr.fill(0);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// The one recipient an `age -y` capture names, or null.
|
|
204
|
+
const recipientOf = (stdout) => {
|
|
205
|
+
const recipients = decoded(stdout).trim().split(/\r?\n/);
|
|
206
|
+
return recipients.length === 1 && /^age1[ac-hj-np-z02-9]+$/.test(recipients[0]) ? recipients[0] : null;
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const toolProfileSupported = (version, ageVersion) => captured(version) && captured(ageVersion)
|
|
210
|
+
&& SELECTED_SOPS_VERSIONS.has(/^sops\s+(\d+\.\d+\.\d+)/.exec(decoded(version.stdout))?.[1])
|
|
211
|
+
&& SELECTED_AGE_VERSIONS.has(/^v?(\d+\.\d+\.\d+)/.exec(decoded(ageVersion.stdout))?.[1]);
|
|
212
|
+
|
|
213
|
+
const invalidCaptureBudget = (deadline, maxBuffer) => !Number.isInteger(deadline) || deadline < 1 || deadline > 2_147_483_647 || !Number.isInteger(maxBuffer) || maxBuffer < 1;
|
|
214
|
+
|
|
215
|
+
// The document bytes into `state.input`; the refusal when they are unavailable or over budget, else null.
|
|
216
|
+
const readSelectedInput = (state, request, spec, file) => {
|
|
217
|
+
try { state.input = request.operation === 'seal' ? Buffer.from(spec.plaintext) : readSecretBytes(file); } catch { return held('actual-document-unavailable'); }
|
|
218
|
+
return state.input.length > CREDENTIAL_FILE_MAX_BYTES ? held('document-over-budget') : null;
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const selectedRunner = (invocation, options, captures, selectedEnv) => (program, args, bytes, childEnv = selectedEnv) => {
|
|
222
|
+
const result = invocation.runProgram(program, args, { ...options, env: childEnv, input: bytes });
|
|
223
|
+
captures.push(result); return result;
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const performSelected = (state, { bin, request, spec, key, selection, invocation, env, cwd, maxBuffer, timeout }) => {
|
|
227
|
+
const file = path.resolve(cwd ?? process.cwd(), spec.file);
|
|
228
|
+
const unavailable = readSelectedInput(state, request, spec, file);
|
|
229
|
+
if (unavailable) return unavailable;
|
|
230
|
+
const exe = bin ?? resolveSops(env), age = invocation.resolveRealTool('age-keygen', { env });
|
|
231
|
+
if (!nativeFile(exe) || !nativeFile(age)) return held('native-tool-unavailable');
|
|
232
|
+
if (!auditAbsent(cwd)) return held('unsupported-audit-context');
|
|
233
|
+
const deadline = timeout ?? IDENTITY_TIMEOUT_MS;
|
|
234
|
+
if (invalidCaptureBudget(deadline, maxBuffer)) return held('invalid-capture-budget');
|
|
235
|
+
state.selectedEnv = isolatedSopsEnv(env, selection.inlineName);
|
|
236
|
+
const publicEnv = isolatedSopsEnv(env, null);
|
|
237
|
+
const options = { cwd, env: state.selectedEnv, shell: false, encoding: 'buffer', windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'], timeout: deadline, maxBuffer };
|
|
238
|
+
const run = selectedRunner(invocation, options, state.captures, state.selectedEnv);
|
|
239
|
+
const version = run(exe, ['--disable-version-check', '--version'], undefined, publicEnv);
|
|
240
|
+
const ageVersion = run(age, ['--version'], undefined, publicEnv);
|
|
241
|
+
if (!toolProfileSupported(version, ageVersion)) return held('unsupported-tool-profile');
|
|
242
|
+
state.identity = Buffer.from(key.trim(), 'utf8');
|
|
243
|
+
const derived = run(age, ['-y'], state.identity, publicEnv);
|
|
244
|
+
if (!captured(derived)) return held('recipient-unproven');
|
|
245
|
+
const recipient = recipientOf(derived.stdout);
|
|
246
|
+
if (recipient === null) return held('recipient-unproven');
|
|
247
|
+
if (request.operation === 'decrypt') return decryptSelected(run, exe, file, state.input, spec, recipient);
|
|
248
|
+
return encryptSelected(run, exe, file, state.input, spec, recipient);
|
|
249
|
+
};
|
|
250
|
+
|
|
144
251
|
/** The domain composes the existing process owner: invocation={runProgram(file,args,options),resolveRealTool(program,{env})}. No API imports another system. */
|
|
145
252
|
export function runSelectedSops(bin, request, { selection, invocation, env, cwd, maxBuffer, timeout } = {}) {
|
|
146
253
|
if (selection?.error?.identityRefusal !== 'inline-context-unqualified' || typeof selection.inlineName !== 'string' || !env || typeof env !== 'object') return held('selected-context-missing');
|
|
147
254
|
const spec = ownedRequest(request);
|
|
148
|
-
|
|
149
|
-
if (!
|
|
150
|
-
|
|
151
|
-
const key = env[selection.inlineName];
|
|
152
|
-
if (typeof key !== 'string' || !/^AGE-SECRET-KEY-1[0-9A-Z]+$/.test(key.trim())) return { status: null, stdout: '', stderr: '', error: selection.error };
|
|
153
|
-
let input, identity, selectedEnv;
|
|
154
|
-
const captures = [];
|
|
255
|
+
const admitted = selectedAdmission(selection, spec, invocation, env);
|
|
256
|
+
if (!admitted.key) return admitted;
|
|
257
|
+
const state = { input: undefined, identity: undefined, selectedEnv: undefined, captures: [] };
|
|
155
258
|
try {
|
|
156
|
-
|
|
157
|
-
try { input = request.operation === 'seal' ? Buffer.from(spec.plaintext) : readSecretBytes(file); } catch { return held('actual-document-unavailable'); }
|
|
158
|
-
if (input.length > CREDENTIAL_FILE_MAX_BYTES) return held('document-over-budget');
|
|
159
|
-
const exe = bin ?? resolveSops(env), age = invocation.resolveRealTool('age-keygen', { env });
|
|
160
|
-
if (!nativeFile(exe) || !nativeFile(age)) return held('native-tool-unavailable');
|
|
161
|
-
if (!auditAbsent(cwd)) return held('unsupported-audit-context');
|
|
162
|
-
const deadline = timeout ?? IDENTITY_TIMEOUT_MS;
|
|
163
|
-
if (!Number.isInteger(deadline) || deadline < 1 || deadline > 2_147_483_647 || !Number.isInteger(maxBuffer) || maxBuffer < 1) return held('invalid-capture-budget');
|
|
164
|
-
selectedEnv = isolatedSopsEnv(env, selection.inlineName);
|
|
165
|
-
const publicEnv = isolatedSopsEnv(env, null);
|
|
166
|
-
const options = { cwd, env: selectedEnv, shell: false, encoding: 'buffer', windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'], timeout: deadline, maxBuffer };
|
|
167
|
-
const run = (program, args, bytes, childEnv = selectedEnv) => {
|
|
168
|
-
const result = invocation.runProgram(program, args, { ...options, env: childEnv, input: bytes });
|
|
169
|
-
captures.push(result); return result;
|
|
170
|
-
};
|
|
171
|
-
const version = run(exe, ['--disable-version-check', '--version'], undefined, publicEnv);
|
|
172
|
-
const ageVersion = run(age, ['--version'], undefined, publicEnv);
|
|
173
|
-
if (!captured(version) || !captured(ageVersion) || !SELECTED_SOPS_VERSIONS.has(/^sops\s+([0-9]+\.[0-9]+\.[0-9]+)/.exec(decoded(version.stdout))?.[1]) || !SELECTED_AGE_VERSIONS.has(/^v?([0-9]+\.[0-9]+\.[0-9]+)/.exec(decoded(ageVersion.stdout))?.[1])) return held('unsupported-tool-profile');
|
|
174
|
-
identity = Buffer.from(key.trim(), 'utf8');
|
|
175
|
-
const derived = run(age, ['-y'], identity, publicEnv);
|
|
176
|
-
if (!captured(derived)) return held('recipient-unproven');
|
|
177
|
-
const recipients = decoded(derived.stdout).trim().split(/\r?\n/);
|
|
178
|
-
if (recipients.length !== 1 || !/^age1[ac-hj-np-z02-9]+$/.test(recipients[0])) return held('recipient-unproven');
|
|
179
|
-
const recipient = recipients[0];
|
|
180
|
-
const decryptArgs = (inputType, outputType) => ['decrypt', '--input-type', inputType, '--output-type', outputType, '--filename-override', file, '--enable-local-keyservice=true', '--decryption-order', 'age'];
|
|
181
|
-
if (request.operation === 'decrypt') {
|
|
182
|
-
const envelope = selectedAgeEnvelope(decoded(input), spec.inputType, recipient);
|
|
183
|
-
if (!envelope.ok) return held(envelope.reason);
|
|
184
|
-
const result = run(exe, decryptArgs(spec.inputType, spec.outputType), input);
|
|
185
|
-
if (!captured(result)) return held('selected-decryption-failed');
|
|
186
|
-
return { status: 0, stdout: decoded(result.stdout), stderr: '', error: null };
|
|
187
|
-
}
|
|
188
|
-
if (spec.recipient !== recipient) return held('recipient-mismatch');
|
|
189
|
-
const args = ['encrypt', '--input-type', spec.inputType, '--output-type', spec.outputType, '--filename-override', file, '--enable-local-keyservice=true', '--age', recipient];
|
|
190
|
-
const result = run(exe, args, input);
|
|
191
|
-
if (!captured(result)) return held('selected-encryption-failed');
|
|
192
|
-
if (result.stdout.length > CREDENTIAL_FILE_MAX_BYTES) return held('document-over-budget');
|
|
193
|
-
const text = decoded(result.stdout);
|
|
194
|
-
const envelope = selectedAgeEnvelope(text, spec.outputType, recipient);
|
|
195
|
-
if (!envelope.ok) return held(envelope.reason);
|
|
196
|
-
const readback = run(exe, decryptArgs(spec.outputType, spec.inputType), result.stdout);
|
|
197
|
-
if (!captured(readback)) return held('selected-readback-failed');
|
|
198
|
-
return { status: 0, stdout: text, stderr: '', error: null };
|
|
259
|
+
return performSelected(state, { bin, request, spec, key: admitted.key, selection, invocation, env, cwd, maxBuffer, timeout });
|
|
199
260
|
} catch { return held('selected-native-refused'); }
|
|
200
261
|
finally {
|
|
201
|
-
identity?.fill(0); input?.fill(0);
|
|
202
|
-
|
|
203
|
-
if (selectedEnv) delete selectedEnv.SOPS_AGE_KEY;
|
|
262
|
+
state.identity?.fill(0); state.input?.fill(0);
|
|
263
|
+
wipeCaptures(state.captures);
|
|
264
|
+
if (state.selectedEnv) delete state.selectedEnv.SOPS_AGE_KEY;
|
|
204
265
|
}
|
|
205
266
|
}
|
|
206
267
|
|
|
268
|
+
const identityRequestValid = ({ env, cwd, invocation, assertLease, consume }) =>
|
|
269
|
+
env && typeof env === 'object' && path.isAbsolute(cwd ?? '') && typeof consume === 'function'
|
|
270
|
+
&& typeof assertLease === 'function' && typeof invocation?.runProgram === 'function'
|
|
271
|
+
&& typeof invocation?.resolveRealTool === 'function';
|
|
272
|
+
const identityLinesValid = (lines, identities) => identities.length === 1 && /^AGE-SECRET-KEY-1[0-9A-Z]+$/.test(identities[0])
|
|
273
|
+
&& !lines.some(line => line.startsWith('#') && !/^# (?:created:|public key:)/.test(line));
|
|
274
|
+
const declaredRecipientMatches = (lines, recipient) => {
|
|
275
|
+
const declared = lines.filter(line => line.startsWith('# public key:'));
|
|
276
|
+
return declared.length === 1 && declared[0].slice('# public key:'.length).trim() === recipient;
|
|
277
|
+
};
|
|
278
|
+
const publicationShaped = (publication) => publication && typeof publication.ok === 'boolean' && ['none', 'unknown', 'complete'].includes(publication.effectState);
|
|
279
|
+
const publicationDurable = (publication) => [true, false].includes(publication.created) && ['file-fsync', 'file-and-parent-fsync', 'file-fsync-namespace-unqualified'].includes(publication.durability);
|
|
280
|
+
|
|
281
|
+
const identityFailure = (reason, generated) => ({ ok: false, reason, captureState: generated ? 'unknown' : 'none', effectState: generated ? 'unknown' : 'none' });
|
|
282
|
+
|
|
283
|
+
// The outcome of handing the captured identity to the installer's publication.
|
|
284
|
+
const publicationOutcome = (publication, recipient) => {
|
|
285
|
+
if (!publicationShaped(publication)) return identityFailure('publication-unknown', true);
|
|
286
|
+
if (!publication.ok || publication.effectState !== 'complete') return { ok: false, captureState: 'generated', effectState: publication.effectState,
|
|
287
|
+
reason: 'publication-held' };
|
|
288
|
+
if (!publicationDurable(publication)) return identityFailure('publication-unknown', true);
|
|
289
|
+
return { ok: true, captureState: 'generated', effectState: 'complete', created: publication.created,
|
|
290
|
+
durability: publication.durability, publicRecipient: recipient };
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// One age-keygen run under the caller's lease, its capture kept for wiping.
|
|
294
|
+
const leasedAgeRunner = (invocation, age, options, captures, assertLease) => (args, input) => {
|
|
295
|
+
if (assertLease() !== true) throw new Error('initial age lease lost');
|
|
296
|
+
const result = invocation.runProgram(age, args, { ...options, input });
|
|
297
|
+
captures.push(result);
|
|
298
|
+
if (!captured(result) || result.stdout.length > options.maxBuffer || result.stderr.length > options.maxBuffer) throw new Error('initial age capture incomplete');
|
|
299
|
+
return result;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const generateAndPublish = (state, { env, cwd, invocation, assertLease, consume }) => {
|
|
303
|
+
const age = invocation.resolveRealTool('age-keygen', { env });
|
|
304
|
+
if (!nativeFile(age)) return identityFailure('native-tool-unavailable', false);
|
|
305
|
+
const options = { cwd, env: isolatedSopsEnv(env, null), shell: false, encoding: 'buffer', windowsHide: true,
|
|
306
|
+
stdio: ['pipe', 'pipe', 'pipe'], timeout: IDENTITY_TIMEOUT_MS, maxBuffer: CREDENTIAL_FILE_MAX_BYTES };
|
|
307
|
+
const run = leasedAgeRunner(invocation, age, options, state.captures, assertLease);
|
|
308
|
+
const version = run(['--version']);
|
|
309
|
+
if (!SELECTED_AGE_VERSIONS.has(/^v?(\d+\.\d+\.\d+)/.exec(decoded(version.stdout))?.[1])) return identityFailure('unsupported-tool-profile', false);
|
|
310
|
+
if (assertLease() !== true) return identityFailure('lease-lost', false);
|
|
311
|
+
state.generated = true;
|
|
312
|
+
const made = run([]), lines = decoded(made.stdout).trim().split(/\r?\n/);
|
|
313
|
+
const identities = lines.filter(line => line && !line.startsWith('#'));
|
|
314
|
+
if (!identityLinesValid(lines, identities)) return identityFailure('capture-incomplete', true);
|
|
315
|
+
state.identity = Buffer.from(identities[0], 'utf8');
|
|
316
|
+
const recipient = recipientOf(run(['-y'], state.identity).stdout);
|
|
317
|
+
if (recipient === null) return identityFailure('recipient-unproven', true);
|
|
318
|
+
if (!declaredRecipientMatches(lines, recipient) || assertLease() !== true) return identityFailure('recipient-unproven', true);
|
|
319
|
+
return publicationOutcome(consume(state.identity, recipient), recipient);
|
|
320
|
+
};
|
|
321
|
+
|
|
207
322
|
/** One real private capture for an admitted initial request; the installer owns its durable attempt. */
|
|
208
323
|
export function withGeneratedAgeIdentity({ env, cwd, invocation, assertLease, consume } = {}) {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|| typeof assertLease !== 'function' || typeof invocation?.runProgram !== 'function'
|
|
212
|
-
|| typeof invocation?.resolveRealTool !== 'function') return failure('invalid-request', false);
|
|
213
|
-
const captures = [];
|
|
214
|
-
let generated = false, identity;
|
|
324
|
+
if (!identityRequestValid({ env, cwd, invocation, assertLease, consume })) return identityFailure('invalid-request', false);
|
|
325
|
+
const state = { captures: [], generated: false, identity: undefined };
|
|
215
326
|
try {
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
const options = { cwd, env: isolatedSopsEnv(env, null), shell: false, encoding: 'buffer', windowsHide: true,
|
|
219
|
-
stdio: ['pipe', 'pipe', 'pipe'], timeout: IDENTITY_TIMEOUT_MS, maxBuffer: CREDENTIAL_FILE_MAX_BYTES };
|
|
220
|
-
const run = (args, input) => {
|
|
221
|
-
if (assertLease() !== true) throw new Error('initial age lease lost');
|
|
222
|
-
const result = invocation.runProgram(age, args, { ...options, input });
|
|
223
|
-
captures.push(result);
|
|
224
|
-
if (!captured(result) || result.stdout.length > options.maxBuffer || result.stderr.length > options.maxBuffer) throw new Error('initial age capture incomplete');
|
|
225
|
-
return result;
|
|
226
|
-
};
|
|
227
|
-
const version = run(['--version']);
|
|
228
|
-
if (!SELECTED_AGE_VERSIONS.has(/^v?([0-9]+\.[0-9]+\.[0-9]+)/.exec(decoded(version.stdout))?.[1])) return failure('unsupported-tool-profile', false);
|
|
229
|
-
if (assertLease() !== true) return failure('lease-lost', false);
|
|
230
|
-
generated = true;
|
|
231
|
-
const made = run([]), lines = decoded(made.stdout).trim().split(/\r?\n/);
|
|
232
|
-
const identities = lines.filter(line => line && !line.startsWith('#'));
|
|
233
|
-
if (identities.length !== 1 || !/^AGE-SECRET-KEY-1[0-9A-Z]+$/.test(identities[0])
|
|
234
|
-
|| lines.some(line => line.startsWith('#') && !/^# (?:created:|public key:)/.test(line))) return failure('capture-incomplete', true);
|
|
235
|
-
identity = Buffer.from(identities[0], 'utf8');
|
|
236
|
-
const derived = run(['-y'], identity), recipients = decoded(derived.stdout).trim().split(/\r?\n/);
|
|
237
|
-
if (recipients.length !== 1 || !/^age1[ac-hj-np-z02-9]+$/.test(recipients[0])) return failure('recipient-unproven', true);
|
|
238
|
-
const recipient = recipients[0], declared = lines.filter(line => line.startsWith('# public key:'));
|
|
239
|
-
if (declared.length !== 1 || declared[0].slice('# public key:'.length).trim() !== recipient || assertLease() !== true) return failure('recipient-unproven', true);
|
|
240
|
-
const publication = consume(identity, recipient);
|
|
241
|
-
if (!publication || typeof publication.ok !== 'boolean' || !['none', 'unknown', 'complete'].includes(publication.effectState)) return failure('publication-unknown', true);
|
|
242
|
-
if (!publication.ok || publication.effectState !== 'complete') return { ok: false, captureState: 'generated', effectState: publication.effectState,
|
|
243
|
-
reason: 'publication-held' };
|
|
244
|
-
if (![true, false].includes(publication.created) || !['file-fsync', 'file-and-parent-fsync', 'file-fsync-namespace-unqualified'].includes(publication.durability)) return failure('publication-unknown', true);
|
|
245
|
-
return { ok: true, captureState: 'generated', effectState: 'complete', created: publication.created,
|
|
246
|
-
durability: publication.durability, publicRecipient: recipient };
|
|
247
|
-
} catch { return failure('capture-or-publication-unknown', generated); }
|
|
327
|
+
return generateAndPublish(state, { env, cwd, invocation, assertLease, consume });
|
|
328
|
+
} catch { return identityFailure('capture-or-publication-unknown', state.generated); }
|
|
248
329
|
finally {
|
|
249
|
-
identity?.fill(0);
|
|
250
|
-
|
|
251
|
-
if (Buffer.isBuffer(result?.stdout)) result.stdout.fill(0);
|
|
252
|
-
if (Buffer.isBuffer(result?.stderr)) result.stderr.fill(0);
|
|
253
|
-
}
|
|
330
|
+
state.identity?.fill(0);
|
|
331
|
+
wipeCaptures(state.captures);
|
|
254
332
|
}
|
|
255
333
|
}
|
|
@@ -36,9 +36,9 @@ export function slotAdmitsFile(resolver, file) {
|
|
|
36
36
|
if (classified.status === 'no-slot' || classified.status === 'ambiguous' || !classified.slot) return null;
|
|
37
37
|
const slot = resolver.slot(classified.slot);
|
|
38
38
|
const relative = relativeToRoot(classified.path, classified.root);
|
|
39
|
-
const hit = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings).
|
|
40
|
-
const forbidden = matchersOf(slot.forbids, classified.bindings).
|
|
41
|
-
return
|
|
39
|
+
const hit = matchersOf([...(slot.requires ?? []), ...(slot.allows ?? [])], classified.bindings).some(matcher => matcher.expression.test(relative));
|
|
40
|
+
const forbidden = matchersOf(slot.forbids, classified.bindings).some(matcher => matcher.expression.test(relative) || matcher.expression.test(`${relative}/`));
|
|
41
|
+
return hit && !forbidden;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
/**
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
// ast-walks.mjs - AST walks the architecture checkers share (moved out of typescript.mjs, which keeps the program loading and module resolution).
|
|
2
2
|
import { canonical, isInside } from './config.mjs';
|
|
3
|
-
import { sourceLocation } from '../../lib/ts-ast.mjs';
|
|
3
|
+
import { hasAnyFlag, isConstVariable, sourceLocation } from '../../lib/ts-ast.mjs';
|
|
4
4
|
import { UNPROVEN_FRAMEWORK, isUnshadowedCommonJsRequire, relativePath, unwrapExpression } from './typescript.mjs';
|
|
5
5
|
|
|
6
6
|
/** `expression` unwrapped by the caller's own predicate set (a predicate absent on an older ts reads as never). */
|
|
7
7
|
export function unwrapEach(ts, expression, predicates) {
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
let current = expression;
|
|
9
|
+
while (current && predicates.some((predicate) => predicate?.(current))) current = current.expression;
|
|
10
|
+
return current;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
/** `value` with every TS alias hop resolved (a re-exported import reads as its target symbol). */
|
|
13
|
-
export function normalizedSymbolValue(ts, checker, value) {
|
|
14
|
-
let symbol = value
|
|
14
|
+
export function normalizedSymbolValue(ts, checker, value = null) {
|
|
15
|
+
let symbol = value;
|
|
15
16
|
const seen = new Set();
|
|
16
|
-
while (symbol && (symbol.flags
|
|
17
|
+
while (symbol && hasAnyFlag(symbol.flags, ts.SymbolFlags.Alias) && !seen.has(symbol)) {
|
|
17
18
|
seen.add(symbol);
|
|
18
19
|
const target = checker.getAliasedSymbol(symbol);
|
|
19
20
|
if (!target || target === symbol) break;
|
|
@@ -48,7 +49,7 @@ export function valueSymbol(ts, checker, node, seen = new Set(), { unselected =
|
|
|
48
49
|
seen.add(symbol);
|
|
49
50
|
const declarations = symbol.getDeclarations?.() ?? [];
|
|
50
51
|
if (declarations.length === 1 && ts.isVariableDeclaration(declarations[0]) && declarations[0].initializer
|
|
51
|
-
&& (ts
|
|
52
|
+
&& isConstVariable(ts, declarations[0])) {
|
|
52
53
|
return valueSymbol(ts, checker, declarations[0].initializer, seen, { unselected });
|
|
53
54
|
}
|
|
54
55
|
return symbol;
|
|
@@ -102,50 +103,59 @@ export function anyDescendant(ts, node, match) {
|
|
|
102
103
|
* selected node's symbols to a kind (default `targets.get(symbol)`); `shorthandOf` (when set) also resolves
|
|
103
104
|
* shorthand-property declarations and descends property assignments; `newExpression` descends `new X()` callees.
|
|
104
105
|
*/
|
|
106
|
+
const addKinds = (target, source) => { for (const kind of source) target.add(kind); };
|
|
107
|
+
|
|
108
|
+
const kindsFromReturns = (ts, checker, declaration, targets, options) => {
|
|
109
|
+
const kinds = new Set();
|
|
110
|
+
for (const returned of returnedExpressions(ts, declaration)) addKinds(kinds, tracedFrameworkKinds(ts, checker, returned, targets, options));
|
|
111
|
+
return kinds;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const kindsFromDeclaration = (ts, checker, declaration, targets, options, shorthandOf) => {
|
|
115
|
+
const kinds = new Set();
|
|
116
|
+
if (ts.isVariableDeclaration(declaration) && declaration.initializer)
|
|
117
|
+
addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
|
|
118
|
+
if (shorthandOf && ts.isShorthandPropertyAssignment(declaration)) {
|
|
119
|
+
const target = normalizedSymbolValue(ts, checker, checker.getShorthandAssignmentValueSymbol?.(declaration));
|
|
120
|
+
const kind = shorthandOf(ts, checker, target, targets);
|
|
121
|
+
if (kind) kinds.add(kind);
|
|
122
|
+
}
|
|
123
|
+
if (shorthandOf && ts.isPropertyAssignment(declaration))
|
|
124
|
+
addKinds(kinds, tracedFrameworkKinds(ts, checker, declaration.initializer, targets, options));
|
|
125
|
+
addKinds(kinds, kindsFromReturns(ts, checker, declaration, targets, options));
|
|
126
|
+
return kinds;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const kindsFromSymbol = (ts, checker, symbol, targets, seen, options) => {
|
|
130
|
+
const nextSeen = new Set(seen).add(symbol);
|
|
131
|
+
const declarationOptions = { ...options, seen: nextSeen };
|
|
132
|
+
const kinds = new Set();
|
|
133
|
+
for (const declaration of symbol.getDeclarations?.() ?? [])
|
|
134
|
+
addKinds(kinds, kindsFromDeclaration(ts, checker, declaration, targets, declarationOptions, options.shorthandOf));
|
|
135
|
+
return kinds;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const defaultDirectOf = (ts, checker, { symbol, targets }) => targets.get(symbol) ?? null;
|
|
139
|
+
|
|
105
140
|
export function tracedFrameworkKinds(ts, checker, expression, targets,
|
|
106
141
|
{ seen = new Set(), depth = 0, directOf = null, shorthandOf = null, newExpression = false } = {}) {
|
|
107
142
|
if (!expression) return new Set();
|
|
108
143
|
if (depth > 10) return new Set([UNPROVEN_FRAMEWORK]);
|
|
109
|
-
|
|
110
|
-
const selected = selectedNode(ts,
|
|
144
|
+
const unwrapped = unwrapExpression(ts, expression);
|
|
145
|
+
const selected = selectedNode(ts, unwrapped);
|
|
111
146
|
const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
|
|
112
|
-
const direct = symbol ? (directOf ??
|
|
147
|
+
const direct = symbol ? (directOf ?? defaultDirectOf)(ts, checker, { symbol, selected, targets }) : null;
|
|
113
148
|
if (direct) return new Set([direct]);
|
|
114
|
-
const
|
|
115
|
-
if (ts.isArrowFunction(
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, { ...opts, seen })) kinds.add(kind);
|
|
119
|
-
}
|
|
120
|
-
return kinds;
|
|
121
|
-
}
|
|
122
|
-
if (ts.isCallExpression(expression)) {
|
|
123
|
-
const kinds = tracedFrameworkKinds(ts, checker, expression.expression, targets, { ...opts, seen });
|
|
149
|
+
const options = { depth: depth + 1, directOf, shorthandOf, newExpression };
|
|
150
|
+
if (ts.isArrowFunction(unwrapped) || ts.isFunctionExpression(unwrapped)) return kindsFromReturns(ts, checker, unwrapped, targets, { ...options, seen });
|
|
151
|
+
if (ts.isCallExpression(unwrapped)) {
|
|
152
|
+
const kinds = tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
|
|
124
153
|
if (kinds.size) return kinds;
|
|
125
154
|
}
|
|
126
|
-
if (newExpression && ts.isNewExpression(
|
|
127
|
-
return tracedFrameworkKinds(ts, checker,
|
|
128
|
-
}
|
|
155
|
+
if (newExpression && ts.isNewExpression(unwrapped))
|
|
156
|
+
return tracedFrameworkKinds(ts, checker, unwrapped.expression, targets, { ...options, seen });
|
|
129
157
|
if (!symbol || seen.has(symbol)) return new Set();
|
|
130
|
-
|
|
131
|
-
const kinds = new Set();
|
|
132
|
-
for (const declaration of symbol.getDeclarations?.() ?? []) {
|
|
133
|
-
if (ts.isVariableDeclaration(declaration) && declaration.initializer) {
|
|
134
|
-
for (const kind of tracedFrameworkKinds(ts, checker, declaration.initializer, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
|
|
135
|
-
}
|
|
136
|
-
if (shorthandOf && ts.isShorthandPropertyAssignment(declaration)) {
|
|
137
|
-
const target = normalizedSymbolValue(ts, checker, checker.getShorthandAssignmentValueSymbol?.(declaration));
|
|
138
|
-
const kind = shorthandOf(ts, checker, target, targets);
|
|
139
|
-
if (kind) kinds.add(kind);
|
|
140
|
-
}
|
|
141
|
-
if (shorthandOf && ts.isPropertyAssignment(declaration)) {
|
|
142
|
-
for (const kind of tracedFrameworkKinds(ts, checker, declaration.initializer, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
|
|
143
|
-
}
|
|
144
|
-
for (const returned of returnedExpressions(ts, declaration)) {
|
|
145
|
-
for (const kind of tracedFrameworkKinds(ts, checker, returned, targets, { ...opts, seen: nextSeen })) kinds.add(kind);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
return kinds;
|
|
158
|
+
return kindsFromSymbol(ts, checker, symbol, targets, seen, options);
|
|
149
159
|
}
|
|
150
160
|
|
|
151
161
|
/** 'unproven framework' | 'multiple framework' | the one kind | null, for a decorator whose expression constructs. */
|
|
@@ -153,7 +163,8 @@ export function constructedDecoratorKind(ts, checker, decorator, targets, traceO
|
|
|
153
163
|
const callee = ts.isCallExpression(decorator.expression) ? decorator.expression.expression : decorator.expression;
|
|
154
164
|
const kinds = tracedFrameworkKinds(ts, checker, callee, targets, traceOpts);
|
|
155
165
|
if (kinds.has(UNPROVEN_FRAMEWORK)) return 'unproven framework';
|
|
156
|
-
|
|
166
|
+
if (kinds.size === 1) return [...kinds][0];
|
|
167
|
+
return kinds.size ? 'multiple framework' : null;
|
|
157
168
|
}
|
|
158
169
|
|
|
159
170
|
/** The target a decorator resolves to through a `let`/`var` alias's single initializer, or null. */
|
|
@@ -163,7 +174,7 @@ export function mutableDecoratorKind(ts, checker, decorator, targets) {
|
|
|
163
174
|
const symbol = selected ? normalizedSymbol(ts, checker, selected) : null;
|
|
164
175
|
const declarations = symbol?.getDeclarations?.() ?? [];
|
|
165
176
|
if (declarations.length !== 1 || !ts.isVariableDeclaration(declarations[0]) || !declarations[0].initializer
|
|
166
|
-
|| (ts
|
|
177
|
+
|| isConstVariable(ts, declarations[0])) return null;
|
|
167
178
|
return targets.get(valueSymbol(ts, checker, declarations[0].initializer)) ?? null;
|
|
168
179
|
}
|
|
169
180
|
|
|
@@ -178,11 +189,12 @@ export function decoratorCallee(ts, decorator) {
|
|
|
178
189
|
* not resolve. Null when the statement names no static specifier.
|
|
179
190
|
*/
|
|
180
191
|
export function moduleExportsOf(ts, checker, statement) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
192
|
+
let moduleSpecifier = null;
|
|
193
|
+
if ((ts.isImportDeclaration(statement) || ts.isExportDeclaration(statement))
|
|
194
|
+
&& statement.moduleSpecifier && ts.isStringLiteralLike(statement.moduleSpecifier)) moduleSpecifier = statement.moduleSpecifier;
|
|
195
|
+
let importEquals = null;
|
|
196
|
+
if (ts.isImportEqualsDeclaration(statement) && ts.isExternalModuleReference(statement.moduleReference)
|
|
197
|
+
&& statement.moduleReference.expression && ts.isStringLiteralLike(statement.moduleReference.expression)) importEquals = statement.moduleReference.expression;
|
|
186
198
|
const specifierNode = moduleSpecifier ?? importEquals;
|
|
187
199
|
const specifier = specifierNode?.text;
|
|
188
200
|
if (specifier == null) return null;
|
|
@@ -208,7 +220,8 @@ export function programSourcesOf(context, checker, localFiles, { root = null } =
|
|
|
208
220
|
* The CommonJS require() caveat every framework scan reports: each `require('<specifier>')` `accepted` pushes
|
|
209
221
|
* `${filePath} uses a CommonJS <label ?? specifier> <detail>` (the import-shape reasoning cannot prove that binding).
|
|
210
222
|
*/
|
|
211
|
-
export function commonJsRequireReasons(ts, checker, sourceFile, accepted, reasons, filePath, detail
|
|
223
|
+
export function commonJsRequireReasons(ts, checker, sourceFile, accepted, reasons, filePath, detail) {
|
|
224
|
+
const label = arguments[7] ?? null;
|
|
212
225
|
const visit = node => {
|
|
213
226
|
if (ts.isCallExpression(node) && isUnshadowedCommonJsRequire(ts, checker, node.expression)
|
|
214
227
|
&& node.arguments.length === 1 && ts.isStringLiteralLike(node.arguments[0]) && accepted(node.arguments[0].text)) {
|