@mauvezero/azpipe-cli 0.3.0 → 0.5.0
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/dist/.tsbuildinfo +1 -1
- package/dist/bin.js +10 -0
- package/dist/bin.js.map +1 -1
- package/dist/cmd-bootstrap.d.ts +2 -0
- package/dist/cmd-bootstrap.d.ts.map +1 -0
- package/dist/cmd-bootstrap.js +510 -0
- package/dist/cmd-bootstrap.js.map +1 -0
- package/dist/cmd-build.d.ts.map +1 -1
- package/dist/cmd-build.js +39 -7
- package/dist/cmd-build.js.map +1 -1
- package/dist/cmd-gha-import.d.ts +3 -0
- package/dist/cmd-gha-import.d.ts.map +1 -0
- package/dist/cmd-gha-import.js +90 -0
- package/dist/cmd-gha-import.js.map +1 -0
- package/package.json +9 -8
package/dist/bin.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { runBuild } from './cmd-build.js';
|
|
3
3
|
import { runBuildImport } from './cmd-build-import.js';
|
|
4
4
|
import { runRelease } from './cmd-release.js';
|
|
5
|
+
import { runBootstrap } from './cmd-bootstrap.js';
|
|
6
|
+
import { runGha } from './cmd-gha-import.js';
|
|
5
7
|
function topLevelUsage() {
|
|
6
8
|
console.log(`azpipe <command> [options]
|
|
7
9
|
|
|
@@ -12,6 +14,8 @@ Commands:
|
|
|
12
14
|
release diff <entry.ts> Show what \`push\` would change
|
|
13
15
|
release push <entry.ts> Diff-first PUT (interactive by default)
|
|
14
16
|
release import <json> Convert a classic-release JSON to TypeScript
|
|
17
|
+
gha import <workflow.yml> Convert a GitHub Actions workflow to TypeScript
|
|
18
|
+
bootstrap Scaffold a pipelines/ directory in the current repo
|
|
15
19
|
|
|
16
20
|
Run \`azpipe <command> --help\` for command-specific options.`);
|
|
17
21
|
}
|
|
@@ -34,6 +38,12 @@ async function main() {
|
|
|
34
38
|
case 'release':
|
|
35
39
|
await runRelease(argv.slice(1));
|
|
36
40
|
return;
|
|
41
|
+
case 'gha':
|
|
42
|
+
await runGha(argv.slice(1));
|
|
43
|
+
return;
|
|
44
|
+
case 'bootstrap':
|
|
45
|
+
await runBootstrap(argv.slice(1));
|
|
46
|
+
return;
|
|
37
47
|
default:
|
|
38
48
|
console.error(`Unknown command: ${command}`);
|
|
39
49
|
topLevelUsage();
|
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,SAAS,aAAa;IACpB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;8DAYgD,CAAC,CAAC;AAChE,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzD,aAAa,EAAE,CAAC;QAChB,OAAO;IACT,CAAC;IACD,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,OAAO;YACV,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACzB,MAAM,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YACtC,CAAC;iBAAM,CAAC;gBACN,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAChC,CAAC;YACD,OAAO;QACT,KAAK,SAAS;YACZ,MAAM,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAChC,OAAO;QACT,KAAK,KAAK;YACR,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5B,OAAO;QACT,KAAK,WAAW;YACd,MAAM,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAClC,OAAO;QACT;YACE,OAAO,CAAC,KAAK,CAAC,oBAAoB,OAAO,EAAE,CAAC,CAAC;YAC7C,aAAa,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cmd-bootstrap.d.ts","sourceRoot":"","sources":["../src/cmd-bootstrap.ts"],"names":[],"mappings":"AAoYA,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA6KhE"}
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { createInterface } from 'node:readline';
|
|
4
|
+
import { dirname, isAbsolute, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { yamlToTs } from '@mauvezero/azpipe-convert';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// CLI version — stamped into generated package.json
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const cliPkgJson = JSON.parse(readFileSync(resolve(here, '../package.json'), 'utf8'));
|
|
12
|
+
const CLI_VERSION = cliPkgJson.version;
|
|
13
|
+
function parseArgs(argv) {
|
|
14
|
+
const opts = { yes: false, outDir: 'pipelines', help: false };
|
|
15
|
+
for (let i = 0; i < argv.length; i++) {
|
|
16
|
+
const a = argv[i];
|
|
17
|
+
if (a === '-h' || a === '--help')
|
|
18
|
+
opts.help = true;
|
|
19
|
+
else if (a === '--yes' || a === '-y')
|
|
20
|
+
opts.yes = true;
|
|
21
|
+
else if (a === '--out' || a === '-o')
|
|
22
|
+
opts.outDir = argv[++i] ?? opts.outDir;
|
|
23
|
+
else if (a.startsWith('-'))
|
|
24
|
+
throw new Error(`Unknown flag: ${a}`);
|
|
25
|
+
}
|
|
26
|
+
return opts;
|
|
27
|
+
}
|
|
28
|
+
function usage() {
|
|
29
|
+
console.log(`azpipe bootstrap [options]
|
|
30
|
+
|
|
31
|
+
Scaffold a pipelines/ directory in the current git repo with:
|
|
32
|
+
- pipelines/package.json (build / diff / push scripts)
|
|
33
|
+
- pipelines/tsconfig.json
|
|
34
|
+
- pipelines/README.md
|
|
35
|
+
- pipelines/azure-pipelines.ts (CI pipeline, converted from root yml if present)
|
|
36
|
+
- pipelines/release.ts (release definition stub)
|
|
37
|
+
- pipelines/release-sync.yml (YAML pipeline to diff/push releases)
|
|
38
|
+
- <root>/azure-pipelines.yml (generated from azure-pipelines.ts)
|
|
39
|
+
|
|
40
|
+
Options:
|
|
41
|
+
-o, --out <dir> Output directory relative to git root (default: pipelines)
|
|
42
|
+
-y, --yes Accept all defaults, skip interactive prompts
|
|
43
|
+
-h, --help Show this help`);
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Git helpers
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
function findGitRoot(from) {
|
|
49
|
+
let dir = isAbsolute(from) ? from : resolve(process.cwd(), from);
|
|
50
|
+
while (true) {
|
|
51
|
+
if (existsSync(join(dir, '.git')))
|
|
52
|
+
return dir;
|
|
53
|
+
const parent = dirname(dir);
|
|
54
|
+
if (parent === dir)
|
|
55
|
+
throw new Error('Not inside a git repository.');
|
|
56
|
+
dir = parent;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function tryExec(cmd) {
|
|
60
|
+
try {
|
|
61
|
+
return execSync(cmd, { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf8' }).trim();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function detectDefaultBranch() {
|
|
68
|
+
const ref = tryExec('git symbolic-ref refs/remotes/origin/HEAD');
|
|
69
|
+
if (ref) {
|
|
70
|
+
const m = ref.match(/refs\/remotes\/origin\/(.+)/);
|
|
71
|
+
if (m?.[1])
|
|
72
|
+
return m[1];
|
|
73
|
+
}
|
|
74
|
+
// Fallback: look for common branch names
|
|
75
|
+
const branches = tryExec('git branch -r');
|
|
76
|
+
if (branches?.includes('origin/main'))
|
|
77
|
+
return 'main';
|
|
78
|
+
if (branches?.includes('origin/master'))
|
|
79
|
+
return 'master';
|
|
80
|
+
return 'main';
|
|
81
|
+
}
|
|
82
|
+
function detectFromGitRemote() {
|
|
83
|
+
const remote = tryExec('git remote get-url origin');
|
|
84
|
+
if (!remote)
|
|
85
|
+
return {};
|
|
86
|
+
// HTTPS: https://dev.azure.com/ORG/PROJECT/_git/REPO
|
|
87
|
+
const httpsMatch = remote.match(/dev\.azure\.com\/([^/]+)\/([^/]+)/);
|
|
88
|
+
if (httpsMatch)
|
|
89
|
+
return { org: httpsMatch[1], project: httpsMatch[2] };
|
|
90
|
+
// SSH: git@ssh.dev.azure.com:v3/ORG/PROJECT/REPO
|
|
91
|
+
const sshMatch = remote.match(/v3\/([^/]+)\/([^/]+)/);
|
|
92
|
+
if (sshMatch)
|
|
93
|
+
return { org: sshMatch[1], project: sshMatch[2] };
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
function makePrompter(rl) {
|
|
97
|
+
const prompt = (question, defaultValue) => new Promise((resolve) => {
|
|
98
|
+
const suffix = defaultValue ? ` [${defaultValue}]` : '';
|
|
99
|
+
rl.question(` ${question}${suffix}: `, (answer) => {
|
|
100
|
+
resolve(answer.trim() || defaultValue || '');
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
const yesNo = (question, defaultYes = true) => new Promise((resolve) => {
|
|
104
|
+
const suffix = defaultYes ? ' [Y/n]' : ' [y/N]';
|
|
105
|
+
rl.question(` ${question}${suffix}: `, (answer) => {
|
|
106
|
+
const a = answer.trim().toLowerCase();
|
|
107
|
+
if (a === '')
|
|
108
|
+
resolve(defaultYes);
|
|
109
|
+
else
|
|
110
|
+
resolve(a === 'y' || a === 'yes');
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
return { prompt, yesNo };
|
|
114
|
+
}
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// Template generators
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
118
|
+
function packageJsonTemplate(org, project) {
|
|
119
|
+
void org;
|
|
120
|
+
void project; // Reserved for future use (e.g. to embed metadata)
|
|
121
|
+
const deps = {
|
|
122
|
+
'@mauvezero/azpipe': CLI_VERSION,
|
|
123
|
+
'@mauvezero/azpipe-releases': CLI_VERSION,
|
|
124
|
+
'@mauvezero/azpipe-tasks': CLI_VERSION,
|
|
125
|
+
'@mauvezero/azpipe-utils': CLI_VERSION,
|
|
126
|
+
'@mauvezero/azpipe-cli': CLI_VERSION,
|
|
127
|
+
};
|
|
128
|
+
const pkg = {
|
|
129
|
+
name: 'pipelines',
|
|
130
|
+
private: true,
|
|
131
|
+
type: 'module',
|
|
132
|
+
scripts: {
|
|
133
|
+
build: 'azpipe build azure-pipelines.ts --out ../azure-pipelines.yml',
|
|
134
|
+
diff: 'azpipe release diff release.ts',
|
|
135
|
+
'push:dry': 'azpipe release push release.ts --dry-run',
|
|
136
|
+
push: 'azpipe release push release.ts',
|
|
137
|
+
},
|
|
138
|
+
dependencies: deps,
|
|
139
|
+
};
|
|
140
|
+
return JSON.stringify(pkg, null, 2) + '\n';
|
|
141
|
+
}
|
|
142
|
+
function tsconfigTemplate() {
|
|
143
|
+
return JSON.stringify({
|
|
144
|
+
compilerOptions: {
|
|
145
|
+
target: 'ES2022',
|
|
146
|
+
module: 'NodeNext',
|
|
147
|
+
moduleResolution: 'NodeNext',
|
|
148
|
+
lib: ['ES2022'],
|
|
149
|
+
strict: true,
|
|
150
|
+
noUncheckedIndexedAccess: true,
|
|
151
|
+
noImplicitOverride: true,
|
|
152
|
+
esModuleInterop: true,
|
|
153
|
+
forceConsistentCasingInFileNames: true,
|
|
154
|
+
skipLibCheck: true,
|
|
155
|
+
noEmit: true,
|
|
156
|
+
resolveJsonModule: true,
|
|
157
|
+
},
|
|
158
|
+
include: ['**/*.ts'],
|
|
159
|
+
}, null, 2) + '\n';
|
|
160
|
+
}
|
|
161
|
+
function pipelineStubTemplate() {
|
|
162
|
+
return `import { pipeline, script, checkout } from '@mauvezero/azpipe';
|
|
163
|
+
|
|
164
|
+
export default pipeline()
|
|
165
|
+
.name('CI $(Date:yyyyMMdd).$(Rev:r)')
|
|
166
|
+
.trigger({ branches: { include: ['main'] } })
|
|
167
|
+
.pr({ branches: { include: ['main'] } })
|
|
168
|
+
.pool({ vmImage: 'ubuntu-latest' })
|
|
169
|
+
.stage('Build', (s) =>
|
|
170
|
+
s.job('build', (j) =>
|
|
171
|
+
j
|
|
172
|
+
.step(checkout('self', { fetchDepth: 1 }))
|
|
173
|
+
.step(script('echo "Add your build steps here"', { displayName: 'Build' })),
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
`;
|
|
177
|
+
}
|
|
178
|
+
function releaseStubTemplate(org, project, releaseName) {
|
|
179
|
+
return `import { releasePipeline, buildArtifact } from '@mauvezero/azpipe-releases';
|
|
180
|
+
|
|
181
|
+
// Replace definitionId with the ID of your Azure Pipelines build pipeline.
|
|
182
|
+
const artifact = buildArtifact({
|
|
183
|
+
alias: 'drop',
|
|
184
|
+
definitionId: 0, // TODO: replace with your build pipeline's definition ID
|
|
185
|
+
defaultVersionType: 'latestType',
|
|
186
|
+
isPrimary: true,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
export default releasePipeline({
|
|
190
|
+
org: '${org}',
|
|
191
|
+
project: '${project}',
|
|
192
|
+
name: '${releaseName}',
|
|
193
|
+
releaseNameFormat: 'Release-$(rev:r)',
|
|
194
|
+
description: 'Managed by azpipe.',
|
|
195
|
+
})
|
|
196
|
+
.artifact(artifact)
|
|
197
|
+
.trigger(artifact)
|
|
198
|
+
.environment('staging', (e) =>
|
|
199
|
+
e.agentPhase((p) =>
|
|
200
|
+
p
|
|
201
|
+
.name('Deploy to staging')
|
|
202
|
+
.pool({ vmImage: 'ubuntu-latest' })
|
|
203
|
+
// TODO: add your deployment steps here
|
|
204
|
+
),
|
|
205
|
+
);
|
|
206
|
+
`;
|
|
207
|
+
}
|
|
208
|
+
function releaseSyncYmlTemplate(defaultBranch, syncPipelineName, org, project) {
|
|
209
|
+
return `# ============================================================
|
|
210
|
+
# Source: pipelines/release-sync.yml
|
|
211
|
+
# This file is NOT compiled from TypeScript — it IS the source.
|
|
212
|
+
#
|
|
213
|
+
# Pipeline name in Azure DevOps: ${syncPipelineName}
|
|
214
|
+
#
|
|
215
|
+
# After registering this pipeline in Azure DevOps, grant the
|
|
216
|
+
# build service account "Release Definition Contributor" in:
|
|
217
|
+
# ${project} project → Project Settings → Permissions
|
|
218
|
+
# Search for: "${project} Build Service (${org})"
|
|
219
|
+
# ============================================================
|
|
220
|
+
name: ${syncPipelineName}
|
|
221
|
+
|
|
222
|
+
trigger:
|
|
223
|
+
branches:
|
|
224
|
+
include:
|
|
225
|
+
- ${defaultBranch}
|
|
226
|
+
paths:
|
|
227
|
+
include:
|
|
228
|
+
- pipelines/**
|
|
229
|
+
|
|
230
|
+
pr:
|
|
231
|
+
branches:
|
|
232
|
+
include:
|
|
233
|
+
- ${defaultBranch}
|
|
234
|
+
paths:
|
|
235
|
+
include:
|
|
236
|
+
- pipelines/**
|
|
237
|
+
|
|
238
|
+
pool:
|
|
239
|
+
vmImage: ubuntu-latest
|
|
240
|
+
|
|
241
|
+
variables:
|
|
242
|
+
AZURE_DEVOPS_ORG: '${org}'
|
|
243
|
+
AZURE_DEVOPS_PROJECT: '${project}'
|
|
244
|
+
|
|
245
|
+
steps:
|
|
246
|
+
- checkout: self
|
|
247
|
+
|
|
248
|
+
- task: NodeTool@0
|
|
249
|
+
inputs:
|
|
250
|
+
versionSpec: '20.x'
|
|
251
|
+
displayName: Install Node
|
|
252
|
+
|
|
253
|
+
- script: npm ci
|
|
254
|
+
workingDirectory: pipelines
|
|
255
|
+
displayName: Install dependencies
|
|
256
|
+
|
|
257
|
+
- script: npx azpipe release diff release.ts
|
|
258
|
+
workingDirectory: pipelines
|
|
259
|
+
displayName: Diff release definitions
|
|
260
|
+
# Non-zero exit means drift detected. On a PR this is expected and informational.
|
|
261
|
+
continueOnError: true
|
|
262
|
+
env:
|
|
263
|
+
AZURE_DEVOPS_ORG: $(AZURE_DEVOPS_ORG)
|
|
264
|
+
AZURE_DEVOPS_PROJECT: $(AZURE_DEVOPS_PROJECT)
|
|
265
|
+
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
|
266
|
+
|
|
267
|
+
- script: npx azpipe release push release.ts --yes
|
|
268
|
+
workingDirectory: pipelines
|
|
269
|
+
displayName: Push release definitions
|
|
270
|
+
# Only runs on the default branch, not on PRs.
|
|
271
|
+
condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'))
|
|
272
|
+
env:
|
|
273
|
+
AZURE_DEVOPS_ORG: $(AZURE_DEVOPS_ORG)
|
|
274
|
+
AZURE_DEVOPS_PROJECT: $(AZURE_DEVOPS_PROJECT)
|
|
275
|
+
SYSTEM_ACCESSTOKEN: $(System.AccessToken)
|
|
276
|
+
`;
|
|
277
|
+
}
|
|
278
|
+
const GENERATED_BANNER = `# ============================================================
|
|
279
|
+
# GENERATED FILE – do not edit by hand.
|
|
280
|
+
# Source: pipelines/azure-pipelines.ts
|
|
281
|
+
# Regenerate: cd pipelines && npm run build
|
|
282
|
+
# ============================================================
|
|
283
|
+
`;
|
|
284
|
+
function readmeTemplate(org, project, syncPipelineName, defaultBranch) {
|
|
285
|
+
return `# pipelines/
|
|
286
|
+
|
|
287
|
+
This directory is managed by [azpipe](https://github.com/mauve/releases).
|
|
288
|
+
It contains TypeScript sources for Azure Pipelines CI and Classic Release definitions.
|
|
289
|
+
|
|
290
|
+
## Contents
|
|
291
|
+
|
|
292
|
+
| File | Description |
|
|
293
|
+
|---|---|
|
|
294
|
+
| \`azure-pipelines.ts\` | CI pipeline source (TypeScript) |
|
|
295
|
+
| \`release.ts\` | Release definition source (TypeScript) |
|
|
296
|
+
| \`release-sync.yml\` | Azure Pipelines YAML that diffs and pushes release definitions |
|
|
297
|
+
| \`package.json\` | npm scripts to build, diff, and push |
|
|
298
|
+
| \`tsconfig.json\` | TypeScript config (self-contained) |
|
|
299
|
+
|
|
300
|
+
## Regenerate \`azure-pipelines.yml\`
|
|
301
|
+
|
|
302
|
+
The root \`azure-pipelines.yml\` is **generated** from \`azure-pipelines.ts\`. Never edit it by hand.
|
|
303
|
+
|
|
304
|
+
\`\`\`bash
|
|
305
|
+
cd pipelines
|
|
306
|
+
npm install
|
|
307
|
+
npm run build # writes ../azure-pipelines.yml
|
|
308
|
+
\`\`\`
|
|
309
|
+
|
|
310
|
+
Commit both \`azure-pipelines.ts\` and the regenerated \`azure-pipelines.yml\`.
|
|
311
|
+
|
|
312
|
+
## Managing release definitions
|
|
313
|
+
|
|
314
|
+
Release definitions are authored as TypeScript files and pushed to Azure DevOps via the CLI.
|
|
315
|
+
|
|
316
|
+
\`\`\`bash
|
|
317
|
+
cd pipelines
|
|
318
|
+
npm run diff # show drift between local and server
|
|
319
|
+
npm run push:dry # dry run – shows what would change
|
|
320
|
+
npm run push # interactive push
|
|
321
|
+
\`\`\`
|
|
322
|
+
|
|
323
|
+
## Adding a new release pipeline
|
|
324
|
+
|
|
325
|
+
1. Create a new \`.ts\` file (e.g. \`release-backend.ts\`) using \`releasePipeline()\`.
|
|
326
|
+
2. Add a \`diff\` and \`push\` step for it in \`release-sync.yml\`.
|
|
327
|
+
3. Open a PR — the \`${syncPipelineName}\` pipeline will diff and report drift.
|
|
328
|
+
4. On merge to \`${defaultBranch}\`, the pipeline pushes the definition automatically.
|
|
329
|
+
|
|
330
|
+
## Credentials
|
|
331
|
+
|
|
332
|
+
\`release-sync.yml\` uses the pipeline's built-in job token (\`SYSTEM_ACCESSTOKEN\`) to
|
|
333
|
+
authenticate against the Azure DevOps Releases API — no service principal or client
|
|
334
|
+
secret required.
|
|
335
|
+
|
|
336
|
+
One-time setup after registering the pipeline:
|
|
337
|
+
|
|
338
|
+
1. Go to **${org}** → **${project}** → Project Settings → **Permissions**.
|
|
339
|
+
2. Search for **${project} Build Service (${org})**.
|
|
340
|
+
3. Grant it the **Release Definition Contributor** role.
|
|
341
|
+
`;
|
|
342
|
+
}
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
// Main
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
export async function runBootstrap(argv) {
|
|
347
|
+
const opts = parseArgs(argv);
|
|
348
|
+
if (opts.help) {
|
|
349
|
+
usage();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
// -- Detect git root -------------------------------------------------------
|
|
353
|
+
const gitRoot = findGitRoot(process.cwd());
|
|
354
|
+
console.log(`Detected repo root: ${gitRoot}`);
|
|
355
|
+
// -- Detect default branch -------------------------------------------------
|
|
356
|
+
const defaultBranch = detectDefaultBranch();
|
|
357
|
+
console.log(`Detected default branch: ${defaultBranch}`);
|
|
358
|
+
// -- Detect org / project --------------------------------------------------
|
|
359
|
+
const fromRemote = detectFromGitRemote();
|
|
360
|
+
const detectedOrg = process.env['AZURE_DEVOPS_ORG'] ?? fromRemote.org ?? '';
|
|
361
|
+
const detectedProject = process.env['AZURE_DEVOPS_PROJECT'] ?? fromRemote.project ?? '';
|
|
362
|
+
if (detectedOrg)
|
|
363
|
+
console.log(`Detected org: ${detectedOrg}`);
|
|
364
|
+
if (detectedProject)
|
|
365
|
+
console.log(`Detected project: ${detectedProject}`);
|
|
366
|
+
// -- Detect existing azure-pipelines.yml -----------------------------------
|
|
367
|
+
const rootYml = join(gitRoot, 'azure-pipelines.yml');
|
|
368
|
+
const hasRootYml = existsSync(rootYml);
|
|
369
|
+
if (hasRootYml)
|
|
370
|
+
console.log(`Detected existing azure-pipelines.yml at repo root.`);
|
|
371
|
+
// -- Interactive wizard ----------------------------------------------------
|
|
372
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
373
|
+
const { prompt, yesNo } = makePrompter(rl);
|
|
374
|
+
let org = detectedOrg;
|
|
375
|
+
let project = detectedProject;
|
|
376
|
+
let releaseName = '';
|
|
377
|
+
let syncPipelineName = '';
|
|
378
|
+
let convertYml = hasRootYml;
|
|
379
|
+
if (!opts.yes) {
|
|
380
|
+
org = await prompt('Azure DevOps org', detectedOrg || undefined);
|
|
381
|
+
project = await prompt('Azure DevOps project', detectedProject || undefined);
|
|
382
|
+
const defaultSyncName = project
|
|
383
|
+
? `${project.charAt(0).toUpperCase()}${project.slice(1)}ReleasesSync`
|
|
384
|
+
: 'ReleasesSync';
|
|
385
|
+
syncPipelineName = await prompt('Sync pipeline name (Azure DevOps display name)', defaultSyncName);
|
|
386
|
+
releaseName = await prompt('Release definition name (e.g. my-service-release)');
|
|
387
|
+
if (hasRootYml) {
|
|
388
|
+
convertYml = await yesNo('Convert existing azure-pipelines.yml to TypeScript?', true);
|
|
389
|
+
}
|
|
390
|
+
const proceed = await yesNo('Create/overwrite files in pipelines/?', true);
|
|
391
|
+
rl.close();
|
|
392
|
+
if (!proceed) {
|
|
393
|
+
console.log('Aborted.');
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
rl.close();
|
|
399
|
+
org = org || '';
|
|
400
|
+
project = project || '';
|
|
401
|
+
const defaultSyncName = project
|
|
402
|
+
? `${project.charAt(0).toUpperCase()}${project.slice(1)}ReleasesSync`
|
|
403
|
+
: 'ReleasesSync';
|
|
404
|
+
syncPipelineName = defaultSyncName;
|
|
405
|
+
releaseName = `${project || 'my-service'}-release`;
|
|
406
|
+
}
|
|
407
|
+
if (!org) {
|
|
408
|
+
console.error('Org is required. Set AZURE_DEVOPS_ORG or pass it when prompted.\n' +
|
|
409
|
+
'You can also edit pipelines/release.ts and pipelines/release-sync.yml manually.');
|
|
410
|
+
}
|
|
411
|
+
if (!project) {
|
|
412
|
+
console.error('Project is required. Set AZURE_DEVOPS_PROJECT or pass it when prompted.\n' +
|
|
413
|
+
'You can also edit pipelines/release.ts and pipelines/release-sync.yml manually.');
|
|
414
|
+
}
|
|
415
|
+
// -- Prepare output directory ----------------------------------------------
|
|
416
|
+
const outDir = isAbsolute(opts.outDir)
|
|
417
|
+
? opts.outDir
|
|
418
|
+
: join(gitRoot, opts.outDir);
|
|
419
|
+
mkdirSync(outDir, { recursive: true });
|
|
420
|
+
// -- Generate azure-pipelines.ts (convert or stub) -------------------------
|
|
421
|
+
let pipelineTs;
|
|
422
|
+
if (convertYml && hasRootYml) {
|
|
423
|
+
console.log('Converting azure-pipelines.yml → azure-pipelines.ts…');
|
|
424
|
+
const yamlText = readFileSync(rootYml, 'utf8');
|
|
425
|
+
const result = await yamlToTs(yamlText, {
|
|
426
|
+
prettier: true,
|
|
427
|
+
emitTemplates: false,
|
|
428
|
+
inlineTemplates: false,
|
|
429
|
+
entryFileName: 'azure-pipelines.ts',
|
|
430
|
+
outputDir: '.',
|
|
431
|
+
loadTemplate: () => undefined,
|
|
432
|
+
});
|
|
433
|
+
for (const w of result.warnings)
|
|
434
|
+
console.error(` warning: ${w}`);
|
|
435
|
+
pipelineTs = result.files[0]?.contents ?? pipelineStubTemplate();
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
pipelineTs = pipelineStubTemplate();
|
|
439
|
+
}
|
|
440
|
+
// -- Generate azure-pipelines.yml at repo root (stub pipeline serialized) --
|
|
441
|
+
// We do a best-effort in-process build using the stub as source.
|
|
442
|
+
// For the converted case, we don't re-emit from TS (that requires tsx at
|
|
443
|
+
// bootstrap time); instead we copy the original YAML with a banner prepended.
|
|
444
|
+
let rootYmlContent;
|
|
445
|
+
if (convertYml && hasRootYml) {
|
|
446
|
+
// Prepend the banner to the original YAML.
|
|
447
|
+
const originalYml = readFileSync(rootYml, 'utf8');
|
|
448
|
+
if (!originalYml.startsWith('# ===')) {
|
|
449
|
+
rootYmlContent = GENERATED_BANNER + originalYml;
|
|
450
|
+
}
|
|
451
|
+
else {
|
|
452
|
+
rootYmlContent = originalYml; // Already has the banner
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
// Generate a minimal YAML for the stub pipeline.
|
|
457
|
+
rootYmlContent =
|
|
458
|
+
GENERATED_BANNER +
|
|
459
|
+
[
|
|
460
|
+
'name: CI $(Date:yyyyMMdd).$(Rev:r)',
|
|
461
|
+
'trigger:',
|
|
462
|
+
' branches:',
|
|
463
|
+
' include:',
|
|
464
|
+
' - main',
|
|
465
|
+
'pr:',
|
|
466
|
+
' branches:',
|
|
467
|
+
' include:',
|
|
468
|
+
' - main',
|
|
469
|
+
'pool:',
|
|
470
|
+
" vmImage: ubuntu-latest",
|
|
471
|
+
'stages:',
|
|
472
|
+
' - stage: Build',
|
|
473
|
+
' jobs:',
|
|
474
|
+
' - job: build',
|
|
475
|
+
' steps:',
|
|
476
|
+
" - checkout: self",
|
|
477
|
+
" - script: echo \"Add your build steps here\"",
|
|
478
|
+
' displayName: Build',
|
|
479
|
+
].join('\n') + '\n';
|
|
480
|
+
}
|
|
481
|
+
// -- Write all files -------------------------------------------------------
|
|
482
|
+
function write(filePath, content) {
|
|
483
|
+
writeFileSync(filePath, content, 'utf8');
|
|
484
|
+
console.log(` Wrote ${filePath}`);
|
|
485
|
+
}
|
|
486
|
+
write(join(outDir, 'package.json'), packageJsonTemplate(org, project));
|
|
487
|
+
write(join(outDir, 'tsconfig.json'), tsconfigTemplate());
|
|
488
|
+
write(join(outDir, 'README.md'), readmeTemplate(org, project, syncPipelineName, defaultBranch));
|
|
489
|
+
write(join(outDir, 'azure-pipelines.ts'), pipelineTs);
|
|
490
|
+
write(join(outDir, 'release.ts'), releaseStubTemplate(org, project, releaseName));
|
|
491
|
+
write(join(outDir, 'release-sync.yml'), releaseSyncYmlTemplate(defaultBranch, syncPipelineName, org, project));
|
|
492
|
+
write(rootYml, rootYmlContent);
|
|
493
|
+
console.log(`
|
|
494
|
+
Bootstrap complete!
|
|
495
|
+
|
|
496
|
+
Next steps:
|
|
497
|
+
1. cd ${opts.outDir} && npm install
|
|
498
|
+
2. npm run build # regenerate azure-pipelines.yml
|
|
499
|
+
3. Edit release.ts # add your artifact source and environments
|
|
500
|
+
4. npm run diff # check drift vs Azure DevOps
|
|
501
|
+
5. Register release-sync.yml in Azure DevOps as a new pipeline
|
|
502
|
+
(point it at: pipelines/release-sync.yml)
|
|
503
|
+
6. Grant the build service account permission to manage releases:
|
|
504
|
+
${org ? `https://dev.azure.com/${org}/${project || '<project>'}/_settings/permissions` : '<org>/<project> → Project Settings → Permissions'}
|
|
505
|
+
Search for: "${project || '<project>'} Build Service (${org || '<org>'})"
|
|
506
|
+
Grant role: Release Definition Contributor
|
|
507
|
+
|
|
508
|
+
See pipelines/README.md for full documentation.`);
|
|
509
|
+
}
|
|
510
|
+
//# sourceMappingURL=cmd-bootstrap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cmd-bootstrap.js","sourceRoot":"","sources":["../src/cmd-bootstrap.ts"],"names":[],"mappings":"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"}
|
package/dist/cmd-build.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cmd-build.d.ts","sourceRoot":"","sources":["../src/cmd-build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cmd-build.d.ts","sourceRoot":"","sources":["../src/cmd-build.ts"],"names":[],"mappings":"AAsHA,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAqD5D"}
|
package/dist/cmd-build.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
2
|
-
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
2
|
+
import { basename, dirname, extname, isAbsolute, resolve } from 'node:path';
|
|
3
3
|
import { stringify as yamlStringify } from 'yaml';
|
|
4
4
|
import { toYaml, toJson, validatePipeline, formatValidationErrors } from '@mauvezero/azpipe-core';
|
|
5
5
|
import { PipelineBuilder } from '@mauvezero/azpipe';
|
|
6
|
+
import { WorkflowBuilder } from '@mauvezero/ghactions';
|
|
6
7
|
import { renderTemplateFile, } from '@mauvezero/azpipe-utils';
|
|
7
8
|
import { loadEntry } from './loader.js';
|
|
8
9
|
function parseArgs(argv) {
|
|
@@ -13,14 +14,17 @@ function parseArgs(argv) {
|
|
|
13
14
|
validate: true,
|
|
14
15
|
templatesDir: '',
|
|
15
16
|
help: false,
|
|
17
|
+
outExplicit: false,
|
|
16
18
|
};
|
|
17
19
|
const positional = [];
|
|
18
20
|
for (let i = 0; i < argv.length; i++) {
|
|
19
21
|
const a = argv[i];
|
|
20
22
|
if (a === '-h' || a === '--help')
|
|
21
23
|
opts.help = true;
|
|
22
|
-
else if (a === '--out' || a === '-o')
|
|
24
|
+
else if (a === '--out' || a === '-o') {
|
|
23
25
|
opts.out = argv[++i] ?? opts.out;
|
|
26
|
+
opts.outExplicit = true;
|
|
27
|
+
}
|
|
24
28
|
else if (a === '--format' || a === '-f') {
|
|
25
29
|
const v = argv[++i];
|
|
26
30
|
if (v !== 'yaml' && v !== 'json')
|
|
@@ -44,14 +48,17 @@ function usage() {
|
|
|
44
48
|
console.log(`azpipe build <entry.ts> [options]
|
|
45
49
|
|
|
46
50
|
Compiles a TypeScript file that default-exports a PipelineBuilder (or plain
|
|
47
|
-
pipeline object) into azure-pipelines.yml
|
|
51
|
+
pipeline object) into azure-pipelines.yml, or a WorkflowBuilder into a
|
|
52
|
+
GitHub Actions workflow YAML file.
|
|
48
53
|
|
|
49
54
|
Options:
|
|
50
|
-
-o, --out <file> Output file
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
-o, --out <file> Output file
|
|
56
|
+
Azure Pipelines default: azure-pipelines.yml
|
|
57
|
+
GitHub Actions default: .github/workflows/<name>.yml
|
|
58
|
+
-f, --format yaml|json Output format (default: yaml; Azure Pipelines only)
|
|
59
|
+
--no-validate Skip schema validation (Azure Pipelines only)
|
|
53
60
|
--templates-dir <d> Override directory for emitted typed templates
|
|
54
|
-
(default: dirname(--out)/templates)
|
|
61
|
+
(default: dirname(--out)/templates; Azure Pipelines only)
|
|
55
62
|
-h, --help Show this help`);
|
|
56
63
|
}
|
|
57
64
|
function pipelineObject(value) {
|
|
@@ -59,6 +66,18 @@ function pipelineObject(value) {
|
|
|
59
66
|
return value.toObject();
|
|
60
67
|
return value;
|
|
61
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* Derive a default output path for a GitHub Actions workflow.
|
|
71
|
+
* If the builder has a name, slugify it; otherwise fall back to the entry
|
|
72
|
+
* file stem. Always resolves under `.github/workflows/`.
|
|
73
|
+
*/
|
|
74
|
+
function defaultWorkflowOut(workflowName, entry) {
|
|
75
|
+
const slug = (workflowName ?? basename(entry, extname(entry)))
|
|
76
|
+
.toLowerCase()
|
|
77
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
78
|
+
.replace(/^-+|-+$/g, '');
|
|
79
|
+
return `.github/workflows/${slug || 'workflow'}.yml`;
|
|
80
|
+
}
|
|
62
81
|
function ensureDir(filePath) {
|
|
63
82
|
mkdirSync(dirname(filePath), { recursive: true });
|
|
64
83
|
}
|
|
@@ -86,6 +105,19 @@ export async function runBuild(argv) {
|
|
|
86
105
|
const mod = await loadEntry(opts.entry);
|
|
87
106
|
if (!mod.default)
|
|
88
107
|
throw new Error(`Entry ${opts.entry} must default-export a pipeline.`);
|
|
108
|
+
// ---------- GitHub Actions path ----------
|
|
109
|
+
if (mod.default instanceof WorkflowBuilder) {
|
|
110
|
+
const workflowObj = mod.default.toObject();
|
|
111
|
+
const outPath = opts.outExplicit
|
|
112
|
+
? opts.out
|
|
113
|
+
: defaultWorkflowOut(workflowObj.name, opts.entry);
|
|
114
|
+
const outAbs = isAbsolute(outPath) ? outPath : resolve(process.cwd(), outPath);
|
|
115
|
+
ensureDir(outAbs);
|
|
116
|
+
writeFileSync(outAbs, mod.default.toYaml());
|
|
117
|
+
console.log(`Wrote ${outAbs}`);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
// ---------- Azure Pipelines path ----------
|
|
89
121
|
const obj = pipelineObject(mod.default);
|
|
90
122
|
if (opts.validate) {
|
|
91
123
|
const result = validatePipeline(obj);
|