@alxia/create 0.1.1 → 0.1.3

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 CHANGED
@@ -28,7 +28,7 @@ bun create @alxia my-site --template react-router
28
28
 
29
29
  | template | what it writes |
30
30
  | --- | --- |
31
- | `api` | an `@alxia/core` app with Zod: `POST /todos` validates its body, behind `requireKey`, a hook made with `defineHook` that answers 401 without an API key; a `bun test` spec calling it with `app.request()` and through `@alxia/client`, typed; `bun dev` restarting on change, `typecheck`, `build`, a strict `tsconfig.json`, `.gitignore` and a README |
31
+ | `api` | an `@alxia/core` app with Zod: `POST /todos` validates its body, behind `requireKey`, a hook made with `defineHook` that answers 401 without an API key; a `bun test` spec calling it with `app.request()` and through `@alxia/client`, typed; `bun dev` restarting on change, `typecheck`, `build`, a strict `tsconfig.json`, a `Dockerfile` on `oven/bun:1`, `.dockerignore`, `.gitignore`, `.env.example` and a README |
32
32
  | `react-router` | React Router's official template, as `create-react-router` writes it, shipped in this package and copied, with [`@alxia/react-router`](https://www.npmjs.com/package/@alxia/react-router) added as its README says: `alxia()` in `vite.config.ts`'s plugins, `start` running `bun build/server/index.js`, a `bunfig.toml` starting React Router's CLI on Bun, and a `Dockerfile` on `oven/bun:1` in place of React Router's Node one. No server file: the default one serves the pages; `bunx alxia-react-router reveal` writes it out to customise |
33
33
 
34
34
  The heart of the `api` project, its route and hook (the whole file, with
@@ -53,9 +53,22 @@ export const app = alxia()
53
53
 
54
54
  ## Docker
55
55
 
56
- The `react-router` project builds into an image as it is written: its
57
- `Dockerfile` installs, builds and runs on `oven/bun:1`, and the image runs
58
- `bun build/server/index.js` as the non-root `bun` user.
56
+ Both projects build into an image as they are written, on `oven/bun:1`,
57
+ the image running the app as the non-root `bun` user. Each `Dockerfile`
58
+ builds in a stage of its own, and the image holds the build output alone,
59
+ no `node_modules`:
60
+
61
+ - `api`: `bun run build` bundles `src/server.ts` and its dependencies into
62
+ `dist/server.js`; the image holds `dist/` and runs `bun --no-install dist/server.js`.
63
+ - `react-router`: `bun run build`, every dependency bundled into
64
+ `build/server/index.js` by `@alxia/react-router`'s plugin; the image
65
+ holds `build/` and runs `bun --no-install build/server/index.js`.
66
+
67
+ ```sh
68
+ cd my-api
69
+ docker build -t my-api .
70
+ docker run -p 3000:3000 -e API_KEY=change-me my-api
71
+ ```
59
72
 
60
73
  ```sh
61
74
  cd my-site
@@ -66,7 +79,9 @@ docker run -p 3000:3000 my-site
66
79
  Commit the `bun.lock` that `bun install` wrote: the image installs from it
67
80
  with `--frozen-lockfile`. The
68
81
  [guide](https://github.com/softistx/alxia/blob/develop/packages/create/docs/guide.md#docker)
69
- has the stages.
82
+ has the stages, and
83
+ [troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/create/docs/troubleshooting.md#error-cannot-find-package--from-appdistserverjs)
84
+ what to do for a dependency that cannot be bundled.
70
85
 
71
86
  ## Options
72
87
 
@@ -117,6 +132,6 @@ the project it writes installs with `bun install`.
117
132
 
118
133
  ## Documentation
119
134
 
120
- - [Guide](https://github.com/softistx/alxia/blob/develop/packages/create/docs/guide.md): each template file by file, what the `react-router` template adds to React Router's, how versions are chosen, and running it in CI.
135
+ - [Guide](https://github.com/softistx/alxia/blob/develop/packages/create/docs/guide.md): each template file by file, what the `react-router` template adds to React Router's, each `Dockerfile`, how versions are chosen, and running it in CI.
121
136
  - [Troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/create/docs/troubleshooting.md): each message the command prints, and what to do about it.
122
137
  - [Roadmap](https://github.com/softistx/alxia/blob/develop/packages/create/docs/roadmap.md): what is coming, and what is not planned.
package/dist/copy.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import type { Template } from './args';
2
+ import type { Manifest } from './registry';
3
+ import type { AlxiaPackage } from './versions';
4
+ /**
5
+ * Where the templates are, beside `src/` here and beside `dist/` published:
6
+ * this module is bundled into `dist/index.js`, as `versions.ts` reads
7
+ * `../package.json`.
8
+ */
9
+ export declare const TEMPLATES: string;
10
+ /**
11
+ * Files a template stores under another name, because a published tarball
12
+ * would not hold them: `bun publish`, as `npm publish`, leaves every
13
+ * `.gitignore` out, and Bun leaves `bunfig.toml` out too.
14
+ */
15
+ export declare const RENAMED: Readonly<Record<string, string>>;
16
+ /**
17
+ * `template`, from `root`: its manifest, named `name` with alxia's packages
18
+ * at `alxia`'s ranges, and every other file, by the path it is written at.
19
+ */
20
+ export declare function copyTemplate(template: Template, name: string, alxia: Record<AlxiaPackage, string>, root?: string): Promise<{
21
+ manifest: Manifest;
22
+ files: Record<string, Blob>;
23
+ }>;
24
+ //# sourceMappingURL=copy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"copy.d.ts","sourceRoot":"","sources":["../src/copy.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,SAAS,QAErB,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGpD,CAAC;AAEF;;;GAGG;AACH,wBAAsB,YAAY,CACjC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,EACnC,IAAI,GAAE,MAAkB,GACtB,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;CAAE,CAAC,CA4B9D"}
package/dist/index.js CHANGED
@@ -113,7 +113,40 @@ async function workspaceRange(name, range, from) {
113
113
 
114
114
  // src/write.ts
115
115
  import { join as join3 } from "node:path";
116
+
117
+ // src/copy.ts
118
+ import { join as join2 } from "node:path";
116
119
  import { fileURLToPath } from "node:url";
120
+ var TEMPLATES2 = fileURLToPath(new URL("../templates", import.meta.url));
121
+ var RENAMED = {
122
+ gitignore: ".gitignore",
123
+ "_bunfig.toml": "bunfig.toml"
124
+ };
125
+ async function copyTemplate(template, name, alxia, root = TEMPLATES2) {
126
+ const dir = join2(root, template);
127
+ const stored = await Bun.file(join2(dir, "package.json")).json();
128
+ const manifest = { ...stored, name };
129
+ for (const field of ["dependencies", "devDependencies"]) {
130
+ const deps = stored[field];
131
+ if (deps === undefined)
132
+ continue;
133
+ manifest[field] = Object.fromEntries(Object.entries(deps).map(([pkg, range]) => [
134
+ pkg,
135
+ Object.hasOwn(alxia, pkg) ? alxia[pkg] : range
136
+ ]));
137
+ }
138
+ const left = JSON.stringify(manifest).match(/"([^"]+)":"workspace:/);
139
+ if (left) {
140
+ throw new Error(`the ${template} template names ${left[1]} at workspace:, which this @alxia/create has no version for`);
141
+ }
142
+ const files = {};
143
+ for await (const path of new Bun.Glob("**").scan({ cwd: dir, dot: true })) {
144
+ if (path === "package.json" || path.endsWith(".DS_Store"))
145
+ continue;
146
+ files[RENAMED[path] ?? path] = Bun.file(join2(dir, path));
147
+ }
148
+ return { manifest, files };
149
+ }
117
150
 
118
151
  // src/registry.ts
119
152
  function registryUrl(env) {
@@ -209,217 +242,9 @@ async function bumpDependencies(manifest, registry, built = {}) {
209
242
  return { moved, held, failed, unmatched, behind };
210
243
  }
211
244
 
212
- // src/templates/api.ts
213
- function apiManifest(name, alxia) {
214
- return {
215
- name,
216
- private: true,
217
- type: "module",
218
- scripts: {
219
- dev: "bun --watch src/server.ts",
220
- build: "bun build src/server.ts --target=bun --outdir=dist",
221
- start: "bun dist/server.js",
222
- test: "bun test",
223
- typecheck: "tsc --noEmit"
224
- },
225
- dependencies: {
226
- "@alxia/core": alxia["@alxia/core"],
227
- zod: "^4.2.0"
228
- },
229
- devDependencies: {
230
- "@alxia/client": alxia["@alxia/client"],
231
- "@types/bun": "^1.4.2",
232
- typescript: "^6.0.3"
233
- }
234
- };
235
- }
236
- var APP = `import { alxia, defineHook } from '@alxia/core';
237
- import { z } from 'zod';
238
-
239
- const Todo = z.object({ id: z.number(), title: z.string(), done: z.boolean() });
240
- const NewTodo = z.object({ title: z.string().min(1) });
241
-
242
- /** Set API_KEY in the environment: this default is for development. */
243
- export const apiKey = Bun.env['API_KEY'] ?? 'dev-key';
244
-
245
- // A hook of the routes it is given to: it answers 401 without the key, and
246
- // that 401 joins the type of each, so the client reads it.
247
- const requireKey = defineHook(({ request, reply }) =>
248
- request.headers.get('x-api-key') === apiKey
249
- ? undefined
250
- : reply(401, { error: 'unauthorized' as const }),
251
- );
252
-
253
- const todos: z.infer<typeof Todo>[] = [];
254
-
255
- export const app = alxia()
256
- .decorate({ todos })
257
- .post(
258
- '/todos',
259
- [requireKey],
260
- { body: NewTodo, response: { 201: Todo } },
261
- ({ body, todos, reply }) => {
262
- const todo = { id: todos.length + 1, title: body.title, done: false };
263
- todos.push(todo);
264
- return reply.created(todo);
265
- },
266
- );
267
-
268
- export type App = typeof app;
269
- `;
270
- var SERVER = `import { app } from './app';
271
-
272
- const server = app.listen(Number(Bun.env['PORT'] ?? 3000));
273
- console.log(\`listening on \${server.url}\`);
274
- `;
275
- var SPEC = `import { expect, test } from 'bun:test';
276
- import { client } from '@alxia/client';
277
- import { apiKey, app } from './app';
278
-
279
- const json = { 'content-type': 'application/json', 'x-api-key': apiKey };
280
-
281
- test('creates a todo from JSON', async () => {
282
- const response = await app.request('/todos', {
283
- method: 'POST',
284
- headers: json,
285
- body: JSON.stringify({ title: 'Write a route' }),
286
- });
287
- expect(response.status).toBe(201);
288
- expect(await response.json()).toEqual({
289
- id: expect.any(Number),
290
- title: 'Write a route',
291
- done: false,
292
- });
293
- });
294
-
295
- test('refuses an empty title with a 400 naming it', async () => {
296
- const response = await app.request('/todos', {
297
- method: 'POST',
298
- headers: json,
299
- body: JSON.stringify({ title: '' }),
300
- });
301
- expect(response.status).toBe(400);
302
- expect((await response.json()).issues[0].path).toEqual(['title']);
303
- });
304
-
305
- test('the typed client reads each status the route answers', async () => {
306
- // Given the app itself, the client calls its fetch in process.
307
- const api = client(app, { headers: { 'x-api-key': apiKey } });
308
- const created = await api.post('/todos', { body: { title: 'Call it typed' } });
309
- if (created.status !== 201) throw new Error(\`got \${created.status}\`);
310
- expect(created.data.title).toBe('Call it typed'); // data is the Todo schema's type
311
-
312
- const anonymous = await client(app).post('/todos', { body: { title: 'No key' } });
313
- expect(anonymous.status).toBe(401);
314
- if (anonymous.status === 401) expect(anonymous.data.error).toBe('unauthorized');
315
- });
316
- `;
317
- var TSCONFIG = `{
318
- "compilerOptions": {
319
- "types": ["bun"],
320
- "lib": ["ESNext", "DOM"],
321
- "target": "ESNext",
322
- "module": "ESNext",
323
- "moduleDetection": "force",
324
- "moduleResolution": "bundler",
325
- "verbatimModuleSyntax": true,
326
- "noEmit": true,
327
- "skipLibCheck": true,
328
- "resolveJsonModule": true,
329
- "strict": true,
330
- "noImplicitOverride": true,
331
- "noImplicitReturns": true,
332
- "noFallthroughCasesInSwitch": true,
333
- "noUncheckedIndexedAccess": true,
334
- "exactOptionalPropertyTypes": true,
335
- "noPropertyAccessFromIndexSignature": true,
336
- "noUnusedLocals": true,
337
- "noUnusedParameters": true,
338
- "forceConsistentCasingInFileNames": true
339
- },
340
- "include": ["src"]
341
- }
342
- `;
343
- var GITIGNORE = `node_modules
344
- dist
345
- *.log
346
- .DS_Store
347
- .env
348
- .env.local
349
- `;
350
- var readme = (name) => `# ${name}
351
-
352
- An [alxia](https://github.com/softistx/alxia) app with
353
- [Zod](https://zod.dev), made with \`bun create @alxia\`.
354
-
355
- \`\`\`sh
356
- bun dev # http://localhost:3000, restarted on every change
357
- bun test # src/app.spec.ts: in process, and through the typed client
358
- bun run typecheck
359
- bun run build # dist/server.js, run with \`bun start\`
360
- \`\`\`
361
-
362
- \`\`\`sh
363
- curl -X POST localhost:3000/todos \\
364
- -H 'content-type: application/json' -H 'x-api-key: dev-key' \\
365
- -d '{"title":"Write a route"}'
366
- \`\`\`
367
-
368
- - \`src/app.ts\`: the app. \`POST /todos\` validates its body with Zod, and
369
- its own hook, \`requireKey\`, answers 401 without the \`x-api-key\` header.
370
- Set \`API_KEY\` in the environment outside development.
371
- - \`src/server.ts\`: listens on \`PORT\`, 3000 by default.
372
- - \`src/app.spec.ts\`: \`app.request()\` and \`@alxia/client\`, no port.
373
-
374
- Next: [Getting started](https://github.com/softistx/alxia/blob/develop/packages/core/docs/guide/getting-started.md)
375
- and the rest of [\`@alxia/core\`'s guide](https://github.com/softistx/alxia/tree/develop/packages/core/docs).
376
- `;
377
- function apiFiles(name) {
378
- return {
379
- "src/app.ts": APP,
380
- "src/server.ts": SERVER,
381
- "src/app.spec.ts": SPEC,
382
- "tsconfig.json": TSCONFIG,
383
- ".gitignore": GITIGNORE,
384
- "README.md": readme(name)
385
- };
386
- }
387
-
388
- // src/templates/react-router.ts
389
- import { join as join2 } from "node:path";
390
- var RENAMED = {
391
- gitignore: ".gitignore",
392
- "_bunfig.toml": "bunfig.toml"
393
- };
394
- async function reactRouterTemplate(dir, name, alxia) {
395
- const stored = await Bun.file(join2(dir, "package.json")).json();
396
- const dependencies = { ...stored.dependencies };
397
- for (const pkg of Object.keys(dependencies)) {
398
- if (pkg in alxia)
399
- dependencies[pkg] = alxia[pkg];
400
- }
401
- const manifest = { ...stored, name, dependencies };
402
- const files = {};
403
- for await (const path of new Bun.Glob("**").scan({ cwd: dir, dot: true })) {
404
- if (path === "package.json" || path.endsWith(".DS_Store"))
405
- continue;
406
- files[RENAMED[path] ?? path] = Bun.file(join2(dir, path));
407
- }
408
- return { manifest, files };
409
- }
410
-
411
245
  // src/write.ts
412
- var REACT_ROUTER = fileURLToPath(new URL("../templates/react-router", import.meta.url));
413
246
  async function write(target, template, alxia, io) {
414
- const name = packageName(target);
415
- let manifest;
416
- let files;
417
- if (template === "api") {
418
- manifest = apiManifest(name, alxia);
419
- files = apiFiles(name);
420
- } else {
421
- ({ manifest, files } = await reactRouterTemplate(REACT_ROUTER, name, alxia));
422
- }
247
+ const { manifest, files } = await copyTemplate(template, packageName(target), alxia);
423
248
  io.out("Resolving the newest versions alxia accepts...");
424
249
  const bumped = await bumpDependencies(manifest, { url: registryUrl(io.env), ...io.fetch ? { fetch: io.fetch } : {} }, alxia);
425
250
  for (const line of bumped.moved)
@@ -429,11 +254,11 @@ async function write(target, template, alxia, io) {
429
254
  if (bumped.failed.length > 0) {
430
255
  io.err(`${NAME}: warning: the registry did not answer for ${bumped.failed.join(", ")}; ` + "kept the versions the template ships.");
431
256
  }
432
- files["package.json"] = `${JSON.stringify(manifest, null, 2)}
433
- `;
434
257
  for (const [file, content] of Object.entries(files)) {
435
258
  await Bun.write(join3(target, file), content);
436
259
  }
260
+ await Bun.write(join3(target, "package.json"), `${JSON.stringify(manifest, null, 2)}
261
+ `);
437
262
  return true;
438
263
  }
439
264
  // src/io.ts
@@ -550,5 +375,5 @@ function askTemplate(io) {
550
375
  // src/index.ts
551
376
  process.exitCode = await main(process.argv.slice(2), process.cwd(), processIo());
552
377
 
553
- //# debugId=793DE037BCA1C3D464756E2164756E21
378
+ //# debugId=DC169542838EEB9F64756E2164756E21
554
379
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1,19 +1,18 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/main.ts", "../src/args.ts", "../src/target.ts", "../src/versions.ts", "../src/write.ts", "../src/registry.ts", "../src/templates/api.ts", "../src/templates/react-router.ts", "../src/io.ts", "../src/index.ts"],
3
+ "sources": ["../src/main.ts", "../src/args.ts", "../src/target.ts", "../src/versions.ts", "../src/write.ts", "../src/copy.ts", "../src/registry.ts", "../src/io.ts", "../src/index.ts"],
4
4
  "sourcesContent": [
5
5
  "/**\n * The `create-alxia` command: its arguments or answers, the project it\n * writes, what it prints, and its exit code. `index.ts` runs it on the\n * process's own streams; a spec runs it on its own.\n */\nimport { stat } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { isTemplate, NAME, parseArgs, TEMPLATES, type Template } from './args';\nimport type { Io } from './io';\nimport { clean, refusal } from './target';\nimport { alxiaRanges } from './versions';\nimport { write } from './write';\n\nexport { NAME } from './args';\nexport type { Io } from './io';\nexport { processIo } from './io';\nexport { packageName, refusal } from './target';\n\nexport const USAGE = `Usage: bun create @alxia [dir] [options]\n\nWrites a new alxia app in dir, which must be empty or not exist yet. Asks\nfor what is not given.\n\nOptions:\n -t, --template <name> api an alxia app with Zod, a spec and the typed client\n react-router React Router's official template, served by alxia\n --no-install write the files, skip bun install\n -h, --help show this help`;\n\n/** Runs the command in `cwd`; resolves to the process's exit code. */\nexport async function main(\n\targs: readonly string[],\n\tcwd: string,\n\tio: Io,\n): Promise<number> {\n\tconst options = parseArgs(args);\n\tif ('error' in options) {\n\t\tio.err(`${NAME}: ${options.error}\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\tif (options.help) {\n\t\tio.out(USAGE);\n\t\treturn 0;\n\t}\n\n\tconst dir =\n\t\toptions.dir ?? io.ask('Where should the project go?', 'alxia-app');\n\tif (dir === null) {\n\t\tio.err(`${NAME}: cancelled, nothing written.`);\n\t\treturn 1;\n\t}\n\tif (dir === undefined) {\n\t\tio.err(\n\t\t\t`${NAME}: no directory given, and no terminal to ask in.\\n\\n${USAGE}`,\n\t\t);\n\t\treturn 1;\n\t}\n\tconst template = options.template ?? askTemplate(io);\n\tif (template === null) {\n\t\tio.err(`${NAME}: cancelled, nothing written.`);\n\t\treturn 1;\n\t}\n\tif (typeof template === 'object') {\n\t\tio.err(`${NAME}: ${template.error}\\n\\n${USAGE}`);\n\t\treturn 1;\n\t}\n\n\tconst target = resolve(cwd, dir);\n\tconst refused = await refusal(target, dir);\n\tif (refused) {\n\t\tio.err(`${NAME}: ${refused}`);\n\t\treturn 1;\n\t}\n\n\tconst existed = (await stat(target).catch(() => undefined)) !== undefined;\n\tconst made = await alxiaRanges()\n\t\t.then((alxia) => write(target, template, alxia, io))\n\t\t.catch(async (error) => {\n\t\t\t// The directory was empty or absent: what is in it now is ours.\n\t\t\tawait clean(target, existed);\n\t\t\tio.err(\n\t\t\t\t`${NAME}: failed: ${error instanceof Error ? error.message : String(error)}`,\n\t\t\t);\n\t\t\treturn false;\n\t\t});\n\tif (!made) return 1;\n\n\tlet installed = true;\n\tif (options.install) {\n\t\tio.out('\\nInstalling dependencies with bun install...');\n\t\tinstalled = (await io.run([process.execPath, 'install'], target)) === 0;\n\t\tif (!installed)\n\t\t\tio.err(`${NAME}: bun install failed; the files are written.`);\n\t}\n\tio.out(\n\t\tdone(relative(cwd, target) || '.', template, options.install && installed),\n\t);\n\treturn installed ? 0 : 1;\n}\n\n/** The last thing printed: what was written, and the commands to run next. */\nfunction done(where: string, template: Template, installed: boolean): string {\n\tconst steps = [\n\t\twhere === '.' ? undefined : `cd ${where}`,\n\t\tinstalled ? undefined : 'bun install',\n\t\t'bun dev',\n\t].filter((step) => step !== undefined);\n\treturn `\\nDone: ${where === '.' ? 'this directory' : where} holds the ${template} template. Next:\\n\\n${steps\n\t\t.map((step) => ` ${step}`)\n\t\t.join('\\n')}\\n`;\n}\n\n/** The template answered; null when cancelled. */\nfunction askTemplate(io: Io): Template | { error: string } | null {\n\tconst answer = io.ask(`Which template? ${TEMPLATES.join(' or ')}`, 'api');\n\tif (answer === null) return null;\n\tif (answer === undefined) {\n\t\treturn { error: 'no --template given, and no terminal to ask in.' };\n\t}\n\treturn isTemplate(answer)\n\t\t? answer\n\t\t: { error: `unknown template ${answer}: use ${TEMPLATES.join(' or ')}.` };\n}\n",
6
6
  "/** The command line of `create-alxia`, read into what to create. */\n\n/** The command's name, which starts every line it prints to stderr. */\nexport const NAME = 'create-alxia';\n\n/** The templates, in the order the prompt lists them. */\nexport const TEMPLATES = ['api', 'react-router'] as const;\nexport type Template = (typeof TEMPLATES)[number];\n\nexport function isTemplate(value: string): value is Template {\n\treturn (TEMPLATES as readonly string[]).includes(value);\n}\n\n/** What the arguments asked for. A missing `dir` or `template` is prompted. */\nexport interface Options {\n\treadonly dir?: string;\n\treadonly template?: Template;\n\treadonly install: boolean;\n\treadonly help: boolean;\n}\n\n/** The arguments as options, or the message that refuses them. */\nexport function parseArgs(\n\targs: readonly string[],\n): Options | { readonly error: string } {\n\tlet dir: string | undefined;\n\tlet template: Template | undefined;\n\tlet install = true;\n\tlet help = false;\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i] as string;\n\t\tif (arg === '--help' || arg === '-h') help = true;\n\t\telse if (arg === '--no-install') install = false;\n\t\telse if (\n\t\t\targ === '--template' ||\n\t\t\targ === '-t' ||\n\t\t\targ.startsWith('--template=')\n\t\t) {\n\t\t\tconst value = arg.includes('=')\n\t\t\t\t? arg.slice(arg.indexOf('=') + 1)\n\t\t\t\t: args[++i];\n\t\t\tif (value === undefined || value === '') {\n\t\t\t\treturn { error: `${arg} needs a template: ${TEMPLATES.join(' or ')}.` };\n\t\t\t}\n\t\t\tif (!isTemplate(value)) {\n\t\t\t\treturn {\n\t\t\t\t\terror: `unknown template ${value}: use ${TEMPLATES.join(' or ')}.`,\n\t\t\t\t};\n\t\t\t}\n\t\t\ttemplate = value;\n\t\t} else if (arg.startsWith('-')) return { error: `unknown option ${arg}.` };\n\t\telse if (dir === undefined) dir = arg;\n\t\telse return { error: `one directory only, given ${dir} and ${arg}.` };\n\t}\n\treturn {\n\t\t...(dir === undefined ? {} : { dir }),\n\t\t...(template === undefined ? {} : { template }),\n\t\tinstall,\n\t\thelp,\n\t};\n}\n",
7
7
  "/** The directory a project is written into: its name, whether it may be, and undoing it. */\nimport { readdir, rm, stat } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\n\n/** A directory name as a package name: lowercased, `-` for the rest. */\nexport function packageName(dir: string): string {\n\tconst name = basename(dir)\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9._~-]+/g, '-')\n\t\t.replace(/^[._-]+|-+$/g, '');\n\treturn name || 'alxia-app';\n}\n\n/** Why `target` cannot take a new project, or undefined when it can. */\nexport async function refusal(\n\ttarget: string,\n\tshown: string = target,\n): Promise<string | undefined> {\n\tconst info = await stat(target).catch(() => undefined);\n\tif (!info) return undefined;\n\tif (!info.isDirectory()) return `${shown} exists and is not a directory.`;\n\tconst entries = (await readdir(target)).sort();\n\tif (entries.length === 0) return undefined;\n\treturn (\n\t\t`${shown} is not empty (${entries.slice(0, 3).join(', ')}${entries.length > 3 ? ', ...' : ''}), ` +\n\t\t'and create-alxia writes only into an empty directory. Choose another directory, or empty this one.'\n\t);\n}\n\n/** Removes what was written: the directory, or what is in the one that was there. */\nexport async function clean(target: string, existed: boolean): Promise<void> {\n\tif (!existed) {\n\t\tawait rm(target, { recursive: true, force: true });\n\t\treturn;\n\t}\n\tfor (const entry of await readdir(target).catch(() => [])) {\n\t\tawait rm(join(target, entry), { recursive: true, force: true });\n\t}\n}\n",
8
8
  "/**\n * The versions a generated project starts from: alxia's own packages at the\n * versions this release of `@alxia/create` was published beside, and the\n * ranges alxia's peers accept, which the registry's newest versions must\n * stay within.\n */\nimport { dirname } from 'node:path';\n\n/** The `@alxia/*` packages a template depends on. */\nexport type AlxiaPackage =\n\t| '@alxia/client'\n\t| '@alxia/core'\n\t| '@alxia/react-router';\n\nconst ALXIA_PACKAGES: readonly AlxiaPackage[] = [\n\t'@alxia/client',\n\t'@alxia/core',\n\t'@alxia/react-router',\n];\n\n/**\n * The peer ranges of alxia's packages that a template's other dependencies\n * must stay within, by the package they constrain: `typescript` is every\n * package's, `zod` is `@alxia/zod`'s, `react-router` and `vite` are\n * `@alxia/react-router`'s. `versions.spec.ts` checks each against the\n * package that declares it, so a widened peer fails there until it is\n * widened here too.\n */\nexport const PEER_RANGES = {\n\ttypescript: '^6.0.3 || ^7.0.0',\n\tzod: '^4.2.0',\n\t'react-router': '^8.0.0',\n\tvite: '^7.0.0 || ^8.0.0',\n} as const;\n\n/**\n * The range a generated project declares for each `@alxia/*` package: what\n * this package's own `package.json` says. Published, that is `^<version>`,\n * which `bun publish` writes in place of `workspace:^` from the version\n * beside it in the workspace. Inside the workspace, `workspace:^` is\n * resolved the same way, from the sibling's installed `package.json`.\n */\nexport async function alxiaRanges(\n\tmanifestUrl: URL = new URL('../package.json', import.meta.url),\n): Promise<Record<AlxiaPackage, string>> {\n\tconst manifest = await Bun.file(manifestUrl).json();\n\tconst declared: Record<string, string> = manifest.devDependencies ?? {};\n\tconst ranges = {} as Record<AlxiaPackage, string>;\n\tfor (const name of ALXIA_PACKAGES) {\n\t\tconst range = declared[name];\n\t\tif (range === undefined) {\n\t\t\tthrow new Error(`@alxia/create: its package.json names no ${name}`);\n\t\t}\n\t\tranges[name] = range.startsWith('workspace:')\n\t\t\t? await workspaceRange(name, range, dirname(manifestUrl.pathname))\n\t\t\t: range;\n\t}\n\treturn ranges;\n}\n\n/** `workspace:^` as `bun publish` rewrites it: `^` and the sibling's version. */\nasync function workspaceRange(\n\tname: string,\n\trange: string,\n\tfrom: string,\n): Promise<string> {\n\tconst file = Bun.resolveSync(`${name}/package.json`, from);\n\tconst { version } = await Bun.file(file).json();\n\tconst prefix = range.slice('workspace:'.length);\n\treturn prefix === '*' ? version : `${prefix}${version}`;\n}\n",
9
- "/** Writes a template's project, its dependencies moved to the registry's newest. */\n\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { NAME, type Template } from './args';\nimport type { Io } from './io';\nimport { bumpDependencies, type Manifest, registryUrl } from './registry';\nimport { packageName } from './target';\nimport { apiFiles, apiManifest } from './templates/api';\nimport { reactRouterTemplate } from './templates/react-router';\nimport type { AlxiaPackage } from './versions';\n\n/**\n * The `react-router` template's files, beside `src/` here and beside\n * `dist/` published: this module is bundled into `dist/index.js`, as\n * `versions.ts` reads `../package.json`.\n */\nconst REACT_ROUTER = fileURLToPath(\n\tnew URL('../templates/react-router', import.meta.url),\n);\n\n/** Writes the project into `target`. */\nexport async function write(\n\ttarget: string,\n\ttemplate: Template,\n\talxia: Record<AlxiaPackage, string>,\n\tio: Io,\n): Promise<true> {\n\tconst name = packageName(target);\n\tlet manifest: Manifest;\n\tlet files: Record<string, string | Blob>;\n\tif (template === 'api') {\n\t\tmanifest = apiManifest(name, alxia);\n\t\tfiles = apiFiles(name);\n\t} else {\n\t\t({ manifest, files } = await reactRouterTemplate(\n\t\t\tREACT_ROUTER,\n\t\t\tname,\n\t\t\talxia,\n\t\t));\n\t}\n\n\tio.out('Resolving the newest versions alxia accepts...');\n\tconst bumped = await bumpDependencies(\n\t\tmanifest,\n\t\t{ url: registryUrl(io.env), ...(io.fetch ? { fetch: io.fetch } : {}) },\n\t\talxia,\n\t);\n\tfor (const line of bumped.moved) io.out(` ${line}`);\n\tfor (const line of [...bumped.held, ...bumped.unmatched, ...bumped.behind])\n\t\tio.out(` ${line}`);\n\tif (bumped.failed.length > 0) {\n\t\tio.err(\n\t\t\t`${NAME}: warning: the registry did not answer for ${bumped.failed.join(', ')}; ` +\n\t\t\t\t'kept the versions the template ships.',\n\t\t);\n\t}\n\n\tfiles['package.json'] = `${JSON.stringify(manifest, null, 2)}\\n`;\n\tfor (const [file, content] of Object.entries(files)) {\n\t\tawait Bun.write(join(target, file), content);\n\t}\n\treturn true;\n}\n",
9
+ "/** Writes a template's project, its dependencies moved to the registry's newest. */\n\nimport { join } from 'node:path';\nimport { NAME, type Template } from './args';\nimport { copyTemplate } from './copy';\nimport type { Io } from './io';\nimport { bumpDependencies, registryUrl } from './registry';\nimport { packageName } from './target';\nimport type { AlxiaPackage } from './versions';\n\n/** Writes the project into `target`. */\nexport async function write(\n\ttarget: string,\n\ttemplate: Template,\n\talxia: Record<AlxiaPackage, string>,\n\tio: Io,\n): Promise<true> {\n\tconst { manifest, files } = await copyTemplate(\n\t\ttemplate,\n\t\tpackageName(target),\n\t\talxia,\n\t);\n\n\tio.out('Resolving the newest versions alxia accepts...');\n\tconst bumped = await bumpDependencies(\n\t\tmanifest,\n\t\t{ url: registryUrl(io.env), ...(io.fetch ? { fetch: io.fetch } : {}) },\n\t\talxia,\n\t);\n\tfor (const line of bumped.moved) io.out(` ${line}`);\n\tfor (const line of [...bumped.held, ...bumped.unmatched, ...bumped.behind])\n\t\tio.out(` ${line}`);\n\tif (bumped.failed.length > 0) {\n\t\tio.err(\n\t\t\t`${NAME}: warning: the registry did not answer for ${bumped.failed.join(', ')}; ` +\n\t\t\t\t'kept the versions the template ships.',\n\t\t);\n\t}\n\n\tfor (const [file, content] of Object.entries(files)) {\n\t\tawait Bun.write(join(target, file), content);\n\t}\n\tawait Bun.write(\n\t\tjoin(target, 'package.json'),\n\t\t`${JSON.stringify(manifest, null, 2)}\\n`,\n\t);\n\treturn true;\n}\n",
10
+ "/**\n * Copies a template stored under `templates/<template>/`: the `api` one,\n * alxia's own, and the `react-router` one, React Router's official scaffold\n * with the layer `examples/react-router` adds on top. The files are copied\n * as they are; only `package.json` is rewritten, with the project's name\n * and alxia's versions in place of `workspace:^`.\n */\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Template } from './args';\nimport type { Manifest } from './registry';\nimport type { AlxiaPackage } from './versions';\n\n/**\n * Where the templates are, beside `src/` here and beside `dist/` published:\n * this module is bundled into `dist/index.js`, as `versions.ts` reads\n * `../package.json`.\n */\nexport const TEMPLATES = fileURLToPath(\n\tnew URL('../templates', import.meta.url),\n);\n\n/**\n * Files a template stores under another name, because a published tarball\n * would not hold them: `bun publish`, as `npm publish`, leaves every\n * `.gitignore` out, and Bun leaves `bunfig.toml` out too.\n */\nexport const RENAMED: Readonly<Record<string, string>> = {\n\tgitignore: '.gitignore',\n\t'_bunfig.toml': 'bunfig.toml',\n};\n\n/**\n * `template`, from `root`: its manifest, named `name` with alxia's packages\n * at `alxia`'s ranges, and every other file, by the path it is written at.\n */\nexport async function copyTemplate(\n\ttemplate: Template,\n\tname: string,\n\talxia: Record<AlxiaPackage, string>,\n\troot: string = TEMPLATES,\n): Promise<{ manifest: Manifest; files: Record<string, Blob> }> {\n\tconst dir = join(root, template);\n\tconst stored: Manifest = await Bun.file(join(dir, 'package.json')).json();\n\tconst manifest: Manifest = { ...stored, name };\n\tfor (const field of ['dependencies', 'devDependencies'] as const) {\n\t\tconst deps = stored[field];\n\t\tif (deps === undefined) continue;\n\t\tmanifest[field] = Object.fromEntries(\n\t\t\tObject.entries(deps).map(([pkg, range]) => [\n\t\t\t\tpkg,\n\t\t\t\tObject.hasOwn(alxia, pkg) ? alxia[pkg as AlxiaPackage] : range,\n\t\t\t]),\n\t\t);\n\t}\n\t// A range only the workspace resolves would make the project uninstallable.\n\tconst left = JSON.stringify(manifest).match(/\"([^\"]+)\":\"workspace:/);\n\tif (left) {\n\t\tthrow new Error(\n\t\t\t`the ${template} template names ${left[1]} at workspace:, which this @alxia/create has no version for`,\n\t\t);\n\t}\n\tconst files: Record<string, Blob> = {};\n\tfor await (const path of new Bun.Glob('**').scan({ cwd: dir, dot: true })) {\n\t\t// A Finder file in a local checkout is not the template's.\n\t\tif (path === 'package.json' || path.endsWith('.DS_Store')) continue;\n\t\tfiles[RENAMED[path] ?? path] = Bun.file(join(dir, path));\n\t}\n\treturn { manifest, files };\n}\n",
10
11
  "/**\n * Moves a generated project's dependencies to their newest versions on the\n * registry, within what alxia accepts: a template ships the versions it was\n * written with, and React Router's lags behind its own releases. alxia's own\n * packages move within the ranges this `@alxia/create` was published with,\n * and fall back on the same minor when the registry does not have the\n * version those ranges start at yet.\n */\nimport { PEER_RANGES } from './versions';\n\n/** The dependency fields of a manifest, as a template writes them. */\nexport interface Manifest {\n\tdependencies?: Record<string, string>;\n\tdevDependencies?: Record<string, string>;\n\t[key: string]: unknown;\n}\n\n/** How to reach the registry: its URL, and the `fetch` to call it with. */\nexport interface Registry {\n\treadonly url: string;\n\treadonly fetch?: (request: Request) => Promise<Response>;\n\t/** How long one package's metadata may take, in milliseconds. */\n\treadonly timeout?: number;\n}\n\n/** What a bump changed, and what it could not. */\nexport interface Bumped {\n\t/** One line per dependency moved: `vite ^8.0.3 -> ^8.3.2`. */\n\treadonly moved: string[];\n\t/** A newer major that was left out, because a range refuses it. */\n\treadonly held: string[];\n\t/** A package whose metadata did not arrive: its version is unchanged. */\n\treadonly failed: string[];\n\t/** A package with no release within its range: its version is unchanged. */\n\treadonly unmatched: string[];\n\t/**\n\t * An `@alxia/*` package whose version the range starts at is not on the\n\t * registry yet, as just after a release: the newest of the same minor\n\t * was written instead.\n\t */\n\treadonly behind: string[];\n}\n\n/** The registry to resolve against: the one Bun or npm was given, or npm's. */\nexport function registryUrl(env: Record<string, string | undefined>): string {\n\tconst url =\n\t\tenv['BUN_CONFIG_REGISTRY'] ||\n\t\tenv['npm_config_registry'] ||\n\t\t'https://registry.npmjs.org';\n\treturn url.replace(/\\/+$/, '');\n}\n\n/**\n * The range alxia's peers hold `name` to — React Router's packages follow\n * `react-router`'s — or undefined when none does, and npm's `latest` is\n * taken.\n */\nexport function allowedRange(name: string): string | undefined {\n\tif (name === 'react-router' || name.startsWith('@react-router/')) {\n\t\treturn PEER_RANGES['react-router'];\n\t}\n\treturn name in PEER_RANGES\n\t\t? PEER_RANGES[name as keyof typeof PEER_RANGES]\n\t\t: undefined;\n}\n\ninterface Packument {\n\treadonly 'dist-tags'?: Record<string, string>;\n\treadonly versions?: Record<string, unknown>;\n}\n\n/** The newest release (not a prerelease) of `versions` within `range`. */\nexport function newestWithin(\n\tversions: readonly string[],\n\trange: string,\n): string | undefined {\n\treturn versions\n\t\t.filter((v) => !v.includes('-') && Bun.semver.satisfies(v, range))\n\t\t.sort(Bun.semver.order)\n\t\t.at(-1);\n}\n\nasync function packument(name: string, registry: Registry): Promise<Packument> {\n\tconst call = registry.fetch ?? ((request: Request) => fetch(request));\n\tconst response = await call(\n\t\tnew Request(`${registry.url}/${name.replace('/', '%2f')}`, {\n\t\t\theaders: { accept: 'application/vnd.npm.install-v1+json' },\n\t\t\tsignal: AbortSignal.timeout(registry.timeout ?? 5000),\n\t\t}),\n\t);\n\tif (!response.ok) throw new Error(`${response.status}`);\n\treturn (await response.json()) as Packument;\n}\n\n/** Within `range`, the newest; with none, npm's `latest`. */\nfunction choose(\n\tversions: readonly string[],\n\trange: string | undefined,\n\tlatest: string | undefined,\n): string | undefined {\n\tif (range !== undefined) return newestWithin(versions, range);\n\treturn latest && versions.includes(latest)\n\t\t? latest\n\t\t: newestWithin(versions, '*');\n}\n\n/**\n * `range`'s own minor, from its first version: `~0.3.0` for `^0.3.1`, or\n * undefined for a range that names no version.\n */\nexport function sameMinor(range: string): string | undefined {\n\tconst match = /(\\d+)\\.(\\d+)\\.\\d+/.exec(range);\n\treturn match ? `~${match[1]}.${match[2]}.0` : undefined;\n}\n\n/**\n * For a `built` range with no release on the registry: the newest of its\n * own minor whose `^` still takes the version the range starts at, or\n * undefined. Below 0.1, `^0.0.2` does not take 0.0.3: no fallback there.\n */\nexport function newestOfMinor(\n\tversions: readonly string[],\n\trange: string,\n): string | undefined {\n\tconst minor = sameMinor(range);\n\tconst start = /\\d+\\.\\d+\\.\\d+/.exec(range)?.[0];\n\tconst version = minor && newestWithin(versions, minor);\n\treturn version && start && Bun.semver.satisfies(start, `^${version}`)\n\t\t? version\n\t\t: undefined;\n}\n\n/**\n * Rewrites every dependency of `manifest` to `^` its newest version: within\n * `built[name]` for a package it names — alxia's, at the ranges this\n * `@alxia/create` was published with — else within the range alxia's peers\n * hold it to, else npm's `latest`. A package of `built` with no release in\n * its range, as when npm has not yet propagated a version published minutes\n * ago, takes the newest of the range's own minor, named in `behind`: `^` it\n * is still within the range. React Router's own packages take the version\n * `react-router` resolved to, when they have it: `@react-router/node` pins\n * `react-router` exactly. A package whose metadata fails to arrive keeps its\n * version, and is named in `failed`.\n */\nexport async function bumpDependencies(\n\tmanifest: Manifest,\n\tregistry: Registry,\n\tbuilt: Readonly<Record<string, string>> = {},\n): Promise<Bumped> {\n\tconst fields = ['dependencies', 'devDependencies'] as const;\n\tconst names = fields.flatMap((field) => Object.keys(manifest[field] ?? {}));\n\tconst fetched = new Map(\n\t\tawait Promise.all(\n\t\t\tnames.map(\n\t\t\t\tasync (name) =>\n\t\t\t\t\t[\n\t\t\t\t\t\tname,\n\t\t\t\t\t\tawait packument(name, registry).catch(() => undefined),\n\t\t\t\t\t] as const,\n\t\t\t),\n\t\t),\n\t);\n\tconst moved: string[] = [];\n\tconst held: string[] = [];\n\tconst failed: string[] = [];\n\tconst unmatched: string[] = [];\n\tconst behind: string[] = [];\n\tconst chosen = new Map<string, string>();\n\t// react-router first: its siblings follow the version it settles on.\n\tconst ordered = [...names].sort(\n\t\t(a, b) => Number(b === 'react-router') - Number(a === 'react-router'),\n\t);\n\tfor (const name of ordered) {\n\t\tconst field = fields.find((f) => manifest[f]?.[name] !== undefined);\n\t\tconst deps = field && manifest[field];\n\t\tconst current = deps?.[name];\n\t\tif (!deps || current === undefined) continue;\n\t\tconst meta = fetched.get(name);\n\t\tif (!meta?.versions) {\n\t\t\tfailed.push(name);\n\t\t\tcontinue;\n\t\t}\n\t\tconst versions = Object.keys(meta.versions);\n\t\tconst pinned = built[name];\n\t\tconst range = pinned ?? allowedRange(name);\n\t\tconst latest = meta['dist-tags']?.['latest'];\n\t\tconst follow = chosen.get('react-router');\n\t\tlet version =\n\t\t\tname.startsWith('@react-router/') && follow && versions.includes(follow)\n\t\t\t\t? follow\n\t\t\t\t: choose(versions, range, latest);\n\t\tif (version === undefined && pinned !== undefined) {\n\t\t\tversion = newestOfMinor(versions, pinned);\n\t\t\tif (version !== undefined) {\n\t\t\t\tbehind.push(\n\t\t\t\t\t`${name}: the registry has no release within ${range} yet; wrote ^${version}, the newest of ${sameMinor(pinned)}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tif (version === undefined) {\n\t\t\tunmatched.push(\n\t\t\t\t`${name}: no release within ${range ?? 'any range'}; kept ${current}`,\n\t\t\t);\n\t\t\tcontinue;\n\t\t}\n\t\tchosen.set(name, version);\n\t\tif (\n\t\t\trange !== undefined &&\n\t\t\tlatest &&\n\t\t\tBun.semver.order(latest, version) > 0 &&\n\t\t\t!Bun.semver.satisfies(latest, range)\n\t\t) {\n\t\t\theld.push(\n\t\t\t\t`${name}: kept to ${range}, where the newest is ${version}; npm's latest, ${latest}, is outside it`,\n\t\t\t);\n\t\t}\n\t\tconst next = `^${version}`;\n\t\tif (next !== current) moved.push(`${name} ${current} -> ${next}`);\n\t\tdeps[name] = next;\n\t}\n\treturn { moved, held, failed, unmatched, behind };\n}\n",
11
- "/**\n * The `api` template: an alxia app with Zod, one validated route behind a\n * hook of its own, a spec that calls it in process and through the typed\n * client, and the scripts to run, test, typecheck and build it.\n */\nimport type { Manifest } from '../registry';\nimport type { AlxiaPackage } from '../versions';\n\n/**\n * The template's manifest. The versions of `zod`, `typescript` and\n * `@types/bun` are where it starts: `bumpDependencies` moves each to the\n * registry's newest within alxia's peer ranges.\n */\nexport function apiManifest(\n\tname: string,\n\talxia: Record<AlxiaPackage, string>,\n): Manifest {\n\treturn {\n\t\tname,\n\t\tprivate: true,\n\t\ttype: 'module',\n\t\tscripts: {\n\t\t\tdev: 'bun --watch src/server.ts',\n\t\t\tbuild: 'bun build src/server.ts --target=bun --outdir=dist',\n\t\t\tstart: 'bun dist/server.js',\n\t\t\ttest: 'bun test',\n\t\t\ttypecheck: 'tsc --noEmit',\n\t\t},\n\t\tdependencies: {\n\t\t\t'@alxia/core': alxia['@alxia/core'],\n\t\t\tzod: '^4.2.0',\n\t\t},\n\t\tdevDependencies: {\n\t\t\t'@alxia/client': alxia['@alxia/client'],\n\t\t\t'@types/bun': '^1.4.2',\n\t\t\ttypescript: '^6.0.3',\n\t\t},\n\t};\n}\n\nconst APP = `import { alxia, defineHook } from '@alxia/core';\nimport { z } from 'zod';\n\nconst Todo = z.object({ id: z.number(), title: z.string(), done: z.boolean() });\nconst NewTodo = z.object({ title: z.string().min(1) });\n\n/** Set API_KEY in the environment: this default is for development. */\nexport const apiKey = Bun.env['API_KEY'] ?? 'dev-key';\n\n// A hook of the routes it is given to: it answers 401 without the key, and\n// that 401 joins the type of each, so the client reads it.\nconst requireKey = defineHook(({ request, reply }) =>\n\trequest.headers.get('x-api-key') === apiKey\n\t\t? undefined\n\t\t: reply(401, { error: 'unauthorized' as const }),\n);\n\nconst todos: z.infer<typeof Todo>[] = [];\n\nexport const app = alxia()\n\t.decorate({ todos })\n\t.post(\n\t\t'/todos',\n\t\t[requireKey],\n\t\t{ body: NewTodo, response: { 201: Todo } },\n\t\t({ body, todos, reply }) => {\n\t\t\tconst todo = { id: todos.length + 1, title: body.title, done: false };\n\t\t\ttodos.push(todo);\n\t\t\treturn reply.created(todo);\n\t\t},\n\t);\n\nexport type App = typeof app;\n`;\n\nconst SERVER = `import { app } from './app';\n\nconst server = app.listen(Number(Bun.env['PORT'] ?? 3000));\nconsole.log(\\`listening on \\${server.url}\\`);\n`;\n\nconst SPEC = `import { expect, test } from 'bun:test';\nimport { client } from '@alxia/client';\nimport { apiKey, app } from './app';\n\nconst json = { 'content-type': 'application/json', 'x-api-key': apiKey };\n\ntest('creates a todo from JSON', async () => {\n\tconst response = await app.request('/todos', {\n\t\tmethod: 'POST',\n\t\theaders: json,\n\t\tbody: JSON.stringify({ title: 'Write a route' }),\n\t});\n\texpect(response.status).toBe(201);\n\texpect(await response.json()).toEqual({\n\t\tid: expect.any(Number),\n\t\ttitle: 'Write a route',\n\t\tdone: false,\n\t});\n});\n\ntest('refuses an empty title with a 400 naming it', async () => {\n\tconst response = await app.request('/todos', {\n\t\tmethod: 'POST',\n\t\theaders: json,\n\t\tbody: JSON.stringify({ title: '' }),\n\t});\n\texpect(response.status).toBe(400);\n\texpect((await response.json()).issues[0].path).toEqual(['title']);\n});\n\ntest('the typed client reads each status the route answers', async () => {\n\t// Given the app itself, the client calls its fetch in process.\n\tconst api = client(app, { headers: { 'x-api-key': apiKey } });\n\tconst created = await api.post('/todos', { body: { title: 'Call it typed' } });\n\tif (created.status !== 201) throw new Error(\\`got \\${created.status}\\`);\n\texpect(created.data.title).toBe('Call it typed'); // data is the Todo schema's type\n\n\tconst anonymous = await client(app).post('/todos', { body: { title: 'No key' } });\n\texpect(anonymous.status).toBe(401);\n\tif (anonymous.status === 401) expect(anonymous.data.error).toBe('unauthorized');\n});\n`;\n\nconst TSCONFIG = `{\n\t\"compilerOptions\": {\n\t\t\"types\": [\"bun\"],\n\t\t\"lib\": [\"ESNext\", \"DOM\"],\n\t\t\"target\": \"ESNext\",\n\t\t\"module\": \"ESNext\",\n\t\t\"moduleDetection\": \"force\",\n\t\t\"moduleResolution\": \"bundler\",\n\t\t\"verbatimModuleSyntax\": true,\n\t\t\"noEmit\": true,\n\t\t\"skipLibCheck\": true,\n\t\t\"resolveJsonModule\": true,\n\t\t\"strict\": true,\n\t\t\"noImplicitOverride\": true,\n\t\t\"noImplicitReturns\": true,\n\t\t\"noFallthroughCasesInSwitch\": true,\n\t\t\"noUncheckedIndexedAccess\": true,\n\t\t\"exactOptionalPropertyTypes\": true,\n\t\t\"noPropertyAccessFromIndexSignature\": true,\n\t\t\"noUnusedLocals\": true,\n\t\t\"noUnusedParameters\": true,\n\t\t\"forceConsistentCasingInFileNames\": true\n\t},\n\t\"include\": [\"src\"]\n}\n`;\n\nconst GITIGNORE = `node_modules\ndist\n*.log\n.DS_Store\n.env\n.env.local\n`;\n\nconst readme = (name: string) => `# ${name}\n\nAn [alxia](https://github.com/softistx/alxia) app with\n[Zod](https://zod.dev), made with \\`bun create @alxia\\`.\n\n\\`\\`\\`sh\nbun dev # http://localhost:3000, restarted on every change\nbun test # src/app.spec.ts: in process, and through the typed client\nbun run typecheck\nbun run build # dist/server.js, run with \\`bun start\\`\n\\`\\`\\`\n\n\\`\\`\\`sh\ncurl -X POST localhost:3000/todos \\\\\n -H 'content-type: application/json' -H 'x-api-key: dev-key' \\\\\n -d '{\"title\":\"Write a route\"}'\n\\`\\`\\`\n\n- \\`src/app.ts\\`: the app. \\`POST /todos\\` validates its body with Zod, and\n its own hook, \\`requireKey\\`, answers 401 without the \\`x-api-key\\` header.\n Set \\`API_KEY\\` in the environment outside development.\n- \\`src/server.ts\\`: listens on \\`PORT\\`, 3000 by default.\n- \\`src/app.spec.ts\\`: \\`app.request()\\` and \\`@alxia/client\\`, no port.\n\nNext: [Getting started](https://github.com/softistx/alxia/blob/develop/packages/core/docs/guide/getting-started.md)\nand the rest of [\\`@alxia/core\\`'s guide](https://github.com/softistx/alxia/tree/develop/packages/core/docs).\n`;\n\n/** The template's files but its manifest, by path. */\nexport function apiFiles(name: string): Record<string, string> {\n\treturn {\n\t\t'src/app.ts': APP,\n\t\t'src/server.ts': SERVER,\n\t\t'src/app.spec.ts': SPEC,\n\t\t'tsconfig.json': TSCONFIG,\n\t\t'.gitignore': GITIGNORE,\n\t\t'README.md': readme(name),\n\t};\n}\n",
12
- "/**\n * The `react-router` template: React Router's official one, as\n * `create-react-router` wrote it, committed under `templates/react-router/`\n * with the layer `examples/react-router` adds on top — `@alxia/core` and\n * `@alxia/react-router` as dependencies, `alxia()` in the Vite plugins,\n * `start` running the build on Bun, and a `bunfig.toml` that starts React\n * Router's CLI on Bun. The files are copied as they are; only `package.json`\n * is rewritten, with the project's name and alxia's versions.\n */\nimport { join } from 'node:path';\nimport type { Manifest } from '../registry';\nimport type { AlxiaPackage } from '../versions';\n\n/**\n * Files the template stores under another name, because a published tarball\n * would not hold them: `bun publish`, as `npm publish`, leaves every\n * `.gitignore` out, and Bun leaves `bunfig.toml` out too.\n */\nexport const RENAMED: Readonly<Record<string, string>> = {\n\tgitignore: '.gitignore',\n\t'_bunfig.toml': 'bunfig.toml',\n};\n\n/**\n * The template in `dir`: its manifest, named `name` with alxia's packages at\n * `alxia`'s ranges, and every other file, by the path it is written at.\n */\nexport async function reactRouterTemplate(\n\tdir: string,\n\tname: string,\n\talxia: Record<AlxiaPackage, string>,\n): Promise<{ manifest: Manifest; files: Record<string, Blob> }> {\n\tconst stored: Manifest = await Bun.file(join(dir, 'package.json')).json();\n\tconst dependencies = { ...stored.dependencies };\n\tfor (const pkg of Object.keys(dependencies)) {\n\t\tif (pkg in alxia) dependencies[pkg] = alxia[pkg as AlxiaPackage];\n\t}\n\tconst manifest: Manifest = { ...stored, name, dependencies };\n\tconst files: Record<string, Blob> = {};\n\tfor await (const path of new Bun.Glob('**').scan({ cwd: dir, dot: true })) {\n\t\t// A Finder file in a local checkout is not the template's.\n\t\tif (path === 'package.json' || path.endsWith('.DS_Store')) continue;\n\t\tfiles[RENAMED[path] ?? path] = Bun.file(join(dir, path));\n\t}\n\treturn { manifest, files };\n}\n",
13
12
  "/** Where `create-alxia` prints, asks and runs: the process's own, or a spec's. */\n\nexport interface Io {\n\treadonly out: (line: string) => void;\n\treadonly err: (line: string) => void;\n\t/**\n\t * An answer to `question`, `fallback` when empty; undefined when there is\n\t * no terminal to ask in, null when the input ended (Ctrl-D) unanswered.\n\t */\n\treadonly ask: (\n\t\tquestion: string,\n\t\tfallback: string,\n\t) => string | null | undefined;\n\t/** Runs a command in `cwd` with the terminal's streams; its exit code. */\n\treadonly run: (command: readonly string[], cwd: string) => Promise<number>;\n\treadonly env: Record<string, string | undefined>;\n\t/** The registry's `fetch`, for a spec's fake one. */\n\treadonly fetch?: (request: Request) => Promise<Response>;\n}\n\n/** The process's own `Io`: its streams, `prompt()`, `Bun.spawn`. */\nexport function processIo(): Io {\n\treturn {\n\t\tout: (line) => console.log(line),\n\t\terr: (line) => console.error(line),\n\t\task: (question, fallback) => {\n\t\t\tif (!process.stdin.isTTY) return undefined;\n\t\t\t// Bun's prompt() prints the default as `[fallback]`, gives it for an\n\t\t\t// empty line, and null at the end of the input.\n\t\t\tconst answer = prompt(question, fallback);\n\t\t\treturn answer === null ? null : answer.trim() || fallback;\n\t\t},\n\t\trun: async (command, cwd) =>\n\t\t\tBun.spawn([...command], { cwd, stdio: ['inherit', 'inherit', 'inherit'] })\n\t\t\t\t.exited,\n\t\tenv: process.env,\n\t};\n}\n",
14
13
  "#!/usr/bin/env bun\n/**\n * `create-alxia`, the package's bin: `bun create @alxia` runs it, as do\n * `bunx @alxia/create` and `npm create @alxia`.\n */\nimport { main, processIo } from './main';\n\nprocess.exitCode = await main(\n\tprocess.argv.slice(2),\n\tprocess.cwd(),\n\tprocessIo(),\n);\n"
15
14
  ],
16
- "mappings": "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",
17
- "debugId": "793DE037BCA1C3D464756E2164756E21",
15
+ "mappings": ";;;;AAKA,iBAAS;AACT;;;ACHO,IAAM,OAAO;AAGb,IAAM,YAAY,CAAC,OAAO,cAAc;AAGxC,SAAS,UAAU,CAAC,OAAkC;AAAA,EAC5D,OAAQ,UAAgC,SAAS,KAAK;AAAA;AAYhD,SAAS,SAAS,CACxB,MACuC;AAAA,EACvC,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI,UAAU;AAAA,EACd,IAAI,OAAO;AAAA,EACX,SAAS,IAAI,EAAG,IAAI,KAAK,QAAQ,KAAK;AAAA,IACrC,MAAM,MAAM,KAAK;AAAA,IACjB,IAAI,QAAQ,YAAY,QAAQ;AAAA,MAAM,OAAO;AAAA,IACxC,SAAI,QAAQ;AAAA,MAAgB,UAAU;AAAA,IACtC,SACJ,QAAQ,gBACR,QAAQ,QACR,IAAI,WAAW,aAAa,GAC3B;AAAA,MACD,MAAM,QAAQ,IAAI,SAAS,GAAG,IAC3B,IAAI,MAAM,IAAI,QAAQ,GAAG,IAAI,CAAC,IAC9B,KAAK,EAAE;AAAA,MACV,IAAI,UAAU,aAAa,UAAU,IAAI;AAAA,QACxC,OAAO,EAAE,OAAO,GAAG,yBAAyB,UAAU,KAAK,MAAM,KAAK;AAAA,MACvE;AAAA,MACA,IAAI,CAAC,WAAW,KAAK,GAAG;AAAA,QACvB,OAAO;AAAA,UACN,OAAO,oBAAoB,cAAc,UAAU,KAAK,MAAM;AAAA,QAC/D;AAAA,MACD;AAAA,MACA,WAAW;AAAA,IACZ,EAAO,SAAI,IAAI,WAAW,GAAG;AAAA,MAAG,OAAO,EAAE,OAAO,kBAAkB,OAAO;AAAA,IACpE,SAAI,QAAQ;AAAA,MAAW,MAAM;AAAA,IAC7B;AAAA,aAAO,EAAE,OAAO,6BAA6B,WAAW,OAAO;AAAA,EACrE;AAAA,EACA,OAAO;AAAA,OACF,QAAQ,YAAY,CAAC,IAAI,EAAE,IAAI;AAAA,OAC/B,aAAa,YAAY,CAAC,IAAI,EAAE,SAAS;AAAA,IAC7C;AAAA,IACA;AAAA,EACD;AAAA;;;AC1DD;AACA;AAGO,SAAS,WAAW,CAAC,KAAqB;AAAA,EAChD,MAAM,OAAO,SAAS,GAAG,EACvB,YAAY,EACZ,QAAQ,mBAAmB,GAAG,EAC9B,QAAQ,gBAAgB,EAAE;AAAA,EAC5B,OAAO,QAAQ;AAAA;AAIhB,eAAsB,OAAO,CAC5B,QACA,QAAgB,QACc;AAAA,EAC9B,MAAM,OAAO,MAAM,KAAK,MAAM,EAAE,MAAM,MAAG;AAAA,IAAG;AAAA,GAAS;AAAA,EACrD,IAAI,CAAC;AAAA,IAAM;AAAA,EACX,IAAI,CAAC,KAAK,YAAY;AAAA,IAAG,OAAO,GAAG;AAAA,EACnC,MAAM,WAAW,MAAM,QAAQ,MAAM,GAAG,KAAK;AAAA,EAC7C,IAAI,QAAQ,WAAW;AAAA,IAAG;AAAA,EAC1B,OACC,GAAG,uBAAuB,QAAQ,MAAM,GAAG,CAAC,EAAE,KAAK,IAAI,IAAI,QAAQ,SAAS,IAAI,UAAU,UAC1F;AAAA;AAKF,eAAsB,KAAK,CAAC,QAAgB,SAAiC;AAAA,EAC5E,IAAI,CAAC,SAAS;AAAA,IACb,MAAM,GAAG,QAAQ,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,IACjD;AAAA,EACD;AAAA,EACA,WAAW,SAAS,MAAM,QAAQ,MAAM,EAAE,MAAM,MAAM,CAAC,CAAC,GAAG;AAAA,IAC1D,MAAM,GAAG,KAAK,QAAQ,KAAK,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC/D;AAAA;;;AC/BD;AAQA,IAAM,iBAA0C;AAAA,EAC/C;AAAA,EACA;AAAA,EACA;AACD;AAUO,IAAM,cAAc;AAAA,EAC1B,YAAY;AAAA,EACZ,KAAK;AAAA,EACL,gBAAgB;AAAA,EAChB,MAAM;AACP;AASA,eAAsB,WAAW,CAChC,cAAmB,IAAI,IAAI,mBAAmB,YAAY,GAAG,GACrB;AAAA,EACxC,MAAM,WAAW,MAAM,IAAI,KAAK,WAAW,EAAE,KAAK;AAAA,EAClD,MAAM,WAAmC,SAAS,mBAAmB,CAAC;AAAA,EACtE,MAAM,SAAS,CAAC;AAAA,EAChB,WAAW,QAAQ,gBAAgB;AAAA,IAClC,MAAM,QAAQ,SAAS;AAAA,IACvB,IAAI,UAAU,WAAW;AAAA,MACxB,MAAM,IAAI,MAAM,4CAA4C,MAAM;AAAA,IACnE;AAAA,IACA,OAAO,QAAQ,MAAM,WAAW,YAAY,IACzC,MAAM,eAAe,MAAM,OAAO,QAAQ,YAAY,QAAQ,CAAC,IAC/D;AAAA,EACJ;AAAA,EACA,OAAO;AAAA;AAIR,eAAe,cAAc,CAC5B,MACA,OACA,MACkB;AAAA,EAClB,MAAM,OAAO,IAAI,YAAY,GAAG,qBAAqB,IAAI;AAAA,EACzD,QAAQ,YAAY,MAAM,IAAI,KAAK,IAAI,EAAE,KAAK;AAAA,EAC9C,MAAM,SAAS,MAAM,MAAM,aAAa,MAAM;AAAA,EAC9C,OAAO,WAAW,MAAM,UAAU,GAAG,SAAS;AAAA;;;ACnE/C,iBAAS;;;ACKT,iBAAS;AACT;AAUO,IAAM,aAAY,cACxB,IAAI,IAAI,gBAAgB,YAAY,GAAG,CACxC;AAOO,IAAM,UAA4C;AAAA,EACxD,WAAW;AAAA,EACX,gBAAgB;AACjB;AAMA,eAAsB,YAAY,CACjC,UACA,MACA,OACA,OAAe,YACgD;AAAA,EAC/D,MAAM,MAAM,MAAK,MAAM,QAAQ;AAAA,EAC/B,MAAM,SAAmB,MAAM,IAAI,KAAK,MAAK,KAAK,cAAc,CAAC,EAAE,KAAK;AAAA,EACxE,MAAM,WAAqB,KAAK,QAAQ,KAAK;AAAA,EAC7C,WAAW,SAAS,CAAC,gBAAgB,iBAAiB,GAAY;AAAA,IACjE,MAAM,OAAO,OAAO;AAAA,IACpB,IAAI,SAAS;AAAA,MAAW;AAAA,IACxB,SAAS,SAAS,OAAO,YACxB,OAAO,QAAQ,IAAI,EAAE,IAAI,EAAE,KAAK,WAAW;AAAA,MAC1C;AAAA,MACA,OAAO,OAAO,OAAO,GAAG,IAAI,MAAM,OAAuB;AAAA,IAC1D,CAAC,CACF;AAAA,EACD;AAAA,EAEA,MAAM,OAAO,KAAK,UAAU,QAAQ,EAAE,MAAM,uBAAuB;AAAA,EACnE,IAAI,MAAM;AAAA,IACT,MAAM,IAAI,MACT,OAAO,2BAA2B,KAAK,+DACxC;AAAA,EACD;AAAA,EACA,MAAM,QAA8B,CAAC;AAAA,EACrC,iBAAiB,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,KAAK,EAAE,KAAK,KAAK,KAAK,KAAK,CAAC,GAAG;AAAA,IAE1E,IAAI,SAAS,kBAAkB,KAAK,SAAS,WAAW;AAAA,MAAG;AAAA,IAC3D,MAAM,QAAQ,SAAS,QAAQ,IAAI,KAAK,MAAK,KAAK,IAAI,CAAC;AAAA,EACxD;AAAA,EACA,OAAO,EAAE,UAAU,MAAM;AAAA;;;ACxBnB,SAAS,WAAW,CAAC,KAAiD;AAAA,EAC5E,MAAM,MACL,IAAI,0BACJ,IAAI,0BACJ;AAAA,EACD,OAAO,IAAI,QAAQ,QAAQ,EAAE;AAAA;AAQvB,SAAS,YAAY,CAAC,MAAkC;AAAA,EAC9D,IAAI,SAAS,kBAAkB,KAAK,WAAW,gBAAgB,GAAG;AAAA,IACjE,OAAO,YAAY;AAAA,EACpB;AAAA,EACA,OAAO,QAAQ,cACZ,YAAY,QACZ;AAAA;AASG,SAAS,YAAY,CAC3B,UACA,OACqB;AAAA,EACrB,OAAO,SACL,OAAO,CAAC,MAAM,CAAC,EAAE,SAAS,GAAG,KAAK,IAAI,OAAO,UAAU,GAAG,KAAK,CAAC,EAChE,KAAK,IAAI,OAAO,KAAK,EACrB,GAAG,EAAE;AAAA;AAGR,eAAe,SAAS,CAAC,MAAc,UAAwC;AAAA,EAC9E,MAAM,OAAO,SAAS,UAAU,CAAC,YAAqB,MAAM,OAAO;AAAA,EACnE,MAAM,WAAW,MAAM,KACtB,IAAI,QAAQ,GAAG,SAAS,OAAO,KAAK,QAAQ,KAAK,KAAK,KAAK;AAAA,IAC1D,SAAS,EAAE,QAAQ,sCAAsC;AAAA,IACzD,QAAQ,YAAY,QAAQ,SAAS,WAAW,IAAI;AAAA,EACrD,CAAC,CACF;AAAA,EACA,IAAI,CAAC,SAAS;AAAA,IAAI,MAAM,IAAI,MAAM,GAAG,SAAS,QAAQ;AAAA,EACtD,OAAQ,MAAM,SAAS,KAAK;AAAA;AAI7B,SAAS,MAAM,CACd,UACA,OACA,QACqB;AAAA,EACrB,IAAI,UAAU;AAAA,IAAW,OAAO,aAAa,UAAU,KAAK;AAAA,EAC5D,OAAO,UAAU,SAAS,SAAS,MAAM,IACtC,SACA,aAAa,UAAU,GAAG;AAAA;AAOvB,SAAS,SAAS,CAAC,OAAmC;AAAA,EAC5D,MAAM,QAAQ,oBAAoB,KAAK,KAAK;AAAA,EAC5C,OAAO,QAAQ,IAAI,MAAM,MAAM,MAAM,SAAS;AAAA;AAQxC,SAAS,aAAa,CAC5B,UACA,OACqB;AAAA,EACrB,MAAM,QAAQ,UAAU,KAAK;AAAA,EAC7B,MAAM,QAAQ,gBAAgB,KAAK,KAAK,IAAI;AAAA,EAC5C,MAAM,UAAU,SAAS,aAAa,UAAU,KAAK;AAAA,EACrD,OAAO,WAAW,SAAS,IAAI,OAAO,UAAU,OAAO,IAAI,SAAS,IACjE,UACA;AAAA;AAeJ,eAAsB,gBAAgB,CACrC,UACA,UACA,QAA0C,CAAC,GACzB;AAAA,EAClB,MAAM,SAAS,CAAC,gBAAgB,iBAAiB;AAAA,EACjD,MAAM,QAAQ,OAAO,QAAQ,CAAC,UAAU,OAAO,KAAK,SAAS,UAAU,CAAC,CAAC,CAAC;AAAA,EAC1E,MAAM,UAAU,IAAI,IACnB,MAAM,QAAQ,IACb,MAAM,IACL,OAAO,SACN;AAAA,IACC;AAAA,IACA,MAAM,UAAU,MAAM,QAAQ,EAAE,MAAM,MAAG;AAAA,MAAG;AAAA,KAAS;AAAA,EACtD,CACF,CACD,CACD;AAAA,EACA,MAAM,QAAkB,CAAC;AAAA,EACzB,MAAM,OAAiB,CAAC;AAAA,EACxB,MAAM,SAAmB,CAAC;AAAA,EAC1B,MAAM,YAAsB,CAAC;AAAA,EAC7B,MAAM,SAAmB,CAAC;AAAA,EAC1B,MAAM,SAAS,IAAI;AAAA,EAEnB,MAAM,UAAU,CAAC,GAAG,KAAK,EAAE,KAC1B,CAAC,GAAG,MAAM,OAAO,MAAM,cAAc,IAAI,OAAO,MAAM,cAAc,CACrE;AAAA,EACA,WAAW,QAAQ,SAAS;AAAA,IAC3B,MAAM,QAAQ,OAAO,KAAK,CAAC,MAAM,SAAS,KAAK,UAAU,SAAS;AAAA,IAClE,MAAM,OAAO,SAAS,SAAS;AAAA,IAC/B,MAAM,UAAU,OAAO;AAAA,IACvB,IAAI,CAAC,QAAQ,YAAY;AAAA,MAAW;AAAA,IACpC,MAAM,OAAO,QAAQ,IAAI,IAAI;AAAA,IAC7B,IAAI,CAAC,MAAM,UAAU;AAAA,MACpB,OAAO,KAAK,IAAI;AAAA,MAChB;AAAA,IACD;AAAA,IACA,MAAM,WAAW,OAAO,KAAK,KAAK,QAAQ;AAAA,IAC1C,MAAM,SAAS,MAAM;AAAA,IACrB,MAAM,QAAQ,UAAU,aAAa,IAAI;AAAA,IACzC,MAAM,SAAS,KAAK,eAAe;AAAA,IACnC,MAAM,SAAS,OAAO,IAAI,cAAc;AAAA,IACxC,IAAI,UACH,KAAK,WAAW,gBAAgB,KAAK,UAAU,SAAS,SAAS,MAAM,IACpE,SACA,OAAO,UAAU,OAAO,MAAM;AAAA,IAClC,IAAI,YAAY,aAAa,WAAW,WAAW;AAAA,MAClD,UAAU,cAAc,UAAU,MAAM;AAAA,MACxC,IAAI,YAAY,WAAW;AAAA,QAC1B,OAAO,KACN,GAAG,4CAA4C,qBAAqB,0BAA0B,UAAU,MAAM,GAC/G;AAAA,MACD;AAAA,IACD;AAAA,IACA,IAAI,YAAY,WAAW;AAAA,MAC1B,UAAU,KACT,GAAG,2BAA2B,SAAS,qBAAqB,SAC7D;AAAA,MACA;AAAA,IACD;AAAA,IACA,OAAO,IAAI,MAAM,OAAO;AAAA,IACxB,IACC,UAAU,aACV,UACA,IAAI,OAAO,MAAM,QAAQ,OAAO,IAAI,KACpC,CAAC,IAAI,OAAO,UAAU,QAAQ,KAAK,GAClC;AAAA,MACD,KAAK,KACJ,GAAG,iBAAiB,8BAA8B,0BAA0B,uBAC7E;AAAA,IACD;AAAA,IACA,MAAM,OAAO,IAAI;AAAA,IACjB,IAAI,SAAS;AAAA,MAAS,MAAM,KAAK,GAAG,QAAQ,cAAc,MAAM;AAAA,IAChE,KAAK,QAAQ;AAAA,EACd;AAAA,EACA,OAAO,EAAE,OAAO,MAAM,QAAQ,WAAW,OAAO;AAAA;;;AFjNjD,eAAsB,KAAK,CAC1B,QACA,UACA,OACA,IACgB;AAAA,EAChB,QAAQ,UAAU,UAAU,MAAM,aACjC,UACA,YAAY,MAAM,GAClB,KACD;AAAA,EAEA,GAAG,IAAI,gDAAgD;AAAA,EACvD,MAAM,SAAS,MAAM,iBACpB,UACA,EAAE,KAAK,YAAY,GAAG,GAAG,MAAO,GAAG,QAAQ,EAAE,OAAO,GAAG,MAAM,IAAI,CAAC,EAAG,GACrE,KACD;AAAA,EACA,WAAW,QAAQ,OAAO;AAAA,IAAO,GAAG,IAAI,KAAK,MAAM;AAAA,EACnD,WAAW,QAAQ,CAAC,GAAG,OAAO,MAAM,GAAG,OAAO,WAAW,GAAG,OAAO,MAAM;AAAA,IACxE,GAAG,IAAI,KAAK,MAAM;AAAA,EACnB,IAAI,OAAO,OAAO,SAAS,GAAG;AAAA,IAC7B,GAAG,IACF,GAAG,kDAAkD,OAAO,OAAO,KAAK,IAAI,QAC3E,uCACF;AAAA,EACD;AAAA,EAEA,YAAY,MAAM,YAAY,OAAO,QAAQ,KAAK,GAAG;AAAA,IACpD,MAAM,IAAI,MAAM,MAAK,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC5C;AAAA,EACA,MAAM,IAAI,MACT,MAAK,QAAQ,cAAc,GAC3B,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC;AAAA,CACpC;AAAA,EACA,OAAO;AAAA;;AGzBD,SAAS,SAAS,GAAO;AAAA,EAC/B,OAAO;AAAA,IACN,KAAK,CAAC,SAAS,QAAQ,IAAI,IAAI;AAAA,IAC/B,KAAK,CAAC,SAAS,QAAQ,MAAM,IAAI;AAAA,IACjC,KAAK,CAAC,UAAU,aAAa;AAAA,MAC5B,IAAI,CAAC,QAAQ,MAAM;AAAA,QAAO;AAAA,MAG1B,MAAM,SAAS,OAAO,UAAU,QAAQ;AAAA,MACxC,OAAO,WAAW,OAAO,OAAO,OAAO,KAAK,KAAK;AAAA;AAAA,IAElD,KAAK,OAAO,SAAS,QACpB,IAAI,MAAM,CAAC,GAAG,OAAO,GAAG,EAAE,KAAK,OAAO,CAAC,WAAW,WAAW,SAAS,EAAE,CAAC,EACvE;AAAA,IACH,KAAK,QAAQ;AAAA,EACd;AAAA;;;APlBM,IAAM,QAAQ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYrB,eAAsB,IAAI,CACzB,MACA,KACA,IACkB;AAAA,EAClB,MAAM,UAAU,UAAU,IAAI;AAAA,EAC9B,IAAI,WAAW,SAAS;AAAA,IACvB,GAAG,IAAI,GAAG,SAAS,QAAQ;AAAA;AAAA,EAAY,OAAO;AAAA,IAC9C,OAAO;AAAA,EACR;AAAA,EACA,IAAI,QAAQ,MAAM;AAAA,IACjB,GAAG,IAAI,KAAK;AAAA,IACZ,OAAO;AAAA,EACR;AAAA,EAEA,MAAM,MACL,QAAQ,OAAO,GAAG,IAAI,gCAAgC,WAAW;AAAA,EAClE,IAAI,QAAQ,MAAM;AAAA,IACjB,GAAG,IAAI,GAAG,mCAAmC;AAAA,IAC7C,OAAO;AAAA,EACR;AAAA,EACA,IAAI,QAAQ,WAAW;AAAA,IACtB,GAAG,IACF,GAAG;AAAA;AAAA,EAA2D,OAC/D;AAAA,IACA,OAAO;AAAA,EACR;AAAA,EACA,MAAM,WAAW,QAAQ,YAAY,YAAY,EAAE;AAAA,EACnD,IAAI,aAAa,MAAM;AAAA,IACtB,GAAG,IAAI,GAAG,mCAAmC;AAAA,IAC7C,OAAO;AAAA,EACR;AAAA,EACA,IAAI,OAAO,aAAa,UAAU;AAAA,IACjC,GAAG,IAAI,GAAG,SAAS,SAAS;AAAA;AAAA,EAAY,OAAO;AAAA,IAC/C,OAAO;AAAA,EACR;AAAA,EAEA,MAAM,SAAS,QAAQ,KAAK,GAAG;AAAA,EAC/B,MAAM,UAAU,MAAM,QAAQ,QAAQ,GAAG;AAAA,EACzC,IAAI,SAAS;AAAA,IACZ,GAAG,IAAI,GAAG,SAAS,SAAS;AAAA,IAC5B,OAAO;AAAA,EACR;AAAA,EAEA,MAAM,UAAW,MAAM,MAAK,MAAM,EAAE,MAAM,MAAG;AAAA,IAAG;AAAA,GAAS,MAAO;AAAA,EAChE,MAAM,OAAO,MAAM,YAAY,EAC7B,KAAK,CAAC,UAAU,MAAM,QAAQ,UAAU,OAAO,EAAE,CAAC,EAClD,MAAM,OAAO,UAAU;AAAA,IAEvB,MAAM,MAAM,QAAQ,OAAO;AAAA,IAC3B,GAAG,IACF,GAAG,iBAAiB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAC1E;AAAA,IACA,OAAO;AAAA,GACP;AAAA,EACF,IAAI,CAAC;AAAA,IAAM,OAAO;AAAA,EAElB,IAAI,YAAY;AAAA,EAChB,IAAI,QAAQ,SAAS;AAAA,IACpB,GAAG,IAAI;AAAA,4CAA+C;AAAA,IACtD,YAAa,MAAM,GAAG,IAAI,CAAC,QAAQ,UAAU,SAAS,GAAG,MAAM,MAAO;AAAA,IACtE,IAAI,CAAC;AAAA,MACJ,GAAG,IAAI,GAAG,kDAAkD;AAAA,EAC9D;AAAA,EACA,GAAG,IACF,KAAK,SAAS,KAAK,MAAM,KAAK,KAAK,UAAU,QAAQ,WAAW,SAAS,CAC1E;AAAA,EACA,OAAO,YAAY,IAAI;AAAA;AAIxB,SAAS,IAAI,CAAC,OAAe,UAAoB,WAA4B;AAAA,EAC5E,MAAM,QAAQ;AAAA,IACb,UAAU,MAAM,YAAY,MAAM;AAAA,IAClC,YAAY,YAAY;AAAA,IACxB;AAAA,EACD,EAAE,OAAO,CAAC,SAAS,SAAS,SAAS;AAAA,EACrC,OAAO;AAAA,QAAW,UAAU,MAAM,mBAAmB,mBAAmB;AAAA;AAAA,EAA+B,MACrG,IAAI,CAAC,SAAS,KAAK,MAAM,EACzB,KAAK;AAAA,CAAI;AAAA;AAAA;AAIZ,SAAS,WAAW,CAAC,IAA6C;AAAA,EACjE,MAAM,SAAS,GAAG,IAAI,mBAAmB,UAAU,KAAK,MAAM,KAAK,KAAK;AAAA,EACxE,IAAI,WAAW;AAAA,IAAM,OAAO;AAAA,EAC5B,IAAI,WAAW,WAAW;AAAA,IACzB,OAAO,EAAE,OAAO,kDAAkD;AAAA,EACnE;AAAA,EACA,OAAO,WAAW,MAAM,IACrB,SACA,EAAE,OAAO,oBAAoB,eAAe,UAAU,KAAK,MAAM,KAAK;AAAA;;;AQlH1E,QAAQ,WAAW,MAAM,KACxB,QAAQ,KAAK,MAAM,CAAC,GACpB,QAAQ,IAAI,GACZ,UAAU,CACX;",
16
+ "debugId": "DC169542838EEB9F64756E2164756E21",
18
17
  "names": []
19
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"AAAA,oFAAoF;AAIpF,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAC7C,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAK/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAW/C,wCAAwC;AACxC,wBAAsB,KAAK,CAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,EACnC,EAAE,EAAE,EAAE,GACJ,OAAO,CAAC,IAAI,CAAC,CAoCf"}
1
+ {"version":3,"file":"write.d.ts","sourceRoot":"","sources":["../src/write.ts"],"names":[],"mappings":"AAAA,oFAAoF;AAGpF,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAG/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,wCAAwC;AACxC,wBAAsB,KAAK,CAC1B,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,EACnC,EAAE,EAAE,EAAE,GACJ,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
package/docs/README.md CHANGED
@@ -7,6 +7,6 @@ the command prints.
7
7
 
8
8
  | Page | Read it when |
9
9
  | --- | --- |
10
- | [Guide](guide.md) | choosing a template, reading what it wrote, pinning or moving versions, or running the command in a script or CI |
10
+ | [Guide](guide.md) | choosing a template, reading what it wrote, building its Docker image, pinning or moving versions, or running the command in a script or CI |
11
11
  | [Troubleshooting](troubleshooting.md) | the command refused a directory, a template or an option, the registry did not answer or lacked a version, or `bun install` failed |
12
12
  | [Roadmap](roadmap.md) | wondering what is coming, and what is not planned |
package/docs/guide.md CHANGED
@@ -6,6 +6,7 @@ chooses the versions it writes.
6
6
  - [Running it](#running-it)
7
7
  - [The `api` template](#the-api-template)
8
8
  - [The `react-router` template](#the-react-router-template)
9
+ - [Docker](#docker)
9
10
  - [Versions](#versions)
10
11
  - [In a script or CI](#in-a-script-or-ci)
11
12
 
@@ -50,6 +51,15 @@ directory's name, lowercased, with `-` for anything npm refuses.
50
51
  to the package `@scope/create`, and npm maps `npm create @scope` the same
51
52
  way. Any of the three runs the same bin, on Bun.
52
53
 
54
+ Both templates are files shipped in this package, under
55
+ `templates/api/` and `templates/react-router/`, and copied as they are:
56
+ nothing is downloaded but the dependencies, and only `package.json` is
57
+ written again, with the directory's name, alxia's versions and the newest
58
+ of the others ([Versions](#versions)). Two files are stored under another
59
+ name, since `bun publish` leaves them out of a tarball, and take theirs
60
+ back on the copy: `gitignore` is written as `.gitignore`, `_bunfig.toml`
61
+ as `bunfig.toml`.
62
+
53
63
  ## The `api` template
54
64
 
55
65
  ```
@@ -60,6 +70,9 @@ my-api/
60
70
  │ └── server.ts app.listen(PORT)
61
71
  ├── package.json
62
72
  ├── tsconfig.json
73
+ ├── Dockerfile bun run build, then dist/ alone, on oven/bun:1
74
+ ├── .dockerignore
75
+ ├── .env.example PORT and API_KEY, for a .env Bun loads
63
76
  ├── .gitignore
64
77
  └── README.md
65
78
  ```
@@ -126,8 +139,25 @@ The scripts:
126
139
  | `bun dev` | `bun --watch src/server.ts`: restarted on every change, on `PORT` or 3000 |
127
140
  | `bun test` | the spec |
128
141
  | `bun run typecheck` | `tsc --noEmit` |
129
- | `bun run build` | `bun build src/server.ts --target=bun --outdir=dist`: one file, its dependencies bundled |
130
- | `bun start` | `bun dist/server.js` |
142
+ | `bun run build` | `bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked`: one file, its dependencies bundled |
143
+ | `bun start` | `bun dist/server.js`: the build, after `bun run build` |
144
+
145
+ `start` runs what `build` wrote, as production and the image do:
146
+
147
+ ```sh
148
+ bun run build && bun start
149
+ ```
150
+
151
+ `dist/server.js` holds every dependency, so it runs on a host that has Bun
152
+ and no `node_modules`. It is minified, and `dist/server.js.map` beside it
153
+ is linked from it: Bun reads the map, so a stack trace names the lines of
154
+ `src/`. `bun dev` and `bun test` run the TypeScript as it is, with no
155
+ build.
156
+
157
+ Bun loads `.env` on every command. `.env.example` names the two variables
158
+ the app reads, `PORT` (3000 by default) and `API_KEY` (`dev-key` by
159
+ default, for development only): copy it to `.env`, which `.gitignore`
160
+ keeps out of git and `.dockerignore` out of the image.
131
161
 
132
162
  `tsconfig.json` holds the settings alxia's own packages are checked under:
133
163
  `strict`, and past it `exactOptionalPropertyTypes`,
@@ -198,15 +228,70 @@ bunx alxia-react-router reveal
198
228
  The [`@alxia/react-router` guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md)
199
229
  goes from there.
200
230
 
201
- ### Docker
231
+ ## Docker
232
+
233
+ Each project's `Dockerfile` runs it on Bun, on the official `oven/bun:1`
234
+ image, as the image's non-root `bun` user. The installs are
235
+ `--frozen-lockfile`, from the `bun.lock` the command's `bun install`
236
+ wrote: commit it.
237
+
238
+ Each one builds in a stage of its own, and the image holds the build
239
+ output alone: no `node_modules`, no sources. The dependencies are inside
240
+ the bundle, so the image is the base image and a few hundred kilobytes to
241
+ a few megabytes. A dependency that cannot be bundled is kept external and
242
+ copied in: see
243
+ [troubleshooting](troubleshooting.md#error-cannot-find-package--from-appdistserverjs).
244
+
245
+ ### `api`
246
+
247
+ Two stages: every dependency, installed with
248
+ `bun install --frozen-lockfile`, then `bun run build`, which writes
249
+ `dist/server.js` and its source map; then an image with `dist/` alone,
250
+ running `bun --no-install dist/server.js`, `start`'s command with
251
+ Bun's `--no-install` (not create-alxia's option of the same name), so that a package missing from the bundle fails at
252
+ startup rather than being fetched from npm, written out so that Bun
253
+ is the container's process.
254
+
255
+ ```dockerfile
256
+ FROM oven/bun:1 AS build
257
+ WORKDIR /app
258
+ COPY package.json bun.lock* bunfig.toml* ./
259
+ RUN bun install --frozen-lockfile
260
+ COPY . .
261
+ RUN bun run build
262
+
263
+ FROM oven/bun:1
264
+ WORKDIR /app
265
+ ENV NODE_ENV=production
266
+ COPY --from=build /app/dist ./dist
267
+ USER bun
268
+ EXPOSE 3000
269
+ CMD ["bun", "--no-install", "dist/server.js"]
270
+ ```
271
+
272
+ `.dockerignore` keeps `node_modules`, `dist`, `.env`, the README and the
273
+ specs out of the context.
274
+
275
+ ```sh
276
+ cd my-api
277
+ docker build -t my-api .
278
+ docker run -p 3000:3000 -e API_KEY=change-me my-api
279
+ ```
280
+
281
+ The server listens on `PORT`, 3000 in the image; set `API_KEY`, whose
282
+ default is for development.
283
+
284
+ ### `react-router`
202
285
 
203
- The project's `Dockerfile` builds the app and runs it on Bun, on the
204
- official `oven/bun:1` image, in three stages: the production dependencies,
205
- then every dependency and `bun run build`, then an image with `build/`
206
- and the production `node_modules` alone, running
207
- `bun build/server/index.js`, `start`'s command, as the image's non-root
208
- `bun` user. `.dockerignore` keeps `node_modules`, `build` and
209
- `.react-router` out of the context.
286
+ Two stages: every dependency and `bun run build`, then an image with
287
+ `build/` alone, running `bun --no-install build/server/index.js`,
288
+ `start`'s command with `--no-install`.
289
+ `@alxia/react-router`'s plugin bundles every package into
290
+ `build/server/index.js` under `react-router build`, so `build/` needs no
291
+ `node_modules`
292
+ ([Self-contained](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#self-contained)).
293
+ `.dockerignore` keeps `node_modules`, `build` and `.react-router` out of
294
+ the context.
210
295
 
211
296
  ```sh
212
297
  cd my-site
@@ -214,9 +299,8 @@ docker build -t my-site .
214
299
  docker run -p 3000:3000 my-site
215
300
  ```
216
301
 
217
- The installs are `--frozen-lockfile`, from the `bun.lock` the command's
218
- `bun install` wrote: commit it. The commented file, and what to change to
219
- write files from the container, are in
302
+ The commented file, and what to change to write files from the container,
303
+ are in
220
304
  [`@alxia/react-router`'s guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#docker).
221
305
 
222
306
  ## Versions
package/docs/roadmap.md CHANGED
@@ -30,6 +30,19 @@ Nothing scheduled yet.
30
30
 
31
31
  ### Next release
32
32
 
33
+ - **Every `Dockerfile` builds, and the image holds the build alone.** The
34
+ `api` template's builds `dist/server.js`, bundled, minified and source
35
+ mapped, and runs it with no `node_modules` and no `src/`; `start` runs
36
+ `dist/server.js` after `bun run build`. The `react-router` template's
37
+ copies `build/` alone, which `@alxia/react-router`'s plugin now bundles
38
+ whole. Each image is about 50 MB (api) and 150 MB (react-router)
39
+ smaller.
40
+ - **The `api` template is files, copied, with a `Dockerfile`.** It ships
41
+ under `templates/api/` and is copied as `react-router`'s is. New in it:
42
+ a `Dockerfile` on `oven/bun:1`, `.dockerignore` and `.env.example`.
43
+
44
+ ### 0.1.1
45
+
33
46
  - **The `react-router` template's `Dockerfile` runs on Bun.** It replaces
34
47
  React Router's Node one: multi-stage on `oven/bun:1`, the production
35
48
  dependencies apart, `bun run build`, then `bun build/server/index.js` as
@@ -36,6 +36,8 @@ nothing — the symptom.
36
36
  **After**
37
37
 
38
38
  - [`error: lockfile had changes, but lockfile is frozen`](#error-lockfile-had-changes-but-lockfile-is-frozen)
39
+ - [`error: Module not found "dist/server.js"`](#error-module-not-found-distserverjs)
40
+ - [`error: Cannot find package '…' from '/app/dist/server.js'`](#error-cannot-find-package--from-appdistserverjs)
39
41
  - [The project's `@alxia/*` are older than npm's latest](#the-projects-alxia-are-older-than-npms-latest)
40
42
 
41
43
  ## Before it runs
@@ -158,6 +160,19 @@ written is removed — the target directory is emptied when it was there,
158
160
  removed when it was not. A parent directory the command created for it, as
159
161
  `a/b` for `a/b/my-site`, stays.
160
162
 
163
+ ### `create-alxia: failed: the api template names @alxia/zod at workspace:, which this @alxia/create has no version for`
164
+
165
+ **When:** at once, before anything is written; the template and package
166
+ vary.
167
+
168
+ **Why:** a template names an `@alxia/*` package at `workspace:^`, which
169
+ the command replaces with the version it was published beside, and this
170
+ release has none for that package. It is a broken `@alxia/create`
171
+ release, not your setup.
172
+
173
+ **Fix:** run the previous release, `bunx @alxia/create@<version>`, and
174
+ report it on [GitHub](https://github.com/softistx/alxia/issues).
175
+
161
176
  ### `create-alxia: warning: the registry did not answer for …; kept the versions the template ships.`
162
177
 
163
178
  **When:** a dependency's metadata did not arrive within five seconds, or
@@ -228,7 +243,7 @@ project is complete; only `node_modules` is missing.
228
243
 
229
244
  ### `error: lockfile had changes, but lockfile is frozen`
230
245
 
231
- **When:** `docker build` in a `react-router` project stops at
246
+ **When:** `docker build` in a project stops at
232
247
  `RUN bun install --frozen-lockfile`.
233
248
 
234
249
  **Why:** the project's `Dockerfile` installs exactly what `bun.lock`
@@ -237,9 +252,79 @@ dependency added or changed by hand, without `bun install`, or a `bun.lock` left
237
252
  behind in a clone where `package.json` moved on without it.
238
253
 
239
254
  **Fix:** run `bun install`, commit `bun.lock`, and build again. The other
240
- traps of the image, a write refused to the `bun` user among them, are in
255
+ traps of the `react-router` image, a write refused to the `bun` user among them, are in
241
256
  [`@alxia/react-router`'s troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#eacces-permission-denied-open-app).
242
257
 
258
+ ### `error: Module not found "dist/server.js"`
259
+
260
+ **When:** `bun start` in an `api` project that was never built, or whose
261
+ `dist/` was deleted.
262
+
263
+ **Why:** `start` runs the build, `bun dist/server.js`, as the image does;
264
+ it no longer runs `src/server.ts`. `bun dev` runs the sources.
265
+
266
+ **Fix:** build first:
267
+
268
+ ```sh
269
+ bun run build && bun start
270
+ ```
271
+
272
+ ### `error: Cannot find package '…' from '/app/dist/server.js'`
273
+
274
+ The container stops at startup; in the `react-router` image the path is
275
+ `/app/build/server/index.js`. For a package loaded with `require`, Bun
276
+ writes `Cannot find module '…'`. Or the server starts and a request fails
277
+ with `ENOENT: no such file or directory`.
278
+
279
+ **When:** the image holds the build alone, `dist/` or `build/`, as the
280
+ `Dockerfile` copies it, and a dependency is not inside the bundle: one
281
+ the build was told to leave external, or one that cannot be bundled, a
282
+ native addon (a `.node` file) or a package that reads files of its own
283
+ folder at runtime.
284
+
285
+ The image runs `bun --no-install`, Bun's flag, not create-alxia's
286
+ option of the same name: without the flag, Bun finds no
287
+ `node_modules` and fetches the missing package from npm at startup, at
288
+ whatever version npm has, instead of failing.
289
+
290
+ **Why:** the image has no `node_modules`. `bun run build` bundles every
291
+ dependency into one file, and only what it leaves out must be installed
292
+ beside it.
293
+
294
+ **Fix:** mark the package external, and give the image the production
295
+ dependencies. In `api`, in the `build` script:
296
+
297
+ ```json
298
+ "build": "bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked --external sharp"
299
+ ```
300
+
301
+ In `react-router`, in `vite.config.ts`:
302
+
303
+ ```ts
304
+ export default defineConfig({
305
+ ssr: { external: ['sharp'] },
306
+ plugins: [tailwindcss(), reactRouter(), alxia()],
307
+ });
308
+ ```
309
+
310
+ Then in the `Dockerfile`, a stage for the production dependencies, copied
311
+ beside the build:
312
+
313
+ ```dockerfile
314
+ # Before the build stage.
315
+ FROM oven/bun:1 AS production-dependencies
316
+ WORKDIR /app
317
+ COPY package.json bun.lock* bunfig.toml* ./
318
+ RUN bun install --frozen-lockfile --production
319
+
320
+ # In the final stage, before USER bun.
321
+ COPY --from=production-dependencies /app/node_modules ./node_modules
322
+ ```
323
+
324
+ `grep '^import' dist/server.js` (or `build/server/index.js`) lists what
325
+ the bundle still imports: Node's and Bun's modules, and the packages left
326
+ external.
327
+
243
328
  ### The project's `@alxia/*` are older than npm's latest
244
329
 
245
330
  **Symptom:** a fresh project declares `@alxia/core` at `^0.3.4` while npm
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alxia/create",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Start an alxia app: `bun create @alxia` writes an API with Zod, or React Router's official template served by alxia",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,6 +10,7 @@
10
10
  "files": [
11
11
  "dist",
12
12
  "docs",
13
+ "templates/api",
13
14
  "templates/react-router",
14
15
  "README.md",
15
16
  "package.json",
@@ -29,7 +30,7 @@
29
30
  },
30
31
  "scripts": {
31
32
  "build": "bun run ../../build.ts",
32
- "test": "bun test src",
33
+ "test": "bun test ./src",
33
34
  "typecheck": "tsc --noEmit"
34
35
  },
35
36
  "alxia": {
@@ -40,7 +41,7 @@
40
41
  "devDependencies": {
41
42
  "@alxia/client": "^0.2.1",
42
43
  "@alxia/core": "^0.3.1",
43
- "@alxia/react-router": "^0.3.0",
44
+ "@alxia/react-router": "^0.4.0",
44
45
  "@types/bun": "^1.4.2",
45
46
  "zod": "^4.6.5"
46
47
  }
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ dist
3
+ .env
4
+ .env.local
5
+ README.md
6
+ **/*.spec.ts
@@ -0,0 +1,3 @@
1
+ # Copy to .env, which Bun loads on every command: bun dev, bun test, bun start.
2
+ PORT=3000
3
+ API_KEY=dev-key
@@ -0,0 +1,24 @@
1
+ # Bun bundles the app and its dependencies into dist/server.js, and the
2
+ # image holds dist/ alone: no node_modules, no src/. Pinned to Bun 1, the
3
+ # major alxia supports.
4
+
5
+ # Every dependency, then bun run build: dist/server.js and its source map.
6
+ FROM oven/bun:1 AS build
7
+ WORKDIR /app
8
+ COPY package.json bun.lock* bunfig.toml* ./
9
+ RUN bun install --frozen-lockfile
10
+ COPY . .
11
+ RUN bun run build
12
+
13
+ # The bundle, run as Bun's own non-root user with the start script's
14
+ # command, written out so that Bun is the container's process, and
15
+ # --no-install: with no node_modules, Bun would otherwise fetch a package
16
+ # the build left out from the registry at startup, where this fails. The
17
+ # server listens on PORT (3000); set API_KEY.
18
+ FROM oven/bun:1
19
+ WORKDIR /app
20
+ ENV NODE_ENV=production
21
+ COPY --from=build /app/dist ./dist
22
+ USER bun
23
+ EXPOSE 3000
24
+ CMD ["bun", "--no-install", "dist/server.js"]
@@ -0,0 +1,77 @@
1
+ # An alxia API
2
+
3
+ An [alxia](https://github.com/softistx/alxia) app with
4
+ [Zod](https://zod.dev), made with `bun create @alxia`.
5
+
6
+ - `src/app.ts`: the app. `POST /todos` validates its body with Zod, and
7
+ its own hook, `requireKey`, answers 401 without the `x-api-key` header.
8
+ - `src/server.ts`: listens on `PORT`, 3000 by default.
9
+ - `src/app.spec.ts`: `app.request()` and `@alxia/client`, no port.
10
+
11
+ ## Environment
12
+
13
+ Bun loads `.env` on every command. Copy `.env.example` to `.env` to set
14
+ `PORT` and `API_KEY`. Outside development, set `API_KEY`: its default,
15
+ `dev-key`, is for development only.
16
+
17
+ ```sh
18
+ cp .env.example .env
19
+ ```
20
+
21
+ ## Develop
22
+
23
+ ```sh
24
+ bun dev # http://localhost:3000, restarted on every change
25
+ ```
26
+
27
+ ```sh
28
+ curl -X POST localhost:3000/todos \
29
+ -H 'content-type: application/json' -H 'x-api-key: dev-key' \
30
+ -d '{"title":"Write a route"}'
31
+ ```
32
+
33
+ ## Test
34
+
35
+ ```sh
36
+ bun test # src/app.spec.ts: in process, and through the typed client
37
+ bun run typecheck
38
+ ```
39
+
40
+ ## Build
41
+
42
+ `bun run build` bundles the server and its dependencies into one file,
43
+ `dist/server.js`, minified, with its source map beside it, and `bun start`
44
+ runs it: what production and the image run.
45
+
46
+ ```sh
47
+ bun run build # dist/server.js and dist/server.js.map
48
+ bun start # bun dist/server.js
49
+ ```
50
+
51
+ `bun start` before any build fails with `Module not found
52
+ "dist/server.js"`: build first. `dist/` needs Bun and nothing else, no
53
+ `node_modules`. Bun reads the
54
+ source map, so a stack trace names the lines of `src/`. `bun dev` and
55
+ `bun test` run the TypeScript as it is, with no build.
56
+
57
+ A dependency that cannot be bundled, a native addon, is left out with
58
+ `--external <name>` in the `build` script, and must then be installed
59
+ beside `dist/`.
60
+
61
+ ## Docker
62
+
63
+ The `Dockerfile` builds in a stage of its own, on `oven/bun:1`: it
64
+ installs every dependency with `--frozen-lockfile`, from the `bun.lock`
65
+ that `bun install` wrote (commit it), and runs `bun run build`. The image
66
+ holds `dist/` alone, no `node_modules` and no `src/`, and runs
67
+ `bun --no-install dist/server.js` as its non-root `bun` user: a package
68
+ missing from the bundle fails at startup instead of being fetched from
69
+ npm.
70
+
71
+ ```sh
72
+ docker build -t my-api .
73
+ docker run --rm -p 3000:3000 -e API_KEY=change-me my-api
74
+ ```
75
+
76
+ Next: [Getting started](https://github.com/softistx/alxia/blob/develop/packages/core/docs/guide/getting-started.md)
77
+ and the rest of [`@alxia/core`'s guide](https://github.com/softistx/alxia/tree/develop/packages/core/docs).
@@ -0,0 +1,6 @@
1
+ node_modules
2
+ dist
3
+ *.log
4
+ .DS_Store
5
+ .env
6
+ .env.local
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "my-api",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "bun --watch src/server.ts",
7
+ "build": "bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked",
8
+ "start": "bun dist/server.js",
9
+ "test": "bun test",
10
+ "typecheck": "tsc --noEmit"
11
+ },
12
+ "dependencies": {
13
+ "@alxia/core": "workspace:^",
14
+ "zod": "^4.2.0"
15
+ },
16
+ "devDependencies": {
17
+ "@alxia/client": "workspace:^",
18
+ "@types/bun": "^1.4.2",
19
+ "typescript": "^6.0.3"
20
+ }
21
+ }
@@ -0,0 +1,46 @@
1
+ import { expect, test } from 'bun:test';
2
+ import { client } from '@alxia/client';
3
+ import { apiKey, app } from './app';
4
+
5
+ const json = { 'content-type': 'application/json', 'x-api-key': apiKey };
6
+
7
+ test('creates a todo from JSON', async () => {
8
+ const response = await app.request('/todos', {
9
+ method: 'POST',
10
+ headers: json,
11
+ body: JSON.stringify({ title: 'Write a route' }),
12
+ });
13
+ expect(response.status).toBe(201);
14
+ expect(await response.json()).toEqual({
15
+ id: expect.any(Number),
16
+ title: 'Write a route',
17
+ done: false,
18
+ });
19
+ });
20
+
21
+ test('refuses an empty title with a 400 naming it', async () => {
22
+ const response = await app.request('/todos', {
23
+ method: 'POST',
24
+ headers: json,
25
+ body: JSON.stringify({ title: '' }),
26
+ });
27
+ expect(response.status).toBe(400);
28
+ expect((await response.json()).issues[0].path).toEqual(['title']);
29
+ });
30
+
31
+ test('the typed client reads each status the route answers', async () => {
32
+ // Given the app itself, the client calls its fetch in process.
33
+ const api = client(app, { headers: { 'x-api-key': apiKey } });
34
+ const created = await api.post('/todos', {
35
+ body: { title: 'Call it typed' },
36
+ });
37
+ if (created.status !== 201) throw new Error(`got ${created.status}`);
38
+ expect(created.data.title).toBe('Call it typed'); // data is the Todo schema's type
39
+
40
+ const anonymous = await client(app).post('/todos', {
41
+ body: { title: 'No key' },
42
+ });
43
+ expect(anonymous.status).toBe(401);
44
+ if (anonymous.status === 401)
45
+ expect(anonymous.data.error).toBe('unauthorized');
46
+ });
@@ -0,0 +1,33 @@
1
+ import { alxia, defineHook } from '@alxia/core';
2
+ import { z } from 'zod';
3
+
4
+ const Todo = z.object({ id: z.number(), title: z.string(), done: z.boolean() });
5
+ const NewTodo = z.object({ title: z.string().min(1) });
6
+
7
+ /** Set API_KEY in the environment: this default is for development. */
8
+ export const apiKey = Bun.env['API_KEY'] ?? 'dev-key';
9
+
10
+ // A hook of the routes it is given to: it answers 401 without the key, and
11
+ // that 401 joins the type of each, so the client reads it.
12
+ const requireKey = defineHook(({ request, reply }) =>
13
+ request.headers.get('x-api-key') === apiKey
14
+ ? undefined
15
+ : reply(401, { error: 'unauthorized' as const }),
16
+ );
17
+
18
+ const todos: z.infer<typeof Todo>[] = [];
19
+
20
+ export const app = alxia()
21
+ .decorate({ todos })
22
+ .post(
23
+ '/todos',
24
+ [requireKey],
25
+ { body: NewTodo, response: { 201: Todo } },
26
+ ({ body, todos, reply }) => {
27
+ const todo = { id: todos.length + 1, title: body.title, done: false };
28
+ todos.push(todo);
29
+ return reply.created(todo);
30
+ },
31
+ );
32
+
33
+ export type App = typeof app;
@@ -0,0 +1,4 @@
1
+ import { app } from './app';
2
+
3
+ const server = app.listen(Number(Bun.env['PORT'] ?? 3000));
4
+ console.log(`listening on ${server.url}`);
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "types": ["bun"],
4
+ "lib": ["ESNext", "DOM"],
5
+ "target": "ESNext",
6
+ "module": "ESNext",
7
+ "moduleDetection": "force",
8
+ "moduleResolution": "bundler",
9
+ "verbatimModuleSyntax": true,
10
+ "noEmit": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true,
13
+ "strict": true,
14
+ "noImplicitOverride": true,
15
+ "noImplicitReturns": true,
16
+ "noFallthroughCasesInSwitch": true,
17
+ "noUncheckedIndexedAccess": true,
18
+ "exactOptionalPropertyTypes": true,
19
+ "noPropertyAccessFromIndexSignature": true,
20
+ "noUnusedLocals": true,
21
+ "noUnusedParameters": true,
22
+ "forceConsistentCasingInFileNames": true
23
+ },
24
+ "include": ["src"]
25
+ }
@@ -1,11 +1,7 @@
1
- # Bun builds the app and runs it: build/server/index.js is alxia's server,
2
- # which runs on Bun. Pinned to Bun 1, the major alxia supports.
3
-
4
- # The production dependencies alone, for the final image.
5
- FROM oven/bun:1 AS production-dependencies
6
- WORKDIR /app
7
- COPY package.json bun.lock* bunfig.toml* ./
8
- RUN bun install --frozen-lockfile --production
1
+ # Bun builds the app, and the image holds build/ alone: alxia's plugin
2
+ # bundles every dependency into build/server/index.js, alxia's server, which
3
+ # runs on Bun with no node_modules. Pinned to Bun 1, the major alxia
4
+ # supports.
9
5
 
10
6
  # Every dependency, then react-router build: build/client and
11
7
  # build/server/index.js.
@@ -16,14 +12,15 @@ RUN bun install --frozen-lockfile
16
12
  COPY . .
17
13
  RUN bun run build
18
14
 
19
- # The build and the production dependencies, run as Bun's own non-root
20
- # user. The server listens on PORT (3000) and HOST (0.0.0.0).
15
+ # The build, run as Bun's own non-root user with the start script's
16
+ # command, and --no-install: with no node_modules, Bun would otherwise
17
+ # fetch a package the build left out from the registry at startup,
18
+ # where this fails.
19
+ # The server listens on PORT (3000) and HOST (0.0.0.0).
21
20
  FROM oven/bun:1
22
21
  WORKDIR /app
23
22
  ENV NODE_ENV=production
24
- COPY package.json ./
25
- COPY --from=production-dependencies /app/node_modules ./node_modules
26
23
  COPY --from=build /app/build ./build
27
24
  USER bun
28
25
  EXPOSE 3000
29
- CMD ["bun", "build/server/index.js"]
26
+ CMD ["bun", "--no-install", "build/server/index.js"]
@@ -66,7 +66,7 @@ The containerized application can be deployed to any platform that supports Dock
66
66
 
67
67
  ### DIY Deployment
68
68
 
69
- The build is production-ready: `bun run start` runs `build/server/index.js` on Bun.
69
+ The build is production-ready and self-contained: `bun run start` runs `build/server/index.js` on Bun, with every dependency bundled into it, so `build/` needs no `node_modules`.
70
70
 
71
71
  Make sure to deploy the output of `bun run build`
72
72
 
@@ -1,16 +0,0 @@
1
- /**
2
- * The `api` template: an alxia app with Zod, one validated route behind a
3
- * hook of its own, a spec that calls it in process and through the typed
4
- * client, and the scripts to run, test, typecheck and build it.
5
- */
6
- import type { Manifest } from '../registry';
7
- import type { AlxiaPackage } from '../versions';
8
- /**
9
- * The template's manifest. The versions of `zod`, `typescript` and
10
- * `@types/bun` are where it starts: `bumpDependencies` moves each to the
11
- * registry's newest within alxia's peer ranges.
12
- */
13
- export declare function apiManifest(name: string, alxia: Record<AlxiaPackage, string>): Manifest;
14
- /** The template's files but its manifest, by path. */
15
- export declare function apiFiles(name: string): Record<string, string>;
16
- //# sourceMappingURL=api.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../../src/templates/api.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,wBAAgB,WAAW,CAC1B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,GACjC,QAAQ,CAsBV;AAqJD,sDAAsD;AACtD,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS7D"}
@@ -1,17 +0,0 @@
1
- import type { Manifest } from '../registry';
2
- import type { AlxiaPackage } from '../versions';
3
- /**
4
- * Files the template stores under another name, because a published tarball
5
- * would not hold them: `bun publish`, as `npm publish`, leaves every
6
- * `.gitignore` out, and Bun leaves `bunfig.toml` out too.
7
- */
8
- export declare const RENAMED: Readonly<Record<string, string>>;
9
- /**
10
- * The template in `dir`: its manifest, named `name` with alxia's packages at
11
- * `alxia`'s ranges, and every other file, by the path it is written at.
12
- */
13
- export declare function reactRouterTemplate(dir: string, name: string, alxia: Record<AlxiaPackage, string>): Promise<{
14
- manifest: Manifest;
15
- files: Record<string, Blob>;
16
- }>;
17
- //# sourceMappingURL=react-router.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"react-router.d.ts","sourceRoot":"","sources":["../../src/templates/react-router.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;GAIG;AACH,eAAO,MAAM,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAGpD,CAAC;AAEF;;;GAGG;AACH,wBAAsB,mBAAmB,CACxC,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,GACjC,OAAO,CAAC;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;CAAE,CAAC,CAc9D"}