quidproquo-cli 0.1.6 → 0.1.8
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/docker/dev-server/Dockerfile +7 -9
- package/lib/commonjs/cli/commandMenu.d.ts +2 -0
- package/lib/commonjs/cli/commandMenu.js +81 -0
- package/lib/commonjs/cli/commandMenu.js.map +1 -0
- package/lib/commonjs/cli/commandRegistry.d.ts +10 -0
- package/lib/commonjs/cli/commandRegistry.js +131 -0
- package/lib/commonjs/cli/commandRegistry.js.map +1 -0
- package/lib/commonjs/cli/runCli.js +49 -49
- package/lib/commonjs/cli/runCli.js.map +1 -1
- package/lib/commonjs/commands/clearResources.d.ts +1 -0
- package/lib/commonjs/commands/clearResources.js +36 -0
- package/lib/commonjs/commands/clearResources.js.map +1 -0
- package/lib/commonjs/commands/go.js +3 -1
- package/lib/commonjs/commands/go.js.map +1 -1
- package/lib/commonjs/commands/goDev.js +23 -122
- package/lib/commonjs/commands/goDev.js.map +1 -1
- package/lib/commonjs/commands/goDevApi.d.ts +1 -0
- package/lib/commonjs/commands/goDevApi.js +110 -0
- package/lib/commonjs/commands/goDevApi.js.map +1 -0
- package/lib/commonjs/commands/goDevWeb.d.ts +4 -1
- package/lib/commonjs/commands/goDevWeb.js +4 -2
- package/lib/commonjs/commands/goDevWeb.js.map +1 -1
- package/lib/commonjs/commands/goDocker.js +3 -1
- package/lib/commonjs/commands/goDocker.js.map +1 -1
- package/lib/commonjs/commands/hooks.js +23 -5
- package/lib/commonjs/commands/hooks.js.map +1 -1
- package/lib/commonjs/lib/clearResourcesPlan.d.ts +15 -0
- package/lib/commonjs/lib/clearResourcesPlan.js +80 -0
- package/lib/commonjs/lib/clearResourcesPlan.js.map +1 -0
- package/lib/commonjs/lib/deployPrompts.d.ts +1 -0
- package/lib/commonjs/lib/deployPrompts.js +40 -2
- package/lib/commonjs/lib/deployPrompts.js.map +1 -1
- package/lib/commonjs/lib/devServerEntry.js +3 -0
- package/lib/commonjs/lib/devServerEntry.js.map +1 -1
- package/lib/commonjs/lib/hooks.d.ts +2 -1
- package/lib/commonjs/lib/hooks.js +100 -5
- package/lib/commonjs/lib/hooks.js.map +1 -1
- package/lib/commonjs/lib/killStaleListeners.d.ts +3 -0
- package/lib/commonjs/lib/killStaleListeners.js +170 -0
- package/lib/commonjs/lib/killStaleListeners.js.map +1 -0
- package/lib/commonjs/lib/prompts.d.ts +10 -0
- package/lib/commonjs/lib/prompts.js +15 -1
- package/lib/commonjs/lib/prompts.js.map +1 -1
- package/lib/commonjs/lib/qpqConfigs.d.ts +1 -0
- package/lib/commonjs/lib/qpqConfigs.js +9 -1
- package/lib/commonjs/lib/qpqConfigs.js.map +1 -1
- package/lib/commonjs/lib/resolveAppSelection.js +1 -1
- package/lib/commonjs/lib/resolveAppSelection.js.map +1 -1
- package/lib/commonjs/lib/runTasks.d.ts +5 -0
- package/lib/commonjs/lib/runTasks.js +82 -1
- package/lib/commonjs/lib/runTasks.js.map +1 -1
- package/lib/commonjs/lib/typeWatcher.d.ts +2 -0
- package/lib/commonjs/lib/typeWatcher.js +59 -0
- package/lib/commonjs/lib/typeWatcher.js.map +1 -0
- package/lib/commonjs/platforms/aws/cdkApp.js +5 -1
- package/lib/commonjs/platforms/aws/cdkApp.js.map +1 -1
- package/lib/commonjs/platforms/aws/clearResources.d.ts +2 -0
- package/lib/commonjs/platforms/aws/clearResources.js +167 -0
- package/lib/commonjs/platforms/aws/clearResources.js.map +1 -0
- package/lib/commonjs/platforms/aws/go.js +15 -1
- package/lib/commonjs/platforms/aws/go.js.map +1 -1
- package/lib/commonjs/platforms/aws/goDocker.js +76 -8
- package/lib/commonjs/platforms/aws/goDocker.js.map +1 -1
- package/lib/commonjs/platforms/aws/index.js +2 -0
- package/lib/commonjs/platforms/aws/index.js.map +1 -1
- package/lib/commonjs/platforms/docker/go.js +2 -2
- package/lib/commonjs/platforms/docker/go.js.map +1 -1
- package/lib/commonjs/platforms/types.d.ts +2 -0
- package/lib/commonjs/platforms/types.js.map +1 -1
- package/lib/esm/cli/commandMenu.d.ts +2 -0
- package/lib/esm/cli/commandMenu.js +67 -0
- package/lib/esm/cli/commandMenu.js.map +1 -0
- package/lib/esm/cli/commandRegistry.d.ts +10 -0
- package/lib/esm/cli/commandRegistry.js +115 -0
- package/lib/esm/cli/commandRegistry.js.map +1 -0
- package/lib/esm/cli/runCli.js +49 -49
- package/lib/esm/cli/runCli.js.map +1 -1
- package/lib/esm/commands/clearResources.d.ts +1 -0
- package/lib/esm/commands/clearResources.js +23 -0
- package/lib/esm/commands/clearResources.js.map +1 -0
- package/lib/esm/commands/go.js +3 -2
- package/lib/esm/commands/go.js.map +1 -1
- package/lib/esm/commands/goDev.js +24 -120
- package/lib/esm/commands/goDev.js.map +1 -1
- package/lib/esm/commands/goDevApi.d.ts +1 -0
- package/lib/esm/commands/goDevApi.js +94 -0
- package/lib/esm/commands/goDevApi.js.map +1 -0
- package/lib/esm/commands/goDevWeb.d.ts +4 -1
- package/lib/esm/commands/goDevWeb.js +4 -2
- package/lib/esm/commands/goDevWeb.js.map +1 -1
- package/lib/esm/commands/goDocker.js +3 -2
- package/lib/esm/commands/goDocker.js.map +1 -1
- package/lib/esm/commands/hooks.js +21 -6
- package/lib/esm/commands/hooks.js.map +1 -1
- package/lib/esm/lib/clearResourcesPlan.d.ts +15 -0
- package/lib/esm/lib/clearResourcesPlan.js +66 -0
- package/lib/esm/lib/clearResourcesPlan.js.map +1 -0
- package/lib/esm/lib/deployPrompts.d.ts +1 -0
- package/lib/esm/lib/deployPrompts.js +38 -1
- package/lib/esm/lib/deployPrompts.js.map +1 -1
- package/lib/esm/lib/devServerEntry.js +3 -0
- package/lib/esm/lib/devServerEntry.js.map +1 -1
- package/lib/esm/lib/hooks.d.ts +2 -1
- package/lib/esm/lib/hooks.js +97 -5
- package/lib/esm/lib/hooks.js.map +1 -1
- package/lib/esm/lib/killStaleListeners.d.ts +3 -0
- package/lib/esm/lib/killStaleListeners.js +161 -0
- package/lib/esm/lib/killStaleListeners.js.map +1 -0
- package/lib/esm/lib/prompts.d.ts +10 -0
- package/lib/esm/lib/prompts.js +12 -0
- package/lib/esm/lib/prompts.js.map +1 -1
- package/lib/esm/lib/qpqConfigs.d.ts +1 -0
- package/lib/esm/lib/qpqConfigs.js +7 -0
- package/lib/esm/lib/qpqConfigs.js.map +1 -1
- package/lib/esm/lib/resolveAppSelection.js +1 -1
- package/lib/esm/lib/resolveAppSelection.js.map +1 -1
- package/lib/esm/lib/runTasks.d.ts +5 -0
- package/lib/esm/lib/runTasks.js +80 -0
- package/lib/esm/lib/runTasks.js.map +1 -1
- package/lib/esm/lib/typeWatcher.d.ts +2 -0
- package/lib/esm/lib/typeWatcher.js +51 -0
- package/lib/esm/lib/typeWatcher.js.map +1 -0
- package/lib/esm/platforms/aws/cdkApp.js +5 -1
- package/lib/esm/platforms/aws/cdkApp.js.map +1 -1
- package/lib/esm/platforms/aws/clearResources.d.ts +2 -0
- package/lib/esm/platforms/aws/clearResources.js +151 -0
- package/lib/esm/platforms/aws/clearResources.js.map +1 -0
- package/lib/esm/platforms/aws/go.js +16 -2
- package/lib/esm/platforms/aws/go.js.map +1 -1
- package/lib/esm/platforms/aws/goDocker.js +77 -9
- package/lib/esm/platforms/aws/goDocker.js.map +1 -1
- package/lib/esm/platforms/aws/index.js +2 -0
- package/lib/esm/platforms/aws/index.js.map +1 -1
- package/lib/esm/platforms/docker/go.js +2 -2
- package/lib/esm/platforms/docker/go.js.map +1 -1
- package/lib/esm/platforms/types.d.ts +2 -0
- package/lib/esm/platforms/types.js.map +1 -1
- package/package.json +12 -10
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// `qpq go:dev:api` — boots the QPQ local dev server. Resolves the app,
|
|
2
|
+
// generates the dev-server entry, builds the rspack config, then runs rspack
|
|
3
|
+
// in watch mode, restarting the server process on every successful rebuild.
|
|
4
|
+
//
|
|
5
|
+
// The one thing NOT hot-reloaded is the qpq configs themselves: each service's
|
|
6
|
+
// infrastructure.ts is evaluated once at launch and baked into the
|
|
7
|
+
// `quidproquo-dynamic-loader` virtual module — config changes need a full
|
|
8
|
+
// `go:dev:api` restart.
|
|
9
|
+
import { getAppServiceQpqConfigs, getDevServerRspackConfig } from 'quidproquo-deploy-rspack';
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { rspack } from '@rspack/core';
|
|
13
|
+
import { primeDeployEnvFromConfig } from '../lib/deployEnv';
|
|
14
|
+
import { writeDevServerEntry } from '../lib/devServerEntry';
|
|
15
|
+
import { getRoot } from '../lib/discovery';
|
|
16
|
+
import { killOtherQpqDevProcesses, killStaleListeners } from '../lib/killStaleListeners';
|
|
17
|
+
import { resolveAppSelection } from '../lib/resolveAppSelection';
|
|
18
|
+
// 8080/8888 are set in the generated entry; 3001 is the quidproquo-dev-server
|
|
19
|
+
// file-storage (secure URL) default port.
|
|
20
|
+
const DEV_SERVER_PORTS = [8080, 8888, 3001];
|
|
21
|
+
const DEV_SERVER_BUNDLE_PATH = path.join('dist', 'qpq', 'dev-server', 'main.js');
|
|
22
|
+
export const goDevApiCommand = async (argv) => {
|
|
23
|
+
const root = getRoot();
|
|
24
|
+
const appName = await resolveAppSelection({ argv, envVar: 'QPQ_DEV_APP' });
|
|
25
|
+
process.env.QPQ_DEV_APP = appName;
|
|
26
|
+
primeDeployEnvFromConfig(appName);
|
|
27
|
+
console.log(`Dev server for app [${appName}]`);
|
|
28
|
+
// Catches a lingering watcher from a previous run even if its spawned
|
|
29
|
+
// child already exited (see killOtherQpqDevProcesses) — then the usual
|
|
30
|
+
// port-based sweep for anything else still bound to our ports.
|
|
31
|
+
killOtherQpqDevProcesses(root);
|
|
32
|
+
killStaleListeners(DEV_SERVER_PORTS, (command) => command.includes(DEV_SERVER_BUNDLE_PATH));
|
|
33
|
+
const qpqConfigs = getAppServiceQpqConfigs(root, appName);
|
|
34
|
+
const entry = writeDevServerEntry(root, appName);
|
|
35
|
+
const rspackConfig = getDevServerRspackConfig({ root, entry, qpqConfigs });
|
|
36
|
+
const bundlePath = path.join(root, DEV_SERVER_BUNDLE_PATH);
|
|
37
|
+
const env = { ...process.env, QPQ_DEV_APP: appName };
|
|
38
|
+
// The long-lived server process (HTTP 8080 / WS 8888), restarted per rebuild.
|
|
39
|
+
let child = null;
|
|
40
|
+
let restartQueued = false;
|
|
41
|
+
const startServer = () => {
|
|
42
|
+
child = spawn('node', [bundlePath], { stdio: 'inherit', env });
|
|
43
|
+
child.on('exit', (code) => {
|
|
44
|
+
child = null;
|
|
45
|
+
if (!restartQueued) {
|
|
46
|
+
// Crashed (or exited) on its own — leave it down; the next successful
|
|
47
|
+
// rebuild (i.e. the next file save) brings it back up.
|
|
48
|
+
console.log(`Dev server exited with code ${code}. Save a file to restart it.`);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
const restartServer = () => {
|
|
53
|
+
if (restartQueued)
|
|
54
|
+
return;
|
|
55
|
+
if (!child) {
|
|
56
|
+
startServer();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
restartQueued = true;
|
|
60
|
+
child.once('exit', () => {
|
|
61
|
+
restartQueued = false;
|
|
62
|
+
startServer();
|
|
63
|
+
});
|
|
64
|
+
child.kill();
|
|
65
|
+
};
|
|
66
|
+
console.log('Bundling dev server (watch mode)...');
|
|
67
|
+
let lastHash;
|
|
68
|
+
const compiler = rspack(rspackConfig);
|
|
69
|
+
compiler.watch({ aggregateTimeout: 200 }, (err, stats) => {
|
|
70
|
+
if (err) {
|
|
71
|
+
console.error(err);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (stats?.hasErrors()) {
|
|
75
|
+
console.error(stats.toString({ colors: true, chunks: false, modules: false }));
|
|
76
|
+
console.log('Build failed — dev server not restarted. Fix the error and save again.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Skip no-op rebuilds (e.g. the virtual-module-triggered second compile at
|
|
80
|
+
// startup): only restart when the output actually changed.
|
|
81
|
+
if (stats?.hash === lastHash && child) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
lastHash = stats?.hash;
|
|
85
|
+
console.log(child ? 'Rebuilt. Restarting dev server...' : 'Dev server bundled. Starting...');
|
|
86
|
+
restartServer();
|
|
87
|
+
});
|
|
88
|
+
process.on('SIGINT', () => {
|
|
89
|
+
restartQueued = true; // suppress the crash log / rebuild-restart on our own kill
|
|
90
|
+
child?.kill();
|
|
91
|
+
compiler.close(() => process.exit(0));
|
|
92
|
+
});
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=goDevApi.js.map
|
|
@@ -0,0 +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// 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 { 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 child.kill();\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 process.on('SIGINT', () => {\n restartQueued = true; // suppress the crash log / rebuild-restart on our own kill\n child?.kill();\n compiler.close(() => process.exit(0));\n });\n};\n"]}
|
|
@@ -15,8 +15,9 @@ import { RspackDevServer } from '@rspack/dev-server';
|
|
|
15
15
|
import { getArgValue } from '../lib/args';
|
|
16
16
|
import { primeDeployEnvFromConfig } from '../lib/deployEnv';
|
|
17
17
|
import { getRoot } from '../lib/discovery';
|
|
18
|
+
import { isQpqCliCommand, killStaleListeners } from '../lib/killStaleListeners';
|
|
18
19
|
import { resolveAppSelection } from '../lib/resolveAppSelection';
|
|
19
|
-
export const goDevWebCommand = async (argv) => {
|
|
20
|
+
export const goDevWebCommand = async (argv, options = {}) => {
|
|
20
21
|
const root = getRoot();
|
|
21
22
|
const appName = await resolveAppSelection({ argv, envVar: 'QPQ_DEV_APP' });
|
|
22
23
|
process.env.LOCAL_DEV_SERVER = 'true';
|
|
@@ -32,6 +33,7 @@ export const goDevWebCommand = async (argv) => {
|
|
|
32
33
|
console.error('No views projects found.');
|
|
33
34
|
process.exit(1);
|
|
34
35
|
}
|
|
36
|
+
killStaleListeners(views.map((v) => v.port), isQpqCliCommand);
|
|
35
37
|
// Every RspackDevServer.start() registers its own SIGINT/SIGTERM handler on
|
|
36
38
|
// process — with a dozen views servers in one process that trips node's
|
|
37
39
|
// default 10-listener warning. Size the limit to the fleet.
|
|
@@ -60,7 +62,7 @@ export const goDevWebCommand = async (argv) => {
|
|
|
60
62
|
};
|
|
61
63
|
const markStarted = (index) => {
|
|
62
64
|
const v = views[index];
|
|
63
|
-
if (!process.stdout.isTTY) {
|
|
65
|
+
if (!process.stdout.isTTY || options.plainStatusLines) {
|
|
64
66
|
logBelowList(` ${v.service} started`);
|
|
65
67
|
return;
|
|
66
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"goDevWeb.js","sourceRoot":"","sources":["../../../src/commands/goDevWeb.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,8EAA8E;AAC9E,mEAAmE;AACnE,mEAAmE;AACnE,+CAA+C;AAC/C,EAAE;AACF,SAAS;AACT,mBAAmB;AACnB,uCAAuC;AACvC,gCAAgC;AAChC,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"goDevWeb.js","sourceRoot":"","sources":["../../../src/commands/goDevWeb.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,8EAA8E;AAC9E,mEAAmE;AACnE,mEAAmE;AACnE,+CAA+C;AAC/C,EAAE;AACF,SAAS;AACT,mBAAmB;AACnB,uCAAuC;AACvC,gCAAgC;AAChC,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAE7E,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAErD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AASjE,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,IAAc,EAAE,UAA2B,EAAE,EAAiB,EAAE;IACpG,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC,CAAC;IAE3E,OAAO,CAAC,GAAG,CAAC,gBAAgB,GAAG,MAAM,CAAC;IACtC,OAAO,CAAC,GAAG,CAAC,QAAQ,GAAG,aAAa,CAAC;IACrC,wBAAwB,CAAC,OAAO,CAAC,CAAC;IAElC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC;QACtC,EAAE,KAAK,CAAC,GAAG,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;IAExB,IAAI,KAAK,GAAG,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACvC,IAAI,IAAI;QAAE,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAChE,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,OAAO,CAAC,KAAK,CAAC,0BAA0B,CAAC,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,kBAAkB,CAChB,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EACxB,eAAe,CAChB,CAAC;IAEF,4EAA4E;IAC5E,wEAAwE;IACxE,4DAA4D;IAC5D,OAAO,CAAC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,eAAe,EAAE,EAAE,KAAK,CAAC,MAAM,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAEpF,6EAA6E;IAC7E,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAE3E,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,CAAC,MAAM,2BAA2B,OAAO,IAAI,CAAC,CAAC;IAC5E,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,EAAE,CAAC,CAAC;IAE5G,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,IAAI,YAAY,GAAG,KAAK,CAAC;IAEzB,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAQ,EAAE;QACtC,IAAI,YAAY;YAAE,OAAO;QACzB,YAAY,GAAG,IAAI,CAAC;QACpB,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5F,CAAC,CAAC;IAEF,yEAAyE;IACzE,wEAAwE;IACxE,0EAA0E;IAC1E,qEAAqE;IACrE,IAAI,cAAc,GAAG,CAAC,CAAC;IAEvB,MAAM,YAAY,GAAG,CAAC,IAAY,EAAQ,EAAE;QAC1C,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,cAAc,IAAI,CAAC,CAAC;IACtB,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,KAAa,EAAQ,EAAE;QAC1C,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC;QACvB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;YACtD,YAAY,CAAC,KAAK,CAAC,CAAC,OAAO,UAAU,CAAC,CAAC;YACvC,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,sBAAsB,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,kBAAkB,CAAC,4BAA4B,CAAC;QAC/G,MAAM,EAAE,GAAG,cAAc,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,WAAW,IAAI,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC/D,CAAC,CAAC;IAEF,MAAM,gBAAgB,GAAG,CAAC,KAAa,EAAQ,EAAE;QAC/C,MAAM,OAAO,GAAG,GAAS,EAAE;YACzB,IAAI,YAAY;gBAAE,OAAO;YACzB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAClB;gBACE,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI;gBACvB,IAAI,EAAE,GAAG;gBACT,OAAO,EAAE,IAAI;aACd,EACD,CAAC,GAAG,EAAE,EAAE;gBACN,GAAG,CAAC,MAAM,EAAE,CAAC;gBACb,WAAW,CAAC,KAAK,CAAC,CAAC;YACrB,CAAC,CACF,CAAC;YACF,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;YAChD,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;QACzC,CAAC,CAAC;QACF,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IAEF,KAAK,MAAM,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC;QAC5C,MAAM,MAAM,GAAG,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,MAAM,MAAM,GAAG,IAAI,eAAe,CAAC,MAAM,CAAC,SAAS,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;QACtF,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAErB,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,YAAY,CAAC,MAAM,IAAI,CAAC,OAAO,sBAAuB,CAAW,CAAC,OAAO,qCAAqC,CAAC,CAAC;YAChH,QAAQ,CAAC,CAAC,CAAC,CAAC;YACZ,OAAO;QACT,CAAC;QAED,gBAAgB,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACxC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3C,CAAC,CAAC","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, so fall back to plain lines.\n plainStatusLines?: 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 || options.plainStatusLines) {\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 pollUntilStarted(index);\n }\n\n process.on('SIGINT', () => shutdown(0));\n process.on('SIGTERM', () => shutdown(0));\n};\n"]}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// platform driver's parallel containerized deploy (drivers without a docker
|
|
3
3
|
// strategy don't offer one).
|
|
4
4
|
import { resolveDeployEnvironment } from '../lib/deployEnv';
|
|
5
|
-
import { promptDeployPlan } from '../lib/deployPrompts';
|
|
5
|
+
import { buildDeployPlanFromArgs, promptDeployPlan } from '../lib/deployPrompts';
|
|
6
6
|
import { resolveAppSelection } from '../lib/resolveAppSelection';
|
|
7
7
|
import { getPlatformDriver } from '../platforms';
|
|
8
8
|
export const goDockerCommand = async (argv) => {
|
|
@@ -13,7 +13,8 @@ export const goDockerCommand = async (argv) => {
|
|
|
13
13
|
console.error(`Platform '${platform}' has no dockerized deploy — use qpq go instead.`);
|
|
14
14
|
process.exit(1);
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
// Positional args (`qpq go:docker all all`) run prompt-free; otherwise ask.
|
|
17
|
+
const plan = buildDeployPlanFromArgs(appName, argv) ?? (await promptDeployPlan(appName));
|
|
17
18
|
await driver.goDocker(appName, plan);
|
|
18
19
|
};
|
|
19
20
|
//# sourceMappingURL=goDocker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"goDocker.js","sourceRoot":"","sources":["../../../src/commands/goDocker.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,4EAA4E;AAC5E,6BAA6B;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"goDocker.js","sourceRoot":"","sources":["../../../src/commands/goDocker.ts"],"names":[],"mappings":"AAAA,uEAAuE;AACvE,4EAA4E;AAC5E,6BAA6B;AAC7B,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,EAAE,IAAc,EAAiB,EAAE;IACrE,MAAM,OAAO,GAAG,MAAM,mBAAmB,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC,CAAC;IAC/E,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,wBAAwB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACnE,MAAM,MAAM,GAAG,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAE3C,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrB,OAAO,CAAC,KAAK,CAAC,aAAa,QAAQ,kDAAkD,CAAC,CAAC;QACvF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,4EAA4E;IAC5E,MAAM,IAAI,GAAG,uBAAuB,CAAC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;IAEzF,MAAM,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;AACvC,CAAC,CAAC","sourcesContent":["// `qpq go:docker` — asks the same questions as `qpq go`, then runs the\n// platform driver's parallel containerized deploy (drivers without a docker\n// strategy don't offer one).\nimport { resolveDeployEnvironment } from '../lib/deployEnv';\nimport { buildDeployPlanFromArgs, promptDeployPlan } from '../lib/deployPrompts';\nimport { resolveAppSelection } from '../lib/resolveAppSelection';\nimport { getPlatformDriver } from '../platforms';\n\nexport const goDockerCommand = async (argv: string[]): Promise<void> => {\n const appName = await resolveAppSelection({ argv, envVar: 'DEPLOY_APP_NAME' });\n const { platform } = await resolveDeployEnvironment(argv, appName);\n const driver = getPlatformDriver(platform);\n\n if (!driver.goDocker) {\n console.error(`Platform '${platform}' has no dockerized deploy — use qpq go instead.`);\n process.exit(1);\n }\n\n // Positional args (`qpq go:docker all all`) run prompt-free; otherwise ask.\n const plan = buildDeployPlanFromArgs(appName, argv) ?? (await promptDeployPlan(appName));\n\n await driver.goDocker(appName, plan);\n};\n"]}
|
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
// `qpq hooks <name>` — run the `qpq:<name>` script
|
|
2
|
-
// one
|
|
3
|
-
//
|
|
4
|
-
|
|
1
|
+
// `qpq hooks <name>` — run the `qpq:<name>` script (or the plain `<name>`
|
|
2
|
+
// script when no qpq:-prefixed one exists) of every app and workspace package
|
|
3
|
+
// that defines one, in dependency order with parallel execution.
|
|
4
|
+
// Wire the root package.json's lifecycle to it, e.g.
|
|
5
|
+
// "postinstall": "qpq hooks postinstall". Use --jobs=1 to force serial runs.
|
|
6
|
+
import os from 'os';
|
|
7
|
+
import { getArgValue } from '../lib/args';
|
|
8
|
+
import { runHookForAllPackages } from '../lib/hooks';
|
|
9
|
+
import { assertNoFailures } from '../lib/runTasks';
|
|
10
|
+
// Leave a core for the rest of the machine, capped so a wide dependency level
|
|
11
|
+
// doesn't stack up too many concurrent npm processes.
|
|
12
|
+
const getJobs = (argv) => {
|
|
13
|
+
const parsed = Number(getArgValue(argv, '--jobs'));
|
|
14
|
+
if (Number.isFinite(parsed) && parsed >= 1) {
|
|
15
|
+
return Math.floor(parsed);
|
|
16
|
+
}
|
|
17
|
+
return Math.max(1, Math.min(8, os.availableParallelism() - 1));
|
|
18
|
+
};
|
|
5
19
|
export const hooksCommand = async (argv) => {
|
|
6
20
|
const [hook] = argv.filter((a) => !a.startsWith('--'));
|
|
7
21
|
if (!hook) {
|
|
8
|
-
console.error('Usage: qpq hooks <name> (runs qpq:<name
|
|
22
|
+
console.error('Usage: qpq hooks <name> [--jobs=N] (runs qpq:<name>, or plain <name>, in every app/package that defines it)');
|
|
9
23
|
process.exit(1);
|
|
10
24
|
}
|
|
11
|
-
await
|
|
25
|
+
const failures = await runHookForAllPackages(hook, getJobs(argv));
|
|
26
|
+
assertNoFailures(`hooks ${hook}`, failures);
|
|
12
27
|
};
|
|
13
28
|
//# sourceMappingURL=hooks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/commands/hooks.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/commands/hooks.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,8EAA8E;AAC9E,iEAAiE;AACjE,qDAAqD;AACrD,6EAA6E;AAC7E,OAAO,EAAE,MAAM,IAAI,CAAC;AAEpB,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEnD,8EAA8E;AAC9E,sDAAsD;AACtD,MAAM,OAAO,GAAG,CAAC,IAAc,EAAU,EAAE;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IACnD,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,EAAE,CAAC;QAC3C,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,oBAAoB,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,IAAc,EAAiB,EAAE;IAClE,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAEvD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,CAAC,KAAK,CAAC,+GAA+G,CAAC,CAAC;QAC/H,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAClE,gBAAgB,CAAC,SAAS,IAAI,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC9C,CAAC,CAAC","sourcesContent":["// `qpq hooks <name>` — run the `qpq:<name>` script (or the plain `<name>`\n// script when no qpq:-prefixed one exists) of every app and workspace package\n// that defines one, in dependency order with parallel execution.\n// Wire the root package.json's lifecycle to it, e.g.\n// \"postinstall\": \"qpq hooks postinstall\". Use --jobs=1 to force serial runs.\nimport os from 'os';\n\nimport { getArgValue } from '../lib/args';\nimport { runHookForAllPackages } from '../lib/hooks';\nimport { assertNoFailures } from '../lib/runTasks';\n\n// Leave a core for the rest of the machine, capped so a wide dependency level\n// doesn't stack up too many concurrent npm processes.\nconst getJobs = (argv: string[]): number => {\n const parsed = Number(getArgValue(argv, '--jobs'));\n if (Number.isFinite(parsed) && parsed >= 1) {\n return Math.floor(parsed);\n }\n return Math.max(1, Math.min(8, os.availableParallelism() - 1));\n};\n\nexport const hooksCommand = async (argv: string[]): Promise<void> => {\n const [hook] = argv.filter((a) => !a.startsWith('--'));\n\n if (!hook) {\n console.error('Usage: qpq hooks <name> [--jobs=N] (runs qpq:<name>, or plain <name>, in every app/package that defines it)');\n process.exit(1);\n }\n\n const failures = await runHookForAllPackages(hook, getJobs(argv));\n assertNoFailures(`hooks ${hook}`, failures);\n};\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type ClearableResourceKind = 'storageDrive' | 'keyValueStore';
|
|
2
|
+
export type ClearableResource = {
|
|
3
|
+
service: string;
|
|
4
|
+
kind: ClearableResourceKind;
|
|
5
|
+
resourceName: string;
|
|
6
|
+
};
|
|
7
|
+
export type ClearResourcesPlan = {
|
|
8
|
+
kind: 'cancelled';
|
|
9
|
+
} | {
|
|
10
|
+
kind: 'resources';
|
|
11
|
+
appName: string;
|
|
12
|
+
resources: ClearableResource[];
|
|
13
|
+
};
|
|
14
|
+
export declare const getClearableResources: (appName: string) => ClearableResource[];
|
|
15
|
+
export declare const promptClearResourcesPlan: (appName: string) => Promise<ClearResourcesPlan>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// The question flow for `qpq clear-resources`: enumerate every service's OWNED
|
|
2
|
+
// data resources (storage drives -> buckets, key value stores -> tables) from
|
|
3
|
+
// its live qpq config, multi-select which to empty, then type the deploy
|
|
4
|
+
// target back to confirm - emptying deletes stored data, so a y/n prompt
|
|
5
|
+
// isn't enough friction. Owned-only so a cross-module ref (e.g. a membership
|
|
6
|
+
// table every service declares against one owner) appears exactly once, on
|
|
7
|
+
// the service that owns it.
|
|
8
|
+
import { qpqCoreUtils } from 'quidproquo-core';
|
|
9
|
+
import { getServiceNames } from './discovery';
|
|
10
|
+
import { promptCheckbox, promptSelect, promptText } from './prompts';
|
|
11
|
+
import { loadServiceQpqConfig } from './qpqConfigs';
|
|
12
|
+
// Every owned data resource across the app's services, in service order.
|
|
13
|
+
export const getClearableResources = (appName) => {
|
|
14
|
+
const resources = [];
|
|
15
|
+
for (const service of getServiceNames(appName)) {
|
|
16
|
+
const qpqConfig = loadServiceQpqConfig(appName, service);
|
|
17
|
+
for (const drive of qpqCoreUtils.getOwnedStorageDrives(qpqConfig)) {
|
|
18
|
+
resources.push({ service, kind: 'storageDrive', resourceName: drive.storageDrive });
|
|
19
|
+
}
|
|
20
|
+
for (const store of qpqCoreUtils.getOwnedKeyValueStores(qpqConfig)) {
|
|
21
|
+
resources.push({ service, kind: 'keyValueStore', resourceName: store.keyValueStoreName });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return resources;
|
|
25
|
+
};
|
|
26
|
+
const BY_SERVICE = 'By service — empty everything the selected services own';
|
|
27
|
+
const BY_RESOURCE = 'By resource — pick individual buckets/tables';
|
|
28
|
+
// Service mode: pick services, take every resource each one owns.
|
|
29
|
+
const pickByService = async (clearable) => {
|
|
30
|
+
const services = [...new Set(clearable.map((resource) => resource.service))];
|
|
31
|
+
const countFor = (service) => clearable.filter((resource) => resource.service === service).length;
|
|
32
|
+
const picked = await promptCheckbox('Select services to EMPTY (every bucket/table the service owns)', services.map((service) => ({ name: `${service} (${countFor(service)} resources)`, value: service })));
|
|
33
|
+
return clearable.filter((resource) => picked.includes(resource.service));
|
|
34
|
+
};
|
|
35
|
+
// Resource mode: pick individual buckets/tables.
|
|
36
|
+
const pickByResource = (clearable) => promptCheckbox('Select resources to EMPTY (all stored data will be deleted)', clearable.map((resource) => ({
|
|
37
|
+
name: `${resource.service} — ${resource.resourceName} (${resource.kind === 'storageDrive' ? 'bucket' : 'table'})`,
|
|
38
|
+
value: resource,
|
|
39
|
+
})));
|
|
40
|
+
export const promptClearResourcesPlan = async (appName) => {
|
|
41
|
+
const deployInfo = process.env.ACTOR_NAME
|
|
42
|
+
? `${appName}-${process.env.ENVIRONMENT}-${process.env.ACTOR_NAME}`
|
|
43
|
+
: `${appName}-${process.env.ENVIRONMENT}`;
|
|
44
|
+
const clearable = getClearableResources(appName);
|
|
45
|
+
if (clearable.length === 0) {
|
|
46
|
+
console.log('No storage drives or key value stores found in this app.');
|
|
47
|
+
return { kind: 'cancelled' };
|
|
48
|
+
}
|
|
49
|
+
const mode = await promptSelect('How do you want to select what to empty?', [BY_SERVICE, BY_RESOURCE]);
|
|
50
|
+
const resources = mode === BY_SERVICE ? await pickByService(clearable) : await pickByResource(clearable);
|
|
51
|
+
if (resources.length === 0) {
|
|
52
|
+
console.log('Nothing selected. Exiting!');
|
|
53
|
+
return { kind: 'cancelled' };
|
|
54
|
+
}
|
|
55
|
+
console.log(`\nThis will EMPTY (delete all stored data from) ${resources.length} resource(s) in [${deployInfo}]:`);
|
|
56
|
+
for (const resource of resources) {
|
|
57
|
+
console.log(` ${resource.service} — ${resource.resourceName} (${resource.kind === 'storageDrive' ? 'bucket' : 'table'})`);
|
|
58
|
+
}
|
|
59
|
+
const confirmation = await promptText(`Type '${deployInfo}' to confirm`);
|
|
60
|
+
if (confirmation !== deployInfo) {
|
|
61
|
+
console.log('Confirmation did not match. Exiting!');
|
|
62
|
+
return { kind: 'cancelled' };
|
|
63
|
+
}
|
|
64
|
+
return { kind: 'resources', appName, resources };
|
|
65
|
+
};
|
|
66
|
+
//# sourceMappingURL=clearResourcesPlan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clearResourcesPlan.js","sourceRoot":"","sources":["../../../src/lib/clearResourcesPlan.ts"],"names":[],"mappings":"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","sourcesContent":["// The question flow for `qpq clear-resources`: enumerate every service's OWNED\n// data resources (storage drives -> buckets, key value stores -> tables) from\n// its live qpq config, multi-select which to empty, then type the deploy\n// target back to confirm - emptying deletes stored data, so a y/n prompt\n// isn't enough friction. Owned-only so a cross-module ref (e.g. a membership\n// table every service declares against one owner) appears exactly once, on\n// the service that owns it.\nimport { qpqCoreUtils } from 'quidproquo-core';\n\nimport { getServiceNames } from './discovery';\nimport { promptCheckbox, promptSelect, promptText } from './prompts';\nimport { loadServiceQpqConfig } from './qpqConfigs';\n\nexport type ClearableResourceKind = 'storageDrive' | 'keyValueStore';\n\nexport type ClearableResource = {\n service: string;\n kind: ClearableResourceKind;\n // The config-level resource name (e.g. 'templateTestAssets', 'userTenantLinks');\n // the platform driver resolves it to the concrete runtime name (bucket/table).\n resourceName: string;\n};\n\nexport type ClearResourcesPlan =\n | { kind: 'cancelled' }\n // Empty (not destroy) the selected resources' stored data.\n | { kind: 'resources'; appName: string; resources: ClearableResource[] };\n\n// Every owned data resource across the app's services, in service order.\nexport const getClearableResources = (appName: string): ClearableResource[] => {\n const resources: ClearableResource[] = [];\n\n for (const service of getServiceNames(appName)) {\n const qpqConfig = loadServiceQpqConfig(appName, service);\n\n for (const drive of qpqCoreUtils.getOwnedStorageDrives(qpqConfig)) {\n resources.push({ service, kind: 'storageDrive', resourceName: drive.storageDrive });\n }\n\n for (const store of qpqCoreUtils.getOwnedKeyValueStores(qpqConfig)) {\n resources.push({ service, kind: 'keyValueStore', resourceName: store.keyValueStoreName });\n }\n }\n\n return resources;\n};\n\nconst BY_SERVICE = 'By service — empty everything the selected services own';\nconst BY_RESOURCE = 'By resource — pick individual buckets/tables';\n\n// Service mode: pick services, take every resource each one owns.\nconst pickByService = async (clearable: ClearableResource[]): Promise<ClearableResource[]> => {\n const services = [...new Set(clearable.map((resource) => resource.service))];\n\n const countFor = (service: string) => clearable.filter((resource) => resource.service === service).length;\n\n const picked = await promptCheckbox(\n 'Select services to EMPTY (every bucket/table the service owns)',\n services.map((service) => ({ name: `${service} (${countFor(service)} resources)`, value: service })),\n );\n\n return clearable.filter((resource) => picked.includes(resource.service));\n};\n\n// Resource mode: pick individual buckets/tables.\nconst pickByResource = (clearable: ClearableResource[]): Promise<ClearableResource[]> =>\n promptCheckbox(\n 'Select resources to EMPTY (all stored data will be deleted)',\n clearable.map((resource) => ({\n name: `${resource.service} — ${resource.resourceName} (${resource.kind === 'storageDrive' ? 'bucket' : 'table'})`,\n value: resource,\n })),\n );\n\nexport const promptClearResourcesPlan = async (appName: string): Promise<ClearResourcesPlan> => {\n const deployInfo = process.env.ACTOR_NAME\n ? `${appName}-${process.env.ENVIRONMENT}-${process.env.ACTOR_NAME}`\n : `${appName}-${process.env.ENVIRONMENT}`;\n\n const clearable = getClearableResources(appName);\n\n if (clearable.length === 0) {\n console.log('No storage drives or key value stores found in this app.');\n return { kind: 'cancelled' };\n }\n\n const mode = await promptSelect('How do you want to select what to empty?', [BY_SERVICE, BY_RESOURCE]);\n\n const resources = mode === BY_SERVICE ? await pickByService(clearable) : await pickByResource(clearable);\n\n if (resources.length === 0) {\n console.log('Nothing selected. Exiting!');\n return { kind: 'cancelled' };\n }\n\n console.log(`\\nThis will EMPTY (delete all stored data from) ${resources.length} resource(s) in [${deployInfo}]:`);\n for (const resource of resources) {\n console.log(` ${resource.service} — ${resource.resourceName} (${resource.kind === 'storageDrive' ? 'bucket' : 'table'})`);\n }\n\n const confirmation = await promptText(`Type '${deployInfo}' to confirm`);\n\n if (confirmation !== deployInfo) {\n console.log('Confirmation did not match. Exiting!');\n return { kind: 'cancelled' };\n }\n\n return { kind: 'resources', appName, resources };\n};\n"]}
|
|
@@ -14,6 +14,7 @@ export type DeployPlan = {
|
|
|
14
14
|
services: string[];
|
|
15
15
|
stacks: StackType;
|
|
16
16
|
};
|
|
17
|
+
export declare const buildDeployPlanFromArgs: (appName: string, argv: string[]) => DeployPlan | null;
|
|
17
18
|
export declare const promptDeployPlan: (appName: string) => Promise<DeployPlan>;
|
|
18
19
|
export type TeardownPlan = {
|
|
19
20
|
kind: 'cancelled';
|
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
// The interactive question flow shared by `qpq go` (sequential AWS deploy) and
|
|
2
2
|
// a future dockerized variant: pick an app, confirm the target, pick services
|
|
3
|
-
// (or domain/bootstrap), pick stacks.
|
|
3
|
+
// (or domain/bootstrap), pick stacks. buildDeployPlanFromArgs is the
|
|
4
|
+
// non-interactive shortcut (`qpq go all all`) that skips every prompt.
|
|
5
|
+
import { getPositionalArgs } from './args';
|
|
4
6
|
import { getServiceNames } from './discovery';
|
|
5
7
|
import { promptSelect, promptText, promptYesNo } from './prompts';
|
|
8
|
+
const STACK_TYPES = ['all', 'inf', 'api', 'web', 'views'];
|
|
9
|
+
// Non-interactive deploy plan from positional args: `qpq go <services> [stacks]`.
|
|
10
|
+
// services: 'all' | comma-separated service names | 'account' | 'domain' | 'bootstrap'
|
|
11
|
+
// stacks: all | inf | api | web | views (defaults to 'all')
|
|
12
|
+
// Returns null when no positional services token is given, so callers fall back
|
|
13
|
+
// to the interactive promptDeployPlan. Passing args is an explicit opt-in to a
|
|
14
|
+
// prompt-free deploy, so the "Deploying to: [env]" confirmation is skipped too.
|
|
15
|
+
export const buildDeployPlanFromArgs = (appName, argv) => {
|
|
16
|
+
const [servicesArg, stacksArg] = getPositionalArgs(argv, ['--app', '--env', '--platform']);
|
|
17
|
+
if (!servicesArg) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if (servicesArg === 'account') {
|
|
21
|
+
return { kind: 'account', appName };
|
|
22
|
+
}
|
|
23
|
+
if (servicesArg === 'domain') {
|
|
24
|
+
return { kind: 'bootstrap', appName, includeDomain: true };
|
|
25
|
+
}
|
|
26
|
+
if (servicesArg === 'bootstrap') {
|
|
27
|
+
return { kind: 'bootstrap', appName, includeDomain: false };
|
|
28
|
+
}
|
|
29
|
+
const allServices = getServiceNames(appName);
|
|
30
|
+
const services = servicesArg === 'all' ? allServices : servicesArg.split(',').map((s) => s.trim()).filter(Boolean);
|
|
31
|
+
const unknownServices = services.filter((service) => !allServices.includes(service));
|
|
32
|
+
if (unknownServices.length > 0) {
|
|
33
|
+
console.error(`Unknown service(s): ${unknownServices.join(', ')}. Valid services: ${allServices.join(', ')}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const stacks = (stacksArg ?? 'all');
|
|
37
|
+
if (!STACK_TYPES.includes(stacks)) {
|
|
38
|
+
console.error(`Unknown stacks '${stacksArg}'. Valid stacks: ${STACK_TYPES.join(', ')}`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
return { kind: 'services', appName, services: [...new Set(services)], stacks };
|
|
42
|
+
};
|
|
6
43
|
export const promptDeployPlan = async (appName) => {
|
|
7
44
|
const deployInfo = process.env.ACTOR_NAME
|
|
8
45
|
? `${appName}-${process.env.ENVIRONMENT}-${process.env.ACTOR_NAME}`
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deployPrompts.js","sourceRoot":"","sources":["../../../src/lib/deployPrompts.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,8EAA8E;AAC9E,sCAAsC;AACtC,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAkBlE,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,EAAE,OAAe,EAAuB,EAAE;IAC7E,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU;QACvC,CAAC,CAAC,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE;QACnE,CAAC,CAAC,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;IAE5C,IAAI,CAAC,CAAC,MAAM,WAAW,CAAC,kBAAkB,UAAU,GAAG,CAAC,CAAC,EAAE,CAAC;QAC1D,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACxB,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC/B,CAAC;IAED,IAAI,QAAQ,GAAa,EAAE,CAAC;IAE5B,GAAG,CAAC;QACF,MAAM,WAAW,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAE7C,uEAAuE;QACvE,MAAM,OAAO,GAAG,SAAS,CAAC;QAC1B,MAAM,WAAW,GAAG,aAAa,CAAC;QAClC,MAAM,aAAa,GAAG,YAAY,CAAC;QACnC,MAAM,gBAAgB,GAAG,2BAA2B,CAAC;QAErD,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,2BAA2B,EAAE,CAAC,OAAO,EAAE,GAAG,WAAW,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,CAAC,CAAC,CAAC;QAEzI,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;YACxB,QAAQ,GAAG,WAAW,CAAC;YACvB,MAAM;QACR,CAAC;QACD,IAAI,OAAO,KAAK,WAAW,EAAE,CAAC;YAC5B,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,CAAC;QACtC,CAAC;QACD,IAAI,OAAO,KAAK,aAAa,EAAE,CAAC;YAC9B,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;QAC7D,CAAC;QACD,IAAI,OAAO,KAAK,gBAAgB,EAAE,CAAC;YACjC,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;QAC9D,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC,QAAQ,MAAM,WAAW,CAAC,wCAAwC,CAAC,EAAE;IAEtE,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElC,MAAM,MAAM,GAAG,CAAC,MAAM,YAAY,CAAC,kBAAkB,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAc,CAAC;IAE5G,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AACzD,CAAC,CAAC;AAOF,4EAA4E;AAC5E,4EAA4E;AAC5E,4EAA4E;AAC5E,MAAM,CAAC,MAAM,kBAAkB,GAAG,KAAK,EAAE,OAAe,EAAyB,EAAE;IACjF,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU;QACvC,CAAC,CAAC,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE;QACnE,CAAC,CAAC,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;IAE5C,IAAI,QAAQ,GAAa,EAAE,CAAC;IAE5B,GAAG,CAAC;QACF,MAAM,WAAW,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;QAC7C,MAAM,OAAO,GAAG,SAAS,CAAC;QAE1B,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,8BAA8B,EAAE,CAAC,OAAO,EAAE,GAAG,WAAW,CAAC,CAAC,CAAC;QAE9F,IAAI,OAAO,KAAK,OAAO,EAAE,CAAC;YACxB,QAAQ,GAAG,WAAW,CAAC;YACvB,MAAM;QACR,CAAC;QAED,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACzB,CAAC,QAAQ,MAAM,WAAW,CAAC,2CAA2C,CAAC,EAAE;IAEzE,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;IAElC,OAAO,CAAC,GAAG,CAAC,oFAAoF,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACvH,MAAM,YAAY,GAAG,MAAM,UAAU,CAAC,SAAS,UAAU,cAAc,CAAC,CAAC;IAEzE,IAAI,YAAY,KAAK,UAAU,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC;QACpD,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IAC/B,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;AACjD,CAAC,CAAC","sourcesContent":["// The interactive question flow shared by `qpq go` (sequential AWS deploy) and\n// a future dockerized variant: pick an app, confirm the target, pick services\n// (or domain/bootstrap), pick stacks.\nimport { getServiceNames } from './discovery';\nimport { promptSelect, promptText, promptYesNo } from './prompts';\n\nexport type StackType = 'all' | 'inf' | 'api' | 'web' | 'views';\n\nexport type DeployPlan =\n | { kind: 'cancelled' }\n // Deploy only the account-level guardrails stack (one per AWS account).\n | { kind: 'account'; appName: string }\n // Deploy the bootstrap stack (and optionally the domain stack first).\n | { kind: 'bootstrap'; appName: string; includeDomain: boolean }\n // Deploy the selected stacks for the selected services.\n | {\n kind: 'services';\n appName: string;\n services: string[];\n stacks: StackType;\n };\n\nexport const promptDeployPlan = async (appName: string): Promise<DeployPlan> => {\n const deployInfo = process.env.ACTOR_NAME\n ? `${appName}-${process.env.ENVIRONMENT}-${process.env.ACTOR_NAME}`\n : `${appName}-${process.env.ENVIRONMENT}`;\n\n if (!(await promptYesNo(`Deploying to: [${deployInfo}]`))) {\n console.log('Exiting!');\n return { kind: 'cancelled' };\n }\n\n let services: string[] = [];\n\n do {\n const allServices = getServiceNames(appName);\n\n // Bracketed so the meta options can't be mistaken for actual services.\n const allText = '[ all ]';\n const accountText = '[ account ]';\n const domainEnvText = '[ domain ]';\n const bootstrapEnvText = '[ bootstrap environment ]';\n\n const service = await promptSelect('Select services to deploy', [allText, ...allServices, accountText, domainEnvText, bootstrapEnvText]);\n\n if (service === allText) {\n services = allServices;\n break;\n }\n if (service === accountText) {\n return { kind: 'account', appName };\n }\n if (service === domainEnvText) {\n return { kind: 'bootstrap', appName, includeDomain: true };\n }\n if (service === bootstrapEnvText) {\n return { kind: 'bootstrap', appName, includeDomain: false };\n }\n\n services.push(service);\n } while (await promptYesNo('Do you want to deploy another service?'));\n\n services = [...new Set(services)];\n\n const stacks = (await promptSelect('Stacks to deploy', ['all', 'inf', 'api', 'web', 'views'])) as StackType;\n\n return { kind: 'services', appName, services, stacks };\n};\n\nexport type TeardownPlan =\n | { kind: 'cancelled' }\n // Destroy the web, api and inf stacks for the selected services.\n | { kind: 'services'; appName: string; services: string[] };\n\n// The question flow for `qpq teardown`: pick services, then type the deploy\n// target back to confirm — destroying inf stacks takes user data (key value\n// stores, user directories) with it, so a y/n prompt isn't enough friction.\nexport const promptTeardownPlan = async (appName: string): Promise<TeardownPlan> => {\n const deployInfo = process.env.ACTOR_NAME\n ? `${appName}-${process.env.ENVIRONMENT}-${process.env.ACTOR_NAME}`\n : `${appName}-${process.env.ENVIRONMENT}`;\n\n let services: string[] = [];\n\n do {\n const allServices = getServiceNames(appName);\n const allText = '[ all ]';\n\n const service = await promptSelect('Select services to tear down', [allText, ...allServices]);\n\n if (service === allText) {\n services = allServices;\n break;\n }\n\n services.push(service);\n } while (await promptYesNo('Do you want to tear down another service?'));\n\n services = [...new Set(services)];\n\n console.log(`\\nThis will DESTROY the web, api and inf stacks (including all stored data) for: ${services.join(', ')}`);\n const confirmation = await promptText(`Type '${deployInfo}' to confirm`);\n\n if (confirmation !== deployInfo) {\n console.log('Confirmation did not match. Exiting!');\n return { kind: 'cancelled' };\n }\n\n return { kind: 'services', appName, services };\n};\n"]}
|
|
1
|
+
{"version":3,"file":"deployPrompts.js","sourceRoot":"","sources":["../../../src/lib/deployPrompts.ts"],"names":[],"mappings":"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","sourcesContent":["// The interactive question flow shared by `qpq go` (sequential AWS deploy) and\n// a future dockerized variant: pick an app, confirm the target, pick services\n// (or domain/bootstrap), pick stacks. buildDeployPlanFromArgs is the\n// non-interactive shortcut (`qpq go all all`) that skips every prompt.\nimport { getPositionalArgs } from './args';\nimport { getServiceNames } from './discovery';\nimport { promptSelect, promptText, promptYesNo } from './prompts';\n\nexport type StackType = 'all' | 'inf' | 'api' | 'web' | 'views';\n\nconst STACK_TYPES: StackType[] = ['all', 'inf', 'api', 'web', 'views'];\n\nexport type DeployPlan =\n | { kind: 'cancelled' }\n // Deploy only the account-level guardrails stack (one per AWS account).\n | { kind: 'account'; appName: string }\n // Deploy the bootstrap stack (and optionally the domain stack first).\n | { kind: 'bootstrap'; appName: string; includeDomain: boolean }\n // Deploy the selected stacks for the selected services.\n | {\n kind: 'services';\n appName: string;\n services: string[];\n stacks: StackType;\n };\n\n// Non-interactive deploy plan from positional args: `qpq go <services> [stacks]`.\n// services: 'all' | comma-separated service names | 'account' | 'domain' | 'bootstrap'\n// stacks: all | inf | api | web | views (defaults to 'all')\n// Returns null when no positional services token is given, so callers fall back\n// to the interactive promptDeployPlan. Passing args is an explicit opt-in to a\n// prompt-free deploy, so the \"Deploying to: [env]\" confirmation is skipped too.\nexport const buildDeployPlanFromArgs = (appName: string, argv: string[]): DeployPlan | null => {\n const [servicesArg, stacksArg] = getPositionalArgs(argv, ['--app', '--env', '--platform']);\n\n if (!servicesArg) {\n return null;\n }\n\n if (servicesArg === 'account') {\n return { kind: 'account', appName };\n }\n if (servicesArg === 'domain') {\n return { kind: 'bootstrap', appName, includeDomain: true };\n }\n if (servicesArg === 'bootstrap') {\n return { kind: 'bootstrap', appName, includeDomain: false };\n }\n\n const allServices = getServiceNames(appName);\n const services = servicesArg === 'all' ? allServices : servicesArg.split(',').map((s) => s.trim()).filter(Boolean);\n\n const unknownServices = services.filter((service) => !allServices.includes(service));\n if (unknownServices.length > 0) {\n console.error(`Unknown service(s): ${unknownServices.join(', ')}. Valid services: ${allServices.join(', ')}`);\n process.exit(1);\n }\n\n const stacks = (stacksArg ?? 'all') as StackType;\n if (!STACK_TYPES.includes(stacks)) {\n console.error(`Unknown stacks '${stacksArg}'. Valid stacks: ${STACK_TYPES.join(', ')}`);\n process.exit(1);\n }\n\n return { kind: 'services', appName, services: [...new Set(services)], stacks };\n};\n\nexport const promptDeployPlan = async (appName: string): Promise<DeployPlan> => {\n const deployInfo = process.env.ACTOR_NAME\n ? `${appName}-${process.env.ENVIRONMENT}-${process.env.ACTOR_NAME}`\n : `${appName}-${process.env.ENVIRONMENT}`;\n\n if (!(await promptYesNo(`Deploying to: [${deployInfo}]`))) {\n console.log('Exiting!');\n return { kind: 'cancelled' };\n }\n\n let services: string[] = [];\n\n do {\n const allServices = getServiceNames(appName);\n\n // Bracketed so the meta options can't be mistaken for actual services.\n const allText = '[ all ]';\n const accountText = '[ account ]';\n const domainEnvText = '[ domain ]';\n const bootstrapEnvText = '[ bootstrap environment ]';\n\n const service = await promptSelect('Select services to deploy', [allText, ...allServices, accountText, domainEnvText, bootstrapEnvText]);\n\n if (service === allText) {\n services = allServices;\n break;\n }\n if (service === accountText) {\n return { kind: 'account', appName };\n }\n if (service === domainEnvText) {\n return { kind: 'bootstrap', appName, includeDomain: true };\n }\n if (service === bootstrapEnvText) {\n return { kind: 'bootstrap', appName, includeDomain: false };\n }\n\n services.push(service);\n } while (await promptYesNo('Do you want to deploy another service?'));\n\n services = [...new Set(services)];\n\n const stacks = (await promptSelect('Stacks to deploy', ['all', 'inf', 'api', 'web', 'views'])) as StackType;\n\n return { kind: 'services', appName, services, stacks };\n};\n\nexport type TeardownPlan =\n | { kind: 'cancelled' }\n // Destroy the web, api and inf stacks for the selected services.\n | { kind: 'services'; appName: string; services: string[] };\n\n// The question flow for `qpq teardown`: pick services, then type the deploy\n// target back to confirm — destroying inf stacks takes user data (key value\n// stores, user directories) with it, so a y/n prompt isn't enough friction.\nexport const promptTeardownPlan = async (appName: string): Promise<TeardownPlan> => {\n const deployInfo = process.env.ACTOR_NAME\n ? `${appName}-${process.env.ENVIRONMENT}-${process.env.ACTOR_NAME}`\n : `${appName}-${process.env.ENVIRONMENT}`;\n\n let services: string[] = [];\n\n do {\n const allServices = getServiceNames(appName);\n const allText = '[ all ]';\n\n const service = await promptSelect('Select services to tear down', [allText, ...allServices]);\n\n if (service === allText) {\n services = allServices;\n break;\n }\n\n services.push(service);\n } while (await promptYesNo('Do you want to tear down another service?'));\n\n services = [...new Set(services)];\n\n console.log(`\\nThis will DESTROY the web, api and inf stacks (including all stored data) for: ${services.join(', ')}`);\n const confirmation = await promptText(`Type '${deployInfo}' to confirm`);\n\n if (confirmation !== deployInfo) {\n console.log('Confirmation did not match. Exiting!');\n return { kind: 'cancelled' };\n }\n\n return { kind: 'services', appName, services };\n};\n"]}
|
|
@@ -29,6 +29,9 @@ startDevServer(
|
|
|
29
29
|
serverDomain: 'localhost',
|
|
30
30
|
serverPort: 8080,
|
|
31
31
|
webSocketPort: 8888,
|
|
32
|
+
// Scoped per app so running the dev server for two apps out of the same
|
|
33
|
+
// repo root doesn't share kvs/file-storage/config state.
|
|
34
|
+
runtimePath: ${JSON.stringify(path.join('.qpq-runtime', appName))},
|
|
32
35
|
fileStorageConfig: {
|
|
33
36
|
secureUrlSecret: 'dev-secure-url-secret',
|
|
34
37
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"devServerEntry.js","sourceRoot":"","sources":["../../../src/lib/devServerEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,6EAA6E;AAC7E,8EAA8E;AAC9E,yEAAyE;AACzE,4EAA4E;AAC5E,4DAA4D;AAC5D,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,OAAe,EAAU,EAAE;IAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAElD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,CAAC;IACjF,MAAM,eAAe,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;QAClD,CAAC,CAAC,wCAAwC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,GAAG;QAC/F,CAAC,CAAC,sCAAsC,CAAC;IAE3C,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,EAAE,CAAC,aAAa,CACd,SAAS,EACT;;;;;;;;EAQF,eAAe
|
|
1
|
+
{"version":3,"file":"devServerEntry.js","sourceRoot":"","sources":["../../../src/lib/devServerEntry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,6EAA6E;AAC7E,8EAA8E;AAC9E,yEAAyE;AACzE,4EAA4E;AAC5E,4DAA4D;AAC5D,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,OAAe,EAAU,EAAE;IAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAElD,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,CAAC;IACjF,MAAM,eAAe,GAAG,EAAE,CAAC,UAAU,CAAC,aAAa,CAAC;QAClD,CAAC,CAAC,wCAAwC,IAAI,CAAC,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,GAAG;QAC/F,CAAC,CAAC,sCAAsC,CAAC;IAE3C,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,EAAE,CAAC,aAAa,CACd,SAAS,EACT;;;;;;;;EAQF,eAAe;;;;;;;;;mBASE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;;;;;;;;;;;;CAYpE,CACE,CAAC;IAEF,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC","sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { getAppDirectory } from './discovery';\n\n// The dev-server bootstrap, generated per build so it can import an optional\n// app-owned overrides file (apps/<app>/devServer.config.ts, default-exporting\n// DevServerConfigOverrides). The `quidproquo-dynamic-loader` import is a\n// virtual module injected by QpqPlugin at bundle time. QPQ_WEB_ROOT (set in\n// the docker platform image) turns on static views serving.\nexport const writeDevServerEntry = (root: string, appName: string): string => {\n const entryDir = path.join(root, 'dist', 'qpq', 'dev-server');\n const entryPath = path.join(entryDir, 'entry.ts');\n\n const overridesPath = path.join(getAppDirectory(appName), 'devServer.config.ts');\n const overridesImport = fs.existsSync(overridesPath)\n ? `import devServerConfigOverrides from ${JSON.stringify(overridesPath.replace(/\\.ts$/, ''))};`\n : 'const devServerConfigOverrides = {};';\n\n fs.mkdirSync(entryDir, { recursive: true });\n fs.writeFileSync(\n entryPath,\n `// GENERATED by qpq — do not edit.\nimport { startDevServer } from 'quidproquo-dev-server';\nimport {\n qpqConfigs,\n qpqDynamicModuleLoaderForService,\n // @ts-ignore - Special build-time virtual module\n} from 'quidproquo-dynamic-loader';\n\n${overridesImport}\n\nstartDevServer(\n {\n serverDomain: 'localhost',\n serverPort: 8080,\n webSocketPort: 8888,\n // Scoped per app so running the dev server for two apps out of the same\n // repo root doesn't share kvs/file-storage/config state.\n runtimePath: ${JSON.stringify(path.join('.qpq-runtime', appName))},\n fileStorageConfig: {\n secureUrlSecret: 'dev-secure-url-secret',\n },\n qpqConfigs,\n dynamicModuleLoader: qpqDynamicModuleLoaderForService,\n webRoot: process.env.QPQ_WEB_ROOT,\n logServiceName: 'admin',\n delay: { default: 0 },\n },\n devServerConfigOverrides,\n);\n`,\n );\n\n return entryPath;\n};\n"]}
|
package/lib/esm/lib/hooks.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import { TaskFailure } from './runTasks';
|
|
1
2
|
export declare const runAppHook: (appName: string, hook: string) => Promise<boolean>;
|
|
2
|
-
export declare const
|
|
3
|
+
export declare const runHookForAllPackages: (hook: string, jobs: number) => Promise<TaskFailure[]>;
|