create-n8n 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -0
- package/dist/generator.js +24 -0
- package/dist/index.js +5 -185
- package/dist/src/generator.js +24 -0
- package/dist/src/index.js +72 -0
- package/dist/src/templates.js +143 -0
- package/dist/src/ui.js +14 -0
- package/dist/templates.js +143 -0
- package/dist/ui.js +14 -0
- package/package.json +1 -1
- package/src/generator.ts +37 -0
- package/src/index.ts +84 -0
- package/src/templates.ts +149 -0
- package/src/ui.ts +15 -0
- package/tsconfig.json +2 -1
- package/index.ts +0 -277
package/README.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<a id="readme-top"></a>
|
|
2
|
+
|
|
3
|
+
<div align="center">
|
|
4
|
+
|
|
5
|
+
# create-n8n
|
|
6
|
+
|
|
7
|
+
**The official scaffolding tool for modular n8n workflow projects.**
|
|
8
|
+
|
|
9
|
+
`create-n8n` generates a native, production-ready directory structure for building n8n workflows as code, powered by the flat8n compiler.
|
|
10
|
+
|
|
11
|
+
[](https://www.npmjs.com/package/create-n8n)
|
|
12
|
+
[](https://n8n.io)
|
|
13
|
+
[](https://github.com/jvondev/flat8n)
|
|
14
|
+
[](https://opensource.org/licenses/MIT)
|
|
15
|
+
|
|
16
|
+
[Quick Start](#quick-start) •
|
|
17
|
+
[Why You Need This](#why-you-need-this) •
|
|
18
|
+
[Features](#features) •
|
|
19
|
+
[Architecture](#architecture)
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
Run the interactive CLI wizard in your terminal to scaffold a new project:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm create n8n@latest
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
The CLI prompts you for a project name, generates the required directory structure, configures the environment variables, and initializes a Git repository.
|
|
32
|
+
|
|
33
|
+
Once scaffolded, boot the local development environment:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
cd your-project-name
|
|
37
|
+
npm install
|
|
38
|
+
npm run dev
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Why You Need This
|
|
42
|
+
|
|
43
|
+
Building n8n workflows through the browser UI is effective for small automations, but scales poorly for engineering teams. Version control becomes difficult, code reuse requires manual copy-pasting, and isolating environments introduces risk.
|
|
44
|
+
|
|
45
|
+
`create-n8n` bridges this gap by establishing a standard, code-first architecture. It integrates directly with `@flat8n/cli`, allowing you to write small, modular JSON workflows in your editor. The compiler then resolves cross-file dependencies and outputs a single, deterministic workflow ready for CI/CD deployment.
|
|
46
|
+
|
|
47
|
+
| Capability | Standard n8n Workspace | `create-n8n` Workspace |
|
|
48
|
+
|---|---|---|
|
|
49
|
+
| **Structure** | Unstructured exports. | Modular, domain-driven directories. |
|
|
50
|
+
| **Development** | Manual UI management. | Automated local dev server sync. |
|
|
51
|
+
| **Reusability** | Copy-paste nodes. | Shared sub-workflow modules. |
|
|
52
|
+
| **Security** | Embedded credentials. | `.env` isolated variables. |
|
|
53
|
+
|
|
54
|
+
## Features
|
|
55
|
+
|
|
56
|
+
- **Zero-config Environment.** The CLI provides a fully working local n8n instance synchronized with your file system out of the box.
|
|
57
|
+
- **Modular Compilation.** Build complex architectures by separating logic into sub-workflows. The underlying compiler merges them into a single deployable asset.
|
|
58
|
+
- **Hot Reloading.** Edit workflow structures in your code editor or the local n8n UI, and changes synchronize instantly across both.
|
|
59
|
+
- **Deterministic Builds.** The compiler generates stable Node IDs and Webhook IDs, ensuring production endpoints remain stable across deployments.
|
|
60
|
+
- **Secure Defaults.** Scaffolding includes `.gitignore` rules and `.env.example` templates to prevent credential leakage.
|
|
61
|
+
|
|
62
|
+
## Architecture
|
|
63
|
+
|
|
64
|
+
A project scaffolded with `create-n8n` uses a standard layout optimized for version control and modularity.
|
|
65
|
+
|
|
66
|
+
### Directory Layout
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
my-n8n-app/
|
|
70
|
+
├── workflows/
|
|
71
|
+
│ ├── main.json # The primary entrypoint workflow
|
|
72
|
+
│ └── subworkflows/
|
|
73
|
+
│ └── child.json # Reusable workflow modules
|
|
74
|
+
├── .env.example # Environment variable templates
|
|
75
|
+
├── .gitignore # Prevents committing secrets
|
|
76
|
+
├── flat8n.config.json # Compiler configuration
|
|
77
|
+
└── package.json # Scripts (dev, build, start)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Build Pipeline
|
|
81
|
+
|
|
82
|
+
The underlying compilation engine is the [`@flat8n/cli`](https://www.npmjs.com/package/@flat8n/cli) framework. When you execute `npm run build`, the compiler parses `main.json` and inlines all referenced `subworkflows` into a single deployable artifact located at `dist/main.json`.
|
|
83
|
+
|
|
84
|
+
> [!TIP]
|
|
85
|
+
> You can deploy the resulting `dist/main.json` to any production n8n instance without external dependencies. The output is a native, monolithic n8n workflow.
|
|
86
|
+
|
|
87
|
+
## License
|
|
88
|
+
|
|
89
|
+
MIT © [JVON DEV](https://github.com/jvondev)
|
|
90
|
+
|
|
91
|
+
<div align="center">
|
|
92
|
+
|
|
93
|
+
<a href="#readme-top">↑ back to top</a>
|
|
94
|
+
|
|
95
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { PACKAGE_JSON_TEMPLATE, GITIGNORE_TEMPLATE, ENV_TEMPLATE, CONFIG_TEMPLATE, README_TEMPLATE, MAIN_JSON_TEMPLATE, CHILD_JSON_TEMPLATE } from "./templates.js";
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
export async function generateProject(projectDir, projectName, initGit) {
|
|
8
|
+
// 1. Create Directories
|
|
9
|
+
await mkdir(projectDir, { recursive: true });
|
|
10
|
+
await mkdir(join(projectDir, "workflows"), { recursive: true });
|
|
11
|
+
await mkdir(join(projectDir, "workflows", "subworkflows"), { recursive: true });
|
|
12
|
+
// 2. Write Config / Root Files
|
|
13
|
+
await writeFile(join(projectDir, "package.json"), PACKAGE_JSON_TEMPLATE(projectName));
|
|
14
|
+
await writeFile(join(projectDir, ".gitignore"), GITIGNORE_TEMPLATE);
|
|
15
|
+
await writeFile(join(projectDir, ".env.example"), ENV_TEMPLATE);
|
|
16
|
+
await writeFile(join(projectDir, "flat8n.config.json"), CONFIG_TEMPLATE);
|
|
17
|
+
await writeFile(join(projectDir, "README.md"), README_TEMPLATE(projectName));
|
|
18
|
+
// 3. Write Workflows
|
|
19
|
+
await writeFile(join(projectDir, "workflows", "main.json"), MAIN_JSON_TEMPLATE);
|
|
20
|
+
await writeFile(join(projectDir, "workflows", "subworkflows", "child.json"), CHILD_JSON_TEMPLATE);
|
|
21
|
+
if (initGit) {
|
|
22
|
+
await execAsync("git init", { cwd: projectDir });
|
|
23
|
+
}
|
|
24
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,168 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { resolve,
|
|
4
|
-
import { exec } from "node:child_process";
|
|
5
|
-
import { promisify } from "node:util";
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { resolve, basename } from "node:path";
|
|
6
4
|
import pc from "picocolors";
|
|
7
5
|
import { intro, outro, text, confirm, spinner, log, isCancel, cancel } from "@clack/prompts";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
" ██████╗ █████╗ ██████╗ ",
|
|
11
|
-
" ██╔══██╗██╔══██╗██╔══██╗",
|
|
12
|
-
" ██║ ██║╚█████╔╝██║ ██║",
|
|
13
|
-
" ██║ ██║██╔══██╗██║ ██║",
|
|
14
|
-
" ██║ ██║╚█████╔╝██║ ██║",
|
|
15
|
-
" ╚═╝ ╚═╝ ╚════╝ ╚═╝ ╚═╝"
|
|
16
|
-
];
|
|
17
|
-
function showBranding() {
|
|
18
|
-
process.stdout.write("\n");
|
|
19
|
-
const color = "\x1b[38;2;234;75;113m"; // #EA4B71
|
|
20
|
-
const reset = "\x1b[0m";
|
|
21
|
-
process.stdout.write(`${color}${ASCII_ART.join('\n')}${reset}\n\n`);
|
|
22
|
-
}
|
|
23
|
-
const PACKAGE_JSON_TEMPLATE = (name) => `{
|
|
24
|
-
"name": "${name}",
|
|
25
|
-
"version": "1.0.0",
|
|
26
|
-
"description": "An n8n workflow project powered by flat8n",
|
|
27
|
-
"scripts": {
|
|
28
|
-
"dev": "flat8n",
|
|
29
|
-
"build": "flat8n link --entry workflows/main.json --out dist/main.json",
|
|
30
|
-
"start": "npx n8n start"
|
|
31
|
-
},
|
|
32
|
-
"dependencies": {},
|
|
33
|
-
"devDependencies": {
|
|
34
|
-
"@flat8n/cli": "latest"
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
`;
|
|
38
|
-
const GITIGNORE_TEMPLATE = `node_modules/
|
|
39
|
-
dist/
|
|
40
|
-
.env
|
|
41
|
-
.n8n/
|
|
42
|
-
`;
|
|
43
|
-
const ENV_TEMPLATE = `# The port your local n8n dev server runs on
|
|
44
|
-
N8N_PORT=5678
|
|
45
|
-
|
|
46
|
-
# The host your local n8n dev server binds to
|
|
47
|
-
N8N_HOST=localhost
|
|
48
|
-
|
|
49
|
-
# Security key for n8n encryption (change this in production)
|
|
50
|
-
N8N_ENCRYPTION_KEY=my-super-secret-encryption-key
|
|
51
|
-
`;
|
|
52
|
-
const CONFIG_TEMPLATE = `{
|
|
53
|
-
"$schema": "https://flat8n.com/schema.json",
|
|
54
|
-
"workflowsDir": "workflows",
|
|
55
|
-
"outDir": "dist"
|
|
56
|
-
}
|
|
57
|
-
`;
|
|
58
|
-
const README_TEMPLATE = (name) => `# ${name}
|
|
59
|
-
|
|
60
|
-
[](https://n8n.io)
|
|
61
|
-
[](https://github.com/jvondev/flat8n)
|
|
62
|
-
|
|
63
|
-
A modern n8n workflow project, scaffolded with \`create-n8n\`.
|
|
64
|
-
|
|
65
|
-
## Development
|
|
66
|
-
|
|
67
|
-
Run the dev server (automatically starts a local n8n instance and watches your files for changes):
|
|
68
|
-
|
|
69
|
-
\`\`\`bash
|
|
70
|
-
npm run dev
|
|
71
|
-
\`\`\`
|
|
72
|
-
|
|
73
|
-
## Build
|
|
74
|
-
|
|
75
|
-
Compile your modular workflows into a single deployable monolithic JSON workflow:
|
|
76
|
-
|
|
77
|
-
\`\`\`bash
|
|
78
|
-
npm run build
|
|
79
|
-
\`\`\`
|
|
80
|
-
|
|
81
|
-
## Architecture
|
|
82
|
-
|
|
83
|
-
Your source workflows are in the \`workflows/\` directory.
|
|
84
|
-
Shared or reusable logic goes in \`workflows/subworkflows/\`.
|
|
85
|
-
`;
|
|
86
|
-
const MAIN_JSON_TEMPLATE = `{
|
|
87
|
-
"name": "Main Entrypoint",
|
|
88
|
-
"nodes": [
|
|
89
|
-
{
|
|
90
|
-
"parameters": {
|
|
91
|
-
"httpMethod": "POST",
|
|
92
|
-
"path": "test",
|
|
93
|
-
"options": {}
|
|
94
|
-
},
|
|
95
|
-
"id": "1",
|
|
96
|
-
"name": "Webhook",
|
|
97
|
-
"type": "n8n-nodes-base.webhook",
|
|
98
|
-
"typeVersion": 1,
|
|
99
|
-
"position": [0, 0],
|
|
100
|
-
"webhookId": "test-webhook"
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
"parameters": {
|
|
104
|
-
"workflowId": "child"
|
|
105
|
-
},
|
|
106
|
-
"id": "2",
|
|
107
|
-
"name": "Execute Subworkflow",
|
|
108
|
-
"type": "n8n-nodes-base.executeWorkflow",
|
|
109
|
-
"typeVersion": 1,
|
|
110
|
-
"position": [200, 0]
|
|
111
|
-
}
|
|
112
|
-
],
|
|
113
|
-
"connections": {
|
|
114
|
-
"Webhook": {
|
|
115
|
-
"main": [
|
|
116
|
-
[
|
|
117
|
-
{
|
|
118
|
-
"node": "Execute Subworkflow",
|
|
119
|
-
"type": "main",
|
|
120
|
-
"index": 0
|
|
121
|
-
}
|
|
122
|
-
]
|
|
123
|
-
]
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
`;
|
|
128
|
-
const CHILD_JSON_TEMPLATE = `{
|
|
129
|
-
"name": "Child Module",
|
|
130
|
-
"id": "child",
|
|
131
|
-
"nodes": [
|
|
132
|
-
{
|
|
133
|
-
"parameters": {},
|
|
134
|
-
"id": "1",
|
|
135
|
-
"name": "Execute Workflow Trigger",
|
|
136
|
-
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
|
137
|
-
"typeVersion": 1,
|
|
138
|
-
"position": [0, 0]
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
"parameters": {
|
|
142
|
-
"jsCode": "return $input.all();"
|
|
143
|
-
},
|
|
144
|
-
"id": "2",
|
|
145
|
-
"name": "Process Logic",
|
|
146
|
-
"type": "n8n-nodes-base.code",
|
|
147
|
-
"typeVersion": 2,
|
|
148
|
-
"position": [200, 0]
|
|
149
|
-
}
|
|
150
|
-
],
|
|
151
|
-
"connections": {
|
|
152
|
-
"Execute Workflow Trigger": {
|
|
153
|
-
"main": [
|
|
154
|
-
[
|
|
155
|
-
{
|
|
156
|
-
"node": "Process Logic",
|
|
157
|
-
"type": "main",
|
|
158
|
-
"index": 0
|
|
159
|
-
}
|
|
160
|
-
]
|
|
161
|
-
]
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
`;
|
|
6
|
+
import { showBranding } from "./ui.js";
|
|
7
|
+
import { generateProject } from "./generator.js";
|
|
166
8
|
async function main() {
|
|
167
9
|
console.clear();
|
|
168
10
|
showBranding();
|
|
@@ -214,30 +56,8 @@ async function main() {
|
|
|
214
56
|
const s = spinner();
|
|
215
57
|
s.start(`Creating project in ${projectDir}...`);
|
|
216
58
|
try {
|
|
217
|
-
|
|
218
|
-
await mkdir(projectDir, { recursive: true });
|
|
219
|
-
await mkdir(join(projectDir, "workflows"), { recursive: true });
|
|
220
|
-
await mkdir(join(projectDir, "workflows", "subworkflows"), { recursive: true });
|
|
221
|
-
// 2. Write Config / Root Files
|
|
222
|
-
await writeFile(join(projectDir, "package.json"), PACKAGE_JSON_TEMPLATE(projectName));
|
|
223
|
-
await writeFile(join(projectDir, ".gitignore"), GITIGNORE_TEMPLATE);
|
|
224
|
-
await writeFile(join(projectDir, ".env.example"), ENV_TEMPLATE);
|
|
225
|
-
await writeFile(join(projectDir, "flat8n.config.json"), CONFIG_TEMPLATE);
|
|
226
|
-
await writeFile(join(projectDir, "README.md"), README_TEMPLATE(projectName));
|
|
227
|
-
// 3. Write Workflows
|
|
228
|
-
await writeFile(join(projectDir, "workflows", "main.json"), MAIN_JSON_TEMPLATE);
|
|
229
|
-
await writeFile(join(projectDir, "workflows", "subworkflows", "child.json"), CHILD_JSON_TEMPLATE);
|
|
59
|
+
await generateProject(projectDir, projectName, initGit);
|
|
230
60
|
s.stop(pc.green(`✔ Project scaffolded successfully in ${projectDir}`));
|
|
231
|
-
if (initGit) {
|
|
232
|
-
s.start("Initializing Git repository...");
|
|
233
|
-
try {
|
|
234
|
-
await execAsync("git init", { cwd: projectDir });
|
|
235
|
-
s.stop(pc.green("✔ Git repository initialized"));
|
|
236
|
-
}
|
|
237
|
-
catch (err) {
|
|
238
|
-
s.stop(pc.red("✖ Failed to initialize Git repository"));
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
61
|
const cdInstruction = targetDir === "." ? "" : ` cd ${targetDir}\n`;
|
|
242
62
|
log.info(`\n${pc.bold("Next steps:")}\n${cdInstruction} npm install\n npm run dev`);
|
|
243
63
|
outro(pc.cyan("Happy automating! ") + pc.dim("— scaffolded by \x1b]8;;https://github.com/jvondev\x1b\\JVON DEV\x1b]8;;\x1b\\"));
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import { PACKAGE_JSON_TEMPLATE, GITIGNORE_TEMPLATE, ENV_TEMPLATE, CONFIG_TEMPLATE, README_TEMPLATE, MAIN_JSON_TEMPLATE, CHILD_JSON_TEMPLATE } from "./templates.js";
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
export async function generateProject(projectDir, projectName, initGit) {
|
|
8
|
+
// 1. Create Directories
|
|
9
|
+
await mkdir(projectDir, { recursive: true });
|
|
10
|
+
await mkdir(join(projectDir, "workflows"), { recursive: true });
|
|
11
|
+
await mkdir(join(projectDir, "workflows", "subworkflows"), { recursive: true });
|
|
12
|
+
// 2. Write Config / Root Files
|
|
13
|
+
await writeFile(join(projectDir, "package.json"), PACKAGE_JSON_TEMPLATE(projectName));
|
|
14
|
+
await writeFile(join(projectDir, ".gitignore"), GITIGNORE_TEMPLATE);
|
|
15
|
+
await writeFile(join(projectDir, ".env.example"), ENV_TEMPLATE);
|
|
16
|
+
await writeFile(join(projectDir, "flat8n.config.json"), CONFIG_TEMPLATE);
|
|
17
|
+
await writeFile(join(projectDir, "README.md"), README_TEMPLATE(projectName));
|
|
18
|
+
// 3. Write Workflows
|
|
19
|
+
await writeFile(join(projectDir, "workflows", "main.json"), MAIN_JSON_TEMPLATE);
|
|
20
|
+
await writeFile(join(projectDir, "workflows", "subworkflows", "child.json"), CHILD_JSON_TEMPLATE);
|
|
21
|
+
if (initGit) {
|
|
22
|
+
await execAsync("git init", { cwd: projectDir });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { resolve, basename } from "node:path";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { intro, outro, text, confirm, spinner, log, isCancel, cancel } from "@clack/prompts";
|
|
6
|
+
import { showBranding } from "./ui.js";
|
|
7
|
+
import { generateProject } from "./generator.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
console.clear();
|
|
10
|
+
showBranding();
|
|
11
|
+
intro(pc.bgCyan(pc.black(" create-n8n ")));
|
|
12
|
+
const cwd = process.cwd();
|
|
13
|
+
const cwdFiles = await readdir(cwd);
|
|
14
|
+
const allowedFiles = new Set([".git", ".DS_Store", ".vscode", ".idea", "Thumbs.db"]);
|
|
15
|
+
const isCwdEmpty = cwdFiles.every((f) => allowedFiles.has(f));
|
|
16
|
+
let targetDir = ".";
|
|
17
|
+
if (!isCwdEmpty) {
|
|
18
|
+
const dirPrompt = await text({
|
|
19
|
+
message: "Project name (creates a new folder, or '.' for current directory)",
|
|
20
|
+
placeholder: "my-n8n-app",
|
|
21
|
+
defaultValue: "my-n8n-app",
|
|
22
|
+
validate: (val) => {
|
|
23
|
+
if (!val || val.length === 0)
|
|
24
|
+
return "Project name is required";
|
|
25
|
+
if (!/^[a-z0-9-_./]+$/.test(val))
|
|
26
|
+
return "Invalid path format";
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
if (isCancel(dirPrompt) || typeof dirPrompt !== "string") {
|
|
31
|
+
cancel("Initialization cancelled.");
|
|
32
|
+
return process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
targetDir = dirPrompt;
|
|
35
|
+
if (targetDir === ".") {
|
|
36
|
+
const confirmOverwrite = await confirm({
|
|
37
|
+
message: "Current directory is not empty. Continue and potentially overwrite files?",
|
|
38
|
+
initialValue: false
|
|
39
|
+
});
|
|
40
|
+
if (isCancel(confirmOverwrite) || !confirmOverwrite) {
|
|
41
|
+
cancel("Initialization cancelled.");
|
|
42
|
+
return process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const projectDir = resolve(cwd, targetDir);
|
|
47
|
+
const projectName = targetDir === "." ? basename(cwd) : basename(projectDir);
|
|
48
|
+
const initGit = await confirm({
|
|
49
|
+
message: "Initialize a new Git repository?",
|
|
50
|
+
initialValue: true
|
|
51
|
+
});
|
|
52
|
+
if (isCancel(initGit)) {
|
|
53
|
+
cancel("Initialization cancelled.");
|
|
54
|
+
return process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const s = spinner();
|
|
57
|
+
s.start(`Creating project in ${projectDir}...`);
|
|
58
|
+
try {
|
|
59
|
+
await generateProject(projectDir, projectName, initGit);
|
|
60
|
+
s.stop(pc.green(`✔ Project scaffolded successfully in ${projectDir}`));
|
|
61
|
+
const cdInstruction = targetDir === "." ? "" : ` cd ${targetDir}\n`;
|
|
62
|
+
log.info(`\n${pc.bold("Next steps:")}\n${cdInstruction} npm install\n npm run dev`);
|
|
63
|
+
outro(pc.cyan("Happy automating! ") + pc.dim("— scaffolded by \x1b]8;;https://github.com/jvondev\x1b\\JVON DEV\x1b]8;;\x1b\\"));
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
s.stop(pc.red("✖ Failed to create project"));
|
|
68
|
+
log.error(error.message);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export const PACKAGE_JSON_TEMPLATE = (name) => `{
|
|
2
|
+
"name": "${name}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An n8n workflow project powered by flat8n",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "flat8n",
|
|
7
|
+
"build": "flat8n link --entry workflows/main.json --out dist/main.json",
|
|
8
|
+
"start": "npx n8n start"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@flat8n/cli": "latest"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
export const GITIGNORE_TEMPLATE = `node_modules/
|
|
17
|
+
dist/
|
|
18
|
+
.env
|
|
19
|
+
.n8n/
|
|
20
|
+
`;
|
|
21
|
+
export const ENV_TEMPLATE = `# The port your local n8n dev server runs on
|
|
22
|
+
N8N_PORT=5678
|
|
23
|
+
|
|
24
|
+
# The host your local n8n dev server binds to
|
|
25
|
+
N8N_HOST=localhost
|
|
26
|
+
|
|
27
|
+
# Security key for n8n encryption (change this in production)
|
|
28
|
+
N8N_ENCRYPTION_KEY=my-super-secret-encryption-key
|
|
29
|
+
`;
|
|
30
|
+
export const CONFIG_TEMPLATE = `{
|
|
31
|
+
"$schema": "https://flat8n.com/schema.json",
|
|
32
|
+
"workflowsDir": "workflows",
|
|
33
|
+
"outDir": "dist"
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
export const README_TEMPLATE = (name) => `# ${name}
|
|
37
|
+
|
|
38
|
+
[](https://n8n.io)
|
|
39
|
+
[](https://github.com/jvondev/flat8n)
|
|
40
|
+
|
|
41
|
+
A modern n8n workflow project, scaffolded with \`create-n8n\`.
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
Run the dev server (automatically starts a local n8n instance and watches your files for changes):
|
|
46
|
+
|
|
47
|
+
\`\`\`bash
|
|
48
|
+
npm run dev
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
## Build
|
|
52
|
+
|
|
53
|
+
Compile your modular workflows into a single deployable monolithic JSON workflow:
|
|
54
|
+
|
|
55
|
+
\`\`\`bash
|
|
56
|
+
npm run build
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
## Architecture
|
|
60
|
+
|
|
61
|
+
Your source workflows are in the \`workflows/\` directory.
|
|
62
|
+
Shared or reusable logic goes in \`workflows/subworkflows/\`.
|
|
63
|
+
`;
|
|
64
|
+
export const MAIN_JSON_TEMPLATE = `{
|
|
65
|
+
"name": "Main Entrypoint",
|
|
66
|
+
"nodes": [
|
|
67
|
+
{
|
|
68
|
+
"parameters": {
|
|
69
|
+
"httpMethod": "POST",
|
|
70
|
+
"path": "test",
|
|
71
|
+
"options": {}
|
|
72
|
+
},
|
|
73
|
+
"id": "1",
|
|
74
|
+
"name": "Webhook",
|
|
75
|
+
"type": "n8n-nodes-base.webhook",
|
|
76
|
+
"typeVersion": 1,
|
|
77
|
+
"position": [0, 0],
|
|
78
|
+
"webhookId": "test-webhook"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"parameters": {
|
|
82
|
+
"workflowId": "child"
|
|
83
|
+
},
|
|
84
|
+
"id": "2",
|
|
85
|
+
"name": "Execute Subworkflow",
|
|
86
|
+
"type": "n8n-nodes-base.executeWorkflow",
|
|
87
|
+
"typeVersion": 1,
|
|
88
|
+
"position": [200, 0]
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"connections": {
|
|
92
|
+
"Webhook": {
|
|
93
|
+
"main": [
|
|
94
|
+
[
|
|
95
|
+
{
|
|
96
|
+
"node": "Execute Subworkflow",
|
|
97
|
+
"type": "main",
|
|
98
|
+
"index": 0
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
export const CHILD_JSON_TEMPLATE = `{
|
|
107
|
+
"name": "Child Module",
|
|
108
|
+
"id": "child",
|
|
109
|
+
"nodes": [
|
|
110
|
+
{
|
|
111
|
+
"parameters": {},
|
|
112
|
+
"id": "1",
|
|
113
|
+
"name": "Execute Workflow Trigger",
|
|
114
|
+
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
|
115
|
+
"typeVersion": 1,
|
|
116
|
+
"position": [0, 0]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"parameters": {
|
|
120
|
+
"jsCode": "return $input.all();"
|
|
121
|
+
},
|
|
122
|
+
"id": "2",
|
|
123
|
+
"name": "Process Logic",
|
|
124
|
+
"type": "n8n-nodes-base.code",
|
|
125
|
+
"typeVersion": 2,
|
|
126
|
+
"position": [200, 0]
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"connections": {
|
|
130
|
+
"Execute Workflow Trigger": {
|
|
131
|
+
"main": [
|
|
132
|
+
[
|
|
133
|
+
{
|
|
134
|
+
"node": "Process Logic",
|
|
135
|
+
"type": "main",
|
|
136
|
+
"index": 0
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
`;
|
package/dist/src/ui.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const ASCII_ART = [
|
|
2
|
+
" ██████╗ █████╗ ██████╗ ",
|
|
3
|
+
" ██╔══██╗██╔══██╗██╔══██╗",
|
|
4
|
+
" ██║ ██║╚█████╔╝██║ ██║",
|
|
5
|
+
" ██║ ██║██╔══██╗██║ ██║",
|
|
6
|
+
" ██║ ██║╚█████╔╝██║ ██║",
|
|
7
|
+
" ╚═╝ ╚═╝ ╚════╝ ╚═╝ ╚═╝"
|
|
8
|
+
];
|
|
9
|
+
export function showBranding() {
|
|
10
|
+
process.stdout.write("\n");
|
|
11
|
+
const color = "\x1b[38;2;234;75;113m"; // #EA4B71
|
|
12
|
+
const reset = "\x1b[0m";
|
|
13
|
+
process.stdout.write(`${color}${ASCII_ART.join('\n')}${reset}\n\n`);
|
|
14
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
export const PACKAGE_JSON_TEMPLATE = (name) => `{
|
|
2
|
+
"name": "${name}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An n8n workflow project powered by flat8n",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "flat8n",
|
|
7
|
+
"build": "flat8n link --entry workflows/main.json --out dist/main.json",
|
|
8
|
+
"start": "npx n8n start"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@flat8n/cli": "latest"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
export const GITIGNORE_TEMPLATE = `node_modules/
|
|
17
|
+
dist/
|
|
18
|
+
.env
|
|
19
|
+
.n8n/
|
|
20
|
+
`;
|
|
21
|
+
export const ENV_TEMPLATE = `# The port your local n8n dev server runs on
|
|
22
|
+
N8N_PORT=5678
|
|
23
|
+
|
|
24
|
+
# The host your local n8n dev server binds to
|
|
25
|
+
N8N_HOST=localhost
|
|
26
|
+
|
|
27
|
+
# Security key for n8n encryption (change this in production)
|
|
28
|
+
N8N_ENCRYPTION_KEY=my-super-secret-encryption-key
|
|
29
|
+
`;
|
|
30
|
+
export const CONFIG_TEMPLATE = `{
|
|
31
|
+
"$schema": "https://flat8n.com/schema.json",
|
|
32
|
+
"workflowsDir": "workflows",
|
|
33
|
+
"outDir": "dist"
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
export const README_TEMPLATE = (name) => `# ${name}
|
|
37
|
+
|
|
38
|
+
[](https://n8n.io)
|
|
39
|
+
[](https://github.com/jvondev/flat8n)
|
|
40
|
+
|
|
41
|
+
A modern n8n workflow project, scaffolded with \`create-n8n\`.
|
|
42
|
+
|
|
43
|
+
## Development
|
|
44
|
+
|
|
45
|
+
Run the dev server (automatically starts a local n8n instance and watches your files for changes):
|
|
46
|
+
|
|
47
|
+
\`\`\`bash
|
|
48
|
+
npm run dev
|
|
49
|
+
\`\`\`
|
|
50
|
+
|
|
51
|
+
## Build
|
|
52
|
+
|
|
53
|
+
Compile your modular workflows into a single deployable monolithic JSON workflow:
|
|
54
|
+
|
|
55
|
+
\`\`\`bash
|
|
56
|
+
npm run build
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
## Architecture
|
|
60
|
+
|
|
61
|
+
Your source workflows are in the \`workflows/\` directory.
|
|
62
|
+
Shared or reusable logic goes in \`workflows/subworkflows/\`.
|
|
63
|
+
`;
|
|
64
|
+
export const MAIN_JSON_TEMPLATE = `{
|
|
65
|
+
"name": "Main Entrypoint",
|
|
66
|
+
"nodes": [
|
|
67
|
+
{
|
|
68
|
+
"parameters": {
|
|
69
|
+
"httpMethod": "POST",
|
|
70
|
+
"path": "test",
|
|
71
|
+
"options": {}
|
|
72
|
+
},
|
|
73
|
+
"id": "1",
|
|
74
|
+
"name": "Webhook",
|
|
75
|
+
"type": "n8n-nodes-base.webhook",
|
|
76
|
+
"typeVersion": 1,
|
|
77
|
+
"position": [0, 0],
|
|
78
|
+
"webhookId": "test-webhook"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"parameters": {
|
|
82
|
+
"workflowId": "child"
|
|
83
|
+
},
|
|
84
|
+
"id": "2",
|
|
85
|
+
"name": "Execute Subworkflow",
|
|
86
|
+
"type": "n8n-nodes-base.executeWorkflow",
|
|
87
|
+
"typeVersion": 1,
|
|
88
|
+
"position": [200, 0]
|
|
89
|
+
}
|
|
90
|
+
],
|
|
91
|
+
"connections": {
|
|
92
|
+
"Webhook": {
|
|
93
|
+
"main": [
|
|
94
|
+
[
|
|
95
|
+
{
|
|
96
|
+
"node": "Execute Subworkflow",
|
|
97
|
+
"type": "main",
|
|
98
|
+
"index": 0
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
`;
|
|
106
|
+
export const CHILD_JSON_TEMPLATE = `{
|
|
107
|
+
"name": "Child Module",
|
|
108
|
+
"id": "child",
|
|
109
|
+
"nodes": [
|
|
110
|
+
{
|
|
111
|
+
"parameters": {},
|
|
112
|
+
"id": "1",
|
|
113
|
+
"name": "Execute Workflow Trigger",
|
|
114
|
+
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
|
115
|
+
"typeVersion": 1,
|
|
116
|
+
"position": [0, 0]
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"parameters": {
|
|
120
|
+
"jsCode": "return $input.all();"
|
|
121
|
+
},
|
|
122
|
+
"id": "2",
|
|
123
|
+
"name": "Process Logic",
|
|
124
|
+
"type": "n8n-nodes-base.code",
|
|
125
|
+
"typeVersion": 2,
|
|
126
|
+
"position": [200, 0]
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
"connections": {
|
|
130
|
+
"Execute Workflow Trigger": {
|
|
131
|
+
"main": [
|
|
132
|
+
[
|
|
133
|
+
{
|
|
134
|
+
"node": "Process Logic",
|
|
135
|
+
"type": "main",
|
|
136
|
+
"index": 0
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
`;
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const ASCII_ART = [
|
|
2
|
+
" ██████╗ █████╗ ██████╗ ",
|
|
3
|
+
" ██╔══██╗██╔══██╗██╔══██╗",
|
|
4
|
+
" ██║ ██║╚█████╔╝██║ ██║",
|
|
5
|
+
" ██║ ██║██╔══██╗██║ ██║",
|
|
6
|
+
" ██║ ██║╚█████╔╝██║ ██║",
|
|
7
|
+
" ╚═╝ ╚═╝ ╚════╝ ╚═╝ ╚═╝"
|
|
8
|
+
];
|
|
9
|
+
export function showBranding() {
|
|
10
|
+
process.stdout.write("\n");
|
|
11
|
+
const color = "\x1b[38;2;234;75;113m"; // #EA4B71
|
|
12
|
+
const reset = "\x1b[0m";
|
|
13
|
+
process.stdout.write(`${color}${ASCII_ART.join('\n')}${reset}\n\n`);
|
|
14
|
+
}
|
package/package.json
CHANGED
package/src/generator.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { promisify } from "node:util";
|
|
5
|
+
import {
|
|
6
|
+
PACKAGE_JSON_TEMPLATE,
|
|
7
|
+
GITIGNORE_TEMPLATE,
|
|
8
|
+
ENV_TEMPLATE,
|
|
9
|
+
CONFIG_TEMPLATE,
|
|
10
|
+
README_TEMPLATE,
|
|
11
|
+
MAIN_JSON_TEMPLATE,
|
|
12
|
+
CHILD_JSON_TEMPLATE
|
|
13
|
+
} from "./templates.js";
|
|
14
|
+
|
|
15
|
+
const execAsync = promisify(exec);
|
|
16
|
+
|
|
17
|
+
export async function generateProject(projectDir: string, projectName: string, initGit: boolean): Promise<void> {
|
|
18
|
+
// 1. Create Directories
|
|
19
|
+
await mkdir(projectDir, { recursive: true });
|
|
20
|
+
await mkdir(join(projectDir, "workflows"), { recursive: true });
|
|
21
|
+
await mkdir(join(projectDir, "workflows", "subworkflows"), { recursive: true });
|
|
22
|
+
|
|
23
|
+
// 2. Write Config / Root Files
|
|
24
|
+
await writeFile(join(projectDir, "package.json"), PACKAGE_JSON_TEMPLATE(projectName));
|
|
25
|
+
await writeFile(join(projectDir, ".gitignore"), GITIGNORE_TEMPLATE);
|
|
26
|
+
await writeFile(join(projectDir, ".env.example"), ENV_TEMPLATE);
|
|
27
|
+
await writeFile(join(projectDir, "flat8n.config.json"), CONFIG_TEMPLATE);
|
|
28
|
+
await writeFile(join(projectDir, "README.md"), README_TEMPLATE(projectName));
|
|
29
|
+
|
|
30
|
+
// 3. Write Workflows
|
|
31
|
+
await writeFile(join(projectDir, "workflows", "main.json"), MAIN_JSON_TEMPLATE);
|
|
32
|
+
await writeFile(join(projectDir, "workflows", "subworkflows", "child.json"), CHILD_JSON_TEMPLATE);
|
|
33
|
+
|
|
34
|
+
if (initGit) {
|
|
35
|
+
await execAsync("git init", { cwd: projectDir });
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readdir } from "node:fs/promises";
|
|
3
|
+
import { resolve, basename } from "node:path";
|
|
4
|
+
import pc from "picocolors";
|
|
5
|
+
import { intro, outro, text, confirm, spinner, log, isCancel, cancel } from "@clack/prompts";
|
|
6
|
+
import { showBranding } from "./ui.js";
|
|
7
|
+
import { generateProject } from "./generator.js";
|
|
8
|
+
|
|
9
|
+
async function main() {
|
|
10
|
+
console.clear();
|
|
11
|
+
showBranding();
|
|
12
|
+
intro(pc.bgCyan(pc.black(" create-n8n ")));
|
|
13
|
+
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
|
|
16
|
+
const cwdFiles = await readdir(cwd);
|
|
17
|
+
const allowedFiles = new Set([".git", ".DS_Store", ".vscode", ".idea", "Thumbs.db"]);
|
|
18
|
+
const isCwdEmpty = cwdFiles.every((f: string) => allowedFiles.has(f));
|
|
19
|
+
|
|
20
|
+
let targetDir = ".";
|
|
21
|
+
|
|
22
|
+
if (!isCwdEmpty) {
|
|
23
|
+
const dirPrompt = await text({
|
|
24
|
+
message: "Project name (creates a new folder, or '.' for current directory)",
|
|
25
|
+
placeholder: "my-n8n-app",
|
|
26
|
+
defaultValue: "my-n8n-app",
|
|
27
|
+
validate: (val) => {
|
|
28
|
+
if (!val || val.length === 0) return "Project name is required";
|
|
29
|
+
if (!/^[a-z0-9-_./]+$/.test(val)) return "Invalid path format";
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (isCancel(dirPrompt) || typeof dirPrompt !== "string") {
|
|
35
|
+
cancel("Initialization cancelled.");
|
|
36
|
+
return process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
targetDir = dirPrompt;
|
|
39
|
+
|
|
40
|
+
if (targetDir === ".") {
|
|
41
|
+
const confirmOverwrite = await confirm({
|
|
42
|
+
message: "Current directory is not empty. Continue and potentially overwrite files?",
|
|
43
|
+
initialValue: false
|
|
44
|
+
});
|
|
45
|
+
if (isCancel(confirmOverwrite) || !confirmOverwrite) {
|
|
46
|
+
cancel("Initialization cancelled.");
|
|
47
|
+
return process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const projectDir = resolve(cwd, targetDir);
|
|
53
|
+
const projectName = targetDir === "." ? basename(cwd) : basename(projectDir);
|
|
54
|
+
|
|
55
|
+
const initGit = await confirm({
|
|
56
|
+
message: "Initialize a new Git repository?",
|
|
57
|
+
initialValue: true
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (isCancel(initGit)) {
|
|
61
|
+
cancel("Initialization cancelled.");
|
|
62
|
+
return process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const s = spinner();
|
|
66
|
+
s.start(`Creating project in ${projectDir}...`);
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
await generateProject(projectDir, projectName, initGit as boolean);
|
|
70
|
+
s.stop(pc.green(`✔ Project scaffolded successfully in ${projectDir}`));
|
|
71
|
+
|
|
72
|
+
const cdInstruction = targetDir === "." ? "" : ` cd ${targetDir}\n`;
|
|
73
|
+
log.info(`\n${pc.bold("Next steps:")}\n${cdInstruction} npm install\n npm run dev`);
|
|
74
|
+
outro(pc.cyan("Happy automating! ") + pc.dim("— scaffolded by \x1b]8;;https://github.com/jvondev\x1b\\JVON DEV\x1b]8;;\x1b\\"));
|
|
75
|
+
process.exit(0);
|
|
76
|
+
|
|
77
|
+
} catch (error: any) {
|
|
78
|
+
s.stop(pc.red("✖ Failed to create project"));
|
|
79
|
+
log.error(error.message);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main().catch(console.error);
|
package/src/templates.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
export const PACKAGE_JSON_TEMPLATE = (name: string) => `{
|
|
2
|
+
"name": "${name}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "An n8n workflow project powered by flat8n",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "flat8n",
|
|
7
|
+
"build": "flat8n link --entry workflows/main.json --out dist/main.json",
|
|
8
|
+
"start": "npx n8n start"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@flat8n/cli": "latest"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
16
|
+
|
|
17
|
+
export const GITIGNORE_TEMPLATE = `node_modules/
|
|
18
|
+
dist/
|
|
19
|
+
.env
|
|
20
|
+
.n8n/
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
export const ENV_TEMPLATE = `# The port your local n8n dev server runs on
|
|
24
|
+
N8N_PORT=5678
|
|
25
|
+
|
|
26
|
+
# The host your local n8n dev server binds to
|
|
27
|
+
N8N_HOST=localhost
|
|
28
|
+
|
|
29
|
+
# Security key for n8n encryption (change this in production)
|
|
30
|
+
N8N_ENCRYPTION_KEY=my-super-secret-encryption-key
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export const CONFIG_TEMPLATE = `{
|
|
34
|
+
"$schema": "https://flat8n.com/schema.json",
|
|
35
|
+
"workflowsDir": "workflows",
|
|
36
|
+
"outDir": "dist"
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
export const README_TEMPLATE = (name: string) => `# ${name}
|
|
41
|
+
|
|
42
|
+
[](https://n8n.io)
|
|
43
|
+
[](https://github.com/jvondev/flat8n)
|
|
44
|
+
|
|
45
|
+
A modern n8n workflow project, scaffolded with \`create-n8n\`.
|
|
46
|
+
|
|
47
|
+
## Development
|
|
48
|
+
|
|
49
|
+
Run the dev server (automatically starts a local n8n instance and watches your files for changes):
|
|
50
|
+
|
|
51
|
+
\`\`\`bash
|
|
52
|
+
npm run dev
|
|
53
|
+
\`\`\`
|
|
54
|
+
|
|
55
|
+
## Build
|
|
56
|
+
|
|
57
|
+
Compile your modular workflows into a single deployable monolithic JSON workflow:
|
|
58
|
+
|
|
59
|
+
\`\`\`bash
|
|
60
|
+
npm run build
|
|
61
|
+
\`\`\`
|
|
62
|
+
|
|
63
|
+
## Architecture
|
|
64
|
+
|
|
65
|
+
Your source workflows are in the \`workflows/\` directory.
|
|
66
|
+
Shared or reusable logic goes in \`workflows/subworkflows/\`.
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
export const MAIN_JSON_TEMPLATE = `{
|
|
70
|
+
"name": "Main Entrypoint",
|
|
71
|
+
"nodes": [
|
|
72
|
+
{
|
|
73
|
+
"parameters": {
|
|
74
|
+
"httpMethod": "POST",
|
|
75
|
+
"path": "test",
|
|
76
|
+
"options": {}
|
|
77
|
+
},
|
|
78
|
+
"id": "1",
|
|
79
|
+
"name": "Webhook",
|
|
80
|
+
"type": "n8n-nodes-base.webhook",
|
|
81
|
+
"typeVersion": 1,
|
|
82
|
+
"position": [0, 0],
|
|
83
|
+
"webhookId": "test-webhook"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"parameters": {
|
|
87
|
+
"workflowId": "child"
|
|
88
|
+
},
|
|
89
|
+
"id": "2",
|
|
90
|
+
"name": "Execute Subworkflow",
|
|
91
|
+
"type": "n8n-nodes-base.executeWorkflow",
|
|
92
|
+
"typeVersion": 1,
|
|
93
|
+
"position": [200, 0]
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"connections": {
|
|
97
|
+
"Webhook": {
|
|
98
|
+
"main": [
|
|
99
|
+
[
|
|
100
|
+
{
|
|
101
|
+
"node": "Execute Subworkflow",
|
|
102
|
+
"type": "main",
|
|
103
|
+
"index": 0
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
111
|
+
|
|
112
|
+
export const CHILD_JSON_TEMPLATE = `{
|
|
113
|
+
"name": "Child Module",
|
|
114
|
+
"id": "child",
|
|
115
|
+
"nodes": [
|
|
116
|
+
{
|
|
117
|
+
"parameters": {},
|
|
118
|
+
"id": "1",
|
|
119
|
+
"name": "Execute Workflow Trigger",
|
|
120
|
+
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
|
121
|
+
"typeVersion": 1,
|
|
122
|
+
"position": [0, 0]
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"parameters": {
|
|
126
|
+
"jsCode": "return $input.all();"
|
|
127
|
+
},
|
|
128
|
+
"id": "2",
|
|
129
|
+
"name": "Process Logic",
|
|
130
|
+
"type": "n8n-nodes-base.code",
|
|
131
|
+
"typeVersion": 2,
|
|
132
|
+
"position": [200, 0]
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"connections": {
|
|
136
|
+
"Execute Workflow Trigger": {
|
|
137
|
+
"main": [
|
|
138
|
+
[
|
|
139
|
+
{
|
|
140
|
+
"node": "Process Logic",
|
|
141
|
+
"type": "main",
|
|
142
|
+
"index": 0
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
`;
|
package/src/ui.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const ASCII_ART = [
|
|
2
|
+
" ██████╗ █████╗ ██████╗ ",
|
|
3
|
+
" ██╔══██╗██╔══██╗██╔══██╗",
|
|
4
|
+
" ██║ ██║╚█████╔╝██║ ██║",
|
|
5
|
+
" ██║ ██║██╔══██╗██║ ██║",
|
|
6
|
+
" ██║ ██║╚█████╔╝██║ ██║",
|
|
7
|
+
" ╚═╝ ╚═╝ ╚════╝ ╚═╝ ╚═╝"
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
export function showBranding(): void {
|
|
11
|
+
process.stdout.write("\n");
|
|
12
|
+
const color = "\x1b[38;2;234;75;113m"; // #EA4B71
|
|
13
|
+
const reset = "\x1b[0m";
|
|
14
|
+
process.stdout.write(`${color}${ASCII_ART.join('\n')}${reset}\n\n`);
|
|
15
|
+
}
|
package/tsconfig.json
CHANGED
package/index.ts
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { mkdir, writeFile, readdir } from "node:fs/promises";
|
|
3
|
-
import { resolve, join, basename } from "node:path";
|
|
4
|
-
import { exec } from "node:child_process";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
import pc from "picocolors";
|
|
7
|
-
import { intro, outro, text, confirm, spinner, log, isCancel, cancel } from "@clack/prompts";
|
|
8
|
-
|
|
9
|
-
const execAsync = promisify(exec);
|
|
10
|
-
|
|
11
|
-
const ASCII_ART = [
|
|
12
|
-
" ██████╗ █████╗ ██████╗ ",
|
|
13
|
-
" ██╔══██╗██╔══██╗██╔══██╗",
|
|
14
|
-
" ██║ ██║╚█████╔╝██║ ██║",
|
|
15
|
-
" ██║ ██║██╔══██╗██║ ██║",
|
|
16
|
-
" ██║ ██║╚█████╔╝██║ ██║",
|
|
17
|
-
" ╚═╝ ╚═╝ ╚════╝ ╚═╝ ╚═╝"
|
|
18
|
-
];
|
|
19
|
-
|
|
20
|
-
function showBranding(): void {
|
|
21
|
-
process.stdout.write("\n");
|
|
22
|
-
const color = "\x1b[38;2;234;75;113m"; // #EA4B71
|
|
23
|
-
const reset = "\x1b[0m";
|
|
24
|
-
process.stdout.write(`${color}${ASCII_ART.join('\n')}${reset}\n\n`);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const PACKAGE_JSON_TEMPLATE = (name: string) => `{
|
|
28
|
-
"name": "${name}",
|
|
29
|
-
"version": "1.0.0",
|
|
30
|
-
"description": "An n8n workflow project powered by flat8n",
|
|
31
|
-
"scripts": {
|
|
32
|
-
"dev": "flat8n",
|
|
33
|
-
"build": "flat8n link --entry workflows/main.json --out dist/main.json",
|
|
34
|
-
"start": "npx n8n start"
|
|
35
|
-
},
|
|
36
|
-
"dependencies": {},
|
|
37
|
-
"devDependencies": {
|
|
38
|
-
"@flat8n/cli": "latest"
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
`;
|
|
42
|
-
|
|
43
|
-
const GITIGNORE_TEMPLATE = `node_modules/
|
|
44
|
-
dist/
|
|
45
|
-
.env
|
|
46
|
-
.n8n/
|
|
47
|
-
`;
|
|
48
|
-
|
|
49
|
-
const ENV_TEMPLATE = `# The port your local n8n dev server runs on
|
|
50
|
-
N8N_PORT=5678
|
|
51
|
-
|
|
52
|
-
# The host your local n8n dev server binds to
|
|
53
|
-
N8N_HOST=localhost
|
|
54
|
-
|
|
55
|
-
# Security key for n8n encryption (change this in production)
|
|
56
|
-
N8N_ENCRYPTION_KEY=my-super-secret-encryption-key
|
|
57
|
-
`;
|
|
58
|
-
|
|
59
|
-
const CONFIG_TEMPLATE = `{
|
|
60
|
-
"$schema": "https://flat8n.com/schema.json",
|
|
61
|
-
"workflowsDir": "workflows",
|
|
62
|
-
"outDir": "dist"
|
|
63
|
-
}
|
|
64
|
-
`;
|
|
65
|
-
|
|
66
|
-
const README_TEMPLATE = (name: string) => `# ${name}
|
|
67
|
-
|
|
68
|
-
[](https://n8n.io)
|
|
69
|
-
[](https://github.com/jvondev/flat8n)
|
|
70
|
-
|
|
71
|
-
A modern n8n workflow project, scaffolded with \`create-n8n\`.
|
|
72
|
-
|
|
73
|
-
## Development
|
|
74
|
-
|
|
75
|
-
Run the dev server (automatically starts a local n8n instance and watches your files for changes):
|
|
76
|
-
|
|
77
|
-
\`\`\`bash
|
|
78
|
-
npm run dev
|
|
79
|
-
\`\`\`
|
|
80
|
-
|
|
81
|
-
## Build
|
|
82
|
-
|
|
83
|
-
Compile your modular workflows into a single deployable monolithic JSON workflow:
|
|
84
|
-
|
|
85
|
-
\`\`\`bash
|
|
86
|
-
npm run build
|
|
87
|
-
\`\`\`
|
|
88
|
-
|
|
89
|
-
## Architecture
|
|
90
|
-
|
|
91
|
-
Your source workflows are in the \`workflows/\` directory.
|
|
92
|
-
Shared or reusable logic goes in \`workflows/subworkflows/\`.
|
|
93
|
-
`;
|
|
94
|
-
|
|
95
|
-
const MAIN_JSON_TEMPLATE = `{
|
|
96
|
-
"name": "Main Entrypoint",
|
|
97
|
-
"nodes": [
|
|
98
|
-
{
|
|
99
|
-
"parameters": {
|
|
100
|
-
"httpMethod": "POST",
|
|
101
|
-
"path": "test",
|
|
102
|
-
"options": {}
|
|
103
|
-
},
|
|
104
|
-
"id": "1",
|
|
105
|
-
"name": "Webhook",
|
|
106
|
-
"type": "n8n-nodes-base.webhook",
|
|
107
|
-
"typeVersion": 1,
|
|
108
|
-
"position": [0, 0],
|
|
109
|
-
"webhookId": "test-webhook"
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
"parameters": {
|
|
113
|
-
"workflowId": "child"
|
|
114
|
-
},
|
|
115
|
-
"id": "2",
|
|
116
|
-
"name": "Execute Subworkflow",
|
|
117
|
-
"type": "n8n-nodes-base.executeWorkflow",
|
|
118
|
-
"typeVersion": 1,
|
|
119
|
-
"position": [200, 0]
|
|
120
|
-
}
|
|
121
|
-
],
|
|
122
|
-
"connections": {
|
|
123
|
-
"Webhook": {
|
|
124
|
-
"main": [
|
|
125
|
-
[
|
|
126
|
-
{
|
|
127
|
-
"node": "Execute Subworkflow",
|
|
128
|
-
"type": "main",
|
|
129
|
-
"index": 0
|
|
130
|
-
}
|
|
131
|
-
]
|
|
132
|
-
]
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
`;
|
|
137
|
-
|
|
138
|
-
const CHILD_JSON_TEMPLATE = `{
|
|
139
|
-
"name": "Child Module",
|
|
140
|
-
"id": "child",
|
|
141
|
-
"nodes": [
|
|
142
|
-
{
|
|
143
|
-
"parameters": {},
|
|
144
|
-
"id": "1",
|
|
145
|
-
"name": "Execute Workflow Trigger",
|
|
146
|
-
"type": "n8n-nodes-base.executeWorkflowTrigger",
|
|
147
|
-
"typeVersion": 1,
|
|
148
|
-
"position": [0, 0]
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
"parameters": {
|
|
152
|
-
"jsCode": "return $input.all();"
|
|
153
|
-
},
|
|
154
|
-
"id": "2",
|
|
155
|
-
"name": "Process Logic",
|
|
156
|
-
"type": "n8n-nodes-base.code",
|
|
157
|
-
"typeVersion": 2,
|
|
158
|
-
"position": [200, 0]
|
|
159
|
-
}
|
|
160
|
-
],
|
|
161
|
-
"connections": {
|
|
162
|
-
"Execute Workflow Trigger": {
|
|
163
|
-
"main": [
|
|
164
|
-
[
|
|
165
|
-
{
|
|
166
|
-
"node": "Process Logic",
|
|
167
|
-
"type": "main",
|
|
168
|
-
"index": 0
|
|
169
|
-
}
|
|
170
|
-
]
|
|
171
|
-
]
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
`;
|
|
176
|
-
|
|
177
|
-
async function main() {
|
|
178
|
-
console.clear();
|
|
179
|
-
showBranding();
|
|
180
|
-
intro(pc.bgCyan(pc.black(" create-n8n ")));
|
|
181
|
-
|
|
182
|
-
const cwd = process.cwd();
|
|
183
|
-
|
|
184
|
-
const cwdFiles = await readdir(cwd);
|
|
185
|
-
const allowedFiles = new Set([".git", ".DS_Store", ".vscode", ".idea", "Thumbs.db"]);
|
|
186
|
-
const isCwdEmpty = cwdFiles.every((f: string) => allowedFiles.has(f));
|
|
187
|
-
|
|
188
|
-
let targetDir = ".";
|
|
189
|
-
|
|
190
|
-
if (!isCwdEmpty) {
|
|
191
|
-
const dirPrompt = await text({
|
|
192
|
-
message: "Project name (creates a new folder, or '.' for current directory)",
|
|
193
|
-
placeholder: "my-n8n-app",
|
|
194
|
-
defaultValue: "my-n8n-app",
|
|
195
|
-
validate: (val) => {
|
|
196
|
-
if (!val || val.length === 0) return "Project name is required";
|
|
197
|
-
if (!/^[a-z0-9-_./]+$/.test(val)) return "Invalid path format";
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
if (isCancel(dirPrompt) || typeof dirPrompt !== "string") {
|
|
203
|
-
cancel("Initialization cancelled.");
|
|
204
|
-
return process.exit(1);
|
|
205
|
-
}
|
|
206
|
-
targetDir = dirPrompt;
|
|
207
|
-
|
|
208
|
-
if (targetDir === ".") {
|
|
209
|
-
const confirmOverwrite = await confirm({
|
|
210
|
-
message: "Current directory is not empty. Continue and potentially overwrite files?",
|
|
211
|
-
initialValue: false
|
|
212
|
-
});
|
|
213
|
-
if (isCancel(confirmOverwrite) || !confirmOverwrite) {
|
|
214
|
-
cancel("Initialization cancelled.");
|
|
215
|
-
return process.exit(1);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const projectDir = resolve(cwd, targetDir);
|
|
221
|
-
const projectName = targetDir === "." ? basename(cwd) : basename(projectDir);
|
|
222
|
-
|
|
223
|
-
const initGit = await confirm({
|
|
224
|
-
message: "Initialize a new Git repository?",
|
|
225
|
-
initialValue: true
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
if (isCancel(initGit)) {
|
|
229
|
-
cancel("Initialization cancelled.");
|
|
230
|
-
return process.exit(1);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const s = spinner();
|
|
234
|
-
s.start(`Creating project in ${projectDir}...`);
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
// 1. Create Directories
|
|
238
|
-
await mkdir(projectDir, { recursive: true });
|
|
239
|
-
await mkdir(join(projectDir, "workflows"), { recursive: true });
|
|
240
|
-
await mkdir(join(projectDir, "workflows", "subworkflows"), { recursive: true });
|
|
241
|
-
|
|
242
|
-
// 2. Write Config / Root Files
|
|
243
|
-
await writeFile(join(projectDir, "package.json"), PACKAGE_JSON_TEMPLATE(projectName));
|
|
244
|
-
await writeFile(join(projectDir, ".gitignore"), GITIGNORE_TEMPLATE);
|
|
245
|
-
await writeFile(join(projectDir, ".env.example"), ENV_TEMPLATE);
|
|
246
|
-
await writeFile(join(projectDir, "flat8n.config.json"), CONFIG_TEMPLATE);
|
|
247
|
-
await writeFile(join(projectDir, "README.md"), README_TEMPLATE(projectName));
|
|
248
|
-
|
|
249
|
-
// 3. Write Workflows
|
|
250
|
-
await writeFile(join(projectDir, "workflows", "main.json"), MAIN_JSON_TEMPLATE);
|
|
251
|
-
await writeFile(join(projectDir, "workflows", "subworkflows", "child.json"), CHILD_JSON_TEMPLATE);
|
|
252
|
-
|
|
253
|
-
s.stop(pc.green(`✔ Project scaffolded successfully in ${projectDir}`));
|
|
254
|
-
|
|
255
|
-
if (initGit) {
|
|
256
|
-
s.start("Initializing Git repository...");
|
|
257
|
-
try {
|
|
258
|
-
await execAsync("git init", { cwd: projectDir });
|
|
259
|
-
s.stop(pc.green("✔ Git repository initialized"));
|
|
260
|
-
} catch (err) {
|
|
261
|
-
s.stop(pc.red("✖ Failed to initialize Git repository"));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const cdInstruction = targetDir === "." ? "" : ` cd ${targetDir}\n`;
|
|
266
|
-
log.info(`\n${pc.bold("Next steps:")}\n${cdInstruction} npm install\n npm run dev`);
|
|
267
|
-
outro(pc.cyan("Happy automating! ") + pc.dim("— scaffolded by \x1b]8;;https://github.com/jvondev\x1b\\JVON DEV\x1b]8;;\x1b\\"));
|
|
268
|
-
process.exit(0);
|
|
269
|
-
|
|
270
|
-
} catch (error: any) {
|
|
271
|
-
s.stop(pc.red("✖ Failed to create project"));
|
|
272
|
-
log.error(error.message);
|
|
273
|
-
process.exit(1);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
main().catch(console.error);
|