@percy/env 1.0.0 → 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/dotenv.js ADDED
@@ -0,0 +1,62 @@
1
+ import fs from 'fs'; // Heavily inspired by dotenv-rails
2
+ // https://github.com/bkeepers/dotenv
3
+ // matches each valid line of a dotenv file
4
+
5
+ const LINE_REG = new RegExp([// key with optional export
6
+ '^\\s*(?:export\\s+)?(?<key>[\\w.]+)', // separator
7
+ '(?:\\s*=\\s*?|:\\s+?)(?:', // single quoted value or
8
+ '\\s*(?<squote>\')(?<sval>(?:\\\\\'|[^\'])*)\'|', // double quoted value or
9
+ '\\s*(?<dquote>")(?<dval>(?:\\\\"|[^"])*)"|', // unquoted value
10
+ '(?<uval>[^#\\r\\n]+))?', // optional comment
11
+ '\\s*(?:#.*)?$'].join(''), 'gm'); // interpolate variable substitutions
12
+
13
+ const INTERPOLATE_REG = /(.?)(\${?([a-zA-Z0-9_]+)?}?)/g; // expand newlines
14
+
15
+ const EXPAND_CRLF_REG = /\\(?:(r)|n)/g; // unescape characters
16
+
17
+ const UNESC_CHAR_REG = /\\([^$])/g;
18
+ export function load() {
19
+ // don't load dotenv files when disabled
20
+ if (process.env.PERCY_DISABLE_DOTENV) return;
21
+ let {
22
+ NODE_ENV
23
+ } = process.env; // dotenv filepaths ordered by priority
24
+
25
+ let paths = [NODE_ENV && `.env.${NODE_ENV}.local`, NODE_ENV !== 'test' && '.env.local', NODE_ENV && `.env.${NODE_ENV}`, '.env']; // load each dotenv file synchronously
26
+
27
+ for (let path of paths) {
28
+ try {
29
+ let src = fs.readFileSync(path, {
30
+ encoding: 'utf-8'
31
+ }); // iterate over each matching line
32
+
33
+ for (let {
34
+ groups: match
35
+ } of src.matchAll(LINE_REG)) {
36
+ let value = match.sval ?? match.dval ?? match.uval ?? ''; // if double quoted, expand newlines
37
+
38
+ if (match.dquote) {
39
+ value = value.replace(EXPAND_CRLF_REG, (_, r) => r ? '\r' : '\n');
40
+ } // unescape characters
41
+
42
+
43
+ value = value.replace(UNESC_CHAR_REG, '$1'); // if not single quoted, interpolate substitutions
44
+
45
+ if (!match.squote) {
46
+ value = value.replace(INTERPOLATE_REG, (_, pre, ref, key) => {
47
+ if (pre === '\\') return ref; // escaped reference
48
+
49
+ return pre + (process.env[key] ?? '');
50
+ });
51
+ } // set process.env if not already
52
+
53
+
54
+ if (!Object.prototype.hasOwnProperty.call(process.env, match.key)) {
55
+ process.env[match.key] = value;
56
+ }
57
+ }
58
+ } catch (e) {// silent error
59
+ }
60
+ }
61
+ }
62
+ export * as default from './dotenv.js';
@@ -0,0 +1,356 @@
1
+ import { getCommitData, getJenkinsSha, github } from './utils.js';
2
+ export class PercyEnv {
3
+ constructor(vars = process.env) {
4
+ this.vars = vars;
5
+ } // used for getter switch statements
6
+
7
+
8
+ get ci() {
9
+ if (this.vars.TRAVIS_BUILD_ID) {
10
+ return 'travis';
11
+ } else if (this.vars.JENKINS_URL && this.vars.ghprbPullId) {
12
+ return 'jenkins-prb';
13
+ } else if (this.vars.JENKINS_URL) {
14
+ return 'jenkins';
15
+ } else if (this.vars.CIRCLECI) {
16
+ return 'circle';
17
+ } else if (this.vars.CI_NAME === 'codeship') {
18
+ return 'codeship';
19
+ } else if (this.vars.DRONE === 'true') {
20
+ return 'drone';
21
+ } else if (this.vars.SEMAPHORE === 'true') {
22
+ return 'semaphore';
23
+ } else if (this.vars.BUILDKITE === 'true') {
24
+ return 'buildkite';
25
+ } else if (this.vars.HEROKU_TEST_RUN_ID) {
26
+ return 'heroku';
27
+ } else if (this.vars.GITLAB_CI === 'true') {
28
+ return 'gitlab';
29
+ } else if (this.vars.TF_BUILD === 'True') {
30
+ return 'azure';
31
+ } else if (this.vars.APPVEYOR === 'True' || this.vars.APPVEYOR === 'true') {
32
+ return 'appveyor';
33
+ } else if (this.vars.PROBO_ENVIRONMENT === 'TRUE') {
34
+ return 'probo';
35
+ } else if (this.vars.BITBUCKET_BUILD_NUMBER) {
36
+ return 'bitbucket';
37
+ } else if (this.vars.GITHUB_ACTIONS === 'true') {
38
+ return 'github';
39
+ } else if (this.vars.NETLIFY === 'true') {
40
+ return 'netlify';
41
+ } else if (this.vars.CI) {
42
+ return 'CI/unknown';
43
+ } else {
44
+ return null;
45
+ }
46
+ } // environment info reported in user-agents
47
+
48
+
49
+ get info() {
50
+ switch (this.ci) {
51
+ case 'github':
52
+ return this.vars.PERCY_GITHUB_ACTION ? `github/${this.vars.PERCY_GITHUB_ACTION}` : this.ci;
53
+
54
+ case 'gitlab':
55
+ return `gitlab/${this.vars.CI_SERVER_VERSION}`;
56
+
57
+ case 'semaphore':
58
+ return this.vars.SEMAPHORE_GIT_SHA ? 'semaphore/2.0' : 'semaphore';
59
+
60
+ default:
61
+ return this.ci;
62
+ }
63
+ } // current commit sha
64
+
65
+
66
+ get commit() {
67
+ if (this.vars.PERCY_COMMIT) {
68
+ return this.vars.PERCY_COMMIT;
69
+ }
70
+
71
+ let commit = (() => {
72
+ var _github$pull_request;
73
+
74
+ switch (this.ci) {
75
+ case 'travis':
76
+ return this.vars.TRAVIS_COMMIT;
77
+
78
+ case 'jenkins-prb':
79
+ return this.vars.ghprbActualCommit || this.vars.GIT_COMMIT;
80
+
81
+ case 'jenkins':
82
+ return getJenkinsSha() || this.vars.GIT_COMMIT;
83
+
84
+ case 'circle':
85
+ return this.vars.CIRCLE_SHA1;
86
+
87
+ case 'codeship':
88
+ return this.vars.CI_COMMIT_ID;
89
+
90
+ case 'drone':
91
+ return this.vars.DRONE_COMMIT;
92
+
93
+ case 'semaphore':
94
+ return this.vars.REVISION || this.vars.SEMAPHORE_GIT_PR_SHA || this.vars.SEMAPHORE_GIT_SHA;
95
+
96
+ case 'buildkite':
97
+ return this.vars.BUILDKITE_COMMIT !== 'HEAD' && this.vars.BUILDKITE_COMMIT;
98
+
99
+ case 'heroku':
100
+ return this.vars.HEROKU_TEST_RUN_COMMIT_VERSION;
101
+
102
+ case 'gitlab':
103
+ return this.vars.CI_COMMIT_SHA;
104
+
105
+ case 'azure':
106
+ return this.vars.SYSTEM_PULLREQUEST_SOURCECOMMITID || this.vars.BUILD_SOURCEVERSION;
107
+
108
+ case 'appveyor':
109
+ return this.vars.APPVEYOR_PULL_REQUEST_HEAD_COMMIT || this.vars.APPVEYOR_REPO_COMMIT;
110
+
111
+ case 'probo':
112
+ case 'netlify':
113
+ return this.vars.COMMIT_REF;
114
+
115
+ case 'bitbucket':
116
+ return this.vars.BITBUCKET_COMMIT;
117
+
118
+ case 'github':
119
+ return ((_github$pull_request = github(this.vars).pull_request) === null || _github$pull_request === void 0 ? void 0 : _github$pull_request.head.sha) || this.vars.GITHUB_SHA;
120
+ }
121
+ })();
122
+
123
+ return commit || null;
124
+ } // current branch name
125
+
126
+
127
+ get branch() {
128
+ if (this.vars.PERCY_BRANCH) {
129
+ return this.vars.PERCY_BRANCH;
130
+ }
131
+
132
+ let branch = (() => {
133
+ var _github$pull_request2;
134
+
135
+ switch (this.ci) {
136
+ case 'travis':
137
+ return this.pullRequest && this.vars.TRAVIS_PULL_REQUEST_BRANCH || this.vars.TRAVIS_BRANCH;
138
+
139
+ case 'jenkins-prb':
140
+ return this.vars.ghprbSourceBranch;
141
+
142
+ case 'jenkins':
143
+ return this.vars.CHANGE_BRANCH || this.vars.GIT_BRANCH;
144
+
145
+ case 'circle':
146
+ return this.vars.CIRCLE_BRANCH;
147
+
148
+ case 'codeship':
149
+ return this.vars.CI_BRANCH;
150
+
151
+ case 'drone':
152
+ return this.vars.DRONE_BRANCH;
153
+
154
+ case 'semaphore':
155
+ return this.vars.BRANCH_NAME || this.vars.SEMAPHORE_GIT_PR_BRANCH || this.vars.SEMAPHORE_GIT_BRANCH;
156
+
157
+ case 'buildkite':
158
+ return this.vars.BUILDKITE_BRANCH;
159
+
160
+ case 'heroku':
161
+ return this.vars.HEROKU_TEST_RUN_BRANCH;
162
+
163
+ case 'gitlab':
164
+ return this.vars.CI_COMMIT_REF_NAME;
165
+
166
+ case 'azure':
167
+ return this.vars.SYSTEM_PULLREQUEST_SOURCEBRANCH || this.vars.BUILD_SOURCEBRANCHNAME;
168
+
169
+ case 'appveyor':
170
+ return this.vars.APPVEYOR_PULL_REQUEST_HEAD_REPO_BRANCH || this.vars.APPVEYOR_REPO_BRANCH;
171
+
172
+ case 'probo':
173
+ return this.vars.BRANCH_NAME;
174
+
175
+ case 'bitbucket':
176
+ return this.vars.BITBUCKET_BRANCH;
177
+
178
+ case 'github':
179
+ return ((_github$pull_request2 = github(this.vars).pull_request) === null || _github$pull_request2 === void 0 ? void 0 : _github$pull_request2.head.ref) || this.vars.GITHUB_REF;
180
+
181
+ case 'netlify':
182
+ return this.vars.HEAD;
183
+ }
184
+ })();
185
+
186
+ return (branch === null || branch === void 0 ? void 0 : branch.replace(/^refs\/\w+?\//, '')) || null;
187
+ } // pull request number
188
+
189
+
190
+ get pullRequest() {
191
+ if (this.vars.PERCY_PULL_REQUEST) {
192
+ return this.vars.PERCY_PULL_REQUEST;
193
+ }
194
+
195
+ let pr = (() => {
196
+ var _this$vars$CI_PULL_RE, _this$vars$PULL_REQUE, _github$pull_request3;
197
+
198
+ switch (this.ci) {
199
+ case 'travis':
200
+ return this.vars.TRAVIS_PULL_REQUEST !== 'false' && this.vars.TRAVIS_PULL_REQUEST;
201
+
202
+ case 'jenkins-prb':
203
+ return this.vars.ghprbPullId;
204
+
205
+ case 'jenkins':
206
+ return this.vars.CHANGE_ID;
207
+
208
+ case 'circle':
209
+ return (_this$vars$CI_PULL_RE = this.vars.CI_PULL_REQUESTS) === null || _this$vars$CI_PULL_RE === void 0 ? void 0 : _this$vars$CI_PULL_RE.split('/').slice(-1)[0];
210
+
211
+ case 'drone':
212
+ return this.vars.CI_PULL_REQUEST;
213
+
214
+ case 'semaphore':
215
+ return this.vars.PULL_REQUEST_NUMBER || this.vars.SEMAPHORE_GIT_PR_NUMBER;
216
+
217
+ case 'buildkite':
218
+ return this.vars.BUILDKITE_PULL_REQUEST !== 'false' && this.vars.BUILDKITE_PULL_REQUEST;
219
+
220
+ case 'heroku':
221
+ return this.vars.HEROKU_PR_NUMBER;
222
+
223
+ case 'gitlab':
224
+ return this.vars.CI_MERGE_REQUEST_IID;
225
+
226
+ case 'azure':
227
+ return this.vars.SYSTEM_PULLREQUEST_PULLREQUESTID || this.vars.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER;
228
+
229
+ case 'appveyor':
230
+ return this.vars.APPVEYOR_PULL_REQUEST_NUMBER;
231
+
232
+ case 'probo':
233
+ return (_this$vars$PULL_REQUE = this.vars.PULL_REQUEST_LINK) === null || _this$vars$PULL_REQUE === void 0 ? void 0 : _this$vars$PULL_REQUE.split('/').slice(-1)[0];
234
+
235
+ case 'bitbucket':
236
+ return this.vars.BITBUCKET_PR_ID;
237
+
238
+ case 'netlify':
239
+ return this.vars.PULL_REQUEST !== 'false' && this.vars.REVIEW_ID;
240
+
241
+ case 'github':
242
+ return (_github$pull_request3 = github(this.vars).pull_request) === null || _github$pull_request3 === void 0 ? void 0 : _github$pull_request3.number;
243
+ }
244
+ })();
245
+
246
+ return pr || null;
247
+ } // parallel total & nonce
248
+
249
+
250
+ get parallel() {
251
+ let total = parseInt(this.vars.PERCY_PARALLEL_TOTAL, 10);
252
+ if (!Number.isInteger(total)) total = null; // no nonce if no total
253
+
254
+ let nonce = total && (() => {
255
+ var _this$vars$BUILD_TAG;
256
+
257
+ if (this.vars.PERCY_PARALLEL_NONCE) {
258
+ return this.vars.PERCY_PARALLEL_NONCE;
259
+ }
260
+
261
+ switch (this.ci) {
262
+ case 'travis':
263
+ return this.vars.TRAVIS_BUILD_NUMBER;
264
+
265
+ case 'jenkins-prb':
266
+ return this.vars.BUILD_NUMBER;
267
+
268
+ case 'jenkins':
269
+ return (_this$vars$BUILD_TAG = this.vars.BUILD_TAG) === null || _this$vars$BUILD_TAG === void 0 ? void 0 : _this$vars$BUILD_TAG.split('').reverse().join('').substring(0, 60);
270
+
271
+ case 'circle':
272
+ return this.vars.CIRCLE_WORKFLOW_ID || this.vars.CIRCLE_BUILD_NUM;
273
+
274
+ case 'codeship':
275
+ return this.vars.CI_BUILD_NUMBER || this.vars.CI_BUILD_ID;
276
+
277
+ case 'drone':
278
+ return this.vars.DRONE_BUILD_NUMBER;
279
+
280
+ case 'semaphore':
281
+ return this.vars.SEMAPHORE_WORKFLOW_ID || `${this.vars.SEMAPHORE_BRANCH_ID}/${this.vars.SEMAPHORE_BUILD_NUMBER}`;
282
+
283
+ case 'buildkite':
284
+ return this.vars.BUILDKITE_BUILD_ID;
285
+
286
+ case 'heroku':
287
+ return this.vars.HEROKU_TEST_RUN_ID;
288
+
289
+ case 'gitlab':
290
+ return this.vars.CI_PIPELINE_ID;
291
+
292
+ case 'azure':
293
+ return this.vars.BUILD_BUILDID;
294
+
295
+ case 'appveyor':
296
+ return this.vars.APPVEYOR_BUILD_ID;
297
+
298
+ case 'probo':
299
+ return this.vars.BUILD_ID;
300
+
301
+ case 'bitbucket':
302
+ return this.vars.BITBUCKET_BUILD_NUMBER;
303
+
304
+ case 'github':
305
+ return this.vars.GITHUB_RUN_ID;
306
+ }
307
+ })();
308
+
309
+ return {
310
+ total: total || null,
311
+ nonce: nonce || null
312
+ };
313
+ } // git information for the current commit
314
+
315
+
316
+ get git() {
317
+ return getCommitData(this.commit, this.branch, this.vars);
318
+ } // manually set build commit and branch targets
319
+
320
+
321
+ get target() {
322
+ return {
323
+ commit: this.vars.PERCY_TARGET_COMMIT || null,
324
+ branch: this.vars.PERCY_TARGET_BRANCH || null
325
+ };
326
+ } // build marked as partial
327
+
328
+
329
+ get partial() {
330
+ let partial = this.vars.PERCY_PARTIAL_BUILD;
331
+ return !!partial && partial !== '0';
332
+ } // percy token
333
+
334
+
335
+ get token() {
336
+ return this.vars.PERCY_TOKEN || null;
337
+ }
338
+
339
+ } // cache getters on initial call so subsequent calls are not re-computed
340
+
341
+ Object.defineProperties(PercyEnv.prototype, Object.entries(Object.getOwnPropertyDescriptors(PercyEnv.prototype)).reduce((proto, [key, {
342
+ get,
343
+ ...descr
344
+ }]) => !get ? proto : Object.assign(proto, {
345
+ [key]: Object.assign(descr, {
346
+ get() {
347
+ let value = get.call(this);
348
+ Object.defineProperty(this, key, {
349
+ value
350
+ });
351
+ return value;
352
+ }
353
+
354
+ })
355
+ }), {}));
356
+ export default PercyEnv;
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import PercyEnv from './environment.js';
2
+ import dotenv from './dotenv.js';
3
+ dotenv.load();
4
+ export { PercyEnv };
5
+ export default PercyEnv;
package/dist/utils.js ADDED
@@ -0,0 +1,53 @@
1
+ import fs from 'fs';
2
+ import cp from 'child_process';
3
+ const GIT_COMMIT_FORMAT = ['COMMIT_SHA:%H', 'AUTHOR_NAME:%an', 'AUTHOR_EMAIL:%ae', 'COMMITTER_NAME:%cn', 'COMMITTER_EMAIL:%ce', 'COMMITTED_DATE:%ai', // order is important, this must come last because the regex is a multiline match.
4
+ 'COMMIT_MESSAGE:%B'].join('%n'); // git show format uses %n for newlines.
5
+
6
+ export function git(args) {
7
+ try {
8
+ return cp.execSync(`git ${args}`, {
9
+ stdio: ['ignore', 'pipe', 'ignore'],
10
+ encoding: 'utf-8'
11
+ });
12
+ } catch (e) {
13
+ return '';
14
+ }
15
+ } // get raw commit data
16
+
17
+ export function getCommitData(sha, branch, vars = {}) {
18
+ let raw = git(`show ${sha || 'HEAD'} --quiet --format=${GIT_COMMIT_FORMAT}`); // prioritize PERCY_GIT_* vars and fallback to GIT_* vars
19
+
20
+ let get = key => {
21
+ var _raw$match;
22
+
23
+ return vars[`PERCY_GIT_${key}`] || ((_raw$match = raw.match(new RegExp(`^${key}:(.*)$`, 'm'))) === null || _raw$match === void 0 ? void 0 : _raw$match[1]) || vars[`GIT_${key}`] || null;
24
+ };
25
+
26
+ return {
27
+ sha: (sha === null || sha === void 0 ? void 0 : sha.length) === 40 ? sha : get('COMMIT_SHA'),
28
+ branch: branch || git('rev-parse --abbrev-ref HEAD') || null,
29
+ message: get('COMMIT_MESSAGE'),
30
+ authorName: get('AUTHOR_NAME'),
31
+ authorEmail: get('AUTHOR_EMAIL'),
32
+ committedAt: get('COMMITTED_DATE'),
33
+ committerName: get('COMMITTER_NAME'),
34
+ committerEmail: get('COMMITTER_EMAIL')
35
+ };
36
+ } // the sha needed from Jenkins merge commits is the parent sha
37
+
38
+ export function getJenkinsSha() {
39
+ let data = getCommitData();
40
+ return data.authorName === 'Jenkins' && data.authorEmail === 'nobody@nowhere' && data.message.match(/^Merge commit [^\s]+ into HEAD$/) && git('rev-parse HEAD^');
41
+ } // github actions are triggered by webhook events which are saved to the filesystem
42
+
43
+ export function github({
44
+ GITHUB_EVENT_PATH
45
+ }) {
46
+ if (!github.payload && GITHUB_EVENT_PATH && fs.existsSync(GITHUB_EVENT_PATH)) {
47
+ try {
48
+ github.payload = JSON.parse(fs.readFileSync(GITHUB_EVENT_PATH));
49
+ } catch {}
50
+ }
51
+
52
+ return github.payload || (github.payload = {});
53
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@percy/env",
3
- "version": "1.0.0",
3
+ "version": "1.0.3",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",
@@ -14,7 +14,8 @@
14
14
  "node": ">=14"
15
15
  },
16
16
  "files": [
17
- "./dist"
17
+ "dist",
18
+ "test/helpers.js"
18
19
  ],
19
20
  "main": "./dist/index.js",
20
21
  "type": "module",
@@ -29,5 +30,5 @@
29
30
  "test": "node ../../scripts/test",
30
31
  "test:coverage": "yarn test --coverage"
31
32
  },
32
- "gitHead": "6df509421a60144e4f9f5d59dc57a5675372a0b2"
33
+ "gitHead": "a259d5cff0933711bced21a979c577e70765d318"
33
34
  }
@@ -0,0 +1,19 @@
1
+ import cp from 'child_process';
2
+
3
+ export function mockgit(branch = '') {
4
+ let spy = jasmine.createSpy('git');
5
+
6
+ spyOn(cp, 'execSync').and.callFake(function(cmd, options) {
7
+ if (cmd.match(/^git\b/)) {
8
+ let result = spy(...cmd.split(' ').slice(1)) ?? '';
9
+ if (!cmd.match(/\b(show|rev-parse)\b/)) return '';
10
+ if (!cmd.includes('rev-parse')) return result;
11
+ if (cmd.includes('--abbrev-ref')) return branch;
12
+ return result.match(/^COMMIT_SHA:(.*)$/m)?.[1];
13
+ } else {
14
+ return cp.execSync.and.originalFn.call(this, cmd, options);
15
+ }
16
+ });
17
+
18
+ return spy;
19
+ }