@ultimat3/cli 27.11.0 → 27.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ultimat3/cli",
3
- "version": "27.11.0",
3
+ "version": "27.12.0",
4
4
  "description": "The `x` binary: new, dev, build, verify, generate, db, mcp, doctor, deploy",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,33 +39,33 @@
39
39
  },
40
40
  "dependencies": {
41
41
  "@babel/core": "^8.0.1",
42
- "@ultimat3/action": "27.11.0",
43
- "@ultimat3/admin": "27.11.0",
44
- "@ultimat3/ai": "27.11.0",
45
- "@ultimat3/auth": "27.11.0",
46
- "@ultimat3/cache": "27.11.0",
47
- "@ultimat3/core": "27.11.0",
48
- "@ultimat3/db": "27.11.0",
49
- "@ultimat3/entity": "27.11.0",
50
- "@ultimat3/flags": "27.11.0",
51
- "@ultimat3/http": "27.11.0",
52
- "@ultimat3/i18n": "27.11.0",
53
- "@ultimat3/jobs": "27.11.0",
54
- "@ultimat3/mail": "27.11.0",
55
- "@ultimat3/manifest": "27.11.0",
56
- "@ultimat3/mcp": "27.11.0",
57
- "@ultimat3/money": "27.11.0",
58
- "@ultimat3/notify": "27.11.0",
59
- "@ultimat3/policy": "27.11.0",
60
- "@ultimat3/pwa": "27.11.0",
61
- "@ultimat3/query": "27.11.0",
62
- "@ultimat3/realtime": "27.11.0",
63
- "@ultimat3/render": "27.11.0",
64
- "@ultimat3/schema": "27.11.0",
65
- "@ultimat3/seo": "27.11.0",
66
- "@ultimat3/storage": "27.11.0",
67
- "@ultimat3/testing": "27.11.0",
68
- "@ultimat3/time": "27.11.0",
42
+ "@ultimat3/action": "27.12.0",
43
+ "@ultimat3/admin": "27.12.0",
44
+ "@ultimat3/ai": "27.12.0",
45
+ "@ultimat3/auth": "27.12.0",
46
+ "@ultimat3/cache": "27.12.0",
47
+ "@ultimat3/core": "27.12.0",
48
+ "@ultimat3/db": "27.12.0",
49
+ "@ultimat3/entity": "27.12.0",
50
+ "@ultimat3/flags": "27.12.0",
51
+ "@ultimat3/http": "27.12.0",
52
+ "@ultimat3/i18n": "27.12.0",
53
+ "@ultimat3/jobs": "27.12.0",
54
+ "@ultimat3/mail": "27.12.0",
55
+ "@ultimat3/manifest": "27.12.0",
56
+ "@ultimat3/mcp": "27.12.0",
57
+ "@ultimat3/money": "27.12.0",
58
+ "@ultimat3/notify": "27.12.0",
59
+ "@ultimat3/policy": "27.12.0",
60
+ "@ultimat3/pwa": "27.12.0",
61
+ "@ultimat3/query": "27.12.0",
62
+ "@ultimat3/realtime": "27.12.0",
63
+ "@ultimat3/render": "27.12.0",
64
+ "@ultimat3/schema": "27.12.0",
65
+ "@ultimat3/seo": "27.12.0",
66
+ "@ultimat3/storage": "27.12.0",
67
+ "@ultimat3/testing": "27.12.0",
68
+ "@ultimat3/time": "27.12.0",
69
69
  "babel-preset-solid": "^1.9.15"
70
70
  }
71
71
  }
@@ -11,6 +11,7 @@
11
11
 
12
12
  import { REQUIRED_BUN } from '../../app-root';
13
13
  import type { GeneratedFile, NameSet } from '../naming';
14
+ import { TEST_COMPOSE_PATH, TEST_ENV_PATH } from '../scaffold-test-services';
14
15
  import { IMAGE_WORKFLOW_PATH, imageWorkflow } from './image.yml';
15
16
  import { RUNS_ON } from './runner';
16
17
 
@@ -39,9 +40,9 @@ const ci = (app: NameSet): string => `name: ${CI_WORKFLOW_NAME}
39
40
  # \`.x/build-stats.json\`, so a run with no build reports X_BUDGET_UNMEASURED. Run \`x verify\` here
40
41
  # instead and this workflow is red for a reason that has nothing to do with the commit.
41
42
  #
42
- # No \`services:\` block, deliberately — \`bun run setup\` brings up embedded Postgres in-process,
43
- # so there is nothing to provision and nothing to wait on. Add one the day this app needs a real
44
- # server, beside the \`DATABASE_URL\` that selects it.
43
+ # \`bun run setup\` brings up embedded Postgres in-process; the TEST services come from
44
+ # \`docker/docker-compose.test.yml\` — Postgres 18, Redis, NATS and S3 in RAM, durability and logs
45
+ # off — started by the same two lines a laptop runs, never a \`services:\` block that restates them.
45
46
 
46
47
  # EVERY push, not just the default branch's. \`x new\` runs a plain \`git init\` and takes whatever
47
48
  # \`init.defaultBranch\` this machine already agreed on, so a \`branches: [main]\` filter silently
@@ -90,6 +91,9 @@ jobs:
90
91
  restore-keys: bun-\${{ runner.os }}-
91
92
  # Two steps rather than one \`&&\`, so the log names which half failed and times each.
92
93
  - run: bun run setup
94
+ # \`--wait\` returns once every healthcheck passes; the env file points the suites at them.
95
+ - run: docker compose -f ${TEST_COMPOSE_PATH} up -d --wait
96
+ - run: cat ${TEST_ENV_PATH} >> "$GITHUB_ENV"
93
97
  - run: bun run check
94
98
  `;
95
99
 
@@ -81,11 +81,13 @@ x-run-once-probe: &run-once-probe
81
81
 
82
82
  services:
83
83
  db:
84
- image: postgres:17-alpine
84
+ # Postgres 18 images keep the cluster under /var/lib/postgresql/18/docker, so the volume is the
85
+ # parent — mounted at .../data, as for 17, the image refuses to start.
86
+ image: postgres:18-alpine
85
87
  environment:
86
88
  POSTGRES_PASSWORD: \${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
87
89
  POSTGRES_DB: ${app.kebab}
88
- volumes: ['pgdata:/var/lib/postgresql/data']
90
+ volumes: ['pgdata:/var/lib/postgresql']
89
91
  healthcheck:
90
92
  test: ['CMD-SHELL', 'pg_isready -U postgres']
91
93
  interval: 5s
@@ -11,6 +11,7 @@ import type { GeneratedFile, NameSet } from './naming';
11
11
  import { binFiles } from './scaffold-bin';
12
12
  import { claudeFiles } from './scaffold-claude';
13
13
  import { containerFiles } from './scaffold-container';
14
+ import { testServicesFiles } from './scaffold-test-services';
14
15
 
15
16
  const agents = (app: NameSet): string => `# AGENTS.md
16
17
 
@@ -67,7 +68,14 @@ branch whose test cannot fail is worse than an uncovered one, because it reads a
67
68
  browser-only mount, a container entry point — carries its \`why\`, and is printed by \`x doctor\`.
68
69
 
69
70
  Every test the scaffold and the generators write is the idiom to copy. All of them run on the
70
- \`unit\` step — in process, on the in-memory driver, with no database, browser or server:
71
+ \`unit\` step — in process, on the in-memory driver, with no database, browser or server.
72
+
73
+ A suite that needs a real server reads it from \`docker/test-services.env\`: Postgres 18, Redis, NATS
74
+ and S3 from \`docker/docker-compose.test.yml\`, in RAM, with durability and logging off — CI starts
75
+ it before \`bun run check\`, a laptop with \`docker compose -f docker/docker-compose.test.yml up -d
76
+ --wait\` then \`set -a; . docker/test-services.env; set +a\`. A resource a test opens is held with
77
+ \`await using\` — \`await using db = await acquireWorkerDatabase()\` drops its database however
78
+ the test ends:
71
79
 
72
80
  | What | How its test runs it | Copy from |
73
81
  |---|---|---|
@@ -136,6 +144,14 @@ bun run dev # all roles in one process, embedded Postgres, /_x mounted
136
144
  bun run check # the gate: a static build, then typecheck, lint, boundaries, tests, drift, budgets
137
145
  \`\`\`
138
146
 
147
+ Tests against real servers — Postgres 18, Redis, NATS, S3, in RAM and silent, as CI runs them:
148
+
149
+ \`\`\`sh
150
+ docker compose -f docker/docker-compose.test.yml up -d --wait
151
+ set -a; . docker/test-services.env; set +a
152
+ bun run check
153
+ \`\`\`
154
+
139
155
  \`packages/db/migrations\` starts empty and \`x db gen\` is its only writer — \`bun run setup\` runs
140
156
  \`x db gen "initial"\` for you on a fresh clone. Until it has, \`x verify\`'s \`drift\` step is red
141
157
  with \`X_DB_DRIFT\`, and that is the fix it names.
@@ -173,7 +189,7 @@ const composeDev = (
173
189
  # production topology is docker-compose.prod.yml, and it is a different file for a reason.
174
190
  services:
175
191
  db:
176
- image: postgres:17-alpine
192
+ image: postgres:18-alpine
177
193
  environment:
178
194
  POSTGRES_PASSWORD: ${app.kebab}
179
195
  POSTGRES_DB: ${app.kebab}
@@ -214,6 +230,8 @@ export function docsFiles(app: NameSet): readonly GeneratedFile[] {
214
230
  // The scripts `bun run setup` and `bun run check` run — TypeScript, so one file per OS.
215
231
  ...binFiles(),
216
232
  { path: 'docker/docker-compose.dev.yml', contents: composeDev(app) },
233
+ // What the suites run against — in RAM, silent; ci.yml starts it before the gate.
234
+ ...testServicesFiles(app),
217
235
  // The harness half of the same job AGENTS.md does. It lands in the app's own repo rather than
218
236
  // in a global config, so it is visible in the scaffold's diff and deletable in one line.
219
237
  ...claudeFiles(app),
@@ -0,0 +1,137 @@
1
+ // The backing services a new app's suites run against: `docker/docker-compose.test.yml` and the
2
+ // one env file that points the suites at it. In RAM, durability off, silent — the framework's own
3
+ // `docker/docker-compose.test.yml`, cut to what an app has. Ports sit apart from the dev stack's
4
+ // so both can run at once; `ci.yml` starts this file before `bun run check`.
5
+
6
+ import type { GeneratedFile, NameSet } from './naming';
7
+
8
+ export const TEST_COMPOSE_PATH = 'docker/docker-compose.test.yml';
9
+ export const TEST_ENV_PATH = 'docker/test-services.env';
10
+
11
+ /** Host ports, one place: the compose file publishes them and the env file names them. */
12
+ export const TEST_PORTS = { postgres: 55432, redis: 56379, nats: 54222, s3: 59000 } as const;
13
+
14
+ const composeTest = (
15
+ app: NameSet,
16
+ ): string => `# The services the test suites run against — in RAM, and silent.
17
+ #
18
+ # docker compose -f ${TEST_COMPOSE_PATH} up -d --wait
19
+ # set -a; . ${TEST_ENV_PATH}; set +a
20
+ # bun run check
21
+ #
22
+ # \`--wait\` returns once every healthcheck passes. CI runs the same lines (.github/workflows/ci.yml).
23
+ # None of it is required: with no TEST_* variable set the unit suite runs on the in-memory driver,
24
+ # and a suite that needs a real server skips. Ports are the dev stack's plus 50000, so both run.
25
+ #
26
+ # Three rules, on every service:
27
+ #
28
+ # * RAM. Data lives on a tmpfs: no block device written, nothing left behind by \`down\`.
29
+ # * NO DURABILITY. fsync, synchronous_commit and full_page_writes off, WAL minimal and no WAL
30
+ # senders; Redis keeps no RDB snapshot and no AOF. A test database that survives a crash is a
31
+ # database nobody asked for.
32
+ # * SILENT. Each server logs as little as it can be told to, and the log driver is \`none\`. To
33
+ # watch one that will not start, run it attached: \`docker compose -f ${TEST_COMPOSE_PATH} up postgres\`.
34
+ #
35
+ # Every port binds 127.0.0.1: the credentials are in this file.
36
+ name: ${app.kebab}-test
37
+
38
+ x-silent: &silent
39
+ logging: { driver: none }
40
+
41
+ services:
42
+ # Postgres 18, the production major. \`initdb -c\` writes each setting into the new cluster;
43
+ # \`--no-sync\` is initdb's own fsync of a cluster on a tmpfs. Postgres 18 images keep the cluster
44
+ # under /var/lib/postgresql/18, so the tmpfs is the parent.
45
+ postgres:
46
+ <<: *silent
47
+ image: postgres:18-alpine
48
+ environment:
49
+ POSTGRES_USER: ${app.kebab}
50
+ POSTGRES_PASSWORD: ${app.kebab}-test
51
+ POSTGRES_DB: postgres
52
+ POSTGRES_INITDB_ARGS: >-
53
+ --no-sync
54
+ -c fsync=off
55
+ -c synchronous_commit=off
56
+ -c full_page_writes=off
57
+ -c wal_level=minimal
58
+ -c max_wal_senders=0
59
+ -c logging_collector=off
60
+ -c log_min_messages=fatal
61
+ -c log_statement=none
62
+ -c log_checkpoints=off
63
+ -c log_connections=off
64
+ -c log_disconnections=off
65
+ tmpfs: [/var/lib/postgresql]
66
+ ports: ['127.0.0.1:${TEST_PORTS.postgres}:5432']
67
+ healthcheck:
68
+ # TCP, never the socket: the entrypoint's temporary init server listens on the socket only.
69
+ test: ['CMD-SHELL', 'pg_isready -U ${app.kebab} -h 127.0.0.1 -p 5432']
70
+ interval: 1s
71
+ timeout: 3s
72
+ retries: 60
73
+
74
+ redis:
75
+ <<: *silent
76
+ image: redis:8-alpine
77
+ command: ['redis-server', '--save', '', '--appendonly', 'no', '--loglevel', 'warning']
78
+ tmpfs: [/data]
79
+ ports: ['127.0.0.1:${TEST_PORTS.redis}:6379']
80
+ healthcheck:
81
+ test: ['CMD', 'redis-cli', 'ping']
82
+ interval: 1s
83
+ timeout: 3s
84
+ retries: 30
85
+
86
+ # JetStream's store on the tmpfs; \`-l /dev/null\` is the only quiet a nats-server has.
87
+ nats:
88
+ <<: *silent
89
+ image: nats:2-alpine
90
+ command: ['-js', '-sd', '/data', '-m', '8222', '-l', '/dev/null']
91
+ tmpfs: [/data]
92
+ ports: ['127.0.0.1:${TEST_PORTS.nats}:4222']
93
+ healthcheck:
94
+ test: ['CMD', 'wget', '-q', '--spider', 'http://127.0.0.1:8222/healthz']
95
+ interval: 1s
96
+ timeout: 3s
97
+ retries: 30
98
+
99
+ # A directory under /data is a bucket, so the tmpfs mounted AT the bucket's path is the bootstrap.
100
+ s3:
101
+ <<: *silent
102
+ image: versity/versitygw:v1.8.0@sha256:30292fc2eeacc67a36993b01f7a7a5e3361a19cced0e80c1d71cfa2a4b0a2499
103
+ environment:
104
+ VGW_BACKEND: posix
105
+ VGW_BACKEND_ARG: /data
106
+ VGW_PORT: ':9000'
107
+ VGW_QUIET: 'true'
108
+ VGW_REGION: auto
109
+ ROOT_ACCESS_KEY: ${app.kebab}
110
+ ROOT_SECRET_KEY: ${app.kebab}-test
111
+ tmpfs: [/data/${app.kebab}-test]
112
+ ports: ['127.0.0.1:${TEST_PORTS.s3}:9000']
113
+ healthcheck:
114
+ test: ['CMD', 'nc', '-z', '127.0.0.1', '9000']
115
+ interval: 1s
116
+ timeout: 3s
117
+ retries: 30
118
+ `;
119
+
120
+ const testEnv = (
121
+ app: NameSet,
122
+ ): string => `# Where the suites find the services ${TEST_COMPOSE_PATH} starts — the one place these URLs are
123
+ # written. A laptop sources it (\`set -a; . ${TEST_ENV_PATH}; set +a\`); CI appends it to $GITHUB_ENV.
124
+ # TEST_DATABASE_URL is what @ultimat3/testing clones one database per test worker from.
125
+ TEST_DATABASE_URL=postgres://${app.kebab}:${app.kebab}-test@127.0.0.1:${TEST_PORTS.postgres}/postgres
126
+ TEST_REDIS_URL=redis://127.0.0.1:${TEST_PORTS.redis}
127
+ TEST_NATS_URL=nats://127.0.0.1:${TEST_PORTS.nats}
128
+ TEST_S3_URL=http://${app.kebab}:${app.kebab}-test@127.0.0.1:${TEST_PORTS.s3}/${app.kebab}-test
129
+ `;
130
+
131
+ /** The test stack and its env file. */
132
+ export function testServicesFiles(app: NameSet): readonly GeneratedFile[] {
133
+ return [
134
+ { path: TEST_COMPOSE_PATH, contents: composeTest(app) },
135
+ { path: TEST_ENV_PATH, contents: testEnv(app) },
136
+ ];
137
+ }