@lenne.tech/cli 1.25.0 → 1.26.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.
@@ -40,13 +40,48 @@ const dev_state_1 = require("../../lib/dev-state");
40
40
  * NSC__MONGOOSE__URI, NSC__BASE_URL, NSC__APP_URL, DATABASE_URL,
41
41
  * NUXT_PUBLIC_API_PROXY=false (Caddy makes vite-proxy obsolete).
42
42
  */
43
+ function formatBytes(bytes) {
44
+ if (bytes < 1024)
45
+ return `${bytes}B`;
46
+ if (bytes < 1024 * 1024)
47
+ return `${(bytes / 1024).toFixed(1)}KB`;
48
+ if (bytes < 1024 * 1024 * 1024)
49
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
50
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
51
+ }
52
+ function formatRotationNote(label, archivePath, previousSize) {
53
+ const size = formatBytes(previousSize);
54
+ const huge = previousSize > 100 * 1024 * 1024 ? ' (large — consider fixing noisy warnings)' : '';
55
+ return `Rotated previous ${label} log → ${archivePath} (${size})${huge}`;
56
+ }
57
+ /**
58
+ * Print the project's bound URLs (app, api, db) as a small info block.
59
+ *
60
+ * Used in two places so the user always sees the URLs next to the PIDs —
61
+ * once after a successful `up` and once when `up` short-circuits on
62
+ * "Already running". Falls back gracefully when only one of api/app is
63
+ * present (single-side projects).
64
+ */
65
+ function printProjectUrls(info, options) {
66
+ if (options.appHostname) {
67
+ const arrow = options.appUpstreamPort ? ` → 127.0.0.1:${options.appUpstreamPort}` : '';
68
+ info(` app: https://${options.appHostname}${arrow}`);
69
+ }
70
+ if (options.apiHostname) {
71
+ const arrow = options.apiUpstreamPort ? ` → 127.0.0.1:${options.apiUpstreamPort}` : '';
72
+ info(` api: https://${options.apiHostname}${arrow}`);
73
+ }
74
+ if (options.dbName) {
75
+ info(` db: mongodb://127.0.0.1/${options.dbName}`);
76
+ }
77
+ }
43
78
  const UpCommand = {
44
79
  alias: ['u'],
45
80
  description: 'Start API + App behind Caddy',
46
81
  hidden: false,
47
82
  name: 'up',
48
83
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
49
- var _a, _b, _c, _d, _e, _f;
84
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
50
85
  const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
51
86
  const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
52
87
  if (!layout.apiDir && !layout.appDir) {
@@ -110,6 +145,17 @@ const UpCommand = {
110
145
  const appUp = existingSession.pids.app ? (0, dev_state_1.isPidAlive)(existingSession.pids.app) : false;
111
146
  if (apiUp || appUp) {
112
147
  warning(`Already running (api pid ${(_a = existingSession.pids.api) !== null && _a !== void 0 ? _a : '-'}, app pid ${(_b = existingSession.pids.app) !== null && _b !== void 0 ? _b : '-'}).`);
148
+ // Surface the bound URLs so the user can copy them out without having
149
+ // to look up `lt dev status` separately. Falls back to the in-process
150
+ // identity/registry data — both sources stay in sync via saveRegistry.
151
+ const existingEntry = (0, dev_state_1.loadRegistry)().projects[identity.slug];
152
+ printProjectUrls(info, {
153
+ apiHostname: (_c = identity.subdomains.api) === null || _c === void 0 ? void 0 : _c.hostname,
154
+ apiUpstreamPort: existingEntry === null || existingEntry === void 0 ? void 0 : existingEntry.internalPorts.api,
155
+ appHostname: (_d = identity.subdomains.app) === null || _d === void 0 ? void 0 : _d.hostname,
156
+ appUpstreamPort: existingEntry === null || existingEntry === void 0 ? void 0 : existingEntry.internalPorts.app,
157
+ dbName: (_e = existingEntry === null || existingEntry === void 0 ? void 0 : existingEntry.dbName) !== null && _e !== void 0 ? _e : (0, dev_project_1.deriveDbName)(layout.apiDir, identity.slug),
158
+ });
113
159
  info('Run `lt dev down` first.');
114
160
  if (!parameters.options.fromGluegunMenu)
115
161
  process.exit(1);
@@ -120,10 +166,10 @@ const UpCommand = {
120
166
  const reg = (0, dev_state_1.loadRegistry)();
121
167
  const entry = reg.projects[identity.slug];
122
168
  const taken = (0, dev_state_1.takenInternalPorts)(reg, identity.slug);
123
- const apiPort = (_c = entry === null || entry === void 0 ? void 0 : entry.internalPorts.api) !== null && _c !== void 0 ? _c : (layout.apiDir ? (0, dev_state_1.allocateInternalPort)(4000, taken) : undefined);
169
+ const apiPort = (_f = entry === null || entry === void 0 ? void 0 : entry.internalPorts.api) !== null && _f !== void 0 ? _f : (layout.apiDir ? (0, dev_state_1.allocateInternalPort)(4000, taken) : undefined);
124
170
  if (apiPort)
125
171
  taken.add(apiPort);
126
- const appPort = (_d = entry === null || entry === void 0 ? void 0 : entry.internalPorts.app) !== null && _d !== void 0 ? _d : (layout.appDir ? (0, dev_state_1.allocateInternalPort)(4000, taken) : undefined);
172
+ const appPort = (_g = entry === null || entry === void 0 ? void 0 : entry.internalPorts.app) !== null && _g !== void 0 ? _g : (layout.appDir ? (0, dev_state_1.allocateInternalPort)(4000, taken) : undefined);
127
173
  // Pre-flight: internal ports free?
128
174
  const portsToCheck = [apiPort, appPort].filter((p) => typeof p === 'number');
129
175
  const snap = yield (0, dev_process_1.listenSnapshot)(portsToCheck);
@@ -174,23 +220,32 @@ const UpCommand = {
174
220
  });
175
221
  const pnpmBin = process.env.LT_PNPM_BIN || 'pnpm';
176
222
  const pids = {};
223
+ const rotationNotes = [];
177
224
  if (layout.apiDir && (0, fs_1.existsSync)((0, path_1.join)(layout.apiDir, 'package.json')) && apiPort) {
178
- const apiPid = (0, dev_process_1.spawnDetached)(pnpmBin, ['start'], {
225
+ const apiResult = (0, dev_process_1.spawnDetached)(pnpmBin, ['start'], {
179
226
  cwd: layout.apiDir,
180
227
  env: devEnv.api.env,
181
228
  logFile: (0, path_1.join)(layout.root, '.lt-dev', 'api.log'),
182
229
  });
183
- if (apiPid)
184
- pids.api = apiPid;
230
+ if (apiResult) {
231
+ pids.api = apiResult.pid;
232
+ if (apiResult.rotated.rotated && apiResult.rotated.archivePath !== undefined) {
233
+ rotationNotes.push(formatRotationNote('api', apiResult.rotated.archivePath, (_h = apiResult.rotated.previousSize) !== null && _h !== void 0 ? _h : 0));
234
+ }
235
+ }
185
236
  }
186
237
  if (layout.appDir && (0, fs_1.existsSync)((0, path_1.join)(layout.appDir, 'package.json')) && appPort) {
187
- const appPid = (0, dev_process_1.spawnDetached)(pnpmBin, ['dev'], {
238
+ const appResult = (0, dev_process_1.spawnDetached)(pnpmBin, ['dev'], {
188
239
  cwd: layout.appDir,
189
240
  env: devEnv.app.env,
190
241
  logFile: (0, path_1.join)(layout.root, '.lt-dev', 'app.log'),
191
242
  });
192
- if (appPid)
193
- pids.app = appPid;
243
+ if (appResult) {
244
+ pids.app = appResult.pid;
245
+ if (appResult.rotated.rotated && appResult.rotated.archivePath !== undefined) {
246
+ rotationNotes.push(formatRotationNote('app', appResult.rotated.archivePath, (_j = appResult.rotated.previousSize) !== null && _j !== void 0 ? _j : 0));
247
+ }
248
+ }
194
249
  }
195
250
  // Persist.
196
251
  const subdomainMap = {};
@@ -209,8 +264,20 @@ const UpCommand = {
209
264
  // custom shell scripts) can pick up the URLs without inheriting our shell.
210
265
  const bridgePath = (0, dev_env_bridge_1.writeEnvBridge)(layout.root, devEnv, dbName);
211
266
  info(colors.dim(`ENV bridge: ${bridgePath}`));
212
- success(`Started: api pid=${(_e = pids.api) !== null && _e !== void 0 ? _e : '-'}, app pid=${(_f = pids.app) !== null && _f !== void 0 ? _f : '-'}`);
267
+ success(`Started: api pid=${(_k = pids.api) !== null && _k !== void 0 ? _k : '-'}, app pid=${(_l = pids.app) !== null && _l !== void 0 ? _l : '-'}`);
268
+ // Echo the bound URLs next to the PIDs as well — the "Starting" block
269
+ // prints them before the spawn, but on a long boot log they scroll out
270
+ // of view, so repeating them here keeps PID + URL visually grouped.
271
+ printProjectUrls(info, {
272
+ apiHostname: (_m = identity.subdomains.api) === null || _m === void 0 ? void 0 : _m.hostname,
273
+ apiUpstreamPort: apiPort,
274
+ appHostname: (_o = identity.subdomains.app) === null || _o === void 0 ? void 0 : _o.hostname,
275
+ appUpstreamPort: appPort,
276
+ dbName,
277
+ });
213
278
  info(colors.dim('Logs: <root>/.lt-dev/api.log, <root>/.lt-dev/app.log'));
279
+ for (const note of rotationNotes)
280
+ info(colors.dim(note));
214
281
  info(colors.dim('Stop with: lt dev down'));
215
282
  // Best-effort: kill orphaned children if neither spawned (unlikely, but tidy).
216
283
  if (Object.keys(pids).length === 0) {
@@ -11,11 +11,13 @@ exports.runMigrate = runMigrate;
11
11
  * Idempotent — safe to run multiple times.
12
12
  */
13
13
  const fs_1 = require("fs");
14
+ const gluegun_1 = require("gluegun");
14
15
  const path_1 = require("path");
15
16
  const dev_identity_1 = require("./dev-identity");
16
17
  const dev_patches_1 = require("./dev-patches");
17
18
  const dev_project_1 = require("./dev-project");
18
19
  const dev_state_1 = require("./dev-state");
20
+ const package_name_1 = require("./package-name");
19
21
  /**
20
22
  * Print a `runMigrate` result via the toolbox. Shared by `lt dev init`
21
23
  * and the auto-init step of `lt dev install` so both render identically.
@@ -32,6 +34,9 @@ function printMigrateResult(toolbox, result) {
32
34
  info(` API URL: https://${result.identity.subdomains.api.hostname}`);
33
35
  info(` DB: mongodb://127.0.0.1/${result.dbName}`);
34
36
  info('');
37
+ if (result.renamedTemplatePackage) {
38
+ success(`renamed root package.json name → "${result.renamedTemplatePackage}" (was unmodified template default)`);
39
+ }
35
40
  if (result.codePatches.length > 0) {
36
41
  for (const r of result.codePatches) {
37
42
  if (r.patched)
@@ -60,6 +65,17 @@ function printMigrateResult(toolbox, result) {
60
65
  */
61
66
  function runMigrate(input) {
62
67
  const { layout } = input;
68
+ // 0. If the root package.json still carries an unmodified starter-template
69
+ // name (e.g. `lt-monorepo` from a raw `git clone`), rewrite it to the
70
+ // directory basename before deriving identity. Otherwise every cloned
71
+ // project would slug to `lt-monorepo` and collide on
72
+ // `https://lt-monorepo.localhost`. `lt fullstack init` already handles
73
+ // this — the call here is the safety net for projects that bypassed
74
+ // init (e.g. manual `git clone lenneTech/lt-monorepo my-project`).
75
+ const renamedTemplatePackage = (0, package_name_1.renameUnmodifiedTemplatePackage)({
76
+ filesystem: gluegun_1.filesystem,
77
+ projectRoot: layout.root,
78
+ });
63
79
  const identity = (0, dev_identity_1.buildIdentity)(layout.root);
64
80
  const dbName = (0, dev_project_1.deriveDbName)(layout.apiDir, identity.slug);
65
81
  // 1. Code patches (config.env.ts, nuxt.config.ts, playwright.config.ts).
@@ -106,7 +122,7 @@ function runMigrate(input) {
106
122
  const addedGitignoreEntry = (0, dev_patches_1.addToGitignore)(layout.root, '.lt-dev/');
107
123
  const codePatched = codePatches.filter((r) => r.patched).length > 0;
108
124
  const claudePatched = claudePatches.filter((r) => r.patched).length > 0;
109
- const alreadyMigrated = !codePatched && !claudePatched && !registryChanged && !addedGitignoreEntry;
125
+ const alreadyMigrated = !codePatched && !claudePatched && !registryChanged && !addedGitignoreEntry && !renamedTemplatePackage;
110
126
  return {
111
127
  addedGitignoreEntry,
112
128
  alreadyMigrated,
@@ -115,5 +131,6 @@ function runMigrate(input) {
115
131
  dbName,
116
132
  identity,
117
133
  registryUpdated: registryChanged,
134
+ renamedTemplatePackage,
118
135
  };
119
136
  }
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.checkPortInUse = checkPortInUse;
13
13
  exports.killProcessGroup = killProcessGroup;
14
14
  exports.listenSnapshot = listenSnapshot;
15
+ exports.rotateLogFile = rotateLogFile;
15
16
  exports.spawnDetached = spawnDetached;
16
17
  /**
17
18
  * Process + port helpers for `lt dev`.
@@ -114,9 +115,43 @@ function listenSnapshot(ports) {
114
115
  });
115
116
  });
116
117
  }
118
+ /**
119
+ * Rotate a log file: rename existing `<logFile>` to `<logFile>.1`, dropping
120
+ * any previous `.1`. Keeps exactly one prior generation so the most recent
121
+ * `lt dev down`-able session stays inspectable without unbounded growth.
122
+ *
123
+ * Returns `{ rotated: false }` when no prior log exists.
124
+ */
125
+ function rotateLogFile(logFile) {
126
+ let previousSize;
127
+ try {
128
+ previousSize = (0, fs_1.statSync)(logFile).size;
129
+ }
130
+ catch (_a) {
131
+ return { rotated: false };
132
+ }
133
+ const archivePath = `${logFile}.1`;
134
+ try {
135
+ (0, fs_1.unlinkSync)(archivePath);
136
+ }
137
+ catch (_b) {
138
+ /* nothing to remove */
139
+ }
140
+ try {
141
+ (0, fs_1.renameSync)(logFile, archivePath);
142
+ }
143
+ catch (_c) {
144
+ return { rotated: false };
145
+ }
146
+ return { archivePath, previousSize, rotated: true };
147
+ }
117
148
  /**
118
149
  * Spawn a detached child whose stdio is redirected to a log file.
119
150
  *
151
+ * Rotates any previous log first (one generation kept as `<logFile>.1`) so
152
+ * each session starts with a fresh file. Prevents the multi-day accumulation
153
+ * that produced ~10 GB logs under continuous `up`/`down` cycles.
154
+ *
120
155
  * The parent's copy of the log file descriptor is closed in `finally`
121
156
  * — the child has already inherited its own fd before `spawn` returns,
122
157
  * so closing prevents fd leaks and avoids racing-write artifacts on
@@ -126,6 +161,7 @@ function listenSnapshot(ports) {
126
161
  */
127
162
  function spawnDetached(cmd, args, opts) {
128
163
  (0, fs_1.mkdirSync)((0, path_1.dirname)(opts.logFile), { recursive: true });
164
+ const rotated = rotateLogFile(opts.logFile);
129
165
  const out = (0, fs_1.openSync)(opts.logFile, 'a');
130
166
  let child;
131
167
  try {
@@ -136,7 +172,9 @@ function spawnDetached(cmd, args, opts) {
136
172
  stdio: ['ignore', out, out],
137
173
  });
138
174
  child.unref();
139
- return child.pid;
175
+ if (child.pid === undefined)
176
+ return undefined;
177
+ return { pid: child.pid, rotated };
140
178
  }
141
179
  catch (_a) {
142
180
  return undefined;
@@ -1,6 +1,66 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isUnmodifiedTemplateName = isUnmodifiedTemplateName;
4
+ exports.renameUnmodifiedTemplatePackage = renameUnmodifiedTemplatePackage;
3
5
  exports.setPackageName = setPackageName;
6
+ const path_1 = require("path");
7
+ const dev_identity_1 = require("./dev-identity");
8
+ /**
9
+ * package.json `name` values that are unchanged starter-template defaults.
10
+ *
11
+ * When a user clones a template manually (`git clone lenneTech/lt-monorepo
12
+ * my-project`) instead of running `lt fullstack init`, the `name` field
13
+ * stays at the template's default. That field is what
14
+ * `dev-identity#projectSlug` reads to derive `<slug>.localhost`, so every
15
+ * cloned project would collide on `https://lt-monorepo.localhost`.
16
+ *
17
+ * `lt fullstack init` rewrites this field already (see `setPackageName`);
18
+ * the detection here is the safety net for projects that bypassed init.
19
+ */
20
+ const UNMODIFIED_TEMPLATE_NAMES = new Set(['lt-monorepo']);
21
+ /**
22
+ * True when `name` matches a known unmodified starter template default.
23
+ */
24
+ function isUnmodifiedTemplateName(name) {
25
+ return typeof name === 'string' && UNMODIFIED_TEMPLATE_NAMES.has(name);
26
+ }
27
+ /**
28
+ * If the package.json at `<projectRoot>/package.json` still carries an
29
+ * unmodified starter-template `name` (e.g. `lt-monorepo` from a raw
30
+ * `git clone`), rewrite it to the directory basename — which is what the
31
+ * user actually called their project when they cloned the folder.
32
+ *
33
+ * Returns the new name if a rewrite happened, `null` otherwise. Reasons
34
+ * for `null`: missing/unreadable package.json, name already custom, or the
35
+ * directory basename itself is in the deny list (pathological case of a
36
+ * fresh clone into a literal `lt-monorepo` folder — leaving the file
37
+ * untouched is correct behaviour there).
38
+ *
39
+ * Idempotent — safe to call from every `lt dev init` invocation.
40
+ */
41
+ function renameUnmodifiedTemplatePackage(options) {
42
+ const { filesystem, projectRoot } = options;
43
+ const packageJsonPath = filesystem.path(projectRoot, 'package.json');
44
+ if (!filesystem.exists(packageJsonPath))
45
+ return null;
46
+ const pkg = filesystem.read(packageJsonPath, 'json');
47
+ if (!pkg || typeof pkg !== 'object' || Array.isArray(pkg))
48
+ return null;
49
+ const currentName = typeof pkg.name === 'string' ? pkg.name : null;
50
+ if (!isUnmodifiedTemplateName(currentName))
51
+ return null;
52
+ // Slugify the directory basename: npm names must be lowercase and
53
+ // URL-safe, and this keeps the rewritten value consistent with what
54
+ // `lt fullstack init` writes (which kebab-cases its --name arg) and
55
+ // with `dev-identity#projectSlug` (which slugifies whatever it reads
56
+ // back). Anything else would produce a slug mismatch between
57
+ // package.json and `<slug>.localhost`.
58
+ const derived = (0, dev_identity_1.slugify)((0, path_1.basename)(projectRoot));
59
+ if (!derived || isUnmodifiedTemplateName(derived))
60
+ return null;
61
+ const written = setPackageName({ filesystem, name: derived, packageJsonPath });
62
+ return written ? derived : null;
63
+ }
4
64
  /**
5
65
  * Set the `name` field of a package.json on disk.
6
66
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lenne.tech/cli",
3
- "version": "1.25.0",
3
+ "version": "1.26.0",
4
4
  "description": "lenne.Tech CLI: lt",
5
5
  "keywords": [
6
6
  "lenne.Tech",