create-bunli 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/dist/cli.js +589 -560
- package/dist/create-project.d.ts +4 -4
- package/dist/create-project.d.ts.map +1 -1
- package/dist/create.d.ts +3 -3
- package/dist/create.d.ts.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +518 -512
- package/dist/steps.d.ts +5 -5
- package/dist/steps.d.ts.map +1 -1
- package/dist/template-engine.d.ts +2 -2
- package/dist/template-engine.d.ts.map +1 -1
- package/dist/templates/advanced/README.md +8 -4
- package/dist/templates/advanced/bunli.config.ts +19 -19
- package/dist/templates/advanced/package.json +8 -4
- package/dist/templates/advanced/src/commands/config.ts +129 -119
- package/dist/templates/advanced/src/commands/init.ts +53 -60
- package/dist/templates/advanced/src/commands/serve.ts +77 -83
- package/dist/templates/advanced/src/commands/validate.ts +58 -66
- package/dist/templates/advanced/src/index.ts +30 -29
- package/dist/templates/advanced/src/utils/config.ts +48 -47
- package/dist/templates/advanced/src/utils/constants.ts +6 -6
- package/dist/templates/advanced/src/utils/glob.ts +29 -28
- package/dist/templates/advanced/src/utils/validator.ts +60 -61
- package/dist/templates/advanced/template.json +2 -6
- package/dist/templates/advanced/tsconfig.json +1 -1
- package/dist/templates/basic/README.md +1 -1
- package/dist/templates/basic/bunli.config.ts +17 -17
- package/dist/templates/basic/package.json +3 -3
- package/dist/templates/basic/src/commands/hello.ts +20 -26
- package/dist/templates/basic/src/index.ts +9 -8
- package/dist/templates/basic/template.json +2 -6
- package/dist/templates/basic/tsconfig.json +1 -1
- package/dist/templates/monorepo/README.md +1 -1
- package/dist/templates/monorepo/bunli.config.ts +21 -21
- package/dist/templates/monorepo/package.json +3 -3
- package/dist/templates/monorepo/packages/cli/package.json +9 -5
- package/dist/templates/monorepo/packages/cli/src/index.ts +13 -13
- package/dist/templates/monorepo/packages/cli/tsconfig.json +2 -5
- package/dist/templates/monorepo/packages/core/package.json +4 -4
- package/dist/templates/monorepo/packages/core/scripts/build.ts +10 -10
- package/dist/templates/monorepo/packages/core/src/commands/analyze.ts +58 -57
- package/dist/templates/monorepo/packages/core/src/commands/process.ts +39 -47
- package/dist/templates/monorepo/packages/core/src/index.ts +3 -3
- package/dist/templates/monorepo/packages/core/src/types.ts +15 -15
- package/dist/templates/monorepo/packages/core/tsconfig.json +2 -4
- package/dist/templates/monorepo/packages/utils/package.json +4 -4
- package/dist/templates/monorepo/packages/utils/scripts/build.ts +10 -10
- package/dist/templates/monorepo/packages/utils/src/format.ts +19 -19
- package/dist/templates/monorepo/packages/utils/src/index.ts +3 -3
- package/dist/templates/monorepo/packages/utils/src/json.ts +4 -4
- package/dist/templates/monorepo/packages/utils/src/logger.ts +9 -9
- package/dist/templates/monorepo/packages/utils/tsconfig.json +1 -1
- package/dist/templates/monorepo/template.json +2 -6
- package/dist/templates/monorepo/tsconfig.json +1 -1
- package/dist/templates/monorepo/turbo.json +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +28 -28
- package/templates/advanced/README.md +8 -4
- package/templates/advanced/bunli.config.ts +19 -19
- package/templates/advanced/package.json +8 -4
- package/templates/advanced/src/commands/config.ts +129 -119
- package/templates/advanced/src/commands/init.ts +53 -60
- package/templates/advanced/src/commands/serve.ts +77 -83
- package/templates/advanced/src/commands/validate.ts +58 -66
- package/templates/advanced/src/index.ts +30 -29
- package/templates/advanced/src/utils/config.ts +48 -47
- package/templates/advanced/src/utils/constants.ts +6 -6
- package/templates/advanced/src/utils/glob.ts +29 -28
- package/templates/advanced/src/utils/validator.ts +60 -61
- package/templates/advanced/template.json +2 -6
- package/templates/advanced/tsconfig.json +1 -1
- package/templates/basic/README.md +1 -1
- package/templates/basic/bunli.config.ts +17 -17
- package/templates/basic/package.json +3 -3
- package/templates/basic/src/commands/hello.ts +20 -26
- package/templates/basic/src/index.ts +9 -8
- package/templates/basic/template.json +2 -6
- package/templates/basic/tsconfig.json +1 -1
- package/templates/monorepo/README.md +1 -1
- package/templates/monorepo/bunli.config.ts +21 -21
- package/templates/monorepo/package.json +3 -3
- package/templates/monorepo/packages/cli/package.json +9 -5
- package/templates/monorepo/packages/cli/src/index.ts +13 -13
- package/templates/monorepo/packages/cli/tsconfig.json +2 -5
- package/templates/monorepo/packages/core/package.json +4 -4
- package/templates/monorepo/packages/core/scripts/build.ts +10 -10
- package/templates/monorepo/packages/core/src/commands/analyze.ts +58 -57
- package/templates/monorepo/packages/core/src/commands/process.ts +39 -47
- package/templates/monorepo/packages/core/src/index.ts +3 -3
- package/templates/monorepo/packages/core/src/types.ts +15 -15
- package/templates/monorepo/packages/core/tsconfig.json +2 -4
- package/templates/monorepo/packages/utils/package.json +4 -4
- package/templates/monorepo/packages/utils/scripts/build.ts +10 -10
- package/templates/monorepo/packages/utils/src/format.ts +19 -19
- package/templates/monorepo/packages/utils/src/index.ts +3 -3
- package/templates/monorepo/packages/utils/src/json.ts +4 -4
- package/templates/monorepo/packages/utils/src/logger.ts +9 -9
- package/templates/monorepo/packages/utils/tsconfig.json +1 -1
- package/templates/monorepo/template.json +2 -6
- package/templates/monorepo/tsconfig.json +1 -1
- package/templates/monorepo/turbo.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,289 +1,4 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
// src/template-engine.ts
|
|
3
|
-
import { downloadTemplate } from "giget";
|
|
4
|
-
import { readdir } from "fs/promises";
|
|
5
|
-
import { join } from "path";
|
|
6
|
-
async function processTemplate(options) {
|
|
7
|
-
const { source, dir, offline, variables = {} } = options;
|
|
8
|
-
let templateDir;
|
|
9
|
-
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
|
|
10
|
-
const sourceDir = source.startsWith("/") ? source : join(process.cwd(), source);
|
|
11
|
-
const copyExitCode = await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
|
|
12
|
-
stdout: "inherit",
|
|
13
|
-
stderr: "inherit"
|
|
14
|
-
}).exited;
|
|
15
|
-
if (copyExitCode !== 0) {
|
|
16
|
-
throw new Error(`Failed to copy local template from ${sourceDir}`);
|
|
17
|
-
}
|
|
18
|
-
templateDir = dir;
|
|
19
|
-
} else {
|
|
20
|
-
const result = await downloadTemplate(source, {
|
|
21
|
-
dir,
|
|
22
|
-
offline,
|
|
23
|
-
preferOffline: true,
|
|
24
|
-
force: true
|
|
25
|
-
});
|
|
26
|
-
templateDir = result.dir;
|
|
27
|
-
}
|
|
28
|
-
const manifest = await loadTemplateManifest(templateDir);
|
|
29
|
-
if (manifest?.files || Object.keys(variables).length > 0) {
|
|
30
|
-
await processTemplateFiles(templateDir, variables, manifest);
|
|
31
|
-
}
|
|
32
|
-
if (manifest?.hooks?.postInstall) {
|
|
33
|
-
await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
|
|
34
|
-
}
|
|
35
|
-
return { dir: templateDir, manifest };
|
|
36
|
-
}
|
|
37
|
-
async function loadTemplateManifest(dir) {
|
|
38
|
-
const possiblePaths = [
|
|
39
|
-
join(dir, "template.json"),
|
|
40
|
-
join(dir, ".template.json"),
|
|
41
|
-
join(dir, "template.yaml"),
|
|
42
|
-
join(dir, ".template.yaml")
|
|
43
|
-
];
|
|
44
|
-
for (const path of possiblePaths) {
|
|
45
|
-
const file = Bun.file(path);
|
|
46
|
-
if (await file.exists()) {
|
|
47
|
-
const content = await file.text();
|
|
48
|
-
if (path.endsWith(".json")) {
|
|
49
|
-
const manifest = JSON.parse(content);
|
|
50
|
-
const removeExitCode = await Bun.spawn(["rm", "-f", path], {
|
|
51
|
-
stdout: "ignore",
|
|
52
|
-
stderr: "ignore"
|
|
53
|
-
}).exited;
|
|
54
|
-
if (removeExitCode !== 0) {
|
|
55
|
-
console.warn(`Warning: failed to remove template manifest ${path}`);
|
|
56
|
-
}
|
|
57
|
-
return manifest;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
async function processTemplateFiles(dir, variables, manifest) {
|
|
64
|
-
const files = await getFilesToProcess(dir, manifest);
|
|
65
|
-
for (const file of files) {
|
|
66
|
-
const filePath = join(dir, file);
|
|
67
|
-
const content = await Bun.file(filePath).text();
|
|
68
|
-
let processedContent = content;
|
|
69
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
70
|
-
processedContent = processedContent.replaceAll(`{{${key}}}`, value).replaceAll(`<%= ${key} %>`, value).replaceAll(`$${key}`, value).replaceAll(`__${key}__`, value);
|
|
71
|
-
}
|
|
72
|
-
let newFilePath = filePath;
|
|
73
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
74
|
-
newFilePath = newFilePath.replaceAll(`__${key}__`, value);
|
|
75
|
-
}
|
|
76
|
-
await Bun.write(newFilePath, processedContent);
|
|
77
|
-
if (newFilePath !== filePath) {
|
|
78
|
-
await Bun.spawn(["rm", filePath]).exited;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
async function getFilesToProcess(dir, manifest) {
|
|
83
|
-
if (manifest?.files?.include) {
|
|
84
|
-
return manifest.files.include;
|
|
85
|
-
}
|
|
86
|
-
const files = [];
|
|
87
|
-
async function walk(currentDir, prefix = "") {
|
|
88
|
-
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
89
|
-
for (const entry of entries) {
|
|
90
|
-
const path = join(prefix, entry.name);
|
|
91
|
-
if (entry.isDirectory()) {
|
|
92
|
-
if (!["node_modules", ".git", ".next", "dist", "build"].includes(entry.name)) {
|
|
93
|
-
await walk(join(currentDir, entry.name), path);
|
|
94
|
-
}
|
|
95
|
-
} else {
|
|
96
|
-
if (!path.match(/^(template\.json|\.template\.json|\.DS_Store|Thumbs\.db)$/) || path === ".gitignore") {
|
|
97
|
-
files.push(path);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
await walk(dir);
|
|
103
|
-
if (manifest?.files?.exclude) {
|
|
104
|
-
return files.filter((file) => {
|
|
105
|
-
return !manifest.files.exclude.some((pattern) => {
|
|
106
|
-
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
107
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
108
|
-
return regex.test(file);
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
return files;
|
|
113
|
-
}
|
|
114
|
-
async function runPostInstallHooks(dir, hooks) {
|
|
115
|
-
for (const hook of hooks) {
|
|
116
|
-
const proc = Bun.spawn(hook.split(" "), {
|
|
117
|
-
cwd: dir,
|
|
118
|
-
stdout: "inherit",
|
|
119
|
-
stderr: "inherit"
|
|
120
|
-
});
|
|
121
|
-
const exitCode = await proc.exited;
|
|
122
|
-
if (exitCode !== 0) {
|
|
123
|
-
throw new Error(`Post-install hook failed: ${hook}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function resolveTemplateSource(template) {
|
|
128
|
-
const specialTemplates = {
|
|
129
|
-
basic: "github:bunli/templates/basic",
|
|
130
|
-
advanced: "github:bunli/templates/advanced",
|
|
131
|
-
monorepo: "github:bunli/templates/monorepo"
|
|
132
|
-
};
|
|
133
|
-
if (specialTemplates[template]) {
|
|
134
|
-
return specialTemplates[template];
|
|
135
|
-
}
|
|
136
|
-
if (template.startsWith("npm:")) {
|
|
137
|
-
return template.replace("npm:", "npm:/");
|
|
138
|
-
}
|
|
139
|
-
if (template.includes("/") && !template.includes(":")) {
|
|
140
|
-
return `github:${template}`;
|
|
141
|
-
}
|
|
142
|
-
return template;
|
|
143
|
-
}
|
|
144
|
-
function getBundledTemplatePath(name) {
|
|
145
|
-
return join(import.meta.dir, "..", "templates", name);
|
|
146
|
-
}
|
|
147
|
-
async function isLocalTemplate(template) {
|
|
148
|
-
if (template.startsWith("file:") || template.startsWith("./") || template.startsWith("../")) {
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
const bundledPath = getBundledTemplatePath(template);
|
|
152
|
-
return await Bun.file(join(bundledPath, "package.json")).exists();
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// src/steps.ts
|
|
156
|
-
import { existsSync } from "fs";
|
|
157
|
-
import { join as join2 } from "path";
|
|
158
|
-
var LOCKFILE_MAP = [
|
|
159
|
-
["bun.lock", "bun"],
|
|
160
|
-
["bun.lockb", "bun"],
|
|
161
|
-
["pnpm-lock.yaml", "pnpm"],
|
|
162
|
-
["yarn.lock", "yarn"],
|
|
163
|
-
["package-lock.json", "npm"]
|
|
164
|
-
];
|
|
165
|
-
function detectPackageManager(cwd) {
|
|
166
|
-
const dir = cwd ?? process.cwd();
|
|
167
|
-
for (const [lockfile, manager] of LOCKFILE_MAP) {
|
|
168
|
-
if (existsSync(join2(dir, lockfile))) {
|
|
169
|
-
return manager;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const userAgent = process.env.npm_config_user_agent;
|
|
173
|
-
if (userAgent) {
|
|
174
|
-
if (userAgent.startsWith("bun"))
|
|
175
|
-
return "bun";
|
|
176
|
-
if (userAgent.startsWith("pnpm"))
|
|
177
|
-
return "pnpm";
|
|
178
|
-
if (userAgent.startsWith("yarn"))
|
|
179
|
-
return "yarn";
|
|
180
|
-
if (userAgent.startsWith("npm"))
|
|
181
|
-
return "npm";
|
|
182
|
-
}
|
|
183
|
-
return "bun";
|
|
184
|
-
}
|
|
185
|
-
function isInGitRepo(cwd) {
|
|
186
|
-
try {
|
|
187
|
-
const result = Bun.spawnSync(["git", "rev-parse", "--is-inside-work-tree"], { cwd: cwd ?? process.cwd(), stdout: "ignore", stderr: "ignore" });
|
|
188
|
-
return result.exitCode === 0;
|
|
189
|
-
} catch {
|
|
190
|
-
return false;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
async function runSteps(dir, steps) {
|
|
194
|
-
for (const step of steps) {
|
|
195
|
-
switch (step.type) {
|
|
196
|
-
case "install":
|
|
197
|
-
await runInstall(dir);
|
|
198
|
-
break;
|
|
199
|
-
case "git-init":
|
|
200
|
-
await runGitInit(dir, step.commit);
|
|
201
|
-
break;
|
|
202
|
-
case "open-editor":
|
|
203
|
-
await runOpenEditor(dir);
|
|
204
|
-
break;
|
|
205
|
-
case "command":
|
|
206
|
-
await runCommand(step.cmd, step.cwd ?? dir);
|
|
207
|
-
break;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
async function runInstall(cwd) {
|
|
212
|
-
const pm = detectPackageManager(cwd);
|
|
213
|
-
const proc = Bun.spawn([pm, "install"], {
|
|
214
|
-
cwd,
|
|
215
|
-
stdout: "pipe",
|
|
216
|
-
stderr: "pipe"
|
|
217
|
-
});
|
|
218
|
-
const exitCode = await proc.exited;
|
|
219
|
-
if (exitCode !== 0) {
|
|
220
|
-
const stderr = await new Response(proc.stderr).text();
|
|
221
|
-
throw new Error(`"${pm} install" exited with code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
async function runGitInit(cwd, commit) {
|
|
225
|
-
await spawnChecked(["git", "init"], cwd, "git init");
|
|
226
|
-
if (commit) {
|
|
227
|
-
await ensureGitIdentity(cwd);
|
|
228
|
-
await spawnChecked(["git", "add", "."], cwd, "git add");
|
|
229
|
-
await spawnChecked(["git", "commit", "-m", commit], cwd, "git commit");
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
async function runOpenEditor(cwd) {
|
|
233
|
-
const editor = process.env.EDITOR || "code";
|
|
234
|
-
try {
|
|
235
|
-
const proc = Bun.spawn([editor, cwd], {
|
|
236
|
-
stdout: "ignore",
|
|
237
|
-
stderr: "ignore"
|
|
238
|
-
});
|
|
239
|
-
const raceResult = await Promise.race([
|
|
240
|
-
proc.exited.then((code) => ({ kind: "exited", code })),
|
|
241
|
-
new Promise((resolve) => setTimeout(() => resolve({ kind: "timeout" }), 500))
|
|
242
|
-
]);
|
|
243
|
-
if (raceResult.kind === "exited" && raceResult.code !== 0) {
|
|
244
|
-
console.warn(`Warning: could not open editor "${editor}" (exit code ${raceResult.code})`);
|
|
245
|
-
}
|
|
246
|
-
} catch {
|
|
247
|
-
console.warn(`Warning: could not open editor "${editor}"`);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
function getCommandSpawnArgs(cmd, platform = process.platform) {
|
|
251
|
-
return platform === "win32" ? ["cmd", "/d", "/s", "/c", cmd] : ["sh", "-c", cmd];
|
|
252
|
-
}
|
|
253
|
-
async function runCommand(cmd, cwd) {
|
|
254
|
-
const proc = Bun.spawn(getCommandSpawnArgs(cmd), {
|
|
255
|
-
cwd,
|
|
256
|
-
stdout: "inherit",
|
|
257
|
-
stderr: "inherit"
|
|
258
|
-
});
|
|
259
|
-
const exitCode = await proc.exited;
|
|
260
|
-
if (exitCode !== 0) {
|
|
261
|
-
throw new Error(`Command "${cmd}" exited with code ${exitCode}`);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
async function ensureGitIdentity(cwd) {
|
|
265
|
-
const hasName = Bun.spawnSync(["git", "config", "user.name"], { cwd }).exitCode === 0;
|
|
266
|
-
const hasEmail = Bun.spawnSync(["git", "config", "user.email"], { cwd }).exitCode === 0;
|
|
267
|
-
if (!hasName) {
|
|
268
|
-
await spawnChecked(["git", "config", "user.name", "Bunli"], cwd, "git config user.name");
|
|
269
|
-
}
|
|
270
|
-
if (!hasEmail) {
|
|
271
|
-
await spawnChecked(["git", "config", "user.email", "bunli@scaffolded.project"], cwd, "git config user.email");
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
async function spawnChecked(cmd, cwd, label) {
|
|
275
|
-
const proc = Bun.spawn(cmd, {
|
|
276
|
-
cwd,
|
|
277
|
-
stdout: "ignore",
|
|
278
|
-
stderr: "pipe"
|
|
279
|
-
});
|
|
280
|
-
const exitCode = await proc.exited;
|
|
281
|
-
if (exitCode !== 0) {
|
|
282
|
-
const stderr = await new Response(proc.stderr).text();
|
|
283
|
-
throw new Error(`"${label}" failed with exit code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
2
|
// ../../node_modules/.bun/better-result@2.7.0/node_modules/better-result/dist/index.mjs
|
|
288
3
|
function dual(arity, body) {
|
|
289
4
|
if (arity === 2)
|
|
@@ -454,258 +169,547 @@ var Ok = class Ok2 {
|
|
|
454
169
|
return this.value;
|
|
455
170
|
}
|
|
456
171
|
};
|
|
457
|
-
var Err = class Err2 {
|
|
458
|
-
status = "error";
|
|
459
|
-
constructor(error) {
|
|
460
|
-
this.error = error;
|
|
172
|
+
var Err = class Err2 {
|
|
173
|
+
status = "error";
|
|
174
|
+
constructor(error) {
|
|
175
|
+
this.error = error;
|
|
176
|
+
}
|
|
177
|
+
isOk() {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
isErr() {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
map(_fn) {
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
186
|
+
mapError(fn) {
|
|
187
|
+
return tryOrPanic(() => new Err2(fn(this.error)), "mapError callback threw");
|
|
188
|
+
}
|
|
189
|
+
andThen(_fn) {
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
andThenAsync(_fn) {
|
|
193
|
+
return Promise.resolve(this);
|
|
194
|
+
}
|
|
195
|
+
match(handlers) {
|
|
196
|
+
return tryOrPanic(() => handlers.err(this.error), "match err handler threw");
|
|
197
|
+
}
|
|
198
|
+
unwrap(message) {
|
|
199
|
+
return panic(message ?? `Unwrap called on Err: ${String(this.error)}`, this.error);
|
|
200
|
+
}
|
|
201
|
+
unwrapOr(fallback) {
|
|
202
|
+
return fallback;
|
|
203
|
+
}
|
|
204
|
+
tap(_fn) {
|
|
205
|
+
return this;
|
|
206
|
+
}
|
|
207
|
+
tapAsync(_fn) {
|
|
208
|
+
return Promise.resolve(this);
|
|
209
|
+
}
|
|
210
|
+
*[Symbol.iterator]() {
|
|
211
|
+
yield this;
|
|
212
|
+
return panic("Unreachable: Err yielded in Result.gen but generator continued", this.error);
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
function ok(value) {
|
|
216
|
+
return new Ok(value);
|
|
217
|
+
}
|
|
218
|
+
var isOk = (result) => {
|
|
219
|
+
return result.status === "ok";
|
|
220
|
+
};
|
|
221
|
+
var err = (error) => new Err(error);
|
|
222
|
+
var isError = (result) => {
|
|
223
|
+
return result.status === "error";
|
|
224
|
+
};
|
|
225
|
+
var tryFn = (options, config) => {
|
|
226
|
+
const execute = () => {
|
|
227
|
+
if (typeof options === "function")
|
|
228
|
+
try {
|
|
229
|
+
return ok(options());
|
|
230
|
+
} catch (cause) {
|
|
231
|
+
return err(new UnhandledException({ cause }));
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
return ok(options.try());
|
|
235
|
+
} catch (originalCause) {
|
|
236
|
+
try {
|
|
237
|
+
return err(options.catch(originalCause));
|
|
238
|
+
} catch (catchHandlerError) {
|
|
239
|
+
throw panic("Result.try catch handler threw", catchHandlerError);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
const times = config?.retry?.times ?? 0;
|
|
244
|
+
let result = execute();
|
|
245
|
+
for (let retry = 0;retry < times && result.status === "error"; retry++)
|
|
246
|
+
result = execute();
|
|
247
|
+
return result;
|
|
248
|
+
};
|
|
249
|
+
var tryPromise = async (options, config) => {
|
|
250
|
+
const execute = async () => {
|
|
251
|
+
if (typeof options === "function")
|
|
252
|
+
try {
|
|
253
|
+
return ok(await options());
|
|
254
|
+
} catch (cause) {
|
|
255
|
+
return err(new UnhandledException({ cause }));
|
|
256
|
+
}
|
|
257
|
+
try {
|
|
258
|
+
return ok(await options.try());
|
|
259
|
+
} catch (originalCause) {
|
|
260
|
+
try {
|
|
261
|
+
return err(await options.catch(originalCause));
|
|
262
|
+
} catch (catchHandlerError) {
|
|
263
|
+
throw panic("Result.tryPromise catch handler threw", catchHandlerError);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
const retry = config?.retry;
|
|
268
|
+
if (!retry)
|
|
269
|
+
return execute();
|
|
270
|
+
const getDelay = (retryAttempt) => {
|
|
271
|
+
switch (retry.backoff) {
|
|
272
|
+
case "constant":
|
|
273
|
+
return retry.delayMs;
|
|
274
|
+
case "linear":
|
|
275
|
+
return retry.delayMs * (retryAttempt + 1);
|
|
276
|
+
case "exponential":
|
|
277
|
+
return retry.delayMs * 2 ** retryAttempt;
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
281
|
+
let result = await execute();
|
|
282
|
+
const shouldRetryFn = retry.shouldRetry ?? (() => true);
|
|
283
|
+
for (let attempt = 0;attempt < retry.times; attempt++) {
|
|
284
|
+
if (result.status !== "error")
|
|
285
|
+
break;
|
|
286
|
+
const error = result.error;
|
|
287
|
+
if (!tryOrPanic(() => shouldRetryFn(error), "shouldRetry predicate threw"))
|
|
288
|
+
break;
|
|
289
|
+
await sleep(getDelay(attempt));
|
|
290
|
+
result = await execute();
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
293
|
+
};
|
|
294
|
+
var map = dual(2, (result, fn) => {
|
|
295
|
+
return result.map(fn);
|
|
296
|
+
});
|
|
297
|
+
var mapError = dual(2, (result, fn) => {
|
|
298
|
+
return result.mapError(fn);
|
|
299
|
+
});
|
|
300
|
+
var andThen = dual(2, (result, fn) => {
|
|
301
|
+
return result.andThen(fn);
|
|
302
|
+
});
|
|
303
|
+
var andThenAsync = dual(2, (result, fn) => {
|
|
304
|
+
return result.andThenAsync(fn);
|
|
305
|
+
});
|
|
306
|
+
var match = dual(2, (result, handlers) => {
|
|
307
|
+
return result.match(handlers);
|
|
308
|
+
});
|
|
309
|
+
var tap = dual(2, (result, fn) => {
|
|
310
|
+
return result.tap(fn);
|
|
311
|
+
});
|
|
312
|
+
var tapAsync = dual(2, (result, fn) => {
|
|
313
|
+
return result.tapAsync(fn);
|
|
314
|
+
});
|
|
315
|
+
var unwrap = (result, message) => {
|
|
316
|
+
return result.unwrap(message);
|
|
317
|
+
};
|
|
318
|
+
function assertIsResult(value) {
|
|
319
|
+
if (value !== null && typeof value === "object" && "status" in value && (value.status === "ok" || value.status === "error"))
|
|
320
|
+
return;
|
|
321
|
+
return panic("Result.gen body must return Result.ok() or Result.err(), got: " + (value === null ? "null" : typeof value === "object" ? JSON.stringify(value) : String(value)));
|
|
322
|
+
}
|
|
323
|
+
var unwrapOr = dual(2, (result, fallback) => {
|
|
324
|
+
return result.unwrapOr(fallback);
|
|
325
|
+
});
|
|
326
|
+
var gen = (body, thisArg) => {
|
|
327
|
+
const iterator = body.call(thisArg);
|
|
328
|
+
if (Symbol.asyncIterator in iterator)
|
|
329
|
+
return (async () => {
|
|
330
|
+
const asyncIter = iterator;
|
|
331
|
+
let state$1;
|
|
332
|
+
try {
|
|
333
|
+
state$1 = await asyncIter.next();
|
|
334
|
+
} catch (cause) {
|
|
335
|
+
throw panic("generator body threw", cause);
|
|
336
|
+
}
|
|
337
|
+
assertIsResult(state$1.value);
|
|
338
|
+
if (!state$1.done)
|
|
339
|
+
try {
|
|
340
|
+
await asyncIter.return?.(undefined);
|
|
341
|
+
} catch (cause) {
|
|
342
|
+
throw panic("generator cleanup threw", cause);
|
|
343
|
+
}
|
|
344
|
+
return state$1.value;
|
|
345
|
+
})();
|
|
346
|
+
const syncIter = iterator;
|
|
347
|
+
let state;
|
|
348
|
+
try {
|
|
349
|
+
state = syncIter.next();
|
|
350
|
+
} catch (cause) {
|
|
351
|
+
throw panic("generator body threw", cause);
|
|
352
|
+
}
|
|
353
|
+
assertIsResult(state.value);
|
|
354
|
+
if (!state.done)
|
|
355
|
+
try {
|
|
356
|
+
syncIter.return?.(undefined);
|
|
357
|
+
} catch (cause) {
|
|
358
|
+
throw panic("generator cleanup threw", cause);
|
|
359
|
+
}
|
|
360
|
+
return state.value;
|
|
361
|
+
};
|
|
362
|
+
async function* resultAwait(promise) {
|
|
363
|
+
return yield* await promise;
|
|
364
|
+
}
|
|
365
|
+
function isSerializedResult(obj) {
|
|
366
|
+
return obj !== null && typeof obj === "object" && "status" in obj && (obj.status === "ok" && ("value" in obj) || obj.status === "error" && ("error" in obj));
|
|
367
|
+
}
|
|
368
|
+
var serialize = (result) => {
|
|
369
|
+
return result.status === "ok" ? {
|
|
370
|
+
status: "ok",
|
|
371
|
+
value: result.value
|
|
372
|
+
} : {
|
|
373
|
+
status: "error",
|
|
374
|
+
error: result.error
|
|
375
|
+
};
|
|
376
|
+
};
|
|
377
|
+
var deserialize = (value) => {
|
|
378
|
+
if (isSerializedResult(value))
|
|
379
|
+
return value.status === "ok" ? new Ok(value.value) : new Err(value.error);
|
|
380
|
+
return err(new ResultDeserializationError({ value }));
|
|
381
|
+
};
|
|
382
|
+
var hydrate = (value) => {
|
|
383
|
+
return deserialize(value);
|
|
384
|
+
};
|
|
385
|
+
var partition = (results) => {
|
|
386
|
+
const oks = [];
|
|
387
|
+
const errs = [];
|
|
388
|
+
for (const r of results)
|
|
389
|
+
if (r.status === "ok")
|
|
390
|
+
oks.push(r.value);
|
|
391
|
+
else
|
|
392
|
+
errs.push(r.error);
|
|
393
|
+
return [oks, errs];
|
|
394
|
+
};
|
|
395
|
+
var flatten = (result) => {
|
|
396
|
+
if (result.status === "ok")
|
|
397
|
+
return result.value;
|
|
398
|
+
return result;
|
|
399
|
+
};
|
|
400
|
+
var Result = {
|
|
401
|
+
ok,
|
|
402
|
+
isOk,
|
|
403
|
+
err,
|
|
404
|
+
isError,
|
|
405
|
+
try: tryFn,
|
|
406
|
+
tryPromise,
|
|
407
|
+
map,
|
|
408
|
+
mapError,
|
|
409
|
+
andThen,
|
|
410
|
+
andThenAsync,
|
|
411
|
+
match,
|
|
412
|
+
tap,
|
|
413
|
+
tapAsync,
|
|
414
|
+
unwrap,
|
|
415
|
+
unwrapOr,
|
|
416
|
+
gen,
|
|
417
|
+
await: resultAwait,
|
|
418
|
+
serialize,
|
|
419
|
+
deserialize,
|
|
420
|
+
hydrate,
|
|
421
|
+
partition,
|
|
422
|
+
flatten
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
// src/steps.ts
|
|
426
|
+
import { existsSync } from "fs";
|
|
427
|
+
import { join } from "path";
|
|
428
|
+
var LOCKFILE_MAP = [
|
|
429
|
+
["bun.lock", "bun"],
|
|
430
|
+
["bun.lockb", "bun"],
|
|
431
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
432
|
+
["yarn.lock", "yarn"],
|
|
433
|
+
["package-lock.json", "npm"]
|
|
434
|
+
];
|
|
435
|
+
function detectPackageManager(cwd) {
|
|
436
|
+
const dir = cwd ?? process.cwd();
|
|
437
|
+
for (const [lockfile, manager] of LOCKFILE_MAP) {
|
|
438
|
+
if (existsSync(join(dir, lockfile))) {
|
|
439
|
+
return manager;
|
|
440
|
+
}
|
|
461
441
|
}
|
|
462
|
-
|
|
442
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
443
|
+
if (userAgent) {
|
|
444
|
+
if (userAgent.startsWith("bun"))
|
|
445
|
+
return "bun";
|
|
446
|
+
if (userAgent.startsWith("pnpm"))
|
|
447
|
+
return "pnpm";
|
|
448
|
+
if (userAgent.startsWith("yarn"))
|
|
449
|
+
return "yarn";
|
|
450
|
+
if (userAgent.startsWith("npm"))
|
|
451
|
+
return "npm";
|
|
452
|
+
}
|
|
453
|
+
return "bun";
|
|
454
|
+
}
|
|
455
|
+
function isInGitRepo(cwd) {
|
|
456
|
+
try {
|
|
457
|
+
const result = Bun.spawnSync(["git", "rev-parse", "--is-inside-work-tree"], {
|
|
458
|
+
cwd: cwd ?? process.cwd(),
|
|
459
|
+
stdout: "ignore",
|
|
460
|
+
stderr: "ignore"
|
|
461
|
+
});
|
|
462
|
+
return result.exitCode === 0;
|
|
463
|
+
} catch {
|
|
463
464
|
return false;
|
|
464
465
|
}
|
|
465
|
-
|
|
466
|
-
|
|
466
|
+
}
|
|
467
|
+
async function runSteps(dir, steps) {
|
|
468
|
+
for (const step of steps) {
|
|
469
|
+
switch (step.type) {
|
|
470
|
+
case "install":
|
|
471
|
+
await runInstall(dir);
|
|
472
|
+
break;
|
|
473
|
+
case "git-init":
|
|
474
|
+
await runGitInit(dir, step.commit);
|
|
475
|
+
break;
|
|
476
|
+
case "open-editor":
|
|
477
|
+
await runOpenEditor(dir);
|
|
478
|
+
break;
|
|
479
|
+
case "command":
|
|
480
|
+
await runCommand(step.cmd, step.cwd ?? dir);
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
467
483
|
}
|
|
468
|
-
|
|
469
|
-
|
|
484
|
+
}
|
|
485
|
+
async function runInstall(cwd) {
|
|
486
|
+
const pm = detectPackageManager(cwd);
|
|
487
|
+
const proc = Bun.spawn([pm, "install"], {
|
|
488
|
+
cwd,
|
|
489
|
+
stdout: "pipe",
|
|
490
|
+
stderr: "pipe"
|
|
491
|
+
});
|
|
492
|
+
const exitCode = await proc.exited;
|
|
493
|
+
if (exitCode !== 0) {
|
|
494
|
+
const stderr = await new Response(proc.stderr).text();
|
|
495
|
+
throw new Error(`"${pm} install" exited with code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
470
496
|
}
|
|
471
|
-
|
|
472
|
-
|
|
497
|
+
}
|
|
498
|
+
async function runGitInit(cwd, commit) {
|
|
499
|
+
await spawnChecked(["git", "init"], cwd, "git init");
|
|
500
|
+
if (commit) {
|
|
501
|
+
await ensureGitIdentity(cwd);
|
|
502
|
+
await spawnChecked(["git", "add", "."], cwd, "git add");
|
|
503
|
+
await spawnChecked(["git", "commit", "-m", commit], cwd, "git commit");
|
|
473
504
|
}
|
|
474
|
-
|
|
475
|
-
|
|
505
|
+
}
|
|
506
|
+
async function runOpenEditor(cwd) {
|
|
507
|
+
const editor = process.env.EDITOR || "code";
|
|
508
|
+
try {
|
|
509
|
+
const proc = Bun.spawn([editor, cwd], {
|
|
510
|
+
stdout: "ignore",
|
|
511
|
+
stderr: "ignore"
|
|
512
|
+
});
|
|
513
|
+
const raceResult = await Promise.race([
|
|
514
|
+
proc.exited.then((code) => ({ kind: "exited", code })),
|
|
515
|
+
new Promise((resolve) => setTimeout(() => resolve({ kind: "timeout" }), 500))
|
|
516
|
+
]);
|
|
517
|
+
if (raceResult.kind === "exited" && raceResult.code !== 0) {
|
|
518
|
+
console.warn(`Warning: could not open editor "${editor}" (exit code ${raceResult.code})`);
|
|
519
|
+
}
|
|
520
|
+
} catch {
|
|
521
|
+
console.warn(`Warning: could not open editor "${editor}"`);
|
|
476
522
|
}
|
|
477
|
-
|
|
478
|
-
|
|
523
|
+
}
|
|
524
|
+
function getCommandSpawnArgs(cmd, platform = process.platform) {
|
|
525
|
+
return platform === "win32" ? ["cmd", "/d", "/s", "/c", cmd] : ["sh", "-c", cmd];
|
|
526
|
+
}
|
|
527
|
+
async function runCommand(cmd, cwd) {
|
|
528
|
+
const proc = Bun.spawn(getCommandSpawnArgs(cmd), {
|
|
529
|
+
cwd,
|
|
530
|
+
stdout: "inherit",
|
|
531
|
+
stderr: "inherit"
|
|
532
|
+
});
|
|
533
|
+
const exitCode = await proc.exited;
|
|
534
|
+
if (exitCode !== 0) {
|
|
535
|
+
throw new Error(`Command "${cmd}" exited with code ${exitCode}`);
|
|
479
536
|
}
|
|
480
|
-
|
|
481
|
-
|
|
537
|
+
}
|
|
538
|
+
async function ensureGitIdentity(cwd) {
|
|
539
|
+
const hasName = Bun.spawnSync(["git", "config", "user.name"], { cwd }).exitCode === 0;
|
|
540
|
+
const hasEmail = Bun.spawnSync(["git", "config", "user.email"], { cwd }).exitCode === 0;
|
|
541
|
+
if (!hasName) {
|
|
542
|
+
await spawnChecked(["git", "config", "user.name", "Bunli"], cwd, "git config user.name");
|
|
482
543
|
}
|
|
483
|
-
|
|
484
|
-
|
|
544
|
+
if (!hasEmail) {
|
|
545
|
+
await spawnChecked(["git", "config", "user.email", "bunli@scaffolded.project"], cwd, "git config user.email");
|
|
485
546
|
}
|
|
486
|
-
|
|
487
|
-
|
|
547
|
+
}
|
|
548
|
+
async function spawnChecked(cmd, cwd, label) {
|
|
549
|
+
const proc = Bun.spawn(cmd, {
|
|
550
|
+
cwd,
|
|
551
|
+
stdout: "ignore",
|
|
552
|
+
stderr: "pipe"
|
|
553
|
+
});
|
|
554
|
+
const exitCode = await proc.exited;
|
|
555
|
+
if (exitCode !== 0) {
|
|
556
|
+
const stderr = await new Response(proc.stderr).text();
|
|
557
|
+
throw new Error(`"${label}" failed with exit code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
488
558
|
}
|
|
489
|
-
|
|
490
|
-
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// src/template-engine.ts
|
|
562
|
+
import { readdir } from "fs/promises";
|
|
563
|
+
import { join as join2 } from "path";
|
|
564
|
+
import { downloadTemplate } from "giget";
|
|
565
|
+
async function processTemplate(options) {
|
|
566
|
+
const { source, dir, offline, variables = {} } = options;
|
|
567
|
+
let templateDir;
|
|
568
|
+
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
|
|
569
|
+
const sourceDir = source.startsWith("/") ? source : join2(process.cwd(), source);
|
|
570
|
+
const copyExitCode = await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
|
|
571
|
+
stdout: "inherit",
|
|
572
|
+
stderr: "inherit"
|
|
573
|
+
}).exited;
|
|
574
|
+
if (copyExitCode !== 0) {
|
|
575
|
+
throw new Error(`Failed to copy local template from ${sourceDir}`);
|
|
576
|
+
}
|
|
577
|
+
templateDir = dir;
|
|
578
|
+
} else {
|
|
579
|
+
const result = await downloadTemplate(source, {
|
|
580
|
+
dir,
|
|
581
|
+
offline,
|
|
582
|
+
preferOffline: true,
|
|
583
|
+
force: true
|
|
584
|
+
});
|
|
585
|
+
templateDir = result.dir;
|
|
491
586
|
}
|
|
492
|
-
|
|
493
|
-
|
|
587
|
+
const manifest = await loadTemplateManifest(templateDir);
|
|
588
|
+
if (manifest?.files || Object.keys(variables).length > 0) {
|
|
589
|
+
await processTemplateFiles(templateDir, variables, manifest);
|
|
494
590
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
return panic("Unreachable: Err yielded in Result.gen but generator continued", this.error);
|
|
591
|
+
if (manifest?.hooks?.postInstall) {
|
|
592
|
+
await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
|
|
498
593
|
}
|
|
499
|
-
};
|
|
500
|
-
function ok(value) {
|
|
501
|
-
return new Ok(value);
|
|
594
|
+
return { dir: templateDir, manifest };
|
|
502
595
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
if (
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
throw panic("Result.try catch handler threw", catchHandlerError);
|
|
596
|
+
async function loadTemplateManifest(dir) {
|
|
597
|
+
const possiblePaths = [
|
|
598
|
+
join2(dir, "template.json"),
|
|
599
|
+
join2(dir, ".template.json"),
|
|
600
|
+
join2(dir, "template.yaml"),
|
|
601
|
+
join2(dir, ".template.yaml")
|
|
602
|
+
];
|
|
603
|
+
for (const path of possiblePaths) {
|
|
604
|
+
const file = Bun.file(path);
|
|
605
|
+
if (await file.exists()) {
|
|
606
|
+
const content = await file.text();
|
|
607
|
+
if (path.endsWith(".json")) {
|
|
608
|
+
const manifest = JSON.parse(content);
|
|
609
|
+
const removeExitCode = await Bun.spawn(["rm", "-f", path], {
|
|
610
|
+
stdout: "ignore",
|
|
611
|
+
stderr: "ignore"
|
|
612
|
+
}).exited;
|
|
613
|
+
if (removeExitCode !== 0) {
|
|
614
|
+
console.warn(`Warning: failed to remove template manifest ${path}`);
|
|
615
|
+
}
|
|
616
|
+
return manifest;
|
|
525
617
|
}
|
|
526
618
|
}
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
return ok(await options());
|
|
539
|
-
} catch (cause) {
|
|
540
|
-
return err(new UnhandledException({ cause }));
|
|
541
|
-
}
|
|
542
|
-
try {
|
|
543
|
-
return ok(await options.try());
|
|
544
|
-
} catch (originalCause) {
|
|
545
|
-
try {
|
|
546
|
-
return err(await options.catch(originalCause));
|
|
547
|
-
} catch (catchHandlerError) {
|
|
548
|
-
throw panic("Result.tryPromise catch handler threw", catchHandlerError);
|
|
549
|
-
}
|
|
619
|
+
}
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
async function processTemplateFiles(dir, variables, manifest) {
|
|
623
|
+
const files = await getFilesToProcess(dir, manifest);
|
|
624
|
+
for (const file of files) {
|
|
625
|
+
const filePath = join2(dir, file);
|
|
626
|
+
const content = await Bun.file(filePath).text();
|
|
627
|
+
let processedContent = content;
|
|
628
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
629
|
+
processedContent = processedContent.replaceAll(`{{${key}}}`, value).replaceAll(`<%= ${key} %>`, value).replaceAll(`$${key}`, value).replaceAll(`__${key}__`, value);
|
|
550
630
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
return retry.delayMs;
|
|
559
|
-
case "linear":
|
|
560
|
-
return retry.delayMs * (retryAttempt + 1);
|
|
561
|
-
case "exponential":
|
|
562
|
-
return retry.delayMs * 2 ** retryAttempt;
|
|
631
|
+
let newFilePath = filePath;
|
|
632
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
633
|
+
newFilePath = newFilePath.replaceAll(`__${key}__`, value);
|
|
634
|
+
}
|
|
635
|
+
await Bun.write(newFilePath, processedContent);
|
|
636
|
+
if (newFilePath !== filePath) {
|
|
637
|
+
await Bun.spawn(["rm", filePath]).exited;
|
|
563
638
|
}
|
|
564
|
-
};
|
|
565
|
-
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
566
|
-
let result = await execute();
|
|
567
|
-
const shouldRetryFn = retry.shouldRetry ?? (() => true);
|
|
568
|
-
for (let attempt = 0;attempt < retry.times; attempt++) {
|
|
569
|
-
if (result.status !== "error")
|
|
570
|
-
break;
|
|
571
|
-
const error = result.error;
|
|
572
|
-
if (!tryOrPanic(() => shouldRetryFn(error), "shouldRetry predicate threw"))
|
|
573
|
-
break;
|
|
574
|
-
await sleep(getDelay(attempt));
|
|
575
|
-
result = await execute();
|
|
576
639
|
}
|
|
577
|
-
return result;
|
|
578
|
-
};
|
|
579
|
-
var map = dual(2, (result, fn) => {
|
|
580
|
-
return result.map(fn);
|
|
581
|
-
});
|
|
582
|
-
var mapError = dual(2, (result, fn) => {
|
|
583
|
-
return result.mapError(fn);
|
|
584
|
-
});
|
|
585
|
-
var andThen = dual(2, (result, fn) => {
|
|
586
|
-
return result.andThen(fn);
|
|
587
|
-
});
|
|
588
|
-
var andThenAsync = dual(2, (result, fn) => {
|
|
589
|
-
return result.andThenAsync(fn);
|
|
590
|
-
});
|
|
591
|
-
var match = dual(2, (result, handlers) => {
|
|
592
|
-
return result.match(handlers);
|
|
593
|
-
});
|
|
594
|
-
var tap = dual(2, (result, fn) => {
|
|
595
|
-
return result.tap(fn);
|
|
596
|
-
});
|
|
597
|
-
var tapAsync = dual(2, (result, fn) => {
|
|
598
|
-
return result.tapAsync(fn);
|
|
599
|
-
});
|
|
600
|
-
var unwrap = (result, message) => {
|
|
601
|
-
return result.unwrap(message);
|
|
602
|
-
};
|
|
603
|
-
function assertIsResult(value) {
|
|
604
|
-
if (value !== null && typeof value === "object" && "status" in value && (value.status === "ok" || value.status === "error"))
|
|
605
|
-
return;
|
|
606
|
-
return panic("Result.gen body must return Result.ok() or Result.err(), got: " + (value === null ? "null" : typeof value === "object" ? JSON.stringify(value) : String(value)));
|
|
607
640
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
var gen = (body, thisArg) => {
|
|
612
|
-
const iterator = body.call(thisArg);
|
|
613
|
-
if (Symbol.asyncIterator in iterator)
|
|
614
|
-
return (async () => {
|
|
615
|
-
const asyncIter = iterator;
|
|
616
|
-
let state$1;
|
|
617
|
-
try {
|
|
618
|
-
state$1 = await asyncIter.next();
|
|
619
|
-
} catch (cause) {
|
|
620
|
-
throw panic("generator body threw", cause);
|
|
621
|
-
}
|
|
622
|
-
assertIsResult(state$1.value);
|
|
623
|
-
if (!state$1.done)
|
|
624
|
-
try {
|
|
625
|
-
await asyncIter.return?.(undefined);
|
|
626
|
-
} catch (cause) {
|
|
627
|
-
throw panic("generator cleanup threw", cause);
|
|
628
|
-
}
|
|
629
|
-
return state$1.value;
|
|
630
|
-
})();
|
|
631
|
-
const syncIter = iterator;
|
|
632
|
-
let state;
|
|
633
|
-
try {
|
|
634
|
-
state = syncIter.next();
|
|
635
|
-
} catch (cause) {
|
|
636
|
-
throw panic("generator body threw", cause);
|
|
641
|
+
async function getFilesToProcess(dir, manifest) {
|
|
642
|
+
if (manifest?.files?.include) {
|
|
643
|
+
return manifest.files.include;
|
|
637
644
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
645
|
+
const files = [];
|
|
646
|
+
async function walk(currentDir, prefix = "") {
|
|
647
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
648
|
+
for (const entry of entries) {
|
|
649
|
+
const path = join2(prefix, entry.name);
|
|
650
|
+
if (entry.isDirectory()) {
|
|
651
|
+
if (!["node_modules", ".git", ".next", "dist", "build"].includes(entry.name)) {
|
|
652
|
+
await walk(join2(currentDir, entry.name), path);
|
|
653
|
+
}
|
|
654
|
+
} else {
|
|
655
|
+
if (!path.match(/^(template\.json|\.template\.json|\.DS_Store|Thumbs\.db)$/) || path === ".gitignore") {
|
|
656
|
+
files.push(path);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
644
659
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
660
|
+
}
|
|
661
|
+
await walk(dir);
|
|
662
|
+
if (manifest?.files?.exclude) {
|
|
663
|
+
return files.filter((file) => {
|
|
664
|
+
return !manifest.files.exclude.some((pattern) => {
|
|
665
|
+
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
666
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
667
|
+
return regex.test(file);
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
return files;
|
|
649
672
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
673
|
+
async function runPostInstallHooks(dir, hooks) {
|
|
674
|
+
for (const hook of hooks) {
|
|
675
|
+
const proc = Bun.spawn(hook.split(" "), {
|
|
676
|
+
cwd: dir,
|
|
677
|
+
stdout: "inherit",
|
|
678
|
+
stderr: "inherit"
|
|
679
|
+
});
|
|
680
|
+
const exitCode = await proc.exited;
|
|
681
|
+
if (exitCode !== 0) {
|
|
682
|
+
throw new Error(`Post-install hook failed: ${hook}`);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
652
685
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
status: "error",
|
|
659
|
-
error: result.error
|
|
686
|
+
function resolveTemplateSource(template) {
|
|
687
|
+
const specialTemplates = {
|
|
688
|
+
basic: "github:bunli/templates/basic",
|
|
689
|
+
advanced: "github:bunli/templates/advanced",
|
|
690
|
+
monorepo: "github:bunli/templates/monorepo"
|
|
660
691
|
};
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
}
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
return result.value;
|
|
683
|
-
return result;
|
|
684
|
-
};
|
|
685
|
-
var Result = {
|
|
686
|
-
ok,
|
|
687
|
-
isOk,
|
|
688
|
-
err,
|
|
689
|
-
isError,
|
|
690
|
-
try: tryFn,
|
|
691
|
-
tryPromise,
|
|
692
|
-
map,
|
|
693
|
-
mapError,
|
|
694
|
-
andThen,
|
|
695
|
-
andThenAsync,
|
|
696
|
-
match,
|
|
697
|
-
tap,
|
|
698
|
-
tapAsync,
|
|
699
|
-
unwrap,
|
|
700
|
-
unwrapOr,
|
|
701
|
-
gen,
|
|
702
|
-
await: resultAwait,
|
|
703
|
-
serialize,
|
|
704
|
-
deserialize,
|
|
705
|
-
hydrate,
|
|
706
|
-
partition,
|
|
707
|
-
flatten
|
|
708
|
-
};
|
|
692
|
+
if (specialTemplates[template]) {
|
|
693
|
+
return specialTemplates[template];
|
|
694
|
+
}
|
|
695
|
+
if (template.startsWith("npm:")) {
|
|
696
|
+
return template.replace("npm:", "npm:/");
|
|
697
|
+
}
|
|
698
|
+
if (template.includes("/") && !template.includes(":")) {
|
|
699
|
+
return `github:${template}`;
|
|
700
|
+
}
|
|
701
|
+
return template;
|
|
702
|
+
}
|
|
703
|
+
function getBundledTemplatePath(name) {
|
|
704
|
+
return join2(import.meta.dir, "..", "templates", name);
|
|
705
|
+
}
|
|
706
|
+
async function isLocalTemplate(template) {
|
|
707
|
+
if (template.startsWith("file:") || template.startsWith("./") || template.startsWith("../")) {
|
|
708
|
+
return true;
|
|
709
|
+
}
|
|
710
|
+
const bundledPath = getBundledTemplatePath(template);
|
|
711
|
+
return await Bun.file(join2(bundledPath, "package.json")).exists();
|
|
712
|
+
}
|
|
709
713
|
|
|
710
714
|
// src/create-project.ts
|
|
711
715
|
var toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
@@ -764,7 +768,9 @@ async function createProject(options) {
|
|
|
764
768
|
const { name, dir, template, git, install, prompt, spinner, colors, shell, offline } = options;
|
|
765
769
|
const directoryCheck = await shell`test -d ${dir}`.nothrow();
|
|
766
770
|
if (directoryCheck.exitCode === 0) {
|
|
767
|
-
const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, {
|
|
771
|
+
const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, {
|
|
772
|
+
default: false
|
|
773
|
+
});
|
|
768
774
|
if (!overwrite) {
|
|
769
775
|
return Result.err(new UserCancelledError("Cancelled"));
|
|
770
776
|
}
|