@izara_project/izara-core-generate-service-code 1.0.56 → 1.0.58
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 +1 -1
- package/src/__tests__/getIntTestConfig.test.js +49 -0
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowRelationships/RelationshipFlowGenerator.js +18 -2
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowRelationships/templates/relationship/ProcessCreateRelationship_Main.ejs +1 -1
- package/src/codeGenerators/app/src/generatedCode/Flow/FlowSchemas/WebSocketGenerator.js +4 -4
- package/src/codeGenerators/app/src/generatedCode/Flow/_shared/shared/flowValidator.js +1 -1
- package/src/codeGenerators/resource/sls_yaml/FlowOutGenerator.js +0 -1
- package/src/codeGenerators/resource/sls_yaml/FlowResourceYamlGenerator.js +4 -1
- package/src/generate.js +40 -40
- package/src/generateCode.js +82 -70
- package/src/generateIntTest.js +11 -0
- package/src/generateResourceIntTest.js +9 -0
- package/src/generateResources/IntTest/generateTests/generateTemplate.js +1 -1
- package/src/generateResources/IntTest/libs/libs.js +10 -2
- package/src/generateSchema.js +78 -18
- package/src/schemaGenerators/app/src/schemas/FlowSchemas/RelationshipFlowSchemaGenerator.js +2 -2
- package/src/schemaGenerators/app/src/schemas/ObjectSchemas/templates/DynamicAttributeTreeStandardSchemaTemplate.ejs +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@izara_project/izara-core-generate-service-code",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.58",
|
|
4
4
|
"description": "Code for locally generating per service files",
|
|
5
5
|
"homepage": "https://bitbucket.org/izara-core-support-services/izara-core-support-services-generate-service-code#readme",
|
|
6
6
|
"repository": {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { describe, it, expect, jest, beforeEach, afterEach } from '@jest/globals';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import libs from '../generateResources/IntTest/libs/libs.js';
|
|
5
|
+
import { getObjectSchema } from '@izara_project/izara-core-library-service-schemas';
|
|
6
|
+
|
|
7
|
+
const { getIntTestConfig } = libs;
|
|
8
|
+
|
|
9
|
+
describe('getIntTestConfig path resolution', () => {
|
|
10
|
+
let originalGetDataFromPath;
|
|
11
|
+
let passedArgs;
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
originalGetDataFromPath = getObjectSchema.getDataFromPath;
|
|
15
|
+
passedArgs = null;
|
|
16
|
+
getObjectSchema.getDataFromPath = async (_izContext, folderName, rootFolderPath, options) => {
|
|
17
|
+
passedArgs = { folderName, rootFolderPath, options };
|
|
18
|
+
return [];
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
getObjectSchema.getDataFromPath = originalGetDataFromPath;
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('Case 1: should resolve correctly when rootPath includes generateIntegrationTest', async () => {
|
|
27
|
+
const rootPath = '/home/ironfist/Pong/Izara Project/Izara-Market-Product/izara-market-products-sell-offer-price-pong/app/src/generateIntegrationTest/';
|
|
28
|
+
await getIntTestConfig({ logger: console }, 'generateIntegrationTest/libs/', rootPath);
|
|
29
|
+
|
|
30
|
+
expect(passedArgs).not.toBeNull();
|
|
31
|
+
expect(passedArgs.folderName).toBe('libs');
|
|
32
|
+
expect(passedArgs.rootFolderPath).toBe('/home/ironfist/Pong/Izara Project/Izara-Market-Product/izara-market-products-sell-offer-price-pong/app/src/generateIntegrationTest');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('Case 2: should resolve correctly when rootPath is repo root with app/src/... structure', async () => {
|
|
36
|
+
const tmpDir = path.join(process.cwd(), 'src', '__tests__', 'scratch_tmp');
|
|
37
|
+
fs.mkdirSync(path.join(tmpDir, 'app', 'src', 'generateIntegrationTest', 'libs'), { recursive: true });
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await getIntTestConfig({ logger: console }, 'generateIntegrationTest/libs/', tmpDir);
|
|
41
|
+
|
|
42
|
+
expect(passedArgs).not.toBeNull();
|
|
43
|
+
expect(passedArgs.folderName).toBe('libs');
|
|
44
|
+
expect(passedArgs.rootFolderPath).toBe(path.join(tmpDir, 'app', 'src', 'generateIntegrationTest'));
|
|
45
|
+
} finally {
|
|
46
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
package/src/codeGenerators/app/src/generatedCode/Flow/FlowRelationships/RelationshipFlowGenerator.js
CHANGED
|
@@ -56,8 +56,13 @@ export async function generateRelationshipFlows(allSchemas, options) {
|
|
|
56
56
|
console.warn(` [RelationshipFlowGenerator] Warning: Action template not found for ${flowTag}. Skipping.`);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
// 2. Generate Action Step Handlers
|
|
60
|
-
const
|
|
59
|
+
// 2. Generate Action Step Handlers
|
|
60
|
+
const flowSchema = allSchemas.flows?.find(f => f.flowTag === flowTag);
|
|
61
|
+
let events = ['ownTopic'];
|
|
62
|
+
if (flowSchema && flowSchema.event) {
|
|
63
|
+
events = Array.isArray(flowSchema.event) ? flowSchema.event : [flowSchema.event];
|
|
64
|
+
}
|
|
65
|
+
|
|
61
66
|
for (const event of events) {
|
|
62
67
|
let handlerFileName, handlerContent, handlerFunctionName;
|
|
63
68
|
if (event === 'ownTopic' || event === 'extTopic') {
|
|
@@ -91,6 +96,17 @@ export async function generateRelationshipFlows(allSchemas, options) {
|
|
|
91
96
|
});
|
|
92
97
|
await fs.writeFile(path.join(flowOutputDir, `${handlerFunctionName}.js`), handlerContent, 'utf-8');
|
|
93
98
|
generatedCount++;
|
|
99
|
+
} else if (event === 'dsq') {
|
|
100
|
+
handlerFunctionName = `${processFunction}_HdrDsq`;
|
|
101
|
+
handlerContent = ejs.render(templates.sqs, {
|
|
102
|
+
flowTag: flowTag,
|
|
103
|
+
flowStepName: '',
|
|
104
|
+
mainFileName: actionFileName,
|
|
105
|
+
functionName: processFunction,
|
|
106
|
+
queueName: `${processFunction}HdrDsq`
|
|
107
|
+
});
|
|
108
|
+
await fs.writeFile(path.join(flowOutputDir, `${handlerFunctionName}.js`), handlerContent, 'utf-8');
|
|
109
|
+
generatedCount++;
|
|
94
110
|
}
|
|
95
111
|
}
|
|
96
112
|
|
|
@@ -22,7 +22,7 @@ export async function generateWebSocket(allLocalFlowSchemas, appPath, outputBase
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
console.log(' [WebSocketGenerator] Generated WebSocket files with
|
|
25
|
+
console.log(' [WebSocketGenerator] Generated WebSocket files with WebSocketComplete_Main convention.');
|
|
26
26
|
const templatesDir = path.join(__dirname, 'templates', 'webSocket');
|
|
27
27
|
const wsDir = path.join(outputBaseDir, 'app', 'src', 'generatedCode', 'SystemFlowSchemas', 'WebSocketMain', 'source');
|
|
28
28
|
await fs.mkdir(wsDir, { recursive: true });
|
|
@@ -79,8 +79,8 @@ export async function generateWebSocket(allLocalFlowSchemas, appPath, outputBase
|
|
|
79
79
|
upperFunctionName: upperCase(wsConnectName)
|
|
80
80
|
});
|
|
81
81
|
|
|
82
|
-
// 3.
|
|
83
|
-
const processFunctionName = '
|
|
82
|
+
// 3. WebSocketComplete
|
|
83
|
+
const processFunctionName = 'WebSocketComplete';
|
|
84
84
|
const processHandlerType = 'HdrSqs';
|
|
85
85
|
const queueName = processFunctionName + shortNameHandler(processHandlerType);
|
|
86
86
|
|
|
@@ -114,5 +114,5 @@ export async function generateWebSocket(allLocalFlowSchemas, appPath, outputBase
|
|
|
114
114
|
}
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
console.log(` [WebSocketGenerator] Generated WebSocket files with
|
|
117
|
+
console.log(` [WebSocketGenerator] Generated WebSocket files with WebSocketComplete_Main convention.`);
|
|
118
118
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const ALLOWED_MAIN_EVENTS = ['ownTopic', 'extTopic', 'lambdaSyncInv', 'lambdaSyncApi', 'eventBridge', 's3'];
|
|
1
|
+
const ALLOWED_MAIN_EVENTS = ['ownTopic', 'extTopic', 'lambdaSyncInv', 'lambdaSyncApi', 'eventBridge', 's3', 'dsq'];
|
|
2
2
|
const MAIN_ASYNC_EVENTS = ['ownTopic', 'extTopic', 's3'];
|
|
3
3
|
const ALLOWED_STEP_EVENTS = ['sqs', 'ownTopic', 'extTopic', 'dsq', 'lambdaSyncInv', 'lambdaSyncApi'];
|
|
4
4
|
const RESERVED_SYSTEM_FLOW_TAGS = new Set([
|
|
@@ -135,6 +135,7 @@ export async function generateFlowResourceYaml(allSchemas, options) {
|
|
|
135
135
|
'CreateRelationship',
|
|
136
136
|
'UpdateRelationship',
|
|
137
137
|
'DeleteRelationship',
|
|
138
|
+
'GetRelationship',
|
|
138
139
|
'ChangeRelationship',
|
|
139
140
|
'MoveRelationship',
|
|
140
141
|
'CreateTargetRole',
|
|
@@ -163,6 +164,9 @@ export async function generateFlowResourceYaml(allSchemas, options) {
|
|
|
163
164
|
await appendUnique(queueName, resourceSqsYamlTpl, { queueName });
|
|
164
165
|
}
|
|
165
166
|
|
|
167
|
+
// ponytail: add Dsq queue for PaginateProcessLogical
|
|
168
|
+
await appendUnique('PaginateProcessLogicalHdrDsq', resourceSqsYamlTpl, { queueName: 'PaginateProcessLogicalHdrDsq' });
|
|
169
|
+
|
|
166
170
|
const systemQueues = [
|
|
167
171
|
'Register',
|
|
168
172
|
'WebSocket',
|
|
@@ -170,7 +174,6 @@ export async function generateFlowResourceYaml(allSchemas, options) {
|
|
|
170
174
|
'UpdateComplete',
|
|
171
175
|
'DeleteComplete',
|
|
172
176
|
'GetComplete',
|
|
173
|
-
'GetRelationship',
|
|
174
177
|
'CreateRelationshipComplete',
|
|
175
178
|
'UpdateRelationshipComplete',
|
|
176
179
|
'DeleteRelationshipComplete',
|
package/src/generate.js
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
Copyright (C) 2020 Sven Mason <http://izara.io>
|
|
3
3
|
|
|
4
4
|
This program is free software: you can redistribute it and/or modify
|
|
5
|
-
it under the terms of the GNU Affero General Public License as
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
it under the terms of the GNU Affero General Public License as published by
|
|
6
|
+
the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
(at your option) any later version.
|
|
8
8
|
|
|
9
9
|
This program is distributed in the hope that it will be useful,
|
|
10
10
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
-
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
11
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
12
|
GNU Affero General Public License for more details.
|
|
13
13
|
|
|
14
14
|
You should have received a copy of the GNU Affero General Public License
|
|
15
|
-
along with this program.
|
|
15
|
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { join } from 'path';
|
|
@@ -24,53 +24,51 @@ import { generateSchema } from './generateSchema.js';
|
|
|
24
24
|
const { validateLocalSchema } = validator;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* High-level
|
|
28
|
-
*
|
|
27
|
+
* High-level API to validate schemas and generate code.
|
|
28
|
+
* Wires legacy Logger and validateLocalSchema.
|
|
29
29
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* @param {string}
|
|
34
|
-
* @param {
|
|
35
|
-
* @param {
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {boolean} [options.
|
|
38
|
-
* @param {
|
|
39
|
-
* @
|
|
40
|
-
* @returns {Promise<{ durationMs: number }>}
|
|
30
|
+
* @param {string} rootPath - The root path of the project.
|
|
31
|
+
* @param {object} [options={}] - Configuration options.
|
|
32
|
+
* @param {object} [options.logger] - Logger instance.
|
|
33
|
+
* @param {string} [options.configPath] - Path to the serverless configuration file.
|
|
34
|
+
* @param {string} [options.outputPath] - Output directory path.
|
|
35
|
+
* @param {boolean} [options.skipValidation=false] - Whether to skip schema validation.
|
|
36
|
+
* @param {boolean} [options.skipGenerateSchema=false] - Whether to skip schema generation.
|
|
37
|
+
* @param {boolean} [options.skipGenerateCode=false] - Whether to skip code generation.
|
|
38
|
+
* @param {Array} [options.schemaTemplateConfig] - Schema template configuration.
|
|
39
|
+
* @returns {Promise<{durationMs: number}>} Generation duration details.
|
|
41
40
|
*/
|
|
42
41
|
async function generate(rootPath, options = {}) {
|
|
43
42
|
const {
|
|
44
43
|
logger = createSilentLogger(),
|
|
45
44
|
configPath = join(rootPath, 'config', 'serverless.config.yml'),
|
|
45
|
+
outputPath = rootPath,
|
|
46
46
|
skipValidation = false,
|
|
47
47
|
skipGenerateSchema = false,
|
|
48
|
-
|
|
48
|
+
skipGenerateCode = false,
|
|
49
|
+
schemaTemplateConfig
|
|
49
50
|
} = options;
|
|
50
51
|
|
|
51
52
|
const schemasPath = join(rootPath, 'app', 'src', 'schemas');
|
|
52
53
|
|
|
53
|
-
// Auto-generate dynamic schemas before validation (unless skipped)
|
|
54
54
|
if (!skipGenerateSchema) {
|
|
55
|
-
await generateSchema(rootPath, {
|
|
55
|
+
await generateSchema(rootPath, {
|
|
56
|
+
configPath,
|
|
57
|
+
outputPath,
|
|
58
|
+
schemaTemplateConfig
|
|
59
|
+
});
|
|
56
60
|
}
|
|
57
61
|
|
|
58
|
-
// Validate schemas before generating (unless skipped)
|
|
59
62
|
if (!skipValidation) {
|
|
60
63
|
patchFlowStepsValidationSchema();
|
|
61
64
|
|
|
62
|
-
console.info('[INFO] [generate]
|
|
65
|
+
console.info('[INFO] [generate] STATUS=VALIDATING | local schemas...');
|
|
63
66
|
const validateStartTime = Date.now();
|
|
64
|
-
|
|
65
|
-
const result = await validateLocalSchema(
|
|
66
|
-
{ logger },
|
|
67
|
-
schemasPath,
|
|
68
|
-
configPath
|
|
69
|
-
);
|
|
70
|
-
|
|
67
|
+
const result = await validateLocalSchema({ logger }, schemasPath, configPath);
|
|
71
68
|
const validateDuration = Date.now() - validateStartTime;
|
|
69
|
+
|
|
72
70
|
console.info(
|
|
73
|
-
`[INFO] [generate]
|
|
71
|
+
`[INFO] [generate] STATUS=VALIDATED | errors=${result.validateErrors.length} | elapsedMs=${validateDuration}`
|
|
74
72
|
);
|
|
75
73
|
|
|
76
74
|
if (result.validateErrors.length > 0) {
|
|
@@ -84,20 +82,26 @@ async function generate(rootPath, options = {}) {
|
|
|
84
82
|
}
|
|
85
83
|
}
|
|
86
84
|
|
|
85
|
+
if (skipGenerateCode) {
|
|
86
|
+
return { durationMs: 0 };
|
|
87
|
+
}
|
|
88
|
+
|
|
87
89
|
const startTime = Date.now();
|
|
88
90
|
await generateCode(rootPath, { outputPath });
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
return { durationMs };
|
|
91
|
+
return { durationMs: Date.now() - startTime };
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
function patchFlowStepsValidationSchema() {
|
|
95
95
|
const flowStepsSchema = basicValidatorSchema.basicFlowSchema?.properties?.flowSteps;
|
|
96
|
+
if (!flowStepsSchema || flowStepsSchema.oneOf) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
96
99
|
|
|
97
|
-
if (!flowStepsSchema || flowStepsSchema.oneOf) return;
|
|
98
100
|
const objectStepSchema = {
|
|
99
101
|
...flowStepsSchema.items,
|
|
100
|
-
required: (flowStepsSchema.items.required || []).filter(
|
|
102
|
+
required: (flowStepsSchema.items.required || []).filter(
|
|
103
|
+
item => item !== 'stepName'
|
|
104
|
+
)
|
|
101
105
|
};
|
|
102
106
|
|
|
103
107
|
basicValidatorSchema.basicFlowSchema.properties.flowSteps = {
|
|
@@ -114,10 +118,6 @@ function patchFlowStepsValidationSchema() {
|
|
|
114
118
|
};
|
|
115
119
|
}
|
|
116
120
|
|
|
117
|
-
/**
|
|
118
|
-
* Creates a silent logger that only outputs errors.
|
|
119
|
-
* @returns {Object} Logger interface
|
|
120
|
-
*/
|
|
121
121
|
function createSilentLogger() {
|
|
122
122
|
return {
|
|
123
123
|
debug: () => {},
|
package/src/generateCode.js
CHANGED
|
@@ -37,6 +37,14 @@ function createGeneratorOptions(rootPath, options = {}) {
|
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Low-level generator to generate application code files from all schemas.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} rootPath - The root path of the project.
|
|
44
|
+
* @param {object} [options={}] - Configuration options.
|
|
45
|
+
* @param {string} [options.outputPath] - Output directory path.
|
|
46
|
+
* @returns {Promise<void>}
|
|
47
|
+
*/
|
|
40
48
|
export async function generateCode(rootPath, options = {}) {
|
|
41
49
|
console.log(
|
|
42
50
|
'[INFO] [generateCode] STATUS=STARTING | Generating code files from all schemas...'
|
|
@@ -130,9 +138,62 @@ export async function generateCode(rootPath, options = {}) {
|
|
|
130
138
|
);
|
|
131
139
|
collectHookBackups(outputPath);
|
|
132
140
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
141
|
+
function processSourceHooks(currentDir) {
|
|
142
|
+
if (!fs.existsSync(currentDir)) return;
|
|
143
|
+
|
|
144
|
+
const items = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
145
|
+
const hasSource = items.some(
|
|
146
|
+
item => item.isDirectory() && item.name === 'source'
|
|
147
|
+
);
|
|
148
|
+
const hasHook = items.some(
|
|
149
|
+
item => item.isDirectory() && item.name === 'hook'
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
if (hasSource && hasHook) {
|
|
153
|
+
const sourcePath = path.join(currentDir, 'source');
|
|
154
|
+
const hookPath = path.join(currentDir, 'hook');
|
|
155
|
+
const hookItems = fs.readdirSync(hookPath, { withFileTypes: true });
|
|
156
|
+
|
|
157
|
+
for (const hookItem of hookItems) {
|
|
158
|
+
if (!hookItem.isFile()) continue;
|
|
159
|
+
|
|
160
|
+
const hookFile = path.join(hookPath, hookItem.name);
|
|
161
|
+
const sourceFile = path.join(sourcePath, hookItem.name);
|
|
162
|
+
|
|
163
|
+
if (!fs.existsSync(sourceFile)) {
|
|
164
|
+
fs.copyFileSync(hookFile, sourceFile);
|
|
165
|
+
console.log(
|
|
166
|
+
`[INFO] [generateCode] HOOK APPLIED: Created ${hookItem.name} from hook.`
|
|
167
|
+
);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const mergedContent = mergeHookIntoSource(
|
|
172
|
+
fs.readFileSync(sourceFile, 'utf8'),
|
|
173
|
+
fs.readFileSync(hookFile, 'utf8'),
|
|
174
|
+
sourceFile
|
|
175
|
+
);
|
|
176
|
+
fs.writeFileSync(sourceFile, mergedContent);
|
|
177
|
+
console.log(
|
|
178
|
+
`[INFO] [generateCode] HOOK APPLIED: Merged ${hookItem.name} into source.`
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
} else if (hasSource) {
|
|
182
|
+
fs.mkdirSync(path.join(currentDir, 'hook'), { recursive: true });
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (const item of items) {
|
|
186
|
+
if (!item.isDirectory() || item.name === 'source' || item.name === 'hook')
|
|
187
|
+
continue;
|
|
188
|
+
processSourceHooks(path.join(currentDir, item.name));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// ponytail: ensure hooks are restored even if generation fails
|
|
193
|
+
try {
|
|
194
|
+
console.log(
|
|
195
|
+
'[INFO] [generateCode] STATUS=CLEANUP | Removing old generatedCode directories...'
|
|
196
|
+
);
|
|
136
197
|
await fs.promises.rm(generatedCodePath, {
|
|
137
198
|
recursive: true,
|
|
138
199
|
force: true
|
|
@@ -202,79 +263,30 @@ export async function generateCode(rootPath, options = {}) {
|
|
|
202
263
|
await generateSharedResourceYaml(allSchemas, generatorOptions);
|
|
203
264
|
await generateInitialSetup(allSchemas, generatorOptions);
|
|
204
265
|
|
|
205
|
-
// 3. Hook Integration
|
|
206
|
-
console.log(
|
|
207
|
-
'[INFO] [generateCode] STATUS=HOOKS | Processing hook folders beside source folders...'
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
function processSourceHooks(currentDir) {
|
|
211
|
-
if (!fs.existsSync(currentDir)) return;
|
|
212
266
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
for (const hookItem of hookItems) {
|
|
227
|
-
if (!hookItem.isFile()) continue;
|
|
228
|
-
|
|
229
|
-
const hookFile = path.join(hookPath, hookItem.name);
|
|
230
|
-
const sourceFile = path.join(sourcePath, hookItem.name);
|
|
231
|
-
|
|
232
|
-
if (!fs.existsSync(sourceFile)) {
|
|
233
|
-
fs.copyFileSync(hookFile, sourceFile);
|
|
234
|
-
console.log(
|
|
235
|
-
`[INFO] [generateCode] HOOK APPLIED: Created ${hookItem.name} from hook.`
|
|
236
|
-
);
|
|
237
|
-
continue;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const mergedContent = mergeHookIntoSource(
|
|
241
|
-
fs.readFileSync(sourceFile, 'utf8'),
|
|
242
|
-
fs.readFileSync(hookFile, 'utf8'),
|
|
243
|
-
sourceFile
|
|
244
|
-
);
|
|
245
|
-
fs.writeFileSync(sourceFile, mergedContent);
|
|
246
|
-
console.log(
|
|
247
|
-
`[INFO] [generateCode] HOOK APPLIED: Merged ${hookItem.name} into source.`
|
|
248
|
-
);
|
|
267
|
+
} finally {
|
|
268
|
+
// Restore backed-up hooks
|
|
269
|
+
if (hookDirs.size > 0 || hookBackups.length > 0) {
|
|
270
|
+
console.log(
|
|
271
|
+
`[INFO] [generateCode] STATUS=RESTORE | Restoring ${hookBackups.length} hook files...`
|
|
272
|
+
);
|
|
273
|
+
for (const relativeHookDir of hookDirs) {
|
|
274
|
+
fs.mkdirSync(path.join(outputPath, relativeHookDir), { recursive: true });
|
|
275
|
+
}
|
|
276
|
+
for (const backup of hookBackups) {
|
|
277
|
+
const hookDir = path.join(outputPath, backup.relativePath);
|
|
278
|
+
fs.writeFileSync(path.join(hookDir, backup.name), backup.content);
|
|
249
279
|
}
|
|
250
|
-
} else if (hasSource) {
|
|
251
|
-
fs.mkdirSync(path.join(currentDir, 'hook'), { recursive: true });
|
|
252
280
|
}
|
|
253
281
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
}
|
|
259
|
-
}
|
|
282
|
+
console.log(
|
|
283
|
+
'[INFO] [generateCode] STATUS=HOOKS | Processing hook folders beside source folders...'
|
|
284
|
+
);
|
|
285
|
+
processSourceHooks(generatorOptions.outputPath);
|
|
260
286
|
|
|
261
|
-
// Restore backed-up hooks
|
|
262
|
-
if (hookDirs.size > 0 || hookBackups.length > 0) {
|
|
263
287
|
console.log(
|
|
264
|
-
|
|
288
|
+
'[INFO] [generateCode] STATUS=FINISHED | Code generation complete.'
|
|
265
289
|
);
|
|
266
|
-
for (const relativeHookDir of hookDirs) {
|
|
267
|
-
fs.mkdirSync(path.join(outputPath, relativeHookDir), { recursive: true });
|
|
268
|
-
}
|
|
269
|
-
for (const backup of hookBackups) {
|
|
270
|
-
const hookDir = path.join(outputPath, backup.relativePath);
|
|
271
|
-
fs.writeFileSync(path.join(hookDir, backup.name), backup.content);
|
|
272
|
-
}
|
|
273
290
|
}
|
|
274
|
-
|
|
275
|
-
processSourceHooks(generatorOptions.outputPath);
|
|
276
|
-
|
|
277
|
-
console.log(
|
|
278
|
-
'[INFO] [generateCode] STATUS=FINISHED | Code generation complete.'
|
|
279
|
-
);
|
|
280
291
|
}
|
|
292
|
+
|
package/src/generateIntTest.js
CHANGED
|
@@ -105,6 +105,17 @@ async function writeSource({ templatePath, templateData, setting }, { checkCreat
|
|
|
105
105
|
return [true, {}];
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Result-Driven generator to generate Integration Tests.
|
|
110
|
+
* Does not have createSource/libs dependencies.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} rootPath - The root path of the project.
|
|
113
|
+
* @param {object} [options={}] - Configuration options.
|
|
114
|
+
* @param {string} [options.outputPath] - Output directory path.
|
|
115
|
+
* @param {string|null} [options.s3Prefix=null] - S3 key prefix for integration test resources.
|
|
116
|
+
* @param {object} [options._izContext] - Integration context containing logger interface.
|
|
117
|
+
* @returns {Promise<{serviceTag: string, count: number, durationMs: number}>}
|
|
118
|
+
*/
|
|
108
119
|
export async function generateIntTest(rootPath, options = {}) {
|
|
109
120
|
const {
|
|
110
121
|
outputPath = rootPath,
|
|
@@ -45,6 +45,15 @@ function writeSource({ templatePath, templateData, setting }, { checkCreateSourc
|
|
|
45
45
|
return [true, {}];
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Result-Driven generator to generate Integration Test resources.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} rootPath - The root path of the project.
|
|
52
|
+
* @param {object} [options={}] - Configuration options.
|
|
53
|
+
* @param {string} [options.outputPath] - Output directory path.
|
|
54
|
+
* @param {object} [options._izContext] - Integration context containing logger interface.
|
|
55
|
+
* @returns {Promise<{serviceTag: string, count: number, durationMs: number}>}
|
|
56
|
+
*/
|
|
48
57
|
export async function generateResourceIntTest(rootPath, options = {}) {
|
|
49
58
|
const {
|
|
50
59
|
outputPath = rootPath,
|
|
@@ -44,7 +44,7 @@ async function generateCodeWithTemplate(_izContext, savePath, settings) {
|
|
|
44
44
|
let allIntTestConfig = await getIntTestConfig(
|
|
45
45
|
_izContext,
|
|
46
46
|
'generateIntegrationTest/libs/',
|
|
47
|
-
rootPath
|
|
47
|
+
rootPath || savePath
|
|
48
48
|
);
|
|
49
49
|
const event = await generateEvent(_izContext, allIntTestConfig, savePath, serviceTag);
|
|
50
50
|
const test = await generateTest(_izContext, allIntTestConfig, savePath, serviceTag);
|
|
@@ -393,10 +393,18 @@ function createResourceName(eventTag) {
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
async function getIntTestConfig(_izContext, intTestPath, rootPath) {
|
|
396
|
+
// ponytail: find first existing integration test libs folder or fall back to standard path
|
|
397
|
+
const targetLibsPath = rootPath.includes('generateIntegrationTest')
|
|
398
|
+
? path.join(rootPath.split('generateIntegrationTest')[0], 'generateIntegrationTest', 'libs')
|
|
399
|
+
: [
|
|
400
|
+
path.join(rootPath, 'app/src/generateIntegrationTest/libs'),
|
|
401
|
+
path.join(rootPath, 'src/generateIntegrationTest/libs')
|
|
402
|
+
].find(fs.existsSync) || path.join(rootPath, 'src/generateIntegrationTest/libs');
|
|
403
|
+
|
|
396
404
|
return await getObjectSchema.getDataFromPath(
|
|
397
405
|
_izContext,
|
|
398
|
-
|
|
399
|
-
path.
|
|
406
|
+
path.basename(targetLibsPath),
|
|
407
|
+
path.dirname(targetLibsPath),
|
|
400
408
|
{ exceptFileName: 'saveConfig' }
|
|
401
409
|
);
|
|
402
410
|
}
|
package/src/generateSchema.js
CHANGED
|
@@ -15,34 +15,36 @@ import { generatePropertyValueRelationshipSchemas } from './schemaGenerators/app
|
|
|
15
15
|
import { generateAttributeTreeObjectSchemas } from './schemaGenerators/app/src/schemas/ObjectSchemas/AttributeTreeSchemaGenerator.js';
|
|
16
16
|
import { generateAttributeTreeRelationshipSchemas } from './schemaGenerators/app/src/schemas/RelationshipSchemas/AttributeTreeRelationshipSchemaGenerator.js';
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Low-level generator to generate dynamic schemas (FlowSchema, etc.).
|
|
20
|
+
*
|
|
21
|
+
* @param {string} rootPath - The root path of the project.
|
|
22
|
+
* @param {object} [options={}] - Configuration options.
|
|
23
|
+
* @param {string} [options.configPath] - Path to the serverless configuration file.
|
|
24
|
+
* @param {string} [options.outputPath] - Output directory path.
|
|
25
|
+
* @param {Array} [options.schemaTemplateConfig] - Schema template configuration.
|
|
26
|
+
* @returns {Promise<void>}
|
|
27
|
+
*/
|
|
28
|
+
export async function generateSchema(rootPath, options = {}) {
|
|
19
29
|
console.log(
|
|
20
30
|
'[INFO] [generateSchema] STATUS=STARTING | Generating dynamic schemas (FlowSchema, etc.)...'
|
|
21
31
|
);
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
try {
|
|
27
|
-
const serviceConfigStr = fs.readFileSync(configPath, 'utf8');
|
|
28
|
-
const serviceConfig = yaml.parse(serviceConfigStr);
|
|
29
|
-
serviceTag = serviceConfig?.main_config?.iz_serviceTag || 'unknownService';
|
|
30
|
-
} catch (e) {
|
|
31
|
-
console.warn(
|
|
32
|
-
`[WARNING] Could not read service config at ${configPath}. Using default serviceTag.`
|
|
33
|
-
);
|
|
34
|
-
}
|
|
33
|
+
const configPath =
|
|
34
|
+
options.configPath || path.join(rootPath, 'config', 'serverless.config.yml');
|
|
35
|
+
const serviceTag = readServiceTag(configPath);
|
|
35
36
|
|
|
36
|
-
// Phase 1 parsing just to get the raw data needed for generation
|
|
37
37
|
const objectData = await parseObjectSchemas(rootPath);
|
|
38
|
+
applySchemaTemplateConfig(objectData, options.schemaTemplateConfig, serviceTag);
|
|
39
|
+
|
|
38
40
|
const flowData = await parseFlowSchemas(rootPath, objectData);
|
|
39
41
|
const relationshipData = await parseRelationshipSchemas(rootPath, objectData);
|
|
40
42
|
|
|
41
|
-
// Call Schema Generators (Phase 1)
|
|
42
43
|
const generatorOptions = {
|
|
43
44
|
outputPath: options.outputPath || rootPath,
|
|
44
45
|
serviceTag
|
|
45
46
|
};
|
|
47
|
+
|
|
46
48
|
await generateSystemTextSchemas(
|
|
47
49
|
objectData,
|
|
48
50
|
relationshipData,
|
|
@@ -56,8 +58,66 @@ export async function generateSchema(rootPath, options) {
|
|
|
56
58
|
await generatePropertyValueRelationshipSchemas(objectData, generatorOptions);
|
|
57
59
|
await generateAttributeTreeObjectSchemas(objectData, generatorOptions);
|
|
58
60
|
await generateAttributeTreeRelationshipSchemas(objectData, generatorOptions);
|
|
61
|
+
}
|
|
59
62
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
function readServiceTag(configPath) {
|
|
64
|
+
try {
|
|
65
|
+
const serviceConfigStr = fs.readFileSync(configPath, 'utf8');
|
|
66
|
+
const serviceConfig = yaml.parse(serviceConfigStr);
|
|
67
|
+
return serviceConfig?.main_config?.iz_serviceTag || 'unknownService';
|
|
68
|
+
} catch {
|
|
69
|
+
console.warn(
|
|
70
|
+
`[WARNING] Could not read service config at ${configPath}. Using default serviceTag.`
|
|
71
|
+
);
|
|
72
|
+
return 'unknownService';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function applySchemaTemplateConfig(objectData, schemaTemplateConfig, defaultServiceTag) {
|
|
77
|
+
const templates = Array.isArray(schemaTemplateConfig)
|
|
78
|
+
? schemaTemplateConfig
|
|
79
|
+
: [];
|
|
80
|
+
|
|
81
|
+
for (const template of templates) {
|
|
82
|
+
if (template?.generateName !== 'mainAttributeTree') {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const objectType = template?.objType?.objectType;
|
|
87
|
+
const serviceTag = template?.objType?.serviceTag || defaultServiceTag;
|
|
88
|
+
const attributeTag = template?.attributeTag;
|
|
89
|
+
|
|
90
|
+
if (!objectType || !attributeTag) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const objectSchema = objectData.objects.find(
|
|
95
|
+
obj =>
|
|
96
|
+
obj.objectType === objectType &&
|
|
97
|
+
(obj.serviceTag || defaultServiceTag) === serviceTag
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
if (!objectSchema) {
|
|
101
|
+
console.warn(
|
|
102
|
+
`[WARNING] [generateSchema] Template target not found for ${serviceTag}.${objectType}`
|
|
103
|
+
);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
objectSchema.addOnDataStructure ||= [];
|
|
108
|
+
|
|
109
|
+
const hasAttributeTree = objectSchema.addOnDataStructure.some(
|
|
110
|
+
addOn =>
|
|
111
|
+
addOn?.type === 'attributeTree' &&
|
|
112
|
+
addOn?.attributeTreeTag === attributeTag
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (!hasAttributeTree) {
|
|
116
|
+
// ponytail: inject the same addon shape the existing generators already understand
|
|
117
|
+
objectSchema.addOnDataStructure.push({
|
|
118
|
+
type: 'attributeTree',
|
|
119
|
+
attributeTreeTag: attributeTag
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
63
123
|
}
|
|
@@ -65,8 +65,8 @@ export async function generateRelationshipFlowSchemas(
|
|
|
65
65
|
let outputTopic = true;
|
|
66
66
|
let hasCompleteStep = true;
|
|
67
67
|
|
|
68
|
-
if (action === '
|
|
69
|
-
events = "['lambdaSyncInv', 'lambdaSyncApi']";
|
|
68
|
+
if (action === 'GetRelationship') {
|
|
69
|
+
events = "['lambdaSyncInv', 'lambdaSyncApi', 'dsq', 'ownTopic']";
|
|
70
70
|
outputTopic = false;
|
|
71
71
|
hasCompleteStep = false;
|
|
72
72
|
}
|