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,59 @@
1
+ import { EndpointListCommand } from '../endpoint-list.command';
2
+ import { BootstrapService } from 'src/services/bootstrap';
3
+ import { LoggerService } from 'src/services/common';
4
+
5
+ describe('EndpointListCommand', () => {
6
+ let bootstrapService: { listEndpoints: jest.Mock };
7
+ let logger: { info: jest.Mock };
8
+ let command: EndpointListCommand;
9
+ let logSpy: jest.SpyInstance;
10
+
11
+ beforeEach(() => {
12
+ bootstrapService = { listEndpoints: jest.fn() };
13
+ logger = { info: jest.fn() };
14
+ command = new EndpointListCommand(
15
+ bootstrapService as unknown as BootstrapService,
16
+ logger as unknown as LoggerService,
17
+ );
18
+ logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
19
+ });
20
+
21
+ afterEach(() => {
22
+ logSpy.mockRestore();
23
+ });
24
+
25
+ it('logs each endpoint id and type', async () => {
26
+ bootstrapService.listEndpoints.mockResolvedValue([
27
+ { id: 'rest', type: 'REST_API' },
28
+ { id: 'graphql', type: 'GRAPHQL' },
29
+ ]);
30
+
31
+ await command.run([], {});
32
+
33
+ expect(logger.info).toHaveBeenCalledWith('rest\tREST_API');
34
+ expect(logger.info).toHaveBeenCalledWith('graphql\tGRAPHQL');
35
+ });
36
+
37
+ it('reports when no endpoints exist', async () => {
38
+ bootstrapService.listEndpoints.mockResolvedValue([]);
39
+
40
+ await command.run([], {});
41
+
42
+ expect(logger.info).toHaveBeenCalledWith('No generated endpoints found');
43
+ });
44
+
45
+ it('emits JSON when --json is set', async () => {
46
+ const endpoints = [{ id: 'rest', type: 'REST_API' }];
47
+ bootstrapService.listEndpoints.mockResolvedValue(endpoints);
48
+
49
+ await command.run([], { json: true });
50
+
51
+ expect(logSpy).toHaveBeenCalledWith(JSON.stringify({ endpoints }, null, 2));
52
+ expect(logger.info).not.toHaveBeenCalled();
53
+ });
54
+
55
+ it('parses --json flag', () => {
56
+ expect(command.parseJson('true')).toBe(true);
57
+ expect(command.parseJson()).toBe(true);
58
+ });
59
+ });
@@ -0,0 +1,14 @@
1
+ import { EndpointCommand } from '../endpoint.command';
2
+
3
+ describe('EndpointCommand', () => {
4
+ it('shows help when no endpoint subcommand is provided', async () => {
5
+ const command = new EndpointCommand();
6
+ const help = jest.fn();
7
+
8
+ Object.assign(command, { command: { help } });
9
+
10
+ await command.run();
11
+
12
+ expect(help).toHaveBeenCalled();
13
+ });
14
+ });
@@ -0,0 +1,74 @@
1
+ import { Option, SubCommand } from 'nest-commander';
2
+ import { BaseCommand, BaseOptions } from 'src/commands/base.command';
3
+ import { BootstrapService, EndpointType } from 'src/services/bootstrap';
4
+ import { LoggerService } from 'src/services/common';
5
+ import { parseBooleanOption } from 'src/utils/parse-boolean.utils';
6
+
7
+ type Options = BaseOptions & {
8
+ type: EndpointType;
9
+ dryRun?: boolean;
10
+ json?: boolean;
11
+ };
12
+
13
+ @SubCommand({
14
+ name: 'ensure',
15
+ description: 'Ensure a generated endpoint exists on the selected revision',
16
+ })
17
+ export class EndpointEnsureCommand extends BaseCommand {
18
+ constructor(
19
+ private readonly bootstrapService: BootstrapService,
20
+ private readonly logger: LoggerService,
21
+ ) {
22
+ super();
23
+ }
24
+
25
+ async run(_inputs: string[], options: Options): Promise<void> {
26
+ if (!options.type) {
27
+ throw new Error('Error: --type option is required');
28
+ }
29
+
30
+ const result = await this.bootstrapService.ensureEndpoint(
31
+ options,
32
+ options.type,
33
+ options.dryRun,
34
+ );
35
+ const target = await this.bootstrapService.resolveTarget(options);
36
+ const endpointId = result.endpoint.id || '<dry-run>';
37
+ const action = result.status === 'created' ? 'Created' : 'Found';
38
+ const hint = this.bootstrapService.formatEndpointHint(target, options.type);
39
+
40
+ if (options.json) {
41
+ console.log(JSON.stringify({ ...result, hint }, null, 2));
42
+ return;
43
+ }
44
+
45
+ this.logger.success(`${action} endpoint ${endpointId} (${options.type})`);
46
+ this.logger.info(`Revision: ${result.revision}`);
47
+ this.logger.info(`Hint: ${hint}`);
48
+ }
49
+
50
+ @Option({
51
+ flags: '--type <type>',
52
+ description: 'Endpoint type: REST_API or GRAPHQL',
53
+ required: true,
54
+ })
55
+ parseType(value: string): EndpointType {
56
+ return this.bootstrapService.parseEndpointType(value);
57
+ }
58
+
59
+ @Option({
60
+ flags: '--dry-run [boolean]',
61
+ description: 'Plan changes without writing',
62
+ })
63
+ parseDryRun(value?: string): boolean {
64
+ return parseBooleanOption(value);
65
+ }
66
+
67
+ @Option({
68
+ flags: '--json [boolean]',
69
+ description: 'Print machine-readable JSON',
70
+ })
71
+ parseJson(value?: string): boolean {
72
+ return parseBooleanOption(value);
73
+ }
74
+ }
@@ -0,0 +1,48 @@
1
+ import { Option, SubCommand } from 'nest-commander';
2
+ import { BaseCommand, BaseOptions } from 'src/commands/base.command';
3
+ import { BootstrapService } from 'src/services/bootstrap';
4
+ import { LoggerService } from 'src/services/common';
5
+ import { parseBooleanOption } from 'src/utils/parse-boolean.utils';
6
+
7
+ type Options = BaseOptions & {
8
+ json?: boolean;
9
+ };
10
+
11
+ @SubCommand({
12
+ name: 'list',
13
+ description: 'List generated endpoints on the selected revision',
14
+ })
15
+ export class EndpointListCommand extends BaseCommand {
16
+ constructor(
17
+ private readonly bootstrapService: BootstrapService,
18
+ private readonly logger: LoggerService,
19
+ ) {
20
+ super();
21
+ }
22
+
23
+ async run(_inputs: string[], options: Options): Promise<void> {
24
+ const endpoints = await this.bootstrapService.listEndpoints(options);
25
+
26
+ if (options.json) {
27
+ console.log(JSON.stringify({ endpoints }, null, 2));
28
+ return;
29
+ }
30
+
31
+ if (endpoints.length === 0) {
32
+ this.logger.info('No generated endpoints found');
33
+ return;
34
+ }
35
+
36
+ for (const endpoint of endpoints) {
37
+ this.logger.info(`${endpoint.id}\t${endpoint.type}`);
38
+ }
39
+ }
40
+
41
+ @Option({
42
+ flags: '--json [boolean]',
43
+ description: 'Print machine-readable JSON',
44
+ })
45
+ parseJson(value?: string): boolean {
46
+ return parseBooleanOption(value);
47
+ }
48
+ }
@@ -0,0 +1,19 @@
1
+ import { Command, CommandRunner } from 'nest-commander';
2
+ import { EndpointEnsureCommand } from './endpoint-ensure.command';
3
+ import { EndpointListCommand } from './endpoint-list.command';
4
+
5
+ @Command({
6
+ name: 'endpoint',
7
+ description: 'Manage generated Revisium endpoints',
8
+ subCommands: [EndpointEnsureCommand, EndpointListCommand],
9
+ })
10
+ export class EndpointCommand extends CommandRunner {
11
+ constructor() {
12
+ super();
13
+ }
14
+
15
+ run(): Promise<void> {
16
+ this.command.help();
17
+ return Promise.resolve();
18
+ }
19
+ }
@@ -0,0 +1,145 @@
1
+ import { ExampleBootstrapCommand } from '../example-bootstrap.command';
2
+ import {
3
+ BootstrapService,
4
+ ExampleBootstrapSummary,
5
+ } from 'src/services/bootstrap';
6
+ import { LoggerService } from 'src/services/common';
7
+
8
+ describe('ExampleBootstrapCommand', () => {
9
+ let bootstrapService: {
10
+ bootstrapExample: jest.Mock;
11
+ parseEndpointType: jest.Mock;
12
+ };
13
+ let logger: { info: jest.Mock; success: jest.Mock; summary: jest.Mock };
14
+ let command: ExampleBootstrapCommand;
15
+ let logSpy: jest.SpyInstance;
16
+ let summary: ExampleBootstrapSummary;
17
+
18
+ beforeEach(() => {
19
+ summary = {
20
+ dryRun: false,
21
+ target: {
22
+ organization: 'admin',
23
+ project: 'dictionary',
24
+ branch: 'master',
25
+ revision: 'draft',
26
+ },
27
+ project: {
28
+ organization: 'admin',
29
+ project: 'dictionary',
30
+ branch: 'master',
31
+ projectStatus: 'created',
32
+ branchStatus: 'created',
33
+ dryRun: false,
34
+ },
35
+ tables: { created: ['Faq'], skipped: [], conflicts: [] },
36
+ rows: { created: [], skipped: ['x/y'], conflicts: [] },
37
+ endpoints: { created: ['REST_API'], skipped: [], conflicts: [] },
38
+ commit: { status: 'created', revisionId: 'rev-1' },
39
+ };
40
+ bootstrapService = {
41
+ bootstrapExample: jest.fn().mockResolvedValue(summary),
42
+ parseEndpointType: jest.fn((value: string) => value),
43
+ };
44
+ logger = {
45
+ info: jest.fn(),
46
+ success: jest.fn(),
47
+ summary: jest.fn(),
48
+ };
49
+ command = new ExampleBootstrapCommand(
50
+ bootstrapService as unknown as BootstrapService,
51
+ logger as unknown as LoggerService,
52
+ );
53
+ logSpy = jest.spyOn(console, 'log').mockImplementation(() => {});
54
+ });
55
+
56
+ afterEach(() => {
57
+ logSpy.mockRestore();
58
+ });
59
+
60
+ it('rejects when --config is missing', async () => {
61
+ await expect(
62
+ command.run([], { config: undefined as never }),
63
+ ).rejects.toThrow('--config option is required');
64
+ });
65
+
66
+ it('forwards options to BootstrapService.bootstrapExample', async () => {
67
+ await command.run([], {
68
+ config: './bootstrap.json',
69
+ url: 'revisium://local',
70
+ context: 'ctx',
71
+ commit: true,
72
+ dryRun: false,
73
+ endpoint: ['REST_API'],
74
+ });
75
+
76
+ expect(bootstrapService.bootstrapExample).toHaveBeenCalledWith({
77
+ url: 'revisium://local',
78
+ context: 'ctx',
79
+ configPath: './bootstrap.json',
80
+ commit: true,
81
+ dryRun: false,
82
+ endpointOverrides: ['REST_API'],
83
+ });
84
+ });
85
+
86
+ it('prints a human-readable summary by default', async () => {
87
+ await command.run([], { config: './bootstrap.json' });
88
+
89
+ expect(logger.summary).toHaveBeenCalledWith(
90
+ 'Bootstrap admin/dictionary/master:draft',
91
+ );
92
+ expect(logger.info).toHaveBeenCalledWith(
93
+ 'Project: created, branch: created',
94
+ );
95
+ expect(logger.info).toHaveBeenCalledWith(
96
+ 'Tables: created 1, skipped 0, conflicts 0',
97
+ );
98
+ expect(logger.info).toHaveBeenCalledWith(
99
+ 'Rows: created 0, skipped 1, conflicts 0',
100
+ );
101
+ expect(logger.info).toHaveBeenCalledWith(
102
+ 'Endpoints: created 1, skipped 0, conflicts 0',
103
+ );
104
+ expect(logger.info).toHaveBeenCalledWith('Commit: created');
105
+ expect(logger.info).toHaveBeenCalledWith('Revision: rev-1');
106
+ });
107
+
108
+ it('prefixes the summary with "Dry run: " when dry-run', async () => {
109
+ summary.dryRun = true;
110
+ summary.commit = { status: 'dry-run', message: 'm' };
111
+ delete (summary.commit as { revisionId?: string }).revisionId;
112
+
113
+ await command.run([], { config: './bootstrap.json' });
114
+
115
+ expect(logger.summary).toHaveBeenCalledWith(
116
+ 'Dry run: Bootstrap admin/dictionary/master:draft',
117
+ );
118
+ expect(logger.info).not.toHaveBeenCalledWith(
119
+ expect.stringContaining('Revision:'),
120
+ );
121
+ });
122
+
123
+ it('emits JSON when --json is set', async () => {
124
+ await command.run([], { config: './bootstrap.json', json: true });
125
+
126
+ expect(logSpy).toHaveBeenCalledWith(JSON.stringify(summary, null, 2));
127
+ expect(logger.summary).not.toHaveBeenCalled();
128
+ });
129
+
130
+ it('parses --endpoint repeatedly via BootstrapService.parseEndpointType', () => {
131
+ const first = command.parseEndpoint('REST_API');
132
+ expect(first).toEqual(['REST_API']);
133
+ const second = command.parseEndpoint('GRAPHQL', first);
134
+ expect(second).toEqual(['REST_API', 'GRAPHQL']);
135
+ expect(bootstrapService.parseEndpointType).toHaveBeenCalledTimes(2);
136
+ });
137
+
138
+ it('parses boolean and string flags', () => {
139
+ expect(command.parseConfig('./bootstrap.json')).toBe('./bootstrap.json');
140
+ expect(command.parseCommit('true')).toBe(true);
141
+ expect(command.parseCommit()).toBe(true);
142
+ expect(command.parseDryRun()).toBe(true);
143
+ expect(command.parseJson()).toBe(true);
144
+ });
145
+ });
@@ -0,0 +1,14 @@
1
+ import { ExampleCommand } from '../example.command';
2
+
3
+ describe('ExampleCommand', () => {
4
+ it('shows help when no example subcommand is provided', async () => {
5
+ const command = new ExampleCommand();
6
+ const help = jest.fn();
7
+
8
+ Object.assign(command, { command: { help } });
9
+
10
+ await command.run();
11
+
12
+ expect(help).toHaveBeenCalled();
13
+ });
14
+ });
@@ -0,0 +1,122 @@
1
+ import { Option, SubCommand } from 'nest-commander';
2
+ import { BaseCommand, BaseOptions } from 'src/commands/base.command';
3
+ import {
4
+ BootstrapResourceSummary,
5
+ BootstrapService,
6
+ EndpointType,
7
+ ExampleBootstrapSummary,
8
+ } from 'src/services/bootstrap';
9
+ import { LoggerService } from 'src/services/common';
10
+ import { parseBooleanOption } from 'src/utils/parse-boolean.utils';
11
+
12
+ type Options = BaseOptions & {
13
+ config: string;
14
+ commit?: boolean;
15
+ dryRun?: boolean;
16
+ json?: boolean;
17
+ endpoint?: EndpointType[];
18
+ };
19
+
20
+ @SubCommand({
21
+ name: 'bootstrap',
22
+ description: 'Bootstrap an example project from a config file',
23
+ })
24
+ export class ExampleBootstrapCommand extends BaseCommand {
25
+ constructor(
26
+ private readonly bootstrapService: BootstrapService,
27
+ private readonly logger: LoggerService,
28
+ ) {
29
+ super();
30
+ }
31
+
32
+ async run(_inputs: string[], options: Options): Promise<void> {
33
+ if (!options.config) {
34
+ throw new Error('Error: --config option is required');
35
+ }
36
+
37
+ const summary = await this.bootstrapService.bootstrapExample({
38
+ url: options.url,
39
+ context: options.context,
40
+ configPath: options.config,
41
+ commit: options.commit,
42
+ dryRun: options.dryRun,
43
+ endpointOverrides: options.endpoint,
44
+ });
45
+
46
+ if (options.json) {
47
+ console.log(JSON.stringify(summary, null, 2));
48
+ return;
49
+ }
50
+
51
+ this.printSummary(summary);
52
+ }
53
+
54
+ private printSummary(summary: ExampleBootstrapSummary): void {
55
+ const mode = summary.dryRun ? 'Dry run: ' : '';
56
+ this.logger.summary(
57
+ `${mode}Bootstrap ${summary.target.organization}/${summary.target.project}/${summary.target.branch}:${summary.target.revision}`,
58
+ );
59
+ this.logger.info(
60
+ `Project: ${summary.project.projectStatus}, branch: ${summary.project.branchStatus}`,
61
+ );
62
+ this.logger.info(this.formatSummary('Tables', summary.tables));
63
+ this.logger.info(this.formatSummary('Rows', summary.rows));
64
+ this.logger.info(this.formatSummary('Endpoints', summary.endpoints));
65
+ this.logger.info(`Commit: ${summary.commit.status}`);
66
+ if (summary.commit.revisionId) {
67
+ this.logger.info(`Revision: ${summary.commit.revisionId}`);
68
+ }
69
+ }
70
+
71
+ private formatSummary<T extends string>(
72
+ label: string,
73
+ summary: BootstrapResourceSummary<T>,
74
+ ): string {
75
+ return `${label}: created ${summary.created.length}, skipped ${summary.skipped.length}, conflicts ${summary.conflicts.length}`;
76
+ }
77
+
78
+ @Option({
79
+ flags: '--config <path>',
80
+ description: 'Bootstrap config JSON file',
81
+ required: true,
82
+ })
83
+ parseConfig(value: string): string {
84
+ return value;
85
+ }
86
+
87
+ @Option({
88
+ flags: '-c, --commit [boolean]',
89
+ description: 'Create a revision after bootstrap changes',
90
+ })
91
+ parseCommit(value?: string): boolean {
92
+ return parseBooleanOption(value);
93
+ }
94
+
95
+ @Option({
96
+ flags: '--dry-run [boolean]',
97
+ description: 'Plan changes without writing',
98
+ })
99
+ parseDryRun(value?: string): boolean {
100
+ return parseBooleanOption(value);
101
+ }
102
+
103
+ @Option({
104
+ flags: '--json [boolean]',
105
+ description: 'Print machine-readable JSON',
106
+ })
107
+ parseJson(value?: string): boolean {
108
+ return parseBooleanOption(value);
109
+ }
110
+
111
+ @Option({
112
+ flags: '--endpoint <type>',
113
+ description:
114
+ 'Endpoint type override. Repeat for multiple values. Replaces config endpoints when provided.',
115
+ })
116
+ parseEndpoint(value: string, previous?: EndpointType[]): EndpointType[] {
117
+ return [
118
+ ...(previous || []),
119
+ this.bootstrapService.parseEndpointType(value),
120
+ ];
121
+ }
122
+ }
@@ -0,0 +1,18 @@
1
+ import { Command, CommandRunner } from 'nest-commander';
2
+ import { ExampleBootstrapCommand } from './example-bootstrap.command';
3
+
4
+ @Command({
5
+ name: 'example',
6
+ description: 'Bootstrap Revisium example projects',
7
+ subCommands: [ExampleBootstrapCommand],
8
+ })
9
+ export class ExampleCommand extends CommandRunner {
10
+ constructor() {
11
+ super();
12
+ }
13
+
14
+ run(): Promise<void> {
15
+ this.command.help();
16
+ return Promise.resolve();
17
+ }
18
+ }
@@ -0,0 +1,72 @@
1
+ import { CommandRunner, Option, SubCommand } from 'nest-commander';
2
+ import { LoggerService } from 'src/services/common';
3
+ import {
4
+ WorkspaceAuthMode,
5
+ WorkspaceConfigService,
6
+ } from 'src/services/workspace';
7
+
8
+ interface Options {
9
+ url?: string;
10
+ auth?: WorkspaceAuthMode;
11
+ }
12
+
13
+ @SubCommand({
14
+ name: 'add',
15
+ arguments: '<name>',
16
+ description: 'Add or update a workspace Revisium instance',
17
+ })
18
+ export class InstanceAddCommand extends CommandRunner {
19
+ constructor(
20
+ private readonly workspaceConfig: WorkspaceConfigService,
21
+ private readonly logger: LoggerService,
22
+ ) {
23
+ super();
24
+ }
25
+
26
+ async run(inputs: string[], options: Options): Promise<void> {
27
+ const name = inputs[0];
28
+ if (!name) {
29
+ throw new Error('Error: instance name is required');
30
+ }
31
+ if (!options.url) {
32
+ throw new Error('Error: --url option is required');
33
+ }
34
+
35
+ const loaded = await this.workspaceConfig.loadOrCreate();
36
+ const baseUrl = this.workspaceConfig.normalizeBaseUrl(options.url);
37
+ const authMode = options.auth || 'stored';
38
+ const existed = Boolean(loaded.config.instances[name]);
39
+
40
+ loaded.config.instances[name] = {
41
+ baseUrl,
42
+ authMode,
43
+ };
44
+
45
+ await this.workspaceConfig.save(loaded.path, loaded.config);
46
+
47
+ this.logger.success(
48
+ `${existed ? 'Updated' : 'Added'} instance "${name}" (${baseUrl}, auth: ${authMode})`,
49
+ );
50
+ this.logger.info(`Config: ${loaded.path}`);
51
+ }
52
+
53
+ @Option({
54
+ flags: '--url <url>',
55
+ description: 'Revisium server URL, for example revisium://localhost:9222',
56
+ required: true,
57
+ })
58
+ parseUrl(value: string) {
59
+ return value;
60
+ }
61
+
62
+ @Option({
63
+ flags: '--auth <mode>',
64
+ description: 'Authentication mode: stored or none',
65
+ })
66
+ parseAuth(value: string): WorkspaceAuthMode {
67
+ if (value !== 'stored' && value !== 'none') {
68
+ throw new Error('Auth mode must be "stored" or "none"');
69
+ }
70
+ return value;
71
+ }
72
+ }
@@ -0,0 +1,31 @@
1
+ import { CommandRunner, SubCommand } from 'nest-commander';
2
+ import { LoggerService } from 'src/services/common';
3
+ import { WorkspaceConfigService } from 'src/services/workspace';
4
+
5
+ @SubCommand({
6
+ name: 'list',
7
+ description: 'List workspace Revisium instances',
8
+ })
9
+ export class InstanceListCommand extends CommandRunner {
10
+ constructor(
11
+ private readonly workspaceConfig: WorkspaceConfigService,
12
+ private readonly logger: LoggerService,
13
+ ) {
14
+ super();
15
+ }
16
+
17
+ async run(): Promise<void> {
18
+ const loaded = await this.workspaceConfig.load();
19
+ if (!loaded || Object.keys(loaded.config.instances).length === 0) {
20
+ this.logger.info('No Revisium instances configured in this workspace.');
21
+ return;
22
+ }
23
+
24
+ this.logger.info(`Config: ${loaded.path}`);
25
+ for (const [name, instance] of Object.entries(loaded.config.instances)) {
26
+ this.logger.info(
27
+ `${name}\t${instance.baseUrl}\tauth:${instance.authMode || 'stored'}`,
28
+ );
29
+ }
30
+ }
31
+ }
@@ -0,0 +1,44 @@
1
+ import { CommandRunner, SubCommand } from 'nest-commander';
2
+ import { LoggerService } from 'src/services/common';
3
+ import { WorkspaceConfigService } from 'src/services/workspace';
4
+
5
+ @SubCommand({
6
+ name: 'remove',
7
+ arguments: '<name>',
8
+ description: 'Remove a workspace Revisium instance',
9
+ })
10
+ export class InstanceRemoveCommand extends CommandRunner {
11
+ constructor(
12
+ private readonly workspaceConfig: WorkspaceConfigService,
13
+ private readonly logger: LoggerService,
14
+ ) {
15
+ super();
16
+ }
17
+
18
+ async run(inputs: string[]): Promise<void> {
19
+ const name = inputs[0];
20
+ if (!name) {
21
+ throw new Error('Error: instance name is required');
22
+ }
23
+
24
+ const loaded = await this.workspaceConfig.load();
25
+ if (!loaded || !Object.hasOwn(loaded.config.instances, name)) {
26
+ throw new Error(`Revisium instance "${name}" was not found`);
27
+ }
28
+
29
+ const usedByContexts = Object.entries(loaded.config.contexts)
30
+ .filter(([, context]) => context.instance === name)
31
+ .map(([contextName]) => contextName);
32
+
33
+ if (usedByContexts.length > 0) {
34
+ throw new Error(
35
+ `Cannot remove instance "${name}" because it is used by contexts: ${usedByContexts.join(', ')}`,
36
+ );
37
+ }
38
+
39
+ delete loaded.config.instances[name];
40
+ await this.workspaceConfig.save(loaded.path, loaded.config);
41
+
42
+ this.logger.success(`Removed instance "${name}"`);
43
+ }
44
+ }