@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.
|
|
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.
|
|
43
|
-
"@ultimat3/admin": "27.
|
|
44
|
-
"@ultimat3/ai": "27.
|
|
45
|
-
"@ultimat3/auth": "27.
|
|
46
|
-
"@ultimat3/cache": "27.
|
|
47
|
-
"@ultimat3/core": "27.
|
|
48
|
-
"@ultimat3/db": "27.
|
|
49
|
-
"@ultimat3/entity": "27.
|
|
50
|
-
"@ultimat3/flags": "27.
|
|
51
|
-
"@ultimat3/http": "27.
|
|
52
|
-
"@ultimat3/i18n": "27.
|
|
53
|
-
"@ultimat3/jobs": "27.
|
|
54
|
-
"@ultimat3/mail": "27.
|
|
55
|
-
"@ultimat3/manifest": "27.
|
|
56
|
-
"@ultimat3/mcp": "27.
|
|
57
|
-
"@ultimat3/money": "27.
|
|
58
|
-
"@ultimat3/notify": "27.
|
|
59
|
-
"@ultimat3/policy": "27.
|
|
60
|
-
"@ultimat3/pwa": "27.
|
|
61
|
-
"@ultimat3/query": "27.
|
|
62
|
-
"@ultimat3/realtime": "27.
|
|
63
|
-
"@ultimat3/render": "27.
|
|
64
|
-
"@ultimat3/schema": "27.
|
|
65
|
-
"@ultimat3/seo": "27.
|
|
66
|
-
"@ultimat3/storage": "27.
|
|
67
|
-
"@ultimat3/testing": "27.
|
|
68
|
-
"@ultimat3/time": "27.
|
|
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
|
-
#
|
|
43
|
-
#
|
|
44
|
-
#
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
+
}
|