create-n8n 1.0.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.
- package/dist/index.js +252 -0
- package/index.ts +277 -0
- package/package.json +26 -0
- package/tsconfig.json +14 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
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
|
+
const execAsync = promisify(exec);
|
|
9
|
+
const ASCII_ART = [
|
|
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
|
+
`;
|
|
166
|
+
async function main() {
|
|
167
|
+
console.clear();
|
|
168
|
+
showBranding();
|
|
169
|
+
intro(pc.bgCyan(pc.black(" create-n8n ")));
|
|
170
|
+
const cwd = process.cwd();
|
|
171
|
+
const cwdFiles = await readdir(cwd);
|
|
172
|
+
const allowedFiles = new Set([".git", ".DS_Store", ".vscode", ".idea", "Thumbs.db"]);
|
|
173
|
+
const isCwdEmpty = cwdFiles.every((f) => allowedFiles.has(f));
|
|
174
|
+
let targetDir = ".";
|
|
175
|
+
if (!isCwdEmpty) {
|
|
176
|
+
const dirPrompt = await text({
|
|
177
|
+
message: "Project name (creates a new folder, or '.' for current directory)",
|
|
178
|
+
placeholder: "my-n8n-app",
|
|
179
|
+
defaultValue: "my-n8n-app",
|
|
180
|
+
validate: (val) => {
|
|
181
|
+
if (!val || val.length === 0)
|
|
182
|
+
return "Project name is required";
|
|
183
|
+
if (!/^[a-z0-9-_./]+$/.test(val))
|
|
184
|
+
return "Invalid path format";
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
if (isCancel(dirPrompt) || typeof dirPrompt !== "string") {
|
|
189
|
+
cancel("Initialization cancelled.");
|
|
190
|
+
return process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
targetDir = dirPrompt;
|
|
193
|
+
if (targetDir === ".") {
|
|
194
|
+
const confirmOverwrite = await confirm({
|
|
195
|
+
message: "Current directory is not empty. Continue and potentially overwrite files?",
|
|
196
|
+
initialValue: false
|
|
197
|
+
});
|
|
198
|
+
if (isCancel(confirmOverwrite) || !confirmOverwrite) {
|
|
199
|
+
cancel("Initialization cancelled.");
|
|
200
|
+
return process.exit(1);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const projectDir = resolve(cwd, targetDir);
|
|
205
|
+
const projectName = targetDir === "." ? basename(cwd) : basename(projectDir);
|
|
206
|
+
const initGit = await confirm({
|
|
207
|
+
message: "Initialize a new Git repository?",
|
|
208
|
+
initialValue: true
|
|
209
|
+
});
|
|
210
|
+
if (isCancel(initGit)) {
|
|
211
|
+
cancel("Initialization cancelled.");
|
|
212
|
+
return process.exit(1);
|
|
213
|
+
}
|
|
214
|
+
const s = spinner();
|
|
215
|
+
s.start(`Creating project in ${projectDir}...`);
|
|
216
|
+
try {
|
|
217
|
+
// 1. Create Directories
|
|
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);
|
|
230
|
+
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
|
+
const cdInstruction = targetDir === "." ? "" : ` cd ${targetDir}\n`;
|
|
242
|
+
log.info(`\n${pc.bold("Next steps:")}\n${cdInstruction} npm install\n npm run dev`);
|
|
243
|
+
outro(pc.cyan("Happy automating! ") + pc.dim("— scaffolded by \x1b]8;;https://github.com/jvondev\x1b\\JVON DEV\x1b]8;;\x1b\\"));
|
|
244
|
+
process.exit(0);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
s.stop(pc.red("✖ Failed to create project"));
|
|
248
|
+
log.error(error.message);
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
main().catch(console.error);
|
package/index.ts
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
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);
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-n8n",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Scaffolding tool for n8n workflow projects",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"author": "JVON DEV <https://github.com/jvondev>",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"create-n8n": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"prepack": "npm run build"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@clack/prompts": "^1.7.0",
|
|
17
|
+
"picocolors": "^1.1.1"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@types/node": "^22.0.0",
|
|
21
|
+
"typescript": "^5.0.0"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=18.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"forceConsistentCasingInFileNames": true,
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"outDir": "./dist",
|
|
11
|
+
"types": ["node"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["index.ts"]
|
|
14
|
+
}
|