@izara_project/izara-core-generate-service-code 1.0.60 → 1.0.62
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 +11 -8
- package/src/codeGenerators/app/initial_setup/InitialSetupGenerator.js +100 -150
- package/src/codeGenerators/app/initial_setup/templates/InitialSetup_LambdaRole.ejs +27 -20
- package/src/codeGenerators/app/sls_yaml/FunctionYamlGenerator.js +12 -22
- package/src/codeGenerators/app/sls_yaml/RoleNameConfigGenerator.js +13 -72
- package/src/codeGenerators/app/sls_yaml/_policy/PolicyEmitter.js +3 -2
- package/src/codeGenerators/app/sls_yaml/_policy/__tests__/PolicyEmitter.test.js +3 -3
- package/src/codeGenerators/app/sls_yaml/_policy/templates/ManagedPolicy_Yaml.ejs +2 -2
- package/src/codeGenerators/app/sls_yaml/templates/SharedResource_Yaml.ejs +1 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowObjects/EndpointsGenerator.js +3 -2
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowRbac/RbacFlowGenerator.js +4 -0
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowRelationships/RelationshipFlowGenerator.js +2 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowEndpoint_HdrWbs.ejs +2 -2
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowEndpoint_Main.ejs +8 -4
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowMain_Wbs.ejs +4 -4
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/s3/CreatePreSignUrl_Main.ejs +1 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/s3/ProcessS3File_Main.ejs +1 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/step/FlowEndpointComplete_Main.ejs +11 -12
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseDsqHandler.js +55 -0
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseSqsHandler.js +20 -21
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/__tests__/BaseDsqHandler.test.js +93 -0
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/dsq.js +5 -0
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncApi.js +10 -19
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncInv.js +10 -19
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/ownTopic.js +16 -4
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/registry.js +3 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrApi.ejs +3 -3
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrDsq.ejs +137 -0
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrInv.ejs +3 -3
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrSqs.ejs +62 -51
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/eventTypes.js +2 -2
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowEntryPointBase.js +8 -4
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowMainFunctionBase.js +28 -36
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowNaming.js +16 -0
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowSelection.js +0 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepBase.js +11 -5
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepNormalizer.js +48 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowValidator.js +7 -21
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/propertyNormalizer.js +20 -0
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/roleNameDetection.js +49 -0
- package/src/codeGenerators/app/src/generatedCode/libs/templates/Consts.ejs +10 -10
- package/src/codeGenerators/resource/sls_yaml/DynamoDBGenerator.js +1 -10
- package/src/codeGenerators/resource/sls_yaml/FlowOutGenerator.js +1 -10
- package/src/codeGenerators/resource/sls_yaml/FlowResourceYamlGenerator.js +3 -12
- package/src/core/validate.js +9 -5
- package/src/generate.js +1 -1
- package/src/generateCode.js +90 -24
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/FlowSchemaGenerator.js +0 -43
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicFlowSchemaTemplate.ejs +3 -3
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicRbacFlowSchemaTemplate.ejs +1 -1
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/RelationshipFlowSchemaTemplate.ejs +2 -2
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/UserRbacFlowSchemaTemplate.ejs +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
const
|
|
1
|
+
const ALLOWED_EVENTS = ['ownTopic', 'extTopic', 'lambdaSyncInv', 'lambdaSyncApi', 'eventBridge', 's3', 'dsq'];
|
|
2
2
|
const MAIN_ASYNC_EVENTS = ['ownTopic', 'extTopic', 's3'];
|
|
3
|
-
const ALLOWED_STEP_EVENTS = ['sqs', 'ownTopic', 'extTopic', 'dsq', 'lambdaSyncInv', 'lambdaSyncApi'];
|
|
4
3
|
const RESERVED_SYSTEM_FLOW_TAGS = new Set([
|
|
5
4
|
'Create',
|
|
6
5
|
'Update',
|
|
@@ -73,9 +72,9 @@ export function validateFlowsForGeneration(flows) {
|
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
const events = asArray(flow.event);
|
|
76
|
-
const invalidMainEvents = events.filter(event => !
|
|
75
|
+
const invalidMainEvents = events.filter(event => !ALLOWED_EVENTS.includes(event));
|
|
77
76
|
if (invalidMainEvents.length > 0) {
|
|
78
|
-
errors.push(`Flow '${flowTag}' uses unsupported main events: ${invalidMainEvents.join(', ')}. Allowed are: ${
|
|
77
|
+
errors.push(`Flow '${flowTag}' uses unsupported main events: ${invalidMainEvents.join(', ')}. Allowed are: ${ALLOWED_EVENTS.join(', ')}.`);
|
|
79
78
|
}
|
|
80
79
|
|
|
81
80
|
const mainAsyncEvents = events.filter(event => MAIN_ASYNC_EVENTS.includes(event));
|
|
@@ -86,7 +85,6 @@ export function validateFlowsForGeneration(flows) {
|
|
|
86
85
|
const flowSteps = Array.isArray(flow.flowSteps) ? flow.flowSteps : [];
|
|
87
86
|
const seenStepNames = new Set();
|
|
88
87
|
let anyStepHasPlugInHooks = false;
|
|
89
|
-
let inTagCount = 0;
|
|
90
88
|
|
|
91
89
|
for (const step of flowSteps) {
|
|
92
90
|
const stepName = step?.stepName;
|
|
@@ -100,34 +98,22 @@ export function validateFlowsForGeneration(flows) {
|
|
|
100
98
|
}
|
|
101
99
|
seenStepNames.add(stepName);
|
|
102
100
|
|
|
103
|
-
if (stepName === 'In') {
|
|
104
|
-
errors.push(`Flow '${flowTag}' uses legacy stepName 'In'. Use 'InTag'.`);
|
|
105
|
-
}
|
|
106
|
-
if (stepName === 'InTag') {
|
|
107
|
-
inTagCount += 1;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
101
|
if (step.plugInHooks && step.plugInHooks.length > 0) {
|
|
111
102
|
anyStepHasPlugInHooks = true;
|
|
112
103
|
}
|
|
113
104
|
|
|
114
105
|
const stepEvents = asArray(step.event);
|
|
115
|
-
const invalidStepEvents = stepEvents.filter(event => !
|
|
106
|
+
const invalidStepEvents = stepEvents.filter(event => !ALLOWED_EVENTS.includes(event));
|
|
116
107
|
if (invalidStepEvents.length > 0) {
|
|
117
|
-
errors.push(`Flow '${flowTag}' step '${stepName}' uses unsupported step events: ${invalidStepEvents.join(', ')}. Allowed are: ${
|
|
108
|
+
errors.push(`Flow '${flowTag}' step '${stepName}' uses unsupported step events: ${invalidStepEvents.join(', ')}. Allowed are: ${ALLOWED_EVENTS.join(', ')}.`);
|
|
118
109
|
}
|
|
119
110
|
|
|
120
111
|
validateNamedRefs(flowTag, stepName, step.properties, flow.stepProperties, 'properties', errors);
|
|
121
112
|
validateNamedRefs(flowTag, stepName, step.messageAttributes, flow.stepMessageAttributes, 'messageAttributes', errors);
|
|
122
113
|
}
|
|
123
114
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (anyStepHasPlugInHooks && inTagCount === 0) {
|
|
129
|
-
errors.push(`Flow '${flowTag}' uses plugInHooks but does not have an 'InTag' step. If flow needs plugin-aware main lambda, it must include an 'InTag' step.`);
|
|
130
|
-
}
|
|
115
|
+
// ponytail: relaxed anyStepHasPlugInHooks requirement
|
|
116
|
+
// since unified entry points (step 0) can now handle plugins directly without start.
|
|
131
117
|
|
|
132
118
|
if (anyStepHasPlugInHooks) {
|
|
133
119
|
const forbiddenEvents = ['lambdaSyncInv', 'lambdaSyncApi', 's3', 'eventBridge'];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const TYPE_MAP = {
|
|
2
|
+
string: 'string', number: 'number', integer: 'number',
|
|
3
|
+
currency: 'string', currencyValue: 'number', numeric: 'number',
|
|
4
|
+
float: 'number', special: 'string', object: 'object',
|
|
5
|
+
boolean: 'boolean', array: 'array', arrayMixed: 'array',
|
|
6
|
+
arrayObject: 'array', arrayNumeric: 'array', arrayString: 'array',
|
|
7
|
+
timestamp: 'number'
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function normalizePropertyType(p, lookupSource) {
|
|
11
|
+
let prop = typeof p === 'string'
|
|
12
|
+
? ((lookupSource && lookupSource[p]) ? lookupSource[p] : { propertyName: p, type: 'string' })
|
|
13
|
+
: p;
|
|
14
|
+
|
|
15
|
+
if (prop && prop.type) {
|
|
16
|
+
prop = { ...prop, type: TYPE_MAP[prop.type] || prop.type };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return prop;
|
|
20
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { getFlowsForGeneration } from './flowSelection.js';
|
|
2
|
+
import { isCrudFlow, isRbacFlow, isRelationshipFlow } from './flowClassifier.js';
|
|
3
|
+
|
|
4
|
+
export const FLOW_OBJECTS_ROLE = 'FlowObjectsRole';
|
|
5
|
+
export const FLOW_RBAC_ROLE = 'FlowRbacRole';
|
|
6
|
+
export const FLOW_RELATIONSHIP_ROLE = 'FlowRelationshipRole';
|
|
7
|
+
export const WEB_SOCKET_MAIN_ROLE = 'WebSocketMainRole';
|
|
8
|
+
export const PROC_FIND_DATA_ROLE = 'ProcFindDataRole';
|
|
9
|
+
|
|
10
|
+
function upperCase(str) {
|
|
11
|
+
if (!str) return str;
|
|
12
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function isCustomRbacFlow(flowTag) {
|
|
16
|
+
return String(flowTag || '').toLowerCase().endsWith('rbacflow');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function detectRoleNames(allSchemas) {
|
|
20
|
+
const flows = getFlowsForGeneration(allSchemas);
|
|
21
|
+
const roleNames = new Set([PROC_FIND_DATA_ROLE]);
|
|
22
|
+
|
|
23
|
+
for (const flow of flows) {
|
|
24
|
+
if (!flow.flowTag) continue;
|
|
25
|
+
|
|
26
|
+
if (isCrudFlow(flow.flowTag)) {
|
|
27
|
+
roleNames.add(FLOW_OBJECTS_ROLE);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isRbacFlow(flow.flowTag) || isCustomRbacFlow(flow.flowTag)) {
|
|
32
|
+
roleNames.add(FLOW_RBAC_ROLE);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isRelationshipFlow(flow.flowTag)) {
|
|
37
|
+
roleNames.add(FLOW_RELATIONSHIP_ROLE);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if ((flow.event || []).includes('ownTopic')) {
|
|
42
|
+
roleNames.add(WEB_SOCKET_MAIN_ROLE);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
roleNames.add(`${upperCase(flow.flowTag)}Role`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return { flows, roleNames };
|
|
49
|
+
}
|
|
@@ -316,30 +316,30 @@ const TOPIC_NAME_GRAPH_HANDLER = {
|
|
|
316
316
|
};
|
|
317
317
|
|
|
318
318
|
const TOPIC_NAME_GENERATE_CODE = {
|
|
319
|
-
updateRelComplete: '
|
|
320
|
-
createRelComplete: '
|
|
321
|
-
deleteRelComplete: '
|
|
322
|
-
getRelComplete: '
|
|
319
|
+
updateRelComplete: 'UpdateRelationshipCompleteHdrSqs',
|
|
320
|
+
createRelComplete: 'CreateRelationshipCompleteHdrSqs',
|
|
321
|
+
deleteRelComplete: 'DeleteRelationshipCompleteHdrSqs',
|
|
322
|
+
getRelComplete: 'GetRelationshipCompleteHdrSqs',
|
|
323
323
|
outUpdateRelComplete: 'UpdateRelationship_Out',
|
|
324
324
|
outCreateRelComplete: 'CreateRelationship_Out',
|
|
325
325
|
outDeleteRelComplete: 'DeleteRelationship_Out',
|
|
326
326
|
outGetRelComplete: 'GetRelationship_Out',
|
|
327
|
-
updateNodeComplete: '
|
|
327
|
+
updateNodeComplete: 'UpdateCompleteHdrSqs',
|
|
328
328
|
outUpdateNodeComplete: 'Update_Out',
|
|
329
|
-
createNodeComplete: '
|
|
329
|
+
createNodeComplete: 'CreateCompleteHdrSqs',
|
|
330
330
|
outCreateNodeComplete: 'Create_Out',
|
|
331
|
-
deleteNodeComplete: '
|
|
331
|
+
deleteNodeComplete: 'DeleteCompleteHdrSqs',
|
|
332
332
|
outDeleteNodeComplete: 'Delete_Out',
|
|
333
|
-
getNodeComplete: '
|
|
333
|
+
getNodeComplete: 'GetCompleteHdrSqs',
|
|
334
334
|
outGetNodeComplete: 'Get_Out',
|
|
335
335
|
reservedLimitComplete: 'ReservedLimitComplete',
|
|
336
336
|
createPreSignUrl: 'CreatePreSignUrl',
|
|
337
337
|
flowSchemaOwnTopicComplete: 'FlowSchemaOwnTopicComplete',
|
|
338
338
|
flowSchemaOwnTopicEndpointComplete: 'FlowSchemaOwnTopicEndpointComplete',
|
|
339
339
|
createRecordComplete: 'CreateRecordComplete',
|
|
340
|
-
changeRelationshipComplete: '
|
|
340
|
+
changeRelationshipComplete: 'ChangeRelationshipCompleteHdrSqs',
|
|
341
341
|
outChangeRelationshipComplete: 'ChangeRelationship_Out',
|
|
342
|
-
moveRelationshipComplete: '
|
|
342
|
+
moveRelationshipComplete: 'MoveRelationshipCompleteHdrSqs',
|
|
343
343
|
outMoveRelationshipComplete: 'MoveRelationship_Out'
|
|
344
344
|
};
|
|
345
345
|
|
|
@@ -6,15 +6,6 @@ import ejs from 'ejs';
|
|
|
6
6
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
7
|
const __dirname = path.dirname(__filename);
|
|
8
8
|
|
|
9
|
-
async function appendToFile(filePath, content) {
|
|
10
|
-
try {
|
|
11
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
12
|
-
await fs.appendFile(filePath, content + '\n', 'utf-8');
|
|
13
|
-
} catch (error) {
|
|
14
|
-
console.error(`[Error] Failed to append to file: ${filePath}`, error);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
9
|
export async function generateDynamoDBTables(allSchemas, options) {
|
|
19
10
|
console.log(' [DynamoDBGenerator] Generating DynamoDB Tables...');
|
|
20
11
|
|
|
@@ -88,7 +79,7 @@ export async function generateDynamoDBTables(allSchemas, options) {
|
|
|
88
79
|
pk: keys.pk,
|
|
89
80
|
sk: keys.sk
|
|
90
81
|
});
|
|
91
|
-
|
|
82
|
+
await fs.appendFile(yamlOutputPath, `${yamlContent}\n`, 'utf-8');
|
|
92
83
|
generatedCount++;
|
|
93
84
|
}
|
|
94
85
|
|
|
@@ -8,15 +8,6 @@ import { getFlowsForGeneration } from '../../app/src/generatedCode/Flow/_shared/
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = path.dirname(__filename);
|
|
10
10
|
|
|
11
|
-
async function appendToFile(filePath, content) {
|
|
12
|
-
try {
|
|
13
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
14
|
-
await fs.appendFile(filePath, `${content}\n`, 'utf-8');
|
|
15
|
-
} catch (error) {
|
|
16
|
-
console.error(`[Error] Failed to append file: ${filePath}`, error);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
11
|
export async function generateFlowOut(allSchemas, options) {
|
|
21
12
|
console.log(' [FlowOutGenerator] Generating Flow Out nodes (SNS Topics)...');
|
|
22
13
|
|
|
@@ -37,7 +28,7 @@ export async function generateFlowOut(allSchemas, options) {
|
|
|
37
28
|
|
|
38
29
|
generatedTopics.add(topicName);
|
|
39
30
|
const yamlContent = ejs.render(yamlTpl, { topicName });
|
|
40
|
-
await
|
|
31
|
+
await fs.appendFile(yamlOutputPath, `${yamlContent}\n`, 'utf-8');
|
|
41
32
|
generatedCount++;
|
|
42
33
|
}
|
|
43
34
|
|
|
@@ -8,15 +8,6 @@ import { getFlowsForGeneration } from '../../app/src/generatedCode/Flow/_shared/
|
|
|
8
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
9
|
const __dirname = path.dirname(__filename);
|
|
10
10
|
|
|
11
|
-
async function appendToFile(filePath, content) {
|
|
12
|
-
try {
|
|
13
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
14
|
-
await fs.appendFile(filePath, `${content}\n`, 'utf-8');
|
|
15
|
-
} catch (error) {
|
|
16
|
-
console.error(`[Error] Failed to append file: ${filePath}`, error);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
11
|
export async function generateFlowResourceYaml(allSchemas, options) {
|
|
21
12
|
console.log(' [FlowResourceYamlGenerator] Generating SQS/SNS YAML resources for flows...');
|
|
22
13
|
|
|
@@ -42,7 +33,7 @@ export async function generateFlowResourceYaml(allSchemas, options) {
|
|
|
42
33
|
|
|
43
34
|
generatedResources.add(resourceKey);
|
|
44
35
|
const yamlContent = ejs.render(template, templateData);
|
|
45
|
-
await
|
|
36
|
+
await fs.appendFile(yamlOutputPath, `${yamlContent}\n`, 'utf-8');
|
|
46
37
|
generatedCount++;
|
|
47
38
|
}
|
|
48
39
|
|
|
@@ -79,9 +70,9 @@ export async function generateFlowResourceYaml(allSchemas, options) {
|
|
|
79
70
|
if (Array.isArray(flow.flowSteps)) {
|
|
80
71
|
for (const stepConfig of flow.flowSteps) {
|
|
81
72
|
const stepName = stepConfig.stepName;
|
|
82
|
-
if (stepName === '
|
|
73
|
+
if (stepName?.toLowerCase() === 'start') continue;
|
|
83
74
|
|
|
84
|
-
const events = stepConfig.event || ['
|
|
75
|
+
const events = stepConfig.event || ['extTopic'];
|
|
85
76
|
for (const ev of events) {
|
|
86
77
|
let queueName = `${flowTag}${stepName}`;
|
|
87
78
|
if (stepName === 'Complete') {
|
package/src/core/validate.js
CHANGED
|
@@ -20,17 +20,21 @@ export async function validateSchemas(rootPath) {
|
|
|
20
20
|
// 1. Read config for bucket name and service tag
|
|
21
21
|
let configStr = '';
|
|
22
22
|
let iz_serviceTag = '';
|
|
23
|
+
let iz_serviceName = '';
|
|
23
24
|
let iz_serviceSchemaBucketName = '';
|
|
24
25
|
try {
|
|
25
26
|
configStr = await fs.readFile(serviceConfigPath, 'utf8');
|
|
26
27
|
const config = yaml.parse(configStr);
|
|
27
28
|
iz_serviceTag = config?.main_config?.iz_serviceTag;
|
|
29
|
+
iz_serviceName = config?.main_config?.iz_serviceName || iz_serviceTag;
|
|
28
30
|
iz_serviceSchemaBucketName = config?.main_config?.iz_serviceSchemaBucketName || 'unknown-bucket';
|
|
29
31
|
|
|
30
|
-
if (!
|
|
31
|
-
validateStatus.validateErrors.push('Missing iz_serviceTag in serverless.config.yml. This is a critical configuration field.');
|
|
32
|
-
} else if (!/^[a-zA-Z0-9]+$/.test(
|
|
33
|
-
validateStatus.validateErrors.push(`
|
|
32
|
+
if (!iz_serviceName) {
|
|
33
|
+
validateStatus.validateErrors.push('Missing iz_serviceName or iz_serviceTag in serverless.config.yml. This is a critical configuration field.');
|
|
34
|
+
} else if (!/^[a-zA-Z0-9]+$/.test(iz_serviceName)) {
|
|
35
|
+
validateStatus.validateErrors.push(`Service Name '${iz_serviceName}' must contain only alphanumeric characters ([a-zA-Z0-9]).`);
|
|
36
|
+
} else {
|
|
37
|
+
console.log(` [Validator] Validating with serviceName: '${iz_serviceName}'`);
|
|
34
38
|
}
|
|
35
39
|
} catch (e) {
|
|
36
40
|
validateStatus.validateErrors.push(`Could not read or parse serverless.config.yml at ${serviceConfigPath}: ${e.message}`);
|
|
@@ -124,7 +128,7 @@ export async function validateSchemas(rootPath) {
|
|
|
124
128
|
if (flow.objType && flow.objType.objectType) {
|
|
125
129
|
const targetObj = objectSchemasMap[flow.objType.objectType];
|
|
126
130
|
if (targetObj) {
|
|
127
|
-
const expectedPrefix =
|
|
131
|
+
const expectedPrefix = `${iz_serviceName}_${flow.flowTag}`; // Prefix logic based on service name
|
|
128
132
|
const isMissingFields = !targetObj.fieldNames || !targetObj.fieldNames[`${expectedPrefix}Status`];
|
|
129
133
|
|
|
130
134
|
if (isMissingFields) {
|
package/src/generate.js
CHANGED
package/src/generateCode.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import yaml from 'yaml';
|
|
3
4
|
|
|
4
5
|
import { parseObjectSchemas } from './parsers/objectSchemaParser.js';
|
|
5
6
|
import { parseRelationshipSchemas } from './parsers/relationshipSchemaParser.js';
|
|
@@ -8,6 +9,7 @@ import { parseFlowSchemas } from './parsers/flowSchemaParser.js';
|
|
|
8
9
|
import { generateFlowEntryPoint } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowEntryPointBase.js';
|
|
9
10
|
import { generateFlowMainFunction } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowMainFunctionBase.js';
|
|
10
11
|
import { generateFlowSteps } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepBase.js';
|
|
12
|
+
import { normalizeUnifiedFlows } from './codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepNormalizer.js';
|
|
11
13
|
|
|
12
14
|
import { generateEndpointsFlow } from './codeGenerators/app/src/generatedCode/Flow/FlowObjects/EndpointsGenerator.js';
|
|
13
15
|
import { generateFlowOut } from './codeGenerators/resource/sls_yaml/FlowOutGenerator.js';
|
|
@@ -53,6 +55,26 @@ export async function generateCode(rootPath, options = {}) {
|
|
|
53
55
|
const generatorOptions = createGeneratorOptions(rootPath, options);
|
|
54
56
|
const { outputPath } = generatorOptions;
|
|
55
57
|
|
|
58
|
+
let serviceName;
|
|
59
|
+
try {
|
|
60
|
+
const configPath = options.configPath || path.join(rootPath, 'config', 'serverless.config.yml');
|
|
61
|
+
if (!fs.existsSync(configPath)) {
|
|
62
|
+
throw new Error(`serverless.config.yml not found at path: ${configPath}`);
|
|
63
|
+
}
|
|
64
|
+
const configStr = fs.readFileSync(configPath, 'utf8');
|
|
65
|
+
const config = yaml.parse(configStr);
|
|
66
|
+
serviceName = config?.main_config?.iz_serviceName || config?.main_config?.iz_serviceTag;
|
|
67
|
+
|
|
68
|
+
if (!serviceName) {
|
|
69
|
+
throw new Error('main_config.iz_serviceName or main_config.iz_serviceTag is missing in serverless.config.yml');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log(`[INFO] [generateCode] Loaded config - serviceName: '${serviceName}' from ${configPath}`);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
throw new Error(`[Developer Error] Failed to read service name from config: ${e.message}`);
|
|
75
|
+
}
|
|
76
|
+
generatorOptions.serviceName = serviceName;
|
|
77
|
+
|
|
56
78
|
// Clean workspace
|
|
57
79
|
const generatedCodePath = path.join(
|
|
58
80
|
outputPath,
|
|
@@ -189,25 +211,19 @@ export async function generateCode(rootPath, options = {}) {
|
|
|
189
211
|
}
|
|
190
212
|
}
|
|
191
213
|
|
|
214
|
+
let cleanupStarted = false;
|
|
215
|
+
let generationSucceeded = false;
|
|
216
|
+
const backedUpDirs = new Set();
|
|
217
|
+
const dirsToBackup = [
|
|
218
|
+
generatedCodePath,
|
|
219
|
+
path.join(outputPath, 'app', 'sls_yaml', 'generatedCode'),
|
|
220
|
+
path.join(outputPath, 'resource', 'sls_yaml', 'generatedCode')
|
|
221
|
+
];
|
|
222
|
+
|
|
192
223
|
// ponytail: ensure hooks are restored even if generation fails
|
|
193
224
|
try {
|
|
194
|
-
|
|
195
|
-
'[INFO] [generateCode] STATUS=CLEANUP | Removing old generatedCode directories...'
|
|
196
|
-
);
|
|
197
|
-
await fs.promises.rm(generatedCodePath, {
|
|
198
|
-
recursive: true,
|
|
199
|
-
force: true
|
|
200
|
-
});
|
|
201
|
-
await fs.promises.rm(
|
|
202
|
-
path.join(outputPath, 'app', 'sls_yaml', 'generatedCode'),
|
|
203
|
-
{ recursive: true, force: true }
|
|
204
|
-
);
|
|
205
|
-
await fs.promises.rm(
|
|
206
|
-
path.join(outputPath, 'resource', 'sls_yaml', 'generatedCode'),
|
|
207
|
-
{ recursive: true, force: true }
|
|
208
|
-
);
|
|
225
|
+
policyRegistry.clear();
|
|
209
226
|
|
|
210
|
-
policyRegistry.clear();
|
|
211
227
|
|
|
212
228
|
// 1. Parsing & Hydration Pipeline
|
|
213
229
|
console.log('[INFO] [generateCode] STATUS=PARSING | ObjectSchemas...');
|
|
@@ -227,9 +243,34 @@ export async function generateCode(rootPath, options = {}) {
|
|
|
227
243
|
flowsForGeneration: flowData.flows || []
|
|
228
244
|
};
|
|
229
245
|
|
|
246
|
+
console.log('[INFO] [generateCode] STATUS=NORMALIZING | Adapting Unified Flows...');
|
|
247
|
+
normalizeUnifiedFlows(allSchemas.flowsForGeneration);
|
|
248
|
+
|
|
230
249
|
console.log('[INFO] [generateCode] STATUS=VALIDATING | Validating Flows...');
|
|
231
250
|
validateFlowsForGeneration(allSchemas.flowsForGeneration);
|
|
232
251
|
|
|
252
|
+
console.log(
|
|
253
|
+
'[INFO] [generateCode] STATUS=BACKUP_DIRS | Backing up old generatedCode directories...'
|
|
254
|
+
);
|
|
255
|
+
for (const dir of dirsToBackup) {
|
|
256
|
+
if (fs.existsSync(dir)) {
|
|
257
|
+
if (fs.existsSync(`${dir}.bak`)) {
|
|
258
|
+
await fs.promises.rm(`${dir}.bak`, { recursive: true, force: true });
|
|
259
|
+
}
|
|
260
|
+
await fs.promises.rename(dir, `${dir}.bak`);
|
|
261
|
+
backedUpDirs.add(dir);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
cleanupStarted = true;
|
|
266
|
+
|
|
267
|
+
console.log(
|
|
268
|
+
'[INFO] [generateCode] STATUS=CLEANUP | Removing old generatedCode directories before generating new files...'
|
|
269
|
+
);
|
|
270
|
+
for (const dir of dirsToBackup) {
|
|
271
|
+
await fs.promises.rm(dir, { recursive: true, force: true });
|
|
272
|
+
}
|
|
273
|
+
|
|
233
274
|
// 2. Generation Pipeline
|
|
234
275
|
await generateEndpointsFlow(allSchemas, generatorOptions);
|
|
235
276
|
await generateFlowMainFunction(allSchemas, generatorOptions);
|
|
@@ -263,8 +304,28 @@ export async function generateCode(rootPath, options = {}) {
|
|
|
263
304
|
await generateSharedResourceYaml(allSchemas, generatorOptions);
|
|
264
305
|
await generateInitialSetup(allSchemas, generatorOptions);
|
|
265
306
|
|
|
307
|
+
generationSucceeded = true;
|
|
266
308
|
|
|
267
309
|
} finally {
|
|
310
|
+
if (generationSucceeded) {
|
|
311
|
+
console.log('[INFO] [generateCode] STATUS=CLEANUP_BAK | Generation successful, removing .bak directories...');
|
|
312
|
+
for (const dir of dirsToBackup) {
|
|
313
|
+
if (fs.existsSync(`${dir}.bak`)) {
|
|
314
|
+
await fs.promises.rm(`${dir}.bak`, { recursive: true, force: true });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
} else if (cleanupStarted) {
|
|
318
|
+
console.log('[INFO] [generateCode] STATUS=FAILED | Generation failed, rolling back directories...');
|
|
319
|
+
for (const dir of dirsToBackup) {
|
|
320
|
+
if (fs.existsSync(dir)) {
|
|
321
|
+
await fs.promises.rm(dir, { recursive: true, force: true });
|
|
322
|
+
}
|
|
323
|
+
if (backedUpDirs.has(dir) && fs.existsSync(`${dir}.bak`)) {
|
|
324
|
+
await fs.promises.rename(`${dir}.bak`, dir);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
268
329
|
// Restore backed-up hooks
|
|
269
330
|
if (hookDirs.size > 0 || hookBackups.length > 0) {
|
|
270
331
|
console.log(
|
|
@@ -279,14 +340,19 @@ export async function generateCode(rootPath, options = {}) {
|
|
|
279
340
|
}
|
|
280
341
|
}
|
|
281
342
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
343
|
+
if (generationSucceeded) {
|
|
344
|
+
console.log(
|
|
345
|
+
'[INFO] [generateCode] STATUS=HOOKS | Processing hook folders beside source folders...'
|
|
346
|
+
);
|
|
347
|
+
processSourceHooks(generatorOptions.outputPath);
|
|
286
348
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
349
|
+
console.log(
|
|
350
|
+
'[INFO] [generateCode] STATUS=FINISHED | Code generation complete.'
|
|
351
|
+
);
|
|
352
|
+
} else {
|
|
353
|
+
console.log(
|
|
354
|
+
'[INFO] [generateCode] STATUS=FAILED | Code generation failed. Hook files were restored, but processSourceHooks was skipped.'
|
|
355
|
+
);
|
|
356
|
+
}
|
|
290
357
|
}
|
|
291
358
|
}
|
|
292
|
-
|
|
@@ -41,50 +41,7 @@ export async function generateDynamicFlowSchemas(
|
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
const validMainEvents = [
|
|
45
|
-
'ownTopic',
|
|
46
|
-
'extTopic',
|
|
47
|
-
'dsq',
|
|
48
|
-
's3',
|
|
49
|
-
'eventBridge',
|
|
50
|
-
'lambdaSyncInv',
|
|
51
|
-
'lambdaSyncApi'
|
|
52
|
-
];
|
|
53
|
-
const validStepEvents = [
|
|
54
|
-
'sqs',
|
|
55
|
-
'extTopic',
|
|
56
|
-
'dsq',
|
|
57
|
-
'lambdaSyncInv',
|
|
58
|
-
'lambdaSyncApi',
|
|
59
|
-
'queue'
|
|
60
|
-
];
|
|
61
44
|
|
|
62
|
-
if (flowData && flowData.flows) {
|
|
63
|
-
for (const flow of flowData.flows) {
|
|
64
|
-
const flowName = flow.flowTag || 'UnknownFlow';
|
|
65
|
-
// Check main flow events
|
|
66
|
-
if (flow.event && Array.isArray(flow.event)) {
|
|
67
|
-
for (const ev of flow.event) {
|
|
68
|
-
if (!validMainEvents.includes(ev)) {
|
|
69
|
-
throw new Error(`[FlowSchemaGenerator] Invalid event type '${ev}' in main flow '${flowName}'. Allowed: ${validMainEvents.join(', ')}`);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
// Check flow steps events
|
|
74
|
-
if (flow.flowSteps && Array.isArray(flow.flowSteps)) {
|
|
75
|
-
for (const step of flow.flowSteps) {
|
|
76
|
-
const stepName = step.stepName || 'UnknownStep';
|
|
77
|
-
if (step.event && Array.isArray(step.event)) {
|
|
78
|
-
for (const ev of step.event) {
|
|
79
|
-
if (!validStepEvents.includes(ev)) {
|
|
80
|
-
throw new Error(`[FlowSchemaGenerator] Invalid event type '${ev}' in flow '${flowName}' step '${stepName}'. Allowed: ${validStepEvents.join(', ')}`);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
45
|
|
|
89
46
|
// Ensure directory exists
|
|
90
47
|
await fs.mkdir(schemasOutputDir, { recursive: true });
|
package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicFlowSchemaTemplate.ejs
CHANGED
|
@@ -17,14 +17,14 @@ export default {
|
|
|
17
17
|
},
|
|
18
18
|
flowSteps: [
|
|
19
19
|
{
|
|
20
|
-
stepName: '
|
|
20
|
+
stepName: 'start',
|
|
21
21
|
properties: [],
|
|
22
22
|
messageAttributes: ['userId', 'correlationId']
|
|
23
23
|
},
|
|
24
24
|
<% if (hasBeforeLogicalStep) { %>
|
|
25
25
|
{
|
|
26
26
|
stepName: 'BeforeLogical',
|
|
27
|
-
event: ['
|
|
27
|
+
event: ['extTopic'],
|
|
28
28
|
properties: [],
|
|
29
29
|
messageAttributes: ['userId', 'correlationId']
|
|
30
30
|
},
|
|
@@ -32,7 +32,7 @@ export default {
|
|
|
32
32
|
<% if (['Create', 'Update', 'Delete'].includes(action)) { %>
|
|
33
33
|
{
|
|
34
34
|
stepName: 'Complete',
|
|
35
|
-
event: ['
|
|
35
|
+
event: ['extTopic'],
|
|
36
36
|
properties: [],
|
|
37
37
|
messageAttributes: ['userId', 'correlationId']
|
|
38
38
|
}
|
|
@@ -17,14 +17,14 @@ export default {
|
|
|
17
17
|
},
|
|
18
18
|
flowSteps: [
|
|
19
19
|
{
|
|
20
|
-
stepName: '
|
|
20
|
+
stepName: 'start',
|
|
21
21
|
properties: [],
|
|
22
22
|
messageAttributes: ['userId', 'correlationId']
|
|
23
23
|
}
|
|
24
24
|
<% if (hasCompleteStep) { %>,
|
|
25
25
|
{
|
|
26
26
|
stepName: 'Complete',
|
|
27
|
-
event: ['
|
|
27
|
+
event: ['extTopic'],
|
|
28
28
|
properties: [],
|
|
29
29
|
messageAttributes: ['userId', 'correlationId']
|
|
30
30
|
}
|