create-warlock 4.15.0 → 5.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/CHANGELOG.md +23 -0
- package/esm/commands/create-new-app/index.mjs +21 -6
- package/esm/commands/create-new-app/index.mjs.map +1 -1
- package/esm/commands/create-warlock-app/index.mjs +122 -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 +31 -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/package-manager.mjs +18 -1
- package/esm/helpers/package-manager.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/llms-full.txt +1 -1
- package/package.json +3 -3
- package/skills/create-a-warlock-project/SKILL.md +1 -1
- package/templates/warlock/package.json +12 -12
- 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,6 +7,23 @@ const execAsync = promisify(exec);
|
|
|
7
7
|
let detectedPackageManager;
|
|
8
8
|
let cachedSystemManagers;
|
|
9
9
|
let cachedPreferredManager;
|
|
10
|
+
/**
|
|
11
|
+
* The only package managers `--pm` may select. This value reaches `spawn()`
|
|
12
|
+
* as the executable to run and is spliced into the generated
|
|
13
|
+
* `package.json`'s scripts — an allow-list here is a hard security
|
|
14
|
+
* boundary, not just input hygiene, so it stays a fixed literal list
|
|
15
|
+
* rather than anything derived from user input or the running environment.
|
|
16
|
+
*/
|
|
17
|
+
const ALLOWED_PACKAGE_MANAGERS = [
|
|
18
|
+
"npm",
|
|
19
|
+
"yarn",
|
|
20
|
+
"pnpm",
|
|
21
|
+
"bun"
|
|
22
|
+
];
|
|
23
|
+
/** Whether `value` is one of the allow-listed package managers. */
|
|
24
|
+
function isValidPackageManager(value) {
|
|
25
|
+
return ALLOWED_PACKAGE_MANAGERS.includes(value);
|
|
26
|
+
}
|
|
10
27
|
function getPackageManager() {
|
|
11
28
|
if (detectedPackageManager) return detectedPackageManager;
|
|
12
29
|
return getPreferredPackageManager();
|
|
@@ -80,5 +97,5 @@ function runPackageManagerCommand(command) {
|
|
|
80
97
|
}
|
|
81
98
|
|
|
82
99
|
//#endregion
|
|
83
|
-
export { detectPackageManagers, getPackageManager, getPreferredPackageManager, getSystemPackageManagers, runPackageManagerCommand, setPackageManager };
|
|
100
|
+
export { ALLOWED_PACKAGE_MANAGERS, detectPackageManagers, getPackageManager, getPreferredPackageManager, getSystemPackageManagers, isValidPackageManager, runPackageManagerCommand, setPackageManager };
|
|
84
101
|
//# sourceMappingURL=package-manager.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"package-manager.mjs","names":[],"sources":["../../../../../../create-warlock/src/helpers/package-manager.ts"],"sourcesContent":["import { exec, execSync } from \"child_process\";\r\nimport { promisify } from \"util\";\r\nimport detectPackageManager from \"which-pm-runs\";\r\n\r\nconst execAsync = promisify(exec);\r\n\r\nlet detectedPackageManager: string | undefined;\r\nlet cachedSystemManagers: string[] | undefined;\r\nlet cachedPreferredManager: string | undefined;\r\n\r\nexport function getPackageManager() {\r\n if (detectedPackageManager) {\r\n return detectedPackageManager;\r\n }\r\n\r\n return getPreferredPackageManager();\r\n}\r\n\r\n/**\r\n * Check if a package manager is installed\r\n */\r\nfunction isInstalled(manager: string): boolean {\r\n try {\r\n execSync(`${manager} --version`, { stdio: \"ignore\" });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Check if a package manager is installed (async)\r\n */\r\nasync function checkManager(manager: string): Promise<boolean> {\r\n try {\r\n await execAsync(`${manager} --version`);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Detect available package managers asynchronously and cache results\r\n */\r\nexport async function detectPackageManagers() {\r\n const managers = [\"npm\"];\r\n const checks = [checkManager(\"yarn\"), checkManager(\"pnpm\")];\r\n\r\n const [hasYarn, hasPnpm] = await Promise.all(checks);\r\n\r\n if (hasYarn) managers.push(\"yarn\");\r\n if (hasPnpm) managers.push(\"pnpm\");\r\n\r\n cachedSystemManagers = managers;\r\n\r\n // Determine preference\r\n const runningPm = detectPackageManager()?.name;\r\n if (runningPm && runningPm !== \"npm\") {\r\n cachedPreferredManager = runningPm;\r\n } else if (hasYarn) {\r\n cachedPreferredManager = \"yarn\";\r\n } else if (hasPnpm) {\r\n cachedPreferredManager = \"pnpm\";\r\n } else {\r\n cachedPreferredManager = \"npm\";\r\n }\r\n}\r\n\r\n/**\r\n * Get available package managers on the system\r\n */\r\nexport function getSystemPackageManagers(): string[] {\r\n if (cachedSystemManagers) return cachedSystemManagers;\r\n\r\n const managers = [\"npm\"]; // npm is assumed to be always available\r\n\r\n if (isInstalled(\"yarn\")) {\r\n managers.push(\"yarn\");\r\n }\r\n\r\n if (isInstalled(\"pnpm\")) {\r\n managers.push(\"pnpm\");\r\n }\r\n\r\n return managers;\r\n}\r\n\r\n/**\r\n * Get the preferred package manager based on priority\r\n */\r\nexport function getPreferredPackageManager(): string {\r\n if (cachedPreferredManager) return cachedPreferredManager;\r\n\r\n // Priority 1: The manager currently running the script\r\n const runningPm = detectPackageManager()?.name;\r\n if (runningPm && runningPm !== \"npm\") return runningPm;\r\n\r\n // Priority 2: Yarn (if installed)\r\n if (isInstalled(\"yarn\")) return \"yarn\";\r\n\r\n // Priority 3: pnpm (if installed)\r\n if (isInstalled(\"pnpm\")) return \"pnpm\";\r\n\r\n // Priority 4: npm (default)\r\n return \"npm\";\r\n}\r\n\r\nexport function setPackageManager(packageManager: string) {\r\n detectedPackageManager = packageManager;\r\n}\r\n\r\nexport function installCommand() {\r\n return `${getPackageManager()} install`;\r\n}\r\n\r\nexport function startCommand() {\r\n if (getPackageManager() === \"npm\") return \"npm run dev\";\r\n\r\n return `${getPackageManager()} dev`;\r\n}\r\n\r\nexport function runPackageManagerCommand(command: string) {\r\n const packageManager = getPackageManager();\r\n\r\n if (packageManager === \"npm\") return `npm run ${command}`;\r\n\r\n return `${packageManager} ${command}`;\r\n}\r\n"],"mappings":";;;;;AAIA,MAAM,YAAY,UAAU,IAAI;AAEhC,IAAI;AACJ,IAAI;AACJ,IAAI;
|
|
1
|
+
{"version":3,"file":"package-manager.mjs","names":[],"sources":["../../../../../../create-warlock/src/helpers/package-manager.ts"],"sourcesContent":["import { exec, execSync } from \"child_process\";\r\nimport { promisify } from \"util\";\r\nimport detectPackageManager from \"which-pm-runs\";\r\n\r\nconst execAsync = promisify(exec);\r\n\r\nlet detectedPackageManager: string | undefined;\r\nlet cachedSystemManagers: string[] | undefined;\r\nlet cachedPreferredManager: string | undefined;\r\n\r\n/**\r\n * The only package managers `--pm` may select. This value reaches `spawn()`\r\n * as the executable to run and is spliced into the generated\r\n * `package.json`'s scripts — an allow-list here is a hard security\r\n * boundary, not just input hygiene, so it stays a fixed literal list\r\n * rather than anything derived from user input or the running environment.\r\n */\r\nexport const ALLOWED_PACKAGE_MANAGERS = [\"npm\", \"yarn\", \"pnpm\", \"bun\"] as const;\r\n\r\nexport type AllowedPackageManager = (typeof ALLOWED_PACKAGE_MANAGERS)[number];\r\n\r\n/** Whether `value` is one of the allow-listed package managers. */\r\nexport function isValidPackageManager(\r\n value: string,\r\n): value is AllowedPackageManager {\r\n return (ALLOWED_PACKAGE_MANAGERS as readonly string[]).includes(value);\r\n}\r\n\r\nexport function getPackageManager() {\r\n if (detectedPackageManager) {\r\n return detectedPackageManager;\r\n }\r\n\r\n return getPreferredPackageManager();\r\n}\r\n\r\n/**\r\n * Check if a package manager is installed\r\n */\r\nfunction isInstalled(manager: string): boolean {\r\n try {\r\n execSync(`${manager} --version`, { stdio: \"ignore\" });\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Check if a package manager is installed (async)\r\n */\r\nasync function checkManager(manager: string): Promise<boolean> {\r\n try {\r\n await execAsync(`${manager} --version`);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Detect available package managers asynchronously and cache results\r\n */\r\nexport async function detectPackageManagers() {\r\n const managers = [\"npm\"];\r\n const checks = [checkManager(\"yarn\"), checkManager(\"pnpm\")];\r\n\r\n const [hasYarn, hasPnpm] = await Promise.all(checks);\r\n\r\n if (hasYarn) managers.push(\"yarn\");\r\n if (hasPnpm) managers.push(\"pnpm\");\r\n\r\n cachedSystemManagers = managers;\r\n\r\n // Determine preference\r\n const runningPm = detectPackageManager()?.name;\r\n if (runningPm && runningPm !== \"npm\") {\r\n cachedPreferredManager = runningPm;\r\n } else if (hasYarn) {\r\n cachedPreferredManager = \"yarn\";\r\n } else if (hasPnpm) {\r\n cachedPreferredManager = \"pnpm\";\r\n } else {\r\n cachedPreferredManager = \"npm\";\r\n }\r\n}\r\n\r\n/**\r\n * Get available package managers on the system\r\n */\r\nexport function getSystemPackageManagers(): string[] {\r\n if (cachedSystemManagers) return cachedSystemManagers;\r\n\r\n const managers = [\"npm\"]; // npm is assumed to be always available\r\n\r\n if (isInstalled(\"yarn\")) {\r\n managers.push(\"yarn\");\r\n }\r\n\r\n if (isInstalled(\"pnpm\")) {\r\n managers.push(\"pnpm\");\r\n }\r\n\r\n return managers;\r\n}\r\n\r\n/**\r\n * Get the preferred package manager based on priority\r\n */\r\nexport function getPreferredPackageManager(): string {\r\n if (cachedPreferredManager) return cachedPreferredManager;\r\n\r\n // Priority 1: The manager currently running the script\r\n const runningPm = detectPackageManager()?.name;\r\n if (runningPm && runningPm !== \"npm\") return runningPm;\r\n\r\n // Priority 2: Yarn (if installed)\r\n if (isInstalled(\"yarn\")) return \"yarn\";\r\n\r\n // Priority 3: pnpm (if installed)\r\n if (isInstalled(\"pnpm\")) return \"pnpm\";\r\n\r\n // Priority 4: npm (default)\r\n return \"npm\";\r\n}\r\n\r\nexport function setPackageManager(packageManager: string) {\r\n detectedPackageManager = packageManager;\r\n}\r\n\r\nexport function installCommand() {\r\n return `${getPackageManager()} install`;\r\n}\r\n\r\nexport function startCommand() {\r\n if (getPackageManager() === \"npm\") return \"npm run dev\";\r\n\r\n return `${getPackageManager()} dev`;\r\n}\r\n\r\nexport function runPackageManagerCommand(command: string) {\r\n const packageManager = getPackageManager();\r\n\r\n if (packageManager === \"npm\") return `npm run ${command}`;\r\n\r\n return `${packageManager} ${command}`;\r\n}\r\n"],"mappings":";;;;;AAIA,MAAM,YAAY,UAAU,IAAI;AAEhC,IAAI;AACJ,IAAI;AACJ,IAAI;;;;;;;;AASJ,MAAa,2BAA2B;CAAC;CAAO;CAAQ;CAAQ;AAAK;;AAKrE,SAAgB,sBACd,OACgC;CAChC,OAAQ,yBAA+C,SAAS,KAAK;AACvE;AAEA,SAAgB,oBAAoB;CAClC,IAAI,wBACF,OAAO;CAGT,OAAO,2BAA2B;AACpC;;;;AAKA,SAAS,YAAY,SAA0B;CAC7C,IAAI;EACF,SAAS,GAAG,QAAQ,aAAa,EAAE,OAAO,SAAS,CAAC;EACpD,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;AAKA,eAAe,aAAa,SAAmC;CAC7D,IAAI;EACF,MAAM,UAAU,GAAG,QAAQ,WAAW;EACtC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;AAKA,eAAsB,wBAAwB;CAC5C,MAAM,WAAW,CAAC,KAAK;CACvB,MAAM,SAAS,CAAC,aAAa,MAAM,GAAG,aAAa,MAAM,CAAC;CAE1D,MAAM,CAAC,SAAS,WAAW,MAAM,QAAQ,IAAI,MAAM;CAEnD,IAAI,SAAS,SAAS,KAAK,MAAM;CACjC,IAAI,SAAS,SAAS,KAAK,MAAM;CAEjC,uBAAuB;CAGvB,MAAM,YAAY,qBAAqB,CAAC,EAAE;CAC1C,IAAI,aAAa,cAAc,OAC7B,yBAAyB;MACpB,IAAI,SACT,yBAAyB;MACpB,IAAI,SACT,yBAAyB;MAEzB,yBAAyB;AAE7B;;;;AAKA,SAAgB,2BAAqC;CACnD,IAAI,sBAAsB,OAAO;CAEjC,MAAM,WAAW,CAAC,KAAK;CAEvB,IAAI,YAAY,MAAM,GACpB,SAAS,KAAK,MAAM;CAGtB,IAAI,YAAY,MAAM,GACpB,SAAS,KAAK,MAAM;CAGtB,OAAO;AACT;;;;AAKA,SAAgB,6BAAqC;CACnD,IAAI,wBAAwB,OAAO;CAGnC,MAAM,YAAY,qBAAqB,CAAC,EAAE;CAC1C,IAAI,aAAa,cAAc,OAAO,OAAO;CAG7C,IAAI,YAAY,MAAM,GAAG,OAAO;CAGhC,IAAI,YAAY,MAAM,GAAG,OAAO;CAGhC,OAAO;AACT;AAEA,SAAgB,kBAAkB,gBAAwB;CACxD,yBAAyB;AAC3B;AAYA,SAAgB,yBAAyB,SAAiB;CACxD,MAAM,iBAAiB,kBAAkB;CAEzC,IAAI,mBAAmB,OAAO,OAAO,WAAW;CAEhD,OAAO,GAAG,eAAe,GAAG;AAC9B"}
|
|
@@ -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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAM,sBAAsB;;;;;AAM5B,SAAS,cAAsB;CAI7B,QAFE,QAAQ,IAAI,qBAAqB,KAAK,KAAK,6BAE9B,CAAC,QAAQ,QAAQ,EAAE;AACpC;;AAGA,SAAgB,cAAc,SAAyB;CACrD,MAAM,QAAQ,gBAAgB,KAAK,OAAO,CAAC,GAAG;CAE9C,OAAO,QAAQ,IAAI,MAAM,QAAQ;AACnC;;;;;;AAYA,eAAe,eACb,aACuC;CACvC,MAAM,MAAM,GAAG,YAAY,EAAE,GAAG,YAAY,QAAQ,KAAK,KAAK;CAE9D,IAAI;EACF,MAAM,WAAW,MAAM,MAAM,KAAK;GAChC,SAAS,EAAE,QAAQ,sCAAsC;GACzD,QAAQ,YAAY,QAAQ,mBAAmB;EACjD,CAAC;EAID,IAAI,SAAS,WAAW,OAAO,SAAS,WAAW,KAAK,OAAO;EAE/D,IAAI,CAAC,SAAS,IAAI,OAAO;EAEzB,OAAQ,MAAM,SAAS,KAAK;CAC9B,QAAQ;EACN;CACF;AACF;;;;;AAMA,eAAe,eACb,aACA,YACuE;CACvE,MAAM,YAAY,MAAM,eAAe,WAAW;CAElD,IAAI,cAAc,QAChB,OAAO;EACL,SAAS;EACT,SAAS,cAAc,UAAU;EACjC,QAAQ;EACR,WAAW;EACX,WAAW;CACb;CAGF,IAAI,cAAc,MAChB,OAAO;EACL,SAAS;EACT,SAAS,cAAc,UAAU;EACjC,QAAQ;EACR,WAAW;EACX,WAAW;CACb;CAGF,IAAI,UAAU,WAAW,aACvB,OAAO;EACL,SAAS;EACT,SAAS;EACT,QAAQ;EACR,WAAW;EACX,WAAW;CACb;CAGF,MAAM,SAAS,UAAU,YAAY,EAAE;CAEvC,IAAI,QACF,OAAO;EACL,SAAS;EACT,SAAS;EACT,QAAQ;EACR,WAAW;EACX,WAAW;CACb;CAGF,OAAO;EACL,SAAS;EACT,SAAS,cAAc,UAAU;EACjC,QAAQ;EACR,WAAW;EACX,WAAW;CACb;AACF;;;;;;AAOA,SAAgB,8BAAwC;CACtD,MAAM,sBAAsB,YAC1B,GAAG,SAAS,SAAS,EAAE,cACzB;CAKA,MAAM,wBAAQ,IAAI,IAAY;CAE9B,KAAK,MAAM,SAAS,CAAC,gBAAgB,iBAAiB,GACpD,KAAK,MAAM,QAAQ,OAAO,KAAK,oBAAoB,UAAU,CAAC,CAAC,GAC7D,IAAI,KAAK,WAAW,cAAc,GAAG,MAAM,IAAI,IAAI;CAIvD,OAAO,CAAC,GAAG,KAAK;AAClB;;AAGA,SAAgB,oBAA4B;CAC1C,OAAQ,YAAY,YAAY,cAAc,CAAC,CAAC,CAC7C;AACL;AAEA,IAAI;;;;;;;AAQJ,SAAgB,uBACd,WAAqB,4BAA4B,GACjD,aAAqB,kBAAkB,GACX;CAC5B,IAAI,QAAQ,OAAO;CAEnB,UAAU,YAAwC;EAChD,MAAM,cAAc,MAAM,QAAQ,IAChC,SAAS,KAAI,SAAQ,eAAe,MAAM,UAAU,CAAC,CACvD;EAEA,MAAM,WAAmC,CAAC;EAC1C,MAAM,QAAkB,CAAC;EACzB,MAAM,cAAwB,CAAC;EAC/B,MAAM,cAAiC,CAAC;EAExC,IAAI,UAAU;EAEd,KAAK,MAAM,cAAc,aAAa;GACpC,SAAS,WAAW,WAAW,WAAW;GAE1C,IAAI,CAAC,WAAW,WAAW,UAAU;GACrC,IAAI,CAAC,WAAW,WAAW,YAAY,KAAK,WAAW,OAAO;GAC9D,IAAI,WAAW,WAAW,mBAAmB,YAAY,KAAK,UAAU;EAC1E;EAEA,IAAI,SACF,MAAM,KACJ,+DAA+D,cAAc,UAAU,EAAE,8BAC3F;EAGF,IAAI,YAAY,SAAS,GAAG;GAC1B,MAAM,SAAS,YAAY,EAAE,CAAC;GAE9B,MAAM,KACJ,kBAAkB,WAAW,iFAAiF,OAAO,GACvH;EACF;EAEA,IAAI,YAAY,SAAS,GACvB,MAAM,KACJ,kCAAkC,YAAY,KAAK,IAAI,EAAE,kDAC3D;EAGF,OAAO;GAAE;GAAU;GAAO;GAAa;EAAQ;CACjD,EAAC,CAAE;CAEH,OAAO;AACT"}
|
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"}
|