@lenne.tech/cli 1.47.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/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/squash.js +4 -1
- package/build/commands/git/update.js +42 -6
- package/build/extensions/api-mode.js +101 -8
- package/build/extensions/frontend-helper.js +11 -12
- package/build/extensions/git.js +23 -25
- package/build/extensions/server.js +66 -49
- 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 +92 -14
- package/build/lib/dev-package-manager.js +13 -9
- package/build/lib/dev-process.js +66 -30
- package/build/lib/dev-state.js +13 -2
- package/build/lib/dev-test-session.js +166 -55
- package/build/lib/fail-run.js +11 -0
- package/build/lib/git-env.js +46 -0
- package/build/lib/heal-oxlintrc.js +20 -3
- 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/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 +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
|
@@ -23,8 +23,10 @@ const dev_ticket_1 = require("../../lib/dev-ticket");
|
|
|
23
23
|
* App mode (default) runs the Playwright suite against a fully ISOLATED test
|
|
24
24
|
* stack (own URLs / ports / Caddy block / dedicated `<…>-test` database) that
|
|
25
25
|
* runs parallel to — and never touches — the developer's `lt dev up` session.
|
|
26
|
-
*
|
|
27
|
-
*
|
|
26
|
+
* That DB outlives the run, so pending migrations are applied to it before the
|
|
27
|
+
* API starts; a project's Playwright global-setup may reset it on top (the CLI
|
|
28
|
+
* does not). The stack is torn down automatically when the run finishes
|
|
29
|
+
* (residue-free).
|
|
28
30
|
*
|
|
29
31
|
* API mode (`--api`) runs the standalone API test suite (`pnpm test:e2e` in the
|
|
30
32
|
* API), which already isolates itself on its own DB — no stack is brought up.
|
|
@@ -225,7 +227,7 @@ const TestCommand = {
|
|
|
225
227
|
try {
|
|
226
228
|
const ctx = yield (0, dev_test_session_1.bringUpTestSession)(layout, identity, log, { devDbName });
|
|
227
229
|
const env = Object.assign(Object.assign(Object.assign(Object.assign({}, process.env), readBridgeEnv(layout.root)), {
|
|
228
|
-
//
|
|
230
|
+
// THIS db, for a project whose global-setup resets or cleans it (allow-listed).
|
|
229
231
|
MONGO_URI: `mongodb://127.0.0.1/${ctx.dbName}` }), (ctx.apiLogPath ? { NEST_SERVER_LOG: ctx.apiLogPath } : {}));
|
|
230
232
|
if (debug) {
|
|
231
233
|
env.PWDEBUG = '1';
|
|
@@ -488,7 +488,7 @@ const NewCommand = {
|
|
|
488
488
|
yield system.run(`cd ${projectDir} && git init --initial-branch=dev`);
|
|
489
489
|
}
|
|
490
490
|
catch (err) {
|
|
491
|
-
|
|
491
|
+
ngBaseSpinner.fail(`Failed to initialize git: ${err.message}`);
|
|
492
492
|
(0, fail_run_1.failRun)(toolbox);
|
|
493
493
|
return;
|
|
494
494
|
}
|
|
@@ -498,7 +498,7 @@ const NewCommand = {
|
|
|
498
498
|
yield system.run(`cd ${projectDir} && git remote add origin ${gitLink}`);
|
|
499
499
|
}
|
|
500
500
|
catch (err) {
|
|
501
|
-
|
|
501
|
+
ngBaseSpinner.fail(`Failed to add remote: ${err.message}`);
|
|
502
502
|
(0, fail_run_1.failRun)(toolbox);
|
|
503
503
|
return;
|
|
504
504
|
}
|
|
@@ -526,7 +526,7 @@ const NewCommand = {
|
|
|
526
526
|
});
|
|
527
527
|
}
|
|
528
528
|
if (!frontendResult.success) {
|
|
529
|
-
|
|
529
|
+
ngBaseSpinner.fail(`Failed to set up ${frontend} frontend: ${frontendResult.path}`);
|
|
530
530
|
(0, fail_run_1.failRun)(toolbox);
|
|
531
531
|
return;
|
|
532
532
|
}
|
|
@@ -774,6 +774,15 @@ const NewCommand = {
|
|
|
774
774
|
// For tests
|
|
775
775
|
return `new workspace ${projectDir} with ${name}`;
|
|
776
776
|
}
|
|
777
|
+
else {
|
|
778
|
+
// The frontend setup reported success but left no `projects/app`. Without this
|
|
779
|
+
// branch the command fell off the end here: no message, no failing exit code,
|
|
780
|
+
// and `ngBaseSpinner` still spinning — which keeps the event loop alive (see
|
|
781
|
+
// the spinner note in CLAUDE.md), so the process never returned at all.
|
|
782
|
+
ngBaseSpinner.fail(`${frontend} was set up but "${projectDir}/projects/app" does not exist — cannot continue`);
|
|
783
|
+
(0, fail_run_1.failRun)(toolbox);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
777
786
|
}),
|
|
778
787
|
};
|
|
779
788
|
exports.default = NewCommand;
|
|
@@ -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
|
|
@@ -129,7 +129,10 @@ const NewCommand = {
|
|
|
129
129
|
const mergeBaseSpin = spin(`Get merge ${base}`);
|
|
130
130
|
const mergeBase = yield git.getMergeBase(base);
|
|
131
131
|
if (!mergeBase) {
|
|
132
|
-
error
|
|
132
|
+
// `fail` rather than `error` + `return`: a running spinner holds the event
|
|
133
|
+
// loop open (ora resumes stdin and keeps a render interval), so the old
|
|
134
|
+
// shape did not just print and leave — the command never exited at all.
|
|
135
|
+
mergeBaseSpin.fail(`No merge base found with ${base}!`);
|
|
133
136
|
return;
|
|
134
137
|
}
|
|
135
138
|
mergeBaseSpin.succeed();
|
|
@@ -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,7 +10,9 @@ 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");
|
|
15
|
+
const config_env_graphql_1 = require("../lib/config-env-graphql");
|
|
14
16
|
/**
|
|
15
17
|
* API Mode processing for nest-server-starter template
|
|
16
18
|
*
|
|
@@ -45,11 +47,18 @@ class ApiMode {
|
|
|
45
47
|
// REST mode: remove graphql regions, keep rest regions
|
|
46
48
|
yield this.removeMode(projectPath, manifest, 'graphql', 'rest');
|
|
47
49
|
yield this.modifyConfigEnvForRest(projectPath);
|
|
50
|
+
this.assertGraphQlDisabled(projectPath);
|
|
48
51
|
}
|
|
49
52
|
else if (mode === 'GraphQL') {
|
|
50
53
|
// GraphQL mode: remove rest regions, keep graphql regions
|
|
51
54
|
yield this.removeMode(projectPath, manifest, 'rest', 'graphql');
|
|
52
55
|
}
|
|
56
|
+
// Both conversions above must leave no marker behind. Verified rather than
|
|
57
|
+
// assumed: a strip that quietly does nothing reports success just as loudly as
|
|
58
|
+
// one that worked (see assertNoMarkersRemain).
|
|
59
|
+
if (mode !== 'Both') {
|
|
60
|
+
this.assertNoMarkersRemain(projectPath, mode === 'Rest' ? 'graphql' : 'rest');
|
|
61
|
+
}
|
|
53
62
|
// Remove manifest and strip-markers script
|
|
54
63
|
this.filesystem.remove(manifestPath);
|
|
55
64
|
this.filesystem.remove((0, path_1.join)(projectPath, 'scripts', 'strip-api-mode-markers.mjs'));
|
|
@@ -114,10 +123,7 @@ class ApiMode {
|
|
|
114
123
|
// 1. Delete files matching filePatterns
|
|
115
124
|
if (modeConfig.filePatterns) {
|
|
116
125
|
for (const pattern of modeConfig.filePatterns) {
|
|
117
|
-
const
|
|
118
|
-
matching: pattern,
|
|
119
|
-
});
|
|
120
|
-
for (const file of matches) {
|
|
126
|
+
for (const file of this.globFiles(projectPath, pattern)) {
|
|
121
127
|
this.filesystem.remove(file);
|
|
122
128
|
}
|
|
123
129
|
}
|
|
@@ -169,6 +175,90 @@ class ApiMode {
|
|
|
169
175
|
this.cleanOrphanImports(projectPath);
|
|
170
176
|
});
|
|
171
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Final check of the REST conversion: every environment block in `config.env.ts`
|
|
180
|
+
* must disable GraphQL.
|
|
181
|
+
*
|
|
182
|
+
* `CoreModule.forRoot` reads a missing `graphQl` as ENABLED, so a block without it
|
|
183
|
+
* yields a project that fails at START time with `Cannot determine a GraphQL output
|
|
184
|
+
* type for the "arguments"` — long after the generator reported success. Every step
|
|
185
|
+
* before this one REPLACES an existing property; none of them adds a missing one, so
|
|
186
|
+
* nothing guaranteed the outcome.
|
|
187
|
+
*
|
|
188
|
+
* Repairs what it can (insert the switch) and throws when a block still lacks it,
|
|
189
|
+
* rather than handing over a project that cannot boot.
|
|
190
|
+
*/
|
|
191
|
+
assertGraphQlDisabled(projectPath) {
|
|
192
|
+
const configPath = (0, path_1.join)(projectPath, 'src', 'config.env.ts');
|
|
193
|
+
const content = this.filesystem.read(configPath);
|
|
194
|
+
if (!content) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const { added, content: repaired } = (0, config_env_graphql_1.disableGraphQlInEveryEnvBlock)(content);
|
|
198
|
+
if (added.length > 0) {
|
|
199
|
+
this.filesystem.write(configPath, repaired);
|
|
200
|
+
}
|
|
201
|
+
const offenders = (0, config_env_graphql_1.envBlocksWithoutGraphQlDisabled)(repaired);
|
|
202
|
+
if (offenders.length > 0) {
|
|
203
|
+
throw new Error(`REST conversion incomplete: src/config.env.ts has no \`graphQl: false\` in ${offenders.join(', ')}. ` +
|
|
204
|
+
'The framework treats a missing switch as GraphQL ENABLED, and the server would fail to start. ' +
|
|
205
|
+
'Add it to those environment blocks and re-run.');
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Refuse to hand over a project that still carries markers of the removed mode.
|
|
210
|
+
*
|
|
211
|
+
* A leftover `// #region graphql` announces a mode the project does not have — and,
|
|
212
|
+
* worse, means the region CONTENT is still there: resolvers, graphql-only wiring,
|
|
213
|
+
* e2e specs. That is what a silent no-op looks like from the outside, and the strip
|
|
214
|
+
* used to be exactly that on Windows (fixed in the glob helper above): it reported
|
|
215
|
+
* success while touching nothing.
|
|
216
|
+
*
|
|
217
|
+
* Cheap, and it fails at generation time instead of in the user's editor.
|
|
218
|
+
*/
|
|
219
|
+
assertNoMarkersRemain(projectPath, removedMarker) {
|
|
220
|
+
const marked = [];
|
|
221
|
+
for (const dir of ['src', 'tests']) {
|
|
222
|
+
const base = (0, path_1.join)(projectPath, dir);
|
|
223
|
+
if (!this.filesystem.exists(base)) {
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
for (const file of this.globFiles(base, '**/*.ts')) {
|
|
227
|
+
const content = this.filesystem.read(file);
|
|
228
|
+
if ((content === null || content === void 0 ? void 0 : content.includes(`// #region ${removedMarker}`)) || (content === null || content === void 0 ? void 0 : content.includes(`// #endregion ${removedMarker}`))) {
|
|
229
|
+
marked.push(`${dir}/${(0, path_1.basename)(file)}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (marked.length > 0) {
|
|
234
|
+
throw new Error(`API-mode conversion incomplete: ${marked.length} file(s) still carry \`// #region ${removedMarker}\` ` +
|
|
235
|
+
`markers (${marked.slice(0, 5).join(', ')}${marked.length > 5 ? ', …' : ''}). ` +
|
|
236
|
+
'The region content was not removed, so this project still contains code of a mode it does not have.');
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Resolve a POSIX-style glob pattern against `baseDir` and return the
|
|
241
|
+
* matching files as absolute, OS-native paths.
|
|
242
|
+
*
|
|
243
|
+
* Deliberately NOT `filesystem.find()`: gluegun/fs-jetpack builds its
|
|
244
|
+
* matcher by string-concatenating the *resolved* base path in front of
|
|
245
|
+
* the pattern (fs-jetpack/lib/utils/matcher.js). On Windows that base
|
|
246
|
+
* path is backslash-separated, and minimatch reads a backslash inside a
|
|
247
|
+
* pattern as an escape character — so a base of `C:\project\src`
|
|
248
|
+
* collapses to the literal `C:projectsrc` and nothing ever matches.
|
|
249
|
+
* Effect: on Windows no file was deleted and no region was stripped,
|
|
250
|
+
* silently. `glob` keeps pattern and base directory apart and
|
|
251
|
+
* normalises separators itself, so it behaves the same on every
|
|
252
|
+
* platform.
|
|
253
|
+
*/
|
|
254
|
+
globFiles(baseDir, pattern) {
|
|
255
|
+
return (0, glob_1.globSync)(pattern, {
|
|
256
|
+
absolute: true,
|
|
257
|
+
cwd: baseDir,
|
|
258
|
+
dot: true,
|
|
259
|
+
nodir: true,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
172
262
|
/**
|
|
173
263
|
* Strip region markers from all .ts files
|
|
174
264
|
*
|
|
@@ -181,7 +271,7 @@ class ApiMode {
|
|
|
181
271
|
if (!this.filesystem.exists(dir)) {
|
|
182
272
|
continue;
|
|
183
273
|
}
|
|
184
|
-
const files = this.
|
|
274
|
+
const files = this.globFiles(dir, '**/*.ts');
|
|
185
275
|
for (const file of files) {
|
|
186
276
|
const content = this.filesystem.read(file);
|
|
187
277
|
if (!content) {
|
|
@@ -196,7 +286,10 @@ class ApiMode {
|
|
|
196
286
|
// graphql` block with an explicit `graphQl: false,` so GraphQL
|
|
197
287
|
// is cleanly disabled.
|
|
198
288
|
let processed;
|
|
199
|
-
|
|
289
|
+
// `basename` instead of `endsWith('/config.env.ts')`: on Windows the
|
|
290
|
+
// path separator is a backslash, so the POSIX-only suffix check never
|
|
291
|
+
// matched and config.env.ts was processed like any other file.
|
|
292
|
+
if (removeMarker === 'graphql' && (0, path_1.basename)(file) === 'config.env.ts') {
|
|
200
293
|
processed = this.replaceGraphqlRegionsWithDisabled(content, keepMarker);
|
|
201
294
|
}
|
|
202
295
|
else {
|
|
@@ -299,7 +392,7 @@ class ApiMode {
|
|
|
299
392
|
if (!this.filesystem.exists(dir)) {
|
|
300
393
|
continue;
|
|
301
394
|
}
|
|
302
|
-
const files = this.
|
|
395
|
+
const files = this.globFiles(dir, '**/*.ts');
|
|
303
396
|
for (const file of files) {
|
|
304
397
|
const content = this.filesystem.read(file);
|
|
305
398
|
if (!content) {
|
|
@@ -444,7 +537,7 @@ class ApiMode {
|
|
|
444
537
|
if (!this.filesystem.exists(dir)) {
|
|
445
538
|
continue;
|
|
446
539
|
}
|
|
447
|
-
const files = this.
|
|
540
|
+
const files = this.globFiles(dir, '**/*.ts');
|
|
448
541
|
for (const file of files) {
|
|
449
542
|
const content = this.filesystem.read(file);
|
|
450
543
|
if (!content) {
|
|
@@ -12,7 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.FrontendHelper = void 0;
|
|
13
13
|
const check_freshness_hooks_1 = require("../lib/check-freshness-hooks");
|
|
14
14
|
const markdown_table_1 = require("../lib/markdown-table");
|
|
15
|
-
const
|
|
15
|
+
const module_specifiers_1 = require("../lib/module-specifiers");
|
|
16
16
|
const strip_vendor_schema_augmentation_1 = require("../lib/strip-vendor-schema-augmentation");
|
|
17
17
|
const vendor_claude_md_1 = require("../lib/vendor-claude-md");
|
|
18
18
|
/**
|
|
@@ -399,7 +399,7 @@ class FrontendHelper {
|
|
|
399
399
|
}
|
|
400
400
|
}
|
|
401
401
|
// ── Post-conversion verification ─────────────────────────────────────
|
|
402
|
-
const stale = this.findStaleFrontendImports(dest,
|
|
402
|
+
const stale = this.findStaleFrontendImports(dest, module_specifiers_1.isRelativeCoreImport);
|
|
403
403
|
if (stale.length > 0) {
|
|
404
404
|
const { print } = this.toolbox;
|
|
405
405
|
print.warning(`${stale.length} file(s) still contain relative core imports after npm conversion:`);
|
|
@@ -716,8 +716,7 @@ class FrontendHelper {
|
|
|
716
716
|
].join('\n'));
|
|
717
717
|
}
|
|
718
718
|
// ── Post-conversion verification ─────────────────────────────────────
|
|
719
|
-
|
|
720
|
-
const staleImports = this.findStaleFrontendImports(dest, /(?:^|\s)(?:import|from)\s+['"][^'"]*@lenne\.tech\/nuxt-extensions/m, 'app/core/');
|
|
719
|
+
const staleImports = this.findStaleFrontendImports(dest, (specifier) => (0, module_specifiers_1.isPackageImport)(specifier, '@lenne.tech/nuxt-extensions'), 'app/core/');
|
|
721
720
|
if (staleImports.length > 0) {
|
|
722
721
|
const { print } = this.toolbox;
|
|
723
722
|
print.warning(`${staleImports.length} file(s) still contain '@lenne.tech/nuxt-extensions' imports after vendor conversion:`);
|
|
@@ -826,20 +825,20 @@ class FrontendHelper {
|
|
|
826
825
|
/**
|
|
827
826
|
* Scan consumer files for stale imports matching a pattern.
|
|
828
827
|
*/
|
|
829
|
-
findStaleFrontendImports(appDir,
|
|
828
|
+
findStaleFrontendImports(appDir, matches, skipPathContaining) {
|
|
830
829
|
const { filesystem } = this.toolbox;
|
|
831
830
|
const allFiles = this.walkConsumerFiles(appDir);
|
|
832
831
|
const stale = [];
|
|
833
832
|
for (const absFile of allFiles) {
|
|
834
833
|
if (skipPathContaining && absFile.includes(skipPathContaining))
|
|
835
834
|
continue;
|
|
836
|
-
//
|
|
837
|
-
//
|
|
838
|
-
//
|
|
839
|
-
//
|
|
840
|
-
|
|
841
|
-
const
|
|
842
|
-
if (matches) {
|
|
835
|
+
// Ask the parser for the file's module specifiers instead of searching its text.
|
|
836
|
+
// A docblock that DOCUMENTS the conversion quotes the very import syntax a text
|
|
837
|
+
// search looks for, and blanking comments first does not hold either — see
|
|
838
|
+
// src/lib/module-specifiers.ts. A comment is not part of the AST, so the whole
|
|
839
|
+
// false-positive class is gone rather than worked around.
|
|
840
|
+
const specifiers = (0, module_specifiers_1.fileImportedSpecifiers)(absFile, (path) => filesystem.read(path) || undefined);
|
|
841
|
+
if (specifiers.some(matches)) {
|
|
843
842
|
stale.push(absFile.replace(`${appDir}/`, ''));
|
|
844
843
|
}
|
|
845
844
|
}
|
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
|
}
|