@izara_project/izara-core-generate-service-code 1.0.97 → 1.0.98

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 (37) hide show
  1. package/package.json +1 -1
  2. package/src/compile/loadSchemas.js +29 -6
  3. package/src/emit/application/crud/templates/FlowEndpoint_HdrWbs.ejs +2 -2
  4. package/src/emit/application/crud/templates/FlowMain_Wbs.ejs +2 -2
  5. package/src/emit/application/custom/templates/s3/CreateObject_Main.ejs +5 -2
  6. package/src/emit/application/custom/templates/triggerCache/CheckTriggerCache_Main.ejs +2 -2
  7. package/src/emit/application/relationship/templates/ChangeRelationship_Main.ejs +3 -2
  8. package/src/emit/application/relationship/templates/CreateRelationship_Main.ejs +3 -2
  9. package/src/emit/application/relationship/templates/DeleteRelationship_Main.ejs +3 -2
  10. package/src/emit/application/relationship/templates/GetRelationship_Main.ejs +3 -2
  11. package/src/emit/application/relationship/templates/MoveRelationship_Main.ejs +3 -2
  12. package/src/emit/application/relationship/templates/UpdateRelationship_Main.ejs +3 -2
  13. package/src/emit/application/system/templates/RegisterComplete_Main.ejs +6 -2
  14. package/src/emit/application/system/templates/Register_Main.ejs +6 -2
  15. package/src/emit/application/system/templates/WebSocketComplete_Main.ejs +3 -3
  16. package/src/emit/infrastructure/resources/iamResourceArtifacts.js +1 -1
  17. package/src/emit/schemas/attributeTree/emitAttributeTreeSchemas.js +14 -22
  18. package/src/emit/schemas/attributeTree/emitAttributeTreeTargetValueStandard.js +132 -0
  19. package/src/emit/schemas/ledger/emitLedgerSchemas.js +33 -11
  20. package/src/emit/schemas/ledger/templates/LedgerObjectSchema.ejs +3 -0
  21. package/src/emit/schemas/ledger/templates/LedgerRelationshipSchema.ejs +1 -0
  22. package/src/emit/schemas/propertyValue/emitPropertyValueSchemas.js +329 -0
  23. package/src/generate.js +16 -75
  24. package/src/output/mergeHooks.js +69 -15
  25. package/src/plan/domainRegistry.js +41 -5
  26. package/task.md +132 -0
  27. package/test/integration/publicApi.test.js +54 -1
  28. package/test/unit/application/customFlowApplicationArtifacts.test.js +9 -0
  29. package/test/unit/application/relationshipApplicationArtifacts.test.js +30 -0
  30. package/test/unit/infrastructure/resourceArtifacts.test.js +1 -0
  31. package/test/unit/output/mergeHooks.test.js +268 -0
  32. package/test/unit/schemas/attributeTreeDirectives.test.js +177 -0
  33. package/test/unit/schemas/attributeTreeSchemas.test.js +10 -61
  34. package/test/unit/schemas/ledgerSchemas.test.js +33 -7
  35. package/test/unit/schemas/propertyValueSchemas.test.js +265 -0
  36. package/src/emit/schemas/attributeTree/emitAttributeTreeTargetSchemas.js +0 -153
  37. package/src/emit/schemas/attributeTree/resolveTargetServicePath.js +0 -111
@@ -52,12 +52,22 @@ import {
52
52
  attributeTreeObjectSchemaManagedPaths,
53
53
  attributeTreeRelationshipSchemaManagedPaths
54
54
  } from '../emit/schemas/attributeTree/emitAttributeTreeSchemas.js';
55
+ import {
56
+ emitAttributeTreeTargetValueStandard,
57
+ attributeTreeTargetValueStandardManagedPaths
58
+ } from '../emit/schemas/attributeTree/emitAttributeTreeTargetValueStandard.js';
55
59
  import {
56
60
  emitLedgerObjectSchemas,
57
61
  emitLedgerRelationshipSchemas,
58
62
  ledgerObjectSchemaManagedPaths,
59
63
  ledgerRelationshipSchemaManagedPaths
60
64
  } from '../emit/schemas/ledger/emitLedgerSchemas.js';
65
+ import {
66
+ emitPropertyValueObjectSchemas,
67
+ emitPropertyValueRelationshipSchemas,
68
+ propertyValueObjectSchemaManagedPaths,
69
+ propertyValueRelationshipSchemaManagedPaths
70
+ } from '../emit/schemas/propertyValue/emitPropertyValueSchemas.js';
61
71
  import {
62
72
  emitErrorSchemas,
63
73
  errorSchemaManagedPaths
@@ -71,6 +81,7 @@ export async function resolveEmissionGroups(context) {
71
81
  flows,
72
82
  notifications,
73
83
  errors,
84
+ directives = [],
74
85
  objectGraphServiceTags,
75
86
  relationshipGraphServiceTags,
76
87
  logger
@@ -85,7 +96,9 @@ export async function resolveEmissionGroups(context) {
85
96
  resource,
86
97
  coreFunction,
87
98
  systemText,
88
- errorSchemas
99
+ errorSchemas,
100
+ ledgerObjectArtifacts,
101
+ ledgerRelationshipArtifacts
89
102
  ] = await Promise.all([
90
103
  emitCrudApplicationArtifacts({
91
104
  objects,
@@ -125,6 +138,14 @@ export async function resolveEmissionGroups(context) {
125
138
  project,
126
139
  errors,
127
140
  objectGraphServiceTags
141
+ }),
142
+ emitLedgerObjectSchemas({
143
+ objects,
144
+ serviceTag: project.serviceTag
145
+ }),
146
+ emitLedgerRelationshipSchemas({
147
+ objects,
148
+ serviceTag: project.serviceTag
128
149
  })
129
150
  ]);
130
151
 
@@ -185,6 +206,11 @@ export async function resolveEmissionGroups(context) {
185
206
  managedPaths: rbacFlowSchemaManagedPaths(objects),
186
207
  artifacts: rbacFlowArtifacts
187
208
  },
209
+ {
210
+ scope: 'attribute-tree-target-value-standard',
211
+ managedPaths: attributeTreeTargetValueStandardManagedPaths(directives),
212
+ artifacts: emitAttributeTreeTargetValueStandard({ directives })
213
+ },
188
214
  {
189
215
  scope: 'attribute-tree-object-schemas',
190
216
  managedPaths: attributeTreeObjectSchemaManagedPaths(objects),
@@ -204,15 +230,25 @@ export async function resolveEmissionGroups(context) {
204
230
  {
205
231
  scope: 'ledger-object-schemas',
206
232
  managedPaths: ledgerObjectSchemaManagedPaths(objects),
207
- artifacts: emitLedgerObjectSchemas({
233
+ artifacts: ledgerObjectArtifacts
234
+ },
235
+ {
236
+ scope: 'ledger-relationship-schemas',
237
+ managedPaths: ledgerRelationshipSchemaManagedPaths(objects),
238
+ artifacts: ledgerRelationshipArtifacts
239
+ },
240
+ {
241
+ scope: 'property-value-object-schemas',
242
+ managedPaths: propertyValueObjectSchemaManagedPaths(objects),
243
+ artifacts: emitPropertyValueObjectSchemas({
208
244
  objects,
209
245
  serviceTag: project.serviceTag
210
246
  })
211
247
  },
212
248
  {
213
- scope: 'ledger-relationship-schemas',
214
- managedPaths: ledgerRelationshipSchemaManagedPaths(objects),
215
- artifacts: emitLedgerRelationshipSchemas({
249
+ scope: 'property-value-relationship-schemas',
250
+ managedPaths: propertyValueRelationshipSchemaManagedPaths(objects),
251
+ artifacts: emitPropertyValueRelationshipSchemas({
216
252
  objects,
217
253
  serviceTag: project.serviceTag
218
254
  })
package/task.md ADDED
@@ -0,0 +1,132 @@
1
+ # Task Checklist: Attribute Tree Per-Repo Code Generation Refactoring
2
+
3
+ > **Canonical Architecture Plan**: [[10.Systems/GenerateServiceCode/30-Generation/Task Roadmap - Attribute Tree Per-Repo Generation Refactoring|Task Roadmap in Obsidian]]
4
+ > **Selected Design**: **Option C (`GenerateDirectives/`)** + **Style 1 (`generatedCode/<domain>/`)**
5
+ > **Repository**: `IzaraCore-SupportServices-GenerateServiceCode-Seagame`
6
+
7
+ ---
8
+
9
+ ## 🎯 Target Architecture: Style 1 (`generatedCode/<domain>/`)
10
+
11
+ ### Target Service (e.g. `SellOfferTermValueStandard`)
12
+ ```text
13
+ app/src/schemas/
14
+ ├── GenerateDirectives/
15
+ │ └── sellOfferTermValueStandard.js <-- [Directive] Developer creates this
16
+ │
17
+ ├── ObjectSchemas/
18
+ │ └── generatedCode/
19
+ │ ├── attributeTree/
20
+ │ │ └── sellOfferTermValueStandard.js <-- [Emitted by TASK-02]
21
+ │ └── propertyValue/
22
+ │ ├── sellOfferTermValueStandardPropertyLabel.js <-- [Emitted by PropertyValue addOn]
23
+ │ └── sellOfferTermValueStandardPropertyValue.js <-- [Emitted by PropertyValue addOn]
24
+ │
25
+ └── RelationshipSchemas/
26
+ └── generatedCode/
27
+ └── propertyValue/
28
+ ├── hasSellOfferTermValueStandardPropertyValue.js
29
+ ├── disabledSellOfferTermValueStandardPropertyValue.js
30
+ └── isSellOfferTermValueStandardPropertyLabel.js
31
+ ```
32
+
33
+ ### Source Service (Self-Repo)
34
+ ```text
35
+ app/src/schemas/
36
+ ├── ObjectSchemas/
37
+ │ ├── sellOffer.js <-- Source ObjectSchema
38
+ │ └── generatedCode/
39
+ │ └── attributeTree/
40
+ │ ├── sellOfferTermLink.js
41
+ │ ├── sellOfferTermValue.js
42
+ │ └── sellOfferTermLabel.js
43
+ │
44
+ └── RelationshipSchemas/
45
+ └── generatedCode/
46
+ └── attributeTree/
47
+ ├── hasSellOfferTermLink.js
48
+ ├── disabledSellOfferTermLink.js
49
+ ├── isSellOfferTerm.js
50
+ └── isSellOfferTermLabel.js
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 📋 Implementation Checklist
56
+
57
+ - [x] **TASK-01: Recursive Schema Loading & Directives Loader**
58
+ - **File**: `src/compile/loadSchemas.js`
59
+ - **Action**:
60
+ 1. Update `loadLocalSchemaDirectory(project, directoryName, options)` to recursively find all `.js` files within subdirectories (supporting `ObjectSchemas/generatedCode/**` and `RelationshipSchemas/generatedCode/**`).
61
+ 2. Export `loadGenerateDirectives(project, options)` calling `loadLocalSchemaDirectory(project, 'GenerateDirectives', options)`.
62
+ - **Acceptance Criteria**: `loadLocalSchemaDirectory(project, 'ObjectSchemas')` discovers and validates schemas nested inside `generatedCode/attributeTree/` and `generatedCode/propertyValue/`. Returns `[]` when `GenerateDirectives` folder is missing.
63
+
64
+ - [x] **TASK-02: Target ValueStandard Schema Emitter**
65
+ - **File**: `src/emit/schemas/attributeTree/emitAttributeTreeTargetValueStandard.js` (NEW)
66
+ - **Action**:
67
+ 1. Filter `directives` for `directive.type === 'attributeTreeTarget'`.
68
+ 2. Emit `{tag}ValueStandard.js` to `app/src/schemas/ObjectSchemas/generatedCode/attributeTree/${tag}ValueStandard.js`.
69
+ 3. Include preservation hook `//(<addOnDataStructure${tag}ValueStandard>)` with commented `propertyValue` addOn template.
70
+ 4. Export `attributeTreeTargetValueStandardManagedPaths(directives)`.
71
+ - **Acceptance Criteria**: Emitted file adheres strictly to Izara backend syntax rules and matches Style 1 output path.
72
+
73
+ - [x] **TASK-03: Align AttributeTree Self-Repo Emitter to Style 1**
74
+ - **File**: `src/emit/schemas/attributeTree/emitAttributeTreeSchemas.js`
75
+ - **Action**:
76
+ 1. Update ObjectSchema emission paths from `generatedAttributeTree/` to `generatedCode/attributeTree/`.
77
+ 2. Update RelationshipSchema emission paths from `generatedAttributeTree/` to `generatedCode/attributeTree/`.
78
+ 3. Update `attributeTreeObjectSchemaManagedPaths` and `attributeTreeRelationshipSchemaManagedPaths`.
79
+ - **Acceptance Criteria**: Self-repo attributeTree schemas output cleanly into `generatedCode/attributeTree/`.
80
+
81
+ - [x] **TASK-04: Align PropertyValue Emitter to Style 1**
82
+ - **File**: `src/emit/schemas/propertyValue/emitPropertyValueSchemas.js`
83
+ - **Action**:
84
+ 1. Update ObjectSchema emission paths from `generatedPropertyValue/` to `generatedCode/propertyValue/`.
85
+ 2. Update RelationshipSchema emission paths from `generatedPropertyValue/` to `generatedCode/propertyValue/`.
86
+ 3. Update `propertyValueObjectSchemaManagedPaths` and `propertyValueRelationshipSchemaManagedPaths`.
87
+ - **Acceptance Criteria**: PropertyValue schemas output cleanly into `generatedCode/propertyValue/`.
88
+
89
+ - [x] **TASK-05: Register Managed Scopes in Domain Registry**
90
+ - **File**: `src/plan/domainRegistry.js`
91
+ - **Action**:
92
+ 1. Import `emitAttributeTreeTargetValueStandard` and `attributeTreeTargetValueStandardManagedPaths`.
93
+ 2. Register new scope `attribute-tree-target-value-standard`.
94
+ 3. Pass `directives` into `resolveEmissionGroups(context)`.
95
+ - **Acceptance Criteria**: All 5 dynamic schema scopes registered and tracked in `.generate-service-code-v2-managed.json`.
96
+
97
+ - [x] **TASK-06: Decouple Generation Pipeline in `src/generate.js`**
98
+ - **File**: `src/generate.js`
99
+ - **Action**:
100
+ 1. Load directives: `const directives = await loadGenerateDirectives(project, schemaLoadOptions);`.
101
+ 2. Pass `directives` to `resolveEmissionGroups`.
102
+ 3. Delete lines 104-111 (`targetFiles = await _emitTargetServiceAttributeTreeSchemas(...)`).
103
+ 4. Delete lines 129-186 (`_emitTargetServiceAttributeTreeSchemas` function definition).
104
+ - **Acceptance Criteria**: Zero cross-repo filesystem writes. Generator operates strictly within `workspaceOutputRoot`.
105
+
106
+ - [x] **TASK-07: Delete Legacy Cross-Repo Files**
107
+ - **Files to Delete**:
108
+ - `src/emit/schemas/attributeTree/resolveTargetServicePath.js`
109
+ - `src/emit/schemas/attributeTree/emitAttributeTreeTargetSchemas.js`
110
+ - **Action**: Remove dead files and any unused imports.
111
+ - **Acceptance Criteria**: `git status` shows clean file deletions.
112
+
113
+ - [x] **TASK-08: Unit & Integration Test Verification**
114
+ - **Files**:
115
+ - `test/unit/schemas/attributeTreeDirectives.test.js` (NEW)
116
+ - `test/unit/schemas/attributeTreeSchemas.test.js` (Update paths)
117
+ - `test/unit/schemas/propertyValueSchemas.test.js` (Update paths)
118
+ - **Verification Command**: `npm test`
119
+ - **Acceptance Criteria**: All 198+ tests pass with zero regression.
120
+
121
+ - [x] **TASK-09: End-to-End Cascading Verification**
122
+ - **Action**: Verify two-phase generation locally:
123
+ 1. Target service with directive -> generates `sellOfferTermValueStandard.js`.
124
+ 2. Target service uncomments `propertyValue` -> generates PropertyLabel, PropertyValue, and 3 Relationship schemas.
125
+ - **Acceptance Criteria**: Zero cross-repo dependency required.
126
+
127
+ - [x] **TASK-10: Synchronize Vault & Taiga Documentation**
128
+ - **Vault Files**:
129
+ - `10.Systems/GenerateServiceCode/20-Schema/Dynamic Schema.md`
130
+ - `40.Vault/Operations/timeline-log.md`
131
+ - `40.Vault/Memory/active-context.md`
132
+ - **Taiga Wiki**: Update `generatecode` wiki page.
@@ -208,7 +208,7 @@ test('generate commits the system WebSocket completion path for own-topic flows'
208
208
  assert.match(webSocketComplete, /\(\<createIdentifiersFlow\>\)/);
209
209
  assert.match(webSocketComplete, /\(\<beforePostMessage\>\)/);
210
210
  assert.match(webSocketComplete, /postToConnection\(_izContext,/);
211
- const webSocketHandler = await readFile(
211
+ const webSocketHandler = await readFile(
212
212
  join(
213
213
  rootPath,
214
214
  'app/src/generatedCode/SystemFlowSchemas/WebSocketMain/source/WebSocketComplete_HdrSqs.js'
@@ -217,6 +217,49 @@ test('generate commits the system WebSocket completion path for own-topic flows'
217
217
  );
218
218
  assert.match(webSocketHandler, /record\.body\.TopicArn/);
219
219
  assert.match(webSocketHandler, /addCallingFlowToPassOnProperties/);
220
+
221
+ const registerComplete = await readFile(
222
+ join(
223
+ rootPath,
224
+ 'app/src/generatedCode/SystemFlowSchemas/Register/source/RegisterComplete_Main.js'
225
+ ),
226
+ 'utf8'
227
+ );
228
+ assert.match(registerComplete, /getFlowSchema\.getFlowSchemaS3WithCache/);
229
+ assert.doesNotMatch(registerComplete, /getObjectSchema\.getFlowSchemaS3WithCache/);
230
+
231
+ const registerMain = await readFile(
232
+ join(
233
+ rootPath,
234
+ 'app/src/generatedCode/SystemFlowSchemas/Register/source/Register_Main.js'
235
+ ),
236
+ 'utf8'
237
+ );
238
+ assert.match(registerMain, /getFlowSchema\.getFlowSchemaS3WithCache/);
239
+ assert.doesNotMatch(registerMain, /getObjectSchema\.getFlowSchemaS3WithCache/);
240
+
241
+ assert.match(webSocketComplete, /getFlowSchema\.getFlowSchemaS3WithCache/);
242
+ assert.doesNotMatch(webSocketComplete, /getObjectSchema\.getFlowSchemaS3WithCache/);
243
+
244
+ const trackHdrWbs = await readFile(
245
+ join(
246
+ rootPath,
247
+ 'app/src/generatedCode/FlowSchemas/Track/source/Track_HdrWbs.js'
248
+ ),
249
+ 'utf8'
250
+ );
251
+ assert.match(trackHdrWbs, /getFlowSchema\.getFlowSchemaS3WithCache/);
252
+ assert.doesNotMatch(trackHdrWbs, /getObjectSchema\.getFlowSchemaS3WithCache/);
253
+
254
+ const trackWbsMain = await readFile(
255
+ join(
256
+ rootPath,
257
+ 'app/src/generatedCode/FlowSchemas/Track/source/Track_WbsMain.js'
258
+ ),
259
+ 'utf8'
260
+ );
261
+ assert.match(trackWbsMain, /getFlowSchema\.getFlowSchemaS3WithCache/);
262
+ assert.doesNotMatch(trackWbsMain, /getObjectSchema\.getFlowSchemaS3WithCache/);
220
263
  });
221
264
 
222
265
  test('generate commits the trigger-cache suite with its queues when triggerFlowTypes is set', async () => {
@@ -245,6 +288,16 @@ test('generate commits the trigger-cache suite with its queues when triggerFlowT
245
288
  ),
246
289
  /CartCheckTriggerFlowsHdrSqs/
247
290
  );
291
+
292
+ const cartCheckTrigger = await readFile(
293
+ join(
294
+ rootPath,
295
+ 'app/src/generatedCode/FlowSchemas/Cart/source/CartCheckTriggerFlows_Main.js'
296
+ ),
297
+ 'utf8'
298
+ );
299
+ assert.match(cartCheckTrigger, /getFlowSchema\.getFlowSchemaS3WithCache/);
300
+ assert.doesNotMatch(cartCheckTrigger, /getObjectSchema\.getFlowSchemaS3WithCache/);
248
301
  });
249
302
 
250
303
  test('generate removes the system WebSocket path when the last own-topic flow is removed', async () => {
@@ -232,6 +232,15 @@ test('emits handlers, function YAML, and IAM roles for supported custom flow eve
232
232
  join(rootPath, `app/src/generatedCode/FlowSchemas/Upload/source/${path}`)
233
233
  );
234
234
  }
235
+ const uploadCreateObject = await readFile(
236
+ join(
237
+ rootPath,
238
+ 'app/src/generatedCode/FlowSchemas/Upload/source/UploadCreateObject_Main.js'
239
+ ),
240
+ 'utf8'
241
+ );
242
+ assert.match(uploadCreateObject, /getFlowSchema\.getFlowSchemaS3WithCache/);
243
+ assert.doesNotMatch(uploadCreateObject, /getObjectSchema\.getFlowSchemaS3WithCache/);
235
244
  for (const path of [
236
245
  'UploadReservedLimit_Main.js',
237
246
  'UploadCreatePreSignUrl_Main.js',
@@ -154,6 +154,36 @@ test('emits valid hook tags without trailing periods and with correct callingFlo
154
154
  assert.doesNotMatch(changeMain, /\/\/\(<requestParamCreateRel>\)/);
155
155
  });
156
156
 
157
+ test('imports getRelSchema and uses getRelationshipSchemaWithCache from getRelSchema in all relationship Main files', async () => {
158
+ const rootPath = await createProject(true);
159
+ await generateRelationshipApplicationArtifacts(rootPath, {
160
+ serviceTag: 'orders'
161
+ });
162
+
163
+ const actions = ['Create', 'Update', 'Delete', 'Get', 'Change', 'Move'];
164
+ for (const action of actions) {
165
+ const mainContent = await readFile(
166
+ join(
167
+ rootPath,
168
+ `app/src/generatedCode/FlowRelationships/${action}Relationship/source/${action}Relationship_Main.js`
169
+ ),
170
+ 'utf8'
171
+ );
172
+ assert.match(
173
+ mainContent,
174
+ /import\s*\{[^}]*getRelSchema[^}]*\}\s*from\s*'@izara_project\/izara-core-library-service-schemas'/
175
+ );
176
+ assert.match(
177
+ mainContent,
178
+ /const\s*\{[^}]*getRelationshipSchemaWithCache[^}]*\}\s*=\s*getRelSchema/
179
+ );
180
+ assert.doesNotMatch(
181
+ mainContent,
182
+ /const\s*\{[^}]*getRelationshipSchemaWithCache[^}]*\}\s*=\s*getObjectSchema/
183
+ );
184
+ }
185
+ });
186
+
157
187
  test('removes managed relationship artifacts after relationships are removed', async () => {
158
188
  const rootPath = await createProject(true);
159
189
  await generateRelationshipApplicationArtifacts(rootPath, {
@@ -167,6 +167,7 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
167
167
  assert.match(procFindDataRole, /dynamodb:Query/);
168
168
  assert.match(procFindDataRole, /sns:Publish/);
169
169
  assert.match(procFindDataRole, /FindData_Out/);
170
+ assert.match(procFindDataRole, /ProcessLogical_Out/);
170
171
  assert.doesNotMatch(
171
172
  iam,
172
173
  /s3:GetObjectVersion|sqs:DeleteMessageBatch|sqs:GetQueueUrl/
@@ -0,0 +1,268 @@
1
+ import assert from 'node:assert/strict';
2
+ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import test from 'node:test';
6
+
7
+ import { mergeHooks } from '../../../src/output/mergeHooks.js';
8
+
9
+ async function writeTestFile(tmpDir, relativePath, content) {
10
+ const fullPath = path.join(tmpDir, relativePath);
11
+ await mkdir(path.dirname(fullPath), { recursive: true });
12
+ await writeFile(fullPath, content, 'utf8');
13
+ }
14
+
15
+ test('merges single hook in JS file when source content has custom code', async () => {
16
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
17
+ try {
18
+ const filePath = 'app/src/sample.js';
19
+ await writeTestFile(
20
+ tmpDir,
21
+ filePath,
22
+ '//(<customHook>)\nconst custom = true;\n//(</customHook>)\n'
23
+ );
24
+
25
+ const artifacts = [
26
+ {
27
+ path: filePath,
28
+ content: '//(<customHook>)\n//(</customHook>)\n'
29
+ }
30
+ ];
31
+
32
+ const result = await mergeHooks(tmpDir, artifacts);
33
+ assert.equal(
34
+ result[0].content,
35
+ '//(<customHook>)\nconst custom = true;\n//(</customHook>)\n'
36
+ );
37
+ } finally {
38
+ await rm(tmpDir, { recursive: true, force: true });
39
+ }
40
+ });
41
+
42
+ test('merges multiple occurrences of the same hook tag without losing occurrences', async () => {
43
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
44
+ try {
45
+ const filePath = 'app/src/Flow_Main.js';
46
+ const diskContent = [
47
+ 'function test() {',
48
+ ' //(<identifiers>)',
49
+ ' firstIdentifier: true',
50
+ ' //(</identifiers>)',
51
+ '',
52
+ ' //(<identifiers>)',
53
+ ' secondIdentifier: "customValue"',
54
+ ' //(</identifiers>)',
55
+ '}'
56
+ ].join('\n');
57
+ await writeTestFile(tmpDir, filePath, diskContent);
58
+
59
+ const templateContent = [
60
+ 'function test() {',
61
+ ' //(<identifiers>)',
62
+ ' //(</identifiers>)',
63
+ '',
64
+ ' //(<identifiers>)',
65
+ ' //(</identifiers>)',
66
+ '}'
67
+ ].join('\n');
68
+
69
+ const artifacts = [
70
+ {
71
+ path: filePath,
72
+ content: templateContent
73
+ }
74
+ ];
75
+
76
+ const result = await mergeHooks(tmpDir, artifacts);
77
+ assert.equal(result[0].content, diskContent);
78
+ } finally {
79
+ await rm(tmpDir, { recursive: true, force: true });
80
+ }
81
+ });
82
+
83
+ test('preserves second hook when the first occurrence of a duplicate hook is empty on disk', async () => {
84
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
85
+ try {
86
+ const filePath = 'app/src/Flow_Main.js';
87
+ const diskContent = [
88
+ 'function test() {',
89
+ ' //(<identifiers>)',
90
+ ' //(</identifiers>)',
91
+ '',
92
+ ' //(<identifiers>)',
93
+ ' orderId: requestParams.orderId',
94
+ ' //(</identifiers>)',
95
+ '}'
96
+ ].join('\n');
97
+ await writeTestFile(tmpDir, filePath, diskContent);
98
+
99
+ const templateContent = [
100
+ 'function test() {',
101
+ ' //(<identifiers>)',
102
+ ' //(</identifiers>)',
103
+ '',
104
+ ' //(<identifiers>)',
105
+ ' //(</identifiers>)',
106
+ '}'
107
+ ].join('\n');
108
+
109
+ const artifacts = [
110
+ {
111
+ path: filePath,
112
+ content: templateContent
113
+ }
114
+ ];
115
+
116
+ const result = await mergeHooks(tmpDir, artifacts);
117
+ assert.equal(result[0].content, diskContent);
118
+ } finally {
119
+ await rm(tmpDir, { recursive: true, force: true });
120
+ }
121
+ });
122
+
123
+ test('merges hooks with relaxed whitespace around comment slashes and tags', async () => {
124
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
125
+ try {
126
+ const filePath = 'app/src/sample.js';
127
+ await writeTestFile(
128
+ tmpDir,
129
+ filePath,
130
+ '// (<spacedHook>)\nconst spaced = 1;\n// (</spacedHook>)\n'
131
+ );
132
+
133
+ const artifacts = [
134
+ {
135
+ path: filePath,
136
+ content: '//(<spacedHook>)\n//(</spacedHook>)\n'
137
+ }
138
+ ];
139
+
140
+ const result = await mergeHooks(tmpDir, artifacts);
141
+ assert.equal(
142
+ result[0].content,
143
+ '// (<spacedHook>)\nconst spaced = 1;\n// (</spacedHook>)\n'
144
+ );
145
+ } finally {
146
+ await rm(tmpDir, { recursive: true, force: true });
147
+ }
148
+ });
149
+
150
+ test('merges hooks with hyphens and dots in tag names', async () => {
151
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
152
+ try {
153
+ const filePath = 'app/src/sample.js';
154
+ await writeTestFile(
155
+ tmpDir,
156
+ filePath,
157
+ '//(<custom-hook.name>)\nconst customName = "valid";\n//(</custom-hook.name>)\n'
158
+ );
159
+
160
+ const artifacts = [
161
+ {
162
+ path: filePath,
163
+ content: '//(<custom-hook.name>)\n//(</custom-hook.name>)\n'
164
+ }
165
+ ];
166
+
167
+ const result = await mergeHooks(tmpDir, artifacts);
168
+ assert.equal(
169
+ result[0].content,
170
+ '//(<custom-hook.name>)\nconst customName = "valid";\n//(</custom-hook.name>)\n'
171
+ );
172
+ } finally {
173
+ await rm(tmpDir, { recursive: true, force: true });
174
+ }
175
+ });
176
+
177
+ test('merges hooks in YAML files with relaxed whitespace and hyphens', async () => {
178
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
179
+ try {
180
+ const filePath = 'app/sls_yaml/test.yml';
181
+ const diskContent = [
182
+ '# <#custom-filter-patterns#>',
183
+ 'filterPatterns:',
184
+ ' - body: custom',
185
+ '# <#/custom-filter-patterns#>'
186
+ ].join('\n');
187
+ await writeTestFile(tmpDir, filePath, diskContent);
188
+
189
+ const templateContent = [
190
+ '#<#custom-filter-patterns#>',
191
+ '#<#/custom-filter-patterns#>'
192
+ ].join('\n');
193
+
194
+ const artifacts = [
195
+ {
196
+ path: filePath,
197
+ content: templateContent
198
+ }
199
+ ];
200
+
201
+ const result = await mergeHooks(tmpDir, artifacts);
202
+ assert.equal(result[0].content, diskContent);
203
+ } finally {
204
+ await rm(tmpDir, { recursive: true, force: true });
205
+ }
206
+ });
207
+
208
+ test('merges hooks in .mjs and .cjs files', async () => {
209
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
210
+ try {
211
+ const mjsPath = 'app/src/sample.mjs';
212
+ const cjsPath = 'app/src/sample.cjs';
213
+ await writeTestFile(
214
+ tmpDir,
215
+ mjsPath,
216
+ '//(<mjsHook>)\nexport default 1;\n//(</mjsHook>)\n'
217
+ );
218
+ await writeTestFile(
219
+ tmpDir,
220
+ cjsPath,
221
+ '//(<cjsHook>)\nmodule.exports = 2;\n//(</cjsHook>)\n'
222
+ );
223
+
224
+ const artifacts = [
225
+ {
226
+ path: mjsPath,
227
+ content: '//(<mjsHook>)\n//(</mjsHook>)\n'
228
+ },
229
+ {
230
+ path: cjsPath,
231
+ content: '//(<cjsHook>)\n//(</cjsHook>)\n'
232
+ }
233
+ ];
234
+
235
+ const result = await mergeHooks(tmpDir, artifacts);
236
+ assert.equal(
237
+ result[0].content,
238
+ '//(<mjsHook>)\nexport default 1;\n//(</mjsHook>)\n'
239
+ );
240
+ assert.equal(
241
+ result[1].content,
242
+ '//(<cjsHook>)\nmodule.exports = 2;\n//(</cjsHook>)\n'
243
+ );
244
+ } finally {
245
+ await rm(tmpDir, { recursive: true, force: true });
246
+ }
247
+ });
248
+
249
+ test('respects writeWhenFileExist false flag by returning disk content unchanged', async () => {
250
+ const tmpDir = await mkdtemp(path.join(os.tmpdir(), 'mergeHooks-test-'));
251
+ try {
252
+ const filePath = 'app/src/immutable.js';
253
+ await writeTestFile(tmpDir, filePath, '// existing disk content\n');
254
+
255
+ const artifacts = [
256
+ {
257
+ path: filePath,
258
+ content: '// new template content\n',
259
+ writeWhenFileExist: false
260
+ }
261
+ ];
262
+
263
+ const result = await mergeHooks(tmpDir, artifacts);
264
+ assert.equal(result[0].content, '// existing disk content\n');
265
+ } finally {
266
+ await rm(tmpDir, { recursive: true, force: true });
267
+ }
268
+ });