create-blixis 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +26 -2
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +239 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Voeten
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `create-blixis`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scaffold a new [Blixis](https://github.com/blixis-io/framework) app.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm create blixis my-app
|
|
7
|
+
npm create blixis@latest my-app
|
|
8
|
+
yarn create blixis my-app
|
|
9
|
+
bun create blixis my-app
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Writes a runnable starter (service, controller, module, `main.ts`, `tsconfig.json` with the decorator settings Blixis needs), then installs `@blixis-io/core`, `@blixis-io/di`, `@blixis-io/http`, `typescript`, `@types/node` and `concurrently` with whichever package manager invoked it.
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
cd my-app
|
|
16
|
+
pnpm dev # rebuilds and restarts on every change
|
|
17
|
+
# or: pnpm build && pnpm start
|
|
18
|
+
curl http://localhost:3000/hello/world # {"message":"Hello, world!"}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| Option | |
|
|
22
|
+
|---|---|
|
|
23
|
+
| `--no-install` | write the files only and print the install commands |
|
|
24
|
+
|
|
25
|
+
The target directory must not exist or must be empty. Nested paths work (`apps/api`); the package is named after the last segment. No `@blixis-io/*` runtime dependency.
|
|
26
|
+
|
|
27
|
+
Part of [Blixis Framework](https://github.com/blixis-io/framework) — [Installation](https://blixis-io.github.io/framework/start-here/installation/) · [Quickstart](https://blixis-io.github.io/framework/start-here/quickstart/).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/index.d.ts
|
|
2
|
+
export type PackageManager = "pnpm" | "npm" | "yarn" | "bun";
|
|
3
|
+
export interface CreateResult {
|
|
4
|
+
exitCode: number;
|
|
5
|
+
stdout: string;
|
|
6
|
+
stderr: string;
|
|
7
|
+
}
|
|
8
|
+
/** Runs one package-manager command; resolves with its exit code. Injected so tests never touch the network. */
|
|
9
|
+
export type InstallRunner = (command: string, args: readonly string[], cwd: string) => Promise<number>;
|
|
10
|
+
export interface CreateOptions {
|
|
11
|
+
cwd: string;
|
|
12
|
+
/** `npm_config_user_agent` — tells us which package manager invoked `create`. */
|
|
13
|
+
userAgent?: string | undefined;
|
|
14
|
+
install?: InstallRunner;
|
|
15
|
+
}
|
|
16
|
+
export declare function detectPackageManager(userAgent: string | undefined): PackageManager;
|
|
17
|
+
/** The whole CLI as a function of argv + environment — no process.exit()/console.log(), so it is directly testable. */
|
|
18
|
+
export declare function runCreate(argv: readonly string[], options: CreateOptions): Promise<CreateResult>;
|
|
19
|
+
//#endregion
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";YAgBY;iBAEK;EACf;EACA;EACA;;;YAIU,iBAAiB,iBAAiB,yBAAyB,gBAAgB;iBAEtE;EACf;;EAEA;EACA,UAAU;;wBAGI,qBAAqB,gCAAgC;;wBAqB/C,UAAU,yBAAyB,SAAS,gBAAgB,QAAQ"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { basename, dirname, join, relative, resolve } from "node:path";
|
|
5
|
+
import { pathToFileURL } from "node:url";
|
|
6
|
+
//#region src/template.ts
|
|
7
|
+
/** Every file of the generated app, keyed by path relative to the project root. */
|
|
8
|
+
function templateFiles(packageName) {
|
|
9
|
+
return {
|
|
10
|
+
"package.json": `${JSON.stringify({
|
|
11
|
+
name: packageName,
|
|
12
|
+
version: "0.0.0",
|
|
13
|
+
private: true,
|
|
14
|
+
type: "module",
|
|
15
|
+
engines: { node: ">=24" },
|
|
16
|
+
scripts: {
|
|
17
|
+
dev: "tsc -p tsconfig.json && concurrently -k -n tsc,app \"tsc -p tsconfig.json --watch --preserveWatchOutput\" \"node --watch dist/main.js\"",
|
|
18
|
+
build: "tsc -p tsconfig.json",
|
|
19
|
+
start: "node dist/main.js",
|
|
20
|
+
typecheck: "tsc --noEmit -p tsconfig.json"
|
|
21
|
+
}
|
|
22
|
+
}, null, 2)}\n`,
|
|
23
|
+
"tsconfig.json": `${JSON.stringify({
|
|
24
|
+
compilerOptions: {
|
|
25
|
+
target: "es2023",
|
|
26
|
+
lib: ["es2023"],
|
|
27
|
+
types: ["node"],
|
|
28
|
+
module: "nodenext",
|
|
29
|
+
moduleResolution: "nodenext",
|
|
30
|
+
strict: true,
|
|
31
|
+
experimentalDecorators: true,
|
|
32
|
+
emitDecoratorMetadata: true,
|
|
33
|
+
useDefineForClassFields: false,
|
|
34
|
+
esModuleInterop: true,
|
|
35
|
+
skipLibCheck: true,
|
|
36
|
+
outDir: "dist",
|
|
37
|
+
rootDir: "src"
|
|
38
|
+
},
|
|
39
|
+
include: ["src"]
|
|
40
|
+
}, null, 2)}\n`,
|
|
41
|
+
".gitignore": "node_modules\ndist\n",
|
|
42
|
+
"README.md": `# ${packageName}
|
|
43
|
+
|
|
44
|
+
A [Blixis](https://blixis-io.github.io/framework/) app.
|
|
45
|
+
|
|
46
|
+
\`\`\`bash
|
|
47
|
+
npm run dev # rebuilds and restarts on every change
|
|
48
|
+
# or: npm run build && npm start
|
|
49
|
+
curl http://localhost:3000/hello/world
|
|
50
|
+
\`\`\`
|
|
51
|
+
|
|
52
|
+
Decorators need \`tsc\` (not tsx/esbuild) — see the
|
|
53
|
+
[installation notes](https://blixis-io.github.io/framework/start-here/installation/).
|
|
54
|
+
`,
|
|
55
|
+
"src/hello.service.ts": `import { Injectable } from "@blixis-io/di";
|
|
56
|
+
|
|
57
|
+
@Injectable()
|
|
58
|
+
export class HelloService {
|
|
59
|
+
greet(name: string): string {
|
|
60
|
+
return \`Hello, \${name}!\`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
`,
|
|
64
|
+
"src/hello.controller.ts": `import { Controller, Get, Param } from "@blixis-io/http";
|
|
65
|
+
import { HelloService } from "./hello.service.js";
|
|
66
|
+
|
|
67
|
+
@Controller("hello")
|
|
68
|
+
export class HelloController {
|
|
69
|
+
constructor(private readonly hello: HelloService) {}
|
|
70
|
+
|
|
71
|
+
@Get(":name")
|
|
72
|
+
greet(@Param("name") name: string) {
|
|
73
|
+
return { message: this.hello.greet(name) };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
`,
|
|
77
|
+
"src/app.module.ts": `import { Module } from "@blixis-io/core";
|
|
78
|
+
import { HelloController } from "./hello.controller.js";
|
|
79
|
+
import { HelloService } from "./hello.service.js";
|
|
80
|
+
|
|
81
|
+
@Module({
|
|
82
|
+
providers: [HelloService],
|
|
83
|
+
controllers: [HelloController],
|
|
84
|
+
})
|
|
85
|
+
export class AppModule {}
|
|
86
|
+
`,
|
|
87
|
+
"src/main.ts": `import { createHttpApplication } from "@blixis-io/http";
|
|
88
|
+
import { AppModule } from "./app.module.js";
|
|
89
|
+
|
|
90
|
+
const app = await createHttpApplication(AppModule);
|
|
91
|
+
const port = Number(process.env["PORT"] ?? 3000);
|
|
92
|
+
await app.listen(port);
|
|
93
|
+
|
|
94
|
+
console.log(\`Listening on http://localhost:\${port}\`);
|
|
95
|
+
|
|
96
|
+
process.on("SIGTERM", () => {
|
|
97
|
+
void app.close("SIGTERM").then(() => process.exit(0));
|
|
98
|
+
});
|
|
99
|
+
`
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
const RUNTIME_DEPENDENCIES = [
|
|
103
|
+
"@blixis-io/core",
|
|
104
|
+
"@blixis-io/di",
|
|
105
|
+
"@blixis-io/http"
|
|
106
|
+
];
|
|
107
|
+
const DEV_DEPENDENCIES = [
|
|
108
|
+
"typescript",
|
|
109
|
+
"@types/node",
|
|
110
|
+
"concurrently"
|
|
111
|
+
];
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/scaffold.ts
|
|
114
|
+
const PACKAGE_NAME = /^[a-z0-9-~][a-z0-9-._~]*$/;
|
|
115
|
+
var ScaffoldError = class extends Error {
|
|
116
|
+
constructor(..._args) {
|
|
117
|
+
super(..._args);
|
|
118
|
+
this.name = "ScaffoldError";
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
/** Writes the starter app into `cwd/<target>`. The directory may not exist yet, or must be empty. */
|
|
122
|
+
function scaffold(cwd, target) {
|
|
123
|
+
const directory = resolve(cwd, target);
|
|
124
|
+
const packageName = basename(directory);
|
|
125
|
+
if (!PACKAGE_NAME.test(packageName)) throw new ScaffoldError(`"${packageName}" is not a valid package name — use lowercase letters, digits, "-", "." or "_", starting with a letter or digit.`);
|
|
126
|
+
if (existsSync(directory) && readdirSync(directory).length > 0) throw new ScaffoldError(`${target} already exists and is not empty.`);
|
|
127
|
+
const files = Object.entries(templateFiles(packageName));
|
|
128
|
+
for (const [path, content] of files) {
|
|
129
|
+
const absolute = join(directory, path);
|
|
130
|
+
mkdirSync(dirname(absolute), { recursive: true });
|
|
131
|
+
writeFileSync(absolute, content);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
directory,
|
|
135
|
+
packageName,
|
|
136
|
+
files: files.map(([path]) => path)
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/index.ts
|
|
141
|
+
const USAGE = `create-blixis <directory> [--no-install]
|
|
142
|
+
|
|
143
|
+
pnpm create blixis my-app
|
|
144
|
+
npm create blixis@latest my-app
|
|
145
|
+
|
|
146
|
+
--no-install write the files only; print the install commands instead
|
|
147
|
+
`;
|
|
148
|
+
function detectPackageManager(userAgent) {
|
|
149
|
+
const name = userAgent?.split("/")[0];
|
|
150
|
+
return name === "pnpm" || name === "yarn" || name === "bun" ? name : "npm";
|
|
151
|
+
}
|
|
152
|
+
function addArgs(pm, dev, packages) {
|
|
153
|
+
return [
|
|
154
|
+
pm === "npm" ? "install" : "add",
|
|
155
|
+
...dev ? [pm === "bun" ? "-d" : "-D"] : [],
|
|
156
|
+
...packages
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
const runWithInheritedStdio = (command, args, cwd) => new Promise((resolve, reject) => {
|
|
160
|
+
const child = spawn(command, args, {
|
|
161
|
+
cwd,
|
|
162
|
+
stdio: "inherit",
|
|
163
|
+
shell: process.platform === "win32"
|
|
164
|
+
});
|
|
165
|
+
child.once("error", reject);
|
|
166
|
+
child.once("close", (code) => {
|
|
167
|
+
resolve(code ?? 1);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
/** The whole CLI as a function of argv + environment — no process.exit()/console.log(), so it is directly testable. */
|
|
171
|
+
async function runCreate(argv, options) {
|
|
172
|
+
const flags = argv.filter((arg) => arg.startsWith("-"));
|
|
173
|
+
const [target, ...extra] = argv.filter((arg) => !arg.startsWith("-"));
|
|
174
|
+
if (flags.includes("--help") || flags.includes("-h")) return {
|
|
175
|
+
exitCode: 0,
|
|
176
|
+
stdout: USAGE,
|
|
177
|
+
stderr: ""
|
|
178
|
+
};
|
|
179
|
+
const unknown = flags.find((flag) => flag !== "--no-install");
|
|
180
|
+
if (unknown) return {
|
|
181
|
+
exitCode: 1,
|
|
182
|
+
stdout: "",
|
|
183
|
+
stderr: `Unknown option "${unknown}"\n\n${USAGE}`
|
|
184
|
+
};
|
|
185
|
+
if (!target || extra.length > 0) return {
|
|
186
|
+
exitCode: 1,
|
|
187
|
+
stdout: "",
|
|
188
|
+
stderr: `Usage: ${USAGE}`
|
|
189
|
+
};
|
|
190
|
+
let result;
|
|
191
|
+
try {
|
|
192
|
+
result = scaffold(options.cwd, target);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
if (error instanceof ScaffoldError) return {
|
|
195
|
+
exitCode: 1,
|
|
196
|
+
stdout: "",
|
|
197
|
+
stderr: `${error.message}\n`
|
|
198
|
+
};
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
const pm = detectPackageManager(options.userAgent);
|
|
202
|
+
const runtime = addArgs(pm, false, RUNTIME_DEPENDENCIES);
|
|
203
|
+
const dev = addArgs(pm, true, DEV_DEPENDENCIES);
|
|
204
|
+
const dir = relative(options.cwd, result.directory) || ".";
|
|
205
|
+
let stdout = `Created ${result.packageName} in ${dir}\n`;
|
|
206
|
+
if (flags.includes("--no-install")) stdout += `\nNext:\n cd ${dir}\n ${pm} ${runtime.join(" ")}\n ${pm} ${dev.join(" ")}\n`;
|
|
207
|
+
else {
|
|
208
|
+
const install = options.install ?? runWithInheritedStdio;
|
|
209
|
+
for (const args of [runtime, dev]) {
|
|
210
|
+
const code = await install(pm, args, result.directory);
|
|
211
|
+
if (code !== 0) return {
|
|
212
|
+
exitCode: code,
|
|
213
|
+
stdout,
|
|
214
|
+
stderr: `\n"${pm} ${args.join(" ")}" failed (exit ${code}). The files are in ${dir}; run the install yourself.\n`
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
stdout += `\nNext:\n cd ${dir}\n ${pm === "npm" ? "npm run" : pm} dev\n curl http://localhost:3000/hello/world\n`;
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
exitCode: 0,
|
|
221
|
+
stdout,
|
|
222
|
+
stderr: ""
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
/* v8 ignore start -- @preserve: process wiring, exercised by index.test.ts spawning the real built binary */
|
|
226
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {
|
|
227
|
+
const result = await runCreate(process.argv.slice(2), {
|
|
228
|
+
cwd: process.cwd(),
|
|
229
|
+
userAgent: process.env["npm_config_user_agent"]
|
|
230
|
+
});
|
|
231
|
+
if (result.stdout) process.stdout.write(result.stdout);
|
|
232
|
+
if (result.stderr) process.stderr.write(result.stderr);
|
|
233
|
+
process.exitCode = result.exitCode;
|
|
234
|
+
}
|
|
235
|
+
/* v8 ignore stop */
|
|
236
|
+
//#endregion
|
|
237
|
+
export { detectPackageManager, runCreate };
|
|
238
|
+
|
|
239
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/template.ts","../src/scaffold.ts","../src/index.ts"],"sourcesContent":["/** Every file of the generated app, keyed by path relative to the project root. */\nexport function templateFiles(packageName: string): Record<string, string> {\n return {\n \"package.json\": `${JSON.stringify(\n {\n name: packageName,\n version: \"0.0.0\",\n private: true,\n type: \"module\",\n engines: { node: \">=24\" },\n scripts: {\n // Decorators need tsc (esbuild/tsx drop the metadata), so dev is \"compile once, then\n // recompile on change while node restarts on the new output\".\n dev: 'tsc -p tsconfig.json && concurrently -k -n tsc,app \"tsc -p tsconfig.json --watch --preserveWatchOutput\" \"node --watch dist/main.js\"',\n build: \"tsc -p tsconfig.json\",\n start: \"node dist/main.js\",\n typecheck: \"tsc --noEmit -p tsconfig.json\",\n },\n },\n null,\n 2,\n )}\\n`,\n\n \"tsconfig.json\": `${JSON.stringify(\n {\n compilerOptions: {\n target: \"es2023\",\n lib: [\"es2023\"],\n types: [\"node\"],\n module: \"nodenext\",\n moduleResolution: \"nodenext\",\n strict: true,\n experimentalDecorators: true,\n emitDecoratorMetadata: true,\n useDefineForClassFields: false,\n esModuleInterop: true,\n skipLibCheck: true,\n outDir: \"dist\",\n rootDir: \"src\",\n },\n include: [\"src\"],\n },\n null,\n 2,\n )}\\n`,\n\n \".gitignore\": \"node_modules\\ndist\\n\",\n\n \"README.md\": `# ${packageName}\n\nA [Blixis](https://blixis-io.github.io/framework/) app.\n\n\\`\\`\\`bash\nnpm run dev # rebuilds and restarts on every change\n# or: npm run build && npm start\ncurl http://localhost:3000/hello/world\n\\`\\`\\`\n\nDecorators need \\`tsc\\` (not tsx/esbuild) — see the\n[installation notes](https://blixis-io.github.io/framework/start-here/installation/).\n`,\n\n \"src/hello.service.ts\": `import { Injectable } from \"@blixis-io/di\";\n\n@Injectable()\nexport class HelloService {\n greet(name: string): string {\n return \\`Hello, \\${name}!\\`;\n }\n}\n`,\n\n \"src/hello.controller.ts\": `import { Controller, Get, Param } from \"@blixis-io/http\";\nimport { HelloService } from \"./hello.service.js\";\n\n@Controller(\"hello\")\nexport class HelloController {\n constructor(private readonly hello: HelloService) {}\n\n @Get(\":name\")\n greet(@Param(\"name\") name: string) {\n return { message: this.hello.greet(name) };\n }\n}\n`,\n\n \"src/app.module.ts\": `import { Module } from \"@blixis-io/core\";\nimport { HelloController } from \"./hello.controller.js\";\nimport { HelloService } from \"./hello.service.js\";\n\n@Module({\n providers: [HelloService],\n controllers: [HelloController],\n})\nexport class AppModule {}\n`,\n\n \"src/main.ts\": `import { createHttpApplication } from \"@blixis-io/http\";\nimport { AppModule } from \"./app.module.js\";\n\nconst app = await createHttpApplication(AppModule);\nconst port = Number(process.env[\"PORT\"] ?? 3000);\nawait app.listen(port);\n\nconsole.log(\\`Listening on http://localhost:\\${port}\\`);\n\nprocess.on(\"SIGTERM\", () => {\n void app.close(\"SIGTERM\").then(() => process.exit(0));\n});\n`,\n };\n}\n\nexport const RUNTIME_DEPENDENCIES = [\"@blixis-io/core\", \"@blixis-io/di\", \"@blixis-io/http\"] as const;\nexport const DEV_DEPENDENCIES = [\"typescript\", \"@types/node\", \"concurrently\"] as const;\n","import { existsSync, mkdirSync, readdirSync, writeFileSync } from \"node:fs\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { templateFiles } from \"./template.js\";\n\nconst PACKAGE_NAME = /^[a-z0-9-~][a-z0-9-._~]*$/;\n\nexport class ScaffoldError extends Error {\n override readonly name = \"ScaffoldError\";\n}\n\nexport interface ScaffoldResult {\n directory: string;\n packageName: string;\n files: string[];\n}\n\n/** Writes the starter app into `cwd/<target>`. The directory may not exist yet, or must be empty. */\nexport function scaffold(cwd: string, target: string): ScaffoldResult {\n const directory = resolve(cwd, target);\n const packageName = basename(directory);\n\n if (!PACKAGE_NAME.test(packageName)) {\n throw new ScaffoldError(\n `\"${packageName}\" is not a valid package name — use lowercase letters, digits, \"-\", \".\" or \"_\", starting with a letter or digit.`,\n );\n }\n if (existsSync(directory) && readdirSync(directory).length > 0) {\n throw new ScaffoldError(`${target} already exists and is not empty.`);\n }\n\n const files = Object.entries(templateFiles(packageName));\n for (const [path, content] of files) {\n const absolute = join(directory, path);\n mkdirSync(dirname(absolute), { recursive: true });\n writeFileSync(absolute, content);\n }\n\n return { directory, packageName, files: files.map(([path]) => path) };\n}\n","#!/usr/bin/env node\nimport { spawn } from \"node:child_process\";\nimport { realpathSync } from \"node:fs\";\nimport { relative } from \"node:path\";\nimport { pathToFileURL } from \"node:url\";\nimport { ScaffoldError, scaffold } from \"./scaffold.js\";\nimport { DEV_DEPENDENCIES, RUNTIME_DEPENDENCIES } from \"./template.js\";\n\nconst USAGE = `create-blixis <directory> [--no-install]\n\n pnpm create blixis my-app\n npm create blixis@latest my-app\n\n --no-install write the files only; print the install commands instead\n`;\n\nexport type PackageManager = \"pnpm\" | \"npm\" | \"yarn\" | \"bun\";\n\nexport interface CreateResult {\n exitCode: number;\n stdout: string;\n stderr: string;\n}\n\n/** Runs one package-manager command; resolves with its exit code. Injected so tests never touch the network. */\nexport type InstallRunner = (command: string, args: readonly string[], cwd: string) => Promise<number>;\n\nexport interface CreateOptions {\n cwd: string;\n /** `npm_config_user_agent` — tells us which package manager invoked `create`. */\n userAgent?: string | undefined;\n install?: InstallRunner;\n}\n\nexport function detectPackageManager(userAgent: string | undefined): PackageManager {\n const name = userAgent?.split(\"/\")[0];\n return name === \"pnpm\" || name === \"yarn\" || name === \"bun\" ? name : \"npm\";\n}\n\nfunction addArgs(pm: PackageManager, dev: boolean, packages: readonly string[]): string[] {\n const verb = pm === \"npm\" ? \"install\" : \"add\";\n const devFlag = pm === \"bun\" ? \"-d\" : \"-D\";\n return [verb, ...(dev ? [devFlag] : []), ...packages];\n}\n\nconst runWithInheritedStdio: InstallRunner = (command, args, cwd) =>\n new Promise((resolve, reject) => {\n const child = spawn(command, args, { cwd, stdio: \"inherit\", shell: process.platform === \"win32\" });\n child.once(\"error\", reject);\n child.once(\"close\", (code) => {\n resolve(code ?? 1);\n });\n });\n\n/** The whole CLI as a function of argv + environment — no process.exit()/console.log(), so it is directly testable. */\nexport async function runCreate(argv: readonly string[], options: CreateOptions): Promise<CreateResult> {\n const flags = argv.filter((arg) => arg.startsWith(\"-\"));\n const [target, ...extra] = argv.filter((arg) => !arg.startsWith(\"-\"));\n\n if (flags.includes(\"--help\") || flags.includes(\"-h\")) {\n return { exitCode: 0, stdout: USAGE, stderr: \"\" };\n }\n const unknown = flags.find((flag) => flag !== \"--no-install\");\n if (unknown) {\n return { exitCode: 1, stdout: \"\", stderr: `Unknown option \"${unknown}\"\\n\\n${USAGE}` };\n }\n if (!target || extra.length > 0) {\n return { exitCode: 1, stdout: \"\", stderr: `Usage: ${USAGE}` };\n }\n\n let result;\n try {\n result = scaffold(options.cwd, target);\n } catch (error) {\n if (error instanceof ScaffoldError) {\n return { exitCode: 1, stdout: \"\", stderr: `${error.message}\\n` };\n }\n throw error;\n }\n\n const pm = detectPackageManager(options.userAgent);\n const runtime = addArgs(pm, false, RUNTIME_DEPENDENCIES);\n const dev = addArgs(pm, true, DEV_DEPENDENCIES);\n const dir = relative(options.cwd, result.directory) || \".\";\n let stdout = `Created ${result.packageName} in ${dir}\\n`;\n\n if (flags.includes(\"--no-install\")) {\n stdout += `\\nNext:\\n cd ${dir}\\n ${pm} ${runtime.join(\" \")}\\n ${pm} ${dev.join(\" \")}\\n`;\n } else {\n const install = options.install ?? runWithInheritedStdio;\n for (const args of [runtime, dev]) {\n const code = await install(pm, args, result.directory);\n if (code !== 0) {\n return {\n exitCode: code,\n stdout,\n stderr: `\\n\"${pm} ${args.join(\" \")}\" failed (exit ${code}). The files are in ${dir}; run the install yourself.\\n`,\n };\n }\n }\n const run = pm === \"npm\" ? \"npm run\" : pm;\n stdout += `\\nNext:\\n cd ${dir}\\n ${run} dev\\n curl http://localhost:3000/hello/world\\n`;\n }\n\n return { exitCode: 0, stdout, stderr: \"\" };\n}\n\n// Same entry-point check as @blixis-io/cli: pathToFileURL(realpathSync(...)) so a\n// bin shim's symlink and a path with spaces both still match import.meta.url.\n/* v8 ignore start -- @preserve: process wiring, exercised by index.test.ts spawning the real built binary */\nif (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) {\n const result = await runCreate(process.argv.slice(2), {\n cwd: process.cwd(),\n userAgent: process.env[\"npm_config_user_agent\"],\n });\n if (result.stdout) {\n process.stdout.write(result.stdout);\n }\n if (result.stderr) {\n process.stderr.write(result.stderr);\n }\n process.exitCode = result.exitCode;\n}\n/* v8 ignore stop */\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-blixis",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"description": "Scaffold a new Blixis app: pnpm create blixis my-app. No @blixis-io/* runtime dependency.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Michael Voeten <michael@voeten.online>",
|
|
7
|
+
"homepage": "https://blixis-io.github.io/framework/",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/blixis-io/framework.git",
|
|
11
|
+
"directory": "packages/create-blixis"
|
|
12
|
+
},
|
|
13
|
+
"type": "module",
|
|
14
|
+
"engines": {
|
|
15
|
+
"node": ">=24"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"create",
|
|
19
|
+
"scaffold",
|
|
20
|
+
"starter",
|
|
21
|
+
"blixis"
|
|
22
|
+
],
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"bin": {
|
|
30
|
+
"create-blixis": "./dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"@blixis-io/source": "./src/index.ts",
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"default": "./dist/index.js"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"typecheck": "tsc -p tsconfig.json",
|
|
41
|
+
"build": "rolldown -c"
|
|
42
|
+
}
|
|
6
43
|
}
|