azdo-release-env 1.0.2 → 1.0.3
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/index.min.js +2 -0
- package/package.json +8 -5
- package/src/index.js +0 -458
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{Command}from"commander";import*as inquirer from"@inquirer/prompts";import{execa}from"execa";import fs from"node:fs/promises";import path from"node:path";import chalk from"chalk";const createCliStyles=e=>({error:n=>e.redBright(n)}),CLI_STYLES=createCliStyles(chalk),formatCliError=(e,n)=>e.error(n?.message||n);function isAzNotInstalledError(e){return"ENOENT"===e?.code||/spawn\s+az\s+enoent/i.test(e?.message||"")}function isAzDevopsAuthError(e){const n=`${e?.stderr||""}\n${e?.message||""}`.toLowerCase();return n.includes("az devops login")||n.includes("authentication failed")||n.includes("not authorized")||n.includes("authorization")||n.includes("tf400813")}function wrapAzError(e,n){if(isAzNotInstalledError(n)){const e=new Error("Azure CLI (az) was not found. Install it first: https://learn.microsoft.com/cli/azure/install-azure-cli");return e.cause=n,e}if(isAzDevopsAuthError(n)){const e=new Error("Azure DevOps CLI authentication is missing/expired. Run: az devops login");return e.cause=n,e}const t=n?.stderr||n?.shortMessage||n?.message||String(n),r=new Error(`Azure CLI command failed: az ${e.join(" ")}\n${t}`);return r.cause=n,r}async function azJson(e,{cwd:n}={}){try{const t=[...e,"--output","json"],{stdout:r}=await execa("az",t,{cwd:n});return JSON.parse(r||"null")}catch(n){throw wrapAzError(e,n)}}async function azText(e,{cwd:n}={}){try{const{stdout:t}=await execa("az",e,{cwd:n});return t}catch(n){throw wrapAzError(e,n)}}async function promptForEnvironment(e,{definitionId:n}={}){const t=e?.environments;if(!Array.isArray(t)||0===t.length)throw new Error(`No environments found in release definition${n?` id ${n}`:""}.`);const r=await inquirer.select({message:"Select an environment:",pageSize:20,choices:t.map((e,n)=>{const t=e?.name&&String(e.name).trim()||"(unnamed environment)",r=e?.id;return{name:void 0!==r?`${t} (id: ${r})`:t,value:void 0!==r?r:n}})}),i=t.find(e=>e?.id===r);if(i)return i;if(Number.isInteger(r)&&r>=0&&r<t.length)return t[r];throw new Error("Selected environment could not be resolved.")}function toDotenv(e){const n=[];for(const[t,r]of Object.entries(e)){const e=r&&"object"==typeof r&&"value"in r?r.value:r;if(null==e){n.push(`${t}=`);continue}const i=String(e);if(/[\s#"'\n\r]/.test(i)){const e=i.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\r/g,"\\r").replace(/\n/g,"\\n");n.push(`${t}="${e}"`)}else n.push(`${t}=${i}`)}return n.join("\n")+(n.length?"\n":"")}function simplifyVars(e){const n={};for(const[t,r]of Object.entries(e||{}))n[t]=r&&"object"==typeof r&&"value"in r?r.value:r;return n}async function ensureAzAvailable(){try{await azText(["--version"])}catch(e){throw e}await azText(["devops","-h"])}async function getDevopsDefaults(){const e=await azText(["devops","configure","-l"]),n={};for(const t of String(e||"").split(/\r?\n/)){const e=t.match(/^\s*([^=]+)=(.*)\s*$/);if(!e)continue;const r=e[1].trim(),i=e[2].trim();"organization"===r&&(n.organization=i),"project"===r&&(n.project=i)}return n}async function promptAndSetDevopsDefaultsIfMissing(){const e=await getDevopsDefaults(),n=[];if(e.organization||n.push({required:!0,message:"Azure DevOps organization URL (e.g., https://dev.azure.com/your-org):",validate:e=>!!String(e||"").trim()||"Organization is required"}),e.project||n.push({required:!0,message:"Azure DevOps project name:",validate:e=>!!String(e||"").trim()||"Project is required"}),0===n.length)return e;n.forEach(async e=>{await inquirer.input(e)});const t={organization:e.organization||answers.organization,project:e.project||answers.project};return await azText(["devops","configure","-d",`organization=${t.organization}`,`project=${t.project}`]),t}async function listReleaseDefinitions(){return azJson(["pipelines","release","definition","list","--query","[].{id:id,name:name}"])}async function showReleaseDefinition(e){return azJson(["pipelines","release","definition","show","--id",String(e)])}async function runInteractive(){await ensureAzAvailable(),await promptAndSetDevopsDefaultsIfMissing();const e=await listReleaseDefinitions();if(!e||0===e.length)throw new Error("No release definitions found in this Azure DevOps project.");const n=await inquirer.select({message:"Select a release pipeline definition:",choices:e.slice().sort((e,n)=>String(e.name).localeCompare(String(n.name))).map(e=>({name:`${e.name} (id: ${e.id})`,value:e.id})),pageSize:20}),t=await showReleaseDefinition(n),r=await promptForEnvironment(t,{definitionId:n}),i=simplifyVars(r.variables||{}),o=Object.keys(i);if(0===o.length){const e=r?.name?`"${r.name}" `:"";throw new Error(`Selected environment ${e}has no variables in definition id ${n}.`)}const a=await inquirer.select({message:"Export format:",choices:[{name:".env",value:"env"},{name:"JSON",value:"json"}],default:"env"}),s=process.cwd(),c="env"===a?path.join(s,".env"):path.join(s,"env.json");await inquirer.confirm({message:`Write ${o.length} variables to ${path.basename(c)} in current directory?`,default:!0})&&("env"===a?await fs.writeFile(c,toDotenv(i),"utf8"):await fs.writeFile(c,JSON.stringify(i,null,2)+"\n","utf8"),console.log(`Wrote ${o.length} variables to ${c}`))}async function main(){const e=new Command;e.name("azdo-release-env").description("Extract DEV/Development environment variables from Azure DevOps release pipelines").version("1.0.0"),e.action(async()=>{try{await runInteractive()}catch(e){console.error(formatCliError(CLI_STYLES,e)),process.exitCode=1}}),await e.parseAsync(process.argv)}main();
|
package/package.json
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azdo-release-env",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "CLI tool for exporting Azure DevOps release environment variables",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "dist/index.min.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"azdo-release-env": "
|
|
7
|
+
"azdo-release-env": "dist/index.min.js"
|
|
8
8
|
},
|
|
9
9
|
"types": "types/index.d.ts",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"start": "node src/index.js",
|
|
12
|
-
"test": "echo \"No tests yet\" && exit 0"
|
|
12
|
+
"test": "echo \"No tests yet\" && exit 0",
|
|
13
|
+
"build": "terser ./src/index.js --compress --mangle --output dist/index.min.js",
|
|
14
|
+
"prepare": "npm run build"
|
|
13
15
|
},
|
|
14
16
|
"files": [
|
|
15
|
-
"
|
|
17
|
+
"dist/*.js",
|
|
16
18
|
"types/index.d.ts"
|
|
17
19
|
],
|
|
18
20
|
"keywords": [
|
|
@@ -37,6 +39,7 @@
|
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
39
41
|
"@types/node": "^25.3.3",
|
|
42
|
+
"terser": "^5.46.0",
|
|
40
43
|
"typescript": "^5.9.3"
|
|
41
44
|
},
|
|
42
45
|
"engines": {
|
package/src/index.js
DELETED
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import * as inquirer from '@inquirer/prompts';
|
|
5
|
-
import { execa } from 'execa';
|
|
6
|
-
import fs from 'node:fs/promises';
|
|
7
|
-
import path from 'node:path';
|
|
8
|
-
import chalk from 'chalk';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* @typedef {Object} AZReleaseVariable
|
|
12
|
-
* @property {string} value Variable value.
|
|
13
|
-
* @property {boolean} isSecret Whether the variable is marked as secret.
|
|
14
|
-
*/
|
|
15
|
-
/**
|
|
16
|
-
* @typedef {Object} AZEnvironment
|
|
17
|
-
* @property {string} name Environment name.
|
|
18
|
-
* @property {number} id Environment id.
|
|
19
|
-
* @property {Record<string, AZReleaseVariable>} variables Environment variables.
|
|
20
|
-
*/
|
|
21
|
-
/**
|
|
22
|
-
* @typedef {Object} AZReleaseDefinition
|
|
23
|
-
* @property {string} name Release definition name.
|
|
24
|
-
* @property {number} id Release definition id.
|
|
25
|
-
* @property {AZEnvironment[]} environments List of environments in the release definition.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Creates a set of CLI style helpers.
|
|
30
|
-
*
|
|
31
|
-
* @param {import('chalk').ChalkInstance} chalkInstance Chalk instance used for styling.
|
|
32
|
-
* @returns {{ error: (message: string) => string }} Style helper functions.
|
|
33
|
-
*/
|
|
34
|
-
const createCliStyles = (chalkInstance) => ({
|
|
35
|
-
error: (message) => chalkInstance.redBright(message),
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const CLI_STYLES = createCliStyles(chalk);
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Formats an error for display in the CLI.
|
|
42
|
-
*
|
|
43
|
-
* @param {{ error: (message: string) => string }} styles Style helpers.
|
|
44
|
-
* @param {unknown} err Error-like value.
|
|
45
|
-
* @returns {string} Formatted error string.
|
|
46
|
-
*/
|
|
47
|
-
const formatCliError = (styles, err) => styles.error(err?.message || err);
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Detects whether an error indicates the Azure CLI is not installed.
|
|
51
|
-
*
|
|
52
|
-
* @param {any} err Error thrown from executing `az`.
|
|
53
|
-
* @returns {boolean} True if `az` appears missing.
|
|
54
|
-
*/
|
|
55
|
-
function isAzNotInstalledError(err) {
|
|
56
|
-
// Windows: spawn az ENOENT, POSIX: ENOENT
|
|
57
|
-
return err?.code === 'ENOENT' || /spawn\s+az\s+enoent/i.test(err?.message || '');
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Detects whether an error indicates Azure DevOps CLI authentication failure.
|
|
62
|
-
*
|
|
63
|
-
* @param {any} err Error thrown from executing `az devops`.
|
|
64
|
-
* @returns {boolean} True if the error appears auth-related.
|
|
65
|
-
*/
|
|
66
|
-
function isAzDevopsAuthError(err) {
|
|
67
|
-
const msg = `${err?.stderr || ''}\n${err?.message || ''}`.toLowerCase();
|
|
68
|
-
// Common messages:
|
|
69
|
-
// - "Please run 'az devops login'"
|
|
70
|
-
// - "TF400813: The user ... is not authorized"
|
|
71
|
-
// - "Authentication failed"
|
|
72
|
-
return (
|
|
73
|
-
msg.includes('az devops login') ||
|
|
74
|
-
msg.includes('authentication failed') ||
|
|
75
|
-
msg.includes('not authorized') ||
|
|
76
|
-
msg.includes('authorization') ||
|
|
77
|
-
msg.includes('tf400813')
|
|
78
|
-
);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Wraps an Azure CLI execution error with a friendlier message.
|
|
83
|
-
*
|
|
84
|
-
* @param {string[]} args Azure CLI args (excluding the `az` binary).
|
|
85
|
-
* @param {any} err Error thrown from executing `az`.
|
|
86
|
-
* @returns {Error} Wrapped error.
|
|
87
|
-
*/
|
|
88
|
-
function wrapAzError(args, err) {
|
|
89
|
-
if (isAzNotInstalledError(err)) {
|
|
90
|
-
const e = new Error(
|
|
91
|
-
"Azure CLI (az) was not found. Install it first: https://learn.microsoft.com/cli/azure/install-azure-cli"
|
|
92
|
-
);
|
|
93
|
-
e.cause = err;
|
|
94
|
-
return e;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
if (isAzDevopsAuthError(err)) {
|
|
98
|
-
const e = new Error(
|
|
99
|
-
"Azure DevOps CLI authentication is missing/expired. Run: az devops login"
|
|
100
|
-
);
|
|
101
|
-
e.cause = err;
|
|
102
|
-
return e;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const details = err?.stderr || err?.shortMessage || err?.message || String(err);
|
|
106
|
-
const wrapped = new Error(`Azure CLI command failed: az ${args.join(' ')}\n${details}`);
|
|
107
|
-
wrapped.cause = err;
|
|
108
|
-
return wrapped;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Executes an Azure CLI command and parses its JSON output.
|
|
113
|
-
*
|
|
114
|
-
* @param {string[]} args Azure CLI args (excluding the `az` binary).
|
|
115
|
-
* @param {{ cwd?: string } | undefined} [options] Execution options.
|
|
116
|
-
* @returns {Promise<any>} Parsed JSON output.
|
|
117
|
-
*/
|
|
118
|
-
async function azJson(args, { cwd } = {}) {
|
|
119
|
-
try {
|
|
120
|
-
const finalArgs = [...args, '--output', 'json'];
|
|
121
|
-
const { stdout } = await execa('az', finalArgs, { cwd });
|
|
122
|
-
return JSON.parse(stdout || 'null');
|
|
123
|
-
} catch (err) {
|
|
124
|
-
throw wrapAzError(args, err);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Executes an Azure CLI command and returns its stdout.
|
|
130
|
-
*
|
|
131
|
-
* @param {string[]} args Azure CLI args (excluding the `az` binary).
|
|
132
|
-
* @param {{ cwd?: string } | undefined} [options] Execution options.
|
|
133
|
-
* @returns {Promise<string>} Command stdout.
|
|
134
|
-
*/
|
|
135
|
-
async function azText(args, { cwd } = {}) {
|
|
136
|
-
try {
|
|
137
|
-
const { stdout } = await execa('az', args, { cwd });
|
|
138
|
-
return stdout;
|
|
139
|
-
} catch (err) {
|
|
140
|
-
throw wrapAzError(args, err);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Prompts the user to choose an environment from a release definition.
|
|
146
|
-
*
|
|
147
|
-
* @param {AZReleaseDefinition} definition Azure DevOps release definition object.
|
|
148
|
-
* @param {{ definitionId?: number | string } | undefined} [options] Options for error context.
|
|
149
|
-
* @returns {Promise<AZEnvironment>} Selected environment object.
|
|
150
|
-
*/
|
|
151
|
-
async function promptForEnvironment(definition, { definitionId } = {}) {
|
|
152
|
-
const envs = definition?.environments;
|
|
153
|
-
if (!Array.isArray(envs) || envs.length === 0) {
|
|
154
|
-
throw new Error(
|
|
155
|
-
`No environments found in release definition${definitionId ? ` id ${definitionId}` : ''}.`
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const envIdOrIndex = await inquirer.select({
|
|
160
|
-
message: 'Select an environment:',
|
|
161
|
-
pageSize: 20,
|
|
162
|
-
// Keep API order (no sorting)
|
|
163
|
-
choices: envs.map((e, idx) => {
|
|
164
|
-
const name = (e?.name && String(e.name).trim()) || '(unnamed environment)';
|
|
165
|
-
const id = e?.id;
|
|
166
|
-
const label = id !== undefined ? `${name} (id: ${id})` : name;
|
|
167
|
-
const value = id !== undefined ? id : idx;
|
|
168
|
-
return { name: label, value };
|
|
169
|
-
}),
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
const selectedById = envs.find((e) => e?.id === envIdOrIndex);
|
|
173
|
-
if (selectedById) return selectedById;
|
|
174
|
-
|
|
175
|
-
if (Number.isInteger(envIdOrIndex) && envIdOrIndex >= 0 && envIdOrIndex < envs.length) {
|
|
176
|
-
return envs[envIdOrIndex];
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
throw new Error('Selected environment could not be resolved.');
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Converts a variable map into `.env` file content.
|
|
184
|
-
*
|
|
185
|
-
* @param {Record<string, { value: string } | string | number | boolean | null | undefined>} vars Variables to serialize.
|
|
186
|
-
* @returns {string} Dotenv formatted content.
|
|
187
|
-
*/
|
|
188
|
-
function toDotenv(vars) {
|
|
189
|
-
// vars: Record<string, { value: string } | string>
|
|
190
|
-
// We will output KEY=VALUE, quoting when necessary.
|
|
191
|
-
const lines = [];
|
|
192
|
-
for (const [key, raw] of Object.entries(vars)) {
|
|
193
|
-
const value = raw && typeof raw === 'object' && 'value' in raw ? raw.value : raw;
|
|
194
|
-
|
|
195
|
-
if (value === null || value === undefined) {
|
|
196
|
-
lines.push(`${key}=`);
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
const str = String(value);
|
|
201
|
-
|
|
202
|
-
// Quote if contains spaces, #, quotes, or newlines.
|
|
203
|
-
if (/[\s#"'\n\r]/.test(str)) {
|
|
204
|
-
// Use double quotes; escape backslashes, double quotes, and newlines.
|
|
205
|
-
const escaped = str
|
|
206
|
-
.replace(/\\/g, '\\\\')
|
|
207
|
-
.replace(/"/g, '\\"')
|
|
208
|
-
.replace(/\r/g, '\\r')
|
|
209
|
-
.replace(/\n/g, '\\n');
|
|
210
|
-
lines.push(`${key}="${escaped}"`);
|
|
211
|
-
} else {
|
|
212
|
-
lines.push(`${key}=${str}`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
return lines.join('\n') + (lines.length ? '\n' : '');
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Simplifies Azure release variables to a plain key/value object.
|
|
220
|
-
*
|
|
221
|
-
* @param {Record<string, { value: string } | string | number | boolean | null | undefined>} vars Azure release variables.
|
|
222
|
-
* @returns {Record<string, any>} Simplified key/value object.
|
|
223
|
-
*/
|
|
224
|
-
function simplifyVars(vars) {
|
|
225
|
-
// Azure release variables shape: { KEY: { value, isSecret, ... } }
|
|
226
|
-
// We'll omit secrets? For now, include value if present.
|
|
227
|
-
const out = {};
|
|
228
|
-
for (const [key, v] of Object.entries(vars || {})) {
|
|
229
|
-
if (v && typeof v === 'object' && 'value' in v) out[key] = v.value;
|
|
230
|
-
else out[key] = v;
|
|
231
|
-
}
|
|
232
|
-
return out;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Ensures the Azure CLI and the azure-devops extension are available.
|
|
237
|
-
*
|
|
238
|
-
* @param {void} _ Unused.
|
|
239
|
-
* @returns {Promise<void>} Resolves when checks pass.
|
|
240
|
-
*/
|
|
241
|
-
async function ensureAzAvailable() {
|
|
242
|
-
// Dedicated check so we can surface a clearer message.
|
|
243
|
-
try {
|
|
244
|
-
await azText(['--version']);
|
|
245
|
-
} catch (err) {
|
|
246
|
-
throw err;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Also validate azure-devops extension is available.
|
|
250
|
-
// This will also catch auth issues in some setups, but mostly ensures az devops exists.
|
|
251
|
-
await azText(['devops', '-h']);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
/**
|
|
255
|
-
* Reads Azure DevOps defaults from `az devops configure -l`.
|
|
256
|
-
*
|
|
257
|
-
* @param {void} _ Unused.
|
|
258
|
-
* @returns {Promise<{ organization?: string, project?: string }>} Current defaults.
|
|
259
|
-
*/
|
|
260
|
-
async function getDevopsDefaults() {
|
|
261
|
-
// Returns { organization, project } possibly undefined.
|
|
262
|
-
// az devops configure -l output example (text):
|
|
263
|
-
// organization=https://dev.azure.com/foo
|
|
264
|
-
// project=bar
|
|
265
|
-
const out = await azText(['devops', 'configure', '-l']);
|
|
266
|
-
const defaults = {};
|
|
267
|
-
for (const line of String(out || '').split(/\r?\n/)) {
|
|
268
|
-
const m = line.match(/^\s*([^=]+)=(.*)\s*$/);
|
|
269
|
-
if (!m) continue;
|
|
270
|
-
const key = m[1].trim();
|
|
271
|
-
const val = m[2].trim();
|
|
272
|
-
if (key === 'organization') defaults.organization = val;
|
|
273
|
-
if (key === 'project') defaults.project = val;
|
|
274
|
-
}
|
|
275
|
-
return defaults;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/**
|
|
279
|
-
* Prompts for missing Azure DevOps defaults and persists them.
|
|
280
|
-
*
|
|
281
|
-
* @param {void} _ Unused.
|
|
282
|
-
* @returns {Promise<{ organization?: string, project?: string }>} Resolved defaults.
|
|
283
|
-
*/
|
|
284
|
-
async function promptAndSetDevopsDefaultsIfMissing() {
|
|
285
|
-
const defaults = await getDevopsDefaults();
|
|
286
|
-
|
|
287
|
-
const questions = [];
|
|
288
|
-
if (!defaults.organization) {
|
|
289
|
-
questions.push({
|
|
290
|
-
required: true,
|
|
291
|
-
message: 'Azure DevOps organization URL (e.g., https://dev.azure.com/your-org):',
|
|
292
|
-
validate: (v) => (String(v || '').trim() ? true : 'Organization is required'),
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
if (!defaults.project) {
|
|
296
|
-
questions.push({
|
|
297
|
-
required: true,
|
|
298
|
-
message: 'Azure DevOps project name:',
|
|
299
|
-
validate: (v) => (String(v || '').trim() ? true : 'Project is required'),
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
if (questions.length === 0) return defaults;
|
|
304
|
-
|
|
305
|
-
questions.forEach(async (q) => {
|
|
306
|
-
const anser = await inquirer.input(q);
|
|
307
|
-
})
|
|
308
|
-
|
|
309
|
-
const newDefaults = {
|
|
310
|
-
organization: defaults.organization || answers.organization,
|
|
311
|
-
project: defaults.project || answers.project,
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
// Persist defaults for subsequent az devops commands.
|
|
315
|
-
await azText([
|
|
316
|
-
'devops',
|
|
317
|
-
'configure',
|
|
318
|
-
'-d',
|
|
319
|
-
`organization=${newDefaults.organization}`,
|
|
320
|
-
`project=${newDefaults.project}`,
|
|
321
|
-
]);
|
|
322
|
-
|
|
323
|
-
return newDefaults;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Lists Azure DevOps release definitions for the configured project.
|
|
328
|
-
*
|
|
329
|
-
* @param {void} _ Unused.
|
|
330
|
-
* @returns {Promise<Array<{ id: number, name: string }>>} Release definitions.
|
|
331
|
-
*/
|
|
332
|
-
async function listReleaseDefinitions() {
|
|
333
|
-
// Query trims output to id/name.
|
|
334
|
-
return azJson([
|
|
335
|
-
'pipelines',
|
|
336
|
-
'release',
|
|
337
|
-
'definition',
|
|
338
|
-
'list',
|
|
339
|
-
'--query',
|
|
340
|
-
"[].{id:id,name:name}",
|
|
341
|
-
]);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
/**
|
|
345
|
-
* Fetches a single release definition by id.
|
|
346
|
-
*
|
|
347
|
-
* @param {number | string} id Release definition id.
|
|
348
|
-
* @returns {Promise<AZReleaseDefinition>} Release definition.
|
|
349
|
-
*/
|
|
350
|
-
async function showReleaseDefinition(id) {
|
|
351
|
-
return azJson(['pipelines', 'release', 'definition', 'show', '--id', String(id)]);
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Runs the interactive CLI flow.
|
|
358
|
-
*
|
|
359
|
-
* @param {void} _ Unused.
|
|
360
|
-
* @returns {Promise<void>} Resolves when flow completes.
|
|
361
|
-
*/
|
|
362
|
-
async function runInteractive() {
|
|
363
|
-
await ensureAzAvailable();
|
|
364
|
-
await promptAndSetDevopsDefaultsIfMissing();
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* List release definitions
|
|
368
|
-
* @type {Array<{id: number, name: string}>}
|
|
369
|
-
*/
|
|
370
|
-
const defs = await listReleaseDefinitions();
|
|
371
|
-
if (!defs || defs.length === 0) {
|
|
372
|
-
throw new Error('No release definitions found in this Azure DevOps project.');
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const defId = await inquirer.select({
|
|
376
|
-
message: 'Select a release pipeline definition:',
|
|
377
|
-
choices: defs
|
|
378
|
-
.slice()
|
|
379
|
-
.sort((a, b) => String(a.name).localeCompare(String(b.name)))
|
|
380
|
-
.map((d) => ({
|
|
381
|
-
name: `${d.name} (id: ${d.id})`,
|
|
382
|
-
value: d.id,
|
|
383
|
-
})),
|
|
384
|
-
pageSize: 20,
|
|
385
|
-
})
|
|
386
|
-
|
|
387
|
-
const definition = await showReleaseDefinition(defId);
|
|
388
|
-
const selectedEnv = await promptForEnvironment(definition, { definitionId: defId });
|
|
389
|
-
|
|
390
|
-
const envVars = selectedEnv.variables || {};
|
|
391
|
-
const simplified = simplifyVars(envVars);
|
|
392
|
-
|
|
393
|
-
const keys = Object.keys(simplified);
|
|
394
|
-
if (keys.length === 0) {
|
|
395
|
-
const envName = selectedEnv?.name ? `"${selectedEnv.name}" ` : '';
|
|
396
|
-
throw new Error(`Selected environment ${envName}has no variables in definition id ${defId}.`);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const format = await inquirer.select({
|
|
400
|
-
message: 'Export format:',
|
|
401
|
-
choices: [
|
|
402
|
-
{ name: '.env', value: 'env' },
|
|
403
|
-
{ name: 'JSON', value: 'json' },
|
|
404
|
-
],
|
|
405
|
-
default: 'env',
|
|
406
|
-
})
|
|
407
|
-
|
|
408
|
-
const cwd = process.cwd();
|
|
409
|
-
const outPath =
|
|
410
|
-
format === 'env' ? path.join(cwd, '.env') : path.join(cwd, 'env.json');
|
|
411
|
-
|
|
412
|
-
const confirmWrite = await inquirer.confirm({
|
|
413
|
-
message: `Write ${keys.length} variables to ${path.basename(outPath)} in current directory?`,
|
|
414
|
-
default: true,
|
|
415
|
-
})
|
|
416
|
-
|
|
417
|
-
if (!confirmWrite) return;
|
|
418
|
-
|
|
419
|
-
if (format === 'env') {
|
|
420
|
-
await fs.writeFile(outPath, toDotenv(simplified), 'utf8');
|
|
421
|
-
} else {
|
|
422
|
-
await fs.writeFile(outPath, JSON.stringify(simplified, null, 2) + '\n', 'utf8');
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// eslint-disable-next-line no-console
|
|
426
|
-
console.log(`Wrote ${keys.length} variables to ${outPath}`);
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
/**
|
|
430
|
-
* CLI entrypoint.
|
|
431
|
-
*
|
|
432
|
-
* @param {void} _ Unused.
|
|
433
|
-
* @returns {Promise<void>} Resolves when the CLI finishes parsing.
|
|
434
|
-
*/
|
|
435
|
-
async function main() {
|
|
436
|
-
const program = new Command();
|
|
437
|
-
|
|
438
|
-
program
|
|
439
|
-
.name('azdo-release-env')
|
|
440
|
-
.description(
|
|
441
|
-
'Extract DEV/Development environment variables from Azure DevOps release pipelines'
|
|
442
|
-
)
|
|
443
|
-
.version('1.0.0');
|
|
444
|
-
|
|
445
|
-
program.action(async () => {
|
|
446
|
-
try {
|
|
447
|
-
await runInteractive();
|
|
448
|
-
} catch (err) {
|
|
449
|
-
// eslint-disable-next-line no-console
|
|
450
|
-
console.error(formatCliError(CLI_STYLES, err));
|
|
451
|
-
process.exitCode = 1;
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
await program.parseAsync(process.argv);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
main();
|