@lenne.tech/cli 1.39.0 → 1.40.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/build/commands/dev/up.js +51 -7
- package/build/lib/dev-api-launch.js +89 -0
- package/build/lib/dev-test-session.js +2 -3
- package/docs/commands.md +11 -2
- package/package.json +1 -1
package/build/commands/dev/up.js
CHANGED
|
@@ -9,9 +9,11 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
});
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.help = void 0;
|
|
12
13
|
const fs_1 = require("fs");
|
|
13
14
|
const path_1 = require("path");
|
|
14
15
|
const caddy_1 = require("../../lib/caddy");
|
|
16
|
+
const dev_api_launch_1 = require("../../lib/dev-api-launch");
|
|
15
17
|
const dev_env_1 = require("../../lib/dev-env");
|
|
16
18
|
const dev_env_bridge_1 = require("../../lib/dev-env-bridge");
|
|
17
19
|
const dev_identity_1 = require("../../lib/dev-identity");
|
|
@@ -112,6 +114,10 @@ const UpCommand = {
|
|
|
112
114
|
// suffixed so the stack is fully isolated from the base dev session and every
|
|
113
115
|
// other ticket. Without a ticket this is the plain project identity.
|
|
114
116
|
const { dbName, identity, ticket } = (0, dev_ticket_1.resolveDevIdentity)(layout, { ticket: parameters.options.ticket });
|
|
117
|
+
// `--api-compiled`: run the API compiled (`node dist`) instead of ts-node.
|
|
118
|
+
// Trades hot reload for stability — ts-node intermittently dies under browser
|
|
119
|
+
// load without a stacktrace (DEV-2525); `lt dev test` already runs compiled.
|
|
120
|
+
const apiCompiled = (0, dev_api_launch_1.isApiCompiledRequested)(parameters.options);
|
|
115
121
|
// Guard against two checkouts of the SAME project (same package.json "name"
|
|
116
122
|
// → same slug → shared URLs / ports / DB / Caddy block). If another checkout
|
|
117
123
|
// is already RUNNING under this slug, abort with a clear message — otherwise
|
|
@@ -321,7 +327,7 @@ const UpCommand = {
|
|
|
321
327
|
if (identity.subdomains.app)
|
|
322
328
|
info(` app: https://${identity.subdomains.app.hostname} → 127.0.0.1:${appPort}`);
|
|
323
329
|
if (identity.subdomains.api)
|
|
324
|
-
info(` api: https://${identity.subdomains.api.hostname} → 127.0.0.1:${apiPort}`);
|
|
330
|
+
info(` api: https://${identity.subdomains.api.hostname} → 127.0.0.1:${apiPort}${apiCompiled ? colors.dim(' (compiled, no hot reload)') : ''}`);
|
|
325
331
|
if (identity.subdomains.api)
|
|
326
332
|
info(` db: mongodb://127.0.0.1/${dbName}`);
|
|
327
333
|
info('');
|
|
@@ -353,6 +359,11 @@ const UpCommand = {
|
|
|
353
359
|
if (apiHealth === 'running') {
|
|
354
360
|
pids.api = existingSession === null || existingSession === void 0 ? void 0 : existingSession.pids.api;
|
|
355
361
|
kept.push('api');
|
|
362
|
+
// `--api-compiled` only takes effect when the API (re)starts; a healthy API is
|
|
363
|
+
// kept as-is (force-restarting it would contradict the keep logic), so tell the
|
|
364
|
+
// user how to switch a currently-running ts-node API to compiled.
|
|
365
|
+
if (apiCompiled)
|
|
366
|
+
info(colors.dim(' --api-compiled: API already running — `lt dev down` first to switch it.'));
|
|
356
367
|
}
|
|
357
368
|
else {
|
|
358
369
|
yield reclaimPort(existingSession === null || existingSession === void 0 ? void 0 : existingSession.pids.api, apiPort, apiHealth !== null && apiHealth !== void 0 ? apiHealth : 'dead');
|
|
@@ -361,11 +372,20 @@ const UpCommand = {
|
|
|
361
372
|
// regenerate a foreign lockfile + crash on un-approved build
|
|
362
373
|
// scripts when run against an npm-only project.
|
|
363
374
|
const apiPm = (0, dev_package_manager_1.pickPackageManager)(layout.apiDir);
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
375
|
+
const apiLogFile = (0, path_1.join)(layout.root, '.lt-dev', 'api.log');
|
|
376
|
+
const apiResult = apiCompiled
|
|
377
|
+
? yield (0, dev_api_launch_1.startCompiledApi)({
|
|
378
|
+
apiDir: layout.apiDir,
|
|
379
|
+
env: devEnv.api.env,
|
|
380
|
+
log: { info, warn: warning },
|
|
381
|
+
logFile: apiLogFile,
|
|
382
|
+
pm: apiPm,
|
|
383
|
+
})
|
|
384
|
+
: (0, dev_process_1.spawnDetached)(apiPm.bin, apiPm.runScript('start'), {
|
|
385
|
+
cwd: layout.apiDir,
|
|
386
|
+
env: devEnv.api.env,
|
|
387
|
+
logFile: apiLogFile,
|
|
388
|
+
});
|
|
369
389
|
if (apiResult) {
|
|
370
390
|
pids.api = apiResult.pid;
|
|
371
391
|
started.push('api');
|
|
@@ -502,4 +522,28 @@ const UpCommand = {
|
|
|
502
522
|
return `dev up: api=${pids.api}, app=${pids.app}`;
|
|
503
523
|
}),
|
|
504
524
|
};
|
|
505
|
-
|
|
525
|
+
exports.help = {
|
|
526
|
+
aliases: ['u'],
|
|
527
|
+
configuration: 'Ephemeral dev-orchestration flags — no lt.config counterpart. Override the spawn binary for both processes via the LT_PNPM_BIN env var.',
|
|
528
|
+
description: 'Start the API + App behind Caddy under stable https://<slug>.localhost URLs. Health-aware and idempotent: re-running (re)starts only the component(s) that are not truly serving.',
|
|
529
|
+
examples: ['dev up', 'dev up --api-compiled', 'dev up --ticket DEV-1234'],
|
|
530
|
+
features: [
|
|
531
|
+
'Registers a Caddy block and allocates opaque internal ports (4000+).',
|
|
532
|
+
'Spawns API + App detached; persists PIDs to <root>/.lt-dev/state.json.',
|
|
533
|
+
'Self-heals legacy hardcoded ports and restarts only the down component(s).',
|
|
534
|
+
],
|
|
535
|
+
name: 'up',
|
|
536
|
+
options: [
|
|
537
|
+
{
|
|
538
|
+
description: 'Run the API compiled (node dist/src/main.js) instead of ts-node — trades hot reload for stability under browser load (DEV-2525). Builds + migrates first; falls back to the ts-node start if the build fails. Only applies when the API (re)starts.',
|
|
539
|
+
flag: '--api-compiled',
|
|
540
|
+
type: 'boolean',
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
description: 'Suffix the slug / URLs / DB for an isolated ticket stack (also auto-detected from a .lt-dev/ticket marker).',
|
|
544
|
+
flag: '--ticket',
|
|
545
|
+
type: 'string',
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
};
|
|
549
|
+
module.exports = Object.assign(UpCommand, { help: exports.help });
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.findCompiledEntry = findCompiledEntry;
|
|
13
|
+
exports.isApiCompiledRequested = isApiCompiledRequested;
|
|
14
|
+
exports.startCompiledApi = startCompiledApi;
|
|
15
|
+
/**
|
|
16
|
+
* Launch strategy for the API process under `lt dev up`.
|
|
17
|
+
*
|
|
18
|
+
* By default `lt dev up` runs the API via ts-node (`<pm> run start` → nodemon →
|
|
19
|
+
* ts-node src/main.ts) for hot reload. Under a browser driving the app, that
|
|
20
|
+
* ts-node process intermittently dies WITHOUT a stacktrace (dev-SSR load plus
|
|
21
|
+
* ts-node's heavier footprint) — see DEV-2525. `lt dev test` already sidesteps
|
|
22
|
+
* this by running the API COMPILED (`node dist/src/main.js`); this module brings
|
|
23
|
+
* the same option to `lt dev up`, opt-in via `--api-compiled`. The trade-off is
|
|
24
|
+
* NO hot reload, so it stays opt-in — the caller decides stability vs. reload.
|
|
25
|
+
*/
|
|
26
|
+
const node_fs_1 = require("node:fs");
|
|
27
|
+
const node_path_1 = require("node:path");
|
|
28
|
+
const dev_process_1 = require("./dev-process");
|
|
29
|
+
/** Candidate compiled entry points, in preference order. Single-sourced so `lt dev test` agrees. */
|
|
30
|
+
const COMPILED_ENTRIES = ['dist/src/main.js', 'dist/main.js'];
|
|
31
|
+
/** Resolve the compiled API entry point in `apiDir`, or `undefined` if none was built. */
|
|
32
|
+
function findCompiledEntry(apiDir) {
|
|
33
|
+
return COMPILED_ENTRIES.map((rel) => (0, node_path_1.join)(apiDir, rel)).find((candidate) => (0, node_fs_1.existsSync)(candidate));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* True when the caller opted into the compiled API via `--api-compiled`.
|
|
37
|
+
*
|
|
38
|
+
* gluegun parses argv with yargs-parser and declares no booleans, so the flag
|
|
39
|
+
* arrives in several shapes: a value-less `--api-compiled` → boolean `true`, but
|
|
40
|
+
* `--api-compiled=true` → the STRING `'true'` and `--api-compiled=1` → the NUMBER
|
|
41
|
+
* `1`. A bare `=== true` check silently ignores the latter two and drops the very
|
|
42
|
+
* stability fix the user asked for. This is an ENABLE flag, so a mis-parse fails
|
|
43
|
+
* SAFE (default ts-node) — but the repo convention is to honour `true`/`'true'`
|
|
44
|
+
* too (see `dev-ticket.ts#keepDbFlag` for the destructive-flag counterpart).
|
|
45
|
+
*/
|
|
46
|
+
function isApiCompiledRequested(options = {}) {
|
|
47
|
+
const affirmative = (value) => value === true || ['1', 'true', 'yes'].includes(String(value).toLowerCase());
|
|
48
|
+
return affirmative(options.apiCompiled) || affirmative(options['api-compiled']);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Build the API and start it compiled (`node dist/src/main.js`). Applies pending
|
|
52
|
+
* migrations first for parity with the ts-node path it replaces (`<pm> run start`
|
|
53
|
+
* = `migrate:up && start:local`). Falls back to the ts-node start when the build
|
|
54
|
+
* fails or produces no dist entry, so this never leaves the developer with a dead
|
|
55
|
+
* API. Returns the detached spawn result (`undefined` when nothing was started).
|
|
56
|
+
*/
|
|
57
|
+
function startCompiledApi(options) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
const { apiDir, env, log, logFile, pm } = options;
|
|
60
|
+
log.info('Building API (compiled, for stability — no hot reload) …');
|
|
61
|
+
const build = yield (0, dev_process_1.runChildInherit)(pm.bin, pm.runScript('build'), { cwd: apiDir, env });
|
|
62
|
+
const entry = findCompiledEntry(apiDir);
|
|
63
|
+
if (build === 0 && entry) {
|
|
64
|
+
if (hasScript(apiDir, 'migrate:up')) {
|
|
65
|
+
const migrate = yield (0, dev_process_1.runChildInherit)(pm.bin, pm.runScript('migrate:up'), { cwd: apiDir, env });
|
|
66
|
+
if (migrate !== 0) {
|
|
67
|
+
// Parity with `migrate:up && start:local`: a failed migration must PREVENT the server
|
|
68
|
+
// from starting rather than boot it against a half-migrated DB behind a "Started" banner.
|
|
69
|
+
log.warn(`migrate:up failed (exit ${String(migrate)}) — API NOT started (would run on an un-migrated DB).`);
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return (0, dev_process_1.spawnDetached)('node', [entry], { cwd: apiDir, env: Object.assign(Object.assign({}, env), { NODE_ENV: 'local' }), logFile });
|
|
74
|
+
}
|
|
75
|
+
log.warn(`compiled API unavailable (build exit ${String(build)}) — falling back to \`${pm.bin} start\` (ts-node).`);
|
|
76
|
+
return (0, dev_process_1.spawnDetached)(pm.bin, pm.runScript('start'), { cwd: apiDir, env, logFile });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
/** True when `package.json` in `apiDir` defines a script named `name`. */
|
|
80
|
+
function hasScript(apiDir, name) {
|
|
81
|
+
var _a;
|
|
82
|
+
try {
|
|
83
|
+
const pkg = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(apiDir, 'package.json'), 'utf8'));
|
|
84
|
+
return typeof ((_a = pkg.scripts) === null || _a === void 0 ? void 0 : _a[name]) === 'string';
|
|
85
|
+
}
|
|
86
|
+
catch (_b) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -47,6 +47,7 @@ const fs_1 = require("fs");
|
|
|
47
47
|
const os_1 = require("os");
|
|
48
48
|
const path_1 = require("path");
|
|
49
49
|
const caddy_1 = require("./caddy");
|
|
50
|
+
const dev_api_launch_1 = require("./dev-api-launch");
|
|
50
51
|
const dev_env_1 = require("./dev-env");
|
|
51
52
|
const dev_env_bridge_1 = require("./dev-env-bridge");
|
|
52
53
|
const dev_identity_1 = require("./dev-identity");
|
|
@@ -245,9 +246,7 @@ function bringUpTestSession(layout_1, baseIdentity_1, log_1) {
|
|
|
245
246
|
log.info(log.dim('Building API (compiled, for stable long runs) …'));
|
|
246
247
|
build = yield (0, dev_process_1.runChildInherit)(apiPm.bin, apiPm.runScript('build'), { cwd: layout.apiDir, env: process.env });
|
|
247
248
|
}
|
|
248
|
-
const entry =
|
|
249
|
-
.map((rel) => (0, path_1.join)(layout.apiDir, rel))
|
|
250
|
-
.find((p) => (0, fs_1.existsSync)(p));
|
|
249
|
+
const entry = (0, dev_api_launch_1.findCompiledEntry)(layout.apiDir);
|
|
251
250
|
// Seed a throwaway initial admin into the fresh, isolated test DB so the
|
|
252
251
|
// standard auth E2E specs run against a set-up system — locally exactly like
|
|
253
252
|
// the lt-monorepo template CI. Defaults first so an explicitly inherited
|
package/docs/commands.md
CHANGED
|
@@ -433,6 +433,13 @@ lt dev up
|
|
|
433
433
|
|
|
434
434
|
**Alias:** `lt d u`
|
|
435
435
|
|
|
436
|
+
**Flags:**
|
|
437
|
+
- `--api-compiled` — run the API **compiled** (`node dist/src/main.js`) instead of ts-node. Trades hot reload for stability: under sustained browser / dev-SSR load the ts-node API process intermittently dies without a stacktrace (DEV-2525); the compiled `node` process does not. Opt-in — omit it for the default ts-node hot-reload start. Details:
|
|
438
|
+
- Builds the API first, then applies pending migrations (`migrate:up`) for parity with the default `migrate:up && start:local`; a failed migration aborts the start rather than booting against a half-migrated DB.
|
|
439
|
+
- Auto-falls-back to the ts-node `start` if the build fails or produces no `dist` entry, so you never end up with a dead API.
|
|
440
|
+
- Only takes effect when the API actually (re)starts. A healthy running API is kept as-is — run `lt dev down` first to switch a live ts-node API to compiled.
|
|
441
|
+
- Accepted spellings: `--api-compiled` or `--api-compiled=true`.
|
|
442
|
+
|
|
436
443
|
**Environment variables injected:**
|
|
437
444
|
| Variable | Consumer | Example value |
|
|
438
445
|
|----------|----------|---------------|
|
|
@@ -467,8 +474,10 @@ Behaviour:
|
|
|
467
474
|
reclaims any orphaned listener still squatting the reused port.
|
|
468
475
|
|
|
469
476
|
This is the fix for the "`status` says api running but no data loads" case: a
|
|
470
|
-
crashed ts-node dev API is healed by simply re-running `lt dev up
|
|
471
|
-
|
|
477
|
+
crashed ts-node dev API is healed by simply re-running `lt dev up`. The **automatic**
|
|
478
|
+
heal keeps ts-node (it does not silently switch to compiled `node dist`, so code
|
|
479
|
+
edits still hot-reload); pass the explicit `--api-compiled` flag (see **Flags** above)
|
|
480
|
+
when you deliberately want to trade hot reload for compiled stability.
|
|
472
481
|
|
|
473
482
|
**Logs:** `<root>/.lt-dev/api.log`, `<root>/.lt-dev/app.log` (append-mode).
|
|
474
483
|
|