@pikku/cli 0.12.91 → 0.12.92
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/LICENSE +106 -0
- package/console-app/assets/{index-CpkvmAGb.js → index-DDpIMCpy.js} +149 -149
- package/console-app/index.html +1 -1
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +15 -0
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +21 -0
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +35 -40
- package/dist/.pikku/function/pikku-functions.gen.js +1 -3
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +2 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +2 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.d.ts +4 -0
- package/dist/.pikku/scenarios/schemas/register.gen.js +4 -0
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +5 -3
- package/dist/.pikku/schemas/schemas/FabricSecretsListOutput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/FabricSecretsRotateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricSecretsRotateOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricSecretsSetOutput.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +2 -8
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +6 -0
- package/dist/src/deploy/analyzer/analyzer.js +22 -5
- package/dist/src/deploy/build-pipeline.js +5 -1
- package/dist/src/fabric/fabric-commands.d.ts +37 -9
- package/dist/src/fabric/fabric-commands.js +12 -0
- package/dist/src/fabric/functions/domains-add.function.d.ts +4 -4
- package/dist/src/fabric/functions/secrets-list.function.d.ts +16 -4
- package/dist/src/fabric/functions/secrets-list.function.js +12 -10
- package/dist/src/fabric/functions/secrets-rotate.function.d.ts +24 -0
- package/dist/src/fabric/functions/secrets-rotate.function.js +36 -0
- package/dist/src/fabric/functions/secrets-set.function.d.ts +8 -4
- package/dist/src/fabric/functions/secrets-set.function.js +18 -7
- package/dist/src/fabric/lib/http.d.ts +7 -1
- package/dist/src/fabric/lib/sealed-box.d.ts +16 -0
- package/dist/src/fabric/lib/sealed-box.js +72 -0
- package/dist/src/functions/commands/dev.js +9 -0
- package/dist/src/functions/commands/pikku-command-bootstrap.js +1 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +17 -2
- package/dist/src/functions/commands/scenario-browser.js +16 -8
- package/dist/src/functions/commands/scenario-environment.d.ts +32 -0
- package/dist/src/functions/commands/scenario-environment.js +61 -0
- package/dist/src/functions/commands/scenario.d.ts +6 -0
- package/dist/src/functions/commands/scenario.js +14 -9
- package/dist/src/functions/commands/skills.js +27 -61
- package/dist/src/functions/db/db-codegen.d.ts +6 -0
- package/dist/src/functions/db/db-codegen.js +9 -0
- package/dist/src/functions/db/db-migrator.js +14 -0
- package/dist/src/functions/db/local-db.d.ts +8 -0
- package/dist/src/functions/db/local-db.js +24 -2
- package/dist/src/functions/db/migration-identifiers.d.ts +58 -0
- package/dist/src/functions/db/migration-identifiers.js +262 -0
- package/dist/src/functions/db/migration-provenance.d.ts +35 -0
- package/dist/src/functions/db/migration-provenance.js +80 -0
- package/dist/src/functions/db/schema-sql.d.ts +43 -0
- package/dist/src/functions/db/schema-sql.js +135 -0
- package/dist/src/functions/db/sqlite/sqlite-kysely.js +22 -2
- package/dist/src/functions/wirings/functions/schemas.js +13 -1
- package/dist/src/functions/wirings/rpc/pikku-command-rpc.js +6 -1
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +10 -0
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +102 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +13 -0
- package/dist/src/functions/wirings/scenarios/scenario-partition.js +15 -2
- package/dist/src/functions/wirings/scenarios/scenario-schema-partition.d.ts +33 -0
- package/dist/src/functions/wirings/scenarios/scenario-schema-partition.js +49 -0
- package/dist/src/functions/workflows/all.workflow.js +2 -7
- package/dist/src/utils/file-writer.js +14 -2
- package/dist/src/utils/pikku-cli-config.d.ts +14 -0
- package/dist/src/utils/pikku-cli-config.js +34 -0
- package/dist/src/utils/remove-legacy-scaffold-file.d.ts +14 -0
- package/dist/src/utils/remove-legacy-scaffold-file.js +23 -0
- package/dist/src/utils/serialize-schemas.d.ts +3 -1
- package/dist/src/utils/serialize-schemas.js +39 -4
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +9 -9
- package/dist/.pikku/schemas/schemas/PikkuScenarioFunctionsOutput.schema.json +0 -1
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.d.ts +0 -1
- package/dist/src/functions/wirings/scenarios/pikku-command-scenario-functions.js +0 -31
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.d.ts +0 -10
- package/dist/src/functions/wirings/scenarios/serialize-scenario-functions.js +0 -104
- package/skills/pikku-addon/SKILL.md +0 -243
- package/skills/pikku-addon/references/addon-package-manifest.md +0 -63
- package/skills/pikku-ai-agent/SKILL.md +0 -231
- package/skills/pikku-ai-vercel/SKILL.md +0 -82
- package/skills/pikku-ai-voice/SKILL.md +0 -88
- package/skills/pikku-audit/SKILL.md +0 -175
- package/skills/pikku-aws/SKILL.md +0 -111
- package/skills/pikku-backblaze/SKILL.md +0 -71
- package/skills/pikku-better-auth/SKILL.md +0 -298
- package/skills/pikku-cli/SKILL.md +0 -198
- package/skills/pikku-cli/references/complete-example.md +0 -82
- package/skills/pikku-concepts/SKILL.md +0 -250
- package/skills/pikku-concepts/references/concept-mapping.md +0 -556
- package/skills/pikku-concepts/references/packages.md +0 -29
- package/skills/pikku-config/SKILL.md +0 -212
- package/skills/pikku-cron/SKILL.md +0 -214
- package/skills/pikku-deploy-azure/SKILL.md +0 -71
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -82
- package/skills/pikku-deploy-express/SKILL.md +0 -86
- package/skills/pikku-deploy-fastify/SKILL.md +0 -73
- package/skills/pikku-deploy-lambda/SKILL.md +0 -110
- package/skills/pikku-deploy-nextjs/SKILL.md +0 -78
- package/skills/pikku-deploy-uws/SKILL.md +0 -88
- package/skills/pikku-deps/SKILL.md +0 -98
- package/skills/pikku-emails/SKILL.md +0 -163
- package/skills/pikku-fabric/SKILL.md +0 -318
- package/skills/pikku-fabric-debug/SKILL.md +0 -112
- package/skills/pikku-feature/SKILL.md +0 -258
- package/skills/pikku-gateway-slack/SKILL.md +0 -115
- package/skills/pikku-http/SKILL.md +0 -220
- package/skills/pikku-http/references/http-options.md +0 -55
- package/skills/pikku-i18n/SKILL.md +0 -137
- package/skills/pikku-info/SKILL.md +0 -100
- package/skills/pikku-jose/SKILL.md +0 -105
- package/skills/pikku-kysely/SKILL.md +0 -219
- package/skills/pikku-machine-auth/SKILL.md +0 -183
- package/skills/pikku-mcp/SKILL.md +0 -241
- package/skills/pikku-middleware/SKILL.md +0 -231
- package/skills/pikku-middleware/references/middleware-patterns.md +0 -61
- package/skills/pikku-mongodb/SKILL.md +0 -105
- package/skills/pikku-n8n-import/SKILL.md +0 -109
- package/skills/pikku-n8n-import/SPEC.md +0 -84
- package/skills/pikku-n8n-import/references/addon-mapping.md +0 -121
- package/skills/pikku-n8n-import/references/code-translation.md +0 -121
- package/skills/pikku-n8n-import/references/loops-and-control.md +0 -87
- package/skills/pikku-paraglide/SKILL.md +0 -117
- package/skills/pikku-permissions/SKILL.md +0 -192
- package/skills/pikku-pino/SKILL.md +0 -79
- package/skills/pikku-product-second-opinion/README.md +0 -43
- package/skills/pikku-product-second-opinion/SKILL.md +0 -158
- package/skills/pikku-product-second-opinion/example/sample-report.md +0 -90
- package/skills/pikku-product-second-opinion/references/report-template.md +0 -73
- package/skills/pikku-queue/SKILL.md +0 -240
- package/skills/pikku-react/SKILL.md +0 -212
- package/skills/pikku-react-query/SKILL.md +0 -242
- package/skills/pikku-realtime/SKILL.md +0 -236
- package/skills/pikku-realtime/references/other-routes.md +0 -23
- package/skills/pikku-redis/SKILL.md +0 -90
- package/skills/pikku-rpc/SKILL.md +0 -171
- package/skills/pikku-rtl/SKILL.md +0 -219
- package/skills/pikku-scenario/SKILL.md +0 -382
- package/skills/pikku-schedule/SKILL.md +0 -57
- package/skills/pikku-schema-ajv/SKILL.md +0 -62
- package/skills/pikku-schema-cfworker/SKILL.md +0 -63
- package/skills/pikku-security/SKILL.md +0 -108
- package/skills/pikku-services/SKILL.md +0 -248
- package/skills/pikku-services/references/audit-wire-service.md +0 -34
- package/skills/pikku-software-archaeology/README.md +0 -70
- package/skills/pikku-software-archaeology/SKILL.md +0 -186
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +0 -625
- package/skills/pikku-software-archaeology/references/pikku-mapping.md +0 -49
- package/skills/pikku-software-archaeology/scripts/validate.mjs +0 -173
- package/skills/pikku-tag-middleware/SKILL.md +0 -13
- package/skills/pikku-template-clone/SKILL.md +0 -40
- package/skills/pikku-trigger/SKILL.md +0 -181
- package/skills/pikku-versioning/SKILL.md +0 -173
- package/skills/pikku-websocket/SKILL.md +0 -243
- package/skills/pikku-workflow/SKILL.md +0 -172
- package/skills/pikku-workflow/references/workflow-reference.md +0 -63
- package/skills/pikku-workflows-client/SKILL.md +0 -150
- package/skills/pikku-ws/SKILL.md +0 -47
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { addFunction } from '@pikku/core/function';
|
|
4
|
+
import { pikkuState } from '@pikku/core/internal';
|
|
5
|
+
import { getStubTracker, isTestRun, } from '@pikku/core/services';
|
|
6
|
+
/**
|
|
7
|
+
* The instrumentation `pikku scenario run` calls on the server under test:
|
|
8
|
+
* snapshot/reset live coverage, and reset/read recorded stub calls.
|
|
9
|
+
*
|
|
10
|
+
* These are pikku's, not the application's. They were previously scaffolded into
|
|
11
|
+
* the project as ordinary `pikkuFunc`s, which is why they ended up registered in
|
|
12
|
+
* the app bootstrap and shipped — `expose: true` — inside every deployed bundle.
|
|
13
|
+
* Registering them here instead means the only process that has them is the one
|
|
14
|
+
* that should: a development server. Nothing is generated, nothing is written to
|
|
15
|
+
* the user's source tree, and a bundle cannot carry what was never in its
|
|
16
|
+
* bootstrap.
|
|
17
|
+
*
|
|
18
|
+
* The bodies are unchanged from the generated ones, minus their schemas: an
|
|
19
|
+
* instrumentation endpoint with a single optional string argument has nothing to
|
|
20
|
+
* validate, and dropping the schemas drops the `zod` dependency the scaffold
|
|
21
|
+
* silently required of every project.
|
|
22
|
+
*/
|
|
23
|
+
const takeLiveCoverage = {
|
|
24
|
+
func: async ({ coverageService, metaService }) => {
|
|
25
|
+
if (!coverageService?.takeReport || !metaService?.basePath)
|
|
26
|
+
return null;
|
|
27
|
+
let functionsMeta;
|
|
28
|
+
try {
|
|
29
|
+
const content = await readFile(join(metaService.basePath, 'function', 'pikku-functions-meta-verbose.gen.json'), 'utf-8');
|
|
30
|
+
functionsMeta = JSON.parse(content);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
return await coverageService.takeReport(functionsMeta);
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const resetLiveCoverage = {
|
|
39
|
+
func: async ({ coverageService }) => {
|
|
40
|
+
if (!coverageService)
|
|
41
|
+
return { enabled: false };
|
|
42
|
+
await coverageService.reset();
|
|
43
|
+
return { enabled: true };
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
const resetStubs = {
|
|
47
|
+
func: async () => {
|
|
48
|
+
getStubTracker().reset();
|
|
49
|
+
return { enabled: isTestRun() };
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
const getStubCalls = {
|
|
53
|
+
func: async (_services, data) => getStubTracker().getCalls(data?.service ?? undefined),
|
|
54
|
+
};
|
|
55
|
+
const instrumentation = {
|
|
56
|
+
pikkuScenarioTakeLiveCoverage: {
|
|
57
|
+
...takeLiveCoverage,
|
|
58
|
+
title: 'Take Live Coverage',
|
|
59
|
+
description: 'Snapshots the live coverage collected since the server started (or since the last reset) — V8 precise coverage on Node, istanbul instrumentation on Bun — and maps it onto function body spans. Returns null unless the server was started with coverage enabled (pikku dev --coverage).',
|
|
60
|
+
},
|
|
61
|
+
pikkuScenarioResetLiveCoverage: {
|
|
62
|
+
...resetLiveCoverage,
|
|
63
|
+
title: 'Reset Live Coverage',
|
|
64
|
+
description: 'Clears coverage call counts so the next takeLiveCoverage snapshot is attributable to a single scenario run. Reports enabled: false when the server was not started with coverage enabled.',
|
|
65
|
+
},
|
|
66
|
+
pikkuScenarioResetStubs: {
|
|
67
|
+
...resetStubs,
|
|
68
|
+
title: 'Reset Stubs',
|
|
69
|
+
description: 'Clears recorded stub calls so the next getStubCalls result is attributable to a single scenario run. Reports enabled: false when the server was not started in test mode.',
|
|
70
|
+
},
|
|
71
|
+
pikkuScenarioGetStubCalls: {
|
|
72
|
+
...getStubCalls,
|
|
73
|
+
title: 'Get Stub Calls',
|
|
74
|
+
description: 'Returns calls recorded against stubbed/spied services (via the stub()/spy() core utils). Empty unless the server records service calls (pikku dev --test).',
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Register the instrumentation into the running dev server's state, so the
|
|
79
|
+
* scenario runner can reach it over `/rpc/<name>` exactly as before.
|
|
80
|
+
*
|
|
81
|
+
* They are registered sessionless: `auth` decides whether a session is
|
|
82
|
+
* *required*, and a sessioned function would demand one regardless of the flag —
|
|
83
|
+
* which is what made `scaffold.scenarios: true` (as opposed to `'auth'`) refuse
|
|
84
|
+
* every call and log a warning about it.
|
|
85
|
+
*/
|
|
86
|
+
export const registerScenarioInstrumentation = (requireAuth) => {
|
|
87
|
+
const meta = pikkuState(null, 'function', 'meta');
|
|
88
|
+
for (const [name, { func, title, description }] of Object.entries(instrumentation)) {
|
|
89
|
+
addFunction(name, { func, auth: requireAuth });
|
|
90
|
+
meta[name] = {
|
|
91
|
+
pikkuFuncId: name,
|
|
92
|
+
inputSchemaName: null,
|
|
93
|
+
outputSchemaName: null,
|
|
94
|
+
expose: true,
|
|
95
|
+
sessionless: true,
|
|
96
|
+
tags: ['pikku'],
|
|
97
|
+
title,
|
|
98
|
+
description,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
pikkuState(null, 'function', 'meta', meta);
|
|
102
|
+
};
|
|
@@ -12,6 +12,12 @@ export type WiringFileMap = Map<string, {
|
|
|
12
12
|
* them in `.pikku/scenarios/`, reachable only from `pikku scenario run`.
|
|
13
13
|
*/
|
|
14
14
|
export declare const isScenarioStep: (meta: FunctionsMeta[string] | undefined) => boolean;
|
|
15
|
+
/**
|
|
16
|
+
* A scenario's own body is a function too, so the app meta used to carry every
|
|
17
|
+
* scenario's name, schemas and hashes — and a deployed bundle imports that meta
|
|
18
|
+
* wholesale. Both kinds belong on the scenario side of the split.
|
|
19
|
+
*/
|
|
20
|
+
export declare const isScenarioFunction: (meta: FunctionsMeta[string] | undefined) => boolean;
|
|
15
21
|
export declare const partitionScenarioFunctions: (files: WiringFileMap, functionsMeta: FunctionsMeta) => {
|
|
16
22
|
app: WiringFileMap;
|
|
17
23
|
scenario: WiringFileMap;
|
|
@@ -27,3 +33,10 @@ export declare const partitionScenarioWorkflows: (workflowNames: string[], workf
|
|
|
27
33
|
appFiles: WiringFileMap;
|
|
28
34
|
scenarioFiles: WiringFileMap;
|
|
29
35
|
};
|
|
36
|
+
/**
|
|
37
|
+
* Drop every scenario and step from a function meta map. The deploy analyzer
|
|
38
|
+
* reads inspector state rather than the partitioned codegen output, so it needs
|
|
39
|
+
* the same split applied before it decides what a deployment contains.
|
|
40
|
+
*/
|
|
41
|
+
export declare const withoutScenarios: (functionsMeta: FunctionsMeta) => FunctionsMeta;
|
|
42
|
+
export declare const withoutScenarioWorkflows: (graphMeta: SerializedWorkflowGraphs) => SerializedWorkflowGraphs;
|
|
@@ -6,11 +6,17 @@
|
|
|
6
6
|
* them in `.pikku/scenarios/`, reachable only from `pikku scenario run`.
|
|
7
7
|
*/
|
|
8
8
|
export const isScenarioStep = (meta) => meta?.scenarioStep === true;
|
|
9
|
+
/**
|
|
10
|
+
* A scenario's own body is a function too, so the app meta used to carry every
|
|
11
|
+
* scenario's name, schemas and hashes — and a deployed bundle imports that meta
|
|
12
|
+
* wholesale. Both kinds belong on the scenario side of the split.
|
|
13
|
+
*/
|
|
14
|
+
export const isScenarioFunction = (meta) => isScenarioStep(meta) || meta?.scenario === true;
|
|
9
15
|
export const partitionScenarioFunctions = (files, functionsMeta) => {
|
|
10
16
|
const app = new Map();
|
|
11
17
|
const scenario = new Map();
|
|
12
18
|
for (const [name, entry] of files) {
|
|
13
|
-
if (
|
|
19
|
+
if (isScenarioFunction(functionsMeta[name])) {
|
|
14
20
|
scenario.set(name, entry);
|
|
15
21
|
}
|
|
16
22
|
else {
|
|
@@ -23,7 +29,7 @@ export const partitionScenarioFunctionsMeta = (functionsMeta) => {
|
|
|
23
29
|
const app = {};
|
|
24
30
|
const scenario = {};
|
|
25
31
|
for (const [name, meta] of Object.entries(functionsMeta)) {
|
|
26
|
-
if (
|
|
32
|
+
if (isScenarioFunction(meta)) {
|
|
27
33
|
scenario[name] = meta;
|
|
28
34
|
}
|
|
29
35
|
else {
|
|
@@ -56,3 +62,10 @@ export const partitionScenarioWorkflows = (workflowNames, workflowFiles, graphMe
|
|
|
56
62
|
}
|
|
57
63
|
return { appNames, scenarioNames, appFiles, scenarioFiles };
|
|
58
64
|
};
|
|
65
|
+
/**
|
|
66
|
+
* Drop every scenario and step from a function meta map. The deploy analyzer
|
|
67
|
+
* reads inspector state rather than the partitioned codegen output, so it needs
|
|
68
|
+
* the same split applied before it decides what a deployment contains.
|
|
69
|
+
*/
|
|
70
|
+
export const withoutScenarios = (functionsMeta) => Object.fromEntries(Object.entries(functionsMeta).filter(([, meta]) => !isScenarioFunction(meta)));
|
|
71
|
+
export const withoutScenarioWorkflows = (graphMeta) => Object.fromEntries(Object.entries(graphMeta ?? {}).filter(([name]) => !isScenarioWorkflow(graphMeta, name)));
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { FunctionsMeta } from '@pikku/core';
|
|
2
|
+
/**
|
|
3
|
+
* The schema names a set of functions validates against.
|
|
4
|
+
*
|
|
5
|
+
* `getUniqueName` is the inspector's own type→schema naming, so this resolves a
|
|
6
|
+
* name the same way `computeRequiredSchemas` did when it filled `requiredSchemas`.
|
|
7
|
+
* The two must not disagree: a scenario schema this failed to name would stay on
|
|
8
|
+
* the app side, which is the leak the split exists to close.
|
|
9
|
+
*/
|
|
10
|
+
export declare const schemaNamesFor: (meta: FunctionsMeta, getUniqueName: (name: string) => string) => Set<string>;
|
|
11
|
+
/**
|
|
12
|
+
* Split the required schemas into the app's register and the scenario register.
|
|
13
|
+
*
|
|
14
|
+
* A scenario's input/output schemas are as test-only as its body, and the app's
|
|
15
|
+
* `register.gen.ts` is imported by every deployed bundle — on one project half the
|
|
16
|
+
* registered schemas belonged to scenarios and steps. Both sets are derived from
|
|
17
|
+
* `requiredSchemas`, so every name lands in exactly one register and a name the
|
|
18
|
+
* scenario side claims is removed from the app side by construction.
|
|
19
|
+
*
|
|
20
|
+
* Two things deliberately stay on the app side: a schema an application function
|
|
21
|
+
* also needs (registering a shared schema twice is worse than registering it in
|
|
22
|
+
* one place), and a type the project asked for by hand via `schemasFromTypes`,
|
|
23
|
+
* whatever happens to reference it.
|
|
24
|
+
*/
|
|
25
|
+
export declare const partitionRequiredSchemas: ({ functionsMeta, requiredSchemas, getUniqueName, schemasFromTypes, }: {
|
|
26
|
+
functionsMeta: FunctionsMeta;
|
|
27
|
+
requiredSchemas: Set<string>;
|
|
28
|
+
getUniqueName: (name: string) => string;
|
|
29
|
+
schemasFromTypes?: string[];
|
|
30
|
+
}) => {
|
|
31
|
+
appRequired: Set<string>;
|
|
32
|
+
scenarioOnly: Set<string>;
|
|
33
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { partitionScenarioFunctionsMeta } from './scenario-partition.js';
|
|
2
|
+
/**
|
|
3
|
+
* The schema names a set of functions validates against.
|
|
4
|
+
*
|
|
5
|
+
* `getUniqueName` is the inspector's own type→schema naming, so this resolves a
|
|
6
|
+
* name the same way `computeRequiredSchemas` did when it filled `requiredSchemas`.
|
|
7
|
+
* The two must not disagree: a scenario schema this failed to name would stay on
|
|
8
|
+
* the app side, which is the leak the split exists to close.
|
|
9
|
+
*/
|
|
10
|
+
export const schemaNamesFor = (meta, getUniqueName) => {
|
|
11
|
+
const names = new Set();
|
|
12
|
+
for (const { inputs, outputs } of Object.values(meta)) {
|
|
13
|
+
for (const type of [inputs?.[0], outputs?.[0]]) {
|
|
14
|
+
if (!type)
|
|
15
|
+
continue;
|
|
16
|
+
try {
|
|
17
|
+
names.add(getUniqueName(type));
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
names.add(type);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return names;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Split the required schemas into the app's register and the scenario register.
|
|
28
|
+
*
|
|
29
|
+
* A scenario's input/output schemas are as test-only as its body, and the app's
|
|
30
|
+
* `register.gen.ts` is imported by every deployed bundle — on one project half the
|
|
31
|
+
* registered schemas belonged to scenarios and steps. Both sets are derived from
|
|
32
|
+
* `requiredSchemas`, so every name lands in exactly one register and a name the
|
|
33
|
+
* scenario side claims is removed from the app side by construction.
|
|
34
|
+
*
|
|
35
|
+
* Two things deliberately stay on the app side: a schema an application function
|
|
36
|
+
* also needs (registering a shared schema twice is worse than registering it in
|
|
37
|
+
* one place), and a type the project asked for by hand via `schemasFromTypes`,
|
|
38
|
+
* whatever happens to reference it.
|
|
39
|
+
*/
|
|
40
|
+
export const partitionRequiredSchemas = ({ functionsMeta, requiredSchemas, getUniqueName, schemasFromTypes, }) => {
|
|
41
|
+
const { app: appFunctionsMeta, scenario: scenarioFunctionsMeta } = partitionScenarioFunctionsMeta(functionsMeta);
|
|
42
|
+
const appSchemaNames = schemaNamesFor(appFunctionsMeta, getUniqueName);
|
|
43
|
+
const requestedByHand = new Set(schemasFromTypes ?? []);
|
|
44
|
+
const scenarioOnly = new Set([...schemaNamesFor(scenarioFunctionsMeta, getUniqueName)].filter((name) => requiredSchemas.has(name) &&
|
|
45
|
+
!appSchemaNames.has(name) &&
|
|
46
|
+
!requestedByHand.has(name)));
|
|
47
|
+
const appRequired = new Set([...requiredSchemas].filter((name) => !scenarioOnly.has(name)));
|
|
48
|
+
return { appRequired, scenarioOnly };
|
|
49
|
+
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from 'fs';
|
|
2
2
|
import { pikkuWorkflowComplexFunc } from '#pikku/workflow/pikku-workflow-types.gen.js';
|
|
3
3
|
import { assertSingleCoreVersion } from '../../utils/assert-single-core-version.js';
|
|
4
|
-
import { pruneLegacyScaffoldFiles } from '../../utils/remove-legacy-scaffold-file.js';
|
|
4
|
+
import { pruneLegacyScaffoldFiles, removeRetiredScaffoldFiles, } from '../../utils/remove-legacy-scaffold-file.js';
|
|
5
5
|
import { PikkuTypecheckFailedError, renderTscFull, renderTscSummary, runProjectTypecheck, } from '../../utils/tsc-check.js';
|
|
6
6
|
const scaffoldFiles = (config) => {
|
|
7
7
|
const files = [];
|
|
@@ -17,11 +17,6 @@ const scaffoldFiles = (config) => {
|
|
|
17
17
|
file: config.consoleFunctionsFile,
|
|
18
18
|
generator: 'pikkuConsoleFunctions',
|
|
19
19
|
});
|
|
20
|
-
if (config.scaffold?.scenarios && config.scenariosFunctionsFile)
|
|
21
|
-
files.push({
|
|
22
|
-
file: config.scenariosFunctionsFile,
|
|
23
|
-
generator: 'pikkuScenarioFunctions',
|
|
24
|
-
});
|
|
25
20
|
if (config.scaffold?.userAdmin && config.userAdminFunctionsFile)
|
|
26
21
|
files.push({
|
|
27
22
|
file: config.userAdminFunctionsFile,
|
|
@@ -46,6 +41,7 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
|
|
|
46
41
|
// (workflows/RPCs "disappear"), so refuse to codegen against it.
|
|
47
42
|
await assertSingleCoreVersion(config.rootDir, logger);
|
|
48
43
|
await pruneLegacyScaffoldFiles(config);
|
|
44
|
+
await removeRetiredScaffoldFiles(config);
|
|
49
45
|
const allImports = [];
|
|
50
46
|
let typesDeclarationFileExists = true;
|
|
51
47
|
if (!existsSync(config.outDir)) {
|
|
@@ -150,7 +146,6 @@ export const allWorkflow = pikkuWorkflowComplexFunc({
|
|
|
150
146
|
workflow.do('Graph wirings', 'pikkuGraphWirings', null),
|
|
151
147
|
workflow.do('Public RPC', 'pikkuPublicRPC', null),
|
|
152
148
|
workflow.do('Console functions', 'pikkuConsoleFunctions', null),
|
|
153
|
-
workflow.do('Scenario functions', 'pikkuScenarioFunctions', null),
|
|
154
149
|
workflow.do('User admin functions', 'pikkuUserAdminFunctions', null),
|
|
155
150
|
workflow.do('Events scaffold', 'pikkuEventsScaffold', null),
|
|
156
151
|
workflow.do('Emails', 'pikkuEmails', null),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { dirname, join } from 'path';
|
|
2
|
-
import { mkdir, readFile, writeFile, rm } from 'fs/promises';
|
|
2
|
+
import { mkdir, readFile, rename, writeFile, rm } from 'fs/promises';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { getCLIVersion } from './get-cli-version.js';
|
|
5
5
|
export const DO_NOT_MODIFY_COMMENT = `/**
|
|
@@ -36,7 +36,19 @@ export const writeFileInDir = async (logger, path, content, { ignoreModifyCommen
|
|
|
36
36
|
// Only write if content has changed or file doesn't exist
|
|
37
37
|
if (existingContent !== content) {
|
|
38
38
|
await mkdir(dirname(path), { recursive: true });
|
|
39
|
-
|
|
39
|
+
// Atomically, because generated files are read while they are generated:
|
|
40
|
+
// `pikku scenario run --spawn` bundles the registers while the dev server it
|
|
41
|
+
// spawned is regenerating them, and `writeFile` truncates before it writes —
|
|
42
|
+
// a reader that lands in that window gets an empty file, not an old one.
|
|
43
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
44
|
+
try {
|
|
45
|
+
await writeFile(tmp, content, 'utf-8');
|
|
46
|
+
await rename(tmp, path);
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
await rm(tmp, { force: true }).catch(() => { });
|
|
50
|
+
throw e;
|
|
51
|
+
}
|
|
40
52
|
if (TS_FILE_REGEX.test(path)) {
|
|
41
53
|
tsWriteGeneration++;
|
|
42
54
|
}
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { PikkuCLIConfig } from '../../types/config.js';
|
|
2
2
|
import type { CLILogger } from '../services/cli-logger.service.js';
|
|
3
3
|
export declare const getPikkuCLIConfig: (logger: CLILogger, configFile: string | undefined, requiredFields: Array<keyof PikkuCLIConfig>, exitProcess?: boolean, outDirOverride?: string) => Promise<PikkuCLIConfig>;
|
|
4
|
+
/** A config that was found and parsed, but says something impossible. */
|
|
5
|
+
export declare class PikkuCLIConfigError extends Error {
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* The two schema registers must be two directories.
|
|
9
|
+
*
|
|
10
|
+
* `saveSchemas` owns whatever directory it is given: it writes `register.gen.ts`
|
|
11
|
+
* and prunes every schema file that its own required-set does not name. Point the
|
|
12
|
+
* scenario write at `schemaDirectory` and the second call overwrites the app's
|
|
13
|
+
* register with the scenario-only one and deletes the app's schema files — the
|
|
14
|
+
* exact leak the split exists to prevent, silently, and only in a deployed
|
|
15
|
+
* bundle. Nothing downstream can detect it, so it is rejected here.
|
|
16
|
+
*/
|
|
17
|
+
export declare const assertSchemaDirectoriesAreDistinct: (config: Pick<PikkuCLIConfig, "schemaDirectory" | "scenarioSchemaDirectory">) => void;
|
|
4
18
|
export declare const validateCLIConfig: (cliConfig: PikkuCLIConfig, required: Array<keyof PikkuCLIConfig>) => void;
|
|
@@ -32,6 +32,28 @@ async function findConfigFile() {
|
|
|
32
32
|
}
|
|
33
33
|
throw new Error('Config file pikku.config.json not found');
|
|
34
34
|
}
|
|
35
|
+
/** A config that was found and parsed, but says something impossible. */
|
|
36
|
+
export class PikkuCLIConfigError extends Error {
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The two schema registers must be two directories.
|
|
40
|
+
*
|
|
41
|
+
* `saveSchemas` owns whatever directory it is given: it writes `register.gen.ts`
|
|
42
|
+
* and prunes every schema file that its own required-set does not name. Point the
|
|
43
|
+
* scenario write at `schemaDirectory` and the second call overwrites the app's
|
|
44
|
+
* register with the scenario-only one and deletes the app's schema files — the
|
|
45
|
+
* exact leak the split exists to prevent, silently, and only in a deployed
|
|
46
|
+
* bundle. Nothing downstream can detect it, so it is rejected here.
|
|
47
|
+
*/
|
|
48
|
+
export const assertSchemaDirectoriesAreDistinct = (config) => {
|
|
49
|
+
const { schemaDirectory, scenarioSchemaDirectory } = config;
|
|
50
|
+
if (!schemaDirectory || !scenarioSchemaDirectory) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (resolve(schemaDirectory) === resolve(scenarioSchemaDirectory)) {
|
|
54
|
+
throw new PikkuCLIConfigError(`scenarioSchemaDirectory must not be the same directory as schemaDirectory (both resolve to ${resolve(schemaDirectory)}). Scenario schemas are written with their own register, which would replace the application one.`);
|
|
55
|
+
}
|
|
56
|
+
};
|
|
35
57
|
const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields, exitProcess = false, outDirOverride) => {
|
|
36
58
|
if (!configFile) {
|
|
37
59
|
configFile = await findConfigFile();
|
|
@@ -374,6 +396,12 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
374
396
|
if (!result.scenarioMetaDir) {
|
|
375
397
|
result.scenarioMetaDir = join(scenarioDir, 'meta');
|
|
376
398
|
}
|
|
399
|
+
// Schemas only a scenario or a step validates against. They live here rather
|
|
400
|
+
// than in `schemaDirectory` because the app's `register.gen.ts` is imported
|
|
401
|
+
// by every deployed bundle, and a test-only schema has no business in one.
|
|
402
|
+
if (!result.scenarioSchemaDirectory) {
|
|
403
|
+
result.scenarioSchemaDirectory = join(scenarioDir, 'schemas');
|
|
404
|
+
}
|
|
377
405
|
if (!result.scenarioBootstrapFile) {
|
|
378
406
|
result.scenarioBootstrapFile = join(result.outDir, 'pikku-bootstrap-scenarios.gen.ts');
|
|
379
407
|
}
|
|
@@ -549,6 +577,7 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
549
577
|
if (!isAbsolute(result.tsconfig)) {
|
|
550
578
|
result.tsconfig = join(result.rootDir, result.tsconfig);
|
|
551
579
|
}
|
|
580
|
+
assertSchemaDirectoriesAreDistinct(result);
|
|
552
581
|
if (result.addon) {
|
|
553
582
|
const packageJsonPath = join(result.rootDir, 'package.json');
|
|
554
583
|
try {
|
|
@@ -568,6 +597,11 @@ const _getPikkuCLIConfig = async (logger, configFile = undefined, requiredFields
|
|
|
568
597
|
return result;
|
|
569
598
|
}
|
|
570
599
|
catch (e) {
|
|
600
|
+
// A config that parsed but is contradictory is not a missing config, and
|
|
601
|
+
// saying so sends the reader looking for the wrong problem.
|
|
602
|
+
if (e instanceof PikkuCLIConfigError) {
|
|
603
|
+
throw e;
|
|
604
|
+
}
|
|
571
605
|
logger.error(e);
|
|
572
606
|
throw new Error(`Config file not found: ${configFile}`);
|
|
573
607
|
}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
import type { PikkuCLIConfig } from '../../types/config.js';
|
|
2
2
|
export declare const removeLegacyScaffoldFile: (file: string) => Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Delete scaffolds pikku has stopped generating.
|
|
5
|
+
*
|
|
6
|
+
* The scenario instrumentation scaffold is why this exists: those four functions
|
|
7
|
+
* are pikku's own and are now registered by `pikku dev` itself, because as project
|
|
8
|
+
* source they were indistinguishable from application code and shipped —
|
|
9
|
+
* `expose: true` — in every deployed bundle. The inspector ignores them by name so
|
|
10
|
+
* an unregenerated project is already safe, but the file is then dead code that
|
|
11
|
+
* still imports `zod`, so it goes.
|
|
12
|
+
*
|
|
13
|
+
* Unlike `pruneLegacyScaffoldFiles`, which removes a file from a path pikku used
|
|
14
|
+
* to write to, this removes a file pikku no longer writes at all.
|
|
15
|
+
*/
|
|
16
|
+
export declare const removeRetiredScaffoldFiles: (config: PikkuCLIConfig) => Promise<void>;
|
|
3
17
|
export declare const pruneLegacyScaffoldFiles: (config: PikkuCLIConfig) => Promise<void>;
|
|
@@ -34,6 +34,29 @@ export const removeLegacyScaffoldFile = async (file) => {
|
|
|
34
34
|
await rm(legacy, { force: true });
|
|
35
35
|
}
|
|
36
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Delete scaffolds pikku has stopped generating.
|
|
39
|
+
*
|
|
40
|
+
* The scenario instrumentation scaffold is why this exists: those four functions
|
|
41
|
+
* are pikku's own and are now registered by `pikku dev` itself, because as project
|
|
42
|
+
* source they were indistinguishable from application code and shipped —
|
|
43
|
+
* `expose: true` — in every deployed bundle. The inspector ignores them by name so
|
|
44
|
+
* an unregenerated project is already safe, but the file is then dead code that
|
|
45
|
+
* still imports `zod`, so it goes.
|
|
46
|
+
*
|
|
47
|
+
* Unlike `pruneLegacyScaffoldFiles`, which removes a file from a path pikku used
|
|
48
|
+
* to write to, this removes a file pikku no longer writes at all.
|
|
49
|
+
*/
|
|
50
|
+
export const removeRetiredScaffoldFiles = async (config) => {
|
|
51
|
+
for (const file of [
|
|
52
|
+
config.scenariosFunctionsFile,
|
|
53
|
+
config.scenariosSchemasFile,
|
|
54
|
+
]) {
|
|
55
|
+
if (file && existsSync(file)) {
|
|
56
|
+
await rm(file, { force: true });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
37
60
|
export const pruneLegacyScaffoldFiles = async (config) => {
|
|
38
61
|
for (const file of scaffoldFiles(config)) {
|
|
39
62
|
if (file) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { JSONValue } from '@pikku/core';
|
|
2
2
|
import type { CLILogger } from '../services/cli-logger.service.js';
|
|
3
|
-
export declare function saveSchemas(logger: CLILogger, schemaParentDir: string, schemas: Record<string, JSONValue>, requiredSchemas: Set<string>, supportsImportAttributes: boolean, packageName?: string | null
|
|
3
|
+
export declare function saveSchemas(logger: CLILogger, schemaParentDir: string, schemas: Record<string, JSONValue>, requiredSchemas: Set<string>, supportsImportAttributes: boolean, packageName?: string | null,
|
|
4
|
+
/** Names the set in log output — there is more than one now (app, scenario). */
|
|
5
|
+
label?: string): Promise<void>;
|
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
import { writeFileInDir } from './file-writer.js';
|
|
2
|
-
import { mkdir, readdir, unlink, writeFile } from 'fs/promises';
|
|
2
|
+
import { mkdir, readdir, readFile, rename, unlink, writeFile, } from 'fs/promises';
|
|
3
3
|
const SCHEMA_FILE_SUFFIX = '.schema.json';
|
|
4
|
+
/**
|
|
5
|
+
* Write a schema file without ever exposing a half-written one.
|
|
6
|
+
*
|
|
7
|
+
* `writeFile` truncates and then writes, so a reader that opens the path in
|
|
8
|
+
* between sees an empty file. That reader is real: `pikku scenario run --spawn`
|
|
9
|
+
* bundles the register while the dev server it just spawned is running codegen,
|
|
10
|
+
* and esbuild fails the whole run with "Unexpected end of file in JSON" on
|
|
11
|
+
* whichever schema it happened to catch mid-write. A rename is atomic, so a
|
|
12
|
+
* reader gets either the previous complete file or the new one.
|
|
13
|
+
*
|
|
14
|
+
* Unchanged content is not rewritten at all. Most runs regenerate hundreds of
|
|
15
|
+
* byte-identical schemas, and every one of those is a window that does not need
|
|
16
|
+
* to exist — it also stops file watchers waking on output that did not change.
|
|
17
|
+
*/
|
|
18
|
+
async function writeSchemaFile(path, contents) {
|
|
19
|
+
try {
|
|
20
|
+
if ((await readFile(path, 'utf-8')) === contents) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Unreadable or absent — fall through and write it.
|
|
26
|
+
}
|
|
27
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
28
|
+
try {
|
|
29
|
+
await writeFile(tmp, contents, 'utf-8');
|
|
30
|
+
await rename(tmp, path);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
await unlink(tmp).catch(() => { });
|
|
34
|
+
throw e;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
4
37
|
function toValidIdentifier(name) {
|
|
5
38
|
let result = name.replace(/[-./: ]/g, '_');
|
|
6
39
|
if (/^\d/.test(result)) {
|
|
@@ -55,19 +88,21 @@ async function pruneOrphanedSchemaFiles(logger, schemaParentDir, keep) {
|
|
|
55
88
|
logger.info(`• Removed ${orphans.length} stale schema file(s).\x1b[0m`);
|
|
56
89
|
}
|
|
57
90
|
}
|
|
58
|
-
export async function saveSchemas(logger, schemaParentDir, schemas, requiredSchemas, supportsImportAttributes, packageName
|
|
91
|
+
export async function saveSchemas(logger, schemaParentDir, schemas, requiredSchemas, supportsImportAttributes, packageName,
|
|
92
|
+
/** Names the set in log output — there is more than one now (app, scenario). */
|
|
93
|
+
label = 'schemas') {
|
|
59
94
|
if (requiredSchemas.size === 0) {
|
|
60
95
|
await writeFileInDir(logger, `${schemaParentDir}/register.gen.ts`, 'export const empty = null;');
|
|
61
96
|
// Every file in schemas/ is now an orphan: register.gen.ts registers nothing, so
|
|
62
97
|
// leaving them would be exactly the misleading state this prune exists to prevent.
|
|
63
98
|
await pruneOrphanedSchemaFiles(logger, schemaParentDir, new Set());
|
|
64
|
-
logger.info(`• Skipping
|
|
99
|
+
logger.info(`• Skipping ${label} since none found.\x1b[0m`);
|
|
65
100
|
return;
|
|
66
101
|
}
|
|
67
102
|
await mkdir(`${schemaParentDir}/schemas`, { recursive: true });
|
|
68
103
|
await Promise.all(Object.entries(schemas).map(async ([schemaName, schema]) => {
|
|
69
104
|
if (requiredSchemas.has(schemaName)) {
|
|
70
|
-
await
|
|
105
|
+
await writeSchemaFile(`${schemaParentDir}/schemas/${schemaName}.schema.json`, JSON.stringify(schema));
|
|
71
106
|
}
|
|
72
107
|
}));
|
|
73
108
|
// Sort so register.gen.ts is byte-identical across runs — requiredSchemas is
|