create-shibumi 0.2.8 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -4
- package/package.json +2 -2
- package/scripts/ship.lock.json +2 -2
- package/src/adopt.ts +250 -0
- package/src/args.ts +32 -6
- package/src/cli.ts +230 -41
- package/src/templates/blog/agents.md +4 -1
- package/src/templates/blog/bun.lock +1 -0
- package/src/templates/blog/gitignore +2 -0
- package/src/templates/blog/package.json +4 -2
- package/src/templates/blog/public/favicon.svg +1 -0
- package/src/templates/blog/public/style.css +77 -4
- package/src/templates/blog/src/components/BaseHead.astro +4 -0
- package/src/templates/blog/src/content/blog/one-vps-is-plenty.md +7 -5
- package/src/templates/blog/src/content/blog/own-your-source.md +9 -5
- package/src/templates/blog/src/content/blog/writing-for-agents.md +7 -5
- package/src/templates/blog/src/content.config.ts +3 -0
- package/src/templates/blog/src/layouts/Base.astro +12 -1
- package/src/templates/blog/src/pages/index.astro +5 -2
- package/src/templates/blog/src/pages/llms.txt.ts +1 -1
- package/src/templates/blog/src/pages/posts/[id].astro +2 -1
- package/src/templates/blog/src/pages/posts/[id].md.ts +1 -1
- package/src/templates/blog/src/pages/rss.xml.ts +1 -1
- package/src/templates/full-stack/agents.md +1 -0
- package/src/templates/full-stack/package.json +1 -0
- package/src/templates/full-stack/public/favicon.svg +1 -0
- package/src/templates/full-stack/public/style.css +5 -6
- package/src/templates/full-stack/src/app.ts +5 -3
- package/src/templates/ship.ts +551 -153
- package/src/templates/static/agents.md +1 -0
- package/src/templates/{web → static}/bun.lock +1 -21
- package/src/templates/static/gitignore +2 -0
- package/src/templates/static/package.json +6 -2
- package/src/templates/static/public/favicon.svg +1 -0
- package/src/templates/static/public/index.html +11 -3
- package/src/templates/static/public/style.css +66 -20
- package/src/templates/web/.dockerignore +0 -12
- package/src/templates/web/Dockerfile +0 -24
- package/src/templates/web/README.md +0 -10
- package/src/templates/web/agents.md +0 -54
- package/src/templates/web/compose.yaml +0 -17
- package/src/templates/web/gitignore +0 -5
- package/src/templates/web/package.json +0 -34
- package/src/templates/web/public/app.js +0 -11
- package/src/templates/web/public/style.css +0 -150
- package/src/templates/web/public/vendor/alpine-csp-3.16.2.min.js +0 -23
- package/src/templates/web/public/vendor/shibumi.css +0 -422
- package/src/templates/web/src/app.ts +0 -106
- package/src/templates/web/src/env.ts +0 -21
- package/src/templates/web/src/server.ts +0 -23
- package/src/templates/web/test/app.test.ts +0 -145
- package/src/templates/web/tsconfig.json +0 -13
package/README.md
CHANGED
|
@@ -8,27 +8,40 @@ cd my-app
|
|
|
8
8
|
bun dev
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
Already have a project? Run it inside that project instead, and it gets deploy tooling rather than a scaffold:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
bun create shibumi@latest .
|
|
15
|
+
```
|
|
16
|
+
|
|
11
17
|
## Three starting points
|
|
12
18
|
|
|
13
|
-
1. **
|
|
14
|
-
2. **
|
|
15
|
-
3. **
|
|
19
|
+
1. **Bun full-stack app**: Hono, HTML, CSS, Alpine, Zod, tests, a health endpoint, and SQLite through Drizzle with migrations, persistent data, backup, and restore.
|
|
20
|
+
2. **Blog**: Astro with posts, RSS, sitemap, SEO meta, and llms.txt.
|
|
21
|
+
3. **Static site**: publish a verified build directory such as `./dist`, `public`, `build`, or `out` from any framework.
|
|
16
22
|
|
|
17
23
|
All three deploy to a Linux VPS or homelab through [shibumi-server](https://server.shibumistack.dev). Other providers can wait until their generated projects pass the same artifact and deployment tests.
|
|
18
24
|
|
|
19
25
|
## Flags
|
|
20
26
|
|
|
21
27
|
```text
|
|
22
|
-
--template <id>
|
|
28
|
+
--template <id> full-stack, blog, or static
|
|
23
29
|
--yes, -y non-interactive; requires name and --template
|
|
24
30
|
--no-git skip git init
|
|
25
31
|
--no-install skip dependency install
|
|
32
|
+
--spa adopting only: unknown paths serve index.html
|
|
26
33
|
--help, -h show help
|
|
27
34
|
--version show version
|
|
28
35
|
```
|
|
29
36
|
|
|
30
37
|
Creation is atomic: the project is built in a temporary sibling directory and renamed into place. Failure or cancellation leaves nothing behind, and an existing destination is never touched. Git init stages and commits nothing; the first commit belongs to you.
|
|
31
38
|
|
|
39
|
+
## Adopting an existing project
|
|
40
|
+
|
|
41
|
+
`bun create shibumi .` detects where your build lands (Astro and Vite write `dist/`, Eleventy `_site/`, an exported Next.js `out/`, plain files `public/`), vendors `scripts/ship.ts`, adds the `ship` scripts, installs the one dependency that client needs, and generates a `Dockerfile`, `compose.yaml`, and `.dockerignore` for the static image. Git is never touched, and `--no-install` skips the install.
|
|
42
|
+
|
|
43
|
+
Two cases stop the run instead of guessing. Deployment files that already exist are never reinterpreted: a `compose.yaml` carrying Shibumi's static labels beside somebody else's `Dockerfile` would deploy the wrong artifact. A `package.json` with a `start` script belongs to `bun ship:setup`, which generates server deployment files. Finish with `bun ship:setup`.
|
|
44
|
+
|
|
32
45
|
## Guidance for coding agents
|
|
33
46
|
|
|
34
47
|
Generated projects include a root `agents.md`. It records route locations, data rules, available checks, and files that need extra care.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-shibumi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Scaffold a Shibumi Stack project: Bun, Hono, Zod, Drizzle, SQLite, Alpine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"src/cli.ts",
|
|
11
11
|
"src/args.ts",
|
|
12
|
+
"src/adopt.ts",
|
|
12
13
|
"src/create.ts",
|
|
13
14
|
"src/templates/ship.ts",
|
|
14
15
|
"src/templates/shibumi.ts",
|
|
@@ -17,7 +18,6 @@
|
|
|
17
18
|
"src/templates/static/",
|
|
18
19
|
"src/templates/blog/",
|
|
19
20
|
"src/templates/full-stack/",
|
|
20
|
-
"src/templates/web/",
|
|
21
21
|
"README.md",
|
|
22
22
|
"LICENSE"
|
|
23
23
|
],
|
package/scripts/ship.lock.json
CHANGED
package/src/adopt.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_TEMPLATES_DIR = join(import.meta.dir, "templates");
|
|
5
|
+
export const CLACK_VERSION = (
|
|
6
|
+
JSON.parse(readFileSync(join(import.meta.dir, "..", "package.json"), "utf8")) as {
|
|
7
|
+
dependencies: Record<string, string>;
|
|
8
|
+
}
|
|
9
|
+
).dependencies["@clack/prompts"]!;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How to install the client's one dependency here. A project with an npm or
|
|
13
|
+
* pnpm lockfile must not get a bun.lock dropped beside it, so those get the
|
|
14
|
+
* matching command to run instead of a silent second lockfile.
|
|
15
|
+
*/
|
|
16
|
+
export function dependencyInstall(lockfiles: string[]): { command: string[]; manual?: string } {
|
|
17
|
+
const pinned = `@clack/prompts@${CLACK_VERSION}`;
|
|
18
|
+
if (lockfiles.includes("package-lock.json")) return { command: [], manual: `npm install --save-dev ${pinned}` };
|
|
19
|
+
if (lockfiles.includes("pnpm-lock.yaml")) return { command: [], manual: `pnpm add -D ${pinned}` };
|
|
20
|
+
if (lockfiles.includes("yarn.lock")) return { command: [], manual: `yarn add --dev --exact ${pinned}` };
|
|
21
|
+
// --exact keeps the version this package pins, instead of widening it to ^.
|
|
22
|
+
return { command: ["bun", "add", "--dev", "--exact", pinned] };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DetectedOutput {
|
|
26
|
+
framework: string;
|
|
27
|
+
outputDir: string;
|
|
28
|
+
// "framework" means a build tool named the directory; "directory" means the
|
|
29
|
+
// directory was simply there, which a Bun server app's dist/ also satisfies.
|
|
30
|
+
source: "framework" | "directory";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Where this project's build lands. Framework signals come first (Astro and
|
|
35
|
+
* Eleventy both pull Vite in, so a bare Vite match must lose), then a build
|
|
36
|
+
* directory that is already on disk, then plain files under public/.
|
|
37
|
+
*/
|
|
38
|
+
export function detectBuildOutput(input: {
|
|
39
|
+
dependencies?: Record<string, unknown>;
|
|
40
|
+
files: string[];
|
|
41
|
+
directories?: string[];
|
|
42
|
+
}): DetectedOutput | undefined {
|
|
43
|
+
const dependencies = input.dependencies ?? {};
|
|
44
|
+
const dep = (name: string) => name in dependencies;
|
|
45
|
+
const file = (name: string) => input.files.includes(name);
|
|
46
|
+
// A plain file named dist must never satisfy a directory fallback.
|
|
47
|
+
const directory = (name: string) => (input.directories ?? []).includes(name);
|
|
48
|
+
const config = (base: string) =>
|
|
49
|
+
["js", "mjs", "cjs", "ts"].some((extension) => file(`${base}.${extension}`));
|
|
50
|
+
|
|
51
|
+
const framework = (name: string, outputDir: string): DetectedOutput => ({ framework: name, outputDir, source: "framework" });
|
|
52
|
+
if (dep("astro") || config("astro.config")) return framework("Astro", "dist");
|
|
53
|
+
if (dep("@11ty/eleventy") || config(".eleventy") || config("eleventy.config")) {
|
|
54
|
+
return framework("Eleventy", "_site");
|
|
55
|
+
}
|
|
56
|
+
if (dep("next") || config("next.config")) return framework("Next.js", "out");
|
|
57
|
+
if (dep("vite") || config("vite.config")) return framework("Vite", "dist");
|
|
58
|
+
for (const candidate of ["dist", "_site", "out", "build"]) {
|
|
59
|
+
if (directory(candidate)) return { framework: "existing build output", outputDir: candidate, source: "directory" };
|
|
60
|
+
}
|
|
61
|
+
if (directory("public")) return { framework: "plain files", outputDir: "public", source: "directory" };
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function shipScripts(config: {
|
|
66
|
+
outputDir: string;
|
|
67
|
+
buildScript?: string;
|
|
68
|
+
spa: boolean;
|
|
69
|
+
}): Record<string, string> {
|
|
70
|
+
const setup = [
|
|
71
|
+
"bun scripts/ship.ts --setup --static",
|
|
72
|
+
`--output-dir ${config.outputDir}`,
|
|
73
|
+
...(config.buildScript ? [`--build-script ${config.buildScript}`] : []),
|
|
74
|
+
config.spa ? "--spa" : "--no-spa",
|
|
75
|
+
].join(" ");
|
|
76
|
+
return {
|
|
77
|
+
ship: "bun scripts/ship.ts",
|
|
78
|
+
"ship:setup": setup,
|
|
79
|
+
"ship:update": "bun scripts/ship.ts --update",
|
|
80
|
+
"ship:status": "bun scripts/ship.ts --status",
|
|
81
|
+
"ship:logs": "bun scripts/ship.ts --logs",
|
|
82
|
+
"ship:webhook": "bun scripts/ship.ts --webhook",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The generators for the static image live in the Ship client, so adopted
|
|
87
|
+
// projects get byte-identical deployment files to the ones ship:setup writes.
|
|
88
|
+
// The specifier stays computed on purpose: the client is a vendored artifact
|
|
89
|
+
// with its own compiler settings and must not join this package's tsc program.
|
|
90
|
+
export interface ShipStatic {
|
|
91
|
+
staticDeploymentFileTemplates(config: {
|
|
92
|
+
outputDir: string;
|
|
93
|
+
buildScript?: string;
|
|
94
|
+
spa: boolean;
|
|
95
|
+
}): Record<string, string>;
|
|
96
|
+
staticServerSource(outputDir: string): string;
|
|
97
|
+
staticOutputDirProblem(value: string): string | undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export class AdoptError extends Error {
|
|
101
|
+
exitCode: number;
|
|
102
|
+
constructor(message: string, exitCode = 1) {
|
|
103
|
+
super(message);
|
|
104
|
+
this.exitCode = exitCode;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function verifiedShipPath(templatesDir: string): string {
|
|
109
|
+
const shipPath = join(templatesDir, "ship.ts");
|
|
110
|
+
if (!existsSync(shipPath)) {
|
|
111
|
+
throw new AdoptError(`Vendored Ship client missing at ${shipPath}; aborting.`);
|
|
112
|
+
}
|
|
113
|
+
if (templatesDir === DEFAULT_TEMPLATES_DIR) {
|
|
114
|
+
const lock = JSON.parse(
|
|
115
|
+
readFileSync(join(import.meta.dir, "..", "scripts", "ship.lock.json"), "utf8")
|
|
116
|
+
) as { sha256: string };
|
|
117
|
+
const digest = new Bun.CryptoHasher("sha256").update(readFileSync(shipPath)).digest("hex");
|
|
118
|
+
if (digest !== lock.sha256) {
|
|
119
|
+
throw new AdoptError(
|
|
120
|
+
`Vendored Ship client does not match its checksum lock; the package may be corrupt. Reinstall create-shibumi.`
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return shipPath;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function loadShipStatic(templatesDir = DEFAULT_TEMPLATES_DIR): Promise<ShipStatic> {
|
|
128
|
+
return (await import(verifiedShipPath(templatesDir))) as ShipStatic;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface AdoptOptions {
|
|
132
|
+
root: string;
|
|
133
|
+
outputDir: string;
|
|
134
|
+
buildScript?: string;
|
|
135
|
+
spa: boolean;
|
|
136
|
+
ship: ShipStatic;
|
|
137
|
+
templatesDir?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export interface AdoptResult {
|
|
141
|
+
written: string[];
|
|
142
|
+
kept: string[];
|
|
143
|
+
scripts: string[];
|
|
144
|
+
dependency: boolean;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function trackedState(root: string, path: string): "tracked" | "untracked" | "no-repository" {
|
|
148
|
+
const listed = Bun.spawnSync(["git", "ls-files", "--", path], { cwd: root, stdout: "pipe", stderr: "pipe" });
|
|
149
|
+
if (listed.exitCode !== 0) return "no-repository";
|
|
150
|
+
return listed.stdout.toString().trim() ? "tracked" : "untracked";
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Vendor the Ship client into an existing project instead of scaffolding a new
|
|
155
|
+
* one. Refuses rather than reinterpreting a project's own packaging: deployment
|
|
156
|
+
* files that already exist may build or run something else entirely.
|
|
157
|
+
*/
|
|
158
|
+
export async function adoptProject(opts: AdoptOptions): Promise<AdoptResult> {
|
|
159
|
+
const problem = opts.ship.staticOutputDirProblem(opts.outputDir);
|
|
160
|
+
if (problem) throw new AdoptError(problem, 2);
|
|
161
|
+
|
|
162
|
+
const templates = opts.ship.staticDeploymentFileTemplates({
|
|
163
|
+
outputDir: opts.outputDir,
|
|
164
|
+
buildScript: opts.buildScript,
|
|
165
|
+
spa: opts.spa,
|
|
166
|
+
});
|
|
167
|
+
// Same refusal ship:setup makes: a compose file carrying shibumi static
|
|
168
|
+
// labels next to somebody else's Dockerfile deploys the wrong artifact.
|
|
169
|
+
const targets = [...Object.keys(templates), ...(opts.spa ? ["scripts/static-server.ts"] : [])];
|
|
170
|
+
const conflicts = targets.filter((name) => existsSync(join(opts.root, name)));
|
|
171
|
+
if (conflicts.length > 0) {
|
|
172
|
+
throw new AdoptError(
|
|
173
|
+
`Adopting would generate ${conflicts.join(", ")}, which already exist and may package or run something else.\n\nNext: remove or rename them, then run bun create shibumi . again.`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
// Without a build script the image can only contain what the commit
|
|
177
|
+
// contains, so the output has to be in git already.
|
|
178
|
+
const state = opts.buildScript ? "tracked" : trackedState(opts.root, opts.outputDir);
|
|
179
|
+
if (state !== "tracked") {
|
|
180
|
+
const next = state === "no-repository"
|
|
181
|
+
? `git init && git add ${opts.outputDir} && git commit -m "Add site"`
|
|
182
|
+
: `commit ${opts.outputDir}/`;
|
|
183
|
+
throw new AdoptError(
|
|
184
|
+
`Without a build script, ${opts.outputDir}/ must be committed so shipped images match the exact commit.\n\nNext: ${next}, or add a build script to package.json, then run bun create shibumi . again.`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const templatesDir = opts.templatesDir ?? DEFAULT_TEMPLATES_DIR;
|
|
189
|
+
const written: string[] = [];
|
|
190
|
+
const kept: string[] = [];
|
|
191
|
+
|
|
192
|
+
if (existsSync(join(opts.root, "scripts", "ship.ts"))) {
|
|
193
|
+
kept.push("scripts/ship.ts");
|
|
194
|
+
} else {
|
|
195
|
+
mkdirSync(join(opts.root, "scripts"), { recursive: true });
|
|
196
|
+
copyFileSync(verifiedShipPath(templatesDir), join(opts.root, "scripts", "ship.ts"));
|
|
197
|
+
written.push("scripts/ship.ts");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Script-less generators (Jekyll) have no package.json; the ship commands
|
|
201
|
+
// and the client's own dependency need one.
|
|
202
|
+
const packagePath = join(opts.root, "package.json");
|
|
203
|
+
const before = existsSync(packagePath) ? readFileSync(packagePath, "utf8") : undefined;
|
|
204
|
+
const pkg = (before === undefined
|
|
205
|
+
? {
|
|
206
|
+
name: opts.root.split("/").pop()?.toLowerCase().replace(/[^a-z0-9._-]+/g, "-") || "app",
|
|
207
|
+
private: true,
|
|
208
|
+
type: "module",
|
|
209
|
+
}
|
|
210
|
+
: JSON.parse(before)) as {
|
|
211
|
+
scripts?: Record<string, string>;
|
|
212
|
+
devDependencies?: Record<string, string>;
|
|
213
|
+
dependencies?: Record<string, string>;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
const scripts = pkg.scripts ?? {};
|
|
217
|
+
const added: string[] = [];
|
|
218
|
+
for (const [name, command] of Object.entries(
|
|
219
|
+
shipScripts({ outputDir: opts.outputDir, buildScript: opts.buildScript, spa: opts.spa })
|
|
220
|
+
)) {
|
|
221
|
+
if (scripts[name]) continue;
|
|
222
|
+
scripts[name] = command;
|
|
223
|
+
added.push(name);
|
|
224
|
+
}
|
|
225
|
+
pkg.scripts = scripts;
|
|
226
|
+
// The vendored client imports @clack/prompts; an adopted project with an
|
|
227
|
+
// existing node_modules gets no auto-install, so the dependency is declared
|
|
228
|
+
// and installed.
|
|
229
|
+
const dependency = !pkg.dependencies?.["@clack/prompts"] && !pkg.devDependencies?.["@clack/prompts"];
|
|
230
|
+
if (dependency) pkg.devDependencies = { ...pkg.devDependencies, "@clack/prompts": CLACK_VERSION };
|
|
231
|
+
const after = `${JSON.stringify(pkg, null, 2)}\n`;
|
|
232
|
+
if (after !== before) {
|
|
233
|
+
writeFileSync(packagePath, after, { mode: 0o644 });
|
|
234
|
+
written.push("package.json");
|
|
235
|
+
} else {
|
|
236
|
+
kept.push("package.json");
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const [name, contents] of Object.entries(templates)) {
|
|
240
|
+
writeFileSync(join(opts.root, name), contents, { mode: 0o644 });
|
|
241
|
+
written.push(name);
|
|
242
|
+
}
|
|
243
|
+
if (opts.spa) {
|
|
244
|
+
mkdirSync(join(opts.root, "scripts"), { recursive: true });
|
|
245
|
+
writeFileSync(join(opts.root, "scripts", "static-server.ts"), opts.ship.staticServerSource(opts.outputDir), { mode: 0o644 });
|
|
246
|
+
written.push("scripts/static-server.ts");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return { written, kept, scripts: added, dependency };
|
|
250
|
+
}
|
package/src/args.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const TEMPLATES = ["static", "
|
|
1
|
+
export const TEMPLATES = ["static", "full-stack", "blog"] as const;
|
|
2
2
|
export type TemplateId = (typeof TEMPLATES)[number];
|
|
3
3
|
|
|
4
4
|
export interface ParsedArgs {
|
|
@@ -7,6 +7,9 @@ export interface ParsedArgs {
|
|
|
7
7
|
yes: boolean;
|
|
8
8
|
git: boolean;
|
|
9
9
|
install: boolean;
|
|
10
|
+
// `bun create shibumi .` adopts the current project instead of scaffolding.
|
|
11
|
+
adopt: boolean;
|
|
12
|
+
spa: boolean;
|
|
10
13
|
name?: string;
|
|
11
14
|
template?: TemplateId;
|
|
12
15
|
}
|
|
@@ -41,6 +44,7 @@ const BOOLEAN_FLAGS = new Set([
|
|
|
41
44
|
"-y",
|
|
42
45
|
"--no-git",
|
|
43
46
|
"--no-install",
|
|
47
|
+
"--spa",
|
|
44
48
|
]);
|
|
45
49
|
|
|
46
50
|
export function validateName(name: string): string | null {
|
|
@@ -60,6 +64,8 @@ export function parseArgs(argv: string[]): ParseResult {
|
|
|
60
64
|
yes: false,
|
|
61
65
|
git: true,
|
|
62
66
|
install: true,
|
|
67
|
+
adopt: false,
|
|
68
|
+
spa: false,
|
|
63
69
|
};
|
|
64
70
|
const positionals: string[] = [];
|
|
65
71
|
|
|
@@ -110,6 +116,7 @@ export function parseArgs(argv: string[]): ParseResult {
|
|
|
110
116
|
else if (flag === "--yes" || flag === "-y") args.yes = true;
|
|
111
117
|
else if (flag === "--no-git") args.git = false;
|
|
112
118
|
else if (flag === "--no-install") args.install = false;
|
|
119
|
+
else if (flag === "--spa") args.spa = true;
|
|
113
120
|
continue;
|
|
114
121
|
}
|
|
115
122
|
|
|
@@ -127,12 +134,29 @@ export function parseArgs(argv: string[]): ParseResult {
|
|
|
127
134
|
};
|
|
128
135
|
}
|
|
129
136
|
if (positionals.length === 1) {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
if (positionals[0] === ".") {
|
|
138
|
+
args.adopt = true;
|
|
139
|
+
} else {
|
|
140
|
+
const err = validateName(positionals[0]!);
|
|
141
|
+
if (err) return { ok: false, error: err };
|
|
142
|
+
args.name = positionals[0]!;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (args.spa && !args.adopt) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
error: `--spa applies to adopting an existing project: bun create shibumi .`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
if (args.adopt && args.template) {
|
|
153
|
+
return {
|
|
154
|
+
ok: false,
|
|
155
|
+
error: `--template does not apply to an existing project; bun create shibumi . adds deploy tooling to what is already here.`,
|
|
156
|
+
};
|
|
133
157
|
}
|
|
134
158
|
|
|
135
|
-
if (args.yes) {
|
|
159
|
+
if (args.yes && !args.adopt) {
|
|
136
160
|
if (!args.name) {
|
|
137
161
|
return { ok: false, error: `--yes requires a project name.` };
|
|
138
162
|
}
|
|
@@ -148,13 +172,15 @@ export const HELP_TEXT = `create-shibumi: scaffold a Shibumi Stack project
|
|
|
148
172
|
|
|
149
173
|
Usage
|
|
150
174
|
bun create shibumi@latest [name] [flags]
|
|
175
|
+
bun create shibumi@latest . add deploy tooling to this project
|
|
151
176
|
bunx create-shibumi [name] [flags]
|
|
152
177
|
|
|
153
178
|
Flags
|
|
154
|
-
--template <id>
|
|
179
|
+
--template <id> full-stack, blog, or static
|
|
155
180
|
--yes, -y non-interactive; requires name and --template
|
|
156
181
|
--no-git skip git init
|
|
157
182
|
--no-install skip dependency install
|
|
183
|
+
--spa adopting only: unknown paths serve index.html
|
|
158
184
|
--help, -h show this help
|
|
159
185
|
--version show version
|
|
160
186
|
|