@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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-source.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/add-source.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,wBAAgB,sBAAsB,IAAI,OAAO,CA+BhD"}
@@ -0,0 +1,28 @@
1
+ import { Command } from 'commander';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { formatSuccess } from '../../output';
4
+ import { getDefaultGraphPathDescription } from '../../graph-path';
5
+ import { withGraphBuilder } from './link-infrastructure';
6
+ export function createAddSourceCommand() {
7
+ return new Command('add-source')
8
+ .description('Add a source repository to the graph')
9
+ .addHelpText('after', `
10
+ Examples:
11
+ $ riviere builder add-source --repository https://github.com/org/orders-service
12
+ $ riviere builder add-source --repository https://github.com/org/payments-api --json
13
+ `)
14
+ .requiredOption('--repository <url>', 'Source repository URL')
15
+ .option('--graph <path>', getDefaultGraphPathDescription())
16
+ .option('--json', 'Output result as JSON')
17
+ .action(async (options) => {
18
+ await withGraphBuilder(options.graph, async (builder, graphPath) => {
19
+ builder.addSource({ repository: options.repository });
20
+ await writeFile(graphPath, builder.serialize(), 'utf-8');
21
+ if (options.json === true) {
22
+ console.log(JSON.stringify(formatSuccess({
23
+ repository: options.repository,
24
+ })));
25
+ }
26
+ });
27
+ });
28
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createCheckConsistencyCommand(): Command;
3
+ //# sourceMappingURL=check-consistency.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"check-consistency.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/check-consistency.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,wBAAgB,6BAA6B,IAAI,OAAO,CA8BvD"}
@@ -0,0 +1,27 @@
1
+ import { Command } from 'commander';
2
+ import { getDefaultGraphPathDescription } from '../../graph-path';
3
+ import { formatSuccess } from '../../output';
4
+ import { withGraphBuilder } from './link-infrastructure';
5
+ export function createCheckConsistencyCommand() {
6
+ return new Command('check-consistency')
7
+ .description('Check for structural issues in the graph')
8
+ .addHelpText('after', `
9
+ Examples:
10
+ $ riviere builder check-consistency
11
+ $ riviere builder check-consistency --json
12
+ `)
13
+ .option('--graph <path>', getDefaultGraphPathDescription())
14
+ .option('--json', 'Output result as JSON')
15
+ .action(async (options) => {
16
+ await withGraphBuilder(options.graph, async (builder) => {
17
+ const warnings = builder.warnings();
18
+ const consistent = warnings.length === 0;
19
+ if (options.json === true) {
20
+ console.log(JSON.stringify(formatSuccess({
21
+ consistent,
22
+ warnings,
23
+ })));
24
+ }
25
+ });
26
+ });
27
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createComponentChecklistCommand(): Command;
3
+ //# sourceMappingURL=component-checklist.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-checklist.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/component-checklist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,wBAAgB,+BAA+B,IAAI,OAAO,CAmDzD"}
@@ -0,0 +1,43 @@
1
+ import { Command } from 'commander';
2
+ import { getDefaultGraphPathDescription } from '../../graph-path';
3
+ import { formatError, formatSuccess } from '../../output';
4
+ import { CliErrorCode } from '../../error-codes';
5
+ import { isValidComponentType } from '../../component-types';
6
+ import { withGraphBuilder } from './link-infrastructure';
7
+ export function createComponentChecklistCommand() {
8
+ return new Command('component-checklist')
9
+ .description('List components as a checklist for linking/enrichment')
10
+ .addHelpText('after', `
11
+ Examples:
12
+ $ riviere builder component-checklist
13
+ $ riviere builder component-checklist --type DomainOp
14
+ $ riviere builder component-checklist --type API --json
15
+ `)
16
+ .option('--graph <path>', getDefaultGraphPathDescription())
17
+ .option('--json', 'Output result as JSON')
18
+ .option('--type <type>', 'Filter by component type')
19
+ .action(async (options) => {
20
+ if (options.type !== undefined && !isValidComponentType(options.type)) {
21
+ console.log(JSON.stringify(formatError(CliErrorCode.InvalidComponentType, `Invalid component type: ${options.type}`, [
22
+ 'Valid types: UI, API, UseCase, DomainOp, Event, EventHandler, Custom',
23
+ ])));
24
+ return;
25
+ }
26
+ await withGraphBuilder(options.graph, async (builder) => {
27
+ const allComponents = builder.query().components();
28
+ const filteredComponents = options.type !== undefined ? allComponents.filter((c) => c.type === options.type) : allComponents;
29
+ const checklistItems = filteredComponents.map((c) => ({
30
+ id: c.id,
31
+ type: c.type,
32
+ name: c.name,
33
+ domain: c.domain,
34
+ }));
35
+ if (options.json === true) {
36
+ console.log(JSON.stringify(formatSuccess({
37
+ total: checklistItems.length,
38
+ components: checklistItems,
39
+ })));
40
+ }
41
+ });
42
+ });
43
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createComponentSummaryCommand(): Command;
3
+ //# sourceMappingURL=component-summary.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"component-summary.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/component-summary.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAUpC,wBAAgB,6BAA6B,IAAI,OAAO,CAsBvD"}
@@ -0,0 +1,23 @@
1
+ import { Command } from 'commander';
2
+ import { getDefaultGraphPathDescription } from '../../graph-path';
3
+ import { formatSuccess } from '../../output';
4
+ import { withGraphBuilder } from './link-infrastructure';
5
+ export function createComponentSummaryCommand() {
6
+ return new Command('component-summary')
7
+ .description('Show component counts by type and domain')
8
+ .addHelpText('after', `
9
+ Examples:
10
+ $ riviere builder component-summary
11
+ $ riviere builder component-summary --json
12
+ `)
13
+ .option('--graph <path>', getDefaultGraphPathDescription())
14
+ .option('--json', 'Output result as JSON')
15
+ .action(async (options) => {
16
+ await withGraphBuilder(options.graph, async (builder) => {
17
+ const stats = builder.stats();
18
+ if (options.json === true) {
19
+ console.log(JSON.stringify(formatSuccess(stats)));
20
+ }
21
+ });
22
+ });
23
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createEnrichCommand(): Command;
3
+ //# sourceMappingURL=enrich.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"enrich.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/enrich.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoDpC,wBAAgB,mBAAmB,IAAI,OAAO,CAsD7C"}
@@ -0,0 +1,85 @@
1
+ import { Command } from 'commander';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { InvalidEnrichmentTargetError } from '@living-architecture/riviere-builder';
4
+ import { withGraphBuilder, handleComponentNotFoundError } from './link-infrastructure';
5
+ import { formatError, formatSuccess } from '../../output';
6
+ import { CliErrorCode } from '../../error-codes';
7
+ import { getDefaultGraphPathDescription } from '../../graph-path';
8
+ function collectOption(value, previous) {
9
+ return [...previous, value];
10
+ }
11
+ function parseStateChange(input) {
12
+ const [from, to, ...rest] = input.split(':');
13
+ if (from === undefined || to === undefined || rest.length > 0) {
14
+ return undefined;
15
+ }
16
+ return { from, to };
17
+ }
18
+ function parseStateChanges(inputs) {
19
+ const stateChanges = [];
20
+ for (const sc of inputs) {
21
+ const parsed = parseStateChange(sc);
22
+ if (parsed === undefined) {
23
+ return { success: false, invalidInput: sc };
24
+ }
25
+ stateChanges.push(parsed);
26
+ }
27
+ return { success: true, stateChanges };
28
+ }
29
+ function handleEnrichmentError(error) {
30
+ if (error instanceof InvalidEnrichmentTargetError) {
31
+ console.log(JSON.stringify(formatError(CliErrorCode.InvalidComponentType, error.message, [])));
32
+ return;
33
+ }
34
+ handleComponentNotFoundError(error);
35
+ }
36
+ export function createEnrichCommand() {
37
+ return new Command('enrich')
38
+ .description('Enrich a DomainOp component with entity, state changes, and business rules')
39
+ .addHelpText('after', `
40
+ Examples:
41
+ $ riviere builder enrich \\
42
+ --id "orders:checkout:domainop:orderbegin" \\
43
+ --entity Order \\
44
+ --state-change "Draft:Placed" \\
45
+ --business-rule "Order must have at least one item"
46
+
47
+ $ riviere builder enrich \\
48
+ --id "payments:gateway:domainop:paymentprocess" \\
49
+ --state-change "Pending:Processing" \\
50
+ --state-change "Processing:Completed" \\
51
+ --business-rule "Amount must be positive" \\
52
+ --business-rule "Currency must be valid"
53
+ `)
54
+ .requiredOption('--id <component-id>', 'Component ID to enrich')
55
+ .option('--entity <name>', 'Entity name')
56
+ .option('--state-change <from:to>', 'State transition (repeatable)', collectOption, [])
57
+ .option('--business-rule <rule>', 'Business rule (repeatable)', collectOption, [])
58
+ .option('--graph <path>', getDefaultGraphPathDescription())
59
+ .option('--json', 'Output result as JSON')
60
+ .action(async (options) => {
61
+ const parseResult = parseStateChanges(options.stateChange);
62
+ if (!parseResult.success) {
63
+ const msg = `Invalid state-change format: '${parseResult.invalidInput}'. Expected 'from:to'.`;
64
+ console.log(JSON.stringify(formatError(CliErrorCode.ValidationError, msg, [])));
65
+ return;
66
+ }
67
+ await withGraphBuilder(options.graph, async (builder, graphPath) => {
68
+ try {
69
+ builder.enrichComponent(options.id, {
70
+ ...(options.entity !== undefined && { entity: options.entity }),
71
+ ...(parseResult.stateChanges.length > 0 && { stateChanges: parseResult.stateChanges }),
72
+ ...(options.businessRule.length > 0 && { businessRules: options.businessRule }),
73
+ });
74
+ }
75
+ catch (error) {
76
+ handleEnrichmentError(error);
77
+ return;
78
+ }
79
+ await writeFile(graphPath, builder.serialize(), 'utf-8');
80
+ if (options.json === true) {
81
+ console.log(JSON.stringify(formatSuccess({ componentId: options.id })));
82
+ }
83
+ });
84
+ });
85
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createFinalizeCommand(): Command;
3
+ //# sourceMappingURL=finalize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finalize.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/finalize.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,wBAAgB,qBAAqB,IAAI,OAAO,CAwC/C"}
@@ -0,0 +1,37 @@
1
+ import { Command } from 'commander';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { formatError, formatSuccess } from '../../output';
4
+ import { CliErrorCode } from '../../error-codes';
5
+ import { getDefaultGraphPathDescription } from '../../graph-path';
6
+ import { withGraphBuilder } from './link-infrastructure';
7
+ export function createFinalizeCommand() {
8
+ return new Command('finalize')
9
+ .description('Validate and export the final graph')
10
+ .addHelpText('after', `
11
+ Examples:
12
+ $ riviere builder finalize
13
+ $ riviere builder finalize --output ./dist/architecture.json
14
+ $ riviere builder finalize --json
15
+ `)
16
+ .option('--graph <path>', getDefaultGraphPathDescription())
17
+ .option('--output <path>', 'Output path for finalized graph (defaults to input path)')
18
+ .option('--json', 'Output result as JSON')
19
+ .action(async (options) => {
20
+ await withGraphBuilder(options.graph, async (builder, graphPath) => {
21
+ const validationResult = builder.validate();
22
+ if (!validationResult.valid) {
23
+ const messages = validationResult.errors.map((e) => e.message).join('; ');
24
+ console.log(JSON.stringify(formatError(CliErrorCode.ValidationError, `Validation failed: ${messages}`, [
25
+ 'Fix the validation errors and try again',
26
+ ])));
27
+ return;
28
+ }
29
+ const outputPath = options.output ?? graphPath;
30
+ const finalGraph = builder.build();
31
+ await writeFile(outputPath, JSON.stringify(finalGraph, null, 2), 'utf-8');
32
+ if (options.json === true) {
33
+ console.log(JSON.stringify(formatSuccess({ path: outputPath })));
34
+ }
35
+ });
36
+ });
37
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createInitCommand(): Command;
3
+ //# sourceMappingURL=init.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqDpC,wBAAgB,iBAAiB,IAAI,OAAO,CAiG3C"}
@@ -0,0 +1,100 @@
1
+ import { Command } from 'commander';
2
+ import { mkdir, writeFile } from 'node:fs/promises';
3
+ import { dirname } from 'node:path';
4
+ import { RiviereBuilder } from '@living-architecture/riviere-builder';
5
+ import { formatError, formatSuccess } from '../../output';
6
+ import { CliErrorCode } from '../../error-codes';
7
+ import { fileExists } from '../../file-existence';
8
+ import { resolveGraphPath, getDefaultGraphPathDescription } from '../../graph-path';
9
+ import { isValidSystemType } from '../../component-types';
10
+ function isDomainInputParsed(value) {
11
+ if (typeof value !== 'object' || value === null) {
12
+ return false;
13
+ }
14
+ return ('name' in value &&
15
+ typeof value.name === 'string' &&
16
+ 'description' in value &&
17
+ typeof value.description === 'string' &&
18
+ 'systemType' in value &&
19
+ typeof value.systemType === 'string' &&
20
+ isValidSystemType(value.systemType));
21
+ }
22
+ function parseDomainJson(value, previous) {
23
+ const parsed = JSON.parse(value);
24
+ if (!isDomainInputParsed(parsed)) {
25
+ throw new Error(`Invalid domain JSON: ${value}`);
26
+ }
27
+ return [...previous, parsed];
28
+ }
29
+ function collectSource(value, previous) {
30
+ return [...previous, value];
31
+ }
32
+ export function createInitCommand() {
33
+ return new Command('init')
34
+ .description('Initialize a new graph')
35
+ .addHelpText('after', `
36
+ Examples:
37
+ $ riviere builder init --source https://github.com/org/repo \\
38
+ --domain '{"name":"orders","description":"Order management","systemType":"domain"}'
39
+
40
+ $ riviere builder init --name "ecommerce" \\
41
+ --source https://github.com/org/orders \\
42
+ --source https://github.com/org/payments \\
43
+ --domain '{"name":"orders","description":"Order management","systemType":"domain"}' \\
44
+ --domain '{"name":"payments","description":"Payment processing","systemType":"domain"}'
45
+ `)
46
+ .option('--name <name>', 'System name')
47
+ .option('--graph <path>', getDefaultGraphPathDescription())
48
+ .option('--json', 'Output result as JSON')
49
+ .option('--source <url>', 'Source repository URL (repeatable)', collectSource, [])
50
+ .option('--domain <json>', 'Domain as JSON (repeatable)', parseDomainJson, [])
51
+ .action(async (options) => {
52
+ // Validate required flags
53
+ if (options.source.length === 0) {
54
+ console.log(JSON.stringify(formatError(CliErrorCode.ValidationError, 'At least one source required', [
55
+ 'Add --source <url> flag',
56
+ ])));
57
+ return;
58
+ }
59
+ if (options.domain.length === 0) {
60
+ console.log(JSON.stringify(formatError(CliErrorCode.ValidationError, 'At least one domain required', [
61
+ 'Add --domain <json> flag',
62
+ ])));
63
+ return;
64
+ }
65
+ const graphPath = resolveGraphPath(options.graph);
66
+ const graphDir = dirname(graphPath);
67
+ const graphExists = await fileExists(graphPath);
68
+ if (graphExists) {
69
+ console.log(JSON.stringify(formatError(CliErrorCode.GraphExists, `Graph already exists at ${graphPath}`, [
70
+ 'Delete the file to reinitialize',
71
+ ])));
72
+ return;
73
+ }
74
+ const domains = {};
75
+ for (const d of options.domain) {
76
+ domains[d.name] = {
77
+ description: d.description,
78
+ systemType: d.systemType,
79
+ };
80
+ }
81
+ const builderOptions = {
82
+ sources: options.source.map((url) => ({ repository: url })),
83
+ domains,
84
+ };
85
+ if (options.name !== undefined) {
86
+ builderOptions.name = options.name;
87
+ }
88
+ const builder = RiviereBuilder.new(builderOptions);
89
+ await mkdir(graphDir, { recursive: true });
90
+ await writeFile(graphPath, builder.serialize(), 'utf-8');
91
+ if (options.json === true) {
92
+ const domainNames = options.domain.map((d) => d.name);
93
+ console.log(JSON.stringify(formatSuccess({
94
+ path: graphPath,
95
+ sources: options.source.length,
96
+ domains: domainNames,
97
+ })));
98
+ }
99
+ });
100
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createLinkExternalCommand(): Command;
3
+ //# sourceMappingURL=link-external.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-external.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/link-external.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkCpC,wBAAgB,yBAAyB,IAAI,OAAO,CAiEnD"}
@@ -0,0 +1,70 @@
1
+ import { Command } from 'commander';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { getDefaultGraphPathDescription, resolveGraphPath } from '../../graph-path';
4
+ import { fileExists } from '../../file-existence';
5
+ import { formatSuccess } from '../../output';
6
+ import { isValidLinkType } from '../../component-types';
7
+ import { validateLinkType } from '../../validation';
8
+ import { loadGraphBuilder, reportGraphNotFound, tryBuilderOperation } from './link-infrastructure';
9
+ function buildExternalTarget(options) {
10
+ return {
11
+ name: options.targetName,
12
+ ...(options.targetDomain && { domain: options.targetDomain }),
13
+ ...(options.targetUrl && { url: options.targetUrl }),
14
+ };
15
+ }
16
+ export function createLinkExternalCommand() {
17
+ return new Command('link-external')
18
+ .description('Link a component to an external system')
19
+ .addHelpText('after', `
20
+ Examples:
21
+ $ riviere builder link-external \\
22
+ --from "payments:gateway:usecase:processpayment" \\
23
+ --target-name "Stripe" \\
24
+ --target-url "https://api.stripe.com" \\
25
+ --link-type sync
26
+
27
+ $ riviere builder link-external \\
28
+ --from "shipping:tracking:usecase:updatetracking" \\
29
+ --target-name "FedEx API" \\
30
+ --target-domain "shipping" \\
31
+ --link-type async
32
+ `)
33
+ .requiredOption('--from <component-id>', 'Source component ID')
34
+ .requiredOption('--target-name <name>', 'External target name')
35
+ .option('--target-domain <domain>', 'External target domain')
36
+ .option('--target-url <url>', 'External target URL')
37
+ .option('--link-type <type>', 'Link type (sync, async)')
38
+ .option('--graph <path>', getDefaultGraphPathDescription())
39
+ .option('--json', 'Output result as JSON')
40
+ .action(async (options) => {
41
+ const linkTypeValidation = validateLinkType(options.linkType);
42
+ if (!linkTypeValidation.valid) {
43
+ console.log(linkTypeValidation.errorJson);
44
+ return;
45
+ }
46
+ const graphPath = resolveGraphPath(options.graph);
47
+ const graphExists = await fileExists(graphPath);
48
+ if (!graphExists) {
49
+ reportGraphNotFound(graphPath);
50
+ return;
51
+ }
52
+ const builder = await loadGraphBuilder(graphPath);
53
+ const target = buildExternalTarget(options);
54
+ const externalLinkInput = {
55
+ from: options.from,
56
+ target,
57
+ };
58
+ if (options.linkType !== undefined && isValidLinkType(options.linkType)) {
59
+ externalLinkInput.type = options.linkType;
60
+ }
61
+ const externalLink = tryBuilderOperation(() => builder.linkExternal(externalLinkInput));
62
+ if (externalLink === undefined) {
63
+ return;
64
+ }
65
+ await writeFile(graphPath, builder.serialize(), 'utf-8');
66
+ if (options.json) {
67
+ console.log(JSON.stringify(formatSuccess({ externalLink })));
68
+ }
69
+ });
70
+ }
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createLinkHttpCommand(): Command;
3
+ //# sourceMappingURL=link-http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-http.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/link-http.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAoGpC,wBAAgB,qBAAqB,IAAI,OAAO,CA+F/C"}
@@ -0,0 +1,130 @@
1
+ import { Command } from 'commander';
2
+ import { writeFile } from 'node:fs/promises';
3
+ import { ComponentId } from '@living-architecture/riviere-builder';
4
+ import { RiviereQuery } from '@living-architecture/riviere-query';
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 { isValidLinkType, normalizeComponentType } from '../../component-types';
10
+ import { isValidHttpMethod, validateComponentType, validateHttpMethod, validateLinkType } from '../../validation';
11
+ import { loadGraphBuilder, reportGraphNotFound } from './link-infrastructure';
12
+ function isRestApiWithPath(component) {
13
+ return component.type === 'API' && 'path' in component && 'httpMethod' in component;
14
+ }
15
+ function findApisByPath(graph, path, method) {
16
+ const query = new RiviereQuery(graph);
17
+ const allComponents = query.componentsByType('API');
18
+ const apis = allComponents.filter(isRestApiWithPath);
19
+ const matchingPath = apis.filter((api) => api.path === path);
20
+ if (method) {
21
+ return matchingPath.filter((api) => api.httpMethod === method);
22
+ }
23
+ return matchingPath;
24
+ }
25
+ function getAllApiPaths(graph) {
26
+ const query = new RiviereQuery(graph);
27
+ const allComponents = query.componentsByType('API');
28
+ const apis = allComponents.filter(isRestApiWithPath);
29
+ return [...new Set(apis.map((api) => api.path))];
30
+ }
31
+ function reportNoApiFoundForPath(path, availablePaths) {
32
+ console.log(JSON.stringify(formatError(CliErrorCode.ComponentNotFound, `No API found with path '${path}'`, availablePaths.length > 0 ? [`Available paths: ${availablePaths.join(', ')}`] : [])));
33
+ }
34
+ function reportAmbiguousApiMatch(path, matchingApis) {
35
+ const apiList = matchingApis.map((api) => `${api.id} (${api.httpMethod})`).join(', ');
36
+ console.log(JSON.stringify(formatError(CliErrorCode.AmbiguousApiMatch, `Multiple APIs match path '${path}': ${apiList}`, [
37
+ 'Add --method flag to disambiguate',
38
+ ])));
39
+ }
40
+ function validateOptions(options) {
41
+ const componentTypeValidation = validateComponentType(options.toType);
42
+ if (!componentTypeValidation.valid) {
43
+ return componentTypeValidation.errorJson;
44
+ }
45
+ const httpMethodValidation = validateHttpMethod(options.method);
46
+ if (!httpMethodValidation.valid) {
47
+ return httpMethodValidation.errorJson;
48
+ }
49
+ const linkTypeValidation = validateLinkType(options.linkType);
50
+ if (!linkTypeValidation.valid) {
51
+ return linkTypeValidation.errorJson;
52
+ }
53
+ return undefined;
54
+ }
55
+ export function createLinkHttpCommand() {
56
+ return new Command('link-http')
57
+ .description('Find an API by HTTP path and link to a target component')
58
+ .addHelpText('after', `
59
+ Examples:
60
+ $ riviere builder link-http \\
61
+ --path "/orders" --method POST \\
62
+ --to-domain orders --to-module checkout --to-type UseCase --to-name "place-order"
63
+
64
+ $ riviere builder link-http \\
65
+ --path "/users/{id}" --method GET \\
66
+ --to-domain users --to-module queries --to-type UseCase --to-name "get-user" \\
67
+ --link-type sync
68
+ `)
69
+ .requiredOption('--path <http-path>', 'HTTP path to match')
70
+ .requiredOption('--to-domain <domain>', 'Target domain')
71
+ .requiredOption('--to-module <module>', 'Target module')
72
+ .requiredOption('--to-type <type>', 'Target component type')
73
+ .requiredOption('--to-name <name>', 'Target component name')
74
+ .option('--method <method>', 'Filter by HTTP method (GET, POST, PUT, PATCH, DELETE)')
75
+ .option('--link-type <type>', 'Link type (sync, async)')
76
+ .option('--graph <path>', getDefaultGraphPathDescription())
77
+ .option('--json', 'Output result as JSON')
78
+ .action(async (options) => {
79
+ const validationError = validateOptions(options);
80
+ if (validationError) {
81
+ console.log(validationError);
82
+ return;
83
+ }
84
+ const graphPath = resolveGraphPath(options.graph);
85
+ const graphExists = await fileExists(graphPath);
86
+ if (!graphExists) {
87
+ reportGraphNotFound(graphPath);
88
+ return;
89
+ }
90
+ const builder = await loadGraphBuilder(graphPath);
91
+ const graph = builder.build();
92
+ const normalizedMethod = options.method?.toUpperCase();
93
+ const httpMethod = normalizedMethod && isValidHttpMethod(normalizedMethod) ? normalizedMethod : undefined;
94
+ const matchingApis = findApisByPath(graph, options.path, httpMethod);
95
+ const [matchedApi, ...otherApis] = matchingApis;
96
+ if (!matchedApi) {
97
+ reportNoApiFoundForPath(options.path, getAllApiPaths(graph));
98
+ return;
99
+ }
100
+ if (otherApis.length > 0) {
101
+ reportAmbiguousApiMatch(options.path, matchingApis);
102
+ return;
103
+ }
104
+ const targetId = ComponentId.create({
105
+ domain: options.toDomain,
106
+ module: options.toModule,
107
+ type: normalizeComponentType(options.toType),
108
+ name: options.toName,
109
+ }).toString();
110
+ const linkInput = {
111
+ from: matchedApi.id,
112
+ to: targetId,
113
+ };
114
+ if (options.linkType !== undefined && isValidLinkType(options.linkType)) {
115
+ linkInput.type = options.linkType;
116
+ }
117
+ const link = builder.link(linkInput);
118
+ await writeFile(graphPath, builder.serialize(), 'utf-8');
119
+ if (options.json) {
120
+ console.log(JSON.stringify(formatSuccess({
121
+ link,
122
+ matchedApi: {
123
+ id: matchedApi.id,
124
+ path: matchedApi.path,
125
+ method: matchedApi.httpMethod,
126
+ },
127
+ })));
128
+ }
129
+ });
130
+ }
@@ -0,0 +1,7 @@
1
+ import { RiviereBuilder } from '@living-architecture/riviere-builder';
2
+ export declare function reportGraphNotFound(graphPath: string): void;
3
+ export declare function loadGraphBuilder(graphPath: string): Promise<RiviereBuilder>;
4
+ export declare function withGraphBuilder(graphPathOption: string | undefined, handler: (builder: RiviereBuilder, graphPath: string) => Promise<void>): Promise<void>;
5
+ export declare function handleComponentNotFoundError(error: unknown): void;
6
+ export declare function tryBuilderOperation<T>(operation: () => T): T | undefined;
7
+ //# sourceMappingURL=link-infrastructure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"link-infrastructure.d.ts","sourceRoot":"","sources":["../../../src/commands/builder/link-infrastructure.ts"],"names":[],"mappings":"AACA,OAAO,EAA0B,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAO9F,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAM3D;AAED,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAKjF;AAED,wBAAsB,gBAAgB,CACpC,eAAe,EAAE,MAAM,GAAG,SAAS,EACnC,OAAO,EAAE,CAAC,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GACrE,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAKjE;AAED,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAOxE"}