quidproquo-cli 0.1.19 → 0.1.20

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 (130) hide show
  1. package/README.md +1 -0
  2. package/lib/commonjs/cli/commandRegistry.js +6 -0
  3. package/lib/commonjs/cli/commandRegistry.js.map +1 -1
  4. package/lib/commonjs/commands/goDevApi.js +18 -5
  5. package/lib/commonjs/commands/goDevApi.js.map +1 -1
  6. package/lib/commonjs/commands/goDevWeb.js +10 -2
  7. package/lib/commonjs/commands/goDevWeb.js.map +1 -1
  8. package/lib/commonjs/commands/setup.d.ts +1 -0
  9. package/lib/commonjs/commands/setup.js +102 -0
  10. package/lib/commonjs/commands/setup.js.map +1 -0
  11. package/lib/commonjs/lib/args.d.ts +1 -0
  12. package/lib/commonjs/lib/args.js +11 -1
  13. package/lib/commonjs/lib/args.js.map +1 -1
  14. package/lib/commonjs/lib/keepOtherDevServersFlag.d.ts +1 -0
  15. package/lib/commonjs/lib/keepOtherDevServersFlag.js +11 -0
  16. package/lib/commonjs/lib/keepOtherDevServersFlag.js.map +1 -0
  17. package/lib/commonjs/lib/killStaleListeners.d.ts +14 -0
  18. package/lib/commonjs/lib/killStaleListeners.js +47 -20
  19. package/lib/commonjs/lib/killStaleListeners.js.map +1 -1
  20. package/lib/commonjs/lib/prompts.d.ts +2 -0
  21. package/lib/commonjs/lib/prompts.js +1 -1
  22. package/lib/commonjs/lib/prompts.js.map +1 -1
  23. package/lib/commonjs/lib/setupStep.d.ts +16 -0
  24. package/lib/commonjs/lib/setupStep.js +3 -0
  25. package/lib/commonjs/lib/setupStep.js.map +1 -0
  26. package/lib/commonjs/platforms/aws/index.js +2 -0
  27. package/lib/commonjs/platforms/aws/index.js.map +1 -1
  28. package/lib/commonjs/platforms/aws/setup/awsSetupContext.d.ts +12 -0
  29. package/lib/commonjs/platforms/aws/setup/awsSetupContext.js +23 -0
  30. package/lib/commonjs/platforms/aws/setup/awsSetupContext.js.map +1 -0
  31. package/lib/commonjs/platforms/aws/setup/awsSetupSteps.d.ts +8 -0
  32. package/lib/commonjs/platforms/aws/setup/awsSetupSteps.js +44 -0
  33. package/lib/commonjs/platforms/aws/setup/awsSetupSteps.js.map +1 -0
  34. package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.d.ts +4 -0
  35. package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.js +77 -0
  36. package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.js.map +1 -0
  37. package/lib/commonjs/platforms/aws/setup/buildCdkBootstrapSteps.d.ts +5 -0
  38. package/lib/commonjs/platforms/aws/setup/buildCdkBootstrapSteps.js +38 -0
  39. package/lib/commonjs/platforms/aws/setup/buildCdkBootstrapSteps.js.map +1 -0
  40. package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.d.ts +4 -0
  41. package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.js +55 -0
  42. package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.js.map +1 -0
  43. package/lib/commonjs/platforms/aws/setup/buildGithubOidcProviderStep.d.ts +4 -0
  44. package/lib/commonjs/platforms/aws/setup/buildGithubOidcProviderStep.js +54 -0
  45. package/lib/commonjs/platforms/aws/setup/buildGithubOidcProviderStep.js.map +1 -0
  46. package/lib/commonjs/platforms/aws/setup/buildHostedZonesStep.d.ts +4 -0
  47. package/lib/commonjs/platforms/aws/setup/buildHostedZonesStep.js +63 -0
  48. package/lib/commonjs/platforms/aws/setup/buildHostedZonesStep.js.map +1 -0
  49. package/lib/commonjs/platforms/aws/setup/buildIamCapabilityStep.d.ts +4 -0
  50. package/lib/commonjs/platforms/aws/setup/buildIamCapabilityStep.js +48 -0
  51. package/lib/commonjs/platforms/aws/setup/buildIamCapabilityStep.js.map +1 -0
  52. package/lib/commonjs/platforms/aws/setup/buildIdentityStep.d.ts +4 -0
  53. package/lib/commonjs/platforms/aws/setup/buildIdentityStep.js +39 -0
  54. package/lib/commonjs/platforms/aws/setup/buildIdentityStep.js.map +1 -0
  55. package/lib/commonjs/platforms/aws/setup/buildStackSteps.d.ts +5 -0
  56. package/lib/commonjs/platforms/aws/setup/buildStackSteps.js +32 -0
  57. package/lib/commonjs/platforms/aws/setup/buildStackSteps.js.map +1 -0
  58. package/lib/commonjs/platforms/aws/setup/describeStackStatus.d.ts +4 -0
  59. package/lib/commonjs/platforms/aws/setup/describeStackStatus.js +32 -0
  60. package/lib/commonjs/platforms/aws/setup/describeStackStatus.js.map +1 -0
  61. package/lib/commonjs/platforms/aws/setup/index.d.ts +1 -0
  62. package/lib/commonjs/platforms/aws/setup/index.js +18 -0
  63. package/lib/commonjs/platforms/aws/setup/index.js.map +1 -0
  64. package/lib/commonjs/platforms/types.d.ts +2 -0
  65. package/lib/commonjs/platforms/types.js.map +1 -1
  66. package/lib/esm/cli/commandRegistry.js +6 -0
  67. package/lib/esm/cli/commandRegistry.js.map +1 -1
  68. package/lib/esm/commands/goDevApi.js +19 -6
  69. package/lib/esm/commands/goDevApi.js.map +1 -1
  70. package/lib/esm/commands/goDevWeb.js +10 -2
  71. package/lib/esm/commands/goDevWeb.js.map +1 -1
  72. package/lib/esm/commands/setup.d.ts +1 -0
  73. package/lib/esm/commands/setup.js +88 -0
  74. package/lib/esm/commands/setup.js.map +1 -0
  75. package/lib/esm/lib/args.d.ts +1 -0
  76. package/lib/esm/lib/args.js +9 -0
  77. package/lib/esm/lib/args.js.map +1 -1
  78. package/lib/esm/lib/keepOtherDevServersFlag.d.ts +1 -0
  79. package/lib/esm/lib/keepOtherDevServersFlag.js +8 -0
  80. package/lib/esm/lib/keepOtherDevServersFlag.js.map +1 -0
  81. package/lib/esm/lib/killStaleListeners.d.ts +14 -0
  82. package/lib/esm/lib/killStaleListeners.js +45 -19
  83. package/lib/esm/lib/killStaleListeners.js.map +1 -1
  84. package/lib/esm/lib/prompts.d.ts +2 -0
  85. package/lib/esm/lib/prompts.js +1 -1
  86. package/lib/esm/lib/prompts.js.map +1 -1
  87. package/lib/esm/lib/setupStep.d.ts +16 -0
  88. package/lib/esm/lib/setupStep.js +2 -0
  89. package/lib/esm/lib/setupStep.js.map +1 -0
  90. package/lib/esm/platforms/aws/index.js +2 -0
  91. package/lib/esm/platforms/aws/index.js.map +1 -1
  92. package/lib/esm/platforms/aws/setup/awsSetupContext.d.ts +12 -0
  93. package/lib/esm/platforms/aws/setup/awsSetupContext.js +18 -0
  94. package/lib/esm/platforms/aws/setup/awsSetupContext.js.map +1 -0
  95. package/lib/esm/platforms/aws/setup/awsSetupSteps.d.ts +8 -0
  96. package/lib/esm/platforms/aws/setup/awsSetupSteps.js +31 -0
  97. package/lib/esm/platforms/aws/setup/awsSetupSteps.js.map +1 -0
  98. package/lib/esm/platforms/aws/setup/buildBucketNamesStep.d.ts +4 -0
  99. package/lib/esm/platforms/aws/setup/buildBucketNamesStep.js +62 -0
  100. package/lib/esm/platforms/aws/setup/buildBucketNamesStep.js.map +1 -0
  101. package/lib/esm/platforms/aws/setup/buildCdkBootstrapSteps.d.ts +5 -0
  102. package/lib/esm/platforms/aws/setup/buildCdkBootstrapSteps.js +24 -0
  103. package/lib/esm/platforms/aws/setup/buildCdkBootstrapSteps.js.map +1 -0
  104. package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.d.ts +4 -0
  105. package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.js +40 -0
  106. package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.js.map +1 -0
  107. package/lib/esm/platforms/aws/setup/buildGithubOidcProviderStep.d.ts +4 -0
  108. package/lib/esm/platforms/aws/setup/buildGithubOidcProviderStep.js +40 -0
  109. package/lib/esm/platforms/aws/setup/buildGithubOidcProviderStep.js.map +1 -0
  110. package/lib/esm/platforms/aws/setup/buildHostedZonesStep.d.ts +4 -0
  111. package/lib/esm/platforms/aws/setup/buildHostedZonesStep.js +49 -0
  112. package/lib/esm/platforms/aws/setup/buildHostedZonesStep.js.map +1 -0
  113. package/lib/esm/platforms/aws/setup/buildIamCapabilityStep.d.ts +4 -0
  114. package/lib/esm/platforms/aws/setup/buildIamCapabilityStep.js +34 -0
  115. package/lib/esm/platforms/aws/setup/buildIamCapabilityStep.js.map +1 -0
  116. package/lib/esm/platforms/aws/setup/buildIdentityStep.d.ts +4 -0
  117. package/lib/esm/platforms/aws/setup/buildIdentityStep.js +26 -0
  118. package/lib/esm/platforms/aws/setup/buildIdentityStep.js.map +1 -0
  119. package/lib/esm/platforms/aws/setup/buildStackSteps.d.ts +5 -0
  120. package/lib/esm/platforms/aws/setup/buildStackSteps.js +17 -0
  121. package/lib/esm/platforms/aws/setup/buildStackSteps.js.map +1 -0
  122. package/lib/esm/platforms/aws/setup/describeStackStatus.d.ts +4 -0
  123. package/lib/esm/platforms/aws/setup/describeStackStatus.js +17 -0
  124. package/lib/esm/platforms/aws/setup/describeStackStatus.js.map +1 -0
  125. package/lib/esm/platforms/aws/setup/index.d.ts +1 -0
  126. package/lib/esm/platforms/aws/setup/index.js +2 -0
  127. package/lib/esm/platforms/aws/setup/index.js.map +1 -0
  128. package/lib/esm/platforms/types.d.ts +2 -0
  129. package/lib/esm/platforms/types.js.map +1 -1
  130. package/package.json +13 -10
@@ -0,0 +1,63 @@
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.buildHostedZonesStep = void 0;
13
+ const quidproquo_deploy_awscdk_1 = require("quidproquo-deploy-awscdk");
14
+ const quidproquo_webserver_1 = require("quidproquo-webserver");
15
+ const client_route_53_1 = require("@aws-sdk/client-route-53");
16
+ // The zones the environment writes records into: one per root, chosen the way the
17
+ // CDK chooses them (each root's site root under the app's resolver, or the root).
18
+ const getRequiredZones = (ctx) => {
19
+ const config = ctx.bootstrapQpqConfig;
20
+ return [
21
+ ...new Set(quidproquo_webserver_1.qpqWebServerUtils
22
+ .getRootDomains(config)
23
+ .map((rootDomain) => quidproquo_deploy_awscdk_1.qpqDeployAwsCdkUtils.resolveHostedZoneForHost(config, quidproquo_deploy_awscdk_1.qpqDeployAwsCdkUtils.resolveDeployHostForRoot(config, rootDomain, {})))),
24
+ ];
25
+ };
26
+ const zoneExists = (client, zoneName) => __awaiter(void 0, void 0, void 0, function* () {
27
+ const { HostedZones } = yield client.send(new client_route_53_1.ListHostedZonesByNameCommand({ DNSName: zoneName, MaxItems: 1 }));
28
+ return (HostedZones !== null && HostedZones !== void 0 ? HostedZones : []).some((zone) => { var _a; return zone.Name === `${zoneName}.` && !((_a = zone.Config) === null || _a === void 0 ? void 0 : _a.PrivateZone); });
29
+ });
30
+ const checkHostedZones = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
31
+ const required = getRequiredZones(ctx);
32
+ if (required.length === 0) {
33
+ return { done: true, detail: 'no domains declared' };
34
+ }
35
+ const client = new client_route_53_1.Route53Client({ region: ctx.region });
36
+ const missing = [];
37
+ for (const zoneName of required) {
38
+ if (!(yield zoneExists(client, zoneName))) {
39
+ missing.push(zoneName);
40
+ }
41
+ }
42
+ return missing.length === 0
43
+ ? { done: true, detail: required.join(', ') }
44
+ : {
45
+ done: false,
46
+ detail: `missing ${missing.join(', ')}: create each as a public hosted zone here and delegate it (NS record) from its parent zone`,
47
+ };
48
+ });
49
+ /** Check-only: the Route53 zones the deploy will write records into exist in this account. */
50
+ const buildHostedZonesStep = (ctx) => ({
51
+ id: 'hosted-zones',
52
+ name: 'Route53 hosted zones',
53
+ check: () => checkHostedZones(ctx),
54
+ run: () => __awaiter(void 0, void 0, void 0, function* () {
55
+ var _a;
56
+ const result = yield checkHostedZones(ctx);
57
+ if (!result.done) {
58
+ throw new Error((_a = result.detail) !== null && _a !== void 0 ? _a : 'hosted zones missing');
59
+ }
60
+ }),
61
+ });
62
+ exports.buildHostedZonesStep = buildHostedZonesStep;
63
+ //# sourceMappingURL=buildHostedZonesStep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildHostedZonesStep.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/buildHostedZonesStep.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,uEAAgE;AAChE,+DAAyD;AAEzD,8DAAuF;AAKvF,kFAAkF;AAClF,kFAAkF;AAClF,MAAM,gBAAgB,GAAG,CAAC,GAAoB,EAAY,EAAE;IAC1D,MAAM,MAAM,GAAG,GAAG,CAAC,kBAAkB,CAAC;IACtC,OAAO;QACL,GAAG,IAAI,GAAG,CACR,wCAAiB;aACd,cAAc,CAAC,MAAM,CAAC;aACtB,GAAG,CAAC,CAAC,UAAU,EAAE,EAAE,CAClB,+CAAoB,CAAC,wBAAwB,CAAC,MAAM,EAAE,+CAAoB,CAAC,wBAAwB,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,CAAC,CAAC,CAC7H,CACJ;KACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,UAAU,GAAG,CAAO,MAAqB,EAAE,QAAgB,EAAoB,EAAE;IACrF,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,8CAA4B,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAChH,OAAO,CAAC,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,WAAC,OAAA,IAAI,CAAC,IAAI,KAAK,GAAG,QAAQ,GAAG,IAAI,CAAC,CAAA,MAAA,IAAI,CAAC,MAAM,0CAAE,WAAW,CAAA,CAAA,EAAA,CAAC,CAAC;AACvG,CAAC,CAAA,CAAC;AAEF,MAAM,gBAAgB,GAAG,CAAO,GAAoB,EAA6B,EAAE;IACjF,MAAM,QAAQ,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC;IACvC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;IACvD,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,+BAAa,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IACzD,MAAM,OAAO,GAAa,EAAE,CAAC;IAC7B,KAAK,MAAM,QAAQ,IAAI,QAAQ,EAAE,CAAC;QAChC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzB,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC,MAAM,KAAK,CAAC;QACzB,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAC7C,CAAC,CAAC;YACE,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,WAAW,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,6FAA6F;SACnI,CAAC;AACR,CAAC,CAAA,CAAC;AAEF,8FAA8F;AACvF,MAAM,oBAAoB,GAAG,CAAC,GAAoB,EAAa,EAAE,CAAC,CAAC;IACxE,EAAE,EAAE,cAAc;IAClB,IAAI,EAAE,sBAAsB;IAC5B,KAAK,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,GAAG,CAAC;IAClC,GAAG,EAAE,GAAS,EAAE;;QACd,MAAM,MAAM,GAAG,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,MAAA,MAAM,CAAC,MAAM,mCAAI,sBAAsB,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC,CAAA;CACF,CAAC,CAAC;AAVU,QAAA,oBAAoB,wBAU9B","sourcesContent":["import { qpqDeployAwsCdkUtils } from 'quidproquo-deploy-awscdk';\nimport { qpqWebServerUtils } from 'quidproquo-webserver';\n\nimport { ListHostedZonesByNameCommand, Route53Client } from '@aws-sdk/client-route-53';\n\nimport { SetupCheckResult, SetupStep } from '../../../lib/setupStep';\nimport { AwsSetupContext } from './awsSetupContext';\n\n// The zones the environment writes records into: one per root, chosen the way the\n// CDK chooses them (each root's site root under the app's resolver, or the root).\nconst getRequiredZones = (ctx: AwsSetupContext): string[] => {\n const config = ctx.bootstrapQpqConfig;\n return [\n ...new Set(\n qpqWebServerUtils\n .getRootDomains(config)\n .map((rootDomain) =>\n qpqDeployAwsCdkUtils.resolveHostedZoneForHost(config, qpqDeployAwsCdkUtils.resolveDeployHostForRoot(config, rootDomain, {})),\n ),\n ),\n ];\n};\n\nconst zoneExists = async (client: Route53Client, zoneName: string): Promise<boolean> => {\n const { HostedZones } = await client.send(new ListHostedZonesByNameCommand({ DNSName: zoneName, MaxItems: 1 }));\n return (HostedZones ?? []).some((zone) => zone.Name === `${zoneName}.` && !zone.Config?.PrivateZone);\n};\n\nconst checkHostedZones = async (ctx: AwsSetupContext): Promise<SetupCheckResult> => {\n const required = getRequiredZones(ctx);\n if (required.length === 0) {\n return { done: true, detail: 'no domains declared' };\n }\n\n const client = new Route53Client({ region: ctx.region });\n const missing: string[] = [];\n for (const zoneName of required) {\n if (!(await zoneExists(client, zoneName))) {\n missing.push(zoneName);\n }\n }\n\n return missing.length === 0\n ? { done: true, detail: required.join(', ') }\n : {\n done: false,\n detail: `missing ${missing.join(', ')}: create each as a public hosted zone here and delegate it (NS record) from its parent zone`,\n };\n};\n\n/** Check-only: the Route53 zones the deploy will write records into exist in this account. */\nexport const buildHostedZonesStep = (ctx: AwsSetupContext): SetupStep => ({\n id: 'hosted-zones',\n name: 'Route53 hosted zones',\n check: () => checkHostedZones(ctx),\n run: async () => {\n const result = await checkHostedZones(ctx);\n if (!result.done) {\n throw new Error(result.detail ?? 'hosted zones missing');\n }\n },\n});\n"]}
@@ -0,0 +1,4 @@
1
+ import { SetupStep } from '../../../lib/setupStep';
2
+ import { AwsSetupContext } from './awsSetupContext';
3
+ /** Check-only: the credentials can manage IAM, which every CDK deploy needs. */
4
+ export declare const buildIamCapabilityStep: (ctx: AwsSetupContext) => SetupStep;
@@ -0,0 +1,48 @@
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.buildIamCapabilityStep = void 0;
13
+ const client_iam_1 = require("@aws-sdk/client-iam");
14
+ // CDK bootstrap and every deploy create IAM roles. PowerUserAccess fails on the
15
+ // first iam:GetRole for a cdk-* role, so probe exactly that: a NoSuchEntity
16
+ // answer means the permission is there, AccessDenied means it is not.
17
+ const checkIamCapability = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
18
+ const roleName = `cdk-hnb659fds-cfn-exec-role-${ctx.accountId}-${ctx.region}`;
19
+ try {
20
+ yield new client_iam_1.IAMClient({ region: ctx.region }).send(new client_iam_1.GetRoleCommand({ RoleName: roleName }));
21
+ return { done: true, detail: 'iam:GetRole allowed' };
22
+ }
23
+ catch (error) {
24
+ const name = error instanceof Error ? error.name : '';
25
+ if (name === 'NoSuchEntityException') {
26
+ return { done: true, detail: 'iam:GetRole allowed' };
27
+ }
28
+ return {
29
+ done: false,
30
+ detail: `iam:GetRole denied (${name}); the permission set needs IAM rights (AdministratorAccess or a custom set adding iam:*)`,
31
+ };
32
+ }
33
+ });
34
+ /** Check-only: the credentials can manage IAM, which every CDK deploy needs. */
35
+ const buildIamCapabilityStep = (ctx) => ({
36
+ id: 'iam',
37
+ name: 'IAM rights on the credentials',
38
+ check: () => checkIamCapability(ctx),
39
+ run: () => __awaiter(void 0, void 0, void 0, function* () {
40
+ var _a;
41
+ const result = yield checkIamCapability(ctx);
42
+ if (!result.done) {
43
+ throw new Error((_a = result.detail) !== null && _a !== void 0 ? _a : 'IAM rights missing');
44
+ }
45
+ }),
46
+ });
47
+ exports.buildIamCapabilityStep = buildIamCapabilityStep;
48
+ //# sourceMappingURL=buildIamCapabilityStep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildIamCapabilityStep.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/buildIamCapabilityStep.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,oDAAgE;AAKhE,gFAAgF;AAChF,4EAA4E;AAC5E,sEAAsE;AACtE,MAAM,kBAAkB,GAAG,CAAO,GAAoB,EAA6B,EAAE;IACnF,MAAM,QAAQ,GAAG,+BAA+B,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;IAC9E,IAAI,CAAC;QACH,MAAM,IAAI,sBAAS,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,2BAAc,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC7F,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;IACvD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACtD,IAAI,IAAI,KAAK,uBAAuB,EAAE,CAAC;YACrC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,qBAAqB,EAAE,CAAC;QACvD,CAAC;QACD,OAAO;YACL,IAAI,EAAE,KAAK;YACX,MAAM,EAAE,uBAAuB,IAAI,2FAA2F;SAC/H,CAAC;IACJ,CAAC;AACH,CAAC,CAAA,CAAC;AAEF,gFAAgF;AACzE,MAAM,sBAAsB,GAAG,CAAC,GAAoB,EAAa,EAAE,CAAC,CAAC;IAC1E,EAAE,EAAE,KAAK;IACT,IAAI,EAAE,+BAA+B;IACrC,KAAK,EAAE,GAAG,EAAE,CAAC,kBAAkB,CAAC,GAAG,CAAC;IACpC,GAAG,EAAE,GAAS,EAAE;;QACd,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,MAAA,MAAM,CAAC,MAAM,mCAAI,oBAAoB,CAAC,CAAC;QACzD,CAAC;IACH,CAAC,CAAA;CACF,CAAC,CAAC;AAVU,QAAA,sBAAsB,0BAUhC","sourcesContent":["import { GetRoleCommand, IAMClient } from '@aws-sdk/client-iam';\n\nimport { SetupCheckResult, SetupStep } from '../../../lib/setupStep';\nimport { AwsSetupContext } from './awsSetupContext';\n\n// CDK bootstrap and every deploy create IAM roles. PowerUserAccess fails on the\n// first iam:GetRole for a cdk-* role, so probe exactly that: a NoSuchEntity\n// answer means the permission is there, AccessDenied means it is not.\nconst checkIamCapability = async (ctx: AwsSetupContext): Promise<SetupCheckResult> => {\n const roleName = `cdk-hnb659fds-cfn-exec-role-${ctx.accountId}-${ctx.region}`;\n try {\n await new IAMClient({ region: ctx.region }).send(new GetRoleCommand({ RoleName: roleName }));\n return { done: true, detail: 'iam:GetRole allowed' };\n } catch (error) {\n const name = error instanceof Error ? error.name : '';\n if (name === 'NoSuchEntityException') {\n return { done: true, detail: 'iam:GetRole allowed' };\n }\n return {\n done: false,\n detail: `iam:GetRole denied (${name}); the permission set needs IAM rights (AdministratorAccess or a custom set adding iam:*)`,\n };\n }\n};\n\n/** Check-only: the credentials can manage IAM, which every CDK deploy needs. */\nexport const buildIamCapabilityStep = (ctx: AwsSetupContext): SetupStep => ({\n id: 'iam',\n name: 'IAM rights on the credentials',\n check: () => checkIamCapability(ctx),\n run: async () => {\n const result = await checkIamCapability(ctx);\n if (!result.done) {\n throw new Error(result.detail ?? 'IAM rights missing');\n }\n },\n});\n"]}
@@ -0,0 +1,4 @@
1
+ import { SetupStep } from '../../../lib/setupStep';
2
+ import { AwsSetupContext } from './awsSetupContext';
3
+ /** Check-only: the active credentials belong to the environment's account. Catches a stale .env or SSO profile. */
4
+ export declare const buildIdentityStep: (ctx: AwsSetupContext) => SetupStep;
@@ -0,0 +1,39 @@
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.buildIdentityStep = void 0;
13
+ const client_sts_1 = require("@aws-sdk/client-sts");
14
+ const checkIdentity = (ctx) => __awaiter(void 0, void 0, void 0, function* () {
15
+ try {
16
+ const { Account, Arn } = yield new client_sts_1.STSClient({ region: ctx.region }).send(new client_sts_1.GetCallerIdentityCommand({}));
17
+ if (Account !== ctx.accountId) {
18
+ return { done: false, detail: `credentials are for account ${Account}, environment '${ctx.environment}' deploys to ${ctx.accountId}` };
19
+ }
20
+ return { done: true, detail: `${Arn}` };
21
+ }
22
+ catch (error) {
23
+ return { done: false, detail: `no valid credentials: ${error instanceof Error ? error.message : String(error)}` };
24
+ }
25
+ });
26
+ /** Check-only: the active credentials belong to the environment's account. Catches a stale .env or SSO profile. */
27
+ const buildIdentityStep = (ctx) => ({
28
+ id: 'identity',
29
+ name: `Credentials for account ${ctx.accountId}`,
30
+ check: () => checkIdentity(ctx),
31
+ run: () => __awaiter(void 0, void 0, void 0, function* () {
32
+ const result = yield checkIdentity(ctx);
33
+ if (!result.done) {
34
+ throw new Error(`${result.detail}. Log in to the right account (or fix AWS_DEFAULT_ACCOUNT / deploy.config.json) and rerun.`);
35
+ }
36
+ }),
37
+ });
38
+ exports.buildIdentityStep = buildIdentityStep;
39
+ //# sourceMappingURL=buildIdentityStep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildIdentityStep.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/buildIdentityStep.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,oDAA0E;AAK1E,MAAM,aAAa,GAAG,CAAO,GAAoB,EAA6B,EAAE;IAC9E,IAAI,CAAC;QACH,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,MAAM,IAAI,sBAAS,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,qCAAwB,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5G,IAAI,OAAO,KAAK,GAAG,CAAC,SAAS,EAAE,CAAC;YAC9B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,+BAA+B,OAAO,kBAAkB,GAAG,CAAC,WAAW,gBAAgB,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC;QACzI,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,GAAG,EAAE,EAAE,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,yBAAyB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IACpH,CAAC;AACH,CAAC,CAAA,CAAC;AAEF,mHAAmH;AAC5G,MAAM,iBAAiB,GAAG,CAAC,GAAoB,EAAa,EAAE,CAAC,CAAC;IACrE,EAAE,EAAE,UAAU;IACd,IAAI,EAAE,2BAA2B,GAAG,CAAC,SAAS,EAAE;IAChD,KAAK,EAAE,GAAG,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC;IAC/B,GAAG,EAAE,GAAS,EAAE;QACd,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,GAAG,MAAM,CAAC,MAAM,4FAA4F,CAAC,CAAC;QAChI,CAAC;IACH,CAAC,CAAA;CACF,CAAC,CAAC;AAVU,QAAA,iBAAiB,qBAU3B","sourcesContent":["import { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';\n\nimport { SetupCheckResult, SetupStep } from '../../../lib/setupStep';\nimport { AwsSetupContext } from './awsSetupContext';\n\nconst checkIdentity = async (ctx: AwsSetupContext): Promise<SetupCheckResult> => {\n try {\n const { Account, Arn } = await new STSClient({ region: ctx.region }).send(new GetCallerIdentityCommand({}));\n if (Account !== ctx.accountId) {\n return { done: false, detail: `credentials are for account ${Account}, environment '${ctx.environment}' deploys to ${ctx.accountId}` };\n }\n return { done: true, detail: `${Arn}` };\n } catch (error) {\n return { done: false, detail: `no valid credentials: ${error instanceof Error ? error.message : String(error)}` };\n }\n};\n\n/** Check-only: the active credentials belong to the environment's account. Catches a stale .env or SSO profile. */\nexport const buildIdentityStep = (ctx: AwsSetupContext): SetupStep => ({\n id: 'identity',\n name: `Credentials for account ${ctx.accountId}`,\n check: () => checkIdentity(ctx),\n run: async () => {\n const result = await checkIdentity(ctx);\n if (!result.done) {\n throw new Error(`${result.detail}. Log in to the right account (or fix AWS_DEFAULT_ACCOUNT / deploy.config.json) and rerun.`);\n }\n },\n});\n"]}
@@ -0,0 +1,5 @@
1
+ import { SetupStep } from '../../../lib/setupStep';
2
+ import { AwsSetupContext } from './awsSetupContext';
3
+ export declare const buildAccountStackStep: (ctx: AwsSetupContext) => SetupStep;
4
+ export declare const buildDomainStackStep: (ctx: AwsSetupContext) => SetupStep;
5
+ export declare const buildBootstrapStackStep: (ctx: AwsSetupContext) => SetupStep;
@@ -0,0 +1,32 @@
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.buildBootstrapStackStep = exports.buildDomainStackStep = exports.buildAccountStackStep = void 0;
13
+ const quidproquo_deploy_awscdk_1 = require("quidproquo-deploy-awscdk");
14
+ const stacks_1 = require("../stacks");
15
+ const describeStackStatus_1 = require("./describeStackStatus");
16
+ const buildStackStep = (id, name, stackName, region, run) => ({
17
+ id,
18
+ name: `${name} (${stackName})`,
19
+ check: () => __awaiter(void 0, void 0, void 0, function* () {
20
+ const status = yield (0, describeStackStatus_1.describeStackStatus)(stackName, region);
21
+ return status === null ? { done: false, detail: 'not deployed' } : { done: (0, describeStackStatus_1.isStackHealthy)(status), detail: status };
22
+ }),
23
+ run,
24
+ });
25
+ const buildAccountStackStep = (ctx) => buildStackStep('account-stack', 'Account guardrails stack', quidproquo_deploy_awscdk_1.qpqDeployAwsCdkUtils.getAccountStackName(), ctx.region, () => (0, stacks_1.deployAccountStack)(ctx.appName));
26
+ exports.buildAccountStackStep = buildAccountStackStep;
27
+ // The domain stack deploys its cert stacks as dependencies, so one step covers certificates too.
28
+ const buildDomainStackStep = (ctx) => buildStackStep('domain-stack', 'Domain stack: certificates + api domains', quidproquo_deploy_awscdk_1.qpqDeployAwsCdkUtils.getDomainStackName(ctx.bootstrapQpqConfig), ctx.region, () => (0, stacks_1.deployDomainStack)(ctx.appName));
29
+ exports.buildDomainStackStep = buildDomainStackStep;
30
+ const buildBootstrapStackStep = (ctx) => buildStackStep('bootstrap-stack', 'Bootstrap stack: WAF and app-wide resources', quidproquo_deploy_awscdk_1.qpqDeployAwsCdkUtils.getBootstrapStackName(ctx.bootstrapQpqConfig), ctx.region, () => (0, stacks_1.deployBootstrapStack)(ctx.appName));
31
+ exports.buildBootstrapStackStep = buildBootstrapStackStep;
32
+ //# sourceMappingURL=buildStackSteps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildStackSteps.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/buildStackSteps.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,uEAAgE;AAGhE,sCAAwF;AAExF,+DAA4E;AAE5E,MAAM,cAAc,GAAG,CAAC,EAAU,EAAE,IAAY,EAAE,SAAiB,EAAE,MAAc,EAAE,GAAwB,EAAa,EAAE,CAAC,CAAC;IAC5H,EAAE;IACF,IAAI,EAAE,GAAG,IAAI,KAAK,SAAS,GAAG;IAC9B,KAAK,EAAE,GAAS,EAAE;QAChB,MAAM,MAAM,GAAG,MAAM,IAAA,yCAAmB,EAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QAC5D,OAAO,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAA,oCAAc,EAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACtH,CAAC,CAAA;IACD,GAAG;CACJ,CAAC,CAAC;AAEI,MAAM,qBAAqB,GAAG,CAAC,GAAoB,EAAa,EAAE,CACvE,cAAc,CAAC,eAAe,EAAE,0BAA0B,EAAE,+CAAoB,CAAC,mBAAmB,EAAE,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CACvH,IAAA,2BAAkB,EAAC,GAAG,CAAC,OAAO,CAAC,CAChC,CAAC;AAHS,QAAA,qBAAqB,yBAG9B;AAEJ,iGAAiG;AAC1F,MAAM,oBAAoB,GAAG,CAAC,GAAoB,EAAa,EAAE,CACtE,cAAc,CACZ,cAAc,EACd,0CAA0C,EAC1C,+CAAoB,CAAC,kBAAkB,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAC/D,GAAG,CAAC,MAAM,EACV,GAAG,EAAE,CAAC,IAAA,0BAAiB,EAAC,GAAG,CAAC,OAAO,CAAC,CACrC,CAAC;AAPS,QAAA,oBAAoB,wBAO7B;AAEG,MAAM,uBAAuB,GAAG,CAAC,GAAoB,EAAa,EAAE,CACzE,cAAc,CACZ,iBAAiB,EACjB,6CAA6C,EAC7C,+CAAoB,CAAC,qBAAqB,CAAC,GAAG,CAAC,kBAAkB,CAAC,EAClE,GAAG,CAAC,MAAM,EACV,GAAG,EAAE,CAAC,IAAA,6BAAoB,EAAC,GAAG,CAAC,OAAO,CAAC,CACxC,CAAC;AAPS,QAAA,uBAAuB,2BAOhC","sourcesContent":["import { qpqDeployAwsCdkUtils } from 'quidproquo-deploy-awscdk';\n\nimport { SetupStep } from '../../../lib/setupStep';\nimport { deployAccountStack, deployBootstrapStack, deployDomainStack } from '../stacks';\nimport { AwsSetupContext } from './awsSetupContext';\nimport { describeStackStatus, isStackHealthy } from './describeStackStatus';\n\nconst buildStackStep = (id: string, name: string, stackName: string, region: string, run: () => Promise<void>): SetupStep => ({\n id,\n name: `${name} (${stackName})`,\n check: async () => {\n const status = await describeStackStatus(stackName, region);\n return status === null ? { done: false, detail: 'not deployed' } : { done: isStackHealthy(status), detail: status };\n },\n run,\n});\n\nexport const buildAccountStackStep = (ctx: AwsSetupContext): SetupStep =>\n buildStackStep('account-stack', 'Account guardrails stack', qpqDeployAwsCdkUtils.getAccountStackName(), ctx.region, () =>\n deployAccountStack(ctx.appName),\n );\n\n// The domain stack deploys its cert stacks as dependencies, so one step covers certificates too.\nexport const buildDomainStackStep = (ctx: AwsSetupContext): SetupStep =>\n buildStackStep(\n 'domain-stack',\n 'Domain stack: certificates + api domains',\n qpqDeployAwsCdkUtils.getDomainStackName(ctx.bootstrapQpqConfig),\n ctx.region,\n () => deployDomainStack(ctx.appName),\n );\n\nexport const buildBootstrapStackStep = (ctx: AwsSetupContext): SetupStep =>\n buildStackStep(\n 'bootstrap-stack',\n 'Bootstrap stack: WAF and app-wide resources',\n qpqDeployAwsCdkUtils.getBootstrapStackName(ctx.bootstrapQpqConfig),\n ctx.region,\n () => deployBootstrapStack(ctx.appName),\n );\n"]}
@@ -0,0 +1,4 @@
1
+ import { Nullable } from 'quidproquo-core';
2
+ /** A stack's status, or null when it does not exist. */
3
+ export declare const describeStackStatus: (stackName: string, region: string) => Promise<Nullable<string>>;
4
+ export declare const isStackHealthy: (status: Nullable<string>) => boolean;
@@ -0,0 +1,32 @@
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.isStackHealthy = exports.describeStackStatus = void 0;
13
+ const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
14
+ /** A stack's status, or null when it does not exist. */
15
+ const describeStackStatus = (stackName, region) => __awaiter(void 0, void 0, void 0, function* () {
16
+ var _a, _b;
17
+ try {
18
+ const { Stacks } = yield new client_cloudformation_1.CloudFormationClient({ region }).send(new client_cloudformation_1.DescribeStacksCommand({ StackName: stackName }));
19
+ return (_b = (_a = Stacks === null || Stacks === void 0 ? void 0 : Stacks[0]) === null || _a === void 0 ? void 0 : _a.StackStatus) !== null && _b !== void 0 ? _b : null;
20
+ }
21
+ catch (error) {
22
+ if (error instanceof Error && /does not exist/.test(error.message)) {
23
+ return null;
24
+ }
25
+ throw error;
26
+ }
27
+ });
28
+ exports.describeStackStatus = describeStackStatus;
29
+ const healthyStatuses = ['CREATE_COMPLETE', 'UPDATE_COMPLETE', 'IMPORT_COMPLETE'];
30
+ const isStackHealthy = (status) => status !== null && healthyStatuses.includes(status);
31
+ exports.isStackHealthy = isStackHealthy;
32
+ //# sourceMappingURL=describeStackStatus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"describeStackStatus.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/describeStackStatus.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,0EAA6F;AAE7F,wDAAwD;AACjD,MAAM,mBAAmB,GAAG,CAAO,SAAiB,EAAE,MAAc,EAA6B,EAAE;;IACxG,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,IAAI,4CAAoB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,6CAAqB,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;QACxH,OAAO,MAAA,MAAA,MAAM,aAAN,MAAM,uBAAN,MAAM,CAAG,CAAC,CAAC,0CAAE,WAAW,mCAAI,IAAI,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,KAAK,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;YACnE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC,CAAA,CAAC;AAVW,QAAA,mBAAmB,uBAU9B;AAEF,MAAM,eAAe,GAAG,CAAC,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,CAAC,CAAC;AAE3E,MAAM,cAAc,GAAG,CAAC,MAAwB,EAAW,EAAE,CAAC,MAAM,KAAK,IAAI,IAAI,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAA5G,QAAA,cAAc,kBAA8F","sourcesContent":["import { Nullable } from 'quidproquo-core';\n\nimport { CloudFormationClient, DescribeStacksCommand } from '@aws-sdk/client-cloudformation';\n\n/** A stack's status, or null when it does not exist. */\nexport const describeStackStatus = async (stackName: string, region: string): Promise<Nullable<string>> => {\n try {\n const { Stacks } = await new CloudFormationClient({ region }).send(new DescribeStacksCommand({ StackName: stackName }));\n return Stacks?.[0]?.StackStatus ?? null;\n } catch (error) {\n if (error instanceof Error && /does not exist/.test(error.message)) {\n return null;\n }\n throw error;\n }\n};\n\nconst healthyStatuses = ['CREATE_COMPLETE', 'UPDATE_COMPLETE', 'IMPORT_COMPLETE'];\n\nexport const isStackHealthy = (status: Nullable<string>): boolean => status !== null && healthyStatuses.includes(status);\n"]}
@@ -0,0 +1 @@
1
+ export * from './awsSetupSteps';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./awsSetupSteps"), exports);
18
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,kDAAgC","sourcesContent":["export * from './awsSetupSteps';\n"]}
@@ -1,6 +1,7 @@
1
1
  import { QpqAppDeployEnvironment } from 'quidproquo-deploy-awscdk';
2
2
  import { ClearResourcesPlan } from '../lib/clearResourcesPlan';
3
3
  import { DeployPlan, TeardownPlan } from '../lib/deployPrompts';
4
+ import { SetupStep } from '../lib/setupStep';
4
5
  export declare enum QpqDeployPlatform {
5
6
  aws = "aws",
6
7
  docker = "docker"
@@ -11,6 +12,7 @@ export interface QpqPlatformDriver {
11
12
  goDocker?: (appName: string, plan: DeployPlan) => Promise<void>;
12
13
  teardown?: (appName: string, plan: TeardownPlan) => Promise<void>;
13
14
  clearResources?: (appName: string, plan: ClearResourcesPlan) => Promise<void>;
15
+ setupSteps?: (appName: string, environment: string) => Promise<SetupStep[]>;
14
16
  publish: (appName: string, serviceNames: string[]) => Promise<void>;
15
17
  publishBuild: (appName: string, serviceNames: string[]) => Promise<void>;
16
18
  publishUpload: (appName: string, serviceNames: string[]) => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/platforms/types.ts"],"names":[],"mappings":";;;AAKA,IAAY,iBAGX;AAHD,WAAY,iBAAiB;IAC3B,gCAAW,CAAA;IACX,sCAAiB,CAAA;AACnB,CAAC,EAHW,iBAAiB,iCAAjB,iBAAiB,QAG5B","sourcesContent":["import { QpqAppDeployEnvironment } from 'quidproquo-deploy-awscdk';\n\nimport { ClearResourcesPlan } from '../lib/clearResourcesPlan';\nimport { DeployPlan, TeardownPlan } from '../lib/deployPrompts';\n\nexport enum QpqDeployPlatform {\n aws = 'aws',\n docker = 'docker',\n}\n\n// A deploy platform driver — everything that happens after \"what to deploy\"\n// has been decided. `qpq go` resolves the app + environment, prompts the plan,\n// then hands off here; the platform comes from the environment's entry in\n// apps/<app>/deploy.config.json (default aws), so an app can move platforms\n// with a config change instead of a different command.\nexport interface QpqPlatformDriver {\n // Prime process.env with this platform's deploy identity from the resolved\n // environment target (already-set env vars win). Service configs read these\n // vars at load time, so this runs before any infrastructure.ts is required.\n // Returns human-readable descriptions of anything still missing — deploys\n // treat that as fatal, local dev commands ignore it.\n primeDeployIdentity: (target: QpqAppDeployEnvironment | undefined) => string[];\n\n // Sequential deploy (`qpq go`).\n go: (appName: string, plan: DeployPlan) => Promise<void>;\n\n // Parallel containerized deploy (`qpq go:docker`) — optional; drivers that\n // have no docker strategy simply omit it.\n goDocker?: (appName: string, plan: DeployPlan) => Promise<void>;\n\n // Destroy the web/api/inf stacks for selected services (`qpq teardown`) —\n // optional; drivers with no teardown strategy simply omit it.\n teardown?: (appName: string, plan: TeardownPlan) => Promise<void>;\n\n // Empty the stored data of selected resources without touching the stacks\n // (`qpq clear-resources`) — optional; drivers with no strategy omit it.\n clearResources?: (appName: string, plan: ClearResourcesPlan) => Promise<void>;\n\n // Federated remote publishing (`qpq publish[:build|:upload|:deploy]`).\n publish: (appName: string, serviceNames: string[]) => Promise<void>;\n publishBuild: (appName: string, serviceNames: string[]) => Promise<void>;\n publishUpload: (appName: string, serviceNames: string[]) => Promise<void>;\n publishDeploy: (appName: string, serviceNames: string[]) => Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/platforms/types.ts"],"names":[],"mappings":";;;AAMA,IAAY,iBAGX;AAHD,WAAY,iBAAiB;IAC3B,gCAAW,CAAA;IACX,sCAAiB,CAAA;AACnB,CAAC,EAHW,iBAAiB,iCAAjB,iBAAiB,QAG5B","sourcesContent":["import { QpqAppDeployEnvironment } from 'quidproquo-deploy-awscdk';\n\nimport { ClearResourcesPlan } from '../lib/clearResourcesPlan';\nimport { DeployPlan, TeardownPlan } from '../lib/deployPrompts';\nimport { SetupStep } from '../lib/setupStep';\n\nexport enum QpqDeployPlatform {\n aws = 'aws',\n docker = 'docker',\n}\n\n// A deploy platform driver — everything that happens after \"what to deploy\"\n// has been decided. `qpq go` resolves the app + environment, prompts the plan,\n// then hands off here; the platform comes from the environment's entry in\n// apps/<app>/deploy.config.json (default aws), so an app can move platforms\n// with a config change instead of a different command.\nexport interface QpqPlatformDriver {\n // Prime process.env with this platform's deploy identity from the resolved\n // environment target (already-set env vars win). Service configs read these\n // vars at load time, so this runs before any infrastructure.ts is required.\n // Returns human-readable descriptions of anything still missing — deploys\n // treat that as fatal, local dev commands ignore it.\n primeDeployIdentity: (target: QpqAppDeployEnvironment | undefined) => string[];\n\n // Sequential deploy (`qpq go`).\n go: (appName: string, plan: DeployPlan) => Promise<void>;\n\n // Parallel containerized deploy (`qpq go:docker`) — optional; drivers that\n // have no docker strategy simply omit it.\n goDocker?: (appName: string, plan: DeployPlan) => Promise<void>;\n\n // Destroy the web/api/inf stacks for selected services (`qpq teardown`) —\n // optional; drivers with no teardown strategy simply omit it.\n teardown?: (appName: string, plan: TeardownPlan) => Promise<void>;\n\n // Empty the stored data of selected resources without touching the stacks\n // (`qpq clear-resources`) — optional; drivers with no strategy omit it.\n clearResources?: (appName: string, plan: ClearResourcesPlan) => Promise<void>;\n\n // The ordered checklist that takes an empty cloud environment to a deployed\n // one (`qpq setup`) — optional; drivers with nothing to set up omit it.\n setupSteps?: (appName: string, environment: string) => Promise<SetupStep[]>;\n\n // Federated remote publishing (`qpq publish[:build|:upload|:deploy]`).\n publish: (appName: string, serviceNames: string[]) => Promise<void>;\n publishBuild: (appName: string, serviceNames: string[]) => Promise<void>;\n publishUpload: (appName: string, serviceNames: string[]) => Promise<void>;\n publishDeploy: (appName: string, serviceNames: string[]) => Promise<void>;\n}\n"]}
@@ -23,6 +23,12 @@ export const cliCommands = [
23
23
  ],
24
24
  run: (a) => require('../commands/go').goCommand(a),
25
25
  },
26
+ {
27
+ name: 'setup',
28
+ summary: 'Take a cloud environment from empty to deployed: checked steps, run in order (interactive)',
29
+ usageExtra: ["--check prints each step's status; --yes runs them all; --only <ids> runs a subset."],
30
+ run: (a) => require('../commands/setup').setupCommand(a),
31
+ },
26
32
  {
27
33
  name: 'go:docker',
28
34
  summary: 'Same as go (incl. positional args), but deploys in parallel via docker',
@@ -1 +1 @@
1
- {"version":3,"file":"commandRegistry.js","sourceRoot":"","sources":["../../../src/cli/commandRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["// Single source of truth for every qpq command: its name, the one-line\n// summary (shared by `qpq help` and the interactive menu) and a runner.\n//\n// Command modules are required lazily inside each runner so a simple\n// `qpq help` doesn't pay for the whole toolchain, and so ts-node can be\n// registered before any command module (or anything it requires) loads.\nimport { promptText } from '../lib/prompts';\n\nexport type CliCommand = {\n name: string;\n // Argument hint shown in the usage text, e.g. '[svc] [stack]'.\n usageArgs?: string;\n summary: string;\n // Extra usage-only detail lines, indented under the summary.\n usageExtra?: string[];\n // Menu-only: commands that need a positional argument (which the menu has\n // no argv to carry) gather it here before running.\n promptArgs?: () => Promise<string[]>;\n run: (commandArgv: string[]) => Promise<void>;\n};\n\n// `qpq hooks` needs a hook name; when launched from the menu there is no\n// argv to pass it in, so ask.\nconst promptHookName = async (): Promise<string[]> => [\n await promptText('Hook name (runs qpq:<name>, or plain <name>, in every app/package that defines it):'),\n];\n\n/* eslint-disable @typescript-eslint/no-require-imports */\nexport const cliCommands: CliCommand[] = [\n {\n name: 'go',\n usageArgs: '[svc] [stack]',\n summary: 'Deploy services (interactive; platform from deploy.config.json, default aws).',\n usageExtra: [\n 'Pass positional args to skip prompts, e.g. qpq go all all — svc is',\n \"'all' | comma-list | account | domain | bootstrap; stack is all|inf|api|web|views.\",\n \"A full 'all all' (or 'all api') deploy also publishes federated backends.\",\n ],\n run: (a) => require('../commands/go').goCommand(a),\n },\n {\n name: 'go:docker',\n summary: 'Same as go (incl. positional args), but deploys in parallel via docker',\n run: (a) => require('../commands/goDocker').goDockerCommand(a),\n },\n {\n name: 'go:dev',\n summary: 'Run the full local dev stack (api + web) in one process',\n run: (a) => require('../commands/goDev').goDevCommand(a),\n },\n {\n name: 'go:dev:api',\n summary: 'Run the local API dev server (hot reload)',\n run: (a) => require('../commands/goDevApi').goDevApiCommand(a),\n },\n {\n name: 'go:dev:web',\n summary: 'Run every views microfrontend dev server',\n run: (a) => require('../commands/goDevWeb').goDevWebCommand(a),\n },\n {\n name: 'migrate',\n usageArgs: '[app]',\n summary: 'Run pending migrations against the LOCAL dev store, once, then exit.',\n usageExtra: [\n 'Deployed, migrations run on a stack update; nothing local ever deploys, so',\n 'this is how you exercise one before shipping it. Records what it runs, so a',\n 'second call is a no-op.',\n ],\n run: (a) => require('../commands/migrate').migrateCommand(a),\n },\n {\n name: 'teardown',\n summary: 'Destroy web/api/inf stacks for selected services (interactive)',\n run: (a) => require('../commands/teardown').teardownCommand(a),\n },\n {\n name: 'clear-resources',\n summary: 'Empty selected buckets/tables (data only, stacks untouched; interactive)',\n run: (a) => require('../commands/clearResources').clearResourcesCommand(a),\n },\n {\n name: 'synth',\n usageArgs: '[service]',\n summary: 'Synth QPQ configs to dist/apps/<app>/infrastructure',\n run: (a) => require('../commands/synth').synthCommand(a),\n },\n {\n name: 'prep',\n summary: 'Regenerate apps/<app>/tsconfig.federated.json',\n run: (a) => require('../commands/prep').prepCommand(a),\n },\n {\n name: 'publish',\n summary: 'Build + upload + deploy federated remotes',\n run: (a) => require('../commands/publish').publishCommand(a),\n },\n {\n name: 'publish:build',\n summary: 'Build federated remotes locally',\n run: (a) => require('../commands/publish').publishBuildCommand(a),\n },\n {\n name: 'publish:upload',\n summary: 'Upload built version dirs (nothing goes live)',\n run: (a) => require('../commands/publish').publishUploadCommand(a),\n },\n {\n name: 'publish:deploy',\n summary: 'Flip manifests to the uploaded versions',\n run: (a) => require('../commands/publish').publishDeployCommand(a),\n },\n {\n name: 'check:circular',\n summary: 'Scan workspace sources for circular imports (fails on cycles; --warn to only report)',\n run: (a) => require('../commands/checkCircular').checkCircularCommand(a),\n },\n {\n name: 'hooks',\n usageArgs: '<name>',\n summary: 'Run qpq:<name> (or plain <name>) in every app/package that defines it (dependency ordered, parallel; --jobs=N)',\n promptArgs: promptHookName,\n run: (a) => require('../commands/hooks').hooksCommand(a),\n },\n];\n/* eslint-enable @typescript-eslint/no-require-imports */\n\n// Width of the \"name [args]\" column in the usage text.\nconst usageColumnWidth = 17;\n\nexport const buildUsage = (): string => {\n const commandLines = cliCommands.flatMap((command) => {\n const nameAndArgs = command.usageArgs ? `${command.name} ${command.usageArgs}` : command.name;\n const extraLines = (command.usageExtra ?? []).map((line) => ` ${''.padEnd(usageColumnWidth)}${line}`);\n return [` ${nameAndArgs.padEnd(usageColumnWidth)}${command.summary}`, ...extraLines];\n });\n\n return [\n 'qpq — build, dev and deploy orchestration for QPQ apps',\n '',\n 'Usage: qpq <command> [--app <name>] [--env <name>] [--platform <name>]',\n ' qpq (no command opens an interactive menu)',\n '',\n 'Commands:',\n ...commandLines,\n '',\n ].join('\\n');\n};\n"]}
1
+ {"version":3,"file":"commandRegistry.js","sourceRoot":"","sources":["../../../src/cli/commandRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["// Single source of truth for every qpq command: its name, the one-line\n// summary (shared by `qpq help` and the interactive menu) and a runner.\n//\n// Command modules are required lazily inside each runner so a simple\n// `qpq help` doesn't pay for the whole toolchain, and so ts-node can be\n// registered before any command module (or anything it requires) loads.\nimport { promptText } from '../lib/prompts';\n\nexport type CliCommand = {\n name: string;\n // Argument hint shown in the usage text, e.g. '[svc] [stack]'.\n usageArgs?: string;\n summary: string;\n // Extra usage-only detail lines, indented under the summary.\n usageExtra?: string[];\n // Menu-only: commands that need a positional argument (which the menu has\n // no argv to carry) gather it here before running.\n promptArgs?: () => Promise<string[]>;\n run: (commandArgv: string[]) => Promise<void>;\n};\n\n// `qpq hooks` needs a hook name; when launched from the menu there is no\n// argv to pass it in, so ask.\nconst promptHookName = async (): Promise<string[]> => [\n await promptText('Hook name (runs qpq:<name>, or plain <name>, in every app/package that defines it):'),\n];\n\n/* eslint-disable @typescript-eslint/no-require-imports */\nexport const cliCommands: CliCommand[] = [\n {\n name: 'go',\n usageArgs: '[svc] [stack]',\n summary: 'Deploy services (interactive; platform from deploy.config.json, default aws).',\n usageExtra: [\n 'Pass positional args to skip prompts, e.g. qpq go all all — svc is',\n \"'all' | comma-list | account | domain | bootstrap; stack is all|inf|api|web|views.\",\n \"A full 'all all' (or 'all api') deploy also publishes federated backends.\",\n ],\n run: (a) => require('../commands/go').goCommand(a),\n },\n {\n name: 'setup',\n summary: 'Take a cloud environment from empty to deployed: checked steps, run in order (interactive)',\n usageExtra: [\"--check prints each step's status; --yes runs them all; --only <ids> runs a subset.\"],\n run: (a) => require('../commands/setup').setupCommand(a),\n },\n {\n name: 'go:docker',\n summary: 'Same as go (incl. positional args), but deploys in parallel via docker',\n run: (a) => require('../commands/goDocker').goDockerCommand(a),\n },\n {\n name: 'go:dev',\n summary: 'Run the full local dev stack (api + web) in one process',\n run: (a) => require('../commands/goDev').goDevCommand(a),\n },\n {\n name: 'go:dev:api',\n summary: 'Run the local API dev server (hot reload)',\n run: (a) => require('../commands/goDevApi').goDevApiCommand(a),\n },\n {\n name: 'go:dev:web',\n summary: 'Run every views microfrontend dev server',\n run: (a) => require('../commands/goDevWeb').goDevWebCommand(a),\n },\n {\n name: 'migrate',\n usageArgs: '[app]',\n summary: 'Run pending migrations against the LOCAL dev store, once, then exit.',\n usageExtra: [\n 'Deployed, migrations run on a stack update; nothing local ever deploys, so',\n 'this is how you exercise one before shipping it. Records what it runs, so a',\n 'second call is a no-op.',\n ],\n run: (a) => require('../commands/migrate').migrateCommand(a),\n },\n {\n name: 'teardown',\n summary: 'Destroy web/api/inf stacks for selected services (interactive)',\n run: (a) => require('../commands/teardown').teardownCommand(a),\n },\n {\n name: 'clear-resources',\n summary: 'Empty selected buckets/tables (data only, stacks untouched; interactive)',\n run: (a) => require('../commands/clearResources').clearResourcesCommand(a),\n },\n {\n name: 'synth',\n usageArgs: '[service]',\n summary: 'Synth QPQ configs to dist/apps/<app>/infrastructure',\n run: (a) => require('../commands/synth').synthCommand(a),\n },\n {\n name: 'prep',\n summary: 'Regenerate apps/<app>/tsconfig.federated.json',\n run: (a) => require('../commands/prep').prepCommand(a),\n },\n {\n name: 'publish',\n summary: 'Build + upload + deploy federated remotes',\n run: (a) => require('../commands/publish').publishCommand(a),\n },\n {\n name: 'publish:build',\n summary: 'Build federated remotes locally',\n run: (a) => require('../commands/publish').publishBuildCommand(a),\n },\n {\n name: 'publish:upload',\n summary: 'Upload built version dirs (nothing goes live)',\n run: (a) => require('../commands/publish').publishUploadCommand(a),\n },\n {\n name: 'publish:deploy',\n summary: 'Flip manifests to the uploaded versions',\n run: (a) => require('../commands/publish').publishDeployCommand(a),\n },\n {\n name: 'check:circular',\n summary: 'Scan workspace sources for circular imports (fails on cycles; --warn to only report)',\n run: (a) => require('../commands/checkCircular').checkCircularCommand(a),\n },\n {\n name: 'hooks',\n usageArgs: '<name>',\n summary: 'Run qpq:<name> (or plain <name>) in every app/package that defines it (dependency ordered, parallel; --jobs=N)',\n promptArgs: promptHookName,\n run: (a) => require('../commands/hooks').hooksCommand(a),\n },\n];\n/* eslint-enable @typescript-eslint/no-require-imports */\n\n// Width of the \"name [args]\" column in the usage text.\nconst usageColumnWidth = 17;\n\nexport const buildUsage = (): string => {\n const commandLines = cliCommands.flatMap((command) => {\n const nameAndArgs = command.usageArgs ? `${command.name} ${command.usageArgs}` : command.name;\n const extraLines = (command.usageExtra ?? []).map((line) => ` ${''.padEnd(usageColumnWidth)}${line}`);\n return [` ${nameAndArgs.padEnd(usageColumnWidth)}${command.summary}`, ...extraLines];\n });\n\n return [\n 'qpq — build, dev and deploy orchestration for QPQ apps',\n '',\n 'Usage: qpq <command> [--app <name>] [--env <name>] [--platform <name>]',\n ' qpq (no command opens an interactive menu)',\n '',\n 'Commands:',\n ...commandLines,\n '',\n ].join('\\n');\n};\n"]}
@@ -14,11 +14,13 @@ import { getAppServiceQpqConfigs, getDevServerRspackConfig } from 'quidproquo-de
14
14
  import { spawn } from 'child_process';
15
15
  import path from 'path';
16
16
  import { rspack } from '@rspack/core';
17
+ import { hasArgFlag } from '../lib/args';
17
18
  import { primeDeployEnvFromConfig } from '../lib/deployEnv';
18
19
  import { writeDevServerEntry } from '../lib/devServerEntry';
19
20
  import { getRoot } from '../lib/discovery';
21
+ import { KEEP_OTHER_DEV_SERVERS_FLAG } from '../lib/keepOtherDevServersFlag';
20
22
  import { killChildWithEscalation } from '../lib/killChildWithEscalation';
21
- import { killOtherQpqDevProcesses, killStaleListeners } from '../lib/killStaleListeners';
23
+ import { killOtherQpqDevProcesses, killStaleListeners, reportPortHolders } from '../lib/killStaleListeners';
22
24
  import { resolveAppSelection } from '../lib/resolveAppSelection';
23
25
  // 8080/8888 are set in the generated entry; 3001 is the quidproquo-dev-server
24
26
  // file-storage (secure URL) default port.
@@ -30,11 +32,22 @@ export const goDevApiCommand = async (argv) => {
30
32
  process.env.QPQ_DEV_APP = appName;
31
33
  primeDeployEnvFromConfig(appName);
32
34
  console.log(`Dev server for app [${appName}]`);
33
- // Catches a lingering watcher from a previous run even if its spawned
34
- // child already exited (see killOtherQpqDevProcesses) then the usual
35
- // port-based sweep for anything else still bound to our ports.
36
- killOtherQpqDevProcesses(root);
37
- killStaleListeners(DEV_SERVER_PORTS, (command) => command.includes(DEV_SERVER_BUNDLE_PATH));
35
+ // The sweep matches on a RELATIVE bundle path, so every checkout's dev
36
+ // server looks like ours and a sibling worktree gets taken down with it.
37
+ // Opting out spares it, at the cost of this one not starting: the ports are
38
+ // the same either way, so what you get instead is EADDRINUSE. Report who
39
+ // holds them, or that failure says nothing about the cause.
40
+ if (hasArgFlag(argv, KEEP_OTHER_DEV_SERVERS_FLAG)) {
41
+ console.log(`${KEEP_OTHER_DEV_SERVERS_FLAG}: leaving other dev servers alone.`);
42
+ reportPortHolders(DEV_SERVER_PORTS);
43
+ }
44
+ else {
45
+ // Catches a lingering watcher from a previous run even if its spawned
46
+ // child already exited (see killOtherQpqDevProcesses) — then the usual
47
+ // port-based sweep for anything else still bound to our ports.
48
+ killOtherQpqDevProcesses(root);
49
+ killStaleListeners(DEV_SERVER_PORTS, (command) => command.includes(DEV_SERVER_BUNDLE_PATH));
50
+ }
38
51
  const qpqConfigs = getAppServiceQpqConfigs(root, appName);
39
52
  const entry = writeDevServerEntry(root, appName);
40
53
  const rspackConfig = getDevServerRspackConfig({ root, entry, qpqConfigs });
@@ -1 +1 @@
1
- {"version":3,"file":"goDevApi.js","sourceRoot":"","sources":["../../../src/commands/goDevApi.ts"],"names":[],"mappings":"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","sourcesContent":["// `qpq go:dev:api` — boots the QPQ local dev server. Resolves the app,\n// generates the dev-server entry, builds the rspack config, then runs rspack\n// in watch mode, restarting the server process on every successful rebuild.\n//\n// A restart is not instant: the server drains in-flight work and checkpoints\n// its stores on the way out (typically tens of ms, 5s worst case), and this\n// command waits for that before starting the replacement.\n//\n// The one thing NOT hot-reloaded is the qpq configs themselves: each service's\n// infrastructure.ts is evaluated once at launch and baked into the\n// `quidproquo-dynamic-loader` virtual module — config changes need a full\n// `go:dev:api` restart.\nimport { getAppServiceQpqConfigs, getDevServerRspackConfig } from 'quidproquo-deploy-rspack';\n\nimport { ChildProcess, spawn } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport { rspack } from '@rspack/core';\n\nimport { primeDeployEnvFromConfig } from '../lib/deployEnv';\nimport { writeDevServerEntry } from '../lib/devServerEntry';\nimport { getRoot } from '../lib/discovery';\nimport { killChildWithEscalation } from '../lib/killChildWithEscalation';\nimport { killOtherQpqDevProcesses, killStaleListeners } from '../lib/killStaleListeners';\nimport { resolveAppSelection } from '../lib/resolveAppSelection';\n\n// 8080/8888 are set in the generated entry; 3001 is the quidproquo-dev-server\n// file-storage (secure URL) default port.\nconst DEV_SERVER_PORTS = [8080, 8888, 3001];\nconst DEV_SERVER_BUNDLE_PATH = path.join('dist', 'qpq', 'dev-server', 'main.js');\n\nexport const goDevApiCommand = async (argv: string[]): Promise<void> => {\n const root = getRoot();\n const appName = await resolveAppSelection({ argv, envVar: 'QPQ_DEV_APP' });\n process.env.QPQ_DEV_APP = appName;\n primeDeployEnvFromConfig(appName);\n console.log(`Dev server for app [${appName}]`);\n\n // Catches a lingering watcher from a previous run even if its spawned\n // child already exited (see killOtherQpqDevProcesses) — then the usual\n // port-based sweep for anything else still bound to our ports.\n killOtherQpqDevProcesses(root);\n killStaleListeners(DEV_SERVER_PORTS, (command) => command.includes(DEV_SERVER_BUNDLE_PATH));\n\n const qpqConfigs = getAppServiceQpqConfigs(root, appName);\n const entry = writeDevServerEntry(root, appName);\n const rspackConfig = getDevServerRspackConfig({ root, entry, qpqConfigs });\n const bundlePath = path.join(root, DEV_SERVER_BUNDLE_PATH);\n const env = { ...process.env, QPQ_DEV_APP: appName };\n\n // The long-lived server process (HTTP 8080 / WS 8888), restarted per rebuild.\n let child: ChildProcess | null = null;\n let restartQueued = false;\n\n const startServer = (): void => {\n child = spawn('node', [bundlePath], { stdio: 'inherit', env });\n child.on('exit', (code) => {\n child = null;\n if (!restartQueued) {\n // Crashed (or exited) on its own — leave it down; the next successful\n // rebuild (i.e. the next file save) brings it back up.\n console.log(`Dev server exited with code ${code}. Save a file to restart it.`);\n }\n });\n };\n\n const restartServer = (): void => {\n if (restartQueued) return;\n if (!child) {\n startServer();\n return;\n }\n restartQueued = true;\n child.once('exit', () => {\n restartQueued = false;\n startServer();\n });\n\n // Not a bare kill: the server drains in-flight work before it exits, so it\n // needs asking properly and then a hard backstop if it wedges. Waiting for\n // the exit is what keeps the ports free for the replacement.\n void killChildWithEscalation(child);\n };\n\n console.log('Bundling dev server (watch mode)...');\n let lastHash: string | null | undefined;\n const compiler = rspack(rspackConfig);\n compiler.watch({ aggregateTimeout: 200 }, (err, stats) => {\n if (err) {\n console.error(err);\n return;\n }\n if (stats?.hasErrors()) {\n console.error(stats.toString({ colors: true, chunks: false, modules: false }));\n console.log('Build failed — dev server not restarted. Fix the error and save again.');\n return;\n }\n // Skip no-op rebuilds (e.g. the virtual-module-triggered second compile at\n // startup): only restart when the output actually changed.\n if (stats?.hash === lastHash && child) {\n return;\n }\n lastHash = stats?.hash;\n console.log(child ? 'Rebuilt. Restarting dev server...' : 'Dev server bundled. Starting...');\n restartServer();\n });\n\n // Wait for the server to finish draining before tearing the watcher down and\n // exiting: closing the compiler first would exit the parent while the child\n // is still writing, which is the race this whole path exists to close.\n //\n // SIGTERM as well as SIGINT, and it is load-bearing rather than symmetry for\n // its own sake. This process is a WRAPPER: the dev server that handles\n // signals and drains its work is the child it spawned. Without a handler\n // here, SIGTERM (what `docker stop` sends, and what any script stopping this\n // programmatically sends) kills the wrapper outright on the default\n // disposition, the child never gets asked to stop, and every guarantee the\n // dev server's shutdown sequence makes is bypassed. go:dev:web already\n // handles both.\n let shuttingDown = false;\n\n const handleShutdownSignal = async (): Promise<void> => {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n\n restartQueued = true; // suppress the crash log / rebuild-restart on our own kill\n\n if (child) {\n await killChildWithEscalation(child);\n }\n\n compiler.close(() => process.exit(0));\n };\n\n process.on('SIGINT', () => {\n void handleShutdownSignal();\n });\n\n process.on('SIGTERM', () => {\n void handleShutdownSignal();\n });\n};\n"]}
1
+ {"version":3,"file":"goDevApi.js","sourceRoot":"","sources":["../../../src/commands/goDevApi.ts"],"names":[],"mappings":"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","sourcesContent":["// `qpq go:dev:api` — boots the QPQ local dev server. Resolves the app,\n// generates the dev-server entry, builds the rspack config, then runs rspack\n// in watch mode, restarting the server process on every successful rebuild.\n//\n// A restart is not instant: the server drains in-flight work and checkpoints\n// its stores on the way out (typically tens of ms, 5s worst case), and this\n// command waits for that before starting the replacement.\n//\n// The one thing NOT hot-reloaded is the qpq configs themselves: each service's\n// infrastructure.ts is evaluated once at launch and baked into the\n// `quidproquo-dynamic-loader` virtual module — config changes need a full\n// `go:dev:api` restart.\nimport { getAppServiceQpqConfigs, getDevServerRspackConfig } from 'quidproquo-deploy-rspack';\n\nimport { ChildProcess, spawn } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\nimport { rspack } from '@rspack/core';\n\nimport { hasArgFlag } from '../lib/args';\nimport { primeDeployEnvFromConfig } from '../lib/deployEnv';\nimport { writeDevServerEntry } from '../lib/devServerEntry';\nimport { getRoot } from '../lib/discovery';\nimport { KEEP_OTHER_DEV_SERVERS_FLAG } from '../lib/keepOtherDevServersFlag';\nimport { killChildWithEscalation } from '../lib/killChildWithEscalation';\nimport { killOtherQpqDevProcesses, killStaleListeners, reportPortHolders } from '../lib/killStaleListeners';\nimport { resolveAppSelection } from '../lib/resolveAppSelection';\n\n// 8080/8888 are set in the generated entry; 3001 is the quidproquo-dev-server\n// file-storage (secure URL) default port.\nconst DEV_SERVER_PORTS = [8080, 8888, 3001];\nconst DEV_SERVER_BUNDLE_PATH = path.join('dist', 'qpq', 'dev-server', 'main.js');\n\nexport const goDevApiCommand = async (argv: string[]): Promise<void> => {\n const root = getRoot();\n const appName = await resolveAppSelection({ argv, envVar: 'QPQ_DEV_APP' });\n process.env.QPQ_DEV_APP = appName;\n primeDeployEnvFromConfig(appName);\n console.log(`Dev server for app [${appName}]`);\n\n // The sweep matches on a RELATIVE bundle path, so every checkout's dev\n // server looks like ours and a sibling worktree gets taken down with it.\n // Opting out spares it, at the cost of this one not starting: the ports are\n // the same either way, so what you get instead is EADDRINUSE. Report who\n // holds them, or that failure says nothing about the cause.\n if (hasArgFlag(argv, KEEP_OTHER_DEV_SERVERS_FLAG)) {\n console.log(`${KEEP_OTHER_DEV_SERVERS_FLAG}: leaving other dev servers alone.`);\n reportPortHolders(DEV_SERVER_PORTS);\n } else {\n // Catches a lingering watcher from a previous run even if its spawned\n // child already exited (see killOtherQpqDevProcesses) — then the usual\n // port-based sweep for anything else still bound to our ports.\n killOtherQpqDevProcesses(root);\n killStaleListeners(DEV_SERVER_PORTS, (command) => command.includes(DEV_SERVER_BUNDLE_PATH));\n }\n\n const qpqConfigs = getAppServiceQpqConfigs(root, appName);\n const entry = writeDevServerEntry(root, appName);\n const rspackConfig = getDevServerRspackConfig({ root, entry, qpqConfigs });\n const bundlePath = path.join(root, DEV_SERVER_BUNDLE_PATH);\n const env = { ...process.env, QPQ_DEV_APP: appName };\n\n // The long-lived server process (HTTP 8080 / WS 8888), restarted per rebuild.\n let child: ChildProcess | null = null;\n let restartQueued = false;\n\n const startServer = (): void => {\n child = spawn('node', [bundlePath], { stdio: 'inherit', env });\n child.on('exit', (code) => {\n child = null;\n if (!restartQueued) {\n // Crashed (or exited) on its own — leave it down; the next successful\n // rebuild (i.e. the next file save) brings it back up.\n console.log(`Dev server exited with code ${code}. Save a file to restart it.`);\n }\n });\n };\n\n const restartServer = (): void => {\n if (restartQueued) return;\n if (!child) {\n startServer();\n return;\n }\n restartQueued = true;\n child.once('exit', () => {\n restartQueued = false;\n startServer();\n });\n\n // Not a bare kill: the server drains in-flight work before it exits, so it\n // needs asking properly and then a hard backstop if it wedges. Waiting for\n // the exit is what keeps the ports free for the replacement.\n void killChildWithEscalation(child);\n };\n\n console.log('Bundling dev server (watch mode)...');\n let lastHash: string | null | undefined;\n const compiler = rspack(rspackConfig);\n compiler.watch({ aggregateTimeout: 200 }, (err, stats) => {\n if (err) {\n console.error(err);\n return;\n }\n if (stats?.hasErrors()) {\n console.error(stats.toString({ colors: true, chunks: false, modules: false }));\n console.log('Build failed — dev server not restarted. Fix the error and save again.');\n return;\n }\n // Skip no-op rebuilds (e.g. the virtual-module-triggered second compile at\n // startup): only restart when the output actually changed.\n if (stats?.hash === lastHash && child) {\n return;\n }\n lastHash = stats?.hash;\n console.log(child ? 'Rebuilt. Restarting dev server...' : 'Dev server bundled. Starting...');\n restartServer();\n });\n\n // Wait for the server to finish draining before tearing the watcher down and\n // exiting: closing the compiler first would exit the parent while the child\n // is still writing, which is the race this whole path exists to close.\n //\n // SIGTERM as well as SIGINT, and it is load-bearing rather than symmetry for\n // its own sake. This process is a WRAPPER: the dev server that handles\n // signals and drains its work is the child it spawned. Without a handler\n // here, SIGTERM (what `docker stop` sends, and what any script stopping this\n // programmatically sends) kills the wrapper outright on the default\n // disposition, the child never gets asked to stop, and every guarantee the\n // dev server's shutdown sequence makes is bypassed. go:dev:web already\n // handles both.\n let shuttingDown = false;\n\n const handleShutdownSignal = async (): Promise<void> => {\n if (shuttingDown) {\n return;\n }\n shuttingDown = true;\n\n restartQueued = true; // suppress the crash log / rebuild-restart on our own kill\n\n if (child) {\n await killChildWithEscalation(child);\n }\n\n compiler.close(() => process.exit(0));\n };\n\n process.on('SIGINT', () => {\n void handleShutdownSignal();\n });\n\n process.on('SIGTERM', () => {\n void handleShutdownSignal();\n });\n};\n"]}
@@ -13,9 +13,11 @@ import http from 'http';
13
13
  import { rspack } from '@rspack/core';
14
14
  import { RspackDevServer } from '@rspack/dev-server';
15
15
  import { getArgValue } from '../lib/args';
16
+ import { hasArgFlag } from '../lib/args';
16
17
  import { primeDeployEnvFromConfig } from '../lib/deployEnv';
17
18
  import { getRoot } from '../lib/discovery';
18
- import { isQpqCliCommand, killStaleListeners } from '../lib/killStaleListeners';
19
+ import { KEEP_OTHER_DEV_SERVERS_FLAG } from '../lib/keepOtherDevServersFlag';
20
+ import { isQpqCliCommand, killStaleListeners, reportPortHolders } from '../lib/killStaleListeners';
19
21
  import { resolveAppSelection } from '../lib/resolveAppSelection';
20
22
  export const goDevWebCommand = async (argv, options = {}) => {
21
23
  const root = getRoot();
@@ -33,7 +35,13 @@ export const goDevWebCommand = async (argv, options = {}) => {
33
35
  console.error('No views projects found.');
34
36
  process.exit(1);
35
37
  }
36
- killStaleListeners(views.map((v) => v.port), isQpqCliCommand);
38
+ // See goDevApi for why this is opt-out rather than always-on.
39
+ if (hasArgFlag(argv, KEEP_OTHER_DEV_SERVERS_FLAG)) {
40
+ reportPortHolders(views.map((v) => v.port));
41
+ }
42
+ else {
43
+ killStaleListeners(views.map((v) => v.port), isQpqCliCommand);
44
+ }
37
45
  // Every RspackDevServer.start() registers its own SIGINT/SIGTERM handler on
38
46
  // process — with a dozen views servers in one process that trips node's
39
47
  // default 10-listener warning. Size the limit to the fleet.
@@ -1 +1 @@
1
- {"version":3,"file":"goDevWeb.js","sourceRoot":"","sources":["../../../src/commands/goDevWeb.ts"],"names":[],"mappings":"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","sourcesContent":["// `qpq go:dev:web` — boots every views microfrontend's Rspack dev server\n// in-process (shell is the MF host app, plus all remotes on their own ports).\n// Everything is served dynamically; remotes resolve at runtime via\n// mf-manifest.json on localhost ports. Programmatic — no per-views\n// rspack.config.ts or npm serve script needed.\n//\n// Usage:\n// qpq go:dev:web\n// qpq go:dev:web --only shell,design\n// qpq go:dev:web --app <name>\nimport { getAllViews, getViewsRspackConfig } from 'quidproquo-deploy-rspack';\n\nimport http from 'http';\nimport { rspack } from '@rspack/core';\nimport { RspackDevServer } from '@rspack/dev-server';\n\nimport { getArgValue } from '../lib/args';\nimport { primeDeployEnvFromConfig } from '../lib/deployEnv';\nimport { getRoot } from '../lib/discovery';\nimport { isQpqCliCommand, killStaleListeners } from '../lib/killStaleListeners';\nimport { resolveAppSelection } from '../lib/resolveAppSelection';\n\nexport type GoDevWebOptions = {\n // The in-place \"(started)\" chip rewrite assumes this command owns the\n // terminal; when stdout is shared with the API dev server (combined\n // `qpq go:dev`) the cursor math breaks. The plain-line fallback is just a\n // wall of \" auth started\" noise interleaved with the API output, so drop\n // the ready status altogether there.\n hideStartedStatus?: boolean;\n};\n\nexport const goDevWebCommand = async (argv: string[], options: GoDevWebOptions = {}): Promise<void> => {\n const root = getRoot();\n const appName = await resolveAppSelection({ argv, envVar: 'QPQ_DEV_APP' });\n\n process.env.LOCAL_DEV_SERVER = 'true';\n process.env.NODE_ENV = 'development';\n primeDeployEnvFromConfig(appName);\n\n const only = getArgValue(argv, '--only')\n ?.split(',')\n .map((s) => s.trim());\n\n let views = getAllViews(root, appName);\n if (only) views = views.filter((v) => only.includes(v.service));\n if (views.length === 0) {\n console.error('No views projects found.');\n process.exit(1);\n }\n\n killStaleListeners(\n views.map((v) => v.port),\n isQpqCliCommand,\n );\n\n // Every RspackDevServer.start() registers its own SIGINT/SIGTERM handler on\n // process — with a dozen views servers in one process that trips node's\n // default 10-listener warning. Size the limit to the fleet.\n process.setMaxListeners(Math.max(process.getMaxListeners(), views.length * 2 + 10));\n\n // Service names are padded to a common width so the \"(started)\" chips align.\n const serviceColumnWidth = Math.max(...views.map((v) => v.service.length));\n\n console.log(`Starting ${views.length} views dev servers for [${appName}]:`);\n for (const v of views) console.log(` http://localhost:${v.port} ${v.service.padEnd(serviceColumnWidth)}`);\n\n const servers: RspackDevServer[] = [];\n let shuttingDown = false;\n\n const shutdown = (code: number): void => {\n if (shuttingDown) return;\n shuttingDown = true;\n Promise.allSettled(servers.map((server) => server.stop())).then(() => process.exit(code));\n };\n\n // The URL list above gets a yellow \"(started)\" chip appended in place as\n // each dev server begins answering HTTP. The in-place rewrite moves the\n // cursor up past everything printed since the list, so every line printed\n // below it MUST go through logBelowList to keep the offset accurate.\n let linesBelowList = 0;\n\n const logBelowList = (line: string): void => {\n console.log(line);\n linesBelowList += 1;\n };\n\n const markStarted = (index: number): void => {\n const v = views[index];\n if (!process.stdout.isTTY) {\n logBelowList(` ${v.service} started`);\n return;\n }\n const line = ` http://localhost:${v.port} ${v.service.padEnd(serviceColumnWidth)} \\x1b[33m(started)\\x1b[0m`;\n const up = linesBelowList + (views.length - index);\n process.stdout.write(`\\x1b[${up}F\\x1b[2K${line}\\x1b[${up}E`);\n };\n\n const pollUntilStarted = (index: number): void => {\n const attempt = (): void => {\n if (shuttingDown) return;\n const req = http.get(\n {\n host: 'localhost',\n port: views[index].port,\n path: '/',\n timeout: 1000,\n },\n (res) => {\n res.resume();\n markStarted(index);\n },\n );\n req.on('error', () => setTimeout(attempt, 500));\n req.on('timeout', () => req.destroy());\n };\n attempt();\n };\n\n for (const [index, view] of views.entries()) {\n const config = getViewsRspackConfig(view.viewsDir);\n const compiler = rspack(config);\n const server = new RspackDevServer(config.devServer || { port: view.port }, compiler);\n servers.push(server);\n\n try {\n await server.start();\n } catch (e) {\n logBelowList(` [${view.service}] failed to start: ${(e as Error).message} — shutting down all views servers.`);\n shutdown(1);\n return;\n }\n\n // markStarted is the only consumer of the ready signal, so there is\n // nothing to poll for when the status is hidden.\n if (!options.hideStartedStatus) pollUntilStarted(index);\n }\n\n process.on('SIGINT', () => shutdown(0));\n process.on('SIGTERM', () => shutdown(0));\n};\n"]}
1
+ {"version":3,"file":"goDevWeb.js","sourceRoot":"","sources":["../../../src/commands/goDevWeb.ts"],"names":[],"mappings":"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","sourcesContent":["// `qpq go:dev:web` — boots every views microfrontend's Rspack dev server\n// in-process (shell is the MF host app, plus all remotes on their own ports).\n// Everything is served dynamically; remotes resolve at runtime via\n// mf-manifest.json on localhost ports. Programmatic — no per-views\n// rspack.config.ts or npm serve script needed.\n//\n// Usage:\n// qpq go:dev:web\n// qpq go:dev:web --only shell,design\n// qpq go:dev:web --app <name>\nimport { getAllViews, getViewsRspackConfig } from 'quidproquo-deploy-rspack';\n\nimport http from 'http';\nimport { rspack } from '@rspack/core';\nimport { RspackDevServer } from '@rspack/dev-server';\n\nimport { getArgValue } from '../lib/args';\nimport { hasArgFlag } from '../lib/args';\nimport { primeDeployEnvFromConfig } from '../lib/deployEnv';\nimport { getRoot } from '../lib/discovery';\nimport { KEEP_OTHER_DEV_SERVERS_FLAG } from '../lib/keepOtherDevServersFlag';\nimport { isQpqCliCommand, killStaleListeners, reportPortHolders } from '../lib/killStaleListeners';\nimport { resolveAppSelection } from '../lib/resolveAppSelection';\n\nexport type GoDevWebOptions = {\n // The in-place \"(started)\" chip rewrite assumes this command owns the\n // terminal; when stdout is shared with the API dev server (combined\n // `qpq go:dev`) the cursor math breaks. The plain-line fallback is just a\n // wall of \" auth started\" noise interleaved with the API output, so drop\n // the ready status altogether there.\n hideStartedStatus?: boolean;\n};\n\nexport const goDevWebCommand = async (argv: string[], options: GoDevWebOptions = {}): Promise<void> => {\n const root = getRoot();\n const appName = await resolveAppSelection({ argv, envVar: 'QPQ_DEV_APP' });\n\n process.env.LOCAL_DEV_SERVER = 'true';\n process.env.NODE_ENV = 'development';\n primeDeployEnvFromConfig(appName);\n\n const only = getArgValue(argv, '--only')\n ?.split(',')\n .map((s) => s.trim());\n\n let views = getAllViews(root, appName);\n if (only) views = views.filter((v) => only.includes(v.service));\n if (views.length === 0) {\n console.error('No views projects found.');\n process.exit(1);\n }\n\n // See goDevApi for why this is opt-out rather than always-on.\n if (hasArgFlag(argv, KEEP_OTHER_DEV_SERVERS_FLAG)) {\n reportPortHolders(views.map((v) => v.port));\n } else {\n killStaleListeners(\n views.map((v) => v.port),\n isQpqCliCommand,\n );\n }\n\n // Every RspackDevServer.start() registers its own SIGINT/SIGTERM handler on\n // process — with a dozen views servers in one process that trips node's\n // default 10-listener warning. Size the limit to the fleet.\n process.setMaxListeners(Math.max(process.getMaxListeners(), views.length * 2 + 10));\n\n // Service names are padded to a common width so the \"(started)\" chips align.\n const serviceColumnWidth = Math.max(...views.map((v) => v.service.length));\n\n console.log(`Starting ${views.length} views dev servers for [${appName}]:`);\n for (const v of views) console.log(` http://localhost:${v.port} ${v.service.padEnd(serviceColumnWidth)}`);\n\n const servers: RspackDevServer[] = [];\n let shuttingDown = false;\n\n const shutdown = (code: number): void => {\n if (shuttingDown) return;\n shuttingDown = true;\n Promise.allSettled(servers.map((server) => server.stop())).then(() => process.exit(code));\n };\n\n // The URL list above gets a yellow \"(started)\" chip appended in place as\n // each dev server begins answering HTTP. The in-place rewrite moves the\n // cursor up past everything printed since the list, so every line printed\n // below it MUST go through logBelowList to keep the offset accurate.\n let linesBelowList = 0;\n\n const logBelowList = (line: string): void => {\n console.log(line);\n linesBelowList += 1;\n };\n\n const markStarted = (index: number): void => {\n const v = views[index];\n if (!process.stdout.isTTY) {\n logBelowList(` ${v.service} started`);\n return;\n }\n const line = ` http://localhost:${v.port} ${v.service.padEnd(serviceColumnWidth)} \\x1b[33m(started)\\x1b[0m`;\n const up = linesBelowList + (views.length - index);\n process.stdout.write(`\\x1b[${up}F\\x1b[2K${line}\\x1b[${up}E`);\n };\n\n const pollUntilStarted = (index: number): void => {\n const attempt = (): void => {\n if (shuttingDown) return;\n const req = http.get(\n {\n host: 'localhost',\n port: views[index].port,\n path: '/',\n timeout: 1000,\n },\n (res) => {\n res.resume();\n markStarted(index);\n },\n );\n req.on('error', () => setTimeout(attempt, 500));\n req.on('timeout', () => req.destroy());\n };\n attempt();\n };\n\n for (const [index, view] of views.entries()) {\n const config = getViewsRspackConfig(view.viewsDir);\n const compiler = rspack(config);\n const server = new RspackDevServer(config.devServer || { port: view.port }, compiler);\n servers.push(server);\n\n try {\n await server.start();\n } catch (e) {\n logBelowList(` [${view.service}] failed to start: ${(e as Error).message} — shutting down all views servers.`);\n shutdown(1);\n return;\n }\n\n // markStarted is the only consumer of the ready signal, so there is\n // nothing to poll for when the status is hidden.\n if (!options.hideStartedStatus) pollUntilStarted(index);\n }\n\n process.on('SIGINT', () => shutdown(0));\n process.on('SIGTERM', () => shutdown(0));\n};\n"]}