quidproquo-cli 0.1.19 → 0.1.21
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.
- package/README.md +1 -0
- package/lib/commonjs/cli/commandRegistry.js +6 -0
- package/lib/commonjs/cli/commandRegistry.js.map +1 -1
- package/lib/commonjs/commands/goDevApi.js +18 -5
- package/lib/commonjs/commands/goDevApi.js.map +1 -1
- package/lib/commonjs/commands/goDevWeb.js +10 -2
- package/lib/commonjs/commands/goDevWeb.js.map +1 -1
- package/lib/commonjs/commands/setup.d.ts +1 -0
- package/lib/commonjs/commands/setup.js +102 -0
- package/lib/commonjs/commands/setup.js.map +1 -0
- package/lib/commonjs/lib/args.d.ts +1 -0
- package/lib/commonjs/lib/args.js +11 -1
- package/lib/commonjs/lib/args.js.map +1 -1
- package/lib/commonjs/lib/keepOtherDevServersFlag.d.ts +1 -0
- package/lib/commonjs/lib/keepOtherDevServersFlag.js +11 -0
- package/lib/commonjs/lib/keepOtherDevServersFlag.js.map +1 -0
- package/lib/commonjs/lib/killStaleListeners.d.ts +14 -0
- package/lib/commonjs/lib/killStaleListeners.js +47 -20
- package/lib/commonjs/lib/killStaleListeners.js.map +1 -1
- package/lib/commonjs/lib/prompts.d.ts +2 -0
- package/lib/commonjs/lib/prompts.js +1 -1
- package/lib/commonjs/lib/prompts.js.map +1 -1
- package/lib/commonjs/lib/setupStep.d.ts +16 -0
- package/lib/commonjs/lib/setupStep.js +3 -0
- package/lib/commonjs/lib/setupStep.js.map +1 -0
- package/lib/commonjs/platforms/aws/index.js +2 -0
- package/lib/commonjs/platforms/aws/index.js.map +1 -1
- package/lib/commonjs/platforms/aws/setup/awsSetupContext.d.ts +12 -0
- package/lib/commonjs/platforms/aws/setup/awsSetupContext.js +23 -0
- package/lib/commonjs/platforms/aws/setup/awsSetupContext.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/awsSetupSteps.d.ts +8 -0
- package/lib/commonjs/platforms/aws/setup/awsSetupSteps.js +44 -0
- package/lib/commonjs/platforms/aws/setup/awsSetupSteps.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.d.ts +4 -0
- package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.js +77 -0
- package/lib/commonjs/platforms/aws/setup/buildBucketNamesStep.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/buildCdkBootstrapSteps.d.ts +5 -0
- package/lib/commonjs/platforms/aws/setup/buildCdkBootstrapSteps.js +38 -0
- package/lib/commonjs/platforms/aws/setup/buildCdkBootstrapSteps.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.d.ts +4 -0
- package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.js +55 -0
- package/lib/commonjs/platforms/aws/setup/buildGithubDeployRoleStep.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/buildGithubOidcProviderStep.d.ts +4 -0
- package/lib/commonjs/platforms/aws/setup/buildGithubOidcProviderStep.js +54 -0
- package/lib/commonjs/platforms/aws/setup/buildGithubOidcProviderStep.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/buildHostedZonesStep.d.ts +4 -0
- package/lib/commonjs/platforms/aws/setup/buildHostedZonesStep.js +63 -0
- package/lib/commonjs/platforms/aws/setup/buildHostedZonesStep.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/buildIamCapabilityStep.d.ts +4 -0
- package/lib/commonjs/platforms/aws/setup/buildIamCapabilityStep.js +48 -0
- package/lib/commonjs/platforms/aws/setup/buildIamCapabilityStep.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/buildIdentityStep.d.ts +4 -0
- package/lib/commonjs/platforms/aws/setup/buildIdentityStep.js +39 -0
- package/lib/commonjs/platforms/aws/setup/buildIdentityStep.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/buildStackSteps.d.ts +5 -0
- package/lib/commonjs/platforms/aws/setup/buildStackSteps.js +32 -0
- package/lib/commonjs/platforms/aws/setup/buildStackSteps.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/describeStackStatus.d.ts +4 -0
- package/lib/commonjs/platforms/aws/setup/describeStackStatus.js +32 -0
- package/lib/commonjs/platforms/aws/setup/describeStackStatus.js.map +1 -0
- package/lib/commonjs/platforms/aws/setup/index.d.ts +1 -0
- package/lib/commonjs/platforms/aws/setup/index.js +18 -0
- package/lib/commonjs/platforms/aws/setup/index.js.map +1 -0
- package/lib/commonjs/platforms/types.d.ts +2 -0
- package/lib/commonjs/platforms/types.js.map +1 -1
- package/lib/esm/cli/commandRegistry.js +6 -0
- package/lib/esm/cli/commandRegistry.js.map +1 -1
- package/lib/esm/commands/goDevApi.js +19 -6
- package/lib/esm/commands/goDevApi.js.map +1 -1
- package/lib/esm/commands/goDevWeb.js +10 -2
- package/lib/esm/commands/goDevWeb.js.map +1 -1
- package/lib/esm/commands/setup.d.ts +1 -0
- package/lib/esm/commands/setup.js +88 -0
- package/lib/esm/commands/setup.js.map +1 -0
- package/lib/esm/lib/args.d.ts +1 -0
- package/lib/esm/lib/args.js +9 -0
- package/lib/esm/lib/args.js.map +1 -1
- package/lib/esm/lib/keepOtherDevServersFlag.d.ts +1 -0
- package/lib/esm/lib/keepOtherDevServersFlag.js +8 -0
- package/lib/esm/lib/keepOtherDevServersFlag.js.map +1 -0
- package/lib/esm/lib/killStaleListeners.d.ts +14 -0
- package/lib/esm/lib/killStaleListeners.js +45 -19
- package/lib/esm/lib/killStaleListeners.js.map +1 -1
- package/lib/esm/lib/prompts.d.ts +2 -0
- package/lib/esm/lib/prompts.js +1 -1
- package/lib/esm/lib/prompts.js.map +1 -1
- package/lib/esm/lib/setupStep.d.ts +16 -0
- package/lib/esm/lib/setupStep.js +2 -0
- package/lib/esm/lib/setupStep.js.map +1 -0
- package/lib/esm/platforms/aws/index.js +2 -0
- package/lib/esm/platforms/aws/index.js.map +1 -1
- package/lib/esm/platforms/aws/setup/awsSetupContext.d.ts +12 -0
- package/lib/esm/platforms/aws/setup/awsSetupContext.js +18 -0
- package/lib/esm/platforms/aws/setup/awsSetupContext.js.map +1 -0
- package/lib/esm/platforms/aws/setup/awsSetupSteps.d.ts +8 -0
- package/lib/esm/platforms/aws/setup/awsSetupSteps.js +31 -0
- package/lib/esm/platforms/aws/setup/awsSetupSteps.js.map +1 -0
- package/lib/esm/platforms/aws/setup/buildBucketNamesStep.d.ts +4 -0
- package/lib/esm/platforms/aws/setup/buildBucketNamesStep.js +62 -0
- package/lib/esm/platforms/aws/setup/buildBucketNamesStep.js.map +1 -0
- package/lib/esm/platforms/aws/setup/buildCdkBootstrapSteps.d.ts +5 -0
- package/lib/esm/platforms/aws/setup/buildCdkBootstrapSteps.js +24 -0
- package/lib/esm/platforms/aws/setup/buildCdkBootstrapSteps.js.map +1 -0
- package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.d.ts +4 -0
- package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.js +40 -0
- package/lib/esm/platforms/aws/setup/buildGithubDeployRoleStep.js.map +1 -0
- package/lib/esm/platforms/aws/setup/buildGithubOidcProviderStep.d.ts +4 -0
- package/lib/esm/platforms/aws/setup/buildGithubOidcProviderStep.js +40 -0
- package/lib/esm/platforms/aws/setup/buildGithubOidcProviderStep.js.map +1 -0
- package/lib/esm/platforms/aws/setup/buildHostedZonesStep.d.ts +4 -0
- package/lib/esm/platforms/aws/setup/buildHostedZonesStep.js +49 -0
- package/lib/esm/platforms/aws/setup/buildHostedZonesStep.js.map +1 -0
- package/lib/esm/platforms/aws/setup/buildIamCapabilityStep.d.ts +4 -0
- package/lib/esm/platforms/aws/setup/buildIamCapabilityStep.js +34 -0
- package/lib/esm/platforms/aws/setup/buildIamCapabilityStep.js.map +1 -0
- package/lib/esm/platforms/aws/setup/buildIdentityStep.d.ts +4 -0
- package/lib/esm/platforms/aws/setup/buildIdentityStep.js +26 -0
- package/lib/esm/platforms/aws/setup/buildIdentityStep.js.map +1 -0
- package/lib/esm/platforms/aws/setup/buildStackSteps.d.ts +5 -0
- package/lib/esm/platforms/aws/setup/buildStackSteps.js +17 -0
- package/lib/esm/platforms/aws/setup/buildStackSteps.js.map +1 -0
- package/lib/esm/platforms/aws/setup/describeStackStatus.d.ts +4 -0
- package/lib/esm/platforms/aws/setup/describeStackStatus.js +17 -0
- package/lib/esm/platforms/aws/setup/describeStackStatus.js.map +1 -0
- package/lib/esm/platforms/aws/setup/index.d.ts +1 -0
- package/lib/esm/platforms/aws/setup/index.js +2 -0
- package/lib/esm/platforms/aws/setup/index.js.map +1 -0
- package/lib/esm/platforms/types.d.ts +2 -0
- package/lib/esm/platforms/types.js.map +1 -1
- package/package.json +13 -10
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const setupCommand: (argv: string[]) => Promise<void>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// `qpq setup` — takes a cloud environment from empty to deployed, in order, with
|
|
2
|
+
// every step checked first so the menu shows what is already in place and a
|
|
3
|
+
// rerun on a half-set-up account is safe. The platform (from the environment's
|
|
4
|
+
// entry in deploy.config.json) contributes the steps.
|
|
5
|
+
// --check print each step's status and exit, run nothing
|
|
6
|
+
// --yes run every step without the menu
|
|
7
|
+
// --only a,b run only the named step ids (no menu)
|
|
8
|
+
import { getArgValue } from '../lib/args';
|
|
9
|
+
import { resolveDeployEnvironment } from '../lib/deployEnv';
|
|
10
|
+
import { promptCheckbox } from '../lib/prompts';
|
|
11
|
+
import { resolveAppSelection } from '../lib/resolveAppSelection';
|
|
12
|
+
import { getPlatformDriver } from '../platforms';
|
|
13
|
+
const statusLabel = (result) => (result === null ? '?' : result.done ? 'done' : 'todo');
|
|
14
|
+
const printChecklist = (checked) => {
|
|
15
|
+
for (const { step, result } of checked) {
|
|
16
|
+
const detail = result?.detail ? ` ${result.detail}` : '';
|
|
17
|
+
console.log(` [${statusLabel(result).padEnd(4)}] ${step.name}${detail}`);
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
const checkAll = async (steps) => {
|
|
21
|
+
const checked = [];
|
|
22
|
+
for (const step of steps) {
|
|
23
|
+
try {
|
|
24
|
+
checked.push({ step, result: await step.check() });
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
checked.push({ step, result: { done: false, detail: `check failed: ${error instanceof Error ? error.message : String(error)}` } });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return checked;
|
|
31
|
+
};
|
|
32
|
+
// Steps not yet done are preselected; done ones can still be picked to rerun.
|
|
33
|
+
const chooseSteps = async (checked) => {
|
|
34
|
+
const chosen = await promptCheckbox('Setup steps to run (space toggles, enter runs in order)', checked.map(({ step, result }) => ({
|
|
35
|
+
name: `[${statusLabel(result).padEnd(4)}] ${step.name}`,
|
|
36
|
+
value: step,
|
|
37
|
+
checked: !result?.done,
|
|
38
|
+
description: result?.detail,
|
|
39
|
+
})));
|
|
40
|
+
// Always run in checklist order, whatever order they were ticked in.
|
|
41
|
+
return checked.map(({ step }) => step).filter((step) => chosen.includes(step));
|
|
42
|
+
};
|
|
43
|
+
const selectFromArgs = (steps, argv) => {
|
|
44
|
+
if (argv.includes('--yes')) {
|
|
45
|
+
return steps;
|
|
46
|
+
}
|
|
47
|
+
const only = getArgValue(argv, '--only');
|
|
48
|
+
if (!only) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const ids = only.split(',').map((id) => id.trim());
|
|
52
|
+
const unknown = ids.filter((id) => !steps.some((step) => step.id === id));
|
|
53
|
+
if (unknown.length > 0) {
|
|
54
|
+
console.error(`Unknown setup step id(s): ${unknown.join(', ')}. Known: ${steps.map((step) => step.id).join(', ')}`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
return steps.filter((step) => ids.includes(step.id));
|
|
58
|
+
};
|
|
59
|
+
export const setupCommand = async (argv) => {
|
|
60
|
+
const appName = await resolveAppSelection({ argv, envVar: 'DEPLOY_APP_NAME' });
|
|
61
|
+
const { environment, platform } = await resolveDeployEnvironment(argv, appName);
|
|
62
|
+
const driver = getPlatformDriver(platform);
|
|
63
|
+
if (!driver.setupSteps) {
|
|
64
|
+
console.log(`The '${platform}' platform has nothing to set up for environment '${environment}'.`);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const steps = await driver.setupSteps(appName, environment);
|
|
68
|
+
console.log(`\nSetup for app '${appName}', environment '${environment}' (${platform})\n`);
|
|
69
|
+
const checked = await checkAll(steps);
|
|
70
|
+
printChecklist(checked);
|
|
71
|
+
console.log('');
|
|
72
|
+
if (argv.includes('--check')) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const toRun = selectFromArgs(steps, argv) ?? (await chooseSteps(checked));
|
|
76
|
+
if (toRun.length === 0) {
|
|
77
|
+
console.log('Nothing selected.');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
for (const step of toRun) {
|
|
81
|
+
console.log(`\n== ${step.name}\n`);
|
|
82
|
+
await step.run();
|
|
83
|
+
}
|
|
84
|
+
console.log('\nSetup complete. Final state:\n');
|
|
85
|
+
printChecklist(await checkAll(steps));
|
|
86
|
+
console.log(`\nNext: deploy the services with npx qpq go:docker all all --app ${appName} --env ${environment}`);
|
|
87
|
+
};
|
|
88
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/commands/setup.ts"],"names":[],"mappings":"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","sourcesContent":["// `qpq setup` — takes a cloud environment from empty to deployed, in order, with\n// every step checked first so the menu shows what is already in place and a\n// rerun on a half-set-up account is safe. The platform (from the environment's\n// entry in deploy.config.json) contributes the steps.\n// --check print each step's status and exit, run nothing\n// --yes run every step without the menu\n// --only a,b run only the named step ids (no menu)\nimport { getArgValue } from '../lib/args';\nimport { resolveDeployEnvironment } from '../lib/deployEnv';\nimport { promptCheckbox } from '../lib/prompts';\nimport { resolveAppSelection } from '../lib/resolveAppSelection';\nimport { SetupCheckResult, SetupStep } from '../lib/setupStep';\nimport { getPlatformDriver } from '../platforms';\n\ntype CheckedStep = { step: SetupStep; result: SetupCheckResult | null };\n\nconst statusLabel = (result: SetupCheckResult | null): string => (result === null ? '?' : result.done ? 'done' : 'todo');\n\nconst printChecklist = (checked: CheckedStep[]): void => {\n for (const { step, result } of checked) {\n const detail = result?.detail ? ` ${result.detail}` : '';\n console.log(` [${statusLabel(result).padEnd(4)}] ${step.name}${detail}`);\n }\n};\n\nconst checkAll = async (steps: SetupStep[]): Promise<CheckedStep[]> => {\n const checked: CheckedStep[] = [];\n for (const step of steps) {\n try {\n checked.push({ step, result: await step.check() });\n } catch (error) {\n checked.push({ step, result: { done: false, detail: `check failed: ${error instanceof Error ? error.message : String(error)}` } });\n }\n }\n return checked;\n};\n\n// Steps not yet done are preselected; done ones can still be picked to rerun.\nconst chooseSteps = async (checked: CheckedStep[]): Promise<SetupStep[]> => {\n const chosen = await promptCheckbox(\n 'Setup steps to run (space toggles, enter runs in order)',\n checked.map(({ step, result }) => ({\n name: `[${statusLabel(result).padEnd(4)}] ${step.name}`,\n value: step,\n checked: !result?.done,\n description: result?.detail,\n })),\n );\n\n // Always run in checklist order, whatever order they were ticked in.\n return checked.map(({ step }) => step).filter((step) => chosen.includes(step));\n};\n\nconst selectFromArgs = (steps: SetupStep[], argv: string[]): SetupStep[] | null => {\n if (argv.includes('--yes')) {\n return steps;\n }\n\n const only = getArgValue(argv, '--only');\n if (!only) {\n return null;\n }\n\n const ids = only.split(',').map((id) => id.trim());\n const unknown = ids.filter((id) => !steps.some((step) => step.id === id));\n if (unknown.length > 0) {\n console.error(`Unknown setup step id(s): ${unknown.join(', ')}. Known: ${steps.map((step) => step.id).join(', ')}`);\n process.exit(1);\n }\n\n return steps.filter((step) => ids.includes(step.id));\n};\n\nexport const setupCommand = async (argv: string[]): Promise<void> => {\n const appName = await resolveAppSelection({ argv, envVar: 'DEPLOY_APP_NAME' });\n const { environment, platform } = await resolveDeployEnvironment(argv, appName);\n const driver = getPlatformDriver(platform);\n\n if (!driver.setupSteps) {\n console.log(`The '${platform}' platform has nothing to set up for environment '${environment}'.`);\n return;\n }\n\n const steps = await driver.setupSteps(appName, environment);\n\n console.log(`\\nSetup for app '${appName}', environment '${environment}' (${platform})\\n`);\n const checked = await checkAll(steps);\n printChecklist(checked);\n console.log('');\n\n if (argv.includes('--check')) {\n return;\n }\n\n const toRun = selectFromArgs(steps, argv) ?? (await chooseSteps(checked));\n if (toRun.length === 0) {\n console.log('Nothing selected.');\n return;\n }\n\n for (const step of toRun) {\n console.log(`\\n== ${step.name}\\n`);\n await step.run();\n }\n\n console.log('\\nSetup complete. Final state:\\n');\n printChecklist(await checkAll(steps));\n console.log(`\\nNext: deploy the services with npx qpq go:docker all all --app ${appName} --env ${environment}`);\n};\n"]}
|
package/lib/esm/lib/args.d.ts
CHANGED
package/lib/esm/lib/args.js
CHANGED
|
@@ -6,6 +6,15 @@ export const getArgValue = (argv, flag) => {
|
|
|
6
6
|
const idx = argv.indexOf(flag);
|
|
7
7
|
return idx >= 0 ? argv[idx + 1] : undefined;
|
|
8
8
|
};
|
|
9
|
+
// A presence-only flag: `--foo`. `--foo=false` is honoured as off, so a
|
|
10
|
+
// wrapper script can pass the flag through with a computed value rather than
|
|
11
|
+
// having to conditionally omit it.
|
|
12
|
+
export const hasArgFlag = (argv, flag) => {
|
|
13
|
+
const eq = argv.find((a) => a.startsWith(`${flag}=`));
|
|
14
|
+
if (eq)
|
|
15
|
+
return eq.slice(`${flag}=`.length) !== 'false';
|
|
16
|
+
return argv.includes(flag);
|
|
17
|
+
};
|
|
9
18
|
// Positional args = non-flag args, skipping values consumed by known flags.
|
|
10
19
|
export const getPositionalArgs = (argv, valueFlags) => argv.filter((arg, index) => {
|
|
11
20
|
if (arg.startsWith('--'))
|
package/lib/esm/lib/args.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../../src/lib/args.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAc,EAAE,IAAY,EAAsB,EAAE;IAC9E,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IACtD,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9C,CAAC,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,IAAc,EAAE,UAAoB,EAAY,EAAE,CAClF,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;IACzB,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACjC,OAAO,CAAC,CAAC,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC","sourcesContent":["// Flag parsing over the command's argv (everything after the command name).\nexport const getArgValue = (argv: string[], flag: string): string | undefined => {\n const eq = argv.find((a) => a.startsWith(`${flag}=`));\n if (eq) return eq.slice(`${flag}=`.length);\n const idx = argv.indexOf(flag);\n return idx >= 0 ? argv[idx + 1] : undefined;\n};\n\n// Positional args = non-flag args, skipping values consumed by known flags.\nexport const getPositionalArgs = (argv: string[], valueFlags: string[]): string[] =>\n argv.filter((arg, index) => {\n if (arg.startsWith('--')) return false;\n const previous = argv[index - 1];\n return !(previous && valueFlags.includes(previous));\n });\n"]}
|
|
1
|
+
{"version":3,"file":"args.js","sourceRoot":"","sources":["../../../src/lib/args.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,IAAc,EAAE,IAAY,EAAsB,EAAE;IAC9E,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IACtD,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9C,CAAC,CAAC;AAEF,wEAAwE;AACxE,6EAA6E;AAC7E,mCAAmC;AACnC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,IAAc,EAAE,IAAY,EAAW,EAAE;IAClE,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,CAAC;IACtD,IAAI,EAAE;QAAE,OAAO,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC;IAEvD,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC,CAAC;AAEF,4EAA4E;AAC5E,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,IAAc,EAAE,UAAoB,EAAY,EAAE,CAClF,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE;IACzB,IAAI,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC;IACjC,OAAO,CAAC,CAAC,QAAQ,IAAI,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC","sourcesContent":["// Flag parsing over the command's argv (everything after the command name).\nexport const getArgValue = (argv: string[], flag: string): string | undefined => {\n const eq = argv.find((a) => a.startsWith(`${flag}=`));\n if (eq) return eq.slice(`${flag}=`.length);\n const idx = argv.indexOf(flag);\n return idx >= 0 ? argv[idx + 1] : undefined;\n};\n\n// A presence-only flag: `--foo`. `--foo=false` is honoured as off, so a\n// wrapper script can pass the flag through with a computed value rather than\n// having to conditionally omit it.\nexport const hasArgFlag = (argv: string[], flag: string): boolean => {\n const eq = argv.find((a) => a.startsWith(`${flag}=`));\n if (eq) return eq.slice(`${flag}=`.length) !== 'false';\n\n return argv.includes(flag);\n};\n\n// Positional args = non-flag args, skipping values consumed by known flags.\nexport const getPositionalArgs = (argv: string[], valueFlags: string[]): string[] =>\n argv.filter((arg, index) => {\n if (arg.startsWith('--')) return false;\n const previous = argv[index - 1];\n return !(previous && valueFlags.includes(previous));\n });\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const KEEP_OTHER_DEV_SERVERS_FLAG = "--keep-other-dev-servers";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Opt out of the pre-start sweep that kills anything already holding the dev
|
|
2
|
+
// ports, including a dev server belonging to a different checkout of the same
|
|
3
|
+
// repo. Off by default, so the existing behaviour is unchanged.
|
|
4
|
+
//
|
|
5
|
+
// One flag rather than one per command: `go:dev` hands the same argv to both
|
|
6
|
+
// `go:dev:api` and `go:dev:web`, so declaring it once covers all three.
|
|
7
|
+
export const KEEP_OTHER_DEV_SERVERS_FLAG = '--keep-other-dev-servers';
|
|
8
|
+
//# sourceMappingURL=keepOtherDevServersFlag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keepOtherDevServersFlag.js","sourceRoot":"","sources":["../../../src/lib/keepOtherDevServersFlag.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,8EAA8E;AAC9E,gEAAgE;AAChE,EAAE;AACF,6EAA6E;AAC7E,wEAAwE;AACxE,MAAM,CAAC,MAAM,2BAA2B,GAAG,0BAA0B,CAAC","sourcesContent":["// Opt out of the pre-start sweep that kills anything already holding the dev\n// ports, including a dev server belonging to a different checkout of the same\n// repo. Off by default, so the existing behaviour is unchanged.\n//\n// One flag rather than one per command: `go:dev` hands the same argv to both\n// `go:dev:api` and `go:dev:web`, so declaring it once covers all three.\nexport const KEEP_OTHER_DEV_SERVERS_FLAG = '--keep-other-dev-servers';\n"]}
|
|
@@ -1,3 +1,17 @@
|
|
|
1
1
|
export declare const isQpqCliCommand: (command: string) => boolean;
|
|
2
2
|
export declare const killOtherQpqDevProcesses: (root: string) => void;
|
|
3
|
+
/**
|
|
4
|
+
* Say what is holding these ports, without touching it.
|
|
5
|
+
*
|
|
6
|
+
* The other half of --keep-other-dev-servers: sparing a sibling checkout's dev
|
|
7
|
+
* server means this one cannot bind, and an unexplained EADDRINUSE is a worse
|
|
8
|
+
* experience than the silent kill it replaces. Naming the pid and the command
|
|
9
|
+
* turns "it just died" into something you can act on.
|
|
10
|
+
*
|
|
11
|
+
* Worded as a prediction, not a fact, because it IS one: this runs before the
|
|
12
|
+
* bundle step, so by the time anything binds - seconds later - the holder may
|
|
13
|
+
* well have gone. Stating "port in use" and then starting cleanly reads like
|
|
14
|
+
* the warning was wrong.
|
|
15
|
+
*/
|
|
16
|
+
export declare const reportPortHolders: (ports: number[]) => void;
|
|
3
17
|
export declare const killStaleListeners: (ports: number[], isOurs: (command: string) => boolean) => void;
|
|
@@ -145,29 +145,55 @@ export const killOtherQpqDevProcesses = (root) => {
|
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
};
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
148
|
+
// Whatever is currently listening on a port, as pid + command line. Empty when
|
|
149
|
+
// the port is free, or when lsof is unavailable.
|
|
150
|
+
const getPortListeners = (port) => {
|
|
151
|
+
let pids = [];
|
|
152
|
+
try {
|
|
153
|
+
pids = execSync(`lsof -t -iTCP:${port} -sTCP:LISTEN`, {
|
|
154
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
155
|
+
})
|
|
156
|
+
.toString()
|
|
157
|
+
.split('\n')
|
|
158
|
+
.map((line) => Number(line.trim()))
|
|
159
|
+
.filter(Boolean);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return []; // lsof exits non-zero when nothing is listening
|
|
163
|
+
}
|
|
164
|
+
return pids.flatMap((pid) => {
|
|
151
165
|
try {
|
|
152
|
-
|
|
153
|
-
stdio: ['ignore', 'pipe', 'ignore'],
|
|
154
|
-
})
|
|
155
|
-
.toString()
|
|
156
|
-
.split('\n')
|
|
157
|
-
.map((line) => Number(line.trim()))
|
|
158
|
-
.filter(Boolean);
|
|
166
|
+
return [{ pid, command: execSync(`ps -p ${pid} -o command=`).toString().trim() }];
|
|
159
167
|
}
|
|
160
168
|
catch {
|
|
161
|
-
|
|
169
|
+
return []; // already gone
|
|
162
170
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Say what is holding these ports, without touching it.
|
|
175
|
+
*
|
|
176
|
+
* The other half of --keep-other-dev-servers: sparing a sibling checkout's dev
|
|
177
|
+
* server means this one cannot bind, and an unexplained EADDRINUSE is a worse
|
|
178
|
+
* experience than the silent kill it replaces. Naming the pid and the command
|
|
179
|
+
* turns "it just died" into something you can act on.
|
|
180
|
+
*
|
|
181
|
+
* Worded as a prediction, not a fact, because it IS one: this runs before the
|
|
182
|
+
* bundle step, so by the time anything binds - seconds later - the holder may
|
|
183
|
+
* well have gone. Stating "port in use" and then starting cleanly reads like
|
|
184
|
+
* the warning was wrong.
|
|
185
|
+
*/
|
|
186
|
+
export const reportPortHolders = (ports) => {
|
|
187
|
+
for (const port of ports) {
|
|
188
|
+
for (const { pid, command } of getPortListeners(port)) {
|
|
189
|
+
console.warn(`Port ${port} is currently held by pid ${pid}: ${command}`);
|
|
190
|
+
console.warn(` Starting anyway. If it still holds port ${port} once this server binds, startup will fail with EADDRINUSE.`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
export const killStaleListeners = (ports, isOurs) => {
|
|
195
|
+
for (const port of ports) {
|
|
196
|
+
for (const { pid, command } of getPortListeners(port)) {
|
|
171
197
|
if (isOurs(command)) {
|
|
172
198
|
console.log(`Killing stale dev server on port ${port} (pid ${pid})`);
|
|
173
199
|
process.kill(pid);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"killStaleListeners.js","sourceRoot":"","sources":["../../../src/lib/killStaleListeners.ts"],"names":[],"mappings":"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","sourcesContent":["// Pre-start sweep shared by the dev-server commands: a previous run whose\n// wrapper died without killing its listener (closed terminal, SIGKILL) leaves\n// an orphaned process holding a dev port, and the next launch dies with\n// EADDRINUSE. Before starting, kill any listener on the given ports whose\n// command line matches `isOurs`. Anything else on the ports is left alone\n// and reported — it's not ours.\nimport { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\n\n// Matches any invocation of the qpq CLI itself, regardless of how it was\n// launched: `node node_modules/.bin/qpq ...` (the npm-script shim, which has\n// no .js extension), a direct `node .../lib/commonjs/bin/qpq.js` call, a\n// global `qpq` shebang script, etc. Checks each whitespace-separated token's\n// basename (extension stripped) rather than a fixed substring like\n// 'bin/qpq.js', which only matches the last of those forms.\nexport const isQpqCliCommand = (command: string): boolean =>\n command\n .trim()\n .split(/\\s+/)\n .some((token) => {\n const base = path.basename(token);\n const withoutExt = base.endsWith('.js') ? base.slice(0, -3) : base;\n return withoutExt === 'qpq';\n });\n\n// SIGTERM only requests termination — the caller (about to bind this same\n// process's ports itself) needs it actually gone first. A stale dev server\n// can be mid-startup when killed (still resolving config, not yet bound to\n// every port it eventually binds) and keep running for a bit past the\n// signal, going on to bind a LATER port — waiting for just the one port that\n// matched isn't enough; wait for the whole pid to disappear so it can't bind\n// anything else afterward. Poll synchronously (this whole sweep runs before\n// anything async starts) via `kill(pid, 0)` — reliable and fast here because\n// these are unrelated, previously-orphaned processes, not children of this\n// one (a real child can sit as a zombie answering `kill(pid, 0)` until this\n// process's own event loop reaps it, which a tight sync loop would starve;\n// doesn't apply to a process we didn't spawn).\n//\n// The default covers the dev server's graceful shutdown (drain in-flight work,\n// then checkpoint the stores) with room to spare — it used to be 3s, which was\n// fine when the server died instantly and is not now. Returns whether the pid\n// actually went, because a caller about to bind these ports has to escalate\n// rather than hope: giving up while the old server still holds 8080 is exactly\n// the EADDRINUSE this file exists to prevent.\nconst waitForPidGone = (pid: number, timeoutMs = 8000): boolean => {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n process.kill(pid, 0);\n } catch {\n return true; // throws once the pid no longer exists\n }\n execSync('sleep 0.05');\n }\n\n return false;\n};\n\n// Last resort for a process that ignored, or wedged during, its graceful stop.\nconst killPidHard = (pid: number): void => {\n console.warn(`Process ${pid} did not exit gracefully — sending SIGKILL.`);\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n return; // raced us and exited after all\n }\n waitForPidGone(pid, 2000);\n};\n\n// realpath'd: lsof reports the canonical path (e.g. /private/tmp on macOS,\n// where /tmp is a symlink), so comparing against a caller-supplied root\n// verbatim would false-negative whenever any component of the repo path is\n// itself a symlink (a symlinked home directory, an NFS mount, etc).\nconst getProcessCwd = (pid: number): string | null => {\n try {\n const line = execSync(`lsof -a -p ${pid} -d cwd -Fn`, { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .split('\\n')\n .find((l) => l.startsWith('n'));\n return line ? fs.realpathSync(line.slice(1)) : null;\n } catch {\n return null; // pid gone, or we don't have permission to inspect it\n }\n};\n\n// The sweep below must never target this process's own wrappers: launched as\n// `npx qpq go:dev:api` (or via any shell whose -c string names qpq), the\n// ancestor chain (`sh -c \"npx ... qpq ...\"`, `npm exec qpq ...`) matches\n// isQpqCliCommand and shares our cwd, so excluding just process.pid isn't\n// enough — SIGINTing an ancestor tears down our own tree. Walk ppid to the\n// top and exclude the whole chain.\nconst getOwnAncestorPids = (): Set<number> => {\n const ancestors = new Set<number>();\n let pid = process.pid;\n while (pid > 1 && !ancestors.has(pid)) {\n ancestors.add(pid);\n try {\n pid = Number(\n execSync(`ps -o ppid= -p ${pid}`, { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim(),\n );\n } catch {\n break;\n }\n }\n return ancestors;\n};\n\n// killStaleListeners only sees processes currently BOUND to one of the given\n// ports — it can't catch a still-running `qpq go:dev*` watcher whose spawned\n// dev-server child already died (a crash, or us killing just the child):\n// the watcher itself holds no port at that moment, so it's invisible to a\n// port scan, yet it's still alive and can spawn a fresh child — and bind a\n// port — moments later, racing whatever we're about to start. Close that gap\n// by finding any OTHER qpq CLI process rooted in the same repo checkout\n// (matched by cwd, not by port or command-line args, which vary by how it\n// was invoked) and killing it outright, regardless of what it currently has\n// bound. SIGINT (not SIGTERM) so goDevApiCommand/goDevWebCommand's own\n// handler runs and tears down its child cleanly rather than orphaning it.\nexport const killOtherQpqDevProcesses = (root: string): void => {\n const resolvedRoot = fs.realpathSync(root);\n const ownPids = getOwnAncestorPids();\n\n let lines: string[] = [];\n try {\n lines = execSync('ps -axo pid=,command=', { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .split('\\n');\n } catch {\n return;\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n const spaceIndex = trimmed.indexOf(' ');\n if (spaceIndex === -1) continue;\n\n const pid = Number(trimmed.slice(0, spaceIndex));\n const command = trimmed.slice(spaceIndex + 1).trim();\n\n if (!pid || ownPids.has(pid) || !isQpqCliCommand(command)) continue;\n if (getProcessCwd(pid) !== resolvedRoot) continue;\n\n console.log(`Killing other qpq dev process for this repo (pid ${pid}): ${command}`);\n process.kill(pid, 'SIGINT');\n if (!waitForPidGone(pid)) {\n killPidHard(pid);\n }\n }\n};\n\nexport const killStaleListeners = (ports: number[], isOurs: (command: string) => boolean): void => {\n for (const port of ports) {\n let pids: number[] = [];\n try {\n pids = execSync(`lsof -t -iTCP:${port} -sTCP:LISTEN`, {\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .split('\\n')\n .map((line) => Number(line.trim()))\n .filter(Boolean);\n } catch {\n continue; // lsof exits non-zero when nothing is listening\n }\n\n for (const pid of pids) {\n let command = '';\n try {\n command = execSync(`ps -p ${pid} -o command=`).toString().trim();\n } catch {\n continue; // already gone\n }\n if (isOurs(command)) {\n console.log(`Killing stale dev server on port ${port} (pid ${pid})`);\n process.kill(pid);\n if (!waitForPidGone(pid)) {\n killPidHard(pid);\n }\n } else {\n console.warn(`Port ${port} is in use by an unrelated process (pid ${pid}: ${command}) — not killing it.`);\n }\n }\n }\n};\n"]}
|
|
1
|
+
{"version":3,"file":"killStaleListeners.js","sourceRoot":"","sources":["../../../src/lib/killStaleListeners.ts"],"names":[],"mappings":"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","sourcesContent":["// Pre-start sweep shared by the dev-server commands: a previous run whose\n// wrapper died without killing its listener (closed terminal, SIGKILL) leaves\n// an orphaned process holding a dev port, and the next launch dies with\n// EADDRINUSE. Before starting, kill any listener on the given ports whose\n// command line matches `isOurs`. Anything else on the ports is left alone\n// and reported — it's not ours.\nimport { execSync } from 'child_process';\nimport fs from 'fs';\nimport path from 'path';\n\n// Matches any invocation of the qpq CLI itself, regardless of how it was\n// launched: `node node_modules/.bin/qpq ...` (the npm-script shim, which has\n// no .js extension), a direct `node .../lib/commonjs/bin/qpq.js` call, a\n// global `qpq` shebang script, etc. Checks each whitespace-separated token's\n// basename (extension stripped) rather than a fixed substring like\n// 'bin/qpq.js', which only matches the last of those forms.\nexport const isQpqCliCommand = (command: string): boolean =>\n command\n .trim()\n .split(/\\s+/)\n .some((token) => {\n const base = path.basename(token);\n const withoutExt = base.endsWith('.js') ? base.slice(0, -3) : base;\n return withoutExt === 'qpq';\n });\n\n// SIGTERM only requests termination — the caller (about to bind this same\n// process's ports itself) needs it actually gone first. A stale dev server\n// can be mid-startup when killed (still resolving config, not yet bound to\n// every port it eventually binds) and keep running for a bit past the\n// signal, going on to bind a LATER port — waiting for just the one port that\n// matched isn't enough; wait for the whole pid to disappear so it can't bind\n// anything else afterward. Poll synchronously (this whole sweep runs before\n// anything async starts) via `kill(pid, 0)` — reliable and fast here because\n// these are unrelated, previously-orphaned processes, not children of this\n// one (a real child can sit as a zombie answering `kill(pid, 0)` until this\n// process's own event loop reaps it, which a tight sync loop would starve;\n// doesn't apply to a process we didn't spawn).\n//\n// The default covers the dev server's graceful shutdown (drain in-flight work,\n// then checkpoint the stores) with room to spare — it used to be 3s, which was\n// fine when the server died instantly and is not now. Returns whether the pid\n// actually went, because a caller about to bind these ports has to escalate\n// rather than hope: giving up while the old server still holds 8080 is exactly\n// the EADDRINUSE this file exists to prevent.\nconst waitForPidGone = (pid: number, timeoutMs = 8000): boolean => {\n const deadline = Date.now() + timeoutMs;\n while (Date.now() < deadline) {\n try {\n process.kill(pid, 0);\n } catch {\n return true; // throws once the pid no longer exists\n }\n execSync('sleep 0.05');\n }\n\n return false;\n};\n\n// Last resort for a process that ignored, or wedged during, its graceful stop.\nconst killPidHard = (pid: number): void => {\n console.warn(`Process ${pid} did not exit gracefully — sending SIGKILL.`);\n try {\n process.kill(pid, 'SIGKILL');\n } catch {\n return; // raced us and exited after all\n }\n waitForPidGone(pid, 2000);\n};\n\n// realpath'd: lsof reports the canonical path (e.g. /private/tmp on macOS,\n// where /tmp is a symlink), so comparing against a caller-supplied root\n// verbatim would false-negative whenever any component of the repo path is\n// itself a symlink (a symlinked home directory, an NFS mount, etc).\nconst getProcessCwd = (pid: number): string | null => {\n try {\n const line = execSync(`lsof -a -p ${pid} -d cwd -Fn`, { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .split('\\n')\n .find((l) => l.startsWith('n'));\n return line ? fs.realpathSync(line.slice(1)) : null;\n } catch {\n return null; // pid gone, or we don't have permission to inspect it\n }\n};\n\n// The sweep below must never target this process's own wrappers: launched as\n// `npx qpq go:dev:api` (or via any shell whose -c string names qpq), the\n// ancestor chain (`sh -c \"npx ... qpq ...\"`, `npm exec qpq ...`) matches\n// isQpqCliCommand and shares our cwd, so excluding just process.pid isn't\n// enough — SIGINTing an ancestor tears down our own tree. Walk ppid to the\n// top and exclude the whole chain.\nconst getOwnAncestorPids = (): Set<number> => {\n const ancestors = new Set<number>();\n let pid = process.pid;\n while (pid > 1 && !ancestors.has(pid)) {\n ancestors.add(pid);\n try {\n pid = Number(\n execSync(`ps -o ppid= -p ${pid}`, { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .trim(),\n );\n } catch {\n break;\n }\n }\n return ancestors;\n};\n\n// killStaleListeners only sees processes currently BOUND to one of the given\n// ports — it can't catch a still-running `qpq go:dev*` watcher whose spawned\n// dev-server child already died (a crash, or us killing just the child):\n// the watcher itself holds no port at that moment, so it's invisible to a\n// port scan, yet it's still alive and can spawn a fresh child — and bind a\n// port — moments later, racing whatever we're about to start. Close that gap\n// by finding any OTHER qpq CLI process rooted in the same repo checkout\n// (matched by cwd, not by port or command-line args, which vary by how it\n// was invoked) and killing it outright, regardless of what it currently has\n// bound. SIGINT (not SIGTERM) so goDevApiCommand/goDevWebCommand's own\n// handler runs and tears down its child cleanly rather than orphaning it.\nexport const killOtherQpqDevProcesses = (root: string): void => {\n const resolvedRoot = fs.realpathSync(root);\n const ownPids = getOwnAncestorPids();\n\n let lines: string[] = [];\n try {\n lines = execSync('ps -axo pid=,command=', { stdio: ['ignore', 'pipe', 'ignore'] })\n .toString()\n .split('\\n');\n } catch {\n return;\n }\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed) continue;\n\n const spaceIndex = trimmed.indexOf(' ');\n if (spaceIndex === -1) continue;\n\n const pid = Number(trimmed.slice(0, spaceIndex));\n const command = trimmed.slice(spaceIndex + 1).trim();\n\n if (!pid || ownPids.has(pid) || !isQpqCliCommand(command)) continue;\n if (getProcessCwd(pid) !== resolvedRoot) continue;\n\n console.log(`Killing other qpq dev process for this repo (pid ${pid}): ${command}`);\n process.kill(pid, 'SIGINT');\n if (!waitForPidGone(pid)) {\n killPidHard(pid);\n }\n }\n};\n\n// Whatever is currently listening on a port, as pid + command line. Empty when\n// the port is free, or when lsof is unavailable.\nconst getPortListeners = (port: number): { pid: number; command: string }[] => {\n let pids: number[] = [];\n try {\n pids = execSync(`lsof -t -iTCP:${port} -sTCP:LISTEN`, {\n stdio: ['ignore', 'pipe', 'ignore'],\n })\n .toString()\n .split('\\n')\n .map((line) => Number(line.trim()))\n .filter(Boolean);\n } catch {\n return []; // lsof exits non-zero when nothing is listening\n }\n\n return pids.flatMap((pid) => {\n try {\n return [{ pid, command: execSync(`ps -p ${pid} -o command=`).toString().trim() }];\n } catch {\n return []; // already gone\n }\n });\n};\n\n/**\n * Say what is holding these ports, without touching it.\n *\n * The other half of --keep-other-dev-servers: sparing a sibling checkout's dev\n * server means this one cannot bind, and an unexplained EADDRINUSE is a worse\n * experience than the silent kill it replaces. Naming the pid and the command\n * turns \"it just died\" into something you can act on.\n *\n * Worded as a prediction, not a fact, because it IS one: this runs before the\n * bundle step, so by the time anything binds - seconds later - the holder may\n * well have gone. Stating \"port in use\" and then starting cleanly reads like\n * the warning was wrong.\n */\nexport const reportPortHolders = (ports: number[]): void => {\n for (const port of ports) {\n for (const { pid, command } of getPortListeners(port)) {\n console.warn(`Port ${port} is currently held by pid ${pid}: ${command}`);\n console.warn(` Starting anyway. If it still holds port ${port} once this server binds, startup will fail with EADDRINUSE.`);\n }\n }\n};\n\nexport const killStaleListeners = (ports: number[], isOurs: (command: string) => boolean): void => {\n for (const port of ports) {\n for (const { pid, command } of getPortListeners(port)) {\n if (isOurs(command)) {\n console.log(`Killing stale dev server on port ${port} (pid ${pid})`);\n process.kill(pid);\n if (!waitForPidGone(pid)) {\n killPidHard(pid);\n }\n } else {\n console.warn(`Port ${port} is in use by an unrelated process (pid ${pid}: ${command}) — not killing it.`);\n }\n }\n }\n};\n"]}
|
package/lib/esm/lib/prompts.d.ts
CHANGED
package/lib/esm/lib/prompts.js
CHANGED
|
@@ -20,7 +20,7 @@ export const promptText = async (message) => {
|
|
|
20
20
|
const { input } = await import('@inquirer/prompts');
|
|
21
21
|
return input({ message });
|
|
22
22
|
};
|
|
23
|
-
// A multi-select checkbox prompt over labelled values.
|
|
23
|
+
// A multi-select checkbox prompt over labelled values; `checked` preselects a choice.
|
|
24
24
|
export const promptCheckbox = async (message, choices) => {
|
|
25
25
|
const { checkbox } = await import('@inquirer/prompts');
|
|
26
26
|
return checkbox({ message, choices });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../../src/lib/prompts.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,wEAAwE;AACxE,sBAAsB;AACtB,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,OAAe,EAAE,OAAiB,EAAE,aAAsB,EAAmB,EAAE;IAChH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;AAC9D,CAAC,CAAC;AAEF,wEAAwE;AACxE,6EAA6E;AAC7E,gEAAgE;AAChE,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,EACvC,OAAe,EACf,OAA2E,EAC/D,EAAE;IACd,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAe,EAAoB,EAAE,CAAC,CAAC,MAAM,YAAY,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC;AAE/H,+EAA+E;AAC/E,2DAA2D;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,OAAe,EAAmB,EAAE;IACnE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../../src/lib/prompts.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,wEAAwE;AACxE,sBAAsB;AACtB,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,OAAe,EAAE,OAAiB,EAAE,aAAsB,EAAmB,EAAE;IAChH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,CAAC,CAAC;AAC9D,CAAC,CAAC;AAEF,wEAAwE;AACxE,6EAA6E;AAC7E,gEAAgE;AAChE,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,EACvC,OAAe,EACf,OAA2E,EAC/D,EAAE;IACd,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACtC,CAAC,CAAC;AAEF,kDAAkD;AAClD,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,OAAe,EAAoB,EAAE,CAAC,CAAC,MAAM,YAAY,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC;AAE/H,+EAA+E;AAC/E,2DAA2D;AAC3D,MAAM,CAAC,MAAM,UAAU,GAAG,KAAK,EAAE,OAAe,EAAmB,EAAE;IACnE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACpD,OAAO,KAAK,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,sFAAsF;AACtF,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,EACjC,OAAe,EACf,OAA8E,EAChE,EAAE;IAChB,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACvD,OAAO,QAAQ,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACxC,CAAC,CAAC","sourcesContent":["// Present a single-choice list prompt and return the selected value.\n// @inquirer/prompts is imported lazily so commands only pay for it when\n// actually prompting.\nexport const promptSelect = async (message: string, choices: string[], defaultChoice?: string): Promise<string> => {\n const { select } = await import('@inquirer/prompts');\n return select({ message, choices, default: defaultChoice });\n};\n\n// A single-choice list prompt over labelled values; a choice's optional\n// `short` replaces its name in the answer line once picked, and its optional\n// `description` renders under the list while it is highlighted.\nexport const promptSelectDetailed = async <T>(\n message: string,\n choices: { name: string; value: T; short?: string; description?: string }[],\n): Promise<T> => {\n const { select } = await import('@inquirer/prompts');\n return select({ message, choices });\n};\n\n// A yes/no list prompt, returning true for \"Yes\".\nexport const promptYesNo = async (message: string): Promise<boolean> => (await promptSelect(message, ['No', 'Yes'])) === 'Yes';\n\n// A free-text prompt — used where a y/n is too easy to fat-finger (destructive\n// confirmations that require typing the target name back).\nexport const promptText = async (message: string): Promise<string> => {\n const { input } = await import('@inquirer/prompts');\n return input({ message });\n};\n\n// A multi-select checkbox prompt over labelled values; `checked` preselects a choice.\nexport const promptCheckbox = async <T>(\n message: string,\n choices: { name: string; value: T; checked?: boolean; description?: string }[],\n): Promise<T[]> => {\n const { checkbox } = await import('@inquirer/prompts');\n return checkbox({ message, choices });\n};\n"]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Nullable } from 'quidproquo-core';
|
|
2
|
+
export type SetupCheckResult = {
|
|
3
|
+
done: boolean;
|
|
4
|
+
detail?: string;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* One item of a platform's setup checklist. `check` says whether it is already in place
|
|
8
|
+
* (null when it cannot tell), `run` makes it so. A check-only step (credentials, zones) has a
|
|
9
|
+
* run that re-checks and throws with guidance, so a failed prerequisite stops the sequence.
|
|
10
|
+
*/
|
|
11
|
+
export type SetupStep = {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
check: () => Promise<Nullable<SetupCheckResult>>;
|
|
15
|
+
run: () => Promise<void>;
|
|
16
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setupStep.js","sourceRoot":"","sources":["../../../src/lib/setupStep.ts"],"names":[],"mappings":"","sourcesContent":["import { Nullable } from 'quidproquo-core';\n\nexport type SetupCheckResult = {\n done: boolean;\n // One line for the menu: what exists, or what is missing and how to fix it.\n detail?: string;\n};\n\n/**\n * One item of a platform's setup checklist. `check` says whether it is already in place\n * (null when it cannot tell), `run` makes it so. A check-only step (credentials, zones) has a\n * run that re-checks and throws with guidance, so a failed prerequisite stops the sequence.\n */\nexport type SetupStep = {\n id: string;\n name: string;\n check: () => Promise<Nullable<SetupCheckResult>>;\n run: () => Promise<void>;\n};\n"]}
|
|
@@ -3,6 +3,7 @@ import { awsGo } from './go';
|
|
|
3
3
|
import { awsGoDocker } from './goDocker';
|
|
4
4
|
import { awsPrimeDeployIdentity } from './identity';
|
|
5
5
|
import { awsPublish, awsPublishBuild, awsPublishDeploy, awsPublishUpload } from './publish';
|
|
6
|
+
import { awsSetupSteps } from './setup';
|
|
6
7
|
import { awsTeardown } from './teardown';
|
|
7
8
|
export const awsPlatformDriver = {
|
|
8
9
|
primeDeployIdentity: awsPrimeDeployIdentity,
|
|
@@ -10,6 +11,7 @@ export const awsPlatformDriver = {
|
|
|
10
11
|
goDocker: awsGoDocker,
|
|
11
12
|
teardown: awsTeardown,
|
|
12
13
|
clearResources: awsClearResources,
|
|
14
|
+
setupSteps: awsSetupSteps,
|
|
13
15
|
publish: awsPublish,
|
|
14
16
|
publishBuild: awsPublishBuild,
|
|
15
17
|
publishUpload: awsPublishUpload,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/platforms/aws/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,mBAAmB,EAAE,sBAAsB;IAE3C,EAAE,EAAE,KAAK;IACT,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,WAAW;IACrB,cAAc,EAAE,iBAAiB;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/platforms/aws/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC5F,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAEzC,MAAM,CAAC,MAAM,iBAAiB,GAAsB;IAClD,mBAAmB,EAAE,sBAAsB;IAE3C,EAAE,EAAE,KAAK;IACT,QAAQ,EAAE,WAAW;IACrB,QAAQ,EAAE,WAAW;IACrB,cAAc,EAAE,iBAAiB;IACjC,UAAU,EAAE,aAAa;IAEzB,OAAO,EAAE,UAAU;IACnB,YAAY,EAAE,eAAe;IAC7B,aAAa,EAAE,gBAAgB;IAC/B,aAAa,EAAE,gBAAgB;CAChC,CAAC","sourcesContent":["import { QpqPlatformDriver } from '../types';\nimport { awsClearResources } from './clearResources';\nimport { awsGo } from './go';\nimport { awsGoDocker } from './goDocker';\nimport { awsPrimeDeployIdentity } from './identity';\nimport { awsPublish, awsPublishBuild, awsPublishDeploy, awsPublishUpload } from './publish';\nimport { awsSetupSteps } from './setup';\nimport { awsTeardown } from './teardown';\n\nexport const awsPlatformDriver: QpqPlatformDriver = {\n primeDeployIdentity: awsPrimeDeployIdentity,\n\n go: awsGo,\n goDocker: awsGoDocker,\n teardown: awsTeardown,\n clearResources: awsClearResources,\n setupSteps: awsSetupSteps,\n\n publish: awsPublish,\n publishBuild: awsPublishBuild,\n publishUpload: awsPublishUpload,\n publishDeploy: awsPublishDeploy,\n};\n"]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { QPQConfig } from 'quidproquo-core';
|
|
2
|
+
/** Everything the AWS setup steps share: the target account/region and the app's configs. */
|
|
3
|
+
export type AwsSetupContext = {
|
|
4
|
+
appName: string;
|
|
5
|
+
environment: string;
|
|
6
|
+
accountId: string;
|
|
7
|
+
region: string;
|
|
8
|
+
accountQpqConfig: QPQConfig;
|
|
9
|
+
bootstrapQpqConfig: QPQConfig;
|
|
10
|
+
serviceQpqConfigs: QPQConfig[];
|
|
11
|
+
};
|
|
12
|
+
export declare const buildAwsSetupContext: (appName: string, environment: string) => AwsSetupContext;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { getQpqAppDeployContext, getWorkspaceAccountQpqConfig, getWorkspaceBootstrapQpqConfig } from 'quidproquo-deploy-awscdk';
|
|
2
|
+
import { getRoot, getServiceNames } from '../../../lib/discovery';
|
|
3
|
+
import { loadServiceQpqConfig } from '../../../lib/qpqConfigs';
|
|
4
|
+
export const buildAwsSetupContext = (appName, environment) => {
|
|
5
|
+
const accountId = process.env.AWS_DEFAULT_ACCOUNT ?? '';
|
|
6
|
+
const region = process.env.AWS_DEFAULT_REGION ?? '';
|
|
7
|
+
const ctx = getQpqAppDeployContext(getRoot(), appName, environment, process.env.ACTOR_NAME, { accountId, region });
|
|
8
|
+
return {
|
|
9
|
+
appName,
|
|
10
|
+
environment,
|
|
11
|
+
accountId,
|
|
12
|
+
region,
|
|
13
|
+
accountQpqConfig: getWorkspaceAccountQpqConfig(ctx),
|
|
14
|
+
bootstrapQpqConfig: getWorkspaceBootstrapQpqConfig(ctx),
|
|
15
|
+
serviceQpqConfigs: getServiceNames(appName).map((service) => loadServiceQpqConfig(appName, service)),
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=awsSetupContext.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"awsSetupContext.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/awsSetupContext.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,sBAAsB,EAAE,4BAA4B,EAAE,8BAA8B,EAAE,MAAM,0BAA0B,CAAC;AAEhI,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAClE,OAAO,EAAE,oBAAoB,EAAE,MAAM,yBAAyB,CAAC;AAa/D,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,OAAe,EAAE,WAAmB,EAAmB,EAAE;IAC5F,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,IAAI,EAAE,CAAC;IACxD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC;IACpD,MAAM,GAAG,GAAG,sBAAsB,CAAC,OAAO,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;IAEnH,OAAO;QACL,OAAO;QACP,WAAW;QACX,SAAS;QACT,MAAM;QACN,gBAAgB,EAAE,4BAA4B,CAAC,GAAG,CAAC;QACnD,kBAAkB,EAAE,8BAA8B,CAAC,GAAG,CAAC;QACvD,iBAAiB,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,oBAAoB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;KACrG,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { QPQConfig } from 'quidproquo-core';\nimport { getQpqAppDeployContext, getWorkspaceAccountQpqConfig, getWorkspaceBootstrapQpqConfig } from 'quidproquo-deploy-awscdk';\n\nimport { getRoot, getServiceNames } from '../../../lib/discovery';\nimport { loadServiceQpqConfig } from '../../../lib/qpqConfigs';\n\n/** Everything the AWS setup steps share: the target account/region and the app's configs. */\nexport type AwsSetupContext = {\n appName: string;\n environment: string;\n accountId: string;\n region: string;\n accountQpqConfig: QPQConfig;\n bootstrapQpqConfig: QPQConfig;\n serviceQpqConfigs: QPQConfig[];\n};\n\nexport const buildAwsSetupContext = (appName: string, environment: string): AwsSetupContext => {\n const accountId = process.env.AWS_DEFAULT_ACCOUNT ?? '';\n const region = process.env.AWS_DEFAULT_REGION ?? '';\n const ctx = getQpqAppDeployContext(getRoot(), appName, environment, process.env.ACTOR_NAME, { accountId, region });\n\n return {\n appName,\n environment,\n accountId,\n region,\n accountQpqConfig: getWorkspaceAccountQpqConfig(ctx),\n bootstrapQpqConfig: getWorkspaceBootstrapQpqConfig(ctx),\n serviceQpqConfigs: getServiceNames(appName).map((service) => loadServiceQpqConfig(appName, service)),\n };\n};\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { SetupStep } from '../../../lib/setupStep';
|
|
2
|
+
/**
|
|
3
|
+
* The AWS checklist, in the order it must run: prerequisites that only check, the CDK
|
|
4
|
+
* toolkit per region, then the app-wide stacks (the account stack creates the GitHub OIDC
|
|
5
|
+
* provider, the bootstrap stack the GitHub deploy role). Services are deployed separately
|
|
6
|
+
* with `qpq go` / `qpq go:docker`, which is where the time goes and where parallelism lives.
|
|
7
|
+
*/
|
|
8
|
+
export declare const awsSetupSteps: (appName: string, environment: string) => Promise<SetupStep[]>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { buildAwsSetupContext } from './awsSetupContext';
|
|
2
|
+
import { buildBucketNamesStep } from './buildBucketNamesStep';
|
|
3
|
+
import { buildCdkBootstrapSteps } from './buildCdkBootstrapSteps';
|
|
4
|
+
import { buildGithubDeployRoleStep } from './buildGithubDeployRoleStep';
|
|
5
|
+
import { buildGithubOidcProviderStep } from './buildGithubOidcProviderStep';
|
|
6
|
+
import { buildHostedZonesStep } from './buildHostedZonesStep';
|
|
7
|
+
import { buildIamCapabilityStep } from './buildIamCapabilityStep';
|
|
8
|
+
import { buildIdentityStep } from './buildIdentityStep';
|
|
9
|
+
import { buildAccountStackStep, buildBootstrapStackStep, buildDomainStackStep } from './buildStackSteps';
|
|
10
|
+
/**
|
|
11
|
+
* The AWS checklist, in the order it must run: prerequisites that only check, the CDK
|
|
12
|
+
* toolkit per region, then the app-wide stacks (the account stack creates the GitHub OIDC
|
|
13
|
+
* provider, the bootstrap stack the GitHub deploy role). Services are deployed separately
|
|
14
|
+
* with `qpq go` / `qpq go:docker`, which is where the time goes and where parallelism lives.
|
|
15
|
+
*/
|
|
16
|
+
export const awsSetupSteps = async (appName, environment) => {
|
|
17
|
+
const ctx = buildAwsSetupContext(appName, environment);
|
|
18
|
+
return [
|
|
19
|
+
buildIdentityStep(ctx),
|
|
20
|
+
buildIamCapabilityStep(ctx),
|
|
21
|
+
buildHostedZonesStep(ctx),
|
|
22
|
+
buildBucketNamesStep(ctx),
|
|
23
|
+
...buildCdkBootstrapSteps(ctx),
|
|
24
|
+
buildAccountStackStep(ctx),
|
|
25
|
+
buildGithubOidcProviderStep(ctx),
|
|
26
|
+
buildDomainStackStep(ctx),
|
|
27
|
+
buildBootstrapStackStep(ctx),
|
|
28
|
+
buildGithubDeployRoleStep(ctx),
|
|
29
|
+
];
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=awsSetupSteps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"awsSetupSteps.js","sourceRoot":"","sources":["../../../../../src/platforms/aws/setup/awsSetupSteps.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,yBAAyB,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAE,2BAA2B,EAAE,MAAM,+BAA+B,CAAC;AAC5E,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,qBAAqB,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAEzG;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,KAAK,EAAE,OAAe,EAAE,WAAmB,EAAwB,EAAE;IAChG,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAEvD,OAAO;QACL,iBAAiB,CAAC,GAAG,CAAC;QACtB,sBAAsB,CAAC,GAAG,CAAC;QAC3B,oBAAoB,CAAC,GAAG,CAAC;QACzB,oBAAoB,CAAC,GAAG,CAAC;QACzB,GAAG,sBAAsB,CAAC,GAAG,CAAC;QAC9B,qBAAqB,CAAC,GAAG,CAAC;QAC1B,2BAA2B,CAAC,GAAG,CAAC;QAChC,oBAAoB,CAAC,GAAG,CAAC;QACzB,uBAAuB,CAAC,GAAG,CAAC;QAC5B,yBAAyB,CAAC,GAAG,CAAC;KAC/B,CAAC;AACJ,CAAC,CAAC","sourcesContent":["import { SetupStep } from '../../../lib/setupStep';\nimport { buildAwsSetupContext } from './awsSetupContext';\nimport { buildBucketNamesStep } from './buildBucketNamesStep';\nimport { buildCdkBootstrapSteps } from './buildCdkBootstrapSteps';\nimport { buildGithubDeployRoleStep } from './buildGithubDeployRoleStep';\nimport { buildGithubOidcProviderStep } from './buildGithubOidcProviderStep';\nimport { buildHostedZonesStep } from './buildHostedZonesStep';\nimport { buildIamCapabilityStep } from './buildIamCapabilityStep';\nimport { buildIdentityStep } from './buildIdentityStep';\nimport { buildAccountStackStep, buildBootstrapStackStep, buildDomainStackStep } from './buildStackSteps';\n\n/**\n * The AWS checklist, in the order it must run: prerequisites that only check, the CDK\n * toolkit per region, then the app-wide stacks (the account stack creates the GitHub OIDC\n * provider, the bootstrap stack the GitHub deploy role). Services are deployed separately\n * with `qpq go` / `qpq go:docker`, which is where the time goes and where parallelism lives.\n */\nexport const awsSetupSteps = async (appName: string, environment: string): Promise<SetupStep[]> => {\n const ctx = buildAwsSetupContext(appName, environment);\n\n return [\n buildIdentityStep(ctx),\n buildIamCapabilityStep(ctx),\n buildHostedZonesStep(ctx),\n buildBucketNamesStep(ctx),\n ...buildCdkBootstrapSteps(ctx),\n buildAccountStackStep(ctx),\n buildGithubOidcProviderStep(ctx),\n buildDomainStackStep(ctx),\n buildBootstrapStackStep(ctx),\n buildGithubDeployRoleStep(ctx),\n ];\n};\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { SetupStep } from '../../../lib/setupStep';
|
|
2
|
+
import { AwsSetupContext } from './awsSetupContext';
|
|
3
|
+
/** Check-only: no bucket the deploy will create is already taken by another AWS account. */
|
|
4
|
+
export declare const buildBucketNamesStep: (ctx: AwsSetupContext) => SetupStep;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { awsNamingUtils } from 'quidproquo-actionprocessor-awslambda';
|
|
2
|
+
import { qpqCoreUtils } from 'quidproquo-core';
|
|
3
|
+
import { qpqWebServerUtils } from 'quidproquo-webserver';
|
|
4
|
+
import { HeadBucketCommand, S3Client } from '@aws-sdk/client-s3';
|
|
5
|
+
// Every bucket a service deploy creates, named the way the constructs name them.
|
|
6
|
+
const getServiceBucketNames = (qpqConfig) => [
|
|
7
|
+
...qpqCoreUtils
|
|
8
|
+
.getOwnedItems(qpqCoreUtils.getStorageDrives(qpqConfig), qpqConfig)
|
|
9
|
+
.map((drive) => awsNamingUtils.getConfigRuntimeResourceNameFromConfig(drive.storageDrive, qpqConfig)),
|
|
10
|
+
...qpqWebServerUtils
|
|
11
|
+
.getWebEntryConfigs(qpqConfig)
|
|
12
|
+
.filter((webEntry) => !webEntry.storageDrive.sourceStorageDrive)
|
|
13
|
+
.map((webEntry) => awsNamingUtils.getQpqRuntimeResourceNameFromConfig(webEntry.name, qpqConfig, 'we')),
|
|
14
|
+
];
|
|
15
|
+
// HeadBucket: 404 means free, 200 ours, 403 exists in another account.
|
|
16
|
+
const getBucketOwner = async (client, bucketName) => {
|
|
17
|
+
try {
|
|
18
|
+
await client.send(new HeadBucketCommand({ Bucket: bucketName }));
|
|
19
|
+
return 'ours';
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
const status = error.$metadata?.httpStatusCode;
|
|
23
|
+
if (status === 404) {
|
|
24
|
+
return 'free';
|
|
25
|
+
}
|
|
26
|
+
if (status === 403 || status === 301) {
|
|
27
|
+
return 'other';
|
|
28
|
+
}
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
// S3 bucket names are global and the framework names them from app, service and
|
|
33
|
+
// environment alone, so a second account cannot reuse a name the first still holds.
|
|
34
|
+
const checkBucketNames = async (ctx) => {
|
|
35
|
+
const names = [...new Set(ctx.serviceQpqConfigs.flatMap(getServiceBucketNames))];
|
|
36
|
+
const client = new S3Client({ region: ctx.region });
|
|
37
|
+
const taken = [];
|
|
38
|
+
let ours = 0;
|
|
39
|
+
for (const name of names) {
|
|
40
|
+
const owner = await getBucketOwner(client, name);
|
|
41
|
+
if (owner === 'other')
|
|
42
|
+
taken.push(name);
|
|
43
|
+
if (owner === 'ours')
|
|
44
|
+
ours += 1;
|
|
45
|
+
}
|
|
46
|
+
return taken.length === 0
|
|
47
|
+
? { done: true, detail: `${names.length} bucket name(s) available or already ours (${ours} existing)` }
|
|
48
|
+
: { done: false, detail: `held by another account: ${taken.join(', ')}; delete them there or change the app prefix` };
|
|
49
|
+
};
|
|
50
|
+
/** Check-only: no bucket the deploy will create is already taken by another AWS account. */
|
|
51
|
+
export const buildBucketNamesStep = (ctx) => ({
|
|
52
|
+
id: 'bucket-names',
|
|
53
|
+
name: 'S3 bucket names available',
|
|
54
|
+
check: () => checkBucketNames(ctx),
|
|
55
|
+
run: async () => {
|
|
56
|
+
const result = await checkBucketNames(ctx);
|
|
57
|
+
if (!result.done) {
|
|
58
|
+
throw new Error(result.detail ?? 'bucket names taken');
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
//# sourceMappingURL=buildBucketNamesStep.js.map
|
|
@@ -0,0 +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 'quidproquo-actionprocessor-awslambda';\nimport { QPQConfig, qpqCoreUtils } from 'quidproquo-core';\nimport { qpqWebServerUtils } from 'quidproquo-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"]}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { SetupStep } from '../../../lib/setupStep';
|
|
2
|
+
import { AwsSetupContext } from './awsSetupContext';
|
|
3
|
+
/** Every region the app deploys into: the deploy region plus each certificate region (us-east-1 for CloudFront). */
|
|
4
|
+
export declare const getSetupRegions: (ctx: AwsSetupContext) => string[];
|
|
5
|
+
export declare const buildCdkBootstrapSteps: (ctx: AwsSetupContext) => SetupStep[];
|