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/cli.js
CHANGED
|
@@ -3,162 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
5
|
import { createCLI, defineCommand, option } from "@bunli/core";
|
|
6
|
-
import { z } from "zod";
|
|
7
|
-
|
|
8
|
-
// src/template-engine.ts
|
|
9
|
-
import { downloadTemplate } from "giget";
|
|
10
|
-
import { readdir } from "fs/promises";
|
|
11
|
-
import { join } from "path";
|
|
12
|
-
async function processTemplate(options) {
|
|
13
|
-
const { source, dir, offline, variables = {} } = options;
|
|
14
|
-
let templateDir;
|
|
15
|
-
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
|
|
16
|
-
const sourceDir = source.startsWith("/") ? source : join(process.cwd(), source);
|
|
17
|
-
const copyExitCode = await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
|
|
18
|
-
stdout: "inherit",
|
|
19
|
-
stderr: "inherit"
|
|
20
|
-
}).exited;
|
|
21
|
-
if (copyExitCode !== 0) {
|
|
22
|
-
throw new Error(`Failed to copy local template from ${sourceDir}`);
|
|
23
|
-
}
|
|
24
|
-
templateDir = dir;
|
|
25
|
-
} else {
|
|
26
|
-
const result = await downloadTemplate(source, {
|
|
27
|
-
dir,
|
|
28
|
-
offline,
|
|
29
|
-
preferOffline: true,
|
|
30
|
-
force: true
|
|
31
|
-
});
|
|
32
|
-
templateDir = result.dir;
|
|
33
|
-
}
|
|
34
|
-
const manifest = await loadTemplateManifest(templateDir);
|
|
35
|
-
if (manifest?.files || Object.keys(variables).length > 0) {
|
|
36
|
-
await processTemplateFiles(templateDir, variables, manifest);
|
|
37
|
-
}
|
|
38
|
-
if (manifest?.hooks?.postInstall) {
|
|
39
|
-
await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
|
|
40
|
-
}
|
|
41
|
-
return { dir: templateDir, manifest };
|
|
42
|
-
}
|
|
43
|
-
async function loadTemplateManifest(dir) {
|
|
44
|
-
const possiblePaths = [
|
|
45
|
-
join(dir, "template.json"),
|
|
46
|
-
join(dir, ".template.json"),
|
|
47
|
-
join(dir, "template.yaml"),
|
|
48
|
-
join(dir, ".template.yaml")
|
|
49
|
-
];
|
|
50
|
-
for (const path of possiblePaths) {
|
|
51
|
-
const file = Bun.file(path);
|
|
52
|
-
if (await file.exists()) {
|
|
53
|
-
const content = await file.text();
|
|
54
|
-
if (path.endsWith(".json")) {
|
|
55
|
-
const manifest = JSON.parse(content);
|
|
56
|
-
const removeExitCode = await Bun.spawn(["rm", "-f", path], {
|
|
57
|
-
stdout: "ignore",
|
|
58
|
-
stderr: "ignore"
|
|
59
|
-
}).exited;
|
|
60
|
-
if (removeExitCode !== 0) {
|
|
61
|
-
console.warn(`Warning: failed to remove template manifest ${path}`);
|
|
62
|
-
}
|
|
63
|
-
return manifest;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
async function processTemplateFiles(dir, variables, manifest) {
|
|
70
|
-
const files = await getFilesToProcess(dir, manifest);
|
|
71
|
-
for (const file of files) {
|
|
72
|
-
const filePath = join(dir, file);
|
|
73
|
-
const content = await Bun.file(filePath).text();
|
|
74
|
-
let processedContent = content;
|
|
75
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
76
|
-
processedContent = processedContent.replaceAll(`{{${key}}}`, value).replaceAll(`<%= ${key} %>`, value).replaceAll(`$${key}`, value).replaceAll(`__${key}__`, value);
|
|
77
|
-
}
|
|
78
|
-
let newFilePath = filePath;
|
|
79
|
-
for (const [key, value] of Object.entries(variables)) {
|
|
80
|
-
newFilePath = newFilePath.replaceAll(`__${key}__`, value);
|
|
81
|
-
}
|
|
82
|
-
await Bun.write(newFilePath, processedContent);
|
|
83
|
-
if (newFilePath !== filePath) {
|
|
84
|
-
await Bun.spawn(["rm", filePath]).exited;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
async function getFilesToProcess(dir, manifest) {
|
|
89
|
-
if (manifest?.files?.include) {
|
|
90
|
-
return manifest.files.include;
|
|
91
|
-
}
|
|
92
|
-
const files = [];
|
|
93
|
-
async function walk(currentDir, prefix = "") {
|
|
94
|
-
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
95
|
-
for (const entry of entries) {
|
|
96
|
-
const path = join(prefix, entry.name);
|
|
97
|
-
if (entry.isDirectory()) {
|
|
98
|
-
if (!["node_modules", ".git", ".next", "dist", "build"].includes(entry.name)) {
|
|
99
|
-
await walk(join(currentDir, entry.name), path);
|
|
100
|
-
}
|
|
101
|
-
} else {
|
|
102
|
-
if (!path.match(/^(template\.json|\.template\.json|\.DS_Store|Thumbs\.db)$/) || path === ".gitignore") {
|
|
103
|
-
files.push(path);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
await walk(dir);
|
|
109
|
-
if (manifest?.files?.exclude) {
|
|
110
|
-
return files.filter((file) => {
|
|
111
|
-
return !manifest.files.exclude.some((pattern) => {
|
|
112
|
-
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
113
|
-
const regex = new RegExp(`^${regexPattern}$`);
|
|
114
|
-
return regex.test(file);
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return files;
|
|
119
|
-
}
|
|
120
|
-
async function runPostInstallHooks(dir, hooks) {
|
|
121
|
-
for (const hook of hooks) {
|
|
122
|
-
const proc = Bun.spawn(hook.split(" "), {
|
|
123
|
-
cwd: dir,
|
|
124
|
-
stdout: "inherit",
|
|
125
|
-
stderr: "inherit"
|
|
126
|
-
});
|
|
127
|
-
const exitCode = await proc.exited;
|
|
128
|
-
if (exitCode !== 0) {
|
|
129
|
-
throw new Error(`Post-install hook failed: ${hook}`);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
function resolveTemplateSource(template) {
|
|
134
|
-
const specialTemplates = {
|
|
135
|
-
basic: "github:bunli/templates/basic",
|
|
136
|
-
advanced: "github:bunli/templates/advanced",
|
|
137
|
-
monorepo: "github:bunli/templates/monorepo"
|
|
138
|
-
};
|
|
139
|
-
if (specialTemplates[template]) {
|
|
140
|
-
return specialTemplates[template];
|
|
141
|
-
}
|
|
142
|
-
if (template.startsWith("npm:")) {
|
|
143
|
-
return template.replace("npm:", "npm:/");
|
|
144
|
-
}
|
|
145
|
-
if (template.includes("/") && !template.includes(":")) {
|
|
146
|
-
return `github:${template}`;
|
|
147
|
-
}
|
|
148
|
-
return template;
|
|
149
|
-
}
|
|
150
|
-
function getBundledTemplatePath(name) {
|
|
151
|
-
return join(import.meta.dir, "..", "templates", name);
|
|
152
|
-
}
|
|
153
|
-
async function isLocalTemplate(template) {
|
|
154
|
-
if (template.startsWith("file:") || template.startsWith("./") || template.startsWith("../")) {
|
|
155
|
-
return true;
|
|
156
|
-
}
|
|
157
|
-
const bundledPath = getBundledTemplatePath(template);
|
|
158
|
-
return await Bun.file(join(bundledPath, "package.json")).exists();
|
|
159
|
-
}
|
|
160
6
|
|
|
161
|
-
// ../../node_modules/better-result/dist/index.mjs
|
|
7
|
+
// ../../node_modules/.bun/better-result@2.7.0/node_modules/better-result/dist/index.mjs
|
|
162
8
|
function dual(arity, body) {
|
|
163
9
|
if (arity === 2)
|
|
164
10
|
return (...args) => {
|
|
@@ -581,8 +427,331 @@ var Result = {
|
|
|
581
427
|
flatten
|
|
582
428
|
};
|
|
583
429
|
|
|
430
|
+
// src/cli.ts
|
|
431
|
+
import { z } from "zod";
|
|
432
|
+
|
|
433
|
+
// src/create.ts
|
|
434
|
+
import path from "path";
|
|
435
|
+
|
|
436
|
+
// src/steps.ts
|
|
437
|
+
import { existsSync } from "fs";
|
|
438
|
+
import { join } from "path";
|
|
439
|
+
var LOCKFILE_MAP = [
|
|
440
|
+
["bun.lock", "bun"],
|
|
441
|
+
["bun.lockb", "bun"],
|
|
442
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
443
|
+
["yarn.lock", "yarn"],
|
|
444
|
+
["package-lock.json", "npm"]
|
|
445
|
+
];
|
|
446
|
+
function detectPackageManager(cwd) {
|
|
447
|
+
const dir = cwd ?? process.cwd();
|
|
448
|
+
for (const [lockfile, manager] of LOCKFILE_MAP) {
|
|
449
|
+
if (existsSync(join(dir, lockfile))) {
|
|
450
|
+
return manager;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const userAgent = process.env.npm_config_user_agent;
|
|
454
|
+
if (userAgent) {
|
|
455
|
+
if (userAgent.startsWith("bun"))
|
|
456
|
+
return "bun";
|
|
457
|
+
if (userAgent.startsWith("pnpm"))
|
|
458
|
+
return "pnpm";
|
|
459
|
+
if (userAgent.startsWith("yarn"))
|
|
460
|
+
return "yarn";
|
|
461
|
+
if (userAgent.startsWith("npm"))
|
|
462
|
+
return "npm";
|
|
463
|
+
}
|
|
464
|
+
return "bun";
|
|
465
|
+
}
|
|
466
|
+
function isInGitRepo(cwd) {
|
|
467
|
+
try {
|
|
468
|
+
const result = Bun.spawnSync(["git", "rev-parse", "--is-inside-work-tree"], {
|
|
469
|
+
cwd: cwd ?? process.cwd(),
|
|
470
|
+
stdout: "ignore",
|
|
471
|
+
stderr: "ignore"
|
|
472
|
+
});
|
|
473
|
+
return result.exitCode === 0;
|
|
474
|
+
} catch {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
async function runSteps(dir, steps) {
|
|
479
|
+
for (const step of steps) {
|
|
480
|
+
switch (step.type) {
|
|
481
|
+
case "install":
|
|
482
|
+
await runInstall(dir);
|
|
483
|
+
break;
|
|
484
|
+
case "git-init":
|
|
485
|
+
await runGitInit(dir, step.commit);
|
|
486
|
+
break;
|
|
487
|
+
case "open-editor":
|
|
488
|
+
await runOpenEditor(dir);
|
|
489
|
+
break;
|
|
490
|
+
case "command":
|
|
491
|
+
await runCommand(step.cmd, step.cwd ?? dir);
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
async function runInstall(cwd) {
|
|
497
|
+
const pm = detectPackageManager(cwd);
|
|
498
|
+
const proc = Bun.spawn([pm, "install"], {
|
|
499
|
+
cwd,
|
|
500
|
+
stdout: "pipe",
|
|
501
|
+
stderr: "pipe"
|
|
502
|
+
});
|
|
503
|
+
const exitCode = await proc.exited;
|
|
504
|
+
if (exitCode !== 0) {
|
|
505
|
+
const stderr = await new Response(proc.stderr).text();
|
|
506
|
+
throw new Error(`"${pm} install" exited with code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
async function runGitInit(cwd, commit) {
|
|
510
|
+
await spawnChecked(["git", "init"], cwd, "git init");
|
|
511
|
+
if (commit) {
|
|
512
|
+
await ensureGitIdentity(cwd);
|
|
513
|
+
await spawnChecked(["git", "add", "."], cwd, "git add");
|
|
514
|
+
await spawnChecked(["git", "commit", "-m", commit], cwd, "git commit");
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
async function runOpenEditor(cwd) {
|
|
518
|
+
const editor = process.env.EDITOR || "code";
|
|
519
|
+
try {
|
|
520
|
+
const proc = Bun.spawn([editor, cwd], {
|
|
521
|
+
stdout: "ignore",
|
|
522
|
+
stderr: "ignore"
|
|
523
|
+
});
|
|
524
|
+
const raceResult = await Promise.race([
|
|
525
|
+
proc.exited.then((code) => ({ kind: "exited", code })),
|
|
526
|
+
new Promise((resolve) => setTimeout(() => resolve({ kind: "timeout" }), 500))
|
|
527
|
+
]);
|
|
528
|
+
if (raceResult.kind === "exited" && raceResult.code !== 0) {
|
|
529
|
+
console.warn(`Warning: could not open editor "${editor}" (exit code ${raceResult.code})`);
|
|
530
|
+
}
|
|
531
|
+
} catch {
|
|
532
|
+
console.warn(`Warning: could not open editor "${editor}"`);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
function getCommandSpawnArgs(cmd, platform = process.platform) {
|
|
536
|
+
return platform === "win32" ? ["cmd", "/d", "/s", "/c", cmd] : ["sh", "-c", cmd];
|
|
537
|
+
}
|
|
538
|
+
async function runCommand(cmd, cwd) {
|
|
539
|
+
const proc = Bun.spawn(getCommandSpawnArgs(cmd), {
|
|
540
|
+
cwd,
|
|
541
|
+
stdout: "inherit",
|
|
542
|
+
stderr: "inherit"
|
|
543
|
+
});
|
|
544
|
+
const exitCode = await proc.exited;
|
|
545
|
+
if (exitCode !== 0) {
|
|
546
|
+
throw new Error(`Command "${cmd}" exited with code ${exitCode}`);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
async function ensureGitIdentity(cwd) {
|
|
550
|
+
const hasName = Bun.spawnSync(["git", "config", "user.name"], { cwd }).exitCode === 0;
|
|
551
|
+
const hasEmail = Bun.spawnSync(["git", "config", "user.email"], { cwd }).exitCode === 0;
|
|
552
|
+
if (!hasName) {
|
|
553
|
+
await spawnChecked(["git", "config", "user.name", "Bunli"], cwd, "git config user.name");
|
|
554
|
+
}
|
|
555
|
+
if (!hasEmail) {
|
|
556
|
+
await spawnChecked(["git", "config", "user.email", "bunli@scaffolded.project"], cwd, "git config user.email");
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
async function spawnChecked(cmd, cwd, label) {
|
|
560
|
+
const proc = Bun.spawn(cmd, {
|
|
561
|
+
cwd,
|
|
562
|
+
stdout: "ignore",
|
|
563
|
+
stderr: "pipe"
|
|
564
|
+
});
|
|
565
|
+
const exitCode = await proc.exited;
|
|
566
|
+
if (exitCode !== 0) {
|
|
567
|
+
const stderr = await new Response(proc.stderr).text();
|
|
568
|
+
throw new Error(`"${label}" failed with exit code ${exitCode}${stderr ? `: ${stderr.trim()}` : ""}`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/template-engine.ts
|
|
573
|
+
import { readdir } from "fs/promises";
|
|
574
|
+
import { join as join2 } from "path";
|
|
575
|
+
import { downloadTemplate } from "giget";
|
|
576
|
+
async function processTemplate(options) {
|
|
577
|
+
const { source, dir, offline, variables = {} } = options;
|
|
578
|
+
let templateDir;
|
|
579
|
+
if (source.startsWith("/") || source.startsWith("./") || source.startsWith("../")) {
|
|
580
|
+
const sourceDir = source.startsWith("/") ? source : join2(process.cwd(), source);
|
|
581
|
+
const copyExitCode = await Bun.spawn(["cp", "-r", sourceDir + "/.", dir], {
|
|
582
|
+
stdout: "inherit",
|
|
583
|
+
stderr: "inherit"
|
|
584
|
+
}).exited;
|
|
585
|
+
if (copyExitCode !== 0) {
|
|
586
|
+
throw new Error(`Failed to copy local template from ${sourceDir}`);
|
|
587
|
+
}
|
|
588
|
+
templateDir = dir;
|
|
589
|
+
} else {
|
|
590
|
+
const result = await downloadTemplate(source, {
|
|
591
|
+
dir,
|
|
592
|
+
offline,
|
|
593
|
+
preferOffline: true,
|
|
594
|
+
force: true
|
|
595
|
+
});
|
|
596
|
+
templateDir = result.dir;
|
|
597
|
+
}
|
|
598
|
+
const manifest = await loadTemplateManifest(templateDir);
|
|
599
|
+
if (manifest?.files || Object.keys(variables).length > 0) {
|
|
600
|
+
await processTemplateFiles(templateDir, variables, manifest);
|
|
601
|
+
}
|
|
602
|
+
if (manifest?.hooks?.postInstall) {
|
|
603
|
+
await runPostInstallHooks(templateDir, manifest.hooks.postInstall);
|
|
604
|
+
}
|
|
605
|
+
return { dir: templateDir, manifest };
|
|
606
|
+
}
|
|
607
|
+
async function loadTemplateManifest(dir) {
|
|
608
|
+
const possiblePaths = [
|
|
609
|
+
join2(dir, "template.json"),
|
|
610
|
+
join2(dir, ".template.json"),
|
|
611
|
+
join2(dir, "template.yaml"),
|
|
612
|
+
join2(dir, ".template.yaml")
|
|
613
|
+
];
|
|
614
|
+
for (const path of possiblePaths) {
|
|
615
|
+
const file = Bun.file(path);
|
|
616
|
+
if (await file.exists()) {
|
|
617
|
+
const content = await file.text();
|
|
618
|
+
if (path.endsWith(".json")) {
|
|
619
|
+
const manifest = JSON.parse(content);
|
|
620
|
+
const removeExitCode = await Bun.spawn(["rm", "-f", path], {
|
|
621
|
+
stdout: "ignore",
|
|
622
|
+
stderr: "ignore"
|
|
623
|
+
}).exited;
|
|
624
|
+
if (removeExitCode !== 0) {
|
|
625
|
+
console.warn(`Warning: failed to remove template manifest ${path}`);
|
|
626
|
+
}
|
|
627
|
+
return manifest;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
async function processTemplateFiles(dir, variables, manifest) {
|
|
634
|
+
const files = await getFilesToProcess(dir, manifest);
|
|
635
|
+
for (const file of files) {
|
|
636
|
+
const filePath = join2(dir, file);
|
|
637
|
+
const content = await Bun.file(filePath).text();
|
|
638
|
+
let processedContent = content;
|
|
639
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
640
|
+
processedContent = processedContent.replaceAll(`{{${key}}}`, value).replaceAll(`<%= ${key} %>`, value).replaceAll(`$${key}`, value).replaceAll(`__${key}__`, value);
|
|
641
|
+
}
|
|
642
|
+
let newFilePath = filePath;
|
|
643
|
+
for (const [key, value] of Object.entries(variables)) {
|
|
644
|
+
newFilePath = newFilePath.replaceAll(`__${key}__`, value);
|
|
645
|
+
}
|
|
646
|
+
await Bun.write(newFilePath, processedContent);
|
|
647
|
+
if (newFilePath !== filePath) {
|
|
648
|
+
await Bun.spawn(["rm", filePath]).exited;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
async function getFilesToProcess(dir, manifest) {
|
|
653
|
+
if (manifest?.files?.include) {
|
|
654
|
+
return manifest.files.include;
|
|
655
|
+
}
|
|
656
|
+
const files = [];
|
|
657
|
+
async function walk(currentDir, prefix = "") {
|
|
658
|
+
const entries = await readdir(currentDir, { withFileTypes: true });
|
|
659
|
+
for (const entry of entries) {
|
|
660
|
+
const path = join2(prefix, entry.name);
|
|
661
|
+
if (entry.isDirectory()) {
|
|
662
|
+
if (!["node_modules", ".git", ".next", "dist", "build"].includes(entry.name)) {
|
|
663
|
+
await walk(join2(currentDir, entry.name), path);
|
|
664
|
+
}
|
|
665
|
+
} else {
|
|
666
|
+
if (!path.match(/^(template\.json|\.template\.json|\.DS_Store|Thumbs\.db)$/) || path === ".gitignore") {
|
|
667
|
+
files.push(path);
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
await walk(dir);
|
|
673
|
+
if (manifest?.files?.exclude) {
|
|
674
|
+
return files.filter((file) => {
|
|
675
|
+
return !manifest.files.exclude.some((pattern) => {
|
|
676
|
+
const regexPattern = pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*").replace(/\?/g, "[^/]");
|
|
677
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
678
|
+
return regex.test(file);
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
return files;
|
|
683
|
+
}
|
|
684
|
+
async function runPostInstallHooks(dir, hooks) {
|
|
685
|
+
for (const hook of hooks) {
|
|
686
|
+
const proc = Bun.spawn(hook.split(" "), {
|
|
687
|
+
cwd: dir,
|
|
688
|
+
stdout: "inherit",
|
|
689
|
+
stderr: "inherit"
|
|
690
|
+
});
|
|
691
|
+
const exitCode = await proc.exited;
|
|
692
|
+
if (exitCode !== 0) {
|
|
693
|
+
throw new Error(`Post-install hook failed: ${hook}`);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function resolveTemplateSource(template) {
|
|
698
|
+
const specialTemplates = {
|
|
699
|
+
basic: "github:bunli/templates/basic",
|
|
700
|
+
advanced: "github:bunli/templates/advanced",
|
|
701
|
+
monorepo: "github:bunli/templates/monorepo"
|
|
702
|
+
};
|
|
703
|
+
if (specialTemplates[template]) {
|
|
704
|
+
return specialTemplates[template];
|
|
705
|
+
}
|
|
706
|
+
if (template.startsWith("npm:")) {
|
|
707
|
+
return template.replace("npm:", "npm:/");
|
|
708
|
+
}
|
|
709
|
+
if (template.includes("/") && !template.includes(":")) {
|
|
710
|
+
return `github:${template}`;
|
|
711
|
+
}
|
|
712
|
+
return template;
|
|
713
|
+
}
|
|
714
|
+
function getBundledTemplatePath(name) {
|
|
715
|
+
return join2(import.meta.dir, "..", "templates", name);
|
|
716
|
+
}
|
|
717
|
+
async function isLocalTemplate(template) {
|
|
718
|
+
if (template.startsWith("file:") || template.startsWith("./") || template.startsWith("../")) {
|
|
719
|
+
return true;
|
|
720
|
+
}
|
|
721
|
+
const bundledPath = getBundledTemplatePath(template);
|
|
722
|
+
return await Bun.file(join2(bundledPath, "package.json")).exists();
|
|
723
|
+
}
|
|
724
|
+
|
|
584
725
|
// src/create-project.ts
|
|
585
726
|
var toErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
727
|
+
function stepLabel(step) {
|
|
728
|
+
switch (step.type) {
|
|
729
|
+
case "install":
|
|
730
|
+
return {
|
|
731
|
+
running: "Installing dependencies...",
|
|
732
|
+
done: "Dependencies installed",
|
|
733
|
+
failed: "Failed to install dependencies"
|
|
734
|
+
};
|
|
735
|
+
case "git-init":
|
|
736
|
+
return {
|
|
737
|
+
running: "Initializing git repository...",
|
|
738
|
+
done: "Git repository initialized",
|
|
739
|
+
failed: "Failed to initialize git repository"
|
|
740
|
+
};
|
|
741
|
+
case "open-editor":
|
|
742
|
+
return {
|
|
743
|
+
running: "Opening editor...",
|
|
744
|
+
done: "Editor opened",
|
|
745
|
+
failed: "Failed to open editor"
|
|
746
|
+
};
|
|
747
|
+
case "command":
|
|
748
|
+
return {
|
|
749
|
+
running: `Running ${step.cmd}...`,
|
|
750
|
+
done: `Completed ${step.cmd}`,
|
|
751
|
+
failed: `Failed to run ${step.cmd}`
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
}
|
|
586
755
|
var tryAsync = (fn, mapError2) => Result.tryPromise({ try: fn, catch: mapError2 });
|
|
587
756
|
|
|
588
757
|
class UserCancelledError extends TaggedError("UserCancelledError")() {
|
|
@@ -610,7 +779,9 @@ async function createProject(options) {
|
|
|
610
779
|
const { name, dir, template, git, install, prompt, spinner, colors, shell, offline } = options;
|
|
611
780
|
const directoryCheck = await shell`test -d ${dir}`.nothrow();
|
|
612
781
|
if (directoryCheck.exitCode === 0) {
|
|
613
|
-
const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, {
|
|
782
|
+
const overwrite = await prompt.confirm(`Directory ${dir} already exists. Overwrite?`, {
|
|
783
|
+
default: false
|
|
784
|
+
});
|
|
614
785
|
if (!overwrite) {
|
|
615
786
|
return Result.err(new UserCancelledError("Cancelled"));
|
|
616
787
|
}
|
|
@@ -655,40 +826,28 @@ async function createProject(options) {
|
|
|
655
826
|
return templateResult;
|
|
656
827
|
}
|
|
657
828
|
spin.succeed("Project structure created");
|
|
829
|
+
const postSteps = [];
|
|
830
|
+
if (install) {
|
|
831
|
+
postSteps.push({ type: "install" });
|
|
832
|
+
}
|
|
658
833
|
if (git) {
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
const gitCommit = await shell`cd ${dir} && git commit -m "feat: initialize ${name} CLI project with Bunli
|
|
664
|
-
|
|
665
|
-
- Generated using create-bunli template
|
|
666
|
-
- Includes basic CLI structure with commands directory
|
|
667
|
-
- Configured with Bunli build system and TypeScript
|
|
668
|
-
- Ready for development with bun run dev"`.nothrow();
|
|
669
|
-
if (gitInit.exitCode === 0 && gitAdd.exitCode === 0 && gitCommit.exitCode === 0) {
|
|
670
|
-
gitSpin.succeed("Git repository initialized");
|
|
671
|
-
} else {
|
|
672
|
-
gitSpin.fail("Failed to initialize git repository");
|
|
673
|
-
const output = [gitInit.stderr, gitAdd.stderr, gitCommit.stderr].map((value) => value.toString().trim()).filter(Boolean).join(`
|
|
674
|
-
`);
|
|
675
|
-
if (output) {
|
|
676
|
-
console.error(colors.dim(` ${output}`));
|
|
677
|
-
}
|
|
678
|
-
}
|
|
834
|
+
postSteps.push({
|
|
835
|
+
type: "git-init",
|
|
836
|
+
commit: `feat: initialize ${name} CLI project with Bunli`
|
|
837
|
+
});
|
|
679
838
|
}
|
|
680
|
-
|
|
681
|
-
const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
const
|
|
690
|
-
if (
|
|
691
|
-
console.error(colors.dim(` ${
|
|
839
|
+
for (const step of postSteps) {
|
|
840
|
+
const label = stepLabel(step);
|
|
841
|
+
const stepSpin = spinner(label.running);
|
|
842
|
+
stepSpin.start();
|
|
843
|
+
try {
|
|
844
|
+
await runSteps(dir, [step]);
|
|
845
|
+
stepSpin.succeed(label.done);
|
|
846
|
+
} catch (error) {
|
|
847
|
+
stepSpin.fail(label.failed);
|
|
848
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
849
|
+
if (message) {
|
|
850
|
+
console.error(colors.dim(` ${message}`));
|
|
692
851
|
}
|
|
693
852
|
}
|
|
694
853
|
}
|
|
@@ -696,10 +855,11 @@ async function createProject(options) {
|
|
|
696
855
|
}
|
|
697
856
|
|
|
698
857
|
// src/create.ts
|
|
699
|
-
import path from "path";
|
|
700
858
|
class InvalidProjectNameError extends TaggedError("InvalidProjectNameError")() {
|
|
701
859
|
constructor(name) {
|
|
702
|
-
super({
|
|
860
|
+
super({
|
|
861
|
+
message: `Project name "${name}" must only contain lowercase letters, numbers, and hyphens`
|
|
862
|
+
});
|
|
703
863
|
}
|
|
704
864
|
}
|
|
705
865
|
|
|
@@ -793,11 +953,28 @@ async function run() {
|
|
|
793
953
|
description: "Create a new Bunli CLI project",
|
|
794
954
|
options: {
|
|
795
955
|
name: option(z.string().optional(), { description: "Project name" }),
|
|
796
|
-
template: option(z.string().default("basic"), {
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
956
|
+
template: option(z.string().default("basic"), {
|
|
957
|
+
short: "t",
|
|
958
|
+
description: "Project template (basic, advanced, monorepo, or github:user/repo)"
|
|
959
|
+
}),
|
|
960
|
+
dir: option(z.string().optional(), {
|
|
961
|
+
short: "d",
|
|
962
|
+
description: "Directory to create project in"
|
|
963
|
+
}),
|
|
964
|
+
git: option(z.boolean().default(true), {
|
|
965
|
+
short: "g",
|
|
966
|
+
description: "Initialize git repository",
|
|
967
|
+
argumentKind: "flag"
|
|
968
|
+
}),
|
|
969
|
+
install: option(z.boolean().default(true), {
|
|
970
|
+
short: "i",
|
|
971
|
+
description: "Install dependencies",
|
|
972
|
+
argumentKind: "flag"
|
|
973
|
+
}),
|
|
974
|
+
offline: option(z.boolean().default(false), {
|
|
975
|
+
description: "Use cached templates when available",
|
|
976
|
+
argumentKind: "flag"
|
|
977
|
+
})
|
|
801
978
|
},
|
|
802
979
|
handler: async (context) => {
|
|
803
980
|
const result = await create(context);
|
package/dist/create-project.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import type { PromptApi, PromptSpinnerFactory } from
|
|
2
|
-
import type { Colors } from
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
1
|
+
import type { PromptApi, PromptSpinnerFactory } from "@bunli/core";
|
|
2
|
+
import type { Colors } from "@bunli/utils";
|
|
3
|
+
import { Result } from "better-result";
|
|
4
|
+
import type { CreateOptions } from "./types.js";
|
|
5
5
|
interface CreateProjectOptions extends CreateOptions {
|
|
6
6
|
name: string;
|
|
7
7
|
dir: string;
|
|
@@ -35,3 +35,4 @@ declare class TemplateProcessingError extends TemplateProcessingError_base {
|
|
|
35
35
|
export type CreateProjectError = UserCancelledError | ShellCommandError | TemplateProcessingError;
|
|
36
36
|
export declare function createProject(options: CreateProjectOptions): Promise<Result<void, CreateProjectError>>;
|
|
37
37
|
export {};
|
|
38
|
+
//# sourceMappingURL=create-project.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-project.d.ts","sourceRoot":"","sources":["../src/create-project.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAUpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAuChD,UAAU,oBAAqB,SAAQ,aAAa;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,OAAO,GAAG,CAAC,CAAC,CAAC;CACrB;;aAGU,MAAM;;AADjB,qBAAa,kBAAmB,SAAQ,uBAEpC;IACF,YAAY,OAAO,EAAE,MAAM,EAE1B;CACF;;aAGU,MAAM;aACN,MAAM;YACP,MAAM;;AAHhB,cAAM,iBAAkB,SAAQ,sBAI5B;IACF,YAAY,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAM1C;CACF;;aAGU,MAAM;WACR,OAAO;;AAFhB,cAAM,uBAAwB,SAAQ,4BAGlC;IACF,YAAY,KAAK,EAAE,OAAO,EAEzB;CACF;AAED,MAAM,MAAM,kBAAkB,GAAG,kBAAkB,GAAG,iBAAiB,GAAG,uBAAuB,CAAC;AAElG,wBAAsB,aAAa,CACjC,OAAO,EAAE,oBAAoB,GAC5B,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAuG3C"}
|
package/dist/create.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { HandlerArgs } from
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import type { HandlerArgs } from "@bunli/core";
|
|
2
|
+
import { Result } from "better-result";
|
|
3
|
+
import { type CreateProjectError } from "./create-project.js";
|
|
4
4
|
interface CreateOptions {
|
|
5
5
|
name?: string;
|
|
6
6
|
template: string;
|
|
@@ -24,3 +24,4 @@ export declare class UserCancelledError extends UserCancelledError_base {
|
|
|
24
24
|
export type CreateCommandError = InvalidProjectNameError | UserCancelledError | CreateProjectError;
|
|
25
25
|
export declare function create(context: HandlerArgs<CreateOptions>): Promise<Result<void, CreateCommandError>>;
|
|
26
26
|
export {};
|
|
27
|
+
//# sourceMappingURL=create.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create.d.ts","sourceRoot":"","sources":["../src/create.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAe,MAAM,eAAe,CAAC;AAEpD,OAAO,EAEL,KAAK,kBAAkB,EAExB,MAAM,qBAAqB,CAAC;AAE7B,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;;aAGU,MAAM;;AADjB,cAAM,uBAAwB,SAAQ,4BAElC;IACF,YAAY,IAAI,EAAE,MAAM,EAIvB;CACF;;aAGU,MAAM;;AADjB,qBAAa,kBAAmB,SAAQ,uBAEpC;IACF,YAAY,OAAO,EAAE,MAAM,EAE1B;CACF;AAED,MAAM,MAAM,kBAAkB,GAAG,uBAAuB,GAAG,kBAAkB,GAAG,kBAAkB,CAAC;AAEnG,wBAAsB,MAAM,CAC1B,OAAO,EAAE,WAAW,CAAC,aAAa,CAAC,GAClC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAC,CAgF3C"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
export { createProject } from
|
|
2
|
-
export { processTemplate, resolveTemplateSource, isLocalTemplate } from
|
|
3
|
-
export
|
|
1
|
+
export { createProject } from "./create-project.js";
|
|
2
|
+
export { processTemplate, resolveTemplateSource, isLocalTemplate } from "./template-engine.js";
|
|
3
|
+
export { runSteps, detectPackageManager, isInGitRepo } from "./steps.js";
|
|
4
|
+
export type { Step, PackageManager } from "./steps.js";
|
|
5
|
+
export type { CreateOptions, ProjectConfig, TemplateManifest, TemplateVariable } from "./types.js";
|
|
4
6
|
export declare const version = "0.1.0";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC/F,OAAO,EAAE,QAAQ,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzE,YAAY,EAAE,IAAI,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACvD,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGnG,eAAO,MAAM,OAAO,UAAU,CAAC"}
|