@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.
@@ -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.FrontendHelper = void 0;
13
+ const check_freshness_hooks_1 = require("../lib/check-freshness-hooks");
13
14
  const markdown_table_1 = require("../lib/markdown-table");
14
15
  const vendor_claude_md_1 = require("../lib/vendor-claude-md");
15
16
  /**
@@ -140,49 +141,54 @@ class FrontendHelper {
140
141
  return __awaiter(this, arguments, void 0, function* (dest, options = {}) {
141
142
  const { system, templateHelper } = this.toolbox;
142
143
  const { branch, copyPath, linkPath, skipInstall } = options;
143
- // Use template extension for link/copy/branch operations
144
- if (linkPath || copyPath || branch) {
145
- const result = yield templateHelper.setup(dest, {
146
- branch,
147
- copyPath,
148
- isNuxt: true,
149
- linkPath,
150
- repoUrl: branch ? 'https://github.com/lenneTech/nuxt-base-starter.git' : undefined,
151
- });
152
- if (!result.success) {
153
- return { method: result.method, path: result.path, success: false };
154
- }
155
- // After a clone, flatten the wrapper layout so `projects/app/`
156
- // IS the Nuxt app (the cloned root is the `create-nuxt-base`
157
- // scaffolder, not the app — see flattenNuxtBaseTemplate).
158
- // Skip on link mode: a symlink points at the user's local
159
- // checkout and must not have its template subdir torn out.
160
- if (result.method === 'clone') {
161
- yield this.flattenNuxtBaseTemplate(dest);
162
- }
163
- // Run install if not skipped and not a symlink
164
- if (!skipInstall && result.method !== 'link') {
165
- try {
166
- const { pm } = this.toolbox;
167
- yield system.run(`cd "${dest}" && ${pm.install(pm.detect(dest))}`);
168
- }
169
- catch (err) {
170
- return { method: result.method, path: dest, success: false };
171
- }
172
- }
173
- return { method: result.method, path: result.path, success: true };
144
+ // Source the Nuxt app from the nuxt-base-starter repo (priority:
145
+ // link > copy > clone), mirroring the backend (nest-server-starter)
146
+ // flow. The default no link/copy/branch — clones the repo's default
147
+ // branch, so a fresh init always integrates the CURRENT GitHub template.
148
+ //
149
+ // This deliberately replaces the former `create-nuxt-base@latest` npx
150
+ // path: that package is marked `private` and is no longer published
151
+ // (nuxt-base-starter commit b534af2 — "distribute via lt CLI fullstack
152
+ // template"), so `@latest` was frozen at an outdated copy and the
153
+ // bundled template drifted from GitHub. Cloning the repo directly is
154
+ // the same mechanism the backend already uses and removes that drift.
155
+ const result = yield templateHelper.setup(dest, {
156
+ branch,
157
+ copyPath,
158
+ isNuxt: true,
159
+ linkPath,
160
+ repoUrl: linkPath || copyPath ? undefined : 'https://github.com/lenneTech/nuxt-base-starter.git',
161
+ });
162
+ if (!result.success) {
163
+ return { method: result.method, path: result.path, success: false };
174
164
  }
175
- // Default: use create-nuxt-base
176
- try {
177
- const { pm } = this.toolbox;
178
- yield system.run(pm.exec(`create-nuxt-base@latest "${dest}"`));
179
- // Fix package name - create-nuxt-base uses path as name which is invalid for lerna
165
+ // After a clone, flatten the wrapper layout so `dest` IS the Nuxt app
166
+ // (the cloned root carries the scaffolder; the app lives in
167
+ // `nuxt-base-template/` see flattenNuxtBaseTemplate). Skip on link
168
+ // mode: a symlink points at the user's checkout and must not be torn out.
169
+ if (result.method === 'clone') {
170
+ yield this.flattenNuxtBaseTemplate(dest);
171
+ }
172
+ // Normalize the sub-project package name to a stable, valid workspace
173
+ // name ("app"). The template ships as `nuxt-base-template`; the former
174
+ // create-nuxt-base path renamed it the same way. Skip link mode — a
175
+ // symlinked checkout is shared with upstream and must not be mutated.
176
+ if (result.method !== 'link') {
180
177
  yield this.fixPackageName(dest);
181
- return { method: 'npx', path: dest, success: true };
182
178
  }
183
- catch (err) {
184
- return { method: 'npx', path: dest, success: false };
179
+ // Run install unless skipped or a symlink. Fullstack init installs at
180
+ // the monorepo level (skipInstall: true); standalone `lt frontend nuxt`
181
+ // installs here (create-nuxt-base used to handle that).
182
+ if (!skipInstall && result.method !== 'link') {
183
+ try {
184
+ const { pm } = this.toolbox;
185
+ yield system.run(`cd "${dest}" && ${pm.install(pm.detect(dest))}`);
186
+ }
187
+ catch (_a) {
188
+ return { method: result.method, path: dest, success: false };
189
+ }
185
190
  }
191
+ return { method: result.method, path: result.path, success: true };
186
192
  });
187
193
  }
188
194
  /**
@@ -316,7 +322,6 @@ class FrontendHelper {
316
322
  */
317
323
  convertAppToNpmMode(options) {
318
324
  return __awaiter(this, void 0, void 0, function* () {
319
- var _a;
320
325
  const { dest, targetVersion } = options;
321
326
  const { filesystem } = this.toolbox;
322
327
  const path = require('node:path');
@@ -375,13 +380,7 @@ class FrontendHelper {
375
380
  // Remove vendor-specific scripts
376
381
  if (pkg.scripts && typeof pkg.scripts === 'object') {
377
382
  const scripts = pkg.scripts;
378
- delete scripts['check:vendor-freshness'];
379
- // Unhook freshness from check/check:fix/check:naf
380
- for (const scriptName of ['check', 'check:fix', 'check:naf']) {
381
- if ((_a = scripts[scriptName]) === null || _a === void 0 ? void 0 : _a.includes('check:vendor-freshness')) {
382
- scripts[scriptName] = scripts[scriptName].replace(/pnpm run check:vendor-freshness && /g, '');
383
- }
384
- }
383
+ (0, check_freshness_hooks_1.unhookCheckFreshness)(scripts);
385
384
  }
386
385
  filesystem.write(pkgPath, pkg, { jsonIndent: 2 });
387
386
  }
@@ -576,18 +575,9 @@ class FrontendHelper {
576
575
  'setTimeout(function(){process.exit(0)},5000)',
577
576
  '"',
578
577
  ].join('');
579
- // Hook freshness into check scripts
580
- const hookFreshness = (scriptName) => {
581
- const existing = scripts[scriptName];
582
- if (!existing)
583
- return;
584
- if (existing.includes('check:vendor-freshness'))
585
- return;
586
- scripts[scriptName] = `pnpm run check:vendor-freshness && ${existing}`;
587
- };
588
- hookFreshness('check');
589
- hookFreshness('check:fix');
590
- hookFreshness('check:naf');
578
+ // Hook freshness into the check scripts (targets check:raw, never the
579
+ // check.mjs wrapper). See the shared lib helper.
580
+ (0, check_freshness_hooks_1.hookCheckFreshness)(scripts);
591
581
  }
592
582
  // Sort dependency maps alphabetically so merged-in entries
593
583
  // (e.g. upstream `@nuxt/kit`) end up in the expected position
@@ -46,6 +46,7 @@ exports.Server = void 0;
46
46
  const crypto = __importStar(require("crypto"));
47
47
  const path_1 = require("path");
48
48
  const ts = __importStar(require("typescript"));
49
+ const check_freshness_hooks_1 = require("../lib/check-freshness-hooks");
49
50
  const markdown_table_1 = require("../lib/markdown-table");
50
51
  const vendor_claude_md_1 = require("../lib/vendor-claude-md");
51
52
  /**
@@ -1005,6 +1006,20 @@ class Server {
1005
1006
  // Best-effort — if we can't read upstream pkg, the starter's own
1006
1007
  // deps should still cover most of the framework's needs.
1007
1008
  }
1009
+ // Snapshot the upstream lockfile too. The shallow clone ships
1010
+ // pnpm-lock.yaml, which pins EXACT versions for every transitive
1011
+ // dependency the vendored core imports directly (cron, jose,
1012
+ // fs-capacitor, graphql-ws, ws, …) but that nest-server never declares
1013
+ // as a direct dep. Step 5b below resolves the core's import closure
1014
+ // against this so those packages become direct project deps.
1015
+ let upstreamLockRaw = '';
1016
+ try {
1017
+ upstreamLockRaw = filesystem.read(`${tmpClone}/pnpm-lock.yaml`) || '';
1018
+ }
1019
+ catch (_b) {
1020
+ // Best-effort — without the lockfile, the closure step falls back to
1021
+ // the upstream package.json ranges (still better than nothing).
1022
+ }
1008
1023
  // Snapshot the upstream CLAUDE.md for section-merge into projects/api/CLAUDE.md.
1009
1024
  // The nest-server CLAUDE.md contains framework-specific instructions that
1010
1025
  // Claude Code needs to work correctly with the vendored source (API conventions,
@@ -1017,7 +1032,7 @@ class Server {
1017
1032
  upstreamClaudeMd = claudeMdContent;
1018
1033
  }
1019
1034
  }
1020
- catch (_b) {
1035
+ catch (_c) {
1021
1036
  // Non-fatal — if missing, the project CLAUDE.md just won't get upstream sections.
1022
1037
  }
1023
1038
  // Snapshot the upstream commit SHA for traceability in VENDOR.md.
@@ -1026,7 +1041,7 @@ class Server {
1026
1041
  const sha = yield system.run(`git -C ${tmpClone} rev-parse HEAD`);
1027
1042
  upstreamCommit = (sha || '').trim();
1028
1043
  }
1029
- catch (_c) {
1044
+ catch (_d) {
1030
1045
  // Non-fatal — VENDOR.md will just show an empty SHA.
1031
1046
  }
1032
1047
  try {
@@ -1309,7 +1324,7 @@ class Server {
1309
1324
  }
1310
1325
  }
1311
1326
  }
1312
- catch (_d) {
1327
+ catch (_e) {
1313
1328
  // skip unreadable file
1314
1329
  }
1315
1330
  }
@@ -1419,6 +1434,25 @@ class Server {
1419
1434
  }
1420
1435
  }
1421
1436
  }
1437
+ // ── 5b. Backfill the vendored core's transitive import closure ──
1438
+ //
1439
+ // The merge above only covers nest-server's *declared* dependencies.
1440
+ // The vendored core ALSO imports packages that used to arrive
1441
+ // transitively through the @lenne.tech/nest-server npm dep
1442
+ // (cron←@nestjs/schedule, jose←better-auth, fs-capacitor←graphql-upload,
1443
+ // graphql-ws / ws). Once vendored, those imports are direct, so under
1444
+ // pnpm's isolated node_modules they are unresolvable unless declared
1445
+ // as direct deps → TS2307 at build time. Scan the core's bare imports
1446
+ // and add any missing package (+ matching @types) at the EXACT version
1447
+ // pinned by the upstream lockfile.
1448
+ this.gatherVendorCoreImportClosure({
1449
+ coreDir,
1450
+ deps,
1451
+ devDeps,
1452
+ lockRaw: upstreamLockRaw,
1453
+ upstreamDeps,
1454
+ upstreamDevDeps,
1455
+ });
1422
1456
  // Add a script to run the local bin/migrate.js. The starter's
1423
1457
  // existing migrate:* scripts are already correct for npm mode; we
1424
1458
  // need them pointing at the local bin + local ts-compiler.
@@ -1458,26 +1492,9 @@ class Server {
1458
1492
  '"',
1459
1493
  ].join('');
1460
1494
  // Hook vendor-freshness as the first step of check / check:fix /
1461
- // check:naf. Non-blocking (exit 0 even on mismatch), so it just
1462
- // surfaces the warning at the top of the log.
1463
- const hookFreshness = (scriptName) => {
1464
- const existing = scripts[scriptName];
1465
- if (!existing)
1466
- return;
1467
- if (existing.includes('check:vendor-freshness'))
1468
- return;
1469
- const installPrefix = 'pnpm install && ';
1470
- if (existing.startsWith(installPrefix)) {
1471
- scripts[scriptName] =
1472
- `${installPrefix}pnpm run check:vendor-freshness && ${existing.slice(installPrefix.length)}`;
1473
- }
1474
- else {
1475
- scripts[scriptName] = `pnpm run check:vendor-freshness && ${existing}`;
1476
- }
1477
- };
1478
- hookFreshness('check');
1479
- hookFreshness('check:fix');
1480
- hookFreshness('check:naf');
1495
+ // check:naf (non-blocking; surfaces a warning at the top of the log).
1496
+ // Targets check:raw never the check.mjs wrapper. See lib helper.
1497
+ (0, check_freshness_hooks_1.hookCheckFreshness)(scripts);
1481
1498
  }
1482
1499
  filesystem.write(pkgPath, pkg, { jsonIndent: 2 });
1483
1500
  }
@@ -1708,7 +1725,13 @@ class Server {
1708
1725
  // Scan all consumer files for stale bare-specifier imports that the
1709
1726
  // codemod should have rewritten. A single miss causes a compile error,
1710
1727
  // so catching it here with a clear message saves the user debugging time.
1711
- const staleImports = this.findStaleImports(dest, '@lenne.tech/nest-server');
1728
+ const staleImports = this.findStaleImports(dest, '@lenne.tech/nest-server',
1729
+ // Match only real import/export/require specifiers in single/double
1730
+ // quotes — NOT comment references like
1731
+ // `node_modules/@lenne.tech/nest-server/...` (backticks, no keyword),
1732
+ // which the codemod legitimately leaves untouched. A naive substring
1733
+ // match flagged config.env.ts's JSDoc path as a false positive.
1734
+ /(?:from|import|export|require)\s*\(?\s*['"]@lenne\.tech\/nest-server(?:\/[^'"]*)?['"]/);
1712
1735
  if (staleImports.length > 0) {
1713
1736
  const { print } = this.toolbox;
1714
1737
  print.warning(`⚠ ${staleImports.length} file(s) still contain '@lenne.tech/nest-server' imports after vendor conversion:`);
@@ -2218,17 +2241,11 @@ class Server {
2218
2241
  // (they'll come back via node_modules when nest-server is installed)
2219
2242
  // We only remove deps that are ALSO in nest-server's package.json.
2220
2243
  // For safety, we don't remove any dep the consumer might use directly.
2221
- // Remove vendor-specific scripts
2244
+ // Remove vendor-specific scripts + unhook vendor-freshness (see lib helper).
2222
2245
  const scripts = pkg.scripts || {};
2223
- delete scripts['check:vendor-freshness'];
2224
2246
  delete scripts['vendor:sync'];
2225
2247
  delete scripts['vendor:propose-upstream'];
2226
- // Unhook vendor-freshness from check / check:fix / check:naf
2227
- for (const key of ['check', 'check:fix', 'check:naf']) {
2228
- if (scripts[key] && typeof scripts[key] === 'string') {
2229
- scripts[key] = scripts[key].replace(/pnpm run check:vendor-freshness && /g, '');
2230
- }
2231
- }
2248
+ (0, check_freshness_hooks_1.unhookCheckFreshness)(scripts);
2232
2249
  // Restore migrate scripts to npm-mode paths
2233
2250
  const migrateCompiler = 'ts:./node_modules/@lenne.tech/nest-server/dist/core/modules/migrate/helpers/ts-compiler.js';
2234
2251
  const migrateStore = '--store ./migrations-utils/migrate.js --migrations-dir ./migrations';
@@ -2363,6 +2380,177 @@ class Server {
2363
2380
  }
2364
2381
  return stale;
2365
2382
  }
2383
+ /**
2384
+ * Backfills the vendored core's transitive import closure into the
2385
+ * project's dependencies. After vendoring, `src/core/**` imports packages
2386
+ * that previously arrived transitively via the `@lenne.tech/nest-server`
2387
+ * npm dependency (e.g. `cron`, `jose`, `fs-capacitor`, `graphql-ws`,
2388
+ * `ws`). nest-server never declares these as direct deps, so the
2389
+ * upstream-deps merge misses them — yet the vendored code imports them
2390
+ * directly, which pnpm's isolated node_modules cannot resolve → TS2307 at
2391
+ * build time.
2392
+ *
2393
+ * Scans every `.ts` file under the vendored core for bare import
2394
+ * specifiers, drops Node builtins and already-declared packages, and adds
2395
+ * the remainder (plus any matching `@types/*`) using EXACT versions read
2396
+ * from the upstream `pnpm-lock.yaml`. Mutates `deps`/`devDeps` in place.
2397
+ */
2398
+ gatherVendorCoreImportClosure(options) {
2399
+ const { coreDir, deps, devDeps, lockRaw, upstreamDeps, upstreamDevDeps } = options;
2400
+ const { print } = this.toolbox;
2401
+ const builtins = new Set(require('module').builtinModules);
2402
+ // Compare two dotted versions numerically (major.minor.patch) — used to
2403
+ // pick the highest when the lockfile pins multiple copies (e.g. ws 7/8).
2404
+ const compareVersions = (a, b) => {
2405
+ const pa = a.split('.').map((n) => parseInt(n, 10) || 0);
2406
+ const pb = b.split('.').map((n) => parseInt(n, 10) || 0);
2407
+ for (let i = 0; i < 3; i++) {
2408
+ if ((pa[i] || 0) !== (pb[i] || 0)) {
2409
+ return (pa[i] || 0) - (pb[i] || 0);
2410
+ }
2411
+ }
2412
+ return 0;
2413
+ };
2414
+ // Parse `<name>@<version>` keys from the lockfile's `packages:` section
2415
+ // into a name→version map (highest version wins). pnpm-lock v9 lists
2416
+ // every package (direct + transitive) there with a clean 2-space indent.
2417
+ const parseLockVersions = (raw) => {
2418
+ const map = {};
2419
+ let inPackages = false;
2420
+ for (const line of raw.split('\n')) {
2421
+ if (/^packages:\s*$/.test(line)) {
2422
+ inPackages = true;
2423
+ continue;
2424
+ }
2425
+ if (inPackages && /^\S/.test(line)) {
2426
+ break; // reached the next top-level section
2427
+ }
2428
+ if (!inPackages) {
2429
+ continue;
2430
+ }
2431
+ const matched = line.match(/^ {2}(['"]?)(.+?)\1:\s*$/);
2432
+ if (!matched) {
2433
+ continue;
2434
+ }
2435
+ let key = matched[2] || '';
2436
+ const paren = key.indexOf('('); // strip pnpm peer suffix
2437
+ if (paren >= 0) {
2438
+ key = key.slice(0, paren);
2439
+ }
2440
+ const at = key.lastIndexOf('@');
2441
+ if (at <= 0) {
2442
+ continue;
2443
+ }
2444
+ const name = key.slice(0, at);
2445
+ const version = key.slice(at + 1);
2446
+ if (!/^\d/.test(version)) {
2447
+ continue; // ranges / non-versions
2448
+ }
2449
+ const current = map[name];
2450
+ if (!current || compareVersions(version, current) > 0) {
2451
+ map[name] = version;
2452
+ }
2453
+ }
2454
+ return map;
2455
+ };
2456
+ // Reduce an import specifier to its package name (or null for relative
2457
+ // paths and Node builtins, which need no dependency entry).
2458
+ const packageNameOf = (spec) => {
2459
+ if (!spec || spec.startsWith('.') || spec.startsWith('/')) {
2460
+ return null;
2461
+ }
2462
+ const bare = spec.startsWith('node:') ? spec.slice('node:'.length) : spec;
2463
+ const parts = bare.split('/');
2464
+ const name = bare.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0] || '';
2465
+ // Drop Node builtins, the framework package itself (never a dependency
2466
+ // of its own vendored code), and anything that is not a syntactically
2467
+ // valid npm package name (defensive guard).
2468
+ if (!name || builtins.has(name) || name === '@lenne.tech/nest-server') {
2469
+ return null;
2470
+ }
2471
+ return /^(?:@[\w.~-]+\/)?[\w.~-]+$/.test(name) ? name : null;
2472
+ };
2473
+ // Collect every bare specifier imported by the vendored core. Parse the
2474
+ // TypeScript AST (ts-morph) rather than scanning text, so that prose in
2475
+ // comments/strings — a JSDoc `import … from '@lenne.tech/nest-server'`
2476
+ // example, or an English `… distinguishable from "never set"` — is never
2477
+ // mistaken for a real import (which a naive regex did).
2478
+ const imported = new Set();
2479
+ const { Project, SyntaxKind } = require('ts-morph');
2480
+ const scanProject = new Project({
2481
+ skipAddingFilesFromTsConfig: true,
2482
+ skipFileDependencyResolution: true,
2483
+ });
2484
+ const sourceFiles = scanProject.addSourceFilesAtPaths(`${coreDir}/**/*.ts`);
2485
+ for (const sourceFile of sourceFiles) {
2486
+ // `import … from 'x'` and `export … from 'x'`
2487
+ for (const decl of [...sourceFile.getImportDeclarations(), ...sourceFile.getExportDeclarations()]) {
2488
+ const spec = decl.getModuleSpecifierValue();
2489
+ const name = spec ? packageNameOf(spec) : null;
2490
+ if (name) {
2491
+ imported.add(name);
2492
+ }
2493
+ }
2494
+ // `import x = require('x')`
2495
+ for (const decl of sourceFile.getDescendantsOfKind(SyntaxKind.ImportEqualsDeclaration)) {
2496
+ const ref = decl
2497
+ .getModuleReference()
2498
+ .getText()
2499
+ .match(/^require\(\s*['"]([^'"]+)['"]\s*\)$/);
2500
+ const name = ref ? packageNameOf(ref[1] || '') : null;
2501
+ if (name) {
2502
+ imported.add(name);
2503
+ }
2504
+ }
2505
+ // Dynamic `import('x')` / `require('x')` calls
2506
+ for (const call of sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression)) {
2507
+ const callee = call.getExpression().getText();
2508
+ if (callee !== 'import' && callee !== 'require') {
2509
+ continue;
2510
+ }
2511
+ const arg = call.getArguments()[0];
2512
+ if (arg && arg.getKind() === SyntaxKind.StringLiteral) {
2513
+ const name = packageNameOf(arg.getLiteralText());
2514
+ if (name) {
2515
+ imported.add(name);
2516
+ }
2517
+ }
2518
+ }
2519
+ }
2520
+ const lockVersions = parseLockVersions(lockRaw);
2521
+ const added = [];
2522
+ const unresolved = [];
2523
+ for (const name of [...imported].sort()) {
2524
+ if (name in deps || name in devDeps) {
2525
+ continue; // already declared by the starter or the upstream merge
2526
+ }
2527
+ const version = lockVersions[name] || upstreamDeps[name] || upstreamDevDeps[name];
2528
+ if (!version) {
2529
+ unresolved.push(name);
2530
+ continue;
2531
+ }
2532
+ deps[name] = version;
2533
+ added.push(`${name}@${version}`);
2534
+ // Add a matching @types/* (for packages that ship no own types) so the
2535
+ // vendored core type-checks. Handles scoped names
2536
+ // (@scope/x → @types/scope__x) too.
2537
+ const typesName = name.startsWith('@') ? `@types/${name.slice(1).replace('/', '__')}` : `@types/${name}`;
2538
+ if (!(typesName in deps) && !(typesName in devDeps)) {
2539
+ const typesVersion = lockVersions[typesName] || upstreamDevDeps[typesName];
2540
+ if (typesVersion) {
2541
+ devDeps[typesName] = typesVersion;
2542
+ }
2543
+ }
2544
+ }
2545
+ if (added.length > 0) {
2546
+ print.info(` vendored core closure: added ${added.length} transitive dep(s) → ${added.join(', ')}`);
2547
+ }
2548
+ if (unresolved.length > 0) {
2549
+ print.warning(`⚠ ${unresolved.length} vendored-core import(s) could not be resolved to a version ` +
2550
+ `(absent from the upstream lock and package.json): ${unresolved.join(', ')}. ` +
2551
+ 'Add them to projects/api/package.json manually if the build fails.');
2552
+ }
2553
+ }
2366
2554
  /**
2367
2555
  * Checks whether an import specifier resolves to the vendored core directory.
2368
2556
  * Used during vendor→npm import rewriting.
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ /**
3
+ * Helpers for (un)hooking the vendor-freshness step in a project's `check`
4
+ * scripts during vendor-mode conversion. Extracted so the logic is unit-tested
5
+ * (server.ts + frontend-helper.ts call these instead of inline closures).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.hookCheckFreshness = hookCheckFreshness;
9
+ exports.unhookCheckFreshness = unhookCheckFreshness;
10
+ const WRAPPER_HINT = 'check.mjs';
11
+ const FRESHNESS_STEP = 'pnpm run check:vendor-freshness';
12
+ const INSTALL_PREFIX = 'pnpm install && ';
13
+ /**
14
+ * Prepend the `check:vendor-freshness` step to the relevant check scripts.
15
+ *
16
+ * - Skips a script that already includes the step (idempotent) or that IS the
17
+ * `node scripts/check.mjs` wrapper (the wrapper only orchestrates the real
18
+ * chain — vendor-freshness belongs in that chain).
19
+ * - Targets `check:raw` instead of `check` when the project runs the wrapper,
20
+ * so freshness becomes a step the wrapper executes.
21
+ * - Preserves a leading `pnpm install && ` so install stays first.
22
+ *
23
+ * Assumes the caller has already defined `scripts['check:vendor-freshness']`.
24
+ * Mutates `scripts` in place.
25
+ */
26
+ function hookCheckFreshness(scripts) {
27
+ const hook = (name) => {
28
+ const existing = scripts[name];
29
+ if (!existing || existing.includes('check:vendor-freshness') || existing.includes(WRAPPER_HINT)) {
30
+ return;
31
+ }
32
+ scripts[name] = existing.startsWith(INSTALL_PREFIX)
33
+ ? `${INSTALL_PREFIX}${FRESHNESS_STEP} && ${existing.slice(INSTALL_PREFIX.length)}`
34
+ : `${FRESHNESS_STEP} && ${existing}`;
35
+ };
36
+ // Prefer the raw chain when the project runs the check.mjs wrapper.
37
+ hook(scripts['check:raw'] ? 'check:raw' : 'check');
38
+ hook('check:fix');
39
+ hook('check:naf');
40
+ }
41
+ /**
42
+ * Remove the vendor-freshness step (and its script) from a project's check
43
+ * scripts — the inverse of {@link hookCheckFreshness}, used when converting a
44
+ * project back to npm mode. Mutates `scripts` in place.
45
+ */
46
+ function unhookCheckFreshness(scripts) {
47
+ delete scripts['check:vendor-freshness'];
48
+ for (const name of ['check', 'check:raw', 'check:fix', 'check:naf']) {
49
+ if (typeof scripts[name] === 'string') {
50
+ scripts[name] = scripts[name].replace(/pnpm run check:vendor-freshness && /g, '');
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,56 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CODEX_MARKETPLACE_ROOT = void 0;
4
+ exports.findCodexCli = findCodexCli;
5
+ exports.runCodexCommand = runCodexCommand;
6
+ /**
7
+ * Codex CLI utilities.
8
+ */
9
+ const child_process_1 = require("child_process");
10
+ const fs_1 = require("fs");
11
+ const os_1 = require("os");
12
+ const path_1 = require("path");
13
+ exports.DEFAULT_CODEX_MARKETPLACE_ROOT = process.env.LT_CODEX_MARKETPLACE_ROOT || '/Users/kaihaase/code/lenneTech/codex';
14
+ function findCodexCli() {
15
+ const possiblePaths = [
16
+ (0, path_1.join)((0, os_1.homedir)(), '.local', 'bin', 'codex'),
17
+ (0, path_1.join)((0, os_1.homedir)(), '.codex', 'bin', 'codex'),
18
+ '/usr/local/bin/codex',
19
+ '/opt/homebrew/bin/codex',
20
+ '/usr/bin/codex',
21
+ ];
22
+ for (const path of possiblePaths) {
23
+ if ((0, fs_1.existsSync)(path)) {
24
+ return path;
25
+ }
26
+ }
27
+ try {
28
+ const result = (0, child_process_1.spawnSync)('which', ['codex'], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] });
29
+ const path = (result.stdout || '').trim();
30
+ if (result.status === 0 && path && (0, fs_1.existsSync)(path)) {
31
+ return path;
32
+ }
33
+ }
34
+ catch (_a) {
35
+ // Codex CLI not found in PATH.
36
+ }
37
+ return null;
38
+ }
39
+ function runCodexCommand(cli, args) {
40
+ try {
41
+ const result = (0, child_process_1.spawnSync)(cli, args, {
42
+ encoding: 'utf-8',
43
+ stdio: ['pipe', 'pipe', 'pipe'],
44
+ });
45
+ return {
46
+ output: result.stdout + result.stderr,
47
+ success: result.status === 0,
48
+ };
49
+ }
50
+ catch (err) {
51
+ return {
52
+ output: err.message,
53
+ success: false,
54
+ };
55
+ }
56
+ }