@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/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;AAE9C,SAAS,aAAa;IACpB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;8DAUgD,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;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"}
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,2 @@
1
+ export declare function runBootstrap(argv: string[]): Promise<void>;
2
+ //# sourceMappingURL=cmd-bootstrap.d.ts.map
@@ -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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"}
@@ -1 +1 @@
1
- {"version":3,"file":"cmd-build.d.ts","sourceRoot":"","sources":["../src/cmd-build.ts"],"names":[],"mappings":"AA6FA,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAsC5D"}
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 (default: azure-pipelines.yml)
51
- -f, --format yaml|json Output format (default: yaml)
52
- --no-validate Skip schema validation
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);