create-bunli 0.7.0 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -6
- package/dist/cli.d.ts +1 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +372 -195
- package/dist/create-project.d.ts +5 -4
- package/dist/create-project.d.ts.map +1 -0
- package/dist/create.d.ts +4 -3
- package/dist/create.d.ts.map +1 -0
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +344 -187
- package/dist/steps.d.ts +36 -0
- package/dist/steps.d.ts.map +1 -0
- package/dist/template-engine.d.ts +3 -2
- package/dist/template-engine.d.ts.map +1 -0
- package/dist/templates/advanced/README.md +8 -4
- package/dist/templates/advanced/bunli.config.ts +19 -19
- package/dist/templates/advanced/package.json +9 -4
- package/dist/templates/advanced/src/commands/config.ts +129 -118
- package/dist/templates/advanced/src/commands/init.ts +53 -59
- package/dist/templates/advanced/src/commands/serve.ts +77 -82
- package/dist/templates/advanced/src/commands/validate.ts +58 -64
- 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 +4 -3
- package/dist/templates/basic/src/commands/hello.ts +20 -25
- 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 -2
- package/dist/templates/monorepo/packages/cli/package.json +10 -5
- package/dist/templates/monorepo/packages/cli/src/index.ts +13 -13
- package/dist/templates/monorepo/packages/cli/tsconfig.json +3 -6
- package/dist/templates/monorepo/packages/core/package.json +6 -5
- package/dist/templates/monorepo/packages/core/scripts/build.ts +10 -10
- package/dist/templates/monorepo/packages/core/src/commands/analyze.ts +58 -56
- package/dist/templates/monorepo/packages/core/src/commands/process.ts +39 -46
- 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 +3 -5
- package/dist/templates/monorepo/packages/utils/package.json +6 -5
- 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 +2 -2
- 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 +2 -1
- package/dist/types.d.ts.map +1 -0
- package/package.json +35 -34
- package/templates/advanced/README.md +8 -4
- package/templates/advanced/bunli.config.ts +19 -19
- package/templates/advanced/package.json +9 -4
- package/templates/advanced/src/commands/config.ts +129 -118
- package/templates/advanced/src/commands/init.ts +53 -59
- package/templates/advanced/src/commands/serve.ts +77 -82
- package/templates/advanced/src/commands/validate.ts +58 -64
- 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 +4 -3
- package/templates/basic/src/commands/hello.ts +20 -25
- 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 -2
- package/templates/monorepo/packages/cli/package.json +10 -5
- package/templates/monorepo/packages/cli/src/index.ts +13 -13
- package/templates/monorepo/packages/cli/tsconfig.json +3 -6
- package/templates/monorepo/packages/core/package.json +6 -5
- package/templates/monorepo/packages/core/scripts/build.ts +10 -10
- package/templates/monorepo/packages/core/src/commands/analyze.ts +58 -56
- package/templates/monorepo/packages/core/src/commands/process.ts +39 -46
- 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 +3 -5
- package/templates/monorepo/packages/utils/package.json +6 -5
- 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 +2 -2
- 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,158 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
//
|
|
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
|
-
// ../../node_modules/better-result/dist/index.mjs
|
|
2
|
+
// ../../node_modules/.bun/better-result@2.7.0/node_modules/better-result/dist/index.mjs
|
|
156
3
|
function dual(arity, body) {
|
|
157
4
|
if (arity === 2)
|
|
158
5
|
return (...args) => {
|
|
@@ -575,8 +422,325 @@ var Result = {
|
|
|
575
422
|
flatten
|
|
576
423
|
};
|
|
577
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
|
+
}
|
|
441
|
+
}
|
|
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 {
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
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
|
+
}
|
|
483
|
+
}
|
|
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()}` : ""}`);
|
|
496
|
+
}
|
|
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");
|
|
504
|
+
}
|
|
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}"`);
|
|
522
|
+
}
|
|
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}`);
|
|
536
|
+
}
|
|
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");
|
|
543
|
+
}
|
|
544
|
+
if (!hasEmail) {
|
|
545
|
+
await spawnChecked(["git", "config", "user.email", "bunli@scaffolded.project"], cwd, "git config user.email");
|
|
546
|
+
}
|
|
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()}` : ""}`);
|
|
558
|
+
}
|
|
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;
|
|
586
|
+
}
|
|
587
|
+
const manifest = await loadTemplateManifest(templateDir);
|
|
588
|
+
if (manifest?.files || Object.keys(variables).length > 0) {
|
|
589
|
+
await processTemplateFiles(templateDir, variables, manifest);
|
|
590
|
+
}
|
|
591
|
+
if (manifest?.hooks?.postInstall) {
|
|
592
|
+
await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
|
|
593
|
+
}
|
|
594
|
+
return { dir: templateDir, manifest };
|
|
595
|
+
}
|
|
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;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
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);
|
|
630
|
+
}
|
|
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;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async function getFilesToProcess(dir, manifest) {
|
|
642
|
+
if (manifest?.files?.include) {
|
|
643
|
+
return manifest.files.include;
|
|
644
|
+
}
|
|
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
|
+
}
|
|
659
|
+
}
|
|
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;
|
|
672
|
+
}
|
|
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
|
+
}
|
|
685
|
+
}
|
|
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"
|
|
691
|
+
};
|
|
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
|
+
}
|
|
713
|
+
|
|
578
714
|
// src/create-project.ts
|
|
579
715
|
var toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
716
|
+
function stepLabel(step) {
|
|
717
|
+
switch (step.type) {
|
|
718
|
+
case "install":
|
|
719
|
+
return {
|
|
720
|
+
running: "Installing dependencies...",
|
|
721
|
+
done: "Dependencies installed",
|
|
722
|
+
failed: "Failed to install dependencies"
|
|
723
|
+
};
|
|
724
|
+
case "git-init":
|
|
725
|
+
return {
|
|
726
|
+
running: "Initializing git repository...",
|
|
727
|
+
done: "Git repository initialized",
|
|
728
|
+
failed: "Failed to initialize git repository"
|
|
729
|
+
};
|
|
730
|
+
case "open-editor":
|
|
731
|
+
return {
|
|
732
|
+
running: "Opening editor...",
|
|
733
|
+
done: "Editor opened",
|
|
734
|
+
failed: "Failed to open editor"
|
|
735
|
+
};
|
|
736
|
+
case "command":
|
|
737
|
+
return {
|
|
738
|
+
running: `Running ${step.cmd}...`,
|
|
739
|
+
done: `Completed ${step.cmd}`,
|
|
740
|
+
failed: `Failed to run ${step.cmd}`
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
}
|
|
580
744
|
var tryAsync = (fn, mapError2) => Result.tryPromise({ try: fn, catch: mapError2 });
|
|
581
745
|
|
|
582
746
|
class UserCancelledError extends TaggedError("UserCancelledError")() {
|
|
@@ -604,7 +768,9 @@ async function createProject(options) {
|
|
|
604
768
|
const { name, dir, template, git, install, prompt, spinner, colors, shell, offline } = options;
|
|
605
769
|
const directoryCheck = await shell`test -d ${dir}`.nothrow();
|
|
606
770
|
if (directoryCheck.exitCode === 0) {
|
|
607
|
-
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
|
+
});
|
|
608
774
|
if (!overwrite) {
|
|
609
775
|
return Result.err(new UserCancelledError("Cancelled"));
|
|
610
776
|
}
|
|
@@ -649,40 +815,28 @@ async function createProject(options) {
|
|
|
649
815
|
return templateResult;
|
|
650
816
|
}
|
|
651
817
|
spin.succeed("Project structure created");
|
|
818
|
+
const postSteps = [];
|
|
819
|
+
if (install) {
|
|
820
|
+
postSteps.push({ type: "install" });
|
|
821
|
+
}
|
|
652
822
|
if (git) {
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
const gitCommit = await shell`cd ${dir} && git commit -m "feat: initialize ${name} CLI project with Bunli
|
|
658
|
-
|
|
659
|
-
- Generated using create-bunli template
|
|
660
|
-
- Includes basic CLI structure with commands directory
|
|
661
|
-
- Configured with Bunli build system and TypeScript
|
|
662
|
-
- Ready for development with bun run dev"`.nothrow();
|
|
663
|
-
if (gitInit.exitCode === 0 && gitAdd.exitCode === 0 && gitCommit.exitCode === 0) {
|
|
664
|
-
gitSpin.succeed("Git repository initialized");
|
|
665
|
-
} else {
|
|
666
|
-
gitSpin.fail("Failed to initialize git repository");
|
|
667
|
-
const output = [gitInit.stderr, gitAdd.stderr, gitCommit.stderr].map((value) => value.toString().trim()).filter(Boolean).join(`
|
|
668
|
-
`);
|
|
669
|
-
if (output) {
|
|
670
|
-
console.error(colors.dim(` ${output}`));
|
|
671
|
-
}
|
|
672
|
-
}
|
|
823
|
+
postSteps.push({
|
|
824
|
+
type: "git-init",
|
|
825
|
+
commit: `feat: initialize ${name} CLI project with Bunli`
|
|
826
|
+
});
|
|
673
827
|
}
|
|
674
|
-
|
|
675
|
-
const
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const
|
|
684
|
-
if (
|
|
685
|
-
console.error(colors.dim(` ${
|
|
828
|
+
for (const step of postSteps) {
|
|
829
|
+
const label = stepLabel(step);
|
|
830
|
+
const stepSpin = spinner(label.running);
|
|
831
|
+
stepSpin.start();
|
|
832
|
+
try {
|
|
833
|
+
await runSteps(dir, [step]);
|
|
834
|
+
stepSpin.succeed(label.done);
|
|
835
|
+
} catch (error) {
|
|
836
|
+
stepSpin.fail(label.failed);
|
|
837
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
838
|
+
if (message) {
|
|
839
|
+
console.error(colors.dim(` ${message}`));
|
|
686
840
|
}
|
|
687
841
|
}
|
|
688
842
|
}
|
|
@@ -693,8 +847,11 @@ async function createProject(options) {
|
|
|
693
847
|
var version = "0.1.0";
|
|
694
848
|
export {
|
|
695
849
|
version,
|
|
850
|
+
runSteps,
|
|
696
851
|
resolveTemplateSource,
|
|
697
852
|
processTemplate,
|
|
698
853
|
isLocalTemplate,
|
|
854
|
+
isInGitRepo,
|
|
855
|
+
detectPackageManager,
|
|
699
856
|
createProject
|
|
700
857
|
};
|
package/dist/steps.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A declarative step to run after scaffolding completes.
|
|
3
|
+
* Steps are executed sequentially in array order by {@link runSteps}.
|
|
4
|
+
*/
|
|
5
|
+
export type Step = {
|
|
6
|
+
readonly type: "install";
|
|
7
|
+
} | {
|
|
8
|
+
readonly type: "git-init";
|
|
9
|
+
readonly commit?: string;
|
|
10
|
+
} | {
|
|
11
|
+
readonly type: "open-editor";
|
|
12
|
+
} | {
|
|
13
|
+
readonly type: "command";
|
|
14
|
+
readonly cmd: string;
|
|
15
|
+
readonly cwd?: string;
|
|
16
|
+
};
|
|
17
|
+
export type PackageManager = "bun" | "npm" | "pnpm" | "yarn";
|
|
18
|
+
/**
|
|
19
|
+
* Detect the package manager for a project directory.
|
|
20
|
+
*
|
|
21
|
+
* 1. Lockfile probe (bun -> pnpm -> yarn -> npm)
|
|
22
|
+
* 2. npm_config_user_agent environment variable
|
|
23
|
+
* 3. Default to "bun" (this is a Bun-first framework)
|
|
24
|
+
*/
|
|
25
|
+
export declare function detectPackageManager(cwd?: string): PackageManager;
|
|
26
|
+
/**
|
|
27
|
+
* Check whether a directory is inside an existing git repository.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isInGitRepo(cwd?: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Execute an array of post-scaffold steps sequentially.
|
|
32
|
+
* If any step fails, the error propagates immediately (remaining steps are skipped).
|
|
33
|
+
*/
|
|
34
|
+
export declare function runSteps(dir: string, steps: Step[]): Promise<void>;
|
|
35
|
+
export declare function getCommandSpawnArgs(cmd: string, platform?: NodeJS.Platform): string[];
|
|
36
|
+
//# sourceMappingURL=steps.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"steps.d.ts","sourceRoot":"","sources":["../src/steps.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,MAAM,IAAI,GACZ;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE,GAChC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9E,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC;AAU7D;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,cAAc,CAkBjE;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAWjD;AAED;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiBxE;AAmDD,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,QAA2B,GAC3C,MAAM,EAAE,CAEV"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { TemplateManifest } from
|
|
1
|
+
import type { TemplateManifest } from "./types.js";
|
|
2
2
|
export interface TemplateOptions {
|
|
3
3
|
source: string;
|
|
4
|
-
type?:
|
|
4
|
+
type?: "github" | "npm" | "local" | "bundled";
|
|
5
5
|
dir: string;
|
|
6
6
|
offline?: boolean;
|
|
7
7
|
variables?: Record<string, string>;
|
|
@@ -25,3 +25,4 @@ export declare function getBundledTemplatePath(name: string): string;
|
|
|
25
25
|
* Check if template exists locally (for development)
|
|
26
26
|
*/
|
|
27
27
|
export declare function isLocalTemplate(template: string): Promise<boolean>;
|
|
28
|
+
//# sourceMappingURL=template-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-engine.d.ts","sourceRoot":"","sources":["../src/template-engine.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,QAAQ,GAAG,KAAK,GAAG,OAAO,GAAG,SAAS,CAAC;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,OAAO,EAAE,eAAe;;;GA6C7D;AA2JD;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAwB9D;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQxE"}
|
|
@@ -21,6 +21,7 @@ bunx {{name}} [command]
|
|
|
21
21
|
### Commands
|
|
22
22
|
|
|
23
23
|
#### `init`
|
|
24
|
+
|
|
24
25
|
Initialize a new configuration file in the current directory.
|
|
25
26
|
|
|
26
27
|
```bash
|
|
@@ -32,6 +33,7 @@ Options:
|
|
|
32
33
|
```
|
|
33
34
|
|
|
34
35
|
#### `validate`
|
|
36
|
+
|
|
35
37
|
Validate files against defined rules.
|
|
36
38
|
|
|
37
39
|
```bash
|
|
@@ -44,6 +46,7 @@ Options:
|
|
|
44
46
|
```
|
|
45
47
|
|
|
46
48
|
#### `serve`
|
|
49
|
+
|
|
47
50
|
Start a development server.
|
|
48
51
|
|
|
49
52
|
```bash
|
|
@@ -56,6 +59,7 @@ Options:
|
|
|
56
59
|
```
|
|
57
60
|
|
|
58
61
|
#### `config`
|
|
62
|
+
|
|
59
63
|
Manage configuration settings.
|
|
60
64
|
|
|
61
65
|
```bash
|
|
@@ -88,9 +92,9 @@ export default {
|
|
|
88
92
|
},
|
|
89
93
|
server: {
|
|
90
94
|
port: 3000,
|
|
91
|
-
host:
|
|
92
|
-
}
|
|
93
|
-
}
|
|
95
|
+
host: "localhost",
|
|
96
|
+
},
|
|
97
|
+
};
|
|
94
98
|
```
|
|
95
99
|
|
|
96
100
|
## Development
|
|
@@ -111,4 +115,4 @@ bun run build
|
|
|
111
115
|
|
|
112
116
|
## License
|
|
113
117
|
|
|
114
|
-
{{license}}
|
|
118
|
+
{{license}}
|