azdo-release-env 1.0.1 → 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 +15 -6
- package/.github/workflows/npm-publish.yml +0 -34
- package/CODE_OF_CONDUCT.md +0 -92
- package/CONTRIBUTING.md +0 -137
- package/src/index.js +0 -458
- package/tsconfig.json +0 -21
|
@@ -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,16 +1,22 @@
|
|
|
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
|
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/*.js",
|
|
18
|
+
"types/index.d.ts"
|
|
19
|
+
],
|
|
14
20
|
"keywords": [
|
|
15
21
|
"azure-devops",
|
|
16
22
|
"release",
|
|
@@ -27,10 +33,13 @@
|
|
|
27
33
|
"type": "module",
|
|
28
34
|
"dependencies": {
|
|
29
35
|
"@inquirer/prompts": "^8.3.0",
|
|
30
|
-
"@types/node": "^25.3.3",
|
|
31
36
|
"chalk": "^5.6.2",
|
|
32
37
|
"commander": "^14.0.3",
|
|
33
|
-
"execa": "^9.6.1"
|
|
38
|
+
"execa": "^9.6.1"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/node": "^25.3.3",
|
|
42
|
+
"terser": "^5.46.0",
|
|
34
43
|
"typescript": "^5.9.3"
|
|
35
44
|
},
|
|
36
45
|
"engines": {
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
|
2
|
-
# For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages
|
|
3
|
-
|
|
4
|
-
name: Node.js Package
|
|
5
|
-
|
|
6
|
-
on:
|
|
7
|
-
release:
|
|
8
|
-
types: [created]
|
|
9
|
-
permissions:
|
|
10
|
-
id-token: write # Required for OIDC
|
|
11
|
-
contents: read
|
|
12
|
-
|
|
13
|
-
jobs:
|
|
14
|
-
build:
|
|
15
|
-
runs-on: ubuntu-latest
|
|
16
|
-
steps:
|
|
17
|
-
- uses: actions/checkout@v4
|
|
18
|
-
- uses: actions/setup-node@v4
|
|
19
|
-
with:
|
|
20
|
-
node-version: 20
|
|
21
|
-
- run: npm ci
|
|
22
|
-
- run: npm test
|
|
23
|
-
|
|
24
|
-
publish-npm:
|
|
25
|
-
needs: build
|
|
26
|
-
runs-on: ubuntu-latest
|
|
27
|
-
steps:
|
|
28
|
-
- uses: actions/checkout@v4
|
|
29
|
-
- uses: actions/setup-node@v4
|
|
30
|
-
with:
|
|
31
|
-
node-version: 20
|
|
32
|
-
registry-url: https://registry.npmjs.org/
|
|
33
|
-
- run: npm ci
|
|
34
|
-
- run: npm publish
|
package/CODE_OF_CONDUCT.md
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
# Contributor Covenant 3.0 Code of Conduct
|
|
3
|
-
|
|
4
|
-
## Our Pledge
|
|
5
|
-
|
|
6
|
-
We pledge to make our community welcoming, safe, and equitable for all.
|
|
7
|
-
|
|
8
|
-
We are committed to fostering an environment that respects and promotes the dignity, rights, and contributions of all individuals, regardless of characteristics including race, ethnicity, caste, color, age, physical characteristics, neurodiversity, disability, sex or gender, gender identity or expression, sexual orientation, language, philosophy or religion, national or social origin, socio-economic position, level of education, or other status. The same privileges of participation are extended to everyone who participates in good faith and in accordance with this Covenant.
|
|
9
|
-
|
|
10
|
-
## Encouraged Behaviors
|
|
11
|
-
|
|
12
|
-
While acknowledging differences in social norms, we all strive to meet our community's expectations for positive behavior. We also understand that our words and actions may be interpreted differently than we intend based on culture, background, or native language.
|
|
13
|
-
|
|
14
|
-
With these considerations in mind, we agree to behave mindfully toward each other and act in ways that center our shared values, including:
|
|
15
|
-
|
|
16
|
-
1. Respecting the **purpose of our community**, our activities, and our ways of gathering.
|
|
17
|
-
2. Engaging **kindly and honestly** with others.
|
|
18
|
-
3. Respecting **different viewpoints** and experiences.
|
|
19
|
-
4. **Taking responsibility** for our actions and contributions.
|
|
20
|
-
5. Gracefully giving and accepting **constructive feedback**.
|
|
21
|
-
6. Committing to **repairing harm** when it occurs.
|
|
22
|
-
7. Behaving in other ways that promote and sustain the **well-being of our community**.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## Restricted Behaviors
|
|
26
|
-
|
|
27
|
-
We agree to restrict the following behaviors in our community. Instances, threats, and promotion of these behaviors are violations of this Code of Conduct.
|
|
28
|
-
|
|
29
|
-
1. **Harassment.** Violating explicitly expressed boundaries or engaging in unnecessary personal attention after any clear request to stop.
|
|
30
|
-
2. **Character attacks.** Making insulting, demeaning, or pejorative comments directed at a community member or group of people.
|
|
31
|
-
3. **Stereotyping or discrimination.** Characterizing anyone’s personality or behavior on the basis of immutable identities or traits.
|
|
32
|
-
4. **Sexualization.** Behaving in a way that would generally be considered inappropriately intimate in the context or purpose of the community.
|
|
33
|
-
5. **Violating confidentiality**. Sharing or acting on someone's personal or private information without their permission.
|
|
34
|
-
6. **Endangerment.** Causing, encouraging, or threatening violence or other harm toward any person or group.
|
|
35
|
-
7. Behaving in other ways that **threaten the well-being** of our community.
|
|
36
|
-
|
|
37
|
-
### Other Restrictions
|
|
38
|
-
|
|
39
|
-
1. **Misleading identity.** Impersonating someone else for any reason, or pretending to be someone else to evade enforcement actions.
|
|
40
|
-
2. **Failing to credit sources.** Not properly crediting the sources of content you contribute.
|
|
41
|
-
3. **Promotional materials**. Sharing marketing or other commercial content in a way that is outside the norms of the community.
|
|
42
|
-
4. **Irresponsible communication.** Failing to responsibly present content which includes, links or describes any other restricted behaviors.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
## Reporting an Issue
|
|
46
|
-
|
|
47
|
-
Tensions can occur between community members even when they are trying their best to collaborate. Not every conflict represents a code of conduct violation, and this Code of Conduct reinforces encouraged behaviors and norms that can help avoid conflicts and minimize harm.
|
|
48
|
-
|
|
49
|
-
When an incident does occur, it is important to report it promptly.
|
|
50
|
-
|
|
51
|
-
Community Moderators take reports of violations seriously and will make every effort to respond in a timely manner. They will investigate all reports of code of conduct violations, reviewing messages, logs, and recordings, or interviewing witnesses and other participants. Community Moderators will keep investigation and enforcement actions as transparent as possible while prioritizing safety and confidentiality. In order to honor these values, enforcement actions are carried out in private with the involved parties, but communicating to the whole community may be part of a mutually agreed upon resolution.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
## Addressing and Repairing Harm
|
|
55
|
-
|
|
56
|
-
****
|
|
57
|
-
|
|
58
|
-
If an investigation by the Community Moderators finds that this Code of Conduct has been violated, the following enforcement ladder may be used to determine how best to repair harm, based on the incident's impact on the individuals involved and the community as a whole. Depending on the severity of a violation, lower rungs on the ladder may be skipped.
|
|
59
|
-
|
|
60
|
-
1) Warning
|
|
61
|
-
1) Event: A violation involving a single incident or series of incidents.
|
|
62
|
-
2) Consequence: A private, written warning from the Community Moderators.
|
|
63
|
-
3) Repair: Examples of repair include a private written apology, acknowledgement of responsibility, and seeking clarification on expectations.
|
|
64
|
-
2) Temporarily Limited Activities
|
|
65
|
-
1) Event: A repeated incidence of a violation that previously resulted in a warning, or the first incidence of a more serious violation.
|
|
66
|
-
2) Consequence: A private, written warning with a time-limited cooldown period designed to underscore the seriousness of the situation and give the community members involved time to process the incident. The cooldown period may be limited to particular communication channels or interactions with particular community members.
|
|
67
|
-
3) Repair: Examples of repair may include making an apology, using the cooldown period to reflect on actions and impact, and being thoughtful about re-entering community spaces after the period is over.
|
|
68
|
-
3) Temporary Suspension
|
|
69
|
-
1) Event: A pattern of repeated violation which the Community Moderators have tried to address with warnings, or a single serious violation.
|
|
70
|
-
2) Consequence: A private written warning with conditions for return from suspension. In general, temporary suspensions give the person being suspended time to reflect upon their behavior and possible corrective actions.
|
|
71
|
-
3) Repair: Examples of repair include respecting the spirit of the suspension, meeting the specified conditions for return, and being thoughtful about how to reintegrate with the community when the suspension is lifted.
|
|
72
|
-
4) Permanent Ban
|
|
73
|
-
1) Event: A pattern of repeated code of conduct violations that other steps on the ladder have failed to resolve, or a violation so serious that the Community Moderators determine there is no way to keep the community safe with this person as a member.
|
|
74
|
-
2) Consequence: Access to all community spaces, tools, and communication channels is removed. In general, permanent bans should be rarely used, should have strong reasoning behind them, and should only be resorted to if working through other remedies has failed to change the behavior.
|
|
75
|
-
3) Repair: There is no possible repair in cases of this severity.
|
|
76
|
-
|
|
77
|
-
This enforcement ladder is intended as a guideline. It does not limit the ability of Community Managers to use their discretion and judgment, in keeping with the best interests of our community.
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
## Scope
|
|
81
|
-
|
|
82
|
-
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public or other spaces. Examples of representing our community include using an official email address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
## Attribution
|
|
86
|
-
|
|
87
|
-
This Code of Conduct is adapted from the Contributor Covenant, version 3.0, permanently available at [https://www.contributor-covenant.org/version/3/0/](https://www.contributor-covenant.org/version/3/0/).
|
|
88
|
-
|
|
89
|
-
Contributor Covenant is stewarded by the Organization for Ethical Source and licensed under CC BY-SA 4.0. To view a copy of this license, visit [https://creativecommons.org/licenses/by-sa/4.0/](https://creativecommons.org/licenses/by-sa/4.0/)
|
|
90
|
-
|
|
91
|
-
For answers to common questions about Contributor Covenant, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are provided at [https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations). Additional enforcement and community guideline resources can be found at [https://www.contributor-covenant.org/resources](https://www.contributor-covenant.org/resources). The enforcement ladder was inspired by the work of [Mozilla’s code of conduct team](https://github.com/mozilla/inclusion).
|
|
92
|
-
|
package/CONTRIBUTING.md
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
# Contributing to CONTRIBUTING.md
|
|
2
|
-
|
|
3
|
-
First off, thanks for taking the time to contribute!
|
|
4
|
-
|
|
5
|
-
All types of contributions are encouraged and valued. See the [Table of Contents](#table-of-contents) for different ways to help and details about how this project handles them. Please make sure to read the relevant section before making your contribution. It will make it a lot easier for us maintainers and smooth out the experience for all involved. The community looks forward to your contributions.
|
|
6
|
-
|
|
7
|
-
> And if you like the project, but just don't have time to contribute, that's fine. There are other easy ways to support the project and show your appreciation, which we would also be very happy about:
|
|
8
|
-
> - Star the project
|
|
9
|
-
> - Tweet about it
|
|
10
|
-
> - Refer this project in your project's readme
|
|
11
|
-
> - Mention the project at local meetups and tell your friends/colleagues
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## Table of Contents
|
|
15
|
-
|
|
16
|
-
- [Code of Conduct](#code-of-conduct)
|
|
17
|
-
- [I Have a Question](#i-have-a-question)
|
|
18
|
-
- [I Want To Contribute](#i-want-to-contribute)
|
|
19
|
-
- [Reporting Bugs](#reporting-bugs)
|
|
20
|
-
- [Suggesting Enhancements](#suggesting-enhancements)
|
|
21
|
-
- [Your First Code Contribution](#your-first-code-contribution)
|
|
22
|
-
- [Improving The Documentation](#improving-the-documentation)
|
|
23
|
-
- [Styleguides](#styleguides)
|
|
24
|
-
- [Commit Messages](#commit-messages)
|
|
25
|
-
- [Join The Project Team](#join-the-project-team)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
## Code of Conduct
|
|
29
|
-
|
|
30
|
-
This project and everyone participating in it is governed by the
|
|
31
|
-
[CONTRIBUTING.md Code of Conduct](blob/main/CODE_OF_CONDUCT.md).
|
|
32
|
-
By participating, you are expected to uphold this code. Please report unacceptable behavior
|
|
33
|
-
to <>.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
## I Have a Question
|
|
37
|
-
|
|
38
|
-
> If you want to ask a question, we assume that you have read the available [Documentation]().
|
|
39
|
-
|
|
40
|
-
Before you ask a question, it is best to search for existing [Issues](/issues) that might help you. In case you have found a suitable issue and still need clarification, you can write your question in this issue. It is also advisable to search the internet for answers first.
|
|
41
|
-
|
|
42
|
-
If you then still feel the need to ask a question and need clarification, we recommend the following:
|
|
43
|
-
|
|
44
|
-
- Open an [Issue](/issues/new).
|
|
45
|
-
- Provide as much context as you can about what you're running into.
|
|
46
|
-
- Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant.
|
|
47
|
-
|
|
48
|
-
We will then take care of the issue as soon as possible.
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
## I Want To Contribute
|
|
53
|
-
|
|
54
|
-
> ### Legal Notice
|
|
55
|
-
> When contributing to this project, you must agree that you have authored 100% of the content, that you have the necessary rights to the content and that the content you contribute may be provided under the project license.
|
|
56
|
-
|
|
57
|
-
### Reporting Bugs
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
#### Before Submitting a Bug Report
|
|
61
|
-
|
|
62
|
-
A good bug report shouldn't leave others needing to chase you up for more information. Therefore, we ask you to investigate carefully, collect information and describe the issue in detail in your report. Please complete the following steps in advance to help us fix any potential bug as fast as possible.
|
|
63
|
-
|
|
64
|
-
- Make sure that you are using the latest version.
|
|
65
|
-
- Determine if your bug is really a bug and not an error on your side e.g. using incompatible environment components/versions (Make sure that you have read the [documentation](). If you are looking for support, you might want to check [this section](#i-have-a-question)).
|
|
66
|
-
- To see if other users have experienced (and potentially already solved) the same issue you are having, check if there is not already a bug report existing for your bug or error in the [bug tracker](issues?q=label%3Abug).
|
|
67
|
-
- Also make sure to search the internet (including Stack Overflow) to see if users outside of the GitHub community have discussed the issue.
|
|
68
|
-
- Collect information about the bug:
|
|
69
|
-
- Stack trace (Traceback)
|
|
70
|
-
- OS, Platform and Version (Windows, Linux, macOS, x86, ARM)
|
|
71
|
-
- Version of the interpreter, compiler, SDK, runtime environment, package manager, depending on what seems relevant.
|
|
72
|
-
- Possibly your input and the output
|
|
73
|
-
- Can you reliably reproduce the issue? And can you also reproduce it with older versions?
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
#### How Do I Submit a Good Bug Report?
|
|
77
|
-
|
|
78
|
-
> You must never report security related issues, vulnerabilities or bugs including sensitive information to the issue tracker, or elsewhere in public. Instead sensitive bugs must be sent by email to <>.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
We use GitHub issues to track bugs and errors. If you run into an issue with the project:
|
|
82
|
-
|
|
83
|
-
- Open an [Issue](/issues/new). (Since we can't be sure at this point whether it is a bug or not, we ask you not to talk about a bug yet and not to label the issue.)
|
|
84
|
-
- Explain the behavior you would expect and the actual behavior.
|
|
85
|
-
- Please provide as much context as possible and describe the *reproduction steps* that someone else can follow to recreate the issue on their own. This usually includes your code. For good bug reports you should isolate the problem and create a reduced test case.
|
|
86
|
-
- Provide the information you collected in the previous section.
|
|
87
|
-
|
|
88
|
-
Once it's filed:
|
|
89
|
-
|
|
90
|
-
- The project team will label the issue accordingly.
|
|
91
|
-
- A team member will try to reproduce the issue with your provided steps. If there are no reproduction steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced.
|
|
92
|
-
- If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#your-first-code-contribution).
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
### Suggesting Enhancements
|
|
98
|
-
|
|
99
|
-
This section guides you through submitting an enhancement suggestion for CONTRIBUTING.md, **including completely new features and minor improvements to existing functionality**. Following these guidelines will help maintainers and the community to understand your suggestion and find related suggestions.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
#### Before Submitting an Enhancement
|
|
103
|
-
|
|
104
|
-
- Make sure that you are using the latest version.
|
|
105
|
-
- Read the [documentation]() carefully and find out if the functionality is already covered, maybe by an individual configuration.
|
|
106
|
-
- Perform a [search](/issues) to see if the enhancement has already been suggested. If it has, add a comment to the existing issue instead of opening a new one.
|
|
107
|
-
- Find out whether your idea fits with the scope and aims of the project. It's up to you to make a strong case to convince the project's developers of the merits of this feature. Keep in mind that we want features that will be useful to the majority of our users and not just a small subset. If you're just targeting a minority of users, consider writing an add-on/plugin library.
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
#### How Do I Submit a Good Enhancement Suggestion?
|
|
111
|
-
|
|
112
|
-
Enhancement suggestions are tracked as [GitHub issues](/issues).
|
|
113
|
-
|
|
114
|
-
- Use a **clear and descriptive title** for the issue to identify the suggestion.
|
|
115
|
-
- Provide a **step-by-step description of the suggested enhancement** in as many details as possible.
|
|
116
|
-
- **Describe the current behavior** and **explain which behavior you expected to see instead** and why. At this point you can also tell which alternatives do not work for you.
|
|
117
|
-
- You may want to **include screenshots and animated GIFs** which help you demonstrate the steps or point out the part which the suggestion is related to. You can use [this tool](https://www.cockos.com/licecap/) to record GIFs on macOS and Windows, and [this tool](https://github.com/colinkeenan/silentcast) or [this tool](https://github.com/GNOME/byzanz) on Linux.
|
|
118
|
-
- **Explain why this enhancement would be useful** to most CONTRIBUTING.md users. You may also want to point out the other projects that solved it better and which could serve as inspiration.
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
### Your First Code Contribution
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
### Improving The Documentation
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
## Styleguides
|
|
129
|
-
### Commit Messages
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
## Join The Project Team
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
## Attribution
|
|
137
|
-
This guide is based on the **contributing.md**. [Make your own](https://contributing.md/)!
|
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();
|
package/tsconfig.json
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
// Change this to match your project
|
|
3
|
-
"include": ["src/**/*"],
|
|
4
|
-
"compilerOptions": {
|
|
5
|
-
// Tells TypeScript to read JS files, as
|
|
6
|
-
// normally they are ignored as source files
|
|
7
|
-
"allowJs": true,
|
|
8
|
-
// Generate d.ts files
|
|
9
|
-
"declaration": true,
|
|
10
|
-
// This compiler run should
|
|
11
|
-
// only output d.ts files
|
|
12
|
-
"emitDeclarationOnly": true,
|
|
13
|
-
// Types should go into this directory.
|
|
14
|
-
// Removing this would place the .d.ts files
|
|
15
|
-
// next to the .js files
|
|
16
|
-
"outDir": "dist",
|
|
17
|
-
// go to js file when using IDE functions like
|
|
18
|
-
// "Go to Definition" in VSCode
|
|
19
|
-
"declarationMap": true,
|
|
20
|
-
},
|
|
21
|
-
}
|