@lenne.tech/cli 1.36.0 → 1.37.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.
- package/build/commands/deployment/create.js +5 -3
- package/build/commands/dev/doctor.js +55 -0
- package/build/commands/dev/prune.js +152 -0
- package/build/commands/dev/test.js +3 -0
- package/build/commands/dev/up.js +63 -0
- package/build/commands/fullstack/update.js +10 -0
- package/build/commands/ticket/start.js +11 -0
- package/build/commands/ticket/stop.js +177 -39
- package/build/lib/dev-prune.js +211 -0
- package/build/lib/dev-state.js +10 -9
- package/build/lib/dev-test-session.js +24 -0
- package/build/lib/dev-ticket.js +325 -16
- package/build/lib/heal-check-wrapper.js +33 -3
- package/build/lib/hoist-workspace-pnpm-config.js +110 -2
- package/build/lib/workspace-integration.js +15 -5
- package/build/templates/check/check.mjs +208 -23
- package/docs/commands.md +130 -0
- package/docs/lt-dev-ticket-workflow.html +5 -5
- package/docs/lt-dev-ticket-workflow.pdf +0 -0
- package/package.json +1 -1
|
@@ -260,10 +260,12 @@ const NewCommand = {
|
|
|
260
260
|
info(` - dev domains: dev.${domain} (app) + api.dev.${domain} (api)`);
|
|
261
261
|
info(` 2. CI/CD variables: TURBOOPS_PROJECT=${project} and TURBOOPS_TOKEN=<token> (masked).`);
|
|
262
262
|
info(' GitLab: Settings > CI/CD > Variables. GitHub: a repo variable + a repo secret.');
|
|
263
|
-
info(' 3. DNS A/AAAA records -> your server IP
|
|
263
|
+
info(' 3. DNS A/AAAA records -> your server IP:');
|
|
264
264
|
info(` - ${domain} (apex — NOT covered by a wildcard)`);
|
|
265
|
-
info(` - *.${domain} (covers api.${domain}
|
|
266
|
-
info(
|
|
265
|
+
info(` - *.${domain} (covers api.${domain}, dev.${domain} AND api.dev.${domain}:`);
|
|
266
|
+
info(' a DNS wildcard matches one or MORE labels, RFC 4592)');
|
|
267
|
+
info(` - *.dev.${domain} only needed if dev.${domain} exists as an EXPLICIT record`);
|
|
268
|
+
info(' (an explicit node blocks the parent wildcard for names below it)');
|
|
267
269
|
info(' 4. Set the stage env vars in TurboOps (per stage), e.g. for production:');
|
|
268
270
|
info(` NODE_ENV=production, NSC__BASE_URL=https://api.${domain},`);
|
|
269
271
|
info(' NSC__MONGOOSE__URI, NSC__BETTER_AUTH__SECRET, NSC__AI__ENCRYPTION_SECRET,');
|
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
36
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
37
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -121,6 +154,28 @@ const DoctorCommand = {
|
|
|
121
154
|
else if (gs.file && gs.hasDbReset) {
|
|
122
155
|
line('OK', colors.green, 'global-setup allow-list is ticket + shard safe');
|
|
123
156
|
}
|
|
157
|
+
// 7.5 check.mjs drift: the root wrapper is canonical (bundled with the
|
|
158
|
+
// CLI, synced by `lt fullstack update`). A diverged copy silently
|
|
159
|
+
// misses fixes (idle-watchdog, install hoisting, summed test
|
|
160
|
+
// metrics) — surface it instead of letting copies drift apart.
|
|
161
|
+
try {
|
|
162
|
+
const { readFileSync: read } = yield Promise.resolve().then(() => __importStar(require('fs')));
|
|
163
|
+
const { join: j } = yield Promise.resolve().then(() => __importStar(require('path')));
|
|
164
|
+
const projectCheck = j(layout.root, 'scripts', 'check.mjs');
|
|
165
|
+
const bundledCheck = j(__dirname, '..', '..', 'templates', 'check', 'check.mjs');
|
|
166
|
+
if (filesystem.exists(projectCheck) && filesystem.exists(bundledCheck)) {
|
|
167
|
+
if (read(projectCheck, 'utf8') === read(bundledCheck, 'utf8')) {
|
|
168
|
+
line('OK', colors.green, 'scripts/check.mjs matches the canonical CLI version');
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
line('WARN', colors.yellow, 'scripts/check.mjs differs from the canonical CLI version');
|
|
172
|
+
line('WARN', colors.yellow, ' run `lt fullstack update` to sync it (skips uncommitted local edits)');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
catch (_a) {
|
|
177
|
+
/* best-effort diagnostics */
|
|
178
|
+
}
|
|
124
179
|
// 8. Slug ↔ path: is this project's slug registered to a DIFFERENT checkout?
|
|
125
180
|
// Two clones of the same project (same package.json "name") share the
|
|
126
181
|
// slug → Caddy block / ports / DB and collide. Surface it proactively.
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
const dev_identity_1 = require("../../lib/dev-identity");
|
|
13
|
+
const dev_project_1 = require("../../lib/dev-project");
|
|
14
|
+
const dev_prune_1 = require("../../lib/dev-prune");
|
|
15
|
+
const dev_state_1 = require("../../lib/dev-state");
|
|
16
|
+
const dev_ticket_1 = require("../../lib/dev-ticket");
|
|
17
|
+
const workspace_integration_1 = require("../../lib/workspace-integration");
|
|
18
|
+
/**
|
|
19
|
+
* `lt dev prune [--dry-run] [--noConfirm]` — collect the leftovers that accumulate
|
|
20
|
+
* around parallel ticket work:
|
|
21
|
+
*
|
|
22
|
+
* 1. ORPHANED TICKET DATABASES of this project: `<base>-<id>`(+`-test`/`-test-<n>`)
|
|
23
|
+
* whose ticket has neither a live worktree nor a live registry entry. The id
|
|
24
|
+
* set comes from this repo's own `feat/*` branches — never from name shapes,
|
|
25
|
+
* so sibling projects sharing a name prefix are safe. DBs recorded via
|
|
26
|
+
* `lt ticket stop --keep-db` are never touched.
|
|
27
|
+
* 2. STALE SHARD TEST DATABASES (`<base>-test-<n>` from `lt dev test --shard`)
|
|
28
|
+
* when no test session is running — they are reset on every run anyway.
|
|
29
|
+
* 3. DEAD REGISTRY ENTRIES (all projects): path no longer exists → the entry and
|
|
30
|
+
* its reserved ports are reclaimed. Databases of dead MAIN projects are NEVER
|
|
31
|
+
* dropped (a deleted folder is not consent to destroy data).
|
|
32
|
+
*
|
|
33
|
+
* `lt dev up` runs the same collection automatically (opt out with --no-prune), so
|
|
34
|
+
* restarting an environment always cleans up after its predecessors.
|
|
35
|
+
*/
|
|
36
|
+
const PruneCommand = {
|
|
37
|
+
description: 'Remove orphaned ticket databases, stale shard test DBs and dead registry entries',
|
|
38
|
+
hidden: false,
|
|
39
|
+
name: 'prune',
|
|
40
|
+
run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
|
|
41
|
+
const { filesystem, parameters, print: { colors, error, info, success, warning }, prompt: { confirm }, } = toolbox;
|
|
42
|
+
const dryRun = parameters.options.dryRun === true || parameters.options['dry-run'] === true;
|
|
43
|
+
const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
|
|
44
|
+
let mainRepoRoot = layout.root;
|
|
45
|
+
try {
|
|
46
|
+
mainRepoRoot = (0, dev_ticket_1.gitMainRepoRoot)(layout.root);
|
|
47
|
+
}
|
|
48
|
+
catch (_a) {
|
|
49
|
+
/* not a git repo — registry prune still works, ticket sweep will be empty */
|
|
50
|
+
}
|
|
51
|
+
const mainLayout = (0, dev_project_1.resolveLayout)(mainRepoRoot, filesystem);
|
|
52
|
+
const slug = (0, dev_identity_1.buildIdentity)(mainRepoRoot).slug;
|
|
53
|
+
const projectDevDb = (0, dev_project_1.deriveDbName)(mainLayout.apiDir, slug);
|
|
54
|
+
const observed = mainLayout.apiDir ? (0, dev_ticket_1.listDatabaseNames)(undefined, [mainLayout.apiDir, mainRepoRoot]) : null;
|
|
55
|
+
if (mainLayout.apiDir && observed === null) {
|
|
56
|
+
warning('Could not list databases (mongosh missing or MongoDB unreachable) — only the registry is pruned.');
|
|
57
|
+
}
|
|
58
|
+
const plan = (0, dev_prune_1.collectDevPrunePlan)({
|
|
59
|
+
loadRegistry: dev_state_1.loadRegistry,
|
|
60
|
+
mainRepoRoot,
|
|
61
|
+
observedDbNames: observed,
|
|
62
|
+
projectDevDb,
|
|
63
|
+
slug,
|
|
64
|
+
});
|
|
65
|
+
const dbTargets = [...new Set([...plan.orphan.targets, ...plan.shardTargets])];
|
|
66
|
+
if (dbTargets.length === 0 && plan.registryPrune.length === 0) {
|
|
67
|
+
success('Nothing to prune — no orphaned ticket DBs, no stale shard DBs, no dead registry entries.');
|
|
68
|
+
if (plan.orphan.protected.length > 0) {
|
|
69
|
+
info(colors.dim(` kept (registry keptDbs / referenced): ${plan.orphan.protected.join(', ')}`));
|
|
70
|
+
}
|
|
71
|
+
if (!parameters.options.fromGluegunMenu)
|
|
72
|
+
process.exit();
|
|
73
|
+
return 'dev prune: nothing to do';
|
|
74
|
+
}
|
|
75
|
+
if (plan.orphan.targets.length > 0) {
|
|
76
|
+
info(colors.bold(`Orphaned ticket databases (tickets: ${plan.orphan.orphanIds.join(', ')}):`));
|
|
77
|
+
plan.orphan.targets.forEach((db) => info(colors.dim(` • ${db}`)));
|
|
78
|
+
}
|
|
79
|
+
if (plan.shardTargets.length > 0) {
|
|
80
|
+
info(colors.bold('Stale shard test databases (no test session running):'));
|
|
81
|
+
plan.shardTargets.forEach((db) => info(colors.dim(` • ${db}`)));
|
|
82
|
+
}
|
|
83
|
+
if (plan.orphan.protected.length > 0) {
|
|
84
|
+
info(colors.dim(`Kept (recorded via --keep-db or still referenced): ${plan.orphan.protected.join(', ')}`));
|
|
85
|
+
}
|
|
86
|
+
if (plan.registryPrune.length > 0) {
|
|
87
|
+
info(colors.bold('Dead registry entries (path gone — entry + ports reclaimed, databases untouched):'));
|
|
88
|
+
plan.registryPrune.forEach((key) => info(colors.dim(` • ${key}`)));
|
|
89
|
+
}
|
|
90
|
+
if (dryRun) {
|
|
91
|
+
info('');
|
|
92
|
+
info(colors.dim('--dry-run → nothing was changed.'));
|
|
93
|
+
if (!parameters.options.fromGluegunMenu)
|
|
94
|
+
process.exit();
|
|
95
|
+
return 'dev prune: dry run';
|
|
96
|
+
}
|
|
97
|
+
// Dropping databases is irreversible — confirm interactively (documented default
|
|
98
|
+
// for non-interactive callers, mirroring `lt ticket stop`).
|
|
99
|
+
let confirmedDbTargets = dbTargets;
|
|
100
|
+
if (dbTargets.length > 0 && !(0, workspace_integration_1.isNonInteractive)(parameters.options.noConfirm === true)) {
|
|
101
|
+
info('');
|
|
102
|
+
warning(' These databases will be DROPPED (irreversible):');
|
|
103
|
+
dbTargets.forEach((db) => info(colors.dim(` • ${db}`)));
|
|
104
|
+
if (!(yield confirm('Drop them?', true))) {
|
|
105
|
+
info(colors.dim(' keeping the databases.'));
|
|
106
|
+
confirmedDbTargets = [];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (confirmedDbTargets.length > 0) {
|
|
110
|
+
const { dropped, reason } = (0, dev_ticket_1.dropDatabases)(confirmedDbTargets, undefined, [mainLayout.apiDir, mainRepoRoot]);
|
|
111
|
+
dropped.forEach((db) => info(colors.dim(` dropped db ${db}`)));
|
|
112
|
+
if (reason === 'no-mongosh') {
|
|
113
|
+
warning(' mongosh is not installed — the databases were NOT dropped.');
|
|
114
|
+
}
|
|
115
|
+
else if (reason === 'unreachable') {
|
|
116
|
+
warning(' MongoDB is not reachable — the databases were NOT dropped.');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (plan.registryPrune.length > 0) {
|
|
120
|
+
try {
|
|
121
|
+
// Locked read-modify-write: a parallel `lt dev up` may be registering a
|
|
122
|
+
// project right now — pruning without the lock clobbers its fresh entry.
|
|
123
|
+
const removed = yield (0, dev_state_1.withRegistryLock)(() => {
|
|
124
|
+
const reg = (0, dev_state_1.loadRegistry)();
|
|
125
|
+
const gone = [];
|
|
126
|
+
for (const key of plan.registryPrune) {
|
|
127
|
+
if (reg.projects[key] && !filesystem.exists(reg.projects[key].path)) {
|
|
128
|
+
delete reg.projects[key];
|
|
129
|
+
gone.push(key);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (gone.length > 0) {
|
|
133
|
+
(0, dev_state_1.saveRegistry)(reg);
|
|
134
|
+
}
|
|
135
|
+
return gone;
|
|
136
|
+
});
|
|
137
|
+
if (removed.length > 0) {
|
|
138
|
+
info(colors.dim(` removed ${removed.length} dead registry entr${removed.length === 1 ? 'y' : 'ies'}: ${removed.join(', ')}`));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch (e) {
|
|
142
|
+
error(` registry update failed: ${e.message}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
info('');
|
|
146
|
+
success('Prune complete.');
|
|
147
|
+
if (!parameters.options.fromGluegunMenu)
|
|
148
|
+
process.exit();
|
|
149
|
+
return 'dev prune: done';
|
|
150
|
+
}),
|
|
151
|
+
};
|
|
152
|
+
module.exports = PruneCommand;
|
|
@@ -236,6 +236,9 @@ const TestCommand = {
|
|
|
236
236
|
info(colors.dim(` app: ${ctx.appUrl} db: ${ctx.dbName}`));
|
|
237
237
|
info('');
|
|
238
238
|
const appPm = (0, dev_package_manager_1.pickPackageManager)(layout.appDir);
|
|
239
|
+
// Self-heal: install the app's Playwright browser build if missing —
|
|
240
|
+
// idempotent (~1s when cached); a fresh project otherwise fails every spec.
|
|
241
|
+
(0, dev_test_session_1.ensurePlaywrightBrowsers)(layout.appDir, appPm, (m) => info(colors.dim(m)));
|
|
239
242
|
exitCode = yield (0, dev_process_1.runChildInherit)(appPm.bin, appPm.runScript('test:e2e', forwarded), {
|
|
240
243
|
cwd: layout.appDir,
|
|
241
244
|
env,
|
package/build/commands/dev/up.js
CHANGED
|
@@ -14,10 +14,12 @@ const path_1 = require("path");
|
|
|
14
14
|
const caddy_1 = require("../../lib/caddy");
|
|
15
15
|
const dev_env_1 = require("../../lib/dev-env");
|
|
16
16
|
const dev_env_bridge_1 = require("../../lib/dev-env-bridge");
|
|
17
|
+
const dev_identity_1 = require("../../lib/dev-identity");
|
|
17
18
|
const dev_package_manager_1 = require("../../lib/dev-package-manager");
|
|
18
19
|
const dev_patches_1 = require("../../lib/dev-patches");
|
|
19
20
|
const dev_process_1 = require("../../lib/dev-process");
|
|
20
21
|
const dev_project_1 = require("../../lib/dev-project");
|
|
22
|
+
const dev_prune_1 = require("../../lib/dev-prune");
|
|
21
23
|
const dev_state_1 = require("../../lib/dev-state");
|
|
22
24
|
const dev_ticket_1 = require("../../lib/dev-ticket");
|
|
23
25
|
/**
|
|
@@ -432,6 +434,67 @@ const UpCommand = {
|
|
|
432
434
|
if (pids.app)
|
|
433
435
|
(0, dev_process_1.killProcessGroup)(pids.app);
|
|
434
436
|
}
|
|
437
|
+
// AUTO-PRUNE — restarting an environment is the moment to clean up after its
|
|
438
|
+
// predecessors: orphaned ticket DBs (ids from this repo's own feat/* branches,
|
|
439
|
+
// no live worktree/registry entry, `--keep-db` records excluded), stale shard
|
|
440
|
+
// test DBs, and dead-path registry entries. Same guards as `lt dev prune`;
|
|
441
|
+
// best-effort and non-blocking — a failure here must never affect `up` itself.
|
|
442
|
+
// Opt out with `--no-prune`.
|
|
443
|
+
if (parameters.options.prune !== false) {
|
|
444
|
+
try {
|
|
445
|
+
let mainRepoRoot = layout.root;
|
|
446
|
+
try {
|
|
447
|
+
mainRepoRoot = (0, dev_ticket_1.gitMainRepoRoot)(layout.root);
|
|
448
|
+
}
|
|
449
|
+
catch (_m) {
|
|
450
|
+
/* not a git repo — registry prune still applies */
|
|
451
|
+
}
|
|
452
|
+
const mainLayout = (0, dev_project_1.resolveLayout)(mainRepoRoot, filesystem);
|
|
453
|
+
const baseSlug = (0, dev_identity_1.buildIdentity)(mainRepoRoot).slug;
|
|
454
|
+
const plan = (0, dev_prune_1.collectDevPrunePlan)({
|
|
455
|
+
loadRegistry: dev_state_1.loadRegistry,
|
|
456
|
+
mainRepoRoot,
|
|
457
|
+
observedDbNames: mainLayout.apiDir ? (0, dev_ticket_1.listDatabaseNames)(undefined, [mainLayout.apiDir, mainRepoRoot]) : null,
|
|
458
|
+
projectDevDb: (0, dev_project_1.deriveDbName)(mainLayout.apiDir, baseSlug),
|
|
459
|
+
slug: baseSlug,
|
|
460
|
+
});
|
|
461
|
+
const dbTargets = [...new Set([...plan.orphan.targets, ...plan.shardTargets])];
|
|
462
|
+
if (dbTargets.length > 0) {
|
|
463
|
+
const { dropped, reason } = (0, dev_ticket_1.dropDatabases)(dbTargets, undefined, [mainLayout.apiDir, mainRepoRoot]);
|
|
464
|
+
if (dropped.length > 0) {
|
|
465
|
+
info(colors.dim(`Pruned ${dropped.length} orphaned test/ticket database(s): ${dropped.join(', ')}`));
|
|
466
|
+
}
|
|
467
|
+
if (reason) {
|
|
468
|
+
info(colors.dim(`(prune incomplete — ${reason === 'no-mongosh' ? 'mongosh missing' : 'MongoDB unreachable'}; run \`lt dev prune\` later)`));
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (plan.registryPrune.length > 0) {
|
|
472
|
+
// Under the registry lock: a parallel `lt dev up` (another project) may be
|
|
473
|
+
// REGISTERING right now — an unlocked read-modify-write here would clobber
|
|
474
|
+
// its fresh entry (classic lost update; observed with two simultaneous ups).
|
|
475
|
+
const removed = yield (0, dev_state_1.withRegistryLock)(() => {
|
|
476
|
+
const reg = (0, dev_state_1.loadRegistry)();
|
|
477
|
+
const gone = [];
|
|
478
|
+
for (const key of plan.registryPrune) {
|
|
479
|
+
if (reg.projects[key] && !(0, fs_1.existsSync)(reg.projects[key].path)) {
|
|
480
|
+
delete reg.projects[key];
|
|
481
|
+
gone.push(key);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (gone.length > 0) {
|
|
485
|
+
(0, dev_state_1.saveRegistry)(reg);
|
|
486
|
+
}
|
|
487
|
+
return gone;
|
|
488
|
+
});
|
|
489
|
+
if (removed.length > 0) {
|
|
490
|
+
info(colors.dim(`Pruned ${removed.length} dead registry entr${removed.length === 1 ? 'y' : 'ies'}: ${removed.join(', ')} (ports reclaimed)`));
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
catch (_o) {
|
|
495
|
+
/* never block `up` on cleanup */
|
|
496
|
+
}
|
|
497
|
+
}
|
|
435
498
|
if (!parameters.options.fromGluegunMenu)
|
|
436
499
|
process.exit();
|
|
437
500
|
return `dev up: api=${pids.api}, app=${pids.app}`;
|
|
@@ -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
|
const path_1 = require("path");
|
|
13
|
+
const dev_patches_1 = require("../../lib/dev-patches");
|
|
13
14
|
const framework_detection_1 = require("../../lib/framework-detection");
|
|
14
15
|
const frontend_framework_detection_1 = require("../../lib/frontend-framework-detection");
|
|
15
16
|
const heal_check_wrapper_1 = require("../../lib/heal-check-wrapper");
|
|
@@ -198,6 +199,15 @@ const NewCommand = {
|
|
|
198
199
|
info('');
|
|
199
200
|
success(` Installed/updated the check wrapper: ${changedCheck.join(', ')}`);
|
|
200
201
|
}
|
|
202
|
+
// ── Self-heal: keep `.lt-dev/` out of git ──────────────────────────────
|
|
203
|
+
//
|
|
204
|
+
// Newer templates ship the entry, but pre-existing projects predate it —
|
|
205
|
+
// `lt dev up` then dirties `.gitignore` in every ticket worktree, which
|
|
206
|
+
// used to block every `lt ticket stop` at its safety gate. Idempotent.
|
|
207
|
+
if ((0, dev_patches_1.addToGitignore)(cwd, '.lt-dev/')) {
|
|
208
|
+
info('');
|
|
209
|
+
success(' Added `.lt-dev/` to .gitignore');
|
|
210
|
+
}
|
|
201
211
|
info('');
|
|
202
212
|
info(colors.bold('For a comprehensive update of everything, use:'));
|
|
203
213
|
info('');
|
|
@@ -65,6 +65,17 @@ const StartCommand = {
|
|
|
65
65
|
}
|
|
66
66
|
const base = (0, dev_identity_1.buildIdentity)(mainRepoRoot);
|
|
67
67
|
const id = (0, dev_ticket_1.deriveTicketId)(name, parameters.options.as != null ? String(parameters.options.as) : undefined);
|
|
68
|
+
// `local`/`dev`/`test`/… would derive a ticket DB that collides with the PROJECT's own
|
|
69
|
+
// dev or test database (both derivations strip a trailing `-(local|dev)` before adding
|
|
70
|
+
// their suffix, so the name round-trips). `lt ticket stop` would then be aimed at the
|
|
71
|
+
// developer's own data. Refuse the id instead of creating that trap.
|
|
72
|
+
if ((0, dev_ticket_1.isReservedTicketId)(id)) {
|
|
73
|
+
error(`Ticket id "${id}" is reserved — its database would collide with the project's own.`);
|
|
74
|
+
info(colors.dim(` Pick a different name, or map it to a distinct id: lt ticket start ${name} --as ${id}-fix`));
|
|
75
|
+
if (!parameters.options.fromGluegunMenu)
|
|
76
|
+
process.exit(1);
|
|
77
|
+
return 'ticket start: reserved id';
|
|
78
|
+
}
|
|
68
79
|
const branch = typeof parameters.options.branch === 'string' ? parameters.options.branch : (0, dev_ticket_1.defaultTicketBranch)(name);
|
|
69
80
|
const ticketIdentity = (0, dev_identity_1.buildTicketIdentity)(base, id);
|
|
70
81
|
const dbName = (0, dev_project_1.deriveTicketDbName)((0, dev_project_1.deriveDbName)(layout.apiDir, base.slug), id);
|