@lenne.tech/cli 1.32.1 → 1.34.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/codex/codex.js +23 -0
- package/build/commands/codex/plugins.js +103 -0
- package/build/commands/codex/shortcuts.js +110 -0
- package/build/commands/dev/test.js +10 -4
- package/build/commands/dev/up.js +9 -3
- package/build/commands/frontend/nuxt.js +4 -7
- package/build/commands/fullstack/init.js +26 -11
- package/build/commands/fullstack/update.js +13 -0
- package/build/commands/ticket/start.js +6 -4
- package/build/extensions/frontend-helper.js +49 -59
- package/build/extensions/server.js +220 -32
- package/build/lib/check-freshness-hooks.js +53 -0
- package/build/lib/codex-cli.js +56 -0
- package/build/lib/codex-plugin-utils.js +102 -0
- package/build/lib/dev-package-manager.js +89 -0
- package/build/lib/dev-patches.js +4 -1
- package/build/lib/dev-test-session.js +30 -16
- package/build/lib/dev-ticket.js +12 -6
- package/build/lib/heal-check-wrapper.js +59 -0
- package/build/lib/hoist-workspace-pnpm-config.js +105 -92
- package/build/lib/vendor-claude-md.js +7 -2
- package/build/templates/check/check.mjs +512 -0
- package/package.json +1 -1
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EMPTY_CODEX_PLUGIN_CONTENTS = void 0;
|
|
4
|
+
exports.installCodexAgents = installCodexAgents;
|
|
5
|
+
exports.installCodexPrompts = installCodexPrompts;
|
|
6
|
+
exports.readCodexMarketplaceName = readCodexMarketplaceName;
|
|
7
|
+
exports.readLocalCodexPluginContents = readLocalCodexPluginContents;
|
|
8
|
+
/**
|
|
9
|
+
* Codex plugin setup helpers.
|
|
10
|
+
*/
|
|
11
|
+
const fs_1 = require("fs");
|
|
12
|
+
const os_1 = require("os");
|
|
13
|
+
const path_1 = require("path");
|
|
14
|
+
const json_utils_1 = require("./json-utils");
|
|
15
|
+
exports.EMPTY_CODEX_PLUGIN_CONTENTS = {
|
|
16
|
+
agents: [],
|
|
17
|
+
hooks: 0,
|
|
18
|
+
mcpServers: [],
|
|
19
|
+
prompts: [],
|
|
20
|
+
skills: [],
|
|
21
|
+
};
|
|
22
|
+
function installCodexAgents(pluginRoot) {
|
|
23
|
+
const sourceDir = (0, path_1.join)(pluginRoot, 'codex-agents');
|
|
24
|
+
if (!(0, fs_1.existsSync)(sourceDir)) {
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
const targetDir = (0, path_1.join)((0, os_1.homedir)(), '.codex', 'agents');
|
|
28
|
+
(0, fs_1.mkdirSync)(targetDir, { recursive: true });
|
|
29
|
+
let count = 0;
|
|
30
|
+
for (const entry of (0, fs_1.readdirSync)(sourceDir, { withFileTypes: true })) {
|
|
31
|
+
if (!entry.isFile() || !entry.name.endsWith('.toml'))
|
|
32
|
+
continue;
|
|
33
|
+
(0, fs_1.copyFileSync)((0, path_1.join)(sourceDir, entry.name), (0, path_1.join)(targetDir, entry.name));
|
|
34
|
+
count += 1;
|
|
35
|
+
}
|
|
36
|
+
return count;
|
|
37
|
+
}
|
|
38
|
+
function installCodexPrompts(pluginRoot) {
|
|
39
|
+
const sourceDir = (0, path_1.join)(pluginRoot, 'prompts');
|
|
40
|
+
if (!(0, fs_1.existsSync)(sourceDir)) {
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
const targetDir = (0, path_1.join)((0, os_1.homedir)(), '.codex', 'prompts');
|
|
44
|
+
(0, fs_1.mkdirSync)(targetDir, { recursive: true });
|
|
45
|
+
let count = 0;
|
|
46
|
+
for (const entry of (0, fs_1.readdirSync)(sourceDir, { withFileTypes: true })) {
|
|
47
|
+
if (!entry.isFile() || !entry.name.endsWith('.md'))
|
|
48
|
+
continue;
|
|
49
|
+
(0, fs_1.copyFileSync)((0, path_1.join)(sourceDir, entry.name), (0, path_1.join)(targetDir, entry.name));
|
|
50
|
+
count += 1;
|
|
51
|
+
}
|
|
52
|
+
return count;
|
|
53
|
+
}
|
|
54
|
+
function readCodexMarketplaceName(root) {
|
|
55
|
+
const path = (0, path_1.join)(root, '.agents', 'plugins', 'marketplace.json');
|
|
56
|
+
if (!(0, fs_1.existsSync)(path)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const parsed = (0, json_utils_1.safeJsonParse)((0, fs_1.readFileSync)(path, 'utf-8'));
|
|
60
|
+
return (parsed === null || parsed === void 0 ? void 0 : parsed.name) || null;
|
|
61
|
+
}
|
|
62
|
+
function readLocalCodexPluginContents(pluginRoot) {
|
|
63
|
+
const result = Object.assign({}, exports.EMPTY_CODEX_PLUGIN_CONTENTS);
|
|
64
|
+
const skillsDir = (0, path_1.join)(pluginRoot, 'skills');
|
|
65
|
+
if ((0, fs_1.existsSync)(skillsDir)) {
|
|
66
|
+
result.skills = (0, fs_1.readdirSync)(skillsDir, { withFileTypes: true })
|
|
67
|
+
.filter((entry) => entry.isDirectory())
|
|
68
|
+
.map((entry) => entry.name)
|
|
69
|
+
.sort();
|
|
70
|
+
}
|
|
71
|
+
const agentsDir = (0, path_1.join)(pluginRoot, 'codex-agents');
|
|
72
|
+
if ((0, fs_1.existsSync)(agentsDir)) {
|
|
73
|
+
result.agents = (0, fs_1.readdirSync)(agentsDir, { withFileTypes: true })
|
|
74
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.toml'))
|
|
75
|
+
.map((entry) => entry.name.replace(/\.toml$/, ''))
|
|
76
|
+
.sort();
|
|
77
|
+
}
|
|
78
|
+
const promptsDir = (0, path_1.join)(pluginRoot, 'prompts');
|
|
79
|
+
if ((0, fs_1.existsSync)(promptsDir)) {
|
|
80
|
+
result.prompts = (0, fs_1.readdirSync)(promptsDir, { withFileTypes: true })
|
|
81
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
82
|
+
.map((entry) => entry.name.replace(/\.md$/, ''))
|
|
83
|
+
.sort();
|
|
84
|
+
}
|
|
85
|
+
const hooksPath = (0, path_1.join)(pluginRoot, 'hooks', 'hooks.json');
|
|
86
|
+
if ((0, fs_1.existsSync)(hooksPath)) {
|
|
87
|
+
const parsed = (0, json_utils_1.safeJsonParse)((0, fs_1.readFileSync)(hooksPath, 'utf-8'));
|
|
88
|
+
if (parsed === null || parsed === void 0 ? void 0 : parsed.hooks) {
|
|
89
|
+
for (const groups of Object.values(parsed.hooks)) {
|
|
90
|
+
for (const group of groups) {
|
|
91
|
+
result.hooks += Array.isArray(group.hooks) ? group.hooks.length : 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const mcpPath = (0, path_1.join)(pluginRoot, '.mcp.json');
|
|
97
|
+
if ((0, fs_1.existsSync)(mcpPath)) {
|
|
98
|
+
const parsed = (0, json_utils_1.safeJsonParse)((0, fs_1.readFileSync)(mcpPath, 'utf-8'));
|
|
99
|
+
result.mcpServers = Object.keys((parsed === null || parsed === void 0 ? void 0 : parsed.mcpServers) || {}).sort();
|
|
100
|
+
}
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pickPackageManager = pickPackageManager;
|
|
4
|
+
/**
|
|
5
|
+
* Pick the package manager `lt dev` should drive for a given project dir.
|
|
6
|
+
*
|
|
7
|
+
* `lt dev up` and the related test/ticket flows used to hard-code `pnpm`
|
|
8
|
+
* for every monorepo. That breaks npm-only and yarn-only projects: a
|
|
9
|
+
* `pnpm install` invoked from inside `pnpm start` regenerates a
|
|
10
|
+
* pnpm-lock.yaml (which the project's .gitignore then refuses to track),
|
|
11
|
+
* fails on un-approved build scripts (bcrypt, sharp, esbuild) and exits
|
|
12
|
+
* non-zero — the supervised api/app processes die immediately and
|
|
13
|
+
* `lt dev status` reports them as `dead`.
|
|
14
|
+
*
|
|
15
|
+
* Detection order — highest precedence first:
|
|
16
|
+
* 1. `LT_PM_BIN` env var (generic override).
|
|
17
|
+
* 2. `LT_PNPM_BIN` env var (legacy — kept for backwards compatibility).
|
|
18
|
+
* 3. `pnpm-lock.yaml` in `cwd` → pnpm
|
|
19
|
+
* 4. `yarn.lock` in `cwd` → yarn
|
|
20
|
+
* 5. `package-lock.json` in `cwd` → npm
|
|
21
|
+
* 6. Fallback: `pnpm` (preserves the historical default so nothing
|
|
22
|
+
* breaks for the projects this CLI was originally written for).
|
|
23
|
+
*
|
|
24
|
+
* The detection is per-cwd so a monorepo with a pnpm api + npm app gets
|
|
25
|
+
* the correct command per component.
|
|
26
|
+
*/
|
|
27
|
+
const node_fs_1 = require("node:fs");
|
|
28
|
+
const node_path_1 = require("node:path");
|
|
29
|
+
/**
|
|
30
|
+
* Resolve which package manager to drive for `cwd`. Pure — only filesystem
|
|
31
|
+
* existence checks, no exec. The override env vars take precedence so a
|
|
32
|
+
* CI pipeline can pin the manager without touching the lockfile.
|
|
33
|
+
*
|
|
34
|
+
* `cwd` is expected to be a project root (i.e. where the lockfile lives).
|
|
35
|
+
* For a monorepo with separate api/app dirs, call this once per dir.
|
|
36
|
+
*/
|
|
37
|
+
function pickPackageManager(cwd, env = process.env) {
|
|
38
|
+
const override = env.LT_PM_BIN || env.LT_PNPM_BIN;
|
|
39
|
+
if (override) {
|
|
40
|
+
return buildCommand(override, inferNameFromBin(override));
|
|
41
|
+
}
|
|
42
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, 'pnpm-lock.yaml'))) {
|
|
43
|
+
return buildCommand('pnpm', 'pnpm');
|
|
44
|
+
}
|
|
45
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, 'yarn.lock'))) {
|
|
46
|
+
return buildCommand('yarn', 'yarn');
|
|
47
|
+
}
|
|
48
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, 'package-lock.json'))) {
|
|
49
|
+
return buildCommand('npm', 'npm');
|
|
50
|
+
}
|
|
51
|
+
// Historical default — keep so projects without a lockfile (fresh
|
|
52
|
+
// scaffolds, vendored monorepos) behave exactly as before.
|
|
53
|
+
return buildCommand('pnpm', 'pnpm');
|
|
54
|
+
}
|
|
55
|
+
function buildCommand(bin, name) {
|
|
56
|
+
const isNpm = name === 'npm';
|
|
57
|
+
return {
|
|
58
|
+
bin,
|
|
59
|
+
exec(binary, args = []) {
|
|
60
|
+
// `npm exec` consumes the args before the binary name and treats
|
|
61
|
+
// anything after as its own flags unless separated by `--`. pnpm
|
|
62
|
+
// and yarn route them through unchanged. Without the separator
|
|
63
|
+
// `npm exec playwright test --shard=1/2` would parse `--shard`
|
|
64
|
+
// as an npm option, NOT a Playwright one.
|
|
65
|
+
return isNpm ? ['exec', '--', binary, ...args] : ['exec', binary, ...args];
|
|
66
|
+
},
|
|
67
|
+
installArgs: ['install'],
|
|
68
|
+
name,
|
|
69
|
+
runScript(script, extra = []) {
|
|
70
|
+
// pnpm + yarn accept the bare-script shortcut (`pnpm dev`), npm
|
|
71
|
+
// does not. Using `run <script>` is universally accepted so we
|
|
72
|
+
// route every manager through the same call.
|
|
73
|
+
return ['run', script, ...extra];
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function inferNameFromBin(bin) {
|
|
78
|
+
const lower = bin.toLowerCase();
|
|
79
|
+
if (lower.endsWith('pnpm') || lower.includes('/pnpm')) {
|
|
80
|
+
return 'pnpm';
|
|
81
|
+
}
|
|
82
|
+
if (lower.endsWith('yarn') || lower.includes('/yarn')) {
|
|
83
|
+
return 'yarn';
|
|
84
|
+
}
|
|
85
|
+
if (lower.endsWith('npm') || lower.includes('/npm')) {
|
|
86
|
+
return 'npm';
|
|
87
|
+
}
|
|
88
|
+
return 'unknown';
|
|
89
|
+
}
|
package/build/lib/dev-patches.js
CHANGED
|
@@ -127,7 +127,10 @@ function patchClaudeMd(file, options) {
|
|
|
127
127
|
}
|
|
128
128
|
else {
|
|
129
129
|
const sep = content.endsWith('\n\n') ? '' : content.endsWith('\n') ? '\n' : '\n\n';
|
|
130
|
-
|
|
130
|
+
// No trailing newline: oxfmt strips it from .md files, so emitting one
|
|
131
|
+
// makes a freshly patched CLAUDE.md fail `format:check` (read-only) until
|
|
132
|
+
// the next `check` auto-fix. Keep the block flush with EOF.
|
|
133
|
+
next = `${content}${sep}${block}`;
|
|
131
134
|
}
|
|
132
135
|
if (next === content)
|
|
133
136
|
return { file, patched: false, replacements: 0 };
|
|
@@ -47,6 +47,7 @@ const caddy_1 = require("./caddy");
|
|
|
47
47
|
const dev_env_1 = require("./dev-env");
|
|
48
48
|
const dev_env_bridge_1 = require("./dev-env-bridge");
|
|
49
49
|
const dev_identity_1 = require("./dev-identity");
|
|
50
|
+
const dev_package_manager_1 = require("./dev-package-manager");
|
|
50
51
|
const dev_patches_1 = require("./dev-patches");
|
|
51
52
|
const dev_process_1 = require("./dev-process");
|
|
52
53
|
const dev_project_1 = require("./dev-project");
|
|
@@ -189,15 +190,17 @@ function bringUpTestSession(layout_1, baseIdentity_1, log_1) {
|
|
|
189
190
|
dbName,
|
|
190
191
|
identity: testIdentity,
|
|
191
192
|
});
|
|
192
|
-
const pnpmBin = process.env.LT_PNPM_BIN || 'pnpm';
|
|
193
193
|
const pids = {};
|
|
194
|
-
// --- API: compiled (`node dist`) for stability; fall back to
|
|
195
|
-
// `skipBuild` (sibling shards) reuses the dist the
|
|
194
|
+
// --- API: compiled (`node dist`) for stability; fall back to the project's
|
|
195
|
+
// own dev start script. `skipBuild` (sibling shards) reuses the dist the
|
|
196
|
+
// first shard produced. Per-component PM detection mirrors `lt dev up`:
|
|
197
|
+
// a monorepo with an npm api and a pnpm app must drive each correctly. ---
|
|
196
198
|
if (layout.apiDir && apiPort) {
|
|
199
|
+
const apiPm = (0, dev_package_manager_1.pickPackageManager)(layout.apiDir);
|
|
197
200
|
let build = 0;
|
|
198
201
|
if (!skipBuild) {
|
|
199
202
|
log.info(log.dim('Building API (compiled, for stable long runs) …'));
|
|
200
|
-
build = yield (0, dev_process_1.runChildInherit)(
|
|
203
|
+
build = yield (0, dev_process_1.runChildInherit)(apiPm.bin, apiPm.runScript('build'), { cwd: layout.apiDir, env: process.env });
|
|
201
204
|
}
|
|
202
205
|
const entry = ['dist/src/main.js', 'dist/main.js']
|
|
203
206
|
.map((rel) => (0, path_1.join)(layout.apiDir, rel))
|
|
@@ -212,8 +215,8 @@ function bringUpTestSession(layout_1, baseIdentity_1, log_1) {
|
|
|
212
215
|
});
|
|
213
216
|
}
|
|
214
217
|
else {
|
|
215
|
-
log.warn(
|
|
216
|
-
apiSpawn = (0, dev_process_1.spawnDetached)(
|
|
218
|
+
log.warn(`compiled API not available — falling back to \`${apiPm.bin} start\` (ts-node).`);
|
|
219
|
+
apiSpawn = (0, dev_process_1.spawnDetached)(apiPm.bin, apiPm.runScript('start'), {
|
|
217
220
|
cwd: layout.apiDir,
|
|
218
221
|
env: apiEnv,
|
|
219
222
|
logFile: (0, path_1.join)(layout.root, '.lt-dev', names.apiLog),
|
|
@@ -231,10 +234,14 @@ function bringUpTestSession(layout_1, baseIdentity_1, log_1) {
|
|
|
231
234
|
// must be a cross-subdomain DOMAIN cookie — see the project's parseCookieHeader).
|
|
232
235
|
// Rebuilt every run so the suite never hits stale code (no build-skip / reuse). ---
|
|
233
236
|
if (layout.appDir && appPort) {
|
|
237
|
+
const appPm = (0, dev_package_manager_1.pickPackageManager)(layout.appDir);
|
|
234
238
|
let appBuild = 0;
|
|
235
239
|
if (!skipBuild) {
|
|
236
240
|
log.info(log.dim('Building App (nuxt build, for speed + prod-fidelity) …'));
|
|
237
|
-
appBuild = yield (0, dev_process_1.runChildInherit)(
|
|
241
|
+
appBuild = yield (0, dev_process_1.runChildInherit)(appPm.bin, appPm.runScript('build'), {
|
|
242
|
+
cwd: layout.appDir,
|
|
243
|
+
env: devEnv.app.env,
|
|
244
|
+
});
|
|
238
245
|
}
|
|
239
246
|
const appEntry = ['.output/server/index.mjs']
|
|
240
247
|
.map((rel) => (0, path_1.join)(layout.appDir, rel))
|
|
@@ -248,8 +255,8 @@ function bringUpTestSession(layout_1, baseIdentity_1, log_1) {
|
|
|
248
255
|
});
|
|
249
256
|
}
|
|
250
257
|
else {
|
|
251
|
-
log.warn(
|
|
252
|
-
appSpawn = (0, dev_process_1.spawnDetached)(
|
|
258
|
+
log.warn(`built app not available — falling back to \`${appPm.bin} dev\` (slower: cold-compiles routes).`);
|
|
259
|
+
appSpawn = (0, dev_process_1.spawnDetached)(appPm.bin, appPm.runScript('dev'), {
|
|
253
260
|
cwd: layout.appDir,
|
|
254
261
|
env: devEnv.app.env,
|
|
255
262
|
logFile: (0, path_1.join)(layout.root, '.lt-dev', names.appLog),
|
|
@@ -340,13 +347,20 @@ function runShardedTestSession(layout, baseIdentity, log, opts) {
|
|
|
340
347
|
// every navigation) without loosening them for serial runs.
|
|
341
348
|
const env = Object.assign(Object.assign({}, ctx.appEnv), { LT_DEV_TEST_SHARDS: String(total), MONGO_URI: `mongodb://127.0.0.1/${ctx.dbName}` });
|
|
342
349
|
const logFile = (0, path_1.join)(layout.root, '.lt-dev', `shard.${index}.test.log`);
|
|
343
|
-
// Invoke Playwright DIRECTLY via `
|
|
344
|
-
// forwarding option flags through
|
|
345
|
-
// passed the separator on to Playwright, which
|
|
346
|
-
// `--reporter` as file FILTERS (not options) →
|
|
347
|
-
// suite.
|
|
348
|
-
|
|
349
|
-
|
|
350
|
+
// Invoke Playwright DIRECTLY via the manager's `exec` (NOT `<pm> run
|
|
351
|
+
// test:e2e -- …`): forwarding option flags through `<pm> run`'s `--`
|
|
352
|
+
// is unreliable — pnpm passed the separator on to Playwright, which
|
|
353
|
+
// then read `--shard` / `--reporter` as file FILTERS (not options) →
|
|
354
|
+
// every shard ran the whole suite. `<pm> exec` hands args straight
|
|
355
|
+
// to the binary (mirrors CI); the helper inserts `--` for npm so
|
|
356
|
+
// those flags don't get re-parsed as npm's own.
|
|
357
|
+
const args = opts.pm.exec('playwright', [
|
|
358
|
+
'test',
|
|
359
|
+
`--shard=${index}/${total}`,
|
|
360
|
+
'--reporter=line',
|
|
361
|
+
...opts.forwarded,
|
|
362
|
+
]);
|
|
363
|
+
const code = yield (0, dev_process_1.runChildToFile)(opts.pm.bin, args, { cwd: appDir, env, logFile });
|
|
350
364
|
return { code, index, logFile };
|
|
351
365
|
})));
|
|
352
366
|
// Aggregate per-shard exit codes into a single result.
|
package/build/lib/dev-ticket.js
CHANGED
|
@@ -8,8 +8,8 @@ exports.dropDatabase = dropDatabase;
|
|
|
8
8
|
exports.gitBranchExists = gitBranchExists;
|
|
9
9
|
exports.gitFetch = gitFetch;
|
|
10
10
|
exports.gitMainRepoRoot = gitMainRepoRoot;
|
|
11
|
+
exports.installWorktreeDeps = installWorktreeDeps;
|
|
11
12
|
exports.listWorktrees = listWorktrees;
|
|
12
|
-
exports.pnpmInstall = pnpmInstall;
|
|
13
13
|
exports.readTicketMarker = readTicketMarker;
|
|
14
14
|
exports.resolveDevIdentity = resolveDevIdentity;
|
|
15
15
|
exports.worktreeAdd = worktreeAdd;
|
|
@@ -43,6 +43,7 @@ const fs_1 = require("fs");
|
|
|
43
43
|
const os_1 = require("os");
|
|
44
44
|
const path_1 = require("path");
|
|
45
45
|
const dev_identity_1 = require("./dev-identity");
|
|
46
|
+
const dev_package_manager_1 = require("./dev-package-manager");
|
|
46
47
|
const dev_patches_1 = require("./dev-patches");
|
|
47
48
|
const dev_project_1 = require("./dev-project");
|
|
48
49
|
const dev_state_1 = require("./dev-state");
|
|
@@ -183,6 +184,16 @@ function gitMainRepoRoot(cwd) {
|
|
|
183
184
|
const commonDir = git(cwd, ['rev-parse', '--path-format=absolute', '--git-common-dir']);
|
|
184
185
|
return (0, path_1.dirname)(commonDir);
|
|
185
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Install dependencies in a freshly-created worktree. Auto-detects the
|
|
189
|
+
* project's package manager from its lockfile (pnpm hard-links from the
|
|
190
|
+
* shared store → fast; npm + yarn install normally). Falls back to pnpm
|
|
191
|
+
* for fresh scaffolds without a lockfile yet.
|
|
192
|
+
*/
|
|
193
|
+
function installWorktreeDeps(dir) {
|
|
194
|
+
const pm = (0, dev_package_manager_1.pickPackageManager)(dir);
|
|
195
|
+
(0, child_process_1.execFileSync)(pm.bin, pm.installArgs, { cwd: dir, stdio: 'inherit' });
|
|
196
|
+
}
|
|
186
197
|
/** List all worktrees of the repo (parsed from `git worktree list --porcelain`). */
|
|
187
198
|
function listWorktrees(repoDir) {
|
|
188
199
|
let out = '';
|
|
@@ -208,11 +219,6 @@ function listWorktrees(repoDir) {
|
|
|
208
219
|
result.push(finalizeWorktree(current));
|
|
209
220
|
return result;
|
|
210
221
|
}
|
|
211
|
-
/** Install dependencies in a freshly-created worktree (pnpm hard-links from the shared store → fast). */
|
|
212
|
-
function pnpmInstall(dir) {
|
|
213
|
-
const pnpmBin = process.env.LT_PNPM_BIN || 'pnpm';
|
|
214
|
-
(0, child_process_1.execFileSync)(pnpmBin, ['install'], { cwd: dir, stdio: 'inherit' });
|
|
215
|
-
}
|
|
216
222
|
/** Read the ticket id this worktree is tagged with, or null. */
|
|
217
223
|
function readTicketMarker(root) {
|
|
218
224
|
const file = (0, path_1.join)(root, dev_state_1.paths.sessionDir, TICKET_MARKER);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.healCheckWrapper = healCheckWrapper;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
/** Marker value for the report-driven check wrapper. */
|
|
7
|
+
const WRAPPER = 'node scripts/check.mjs';
|
|
8
|
+
/**
|
|
9
|
+
* Idempotently install the report-driven `check.mjs` wrapper into a project.
|
|
10
|
+
*
|
|
11
|
+
* Copies the bundled canonical wrapper to `<root>/scripts/check.mjs` and rewrites
|
|
12
|
+
* the root `package.json` so that `check` runs the wrapper while the original
|
|
13
|
+
* chain is preserved as `check:raw`. A no-op once already wired (so it is safe to
|
|
14
|
+
* run on every `lt fullstack update`).
|
|
15
|
+
*
|
|
16
|
+
* `lt fullstack init` already ships the wrapper via the template clone; this is
|
|
17
|
+
* the MIGRATION path that brings it into pre-existing projects.
|
|
18
|
+
*
|
|
19
|
+
* @param projectRoot Absolute path to the (workspace) project root.
|
|
20
|
+
* @param assetPath Absolute path to the bundled canonical `check.mjs`.
|
|
21
|
+
* @returns The list of changed file paths (relative to `projectRoot`); empty when nothing changed.
|
|
22
|
+
*/
|
|
23
|
+
function healCheckWrapper(projectRoot, assetPath) {
|
|
24
|
+
const changed = [];
|
|
25
|
+
const pkgPath = (0, path_1.join)(projectRoot, 'package.json');
|
|
26
|
+
if (!(0, fs_1.existsSync)(pkgPath) || !(0, fs_1.existsSync)(assetPath)) {
|
|
27
|
+
return changed;
|
|
28
|
+
}
|
|
29
|
+
let pkg;
|
|
30
|
+
try {
|
|
31
|
+
pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8'));
|
|
32
|
+
}
|
|
33
|
+
catch (_a) {
|
|
34
|
+
return changed;
|
|
35
|
+
}
|
|
36
|
+
const scripts = pkg.scripts;
|
|
37
|
+
// Only touch projects that actually define a `check` script.
|
|
38
|
+
if (!scripts || typeof scripts.check !== 'string') {
|
|
39
|
+
return changed;
|
|
40
|
+
}
|
|
41
|
+
// 1. Ensure scripts/check.mjs exists and matches the bundled canonical version.
|
|
42
|
+
const targetScript = (0, path_1.join)(projectRoot, 'scripts', 'check.mjs');
|
|
43
|
+
const bundled = (0, fs_1.readFileSync)(assetPath, 'utf8');
|
|
44
|
+
if (!(0, fs_1.existsSync)(targetScript) || (0, fs_1.readFileSync)(targetScript, 'utf8') !== bundled) {
|
|
45
|
+
(0, fs_1.mkdirSync)((0, path_1.dirname)(targetScript), { recursive: true });
|
|
46
|
+
(0, fs_1.copyFileSync)(assetPath, targetScript);
|
|
47
|
+
changed.push('scripts/check.mjs');
|
|
48
|
+
}
|
|
49
|
+
// 2. Wire package.json: `check` runs the wrapper; the original chain becomes `check:raw`.
|
|
50
|
+
if (scripts.check !== WRAPPER) {
|
|
51
|
+
if (!scripts['check:raw']) {
|
|
52
|
+
scripts['check:raw'] = scripts.check;
|
|
53
|
+
}
|
|
54
|
+
scripts.check = WRAPPER;
|
|
55
|
+
(0, fs_1.writeFileSync)(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
56
|
+
changed.push('package.json');
|
|
57
|
+
}
|
|
58
|
+
return changed;
|
|
59
|
+
}
|