yarramate 0.10.0 → 0.12.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.
- package/dist/adapters/likec4-cli.js +47 -2
- package/dist/adapters/likec4-export.d.ts +8 -0
- package/dist/adapters/likec4-export.js +21 -0
- package/dist/adapters/likec4-project.d.ts +7 -2
- package/dist/adapters/likec4-project.js +83 -13
- package/dist/changed.d.ts +3 -0
- package/dist/changed.js +20 -10
- package/dist/cli-support.d.ts +1 -1
- package/dist/cli-support.js +1 -1
- package/dist/export-command.js +2 -0
- package/package.json +1 -1
- package/schema/yarramate-ask-result.schema.json +7 -0
- package/schema/yarramate-likec4-project.schema.json +5 -0
|
@@ -6,6 +6,7 @@ import { createHash, randomUUID } from 'node:crypto';
|
|
|
6
6
|
import Ajv2020Module from 'ajv/dist/2020.js';
|
|
7
7
|
import { isMap, isSeq, parseDocument } from 'yaml';
|
|
8
8
|
import { isMainModule, resolveCliWorkspaceSources, versionResult, } from '../cli-support.js';
|
|
9
|
+
import { deriveChangedSubjects } from '../changed.js';
|
|
9
10
|
import { compileWorkspace } from '../compiler.js';
|
|
10
11
|
import { adapterMappingLocation, loadAdapterMapping, validateAdapterMapping, } from '../adapter-mapping.js';
|
|
11
12
|
import { locateSourcePath } from '../source-document.js';
|
|
@@ -78,7 +79,7 @@ const usage = 'Usage:\n' +
|
|
|
78
79
|
' yarramate-likec4 check <likec4-project.yaml> [--json] <workspace-or-source...>\n' +
|
|
79
80
|
' yarramate-likec4 export <projection.yaml> <mapping.yaml> [--kinds <kind-mapping.yaml>] [--compare <from-state> <to-state>] <workspace-or-source...>\n' +
|
|
80
81
|
' yarramate-likec4 export-project [--check] <projection.yaml> <mapping.yaml> <output-dir> [--kinds <kind-mapping.yaml>] [--compare <from-state> <to-state>] <workspace-or-source...>\n' +
|
|
81
|
-
' yarramate-likec4 export-project [--check] <likec4-project.yaml> <output-dir> <workspace-or-source...>\n';
|
|
82
|
+
' yarramate-likec4 export-project [--check] <likec4-project.yaml> <output-dir> [--changed <git-range>] <workspace-or-source...>\n';
|
|
82
83
|
const diagnosticJson = (diagnostics) => `${JSON.stringify({
|
|
83
84
|
format: 'yarramate/likec4-diagnostic-result/v1',
|
|
84
85
|
diagnostics,
|
|
@@ -528,6 +529,7 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
|
|
|
528
529
|
let invalidOptions = false;
|
|
529
530
|
let kindMappingPath;
|
|
530
531
|
let comparison;
|
|
532
|
+
let changedRange;
|
|
531
533
|
const sourcePaths = [];
|
|
532
534
|
for (let index = 0; index < sourceOptions.length; index += 1) {
|
|
533
535
|
const option = sourceOptions[index];
|
|
@@ -549,6 +551,19 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
|
|
|
549
551
|
}
|
|
550
552
|
continue;
|
|
551
553
|
}
|
|
554
|
+
if (option === '--changed') {
|
|
555
|
+
const value = sourceOptions[index + 1];
|
|
556
|
+
if (value === undefined ||
|
|
557
|
+
value.startsWith('-') ||
|
|
558
|
+
changedRange !== undefined) {
|
|
559
|
+
invalidOptions = true;
|
|
560
|
+
}
|
|
561
|
+
else {
|
|
562
|
+
changedRange = value;
|
|
563
|
+
index += 1;
|
|
564
|
+
}
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
552
567
|
if (option === '--compare') {
|
|
553
568
|
const from = sourceOptions[index + 1];
|
|
554
569
|
const to = sourceOptions[index + 2];
|
|
@@ -581,6 +596,8 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
|
|
|
581
596
|
(command === 'export-project' && outputDirectory === undefined) ||
|
|
582
597
|
(projectDefinitionMode &&
|
|
583
598
|
(kindMappingPath !== undefined || comparison !== undefined)) ||
|
|
599
|
+
(changedRange !== undefined &&
|
|
600
|
+
!(projectDefinitionMode && command === 'export-project')) ||
|
|
584
601
|
invalidOptions ||
|
|
585
602
|
sourcePaths.length === 0 ||
|
|
586
603
|
sourcePaths.some((argument) => argument.startsWith('-'))) {
|
|
@@ -695,6 +712,9 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
|
|
|
695
712
|
? [
|
|
696
713
|
{
|
|
697
714
|
...(view.id === undefined ? {} : { id: view.id }),
|
|
715
|
+
...(view.folder === undefined
|
|
716
|
+
? {}
|
|
717
|
+
: { folder: view.folder }),
|
|
698
718
|
prepared,
|
|
699
719
|
...(view.compare === undefined
|
|
700
720
|
? {}
|
|
@@ -886,7 +906,32 @@ export function runLikeC4Cli(args, cwd = process.cwd()) {
|
|
|
886
906
|
}
|
|
887
907
|
renderedViewIds.add(renderedId);
|
|
888
908
|
}
|
|
889
|
-
|
|
909
|
+
let gitChange;
|
|
910
|
+
if (changedRange !== undefined) {
|
|
911
|
+
const modelDocuments = sources.flatMap((candidate) => {
|
|
912
|
+
const parsed = parseDocument(candidate.source);
|
|
913
|
+
if (parsed.get('format') !== 'yarramate/v1')
|
|
914
|
+
return [];
|
|
915
|
+
const documentId = parsed.get('id');
|
|
916
|
+
return typeof documentId === 'string'
|
|
917
|
+
? [{ ...candidate, documentId }]
|
|
918
|
+
: [];
|
|
919
|
+
});
|
|
920
|
+
const derived = deriveChangedSubjects(cwd, changedRange, modelDocuments);
|
|
921
|
+
if (!derived.ok) {
|
|
922
|
+
return { exitCode: 2, stdout: '', stderr: `${derived.message}\n` };
|
|
923
|
+
}
|
|
924
|
+
const all = [
|
|
925
|
+
...derived.changed.concepts,
|
|
926
|
+
...derived.changed.relationships,
|
|
927
|
+
];
|
|
928
|
+
gitChange = {
|
|
929
|
+
range: derived.changed.range,
|
|
930
|
+
added: derived.changed.added,
|
|
931
|
+
modified: all.filter((id) => !derived.changed.added.includes(id)),
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
const exported = exportLikeC4Project(loadedProject.document.value, successfulViews, gitChange === undefined ? {} : { gitChange });
|
|
890
935
|
if (!exported.ok) {
|
|
891
936
|
return {
|
|
892
937
|
exitCode: 1,
|
|
@@ -21,5 +21,13 @@ export type LikeC4ExportResult = {
|
|
|
21
21
|
};
|
|
22
22
|
export interface LikeC4ExportOptions {
|
|
23
23
|
readonly comparison?: StateComparison;
|
|
24
|
+
/** Git-derived review overlay (ADR 0066): subjects new or changed in a
|
|
25
|
+
* ref range carry metadata and view styling; nothing is authored. */
|
|
26
|
+
readonly gitChange?: GitChangeOverlay;
|
|
27
|
+
}
|
|
28
|
+
export interface GitChangeOverlay {
|
|
29
|
+
readonly range: string;
|
|
30
|
+
readonly added: readonly string[];
|
|
31
|
+
readonly modified: readonly string[];
|
|
24
32
|
}
|
|
25
33
|
export declare function exportLikeC4(projection: ProjectionResult, mapping: AdapterMapping, kindMapping?: LikeC4KindMapping, options?: LikeC4ExportOptions): LikeC4ExportResult;
|
|
@@ -62,6 +62,13 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
|
|
|
62
62
|
const concepts = projection.subjects
|
|
63
63
|
.filter(({ type }) => type === 'concept')
|
|
64
64
|
.sort((left, right) => left.id.localeCompare(right.id));
|
|
65
|
+
const gitChangeOf = (id) => options.gitChange === undefined
|
|
66
|
+
? undefined
|
|
67
|
+
: options.gitChange.added.includes(id)
|
|
68
|
+
? 'new'
|
|
69
|
+
: options.gitChange.modified.includes(id)
|
|
70
|
+
? 'changed'
|
|
71
|
+
: undefined;
|
|
65
72
|
const comparisonChange = new Map(options.comparison === undefined
|
|
66
73
|
? []
|
|
67
74
|
: [
|
|
@@ -121,6 +128,7 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
|
|
|
121
128
|
['yarramateId', concept.id],
|
|
122
129
|
['yarramateKind', semanticKind],
|
|
123
130
|
['yarramateChange', comparisonChange.get(concept.id)],
|
|
131
|
+
['yarramateGitChange', gitChangeOf(concept.id)],
|
|
124
132
|
[
|
|
125
133
|
'status',
|
|
126
134
|
valueFor(projection.claims, concept.id, 'yarramate/lifecycle/status'),
|
|
@@ -168,6 +176,7 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
|
|
|
168
176
|
['yarramateId', relationship.id],
|
|
169
177
|
['yarramateKind', structural.predicate],
|
|
170
178
|
['yarramateChange', comparisonChange.get(relationship.id)],
|
|
179
|
+
['yarramateGitChange', gitChangeOf(relationship.id)],
|
|
171
180
|
[
|
|
172
181
|
'status',
|
|
173
182
|
valueFor(projection.claims, relationship.id, 'yarramate/lifecycle/status'),
|
|
@@ -212,6 +221,18 @@ export function exportLikeC4(projection, mapping, kindMapping, options = {}) {
|
|
|
212
221
|
return ` style ${external} { color red; border dashed }`;
|
|
213
222
|
}
|
|
214
223
|
return ` style ${external} { color gray }`;
|
|
224
|
+
})), ...(options.gitChange === undefined
|
|
225
|
+
? []
|
|
226
|
+
: concepts.flatMap((concept) => {
|
|
227
|
+
const external = externalByNative.get(concept.id);
|
|
228
|
+
const change = gitChangeOf(concept.id);
|
|
229
|
+
if (change === 'new') {
|
|
230
|
+
return [` style ${external} { color green }`];
|
|
231
|
+
}
|
|
232
|
+
if (change === 'changed') {
|
|
233
|
+
return [` style ${external} { color amber }`];
|
|
234
|
+
}
|
|
235
|
+
return [];
|
|
215
236
|
})), ' autoLayout LeftRight', ' }', '}', '');
|
|
216
237
|
return { ok: true, source: lines.join('\n') };
|
|
217
238
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WorkspaceSource } from '../compiler.js';
|
|
2
2
|
import type { LikeC4PreparationResult } from './likec4-prepare.js';
|
|
3
|
-
import { type LikeC4ExportResult } from './likec4-export.js';
|
|
3
|
+
import { type GitChangeOverlay, type LikeC4ExportResult } from './likec4-export.js';
|
|
4
4
|
export interface LikeC4ProjectDefinition {
|
|
5
5
|
readonly format: 'yarramate/likec4-project/v1';
|
|
6
6
|
readonly id: string;
|
|
@@ -11,6 +11,7 @@ export interface LikeC4ProjectDefinition {
|
|
|
11
11
|
readonly views: ReadonlyArray<{
|
|
12
12
|
readonly id?: string;
|
|
13
13
|
readonly projection: string;
|
|
14
|
+
readonly folder?: string;
|
|
14
15
|
readonly compare?: {
|
|
15
16
|
readonly from: string;
|
|
16
17
|
readonly to: string;
|
|
@@ -40,6 +41,7 @@ export interface LikeC4Deployment {
|
|
|
40
41
|
export declare const loadLikeC4ProjectDefinition: (source: WorkspaceSource) => import("../source-document.js").SourceDocumentResult<LikeC4ProjectDefinition>;
|
|
41
42
|
export interface PreparedLikeC4ProjectView {
|
|
42
43
|
readonly id?: string;
|
|
44
|
+
readonly folder?: string;
|
|
43
45
|
readonly prepared: Extract<LikeC4PreparationResult, {
|
|
44
46
|
readonly ok: true;
|
|
45
47
|
}>;
|
|
@@ -55,4 +57,7 @@ export interface PreparedLikeC4ProjectView {
|
|
|
55
57
|
};
|
|
56
58
|
readonly deployment?: LikeC4Deployment;
|
|
57
59
|
}
|
|
58
|
-
export
|
|
60
|
+
export interface LikeC4ProjectExportOptions {
|
|
61
|
+
readonly gitChange?: GitChangeOverlay;
|
|
62
|
+
}
|
|
63
|
+
export declare function exportLikeC4Project(project: LikeC4ProjectDefinition, views: readonly PreparedLikeC4ProjectView[], options?: LikeC4ProjectExportOptions): LikeC4ExportResult;
|
|
@@ -8,6 +8,15 @@ const Ajv2020 = Ajv2020Module.default;
|
|
|
8
8
|
const validateLikeC4Project = new Ajv2020({ allErrors: true }).compile(likeC4ProjectSchema);
|
|
9
9
|
export const loadLikeC4ProjectDefinition = (source) => loadSourceDocument(source, validateLikeC4Project, 'LikeC4 project');
|
|
10
10
|
const quote = (value) => `'${value.replaceAll('\\', '\\\\').replaceAll("'", "\\'").replaceAll('\n', '\\n')}'`;
|
|
11
|
+
// A folder files the view in LikeC4's sidebar tree: the title becomes a
|
|
12
|
+
// path ('Folder / Title') whose last segment is the displayed title (ADR
|
|
13
|
+
// 0067). A view without a declared title still needs a title line to
|
|
14
|
+
// carry the path, so the view id stands in as the leaf.
|
|
15
|
+
const titleLines = (folder, title, viewId) => folder === undefined
|
|
16
|
+
? title === undefined
|
|
17
|
+
? []
|
|
18
|
+
: [` title ${quote(title)}`]
|
|
19
|
+
: [` title ${quote(`${folder} / ${title ?? viewId}`)}`];
|
|
11
20
|
const unionProjection = (project, views) => ({
|
|
12
21
|
format: 'yarramate/projection-result/v1',
|
|
13
22
|
projection: `${project.id}@${project.version}`,
|
|
@@ -28,7 +37,7 @@ const unionProjection = (project, views) => ({
|
|
|
28
37
|
...new Map(views.flatMap(({ prepared }) => prepared.projection.claims.map((claim) => [claim.id, claim]))).values(),
|
|
29
38
|
].sort((left, right) => left.id.localeCompare(right.id)),
|
|
30
39
|
});
|
|
31
|
-
const viewBody = ({ id, prepared, dynamic, deployment, }) => {
|
|
40
|
+
const viewBody = ({ id, folder, prepared, dynamic, deployment, }, gitChange) => {
|
|
32
41
|
const source = prepared.source;
|
|
33
42
|
const startToken = '\nviews {\n';
|
|
34
43
|
const start = source.indexOf(startToken);
|
|
@@ -58,9 +67,7 @@ const viewBody = ({ id, prepared, dynamic, deployment, }) => {
|
|
|
58
67
|
const viewId = id ?? prepared.projection.projection.split('@')[0];
|
|
59
68
|
return [
|
|
60
69
|
` deployment view ${viewId} {`,
|
|
61
|
-
...(prepared.projection.presentation?.title
|
|
62
|
-
? []
|
|
63
|
-
: [` title ${quote(prepared.projection.presentation.title)}`]),
|
|
70
|
+
...titleLines(folder, prepared.projection.presentation?.title, viewId),
|
|
64
71
|
...(prepared.projection.presentation?.description === undefined
|
|
65
72
|
? []
|
|
66
73
|
: [
|
|
@@ -105,9 +112,7 @@ const viewBody = ({ id, prepared, dynamic, deployment, }) => {
|
|
|
105
112
|
const viewId = id ?? prepared.projection.projection.split('@')[0];
|
|
106
113
|
return [
|
|
107
114
|
` dynamic view ${viewId} {`,
|
|
108
|
-
...(prepared.projection.presentation?.title
|
|
109
|
-
? []
|
|
110
|
-
: [` title ${quote(prepared.projection.presentation.title)}`]),
|
|
115
|
+
...titleLines(folder, prepared.projection.presentation?.title, viewId),
|
|
111
116
|
...(prepared.projection.presentation?.description === undefined
|
|
112
117
|
? []
|
|
113
118
|
: [
|
|
@@ -117,15 +122,40 @@ const viewBody = ({ id, prepared, dynamic, deployment, }) => {
|
|
|
117
122
|
' }',
|
|
118
123
|
].join('\n');
|
|
119
124
|
}
|
|
125
|
+
const overlayRules = gitChange === undefined
|
|
126
|
+
? []
|
|
127
|
+
: prepared.projection.subjects
|
|
128
|
+
.filter(({ type }) => type === 'concept')
|
|
129
|
+
.flatMap(({ id: nativeId }) => {
|
|
130
|
+
const external = externalByNative.get(nativeId);
|
|
131
|
+
if (external === undefined)
|
|
132
|
+
return [];
|
|
133
|
+
if (gitChange.added.includes(nativeId)) {
|
|
134
|
+
return [` style ${external} { color green }`];
|
|
135
|
+
}
|
|
136
|
+
if (gitChange.modified.includes(nativeId)) {
|
|
137
|
+
return [` style ${external} { color amber }`];
|
|
138
|
+
}
|
|
139
|
+
return [];
|
|
140
|
+
})
|
|
141
|
+
.sort();
|
|
120
142
|
const membershipRules = [
|
|
121
143
|
includeRule,
|
|
122
144
|
' exclude * -> *',
|
|
123
145
|
...relationshipRules,
|
|
146
|
+
...overlayRules,
|
|
124
147
|
].join('\n');
|
|
125
|
-
|
|
148
|
+
const viewId = id ?? prepared.projection.projection.split('@')[0];
|
|
149
|
+
const body = source
|
|
126
150
|
.slice(start + startToken.length, end)
|
|
127
|
-
.replace(/^ view [A-Za-z_][A-Za-z0-9_-]* \{/, ` view ${
|
|
151
|
+
.replace(/^ view [A-Za-z_][A-Za-z0-9_-]* \{/, ` view ${viewId} {`)
|
|
128
152
|
.replace(' include *', membershipRules);
|
|
153
|
+
if (folder === undefined)
|
|
154
|
+
return body;
|
|
155
|
+
const title = prepared.projection.presentation?.title;
|
|
156
|
+
return title === undefined
|
|
157
|
+
? body.replace(` view ${viewId} {`, ` view ${viewId} {\n${titleLines(folder, undefined, viewId).join('\n')}`)
|
|
158
|
+
: body.replace(` title ${quote(title)}`, titleLines(folder, title, viewId).join('\n'));
|
|
129
159
|
};
|
|
130
160
|
const deploymentBody = ({ deployment, prepared, }) => {
|
|
131
161
|
if (deployment === undefined)
|
|
@@ -168,12 +198,48 @@ const deploymentBody = ({ deployment, prepared, }) => {
|
|
|
168
198
|
'}',
|
|
169
199
|
].join('\n');
|
|
170
200
|
};
|
|
171
|
-
|
|
201
|
+
// The synthetic review view (ADR 0066): every changed subject with its
|
|
202
|
+
// highlight, and a description that doubles as the legend.
|
|
203
|
+
const reviewChangesView = (views, gitChange) => {
|
|
204
|
+
const externalByNative = new Map(views.flatMap((view) => view.prepared.subjectMapping.mappings
|
|
205
|
+
.filter(({ type }) => type === 'concept')
|
|
206
|
+
.map(({ native, external }) => [native, external])));
|
|
207
|
+
const changedConcepts = [
|
|
208
|
+
...new Set([...gitChange.added, ...gitChange.modified].flatMap((nativeId) => {
|
|
209
|
+
const external = externalByNative.get(nativeId);
|
|
210
|
+
return external === undefined ? [] : [external];
|
|
211
|
+
})),
|
|
212
|
+
].sort();
|
|
213
|
+
if (changedConcepts.length === 0)
|
|
214
|
+
return undefined;
|
|
215
|
+
const changedRelationships = [
|
|
216
|
+
...new Set([...gitChange.added, ...gitChange.modified].filter((nativeId) => !externalByNative.has(nativeId))),
|
|
217
|
+
].sort();
|
|
218
|
+
const styles = changedConcepts.map((external) => {
|
|
219
|
+
const nativeId = [...externalByNative.entries()].find(([, candidate]) => candidate === external)[0];
|
|
220
|
+
return gitChange.added.includes(nativeId)
|
|
221
|
+
? ` style ${external} { color green }`
|
|
222
|
+
: ` style ${external} { color amber }`;
|
|
223
|
+
});
|
|
224
|
+
return [
|
|
225
|
+
' view review-changes {',
|
|
226
|
+
` title ${quote(`Review: ${gitChange.range}`)}`,
|
|
227
|
+
` description ${quote(`Subjects touched in ${gitChange.range}. Legend: green = new, amber = changed; connected context unhighlighted. Derived from git - nothing here is authored (ADR 0066).`)}`,
|
|
228
|
+
` include ${changedConcepts.join(', ')}`,
|
|
229
|
+
...changedRelationships.map((id) => ` include * -> * where metadata.yarramateId is '${id}'`),
|
|
230
|
+
...styles,
|
|
231
|
+
' autoLayout LeftRight',
|
|
232
|
+
' }',
|
|
233
|
+
].join('\n');
|
|
234
|
+
};
|
|
235
|
+
export function exportLikeC4Project(project, views, options = {}) {
|
|
172
236
|
const first = views[0];
|
|
173
237
|
if (first === undefined) {
|
|
174
238
|
throw new Error('LikeC4 project requires at least one view');
|
|
175
239
|
}
|
|
176
|
-
const model = exportLikeC4(unionProjection(project, views), first.prepared.subjectMapping, first.prepared.kindMapping
|
|
240
|
+
const model = exportLikeC4(unionProjection(project, views), first.prepared.subjectMapping, first.prepared.kindMapping, options.gitChange === undefined
|
|
241
|
+
? {}
|
|
242
|
+
: { gitChange: options.gitChange });
|
|
177
243
|
if (!model.ok)
|
|
178
244
|
return model;
|
|
179
245
|
const startToken = '\nviews {\n';
|
|
@@ -182,9 +248,13 @@ export function exportLikeC4Project(project, views) {
|
|
|
182
248
|
const rendered = deploymentBody(view);
|
|
183
249
|
return rendered === undefined ? [] : [rendered];
|
|
184
250
|
});
|
|
185
|
-
const renderedViews = views.map(viewBody);
|
|
251
|
+
const renderedViews = views.map((view) => viewBody(view, options.gitChange));
|
|
252
|
+
const review = options.gitChange === undefined
|
|
253
|
+
? undefined
|
|
254
|
+
: reviewChangesView(views, options.gitChange);
|
|
255
|
+
const allViews = review === undefined ? renderedViews : [...renderedViews, review];
|
|
186
256
|
return {
|
|
187
257
|
ok: true,
|
|
188
|
-
source: `${model.source.slice(0, modelEnd)}${deployments.length === 0 ? '' : `\n${deployments.join('\n')}\n`}\nviews {\n${
|
|
258
|
+
source: `${model.source.slice(0, modelEnd)}${deployments.length === 0 ? '' : `\n${deployments.join('\n')}\n`}\nviews {\n${allViews.join('\n')}\n}\n`,
|
|
189
259
|
};
|
|
190
260
|
}
|
package/dist/changed.d.ts
CHANGED
|
@@ -2,6 +2,9 @@ export interface ChangedSubjects {
|
|
|
2
2
|
readonly range: string;
|
|
3
3
|
readonly concepts: readonly string[];
|
|
4
4
|
readonly relationships: readonly string[];
|
|
5
|
+
/** Subjects whose entire declaration is new in the range (subset of
|
|
6
|
+
* concepts + relationships); the rest changed in place. */
|
|
7
|
+
readonly added: readonly string[];
|
|
5
8
|
}
|
|
6
9
|
export type ChangedResult = {
|
|
7
10
|
readonly ok: true;
|
package/dist/changed.js
CHANGED
|
@@ -52,16 +52,21 @@ const itemSpans = (source) => {
|
|
|
52
52
|
}
|
|
53
53
|
return spans;
|
|
54
54
|
};
|
|
55
|
-
// New-side line ranges from `git diff --unified=0`. A pure deletion has a
|
|
56
|
-
// zero count; it still touches the position it collapsed onto.
|
|
57
55
|
const changedLineRanges = (diff) => {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
56
|
+
const touched = [];
|
|
57
|
+
const inserted = [];
|
|
58
|
+
for (const match of diff.matchAll(/^@@ -\d+(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/gm)) {
|
|
59
|
+
const oldCount = match[1] === undefined ? 1 : Number(match[1]);
|
|
60
|
+
const start = Number(match[2]);
|
|
61
|
+
const count = match[3] === undefined ? 1 : Number(match[3]);
|
|
62
|
+
const range = count === 0
|
|
63
|
+
? [Math.max(start, 1), Math.max(start, 1)]
|
|
64
|
+
: [start, start + count - 1];
|
|
65
|
+
touched.push(range);
|
|
66
|
+
if (oldCount === 0 && count > 0)
|
|
67
|
+
inserted.push(range);
|
|
63
68
|
}
|
|
64
|
-
return
|
|
69
|
+
return { touched, inserted };
|
|
65
70
|
};
|
|
66
71
|
export function deriveChangedSubjects(cwd, range, documents) {
|
|
67
72
|
const probe = spawnSync('git', ['-C', cwd, 'rev-parse', '--git-dir'], {
|
|
@@ -75,6 +80,7 @@ export function deriveChangedSubjects(cwd, range, documents) {
|
|
|
75
80
|
}
|
|
76
81
|
const concepts = new Set();
|
|
77
82
|
const relationships = new Set();
|
|
83
|
+
const added = new Set();
|
|
78
84
|
for (const document of documents) {
|
|
79
85
|
const diffed = spawnSync('git', ['-C', cwd, 'diff', '--unified=0', range, '--', document.path], { encoding: 'utf8' });
|
|
80
86
|
if (diffed.status !== 0) {
|
|
@@ -84,13 +90,13 @@ export function deriveChangedSubjects(cwd, range, documents) {
|
|
|
84
90
|
};
|
|
85
91
|
}
|
|
86
92
|
const ranges = changedLineRanges(diffed.stdout ?? '');
|
|
87
|
-
if (ranges.length === 0)
|
|
93
|
+
if (ranges.touched.length === 0)
|
|
88
94
|
continue;
|
|
89
95
|
const spans = itemSpans(document.source);
|
|
90
96
|
for (const span of spans) {
|
|
91
97
|
if (span.collection === 'states')
|
|
92
98
|
continue;
|
|
93
|
-
const touched = ranges.some(([from, to]) => from <= span.endLine && to >= span.startLine);
|
|
99
|
+
const touched = ranges.touched.some(([from, to]) => from <= span.endLine && to >= span.startLine);
|
|
94
100
|
if (!touched)
|
|
95
101
|
continue;
|
|
96
102
|
const qualified = `${document.documentId}#${span.id}`;
|
|
@@ -98,6 +104,9 @@ export function deriveChangedSubjects(cwd, range, documents) {
|
|
|
98
104
|
concepts.add(qualified);
|
|
99
105
|
else
|
|
100
106
|
relationships.add(qualified);
|
|
107
|
+
const whollyInserted = ranges.inserted.some(([from, to]) => from <= span.startLine && to >= span.endLine);
|
|
108
|
+
if (whollyInserted)
|
|
109
|
+
added.add(qualified);
|
|
101
110
|
}
|
|
102
111
|
}
|
|
103
112
|
return {
|
|
@@ -106,6 +115,7 @@ export function deriveChangedSubjects(cwd, range, documents) {
|
|
|
106
115
|
range,
|
|
107
116
|
concepts: [...concepts].sort(),
|
|
108
117
|
relationships: [...relationships].sort(),
|
|
118
|
+
added: [...added].sort(),
|
|
109
119
|
},
|
|
110
120
|
};
|
|
111
121
|
}
|
package/dist/cli-support.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface CliResult {
|
|
|
7
7
|
export declare const isMainModule: (moduleUrl: string, entrypoint: string | undefined) => boolean;
|
|
8
8
|
export declare const packageVersion: string;
|
|
9
9
|
export declare const versionResult: (binary: string) => CliResult;
|
|
10
|
-
export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise \"<topic>\" [--budget <tokens>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml
|
|
10
|
+
export declare const usage = "Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> \"<free text>\" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise \"<topic>\" [--budget <tokens>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n";
|
|
11
11
|
export declare const diagnosticJson: (diagnostics: unknown) => string;
|
|
12
12
|
export declare const checkResultJson: (ok: boolean, diagnostics: unknown, counted?: {
|
|
13
13
|
readonly documents: number;
|
package/dist/cli-support.js
CHANGED
|
@@ -23,7 +23,7 @@ export const versionResult = (binary) => ({
|
|
|
23
23
|
stdout: `${binary} ${packageVersion}\n`,
|
|
24
24
|
stderr: '',
|
|
25
25
|
});
|
|
26
|
-
export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise "<topic>" [--budget <tokens>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml
|
|
26
|
+
export const usage = 'Usage:\n yarramate init <directory> [--no-pointer]\n yarramate design <workspace.yaml> [--subject <document-id>#<local-id>] [--catalogue <catalogue.yaml>] [--json]\n yarramate apply <operations.yaml> <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> [--json]\n yarramate ask <workspace.yaml> "<free text>" | <document-id>#<local-id> ... | <projection.yaml> [--budget <tokens>] [--json]\n yarramate ask <workspace.yaml> --subjects [--kind <term>] [--status <status>] [--json]\n yarramate ask <workspace.yaml> --kinds [--json]\n yarramate ask <workspace.yaml> --advise "<topic>" [--budget <tokens>] [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --next [--json]\n yarramate ask <workspace.yaml> --open [--catalogue <catalogue.yaml>] [--json]\n yarramate ask <workspace.yaml> --compare <from-state> <to-state> [--json]\n yarramate ask <workspace.yaml> --changed <git-range> [--budget <tokens>] [--json]\n yarramate check <source.yaml> [source.yaml ...] [--json] [--strict]\n yarramate reconcile <workspace.yaml>\n yarramate export graph <workspace.yaml> [--out <file>]\n yarramate export markdown <projection.yaml> <workspace.yaml> [--out <file>]\n yarramate export markdown --changed <git-range> <workspace.yaml> [--out <file>]\n yarramate export briefs <projection.yaml> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export briefs --changed <git-range> <workspace.yaml> --out <directory> [--budget <tokens>]\n yarramate export likec4 <likec4-project.yaml> <output-dir> <workspace.yaml> [--changed <git-range>]\n';
|
|
27
27
|
export const diagnosticJson = (diagnostics) => `${JSON.stringify({
|
|
28
28
|
format: 'yarramate/diagnostic-result/v1',
|
|
29
29
|
diagnostics,
|
package/dist/export-command.js
CHANGED
|
@@ -91,6 +91,7 @@ export function runExportCommand(options, cwd) {
|
|
|
91
91
|
parsed.json) {
|
|
92
92
|
return { exitCode: 2, stdout: '', stderr: usage };
|
|
93
93
|
}
|
|
94
|
+
const changedArguments = parsed.changed === undefined ? [] : ['--changed', parsed.changed];
|
|
94
95
|
if (!existsSync(likec4AdapterEntry)) {
|
|
95
96
|
return {
|
|
96
97
|
exitCode: 2,
|
|
@@ -105,6 +106,7 @@ export function runExportCommand(options, cwd) {
|
|
|
105
106
|
projectDefinition,
|
|
106
107
|
outputDirectory,
|
|
107
108
|
workspacePath,
|
|
109
|
+
...changedArguments,
|
|
108
110
|
], { cwd, encoding: 'utf8' });
|
|
109
111
|
const exitCode = delegated.status === 0 ? 0 : delegated.status === 1 ? 1 : 2;
|
|
110
112
|
return {
|
package/package.json
CHANGED
|
@@ -72,6 +72,11 @@
|
|
|
72
72
|
"projection": {
|
|
73
73
|
"$ref": "#/$defs/path"
|
|
74
74
|
},
|
|
75
|
+
"folder": {
|
|
76
|
+
"type": "string",
|
|
77
|
+
"description": "Sidebar folder for the rendered view, emitted as a LikeC4 title path ('Folder / Title'). Nest with '/' separators; folders appear in the order their first view appears in the views list.",
|
|
78
|
+
"pattern": "^[^/]+(?:/[^/]+)*$"
|
|
79
|
+
},
|
|
75
80
|
"compare": {
|
|
76
81
|
"$ref": "#/$defs/comparison"
|
|
77
82
|
},
|