@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.
Files changed (52) hide show
  1. package/package.json +11 -8
  2. package/src/codeGenerators/app/initial_setup/InitialSetupGenerator.js +100 -150
  3. package/src/codeGenerators/app/initial_setup/templates/InitialSetup_LambdaRole.ejs +27 -20
  4. package/src/codeGenerators/app/sls_yaml/FunctionYamlGenerator.js +12 -22
  5. package/src/codeGenerators/app/sls_yaml/RoleNameConfigGenerator.js +13 -72
  6. package/src/codeGenerators/app/sls_yaml/_policy/PolicyEmitter.js +3 -2
  7. package/src/codeGenerators/app/sls_yaml/_policy/__tests__/PolicyEmitter.test.js +3 -3
  8. package/src/codeGenerators/app/sls_yaml/_policy/templates/ManagedPolicy_Yaml.ejs +2 -2
  9. package/src/codeGenerators/app/sls_yaml/templates/SharedResource_Yaml.ejs +1 -1
  10. package/src/codeGenerators/app/src/generatedCode/Flow/FlowObjects/EndpointsGenerator.js +3 -2
  11. package/src/codeGenerators/app/src/generatedCode/Flow/FlowRbac/RbacFlowGenerator.js +4 -0
  12. package/src/codeGenerators/app/src/generatedCode/Flow/FlowRelationships/RelationshipFlowGenerator.js +2 -1
  13. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowEndpoint_HdrWbs.ejs +2 -2
  14. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowEndpoint_Main.ejs +8 -4
  15. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/endpoint/FlowMain_Wbs.ejs +4 -4
  16. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/s3/CreatePreSignUrl_Main.ejs +1 -1
  17. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/s3/ProcessS3File_Main.ejs +1 -1
  18. package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/templates/step/FlowEndpointComplete_Main.ejs +11 -12
  19. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseDsqHandler.js +55 -0
  20. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/BaseSqsHandler.js +20 -21
  21. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/__tests__/BaseDsqHandler.test.js +93 -0
  22. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/dsq.js +5 -0
  23. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncApi.js +10 -19
  24. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/lambdaSyncInv.js +10 -19
  25. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/ownTopic.js +16 -4
  26. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/events/registry.js +3 -1
  27. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrApi.ejs +3 -3
  28. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrDsq.ejs +137 -0
  29. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrInv.ejs +3 -3
  30. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/handlers/FlowHandler_HdrSqs.ejs +62 -51
  31. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/eventTypes.js +2 -2
  32. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowEntryPointBase.js +8 -4
  33. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowMainFunctionBase.js +28 -36
  34. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowNaming.js +16 -0
  35. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowSelection.js +0 -1
  36. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepBase.js +11 -5
  37. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowStepNormalizer.js +48 -1
  38. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowValidator.js +7 -21
  39. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/propertyNormalizer.js +20 -0
  40. package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/roleNameDetection.js +49 -0
  41. package/src/codeGenerators/app/src/generatedCode/libs/templates/Consts.ejs +10 -10
  42. package/src/codeGenerators/resource/sls_yaml/DynamoDBGenerator.js +1 -10
  43. package/src/codeGenerators/resource/sls_yaml/FlowOutGenerator.js +1 -10
  44. package/src/codeGenerators/resource/sls_yaml/FlowResourceYamlGenerator.js +3 -12
  45. package/src/core/validate.js +9 -5
  46. package/src/generate.js +1 -1
  47. package/src/generateCode.js +90 -24
  48. package/src/schemaGenerators/app/src/schemas/FlowSchemas/FlowSchemaGenerator.js +0 -43
  49. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicFlowSchemaTemplate.ejs +3 -3
  50. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/DynamicRbacFlowSchemaTemplate.ejs +1 -1
  51. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/RelationshipFlowSchemaTemplate.ejs +2 -2
  52. package/src/schemaGenerators/app/src/schemas/FlowSchemas/templates/UserRbacFlowSchemaTemplate.ejs +1 -1
@@ -1,6 +1,5 @@
1
- const ALLOWED_MAIN_EVENTS = ['ownTopic', 'extTopic', 'lambdaSyncInv', 'lambdaSyncApi', 'eventBridge', 's3', 'dsq'];
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 => !ALLOWED_MAIN_EVENTS.includes(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: ${ALLOWED_MAIN_EVENTS.join(', ')}.`);
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 => !ALLOWED_STEP_EVENTS.includes(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: ${ALLOWED_STEP_EVENTS.join(', ')}.`);
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
- if (inTagCount > 1) {
125
- errors.push(`Flow '${flowTag}' has multiple 'InTag' steps. Only one is allowed.`);
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: 'UpdateRelationshipComplete',
320
- createRelComplete: 'CreateRelationshipComplete',
321
- deleteRelComplete: 'DeleteRelationshipComplete',
322
- getRelComplete: 'GetRelationshipComplete',
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: 'UpdateComplete',
327
+ updateNodeComplete: 'UpdateCompleteHdrSqs',
328
328
  outUpdateNodeComplete: 'Update_Out',
329
- createNodeComplete: 'CreateComplete',
329
+ createNodeComplete: 'CreateCompleteHdrSqs',
330
330
  outCreateNodeComplete: 'Create_Out',
331
- deleteNodeComplete: 'DeleteComplete',
331
+ deleteNodeComplete: 'DeleteCompleteHdrSqs',
332
332
  outDeleteNodeComplete: 'Delete_Out',
333
- getNodeComplete: 'GetComplete',
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: 'ChangeRelationshipComplete',
340
+ changeRelationshipComplete: 'ChangeRelationshipCompleteHdrSqs',
341
341
  outChangeRelationshipComplete: 'ChangeRelationship_Out',
342
- moveRelationshipComplete: '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
- await appendToFile(yamlOutputPath, yamlContent);
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 appendToFile(yamlOutputPath, yamlContent);
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 appendToFile(yamlOutputPath, yamlContent);
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 === 'InTag') continue;
73
+ if (stepName?.toLowerCase() === 'start') continue;
83
74
 
84
- const events = stepConfig.event || ['sqs'];
75
+ const events = stepConfig.event || ['extTopic'];
85
76
  for (const ev of events) {
86
77
  let queueName = `${flowTag}${stepName}`;
87
78
  if (stepName === 'Complete') {
@@ -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 (!iz_serviceTag) {
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(iz_serviceTag)) {
33
- validateStatus.validateErrors.push(`iz_serviceTag '${iz_serviceTag}' must contain only alphanumeric characters ([a-zA-Z0-9]).`);
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 = `TST_${flow.flowTag}`; // Example prefix logic
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
@@ -87,7 +87,7 @@ async function generate(rootPath, options = {}) {
87
87
  }
88
88
 
89
89
  const startTime = Date.now();
90
- await generateCode(rootPath, { outputPath });
90
+ await generateCode(rootPath, { outputPath, configPath });
91
91
  return { durationMs: Date.now() - startTime };
92
92
  }
93
93
 
@@ -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
- console.log(
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
- console.log(
283
- '[INFO] [generateCode] STATUS=HOOKS | Processing hook folders beside source folders...'
284
- );
285
- processSourceHooks(generatorOptions.outputPath);
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
- console.log(
288
- '[INFO] [generateCode] STATUS=FINISHED | Code generation complete.'
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 });
@@ -17,14 +17,14 @@ export default {
17
17
  },
18
18
  flowSteps: [
19
19
  {
20
- stepName: 'InTag',
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: ['sqs'],
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: ['sqs'],
35
+ event: ['extTopic'],
36
36
  properties: [],
37
37
  messageAttributes: ['userId', 'correlationId']
38
38
  }
@@ -12,7 +12,7 @@ export default {
12
12
  outputTopic: true,
13
13
  flowSteps: [
14
14
  {
15
- stepName: 'InTag',
15
+ stepName: 'start',
16
16
  properties: [],
17
17
  messageAttributes: []
18
18
  }
@@ -17,14 +17,14 @@ export default {
17
17
  },
18
18
  flowSteps: [
19
19
  {
20
- stepName: 'InTag',
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: ['sqs'],
27
+ event: ['extTopic'],
28
28
  properties: [],
29
29
  messageAttributes: ['userId', 'correlationId']
30
30
  }
@@ -15,7 +15,7 @@ export default {
15
15
  },
16
16
  steps: [
17
17
  {
18
- stepName: 'InTag',
18
+ stepName: 'start',
19
19
  messageAttributes: ['userId', 'correlationId']
20
20
  }
21
21
  ]