@izara_project/izara-core-generate-service-code 1.0.68 → 1.0.69

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 (81) hide show
  1. package/package.json +1 -1
  2. package/scripts/format-ejs.js +1 -2
  3. package/src/compile/compileProject.js +26 -10
  4. package/src/compile/loadSchemas.js +47 -23
  5. package/src/emit/crud/emitCrudApplicationArtifacts.js +188 -41
  6. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +34 -17
  7. package/src/emit/crud/generateCrudApplicationArtifacts.js +14 -6
  8. package/src/emit/crud/generateDynamicCrudFlowSchemas.js +4 -1
  9. package/src/emit/crud/templates/GetEndpoint_Main.ejs +1 -1
  10. package/src/emit/custom/customFlows.js +23 -13
  11. package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +515 -113
  12. package/src/emit/custom/generateCustomFlowApplicationArtifacts.js +28 -8
  13. package/src/emit/custom/templates/s3/FlowHandler_HdrS3.ejs +2 -2
  14. package/src/emit/custom/templates/s3/ReservedLimit_HdrSqs.ejs +1 -1
  15. package/src/emit/function/functionYamlArtifact.js +270 -97
  16. package/src/emit/function/templates/ApiFunction_Yaml.ejs +7 -0
  17. package/src/emit/function/templates/EventBridgeFunction_Yaml.ejs +6 -0
  18. package/src/emit/function/templates/InvFunction_Yaml.ejs +5 -0
  19. package/src/emit/function/templates/S3Function_Yaml.ejs +16 -0
  20. package/src/emit/function/templates/SqsFunction_Yaml.ejs +10 -0
  21. package/src/emit/function/templates/WbsFunction_Yaml.ejs +10 -0
  22. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +28 -30
  23. package/src/emit/rbac/emitRbacApplicationArtifacts.js +6 -6
  24. package/src/emit/rbac/generateCreateTargetRoleHandler.js +6 -4
  25. package/src/emit/rbac/generateDynamicRbacFlowSchemas.js +8 -2
  26. package/src/emit/rbac/rbacFlowTag.js +3 -1
  27. package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +1 -1
  28. package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +26 -10
  29. package/src/emit/relationship/emitRelationshipApplicationArtifacts.js +179 -37
  30. package/src/emit/relationship/generateDynamicRelationshipFlowSchemas.js +8 -2
  31. package/src/emit/relationship/generateRelationshipApplicationArtifacts.js +15 -6
  32. package/src/emit/resource/emitResourceArtifacts.js +187 -114
  33. package/src/emit/resource/generateResourceArtifacts.js +15 -6
  34. package/src/emit/resource/templates/DynamoDBTable_Yaml.ejs +18 -18
  35. package/src/emit/resource/templates/FlowOut_Yaml.ejs +5 -5
  36. package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +10 -9
  37. package/src/emit/resource/templates/ResourceBeforeLogicalYaml.ejs +44 -44
  38. package/src/emit/resource/templates/ResourceSqsYaml.ejs +36 -36
  39. package/src/emit/resource/templates/ResourceYaml.ejs +48 -48
  40. package/src/emit/resource/templates/S3Subscription_Yaml.ejs +7 -0
  41. package/src/emit/resource/templates/SharedResource_Yaml.ejs +11 -11
  42. package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +105 -105
  43. package/src/emit/resource/templates/WebSocketSubscription_Yaml.ejs +7 -0
  44. package/src/emit/system/emitSystemWebSocketArtifacts.js +61 -165
  45. package/src/emit/system/templates/WebSocketComplete_HdrSqs.ejs +32 -0
  46. package/src/emit/system/templates/WebSocketComplete_Main.ejs +46 -0
  47. package/src/emit/system/templates/WebSocketComplete_Yaml.ejs +19 -0
  48. package/src/emit/system/templates/WebSocketConnect.ejs +25 -0
  49. package/src/emit/system/templates/WebSocketConnect_Yaml.ejs +11 -0
  50. package/src/generate.js +136 -33
  51. package/src/generateIntTest.js +76 -28
  52. package/src/generateResourceIntTest.js +35 -12
  53. package/src/generateResources/IntTest/generateResources/generateResources.js +5 -2
  54. package/src/generateResources/IntTest/generateResources/templateResources.ejs +18 -30
  55. package/src/generateResources/IntTest/generateTests/events/events.js +1 -1
  56. package/src/generateResources/IntTest/generateTests/events/templateEvents.ejs +13 -10
  57. package/src/generateResources/IntTest/generateTests/generateTemplate.js +2 -2
  58. package/src/generateResources/IntTest/generateTests/pathIntTest/pathIntTest.js +2 -1
  59. package/src/generateResources/IntTest/generateTests/pathIntTest/template.ejs +1 -3
  60. package/src/generateResources/IntTest/generateTests/tests/templateTests.ejs +1 -1
  61. package/src/generateResources/IntTest/generateTests/tests/tests.js +7 -3
  62. package/src/generateResources/IntTest/libs/{libs.js → IntTestGenerateLib.js} +34 -23
  63. package/src/generateResources/IntTest/upload/uploadIntTest.js +33 -19
  64. package/src/intTestGeneratorShared.js +15 -10
  65. package/src/output/commitArtifacts.js +99 -39
  66. package/src/output/mergeHooks.js +24 -10
  67. package/src/output/validateArtifacts.js +37 -27
  68. package/src/plan/buildGenerationPlan.js +24 -13
  69. package/test/core.test.js +116 -49
  70. package/test/createTargetRoleHandler.test.js +1 -1
  71. package/test/crudApplicationArtifacts.test.js +128 -23
  72. package/test/customFlowApplicationArtifacts.test.js +353 -71
  73. package/test/dynamicCrudFlowSchemas.test.js +74 -26
  74. package/test/dynamicRbacFlowSchemas.test.js +61 -14
  75. package/test/dynamicRelationshipFlowSchemas.test.js +65 -18
  76. package/test/functionYamlArtifact.test.js +67 -10
  77. package/test/intTestApi.test.js +9 -10
  78. package/test/publicApi.test.js +176 -34
  79. package/test/relationshipApplicationArtifacts.test.js +90 -19
  80. package/test/resourceArtifacts.test.js +172 -32
  81. package/src/emit/relationship/dynamicRelationshipFlowSchemaTemplate.js +0 -11
@@ -22,7 +22,7 @@ const __dirname = path.dirname(__filename);
22
22
 
23
23
  const templatePath = path.join(__dirname, './templateTests.ejs');
24
24
 
25
- import libs from '../../libs/libs.js';
25
+ import libs from '../../libs/IntTestGenerateLib.js';
26
26
  const {
27
27
  createTestFunctionName,
28
28
  getHandlerFromEventTag,
@@ -31,7 +31,11 @@ const {
31
31
  setInvokeTest
32
32
  } = libs;
33
33
 
34
- import { HANDLER, GENERATE_SEED_DATA_TYPE, SEED_DATA_TAG } from '../../consts.js';
34
+ import {
35
+ HANDLER,
36
+ GENERATE_SEED_DATA_TYPE,
37
+ SEED_DATA_TAG
38
+ } from '../../consts.js';
35
39
 
36
40
  async function data(_izContext, allIntTestConfig, srcPath, serviceTag) {
37
41
  let createSourceData = [];
@@ -139,7 +143,7 @@ async function data(_izContext, allIntTestConfig, srcPath, serviceTag) {
139
143
  // baseIntTest.stage[currentStageIdx].invokes = invokeTest.invokeEventTags;
140
144
  stages[currentStageIdx].invokes = invokeTest.invokeEventTags;
141
145
 
142
- const invokeStagesCloned = invokeTest.invokeStages.map(stage =>
146
+ const invokeStagesCloned = invokeTest.invokeStages.map((stage) =>
143
147
  structuredClone(stage)
144
148
  );
145
149
 
@@ -1,7 +1,10 @@
1
- import { getObjectSchema, consts as coreConsts } from '@izara_project/izara-core-library-service-schemas';
1
+ import {
2
+ getObjectSchema,
3
+ consts as coreConsts
4
+ } from '@izara_project/izara-core-library-service-schemas';
2
5
  const { getDataFromPath } = getObjectSchema;
3
6
  const { HANDLER } = coreConsts;
4
- import yaml from 'yaml';
7
+ import yamlLib from 'yaml';
5
8
  import fs from 'fs';
6
9
  import path from 'path';
7
10
 
@@ -22,13 +25,13 @@ function directoryPath(dirPath, result = []) {
22
25
  directoryPath(fullPath, result); // ✅ ใช้ result เดียวกัน
23
26
  } else if (stat.isFile()) {
24
27
  if (
25
- !fullPath.includes("shared-resource.yml") &&
26
- !fullPath.includes("core-function.yml")
28
+ !fullPath.includes('shared-resource.yml') &&
29
+ !fullPath.includes('core-function.yml')
27
30
  ) {
28
- if (fullPath.endsWith(".yml")) {
31
+ if (fullPath.endsWith('.yml')) {
29
32
  result.push(fullPath);
30
33
  } else {
31
- console.log("Skipped File:", fullPath);
34
+ console.log('Skipped File:', fullPath);
32
35
  }
33
36
  }
34
37
  }
@@ -37,19 +40,19 @@ function directoryPath(dirPath, result = []) {
37
40
  let functionComponent = {};
38
41
 
39
42
  for (let ymlFilesPath of result) {
40
- const fileContent = fs.readFileSync(ymlFilesPath, "utf8");
41
- const parsedYaml = yaml.parse(fileContent);
43
+ const fileContent = fs.readFileSync(ymlFilesPath, 'utf8');
44
+ const parsedYaml = yamlLib.parse(fileContent);
42
45
 
43
46
  for (const component of Object.values(parsedYaml)) {
44
47
  if (component.handler && component.name) {
45
48
  const clearName = component.name.replace(
46
- "${self:custom.iz_resourcePrefix}",
47
- ""
49
+ '${self:custom.iz_resourcePrefix}',
50
+ ''
48
51
  );
49
- const clearMain = component.handler.replace(/\.main$/, "");
52
+ const clearMain = component.handler.replace(/\.main$/, '');
50
53
 
51
54
  Object.assign(functionComponent, {
52
- [clearName]: clearMain,
55
+ [clearName]: clearMain
53
56
  });
54
57
  }
55
58
  }
@@ -347,16 +350,19 @@ function setNoInvokeTest(
347
350
  // setTests
348
351
 
349
352
  function createEventCaseByHandler(handler, messageAttributes = {}) {
350
- return {
351
- [HANDLER.hdrApi]: {},
352
- [HANDLER.hdrDsq]: { DirectSqs: true },
353
- [HANDLER.hdrInv]: {},
354
- [HANDLER.hdrSqs]: {
355
- ...(messageAttributes && Object.keys(messageAttributes).length > 0
356
- ? { messageAttributes: setEventMessageAttributes(messageAttributes) }
357
- : {})
353
+ if (handler === HANDLER.hdrDsq) {
354
+ return { DirectSqs: true };
355
+ }
356
+ if (handler === HANDLER.hdrSqs) {
357
+ if (messageAttributes && Object.keys(messageAttributes).length > 0) {
358
+ return { messageAttributes: setEventMessageAttributes(messageAttributes) };
358
359
  }
359
- }[handler];
360
+ return {};
361
+ }
362
+ if (handler === HANDLER.hdrApi || handler === HANDLER.hdrInv) {
363
+ return {};
364
+ }
365
+ return null;
360
366
  }
361
367
 
362
368
  function getHandlerFromEventTag(testTag) {
@@ -395,11 +401,16 @@ function createResourceName(eventTag) {
395
401
  async function getIntTestConfig(_izContext, intTestPath, rootPath) {
396
402
  // ponytail: find first existing integration test libs folder or fall back to standard path
397
403
  const targetLibsPath = rootPath.includes('generateIntegrationTest')
398
- ? path.join(rootPath.split('generateIntegrationTest')[0], 'generateIntegrationTest', 'libs')
404
+ ? path.join(
405
+ rootPath.split('generateIntegrationTest')[0],
406
+ 'generateIntegrationTest',
407
+ 'libs'
408
+ )
399
409
  : [
400
410
  path.join(rootPath, 'app/src/generateIntegrationTest/libs'),
401
411
  path.join(rootPath, 'src/generateIntegrationTest/libs')
402
- ].find(fs.existsSync) || path.join(rootPath, 'src/generateIntegrationTest/libs');
412
+ ].find(fs.existsSync) ||
413
+ path.join(rootPath, 'src/generateIntegrationTest/libs');
403
414
 
404
415
  return await getObjectSchema.getDataFromPath(
405
416
  _izContext,
@@ -22,15 +22,17 @@ import Logger from '@izara_project/izara-core-library-logger';
22
22
  const DEFAULT_BUCKET = 'integrationtest-config-us-east-2';
23
23
  const _izContext = { Logger };
24
24
 
25
- function getAllFiles(dirPath, filePaths = []) {
26
- if (!fs.existsSync(dirPath)) return filePaths;
25
+ function _getAllFiles(dirPath, filePaths = []) {
26
+ if (!fs.existsSync(dirPath)) {
27
+ return filePaths;
28
+ }
27
29
 
28
30
  for (const file of fs.readdirSync(dirPath)) {
29
31
  const fullPath = path.join(dirPath, file);
30
32
  const stat = fs.statSync(fullPath);
31
33
 
32
34
  if (stat.isDirectory()) {
33
- getAllFiles(fullPath, filePaths);
35
+ _getAllFiles(fullPath, filePaths);
34
36
  continue;
35
37
  }
36
38
 
@@ -42,15 +44,18 @@ function getAllFiles(dirPath, filePaths = []) {
42
44
 
43
45
  async function bucketPathForUpload(folderPath, settings = {}) {
44
46
  const dataToUploads = [];
45
- const files = getAllFiles(folderPath);
47
+ const files = _getAllFiles(folderPath);
46
48
 
47
49
  for (const filePath of files) {
48
50
  const fileContent = fs.readFileSync(filePath);
49
- // ponytail: use indexOf to find first occurrence of serviceTag so nested test case folders containing the serviceTag name do not get cut off.
50
- const index = settings.serviceTag ? filePath.indexOf(settings.serviceTag) : -1;
51
- const relativePath = index !== -1
52
- ? filePath.substring(index)
53
- : path.basename(filePath);
51
+ let index = -1;
52
+ if (settings.serviceTag) {
53
+ index = filePath.indexOf(settings.serviceTag);
54
+ }
55
+ let relativePath = path.basename(filePath);
56
+ if (index !== -1) {
57
+ relativePath = filePath.substring(index);
58
+ }
54
59
  const s3Key = path
55
60
  .join('test_config', settings.s3Prefix || '', relativePath)
56
61
  .split(path.sep)
@@ -64,18 +69,27 @@ async function bucketPathForUpload(folderPath, settings = {}) {
64
69
 
65
70
  async function uploadToS3(folderPath, settings = {}) {
66
71
  const uploads = await bucketPathForUpload(folderPath, settings);
67
- const bucket = settings.bucket || DEFAULT_BUCKET;
68
-
69
- await Promise.all(uploads.map(({ key, fileContent }) => (
70
- s3.putObjectS3(_izContext, {
71
- Bucket: bucket,
72
- Key: key,
73
- Body: fileContent,
74
- ContentType: 'application/json'
72
+ let bucket = DEFAULT_BUCKET;
73
+ if (settings.bucket) {
74
+ bucket = settings.bucket;
75
+ }
76
+
77
+ await Promise.all(
78
+ uploads.map(async ({ key, fileContent }) => {
79
+ await s3.putObjectS3(_izContext, {
80
+ Bucket: bucket,
81
+ Key: key,
82
+ Body: fileContent,
83
+ ContentType: 'application/json'
84
+ });
75
85
  })
76
- )));
86
+ );
77
87
 
78
- return uploads.map(({ key }) => key);
88
+ const keys = [];
89
+ for (const { key } of uploads) {
90
+ keys.push(key);
91
+ }
92
+ return keys;
79
93
  }
80
94
 
81
95
  export { DEFAULT_BUCKET, bucketPathForUpload, uploadToS3 };
@@ -1,32 +1,37 @@
1
1
  import { existsSync, readdirSync, readFileSync, rmSync } from 'fs';
2
2
  import { join } from 'path';
3
- import yaml from 'yaml';
3
+ import yamlLib from 'yaml';
4
4
 
5
5
  function getServiceTag(rootPath) {
6
6
  const configPath = join(rootPath, 'config', 'serverless.config.yml');
7
- const config = yaml.parse(readFileSync(configPath, 'utf8'));
7
+ const config = yamlLib.parse(readFileSync(configPath, 'utf8'));
8
8
  return config?.main_config?.iz_serviceTag;
9
9
  }
10
10
 
11
11
  function getIntTestOutputPath(outputPath, projectRootPath = outputPath) {
12
- return existsSync(join(projectRootPath, 'app'))
13
- ? join(outputPath, 'app', 'src', 'generateIntegrationTest')
14
- : join(outputPath, 'src', 'generateIntegrationTest');
12
+ if (existsSync(join(projectRootPath, 'app'))) {
13
+ return join(outputPath, 'app', 'src', 'generateIntegrationTest');
14
+ }
15
+ return join(outputPath, 'src', 'generateIntegrationTest');
15
16
  }
16
17
 
17
- function clearSavePath(savePath, preserveFileNames = []) {
18
- if (!savePath || !existsSync(savePath)) return;
18
+ function _clearSavePath(savePath, preserveFileNames = []) {
19
+ if (!savePath || !existsSync(savePath)) {
20
+ return;
21
+ }
19
22
 
20
23
  const preserved = new Set(preserveFileNames);
21
24
  for (const entry of readdirSync(savePath)) {
22
- if (preserved.has(entry)) continue;
25
+ if (preserved.has(entry)) {
26
+ continue;
27
+ }
23
28
  rmSync(join(savePath, entry), { recursive: true, force: true });
24
29
  }
25
30
  }
26
31
 
27
32
  async function runTemplateItems(items, { writeSource, errorPrefix }) {
28
33
  const normalizedItems = (items || []).filter(
29
- item => !(Array.isArray(item) && item.length === 0)
34
+ (item) => !(Array.isArray(item) && item.length === 0)
30
35
  );
31
36
  if (!normalizedItems.length) {
32
37
  return;
@@ -55,7 +60,7 @@ async function runTemplateItems(items, { writeSource, errorPrefix }) {
55
60
  const savePath = item.setting?.savePath;
56
61
  const cleanSavePathEnabled = item.setting?.cleanSavePath !== false;
57
62
  if (savePath && cleanSavePathEnabled && !clearedPaths.has(savePath)) {
58
- clearSavePath(savePath, item.setting?.preserveFileNames || []);
63
+ _clearSavePath(savePath, item.setting?.preserveFileNames || []);
59
64
  clearedPaths.add(savePath);
60
65
  }
61
66
 
@@ -1,8 +1,22 @@
1
1
  import { constants } from 'node:fs';
2
- import { copyFile, lstat, mkdir, mkdtemp, readFile, realpath, rename, rm, unlink, writeFile } from 'node:fs/promises';
2
+ import {
3
+ copyFile,
4
+ lstat,
5
+ mkdir,
6
+ mkdtemp,
7
+ readFile,
8
+ realpath,
9
+ rename,
10
+ rm,
11
+ unlink,
12
+ writeFile
13
+ } from 'node:fs/promises';
3
14
  import { dirname, join, relative, resolve, sep } from 'node:path';
4
15
 
5
- import { normalizeArtifactPaths, validateArtifacts } from './validateArtifacts.js';
16
+ import {
17
+ normalizeArtifactPaths,
18
+ validateArtifacts
19
+ } from './validateArtifacts.js';
6
20
  import { mergeHooks } from './mergeHooks.js';
7
21
 
8
22
  const MANAGED_ARTIFACTS_PATH = '.generate-service-code-v2-managed.json';
@@ -10,36 +24,51 @@ const STAGING_PREFIX = '.generate-service-code-v2-stage-';
10
24
 
11
25
  export async function commitArtifacts(plan) {
12
26
  if (!plan?.project?.outputPath || !Array.isArray(plan.artifacts)) {
13
- throw new TypeError('GenerationPlan must include project.outputPath and artifacts');
27
+ throw new TypeError(
28
+ 'GenerationPlan must include project.outputPath and artifacts'
29
+ );
14
30
  }
15
31
 
16
32
  const outputRoot = resolve(plan.project.outputPath);
17
33
  const artifacts = validateArtifacts(plan.artifacts);
18
34
  await mkdir(outputRoot, { recursive: true });
19
35
  const physicalOutputRoot = await realpath(outputRoot);
20
- const manifestPath = await resolveSafeTarget(physicalOutputRoot, MANAGED_ARTIFACTS_PATH);
21
- const managedArtifacts = await loadManagedArtifacts(manifestPath);
22
- const managedScopes = normalizeManagedScopes(plan);
23
- const stalePaths = staleManagedPaths(managedScopes, managedArtifacts, artifacts);
36
+ const manifestPath = await _resolveSafeTarget(
37
+ physicalOutputRoot,
38
+ MANAGED_ARTIFACTS_PATH
39
+ );
40
+ const managedArtifacts = await _loadManagedArtifacts(manifestPath);
41
+ const managedScopes = _normalizeManagedScopes(plan);
42
+ const stalePaths = _staleManagedPaths(
43
+ managedScopes,
44
+ managedArtifacts,
45
+ artifacts
46
+ );
24
47
  const mergedArtifacts = await mergeHooks(physicalOutputRoot, artifacts);
25
48
  const stagedRoot = await mkdtemp(join(physicalOutputRoot, STAGING_PREFIX));
26
49
 
27
50
  try {
28
- const changes = await stageChanges({
51
+ let manifestContent;
52
+ if (managedScopes) {
53
+ manifestContent = _managedManifestContent(
54
+ managedScopes,
55
+ managedArtifacts,
56
+ mergedArtifacts
57
+ );
58
+ }
59
+ const changes = await _stageChanges({
29
60
  outputRoot: physicalOutputRoot,
30
61
  stagedRoot,
31
62
  artifacts: mergedArtifacts,
32
63
  stalePaths,
33
64
  manifestPath,
34
- manifestContent: managedScopes
35
- ? managedManifestContent(managedScopes, managedArtifacts, mergedArtifacts)
36
- : undefined
65
+ manifestContent
37
66
  });
38
- const snapshots = await snapshotChanges(changes, stagedRoot);
67
+ const snapshots = await _snapshotChanges(changes, stagedRoot);
39
68
  try {
40
- await applyChanges(changes);
69
+ await _applyChanges(changes);
41
70
  } catch (error) {
42
- await rollbackChanges(changes, snapshots);
71
+ await _rollbackChanges(changes, snapshots);
43
72
  throw error;
44
73
  }
45
74
  } finally {
@@ -49,21 +78,23 @@ export async function commitArtifacts(plan) {
49
78
  return mergedArtifacts.map(({ path }) => ({ path }));
50
79
  }
51
80
 
52
- function normalizeManagedScopes(plan) {
53
- if (plan.managedScopes !== undefined) {
81
+ function _normalizeManagedScopes(plan) {
82
+ if (plan.managedScopes !== undefined && plan.managedScopes !== null) {
54
83
  return plan.managedScopes;
55
84
  }
56
- if (plan.managedScope === undefined) {
57
- return undefined;
85
+ if (plan.managedScope === undefined || plan.managedScope === null) {
86
+ return null;
58
87
  }
59
- return { [plan.managedScope]: normalizeArtifactPaths(plan.managedPaths ?? []) };
88
+ return {
89
+ [plan.managedScope]: normalizeArtifactPaths(plan.managedPaths ?? [])
90
+ };
60
91
  }
61
92
 
62
- function staleManagedPaths(managedScopes, managedArtifacts, artifacts) {
93
+ function _staleManagedPaths(managedScopes, managedArtifacts, artifacts) {
63
94
  if (!managedScopes) {
64
95
  return [];
65
96
  }
66
- const emittedPaths = new Set(artifacts.map(artifact => artifact.path));
97
+ const emittedPaths = new Set(artifacts.map((artifact) => artifact.path));
67
98
  const stalePaths = new Set();
68
99
  for (const [scope, declaredPaths] of Object.entries(managedScopes)) {
69
100
  if (!Object.hasOwn(managedArtifacts, scope)) {
@@ -78,35 +109,58 @@ function staleManagedPaths(managedScopes, managedArtifacts, artifacts) {
78
109
  return [...stalePaths];
79
110
  }
80
111
 
81
- function managedManifestContent(managedScopes, managedArtifacts, artifacts) {
82
- const emittedPaths = new Set(artifacts.map(artifact => artifact.path));
112
+ function _managedManifestContent(managedScopes, managedArtifacts, artifacts) {
113
+ const emittedPaths = new Set(artifacts.map((artifact) => artifact.path));
83
114
  const nextManifest = { ...managedArtifacts };
84
115
  for (const [scope, paths] of Object.entries(managedScopes)) {
85
- nextManifest[scope] = paths.filter(path => emittedPaths.has(path));
116
+ nextManifest[scope] = paths.filter((path) => emittedPaths.has(path));
86
117
  }
87
118
  return `${JSON.stringify(nextManifest, null, 2)}\n`;
88
119
  }
89
120
 
90
- async function stageChanges({ outputRoot, stagedRoot, artifacts, stalePaths, manifestPath, manifestContent }) {
121
+ async function _stageChanges({
122
+ outputRoot,
123
+ stagedRoot,
124
+ artifacts,
125
+ stalePaths,
126
+ manifestPath,
127
+ manifestContent
128
+ }) {
91
129
  const changes = [];
92
130
  for (const artifact of artifacts) {
93
- const targetPath = await resolveSafeTarget(outputRoot, artifact.path);
94
- const stagedPath = await resolveSafeTarget(stagedRoot, artifact.path, { createDirectories: true });
95
- await writeFile(stagedPath, artifact.content, { encoding: 'utf8', flag: constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW });
131
+ const targetPath = await _resolveSafeTarget(outputRoot, artifact.path);
132
+ const stagedPath = await _resolveSafeTarget(stagedRoot, artifact.path, {
133
+ createDirectories: true
134
+ });
135
+ await writeFile(stagedPath, artifact.content, {
136
+ encoding: 'utf8',
137
+ flag:
138
+ constants.O_WRONLY |
139
+ constants.O_CREAT |
140
+ constants.O_TRUNC |
141
+ constants.O_NOFOLLOW
142
+ });
96
143
  changes.push({ targetPath, stagedPath });
97
144
  }
98
145
  for (const path of stalePaths) {
99
- changes.push({ targetPath: await resolveSafeTarget(outputRoot, path), delete: true });
146
+ changes.push({
147
+ targetPath: await _resolveSafeTarget(outputRoot, path),
148
+ delete: true
149
+ });
100
150
  }
101
151
  if (manifestContent !== undefined) {
102
- const stagedPath = await resolveSafeTarget(stagedRoot, MANAGED_ARTIFACTS_PATH, { createDirectories: true });
152
+ const stagedPath = await _resolveSafeTarget(
153
+ stagedRoot,
154
+ MANAGED_ARTIFACTS_PATH,
155
+ { createDirectories: true }
156
+ );
103
157
  await writeFile(stagedPath, manifestContent, 'utf8');
104
158
  changes.push({ targetPath: manifestPath, stagedPath });
105
159
  }
106
160
  return changes;
107
161
  }
108
162
 
109
- async function snapshotChanges(changes, stagedRoot) {
163
+ async function _snapshotChanges(changes, stagedRoot) {
110
164
  const snapshots = new Map();
111
165
  for (const [index, change] of changes.entries()) {
112
166
  try {
@@ -128,10 +182,10 @@ async function snapshotChanges(changes, stagedRoot) {
128
182
  return snapshots;
129
183
  }
130
184
 
131
- async function applyChanges(changes) {
185
+ async function _applyChanges(changes) {
132
186
  for (const change of changes) {
133
187
  if (change.delete) {
134
- await unlink(change.targetPath).catch(ignoreMissingFile);
188
+ await unlink(change.targetPath).catch(_ignoreMissingFile);
135
189
  continue;
136
190
  }
137
191
  await mkdir(dirname(change.targetPath), { recursive: true });
@@ -139,11 +193,11 @@ async function applyChanges(changes) {
139
193
  }
140
194
  }
141
195
 
142
- async function rollbackChanges(changes, snapshots) {
196
+ async function _rollbackChanges(changes, snapshots) {
143
197
  for (const change of [...changes].reverse()) {
144
198
  const snapshot = snapshots.get(change.targetPath);
145
199
  if (!snapshot) {
146
- await unlink(change.targetPath).catch(ignoreMissingFile);
200
+ await unlink(change.targetPath).catch(_ignoreMissingFile);
147
201
  continue;
148
202
  }
149
203
  await mkdir(dirname(change.targetPath), { recursive: true });
@@ -151,13 +205,13 @@ async function rollbackChanges(changes, snapshots) {
151
205
  }
152
206
  }
153
207
 
154
- function ignoreMissingFile(error) {
208
+ function _ignoreMissingFile(error) {
155
209
  if (error.code !== 'ENOENT') {
156
210
  throw error;
157
211
  }
158
212
  }
159
213
 
160
- async function loadManagedArtifacts(manifestPath) {
214
+ async function _loadManagedArtifacts(manifestPath) {
161
215
  try {
162
216
  const parsed = JSON.parse(await readFile(manifestPath, 'utf8'));
163
217
  if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
@@ -175,7 +229,11 @@ async function loadManagedArtifacts(manifestPath) {
175
229
  }
176
230
  }
177
231
 
178
- async function resolveSafeTarget(outputRoot, artifactPath, { createDirectories = false } = {}) {
232
+ async function _resolveSafeTarget(
233
+ outputRoot,
234
+ artifactPath,
235
+ { createDirectories = false } = {}
236
+ ) {
179
237
  const targetPath = resolve(outputRoot, artifactPath);
180
238
  if (relative(outputRoot, targetPath).startsWith('..')) {
181
239
  throw new Error(`Artifact path escapes output root: ${artifactPath}`);
@@ -187,7 +245,9 @@ async function resolveSafeTarget(outputRoot, artifactPath, { createDirectories =
187
245
  try {
188
246
  const details = await lstat(currentPath);
189
247
  if (details.isSymbolicLink() || !details.isDirectory()) {
190
- throw new Error(`Artifact path traverses a non-directory: ${artifactPath}`);
248
+ throw new Error(
249
+ `Artifact path traverses a non-directory: ${artifactPath}`
250
+ );
191
251
  }
192
252
  } catch (error) {
193
253
  if (error.code !== 'ENOENT') {
@@ -2,10 +2,12 @@ import { lstat, readFile } from 'node:fs/promises';
2
2
  import { join, sep } from 'node:path';
3
3
 
4
4
  export async function mergeHooks(outputRoot, artifacts) {
5
- return Promise.all(artifacts.map(async artifact => ({
6
- ...artifact,
7
- content: await mergeHook(outputRoot, artifact)
8
- })));
5
+ return Promise.all(
6
+ artifacts.map(async (artifact) => ({
7
+ ...artifact,
8
+ content: await mergeHook(outputRoot, artifact)
9
+ }))
10
+ );
9
11
  }
10
12
 
11
13
  async function mergeHook(outputRoot, artifact) {
@@ -15,7 +17,12 @@ async function mergeHook(outputRoot, artifact) {
15
17
  return artifact.content;
16
18
  }
17
19
 
18
- const hookPath = join(outputRoot, ...parts.slice(0, sourceIndex), 'hook', ...parts.slice(sourceIndex + 1));
20
+ const hookPath = join(
21
+ outputRoot,
22
+ ...parts.slice(0, sourceIndex),
23
+ 'hook',
24
+ ...parts.slice(sourceIndex + 1)
25
+ );
19
26
  try {
20
27
  if (!(await lstat(hookPath)).isFile()) {
21
28
  return artifact.content;
@@ -33,22 +40,29 @@ async function mergeHook(outputRoot, artifact) {
33
40
  }
34
41
 
35
42
  const hookContent = await readFile(hookPath, 'utf8');
36
- return artifact.content.replace(matchers.anyTag, (match, tagName) => (
37
- hookContent.match(matchers.tagPattern(tagName))?.[0] ?? match
38
- ));
43
+ return artifact.content.replace(
44
+ matchers.anyTag,
45
+ (match, tagName) =>
46
+ hookContent.match(matchers.tagPattern(tagName))?.[0] ?? match
47
+ );
39
48
  }
40
49
 
41
50
  function hookMatchers(filePath) {
42
51
  if (filePath.endsWith('.js')) {
43
52
  return {
44
53
  anyTag: /\/\/\(<(\w+)>\)([\s\S]*?)\/\/\(<\/\1>\)/gm,
45
- tagPattern: name => new RegExp(`\\/\\/\\(<${name}>\\)([\\s\\S]*?)\\/\\/\\(<\\/${name}>\\)`, 'gm')
54
+ tagPattern: (name) =>
55
+ new RegExp(
56
+ `\\/\\/\\(<${name}>\\)([\\s\\S]*?)\\/\\/\\(<\\/${name}>\\)`,
57
+ 'gm'
58
+ )
46
59
  };
47
60
  }
48
61
  if (filePath.endsWith('.yml') || filePath.endsWith('.yaml')) {
49
62
  return {
50
63
  anyTag: /#<#(\w+)#>([\s\S]*?)#<#\/\1#>/gm,
51
- tagPattern: name => new RegExp(`#<#${name}#>([\\s\\S]*?)#<#\\/${name}#>`, 'gm')
64
+ tagPattern: (name) =>
65
+ new RegExp(`#<#${name}#>([\\s\\S]*?)#<#\\/${name}#>`, 'gm')
52
66
  };
53
67
  }
54
68
  return null;
@@ -6,24 +6,32 @@ export function normalizeArtifacts(artifacts) {
6
6
  }
7
7
 
8
8
  const paths = new Set();
9
- return Object.freeze(artifacts.map(artifact => {
10
- if (!artifact || typeof artifact.path !== 'string' || artifact.path.trim() === '') {
11
- throw new TypeError('Artifact.path must be a non-empty relative path');
12
- }
13
- if (isAbsolute(artifact.path)) {
14
- throw new TypeError(`Artifact.path must be relative: ${artifact.path}`);
15
- }
16
- if (typeof artifact.content !== 'string') {
17
- throw new TypeError(`Artifact.content must be a string: ${artifact.path}`);
18
- }
19
-
20
- const path = normalizeArtifactPath(artifact.path);
21
- if (paths.has(path)) {
22
- throw new Error(`Duplicate artifact path: ${path}`);
23
- }
24
- paths.add(path);
25
- return Object.freeze({ path, content: artifact.content });
26
- }));
9
+ return Object.freeze(
10
+ artifacts.map((artifact) => {
11
+ if (
12
+ !artifact ||
13
+ typeof artifact.path !== 'string' ||
14
+ artifact.path.trim() === ''
15
+ ) {
16
+ throw new TypeError('Artifact.path must be a non-empty relative path');
17
+ }
18
+ if (isAbsolute(artifact.path)) {
19
+ throw new TypeError(`Artifact.path must be relative: ${artifact.path}`);
20
+ }
21
+ if (typeof artifact.content !== 'string') {
22
+ throw new TypeError(
23
+ `Artifact.content must be a string: ${artifact.path}`
24
+ );
25
+ }
26
+
27
+ const path = _normalizeArtifactPath(artifact.path);
28
+ if (paths.has(path)) {
29
+ throw new Error(`Duplicate artifact path: ${path}`);
30
+ }
31
+ paths.add(path);
32
+ return Object.freeze({ path, content: artifact.content });
33
+ })
34
+ );
27
35
  }
28
36
 
29
37
  export function validateArtifacts(artifacts) {
@@ -36,17 +44,19 @@ export function normalizeArtifactPaths(paths) {
36
44
  }
37
45
 
38
46
  const seenPaths = new Set();
39
- return Object.freeze(paths.map(path => {
40
- const normalizedPath = normalizeArtifactPath(path);
41
- if (seenPaths.has(normalizedPath)) {
42
- throw new Error(`Duplicate artifact path: ${normalizedPath}`);
43
- }
44
- seenPaths.add(normalizedPath);
45
- return normalizedPath;
46
- }));
47
+ return Object.freeze(
48
+ paths.map((path) => {
49
+ const normalizedPath = _normalizeArtifactPath(path);
50
+ if (seenPaths.has(normalizedPath)) {
51
+ throw new Error(`Duplicate artifact path: ${normalizedPath}`);
52
+ }
53
+ seenPaths.add(normalizedPath);
54
+ return normalizedPath;
55
+ })
56
+ );
47
57
  }
48
58
 
49
- function normalizeArtifactPath(path) {
59
+ function _normalizeArtifactPath(path) {
50
60
  if (typeof path !== 'string' || path.trim() === '') {
51
61
  throw new TypeError('Artifact.path must be a non-empty relative path');
52
62
  }