@memberjunction/cli 5.37.0 → 5.39.0

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 (177) hide show
  1. package/README.md +74 -0
  2. package/dist/baseline/cli-helpers.d.ts +12 -0
  3. package/dist/baseline/cli-helpers.d.ts.map +1 -0
  4. package/dist/baseline/cli-helpers.js +40 -0
  5. package/dist/baseline/cli-helpers.js.map +1 -0
  6. package/dist/baseline/comparator.d.ts +26 -0
  7. package/dist/baseline/comparator.d.ts.map +1 -0
  8. package/dist/baseline/comparator.js +527 -0
  9. package/dist/baseline/comparator.js.map +1 -0
  10. package/dist/baseline/connection.d.ts +39 -0
  11. package/dist/baseline/connection.d.ts.map +1 -0
  12. package/dist/baseline/connection.js +104 -0
  13. package/dist/baseline/connection.js.map +1 -0
  14. package/dist/baseline/data-dumper.d.ts +23 -0
  15. package/dist/baseline/data-dumper.d.ts.map +1 -0
  16. package/dist/baseline/data-dumper.js +94 -0
  17. package/dist/baseline/data-dumper.js.map +1 -0
  18. package/dist/baseline/emitter.d.ts +36 -0
  19. package/dist/baseline/emitter.d.ts.map +1 -0
  20. package/dist/baseline/emitter.js +519 -0
  21. package/dist/baseline/emitter.js.map +1 -0
  22. package/dist/baseline/index.d.ts +18 -0
  23. package/dist/baseline/index.d.ts.map +1 -0
  24. package/dist/baseline/index.js +14 -0
  25. package/dist/baseline/index.js.map +1 -0
  26. package/dist/baseline/introspector-mssql.d.ts +15 -0
  27. package/dist/baseline/introspector-mssql.d.ts.map +1 -0
  28. package/dist/baseline/introspector-mssql.js +673 -0
  29. package/dist/baseline/introspector-mssql.js.map +1 -0
  30. package/dist/baseline/introspector-postgres.d.ts +14 -0
  31. package/dist/baseline/introspector-postgres.d.ts.map +1 -0
  32. package/dist/baseline/introspector-postgres.js +277 -0
  33. package/dist/baseline/introspector-postgres.js.map +1 -0
  34. package/dist/baseline/report.d.ts +7 -0
  35. package/dist/baseline/report.d.ts.map +1 -0
  36. package/dist/baseline/report.js +105 -0
  37. package/dist/baseline/report.js.map +1 -0
  38. package/dist/baseline/types.d.ts +286 -0
  39. package/dist/baseline/types.d.ts.map +1 -0
  40. package/dist/baseline/types.js +10 -0
  41. package/dist/baseline/types.js.map +1 -0
  42. package/dist/baseline/util.d.ts +112 -0
  43. package/dist/baseline/util.d.ts.map +1 -0
  44. package/dist/baseline/util.js +325 -0
  45. package/dist/baseline/util.js.map +1 -0
  46. package/dist/commands/baseline/build.d.ts +29 -0
  47. package/dist/commands/baseline/build.d.ts.map +1 -0
  48. package/dist/commands/baseline/build.js +188 -0
  49. package/dist/commands/baseline/build.js.map +1 -0
  50. package/dist/commands/baseline/compare.d.ts +19 -0
  51. package/dist/commands/baseline/compare.d.ts.map +1 -0
  52. package/dist/commands/baseline/compare.js +133 -0
  53. package/dist/commands/baseline/compare.js.map +1 -0
  54. package/dist/commands/baseline/roundtrip.d.ts +23 -0
  55. package/dist/commands/baseline/roundtrip.d.ts.map +1 -0
  56. package/dist/commands/baseline/roundtrip.js +245 -0
  57. package/dist/commands/baseline/roundtrip.js.map +1 -0
  58. package/dist/commands/bundle/index.d.ts +21 -0
  59. package/dist/commands/bundle/index.d.ts.map +1 -0
  60. package/dist/commands/bundle/index.js +62 -0
  61. package/dist/commands/bundle/index.js.map +1 -0
  62. package/dist/commands/migrate/index.d.ts +29 -0
  63. package/dist/commands/migrate/index.d.ts.map +1 -1
  64. package/dist/commands/migrate/index.js +111 -5
  65. package/dist/commands/migrate/index.js.map +1 -1
  66. package/dist/commands/test/compare.d.ts +3 -0
  67. package/dist/commands/test/compare.d.ts.map +1 -1
  68. package/dist/commands/test/compare.js +23 -3
  69. package/dist/commands/test/compare.js.map +1 -1
  70. package/dist/commands/test/regression/build.d.ts +14 -0
  71. package/dist/commands/test/regression/build.d.ts.map +1 -0
  72. package/dist/commands/test/regression/build.js +48 -0
  73. package/dist/commands/test/regression/build.js.map +1 -0
  74. package/dist/commands/test/regression/compare.d.ts +15 -0
  75. package/dist/commands/test/regression/compare.d.ts.map +1 -0
  76. package/dist/commands/test/regression/compare.js +66 -0
  77. package/dist/commands/test/regression/compare.js.map +1 -0
  78. package/dist/commands/test/regression/down.d.ts +10 -0
  79. package/dist/commands/test/regression/down.d.ts.map +1 -0
  80. package/dist/commands/test/regression/down.js +29 -0
  81. package/dist/commands/test/regression/down.js.map +1 -0
  82. package/dist/commands/test/regression/export.d.ts +11 -0
  83. package/dist/commands/test/regression/export.d.ts.map +1 -0
  84. package/dist/commands/test/regression/export.js +56 -0
  85. package/dist/commands/test/regression/export.js.map +1 -0
  86. package/dist/commands/test/regression/gen-forms.d.ts +7 -0
  87. package/dist/commands/test/regression/gen-forms.d.ts.map +1 -0
  88. package/dist/commands/test/regression/gen-forms.js +18 -0
  89. package/dist/commands/test/regression/gen-forms.js.map +1 -0
  90. package/dist/commands/test/regression/index.d.ts +7 -0
  91. package/dist/commands/test/regression/index.d.ts.map +1 -0
  92. package/dist/commands/test/regression/index.js +33 -0
  93. package/dist/commands/test/regression/index.js.map +1 -0
  94. package/dist/commands/test/regression/init.d.ts +21 -0
  95. package/dist/commands/test/regression/init.d.ts.map +1 -0
  96. package/dist/commands/test/regression/init.js +111 -0
  97. package/dist/commands/test/regression/init.js.map +1 -0
  98. package/dist/commands/test/regression/remote.d.ts +23 -0
  99. package/dist/commands/test/regression/remote.d.ts.map +1 -0
  100. package/dist/commands/test/regression/remote.js +140 -0
  101. package/dist/commands/test/regression/remote.js.map +1 -0
  102. package/dist/commands/test/regression/up.d.ts +21 -0
  103. package/dist/commands/test/regression/up.d.ts.map +1 -0
  104. package/dist/commands/test/regression/up.js +111 -0
  105. package/dist/commands/test/regression/up.js.map +1 -0
  106. package/dist/commands/test/run.d.ts +1 -0
  107. package/dist/commands/test/run.d.ts.map +1 -1
  108. package/dist/commands/test/run.js +6 -0
  109. package/dist/commands/test/run.js.map +1 -1
  110. package/dist/commands/test/suite.d.ts +5 -0
  111. package/dist/commands/test/suite.d.ts.map +1 -1
  112. package/dist/commands/test/suite.js +27 -0
  113. package/dist/commands/test/suite.js.map +1 -1
  114. package/dist/commands/translate-sql/index.d.ts.map +1 -1
  115. package/dist/commands/translate-sql/index.js +3 -2
  116. package/dist/commands/translate-sql/index.js.map +1 -1
  117. package/dist/config.d.ts +6 -0
  118. package/dist/config.d.ts.map +1 -1
  119. package/dist/config.js +13 -0
  120. package/dist/config.js.map +1 -1
  121. package/dist/init-templates/bring-your-own-app/README.md +74 -0
  122. package/dist/init-templates/bring-your-own-app/byo-metadata/.mj-sync.json +10 -0
  123. package/dist/init-templates/bring-your-own-app/byo-metadata/test-suites/.byo-suite.json +51 -0
  124. package/dist/init-templates/bring-your-own-app/byo-metadata/test-suites/.mj-sync.json +12 -0
  125. package/dist/init-templates/bring-your-own-app/byo-metadata/tests/.T01-counter-increment.json +63 -0
  126. package/dist/init-templates/bring-your-own-app/byo-metadata/tests/.T02-contact-form-submit.json +63 -0
  127. package/dist/init-templates/bring-your-own-app/byo-metadata/tests/.mj-sync.json +12 -0
  128. package/dist/init-templates/bring-your-own-app/docker-compose.app.yml +76 -0
  129. package/dist/init-templates/bring-your-own-app/target.json +23 -0
  130. package/dist/init-templates/generic-web/README.md +115 -0
  131. package/dist/init-templates/generic-web/metadata/.mj-sync.json +10 -0
  132. package/dist/init-templates/generic-web/metadata/test-suites/.generic-web-suite.json +48 -0
  133. package/dist/init-templates/generic-web/metadata/test-suites/.mj-sync.json +12 -0
  134. package/dist/init-templates/generic-web/metadata/tests/.GW01-home-loads.json +74 -0
  135. package/dist/init-templates/generic-web/metadata/tests/.GW02-search-array-map.json +82 -0
  136. package/dist/init-templates/generic-web/metadata/tests/.mj-sync.json +12 -0
  137. package/dist/init-templates/generic-web/oracles/mdn-oracles.cjs +121 -0
  138. package/dist/init-templates/generic-web/target.json +25 -0
  139. package/dist/init-templates/github-actions/README.md +26 -0
  140. package/dist/init-templates/github-actions/regression.yml +52 -0
  141. package/dist/init-templates/remote-mj/README.md +95 -0
  142. package/dist/init-templates/remote-mj/target.json +22 -0
  143. package/dist/init-templates/static-file-server/Dockerfile +23 -0
  144. package/dist/init-templates/static-file-server/README.md +88 -0
  145. package/dist/init-templates/static-file-server/docker-compose.app.yml +44 -0
  146. package/dist/init-templates/static-file-server/metadata/.mj-sync.json +10 -0
  147. package/dist/init-templates/static-file-server/metadata/test-suites/.mj-sync.json +12 -0
  148. package/dist/init-templates/static-file-server/metadata/test-suites/.static-server-suite.json +27 -0
  149. package/dist/init-templates/static-file-server/metadata/tests/.SS01-home-loads.json +45 -0
  150. package/dist/init-templates/static-file-server/metadata/tests/.SS02-navigate-about.json +45 -0
  151. package/dist/init-templates/static-file-server/metadata/tests/.mj-sync.json +12 -0
  152. package/dist/init-templates/static-file-server/public/about.html +12 -0
  153. package/dist/init-templates/static-file-server/public/index.html +27 -0
  154. package/dist/init-templates/static-file-server/target.json +16 -0
  155. package/dist/lib/db-preflight.d.ts +42 -0
  156. package/dist/lib/db-preflight.d.ts.map +1 -0
  157. package/dist/lib/db-preflight.js +113 -0
  158. package/dist/lib/db-preflight.js.map +1 -0
  159. package/dist/lib/db-version.d.ts +36 -0
  160. package/dist/lib/db-version.d.ts.map +1 -0
  161. package/dist/lib/db-version.js +45 -0
  162. package/dist/lib/db-version.js.map +1 -0
  163. package/dist/lib/migration-fetch.d.ts +49 -0
  164. package/dist/lib/migration-fetch.d.ts.map +1 -0
  165. package/dist/lib/migration-fetch.js +152 -0
  166. package/dist/lib/migration-fetch.js.map +1 -0
  167. package/dist/lib/regression/docker-helpers.d.ts +98 -0
  168. package/dist/lib/regression/docker-helpers.d.ts.map +1 -0
  169. package/dist/lib/regression/docker-helpers.js +184 -0
  170. package/dist/lib/regression/docker-helpers.js.map +1 -0
  171. package/dist/light-commands.d.ts.map +1 -1
  172. package/dist/light-commands.js +2 -0
  173. package/dist/light-commands.js.map +1 -1
  174. package/oclif.manifest.json +1635 -702
  175. package/package.json +22 -17
  176. package/regression-compose/docker-compose.bacpac-standalone.yml +109 -0
  177. package/regression-compose/docker-compose.standalone.yml +50 -0
@@ -0,0 +1,95 @@
1
+ # Remote MJ — Mode B Reference
2
+
3
+ Target profile for driving the **canonical MJ Explorer Regression Suite** (the
4
+ 25 tests baked into the MJ monorepo) against a **remote MJ Explorer
5
+ deployment** — staging, customer, production. No app code lives here; this
6
+ example is purely a target-profile + archive-config recipe.
7
+
8
+ ## When to use this
9
+
10
+ You have a deployed MJ Explorer instance (not the local docker stack) and want
11
+ to run the same regression coverage against it that the MJ team runs against
12
+ `next`. Use cases:
13
+
14
+ - Nightly health-check of a staging deployment.
15
+ - Pre-release smoke test of a customer instance.
16
+ - Validating a behavioral fix landed on production after a hotfix.
17
+
18
+ If you need to test a **non-MJ app**, see [`../generic-web/`](../generic-web/)
19
+ or [`../bring-your-own-app/`](../bring-your-own-app/) instead.
20
+
21
+ ## Layout
22
+
23
+ ```
24
+ remote-mj/
25
+ ├── README.md # this file
26
+ └── target.json # target profile (kind: mj-explorer)
27
+ ```
28
+
29
+ No tests/, no oracles/, no metadata/ — this example reuses the canonical MJ
30
+ suite that ships in `metadata/test-suites/.regression-suite.json` and the 25
31
+ test JSONs in `metadata/tests/regression/`.
32
+
33
+ ## Running it (inside the MJ monorepo)
34
+
35
+ ```bash
36
+ # 1. Copy + edit the target profile
37
+ cp docker/regression/examples/remote-mj/target.json docker/regression/targets/staging.target.json
38
+ # Edit URL, allowedDomains, env: refs to match your deployment
39
+
40
+ # 2. Export the credentials referenced via env:
41
+ export REMOTE_MJ_TEST_USER=qa@example.com
42
+ export REMOTE_MJ_TEST_PASSWORD=...
43
+
44
+ # 3. Run
45
+ mj test regression remote --target=staging
46
+ ```
47
+
48
+ Behind the scenes:
49
+
50
+ 1. The CLI loads `staging.target.json`, resolves `env:` refs, and exports
51
+ `MJ_TEST_VAR_baseUrl`, `MJ_TEST_VAR_allowedDomains`, `MJ_TEST_VAR_authUsername`,
52
+ `MJ_TEST_VAR_authPassword`, etc.
53
+ 2. Compose brings up the **full** profile so the runner has the local
54
+ ephemeral DB for result recording (MJExplorer + MJAPI also boot but are
55
+ unused — the runner targets the remote URL instead).
56
+ 3. The runner pushes test metadata to the ephemeral DB, then invokes
57
+ `mj test suite --name "MJ Explorer Regression Suite"`.
58
+ 4. Reports land in `docker/regression/test-results/run-{TIMESTAMP}/`.
59
+ 5. If the `ARCHIVE_DB_*` env vars are set, the suite run + children are
60
+ pulled to a JSON folder and pushed to the destination MJ via `mj sync`.
61
+
62
+ ## ⚠️ Prerequisite — variable substitution
63
+
64
+ For Mode B to actually retarget the canonical MJ suite, the 25 test JSONs in
65
+ `metadata/tests/regression/` need to use `{{baseUrl}}` / `{{authUsername}}` /
66
+ `{{authPassword}}` / `{{allowedDomains}}` placeholders instead of hardcoded
67
+ `http://localhost:4200`. This was Phase 1A's deliverable; the substitution
68
+ **code** ships and is fully tested, but the **JSON conversion** is a follow-up
69
+ (tracked in [`docker/regression/LIMITATIONS.md`](../../LIMITATIONS.md)).
70
+
71
+ Until the conversion lands, this example sets up the plumbing correctly but
72
+ the runner will still attempt `http://localhost:4200` — useful for archive
73
+ flow + auth-flow testing, not yet for actual remote regression coverage.
74
+
75
+ ## Running it externally (outside the monorepo, via the published image)
76
+
77
+ Once `memberjunction/agentic-test-runner:vX` is published:
78
+
79
+ ```bash
80
+ # Scaffold the directory
81
+ docker run --rm -v $(pwd):/out memberjunction/agentic-test-runner init remote-mj
82
+
83
+ # Edit ./remote-mj/target.json, then run
84
+ export REMOTE_MJ_TEST_USER=qa@example.com
85
+ export REMOTE_MJ_TEST_PASSWORD=...
86
+
87
+ docker run --rm \
88
+ -v $(pwd)/test-results:/app/test-results \
89
+ -e MJ_TEST_VAR_baseUrl="https://mj.example.com" \
90
+ -e MJ_TEST_VAR_allowedDomains='["mj.example.com","*.auth0.com"]' \
91
+ -e MJ_TEST_VAR_authUsername="$REMOTE_MJ_TEST_USER" \
92
+ -e MJ_TEST_VAR_authPassword="$REMOTE_MJ_TEST_PASSWORD" \
93
+ -e TEST_SUITE_NAME="MJ Explorer Regression Suite" \
94
+ memberjunction/agentic-test-runner
95
+ ```
@@ -0,0 +1,22 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "name": "remote-mj",
4
+ "kind": "mj-explorer",
5
+ "description": "Mode B reference target: drives the canonical MJ Explorer Regression Suite against a remote MJ Explorer deployment. Replace the URL + auth refs with your own values before running.",
6
+
7
+ "baseUrl": "https://mj.example.com",
8
+ "allowedDomains": ["mj.example.com", "*.auth0.com"],
9
+
10
+ "auth": {
11
+ "username": "env:REMOTE_MJ_TEST_USER",
12
+ "password": "env:REMOTE_MJ_TEST_PASSWORD",
13
+ "domains": ["mj.example.com", "*.auth0.com"]
14
+ },
15
+
16
+ "suite": "MJ Explorer Regression Suite",
17
+
18
+ "archive": {
19
+ "tag": "remote-mj-nightly",
20
+ "source": "remote-mj"
21
+ }
22
+ }
@@ -0,0 +1,23 @@
1
+ # Mode D demo: bake a static HTML payload into nginx:alpine so the test-runner
2
+ # can drive a tiny app that lives inside the regression compose network.
3
+ #
4
+ # This image is intentionally trivial — the whole point of the example is to
5
+ # show the *overlay pattern*, not to teach app deployment. For a more realistic
6
+ # Angular + Express + planted-bugs example, see ../bring-your-own-app/.
7
+ FROM nginx:alpine
8
+
9
+ # Tiny default config: serve /public on :3000, no caching so test re-runs
10
+ # always see the latest HTML if the volume is bind-mounted.
11
+ RUN printf '%s\n' \
12
+ 'server {' \
13
+ ' listen 3000;' \
14
+ ' root /public;' \
15
+ ' index index.html;' \
16
+ ' add_header Cache-Control "no-store";' \
17
+ ' location / { try_files $uri $uri/ =404; }' \
18
+ '}' \
19
+ > /etc/nginx/conf.d/default.conf
20
+
21
+ COPY public/ /public/
22
+
23
+ EXPOSE 3000
@@ -0,0 +1,88 @@
1
+ # Static File Server — Mode D Minimal Demo
2
+
3
+ The smallest possible **Mode D** (Bring-Your-Own-App) example. An `nginx:alpine`
4
+ container serving two HTML pages, brought up alongside the regression
5
+ test-runner via a docker-compose overlay. Two trivial Computer Use tests
6
+ verify the runner can reach the overlay-supplied service.
7
+
8
+ Use this as the starting point when you want to test your own app inside the
9
+ regression network without any framework assumptions. For a more realistic
10
+ Angular + Express app with planted bugs, see [`../bring-your-own-app/`](../bring-your-own-app/).
11
+
12
+ ## Layout
13
+
14
+ ```
15
+ static-file-server/
16
+ ├── README.md # this file
17
+ ├── Dockerfile # nginx:alpine + a custom server config
18
+ ├── docker-compose.app.yml # Mode D overlay (adds static-server service)
19
+ ├── target.json # target profile: kind=generic-web, baseUrl=http://static-server:3000
20
+ ├── public/
21
+ │ ├── index.html # home page
22
+ │ └── about.html # about page
23
+ └── metadata/
24
+ ├── .mj-sync.json
25
+ ├── tests/
26
+ │ ├── .mj-sync.json
27
+ │ ├── .SS01-home-loads.json # home-page smoke
28
+ │ └── .SS02-navigate-about.json # nav-flow test
29
+ └── test-suites/
30
+ ├── .mj-sync.json
31
+ └── .static-server-suite.json
32
+ ```
33
+
34
+ ## Running it
35
+
36
+ From the MJ monorepo root:
37
+
38
+ ```bash
39
+ mj test regression remote \
40
+ --target=docker/regression/examples/static-file-server/target.json \
41
+ --overlay=docker/regression/examples/static-file-server/docker-compose.app.yml
42
+ ```
43
+
44
+ What happens:
45
+
46
+ 1. **CLI loads the target** — exports `MJ_TEST_VAR_baseUrl=http://static-server:3000`,
47
+ `MJ_TEST_VAR_allowedDomains=["static-server"]`, `TEST_SUITE_NAME="Static Server Mode D Suite"`,
48
+ `EXTRA_METADATA_DIRS=/app/static-server-metadata`, and the remote-entrypoint dispatch.
49
+ 2. **Compose merges the overlay** — `docker compose -f base.yml -f overlay.yml --profile full up`.
50
+ The overlay adds the `static-server` service, makes it a test-runner dep
51
+ (`condition: service_healthy`), and mounts the test/suite metadata.
52
+ 3. **Stack boots in order** — SQL Server → migrations → MJAPI → MJExplorer → static-server →
53
+ test-runner. The MJ stack is up so the runner has an ephemeral DB for `TestRun` /
54
+ `TestRunOutput` rows; MJExplorer itself is unused in this run.
55
+ 4. **Runner pushes the metadata** — `mj sync push --dir=/app/static-server-metadata` adds
56
+ the two tests + suite to the DB.
57
+ 5. **Suite runs** — `mj test suite --name "Static Server Mode D Suite" --parallel`.
58
+ Each test navigates to `http://static-server:3000` (resolved via Docker's compose
59
+ DNS) and runs Computer Use against the HTML.
60
+ 6. **Reports** — markdown + HTML reports land in `docker/regression/test-results/run-{TIMESTAMP}/`.
61
+
62
+ ## What the overlay does
63
+
64
+ [`docker-compose.app.yml`](docker-compose.app.yml) is a stack-extension overlay
65
+ that compose merges with the base regression compose. It:
66
+
67
+ - Defines a `static-server` service (build from this directory's `Dockerfile`).
68
+ - Joins the `full` compose profile so it boots alongside the test-runner.
69
+ - Adds a healthcheck so the runner waits for nginx to be ready.
70
+ - Adds `static-server` to the test-runner's `depends_on`.
71
+ - Mounts `metadata/` into the runner at `/app/static-server-metadata` (RW so
72
+ mj-sync can write back primary-key + sync state).
73
+
74
+ Path note: compose resolves relative paths in overlays against the **first**
75
+ `-f` file's directory (the base compose at `docker/regression/`), not against
76
+ the overlay's own location. That's why the overlay references
77
+ `examples/static-file-server/...` instead of `./...`.
78
+
79
+ ## Copying this example for your own app
80
+
81
+ 1. Copy the directory to `docker/regression/examples/my-app/` (or anywhere; mount it).
82
+ 2. Replace `Dockerfile` + `public/` (or whatever you serve) with your app's container.
83
+ 3. Edit `docker-compose.app.yml` — rename the service, adjust the healthcheck, add any
84
+ extra mounts/env-vars your app needs.
85
+ 4. Edit `target.json` — point `baseUrl` at your service hostname, list the hostname in
86
+ `allowedDomains`, rename the suite.
87
+ 5. Replace the test JSONs in `metadata/tests/` with goals specific to your app.
88
+ 6. Run with the same `mj test regression remote --target=... --overlay=...` command.
@@ -0,0 +1,44 @@
1
+ # Mode D overlay: boots the static-file-server alongside the test-runner so
2
+ # the runner can reach it at http://static-server:3000 over the compose
3
+ # network. Combine with the base regression compose:
4
+ #
5
+ # mj test regression remote \
6
+ # --target=docker/regression/examples/static-file-server/target.json \
7
+ # --overlay=docker/regression/examples/static-file-server/docker-compose.app.yml
8
+ #
9
+ # What this overlay does:
10
+ # - Adds a `static-server` service (the app under test)
11
+ # - Mounts the example's test/suite metadata into the test-runner so the
12
+ # entrypoint can push them at run time
13
+ # - Adds `static-server` as a test-runner dependency so the runner waits
14
+ # for nginx to be ready
15
+
16
+ services:
17
+ static-server:
18
+ # The base compose file is profile-gated. Mode D launches the test-runner
19
+ # via the `full` profile (so the local DB is available for results
20
+ # recording); join the same profile so this service starts alongside.
21
+ profiles: ["full"]
22
+ # Compose resolves relative paths in overlay files using the directory of
23
+ # the FIRST `-f` file (the base compose at docker/regression/), not the
24
+ # overlay's own location. Hence `examples/static-file-server` not `.`.
25
+ build:
26
+ context: examples/static-file-server
27
+ dockerfile: Dockerfile
28
+ image: mj-regression-static-server:latest
29
+ container_name: mj-regression-static-server
30
+ healthcheck:
31
+ test: ["CMD-SHELL", "wget -q -O- http://localhost:3000/ > /dev/null"]
32
+ interval: 5s
33
+ timeout: 3s
34
+ retries: 6
35
+ start_period: 5s
36
+
37
+ test-runner:
38
+ depends_on:
39
+ static-server:
40
+ condition: service_healthy
41
+ volumes:
42
+ # Mode D test/suite metadata. RW because mj-sync writes primaryKey +
43
+ # sync state back into the JSON files after autoCreateMissingRecords.
44
+ - ./examples/static-file-server/metadata:/app/static-server-metadata
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": "1.0.0",
3
+ "push": {
4
+ "autoCreateMissingRecords": true
5
+ },
6
+ "directoryOrder": [
7
+ "tests",
8
+ "test-suites"
9
+ ]
10
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "entity": "MJ: Test Suites",
3
+ "filePattern": "**/.*.json",
4
+ "pull": {
5
+ "relatedEntities": {
6
+ "MJ: Test Suite Tests": {
7
+ "entity": "MJ: Test Suite Tests",
8
+ "foreignKey": "TestSuiteID"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "fields": {
3
+ "Name": "Static Server Mode D Suite",
4
+ "Description": "Mode D reference suite — drives the regression runner against the static-file-server brought up by docker-compose.app.yml. Used as the minimal end-to-end demo of the overlay pattern.",
5
+ "Status": "Active"
6
+ },
7
+ "relatedEntities": {
8
+ "MJ: Test Suite Tests": [
9
+ {
10
+ "fields": {
11
+ "SuiteID": "@parent:ID",
12
+ "TestID": "@lookup:MJ: Tests.Name=SS01 - Static Server Home Loads",
13
+ "Sequence": 1,
14
+ "Status": "Active"
15
+ }
16
+ },
17
+ {
18
+ "fields": {
19
+ "SuiteID": "@parent:ID",
20
+ "TestID": "@lookup:MJ: Tests.Name=SS02 - Static Server Navigate About",
21
+ "Sequence": 2,
22
+ "Status": "Active"
23
+ }
24
+ }
25
+ ]
26
+ }
27
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "fields": {
3
+ "TypeID": "@lookup:MJ: Test Types.Name=Computer Use",
4
+ "Name": "SS01 - Static Server Home Loads",
5
+ "Description": "Mode D smoke: verifies the static-file-server's index page renders inside the regression network. Demonstrates that the test-runner can reach an overlay-supplied service via its compose hostname.",
6
+ "InputDefinition": {
7
+ "goal": "Verify the page loaded successfully. Look for a heading 'Mode D Demo', a paragraph mentioning the overlay pattern, and a navigation link to 'About'. Do not click anything.",
8
+ "startUrl": "{{baseUrl}}",
9
+ "allowedDomains": "{{allowedDomains}}",
10
+ "auth": { "bindings": [] }
11
+ },
12
+ "ExpectedOutcomes": {
13
+ "goalCompleted": true,
14
+ "finalUrlPattern": "^http://static-server:3000/?$",
15
+ "minConfidence": 0.7,
16
+ "maxSteps": 5,
17
+ "judgeValidationCriteria": [
18
+ "Page heading reads 'Mode D Demo'",
19
+ "Navigation includes a link to About",
20
+ "No browser error"
21
+ ]
22
+ },
23
+ "Configuration": {
24
+ "headless": true,
25
+ "maxSteps": 5,
26
+ "maxExecutionTime": 60000,
27
+ "screenshotHistoryDepth": 3,
28
+ "viewportWidth": 1024,
29
+ "viewportHeight": 600,
30
+ "controllerPromptName": "Computer Use - Controller",
31
+ "judgePromptName": "Computer Use - Judge",
32
+ "judgeFrequency": "EveryNSteps:2",
33
+ "oracles": [
34
+ { "type": "goal-completion", "weight": 0.7, "config": { "minConfidence": 0.7 } },
35
+ { "type": "url-match", "weight": 0.3, "config": { "pattern": "^http://static-server:3000/?$" } }
36
+ ],
37
+ "scoringWeights": {
38
+ "goal-completion": 0.7,
39
+ "url-match": 0.3
40
+ }
41
+ },
42
+ "Status": "Active",
43
+ "Tags": "P0,mode-d,static-server,smoke"
44
+ }
45
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "fields": {
3
+ "TypeID": "@lookup:MJ: Test Types.Name=Computer Use",
4
+ "Name": "SS02 - Static Server Navigate About",
5
+ "Description": "Click the 'About' link in the static-server nav and verify the about page loads. Demonstrates a navigation flow against an overlay-supplied service.",
6
+ "InputDefinition": {
7
+ "goal": "Click the link labelled 'About' in the navigation. Verify the about page loads — it should display the heading 'About' and a link back to Home.",
8
+ "startUrl": "{{baseUrl}}",
9
+ "allowedDomains": "{{allowedDomains}}",
10
+ "auth": { "bindings": [] }
11
+ },
12
+ "ExpectedOutcomes": {
13
+ "goalCompleted": true,
14
+ "finalUrlPattern": "/about\\.html$",
15
+ "minConfidence": 0.7,
16
+ "maxSteps": 6,
17
+ "judgeValidationCriteria": [
18
+ "Final URL ends with /about.html",
19
+ "Page heading reads 'About'",
20
+ "A link back to Home is visible"
21
+ ]
22
+ },
23
+ "Configuration": {
24
+ "headless": true,
25
+ "maxSteps": 6,
26
+ "maxExecutionTime": 90000,
27
+ "screenshotHistoryDepth": 3,
28
+ "viewportWidth": 1024,
29
+ "viewportHeight": 600,
30
+ "controllerPromptName": "Computer Use - Controller",
31
+ "judgePromptName": "Computer Use - Judge",
32
+ "judgeFrequency": "EveryNSteps:2",
33
+ "oracles": [
34
+ { "type": "goal-completion", "weight": 0.6, "config": { "minConfidence": 0.7 } },
35
+ { "type": "url-match", "weight": 0.4, "config": { "pattern": "/about\\.html$" } }
36
+ ],
37
+ "scoringWeights": {
38
+ "goal-completion": 0.6,
39
+ "url-match": 0.4
40
+ }
41
+ },
42
+ "Status": "Active",
43
+ "Tags": "P1,mode-d,static-server,navigation"
44
+ }
45
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "entity": "MJ: Tests",
3
+ "filePattern": "**/.*.json",
4
+ "pull": {
5
+ "lookupFields": {
6
+ "TypeID": {
7
+ "entity": "MJ: Test Types",
8
+ "field": "Name"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,12 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>About — Static File Server</title>
6
+ </head>
7
+ <body>
8
+ <h1>About</h1>
9
+ <p>This is the "about" page used by the Mode D demo's navigation test.</p>
10
+ <p><a href="/">← Home</a></p>
11
+ </body>
12
+ </html>
@@ -0,0 +1,27 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Static File Server — Mode D Demo</title>
6
+ <style>
7
+ body { font: 16px/1.5 system-ui, sans-serif; max-width: 640px; margin: 40px auto; padding: 0 16px; color: #222; }
8
+ h1 { color: #264FAF; }
9
+ nav { margin: 16px 0; }
10
+ nav a { margin-right: 12px; }
11
+ footer { margin-top: 40px; color: #888; font-size: 14px; }
12
+ </style>
13
+ </head>
14
+ <body>
15
+ <h1>Mode D Demo</h1>
16
+ <p>This static page is served by an nginx container that the regression test-runner
17
+ reaches via its compose-network hostname. The point of this example is to demonstrate
18
+ the docker-compose <em>overlay</em> pattern — bring your own app alongside the runner.</p>
19
+
20
+ <nav>
21
+ <a href="/">Home</a>
22
+ <a href="/about.html">About</a>
23
+ </nav>
24
+
25
+ <footer>Served by nginx:alpine inside the mj-regression compose network.</footer>
26
+ </body>
27
+ </html>
@@ -0,0 +1,16 @@
1
+ {
2
+ "schemaVersion": "1.0",
3
+ "name": "static-server",
4
+ "kind": "generic-web",
5
+ "description": "Mode D demo: target the static-file-server container that the docker-compose.app.yml overlay brings up alongside the test-runner.",
6
+
7
+ "baseUrl": "http://static-server:3000",
8
+ "allowedDomains": ["static-server"],
9
+
10
+ "auth": { "bindings": [] },
11
+
12
+ "suite": "Static Server Mode D Suite",
13
+ "extraMetadataDirs": [
14
+ "/app/static-server-metadata"
15
+ ]
16
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Database connection preflight.
3
+ *
4
+ * Opens a real connection using the same settings `mj migrate` uses, runs a
5
+ * trivial query, and classifies any failure into an actionable result. The
6
+ * headline case: detecting a self-signed / untrusted TLS certificate and
7
+ * suggesting `DB_TRUST_SERVER_CERTIFICATE` for local instances — instead of the
8
+ * cryptic "self-signed certificate" error surfacing mid-migration.
9
+ *
10
+ * Lives in MJCLI (not the installer) because the SQL driver (`mssql`) ships
11
+ * here; the installer surfaces it through `mj migrate` / `--check-connection`.
12
+ *
13
+ * @module lib/db-preflight
14
+ */
15
+ import type { MJConfig } from '../config.js';
16
+ /** The subset of config a connection needs — keeps this independently testable. */
17
+ export type DbConnectionConfig = Pick<MJConfig, 'dbPlatform' | 'dbHost' | 'dbPort' | 'dbDatabase' | 'codeGenLogin' | 'codeGenPassword' | 'dbEncrypt' | 'dbTrustServerCertificate'>;
18
+ /** Why a preflight connection failed, ordered by how specifically we can advise. */
19
+ export type DbPreflightReason = 'tls-untrusted-cert' | 'auth' | 'unreachable' | 'other';
20
+ /**
21
+ * Outcome of a connection preflight. A flat shape (rather than a discriminated
22
+ * union) because this package compiles without `strictNullChecks`, so union
23
+ * narrowing on `Ok` is unreliable; `Reason`/`Message`/`Suggestion` are populated
24
+ * only when `Ok` is false.
25
+ */
26
+ export interface DbPreflightResult {
27
+ /** True when a connection was established and a probe query succeeded. */
28
+ Ok: boolean;
29
+ /** Failure classification (set only when `Ok` is false). */
30
+ Reason?: DbPreflightReason;
31
+ /** Underlying error message (set only when `Ok` is false). */
32
+ Message?: string;
33
+ /** An actionable next step, when one can be offered. */
34
+ Suggestion?: string;
35
+ }
36
+ /**
37
+ * Open a real connection with the configured encrypt/trust settings, run a
38
+ * trivial query, and report success or a classified failure. Always closes the
39
+ * connection it opened.
40
+ */
41
+ export declare function verifyDatabaseConnection(config: DbConnectionConfig): Promise<DbPreflightResult>;
42
+ //# sourceMappingURL=db-preflight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-preflight.d.ts","sourceRoot":"","sources":["../../src/lib/db-preflight.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAAG,IAAI,CACnC,QAAQ,EACR,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,YAAY,GAAG,cAAc,GAAG,iBAAiB,GAAG,WAAW,GAAG,0BAA0B,CAClI,CAAC;AAEF,oFAAoF;AACpF,MAAM,MAAM,iBAAiB,GAAG,oBAAoB,GAAG,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC;AAExF;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,0EAA0E;IAC1E,EAAE,EAAE,OAAO,CAAC;IACZ,4DAA4D;IAC5D,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wDAAwD;IACxD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAiED;;;;GAIG;AACH,wBAAsB,wBAAwB,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAuBrG"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Database connection preflight.
3
+ *
4
+ * Opens a real connection using the same settings `mj migrate` uses, runs a
5
+ * trivial query, and classifies any failure into an actionable result. The
6
+ * headline case: detecting a self-signed / untrusted TLS certificate and
7
+ * suggesting `DB_TRUST_SERVER_CERTIFICATE` for local instances — instead of the
8
+ * cryptic "self-signed certificate" error surfacing mid-migration.
9
+ *
10
+ * Lives in MJCLI (not the installer) because the SQL driver (`mssql`) ships
11
+ * here; the installer surfaces it through `mj migrate` / `--check-connection`.
12
+ *
13
+ * @module lib/db-preflight
14
+ */
15
+ import { openConnection } from '../baseline/connection.js';
16
+ /** Map the config's DB platform to the connection helper's dialect. */
17
+ function dialectFor(platform) {
18
+ return platform === 'postgresql' ? 'postgres' : 'mssql';
19
+ }
20
+ /** True when the error indicates an untrusted / self-signed TLS certificate. */
21
+ function isUntrustedCertError(message) {
22
+ const m = message.toLowerCase();
23
+ return (m.includes('self-signed certificate') ||
24
+ m.includes('self signed certificate') ||
25
+ m.includes('depth_zero_self_signed_cert') ||
26
+ m.includes('self_signed_cert_in_chain') ||
27
+ m.includes('unable to verify the first certificate') ||
28
+ m.includes('unable to get local issuer certificate'));
29
+ }
30
+ /** True when the error indicates a credentials / login problem. */
31
+ function isAuthError(message) {
32
+ const m = message.toLowerCase();
33
+ return m.includes('login failed') || m.includes('password authentication failed');
34
+ }
35
+ /** True when the error indicates the host / port could not be reached. */
36
+ function isUnreachableError(message) {
37
+ const m = message.toLowerCase();
38
+ return (m.includes('econnrefused') || m.includes('etimedout') || m.includes('enotfound') || m.includes('connection timed out') || m.includes('could not connect'));
39
+ }
40
+ /** Turn a connection error message into a classified, actionable result. */
41
+ function classifyFailure(message, config) {
42
+ if (isUntrustedCertError(message)) {
43
+ // Only suggest trusting the cert when we aren't already trusting it — otherwise
44
+ // the problem is a different cert issue and that advice would mislead.
45
+ const suggestion = config.dbTrustServerCertificate
46
+ ? undefined
47
+ : 'The database is presenting a self-signed / untrusted TLS certificate. For a local or ' +
48
+ 'development instance, set DB_TRUST_SERVER_CERTIFICATE=1 in your .env (or ' +
49
+ 'dbTrustServerCertificate: true in mj.config.cjs) to trust it, then retry.';
50
+ return { Ok: false, Reason: 'tls-untrusted-cert', Message: message, Suggestion: suggestion };
51
+ }
52
+ if (isAuthError(message)) {
53
+ return {
54
+ Ok: false,
55
+ Reason: 'auth',
56
+ Message: message,
57
+ Suggestion: 'Check the database login (codeGenLogin / codeGenPassword) in your .env or mj.config.cjs.',
58
+ };
59
+ }
60
+ if (isUnreachableError(message)) {
61
+ return {
62
+ Ok: false,
63
+ Reason: 'unreachable',
64
+ Message: message,
65
+ Suggestion: `Confirm the database is running and reachable at ${config.dbHost}:${config.dbPort}.`,
66
+ };
67
+ }
68
+ return { Ok: false, Reason: 'other', Message: message };
69
+ }
70
+ /**
71
+ * Open a real connection with the configured encrypt/trust settings, run a
72
+ * trivial query, and report success or a classified failure. Always closes the
73
+ * connection it opened.
74
+ */
75
+ export async function verifyDatabaseConnection(config) {
76
+ const params = {
77
+ dialect: dialectFor(config.dbPlatform),
78
+ host: config.dbHost,
79
+ port: config.dbPort,
80
+ user: config.codeGenLogin,
81
+ password: config.codeGenPassword,
82
+ database: config.dbDatabase,
83
+ encrypt: config.dbEncrypt,
84
+ trustServerCertificate: config.dbTrustServerCertificate,
85
+ };
86
+ let conn;
87
+ try {
88
+ const runner = await openConnection(params);
89
+ conn = runner;
90
+ await runner.query('SELECT 1');
91
+ return { Ok: true };
92
+ }
93
+ catch (err) {
94
+ return classifyFailure(err instanceof Error ? err.message : String(err), config);
95
+ }
96
+ finally {
97
+ await closeQuietly(conn);
98
+ }
99
+ }
100
+ /** Best-effort close of a preflight connection; a teardown error must not mask the verdict. */
101
+ async function closeQuietly(conn) {
102
+ if (!conn)
103
+ return;
104
+ try {
105
+ await conn.close();
106
+ }
107
+ catch (closeErr) {
108
+ // Intentionally ignored: this connection only probed reachability, so a close
109
+ // failure is not actionable and must not overwrite the preflight result.
110
+ void closeErr;
111
+ }
112
+ }
113
+ //# sourceMappingURL=db-preflight.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db-preflight.js","sourceRoot":"","sources":["../../src/lib/db-preflight.ts"],"names":[],"mappings":"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"}