@lenne.tech/cli 1.24.0 → 1.25.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,15 +10,25 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  const caddy_1 = require("../../lib/caddy");
13
+ const dev_bootstrap_1 = require("../../lib/dev-bootstrap");
14
+ const dev_install_helper_1 = require("../../lib/dev-install-helper");
13
15
  const dev_migrate_helper_1 = require("../../lib/dev-migrate-helper");
14
16
  const dev_project_1 = require("../../lib/dev-project");
17
+ const dev_service_1 = require("../../lib/dev-service");
15
18
  /**
16
- * Register an existing project with `lt dev` and apply idempotent
17
- * env-aware patches.
19
+ * Initialize an existing project for `lt dev` and apply idempotent
20
+ * env-aware patches. (Formerly `lt dev migrate`; `migrate` stays as an
21
+ * alias for backwards compatibility.)
18
22
  *
19
23
  * Idempotent — re-running with no changes is a no-op. Safe to invoke
20
24
  * automatically after `lt fullstack init` or by developers manually.
21
25
  *
26
+ * Auto-chaining: if the machine has not been prepared yet (`lt dev
27
+ * install` never ran), it runs install FIRST, then initializes the
28
+ * project. The chain is one hop deep and cannot recurse, because this
29
+ * command calls the `runInstall` *helper* — never the install command
30
+ * (see `dev-bootstrap.ts`). Pass `--skip-install` to opt out.
31
+ *
22
32
  * Steps (delegated to `lib/dev-migrate-helper.ts#runMigrate`):
23
33
  * 1. Resolve workspace layout (api/app dirs, root)
24
34
  * 2. Build identity (slug + subdomains)
@@ -27,14 +37,12 @@ const dev_project_1 = require("../../lib/dev-project");
27
37
  * 4. Inject CLAUDE.md URL block (root + each subproject)
28
38
  * 5. Persist project to ~/.lenneTech/projects.json
29
39
  * 6. Add `.lt-dev/` to .gitignore
30
- *
31
- * Closes with a hint to run `lt dev install` if Caddy is missing.
32
40
  */
33
- const MigrateCommand = {
34
- alias: ['m'],
35
- description: 'Migrate an existing project to lt dev (idempotent)',
41
+ const InitCommand = {
42
+ alias: ['migrate', 'm'],
43
+ description: 'Init project for lt dev (idempotent)',
36
44
  hidden: false,
37
- name: 'migrate',
45
+ name: 'init',
38
46
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
39
47
  const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
40
48
  const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
@@ -42,55 +50,35 @@ const MigrateCommand = {
42
50
  error('No API (src/config.env.ts) or App (nuxt.config.ts) project detected at this path.');
43
51
  if (!parameters.options.fromGluegunMenu)
44
52
  process.exit(1);
45
- return 'dev migrate: not a project';
46
- }
47
- const result = (0, dev_migrate_helper_1.runMigrate)({ layout });
48
- info('');
49
- info(colors.bold(`Migrating "${result.identity.slug}"`));
50
- info(colors.dim('─'.repeat(60)));
51
- if (result.identity.subdomains.app)
52
- info(` App URL: https://${result.identity.subdomains.app.hostname}`);
53
- if (result.identity.subdomains.api)
54
- info(` API URL: https://${result.identity.subdomains.api.hostname}`);
55
- info(` DB: mongodb://127.0.0.1/${result.dbName}`);
56
- info('');
57
- // Code patches
58
- if (result.codePatches.length > 0) {
59
- for (const r of result.codePatches) {
60
- if (r.patched)
61
- success(`patched ${r.replacements}× in ${r.file}`);
62
- else
63
- info(colors.dim(`already patched: ${r.file}`));
64
- }
65
- }
66
- else {
67
- info(colors.dim(' patches: not needed (already env-aware)'));
53
+ return 'dev init: not a project';
68
54
  }
69
- // CLAUDE.md
70
- result.claudePatches.filter((r) => r.patched).forEach((r) => success(`updated CLAUDE.md URL block: ${r.file}`));
71
- // Registry
72
- if (result.registryUpdated) {
73
- success(`registered in ${process.env.LT_DEV_REGISTRY_PATH || '~/.lenneTech/projects.json'}`);
74
- }
75
- // .gitignore
76
- if (result.addedGitignoreEntry)
77
- success('added `.lt-dev/` to .gitignore');
78
- if (result.alreadyMigrated) {
79
- info(colors.dim(' Project was already fully migrated — nothing changed.'));
55
+ // Auto-chain: prepare the machine first if `lt dev install` never ran.
56
+ // `--skip-install` opts out. Calling the runInstall HELPER (not the
57
+ // install command) makes infinite recursion structurally impossible.
58
+ const runInstallFirst = (0, dev_bootstrap_1.shouldRunInstallBeforeInit)({
59
+ machinePrepared: (0, dev_bootstrap_1.isMachinePrepared)(),
60
+ platformSupported: (0, dev_service_1.platformSupported)() !== 'unsupported',
61
+ skipInstall: parameters.options.skipInstall === true,
62
+ });
63
+ if (runInstallFirst) {
64
+ info(colors.dim('Machine not prepared for lt dev yet — running `lt dev install` first ...'));
65
+ yield (0, dev_install_helper_1.runInstall)(toolbox, { auto: true });
80
66
  }
67
+ const result = (0, dev_migrate_helper_1.runMigrate)({ layout });
68
+ (0, dev_migrate_helper_1.printMigrateResult)(toolbox, result);
81
69
  info('');
82
- success('Migration complete.');
83
- // Hint: Caddy installed?
70
+ success('Project initialized for lt dev.');
71
+ // Closing hint based on Caddy availability.
84
72
  const caddyOk = yield (0, caddy_1.caddyAvailable)();
85
73
  if (!caddyOk) {
86
- warning('Caddy is not installed yet. Run `lt dev install` first, then `lt dev up`.');
74
+ warning('Caddy is not installed yet. Run `lt dev install` (installs caddy first), then `lt dev up`.');
87
75
  }
88
76
  else {
89
77
  info('Start the project with `lt dev up`.');
90
78
  }
91
79
  if (!parameters.options.fromGluegunMenu)
92
80
  process.exit();
93
- return `dev migrate: ${result.identity.slug}`;
81
+ return `dev init: ${result.identity.slug}`;
94
82
  }),
95
83
  };
96
- module.exports = MigrateCommand;
84
+ module.exports = InitCommand;
@@ -9,29 +9,24 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- const caddy_1 = require("../../lib/caddy");
13
- const dev_service_1 = require("../../lib/dev-service");
12
+ const dev_bootstrap_1 = require("../../lib/dev-bootstrap");
13
+ const dev_install_helper_1 = require("../../lib/dev-install-helper");
14
+ const dev_migrate_helper_1 = require("../../lib/dev-migrate-helper");
15
+ const dev_project_1 = require("../../lib/dev-project");
14
16
  /**
15
17
  * One-time per-machine setup for `lt dev`.
16
18
  *
17
19
  * Owns the full Caddy lifecycle through a dedicated LaunchAgent
18
- * (macOS) / systemd-user unit (Linux). This intentionally bypasses
19
- * `brew services caddy`, whose plist hardcodes
20
- * `--config /opt/homebrew/etc/Caddyfile` and would crash-loop against
21
- * our `~/.lenneTech/Caddyfile` location which is the bug that
22
- * blocked the first real install.
20
+ * (macOS) / systemd-user unit (Linux), bypassing `brew services caddy`
21
+ * (whose plist hardcodes a different Caddyfile path and crash-loops).
22
+ * The actual steps live in `lib/dev-install-helper.ts#runInstall` so
23
+ * `lt dev init` can reuse them without a cross-command call.
23
24
  *
24
- * Steps (each idempotent safe to re-run):
25
- * 1. Verify `caddy` is on PATH (suggest install instructions otherwise)
26
- * 2. Ensure `~/.lenneTech/Caddyfile` exists (stub written if missing)
27
- * 3. Detect conflicting `brew services caddy` and tell the user to
28
- * stop it it would fight us for ports 80/443
29
- * 4. Install + bootstrap our LaunchAgent/systemd unit
30
- * 5. Wait for the Caddy admin endpoint to respond
31
- * 6. Validate the Caddyfile
32
- * 7. Surface the `sudo -E HOME="$HOME" caddy trust` instruction for
33
- * installing the local CA (HOME must survive sudo so caddy can
34
- * find its CA files under the *user* profile, not /var/root)
25
+ * Auto-chaining: when run from inside an lt-dev-capable project that is
26
+ * not yet registered, it initializes that project afterwards (the same
27
+ * work `lt dev init` does). The chain is one hop deep and cannot recurse
28
+ * because it calls the `runMigrate` *helper* never the init command
29
+ * (see `dev-bootstrap.ts`). Pass `--skip-init` to opt out.
35
30
  */
36
31
  const InstallCommand = {
37
32
  alias: ['i'],
@@ -39,134 +34,44 @@ const InstallCommand = {
39
34
  hidden: false,
40
35
  name: 'install',
41
36
  run: (toolbox) => __awaiter(void 0, void 0, void 0, function* () {
42
- const { parameters, print: { colors, error, info, success, warning }, } = toolbox;
43
- info('');
44
- info(colors.bold('lt dev installone-time per-machine setup'));
45
- info(colors.dim('─'.repeat(60)));
46
- const plat = (0, dev_service_1.platformSupported)();
47
- if (plat === 'unsupported') {
48
- error(`Service management is not supported on ${process.platform}. Only macOS and Linux are covered.`);
49
- info(` Workaround: run \`${colors.cyan(`caddy run --config ${caddy_1.paths.caddyfile}`)}\` manually.`);
37
+ const { filesystem, parameters, print: { colors, info, success, warning }, } = toolbox;
38
+ const result = yield (0, dev_install_helper_1.runInstall)(toolbox);
39
+ // Fatal preconditionsnothing more to do, and an auto-init would be
40
+ // premature (the user must install caddy / use a supported OS first).
41
+ if (result.unsupported) {
50
42
  if (!parameters.options.fromGluegunMenu)
51
43
  process.exit(1);
52
44
  return 'dev install: unsupported platform';
53
45
  }
54
- let blocked = false;
55
- // 1. caddy on PATH
56
- const hasCaddy = yield (0, caddy_1.caddyAvailable)();
57
- if (hasCaddy) {
58
- success('caddy is on PATH');
59
- }
60
- else {
61
- warning('caddy is not installed.');
62
- info(` → macOS: ${colors.cyan('brew install caddy')}`);
63
- info(` → Linux: ${colors.cyan('https://caddyserver.com/docs/install')}`);
64
- info(' (Do NOT start it via `brew services` — `lt dev install` runs its own service.)');
65
- blocked = true;
66
- }
67
- // 2. Caddyfile stub
68
- (0, caddy_1.writeCaddyfile)('# lt dev — managed Caddyfile\n# Add per-project blocks via `lt dev up`.\n');
69
- success(`Caddyfile present at ${caddy_1.paths.caddyfile}`);
70
- if (!hasCaddy) {
71
- info('');
72
- error('Cannot continue setup until Caddy is installed. Re-run `lt dev install` afterwards.');
46
+ if (result.caddyMissing) {
73
47
  if (!parameters.options.fromGluegunMenu)
74
- process.exit(blocked ? 1 : 0);
48
+ process.exit(1);
75
49
  return 'dev install: caddy missing';
76
50
  }
77
- // 3. brew services conflict warning
78
- const brewConflict = yield detectBrewCaddyConflict();
79
- if (brewConflict) {
80
- warning('A `brew services caddy` instance is registered.');
81
- info(` Stop it (it crash-loops against our Caddyfile): ${colors.cyan('brew services stop caddy')}`);
82
- info(' `lt dev install` runs its own service — the brew one is no longer needed.');
83
- }
84
- // 4. Install our LaunchAgent / systemd unit
85
- const paths = (0, dev_service_1.getServicePaths)();
86
- info('');
87
- info(`Installing ${plat === 'darwin' ? 'LaunchAgent' : 'systemd-user unit'} at:`);
88
- info(colors.dim(` ${paths.unitFile}`));
89
- const installResult = yield (0, dev_service_1.installService)();
90
- if (!installResult.ok) {
91
- error(installResult.message);
92
- blocked = true;
93
- }
94
- else {
95
- if (installResult.created)
96
- success(installResult.message);
97
- else
98
- info(colors.dim(installResult.message));
99
- }
100
- // 5. Wait for admin endpoint
101
- if (installResult.ok) {
102
- info(colors.dim('Waiting for Caddy admin endpoint (:2019) ...'));
103
- const ready = yield (0, dev_service_1.waitForServiceReady)(8000);
104
- const status = yield (0, dev_service_1.getServiceStatus)();
105
- if (ready && status.daemonReachable) {
106
- success(`Caddy daemon ready${status.pid ? ` (pid ${status.pid})` : ''}.`);
107
- }
108
- else if (status.loaded && !status.daemonReachable) {
109
- warning('Service is loaded but admin endpoint did not respond within 8s.');
110
- info(colors.dim(` Logs: ${paths.logFile} / ${paths.errFile}`));
111
- blocked = true;
112
- }
113
- else {
114
- warning('Caddy daemon did not start. See logs:');
115
- info(colors.dim(` ${paths.logFile}`));
116
- info(colors.dim(` ${paths.errFile}`));
117
- blocked = true;
118
- }
119
- }
120
- // 6. Validate Caddyfile
121
- if (installResult.ok) {
122
- const validation = yield (0, caddy_1.validateCaddyfile)();
123
- if (validation.ok)
124
- success('Caddyfile validates');
125
- else
126
- warning(`Caddyfile validation: ${validation.stderr.split('\n').slice(0, 2).join(' / ')}`);
51
+ // Auto-chain: if we're inside an un-initialized lt-dev project, run the
52
+ // project init now. Calling the runMigrate HELPER (not the init command)
53
+ // makes infinite recursion structurally impossible. `--skip-init` opts out.
54
+ const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
55
+ const runInitAfter = (0, dev_bootstrap_1.shouldRunInitAfterInstall)({
56
+ isProject: (0, dev_bootstrap_1.isLtDevProject)(layout),
57
+ projectInitialized: (0, dev_bootstrap_1.isProjectInitialized)(layout),
58
+ skipInit: parameters.options.skipInit === true,
59
+ });
60
+ if (runInitAfter) {
61
+ info('');
62
+ info(colors.dim('Un-initialized lt dev project detected here — running `lt dev init` ...'));
63
+ (0, dev_migrate_helper_1.printMigrateResult)(toolbox, (0, dev_migrate_helper_1.runMigrate)({ layout }));
127
64
  }
128
- // 7. CA trust
129
- info('');
130
- info(colors.bold('Local CA trust'));
131
- info(' Caddy creates its local CA on first run. To trust it system-wide,');
132
- info(' run this once (HOME must be preserved so sudo keeps the user-scoped');
133
- info(' CA, otherwise caddy looks in /var/root and fails):');
134
- info(` ${colors.cyan('sudo -E HOME="$HOME" caddy trust')}`);
135
- info(` Browsers will then accept ${colors.cyan('https://*.localhost')} without warnings.`);
136
65
  info('');
137
- if (blocked) {
66
+ if (result.blocked) {
138
67
  warning('Setup incomplete. Address the items above and re-run `lt dev install`.');
139
68
  }
140
69
  else {
141
- success('Setup complete. Use `lt dev migrate` in a project, then `lt dev up`.');
70
+ success('Setup complete. Use `lt dev init` in a project, then `lt dev up`.');
142
71
  }
143
72
  if (!parameters.options.fromGluegunMenu)
144
- process.exit(blocked ? 1 : 0);
145
- return blocked ? 'dev install: incomplete' : 'dev install: ok';
73
+ process.exit(result.blocked ? 1 : 0);
74
+ return result.blocked ? 'dev install: incomplete' : 'dev install: ok';
146
75
  }),
147
76
  };
148
- /**
149
- * Quick `brew services list` scan for a registered caddy service.
150
- * Returns true on macOS if any entry contains "caddy" — error/started
151
- * alike, both are conflicts. Always returns false on non-darwin or
152
- * when `brew` is unavailable (no false positives).
153
- */
154
- function detectBrewCaddyConflict() {
155
- return __awaiter(this, void 0, void 0, function* () {
156
- if (process.platform !== 'darwin')
157
- return false;
158
- return new Promise((resolve) => {
159
- var _a;
160
- const { spawn } = require('child_process');
161
- const child = spawn('brew', ['services', 'list'], { stdio: ['ignore', 'pipe', 'ignore'] });
162
- let out = '';
163
- (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', (b) => (out += String(b)));
164
- child.on('error', () => resolve(false));
165
- child.on('close', () => {
166
- const conflict = /\bcaddy\b/.test(out) && !/^caddy\s+none\b/m.test(out);
167
- resolve(conflict);
168
- });
169
- });
170
- });
171
- }
172
77
  module.exports = InstallCommand;
@@ -36,7 +36,7 @@ const StatusCommand = {
36
36
  info(colors.dim('─'.repeat(60)));
37
37
  const slugs = Object.keys(reg.projects).sort();
38
38
  if (slugs.length === 0) {
39
- warning('No projects registered. Run `lt dev migrate` in a project.');
39
+ warning('No projects registered. Run `lt dev init` in a project.');
40
40
  }
41
41
  else {
42
42
  for (const slug of slugs) {
@@ -62,7 +62,7 @@ const StatusCommand = {
62
62
  info(colors.bold(`lt dev status: ${identity.slug}`));
63
63
  info(colors.dim('─'.repeat(60)));
64
64
  if (!entry) {
65
- warning('Not registered. Run `lt dev migrate` first.');
65
+ warning('Not registered. Run `lt dev init` first.');
66
66
  // Show what migrate would do (legacy code present?) so the user
67
67
  // can judge urgency before running it.
68
68
  const legacyFiles = [];
@@ -74,11 +74,11 @@ const StatusCommand = {
74
74
  if (layout.appDir)
75
75
  legacyFiles.push(...(0, dev_project_1.appNeedsPortPatch)(layout.appDir));
76
76
  if (legacyFiles.length > 0) {
77
- info(colors.dim(' Legacy hardcoded ports detected — `lt dev migrate` will patch:'));
77
+ info(colors.dim(' Legacy hardcoded ports detected — `lt dev init` will patch:'));
78
78
  legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
79
79
  }
80
80
  else {
81
- info(colors.dim(' Code is already env-aware; `lt dev migrate` will only register + patch CLAUDE.md.'));
81
+ info(colors.dim(' Code is already env-aware; `lt dev init` will only register + patch CLAUDE.md.'));
82
82
  }
83
83
  info('');
84
84
  if (!parameters.options.fromGluegunMenu)
@@ -104,7 +104,7 @@ const StatusCommand = {
104
104
  info('');
105
105
  warning(' Legacy hardcoded ports still present:');
106
106
  legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
107
- info(colors.dim(' Run `lt dev migrate` to patch them; otherwise Caddy may proxy into the void.'));
107
+ info(colors.dim(' Run `lt dev init` to patch them; otherwise Caddy may proxy into the void.'));
108
108
  }
109
109
  // Caddy status — quick view whether the daemon is reachable.
110
110
  {
@@ -49,7 +49,7 @@ const TunnelCommand = {
49
49
  const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
50
50
  const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
51
51
  if (!layout.apiDir && !layout.appDir) {
52
- error('No API or App project detected at this path. Run `lt dev migrate` first.');
52
+ error('No API or App project detected at this path. Run `lt dev init` first.');
53
53
  if (!parameters.options.fromGluegunMenu)
54
54
  process.exit(1);
55
55
  return 'dev tunnel: not a project';
@@ -50,7 +50,7 @@ const UpCommand = {
50
50
  const { filesystem, parameters, print: { colors, error, info, success, warning }, } = toolbox;
51
51
  const layout = (0, dev_project_1.resolveLayout)(filesystem.cwd(), filesystem);
52
52
  if (!layout.apiDir && !layout.appDir) {
53
- error('No API or App project detected at this path. Run `lt dev migrate` first.');
53
+ error('No API or App project detected at this path. Run `lt dev init` first.');
54
54
  if (!parameters.options.fromGluegunMenu)
55
55
  process.exit(1);
56
56
  return 'dev up: not a project';
@@ -72,7 +72,7 @@ const UpCommand = {
72
72
  const dbName = (0, dev_project_1.deriveDbName)(layout.apiDir, identity.slug);
73
73
  // Sanft auto-migrate sichere Operationen (ohne Code-Modifikation):
74
74
  // CLAUDE.md-URL-Block einfügen + .gitignore ergänzen.
75
- // Code-Patches (config.env.ts, nuxt.config.ts) bleiben explizit `lt dev migrate`.
75
+ // Code-Patches (config.env.ts, nuxt.config.ts) bleiben explizit `lt dev init`.
76
76
  {
77
77
  const claudeCandidates = [
78
78
  (0, path_1.join)(layout.root, 'CLAUDE.md'),
@@ -98,7 +98,7 @@ const UpCommand = {
98
98
  if (layout.appDir)
99
99
  legacyFiles.push(...(0, dev_project_1.appNeedsPortPatch)(layout.appDir));
100
100
  if (legacyFiles.length > 0) {
101
- warning('Legacy hardcoded ports detected — Caddy will proxy correctly only after running `lt dev migrate`:');
101
+ warning('Legacy hardcoded ports detected — Caddy will proxy correctly only after running `lt dev init`:');
102
102
  legacyFiles.forEach((f) => info(colors.dim(` - ${f}`)));
103
103
  info(colors.dim(' (Continuing — env-aware files will work; legacy files may bind on 3000/3001 and miss Caddy.)'));
104
104
  }
@@ -301,6 +301,18 @@ const NewCommand = {
301
301
  else {
302
302
  renameSpinner.succeed(`Renamed nest-base → ${projectDir} in projects/api`);
303
303
  }
304
+ // Flip .claude/upstream.json from the template-self default to the
305
+ // downstream shape so `/upstream-pr` can contribute core fixes back
306
+ // to nest-base. Independent of the rename above — run it even if
307
+ // the rename failed. Non-fatal when the file is absent.
308
+ const upstreamResult = (0, workspace_integration_1.reconfigureUpstreamForDownstream)({
309
+ apiDir: apiDest,
310
+ filesystem,
311
+ upstreamBranch: apiBranch,
312
+ });
313
+ if (upstreamResult.updated) {
314
+ info('Configured .claude/upstream.json for downstream contributions');
315
+ }
304
316
  }
305
317
  // Persist apiMode + frameworkMode for downstream generators.
306
318
  if (apiResult.method !== 'link') {
@@ -17,6 +17,7 @@ const caddy_1 = require("../../lib/caddy");
17
17
  const dev_migrate_helper_1 = require("../../lib/dev-migrate-helper");
18
18
  const dev_project_1 = require("../../lib/dev-project");
19
19
  const hoist_workspace_pnpm_config_1 = require("../../lib/hoist-workspace-pnpm-config");
20
+ const package_name_1 = require("../../lib/package-name");
20
21
  const workspace_integration_1 = require("../../lib/workspace-integration");
21
22
  const add_api_1 = __importDefault(require("./add-api"));
22
23
  const add_app_1 = __importDefault(require("./add-app"));
@@ -440,6 +441,11 @@ const NewCommand = {
440
441
  .replace(/\{\{FRONTEND_FRAMEWORK\}\}/g, () => frontendName));
441
442
  }
442
443
  }
444
+ // Rename the cloned monorepo's root package so each project gets a unique
445
+ // `lt dev` slug. The slug is derived from package.json `name`; without
446
+ // this rename every lt-monorepo-based project would register as
447
+ // `lt-monorepo` and collide on `https://lt-monorepo.localhost`.
448
+ (0, package_name_1.setPackageName)({ filesystem, name: projectDir, packageJsonPath: `${projectDir}/package.json` });
443
449
  // Always initialize git
444
450
  try {
445
451
  yield system.run(`cd ${projectDir} && git init --initial-branch=dev`);
@@ -559,6 +565,18 @@ const NewCommand = {
559
565
  catch (err) {
560
566
  renameSpinner.warn(`Auto-rename failed (${err.message}). Run \`bun run rename ${projectDir}\` manually inside projects/api.`);
561
567
  }
568
+ // Flip .claude/upstream.json from the template-self default to the
569
+ // downstream shape so `/upstream-pr` can contribute core fixes back
570
+ // to nest-base. Independent of the rename above — run it even if the
571
+ // rename failed. Non-fatal when the file is absent.
572
+ const upstreamResult = (0, workspace_integration_1.reconfigureUpstreamForDownstream)({
573
+ apiDir: apiDest,
574
+ filesystem,
575
+ upstreamBranch: apiBranch,
576
+ });
577
+ if (upstreamResult.updated) {
578
+ info('Configured .claude/upstream.json for downstream contributions');
579
+ }
562
580
  }
563
581
  // Create lt.config.json for API
564
582
  // Note: frameworkMode is persisted under meta so that subsequent `lt
@@ -641,7 +659,7 @@ const NewCommand = {
641
659
  return;
642
660
  }
643
661
  }
644
- // Best-effort `lt dev migrate` so the workspace is ready for `lt dev up`
662
+ // Best-effort `lt dev init` so the workspace is ready for `lt dev up`
645
663
  // out-of-the-box: registers the slug in `~/.lenneTech/projects.json`,
646
664
  // injects the URL block into CLAUDE.md, adds `.lt-dev/` to .gitignore.
647
665
  // Failures here are non-fatal — `init` itself remains successful.
@@ -376,6 +376,21 @@ const NewCommand = {
376
376
  }
377
377
  return `created server symlink ${name}`;
378
378
  }
379
+ // For the experimental nest-base template, flip .claude/upstream.json
380
+ // from the template-self default to the downstream shape so
381
+ // `/upstream-pr` can contribute core fixes back to nest-base. The
382
+ // standalone clone lands directly at `projectDir`. Non-fatal when the
383
+ // file is absent (older templates).
384
+ if (experimental) {
385
+ const upstreamResult = (0, workspace_integration_1.reconfigureUpstreamForDownstream)({
386
+ apiDir: projectDir,
387
+ filesystem,
388
+ upstreamBranch: branch,
389
+ });
390
+ if (upstreamResult.updated) {
391
+ info('Configured .claude/upstream.json for downstream contributions');
392
+ }
393
+ }
379
394
  // Git initialization (after npm install which is done in setupServer).
380
395
  // When cwd is not inside a repo, `git rev-parse` exits 128 — treat as false.
381
396
  if (git) {
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isLtDevProject = isLtDevProject;
4
+ exports.isMachinePrepared = isMachinePrepared;
5
+ exports.isProjectInitialized = isProjectInitialized;
6
+ exports.shouldRunInitAfterInstall = shouldRunInitAfterInstall;
7
+ exports.shouldRunInstallBeforeInit = shouldRunInstallBeforeInit;
8
+ /**
9
+ * Bootstrap-state checks that drive the mutual auto-chaining between
10
+ * `lt dev install` (per-machine setup) and `lt dev init` (per-project
11
+ * setup).
12
+ *
13
+ * These are pure, synchronous predicates so the chaining decision is
14
+ * trivially testable and cannot itself trigger side effects. The actual
15
+ * work is delegated to `runInstall` / `runMigrate`, which never call each
16
+ * other — so the install↔init chaining can never recurse infinitely:
17
+ *
18
+ * - `lt dev init` → if NOT machine-prepared, run install first, then init
19
+ * - `lt dev install`→ if inside an un-initialized project, run init after
20
+ *
21
+ * Because each command calls the *helpers* (not the other command), the
22
+ * chain is at most one hop deep in either direction. The predicates below
23
+ * additionally make re-runs no-ops (nothing repeated unnecessarily).
24
+ */
25
+ const fs_1 = require("fs");
26
+ const dev_identity_1 = require("./dev-identity");
27
+ const dev_service_1 = require("./dev-service");
28
+ const dev_state_1 = require("./dev-state");
29
+ /**
30
+ * True if this path is an lt-dev-capable project — i.e. `resolveLayout`
31
+ * found an API (`src/config.env.ts`) and/or App (`nuxt.config.ts`).
32
+ * Used by `lt dev install` to decide whether an auto-init makes sense.
33
+ */
34
+ function isLtDevProject(layout) {
35
+ return !!(layout.apiDir || layout.appDir);
36
+ }
37
+ /**
38
+ * True once `lt dev install` has set up this machine — i.e. the
39
+ * LaunchAgent/systemd unit file exists. This is the durable marker that
40
+ * install has run; whether the daemon is currently *running* is a
41
+ * separate concern handled by `lt dev up` / `doctor`.
42
+ *
43
+ * Always false on unsupported platforms (no service model), so the
44
+ * chaining never tries to install where it cannot.
45
+ */
46
+ function isMachinePrepared() {
47
+ const paths = (0, dev_service_1.getServicePaths)();
48
+ return paths.platform !== 'unsupported' && (0, fs_1.existsSync)(paths.unitFile);
49
+ }
50
+ /**
51
+ * True if this project is already registered with `lt dev` (present in
52
+ * `~/.lenneTech/projects.json` under its slug, pointing at this root).
53
+ * This is the durable marker that `lt dev init` has run for the project.
54
+ */
55
+ function isProjectInitialized(layout) {
56
+ const slug = (0, dev_identity_1.buildIdentity)(layout.root).slug;
57
+ const entry = (0, dev_state_1.loadRegistry)().projects[slug];
58
+ return !!entry && entry.path === layout.root;
59
+ }
60
+ /**
61
+ * Pure decision: should `lt dev install` run `init` AFTERWARDS? Yes only
62
+ * when not opted out, we're inside an lt-dev-capable project, and that
63
+ * project isn't initialized yet. Re-runs become no-ops once initialized.
64
+ */
65
+ function shouldRunInitAfterInstall(input) {
66
+ return !input.skipInit && input.isProject && !input.projectInitialized;
67
+ }
68
+ /**
69
+ * Pure decision: should `lt dev init` run `install` BEFORE initializing?
70
+ * Yes only when not opted out, the platform supports a service model, and
71
+ * the machine isn't prepared yet. Re-runs become no-ops once prepared.
72
+ */
73
+ function shouldRunInstallBeforeInit(input) {
74
+ return !input.skipInstall && input.platformSupported && !input.machinePrepared;
75
+ }
@@ -35,7 +35,12 @@ function buildDevEnv(input) {
35
35
  const apiUrl = apiSub ? `https://${apiSub.hostname}` : '';
36
36
  const appUrl = appSub ? `https://${appSub.hostname}` : '';
37
37
  const caPath = (0, dev_env_bridge_1.detectCaddyRootCa)();
38
- const sharedKeys = Object.assign(Object.assign(Object.assign(Object.assign({}, (apiUrl ? { BASE_URL: apiUrl, NSC__BASE_URL: apiUrl } : {})), (appUrl ? { APP_URL: appUrl, NSC__APP_URL: appUrl } : {})), (dbName ? { DATABASE_URL: buildPostgresUrl(dbName), NSC__MONGOOSE__URI: `mongodb://127.0.0.1/${dbName}` } : {})), (caPath ? { NODE_EXTRA_CA_CERTS: caPath } : {}));
38
+ const sharedKeys = Object.assign(Object.assign(Object.assign(Object.assign({
39
+ // Marks the API + App processes as running under `lt dev`. Consumed by the
40
+ // backend to relax dev-only behaviour (rate limiting, Better-Auth
41
+ // user-cache) so E2E suites run without a separate VITEST/PLAYWRIGHT flag.
42
+ // (Also written to the .lt-dev/.env bridge for external test runners.)
43
+ LT_DEV_ACTIVE: 'true' }, (apiUrl ? { BASE_URL: apiUrl, NSC__BASE_URL: apiUrl } : {})), (appUrl ? { APP_URL: appUrl, NSC__APP_URL: appUrl } : {})), (dbName ? { DATABASE_URL: buildPostgresUrl(dbName), NSC__MONGOOSE__URI: `mongodb://127.0.0.1/${dbName}` } : {})), (caPath ? { NODE_EXTRA_CA_CERTS: caPath } : {}));
39
44
  return {
40
45
  api: {
41
46
  env: Object.assign(Object.assign(Object.assign({}, baseEnv), sharedKeys), {