@qpqjs/cli 0.2.1 → 0.4.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 (284) hide show
  1. package/lib/commonjs/cli/commandMenu.js +5 -4
  2. package/lib/commonjs/cli/commandMenu.js.map +1 -1
  3. package/lib/commonjs/cli/runCli.js +15 -7
  4. package/lib/commonjs/cli/runCli.js.map +1 -1
  5. package/lib/commonjs/commands/checkCircular.js +15 -7
  6. package/lib/commonjs/commands/checkCircular.js.map +1 -1
  7. package/lib/commonjs/commands/clearResources.js +8 -14
  8. package/lib/commonjs/commands/clearResources.js.map +1 -1
  9. package/lib/commonjs/commands/go.js +3 -7
  10. package/lib/commonjs/commands/go.js.map +1 -1
  11. package/lib/commonjs/commands/goDev.js +4 -2
  12. package/lib/commonjs/commands/goDev.js.map +1 -1
  13. package/lib/commonjs/commands/goDevApi.js +23 -20
  14. package/lib/commonjs/commands/goDevApi.js.map +1 -1
  15. package/lib/commonjs/commands/goDevWeb.js +13 -8
  16. package/lib/commonjs/commands/goDevWeb.js.map +1 -1
  17. package/lib/commonjs/commands/goDocker.js +10 -13
  18. package/lib/commonjs/commands/goDocker.js.map +1 -1
  19. package/lib/commonjs/commands/hooks.js +4 -2
  20. package/lib/commonjs/commands/hooks.js.map +1 -1
  21. package/lib/commonjs/commands/migrate.js +11 -9
  22. package/lib/commonjs/commands/migrate.js.map +1 -1
  23. package/lib/commonjs/commands/prep.js +4 -2
  24. package/lib/commonjs/commands/prep.js.map +1 -1
  25. package/lib/commonjs/commands/publish.js +4 -7
  26. package/lib/commonjs/commands/publish.js.map +1 -1
  27. package/lib/commonjs/commands/scriptBuild.js +7 -5
  28. package/lib/commonjs/commands/scriptBuild.js.map +1 -1
  29. package/lib/commonjs/commands/scriptBuilderLayer.js +6 -4
  30. package/lib/commonjs/commands/scriptBuilderLayer.js.map +1 -1
  31. package/lib/commonjs/commands/scriptNew.js +7 -5
  32. package/lib/commonjs/commands/scriptNew.js.map +1 -1
  33. package/lib/commonjs/commands/setup.js +14 -22
  34. package/lib/commonjs/commands/setup.js.map +1 -1
  35. package/lib/commonjs/commands/synth.js +16 -20
  36. package/lib/commonjs/commands/synth.js.map +1 -1
  37. package/lib/commonjs/commands/teardown.js +8 -14
  38. package/lib/commonjs/commands/teardown.js.map +1 -1
  39. package/lib/commonjs/lib/CliError.d.ts +4 -0
  40. package/lib/commonjs/lib/CliError.js +14 -0
  41. package/lib/commonjs/lib/CliError.js.map +1 -0
  42. package/lib/commonjs/lib/circularImports.d.ts +0 -1
  43. package/lib/commonjs/lib/circularImports.js +5 -6
  44. package/lib/commonjs/lib/circularImports.js.map +1 -1
  45. package/lib/commonjs/lib/clearResourcesPlan.d.ts +0 -1
  46. package/lib/commonjs/lib/clearResourcesPlan.js +12 -13
  47. package/lib/commonjs/lib/clearResourcesPlan.js.map +1 -1
  48. package/lib/commonjs/lib/deployPrompts.d.ts +2 -1
  49. package/lib/commonjs/lib/deployPrompts.js +16 -16
  50. package/lib/commonjs/lib/deployPrompts.js.map +1 -1
  51. package/lib/commonjs/lib/devServerEntry.js +5 -4
  52. package/lib/commonjs/lib/devServerEntry.js.map +1 -1
  53. package/lib/commonjs/lib/devServerPorts.js +2 -4
  54. package/lib/commonjs/lib/devServerPorts.js.map +1 -1
  55. package/lib/commonjs/lib/discovery.d.ts +0 -2
  56. package/lib/commonjs/lib/discovery.js +11 -44
  57. package/lib/commonjs/lib/discovery.js.map +1 -1
  58. package/lib/commonjs/lib/hooks.js +5 -3
  59. package/lib/commonjs/lib/hooks.js.map +1 -1
  60. package/lib/commonjs/lib/killChildWithEscalation.js +3 -1
  61. package/lib/commonjs/lib/killChildWithEscalation.js.map +1 -1
  62. package/lib/commonjs/lib/killStaleListeners.js +24 -37
  63. package/lib/commonjs/lib/killStaleListeners.js.map +1 -1
  64. package/lib/commonjs/lib/out.d.ts +11 -0
  65. package/lib/commonjs/lib/out.js +30 -0
  66. package/lib/commonjs/lib/out.js.map +1 -0
  67. package/lib/commonjs/lib/prompts.d.ts +1 -0
  68. package/lib/commonjs/lib/prompts.js +15 -2
  69. package/lib/commonjs/lib/prompts.js.map +1 -1
  70. package/lib/commonjs/lib/qpqConfigs.js +4 -4
  71. package/lib/commonjs/lib/qpqConfigs.js.map +1 -1
  72. package/lib/commonjs/lib/requireDriverCapability.d.ts +7 -0
  73. package/lib/commonjs/lib/requireDriverCapability.js +17 -0
  74. package/lib/commonjs/lib/requireDriverCapability.js.map +1 -0
  75. package/lib/commonjs/lib/resolveAppSelection.d.ts +1 -0
  76. package/lib/commonjs/lib/resolveAppSelection.js +19 -12
  77. package/lib/commonjs/lib/resolveAppSelection.js.map +1 -1
  78. package/lib/commonjs/lib/resolveCommandContext.d.ts +12 -0
  79. package/lib/commonjs/lib/resolveCommandContext.js +26 -0
  80. package/lib/commonjs/lib/resolveCommandContext.js.map +1 -0
  81. package/lib/commonjs/lib/resolveDeployment.d.ts +0 -1
  82. package/lib/commonjs/lib/resolveDeployment.js +15 -10
  83. package/lib/commonjs/lib/resolveDeployment.js.map +1 -1
  84. package/lib/commonjs/lib/runCommand.d.ts +20 -0
  85. package/lib/commonjs/lib/runCommand.js +64 -34
  86. package/lib/commonjs/lib/runCommand.js.map +1 -1
  87. package/lib/commonjs/lib/runTasks.js +8 -4
  88. package/lib/commonjs/lib/runTasks.js.map +1 -1
  89. package/lib/commonjs/lib/timing.js +5 -3
  90. package/lib/commonjs/lib/timing.js.map +1 -1
  91. package/lib/commonjs/lib/typeWatcher.d.ts +2 -1
  92. package/lib/commonjs/lib/typeWatcher.js +6 -13
  93. package/lib/commonjs/lib/typeWatcher.js.map +1 -1
  94. package/lib/commonjs/lib/userScriptBuilderLayer.d.ts +0 -2
  95. package/lib/commonjs/lib/userScriptBuilderLayer.js +7 -8
  96. package/lib/commonjs/lib/userScriptBuilderLayer.js.map +1 -1
  97. package/lib/commonjs/lib/views.js +3 -2
  98. package/lib/commonjs/lib/views.js.map +1 -1
  99. package/lib/commonjs/lib/webAddressingEnv.d.ts +0 -2
  100. package/lib/commonjs/lib/webAddressingEnv.js +4 -4
  101. package/lib/commonjs/lib/webAddressingEnv.js.map +1 -1
  102. package/lib/commonjs/platforms/aws/awsCredentials.js +3 -1
  103. package/lib/commonjs/platforms/aws/awsCredentials.js.map +1 -1
  104. package/lib/commonjs/platforms/aws/cdkApp.js +1 -1
  105. package/lib/commonjs/platforms/aws/cdkApp.js.map +1 -1
  106. package/lib/commonjs/platforms/aws/clearResources.js +16 -26
  107. package/lib/commonjs/platforms/aws/clearResources.js.map +1 -1
  108. package/lib/commonjs/platforms/aws/getDeployEnv.d.ts +1 -0
  109. package/lib/commonjs/platforms/aws/getDeployEnv.js +12 -0
  110. package/lib/commonjs/platforms/aws/getDeployEnv.js.map +1 -0
  111. package/lib/commonjs/platforms/aws/go.js +17 -15
  112. package/lib/commonjs/platforms/aws/go.js.map +1 -1
  113. package/lib/commonjs/platforms/aws/goDocker.js +29 -26
  114. package/lib/commonjs/platforms/aws/goDocker.js.map +1 -1
  115. package/lib/commonjs/platforms/aws/goDockerManifest.js +6 -1
  116. package/lib/commonjs/platforms/aws/goDockerManifest.js.map +1 -1
  117. package/lib/commonjs/platforms/aws/publish.js +13 -11
  118. package/lib/commonjs/platforms/aws/publish.js.map +1 -1
  119. package/lib/commonjs/platforms/aws/remote.js +8 -7
  120. package/lib/commonjs/platforms/aws/remote.js.map +1 -1
  121. package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.js +4 -4
  122. package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.js.map +1 -1
  123. package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.js +4 -3
  124. package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.js.map +1 -1
  125. package/lib/commonjs/platforms/aws/stacks.d.ts +0 -1
  126. package/lib/commonjs/platforms/aws/stacks.js +21 -33
  127. package/lib/commonjs/platforms/aws/stacks.js.map +1 -1
  128. package/lib/commonjs/platforms/aws/teardown.js +9 -7
  129. package/lib/commonjs/platforms/aws/teardown.js.map +1 -1
  130. package/lib/commonjs/platforms/aws/viewsSync.js +23 -11
  131. package/lib/commonjs/platforms/aws/viewsSync.js.map +1 -1
  132. package/lib/commonjs/platforms/docker/go.js +20 -18
  133. package/lib/commonjs/platforms/docker/go.js.map +1 -1
  134. package/lib/commonjs/platforms/docker/index.js +5 -4
  135. package/lib/commonjs/platforms/docker/index.js.map +1 -1
  136. package/lib/commonjs/platforms/docker/parseMounts.js +3 -2
  137. package/lib/commonjs/platforms/docker/parseMounts.js.map +1 -1
  138. package/lib/commonjs/platforms/index.js +4 -2
  139. package/lib/commonjs/platforms/index.js.map +1 -1
  140. package/lib/commonjs/platforms/types.d.ts +2 -2
  141. package/lib/commonjs/platforms/types.js.map +1 -1
  142. package/lib/esm/cli/commandMenu.js +5 -4
  143. package/lib/esm/cli/commandMenu.js.map +1 -1
  144. package/lib/esm/cli/runCli.js +15 -7
  145. package/lib/esm/cli/runCli.js.map +1 -1
  146. package/lib/esm/commands/checkCircular.js +15 -7
  147. package/lib/esm/commands/checkCircular.js.map +1 -1
  148. package/lib/esm/commands/clearResources.js +8 -14
  149. package/lib/esm/commands/clearResources.js.map +1 -1
  150. package/lib/esm/commands/go.js +3 -7
  151. package/lib/esm/commands/go.js.map +1 -1
  152. package/lib/esm/commands/goDev.js +4 -2
  153. package/lib/esm/commands/goDev.js.map +1 -1
  154. package/lib/esm/commands/goDevApi.js +21 -18
  155. package/lib/esm/commands/goDevApi.js.map +1 -1
  156. package/lib/esm/commands/goDevWeb.js +12 -8
  157. package/lib/esm/commands/goDevWeb.js.map +1 -1
  158. package/lib/esm/commands/goDocker.js +10 -13
  159. package/lib/esm/commands/goDocker.js.map +1 -1
  160. package/lib/esm/commands/hooks.js +4 -2
  161. package/lib/esm/commands/hooks.js.map +1 -1
  162. package/lib/esm/commands/migrate.js +9 -7
  163. package/lib/esm/commands/migrate.js.map +1 -1
  164. package/lib/esm/commands/prep.js +4 -2
  165. package/lib/esm/commands/prep.js.map +1 -1
  166. package/lib/esm/commands/publish.js +4 -7
  167. package/lib/esm/commands/publish.js.map +1 -1
  168. package/lib/esm/commands/scriptBuild.js +7 -5
  169. package/lib/esm/commands/scriptBuild.js.map +1 -1
  170. package/lib/esm/commands/scriptBuilderLayer.js +6 -4
  171. package/lib/esm/commands/scriptBuilderLayer.js.map +1 -1
  172. package/lib/esm/commands/scriptNew.js +7 -5
  173. package/lib/esm/commands/scriptNew.js.map +1 -1
  174. package/lib/esm/commands/setup.js +14 -22
  175. package/lib/esm/commands/setup.js.map +1 -1
  176. package/lib/esm/commands/synth.js +18 -22
  177. package/lib/esm/commands/synth.js.map +1 -1
  178. package/lib/esm/commands/teardown.js +8 -14
  179. package/lib/esm/commands/teardown.js.map +1 -1
  180. package/lib/esm/lib/CliError.d.ts +4 -0
  181. package/lib/esm/lib/CliError.js +11 -0
  182. package/lib/esm/lib/CliError.js.map +1 -0
  183. package/lib/esm/lib/circularImports.d.ts +0 -1
  184. package/lib/esm/lib/circularImports.js +4 -4
  185. package/lib/esm/lib/circularImports.js.map +1 -1
  186. package/lib/esm/lib/clearResourcesPlan.d.ts +0 -1
  187. package/lib/esm/lib/clearResourcesPlan.js +12 -12
  188. package/lib/esm/lib/clearResourcesPlan.js.map +1 -1
  189. package/lib/esm/lib/deployPrompts.d.ts +2 -1
  190. package/lib/esm/lib/deployPrompts.js +17 -17
  191. package/lib/esm/lib/deployPrompts.js.map +1 -1
  192. package/lib/esm/lib/devServerEntry.js +5 -4
  193. package/lib/esm/lib/devServerEntry.js.map +1 -1
  194. package/lib/esm/lib/devServerPorts.js +2 -3
  195. package/lib/esm/lib/devServerPorts.js.map +1 -1
  196. package/lib/esm/lib/discovery.d.ts +0 -2
  197. package/lib/esm/lib/discovery.js +10 -38
  198. package/lib/esm/lib/discovery.js.map +1 -1
  199. package/lib/esm/lib/hooks.js +5 -3
  200. package/lib/esm/lib/hooks.js.map +1 -1
  201. package/lib/esm/lib/killChildWithEscalation.js +3 -1
  202. package/lib/esm/lib/killChildWithEscalation.js.map +1 -1
  203. package/lib/esm/lib/killStaleListeners.js +24 -37
  204. package/lib/esm/lib/killStaleListeners.js.map +1 -1
  205. package/lib/esm/lib/out.d.ts +11 -0
  206. package/lib/esm/lib/out.js +26 -0
  207. package/lib/esm/lib/out.js.map +1 -0
  208. package/lib/esm/lib/prompts.d.ts +1 -0
  209. package/lib/esm/lib/prompts.js +13 -1
  210. package/lib/esm/lib/prompts.js.map +1 -1
  211. package/lib/esm/lib/qpqConfigs.js +4 -4
  212. package/lib/esm/lib/qpqConfigs.js.map +1 -1
  213. package/lib/esm/lib/requireDriverCapability.d.ts +7 -0
  214. package/lib/esm/lib/requireDriverCapability.js +13 -0
  215. package/lib/esm/lib/requireDriverCapability.js.map +1 -0
  216. package/lib/esm/lib/resolveAppSelection.d.ts +1 -0
  217. package/lib/esm/lib/resolveAppSelection.js +17 -11
  218. package/lib/esm/lib/resolveAppSelection.js.map +1 -1
  219. package/lib/esm/lib/resolveCommandContext.d.ts +12 -0
  220. package/lib/esm/lib/resolveCommandContext.js +12 -0
  221. package/lib/esm/lib/resolveCommandContext.js.map +1 -0
  222. package/lib/esm/lib/resolveDeployment.d.ts +0 -1
  223. package/lib/esm/lib/resolveDeployment.js +13 -8
  224. package/lib/esm/lib/resolveDeployment.js.map +1 -1
  225. package/lib/esm/lib/runCommand.d.ts +20 -0
  226. package/lib/esm/lib/runCommand.js +52 -31
  227. package/lib/esm/lib/runCommand.js.map +1 -1
  228. package/lib/esm/lib/runTasks.js +8 -4
  229. package/lib/esm/lib/runTasks.js.map +1 -1
  230. package/lib/esm/lib/timing.js +5 -3
  231. package/lib/esm/lib/timing.js.map +1 -1
  232. package/lib/esm/lib/typeWatcher.d.ts +2 -1
  233. package/lib/esm/lib/typeWatcher.js +6 -13
  234. package/lib/esm/lib/typeWatcher.js.map +1 -1
  235. package/lib/esm/lib/userScriptBuilderLayer.d.ts +0 -2
  236. package/lib/esm/lib/userScriptBuilderLayer.js +5 -5
  237. package/lib/esm/lib/userScriptBuilderLayer.js.map +1 -1
  238. package/lib/esm/lib/views.js +3 -2
  239. package/lib/esm/lib/views.js.map +1 -1
  240. package/lib/esm/lib/webAddressingEnv.d.ts +0 -2
  241. package/lib/esm/lib/webAddressingEnv.js +1 -1
  242. package/lib/esm/lib/webAddressingEnv.js.map +1 -1
  243. package/lib/esm/platforms/aws/awsCredentials.js +3 -1
  244. package/lib/esm/platforms/aws/awsCredentials.js.map +1 -1
  245. package/lib/esm/platforms/aws/cdkApp.js +1 -1
  246. package/lib/esm/platforms/aws/cdkApp.js.map +1 -1
  247. package/lib/esm/platforms/aws/clearResources.js +16 -25
  248. package/lib/esm/platforms/aws/clearResources.js.map +1 -1
  249. package/lib/esm/platforms/aws/getDeployEnv.d.ts +1 -0
  250. package/lib/esm/platforms/aws/getDeployEnv.js +8 -0
  251. package/lib/esm/platforms/aws/getDeployEnv.js.map +1 -0
  252. package/lib/esm/platforms/aws/go.js +17 -15
  253. package/lib/esm/platforms/aws/go.js.map +1 -1
  254. package/lib/esm/platforms/aws/goDocker.js +29 -26
  255. package/lib/esm/platforms/aws/goDocker.js.map +1 -1
  256. package/lib/esm/platforms/aws/goDockerManifest.js +6 -1
  257. package/lib/esm/platforms/aws/goDockerManifest.js.map +1 -1
  258. package/lib/esm/platforms/aws/publish.js +13 -11
  259. package/lib/esm/platforms/aws/publish.js.map +1 -1
  260. package/lib/esm/platforms/aws/remote.js +9 -7
  261. package/lib/esm/platforms/aws/remote.js.map +1 -1
  262. package/lib/esm/platforms/aws/setup/buildBucketNamesStep.js +2 -2
  263. package/lib/esm/platforms/aws/setup/buildBucketNamesStep.js.map +1 -1
  264. package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.js +4 -3
  265. package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.js.map +1 -1
  266. package/lib/esm/platforms/aws/stacks.d.ts +0 -1
  267. package/lib/esm/platforms/aws/stacks.js +22 -30
  268. package/lib/esm/platforms/aws/stacks.js.map +1 -1
  269. package/lib/esm/platforms/aws/teardown.js +9 -7
  270. package/lib/esm/platforms/aws/teardown.js.map +1 -1
  271. package/lib/esm/platforms/aws/viewsSync.js +22 -10
  272. package/lib/esm/platforms/aws/viewsSync.js.map +1 -1
  273. package/lib/esm/platforms/docker/go.js +21 -19
  274. package/lib/esm/platforms/docker/go.js.map +1 -1
  275. package/lib/esm/platforms/docker/index.js +5 -4
  276. package/lib/esm/platforms/docker/index.js.map +1 -1
  277. package/lib/esm/platforms/docker/parseMounts.js +3 -2
  278. package/lib/esm/platforms/docker/parseMounts.js.map +1 -1
  279. package/lib/esm/platforms/index.js +4 -2
  280. package/lib/esm/platforms/index.js.map +1 -1
  281. package/lib/esm/platforms/types.d.ts +2 -2
  282. package/lib/esm/platforms/types.js.map +1 -1
  283. package/package.json +28 -15
  284. package/templates/user-script/README.md +1 -1
@@ -4,15 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.killStaleListeners = exports.reportPortHolders = exports.killOtherQpqDevProcesses = exports.isQpqCliCommand = void 0;
7
- // Pre-start sweep shared by the dev-server commands: a previous run whose
8
- // wrapper died without killing its listener (closed terminal, SIGKILL) leaves
9
- // an orphaned process holding a dev port, and the next launch dies with
10
- // EADDRINUSE. Before starting, kill any listener on the given ports whose
11
- // command line matches `isOurs`. Anything else on the ports is left alone
12
- // and reported — it's not ours.
13
- const child_process_1 = require("child_process");
7
+ const runCommand_1 = require("./runCommand");
14
8
  const fs_1 = __importDefault(require("fs"));
15
9
  const path_1 = __importDefault(require("path"));
10
+ const out_1 = require("./out");
11
+ const out = (0, out_1.createOutput)('kill-stale-listeners');
16
12
  // Matches any invocation of the qpq CLI itself, regardless of how it was
17
13
  // launched: `node node_modules/.bin/qpq ...` (the npm-script shim, which has
18
14
  // no .js extension), a direct `node .../lib/commonjs/bin/qpq.js` call, a
@@ -28,22 +24,21 @@ const isQpqCliCommand = (command) => command
28
24
  return withoutExt === 'qpq';
29
25
  });
30
26
  exports.isQpqCliCommand = isQpqCliCommand;
31
- // SIGTERM only requests termination — the caller (about to bind this same
27
+ // SIGTERM only requests termination, the caller (about to bind this same
32
28
  // process's ports itself) needs it actually gone first. A stale dev server
33
29
  // can be mid-startup when killed (still resolving config, not yet bound to
34
30
  // every port it eventually binds) and keep running for a bit past the
35
- // signal, going on to bind a LATER port — waiting for just the one port that
31
+ // signal, going on to bind a LATER port, waiting for just the one port that
36
32
  // matched isn't enough; wait for the whole pid to disappear so it can't bind
37
33
  // anything else afterward. Poll synchronously (this whole sweep runs before
38
- // anything async starts) via `kill(pid, 0)` — reliable and fast here because
39
- // these are unrelated, previously-orphaned processes, not children of this
34
+ // anything async starts) via `kill(pid, 0)`, reliable and fast here because
35
+ // these are unrelated processes orphaned earlier, not children of this
40
36
  // one (a real child can sit as a zombie answering `kill(pid, 0)` until this
41
37
  // process's own event loop reaps it, which a tight sync loop would starve;
42
38
  // doesn't apply to a process we didn't spawn).
43
39
  //
44
40
  // The default covers the dev server's graceful shutdown (drain in-flight work,
45
- // then checkpoint the stores) with room to spare — it used to be 3s, which was
46
- // fine when the server died instantly and is not now. Returns whether the pid
41
+ // then checkpoint the stores) with room to spare. Returns whether the pid
47
42
  // actually went, because a caller about to bind these ports has to escalate
48
43
  // rather than hope: giving up while the old server still holds 8080 is exactly
49
44
  // the EADDRINUSE this file exists to prevent.
@@ -56,13 +51,13 @@ const waitForPidGone = (pid, timeoutMs = 8000) => {
56
51
  catch (_a) {
57
52
  return true; // throws once the pid no longer exists
58
53
  }
59
- (0, child_process_1.execSync)('sleep 0.05');
54
+ (0, runCommand_1.sleepSync)(50);
60
55
  }
61
56
  return false;
62
57
  };
63
58
  // Last resort for a process that ignored, or wedged during, its graceful stop.
64
59
  const killPidHard = (pid) => {
65
- console.warn(`Process ${pid} did not exit gracefully — sending SIGKILL.`);
60
+ out.warn(`Process ${pid} did not exit gracefully — sending SIGKILL.`);
66
61
  try {
67
62
  process.kill(pid, 'SIGKILL');
68
63
  }
@@ -77,8 +72,7 @@ const killPidHard = (pid) => {
77
72
  // itself a symlink (a symlinked home directory, an NFS mount, etc).
78
73
  const getProcessCwd = (pid) => {
79
74
  try {
80
- const line = (0, child_process_1.execSync)(`lsof -a -p ${pid} -d cwd -Fn`, { stdio: ['ignore', 'pipe', 'ignore'] })
81
- .toString()
75
+ const line = (0, runCommand_1.runCommandCaptureSync)('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'])
82
76
  .split('\n')
83
77
  .find((l) => l.startsWith('n'));
84
78
  return line ? fs_1.default.realpathSync(line.slice(1)) : null;
@@ -91,7 +85,7 @@ const getProcessCwd = (pid) => {
91
85
  // `npx qpq go:dev:api` (or via any shell whose -c string names qpq), the
92
86
  // ancestor chain (`sh -c "npx ... qpq ..."`, `npm exec qpq ...`) matches
93
87
  // isQpqCliCommand and shares our cwd, so excluding just process.pid isn't
94
- // enough — SIGINTing an ancestor tears down our own tree. Walk ppid to the
88
+ // enough, SIGINTing an ancestor tears down our own tree. Walk ppid to the
95
89
  // top and exclude the whole chain.
96
90
  const getOwnAncestorPids = () => {
97
91
  const ancestors = new Set();
@@ -99,9 +93,7 @@ const getOwnAncestorPids = () => {
99
93
  while (pid > 1 && !ancestors.has(pid)) {
100
94
  ancestors.add(pid);
101
95
  try {
102
- pid = Number((0, child_process_1.execSync)(`ps -o ppid= -p ${pid}`, { stdio: ['ignore', 'pipe', 'ignore'] })
103
- .toString()
104
- .trim());
96
+ pid = Number((0, runCommand_1.runCommandCaptureSync)('ps', ['-o', 'ppid=', '-p', String(pid)]).trim());
105
97
  }
106
98
  catch (_a) {
107
99
  break;
@@ -110,11 +102,11 @@ const getOwnAncestorPids = () => {
110
102
  return ancestors;
111
103
  };
112
104
  // killStaleListeners only sees processes currently BOUND to one of the given
113
- // ports — it can't catch a still-running `qpq go:dev*` watcher whose spawned
105
+ // ports, it can't catch a still-running `qpq go:dev*` watcher whose spawned
114
106
  // dev-server child already died (a crash, or us killing just the child):
115
107
  // the watcher itself holds no port at that moment, so it's invisible to a
116
- // port scan, yet it's still alive and can spawn a fresh child — and bind a
117
- // port — moments later, racing whatever we're about to start. Close that gap
108
+ // port scan, yet it's still alive and can spawn a fresh child, and bind a
109
+ // port, moments later, racing whatever we're about to start. Close that gap
118
110
  // by finding any OTHER qpq CLI process rooted in the same repo checkout
119
111
  // (matched by cwd, not by port or command-line args, which vary by how it
120
112
  // was invoked) and killing it outright, regardless of what it currently has
@@ -125,9 +117,7 @@ const killOtherQpqDevProcesses = (root) => {
125
117
  const ownPids = getOwnAncestorPids();
126
118
  let lines = [];
127
119
  try {
128
- lines = (0, child_process_1.execSync)('ps -axo pid=,command=', { stdio: ['ignore', 'pipe', 'ignore'] })
129
- .toString()
130
- .split('\n');
120
+ lines = (0, runCommand_1.runCommandCaptureSync)('ps', ['-axo', 'pid=,command=']).split('\n');
131
121
  }
132
122
  catch (_a) {
133
123
  return;
@@ -145,7 +135,7 @@ const killOtherQpqDevProcesses = (root) => {
145
135
  continue;
146
136
  if (getProcessCwd(pid) !== resolvedRoot)
147
137
  continue;
148
- console.log(`Killing other qpq dev process for this repo (pid ${pid}): ${command}`);
138
+ out.log(`Killing other qpq dev process for this repo (pid ${pid}): ${command}`);
149
139
  process.kill(pid, 'SIGINT');
150
140
  if (!waitForPidGone(pid)) {
151
141
  killPidHard(pid);
@@ -158,10 +148,7 @@ exports.killOtherQpqDevProcesses = killOtherQpqDevProcesses;
158
148
  const getPortListeners = (port) => {
159
149
  let pids = [];
160
150
  try {
161
- pids = (0, child_process_1.execSync)(`lsof -t -iTCP:${port} -sTCP:LISTEN`, {
162
- stdio: ['ignore', 'pipe', 'ignore'],
163
- })
164
- .toString()
151
+ pids = (0, runCommand_1.runCommandCaptureSync)('lsof', ['-t', `-iTCP:${port}`, '-sTCP:LISTEN'])
165
152
  .split('\n')
166
153
  .map((line) => Number(line.trim()))
167
154
  .filter(Boolean);
@@ -171,7 +158,7 @@ const getPortListeners = (port) => {
171
158
  }
172
159
  return pids.flatMap((pid) => {
173
160
  try {
174
- return [{ pid, command: (0, child_process_1.execSync)(`ps -p ${pid} -o command=`).toString().trim() }];
161
+ return [{ pid, command: (0, runCommand_1.runCommandCaptureSync)('ps', ['-p', String(pid), '-o', 'command=']).trim() }];
175
162
  }
176
163
  catch (_a) {
177
164
  return []; // already gone
@@ -194,8 +181,8 @@ const getPortListeners = (port) => {
194
181
  const reportPortHolders = (ports) => {
195
182
  for (const port of ports) {
196
183
  for (const { pid, command } of getPortListeners(port)) {
197
- console.warn(`Port ${port} is currently held by pid ${pid}: ${command}`);
198
- console.warn(` Starting anyway. If it still holds port ${port} once this server binds, startup will fail with EADDRINUSE.`);
184
+ out.warn(`Port ${port} is currently held by pid ${pid}: ${command}`);
185
+ out.warn(` Starting anyway. If it still holds port ${port} once this server binds, startup will fail with EADDRINUSE.`);
199
186
  }
200
187
  }
201
188
  };
@@ -204,14 +191,14 @@ const killStaleListeners = (ports, isOurs) => {
204
191
  for (const port of ports) {
205
192
  for (const { pid, command } of getPortListeners(port)) {
206
193
  if (isOurs(command)) {
207
- console.log(`Killing stale dev server on port ${port} (pid ${pid})`);
194
+ out.log(`Killing stale dev server on port ${port} (pid ${pid})`);
208
195
  process.kill(pid);
209
196
  if (!waitForPidGone(pid)) {
210
197
  killPidHard(pid);
211
198
  }
212
199
  }
213
200
  else {
214
- console.warn(`Port ${port} is in use by an unrelated process (pid ${pid}: ${command}) — not killing it.`);
201
+ out.warn(`Port ${port} is in use by an unrelated process (pid ${pid}: ${command}) — not killing it.`);
215
202
  }
216
203
  }
217
204
  }
@@ -1 +1 @@
1
- {"version":3,"file":"killStaleListeners.js","sourceRoot":"","sources":["../../../src/lib/killStaleListeners.ts"],"names":[],"mappings":"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","sourcesContent":["// Pre-start sweep shared by the dev-server commands: a previous run whose\n// wrapper died without killing its listener (closed terminal, SIGKILL) leaves\n// an orphaned process holding a dev port, and the next launch dies with\n// EADDRINUSE. Before starting, kill any listener on the given ports whose\n// command line matches `isOurs`. Anything else on the ports is left alone\n// and reported — it's not ours.\nimport { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\n\n// Matches any invocation of the qpq CLI itself, regardless of how it was\n// launched: `node node_modules/.bin/qpq ...` (the npm-script shim, which has\n// no .js extension), a direct `node .../lib/commonjs/bin/qpq.js` call, a\n// global `qpq` shebang script, etc. Checks each whitespace-separated token's\n// basename (extension stripped) rather than a fixed substring like\n// 'bin/qpq.js', which only matches the last of those forms.\nexport const isQpqCliCommand = (command: string): boolean =>\n command\n .trim()\n .split(/\\s+/)\n .some((token) => {\n const base = path.basename(token);\n const withoutExt = base.endsWith('.js') ? base.slice(0, -3) : base;\n return withoutExt === 'qpq';\n });\n\n// SIGTERM only requests termination — the caller (about to bind this same\n// process's ports itself) needs it actually gone first. A stale dev server\n// can be mid-startup when killed (still resolving config, not yet bound to\n// every port it eventually binds) and keep running for a bit past the\n// signal, going on to bind a LATER port — waiting for just the one port that\n// matched isn't enough; wait for the whole pid to disappear so it can't bind\n// anything else afterward. Poll synchronously (this whole sweep runs before\n// anything async starts) via `kill(pid, 0)` — reliable and fast here because\n// these are unrelated, previously-orphaned processes, not children of this\n// one (a real child can sit as a zombie answering `kill(pid, 0)` until this\n// process's own event loop reaps it, which a tight sync loop would starve;\n// doesn't apply to a process we didn't spawn).\n//\n// The default covers the dev server's graceful shutdown (drain in-flight work,\n// then checkpoint the stores) with room to spare — it used to be 3s, which was\n// fine when the server died instantly and is not now. Returns whether the pid\n// actually went, because a caller about to bind these ports has to escalate\n// rather than hope: giving up while the old server still holds 8080 is exactly\n// the EADDRINUSE this file exists to prevent.\nconst waitForPidGone = (pid: number, timeoutMs = 8000): boolean => {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n process.kill(pid, 0);\n } catch {\n return true; // throws once the pid no longer exists\n }\n execSync('sleep 0.05');\n }\n\n return false;\n};\n\n// Last resort for a process that ignored, or wedged during, its graceful stop.\nconst killPidHard = (pid: number): void => {\n console.warn(`Process ${pid} did not exit gracefully — sending SIGKILL.`);\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n return; // raced us and exited after all\n }\n waitForPidGone(pid, 2000);\n};\n\n// realpath'd: lsof reports the canonical path (e.g. /private/tmp on macOS,\n// where /tmp is a symlink), so comparing against a caller-supplied root\n// verbatim would false-negative whenever any component of the repo path is\n// itself a symlink (a symlinked home directory, an NFS mount, etc).\nconst getProcessCwd = (pid: number): string | null => {\n try {\n const line = execSync(`lsof -a -p ${pid} -d cwd -Fn`, { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .split('\\n')\n .find((l) => l.startsWith('n'));\n return line ? fs.realpathSync(line.slice(1)) : null;\n } catch {\n return null; // pid gone, or we don't have permission to inspect it\n }\n};\n\n// The sweep below must never target this process's own wrappers: launched as\n// `npx qpq go:dev:api` (or via any shell whose -c string names qpq), the\n// ancestor chain (`sh -c \"npx ... qpq ...\"`, `npm exec qpq ...`) matches\n// isQpqCliCommand and shares our cwd, so excluding just process.pid isn't\n// enough — SIGINTing an ancestor tears down our own tree. Walk ppid to the\n// top and exclude the whole chain.\nconst getOwnAncestorPids = (): Set<number> => {\n const ancestors = new Set<number>();\n let pid = process.pid;\n while (pid > 1 && !ancestors.has(pid)) {\n ancestors.add(pid);\n try {\n pid = Number(\n execSync(`ps -o ppid= -p ${pid}`, { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim(),\n );\n } catch {\n break;\n }\n }\n return ancestors;\n};\n\n// killStaleListeners only sees processes currently BOUND to one of the given\n// ports — it can't catch a still-running `qpq go:dev*` watcher whose spawned\n// dev-server child already died (a crash, or us killing just the child):\n// the watcher itself holds no port at that moment, so it's invisible to a\n// port scan, yet it's still alive and can spawn a fresh child — and bind a\n// port — moments later, racing whatever we're about to start. Close that gap\n// by finding any OTHER qpq CLI process rooted in the same repo checkout\n// (matched by cwd, not by port or command-line args, which vary by how it\n// was invoked) and killing it outright, regardless of what it currently has\n// bound. SIGINT (not SIGTERM) so goDevApiCommand/goDevWebCommand's own\n// handler runs and tears down its child cleanly rather than orphaning it.\nexport const killOtherQpqDevProcesses = (root: string): void => {\n const resolvedRoot = fs.realpathSync(root);\n const ownPids = getOwnAncestorPids();\n\n let lines: string[] = [];\n try {\n lines = execSync('ps -axo pid=,command=', { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .split('\\n');\n } catch {\n return;\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n const spaceIndex = trimmed.indexOf(' ');\n if (spaceIndex === -1) continue;\n\n const pid = Number(trimmed.slice(0, spaceIndex));\n const command = trimmed.slice(spaceIndex + 1).trim();\n\n if (!pid || ownPids.has(pid) || !isQpqCliCommand(command)) continue;\n if (getProcessCwd(pid) !== resolvedRoot) continue;\n\n console.log(`Killing other qpq dev process for this repo (pid ${pid}): ${command}`);\n process.kill(pid, 'SIGINT');\n if (!waitForPidGone(pid)) {\n killPidHard(pid);\n }\n }\n};\n\n// Whatever is currently listening on a port, as pid + command line. Empty when\n// the port is free, or when lsof is unavailable.\nconst getPortListeners = (port: number): { pid: number; command: string }[] => {\n let pids: number[] = [];\n try {\n pids = execSync(`lsof -t -iTCP:${port} -sTCP:LISTEN`, {\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .split('\\n')\n .map((line) => Number(line.trim()))\n .filter(Boolean);\n } catch {\n return []; // lsof exits non-zero when nothing is listening\n }\n\n return pids.flatMap((pid) => {\n try {\n return [{ pid, command: execSync(`ps -p ${pid} -o command=`).toString().trim() }];\n } catch {\n return []; // already gone\n }\n });\n};\n\n/**\n * Say what is holding these ports, without touching it.\n *\n * The other half of --keep-other-dev-servers: sparing a sibling checkout's dev\n * server means this one cannot bind, and an unexplained EADDRINUSE is a worse\n * experience than the silent kill it replaces. Naming the pid and the command\n * turns \"it just died\" into something you can act on.\n *\n * Worded as a prediction, not a fact, because it IS one: this runs before the\n * bundle step, so by the time anything binds - seconds later - the holder may\n * well have gone. Stating \"port in use\" and then starting cleanly reads like\n * the warning was wrong.\n */\nexport const reportPortHolders = (ports: number[]): void => {\n for (const port of ports) {\n for (const { pid, command } of getPortListeners(port)) {\n console.warn(`Port ${port} is currently held by pid ${pid}: ${command}`);\n console.warn(` Starting anyway. If it still holds port ${port} once this server binds, startup will fail with EADDRINUSE.`);\n }\n }\n};\n\nexport const killStaleListeners = (ports: number[], isOurs: (command: string) => boolean): void => {\n for (const port of ports) {\n for (const { pid, command } of getPortListeners(port)) {\n if (isOurs(command)) {\n console.log(`Killing stale dev server on port ${port} (pid ${pid})`);\n process.kill(pid);\n if (!waitForPidGone(pid)) {\n killPidHard(pid);\n }\n } else {\n console.warn(`Port ${port} is in use by an unrelated process (pid ${pid}: ${command}) — not killing it.`);\n }\n }\n }\n};\n"]}
1
+ {"version":3,"file":"killStaleListeners.js","sourceRoot":"","sources":["../../../src/lib/killStaleListeners.ts"],"names":[],"mappings":";;;;;;AAAA,6CAAgE;AAShE,4CAAoB;AACpB,gDAAwB;AACxB,+BAAqC;AAErC,MAAM,GAAG,GAAG,IAAA,kBAAY,EAAC,sBAAsB,CAAC,CAAC;AAEjD,yEAAyE;AACzE,6EAA6E;AAC7E,yEAAyE;AACzE,6EAA6E;AAC7E,mEAAmE;AACnE,4DAA4D;AACrD,MAAM,eAAe,GAAG,CAAC,OAAe,EAAW,EAAE,CAC1D,OAAO;KACJ,IAAI,EAAE;KACN,KAAK,CAAC,KAAK,CAAC;KACZ,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;IACd,MAAM,IAAI,GAAG,cAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACnE,OAAO,UAAU,KAAK,KAAK,CAAC;AAC9B,CAAC,CAAC,CAAC;AARM,QAAA,eAAe,mBAQrB;AAEP,yEAAyE;AACzE,2EAA2E;AAC3E,2EAA2E;AAC3E,sEAAsE;AACtE,4EAA4E;AAC5E,6EAA6E;AAC7E,4EAA4E;AAC5E,4EAA4E;AAC5E,uEAAuE;AACvE,4EAA4E;AAC5E,2EAA2E;AAC3E,+CAA+C;AAC/C,EAAE;AACF,+EAA+E;AAC/E,0EAA0E;AAC1E,4EAA4E;AAC5E,+EAA+E;AAC/E,8CAA8C;AAC9C,MAAM,cAAc,GAAG,CAAC,GAAW,EAAE,SAAS,GAAG,IAAI,EAAW,EAAE;IAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;IACxC,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACvB,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,IAAI,CAAC,CAAC,uCAAuC;QACtD,CAAC;QACD,IAAA,sBAAS,EAAC,EAAE,CAAC,CAAC;IAChB,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,+EAA+E;AAC/E,MAAM,WAAW,GAAG,CAAC,GAAW,EAAQ,EAAE;IACxC,GAAG,CAAC,IAAI,CAAC,WAAW,GAAG,6CAA6C,CAAC,CAAC;IACtE,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;IAC/B,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,CAAC,gCAAgC;IAC1C,CAAC;IACD,cAAc,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,2EAA2E;AAC3E,wEAAwE;AACxE,2EAA2E;AAC3E,oEAAoE;AACpE,MAAM,aAAa,GAAG,CAAC,GAAW,EAAoB,EAAE;IACtD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAA,kCAAqB,EAAC,MAAM,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;aACtF,KAAK,CAAC,IAAI,CAAC;aACX,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC;QAClC,OAAO,IAAI,CAAC,CAAC,CAAC,YAAE,CAAC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACtD,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,IAAI,CAAC,CAAC,sDAAsD;IACrE,CAAC;AACH,CAAC,CAAC;AAEF,6EAA6E;AAC7E,yEAAyE;AACzE,yEAAyE;AACzE,0EAA0E;AAC1E,0EAA0E;AAC1E,mCAAmC;AACnC,MAAM,kBAAkB,GAAG,GAAgB,EAAE;IAC3C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IACpC,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IACtB,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACnB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,CAAC,IAAA,kCAAqB,EAAC,IAAI,EAAE,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACvF,CAAC;QAAC,WAAM,CAAC;YACP,MAAM;QACR,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAEF,6EAA6E;AAC7E,4EAA4E;AAC5E,yEAAyE;AACzE,0EAA0E;AAC1E,0EAA0E;AAC1E,4EAA4E;AAC5E,wEAAwE;AACxE,0EAA0E;AAC1E,4EAA4E;AAC5E,uEAAuE;AACvE,0EAA0E;AACnE,MAAM,wBAAwB,GAAG,CAAC,IAAY,EAAQ,EAAE;IAC7D,MAAM,YAAY,GAAG,YAAE,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;IAErC,IAAI,KAAK,GAAa,EAAE,CAAC;IACzB,IAAI,CAAC;QACH,KAAK,GAAG,IAAA,kCAAqB,EAAC,IAAI,EAAE,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC7E,CAAC;IAAC,WAAM,CAAC;QACP,OAAO;IACT,CAAC;IAED,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QAEvB,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,UAAU,KAAK,CAAC,CAAC;YAAE,SAAS;QAEhC,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAErD,IAAI,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,IAAA,uBAAe,EAAC,OAAO,CAAC;YAAE,SAAS;QACpE,IAAI,aAAa,CAAC,GAAG,CAAC,KAAK,YAAY;YAAE,SAAS;QAElD,GAAG,CAAC,GAAG,CAAC,oDAAoD,GAAG,MAAM,OAAO,EAAE,CAAC,CAAC;QAChF,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC5B,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;YACzB,WAAW,CAAC,GAAG,CAAC,CAAC;QACnB,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AA9BW,QAAA,wBAAwB,4BA8BnC;AAEF,+EAA+E;AAC/E,iDAAiD;AACjD,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAsC,EAAE;IAC5E,IAAI,IAAI,GAAa,EAAE,CAAC;IACxB,IAAI,CAAC;QACH,IAAI,GAAG,IAAA,kCAAqB,EAAC,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,IAAI,EAAE,EAAE,cAAc,CAAC,CAAC;aAC1E,KAAK,CAAC,IAAI,CAAC;aACX,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;aAClC,MAAM,CAAC,OAAO,CAAC,CAAC;IACrB,CAAC;IAAC,WAAM,CAAC;QACP,OAAO,EAAE,CAAC,CAAC,gDAAgD;IAC7D,CAAC;IAED,OAAO,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAC1B,IAAI,CAAC;YACH,OAAO,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,IAAA,kCAAqB,EAAC,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvG,CAAC;QAAC,WAAM,CAAC;YACP,OAAO,EAAE,CAAC,CAAC,eAAe;QAC5B,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC;AAEF;;;;;;;;;;;;GAYG;AACI,MAAM,iBAAiB,GAAG,CAAC,KAAe,EAAQ,EAAE;IACzD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;YACtD,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,6BAA6B,GAAG,KAAK,OAAO,EAAE,CAAC,CAAC;YACrE,GAAG,CAAC,IAAI,CAAC,6CAA6C,IAAI,6DAA6D,CAAC,CAAC;QAC3H,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAPW,QAAA,iBAAiB,qBAO5B;AAEK,MAAM,kBAAkB,GAAG,CAAC,KAAe,EAAE,MAAoC,EAAQ,EAAE;IAChG,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,MAAM,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC;YACtD,IAAI,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;gBACpB,GAAG,CAAC,GAAG,CAAC,oCAAoC,IAAI,SAAS,GAAG,GAAG,CAAC,CAAC;gBACjE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;gBAClB,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC;oBACzB,WAAW,CAAC,GAAG,CAAC,CAAC;gBACnB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,IAAI,CAAC,QAAQ,IAAI,2CAA2C,GAAG,KAAK,OAAO,qBAAqB,CAAC,CAAC;YACxG,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC,CAAC;AAdW,QAAA,kBAAkB,sBAc7B","sourcesContent":["import { runCommandCaptureSync, sleepSync } from './runCommand';\n// Pre-start sweep shared by the dev-server commands: a previous run whose\n// wrapper died without killing its listener (closed terminal, SIGKILL) leaves\n// an orphaned process holding a dev port, and the next launch dies with\n// EADDRINUSE. Before starting, kill any listener on the given ports whose\n// command line matches `isOurs`. Anything else on the ports is left alone\n// and reported, it's not ours.\nimport { Nullable } from '@qpqjs/core';\n\nimport fs from 'fs';\nimport path from 'path';\nimport { createOutput } from './out';\n\nconst out = createOutput('kill-stale-listeners');\n\n// Matches any invocation of the qpq CLI itself, regardless of how it was\n// launched: `node node_modules/.bin/qpq ...` (the npm-script shim, which has\n// no .js extension), a direct `node .../lib/commonjs/bin/qpq.js` call, a\n// global `qpq` shebang script, etc. Checks each whitespace-separated token's\n// basename (extension stripped) rather than a fixed substring like\n// 'bin/qpq.js', which only matches the last of those forms.\nexport const isQpqCliCommand = (command: string): boolean =>\n command\n .trim()\n .split(/\\s+/)\n .some((token) => {\n const base = path.basename(token);\n const withoutExt = base.endsWith('.js') ? base.slice(0, -3) : base;\n return withoutExt === 'qpq';\n });\n\n// SIGTERM only requests termination, the caller (about to bind this same\n// process's ports itself) needs it actually gone first. A stale dev server\n// can be mid-startup when killed (still resolving config, not yet bound to\n// every port it eventually binds) and keep running for a bit past the\n// signal, going on to bind a LATER port, waiting for just the one port that\n// matched isn't enough; wait for the whole pid to disappear so it can't bind\n// anything else afterward. Poll synchronously (this whole sweep runs before\n// anything async starts) via `kill(pid, 0)`, reliable and fast here because\n// these are unrelated processes orphaned earlier, not children of this\n// one (a real child can sit as a zombie answering `kill(pid, 0)` until this\n// process's own event loop reaps it, which a tight sync loop would starve;\n// doesn't apply to a process we didn't spawn).\n//\n// The default covers the dev server's graceful shutdown (drain in-flight work,\n// then checkpoint the stores) with room to spare. Returns whether the pid\n// actually went, because a caller about to bind these ports has to escalate\n// rather than hope: giving up while the old server still holds 8080 is exactly\n// the EADDRINUSE this file exists to prevent.\nconst waitForPidGone = (pid: number, timeoutMs = 8000): boolean => {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n process.kill(pid, 0);\n } catch {\n return true; // throws once the pid no longer exists\n }\n sleepSync(50);\n }\n\n return false;\n};\n\n// Last resort for a process that ignored, or wedged during, its graceful stop.\nconst killPidHard = (pid: number): void => {\n out.warn(`Process ${pid} did not exit gracefully — sending SIGKILL.`);\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n return; // raced us and exited after all\n }\n waitForPidGone(pid, 2000);\n};\n\n// realpath'd: lsof reports the canonical path (e.g. /private/tmp on macOS,\n// where /tmp is a symlink), so comparing against a caller-supplied root\n// verbatim would false-negative whenever any component of the repo path is\n// itself a symlink (a symlinked home directory, an NFS mount, etc).\nconst getProcessCwd = (pid: number): Nullable<string> => {\n try {\n const line = runCommandCaptureSync('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'])\n .split('\\n')\n .find((l) => l.startsWith('n'));\n return line ? fs.realpathSync(line.slice(1)) : null;\n } catch {\n return null; // pid gone, or we don't have permission to inspect it\n }\n};\n\n// The sweep below must never target this process's own wrappers: launched as\n// `npx qpq go:dev:api` (or via any shell whose -c string names qpq), the\n// ancestor chain (`sh -c \"npx ... qpq ...\"`, `npm exec qpq ...`) matches\n// isQpqCliCommand and shares our cwd, so excluding just process.pid isn't\n// enough, SIGINTing an ancestor tears down our own tree. Walk ppid to the\n// top and exclude the whole chain.\nconst getOwnAncestorPids = (): Set<number> => {\n const ancestors = new Set<number>();\n let pid = process.pid;\n while (pid > 1 && !ancestors.has(pid)) {\n ancestors.add(pid);\n try {\n pid = Number(runCommandCaptureSync('ps', ['-o', 'ppid=', '-p', String(pid)]).trim());\n } catch {\n break;\n }\n }\n return ancestors;\n};\n\n// killStaleListeners only sees processes currently BOUND to one of the given\n// ports, it can't catch a still-running `qpq go:dev*` watcher whose spawned\n// dev-server child already died (a crash, or us killing just the child):\n// the watcher itself holds no port at that moment, so it's invisible to a\n// port scan, yet it's still alive and can spawn a fresh child, and bind a\n// port, moments later, racing whatever we're about to start. Close that gap\n// by finding any OTHER qpq CLI process rooted in the same repo checkout\n// (matched by cwd, not by port or command-line args, which vary by how it\n// was invoked) and killing it outright, regardless of what it currently has\n// bound. SIGINT (not SIGTERM) so goDevApiCommand/goDevWebCommand's own\n// handler runs and tears down its child cleanly rather than orphaning it.\nexport const killOtherQpqDevProcesses = (root: string): void => {\n const resolvedRoot = fs.realpathSync(root);\n const ownPids = getOwnAncestorPids();\n\n let lines: string[] = [];\n try {\n lines = runCommandCaptureSync('ps', ['-axo', 'pid=,command=']).split('\\n');\n } catch {\n return;\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n const spaceIndex = trimmed.indexOf(' ');\n if (spaceIndex === -1) continue;\n\n const pid = Number(trimmed.slice(0, spaceIndex));\n const command = trimmed.slice(spaceIndex + 1).trim();\n\n if (!pid || ownPids.has(pid) || !isQpqCliCommand(command)) continue;\n if (getProcessCwd(pid) !== resolvedRoot) continue;\n\n out.log(`Killing other qpq dev process for this repo (pid ${pid}): ${command}`);\n process.kill(pid, 'SIGINT');\n if (!waitForPidGone(pid)) {\n killPidHard(pid);\n }\n }\n};\n\n// Whatever is currently listening on a port, as pid + command line. Empty when\n// the port is free, or when lsof is unavailable.\nconst getPortListeners = (port: number): { pid: number; command: string }[] => {\n let pids: number[] = [];\n try {\n pids = runCommandCaptureSync('lsof', ['-t', `-iTCP:${port}`, '-sTCP:LISTEN'])\n .split('\\n')\n .map((line) => Number(line.trim()))\n .filter(Boolean);\n } catch {\n return []; // lsof exits non-zero when nothing is listening\n }\n\n return pids.flatMap((pid) => {\n try {\n return [{ pid, command: runCommandCaptureSync('ps', ['-p', String(pid), '-o', 'command=']).trim() }];\n } catch {\n return []; // already gone\n }\n });\n};\n\n/**\n * Say what is holding these ports, without touching it.\n *\n * The other half of --keep-other-dev-servers: sparing a sibling checkout's dev\n * server means this one cannot bind, and an unexplained EADDRINUSE is a worse\n * experience than the silent kill it replaces. Naming the pid and the command\n * turns \"it just died\" into something you can act on.\n *\n * Worded as a prediction, not a fact, because it IS one: this runs before the\n * bundle step, so by the time anything binds - seconds later - the holder may\n * well have gone. Stating \"port in use\" and then starting cleanly reads like\n * the warning was wrong.\n */\nexport const reportPortHolders = (ports: number[]): void => {\n for (const port of ports) {\n for (const { pid, command } of getPortListeners(port)) {\n out.warn(`Port ${port} is currently held by pid ${pid}: ${command}`);\n out.warn(` Starting anyway. If it still holds port ${port} once this server binds, startup will fail with EADDRINUSE.`);\n }\n }\n};\n\nexport const killStaleListeners = (ports: number[], isOurs: (command: string) => boolean): void => {\n for (const port of ports) {\n for (const { pid, command } of getPortListeners(port)) {\n if (isOurs(command)) {\n out.log(`Killing stale dev server on port ${port} (pid ${pid})`);\n process.kill(pid);\n if (!waitForPidGone(pid)) {\n killPidHard(pid);\n }\n } else {\n out.warn(`Port ${port} is in use by an unrelated process (pid ${pid}: ${command}) — not killing it.`);\n }\n }\n }\n};\n"]}
@@ -0,0 +1,11 @@
1
+ type Print = (...parts: unknown[]) => void;
2
+ export type Output = {
3
+ log: Print;
4
+ info: Print;
5
+ warn: Print;
6
+ error: Print;
7
+ time: (label: string) => void;
8
+ timeEnd: (label: string) => void;
9
+ };
10
+ export declare const createOutput: (area: string) => Output;
11
+ export {};
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createOutput = void 0;
4
+ // Every line the cli prints carries `[qpq:<area>]`, so output from the concurrent and nested
5
+ // commands reads as one program. This is the only place the cli touches the console.
6
+ const createOutput = (area) => {
7
+ const prefix = `[qpq:${area}]`;
8
+ // A leading string is joined onto the prefix; anything else (an error, an object) is printed
9
+ // after it as its own argument so the console keeps formatting it. The console method is
10
+ // looked up per call, not captured, so a test can spy on it after this module loads.
11
+ const withPrefix = (method) => {
12
+ return (...parts) => {
13
+ const [first, ...rest] = parts;
14
+ if (typeof first === 'string') {
15
+ return console[method](`${prefix} ${first}`, ...rest);
16
+ }
17
+ return console[method](prefix, ...parts);
18
+ };
19
+ };
20
+ return {
21
+ log: withPrefix('log'),
22
+ info: withPrefix('info'),
23
+ warn: withPrefix('warn'),
24
+ error: withPrefix('error'),
25
+ time: (label) => console.time(`${prefix} ${label}`),
26
+ timeEnd: (label) => console.timeEnd(`${prefix} ${label}`),
27
+ };
28
+ };
29
+ exports.createOutput = createOutput;
30
+ //# sourceMappingURL=out.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"out.js","sourceRoot":"","sources":["../../../src/lib/out.ts"],"names":[],"mappings":";;;AAaA,6FAA6F;AAC7F,qFAAqF;AAC9E,MAAM,YAAY,GAAG,CAAC,IAAY,EAAU,EAAE;IACnD,MAAM,MAAM,GAAG,QAAQ,IAAI,GAAG,CAAC;IAE/B,6FAA6F;IAC7F,yFAAyF;IACzF,qFAAqF;IACrF,MAAM,UAAU,GAAG,CAAC,MAAyC,EAAS,EAAE;QACtE,OAAO,CAAC,GAAG,KAAK,EAAE,EAAE;YAClB,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,KAAK,CAAC;YAE/B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,EAAE,GAAG,IAAI,CAAC,CAAC;YACxD,CAAC;YAED,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC;QAC3C,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO;QACL,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC;QACtB,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC;QACxB,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC;QACxB,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC;QAC1B,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;QACnD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,CAAC;KAC1D,CAAC;AACJ,CAAC,CAAC;AA1BW,QAAA,YAAY,gBA0BvB","sourcesContent":["/* eslint-disable no-console */\ntype Print = (...parts: unknown[]) => void;\n\nexport type Output = {\n log: Print;\n info: Print;\n warn: Print;\n error: Print;\n // A named stopwatch, printed on end.\n time: (label: string) => void;\n timeEnd: (label: string) => void;\n};\n\n// Every line the cli prints carries `[qpq:<area>]`, so output from the concurrent and nested\n// commands reads as one program. This is the only place the cli touches the console.\nexport const createOutput = (area: string): Output => {\n const prefix = `[qpq:${area}]`;\n\n // A leading string is joined onto the prefix; anything else (an error, an object) is printed\n // after it as its own argument so the console keeps formatting it. The console method is\n // looked up per call, not captured, so a test can spy on it after this module loads.\n const withPrefix = (method: 'log' | 'info' | 'warn' | 'error'): Print => {\n return (...parts) => {\n const [first, ...rest] = parts;\n\n if (typeof first === 'string') {\n return console[method](`${prefix} ${first}`, ...rest);\n }\n\n return console[method](prefix, ...parts);\n };\n };\n\n return {\n log: withPrefix('log'),\n info: withPrefix('info'),\n warn: withPrefix('warn'),\n error: withPrefix('error'),\n time: (label) => console.time(`${prefix} ${label}`),\n timeEnd: (label) => console.timeEnd(`${prefix} ${label}`),\n };\n};\n"]}
@@ -7,6 +7,7 @@ export declare const promptSelectDetailed: <T>(message: string, choices: {
7
7
  }[]) => Promise<T>;
8
8
  export declare const promptYesNo: (message: string) => Promise<boolean>;
9
9
  export declare const promptText: (message: string) => Promise<string>;
10
+ export declare const promptTypedConfirmation: (target: string) => Promise<boolean>;
10
11
  export declare const promptCheckbox: <T>(message: string, choices: {
11
12
  name: string;
12
13
  value: T;
@@ -42,7 +42,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
42
42
  });
43
43
  };
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
- exports.promptCheckbox = exports.promptText = exports.promptYesNo = exports.promptSelectDetailed = exports.promptSelect = void 0;
45
+ exports.promptCheckbox = exports.promptTypedConfirmation = exports.promptText = exports.promptYesNo = exports.promptSelectDetailed = exports.promptSelect = void 0;
46
+ const out_1 = require("./out");
47
+ const out = (0, out_1.createOutput)('prompts');
46
48
  // Present a single-choice list prompt and return the selected value.
47
49
  // @inquirer/prompts is imported lazily so commands only pay for it when
48
50
  // actually prompting.
@@ -62,13 +64,24 @@ exports.promptSelectDetailed = promptSelectDetailed;
62
64
  // A yes/no list prompt, returning true for "Yes".
63
65
  const promptYesNo = (message) => __awaiter(void 0, void 0, void 0, function* () { return (yield (0, exports.promptSelect)(message, ['No', 'Yes'])) === 'Yes'; });
64
66
  exports.promptYesNo = promptYesNo;
65
- // A free-text prompt — used where a y/n is too easy to fat-finger (destructive
67
+ // A free-text prompt, used where a y/n is too easy to fat-finger (destructive
66
68
  // confirmations that require typing the target name back).
67
69
  const promptText = (message) => __awaiter(void 0, void 0, void 0, function* () {
68
70
  const { input } = yield Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
69
71
  return input({ message });
70
72
  });
71
73
  exports.promptText = promptText;
74
+ // The friction for destructive commands: the target has to be typed back exactly. Resolves
75
+ // true when it was; prints why it stops otherwise, so callers only have to bail out.
76
+ const promptTypedConfirmation = (target) => __awaiter(void 0, void 0, void 0, function* () {
77
+ const confirmation = yield (0, exports.promptText)(`Type '${target}' to confirm`);
78
+ if (confirmation !== target) {
79
+ out.log('Confirmation did not match. Exiting!');
80
+ return false;
81
+ }
82
+ return true;
83
+ });
84
+ exports.promptTypedConfirmation = promptTypedConfirmation;
72
85
  // A multi-select checkbox prompt over labelled values; `checked` preselects a choice.
73
86
  const promptCheckbox = (message, choices) => __awaiter(void 0, void 0, void 0, function* () {
74
87
  const { checkbox } = yield Promise.resolve().then(() => __importStar(require('@inquirer/prompts')));
@@ -1 +1 @@
1
- {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../../src/lib/prompts.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qEAAqE;AACrE,wEAAwE;AACxE,sBAAsB;AACf,MAAM,YAAY,GAAG,CAAO,OAAe,EAAE,OAAiB,EAAE,aAAsB,EAAmB,EAAE;IAChH,MAAM,EAAE,MAAM,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;IACrD,OAAO,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;AAC9D,CAAC,CAAA,CAAC;AAHW,QAAA,YAAY,gBAGvB;AAEF,wEAAwE;AACxE,6EAA6E;AAC7E,gEAAgE;AACzD,MAAM,oBAAoB,GAAG,CAClC,OAAe,EACf,OAA2E,EAC/D,EAAE;IACd,MAAM,EAAE,MAAM,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;IACrD,OAAO,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACtC,CAAC,CAAA,CAAC;AANW,QAAA,oBAAoB,wBAM/B;AAEF,kDAAkD;AAC3C,MAAM,WAAW,GAAG,CAAO,OAAe,EAAoB,EAAE,kDAAC,OAAA,CAAC,MAAM,IAAA,oBAAY,EAAC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAA,GAAA,CAAC;AAAlH,QAAA,WAAW,eAAuG;AAE/H,+EAA+E;AAC/E,2DAA2D;AACpD,MAAM,UAAU,GAAG,CAAO,OAAe,EAAmB,EAAE;IACnE,MAAM,EAAE,KAAK,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;IACpD,OAAO,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5B,CAAC,CAAA,CAAC;AAHW,QAAA,UAAU,cAGrB;AAEF,sFAAsF;AAC/E,MAAM,cAAc,GAAG,CAC5B,OAAe,EACf,OAA8E,EAChE,EAAE;IAChB,MAAM,EAAE,QAAQ,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;IACvD,OAAO,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACxC,CAAC,CAAA,CAAC;AANW,QAAA,cAAc,kBAMzB","sourcesContent":["// Present a single-choice list prompt and return the selected value.\n// @inquirer/prompts is imported lazily so commands only pay for it when\n// actually prompting.\nexport const promptSelect = async (message: string, choices: string[], defaultChoice?: string): Promise<string> => {\n const { select } = await import('@inquirer/prompts');\n return select({ message, choices, default: defaultChoice });\n};\n\n// A single-choice list prompt over labelled values; a choice's optional\n// `short` replaces its name in the answer line once picked, and its optional\n// `description` renders under the list while it is highlighted.\nexport const promptSelectDetailed = async <T>(\n message: string,\n choices: { name: string; value: T; short?: string; description?: string }[],\n): Promise<T> => {\n const { select } = await import('@inquirer/prompts');\n return select({ message, choices });\n};\n\n// A yes/no list prompt, returning true for \"Yes\".\nexport const promptYesNo = async (message: string): Promise<boolean> => (await promptSelect(message, ['No', 'Yes'])) === 'Yes';\n\n// A free-text prompt — used where a y/n is too easy to fat-finger (destructive\n// confirmations that require typing the target name back).\nexport const promptText = async (message: string): Promise<string> => {\n const { input } = await import('@inquirer/prompts');\n return input({ message });\n};\n\n// A multi-select checkbox prompt over labelled values; `checked` preselects a choice.\nexport const promptCheckbox = async <T>(\n message: string,\n choices: { name: string; value: T; checked?: boolean; description?: string }[],\n): Promise<T[]> => {\n const { checkbox } = await import('@inquirer/prompts');\n return checkbox({ message, choices });\n};\n"]}
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../../src/lib/prompts.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+BAAqC;AAErC,MAAM,GAAG,GAAG,IAAA,kBAAY,EAAC,SAAS,CAAC,CAAC;AACpC,qEAAqE;AACrE,wEAAwE;AACxE,sBAAsB;AACf,MAAM,YAAY,GAAG,CAAO,OAAe,EAAE,OAAiB,EAAE,aAAsB,EAAmB,EAAE;IAChH,MAAM,EAAE,MAAM,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;IACrD,OAAO,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;AAC9D,CAAC,CAAA,CAAC;AAHW,QAAA,YAAY,gBAGvB;AAEF,wEAAwE;AACxE,6EAA6E;AAC7E,gEAAgE;AACzD,MAAM,oBAAoB,GAAG,CAClC,OAAe,EACf,OAA2E,EAC/D,EAAE;IACd,MAAM,EAAE,MAAM,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;IACrD,OAAO,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACtC,CAAC,CAAA,CAAC;AANW,QAAA,oBAAoB,wBAM/B;AAEF,kDAAkD;AAC3C,MAAM,WAAW,GAAG,CAAO,OAAe,EAAoB,EAAE,kDAAC,OAAA,CAAC,MAAM,IAAA,oBAAY,EAAC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAA,GAAA,CAAC;AAAlH,QAAA,WAAW,eAAuG;AAE/H,8EAA8E;AAC9E,2DAA2D;AACpD,MAAM,UAAU,GAAG,CAAO,OAAe,EAAmB,EAAE;IACnE,MAAM,EAAE,KAAK,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;IACpD,OAAO,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5B,CAAC,CAAA,CAAC;AAHW,QAAA,UAAU,cAGrB;AAEF,2FAA2F;AAC3F,qFAAqF;AAC9E,MAAM,uBAAuB,GAAG,CAAO,MAAc,EAAoB,EAAE;IAChF,MAAM,YAAY,GAAG,MAAM,IAAA,kBAAU,EAAC,SAAS,MAAM,cAAc,CAAC,CAAC;IAErE,IAAI,YAAY,KAAK,MAAM,EAAE,CAAC;QAC5B,GAAG,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC,CAAA,CAAC;AATW,QAAA,uBAAuB,2BASlC;AAEF,sFAAsF;AAC/E,MAAM,cAAc,GAAG,CAC5B,OAAe,EACf,OAA8E,EAChE,EAAE;IAChB,MAAM,EAAE,QAAQ,EAAE,GAAG,wDAAa,mBAAmB,GAAC,CAAC;IACvD,OAAO,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACxC,CAAC,CAAA,CAAC;AANW,QAAA,cAAc,kBAMzB","sourcesContent":["import { createOutput } from './out';\n\nconst out = createOutput('prompts');\n// Present a single-choice list prompt and return the selected value.\n// @inquirer/prompts is imported lazily so commands only pay for it when\n// actually prompting.\nexport const promptSelect = async (message: string, choices: string[], defaultChoice?: string): Promise<string> => {\n const { select } = await import('@inquirer/prompts');\n return select({ message, choices, default: defaultChoice });\n};\n\n// A single-choice list prompt over labelled values; a choice's optional\n// `short` replaces its name in the answer line once picked, and its optional\n// `description` renders under the list while it is highlighted.\nexport const promptSelectDetailed = async <T>(\n message: string,\n choices: { name: string; value: T; short?: string; description?: string }[],\n): Promise<T> => {\n const { select } = await import('@inquirer/prompts');\n return select({ message, choices });\n};\n\n// A yes/no list prompt, returning true for \"Yes\".\nexport const promptYesNo = async (message: string): Promise<boolean> => (await promptSelect(message, ['No', 'Yes'])) === 'Yes';\n\n// A free-text prompt, used where a y/n is too easy to fat-finger (destructive\n// confirmations that require typing the target name back).\nexport const promptText = async (message: string): Promise<string> => {\n const { input } = await import('@inquirer/prompts');\n return input({ message });\n};\n\n// The friction for destructive commands: the target has to be typed back exactly. Resolves\n// true when it was; prints why it stops otherwise, so callers only have to bail out.\nexport const promptTypedConfirmation = async (target: string): Promise<boolean> => {\n const confirmation = await promptText(`Type '${target}' to confirm`);\n\n if (confirmation !== target) {\n out.log('Confirmation did not match. Exiting!');\n return false;\n }\n\n return true;\n};\n\n// A multi-select checkbox prompt over labelled values; `checked` preselects a choice.\nexport const promptCheckbox = async <T>(\n message: string,\n choices: { name: string; value: T; checked?: boolean; description?: string }[],\n): Promise<T[]> => {\n const { checkbox } = await import('@inquirer/prompts');\n return checkbox({ message, choices });\n};\n"]}
@@ -10,16 +10,16 @@ const discovery_1 = require("./discovery");
10
10
  const loadServiceQpqConfig = (appName, service) => (0, deploy_aws_1.getWorkspaceServiceQpqConfig)((0, discovery_1.getRoot)(), appName, service);
11
11
  exports.loadServiceQpqConfig = loadServiceQpqConfig;
12
12
  const getAppPrefix = (appName) => {
13
- const services = (0, discovery_1.getServiceNames)(appName);
14
- if (services.length === 0)
13
+ const [firstService] = (0, discovery_1.getServiceNames)(appName);
14
+ if (firstService === undefined)
15
15
  throw new Error(`No services found for app ${appName}`);
16
- return core_1.qpqCoreUtils.getApplicationName((0, exports.loadServiceQpqConfig)(appName, services[0]));
16
+ return core_1.qpqCoreUtils.getApplicationName((0, exports.loadServiceQpqConfig)(appName, firstService));
17
17
  };
18
18
  exports.getAppPrefix = getAppPrefix;
19
19
  // The subset of the given services that opt into module-federation publishing,
20
20
  // i.e. declare defineFederatedModuleStore(...). Federation is a config setting,
21
21
  // not a directory convention (unlike views/ backends), so detecting it means
22
- // loading each service's config — which is why this lives here and not in the
22
+ // loading each service's config, which is why this lives here and not in the
23
23
  // directory-only discovery module. Used by the deploy drivers to decide which
24
24
  // backends to publish federated remotes for after a deploy.
25
25
  const getServiceNamesWithFederation = (appName, serviceNames) => serviceNames.filter((service) => !!core_1.qpqCoreUtils.getFederatedModuleStore((0, exports.loadServiceQpqConfig)(appName, service)));
@@ -1 +1 @@
1
- {"version":3,"file":"qpqConfigs.js","sourceRoot":"","sources":["../../../src/lib/qpqConfigs.ts"],"names":[],"mappings":";;;AAAA,sCAA2D;AAC3D,kDAAiE;AAEjE,2CAAuD;AAEvD,wEAAwE;AACxE,2EAA2E;AAC3E,uBAAuB;AAChB,MAAM,oBAAoB,GAAG,CAAC,OAAe,EAAE,OAAe,EAAa,EAAE,CAAC,IAAA,yCAA4B,EAAC,IAAA,mBAAO,GAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAAlI,QAAA,oBAAoB,wBAA8G;AAExI,MAAM,YAAY,GAAG,CAAC,OAAe,EAAU,EAAE;IACtD,MAAM,QAAQ,GAAG,IAAA,2BAAe,EAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;IACnF,OAAO,mBAAY,CAAC,kBAAkB,CAAC,IAAA,4BAAoB,EAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACrF,CAAC,CAAC;AAJW,QAAA,YAAY,gBAIvB;AAEF,+EAA+E;AAC/E,gFAAgF;AAChF,6EAA6E;AAC7E,8EAA8E;AAC9E,8EAA8E;AAC9E,4DAA4D;AACrD,MAAM,6BAA6B,GAAG,CAAC,OAAe,EAAE,YAAsB,EAAY,EAAE,CACjG,YAAY,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,mBAAY,CAAC,uBAAuB,CAAC,IAAA,4BAAoB,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AADtG,QAAA,6BAA6B,iCACyE","sourcesContent":["import { type QPQConfig, qpqCoreUtils } from '@qpqjs/core';\nimport { getWorkspaceServiceQpqConfig } from '@qpqjs/deploy-aws';\n\nimport { getRoot, getServiceNames } from './discovery';\n\n// Loads a service's live infrastructure.ts (qpq runs with ts-node hooks\n// registered, so requiring TS works and `@scope/*` imports resolve through\n// workspace symlinks).\nexport const loadServiceQpqConfig = (appName: string, service: string): QPQConfig => getWorkspaceServiceQpqConfig(getRoot(), appName, service);\n\nexport const getAppPrefix = (appName: string): string => {\n const services = getServiceNames(appName);\n if (services.length === 0) throw new Error(`No services found for app ${appName}`);\n return qpqCoreUtils.getApplicationName(loadServiceQpqConfig(appName, services[0]));\n};\n\n// The subset of the given services that opt into module-federation publishing,\n// i.e. declare defineFederatedModuleStore(...). Federation is a config setting,\n// not a directory convention (unlike views/ backends), so detecting it means\n// loading each service's config — which is why this lives here and not in the\n// directory-only discovery module. Used by the deploy drivers to decide which\n// backends to publish federated remotes for after a deploy.\nexport const getServiceNamesWithFederation = (appName: string, serviceNames: string[]): string[] =>\n serviceNames.filter((service) => !!qpqCoreUtils.getFederatedModuleStore(loadServiceQpqConfig(appName, service)));\n"]}
1
+ {"version":3,"file":"qpqConfigs.js","sourceRoot":"","sources":["../../../src/lib/qpqConfigs.ts"],"names":[],"mappings":";;;AAAA,sCAA2D;AAC3D,kDAAiE;AAEjE,2CAAuD;AAEvD,wEAAwE;AACxE,2EAA2E;AAC3E,uBAAuB;AAChB,MAAM,oBAAoB,GAAG,CAAC,OAAe,EAAE,OAAe,EAAa,EAAE,CAAC,IAAA,yCAA4B,EAAC,IAAA,mBAAO,GAAE,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;AAAlI,QAAA,oBAAoB,wBAA8G;AAExI,MAAM,YAAY,GAAG,CAAC,OAAe,EAAU,EAAE;IACtD,MAAM,CAAC,YAAY,CAAC,GAAG,IAAA,2BAAe,EAAC,OAAO,CAAC,CAAC;IAChD,IAAI,YAAY,KAAK,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,OAAO,EAAE,CAAC,CAAC;IACxF,OAAO,mBAAY,CAAC,kBAAkB,CAAC,IAAA,4BAAoB,EAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;AACtF,CAAC,CAAC;AAJW,QAAA,YAAY,gBAIvB;AAEF,+EAA+E;AAC/E,gFAAgF;AAChF,6EAA6E;AAC7E,6EAA6E;AAC7E,8EAA8E;AAC9E,4DAA4D;AACrD,MAAM,6BAA6B,GAAG,CAAC,OAAe,EAAE,YAAsB,EAAY,EAAE,CACjG,YAAY,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,mBAAY,CAAC,uBAAuB,CAAC,IAAA,4BAAoB,EAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AADtG,QAAA,6BAA6B,iCACyE","sourcesContent":["import { type QPQConfig, qpqCoreUtils } from '@qpqjs/core';\nimport { getWorkspaceServiceQpqConfig } from '@qpqjs/deploy-aws';\n\nimport { getRoot, getServiceNames } from './discovery';\n\n// Loads a service's live infrastructure.ts (qpq runs with ts-node hooks\n// registered, so requiring TS works and `@scope/*` imports resolve through\n// workspace symlinks).\nexport const loadServiceQpqConfig = (appName: string, service: string): QPQConfig => getWorkspaceServiceQpqConfig(getRoot(), appName, service);\n\nexport const getAppPrefix = (appName: string): string => {\n const [firstService] = getServiceNames(appName);\n if (firstService === undefined) throw new Error(`No services found for app ${appName}`);\n return qpqCoreUtils.getApplicationName(loadServiceQpqConfig(appName, firstService));\n};\n\n// The subset of the given services that opt into module-federation publishing,\n// i.e. declare defineFederatedModuleStore(...). Federation is a config setting,\n// not a directory convention (unlike views/ backends), so detecting it means\n// loading each service's config, which is why this lives here and not in the\n// directory-only discovery module. Used by the deploy drivers to decide which\n// backends to publish federated remotes for after a deploy.\nexport const getServiceNamesWithFederation = (appName: string, serviceNames: string[]): string[] =>\n serviceNames.filter((service) => !!qpqCoreUtils.getFederatedModuleStore(loadServiceQpqConfig(appName, service)));\n"]}
@@ -0,0 +1,7 @@
1
+ import { QpqPlatformDriver } from '../platforms';
2
+ import { CommandContext } from './resolveCommandContext';
3
+ type OptionalCapability = {
4
+ [K in keyof QpqPlatformDriver]-?: undefined extends QpqPlatformDriver[K] ? K : never;
5
+ }[keyof QpqPlatformDriver];
6
+ export declare const requireDriverCapability: <K extends OptionalCapability>(context: CommandContext, capability: K, hint?: string) => NonNullable<QpqPlatformDriver[K]>;
7
+ export {};
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requireDriverCapability = void 0;
4
+ const CliError_1 = require("./CliError");
5
+ const out_1 = require("./out");
6
+ const out = (0, out_1.createOutput)('require-driver-capability');
7
+ // Hands back a driver's optional capability, or stops the command with a message naming the
8
+ // platform that lacks it. The capability name doubles as the strategy name in that message.
9
+ const requireDriverCapability = (context, capability, hint = '') => {
10
+ const implementation = context.driver[capability];
11
+ if (!implementation) {
12
+ throw new CliError_1.CliError(`The '${context.deployment.platform}' platform has no ${capability} strategy.${hint ? ` ${hint}` : ''}`);
13
+ }
14
+ return implementation;
15
+ };
16
+ exports.requireDriverCapability = requireDriverCapability;
17
+ //# sourceMappingURL=requireDriverCapability.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"requireDriverCapability.js","sourceRoot":"","sources":["../../../src/lib/requireDriverCapability.ts"],"names":[],"mappings":";;;AAEA,yCAAsC;AACtC,+BAAqC;AAErC,MAAM,GAAG,GAAG,IAAA,kBAAY,EAAC,2BAA2B,CAAC,CAAC;AAMtD,4FAA4F;AAC5F,4FAA4F;AACrF,MAAM,uBAAuB,GAAG,CACrC,OAAuB,EACvB,UAAa,EACb,OAAe,EAAE,EACkB,EAAE;IACrC,MAAM,cAAc,GAAG,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAElD,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,MAAM,IAAI,mBAAQ,CAAC,QAAQ,OAAO,CAAC,UAAU,CAAC,QAAQ,qBAAqB,UAAU,aAAa,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAC9H,CAAC;IAED,OAAO,cAAmD,CAAC;AAC7D,CAAC,CAAC;AAZW,QAAA,uBAAuB,2BAYlC","sourcesContent":["import { QpqPlatformDriver } from '../platforms';\nimport { CommandContext } from './resolveCommandContext';\nimport { CliError } from './CliError';\nimport { createOutput } from './out';\n\nconst out = createOutput('require-driver-capability');\n\ntype OptionalCapability = {\n [K in keyof QpqPlatformDriver]-?: undefined extends QpqPlatformDriver[K] ? K : never;\n}[keyof QpqPlatformDriver];\n\n// Hands back a driver's optional capability, or stops the command with a message naming the\n// platform that lacks it. The capability name doubles as the strategy name in that message.\nexport const requireDriverCapability = <K extends OptionalCapability>(\n context: CommandContext,\n capability: K,\n hint: string = '',\n): NonNullable<QpqPlatformDriver[K]> => {\n const implementation = context.driver[capability];\n\n if (!implementation) {\n throw new CliError(`The '${context.deployment.platform}' platform has no ${capability} strategy.${hint ? ` ${hint}` : ''}`);\n }\n\n return implementation as NonNullable<QpqPlatformDriver[K]>;\n};\n"]}
@@ -3,4 +3,5 @@ export type ResolveAppOptions = {
3
3
  envVar?: string;
4
4
  allowAll?: boolean;
5
5
  };
6
+ export declare const getRequestedAppName: (argv: string[], envVar?: string) => string | undefined;
6
7
  export declare const resolveAppSelection: (options: ResolveAppOptions) => Promise<string>;
@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  });
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.resolveAppSelection = void 0;
12
+ exports.resolveAppSelection = exports.getRequestedAppName = void 0;
13
13
  // One app-selection contract for every app-facing command (prep / synth / go /
14
14
  // go:dev / go:dev:api / go:dev:web / publish). Resolution order:
15
15
  // 1. --app <name> / --app=<name> on argv
@@ -24,20 +24,26 @@ exports.resolveAppSelection = void 0;
24
24
  const args_1 = require("./args");
25
25
  const discovery_1 = require("./discovery");
26
26
  const prompts_1 = require("./prompts");
27
+ const CliError_1 = require("./CliError");
28
+ const out_1 = require("./out");
29
+ const out = (0, out_1.createOutput)('resolve-app-selection');
27
30
  const exitWithAppList = (requested, availableApps) => {
28
- if (requested)
29
- console.error(`Unknown app '${requested}'.`);
30
- console.error(`Valid apps: ${availableApps.join(', ')}`);
31
- console.error(`Pass one with --app <name> (e.g. qpq <command> --app ${availableApps[0]}).`);
32
- process.exit(1);
31
+ throw new CliError_1.CliError([
32
+ ...(requested ? [`Unknown app '${requested}'.`] : []),
33
+ `Valid apps: ${availableApps.join(', ')}`,
34
+ `Pass one with --app <name> (e.g. qpq <command> --app ${availableApps[0]}).`,
35
+ ].join('\n'));
33
36
  };
37
+ // The app a caller named, from the sources above the auto-select and the prompt (--app, the npm
38
+ // config flag, the command's env var); undefined when none did.
39
+ const getRequestedAppName = (argv, envVar) => (0, args_1.getArgValue)(argv, '--app') || process.env.npm_config_app || (envVar ? process.env[envVar] : undefined);
40
+ exports.getRequestedAppName = getRequestedAppName;
34
41
  const resolveAppSelection = (options) => __awaiter(void 0, void 0, void 0, function* () {
35
42
  const availableApps = (0, discovery_1.getAvailableApps)();
36
43
  if (availableApps.length === 0) {
37
- console.error('No apps found under apps/.');
38
- process.exit(1);
44
+ throw new CliError_1.CliError('No apps found under apps/.');
39
45
  }
40
- const requested = (0, args_1.getArgValue)(options.argv, '--app') || process.env.npm_config_app || (options.envVar ? process.env[options.envVar] : undefined);
46
+ const requested = (0, exports.getRequestedAppName)(options.argv, options.envVar);
41
47
  if (requested) {
42
48
  if (options.allowAll && requested === 'all')
43
49
  return 'all';
@@ -45,9 +51,10 @@ const resolveAppSelection = (options) => __awaiter(void 0, void 0, void 0, funct
45
51
  exitWithAppList(requested, availableApps);
46
52
  return requested;
47
53
  }
48
- if (availableApps.length === 1) {
49
- console.log(`Using app: ${availableApps[0]}`);
50
- return availableApps[0];
54
+ const [onlyApp] = availableApps;
55
+ if (availableApps.length === 1 && onlyApp !== undefined) {
56
+ out.log(`Using app: ${onlyApp}`);
57
+ return onlyApp;
51
58
  }
52
59
  if (process.stdin.isTTY) {
53
60
  return (0, prompts_1.promptSelect)('Select app', options.allowAll ? [...availableApps, 'all'] : availableApps);
@@ -1 +1 @@
1
- {"version":3,"file":"resolveAppSelection.js","sourceRoot":"","sources":["../../../src/lib/resolveAppSelection.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,+EAA+E;AAC/E,iEAAiE;AACjE,2CAA2C;AAC3C,+EAA+E;AAC/E,kFAAkF;AAClF,kDAAkD;AAClD,qCAAqC;AACrC,6DAA6D;AAC7D,6EAA6E;AAC7E,+EAA+E;AAC/E,+CAA+C;AAC/C,iCAAqC;AACrC,2CAA+C;AAC/C,uCAAyC;AAUzC,MAAM,eAAe,GAAG,CAAC,SAA6B,EAAE,aAAuB,EAAS,EAAE;IACxF,IAAI,SAAS;QAAE,OAAO,CAAC,KAAK,CAAC,gBAAgB,SAAS,IAAI,CAAC,CAAC;IAC5D,OAAO,CAAC,KAAK,CAAC,eAAe,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,KAAK,CAAC,wDAAwD,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC;AAEK,MAAM,mBAAmB,GAAG,CAAO,OAA0B,EAAmB,EAAE;IACvF,MAAM,aAAa,GAAG,IAAA,4BAAgB,GAAE,CAAC;IACzC,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QAC5C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,SAAS,GACb,IAAA,kBAAW,EAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAEjI,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,OAAO,CAAC,QAAQ,IAAI,SAAS,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAC1D,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,eAAe,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAClF,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,cAAc,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9C,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACxB,OAAO,IAAA,sBAAY,EAAC,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAClG,CAAC;IAED,OAAO,eAAe,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AACnD,CAAC,CAAA,CAAC;AA1BW,QAAA,mBAAmB,uBA0B9B","sourcesContent":["// One app-selection contract for every app-facing command (prep / synth / go /\n// go:dev / go:dev:api / go:dev:web / publish). Resolution order:\n// 1. --app <name> / --app=<name> on argv\n// 2. npm_config_app (npm run x --app foo, no --)\n// 3. an env var the command already honours (QPQ_DEV_APP / DEPLOY_APP_NAME)\n// 4. exactly one app under apps/ -> auto-select\n// 5. interactive prompt (TTY only)\n// 6. otherwise: print the valid app list and exit non-zero\n// An unknown name (from any source) also prints the list and exits non-zero.\n// Commands that can meaningfully run for every app may pass allowAll to accept\n// `--app all` (returned as the literal 'all').\nimport { getArgValue } from './args';\nimport { getAvailableApps } from './discovery';\nimport { promptSelect } from './prompts';\n\nexport type ResolveAppOptions = {\n argv: string[];\n // Env var this command historically honours (e.g. QPQ_DEV_APP, DEPLOY_APP_NAME).\n envVar?: string;\n // Accept `--app all` and return 'all'.\n allowAll?: boolean;\n};\n\nconst exitWithAppList = (requested: string | undefined, availableApps: string[]): never => {\n if (requested) console.error(`Unknown app '${requested}'.`);\n console.error(`Valid apps: ${availableApps.join(', ')}`);\n console.error(`Pass one with --app <name> (e.g. qpq <command> --app ${availableApps[0]}).`);\n process.exit(1);\n};\n\nexport const resolveAppSelection = async (options: ResolveAppOptions): Promise<string> => {\n const availableApps = getAvailableApps();\n if (availableApps.length === 0) {\n console.error('No apps found under apps/.');\n process.exit(1);\n }\n\n const requested =\n getArgValue(options.argv, '--app') || process.env.npm_config_app || (options.envVar ? process.env[options.envVar] : undefined);\n\n if (requested) {\n if (options.allowAll && requested === 'all') return 'all';\n if (!availableApps.includes(requested)) exitWithAppList(requested, availableApps);\n return requested;\n }\n\n if (availableApps.length === 1) {\n console.log(`Using app: ${availableApps[0]}`);\n return availableApps[0];\n }\n\n if (process.stdin.isTTY) {\n return promptSelect('Select app', options.allowAll ? [...availableApps, 'all'] : availableApps);\n }\n\n return exitWithAppList(undefined, availableApps);\n};\n"]}
1
+ {"version":3,"file":"resolveAppSelection.js","sourceRoot":"","sources":["../../../src/lib/resolveAppSelection.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,+EAA+E;AAC/E,iEAAiE;AACjE,2CAA2C;AAC3C,+EAA+E;AAC/E,kFAAkF;AAClF,kDAAkD;AAClD,qCAAqC;AACrC,6DAA6D;AAC7D,6EAA6E;AAC7E,+EAA+E;AAC/E,+CAA+C;AAC/C,iCAAqC;AACrC,2CAA+C;AAC/C,uCAAyC;AACzC,yCAAsC;AACtC,+BAAqC;AAErC,MAAM,GAAG,GAAG,IAAA,kBAAY,EAAC,uBAAuB,CAAC,CAAC;AAUlD,MAAM,eAAe,GAAG,CAAC,SAA6B,EAAE,aAAuB,EAAS,EAAE;IACxF,MAAM,IAAI,mBAAQ,CAChB;QACE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,gBAAgB,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,eAAe,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QACzC,wDAAwD,aAAa,CAAC,CAAC,CAAC,IAAI;KAC7E,CAAC,IAAI,CAAC,IAAI,CAAC,CACb,CAAC;AACJ,CAAC,CAAC;AAEF,gGAAgG;AAChG,gEAAgE;AACzD,MAAM,mBAAmB,GAAG,CAAC,IAAc,EAAE,MAAe,EAAsB,EAAE,CACzF,IAAA,kBAAW,EAAC,IAAI,EAAE,OAAO,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAD5F,QAAA,mBAAmB,uBACyE;AAElG,MAAM,mBAAmB,GAAG,CAAO,OAA0B,EAAmB,EAAE;IACvF,MAAM,aAAa,GAAG,IAAA,4BAAgB,GAAE,CAAC;IACzC,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,mBAAQ,CAAC,4BAA4B,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,SAAS,GAAG,IAAA,2BAAmB,EAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAEpE,IAAI,SAAS,EAAE,CAAC;QACd,IAAI,OAAO,CAAC,QAAQ,IAAI,SAAS,KAAK,KAAK;YAAE,OAAO,KAAK,CAAC;QAC1D,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC;YAAE,eAAe,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QAClF,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC;IAChC,IAAI,aAAa,CAAC,MAAM,KAAK,CAAC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;QACxD,GAAG,CAAC,GAAG,CAAC,cAAc,OAAO,EAAE,CAAC,CAAC;QACjC,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACxB,OAAO,IAAA,sBAAY,EAAC,YAAY,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,aAAa,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC;IAClG,CAAC;IAED,OAAO,eAAe,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;AACnD,CAAC,CAAA,CAAC;AAzBW,QAAA,mBAAmB,uBAyB9B","sourcesContent":["// One app-selection contract for every app-facing command (prep / synth / go /\n// go:dev / go:dev:api / go:dev:web / publish). Resolution order:\n// 1. --app <name> / --app=<name> on argv\n// 2. npm_config_app (npm run x --app foo, no --)\n// 3. an env var the command already honours (QPQ_DEV_APP / DEPLOY_APP_NAME)\n// 4. exactly one app under apps/ -> auto-select\n// 5. interactive prompt (TTY only)\n// 6. otherwise: print the valid app list and exit non-zero\n// An unknown name (from any source) also prints the list and exits non-zero.\n// Commands that can meaningfully run for every app may pass allowAll to accept\n// `--app all` (returned as the literal 'all').\nimport { getArgValue } from './args';\nimport { getAvailableApps } from './discovery';\nimport { promptSelect } from './prompts';\nimport { CliError } from './CliError';\nimport { createOutput } from './out';\n\nconst out = createOutput('resolve-app-selection');\n\nexport type ResolveAppOptions = {\n argv: string[];\n // Env var this command historically honours (e.g. QPQ_DEV_APP, DEPLOY_APP_NAME).\n envVar?: string;\n // Accept `--app all` and return 'all'.\n allowAll?: boolean;\n};\n\nconst exitWithAppList = (requested: string | undefined, availableApps: string[]): never => {\n throw new CliError(\n [\n ...(requested ? [`Unknown app '${requested}'.`] : []),\n `Valid apps: ${availableApps.join(', ')}`,\n `Pass one with --app <name> (e.g. qpq <command> --app ${availableApps[0]}).`,\n ].join('\\n'),\n );\n};\n\n// The app a caller named, from the sources above the auto-select and the prompt (--app, the npm\n// config flag, the command's env var); undefined when none did.\nexport const getRequestedAppName = (argv: string[], envVar?: string): string | undefined =>\n getArgValue(argv, '--app') || process.env.npm_config_app || (envVar ? process.env[envVar] : undefined);\n\nexport const resolveAppSelection = async (options: ResolveAppOptions): Promise<string> => {\n const availableApps = getAvailableApps();\n if (availableApps.length === 0) {\n throw new CliError('No apps found under apps/.');\n }\n\n const requested = getRequestedAppName(options.argv, options.envVar);\n\n if (requested) {\n if (options.allowAll && requested === 'all') return 'all';\n if (!availableApps.includes(requested)) exitWithAppList(requested, availableApps);\n return requested;\n }\n\n const [onlyApp] = availableApps;\n if (availableApps.length === 1 && onlyApp !== undefined) {\n out.log(`Using app: ${onlyApp}`);\n return onlyApp;\n }\n\n if (process.stdin.isTTY) {\n return promptSelect('Select app', options.allowAll ? [...availableApps, 'all'] : availableApps);\n }\n\n return exitWithAppList(undefined, availableApps);\n};\n"]}
@@ -0,0 +1,12 @@
1
+ import { QpqAppDeployment } from '@qpqjs/core';
2
+ import { QpqPlatformDriver } from '../platforms';
3
+ export type CommandContext = {
4
+ appName: string;
5
+ deploymentName: string;
6
+ deployment: QpqAppDeployment;
7
+ driver: QpqPlatformDriver;
8
+ };
9
+ export type ResolveCommandContextOptions = {
10
+ appEnvVar?: string;
11
+ };
12
+ export declare const resolveCommandContext: (argv: string[], options?: ResolveCommandContextOptions) => Promise<CommandContext>;
@@ -0,0 +1,26 @@
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
+ exports.resolveCommandContext = void 0;
13
+ const core_1 = require("@qpqjs/core");
14
+ const platforms_1 = require("../platforms");
15
+ const resolveAppSelection_1 = require("./resolveAppSelection");
16
+ const resolveDeployment_1 = require("./resolveDeployment");
17
+ // The one preamble for every deploy-facing command: which app, which entry of its
18
+ // deploy.config.json (primed into the environment), and the platform driver that entry names.
19
+ const resolveCommandContext = (argv_1, ...args_1) => __awaiter(void 0, [argv_1, ...args_1], void 0, function* (argv, options = {}) {
20
+ var _a;
21
+ const appName = yield (0, resolveAppSelection_1.resolveAppSelection)({ argv, envVar: (_a = options.appEnvVar) !== null && _a !== void 0 ? _a : core_1.QpqDeployEnvVar.deployAppName });
22
+ const { deploymentName, deployment } = yield (0, resolveDeployment_1.resolveDeployment)(argv, appName);
23
+ return { appName, deploymentName, deployment, driver: (0, platforms_1.getPlatformDriver)(deployment.platform) };
24
+ });
25
+ exports.resolveCommandContext = resolveCommandContext;
26
+ //# sourceMappingURL=resolveCommandContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolveCommandContext.js","sourceRoot":"","sources":["../../../src/lib/resolveCommandContext.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,sCAAgE;AAEhE,4CAAoE;AACpE,+DAA4D;AAC5D,2DAAwD;AAcxD,kFAAkF;AAClF,8FAA8F;AACvF,MAAM,qBAAqB,GAAG,oBAA4F,EAAE,2DAAvF,IAAc,EAAE,UAAwC,EAAE;;IACpG,MAAM,OAAO,GAAG,MAAM,IAAA,yCAAmB,EAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAA,OAAO,CAAC,SAAS,mCAAI,sBAAe,CAAC,aAAa,EAAE,CAAC,CAAC;IAChH,MAAM,EAAE,cAAc,EAAE,UAAU,EAAE,GAAG,MAAM,IAAA,qCAAiB,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IAE9E,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,EAAE,IAAA,6BAAiB,EAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;AACjG,CAAC,CAAA,CAAC;AALW,QAAA,qBAAqB,yBAKhC","sourcesContent":["import { QpqAppDeployment, QpqDeployEnvVar } from '@qpqjs/core';\n\nimport { getPlatformDriver, QpqPlatformDriver } from '../platforms';\nimport { resolveAppSelection } from './resolveAppSelection';\nimport { resolveDeployment } from './resolveDeployment';\n\nexport type CommandContext = {\n appName: string;\n deploymentName: string;\n deployment: QpqAppDeployment;\n driver: QpqPlatformDriver;\n};\n\nexport type ResolveCommandContextOptions = {\n // The env var the command honours for the app name when no --app is given.\n appEnvVar?: string;\n};\n\n// The one preamble for every deploy-facing command: which app, which entry of its\n// deploy.config.json (primed into the environment), and the platform driver that entry names.\nexport const resolveCommandContext = async (argv: string[], options: ResolveCommandContextOptions = {}): Promise<CommandContext> => {\n const appName = await resolveAppSelection({ argv, envVar: options.appEnvVar ?? QpqDeployEnvVar.deployAppName });\n const { deploymentName, deployment } = await resolveDeployment(argv, appName);\n\n return { appName, deploymentName, deployment, driver: getPlatformDriver(deployment.platform) };\n};\n"]}
@@ -1,5 +1,4 @@
1
1
  import { QpqAppDeployment } from '@qpqjs/core';
2
- export declare const DEPLOYMENT_FLAG = "--deployment";
3
2
  export type ResolvedDeployment = {
4
3
  deploymentName: string;
5
4
  deployment: QpqAppDeployment;