@alxia/create 0.1.4 → 0.1.5
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 +47 -15
- package/dist/copy.d.ts +11 -3
- package/dist/copy.d.ts.map +1 -1
- package/dist/index.js +23 -5
- package/dist/index.js.map +4 -4
- package/dist/registry.d.ts +11 -1
- package/dist/registry.d.ts.map +1 -1
- package/docs/README.md +2 -2
- package/docs/guide.md +158 -32
- package/docs/roadmap.md +13 -0
- package/docs/troubleshooting.md +67 -0
- package/package.json +1 -1
- package/templates/api/.vscode/extensions.json +3 -0
- package/templates/api/.vscode/settings.json +8 -0
- package/templates/api/README.md +27 -0
- package/templates/api/_biome.json +32 -0
- package/templates/api/package.json +25 -19
- package/templates/api/src/app.spec.ts +38 -38
- package/templates/api/src/app.ts +17 -17
- package/templates/api/src/server.ts +12 -12
- package/templates/api/tsconfig.json +23 -23
- package/templates/react-router/.vscode/extensions.json +3 -0
- package/templates/react-router/.vscode/settings.json +8 -0
- package/templates/react-router/README.md +27 -0
- package/templates/react-router/_biome.json +49 -0
- package/templates/react-router/app/app.css +3 -2
- package/templates/react-router/app/routes/home.tsx +1 -1
- package/templates/react-router/app/routes.ts +1 -1
- package/templates/react-router/package.json +7 -1
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
|
+
- [Lint and format](#lint-and-format)
|
|
9
10
|
- [Docker](#docker)
|
|
10
11
|
- [Versions](#versions)
|
|
11
12
|
- [In a script or CI](#in-a-script-or-ci)
|
|
@@ -45,7 +46,10 @@ What the command line gives is not asked: `bun create @alxia my-app
|
|
|
45
46
|
--template react-router` asks nothing. The directory must be empty or not
|
|
46
47
|
exist yet; `.` writes into the current one, when it is empty, and the next
|
|
47
48
|
steps then start at `bun dev`. The package name in `package.json` is the
|
|
48
|
-
directory's name, lowercased, with `-` for anything npm refuses
|
|
49
|
+
directory's name, lowercased, with `-` for anything npm refuses, and the
|
|
50
|
+
same name replaces the template's own (`my-api`, `my-app`) in its other
|
|
51
|
+
files, as the README's `docker build -t` and `docker run`: `bun create
|
|
52
|
+
@alxia "Mon Super Projet"` writes `mon-super-projet` in both.
|
|
49
53
|
|
|
50
54
|
`bun create @alxia` is `bunx @alxia/create`: Bun maps `bun create @scope`
|
|
51
55
|
to the package `@scope/create`, and npm maps `npm create @scope` the same
|
|
@@ -53,12 +57,15 @@ way. Any of the three runs the same bin, on Bun.
|
|
|
53
57
|
|
|
54
58
|
Both templates are files shipped in this package, under
|
|
55
59
|
`templates/api/` and `templates/react-router/`, and copied as they are:
|
|
56
|
-
nothing is downloaded but the dependencies
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
name,
|
|
60
|
-
back on the copy: `gitignore` is
|
|
61
|
-
as `bunfig.toml
|
|
60
|
+
nothing is downloaded but the dependencies. `package.json` is written
|
|
61
|
+
again, with the directory's name, alxia's versions and the newest of the
|
|
62
|
+
others ([Versions](#versions)); in every other text file, the template's
|
|
63
|
+
own name, as a whole word, becomes the project's. Three files are stored
|
|
64
|
+
under another name and take theirs back on the copy: `gitignore` is
|
|
65
|
+
written as `.gitignore` and `_bunfig.toml` as `bunfig.toml`, since `bun
|
|
66
|
+
publish` leaves those out of a tarball, and `_biome.json` as `biome.json`,
|
|
67
|
+
since alxia's own Biome refuses a second root configuration inside its
|
|
68
|
+
repository ([Lint and format](#lint-and-format)).
|
|
62
69
|
|
|
63
70
|
## The `api` template
|
|
64
71
|
|
|
@@ -70,6 +77,8 @@ my-api/
|
|
|
70
77
|
│ └── server.ts app.listen(PORT), stopped on SIGTERM
|
|
71
78
|
├── package.json
|
|
72
79
|
├── tsconfig.json
|
|
80
|
+
├── biome.json Biome: lint, format, imports sorted
|
|
81
|
+
├── .vscode/ Biome's extension recommended, format on save
|
|
73
82
|
├── Dockerfile bun run build, then dist/ alone, on oven/bun:1-alpine
|
|
74
83
|
├── .dockerignore
|
|
75
84
|
├── .env.example PORT and API_KEY, for a .env Bun loads
|
|
@@ -81,35 +90,35 @@ my-api/
|
|
|
81
90
|
body validated by a Zod schema, a declared reply, and a hook of its own.
|
|
82
91
|
|
|
83
92
|
```ts
|
|
84
|
-
import { alxia, defineHook } from
|
|
85
|
-
import { z } from
|
|
93
|
+
import { alxia, defineHook } from "@alxia/core";
|
|
94
|
+
import { z } from "zod";
|
|
86
95
|
|
|
87
96
|
const Todo = z.object({ id: z.number(), title: z.string(), done: z.boolean() });
|
|
88
97
|
const NewTodo = z.object({ title: z.string().min(1) });
|
|
89
98
|
|
|
90
99
|
/** Set API_KEY in the environment: this default is for development. */
|
|
91
|
-
export const apiKey = Bun.env[
|
|
100
|
+
export const apiKey = Bun.env["API_KEY"] ?? "dev-key";
|
|
92
101
|
|
|
93
102
|
const requireKey = defineHook(({ request, reply }) =>
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
103
|
+
request.headers.get("x-api-key") === apiKey
|
|
104
|
+
? undefined
|
|
105
|
+
: reply(401, { error: "unauthorized" as const }),
|
|
97
106
|
);
|
|
98
107
|
|
|
99
108
|
const todos: z.infer<typeof Todo>[] = [];
|
|
100
109
|
|
|
101
110
|
export const app = alxia()
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
.decorate({ todos })
|
|
112
|
+
.post(
|
|
113
|
+
"/todos",
|
|
114
|
+
[requireKey],
|
|
115
|
+
{ body: NewTodo, response: { 201: Todo } },
|
|
116
|
+
({ body, todos, reply }) => {
|
|
117
|
+
const todo = { id: todos.length + 1, title: body.title, done: false };
|
|
118
|
+
todos.push(todo);
|
|
119
|
+
return reply.created(todo);
|
|
120
|
+
},
|
|
121
|
+
);
|
|
113
122
|
|
|
114
123
|
export type App = typeof app;
|
|
115
124
|
```
|
|
@@ -126,10 +135,10 @@ JSON body, then `@alxia/client` given the app itself, whose result is typed
|
|
|
126
135
|
by status:
|
|
127
136
|
|
|
128
137
|
```ts
|
|
129
|
-
const api = client(app, { headers: {
|
|
130
|
-
const created = await api.post(
|
|
138
|
+
const api = client(app, { headers: { "x-api-key": apiKey } });
|
|
139
|
+
const created = await api.post("/todos", { body: { title: "Call it typed" } });
|
|
131
140
|
if (created.status !== 201) throw new Error(`got ${created.status}`);
|
|
132
|
-
expect(created.data.title).toBe(
|
|
141
|
+
expect(created.data.title).toBe("Call it typed"); // data is the Todo schema's type
|
|
133
142
|
```
|
|
134
143
|
|
|
135
144
|
The scripts:
|
|
@@ -141,6 +150,10 @@ The scripts:
|
|
|
141
150
|
| `bun run typecheck` | `tsc --noEmit` |
|
|
142
151
|
| `bun run build` | `bun build src/server.ts --target=bun --outdir=dist --minify --sourcemap=linked`: one file, its dependencies bundled |
|
|
143
152
|
| `bun start` | `bun dist/server.js`: the build, after `bun run build` |
|
|
153
|
+
| `bun run check` | `biome check --write`: lint, format, sort imports, fixing what it can |
|
|
154
|
+
| `bun run lint`, `bun run format` | `biome lint`, `biome format --write` |
|
|
155
|
+
| `bun run check:ci` | `biome ci`: read-only, for CI |
|
|
156
|
+
| `bun run verify` | `check:ci`, `typecheck`, then `test` |
|
|
144
157
|
|
|
145
158
|
`start` runs what `build` wrote, as production and the image do:
|
|
146
159
|
|
|
@@ -162,9 +175,10 @@ keeps out of git and `.dockerignore` out of the image.
|
|
|
162
175
|
`tsconfig.json` holds the settings alxia's own packages are checked under:
|
|
163
176
|
`strict`, and past it `exactOptionalPropertyTypes`,
|
|
164
177
|
`noUncheckedIndexedAccess`, `noPropertyAccessFromIndexSignature`,
|
|
165
|
-
`noUnusedLocals` and the rest. Hence `Bun.env[
|
|
166
|
-
`Bun.env.API_KEY
|
|
167
|
-
|
|
178
|
+
`noUnusedLocals` and the rest. Hence `Bun.env["API_KEY"]` and not
|
|
179
|
+
`Bun.env.API_KEY`, and Biome's `useLiteralKeys`, which would ask for the
|
|
180
|
+
second, is off in `biome.json`. Loosen what you would rather not keep:
|
|
181
|
+
alxia's types compile under each one, and under none.
|
|
168
182
|
|
|
169
183
|
## The `react-router` template
|
|
170
184
|
|
|
@@ -195,6 +209,19 @@ before `bun install`. The change, against React Router's files:
|
|
|
195
209
|
+ plugins: [tailwindcss(), reactRouter(), alxia()],
|
|
196
210
|
```
|
|
197
211
|
|
|
212
|
+
```diff
|
|
213
|
+
// package.json
|
|
214
|
+
"scripts": {
|
|
215
|
+
+ "lint": "biome lint",
|
|
216
|
+
+ "format": "biome format --write",
|
|
217
|
+
+ "check": "biome check --write",
|
|
218
|
+
+ "check:ci": "biome ci",
|
|
219
|
+
+ "verify": "bun run check:ci && bun run typecheck && bun run build"
|
|
220
|
+
},
|
|
221
|
+
"devDependencies": {
|
|
222
|
+
+ "@biomejs/biome": "2.5.15",
|
|
223
|
+
```
|
|
224
|
+
|
|
198
225
|
```toml
|
|
199
226
|
# bunfig.toml, new
|
|
200
227
|
[run]
|
|
@@ -204,7 +231,12 @@ bun = true
|
|
|
204
231
|
```
|
|
205
232
|
|
|
206
233
|
The `Dockerfile` is alxia's, in place of React Router's, which builds and
|
|
207
|
-
runs on Node ([Docker](#docker)).
|
|
234
|
+
runs on Node ([Docker](#docker)). `biome.json` and `.vscode/` are new,
|
|
235
|
+
and the README gains a Lint and format section
|
|
236
|
+
([Lint and format](#lint-and-format)); Biome formatted the scaffold once,
|
|
237
|
+
which changed three of its files: `app/app.css`'s font list wraps
|
|
238
|
+
differently, and `app/routes.ts` and `app/routes/home.tsx` have their
|
|
239
|
+
imports sorted. Every other file is React Router's:
|
|
208
240
|
`app/`, `public/`, `tsconfig.json`, `react-router.config.ts`, its
|
|
209
241
|
`README.md` (with Bun's commands where it wrote npm's: `bun install`,
|
|
210
242
|
`bun dev`, `bun run build`), `.gitignore` and `.dockerignore`. `package.json` takes the directory's name, and its
|
|
@@ -228,6 +260,98 @@ bunx alxia-react-router reveal
|
|
|
228
260
|
The [`@alxia/react-router` guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md)
|
|
229
261
|
goes from there.
|
|
230
262
|
|
|
263
|
+
## Lint and format
|
|
264
|
+
|
|
265
|
+
Both projects lint and format with [Biome](https://biomejs.dev), in one
|
|
266
|
+
style whichever template wrote them. Their `biome.json` is a root
|
|
267
|
+
configuration of its own, extending nothing:
|
|
268
|
+
|
|
269
|
+
```json
|
|
270
|
+
{
|
|
271
|
+
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
|
272
|
+
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
|
|
273
|
+
"files": { "includes": ["**", "!!**/build", "!!**/.react-router"] },
|
|
274
|
+
"formatter": { "enabled": true, "indentStyle": "space" },
|
|
275
|
+
"css": { "parser": { "tailwindDirectives": true } },
|
|
276
|
+
"linter": {
|
|
277
|
+
"enabled": true,
|
|
278
|
+
"rules": {
|
|
279
|
+
"preset": "recommended",
|
|
280
|
+
"correctness": { "noEmptyPattern": "off" }
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
"assist": {
|
|
284
|
+
"enabled": true,
|
|
285
|
+
"actions": { "source": { "organizeImports": "on" } }
|
|
286
|
+
},
|
|
287
|
+
"overrides": [
|
|
288
|
+
{
|
|
289
|
+
"includes": ["**/app/welcome/**"],
|
|
290
|
+
"linter": { "rules": { "a11y": { "noSvgWithoutTitle": "off" } } }
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
That is the `react-router` project's, written here compact. The `api`
|
|
297
|
+
project's skips `dist/` instead of `build/` and `.react-router/`, has no
|
|
298
|
+
CSS settings and no overrides, and turns `complexity.useLiteralKeys` off
|
|
299
|
+
in place of `noEmptyPattern` ([the `api` template](#the-api-template)).
|
|
300
|
+
|
|
301
|
+
- **The style is Biome's default but for spaces**: two spaces, double
|
|
302
|
+
quotes, 80 columns. React Router's scaffold is written so, and so is
|
|
303
|
+
the `package.json` the command writes: formatting the scaffold once
|
|
304
|
+
changed three of its files, where tabs would change every line. The
|
|
305
|
+
`api` project takes the same style, so the two read alike.
|
|
306
|
+
- **`$schema` is the installed Biome's own schema**, so an editor checks
|
|
307
|
+
the file against the version `bun install` put in `node_modules`,
|
|
308
|
+
whichever the command wrote.
|
|
309
|
+
- **What is generated is skipped.** `files.includes` leaves `dist/`, or
|
|
310
|
+
`build/` and `.react-router/`, out, and `vcs.useIgnoreFile` every path
|
|
311
|
+
`.gitignore` names, the project in a git repository or not (with no
|
|
312
|
+
`.gitignore` and no git repository, Biome refuses to run:
|
|
313
|
+
[troubleshooting](troubleshooting.md#-biome-couldnt-find-an-ignore-file-in-the-following-folder-)).
|
|
314
|
+
- **Two rules are off in the `react-router` project, for the scaffold's
|
|
315
|
+
own code.** `noEmptyPattern`: `meta({}: Route.MetaArgs)` is React
|
|
316
|
+
Router's idiom for a route module's function that reads none of its
|
|
317
|
+
arguments. `noSvgWithoutTitle`, under `app/welcome/` alone: the welcome
|
|
318
|
+
page's logos, which a project replaces.
|
|
319
|
+
- **`css.parser.tailwindDirectives`** lets Biome read Tailwind v4's
|
|
320
|
+
`@import "tailwindcss"` and `@theme` in `app/app.css`.
|
|
321
|
+
|
|
322
|
+
`@biomejs/biome` is a devDependency pinned exactly, as Biome recommends,
|
|
323
|
+
since a release may format differently. The command moves it to the
|
|
324
|
+
newest patch of the same minor and keeps it exact: a minor may add a
|
|
325
|
+
recommended rule the template was not checked against
|
|
326
|
+
([Versions](#versions)). To move it later:
|
|
327
|
+
|
|
328
|
+
```sh
|
|
329
|
+
bun add --dev --exact @biomejs/biome@latest
|
|
330
|
+
bunx biome migrate --write
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
| script | runs |
|
|
334
|
+
| --- | --- |
|
|
335
|
+
| `bun run check` | `biome check --write`: lint, format and sort imports, fixing what it can |
|
|
336
|
+
| `bun run lint` | `biome lint` |
|
|
337
|
+
| `bun run format` | `biome format --write` |
|
|
338
|
+
| `bun run check:ci` | `biome ci`: changes nothing, and fails on any error |
|
|
339
|
+
| `bun run verify` | `check:ci`, `typecheck`, then `test` (`api`) or `build` (`react-router`) |
|
|
340
|
+
|
|
341
|
+
The read-only one is `check:ci`, not `ci`: `bun ci` is Bun's
|
|
342
|
+
`bun install --frozen-lockfile`, and a script named `ci` would only run
|
|
343
|
+
as `bun run ci`. A CI job runs:
|
|
344
|
+
|
|
345
|
+
```sh
|
|
346
|
+
bun install --frozen-lockfile
|
|
347
|
+
bun run verify
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`.vscode/extensions.json` recommends Biome's extension, `biomejs.biome`,
|
|
351
|
+
and `.vscode/settings.json` makes it the default formatter, formatting on
|
|
352
|
+
save. Another editor reads `biome.json` through Biome's own extension for
|
|
353
|
+
it.
|
|
354
|
+
|
|
231
355
|
## Docker
|
|
232
356
|
|
|
233
357
|
Each project's `Dockerfile` builds it on Bun's official `oven/bun:1`
|
|
@@ -319,7 +443,8 @@ are in
|
|
|
319
443
|
A template ships the versions it was generated with, and React Router's
|
|
320
444
|
lags behind its own releases. So before installing, the command
|
|
321
445
|
asks the registry for every dependency's versions, and writes `^` the
|
|
322
|
-
newest one alxia accepts
|
|
446
|
+
newest one alxia accepts, or the version alone for a dependency the
|
|
447
|
+
template pins exactly:
|
|
323
448
|
|
|
324
449
|
| dependency | moved to the newest within |
|
|
325
450
|
| --- | --- |
|
|
@@ -328,6 +453,7 @@ newest one alxia accepts:
|
|
|
328
453
|
| `zod` | `^4.2.0`, `@alxia/zod`'s |
|
|
329
454
|
| `vite` | `^7.0.0 \|\| ^8.0.0`, `@alxia/react-router`'s |
|
|
330
455
|
| `react-router`, `@react-router/*` | `^8.0.0`, `@alxia/react-router`'s; the `@react-router/*` packages take `react-router`'s version, which `@react-router/node` pins exactly |
|
|
456
|
+
| `@biomejs/biome` | its own minor, from the exact version the template pins (`~2.5.15`): written exactly, `2.5.16`, never `^` |
|
|
331
457
|
| anything else: `react`, `isbot`, Tailwind, `@types/*` | no alxia range: npm's `latest` |
|
|
332
458
|
|
|
333
459
|
The command prints each move, and each newer major it left out:
|
package/docs/roadmap.md
CHANGED
|
@@ -30,6 +30,19 @@ Nothing scheduled yet.
|
|
|
30
30
|
|
|
31
31
|
### Next release
|
|
32
32
|
|
|
33
|
+
- **Both projects lint and format with Biome.** Each has a `biome.json`
|
|
34
|
+
of its own (recommended rules, spaces and double quotes, imports
|
|
35
|
+
sorted, the build output skipped), `@biomejs/biome` pinned exactly,
|
|
36
|
+
the scripts `lint`, `format`, `check`, `check:ci` and `verify`, and
|
|
37
|
+
`.vscode/` recommending Biome's extension. A new project passes
|
|
38
|
+
`bun run check:ci` with no finding.
|
|
39
|
+
- **The project's name in its README.** The name given to the command,
|
|
40
|
+
normalised, replaces the template's own, as a whole word, in every text
|
|
41
|
+
file, as the README's
|
|
42
|
+
`docker build -t` and `docker run`.
|
|
43
|
+
|
|
44
|
+
### 0.1.4
|
|
45
|
+
|
|
33
46
|
- **The images run on Alpine.** Every `Dockerfile`'s final stage is
|
|
34
47
|
`oven/bun:1-alpine`, the build stages staying on `oven/bun:1`: each
|
|
35
48
|
image is about 130 MB, where it was about 345 MB. The `api` project
|
package/docs/troubleshooting.md
CHANGED
|
@@ -41,6 +41,12 @@ nothing — the symptom.
|
|
|
41
41
|
- [`error: … is linked against glibc (DT_NEEDED libm.so.6), but this Bun build uses musl.`](#error--is-linked-against-glibc-dt_needed-libmso6-but-this-bun-build-uses-musl)
|
|
42
42
|
- [The project's `@alxia/*` are older than npm's latest](#the-projects-alxia-are-older-than-npms-latest)
|
|
43
43
|
|
|
44
|
+
**Biome**
|
|
45
|
+
|
|
46
|
+
- [`bun ci` installs, and checks nothing](#bun-ci-installs-and-checks-nothing)
|
|
47
|
+
- [`× Found a nested root configuration, but there's already a root configuration.`](#-found-a-nested-root-configuration-but-theres-already-a-root-configuration)
|
|
48
|
+
- [`× Biome couldn't find an ignore file in the following folder: …`](#-biome-couldnt-find-an-ignore-file-in-the-following-folder-)
|
|
49
|
+
|
|
44
50
|
## Before it runs
|
|
45
51
|
|
|
46
52
|
### `error: GET https://registry.npmjs.org/@alxia%2fcreate - 404`
|
|
@@ -372,3 +378,64 @@ an existing project
|
|
|
372
378
|
`bun add @alxia/core@latest @alxia/react-router@latest` (`react-router`), reading
|
|
373
379
|
[`@alxia/core`'s upgrading page](https://github.com/softistx/alxia/blob/develop/packages/core/docs/upgrading.md)
|
|
374
380
|
for what a minor changed.
|
|
381
|
+
|
|
382
|
+
## Biome
|
|
383
|
+
|
|
384
|
+
### `bun ci` installs, and checks nothing
|
|
385
|
+
|
|
386
|
+
**Symptom:** `bun ci` in a project prints an install, `bun install
|
|
387
|
+
v1.4.2`, and no Biome output; a CI step meant to lint passes whatever the
|
|
388
|
+
code.
|
|
389
|
+
|
|
390
|
+
**Why:** `bun ci` is Bun's own command, `bun install --frozen-lockfile`,
|
|
391
|
+
and a command Bun knows wins over a script. The projects name their
|
|
392
|
+
read-only check `check:ci` for that reason
|
|
393
|
+
([Lint and format](guide.md#lint-and-format)).
|
|
394
|
+
|
|
395
|
+
**Fix:** run the script by `bun run`:
|
|
396
|
+
|
|
397
|
+
```sh
|
|
398
|
+
bun run check:ci # biome ci alone
|
|
399
|
+
bun run verify # biome ci, typecheck, then test or build
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### `× Found a nested root configuration, but there's already a root configuration.`
|
|
403
|
+
|
|
404
|
+
**When:** Biome runs from a folder above the project that has its own
|
|
405
|
+
`biome.json`: the project was created inside a monorepo, and `biome ci`
|
|
406
|
+
runs from the monorepo's root.
|
|
407
|
+
|
|
408
|
+
**Why:** the project's `biome.json` is a root configuration, so that it
|
|
409
|
+
works on its own. Biome takes one root per run; a configuration below it
|
|
410
|
+
must say it is nested.
|
|
411
|
+
|
|
412
|
+
**Fix:** to keep the monorepo's settings and add the project's, make the
|
|
413
|
+
project's nested, at the top of its `biome.json`:
|
|
414
|
+
|
|
415
|
+
```json
|
|
416
|
+
{
|
|
417
|
+
"root": false,
|
|
418
|
+
"extends": "//",
|
|
419
|
+
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json"
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
and keep the rest of the file. `"extends": "//"` takes the root's
|
|
424
|
+
settings, which the project's own override. To use the monorepo's
|
|
425
|
+
settings alone, delete the project's `biome.json` and its
|
|
426
|
+
`@biomejs/biome`.
|
|
427
|
+
|
|
428
|
+
### `× Biome couldn't find an ignore file in the following folder: …`
|
|
429
|
+
|
|
430
|
+
**When:** `bun run check:ci`, `check`, `lint` or `format` in a project
|
|
431
|
+
with no `.gitignore`, deleted or renamed, and outside any git repository.
|
|
432
|
+
Inside one, Biome reads git's ignore rules and runs.
|
|
433
|
+
|
|
434
|
+
**Why:** `biome.json` sets `vcs.useIgnoreFile`, which skips what
|
|
435
|
+
`.gitignore` names. Inside a git repository Biome falls back on git's
|
|
436
|
+
own ignore rules; with no `.gitignore` and no repository, it refuses to
|
|
437
|
+
run rather than skip nothing.
|
|
438
|
+
|
|
439
|
+
**Fix:** put a `.gitignore` back, even an empty one, or set
|
|
440
|
+
`"useIgnoreFile": false` in `biome.json`'s `vcs`: `files.includes` still
|
|
441
|
+
skips the build output.
|
package/package.json
CHANGED
package/templates/api/README.md
CHANGED
|
@@ -7,6 +7,7 @@ An [alxia](https://github.com/softistx/alxia) app with
|
|
|
7
7
|
its own hook, `requireKey`, answers 401 without the `x-api-key` header.
|
|
8
8
|
- `src/server.ts`: listens on `PORT`, 3000 by default.
|
|
9
9
|
- `src/app.spec.ts`: `app.request()` and `@alxia/client`, no port.
|
|
10
|
+
- `biome.json`: Biome's lint and format settings ([Lint and format](#lint-and-format)).
|
|
10
11
|
|
|
11
12
|
## Environment
|
|
12
13
|
|
|
@@ -37,6 +38,32 @@ bun test # src/app.spec.ts: in process, and through the typed client
|
|
|
37
38
|
bun run typecheck
|
|
38
39
|
```
|
|
39
40
|
|
|
41
|
+
## Lint and format
|
|
42
|
+
|
|
43
|
+
[Biome](https://biomejs.dev) lints and formats the project, as `biome.json`
|
|
44
|
+
sets it: Biome's recommended rules, spaces, double quotes, imports
|
|
45
|
+
sorted. What the build writes, `dist/`, is skipped.
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
bun run check # lint, format and sort imports, fixing what it can
|
|
49
|
+
bun run lint # lint only
|
|
50
|
+
bun run format # format only, in place
|
|
51
|
+
bun run check:ci # what CI runs: changes nothing, fails on an error
|
|
52
|
+
bun run verify # check:ci, then typecheck, then test
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`bun run check:ci`, not `bun ci`: `bun ci` is Bun's frozen-lockfile
|
|
56
|
+
install. In VS Code, `.vscode/` recommends Biome's extension and formats
|
|
57
|
+
on save with it.
|
|
58
|
+
|
|
59
|
+
`@biomejs/biome` is pinned exactly, since a release of Biome may format
|
|
60
|
+
differently. To move it:
|
|
61
|
+
|
|
62
|
+
```sh
|
|
63
|
+
bun add --dev --exact @biomejs/biome@latest
|
|
64
|
+
bunx biome migrate --write
|
|
65
|
+
```
|
|
66
|
+
|
|
40
67
|
## Build
|
|
41
68
|
|
|
42
69
|
`bun run build` bundles the server and its dependencies into one file,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
|
|
3
|
+
"vcs": {
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"clientKind": "git",
|
|
6
|
+
"useIgnoreFile": true
|
|
7
|
+
},
|
|
8
|
+
"files": {
|
|
9
|
+
"includes": ["**", "!!**/dist"]
|
|
10
|
+
},
|
|
11
|
+
"formatter": {
|
|
12
|
+
"enabled": true,
|
|
13
|
+
"indentStyle": "space"
|
|
14
|
+
},
|
|
15
|
+
"linter": {
|
|
16
|
+
"enabled": true,
|
|
17
|
+
"rules": {
|
|
18
|
+
"preset": "recommended",
|
|
19
|
+
"complexity": {
|
|
20
|
+
"useLiteralKeys": "off"
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"assist": {
|
|
25
|
+
"enabled": true,
|
|
26
|
+
"actions": {
|
|
27
|
+
"source": {
|
|
28
|
+
"organizeImports": "on"
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
+
"lint": "biome lint",
|
|
12
|
+
"format": "biome format --write",
|
|
13
|
+
"check": "biome check --write",
|
|
14
|
+
"check:ci": "biome ci",
|
|
15
|
+
"verify": "bun run check:ci && bun run typecheck && bun run test"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@alxia/core": "workspace:^",
|
|
19
|
+
"zod": "^4.2.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@alxia/client": "workspace:^",
|
|
23
|
+
"@biomejs/biome": "2.5.15",
|
|
24
|
+
"@types/bun": "^1.4.2",
|
|
25
|
+
"typescript": "^6.0.3"
|
|
26
|
+
}
|
|
21
27
|
}
|
|
@@ -1,46 +1,46 @@
|
|
|
1
|
-
import { expect, test } from
|
|
2
|
-
import { client } from
|
|
3
|
-
import { apiKey, app } from
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { client } from "@alxia/client";
|
|
3
|
+
import { apiKey, app } from "./app";
|
|
4
4
|
|
|
5
|
-
const json = {
|
|
5
|
+
const json = { "content-type": "application/json", "x-api-key": apiKey };
|
|
6
6
|
|
|
7
|
-
test(
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
19
|
});
|
|
20
20
|
|
|
21
|
-
test(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
29
|
});
|
|
30
30
|
|
|
31
|
-
test(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
46
|
});
|