@lenne.tech/cli 1.46.0 → 1.48.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/bin/postinstall.js +28 -5
- package/build/commands/dev/doctor.js +45 -3
- package/build/commands/fullstack/update.js +31 -0
- package/build/commands/git/force-pull.js +11 -2
- package/build/commands/git/rebase.js +11 -1
- package/build/commands/git/reset.js +36 -4
- package/build/commands/git/update.js +42 -6
- package/build/extensions/api-mode.js +32 -8
- package/build/extensions/git.js +23 -25
- package/build/extensions/server.js +24 -17
- package/build/lib/check-template-sync.js +127 -0
- package/build/lib/claude-cli.js +14 -25
- package/build/lib/codex-cli.js +24 -31
- package/build/lib/dev-api-launch.js +58 -6
- package/build/lib/dev-process.js +66 -30
- package/build/lib/dev-state.js +13 -2
- package/build/lib/dev-test-session.js +85 -20
- package/build/lib/git-env.js +46 -0
- package/build/lib/heal-check-wrapper.js +128 -11
- package/build/lib/heal-oxlintrc.js +232 -0
- package/build/lib/platform.js +107 -0
- package/build/lib/plugin-utils.js +9 -8
- package/build/lib/strip-vendor-schema-augmentation.js +13 -2
- package/build/lib/vendor-claude-md.js +9 -6
- package/build/lib/workspace-integration.js +12 -2
- package/build/templates/check/check.mjs +270 -268
- package/build/templates/check/lib/ansi.mjs +22 -0
- package/build/templates/check/lib/audit-report.mjs +412 -0
- package/build/templates/check/lib/workspace-packages.mjs +150 -0
- package/build/templates/check/lt-monorepo-pin.json +13 -0
- package/docs/commands.md +1 -11
- package/package.json +13 -14
package/bin/postinstall.js
CHANGED
|
@@ -3,9 +3,13 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* Postinstall script for lt CLI
|
|
5
5
|
* Generates static completion files on install/update (no runtime overhead)
|
|
6
|
+
*
|
|
7
|
+
* Must never fail the install: npm runs this through cmd.exe on Windows, so the
|
|
8
|
+
* former `2>/dev/null || true` guard in package.json broke `npm i -g` there
|
|
9
|
+
* instead of protecting it. The guard now lives in this file (see bottom).
|
|
6
10
|
*/
|
|
7
11
|
|
|
8
|
-
const {
|
|
12
|
+
const { execFileSync } = require('child_process');
|
|
9
13
|
const { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } = require('fs');
|
|
10
14
|
const { homedir } = require('os');
|
|
11
15
|
const { join } = require('path');
|
|
@@ -26,11 +30,17 @@ function addSourceLine(configFile, sourceLine) {
|
|
|
26
30
|
|
|
27
31
|
/**
|
|
28
32
|
* Detect user's shell
|
|
33
|
+
*
|
|
34
|
+
* Returns null when no supported shell can be identified. On Windows an unset
|
|
35
|
+
* SHELL means PowerShell or cmd.exe, which read none of the rc files below —
|
|
36
|
+
* appending to a ~/.bashrc nothing sources would only leave clutter behind.
|
|
37
|
+
* Git Bash sets SHELL, so it still gets bash completions.
|
|
29
38
|
*/
|
|
30
39
|
function detectShell() {
|
|
31
40
|
const shell = process.env.SHELL || '';
|
|
32
41
|
if (shell.includes('zsh')) return 'zsh';
|
|
33
42
|
if (shell.includes('fish')) return 'fish';
|
|
43
|
+
if (!shell && process.platform === 'win32') return null;
|
|
34
44
|
return 'bash';
|
|
35
45
|
}
|
|
36
46
|
|
|
@@ -45,8 +55,13 @@ function generateCompletionFile(shell, completionFile) {
|
|
|
45
55
|
mkdirSync(dir, { recursive: true });
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
//
|
|
49
|
-
|
|
58
|
+
// Run this package's own entry point with the current Node binary instead of
|
|
59
|
+
// resolving `lt` from PATH: during a global install the bin link may not exist
|
|
60
|
+
// yet, and on Windows `lt` is a .cmd shim that cannot be spawned without a shell.
|
|
61
|
+
const script = execFileSync(process.execPath, [join(__dirname, 'lt'), 'completion', shell], {
|
|
62
|
+
encoding: 'utf-8',
|
|
63
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
64
|
+
});
|
|
50
65
|
writeFileSync(completionFile, script);
|
|
51
66
|
return true;
|
|
52
67
|
} catch {
|
|
@@ -109,6 +124,10 @@ function main() {
|
|
|
109
124
|
}
|
|
110
125
|
|
|
111
126
|
const shell = detectShell();
|
|
127
|
+
if (!shell) {
|
|
128
|
+
return; // No supported shell (e.g. PowerShell) - nothing to install
|
|
129
|
+
}
|
|
130
|
+
|
|
112
131
|
const paths = getCompletionPaths(shell);
|
|
113
132
|
const isTTY = process.stdout.isTTY;
|
|
114
133
|
|
|
@@ -152,5 +171,9 @@ function main() {
|
|
|
152
171
|
}
|
|
153
172
|
}
|
|
154
173
|
|
|
155
|
-
// Run
|
|
156
|
-
|
|
174
|
+
// Run - completions are a convenience, so no failure may abort the install
|
|
175
|
+
try {
|
|
176
|
+
main();
|
|
177
|
+
} catch {
|
|
178
|
+
// Silent fail
|
|
179
|
+
}
|
|
@@ -169,7 +169,7 @@ const DoctorCommand = {
|
|
|
169
169
|
try {
|
|
170
170
|
const { readFileSync: read } = yield Promise.resolve().then(() => __importStar(require('fs')));
|
|
171
171
|
const { join: j } = yield Promise.resolve().then(() => __importStar(require('path')));
|
|
172
|
-
const { resolveCopySet } = yield Promise.resolve().then(() => __importStar(require('../../lib/heal-check-wrapper')));
|
|
172
|
+
const { keptWrapperReason, resolveCopySet } = yield Promise.resolve().then(() => __importStar(require('../../lib/heal-check-wrapper')));
|
|
173
173
|
const bundledCheck = j(__dirname, '..', '..', 'templates', 'check', 'check.mjs');
|
|
174
174
|
if (filesystem.exists(bundledCheck)) {
|
|
175
175
|
const missing = [];
|
|
@@ -183,12 +183,26 @@ const DoctorCommand = {
|
|
|
183
183
|
drifted.push(rel);
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
-
|
|
186
|
+
// A missing module only breaks `check` when the project's wrapper is the
|
|
187
|
+
// canonical one that imports it (or the wrapper itself is missing). An
|
|
188
|
+
// older wrapper does not import the newer modules (e.g. `lib/*.mjs`) and
|
|
189
|
+
// still runs — calling that "cannot start" would be a false alarm.
|
|
190
|
+
const wrapperDrifted = drifted.includes('scripts/check.mjs');
|
|
191
|
+
// A project wrapper that heal refuses to replace (newer release, or the same
|
|
192
|
+
// release with other content) is not drift to fix: pointing at
|
|
193
|
+
// `lt fullstack update` there would recommend a no-op or a downgrade.
|
|
194
|
+
const kept = keptWrapperReason(layout.root, bundledCheck);
|
|
195
|
+
if (kept) {
|
|
196
|
+
line('INFO', colors.cyan, `check wrapper: ${kept}`);
|
|
197
|
+
}
|
|
198
|
+
else if (missing.length > 0 && !wrapperDrifted) {
|
|
187
199
|
line('ERROR', colors.red, `check wrapper incomplete — missing ${missing.join(', ')}`);
|
|
188
200
|
line('ERROR', colors.red, ' `pnpm run check` cannot start; run `lt fullstack update` to install it');
|
|
189
201
|
}
|
|
190
202
|
else if (drifted.length > 0) {
|
|
191
|
-
|
|
203
|
+
const outdated = [...drifted, ...missing.map((rel) => `${rel} (missing)`)];
|
|
204
|
+
const verb = outdated.length === 1 ? 'differs' : 'differ';
|
|
205
|
+
line('WARN', colors.yellow, `${outdated.join(', ')} ${verb} from the canonical CLI version`);
|
|
192
206
|
line('WARN', colors.yellow, ' run `lt fullstack update` to sync it (skips uncommitted local edits)');
|
|
193
207
|
}
|
|
194
208
|
else if (filesystem.exists(j(layout.root, 'scripts', 'check.mjs'))) {
|
|
@@ -199,6 +213,34 @@ const DoctorCommand = {
|
|
|
199
213
|
catch (_a) {
|
|
200
214
|
/* best-effort diagnostics */
|
|
201
215
|
}
|
|
216
|
+
// 7.6 oxlint config filename: oxlint only auto-discovers `.oxlintrc.json`.
|
|
217
|
+
// Apps from the template before the rename carry `oxlint.json`, whose
|
|
218
|
+
// rules then silently never apply — lint passes on code the project's
|
|
219
|
+
// own rules forbid.
|
|
220
|
+
if (layout.appDir) {
|
|
221
|
+
// Behaviour-changing fix flags delete console calls once rules load.
|
|
222
|
+
try {
|
|
223
|
+
const { findDangerousFixFlagUsage } = yield Promise.resolve().then(() => __importStar(require('../../lib/heal-oxlintrc')));
|
|
224
|
+
const workspaceRoot = layout.root !== layout.appDir ? layout.root : undefined;
|
|
225
|
+
const flagged = findDangerousFixFlagUsage(layout.appDir, workspaceRoot);
|
|
226
|
+
if (flagged.length > 0) {
|
|
227
|
+
line('WARN', colors.yellow, `oxlint --fix-suggestions/--fix-dangerously in ${flagged.join(', ')}`);
|
|
228
|
+
line('WARN', colors.yellow, ' they apply behaviour-changing fixes (e.g. delete console calls); run `lt fullstack update`');
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
catch (_b) {
|
|
232
|
+
/* best-effort diagnostics */
|
|
233
|
+
}
|
|
234
|
+
const hasLegacy = filesystem.exists(filesystem.path(layout.appDir, 'oxlint.json'));
|
|
235
|
+
const hasCurrent = filesystem.exists(filesystem.path(layout.appDir, '.oxlintrc.json'));
|
|
236
|
+
if (hasLegacy && !hasCurrent) {
|
|
237
|
+
line('WARN', colors.yellow, 'oxlint config not loaded — oxlint only reads .oxlintrc.json, the app has oxlint.json');
|
|
238
|
+
line('WARN', colors.yellow, ' run `lt fullstack update` to rename it');
|
|
239
|
+
}
|
|
240
|
+
else if (hasLegacy) {
|
|
241
|
+
line('WARN', colors.yellow, 'oxlint.json is ignored next to .oxlintrc.json — merge its rules and delete it');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
202
244
|
// 8. Slug ↔ path: is this project's slug registered to a DIFFERENT checkout?
|
|
203
245
|
// Two clones of the same project (same package.json "name") share the
|
|
204
246
|
// slug → Caddy block / ports / DB and collide. Surface it proactively.
|
|
@@ -14,6 +14,7 @@ const dev_patches_1 = require("../../lib/dev-patches");
|
|
|
14
14
|
const framework_detection_1 = require("../../lib/framework-detection");
|
|
15
15
|
const frontend_framework_detection_1 = require("../../lib/frontend-framework-detection");
|
|
16
16
|
const heal_check_wrapper_1 = require("../../lib/heal-check-wrapper");
|
|
17
|
+
const heal_oxlintrc_1 = require("../../lib/heal-oxlintrc");
|
|
17
18
|
const heal_vendor_migrate_store_1 = require("../../lib/heal-vendor-migrate-store");
|
|
18
19
|
const vendor_claude_md_1 = require("../../lib/vendor-claude-md");
|
|
19
20
|
/**
|
|
@@ -213,6 +214,36 @@ const NewCommand = {
|
|
|
213
214
|
warning(` Check wrapper NOT updated: ${entry}`);
|
|
214
215
|
}
|
|
215
216
|
}
|
|
217
|
+
// ── Self-heal: let oxlint actually load the app's config ───────────────
|
|
218
|
+
//
|
|
219
|
+
// oxlint only auto-discovers `.oxlintrc.json`; the app template shipped
|
|
220
|
+
// `oxlint.json`, so its rules never applied. Runs AFTER the check wrapper
|
|
221
|
+
// heal on purpose: the rename is refused while anything still passes
|
|
222
|
+
// `--fix-suggestions`, which would delete console calls once the config loads.
|
|
223
|
+
if (appDir) {
|
|
224
|
+
const workspaceRoot = isWorkspace ? cwd : undefined;
|
|
225
|
+
// 1. Strip the behaviour-changing fix flags first (the root wrapper was
|
|
226
|
+
// handled above). A file it has to skip keeps blocking the rename below.
|
|
227
|
+
const flags = (0, heal_oxlintrc_1.healDangerousOxlintFixFlags)(appDir, workspaceRoot);
|
|
228
|
+
if (flags.changed.length > 0) {
|
|
229
|
+
info('');
|
|
230
|
+
success(` Removed --fix-suggestions/--fix-dangerously from: ${flags.changed.join(', ')}`);
|
|
231
|
+
}
|
|
232
|
+
for (const entry of flags.skipped) {
|
|
233
|
+
warning(` Fix flag NOT removed: ${entry}`);
|
|
234
|
+
}
|
|
235
|
+
// 2. Rename — the gate inside re-checks every file.
|
|
236
|
+
const oxlintrc = (0, heal_oxlintrc_1.healOxlintrcFilename)(appDir, workspaceRoot);
|
|
237
|
+
if (oxlintrc.action === 'renamed') {
|
|
238
|
+
info('');
|
|
239
|
+
success(` Renamed the oxlint config so oxlint loads it: ${oxlintrc.changed.join(', ')}`);
|
|
240
|
+
info(' Its rules apply from now on — expect new lint findings on the next check.');
|
|
241
|
+
}
|
|
242
|
+
else if (oxlintrc.detail) {
|
|
243
|
+
info('');
|
|
244
|
+
warning(` oxlint config NOT renamed: ${oxlintrc.detail}`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
216
247
|
// ── Self-heal: keep `.lt-dev/` out of git ──────────────────────────────
|
|
217
248
|
//
|
|
218
249
|
// Newer templates ship the entry, but pre-existing projects predate it —
|
|
@@ -55,8 +55,17 @@ const NewCommand = {
|
|
|
55
55
|
else {
|
|
56
56
|
info('No local changes to discard.');
|
|
57
57
|
}
|
|
58
|
-
// Show commits that would be lost
|
|
59
|
-
|
|
58
|
+
// Show commits that would be lost (none listed when the upstream ref is unknown locally).
|
|
59
|
+
// `2>/dev/null || echo ""` used to swallow the failure, but cmd.exe on Windows has no
|
|
60
|
+
// /dev/null: the redirect failed first and `echo ""` then reported success, so the preview
|
|
61
|
+
// silently claimed there was nothing ahead. try/catch behaves the same on every platform.
|
|
62
|
+
let localCommits = '';
|
|
63
|
+
try {
|
|
64
|
+
localCommits = yield run(`git log origin/${branch}..HEAD --oneline`);
|
|
65
|
+
}
|
|
66
|
+
catch (_d) {
|
|
67
|
+
// ignore - no upstream ref, so nothing ahead to report
|
|
68
|
+
}
|
|
60
69
|
if (localCommits === null || localCommits === void 0 ? void 0 : localCommits.trim()) {
|
|
61
70
|
info('');
|
|
62
71
|
info('Local commits that would be lost:');
|
|
@@ -54,7 +54,17 @@ const NewCommand = {
|
|
|
54
54
|
warning('DRY-RUN MODE - No changes will be made');
|
|
55
55
|
info('');
|
|
56
56
|
// Show commits that would be rebased
|
|
57
|
-
|
|
57
|
+
// try/catch, not `2>/dev/null || echo ""`: on Windows that `echo` succeeds
|
|
58
|
+
// and the git log never runs, so a dry-run would report zero commits to
|
|
59
|
+
// rebase for a branch that has some — a preview that quietly understates
|
|
60
|
+
// what the real command will do.
|
|
61
|
+
let commitsToRebase = '';
|
|
62
|
+
try {
|
|
63
|
+
commitsToRebase = yield run(`git log ${baseBranchPreview}..HEAD --oneline`);
|
|
64
|
+
}
|
|
65
|
+
catch (_k) {
|
|
66
|
+
// Unknown base ref — nothing to preview, which the check below handles.
|
|
67
|
+
}
|
|
58
68
|
if (commitsToRebase === null || commitsToRebase === void 0 ? void 0 : commitsToRebase.trim()) {
|
|
59
69
|
const commitCount = commitsToRebase.trim().split('\n').length;
|
|
60
70
|
info(`Would rebase ${commitCount} commit(s) from branch "${branch}" onto "${baseBranchPreview}":`);
|
|
@@ -10,6 +10,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const path_1 = require("path");
|
|
13
|
+
const git_env_1 = require("../../lib/git-env");
|
|
14
|
+
/**
|
|
15
|
+
* Environment that keeps the `git ls-remote` below non-interactive.
|
|
16
|
+
*
|
|
17
|
+
* This used to be a POSIX prefix on the command string
|
|
18
|
+
* (`GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-…}" git …`).
|
|
19
|
+
* `system.run` shells out through `cmd.exe` on Windows, which reads
|
|
20
|
+
* `VAR=value cmd` as a command name, not as an assignment. Paired with the
|
|
21
|
+
* trailing `|| true` the call then "succeeded" with empty output, so every
|
|
22
|
+
* `lt git reset` on Windows aborted with "No remote branch … found!" — the
|
|
23
|
+
* ls-remote had never run. Handing the variables to the child as its
|
|
24
|
+
* environment works on every platform.
|
|
25
|
+
*
|
|
26
|
+
* The `||` keeps the semantics of the shell's `:-` default: a caller who
|
|
27
|
+
* configured ssh deliberately (a user with a custom agent, or a test harness
|
|
28
|
+
* pinning the behaviour) still wins. See `git.ts#gitInstalled` for why the
|
|
29
|
+
* assignment must never be unconditional.
|
|
30
|
+
*/
|
|
13
31
|
/**
|
|
14
32
|
* Reset current branch
|
|
15
33
|
*/
|
|
@@ -46,7 +64,13 @@ const NewCommand = {
|
|
|
46
64
|
return;
|
|
47
65
|
}
|
|
48
66
|
// Check remote (use short SSH timeout so ls-remote doesn't hang in offline environments)
|
|
49
|
-
|
|
67
|
+
let remoteBranch = '';
|
|
68
|
+
try {
|
|
69
|
+
remoteBranch = yield system.run(`git ls-remote --heads origin ${branch}`, { env: (0, git_env_1.nonInteractiveGitEnv)() });
|
|
70
|
+
}
|
|
71
|
+
catch (_d) {
|
|
72
|
+
// ignore - unreachable remote is reported as "no remote branch" below
|
|
73
|
+
}
|
|
50
74
|
if (!remoteBranch) {
|
|
51
75
|
error(`No remote branch ${branch} found!`);
|
|
52
76
|
return;
|
|
@@ -68,8 +92,14 @@ const NewCommand = {
|
|
|
68
92
|
else {
|
|
69
93
|
info('No local changes to discard.');
|
|
70
94
|
}
|
|
71
|
-
// Show commits that would be lost
|
|
72
|
-
|
|
95
|
+
// Show commits that would be lost (none listed when the upstream ref is unknown locally)
|
|
96
|
+
let localCommits = '';
|
|
97
|
+
try {
|
|
98
|
+
localCommits = yield system.run(`git log origin/${branch}..HEAD --oneline`);
|
|
99
|
+
}
|
|
100
|
+
catch (_e) {
|
|
101
|
+
// ignore - no upstream ref, so nothing ahead to report
|
|
102
|
+
}
|
|
73
103
|
if (localCommits === null || localCommits === void 0 ? void 0 : localCommits.trim()) {
|
|
74
104
|
info('');
|
|
75
105
|
info('Local commits that would be lost:');
|
|
@@ -109,7 +139,9 @@ const NewCommand = {
|
|
|
109
139
|
const projectDir = (0, path_1.dirname)(pkgPath);
|
|
110
140
|
const detectedPm = toolbox.pm.detect(projectDir);
|
|
111
141
|
const installSpin = spin(`Install packages using ${detectedPm}`);
|
|
112
|
-
|
|
142
|
+
// `cwd` instead of `cd <dir> &&`: cmd.exe's `cd` does not switch drives,
|
|
143
|
+
// and it needs no quoting for paths with spaces.
|
|
144
|
+
yield system.run(toolbox.pm.install(detectedPm), { cwd: projectDir });
|
|
113
145
|
installSpin.succeed();
|
|
114
146
|
}
|
|
115
147
|
// Success info
|
|
@@ -10,6 +10,22 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
const path_1 = require("path");
|
|
13
|
+
const git_env_1 = require("../../lib/git-env");
|
|
14
|
+
/**
|
|
15
|
+
* Environment that keeps the `git` calls below non-interactive.
|
|
16
|
+
*
|
|
17
|
+
* This used to be a POSIX prefix on the command string
|
|
18
|
+
* (`GIT_TERMINAL_PROMPT=0 GIT_SSH_COMMAND="${GIT_SSH_COMMAND:-…}" git …`).
|
|
19
|
+
* `system.run` shells out through `cmd.exe` on Windows, which does not read
|
|
20
|
+
* `VAR=value cmd` as an assignment but as a command name — so every `lt git
|
|
21
|
+
* update` failed there with "'GIT_TERMINAL_PROMPT' is not recognized". Handing
|
|
22
|
+
* the variables to the child as its environment works on every platform.
|
|
23
|
+
*
|
|
24
|
+
* The `||` keeps the semantics of the shell's `:-` default: a caller who
|
|
25
|
+
* configured ssh deliberately (a user with a custom agent, or a test harness
|
|
26
|
+
* pinning the behaviour) still wins. See `git.ts#gitInstalled` for why the
|
|
27
|
+
* assignment must never be unconditional.
|
|
28
|
+
*/
|
|
13
29
|
/**
|
|
14
30
|
* Update branch
|
|
15
31
|
*/
|
|
@@ -47,10 +63,22 @@ const NewCommand = {
|
|
|
47
63
|
info('');
|
|
48
64
|
info(`Current branch: ${branch}`);
|
|
49
65
|
info('');
|
|
50
|
-
// Fetch to see incoming changes (use short SSH timeout so it doesn't hang offline)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
66
|
+
// Fetch to see incoming changes (use short SSH timeout so it doesn't hang offline).
|
|
67
|
+
// Best effort: offline or without credentials the preview falls back to the local refs.
|
|
68
|
+
try {
|
|
69
|
+
yield run('git fetch', { env: (0, git_env_1.nonInteractiveGitEnv)() });
|
|
70
|
+
}
|
|
71
|
+
catch (_d) {
|
|
72
|
+
// ignore - a failed fetch only makes the preview less current
|
|
73
|
+
}
|
|
74
|
+
// Check for incoming commits (none yet when the upstream ref is unknown locally)
|
|
75
|
+
let incomingCommits = '';
|
|
76
|
+
try {
|
|
77
|
+
incomingCommits = yield run(`git log ${branch}..origin/${branch} --oneline`);
|
|
78
|
+
}
|
|
79
|
+
catch (_e) {
|
|
80
|
+
// ignore - no upstream ref, so nothing incoming to report
|
|
81
|
+
}
|
|
54
82
|
const commits = (incomingCommits === null || incomingCommits === void 0 ? void 0 : incomingCommits.trim().split('\n').filter((c) => c)) || [];
|
|
55
83
|
if (commits.length > 0) {
|
|
56
84
|
info(`Incoming commits (${commits.length}):`);
|
|
@@ -78,7 +106,13 @@ const NewCommand = {
|
|
|
78
106
|
const timer = startTimer();
|
|
79
107
|
// Update
|
|
80
108
|
const updateSpin = spin(`Update branch ${branch}`);
|
|
81
|
-
|
|
109
|
+
try {
|
|
110
|
+
yield run('git fetch', { env: (0, git_env_1.nonInteractiveGitEnv)() });
|
|
111
|
+
}
|
|
112
|
+
catch (_f) {
|
|
113
|
+
// ignore - `git pull --rebase` below fetches again and reports the real failure
|
|
114
|
+
}
|
|
115
|
+
yield run('git pull --rebase', { env: (0, git_env_1.nonInteractiveGitEnv)() });
|
|
82
116
|
updateSpin.succeed();
|
|
83
117
|
// Install packages (unless skipped) with correctly detected package manager (supports monorepo lockfiles)
|
|
84
118
|
if (!skipInstall) {
|
|
@@ -87,7 +121,9 @@ const NewCommand = {
|
|
|
87
121
|
const projectDir = (0, path_1.dirname)(pkgPath);
|
|
88
122
|
const detectedPm = toolbox.pm.detect(projectDir);
|
|
89
123
|
const installSpin = spin(`Install packages using ${detectedPm}`);
|
|
90
|
-
|
|
124
|
+
// `cwd` instead of `cd <dir> &&`: cmd.exe's `cd` does not switch drives,
|
|
125
|
+
// and it needs no quoting for paths with spaces.
|
|
126
|
+
yield run(toolbox.pm.install(detectedPm), { cwd: projectDir });
|
|
91
127
|
installSpin.succeed();
|
|
92
128
|
}
|
|
93
129
|
}
|
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.ApiMode = void 0;
|
|
13
|
+
const glob_1 = require("glob");
|
|
13
14
|
const path_1 = require("path");
|
|
14
15
|
/**
|
|
15
16
|
* API Mode processing for nest-server-starter template
|
|
@@ -114,10 +115,7 @@ class ApiMode {
|
|
|
114
115
|
// 1. Delete files matching filePatterns
|
|
115
116
|
if (modeConfig.filePatterns) {
|
|
116
117
|
for (const pattern of modeConfig.filePatterns) {
|
|
117
|
-
const
|
|
118
|
-
matching: pattern,
|
|
119
|
-
});
|
|
120
|
-
for (const file of matches) {
|
|
118
|
+
for (const file of this.globFiles(projectPath, pattern)) {
|
|
121
119
|
this.filesystem.remove(file);
|
|
122
120
|
}
|
|
123
121
|
}
|
|
@@ -169,6 +167,29 @@ class ApiMode {
|
|
|
169
167
|
this.cleanOrphanImports(projectPath);
|
|
170
168
|
});
|
|
171
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Resolve a POSIX-style glob pattern against `baseDir` and return the
|
|
172
|
+
* matching files as absolute, OS-native paths.
|
|
173
|
+
*
|
|
174
|
+
* Deliberately NOT `filesystem.find()`: gluegun/fs-jetpack builds its
|
|
175
|
+
* matcher by string-concatenating the *resolved* base path in front of
|
|
176
|
+
* the pattern (fs-jetpack/lib/utils/matcher.js). On Windows that base
|
|
177
|
+
* path is backslash-separated, and minimatch reads a backslash inside a
|
|
178
|
+
* pattern as an escape character — so a base of `C:\project\src`
|
|
179
|
+
* collapses to the literal `C:projectsrc` and nothing ever matches.
|
|
180
|
+
* Effect: on Windows no file was deleted and no region was stripped,
|
|
181
|
+
* silently. `glob` keeps pattern and base directory apart and
|
|
182
|
+
* normalises separators itself, so it behaves the same on every
|
|
183
|
+
* platform.
|
|
184
|
+
*/
|
|
185
|
+
globFiles(baseDir, pattern) {
|
|
186
|
+
return (0, glob_1.globSync)(pattern, {
|
|
187
|
+
absolute: true,
|
|
188
|
+
cwd: baseDir,
|
|
189
|
+
dot: true,
|
|
190
|
+
nodir: true,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
172
193
|
/**
|
|
173
194
|
* Strip region markers from all .ts files
|
|
174
195
|
*
|
|
@@ -181,7 +202,7 @@ class ApiMode {
|
|
|
181
202
|
if (!this.filesystem.exists(dir)) {
|
|
182
203
|
continue;
|
|
183
204
|
}
|
|
184
|
-
const files = this.
|
|
205
|
+
const files = this.globFiles(dir, '**/*.ts');
|
|
185
206
|
for (const file of files) {
|
|
186
207
|
const content = this.filesystem.read(file);
|
|
187
208
|
if (!content) {
|
|
@@ -196,7 +217,10 @@ class ApiMode {
|
|
|
196
217
|
// graphql` block with an explicit `graphQl: false,` so GraphQL
|
|
197
218
|
// is cleanly disabled.
|
|
198
219
|
let processed;
|
|
199
|
-
|
|
220
|
+
// `basename` instead of `endsWith('/config.env.ts')`: on Windows the
|
|
221
|
+
// path separator is a backslash, so the POSIX-only suffix check never
|
|
222
|
+
// matched and config.env.ts was processed like any other file.
|
|
223
|
+
if (removeMarker === 'graphql' && (0, path_1.basename)(file) === 'config.env.ts') {
|
|
200
224
|
processed = this.replaceGraphqlRegionsWithDisabled(content, keepMarker);
|
|
201
225
|
}
|
|
202
226
|
else {
|
|
@@ -299,7 +323,7 @@ class ApiMode {
|
|
|
299
323
|
if (!this.filesystem.exists(dir)) {
|
|
300
324
|
continue;
|
|
301
325
|
}
|
|
302
|
-
const files = this.
|
|
326
|
+
const files = this.globFiles(dir, '**/*.ts');
|
|
303
327
|
for (const file of files) {
|
|
304
328
|
const content = this.filesystem.read(file);
|
|
305
329
|
if (!content) {
|
|
@@ -444,7 +468,7 @@ class ApiMode {
|
|
|
444
468
|
if (!this.filesystem.exists(dir)) {
|
|
445
469
|
continue;
|
|
446
470
|
}
|
|
447
|
-
const files = this.
|
|
471
|
+
const files = this.globFiles(dir, '**/*.ts');
|
|
448
472
|
for (const file of files) {
|
|
449
473
|
const content = this.filesystem.read(file);
|
|
450
474
|
if (!content) {
|
package/build/extensions/git.js
CHANGED
|
@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
10
10
|
};
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.Git = void 0;
|
|
13
|
+
const git_env_1 = require("../lib/git-env");
|
|
13
14
|
/**
|
|
14
15
|
* Git functions
|
|
15
16
|
*/
|
|
@@ -119,8 +120,17 @@ class Git {
|
|
|
119
120
|
const result = [];
|
|
120
121
|
// Toolbox features
|
|
121
122
|
const { system } = this.toolbox;
|
|
122
|
-
//
|
|
123
|
-
|
|
123
|
+
// Refresh first, but never let a failed fetch cost us the branch list: the
|
|
124
|
+
// two commands used to be chained with `;`, which cmd.exe does not honour as
|
|
125
|
+
// a separator at all, so on Windows this ran neither reliably. They are two
|
|
126
|
+
// calls now — the fetch is best-effort, the listing is the actual result.
|
|
127
|
+
try {
|
|
128
|
+
yield system.run('git fetch', { env: (0, git_env_1.nonInteractiveGitEnv)() });
|
|
129
|
+
}
|
|
130
|
+
catch (_a) {
|
|
131
|
+
// Offline or no remote — `git show-branch` still lists what is local.
|
|
132
|
+
}
|
|
133
|
+
const branches = yield system.run('git show-branch --list');
|
|
124
134
|
branches.split('\n').forEach((item) => {
|
|
125
135
|
const matches = item.match(/\[(.*?)]/);
|
|
126
136
|
if (matches) {
|
|
@@ -197,27 +207,6 @@ class Git {
|
|
|
197
207
|
/**
|
|
198
208
|
* Check if git is installed (cached for performance)
|
|
199
209
|
*/
|
|
200
|
-
/**
|
|
201
|
-
* Why the git calls in this repo spell `GIT_SSH_COMMAND="\${GIT_SSH_COMMAND:-…}"` (shell default)
|
|
202
|
-
* rather than assigning it outright.
|
|
203
|
-
*
|
|
204
|
-
* These commands do a best-effort `git fetch` to see whether the branch is
|
|
205
|
-
* behind. They set `BatchMode=yes` so ssh fails instead of PROMPTING — but an
|
|
206
|
-
* agent that stalls is not a prompt. On a 1Password-backed machine the agent is
|
|
207
|
-
* reachable and every signature needs an interactive approval; unattended it
|
|
208
|
-
* waits and then reports `communication with agent failed`. Measured: **61 s per
|
|
209
|
-
* fetch**, so `lt git update --dry-run` took 62 s and `lt git create --dry-run`
|
|
210
|
-
* 123 s (two fetches). `ConnectTimeout` does not bound it — that covers the TCP
|
|
211
|
-
* connect, not the agent.
|
|
212
|
-
*
|
|
213
|
-
* Waiting for a human to approve a key is legitimate for an interactive command,
|
|
214
|
-
* so the default is unchanged. What was wrong is that the assignment was
|
|
215
|
-
* UNCONDITIONAL: it overrode a caller who had deliberately configured ssh,
|
|
216
|
-
* including a test harness trying to make the behaviour deterministic. The
|
|
217
|
-
* `:-` default respects an existing value and keeps the old behaviour when there
|
|
218
|
-
* is none. `IdentityAgent=none` in the caller's env then drops the same fetch to
|
|
219
|
-
* ~1 s with a clean `Permission denied (publickey)`.
|
|
220
|
-
*/
|
|
221
210
|
gitInstalled() {
|
|
222
211
|
return __awaiter(this, void 0, void 0, function* () {
|
|
223
212
|
// Return cached result if available
|
|
@@ -272,8 +261,17 @@ class Git {
|
|
|
272
261
|
if (opts.spin) {
|
|
273
262
|
searchSpin = spin(opts.spinText);
|
|
274
263
|
}
|
|
275
|
-
// Update infos
|
|
276
|
-
|
|
264
|
+
// Update infos. Best-effort, but expressed as try/catch rather than
|
|
265
|
+
// `2>/dev/null || true`: on Windows `true` resolves to Git-for-Windows'
|
|
266
|
+
// `true.exe`, so the pipeline succeeded with empty output and the fetch
|
|
267
|
+
// never ran — a silent no-op that read as a successful refresh.
|
|
268
|
+
let fetch = '';
|
|
269
|
+
try {
|
|
270
|
+
fetch = yield system.run('git fetch', { env: (0, git_env_1.nonInteractiveGitEnv)() });
|
|
271
|
+
}
|
|
272
|
+
catch (_b) {
|
|
273
|
+
// Offline or no remote — the branch search below still works on local refs.
|
|
274
|
+
}
|
|
277
275
|
if (fetch.length && !fetch.startsWith('remote')) {
|
|
278
276
|
info(`Could not update infos ${fetch.length}`);
|
|
279
277
|
}
|
|
@@ -44,6 +44,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.Server = void 0;
|
|
46
46
|
const crypto = __importStar(require("crypto"));
|
|
47
|
+
const glob_1 = require("glob");
|
|
47
48
|
const path_1 = require("path");
|
|
48
49
|
const ts = __importStar(require("typescript"));
|
|
49
50
|
const adopt_upstream_build_allowlist_1 = require("../lib/adopt-upstream-build-allowlist");
|
|
@@ -1232,13 +1233,15 @@ class Server {
|
|
|
1232
1233
|
// only as type annotations — any runtime `new Request(...)` calls refer
|
|
1233
1234
|
// to the global Fetch API Request, not the express one.
|
|
1234
1235
|
const expressImportRegex = /^import\s+\{([^}]*)\}\s+from\s+['"]express['"]\s*;?\s*$/gm;
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1236
|
+
// `glob`, not `filesystem.find`: jetpack builds its matcher by concatenating the
|
|
1237
|
+
// RESOLVED base path in front of the pattern, and minimatch reads a backslash
|
|
1238
|
+
// inside a PATTERN as an escape. On Windows `C:\…\core` + `**/*.ts` therefore
|
|
1239
|
+
// collapses to a literal that matches nothing, so this loop silently patched
|
|
1240
|
+
// no files at all. (minimatch 3 normalised the pattern's separators; the repo
|
|
1241
|
+
// now resolves the hoisted minimatch 10, which does not — see package.json.)
|
|
1242
|
+
// `glob` keeps base and pattern apart and returns absolute OS-native paths.
|
|
1243
|
+
const vendoredTsFiles = (0, glob_1.globSync)('**/*.ts', { absolute: true, cwd: coreDir, dot: true, nodir: true });
|
|
1244
|
+
for (const absPath of vendoredTsFiles) {
|
|
1242
1245
|
if (!filesystem.exists(absPath))
|
|
1243
1246
|
continue;
|
|
1244
1247
|
const content = filesystem.read(absPath) || '';
|
|
@@ -2516,20 +2519,24 @@ class Server {
|
|
|
2516
2519
|
* @param pattern Optional regex for more flexible matching
|
|
2517
2520
|
*/
|
|
2518
2521
|
findStaleImports(dest, needle, pattern) {
|
|
2522
|
+
// Patterns are relative to `dest` and passed to `glob` rather than
|
|
2523
|
+
// `filesystem.find`: jetpack concatenates the resolved base path in front of
|
|
2524
|
+
// the pattern, and minimatch treats a backslash inside a PATTERN as an escape,
|
|
2525
|
+
// so on Windows every slash-bearing pattern below matched nothing. This
|
|
2526
|
+
// function would then report "no stale imports" for a project that has them —
|
|
2527
|
+
// a false clean bill of health on the one check meant to catch a broken
|
|
2528
|
+
// conversion. See the same fix at the vendored-express patch above.
|
|
2519
2529
|
const globs = [
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2530
|
+
'src/server/**/*.ts',
|
|
2531
|
+
'src/main.ts',
|
|
2532
|
+
'src/config.env.ts',
|
|
2533
|
+
'tests/**/*.ts',
|
|
2534
|
+
'migrations/**/*.ts',
|
|
2535
|
+
'scripts/**/*.ts',
|
|
2526
2536
|
];
|
|
2527
2537
|
const stale = [];
|
|
2528
2538
|
for (const glob of globs) {
|
|
2529
|
-
const files =
|
|
2530
|
-
matching: glob.replace(`${dest}/`, ''),
|
|
2531
|
-
recursive: true,
|
|
2532
|
-
}) || [];
|
|
2539
|
+
const files = (0, glob_1.globSync)(glob, { absolute: true, cwd: dest, dot: true, nodir: true });
|
|
2533
2540
|
for (const file of files) {
|
|
2534
2541
|
const raw = this.filesystem.read(file) || '';
|
|
2535
2542
|
// Strip comments before matching. The keyword-anchored pattern is not enough on its own:
|