@izara_project/izara-core-generate-service-code 1.0.91 → 1.0.93
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/package.json +7 -10
- package/src/compile/flowValidator.js +6 -0
- package/src/compile/loadSchemas.js +195 -69
- package/src/emit/coreFunction/templates/UploadSchema_Main.ejs +2 -2
- package/src/emit/crud/generateCrudApplicationArtifacts.js +15 -17
- package/src/emit/crud/generateDynamicCrudFlowSchemas.js +16 -21
- package/src/emit/crud/loadLocalSchemaArrays.js +4 -8
- package/src/emit/custom/customFlowPaths.js +149 -0
- package/src/emit/custom/customFlowS3.js +156 -0
- package/src/emit/custom/customFlowSteps.js +385 -0
- package/src/emit/custom/customFlowTemplates.js +28 -0
- package/src/emit/custom/customFlows.js +48 -19
- package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +57 -692
- package/src/emit/custom/generateCustomFlowApplicationArtifacts.js +27 -25
- package/src/emit/error/emitErrorSchemas.js +73 -0
- package/src/emit/error/templates/errorSchema.ejs +8 -0
- package/src/emit/rbac/generateCreateTargetRoleHandler.js +11 -13
- package/src/emit/rbac/generateDynamicRbacFlowSchemas.js +14 -18
- package/src/emit/relationship/generateDynamicRelationshipFlowSchemas.js +16 -19
- package/src/emit/relationship/generateRelationshipApplicationArtifacts.js +19 -19
- package/src/emit/resource/dynamoResourceArtifacts.js +18 -0
- package/src/emit/resource/emitResourceArtifacts.js +13 -1247
- package/src/emit/resource/generateResourceArtifacts.js +20 -20
- package/src/emit/resource/iamResourceArtifacts.js +612 -0
- package/src/emit/resource/resourceState.js +564 -0
- package/src/emit/resource/resourceTemplate.js +30 -0
- package/src/emit/resource/s3ResourceArtifacts.js +14 -0
- package/src/emit/resource/sqsResourceArtifacts.js +113 -0
- package/src/emit/shared/runSubsystemGeneration.js +55 -0
- package/src/emit/system/emitSystemWebSocketArtifacts.js +2 -1
- package/src/generate.js +37 -4
- package/test/crudApplicationArtifacts.test.js +72 -0
- package/test/customFlowApplicationArtifacts.test.js +98 -0
- package/test/errorSchemas.test.js +154 -0
- package/test/publicApi.test.js +1 -1
- package/test/resourceArtifacts.test.js +99 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { indentYaml, renderResourceTemplate } from './resourceTemplate.js';
|
|
2
|
+
|
|
3
|
+
export async function renderResourceGroup({
|
|
4
|
+
ownTopics = [],
|
|
5
|
+
sqsQueues = [],
|
|
6
|
+
dsqQueues = [],
|
|
7
|
+
webSocketSubscriptions = [],
|
|
8
|
+
s3Subscriptions = []
|
|
9
|
+
}) {
|
|
10
|
+
const hasResources =
|
|
11
|
+
ownTopics.length > 0 ||
|
|
12
|
+
sqsQueues.length > 0 ||
|
|
13
|
+
dsqQueues.length > 0 ||
|
|
14
|
+
webSocketSubscriptions.length > 0 ||
|
|
15
|
+
s3Subscriptions.length > 0;
|
|
16
|
+
if (!hasResources) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const chunks = await Promise.all([
|
|
21
|
+
...ownTopics.map((queueName) =>
|
|
22
|
+
renderResourceTemplate('ResourceYaml.ejs', { queueName })
|
|
23
|
+
),
|
|
24
|
+
...sqsQueues.map((queueName) =>
|
|
25
|
+
renderResourceTemplate('ResourceSqsYaml.ejs', { queueName })
|
|
26
|
+
),
|
|
27
|
+
...dsqQueues.map((queueName) =>
|
|
28
|
+
renderResourceTemplate('ResourceSqsYaml.ejs', { queueName })
|
|
29
|
+
)
|
|
30
|
+
]);
|
|
31
|
+
const wsSubs = await Promise.all(
|
|
32
|
+
webSocketSubscriptions.map((topicName) =>
|
|
33
|
+
renderWebSocketSubscription(topicName)
|
|
34
|
+
)
|
|
35
|
+
);
|
|
36
|
+
const s3Subs = await Promise.all(
|
|
37
|
+
s3Subscriptions.map(({ subscriptionName, topicName, queueName }) =>
|
|
38
|
+
renderResourceTemplate('S3Subscription_Yaml.ejs', {
|
|
39
|
+
subscriptionName,
|
|
40
|
+
topicName,
|
|
41
|
+
queueName
|
|
42
|
+
})
|
|
43
|
+
)
|
|
44
|
+
);
|
|
45
|
+
const parts = [chunks.join('\n')];
|
|
46
|
+
if (wsSubs.length > 0) {
|
|
47
|
+
parts.push(wsSubs.join('\n'));
|
|
48
|
+
}
|
|
49
|
+
if (s3Subs.length > 0) {
|
|
50
|
+
parts.push(s3Subs.join('\n'));
|
|
51
|
+
}
|
|
52
|
+
const body = parts.filter(Boolean).join('\n');
|
|
53
|
+
if (!body.trim()) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
return `Resources:\n${indentYaml(body, 2)}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export async function renderCrudInSqs(state) {
|
|
60
|
+
return renderResourceGroup({
|
|
61
|
+
ownTopics: state.crudOwnTopics,
|
|
62
|
+
sqsQueues: state.crudSqsQueues
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function renderRelationshipInSqs(state) {
|
|
67
|
+
return renderResourceGroup({
|
|
68
|
+
ownTopics: state.relationshipOwnTopics,
|
|
69
|
+
sqsQueues: state.relationshipSqsQueues,
|
|
70
|
+
dsqQueues: state.relationshipDsqQueues
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function renderRbacInSqs(state) {
|
|
75
|
+
return renderResourceGroup({
|
|
76
|
+
ownTopics: state.rbacOwnTopics,
|
|
77
|
+
sqsQueues: state.rbacSqsQueues
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function renderCustomFlowInSqs(state) {
|
|
82
|
+
return renderResourceGroup({
|
|
83
|
+
ownTopics: state.customOwnTopics,
|
|
84
|
+
sqsQueues: state.customSqsQueues,
|
|
85
|
+
dsqQueues: state.customDsqQueues,
|
|
86
|
+
s3Subscriptions: state.s3Subscriptions
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function renderSystemInSqs(state) {
|
|
91
|
+
return renderResourceGroup({
|
|
92
|
+
ownTopics: state.systemOwnTopics,
|
|
93
|
+
sqsQueues: state.systemSqsQueues,
|
|
94
|
+
dsqQueues: state.systemDsqQueues,
|
|
95
|
+
webSocketSubscriptions: state.webSocketSubscriptions
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function renderOut(state) {
|
|
100
|
+
const renderedTopics = await Promise.all(
|
|
101
|
+
state.outTopics.map((topicName) =>
|
|
102
|
+
renderResourceTemplate('FlowOut_Yaml.ejs', { topicName })
|
|
103
|
+
)
|
|
104
|
+
);
|
|
105
|
+
const body = renderedTopics.join('\n');
|
|
106
|
+
return `Resources:\n${indentYaml(body, 2)}`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function renderWebSocketSubscription(topicName) {
|
|
110
|
+
return renderResourceTemplate('WebSocketSubscription_Yaml.ejs', {
|
|
111
|
+
topicName
|
|
112
|
+
});
|
|
113
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { compileProject } from '../../compile/compileProject.js';
|
|
2
|
+
import { buildGenerationPlan } from '../../plan/buildGenerationPlan.js';
|
|
3
|
+
import { commitArtifacts } from '../../output/commitArtifacts.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Common pipeline runner for emitting and committing subsystem artifacts.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} params
|
|
9
|
+
* @param {string} params.rootPath Root path of the service project.
|
|
10
|
+
* @param {Object} [params.options={}] Options passed to compile and load.
|
|
11
|
+
* @param {Function} [params.loadInputs] Function to load schemas or inputs.
|
|
12
|
+
* @param {Function} params.emitArtifacts Function to emit artifacts.
|
|
13
|
+
* @param {string} params.managedScope Scope identifier for generation plan.
|
|
14
|
+
* @param {Array<string>|Function} params.resolveManagedPaths Managed paths.
|
|
15
|
+
* @returns {Promise<{ project: Object, files: Array<string> }>}
|
|
16
|
+
*/
|
|
17
|
+
export async function runSubsystemGeneration({
|
|
18
|
+
rootPath,
|
|
19
|
+
options = {},
|
|
20
|
+
loadInputs,
|
|
21
|
+
emitArtifacts,
|
|
22
|
+
managedScope,
|
|
23
|
+
resolveManagedPaths
|
|
24
|
+
}) {
|
|
25
|
+
const project = compileProject({ ...options, rootPath });
|
|
26
|
+
|
|
27
|
+
let inputs = {};
|
|
28
|
+
if (typeof loadInputs === 'function') {
|
|
29
|
+
inputs = await loadInputs(project, options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const artifacts = await emitArtifacts({
|
|
33
|
+
project,
|
|
34
|
+
options,
|
|
35
|
+
...inputs
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
let managedPaths = resolveManagedPaths;
|
|
39
|
+
if (typeof resolveManagedPaths === 'function') {
|
|
40
|
+
managedPaths = await resolveManagedPaths({
|
|
41
|
+
project,
|
|
42
|
+
options,
|
|
43
|
+
artifacts,
|
|
44
|
+
...inputs
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const plan = buildGenerationPlan(project, artifacts, {
|
|
49
|
+
managedPaths,
|
|
50
|
+
managedScope
|
|
51
|
+
});
|
|
52
|
+
const files = await commitArtifacts(plan);
|
|
53
|
+
|
|
54
|
+
return { project, files };
|
|
55
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import ejsLib from 'ejs';
|
|
3
|
+
import { flowHasOwnTopic } from '../custom/customFlows.js';
|
|
3
4
|
|
|
4
5
|
const TEMPLATE_URL = new URL('./templates/', import.meta.url);
|
|
5
6
|
const SOURCE = 'app/src/generatedCode/SystemFlowSchemas/WebSocketMain/source';
|
|
@@ -126,7 +127,7 @@ export function hasSystemWebSocket(
|
|
|
126
127
|
Array.isArray(flow?.event) &&
|
|
127
128
|
(flow.event.includes('s3') ||
|
|
128
129
|
(includeRelationshipOwnTopic &&
|
|
129
|
-
flow
|
|
130
|
+
flowHasOwnTopic(flow) &&
|
|
130
131
|
(flow.initiateBy === 'user' || flow.initiateBy === 'systemWithUser')))
|
|
131
132
|
)
|
|
132
133
|
);
|
package/src/generate.js
CHANGED
|
@@ -61,6 +61,10 @@ import {
|
|
|
61
61
|
attributeTreeObjectSchemaManagedPaths,
|
|
62
62
|
attributeTreeRelationshipSchemaManagedPaths
|
|
63
63
|
} from './emit/attributeTree/emitAttributeTreeSchemas.js';
|
|
64
|
+
import {
|
|
65
|
+
emitErrorSchemas,
|
|
66
|
+
errorSchemaManagedPaths
|
|
67
|
+
} from './emit/error/emitErrorSchemas.js';
|
|
64
68
|
|
|
65
69
|
export async function generate(rootPath, options = {}) {
|
|
66
70
|
const startedAt = Date.now();
|
|
@@ -70,11 +74,18 @@ export async function generate(rootPath, options = {}) {
|
|
|
70
74
|
const schemaLoadOptions = {
|
|
71
75
|
skipValidation: options.skipValidation
|
|
72
76
|
};
|
|
73
|
-
const [
|
|
77
|
+
const [
|
|
78
|
+
rawObjects,
|
|
79
|
+
relationships,
|
|
80
|
+
flows,
|
|
81
|
+
notifications,
|
|
82
|
+
errors
|
|
83
|
+
] = await Promise.all([
|
|
74
84
|
loadLocalSchemaDirectory(project, 'ObjectSchemas', schemaLoadOptions),
|
|
75
85
|
loadLocalSchemaDirectory(project, 'RelationshipSchemas', schemaLoadOptions),
|
|
76
86
|
loadLocalSchemaDirectory(project, 'FlowSchemas', schemaLoadOptions),
|
|
77
|
-
loadLocalSchemaDirectory(project, 'NotificationSchemas', schemaLoadOptions)
|
|
87
|
+
loadLocalSchemaDirectory(project, 'NotificationSchemas', schemaLoadOptions),
|
|
88
|
+
loadLocalSchemaDirectory(project, 'ErrorSchemas', schemaLoadOptions)
|
|
78
89
|
]);
|
|
79
90
|
const schemaBucketName =
|
|
80
91
|
options.schemaBucketName || process.env.iz_serviceSchemaBucketName;
|
|
@@ -93,14 +104,24 @@ export async function generate(rootPath, options = {}) {
|
|
|
93
104
|
console.log(
|
|
94
105
|
`[DEBUG] [Generate] STATUS=SCHEMAS_LOADED | ` +
|
|
95
106
|
`objects=${objects.length} ` +
|
|
96
|
-
`relationships=${relationships.length} flows=${flows.length}`
|
|
107
|
+
`relationships=${relationships.length} flows=${flows.length} ` +
|
|
108
|
+
`notifications=${notifications.length} ` +
|
|
109
|
+
`errors=${errors.length}`
|
|
97
110
|
);
|
|
111
|
+
for (const objectSchema of objects) {
|
|
112
|
+
const objectType = objectSchema.objectType || 'unknown';
|
|
113
|
+
console.log(
|
|
114
|
+
`[DEBUG] [Generate] OBJECT_SCHEMA | objectType=${objectType}\n` +
|
|
115
|
+
JSON.stringify(objectSchema, null, 2)
|
|
116
|
+
);
|
|
117
|
+
}
|
|
98
118
|
const groups = await _emitGroups({
|
|
99
119
|
project,
|
|
100
120
|
objects,
|
|
101
121
|
relationships,
|
|
102
122
|
flows,
|
|
103
123
|
notifications,
|
|
124
|
+
errors,
|
|
104
125
|
objectGraphServiceTags,
|
|
105
126
|
relationshipGraphServiceTags,
|
|
106
127
|
logger: options.logger
|
|
@@ -149,6 +170,7 @@ async function _emitGroups({
|
|
|
149
170
|
relationships,
|
|
150
171
|
flows,
|
|
151
172
|
notifications,
|
|
173
|
+
errors,
|
|
152
174
|
objectGraphServiceTags,
|
|
153
175
|
relationshipGraphServiceTags,
|
|
154
176
|
logger
|
|
@@ -161,7 +183,8 @@ async function _emitGroups({
|
|
|
161
183
|
findData,
|
|
162
184
|
resource,
|
|
163
185
|
coreFunction,
|
|
164
|
-
systemText
|
|
186
|
+
systemText,
|
|
187
|
+
errorSchemas
|
|
165
188
|
] = await Promise.all([
|
|
166
189
|
emitCrudApplicationArtifacts({
|
|
167
190
|
objects,
|
|
@@ -196,6 +219,11 @@ async function _emitGroups({
|
|
|
196
219
|
relationships,
|
|
197
220
|
flows,
|
|
198
221
|
notifications
|
|
222
|
+
}),
|
|
223
|
+
emitErrorSchemas({
|
|
224
|
+
project,
|
|
225
|
+
errors,
|
|
226
|
+
objectGraphServiceTags
|
|
199
227
|
})
|
|
200
228
|
]);
|
|
201
229
|
const custom = await emitCustomFlowApplicationArtifacts(flows, {
|
|
@@ -307,6 +335,11 @@ async function _emitGroups({
|
|
|
307
335
|
scope: 'system-text-schemas',
|
|
308
336
|
managedPaths: systemTextSchemaManagedPaths(),
|
|
309
337
|
artifacts: systemText
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
scope: 'error-schemas',
|
|
341
|
+
managedPaths: errorSchemaManagedPaths(errors),
|
|
342
|
+
artifacts: errorSchemas
|
|
310
343
|
}
|
|
311
344
|
];
|
|
312
345
|
}
|
|
@@ -1388,6 +1388,78 @@ test('emits autoProvisionOwnerTargetRole in CreateNodeCallback when service has
|
|
|
1388
1388
|
);
|
|
1389
1389
|
});
|
|
1390
1390
|
|
|
1391
|
+
test('emits synchronous before-hook invocation in Create_Main when event is generateCode and invocationType is lambdaSyncInv', async () => {
|
|
1392
|
+
const rootPath = await createProject(true);
|
|
1393
|
+
const schemas = join(rootPath, 'app/src/schemas');
|
|
1394
|
+
await writeFile(
|
|
1395
|
+
join(schemas, 'FlowSchemas/ValidateBeforeCreateSync.js'),
|
|
1396
|
+
"export default { flowTag: 'validateBeforeCreateSync', event: ['generateCode'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'before', invocationType: 'lambdaSyncInv' }, objType: { objectType: 'order' } };\n"
|
|
1397
|
+
);
|
|
1398
|
+
await generateCrudApplicationArtifacts(rootPath, {
|
|
1399
|
+
serviceTag: 'orders'
|
|
1400
|
+
});
|
|
1401
|
+
|
|
1402
|
+
const createMain = await readFile(
|
|
1403
|
+
join(
|
|
1404
|
+
rootPath,
|
|
1405
|
+
'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
|
|
1406
|
+
),
|
|
1407
|
+
'utf8'
|
|
1408
|
+
);
|
|
1409
|
+
assert.match(
|
|
1410
|
+
createMain,
|
|
1411
|
+
/lambda\.invokeSync\(\s*_izContext,\s*await lambdaSharedLib\.lambdaFunctionName\(\s*_izContext,\s*'validateBeforeCreateSync'\s*\)/
|
|
1412
|
+
);
|
|
1413
|
+
assert.match(createMain, /Hook validateBeforeCreateSync failed/);
|
|
1414
|
+
|
|
1415
|
+
execFileSync(
|
|
1416
|
+
process.execPath,
|
|
1417
|
+
[
|
|
1418
|
+
'--check',
|
|
1419
|
+
join(
|
|
1420
|
+
rootPath,
|
|
1421
|
+
'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
|
|
1422
|
+
)
|
|
1423
|
+
],
|
|
1424
|
+
{ stdio: 'inherit' }
|
|
1425
|
+
);
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
test('emits async before-hook callback and SNS publish in Create_Main when event is generateCode and invocationType is ownTopic', async () => {
|
|
1429
|
+
const rootPath = await createProject(true);
|
|
1430
|
+
const schemas = join(rootPath, 'app/src/schemas');
|
|
1431
|
+
await writeFile(
|
|
1432
|
+
join(schemas, 'FlowSchemas/ValidateBeforeCreateAsync.js'),
|
|
1433
|
+
"export default { flowTag: 'validateBeforeCreateAsync', event: ['generateCode'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'before', invocationType: 'ownTopic' }, objType: { objectType: 'order' } };\n"
|
|
1434
|
+
);
|
|
1435
|
+
await generateCrudApplicationArtifacts(rootPath, {
|
|
1436
|
+
serviceTag: 'orders'
|
|
1437
|
+
});
|
|
1438
|
+
|
|
1439
|
+
const createMain = await readFile(
|
|
1440
|
+
join(
|
|
1441
|
+
rootPath,
|
|
1442
|
+
'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
|
|
1443
|
+
),
|
|
1444
|
+
'utf8'
|
|
1445
|
+
);
|
|
1446
|
+
assert.match(createMain, /sns\.publishAsync\(_izContext,/);
|
|
1447
|
+
assert.match(
|
|
1448
|
+
createMain,
|
|
1449
|
+
/snsTopicArnByFlowSchema\(\s*_izContext,\s*'validateBeforeCreateAsync_In'\s*\)/
|
|
1450
|
+
);
|
|
1451
|
+
assert.match(createMain, /CreateBeforeHookCallbackHdrSqs/);
|
|
1452
|
+
|
|
1453
|
+
const callbackHandler = await readFile(
|
|
1454
|
+
join(
|
|
1455
|
+
rootPath,
|
|
1456
|
+
'app/src/generatedCode/FlowObjects/Create/source/CreateBeforeHookCallback_HdrSqs.js'
|
|
1457
|
+
),
|
|
1458
|
+
'utf8'
|
|
1459
|
+
);
|
|
1460
|
+
assert.match(callbackHandler, /CreateBeforeHookCallback_Main/);
|
|
1461
|
+
});
|
|
1462
|
+
|
|
1391
1463
|
async function createProject(withCrudObject) {
|
|
1392
1464
|
const rootPath = await mkdtemp(
|
|
1393
1465
|
join(tmpdir(), 'generate-service-code-v2-crud-application-')
|
|
@@ -1173,3 +1173,101 @@ test('renders previousSteps extTopic subscriptions in InitialSetupConfig', async
|
|
|
1173
1173
|
/sqsEndpoint: 'OrderBuildOrderSellOfferLinkAndReserveOrderDataHdrSqs'/
|
|
1174
1174
|
);
|
|
1175
1175
|
});
|
|
1176
|
+
|
|
1177
|
+
test('emits handlers, YAML and templates for flow with event generateCode and invocationType ownTopic', async () => {
|
|
1178
|
+
const rootPath = await mkdtemp(
|
|
1179
|
+
join(tmpdir(), 'generate-service-code-v2-custom-gen-own-')
|
|
1180
|
+
);
|
|
1181
|
+
roots.add(rootPath);
|
|
1182
|
+
const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
|
|
1183
|
+
await mkdir(schemaRoot, { recursive: true });
|
|
1184
|
+
await writeFile(
|
|
1185
|
+
join(schemaRoot, 'GenOwn.js'),
|
|
1186
|
+
`export default {
|
|
1187
|
+
flowTag: 'genOwn',
|
|
1188
|
+
event: ['generateCode'],
|
|
1189
|
+
generatedCodeConfig: {
|
|
1190
|
+
generateCodeTag: 'create',
|
|
1191
|
+
codeHookTag: 'before',
|
|
1192
|
+
invocationType: 'ownTopic'
|
|
1193
|
+
},
|
|
1194
|
+
flowSteps: [{ stepName: 'Start', properties: ['dummyId'] }],
|
|
1195
|
+
stepProperties: { dummyId: { type: 'string' } }
|
|
1196
|
+
};\n`
|
|
1197
|
+
);
|
|
1198
|
+
|
|
1199
|
+
await generateCustomFlowApplicationArtifacts(rootPath);
|
|
1200
|
+
|
|
1201
|
+
const mainPath = join(
|
|
1202
|
+
rootPath,
|
|
1203
|
+
'app/src/generatedCode/FlowSchemas/GenOwn/source/GenOwn_Main.js'
|
|
1204
|
+
);
|
|
1205
|
+
const hdrSqsPath = join(
|
|
1206
|
+
rootPath,
|
|
1207
|
+
'app/src/generatedCode/FlowSchemas/GenOwn/source/GenOwn_HdrSqs.js'
|
|
1208
|
+
);
|
|
1209
|
+
const wbsMainPath = join(
|
|
1210
|
+
rootPath,
|
|
1211
|
+
'app/src/generatedCode/FlowSchemas/GenOwn/source/GenOwn_WbsMain.js'
|
|
1212
|
+
);
|
|
1213
|
+
const hdrWbsPath = join(
|
|
1214
|
+
rootPath,
|
|
1215
|
+
'app/src/generatedCode/FlowSchemas/GenOwn/source/GenOwn_HdrWbs.js'
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
assert.match(await readFile(mainPath, 'utf8'), /asyncFlowSharedLib/);
|
|
1219
|
+
assert.match(await readFile(hdrSqsPath, 'utf8'), /GenOwn_Main/);
|
|
1220
|
+
assert.match(await readFile(wbsMainPath, 'utf8'), /GenOwn_WbsMain/);
|
|
1221
|
+
assert.match(await readFile(hdrWbsPath, 'utf8'), /GenOwn_WbsMain/);
|
|
1222
|
+
|
|
1223
|
+
const yaml = await readFile(
|
|
1224
|
+
join(rootPath, 'app/sls_yaml/generatedCode/source/flow-schema.yml'),
|
|
1225
|
+
'utf8'
|
|
1226
|
+
);
|
|
1227
|
+
assert.match(yaml, /GenOwnHdrWbs:/);
|
|
1228
|
+
assert.match(yaml, /GenOwnHdrSqs:/);
|
|
1229
|
+
});
|
|
1230
|
+
|
|
1231
|
+
test('emits handler and YAML for flow with event generateCode and invocationType lambdaSyncInv', async () => {
|
|
1232
|
+
const rootPath = await mkdtemp(
|
|
1233
|
+
join(tmpdir(), 'generate-service-code-v2-custom-gen-sync-')
|
|
1234
|
+
);
|
|
1235
|
+
roots.add(rootPath);
|
|
1236
|
+
const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
|
|
1237
|
+
await mkdir(schemaRoot, { recursive: true });
|
|
1238
|
+
await writeFile(
|
|
1239
|
+
join(schemaRoot, 'GenSync.js'),
|
|
1240
|
+
`export default {
|
|
1241
|
+
flowTag: 'genSync',
|
|
1242
|
+
event: ['generateCode'],
|
|
1243
|
+
generatedCodeConfig: {
|
|
1244
|
+
generateCodeTag: 'create',
|
|
1245
|
+
codeHookTag: 'before',
|
|
1246
|
+
invocationType: 'lambdaSyncInv'
|
|
1247
|
+
},
|
|
1248
|
+
flowSteps: [{ stepName: 'Start', properties: ['dummyId'] }],
|
|
1249
|
+
stepProperties: { dummyId: { type: 'string' } }
|
|
1250
|
+
};\n`
|
|
1251
|
+
);
|
|
1252
|
+
|
|
1253
|
+
await generateCustomFlowApplicationArtifacts(rootPath);
|
|
1254
|
+
|
|
1255
|
+
const mainPath = join(
|
|
1256
|
+
rootPath,
|
|
1257
|
+
'app/src/generatedCode/FlowSchemas/GenSync/source/GenSync_Main.js'
|
|
1258
|
+
);
|
|
1259
|
+
const hdrInvPath = join(
|
|
1260
|
+
rootPath,
|
|
1261
|
+
'app/src/generatedCode/FlowSchemas/GenSync/source/GenSync_HdrInv.js'
|
|
1262
|
+
);
|
|
1263
|
+
|
|
1264
|
+
assert.match(await readFile(mainPath, 'utf8'), /export default async function GenSync/);
|
|
1265
|
+
assert.match(await readFile(hdrInvPath, 'utf8'), /GenSync_Main/);
|
|
1266
|
+
|
|
1267
|
+
const yaml = await readFile(
|
|
1268
|
+
join(rootPath, 'app/sls_yaml/generatedCode/source/flow-schema.yml'),
|
|
1269
|
+
'utf8'
|
|
1270
|
+
);
|
|
1271
|
+
assert.match(yaml, /GenSyncHdrInv:/);
|
|
1272
|
+
});
|
|
1273
|
+
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFile, rm, writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import test, { after } from 'node:test';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
emitErrorSchemas,
|
|
9
|
+
errorSchemaManagedPaths,
|
|
10
|
+
ERROR_SCHEMA_PATH
|
|
11
|
+
} from '../src/emit/error/emitErrorSchemas.js';
|
|
12
|
+
import { generate } from '../src/generate.js';
|
|
13
|
+
|
|
14
|
+
const roots = new Set();
|
|
15
|
+
after(() =>
|
|
16
|
+
Promise.all(
|
|
17
|
+
[...roots].map((root) => rm(root, { recursive: true, force: true }))
|
|
18
|
+
)
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
test('emitErrorSchemas produces expected ERROR_TYPES for graph and general errors', async () => {
|
|
22
|
+
const artifacts = await emitErrorSchemas({
|
|
23
|
+
project: { serviceTag: 'MyService' },
|
|
24
|
+
objectGraphServiceTags: ['GraphHandler'],
|
|
25
|
+
errors: [
|
|
26
|
+
{
|
|
27
|
+
errorTag: 'NOT_FOUND_SCHEMA',
|
|
28
|
+
errorAction: 'throw',
|
|
29
|
+
sharedLibrary: true,
|
|
30
|
+
_sourceFile: 'errorgraph.js'
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
errorTag: 'INVALID_GENERATED_BY',
|
|
34
|
+
errorAction: 'throw',
|
|
35
|
+
sharedLibrary: true,
|
|
36
|
+
_sourceFile: 'errorgraph.js'
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
errorTag: 'CUSTOM_INTERNAL_ERROR',
|
|
40
|
+
errorAction: 'return',
|
|
41
|
+
_sourceFile: 'errors.js'
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
errorTag: 'EXPLICIT_SERVICE_ERROR',
|
|
45
|
+
errorAction: 'throw',
|
|
46
|
+
serviceTag: 'CustomService',
|
|
47
|
+
_sourceFile: 'custom.js'
|
|
48
|
+
}
|
|
49
|
+
]
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
assert.equal(artifacts.length, 1);
|
|
53
|
+
assert.equal(artifacts[0].path, ERROR_SCHEMA_PATH);
|
|
54
|
+
|
|
55
|
+
const content = artifacts[0].content;
|
|
56
|
+
assert.match(content, /^export const ERROR_TYPES = {/m);
|
|
57
|
+
assert.doesNotMatch(content, /export default/);
|
|
58
|
+
|
|
59
|
+
// Errors without explicit serviceTag resolve to project.serviceTag
|
|
60
|
+
assert.match(
|
|
61
|
+
content,
|
|
62
|
+
/NOT_FOUND_SCHEMA:\s*{\s*errorTag:\s*'NOT_FOUND_SCHEMA',\s*serviceTag:\s*'MyService'\s*}/
|
|
63
|
+
);
|
|
64
|
+
assert.match(
|
|
65
|
+
content,
|
|
66
|
+
/INVALID_GENERATED_BY:\s*{\s*errorTag:\s*'INVALID_GENERATED_BY',\s*serviceTag:\s*'MyService'\s*}/
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
// General error resolved to project.serviceTag
|
|
70
|
+
assert.match(
|
|
71
|
+
content,
|
|
72
|
+
/CUSTOM_INTERNAL_ERROR:\s*{\s*errorTag:\s*'CUSTOM_INTERNAL_ERROR',\s*serviceTag:\s*'MyService'\s*}/
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Explicit serviceTag respected
|
|
76
|
+
assert.match(
|
|
77
|
+
content,
|
|
78
|
+
/EXPLICIT_SERVICE_ERROR:\s*{\s*errorTag:\s*'EXPLICIT_SERVICE_ERROR',\s*serviceTag:\s*'CustomService'\s*}/
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// Keys sorted alphabetically
|
|
82
|
+
const customIdx = content.indexOf('CUSTOM_INTERNAL_ERROR');
|
|
83
|
+
const explicitIdx = content.indexOf('EXPLICIT_SERVICE_ERROR');
|
|
84
|
+
const invalidIdx = content.indexOf('INVALID_GENERATED_BY');
|
|
85
|
+
const notFoundIdx = content.indexOf('NOT_FOUND_SCHEMA');
|
|
86
|
+
assert.ok(customIdx < explicitIdx);
|
|
87
|
+
assert.ok(explicitIdx < invalidIdx);
|
|
88
|
+
assert.ok(invalidIdx < notFoundIdx);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test('emitErrorSchemas returns empty array when no errors provided', async () => {
|
|
92
|
+
const artifacts = await emitErrorSchemas({
|
|
93
|
+
project: { serviceTag: 'MyService' },
|
|
94
|
+
errors: []
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
assert.deepEqual(artifacts, []);
|
|
98
|
+
assert.deepEqual(errorSchemaManagedPaths([]), []);
|
|
99
|
+
assert.deepEqual(errorSchemaManagedPaths(['someError']), [ERROR_SCHEMA_PATH]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('generate creates errorSchema.js when ErrorSchemas directory exists', async () => {
|
|
103
|
+
const rootPath = join(
|
|
104
|
+
tmpdir(),
|
|
105
|
+
`error-schema-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
106
|
+
);
|
|
107
|
+
roots.add(rootPath);
|
|
108
|
+
await mkdir(join(rootPath, 'app/src/schemas/ErrorSchemas'), {
|
|
109
|
+
recursive: true
|
|
110
|
+
});
|
|
111
|
+
await mkdir(join(rootPath, 'config'), { recursive: true });
|
|
112
|
+
|
|
113
|
+
await writeFile(
|
|
114
|
+
join(rootPath, 'config/serverless.config.yml'),
|
|
115
|
+
'main_config:\n iz_serviceTag: CategoryTreeStandard\n'
|
|
116
|
+
);
|
|
117
|
+
await writeFile(
|
|
118
|
+
join(rootPath, 'app/src/schemas/ErrorSchemas/errorgraph.js'),
|
|
119
|
+
`export default [
|
|
120
|
+
{
|
|
121
|
+
errorTag: 'NOT_FOUND_SCHEMA',
|
|
122
|
+
errorAction: 'throw',
|
|
123
|
+
sharedLibrary: true
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
errorTag: 'INVALID_GENERATED_BY',
|
|
127
|
+
errorAction: 'throw',
|
|
128
|
+
sharedLibrary: true
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
errorTag: 'MISSING_USER_NODE',
|
|
132
|
+
errorAction: 'throw',
|
|
133
|
+
sharedLibrary: true
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
`
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
// Run generate
|
|
140
|
+
await generate(rootPath);
|
|
141
|
+
|
|
142
|
+
const targetFile = join(
|
|
143
|
+
rootPath,
|
|
144
|
+
'app/src/schemas/ErrorSchemas/generateCode/errorSchema.js'
|
|
145
|
+
);
|
|
146
|
+
const content = await readFile(targetFile, 'utf8');
|
|
147
|
+
|
|
148
|
+
assert.match(content, /export const ERROR_TYPES = {/);
|
|
149
|
+
assert.match(content, /NOT_FOUND_SCHEMA:/);
|
|
150
|
+
assert.match(content, /INVALID_GENERATED_BY:/);
|
|
151
|
+
assert.match(content, /MISSING_USER_NODE:/);
|
|
152
|
+
assert.match(content, /serviceTag:\s*'CategoryTreeStandard'/);
|
|
153
|
+
assert.doesNotMatch(content, /export default/);
|
|
154
|
+
});
|
package/test/publicApi.test.js
CHANGED
|
@@ -70,7 +70,7 @@ test('generate logs its lifecycle through console.log', async () => {
|
|
|
70
70
|
|
|
71
71
|
assert.match(messages[0], /STATUS=STARTED/);
|
|
72
72
|
assert.ok(messages.some((message) => {
|
|
73
|
-
return /STATUS=SCHEMAS_LOADED
|
|
73
|
+
return /STATUS=SCHEMAS_LOADED.*notifications=/.test(message);
|
|
74
74
|
}));
|
|
75
75
|
assert.ok(messages.some((message) => {
|
|
76
76
|
return /STATUS=ARTIFACTS_EMITTED/.test(message);
|