@lenne.tech/cli 1.41.3 → 1.43.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/docs/commands.md CHANGED
@@ -606,6 +606,46 @@ lt dev doctor
606
606
 
607
607
  ---
608
608
 
609
+ ### `lt dev vscode`
610
+
611
+ Apply a verified low-memory profile to VS Code's **user** settings. Targets the per-workspace TypeScript servers, which dominate memory once several monorepos are open at once: every workspace root spawns its own semantic TS server, so eight monorepos with an `api` and an `app` root each add up to 16 of them.
612
+
613
+ **Usage:**
614
+ ```bash
615
+ lt dev vscode # preview, then apply after confirmation
616
+ lt dev vscode --dry-run # show the diff, write nothing
617
+ lt dev vscode --explain # print the profile with reasons + the keys left out on purpose
618
+ lt dev vscode --revert # remove the profile keys again
619
+ lt dev vscode --variant cursor # limit to one: code | insiders | cursor | vscodium
620
+ lt dev vscode --noConfirm # skip the confirmation prompt
621
+ ```
622
+
623
+ **Alias:** `lt d vsc`
624
+
625
+ **Profile:**
626
+
627
+ | Key | Value | Why |
628
+ |---|---|---|
629
+ | `typescript.tsserver.maxTsServerMemory` | 2048 | Per-server heap ceiling; the default is 3072 and multiplies across every open root |
630
+ | `typescript.preferences.includePackageJsonAutoImports` | `off` | Auto-import stops scanning every package.json — the largest monorepo win |
631
+ | `typescript.disableAutomaticTypeAcquisition` | `true` | Skips the `@types` download/scan pass |
632
+ | `files.watcherExclude` | node_modules, dist, `.nuxt*`, `.output*`, .git internals | Keeps the file watcher off generated trees |
633
+ | `search.exclude` | node_modules, dist, `.nuxt*`, `.output*` | Keeps full-text search from indexing them |
634
+
635
+ The globs are `.nuxt*` / `.output*`, not the bare names: a glob segment matches whole path segments, so `**/.nuxt/**` would leave the sibling build dirs (`.nuxt-check` from the check chain, `.nuxt-test` / `.output-test` from `lt dev test`) watched and indexed — and a `.output-test` tree is 37-294 MB.
636
+
637
+ **Safety:** JSONC-aware via `jsonc-parser`, so comments and formatting in a hand-maintained `settings.json` survive. Refuses to write into a file it cannot parse or a symlink, backs up to `settings.json.bak` (the **first** backup is kept, so a later run — including `--revert` — cannot overwrite the record of the pre-tuning state), and merges the object-valued exclude maps so hand-added entries are kept. Re-running is a true no-op.
638
+
639
+ **`--revert`** subtracts only the entries this profile contributed, so a hand-maintained exclusion is never removed along with them. For the scalar keys it deletes the key, restoring VS Code's own default — an explicit value that preceded the tuning is not restored; that is what the `.bak` is for.
640
+
641
+ **Detects:** VS Code, VS Code Insiders, Cursor, VSCodium — every variant whose user settings file exists.
642
+
643
+ **After applying**, restart VS Code (or run *Developer: Reload Window*) — the TypeScript servers read these settings at startup, so nothing changes until they do.
644
+
645
+ Run `--explain` to see three commonly recommended keys the profile deliberately omits, each with the manifest check or measurement that ruled it out.
646
+
647
+ ---
648
+
609
649
  ### `lt dev test`
610
650
 
611
651
  One-shot E2E wrapper: ensure `up`, wait for the App URL, run `pnpm run test:e2e` with the `.lt-dev/.env` bridge loaded. Optional teardown after.
@@ -649,7 +689,37 @@ lt dev test -- --ui spec.ts # everything after `--` is forwarded to playwri
649
689
  | `LT_DEV_ACTIVE`, `LT_DEV_DB_NAME` | Marker keys for consumers |
650
690
  | `NODE_EXTRA_CA_CERTS` | Path to Caddy's root CA cert (auto-detected) |
651
691
 
652
- `lt dev init` injects a tiny `// >>> lt-dev:bridge >>>` block at the top of `playwright.config.ts` that loads this file at config-load time — making Playwright (CLI, IDE, VS Code extension) automatically use the `lt dev` URLs and trust the local CA, without inheriting the parent shell.
692
+ Additionally, `lt dev test` exports two build-directory keys into the app process it
693
+ spawns. They are **not** written to the bridge file — they scope one run, not the
694
+ project:
695
+
696
+ | Key | Value | Why |
697
+ |-----|-------|-----|
698
+ | `NUXT_BUILD_DIR` | `.nuxt-test` | Nuxt holds its lock on the build dir (`acquireLock(nuxt.options.buildDir)`). Sharing `.nuxt` with a parked `nuxt dev` did not interleave writes — it made the test build **abort** with "Another Nuxt dev is already running", so the app never came up and every spec failed on a missing selector. That reads like broken specs while being pure infrastructure. |
699
+ | `NITRO_OUTPUT_DIR` | `.output-test` | A separate axis: `buildDir` and Nitro's `output.dir` are unrelated knobs. `lt dev test` serves the production bundle, so it rebuilds on every run and used to overwrite the `.output` a local `pnpm run build` was using. |
700
+
701
+ **The project must forward both**, or the isolation silently degrades to the shared
702
+ directories. `nuxt-base-starter` ≥ 2.16.0 does this out of the box:
703
+
704
+ ```ts
705
+ // nuxt.config.ts
706
+ buildDir: process.env.NUXT_BUILD_DIR || '.nuxt',
707
+ nitro: { output: { dir: process.env.NITRO_OUTPUT_DIR || '.output' } },
708
+ ```
709
+
710
+ **Neither key is framework-native**, despite the prefixes — verified against
711
+ `@nuxt/schema`, `nitropack` and `c12`: none of them reads `NUXT_BUILD_DIR` or
712
+ `NITRO_OUTPUT_DIR`. Both levers are opened by the project's own `nuxt.config.ts`,
713
+ which is why the two-line snippet above is required rather than optional. (Singling
714
+ one of them out as "not a framework feature" reads as if the other one were, and a
715
+ reader acting on that forwards only half — leaving exactly the collision this
716
+ section exists to prevent.) For projects that have not adopted it, the CLI keeps a
717
+ `.output/server/index.mjs` fallback when locating the built server.
718
+
719
+ A project that ignores both keys still works; it just loses the isolation, so a
720
+ `lt dev test` run and a parked `nuxt dev` collide again.
721
+
722
+ `lt dev init` injects a tiny `// >>> lt-dev:bridge v2 >>>` block at the top of `playwright.config.ts` that loads this file at config-load time — making Playwright (CLI, IDE, VS Code extension) automatically use the `lt dev` URLs and trust the local CA, without inheriting the parent shell.
653
723
 
654
724
  `lt dev down` removes the bridge file so subsequent runs without `lt dev up` fall back cleanly to the classic `localhost:3000`/`localhost:3001` defaults.
655
725
 
@@ -1005,6 +1075,15 @@ Installs helper scripts:
1005
1075
 
1006
1076
  ### `lt fullstack init`
1007
1077
 
1078
+ **Exit codes.** `0` on success, `1` on any failure — a failed clone, an aborted
1079
+ `pnpm install`, an invalid flag value, an existing target directory. Until 1.43.0
1080
+ every one of those exited `0`, so `lt fullstack init … && echo ok` printed `ok`
1081
+ after a broken scaffold; scripts and CI jobs that check `$?` need no workaround
1082
+ any more. The same contract holds for `lt fullstack add-api` / `add-app`, which
1083
+ `init` delegates to inside an existing workspace. A cancelled interactive prompt
1084
+ is not a failure and still exits `0`.
1085
+
1086
+
1008
1087
  Creates a new fullstack workspace with API and frontend.
1009
1088
 
1010
1089
  **Usage:**
@@ -1127,6 +1206,36 @@ lt fullstack add-app [options]
1127
1206
 
1128
1207
  ---
1129
1208
 
1209
+ ### `lt fullstack update`
1210
+
1211
+ Prints the mode-specific update entry points for backend and frontend — **and repairs generated project scaffolding on the way**. The name undersells it: this command writes files.
1212
+
1213
+ **Usage:**
1214
+ ```bash
1215
+ lt fullstack update
1216
+ ```
1217
+
1218
+ **Self-heals** (each is idempotent and a no-op when nothing is wrong):
1219
+
1220
+ | What | Why it needs healing |
1221
+ |---|---|
1222
+ | `.gitignore` — adds `.lt-dev/` | Added after many projects were scaffolded |
1223
+ | `check` wrapper script | Same |
1224
+ | Vendor `CLAUDE.md` | Same |
1225
+ | `migrations-utils/migrate.js` | Written **once**, at vendor-conversion time. It is project scaffolding, not `src/core/`, so no update path ever revisits it — a project converted before the template stopped requiring `ts-node` unconditionally keeps the broken file forever, and every deployed container then dies with `Cannot find module 'ts-node'` before applying a single migration (silently, because the entrypoint degrades a migration failure to a warning on purpose). |
1226
+
1227
+ The migration-store repair is deliberately narrow. It acts **only** when the `require('./ts-compiler')` is a top-level, unconditional statement — the one shape that provably cannot survive a production image where `ts-node` was pruned. Any conditional form (inside `try`, `if`, a function, a ternary) is the project's own working solution and is left untouched, because the replacement is not behaviour-neutral: the bundled template hardcodes the collection name and takes its URI from `./mongo-uri`, so overwriting a customized store would empty the migration ledger and re-run every historical migration.
1228
+
1229
+ Before overwriting, the command establishes that the change is undoable. A file that git tracks and that is unmodified is simply replaced (git has the copy). A file git cannot recover — untracked, `.gitignore`d, or outside a repo — gets a `.bak` first. A tracked file with **uncommitted** changes is never touched and is reported as skipped:
1230
+
1231
+ ```
1232
+ migrations-utils/migrate.js (skipped: uncommitted changes — commit or discard them, then re-run)
1233
+ ```
1234
+
1235
+ That skip line is the only signal that a repair was needed but not applied — commit or discard, then re-run.
1236
+
1237
+ ---
1238
+
1130
1239
  ### `lt fullstack convert-mode`
1131
1240
 
1132
1241
  Convert **both** backend (`projects/api/`) and frontend (`projects/app/`) of a fullstack monorepo between npm mode and vendor mode in a single command. Auto-detects the subprojects, shows the plan for each side, and orchestrates the backend + frontend conversions sequentially.
@@ -1220,6 +1329,36 @@ them from the TurboOps stage env at runtime, so nothing is patched there):
1220
1329
  `environment.ts` (local dev) is never touched. Only the URL origin is replaced, so
1221
1330
  custom paths (`/v2/graphql`) survive and a re-run with a new domain updates them.
1222
1331
 
1332
+ #### Database host: always stack-prefixed, never bare `mongo`
1333
+
1334
+ The printed checklist spells the DB URI out per stage, and the exact host matters:
1335
+
1336
+ ```
1337
+ NSC__MONGOOSE__URI=mongodb://<user>:<pass>@<project>-production_mongo:27017/<db>?authSource=admin
1338
+ NSC__MONGOOSE__URI=mongodb://<user>:<pass>@<project>-dev_mongo:27017/<db>?authSource=admin
1339
+ ```
1340
+
1341
+ **Never `mongodb://mongo:27017/<db>`.** The project's own `docker-compose.yml` names
1342
+ the service `mongo`, which makes the short name the obvious guess — and the wrong
1343
+ one. TurboOps deploys every stack onto a shared overlay network, where the bare
1344
+ service name is an alias that *every* stack's `mongo` answers to. The connection
1345
+ then lands on a foreign project's database, and on a different one per connection.
1346
+
1347
+ The symptoms do not look like a configuration problem: writes split across two
1348
+ databases, sessions that vanish after a reconnect, files whose bytes are "sometimes"
1349
+ missing. Meanwhile the application's own database sits empty. A project lost a day of
1350
+ debugging to this (DEV-2140) with the application code fully correct.
1351
+
1352
+ TurboOps rejects bare DB hosts and isolates bare-named DB services from the shared
1353
+ overlay since v1.72.0 — but only from that version, and only for stacks deployed
1354
+ after it. The stack-prefixed host is correct either way, so use it unconditionally.
1355
+
1356
+ The prefix fixes **which** database you reach, not **who** may reach it: the overlay
1357
+ network stays shared, so the database credentials are the actual boundary. Give the
1358
+ mongo service a user and password and connect with
1359
+ `mongodb://<user>:<pass>@<project>-<stage>_mongo:27017/<db>?authSource=admin` — an
1360
+ unauthenticated instance is readable and writable by every co-tenant stack.
1361
+
1223
1362
  **Usage:**
1224
1363
  ```bash
1225
1364
  lt deployment create [name] [domain] [options]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/cli",
3
- "version": "1.41.3",
3
+ "version": "1.43.0",
4
4
  "description": "lenne.Tech CLI: lt",
5
5
  "keywords": [
6
6
  "lenne.Tech",
@@ -57,19 +57,20 @@
57
57
  "bin"
58
58
  ],
59
59
  "dependencies": {
60
- "@aws-sdk/client-s3": "3.1090.0",
60
+ "@aws-sdk/client-s3": "3.1100.0",
61
61
  "@lenne.tech/cli-plugin-helper": "0.0.14",
62
- "axios": "1.18.1",
62
+ "axios": "1.19.0",
63
63
  "bcrypt": "6.0.0",
64
- "defuddle": "0.19.1",
64
+ "defuddle": "0.19.2",
65
65
  "glob": "13.0.6",
66
66
  "gluegun": "5.2.2",
67
- "js-sha256": "0.11.1",
68
- "js-yaml": "4.3.0",
67
+ "js-sha256": "1.0.0",
68
+ "js-yaml": "4.3.1",
69
69
  "jsdom": "29.1.1",
70
+ "jsonc-parser": "3.3.1",
70
71
  "lodash": "4.18.1",
71
72
  "open": "11.0.0",
72
- "playwright-core": "1.61.1",
73
+ "playwright-core": "1.62.1",
73
74
  "ts-morph": "28.0.0",
74
75
  "ts-node": "10.9.2",
75
76
  "turndown": "7.2.4",
@@ -77,39 +78,57 @@
77
78
  "typescript": "6.0.3"
78
79
  },
79
80
  "devDependencies": {
80
- "@lenne.tech/eslint-config-ts": "2.1.4",
81
+ "@lenne.tech/eslint-config-ts": "2.3.0",
81
82
  "@lenne.tech/npm-package-helper": "0.0.12",
82
83
  "@types/ejs": "3.1.5",
83
84
  "@types/jest": "30.0.0",
84
85
  "@types/js-yaml": "4.0.9",
85
86
  "@types/jsdom": "28.0.1",
86
87
  "@types/lodash": "4.17.24",
87
- "@types/node": "26.1.1",
88
+ "@types/node": "26.1.2",
88
89
  "@types/turndown": "5.0.6",
89
90
  "ejs": "6.0.1",
90
- "eslint": "9.39.4",
91
+ "eslint": "10.8.0",
91
92
  "husky": "9.1.7",
92
93
  "jest": "30.4.2",
94
+ "minimatch": "10.2.6",
93
95
  "prettier": "3.8.3",
94
96
  "rimraf": "6.1.3",
95
- "ts-jest": "29.4.11"
97
+ "ts-jest": "29.4.12"
96
98
  },
97
99
  "//overrides": {
98
100
  "semver@*": "Force latest semver 7.x across all sub-deps; gluegun@5.2.2 pins semver@7.7.0 which is stale - remove once gluegun updates its dep.",
99
- "brace-expansion@<1.1.16": "DoS via exponential-time expansion of consecutive non-expanding {} groups (GHSA-3jxr-9vmj-r5cp, high). Transitive via dotgitignore/eslint/fs-jetpack/glob/test-exclude > minimatch. One bounded key per affected major so each can only raise a vulnerable version, never cap a patched one - remove once minimatch requests the patched ranges.",
100
- "brace-expansion@>=2.0.0 <2.1.2": "Same advisory, 2.x line.",
101
- "brace-expansion@>=5.0.0 <5.0.7": "Same advisory, 5.x line. Floored at >=5.0.0 so a future 3.x/4.x dependency is not silently forced across two majors."
101
+ "brace-expansion@<1.1.18": "Two DoS advisories: exponential-time expansion of consecutive non-expanding {} groups (GHSA-3jxr-9vmj-r5cp, high) and unbounded expansion length causing an OOM crash (GHSA-mh99-v99m-4gvg, high). Transitive via dotgitignore/eslint/fs-jetpack/glob/test-exclude/ts-morph > minimatch. One bounded key per affected major so each can only raise a vulnerable version, never cap a patched one - remove once minimatch requests the patched ranges.",
102
+ "brace-expansion@>=2.0.0 <2.1.4": "Same advisories, 2.x line.",
103
+ "brace-expansion@>=5.0.0 <5.0.9": "Same advisories, 5.x line. Floored at >=5.0.0 so a future 3.x/4.x dependency is not silently forced across two majors.",
104
+ "//minimatch-note": "brace-expansion 1.x and 2.x are END OF LINE for GHSA-mh99-v99m-4gvg: the advisory range is <=5.0.7 across ALL majors and upstream patched ONLY 5.0.8+. 1.1.18 and 2.1.4 are the newest releases of their majors and remain vulnerable. Forcing brace-expansion 5.x globally is NOT an option: 5.x is ESM/tshy and exports an object ({ expand, ... }) while 1.x/2.x export the function itself, so minimatch 3.x/5.x would die on `expand is not a function`. The only real fix is to raise each CONSUMER off minimatch 3.x/5.x, which is what the scoped entries below do. Each was verified against the consumer's actual call site before being added. The eslint / @eslint/eslintrc / @eslint/config-array entries were REMOVED in 1.42.0: eslint 10 requests minimatch ^10.2.5 itself, config-array ^10.2.4, and eslintrc is no longer in the tree at all.",
105
+ "minimatch@>=4 <10": "Bounded raise of every minimatch 4.x-9.x install to 10.2.6, whose brace-expansion ^5.0.8 is patched. Covers filelist (gluegun > ejs > jake > filelist, PRODUCTION-reachable, minimatch ^5.0.1), @typescript-eslint/typescript-estree (^9.0.4) and jest-config/jest-runtime/@jest/reporters (^9). Verified safe per consumer: filelist calls only the static helper `minimatch.match(files, pat, opts)`; typescript-estree calls the named `minimatch(filePath, pattern, { dot: true })`; the three jest packages declare minimatch but never require it. Floored at >=4 so the 3.x line is NOT swept in: minimatch 3.x is callable while 9.x/10.x export an object with __esModule but no `default` key, so sweeping it would break any consumer that calls the default export. Drop once these consumers request minimatch >=10 themselves.",
106
+ "babel-plugin-istanbul > test-exclude@<8": "test-exclude 6.0.0 pins minimatch ^3.0.4 and glob ^7. test-exclude 8.0.0 uses minimatch ^10.2.2 + glob ^13 (the glob major this project already ships) and is still CJS with the same `module.exports = TestExclude` class shape, so babel-plugin-istanbul's _interopRequireDefault + `new TestExclude(opts)` keeps working. Drop once babel-plugin-istanbul widens its ^6.0.0 range.",
107
+ "fs-jetpack > minimatch@<10": "Same chain, PRODUCTION-reachable (gluegun > fs-jetpack). Safe: lib/utils/matcher.js does `require('minimatch').Minimatch` and uses only `new Minimatch(pattern, { matchBase, nocomment, nocase, dot })`, `.negate` and `.match()` - all verified against 10.x. Not fixable by upgrading fs-jetpack: gluegun@5.2.2 is the latest release and fs-jetpack@5.1.0 still requests minimatch ^5.1.0 (brace-expansion ^2.0.1, also unpatched).",
108
+ "@istanbuljs/load-nyc-config": "GHSA-5p4m-2wfm-xmqj (high): quadratic CPU consumption resolving !!omap. Patched only in 4.3.1 - the advisory states the fix was NOT backported to 3.x, so 3.15.0 is the end of its line and there is nothing to raise it to within the major. The one 3.x path is dev-only: ts-jest > @jest/transform > babel-plugin-istanbul > @istanbuljs/load-nyc-config > js-yaml@3.15.0. Cross-major export shape verified before adding (repo policy): load-nyc-config calls `require('js-yaml').load(...)` at index.js:80, and 4.x exports `load` - so the raise is API-compatible. It is also strictly SAFER: 4.x `load` behaves like 3.x `safeLoad`, refusing arbitrary type construction, and the input here is a repo-local .nycrc.yml. Scoped to this ONE consumer rather than raised globally: that is the call site whose shape was verified, and a global raise would silently apply to consumers nobody checked. A top-level `js-yaml@<4.3.1` selector was tried first and npm did not apply it to this nested path at all - the scoped form is what actually resolves. Remove once ts-jest's istanbul chain requests a patched js-yaml itself."
102
109
  },
103
110
  "overrides": {
104
111
  "semver@*": "7.8.5",
105
- "brace-expansion@<1.1.16": "1.1.16",
106
- "brace-expansion@>=2.0.0 <2.1.2": "2.1.2",
107
- "brace-expansion@>=5.0.0 <5.0.7": "5.0.7"
112
+ "brace-expansion@<1.1.18": "1.1.18",
113
+ "brace-expansion@>=2.0.0 <2.1.4": "2.1.4",
114
+ "brace-expansion@>=5.0.0 <5.0.9": "5.0.9",
115
+ "fs-jetpack": {
116
+ "minimatch@<10": "10.2.6"
117
+ },
118
+ "babel-plugin-istanbul": {
119
+ "test-exclude@<8": "8.0.0"
120
+ },
121
+ "minimatch@>=4 <10": "10.2.6",
122
+ "@istanbuljs/load-nyc-config": {
123
+ "js-yaml": "4.3.1"
124
+ }
108
125
  },
126
+ "//jest.workerIdleMemoryLimit": "Recycle a ts-jest worker once its heap passes this. Guards against an intermittent `A jest worker process was terminated by another process: signal=SIGSEGV` that kills ONE suite while every other test passes (seen twice in marketplace*, both under `npm run check`, never reproducible on demand - 0 in 9 targeted runs incl. --maxWorkers=16). It is a worker crash, NOT an assertion failure: the signature is `Test suite failed to run` with the remaining count still green. All versions are current and in-range (node 24 / jest 30 / ts-jest 29 / ts 6), so this is a resilience measure against unbounded worker heap growth, not a proven root-cause fix - if it recurs, capture the suite name and re-open.",
109
127
  "jest": {
110
128
  "testEnvironment": "node",
111
129
  "rootDir": "__tests__",
112
130
  "testTimeout": 60000,
131
+ "workerIdleMemoryLimit": "512MB",
113
132
  "testMatch": [
114
133
  "<rootDir>/*.test.ts"
115
134
  ],