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.
- package/README.md +20 -2
- package/index.mjs +74 -17
- 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
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
114
|
-
const toml =
|
|
115
|
-
const docker =
|
|
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
|
|
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
|
-
|
|
162
|
-
|
|
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(
|
|
175
|
-
if(i==0) tpl = tpl.replace(regex, ` blocks
|
|
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 =
|
|
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]
|
|
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(
|
|
214
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
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
|
}
|