revisium 2.3.0 → 2.5.0-alpha.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 (235) hide show
  1. package/.github/workflows/build.yml +11 -30
  2. package/.github/workflows/ci.yml +0 -6
  3. package/.github/workflows/npm-publish.yml +13 -78
  4. package/.github/workflows/release-train.yml +90 -0
  5. package/AGENTS.md +98 -0
  6. package/README.md +31 -13
  7. package/dist/e2e/utils/cli-runner.d.ts +2 -0
  8. package/dist/e2e/utils/cli-runner.js +14 -7
  9. package/dist/e2e/utils/cli-runner.js.map +1 -1
  10. package/dist/e2e/utils/matrix-fixtures.d.ts +26 -0
  11. package/dist/e2e/utils/matrix-fixtures.js +109 -0
  12. package/dist/e2e/utils/matrix-fixtures.js.map +1 -0
  13. package/dist/e2e/utils/matrix-workspace.d.ts +25 -0
  14. package/dist/e2e/utils/matrix-workspace.js +61 -0
  15. package/dist/e2e/utils/matrix-workspace.js.map +1 -0
  16. package/dist/e2e/utils/standalone-api.d.ts +53 -0
  17. package/dist/e2e/utils/standalone-api.js +146 -0
  18. package/dist/e2e/utils/standalone-api.js.map +1 -0
  19. package/dist/e2e/utils/standalone-runner.d.ts +23 -0
  20. package/dist/e2e/utils/standalone-runner.js +182 -0
  21. package/dist/e2e/utils/standalone-runner.js.map +1 -0
  22. package/dist/package.json +6 -3
  23. package/dist/src/app.module.js +51 -0
  24. package/dist/src/app.module.js.map +1 -1
  25. package/dist/src/commands/auth/auth-command.utils.d.ts +8 -0
  26. package/dist/src/commands/auth/auth-command.utils.js +28 -0
  27. package/dist/src/commands/auth/auth-command.utils.js.map +1 -0
  28. package/dist/src/commands/auth/auth-login.command.d.ts +29 -0
  29. package/dist/src/commands/auth/auth-login.command.js +155 -0
  30. package/dist/src/commands/auth/auth-login.command.js.map +1 -0
  31. package/dist/src/commands/auth/auth-logout.command.d.ts +19 -0
  32. package/dist/src/commands/auth/auth-logout.command.js +86 -0
  33. package/dist/src/commands/auth/auth-logout.command.js.map +1 -0
  34. package/dist/src/commands/auth/auth-status.command.d.ts +19 -0
  35. package/dist/src/commands/auth/auth-status.command.js +101 -0
  36. package/dist/src/commands/auth/auth-status.command.js.map +1 -0
  37. package/dist/src/commands/auth/auth.command.d.ts +5 -0
  38. package/dist/src/commands/auth/auth.command.js +35 -0
  39. package/dist/src/commands/auth/auth.command.js.map +1 -0
  40. package/dist/src/commands/base-sync.command.d.ts +2 -2
  41. package/dist/src/commands/base-sync.command.js +1 -2
  42. package/dist/src/commands/base-sync.command.js.map +1 -1
  43. package/dist/src/commands/base.command.d.ts +2 -0
  44. package/dist/src/commands/base.command.js +13 -0
  45. package/dist/src/commands/base.command.js.map +1 -1
  46. package/dist/src/commands/context/context-create.command.d.ts +28 -0
  47. package/dist/src/commands/context/context-create.command.js +172 -0
  48. package/dist/src/commands/context/context-create.command.js.map +1 -0
  49. package/dist/src/commands/context/context-list.command.d.ts +9 -0
  50. package/dist/src/commands/context/context-list.command.js +46 -0
  51. package/dist/src/commands/context/context-list.command.js.map +1 -0
  52. package/dist/src/commands/context/context-remove.command.d.ts +9 -0
  53. package/dist/src/commands/context/context-remove.command.js +48 -0
  54. package/dist/src/commands/context/context-remove.command.js.map +1 -0
  55. package/dist/src/commands/context/context-show.command.d.ts +9 -0
  56. package/dist/src/commands/context/context-show.command.js +64 -0
  57. package/dist/src/commands/context/context-show.command.js.map +1 -0
  58. package/dist/src/commands/context/context-use.command.d.ts +9 -0
  59. package/dist/src/commands/context/context-use.command.js +45 -0
  60. package/dist/src/commands/context/context-use.command.js.map +1 -0
  61. package/dist/src/commands/context/context.command.d.ts +5 -0
  62. package/dist/src/commands/context/context.command.js +43 -0
  63. package/dist/src/commands/context/context.command.js.map +1 -0
  64. package/dist/src/commands/endpoint/endpoint-ensure.command.d.ts +18 -0
  65. package/dist/src/commands/endpoint/endpoint-ensure.command.js +90 -0
  66. package/dist/src/commands/endpoint/endpoint-ensure.command.js.map +1 -0
  67. package/dist/src/commands/endpoint/endpoint-list.command.d.ts +14 -0
  68. package/dist/src/commands/endpoint/endpoint-list.command.js +62 -0
  69. package/dist/src/commands/endpoint/endpoint-list.command.js.map +1 -0
  70. package/dist/src/commands/endpoint/endpoint.command.d.ts +5 -0
  71. package/dist/src/commands/endpoint/endpoint.command.js +34 -0
  72. package/dist/src/commands/endpoint/endpoint.command.js.map +1 -0
  73. package/dist/src/commands/example/example-bootstrap.command.d.ts +24 -0
  74. package/dist/src/commands/example/example-bootstrap.command.js +133 -0
  75. package/dist/src/commands/example/example-bootstrap.command.js.map +1 -0
  76. package/dist/src/commands/example/example.command.d.ts +5 -0
  77. package/dist/src/commands/example/example.command.js +33 -0
  78. package/dist/src/commands/example/example.command.js.map +1 -0
  79. package/dist/src/commands/instance/instance-add.command.d.ts +16 -0
  80. package/dist/src/commands/instance/instance-add.command.js +83 -0
  81. package/dist/src/commands/instance/instance-add.command.js.map +1 -0
  82. package/dist/src/commands/instance/instance-list.command.d.ts +9 -0
  83. package/dist/src/commands/instance/instance-list.command.js +45 -0
  84. package/dist/src/commands/instance/instance-list.command.js.map +1 -0
  85. package/dist/src/commands/instance/instance-remove.command.d.ts +9 -0
  86. package/dist/src/commands/instance/instance-remove.command.js +54 -0
  87. package/dist/src/commands/instance/instance-remove.command.js.map +1 -0
  88. package/dist/src/commands/instance/instance-show.command.d.ts +9 -0
  89. package/dist/src/commands/instance/instance-show.command.js +50 -0
  90. package/dist/src/commands/instance/instance-show.command.js.map +1 -0
  91. package/dist/src/commands/instance/instance.command.d.ts +5 -0
  92. package/dist/src/commands/instance/instance.command.js +41 -0
  93. package/dist/src/commands/instance/instance.command.js.map +1 -0
  94. package/dist/src/commands/migration/apply-migrations.command.js +1 -0
  95. package/dist/src/commands/migration/apply-migrations.command.js.map +1 -1
  96. package/dist/src/commands/project/project-ensure.command.d.ts +16 -0
  97. package/dist/src/commands/project/project-ensure.command.js +71 -0
  98. package/dist/src/commands/project/project-ensure.command.js.map +1 -0
  99. package/dist/src/commands/project/project.command.d.ts +5 -0
  100. package/dist/src/commands/project/project.command.js +33 -0
  101. package/dist/src/commands/project/project.command.js.map +1 -0
  102. package/dist/src/services/bootstrap/bootstrap.service.d.ts +112 -0
  103. package/dist/src/services/bootstrap/bootstrap.service.js +438 -0
  104. package/dist/src/services/bootstrap/bootstrap.service.js.map +1 -0
  105. package/dist/src/services/bootstrap/index.d.ts +1 -0
  106. package/dist/src/services/bootstrap/index.js +6 -0
  107. package/dist/src/services/bootstrap/index.js.map +1 -0
  108. package/dist/src/services/connection/api-client.d.ts +4 -0
  109. package/dist/src/services/connection/api-client.js +67 -5
  110. package/dist/src/services/connection/api-client.js.map +1 -1
  111. package/dist/src/services/connection/connection-factory.service.js +4 -3
  112. package/dist/src/services/connection/connection-factory.service.js.map +1 -1
  113. package/dist/src/services/connection/connection.service.d.ts +10 -2
  114. package/dist/src/services/connection/connection.service.js +49 -4
  115. package/dist/src/services/connection/connection.service.js.map +1 -1
  116. package/dist/src/services/credentials/credential-store.service.d.ts +19 -0
  117. package/dist/src/services/credentials/credential-store.service.js +112 -0
  118. package/dist/src/services/credentials/credential-store.service.js.map +1 -0
  119. package/dist/src/services/credentials/credential-target.service.d.ts +22 -0
  120. package/dist/src/services/credentials/credential-target.service.js +107 -0
  121. package/dist/src/services/credentials/credential-target.service.js.map +1 -0
  122. package/dist/src/services/credentials/index.d.ts +2 -0
  123. package/dist/src/services/credentials/index.js +8 -0
  124. package/dist/src/services/credentials/index.js.map +1 -0
  125. package/dist/src/services/index.d.ts +3 -0
  126. package/dist/src/services/index.js +3 -0
  127. package/dist/src/services/index.js.map +1 -1
  128. package/dist/src/services/url/auth-prompt.service.d.ts +3 -1
  129. package/dist/src/services/url/auth-prompt.service.js +3 -0
  130. package/dist/src/services/url/auth-prompt.service.js.map +1 -1
  131. package/dist/src/services/url/url-builder.service.js +3 -0
  132. package/dist/src/services/url/url-builder.service.js.map +1 -1
  133. package/dist/src/services/url/url-parser.service.d.ts +1 -1
  134. package/dist/src/services/url/url-parser.service.js +12 -6
  135. package/dist/src/services/url/url-parser.service.js.map +1 -1
  136. package/dist/src/services/workspace/index.d.ts +1 -0
  137. package/dist/src/services/workspace/index.js +11 -0
  138. package/dist/src/services/workspace/index.js.map +1 -0
  139. package/dist/src/services/workspace/workspace-config.service.d.ts +66 -0
  140. package/dist/src/services/workspace/workspace-config.service.js +313 -0
  141. package/dist/src/services/workspace/workspace-config.service.js.map +1 -0
  142. package/dist/tsconfig.build.tsbuildinfo +1 -1
  143. package/docs/authentication.md +69 -26
  144. package/docs/bootstrap-commands.md +105 -0
  145. package/docs/configuration.md +49 -22
  146. package/docs/url-format.md +27 -2
  147. package/docs/workspace-config.md +135 -0
  148. package/e2e/jest-matrix.json +14 -0
  149. package/e2e/matrix/M01-auth-commands.e2e-spec.ts +241 -0
  150. package/e2e/matrix/M02-instance-commands.e2e-spec.ts +213 -0
  151. package/e2e/matrix/M03-context-commands.e2e-spec.ts +279 -0
  152. package/e2e/matrix/M04-project-ensure.e2e-spec.ts +218 -0
  153. package/e2e/matrix/M05-endpoint-commands.e2e-spec.ts +172 -0
  154. package/e2e/matrix/M06-example-bootstrap.e2e-spec.ts +437 -0
  155. package/e2e/matrix/M07-migrate.e2e-spec.ts +229 -0
  156. package/e2e/matrix/M08-schema.e2e-spec.ts +163 -0
  157. package/e2e/matrix/M09-rows.e2e-spec.ts +185 -0
  158. package/e2e/matrix/M10-sync.e2e-spec.ts +224 -0
  159. package/e2e/matrix/M11-target-resolution.e2e-spec.ts +177 -0
  160. package/e2e/matrix/M12-error-paths.e2e-spec.ts +162 -0
  161. package/e2e/matrix/M13-no-auth-mode.e2e-spec.ts +119 -0
  162. package/e2e/matrix/M14-multi-instance-workspace.e2e-spec.ts +182 -0
  163. package/e2e/matrix/README.md +354 -0
  164. package/e2e/tests/01-auth.e2e-spec.ts +19 -0
  165. package/e2e/tests/07-workspace-config.e2e-spec.ts +492 -0
  166. package/e2e/tests/08-bootstrap.e2e-spec.ts +304 -0
  167. package/e2e/utils/cli-runner.ts +27 -8
  168. package/e2e/utils/matrix-fixtures.ts +141 -0
  169. package/e2e/utils/matrix-workspace.ts +106 -0
  170. package/e2e/utils/standalone-api.ts +314 -0
  171. package/e2e/utils/standalone-runner.ts +276 -0
  172. package/package.json +6 -3
  173. package/src/app.module.ts +54 -0
  174. package/src/commands/auth/__tests__/auth-command.utils.spec.ts +41 -0
  175. package/src/commands/auth/__tests__/auth-login.command.spec.ts +131 -0
  176. package/src/commands/auth/__tests__/auth-logout.command.spec.ts +85 -0
  177. package/src/commands/auth/__tests__/auth-status.command.spec.ts +106 -0
  178. package/src/commands/auth/__tests__/auth.command.spec.ts +18 -0
  179. package/src/commands/auth/auth-command.utils.ts +36 -0
  180. package/src/commands/auth/auth-login.command.ts +146 -0
  181. package/src/commands/auth/auth-logout.command.ts +71 -0
  182. package/src/commands/auth/auth-status.command.ts +89 -0
  183. package/src/commands/auth/auth.command.ts +20 -0
  184. package/src/commands/base-sync.command.ts +2 -3
  185. package/src/commands/base.command.ts +11 -0
  186. package/src/commands/context/context-create.command.ts +173 -0
  187. package/src/commands/context/context-list.command.ts +36 -0
  188. package/src/commands/context/context-remove.command.ts +35 -0
  189. package/src/commands/context/context-show.command.ts +59 -0
  190. package/src/commands/context/context-use.command.ts +31 -0
  191. package/src/commands/context/context.command.ts +28 -0
  192. package/src/commands/endpoint/__tests__/endpoint-ensure.command.spec.ts +129 -0
  193. package/src/commands/endpoint/__tests__/endpoint-list.command.spec.ts +59 -0
  194. package/src/commands/endpoint/__tests__/endpoint.command.spec.ts +14 -0
  195. package/src/commands/endpoint/endpoint-ensure.command.ts +74 -0
  196. package/src/commands/endpoint/endpoint-list.command.ts +48 -0
  197. package/src/commands/endpoint/endpoint.command.ts +19 -0
  198. package/src/commands/example/__tests__/example-bootstrap.command.spec.ts +145 -0
  199. package/src/commands/example/__tests__/example.command.spec.ts +14 -0
  200. package/src/commands/example/example-bootstrap.command.ts +122 -0
  201. package/src/commands/example/example.command.ts +18 -0
  202. package/src/commands/instance/instance-add.command.ts +72 -0
  203. package/src/commands/instance/instance-list.command.ts +31 -0
  204. package/src/commands/instance/instance-remove.command.ts +44 -0
  205. package/src/commands/instance/instance-show.command.ts +35 -0
  206. package/src/commands/instance/instance.command.ts +26 -0
  207. package/src/commands/migration/apply-migrations.command.ts +1 -0
  208. package/src/commands/project/__tests__/project-ensure.command.spec.ts +119 -0
  209. package/src/commands/project/__tests__/project.command.spec.ts +14 -0
  210. package/src/commands/project/project-ensure.command.ts +61 -0
  211. package/src/commands/project/project.command.ts +18 -0
  212. package/src/services/bootstrap/__tests__/bootstrap.service.spec.ts +798 -0
  213. package/src/services/bootstrap/bootstrap.service.ts +698 -0
  214. package/src/services/bootstrap/index.ts +12 -0
  215. package/src/services/connection/__tests__/api-client.spec.ts +262 -0
  216. package/src/services/connection/__tests__/connection-factory.service.spec.ts +2 -0
  217. package/src/services/connection/__tests__/connection.service.spec.ts +161 -0
  218. package/src/services/connection/api-client.ts +88 -5
  219. package/src/services/connection/connection-factory.service.ts +4 -3
  220. package/src/services/connection/connection.service.ts +74 -3
  221. package/src/services/credentials/__tests__/credential-store.service.spec.ts +128 -0
  222. package/src/services/credentials/__tests__/credential-target.service.spec.ts +126 -0
  223. package/src/services/credentials/credential-store.service.ts +145 -0
  224. package/src/services/credentials/credential-target.service.ts +145 -0
  225. package/src/services/credentials/index.ts +9 -0
  226. package/src/services/index.ts +3 -0
  227. package/src/services/url/__tests__/url-builder.service.spec.ts +17 -0
  228. package/src/services/url/__tests__/url-parser.service.spec.ts +162 -0
  229. package/src/services/url/auth-prompt.service.ts +6 -1
  230. package/src/services/url/url-builder.service.ts +4 -0
  231. package/src/services/url/url-parser.service.ts +21 -6
  232. package/src/services/workspace/__tests__/workspace-config.service.spec.ts +378 -0
  233. package/src/services/workspace/index.ts +14 -0
  234. package/src/services/workspace/workspace-config.service.ts +467 -0
  235. package/.github/workflows/bump-version.yml +0 -86
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Matrix M11 — Target / auth resolution precedence on a single command
3
+ * (`project ensure`).
4
+ *
5
+ * Standalone: one `--auth` instance.
6
+ *
7
+ * Prep:
8
+ * 1. Spawn standalone, login, mint API key.
9
+ * 2. Workspace tempdir gets a config with two contexts and one
10
+ * `currentContext`.
11
+ * 3. Project is created lazily per case so tests don't depend on each other.
12
+ */
13
+
14
+ import { runCli } from '../utils/cli-runner';
15
+ import {
16
+ CLEAR_REVISIUM_ENV,
17
+ createWorkspace,
18
+ removeWorkspace,
19
+ writeWorkspaceConfig,
20
+ } from '../utils/matrix-workspace';
21
+ import {
22
+ startStandalone,
23
+ StandaloneInstance,
24
+ } from '../utils/standalone-runner';
25
+
26
+ describe('M11 — target & auth resolution', () => {
27
+ let standalone: StandaloneInstance;
28
+ let apiKey: string;
29
+ let adminToken: string;
30
+ const workspaces: string[] = [];
31
+
32
+ beforeAll(async () => {
33
+ standalone = await startStandalone({
34
+ auth: true,
35
+ adminPassword: 'test-admin',
36
+ });
37
+ adminToken = await standalone.api.login('admin', 'test-admin');
38
+ apiKey = (
39
+ await standalone.api.mintApiKey('admin', { name: 'matrix-resolution' })
40
+ ).apiKey;
41
+ }, 180_000);
42
+
43
+ afterAll(async () => {
44
+ for (const workspace of workspaces) removeWorkspace(workspace);
45
+ workspaces.length = 0;
46
+ if (standalone) {
47
+ await standalone.stop();
48
+ }
49
+ });
50
+
51
+ function buildWorkspace(currentContext: string): string {
52
+ const workspace = createWorkspace('revisium-cli-matrix-resolution-');
53
+ workspaces.push(workspace);
54
+ writeWorkspaceConfig(workspace, {
55
+ currentContext,
56
+ instances: { local: { baseUrl: standalone.baseUrl, authMode: 'stored' } },
57
+ contexts: {
58
+ primary: {
59
+ instance: 'local',
60
+ organization: 'admin',
61
+ project: 'project-primary',
62
+ },
63
+ secondary: {
64
+ instance: 'local',
65
+ organization: 'admin',
66
+ project: 'project-secondary',
67
+ },
68
+ },
69
+ });
70
+ return workspace;
71
+ }
72
+
73
+ it('--url overrides --context', async () => {
74
+ const workspace = buildWorkspace('primary');
75
+ const projectFromUrl = `m11-url-${Date.now()}`;
76
+ const result = await runCli(
77
+ [
78
+ 'project',
79
+ 'ensure',
80
+ '--context',
81
+ 'primary',
82
+ '--url',
83
+ standalone.url({ project: projectFromUrl }),
84
+ '--json',
85
+ ],
86
+ {
87
+ cwd: workspace,
88
+ env: { ...CLEAR_REVISIUM_ENV, REVISIUM_API_KEY: apiKey },
89
+ },
90
+ );
91
+ expect(result.exitCode).toBe(0);
92
+ const summary = JSON.parse(result.stdout) as { project: string };
93
+ expect(summary.project).toBe(projectFromUrl);
94
+ });
95
+
96
+ it('--context overrides REVISIUM_URL', async () => {
97
+ const workspace = buildWorkspace('primary');
98
+ const result = await runCli(
99
+ ['project', 'ensure', '--context', 'secondary', '--json'],
100
+ {
101
+ cwd: workspace,
102
+ env: {
103
+ ...CLEAR_REVISIUM_ENV,
104
+ REVISIUM_URL: standalone.url({ project: 'project-from-env' }),
105
+ REVISIUM_API_KEY: apiKey,
106
+ },
107
+ },
108
+ );
109
+ expect(result.exitCode).toBe(0);
110
+ const summary = JSON.parse(result.stdout) as { project: string };
111
+ expect(summary.project).toBe('project-secondary');
112
+ });
113
+
114
+ it('REVISIUM_URL overrides currentContext', async () => {
115
+ const workspace = buildWorkspace('primary');
116
+ const result = await runCli(['project', 'ensure', '--json'], {
117
+ cwd: workspace,
118
+ env: {
119
+ ...CLEAR_REVISIUM_ENV,
120
+ REVISIUM_URL: standalone.url({ project: 'project-from-env' }),
121
+ REVISIUM_API_KEY: apiKey,
122
+ },
123
+ });
124
+ expect(result.exitCode).toBe(0);
125
+ const summary = JSON.parse(result.stdout) as { project: string };
126
+ expect(summary.project).toBe('project-from-env');
127
+ });
128
+
129
+ it('falls back to currentContext when nothing else is set', async () => {
130
+ const workspace = buildWorkspace('secondary');
131
+ const result = await runCli(['project', 'ensure', '--json'], {
132
+ cwd: workspace,
133
+ env: { ...CLEAR_REVISIUM_ENV, REVISIUM_API_KEY: apiKey },
134
+ });
135
+ expect(result.exitCode).toBe(0);
136
+ const summary = JSON.parse(result.stdout) as { project: string };
137
+ expect(summary.project).toBe('project-secondary');
138
+ });
139
+
140
+ it('--token overrides REVISIUM_API_KEY', async () => {
141
+ const workspace = buildWorkspace('primary');
142
+ const project = `m11-token-${Date.now()}`;
143
+ const result = await runCli(
144
+ [
145
+ 'project',
146
+ 'ensure',
147
+ '--url',
148
+ standalone.url({ project }),
149
+ '--token',
150
+ adminToken,
151
+ ],
152
+ {
153
+ cwd: workspace,
154
+ env: { ...CLEAR_REVISIUM_ENV, REVISIUM_API_KEY: 'wrong-key' },
155
+ },
156
+ );
157
+ expect(result.exitCode).toBe(0);
158
+ });
159
+
160
+ it('?token=... in URL overrides REVISIUM_TOKEN', async () => {
161
+ const workspace = buildWorkspace('primary');
162
+ const project = `m11-url-token-${Date.now()}`;
163
+ const result = await runCli(
164
+ [
165
+ 'project',
166
+ 'ensure',
167
+ '--url',
168
+ `${standalone.url({ project })}?token=${adminToken}`,
169
+ ],
170
+ {
171
+ cwd: workspace,
172
+ env: { ...CLEAR_REVISIUM_ENV, REVISIUM_TOKEN: 'wrong-token' },
173
+ },
174
+ );
175
+ expect(result.exitCode).toBe(0);
176
+ });
177
+ });
@@ -0,0 +1,162 @@
1
+ /**
2
+ * Matrix M12 — Error paths and remediation messages.
3
+ *
4
+ * Standalone: one `--auth` instance.
5
+ *
6
+ * Prep: spawn standalone, login, mint a key. Tests deliberately send the
7
+ * wrong credentials, missing files, malformed config, etc., and assert on
8
+ * non-zero exit codes + helpful stderr.
9
+ */
10
+
11
+ import * as fs from 'node:fs';
12
+ import { runCli } from '../utils/cli-runner';
13
+ import {
14
+ CLEAR_REVISIUM_ENV,
15
+ createWorkspace,
16
+ removeWorkspace,
17
+ writeWorkspaceConfig,
18
+ workspaceFile,
19
+ } from '../utils/matrix-workspace';
20
+ import {
21
+ startStandalone,
22
+ StandaloneInstance,
23
+ } from '../utils/standalone-runner';
24
+
25
+ describe('M12 — error paths', () => {
26
+ let standalone: StandaloneInstance;
27
+ let apiKey: string;
28
+ const workspaces: string[] = [];
29
+
30
+ beforeAll(async () => {
31
+ standalone = await startStandalone({
32
+ auth: true,
33
+ adminPassword: 'test-admin',
34
+ });
35
+ await standalone.api.login('admin', 'test-admin');
36
+ apiKey = (
37
+ await standalone.api.mintApiKey('admin', { name: 'matrix-errors' })
38
+ ).apiKey;
39
+ }, 180_000);
40
+
41
+ afterAll(async () => {
42
+ for (const workspace of workspaces) removeWorkspace(workspace);
43
+ workspaces.length = 0;
44
+ if (standalone) {
45
+ await standalone.stop();
46
+ }
47
+ });
48
+
49
+ function newWorkspace(): string {
50
+ const ws = createWorkspace('revisium-cli-matrix-errors-');
51
+ workspaces.push(ws);
52
+ return ws;
53
+ }
54
+
55
+ it('unauthenticated request to --auth standalone surfaces an auth error', async () => {
56
+ const workspace = newWorkspace();
57
+ const result = await runCli(
58
+ ['project', 'ensure', '--url', standalone.url({ project: 'whatever' })],
59
+ { cwd: workspace, env: { ...CLEAR_REVISIUM_ENV } },
60
+ );
61
+ expect(result.exitCode).not.toBe(0);
62
+ expect(result.stderr.toLowerCase()).toMatch(
63
+ /auth|credential|unauthorized|api key|401/,
64
+ );
65
+ });
66
+
67
+ it('wrong API key surfaces a credential error', async () => {
68
+ const workspace = newWorkspace();
69
+ const result = await runCli(
70
+ ['project', 'ensure', '--url', standalone.url({ project: 'whatever' })],
71
+ {
72
+ cwd: workspace,
73
+ env: { ...CLEAR_REVISIUM_ENV, REVISIUM_API_KEY: 'invalid-key' },
74
+ },
75
+ );
76
+ expect(result.exitCode).not.toBe(0);
77
+ expect(result.stderr.toLowerCase()).toMatch(
78
+ /unauthorized|forbidden|invalid|credential|api key|401|403/,
79
+ );
80
+ });
81
+
82
+ it('broken workspace config surfaces a parse error with the file path', async () => {
83
+ const workspace = newWorkspace();
84
+ const path = workspaceFile(
85
+ workspace,
86
+ '.revisium',
87
+ 'revisium-cli.config.json',
88
+ );
89
+ fs.mkdirSync(workspaceFile(workspace, '.revisium'), { recursive: true });
90
+ fs.writeFileSync(path, '{ broken json');
91
+ const result = await runCli(['instance', 'list'], {
92
+ cwd: workspace,
93
+ env: CLEAR_REVISIUM_ENV,
94
+ });
95
+ expect(result.exitCode).not.toBe(0);
96
+ expect(result.stderr).toContain('revisium-cli.config.json');
97
+ });
98
+
99
+ it('instance remove against an unknown name fails fast', async () => {
100
+ const workspace = newWorkspace();
101
+ writeWorkspaceConfig(workspace, { instances: {} });
102
+ const result = await runCli(['instance', 'remove', 'unknown'], {
103
+ cwd: workspace,
104
+ env: CLEAR_REVISIUM_ENV,
105
+ });
106
+ expect(result.exitCode).not.toBe(0);
107
+ });
108
+
109
+ it('context use against an unknown name fails fast', async () => {
110
+ const workspace = newWorkspace();
111
+ writeWorkspaceConfig(workspace, {
112
+ instances: { local: { baseUrl: standalone.baseUrl } },
113
+ contexts: {},
114
+ });
115
+ const result = await runCli(['context', 'use', 'unknown'], {
116
+ cwd: workspace,
117
+ env: CLEAR_REVISIUM_ENV,
118
+ });
119
+ expect(result.exitCode).not.toBe(0);
120
+ });
121
+
122
+ it('migrate apply with a missing file surfaces ENOENT-like error', async () => {
123
+ const workspace = newWorkspace();
124
+ const result = await runCli(
125
+ [
126
+ 'migrate',
127
+ 'apply',
128
+ '--file',
129
+ workspaceFile(workspace, 'absent.json'),
130
+ '--commit',
131
+ '--url',
132
+ standalone.url({ project: 'whatever' }),
133
+ ],
134
+ {
135
+ cwd: workspace,
136
+ env: { ...CLEAR_REVISIUM_ENV, REVISIUM_API_KEY: apiKey },
137
+ },
138
+ );
139
+ expect(result.exitCode).not.toBe(0);
140
+ expect(result.stderr.toLowerCase()).toMatch(/file|not found|enoent/);
141
+ });
142
+
143
+ it('example bootstrap with a missing config fails before contacting the server', async () => {
144
+ const workspace = newWorkspace();
145
+ const result = await runCli(
146
+ [
147
+ 'example',
148
+ 'bootstrap',
149
+ '--config',
150
+ workspaceFile(workspace, 'absent.json'),
151
+ '--url',
152
+ standalone.url({ project: 'whatever' }),
153
+ ],
154
+ {
155
+ cwd: workspace,
156
+ env: { ...CLEAR_REVISIUM_ENV, REVISIUM_API_KEY: apiKey },
157
+ },
158
+ );
159
+ expect(result.exitCode).not.toBe(0);
160
+ expect(result.stderr).toMatch(/Could not read bootstrap config/);
161
+ });
162
+ });
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Matrix M13 — `authMode: "none"` flow against an unauthenticated standalone.
3
+ *
4
+ * Standalone: one instance WITHOUT `--auth`.
5
+ *
6
+ * Prep:
7
+ * 1. Spawn standalone without --auth (no admin credentials needed).
8
+ * 2. Use REST API directly to create projects (no auth required).
9
+ * 3. Per-test workspace tempdir.
10
+ */
11
+
12
+ import { runCli } from '../utils/cli-runner';
13
+ import {
14
+ CLEAR_REVISIUM_ENV,
15
+ createWorkspace,
16
+ removeWorkspace,
17
+ uniqueCredentialStoreService,
18
+ writeWorkspaceConfig,
19
+ } from '../utils/matrix-workspace';
20
+ import {
21
+ startStandalone,
22
+ StandaloneInstance,
23
+ } from '../utils/standalone-runner';
24
+
25
+ describe('M13 — authMode none', () => {
26
+ let standalone: StandaloneInstance;
27
+ const credentialStoreService = uniqueCredentialStoreService();
28
+ const workspaces: string[] = [];
29
+
30
+ beforeAll(async () => {
31
+ standalone = await startStandalone({ auth: false });
32
+ }, 180_000);
33
+
34
+ afterAll(async () => {
35
+ for (const workspace of workspaces) removeWorkspace(workspace);
36
+ workspaces.length = 0;
37
+ if (standalone) {
38
+ await standalone.stop();
39
+ }
40
+ });
41
+
42
+ function newWorkspace(): string {
43
+ const ws = createWorkspace('revisium-cli-matrix-noauth-');
44
+ workspaces.push(ws);
45
+ return ws;
46
+ }
47
+
48
+ function buildEnv(): Record<string, string> {
49
+ return {
50
+ ...CLEAR_REVISIUM_ENV,
51
+ REVISIUM_CREDENTIAL_STORE_SERVICE: credentialStoreService,
52
+ };
53
+ }
54
+
55
+ it('project ensure works against an authMode "none" instance without credentials', async () => {
56
+ const workspace = newWorkspace();
57
+ writeWorkspaceConfig(workspace, {
58
+ currentContext: 'local-default',
59
+ instances: { local: { baseUrl: standalone.baseUrl, authMode: 'none' } },
60
+ contexts: {
61
+ 'local-default': {
62
+ instance: 'local',
63
+ organization: 'admin',
64
+ project: `m13-${Date.now()}`,
65
+ },
66
+ },
67
+ });
68
+ const result = await runCli(['project', 'ensure', '--json'], {
69
+ cwd: workspace,
70
+ env: buildEnv(),
71
+ });
72
+ expect(result.exitCode).toBe(0);
73
+ });
74
+
75
+ it('auth login refuses to save a credential for authMode "none" without --force', async () => {
76
+ const workspace = newWorkspace();
77
+ writeWorkspaceConfig(workspace, {
78
+ instances: { local: { baseUrl: standalone.baseUrl, authMode: 'none' } },
79
+ });
80
+ const result = await runCli(
81
+ ['auth', 'login', '--instance', 'local', '--api-key-stdin'],
82
+ { cwd: workspace, env: buildEnv(), stdin: 'rev_dummy\n' },
83
+ );
84
+ expect(result.exitCode).not.toBe(0);
85
+ });
86
+
87
+ it('example bootstrap works without any credential', async () => {
88
+ const workspace = newWorkspace();
89
+ const project = `m13-bootstrap-${Date.now()}`;
90
+ writeWorkspaceConfig(workspace, {
91
+ currentContext: 'local-default',
92
+ instances: { local: { baseUrl: standalone.baseUrl, authMode: 'none' } },
93
+ contexts: {
94
+ 'local-default': {
95
+ instance: 'local',
96
+ organization: 'admin',
97
+ project,
98
+ },
99
+ },
100
+ });
101
+ const configPath = `${workspace}/bootstrap.config.json`;
102
+ const fs = await import('node:fs');
103
+ fs.writeFileSync(
104
+ configPath,
105
+ JSON.stringify({
106
+ projectName: project,
107
+ endpoints: ['REST_API'],
108
+ tables: [],
109
+ rows: [],
110
+ }),
111
+ 'utf-8',
112
+ );
113
+ const result = await runCli(
114
+ ['example', 'bootstrap', '--config', configPath, '--json'],
115
+ { cwd: workspace, env: buildEnv(), timeout: 120_000 },
116
+ );
117
+ expect(result.exitCode).toBe(0);
118
+ });
119
+ });
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Matrix M14 — Two standalone instances driven from a single workspace.
3
+ *
4
+ * Prep:
5
+ * 1. Spawn `primary` and `secondary` standalone instances, both `--auth`.
6
+ * 2. Login + mint API key on each; per-suite credential-store service so
7
+ * saved creds for `primary` and `secondary` are isolated from the host
8
+ * keyring.
9
+ * 3. Workspace tempdir holds two instances + two contexts (one per).
10
+ */
11
+
12
+ import { runCli } from '../utils/cli-runner';
13
+ import {
14
+ CLEAR_REVISIUM_ENV,
15
+ createWorkspace,
16
+ removeWorkspace,
17
+ uniqueCredentialStoreService,
18
+ writeWorkspaceConfig,
19
+ } from '../utils/matrix-workspace';
20
+ import {
21
+ startStandalone,
22
+ StandaloneInstance,
23
+ } from '../utils/standalone-runner';
24
+
25
+ describe('M14 — multi-instance workspace', () => {
26
+ let primary: StandaloneInstance;
27
+ let secondary: StandaloneInstance;
28
+ let primaryKey: string;
29
+ let secondaryKey: string;
30
+ const credentialStoreService = uniqueCredentialStoreService();
31
+ const workspaces: string[] = [];
32
+
33
+ beforeAll(async () => {
34
+ primary = await startStandalone({
35
+ auth: true,
36
+ adminPassword: 'test-admin-primary',
37
+ });
38
+ secondary = await startStandalone({
39
+ auth: true,
40
+ adminPassword: 'test-admin-secondary',
41
+ });
42
+
43
+ await primary.api.login('admin', 'test-admin-primary');
44
+ await secondary.api.login('admin', 'test-admin-secondary');
45
+ primaryKey = (
46
+ await primary.api.mintApiKey('admin', { name: 'matrix-primary' })
47
+ ).apiKey;
48
+ secondaryKey = (
49
+ await secondary.api.mintApiKey('admin', { name: 'matrix-secondary' })
50
+ ).apiKey;
51
+ }, 360_000);
52
+
53
+ afterAll(async () => {
54
+ for (const workspace of workspaces) removeWorkspace(workspace);
55
+ workspaces.length = 0;
56
+ await Promise.all([
57
+ primary ? primary.stop() : Promise.resolve(),
58
+ secondary ? secondary.stop() : Promise.resolve(),
59
+ ]);
60
+ });
61
+
62
+ function setup(): { workspace: string; env: Record<string, string> } {
63
+ const workspace = createWorkspace('revisium-cli-matrix-multi-');
64
+ workspaces.push(workspace);
65
+ writeWorkspaceConfig(workspace, {
66
+ instances: {
67
+ primary: { baseUrl: primary.baseUrl, authMode: 'stored' },
68
+ secondary: { baseUrl: secondary.baseUrl, authMode: 'stored' },
69
+ },
70
+ contexts: {
71
+ 'primary-default': {
72
+ instance: 'primary',
73
+ organization: 'admin',
74
+ project: 'p',
75
+ },
76
+ 'secondary-default': {
77
+ instance: 'secondary',
78
+ organization: 'admin',
79
+ project: 's',
80
+ },
81
+ },
82
+ });
83
+ const env: Record<string, string> = {
84
+ ...CLEAR_REVISIUM_ENV,
85
+ REVISIUM_CREDENTIAL_STORE_SERVICE: credentialStoreService,
86
+ };
87
+ return { workspace, env };
88
+ }
89
+
90
+ it('saves independent credentials per instance', async () => {
91
+ const { workspace, env } = setup();
92
+ const loginPrimary = await runCli(
93
+ ['auth', 'login', '--instance', 'primary', '--api-key-stdin'],
94
+ { cwd: workspace, env, stdin: primaryKey + '\n' },
95
+ );
96
+ expect(loginPrimary.exitCode).toBe(0);
97
+ const loginSecondary = await runCli(
98
+ ['auth', 'login', '--instance', 'secondary', '--api-key-stdin'],
99
+ { cwd: workspace, env, stdin: secondaryKey + '\n' },
100
+ );
101
+ expect(loginSecondary.exitCode).toBe(0);
102
+
103
+ const primaryStatus = await runCli(
104
+ ['auth', 'status', '--instance', 'primary'],
105
+ { cwd: workspace, env },
106
+ );
107
+ expect(primaryStatus.exitCode).toBe(0);
108
+ expect(primaryStatus.stdout).toContain('Saved credential found');
109
+
110
+ const secondaryStatus = await runCli(
111
+ ['auth', 'status', '--instance', 'secondary'],
112
+ { cwd: workspace, env },
113
+ );
114
+ expect(secondaryStatus.exitCode).toBe(0);
115
+ expect(secondaryStatus.stdout).toContain('Saved credential found');
116
+
117
+ // Independence: each instance points at its own baseUrl, so the two
118
+ // status outputs should differ on the host portion. Removing one
119
+ // credential must not affect the other.
120
+ expect(primaryStatus.stdout).not.toEqual(secondaryStatus.stdout);
121
+
122
+ const logoutPrimary = await runCli(
123
+ ['auth', 'logout', '--instance', 'primary'],
124
+ { cwd: workspace, env },
125
+ );
126
+ expect(logoutPrimary.exitCode).toBe(0);
127
+
128
+ const primaryAfter = await runCli(
129
+ ['auth', 'status', '--instance', 'primary'],
130
+ { cwd: workspace, env },
131
+ );
132
+ expect(primaryAfter.stdout).toContain('No saved credential found');
133
+
134
+ const secondaryAfter = await runCli(
135
+ ['auth', 'status', '--instance', 'secondary'],
136
+ { cwd: workspace, env },
137
+ );
138
+ expect(secondaryAfter.stdout).toContain('Saved credential found');
139
+ });
140
+
141
+ it('context switching flips the target between instances', async () => {
142
+ const { workspace, env } = setup();
143
+ await runCli(
144
+ ['auth', 'login', '--instance', 'primary', '--api-key-stdin'],
145
+ {
146
+ cwd: workspace,
147
+ env,
148
+ stdin: primaryKey + '\n',
149
+ },
150
+ );
151
+ await runCli(
152
+ ['auth', 'login', '--instance', 'secondary', '--api-key-stdin'],
153
+ {
154
+ cwd: workspace,
155
+ env,
156
+ stdin: secondaryKey + '\n',
157
+ },
158
+ );
159
+
160
+ await runCli(['context', 'use', 'primary-default'], {
161
+ cwd: workspace,
162
+ env,
163
+ });
164
+ const primaryEnsure = await runCli(['project', 'ensure', '--json'], {
165
+ cwd: workspace,
166
+ env,
167
+ });
168
+ expect(primaryEnsure.exitCode).toBe(0);
169
+ expect(await primary.api.projectExists('admin', 'p')).toBe(true);
170
+
171
+ await runCli(['context', 'use', 'secondary-default'], {
172
+ cwd: workspace,
173
+ env,
174
+ });
175
+ const secondaryEnsure = await runCli(['project', 'ensure', '--json'], {
176
+ cwd: workspace,
177
+ env,
178
+ });
179
+ expect(secondaryEnsure.exitCode).toBe(0);
180
+ expect(await secondary.api.projectExists('admin', 's')).toBe(true);
181
+ });
182
+ });