enablement-build-template-generate 1.0.16 → 1.0.17
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 +48 -73
- package/package.json +6 -4
- package/src/index.mjs +163 -65
- package/src/lib.mjs +9 -2
package/README.md
CHANGED
|
@@ -12,9 +12,9 @@ npx enablement-build-template-generate@latest [flags]
|
|
|
12
12
|
|
|
13
13
|
## How it works
|
|
14
14
|
|
|
15
|
-
1. **
|
|
16
|
-
2. **
|
|
17
|
-
3. **Expand job
|
|
15
|
+
1. **Detect platform** — Checks for `manifest.json` (preferred) or `templated.json` (legacy) in `.cicd/` (ADO) or `.github/` (GitHub). Defaults to `ado` if none are found, or use `--platform` to override.
|
|
16
|
+
2. **Read config** — If `manifest.json` is present, reads the `templated` section (each package maps to its own job template file). If only `templated.json` is present, reads the legacy `{ rootPath, template }` format and globs for packages automatically.
|
|
17
|
+
3. **Expand job templates** — For each package, reads its job snippet template and fills in placeholders. When using `manifest.json`, metadata (`folderName`, `packageFolder`) comes from `manifest.cicd`; when using `templated.json`, package names are read from each folder's manifest file.
|
|
18
18
|
4. **Stitch** — Joins all expanded job snippets and substitutes them into the document template at the named placeholder.
|
|
19
19
|
5. **Write output** — Writes the final file one level above the template directory (`.github/templates/` → `.github/` or `.cicd/templates/` → `.cicd/`), wrapped in `DO NOT EDIT` banners.
|
|
20
20
|
|
|
@@ -24,55 +24,55 @@ npx enablement-build-template-generate@latest [flags]
|
|
|
24
24
|
|
|
25
25
|
| Flag | Default | Description |
|
|
26
26
|
|---|---|---|
|
|
27
|
-
| `--
|
|
28
|
-
| `--platform <name>` | auto-detect | Force the platform: `ado` (Azure DevOps) or `github` (GitHub Actions). Auto-detected from the presence of a `.github/` directory at the working directory root. |
|
|
27
|
+
| `--platform <name>` | auto-detect | Force the platform: `ado` (Azure DevOps) or `github` (GitHub Actions). Auto-detected from `manifest.json` or `templated.json` presence in `.cicd/` or `.github/`. |
|
|
29
28
|
| `--exclude <list>` | — | Comma- or space-separated list of package folder names to skip during generation. |
|
|
30
29
|
| `--debug` | off | Print verbose output. |
|
|
31
30
|
|
|
32
31
|
---
|
|
33
32
|
|
|
34
|
-
## Config
|
|
33
|
+
## Config source
|
|
35
34
|
|
|
36
|
-
The
|
|
35
|
+
The generator supports two config formats, checked in this order:
|
|
36
|
+
|
|
37
|
+
### 1. `manifest.json` (preferred)
|
|
38
|
+
|
|
39
|
+
Reads the `templated` section of `{configDir}/manifest.json` (produced by `enablement-build-manifest`). Each key is the **output filename**; each value maps a placeholder variable name to a per-package template mapping.
|
|
37
40
|
|
|
38
41
|
```json
|
|
39
42
|
{
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
"templated": {
|
|
44
|
+
"build.yml": {
|
|
45
|
+
"PACKAGES": {
|
|
46
|
+
"my-scope/component-a": "build_node_component.yml",
|
|
47
|
+
"my-scope/component-b": ["build_node_component.yml", "build_scan_node.yml"],
|
|
48
|
+
"my-scope/python-lib": ["build_python_package.yml", "build_scan.yml"]
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
```
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
Each package name maps to a **single template filename** or an **array of template filenames** (all expanded in order for that package). Template lookup tries the active platform templates directory first (`.cicd/templates/` or `.github/templates/`) and then falls back to the other platform directory if a file is missing.
|
|
56
|
+
|
|
57
|
+
Package metadata (`folderName`, `packageFolder`) is read from `manifest.cicd[packageName]` and used to fill template placeholders.
|
|
58
|
+
|
|
59
|
+
### 2. `templated.json` (legacy fallback)
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
When `manifest.json` is absent, reads `{configDir}/templates/templated.json`. Each entry provides a `rootPath` to glob for packages and a single `template` applied to all discovered packages. Template lookup still uses active-platform-first, then falls back to the other platform templates folder if needed.
|
|
57
62
|
|
|
58
63
|
```json
|
|
59
64
|
{
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"
|
|
64
|
-
}
|
|
65
|
-
},
|
|
66
|
-
"ci-saas.yaml": {
|
|
67
|
-
"saasJobs": {
|
|
68
|
-
"template": "saas-job.yaml",
|
|
69
|
-
"rootPath": "./saas",
|
|
70
|
-
"exclude": ["saas-legacy"]
|
|
65
|
+
"build.yml": {
|
|
66
|
+
"PACKAGES": {
|
|
67
|
+
"rootPath": "../../packages",
|
|
68
|
+
"template": "build_node_component.yml"
|
|
71
69
|
}
|
|
72
70
|
}
|
|
73
71
|
}
|
|
74
72
|
```
|
|
75
73
|
|
|
74
|
+
Package names are read from each folder's `package.json`, `version.json`, or `pyproject.toml`.
|
|
75
|
+
|
|
76
76
|
---
|
|
77
77
|
|
|
78
78
|
## Template placeholders
|
|
@@ -82,71 +82,52 @@ Multiple output files and multiple variables per output file are supported:
|
|
|
82
82
|
The document template (the top-level YAML file) uses `{variableName}` placeholders that are replaced with the fully expanded set of job snippets:
|
|
83
83
|
|
|
84
84
|
```yaml
|
|
85
|
-
#
|
|
85
|
+
# build.yml (document template)
|
|
86
86
|
stages:
|
|
87
87
|
- stage: Build
|
|
88
88
|
jobs:
|
|
89
|
-
{
|
|
89
|
+
{PACKAGES}
|
|
90
90
|
```
|
|
91
91
|
|
|
92
|
-
### Supported manifest files
|
|
93
|
-
|
|
94
|
-
Each subfolder is scanned for a version manifest in this priority order:
|
|
95
|
-
|
|
96
|
-
| Priority | File | Format |
|
|
97
|
-
|---|---|---|
|
|
98
|
-
| 1 | `package.json` | JSON — `"name"` field |
|
|
99
|
-
| 2 | `version.json` | JSON — same shape as `package.json` |
|
|
100
|
-
| 3 | `pyproject.toml` | TOML — `name` field under `[project]` or `[tool.poetry]` |
|
|
101
|
-
|
|
102
|
-
When multiple manifest files exist in the same folder the highest-priority one is used. If none contains a `name` field the folder name is used as a fallback.
|
|
103
|
-
|
|
104
92
|
### Job template
|
|
105
93
|
|
|
106
94
|
Each job snippet template is expanded once per package. The following placeholders are available:
|
|
107
95
|
|
|
108
96
|
| Placeholder | Value |
|
|
109
97
|
|---|---|
|
|
110
|
-
| `{name}` | Package folder name (e.g. `my-component`) |
|
|
98
|
+
| `{name}` | Package folder name (from `cicd[pkg].folderName`, e.g. `my-component`) |
|
|
111
99
|
| `{safe}` | Folder name with `-`, `_`, and `.` removed — safe for use as a YAML identifier (e.g. `mycomponent`) |
|
|
112
100
|
| `{package}` | npm package name without scope prefix (e.g. `my-component` from `@scope/my-component`) |
|
|
113
101
|
| `{full}` | Full npm package name including scope (e.g. `@scope/my-component`) |
|
|
114
|
-
| `{path}` | The
|
|
115
|
-
| `{other}` | Expands to a comma-separated list of paths to every **other** package in the same
|
|
116
|
-
|
|
117
|
-
Example job template:
|
|
118
|
-
|
|
119
|
-
```yaml
|
|
120
|
-
- job: build_{safe}
|
|
121
|
-
displayName: Build {name}
|
|
122
|
-
steps:
|
|
123
|
-
- script: pnpm --filter {full} build
|
|
124
|
-
```
|
|
102
|
+
| `{path}` | The package folder root (from `cicd[pkg].packageFolder`, e.g. `packages`) |
|
|
103
|
+
| `{other}` | Expands to a comma-separated list of paths to every **other** package in the same variable — useful for pipeline dependency declarations |
|
|
125
104
|
|
|
126
105
|
---
|
|
127
106
|
|
|
128
107
|
## Directory layout
|
|
129
108
|
|
|
130
|
-
|
|
109
|
+
Job template files live in the templates subfolder; document templates and generated output files are one level up.
|
|
131
110
|
|
|
132
|
-
**Azure DevOps** (
|
|
111
|
+
**Azure DevOps** (when `.cicd/manifest.json` exists):
|
|
133
112
|
```
|
|
134
113
|
.cicd/
|
|
135
|
-
├──
|
|
114
|
+
├── manifest.json ← source of truth for templated config (preferred)
|
|
115
|
+
├── build.yml ← generated output
|
|
136
116
|
└── templates/
|
|
137
|
-
├──
|
|
138
|
-
├──
|
|
139
|
-
└──
|
|
117
|
+
├── build.yml ← document template
|
|
118
|
+
├── templated.json ← legacy config (used when manifest.json is absent)
|
|
119
|
+
└── build_node_component.yml ← job snippet template
|
|
140
120
|
```
|
|
141
121
|
|
|
142
|
-
**GitHub Actions** (when `.github
|
|
122
|
+
**GitHub Actions** (when `.github/manifest.json` exists):
|
|
143
123
|
```
|
|
144
124
|
.github/
|
|
145
|
-
├──
|
|
125
|
+
├── manifest.json ← source of truth for templated config (preferred)
|
|
126
|
+
├── build.yml ← generated output
|
|
146
127
|
└── templates/
|
|
147
|
-
├──
|
|
148
|
-
├──
|
|
149
|
-
└──
|
|
128
|
+
├── build.yml ← document template
|
|
129
|
+
├── templated.json ← legacy config (used when manifest.json is absent)
|
|
130
|
+
└── build_node_component.yml ← job snippet template
|
|
150
131
|
```
|
|
151
132
|
|
|
152
133
|
---
|
|
@@ -160,12 +141,6 @@ Run the generator as a pipeline step before the build stage:
|
|
|
160
141
|
displayName: Generate pipeline YAML
|
|
161
142
|
```
|
|
162
143
|
|
|
163
|
-
With a custom config location:
|
|
164
|
-
|
|
165
|
-
```bash
|
|
166
|
-
npx enablement-build-template-generate@latest --config .azuredevops/templates/templated.json
|
|
167
|
-
```
|
|
168
|
-
|
|
169
144
|
Excluding specific packages:
|
|
170
145
|
|
|
171
146
|
```bash
|
|
@@ -176,7 +151,7 @@ npx enablement-build-template-generate@latest --exclude legacy-pkg,test-harness
|
|
|
176
151
|
|
|
177
152
|
## Output format
|
|
178
153
|
|
|
179
|
-
Generated files are wrapped in `DO NOT EDIT` banners. Edit the templates
|
|
154
|
+
Generated files are wrapped in `DO NOT EDIT` banners. Edit the templates or re-generate via `manifest`, not the output directly:
|
|
180
155
|
|
|
181
156
|
```yaml
|
|
182
157
|
#######################################
|
package/package.json
CHANGED
|
@@ -1,17 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "enablement-build-template-generate",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.17",
|
|
4
4
|
"description": "Generates build yaml for mono repos",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./src/index.mjs",
|
|
7
7
|
"main": "./src/index.mjs",
|
|
8
|
-
"bin": "./src/index.mjs",
|
|
9
8
|
"dependencies": {
|
|
10
|
-
"globby": "^
|
|
9
|
+
"globby": "^16.2.0",
|
|
11
10
|
"user": "^0.0.0"
|
|
12
11
|
},
|
|
13
12
|
"devDependencies": {
|
|
14
|
-
"jest": "^
|
|
13
|
+
"jest": "^30.4.2"
|
|
15
14
|
},
|
|
16
15
|
"jest": {
|
|
17
16
|
"testEnvironment": "node",
|
|
@@ -25,5 +24,8 @@
|
|
|
25
24
|
"start": "node src/index.mjs",
|
|
26
25
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
27
26
|
"test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"enablement-build-template-generate": "./src/index.mjs"
|
|
28
30
|
}
|
|
29
31
|
}
|
package/src/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as url from 'url';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { globby } from 'globby';
|
|
5
4
|
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
5
|
+
import { globby } from 'globby';
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
stripWhitespace,
|
|
@@ -19,87 +19,181 @@ import {
|
|
|
19
19
|
|
|
20
20
|
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Read a job template file, falling back to the alternate platform folder
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
function readJobTemplate(templateFile, templatePath, fallbackTemplatePath) {
|
|
26
|
+
const primary = path.join(templatePath, templateFile);
|
|
27
|
+
if (existsSync(primary)) return readFileSync(primary, 'utf8');
|
|
28
|
+
if (fallbackTemplatePath) {
|
|
29
|
+
const fallback = path.join(fallbackTemplatePath, templateFile);
|
|
30
|
+
if (existsSync(fallback)) return readFileSync(fallback, 'utf8');
|
|
31
|
+
}
|
|
32
|
+
throw new Error(`Template file not found: ${templateFile} (looked in ${templatePath}${fallbackTemplatePath ? ' and ' + fallbackTemplatePath : ''})`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Expand using manifest.json "templated" section (new format)
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
function expandVarFromManifest(varname, packageMap, cicd, templatePath, fallbackTemplatePath, options) {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
try {
|
|
41
|
+
const packageNames = Object.keys(packageMap).filter(pkg => {
|
|
42
|
+
if (packageMap[pkg] === false) return false;
|
|
43
|
+
const folderName = (cicd[pkg] || {}).folderName || pkg;
|
|
44
|
+
return !options.exclude.includes(folderName);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const allFolderNames = packageNames.map(pkg => (cicd[pkg] || {}).folderName || pkg);
|
|
48
|
+
|
|
49
|
+
const folderCounts = {};
|
|
50
|
+
packageNames.forEach(pkg => {
|
|
51
|
+
const f = (cicd[pkg] || {}).packageFolder || 'packages';
|
|
52
|
+
folderCounts[f] = (folderCounts[f] || 0) + 1;
|
|
53
|
+
});
|
|
54
|
+
const commonPackageFolder = Object.keys(folderCounts).sort((a, b) => folderCounts[b] - folderCounts[a])[0] || 'packages';
|
|
55
|
+
const cwd = path.join(process.cwd(), commonPackageFolder);
|
|
56
|
+
|
|
57
|
+
const expandedJobs = packageNames.map(pkgName => {
|
|
58
|
+
const templateFiles = Array.isArray(packageMap[pkgName])
|
|
59
|
+
? packageMap[pkgName]
|
|
60
|
+
: [packageMap[pkgName]];
|
|
61
|
+
|
|
62
|
+
const info = cicd[pkgName] || {};
|
|
63
|
+
const name = info.folderName || pkgName;
|
|
64
|
+
const jobRoot = info.packageFolder || 'packages';
|
|
65
|
+
const safe = makeSafe(name);
|
|
66
|
+
const packageName = resolvePackageName(pkgName);
|
|
67
|
+
const metadata = info.metadata || {};
|
|
68
|
+
|
|
69
|
+
return templateFiles.map(templateFile => {
|
|
70
|
+
const jobTemplate = readJobTemplate(templateFile, templatePath, fallbackTemplatePath);
|
|
71
|
+
return expandJobTemplate(jobTemplate, { name, safe, packageName, fullName: pkgName, jobRoot, metadata });
|
|
72
|
+
}).join('\n');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const jobs = resolveOtherPlaceholders(expandedJobs.join('\n'), allFolderNames, cwd);
|
|
76
|
+
resolve({ name: varname, text: jobs });
|
|
77
|
+
} catch (e) {
|
|
78
|
+
reject(e);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Expand using templated.json (legacy format: rootPath + template glob)
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
function expandVarFromTemplatedJson(varname, jobConfig, templatePath, fallbackTemplatePath, options) {
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
const jobTemplate = readJobTemplate(jobConfig.template, templatePath, fallbackTemplatePath);
|
|
89
|
+
const cwd = path.join(templatePath, jobConfig.rootPath);
|
|
90
|
+
const search = buildSearchGlobs(options.exclude);
|
|
91
|
+
|
|
92
|
+
globby(search, { cwd }).then((files) => {
|
|
93
|
+
const folderBest = groupByFolder(files);
|
|
94
|
+
const allNames = Object.keys(folderBest).sort().filter(n => !options.exclude.includes(n));
|
|
95
|
+
|
|
96
|
+
const expandedJobs = allNames.map(name => {
|
|
97
|
+
const content = readFileSync(path.join(cwd, folderBest[name]), 'utf8');
|
|
98
|
+
const ext = path.extname(folderBest[name]).substring(1).toLowerCase();
|
|
99
|
+
const fullName = parseManifestName(content, ext) || name;
|
|
100
|
+
const safe = makeSafe(name);
|
|
101
|
+
const packageName = resolvePackageName(fullName);
|
|
102
|
+
const jobRoot = path.relative(process.cwd(), cwd);
|
|
103
|
+
return expandJobTemplate(jobTemplate, { name, safe, packageName, fullName, jobRoot, metadata: {} });
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
const jobs = resolveOtherPlaceholders(expandedJobs.join('\n'), allNames, cwd);
|
|
107
|
+
resolve({ name: varname, text: jobs });
|
|
108
|
+
}, reject);
|
|
109
|
+
});
|
|
26
110
|
}
|
|
27
111
|
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// main
|
|
114
|
+
// ---------------------------------------------------------------------------
|
|
28
115
|
async function main(options) {
|
|
29
|
-
|
|
30
|
-
const
|
|
116
|
+
const configDir = options.platform === 'github' ? '.github' : '.cicd';
|
|
117
|
+
const altConfigDir = options.platform === 'github' ? '.cicd' : '.github';
|
|
118
|
+
const templatePath = path.join(process.cwd(), `${configDir}/templates`);
|
|
119
|
+
const fallbackTemplatePath = path.join(process.cwd(), `${altConfigDir}/templates`);
|
|
31
120
|
|
|
32
|
-
|
|
33
|
-
|
|
121
|
+
// Try manifest.json first, then fall back to templated.json
|
|
122
|
+
const manifestPath = path.join(process.cwd(), `${configDir}/manifest.json`);
|
|
123
|
+
const templatedJsonPath = path.join(process.cwd(), `${configDir}/templates/templated.json`);
|
|
124
|
+
|
|
125
|
+
const useManifest = existsSync(manifestPath);
|
|
126
|
+
const useTemplatedJson = !useManifest && existsSync(templatedJsonPath);
|
|
127
|
+
|
|
128
|
+
if (!useManifest && !useTemplatedJson) {
|
|
129
|
+
throw `enablement-build-template-generate: could not find manifest.json at ${configDir}/manifest.json or templated.json at ${configDir}/templates/templated.json`;
|
|
34
130
|
}
|
|
35
131
|
|
|
36
|
-
|
|
37
|
-
|
|
132
|
+
let config;
|
|
133
|
+
let cicd = {};
|
|
134
|
+
|
|
135
|
+
if (useManifest) {
|
|
136
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
137
|
+
if (!manifest.templated) {
|
|
138
|
+
throw `enablement-build-template-generate: manifest.json has no "templated" section`;
|
|
139
|
+
}
|
|
140
|
+
config = manifest.templated;
|
|
141
|
+
cicd = manifest.cicd || {};
|
|
142
|
+
} else {
|
|
143
|
+
config = JSON.parse(readFileSync(templatedJsonPath, 'utf8'));
|
|
38
144
|
}
|
|
39
145
|
|
|
40
|
-
const templatePath = path.dirname(configFile);
|
|
41
|
-
const config = JSON.parse(readFileSync(configFile, 'utf8'));
|
|
42
|
-
const search = buildSearchGlobs(options.exclude);
|
|
43
146
|
const plist = [];
|
|
44
147
|
|
|
45
148
|
Object.keys(config).forEach((templateFilename) => {
|
|
46
|
-
plist.push(new Promise(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const allNames = Object.keys(folderBest).sort()
|
|
62
|
-
.filter(name => !name.startsWith('_') && !exclude.includes(name));
|
|
63
|
-
|
|
64
|
-
const expandedJobs = allNames.map(name => {
|
|
65
|
-
const fullName = readManifestName(path.join(cwd, folderBest[name].filePath)) || name;
|
|
66
|
-
const packageName = resolvePackageName(fullName);
|
|
67
|
-
const safe = makeSafe(name);
|
|
68
|
-
return expandJobTemplate(jobTemplate, { name, safe, packageName, fullName, jobRoot });
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
let jobs = resolveOtherPlaceholders(expandedJobs.join('\n'), allNames, cwd);
|
|
72
|
-
resolve({ name: varname, text: jobs });
|
|
73
|
-
}, reject);
|
|
149
|
+
plist.push(new Promise((primaryResolve, reject) => {
|
|
150
|
+
try {
|
|
151
|
+
const docTemplate = readFileSync(path.join(templatePath, templateFilename), 'utf8');
|
|
152
|
+
const jlist = [];
|
|
153
|
+
|
|
154
|
+
Object.keys(config[templateFilename]).forEach((varname) => {
|
|
155
|
+
const varConfig = config[templateFilename][varname];
|
|
156
|
+
|
|
157
|
+
if (useManifest) {
|
|
158
|
+
// New format: varConfig is { pkgName: "template.yml" | [...] }
|
|
159
|
+
jlist.push(expandVarFromManifest(varname, varConfig, cicd, templatePath, fallbackTemplatePath, options));
|
|
160
|
+
} else {
|
|
161
|
+
// Legacy format: varConfig is { rootPath, template, exclude? }
|
|
162
|
+
jlist.push(expandVarFromTemplatedJson(varname, varConfig, templatePath, fallbackTemplatePath, options));
|
|
163
|
+
}
|
|
74
164
|
});
|
|
75
|
-
jlist.push(job);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
Promise.all(jlist).then((results) => {
|
|
79
|
-
const output = applyVarsToDocTemplate(docTemplate, results);
|
|
80
|
-
const wrapped = wrapWithBanner(output);
|
|
81
|
-
|
|
82
|
-
if (stripWhitespace(wrapped) !== docTemplate) {
|
|
83
|
-
writeFileSync(path.join(templatePath, `../${templateFilename}`), wrapped, 'utf8');
|
|
84
|
-
}
|
|
85
165
|
|
|
86
|
-
|
|
87
|
-
|
|
166
|
+
Promise.all(jlist).then((results) => {
|
|
167
|
+
const output = applyVarsToDocTemplate(docTemplate, results);
|
|
168
|
+
const wrapped = wrapWithBanner(output);
|
|
169
|
+
|
|
170
|
+
const outPath = path.join(templatePath, `../${templateFilename}`);
|
|
171
|
+
const existing = existsSync(outPath) ? readFileSync(outPath, 'utf8') : '';
|
|
172
|
+
if (stripWhitespace(wrapped) !== stripWhitespace(existing)) {
|
|
173
|
+
writeFileSync(outPath, wrapped, 'utf8');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
primaryResolve(`generated template ${templateFilename}`);
|
|
177
|
+
}, reject);
|
|
178
|
+
} catch (e) {
|
|
179
|
+
reject(e);
|
|
180
|
+
}
|
|
88
181
|
}));
|
|
89
182
|
});
|
|
90
183
|
|
|
91
184
|
return Promise.all(plist);
|
|
92
185
|
}
|
|
93
186
|
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// CLI
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
94
190
|
let options = {
|
|
95
|
-
config: null,
|
|
96
191
|
platform: null,
|
|
97
192
|
exclude: [],
|
|
98
193
|
debug: false,
|
|
99
194
|
};
|
|
100
195
|
|
|
101
196
|
let argv = process.argv;
|
|
102
|
-
|
|
103
197
|
for (let i = 2; i < argv.length; i++) {
|
|
104
198
|
if (argv[i] === '--debug') { options.debug = true; continue; }
|
|
105
199
|
if (argv[i].substring(0, 2) === '--') {
|
|
@@ -111,21 +205,25 @@ for (let i = 2; i < argv.length; i++) {
|
|
|
111
205
|
options[name] = argv[i + 1];
|
|
112
206
|
i++;
|
|
113
207
|
} else {
|
|
114
|
-
console.error(
|
|
208
|
+
console.error(`Expected a known option, got ${argv[i]}`);
|
|
115
209
|
process.exit(1);
|
|
116
210
|
}
|
|
117
211
|
}
|
|
118
212
|
}
|
|
119
213
|
|
|
120
214
|
if (!options.platform) {
|
|
121
|
-
const
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
215
|
+
const hasGithubManifest = existsSync(path.join(process.cwd(), '.github/manifest.json'));
|
|
216
|
+
const hasAdoManifest = existsSync(path.join(process.cwd(), '.cicd/manifest.json'));
|
|
217
|
+
const hasGithubTemplated = existsSync(path.join(process.cwd(), '.github/templates/templated.json'));
|
|
218
|
+
const hasAdoTemplated = existsSync(path.join(process.cwd(), '.cicd/templates/templated.json'));
|
|
219
|
+
|
|
220
|
+
if (hasGithubManifest || hasGithubTemplated) {
|
|
221
|
+
options.platform = 'github';
|
|
222
|
+
} else if (hasAdoManifest || hasAdoTemplated) {
|
|
223
|
+
options.platform = 'ado';
|
|
224
|
+
} else {
|
|
225
|
+
options.platform = 'ado';
|
|
226
|
+
}
|
|
129
227
|
}
|
|
130
228
|
|
|
131
229
|
(async () => {
|
package/src/lib.mjs
CHANGED
|
@@ -70,14 +70,21 @@ export function makeSafe(name) {
|
|
|
70
70
|
|
|
71
71
|
// Expand a job template string for one package, substituting all known placeholders.
|
|
72
72
|
// {other} is replaced with a tagged sentinel {other:name} for later resolution.
|
|
73
|
-
export function expandJobTemplate(template, { name, safe, packageName, fullName, jobRoot }) {
|
|
74
|
-
|
|
73
|
+
export function expandJobTemplate(template, { name, safe, packageName, fullName, jobRoot, metadata = {} }) {
|
|
74
|
+
let output = template
|
|
75
75
|
.replace(/{other}/g, `{other:${name}}`)
|
|
76
76
|
.replace(/{path}/g, jobRoot)
|
|
77
77
|
.replace(/{name}/g, name)
|
|
78
78
|
.replace(/{safe}/g, safe)
|
|
79
79
|
.replace(/{package}/g, packageName)
|
|
80
80
|
.replace(/{full}/g, fullName);
|
|
81
|
+
|
|
82
|
+
Object.entries(metadata).forEach(([key, value]) => {
|
|
83
|
+
if (value === undefined || value === null) return;
|
|
84
|
+
output = output.replace(new RegExp(`\\{${key}\\}`, 'g'), String(value));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
return output;
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
// Resolve all {other:name} sentinels in the combined jobs string.
|