@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
|
@@ -37,8 +37,11 @@ export async function parsePlpgsqlBody(body, parameters = [], returnKind = 'void
|
|
|
37
37
|
let tag = `$hfs_probe_${i}$`;
|
|
38
38
|
while (body.includes(tag)) { i += 1; tag = `$hfs_probe_${i}$`; }
|
|
39
39
|
const args = parameters.map((parameter) => parameter?.FunctionParameter?.name).filter(Boolean)
|
|
40
|
-
.map((name) => `"${name.
|
|
41
|
-
|
|
40
|
+
.map((name) => `"${name.replaceAll('"', '""')}" text`).join(', ');
|
|
41
|
+
let returns = 'void';
|
|
42
|
+
if (returnKind === 'setof') returns = 'setof text';
|
|
43
|
+
else if (returnKind === 'trigger') returns = 'trigger';
|
|
44
|
+
else if (returnKind === 'scalar') returns = 'text';
|
|
42
45
|
const result = await (await loadPgParser()).parsePlPgSQLSync(
|
|
43
46
|
`create function hfs_probe(${args}) returns ${returns} language plpgsql as ${tag}${body}${tag};`,
|
|
44
47
|
);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Trailing-slash trimming for slot paths and generated roots, linear in the length of the text.
|
|
2
|
+
|
|
3
|
+
/** `text` without its trailing `/` characters. */
|
|
4
|
+
export function trimTrailingSlashes(text) {
|
|
5
|
+
let end = text.length;
|
|
6
|
+
while (end > 0 && text.codePointAt(end - 1) === 47) end -= 1;
|
|
7
|
+
return text.slice(0, end);
|
|
8
|
+
}
|
|
@@ -49,6 +49,24 @@ export function readTree(repoRoot, { isIgnored }) {
|
|
|
49
49
|
const holdsNoFile = (node) => node.files === 0 && node.dirs.every(holdsNoFile);
|
|
50
50
|
const countDirs = (node) => node.dirs.reduce((sum, child) => sum + 1 + countDirs(child), 0);
|
|
51
51
|
|
|
52
|
+
function collectGhosts(node, ghosts) {
|
|
53
|
+
for (const [i, a] of node.dirs.entries()) {
|
|
54
|
+
for (const b of node.dirs.slice(i + 1)) {
|
|
55
|
+
if (editDistance(a.name, b.name) > GHOST_DISTANCE) continue;
|
|
56
|
+
if (holdsNoFile(a)) ghosts.push({ path: a.rel, of: b.rel, distance: editDistance(a.name, b.name) });
|
|
57
|
+
if (holdsNoFile(b)) ghosts.push({ path: b.rel, of: a.rel, distance: editDistance(a.name, b.name) });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function collectTreeFacts(node, empty, ghosts) {
|
|
63
|
+
for (const child of node.dirs) {
|
|
64
|
+
if (holdsNoFile(child)) empty.push({ path: child.rel, below: countDirs(child) });
|
|
65
|
+
else collectTreeFacts(child, empty, ghosts);
|
|
66
|
+
}
|
|
67
|
+
collectGhosts(node, ghosts);
|
|
68
|
+
}
|
|
69
|
+
|
|
52
70
|
/**
|
|
53
71
|
* The tree's own findings: `empty` are the topmost directories with no file anywhere below them (each with the number of
|
|
54
72
|
* directories it holds); `ghosts` are pairs of sibling directories within GHOST_DISTANCE edits where `ghost` has no file
|
|
@@ -57,20 +75,7 @@ const countDirs = (node) => node.dirs.reduce((sum, child) => sum + 1 + countDirs
|
|
|
57
75
|
export function treeFacts(tree) {
|
|
58
76
|
const empty = [];
|
|
59
77
|
const ghosts = [];
|
|
60
|
-
|
|
61
|
-
for (const child of node.dirs) {
|
|
62
|
-
if (holdsNoFile(child)) empty.push({ path: child.rel, below: countDirs(child) });
|
|
63
|
-
else visit(child);
|
|
64
|
-
}
|
|
65
|
-
for (const [i, a] of node.dirs.entries()) {
|
|
66
|
-
for (const b of node.dirs.slice(i + 1)) {
|
|
67
|
-
if (editDistance(a.name, b.name) > GHOST_DISTANCE) continue;
|
|
68
|
-
if (holdsNoFile(a)) ghosts.push({ path: a.rel, of: b.rel, distance: editDistance(a.name, b.name) });
|
|
69
|
-
if (holdsNoFile(b)) ghosts.push({ path: b.rel, of: a.rel, distance: editDistance(a.name, b.name) });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
visit(tree);
|
|
78
|
+
collectTreeFacts(tree, empty, ghosts);
|
|
74
79
|
return { empty, ghosts };
|
|
75
80
|
}
|
|
76
81
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
import { createRequire } from 'node:module';
|
|
3
2
|
import path from 'node:path';
|
|
3
|
+
import { byCodeUnit } from '../lib/list.mjs';
|
|
4
4
|
|
|
5
5
|
// One TypeScript program per (compiler, root names, compiler options, project references) inside a program run, and
|
|
6
6
|
// one value per (kind, compiler, input) for what callers derive from those programs (the architecture context).
|
|
@@ -27,7 +27,7 @@ function keyOf(value) {
|
|
|
27
27
|
}
|
|
28
28
|
if (typeof value !== 'object' || (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)) return null;
|
|
29
29
|
const entries = [];
|
|
30
|
-
for (const name of Object.keys(value).sort()) {
|
|
30
|
+
for (const name of Object.keys(value).sort(byCodeUnit)) {
|
|
31
31
|
const item = keyOf(value[name]);
|
|
32
32
|
if (item === null) return null;
|
|
33
33
|
entries.push(`${JSON.stringify(name)}:${item}`);
|
|
@@ -44,7 +44,10 @@ export function sharedInProgramRun(kind, ts, input, build) {
|
|
|
44
44
|
const key = values ? keyOf(input) : null;
|
|
45
45
|
if (key === null) return build();
|
|
46
46
|
let byKey = values.get(ts);
|
|
47
|
-
if (!byKey)
|
|
47
|
+
if (!byKey) {
|
|
48
|
+
byKey = new Map();
|
|
49
|
+
values.set(ts, byKey);
|
|
50
|
+
}
|
|
48
51
|
const id = `${kind}\0${key}`;
|
|
49
52
|
if (!byKey.has(id)) byKey.set(id, build());
|
|
50
53
|
return byKey.get(id);
|
|
@@ -74,4 +77,3 @@ export function typeScriptProjectReferencePath(ts, reference) {
|
|
|
74
77
|
export function resolveTypeScriptModule(ts, specifier, from, options, host = ts.sys) {
|
|
75
78
|
return ts.resolveModuleName(specifier, from, options, host).resolvedModule?.resolvedFileName;
|
|
76
79
|
}
|
|
77
|
-
|
|
@@ -6,7 +6,7 @@ import path from 'node:path';
|
|
|
6
6
|
import { allowsFile } from './allows.mjs';
|
|
7
7
|
import { openHfs } from './slots.mjs';
|
|
8
8
|
|
|
9
|
-
const posix = (p) => String(p).
|
|
9
|
+
const posix = (p) => String(p).replaceAll('\\', '/');
|
|
10
10
|
const isAbsoluteAny = (text) => path.isAbsolute(text) || /^[A-Za-z]:\//.test(text);
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -58,8 +58,8 @@ export function shellCommands(body) {
|
|
|
58
58
|
} else if (char === '"' || char === "'") {
|
|
59
59
|
quote = char;
|
|
60
60
|
current += char;
|
|
61
|
-
} else if (
|
|
62
|
-
|
|
61
|
+
} else if (isCommandSeparator(body, i, char)) {
|
|
62
|
+
i += commandSeparatorAdvance(body, i, char);
|
|
63
63
|
flush();
|
|
64
64
|
} else if (char === '\\' && body[i + 1] === '\n') {
|
|
65
65
|
i += 1;
|
|
@@ -71,43 +71,17 @@ export function shellCommands(body) {
|
|
|
71
71
|
return commands;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
const isCommandSeparator = (body, index, char) => char === ';' || char === '|' || (char === '&' && body[index + 1] === '&');
|
|
75
|
+
const commandSeparatorAdvance = (body, index, char) => char !== ';' && body[index + 1] === char ? 1 : 0;
|
|
76
|
+
|
|
74
77
|
/**
|
|
75
78
|
* Parse a Dockerfile: { instructions: [{ keyword, text, line, stage }], stages: [{ index, name, image, line, instructions }],
|
|
76
79
|
* comments: [{ text, line }], preamble: [instruction before the first FROM (ARG)] }. `keyword` is upper-case; `text` is the
|
|
77
80
|
* argument text of the logical line; `stage` is the index of the stage the instruction belongs to (-1 before the first FROM).
|
|
78
81
|
*/
|
|
79
82
|
export function parseDockerfile(source) {
|
|
80
|
-
const lines = String(source).
|
|
81
|
-
const instructions =
|
|
82
|
-
const comments = [];
|
|
83
|
-
let escape = '\\';
|
|
84
|
-
let directives = true;
|
|
85
|
-
for (let i = 0; i < lines.length; i += 1) {
|
|
86
|
-
const raw = lines[i];
|
|
87
|
-
const trimmed = raw.trim();
|
|
88
|
-
if (trimmed === '') { directives = false; continue; }
|
|
89
|
-
if (trimmed.startsWith('#')) {
|
|
90
|
-
const body = trimmed.slice(1).trim();
|
|
91
|
-
const directive = directives ? /^(syntax|escape|check)\s*=\s*(\S+)$/i.exec(body) : null;
|
|
92
|
-
if (directive) { if (directive[1].toLowerCase() === 'escape') escape = directive[2]; continue; }
|
|
93
|
-
directives = false;
|
|
94
|
-
comments.push({ text: body, line: i + 1 });
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
directives = false;
|
|
98
|
-
const start = i + 1;
|
|
99
|
-
let logical = trimmed;
|
|
100
|
-
while (logical.endsWith(escape) && i + 1 < lines.length) {
|
|
101
|
-
logical = logical.slice(0, -1).trimEnd();
|
|
102
|
-
i += 1;
|
|
103
|
-
while (i < lines.length && lines[i].trim().startsWith('#')) i += 1;
|
|
104
|
-
if (i >= lines.length) break;
|
|
105
|
-
logical = `${logical} ${lines[i].trim()}`;
|
|
106
|
-
}
|
|
107
|
-
const split = logical.search(/\s/);
|
|
108
|
-
const keyword = (split < 0 ? logical : logical.slice(0, split)).toUpperCase();
|
|
109
|
-
instructions.push({ keyword, text: split < 0 ? '' : logical.slice(split).trim(), line: start, stage: -1 });
|
|
110
|
-
}
|
|
83
|
+
const lines = String(source).replaceAll('\r\n', '\n').split('\n');
|
|
84
|
+
const { instructions, comments } = parseInstructions(lines);
|
|
111
85
|
const stages = [];
|
|
112
86
|
const preamble = [];
|
|
113
87
|
for (const instruction of instructions) {
|
|
@@ -122,3 +96,52 @@ export function parseDockerfile(source) {
|
|
|
122
96
|
}
|
|
123
97
|
return { instructions, stages, comments, preamble };
|
|
124
98
|
}
|
|
99
|
+
|
|
100
|
+
function parseInstructions(lines) {
|
|
101
|
+
const instructions = [];
|
|
102
|
+
const comments = [];
|
|
103
|
+
const state = { escape: '\\', directives: true };
|
|
104
|
+
let i = 0;
|
|
105
|
+
while (i < lines.length) {
|
|
106
|
+
const parsed = parseInstructionLine(lines, i, state);
|
|
107
|
+
if (parsed.comment) comments.push(parsed.comment);
|
|
108
|
+
if (parsed.instruction) instructions.push(parsed.instruction);
|
|
109
|
+
i = parsed.nextLine + 1;
|
|
110
|
+
}
|
|
111
|
+
return { instructions, comments };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function parseInstructionLine(lines, index, state) {
|
|
115
|
+
const trimmed = lines[index].trim();
|
|
116
|
+
if (trimmed === '') { state.directives = false; return { nextLine: index }; }
|
|
117
|
+
if (trimmed.startsWith('#')) return parseCommentLine(trimmed, index, state);
|
|
118
|
+
state.directives = false;
|
|
119
|
+
const { logical, nextLine } = logicalInstructionOf(lines, index, state.escape);
|
|
120
|
+
const split = logical.search(/\s/);
|
|
121
|
+
const keyword = (split < 0 ? logical : logical.slice(0, split)).toUpperCase();
|
|
122
|
+
return { nextLine, instruction: { keyword, text: split < 0 ? '' : logical.slice(split).trim(), line: index + 1, stage: -1 } };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseCommentLine(trimmed, index, state) {
|
|
126
|
+
const body = trimmed.slice(1).trim();
|
|
127
|
+
const directive = state.directives ? /^(syntax|escape|check)\s*=\s*(\S+)$/i.exec(body) : null;
|
|
128
|
+
if (directive) {
|
|
129
|
+
if (directive[1].toLowerCase() === 'escape') state.escape = directive[2];
|
|
130
|
+
return { nextLine: index };
|
|
131
|
+
}
|
|
132
|
+
state.directives = false;
|
|
133
|
+
return { nextLine: index, comment: { text: body, line: index + 1 } };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function logicalInstructionOf(lines, index, escape) {
|
|
137
|
+
let logical = lines[index].trim();
|
|
138
|
+
let nextLine = index;
|
|
139
|
+
while (logical.endsWith(escape) && nextLine + 1 < lines.length) {
|
|
140
|
+
logical = logical.slice(0, -1).trimEnd();
|
|
141
|
+
nextLine += 1;
|
|
142
|
+
while (nextLine < lines.length && lines[nextLine].trim().startsWith('#')) nextLine += 1;
|
|
143
|
+
if (nextLine >= lines.length) break;
|
|
144
|
+
logical = `${logical} ${lines[nextLine].trim()}`;
|
|
145
|
+
}
|
|
146
|
+
return { logical, nextLine };
|
|
147
|
+
}
|
|
@@ -8,3 +8,13 @@ export const readEnv = (name, env = process.env) => env[name];
|
|
|
8
8
|
|
|
9
9
|
/** True when `env` (default: the process environment) belongs to a spec run (`node --test` marks its children with NODE_TEST_CONTEXT). */
|
|
10
10
|
export const isSpecRun = (env = process.env) => Boolean(env.NODE_TEST_CONTEXT);
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The environment for a child that is itself a test runner: `env` without the mark of an enclosing spec run. A `node --test` that inherits NODE_TEST_CONTEXT believes it is a
|
|
14
|
+
* subtest of that run: it reports to its parent and exits 0 whatever its tests did, so a verb that runs specs as a child (starci test affected, the land gate, the release cut, the
|
|
15
|
+
* suite) would pass red specs from inside a test process. The runner marks the spec files it starts itself, so isSpecRun stays true inside them.
|
|
16
|
+
*/
|
|
17
|
+
export function withoutTestRunner(env = process.env) {
|
|
18
|
+
const { NODE_TEST_CONTEXT: _enclosing, ...rest } = env;
|
|
19
|
+
return rest;
|
|
20
|
+
}
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// }
|
|
9
9
|
// `starci app emit` writes `be/contracts/<service>/events.json` from them (name, version, compensates, and the payload fields of the
|
|
10
10
|
// interface `create` takes); `starci app check` compares the committed file with the same reader. Nothing is executed: both walk the TypeScript
|
|
11
|
-
// syntax tree. The functions take the `ts` module the caller loaded (the repository's own, else the runtime's); this file imports
|
|
11
|
+
// syntax tree. The functions take the `ts` module the caller loaded (the repository's own, else the runtime's); this file imports only the code-unit comparator.
|
|
12
|
+
import { byCodeUnit } from './list.mjs';
|
|
12
13
|
|
|
13
14
|
const EVENT_CONTRACT_SCHEMA = 'starci/event-contract@1';
|
|
14
15
|
const FIELD_TYPE = /^(?:string|number|boolean|string\[\]|number\[\])\??$/;
|
|
@@ -37,6 +38,18 @@ function fieldType(ts, node) {
|
|
|
37
38
|
return null;
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
function appendPayloadMember(ts, member, typeName, problems, where, payload) {
|
|
42
|
+
if (!ts.isPropertySignature(member) || !ts.isIdentifier(member.name) || member.type === undefined) {
|
|
43
|
+
problems.push(`${where}: the payload ${typeName} holds a member that is not \`name: type\``);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const type = fieldType(ts, member.type);
|
|
47
|
+
let written = null;
|
|
48
|
+
if (type !== null) written = `${type}${member.questionToken ? '?' : ''}`;
|
|
49
|
+
if (written === null || !FIELD_TYPE.test(written)) problems.push(`${where}: payload field ${member.name.text} must be string, number, boolean, string[] or number[]`);
|
|
50
|
+
else payload[member.name.text] = written;
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
/** The payload fields of the interface or type literal named `typeName` in the file: `{ <field>: <type> }`, optional fields with a `?`. */
|
|
41
54
|
function payloadOf(ts, sourceFile, typeName, problems, where) {
|
|
42
55
|
let members = null;
|
|
@@ -49,16 +62,7 @@ function payloadOf(ts, sourceFile, typeName, problems, where) {
|
|
|
49
62
|
return {};
|
|
50
63
|
}
|
|
51
64
|
const payload = {};
|
|
52
|
-
for (const member of members)
|
|
53
|
-
if (!ts.isPropertySignature(member) || !ts.isIdentifier(member.name) || member.type === undefined) {
|
|
54
|
-
problems.push(`${where}: the payload ${typeName} holds a member that is not \`name: type\``);
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
|
-
const type = fieldType(ts, member.type);
|
|
58
|
-
const written = type === null ? null : `${type}${member.questionToken ? '?' : ''}`;
|
|
59
|
-
if (written === null || !FIELD_TYPE.test(written)) problems.push(`${where}: payload field ${member.name.text} must be string, number, boolean, string[] or number[]`);
|
|
60
|
-
else payload[member.name.text] = written;
|
|
61
|
-
}
|
|
65
|
+
for (const member of members) appendPayloadMember(ts, member, typeName, problems, where, payload);
|
|
62
66
|
return payload;
|
|
63
67
|
}
|
|
64
68
|
|
|
@@ -91,7 +95,7 @@ export function readEventClasses(ts, text, file = 'event.ts') {
|
|
|
91
95
|
}
|
|
92
96
|
|
|
93
97
|
/** The sorted object: keys in code-point order, recursively (what the snapshot prints). */
|
|
94
|
-
const sorted = (value) => (value !== null && typeof value === 'object' && !Array.isArray(value) ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, sorted(value[key])])) : value);
|
|
98
|
+
const sorted = (value) => (value !== null && typeof value === 'object' && !Array.isArray(value) ? Object.fromEntries(Object.keys(value).sort(byCodeUnit).map((key) => [key, sorted(value[key])])) : value);
|
|
95
99
|
|
|
96
100
|
/** The text of the snapshot of a service from its event classes: 2-space JSON, sorted keys, one final newline. */
|
|
97
101
|
export function snapshotText(service, events) {
|
|
@@ -100,4 +104,4 @@ export function snapshotText(service, events) {
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
/** The snapshot's text with line endings folded, for comparing a committed file to a fresh one. */
|
|
103
|
-
export const folded = (text) => text.
|
|
107
|
+
export const folded = (text) => text.replaceAll('\r\n', '\n');
|
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export const unquoteDiffPath = (value) =>
|
|
9
9
|
/^".*"$/.test(value)
|
|
10
|
-
? JSON.parse(value.replace(/\\([0-7]{3})/g, (_, octal) =>
|
|
10
|
+
? JSON.parse(value.replace(/\\([0-7]{3})/g, (_, octal) => String.raw`\u00${Number.parseInt(octal, 8).toString(16).padStart(2, '0')}`))
|
|
11
11
|
: value;
|
|
12
12
|
|
|
13
13
|
// git's repository-local variables (git rev-parse --local-env-vars). A hook or alias run in a linked worktree exports
|
|
14
14
|
// GIT_DIR=<main>/.git/worktrees/<wt>; a spec inheriting it pointed every fixture git at the LIVE .claude repo and re-inited
|
|
15
15
|
// it core.bare=true (2026-09-29, a6f60352c). tests/setup/isolated-registry.mjs drops the same list.
|
|
16
|
-
export const GIT_LOCAL_ENV_VARS = ['GIT_DIR', 'GIT_COMMON_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_IMPLICIT_WORK_TREE', 'GIT_PREFIX', 'GIT_CONFIG', 'GIT_CONFIG_PARAMETERS', 'GIT_CONFIG_COUNT', 'GIT_GRAFT_FILE', 'GIT_NO_REPLACE_OBJECTS', 'GIT_REPLACE_REF_BASE', 'GIT_SHALLOW_FILE'];
|
|
16
|
+
export const GIT_LOCAL_ENV_VARS = ['GIT_DIR', 'GIT_COMMON_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_OBJECT_DIRECTORY', 'GIT_ALTERNATE_OBJECT_DIRECTORIES', 'GIT_IMPLICIT_WORK_TREE', 'GIT_PREFIX', 'GIT_CONFIG', 'GIT_CONFIG_PARAMETERS', 'GIT_CONFIG_COUNT', 'GIT_GRAFT_FILE', 'GIT_NO_REPLACE_OBJECTS', 'GIT_REPLACE_REF_BASE', 'GIT_SHALLOW_FILE', 'GIT_INTERNAL_SUPER_PREFIX'];
|
|
17
17
|
/** `parent` (the caller's env) without git's repository-local variables (GIT_CONFIG_KEY_n/VALUE_n go with GIT_CONFIG_COUNT). Pure. */
|
|
18
18
|
export function withoutGitLocalEnv(parent) {
|
|
19
19
|
const env = { ...parent };
|