quidproquo-cli 0.1.12 → 0.1.14

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.
@@ -57,6 +57,17 @@ exports.cliCommands = [
57
57
  summary: 'Run every views microfrontend dev server',
58
58
  run: (a) => require('../commands/goDevWeb').goDevWebCommand(a),
59
59
  },
60
+ {
61
+ name: 'migrate',
62
+ usageArgs: '[app]',
63
+ summary: 'Run pending migrations against the LOCAL dev store, once, then exit.',
64
+ usageExtra: [
65
+ 'Deployed, migrations run on a stack update; nothing local ever deploys, so',
66
+ 'this is how you exercise one before shipping it. Records what it runs, so a',
67
+ 'second call is a no-op.',
68
+ ],
69
+ run: (a) => require('../commands/migrate').migrateCommand(a),
70
+ },
60
71
  {
61
72
  name: 'teardown',
62
73
  summary: 'Destroy web/api/inf stacks for selected services (interactive)',
@@ -1 +1 @@
1
- {"version":3,"file":"commandRegistry.js","sourceRoot":"","sources":["../../../src/cli/commandRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["// Single source of truth for every qpq command: its name, the one-line\n// summary (shared by `qpq help` and the interactive menu) and a runner.\n//\n// Command modules are required lazily inside each runner so a simple\n// `qpq help` doesn't pay for the whole toolchain, and so ts-node can be\n// registered before any command module (or anything it requires) loads.\nimport { promptText } from '../lib/prompts';\n\nexport type CliCommand = {\n name: string;\n // Argument hint shown in the usage text, e.g. '[svc] [stack]'.\n usageArgs?: string;\n summary: string;\n // Extra usage-only detail lines, indented under the summary.\n usageExtra?: string[];\n // Menu-only: commands that need a positional argument (which the menu has\n // no argv to carry) gather it here before running.\n promptArgs?: () => Promise<string[]>;\n run: (commandArgv: string[]) => Promise<void>;\n};\n\n// `qpq hooks` needs a hook name; when launched from the menu there is no\n// argv to pass it in, so ask.\nconst promptHookName = async (): Promise<string[]> => [\n await promptText('Hook name (runs qpq:<name>, or plain <name>, in every app/package that defines it):'),\n];\n\n/* eslint-disable @typescript-eslint/no-require-imports */\nexport const cliCommands: CliCommand[] = [\n {\n name: 'go',\n usageArgs: '[svc] [stack]',\n summary: 'Deploy services (interactive; platform from deploy.config.json, default aws).',\n usageExtra: [\n 'Pass positional args to skip prompts, e.g. qpq go all all — svc is',\n \"'all' | comma-list | account | domain | bootstrap; stack is all|inf|api|web|views.\",\n \"A full 'all all' (or 'all api') deploy also publishes federated backends.\",\n ],\n run: (a) => require('../commands/go').goCommand(a),\n },\n {\n name: 'go:docker',\n summary: 'Same as go (incl. positional args), but deploys in parallel via docker',\n run: (a) => require('../commands/goDocker').goDockerCommand(a),\n },\n {\n name: 'go:dev',\n summary: 'Run the full local dev stack (api + web) in one process',\n run: (a) => require('../commands/goDev').goDevCommand(a),\n },\n {\n name: 'go:dev:api',\n summary: 'Run the local API dev server (hot reload)',\n run: (a) => require('../commands/goDevApi').goDevApiCommand(a),\n },\n {\n name: 'go:dev:web',\n summary: 'Run every views microfrontend dev server',\n run: (a) => require('../commands/goDevWeb').goDevWebCommand(a),\n },\n {\n name: 'teardown',\n summary: 'Destroy web/api/inf stacks for selected services (interactive)',\n run: (a) => require('../commands/teardown').teardownCommand(a),\n },\n {\n name: 'clear-resources',\n summary: 'Empty selected buckets/tables (data only, stacks untouched; interactive)',\n run: (a) => require('../commands/clearResources').clearResourcesCommand(a),\n },\n {\n name: 'synth',\n usageArgs: '[service]',\n summary: 'Synth QPQ configs to dist/apps/<app>/infrastructure',\n run: (a) => require('../commands/synth').synthCommand(a),\n },\n {\n name: 'prep',\n summary: 'Regenerate apps/<app>/tsconfig.federated.json',\n run: (a) => require('../commands/prep').prepCommand(a),\n },\n {\n name: 'publish',\n summary: 'Build + upload + deploy federated remotes',\n run: (a) => require('../commands/publish').publishCommand(a),\n },\n {\n name: 'publish:build',\n summary: 'Build federated remotes locally',\n run: (a) => require('../commands/publish').publishBuildCommand(a),\n },\n {\n name: 'publish:upload',\n summary: 'Upload built version dirs (nothing goes live)',\n run: (a) => require('../commands/publish').publishUploadCommand(a),\n },\n {\n name: 'publish:deploy',\n summary: 'Flip manifests to the uploaded versions',\n run: (a) => require('../commands/publish').publishDeployCommand(a),\n },\n {\n name: 'check:circular',\n summary: 'Scan workspace sources for circular imports (fails on cycles; --warn to only report)',\n run: (a) => require('../commands/checkCircular').checkCircularCommand(a),\n },\n {\n name: 'hooks',\n usageArgs: '<name>',\n summary: 'Run qpq:<name> (or plain <name>) in every app/package that defines it (dependency ordered, parallel; --jobs=N)',\n promptArgs: promptHookName,\n run: (a) => require('../commands/hooks').hooksCommand(a),\n },\n];\n/* eslint-enable @typescript-eslint/no-require-imports */\n\n// Width of the \"name [args]\" column in the usage text.\nconst usageColumnWidth = 17;\n\nexport const buildUsage = (): string => {\n const commandLines = cliCommands.flatMap((command) => {\n const nameAndArgs = command.usageArgs ? `${command.name} ${command.usageArgs}` : command.name;\n const extraLines = (command.usageExtra ?? []).map((line) => ` ${''.padEnd(usageColumnWidth)}${line}`);\n return [` ${nameAndArgs.padEnd(usageColumnWidth)}${command.summary}`, ...extraLines];\n });\n\n return [\n 'qpq — build, dev and deploy orchestration for QPQ apps',\n '',\n 'Usage: qpq <command> [--app <name>] [--env <name>] [--platform <name>]',\n ' qpq (no command opens an interactive menu)',\n '',\n 'Commands:',\n ...commandLines,\n '',\n ].join('\\n');\n};\n"]}
1
+ {"version":3,"file":"commandRegistry.js","sourceRoot":"","sources":["../../../src/cli/commandRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["// Single source of truth for every qpq command: its name, the one-line\n// summary (shared by `qpq help` and the interactive menu) and a runner.\n//\n// Command modules are required lazily inside each runner so a simple\n// `qpq help` doesn't pay for the whole toolchain, and so ts-node can be\n// registered before any command module (or anything it requires) loads.\nimport { promptText } from '../lib/prompts';\n\nexport type CliCommand = {\n name: string;\n // Argument hint shown in the usage text, e.g. '[svc] [stack]'.\n usageArgs?: string;\n summary: string;\n // Extra usage-only detail lines, indented under the summary.\n usageExtra?: string[];\n // Menu-only: commands that need a positional argument (which the menu has\n // no argv to carry) gather it here before running.\n promptArgs?: () => Promise<string[]>;\n run: (commandArgv: string[]) => Promise<void>;\n};\n\n// `qpq hooks` needs a hook name; when launched from the menu there is no\n// argv to pass it in, so ask.\nconst promptHookName = async (): Promise<string[]> => [\n await promptText('Hook name (runs qpq:<name>, or plain <name>, in every app/package that defines it):'),\n];\n\n/* eslint-disable @typescript-eslint/no-require-imports */\nexport const cliCommands: CliCommand[] = [\n {\n name: 'go',\n usageArgs: '[svc] [stack]',\n summary: 'Deploy services (interactive; platform from deploy.config.json, default aws).',\n usageExtra: [\n 'Pass positional args to skip prompts, e.g. qpq go all all — svc is',\n \"'all' | comma-list | account | domain | bootstrap; stack is all|inf|api|web|views.\",\n \"A full 'all all' (or 'all api') deploy also publishes federated backends.\",\n ],\n run: (a) => require('../commands/go').goCommand(a),\n },\n {\n name: 'go:docker',\n summary: 'Same as go (incl. positional args), but deploys in parallel via docker',\n run: (a) => require('../commands/goDocker').goDockerCommand(a),\n },\n {\n name: 'go:dev',\n summary: 'Run the full local dev stack (api + web) in one process',\n run: (a) => require('../commands/goDev').goDevCommand(a),\n },\n {\n name: 'go:dev:api',\n summary: 'Run the local API dev server (hot reload)',\n run: (a) => require('../commands/goDevApi').goDevApiCommand(a),\n },\n {\n name: 'go:dev:web',\n summary: 'Run every views microfrontend dev server',\n run: (a) => require('../commands/goDevWeb').goDevWebCommand(a),\n },\n {\n name: 'migrate',\n usageArgs: '[app]',\n summary: 'Run pending migrations against the LOCAL dev store, once, then exit.',\n usageExtra: [\n 'Deployed, migrations run on a stack update; nothing local ever deploys, so',\n 'this is how you exercise one before shipping it. Records what it runs, so a',\n 'second call is a no-op.',\n ],\n run: (a) => require('../commands/migrate').migrateCommand(a),\n },\n {\n name: 'teardown',\n summary: 'Destroy web/api/inf stacks for selected services (interactive)',\n run: (a) => require('../commands/teardown').teardownCommand(a),\n },\n {\n name: 'clear-resources',\n summary: 'Empty selected buckets/tables (data only, stacks untouched; interactive)',\n run: (a) => require('../commands/clearResources').clearResourcesCommand(a),\n },\n {\n name: 'synth',\n usageArgs: '[service]',\n summary: 'Synth QPQ configs to dist/apps/<app>/infrastructure',\n run: (a) => require('../commands/synth').synthCommand(a),\n },\n {\n name: 'prep',\n summary: 'Regenerate apps/<app>/tsconfig.federated.json',\n run: (a) => require('../commands/prep').prepCommand(a),\n },\n {\n name: 'publish',\n summary: 'Build + upload + deploy federated remotes',\n run: (a) => require('../commands/publish').publishCommand(a),\n },\n {\n name: 'publish:build',\n summary: 'Build federated remotes locally',\n run: (a) => require('../commands/publish').publishBuildCommand(a),\n },\n {\n name: 'publish:upload',\n summary: 'Upload built version dirs (nothing goes live)',\n run: (a) => require('../commands/publish').publishUploadCommand(a),\n },\n {\n name: 'publish:deploy',\n summary: 'Flip manifests to the uploaded versions',\n run: (a) => require('../commands/publish').publishDeployCommand(a),\n },\n {\n name: 'check:circular',\n summary: 'Scan workspace sources for circular imports (fails on cycles; --warn to only report)',\n run: (a) => require('../commands/checkCircular').checkCircularCommand(a),\n },\n {\n name: 'hooks',\n usageArgs: '<name>',\n summary: 'Run qpq:<name> (or plain <name>) in every app/package that defines it (dependency ordered, parallel; --jobs=N)',\n promptArgs: promptHookName,\n run: (a) => require('../commands/hooks').hooksCommand(a),\n },\n];\n/* eslint-enable @typescript-eslint/no-require-imports */\n\n// Width of the \"name [args]\" column in the usage text.\nconst usageColumnWidth = 17;\n\nexport const buildUsage = (): string => {\n const commandLines = cliCommands.flatMap((command) => {\n const nameAndArgs = command.usageArgs ? `${command.name} ${command.usageArgs}` : command.name;\n const extraLines = (command.usageExtra ?? []).map((line) => ` ${''.padEnd(usageColumnWidth)}${line}`);\n return [` ${nameAndArgs.padEnd(usageColumnWidth)}${command.summary}`, ...extraLines];\n });\n\n return [\n 'qpq — build, dev and deploy orchestration for QPQ apps',\n '',\n 'Usage: qpq <command> [--app <name>] [--env <name>] [--platform <name>]',\n ' qpq (no command opens an interactive menu)',\n '',\n 'Commands:',\n ...commandLines,\n '',\n ].join('\\n');\n};\n"]}
@@ -0,0 +1 @@
1
+ export declare const migrateCommand: (argv: string[]) => Promise<void>;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.migrateCommand = void 0;
16
+ // `qpq migrate` — run every pending migration against the LOCAL dev store, once, then exit.
17
+ //
18
+ // Deployed, migrations are triggered by a stack update. Nothing locally ever deploys, so
19
+ // without this a migration is first executed in a real environment, which is a poor place to
20
+ // discover what it does. This bundles exactly what `go:dev:api` bundles and runs the same
21
+ // queue path, so a local run rehearses the deployed one rather than a convenient lookalike.
22
+ //
23
+ // Migrations are recorded as they run, in the same store the deploy path uses, so running
24
+ // this twice is a no-op the second time.
25
+ const quidproquo_deploy_rspack_1 = require("quidproquo-deploy-rspack");
26
+ const child_process_1 = require("child_process");
27
+ const path_1 = __importDefault(require("path"));
28
+ const core_1 = require("@rspack/core");
29
+ const deployEnv_1 = require("../lib/deployEnv");
30
+ const devServerEntry_1 = require("../lib/devServerEntry");
31
+ const discovery_1 = require("../lib/discovery");
32
+ const resolveAppSelection_1 = require("../lib/resolveAppSelection");
33
+ const MIGRATE_BUNDLE_PATH = path_1.default.join('dist', 'qpq', 'dev-server', 'migrate.js');
34
+ const migrateCommand = (argv) => __awaiter(void 0, void 0, void 0, function* () {
35
+ const root = (0, discovery_1.getRoot)();
36
+ const appName = yield (0, resolveAppSelection_1.resolveAppSelection)({ argv, envVar: 'QPQ_DEV_APP' });
37
+ process.env.QPQ_DEV_APP = appName;
38
+ (0, deployEnv_1.primeDeployEnvFromConfig)(appName);
39
+ console.log(`Running local migrations for app [${appName}]`);
40
+ const qpqConfigs = (0, quidproquo_deploy_rspack_1.getAppServiceQpqConfigs)(root, appName);
41
+ const entry = (0, devServerEntry_1.writeDevServerEntry)(root, appName, 'migrate');
42
+ const rspackConfig = (0, quidproquo_deploy_rspack_1.getDevServerRspackConfig)({ root, entry, qpqConfigs });
43
+ // Same bundle shape as the dev server, different output name so a running `go:dev:api`
44
+ // watcher does not fight over the file.
45
+ const bundlePath = path_1.default.join(root, MIGRATE_BUNDLE_PATH);
46
+ const migrateConfig = Object.assign(Object.assign({}, rspackConfig), { output: Object.assign(Object.assign({}, rspackConfig.output), { filename: 'migrate.js' }) });
47
+ yield new Promise((resolve, reject) => {
48
+ (0, core_1.rspack)(migrateConfig, (error, stats) => {
49
+ if (error) {
50
+ return reject(error);
51
+ }
52
+ if (stats === null || stats === void 0 ? void 0 : stats.hasErrors()) {
53
+ console.error(stats.toString({ colors: true, all: false, errors: true }));
54
+ return reject(new Error('Failed to bundle migrations'));
55
+ }
56
+ resolve();
57
+ });
58
+ });
59
+ // The bundle exits non-zero on failure; mirror that so a shell chain or CI notices instead
60
+ // of carrying on as though the data were migrated.
61
+ const exitCode = yield new Promise((resolve) => {
62
+ const child = (0, child_process_1.spawn)('node', [bundlePath], { stdio: 'inherit', env: Object.assign(Object.assign({}, process.env), { QPQ_DEV_APP: appName }) });
63
+ child.on('exit', (code) => resolve(code !== null && code !== void 0 ? code : 1));
64
+ });
65
+ if (exitCode !== 0) {
66
+ process.exitCode = exitCode;
67
+ }
68
+ });
69
+ exports.migrateCommand = migrateCommand;
70
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/commands/migrate.ts"],"names":[],"mappings":"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","sourcesContent":["// `qpq migrate` — run every pending migration against the LOCAL dev store, once, then exit.\n//\n// Deployed, migrations are triggered by a stack update. Nothing locally ever deploys, so\n// without this a migration is first executed in a real environment, which is a poor place to\n// discover what it does. This bundles exactly what `go:dev:api` bundles and runs the same\n// queue path, so a local run rehearses the deployed one rather than a convenient lookalike.\n//\n// Migrations are recorded as they run, in the same store the deploy path uses, so running\n// this twice is a no-op the second time.\nimport { getAppServiceQpqConfigs, getDevServerRspackConfig } from 'quidproquo-deploy-rspack';\n\nimport { spawn } from 'child_process';\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 { resolveAppSelection } from '../lib/resolveAppSelection';\n\nconst MIGRATE_BUNDLE_PATH = path.join('dist', 'qpq', 'dev-server', 'migrate.js');\n\nexport const migrateCommand = 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\n console.log(`Running local migrations for app [${appName}]`);\n\n const qpqConfigs = getAppServiceQpqConfigs(root, appName);\n const entry = writeDevServerEntry(root, appName, 'migrate');\n const rspackConfig = getDevServerRspackConfig({ root, entry, qpqConfigs });\n\n // Same bundle shape as the dev server, different output name so a running `go:dev:api`\n // watcher does not fight over the file.\n const bundlePath = path.join(root, MIGRATE_BUNDLE_PATH);\n const migrateConfig = {\n ...rspackConfig,\n output: { ...rspackConfig.output, filename: 'migrate.js' },\n };\n\n await new Promise<void>((resolve, reject) => {\n rspack(migrateConfig as any, (error, stats) => {\n if (error) {\n return reject(error);\n }\n\n if (stats?.hasErrors()) {\n console.error(stats.toString({ colors: true, all: false, errors: true }));\n return reject(new Error('Failed to bundle migrations'));\n }\n\n resolve();\n });\n });\n\n // The bundle exits non-zero on failure; mirror that so a shell chain or CI notices instead\n // of carrying on as though the data were migrated.\n const exitCode = await new Promise<number>((resolve) => {\n const child = spawn('node', [bundlePath], { stdio: 'inherit', env: { ...process.env, QPQ_DEV_APP: appName } });\n child.on('exit', (code) => resolve(code ?? 1));\n });\n\n if (exitCode !== 0) {\n process.exitCode = exitCode;\n }\n};\n"]}
@@ -1 +1,2 @@
1
- export declare const writeDevServerEntry: (root: string, appName: string) => string;
1
+ export type DevServerEntryMode = 'serve' | 'migrate';
2
+ export declare const writeDevServerEntry: (root: string, appName: string, mode?: DevServerEntryMode) => string;
@@ -7,21 +7,16 @@ exports.writeDevServerEntry = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const discovery_1 = require("./discovery");
10
- // The dev-server bootstrap, generated per build so it can import an optional
11
- // app-owned overrides file (apps/<app>/devServer.config.ts, default-exporting
12
- // DevServerConfigOverrides). The `quidproquo-dynamic-loader` import is a
13
- // virtual module injected by QpqPlugin at bundle time. QPQ_WEB_ROOT (set in
14
- // the docker platform image) turns on static views serving.
15
- const writeDevServerEntry = (root, appName) => {
10
+ const writeDevServerEntry = (root, appName, mode = 'serve') => {
16
11
  const entryDir = path_1.default.join(root, 'dist', 'qpq', 'dev-server');
17
- const entryPath = path_1.default.join(entryDir, 'entry.ts');
12
+ const entryPath = path_1.default.join(entryDir, mode === 'migrate' ? 'migrate.entry.ts' : 'entry.ts');
18
13
  const overridesPath = path_1.default.join((0, discovery_1.getAppDirectory)(appName), 'devServer.config.ts');
19
14
  const overridesImport = fs_1.default.existsSync(overridesPath)
20
15
  ? `import devServerConfigOverrides from ${JSON.stringify(overridesPath.replace(/\.ts$/, ''))};`
21
16
  : 'const devServerConfigOverrides = {};';
22
17
  fs_1.default.mkdirSync(entryDir, { recursive: true });
23
18
  fs_1.default.writeFileSync(entryPath, `// GENERATED by qpq — do not edit.
24
- import { startDevServer } from 'quidproquo-dev-server';
19
+ import { ${mode === 'migrate' ? 'runMigrations' : 'startDevServer'} } from 'quidproquo-dev-server';
25
20
  import {
26
21
  qpqConfigs,
27
22
  qpqDynamicModuleLoaderForService,
@@ -30,7 +25,7 @@ import {
30
25
 
31
26
  ${overridesImport}
32
27
 
33
- startDevServer(
28
+ ${mode === 'migrate' ? 'runMigrations' : 'startDevServer'}(
34
29
  {
35
30
  serverDomain: 'localhost',
36
31
  serverPort: 8080,
@@ -48,7 +43,24 @@ startDevServer(
48
43
  delay: { default: 0 },
49
44
  },
50
45
  devServerConfigOverrides,
51
- );
46
+ )${mode === 'migrate'
47
+ ? `
48
+ .then((ran) => {
49
+ const total = Object.values(ran).reduce((sum, keys) => sum + keys.length, 0);
50
+ for (const [service, keys] of Object.entries(ran)) {
51
+ for (const key of keys) {
52
+ console.log(\` [\${service}] \${key}\`);
53
+ }
54
+ }
55
+ console.log(total === 0 ? 'No pending migrations.' : \`Ran \${total} migration(s).\`);
56
+ process.exit(0);
57
+ })
58
+ .catch((error) => {
59
+ console.error('Migration failed:', error);
60
+ // Non-zero so CI and shell chains actually notice.
61
+ process.exit(1);
62
+ })`
63
+ : ''};
52
64
  `);
53
65
  return entryPath;
54
66
  };
@@ -1 +1 @@
1
- {"version":3,"file":"devServerEntry.js","sourceRoot":"","sources":["../../../src/lib/devServerEntry.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AAExB,2CAA8C;AAE9C,6EAA6E;AAC7E,8EAA8E;AAC9E,yEAAyE;AACzE,4EAA4E;AAC5E,4DAA4D;AACrD,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,OAAe,EAAU,EAAE;IAC3E,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IAElD,MAAM,aAAa,GAAG,cAAI,CAAC,IAAI,CAAC,IAAA,2BAAe,EAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,CAAC;IACjF,MAAM,eAAe,GAAG,YAAE,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,YAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,YAAE,CAAC,aAAa,CACd,SAAS,EACT;;;;;;;;EAQF,eAAe;;;;;;;;;mBASE,IAAI,CAAC,SAAS,CAAC,cAAI,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;;;;;;;;;;;;CAYpE,CACE,CAAC;IAEF,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AA7CW,QAAA,mBAAmB,uBA6C9B","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"]}
1
+ {"version":3,"file":"devServerEntry.js","sourceRoot":"","sources":["../../../src/lib/devServerEntry.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AACpB,gDAAwB;AAExB,2CAA8C;AAcvC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,OAA2B,OAAO,EAAU,EAAE;IAC/G,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAE5F,MAAM,aAAa,GAAG,cAAI,CAAC,IAAI,CAAC,IAAA,2BAAe,EAAC,OAAO,CAAC,EAAE,qBAAqB,CAAC,CAAC;IACjF,MAAM,eAAe,GAAG,YAAE,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,YAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5C,YAAE,CAAC,aAAa,CACd,SAAS,EACT;WACO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB;;;;;;;EAOhE,eAAe;;EAEf,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB;;;;;;;mBAOtC,IAAI,CAAC,SAAS,CAAC,cAAI,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;;;;;;;;;;;GAYnE,IAAI,KAAK,SAAS;QAChB,CAAC,CAAC;;;;;;;;;;;;;;;KAeD;QACD,CAAC,CAAC,EACN;CACC,CACE,CAAC;IAEF,OAAO,SAAS,CAAC;AACnB,CAAC,CAAC;AAhEW,QAAA,mBAAmB,uBAgE9B","sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { getAppDirectory } from './discovery';\n\n// TODO: This file is a mess, revisit and clean up.\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.\n// `startDevServer` runs forever; `runMigrations` does one pass and resolves. Both need the\n// identical bundle — same configs, same dynamic loader — because the whole value of running\n// migrations locally is that it rehearses the deployed path rather than a lookalike.\nexport type DevServerEntryMode = 'serve' | 'migrate';\n\nexport const writeDevServerEntry = (root: string, appName: string, mode: DevServerEntryMode = 'serve'): string => {\n const entryDir = path.join(root, 'dist', 'qpq', 'dev-server');\n const entryPath = path.join(entryDir, mode === 'migrate' ? 'migrate.entry.ts' : '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 { ${mode === 'migrate' ? 'runMigrations' : '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\n${mode === 'migrate' ? 'runMigrations' : 'startDevServer'}(\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 mode === 'migrate'\n ? `\n .then((ran) => {\n const total = Object.values(ran).reduce((sum, keys) => sum + keys.length, 0);\n for (const [service, keys] of Object.entries(ran)) {\n for (const key of keys) {\n console.log(\\` [\\${service}] \\${key}\\`);\n }\n }\n console.log(total === 0 ? 'No pending migrations.' : \\`Ran \\${total} migration(s).\\`);\n process.exit(0);\n })\n .catch((error) => {\n console.error('Migration failed:', error);\n // Non-zero so CI and shell chains actually notice.\n process.exit(1);\n })`\n : ''\n};\n`,\n );\n\n return entryPath;\n};\n"]}
@@ -43,6 +43,17 @@ export const cliCommands = [
43
43
  summary: 'Run every views microfrontend dev server',
44
44
  run: (a) => require('../commands/goDevWeb').goDevWebCommand(a),
45
45
  },
46
+ {
47
+ name: 'migrate',
48
+ usageArgs: '[app]',
49
+ summary: 'Run pending migrations against the LOCAL dev store, once, then exit.',
50
+ usageExtra: [
51
+ 'Deployed, migrations run on a stack update; nothing local ever deploys, so',
52
+ 'this is how you exercise one before shipping it. Records what it runs, so a',
53
+ 'second call is a no-op.',
54
+ ],
55
+ run: (a) => require('../commands/migrate').migrateCommand(a),
56
+ },
46
57
  {
47
58
  name: 'teardown',
48
59
  summary: 'Destroy web/api/inf stacks for selected services (interactive)',
@@ -1 +1 @@
1
- {"version":3,"file":"commandRegistry.js","sourceRoot":"","sources":["../../../src/cli/commandRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["// Single source of truth for every qpq command: its name, the one-line\n// summary (shared by `qpq help` and the interactive menu) and a runner.\n//\n// Command modules are required lazily inside each runner so a simple\n// `qpq help` doesn't pay for the whole toolchain, and so ts-node can be\n// registered before any command module (or anything it requires) loads.\nimport { promptText } from '../lib/prompts';\n\nexport type CliCommand = {\n name: string;\n // Argument hint shown in the usage text, e.g. '[svc] [stack]'.\n usageArgs?: string;\n summary: string;\n // Extra usage-only detail lines, indented under the summary.\n usageExtra?: string[];\n // Menu-only: commands that need a positional argument (which the menu has\n // no argv to carry) gather it here before running.\n promptArgs?: () => Promise<string[]>;\n run: (commandArgv: string[]) => Promise<void>;\n};\n\n// `qpq hooks` needs a hook name; when launched from the menu there is no\n// argv to pass it in, so ask.\nconst promptHookName = async (): Promise<string[]> => [\n await promptText('Hook name (runs qpq:<name>, or plain <name>, in every app/package that defines it):'),\n];\n\n/* eslint-disable @typescript-eslint/no-require-imports */\nexport const cliCommands: CliCommand[] = [\n {\n name: 'go',\n usageArgs: '[svc] [stack]',\n summary: 'Deploy services (interactive; platform from deploy.config.json, default aws).',\n usageExtra: [\n 'Pass positional args to skip prompts, e.g. qpq go all all — svc is',\n \"'all' | comma-list | account | domain | bootstrap; stack is all|inf|api|web|views.\",\n \"A full 'all all' (or 'all api') deploy also publishes federated backends.\",\n ],\n run: (a) => require('../commands/go').goCommand(a),\n },\n {\n name: 'go:docker',\n summary: 'Same as go (incl. positional args), but deploys in parallel via docker',\n run: (a) => require('../commands/goDocker').goDockerCommand(a),\n },\n {\n name: 'go:dev',\n summary: 'Run the full local dev stack (api + web) in one process',\n run: (a) => require('../commands/goDev').goDevCommand(a),\n },\n {\n name: 'go:dev:api',\n summary: 'Run the local API dev server (hot reload)',\n run: (a) => require('../commands/goDevApi').goDevApiCommand(a),\n },\n {\n name: 'go:dev:web',\n summary: 'Run every views microfrontend dev server',\n run: (a) => require('../commands/goDevWeb').goDevWebCommand(a),\n },\n {\n name: 'teardown',\n summary: 'Destroy web/api/inf stacks for selected services (interactive)',\n run: (a) => require('../commands/teardown').teardownCommand(a),\n },\n {\n name: 'clear-resources',\n summary: 'Empty selected buckets/tables (data only, stacks untouched; interactive)',\n run: (a) => require('../commands/clearResources').clearResourcesCommand(a),\n },\n {\n name: 'synth',\n usageArgs: '[service]',\n summary: 'Synth QPQ configs to dist/apps/<app>/infrastructure',\n run: (a) => require('../commands/synth').synthCommand(a),\n },\n {\n name: 'prep',\n summary: 'Regenerate apps/<app>/tsconfig.federated.json',\n run: (a) => require('../commands/prep').prepCommand(a),\n },\n {\n name: 'publish',\n summary: 'Build + upload + deploy federated remotes',\n run: (a) => require('../commands/publish').publishCommand(a),\n },\n {\n name: 'publish:build',\n summary: 'Build federated remotes locally',\n run: (a) => require('../commands/publish').publishBuildCommand(a),\n },\n {\n name: 'publish:upload',\n summary: 'Upload built version dirs (nothing goes live)',\n run: (a) => require('../commands/publish').publishUploadCommand(a),\n },\n {\n name: 'publish:deploy',\n summary: 'Flip manifests to the uploaded versions',\n run: (a) => require('../commands/publish').publishDeployCommand(a),\n },\n {\n name: 'check:circular',\n summary: 'Scan workspace sources for circular imports (fails on cycles; --warn to only report)',\n run: (a) => require('../commands/checkCircular').checkCircularCommand(a),\n },\n {\n name: 'hooks',\n usageArgs: '<name>',\n summary: 'Run qpq:<name> (or plain <name>) in every app/package that defines it (dependency ordered, parallel; --jobs=N)',\n promptArgs: promptHookName,\n run: (a) => require('../commands/hooks').hooksCommand(a),\n },\n];\n/* eslint-enable @typescript-eslint/no-require-imports */\n\n// Width of the \"name [args]\" column in the usage text.\nconst usageColumnWidth = 17;\n\nexport const buildUsage = (): string => {\n const commandLines = cliCommands.flatMap((command) => {\n const nameAndArgs = command.usageArgs ? `${command.name} ${command.usageArgs}` : command.name;\n const extraLines = (command.usageExtra ?? []).map((line) => ` ${''.padEnd(usageColumnWidth)}${line}`);\n return [` ${nameAndArgs.padEnd(usageColumnWidth)}${command.summary}`, ...extraLines];\n });\n\n return [\n 'qpq — build, dev and deploy orchestration for QPQ apps',\n '',\n 'Usage: qpq <command> [--app <name>] [--env <name>] [--platform <name>]',\n ' qpq (no command opens an interactive menu)',\n '',\n 'Commands:',\n ...commandLines,\n '',\n ].join('\\n');\n};\n"]}
1
+ {"version":3,"file":"commandRegistry.js","sourceRoot":"","sources":["../../../src/cli/commandRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["// Single source of truth for every qpq command: its name, the one-line\n// summary (shared by `qpq help` and the interactive menu) and a runner.\n//\n// Command modules are required lazily inside each runner so a simple\n// `qpq help` doesn't pay for the whole toolchain, and so ts-node can be\n// registered before any command module (or anything it requires) loads.\nimport { promptText } from '../lib/prompts';\n\nexport type CliCommand = {\n name: string;\n // Argument hint shown in the usage text, e.g. '[svc] [stack]'.\n usageArgs?: string;\n summary: string;\n // Extra usage-only detail lines, indented under the summary.\n usageExtra?: string[];\n // Menu-only: commands that need a positional argument (which the menu has\n // no argv to carry) gather it here before running.\n promptArgs?: () => Promise<string[]>;\n run: (commandArgv: string[]) => Promise<void>;\n};\n\n// `qpq hooks` needs a hook name; when launched from the menu there is no\n// argv to pass it in, so ask.\nconst promptHookName = async (): Promise<string[]> => [\n await promptText('Hook name (runs qpq:<name>, or plain <name>, in every app/package that defines it):'),\n];\n\n/* eslint-disable @typescript-eslint/no-require-imports */\nexport const cliCommands: CliCommand[] = [\n {\n name: 'go',\n usageArgs: '[svc] [stack]',\n summary: 'Deploy services (interactive; platform from deploy.config.json, default aws).',\n usageExtra: [\n 'Pass positional args to skip prompts, e.g. qpq go all all — svc is',\n \"'all' | comma-list | account | domain | bootstrap; stack is all|inf|api|web|views.\",\n \"A full 'all all' (or 'all api') deploy also publishes federated backends.\",\n ],\n run: (a) => require('../commands/go').goCommand(a),\n },\n {\n name: 'go:docker',\n summary: 'Same as go (incl. positional args), but deploys in parallel via docker',\n run: (a) => require('../commands/goDocker').goDockerCommand(a),\n },\n {\n name: 'go:dev',\n summary: 'Run the full local dev stack (api + web) in one process',\n run: (a) => require('../commands/goDev').goDevCommand(a),\n },\n {\n name: 'go:dev:api',\n summary: 'Run the local API dev server (hot reload)',\n run: (a) => require('../commands/goDevApi').goDevApiCommand(a),\n },\n {\n name: 'go:dev:web',\n summary: 'Run every views microfrontend dev server',\n run: (a) => require('../commands/goDevWeb').goDevWebCommand(a),\n },\n {\n name: 'migrate',\n usageArgs: '[app]',\n summary: 'Run pending migrations against the LOCAL dev store, once, then exit.',\n usageExtra: [\n 'Deployed, migrations run on a stack update; nothing local ever deploys, so',\n 'this is how you exercise one before shipping it. Records what it runs, so a',\n 'second call is a no-op.',\n ],\n run: (a) => require('../commands/migrate').migrateCommand(a),\n },\n {\n name: 'teardown',\n summary: 'Destroy web/api/inf stacks for selected services (interactive)',\n run: (a) => require('../commands/teardown').teardownCommand(a),\n },\n {\n name: 'clear-resources',\n summary: 'Empty selected buckets/tables (data only, stacks untouched; interactive)',\n run: (a) => require('../commands/clearResources').clearResourcesCommand(a),\n },\n {\n name: 'synth',\n usageArgs: '[service]',\n summary: 'Synth QPQ configs to dist/apps/<app>/infrastructure',\n run: (a) => require('../commands/synth').synthCommand(a),\n },\n {\n name: 'prep',\n summary: 'Regenerate apps/<app>/tsconfig.federated.json',\n run: (a) => require('../commands/prep').prepCommand(a),\n },\n {\n name: 'publish',\n summary: 'Build + upload + deploy federated remotes',\n run: (a) => require('../commands/publish').publishCommand(a),\n },\n {\n name: 'publish:build',\n summary: 'Build federated remotes locally',\n run: (a) => require('../commands/publish').publishBuildCommand(a),\n },\n {\n name: 'publish:upload',\n summary: 'Upload built version dirs (nothing goes live)',\n run: (a) => require('../commands/publish').publishUploadCommand(a),\n },\n {\n name: 'publish:deploy',\n summary: 'Flip manifests to the uploaded versions',\n run: (a) => require('../commands/publish').publishDeployCommand(a),\n },\n {\n name: 'check:circular',\n summary: 'Scan workspace sources for circular imports (fails on cycles; --warn to only report)',\n run: (a) => require('../commands/checkCircular').checkCircularCommand(a),\n },\n {\n name: 'hooks',\n usageArgs: '<name>',\n summary: 'Run qpq:<name> (or plain <name>) in every app/package that defines it (dependency ordered, parallel; --jobs=N)',\n promptArgs: promptHookName,\n run: (a) => require('../commands/hooks').hooksCommand(a),\n },\n];\n/* eslint-enable @typescript-eslint/no-require-imports */\n\n// Width of the \"name [args]\" column in the usage text.\nconst usageColumnWidth = 17;\n\nexport const buildUsage = (): string => {\n const commandLines = cliCommands.flatMap((command) => {\n const nameAndArgs = command.usageArgs ? `${command.name} ${command.usageArgs}` : command.name;\n const extraLines = (command.usageExtra ?? []).map((line) => ` ${''.padEnd(usageColumnWidth)}${line}`);\n return [` ${nameAndArgs.padEnd(usageColumnWidth)}${command.summary}`, ...extraLines];\n });\n\n return [\n 'qpq — build, dev and deploy orchestration for QPQ apps',\n '',\n 'Usage: qpq <command> [--app <name>] [--env <name>] [--platform <name>]',\n ' qpq (no command opens an interactive menu)',\n '',\n 'Commands:',\n ...commandLines,\n '',\n ].join('\\n');\n};\n"]}
@@ -0,0 +1 @@
1
+ export declare const migrateCommand: (argv: string[]) => Promise<void>;
@@ -0,0 +1,57 @@
1
+ // `qpq migrate` — run every pending migration against the LOCAL dev store, once, then exit.
2
+ //
3
+ // Deployed, migrations are triggered by a stack update. Nothing locally ever deploys, so
4
+ // without this a migration is first executed in a real environment, which is a poor place to
5
+ // discover what it does. This bundles exactly what `go:dev:api` bundles and runs the same
6
+ // queue path, so a local run rehearses the deployed one rather than a convenient lookalike.
7
+ //
8
+ // Migrations are recorded as they run, in the same store the deploy path uses, so running
9
+ // this twice is a no-op the second time.
10
+ import { getAppServiceQpqConfigs, getDevServerRspackConfig } from 'quidproquo-deploy-rspack';
11
+ import { spawn } from 'child_process';
12
+ import path from 'path';
13
+ import { rspack } from '@rspack/core';
14
+ import { primeDeployEnvFromConfig } from '../lib/deployEnv';
15
+ import { writeDevServerEntry } from '../lib/devServerEntry';
16
+ import { getRoot } from '../lib/discovery';
17
+ import { resolveAppSelection } from '../lib/resolveAppSelection';
18
+ const MIGRATE_BUNDLE_PATH = path.join('dist', 'qpq', 'dev-server', 'migrate.js');
19
+ export const migrateCommand = async (argv) => {
20
+ const root = getRoot();
21
+ const appName = await resolveAppSelection({ argv, envVar: 'QPQ_DEV_APP' });
22
+ process.env.QPQ_DEV_APP = appName;
23
+ primeDeployEnvFromConfig(appName);
24
+ console.log(`Running local migrations for app [${appName}]`);
25
+ const qpqConfigs = getAppServiceQpqConfigs(root, appName);
26
+ const entry = writeDevServerEntry(root, appName, 'migrate');
27
+ const rspackConfig = getDevServerRspackConfig({ root, entry, qpqConfigs });
28
+ // Same bundle shape as the dev server, different output name so a running `go:dev:api`
29
+ // watcher does not fight over the file.
30
+ const bundlePath = path.join(root, MIGRATE_BUNDLE_PATH);
31
+ const migrateConfig = {
32
+ ...rspackConfig,
33
+ output: { ...rspackConfig.output, filename: 'migrate.js' },
34
+ };
35
+ await new Promise((resolve, reject) => {
36
+ rspack(migrateConfig, (error, stats) => {
37
+ if (error) {
38
+ return reject(error);
39
+ }
40
+ if (stats?.hasErrors()) {
41
+ console.error(stats.toString({ colors: true, all: false, errors: true }));
42
+ return reject(new Error('Failed to bundle migrations'));
43
+ }
44
+ resolve();
45
+ });
46
+ });
47
+ // The bundle exits non-zero on failure; mirror that so a shell chain or CI notices instead
48
+ // of carrying on as though the data were migrated.
49
+ const exitCode = await new Promise((resolve) => {
50
+ const child = spawn('node', [bundlePath], { stdio: 'inherit', env: { ...process.env, QPQ_DEV_APP: appName } });
51
+ child.on('exit', (code) => resolve(code ?? 1));
52
+ });
53
+ if (exitCode !== 0) {
54
+ process.exitCode = exitCode;
55
+ }
56
+ };
57
+ //# sourceMappingURL=migrate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate.js","sourceRoot":"","sources":["../../../src/commands/migrate.ts"],"names":[],"mappings":"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","sourcesContent":["// `qpq migrate` — run every pending migration against the LOCAL dev store, once, then exit.\n//\n// Deployed, migrations are triggered by a stack update. Nothing locally ever deploys, so\n// without this a migration is first executed in a real environment, which is a poor place to\n// discover what it does. This bundles exactly what `go:dev:api` bundles and runs the same\n// queue path, so a local run rehearses the deployed one rather than a convenient lookalike.\n//\n// Migrations are recorded as they run, in the same store the deploy path uses, so running\n// this twice is a no-op the second time.\nimport { getAppServiceQpqConfigs, getDevServerRspackConfig } from 'quidproquo-deploy-rspack';\n\nimport { spawn } from 'child_process';\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 { resolveAppSelection } from '../lib/resolveAppSelection';\n\nconst MIGRATE_BUNDLE_PATH = path.join('dist', 'qpq', 'dev-server', 'migrate.js');\n\nexport const migrateCommand = 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\n console.log(`Running local migrations for app [${appName}]`);\n\n const qpqConfigs = getAppServiceQpqConfigs(root, appName);\n const entry = writeDevServerEntry(root, appName, 'migrate');\n const rspackConfig = getDevServerRspackConfig({ root, entry, qpqConfigs });\n\n // Same bundle shape as the dev server, different output name so a running `go:dev:api`\n // watcher does not fight over the file.\n const bundlePath = path.join(root, MIGRATE_BUNDLE_PATH);\n const migrateConfig = {\n ...rspackConfig,\n output: { ...rspackConfig.output, filename: 'migrate.js' },\n };\n\n await new Promise<void>((resolve, reject) => {\n rspack(migrateConfig as any, (error, stats) => {\n if (error) {\n return reject(error);\n }\n\n if (stats?.hasErrors()) {\n console.error(stats.toString({ colors: true, all: false, errors: true }));\n return reject(new Error('Failed to bundle migrations'));\n }\n\n resolve();\n });\n });\n\n // The bundle exits non-zero on failure; mirror that so a shell chain or CI notices instead\n // of carrying on as though the data were migrated.\n const exitCode = await new Promise<number>((resolve) => {\n const child = spawn('node', [bundlePath], { stdio: 'inherit', env: { ...process.env, QPQ_DEV_APP: appName } });\n child.on('exit', (code) => resolve(code ?? 1));\n });\n\n if (exitCode !== 0) {\n process.exitCode = exitCode;\n }\n};\n"]}
@@ -1 +1,2 @@
1
- export declare const writeDevServerEntry: (root: string, appName: string) => string;
1
+ export type DevServerEntryMode = 'serve' | 'migrate';
2
+ export declare const writeDevServerEntry: (root: string, appName: string, mode?: DevServerEntryMode) => string;
@@ -1,21 +1,16 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { getAppDirectory } from './discovery';
4
- // The dev-server bootstrap, generated per build so it can import an optional
5
- // app-owned overrides file (apps/<app>/devServer.config.ts, default-exporting
6
- // DevServerConfigOverrides). The `quidproquo-dynamic-loader` import is a
7
- // virtual module injected by QpqPlugin at bundle time. QPQ_WEB_ROOT (set in
8
- // the docker platform image) turns on static views serving.
9
- export const writeDevServerEntry = (root, appName) => {
4
+ export const writeDevServerEntry = (root, appName, mode = 'serve') => {
10
5
  const entryDir = path.join(root, 'dist', 'qpq', 'dev-server');
11
- const entryPath = path.join(entryDir, 'entry.ts');
6
+ const entryPath = path.join(entryDir, mode === 'migrate' ? 'migrate.entry.ts' : 'entry.ts');
12
7
  const overridesPath = path.join(getAppDirectory(appName), 'devServer.config.ts');
13
8
  const overridesImport = fs.existsSync(overridesPath)
14
9
  ? `import devServerConfigOverrides from ${JSON.stringify(overridesPath.replace(/\.ts$/, ''))};`
15
10
  : 'const devServerConfigOverrides = {};';
16
11
  fs.mkdirSync(entryDir, { recursive: true });
17
12
  fs.writeFileSync(entryPath, `// GENERATED by qpq — do not edit.
18
- import { startDevServer } from 'quidproquo-dev-server';
13
+ import { ${mode === 'migrate' ? 'runMigrations' : 'startDevServer'} } from 'quidproquo-dev-server';
19
14
  import {
20
15
  qpqConfigs,
21
16
  qpqDynamicModuleLoaderForService,
@@ -24,7 +19,7 @@ import {
24
19
 
25
20
  ${overridesImport}
26
21
 
27
- startDevServer(
22
+ ${mode === 'migrate' ? 'runMigrations' : 'startDevServer'}(
28
23
  {
29
24
  serverDomain: 'localhost',
30
25
  serverPort: 8080,
@@ -42,7 +37,24 @@ startDevServer(
42
37
  delay: { default: 0 },
43
38
  },
44
39
  devServerConfigOverrides,
45
- );
40
+ )${mode === 'migrate'
41
+ ? `
42
+ .then((ran) => {
43
+ const total = Object.values(ran).reduce((sum, keys) => sum + keys.length, 0);
44
+ for (const [service, keys] of Object.entries(ran)) {
45
+ for (const key of keys) {
46
+ console.log(\` [\${service}] \${key}\`);
47
+ }
48
+ }
49
+ console.log(total === 0 ? 'No pending migrations.' : \`Ran \${total} migration(s).\`);
50
+ process.exit(0);
51
+ })
52
+ .catch((error) => {
53
+ console.error('Migration failed:', error);
54
+ // Non-zero so CI and shell chains actually notice.
55
+ process.exit(1);
56
+ })`
57
+ : ''};
46
58
  `);
47
59
  return entryPath;
48
60
  };
@@ -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;;;;;;;;;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"]}
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;AAc9C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,IAAY,EAAE,OAAe,EAAE,OAA2B,OAAO,EAAU,EAAE;IAC/G,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,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;IAE5F,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;WACO,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB;;;;;;;EAOhE,eAAe;;EAEf,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC,gBAAgB;;;;;;;mBAOtC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC;;;;;;;;;;;GAYnE,IAAI,KAAK,SAAS;QAChB,CAAC,CAAC;;;;;;;;;;;;;;;KAeD;QACD,CAAC,CAAC,EACN;CACC,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// TODO: This file is a mess, revisit and clean up.\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.\n// `startDevServer` runs forever; `runMigrations` does one pass and resolves. Both need the\n// identical bundle — same configs, same dynamic loader — because the whole value of running\n// migrations locally is that it rehearses the deployed path rather than a lookalike.\nexport type DevServerEntryMode = 'serve' | 'migrate';\n\nexport const writeDevServerEntry = (root: string, appName: string, mode: DevServerEntryMode = 'serve'): string => {\n const entryDir = path.join(root, 'dist', 'qpq', 'dev-server');\n const entryPath = path.join(entryDir, mode === 'migrate' ? 'migrate.entry.ts' : '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 { ${mode === 'migrate' ? 'runMigrations' : '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\n${mode === 'migrate' ? 'runMigrations' : 'startDevServer'}(\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 mode === 'migrate'\n ? `\n .then((ran) => {\n const total = Object.values(ran).reduce((sum, keys) => sum + keys.length, 0);\n for (const [service, keys] of Object.entries(ran)) {\n for (const key of keys) {\n console.log(\\` [\\${service}] \\${key}\\`);\n }\n }\n console.log(total === 0 ? 'No pending migrations.' : \\`Ran \\${total} migration(s).\\`);\n process.exit(0);\n })\n .catch((error) => {\n console.error('Migration failed:', error);\n // Non-zero so CI and shell chains actually notice.\n process.exit(1);\n })`\n : ''\n};\n`,\n );\n\n return entryPath;\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "quidproquo-cli",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "The qpq CLI — build, dev and deploy orchestration for QPQ apps",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -41,21 +41,21 @@
41
41
  "@rspack/dev-server": "^2.1.0",
42
42
  "aws-cdk": "^2.1132.1",
43
43
  "madge": "^8.0.0",
44
- "quidproquo": "0.1.12",
45
- "quidproquo-actionprocessor-awslambda": "0.1.12",
46
- "quidproquo-config-aws": "0.1.12",
47
- "quidproquo-core": "0.1.12",
48
- "quidproquo-deploy-awscdk": "0.1.12",
49
- "quidproquo-deploy-rspack": "0.1.12",
50
- "quidproquo-dev-server": "0.1.12",
51
- "quidproquo-webserver": "0.1.12",
44
+ "quidproquo": "0.1.14",
45
+ "quidproquo-actionprocessor-awslambda": "0.1.14",
46
+ "quidproquo-config-aws": "0.1.14",
47
+ "quidproquo-core": "0.1.14",
48
+ "quidproquo-deploy-awscdk": "0.1.14",
49
+ "quidproquo-deploy-rspack": "0.1.14",
50
+ "quidproquo-dev-server": "0.1.14",
51
+ "quidproquo-webserver": "0.1.14",
52
52
  "ts-node": "^10.9.1",
53
53
  "typescript": "^5.8.2"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@rspack/core": "^2.1.2",
57
57
  "@types/node": "^22.13.13",
58
- "quidproquo-tsconfig": "0.1.12"
58
+ "quidproquo-tsconfig": "0.1.14"
59
59
  },
60
60
  "bin": {
61
61
  "qpq": "./lib/commonjs/bin/qpq.js"