happy-stacks 0.4.0 → 0.5.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.
Files changed (104) hide show
  1. package/README.md +64 -33
  2. package/bin/happys.mjs +44 -1
  3. package/docs/codex-mcp-resume.md +130 -0
  4. package/docs/commit-audits/happy/leeroy-wip.commit-analysis.md +17640 -0
  5. package/docs/commit-audits/happy/leeroy-wip.commit-export.fuller-stat.md +3845 -0
  6. package/docs/commit-audits/happy/leeroy-wip.commit-inventory.md +102 -0
  7. package/docs/commit-audits/happy/leeroy-wip.commit-manual-review.md +1452 -0
  8. package/docs/commit-audits/happy/leeroy-wip.manual-review-queue.md +116 -0
  9. package/docs/happy-development.md +1 -2
  10. package/docs/monorepo-migration.md +286 -0
  11. package/docs/server-flavors.md +19 -3
  12. package/docs/stacks.md +35 -0
  13. package/package.json +1 -1
  14. package/scripts/auth.mjs +21 -3
  15. package/scripts/build.mjs +1 -1
  16. package/scripts/dev.mjs +20 -7
  17. package/scripts/doctor.mjs +0 -4
  18. package/scripts/edison.mjs +2 -2
  19. package/scripts/env.mjs +150 -0
  20. package/scripts/env_cmd.test.mjs +128 -0
  21. package/scripts/init.mjs +5 -2
  22. package/scripts/install.mjs +99 -57
  23. package/scripts/migrate.mjs +3 -12
  24. package/scripts/monorepo.mjs +1096 -0
  25. package/scripts/monorepo_port.test.mjs +1470 -0
  26. package/scripts/review.mjs +715 -24
  27. package/scripts/review_pr.mjs +5 -20
  28. package/scripts/run.mjs +21 -15
  29. package/scripts/setup.mjs +147 -25
  30. package/scripts/setup_pr.mjs +19 -28
  31. package/scripts/stack.mjs +493 -157
  32. package/scripts/stack_archive_cmd.test.mjs +91 -0
  33. package/scripts/stack_editor_workspace_monorepo_root.test.mjs +65 -0
  34. package/scripts/stack_env_cmd.test.mjs +87 -0
  35. package/scripts/stack_happy_cmd.test.mjs +126 -0
  36. package/scripts/stack_interactive_monorepo_group.test.mjs +71 -0
  37. package/scripts/stack_monorepo_defaults.test.mjs +62 -0
  38. package/scripts/stack_monorepo_server_light_from_happy_spec.test.mjs +66 -0
  39. package/scripts/stack_server_flavors_defaults.test.mjs +55 -0
  40. package/scripts/stack_shorthand_cmd.test.mjs +55 -0
  41. package/scripts/stack_wt_list.test.mjs +128 -0
  42. package/scripts/tui.mjs +88 -2
  43. package/scripts/utils/cli/cli_registry.mjs +20 -5
  44. package/scripts/utils/cli/cwd_scope.mjs +56 -2
  45. package/scripts/utils/cli/cwd_scope.test.mjs +40 -7
  46. package/scripts/utils/cli/prereqs.mjs +8 -5
  47. package/scripts/utils/cli/prereqs.test.mjs +34 -0
  48. package/scripts/utils/cli/wizard.mjs +17 -9
  49. package/scripts/utils/cli/wizard_prompt_worktree_source_lazy.test.mjs +60 -0
  50. package/scripts/utils/dev/daemon.mjs +14 -1
  51. package/scripts/utils/dev/expo_dev.mjs +188 -4
  52. package/scripts/utils/dev/server.mjs +21 -17
  53. package/scripts/utils/edison/git_roots.mjs +29 -0
  54. package/scripts/utils/edison/git_roots.test.mjs +36 -0
  55. package/scripts/utils/env/env.mjs +7 -3
  56. package/scripts/utils/env/env_file.mjs +4 -2
  57. package/scripts/utils/env/env_file.test.mjs +44 -0
  58. package/scripts/utils/git/worktrees.mjs +63 -12
  59. package/scripts/utils/git/worktrees_monorepo.test.mjs +54 -0
  60. package/scripts/utils/net/tcp_forward.mjs +162 -0
  61. package/scripts/utils/paths/paths.mjs +118 -3
  62. package/scripts/utils/paths/paths_monorepo.test.mjs +58 -0
  63. package/scripts/utils/paths/paths_server_flavors.test.mjs +45 -0
  64. package/scripts/utils/proc/commands.mjs +2 -3
  65. package/scripts/utils/proc/pm.mjs +113 -16
  66. package/scripts/utils/proc/pm_spawn.test.mjs +76 -0
  67. package/scripts/utils/proc/pm_stack_cache_env.test.mjs +142 -0
  68. package/scripts/utils/proc/proc.mjs +68 -10
  69. package/scripts/utils/proc/proc.test.mjs +77 -0
  70. package/scripts/utils/review/chunks.mjs +55 -0
  71. package/scripts/utils/review/chunks.test.mjs +51 -0
  72. package/scripts/utils/review/findings.mjs +165 -0
  73. package/scripts/utils/review/findings.test.mjs +85 -0
  74. package/scripts/utils/review/head_slice.mjs +153 -0
  75. package/scripts/utils/review/head_slice.test.mjs +91 -0
  76. package/scripts/utils/review/instructions/deep.md +20 -0
  77. package/scripts/utils/review/runners/coderabbit.mjs +56 -14
  78. package/scripts/utils/review/runners/coderabbit.test.mjs +59 -0
  79. package/scripts/utils/review/runners/codex.mjs +32 -22
  80. package/scripts/utils/review/runners/codex.test.mjs +35 -0
  81. package/scripts/utils/review/slices.mjs +140 -0
  82. package/scripts/utils/review/slices.test.mjs +32 -0
  83. package/scripts/utils/server/flavor_scripts.mjs +98 -0
  84. package/scripts/utils/server/flavor_scripts.test.mjs +146 -0
  85. package/scripts/utils/server/prisma_import.mjs +37 -0
  86. package/scripts/utils/server/prisma_import.test.mjs +70 -0
  87. package/scripts/utils/server/ui_env.mjs +14 -0
  88. package/scripts/utils/server/ui_env.test.mjs +46 -0
  89. package/scripts/utils/server/validate.mjs +53 -16
  90. package/scripts/utils/server/validate.test.mjs +89 -0
  91. package/scripts/utils/stack/editor_workspace.mjs +4 -4
  92. package/scripts/utils/stack/interactive_stack_config.mjs +185 -0
  93. package/scripts/utils/stack/startup.mjs +113 -13
  94. package/scripts/utils/stack/startup_server_light_dirs.test.mjs +64 -0
  95. package/scripts/utils/stack/startup_server_light_generate.test.mjs +70 -0
  96. package/scripts/utils/stack/startup_server_light_legacy.test.mjs +88 -0
  97. package/scripts/utils/tailscale/ip.mjs +116 -0
  98. package/scripts/utils/ui/ansi.mjs +39 -0
  99. package/scripts/where.mjs +2 -2
  100. package/scripts/worktrees.mjs +627 -137
  101. package/scripts/worktrees_archive_cmd.test.mjs +245 -0
  102. package/scripts/worktrees_cursor_monorepo_root.test.mjs +63 -0
  103. package/scripts/worktrees_list_specs_no_recurse.test.mjs +33 -0
  104. package/scripts/worktrees_monorepo_use_group.test.mjs +67 -0
@@ -2,7 +2,7 @@ import './utils/env/env.mjs';
2
2
  import { parseArgs } from './utils/cli/args.mjs';
3
3
  import { pathExists } from './utils/fs/fs.mjs';
4
4
  import { run, runCapture } from './utils/proc/proc.mjs';
5
- import { getComponentDir, getRootDir } from './utils/paths/paths.mjs';
5
+ import { getComponentDir, getComponentRepoDir, getRootDir, isHappyMonorepoRoot } from './utils/paths/paths.mjs';
6
6
  import { getServerComponentName } from './utils/server/server.mjs';
7
7
  import { ensureCliBuilt, ensureDepsInstalled, ensureHappyCliLocalNpmLinked } from './utils/proc/pm.mjs';
8
8
  import { dirname, join } from 'node:path';
@@ -12,6 +12,7 @@ import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
12
12
  import { ensureEnvLocalUpdated } from './utils/env/env_local.mjs';
13
13
  import { isTty, prompt, promptSelect, withRl } from './utils/cli/wizard.mjs';
14
14
  import { isSandboxed, sandboxAllowsGlobalSideEffects } from './utils/env/sandbox.mjs';
15
+ import { bold, cyan, dim } from './utils/ui/ansi.mjs';
15
16
 
16
17
  /**
17
18
  * Install/setup the local stack:
@@ -35,8 +36,12 @@ const DEFAULT_FORK_REPOS = {
35
36
  const DEFAULT_UPSTREAM_REPOS = {
36
37
  // Upstream for server-light lives in the main happy-server repo.
37
38
  serverLight: 'https://github.com/slopus/happy-server.git',
38
- serverFull: 'https://github.com/slopus/happy-server.git',
39
- cli: 'https://github.com/slopus/happy-cli.git',
39
+ serverFull: 'https://github.com/slopus/happy.git',
40
+ // slopus/happy is now a monorepo that contains:
41
+ // - expo-app/ (UI)
42
+ // - cli/ (happy-cli)
43
+ // - server/ (happy-server)
44
+ cli: 'https://github.com/slopus/happy.git',
40
45
  ui: 'https://github.com/slopus/happy.git',
41
46
  };
42
47
 
@@ -54,8 +59,8 @@ function repoUrlsFromOwners({ forkOwner, upstreamOwner }) {
54
59
  upstream: {
55
60
  // server-light upstream lives in happy-server
56
61
  serverLight: up('happy-server'),
57
- serverFull: up('happy-server'),
58
- cli: up('happy-cli'),
62
+ serverFull: up('happy'),
63
+ cli: up('happy'),
59
64
  ui: up('happy'),
60
65
  },
61
66
  };
@@ -80,12 +85,14 @@ function resolveRepoSource({ flags }) {
80
85
 
81
86
  function getRepoUrls({ repoSource }) {
82
87
  const defaults = repoSource === 'upstream' ? DEFAULT_UPSTREAM_REPOS : DEFAULT_FORK_REPOS;
88
+ const ui = process.env.HAPPY_LOCAL_UI_REPO_URL?.trim() || defaults.ui;
83
89
  return {
84
90
  // Backwards compatible: HAPPY_LOCAL_SERVER_REPO_URL historically referred to the server-light component.
85
91
  serverLight: process.env.HAPPY_LOCAL_SERVER_LIGHT_REPO_URL?.trim() || process.env.HAPPY_LOCAL_SERVER_REPO_URL?.trim() || defaults.serverLight,
86
- serverFull: process.env.HAPPY_LOCAL_SERVER_FULL_REPO_URL?.trim() || defaults.serverFull,
87
- cli: process.env.HAPPY_LOCAL_CLI_REPO_URL?.trim() || defaults.cli,
88
- ui: process.env.HAPPY_LOCAL_UI_REPO_URL?.trim() || defaults.ui,
92
+ // Default to the UI repo when using a monorepo (override to keep split repos).
93
+ serverFull: process.env.HAPPY_LOCAL_SERVER_FULL_REPO_URL?.trim() || defaults.serverFull || ui,
94
+ cli: process.env.HAPPY_LOCAL_CLI_REPO_URL?.trim() || defaults.cli || ui,
95
+ ui,
89
96
  };
90
97
  }
91
98
 
@@ -168,31 +175,33 @@ async function ensureUpstreamRemote({ repoDir, upstreamUrl }) {
168
175
  async function interactiveWizard({ rootDir, defaults }) {
169
176
  return await withRl(async (rl) => {
170
177
  const repoSource = await promptSelect(rl, {
171
- title: 'Select repo source:',
178
+ title: `${bold('Repo source')}\n${dim('Where should Happy Stacks clone the component repos from?')}`,
172
179
  options: [
173
- { label: `forks (default, recommended)`, value: 'forks' },
174
- { label: `upstream (slopus/*)`, value: 'upstream' },
180
+ { label: `${cyan('forks')} (default, recommended)`, value: 'forks' },
181
+ { label: `${cyan('upstream')} (slopus/*)`, value: 'upstream' },
175
182
  ],
176
183
  defaultIndex: defaults.repoSource === 'upstream' ? 1 : 0,
177
184
  });
178
185
 
186
+ // eslint-disable-next-line no-console
187
+ console.log(dim('Tip: keep the defaults unless you maintain your own forks.'));
179
188
  const forkOwner = await prompt(rl, `GitHub fork owner (default: ${defaults.forkOwner}): `, { defaultValue: defaults.forkOwner });
180
189
  const upstreamOwner = await prompt(rl, `GitHub upstream owner (default: ${defaults.upstreamOwner}): `, {
181
190
  defaultValue: defaults.upstreamOwner,
182
191
  });
183
192
 
184
193
  const serverMode = await promptSelect(rl, {
185
- title: 'Which server components should be set up?',
194
+ title: `${bold('Server components')}\n${dim('Choose which server repo(s) to clone and install deps for.')}`,
186
195
  options: [
187
- { label: 'happy-server-light only (default)', value: 'happy-server-light' },
188
- { label: 'happy-server only (full server)', value: 'happy-server' },
189
- { label: 'both (server-light + full server)', value: 'both' },
196
+ { label: `${cyan('happy-server-light')} only (default)`, value: 'happy-server-light' },
197
+ { label: `${cyan('happy-server')} only (full server)`, value: 'happy-server' },
198
+ { label: `both (${cyan('server-light')} + ${cyan('full server')})`, value: 'both' },
190
199
  ],
191
200
  defaultIndex: defaults.serverComponentName === 'both' ? 2 : defaults.serverComponentName === 'happy-server' ? 1 : 0,
192
201
  });
193
202
 
194
203
  const allowClone = await promptSelect(rl, {
195
- title: 'Clone missing component repos?',
204
+ title: `${bold('Cloning')}\n${dim('If repos are missing under components/, should we clone them automatically?')}`,
196
205
  options: [
197
206
  { label: 'yes (default)', value: true },
198
207
  { label: 'no', value: false },
@@ -202,8 +211,8 @@ async function interactiveWizard({ rootDir, defaults }) {
202
211
 
203
212
  const enableAutostart = await promptSelect(rl, {
204
213
  title: isSandboxed()
205
- ? 'Enable macOS autostart (LaunchAgent)? (NOTE: sandbox mode; this is global OS state)'
206
- : 'Enable macOS autostart (LaunchAgent)?',
214
+ ? `${bold('Autostart (macOS)')}\n${dim('Sandbox mode: this is global OS state; normally disabled in sandbox.')}`
215
+ : `${bold('Autostart (macOS)')}\n${dim('Install a LaunchAgent so Happy starts at login?')}`,
207
216
  options: [
208
217
  { label: 'no (default)', value: false },
209
218
  { label: 'yes', value: true },
@@ -212,7 +221,7 @@ async function interactiveWizard({ rootDir, defaults }) {
212
221
  });
213
222
 
214
223
  const buildTauri = await promptSelect(rl, {
215
- title: 'Build Tauri desktop app as part of setup?',
224
+ title: `${bold('Desktop app (optional)')}\n${dim('Build the Tauri desktop app as part of setup? (slow; requires extra toolchain)')}`,
216
225
  options: [
217
226
  { label: 'no (default)', value: false },
218
227
  { label: 'yes', value: true },
@@ -221,7 +230,7 @@ async function interactiveWizard({ rootDir, defaults }) {
221
230
  });
222
231
 
223
232
  const configureGit = await promptSelect(rl, {
224
- title: 'Configure upstream Git remotes and create mirror branches (slopus/main)?',
233
+ title: `${bold('Git remotes')}\n${dim('Configure upstream remotes and create/update mirror branches (e.g. slopus/main)?')}`,
225
234
  options: [
226
235
  { label: 'yes (default)', value: true },
227
236
  { label: 'no', value: false },
@@ -344,49 +353,71 @@ async function main() {
344
353
  `- use --server=happy-server with --upstream`
345
354
  );
346
355
  }
347
- const serverLightDir = getComponentDir(rootDir, 'happy-server-light');
348
- const serverFullDir = getComponentDir(rootDir, 'happy-server');
349
- const cliDir = getComponentDir(rootDir, 'happy-cli');
356
+ // Repo roots (clone locations)
357
+ const uiRepoDir = getComponentRepoDir(rootDir, 'happy');
358
+ const serverLightRepoDir = getComponentRepoDir(rootDir, 'happy-server-light');
359
+
360
+ // Ensure UI exists first (monorepo anchor in slopus/happy).
361
+ await ensureComponentPresent({
362
+ dir: uiRepoDir,
363
+ label: 'UI',
364
+ repoUrl: repos.ui,
365
+ allowClone,
366
+ });
367
+
368
+ // Package dirs (where we run installs/builds). Recompute after cloning UI.
350
369
  const uiDir = getComponentDir(rootDir, 'happy');
370
+ const cliDir = getComponentDir(rootDir, 'happy-cli');
371
+ const serverFullDir = getComponentDir(rootDir, 'happy-server');
372
+
373
+ const cliRepoDir = getComponentRepoDir(rootDir, 'happy-cli');
374
+ const serverFullRepoDir = getComponentRepoDir(rootDir, 'happy-server');
375
+ const hasMonorepo = isHappyMonorepoRoot(uiRepoDir);
351
376
 
352
- // Ensure components exist (embedded layout)
377
+ // Ensure other components exist.
378
+ // - server-light stays separate for now.
379
+ // - full server + cli may be embedded in the monorepo.
353
380
  if (serverComponentName === 'both' || serverComponentName === 'happy-server-light') {
354
- await ensureComponentPresent({
355
- dir: serverLightDir,
356
- label: 'SERVER',
381
+ await ensureComponentPresent({
382
+ dir: serverLightRepoDir,
383
+ label: 'SERVER',
357
384
  repoUrl: repos.serverLight,
358
385
  allowClone,
359
386
  });
360
387
  }
361
- if (serverComponentName === 'both' || serverComponentName === 'happy-server') {
388
+ if (!hasMonorepo) {
389
+ if (serverComponentName === 'both' || serverComponentName === 'happy-server') {
390
+ await ensureComponentPresent({
391
+ dir: serverFullRepoDir,
392
+ label: 'SERVER_FULL',
393
+ repoUrl: repos.serverFull,
394
+ allowClone,
395
+ });
396
+ }
362
397
  await ensureComponentPresent({
363
- dir: serverFullDir,
364
- label: 'SERVER_FULL',
365
- repoUrl: repos.serverFull,
366
- allowClone,
367
- });
398
+ dir: cliRepoDir,
399
+ label: 'CLI',
400
+ repoUrl: repos.cli,
401
+ allowClone,
402
+ });
403
+ } else {
404
+ if ((serverComponentName === 'both' || serverComponentName === 'happy-server') && !(await pathExists(serverFullDir))) {
405
+ throw new Error(`[bootstrap] expected monorepo server package at ${serverFullDir} (missing).`);
406
+ }
407
+ if (!(await pathExists(cliDir))) {
408
+ throw new Error(`[bootstrap] expected monorepo cli package at ${cliDir} (missing).`);
409
+ }
368
410
  }
369
- await ensureComponentPresent({
370
- dir: cliDir,
371
- label: 'CLI',
372
- repoUrl: repos.cli,
373
- allowClone,
374
- });
375
- await ensureComponentPresent({
376
- dir: uiDir,
377
- label: 'UI',
378
- repoUrl: repos.ui,
379
- allowClone,
380
- });
381
411
 
382
412
  // Ensure expected branches are checked out for server flavors (avoids "server-light directory contains full server" mistakes).
383
413
  if (serverComponentName === 'both' || serverComponentName === 'happy-server-light') {
384
- await ensureGitBranchCheckedOut({ repoDir: serverLightDir, branch: 'happy-server-light', label: 'SERVER' });
414
+ await ensureGitBranchCheckedOut({ repoDir: serverLightRepoDir, branch: 'happy-server-light', label: 'SERVER' });
385
415
  }
386
416
  if (serverComponentName === 'both' || serverComponentName === 'happy-server') {
387
- // In fork mode, full server is a branch in the fork server repo. In upstream mode, use upstream main.
388
- const serverFullBranch = repoSource === 'upstream' ? 'main' : 'happy-server';
389
- await ensureGitBranchCheckedOut({ repoDir: serverFullDir, branch: serverFullBranch, label: 'SERVER_FULL' });
417
+ // In fork mode (split repos), full server is a branch in the fork server repo.
418
+ // In upstream mode and in monorepo mode, use main.
419
+ const serverFullBranch = isHappyMonorepoRoot(serverFullRepoDir) ? 'main' : repoSource === 'upstream' ? 'main' : 'happy-server';
420
+ await ensureGitBranchCheckedOut({ repoDir: serverFullRepoDir, branch: serverFullBranch, label: 'SERVER_FULL' });
390
421
  }
391
422
 
392
423
  const cliDirFinal = cliDir;
@@ -396,7 +427,7 @@ async function main() {
396
427
  const skipUiDeps = flags.has('--no-ui-deps') || (process.env.HAPPY_STACKS_INSTALL_NO_UI_DEPS ?? '').trim() === '1';
397
428
  const skipCliDeps = flags.has('--no-cli-deps') || (process.env.HAPPY_STACKS_INSTALL_NO_CLI_DEPS ?? '').trim() === '1';
398
429
  if (serverComponentName === 'both' || serverComponentName === 'happy-server-light') {
399
- await ensureDepsInstalled(serverLightDir, 'happy-server-light');
430
+ await ensureDepsInstalled(getComponentDir(rootDir, 'happy-server-light'), 'happy-server-light');
400
431
  }
401
432
  if (serverComponentName === 'both' || serverComponentName === 'happy-server') {
402
433
  await ensureDepsInstalled(serverFullDir, 'happy-server');
@@ -442,14 +473,16 @@ async function main() {
442
473
  if (wizard?.configureGit) {
443
474
  // Ensure upstream remotes exist so `happys wt sync-all` works consistently.
444
475
  const upstreamRepos = getRepoUrls({ repoSource: 'upstream' });
445
- await ensureUpstreamRemote({ repoDir: uiDir, upstreamUrl: upstreamRepos.ui });
446
- await ensureUpstreamRemote({ repoDir: cliDir, upstreamUrl: upstreamRepos.cli });
476
+ await ensureUpstreamRemote({ repoDir: uiRepoDir, upstreamUrl: upstreamRepos.ui });
477
+ if (cliRepoDir !== uiRepoDir) {
478
+ await ensureUpstreamRemote({ repoDir: cliRepoDir, upstreamUrl: upstreamRepos.cli });
479
+ }
447
480
  // server-light and server-full both track upstream happy-server
448
- if (await pathExists(serverLightDir)) {
449
- await ensureUpstreamRemote({ repoDir: serverLightDir, upstreamUrl: upstreamRepos.serverLight });
481
+ if (await pathExists(serverLightRepoDir)) {
482
+ await ensureUpstreamRemote({ repoDir: serverLightRepoDir, upstreamUrl: upstreamRepos.serverLight });
450
483
  }
451
- if (await pathExists(serverFullDir)) {
452
- await ensureUpstreamRemote({ repoDir: serverFullDir, upstreamUrl: upstreamRepos.serverFull });
484
+ if (serverFullRepoDir !== uiRepoDir && (await pathExists(serverFullRepoDir))) {
485
+ await ensureUpstreamRemote({ repoDir: serverFullRepoDir, upstreamUrl: upstreamRepos.serverFull });
453
486
  }
454
487
 
455
488
  // Create/update mirror branches like slopus/main for each repo (best-effort).
@@ -466,7 +499,16 @@ async function main() {
466
499
  ok: true,
467
500
  repoSource,
468
501
  serverComponentName,
469
- dirs: { serverLightDir, serverFullDir, cliDir: cliDirFinal, uiDir: uiDirFinal },
502
+ dirs: {
503
+ uiRepoDir,
504
+ uiDir: uiDirFinal,
505
+ cliRepoDir,
506
+ cliDir: cliDirFinal,
507
+ serverLightRepoDir,
508
+ serverLightDir: getComponentDir(rootDir, 'happy-server-light'),
509
+ serverFullRepoDir,
510
+ serverFullDir,
511
+ },
470
512
  cloned: allowClone,
471
513
  autostart: enableAutostart ? 'enabled' : sandboxed && enableAutostartRaw && !allowGlobal ? 'skipped (sandbox)' : disableAutostart ? 'disabled' : 'unchanged',
472
514
  interactive: Boolean(wizard),
@@ -1,7 +1,6 @@
1
1
  import './utils/env/env.mjs';
2
2
  import { copyFile, mkdir, readFile } from 'node:fs/promises';
3
3
  import { basename, join } from 'node:path';
4
- import { createRequire } from 'node:module';
5
4
 
6
5
  import { parseArgs } from './utils/cli/args.mjs';
7
6
  import { printResult, wantsHelp, wantsJson } from './utils/cli/cli.mjs';
@@ -13,6 +12,7 @@ import { ensureHappyServerManagedInfra, applyHappyServerMigrations } from './uti
13
12
  import { runCapture } from './utils/proc/proc.mjs';
14
13
  import { pickNextFreeTcpPort } from './utils/net/ports.mjs';
15
14
  import { getEnvValue } from './utils/env/values.mjs';
15
+ import { importPrismaClientForHappyServerLight, importPrismaClientFromNodeModules } from './utils/server/prisma_import.mjs';
16
16
 
17
17
  function usage() {
18
18
  return [
@@ -54,14 +54,6 @@ async function ensureTargetSecretMatchesSource({ sourceSecretPath, targetSecretP
54
54
  }
55
55
  }
56
56
 
57
- async function importPrismaClientFrom(dir) {
58
- const req = createRequire(import.meta.url);
59
- const resolved = req.resolve('@prisma/client', { paths: [dir] });
60
- // eslint-disable-next-line import/no-dynamic-require
61
- const mod = req(resolved);
62
- return mod.PrismaClient;
63
- }
64
-
65
57
  async function migrateLightToServer({ rootDir, fromStack, toStack, includeFiles, force, json }) {
66
58
  const from = resolveStackEnvPath(fromStack);
67
59
  const to = resolveStackEnvPath(toStack);
@@ -139,8 +131,8 @@ async function migrateLightToServer({ rootDir, fromStack, toStack, includeFiles,
139
131
  await copyFile(fromParsed.path, snapshotPath);
140
132
  const snapshotDbUrl = `file:${snapshotPath}`;
141
133
 
142
- const SourcePrismaClient = await importPrismaClientFrom(lightDir);
143
- const TargetPrismaClient = await importPrismaClientFrom(fullDir);
134
+ const SourcePrismaClient = await importPrismaClientForHappyServerLight({ serverDir: lightDir });
135
+ const TargetPrismaClient = await importPrismaClientFromNodeModules({ dir: fullDir });
144
136
 
145
137
  const sourceDb = new SourcePrismaClient({ datasources: { db: { url: snapshotDbUrl } } });
146
138
  const targetDb = new TargetPrismaClient({ datasources: { db: { url: infra.env.DATABASE_URL } } });
@@ -289,4 +281,3 @@ main().catch((err) => {
289
281
  console.error(err);
290
282
  process.exit(1);
291
283
  });
292
-