@lenne.tech/cli 1.25.0 → 1.26.1
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.
|
@@ -19,20 +19,22 @@ const shell_config_1 = require("../../lib/shell-config");
|
|
|
19
19
|
function installPlugin(plugin, cli, toolbox) {
|
|
20
20
|
return __awaiter(this, void 0, void 0, function* () {
|
|
21
21
|
const { print: { error, info, spin }, } = toolbox;
|
|
22
|
-
// Step 1: Install or update plugin
|
|
22
|
+
// Step 1: Install or update plugin.
|
|
23
|
+
//
|
|
24
|
+
// `claude plugin install` is a no-op for an already-installed plugin: it reports
|
|
25
|
+
// "already installed" and leaves the active version pinned to whatever was installed
|
|
26
|
+
// before. To actually pull a newer release from the (freshly updated) marketplace
|
|
27
|
+
// cache, an existing install must be bumped with `claude plugin update`.
|
|
23
28
|
const fullPluginName = `${plugin.pluginName}@${plugin.marketplaceName}`;
|
|
24
29
|
const pluginSpinner = spin(`Installing/updating ${plugin.pluginName}`);
|
|
25
|
-
|
|
30
|
+
let installResult = (0, claude_cli_1.runClaudeCommand)(cli, `plugin install ${fullPluginName}`);
|
|
26
31
|
let pluginAction = 'installed';
|
|
27
|
-
if (installResult.output.includes('already
|
|
28
|
-
|
|
32
|
+
if (installResult.output.includes('already installed')) {
|
|
33
|
+
// Plugin was already present — follow up with an update to reach the latest version.
|
|
34
|
+
installResult = (0, claude_cli_1.runClaudeCommand)(cli, `plugin update ${fullPluginName}`);
|
|
35
|
+
pluginAction = installResult.output.includes('already') ? 'up to date' : 'updated';
|
|
29
36
|
}
|
|
30
|
-
|
|
31
|
-
pluginAction = 'updated';
|
|
32
|
-
}
|
|
33
|
-
if (installResult.success ||
|
|
34
|
-
installResult.output.includes('already') ||
|
|
35
|
-
installResult.output.includes('up to date')) {
|
|
37
|
+
if (installResult.success || installResult.output.includes('already')) {
|
|
36
38
|
pluginSpinner.succeed(`${plugin.pluginName} ${pluginAction}`);
|
|
37
39
|
}
|
|
38
40
|
else {
|
|
@@ -42,6 +44,7 @@ function installPlugin(plugin, cli, toolbox) {
|
|
|
42
44
|
info('Manual installation:');
|
|
43
45
|
info(` /plugin marketplace add ${plugin.marketplaceRepo}`);
|
|
44
46
|
info(` /plugin install ${fullPluginName}`);
|
|
47
|
+
info(` /plugin update ${fullPluginName}`);
|
|
45
48
|
return { action: 'failed', contents: plugin_utils_1.EMPTY_PLUGIN_CONTENTS, postInstall: null, success: false };
|
|
46
49
|
}
|
|
47
50
|
// Step 2: Read plugin contents
|
package/build/commands/dev/up.js
CHANGED
|
@@ -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 = (
|
|
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 = (
|
|
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
|
|
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 (
|
|
184
|
-
pids.api =
|
|
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
|
|
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 (
|
|
193
|
-
pids.app =
|
|
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=${(
|
|
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
|
}
|
package/build/lib/dev-process.js
CHANGED
|
@@ -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
|
-
|
|
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
|
*
|