create-warlock 4.16.0 → 5.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/CHANGELOG.md +11 -0
- package/esm/commands/create-new-app/index.mjs +14 -4
- package/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-warlock-app/index.mjs +123 -16
- package/esm/commands/create-warlock-app/index.mjs.map +1 -1
- package/esm/features/features-map.mjs +6 -0
- package/esm/features/features-map.mjs.map +1 -1
- package/esm/helpers/app.mjs +74 -3
- package/esm/helpers/app.mjs.map +1 -1
- package/esm/helpers/exec.mjs +164 -23
- package/esm/helpers/exec.mjs.map +1 -1
- package/esm/helpers/project-builder-helpers.mjs +22 -12
- package/esm/helpers/project-builder-helpers.mjs.map +1 -1
- package/esm/helpers/warlock-versions.mjs +166 -0
- package/esm/helpers/warlock-versions.mjs.map +1 -0
- package/esm/index.mjs +7 -1
- package/esm/index.mjs.map +1 -1
- package/esm/ui/report.mjs +98 -0
- package/esm/ui/report.mjs.map +1 -0
- package/esm/ui/spinners.mjs +13 -3
- package/esm/ui/spinners.mjs.map +1 -1
- package/package.json +2 -2
- package/templates/warlock/src/app/auth/controllers/forgot-password.controller.ts +2 -5
- package/templates/warlock/src/app/auth/controllers/login.controller.ts +4 -1
- package/templates/warlock/src/app/auth/controllers/logout-all.controller.ts +2 -2
- package/templates/warlock/src/app/auth/controllers/logout.controller.ts +2 -2
- package/templates/warlock/src/app/auth/controllers/me.controller.ts +2 -2
- package/templates/warlock/src/app/auth/controllers/refresh-token.controller.ts +2 -5
- package/templates/warlock/src/app/auth/controllers/reset-password.controller.ts +2 -2
- package/templates/warlock/src/app/posts/controllers/create-new-post.controller.ts +2 -2
- package/templates/warlock/src/app/posts/controllers/update-post.controller.ts +2 -2
- package/templates/warlock/src/app/shared/controllers/home-page.controller.ts +1 -1
- package/templates/warlock/src/app/shared/controllers/home-page.controller.tsx +2 -2
- package/templates/warlock/src/app/uploads/controllers/fetch-uploaded-file.controller.ts +1 -1
- package/templates/warlock/src/app/users/controllers/create-new-user.controller.ts +2 -2
- package/templates/warlock/src/app/users/controllers/list-users.controller.ts +1 -1
- package/templates/warlock/src/app/users/services/login-social.ts +13 -3
- package/templates/warlock/src/config/cache.ts +4 -1
package/esm/helpers/exec.mjs
CHANGED
|
@@ -4,44 +4,184 @@ import childProcess from "cross-spawn";
|
|
|
4
4
|
|
|
5
5
|
//#region ../create-warlock/src/helpers/exec.ts
|
|
6
6
|
/**
|
|
7
|
+
* The last command executed through {@link executeCommand}.
|
|
8
|
+
*
|
|
9
|
+
* `executeCommand` resolves a bare boolean (a contract several callers depend
|
|
10
|
+
* on), so the *reason* a command failed would otherwise be lost. It is parked
|
|
11
|
+
* here and claimed with {@link takeLastCommandOutput}, which clears it — a
|
|
12
|
+
* caller can never accidentally attribute a stale failure to the wrong step.
|
|
13
|
+
*
|
|
14
|
+
* The scaffolder runs one command at a time, so a single slot is enough.
|
|
15
|
+
*/
|
|
16
|
+
let lastCommandOutput;
|
|
17
|
+
/**
|
|
18
|
+
* Claim (and clear) the output of the most recent {@link executeCommand} call.
|
|
19
|
+
*/
|
|
20
|
+
function takeLastCommandOutput() {
|
|
21
|
+
const output = lastCommandOutput;
|
|
22
|
+
lastCommandOutput = void 0;
|
|
23
|
+
return output;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Keep only the last `lines` non-empty lines of a stream — enough context to
|
|
27
|
+
* act on, without dumping a 500-line npm log over the wizard.
|
|
28
|
+
*/
|
|
29
|
+
function tail(text, lines = 12) {
|
|
30
|
+
return text.split(/\r?\n/).filter((line) => line.trim().length > 0).slice(-lines).join("\n");
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Attach stdout/stderr collectors to a child and return the accumulated text.
|
|
34
|
+
*
|
|
35
|
+
* The streams are optional on purpose: a child spawned with `stdio: "ignore"`
|
|
36
|
+
* (or a test double) exposes none.
|
|
37
|
+
*/
|
|
38
|
+
function collectOutput(child) {
|
|
39
|
+
const chunks = {
|
|
40
|
+
stdout: "",
|
|
41
|
+
stderr: ""
|
|
42
|
+
};
|
|
43
|
+
child.stdout?.on("data", (data) => {
|
|
44
|
+
chunks.stdout += String(data);
|
|
45
|
+
});
|
|
46
|
+
child.stderr?.on("data", (data) => {
|
|
47
|
+
chunks.stderr += String(data);
|
|
48
|
+
});
|
|
49
|
+
return chunks;
|
|
50
|
+
}
|
|
51
|
+
function logSpawnError(error) {
|
|
52
|
+
if (!error) return;
|
|
53
|
+
const message = error?.message;
|
|
54
|
+
log.error(colors.red(String(message ?? error)) + `\n\n`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
7
57
|
* This function directly executes a command
|
|
58
|
+
*
|
|
59
|
+
* Resolves a boolean for backwards compatibility; the full outcome (exit code
|
|
60
|
+
* and captured output) is available to the caller via
|
|
61
|
+
* {@link takeLastCommandOutput}.
|
|
8
62
|
*/
|
|
9
63
|
async function executeCommand(cmd, args, cwd) {
|
|
64
|
+
const result = await runCapturedCommand(cmd, args, cwd);
|
|
65
|
+
lastCommandOutput = result;
|
|
66
|
+
return result.ok;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Run a command to completion and resolve its full {@link CommandResult}.
|
|
70
|
+
*
|
|
71
|
+
* Output is piped and captured rather than discarded — the whole point is that
|
|
72
|
+
* a failure can be explained instead of merely announced.
|
|
73
|
+
*/
|
|
74
|
+
function runCapturedCommand(cmd, args, cwd, options = {}) {
|
|
10
75
|
return new Promise((resolve) => {
|
|
11
|
-
const
|
|
76
|
+
const command = [cmd, ...args].join(" ");
|
|
77
|
+
const settle = (result) => resolve({
|
|
78
|
+
ok: false,
|
|
79
|
+
command,
|
|
12
80
|
cwd,
|
|
13
|
-
|
|
81
|
+
code: null,
|
|
82
|
+
signal: null,
|
|
83
|
+
stdout: "",
|
|
84
|
+
stderr: "",
|
|
85
|
+
...result
|
|
14
86
|
});
|
|
15
|
-
child
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
87
|
+
let child;
|
|
88
|
+
try {
|
|
89
|
+
child = childProcess(cmd, args, {
|
|
90
|
+
cwd,
|
|
91
|
+
stdio: [
|
|
92
|
+
"ignore",
|
|
93
|
+
"pipe",
|
|
94
|
+
"pipe"
|
|
95
|
+
],
|
|
96
|
+
env: options.env ? {
|
|
97
|
+
...process.env,
|
|
98
|
+
...options.env
|
|
99
|
+
} : process.env
|
|
100
|
+
});
|
|
101
|
+
} catch (error) {
|
|
102
|
+
logSpawnError(error);
|
|
103
|
+
return settle({
|
|
104
|
+
error,
|
|
105
|
+
stderr: String(error)
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const output = collectOutput(child);
|
|
109
|
+
child.on("error", (error) => {
|
|
110
|
+
logSpawnError(error);
|
|
111
|
+
settle({
|
|
112
|
+
error,
|
|
113
|
+
stdout: output.stdout,
|
|
114
|
+
stderr: output.stderr || String(error?.message ?? error)
|
|
115
|
+
});
|
|
19
116
|
});
|
|
20
|
-
child.on("close", (code) => {
|
|
21
|
-
|
|
22
|
-
|
|
117
|
+
child.on("close", (code, signal) => {
|
|
118
|
+
settle({
|
|
119
|
+
ok: code === 0,
|
|
120
|
+
code,
|
|
121
|
+
signal,
|
|
122
|
+
stdout: output.stdout,
|
|
123
|
+
stderr: output.stderr
|
|
124
|
+
});
|
|
23
125
|
});
|
|
24
126
|
});
|
|
25
127
|
}
|
|
26
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Run a long command with an abort handle.
|
|
130
|
+
*
|
|
131
|
+
* `install` stays a `Promise<boolean>` for existing callers; `result` carries
|
|
132
|
+
* the exit code and the captured output so a failing install can be reported
|
|
133
|
+
* with the command, its status and its stderr instead of a shrug.
|
|
134
|
+
*/
|
|
135
|
+
function runCommand(cmd, args, cwd, options = {}) {
|
|
27
136
|
let child;
|
|
28
|
-
const
|
|
137
|
+
const result = new Promise((resolve) => {
|
|
138
|
+
const command = [cmd, ...args].join(" ");
|
|
139
|
+
const settle = (partial) => resolve({
|
|
140
|
+
ok: false,
|
|
141
|
+
command,
|
|
142
|
+
cwd,
|
|
143
|
+
code: null,
|
|
144
|
+
signal: null,
|
|
145
|
+
stdout: "",
|
|
146
|
+
stderr: "",
|
|
147
|
+
...partial
|
|
148
|
+
});
|
|
29
149
|
try {
|
|
30
150
|
child = childProcess(cmd, args, {
|
|
31
151
|
cwd,
|
|
32
|
-
stdio:
|
|
152
|
+
stdio: [
|
|
153
|
+
"ignore",
|
|
154
|
+
"pipe",
|
|
155
|
+
"pipe"
|
|
156
|
+
],
|
|
157
|
+
env: options.env ? {
|
|
158
|
+
...process.env,
|
|
159
|
+
...options.env
|
|
160
|
+
} : process.env
|
|
161
|
+
});
|
|
162
|
+
const output = collectOutput(child);
|
|
163
|
+
child.on("error", (error) => {
|
|
164
|
+
logSpawnError(error);
|
|
165
|
+
settle({
|
|
166
|
+
error,
|
|
167
|
+
stdout: output.stdout,
|
|
168
|
+
stderr: output.stderr || String(error?.message ?? error)
|
|
169
|
+
});
|
|
33
170
|
});
|
|
34
|
-
child.on("
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
171
|
+
child.on("close", (code, signal) => {
|
|
172
|
+
settle({
|
|
173
|
+
ok: code === 0,
|
|
174
|
+
code,
|
|
175
|
+
signal,
|
|
176
|
+
stdout: output.stdout,
|
|
177
|
+
stderr: output.stderr
|
|
178
|
+
});
|
|
38
179
|
});
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
180
|
+
} catch (error) {
|
|
181
|
+
settle({
|
|
182
|
+
error,
|
|
183
|
+
stderr: String(error)
|
|
42
184
|
});
|
|
43
|
-
} catch (e) {
|
|
44
|
-
resolve(false);
|
|
45
185
|
}
|
|
46
186
|
});
|
|
47
187
|
const abort = async () => {
|
|
@@ -49,10 +189,11 @@ function runCommand(cmd, args, cwd) {
|
|
|
49
189
|
};
|
|
50
190
|
return {
|
|
51
191
|
abort,
|
|
52
|
-
install
|
|
192
|
+
install: result.then((outcome) => outcome.ok),
|
|
193
|
+
result
|
|
53
194
|
};
|
|
54
195
|
}
|
|
55
196
|
|
|
56
197
|
//#endregion
|
|
57
|
-
export { executeCommand, runCommand };
|
|
198
|
+
export { executeCommand, runCommand, tail, takeLastCommandOutput };
|
|
58
199
|
//# sourceMappingURL=exec.mjs.map
|
package/esm/helpers/exec.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exec.mjs","names":["spawn"],"sources":["../../../../../../create-warlock/src/helpers/exec.ts"],"sourcesContent":["import { log } from \"@clack/prompts\";\
|
|
1
|
+
{"version":3,"file":"exec.mjs","names":["spawn"],"sources":["../../../../../../create-warlock/src/helpers/exec.ts"],"sourcesContent":["import { log } from \"@clack/prompts\";\nimport { colors } from \"@mongez/copper\";\nimport { ChildProcess } from \"child_process\";\nimport { default as childProcess, default as spawn } from \"cross-spawn\";\n\n/**\n * The full outcome of a spawned command — everything a human needs to act on a\n * failure: what ran, where, how it ended, and what it printed.\n *\n * Every runner in this file produces one. A boolean is never enough: \"it\n * failed\" with no command, no exit code and no stderr is exactly how a broken\n * scaffold gets announced as a success.\n */\nexport type CommandResult = {\n /** Whether the command exited cleanly (code 0, no spawn error). */\n ok: boolean;\n /** The command as typed, e.g. `npm install`. */\n command: string;\n /** Directory the command ran in, when known. */\n cwd?: string;\n /** Exit code, or `null` when the process was signalled / never spawned. */\n code: number | null;\n /** Terminating signal, when the process was killed. */\n signal: NodeJS.Signals | null;\n stdout: string;\n stderr: string;\n /** Set when the process could not be spawned at all (ENOENT, EACCES, ...). */\n error?: Error;\n};\n\n/** Options accepted by the capturing runners. */\nexport type RunOptions = {\n /** Extra environment for the child; merged over `process.env`. */\n env?: NodeJS.ProcessEnv;\n};\n\n/**\n * The last command executed through {@link executeCommand}.\n *\n * `executeCommand` resolves a bare boolean (a contract several callers depend\n * on), so the *reason* a command failed would otherwise be lost. It is parked\n * here and claimed with {@link takeLastCommandOutput}, which clears it — a\n * caller can never accidentally attribute a stale failure to the wrong step.\n *\n * The scaffolder runs one command at a time, so a single slot is enough.\n */\nlet lastCommandOutput: CommandResult | undefined;\n\n/**\n * Claim (and clear) the output of the most recent {@link executeCommand} call.\n */\nexport function takeLastCommandOutput(): CommandResult | undefined {\n const output = lastCommandOutput;\n\n lastCommandOutput = undefined;\n\n return output;\n}\n\n/**\n * Keep only the last `lines` non-empty lines of a stream — enough context to\n * act on, without dumping a 500-line npm log over the wizard.\n */\nexport function tail(text: string, lines = 12): string {\n return text\n .split(/\\r?\\n/)\n .filter(line => line.trim().length > 0)\n .slice(-lines)\n .join(\"\\n\");\n}\n\nexport default async function exec(command: string, options: any = {}) {\n const [commandName, ...optionsList] = command.split(\" \");\n\n const commandOutput = childProcess.sync(commandName, optionsList, options);\n\n // it means command didn't end as expected, then stop the rest of the program\n if (commandOutput.error !== null) {\n process.exit(1);\n }\n\n return commandOutput;\n}\n\n/**\n * Attach stdout/stderr collectors to a child and return the accumulated text.\n *\n * The streams are optional on purpose: a child spawned with `stdio: \"ignore\"`\n * (or a test double) exposes none.\n */\nfunction collectOutput(child: ChildProcess) {\n const chunks = { stdout: \"\", stderr: \"\" };\n\n child.stdout?.on(\"data\", data => {\n chunks.stdout += String(data);\n });\n\n child.stderr?.on(\"data\", data => {\n chunks.stderr += String(data);\n });\n\n return chunks;\n}\n\nfunction logSpawnError(error: unknown) {\n if (!error) return;\n\n const message = (error as Error)?.message;\n\n log.error(colors.red(String(message ?? error)) + `\\n\\n`);\n}\n\n/**\n * This function directly executes a command\n *\n * Resolves a boolean for backwards compatibility; the full outcome (exit code\n * and captured output) is available to the caller via\n * {@link takeLastCommandOutput}.\n */\nexport async function executeCommand(cmd: string, args: string[], cwd: string) {\n const result = await runCapturedCommand(cmd, args, cwd);\n\n lastCommandOutput = result;\n\n return result.ok;\n}\n\n/**\n * Run a command to completion and resolve its full {@link CommandResult}.\n *\n * Output is piped and captured rather than discarded — the whole point is that\n * a failure can be explained instead of merely announced.\n */\nexport function runCapturedCommand(\n cmd: string,\n args: string[],\n cwd: string,\n options: RunOptions = {},\n): Promise<CommandResult> {\n return new Promise<CommandResult>(resolve => {\n const command = [cmd, ...args].join(\" \");\n\n const settle = (result: Partial<CommandResult>): void =>\n resolve({\n ok: false,\n command,\n cwd,\n code: null,\n signal: null,\n stdout: \"\",\n stderr: \"\",\n ...result,\n });\n\n let child: ChildProcess;\n\n try {\n child = spawn(cmd, args, {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: options.env ? { ...process.env, ...options.env } : process.env,\n });\n } catch (error) {\n logSpawnError(error);\n\n return settle({ error: error as Error, stderr: String(error) });\n }\n\n const output = collectOutput(child);\n\n child.on(\"error\", error => {\n logSpawnError(error);\n\n settle({\n error: error as Error,\n stdout: output.stdout,\n stderr: output.stderr || String((error as Error)?.message ?? error),\n });\n });\n\n child.on(\"close\", (code, signal) => {\n settle({\n ok: code === 0,\n code,\n signal,\n stdout: output.stdout,\n stderr: output.stderr,\n });\n });\n });\n}\n\n/**\n * Run a long command with an abort handle.\n *\n * `install` stays a `Promise<boolean>` for existing callers; `result` carries\n * the exit code and the captured output so a failing install can be reported\n * with the command, its status and its stderr instead of a shrug.\n */\nexport function runCommand(\n cmd: string,\n args: string[],\n cwd: string,\n options: RunOptions = {},\n) {\n let child: ChildProcess;\n\n const result = new Promise<CommandResult>(resolve => {\n const command = [cmd, ...args].join(\" \");\n\n const settle = (partial: Partial<CommandResult>): void =>\n resolve({\n ok: false,\n command,\n cwd,\n code: null,\n signal: null,\n stdout: \"\",\n stderr: \"\",\n ...partial,\n });\n\n try {\n child = spawn(cmd, args, {\n cwd,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n env: options.env ? { ...process.env, ...options.env } : process.env,\n });\n\n const output = collectOutput(child);\n\n child.on(\"error\", error => {\n logSpawnError(error);\n\n settle({\n error: error as Error,\n stdout: output.stdout,\n stderr: output.stderr || String((error as Error)?.message ?? error),\n });\n });\n\n child.on(\"close\", (code, signal) => {\n settle({\n ok: code === 0,\n code,\n signal,\n stdout: output.stdout,\n stderr: output.stderr,\n });\n });\n } catch (error) {\n settle({ error: error as Error, stderr: String(error) });\n }\n });\n\n const abort = async () => {\n if (child) {\n child.kill(\"SIGINT\");\n }\n };\n\n return { abort, install: result.then(outcome => outcome.ok), result };\n}\n"],"mappings":"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"}
|
|
@@ -7,19 +7,29 @@ import { copyDirectory, getFile, getJsonFile, putFile, putJsonFile, renameFile }
|
|
|
7
7
|
import path from "path";
|
|
8
8
|
|
|
9
9
|
//#region ../create-warlock/src/helpers/project-builder-helpers.ts
|
|
10
|
+
/**
|
|
11
|
+
* Bootstrap the project's git repository.
|
|
12
|
+
*
|
|
13
|
+
* Returns `false` as soon as a step fails, and leaves that step's captured
|
|
14
|
+
* output where the caller can claim it with `takeLastCommandOutput()`. It used
|
|
15
|
+
* to `return true` unconditionally, which meant a machine without git printed
|
|
16
|
+
* "Grimoire initialized!" over a directory that was not a repository.
|
|
17
|
+
*/
|
|
10
18
|
async function initializeGitRepository(appPath) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
for (const args of [
|
|
20
|
+
["init"],
|
|
21
|
+
[
|
|
22
|
+
"checkout",
|
|
23
|
+
"-b",
|
|
24
|
+
"main"
|
|
25
|
+
],
|
|
26
|
+
["add", "."],
|
|
27
|
+
[
|
|
28
|
+
"commit",
|
|
29
|
+
"-m",
|
|
30
|
+
"Initial commit"
|
|
31
|
+
]
|
|
32
|
+
]) if (!await executeCommand(`git`, args, appPath)) return false;
|
|
23
33
|
return true;
|
|
24
34
|
}
|
|
25
35
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-builder-helpers.mjs","names":[],"sources":["../../../../../../create-warlock/src/helpers/project-builder-helpers.ts"],"sourcesContent":["import { outro } from \"@clack/prompts\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport {\r\n copyDirectory,\r\n getFile,\r\n getJsonFile,\r\n putFile,\r\n putJsonFile,\r\n renameFile,\r\n} from \"@warlock.js/fs\";\r\nimport path from \"path\";\r\nimport { executeCommand } from \"./exec\";\r\nimport { startCommand } from \"./package-manager\";\r\nimport { Template, template } from \"./paths\";\r\n\r\nexport async function initializeGitRepository(appPath: string) {\r\n // initialize git repository\r\n
|
|
1
|
+
{"version":3,"file":"project-builder-helpers.mjs","names":[],"sources":["../../../../../../create-warlock/src/helpers/project-builder-helpers.ts"],"sourcesContent":["import { outro } from \"@clack/prompts\";\r\nimport { colors } from \"@mongez/copper\";\r\nimport {\r\n copyDirectory,\r\n getFile,\r\n getJsonFile,\r\n putFile,\r\n putJsonFile,\r\n renameFile,\r\n} from \"@warlock.js/fs\";\r\nimport path from \"path\";\r\nimport { executeCommand } from \"./exec\";\r\nimport { startCommand } from \"./package-manager\";\r\nimport { Template, template } from \"./paths\";\r\n\r\n/**\r\n * Bootstrap the project's git repository.\r\n *\r\n * Returns `false` as soon as a step fails, and leaves that step's captured\r\n * output where the caller can claim it with `takeLastCommandOutput()`. It used\r\n * to `return true` unconditionally, which meant a machine without git printed\r\n * \"Grimoire initialized!\" over a directory that was not a repository.\r\n */\r\nexport async function initializeGitRepository(appPath: string) {\r\n const steps: string[][] = [\r\n // initialize git repository\r\n [\"init\"],\r\n // switching to `main` branch\r\n [\"checkout\", \"-b\", \"main\"],\r\n // add files\r\n [\"add\", \".\"],\r\n // commit files\r\n [\"commit\", \"-m\", \"Initial commit\"],\r\n ];\r\n\r\n for (const args of steps) {\r\n const succeeded = await executeCommand(`git`, args, appPath);\r\n\r\n if (!succeeded) return false;\r\n }\r\n\r\n return true;\r\n}\r\n\r\nexport async function updateEnvFile(appPath: string, appName: string) {\r\n // update package.json file\r\n const packageJson: any = getJsonFile(path.resolve(appPath, \"package.json\"));\r\n\r\n packageJson.name = appName;\r\n\r\n putJsonFile(path.resolve(appPath, \"package.json\"), packageJson);\r\n\r\n // update env file\r\n const dotEnv = getFile(path.resolve(appPath, \".env\"))\r\n .replace(\"AppName\", appName)\r\n .replace(\r\n \"AppCodeName\",\r\n appName\r\n .split(/-|_/g)\r\n .map(word => word[0])\r\n .join(\"\"),\r\n );\r\n\r\n putFile(path.resolve(appPath, \".env\"), dotEnv);\r\n\r\n // update .env.production file\r\n let dotEnvProduction = getFile(path.resolve(appPath, \".env.shared\"));\r\n\r\n dotEnvProduction = dotEnvProduction.replace(\"AppName\", appName).replace(\r\n \"AppCodeName\",\r\n appName\r\n .split(/-|_/g)\r\n .map(word => word[0])\r\n .join(\"\"),\r\n );\r\n\r\n putFile(path.resolve(appPath, \".env.shared\"), dotEnvProduction);\r\n}\r\n\r\nexport async function copyTemplateFiles(\r\n templateName: Template,\r\n appPath: string,\r\n _appName: string,\r\n) {\r\n // copy project files\r\n copyDirectory(template(templateName), appPath);\r\n\r\n // replace _.gitignore to\r\n renameFile(\r\n path.resolve(appPath, \"_.gitignore\"),\r\n path.resolve(appPath, \".gitignore\"),\r\n );\r\n}\r\n\r\nexport async function allDone(appName: string) {\r\n outro(\r\n \"Awesome! Your project is ready to rock! \" +\r\n \"Run the following command to start development:\",\r\n );\r\n\r\n console.log(colors.cyan(`cd ${appName} && ${startCommand()}`));\r\n\r\n console.log();\r\n\r\n console.log(\r\n `Pro tip: Install the ${colors.yellow(\r\n \"Generator Z\",\r\n )} extension in VSCode for helpful code snippets and productivity boosters!`,\r\n );\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;AAuBA,eAAsB,wBAAwB,SAAiB;CAY7D,KAAK,MAAM,QAAQ;EATjB,CAAC,MAAM;EAEP;GAAC;GAAY;GAAM;EAAM;EAEzB,CAAC,OAAO,GAAG;EAEX;GAAC;GAAU;GAAM;EAAgB;CAGZ,GAGrB,IAAI,CAAC,MAFmB,eAAe,OAAO,MAAM,OAAO,GAE3C,OAAO;CAGzB,OAAO;AACT"}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { packageRoot, template } from "./paths.mjs";
|
|
2
|
+
import { getJsonFile } from "@warlock.js/fs";
|
|
3
|
+
|
|
4
|
+
//#region ../create-warlock/src/helpers/warlock-versions.ts
|
|
5
|
+
/**
|
|
6
|
+
* Resolving the version to stamp onto the generated project's `@warlock.js/*`
|
|
7
|
+
* dependencies.
|
|
8
|
+
*
|
|
9
|
+
* ## Why this file exists
|
|
10
|
+
*
|
|
11
|
+
* The scaffolder used to stamp its OWN version onto every sibling package
|
|
12
|
+
* (`"@warlock.js/core": "4.16.2"`). That is only correct while the scaffolder's
|
|
13
|
+
* version is published — and it usually is not: the release tooling bumps the
|
|
14
|
+
* source version on every build, including `--no-publish` builds, so between
|
|
15
|
+
* publishes the working tree carries a version that exists nowhere on the
|
|
16
|
+
* registry. Every project scaffolded in that window pinned eight dependencies
|
|
17
|
+
* to a version npm cannot resolve, and the install died with `ETARGET`.
|
|
18
|
+
*
|
|
19
|
+
* ## The rule
|
|
20
|
+
*
|
|
21
|
+
* Never write a dependency version we have not established exists. Resolution
|
|
22
|
+
* order, per package:
|
|
23
|
+
*
|
|
24
|
+
* 1. the scaffolder's own version, IF the registry has it published — this
|
|
25
|
+
* preserves the lockstep guarantee the pin was introduced for;
|
|
26
|
+
* 2. otherwise the registry's `latest` — the newest thing that actually
|
|
27
|
+
* exists, with a note explaining the substitution;
|
|
28
|
+
* 3. otherwise (registry unreachable) a caret range floored to the major,
|
|
29
|
+
* e.g. `^4.0.0` — always satisfiable by any published 4.x, and the
|
|
30
|
+
* scaffold-time install writes a lockfile that freezes the result anyway.
|
|
31
|
+
*
|
|
32
|
+
* A package that resolves to nothing (404 — never published) is reported, not
|
|
33
|
+
* papered over: it is the difference between "your install failed" and "the
|
|
34
|
+
* feature you asked for does not exist yet".
|
|
35
|
+
*/
|
|
36
|
+
const REGISTRY_TIMEOUT_MS = 6e3;
|
|
37
|
+
/**
|
|
38
|
+
* Registry to query. `npm_config_registry` is set by npm/npx when the
|
|
39
|
+
* scaffolder runs through them, so a private mirror is honoured for free.
|
|
40
|
+
*/
|
|
41
|
+
function registryUrl() {
|
|
42
|
+
return (process.env.npm_config_registry?.trim() || "https://registry.npmjs.org").replace(/\/+$/, "");
|
|
43
|
+
}
|
|
44
|
+
/** `4.16.2` -> `^4.0.0`; anything unparseable -> `latest`. */
|
|
45
|
+
function fallbackRange(version) {
|
|
46
|
+
const major = /^\s*v?(\d+)\./.exec(version)?.[1];
|
|
47
|
+
return major ? `^${major}.0.0` : "latest";
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Fetch the abbreviated packument for a package. Returns `undefined` when the
|
|
51
|
+
* registry cannot be reached (network / timeout) and `null` when the registry
|
|
52
|
+
* answers that the package does not exist.
|
|
53
|
+
*/
|
|
54
|
+
async function fetchPackument(packageName) {
|
|
55
|
+
const url = `${registryUrl()}/${packageName.replace("/", "%2F")}`;
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(url, {
|
|
58
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
59
|
+
signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS)
|
|
60
|
+
});
|
|
61
|
+
if (response.status === 404 || response.status === 401) return null;
|
|
62
|
+
if (!response.ok) return void 0;
|
|
63
|
+
return await response.json();
|
|
64
|
+
} catch {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Resolve one package against the registry. Pure aside from the fetch, so the
|
|
70
|
+
* decision table above is readable in one place.
|
|
71
|
+
*/
|
|
72
|
+
async function resolvePackage(packageName, ownVersion) {
|
|
73
|
+
const packument = await fetchPackument(packageName);
|
|
74
|
+
if (packument === void 0) return {
|
|
75
|
+
package: packageName,
|
|
76
|
+
version: fallbackRange(ownVersion),
|
|
77
|
+
source: "range-fallback",
|
|
78
|
+
published: true,
|
|
79
|
+
reachable: false
|
|
80
|
+
};
|
|
81
|
+
if (packument === null) return {
|
|
82
|
+
package: packageName,
|
|
83
|
+
version: fallbackRange(ownVersion),
|
|
84
|
+
source: "range-fallback",
|
|
85
|
+
published: false,
|
|
86
|
+
reachable: true
|
|
87
|
+
};
|
|
88
|
+
if (packument.versions?.[ownVersion]) return {
|
|
89
|
+
package: packageName,
|
|
90
|
+
version: ownVersion,
|
|
91
|
+
source: "own-version",
|
|
92
|
+
published: true,
|
|
93
|
+
reachable: true
|
|
94
|
+
};
|
|
95
|
+
const latest = packument["dist-tags"]?.latest;
|
|
96
|
+
if (latest) return {
|
|
97
|
+
package: packageName,
|
|
98
|
+
version: latest,
|
|
99
|
+
source: "registry-latest",
|
|
100
|
+
published: true,
|
|
101
|
+
reachable: true
|
|
102
|
+
};
|
|
103
|
+
return {
|
|
104
|
+
package: packageName,
|
|
105
|
+
version: fallbackRange(ownVersion),
|
|
106
|
+
source: "range-fallback",
|
|
107
|
+
published: true,
|
|
108
|
+
reachable: true
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Every `@warlock.js/*` dependency the template declares. Read from the
|
|
113
|
+
* template rather than the copied project so resolution can start before (or
|
|
114
|
+
* in parallel with) the copy.
|
|
115
|
+
*/
|
|
116
|
+
function templateWarlockDependencies() {
|
|
117
|
+
const templatePackageJson = getJsonFile(`${template("warlock")}/package.json`);
|
|
118
|
+
const names = /* @__PURE__ */ new Set();
|
|
119
|
+
for (const field of ["dependencies", "devDependencies"]) for (const name of Object.keys(templatePackageJson[field] ?? {})) if (name.startsWith("@warlock.js/")) names.add(name);
|
|
120
|
+
return [...names];
|
|
121
|
+
}
|
|
122
|
+
/** The scaffolder's own published version, i.e. the lockstep candidate. */
|
|
123
|
+
function scaffolderVersion() {
|
|
124
|
+
return getJsonFile(packageRoot("package.json")).version;
|
|
125
|
+
}
|
|
126
|
+
let cached;
|
|
127
|
+
/**
|
|
128
|
+
* Resolve the versions to stamp, once per process.
|
|
129
|
+
*
|
|
130
|
+
* Never throws and never blocks a scaffold: the worst case is the caret
|
|
131
|
+
* fallback plus a note saying so.
|
|
132
|
+
*/
|
|
133
|
+
function resolveWarlockVersions(packages = templateWarlockDependencies(), ownVersion = scaffolderVersion()) {
|
|
134
|
+
if (cached) return cached;
|
|
135
|
+
cached = (async () => {
|
|
136
|
+
const resolutions = await Promise.all(packages.map((name) => resolvePackage(name, ownVersion)));
|
|
137
|
+
const versions = {};
|
|
138
|
+
const notes = [];
|
|
139
|
+
const unpublished = [];
|
|
140
|
+
const substituted = [];
|
|
141
|
+
let offline = false;
|
|
142
|
+
for (const resolution of resolutions) {
|
|
143
|
+
versions[resolution.package] = resolution.version;
|
|
144
|
+
if (!resolution.reachable) offline = true;
|
|
145
|
+
if (!resolution.published) unpublished.push(resolution.package);
|
|
146
|
+
if (resolution.source === "registry-latest") substituted.push(resolution);
|
|
147
|
+
}
|
|
148
|
+
if (offline) notes.push(`Could not reach the npm registry — pinning @warlock.js/* to ${fallbackRange(ownVersion)} instead of an exact version.`);
|
|
149
|
+
if (substituted.length > 0) {
|
|
150
|
+
const latest = substituted[0].version;
|
|
151
|
+
notes.push(`create-warlock ${ownVersion} is not published yet — pinning @warlock.js/* to the latest published version (${latest}).`);
|
|
152
|
+
}
|
|
153
|
+
if (unpublished.length > 0) notes.push(`Not published on the registry: ${unpublished.join(", ")} — the install will fail until they are released.`);
|
|
154
|
+
return {
|
|
155
|
+
versions,
|
|
156
|
+
notes,
|
|
157
|
+
unpublished,
|
|
158
|
+
offline
|
|
159
|
+
};
|
|
160
|
+
})();
|
|
161
|
+
return cached;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
//#endregion
|
|
165
|
+
export { fallbackRange, resolveWarlockVersions };
|
|
166
|
+
//# sourceMappingURL=warlock-versions.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"warlock-versions.mjs","names":[],"sources":["../../../../../../create-warlock/src/helpers/warlock-versions.ts"],"sourcesContent":["/**\n * Resolving the version to stamp onto the generated project's `@warlock.js/*`\n * dependencies.\n *\n * ## Why this file exists\n *\n * The scaffolder used to stamp its OWN version onto every sibling package\n * (`\"@warlock.js/core\": \"4.16.2\"`). That is only correct while the scaffolder's\n * version is published — and it usually is not: the release tooling bumps the\n * source version on every build, including `--no-publish` builds, so between\n * publishes the working tree carries a version that exists nowhere on the\n * registry. Every project scaffolded in that window pinned eight dependencies\n * to a version npm cannot resolve, and the install died with `ETARGET`.\n *\n * ## The rule\n *\n * Never write a dependency version we have not established exists. Resolution\n * order, per package:\n *\n * 1. the scaffolder's own version, IF the registry has it published — this\n * preserves the lockstep guarantee the pin was introduced for;\n * 2. otherwise the registry's `latest` — the newest thing that actually\n * exists, with a note explaining the substitution;\n * 3. otherwise (registry unreachable) a caret range floored to the major,\n * e.g. `^4.0.0` — always satisfiable by any published 4.x, and the\n * scaffold-time install writes a lockfile that freezes the result anyway.\n *\n * A package that resolves to nothing (404 — never published) is reported, not\n * papered over: it is the difference between \"your install failed\" and \"the\n * feature you asked for does not exist yet\".\n */\n\nimport { getJsonFile } from \"@warlock.js/fs\";\nimport { packageRoot, template } from \"./paths\";\n\nexport type VersionSource =\n \"own-version\" | \"registry-latest\" | \"range-fallback\";\n\nexport type ResolvedVersion = {\n package: string;\n /** The exact version or range to write into the generated package.json. */\n version: string;\n source: VersionSource;\n};\n\nexport type VersionResolution = {\n /** package name -> version/range to stamp. */\n versions: Record<string, string>;\n /** Human-readable notes worth showing before the install runs. */\n notes: string[];\n /** Packages the registry does not know about at all. */\n unpublished: string[];\n /** True when the registry could not be reached and ranges were guessed. */\n offline: boolean;\n};\n\nconst REGISTRY_TIMEOUT_MS = 6_000;\n\n/**\n * Registry to query. `npm_config_registry` is set by npm/npx when the\n * scaffolder runs through them, so a private mirror is honoured for free.\n */\nfunction registryUrl(): string {\n const registry =\n process.env.npm_config_registry?.trim() || \"https://registry.npmjs.org\";\n\n return registry.replace(/\\/+$/, \"\");\n}\n\n/** `4.16.2` -> `^4.0.0`; anything unparseable -> `latest`. */\nexport function fallbackRange(version: string): string {\n const major = /^\\s*v?(\\d+)\\./.exec(version)?.[1];\n\n return major ? `^${major}.0.0` : \"latest\";\n}\n\ntype Packument = {\n \"dist-tags\"?: Record<string, string>;\n versions?: Record<string, unknown>;\n};\n\n/**\n * Fetch the abbreviated packument for a package. Returns `undefined` when the\n * registry cannot be reached (network / timeout) and `null` when the registry\n * answers that the package does not exist.\n */\nasync function fetchPackument(\n packageName: string,\n): Promise<Packument | null | undefined> {\n const url = `${registryUrl()}/${packageName.replace(\"/\", \"%2F\")}`;\n\n try {\n const response = await fetch(url, {\n headers: { accept: \"application/vnd.npm.install-v1+json\" },\n signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS),\n });\n\n // 404 (public) and 401 (scoped package the registry hides) both mean the\n // same thing to us: there is nothing here to install.\n if (response.status === 404 || response.status === 401) return null;\n\n if (!response.ok) return undefined;\n\n return (await response.json()) as Packument;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Resolve one package against the registry. Pure aside from the fetch, so the\n * decision table above is readable in one place.\n */\nasync function resolvePackage(\n packageName: string,\n ownVersion: string,\n): Promise<ResolvedVersion & { published: boolean; reachable: boolean }> {\n const packument = await fetchPackument(packageName);\n\n if (packument === undefined) {\n return {\n package: packageName,\n version: fallbackRange(ownVersion),\n source: \"range-fallback\",\n published: true,\n reachable: false,\n };\n }\n\n if (packument === null) {\n return {\n package: packageName,\n version: fallbackRange(ownVersion),\n source: \"range-fallback\",\n published: false,\n reachable: true,\n };\n }\n\n if (packument.versions?.[ownVersion]) {\n return {\n package: packageName,\n version: ownVersion,\n source: \"own-version\",\n published: true,\n reachable: true,\n };\n }\n\n const latest = packument[\"dist-tags\"]?.latest;\n\n if (latest) {\n return {\n package: packageName,\n version: latest,\n source: \"registry-latest\",\n published: true,\n reachable: true,\n };\n }\n\n return {\n package: packageName,\n version: fallbackRange(ownVersion),\n source: \"range-fallback\",\n published: true,\n reachable: true,\n };\n}\n\n/**\n * Every `@warlock.js/*` dependency the template declares. Read from the\n * template rather than the copied project so resolution can start before (or\n * in parallel with) the copy.\n */\nexport function templateWarlockDependencies(): string[] {\n const templatePackageJson = getJsonFile(\n `${template(\"warlock\")}/package.json`,\n ) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n\n const names = new Set<string>();\n\n for (const field of [\"dependencies\", \"devDependencies\"] as const) {\n for (const name of Object.keys(templatePackageJson[field] ?? {})) {\n if (name.startsWith(\"@warlock.js/\")) names.add(name);\n }\n }\n\n return [...names];\n}\n\n/** The scaffolder's own published version, i.e. the lockstep candidate. */\nexport function scaffolderVersion(): string {\n return (getJsonFile(packageRoot(\"package.json\")) as { version: string })\n .version;\n}\n\nlet cached: Promise<VersionResolution> | undefined;\n\n/**\n * Resolve the versions to stamp, once per process.\n *\n * Never throws and never blocks a scaffold: the worst case is the caret\n * fallback plus a note saying so.\n */\nexport function resolveWarlockVersions(\n packages: string[] = templateWarlockDependencies(),\n ownVersion: string = scaffolderVersion(),\n): Promise<VersionResolution> {\n if (cached) return cached;\n\n cached = (async (): Promise<VersionResolution> => {\n const resolutions = await Promise.all(\n packages.map(name => resolvePackage(name, ownVersion)),\n );\n\n const versions: Record<string, string> = {};\n const notes: string[] = [];\n const unpublished: string[] = [];\n const substituted: ResolvedVersion[] = [];\n\n let offline = false;\n\n for (const resolution of resolutions) {\n versions[resolution.package] = resolution.version;\n\n if (!resolution.reachable) offline = true;\n if (!resolution.published) unpublished.push(resolution.package);\n if (resolution.source === \"registry-latest\") substituted.push(resolution);\n }\n\n if (offline) {\n notes.push(\n `Could not reach the npm registry — pinning @warlock.js/* to ${fallbackRange(ownVersion)} instead of an exact version.`,\n );\n }\n\n if (substituted.length > 0) {\n const latest = substituted[0].version;\n\n notes.push(\n `create-warlock ${ownVersion} is not published yet — pinning @warlock.js/* to the latest published version (${latest}).`,\n );\n }\n\n if (unpublished.length > 0) {\n notes.push(\n `Not published on the registry: ${unpublished.join(\", \")} — the install will fail until they are released.`,\n );\n }\n\n return { versions, notes, unpublished, offline };\n })();\n\n return cached;\n}\n\n/** Test/CLI seam: forget the memoized resolution. */\nexport function resetWarlockVersionsCache() {\n cached = undefined;\n}\n"],"mappings":"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"}
|
package/esm/index.mjs
CHANGED
|
@@ -79,7 +79,13 @@ function splitList(value) {
|
|
|
79
79
|
return value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
80
80
|
}
|
|
81
81
|
function createApp() {
|
|
82
|
-
|
|
82
|
+
const flags = parseFlags(process.argv.slice(2));
|
|
83
|
+
Promise.resolve(createNewApp(flags)).catch((error) => {
|
|
84
|
+
console.error();
|
|
85
|
+
console.error(` create-warlock failed: ${error?.message ?? String(error)}`);
|
|
86
|
+
console.error();
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|
|
83
89
|
}
|
|
84
90
|
|
|
85
91
|
//#endregion
|
package/esm/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../create-warlock/src/index.ts"],"sourcesContent":["import createNewApp from \"./commands/create-new-app\";\nimport { CliFlags } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\"];\n\n/**\n * Parse the scaffolder's own CLI flags for non-interactive mode.\n *\n * @example\n * create-warlock my-app --db=postgres --features=test,herald --ai=openai,anthropic --yes\n */\nexport function parseFlags(argv: string[]): CliFlags {\n const flags: CliFlags = {};\n const positionals: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n\n if (!arg.startsWith(\"-\")) {\n positionals.push(arg);\n continue;\n }\n\n const equalIndex = arg.indexOf(\"=\");\n const key = (equalIndex === -1 ? arg : arg.slice(0, equalIndex)).replace(/^-+/, \"\");\n let value: string | undefined = equalIndex === -1 ? undefined : arg.slice(equalIndex + 1);\n\n // Value-taking flags may use either `--key=value` or `--key value`.\n if (valueFlags.includes(key) && value === undefined) {\n const next = argv[i + 1];\n\n if (next && !next.startsWith(\"-\")) {\n value = next;\n i++;\n }\n }\n\n switch (key) {\n case \"yes\":\n case \"y\":\n flags.yes = true;\n break;\n case \"git\":\n flags.git = true;\n break;\n case \"no-git\":\n flags.git = false;\n break;\n case \"jwt\":\n flags.jwt = true;\n break;\n case \"no-jwt\":\n flags.jwt = false;\n break;\n case \"name\":\n flags.name = value;\n break;\n case \"db\":\n flags.db = value;\n break;\n case \"no-db\":\n // Opt out of a database entirely — equivalent to `--db=none`.\n flags.db = NO_DATABASE;\n break;\n case \"pm\":\n flags.pm = value;\n break;\n case \"features\":\n flags.features = splitList(value);\n break;\n case \"ai\":\n flags.ai = splitList(value);\n break;\n }\n }\n\n if (!flags.name && positionals.length > 0) {\n flags.name = positionals[0];\n }\n\n return flags;\n}\n\nfunction splitList(value: string | undefined): string[] {\n if (!value) return [];\n\n return value\n .split(\",\")\n .map(item => item.trim())\n .filter(Boolean);\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n createNewApp(flags);\n}\n"],"mappings":";;;;AAIA,MAAM,aAAa;CAAC;CAAQ;CAAM;CAAM;CAAY;AAAI;;;;;;;AAQxD,SAAgB,WAAW,MAA0B;CACnD,MAAM,QAAkB,CAAC;CACzB,MAAM,cAAwB,CAAC;CAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;GACxB,YAAY,KAAK,GAAG;GACpB;EACF;EAEA,MAAM,aAAa,IAAI,QAAQ,GAAG;EAClC,MAAM,OAAO,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU,EAAC,CAAE,QAAQ,OAAO,EAAE;EAClF,IAAI,QAA4B,eAAe,KAAK,SAAY,IAAI,MAAM,aAAa,CAAC;EAGxF,IAAI,WAAW,SAAS,GAAG,KAAK,UAAU,QAAW;GACnD,MAAM,OAAO,KAAK,IAAI;GAEtB,IAAI,QAAQ,CAAC,KAAK,WAAW,GAAG,GAAG;IACjC,QAAQ;IACR;GACF;EACF;EAEA,QAAQ,KAAR;GACE,KAAK;GACL,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IAEH,MAAM,KAAK;IACX;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IACH,MAAM,WAAW,UAAU,KAAK;IAChC;GACF,KAAK;IACH,MAAM,KAAK,UAAU,KAAK;IAC1B;EACJ;CACF;CAEA,IAAI,CAAC,MAAM,QAAQ,YAAY,SAAS,GACtC,MAAM,OAAO,YAAY;CAG3B,OAAO;AACT;AAEA,SAAS,UAAU,OAAqC;CACtD,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,OAAO,OAAO;AACnB;AAEA,SAAwB,YAAY;
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../../create-warlock/src/index.ts"],"sourcesContent":["import createNewApp from \"./commands/create-new-app\";\nimport { CliFlags } from \"./commands/create-new-app/types\";\nimport { NO_DATABASE } from \"./features/database-drivers\";\n\nconst valueFlags = [\"name\", \"db\", \"pm\", \"features\", \"ai\"];\n\n/**\n * Parse the scaffolder's own CLI flags for non-interactive mode.\n *\n * @example\n * create-warlock my-app --db=postgres --features=test,herald --ai=openai,anthropic --yes\n */\nexport function parseFlags(argv: string[]): CliFlags {\n const flags: CliFlags = {};\n const positionals: string[] = [];\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n\n if (!arg.startsWith(\"-\")) {\n positionals.push(arg);\n continue;\n }\n\n const equalIndex = arg.indexOf(\"=\");\n const key = (equalIndex === -1 ? arg : arg.slice(0, equalIndex)).replace(/^-+/, \"\");\n let value: string | undefined = equalIndex === -1 ? undefined : arg.slice(equalIndex + 1);\n\n // Value-taking flags may use either `--key=value` or `--key value`.\n if (valueFlags.includes(key) && value === undefined) {\n const next = argv[i + 1];\n\n if (next && !next.startsWith(\"-\")) {\n value = next;\n i++;\n }\n }\n\n switch (key) {\n case \"yes\":\n case \"y\":\n flags.yes = true;\n break;\n case \"git\":\n flags.git = true;\n break;\n case \"no-git\":\n flags.git = false;\n break;\n case \"jwt\":\n flags.jwt = true;\n break;\n case \"no-jwt\":\n flags.jwt = false;\n break;\n case \"name\":\n flags.name = value;\n break;\n case \"db\":\n flags.db = value;\n break;\n case \"no-db\":\n // Opt out of a database entirely — equivalent to `--db=none`.\n flags.db = NO_DATABASE;\n break;\n case \"pm\":\n flags.pm = value;\n break;\n case \"features\":\n flags.features = splitList(value);\n break;\n case \"ai\":\n flags.ai = splitList(value);\n break;\n }\n }\n\n if (!flags.name && positionals.length > 0) {\n flags.name = positionals[0];\n }\n\n return flags;\n}\n\nfunction splitList(value: string | undefined): string[] {\n if (!value) return [];\n\n return value\n .split(\",\")\n .map(item => item.trim())\n .filter(Boolean);\n}\n\nexport default function createApp() {\n const flags = parseFlags(process.argv.slice(2));\n\n // An unexpected throw must surface as a readable error AND a non-zero exit\n // code — never as a stack trace the user scrolls past on the way to a green\n // banner (there is no banner after this point).\n Promise.resolve(createNewApp(flags)).catch((error: unknown) => {\n console.error();\n console.error(\n ` create-warlock failed: ${(error as Error)?.message ?? String(error)}`,\n );\n console.error();\n\n process.exit(1);\n });\n}\n"],"mappings":";;;;AAIA,MAAM,aAAa;CAAC;CAAQ;CAAM;CAAM;CAAY;AAAI;;;;;;;AAQxD,SAAgB,WAAW,MAA0B;CACnD,MAAM,QAAkB,CAAC;CACzB,MAAM,cAAwB,CAAC;CAE/B,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,MAAM,KAAK;EAEjB,IAAI,CAAC,IAAI,WAAW,GAAG,GAAG;GACxB,YAAY,KAAK,GAAG;GACpB;EACF;EAEA,MAAM,aAAa,IAAI,QAAQ,GAAG;EAClC,MAAM,OAAO,eAAe,KAAK,MAAM,IAAI,MAAM,GAAG,UAAU,EAAC,CAAE,QAAQ,OAAO,EAAE;EAClF,IAAI,QAA4B,eAAe,KAAK,SAAY,IAAI,MAAM,aAAa,CAAC;EAGxF,IAAI,WAAW,SAAS,GAAG,KAAK,UAAU,QAAW;GACnD,MAAM,OAAO,KAAK,IAAI;GAEtB,IAAI,QAAQ,CAAC,KAAK,WAAW,GAAG,GAAG;IACjC,QAAQ;IACR;GACF;EACF;EAEA,QAAQ,KAAR;GACE,KAAK;GACL,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,MAAM;IACZ;GACF,KAAK;IACH,MAAM,OAAO;IACb;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IAEH,MAAM,KAAK;IACX;GACF,KAAK;IACH,MAAM,KAAK;IACX;GACF,KAAK;IACH,MAAM,WAAW,UAAU,KAAK;IAChC;GACF,KAAK;IACH,MAAM,KAAK,UAAU,KAAK;IAC1B;EACJ;CACF;CAEA,IAAI,CAAC,MAAM,QAAQ,YAAY,SAAS,GACtC,MAAM,OAAO,YAAY;CAG3B,OAAO;AACT;AAEA,SAAS,UAAU,OAAqC;CACtD,IAAI,CAAC,OAAO,OAAO,CAAC;CAEpB,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,KAAI,SAAQ,KAAK,KAAK,CAAC,CAAC,CACxB,OAAO,OAAO;AACnB;AAEA,SAAwB,YAAY;CAClC,MAAM,QAAQ,WAAW,QAAQ,KAAK,MAAM,CAAC,CAAC;CAK9C,QAAQ,QAAQ,aAAa,KAAK,CAAC,CAAC,CAAC,OAAO,UAAmB;EAC7D,QAAQ,MAAM;EACd,QAAQ,MACN,4BAA6B,OAAiB,WAAW,OAAO,KAAK,GACvE;EACA,QAAQ,MAAM;EAEd,QAAQ,KAAK,CAAC;CAChB,CAAC;AACH"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { tail } from "../helpers/exec.mjs";
|
|
2
|
+
import { colors } from "@mongez/copper";
|
|
3
|
+
|
|
4
|
+
//#region ../create-warlock/src/ui/report.ts
|
|
5
|
+
const bullet = colors.red("✖");
|
|
6
|
+
/**
|
|
7
|
+
* The command line, its exit status, and the tail of what it printed.
|
|
8
|
+
*/
|
|
9
|
+
function describeCommand(result) {
|
|
10
|
+
const lines = [];
|
|
11
|
+
lines.push(`${colors.dim("command:")} ${colors.white(result.command)}`);
|
|
12
|
+
if (result.cwd) lines.push(`${colors.dim("in:")} ${colors.white(result.cwd)}`);
|
|
13
|
+
if (result.error) lines.push(`${colors.dim("failed:")} ${colors.white(result.error.message || String(result.error))}`);
|
|
14
|
+
else if (result.signal) lines.push(`${colors.dim("killed:")} ${colors.white(result.signal)}`);
|
|
15
|
+
else lines.push(`${colors.dim("exited:")} ${colors.white(`code ${result.code}`)}`);
|
|
16
|
+
const output = tail(result.stderr) || tail(result.stdout);
|
|
17
|
+
if (output) {
|
|
18
|
+
lines.push(colors.dim("output:"));
|
|
19
|
+
for (const line of output.split("\n")) lines.push(` ${colors.dim(line)}`);
|
|
20
|
+
}
|
|
21
|
+
return lines;
|
|
22
|
+
}
|
|
23
|
+
function printProblem(problem) {
|
|
24
|
+
console.log(` ${bullet} ${colors.bold(colors.red(problem.step))}`);
|
|
25
|
+
console.log(` ${colors.white(problem.detail)}`);
|
|
26
|
+
if (problem.result) for (const line of describeCommand(problem.result)) console.log(` ${line}`);
|
|
27
|
+
for (const hint of problem.hints ?? []) console.log(` ${colors.yellow("→")} ${colors.yellow(hint)}`);
|
|
28
|
+
console.log();
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Report a failure the scaffold cannot continue past, and leave the process
|
|
32
|
+
* with a non-zero exit code. Nothing after this point may print a success.
|
|
33
|
+
*/
|
|
34
|
+
function failFatally(problem) {
|
|
35
|
+
console.log();
|
|
36
|
+
console.log(colors.bold(colors.red(" SCAFFOLD FAILED")));
|
|
37
|
+
console.log();
|
|
38
|
+
printProblem(problem);
|
|
39
|
+
console.log(colors.dim(" The project directory was left in place so you can inspect it."));
|
|
40
|
+
console.log();
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Report the steps that failed on a scaffold that otherwise completed, and say
|
|
45
|
+
* plainly what the project does NOT have as a result.
|
|
46
|
+
*/
|
|
47
|
+
function showProblems(problems) {
|
|
48
|
+
if (problems.length === 0) return;
|
|
49
|
+
console.log();
|
|
50
|
+
console.log(colors.bold(colors.yellow(` COMPLETED WITH ${problems.length} PROBLEM${problems.length === 1 ? "" : "S"}`)));
|
|
51
|
+
console.log();
|
|
52
|
+
for (const problem of problems) printProblem(problem);
|
|
53
|
+
}
|
|
54
|
+
/** Neutral, non-failing information — e.g. which versions got pinned and why. */
|
|
55
|
+
function showNotes(notes) {
|
|
56
|
+
for (const note of notes) console.log(` ${colors.yellow("!")} ${colors.dim(note)}`);
|
|
57
|
+
if (notes.length > 0) console.log();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A scaffold that finished with problems still produced a project, so print the
|
|
61
|
+
* same facts the success screen would — minus the celebration, and listing only
|
|
62
|
+
* what is actually installed.
|
|
63
|
+
*/
|
|
64
|
+
function showPartialScreen(options) {
|
|
65
|
+
const { projectName, database, features, missingFeatures, packageManager } = options;
|
|
66
|
+
const devCommand = packageManager === "npm" ? "npm run dev" : `${packageManager} dev`;
|
|
67
|
+
console.log(` ${colors.bold(colors.yellow("⚠ PROJECT CREATED — BUT NOT AS REQUESTED"))}`);
|
|
68
|
+
console.log();
|
|
69
|
+
console.log(` ${colors.dim("Project: ")}${colors.white(projectName)}`);
|
|
70
|
+
console.log(` ${colors.dim("Database: ")}${colors.white(database)}`);
|
|
71
|
+
console.log(` ${colors.dim("Installed:")}${colors.white(features.length > 0 ? " " + features.join(", ") : " none")}`);
|
|
72
|
+
if (missingFeatures.length > 0) console.log(` ${colors.dim("Missing: ")}${colors.red(missingFeatures.join(", "))}`);
|
|
73
|
+
console.log();
|
|
74
|
+
console.log(` ${colors.dim("Fix the problems above, then:")}`);
|
|
75
|
+
console.log();
|
|
76
|
+
console.log(` ${colors.cyan("cd")} ${projectName}`);
|
|
77
|
+
if (missingFeatures.length > 0) console.log(` ${colors.cyan(`npx warlock add ${missingFeatures.join(" ")}`)}`);
|
|
78
|
+
console.log(` ${colors.cyan(devCommand)}`);
|
|
79
|
+
console.log();
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Turn an npm/yarn/pnpm failure into actionable guidance where we can
|
|
83
|
+
* recognise it. `ETARGET` in particular is the signature of a dependency
|
|
84
|
+
* pinned to a version that was never published — the bug this whole reporting
|
|
85
|
+
* path was written for.
|
|
86
|
+
*/
|
|
87
|
+
function installFailureHints(result) {
|
|
88
|
+
const hints = ["Nothing was installed. Fix the error above, then run the install again inside the project."];
|
|
89
|
+
const output = `${result?.stdout ?? ""}${result?.stderr ?? ""}`;
|
|
90
|
+
if (/ETARGET|No matching version found/i.test(output)) hints.push("A dependency is pinned to a version that does not exist on the registry — check the @warlock.js/* versions in package.json.");
|
|
91
|
+
if (/ENOTFOUND|ETIMEDOUT|ECONNREFUSED|network/i.test(output)) hints.push("The registry was unreachable — check your network or proxy.");
|
|
92
|
+
if (/EACCES|EPERM/i.test(output)) hints.push("Permission denied — check the directory's ownership before retrying.");
|
|
93
|
+
return hints;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
//#endregion
|
|
97
|
+
export { failFatally, installFailureHints, showNotes, showPartialScreen, showProblems };
|
|
98
|
+
//# sourceMappingURL=report.mjs.map
|