@lenne.tech/cli 1.48.0 → 1.49.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/test.js +5 -3
- package/build/commands/fullstack/init.js +12 -3
- package/build/commands/git/squash.js +4 -1
- package/build/extensions/api-mode.js +69 -0
- package/build/extensions/frontend-helper.js +11 -12
- package/build/extensions/server.js +42 -32
- package/build/lib/check-template-sync.js +62 -2
- package/build/lib/config-env-graphql.js +106 -0
- package/build/lib/cross-env.js +94 -0
- package/build/lib/dev-api-launch.js +34 -8
- package/build/lib/dev-package-manager.js +13 -9
- package/build/lib/dev-test-session.js +85 -39
- package/build/lib/fail-run.js +11 -0
- package/build/lib/heal-vendor-migrate-store.js +4 -3
- package/build/lib/migrate-scripts.js +69 -0
- package/build/lib/module-specifiers.js +144 -0
- package/build/lib/spinner-lifetime.js +245 -0
- package/build/templates/check/check.mjs +59 -9
- package/build/templates/check/lt-monorepo-pin.json +4 -4
- package/docs/commands.md +16 -12
- package/package.json +1 -1
- package/build/lib/strip-comments.js +0 -78
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.findSpinnerLeaks = findSpinnerLeaks;
|
|
37
|
+
/**
|
|
38
|
+
* Finds progress spinners that can be left running.
|
|
39
|
+
*
|
|
40
|
+
* gluegun's `spin(text)` is `require('ora')(text).start()`. ora 4.0.2 with the
|
|
41
|
+
* default `discardStdin: true` does three things on start that only a stop undoes:
|
|
42
|
+
* it puts the terminal into raw mode (remembering the value it found), it calls
|
|
43
|
+
* `stdin.resume()` and adds a `data` listener, and it starts a `setInterval` for
|
|
44
|
+
* the animation.
|
|
45
|
+
*
|
|
46
|
+
* A spinner that is never stopped therefore does not merely look untidy — the
|
|
47
|
+
* interval and the resumed stdin keep the Node event loop alive, so the process
|
|
48
|
+
* never exits, and the terminal stays in raw mode. That combination is how
|
|
49
|
+
* `lt fullstack init` could reach the end of its work and simply not return, with
|
|
50
|
+
* a spinner still turning. `failRun` deliberately sets `process.exitCode` instead
|
|
51
|
+
* of calling `process.exit()` (so a failure message is never truncated), which
|
|
52
|
+
* means "the event loop drains" is a precondition the CLI relies on.
|
|
53
|
+
*
|
|
54
|
+
* ## What "stopped" means here
|
|
55
|
+
*
|
|
56
|
+
* This is a dominance approximation, not a full control-flow analysis, and it is
|
|
57
|
+
* deliberately conservative in the direction that avoids false alarms — a guard
|
|
58
|
+
* that flags correct code gets switched off. A statement list definitely stops a
|
|
59
|
+
* spinner when it contains a direct stop call, or an `if` whose BOTH branches
|
|
60
|
+
* stop, or a `try` whose body and `catch` both stop (or whose `finally` stops).
|
|
61
|
+
* Anything else does not count.
|
|
62
|
+
*
|
|
63
|
+
* Two questions are asked per spinner:
|
|
64
|
+
*
|
|
65
|
+
* 1. Can the end of its enclosing block be reached without a stop? That is the
|
|
66
|
+
* silent case — the command falls off the end, exit code 0, spinner running.
|
|
67
|
+
* 2. Is there a `return` after it that no stop dominates? That is the early-exit
|
|
68
|
+
* case — an error path that prints and leaves.
|
|
69
|
+
*
|
|
70
|
+
* Returns inside a nested function are ignored: they leave the callback, not the
|
|
71
|
+
* command.
|
|
72
|
+
*/
|
|
73
|
+
const ts = __importStar(require("typescript"));
|
|
74
|
+
/** Methods that stop an ora spinner. `stopAndPersist` is what `succeed`/`fail`/… call. */
|
|
75
|
+
const STOP_METHODS = new Set(['fail', 'info', 'stop', 'stopAndPersist', 'succeed', 'warn']);
|
|
76
|
+
/**
|
|
77
|
+
* Every spinner in `source` that can outlive the code path that started it.
|
|
78
|
+
*
|
|
79
|
+
* @param source The file's text.
|
|
80
|
+
* @param fileName Only used to pick the parser dialect.
|
|
81
|
+
*/
|
|
82
|
+
function findSpinnerLeaks(source, fileName = 'file.ts') {
|
|
83
|
+
const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true);
|
|
84
|
+
const leaks = [];
|
|
85
|
+
const lineOf = (node) => sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
|
|
86
|
+
for (const declaration of spinnerDeclarations(sourceFile)) {
|
|
87
|
+
const name = declaration.name.getText(sourceFile);
|
|
88
|
+
const block = enclosingBlock(declaration);
|
|
89
|
+
if (!block) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const returns = returnsAfter(block, declaration);
|
|
93
|
+
// `return spinner;` hands ownership to the caller, which is then responsible
|
|
94
|
+
// for stopping it — a factory like `logger.ts#spin` is not a leak. One such
|
|
95
|
+
// return exempts the whole declaration, because the spinner's lifetime is no
|
|
96
|
+
// longer decided in this function.
|
|
97
|
+
if (returns.some((statement) => handsOverOwnership(statement, name))) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const after = statementsAfter(block, declaration);
|
|
101
|
+
if (!after.some((statement) => definitelyStops(statement, name, sourceFile))) {
|
|
102
|
+
leaks.push({
|
|
103
|
+
line: lineOf(declaration),
|
|
104
|
+
reason: after.length === 0 ? 'never-stopped' : 'falls-through',
|
|
105
|
+
spinner: name,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
for (const returnStatement of returns) {
|
|
109
|
+
if (!dominatedByStop(returnStatement, block, declaration, name, sourceFile)) {
|
|
110
|
+
leaks.push({ line: lineOf(returnStatement), reason: 'return-without-stop', spinner: name });
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return leaks.sort((a, b) => a.line - b.line);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* True when executing `statement` always stops `name`.
|
|
118
|
+
*
|
|
119
|
+
* Only the shapes the CLI actually uses are recognised. Everything unrecognised
|
|
120
|
+
* answers false, so an unusual construct produces a finding to look at rather
|
|
121
|
+
* than a silent pass.
|
|
122
|
+
*/
|
|
123
|
+
function definitelyStops(statement, name, sourceFile) {
|
|
124
|
+
if (ts.isExpressionStatement(statement)) {
|
|
125
|
+
return isStopCall(statement.expression, name, sourceFile);
|
|
126
|
+
}
|
|
127
|
+
if (ts.isBlock(statement)) {
|
|
128
|
+
return statement.statements.some((child) => definitelyStops(child, name, sourceFile));
|
|
129
|
+
}
|
|
130
|
+
if (ts.isIfStatement(statement)) {
|
|
131
|
+
// Only an if/else that stops on BOTH sides stops unconditionally.
|
|
132
|
+
return (!!statement.elseStatement &&
|
|
133
|
+
definitelyStops(statement.thenStatement, name, sourceFile) &&
|
|
134
|
+
definitelyStops(statement.elseStatement, name, sourceFile));
|
|
135
|
+
}
|
|
136
|
+
if (ts.isTryStatement(statement)) {
|
|
137
|
+
if (statement.finallyBlock && definitelyStops(statement.finallyBlock, name, sourceFile)) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return (definitelyStops(statement.tryBlock, name, sourceFile) &&
|
|
141
|
+
!!statement.catchClause &&
|
|
142
|
+
definitelyStops(statement.catchClause.block, name, sourceFile));
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* True when some stop of `name` is guaranteed to run before `returnStatement`.
|
|
148
|
+
*
|
|
149
|
+
* Walks out from the return towards `block`, and at each level checks the
|
|
150
|
+
* statements that precede the one we came from — a stop in any of them dominates.
|
|
151
|
+
*/
|
|
152
|
+
function dominatedByStop(returnStatement, block, declaration, name, sourceFile) {
|
|
153
|
+
let child = returnStatement;
|
|
154
|
+
let parent = returnStatement.parent;
|
|
155
|
+
while (parent) {
|
|
156
|
+
if (ts.isBlock(parent) || ts.isSourceFile(parent)) {
|
|
157
|
+
for (const statement of parent.statements) {
|
|
158
|
+
if (statement === child || statement.getStart(sourceFile) >= child.getStart(sourceFile)) {
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
if (statement.getStart(sourceFile) > declaration.getStart(sourceFile) &&
|
|
162
|
+
definitelyStops(statement, name, sourceFile)) {
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (parent === block) {
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
child = parent;
|
|
171
|
+
parent = parent.parent;
|
|
172
|
+
}
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
/** The nearest enclosing block of a declaration — the scope its spinner must not outlive. */
|
|
176
|
+
function enclosingBlock(node) {
|
|
177
|
+
let current = node.parent;
|
|
178
|
+
while (current) {
|
|
179
|
+
if (ts.isBlock(current)) {
|
|
180
|
+
return current;
|
|
181
|
+
}
|
|
182
|
+
current = current.parent;
|
|
183
|
+
}
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
/** True for `return spinner;` — the caller becomes responsible for stopping it. */
|
|
187
|
+
function handsOverOwnership(statement, name) {
|
|
188
|
+
return !!statement.expression && ts.isIdentifier(statement.expression) && statement.expression.text === name;
|
|
189
|
+
}
|
|
190
|
+
/** True for `x.succeed(…)` / `x.fail(…)` / … on the spinner called `name`. */
|
|
191
|
+
function isStopCall(expression, name, sourceFile) {
|
|
192
|
+
if (!ts.isCallExpression(expression) || !ts.isPropertyAccessExpression(expression.expression)) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
const access = expression.expression;
|
|
196
|
+
return access.expression.getText(sourceFile) === name && STOP_METHODS.has(access.name.text);
|
|
197
|
+
}
|
|
198
|
+
/** Every `return` after `declaration` inside `block`, skipping nested functions. */
|
|
199
|
+
function returnsAfter(block, declaration) {
|
|
200
|
+
const found = [];
|
|
201
|
+
const start = declaration.getStart();
|
|
202
|
+
const visit = (node) => {
|
|
203
|
+
if (ts.isFunctionLike(node)) {
|
|
204
|
+
return; // A return there leaves the callback, not the command.
|
|
205
|
+
}
|
|
206
|
+
if (ts.isReturnStatement(node) && node.getStart() > start) {
|
|
207
|
+
found.push(node);
|
|
208
|
+
}
|
|
209
|
+
ts.forEachChild(node, visit);
|
|
210
|
+
};
|
|
211
|
+
for (const statement of block.statements) {
|
|
212
|
+
if (statement.getStart() > start) {
|
|
213
|
+
visit(statement);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return found;
|
|
217
|
+
}
|
|
218
|
+
/** `const x = spin(…)` declarations, including `toolbox.print.spin(…)`. */
|
|
219
|
+
function spinnerDeclarations(sourceFile) {
|
|
220
|
+
const found = [];
|
|
221
|
+
const visit = (node) => {
|
|
222
|
+
if (ts.isVariableDeclaration(node) &&
|
|
223
|
+
ts.isIdentifier(node.name) &&
|
|
224
|
+
node.initializer &&
|
|
225
|
+
ts.isCallExpression(node.initializer)) {
|
|
226
|
+
const callee = node.initializer.expression;
|
|
227
|
+
const name = ts.isPropertyAccessExpression(callee)
|
|
228
|
+
? callee.name.text
|
|
229
|
+
: ts.isIdentifier(callee)
|
|
230
|
+
? callee.text
|
|
231
|
+
: '';
|
|
232
|
+
if (name === 'spin') {
|
|
233
|
+
found.push(node);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
ts.forEachChild(node, visit);
|
|
237
|
+
};
|
|
238
|
+
visit(sourceFile);
|
|
239
|
+
return found;
|
|
240
|
+
}
|
|
241
|
+
/** The statements of `block` that follow `declaration`. */
|
|
242
|
+
function statementsAfter(block, declaration) {
|
|
243
|
+
const start = declaration.getStart();
|
|
244
|
+
return block.statements.filter((statement) => statement.getStart() > start);
|
|
245
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// @lt-check-wrapper 3.13.
|
|
2
|
+
// @lt-check-wrapper 3.13.1
|
|
3
3
|
/**
|
|
4
4
|
* Quiet, report-driven wrapper around the project `check` pipeline.
|
|
5
5
|
*
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* scripts/check-wrapper-version.test.mjs fails when it drifts from
|
|
34
34
|
* package.json. In a generated project it keeps the release it came from.
|
|
35
35
|
*/
|
|
36
|
-
import { execSync, spawn } from 'node:child_process';
|
|
36
|
+
import { execFileSync, execSync, spawn } from 'node:child_process';
|
|
37
37
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
38
38
|
import { dirname, join } from 'node:path';
|
|
39
39
|
import { fileURLToPath } from 'node:url';
|
|
@@ -84,6 +84,12 @@ function fmtDuration(ms) {
|
|
|
84
84
|
// binds only to the first simple command, so a step written with `;` or a
|
|
85
85
|
// leading `cd` would be reported as pinned and run unpinned. Both carry the
|
|
86
86
|
// same value, so they cannot disagree.
|
|
87
|
+
//
|
|
88
|
+
// On Windows only the second level exists: cmd.exe has no `VAR=value cmd`
|
|
89
|
+
// syntax and would run the assignment as a command, so `pinCheckBuildDir`
|
|
90
|
+
// writes no prefix there and `stepEnv` carries the pin alone. That is why
|
|
91
|
+
// `stepEnv` keys on the command rather than on the presence of a prefix —
|
|
92
|
+
// scripts/check-windows.test.mjs holds the two together.
|
|
87
93
|
const CHECK_BUILD_DIR = '.nuxt-check';
|
|
88
94
|
|
|
89
95
|
// Deliberately narrow: a blanket prefix would override the dirs the package.json
|
|
@@ -123,8 +129,15 @@ export const PM_INVOCATION = new RegExp(`${PM_INSTALL.source}|${PM_AUDIT.source}
|
|
|
123
129
|
* existing-pin test is NOT anchored to the start of the string, because the
|
|
124
130
|
* shape the nuxt starter actually ships is `cross-env NUXT_BUILD_DIR=… pnpm …`,
|
|
125
131
|
* which a `^` anchor does not see.
|
|
132
|
+
*
|
|
133
|
+
* On Windows there is no prefix at all: `VAR=value cmd` is POSIX shell syntax,
|
|
134
|
+
* and cmd.exe reads the assignment as the command name ('NUXT_BUILD_DIR' is not
|
|
135
|
+
* recognized as an internal or external command). `stepEnv` below hands the
|
|
136
|
+
* same value to the child as a real environment variable, which is what every
|
|
137
|
+
* step relies on there.
|
|
126
138
|
*/
|
|
127
|
-
export function pinCheckBuildDir(cmd) {
|
|
139
|
+
export function pinCheckBuildDir(cmd, platform = process.platform) {
|
|
140
|
+
if (platform === 'win32') return cmd;
|
|
128
141
|
if (!PM_INVOCATION.test(cmd) || /(^|\s)NUXT_BUILD_DIR=/.test(cmd)) return cmd;
|
|
129
142
|
return `NUXT_BUILD_DIR=${CHECK_BUILD_DIR} ${cmd}`;
|
|
130
143
|
}
|
|
@@ -132,13 +145,22 @@ export function pinCheckBuildDir(cmd) {
|
|
|
132
145
|
/**
|
|
133
146
|
* The environment a step needs beyond the inherited one.
|
|
134
147
|
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
148
|
+
* Keyed on the COMMAND, not on the textual pin. Two reasons, and the second is
|
|
149
|
+
* why reading the text was not enough:
|
|
150
|
+
*
|
|
151
|
+
* - a shell construct the prefix cannot reach (a `;` separator, a leading
|
|
152
|
+
* `cd`) would be reported as pinned and run unpinned;
|
|
153
|
+
* - on Windows `pinCheckBuildDir` writes no prefix, so a predicate that looks
|
|
154
|
+
* for one would find nothing and hand the step NO build dir — the pin would
|
|
155
|
+
* silently disappear on exactly the platform that cannot use the prefix.
|
|
156
|
+
*
|
|
157
|
+
* A step that pins itself keeps its own value: the textual prefix wins over the
|
|
158
|
+
* inherited environment, and `cross-env` sets the variable itself.
|
|
139
159
|
*/
|
|
140
|
-
function stepEnv(step) {
|
|
141
|
-
return /(^|\s)NUXT_BUILD_DIR=/.test(step.cmd)
|
|
160
|
+
export function stepEnv(step) {
|
|
161
|
+
return PM_INVOCATION.test(step.cmd) || /(^|\s)NUXT_BUILD_DIR=/.test(step.cmd)
|
|
162
|
+
? { NUXT_BUILD_DIR: CHECK_BUILD_DIR }
|
|
163
|
+
: null;
|
|
142
164
|
}
|
|
143
165
|
|
|
144
166
|
// ── step classification ────────────────────────────────────────────────────
|
|
@@ -407,11 +429,39 @@ const IDLE_TIMEOUT_MS = (() => {
|
|
|
407
429
|
// ── command runner ─────────────────────────────────────────────────────────
|
|
408
430
|
const RUNNING = new Set();
|
|
409
431
|
|
|
432
|
+
/**
|
|
433
|
+
* How to kill a process tree on this platform.
|
|
434
|
+
*
|
|
435
|
+
* Windows has neither `pgrep` nor signals: `process.kill(pid, 'SIGTERM')` there ends the one
|
|
436
|
+
* process and orphans its children, and `taskkill /T` alone was measured to leave the tree
|
|
437
|
+
* running ("Die Beendigung dieses Prozesses muss erzwungen werden") with the port still held.
|
|
438
|
+
* `/F` is what actually frees it, so the whole tree is force-killed in one call.
|
|
439
|
+
*
|
|
440
|
+
* The consequence is worth stating, because it is a behaviour difference and not an
|
|
441
|
+
* implementation detail: on Windows there is no graceful stage. The SIGTERM call already
|
|
442
|
+
* terminates, so a child's graceful-shutdown hook does not run — a server gets no chance to
|
|
443
|
+
* close connections or flush. The SIGKILL escalation five seconds later then finds nothing.
|
|
444
|
+
*
|
|
445
|
+
* Split out as a pure function so both branches can be tested from either platform.
|
|
446
|
+
*/
|
|
447
|
+
export function killTreePlan(pid, signal, platform = process.platform) {
|
|
448
|
+
return platform === 'win32' ? { args: ['/PID', String(pid), '/T', '/F'], command: 'taskkill' } : { signal };
|
|
449
|
+
}
|
|
450
|
+
|
|
410
451
|
// Best-effort kill of a child's whole process tree (sh → pnpm → vitest →
|
|
411
452
|
// fork workers). Killing only the direct child orphans the tree — exactly the
|
|
412
453
|
// zombie workers a deadlock leaves behind. Children are collected via pgrep
|
|
413
454
|
// and killed leaves-first.
|
|
414
455
|
function killTree(child, signal = 'SIGTERM') {
|
|
456
|
+
const plan = killTreePlan(child.pid, signal);
|
|
457
|
+
if (plan.command) {
|
|
458
|
+
try {
|
|
459
|
+
execFileSync(plan.command, plan.args, { stdio: 'ignore' });
|
|
460
|
+
} catch {
|
|
461
|
+
/* already gone, or taskkill refused — nothing further to try */
|
|
462
|
+
}
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
415
465
|
const pids = [];
|
|
416
466
|
const collect = (pid) => {
|
|
417
467
|
pids.push(pid);
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"commit": "
|
|
2
|
+
"commit": "fcf7c83cffa37b09db88481833e7ccc0dd2fbe0f",
|
|
3
3
|
"files": {
|
|
4
4
|
"build-test-gate.mjs": "865bf1b9dd28151f53b9f2dfe4c3c46d29b9303fb1c2f497ce0e3136bcd37d71",
|
|
5
|
-
"check.mjs": "
|
|
5
|
+
"check.mjs": "fed669a625b56d907ab2e3693c2e5797a877c460588bd79f89249d88c568c178",
|
|
6
6
|
"lib/ansi.mjs": "f997ee6e9bf1c7ee54ab612d4040fcc778064ec160eae19bf5c3960a75a6e2ff",
|
|
7
7
|
"lib/audit-report.mjs": "503825c3342dec1ee5d8d70e5af21b73e798faee320426b2492190cde0801df2",
|
|
8
8
|
"lib/workspace-packages.mjs": "ccdd133622a88186c924bc8271d35cf1f8357dc26d4a607998ca5e91fce5bf88"
|
|
9
9
|
},
|
|
10
|
-
"ref": "v3.13.
|
|
10
|
+
"ref": "v3.13.1",
|
|
11
11
|
"repository": "lenneTech/lt-monorepo",
|
|
12
|
-
"version": "3.13.
|
|
12
|
+
"version": "3.13.1"
|
|
13
13
|
}
|
package/docs/commands.md
CHANGED
|
@@ -663,29 +663,33 @@ Run `--explain` to see three commonly recommended keys the profile deliberately
|
|
|
663
663
|
|
|
664
664
|
### `lt dev test`
|
|
665
665
|
|
|
666
|
-
One-shot E2E wrapper:
|
|
666
|
+
One-shot E2E wrapper: brings up an isolated test stack next to the `lt dev up` session (own URLs `<slug>-test.localhost` / `api.<slug>-test.localhost`, own ports, own Caddy block, own database `<…>-test`), runs `test:e2e` against it, and tears the stack down afterwards. The dev session is never touched.
|
|
667
667
|
|
|
668
668
|
**Usage:**
|
|
669
669
|
```bash
|
|
670
|
-
lt dev test # App E2E
|
|
671
|
-
lt dev test --
|
|
672
|
-
lt dev test
|
|
670
|
+
lt dev test # App E2E against the isolated stack, auto teardown
|
|
671
|
+
lt dev test --keep # leave the test stack up afterwards
|
|
672
|
+
lt dev test down # tear a leftover test stack down
|
|
673
|
+
lt dev test --api # API E2E (projects/api) — no stack, no Caddy
|
|
673
674
|
lt dev test --debug # PWDEBUG=1 + HEADED=1
|
|
675
|
+
lt dev test --shard [N|auto] # split across N isolated stacks (bare --shard = 2)
|
|
674
676
|
lt dev test -- --ui spec.ts # everything after `--` is forwarded to playwright
|
|
675
677
|
```
|
|
676
678
|
|
|
677
679
|
**Alias:** `lt d t`
|
|
678
680
|
|
|
679
|
-
**Behaviour:**
|
|
680
|
-
1. Pre-flight: Caddy installed + daemon running (
|
|
681
|
-
2.
|
|
682
|
-
3.
|
|
683
|
-
4.
|
|
684
|
-
5.
|
|
685
|
-
6.
|
|
681
|
+
**Behaviour (App mode):**
|
|
682
|
+
1. Pre-flight: Caddy installed + daemon running, and `playwright.config.ts` env-aware (otherwise abort; `--force` overrides).
|
|
683
|
+
2. Builds the API and applies pending migrations to the test DB (`migrate:up`, with the API's own env, so it hits the same DB). A failed migration aborts the run; a project without a `migrate:up` script starts unmigrated. Then starts the compiled API. Without a build output it falls back to the project's `start` script, which migrates by itself.
|
|
684
|
+
3. Builds the App (`nuxt build`) and serves the Nitro output.
|
|
685
|
+
4. Waits for both to answer and aborts if either never does, so the suite never runs against a dead stack.
|
|
686
|
+
5. Runs `test:e2e [forwarded args]` in the App with the `.lt-dev/.env.test` bridge loaded.
|
|
687
|
+
6. Tears the stack down (processes, Caddy block, env bridge, session file) unless `--keep` is set.
|
|
688
|
+
|
|
689
|
+
The test DB outlives the run: the CLI never drops it. That is why step 2 migrates it: without the migration the DB drifts behind every environment that does migrate. A project whose Playwright global-setup resets the DB gets it passed as `MONGO_URI`.
|
|
686
690
|
|
|
687
691
|
**When to use this vs. `pnpm run test:e2e` directly:**
|
|
688
|
-
- Use **`lt dev test`** for TDD loops, ad-hoc reproduction, or when you want a single-command "
|
|
692
|
+
- Use **`lt dev test`** for TDD loops, ad-hoc reproduction, or when you want a single-command "isolated stack + run + teardown" flow.
|
|
689
693
|
- Use **direct `pnpm run test:e2e`** (or VS Code Playwright Extension, IDE test runners) for everyday work — the auto-injected `playwright.config.ts` bridge loads the `.lt-dev/.env` automatically, so the env is correct without the wrapper.
|
|
690
694
|
|
|
691
695
|
---
|
package/package.json
CHANGED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.stripComments = stripComments;
|
|
37
|
-
const ts = __importStar(require("typescript"));
|
|
38
|
-
/**
|
|
39
|
-
* Removes comments from TypeScript/JavaScript source, preserving everything else verbatim.
|
|
40
|
-
*
|
|
41
|
-
* Exists because detectors that search source for import specifiers must not read comments as
|
|
42
|
-
* code. A keyword-anchored regex is not sufficient on its own: a docblock that DOCUMENTS an import
|
|
43
|
-
* rewrite legitimately quotes the exact syntax the detector looks for. nest-server-starter's
|
|
44
|
-
* `tests/unit/bootstrap-diagnostics.spec.ts` contains
|
|
45
|
-
*
|
|
46
|
-
* * CLI's vendor conversion rewrites `from '@lenne.tech/nest-server'` to a relative `./core` path
|
|
47
|
-
*
|
|
48
|
-
* which matched `/(?:from|import|…)\s*['"]@lenne\.tech\/nest-server['"]/` and made
|
|
49
|
-
* `lt fullstack init --framework-mode vendor` warn about imports that file does not have.
|
|
50
|
-
*
|
|
51
|
-
* Uses the TypeScript scanner rather than a regex, so string literals, template literals and
|
|
52
|
-
* regex literals containing `//` or `/*` are handled correctly by construction — a hand-rolled
|
|
53
|
-
* stripper trips over `'https://…'` and over `/* ` inside a string.
|
|
54
|
-
*
|
|
55
|
-
* Comment characters are replaced with spaces instead of being deleted, so byte offsets and line
|
|
56
|
-
* numbers of the surrounding code stay unchanged — a caller can still report a meaningful
|
|
57
|
-
* position from a match.
|
|
58
|
-
*
|
|
59
|
-
* @param source - TypeScript or JavaScript source text
|
|
60
|
-
* @returns The source with every comment blanked out
|
|
61
|
-
*
|
|
62
|
-
* @example
|
|
63
|
-
* stripComments("// from 'pkg'\nimport x from 'pkg';")
|
|
64
|
-
* // => " \nimport x from 'pkg';"
|
|
65
|
-
*/
|
|
66
|
-
function stripComments(source) {
|
|
67
|
-
const scanner = ts.createScanner(ts.ScriptTarget.Latest, /* skipTrivia */ false, ts.LanguageVariant.Standard, source);
|
|
68
|
-
let result = '';
|
|
69
|
-
let token = scanner.scan();
|
|
70
|
-
while (token !== ts.SyntaxKind.EndOfFileToken) {
|
|
71
|
-
const text = scanner.getTokenText();
|
|
72
|
-
const isComment = token === ts.SyntaxKind.SingleLineCommentTrivia || token === ts.SyntaxKind.MultiLineCommentTrivia;
|
|
73
|
-
// Keep newlines so line numbers survive; blank everything else in the comment.
|
|
74
|
-
result += isComment ? text.replace(/[^\n]/g, ' ') : text;
|
|
75
|
-
token = scanner.scan();
|
|
76
|
-
}
|
|
77
|
-
return result;
|
|
78
|
-
}
|