@qpqjs/cli 0.3.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 (292) 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 +27 -23
  14. package/lib/commonjs/commands/goDevApi.js.map +1 -1
  15. package/lib/commonjs/commands/goDevWeb.js +14 -9
  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/Output.d.ts +12 -0
  43. package/lib/commonjs/lib/Output.js +3 -0
  44. package/lib/commonjs/lib/Output.js.map +1 -0
  45. package/lib/commonjs/lib/circularImports.d.ts +0 -1
  46. package/lib/commonjs/lib/circularImports.js +5 -6
  47. package/lib/commonjs/lib/circularImports.js.map +1 -1
  48. package/lib/commonjs/lib/clearResourcesPlan.d.ts +0 -1
  49. package/lib/commonjs/lib/clearResourcesPlan.js +12 -13
  50. package/lib/commonjs/lib/clearResourcesPlan.js.map +1 -1
  51. package/lib/commonjs/lib/createOutput.d.ts +2 -0
  52. package/lib/commonjs/lib/createOutput.js +39 -0
  53. package/lib/commonjs/lib/createOutput.js.map +1 -0
  54. package/lib/commonjs/lib/deployPrompts.d.ts +2 -1
  55. package/lib/commonjs/lib/deployPrompts.js +16 -16
  56. package/lib/commonjs/lib/deployPrompts.js.map +1 -1
  57. package/lib/commonjs/lib/devServerEntry.js +5 -4
  58. package/lib/commonjs/lib/devServerEntry.js.map +1 -1
  59. package/lib/commonjs/lib/devServerPorts.js +2 -4
  60. package/lib/commonjs/lib/devServerPorts.js.map +1 -1
  61. package/lib/commonjs/lib/discovery.d.ts +0 -2
  62. package/lib/commonjs/lib/discovery.js +11 -44
  63. package/lib/commonjs/lib/discovery.js.map +1 -1
  64. package/lib/commonjs/lib/hooks.js +5 -3
  65. package/lib/commonjs/lib/hooks.js.map +1 -1
  66. package/lib/commonjs/lib/killChildWithEscalation.js +3 -1
  67. package/lib/commonjs/lib/killChildWithEscalation.js.map +1 -1
  68. package/lib/commonjs/lib/killStaleListeners.js +24 -37
  69. package/lib/commonjs/lib/killStaleListeners.js.map +1 -1
  70. package/lib/commonjs/lib/prompts.d.ts +1 -0
  71. package/lib/commonjs/lib/prompts.js +15 -2
  72. package/lib/commonjs/lib/prompts.js.map +1 -1
  73. package/lib/commonjs/lib/qpqConfigs.d.ts +1 -0
  74. package/lib/commonjs/lib/qpqConfigs.js +11 -6
  75. package/lib/commonjs/lib/qpqConfigs.js.map +1 -1
  76. package/lib/commonjs/lib/requireDriverCapability.d.ts +7 -0
  77. package/lib/commonjs/lib/requireDriverCapability.js +17 -0
  78. package/lib/commonjs/lib/requireDriverCapability.js.map +1 -0
  79. package/lib/commonjs/lib/resolveAppSelection.d.ts +1 -0
  80. package/lib/commonjs/lib/resolveAppSelection.js +19 -12
  81. package/lib/commonjs/lib/resolveAppSelection.js.map +1 -1
  82. package/lib/commonjs/lib/resolveCommandContext.d.ts +12 -0
  83. package/lib/commonjs/lib/resolveCommandContext.js +26 -0
  84. package/lib/commonjs/lib/resolveCommandContext.js.map +1 -0
  85. package/lib/commonjs/lib/resolveDeployment.d.ts +0 -1
  86. package/lib/commonjs/lib/resolveDeployment.js +15 -10
  87. package/lib/commonjs/lib/resolveDeployment.js.map +1 -1
  88. package/lib/commonjs/lib/runCommand.d.ts +20 -0
  89. package/lib/commonjs/lib/runCommand.js +64 -34
  90. package/lib/commonjs/lib/runCommand.js.map +1 -1
  91. package/lib/commonjs/lib/runTasks.js +8 -4
  92. package/lib/commonjs/lib/runTasks.js.map +1 -1
  93. package/lib/commonjs/lib/timing.js +5 -3
  94. package/lib/commonjs/lib/timing.js.map +1 -1
  95. package/lib/commonjs/lib/typeWatcher.d.ts +2 -1
  96. package/lib/commonjs/lib/typeWatcher.js +6 -13
  97. package/lib/commonjs/lib/typeWatcher.js.map +1 -1
  98. package/lib/commonjs/lib/userScriptBuilderLayer.d.ts +0 -2
  99. package/lib/commonjs/lib/userScriptBuilderLayer.js +7 -8
  100. package/lib/commonjs/lib/userScriptBuilderLayer.js.map +1 -1
  101. package/lib/commonjs/lib/views.js +3 -2
  102. package/lib/commonjs/lib/views.js.map +1 -1
  103. package/lib/commonjs/lib/webAddressingEnv.d.ts +0 -2
  104. package/lib/commonjs/lib/webAddressingEnv.js +4 -4
  105. package/lib/commonjs/lib/webAddressingEnv.js.map +1 -1
  106. package/lib/commonjs/platforms/aws/awsCredentials.js +3 -1
  107. package/lib/commonjs/platforms/aws/awsCredentials.js.map +1 -1
  108. package/lib/commonjs/platforms/aws/cdkApp.js +1 -1
  109. package/lib/commonjs/platforms/aws/cdkApp.js.map +1 -1
  110. package/lib/commonjs/platforms/aws/clearResources.js +17 -26
  111. package/lib/commonjs/platforms/aws/clearResources.js.map +1 -1
  112. package/lib/commonjs/platforms/aws/getDeployEnv.d.ts +1 -0
  113. package/lib/commonjs/platforms/aws/getDeployEnv.js +12 -0
  114. package/lib/commonjs/platforms/aws/getDeployEnv.js.map +1 -0
  115. package/lib/commonjs/platforms/aws/go.js +17 -15
  116. package/lib/commonjs/platforms/aws/go.js.map +1 -1
  117. package/lib/commonjs/platforms/aws/goDocker.js +29 -26
  118. package/lib/commonjs/platforms/aws/goDocker.js.map +1 -1
  119. package/lib/commonjs/platforms/aws/goDockerManifest.js +6 -1
  120. package/lib/commonjs/platforms/aws/goDockerManifest.js.map +1 -1
  121. package/lib/commonjs/platforms/aws/publish.js +13 -11
  122. package/lib/commonjs/platforms/aws/publish.js.map +1 -1
  123. package/lib/commonjs/platforms/aws/remote.js +8 -7
  124. package/lib/commonjs/platforms/aws/remote.js.map +1 -1
  125. package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.js +4 -4
  126. package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.js.map +1 -1
  127. package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.js +4 -3
  128. package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.js.map +1 -1
  129. package/lib/commonjs/platforms/aws/stacks.d.ts +0 -1
  130. package/lib/commonjs/platforms/aws/stacks.js +17 -33
  131. package/lib/commonjs/platforms/aws/stacks.js.map +1 -1
  132. package/lib/commonjs/platforms/aws/teardown.js +9 -7
  133. package/lib/commonjs/platforms/aws/teardown.js.map +1 -1
  134. package/lib/commonjs/platforms/aws/viewsSync.js +23 -11
  135. package/lib/commonjs/platforms/aws/viewsSync.js.map +1 -1
  136. package/lib/commonjs/platforms/docker/go.js +20 -18
  137. package/lib/commonjs/platforms/docker/go.js.map +1 -1
  138. package/lib/commonjs/platforms/docker/index.js +5 -4
  139. package/lib/commonjs/platforms/docker/index.js.map +1 -1
  140. package/lib/commonjs/platforms/docker/parseMounts.js +3 -2
  141. package/lib/commonjs/platforms/docker/parseMounts.js.map +1 -1
  142. package/lib/commonjs/platforms/index.js +4 -2
  143. package/lib/commonjs/platforms/index.js.map +1 -1
  144. package/lib/commonjs/platforms/types.d.ts +2 -2
  145. package/lib/commonjs/platforms/types.js.map +1 -1
  146. package/lib/esm/cli/commandMenu.js +5 -4
  147. package/lib/esm/cli/commandMenu.js.map +1 -1
  148. package/lib/esm/cli/runCli.js +15 -7
  149. package/lib/esm/cli/runCli.js.map +1 -1
  150. package/lib/esm/commands/checkCircular.js +15 -7
  151. package/lib/esm/commands/checkCircular.js.map +1 -1
  152. package/lib/esm/commands/clearResources.js +8 -14
  153. package/lib/esm/commands/clearResources.js.map +1 -1
  154. package/lib/esm/commands/go.js +3 -7
  155. package/lib/esm/commands/go.js.map +1 -1
  156. package/lib/esm/commands/goDev.js +4 -2
  157. package/lib/esm/commands/goDev.js.map +1 -1
  158. package/lib/esm/commands/goDevApi.js +24 -21
  159. package/lib/esm/commands/goDevApi.js.map +1 -1
  160. package/lib/esm/commands/goDevWeb.js +13 -9
  161. package/lib/esm/commands/goDevWeb.js.map +1 -1
  162. package/lib/esm/commands/goDocker.js +10 -13
  163. package/lib/esm/commands/goDocker.js.map +1 -1
  164. package/lib/esm/commands/hooks.js +4 -2
  165. package/lib/esm/commands/hooks.js.map +1 -1
  166. package/lib/esm/commands/migrate.js +9 -7
  167. package/lib/esm/commands/migrate.js.map +1 -1
  168. package/lib/esm/commands/prep.js +4 -2
  169. package/lib/esm/commands/prep.js.map +1 -1
  170. package/lib/esm/commands/publish.js +4 -7
  171. package/lib/esm/commands/publish.js.map +1 -1
  172. package/lib/esm/commands/scriptBuild.js +7 -5
  173. package/lib/esm/commands/scriptBuild.js.map +1 -1
  174. package/lib/esm/commands/scriptBuilderLayer.js +6 -4
  175. package/lib/esm/commands/scriptBuilderLayer.js.map +1 -1
  176. package/lib/esm/commands/scriptNew.js +7 -5
  177. package/lib/esm/commands/scriptNew.js.map +1 -1
  178. package/lib/esm/commands/setup.js +14 -22
  179. package/lib/esm/commands/setup.js.map +1 -1
  180. package/lib/esm/commands/synth.js +18 -22
  181. package/lib/esm/commands/synth.js.map +1 -1
  182. package/lib/esm/commands/teardown.js +8 -14
  183. package/lib/esm/commands/teardown.js.map +1 -1
  184. package/lib/esm/lib/CliError.d.ts +4 -0
  185. package/lib/esm/lib/CliError.js +11 -0
  186. package/lib/esm/lib/CliError.js.map +1 -0
  187. package/lib/esm/lib/Output.d.ts +12 -0
  188. package/lib/esm/lib/Output.js +2 -0
  189. package/lib/esm/lib/Output.js.map +1 -0
  190. package/lib/esm/lib/circularImports.d.ts +0 -1
  191. package/lib/esm/lib/circularImports.js +4 -4
  192. package/lib/esm/lib/circularImports.js.map +1 -1
  193. package/lib/esm/lib/clearResourcesPlan.d.ts +0 -1
  194. package/lib/esm/lib/clearResourcesPlan.js +12 -12
  195. package/lib/esm/lib/clearResourcesPlan.js.map +1 -1
  196. package/lib/esm/lib/createOutput.d.ts +2 -0
  197. package/lib/esm/lib/createOutput.js +35 -0
  198. package/lib/esm/lib/createOutput.js.map +1 -0
  199. package/lib/esm/lib/deployPrompts.d.ts +2 -1
  200. package/lib/esm/lib/deployPrompts.js +17 -17
  201. package/lib/esm/lib/deployPrompts.js.map +1 -1
  202. package/lib/esm/lib/devServerEntry.js +5 -4
  203. package/lib/esm/lib/devServerEntry.js.map +1 -1
  204. package/lib/esm/lib/devServerPorts.js +2 -3
  205. package/lib/esm/lib/devServerPorts.js.map +1 -1
  206. package/lib/esm/lib/discovery.d.ts +0 -2
  207. package/lib/esm/lib/discovery.js +10 -38
  208. package/lib/esm/lib/discovery.js.map +1 -1
  209. package/lib/esm/lib/hooks.js +5 -3
  210. package/lib/esm/lib/hooks.js.map +1 -1
  211. package/lib/esm/lib/killChildWithEscalation.js +3 -1
  212. package/lib/esm/lib/killChildWithEscalation.js.map +1 -1
  213. package/lib/esm/lib/killStaleListeners.js +24 -37
  214. package/lib/esm/lib/killStaleListeners.js.map +1 -1
  215. package/lib/esm/lib/prompts.d.ts +1 -0
  216. package/lib/esm/lib/prompts.js +13 -1
  217. package/lib/esm/lib/prompts.js.map +1 -1
  218. package/lib/esm/lib/qpqConfigs.d.ts +1 -0
  219. package/lib/esm/lib/qpqConfigs.js +9 -5
  220. package/lib/esm/lib/qpqConfigs.js.map +1 -1
  221. package/lib/esm/lib/requireDriverCapability.d.ts +7 -0
  222. package/lib/esm/lib/requireDriverCapability.js +13 -0
  223. package/lib/esm/lib/requireDriverCapability.js.map +1 -0
  224. package/lib/esm/lib/resolveAppSelection.d.ts +1 -0
  225. package/lib/esm/lib/resolveAppSelection.js +17 -11
  226. package/lib/esm/lib/resolveAppSelection.js.map +1 -1
  227. package/lib/esm/lib/resolveCommandContext.d.ts +12 -0
  228. package/lib/esm/lib/resolveCommandContext.js +12 -0
  229. package/lib/esm/lib/resolveCommandContext.js.map +1 -0
  230. package/lib/esm/lib/resolveDeployment.d.ts +0 -1
  231. package/lib/esm/lib/resolveDeployment.js +13 -8
  232. package/lib/esm/lib/resolveDeployment.js.map +1 -1
  233. package/lib/esm/lib/runCommand.d.ts +20 -0
  234. package/lib/esm/lib/runCommand.js +52 -31
  235. package/lib/esm/lib/runCommand.js.map +1 -1
  236. package/lib/esm/lib/runTasks.js +8 -4
  237. package/lib/esm/lib/runTasks.js.map +1 -1
  238. package/lib/esm/lib/timing.js +5 -3
  239. package/lib/esm/lib/timing.js.map +1 -1
  240. package/lib/esm/lib/typeWatcher.d.ts +2 -1
  241. package/lib/esm/lib/typeWatcher.js +6 -13
  242. package/lib/esm/lib/typeWatcher.js.map +1 -1
  243. package/lib/esm/lib/userScriptBuilderLayer.d.ts +0 -2
  244. package/lib/esm/lib/userScriptBuilderLayer.js +5 -5
  245. package/lib/esm/lib/userScriptBuilderLayer.js.map +1 -1
  246. package/lib/esm/lib/views.js +3 -2
  247. package/lib/esm/lib/views.js.map +1 -1
  248. package/lib/esm/lib/webAddressingEnv.d.ts +0 -2
  249. package/lib/esm/lib/webAddressingEnv.js +1 -1
  250. package/lib/esm/lib/webAddressingEnv.js.map +1 -1
  251. package/lib/esm/platforms/aws/awsCredentials.js +3 -1
  252. package/lib/esm/platforms/aws/awsCredentials.js.map +1 -1
  253. package/lib/esm/platforms/aws/cdkApp.js +1 -1
  254. package/lib/esm/platforms/aws/cdkApp.js.map +1 -1
  255. package/lib/esm/platforms/aws/clearResources.js +18 -26
  256. package/lib/esm/platforms/aws/clearResources.js.map +1 -1
  257. package/lib/esm/platforms/aws/getDeployEnv.d.ts +1 -0
  258. package/lib/esm/platforms/aws/getDeployEnv.js +8 -0
  259. package/lib/esm/platforms/aws/getDeployEnv.js.map +1 -0
  260. package/lib/esm/platforms/aws/go.js +17 -15
  261. package/lib/esm/platforms/aws/go.js.map +1 -1
  262. package/lib/esm/platforms/aws/goDocker.js +29 -26
  263. package/lib/esm/platforms/aws/goDocker.js.map +1 -1
  264. package/lib/esm/platforms/aws/goDockerManifest.js +6 -1
  265. package/lib/esm/platforms/aws/goDockerManifest.js.map +1 -1
  266. package/lib/esm/platforms/aws/publish.js +13 -11
  267. package/lib/esm/platforms/aws/publish.js.map +1 -1
  268. package/lib/esm/platforms/aws/remote.js +9 -7
  269. package/lib/esm/platforms/aws/remote.js.map +1 -1
  270. package/lib/esm/platforms/aws/setup/buildBucketNamesStep.js +2 -2
  271. package/lib/esm/platforms/aws/setup/buildBucketNamesStep.js.map +1 -1
  272. package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.js +4 -3
  273. package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.js.map +1 -1
  274. package/lib/esm/platforms/aws/stacks.d.ts +0 -1
  275. package/lib/esm/platforms/aws/stacks.js +20 -32
  276. package/lib/esm/platforms/aws/stacks.js.map +1 -1
  277. package/lib/esm/platforms/aws/teardown.js +9 -7
  278. package/lib/esm/platforms/aws/teardown.js.map +1 -1
  279. package/lib/esm/platforms/aws/viewsSync.js +22 -10
  280. package/lib/esm/platforms/aws/viewsSync.js.map +1 -1
  281. package/lib/esm/platforms/docker/go.js +21 -19
  282. package/lib/esm/platforms/docker/go.js.map +1 -1
  283. package/lib/esm/platforms/docker/index.js +5 -4
  284. package/lib/esm/platforms/docker/index.js.map +1 -1
  285. package/lib/esm/platforms/docker/parseMounts.js +3 -2
  286. package/lib/esm/platforms/docker/parseMounts.js.map +1 -1
  287. package/lib/esm/platforms/index.js +4 -2
  288. package/lib/esm/platforms/index.js.map +1 -1
  289. package/lib/esm/platforms/types.d.ts +2 -2
  290. package/lib/esm/platforms/types.js.map +1 -1
  291. package/package.json +28 -15
  292. package/templates/user-script/README.md +1 -1
@@ -1,3 +1,4 @@
1
+ import { awsNamingUtils } from '@qpqjs/config-aws';
1
2
  // Build a backend service as a module-federation remote and publish it into the
2
3
  // shared federated-code storage drive, under the service's own prefix. After this
3
4
  // runs, the already-deployed lambdas load their stories from the bucket (quidproquo's
@@ -17,15 +18,16 @@
17
18
  // deployRemoteManifest - upload manifest.json, atomically flipping lambdas over
18
19
  // Version files always land before the manifest that references them, so a reading
19
20
  // lambda never sees a manifest whose files aren't all present yet.
20
- import { awsNamingUtils } from '@qpqjs/actionprocessor-aws';
21
- import { qpqCoreUtils } from '@qpqjs/core';
21
+ import { getServiceRemotePublishedDistPath, qpqCoreUtils } from '@qpqjs/core';
22
22
  import { getServiceRemoteRspackConfig, publishFederatedRemote, runRspack } from '@qpqjs/deploy-rspack';
23
23
  import fs from 'fs';
24
24
  import path from 'path';
25
+ import { createOutput } from '../../lib/createOutput';
25
26
  import { getRoot, getServiceDirectory } from '../../lib/discovery';
26
27
  import { loadServiceQpqConfig } from '../../lib/qpqConfigs';
27
28
  import { runCommand } from '../../lib/runCommand';
28
- const getPublishPath = (appName, serviceName) => path.join(getRoot(), 'dist', 'apps', appName, 'services', serviceName, 'service-remote-published');
29
+ const out = createOutput('aws:remote');
30
+ const getPublishPath = (appName, serviceName) => getServiceRemotePublishedDistPath(getRoot(), appName, serviceName);
29
31
  // Resolve the shared store bucket from the service's defineFederatedModuleStore
30
32
  // setting (same derivation the lambda env var uses) and the per-service prefix
31
33
  // so many services share one bucket.
@@ -39,7 +41,7 @@ const resolveRemoteStore = (appName, serviceName) => {
39
41
  if (!storageDrive) {
40
42
  throw new Error(`defineFederatedModuleStore references storage drive [${federatedStore.storageDrive}] which service [${serviceName}] does not declare`);
41
43
  }
42
- const bucketName = awsNamingUtils.resolveConfigRuntimeResourceNameFromConfig(storageDrive.owner?.resourceNameOverride || storageDrive.storageDrive, qpqConfig, storageDrive.owner);
44
+ const bucketName = awsNamingUtils.resolveOwnedResourceName(storageDrive.storageDrive, qpqConfig, storageDrive.owner);
43
45
  const servicePrefix = qpqCoreUtils.getApplicationModuleName(qpqConfig);
44
46
  return { bucketName, servicePrefix };
45
47
  };
@@ -50,7 +52,7 @@ export const buildRemote = async (appName, serviceName) => {
50
52
  resolveRemoteStore(appName, serviceName);
51
53
  const serviceDir = path.join(getServiceDirectory(appName, serviceName), 'service');
52
54
  const qpqConfig = loadServiceQpqConfig(appName, serviceName);
53
- console.log(`Building federated remote: [${serviceName}]`);
55
+ out.log(`Building federated remote: [${serviceName}]`);
54
56
  const config = getServiceRemoteRspackConfig(qpqConfig, serviceDir);
55
57
  await runRspack(config);
56
58
  const remoteBuildPath = config.output.path;
@@ -76,14 +78,14 @@ export const resolvePublishTarget = (appName, serviceName) => {
76
78
  // services - each writes only under its own prefix.
77
79
  export const uploadRemoteVersion = async (target) => {
78
80
  const { serviceName, publishPath, manifestHash, bucketName, servicePrefix } = target;
79
- console.log(`Uploading [${serviceName}] version [${manifestHash}] to [s3://${bucketName}/${servicePrefix}/${manifestHash}]`);
81
+ out.log(`Uploading [${serviceName}] version [${manifestHash}] to [s3://${bucketName}/${servicePrefix}/${manifestHash}]`);
80
82
  await runCommand('aws', ['s3', 'sync', path.join(publishPath, manifestHash), `s3://${bucketName}/${servicePrefix}/${manifestHash}`]);
81
83
  };
82
84
  // Phase 3: upload manifest.json, the pointer lambdas read - this is the moment
83
85
  // the new version goes live. Only run once the version dir is fully uploaded.
84
86
  export const deployRemoteManifest = async (target) => {
85
87
  const { serviceName, publishPath, manifestHash, bucketName, servicePrefix } = target;
86
- console.log(`Deploying [${serviceName}] manifest for version [${manifestHash}] to [s3://${bucketName}/${servicePrefix}/manifest.json]`);
88
+ out.log(`Deploying [${serviceName}] manifest for version [${manifestHash}] to [s3://${bucketName}/${servicePrefix}/manifest.json]`);
87
89
  await runCommand('aws', ['s3', 'cp', path.join(publishPath, 'manifest.json'), `s3://${bucketName}/${servicePrefix}/manifest.json`]);
88
90
  };
89
91
  //# sourceMappingURL=remote.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../../../src/platforms/aws/remote.ts"],"names":[],"mappings":"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","sourcesContent":["// Build a backend service as a module-federation remote and publish it into the\n// shared federated-code storage drive, under the service's own prefix. After this\n// runs, the already-deployed lambdas load their stories from the bucket (quidproquo's\n// dynamicModuleLoader tries the federated store first, falls back to the bundled\n// code when nothing's published).\n//\n// The store is one storage drive shared across services (via `owner`). Each service\n// opts in with defineFederatedModuleStore(<driveName>) and its lambdas read from\n// s3://<bucket>/<service>. Read access comes from the storage drive's own grants, so\n// the drive + a deploy of each service must exist before publishing. The bucket name\n// resolves from the same service config the deploy used, so it always matches.\n//\n// Publishing is split into three phases so they can run as separate commands\n// (publish:build / publish:upload / publish:deploy) or chained by `qpq publish`:\n// buildRemote - rspack the remote + lay out service-remote-published/\n// uploadRemoteVersion - sync the hashed version dir (everything BUT manifest.json)\n// deployRemoteManifest - upload manifest.json, atomically flipping lambdas over\n// Version files always land before the manifest that references them, so a reading\n// lambda never sees a manifest whose files aren't all present yet.\nimport { awsNamingUtils } from '@qpqjs/actionprocessor-aws';\nimport { qpqCoreUtils } from '@qpqjs/core';\nimport { getServiceRemoteRspackConfig, publishFederatedRemote, runRspack } from '@qpqjs/deploy-rspack';\n\nimport fs from 'fs';\nimport path from 'path';\n\nimport { getRoot, getServiceDirectory } from '../../lib/discovery';\nimport { loadServiceQpqConfig } from '../../lib/qpqConfigs';\nimport { runCommand } from '../../lib/runCommand';\n\n// Everything the upload/deploy phases need to publish a built remote. Resolved\n// from the on-disk build output + service config, so each phase can run in its\n// own process without handing state between commands.\nexport type RemotePublishTarget = {\n serviceName: string;\n publishPath: string;\n manifestHash: string;\n bucketName: string;\n servicePrefix: string;\n};\n\nconst getPublishPath = (appName: string, serviceName: string): string =>\n path.join(getRoot(), 'dist', 'apps', appName, 'services', serviceName, 'service-remote-published');\n\n// Resolve the shared store bucket from the service's defineFederatedModuleStore\n// setting (same derivation the lambda env var uses) and the per-service prefix\n// so many services share one bucket.\nconst resolveRemoteStore = (appName: string, serviceName: string): { bucketName: string; servicePrefix: string } => {\n const qpqConfig = loadServiceQpqConfig(appName, serviceName);\n\n const federatedStore = qpqCoreUtils.getFederatedModuleStore(qpqConfig);\n if (!federatedStore) {\n throw new Error(\n `service [${serviceName}] has no defineFederatedModuleStore(...) - add it (and the matching storage drive) to opt into federation`,\n );\n }\n const storageDrive = qpqCoreUtils.getStorageDriveByName(federatedStore.storageDrive, qpqConfig);\n if (!storageDrive) {\n throw new Error(\n `defineFederatedModuleStore references storage drive [${federatedStore.storageDrive}] which service [${serviceName}] does not declare`,\n );\n }\n const bucketName = awsNamingUtils.resolveConfigRuntimeResourceNameFromConfig(\n storageDrive.owner?.resourceNameOverride || storageDrive.storageDrive,\n qpqConfig,\n storageDrive.owner,\n );\n const servicePrefix = qpqCoreUtils.getApplicationModuleName(qpqConfig);\n\n return { bucketName, servicePrefix };\n};\n\n// Phase 1: rspack the federated remote container and lay out the publishable\n// version dir + manifest.json under service-remote-published/. Validates the\n// federation config up front so a misconfigured service fails before the build.\nexport const buildRemote = async (appName: string, serviceName: string): Promise<void> => {\n resolveRemoteStore(appName, serviceName);\n\n const serviceDir = path.join(getServiceDirectory(appName, serviceName), 'service');\n const qpqConfig = loadServiceQpqConfig(appName, serviceName);\n\n console.log(`Building federated remote: [${serviceName}]`);\n const config = getServiceRemoteRspackConfig(qpqConfig, serviceDir);\n await runRspack(config);\n\n const remoteBuildPath = config.output!.path as string;\n publishFederatedRemote(qpqConfig, remoteBuildPath, getPublishPath(appName, serviceName));\n};\n\n// Resolve a publish target from a prior buildRemote's on-disk output.\nexport const resolvePublishTarget = (appName: string, serviceName: string): RemotePublishTarget => {\n const publishPath = getPublishPath(appName, serviceName);\n const manifestPath = path.join(publishPath, 'manifest.json');\n\n if (!fs.existsSync(manifestPath)) {\n throw new Error(`no built remote found for [${serviceName}] (missing ${manifestPath}) - run \\`qpq publish:build --app ${appName}\\` first`);\n }\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as {\n hash: string;\n };\n\n return {\n serviceName,\n publishPath,\n manifestHash: manifest.hash,\n ...resolveRemoteStore(appName, serviceName),\n };\n};\n\n// Phase 2: upload the hashed version dir - everything EXCEPT manifest.json, so\n// nothing referenced by a live manifest changes. Safe to run concurrently across\n// services - each writes only under its own prefix.\nexport const uploadRemoteVersion = async (target: RemotePublishTarget): Promise<void> => {\n const { serviceName, publishPath, manifestHash, bucketName, servicePrefix } = target;\n\n console.log(`Uploading [${serviceName}] version [${manifestHash}] to [s3://${bucketName}/${servicePrefix}/${manifestHash}]`);\n await runCommand('aws', ['s3', 'sync', path.join(publishPath, manifestHash), `s3://${bucketName}/${servicePrefix}/${manifestHash}`]);\n};\n\n// Phase 3: upload manifest.json, the pointer lambdas read - this is the moment\n// the new version goes live. Only run once the version dir is fully uploaded.\nexport const deployRemoteManifest = async (target: RemotePublishTarget): Promise<void> => {\n const { serviceName, publishPath, manifestHash, bucketName, servicePrefix } = target;\n\n console.log(`Deploying [${serviceName}] manifest for version [${manifestHash}] to [s3://${bucketName}/${servicePrefix}/manifest.json]`);\n await runCommand('aws', ['s3', 'cp', path.join(publishPath, 'manifest.json'), `s3://${bucketName}/${servicePrefix}/manifest.json`]);\n};\n"]}
1
+ {"version":3,"file":"remote.js","sourceRoot":"","sources":["../../../../src/platforms/aws/remote.ts"],"names":[],"mappings":"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","sourcesContent":["import { awsNamingUtils } from '@qpqjs/config-aws';\n// Build a backend service as a module-federation remote and publish it into the\n// shared federated-code storage drive, under the service's own prefix. After this\n// runs, the already-deployed lambdas load their stories from the bucket (quidproquo's\n// dynamicModuleLoader tries the federated store first, falls back to the bundled\n// code when nothing's published).\n//\n// The store is one storage drive shared across services (via `owner`). Each service\n// opts in with defineFederatedModuleStore(<driveName>) and its lambdas read from\n// s3://<bucket>/<service>. Read access comes from the storage drive's own grants, so\n// the drive + a deploy of each service must exist before publishing. The bucket name\n// resolves from the same service config the deploy used, so it always matches.\n//\n// Publishing is split into three phases so they can run as separate commands\n// (publish:build / publish:upload / publish:deploy) or chained by `qpq publish`:\n// buildRemote - rspack the remote + lay out service-remote-published/\n// uploadRemoteVersion - sync the hashed version dir (everything BUT manifest.json)\n// deployRemoteManifest - upload manifest.json, atomically flipping lambdas over\n// Version files always land before the manifest that references them, so a reading\n// lambda never sees a manifest whose files aren't all present yet.\nimport { getServiceRemotePublishedDistPath, qpqCoreUtils } from '@qpqjs/core';\nimport { getServiceRemoteRspackConfig, publishFederatedRemote, runRspack } from '@qpqjs/deploy-rspack';\n\nimport fs from 'fs';\nimport path from 'path';\n\nimport { createOutput } from '../../lib/createOutput';\nimport { getRoot, getServiceDirectory } from '../../lib/discovery';\nimport { loadServiceQpqConfig } from '../../lib/qpqConfigs';\nimport { runCommand } from '../../lib/runCommand';\n\nconst out = createOutput('aws:remote');\n\n// Everything the upload/deploy phases need to publish a built remote. Resolved\n// from the on-disk build output + service config, so each phase can run in its\n// own process without handing state between commands.\nexport type RemotePublishTarget = {\n serviceName: string;\n publishPath: string;\n manifestHash: string;\n bucketName: string;\n servicePrefix: string;\n};\n\nconst getPublishPath = (appName: string, serviceName: string): string => getServiceRemotePublishedDistPath(getRoot(), appName, serviceName);\n\n// Resolve the shared store bucket from the service's defineFederatedModuleStore\n// setting (same derivation the lambda env var uses) and the per-service prefix\n// so many services share one bucket.\nconst resolveRemoteStore = (appName: string, serviceName: string): { bucketName: string; servicePrefix: string } => {\n const qpqConfig = loadServiceQpqConfig(appName, serviceName);\n\n const federatedStore = qpqCoreUtils.getFederatedModuleStore(qpqConfig);\n if (!federatedStore) {\n throw new Error(\n `service [${serviceName}] has no defineFederatedModuleStore(...) - add it (and the matching storage drive) to opt into federation`,\n );\n }\n const storageDrive = qpqCoreUtils.getStorageDriveByName(federatedStore.storageDrive, qpqConfig);\n if (!storageDrive) {\n throw new Error(\n `defineFederatedModuleStore references storage drive [${federatedStore.storageDrive}] which service [${serviceName}] does not declare`,\n );\n }\n const bucketName = awsNamingUtils.resolveOwnedResourceName(storageDrive.storageDrive, qpqConfig, storageDrive.owner);\n const servicePrefix = qpqCoreUtils.getApplicationModuleName(qpqConfig);\n\n return { bucketName, servicePrefix };\n};\n\n// Phase 1: rspack the federated remote container and lay out the publishable\n// version dir + manifest.json under service-remote-published/. Validates the\n// federation config up front so a misconfigured service fails before the build.\nexport const buildRemote = async (appName: string, serviceName: string): Promise<void> => {\n resolveRemoteStore(appName, serviceName);\n\n const serviceDir = path.join(getServiceDirectory(appName, serviceName), 'service');\n const qpqConfig = loadServiceQpqConfig(appName, serviceName);\n\n out.log(`Building federated remote: [${serviceName}]`);\n const config = getServiceRemoteRspackConfig(qpqConfig, serviceDir);\n await runRspack(config);\n\n const remoteBuildPath = config.output!.path as string;\n publishFederatedRemote(qpqConfig, remoteBuildPath, getPublishPath(appName, serviceName));\n};\n\n// Resolve a publish target from a prior buildRemote's on-disk output.\nexport const resolvePublishTarget = (appName: string, serviceName: string): RemotePublishTarget => {\n const publishPath = getPublishPath(appName, serviceName);\n const manifestPath = path.join(publishPath, 'manifest.json');\n\n if (!fs.existsSync(manifestPath)) {\n throw new Error(`no built remote found for [${serviceName}] (missing ${manifestPath}) - run \\`qpq publish:build --app ${appName}\\` first`);\n }\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) as {\n hash: string;\n };\n\n return {\n serviceName,\n publishPath,\n manifestHash: manifest.hash,\n ...resolveRemoteStore(appName, serviceName),\n };\n};\n\n// Phase 2: upload the hashed version dir - everything EXCEPT manifest.json, so\n// nothing referenced by a live manifest changes. Safe to run concurrently across\n// services - each writes only under its own prefix.\nexport const uploadRemoteVersion = async (target: RemotePublishTarget): Promise<void> => {\n const { serviceName, publishPath, manifestHash, bucketName, servicePrefix } = target;\n\n out.log(`Uploading [${serviceName}] version [${manifestHash}] to [s3://${bucketName}/${servicePrefix}/${manifestHash}]`);\n await runCommand('aws', ['s3', 'sync', path.join(publishPath, manifestHash), `s3://${bucketName}/${servicePrefix}/${manifestHash}`]);\n};\n\n// Phase 3: upload manifest.json, the pointer lambdas read - this is the moment\n// the new version goes live. Only run once the version dir is fully uploaded.\nexport const deployRemoteManifest = async (target: RemotePublishTarget): Promise<void> => {\n const { serviceName, publishPath, manifestHash, bucketName, servicePrefix } = target;\n\n out.log(`Deploying [${serviceName}] manifest for version [${manifestHash}] to [s3://${bucketName}/${servicePrefix}/manifest.json]`);\n await runCommand('aws', ['s3', 'cp', path.join(publishPath, 'manifest.json'), `s3://${bucketName}/${servicePrefix}/manifest.json`]);\n};\n"]}
@@ -1,11 +1,11 @@
1
- import { awsNamingUtils } from '@qpqjs/actionprocessor-aws';
1
+ import { awsNamingUtils } from '@qpqjs/config-aws';
2
2
  import { qpqCoreUtils } from '@qpqjs/core';
3
3
  import { qpqWebServerUtils } from '@qpqjs/webserver';
4
4
  import { HeadBucketCommand, S3Client } from '@aws-sdk/client-s3';
5
5
  // Every bucket a service deploy creates, named the way the constructs name them.
6
6
  const getServiceBucketNames = (qpqConfig) => [
7
7
  ...qpqCoreUtils
8
- .getOwnedItems(qpqCoreUtils.getStorageDrives(qpqConfig), qpqConfig)
8
+ .getOwnedStorageDriveConfigs(qpqConfig)
9
9
  .map((drive) => awsNamingUtils.getConfigRuntimeResourceNameFromConfig(drive.storageDrive, qpqConfig)),
10
10
  ...qpqWebServerUtils
11
11
  .getWebEntryConfigs(qpqConfig)
@@ -1 +1 @@
1
- {"version":3,"file":"buildBucketNamesStep.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/buildBucketNamesStep.ts"],"names":[],"mappings":"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","sourcesContent":["import { awsNamingUtils } from '@qpqjs/actionprocessor-aws';\nimport { QPQConfig, qpqCoreUtils } from '@qpqjs/core';\nimport { qpqWebServerUtils } from '@qpqjs/webserver';\n\nimport { HeadBucketCommand, S3Client } from '@aws-sdk/client-s3';\n\nimport { SetupCheckResult, SetupStep } from '../../../lib/setupStep';\nimport { AwsSetupContext } from './awsSetupContext';\n\n// Every bucket a service deploy creates, named the way the constructs name them.\nconst getServiceBucketNames = (qpqConfig: QPQConfig): string[] => [\n ...qpqCoreUtils\n .getOwnedItems(qpqCoreUtils.getStorageDrives(qpqConfig), qpqConfig)\n .map((drive) => awsNamingUtils.getConfigRuntimeResourceNameFromConfig(drive.storageDrive, qpqConfig)),\n ...qpqWebServerUtils\n .getWebEntryConfigs(qpqConfig)\n .filter((webEntry) => !webEntry.storageDrive.sourceStorageDrive)\n .map((webEntry) => awsNamingUtils.getQpqRuntimeResourceNameFromConfig(webEntry.name, qpqConfig, 'we')),\n];\n\ntype BucketOwner = 'free' | 'ours' | 'other';\n\n// HeadBucket: 404 means free, 200 ours, 403 exists in another account.\nconst getBucketOwner = async (client: S3Client, bucketName: string): Promise<BucketOwner> => {\n try {\n await client.send(new HeadBucketCommand({ Bucket: bucketName }));\n return 'ours';\n } catch (error) {\n const status = (error as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode;\n if (status === 404) {\n return 'free';\n }\n if (status === 403 || status === 301) {\n return 'other';\n }\n throw error;\n }\n};\n\n// S3 bucket names are global and the framework names them from app, service and\n// environment alone, so a second account cannot reuse a name the first still holds.\nconst checkBucketNames = async (ctx: AwsSetupContext): Promise<SetupCheckResult> => {\n const names = [...new Set(ctx.serviceQpqConfigs.flatMap(getServiceBucketNames))];\n const client = new S3Client({ region: ctx.region });\n\n const taken: string[] = [];\n let ours = 0;\n for (const name of names) {\n const owner = await getBucketOwner(client, name);\n if (owner === 'other') taken.push(name);\n if (owner === 'ours') ours += 1;\n }\n\n return taken.length === 0\n ? { done: true, detail: `${names.length} bucket name(s) available or already ours (${ours} existing)` }\n : { done: false, detail: `held by another account: ${taken.join(', ')}; delete them there or change the app prefix` };\n};\n\n/** Check-only: no bucket the deploy will create is already taken by another AWS account. */\nexport const buildBucketNamesStep = (ctx: AwsSetupContext): SetupStep => ({\n id: 'bucket-names',\n name: 'S3 bucket names available',\n check: () => checkBucketNames(ctx),\n run: async () => {\n const result = await checkBucketNames(ctx);\n if (!result.done) {\n throw new Error(result.detail ?? 'bucket names taken');\n }\n },\n});\n"]}
1
+ {"version":3,"file":"buildBucketNamesStep.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/buildBucketNamesStep.ts"],"names":[],"mappings":"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","sourcesContent":["import { awsNamingUtils } from '@qpqjs/config-aws';\nimport { QPQConfig, qpqCoreUtils } from '@qpqjs/core';\nimport { qpqWebServerUtils } from '@qpqjs/webserver';\n\nimport { HeadBucketCommand, S3Client } from '@aws-sdk/client-s3';\n\nimport { SetupCheckResult, SetupStep } from '../../../lib/setupStep';\nimport { AwsSetupContext } from './awsSetupContext';\n\n// Every bucket a service deploy creates, named the way the constructs name them.\nconst getServiceBucketNames = (qpqConfig: QPQConfig): string[] => [\n ...qpqCoreUtils\n .getOwnedStorageDriveConfigs(qpqConfig)\n .map((drive) => awsNamingUtils.getConfigRuntimeResourceNameFromConfig(drive.storageDrive, qpqConfig)),\n ...qpqWebServerUtils\n .getWebEntryConfigs(qpqConfig)\n .filter((webEntry) => !webEntry.storageDrive.sourceStorageDrive)\n .map((webEntry) => awsNamingUtils.getQpqRuntimeResourceNameFromConfig(webEntry.name, qpqConfig, 'we')),\n];\n\ntype BucketOwner = 'free' | 'ours' | 'other';\n\n// HeadBucket: 404 means free, 200 ours, 403 exists in another account.\nconst getBucketOwner = async (client: S3Client, bucketName: string): Promise<BucketOwner> => {\n try {\n await client.send(new HeadBucketCommand({ Bucket: bucketName }));\n return 'ours';\n } catch (error) {\n const status = (error as { $metadata?: { httpStatusCode?: number } }).$metadata?.httpStatusCode;\n if (status === 404) {\n return 'free';\n }\n if (status === 403 || status === 301) {\n return 'other';\n }\n throw error;\n }\n};\n\n// S3 bucket names are global and the framework names them from app, service and\n// environment alone, so a second account cannot reuse a name the first still holds.\nconst checkBucketNames = async (ctx: AwsSetupContext): Promise<SetupCheckResult> => {\n const names = [...new Set(ctx.serviceQpqConfigs.flatMap(getServiceBucketNames))];\n const client = new S3Client({ region: ctx.region });\n\n const taken: string[] = [];\n let ours = 0;\n for (const name of names) {\n const owner = await getBucketOwner(client, name);\n if (owner === 'other') taken.push(name);\n if (owner === 'ours') ours += 1;\n }\n\n return taken.length === 0\n ? { done: true, detail: `${names.length} bucket name(s) available or already ours (${ours} existing)` }\n : { done: false, detail: `held by another account: ${taken.join(', ')}; delete them there or change the app prefix` };\n};\n\n/** Check-only: no bucket the deploy will create is already taken by another AWS account. */\nexport const buildBucketNamesStep = (ctx: AwsSetupContext): SetupStep => ({\n id: 'bucket-names',\n name: 'S3 bucket names available',\n check: () => checkBucketNames(ctx),\n run: async () => {\n const result = await checkBucketNames(ctx);\n if (!result.done) {\n throw new Error(result.detail ?? 'bucket names taken');\n }\n },\n});\n"]}
@@ -1,6 +1,7 @@
1
- import { awsNamingUtils } from '@qpqjs/actionprocessor-aws';
2
- import { qpqConfigAwsUtils } from '@qpqjs/config-aws';
1
+ import { awsNamingUtils, qpqConfigAwsUtils } from '@qpqjs/config-aws';
3
2
  import { GetRoleCommand, IAMClient } from '@aws-sdk/client-iam';
3
+ import { createOutput } from '../../../lib/createOutput';
4
+ const out = createOutput('aws:build-github-deploy-role-step');
4
5
  // The role itself is deployed by the bootstrap stack; this step tells you whether the config
5
6
  // declares it, whether it exists yet, and what to put in the GitHub Environment once it does.
6
7
  const checkDeployRole = async (ctx) => {
@@ -34,7 +35,7 @@ export const buildGithubDeployRoleStep = (ctx) => ({
34
35
  if (!result.done) {
35
36
  throw new Error(result.detail ?? 'deploy role missing');
36
37
  }
37
- console.log(result.detail);
38
+ out.log(result.detail);
38
39
  },
39
40
  });
40
41
  //# sourceMappingURL=buildGithubDeployRoleStep.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"buildGithubDeployRoleStep.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/buildGithubDeployRoleStep.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAKhE,6FAA6F;AAC7F,8FAA8F;AAC9F,MAAM,eAAe,GAAG,KAAK,EAAE,GAAoB,EAA6B,EAAE;IAChF,MAAM,MAAM,GAAG,iBAAiB,CAAC,4BAA4B,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACtF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,0GAA0G,EAAE,CAAC;IAC7I,CAAC;IAED,MAAM,QAAQ,GAAG,cAAc,CAAC,iCAAiC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC1F,MAAM,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,IAAI,GAAG,CAAC,WAAW,CAAC;IACtE,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC9G,OAAO;YACL,IAAI,EAAE,IAAI;YACV,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,yBAAyB,iBAAiB,qDAAqD,GAAG,CAAC,MAAM,EAAE;SAChI,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;YACrE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,oDAAoD,EAAE,CAAC;QAClG,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,yHAAyH;AACzH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,GAAoB,EAAa,EAAE,CAAC,CAAC;IAC7E,EAAE,EAAE,oBAAoB;IACxB,IAAI,EAAE,4BAA4B;IAClC,KAAK,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;IACjC,GAAG,EAAE,KAAK,IAAI,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,qBAAqB,CAAC,CAAC;QAC1D,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAC7B,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { awsNamingUtils } from '@qpqjs/actionprocessor-aws';\nimport { qpqConfigAwsUtils } from '@qpqjs/config-aws';\n\nimport { GetRoleCommand, IAMClient } from '@aws-sdk/client-iam';\n\nimport { SetupCheckResult, SetupStep } from '../../../lib/setupStep';\nimport { AwsSetupContext } from './awsSetupContext';\n\n// The role itself is deployed by the bootstrap stack; this step tells you whether the config\n// declares it, whether it exists yet, and what to put in the GitHub Environment once it does.\nconst checkDeployRole = async (ctx: AwsSetupContext): Promise<SetupCheckResult> => {\n const config = qpqConfigAwsUtils.getAwsGithubDeployRoleConfig(ctx.bootstrapQpqConfig);\n if (!config) {\n return { done: false, detail: 'not declared: add defineAwsGithubDeployRole(\"owner/repo\", { ownerId, repositoryId }) to bootstrap.qpq.ts' };\n }\n\n const roleName = awsNamingUtils.getGithubDeployRoleNameFromConfig(ctx.bootstrapQpqConfig);\n const githubEnvironment = config.githubEnvironment ?? ctx.environment;\n try {\n const { Role } = await new IAMClient({ region: ctx.region }).send(new GetRoleCommand({ RoleName: roleName }));\n return {\n done: true,\n detail: `${Role?.Arn}; GitHub Environment '${githubEnvironment}' needs DEPLOY_ROLE_ARN=<that arn> and AWS_REGION=${ctx.region}`,\n };\n } catch (error) {\n if (error instanceof Error && error.name === 'NoSuchEntityException') {\n return { done: false, detail: `${roleName} not deployed yet (the bootstrap stack creates it)` };\n }\n throw error;\n }\n};\n\n/** Check-only: the GitHub Actions deploy role is declared and deployed, with the values the GitHub Environment needs. */\nexport const buildGithubDeployRoleStep = (ctx: AwsSetupContext): SetupStep => ({\n id: 'github-deploy-role',\n name: 'GitHub Actions deploy role',\n check: () => checkDeployRole(ctx),\n run: async () => {\n const result = await checkDeployRole(ctx);\n if (!result.done) {\n throw new Error(result.detail ?? 'deploy role missing');\n }\n console.log(result.detail);\n },\n});\n"]}
1
+ {"version":3,"file":"buildGithubDeployRoleStep.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/buildGithubDeployRoleStep.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAEhE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAIzD,MAAM,GAAG,GAAG,YAAY,CAAC,mCAAmC,CAAC,CAAC;AAE9D,6FAA6F;AAC7F,8FAA8F;AAC9F,MAAM,eAAe,GAAG,KAAK,EAAE,GAAoB,EAA6B,EAAE;IAChF,MAAM,MAAM,GAAG,iBAAiB,CAAC,4BAA4B,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACtF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,0GAA0G,EAAE,CAAC;IAC7I,CAAC;IAED,MAAM,QAAQ,GAAG,cAAc,CAAC,iCAAiC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAC1F,MAAM,iBAAiB,GAAG,MAAM,CAAC,iBAAiB,IAAI,GAAG,CAAC,WAAW,CAAC;IACtE,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,IAAI,SAAS,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,cAAc,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC9G,OAAO;YACL,IAAI,EAAE,IAAI;YACV,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,yBAAyB,iBAAiB,qDAAqD,GAAG,CAAC,MAAM,EAAE;SAChI,CAAC;IACJ,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,uBAAuB,EAAE,CAAC;YACrE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,oDAAoD,EAAE,CAAC;QAClG,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAC;AAEF,yHAAyH;AACzH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,GAAoB,EAAa,EAAE,CAAC,CAAC;IAC7E,EAAE,EAAE,oBAAoB;IACxB,IAAI,EAAE,4BAA4B;IAClC,KAAK,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,GAAG,CAAC;IACjC,GAAG,EAAE,KAAK,IAAI,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,IAAI,qBAAqB,CAAC,CAAC;QAC1D,CAAC;QACD,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { awsNamingUtils, qpqConfigAwsUtils } from '@qpqjs/config-aws';\n\nimport { GetRoleCommand, IAMClient } from '@aws-sdk/client-iam';\n\nimport { createOutput } from '../../../lib/createOutput';\nimport { SetupCheckResult, SetupStep } from '../../../lib/setupStep';\nimport { AwsSetupContext } from './awsSetupContext';\n\nconst out = createOutput('aws:build-github-deploy-role-step');\n\n// The role itself is deployed by the bootstrap stack; this step tells you whether the config\n// declares it, whether it exists yet, and what to put in the GitHub Environment once it does.\nconst checkDeployRole = async (ctx: AwsSetupContext): Promise<SetupCheckResult> => {\n const config = qpqConfigAwsUtils.getAwsGithubDeployRoleConfig(ctx.bootstrapQpqConfig);\n if (!config) {\n return { done: false, detail: 'not declared: add defineAwsGithubDeployRole(\"owner/repo\", { ownerId, repositoryId }) to bootstrap.qpq.ts' };\n }\n\n const roleName = awsNamingUtils.getGithubDeployRoleNameFromConfig(ctx.bootstrapQpqConfig);\n const githubEnvironment = config.githubEnvironment ?? ctx.environment;\n try {\n const { Role } = await new IAMClient({ region: ctx.region }).send(new GetRoleCommand({ RoleName: roleName }));\n return {\n done: true,\n detail: `${Role?.Arn}; GitHub Environment '${githubEnvironment}' needs DEPLOY_ROLE_ARN=<that arn> and AWS_REGION=${ctx.region}`,\n };\n } catch (error) {\n if (error instanceof Error && error.name === 'NoSuchEntityException') {\n return { done: false, detail: `${roleName} not deployed yet (the bootstrap stack creates it)` };\n }\n throw error;\n }\n};\n\n/** Check-only: the GitHub Actions deploy role is declared and deployed, with the values the GitHub Environment needs. */\nexport const buildGithubDeployRoleStep = (ctx: AwsSetupContext): SetupStep => ({\n id: 'github-deploy-role',\n name: 'GitHub Actions deploy role',\n check: () => checkDeployRole(ctx),\n run: async () => {\n const result = await checkDeployRole(ctx);\n if (!result.done) {\n throw new Error(result.detail ?? 'deploy role missing');\n }\n out.log(result.detail);\n },\n});\n"]}
@@ -1,6 +1,5 @@
1
1
  export declare const deployStack: (stackName: string, env?: Record<string, string>) => Promise<void>;
2
2
  export declare const deployServiceStack: (serviceName: string, stackName: string, appName: string) => Promise<void>;
3
- export declare const destroyStack: (stackName: string, env?: Record<string, string>) => Promise<void>;
4
3
  export declare const destroyServiceStack: (serviceName: string, stackName: string, appName: string) => Promise<void>;
5
4
  export declare const deployDomainStack: (appName: string) => Promise<void>;
6
5
  export declare const deployAccountStack: (appName: string) => Promise<void>;
@@ -1,51 +1,45 @@
1
+ import { awsNamingUtils } from '@qpqjs/config-aws';
1
2
  // Host-side `cdk deploy` helpers. No cdk.json anywhere: the CDK app command is
2
3
  // passed via --app and runs @qpqjs/deploy-aws's generic workspace app
3
4
  // (bin/qpq-cdk-app.js) under ts-node hooks. The child inherits the primed
4
5
  // deployment env (DEPLOY_APP_NAME / DEPLOY_NAME and everything resolveDeployment
5
6
  // set); only the per-invocation service is added here.
6
- import { QpqDeployEnvVar } from '@qpqjs/core';
7
+ import { getCdkOutPath, QpqDeployEnvVar } from '@qpqjs/core';
7
8
  import { qpqDeployAwsUtils } from '@qpqjs/deploy-aws';
8
- import path from 'path';
9
- import { getRoot, getServiceNames } from '../../lib/discovery';
10
- import { getAppPrefix, loadServiceQpqConfig } from '../../lib/qpqConfigs';
9
+ import { createOutput } from '../../lib/createOutput';
10
+ import { getRoot } from '../../lib/discovery';
11
+ import { getAppPrefix, loadFirstServiceQpqConfig } from '../../lib/qpqConfigs';
11
12
  import { runCommand } from '../../lib/runCommand';
12
13
  import { logTimeEnd, logTimeStart } from '../../lib/timing';
13
14
  import { getCdkAppCommand } from './cdkApp';
15
+ import { getDeployEnv } from './getDeployEnv';
16
+ const out = createOutput('aws:stacks');
14
17
  export const deployStack = async (stackName, env = {}) => {
15
18
  logTimeStart(stackName);
16
- await runCommand('npx', ['cdk', 'deploy', stackName, '--require-approval', 'never', '--app', `'${getCdkAppCommand()}'`, '--output', path.join('dist', 'qpq', 'cdk.out')], {
19
+ await runCommand('npx', ['cdk', 'deploy', stackName, '--require-approval', 'never', '--app', `'${getCdkAppCommand()}'`, '--output', getCdkOutPath(getRoot())], {
17
20
  cwd: getRoot(),
18
21
  env,
19
22
  });
20
23
  logTimeEnd(stackName);
21
24
  };
22
- export const deployServiceStack = (serviceName, stackName, appName) => deployStack(stackName, {
23
- DEPLOY_SERVICE_NAME: serviceName,
24
- DEPLOY_APP_NAME: appName,
25
- });
25
+ export const deployServiceStack = (serviceName, stackName, appName) => deployStack(stackName, getDeployEnv(appName, serviceName));
26
26
  // `cdk destroy` still synths the app to find the stack, so it runs the same
27
- // generic workspace app with the same DEPLOY_* env as deploys — meaning the
27
+ // generic workspace app with the same DEPLOY_* env as deploys, meaning the
28
28
  // service bundles under dist/ must exist for asset resolution to succeed.
29
- export const destroyStack = async (stackName, env = {}) => {
29
+ const destroyStack = async (stackName, env = {}) => {
30
30
  logTimeStart(stackName);
31
- await runCommand('npx', ['cdk', 'destroy', stackName, '--force', '--app', `'${getCdkAppCommand()}'`, '--output', path.join('dist', 'qpq', 'cdk.out')], {
31
+ await runCommand('npx', ['cdk', 'destroy', stackName, '--force', '--app', `'${getCdkAppCommand()}'`, '--output', getCdkOutPath(getRoot())], {
32
32
  cwd: getRoot(),
33
33
  env,
34
34
  });
35
35
  logTimeEnd(stackName);
36
36
  };
37
- export const destroyServiceStack = (serviceName, stackName, appName) => destroyStack(stackName, {
38
- DEPLOY_SERVICE_NAME: serviceName,
39
- DEPLOY_APP_NAME: appName,
40
- });
37
+ export const destroyServiceStack = (serviceName, stackName, appName) => destroyStack(stackName, getDeployEnv(appName, serviceName));
41
38
  export const deployDomainStack = async (appName) => {
42
- const qpqConfig = loadServiceQpqConfig(appName, getServiceNames(appName)[0]);
39
+ const qpqConfig = loadFirstServiceQpqConfig(appName);
43
40
  const stackName = qpqDeployAwsUtils.getDomainStackName(qpqConfig);
44
- console.log('Deploy Domain: ', stackName);
45
- await deployStack(stackName, {
46
- DEPLOY_SERVICE_NAME: '',
47
- DEPLOY_APP_NAME: appName,
48
- });
41
+ out.log('Deploy Domain: ', stackName);
42
+ await deployStack(stackName, getDeployEnv(appName));
49
43
  };
50
44
  // Account-level guardrails stack - statically named (one per AWS account,
51
45
  // app-agnostic), so no config load is needed to know the name. Callers decide
@@ -53,20 +47,14 @@ export const deployDomainStack = async (appName) => {
53
47
  // 'account' menu option always deploys).
54
48
  export const deployAccountStack = async (appName) => {
55
49
  const stackName = qpqDeployAwsUtils.getAccountStackName();
56
- console.log('Deploy Account: ', stackName);
57
- await deployStack(stackName, {
58
- DEPLOY_SERVICE_NAME: '',
59
- DEPLOY_APP_NAME: appName,
60
- });
50
+ out.log('Deploy Account: ', stackName);
51
+ await deployStack(stackName, getDeployEnv(appName));
61
52
  };
62
53
  export const deployBootstrapStack = async (appName) => {
63
54
  const appPrefix = getAppPrefix(appName);
64
55
  const environment = process.env[QpqDeployEnvVar.environment];
65
56
  const feature = process.env[QpqDeployEnvVar.featureName];
66
- const stackName = feature ? `${appPrefix}-${environment}-${feature}-bs` : `${appPrefix}-${environment}-bs`;
67
- await deployStack(stackName, {
68
- DEPLOY_SERVICE_NAME: '',
69
- DEPLOY_APP_NAME: appName,
70
- });
57
+ const stackName = awsNamingUtils.getAppStackName(appPrefix, environment ?? '', 'bs', feature);
58
+ await deployStack(stackName, getDeployEnv(appName));
71
59
  };
72
60
  //# sourceMappingURL=stacks.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"stacks.js","sourceRoot":"","sources":["../../../../src/platforms/aws/stacks.ts"],"names":[],"mappings":"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","sourcesContent":["// Host-side `cdk deploy` helpers. No cdk.json anywhere: the CDK app command is\n// passed via --app and runs @qpqjs/deploy-aws's generic workspace app\n// (bin/qpq-cdk-app.js) under ts-node hooks. The child inherits the primed\n// deployment env (DEPLOY_APP_NAME / DEPLOY_NAME and everything resolveDeployment\n// set); only the per-invocation service is added here.\nimport { QpqDeployEnvVar } from '@qpqjs/core';\nimport { qpqDeployAwsUtils } from '@qpqjs/deploy-aws';\n\nimport path from 'path';\n\nimport { getRoot, getServiceNames } from '../../lib/discovery';\nimport { getAppPrefix, loadServiceQpqConfig } from '../../lib/qpqConfigs';\nimport { runCommand } from '../../lib/runCommand';\nimport { logTimeEnd, logTimeStart } from '../../lib/timing';\nimport { getCdkAppCommand } from './cdkApp';\n\nexport const deployStack = async (stackName: string, env: Record<string, string> = {}): Promise<void> => {\n logTimeStart(stackName);\n await runCommand(\n 'npx',\n ['cdk', 'deploy', stackName, '--require-approval', 'never', '--app', `'${getCdkAppCommand()}'`, '--output', path.join('dist', 'qpq', 'cdk.out')],\n {\n cwd: getRoot(),\n env,\n },\n );\n logTimeEnd(stackName);\n};\n\nexport const deployServiceStack = (serviceName: string, stackName: string, appName: string): Promise<void> =>\n deployStack(stackName, {\n DEPLOY_SERVICE_NAME: serviceName,\n DEPLOY_APP_NAME: appName,\n });\n\n// `cdk destroy` still synths the app to find the stack, so it runs the same\n// generic workspace app with the same DEPLOY_* env as deploys — meaning the\n// service bundles under dist/ must exist for asset resolution to succeed.\nexport const destroyStack = async (stackName: string, env: Record<string, string> = {}): Promise<void> => {\n logTimeStart(stackName);\n await runCommand(\n 'npx',\n ['cdk', 'destroy', stackName, '--force', '--app', `'${getCdkAppCommand()}'`, '--output', path.join('dist', 'qpq', 'cdk.out')],\n {\n cwd: getRoot(),\n env,\n },\n );\n logTimeEnd(stackName);\n};\n\nexport const destroyServiceStack = (serviceName: string, stackName: string, appName: string): Promise<void> =>\n destroyStack(stackName, {\n DEPLOY_SERVICE_NAME: serviceName,\n DEPLOY_APP_NAME: appName,\n });\n\nexport const deployDomainStack = async (appName: string): Promise<void> => {\n const qpqConfig = loadServiceQpqConfig(appName, getServiceNames(appName)[0]);\n const stackName = qpqDeployAwsUtils.getDomainStackName(qpqConfig);\n console.log('Deploy Domain: ', stackName);\n await deployStack(stackName, {\n DEPLOY_SERVICE_NAME: '',\n DEPLOY_APP_NAME: appName,\n });\n};\n\n// Account-level guardrails stack - statically named (one per AWS account,\n// app-agnostic), so no config load is needed to know the name. Callers decide\n// whether an actor deploy should include it (bootstrap skips it; the explicit\n// 'account' menu option always deploys).\nexport const deployAccountStack = async (appName: string): Promise<void> => {\n const stackName = qpqDeployAwsUtils.getAccountStackName();\n console.log('Deploy Account: ', stackName);\n await deployStack(stackName, {\n DEPLOY_SERVICE_NAME: '',\n DEPLOY_APP_NAME: appName,\n });\n};\n\nexport const deployBootstrapStack = async (appName: string): Promise<void> => {\n const appPrefix = getAppPrefix(appName);\n const environment = process.env[QpqDeployEnvVar.environment];\n const feature = process.env[QpqDeployEnvVar.featureName];\n const stackName = feature ? `${appPrefix}-${environment}-${feature}-bs` : `${appPrefix}-${environment}-bs`;\n await deployStack(stackName, {\n DEPLOY_SERVICE_NAME: '',\n DEPLOY_APP_NAME: appName,\n });\n};\n"]}
1
+ {"version":3,"file":"stacks.js","sourceRoot":"","sources":["../../../../src/platforms/aws/stacks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,+EAA+E;AAC/E,sEAAsE;AACtE,0EAA0E;AAC1E,iFAAiF;AACjF,uDAAuD;AACvD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAItD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAE9C,MAAM,GAAG,GAAG,YAAY,CAAC,YAAY,CAAC,CAAC;AAEvC,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,SAAiB,EAAE,MAA8B,EAAE,EAAiB,EAAE;IACtG,YAAY,CAAC,SAAS,CAAC,CAAC;IACxB,MAAM,UAAU,CACd,KAAK,EACL,CAAC,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,oBAAoB,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,gBAAgB,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC,EACrI;QACE,GAAG,EAAE,OAAO,EAAE;QACd,GAAG;KACJ,CACF,CAAC;IACF,UAAU,CAAC,SAAS,CAAC,CAAC;AACxB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,WAAmB,EAAE,SAAiB,EAAE,OAAe,EAAiB,EAAE,CAC3G,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;AAE7D,4EAA4E;AAC5E,2EAA2E;AAC3E,0EAA0E;AAC1E,MAAM,YAAY,GAAG,KAAK,EAAE,SAAiB,EAAE,MAA8B,EAAE,EAAiB,EAAE;IAChG,YAAY,CAAC,SAAS,CAAC,CAAC;IACxB,MAAM,UAAU,CAAC,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,gBAAgB,EAAE,GAAG,EAAE,UAAU,EAAE,aAAa,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE;QAC1I,GAAG,EAAE,OAAO,EAAE;QACd,GAAG;KACJ,CAAC,CAAC;IACH,UAAU,CAAC,SAAS,CAAC,CAAC;AACxB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,WAAmB,EAAE,SAAiB,EAAE,OAAe,EAAiB,EAAE,CAC5G,YAAY,CAAC,SAAS,EAAE,YAAY,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC;AAE9D,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;IACxE,MAAM,SAAS,GAAG,yBAAyB,CAAC,OAAO,CAAC,CAAC;IACrD,MAAM,SAAS,GAAG,iBAAiB,CAAC,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAClE,GAAG,CAAC,GAAG,CAAC,iBAAiB,EAAE,SAAS,CAAC,CAAC;IACtC,MAAM,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC;AAEF,0EAA0E;AAC1E,8EAA8E;AAC9E,8EAA8E;AAC9E,yCAAyC;AACzC,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;IACzE,MAAM,SAAS,GAAG,iBAAiB,CAAC,mBAAmB,EAAE,CAAC;IAC1D,GAAG,CAAC,GAAG,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;IACvC,MAAM,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,EAAE,OAAe,EAAiB,EAAE;IAC3E,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;IAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,cAAc,CAAC,eAAe,CAAC,SAAS,EAAE,WAAW,IAAI,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IAC9F,MAAM,WAAW,CAAC,SAAS,EAAE,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC","sourcesContent":["import { awsNamingUtils } from '@qpqjs/config-aws';\n// Host-side `cdk deploy` helpers. No cdk.json anywhere: the CDK app command is\n// passed via --app and runs @qpqjs/deploy-aws's generic workspace app\n// (bin/qpq-cdk-app.js) under ts-node hooks. The child inherits the primed\n// deployment env (DEPLOY_APP_NAME / DEPLOY_NAME and everything resolveDeployment\n// set); only the per-invocation service is added here.\nimport { getCdkOutPath, QpqDeployEnvVar } from '@qpqjs/core';\nimport { qpqDeployAwsUtils } from '@qpqjs/deploy-aws';\n\nimport path from 'path';\n\nimport { createOutput } from '../../lib/createOutput';\nimport { getRoot } from '../../lib/discovery';\nimport { getAppPrefix, loadFirstServiceQpqConfig } from '../../lib/qpqConfigs';\nimport { runCommand } from '../../lib/runCommand';\nimport { logTimeEnd, logTimeStart } from '../../lib/timing';\nimport { getCdkAppCommand } from './cdkApp';\nimport { getDeployEnv } from './getDeployEnv';\n\nconst out = createOutput('aws:stacks');\n\nexport const deployStack = async (stackName: string, env: Record<string, string> = {}): Promise<void> => {\n logTimeStart(stackName);\n await runCommand(\n 'npx',\n ['cdk', 'deploy', stackName, '--require-approval', 'never', '--app', `'${getCdkAppCommand()}'`, '--output', getCdkOutPath(getRoot())],\n {\n cwd: getRoot(),\n env,\n },\n );\n logTimeEnd(stackName);\n};\n\nexport const deployServiceStack = (serviceName: string, stackName: string, appName: string): Promise<void> =>\n deployStack(stackName, getDeployEnv(appName, serviceName));\n\n// `cdk destroy` still synths the app to find the stack, so it runs the same\n// generic workspace app with the same DEPLOY_* env as deploys, meaning the\n// service bundles under dist/ must exist for asset resolution to succeed.\nconst destroyStack = async (stackName: string, env: Record<string, string> = {}): Promise<void> => {\n logTimeStart(stackName);\n await runCommand('npx', ['cdk', 'destroy', stackName, '--force', '--app', `'${getCdkAppCommand()}'`, '--output', getCdkOutPath(getRoot())], {\n cwd: getRoot(),\n env,\n });\n logTimeEnd(stackName);\n};\n\nexport const destroyServiceStack = (serviceName: string, stackName: string, appName: string): Promise<void> =>\n destroyStack(stackName, getDeployEnv(appName, serviceName));\n\nexport const deployDomainStack = async (appName: string): Promise<void> => {\n const qpqConfig = loadFirstServiceQpqConfig(appName);\n const stackName = qpqDeployAwsUtils.getDomainStackName(qpqConfig);\n out.log('Deploy Domain: ', stackName);\n await deployStack(stackName, getDeployEnv(appName));\n};\n\n// Account-level guardrails stack - statically named (one per AWS account,\n// app-agnostic), so no config load is needed to know the name. Callers decide\n// whether an actor deploy should include it (bootstrap skips it; the explicit\n// 'account' menu option always deploys).\nexport const deployAccountStack = async (appName: string): Promise<void> => {\n const stackName = qpqDeployAwsUtils.getAccountStackName();\n out.log('Deploy Account: ', stackName);\n await deployStack(stackName, getDeployEnv(appName));\n};\n\nexport const deployBootstrapStack = async (appName: string): Promise<void> => {\n const appPrefix = getAppPrefix(appName);\n const environment = process.env[QpqDeployEnvVar.environment];\n const feature = process.env[QpqDeployEnvVar.featureName];\n const stackName = awsNamingUtils.getAppStackName(appPrefix, environment ?? '', 'bs', feature);\n await deployStack(stackName, getDeployEnv(appName));\n};\n"]}
@@ -1,15 +1,17 @@
1
- // AWS sequential teardown (`qpq teardown`) — the stack phases of `qpq go` in
1
+ // AWS sequential teardown (`qpq teardown`), the stack phases of `qpq go` in
2
2
  // reverse. Per phase it destroys every selected service's web, then api, then
3
3
  // inf CDK stacks. Phases don't cross-reference each other's CFN outputs
4
4
  // (values flow through SSM), so this order is prudence, not a hard dependency.
5
- // Bootstrap, domain and account stacks are deliberately out of scope — they
5
+ // Bootstrap, domain and account stacks are deliberately out of scope, they
6
6
  // outlive any one service and are torn down by hand if ever.
7
7
  import { qpqDeployAwsUtils } from '@qpqjs/deploy-aws';
8
8
  import { synthCommand } from '../../commands/synth';
9
+ import { createOutput } from '../../lib/createOutput';
9
10
  import { loadServiceQpqConfig } from '../../lib/qpqConfigs';
10
11
  import { logTimeEnd, logTimeStart } from '../../lib/timing';
11
12
  import { isAwsCredentialsValid } from './awsCredentials';
12
13
  import { destroyServiceStack } from './stacks';
14
+ const out = createOutput('aws:teardown');
13
15
  const teardownPhases = [
14
16
  { label: 'Web', getStackName: qpqDeployAwsUtils.getWebStackName },
15
17
  { label: 'Api', getStackName: qpqDeployAwsUtils.getApiStackName },
@@ -20,11 +22,11 @@ export const awsTeardown = async (appName, plan) => {
20
22
  return;
21
23
  }
22
24
  const { services } = plan;
23
- console.log('\n\nRunning automated teardown\n\n');
24
- console.log('Executing for services');
25
- console.log(services.join(', '));
25
+ out.log('\n\nRunning automated teardown\n\n');
26
+ out.log('Executing for services');
27
+ out.log(services.join(', '));
26
28
  if ((await isAwsCredentialsValid()) === false) {
27
- console.log('Credentials are expired or invalid.');
29
+ out.log('Credentials are expired or invalid.');
28
30
  return;
29
31
  }
30
32
  logTimeStart('totalTime');
@@ -37,7 +39,7 @@ export const awsTeardown = async (appName, plan) => {
37
39
  await services.reduce(async (cur, service) => {
38
40
  await cur;
39
41
  const qpqConfigForService = loadServiceQpqConfig(appName, service);
40
- console.log(`Destroy ${label}: `, service);
42
+ out.log(`Destroy ${label}: `, service);
41
43
  await destroyServiceStack(service, getStackName(qpqConfigForService), appName);
42
44
  }, Promise.resolve());
43
45
  }
@@ -1 +1 @@
1
- {"version":3,"file":"teardown.js","sourceRoot":"","sources":["../../../../src/platforms/aws/teardown.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,wEAAwE;AACxE,+EAA+E;AAC/E,4EAA4E;AAC5E,6DAA6D;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,cAAc,GAAG;IACrB,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,iBAAiB,CAAC,eAAe,EAAE;IACjE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,iBAAiB,CAAC,eAAe,EAAE;IACjE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,iBAAiB,CAAC,eAAe,EAAE;CAClE,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAe,EAAE,IAAkB,EAAiB,EAAE;IACtF,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAE1B,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAClD,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAEjC,IAAI,CAAC,MAAM,qBAAqB,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;QACnD,OAAO;IACT,CAAC;IAED,YAAY,CAAC,WAAW,CAAC,CAAC;IAE1B,0EAA0E;IAC1E,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;QAC3C,MAAM,GAAG,CAAC;QACV,MAAM,YAAY,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAEtB,KAAK,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,cAAc,EAAE,CAAC;QACrD,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;YAC3C,MAAM,GAAG,CAAC;YACV,MAAM,mBAAmB,GAAG,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACnE,OAAO,CAAC,GAAG,CAAC,WAAW,KAAK,IAAI,EAAE,OAAO,CAAC,CAAC;YAC3C,MAAM,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,CAAC;QACjF,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACxB,CAAC;IAED,UAAU,CAAC,WAAW,CAAC,CAAC;AAC1B,CAAC,CAAC","sourcesContent":["// AWS sequential teardown (`qpq teardown`) — the stack phases of `qpq go` in\n// reverse. Per phase it destroys every selected service's web, then api, then\n// inf CDK stacks. Phases don't cross-reference each other's CFN outputs\n// (values flow through SSM), so this order is prudence, not a hard dependency.\n// Bootstrap, domain and account stacks are deliberately out of scope — they\n// outlive any one service and are torn down by hand if ever.\nimport { qpqDeployAwsUtils } from '@qpqjs/deploy-aws';\n\nimport { synthCommand } from '../../commands/synth';\nimport { TeardownPlan } from '../../lib/deployPrompts';\nimport { loadServiceQpqConfig } from '../../lib/qpqConfigs';\nimport { logTimeEnd, logTimeStart } from '../../lib/timing';\nimport { isAwsCredentialsValid } from './awsCredentials';\nimport { destroyServiceStack } from './stacks';\n\nconst teardownPhases = [\n { label: 'Web', getStackName: qpqDeployAwsUtils.getWebStackName },\n { label: 'Api', getStackName: qpqDeployAwsUtils.getApiStackName },\n { label: 'Inf', getStackName: qpqDeployAwsUtils.getInfStackName },\n];\n\nexport const awsTeardown = async (appName: string, plan: TeardownPlan): Promise<void> => {\n if (plan.kind === 'cancelled') {\n return;\n }\n\n const { services } = plan;\n\n console.log('\\n\\nRunning automated teardown\\n\\n');\n console.log('Executing for services');\n console.log(services.join(', '));\n\n if ((await isAwsCredentialsValid()) === false) {\n console.log('Credentials are expired or invalid.');\n return;\n }\n\n logTimeStart('totalTime');\n\n // The stack-name utils and the cdk app both read the synthed QPQ configs.\n await services.reduce(async (cur, service) => {\n await cur;\n await synthCommand([service, '--app', appName]);\n }, Promise.resolve());\n\n for (const { label, getStackName } of teardownPhases) {\n await services.reduce(async (cur, service) => {\n await cur;\n const qpqConfigForService = loadServiceQpqConfig(appName, service);\n console.log(`Destroy ${label}: `, service);\n await destroyServiceStack(service, getStackName(qpqConfigForService), appName);\n }, Promise.resolve());\n }\n\n logTimeEnd('totalTime');\n};\n"]}
1
+ {"version":3,"file":"teardown.js","sourceRoot":"","sources":["../../../../src/platforms/aws/teardown.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,8EAA8E;AAC9E,wEAAwE;AACxE,+EAA+E;AAC/E,2EAA2E;AAC3E,6DAA6D;AAC7D,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAEtD,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAC;AAE/C,MAAM,GAAG,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;AAEzC,MAAM,cAAc,GAAG;IACrB,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,iBAAiB,CAAC,eAAe,EAAE;IACjE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,iBAAiB,CAAC,eAAe,EAAE;IACjE,EAAE,KAAK,EAAE,KAAK,EAAE,YAAY,EAAE,iBAAiB,CAAC,eAAe,EAAE;CAClE,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAe,EAAE,IAAkB,EAAiB,EAAE;IACtF,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAC9B,OAAO;IACT,CAAC;IAED,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAC;IAE1B,GAAG,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAC9C,GAAG,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IAClC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAE7B,IAAI,CAAC,MAAM,qBAAqB,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC;QAC9C,GAAG,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IAED,YAAY,CAAC,WAAW,CAAC,CAAC;IAE1B,0EAA0E;IAC1E,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;QAC3C,MAAM,GAAG,CAAC;QACV,MAAM,YAAY,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;IAClD,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAEtB,KAAK,MAAM,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,cAAc,EAAE,CAAC;QACrD,MAAM,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE;YAC3C,MAAM,GAAG,CAAC;YACV,MAAM,mBAAmB,GAAG,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;YACnE,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,IAAI,EAAE,OAAO,CAAC,CAAC;YACvC,MAAM,mBAAmB,CAAC,OAAO,EAAE,YAAY,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,CAAC;QACjF,CAAC,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IACxB,CAAC;IAED,UAAU,CAAC,WAAW,CAAC,CAAC;AAC1B,CAAC,CAAC","sourcesContent":["// AWS sequential teardown (`qpq teardown`), the stack phases of `qpq go` in\n// reverse. Per phase it destroys every selected service's web, then api, then\n// inf CDK stacks. Phases don't cross-reference each other's CFN outputs\n// (values flow through SSM), so this order is prudence, not a hard dependency.\n// Bootstrap, domain and account stacks are deliberately out of scope, they\n// outlive any one service and are torn down by hand if ever.\nimport { qpqDeployAwsUtils } from '@qpqjs/deploy-aws';\n\nimport { synthCommand } from '../../commands/synth';\nimport { createOutput } from '../../lib/createOutput';\nimport { TeardownPlan } from '../../lib/deployPrompts';\nimport { loadServiceQpqConfig } from '../../lib/qpqConfigs';\nimport { logTimeEnd, logTimeStart } from '../../lib/timing';\nimport { isAwsCredentialsValid } from './awsCredentials';\nimport { destroyServiceStack } from './stacks';\n\nconst out = createOutput('aws:teardown');\n\nconst teardownPhases = [\n { label: 'Web', getStackName: qpqDeployAwsUtils.getWebStackName },\n { label: 'Api', getStackName: qpqDeployAwsUtils.getApiStackName },\n { label: 'Inf', getStackName: qpqDeployAwsUtils.getInfStackName },\n];\n\nexport const awsTeardown = async (appName: string, plan: TeardownPlan): Promise<void> => {\n if (plan.kind === 'cancelled') {\n return;\n }\n\n const { services } = plan;\n\n out.log('\\n\\nRunning automated teardown\\n\\n');\n out.log('Executing for services');\n out.log(services.join(', '));\n\n if ((await isAwsCredentialsValid()) === false) {\n out.log('Credentials are expired or invalid.');\n return;\n }\n\n logTimeStart('totalTime');\n\n // The stack-name utils and the cdk app both read the synthed QPQ configs.\n await services.reduce(async (cur, service) => {\n await cur;\n await synthCommand([service, '--app', appName]);\n }, Promise.resolve());\n\n for (const { label, getStackName } of teardownPhases) {\n await services.reduce(async (cur, service) => {\n await cur;\n const qpqConfigForService = loadServiceQpqConfig(appName, service);\n out.log(`Destroy ${label}: `, service);\n await destroyServiceStack(service, getStackName(qpqConfigForService), appName);\n }, Promise.resolve());\n }\n\n logTimeEnd('totalTime');\n};\n"]}
@@ -1,32 +1,44 @@
1
+ import { awsNamingUtils } from '@qpqjs/config-aws';
1
2
  // Views upload to S3. Every service's views go to its prefix in the shared
2
3
  // 'views' bucket (owned by shell). A service can also be a standalone host on
3
4
  // its own domain: each of its web entries marked `storageDrive.syncServiceViews`
4
5
  // gets the bundle at its drive's root. shell is the root website, which predates
5
6
  // that flag, so it keeps its 'website' bucket root when it declares none.
6
- import { awsNamingUtils } from '@qpqjs/actionprocessor-aws';
7
7
  import { qpqWebServerUtils } from '@qpqjs/webserver';
8
8
  import fs from 'fs';
9
9
  import path from 'path';
10
+ import { createOutput } from '../../lib/createOutput';
10
11
  import { loadServiceQpqConfig } from '../../lib/qpqConfigs';
11
12
  import { runCommand } from '../../lib/runCommand';
12
13
  import { getViewsDistDir } from '../../lib/views';
14
+ const out = createOutput('aws:views-sync');
13
15
  // The mutable entry points: fixed names whose CONTENT changes every deploy. They
14
- // must never be cached without revalidation — a browser holding a stale
16
+ // must never be cached without revalidation, a browser holding a stale
15
17
  // mf-manifest.json or remoteEntry.js keeps running the previous deploy's bundle
16
18
  // (the module-reload hot-swap and plain page refreshes both depend on these being
17
19
  // fresh). Everything else in a views dist is content-hashed and safely immutable.
18
20
  export const NO_CACHE_FILES = ['mf-manifest.json', 'remoteEntry.js', 'index.html', 'favicon.ico'];
21
+ // The service that owns the shared views bucket and the root website.
22
+ const SHELL_SERVICE_NAME = 'shell';
23
+ // The drive every service's views are uploaded into, under the service's prefix.
24
+ const SHARED_VIEWS_DRIVE_NAME = 'views';
25
+ // Shell's bucket root from before `syncServiceViews`, kept when it declares no host drive.
26
+ const LEGACY_WEBSITE_DRIVE_NAME = 'website';
27
+ // Content-hashed assets never change under their name, so they cache for a year; the
28
+ // entry points above must be revalidated on every load.
29
+ const IMMUTABLE_CACHE_CONTROL = 'public,max-age=31536000,immutable';
30
+ const NO_CACHE_CONTROL = 'no-cache';
19
31
  export const getViewsS3Destinations = (appName, serviceName) => {
20
- const shellConfig = loadServiceQpqConfig(appName, 'shell');
21
- const viewsBucketName = awsNamingUtils.getConfigRuntimeResourceNameFromConfig('views', shellConfig);
32
+ const shellConfig = loadServiceQpqConfig(appName, SHELL_SERVICE_NAME);
33
+ const viewsBucketName = awsNamingUtils.getConfigRuntimeResourceNameFromConfig(SHARED_VIEWS_DRIVE_NAME, shellConfig);
22
34
  const destinations = [`s3://${viewsBucketName}/${serviceName}`];
23
- const serviceConfig = serviceName === 'shell' ? shellConfig : loadServiceQpqConfig(appName, serviceName);
35
+ const serviceConfig = serviceName === SHELL_SERVICE_NAME ? shellConfig : loadServiceQpqConfig(appName, serviceName);
24
36
  const hostDrives = qpqWebServerUtils
25
37
  .getWebEntryConfigs(serviceConfig)
26
38
  .filter((webEntry) => webEntry.storageDrive.syncServiceViews && webEntry.storageDrive.sourceStorageDrive)
27
39
  .map((webEntry) => webEntry.storageDrive.sourceStorageDrive);
28
- if (serviceName === 'shell' && hostDrives.length === 0) {
29
- hostDrives.push('website');
40
+ if (serviceName === SHELL_SERVICE_NAME && hostDrives.length === 0) {
41
+ hostDrives.push(LEGACY_WEBSITE_DRIVE_NAME);
30
42
  }
31
43
  for (const drive of hostDrives) {
32
44
  destinations.push(`s3://${awsNamingUtils.getConfigRuntimeResourceNameFromConfig(drive, serviceConfig)}`);
@@ -36,7 +48,7 @@ export const getViewsS3Destinations = (appName, serviceName) => {
36
48
  export const syncViewsToS3 = async (appName, serviceName) => {
37
49
  const distDir = getViewsDistDir(appName, serviceName);
38
50
  for (const destination of getViewsS3Destinations(appName, serviceName)) {
39
- console.log(`Copying [${serviceName}] views to [${destination}]`);
51
+ out.log(`Copying [${serviceName}] views to [${destination}]`);
40
52
  // Content-hashed assets: cache forever.
41
53
  await runCommand('aws', [
42
54
  's3',
@@ -44,7 +56,7 @@ export const syncViewsToS3 = async (appName, serviceName) => {
44
56
  distDir,
45
57
  destination,
46
58
  '--cache-control',
47
- 'public,max-age=31536000,immutable',
59
+ IMMUTABLE_CACHE_CONTROL,
48
60
  ...NO_CACHE_FILES.flatMap((file) => ['--exclude', file]),
49
61
  ]);
50
62
  // Entry points: cp (not sync) so the no-cache header is (re)applied on every
@@ -53,7 +65,7 @@ export const syncViewsToS3 = async (appName, serviceName) => {
53
65
  if (!fs.existsSync(path.join(distDir, file))) {
54
66
  continue;
55
67
  }
56
- await runCommand('aws', ['s3', 'cp', path.join(distDir, file), `${destination}/${file}`, '--cache-control', 'no-cache']);
68
+ await runCommand('aws', ['s3', 'cp', path.join(distDir, file), `${destination}/${file}`, '--cache-control', NO_CACHE_CONTROL]);
57
69
  }
58
70
  }
59
71
  };
@@ -1 +1 @@
1
- {"version":3,"file":"viewsSync.js","sourceRoot":"","sources":["../../../../src/platforms/aws/viewsSync.ts"],"names":[],"mappings":"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","sourcesContent":["// Views upload to S3. Every service's views go to its prefix in the shared\n// 'views' bucket (owned by shell). A service can also be a standalone host on\n// its own domain: each of its web entries marked `storageDrive.syncServiceViews`\n// gets the bundle at its drive's root. shell is the root website, which predates\n// that flag, so it keeps its 'website' bucket root when it declares none.\nimport { awsNamingUtils } from '@qpqjs/actionprocessor-aws';\nimport { qpqWebServerUtils } from '@qpqjs/webserver';\n\nimport fs from 'fs';\nimport path from 'path';\n\nimport { loadServiceQpqConfig } from '../../lib/qpqConfigs';\nimport { runCommand } from '../../lib/runCommand';\nimport { getViewsDistDir } from '../../lib/views';\n\n// The mutable entry points: fixed names whose CONTENT changes every deploy. They\n// must never be cached without revalidation — a browser holding a stale\n// mf-manifest.json or remoteEntry.js keeps running the previous deploy's bundle\n// (the module-reload hot-swap and plain page refreshes both depend on these being\n// fresh). Everything else in a views dist is content-hashed and safely immutable.\nexport const NO_CACHE_FILES = ['mf-manifest.json', 'remoteEntry.js', 'index.html', 'favicon.ico'];\n\nexport const getViewsS3Destinations = (appName: string, serviceName: string): string[] => {\n const shellConfig = loadServiceQpqConfig(appName, 'shell');\n const viewsBucketName = awsNamingUtils.getConfigRuntimeResourceNameFromConfig('views', shellConfig);\n\n const destinations = [`s3://${viewsBucketName}/${serviceName}`];\n\n const serviceConfig = serviceName === 'shell' ? shellConfig : loadServiceQpqConfig(appName, serviceName);\n const hostDrives = qpqWebServerUtils\n .getWebEntryConfigs(serviceConfig)\n .filter((webEntry) => webEntry.storageDrive.syncServiceViews && webEntry.storageDrive.sourceStorageDrive)\n .map((webEntry) => webEntry.storageDrive.sourceStorageDrive!);\n\n if (serviceName === 'shell' && hostDrives.length === 0) {\n hostDrives.push('website');\n }\n\n for (const drive of hostDrives) {\n destinations.push(`s3://${awsNamingUtils.getConfigRuntimeResourceNameFromConfig(drive, serviceConfig)}`);\n }\n\n return destinations;\n};\n\nexport const syncViewsToS3 = async (appName: string, serviceName: string): Promise<void> => {\n const distDir = getViewsDistDir(appName, serviceName);\n\n for (const destination of getViewsS3Destinations(appName, serviceName)) {\n console.log(`Copying [${serviceName}] views to [${destination}]`);\n\n // Content-hashed assets: cache forever.\n await runCommand('aws', [\n 's3',\n 'sync',\n distDir,\n destination,\n '--cache-control',\n 'public,max-age=31536000,immutable',\n ...NO_CACHE_FILES.flatMap((file) => ['--exclude', file]),\n ]);\n\n // Entry points: cp (not sync) so the no-cache header is (re)applied on every\n // deploy even when a body happens to be byte-identical.\n for (const file of NO_CACHE_FILES) {\n if (!fs.existsSync(path.join(distDir, file))) {\n continue;\n }\n\n await runCommand('aws', ['s3', 'cp', path.join(distDir, file), `${destination}/${file}`, '--cache-control', 'no-cache']);\n }\n }\n};\n"]}
1
+ {"version":3,"file":"viewsSync.js","sourceRoot":"","sources":["../../../../src/platforms/aws/viewsSync.ts"],"names":[],"mappings":"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","sourcesContent":["import { awsNamingUtils } from '@qpqjs/config-aws';\n// Views upload to S3. Every service's views go to its prefix in the shared\n// 'views' bucket (owned by shell). A service can also be a standalone host on\n// its own domain: each of its web entries marked `storageDrive.syncServiceViews`\n// gets the bundle at its drive's root. shell is the root website, which predates\n// that flag, so it keeps its 'website' bucket root when it declares none.\nimport { qpqWebServerUtils } from '@qpqjs/webserver';\n\nimport fs from 'fs';\nimport path from 'path';\n\nimport { createOutput } from '../../lib/createOutput';\nimport { loadServiceQpqConfig } from '../../lib/qpqConfigs';\nimport { runCommand } from '../../lib/runCommand';\nimport { getViewsDistDir } from '../../lib/views';\n\nconst out = createOutput('aws:views-sync');\n\n// The mutable entry points: fixed names whose CONTENT changes every deploy. They\n// must never be cached without revalidation, a browser holding a stale\n// mf-manifest.json or remoteEntry.js keeps running the previous deploy's bundle\n// (the module-reload hot-swap and plain page refreshes both depend on these being\n// fresh). Everything else in a views dist is content-hashed and safely immutable.\nexport const NO_CACHE_FILES = ['mf-manifest.json', 'remoteEntry.js', 'index.html', 'favicon.ico'];\n\n// The service that owns the shared views bucket and the root website.\nconst SHELL_SERVICE_NAME = 'shell';\n// The drive every service's views are uploaded into, under the service's prefix.\nconst SHARED_VIEWS_DRIVE_NAME = 'views';\n// Shell's bucket root from before `syncServiceViews`, kept when it declares no host drive.\nconst LEGACY_WEBSITE_DRIVE_NAME = 'website';\n\n// Content-hashed assets never change under their name, so they cache for a year; the\n// entry points above must be revalidated on every load.\nconst IMMUTABLE_CACHE_CONTROL = 'public,max-age=31536000,immutable';\nconst NO_CACHE_CONTROL = 'no-cache';\n\nexport const getViewsS3Destinations = (appName: string, serviceName: string): string[] => {\n const shellConfig = loadServiceQpqConfig(appName, SHELL_SERVICE_NAME);\n const viewsBucketName = awsNamingUtils.getConfigRuntimeResourceNameFromConfig(SHARED_VIEWS_DRIVE_NAME, shellConfig);\n\n const destinations = [`s3://${viewsBucketName}/${serviceName}`];\n\n const serviceConfig = serviceName === SHELL_SERVICE_NAME ? shellConfig : loadServiceQpqConfig(appName, serviceName);\n const hostDrives = qpqWebServerUtils\n .getWebEntryConfigs(serviceConfig)\n .filter((webEntry) => webEntry.storageDrive.syncServiceViews && webEntry.storageDrive.sourceStorageDrive)\n .map((webEntry) => webEntry.storageDrive.sourceStorageDrive!);\n\n if (serviceName === SHELL_SERVICE_NAME && hostDrives.length === 0) {\n hostDrives.push(LEGACY_WEBSITE_DRIVE_NAME);\n }\n\n for (const drive of hostDrives) {\n destinations.push(`s3://${awsNamingUtils.getConfigRuntimeResourceNameFromConfig(drive, serviceConfig)}`);\n }\n\n return destinations;\n};\n\nexport const syncViewsToS3 = async (appName: string, serviceName: string): Promise<void> => {\n const distDir = getViewsDistDir(appName, serviceName);\n\n for (const destination of getViewsS3Destinations(appName, serviceName)) {\n out.log(`Copying [${serviceName}] views to [${destination}]`);\n\n // Content-hashed assets: cache forever.\n await runCommand('aws', [\n 's3',\n 'sync',\n distDir,\n destination,\n '--cache-control',\n IMMUTABLE_CACHE_CONTROL,\n ...NO_CACHE_FILES.flatMap((file) => ['--exclude', file]),\n ]);\n\n // Entry points: cp (not sync) so the no-cache header is (re)applied on every\n // deploy even when a body happens to be byte-identical.\n for (const file of NO_CACHE_FILES) {\n if (!fs.existsSync(path.join(distDir, file))) {\n continue;\n }\n\n await runCommand('aws', ['s3', 'cp', path.join(distDir, file), `${destination}/${file}`, '--cache-control', NO_CACHE_CONTROL]);\n }\n }\n};\n"]}