@flow-as-code/cli 0.1.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.
Files changed (120) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +446 -0
  3. package/dist/aws.d.ts +28 -0
  4. package/dist/aws.d.ts.map +1 -0
  5. package/dist/aws.js +104 -0
  6. package/dist/aws.js.map +1 -0
  7. package/dist/bin.d.ts +3 -0
  8. package/dist/bin.d.ts.map +1 -0
  9. package/dist/bin.js +132 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/bridge/exportFiles.d.ts +16 -0
  12. package/dist/bridge/exportFiles.d.ts.map +1 -0
  13. package/dist/bridge/exportFiles.js +95 -0
  14. package/dist/bridge/exportFiles.js.map +1 -0
  15. package/dist/bridge/pair.d.ts +103 -0
  16. package/dist/bridge/pair.d.ts.map +1 -0
  17. package/dist/bridge/pair.js +219 -0
  18. package/dist/bridge/pair.js.map +1 -0
  19. package/dist/bridge/protocol.d.ts +187 -0
  20. package/dist/bridge/protocol.d.ts.map +1 -0
  21. package/dist/bridge/protocol.js +132 -0
  22. package/dist/bridge/protocol.js.map +1 -0
  23. package/dist/bridge/server.d.ts +95 -0
  24. package/dist/bridge/server.d.ts.map +1 -0
  25. package/dist/bridge/server.js +669 -0
  26. package/dist/bridge/server.js.map +1 -0
  27. package/dist/cdk-scaffold.d.ts +16 -0
  28. package/dist/cdk-scaffold.d.ts.map +1 -0
  29. package/dist/cdk-scaffold.js +28 -0
  30. package/dist/cdk-scaffold.js.map +1 -0
  31. package/dist/codegen.d.ts +5 -0
  32. package/dist/codegen.d.ts.map +1 -0
  33. package/dist/codegen.js +27 -0
  34. package/dist/codegen.js.map +1 -0
  35. package/dist/diff.d.ts +33 -0
  36. package/dist/diff.d.ts.map +1 -0
  37. package/dist/diff.js +225 -0
  38. package/dist/diff.js.map +1 -0
  39. package/dist/docs.d.ts +46 -0
  40. package/dist/docs.d.ts.map +1 -0
  41. package/dist/docs.js +223 -0
  42. package/dist/docs.js.map +1 -0
  43. package/dist/emit.d.ts +8 -0
  44. package/dist/emit.d.ts.map +1 -0
  45. package/dist/emit.js +55 -0
  46. package/dist/emit.js.map +1 -0
  47. package/dist/errors.d.ts +12 -0
  48. package/dist/errors.d.ts.map +1 -0
  49. package/dist/errors.js +25 -0
  50. package/dist/errors.js.map +1 -0
  51. package/dist/export.d.ts +27 -0
  52. package/dist/export.d.ts.map +1 -0
  53. package/dist/export.js +126 -0
  54. package/dist/export.js.map +1 -0
  55. package/dist/index.d.ts +4 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +23 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/lint.d.ts +6 -0
  60. package/dist/lint.d.ts.map +1 -0
  61. package/dist/lint.js +31 -0
  62. package/dist/lint.js.map +1 -0
  63. package/dist/render.d.ts +6 -0
  64. package/dist/render.d.ts.map +1 -0
  65. package/dist/render.js +49 -0
  66. package/dist/render.js.map +1 -0
  67. package/dist/run.d.ts +13 -0
  68. package/dist/run.d.ts.map +1 -0
  69. package/dist/run.js +38 -0
  70. package/dist/run.js.map +1 -0
  71. package/dist/simulate.d.ts +35 -0
  72. package/dist/simulate.d.ts.map +1 -0
  73. package/dist/simulate.js +194 -0
  74. package/dist/simulate.js.map +1 -0
  75. package/dist/studio.d.ts +23 -0
  76. package/dist/studio.d.ts.map +1 -0
  77. package/dist/studio.js +120 -0
  78. package/dist/studio.js.map +1 -0
  79. package/dist/synth-resolve-hook.d.ts +19 -0
  80. package/dist/synth-resolve-hook.d.ts.map +1 -0
  81. package/dist/synth-resolve-hook.js +127 -0
  82. package/dist/synth-resolve-hook.js.map +1 -0
  83. package/dist/synth-runner.d.ts +12 -0
  84. package/dist/synth-runner.d.ts.map +1 -0
  85. package/dist/synth-runner.js +145 -0
  86. package/dist/synth-runner.js.map +1 -0
  87. package/dist/synth.d.ts +55 -0
  88. package/dist/synth.d.ts.map +1 -0
  89. package/dist/synth.js +327 -0
  90. package/dist/synth.js.map +1 -0
  91. package/dist/watch.d.ts +66 -0
  92. package/dist/watch.d.ts.map +1 -0
  93. package/dist/watch.js +293 -0
  94. package/dist/watch.js.map +1 -0
  95. package/package.json +85 -0
  96. package/schema/flowdoc-0.1.schema.json +669 -0
  97. package/schema/scenario-0.1.schema.json +318 -0
  98. package/src/aws.ts +149 -0
  99. package/src/bin.ts +179 -0
  100. package/src/bridge/exportFiles.ts +116 -0
  101. package/src/bridge/pair.ts +312 -0
  102. package/src/bridge/protocol.ts +289 -0
  103. package/src/bridge/server.ts +827 -0
  104. package/src/cdk-scaffold.ts +41 -0
  105. package/src/codegen.ts +36 -0
  106. package/src/diff.ts +279 -0
  107. package/src/docs.ts +249 -0
  108. package/src/emit.ts +69 -0
  109. package/src/errors.ts +27 -0
  110. package/src/export.ts +167 -0
  111. package/src/index.ts +27 -0
  112. package/src/lint.ts +42 -0
  113. package/src/render.ts +58 -0
  114. package/src/run.ts +42 -0
  115. package/src/simulate.ts +244 -0
  116. package/src/studio.ts +143 -0
  117. package/src/synth-resolve-hook.ts +152 -0
  118. package/src/synth-runner.ts +157 -0
  119. package/src/synth.ts +380 -0
  120. package/src/watch.ts +388 -0
package/dist/aws.d.ts ADDED
@@ -0,0 +1,28 @@
1
+ import type { ConnectInventoryClient, FlowTestClient } from "@flow-as-code/core";
2
+ /** An instance, as `--instance` names it. */
3
+ export interface InstanceTarget {
4
+ /** The ARN exactly as given. */
5
+ arn: string;
6
+ region: string;
7
+ instanceId: string;
8
+ }
9
+ /** The clients a live command runs through. Tests supply fixture-backed ones. */
10
+ export interface LiveClients {
11
+ /** For `export` and `diff`: the inventory and flow content of an instance. */
12
+ inventory(target: InstanceTarget): Promise<ConnectInventoryClient>;
13
+ /** For `simulate`: the TestCase operations. */
14
+ test(target: InstanceTarget): Promise<FlowTestClient>;
15
+ }
16
+ /** The peer dependency every live command needs. */
17
+ export declare const SDK_PACKAGE = "@aws-sdk/client-connect";
18
+ export declare const MISSING_SDK_MESSAGE: string;
19
+ export declare const STALE_SDK_MESSAGE: string;
20
+ /**
21
+ * Parses `--instance`. Only an instance ARN will do: a bare id carries no
22
+ * Region, and a resource ARN names the wrong thing. `exitCode` is the code the
23
+ * rejection exits with, because `diff` reserves 1 for "differs".
24
+ */
25
+ export declare function parseInstanceArn(arn: string, exitCode?: number): InstanceTarget;
26
+ /** The default LiveClients: @flow-as-code/core's SDK adapters over a real ConnectClient. */
27
+ export declare const SDK_CLIENTS: LiveClients;
28
+ //# sourceMappingURL=aws.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws.d.ts","sourceRoot":"","sources":["../src/aws.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAoB,sBAAsB,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AASnG,6CAA6C;AAC7C,MAAM,WAAW,cAAc;IAC7B,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,SAAS,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACnE,+CAA+C;IAC/C,IAAI,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CACvD;AAED,oDAAoD;AACpD,eAAO,MAAM,WAAW,4BAA4B,CAAC;AAErD,eAAO,MAAM,mBAAmB,QAE4B,CAAC;AAE7D,eAAO,MAAM,iBAAiB,QAE+B,CAAC;AAE9D;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,SAAI,GAAG,cAAc,CAiB1E;AA2CD,4FAA4F;AAC5F,eAAO,MAAM,WAAW,EAAE,WAazB,CAAC"}
package/dist/aws.js ADDED
@@ -0,0 +1,104 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ // Where the CLI meets the AWS SDK, and the seam the tests replace.
6
+ //
7
+ // `export`, `simulate`, and `diff` each reach their instance through one of the
8
+ // two factories in LiveClients. The default set builds @flow-as-code/core's SDK adapters
9
+ // over a real ConnectClient; a test passes its own set, holding a fixture
10
+ // client, and every line of the commands runs offline. Nothing else in this
11
+ // package imports the SDK.
12
+ //
13
+ // Credentials: the SDK's default provider chain (environment variables, the
14
+ // shared config and credentials files with their profiles and SSO sessions,
15
+ // web identity, the container and instance metadata endpoints). The CLI takes
16
+ // no credential flags and reads no secrets of its own.
17
+ // https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html
18
+ //
19
+ // Region: the one in the instance ARN. A v3 client must be given a Region, and
20
+ // an instance ARN spells it (`arn:aws:connect:<region>:<account>:instance/<id>`),
21
+ // so nothing is read from AWS_REGION or a profile, and an instance in one
22
+ // Region cannot be addressed through a client configured for another.
23
+ // https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html
24
+ // https://docs.aws.amazon.com/connect/latest/adminguide/find-instance-arn.html
25
+ //
26
+ // @aws-sdk/client-connect is an optional peer dependency, loaded with a dynamic
27
+ // import at the moment a command needs it. A missing SDK is a CliError naming
28
+ // the package, not a stack trace from inside a module resolver. Only the
29
+ // package itself being unresolvable counts as missing; an SDK that is present
30
+ // but cannot be loaded (a dependency of its own missing, a file it cannot
31
+ // evaluate) is reported with the loader's reason, because the install command
32
+ // is not the fix for that.
33
+ import { createConnectInventoryClient, createConnectTestClient, parseConnectArn, } from "@flow-as-code/core";
34
+ import { CliError, messageOf } from "./errors.js";
35
+ /** The peer dependency every live command needs. */
36
+ export const SDK_PACKAGE = "@aws-sdk/client-connect";
37
+ export const MISSING_SDK_MESSAGE = `Connecting to an instance needs the optional peer dependency ${SDK_PACKAGE}. ` +
38
+ `Install it beside flow-cli (npm install ${SDK_PACKAGE}).`;
39
+ export const STALE_SDK_MESSAGE = `The installed ${SDK_PACKAGE} does not expose the TestCase operations. ` +
40
+ "They are recent; upgrade the SDK (3.1122.0 has them all).";
41
+ /**
42
+ * Parses `--instance`. Only an instance ARN will do: a bare id carries no
43
+ * Region, and a resource ARN names the wrong thing. `exitCode` is the code the
44
+ * rejection exits with, because `diff` reserves 1 for "differs".
45
+ */
46
+ export function parseInstanceArn(arn, exitCode = 1) {
47
+ const parsed = parseConnectArn(arn);
48
+ if (parsed === undefined || parsed.resourceType !== undefined || parsed.qualifier !== undefined) {
49
+ throw new CliError("--instance must be an Amazon Connect instance ARN " +
50
+ `(arn:<partition>:connect:<region>:<account>:instance/<id>), got "${arn}". ` +
51
+ "The Region is read from it.", exitCode);
52
+ }
53
+ if (parsed.region === "") {
54
+ throw new CliError(`--instance ARN "${arn}" has no Region; one is needed to connect.`, exitCode);
55
+ }
56
+ return { arn, region: parsed.region, instanceId: parsed.instanceId };
57
+ }
58
+ /**
59
+ * Whether `error` is Node failing to find the SDK package itself: the ESM
60
+ * resolver's ERR_MODULE_NOT_FOUND, whose message quotes the bare specifier it
61
+ * could not resolve (`Cannot find package '<name>' imported from <file>`). A
62
+ * package the SDK depends on being absent raises the same code for a
63
+ * different specifier, or CommonJS's MODULE_NOT_FOUND, and is not this.
64
+ * https://nodejs.org/api/errors.html#err_module_not_found
65
+ */
66
+ function isSdkNotFound(error) {
67
+ return (error instanceof Error &&
68
+ error.code === "ERR_MODULE_NOT_FOUND" &&
69
+ error.message.includes(`'${SDK_PACKAGE}'`));
70
+ }
71
+ async function loadSdk() {
72
+ try {
73
+ return (await import("@aws-sdk/client-connect"));
74
+ }
75
+ catch (error) {
76
+ if (isSdkNotFound(error))
77
+ throw new CliError(MISSING_SDK_MESSAGE, 1, { cause: error });
78
+ throw new CliError(`${SDK_PACKAGE} could not be loaded: ${messageOf(error)}`, 1, {
79
+ cause: error,
80
+ });
81
+ }
82
+ }
83
+ /** Loads the SDK and builds a client for the Region the ARN names. */
84
+ async function connectClient(target) {
85
+ const sdk = await loadSdk();
86
+ return { sdk, connect: new sdk.ConnectClient({ region: target.region }) };
87
+ }
88
+ /** The default LiveClients: @flow-as-code/core's SDK adapters over a real ConnectClient. */
89
+ export const SDK_CLIENTS = {
90
+ async inventory(target) {
91
+ const { connect } = await connectClient(target);
92
+ return createConnectInventoryClient({ connect, instanceId: target.instanceId });
93
+ },
94
+ async test(target) {
95
+ const { sdk, connect } = await connectClient(target);
96
+ // @flow-as-code/core checks the whole TestCase command set on first use. Checking
97
+ // one here turns an SDK too old for `simulate` into a message before any
98
+ // scenario is reported as errored.
99
+ if (typeof sdk.CreateTestCaseCommand !== "function")
100
+ throw new CliError(STALE_SDK_MESSAGE);
101
+ return createConnectTestClient({ connect, instanceId: target.instanceId });
102
+ },
103
+ };
104
+ //# sourceMappingURL=aws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws.js","sourceRoot":"","sources":["../src/aws.ts"],"names":[],"mappings":"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"}
package/dist/bin.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":""}
package/dist/bin.js ADDED
@@ -0,0 +1,132 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * Copyright 2026 The flow-as-code Authors
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ // Command registration only. Every command's behaviour lives in its own module
7
+ // so it can be tested without a subprocess; this file wires arguments, options,
8
+ // and help text, in the order packages/cli/README.md lists them.
9
+ import { PACKAGE_NAMES } from "@flow-as-code/core";
10
+ import { Command } from "commander";
11
+ import { runCodegen } from "./codegen.js";
12
+ import { EXIT_DIFF_ERROR, runDiff } from "./diff.js";
13
+ import { runEmit } from "./emit.js";
14
+ import { runExport } from "./export.js";
15
+ import { runLint } from "./lint.js";
16
+ import { runRender } from "./render.js";
17
+ import { action, usageErrorsExit } from "./run.js";
18
+ import { runSimulate } from "./simulate.js";
19
+ import { studioCommand } from "./studio.js";
20
+ import { synthToFiles } from "./synth.js";
21
+ const program = new Command();
22
+ program
23
+ .name("flow-cli")
24
+ .description("Typed, IaC-native tooling for Amazon Connect flows")
25
+ .version("0.0.1");
26
+ program
27
+ .command("lint")
28
+ .description(`Check every FlowDoc in a directory (or one file) against the ${PACKAGE_NAMES.core} rule set. ` +
29
+ "The whole set is linted in one pass so cross-document rules can follow module " +
30
+ "references. Exits 1 when any finding has error severity; warnings alone exit 0.")
31
+ .argument("<dir-or-file>", "directory of *.flowdoc.json files, or one FlowDoc file")
32
+ .option("--format <format>", "report format: text or json", "text")
33
+ .action(action((target, opts) => runLint(target, opts)));
34
+ program
35
+ .command("codegen")
36
+ .description("Generate idiomatic TypeScript builder source from a FlowDoc. Writes " +
37
+ "<doc name>.flow.ts next to the input unless --out says otherwise, and re-reads " +
38
+ "an existing output file first so comments marked @keep survive regeneration.")
39
+ .argument("<file>", "path to a .flowdoc.json file")
40
+ .option("--out <file>", "output file (default: <doc name>.flow.ts beside the input)")
41
+ .action(action((file, opts) => {
42
+ console.log(runCodegen(file, opts));
43
+ }));
44
+ program
45
+ .command("synth")
46
+ .description("Execute a TypeScript builder file in a sandboxed child process and write one " +
47
+ "<flow.name>.flowdoc.json per exported flow. A flow is any exported Flow instance " +
48
+ "or the result of any exported zero-argument function returning one.")
49
+ .argument("<file>", "path to a .flow.ts builder file")
50
+ .option("--out <dir>", "output directory (default: the source file's directory)")
51
+ .action(action(async (file, opts) => {
52
+ // SynthError already carries a terminal-ready message; action() prints it.
53
+ for (const path of await synthToFiles(file, opts.out)) {
54
+ console.log(path);
55
+ }
56
+ }));
57
+ program
58
+ .command("render")
59
+ .description("Materialize FlowDocs into deployable Flow language JSON, replacing every " +
60
+ "${cdref:...} token from the resource map. Writes <doc name>.json. A token with " +
61
+ "no entry in the map is fatal, and every missing token is listed at once.")
62
+ .argument("<dir-or-file>", "directory of *.flowdoc.json files, or one FlowDoc file")
63
+ .requiredOption("--resources <map.json>", "JSON object mapping reference tokens to values")
64
+ .option("--out <dir>", "output directory (default: the input directory)")
65
+ .action(action((target, opts) => {
66
+ for (const path of runRender(target, opts))
67
+ console.log(path);
68
+ }));
69
+ program
70
+ .command("emit")
71
+ .description("Emit infrastructure as code for a set of FlowDocs. --target tf writes the " +
72
+ `Terraform/OpenTofu files from ${PACKAGE_NAMES.tf}; --target cdk writes a flow-stack.ts ` +
73
+ `scaffold that constructs a ${PACKAGE_NAMES.cdk} FlowSet over the directory.`)
74
+ .argument("<dir-or-file>", "directory of *.flowdoc.json files, or one FlowDoc file")
75
+ .requiredOption("--target <target>", "cdk or tf")
76
+ .option("--address-map <refs.tfmap.json>", "tf only: reference to terraform address expressions")
77
+ .option("--out <dir>", "output directory (default: the input directory)")
78
+ .action(action((input, opts) => {
79
+ for (const path of runEmit(input, opts))
80
+ console.log(path);
81
+ }));
82
+ program
83
+ .command("diff")
84
+ .description("Compare every FlowDoc in a directory against the flow or module of the same name " +
85
+ "in a live Amazon Connect instance. Prints one line per document (unchanged, changed, " +
86
+ "missing-live) and a unified diff of the canonical JSON for each changed one; layout " +
87
+ "and meta are ignored. Exits 0 when nothing differs, 1 when something does, 2 when the " +
88
+ "comparison itself failed.")
89
+ .argument("<dir>", "directory of *.flowdoc.json files")
90
+ .requiredOption("--instance <arn>", "ARN of the Connect instance to compare against")
91
+ // 1 means "differs", so commander's own usage errors take the error code too.
92
+ .exitOverride(usageErrorsExit(EXIT_DIFF_ERROR))
93
+ .action(action(async (dir, opts) => {
94
+ await runDiff(dir, opts);
95
+ }));
96
+ program
97
+ .command("export")
98
+ .description("Read every flow and module in a live Amazon Connect instance and write " +
99
+ "<name>.flowdoc.json plus <name>.flow.ts for each. References come out as tokens, " +
100
+ "never ARNs. Exits 1 when any flow could not be exported; --on-error collect (the " +
101
+ "default) still writes the rest and reports every failure at once.")
102
+ .requiredOption("--instance <arn>", "ARN of the Connect instance to read")
103
+ .option("--out <dir>", "output directory (default: the working directory)")
104
+ .option("--no-codegen", "write FlowDocs only, no TypeScript")
105
+ .option("--on-error <mode>", "abort on the first failed flow, or collect them all", "collect")
106
+ .action(action(async (opts) => {
107
+ await runExport(opts);
108
+ }));
109
+ program
110
+ .command("simulate")
111
+ .description("Run a scenario suite against a live Amazon Connect instance through its TestCase " +
112
+ "operations, within the documented limits (5 concurrent, 100 in flight, 5 minutes " +
113
+ "each), and write a JUnit or JSON report. Exits 0 only when every scenario passed.")
114
+ .argument("<scenarios>", "scenario file, or a directory of scenario.json / *.scenario.json")
115
+ .requiredOption("--instance <arn>", "ARN of the Connect instance to run against")
116
+ .option("--resource-map <map.json>", "JSON object mapping reference tokens to ARNs")
117
+ .option("--format <format>", "report format: junit or json", "junit")
118
+ .option("--out <file>", "report file (default: stdout)")
119
+ .action(action(async (scenarios, opts) => {
120
+ await runSimulate(scenarios, opts);
121
+ }));
122
+ program
123
+ .command("studio")
124
+ .description("Serve the local visual editor over a directory. The bridge binds 127.0.0.1 only, " +
125
+ "picks a free port unless --port says otherwise, and keeps every <name>.flowdoc.json " +
126
+ "in sync with its <name>.flow.ts: a canvas save regenerates the builder source, and " +
127
+ "an edit to the source reloads the canvas.")
128
+ .argument("[dir]", "directory to open (default: the working directory)")
129
+ .option("--port <port>", "port to listen on (default: a free port)")
130
+ .action(action((dir, opts) => studioCommand(dir, opts)));
131
+ program.parseAsync(process.argv);
132
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { type BridgeExportRequest, type BridgeExportResult } from "./protocol.js";
2
+ /**
3
+ * The absolute path an export file lands on, or undefined when it would leave
4
+ * the served directory. Containment is checked against `dir` itself rather
5
+ * than against `dir/subdir`, so neither half can escape on its own.
6
+ */
7
+ export declare function exportPathFor(dir: string, subdir: string, relative: string): string | undefined;
8
+ /** Validates a request body from the wire. Throws BridgeError with a status. */
9
+ export declare function checkExportRequest(body: unknown): BridgeExportRequest;
10
+ /**
11
+ * Writes a checked export under `dir`, creating directories as needed, and
12
+ * returns the absolute paths written, sorted. Existing files are overwritten
13
+ * and nothing else is touched, exactly as `flow-cli emit` behaves.
14
+ */
15
+ export declare function writeExport(dir: string, request: BridgeExportRequest): Promise<BridgeExportResult>;
16
+ //# sourceMappingURL=exportFiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exportFiles.d.ts","sourceRoot":"","sources":["../../src/bridge/exportFiles.ts"],"names":[],"mappings":"AA2BA,OAAO,EAML,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EAExB,MAAM,eAAe,CAAC;AAIvB;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAI/F;AAED,gFAAgF;AAChF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,CAsCrE;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,kBAAkB,CAAC,CAe7B"}
@@ -0,0 +1,95 @@
1
+ /*
2
+ * Copyright 2026 The flow-as-code Authors
3
+ * SPDX-License-Identifier: Apache-2.0
4
+ */
5
+ // Writing a studio export to disk (POST /bridge/export).
6
+ //
7
+ // The studio computes the bytes: it holds the documents, the address map, and
8
+ // the resource map, and it runs the same emitters the CLI runs
9
+ // (@flow-as-code/tf, the @flow-as-code/cdk scaffold, @flow-as-code/core
10
+ // materialization). All that is left here is the half a browser cannot do,
11
+ // and it is deliberately dumb: check, then write.
12
+ //
13
+ // The checks are the same ones every other bridge path gets. Paths are
14
+ // validated by the shared protocol rule and then, independently, by resolving
15
+ // them and requiring the result to be inside the served directory: the first
16
+ // check states the rule, the second is what actually holds if the rule is ever
17
+ // loosened. Nothing is sanitized into shape, because a sanitizer is a guess
18
+ // about intent and this writes files the user owns.
19
+ //
20
+ // Unlike a document write this publishes no event and pairs nothing: emitted
21
+ // terraform and CDK files are outputs, not documents, and the watch engine
22
+ // ignores them (it pairs *.flow.ts with *.flowdoc.json only).
23
+ import { mkdir, writeFile } from "node:fs/promises";
24
+ import { dirname, resolve, sep } from "node:path";
25
+ import { BridgeError } from "./pair.js";
26
+ import { EXPORT_MAX_BYTES, EXPORT_MAX_FILES, EXPORT_TARGETS, isExportPath, isExportSubdir, } from "./protocol.js";
27
+ const TARGETS = new Set(EXPORT_TARGETS);
28
+ /**
29
+ * The absolute path an export file lands on, or undefined when it would leave
30
+ * the served directory. Containment is checked against `dir` itself rather
31
+ * than against `dir/subdir`, so neither half can escape on its own.
32
+ */
33
+ export function exportPathFor(dir, subdir, relative) {
34
+ const base = resolve(dir);
35
+ const target = resolve(base, subdir, relative);
36
+ return target !== base && target.startsWith(base + sep) ? target : undefined;
37
+ }
38
+ /** Validates a request body from the wire. Throws BridgeError with a status. */
39
+ export function checkExportRequest(body) {
40
+ if (body === null || typeof body !== "object" || Array.isArray(body)) {
41
+ throw new BridgeError(400, "The body must be a JSON object.");
42
+ }
43
+ const request = body;
44
+ if (typeof request.target !== "string" || !TARGETS.has(request.target)) {
45
+ throw new BridgeError(400, `"target" must be one of: ${[...TARGETS].sort().join(", ")}.`);
46
+ }
47
+ const subdir = request.subdir ?? "";
48
+ if (typeof subdir !== "string" || !isExportSubdir(subdir)) {
49
+ throw new BridgeError(400, `"${subdir}" is not a usable export subdirectory.`);
50
+ }
51
+ const files = request.files;
52
+ if (files === null || typeof files !== "object" || Array.isArray(files)) {
53
+ throw new BridgeError(400, '"files" must be a JSON object of path to content.');
54
+ }
55
+ const entries = Object.entries(files);
56
+ if (entries.length === 0)
57
+ throw new BridgeError(400, "An export must carry at least one file.");
58
+ if (entries.length > EXPORT_MAX_FILES) {
59
+ throw new BridgeError(413, `An export may carry at most ${String(EXPORT_MAX_FILES)} files.`);
60
+ }
61
+ let bytes = 0;
62
+ for (const [path, content] of entries) {
63
+ if (typeof content !== "string") {
64
+ throw new BridgeError(400, `The content of "${path}" is not a string.`);
65
+ }
66
+ if (!isExportPath(path)) {
67
+ throw new BridgeError(400, `"${path}" is not a usable export path.`);
68
+ }
69
+ bytes += Buffer.byteLength(content, "utf8");
70
+ }
71
+ if (bytes > EXPORT_MAX_BYTES) {
72
+ throw new BridgeError(413, `An export may carry at most ${String(EXPORT_MAX_BYTES)} bytes.`);
73
+ }
74
+ return { target: request.target, files: files, subdir };
75
+ }
76
+ /**
77
+ * Writes a checked export under `dir`, creating directories as needed, and
78
+ * returns the absolute paths written, sorted. Existing files are overwritten
79
+ * and nothing else is touched, exactly as `flow-cli emit` behaves.
80
+ */
81
+ export async function writeExport(dir, request) {
82
+ const subdir = request.subdir ?? "";
83
+ const written = [];
84
+ for (const [relative, content] of Object.entries(request.files).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) {
85
+ const target = exportPathFor(dir, subdir, relative);
86
+ if (target === undefined) {
87
+ throw new BridgeError(400, `"${relative}" resolves outside the served directory.`);
88
+ }
89
+ await mkdir(dirname(target), { recursive: true });
90
+ await writeFile(target, content, "utf8");
91
+ written.push(target);
92
+ }
93
+ return { target: request.target, paths: written };
94
+ }
95
+ //# sourceMappingURL=exportFiles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exportFiles.js","sourceRoot":"","sources":["../../src/bridge/exportFiles.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,103 @@
1
+ import { type FlowDoc } from "@flow-as-code/core";
2
+ import { type FlowWatcher } from "../watch.js";
3
+ import { type BridgeDocPayload, type BridgeWriteResult } from "./protocol.js";
4
+ /** A failure with the HTTP status the bridge should answer with. */
5
+ export declare class BridgeError extends Error {
6
+ readonly status: number;
7
+ constructor(status: number, message: string);
8
+ }
9
+ /**
10
+ * Thrown by writePair when the builder file has moved since the document being
11
+ * written was generated from it. Both sides changed, so the write is refused
12
+ * and the caller turns this into the "which side wins" question.
13
+ */
14
+ export declare class PairConflict extends Error {
15
+ readonly name = "PairConflict";
16
+ readonly docName: string;
17
+ readonly reason: string;
18
+ constructor(docName: string, reason: string);
19
+ }
20
+ export interface PairPaths {
21
+ docPath: string;
22
+ tsPath: string;
23
+ }
24
+ /**
25
+ * Both file paths for a document name. The name is re-checked here rather than
26
+ * only at the route, because this function is what turns a client string into
27
+ * a filesystem path: a slug cannot contain a separator, a dot, or a NUL, so
28
+ * traversal is impossible by construction rather than by sanitizing.
29
+ */
30
+ export declare function pairPaths(dir: string, name: string): PairPaths;
31
+ /** Every FlowDoc name in the directory, sorted, non-recursive. */
32
+ export declare function listDocNames(dir: string): Promise<string[]>;
33
+ /** Parses and schema-validates a FlowDoc, naming what is wrong with it. */
34
+ export declare function parseDoc(text: string, what: string): FlowDoc;
35
+ /** The document on disk, exactly as the file holds it. */
36
+ export declare function readPair(dir: string, name: string): Promise<BridgeDocPayload>;
37
+ /**
38
+ * Writes both halves of the pair from a FlowDoc the canvas produced.
39
+ *
40
+ * Order matters and is: generate, hash the generated source, serialize the doc
41
+ * with that hash, write both, then tell the watcher these exact bytes are
42
+ * ours. The watcher is told last because chokidar's awaitWriteFinish window
43
+ * (50 ms) is far longer than the gap, so no change event can be routed against
44
+ * a half-updated ledger, and a write that throws leaves the ledger untouched.
45
+ */
46
+ export interface WritePairOptions {
47
+ /**
48
+ * Write even though the builder file no longer matches the document's
49
+ * meta.sourceHash. Set only when the user has answered the conflict dialog.
50
+ */
51
+ force?: boolean;
52
+ }
53
+ export declare function writePair(dir: string, name: string, doc: FlowDoc, watcher?: FlowWatcher, options?: WritePairOptions): Promise<BridgeWriteResult>;
54
+ /** The FlowDoc the builder file synths to right now. */
55
+ export declare function synthPair(dir: string, name: string): Promise<FlowDoc>;
56
+ /**
57
+ * Resolution in the code direction: the builder file wins, so the FlowDoc is
58
+ * rewritten from it. The source is NOT regenerated, because the user chose the
59
+ * source they have; only the doc moves.
60
+ */
61
+ export declare function adoptCode(dir: string, name: string, watcher?: FlowWatcher): Promise<BridgeDocPayload>;
62
+ /** One document whose builder file was written by ensureBuilderFiles. */
63
+ export interface GeneratedBuilderFile {
64
+ name: string;
65
+ tsPath: string;
66
+ /** Exact bytes written, so the caller can seed a watcher's ledger. */
67
+ tsText: string;
68
+ docText: string;
69
+ }
70
+ /** One document ensureBuilderFiles could not generate a builder file for. */
71
+ export interface UnbuildableDoc {
72
+ name: string;
73
+ message: string;
74
+ }
75
+ export interface EnsureBuilderFilesResult {
76
+ generated: GeneratedBuilderFile[];
77
+ problems: UnbuildableDoc[];
78
+ }
79
+ /**
80
+ * Writes `<name>.flow.ts` for every document in `dir` that has none.
81
+ *
82
+ * A directory holding only FlowDocs (an export from a live instance, a doc
83
+ * copied out of conformance/, a file a colleague sent) had no builder file, so
84
+ * the loop the studio is built around - edit the builder file, watch the canvas
85
+ * follow - could not start there: there was nothing to edit. Generating the
86
+ * file on open is safe because codegen is deterministic and the studio already
87
+ * regenerates it on every canvas save, so this writes exactly the bytes the
88
+ * first save would have written.
89
+ *
90
+ * It goes through writePair, which also stamps the document with the
91
+ * meta.sourceHash of the source just generated. Without that stamp the watcher
92
+ * would meet a pair it has never seen in sync and report a conflict on the
93
+ * first edit, which is the state a hand-assembled pair lands in. The bytes
94
+ * come back so the caller can hand them to the watcher's noteWrite as well:
95
+ * the stamp alone leaves the outcome resting on the initial scan winning a
96
+ * race against the user's first edit.
97
+ *
98
+ * A document that cannot be generated from (invalid, or holding something
99
+ * codegen cannot express) is reported and skipped, not thrown: the rest of the
100
+ * directory still opens.
101
+ */
102
+ export declare function ensureBuilderFiles(dir: string): Promise<EnsureBuilderFilesResult>;
103
+ //# sourceMappingURL=pair.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pair.d.ts","sourceRoot":"","sources":["../../src/bridge/pair.ts"],"names":[],"mappings":"AA0BA,OAAO,EAAW,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAI3D,OAAO,EAAW,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAIL,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,eAAe,CAAC;AAEvB,oEAAoE;AACpE,qBAAa,WAAY,SAAQ,KAAK;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK5C;AAED;;;;GAIG;AACH,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,kBAAkB;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBAEZ,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK5C;AAED,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,SAAS,CAS9D;AAED,kEAAkE;AAClE,wBAAsB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAYjE;AAED,2EAA2E;AAC3E,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAe5D;AAED,0DAA0D;AAC1D,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC,CASnF;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,OAAO,EACZ,OAAO,CAAC,EAAE,WAAW,EACrB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,iBAAiB,CAAC,CAyD5B;AAED,wDAAwD;AACxD,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAW3E;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,WAAW,GACpB,OAAO,CAAC,gBAAgB,CAAC,CAU3B;AAED,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,oBAAoB,EAAE,CAAC;IAClC,QAAQ,EAAE,cAAc,EAAE,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAiBvF"}