@living-architecture/riviere-cli 0.2.1

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 (95) hide show
  1. package/README.md +11 -0
  2. package/dist/cli.d.ts +8 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +65 -0
  5. package/dist/command-test-fixtures.d.ts +110 -0
  6. package/dist/command-test-fixtures.d.ts.map +1 -0
  7. package/dist/command-test-fixtures.js +184 -0
  8. package/dist/commands/builder/add-component.d.ts +4 -0
  9. package/dist/commands/builder/add-component.d.ts.map +1 -0
  10. package/dist/commands/builder/add-component.js +204 -0
  11. package/dist/commands/builder/add-domain.d.ts +3 -0
  12. package/dist/commands/builder/add-domain.d.ts.map +1 -0
  13. package/dist/commands/builder/add-domain.js +56 -0
  14. package/dist/commands/builder/add-source.d.ts +3 -0
  15. package/dist/commands/builder/add-source.d.ts.map +1 -0
  16. package/dist/commands/builder/add-source.js +28 -0
  17. package/dist/commands/builder/check-consistency.d.ts +3 -0
  18. package/dist/commands/builder/check-consistency.d.ts.map +1 -0
  19. package/dist/commands/builder/check-consistency.js +27 -0
  20. package/dist/commands/builder/component-checklist.d.ts +3 -0
  21. package/dist/commands/builder/component-checklist.d.ts.map +1 -0
  22. package/dist/commands/builder/component-checklist.js +43 -0
  23. package/dist/commands/builder/component-summary.d.ts +3 -0
  24. package/dist/commands/builder/component-summary.d.ts.map +1 -0
  25. package/dist/commands/builder/component-summary.js +23 -0
  26. package/dist/commands/builder/enrich.d.ts +3 -0
  27. package/dist/commands/builder/enrich.d.ts.map +1 -0
  28. package/dist/commands/builder/enrich.js +85 -0
  29. package/dist/commands/builder/finalize.d.ts +3 -0
  30. package/dist/commands/builder/finalize.d.ts.map +1 -0
  31. package/dist/commands/builder/finalize.js +37 -0
  32. package/dist/commands/builder/init.d.ts +3 -0
  33. package/dist/commands/builder/init.d.ts.map +1 -0
  34. package/dist/commands/builder/init.js +100 -0
  35. package/dist/commands/builder/link-external.d.ts +3 -0
  36. package/dist/commands/builder/link-external.d.ts.map +1 -0
  37. package/dist/commands/builder/link-external.js +70 -0
  38. package/dist/commands/builder/link-http.d.ts +3 -0
  39. package/dist/commands/builder/link-http.d.ts.map +1 -0
  40. package/dist/commands/builder/link-http.js +130 -0
  41. package/dist/commands/builder/link-infrastructure.d.ts +7 -0
  42. package/dist/commands/builder/link-infrastructure.d.ts.map +1 -0
  43. package/dist/commands/builder/link-infrastructure.js +41 -0
  44. package/dist/commands/builder/link.d.ts +3 -0
  45. package/dist/commands/builder/link.d.ts.map +1 -0
  46. package/dist/commands/builder/link.js +73 -0
  47. package/dist/commands/builder/validate.d.ts +3 -0
  48. package/dist/commands/builder/validate.d.ts.map +1 -0
  49. package/dist/commands/builder/validate.js +29 -0
  50. package/dist/commands/query/component-output.d.ts +9 -0
  51. package/dist/commands/query/component-output.d.ts.map +1 -0
  52. package/dist/commands/query/component-output.js +8 -0
  53. package/dist/commands/query/components.d.ts +3 -0
  54. package/dist/commands/query/components.d.ts.map +1 -0
  55. package/dist/commands/query/components.js +45 -0
  56. package/dist/commands/query/domains.d.ts +3 -0
  57. package/dist/commands/query/domains.d.ts.map +1 -0
  58. package/dist/commands/query/domains.js +22 -0
  59. package/dist/commands/query/entry-points.d.ts +3 -0
  60. package/dist/commands/query/entry-points.d.ts.map +1 -0
  61. package/dist/commands/query/entry-points.js +22 -0
  62. package/dist/commands/query/load-graph.d.ts +16 -0
  63. package/dist/commands/query/load-graph.d.ts.map +1 -0
  64. package/dist/commands/query/load-graph.js +50 -0
  65. package/dist/commands/query/orphans.d.ts +3 -0
  66. package/dist/commands/query/orphans.d.ts.map +1 -0
  67. package/dist/commands/query/orphans.js +22 -0
  68. package/dist/commands/query/search.d.ts +3 -0
  69. package/dist/commands/query/search.d.ts.map +1 -0
  70. package/dist/commands/query/search.js +25 -0
  71. package/dist/commands/query/trace.d.ts +3 -0
  72. package/dist/commands/query/trace.d.ts.map +1 -0
  73. package/dist/commands/query/trace.js +41 -0
  74. package/dist/component-types.d.ts +15 -0
  75. package/dist/component-types.d.ts.map +1 -0
  76. package/dist/component-types.js +39 -0
  77. package/dist/error-codes.d.ts +15 -0
  78. package/dist/error-codes.d.ts.map +1 -0
  79. package/dist/error-codes.js +15 -0
  80. package/dist/file-existence.d.ts +2 -0
  81. package/dist/file-existence.d.ts.map +1 -0
  82. package/dist/file-existence.js +16 -0
  83. package/dist/graph-path.d.ts +3 -0
  84. package/dist/graph-path.d.ts.map +1 -0
  85. package/dist/graph-path.js +8 -0
  86. package/dist/index.d.ts +5 -0
  87. package/dist/index.d.ts.map +1 -0
  88. package/dist/index.js +7 -0
  89. package/dist/output.d.ts +17 -0
  90. package/dist/output.d.ts.map +1 -0
  91. package/dist/output.js +7 -0
  92. package/dist/validation.d.ts +12 -0
  93. package/dist/validation.d.ts.map +1 -0
  94. package/dist/validation.js +51 -0
  95. package/package.json +34 -0
package/README.md ADDED
@@ -0,0 +1,11 @@
1
+ # cli
2
+
3
+ This library was generated with [Nx](https://nx.dev).
4
+
5
+ ## Building
6
+
7
+ Run `nx build cli` to build the library.
8
+
9
+ ## Running unit tests
10
+
11
+ Run `nx test cli` to execute the unit tests via [Vitest](https://vitest.dev/).
package/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import { Command } from 'commander';
2
+ interface PackageJson {
3
+ version: string;
4
+ }
5
+ export declare function parsePackageJson(pkg: unknown): PackageJson;
6
+ export declare function createProgram(): Command;
7
+ export {};
8
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,WAAW,CAQ1D;AASD,wBAAgB,aAAa,IAAI,OAAO,CAmCvC"}
package/dist/cli.js ADDED
@@ -0,0 +1,65 @@
1
+ import { Command } from 'commander';
2
+ import { createRequire } from 'module';
3
+ import { createAddComponentCommand } from './commands/builder/add-component';
4
+ import { createAddDomainCommand } from './commands/builder/add-domain';
5
+ import { createAddSourceCommand } from './commands/builder/add-source';
6
+ import { createInitCommand } from './commands/builder/init';
7
+ import { createLinkCommand } from './commands/builder/link';
8
+ import { createLinkExternalCommand } from './commands/builder/link-external';
9
+ import { createLinkHttpCommand } from './commands/builder/link-http';
10
+ import { createValidateCommand } from './commands/builder/validate';
11
+ import { createFinalizeCommand } from './commands/builder/finalize';
12
+ import { createEnrichCommand } from './commands/builder/enrich';
13
+ import { createComponentSummaryCommand } from './commands/builder/component-summary';
14
+ import { createComponentChecklistCommand } from './commands/builder/component-checklist';
15
+ import { createCheckConsistencyCommand } from './commands/builder/check-consistency';
16
+ import { createEntryPointsCommand } from './commands/query/entry-points';
17
+ import { createDomainsCommand } from './commands/query/domains';
18
+ import { createTraceCommand } from './commands/query/trace';
19
+ import { createOrphansCommand } from './commands/query/orphans';
20
+ import { createComponentsCommand } from './commands/query/components';
21
+ import { createSearchCommand } from './commands/query/search';
22
+ export function parsePackageJson(pkg) {
23
+ if (typeof pkg !== 'object' || pkg === null || !('version' in pkg)) {
24
+ throw new Error('Invalid package.json: missing version field');
25
+ }
26
+ if (typeof pkg.version !== 'string') {
27
+ throw new Error('Invalid package.json: version must be a string');
28
+ }
29
+ return { version: pkg.version };
30
+ }
31
+ function loadPackageJson() {
32
+ const require = createRequire(import.meta.url);
33
+ return parsePackageJson(require('../package.json'));
34
+ }
35
+ const packageJson = loadPackageJson();
36
+ export function createProgram() {
37
+ const program = new Command();
38
+ program.name('riviere').version(packageJson.version);
39
+ const builderCmd = program
40
+ .command('builder')
41
+ .description('Commands for building a graph');
42
+ builderCmd.addCommand(createAddComponentCommand());
43
+ builderCmd.addCommand(createAddDomainCommand());
44
+ builderCmd.addCommand(createAddSourceCommand());
45
+ builderCmd.addCommand(createInitCommand());
46
+ builderCmd.addCommand(createLinkCommand());
47
+ builderCmd.addCommand(createLinkExternalCommand());
48
+ builderCmd.addCommand(createLinkHttpCommand());
49
+ builderCmd.addCommand(createValidateCommand());
50
+ builderCmd.addCommand(createFinalizeCommand());
51
+ builderCmd.addCommand(createEnrichCommand());
52
+ builderCmd.addCommand(createComponentSummaryCommand());
53
+ builderCmd.addCommand(createComponentChecklistCommand());
54
+ builderCmd.addCommand(createCheckConsistencyCommand());
55
+ const queryCmd = program
56
+ .command('query')
57
+ .description('Commands for querying a graph');
58
+ queryCmd.addCommand(createEntryPointsCommand());
59
+ queryCmd.addCommand(createDomainsCommand());
60
+ queryCmd.addCommand(createTraceCommand());
61
+ queryCmd.addCommand(createOrphansCommand());
62
+ queryCmd.addCommand(createComponentsCommand());
63
+ queryCmd.addCommand(createSearchCommand());
64
+ return program;
65
+ }
@@ -0,0 +1,110 @@
1
+ export interface ErrorOutput {
2
+ success: false;
3
+ error: {
4
+ code: string;
5
+ message: string;
6
+ suggestions: string[];
7
+ };
8
+ }
9
+ export declare function isErrorOutput(value: unknown): value is ErrorOutput;
10
+ export declare function parseErrorOutput(consoleOutput: string[]): ErrorOutput;
11
+ export declare function parseSuccessOutput<T>(consoleOutput: string[], guard: (value: unknown) => value is T, errorMessage: string): T;
12
+ export interface TestContext {
13
+ testDir: string;
14
+ originalCwd: string;
15
+ consoleOutput: string[];
16
+ }
17
+ export declare function createTestContext(): TestContext;
18
+ export declare function setupCommandTest(ctx: TestContext): void;
19
+ export declare function createGraph(testDir: string, graphData: object, subPath?: string): Promise<string>;
20
+ export declare const baseMetadata: {
21
+ sources: {
22
+ repository: string;
23
+ }[];
24
+ domains: {
25
+ orders: {
26
+ description: string;
27
+ systemType: string;
28
+ };
29
+ };
30
+ };
31
+ export declare const sourceLocation: {
32
+ repository: string;
33
+ filePath: string;
34
+ };
35
+ export declare const useCaseComponent: {
36
+ id: string;
37
+ type: string;
38
+ name: string;
39
+ domain: string;
40
+ module: string;
41
+ sourceLocation: {
42
+ repository: string;
43
+ filePath: string;
44
+ };
45
+ };
46
+ export declare const apiComponent: {
47
+ id: string;
48
+ type: string;
49
+ name: string;
50
+ domain: string;
51
+ module: string;
52
+ sourceLocation: {
53
+ repository: string;
54
+ filePath: string;
55
+ };
56
+ apiType: string;
57
+ httpMethod: string;
58
+ path: string;
59
+ };
60
+ export declare const eventHandlerComponent: {
61
+ id: string;
62
+ type: string;
63
+ name: string;
64
+ domain: string;
65
+ module: string;
66
+ sourceLocation: {
67
+ repository: string;
68
+ filePath: string;
69
+ };
70
+ subscribedEvents: string[];
71
+ };
72
+ export declare const validLink: {
73
+ id: string;
74
+ source: string;
75
+ target: string;
76
+ type: string;
77
+ };
78
+ export declare function createGraphWithDomain(testDir: string, domainName: string): Promise<void>;
79
+ export declare function createGraphWithSource(testDir: string, repository: string): Promise<void>;
80
+ export declare function createGraphWithComponent(testDir: string, component: object): Promise<void>;
81
+ export declare const domainOpComponent: {
82
+ id: string;
83
+ type: string;
84
+ name: string;
85
+ domain: string;
86
+ module: string;
87
+ operationName: string;
88
+ sourceLocation: {
89
+ repository: string;
90
+ filePath: string;
91
+ };
92
+ };
93
+ export declare const simpleUseCaseComponent: {
94
+ id: string;
95
+ type: string;
96
+ name: string;
97
+ domain: string;
98
+ module: string;
99
+ sourceLocation: {
100
+ repository: string;
101
+ filePath: string;
102
+ };
103
+ };
104
+ export declare function hasSuccessOutputStructure(value: unknown): value is {
105
+ success: true;
106
+ data: object;
107
+ };
108
+ export declare function testCommandRegistration(commandName: string): void;
109
+ export declare function testCustomGraphPath<T>(ctx: TestContext, commandArgs: string[], parseOutput: (consoleOutput: string[]) => T): Promise<T>;
110
+ //# sourceMappingURL=command-test-fixtures.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-test-fixtures.d.ts","sourceRoot":"","sources":["../src/command-test-fixtures.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,EAAE,CAAC;KACvB,CAAC;CACH;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAKlE;AAED,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,GAAG,WAAW,CAUrE;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAClC,aAAa,EAAE,MAAM,EAAE,EACvB,KAAK,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EACrC,YAAY,EAAE,MAAM,GACnB,CAAC,CAUH;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,wBAAgB,iBAAiB,IAAI,WAAW,CAM/C;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,GAAG,IAAI,CAcvD;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,SAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CAM3G;AAED,eAAO,MAAM,YAAY;;;;;;;;;;CAGxB,CAAC;AAEF,eAAO,MAAM,cAAc;;;CAAmF,CAAC;AAE/G,eAAO,MAAM,gBAAgB;;;;;;;;;;CAO5B,CAAC;AAEF,eAAO,MAAM,YAAY;;;;;;;;;;;;;CAUxB,CAAC;AAEF,eAAO,MAAM,qBAAqB;;;;;;;;;;;CAQjC,CAAC;AAEF,eAAO,MAAM,SAAS;;;;;CAKrB,CAAC;AAEF,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAa9F;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAa9F;AAED,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAahG;AAED,eAAO,MAAM,iBAAiB;;;;;;;;;;;CAQ7B,CAAC;AAEF,eAAO,MAAM,sBAAsB;;;;;;;;;;CAOlC,CAAC;AAEF,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI;IAAE,OAAO,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAKlG;AAED,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAOjE;AAED,wBAAsB,mBAAmB,CAAC,CAAC,EACzC,GAAG,EAAE,WAAW,EAChB,WAAW,EAAE,MAAM,EAAE,EACrB,WAAW,EAAE,CAAC,aAAa,EAAE,MAAM,EAAE,KAAK,CAAC,GAC1C,OAAO,CAAC,CAAC,CAAC,CASZ"}
@@ -0,0 +1,184 @@
1
+ import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { vi, beforeEach, afterEach, expect, it } from 'vitest';
5
+ import { createProgram } from './cli';
6
+ export function isErrorOutput(value) {
7
+ if (typeof value !== 'object' || value === null)
8
+ return false;
9
+ if (!('success' in value) || value.success !== false)
10
+ return false;
11
+ if (!('error' in value) || typeof value.error !== 'object' || value.error === null)
12
+ return false;
13
+ return true;
14
+ }
15
+ export function parseErrorOutput(consoleOutput) {
16
+ const firstLine = consoleOutput[0];
17
+ if (firstLine === undefined) {
18
+ throw new Error('Expected console output but got empty array');
19
+ }
20
+ const parsed = JSON.parse(firstLine);
21
+ if (!isErrorOutput(parsed)) {
22
+ throw new Error('Invalid error output');
23
+ }
24
+ return parsed;
25
+ }
26
+ export function parseSuccessOutput(consoleOutput, guard, errorMessage) {
27
+ const firstLine = consoleOutput[0];
28
+ if (firstLine === undefined) {
29
+ throw new Error('Expected console output but got empty array');
30
+ }
31
+ const parsed = JSON.parse(firstLine);
32
+ if (!guard(parsed)) {
33
+ throw new Error(errorMessage);
34
+ }
35
+ return parsed;
36
+ }
37
+ export function createTestContext() {
38
+ return {
39
+ testDir: '',
40
+ originalCwd: '',
41
+ consoleOutput: [],
42
+ };
43
+ }
44
+ export function setupCommandTest(ctx) {
45
+ beforeEach(async () => {
46
+ ctx.testDir = await mkdtemp(join(tmpdir(), 'riviere-test-'));
47
+ ctx.originalCwd = process.cwd();
48
+ ctx.consoleOutput = [];
49
+ process.chdir(ctx.testDir);
50
+ vi.spyOn(console, 'log').mockImplementation((msg) => ctx.consoleOutput.push(msg));
51
+ });
52
+ afterEach(async () => {
53
+ vi.restoreAllMocks();
54
+ process.chdir(ctx.originalCwd);
55
+ await rm(ctx.testDir, { recursive: true });
56
+ });
57
+ }
58
+ export async function createGraph(testDir, graphData, subPath = '.riviere') {
59
+ const graphDir = join(testDir, subPath);
60
+ await mkdir(graphDir, { recursive: true });
61
+ const graphPath = join(graphDir, 'graph.json');
62
+ await writeFile(graphPath, JSON.stringify(graphData), 'utf-8');
63
+ return graphPath;
64
+ }
65
+ export const baseMetadata = {
66
+ sources: [{ repository: 'https://github.com/org/repo' }],
67
+ domains: { orders: { description: 'Order management', systemType: 'domain' } },
68
+ };
69
+ export const sourceLocation = { repository: 'https://github.com/org/repo', filePath: 'src/orders/handler.ts' };
70
+ export const useCaseComponent = {
71
+ id: 'orders:checkout:usecase:place-order',
72
+ type: 'UseCase',
73
+ name: 'place-order',
74
+ domain: 'orders',
75
+ module: 'checkout',
76
+ sourceLocation,
77
+ };
78
+ export const apiComponent = {
79
+ id: 'orders:checkout:api:place-order',
80
+ type: 'API',
81
+ name: 'place-order',
82
+ domain: 'orders',
83
+ module: 'checkout',
84
+ sourceLocation,
85
+ apiType: 'REST',
86
+ httpMethod: 'POST',
87
+ path: '/orders',
88
+ };
89
+ export const eventHandlerComponent = {
90
+ id: 'orders:checkout:eventhandler:on-order-placed',
91
+ type: 'EventHandler',
92
+ name: 'on-order-placed',
93
+ domain: 'orders',
94
+ module: 'checkout',
95
+ sourceLocation,
96
+ subscribedEvents: ['OrderPlaced'],
97
+ };
98
+ export const validLink = {
99
+ id: 'orders:checkout:api:place-order→orders:checkout:usecase:place-order:sync',
100
+ source: 'orders:checkout:api:place-order',
101
+ target: 'orders:checkout:usecase:place-order',
102
+ type: 'sync',
103
+ };
104
+ export async function createGraphWithDomain(testDir, domainName) {
105
+ const graphDir = join(testDir, '.riviere');
106
+ await mkdir(graphDir, { recursive: true });
107
+ const graph = {
108
+ version: '1.0',
109
+ metadata: {
110
+ sources: [{ repository: 'https://github.com/org/repo' }],
111
+ domains: { [domainName]: { description: 'Test domain', systemType: 'domain' } },
112
+ },
113
+ components: [],
114
+ links: [],
115
+ };
116
+ await writeFile(join(graphDir, 'graph.json'), JSON.stringify(graph), 'utf-8');
117
+ }
118
+ export async function createGraphWithSource(testDir, repository) {
119
+ const graphDir = join(testDir, '.riviere');
120
+ await mkdir(graphDir, { recursive: true });
121
+ const graph = {
122
+ version: '1.0',
123
+ metadata: {
124
+ sources: [{ repository }],
125
+ domains: { orders: { description: 'Orders', systemType: 'domain' } },
126
+ },
127
+ components: [],
128
+ links: [],
129
+ };
130
+ await writeFile(join(graphDir, 'graph.json'), JSON.stringify(graph), 'utf-8');
131
+ }
132
+ export async function createGraphWithComponent(testDir, component) {
133
+ const graphDir = join(testDir, '.riviere');
134
+ await mkdir(graphDir, { recursive: true });
135
+ const graph = {
136
+ version: '1.0',
137
+ metadata: {
138
+ sources: [{ repository: 'https://github.com/org/repo' }],
139
+ domains: { orders: { description: 'Order management', systemType: 'domain' } },
140
+ },
141
+ components: [component],
142
+ links: [],
143
+ };
144
+ await writeFile(join(graphDir, 'graph.json'), JSON.stringify(graph), 'utf-8');
145
+ }
146
+ export const domainOpComponent = {
147
+ id: 'orders:checkout:domainop:confirm-order',
148
+ type: 'DomainOp',
149
+ name: 'Confirm Order',
150
+ domain: 'orders',
151
+ module: 'checkout',
152
+ operationName: 'confirmOrder',
153
+ sourceLocation: { repository: 'https://github.com/org/repo', filePath: 'src/domain.ts' },
154
+ };
155
+ export const simpleUseCaseComponent = {
156
+ id: 'orders:checkout:usecase:place-order',
157
+ type: 'UseCase',
158
+ name: 'Place Order',
159
+ domain: 'orders',
160
+ module: 'checkout',
161
+ sourceLocation: { repository: 'https://github.com/org/repo', filePath: 'src/usecase.ts' },
162
+ };
163
+ export function hasSuccessOutputStructure(value) {
164
+ if (typeof value !== 'object' || value === null)
165
+ return false;
166
+ if (!('success' in value) || value.success !== true)
167
+ return false;
168
+ if (!('data' in value) || typeof value.data !== 'object' || value.data === null)
169
+ return false;
170
+ return true;
171
+ }
172
+ export function testCommandRegistration(commandName) {
173
+ it(`registers ${commandName} command under builder`, () => {
174
+ const program = createProgram();
175
+ const builderCmd = program.commands.find((cmd) => cmd.name() === 'builder');
176
+ const cmd = builderCmd?.commands.find((cmd) => cmd.name() === commandName);
177
+ expect(cmd?.name()).toBe(commandName);
178
+ });
179
+ }
180
+ export async function testCustomGraphPath(ctx, commandArgs, parseOutput) {
181
+ const customPath = await createGraph(ctx.testDir, { version: '1.0', metadata: baseMetadata, components: [], links: [] }, 'custom');
182
+ await createProgram().parseAsync(['node', 'riviere', ...commandArgs, '--graph', customPath, '--json']);
183
+ return parseOutput(ctx.consoleOutput);
184
+ }
@@ -0,0 +1,4 @@
1
+ import { Command } from 'commander';
2
+ export declare function getErrorMessage(error: unknown): string;
3
+ export declare function createAddComponentCommand(): Command;
4
+ //# sourceMappingURL=add-component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-component.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/add-component.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsBpC,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAKtD;AAyKD,wBAAgB,yBAAyB,IAAI,OAAO,CAkGnD"}
@@ -0,0 +1,204 @@
1
+ import { Command } from 'commander';
2
+ import { readFile, writeFile } from 'node:fs/promises';
3
+ import { CustomTypeNotFoundError, DomainNotFoundError, DuplicateComponentError, RiviereBuilder, } from '@living-architecture/riviere-builder';
4
+ import { parseRiviereGraph } from '@living-architecture/riviere-schema';
5
+ import { getDefaultGraphPathDescription, resolveGraphPath } from '../../graph-path';
6
+ import { fileExists } from '../../file-existence';
7
+ import { formatError, formatSuccess } from '../../output';
8
+ import { CliErrorCode } from '../../error-codes';
9
+ import { isValidComponentType, isValidApiType, VALID_COMPONENT_TYPES, } from '../../component-types';
10
+ import { isValidHttpMethod } from '../../validation';
11
+ export function getErrorMessage(error) {
12
+ if (error instanceof Error) {
13
+ return error.message;
14
+ }
15
+ return 'Unknown error';
16
+ }
17
+ function addUIComponent(builder, common, options) {
18
+ if (!options.route) {
19
+ throw new Error('--route is required for UI component');
20
+ }
21
+ const component = builder.addUI({ ...common, route: options.route });
22
+ return component.id;
23
+ }
24
+ function addAPIComponent(builder, common, options) {
25
+ if (!options.apiType || !isValidApiType(options.apiType)) {
26
+ throw new Error('--api-type is required for API component');
27
+ }
28
+ const input = {
29
+ ...common,
30
+ apiType: options.apiType,
31
+ };
32
+ if (options.httpMethod && isValidHttpMethod(options.httpMethod)) {
33
+ input.httpMethod = options.httpMethod;
34
+ }
35
+ if (options.httpPath) {
36
+ input.path = options.httpPath;
37
+ }
38
+ const component = builder.addApi(input);
39
+ return component.id;
40
+ }
41
+ function addUseCaseComponent(builder, common) {
42
+ const component = builder.addUseCase(common);
43
+ return component.id;
44
+ }
45
+ function addDomainOpComponent(builder, common, options) {
46
+ if (!options.operationName) {
47
+ throw new Error('--operation-name is required for DomainOp component');
48
+ }
49
+ const input = { ...common, operationName: options.operationName };
50
+ const component = options.entity
51
+ ? builder.addDomainOp({ ...input, entity: options.entity })
52
+ : builder.addDomainOp(input);
53
+ return component.id;
54
+ }
55
+ function addEventComponent(builder, common, options) {
56
+ if (!options.eventName) {
57
+ throw new Error('--event-name is required for Event component');
58
+ }
59
+ const component = builder.addEvent({ ...common, eventName: options.eventName });
60
+ return component.id;
61
+ }
62
+ function addEventHandlerComponent(builder, common, options) {
63
+ if (!options.subscribedEvents) {
64
+ throw new Error('--subscribed-events is required for EventHandler component');
65
+ }
66
+ const component = builder.addEventHandler({
67
+ ...common,
68
+ subscribedEvents: options.subscribedEvents
69
+ .split(',')
70
+ .map((e) => e.trim())
71
+ .filter((e) => e.length > 0),
72
+ });
73
+ return component.id;
74
+ }
75
+ const componentHandlers = {
76
+ UI: addUIComponent,
77
+ API: addAPIComponent,
78
+ UseCase: addUseCaseComponent,
79
+ DomainOp: addDomainOpComponent,
80
+ Event: addEventComponent,
81
+ EventHandler: addEventHandlerComponent,
82
+ Custom: (builder, common, options) => {
83
+ if (!options.customType) {
84
+ throw new Error('--custom-type is required for Custom component');
85
+ }
86
+ const component = builder.addCustom({ ...common, customTypeName: options.customType });
87
+ return component.id;
88
+ },
89
+ };
90
+ function addComponentToBuilder(builder, componentType, options, sourceLocation) {
91
+ const commonInput = {
92
+ name: options.name,
93
+ domain: options.domain,
94
+ module: options.module,
95
+ sourceLocation,
96
+ ...(options.description ? { description: options.description } : {}),
97
+ };
98
+ const handler = componentHandlers[componentType];
99
+ return handler(builder, commonInput, options);
100
+ }
101
+ function tryAddComponent(builder, componentType, options, sourceLocation) {
102
+ try {
103
+ return addComponentToBuilder(builder, componentType, options, sourceLocation);
104
+ }
105
+ catch (error) {
106
+ if (error instanceof DomainNotFoundError) {
107
+ console.log(JSON.stringify(formatError(CliErrorCode.DomainNotFound, error.message, ['Run riviere builder add-domain first'])));
108
+ return undefined;
109
+ }
110
+ if (error instanceof CustomTypeNotFoundError) {
111
+ console.log(JSON.stringify(formatError(CliErrorCode.CustomTypeNotFound, error.message, [
112
+ 'Run riviere builder add-custom-type first',
113
+ ])));
114
+ return undefined;
115
+ }
116
+ if (error instanceof DuplicateComponentError) {
117
+ console.log(JSON.stringify(formatError(CliErrorCode.DuplicateComponent, error.message, [])));
118
+ return undefined;
119
+ }
120
+ const message = getErrorMessage(error);
121
+ console.log(JSON.stringify(formatError(CliErrorCode.ValidationError, message, [])));
122
+ return undefined;
123
+ }
124
+ }
125
+ export function createAddComponentCommand() {
126
+ return new Command('add-component')
127
+ .description('Add a component to the graph')
128
+ .addHelpText('after', `
129
+ Examples:
130
+ # Add an API endpoint
131
+ $ riviere builder add-component --type API --name "place-order" \\
132
+ --domain orders --module api --repository ecommerce \\
133
+ --file-path src/api/orders.ts --api-type REST \\
134
+ --http-method POST --http-path /orders
135
+
136
+ # Add a UseCase
137
+ $ riviere builder add-component --type UseCase --name "place-order" \\
138
+ --domain orders --module checkout --repository ecommerce \\
139
+ --file-path src/usecases/PlaceOrder.ts
140
+
141
+ # Add a DomainOp
142
+ $ riviere builder add-component --type DomainOp --name "order-begin" \\
143
+ --domain orders --module domain --repository ecommerce \\
144
+ --file-path src/domain/Order.ts --entity Order --operation-name begin
145
+
146
+ # Add an Event
147
+ $ riviere builder add-component --type Event --name "order-placed" \\
148
+ --domain orders --module events --repository ecommerce \\
149
+ --file-path src/events/OrderPlaced.ts --event-name "order-placed"
150
+ `)
151
+ .requiredOption('--type <type>', 'Component type (UI, API, UseCase, DomainOp, Event, EventHandler, Custom)')
152
+ .requiredOption('--name <name>', 'Component name')
153
+ .requiredOption('--domain <domain>', 'Domain name')
154
+ .requiredOption('--module <module>', 'Module name')
155
+ .requiredOption('--repository <url>', 'Source repository URL')
156
+ .requiredOption('--file-path <path>', 'Source file path')
157
+ .option('--route <route>', 'UI route path')
158
+ .option('--api-type <type>', 'API type (REST, GraphQL, other)')
159
+ .option('--http-method <method>', 'HTTP method')
160
+ .option('--http-path <path>', 'HTTP endpoint path')
161
+ .option('--operation-name <name>', 'Operation name (DomainOp)')
162
+ .option('--entity <entity>', 'Entity name (DomainOp)')
163
+ .option('--event-name <name>', 'Event name')
164
+ .option('--subscribed-events <events>', 'Comma-separated subscribed event names')
165
+ .option('--custom-type <name>', 'Custom type name')
166
+ .option('--description <desc>', 'Component description')
167
+ .option('--line-number <n>', 'Source line number')
168
+ .option('--graph <path>', getDefaultGraphPathDescription())
169
+ .option('--json', 'Output result as JSON')
170
+ .action(async (options) => {
171
+ if (!isValidComponentType(options.type)) {
172
+ console.log(JSON.stringify(formatError(CliErrorCode.ValidationError, `Invalid component type: ${options.type}`, [
173
+ `Valid types: ${VALID_COMPONENT_TYPES.join(', ')}`,
174
+ ])));
175
+ return;
176
+ }
177
+ const componentType = options.type;
178
+ const graphPath = resolveGraphPath(options.graph);
179
+ const graphExists = await fileExists(graphPath);
180
+ if (!graphExists) {
181
+ console.log(JSON.stringify(formatError(CliErrorCode.GraphNotFound, `Graph not found at ${graphPath}`, [
182
+ 'Run riviere builder init first',
183
+ ])));
184
+ return;
185
+ }
186
+ const content = await readFile(graphPath, 'utf-8');
187
+ const parsed = JSON.parse(content);
188
+ const graph = parseRiviereGraph(parsed);
189
+ const builder = RiviereBuilder.resume(graph);
190
+ const sourceLocation = {
191
+ repository: options.repository,
192
+ filePath: options.filePath,
193
+ ...(options.lineNumber ? { lineNumber: parseInt(options.lineNumber, 10) } : {}),
194
+ };
195
+ const componentId = tryAddComponent(builder, componentType, options, sourceLocation);
196
+ if (componentId === undefined) {
197
+ return;
198
+ }
199
+ await writeFile(graphPath, builder.serialize(), 'utf-8');
200
+ if (options.json) {
201
+ console.log(JSON.stringify(formatSuccess({ componentId })));
202
+ }
203
+ });
204
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createAddDomainCommand(): Command;
3
+ //# sourceMappingURL=add-domain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-domain.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/add-domain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiBpC,wBAAgB,sBAAsB,IAAI,OAAO,CAoEhD"}
@@ -0,0 +1,56 @@
1
+ import { Command } from 'commander';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { DuplicateDomainError } from '@living-architecture/riviere-builder';
4
+ import { formatError, formatSuccess } from '../../output';
5
+ import { CliErrorCode } from '../../error-codes';
6
+ import { getDefaultGraphPathDescription } from '../../graph-path';
7
+ import { isValidSystemType, VALID_SYSTEM_TYPES } from '../../component-types';
8
+ import { withGraphBuilder } from './link-infrastructure';
9
+ export function createAddDomainCommand() {
10
+ return new Command('add-domain')
11
+ .description('Add a domain to the graph')
12
+ .addHelpText('after', `
13
+ Examples:
14
+ $ riviere builder add-domain --name orders --system-type domain \\
15
+ --description "Order management"
16
+
17
+ $ riviere builder add-domain --name checkout-bff --system-type bff \\
18
+ --description "Checkout backend-for-frontend"
19
+ `)
20
+ .requiredOption('--name <name>', 'Domain name')
21
+ .requiredOption('--description <description>', 'Domain description')
22
+ .requiredOption('--system-type <type>', 'System type (domain, bff, ui, other)')
23
+ .option('--graph <path>', getDefaultGraphPathDescription())
24
+ .option('--json', 'Output result as JSON')
25
+ .action(async (options) => {
26
+ if (!isValidSystemType(options.systemType)) {
27
+ console.log(JSON.stringify(formatError(CliErrorCode.ValidationError, `Invalid system type: ${options.systemType}`, [`Valid types: ${VALID_SYSTEM_TYPES.join(', ')}`])));
28
+ return;
29
+ }
30
+ const systemType = options.systemType;
31
+ await withGraphBuilder(options.graph, async (builder, graphPath) => {
32
+ try {
33
+ builder.addDomain({
34
+ name: options.name,
35
+ description: options.description,
36
+ systemType,
37
+ });
38
+ }
39
+ catch (error) {
40
+ if (error instanceof DuplicateDomainError) {
41
+ console.log(JSON.stringify(formatError(CliErrorCode.DuplicateDomain, error.message, ['Use a different domain name'])));
42
+ return;
43
+ }
44
+ throw error;
45
+ }
46
+ await writeFile(graphPath, builder.serialize(), 'utf-8');
47
+ if (options.json === true) {
48
+ console.log(JSON.stringify(formatSuccess({
49
+ name: options.name,
50
+ description: options.description,
51
+ systemType: options.systemType,
52
+ })));
53
+ }
54
+ });
55
+ });
56
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createAddSourceCommand(): Command;
3
+ //# sourceMappingURL=add-source.d.ts.map