@servicetitan/startup 27.4.0 → 28.1.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/get-command.d.ts.map +1 -1
- package/dist/cli/commands/get-command.js +4 -2
- package/dist/cli/commands/get-command.js.map +1 -1
- package/dist/cli/commands/init.d.ts +0 -1
- package/dist/cli/commands/init.d.ts.map +1 -1
- package/dist/cli/commands/init.js +39 -9
- package/dist/cli/commands/init.js.map +1 -1
- package/dist/cli/commands/mfe-package-clean.d.ts +14 -0
- package/dist/cli/commands/mfe-package-clean.d.ts.map +1 -0
- package/dist/cli/commands/mfe-package-clean.js +124 -0
- package/dist/cli/commands/mfe-package-clean.js.map +1 -0
- package/dist/cli/commands/mfe-package-publish.d.ts +20 -0
- package/dist/cli/commands/mfe-package-publish.d.ts.map +1 -0
- package/dist/cli/commands/mfe-package-publish.js +153 -0
- package/dist/cli/commands/mfe-package-publish.js.map +1 -0
- package/dist/cli/commands/mfe-publish.d.ts +6 -30
- package/dist/cli/commands/mfe-publish.d.ts.map +1 -1
- package/dist/cli/commands/mfe-publish.js +14 -226
- package/dist/cli/commands/mfe-publish.js.map +1 -1
- package/dist/cli/utils/assets-copy.d.ts.map +1 -1
- package/dist/cli/utils/assets-copy.js +3 -3
- package/dist/cli/utils/assets-copy.js.map +1 -1
- package/dist/cli/utils/cli-os.d.ts +9 -2
- package/dist/cli/utils/cli-os.d.ts.map +1 -1
- package/dist/cli/utils/cli-os.js +16 -8
- package/dist/cli/utils/cli-os.js.map +1 -1
- package/dist/cli/utils/styles-copy.d.ts.map +1 -1
- package/dist/cli/utils/styles-copy.js +3 -3
- package/dist/cli/utils/styles-copy.js.map +1 -1
- package/dist/utils/get-branch-configs.d.ts +3 -0
- package/dist/utils/get-branch-configs.d.ts.map +1 -0
- package/dist/utils/get-branch-configs.js +18 -0
- package/dist/utils/get-branch-configs.js.map +1 -0
- package/dist/utils/get-configuration.d.ts +13 -3
- package/dist/utils/get-configuration.d.ts.map +1 -1
- package/dist/utils/get-configuration.js +28 -5
- package/dist/utils/get-configuration.js.map +1 -1
- package/dist/utils/get-folders.js +2 -2
- package/dist/utils/get-folders.js.map +1 -1
- package/dist/utils/get-jest-config.d.ts.map +1 -1
- package/dist/utils/get-jest-config.js +2 -10
- package/dist/utils/get-jest-config.js.map +1 -1
- package/dist/webpack/configs/dev-server-config.d.ts.map +1 -1
- package/dist/webpack/configs/dev-server-config.js +1 -1
- package/dist/webpack/configs/dev-server-config.js.map +1 -1
- package/dist/webpack/configs/plugins/ignore-plugin/is-optional-anvil-peer-dependency.js +1 -1
- package/dist/webpack/configs/plugins/ignore-plugin/is-optional-anvil-peer-dependency.js.map +1 -1
- package/dist/webpack/configs/plugins/index.d.ts +1 -0
- package/dist/webpack/configs/plugins/index.d.ts.map +1 -1
- package/dist/webpack/configs/plugins/index.js +1 -0
- package/dist/webpack/configs/plugins/index.js.map +1 -1
- package/dist/webpack/configs/plugins/virtual-modules-plugin.d.ts.map +1 -1
- package/dist/webpack/configs/plugins/virtual-modules-plugin.js +15 -5
- package/dist/webpack/configs/plugins/virtual-modules-plugin.js.map +1 -1
- package/dist/webpack/configs/plugins/watch-run-plugin.d.ts +8 -0
- package/dist/webpack/configs/plugins/watch-run-plugin.d.ts.map +1 -0
- package/dist/webpack/configs/plugins/watch-run-plugin.js +26 -0
- package/dist/webpack/configs/plugins/watch-run-plugin.js.map +1 -0
- package/dist/webpack/configs/plugins-config.d.ts.map +1 -1
- package/dist/webpack/configs/plugins-config.js +1 -0
- package/dist/webpack/configs/plugins-config.js.map +1 -1
- package/dist/webpack/configs/types.d.ts +16 -0
- package/dist/webpack/configs/types.d.ts.map +1 -1
- package/dist/webpack/configs/utils/generate-metadata.d.ts.map +1 -1
- package/dist/webpack/configs/utils/generate-metadata.js +3 -3
- package/dist/webpack/configs/utils/generate-metadata.js.map +1 -1
- package/jest/jest-preset.js +9 -0
- package/package.json +18 -23
- package/src/cli/commands/__tests__/init.test.ts +108 -28
- package/src/cli/commands/__tests__/mfe-package-clean.test.ts +45 -6
- package/src/cli/commands/__tests__/mfe-package-publish.test.ts +77 -7
- package/src/cli/commands/__tests__/mfe-publish.test.ts +19 -1
- package/src/cli/commands/__tests__/tests.test.ts +4 -0
- package/src/cli/commands/get-command.ts +3 -1
- package/src/cli/commands/init.ts +40 -10
- package/src/cli/commands/mfe-package-clean.ts +143 -0
- package/src/cli/commands/mfe-package-publish.ts +189 -0
- package/src/cli/commands/mfe-publish.ts +18 -298
- package/src/cli/utils/__tests__/assets-copy.test.ts +3 -7
- package/src/cli/utils/__tests__/cli-os.test.ts +41 -6
- package/src/cli/utils/__tests__/eslint.test.ts +4 -0
- package/src/cli/utils/__tests__/styles-copy.test.ts +3 -7
- package/src/cli/utils/assets-copy.ts +3 -3
- package/src/cli/utils/cli-os.ts +20 -8
- package/src/cli/utils/styles-copy.ts +3 -3
- package/src/utils/__tests__/get-branch-configs.test.ts +36 -0
- package/src/utils/__tests__/get-configuration.test.ts +65 -0
- package/src/utils/__tests__/get-jest-config.test.ts +1 -7
- package/src/utils/__tests__/load-shared-dependencies.test.ts +82 -88
- package/src/utils/get-branch-configs.ts +17 -0
- package/src/utils/get-configuration.ts +45 -7
- package/src/utils/get-folders.ts +1 -1
- package/src/utils/get-jest-config.ts +2 -10
- package/src/webpack/__tests__/create-webpack-config-shared-dependencies.test.ts +0 -1
- package/src/webpack/__tests__/create-webpack-config-web-component.test.ts +47 -13
- package/src/webpack/__tests__/create-webpack-config.test.ts +3 -2
- package/src/webpack/configs/dev-server-config.ts +2 -1
- package/src/webpack/configs/plugins/ignore-plugin/is-optional-anvil-peer-dependency.ts +1 -1
- package/src/webpack/configs/plugins/index.ts +1 -0
- package/src/webpack/configs/plugins/virtual-modules-plugin.ts +17 -5
- package/src/webpack/configs/plugins/watch-run-plugin.ts +23 -0
- package/src/webpack/configs/plugins-config.ts +2 -0
- package/src/webpack/configs/types.ts +19 -0
- package/src/webpack/configs/utils/generate-metadata.ts +5 -5
- package/tsconfig/base.json +1 -1
- package/template/.eslintrc.json +0 -3
- package/template/.gitignore +0 -21
- package/template/.npmrc +0 -3
- package/template/.prettierrc +0 -9
- package/template/.stylelintignore +0 -1
- package/template/.stylelintrc.json +0 -3
- package/template/.vscode/extensions.json +0 -18
- package/template/.vscode/settings.json +0 -4
- package/template/lerna.json +0 -4
- package/template/package.json +0 -32
- package/template/packages/application/package.json +0 -35
- package/template/packages/application/src/__tests__/app.test.tsx +0 -33
- package/template/packages/application/src/app.css +0 -3
- package/template/packages/application/src/app.tsx +0 -45
- package/template/packages/application/src/design-system.css +0 -3
- package/template/packages/application/src/index.tsx +0 -8
- package/template/packages/application/src/main-page.tsx +0 -5
- package/template/packages/application/src/second-page.tsx +0 -5
- package/template/packages/application/tsconfig.json +0 -13
- package/template/packages/feature-a/package.json +0 -19
- package/template/packages/feature-a/src/index.ts +0 -0
- package/template/packages/feature-a/tsconfig.json +0 -9
- package/template/packages/feature-b/package.json +0 -19
- package/template/packages/feature-b/src/index.ts +0 -0
- package/template/packages/feature-b/tsconfig.json +0 -9
- package/template/packages/feature-c/package.json +0 -19
- package/template/packages/feature-c/src/index.ts +0 -0
- package/template/packages/feature-c/tsconfig.json +0 -9
- package/template/setupTests.ts +0 -27
- package/template/tsconfig.test.json +0 -5
- package/template-react18/packages/application/package.json +0 -35
- package/template-react18/packages/application/src/index.tsx +0 -9
- package/template-react18/packages/feature-a/package.json +0 -19
- package/template-react18/packages/feature-b/package.json +0 -19
- package/template-react18/packages/feature-c/package.json +0 -19
- package/tsconfig.json +0 -13
|
@@ -1,67 +1,118 @@
|
|
|
1
|
-
import cpx from 'cpx2';
|
|
2
|
-
import path from 'path';
|
|
3
1
|
import { fs, vol } from 'memfs';
|
|
2
|
+
import { mkdirSync, rmSync } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
4
|
|
|
5
5
|
import { Init } from '../init';
|
|
6
|
+
import { runCommand, runCommandOutput } from '../../utils/cli-os';
|
|
6
7
|
|
|
7
8
|
jest.mock('fs', () => fs);
|
|
8
|
-
|
|
9
|
-
jest.mock('cpx2', () => ({
|
|
10
|
-
copy: jest.fn().mockImplementation((...args: any[]) => {
|
|
11
|
-
const callback = args[args.length - 1];
|
|
12
|
-
callback(null);
|
|
13
|
-
}),
|
|
14
|
-
}));
|
|
15
|
-
|
|
9
|
+
jest.mock('../../utils//cli-os', () => ({ runCommand: jest.fn(), runCommandOutput: jest.fn() }));
|
|
16
10
|
jest.mock('../../../utils', () => ({
|
|
17
11
|
log: { info: jest.fn() }, // suppress log output
|
|
18
12
|
}));
|
|
19
13
|
|
|
14
|
+
const webUrl = 'https://github.com/servicetitan/frontend-example.git';
|
|
15
|
+
const sshUrl = 'git@github.com:servicetitan/frontend-example.git';
|
|
16
|
+
|
|
20
17
|
describe(`[startup] ${Init.name}`, () => {
|
|
21
18
|
let args: ConstructorParameters<typeof Init>[0];
|
|
22
19
|
|
|
23
20
|
beforeEach(() => {
|
|
24
21
|
args = {};
|
|
25
22
|
vol.reset();
|
|
23
|
+
jest.clearAllMocks();
|
|
24
|
+
jest.mocked(runCommand).mockImplementation(jest.fn());
|
|
25
|
+
jest.mocked(runCommandOutput).mockImplementation(jest.fn());
|
|
26
|
+
jest.spyOn(fs, 'mkdirSync').mockImplementation(jest.fn());
|
|
27
|
+
jest.spyOn(fs, 'rmSync').mockImplementation(jest.fn());
|
|
26
28
|
});
|
|
27
29
|
|
|
28
30
|
const subject = async () => new Init(args).execute();
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
test(`clones ${webUrl} to current directory`, async () => {
|
|
33
|
+
const cwd = path.resolve('.');
|
|
34
|
+
await subject();
|
|
35
|
+
|
|
36
|
+
expect(runCommand).toHaveBeenCalledWith(`git clone -q ${webUrl} ${cwd}`, {
|
|
37
|
+
quiet: true,
|
|
38
|
+
});
|
|
39
|
+
expect(rmSync).toHaveBeenCalledWith(path.join(cwd, '.git'), {
|
|
40
|
+
recursive: true,
|
|
41
|
+
force: true,
|
|
42
|
+
});
|
|
43
|
+
expect(rmSync).toHaveBeenCalledWith(path.join(cwd, '.github', 'CODEOWNERS'));
|
|
44
|
+
expect(rmSync).toHaveBeenCalledWith(path.join(cwd, 'package-lock.json'));
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe(`when cloning ${webUrl} fails`, () => {
|
|
48
|
+
beforeEach(() => jest.mocked(runCommand).mockRejectedValueOnce('Nope!'));
|
|
49
|
+
|
|
50
|
+
test(`clones ${sshUrl} to current directory`, async () => {
|
|
32
51
|
await subject();
|
|
33
52
|
|
|
34
|
-
expect(
|
|
35
|
-
|
|
36
|
-
process.cwd(),
|
|
53
|
+
expect(runCommand).toHaveBeenCalledWith(
|
|
54
|
+
`git clone -q ${sshUrl} ${path.resolve('.')}`,
|
|
37
55
|
expect.anything()
|
|
38
56
|
);
|
|
39
57
|
});
|
|
40
|
-
}
|
|
41
58
|
|
|
42
|
-
|
|
59
|
+
describe(`when cloning ${sshUrl} also fails`, () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
jest.mocked(runCommand).mockRejectedValue('Nope!');
|
|
62
|
+
});
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
|
|
64
|
+
test(`checks if ${webUrl} is reachable`, async () => {
|
|
65
|
+
await subject();
|
|
46
66
|
|
|
47
|
-
|
|
67
|
+
expect(runCommandOutput).toHaveBeenCalledWith(`git ls-remote -qt ${webUrl}`, {
|
|
68
|
+
quiet: true,
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe(`when ${webUrl} is not reachable`, () => {
|
|
73
|
+
beforeEach(() => {
|
|
74
|
+
jest.mocked(runCommandOutput).mockImplementationOnce(() => {
|
|
75
|
+
throw new Error('Oops!');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test(`checks if ${sshUrl} is reachable`, async () => {
|
|
80
|
+
await subject();
|
|
81
|
+
|
|
82
|
+
expect(runCommandOutput).toHaveBeenCalledWith(`git ls-remote -qt ${sshUrl}`, {
|
|
83
|
+
quiet: true,
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe(`when ${sshUrl} is also unreachable`, () => {
|
|
88
|
+
beforeEach(() =>
|
|
89
|
+
jest.mocked(runCommandOutput).mockImplementation(() => {
|
|
90
|
+
throw new Error('Oops');
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
test('raises error', async () => {
|
|
95
|
+
await expect(subject()).rejects.toThrow(
|
|
96
|
+
/could not read servicetitan\/frontend-example repository/
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
48
102
|
});
|
|
49
103
|
|
|
50
104
|
describe('with an output location', () => {
|
|
51
105
|
beforeEach(() => (args.output = 'foo/bar'));
|
|
52
106
|
|
|
53
|
-
test(
|
|
54
|
-
const mkdirSpy = jest.spyOn(fs, 'mkdirSync');
|
|
107
|
+
test(`clones ${webUrl} to output location`, async () => {
|
|
55
108
|
const destination = path.resolve(args.output!);
|
|
56
109
|
|
|
57
110
|
await subject();
|
|
58
111
|
|
|
59
|
-
expect(
|
|
60
|
-
expect(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
expect.anything()
|
|
64
|
-
);
|
|
112
|
+
expect(mkdirSync).toHaveBeenCalledWith(destination, { recursive: true });
|
|
113
|
+
expect(runCommand).toHaveBeenCalledWith(`git clone -q ${webUrl} ${destination}`, {
|
|
114
|
+
quiet: true,
|
|
115
|
+
});
|
|
65
116
|
});
|
|
66
117
|
|
|
67
118
|
describe('when output location is a file', () => {
|
|
@@ -71,5 +122,34 @@ describe(`[startup] ${Init.name}`, () => {
|
|
|
71
122
|
await expect(subject()).rejects.toThrow(/is not a directory/);
|
|
72
123
|
});
|
|
73
124
|
});
|
|
125
|
+
|
|
126
|
+
describe('when output location is not empty', () => {
|
|
127
|
+
beforeEach(() => vol.fromNestedJSON({ [args.output!]: { baz: '' } }));
|
|
128
|
+
|
|
129
|
+
test('raises error', async () => {
|
|
130
|
+
await expect(subject()).rejects.toThrow(/is not an empty directory/);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('when running in CI environment with GITHUB_TOKEN', () => {
|
|
136
|
+
const originalEnv = process.env;
|
|
137
|
+
const token = 'foo-bar';
|
|
138
|
+
|
|
139
|
+
beforeEach(() => {
|
|
140
|
+
process.env.CI = 'true';
|
|
141
|
+
process.env.GITHUB_TOKEN = token;
|
|
142
|
+
});
|
|
143
|
+
afterEach(() => (process.env = originalEnv));
|
|
144
|
+
|
|
145
|
+
test(`adds token to ${webUrl}`, async () => {
|
|
146
|
+
await subject();
|
|
147
|
+
|
|
148
|
+
const urlWithToken = webUrl.replace('github.com', `oauth2:${token}@github.com`);
|
|
149
|
+
expect(runCommand).toHaveBeenCalledWith(
|
|
150
|
+
`git clone -q ${urlWithToken} ${path.resolve('.')}`,
|
|
151
|
+
expect.anything()
|
|
152
|
+
);
|
|
153
|
+
});
|
|
74
154
|
});
|
|
75
155
|
});
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { fs, vol } from 'memfs';
|
|
2
2
|
import { isWebComponent, log } from '../../../utils';
|
|
3
|
+
import { gitGetBranch } from '../../utils/cli-git';
|
|
3
4
|
import { npmGetPackageVersionDates, npmUnpublish } from '../../utils/cli-npm';
|
|
4
5
|
|
|
5
|
-
import { MFEPackageClean } from '../mfe-
|
|
6
|
+
import { MFEPackageClean } from '../mfe-package-clean';
|
|
6
7
|
|
|
7
8
|
jest.mock('fs', () => fs);
|
|
8
9
|
jest.mock('../../../utils', () => ({
|
|
@@ -14,6 +15,9 @@ jest.mock('../../utils/cli-npm', () => ({
|
|
|
14
15
|
npmGetPackageVersionDates: jest.fn(),
|
|
15
16
|
npmUnpublish: jest.fn(),
|
|
16
17
|
}));
|
|
18
|
+
jest.mock('../../utils/cli-git', () => ({
|
|
19
|
+
gitGetBranch: jest.fn(),
|
|
20
|
+
}));
|
|
17
21
|
|
|
18
22
|
const DEFAULT_VERSIONS_TO_KEEP = 5;
|
|
19
23
|
|
|
@@ -23,18 +27,23 @@ describe(`[startup] ${MFEPackageClean.name}`, () => {
|
|
|
23
27
|
let args: ConstructorParameters<typeof MFEPackageClean>[0];
|
|
24
28
|
let versions: Record<string, Date>;
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
`0.0.0-master.${(0xff000000 + index).toString(16)}`,
|
|
30
|
+
function getBranchVersions(branchName: string, length: number) {
|
|
31
|
+
return Object.fromEntries(
|
|
32
|
+
Array.from({ length }).map((_, index) => [
|
|
33
|
+
`0.0.0-${branchName}.${(0xff000000 + index).toString(16)}`,
|
|
31
34
|
dayAgo(index + 1), // tests assume versions are in reverse chronological order
|
|
32
35
|
])
|
|
33
36
|
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
beforeEach(() => {
|
|
40
|
+
args = {};
|
|
41
|
+
versions = getBranchVersions('master', 10);
|
|
34
42
|
|
|
35
43
|
jest.resetAllMocks();
|
|
36
44
|
jest.mocked(isWebComponent).mockReturnValue(true);
|
|
37
45
|
jest.mocked(npmGetPackageVersionDates).mockImplementation(() => Object.entries(versions));
|
|
46
|
+
jest.mocked(gitGetBranch).mockImplementation(() => 'current');
|
|
38
47
|
vol.fromJSON({ 'package.json': JSON.stringify({ name: packageName }) });
|
|
39
48
|
});
|
|
40
49
|
|
|
@@ -74,6 +83,36 @@ describe(`[startup] ${MFEPackageClean.name}`, () => {
|
|
|
74
83
|
itLogsAndUnpublishedOldestPackages({ branch });
|
|
75
84
|
});
|
|
76
85
|
|
|
86
|
+
describe.each(['qa', true])('when selected branch is %s', (branch: string | true) => {
|
|
87
|
+
let branchedVersions: typeof versions = {};
|
|
88
|
+
const branchName = branch === true ? 'current' : branch;
|
|
89
|
+
|
|
90
|
+
beforeEach(() => {
|
|
91
|
+
args.branch = branch;
|
|
92
|
+
|
|
93
|
+
branchedVersions = getBranchVersions(branchName, 7);
|
|
94
|
+
|
|
95
|
+
versions = {
|
|
96
|
+
...versions,
|
|
97
|
+
...branchedVersions,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test(`logs and unpublishes oldest packages for ${branchName} branch`, async () => {
|
|
102
|
+
const oldest = Object.entries(branchedVersions).slice(DEFAULT_VERSIONS_TO_KEEP);
|
|
103
|
+
|
|
104
|
+
await subject();
|
|
105
|
+
|
|
106
|
+
expect(log.info).toHaveBeenCalledWith(
|
|
107
|
+
'found versions for unpublish:',
|
|
108
|
+
JSON.stringify({ [branchName]: oldest }, null, 4)
|
|
109
|
+
);
|
|
110
|
+
oldest.forEach(([version]) => {
|
|
111
|
+
expect(npmUnpublish).toHaveBeenCalledWith(registry, packageName, version);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
77
116
|
describe('with master versions generated by nerdbank versioning', () => {
|
|
78
117
|
beforeEach(() => {
|
|
79
118
|
versions = transform(versions, (v: string, index: number) =>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { fs, vol } from 'memfs';
|
|
2
|
-
import
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { isWebComponent, log, readJson } from '../../../utils';
|
|
3
4
|
import { gitGetBranch, gitGetCommitHash } from '../../utils/cli-git';
|
|
4
5
|
import {
|
|
5
6
|
npmGetPackageVersions,
|
|
@@ -8,7 +9,7 @@ import {
|
|
|
8
9
|
npmPublishDry,
|
|
9
10
|
npmTagVersion,
|
|
10
11
|
} from '../../utils/cli-npm';
|
|
11
|
-
import { MFEPackagePublish } from '../mfe-publish';
|
|
12
|
+
import { MFEPackagePublish } from '../mfe-package-publish';
|
|
12
13
|
|
|
13
14
|
jest.mock('fs', () => fs);
|
|
14
15
|
jest.mock('../../../utils', () => ({
|
|
@@ -44,7 +45,30 @@ describe(`[startup] ${MFEPackagePublish.name}`, () => {
|
|
|
44
45
|
jest.mocked(gitGetBranch).mockReturnValue(branch);
|
|
45
46
|
jest.mocked(gitGetCommitHash).mockReturnValue(commitHash);
|
|
46
47
|
jest.mocked(npmGetPackageVersions).mockReturnValue([]);
|
|
47
|
-
vol.
|
|
48
|
+
vol.fromNestedJSON({
|
|
49
|
+
'package.json': JSON.stringify({ name: packageName, files: [] }),
|
|
50
|
+
'tsconfig.json': JSON.stringify({
|
|
51
|
+
compilerOptions: { outDir: 'dist', rootDir: 'src' },
|
|
52
|
+
}),
|
|
53
|
+
'dist': {
|
|
54
|
+
'metadata.json': JSON.stringify({
|
|
55
|
+
entrypoints: {
|
|
56
|
+
full: { css: ['main.bundle.css'], js: ['main.bundle.js'] },
|
|
57
|
+
light: { css: ['main.bundle.css'], js: ['main.bundle.js'] },
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
'bundle': {
|
|
61
|
+
full: {
|
|
62
|
+
'main.bundle.css': '',
|
|
63
|
+
'main.bundle.js': '',
|
|
64
|
+
},
|
|
65
|
+
light: {
|
|
66
|
+
'main.bundle.css': '',
|
|
67
|
+
'main.bundle.js': '',
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
});
|
|
48
72
|
});
|
|
49
73
|
|
|
50
74
|
afterEach(() => vol.reset());
|
|
@@ -239,17 +263,63 @@ describe(`[startup] ${MFEPackagePublish.name}`, () => {
|
|
|
239
263
|
});
|
|
240
264
|
});
|
|
241
265
|
|
|
242
|
-
|
|
243
|
-
beforeEach(() => jest.mocked(isWebComponent).mockReturnValue(false));
|
|
244
|
-
|
|
266
|
+
function itThrowsError(message: RegExp) {
|
|
245
267
|
test('throws error', async () => {
|
|
246
268
|
jest.spyOn(process.stdout, 'write').mockImplementation(jest.fn()); // suppress error output
|
|
247
269
|
expect.assertions(1);
|
|
248
270
|
try {
|
|
249
271
|
await subject();
|
|
250
272
|
} catch (error: any) {
|
|
251
|
-
expect(error.message).toMatch(
|
|
273
|
+
expect(error.message).toMatch(message);
|
|
252
274
|
}
|
|
253
275
|
});
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
describe('when package is not web component', () => {
|
|
279
|
+
beforeEach(() => jest.mocked(isWebComponent).mockReturnValue(false));
|
|
280
|
+
|
|
281
|
+
itThrowsError(/only web-components can be published/);
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe('when metadata.json is missing', () => {
|
|
285
|
+
beforeEach(() => fs.unlinkSync('dist/metadata.json'));
|
|
286
|
+
|
|
287
|
+
itThrowsError(/metadata.json is not present/);
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe(`when metadata.json contains no entrypoints`, () => {
|
|
291
|
+
beforeEach(() => {
|
|
292
|
+
const metadataJson = path.join('dist', 'metadata.json');
|
|
293
|
+
const metadata = readJson(metadataJson);
|
|
294
|
+
delete metadata.entrypoints;
|
|
295
|
+
fs.writeFileSync(metadataJson, JSON.stringify(metadata));
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
itThrowsError(/not found in metadata.json/);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
['full', 'light'].forEach(name => {
|
|
302
|
+
describe.each(['css', 'js'])(`when ${name}.%s entry point is missing`, key => {
|
|
303
|
+
beforeEach(() => {
|
|
304
|
+
const metadataJson = path.join('dist', 'metadata.json');
|
|
305
|
+
const metadata = readJson(metadataJson);
|
|
306
|
+
delete metadata.entrypoints[name][key];
|
|
307
|
+
fs.writeFileSync(metadataJson, JSON.stringify(metadata));
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
itThrowsError(new RegExp(`${name}.${key} not found in metadata.json`));
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
describe(`when ${name} JS bundle is missing`, () => {
|
|
314
|
+
beforeEach(() => fs.unlinkSync(`dist/bundle/${name}/main.bundle.js`));
|
|
315
|
+
|
|
316
|
+
itThrowsError(/referenced bundle main.bundle.js was not found/);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe(`when ${name} CSS bundle is missing`, () => {
|
|
320
|
+
beforeEach(() => fs.unlinkSync(`dist/bundle/${name}/main.bundle.css`));
|
|
321
|
+
|
|
322
|
+
itThrowsError(/referenced bundle main.bundle.css was not found/);
|
|
323
|
+
});
|
|
254
324
|
});
|
|
255
325
|
});
|
|
@@ -94,6 +94,25 @@ describe(`[startup] ${MFEPublish.name}`, () => {
|
|
|
94
94
|
expect.objectContaining({ cmd: 'startup mfe-package-clean' })
|
|
95
95
|
);
|
|
96
96
|
});
|
|
97
|
+
|
|
98
|
+
type ArgumentName = keyof typeof args;
|
|
99
|
+
const testArgs: { name: ArgumentName; value: any; expected?: string }[] = [
|
|
100
|
+
{ name: 'count', value: 10 },
|
|
101
|
+
{ name: 'branch', value: 'foo-123' },
|
|
102
|
+
{ name: 'branch', value: true, expected: '--branch' },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
describe.each(testArgs)('with "{$name: $value}"', ({ name, value, expected }) => {
|
|
106
|
+
beforeEach(() => (args[name] = value));
|
|
107
|
+
|
|
108
|
+
test(`runs clean with "${expected ?? `--${name} ${value}`}"`, async () => {
|
|
109
|
+
await subject();
|
|
110
|
+
|
|
111
|
+
expect(lernaExec).toHaveBeenCalledWith(
|
|
112
|
+
expect.objectContaining({ '--': [expected ?? `--${name} ${value}`] })
|
|
113
|
+
);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
97
116
|
});
|
|
98
117
|
|
|
99
118
|
type ArgumentName = keyof typeof args;
|
|
@@ -105,7 +124,6 @@ describe(`[startup] ${MFEPublish.name}`, () => {
|
|
|
105
124
|
{ name: 'dry', value: true, expected: '--dry' },
|
|
106
125
|
{ name: 'force', value: true, expected: '--force' },
|
|
107
126
|
{ name: 'registry', value: 'https://foo' },
|
|
108
|
-
{ name: 'count', value: 42 },
|
|
109
127
|
];
|
|
110
128
|
|
|
111
129
|
describe.each(testArgs)('with "{$name: $value}"', ({ name, value, expected }) => {
|
|
@@ -30,10 +30,14 @@ describe(`[startup] ${Tests.name}`, () => {
|
|
|
30
30
|
});
|
|
31
31
|
|
|
32
32
|
describe('when the command fails', () => {
|
|
33
|
+
const exitCode = process.exitCode;
|
|
34
|
+
|
|
33
35
|
beforeEach(() => {
|
|
34
36
|
jest.mocked(runCLI).mockResolvedValue({ results: { success: false } } as any);
|
|
35
37
|
});
|
|
36
38
|
|
|
39
|
+
afterAll(() => (process.exitCode = exitCode));
|
|
40
|
+
|
|
37
41
|
test('sets process execCode to 1', async () => {
|
|
38
42
|
await subject();
|
|
39
43
|
|
|
@@ -7,7 +7,9 @@ import { Init } from './init';
|
|
|
7
7
|
import { Install } from './install';
|
|
8
8
|
import { KendoUILicense } from './kendo-ui-license';
|
|
9
9
|
import { Lint } from './lint';
|
|
10
|
-
import { MFEPackageClean
|
|
10
|
+
import { MFEPackageClean } from './mfe-package-clean';
|
|
11
|
+
import { MFEPackagePublish } from './mfe-package-publish';
|
|
12
|
+
import { MFEPublish } from './mfe-publish';
|
|
11
13
|
import { PreparePackage } from './prepare-package';
|
|
12
14
|
import { Start } from './start';
|
|
13
15
|
import { StylesCheck } from './styles-check';
|
package/src/cli/commands/init.ts
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
|
-
import cpx from 'cpx2';
|
|
3
|
-
import util from 'util';
|
|
4
2
|
import path from 'path';
|
|
5
3
|
|
|
6
4
|
import { log, logErrors } from '../../utils';
|
|
5
|
+
import { runCommand, runCommandOutput } from '../utils/cli-os';
|
|
7
6
|
import { Command } from './';
|
|
8
7
|
|
|
9
8
|
interface Args {
|
|
10
|
-
react17?: boolean;
|
|
11
9
|
output?: string;
|
|
12
10
|
}
|
|
13
11
|
|
|
12
|
+
const webUrl = 'https://github.com/servicetitan/frontend-example.git';
|
|
13
|
+
const sshUrl = 'git@github.com:servicetitan/frontend-example.git';
|
|
14
|
+
|
|
14
15
|
export class Init implements Command {
|
|
15
16
|
constructor(private args: Args) {}
|
|
16
17
|
|
|
17
18
|
description() {
|
|
18
|
-
return '
|
|
19
|
+
return 'create example project';
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
@logErrors
|
|
@@ -25,18 +26,47 @@ export class Init implements Command {
|
|
|
25
26
|
fs.mkdirSync(destination, { recursive: true });
|
|
26
27
|
} else if (!fs.lstatSync(destination).isDirectory()) {
|
|
27
28
|
throw new Error(`${destination} is not a directory`);
|
|
29
|
+
} else if (fs.readdirSync(destination).length !== 0) {
|
|
30
|
+
throw new Error(`${destination} is not an empty directory`);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
const gitUrls = [webUrl, sshUrl];
|
|
34
|
+
if (!!process.env.CI && !!process.env.GITHUB_TOKEN) {
|
|
35
|
+
gitUrls.unshift(
|
|
36
|
+
webUrl.replace('github.com', `oauth2:${process.env.GITHUB_TOKEN}@github.com`)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
await
|
|
40
|
+
for await (const url of gitUrls) {
|
|
41
|
+
if (await cloneRepo(url, destination)) {
|
|
42
|
+
log.info(`copied example project to ${destination}`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
34
45
|
}
|
|
35
46
|
|
|
36
|
-
|
|
47
|
+
if (!gitUrls.some(isReachable)) {
|
|
48
|
+
throw new Error('could not read servicetitan/frontend-example repository');
|
|
49
|
+
}
|
|
37
50
|
}
|
|
38
51
|
}
|
|
39
52
|
|
|
40
|
-
async function
|
|
41
|
-
|
|
53
|
+
async function cloneRepo(url: string, destination: string) {
|
|
54
|
+
try {
|
|
55
|
+
await runCommand(`git clone -q ${url} ${destination}`, { quiet: true });
|
|
56
|
+
} catch (e) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
fs.rmSync(path.join(destination, '.git'), { recursive: true, force: true });
|
|
60
|
+
fs.rmSync(path.join(destination, '.github', 'CODEOWNERS'));
|
|
61
|
+
fs.rmSync(path.join(destination, 'package-lock.json'));
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isReachable(url: string) {
|
|
66
|
+
try {
|
|
67
|
+
runCommandOutput(`git ls-remote -qt ${url}`, { quiet: true });
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
42
72
|
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { isWebComponent, log, logErrors, readJson } from '../../utils';
|
|
2
|
+
import { gitGetBranch } from '../utils/cli-git';
|
|
3
|
+
import { npmGetPackageVersionDates, npmUnpublish } from '../utils/cli-npm';
|
|
4
|
+
import { getBranchesConfigs } from '../../utils/get-branch-configs';
|
|
5
|
+
import { Command } from './types';
|
|
6
|
+
|
|
7
|
+
export interface ArgsPackageClean {
|
|
8
|
+
branch?: string | true;
|
|
9
|
+
count?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class MFEPackageClean implements Command {
|
|
13
|
+
constructor(private args: ArgsPackageClean) {}
|
|
14
|
+
|
|
15
|
+
description() {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
@logErrors
|
|
20
|
+
async execute() {
|
|
21
|
+
if (!isWebComponent()) {
|
|
22
|
+
throw new Error('only web-components can be cleaned');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const data = this.getCleanData();
|
|
26
|
+
const packageJson = readJson('package.json');
|
|
27
|
+
const packageName = packageJson.name;
|
|
28
|
+
const branchedVersions = this.getBranchedVersions(packageName, data.registry);
|
|
29
|
+
|
|
30
|
+
log.info(
|
|
31
|
+
`branched versions (${data.count}):`,
|
|
32
|
+
JSON.stringify(branchedVersions, undefined, 4)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const branchedVersionsToClean: Record<string, [string, Date][]> = {};
|
|
36
|
+
|
|
37
|
+
for (const branch of Object.keys(branchedVersions)) {
|
|
38
|
+
// limit branches for now
|
|
39
|
+
if (!branchedVersions[branch] || !data.branches.includes(branch)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
branchedVersions[branch].sort(([, adt], [, bdt]) => (adt > bdt ? -1 : 1));
|
|
44
|
+
branchedVersionsToClean[branch] = branchedVersions[branch].slice(data.count);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
log.info(
|
|
48
|
+
'found versions for unpublish:',
|
|
49
|
+
JSON.stringify(branchedVersionsToClean, undefined, 4)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const unVersions = Object.keys(branchedVersionsToClean).reduce(
|
|
53
|
+
(out, br) => [...out, ...branchedVersionsToClean[br].map(([v]) => v)],
|
|
54
|
+
[]
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
for (const version of unVersions) {
|
|
58
|
+
try {
|
|
59
|
+
// eslint-disable-next-line no-await-in-loop
|
|
60
|
+
await npmUnpublish(data.registry, packageName, version);
|
|
61
|
+
} catch {
|
|
62
|
+
log.error(`error while removing ${packageName} version ${version}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private getCleanData(): {
|
|
68
|
+
count: number;
|
|
69
|
+
registry: string;
|
|
70
|
+
branches: string[];
|
|
71
|
+
} {
|
|
72
|
+
let count = this.args.count;
|
|
73
|
+
if (!count) {
|
|
74
|
+
count = 5;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let branches: string[];
|
|
78
|
+
|
|
79
|
+
if (this.args.branch === true) {
|
|
80
|
+
branches = [gitGetBranch()];
|
|
81
|
+
} else if (typeof this.args.branch === 'string') {
|
|
82
|
+
branches = [this.args.branch];
|
|
83
|
+
} else {
|
|
84
|
+
branches = Object.keys(getBranchesConfigs());
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const registry = 'https://verdaccio.servicetitan.com';
|
|
88
|
+
|
|
89
|
+
return { count, registry, branches };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private getBranchedVersions(packageName: string, registry: string) {
|
|
93
|
+
const versions = npmGetPackageVersionDates(registry, packageName);
|
|
94
|
+
const branchedVersions: Record<string, [string, Date][]> = {};
|
|
95
|
+
const unknownVersions: string[] = [];
|
|
96
|
+
|
|
97
|
+
const addVersion = (branch: string, version: string, dt: Date) => {
|
|
98
|
+
if (!branchedVersions[branch]) {
|
|
99
|
+
branchedVersions[branch] = [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
branchedVersions[branch].push([version, dt]);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
for (const [version, dt] of versions) {
|
|
106
|
+
if (!version.startsWith('0.0.0-')) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const buildVersion = version.replace('0.0.0-', '');
|
|
111
|
+
|
|
112
|
+
if (/^(\d+)\.(\d+)\.(\d+)$/.test(buildVersion)) {
|
|
113
|
+
// master version generated by nerdbank versioning
|
|
114
|
+
addVersion('master', version, dt);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const match1 = buildVersion.match(/^(\d+)\.(\d+)\.(\d+)-([\dA-Za-z-]+).([\dA-Za-z]+)$/);
|
|
119
|
+
|
|
120
|
+
if (match1?.length) {
|
|
121
|
+
// branch version generated by nerdbank versioning
|
|
122
|
+
addVersion(match1[4], version, dt);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const match2 = buildVersion.match(/^([\dA-Za-z-]+).([\dA-Za-z]+)$/);
|
|
127
|
+
|
|
128
|
+
if (match2?.length) {
|
|
129
|
+
// branch version generated by mfe-publisher versioning
|
|
130
|
+
addVersion(match2[1], version, dt);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
unknownVersions.push(version);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (unknownVersions.length) {
|
|
138
|
+
log.info('unknown versions:', unknownVersions.join());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return branchedVersions;
|
|
142
|
+
}
|
|
143
|
+
}
|