@auto-engineer/server-generator-apollo-emmett 1.144.0 → 1.146.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +6 -6
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +55 -0
- package/dist/src/codegen/extract/imports.js +1 -1
- package/dist/src/codegen/extract/imports.js.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.d.ts +11 -1
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +52 -9
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/command/handle.specs.ts +201 -0
- package/dist/src/codegen/templates/command/handle.ts.ejs +34 -0
- package/dist/src/codegen/templates/query/projection.specs.ts +3 -10
- package/dist/src/codegen/templates/query/query.resolver.specs.ts +6 -20
- package/dist/src/codegen/templates/query/query.resolver.ts.ejs +14 -5
- package/dist/src/codegen/templates/react/react.specs.ts.ejs +2 -2
- package/dist/src/codegen/templates/react/react.ts.ejs +1 -1
- package/dist/src/codegen/templates/react/register.ts.ejs +1 -1
- package/dist/src/codegen/types.d.ts +1 -0
- package/dist/src/codegen/types.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ketchup-plan.md +5 -6
- package/package.json +4 -4
- package/src/codegen/buildCrossSceneGivens.specs.ts +263 -0
- package/src/codegen/extract/imports.specs.ts +26 -0
- package/src/codegen/extract/imports.ts +1 -1
- package/src/codegen/scaffoldFromSchema.ts +73 -5
- package/src/codegen/templates/command/handle.specs.ts +201 -0
- package/src/codegen/templates/command/handle.ts.ejs +34 -0
- package/src/codegen/templates/query/projection.specs.ts +3 -10
- package/src/codegen/templates/query/query.resolver.specs.ts +6 -20
- package/src/codegen/templates/query/query.resolver.ts.ejs +14 -5
- package/src/codegen/templates/react/react.specs.ts.ejs +2 -2
- package/src/codegen/templates/react/react.ts.ejs +1 -1
- package/src/codegen/templates/react/register.ts.ejs +1 -1
- package/src/codegen/types.ts +1 -0
package/ketchup-plan.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
# Ketchup Plan:
|
|
1
|
+
# Ketchup Plan: Cross-Scene Given Events in handle.ts
|
|
2
2
|
|
|
3
3
|
## TODO
|
|
4
4
|
|
|
5
|
-
- [x] Burst 1: Add `escapeJsString` utility + unit tests + wire into renderTemplate (7a5544a6)
|
|
6
|
-
- [x] Burst 2: Fix `projection.specs.ts.ejs` interpolation points (d108c530)
|
|
7
|
-
- [x] Burst 3: Fix `decide.specs.ts.ejs` and `decide.ts.ejs` interpolation points (f9b46ada)
|
|
8
|
-
- [x] Burst 4: Fix `react.specs.ts.ejs` interpolation points
|
|
9
|
-
|
|
10
5
|
## DONE
|
|
6
|
+
|
|
7
|
+
- [x] Burst 1: Compute `crossSceneGivens` in template data (d5786a13)
|
|
8
|
+
- [x] Burst 2: Generate cross-scene handler in handle.ts.ejs (f919ab8b)
|
|
9
|
+
- [x] Burst 3: End-to-end verification — all 299 tests pass, existing snapshots unchanged
|
package/package.json
CHANGED
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"uuid": "^13.0.0",
|
|
33
33
|
"web-streams-polyfill": "^4.1.0",
|
|
34
34
|
"zod": "^3.22.4",
|
|
35
|
-
"@auto-engineer/narrative": "1.
|
|
36
|
-
"@auto-engineer/message-bus": "1.
|
|
35
|
+
"@auto-engineer/narrative": "1.146.0",
|
|
36
|
+
"@auto-engineer/message-bus": "1.146.0"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
"typescript": "^5.8.3",
|
|
45
45
|
"vitest": "^3.2.4",
|
|
46
46
|
"tsx": "^4.19.2",
|
|
47
|
-
"@auto-engineer/cli": "1.
|
|
47
|
+
"@auto-engineer/cli": "1.146.0"
|
|
48
48
|
},
|
|
49
|
-
"version": "1.
|
|
49
|
+
"version": "1.146.0",
|
|
50
50
|
"scripts": {
|
|
51
51
|
"generate:server": "tsx src/cli/index.ts",
|
|
52
52
|
"build": "tsc && tsx ../../scripts/fix-esm-imports.ts && rm -rf dist/src/codegen/templates && mkdir -p dist/src/codegen && cp -r src/codegen/templates dist/src/codegen/templates && cp src/server.ts dist/src && cp -r src/utils dist/src && cp -r src/domain dist/src",
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import type { Scene } from '@auto-engineer/narrative';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
import { buildCrossSceneGivens } from './scaffoldFromSchema';
|
|
4
|
+
import type { GwtCondition, Message } from './types';
|
|
5
|
+
|
|
6
|
+
describe('buildCrossSceneGivens', () => {
|
|
7
|
+
const workoutsScene: Scene = {
|
|
8
|
+
name: 'Workouts',
|
|
9
|
+
moments: [
|
|
10
|
+
{
|
|
11
|
+
type: 'command',
|
|
12
|
+
name: 'Create workout',
|
|
13
|
+
stream: 'workout-${workoutId}',
|
|
14
|
+
client: { specs: [] },
|
|
15
|
+
server: {
|
|
16
|
+
description: '',
|
|
17
|
+
specs: [
|
|
18
|
+
{
|
|
19
|
+
type: 'gherkin',
|
|
20
|
+
feature: 'Create workout',
|
|
21
|
+
rules: [
|
|
22
|
+
{
|
|
23
|
+
name: 'Should create',
|
|
24
|
+
examples: [
|
|
25
|
+
{
|
|
26
|
+
name: 'Workout created',
|
|
27
|
+
steps: [
|
|
28
|
+
{ keyword: 'When', text: 'CreateWorkout', docString: { workoutId: 'w1' } },
|
|
29
|
+
{ keyword: 'Then', text: 'WorkoutCreated', docString: { workoutId: 'w1' } },
|
|
30
|
+
],
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
data: {
|
|
38
|
+
items: [
|
|
39
|
+
{
|
|
40
|
+
target: { type: 'Event', name: 'WorkoutCreated' },
|
|
41
|
+
destination: { type: 'stream', pattern: 'workout-${workoutId}' },
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const progressScene: Scene = {
|
|
51
|
+
name: 'Progress',
|
|
52
|
+
moments: [
|
|
53
|
+
{
|
|
54
|
+
type: 'command',
|
|
55
|
+
name: 'Record progress',
|
|
56
|
+
stream: 'progress-${progressId}',
|
|
57
|
+
client: { specs: [] },
|
|
58
|
+
server: {
|
|
59
|
+
description: '',
|
|
60
|
+
specs: [
|
|
61
|
+
{
|
|
62
|
+
type: 'gherkin',
|
|
63
|
+
feature: 'Record progress',
|
|
64
|
+
rules: [
|
|
65
|
+
{
|
|
66
|
+
name: 'Should record',
|
|
67
|
+
examples: [
|
|
68
|
+
{
|
|
69
|
+
name: 'Progress recorded',
|
|
70
|
+
steps: [
|
|
71
|
+
{ keyword: 'Given', text: 'WorkoutCreated', docString: { workoutId: 'w1' } },
|
|
72
|
+
{
|
|
73
|
+
keyword: 'When',
|
|
74
|
+
text: 'RecordProgress',
|
|
75
|
+
docString: { workoutId: 'w1', date: '2024-01-01' },
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
keyword: 'Then',
|
|
79
|
+
text: 'ProgressRecorded',
|
|
80
|
+
docString: { workoutId: 'w1', date: '2024-01-01' },
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
data: {
|
|
90
|
+
items: [
|
|
91
|
+
{
|
|
92
|
+
target: { type: 'Event', name: 'ProgressRecorded' },
|
|
93
|
+
destination: { type: 'stream', pattern: 'progress-${progressId}' },
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
it('detects cross-scene Given with resolved linking fields', () => {
|
|
103
|
+
const gwtMapping: Record<string, GwtCondition[]> = {
|
|
104
|
+
RecordProgress: [
|
|
105
|
+
{
|
|
106
|
+
given: [{ eventRef: 'WorkoutCreated', exampleData: { workoutId: 'w1' } }],
|
|
107
|
+
when: { commandRef: 'RecordProgress', exampleData: { workoutId: 'w1', date: '2024-01-01' } },
|
|
108
|
+
then: [{ eventRef: 'ProgressRecorded', exampleData: { workoutId: 'w1', date: '2024-01-01' } }],
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const events: Message[] = [
|
|
114
|
+
{
|
|
115
|
+
type: 'WorkoutCreated',
|
|
116
|
+
fields: [{ name: 'workoutId', tsType: 'string', required: true }],
|
|
117
|
+
sourceSceneName: 'Workouts',
|
|
118
|
+
sourceMomentName: 'Create workout',
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
type: 'ProgressRecorded',
|
|
122
|
+
fields: [
|
|
123
|
+
{ name: 'workoutId', tsType: 'string', required: true },
|
|
124
|
+
{ name: 'date', tsType: 'string', required: true },
|
|
125
|
+
],
|
|
126
|
+
sourceSceneName: 'Progress',
|
|
127
|
+
sourceMomentName: 'Record progress',
|
|
128
|
+
},
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
const commands: Message[] = [
|
|
132
|
+
{
|
|
133
|
+
type: 'RecordProgress',
|
|
134
|
+
fields: [
|
|
135
|
+
{ name: 'workoutId', tsType: 'string', required: true },
|
|
136
|
+
{ name: 'date', tsType: 'string', required: true },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
const result = buildCrossSceneGivens(gwtMapping, events, progressScene, [workoutsScene, progressScene], commands);
|
|
142
|
+
|
|
143
|
+
expect(result).toEqual([
|
|
144
|
+
{
|
|
145
|
+
sourceStreamPattern: 'workout-${workoutId}',
|
|
146
|
+
linkingFields: [{ streamVar: 'workoutId', commandField: 'workoutId' }],
|
|
147
|
+
allVarsResolved: true,
|
|
148
|
+
},
|
|
149
|
+
]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('returns empty array when no cross-scene Givens exist', () => {
|
|
153
|
+
const gwtMapping: Record<string, GwtCondition[]> = {
|
|
154
|
+
CreateWorkout: [
|
|
155
|
+
{
|
|
156
|
+
when: { commandRef: 'CreateWorkout', exampleData: { workoutId: 'w1' } },
|
|
157
|
+
then: [{ eventRef: 'WorkoutCreated', exampleData: { workoutId: 'w1' } }],
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const events: Message[] = [
|
|
163
|
+
{
|
|
164
|
+
type: 'WorkoutCreated',
|
|
165
|
+
fields: [{ name: 'workoutId', tsType: 'string', required: true }],
|
|
166
|
+
sourceSceneName: 'Workouts',
|
|
167
|
+
sourceMomentName: 'Create workout',
|
|
168
|
+
},
|
|
169
|
+
];
|
|
170
|
+
|
|
171
|
+
const commands: Message[] = [
|
|
172
|
+
{
|
|
173
|
+
type: 'CreateWorkout',
|
|
174
|
+
fields: [{ name: 'workoutId', tsType: 'string', required: true }],
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
const result = buildCrossSceneGivens(gwtMapping, events, workoutsScene, [workoutsScene], commands);
|
|
179
|
+
|
|
180
|
+
expect(result).toEqual([]);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it('marks unresolved when stream var has no matching command field', () => {
|
|
184
|
+
const gwtMapping: Record<string, GwtCondition[]> = {
|
|
185
|
+
RecordProgress: [
|
|
186
|
+
{
|
|
187
|
+
given: [{ eventRef: 'WorkoutCreated', exampleData: { workoutId: 'w1' } }],
|
|
188
|
+
when: { commandRef: 'RecordProgress', exampleData: { date: '2024-01-01' } },
|
|
189
|
+
then: [{ eventRef: 'ProgressRecorded', exampleData: { date: '2024-01-01' } }],
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const events: Message[] = [
|
|
195
|
+
{
|
|
196
|
+
type: 'WorkoutCreated',
|
|
197
|
+
fields: [{ name: 'workoutId', tsType: 'string', required: true }],
|
|
198
|
+
sourceSceneName: 'Workouts',
|
|
199
|
+
sourceMomentName: 'Create workout',
|
|
200
|
+
},
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
const commands: Message[] = [
|
|
204
|
+
{
|
|
205
|
+
type: 'RecordProgress',
|
|
206
|
+
fields: [{ name: 'date', tsType: 'string', required: true }],
|
|
207
|
+
},
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
const result = buildCrossSceneGivens(gwtMapping, events, progressScene, [workoutsScene, progressScene], commands);
|
|
211
|
+
|
|
212
|
+
expect(result).toEqual([
|
|
213
|
+
{
|
|
214
|
+
sourceStreamPattern: 'workout-${workoutId}',
|
|
215
|
+
linkingFields: [],
|
|
216
|
+
allVarsResolved: false,
|
|
217
|
+
},
|
|
218
|
+
]);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('deduplicates by source stream pattern', () => {
|
|
222
|
+
const gwtMapping: Record<string, GwtCondition[]> = {
|
|
223
|
+
RecordProgress: [
|
|
224
|
+
{
|
|
225
|
+
given: [{ eventRef: 'WorkoutCreated', exampleData: { workoutId: 'w1' } }],
|
|
226
|
+
when: { commandRef: 'RecordProgress', exampleData: { workoutId: 'w1' } },
|
|
227
|
+
then: [{ eventRef: 'ProgressRecorded', exampleData: { workoutId: 'w1' } }],
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
given: [{ eventRef: 'WorkoutCreated', exampleData: { workoutId: 'w2' } }],
|
|
231
|
+
when: { commandRef: 'RecordProgress', exampleData: { workoutId: 'w2' } },
|
|
232
|
+
then: [{ eventRef: 'ProgressRecorded', exampleData: { workoutId: 'w2' } }],
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const events: Message[] = [
|
|
238
|
+
{
|
|
239
|
+
type: 'WorkoutCreated',
|
|
240
|
+
fields: [{ name: 'workoutId', tsType: 'string', required: true }],
|
|
241
|
+
sourceSceneName: 'Workouts',
|
|
242
|
+
sourceMomentName: 'Create workout',
|
|
243
|
+
},
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
const commands: Message[] = [
|
|
247
|
+
{
|
|
248
|
+
type: 'RecordProgress',
|
|
249
|
+
fields: [{ name: 'workoutId', tsType: 'string', required: true }],
|
|
250
|
+
},
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
const result = buildCrossSceneGivens(gwtMapping, events, progressScene, [workoutsScene, progressScene], commands);
|
|
254
|
+
|
|
255
|
+
expect(result).toEqual([
|
|
256
|
+
{
|
|
257
|
+
sourceStreamPattern: 'workout-${workoutId}',
|
|
258
|
+
linkingFields: [{ streamVar: 'workoutId', commandField: 'workoutId' }],
|
|
259
|
+
allVarsResolved: true,
|
|
260
|
+
},
|
|
261
|
+
]);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
@@ -60,6 +60,32 @@ describe('groupEventImports', () => {
|
|
|
60
60
|
]);
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
it('uses sourceSceneDirName for cross-scene imports when set', () => {
|
|
64
|
+
const events: Message[] = [
|
|
65
|
+
{
|
|
66
|
+
type: 'WorkoutCreated',
|
|
67
|
+
fields: [],
|
|
68
|
+
source: 'when',
|
|
69
|
+
sourceSceneName: 'gym workout creation',
|
|
70
|
+
sourceMomentName: 'create workout',
|
|
71
|
+
sourceSceneDirName: 'gym-tracker_gym-workout-creation',
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
const result = groupEventImports({
|
|
76
|
+
currentMomentName: 'view workout progress',
|
|
77
|
+
currentSceneName: 'gym workout completion',
|
|
78
|
+
events,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
expect(result).toEqual([
|
|
82
|
+
{
|
|
83
|
+
importPath: '../../gym-tracker_gym-workout-creation/create-workout/events',
|
|
84
|
+
eventTypes: ['WorkoutCreated'],
|
|
85
|
+
},
|
|
86
|
+
]);
|
|
87
|
+
});
|
|
88
|
+
|
|
63
89
|
it('handles mixed same-scene and cross-scene events', () => {
|
|
64
90
|
const events: Message[] = [
|
|
65
91
|
{
|
|
@@ -34,7 +34,7 @@ export function groupEventImports(context: CrossMomentImportContext): ImportGrou
|
|
|
34
34
|
const isCrossScene = sourceSceneName != null && sourceSceneName !== currentSceneName;
|
|
35
35
|
const sliceSegment = toKebabCase(event.sourceMomentName ?? currentMomentName);
|
|
36
36
|
importPath = isCrossScene
|
|
37
|
-
? `../../${toKebabCase(sourceSceneName)}/${sliceSegment}/events`
|
|
37
|
+
? `../../${event.sourceSceneDirName ?? toKebabCase(sourceSceneName)}/${sliceSegment}/events`
|
|
38
38
|
: `../${sliceSegment}/events`;
|
|
39
39
|
}
|
|
40
40
|
if (!importGroups.has(importPath)) {
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
isValidTsIdentifier,
|
|
43
43
|
sanitizeFieldType,
|
|
44
44
|
} from './extract';
|
|
45
|
-
import { getStreamFromSink } from './extract/data-sink';
|
|
45
|
+
import { extractStreamIdFields, getStreamFromSink } from './extract/data-sink';
|
|
46
46
|
import { buildEventIdFieldMap } from './extract/projection';
|
|
47
47
|
import { buildArgToStateFieldMap } from './extract/query';
|
|
48
48
|
import { normalizeMomentForTemplate } from './extract/slice-normalizer';
|
|
@@ -709,6 +709,8 @@ async function prepareTemplateData(
|
|
|
709
709
|
const argToStateFieldMap =
|
|
710
710
|
slice.type === 'query' ? buildArgToStateFieldMap(queryGwtMapping, stateFieldNames, slice.mappings) : undefined;
|
|
711
711
|
|
|
712
|
+
const crossSceneGivens = buildCrossSceneGivens(gwtMapping, events, scene, scenes ?? [], filteredCommands);
|
|
713
|
+
|
|
712
714
|
return {
|
|
713
715
|
sceneName: scene.name,
|
|
714
716
|
momentName: slice.name,
|
|
@@ -739,21 +741,77 @@ async function prepareTemplateData(
|
|
|
739
741
|
eventCommandPairs,
|
|
740
742
|
eventIdFieldMap,
|
|
741
743
|
argToStateFieldMap,
|
|
744
|
+
crossSceneGivens,
|
|
742
745
|
};
|
|
743
746
|
}
|
|
744
747
|
|
|
748
|
+
interface CrossSceneGiven {
|
|
749
|
+
sourceStreamPattern: string;
|
|
750
|
+
linkingFields: Array<{
|
|
751
|
+
streamVar: string;
|
|
752
|
+
commandField: string;
|
|
753
|
+
}>;
|
|
754
|
+
allVarsResolved: boolean;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export function buildCrossSceneGivens(
|
|
758
|
+
gwtMapping: Record<string, GwtCondition[]>,
|
|
759
|
+
events: Message[],
|
|
760
|
+
currentScene: Scene,
|
|
761
|
+
scenes: Scene[],
|
|
762
|
+
filteredCommands: Message[],
|
|
763
|
+
): CrossSceneGiven[] {
|
|
764
|
+
const commandFieldNames = new Set(filteredCommands.flatMap((c) => c.fields.map((f) => f.name)));
|
|
765
|
+
const seenPatterns = new Set<string>();
|
|
766
|
+
const result: CrossSceneGiven[] = [];
|
|
767
|
+
|
|
768
|
+
for (const conditions of Object.values(gwtMapping)) {
|
|
769
|
+
for (const gwt of conditions) {
|
|
770
|
+
for (const givenRef of gwt.given ?? []) {
|
|
771
|
+
if (!('eventRef' in givenRef)) continue;
|
|
772
|
+
|
|
773
|
+
const event = events.find((e) => e.type === givenRef.eventRef);
|
|
774
|
+
if (!event || event.sourceSceneName === currentScene.name) continue;
|
|
775
|
+
|
|
776
|
+
const sourceScene = scenes.find((s) => s.name === event.sourceSceneName);
|
|
777
|
+
const sourceMoment = sourceScene?.moments.find((m) => m.name === event.sourceMomentName);
|
|
778
|
+
if (!sourceMoment) continue;
|
|
779
|
+
|
|
780
|
+
const { streamPattern } = getStreamFromSink(sourceMoment);
|
|
781
|
+
if (!streamPattern || seenPatterns.has(streamPattern)) continue;
|
|
782
|
+
seenPatterns.add(streamPattern);
|
|
783
|
+
|
|
784
|
+
const streamVars = extractStreamIdFields(streamPattern);
|
|
785
|
+
const linkingFields = streamVars
|
|
786
|
+
.filter((v) => commandFieldNames.has(v))
|
|
787
|
+
.map((v) => ({ streamVar: v, commandField: v }));
|
|
788
|
+
|
|
789
|
+
result.push({
|
|
790
|
+
sourceStreamPattern: streamPattern,
|
|
791
|
+
linkingFields,
|
|
792
|
+
allVarsResolved: linkingFields.length === streamVars.length,
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return result;
|
|
799
|
+
}
|
|
800
|
+
|
|
745
801
|
function annotateEventSources(
|
|
746
802
|
events: Message[],
|
|
747
803
|
scenes: Scene[],
|
|
748
804
|
fallbackFlowName: string,
|
|
749
805
|
fallbackMomentName: string,
|
|
750
806
|
momentType: string,
|
|
807
|
+
sceneNameToDirName?: Map<string, string>,
|
|
751
808
|
): void {
|
|
752
809
|
debug('Annotating event sources for %d events', events.length);
|
|
753
810
|
for (const event of events) {
|
|
754
811
|
const match = findEventSource(scenes, event.type);
|
|
755
812
|
event.sourceSceneName = match?.sceneName ?? fallbackFlowName;
|
|
756
813
|
event.sourceMomentName = match?.momentName ?? (momentType === 'react' ? undefined : fallbackMomentName);
|
|
814
|
+
event.sourceSceneDirName = sceneNameToDirName?.get(event.sourceSceneName);
|
|
757
815
|
debug(' Event %s: scene=%s, slice=%s', event.type, event.sourceSceneName, event.sourceMomentName);
|
|
758
816
|
}
|
|
759
817
|
}
|
|
@@ -799,12 +857,14 @@ function annotateCommandSources(
|
|
|
799
857
|
fallbackFlowName: string,
|
|
800
858
|
fallbackMomentName: string,
|
|
801
859
|
momentType: string,
|
|
860
|
+
sceneNameToDirName?: Map<string, string>,
|
|
802
861
|
): void {
|
|
803
862
|
debug('Annotating command sources for %d commands', commands.length);
|
|
804
863
|
for (const command of commands) {
|
|
805
864
|
const match = findCommandSource(scenes, command.type);
|
|
806
865
|
command.sourceSceneName = match?.sceneName ?? fallbackFlowName;
|
|
807
866
|
command.sourceMomentName = match?.momentName ?? (momentType === 'react' ? undefined : fallbackMomentName);
|
|
867
|
+
command.sourceSceneDirName = sceneNameToDirName?.get(command.sourceSceneName);
|
|
808
868
|
debug(' Command %s: scene=%s, slice=%s', command.type, command.sourceSceneName, command.sourceMomentName);
|
|
809
869
|
}
|
|
810
870
|
}
|
|
@@ -835,6 +895,7 @@ async function generateFilesForMoment(
|
|
|
835
895
|
unionToEnumName: Map<string, string>,
|
|
836
896
|
integrations?: Model['integrations'],
|
|
837
897
|
registeredCommands?: Map<string, { sceneName: string; momentName: string }>,
|
|
898
|
+
sceneNameToDirName?: Map<string, string>,
|
|
838
899
|
): Promise<{ plans: FilePlan[]; duplicateCommands: DuplicateCommandInfo[] }> {
|
|
839
900
|
debugMoment('Generating files for moment: %s (type: %s)', slice.name, slice.type);
|
|
840
901
|
debugMoment(' Scene: %s', scene.name);
|
|
@@ -867,8 +928,8 @@ async function generateFilesForMoment(
|
|
|
867
928
|
slice.name,
|
|
868
929
|
extracted.events.map((e) => e.type),
|
|
869
930
|
);
|
|
870
|
-
annotateEventSources(extracted.events, scenes, scene.name, slice.name, slice.type);
|
|
871
|
-
annotateCommandSources(extracted.commands, scenes, scene.name, slice.name, slice.type);
|
|
931
|
+
annotateEventSources(extracted.events, scenes, scene.name, slice.name, slice.type, sceneNameToDirName);
|
|
932
|
+
annotateCommandSources(extracted.commands, scenes, scene.name, slice.name, slice.type, sceneNameToDirName);
|
|
872
933
|
|
|
873
934
|
let filteredTemplates = templates;
|
|
874
935
|
|
|
@@ -986,12 +1047,18 @@ export async function generateScaffoldFilePlans(
|
|
|
986
1047
|
}
|
|
987
1048
|
}
|
|
988
1049
|
|
|
1050
|
+
const sceneNameToDirName = new Map<string, string>();
|
|
989
1051
|
for (const scene of scenes) {
|
|
990
|
-
debugScene('Processing scene: %s', scene.name);
|
|
991
1052
|
const narrativeName = scene.id ? sceneToNarrative.get(scene.id) : undefined;
|
|
992
|
-
const
|
|
1053
|
+
const dirName = narrativeName
|
|
993
1054
|
? `${toKebabCase(narrativeName)}_${toKebabCase(scene.name)}`
|
|
994
1055
|
: toKebabCase(scene.name);
|
|
1056
|
+
sceneNameToDirName.set(scene.name, dirName);
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
for (const scene of scenes) {
|
|
1060
|
+
debugScene('Processing scene: %s', scene.name);
|
|
1061
|
+
const sceneDirName = sceneNameToDirName.get(scene.name)!;
|
|
995
1062
|
const sceneDir = ensureDirPath(baseDir, sceneDirName);
|
|
996
1063
|
debugScene(' Scene directory: %s', sceneDir);
|
|
997
1064
|
debugScene(' Number of moments: %d', scene.moments.length);
|
|
@@ -1014,6 +1081,7 @@ export async function generateScaffoldFilePlans(
|
|
|
1014
1081
|
unionToEnumName,
|
|
1015
1082
|
integrations,
|
|
1016
1083
|
registeredCommands,
|
|
1084
|
+
sceneNameToDirName,
|
|
1017
1085
|
);
|
|
1018
1086
|
debugScene(' Generated %d plans for moment', plans.length);
|
|
1019
1087
|
allPlans.push(...plans);
|