create-qpq-app 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/lib/commonjs/steps/006_applyDomain.js +3 -8
  2. package/lib/commonjs/steps/006_applyDomain.js.map +1 -1
  3. package/lib/commonjs/steps/013_printNextSteps.js +1 -1
  4. package/lib/commonjs/steps/013_printNextSteps.js.map +1 -1
  5. package/lib/esm/steps/006_applyDomain.js +3 -8
  6. package/lib/esm/steps/006_applyDomain.js.map +1 -1
  7. package/lib/esm/steps/013_printNextSteps.js +1 -1
  8. package/lib/esm/steps/013_printNextSteps.js.map +1 -1
  9. package/package.json +2 -2
  10. package/template/README.md +6 -3
  11. package/template/apps/qpqjs/account.qpq.ts +5 -13
  12. package/template/apps/qpqjs/bootstrap.qpq.ts +29 -14
  13. package/template/apps/qpqjs/deploy.config.json +0 -1
  14. package/template/apps/qpqjs/packages/constants/src/domain.ts +3 -1
  15. package/template/apps/qpqjs/packages/service-utils/src/defineQpqjsService.ts +15 -9
  16. package/template/apps/qpqjs/services/design/views/src/components/LandingPage/components/Infrastructure.tsx +1 -2
  17. package/template/apps/qpqjs/services/shell/service/src/infrastructure.ts +1 -4
  18. package/template/apps/qpqjs/services/test/models/package.json +2 -1
  19. package/template/apps/qpqjs/services/test/models/src/EchoRequest.ts +8 -0
  20. package/template/apps/qpqjs/services/test/models/src/EchoResponse.ts +9 -0
  21. package/template/apps/qpqjs/services/test/models/src/SmokeRun.ts +11 -9
  22. package/template/apps/qpqjs/services/test/models/src/SmokeRunStarted.ts +8 -0
  23. package/template/apps/qpqjs/services/test/models/src/SmokeRunSummary.ts +10 -6
  24. package/template/apps/qpqjs/services/test/models/src/SmokeRunWithSummary.ts +9 -5
  25. package/template/apps/qpqjs/services/test/models/src/SmokeTestResult.ts +11 -9
  26. package/template/apps/qpqjs/services/test/models/src/index.ts +3 -0
  27. package/template/apps/qpqjs/services/test/service/src/echo/config/defineEcho.ts +9 -0
  28. package/template/apps/qpqjs/services/test/service/src/echo/controller/echo.ts +32 -0
  29. package/template/apps/qpqjs/services/test/service/src/echo/controller/index.ts +1 -0
  30. package/template/apps/qpqjs/services/test/service/src/infrastructure.ts +3 -0
  31. package/template/apps/qpqjs/services/test/service/src/smoke/config/defineSmoke.ts +83 -18
  32. package/template/apps/qpqjs/services/test/service/src/smoke/constants/index.ts +1 -0
  33. package/template/apps/qpqjs/services/test/service/src/smoke/constants/smokeEventDoc.ts +27 -0
  34. package/template/apps/qpqjs/services/test/service/src/smoke/constants/smokeRunQueue.ts +4 -4
  35. package/template/apps/qpqjs/services/test/service/src/smoke/controller/askRunSmokeTests.ts +5 -1
  36. package/template/apps/qpqjs/services/test/service/src/smoke/data/askRecordSmokeTestResult.ts +32 -0
  37. package/template/apps/qpqjs/services/test/service/src/smoke/data/askSetSmokeRunOutcome.ts +24 -0
  38. package/template/apps/qpqjs/services/test/service/src/smoke/data/index.ts +2 -0
  39. package/template/apps/qpqjs/services/test/service/src/smoke/eventDoc/index.ts +1 -0
  40. package/template/apps/qpqjs/services/test/service/src/smoke/eventDoc/smokeProbeDocDefinition.ts +74 -0
  41. package/template/apps/qpqjs/services/test/service/src/smoke/index.ts +1 -0
  42. package/template/apps/qpqjs/services/test/service/src/smoke/logic/smokeRun/askExecuteSmokeTest.ts +97 -0
  43. package/template/apps/qpqjs/services/test/service/src/smoke/logic/smokeRun/askStartSmokeRun.ts +18 -10
  44. package/template/apps/qpqjs/services/test/service/src/smoke/logic/smokeRun/index.ts +1 -1
  45. package/template/apps/qpqjs/services/test/service/src/smoke/models/SmokeEventDocAppendQueueEvent.ts +15 -0
  46. package/template/apps/qpqjs/services/test/service/src/smoke/models/SmokeEventDocMark.ts +8 -0
  47. package/template/apps/qpqjs/services/test/service/src/smoke/models/SmokeTestRequestedQueueEvent.ts +12 -0
  48. package/template/apps/qpqjs/services/test/service/src/smoke/models/index.ts +3 -1
  49. package/template/apps/qpqjs/services/test/service/src/smoke/queue/index.ts +2 -1
  50. package/template/apps/qpqjs/services/test/service/src/smoke/queue/onSmokeEventDocAppend.ts +64 -0
  51. package/template/apps/qpqjs/services/test/service/src/smoke/queue/onSmokeTestRequested.ts +17 -0
  52. package/template/apps/qpqjs/services/test/service/src/smoke/tests/eventDoc/askAssertContiguousEventIds.ts +18 -0
  53. package/template/apps/qpqjs/services/test/service/src/smoke/tests/eventDoc/askAssertSmokeProbeDocState.ts +32 -0
  54. package/template/apps/qpqjs/services/test/service/src/smoke/tests/eventDoc/askAwaitSmokeEventDocLog.ts +35 -0
  55. package/template/apps/qpqjs/services/test/service/src/smoke/tests/eventDoc/askRunEventDocConcurrentAppendTest.ts +82 -0
  56. package/template/apps/qpqjs/services/test/service/src/smoke/tests/eventDoc/askRunEventDocInterleavedBatchAppendTest.ts +110 -0
  57. package/template/apps/qpqjs/services/test/service/src/smoke/tests/eventDoc/index.ts +5 -0
  58. package/template/apps/qpqjs/services/test/service/src/smoke/tests/index.ts +2 -0
  59. package/template/apps/qpqjs/services/test/service/src/smoke/tests/openApi/askRunEchoRoundTripTest.ts +45 -0
  60. package/template/apps/qpqjs/services/test/service/src/smoke/tests/openApi/askRunOpenApiDocumentTest.ts +42 -0
  61. package/template/apps/qpqjs/services/test/service/src/smoke/tests/openApi/index.ts +2 -0
  62. package/template/apps/qpqjs/services/test/service/src/smoke/tests/smokeTestRegistry.ts +18 -2
  63. package/template/apps/todo/deploy.config.json +0 -1
  64. package/template/apps/todo/packages/constants/src/domain.ts +2 -1
  65. package/template/apps/todo/packages/service-utils/src/defineTodoService.ts +5 -6
  66. package/template/apps/todo/services/shell/service/src/infrastructure.ts +1 -3
  67. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-upsert-with-retry.md +2 -2
  68. package/template/docusaurus/docs/actions/core/key-value-store/ask-key-value-store-upsert.md +2 -1
  69. package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-append-server-event.md +1 -1
  70. package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-create.md +1 -1
  71. package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-event-append.md +18 -12
  72. package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-event-list.md +4 -4
  73. package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-event-write.md +6 -6
  74. package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-get-by-id.md +2 -2
  75. package/template/docusaurus/docs/actions/features/event-doc/ask-event-doc-get-draft.md +2 -2
  76. package/template/docusaurus/docs/actions/webserver/dns/ask-dns-list.md +9 -8
  77. package/template/docusaurus/docs/actions/webserver/dns/ask-dns-resolve-hosts.md +42 -0
  78. package/template/docusaurus/docs/actions/webserver/open-api/_category_.json +1 -0
  79. package/template/docusaurus/docs/actions/webserver/open-api/ask-open-api-get-document.md +41 -0
  80. package/template/docusaurus/docs/config/config-aws/account-github-oidc-provider.md +31 -0
  81. package/template/docusaurus/docs/config/config-aws/domain-certificate.md +16 -32
  82. package/template/docusaurus/docs/config/config-aws/email-sender-allow-list.md +7 -14
  83. package/template/docusaurus/docs/config/config-aws/github-deploy-role.md +68 -0
  84. package/template/docusaurus/docs/config/core/user-directory.md +2 -3
  85. package/template/docusaurus/docs/config/features/admin-settings.md +2 -6
  86. package/template/docusaurus/docs/config/features/event-doc-summary.md +2 -2
  87. package/template/docusaurus/docs/config/features/open-api-routes.md +82 -0
  88. package/template/docusaurus/docs/config/features/tenanted-web-socket-queue.md +2 -3
  89. package/template/docusaurus/docs/config/features/web-socket-queue.md +1 -5
  90. package/template/docusaurus/docs/config/webserver/api.md +6 -9
  91. package/template/docusaurus/docs/config/webserver/cache.md +1 -1
  92. package/template/docusaurus/docs/config/webserver/dns.md +25 -17
  93. package/template/docusaurus/docs/config/webserver/domain-proxy.md +1 -5
  94. package/template/docusaurus/docs/config/webserver/email-sender.md +9 -15
  95. package/template/docusaurus/docs/config/webserver/route.md +1 -0
  96. package/template/docusaurus/docs/config/webserver/seo.md +1 -1
  97. package/template/docusaurus/docs/config/webserver/web-entry.md +0 -5
  98. package/template/docusaurus/docs/config/webserver/websocket.md +3 -7
  99. package/template/docusaurus/docs/domains.md +76 -0
  100. package/template/docusaurus/docs/use-cases.md +0 -6
  101. package/template/docusaurus/docusaurus.config.ts +38 -14
  102. package/template/docusaurus/sidebars.ts +5 -5
  103. package/template/docusaurus/src/components/HomepageFeatures/index.tsx +2 -2
  104. package/template/scripts/deployedSmoke.mjs +82 -23
  105. package/template/scripts/smoke/runEdgeChecks.mjs +152 -0
  106. package/template/scripts/smoke/runSmokeRun.mjs +1 -1
  107. package/template/apps/qpqjs/services/test/service/src/smoke/logic/smokeRun/askExecuteSmokeRun.ts +0 -83
  108. package/template/apps/qpqjs/services/test/service/src/smoke/models/SmokeRunRequestedQueueEvent.ts +0 -9
  109. package/template/apps/qpqjs/services/test/service/src/smoke/queue/onSmokeRunRequested.ts +0 -15
  110. package/template/docusaurus/docs/actions/webserver/open-api-spec/_category_.json +0 -1
  111. package/template/docusaurus/docs/actions/webserver/open-api-spec/ask-get-open-api-spec.md +0 -43
  112. package/template/docusaurus/docs/config/webserver/open-api.md +0 -36
@@ -14,19 +14,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.applyDomain = void 0;
16
16
  const path_1 = __importDefault(require("path"));
17
- const readJsonFile_1 = require("../lib/readJsonFile");
18
17
  const replaceInFileExact_1 = require("../lib/replaceInFileExact");
19
- const writeJsonFile_1 = require("../lib/writeJsonFile");
20
- // The template's domain, replaced wherever it's authoritative: the deploy
21
- // config and the app's domain constant (everything else derives from those).
18
+ // The template's domain, replaced where it's authoritative: the app's domain
19
+ // constant (everything else derives from it). One root at scaffold time; more
20
+ // can be appended later.
22
21
  const TEMPLATE_DOMAIN = 'todo.quidproquojs.com';
23
22
  exports.applyDomain = {
24
23
  name: 'Applying domain',
25
24
  run: (_a) => __awaiter(void 0, [_a], void 0, function* ({ targetDirectory, answers }) {
26
- const deployConfigPath = path_1.default.join(targetDirectory, 'apps', answers.appName, 'deploy.config.json');
27
- const deployConfig = (0, readJsonFile_1.readJsonFile)(deployConfigPath);
28
- deployConfig.domain = answers.domain;
29
- (0, writeJsonFile_1.writeJsonFile)(deployConfigPath, deployConfig);
30
25
  (0, replaceInFileExact_1.replaceInFileExact)(path_1.default.join(targetDirectory, 'apps', answers.appName, 'packages', 'constants', 'src', 'domain.ts'), TEMPLATE_DOMAIN, answers.domain);
31
26
  }),
32
27
  };
@@ -1 +1 @@
1
- {"version":3,"file":"006_applyDomain.js","sourceRoot":"","sources":["../../../src/steps/006_applyDomain.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,gDAAwB;AAExB,sDAAmD;AACnD,kEAA+D;AAC/D,wDAAqD;AAGrD,0EAA0E;AAC1E,6EAA6E;AAC7E,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEnC,QAAA,WAAW,GAAqB;IAC3C,IAAI,EAAE,iBAAiB;IAEvB,GAAG,EAAE,KAAqC,EAAE,4CAAhC,EAAE,eAAe,EAAE,OAAO,EAAE;QACtC,MAAM,gBAAgB,GAAG,cAAI,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;QACnG,MAAM,YAAY,GAAG,IAAA,2BAAY,EAAC,gBAAgB,CAAC,CAAC;QACpD,YAAY,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QACrC,IAAA,6BAAa,EAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC;QAE9C,IAAA,uCAAkB,EAChB,cAAI,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,EAChG,eAAe,EACf,OAAO,CAAC,MAAM,CACf,CAAC;IACJ,CAAC,CAAA;CACF,CAAC","sourcesContent":["import path from 'path';\n\nimport { readJsonFile } from '../lib/readJsonFile';\nimport { replaceInFileExact } from '../lib/replaceInFileExact';\nimport { writeJsonFile } from '../lib/writeJsonFile';\nimport { CreateQpqAppStep } from '../types';\n\n// The template's domain, replaced wherever it's authoritative: the deploy\n// config and the app's domain constant (everything else derives from those).\nconst TEMPLATE_DOMAIN = 'todo.quidproquojs.com';\n\nexport const applyDomain: CreateQpqAppStep = {\n name: 'Applying domain',\n\n run: async ({ targetDirectory, answers }) => {\n const deployConfigPath = path.join(targetDirectory, 'apps', answers.appName, 'deploy.config.json');\n const deployConfig = readJsonFile(deployConfigPath);\n deployConfig.domain = answers.domain;\n writeJsonFile(deployConfigPath, deployConfig);\n\n replaceInFileExact(\n path.join(targetDirectory, 'apps', answers.appName, 'packages', 'constants', 'src', 'domain.ts'),\n TEMPLATE_DOMAIN,\n answers.domain,\n );\n },\n};\n"]}
1
+ {"version":3,"file":"006_applyDomain.js","sourceRoot":"","sources":["../../../src/steps/006_applyDomain.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,gDAAwB;AAExB,kEAA+D;AAG/D,6EAA6E;AAC7E,8EAA8E;AAC9E,yBAAyB;AACzB,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEnC,QAAA,WAAW,GAAqB;IAC3C,IAAI,EAAE,iBAAiB;IAEvB,GAAG,EAAE,KAAqC,EAAE,4CAAhC,EAAE,eAAe,EAAE,OAAO,EAAE;QACtC,IAAA,uCAAkB,EAChB,cAAI,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,EAChG,eAAe,EACf,OAAO,CAAC,MAAM,CACf,CAAC;IACJ,CAAC,CAAA;CACF,CAAC","sourcesContent":["import path from 'path';\n\nimport { replaceInFileExact } from '../lib/replaceInFileExact';\nimport { CreateQpqAppStep } from '../types';\n\n// The template's domain, replaced where it's authoritative: the app's domain\n// constant (everything else derives from it). One root at scaffold time; more\n// can be appended later.\nconst TEMPLATE_DOMAIN = 'todo.quidproquojs.com';\n\nexport const applyDomain: CreateQpqAppStep = {\n name: 'Applying domain',\n\n run: async ({ targetDirectory, answers }) => {\n replaceInFileExact(\n path.join(targetDirectory, 'apps', answers.appName, 'packages', 'constants', 'src', 'domain.ts'),\n TEMPLATE_DOMAIN,\n answers.domain,\n );\n },\n};\n"]}
@@ -26,7 +26,7 @@ Next steps:
26
26
  ${install} npm run dev # api on http://localhost:8080, web on http://localhost:3080
27
27
  npm run deploy # build the docker image (then run the printed docker command)
28
28
 
29
- Deploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).
29
+ Deploy config lives in apps/${answers.appName}/deploy.config.json; the domain (${answers.domain}) lives in the app's constants package.
30
30
  `);
31
31
  }),
32
32
  };
@@ -1 +1 @@
1
- {"version":3,"file":"013_printNextSteps.js","sourceRoot":"","sources":["../../../src/steps/013_printNextSteps.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEa,QAAA,cAAc,GAAqB;IAC9C,IAAI,EAAE,MAAM;IAEZ,GAAG,EAAE,KAAoB,EAAE,4CAAf,EAAE,OAAO,EAAE;QACrB,MAAM,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kCAAkC,CAAC;QAEtF,OAAO,CAAC,GAAG,CAAC;UACN,OAAO,CAAC,OAAO;;;;;;;OAOlB,OAAO,CAAC,OAAO;EACpB,OAAO;;;8BAGqB,OAAO,CAAC,OAAO,gCAAgC,OAAO,CAAC,MAAM;CAC1F,CAAC,CAAC;IACD,CAAC,CAAA;CACF,CAAC","sourcesContent":["import { CreateQpqAppStep } from '../types';\n\nexport const printNextSteps: CreateQpqAppStep = {\n name: 'Done',\n\n run: async ({ answers }) => {\n const install = answers.installDependencies ? '' : ' npm install\\n npm run build\\n';\n\n console.log(`\nCreated ${answers.appName}!\n\nYour app has five services (admin, auth, design, shell and todo) and\ndeploys as a single docker image.\n\nNext steps:\n\n cd ${answers.appName}\n${install} npm run dev # api on http://localhost:8080, web on http://localhost:3080\n npm run deploy # build the docker image (then run the printed docker command)\n\nDeploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).\n`);\n },\n};\n"]}
1
+ {"version":3,"file":"013_printNextSteps.js","sourceRoot":"","sources":["../../../src/steps/013_printNextSteps.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEa,QAAA,cAAc,GAAqB;IAC9C,IAAI,EAAE,MAAM;IAEZ,GAAG,EAAE,KAAoB,EAAE,4CAAf,EAAE,OAAO,EAAE;QACrB,MAAM,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kCAAkC,CAAC;QAEtF,OAAO,CAAC,GAAG,CAAC;UACN,OAAO,CAAC,OAAO;;;;;;;OAOlB,OAAO,CAAC,OAAO;EACpB,OAAO;;;8BAGqB,OAAO,CAAC,OAAO,oCAAoC,OAAO,CAAC,MAAM;CAC9F,CAAC,CAAC;IACD,CAAC,CAAA;CACF,CAAC","sourcesContent":["import { CreateQpqAppStep } from '../types';\n\nexport const printNextSteps: CreateQpqAppStep = {\n name: 'Done',\n\n run: async ({ answers }) => {\n const install = answers.installDependencies ? '' : ' npm install\\n npm run build\\n';\n\n console.log(`\nCreated ${answers.appName}!\n\nYour app has five services (admin, auth, design, shell and todo) and\ndeploys as a single docker image.\n\nNext steps:\n\n cd ${answers.appName}\n${install} npm run dev # api on http://localhost:8080, web on http://localhost:3080\n npm run deploy # build the docker image (then run the printed docker command)\n\nDeploy config lives in apps/${answers.appName}/deploy.config.json; the domain (${answers.domain}) lives in the app's constants package.\n`);\n },\n};\n"]}
@@ -1,17 +1,12 @@
1
1
  import path from 'path';
2
- import { readJsonFile } from '../lib/readJsonFile';
3
2
  import { replaceInFileExact } from '../lib/replaceInFileExact';
4
- import { writeJsonFile } from '../lib/writeJsonFile';
5
- // The template's domain, replaced wherever it's authoritative: the deploy
6
- // config and the app's domain constant (everything else derives from those).
3
+ // The template's domain, replaced where it's authoritative: the app's domain
4
+ // constant (everything else derives from it). One root at scaffold time; more
5
+ // can be appended later.
7
6
  const TEMPLATE_DOMAIN = 'todo.quidproquojs.com';
8
7
  export const applyDomain = {
9
8
  name: 'Applying domain',
10
9
  run: async ({ targetDirectory, answers }) => {
11
- const deployConfigPath = path.join(targetDirectory, 'apps', answers.appName, 'deploy.config.json');
12
- const deployConfig = readJsonFile(deployConfigPath);
13
- deployConfig.domain = answers.domain;
14
- writeJsonFile(deployConfigPath, deployConfig);
15
10
  replaceInFileExact(path.join(targetDirectory, 'apps', answers.appName, 'packages', 'constants', 'src', 'domain.ts'), TEMPLATE_DOMAIN, answers.domain);
16
11
  },
17
12
  };
@@ -1 +1 @@
1
- {"version":3,"file":"006_applyDomain.js","sourceRoot":"","sources":["../../../src/steps/006_applyDomain.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,0EAA0E;AAC1E,6EAA6E;AAC7E,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD,MAAM,CAAC,MAAM,WAAW,GAAqB;IAC3C,IAAI,EAAE,iBAAiB;IAEvB,GAAG,EAAE,KAAK,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE,EAAE;QAC1C,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,oBAAoB,CAAC,CAAC;QACnG,MAAM,YAAY,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;QACpD,YAAY,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QACrC,aAAa,CAAC,gBAAgB,EAAE,YAAY,CAAC,CAAC;QAE9C,kBAAkB,CAChB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,EAChG,eAAe,EACf,OAAO,CAAC,MAAM,CACf,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import path from 'path';\n\nimport { readJsonFile } from '../lib/readJsonFile';\nimport { replaceInFileExact } from '../lib/replaceInFileExact';\nimport { writeJsonFile } from '../lib/writeJsonFile';\nimport { CreateQpqAppStep } from '../types';\n\n// The template's domain, replaced wherever it's authoritative: the deploy\n// config and the app's domain constant (everything else derives from those).\nconst TEMPLATE_DOMAIN = 'todo.quidproquojs.com';\n\nexport const applyDomain: CreateQpqAppStep = {\n name: 'Applying domain',\n\n run: async ({ targetDirectory, answers }) => {\n const deployConfigPath = path.join(targetDirectory, 'apps', answers.appName, 'deploy.config.json');\n const deployConfig = readJsonFile(deployConfigPath);\n deployConfig.domain = answers.domain;\n writeJsonFile(deployConfigPath, deployConfig);\n\n replaceInFileExact(\n path.join(targetDirectory, 'apps', answers.appName, 'packages', 'constants', 'src', 'domain.ts'),\n TEMPLATE_DOMAIN,\n answers.domain,\n );\n },\n};\n"]}
1
+ {"version":3,"file":"006_applyDomain.js","sourceRoot":"","sources":["../../../src/steps/006_applyDomain.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,6EAA6E;AAC7E,8EAA8E;AAC9E,yBAAyB;AACzB,MAAM,eAAe,GAAG,uBAAuB,CAAC;AAEhD,MAAM,CAAC,MAAM,WAAW,GAAqB;IAC3C,IAAI,EAAE,iBAAiB;IAEvB,GAAG,EAAE,KAAK,EAAE,EAAE,eAAe,EAAE,OAAO,EAAE,EAAE,EAAE;QAC1C,kBAAkB,CAChB,IAAI,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,EAAE,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,CAAC,EAChG,eAAe,EACf,OAAO,CAAC,MAAM,CACf,CAAC;IACJ,CAAC;CACF,CAAC","sourcesContent":["import path from 'path';\n\nimport { replaceInFileExact } from '../lib/replaceInFileExact';\nimport { CreateQpqAppStep } from '../types';\n\n// The template's domain, replaced where it's authoritative: the app's domain\n// constant (everything else derives from it). One root at scaffold time; more\n// can be appended later.\nconst TEMPLATE_DOMAIN = 'todo.quidproquojs.com';\n\nexport const applyDomain: CreateQpqAppStep = {\n name: 'Applying domain',\n\n run: async ({ targetDirectory, answers }) => {\n replaceInFileExact(\n path.join(targetDirectory, 'apps', answers.appName, 'packages', 'constants', 'src', 'domain.ts'),\n TEMPLATE_DOMAIN,\n answers.domain,\n );\n },\n};\n"]}
@@ -14,7 +14,7 @@ Next steps:
14
14
  ${install} npm run dev # api on http://localhost:8080, web on http://localhost:3080
15
15
  npm run deploy # build the docker image (then run the printed docker command)
16
16
 
17
- Deploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).
17
+ Deploy config lives in apps/${answers.appName}/deploy.config.json; the domain (${answers.domain}) lives in the app's constants package.
18
18
  `);
19
19
  },
20
20
  };
@@ -1 +1 @@
1
- {"version":3,"file":"013_printNextSteps.js","sourceRoot":"","sources":["../../../src/steps/013_printNextSteps.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAqB;IAC9C,IAAI,EAAE,MAAM;IAEZ,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kCAAkC,CAAC;QAEtF,OAAO,CAAC,GAAG,CAAC;UACN,OAAO,CAAC,OAAO;;;;;;;OAOlB,OAAO,CAAC,OAAO;EACpB,OAAO;;;8BAGqB,OAAO,CAAC,OAAO,gCAAgC,OAAO,CAAC,MAAM;CAC1F,CAAC,CAAC;IACD,CAAC;CACF,CAAC","sourcesContent":["import { CreateQpqAppStep } from '../types';\n\nexport const printNextSteps: CreateQpqAppStep = {\n name: 'Done',\n\n run: async ({ answers }) => {\n const install = answers.installDependencies ? '' : ' npm install\\n npm run build\\n';\n\n console.log(`\nCreated ${answers.appName}!\n\nYour app has five services (admin, auth, design, shell and todo) and\ndeploys as a single docker image.\n\nNext steps:\n\n cd ${answers.appName}\n${install} npm run dev # api on http://localhost:8080, web on http://localhost:3080\n npm run deploy # build the docker image (then run the printed docker command)\n\nDeploy config lives in apps/${answers.appName}/deploy.config.json (domain: ${answers.domain}).\n`);\n },\n};\n"]}
1
+ {"version":3,"file":"013_printNextSteps.js","sourceRoot":"","sources":["../../../src/steps/013_printNextSteps.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,cAAc,GAAqB;IAC9C,IAAI,EAAE,MAAM;IAEZ,GAAG,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,EAAE,EAAE;QACzB,MAAM,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,kCAAkC,CAAC;QAEtF,OAAO,CAAC,GAAG,CAAC;UACN,OAAO,CAAC,OAAO;;;;;;;OAOlB,OAAO,CAAC,OAAO;EACpB,OAAO;;;8BAGqB,OAAO,CAAC,OAAO,oCAAoC,OAAO,CAAC,MAAM;CAC9F,CAAC,CAAC;IACD,CAAC;CACF,CAAC","sourcesContent":["import { CreateQpqAppStep } from '../types';\n\nexport const printNextSteps: CreateQpqAppStep = {\n name: 'Done',\n\n run: async ({ answers }) => {\n const install = answers.installDependencies ? '' : ' npm install\\n npm run build\\n';\n\n console.log(`\nCreated ${answers.appName}!\n\nYour app has five services (admin, auth, design, shell and todo) and\ndeploys as a single docker image.\n\nNext steps:\n\n cd ${answers.appName}\n${install} npm run dev # api on http://localhost:8080, web on http://localhost:3080\n npm run deploy # build the docker image (then run the printed docker command)\n\nDeploy config lives in apps/${answers.appName}/deploy.config.json; the domain (${answers.domain}) lives in the app's constants package.\n`);\n },\n};\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-qpq-app",
3
- "version": "0.1.19",
3
+ "version": "0.1.21",
4
4
  "description": "Scaffold a new quidproquo app: npx create-qpq-app my-app",
5
5
  "main": "./lib/commonjs/index.js",
6
6
  "module": "./lib/esm/index.js",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "devDependencies": {
57
57
  "@types/node": "^22.13.13",
58
- "quidproquo-tsconfig": "0.1.19"
58
+ "quidproquo-tsconfig": "0.1.21"
59
59
  },
60
60
  "bin": {
61
61
  "create-qpq-app": "./lib/commonjs/bin/createQpqApp.js"
@@ -28,7 +28,7 @@ curl -H "Host: qpqjs.development.localhost" http://localhost:8080/api/shell/v1/h
28
28
 
29
29
  ```
30
30
  apps/<app>/ # qpqjs
31
- deploy.config.json # { "prefix", "domain" }: dns config for deploy
31
+ deploy.config.json # { "prefix", "environments" }: deploy identity per environment
32
32
  tsconfig.federated.json # generated by `npm run prep`: cross-view type aliases
33
33
  packages/ # app-wide libs (config, constants, service-utils)
34
34
  services/<svc>/
@@ -85,14 +85,17 @@ Every app-facing command (`prep`, `synth`, `go`, `go:docker`, `go:dev`, `go:dev:
85
85
 
86
86
  ```bash
87
87
  npm run deploy # docker deploy (qpq go:docker)
88
+ npx qpq setup # new account/environment: checked steps (creds, IAM, zones, bucket names,
89
+ # cdk bootstrap, account/domain/bootstrap stacks incl. the GitHub OIDC
90
+ # provider + deploy role), run in order; then deploy services with go:docker
88
91
  npx qpq synth # writes dist/apps/<app>/infrastructure/<svc>-config.json for cdk
89
92
  npx qpq go # interactive aws deploy: pick app, services, stacks (inf/api/web/views)
90
93
  # bundles (rspack production), cdk deploy, and s3 sync per phase
91
94
  ```
92
95
 
93
- `qpq go` needs AWS credentials plus a deploy identity: either an `"environments"` map in `apps/<app>/deploy.config.json` (`{ "prefix", "domain", "environments": { "<env>": { "accountId", "region" } } }`, selected with `--env <name>`) or the legacy `ENVIRONMENT` and `AWS_DEFAULT_*` env vars. App-specific bootstrap and account extras live in `apps/<app>/bootstrap.qpq.ts` and `apps/<app>/account.qpq.ts` as config fragments; the identity plumbing is provided by the generic CDK app in `quidproquo-deploy-awscdk`.
96
+ `qpq go` needs AWS credentials plus a deploy identity: either an `"environments"` map in `apps/<app>/deploy.config.json` (`{ "prefix", "environments": { "<env>": { "accountId", "region" } } }`, selected with `--env <name>`) or the legacy `ENVIRONMENT` and `AWS_DEFAULT_*` env vars. App-specific bootstrap and account extras live in `apps/<app>/bootstrap.qpq.ts` and `apps/<app>/account.qpq.ts` as config fragments; the identity plumbing is provided by the generic CDK app in `quidproquo-deploy-awscdk`.
94
97
 
95
- - Production views bundles get prod remote URLs (`https://views.<feature>.<env>.<domain>/<svc>`) derived from `deploy.config.json` plus the service's qpq config. Dev bundles use localhost ports.
98
+ - Production views bundles get prod remote URLs (the `views` host on the primary root, under the app's domain resolver) derived from the service's qpq config. Dev bundles use localhost ports.
96
99
  - `cdk.context.json` at the repo root is CDK's regenerable Route53 lookup cache. It is written on the first deploy, and checking it in means later synths need no live AWS lookups.
97
100
 
98
101
  ## Adding a service
@@ -7,33 +7,25 @@ import { QPQConfig } from 'quidproquo';
7
7
  import {
8
8
  AwsDataStoreRemovalPolicy,
9
9
  defineAccountBudget,
10
- defineAccountCloudTrail,
10
+ defineAccountGithubOidcProvider,
11
11
  defineAccountSecurityServices,
12
12
  defineAwsDataStoreRemovalPolicy,
13
13
  } from 'quidproquo-config-aws';
14
14
 
15
15
  export default (): QPQConfig => [
16
- // Dev account: deleting the account stack takes the CloudTrail bucket
17
- // (and its audit history) with it.
16
+ // Dev account: data stores created by the account stack are torn down with it.
18
17
  defineAwsDataStoreRemovalPolicy(AwsDataStoreRemovalPolicy.destroy),
19
18
 
20
- defineAccountCloudTrail('main', {
21
- cloudWatchLogs: {
22
- retentionDays: 30,
23
- },
24
- }),
25
-
26
19
  defineAccountBudget('main', 30, ['joecoady@gmail.com']),
27
20
 
21
+ // GitHub Actions OIDC provider, trusted by every app's deploy role in this account.
22
+ defineAccountGithubOidcProvider(),
23
+
28
24
  defineAccountSecurityServices({
29
25
  // GuardDuty is org-managed for this account
30
26
  // (delegated admin 816650600287 auto-enables detectors - a stack-created one
31
27
  // would collide), and Security Hub is off pending the AWS Config cost decision.
32
28
  enableGuardDuty: false,
33
29
  enableSecurityHub: false,
34
- cognitoAuthFailureAlert: {
35
- emails: ['joecoady@gmail.com'],
36
- thresholdPer5Minutes: 10,
37
- },
38
30
  }),
39
31
  ];
@@ -1,10 +1,10 @@
1
- // App-specific bootstrap extras WAF, api, and the domain certificates. The
2
- // identity plumbing (defineApplication + defineAwsServiceAccountInfo) is
3
- // provided by quidproquo-deploy-awscdk's workspace CDK app; this fragment must
4
- // not declare its own. Prefix/domain/environment arrive via the deploy context
5
- // (from deploy.config.json + qpq go).
6
- import { defineApi, QPQConfig } from 'quidproquo';
1
+ // App-specific bootstrap extras: the root domains, WAF, the api domain, and the
2
+ // domain certificates. The identity plumbing (defineApplication +
3
+ // defineAwsServiceAccountInfo) is provided by quidproquo-deploy-awscdk's
4
+ // workspace CDK app; this fragment must not declare its own.
5
+ import { defineApi, defineDns, QPQConfig } from 'quidproquo';
7
6
  import {
7
+ defineAwsGithubDeployRole,
8
8
  defineBootstrapWaf,
9
9
  defineDomainCertificate,
10
10
  WafManagedRuleGroup,
@@ -12,7 +12,14 @@ import {
12
12
  } from 'quidproquo-config-aws';
13
13
  import { QpqAppDeployContext } from 'quidproquo-deploy-awscdk';
14
14
 
15
- export default ({ domain, region }: QpqAppDeployContext): QPQConfig => [
15
+ import { QPQJS_DOMAINS, QpqjsServiceEnum } from '@qpqjs/constants';
16
+
17
+ export default ({ region }: QpqAppDeployContext): QPQConfig => [
18
+ defineDns(QPQJS_DOMAINS),
19
+
20
+ // The role the deploy workflow assumes (ids from `gh api repos/qpqjs/quidproquo`).
21
+ defineAwsGithubDeployRole('qpqjs/quidproquo', { ownerId: 314167689, repositoryId: 571382961 }),
22
+
16
23
  defineBootstrapWaf({
17
24
  rateLimits: [{ name: 'all-traffic', limit: 2000 }],
18
25
  managedRuleGroups: [
@@ -28,15 +35,23 @@ export default ({ domain, region }: QpqAppDeployContext): QPQConfig => [
28
35
  },
29
36
  }),
30
37
 
31
- defineApi('api', domain),
38
+ defineApi('api'),
32
39
 
40
+ // CloudFront (us-east-1): the site root plus every web entry, on every root domain.
33
41
  defineDomainCertificate(
34
- domain,
35
42
  'us-east-1',
36
- ['www', 'views', 'docs', 'storybook'],
37
- {
38
- includeApex: true,
39
- }
43
+ [
44
+ { subdomain: 'www' },
45
+ { subdomain: 'views' },
46
+ { subdomain: 'docs' },
47
+ { subdomain: 'storybook' },
48
+ ],
49
+ { includeApex: true }
40
50
  ),
41
- defineDomainCertificate(domain, region, ['api', 'ws.ws', 'qpqadmin.admin']),
51
+
52
+ // Regional API Gateway: the api and the admin websocket.
53
+ defineDomainCertificate(region, [
54
+ { subdomain: 'api' },
55
+ { subdomain: 'qpqadmin', service: QpqjsServiceEnum.Admin },
56
+ ]),
42
57
  ];
@@ -1,6 +1,5 @@
1
1
  {
2
2
  "prefix": "qpqjs",
3
- "domain": "quidproquojs.com",
4
3
  "environments": {
5
4
  "development": {
6
5
  "platform": "aws",
@@ -1 +1,3 @@
1
- export const QPQJS_DOMAIN = 'quidproquojs.com';
1
+ // Root domains the app is served on, primary first. Every api, web entry and
2
+ // websocket is created on each of them; absolute URLs bake to the primary.
3
+ export const QPQJS_DOMAINS = ['quidproquojs.com', 'qpqjs.com'];
@@ -11,19 +11,20 @@ import {
11
11
  } from 'quidproquo';
12
12
  import {
13
13
  ApiLayer,
14
+ AwsDataStoreRemovalPolicy,
15
+ defineAwsDataStoreRemovalPolicy,
14
16
  defineAwsServiceAccountInfo,
15
17
  defineWafProtection,
16
- AwsDataStoreRemovalPolicy,
17
- defineAwsDataStoreRemovalPolicy
18
18
  } from 'quidproquo-config-aws';
19
19
  import {
20
20
  defineAdminSettings,
21
21
  defineAdminUserDirectory,
22
+ defineOpenApiRoutes,
22
23
  } from 'quidproquo-features';
23
24
 
24
25
  import { execSync } from 'child_process';
25
26
  import {
26
- QPQJS_DOMAIN,
27
+ QPQJS_DOMAINS,
27
28
  QPQJS_USER_DIRECTORY,
28
29
  QpqjsServiceEnum,
29
30
  qpqjsServiceNames,
@@ -40,8 +41,7 @@ export const defineQpqjsService = (
40
41
  configRoot: string,
41
42
  apiBuildPath: string,
42
43
  apiLayers: ApiLayer[] = [],
43
- lambdaMemoryInMiB = 1024,
44
- domainName = QPQJS_DOMAIN
44
+ lambdaMemoryInMiB = 1024
45
45
  ): QPQConfig => [
46
46
  defineApplicationModule(
47
47
  modulePrefix,
@@ -58,7 +58,7 @@ export const defineQpqjsService = (
58
58
  .trim()}-${new Date().toISOString()}`
59
59
  ),
60
60
 
61
- defineDns(domainName),
61
+ defineDns(QPQJS_DOMAINS),
62
62
 
63
63
  defineAdminUserDirectory({
64
64
  owner: {
@@ -111,7 +111,7 @@ export const defineQpqjsService = (
111
111
  sharedSingletons: ['chakra', 'zod'],
112
112
  }),
113
113
 
114
- defineApi('api', domainName),
114
+ defineApi('api'),
115
115
  defineDefaultRouteOptions('api', {
116
116
  allowedOrigins: [
117
117
  'http://localhost:3080',
@@ -138,11 +138,17 @@ export const defineQpqjsService = (
138
138
  }
139
139
  ),
140
140
 
141
- defineAdminSettings(QpqjsServiceEnum.Admin, QPQJS_DOMAIN, {
141
+ defineAdminSettings(QpqjsServiceEnum.Admin, {
142
142
  services: qpqjsServiceNames,
143
143
  coldStorageAfterDays: 90,
144
144
  }),
145
145
 
146
+ // Every service documents itself: reference page at /v1/docs, the generated
147
+ // OpenAPI document at /v1/docs/openapi.json. Public, like the docs site.
148
+ defineOpenApiRoutes({
149
+ info: { title: `${modulePrefix} ${service}`, version: '1.0.0' },
150
+ }),
151
+
146
152
  // turning this off is super painful for maintaince, dont be a nubbet.
147
- defineAwsDataStoreRemovalPolicy(AwsDataStoreRemovalPolicy.destroy)
153
+ defineAwsDataStoreRemovalPolicy(AwsDataStoreRemovalPolicy.destroy),
148
154
  ];
@@ -29,8 +29,7 @@ const CONFIG_LINES: ReactNode[] = [
29
29
  </>,
30
30
  <>
31
31
  {' '}
32
- {pr('domain')}: {'{'} onRootDomain: {kw('true')}, rootDomain: QPQJS_DOMAIN{' '}
33
- {'}'},
32
+ {pr('domain')}: {'{'} onRootDomain: {kw('true')} {'}'},
34
33
  </>,
35
34
  <>{' }),'}</>,
36
35
  <>&nbsp;</>,
@@ -6,7 +6,7 @@ import {
6
6
  import { defineDevServerOptions } from 'quidproquo-dev-server/config';
7
7
  import { defineDynamicRoutes } from 'quidproquo-features';
8
8
 
9
- import { QPQJS_DOMAIN, QpqjsServiceEnum } from '@qpqjs/constants';
9
+ import { QpqjsServiceEnum } from '@qpqjs/constants';
10
10
  import { defineQpqjsService } from '@qpqjs/service-utils';
11
11
 
12
12
  import * as dynamicRoutes from './entry/controller';
@@ -23,7 +23,6 @@ const webEntryOptions: QPQConfigAdvancedWebEntrySettings = {
23
23
  cacheSettingsName: 'default',
24
24
  domain: {
25
25
  onRootDomain: true,
26
- rootDomain: QPQJS_DOMAIN,
27
26
  },
28
27
  storageDrive: {
29
28
  sourceStorageDrive: websiteWebEntryStorageDriveName,
@@ -40,7 +39,6 @@ const mfeHostWebEntryOptions: QPQConfigAdvancedWebEntrySettings = {
40
39
  domain: {
41
40
  subDomainName: 'views',
42
41
  onRootDomain: true,
43
- rootDomain: QPQJS_DOMAIN,
44
42
  },
45
43
  storageDrive: {
46
44
  sourceStorageDrive: mfeHostWebEntryStorageDriveName,
@@ -65,7 +63,6 @@ const docsWebEntryOptions: QPQConfigAdvancedWebEntrySettings = {
65
63
  domain: {
66
64
  subDomainName: 'docs',
67
65
  onRootDomain: true,
68
- rootDomain: QPQJS_DOMAIN,
69
66
  },
70
67
  };
71
68
 
@@ -2,7 +2,8 @@
2
2
  "name": "@qpqjs/test-models",
3
3
  "version": "0.0.1",
4
4
  "dependencies": {
5
- "tslib": "^2.3.0"
5
+ "tslib": "^2.3.0",
6
+ "zod": "^3.25.76"
6
7
  },
7
8
  "type": "commonjs",
8
9
  "main": "./dist/src/index.js",
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ // Body of POST /echo/{pathValue}: one value to be sent straight back.
4
+ export const EchoRequestSchema = z.object({
5
+ bodyValue: z.string().min(1).describe('Any text; it comes back unchanged'),
6
+ });
7
+
8
+ export type EchoRequest = z.infer<typeof EchoRequestSchema>;
@@ -0,0 +1,9 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ // What POST /echo/{pathValue} returns: the path and body values, untouched.
4
+ export const EchoResponseSchema = z.object({
5
+ pathValue: z.string().describe('The {pathValue} segment of the url'),
6
+ bodyValue: z.string().describe('The bodyValue from the request body'),
7
+ });
8
+
9
+ export type EchoResponse = z.infer<typeof EchoResponseSchema>;
@@ -1,14 +1,16 @@
1
- import { Nullable } from 'quidproquo';
1
+ import { z } from 'zod/v4';
2
2
 
3
3
  import { SmokeRunStatus } from './SmokeRunStatus';
4
- import { SmokeTestResult } from './SmokeTestResult';
4
+ import { SmokeTestResultSchema } from './SmokeTestResult';
5
5
 
6
6
  // One smoke run: POST /smoke/run creates it, GET /smoke/run/{runId} polls it.
7
7
  // The shape is the part-2 contract; part 3 only grows what `tests` contains.
8
- export type SmokeRun = {
9
- runId: string;
10
- status: SmokeRunStatus;
11
- startedAt: string;
12
- finishedAt: Nullable<string>;
13
- tests: SmokeTestResult[];
14
- };
8
+ export const SmokeRunSchema = z.object({
9
+ runId: z.string(),
10
+ status: z.enum(SmokeRunStatus),
11
+ startedAt: z.iso.datetime(),
12
+ finishedAt: z.iso.datetime().nullable(),
13
+ tests: z.array(SmokeTestResultSchema),
14
+ });
15
+
16
+ export type SmokeRun = z.infer<typeof SmokeRunSchema>;
@@ -0,0 +1,8 @@
1
+ import { z } from 'zod/v4';
2
+
3
+ // What POST /smoke/run returns: just the handle to poll with.
4
+ export const SmokeRunStartedSchema = z.object({
5
+ runId: z.string().describe('Poll GET /smoke/run/{runId} with this'),
6
+ });
7
+
8
+ export type SmokeRunStarted = z.infer<typeof SmokeRunStartedSchema>;
@@ -1,8 +1,12 @@
1
+ import { z } from 'zod/v4';
2
+
1
3
  // Counts derived from a run's test entries at read time, never stored, so they
2
4
  // cannot drift from the entries themselves.
3
- export type SmokeRunSummary = {
4
- total: number;
5
- completed: number;
6
- passed: number;
7
- failed: number;
8
- };
5
+ export const SmokeRunSummarySchema = z.object({
6
+ total: z.int().nonnegative().describe('Tests registered for the run'),
7
+ completed: z.int().nonnegative().describe('Tests that have passed or failed'),
8
+ passed: z.int().nonnegative(),
9
+ failed: z.int().nonnegative(),
10
+ });
11
+
12
+ export type SmokeRunSummary = z.infer<typeof SmokeRunSummarySchema>;
@@ -1,7 +1,11 @@
1
- import { SmokeRun } from './SmokeRun';
2
- import { SmokeRunSummary } from './SmokeRunSummary';
1
+ import { z } from 'zod/v4';
2
+
3
+ import { SmokeRunSchema } from './SmokeRun';
4
+ import { SmokeRunSummarySchema } from './SmokeRunSummary';
3
5
 
4
6
  // What GET /smoke/run/{runId} returns: the stored run plus derived counts.
5
- export type SmokeRunWithSummary = SmokeRun & {
6
- summary: SmokeRunSummary;
7
- };
7
+ export const SmokeRunWithSummarySchema = SmokeRunSchema.extend({
8
+ summary: SmokeRunSummarySchema,
9
+ });
10
+
11
+ export type SmokeRunWithSummary = z.infer<typeof SmokeRunWithSummarySchema>;
@@ -1,15 +1,17 @@
1
- import { Nullable } from 'quidproquo';
1
+ import { z } from 'zod/v4';
2
2
 
3
3
  import { SmokeTestStatus } from './SmokeTestStatus';
4
4
 
5
5
  // One registered test's entry in a run. `name` is the stable key; `id` is the
6
6
  // test's 1-based position in the registry, for humans, and shifts if the
7
7
  // registry is reordered, so nothing should key on it.
8
- export type SmokeTestResult = {
9
- id: number;
10
- name: string;
11
- status: SmokeTestStatus;
12
- message: string;
13
- startedAt: Nullable<string>;
14
- finishedAt: Nullable<string>;
15
- };
8
+ export const SmokeTestResultSchema = z.object({
9
+ id: z.int().positive().describe('1-based position in the test registry'),
10
+ name: z.string().describe('Stable test name'),
11
+ status: z.enum(SmokeTestStatus),
12
+ message: z.string(),
13
+ startedAt: z.iso.datetime().nullable(),
14
+ finishedAt: z.iso.datetime().nullable(),
15
+ });
16
+
17
+ export type SmokeTestResult = z.infer<typeof SmokeTestResultSchema>;
@@ -1,5 +1,8 @@
1
+ export * from './EchoRequest';
2
+ export * from './EchoResponse';
1
3
  export * from './SmokeProbeRecord';
2
4
  export * from './SmokeRun';
5
+ export * from './SmokeRunStarted';
3
6
  export * from './SmokeRunStatus';
4
7
  export * from './SmokeRunSummary';
5
8
  export * from './SmokeRunWithSummary';
@@ -0,0 +1,9 @@
1
+ import { QPQConfig } from 'quidproquo';
2
+ import { defineDynamicRoutes } from 'quidproquo-features';
3
+
4
+ import * as controllers from '../controller';
5
+
6
+ // The echo routes carry their own method/path/schema on the handler; this just
7
+ // registers the controller module under the service's src root.
8
+ export const defineEcho = (): QPQConfig =>
9
+ defineDynamicRoutes(controllers, '/echo/controller');
@@ -0,0 +1,32 @@
1
+ import { AskResponse, HTTPEventResponse, qpqWebServerUtils } from 'quidproquo';
2
+ import { dynamicRoute } from 'quidproquo-features';
3
+
4
+ import {
5
+ EchoRequestSchema,
6
+ EchoResponse,
7
+ EchoResponseSchema,
8
+ } from '@qpqjs/test-models';
9
+
10
+ // POST /echo/{pathValue}. Public. Exists to prove a value survives the trip
11
+ // through the url and the validated body: the response is both, unchanged.
12
+ export const echo = dynamicRoute(
13
+ ['POST', '/echo/{pathValue}'],
14
+ function* (event, params, { body }): AskResponse<HTTPEventResponse> {
15
+ const response: EchoResponse = {
16
+ pathValue: params.pathValue,
17
+ bodyValue: body.bodyValue,
18
+ };
19
+
20
+ return qpqWebServerUtils.toJsonEventResponse(response);
21
+ },
22
+ {
23
+ schema: {
24
+ summary: 'Echo a path and body value',
25
+ description:
26
+ 'Returns the {pathValue} url segment and the bodyValue from the JSON body, untouched. Public, for trying the docs UI.',
27
+ tags: ['echo'],
28
+ body: EchoRequestSchema,
29
+ response: EchoResponseSchema,
30
+ },
31
+ }
32
+ );
@@ -0,0 +1 @@
1
+ export * from './echo';
@@ -3,6 +3,7 @@ import { defineDevServerOptions } from 'quidproquo-dev-server/config';
3
3
  import { QpqjsServiceEnum } from '@qpqjs/constants';
4
4
  import { defineQpqjsService } from '@qpqjs/service-utils';
5
5
 
6
+ import { defineEcho } from './echo/config/defineEcho';
6
7
  import { defineSmoke } from './smoke/config/defineSmoke';
7
8
  import { defineTick } from './tick/config/defineTick';
8
9
 
@@ -18,5 +19,7 @@ export default [
18
19
 
19
20
  defineSmoke(),
20
21
 
22
+ defineEcho(),
23
+
21
24
  defineTick(),
22
25
  ];