@alxia/create 0.1.2 → 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
@@ -54,12 +54,15 @@ export const app = alxia()
54
54
  ## Docker
55
55
 
56
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:
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`:
58
60
 
59
- - `api`: the production dependencies, then `src/server.ts` run as it is.
60
- Bun runs TypeScript, so there is no build stage.
61
- - `react-router`: the production dependencies, then `bun run build`, then
62
- `bun build/server/index.js`.
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`.
63
66
 
64
67
  ```sh
65
68
  cd my-api
@@ -76,7 +79,9 @@ docker run -p 3000:3000 my-site
76
79
  Commit the `bun.lock` that `bun install` wrote: the image installs from it
77
80
  with `--frozen-lockfile`. The
78
81
  [guide](https://github.com/softistx/alxia/blob/develop/packages/create/docs/guide.md#docker)
79
- 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.
80
85
 
81
86
  ## Options
82
87
 
package/docs/guide.md CHANGED
@@ -70,7 +70,7 @@ my-api/
70
70
  │ └── server.ts app.listen(PORT)
71
71
  ├── package.json
72
72
  ├── tsconfig.json
73
- ├── Dockerfile the production dependencies and src/, on oven/bun:1
73
+ ├── Dockerfile bun run build, then dist/ alone, on oven/bun:1
74
74
  ├── .dockerignore
75
75
  ├── .env.example PORT and API_KEY, for a .env Bun loads
76
76
  ├── .gitignore
@@ -139,12 +139,20 @@ The scripts:
139
139
  | `bun dev` | `bun --watch src/server.ts`: restarted on every change, on `PORT` or 3000 |
140
140
  | `bun test` | the spec |
141
141
  | `bun run typecheck` | `tsc --noEmit` |
142
- | `bun run build` | `bun build src/server.ts --target=bun --outdir=dist`: one file, its dependencies bundled |
143
- | `bun start` | `bun src/server.ts`: Bun runs the TypeScript as it is, no build first |
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
144
 
145
- `build` is for a host that has Bun and no `node_modules`: `dist/server.js`
146
- holds the dependencies, and runs as `bun dist/server.js`. `start`, `bun dev`
147
- and the image need no build.
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.
148
156
 
149
157
  Bun loads `.env` on every command. `.env.example` names the two variables
150
158
  the app reads, `PORT` (3000 by default) and `API_KEY` (`dev-key` by
@@ -227,14 +235,40 @@ image, as the image's non-root `bun` user. The installs are
227
235
  `--frozen-lockfile`, from the `bun.lock` the command's `bun install`
228
236
  wrote: commit it.
229
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
+
230
245
  ### `api`
231
246
 
232
- Two stages: the production dependencies, installed with
233
- `bun install --frozen-lockfile --production`, then an image with them,
234
- `package.json` and `src/`, running `bun src/server.ts`, `start`'s command,
235
- written out so that Bun is the container's process. There is no build
236
- stage: Bun runs the TypeScript as it is, so a build would only add a
237
- second, full install and a bundle the image does not need.
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
+
238
272
  `.dockerignore` keeps `node_modules`, `dist`, `.env`, the README and the
239
273
  specs out of the context.
240
274
 
@@ -249,11 +283,15 @@ default is for development.
249
283
 
250
284
  ### `react-router`
251
285
 
252
- Three stages: the production dependencies, then every dependency and
253
- `bun run build`, then an image with `build/` and the production
254
- `node_modules` alone, running `bun build/server/index.js`, `start`'s
255
- command. `.dockerignore` keeps `node_modules`, `build` and
256
- `.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.
257
295
 
258
296
  ```sh
259
297
  cd my-site
package/docs/roadmap.md CHANGED
@@ -30,12 +30,16 @@ 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.
33
40
  - **The `api` template is files, copied, with a `Dockerfile`.** It ships
34
41
  under `templates/api/` and is copied as `react-router`'s is. New in it:
35
- a `Dockerfile` on `oven/bun:1` that installs the production
36
- dependencies and runs `src/server.ts` as the non-root `bun` user,
37
- `.dockerignore`, `.env.example`, and `start` running the source with no
38
- build first.
42
+ a `Dockerfile` on `oven/bun:1`, `.dockerignore` and `.env.example`.
39
43
 
40
44
  ### 0.1.1
41
45
 
@@ -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
@@ -242,8 +244,7 @@ project is complete; only `node_modules` is missing.
242
244
  ### `error: lockfile had changes, but lockfile is frozen`
243
245
 
244
246
  **When:** `docker build` in a project stops at
245
- `RUN bun install --frozen-lockfile` (`react-router`) or
246
- `RUN bun install --frozen-lockfile --production` (`api`).
247
+ `RUN bun install --frozen-lockfile`.
247
248
 
248
249
  **Why:** the project's `Dockerfile` installs exactly what `bun.lock`
249
250
  records, and `package.json` now asks for something it does not: a
@@ -254,6 +255,76 @@ behind in a clone where `package.json` moved on without it.
254
255
  traps of the `react-router` image, a write refused to the `bun` user among them, are in
255
256
  [`@alxia/react-router`'s troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md#eacces-permission-denied-open-app).
256
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
+
257
328
  ### The project's `@alxia/*` are older than npm's latest
258
329
 
259
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.2",
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",
@@ -41,7 +41,7 @@
41
41
  "devDependencies": {
42
42
  "@alxia/client": "^0.2.1",
43
43
  "@alxia/core": "^0.3.1",
44
- "@alxia/react-router": "^0.3.0",
44
+ "@alxia/react-router": "^0.4.0",
45
45
  "@types/bun": "^1.4.2",
46
46
  "zod": "^4.6.5"
47
47
  }
@@ -1,22 +1,24 @@
1
- # Bun runs the app's TypeScript as it is: no build stage, the image holds
2
- # src/ and the production dependencies. Pinned to Bun 1, the major alxia
3
- # supports.
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
4
 
5
- # The production dependencies alone, for the final image.
6
- FROM oven/bun:1 AS production-dependencies
5
+ # Every dependency, then bun run build: dist/server.js and its source map.
6
+ FROM oven/bun:1 AS build
7
7
  WORKDIR /app
8
8
  COPY package.json bun.lock* bunfig.toml* ./
9
- RUN bun install --frozen-lockfile --production
9
+ RUN bun install --frozen-lockfile
10
+ COPY . .
11
+ RUN bun run build
10
12
 
11
- # The app and its production dependencies, run as Bun's own non-root user
12
- # with the start script's command, written out so that Bun is the
13
- # container's process. The server listens on PORT (3000); set API_KEY.
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.
14
18
  FROM oven/bun:1
15
19
  WORKDIR /app
16
20
  ENV NODE_ENV=production
17
- COPY package.json ./
18
- COPY --from=production-dependencies /app/node_modules ./node_modules
19
- COPY src ./src
21
+ COPY --from=build /app/dist ./dist
20
22
  USER bun
21
23
  EXPOSE 3000
22
- CMD ["bun", "src/server.ts"]
24
+ CMD ["bun", "--no-install", "dist/server.js"]
@@ -39,21 +39,34 @@ bun run typecheck
39
39
 
40
40
  ## Build
41
41
 
42
- Bun runs the TypeScript as it is: `bun start` serves `src/server.ts`, with
43
- no build step. `bun run build` bundles the server and its dependencies into
44
- one file, for a host with Bun and no `node_modules`:
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
45
 
46
46
  ```sh
47
- bun run build # dist/server.js
48
- bun dist/server.js
47
+ bun run build # dist/server.js and dist/server.js.map
48
+ bun start # bun dist/server.js
49
49
  ```
50
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
+
51
61
  ## Docker
52
62
 
53
- The `Dockerfile` installs the production dependencies on `oven/bun:1` and
54
- runs `src/server.ts` as the image's non-root `bun` user. It installs with
55
- `--frozen-lockfile`, from the `bun.lock` that `bun install` wrote: commit
56
- it.
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.
57
70
 
58
71
  ```sh
59
72
  docker build -t my-api .
@@ -4,8 +4,8 @@
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "dev": "bun --watch src/server.ts",
7
- "build": "bun build src/server.ts --target=bun --outdir=dist",
8
- "start": "bun src/server.ts",
7
+ "build": "bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked",
8
+ "start": "bun dist/server.js",
9
9
  "test": "bun test",
10
10
  "typecheck": "tsc --noEmit"
11
11
  },
@@ -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