@servicetitan/startup 31.6.0 → 32.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/init.d.ts +2 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +24 -43
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/install.d.ts +4 -0
- package/dist/cli/commands/install.d.ts.map +1 -1
- package/dist/cli/commands/install.js +91 -3
- package/dist/cli/commands/install.js.map +1 -1
- package/dist/cli/commands/mfe-package-clean.d.ts.map +1 -1
- package/dist/cli/commands/mfe-package-clean.js +5 -7
- package/dist/cli/commands/mfe-package-clean.js.map +1 -1
- package/dist/cli/commands/mfe-package-publish.d.ts.map +1 -1
- package/dist/cli/commands/mfe-package-publish.js +11 -15
- package/dist/cli/commands/mfe-package-publish.js.map +1 -1
- package/dist/cli/commands/upload-sourcemaps.d.ts.map +1 -1
- package/dist/cli/commands/upload-sourcemaps.js +1 -1
- package/dist/cli/commands/upload-sourcemaps.js.map +1 -1
- package/dist/cli/index.js +1 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/utils/cli-git.d.ts +11 -2
- package/dist/cli/utils/cli-git.d.ts.map +1 -1
- package/dist/cli/utils/cli-git.js +60 -4
- package/dist/cli/utils/cli-git.js.map +1 -1
- package/dist/cli/utils/index.d.ts +6 -0
- package/dist/cli/utils/index.d.ts.map +1 -1
- package/dist/cli/utils/index.js +6 -0
- package/dist/cli/utils/index.js.map +1 -1
- package/dist/cli/utils/is-ci.d.ts +2 -0
- package/dist/cli/utils/is-ci.d.ts.map +1 -0
- package/dist/cli/utils/is-ci.js +15 -0
- package/dist/cli/utils/is-ci.js.map +1 -0
- package/dist/cli/utils/lerna-exec.d.ts.map +1 -1
- package/dist/cli/utils/lerna-exec.js +2 -1
- package/dist/cli/utils/lerna-exec.js.map +1 -1
- package/dist/utils/get-branch-configs.d.ts +1 -1
- package/dist/utils/get-branch-configs.d.ts.map +1 -1
- package/dist/utils/get-branch-configs.js +2 -2
- package/dist/utils/get-branch-configs.js.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +1 -0
- package/dist/utils/index.js.map +1 -1
- package/package.json +6 -6
- package/src/cli/commands/__tests__/init.test.ts +21 -87
- package/src/cli/commands/__tests__/install.test.ts +174 -12
- package/src/cli/commands/__tests__/mfe-package-clean.test.ts +3 -6
- package/src/cli/commands/__tests__/mfe-package-publish.test.ts +6 -8
- package/src/cli/commands/__tests__/upload-sourcemaps.test.ts +7 -3
- package/src/cli/commands/init.ts +17 -37
- package/src/cli/commands/install.ts +95 -6
- package/src/cli/commands/mfe-package-clean.ts +2 -4
- package/src/cli/commands/mfe-package-publish.ts +18 -6
- package/src/cli/commands/upload-sourcemaps.ts +2 -2
- package/src/cli/index.ts +1 -2
- package/src/cli/utils/__tests__/cli-git.test.ts +142 -6
- package/src/cli/utils/__tests__/eslint.test.ts +3 -2
- package/src/cli/utils/__tests__/is-ci.test.ts +40 -0
- package/src/cli/utils/__tests__/lerna-exec.test.ts +6 -3
- package/src/cli/utils/cli-git.ts +55 -5
- package/src/cli/utils/index.ts +6 -0
- package/src/cli/utils/is-ci.ts +3 -0
- package/src/cli/utils/lerna-exec.ts +2 -1
- package/src/utils/get-branch-configs.ts +1 -1
- package/src/utils/index.ts +1 -0
|
@@ -1,33 +1,92 @@
|
|
|
1
|
+
import { vol, fs } from 'memfs';
|
|
1
2
|
import { execSync } from 'child_process';
|
|
2
|
-
import { log } from '../../../utils';
|
|
3
|
+
import { getStartupVersion, log } from '../../../utils';
|
|
4
|
+
import { gitCloneRepo, isCI } from '../../utils';
|
|
3
5
|
|
|
4
6
|
import { Install } from '../install';
|
|
5
7
|
|
|
8
|
+
jest.mock('fs', () => fs);
|
|
6
9
|
jest.mock('child_process', () => ({ execSync: jest.fn() }));
|
|
10
|
+
jest.mock('../../utils', () => ({
|
|
11
|
+
...jest.requireActual('../../utils'),
|
|
12
|
+
gitCloneRepo: jest.fn(),
|
|
13
|
+
isCI: jest.fn(),
|
|
14
|
+
}));
|
|
7
15
|
jest.mock('../../../utils', () => ({
|
|
8
16
|
...jest.requireActual('../../../utils'),
|
|
9
|
-
|
|
17
|
+
getStartupVersion: jest.fn(),
|
|
18
|
+
log: { debug: jest.fn(), info: jest.fn(), warning: jest.fn() }, // suppress log output
|
|
10
19
|
}));
|
|
11
20
|
|
|
12
21
|
describe(`${Install.name}`, () => {
|
|
13
|
-
const
|
|
14
|
-
const
|
|
22
|
+
const mockNpmToken = 'npm_Foo';
|
|
23
|
+
const npmOptions = ['--audit=false', '--fund=false', '--legacy-peer-deps'];
|
|
24
|
+
const startupVersion = '1.2.3';
|
|
25
|
+
const tempDirPath = 'tempDirPath';
|
|
15
26
|
let args: ConstructorParameters<typeof Install>[0];
|
|
16
27
|
|
|
28
|
+
function volFromJSON(overrides?: Record<string, string>) {
|
|
29
|
+
vol.fromJSON({
|
|
30
|
+
'.npmrc': '',
|
|
31
|
+
// Mock cloned Github repo with .npm.json containing readOnlyToken
|
|
32
|
+
[`${tempDirPath}/.npm.json`]: JSON.stringify({
|
|
33
|
+
readOnlyToken: Buffer.from(mockNpmToken).toString('base64'),
|
|
34
|
+
}),
|
|
35
|
+
...overrides,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
17
39
|
beforeEach(() => {
|
|
18
|
-
process.env = {};
|
|
19
40
|
args = undefined;
|
|
20
41
|
jest.clearAllMocks();
|
|
42
|
+
jest.mocked(gitCloneRepo).mockResolvedValue(true);
|
|
43
|
+
jest.mocked(getStartupVersion).mockReturnValue(startupVersion);
|
|
44
|
+
jest.mocked(isCI).mockReturnValue(false);
|
|
45
|
+
jest.spyOn(fs, 'mkdtempSync').mockImplementation(() => tempDirPath);
|
|
46
|
+
volFromJSON();
|
|
21
47
|
});
|
|
22
48
|
|
|
23
|
-
afterEach(() => (
|
|
49
|
+
afterEach(() => vol.reset());
|
|
24
50
|
|
|
25
51
|
const subject = async () => new Install(args).execute();
|
|
26
52
|
|
|
27
|
-
test(
|
|
53
|
+
test('clones "frontend-dev-config" repo to temp directory', async () => {
|
|
54
|
+
await subject();
|
|
55
|
+
|
|
56
|
+
expect(gitCloneRepo).toHaveBeenCalledWith({
|
|
57
|
+
destination: tempDirPath,
|
|
58
|
+
name: 'frontend-dev-config',
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test('configures NPM token with value from cloned repo', async () => {
|
|
63
|
+
await subject();
|
|
64
|
+
|
|
65
|
+
expect(execSync).toHaveBeenCalledWith(
|
|
66
|
+
`npm config set "//registry.npmjs.org/:_authToken"="${mockNpmToken}"`
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('deletes NPM token from project .npmrc', async () => {
|
|
71
|
+
await subject();
|
|
72
|
+
|
|
73
|
+
expect(execSync).toHaveBeenCalledWith(
|
|
74
|
+
`npm config delete --location=project "//registry.npmjs.org/:_authToken"`
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('removes temp directory', async () => {
|
|
79
|
+
const rmSpy = jest.spyOn(fs, 'rmSync');
|
|
80
|
+
|
|
28
81
|
await subject();
|
|
29
82
|
|
|
30
|
-
expect(
|
|
83
|
+
expect(rmSpy).toHaveBeenCalledWith(tempDirPath, { recursive: true, force: true });
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test(`runs npm i ${npmOptions.join(' ')}`, async () => {
|
|
87
|
+
await subject();
|
|
88
|
+
|
|
89
|
+
expect(execSync).toHaveBeenCalledWith(`npm i ${npmOptions.join(' ')}`, {
|
|
31
90
|
stdio: 'inherit',
|
|
32
91
|
});
|
|
33
92
|
});
|
|
@@ -35,19 +94,72 @@ describe(`${Install.name}`, () => {
|
|
|
35
94
|
test('logs progress', async () => {
|
|
36
95
|
await subject();
|
|
37
96
|
|
|
38
|
-
|
|
97
|
+
[`startup cli v${startupVersion}`, 'Configuring NPM token', 'Running npm'].forEach(
|
|
98
|
+
message => {
|
|
99
|
+
expect(log.info).toHaveBeenCalledWith(expect.stringMatching(message));
|
|
100
|
+
}
|
|
101
|
+
);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
function itDoesNotConfigureNpmToken() {
|
|
105
|
+
test('does not configure NPM token', async () => {
|
|
106
|
+
await subject();
|
|
107
|
+
|
|
108
|
+
expect(execSync).not.toHaveBeenCalledWith(expect.stringMatching(/npm config/));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function itDoesNotDeleteProjectToken() {
|
|
113
|
+
test('does not delete project token', async () => {
|
|
114
|
+
await subject();
|
|
115
|
+
|
|
116
|
+
expect(execSync).not.toHaveBeenCalledWith(expect.stringMatching(/npm config delete/));
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
describe('when .npmrc uses an environment variable', () => {
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
volFromJSON({
|
|
123
|
+
'.npmrc': [
|
|
124
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
125
|
+
'#//registry.npmjs.org/:_authToken=${NPM_READONLY_TOKEN}', // should ignore this comment
|
|
126
|
+
// eslint-disable-next-line no-template-curly-in-string
|
|
127
|
+
'//registry.npmjs.org/:_authToken=${ST_NPM_READONLY_TOKEN}',
|
|
128
|
+
].join('\n'),
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('runs npm i with environment variable', async () => {
|
|
133
|
+
await subject();
|
|
134
|
+
|
|
135
|
+
expect(execSync).toHaveBeenCalledWith(`npm i ${npmOptions.join(' ')}`, {
|
|
136
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
137
|
+
env: expect.objectContaining({ ST_NPM_READONLY_TOKEN: mockNpmToken }),
|
|
138
|
+
stdio: 'inherit',
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
itDoesNotDeleteProjectToken();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('with no .npmrc', () => {
|
|
146
|
+
beforeEach(() => fs.rmSync('.npmrc'));
|
|
147
|
+
|
|
148
|
+
itDoesNotDeleteProjectToken();
|
|
39
149
|
});
|
|
40
150
|
|
|
41
151
|
describe('when in CI environment', () => {
|
|
42
|
-
beforeEach(() => (
|
|
152
|
+
beforeEach(() => jest.mocked(isCI).mockReturnValue(true));
|
|
43
153
|
|
|
44
154
|
test('runs npm ci', async () => {
|
|
45
155
|
await subject();
|
|
46
156
|
|
|
47
|
-
expect(execSync).toHaveBeenCalledWith(`npm ci ${
|
|
157
|
+
expect(execSync).toHaveBeenCalledWith(`npm ci ${npmOptions.join(' ')}`, {
|
|
48
158
|
stdio: 'inherit',
|
|
49
159
|
});
|
|
50
160
|
});
|
|
161
|
+
|
|
162
|
+
itDoesNotConfigureNpmToken();
|
|
51
163
|
});
|
|
52
164
|
|
|
53
165
|
describe('with --quite', () => {
|
|
@@ -61,7 +173,7 @@ describe(`${Install.name}`, () => {
|
|
|
61
173
|
});
|
|
62
174
|
|
|
63
175
|
describe('with --fix', () => {
|
|
64
|
-
const fixOptions = [...
|
|
176
|
+
const fixOptions = [...npmOptions, '--package-lock-only', '--prefer-dedupe'];
|
|
65
177
|
|
|
66
178
|
beforeEach(() => (args = { fix: true }));
|
|
67
179
|
|
|
@@ -73,5 +185,55 @@ describe(`${Install.name}`, () => {
|
|
|
73
185
|
expect.anything()
|
|
74
186
|
);
|
|
75
187
|
});
|
|
188
|
+
|
|
189
|
+
itDoesNotConfigureNpmToken();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
describe('with --no-token', () => {
|
|
193
|
+
beforeEach(() => (args = { token: false }));
|
|
194
|
+
|
|
195
|
+
itDoesNotConfigureNpmToken();
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
function itLogsError(message: string | RegExp) {
|
|
199
|
+
test('logs error', async () => {
|
|
200
|
+
await subject();
|
|
201
|
+
|
|
202
|
+
expect(log.warning).toHaveBeenCalledWith(expect.stringMatching(message));
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
describe('when error occurs fetching token', () => {
|
|
207
|
+
beforeEach(() => jest.mocked(gitCloneRepo).mockResolvedValue(false));
|
|
208
|
+
|
|
209
|
+
itLogsError(/could not clone servicetitan\/frontend-dev-config/);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
describe('when .npm.json is not an object', () => {
|
|
213
|
+
beforeEach(() => volFromJSON({ [`${tempDirPath}/.npm.json`]: JSON.stringify('') }));
|
|
214
|
+
|
|
215
|
+
itLogsError(/is not an object/);
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
describe('when .npm.json omits readOnlyToken', () => {
|
|
219
|
+
beforeEach(() => volFromJSON({ [`${tempDirPath}/.npm.json`]: JSON.stringify({}) }));
|
|
220
|
+
|
|
221
|
+
itLogsError(/does not contain auth token/);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
describe('when readOnlyToken is blank', () => {
|
|
225
|
+
beforeEach(() =>
|
|
226
|
+
volFromJSON({ [`${tempDirPath}/.npm.json`]: JSON.stringify({ readOnlyToken: '' }) })
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
itLogsError(/does not contain auth token/);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe('when readOnlyToken is not a string', () => {
|
|
233
|
+
beforeEach(() =>
|
|
234
|
+
volFromJSON({ [`${tempDirPath}/.npm.json`]: JSON.stringify({ readOnlyToken: {} }) })
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
itLogsError(/token is not a string/);
|
|
76
238
|
});
|
|
77
239
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { fs, vol } from 'memfs';
|
|
2
2
|
import { isWebComponent, log } from '../../../utils';
|
|
3
|
-
import { gitGetBranch } from '../../utils
|
|
4
|
-
import { Version, npmGetPackageVersionsDetails, npmUnpublish } from '../../utils/cli-npm';
|
|
3
|
+
import { gitGetBranch, npmGetPackageVersionsDetails, npmUnpublish, Version } from '../../utils';
|
|
5
4
|
|
|
6
5
|
import { MFEPackageClean } from '../mfe-package-clean';
|
|
7
6
|
|
|
@@ -11,13 +10,11 @@ jest.mock('../../../utils', () => ({
|
|
|
11
10
|
isWebComponent: jest.fn(),
|
|
12
11
|
log: { error: jest.fn(), info: jest.fn() },
|
|
13
12
|
}));
|
|
14
|
-
jest.mock('../../utils
|
|
13
|
+
jest.mock('../../utils', () => ({
|
|
14
|
+
gitGetBranch: jest.fn(),
|
|
15
15
|
npmGetPackageVersionsDetails: jest.fn(),
|
|
16
16
|
npmUnpublish: jest.fn(),
|
|
17
17
|
}));
|
|
18
|
-
jest.mock('../../utils/cli-git', () => ({
|
|
19
|
-
gitGetBranch: jest.fn(),
|
|
20
|
-
}));
|
|
21
18
|
|
|
22
19
|
const DEFAULT_VERSIONS_TO_KEEP = 5;
|
|
23
20
|
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { fs, vol } from 'memfs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { isWebComponent, log, readJson, WebComponentBranchConfigs } from '../../../utils';
|
|
4
|
-
import { gitGetBranch, gitGetCommitHash } from '../../utils/cli-git';
|
|
5
4
|
import {
|
|
5
|
+
gitGetBranch,
|
|
6
|
+
gitGetCommitHash,
|
|
6
7
|
npmGetPackageVersions,
|
|
7
8
|
npmPackageSet,
|
|
8
9
|
npmPublish,
|
|
9
10
|
npmTagVersion,
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
runCommand,
|
|
12
|
+
} from '../../utils';
|
|
12
13
|
import { MFEPackagePublish } from '../mfe-package-publish';
|
|
13
14
|
|
|
14
15
|
jest.mock('fs', () => fs);
|
|
@@ -17,17 +18,14 @@ jest.mock('../../../utils', () => ({
|
|
|
17
18
|
isWebComponent: jest.fn(),
|
|
18
19
|
log: { info: jest.fn(), warning: jest.fn() },
|
|
19
20
|
}));
|
|
20
|
-
jest.mock('../../utils
|
|
21
|
+
jest.mock('../../utils', () => ({
|
|
22
|
+
...jest.requireActual('../../utils'),
|
|
21
23
|
gitGetBranch: jest.fn(),
|
|
22
24
|
gitGetCommitHash: jest.fn(),
|
|
23
|
-
}));
|
|
24
|
-
jest.mock('../../utils/cli-npm', () => ({
|
|
25
25
|
npmGetPackageVersions: jest.fn(),
|
|
26
26
|
npmPackageSet: jest.fn(),
|
|
27
27
|
npmPublish: jest.fn(),
|
|
28
28
|
npmTagVersion: jest.fn(),
|
|
29
|
-
}));
|
|
30
|
-
jest.mock('../../utils/cli-os', () => ({
|
|
31
29
|
runCommand: jest.fn(),
|
|
32
30
|
}));
|
|
33
31
|
|
|
@@ -2,11 +2,16 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
import { vol, fs } from 'memfs';
|
|
3
3
|
import { inspect } from 'node:util';
|
|
4
4
|
import path from 'path';
|
|
5
|
+
import { isCI } from '../../utils';
|
|
5
6
|
import { log } from '../../../utils';
|
|
6
7
|
import { UploadSourcemaps } from '../upload-sourcemaps';
|
|
7
8
|
|
|
8
9
|
jest.mock('child_process', () => ({ execSync: jest.fn() }));
|
|
9
10
|
jest.mock('fs', () => fs);
|
|
11
|
+
jest.mock('../../utils', () => ({
|
|
12
|
+
...jest.requireActual('../../utils'),
|
|
13
|
+
isCI: jest.fn(),
|
|
14
|
+
}));
|
|
10
15
|
jest.mock('../../../utils', () => ({
|
|
11
16
|
...jest.requireActual('../../../utils'),
|
|
12
17
|
log: { info: jest.fn(), warning: jest.fn() },
|
|
@@ -33,6 +38,7 @@ describe(`[startup] ${UploadSourcemaps.name}`, () => {
|
|
|
33
38
|
|
|
34
39
|
beforeEach(() => {
|
|
35
40
|
jest.clearAllMocks();
|
|
41
|
+
jest.mocked(isCI).mockReturnValue(false);
|
|
36
42
|
|
|
37
43
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
38
44
|
process.env = { DATADOG_API_KEY: 'datadog-api-key' };
|
|
@@ -91,9 +97,7 @@ describe(`[startup] ${UploadSourcemaps.name}`, () => {
|
|
|
91
97
|
itThrowsError('DATADOG_API_KEY environment variable is not set');
|
|
92
98
|
|
|
93
99
|
describe('when in CI environment', () => {
|
|
94
|
-
beforeEach(() => (
|
|
95
|
-
|
|
96
|
-
afterEach(() => delete process.env.CI);
|
|
100
|
+
beforeEach(() => jest.mocked(isCI).mockReturnValue(true));
|
|
97
101
|
|
|
98
102
|
test('logs warning', async () => {
|
|
99
103
|
await subject();
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -2,15 +2,14 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
|
|
4
4
|
import { log, logErrors } from '../../utils';
|
|
5
|
-
import {
|
|
6
|
-
import { Command } from './';
|
|
5
|
+
import { gitCloneRepo, gitIsReachable } from '../utils';
|
|
6
|
+
import { Command } from './types';
|
|
7
7
|
|
|
8
8
|
interface Args {
|
|
9
9
|
output?: string;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
const
|
|
13
|
-
const sshUrl = 'git@github.com:servicetitan/frontend-example.git';
|
|
12
|
+
const REPO_NAME = 'frontend-example';
|
|
14
13
|
|
|
15
14
|
export class Init implements Command {
|
|
16
15
|
constructor(private readonly args: Args) {}
|
|
@@ -30,44 +29,25 @@ export class Init implements Command {
|
|
|
30
29
|
throw new Error(`${destination} is not an empty directory`);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
webUrl.replace('github.com', `oauth2:${process.env.GITHUB_TOKEN}@github.com`)
|
|
37
|
-
);
|
|
32
|
+
if (await this.cloneRepo(destination)) {
|
|
33
|
+
log.info(`copied example project to ${destination}`);
|
|
34
|
+
return;
|
|
38
35
|
}
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (await cloneRepo(url, destination)) {
|
|
43
|
-
log.info(`copied example project to ${destination}`);
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
if (!gitUrls.some(isReachable)) {
|
|
49
|
-
throw new Error('could not read servicetitan/frontend-example repository');
|
|
37
|
+
if (!gitIsReachable({ name: REPO_NAME })) {
|
|
38
|
+
throw new Error(`could not read servicetitan/${REPO_NAME} repository`);
|
|
50
39
|
}
|
|
51
40
|
}
|
|
52
|
-
}
|
|
53
41
|
|
|
54
|
-
async
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
fs.rmSync(path.join(destination, '.git'), { recursive: true, force: true });
|
|
61
|
-
fs.rmSync(path.join(destination, '.github', 'CODEOWNERS'));
|
|
62
|
-
fs.rmSync(path.join(destination, 'package-lock.json'));
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
42
|
+
async cloneRepo(destination: string) {
|
|
43
|
+
const ok = await gitCloneRepo({ destination, name: REPO_NAME });
|
|
44
|
+
if (!ok) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
65
47
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
return false;
|
|
48
|
+
fs.rmSync(path.join(destination, '.git'), { recursive: true, force: true });
|
|
49
|
+
fs.rmSync(path.join(destination, '.github', 'CODEOWNERS'));
|
|
50
|
+
fs.rmSync(path.join(destination, 'package-lock.json'));
|
|
51
|
+
return true;
|
|
71
52
|
}
|
|
72
|
-
return true;
|
|
73
53
|
}
|
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
|
-
import
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { log, logErrors, getStartupVersion, readJsonSafe } from '../../utils';
|
|
3
6
|
import { Command } from './types';
|
|
7
|
+
import { gitCloneRepo, isCI } from '../utils';
|
|
4
8
|
|
|
5
9
|
interface Args {
|
|
6
10
|
quiet?: boolean;
|
|
7
11
|
fix?: boolean;
|
|
12
|
+
token?: boolean;
|
|
8
13
|
}
|
|
9
14
|
|
|
15
|
+
const REPO_NAME = 'frontend-dev-config';
|
|
16
|
+
const AUTH_TOKEN_KEY = '//registry.npmjs.org/:_authToken';
|
|
17
|
+
const AUTH_TOKEN_REGEX = /^\/\/registry\.npmjs\.org\/:_authToken=\s*\${([^}]+)}/m;
|
|
18
|
+
|
|
10
19
|
export class Install implements Command {
|
|
11
20
|
constructor(private readonly args?: Args) {}
|
|
12
21
|
|
|
@@ -16,12 +25,92 @@ export class Install implements Command {
|
|
|
16
25
|
|
|
17
26
|
@logErrors
|
|
18
27
|
async execute() {
|
|
28
|
+
if (!this.args?.quiet) {
|
|
29
|
+
log.info(`startup cli v${getStartupVersion()}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const env = await this.configureNpmToken();
|
|
33
|
+
this.installPackages(env);
|
|
34
|
+
|
|
35
|
+
return Promise.resolve(); // stops "async method has no 'await' expression" lint error
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private async configureNpmToken() {
|
|
39
|
+
if (isCI() || this.args?.fix || this.args?.token === false) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!this.args?.quiet) {
|
|
44
|
+
log.info('Configuring NPM token ...');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const token = await this.fetchNpmToken();
|
|
48
|
+
if (!token) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
execSync(`npm config set "${AUTH_TOKEN_KEY}"="${token}"`);
|
|
53
|
+
|
|
54
|
+
if (!fs.existsSync('.npmrc')) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const npmrc = fs.readFileSync('.npmrc', 'utf-8');
|
|
59
|
+
const match = AUTH_TOKEN_REGEX.exec(npmrc);
|
|
60
|
+
if (match) {
|
|
61
|
+
return { [match[1]]: token };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
execSync(`npm config delete --location=project "${AUTH_TOKEN_KEY}"`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private async fetchNpmToken() {
|
|
68
|
+
const tempDirPath = fs.mkdtempSync(path.join(os.tmpdir(), 'st-install-'));
|
|
69
|
+
try {
|
|
70
|
+
if (!(await gitCloneRepo({ destination: tempDirPath, name: REPO_NAME }))) {
|
|
71
|
+
throw new Error(`could not clone servicetitan/${REPO_NAME}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const npmJson = readJsonSafe(path.join(tempDirPath, '.npm.json'));
|
|
75
|
+
|
|
76
|
+
/* istanbul ignore next: debug only */
|
|
77
|
+
log.debug('install:fetch-token', () => JSON.stringify(npmJson, null, 2));
|
|
78
|
+
|
|
79
|
+
if (!((npmJson && typeof npmJson === 'object') || Array.isArray(npmJson))) {
|
|
80
|
+
throw new Error('.npm.json is not an object');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const { readOnlyToken: authToken } = npmJson;
|
|
84
|
+
if (!authToken) {
|
|
85
|
+
throw new Error('.npm.json does not contain auth token');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof authToken !== 'string') {
|
|
89
|
+
throw new Error('.npm.json auth token is not a string');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return Buffer.from(authToken, 'base64').toString('utf-8');
|
|
93
|
+
} catch (e) {
|
|
94
|
+
log.warning(String(e));
|
|
95
|
+
} finally {
|
|
96
|
+
try {
|
|
97
|
+
fs.rmSync(tempDirPath, { recursive: true, force: true });
|
|
98
|
+
} catch {
|
|
99
|
+
// ignore
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private installPackages(env?: Record<string, string>) {
|
|
105
|
+
/* istanbul ignore next: debug only */
|
|
106
|
+
log.debug('install:install-packages', () => JSON.stringify({ env }));
|
|
107
|
+
|
|
19
108
|
/**
|
|
20
109
|
* Note, if these are changed, update bootstrap.js to match
|
|
21
110
|
* @see {@link file://./../../../../../bootstrap.js}
|
|
22
111
|
*/
|
|
23
112
|
const npmArguments = [
|
|
24
|
-
|
|
113
|
+
isCI() ? 'ci' : 'i',
|
|
25
114
|
'--audit=false',
|
|
26
115
|
'--fund=false',
|
|
27
116
|
'--legacy-peer-deps',
|
|
@@ -29,12 +118,12 @@ export class Install implements Command {
|
|
|
29
118
|
].join(' ');
|
|
30
119
|
|
|
31
120
|
if (!this.args?.quiet) {
|
|
32
|
-
log.info(`startup cli v${getStartupVersion()}`);
|
|
33
121
|
log.info(`Running npm ${npmArguments} ...`);
|
|
34
122
|
}
|
|
35
123
|
|
|
36
|
-
execSync(`npm ${npmArguments}`, {
|
|
37
|
-
|
|
38
|
-
|
|
124
|
+
execSync(`npm ${npmArguments}`, {
|
|
125
|
+
...(env ? { env: { ...process.env, ...env } } : {}),
|
|
126
|
+
stdio: 'inherit',
|
|
127
|
+
});
|
|
39
128
|
}
|
|
40
129
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import { isWebComponent, log, logErrors, readJson } from '../../utils';
|
|
2
|
-
import {
|
|
3
|
-
import { gitGetBranch } from '../utils/cli-git';
|
|
4
|
-
import { Version, npmGetPackageVersionsDetails, npmUnpublish } from '../utils/cli-npm';
|
|
1
|
+
import { getBranchesConfigs, isWebComponent, log, logErrors, readJson } from '../../utils';
|
|
2
|
+
import { gitGetBranch, npmGetPackageVersionsDetails, npmUnpublish, Version } from '../utils';
|
|
5
3
|
import { Command } from './types';
|
|
6
4
|
|
|
7
5
|
export interface Args {
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import fs from 'fs';
|
|
3
|
-
import {
|
|
4
|
-
|
|
3
|
+
import {
|
|
4
|
+
getBranchesConfigs,
|
|
5
|
+
getFolders,
|
|
6
|
+
isWebComponent,
|
|
7
|
+
log,
|
|
8
|
+
logErrors,
|
|
9
|
+
readJson,
|
|
10
|
+
} from '../../utils';
|
|
5
11
|
import { EntryPoint, EntryPoints, Metadata } from '../../webpack/configs';
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
12
|
+
import {
|
|
13
|
+
getDefaultBuildVersion,
|
|
14
|
+
gitGetBranch,
|
|
15
|
+
gitGetCommitHash,
|
|
16
|
+
npmGetPackageVersions,
|
|
17
|
+
npmPackageSet,
|
|
18
|
+
npmPublish,
|
|
19
|
+
npmTagVersion,
|
|
20
|
+
runCommand,
|
|
21
|
+
} from '../utils';
|
|
10
22
|
import { Command } from './types';
|
|
11
23
|
|
|
12
24
|
export interface Args {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
3
|
import { getTsConfig, isWebComponent, log, logErrors, readJson } from '../../utils';
|
|
4
|
+
import { isCI, TSConfig } from '../utils';
|
|
4
5
|
import { Command } from './types';
|
|
5
|
-
import { TSConfig } from '../utils';
|
|
6
6
|
|
|
7
7
|
interface Args {
|
|
8
8
|
dry?: boolean;
|
|
@@ -35,7 +35,7 @@ export class UploadSourcemaps implements Command {
|
|
|
35
35
|
private checkArgs() {
|
|
36
36
|
if (!process.env.DATADOG_API_KEY) {
|
|
37
37
|
const message = 'DATADOG_API_KEY environment variable is not set';
|
|
38
|
-
if (!
|
|
38
|
+
if (!isCI()) {
|
|
39
39
|
throw new Error(message);
|
|
40
40
|
}
|
|
41
41
|
log.warning(`${message}; skipping sourcemaps`);
|
package/src/cli/index.ts
CHANGED
|
@@ -3,8 +3,7 @@ import path from 'path';
|
|
|
3
3
|
import { argv, Arguments } from 'yargs';
|
|
4
4
|
import { CommandName, getStartupVersion, log } from '../utils';
|
|
5
5
|
import { getCommand, getUserCommands } from './commands';
|
|
6
|
-
import { setNodeOptions } from './utils';
|
|
7
|
-
import { maybeCreateGitFolder } from './utils/maybe-create-git-folder';
|
|
6
|
+
import { maybeCreateGitFolder, setNodeOptions } from './utils';
|
|
8
7
|
|
|
9
8
|
const argvSync = argv as Arguments;
|
|
10
9
|
const name = argvSync._[0]?.toString() as CommandName;
|