enablement-build-manifest 1.0.0 → 1.0.2

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 (3) hide show
  1. package/README.md +20 -2
  2. package/index.mjs +74 -17
  3. package/package.json +5 -3
package/README.md CHANGED
@@ -10,6 +10,8 @@ npx enablement-build-manifest [options]
10
10
 
11
11
  Run from the root of your monorepo. The manifest is printed to stdout and optionally saved to disk.
12
12
 
13
+ When using `--save`, existing `templated` package mappings and existing `cicd` hash/version fields in the target manifest are preserved.
14
+
13
15
  ## Options
14
16
 
15
17
  | Flag | Description |
@@ -29,7 +31,7 @@ Pass `--platform github` or `--platform ado` to override.
29
31
 
30
32
  ## Output
31
33
 
32
- The manifest is a JSON object with five top-level keys:
34
+ The manifest is a JSON object with six top-level keys:
33
35
 
34
36
  ```json
35
37
  {
@@ -37,6 +39,7 @@ The manifest is a JSON object with five top-level keys:
37
39
  "dependencies": { "<package-name>": ["<local-dep>", ...], ... },
38
40
  "dependents": { "<package-name>": ["<consumer>", ...], ... },
39
41
  "pipelines": { "<template-file>": [ <stage>, ... ], ... },
42
+ "templated": { "<template-file>": { "<placeholder>": { "<package-name>": "<job-template>", ... } } },
40
43
  "cicd": { "<package-name>": { "output": "...", "language": "...", "tier": "...", ... }, ... }
41
44
  }
42
45
  ```
@@ -54,6 +57,22 @@ Maps every local package name to its current version from `package.json`.
54
57
 
55
58
  Populated when `{configDir}/templates/templated.json` exists. Each key is a pipeline template filename; the value is an ordered list of resolved stages with their associated package lists.
56
59
 
60
+ ### `templated`
61
+
62
+ Stores the template routing that used to live in `templated.json`. The first key is the output file name, the second key is the placeholder name, and the innermost map assigns each package name to the job template file or files it should use.
63
+
64
+ Example:
65
+
66
+ ```json
67
+ {
68
+ "build.yml": {
69
+ "PACKAGES": {
70
+ "enablement-build-deploy-version": "build_node_component.yml"
71
+ }
72
+ }
73
+ }
74
+ ```
75
+
57
76
  ### `cicd`
58
77
 
59
78
  CI/CD metadata for each package. Sourced from the `.cicd` field in `package.json` when present; otherwise auto-detected by inspecting the package:
@@ -68,7 +87,6 @@ CI/CD metadata for each package. Sourced from the `.cicd` field in `package.json
68
87
  | `pyproject.toml` present | `pypi` | `Python` | `api` |
69
88
  | `Dockerfile` in image/container path | `docker` | `Dockerfile` | `infra` |
70
89
 
71
- Template filenames are also resolved and attached to each `cicd` entry based on the package type.
72
90
 
73
91
  ## Example
74
92
 
package/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import fs from 'fs';
4
- import { glob } from 'glob';
4
+ import { globbySync } from 'globby';
5
5
  import YAML from 'yaml'
6
6
  import path from 'path';
7
7
 
@@ -13,8 +13,8 @@ const main = async (options) => {
13
13
  const configDir = options.platform === 'github' ? '.github' : '.cicd';
14
14
 
15
15
  // Find all package.json files, excluding node_modules
16
- const packageJsonPaths = glob.sync('*/**/package.json', {
17
- ignore: ['**/node_modules/**'],
16
+ const packageJsonPaths = globbySync('*/**/package.json', {
17
+ ignore: ['**/node_modules/**', '**/tests/**'],
18
18
  cwd: process.cwd(),
19
19
  absolute: true
20
20
  });
@@ -44,8 +44,19 @@ const main = async (options) => {
44
44
 
45
45
  versions[pkgJson.name] = pkgJson.version;
46
46
  dependents[pkgJson.name] = []; // Initialize reverse dependency map
47
+ const folderName = path.basename(path.dirname(pkgPath));
48
+ const packageFolder = path.basename(path.dirname(path.dirname(pkgPath)));
47
49
  if (pkgJson['.cicd']) {
48
- cicd[pkgJson.name] = pkgJson['.cicd'];
50
+ const cicdEntry = { ...pkgJson['.cicd'] };
51
+ delete cicdEntry.templates;
52
+ cicdEntry.folderName = folderName;
53
+ cicdEntry.packageFolder = packageFolder;
54
+ cicd[pkgJson.name] = cicdEntry;
55
+ }
56
+ // Always ensure folderName/packageFolder are present if cicd entry exists
57
+ if (cicd[pkgJson.name]) {
58
+ if (!cicd[pkgJson.name].folderName) cicd[pkgJson.name].folderName = folderName;
59
+ if (!cicd[pkgJson.name].packageFolder) cicd[pkgJson.name].packageFolder = packageFolder;
49
60
  }
50
61
  }
51
62
  });
@@ -110,9 +121,9 @@ const main = async (options) => {
110
121
  } else {
111
122
  // ok it is not node
112
123
  var folderName = path.dirname(pkgPath);
113
- const csproj = glob.sync('*.csproj', { cwd: folderName, absolute: true });
114
- const toml = glob.sync('pyproject.toml', { cwd: folderName, absolute: true });
115
- const docker = glob.sync('Dockerfile', { cwd: folderName, absolute: true });
124
+ const csproj = globbySync('*.csproj', { cwd: folderName, absolute: true });
125
+ const toml = globbySync('pyproject.toml', { cwd: folderName, absolute: true });
126
+ const docker = globbySync('Dockerfile', { cwd: folderName, absolute: true });
116
127
 
117
128
  if (csproj.length > 0) {
118
129
  cicd[pkgName] = {
@@ -143,9 +154,19 @@ const main = async (options) => {
143
154
  }
144
155
  });
145
156
 
157
+ // Ensure folderName and packageFolder are set on every cicd entry
158
+ Object.entries(nameToPath).forEach(([pkgName, pkgPath]) => {
159
+ if (cicd[pkgName]) {
160
+ if (!cicd[pkgName].folderName)
161
+ cicd[pkgName].folderName = path.basename(path.dirname(pkgPath));
162
+ if (!cicd[pkgName].packageFolder)
163
+ cicd[pkgName].packageFolder = path.basename(path.dirname(path.dirname(pkgPath)));
164
+ }
165
+ });
166
+
146
167
  // now find all packages where templates are missing
147
168
  // first load the templated.json
148
- let templated = {};
169
+ let templatedConfig = {};
149
170
  if (fs.existsSync(`./${configDir}/templates/templated.json`)) {
150
171
  let data = JSON.parse(fs.readFileSync(`./${configDir}/templates/templated.json`, 'utf-8'));
151
172
  let innerNames ={};
@@ -158,8 +179,8 @@ const main = async (options) => {
158
179
  rootPath = rootPath.replace(/\.\.\/+/g, ''); // remove leading ../
159
180
 
160
181
  innerNames[innerKey] = rootPath;
161
- templated[rootPath] = templated[rootPath] || [];
162
- templated[rootPath].push(template);
182
+ templatedConfig[rootPath] = templatedConfig[rootPath] || [];
183
+ templatedConfig[rootPath].push(template);
163
184
  });
164
185
  }
165
186
 
@@ -171,8 +192,9 @@ const main = async (options) => {
171
192
 
172
193
  let i = 0;
173
194
  for (const innerKey of Object.keys(innerNames)) {
174
- const regex = new RegExp(`\\{\\s*${innerKey.toUpperCase()}\\s*\\}`, 'g');
175
- if(i==0) tpl = tpl.replace(regex, ` blocks:\n - ${innerKey.toUpperCase()}`);
195
+ const regex = new RegExp(`\{\s*${innerKey.toUpperCase()}\s*\}`, 'g');
196
+ if(i==0) tpl = tpl.replace(regex, ` blocks:
197
+ - ${innerKey.toUpperCase()}`);
176
198
  else tpl = tpl.replace(regex, ` - ${innerKey.toUpperCase()}`);
177
199
  i++;
178
200
  }
@@ -199,7 +221,7 @@ const main = async (options) => {
199
221
  }
200
222
  }
201
223
 
202
- const templates = glob.sync(`./${configDir}/templates/*.yml`);
224
+ const templates = globbySync(`./${configDir}/templates/*.yml`);
203
225
  const withUnderscore = templates
204
226
  .filter(file => file.includes('_'))
205
227
  .map(file => path.basename(file));
@@ -207,11 +229,33 @@ const main = async (options) => {
207
229
  const codeScan = withUnderscore.filter(file => file.includes('scan') || file.includes('check'));
208
230
  const build = withUnderscore.filter(file => !file.includes('scan') && !file.includes('check'));
209
231
 
232
+ // Load existing manifest to preserve any already-set templated values
233
+ const manifestPath = `./${configDir}/manifest.json`;
234
+ const existingManifest = fs.existsSync(manifestPath)
235
+ ? JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
236
+ : {};
237
+ const existingTemplated = existingManifest.templated || {};
238
+ const existingPackages = (existingTemplated["build.yml"] || {})["PACKAGES"] || {};
239
+ const existingCicd = existingManifest.cicd || {};
240
+
241
+ const templated = {
242
+ "build.yml": {
243
+ "PACKAGES": {}
244
+ }
245
+ };
246
+
210
247
  Object.entries(nameToPath).forEach(([pkgName, pkgPath]) => {
211
- if (cicd[pkgName] && !cicd[pkgName]["templates"]) {
248
+ if (cicd[pkgName]) {
249
+ // Preserve any existing templated assignment for this package
250
+ if (existingPackages[pkgName] !== undefined) {
251
+ templated["build.yml"]["PACKAGES"][pkgName] = existingPackages[pkgName];
252
+ return;
253
+ }
212
254
  const pkgDir = path.basename(path.dirname(path.dirname(pkgPath)));
213
- if(templated[pkgDir]) {
214
- cicd[pkgName]["templates"] = templated[pkgDir];
255
+ if(templatedConfig[pkgDir]) {
256
+ templated["build.yml"]["PACKAGES"][pkgName] = templatedConfig[pkgDir].length === 1
257
+ ? templatedConfig[pkgDir][0]
258
+ : templatedConfig[pkgDir];
215
259
  } else {
216
260
  let lookfor="node";
217
261
  if(cicd[pkgName]["output"]=="pypi") lookfor = "python";
@@ -232,17 +276,30 @@ const main = async (options) => {
232
276
  }
233
277
  });
234
278
  if(!found && templates.length>0) templates.push("build_scan.yml");
235
- cicd[pkgName]["templates"] = templates;
279
+ templated["build.yml"]["PACKAGES"][pkgName] = templates.length === 1 ? templates[0] : templates;
236
280
  }
237
281
  }
238
282
  });
239
283
 
284
+ // Merge in any pre-existing cicd fields (hash, folderName, packageFolder set by release:finalize)
285
+ Object.keys(existingCicd).forEach(pkgName => {
286
+ if (cicd[pkgName]) {
287
+ // Preserve fields from the existing entry that we don't regenerate
288
+ if (existingCicd[pkgName].hash) cicd[pkgName].hash = existingCicd[pkgName].hash;
289
+ if (existingCicd[pkgName].version) cicd[pkgName].version = existingCicd[pkgName].version;
290
+ } else {
291
+ // If package was in cicd before but not now, preserve entire entry
292
+ cicd[pkgName] = existingCicd[pkgName];
293
+ }
294
+ });
295
+
240
296
  // Final output structure
241
297
  const output = {
242
298
  versions,
243
299
  dependencies,
244
300
  dependents,
245
301
  pipelines,
302
+ templated,
246
303
  cicd
247
304
  };
248
305
 
package/package.json CHANGED
@@ -1,17 +1,19 @@
1
1
  {
2
2
  "name": "enablement-build-manifest",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "This builds a dependency tree.",
5
5
  "type": "module",
6
6
  "module": "./index.mjs",
7
- "bin": "./index.mjs",
8
7
  "license": "MIT",
9
8
  "author": "Contributors",
10
9
  "dependencies": {
11
- "glob": "^11.0.3",
10
+ "globby": "^16.2.0",
12
11
  "yaml": "^2.8.1"
13
12
  },
14
13
  "scripts": {
15
14
  "start": "node index.mjs"
15
+ },
16
+ "bin": {
17
+ "enablement-build-manifest": "./index.mjs"
16
18
  }
17
19
  }