@quatronic/qore 0.1.2 → 0.1.4

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
@@ -140,20 +140,145 @@ qore run -- pnpm dev
140
140
  no worse than a `.env.local` on disk, but worth knowing before running an
141
141
  untrusted command this way.
142
142
 
143
+ #### `qore dev`
144
+
145
+ Starts this module's dev server, from a trusted repository:
146
+
147
+ ```sh
148
+ qore dev
149
+ ```
150
+
151
+ - **Context + trust**, like `qore run`/`qore env pull` — the same
152
+ `--module`/`--app` resolution, the same trust-on-first-use prompt
153
+ (`--yes` to skip it non-interactively).
154
+ - **Install**, if needed: the package manager comes from package.json's
155
+ `"packageManager": "<pnpm|npm|yarn>@<version>"` field, or (absent that)
156
+ whichever lockfile is present. `node_modules` missing, or older than the
157
+ lockfile, triggers an install; `--no-install` skips this step entirely.
158
+ - **Env merge**: this module's local secrets are pulled and merged into its
159
+ env file on disk (same target and conflict rules as `qore env pull`) —
160
+ unlike `qore run`, which only injects them into the child's environment.
161
+ - **`db:up`**, if the repository declares one: run automatically (unless
162
+ `--no-services`), then `qore dev` waits for `DB_PORT` to accept
163
+ connections before continuing — bounded by `--wait-timeout <s>` (default
164
+ `60`), which also bounds a workspace's provider-port wait (below). A
165
+ service that needs Docker and finds it missing or not running fails
166
+ fast, naming which.
167
+ - **Port check**: `PORT` from the merged env must be free; qore never
168
+ picks, shifts or suggests a port (see "Ports never move" below).
169
+ - **Runs `dev` exactly as `qore run` would** — same package manager, same
170
+ child-process semantics, same exit-code passthrough. The script name is
171
+ `"dev"` by default, or `dev.script` in `.qore.json` (below).
172
+ - `qore dev` in a module repository has no `--json`: the dev server's own
173
+ output passes straight through untouched.
174
+
175
+ A legacy backend `dev` script (from older templates, which need a
176
+ build and a `.env` first) fails fast with `QORE_DEV_UNSUPPORTED`, naming
177
+ the old script and the fix — the same code a repository with no `dev`
178
+ script at all, or an unsupported package manager, gets.
179
+
180
+ **Offline start**: if the portal is unreachable, `qore dev`
181
+ still starts — but only when the repository is already trusted **and** its
182
+ env file already exists on disk (from a previous `qore dev`/`env pull`).
183
+ It starts from that file as last written, with a warning that values may
184
+ be stale. This is the one place `qore` starts without confirming secrets
185
+ are current; every other command still requires the portal. An untrusted
186
+ repository, or one with no env file yet, surfaces the original
187
+ "portal unreachable" error instead.
188
+
189
+ **In a workspace root** (see "Workspaces" below), `qore dev` starts every
190
+ present module of the app, in dependency order, from one process — unless
191
+ `--module <slug>` is passed, which takes the module flow above instead
192
+ (run it from inside that module's own repository, not the workspace root
193
+ itself, or it fails with "Not inside a module repository"). `--app` is
194
+ refused in a workspace root: an app is already implied by
195
+ `qore.workspace.json`.
196
+
197
+ **Ports never move.** If `PORT` (or, for a workspace, a `db:up`'s
198
+ `DB_PORT`) is already taken, `qore dev` fails with `QORE_PORT_IN_USE`
199
+ naming the port and, when it can determine one, the process already
200
+ holding it — it never picks a different port, shifts one, or suggests an
201
+ alternative. Ports are part of the local contract between modules
202
+ (`CONN_*_URL` values point at fixed ports), so moving one silently would
203
+ break every other module that depends on it.
204
+
205
+ #### What qore diagnoses
206
+
207
+ Beyond the checks above, `qore dev` recognises common failure shapes and adds
208
+ a hint rather than leaving a bare error (spec §14.2):
209
+
210
+ - **Docker not running**: in a TTY, waits up to 120 s for it (`Ctrl+C` to
211
+ stop) instead of failing immediately; non-interactive or `--json` fails at
212
+ once.
213
+ - **A leftover `qore dev` holds the port**: in a TTY, offers to stop it
214
+ (`Stop the previous qore dev (pid N)?`); outside a TTY it's just
215
+ `QORE_PORT_IN_USE`.
216
+ - **The dev server exits within seconds of starting**: its tail output is
217
+ classified into a hint — a missing dependency (`Cannot find module`), an
218
+ invalid/missing env var (a `ZodError`'s keys, never values), a port taken
219
+ after the free-port check (`EADDRINUSE`), or a Linux file-watcher limit —
220
+ printed after the exit, never rewriting the server's own output. An
221
+ unrecognised failure falls back to the tool's last lines plus the command
222
+ that ran, never a bare "failed".
223
+ - **The portal is unreachable behind a corporate proxy**: the connect error
224
+ names `HTTPS_PROXY`/`NODE_EXTRA_CA_CERTS` as things to check.
225
+
226
+ `QORE_DEV_EXITED` is not a thrown code: when none of this applies, `qore dev`
227
+ just passes the child process's own exit code through.
228
+
229
+ #### `qore doctor`
230
+
231
+ Runs the same checks `qore` makes before other commands, on demand, without
232
+ doing anything else — the first thing to run when something's wrong, and
233
+ `--json` makes it attachable to a support ticket:
234
+
235
+ ```sh
236
+ qore doctor
237
+ ```
238
+
239
+ - Prints one line per check: `✓` ok, `!` warning, `✗` failing.
240
+ - Checks qore's own version, Node, git, GitHub SSH, Docker + compose, every
241
+ logged-in portal's session, clock skew, and — inside a workspace or module
242
+ repository — each module's port, access and database.
243
+ - **Never fetches the local env and never starts anything.** Ports are read
244
+ from the env file already on disk; access comes from the ordinary
245
+ `GET /api/cli/v1/me` and `.../modules` routes.
246
+ - Exit code `0` only when every check passes; a `!` warning never fails it,
247
+ only a `✗` does.
248
+ - No login profile at all is a warning, not a failure.
249
+ - `--json` shape: success is `{"ok":true,"data":{"results":[...],"context":{...}}}`;
250
+ on failure it's `{"ok":false,"error":{...,"details":{"results":[...],"context":{...}}}}`
251
+ — the full report is always in `details`, even though the top-level error
252
+ names only the first failing check. A check that throws still lets the
253
+ rest run; it just becomes a `fail` with that error attached.
254
+
143
255
  #### `.qore.json`
144
256
 
145
257
  An optional file at the repository root that configures `qore env pull`
146
- for that repository. Schema:
258
+ and `qore dev` for that repository. Schema:
147
259
  [`schema/qore.schema.json`](./schema/qore.schema.json).
148
260
 
149
261
  ```json
150
262
  {
151
263
  "$schema": "https://unpkg.com/@quatronic/qore/schema/qore.schema.json",
152
264
  "envFile": ".env.local",
153
- "localSecrets": ["MY_LOCAL_OVERRIDE"]
265
+ "localSecrets": ["MY_LOCAL_OVERRIDE"],
266
+ "dev": {
267
+ "install": true,
268
+ "services": ["db:up"],
269
+ "script": "dev"
270
+ }
154
271
  }
155
272
  ```
156
273
 
274
+ - **`dev.install`** — run the package manager's install when needed
275
+ (default `true`).
276
+ - **`dev.services`** — package.json scripts to run before the dev server,
277
+ in order (default: `["db:up"]` if that script exists, else none — each
278
+ name must be a real script in package.json).
279
+ - **`dev.script`** — the package.json script `qore dev` runs (default
280
+ `"dev"`).
281
+
157
282
  It never holds a portal URL, tenant, app or module id — those are always
158
283
  resolved from the git remote or `--module`/`--app`, never committed.
159
284
 
@@ -255,6 +380,95 @@ qore sync --pull # ...and fast-forward every clean repository with
255
380
  no workspace, a conflicting target directory) is an ordinary error
256
381
  document without results.
257
382
 
383
+ #### `qore dev` (workspace root)
384
+
385
+ Run from the directory holding `qore.workspace.json` (not inside one of its
386
+ module repositories — that takes the single-module flow above), starts
387
+ every present module of the app as one supervised run:
388
+
389
+ ```sh
390
+ qore dev # every present, writable module, in dependency order
391
+ qore dev --only api,web # just these (comma-separated)
392
+ qore dev --skip docs # all but these
393
+ qore dev --kill-others # the first module to exit stops the rest
394
+ ```
395
+
396
+ - Only **present** modules run (cloned into the workspace); one not cloned
397
+ yet is skipped with "not cloned — run `qore sync`". A module without
398
+ write access is skipped too (local secrets need write) naming who to
399
+ ask; its dependents still start, with a warning that the dependency
400
+ they point at (`CONN_*_URL`) won't be running — qore never falls back to
401
+ staging. `--only`/`--skip` select by slug; a skip is never fatal by
402
+ itself, only when it leaves nothing to start.
403
+ - **Every module's install, package-manager check and port check run
404
+ first, for every module, before anything starts.** Problems are
405
+ collected per module and all reported together — one module's install
406
+ failure never hides another's, and nothing starts if any of them failed.
407
+ Installs run with bounded parallelism (4 at a time).
408
+ - **Start order follows `dependsOn`**: providers before their consumers.
409
+ A consumer waits (up to `--wait-timeout`, default `60` s) for each
410
+ dependency's `PORT` to accept connections before its own `dev` script
411
+ runs; a dependency that doesn't come up in time (or that exited) logs a
412
+ warning and the consumer starts anyway. Modules that depend on each
413
+ other (a cycle) start together, without waiting, and the cycle is
414
+ reported once. A backend under `node --watch` that crashes waits for a
415
+ file change instead of exiting, so no exit line appears and its
416
+ consumers wait up to `--wait-timeout`.
417
+ - **`--kill-others`**: the first module to exit on its own (success or
418
+ failure) stops every other module; the others' own exit is then reported
419
+ quietly, not as a failure. Without it, `qore dev` keeps every surviving
420
+ module running until you stop it yourself.
421
+ - **Ctrl+C / SIGTERM/SIGHUP** stops every running module in reverse start
422
+ order, `SIGKILL` after a 10 s grace if one doesn't exit on its own; a
423
+ second Ctrl+C escalates to `SIGKILL` immediately. A `db:up` service a
424
+ module started is left running — `qore dev` only stops the dev servers
425
+ it launched directly. During the checks, Ctrl+C / SIGTERM / SIGHUP
426
+ stops a running install or `db:up` before `qore dev` exits.
427
+ - **Offline start** (same rule as module-level): only when every
428
+ module that would start is already trusted and has an env file on disk;
429
+ one exception fails the whole run with the original error.
430
+
431
+ **Feedback.** Before anything starts, a module not yet cloned is offered as
432
+ `Clone <module>, <module> now with qore sync? [Y/n]` (a TTY only, never
433
+ with `--json`/`--no-input`; `--yes` does not accept it). A module sync
434
+ cannot clone (a conflicting directory, no repository yet, an unsupported
435
+ repository URL, GitHub not linked, no repository access) gets its own
436
+ warning naming the real next step, and no prompt. While preflight runs, one progress line per
437
+ module per step (package manager, install, env, ports) is printed as it
438
+ completes. Once every module with a port is either listening or has been
439
+ reported as not running, a one-line summary follows: `✔ All modules
440
+ running:` (or `✔ Running:` / `! Not running:` when some were skipped) lists
441
+ each module with its URL or its reason. A module that crashes while others
442
+ keep running gets a restart hint
443
+ (`Restart it on its own: qore dev --only <module>`, in another terminal;
444
+ the others keep running); on shutdown, a final
445
+ line lists what stopped cleanly and what crashed, with how long it ran.
446
+ When the failure is about the developer's machine rather than the app
447
+ (Docker, Node version, a port busy with something other than a previous
448
+ `qore dev`, a package manager), the error is followed by a pointer to
449
+ `qore doctor` for a full check.
450
+
451
+ **Output.** Every line of a dev server's own stdout/stderr is prefixed per
452
+ module and column-aligned:
453
+
454
+ ```
455
+ api │ listening on 8000
456
+ web │ listening on 3001
457
+ ```
458
+
459
+ qore's own start, ready and exit lines (`▶ api (pid 4121)`,
460
+ `✓ api is listening on 8000`, `✗ web exited with code 1`) are not prefixed
461
+ and go to stderr, so `--json`'s stdout stays pure NDJSON. A module's own
462
+ stdout goes to stdout and its stderr to stderr. Colour is disabled with `--json`, `--no-color` or `NO_COLOR`.
463
+
464
+ With `--json` — the only qore command that streams instead of printing one
465
+ document — stdout is newline-delimited JSON, one event object per line
466
+ (`{"type":"start"|"ready"|"line"|"exit"|"warning"|"skip"|...}`), instead of
467
+ the usual single `{"ok":...}` document. An `exit` event carries an optional
468
+ `hint` (`{"hint":"..."}`, the same early-exit hint the human renderer prints
469
+ as ` → ...`) when the module crashed early with a non-zero code and no
470
+ signal — never on a signalled or graceful exit.
471
+
258
472
  ### `--json`
259
473
 
260
474
  Every command accepts `--json`, which prints exactly one JSON document on
@@ -271,8 +485,8 @@ qore whoami --json
271
485
  | Code | Meaning |
272
486
  |------|---------|
273
487
  | 0 | Success |
274
- | 1 | Internal error, `git` is not installed or not on `PATH`, or the OS keychain is unavailable/access was denied |
275
- | 2 | Missing or invalid context (e.g. a bad `--portal` value, or no module selected), or the module is part of several apps and no `--app` was given |
488
+ | 1 | Internal error, `git` is not installed or not on `PATH`, the OS keychain is unavailable/access was denied, or qore itself needs a newer Node (`QORE_NODE_VERSION`, checked by `qore doctor`'s own `node` check against qore's own `process.version`); `qore dev`: its package manager isn't installed (`QORE_PACKAGE_MANAGER`), its install failed (`QORE_INSTALL_FAILED`), Docker is missing (`QORE_DOCKER_MISSING`), not running (`QORE_DOCKER_NOT_RUNNING`) or its compose v2 plugin is missing (`QORE_DOCKER_COMPOSE_MISSING`), a `db:up`/service failed, never became healthy (`QORE_SERVICE_UNHEALTHY`) or its data is incompatible (`QORE_SERVICE_DATA_INCOMPATIBLE`), or the repository's own `engines.node` doesn't match the `node` on `PATH` (`QORE_NODE_VERSION`, a different check from qore's own — `qore doctor`'s `node:<module>` check, run only in a trusted repository) |
489
+ | 2 | Missing or invalid context (e.g. a bad `--portal` value, or no module selected), or the module is part of several apps and no `--app` was given; `qore dev`: a port is already in use (`QORE_PORT_IN_USE`) or the repository's dev setup isn't supported (`QORE_DEV_UNSUPPORTED`) |
276
490
  | 4 | Not logged in, session/login expired, login denied, or clock skew |
277
491
  | 5 | Access denied — lost tenant access, resource not found, no developer access |
278
492
  | 6 | Auth or API temporarily unreachable/unavailable |