@pikku/cli 0.12.89 → 0.12.91

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.
Files changed (162) hide show
  1. package/README.md +25 -2
  2. package/console-app/assets/{index-BaOGIr2O.js → index-CpkvmAGb.js} +148 -148
  3. package/console-app/assets/{index-CSzCJzBb.css → index-D0HG8q0B.css} +1 -1
  4. package/console-app/index.html +2 -2
  5. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  7. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-channel.js +21 -1
  9. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  10. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  13. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +6 -1
  14. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  15. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  17. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +52 -1
  18. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  19. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  20. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  21. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  22. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  23. package/dist/.pikku/function/pikku-function-types.gen.d.ts +4 -4
  24. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  25. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  26. package/dist/.pikku/function/pikku-functions-meta.gen.json +73 -13
  27. package/dist/.pikku/function/pikku-functions.gen.js +9 -1
  28. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  29. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  30. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  31. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  32. package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +8 -0
  33. package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +8 -0
  34. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  35. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  36. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  37. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  38. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  40. package/dist/.pikku/pikku-types.gen.js +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  42. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  45. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  46. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  47. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +4 -0
  48. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.d.ts +1 -0
  49. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +10 -0
  50. package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.json +1 -0
  51. package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +4 -0
  52. package/dist/.pikku/scenarios/pikku-scenario-functions.gen.js +1 -0
  53. package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.d.ts +1 -0
  54. package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +10 -0
  55. package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +4 -0
  56. package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.js +1 -0
  57. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  58. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  59. package/dist/.pikku/schemas/register.gen.js +9 -1
  60. package/dist/.pikku/schemas/schemas/DbBaselineInput.schema.json +1 -0
  61. package/dist/.pikku/schemas/schemas/DbCheckInput.schema.json +1 -0
  62. package/dist/.pikku/schemas/schemas/DbCodegenInput.schema.json +1 -0
  63. package/dist/.pikku/schemas/schemas/DbExportInput.schema.json +1 -0
  64. package/dist/.pikku/schemas/schemas/FabricValidateInput.schema.json +1 -1
  65. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  66. package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +1 -1
  67. package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +1 -1
  68. package/dist/.pikku/scopes/pikku-scope-types.gen.js +1 -1
  69. package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
  70. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  71. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  72. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  73. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  74. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  75. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  76. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  77. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  78. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  79. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  80. package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +19 -0
  81. package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +17 -0
  82. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +152 -3
  83. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +39 -1
  84. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  85. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  86. package/dist/bin/pikku-bin.mjs +2 -2
  87. package/dist/src/cli.wiring.js +42 -0
  88. package/dist/src/fabric/fabric-commands.d.ts +9 -3
  89. package/dist/src/fabric/fabric-commands.js +6 -0
  90. package/dist/src/fabric/functions/validate.function.d.ts +15 -5
  91. package/dist/src/fabric/functions/validate.function.js +69 -16
  92. package/dist/src/fabric/lib/config.d.ts +11 -1
  93. package/dist/src/fabric/lib/config.js +39 -1
  94. package/dist/src/fabric/lib/frontend-typecheck.d.ts +46 -0
  95. package/dist/src/fabric/lib/frontend-typecheck.js +139 -0
  96. package/dist/src/functions/commands/db-baseline.d.ts +13 -0
  97. package/dist/src/functions/commands/db-baseline.js +48 -0
  98. package/dist/src/functions/commands/db-check.d.ts +12 -0
  99. package/dist/src/functions/commands/db-check.js +64 -0
  100. package/dist/src/functions/commands/db-codegen.d.ts +11 -0
  101. package/dist/src/functions/commands/db-codegen.js +39 -0
  102. package/dist/src/functions/commands/db-export.d.ts +12 -0
  103. package/dist/src/functions/commands/db-export.js +31 -0
  104. package/dist/src/functions/commands/db-generate.js +19 -26
  105. package/dist/src/functions/commands/dev.js +2 -0
  106. package/dist/src/functions/commands/load-user-project.d.ts +7 -0
  107. package/dist/src/functions/commands/load-user-project.js +21 -0
  108. package/dist/src/functions/commands/pikku-command-bootstrap.js +12 -0
  109. package/dist/src/functions/commands/scenario-browser.d.ts +69 -0
  110. package/dist/src/functions/commands/scenario-browser.js +60 -0
  111. package/dist/src/functions/commands/scenario-formatter.d.ts +81 -0
  112. package/dist/src/functions/commands/scenario-formatter.js +120 -0
  113. package/dist/src/functions/commands/scenario-ladder.d.ts +66 -0
  114. package/dist/src/functions/commands/scenario-ladder.js +132 -0
  115. package/dist/src/functions/commands/scenario-plan.d.ts +61 -0
  116. package/dist/src/functions/commands/scenario-plan.js +106 -0
  117. package/dist/src/functions/commands/scenario.d.ts +18 -0
  118. package/dist/src/functions/commands/scenario.js +305 -38
  119. package/dist/src/functions/commands/serve.js +2 -0
  120. package/dist/src/functions/db/db-migrator.d.ts +35 -4
  121. package/dist/src/functions/db/db-migrator.js +67 -10
  122. package/dist/src/functions/db/local-db.d.ts +235 -13
  123. package/dist/src/functions/db/local-db.js +430 -45
  124. package/dist/src/functions/db/postgres/postgres-introspector.js +19 -1
  125. package/dist/src/functions/db/postgres/postgres-migrator.d.ts +1 -0
  126. package/dist/src/functions/db/postgres/postgres-migrator.js +4 -1
  127. package/dist/src/functions/db/sqlite/sqlite-introspector.js +2 -1
  128. package/dist/src/functions/db/sqlite/sqlite-migrator.d.ts +1 -0
  129. package/dist/src/functions/db/sqlite/sqlite-migrator.js +6 -1
  130. package/dist/src/functions/wirings/functions/pikku-command-functions.js +26 -8
  131. package/dist/src/functions/wirings/functions/serialize-function-types.js +3 -3
  132. package/dist/src/functions/wirings/scenarios/scenario-partition.d.ts +29 -0
  133. package/dist/src/functions/wirings/scenarios/scenario-partition.js +58 -0
  134. package/dist/src/functions/wirings/scenarios/serialize-feature-meta.d.ts +14 -0
  135. package/dist/src/functions/wirings/scenarios/serialize-feature-meta.js +29 -0
  136. package/dist/src/functions/wirings/scenarios/serialize-scenario-meta.d.ts +12 -0
  137. package/dist/src/functions/wirings/scenarios/serialize-scenario-meta.js +56 -0
  138. package/dist/src/functions/wirings/scenarios/serialize-scenario-registration.d.ts +7 -0
  139. package/dist/src/functions/wirings/scenarios/serialize-scenario-registration.js +40 -0
  140. package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +43 -8
  141. package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +1 -1
  142. package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +13 -2
  143. package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.d.ts +4 -0
  144. package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +62 -0
  145. package/dist/src/functions/wirings/workflow/serialize-workflow-bootstrap-map.js +10 -3
  146. package/dist/src/functions/wirings/workflow/serialize-workflow-map.js +10 -3
  147. package/dist/src/functions/wirings/workflow/serialize-workflow-meta.js +4 -4
  148. package/dist/src/functions/wirings/workflow/serialize-workflow-types.d.ts +1 -1
  149. package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +232 -3
  150. package/dist/src/server/server-ready.d.ts +12 -0
  151. package/dist/src/server/server-ready.js +12 -0
  152. package/dist/src/server/spawn-dev-server.d.ts +50 -0
  153. package/dist/src/server/spawn-dev-server.js +112 -0
  154. package/dist/src/services.js +12 -1
  155. package/dist/src/utils/meta-diff.js +5 -2
  156. package/dist/src/utils/pikku-cli-config.js +26 -0
  157. package/dist/src/utils/resolve-scenario-actors.d.ts +23 -0
  158. package/dist/src/utils/resolve-scenario-actors.js +74 -0
  159. package/dist/tsconfig.tsbuildinfo +1 -1
  160. package/package.json +16 -6
  161. package/skills/pikku-i18n/SKILL.md +53 -6
  162. package/skills/pikku-scenario/SKILL.md +196 -29
@@ -4,19 +4,22 @@ import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-
4
4
  import { serializeFunctionImports } from './serialize-function-imports.js';
5
5
  import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
6
6
  import { stripVerboseFields, hasVerboseFields, reattachFunctionServices, } from '../../../utils/strip-verbose-meta.js';
7
+ import { partitionScenarioFunctions, partitionScenarioFunctionsMeta, } from '../scenarios/scenario-partition.js';
8
+ import { serializeScenarioFunctionMeta } from '../scenarios/serialize-scenario-meta.js';
7
9
  export const pikkuFunctions = pikkuSessionlessFunc({
8
10
  func: async ({ logger, config, getInspectorState }) => {
9
11
  const { functions, rpc } = await getInspectorState();
10
- const { functionsMetaFile, functionsMetaJsonFile, functionsFile, packageMappings, schema, } = config;
11
- let minimalMeta = stripVerboseFields(functions.meta);
12
+ const { functionsMetaFile, functionsMetaJsonFile, functionsFile, scenarioStepsFile, scenarioStepsMetaFile, scenarioStepsMetaJsonFile, packageMappings, schema, } = config;
13
+ const { app: appFunctionsMeta, scenario: scenarioFunctionsMeta } = partitionScenarioFunctionsMeta(functions.meta);
14
+ let minimalMeta = stripVerboseFields(appFunctionsMeta);
12
15
  if (config.addonName) {
13
- minimalMeta = reattachFunctionServices(minimalMeta, functions.meta);
16
+ minimalMeta = reattachFunctionServices(minimalMeta, appFunctionsMeta);
14
17
  }
15
18
  await writeFileInDir(logger, functionsMetaJsonFile, JSON.stringify(minimalMeta, null, 2));
16
19
  // Write verbose JSON only if it has additional fields
17
- if (hasVerboseFields(functions.meta)) {
20
+ if (hasVerboseFields(appFunctionsMeta)) {
18
21
  const verbosePath = functionsMetaJsonFile.replace(/\.gen\.json$/, '-verbose.gen.json');
19
- await writeFileInDir(logger, verbosePath, JSON.stringify(functions.meta, null, 2));
22
+ await writeFileInDir(logger, verbosePath, JSON.stringify(appFunctionsMeta, null, 2));
20
23
  }
21
24
  const jsonImportPath = getFileImportRelativePath(functionsMetaFile, functionsMetaJsonFile, packageMappings);
22
25
  const supportsImportAttributes = schema?.supportsImportAttributes ?? false;
@@ -31,11 +34,26 @@ export const pikkuFunctions = pikkuSessionlessFunc({
31
34
  const hasRPCs = rpc.exposedFiles.size > 0 || rpc.internalFiles.size > 0;
32
35
  const hasFunctions = functions.files.size > 0;
33
36
  const shouldGenerateFunctionsFile = isAddon ? hasFunctions : hasRPCs;
37
+ // For addon packages, use all function files; for main packages, use internal RPC files
38
+ const filesToRegister = isAddon ? functions.files : rpc.internalFiles;
39
+ const { app: appFiles, scenario: scenarioFiles } = partitionScenarioFunctions(filesToRegister, functions.meta);
34
40
  if (shouldGenerateFunctionsFile) {
35
- // For addon packages, use all function files; for main packages, use internal RPC files
36
- const filesToRegister = isAddon ? functions.files : rpc.internalFiles;
37
- await writeFileInDir(logger, functionsFile, serializeFunctionImports(functionsFile, filesToRegister, functions.meta, packageMappings, config.addonName));
41
+ await writeFileInDir(logger, functionsFile, serializeFunctionImports(functionsFile, appFiles, appFunctionsMeta, packageMappings, config.addonName));
38
42
  }
43
+ // Always written, even when empty: a project that deletes its last scenario
44
+ // must stop registering the one it had, and the scenario bootstrap imports
45
+ // these unconditionally.
46
+ await writeFileInDir(logger, scenarioStepsMetaJsonFile, JSON.stringify(stripVerboseFields(scenarioFunctionsMeta), null, 2));
47
+ // The bootstrap imports the stripped copy; the verbose one is read off disk
48
+ // by whatever documents a scenario — the console needs `sourceFile` and
49
+ // `exportedName` to show the code a step runs.
50
+ if (hasVerboseFields(scenarioFunctionsMeta)) {
51
+ await writeFileInDir(logger, scenarioStepsMetaJsonFile.replace(/\.gen\.json$/, '-verbose.gen.json'), JSON.stringify(scenarioFunctionsMeta, null, 2));
52
+ }
53
+ await writeFileInDir(logger, scenarioStepsMetaFile, serializeScenarioFunctionMeta(getFileImportRelativePath(scenarioStepsMetaFile, scenarioStepsMetaJsonFile, packageMappings), getFileImportRelativePath(scenarioStepsMetaFile, functionsMetaFile, packageMappings), supportsImportAttributes, config.addonName));
54
+ await writeFileInDir(logger, scenarioStepsFile, scenarioFiles.size === 0
55
+ ? 'export {}'
56
+ : serializeFunctionImports(scenarioStepsFile, scenarioFiles, scenarioFunctionsMeta, packageMappings, config.addonName));
39
57
  return shouldGenerateFunctionsFile;
40
58
  },
41
59
  middleware: [
@@ -7,7 +7,7 @@ export const serializeFunctionTypes = (userSessionTypeImport, userSessionTypeNam
7
7
  ? nodeCategories.map((c) => `'${c}'`).join(' | ')
8
8
  : 'string';
9
9
  const workflowImport = workflowTypesImport ||
10
- `import type { TypedWorkflow, TypedScenario } from '../workflow/pikku-workflow-types.gen.js'`;
10
+ `import type { TypedWorkflow, TypedScenario, TypedScenarioActors } from '../workflow/pikku-workflow-types.gen.js'`;
11
11
  // Falls back to `string` when a project has no scopes codegen, so that
12
12
  // `scopes` stays usable rather than resolving to an unbound type name.
13
13
  const scopesImport = scopesTypeImport || `type ScopeId = string`;
@@ -299,7 +299,7 @@ export type PikkuFunctionSessionless<
299
299
  Out,
300
300
  RequiredServices,
301
301
  Session,
302
- PickRequired<PikkuWire<In, Out, false, Session, TypedPikkuRPC, null, any, TypedWorkflow, unknown, TypedScenario>, RequiredWires>
302
+ PickRequired<PikkuWire<In, Out, false, Session, TypedPikkuRPC, null, any, TypedWorkflow, unknown, TypedScenario, TypedScenarioActors>, RequiredWires>
303
303
  >
304
304
 
305
305
  /**
@@ -320,7 +320,7 @@ export type PikkuFunction<
320
320
  Out,
321
321
  RequiredServices,
322
322
  Session,
323
- PickRequired<PikkuWire<In, Out, true, Session, TypedPikkuRPC, null, any, TypedWorkflow, unknown, TypedScenario>, RequiredWires>
323
+ PickRequired<PikkuWire<In, Out, true, Session, TypedPikkuRPC, null, any, TypedWorkflow, unknown, TypedScenario, TypedScenarioActors>, RequiredWires>
324
324
  >
325
325
 
326
326
  /**
@@ -0,0 +1,29 @@
1
+ import type { FunctionsMeta } from '@pikku/core';
2
+ import type { SerializedWorkflowGraphs } from '@pikku/inspector/workflow-graph';
3
+ export type WiringFileMap = Map<string, {
4
+ path: string;
5
+ exportedName: string;
6
+ }>;
7
+ /**
8
+ * Scenario steps and scenarios are ordinary pikku functions and workflows, so
9
+ * without a split they register into the same bootstrap a deployed server
10
+ * imports — dragging every step body (and whatever it imports: Playwright, test
11
+ * fixtures, assertion helpers) into production. These partitions are what keep
12
+ * them in `.pikku/scenarios/`, reachable only from `pikku scenario run`.
13
+ */
14
+ export declare const isScenarioStep: (meta: FunctionsMeta[string] | undefined) => boolean;
15
+ export declare const partitionScenarioFunctions: (files: WiringFileMap, functionsMeta: FunctionsMeta) => {
16
+ app: WiringFileMap;
17
+ scenario: WiringFileMap;
18
+ };
19
+ export declare const partitionScenarioFunctionsMeta: (functionsMeta: FunctionsMeta) => {
20
+ app: FunctionsMeta;
21
+ scenario: FunctionsMeta;
22
+ };
23
+ export declare const isScenarioWorkflow: (graphMeta: SerializedWorkflowGraphs, name: string) => boolean;
24
+ export declare const partitionScenarioWorkflows: (workflowNames: string[], workflowFiles: WiringFileMap, graphMeta: SerializedWorkflowGraphs) => {
25
+ appNames: string[];
26
+ scenarioNames: string[];
27
+ appFiles: WiringFileMap;
28
+ scenarioFiles: WiringFileMap;
29
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Scenario steps and scenarios are ordinary pikku functions and workflows, so
3
+ * without a split they register into the same bootstrap a deployed server
4
+ * imports — dragging every step body (and whatever it imports: Playwright, test
5
+ * fixtures, assertion helpers) into production. These partitions are what keep
6
+ * them in `.pikku/scenarios/`, reachable only from `pikku scenario run`.
7
+ */
8
+ export const isScenarioStep = (meta) => meta?.scenarioStep === true;
9
+ export const partitionScenarioFunctions = (files, functionsMeta) => {
10
+ const app = new Map();
11
+ const scenario = new Map();
12
+ for (const [name, entry] of files) {
13
+ if (isScenarioStep(functionsMeta[name])) {
14
+ scenario.set(name, entry);
15
+ }
16
+ else {
17
+ app.set(name, entry);
18
+ }
19
+ }
20
+ return { app, scenario };
21
+ };
22
+ export const partitionScenarioFunctionsMeta = (functionsMeta) => {
23
+ const app = {};
24
+ const scenario = {};
25
+ for (const [name, meta] of Object.entries(functionsMeta)) {
26
+ if (isScenarioStep(meta)) {
27
+ scenario[name] = meta;
28
+ }
29
+ else {
30
+ app[name] = meta;
31
+ }
32
+ }
33
+ return { app, scenario };
34
+ };
35
+ export const isScenarioWorkflow = (graphMeta, name) => graphMeta[name]?.source === 'scenario';
36
+ export const partitionScenarioWorkflows = (workflowNames, workflowFiles, graphMeta) => {
37
+ const appNames = [];
38
+ const scenarioNames = [];
39
+ for (const name of workflowNames) {
40
+ if (isScenarioWorkflow(graphMeta, name)) {
41
+ scenarioNames.push(name);
42
+ }
43
+ else {
44
+ appNames.push(name);
45
+ }
46
+ }
47
+ const appFiles = new Map();
48
+ const scenarioFiles = new Map();
49
+ for (const [name, entry] of workflowFiles) {
50
+ if (isScenarioWorkflow(graphMeta, name)) {
51
+ scenarioFiles.set(name, entry);
52
+ }
53
+ else {
54
+ appFiles.set(name, entry);
55
+ }
56
+ }
57
+ return { appNames, scenarioNames, appFiles, scenarioFiles };
58
+ };
@@ -0,0 +1,14 @@
1
+ import type { FeaturesMeta } from '@pikku/core/workflow/types';
2
+ import type { InspectorFeature } from '@pikku/inspector';
3
+ /**
4
+ * The feature meta the console reads, built from what the inspector could read
5
+ * off the source.
6
+ *
7
+ * Entry order is the declared order and is deliberately not sorted — a
8
+ * feature's scenarios read top to bottom the way they were written, the same
9
+ * way a gherkin Feature file does.
10
+ *
11
+ * The source path is dropped: it is absolute to the machine that generated the
12
+ * meta, and nothing rendering a feature needs it.
13
+ */
14
+ export declare const buildFeaturesMeta: (featureFiles: Map<string, InspectorFeature>) => FeaturesMeta;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * The feature meta the console reads, built from what the inspector could read
3
+ * off the source.
4
+ *
5
+ * Entry order is the declared order and is deliberately not sorted — a
6
+ * feature's scenarios read top to bottom the way they were written, the same
7
+ * way a gherkin Feature file does.
8
+ *
9
+ * The source path is dropped: it is absolute to the machine that generated the
10
+ * meta, and nothing rendering a feature needs it.
11
+ */
12
+ export const buildFeaturesMeta = (featureFiles) => {
13
+ const meta = {};
14
+ for (const [id, feature] of featureFiles) {
15
+ meta[id] = {
16
+ id,
17
+ name: feature.name ?? id,
18
+ ...(feature.description ? { description: feature.description } : {}),
19
+ tags: feature.tags ?? [],
20
+ entries: feature.entries.map((entry) => entry.data === undefined
21
+ ? { scenario: entry.scenario }
22
+ : { scenario: entry.scenario, data: entry.data }),
23
+ unresolvedEntries: feature.unresolvedEntries,
24
+ hasBefore: feature.hasBefore,
25
+ hasAfter: feature.hasAfter,
26
+ };
27
+ }
28
+ return meta;
29
+ };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Scenario workflow meta, merged onto whatever the app registered rather than
3
+ * replacing it — `pikkuState(…, 'workflows', 'meta', value)` is a wholesale
4
+ * setter, and the app's own meta file is one. The app file is imported here so
5
+ * the ordering holds however this module is reached, not only through the
6
+ * generated scenario bootstrap.
7
+ */
8
+ export declare const serializeScenarioWorkflowMeta: (outputPath: string, metaDir: string, appMetaImportPath: string, scenarioNames: string[], packageMappings: Record<string, string>, supportsImportAttributes: boolean, packageName?: string) => string;
9
+ /**
10
+ * Scenario step meta, merged onto the app's function meta for the same reason.
11
+ */
12
+ export declare const serializeScenarioFunctionMeta: (jsonImportPath: string, appMetaImportPath: string, supportsImportAttributes: boolean, packageName?: string) => string;
@@ -0,0 +1,56 @@
1
+ import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
2
+ import { sanitizeTypeName } from '@pikku/inspector';
3
+ /**
4
+ * Scenario workflow meta, merged onto whatever the app registered rather than
5
+ * replacing it — `pikkuState(…, 'workflows', 'meta', value)` is a wholesale
6
+ * setter, and the app's own meta file is one. The app file is imported here so
7
+ * the ordering holds however this module is reached, not only through the
8
+ * generated scenario bootstrap.
9
+ */
10
+ export const serializeScenarioWorkflowMeta = (outputPath, metaDir, appMetaImportPath, scenarioNames, packageMappings, supportsImportAttributes, packageName) => {
11
+ const pkg = packageName ? `'${packageName}'` : 'null';
12
+ const sortedNames = [...scenarioNames].sort();
13
+ const imports = sortedNames
14
+ .map((name) => {
15
+ const sanitizedIdentifier = sanitizeTypeName(name);
16
+ const jsonPath = `${metaDir}/${name}.gen.json`;
17
+ const importPath = getFileImportRelativePath(outputPath, jsonPath, packageMappings);
18
+ return supportsImportAttributes
19
+ ? `import ${sanitizedIdentifier}Meta from '${importPath}' with { type: 'json' }`
20
+ : `import ${sanitizedIdentifier}Meta from '${importPath}'`;
21
+ })
22
+ .join('\n');
23
+ const metaEntries = sortedNames
24
+ .map((name) => ` '${name}': ${sanitizeTypeName(name)}Meta,`)
25
+ .join('\n');
26
+ return `import { pikkuState } from '@pikku/core/internal'
27
+ import type { WorkflowsRuntimeMeta } from '@pikku/core/workflow/types'
28
+ import '${appMetaImportPath}'
29
+ ${imports ? `\n${imports}\n` : ''}
30
+ const scenariosMeta = {
31
+ ${metaEntries}
32
+ } as WorkflowsRuntimeMeta
33
+
34
+ pikkuState(${pkg}, 'workflows', 'meta', {
35
+ ...pikkuState(${pkg}, 'workflows', 'meta'),
36
+ ...scenariosMeta,
37
+ })`;
38
+ };
39
+ /**
40
+ * Scenario step meta, merged onto the app's function meta for the same reason.
41
+ */
42
+ export const serializeScenarioFunctionMeta = (jsonImportPath, appMetaImportPath, supportsImportAttributes, packageName) => {
43
+ const pkg = packageName ? `'${packageName}'` : 'null';
44
+ const importStatement = supportsImportAttributes
45
+ ? `import metaData from '${jsonImportPath}' with { type: 'json' }`
46
+ : `import metaData from '${jsonImportPath}'`;
47
+ return `import { pikkuState } from '@pikku/core/internal'
48
+ import type { FunctionsMeta } from '@pikku/core'
49
+ import '${appMetaImportPath}'
50
+ ${importStatement}
51
+
52
+ pikkuState(${pkg}, 'function', 'meta', {
53
+ ...pikkuState(${pkg}, 'function', 'meta'),
54
+ ...(metaData as FunctionsMeta),
55
+ })`;
56
+ };
@@ -0,0 +1,7 @@
1
+ import type { WiringFileMap } from './scenario-partition.js';
2
+ /**
3
+ * The scenario counterpart of `serializeWorkflowRegistration`: registers the
4
+ * scenarios and features that were held back from the app wirings, so they
5
+ * exist only in the module graph `pikku scenario run` loads.
6
+ */
7
+ export declare const serializeScenarioRegistration: (outputPath: string, metaImportPath: string, scenarioFiles: WiringFileMap, featureFiles: WiringFileMap, packageMappings: Record<string, string>, packageName?: string) => string;
@@ -0,0 +1,40 @@
1
+ import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
2
+ /**
3
+ * The scenario counterpart of `serializeWorkflowRegistration`: registers the
4
+ * scenarios and features that were held back from the app wirings, so they
5
+ * exist only in the module graph `pikku scenario run` loads.
6
+ */
7
+ export const serializeScenarioRegistration = (outputPath, metaImportPath, scenarioFiles, featureFiles, packageMappings, packageName) => {
8
+ const hasScenarios = scenarioFiles.size > 0;
9
+ const hasFeatures = featureFiles.size > 0;
10
+ if (!hasScenarios && !hasFeatures) {
11
+ return 'export {}';
12
+ }
13
+ const lines = [];
14
+ const registrars = [
15
+ ...(hasScenarios ? ['addWorkflow'] : []),
16
+ ...(hasFeatures ? ['addFeature'] : []),
17
+ ];
18
+ lines.push(`import { ${registrars.join(', ')} } from '@pikku/core/workflow'`);
19
+ lines.push(`import '${metaImportPath}'`);
20
+ const sortedScenarios = Array.from(scenarioFiles.entries()).sort((a, b) => a[0].localeCompare(b[0]));
21
+ // A feature holds the very objects its scenarios were registered with, so it
22
+ // has to be imported for its membership to be resolvable at all.
23
+ const sortedFeatures = Array.from(featureFiles.entries()).sort((a, b) => a[0].localeCompare(b[0]));
24
+ for (const [, { path, exportedName }] of [
25
+ ...sortedScenarios,
26
+ ...sortedFeatures,
27
+ ]) {
28
+ const importPath = getFileImportRelativePath(outputPath, path, packageMappings);
29
+ lines.push(`import { ${exportedName} } from '${importPath}'`);
30
+ }
31
+ lines.push('');
32
+ const packageArg = packageName ? `, '${packageName}'` : '';
33
+ for (const [pikkuFuncId, { exportedName }] of sortedScenarios) {
34
+ lines.push(`addWorkflow('${pikkuFuncId}', ${exportedName}${packageArg})`);
35
+ }
36
+ for (const [featureId, { exportedName }] of sortedFeatures) {
37
+ lines.push(`addFeature('${featureId}', ${exportedName}${packageArg})`);
38
+ }
39
+ return lines.join('\n');
40
+ };
@@ -4,13 +4,20 @@ import { writeFileInDir } from '../../../utils/file-writer.js';
4
4
  import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
5
5
  import { serializeWorkflowTypes } from './serialize-workflow-types.js';
6
6
  import { serializeScenarioActors } from './serialize-scenario-actors.js';
7
+ import { resolveScenarioActors } from '../../../utils/resolve-scenario-actors.js';
7
8
  import { serializeWorkflowRegistration } from './serialize-workflow-registration.js';
8
9
  import { serializeWorkflowMap } from './serialize-workflow-map.js';
10
+ import { serializeScenarioStepMap } from './serialize-scenario-step-map.js';
9
11
  import { serializeWorkflowBootstrapMap } from './serialize-workflow-bootstrap-map.js';
10
12
  import { serializeWorkflowMeta } from './serialize-workflow-meta.js';
13
+ import { partitionScenarioWorkflows } from '../scenarios/scenario-partition.js';
14
+ import { serializeScenarioRegistration } from '../scenarios/serialize-scenario-registration.js';
15
+ import { buildFeaturesMeta } from '../scenarios/serialize-feature-meta.js';
16
+ import { serializeScenarioWorkflowMeta } from '../scenarios/serialize-scenario-meta.js';
11
17
  import { getFileImportRelativePath } from '../../../utils/file-import-path.js';
12
18
  import { stripVerboseFields, hasVerboseFields, } from '../../../utils/strip-verbose-meta.js';
13
19
  import { join } from 'path';
20
+ import { rm } from 'fs/promises';
14
21
  export const pikkuWorkflow = pikkuSessionlessFunc({
15
22
  func: async ({ logger, config, getInspectorState }, input) => {
16
23
  const bootstrap = input?.bootstrap === true;
@@ -28,6 +35,11 @@ export const pikkuWorkflow = pikkuSessionlessFunc({
28
35
  ];
29
36
  const hasRelevantWorkflows = allWorkflowNames.length > 0;
30
37
  const hasWorkflows = hasRelevantWorkflows;
38
+ // Scenarios and features are held back from every app-facing artifact: they
39
+ // are only ever run by `pikku scenario run`, and registering them in the app
40
+ // bootstrap drags each scenario's steps — and whatever those import — into a
41
+ // deployed server.
42
+ const { appNames, scenarioNames, appFiles, scenarioFiles } = partitionScenarioWorkflows(allWorkflowNames, workflows.files, workflows.graphMeta);
31
43
  if (hasWorkflows) {
32
44
  const singletonServices = visitState.serviceAggregation.allSingletonServices.length > 0
33
45
  ? visitState.serviceAggregation.allSingletonServices
@@ -44,35 +56,58 @@ export const pikkuWorkflow = pikkuSessionlessFunc({
44
56
  }
45
57
  }
46
58
  if (hasWorkflows && workflowMetaDir) {
59
+ const scenarioNameSet = new Set(scenarioNames);
47
60
  for (const [name, graphMeta] of Object.entries(workflows.graphMeta)) {
61
+ const metaDir = scenarioNameSet.has(name)
62
+ ? config.scenarioMetaDir
63
+ : workflowMetaDir;
48
64
  const minimalMeta = stripVerboseFields(graphMeta);
49
- const minimalPath = join(workflowMetaDir, `${name}.gen.json`);
65
+ const minimalPath = join(metaDir, `${name}.gen.json`);
50
66
  await writeFileInDir(logger, minimalPath, JSON.stringify(minimalMeta, null, 2), { ignoreModifyComment: true });
51
67
  if (hasVerboseFields(graphMeta)) {
52
- const verbosePath = join(workflowMetaDir, `${name}-verbose.gen.json`);
68
+ const verbosePath = join(metaDir, `${name}-verbose.gen.json`);
53
69
  await writeFileInDir(logger, verbosePath, JSON.stringify(graphMeta, null, 2), { ignoreModifyComment: true });
54
70
  }
55
71
  }
56
72
  }
73
+ // A project generated before scenarios moved out has their meta sitting in
74
+ // workflow/meta, and getWorkflowMeta() reads both directories — leaving it
75
+ // there would serve a stale copy of every scenario forever.
76
+ if (workflowMetaDir) {
77
+ await Promise.all(scenarioNames.flatMap((name) => [`${name}.gen.json`, `${name}-verbose.gen.json`].map((file) => rm(join(workflowMetaDir, file), { force: true }))));
78
+ }
57
79
  if (workflowsWiringMetaFile && workflowMetaDir) {
58
- await writeFileInDir(logger, workflowsWiringMetaFile, serializeWorkflowMeta(workflowsWiringMetaFile, workflowMetaDir, allWorkflowNames, packageMappings, schema?.supportsImportAttributes ?? false, config.addonName));
80
+ await writeFileInDir(logger, workflowsWiringMetaFile, serializeWorkflowMeta(workflowsWiringMetaFile, workflowMetaDir, appNames, packageMappings, schema?.supportsImportAttributes ?? false, config.addonName));
59
81
  }
82
+ await writeFileInDir(logger, config.scenarioWiringsMetaFile, serializeScenarioWorkflowMeta(config.scenarioWiringsMetaFile, config.scenarioMetaDir, getFileImportRelativePath(config.scenarioWiringsMetaFile, workflowsWiringMetaFile, packageMappings), scenarioNames, packageMappings, schema?.supportsImportAttributes ?? false, config.addonName));
83
+ await writeFileInDir(logger, config.scenarioWiringsFile, serializeScenarioRegistration(config.scenarioWiringsFile, getFileImportRelativePath(config.scenarioWiringsFile, config.scenarioWiringsMetaFile, packageMappings), scenarioFiles, workflows.featureFiles, packageMappings, config.addonName));
60
84
  const metaImportPath = getFileImportRelativePath(workflowsWiringFile, workflowsWiringMetaFile, packageMappings);
61
- await writeFileInDir(logger, workflowsWiringFile, serializeWorkflowRegistration(workflowsWiringFile, metaImportPath, allWorkflowNames, workflows.files, workflows.graphFiles, packageMappings, config.addonName));
85
+ await writeFileInDir(logger, workflowsWiringFile, serializeWorkflowRegistration(workflowsWiringFile, metaImportPath, appNames, appFiles, workflows.graphFiles, packageMappings, config.addonName));
62
86
  const functionTypesImportPath = getFileImportRelativePath(workflowTypesFile, functionTypesFile, packageMappings);
63
87
  const rpcMapImportPath = getFileImportRelativePath(workflowTypesFile, config.rpcInternalMapDeclarationFile, packageMappings);
64
88
  const workflowMapImportPath = getFileImportRelativePath(workflowTypesFile, workflowMapDeclarationFile, packageMappings);
65
89
  const agentMapImportPath = getFileImportRelativePath(workflowTypesFile, config.agentMapDeclarationFile, packageMappings);
66
90
  const scopesImportPath = getFileImportRelativePath(workflowTypesFile, config.scopesFile, packageMappings);
67
- await writeFileInDir(logger, workflowTypesFile, serializeWorkflowTypes(functionTypesImportPath, rpcMapImportPath, workflowMapImportPath, agentMapImportPath, scopesImportPath));
68
- const scenarioActors = config.scenarios?.actors;
69
- if (scenarioActors && Object.keys(scenarioActors).length > 0) {
91
+ const scenarioStepMapImportPath = getFileImportRelativePath(workflowTypesFile, config.scenarioStepMapDeclarationFile, packageMappings);
92
+ const scenarioActorsImportPath = getFileImportRelativePath(workflowTypesFile, config.scenarioActorsFile, packageMappings);
93
+ await writeFileInDir(logger, workflowTypesFile, serializeWorkflowTypes(functionTypesImportPath, rpcMapImportPath, workflowMapImportPath, agentMapImportPath, scopesImportPath, scenarioStepMapImportPath, scenarioActorsImportPath));
94
+ // Rides the workflow command rather than getting its own codegen node, so
95
+ // it can't drift out of the two hand-maintained ordering lists.
96
+ await writeFileInDir(logger, config.scenarioStepMapDeclarationFile, serializeScenarioStepMap(logger, config.scenarioStepMapDeclarationFile, packageMappings, typesMap, functionState.meta));
97
+ // Written even for an empty registry, so `TypedScenarioActors` is always a
98
+ // resolvable import for the generated function types.
99
+ const scenarioActors = resolveScenarioActors(config.scenarios);
100
+ {
70
101
  const agentMapImportPath = getFileImportRelativePath(config.scenarioActorsFile, config.agentMapDeclarationFile, packageMappings);
71
- await writeFileInDir(logger, config.scenarioActorsFile, serializeScenarioActors(scenarioActors, agentMapImportPath));
102
+ const exposedRpcMapImportPath = getFileImportRelativePath(config.scenarioActorsFile, config.rpcMapDeclarationFile, packageMappings);
103
+ await writeFileInDir(logger, config.scenarioActorsFile, serializeScenarioActors(scenarioActors, agentMapImportPath, exposedRpcMapImportPath));
72
104
  // Fixed path getScenarioActorsMeta() reads; kept out of workflow/meta,
73
105
  // which getWorkflowMeta() globs as workflows.
74
106
  await writeFileInDir(logger, join(config.outDir, 'workflow', 'scenario-actors.gen.json'), JSON.stringify(scenarioActors, null, 2));
75
107
  }
108
+ // Fixed path getFeaturesMeta() reads. Written even when empty so the
109
+ // console can tell "no features declared" from "meta not generated yet".
110
+ await writeFileInDir(logger, join(config.outDir, 'scenarios', 'features.gen.json'), JSON.stringify(buildFeaturesMeta(workflows.featureFiles), null, 2));
76
111
  await writeFileInDir(logger, workflowMapDeclarationFile, bootstrap
77
112
  ? serializeWorkflowBootstrapMap(workflows.meta, workflows.graphMeta)
78
113
  : serializeWorkflowMap(logger, workflowMapDeclarationFile, packageMappings, typesMap, functionState.meta, workflows.meta, workflows.graphMeta, visitState.rpc?.wireAddonDeclarations));
@@ -1,2 +1,2 @@
1
1
  import type { PikkuCLIConfig } from '../../../../types/config.js';
2
- export declare const serializeScenarioActors: (actors: NonNullable<NonNullable<PikkuCLIConfig["scenarios"]>["actors"]>, agentMapImportPath: string) => string;
2
+ export declare const serializeScenarioActors: (actors: NonNullable<NonNullable<PikkuCLIConfig["scenarios"]>["actors"]>, agentMapImportPath: string, rpcMapImportPath: string) => string;
@@ -1,4 +1,4 @@
1
- export const serializeScenarioActors = (actors, agentMapImportPath) => {
1
+ export const serializeScenarioActors = (actors, agentMapImportPath, rpcMapImportPath) => {
2
2
  return `/** Scenario actors declared in pikku.config.json (\`scenarios.actors\`) */
3
3
  import {
4
4
  createHttpScenarioActors,
@@ -7,15 +7,26 @@ import {
7
7
  type ScenarioActorConfig,
8
8
  } from '@pikku/core/services'
9
9
  import type { AgentMap } from '${agentMapImportPath}'
10
+ import type { FlattenedRPCMap } from '${rpcMapImportPath}'
10
11
 
11
12
  export const scenarioActorConfigs = ${JSON.stringify(actors, null, 2)} as const satisfies Record<string, ScenarioActorConfig>
12
13
 
13
14
  export type ScenarioActorName = keyof typeof scenarioActorConfigs
14
15
 
16
+ /**
17
+ * The same registry as a list, widened to \`ScenarioActorConfig\`. Iterating
18
+ * \`scenarioActorConfigs\` directly yields the literal type of each entry, on
19
+ * which an optional field an actor did not declare does not exist — so a seed
20
+ * reading \`scopes\`/\`roles\` off every actor needs this instead.
21
+ */
22
+ export const scenarioActorList: ScenarioActorConfig[] = Object.values(
23
+ scenarioActorConfigs
24
+ )
25
+
15
26
  export type AgentName = keyof AgentMap & string
16
27
  export type TypedScenarioActors = Record<
17
28
  ScenarioActorName,
18
- ScenarioActor<AgentName>
29
+ ScenarioActor<AgentName, FlattenedRPCMap>
19
30
  >
20
31
 
21
32
  export const createScenarioActors = (
@@ -0,0 +1,4 @@
1
+ import { type TypesMap } from '@pikku/inspector';
2
+ import type { FunctionsMeta } from '@pikku/core';
3
+ import type { Logger } from '@pikku/core/services';
4
+ export declare const serializeScenarioStepMap: (logger: Logger, relativeToPath: string, packageMappings: Record<string, string>, typesMap: TypesMap, functionsMeta: FunctionsMeta) => string;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Generate the scenario step map: the typed set of names a scenario may pass to
3
+ * `scenario.step/given/when/then`.
4
+ *
5
+ * Steps are referenced by string, exactly like `workflow.do` references an RPC,
6
+ * so type safety has to come from a generated map rather than from importing
7
+ * the step's const. This is what makes `scenario.given('…', 'buysAnApple', …)`
8
+ * autocomplete and type-check its data.
9
+ */
10
+ import { serializeImportMap } from '../../../utils/serialize-import-map.js';
11
+ import { generateCustomTypes } from '@pikku/inspector';
12
+ const SCENARIO_STEP_WRAPPER = 'pikkuScenarioStep';
13
+ export const serializeScenarioStepMap = (logger, relativeToPath, packageMappings, typesMap, functionsMeta) => {
14
+ const requiredTypes = new Set();
15
+ const steps = Object.entries(functionsMeta).filter(([, meta]) => meta.funcWrapper === SCENARIO_STEP_WRAPPER);
16
+ const resolveType = (name) => {
17
+ if (!name) {
18
+ return 'void';
19
+ }
20
+ try {
21
+ return typesMap.getTypeMeta(name).uniqueName;
22
+ }
23
+ catch {
24
+ return name;
25
+ }
26
+ };
27
+ let stepsStr = 'export type FlattenedScenarioStepMap = {\n';
28
+ for (const [stepName, meta] of steps) {
29
+ const inputType = resolveType(meta.inputs?.[0]);
30
+ const outputType = resolveType(meta.outputs?.[0]);
31
+ requiredTypes.add(inputType);
32
+ requiredTypes.add(outputType);
33
+ stepsStr += ` readonly '${stepName}': ScenarioStepHandler<${inputType}, ${outputType}>,\n`;
34
+ }
35
+ stepsStr += '};';
36
+ const hasAny = steps.length > 0;
37
+ const serializedCustomTypes = hasAny
38
+ ? generateCustomTypes(typesMap, requiredTypes)
39
+ : '';
40
+ const serializedImportMap = hasAny
41
+ ? serializeImportMap(logger, relativeToPath, packageMappings, typesMap, requiredTypes)
42
+ : '';
43
+ const serializedCustomTypesDeclarationsOnly = serializedCustomTypes
44
+ .split('\n')
45
+ .filter((line) => !line.startsWith('import '))
46
+ .join('\n');
47
+ return `/**
48
+ * Scenario step map: the names \`scenario.step/given/when/then\` accepts, with
49
+ * the input and output types of each step.
50
+ */
51
+
52
+ ${serializedImportMap}
53
+ ${serializedCustomTypesDeclarationsOnly}
54
+
55
+ interface ScenarioStepHandler<I, O> {
56
+ input: I;
57
+ output: O;
58
+ }
59
+
60
+ ${stepsStr}
61
+ `;
62
+ };
@@ -1,16 +1,23 @@
1
+ /**
2
+ * A workflow or graph node name is free-form prose written by a human, so an
3
+ * apostrophe or a backslash in it is ordinary. `JSON.stringify` emits a valid,
4
+ * escaped TypeScript string literal; interpolating the raw name into a quoted
5
+ * key terminates the string and the whole `.d.ts` stops parsing.
6
+ */
7
+ const key = (name) => JSON.stringify(name);
1
8
  export const serializeWorkflowBootstrapMap = (workflowsMeta, graphMeta) => {
2
9
  const workflowEntries = Object.keys(workflowsMeta)
3
10
  .sort()
4
- .map((workflowName) => ` readonly '${workflowName}': WorkflowHandler<unknown, unknown>,`)
11
+ .map((workflowName) => ` readonly ${key(workflowName)}: WorkflowHandler<unknown, unknown>,`)
5
12
  .join('\n');
6
13
  const graphEntries = Object.entries(graphMeta)
7
14
  .sort(([a], [b]) => a.localeCompare(b))
8
15
  .map(([graphName, graph]) => {
9
16
  const nodeEntries = Object.keys(graph.nodes)
10
17
  .sort()
11
- .map((nodeId) => ` readonly '${nodeId}': GraphNodeHandler<unknown>,`)
18
+ .map((nodeId) => ` readonly ${key(nodeId)}: GraphNodeHandler<unknown>,`)
12
19
  .join('\n');
13
- return ` readonly '${graphName}': {\n${nodeEntries}\n },`;
20
+ return ` readonly ${key(graphName)}: {\n${nodeEntries}\n },`;
14
21
  })
15
22
  .join('\n');
16
23
  return `/**
@@ -1,6 +1,13 @@
1
1
  import { serializeImportMap } from '../../../utils/serialize-import-map.js';
2
2
  import { generateCustomTypes } from '@pikku/inspector';
3
3
  import { parseVersionedId } from '@pikku/core';
4
+ /**
5
+ * A workflow or graph node name is free-form prose written by a human, so an
6
+ * apostrophe or a backslash in it is ordinary. `JSON.stringify` emits a valid,
7
+ * escaped TypeScript string literal; interpolating the raw name into a quoted
8
+ * key terminates the string and the whole `.d.ts` stops parsing.
9
+ */
10
+ const key = (name) => JSON.stringify(name);
4
11
  export const serializeWorkflowMap = (logger, relativeToPath, packageMappings, typesMap, functionsMeta, workflowsMeta, graphMeta, wireAddonDeclarations) => {
5
12
  const requiredTypes = new Set();
6
13
  const serializedWorkflows = generateWorkflows(workflowsMeta, functionsMeta, typesMap, requiredTypes);
@@ -84,7 +91,7 @@ function generateWorkflows(workflowsMeta, functionsMeta, typesMap, requiredTypes
84
91
  }
85
92
  let workflowsStr = 'export type WorkflowMap = {\n';
86
93
  for (const [workflowName, handler] of Object.entries(workflowsObj)) {
87
- workflowsStr += ` readonly '${workflowName}': WorkflowHandler<${handler.inputType}, ${handler.outputType}>,\n`;
94
+ workflowsStr += ` readonly ${key(workflowName)}: WorkflowHandler<${handler.inputType}, ${handler.outputType}>,\n`;
88
95
  }
89
96
  workflowsStr += '};';
90
97
  return workflowsStr;
@@ -121,9 +128,9 @@ function generateGraphs(graphMeta, functionsMeta, typesMap, requiredTypes) {
121
128
  }
122
129
  let graphsStr = 'export type GraphsMap = {\n';
123
130
  for (const [graphName, nodes] of Object.entries(graphsObj)) {
124
- graphsStr += ` readonly '${graphName}': {\n`;
131
+ graphsStr += ` readonly ${key(graphName)}: {\n`;
125
132
  for (const [nodeId, handler] of Object.entries(nodes)) {
126
- graphsStr += ` readonly '${nodeId}': GraphNodeHandler<${handler.inputType}>,\n`;
133
+ graphsStr += ` readonly ${key(nodeId)}: GraphNodeHandler<${handler.inputType}>,\n`;
127
134
  }
128
135
  graphsStr += ' },\n';
129
136
  }