Skip to content

Commit 77e6430

Browse files
committed
feat: add os-env package for managing path, env, rc files
1 parent 5962369 commit 77e6430

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+616
-504
lines changed

dist/actions/setup-cpp.js

Lines changed: 21 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/actions/setup-cpp.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/legacy/setup-cpp.js

Lines changed: 21 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/legacy/setup-cpp.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/modern/setup-cpp.js

Lines changed: 21 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/modern/setup-cpp.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
"admina": "^1.0.1",
7575
"caxa": "^3.0.1",
7676
"ci-info": "^4.0.0",
77+
"os-env": "workspace:*",
7778
"ci-log": "workspace:*",
7879
"cross-env": "7.0.3",
7980
"cross-spawn": "^7.0.3",
@@ -128,6 +129,7 @@
128129
"admina",
129130
"ci-info",
130131
"ci-log",
132+
"os-env",
131133
"escape-path-with-spaces",
132134
"escape-quotes",
133135
"escape-string-regexp",

packages/os-env/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"name": "os-env",
3+
"version": "1.0.0",
4+
"description": "Manage environment variables, PATH, and rc files",
5+
"homepage": "https://github.com/aminya/setup-cpp",
6+
"license": "Apache-2.0",
7+
"author": "Amin Yahyaabadi",
8+
"main": "./dist/index.js",
9+
"source": "./src/index.ts",
10+
"scripts": {
11+
"build": "tsc"
12+
},
13+
"dependencies": {
14+
"@actions/core": "^1.10.1",
15+
"@types/node": "^12",
16+
"admina": "^1.0.1",
17+
"ci-info": "^4.0.0",
18+
"escape-path-with-spaces": "^1.0.2",
19+
"escape-quotes": "^1.0.2",
20+
"micro-memoize": "^4.1.2",
21+
"path-exists": "^5.0.0",
22+
"ci-log": "workspace:*",
23+
"exec-powershell": "workspace:*",
24+
"untildify-user": "workspace:*"
25+
},
26+
"engines": {
27+
"node": ">=12"
28+
},
29+
"keywords": [
30+
"env",
31+
"path",
32+
"dotenv",
33+
"rc",
34+
"addEnv",
35+
"addPath",
36+
"setEnv",
37+
"linux",
38+
"windows",
39+
"unix",
40+
"macos"
41+
]
42+
}

packages/os-env/src/add-env.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { promises } from "fs"
2+
import { exportVariable as ghExportVariable } from "@actions/core"
3+
import { GITHUB_ACTIONS } from "ci-info"
4+
import { error, info } from "ci-log"
5+
import { execPowershell } from "exec-powershell"
6+
import { untildifyUser } from "untildify-user"
7+
import { sourceRC } from "./rc-file.js"
8+
import { escapeString } from "./utils.js"
9+
const { appendFile } = promises
10+
11+
export type AddEnvOptions = {
12+
/** If true, the value will be escaped with quotes and spaces will be escaped with backslash */
13+
shouldEscapeSpace: boolean
14+
/** If true, the variable will be only added if it is not defined */
15+
shouldAddOnlyIfNotDefined: boolean
16+
/**
17+
* The path to the RC file that the env variables should be added to.
18+
*/
19+
rcPath: string
20+
}
21+
/**
22+
* Add an environment variable.
23+
*
24+
* This function is cross-platforms and works in all the local or CI systems.
25+
*/
26+
27+
export async function addEnv(
28+
name: string,
29+
valGiven: string | undefined,
30+
givenOptions: Partial<AddEnvOptions> = {},
31+
) {
32+
const options = {
33+
shouldEscapeSpace: false,
34+
shouldAddOnlyIfNotDefined: false,
35+
rcPath: untildifyUser(".bashrc"),
36+
...givenOptions,
37+
}
38+
39+
const val = escapeString(valGiven ?? "", options.shouldEscapeSpace)
40+
try {
41+
if (GITHUB_ACTIONS) {
42+
try {
43+
if (options.shouldAddOnlyIfNotDefined) {
44+
if (process.env[name] !== undefined) {
45+
info(`Environment variable ${name} is already defined. Skipping.`)
46+
return
47+
}
48+
}
49+
ghExportVariable(name, val)
50+
} catch (err) {
51+
error(err as Error)
52+
await addEnvSystem(name, val, options)
53+
}
54+
} else {
55+
await addEnvSystem(name, val, options)
56+
}
57+
} catch (err) {
58+
error(`${err}\nFailed to export environment variable ${name}=${val}. You should add it manually.`)
59+
}
60+
}
61+
62+
async function addEnvSystem(name: string, valGiven: string | undefined, options: AddEnvOptions) {
63+
const val = valGiven ?? ""
64+
switch (process.platform) {
65+
case "win32": {
66+
if (options.shouldAddOnlyIfNotDefined) {
67+
if (process.env[name] !== undefined) {
68+
info(`Environment variable ${name} is already defined. Skipping.`)
69+
return
70+
}
71+
}
72+
// We do not use `execaSync(`setx PATH "${path};%PATH%"`)` because of its character limit
73+
await execPowershell(`[Environment]::SetEnvironmentVariable('${name}', '${val}', "User")`)
74+
info(`${name}='${val}' was set in the environment.`)
75+
return
76+
}
77+
case "linux":
78+
case "darwin": {
79+
await sourceRC(options.rcPath)
80+
if (options.shouldAddOnlyIfNotDefined) {
81+
await appendFile(options.rcPath, `\nif [ -z "\${${name}}" ]; then export ${name}="${val}"; fi\n`)
82+
info(`if not defined ${name} then ${name}="${val}" was added to "${options.rcPath}`)
83+
} else {
84+
await appendFile(options.rcPath, `\nexport ${name}="${val}"\n`)
85+
info(`${name}="${val}" was added to "${options.rcPath}`)
86+
}
87+
return
88+
}
89+
default: {
90+
// fall through shell path modification
91+
}
92+
}
93+
process.env[name] = val
94+
}

packages/os-env/src/add-path.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { promises } from "fs"
2+
import { delimiter } from "path"
3+
import { addPath as ghAddPath } from "@actions/core"
4+
import { GITHUB_ACTIONS } from "ci-info"
5+
import { error, info } from "ci-log"
6+
import { execPowershell } from "exec-powershell"
7+
import { untildifyUser } from "untildify-user"
8+
import { sourceRC } from "./rc-file.js"
9+
const { appendFile } = promises
10+
11+
type AddPathOptions = {
12+
/**
13+
* The path to the RC file that the PATH variables should be added to.
14+
*/
15+
rcPath: string
16+
}
17+
/**
18+
* Add a path to the PATH environment variable.
19+
*
20+
* This function is cross-platforms and works in all the local or CI systems.
21+
*/
22+
23+
export async function addPath(path: string, givenOptions: Partial<AddPathOptions> = {}) {
24+
const options = { rcPath: untildifyUser(".bashrc"), ...givenOptions }
25+
26+
if (isIgnoredPath(path)) {
27+
return
28+
}
29+
30+
process.env.PATH = `${path}${delimiter}${process.env.PATH}`
31+
try {
32+
if (GITHUB_ACTIONS) {
33+
try {
34+
ghAddPath(path)
35+
} catch (err) {
36+
error(err as Error)
37+
await addPathSystem(path, options.rcPath)
38+
}
39+
} else {
40+
await addPathSystem(path, options.rcPath)
41+
}
42+
} catch (err) {
43+
error(`${err}\nFailed to add ${path} to the percistent PATH. You should add it manually.`)
44+
}
45+
}
46+
47+
async function addPathSystem(path: string, rcPath: string) {
48+
switch (process.platform) {
49+
case "win32": {
50+
// We do not use `execaSync(`setx PATH "${path};%PATH%"`)` because of its character limit and also because %PATH% is different for user and system
51+
await execPowershell(
52+
`$USER_PATH=([Environment]::GetEnvironmentVariable("PATH", "User")); [Environment]::SetEnvironmentVariable("PATH", "${path};$USER_PATH", "User")`,
53+
)
54+
info(`"${path}" was added to the PATH.`)
55+
return
56+
}
57+
case "linux":
58+
case "darwin": {
59+
await sourceRC(rcPath)
60+
await appendFile(rcPath, `\nexport PATH="${path}:$PATH"\n`)
61+
info(`"${path}" was added to "${rcPath}"`)
62+
return
63+
}
64+
default: {
65+
return
66+
}
67+
}
68+
}
69+
70+
const ignoredPaths = [/\/usr\/bin\/?/, /\/usr\/local\/bin\/?/]
71+
72+
/** Skip adding /usr/bin to PATH if it is already there */
73+
function isIgnoredPath(path: string) {
74+
if (ignoredPaths.some((checkedPath) => checkedPath.test(path))) {
75+
const paths = process.env.PATH?.split(delimiter) ?? []
76+
return paths.includes(path)
77+
}
78+
return false
79+
}

0 commit comments

Comments
 (0)