@flat8n/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 (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +155 -0
  3. package/dist/src/cli/api.d.ts +10 -0
  4. package/dist/src/cli/api.js +57 -0
  5. package/dist/src/cli/api.js.map +1 -0
  6. package/dist/src/cli/build.d.ts +2 -0
  7. package/dist/src/cli/build.js +162 -0
  8. package/dist/src/cli/build.js.map +1 -0
  9. package/dist/src/cli/compile.d.ts +2 -0
  10. package/dist/src/cli/compile.js +149 -0
  11. package/dist/src/cli/compile.js.map +1 -0
  12. package/dist/src/cli/deploy.d.ts +4 -0
  13. package/dist/src/cli/deploy.js +47 -0
  14. package/dist/src/cli/deploy.js.map +1 -0
  15. package/dist/src/cli/dev.d.ts +2 -0
  16. package/dist/src/cli/dev.js +54 -0
  17. package/dist/src/cli/dev.js.map +1 -0
  18. package/dist/src/cli/init.d.ts +1 -0
  19. package/dist/src/cli/init.js +237 -0
  20. package/dist/src/cli/init.js.map +1 -0
  21. package/dist/src/cli/main.d.ts +2 -0
  22. package/dist/src/cli/main.js +249 -0
  23. package/dist/src/cli/main.js.map +1 -0
  24. package/dist/src/cli/run.d.ts +2 -0
  25. package/dist/src/cli/run.js +84 -0
  26. package/dist/src/cli/run.js.map +1 -0
  27. package/dist/src/cli/server.d.ts +2 -0
  28. package/dist/src/cli/server.js +62 -0
  29. package/dist/src/cli/server.js.map +1 -0
  30. package/dist/src/cli/setup.d.ts +1 -0
  31. package/dist/src/cli/setup.js +62 -0
  32. package/dist/src/cli/setup.js.map +1 -0
  33. package/dist/src/cli/sync.d.ts +4 -0
  34. package/dist/src/cli/sync.js +45 -0
  35. package/dist/src/cli/sync.js.map +1 -0
  36. package/dist/src/cli/ui.d.ts +5 -0
  37. package/dist/src/cli/ui.js +66 -0
  38. package/dist/src/cli/ui.js.map +1 -0
  39. package/dist/src/compiler/analyze.d.ts +15 -0
  40. package/dist/src/compiler/analyze.js +26 -0
  41. package/dist/src/compiler/analyze.js.map +1 -0
  42. package/dist/src/compiler/diagnostics.d.ts +28 -0
  43. package/dist/src/compiler/diagnostics.js +26 -0
  44. package/dist/src/compiler/diagnostics.js.map +1 -0
  45. package/dist/src/compiler/inputs.d.ts +30 -0
  46. package/dist/src/compiler/inputs.js +2 -0
  47. package/dist/src/compiler/inputs.js.map +1 -0
  48. package/dist/src/compiler/json.d.ts +15 -0
  49. package/dist/src/compiler/json.js +83 -0
  50. package/dist/src/compiler/json.js.map +1 -0
  51. package/dist/src/compiler/link.d.ts +18 -0
  52. package/dist/src/compiler/link.js +84 -0
  53. package/dist/src/compiler/link.js.map +1 -0
  54. package/dist/src/compiler/stable-json.d.ts +3 -0
  55. package/dist/src/compiler/stable-json.js +18 -0
  56. package/dist/src/compiler/stable-json.js.map +1 -0
  57. package/dist/src/compiler/validate.d.ts +9 -0
  58. package/dist/src/compiler/validate.js +25 -0
  59. package/dist/src/compiler/validate.js.map +1 -0
  60. package/dist/src/compiler/watch.d.ts +19 -0
  61. package/dist/src/compiler/watch.js +67 -0
  62. package/dist/src/compiler/watch.js.map +1 -0
  63. package/dist/src/compiler/workflow-file.d.ts +2 -0
  64. package/dist/src/compiler/workflow-file.js +27 -0
  65. package/dist/src/compiler/workflow-file.js.map +1 -0
  66. package/dist/src/generator/n8n-generator.d.ts +3 -0
  67. package/dist/src/generator/n8n-generator.js +129 -0
  68. package/dist/src/generator/n8n-generator.js.map +1 -0
  69. package/dist/src/graph/graph.d.ts +11 -0
  70. package/dist/src/graph/graph.js +76 -0
  71. package/dist/src/graph/graph.js.map +1 -0
  72. package/dist/src/index.d.ts +7 -0
  73. package/dist/src/index.js +5 -0
  74. package/dist/src/index.js.map +1 -0
  75. package/dist/src/ir/model.d.ts +58 -0
  76. package/dist/src/ir/model.js +29 -0
  77. package/dist/src/ir/model.js.map +1 -0
  78. package/dist/src/parser/n8n-parser.d.ts +8 -0
  79. package/dist/src/parser/n8n-parser.js +119 -0
  80. package/dist/src/parser/n8n-parser.js.map +1 -0
  81. package/dist/src/parser/n8n-schema.d.ts +60 -0
  82. package/dist/src/parser/n8n-schema.js +35 -0
  83. package/dist/src/parser/n8n-schema.js.map +1 -0
  84. package/dist/src/passes/auto-layout.d.ts +6 -0
  85. package/dist/src/passes/auto-layout.js +52 -0
  86. package/dist/src/passes/auto-layout.js.map +1 -0
  87. package/dist/src/passes/credential-isolation.d.ts +10 -0
  88. package/dist/src/passes/credential-isolation.js +56 -0
  89. package/dist/src/passes/credential-isolation.js.map +1 -0
  90. package/dist/src/passes/deterministic-ids.d.ts +7 -0
  91. package/dist/src/passes/deterministic-ids.js +40 -0
  92. package/dist/src/passes/deterministic-ids.js.map +1 -0
  93. package/dist/src/passes/expression-rewriter.d.ts +7 -0
  94. package/dist/src/passes/expression-rewriter.js +294 -0
  95. package/dist/src/passes/expression-rewriter.js.map +1 -0
  96. package/dist/src/passes/inline-subworkflows.d.ts +11 -0
  97. package/dist/src/passes/inline-subworkflows.js +437 -0
  98. package/dist/src/passes/inline-subworkflows.js.map +1 -0
  99. package/dist/src/passes/pass.d.ts +10 -0
  100. package/dist/src/passes/pass.js +2 -0
  101. package/dist/src/passes/pass.js.map +1 -0
  102. package/dist/src/validation/graph-integrity.d.ts +9 -0
  103. package/dist/src/validation/graph-integrity.js +104 -0
  104. package/dist/src/validation/graph-integrity.js.map +1 -0
  105. package/package.json +60 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 flatt8n contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,155 @@
1
+ <a id="readme-top"></a>
2
+
3
+ <div align="center">
4
+
5
+ # flat8n
6
+
7
+ **Native n8n workflow packager.**
8
+
9
+ `flat8n` parses your project, resolves dependencies across multiple files, and bundles them into a single monolithic n8n workflow JSON ready for deployment.
10
+
11
+ [![npm](https://img.shields.io/npm/v/@flat8n/cli?label=npm)](https://www.npmjs.com/package/@flat8n/cli)
12
+ [![n8n](https://img.shields.io/badge/n8n-Compatible-FF6E4A.svg)](https://n8n.io)
13
+ [![flat8n](https://img.shields.io/badge/flat8n-FF6E4A.svg)](https://github.com/jvondev/flat8n)
14
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
15
+ [![GitHub stars](https://img.shields.io/github/stars/jvondev/flat8n?style=social)](https://github.com/jvondev/flat8n)
16
+
17
+ [Quick Start](#quick-start) &nbsp;•&nbsp;
18
+ [Why You Need This](#why-you-need-this) &nbsp;•&nbsp;
19
+ [Features](#features) &nbsp;•&nbsp;
20
+ [API Reference](#api-reference) &nbsp;•&nbsp;
21
+ [CLI Reference](#cli-reference)
22
+
23
+ </div>
24
+
25
+ ## Quick Start
26
+
27
+ > [!TIP]
28
+ > **Start a new project**
29
+ > The modern, code-first way to scaffold a best-practice n8n project is:
30
+ > ```bash
31
+ > npx @flat8n/cli init
32
+ > ```
33
+
34
+ > [!NOTE]
35
+ > **Run in an existing project**
36
+ > If you already have workflows, just run the packager directly to start the dev server:
37
+ > ```bash
38
+ > npx @flat8n/cli
39
+ > ```
40
+
41
+ <details>
42
+ <summary><b>Other Installation Methods</b></summary>
43
+
44
+ Global installation:
45
+ ```bash
46
+ npm install -g @flat8n/cli
47
+ ```
48
+
49
+ Local installation for programmatic usage:
50
+ ```bash
51
+ npm install @flat8n/cli
52
+ ```
53
+ </details>
54
+
55
+ ## Why You Need This
56
+
57
+ Building an n8n workflow with dozens of nodes in a single file quickly becomes unmanageable. Finding specific nodes takes time, reusing logic across projects requires copy-pasting, and collaborating with a team in Git produces merge conflicts.
58
+
59
+ `flat8n` lets you split your workflow into small, manageable, logical pieces. When you run `npx @flat8n/cli`, it reads those separate files, links them together, and compiles them into **one single monolithic workflow JSON**. You import this single output file directly into n8n and execute it without external dependencies.
60
+
61
+ | Factor | Without `flat8n` | With `flat8n` |
62
+ |---|---|---|
63
+ | **File Structure** | One massive `.json` file. | Multiple modular, focused `.json` files. |
64
+ | **Git Collaboration** | Constant merge conflicts on canvas coordinates. | Clean commits, easy pull-request reviews. |
65
+ | **Reusability** | Copy-paste nodes between workflows. | Write once, reference anywhere via sub-workflows. |
66
+ | **Validation** | Discover broken links at runtime in n8n. | CLI validates graph integrity upfront. |
67
+ | **Deployment** | Manual import/export via the n8n UI. | Outputs a single `bundled.json` ready for CI/CD. |
68
+
69
+ ## Features
70
+
71
+ - **Project Scaffolding** — Run `npx @flat8n/cli init` to instantly generate a robust, domain-native folder structure for your modular workflows.
72
+ - **Zero-config Packager** — Run `npx @flat8n/cli` without arguments to automatically detect your workflows, bundle them into a single file in `dist`, and optionally boot a local n8n instance for testing.
73
+ - **Dependency Linking** — Write modular workflows and let `flat8n` parse them, build graph intermediate representation (IR), and inline referenced sub-workflows.
74
+ - **Native Validation** — Catch errors early. The compiler validates your output against native n8n workflow structures and verifies graph integrity.
75
+ - **Parse Caching** — The watcher only recompiles changed files, using metadata-based parse caching for fast rebuilds.
76
+ - **Graph Analysis** — Inspect workflow structures, node counts, and dependency chains programmatically or via CLI.
77
+
78
+ ## What This Looks Like
79
+
80
+ ### The Packager In Action
81
+
82
+ When you run the tool without parameters, it acts as a unified environment:
83
+
84
+ ```text
85
+ > npx @flat8n/cli
86
+ [+] Auto-detected workflows
87
+ [+] Watching for changes...
88
+ [+] Booting local n8n instance
89
+ [+] Deployed "bundled.json" to local n8n
90
+ ```
91
+
92
+ ### Explicit Linking
93
+
94
+ If you prefer explicit linking over the auto-detect feature, pass arguments directly:
95
+
96
+ ```text
97
+ > flat8n link --entry workflow.json --dep child=child.json --out bundled.json
98
+ ```
99
+
100
+ ### Programmatic Linking
101
+
102
+ ```typescript
103
+ import { link } from "flat8n";
104
+
105
+ const bundled = link({
106
+ entry: "workflow.json",
107
+ dependencies: {
108
+ child: "child.json"
109
+ }
110
+ });
111
+ // bundled is a deterministic, native monolithic n8n workflow JSON
112
+ ```
113
+
114
+ ## API Reference
115
+
116
+ <details>
117
+ <summary><b>Available Functions</b></summary>
118
+
119
+ ```typescript
120
+ import { analyze, link, validate, watch } from "flat8n";
121
+ ```
122
+
123
+ | Function | Description |
124
+ |----------|-------------|
125
+ | `link()` | Parses workflows, builds graph IR, inlines referenced workflows, validates output, and returns deterministic JSON. |
126
+ | `validate()` | Checks native n8n workflow structure and graph integrity. |
127
+ | `analyze()` | Returns graph stats and diagnostics. |
128
+ | `watch()` | Recompiles file inputs with metadata-based parse caching. |
129
+
130
+ </details>
131
+
132
+ ## CLI Reference
133
+
134
+ <details>
135
+ <summary><b>Available Commands</b></summary>
136
+
137
+ | Scenario | Command |
138
+ |----------|---------|
139
+ | Scaffold a new n8n project | `npx @flat8n/cli init` |
140
+ | Start the packager | `npx @flat8n/cli` |
141
+ | Link and bundle a workflow | `npx @flat8n/cli link --entry workflow.json --dep child=child.json --out bundled.json` |
142
+ | Validate a workflow's integrity | `npx @flat8n/cli validate --entry workflow.json` |
143
+ | Analyze a workflow's graph | `npx @flat8n/cli analyze --entry workflow.json --dep child=child.json` |
144
+
145
+ </details>
146
+
147
+ ## Contributing
148
+
149
+ Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to the project, and our [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for the code of conduct.
150
+
151
+ <div align="center">
152
+
153
+ <a href="#readme-top">↑ back to top</a>
154
+
155
+ </div>
@@ -0,0 +1,10 @@
1
+ import type { JsonObject } from "../compiler/json.js";
2
+ export declare function upsertWorkflow(options: {
3
+ host?: string;
4
+ apiKey?: string | undefined;
5
+ }, parsedWorkflow: JsonObject): Promise<{
6
+ success: true;
7
+ } | {
8
+ success: false;
9
+ error: string;
10
+ }>;
@@ -0,0 +1,57 @@
1
+ export async function upsertWorkflow(options, parsedWorkflow) {
2
+ const targetName = String(parsedWorkflow.name);
3
+ // Default to localhost if no host is provided via options or environment variables
4
+ const host = options.host || process.env.N8N_HOST || "http://localhost:5678";
5
+ const apiBase = host.endsWith("/") ? `${host}api/v1` : `${host}/api/v1`;
6
+ // Clean payload for upload by creating a shallow copy
7
+ const payload = { ...parsedWorkflow };
8
+ delete payload.id;
9
+ delete payload.createdAt;
10
+ delete payload.updatedAt;
11
+ delete payload.tags;
12
+ let existingId = null;
13
+ const headers = {};
14
+ if (options.apiKey) {
15
+ headers["X-N8N-API-KEY"] = options.apiKey;
16
+ }
17
+ try {
18
+ const listRes = await fetch(`${apiBase}/workflows`, {
19
+ headers,
20
+ signal: AbortSignal.timeout(10000)
21
+ });
22
+ if (listRes.ok) {
23
+ const data = await listRes.json();
24
+ const existing = (data.data || []).find((w) => w.name === targetName);
25
+ if (existing)
26
+ existingId = existing.id;
27
+ }
28
+ }
29
+ catch (e) {
30
+ // Ignore list failure, will just try POST
31
+ }
32
+ const method = existingId ? "PUT" : "POST";
33
+ const url = existingId ? `${apiBase}/workflows/${existingId}` : `${apiBase}/workflows`;
34
+ let res;
35
+ try {
36
+ res = await fetch(url, {
37
+ method,
38
+ headers: {
39
+ ...headers,
40
+ "Content-Type": "application/json"
41
+ },
42
+ signal: AbortSignal.timeout(10000),
43
+ body: JSON.stringify(payload)
44
+ });
45
+ }
46
+ catch (e) {
47
+ return { success: false, error: `Connection failed to ${host}. Is n8n running?` };
48
+ }
49
+ if (res.ok) {
50
+ return { success: true };
51
+ }
52
+ else {
53
+ const errorBody = await res.text();
54
+ return { success: false, error: `${res.status} - ${errorBody}` };
55
+ }
56
+ }
57
+ //# sourceMappingURL=api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.js","sourceRoot":"","sources":["../../../src/cli/api.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,OAAuD,EAAE,cAA0B;IACtH,MAAM,UAAU,GAAG,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC;IAE/C,mFAAmF;IACnF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,uBAAuB,CAAC;IAC7E,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,SAAS,CAAC;IAExE,sDAAsD;IACtD,MAAM,OAAO,GAA4B,EAAE,GAAG,cAAc,EAAE,CAAC;IAC/D,OAAO,OAAO,CAAC,EAAE,CAAC;IAClB,OAAO,OAAO,CAAC,SAAS,CAAC;IACzB,OAAO,OAAO,CAAC,SAAS,CAAC;IACzB,OAAO,OAAO,CAAC,IAAI,CAAC;IAEpB,IAAI,UAAU,GAAkB,IAAI,CAAC;IACrC,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,CAAC,eAAe,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IAC5C,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,YAAY,EAAE;YAClD,OAAO;YACP,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,CAAC,CAAC;QACH,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC;YAClC,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;YAC3E,IAAI,QAAQ;gBAAE,UAAU,GAAG,QAAQ,CAAC,EAAE,CAAC;QACzC,CAAC;IACH,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,0CAA0C;IAC5C,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC3C,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,OAAO,cAAc,UAAU,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,YAAY,CAAC;IAEvF,IAAI,GAAG,CAAC;IACR,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YACrB,MAAM;YACN,OAAO,EAAE;gBACP,GAAG,OAAO;gBACV,cAAc,EAAE,kBAAkB;aACnC;YACD,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;YAClC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;SAC9B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,wBAAwB,IAAI,mBAAmB,EAAE,CAAC;IACpF,CAAC;IAED,IAAI,GAAG,CAAC,EAAE,EAAE,CAAC;QACX,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QACnC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC,MAAM,MAAM,SAAS,EAAE,EAAE,CAAC;IACnE,CAAC;AACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { LinkOptions } from "../compiler/inputs.js";
2
+ export declare function runBuild(options: LinkOptions): Promise<number>;
@@ -0,0 +1,162 @@
1
+ import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
2
+ import { resolve, join, basename, extname } from "node:path";
3
+ import pc from "picocolors";
4
+ import { parseN8nWorkflow } from "../parser/n8n-parser.js";
5
+ import { link } from "../compiler/link.js";
6
+ import { formatDiagnostic } from "../compiler/diagnostics.js";
7
+ import { asJsonValue, isJsonObject } from "../compiler/json.js";
8
+ import { showBranding } from "./ui.js";
9
+ const IGNORED_DIRS = new Set(["node_modules", "dist", "build", "coverage"]);
10
+ const IGNORED_FILES = new Set(["package.json", "package-lock.json", "tsconfig.json"]);
11
+ async function findJsonFiles(dir) {
12
+ try {
13
+ const entries = await readdir(dir, { withFileTypes: true });
14
+ const nested = await Promise.all(entries.map(async (entry) => {
15
+ if (entry.isDirectory()) {
16
+ // Skip hidden folders (like .git, .n8n) and explicitly ignored directories
17
+ if (!IGNORED_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
18
+ return findJsonFiles(join(dir, entry.name));
19
+ }
20
+ return [];
21
+ }
22
+ else if (entry.isFile() && entry.name.endsWith(".json") && !IGNORED_FILES.has(entry.name)) {
23
+ return [join(dir, entry.name)];
24
+ }
25
+ return [];
26
+ }));
27
+ return nested.flat();
28
+ }
29
+ catch (error) {
30
+ // Ignore permissions or missing directory errors quietly
31
+ return [];
32
+ }
33
+ }
34
+ export async function runBuild(options) {
35
+ const cwd = process.cwd();
36
+ // Only show branding if running standalone build, not during dev watch loops
37
+ if (!process.env.FLAT8N_DEV_MODE) {
38
+ showBranding();
39
+ }
40
+ process.stdout.write(pc.cyan("ℹ Scanning workspace for n8n workflows...\n"));
41
+ const jsonFiles = await findJsonFiles(cwd);
42
+ if (jsonFiles.length === 0) {
43
+ process.stdout.write("No JSON workflow files found in workspace.\n");
44
+ return 0;
45
+ }
46
+ const workflows = new Map();
47
+ await Promise.all(jsonFiles.map(async (file) => {
48
+ const absolutePath = resolve(cwd, file);
49
+ try {
50
+ const text = await readFile(absolutePath, "utf8");
51
+ const parsed = JSON.parse(text);
52
+ const workflowValue = asJsonValue(parsed);
53
+ if (workflowValue !== undefined && isJsonObject(workflowValue)) {
54
+ // Simple heuristic: n8n workflows have a 'nodes' array
55
+ if (Array.isArray(workflowValue.nodes)) {
56
+ const workflowId = typeof workflowValue.id === "string" ? workflowValue.id : basename(file, extname(file));
57
+ workflows.set(workflowId, {
58
+ id: workflowId,
59
+ path: absolutePath,
60
+ workflow: workflowValue
61
+ });
62
+ }
63
+ }
64
+ }
65
+ catch (e) {
66
+ // ignore invalid json files quietly
67
+ }
68
+ }));
69
+ if (workflows.size === 0) {
70
+ process.stdout.write("No valid n8n workflow files found.\n");
71
+ return 0;
72
+ }
73
+ process.stdout.write(pc.cyan(`ℹ Found ${workflows.size} valid workflow(s). Analyzing dependencies...\n`));
74
+ const referencedIds = new Set();
75
+ for (const source of workflows.values()) {
76
+ const parsed = parseN8nWorkflow(source);
77
+ if (parsed.workflow) {
78
+ for (const node of parsed.workflow.nodes) {
79
+ if (node.type === "n8n-nodes-base.executeWorkflow" || node.type.endsWith(".executeWorkflow")) {
80
+ // Check standard parameter fields for subworkflow ID/Name
81
+ for (const key of ["workflowId", "workflow", "workflowName"]) {
82
+ const val = node.parameters[key];
83
+ if (typeof val === "string" || typeof val === "number") {
84
+ referencedIds.add(String(val));
85
+ }
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ // Top-level workflows are those NOT called by any Execute Workflow nodes
92
+ const topLevelWorkflows = Array.from(workflows.values()).filter(w => !referencedIds.has(w.id));
93
+ if (topLevelWorkflows.length === 0) {
94
+ process.stderr.write(pc.yellow("⚠ Could not identify any top-level workflows. Are there circular dependencies?\n"));
95
+ return 1;
96
+ }
97
+ process.stdout.write(pc.cyan(`ℹ Identified ${topLevelWorkflows.length} top-level workflow(s) to build.\n`));
98
+ const distDir = resolve(cwd, "dist");
99
+ await mkdir(distDir, { recursive: true });
100
+ let hasError = false;
101
+ for (const entry of topLevelWorkflows) {
102
+ const workflowName = typeof entry.workflow.name === "string" ? entry.workflow.name : entry.id;
103
+ process.stdout.write(pc.dim(`\n⚙ Linking: ${workflowName} (${entry.id})\n`));
104
+ // Provide all other workflows as dependencies
105
+ const deps = {};
106
+ for (const [id, w] of workflows.entries()) {
107
+ if (id !== entry.id) {
108
+ deps[id] = w;
109
+ }
110
+ }
111
+ const result = link({
112
+ entry,
113
+ dependencies: deps,
114
+ options
115
+ });
116
+ for (const diag of result.diagnostics) {
117
+ const line = formatDiagnostic(diag);
118
+ if (diag.severity === "error") {
119
+ process.stderr.write(`${line}\n`);
120
+ hasError = true;
121
+ }
122
+ else {
123
+ process.stdout.write(`${line}\n`);
124
+ }
125
+ }
126
+ if (result.ok) {
127
+ let outName = sanitizeName(workflowName);
128
+ outName = `${outName}.json`;
129
+ const outPath = resolve(distDir, outName);
130
+ await writeFile(outPath, result.json, "utf8");
131
+ process.stdout.write(pc.green(`✔ Exported: dist/${outName}\n`));
132
+ }
133
+ }
134
+ if (hasError) {
135
+ process.stderr.write(pc.red("\n✖ Build completed with errors.\n"));
136
+ return 1;
137
+ }
138
+ else {
139
+ process.stdout.write(pc.green("\n✔ Build completed successfully.\n"));
140
+ // Only prompt if running interactively and not inside dev watcher
141
+ if (process.stdout.isTTY && !process.env.FLAT8N_DEV_MODE) {
142
+ const { confirm } = await import("@clack/prompts");
143
+ const { runSync } = await import("./sync.js");
144
+ const shouldSync = await confirm({
145
+ message: "Would you like to deploy these compiled workflows to n8n directly?",
146
+ initialValue: false
147
+ });
148
+ if (shouldSync) {
149
+ await runSync({ apiKey: process.env.N8N_API_KEY });
150
+ }
151
+ }
152
+ return 0;
153
+ }
154
+ }
155
+ function sanitizeName(value) {
156
+ const sanitized = value
157
+ .trim()
158
+ .replaceAll(/[^A-Za-z0-9_$-]+/gu, "-")
159
+ .replaceAll(/^-+|-+$/gu, "");
160
+ return sanitized.length === 0 ? "workflow" : sanitized;
161
+ }
162
+ //# sourceMappingURL=build.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../src/cli/build.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7D,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,CAAC,CAAC,CAAC;AAC5E,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,cAAc,EAAE,mBAAmB,EAAE,eAAe,CAAC,CAAC,CAAC;AAEtF,KAAK,UAAU,aAAa,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5D,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,GAAG,CAC9B,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;YAC1B,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;gBACxB,2EAA2E;gBAC3E,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;oBACjE,OAAO,aAAa,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;gBAC9C,CAAC;gBACD,OAAO,EAAE,CAAC;YACZ,CAAC;iBAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC5F,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;YACjC,CAAC;YACD,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC,CACH,CAAC;QACF,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC;IACvB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,yDAAyD;QACzD,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,OAAoB;IACjD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,6EAA6E;IAC7E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;QACjC,YAAY,EAAE,CAAC;IACjB,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC,CAAC;IAE7E,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;IAE3C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8CAA8C,CAAC,CAAC;QACrE,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,GAAG,EAA0B,CAAC;IAEpD,MAAM,OAAO,CAAC,GAAG,CACf,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAC3B,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;YAClD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;YAChC,MAAM,aAAa,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;YAC1C,IAAI,aAAa,KAAK,SAAS,IAAI,YAAY,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC/D,uDAAuD;gBACvD,IAAI,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC;oBACvC,MAAM,UAAU,GAAG,OAAO,aAAa,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;oBAC3G,SAAS,CAAC,GAAG,CAAC,UAAU,EAAE;wBACxB,EAAE,EAAE,UAAU;wBACd,IAAI,EAAE,YAAY;wBAClB,QAAQ,EAAE,aAAa;qBACxB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;QACH,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,oCAAoC;QACtC,CAAC;IACH,CAAC,CAAC,CACH,CAAC;IAEF,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC7D,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,WAAW,SAAS,CAAC,IAAI,iDAAiD,CAAC,CAAC,CAAC;IAE1G,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IAExC,KAAK,MAAM,MAAM,IAAI,SAAS,CAAC,MAAM,EAAE,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;QACxC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;YACpB,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;gBACzC,IAAI,IAAI,CAAC,IAAI,KAAK,gCAAgC,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;oBAC7F,0DAA0D;oBAC1D,KAAK,MAAM,GAAG,IAAI,CAAC,YAAY,EAAE,UAAU,EAAE,cAAc,CAAC,EAAE,CAAC;wBAC7D,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;wBACjC,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;4BACrD,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;wBACnC,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,MAAM,iBAAiB,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAE/F,IAAI,iBAAiB,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,kFAAkF,CAAC,CAAC,CAAC;QACpH,OAAO,CAAC,CAAC;IACX,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,gBAAgB,iBAAiB,CAAC,MAAM,oCAAoC,CAAC,CAAC,CAAC;IAE5G,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACrC,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,IAAI,QAAQ,GAAG,KAAK,CAAC;IAErB,KAAK,MAAM,KAAK,IAAI,iBAAiB,EAAE,CAAC;QACtC,MAAM,YAAY,GAAG,OAAO,KAAK,CAAC,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9F,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,gBAAgB,YAAY,KAAK,KAAK,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;QAE7E,8CAA8C;QAC9C,MAAM,IAAI,GAAmC,EAAE,CAAC;QAChD,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,SAAS,CAAC,OAAO,EAAE,EAAE,CAAC;YAC1C,IAAI,EAAE,KAAK,KAAK,CAAC,EAAE,EAAE,CAAC;gBACpB,IAAI,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;YACf,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAG,IAAI,CAAC;YAClB,KAAK;YACL,YAAY,EAAE,IAAI;YAClB,OAAO;SACR,CAAC,CAAC;QAEH,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;YACpC,IAAI,IAAI,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBAC9B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;gBAClC,QAAQ,GAAG,IAAI,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,IAAI,CAAC,CAAC;YACpC,CAAC;QACH,CAAC;QAED,IAAI,MAAM,CAAC,EAAE,EAAE,CAAC;YACZ,IAAI,OAAO,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;YACzC,OAAO,GAAG,GAAG,OAAO,OAAO,CAAC;YAC5B,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YAC1C,MAAM,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,oBAAoB,OAAO,IAAI,CAAC,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC,CAAC;QACnE,OAAO,CAAC,CAAC;IACX,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,qCAAqC,CAAC,CAAC,CAAC;QAEtE,kEAAkE;QAClE,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC;YACzD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;YACnD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,CAAC;YAE9C,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC;gBAC/B,OAAO,EAAE,oEAAoE;gBAC7E,YAAY,EAAE,KAAK;aACpB,CAAC,CAAC;YAEH,IAAI,UAAU,EAAE,CAAC;gBACf,MAAM,OAAO,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC;YACrD,CAAC;QACH,CAAC;QAED,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,KAAa;IACjC,MAAM,SAAS,GAAG,KAAK;SACpB,IAAI,EAAE;SACN,UAAU,CAAC,oBAAoB,EAAE,GAAG,CAAC;SACrC,UAAU,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAC/B,OAAO,SAAS,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;AACzD,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { LinkOptions } from "../compiler/inputs.js";
2
+ export declare function runCompile(options: LinkOptions): Promise<number>;
@@ -0,0 +1,149 @@
1
+ import { readdir, readFile, mkdir, writeFile } from "node:fs/promises";
2
+ import { resolve, join, basename, extname } from "node:path";
3
+ import pc from "picocolors";
4
+ import { log, spinner } from "@clack/prompts";
5
+ import { parseN8nWorkflow } from "../parser/n8n-parser.js";
6
+ import { link } from "../compiler/link.js";
7
+ import { formatDiagnostic } from "../compiler/diagnostics.js";
8
+ import { asJsonValue, isJsonObject } from "../compiler/json.js";
9
+ import { showBranding } from "./ui.js";
10
+ const IGNORED_DIRS = new Set(["node_modules", "dist", "build", "coverage"]);
11
+ const IGNORED_FILES = new Set(["package.json", "package-lock.json", "tsconfig.json"]);
12
+ async function findJsonFiles(dir) {
13
+ try {
14
+ const entries = await readdir(dir, { withFileTypes: true });
15
+ const nested = await Promise.all(entries.map(async (entry) => {
16
+ if (entry.isDirectory()) {
17
+ // Skip hidden folders (like .git, .n8n) and explicitly ignored directories
18
+ if (!IGNORED_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
19
+ return findJsonFiles(join(dir, entry.name));
20
+ }
21
+ return [];
22
+ }
23
+ else if (entry.isFile() && entry.name.endsWith(".json") && !IGNORED_FILES.has(entry.name)) {
24
+ return [join(dir, entry.name)];
25
+ }
26
+ return [];
27
+ }));
28
+ return nested.flat();
29
+ }
30
+ catch (error) {
31
+ // Ignore permissions or missing directory errors quietly
32
+ return [];
33
+ }
34
+ }
35
+ export async function runCompile(options) {
36
+ const cwd = process.cwd();
37
+ // Only show branding if running standalone build, not during dev watch loops
38
+ if (!process.env.FLATT8N_DEV_MODE) {
39
+ showBranding();
40
+ }
41
+ const s = spinner();
42
+ s.start(pc.cyan("Compiling workflows..."));
43
+ const jsonFiles = await findJsonFiles(cwd);
44
+ if (jsonFiles.length === 0) {
45
+ s.stop("No JSON workflow files found in workspace.");
46
+ return 0;
47
+ }
48
+ const workflows = new Map();
49
+ await Promise.all(jsonFiles.map(async (file) => {
50
+ const absolutePath = resolve(cwd, file);
51
+ try {
52
+ const text = await readFile(absolutePath, "utf8");
53
+ const parsed = JSON.parse(text);
54
+ const workflowValue = asJsonValue(parsed);
55
+ if (workflowValue !== undefined && isJsonObject(workflowValue)) {
56
+ // Simple heuristic: n8n workflows have a 'nodes' array
57
+ if (Array.isArray(workflowValue.nodes)) {
58
+ const workflowId = typeof workflowValue.id === "string" ? workflowValue.id : basename(file, extname(file));
59
+ workflows.set(workflowId, {
60
+ id: workflowId,
61
+ path: absolutePath,
62
+ workflow: workflowValue
63
+ });
64
+ }
65
+ }
66
+ }
67
+ catch (e) {
68
+ // ignore invalid json files quietly
69
+ }
70
+ }));
71
+ if (workflows.size === 0) {
72
+ s.stop("No valid n8n workflow files found.");
73
+ return 0;
74
+ }
75
+ const referencedIds = new Set();
76
+ for (const source of workflows.values()) {
77
+ const parsed = parseN8nWorkflow(source);
78
+ if (parsed.workflow) {
79
+ for (const node of parsed.workflow.nodes) {
80
+ if (node.type === "n8n-nodes-base.executeWorkflow" || node.type.endsWith(".executeWorkflow")) {
81
+ // Check standard parameter fields for subworkflow ID/Name
82
+ for (const key of ["workflowId", "workflow", "workflowName"]) {
83
+ const val = node.parameters[key];
84
+ if (typeof val === "string" || typeof val === "number") {
85
+ referencedIds.add(String(val));
86
+ }
87
+ }
88
+ }
89
+ }
90
+ }
91
+ }
92
+ // Top-level workflows are those NOT called by any Execute Workflow nodes
93
+ const topLevelWorkflows = Array.from(workflows.values()).filter(w => !referencedIds.has(w.id));
94
+ if (topLevelWorkflows.length === 0) {
95
+ s.stop("Compilation aborted.");
96
+ log.warn("Could not identify any top-level workflows. Are there circular dependencies?");
97
+ return 1;
98
+ }
99
+ const distDir = resolve(cwd, "dist");
100
+ await mkdir(distDir, { recursive: true });
101
+ let hasError = false;
102
+ for (const entry of topLevelWorkflows) {
103
+ const workflowName = typeof entry.workflow.name === "string" ? entry.workflow.name : entry.id;
104
+ // Provide all other workflows as dependencies
105
+ const deps = {};
106
+ for (const [id, w] of workflows.entries()) {
107
+ if (id !== entry.id) {
108
+ deps[id] = w;
109
+ }
110
+ }
111
+ const result = link({
112
+ entry,
113
+ dependencies: deps,
114
+ options
115
+ });
116
+ for (const diag of result.diagnostics) {
117
+ const line = formatDiagnostic(diag);
118
+ if (diag.severity === "error") {
119
+ log.error(line);
120
+ hasError = true;
121
+ }
122
+ else {
123
+ log.info(line);
124
+ }
125
+ }
126
+ if (result.ok) {
127
+ let outName = sanitizeName(workflowName);
128
+ outName = `${outName}.json`;
129
+ const outPath = resolve(distDir, outName);
130
+ await writeFile(outPath, result.json, "utf8");
131
+ }
132
+ }
133
+ if (hasError) {
134
+ s.stop("Compilation failed with errors.");
135
+ return 1;
136
+ }
137
+ else {
138
+ s.stop(`${topLevelWorkflows.length} workflow(s) successfully compiled to ${pc.cyan('./dist')}`);
139
+ return 0;
140
+ }
141
+ }
142
+ function sanitizeName(value) {
143
+ const sanitized = value
144
+ .trim()
145
+ .replaceAll(/[^A-Za-z0-9_$-]+/gu, "-")
146
+ .replaceAll(/^-+|-+$/gu, "");
147
+ return sanitized.length === 0 ? "workflow" : sanitized;
148
+ }
149
+ //# sourceMappingURL=compile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compile.js","sourceRoot":"","sources":["../../../src/cli/compile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export declare function runDeploy(options: {
2
+ host?: string;
3
+ apiKey?: string | undefined;
4
+ }): Promise<number>;
@@ -0,0 +1,47 @@
1
+ import { readdir, readFile } from "node:fs/promises";
2
+ import { resolve, join } from "node:path";
3
+ import { spinner, log } from "@clack/prompts";
4
+ import pc from "picocolors";
5
+ import { upsertWorkflow } from "./api.js";
6
+ export async function runDeploy(options) {
7
+ const cwd = process.cwd();
8
+ const distDir = resolve(cwd, "dist");
9
+ let files;
10
+ try {
11
+ const entries = await readdir(distDir, { withFileTypes: true });
12
+ files = entries.filter(e => e.isFile() && e.name.endsWith(".json")).map(e => e.name);
13
+ }
14
+ catch (error) {
15
+ log.error(`Failed to read dist directory. Have you run 'flat8n' yet?`);
16
+ return 1;
17
+ }
18
+ if (files.length === 0) {
19
+ log.info(`No compiled workflows found in dist/`);
20
+ return 0;
21
+ }
22
+ const s = spinner();
23
+ s.start(pc.cyan("Synchronizing workflows to n8n database..."));
24
+ let successCount = 0;
25
+ for (const file of files) {
26
+ const filePath = join(distDir, file);
27
+ try {
28
+ const content = await readFile(filePath, "utf8");
29
+ const parsed = JSON.parse(content);
30
+ const result = await upsertWorkflow(options, parsed);
31
+ if (!result.success) {
32
+ s.stop("Deployment aborted.");
33
+ log.error(`Failed to deploy ${parsed.name || file}: ${result.error}`);
34
+ return 1;
35
+ }
36
+ successCount++;
37
+ }
38
+ catch (e) {
39
+ s.stop("Deployment aborted.");
40
+ log.error(`Failed to parse or upload ${file}`);
41
+ return 1;
42
+ }
43
+ }
44
+ s.stop(`${successCount} module(s) successfully deployed!`);
45
+ return 0;
46
+ }
47
+ //# sourceMappingURL=deploy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../../src/cli/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1C,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,OAAuD;IACrF,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAErC,IAAI,KAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;QAChE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACvF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,GAAG,CAAC,KAAK,CAAC,2DAA2D,CAAC,CAAC;QACvE,OAAO,CAAC,CAAC;IACX,CAAC;IAED,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,GAAG,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC;QACjD,OAAO,CAAC,CAAC;IACX,CAAC;IAED,MAAM,CAAC,GAAG,OAAO,EAAE,CAAC;IACpB,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC,CAAC;IAE/D,IAAI,YAAY,GAAG,CAAC,CAAC;IAErB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;YACjD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAEnC,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACrD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;gBAC9B,GAAG,CAAC,KAAK,CAAC,oBAAoB,MAAM,CAAC,IAAI,IAAI,IAAI,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;gBACtE,OAAO,CAAC,CAAC;YACX,CAAC;YACD,YAAY,EAAE,CAAC;QACjB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;YAC9B,GAAG,CAAC,KAAK,CAAC,6BAA6B,IAAI,EAAE,CAAC,CAAC;YAC/C,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IAED,CAAC,CAAC,IAAI,CAAC,GAAG,YAAY,mCAAmC,CAAC,CAAC;IAC3D,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { LinkOptions } from "../compiler/inputs.js";
2
+ export declare function runDev(options: LinkOptions): Promise<number>;