@servicetitan/startup 31.5.1 → 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.
Files changed (199) hide show
  1. package/dist/cli/commands/build.d.ts +1 -1
  2. package/dist/cli/commands/build.d.ts.map +1 -1
  3. package/dist/cli/commands/build.js.map +1 -1
  4. package/dist/cli/commands/bundle-package.d.ts +1 -1
  5. package/dist/cli/commands/bundle-package.d.ts.map +1 -1
  6. package/dist/cli/commands/bundle-package.js.map +1 -1
  7. package/dist/cli/commands/eslint.d.ts +1 -1
  8. package/dist/cli/commands/eslint.d.ts.map +1 -1
  9. package/dist/cli/commands/eslint.js.map +1 -1
  10. package/dist/cli/commands/get-command.js +2 -2
  11. package/dist/cli/commands/get-command.js.map +1 -1
  12. package/dist/cli/commands/index.d.ts +1 -1
  13. package/dist/cli/commands/index.d.ts.map +1 -1
  14. package/dist/cli/commands/index.js +1 -1
  15. package/dist/cli/commands/index.js.map +1 -1
  16. package/dist/cli/commands/init.d.ts +3 -2
  17. package/dist/cli/commands/init.d.ts.map +1 -1
  18. package/dist/cli/commands/init.js +24 -43
  19. package/dist/cli/commands/init.js.map +1 -1
  20. package/dist/cli/commands/install.d.ts +4 -0
  21. package/dist/cli/commands/install.d.ts.map +1 -1
  22. package/dist/cli/commands/install.js +91 -3
  23. package/dist/cli/commands/install.js.map +1 -1
  24. package/dist/cli/commands/lint.d.ts +1 -1
  25. package/dist/cli/commands/lint.d.ts.map +1 -1
  26. package/dist/cli/commands/lint.js.map +1 -1
  27. package/dist/cli/commands/mfe-package-clean.d.ts.map +1 -1
  28. package/dist/cli/commands/mfe-package-clean.js +5 -7
  29. package/dist/cli/commands/mfe-package-clean.js.map +1 -1
  30. package/dist/cli/commands/mfe-package-publish.d.ts.map +1 -1
  31. package/dist/cli/commands/mfe-package-publish.js +11 -15
  32. package/dist/cli/commands/mfe-package-publish.js.map +1 -1
  33. package/dist/cli/commands/mfe-publish.d.ts +1 -1
  34. package/dist/cli/commands/mfe-publish.d.ts.map +1 -1
  35. package/dist/cli/commands/mfe-publish.js.map +1 -1
  36. package/dist/cli/commands/prepare-package.d.ts +1 -1
  37. package/dist/cli/commands/prepare-package.d.ts.map +1 -1
  38. package/dist/cli/commands/prepare-package.js.map +1 -1
  39. package/dist/cli/commands/review/rules/index.d.ts.map +1 -1
  40. package/dist/cli/commands/review/rules/index.js +4 -0
  41. package/dist/cli/commands/review/rules/index.js.map +1 -1
  42. package/dist/cli/commands/review/rules/require-all-react-dependencies.d.ts +14 -0
  43. package/dist/cli/commands/review/rules/require-all-react-dependencies.d.ts.map +1 -0
  44. package/dist/cli/commands/review/rules/require-all-react-dependencies.js +64 -0
  45. package/dist/cli/commands/review/rules/require-all-react-dependencies.js.map +1 -0
  46. package/dist/cli/commands/review/rules/require-one-react-version.d.ts +5 -0
  47. package/dist/cli/commands/review/rules/require-one-react-version.d.ts.map +1 -0
  48. package/dist/cli/commands/review/rules/require-one-react-version.js +25 -0
  49. package/dist/cli/commands/review/rules/require-one-react-version.js.map +1 -0
  50. package/dist/cli/commands/review/rules/require-one-uikit-version.d.ts.map +1 -1
  51. package/dist/cli/commands/review/rules/require-one-uikit-version.js +1 -0
  52. package/dist/cli/commands/review/rules/require-one-uikit-version.js.map +1 -1
  53. package/dist/cli/commands/review/utils/format-depends-on.d.ts +1 -0
  54. package/dist/cli/commands/review/utils/format-depends-on.d.ts.map +1 -1
  55. package/dist/cli/commands/review/utils/format-depends-on.js +15 -8
  56. package/dist/cli/commands/review/utils/format-depends-on.js.map +1 -1
  57. package/dist/cli/commands/review/utils/set-version.d.ts +2 -1
  58. package/dist/cli/commands/review/utils/set-version.d.ts.map +1 -1
  59. package/dist/cli/commands/review/utils/set-version.js +4 -2
  60. package/dist/cli/commands/review/utils/set-version.js.map +1 -1
  61. package/dist/cli/commands/start.d.ts +1 -1
  62. package/dist/cli/commands/start.d.ts.map +1 -1
  63. package/dist/cli/commands/start.js.map +1 -1
  64. package/dist/cli/commands/test/index.d.ts +2 -0
  65. package/dist/cli/commands/test/index.d.ts.map +1 -0
  66. package/dist/cli/commands/test/index.js +20 -0
  67. package/dist/cli/commands/test/index.js.map +1 -0
  68. package/dist/cli/commands/test/runners/index.d.ts +3 -0
  69. package/dist/cli/commands/test/runners/index.d.ts.map +1 -0
  70. package/dist/cli/commands/test/runners/index.js +21 -0
  71. package/dist/cli/commands/test/runners/index.js.map +1 -0
  72. package/dist/cli/commands/test/runners/jest.d.ts +7 -0
  73. package/dist/cli/commands/test/runners/jest.d.ts.map +1 -0
  74. package/dist/cli/commands/test/runners/jest.js +43 -0
  75. package/dist/cli/commands/test/runners/jest.js.map +1 -0
  76. package/dist/cli/commands/test/runners/vitest.d.ts +4 -0
  77. package/dist/cli/commands/test/runners/vitest.d.ts.map +1 -0
  78. package/dist/cli/commands/test/runners/vitest.js +86 -0
  79. package/dist/cli/commands/test/runners/vitest.js.map +1 -0
  80. package/dist/cli/commands/test/tests.d.ts +18 -0
  81. package/dist/cli/commands/test/tests.d.ts.map +1 -0
  82. package/dist/cli/commands/{tests.js → test/tests.js} +10 -9
  83. package/dist/cli/commands/test/tests.js.map +1 -0
  84. package/dist/cli/commands/upload-sourcemaps.d.ts.map +1 -1
  85. package/dist/cli/commands/upload-sourcemaps.js +1 -1
  86. package/dist/cli/commands/upload-sourcemaps.js.map +1 -1
  87. package/dist/cli/index.js +1 -2
  88. package/dist/cli/index.js.map +1 -1
  89. package/dist/cli/utils/bundle.d.ts.map +1 -1
  90. package/dist/cli/utils/bundle.js +7 -4
  91. package/dist/cli/utils/bundle.js.map +1 -1
  92. package/dist/cli/utils/cli-git.d.ts +11 -2
  93. package/dist/cli/utils/cli-git.d.ts.map +1 -1
  94. package/dist/cli/utils/cli-git.js +60 -4
  95. package/dist/cli/utils/cli-git.js.map +1 -1
  96. package/dist/cli/utils/index.d.ts +6 -0
  97. package/dist/cli/utils/index.d.ts.map +1 -1
  98. package/dist/cli/utils/index.js +6 -0
  99. package/dist/cli/utils/index.js.map +1 -1
  100. package/dist/cli/utils/is-ci.d.ts +2 -0
  101. package/dist/cli/utils/is-ci.d.ts.map +1 -0
  102. package/dist/cli/utils/is-ci.js +15 -0
  103. package/dist/cli/utils/is-ci.js.map +1 -0
  104. package/dist/cli/utils/lerna-exec.d.ts.map +1 -1
  105. package/dist/cli/utils/lerna-exec.js +2 -1
  106. package/dist/cli/utils/lerna-exec.js.map +1 -1
  107. package/dist/cli/utils/set-node-options.d.ts.map +1 -1
  108. package/dist/cli/utils/set-node-options.js.map +1 -1
  109. package/dist/index.d.ts +0 -1
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +0 -1
  112. package/dist/index.js.map +1 -1
  113. package/dist/utils/get-branch-configs.d.ts +1 -1
  114. package/dist/utils/get-branch-configs.d.ts.map +1 -1
  115. package/dist/utils/get-branch-configs.js +2 -2
  116. package/dist/utils/get-branch-configs.js.map +1 -1
  117. package/dist/utils/get-configuration.d.ts +8 -2
  118. package/dist/utils/get-configuration.d.ts.map +1 -1
  119. package/dist/utils/get-configuration.js +10 -2
  120. package/dist/utils/get-configuration.js.map +1 -1
  121. package/dist/utils/get-jest-config.d.ts +1 -7
  122. package/dist/utils/get-jest-config.d.ts.map +1 -1
  123. package/dist/utils/get-jest-config.js +49 -58
  124. package/dist/utils/get-jest-config.js.map +1 -1
  125. package/dist/utils/index.d.ts +1 -0
  126. package/dist/utils/index.d.ts.map +1 -1
  127. package/dist/utils/index.js +1 -0
  128. package/dist/utils/index.js.map +1 -1
  129. package/dist/webpack/utils/testing/get-compiler.js.map +1 -1
  130. package/package.json +16 -14
  131. package/src/cli/commands/__tests__/init.test.ts +21 -87
  132. package/src/cli/commands/__tests__/install.test.ts +174 -12
  133. package/src/cli/commands/__tests__/mfe-package-clean.test.ts +3 -6
  134. package/src/cli/commands/__tests__/mfe-package-publish.test.ts +6 -8
  135. package/src/cli/commands/__tests__/upload-sourcemaps.test.ts +7 -3
  136. package/src/cli/commands/build.ts +1 -1
  137. package/src/cli/commands/bundle-package.ts +1 -1
  138. package/src/cli/commands/eslint.ts +1 -1
  139. package/src/cli/commands/get-command.ts +1 -1
  140. package/src/cli/commands/index.ts +1 -1
  141. package/src/cli/commands/init.ts +18 -38
  142. package/src/cli/commands/install.ts +95 -6
  143. package/src/cli/commands/lint.ts +1 -1
  144. package/src/cli/commands/mfe-package-clean.ts +2 -4
  145. package/src/cli/commands/mfe-package-publish.ts +18 -6
  146. package/src/cli/commands/mfe-publish.ts +1 -1
  147. package/src/cli/commands/prepare-package.ts +1 -1
  148. package/src/cli/commands/review/rules/__tests__/require-all-react-dependencies.test.ts +132 -0
  149. package/src/cli/commands/review/rules/__tests__/require-one-package-version.test.ts +0 -1
  150. package/src/cli/commands/review/rules/__tests__/require-one-react-version.test.ts +22 -0
  151. package/src/cli/commands/review/rules/__tests__/{require-one-uikit-version.ts → require-one-uikit-version.test.ts} +1 -0
  152. package/src/cli/commands/review/rules/index.ts +4 -0
  153. package/src/cli/commands/review/rules/require-all-react-dependencies.ts +53 -0
  154. package/src/cli/commands/review/rules/require-one-react-version.ts +9 -0
  155. package/src/cli/commands/review/rules/require-one-uikit-version.ts +1 -0
  156. package/src/cli/commands/review/utils/__tests__/set-version.test.ts +18 -8
  157. package/src/cli/commands/review/utils/format-depends-on.ts +8 -5
  158. package/src/cli/commands/review/utils/set-version.ts +10 -2
  159. package/src/cli/commands/start.ts +1 -1
  160. package/src/cli/commands/test/__tests__/tests.test.ts +78 -0
  161. package/src/cli/commands/test/index.ts +1 -0
  162. package/src/cli/commands/{__tests__/tests.test.ts → test/runners/__tests__/jest.test.ts} +10 -13
  163. package/src/cli/commands/test/runners/__tests__/vitest.test.ts +150 -0
  164. package/src/cli/commands/test/runners/index.ts +2 -0
  165. package/src/cli/commands/{tests.ts → test/runners/jest.ts} +5 -16
  166. package/src/cli/commands/test/runners/vitest.ts +70 -0
  167. package/src/cli/commands/test/tests.ts +34 -0
  168. package/src/cli/commands/upload-sourcemaps.ts +2 -2
  169. package/src/cli/index.ts +1 -2
  170. package/src/cli/utils/__tests__/bundle.test.ts +1 -1
  171. package/src/cli/utils/__tests__/cli-git.test.ts +142 -6
  172. package/src/cli/utils/__tests__/eslint.test.ts +3 -2
  173. package/src/cli/utils/__tests__/is-ci.test.ts +40 -0
  174. package/src/cli/utils/__tests__/lerna-exec.test.ts +6 -3
  175. package/src/cli/utils/bundle.ts +8 -3
  176. package/src/cli/utils/cli-git.ts +55 -5
  177. package/src/cli/utils/index.ts +6 -0
  178. package/src/cli/utils/is-ci.ts +3 -0
  179. package/src/cli/utils/lerna-exec.ts +2 -1
  180. package/src/cli/utils/set-node-options.ts +2 -2
  181. package/src/index.ts +0 -1
  182. package/src/utils/__tests__/get-configuration.test.ts +48 -11
  183. package/src/utils/__tests__/get-jest-config.test.ts +35 -61
  184. package/src/utils/get-branch-configs.ts +1 -1
  185. package/src/utils/get-configuration.ts +14 -3
  186. package/src/utils/get-jest-config.ts +39 -47
  187. package/src/utils/index.ts +1 -0
  188. package/src/webpack/__tests__/create-webpack-config-shared-dependencies.test.ts +1 -1
  189. package/src/webpack/__tests__/create-webpack-config-web-component.test.ts +1 -1
  190. package/src/webpack/__tests__/create-webpack-config.test.ts +1 -1
  191. package/src/webpack/utils/testing/get-compiler.ts +1 -1
  192. package/dist/cli/commands/tests.d.ts +0 -13
  193. package/dist/cli/commands/tests.d.ts.map +0 -1
  194. package/dist/cli/commands/tests.js.map +0 -1
  195. package/dist/jest/index.d.ts +0 -2
  196. package/dist/jest/index.d.ts.map +0 -1
  197. package/dist/jest/index.js +0 -16
  198. package/dist/jest/index.js.map +0 -1
  199. package/src/jest/index.ts +0 -5
@@ -7,7 +7,7 @@ interface Args {
7
7
  }
8
8
 
9
9
  export class PreparePackage implements Command {
10
- constructor(private args: Args) {}
10
+ constructor(private readonly args: Args) {}
11
11
 
12
12
  @logErrors
13
13
  execute() {
@@ -0,0 +1,132 @@
1
+ import { FixCategory, Project, ReviewConfiguration } from '../../types';
2
+ import { setVersion } from '../../utils';
3
+ import { mockConfig, mockPackages, mockProject } from '../__mocks__';
4
+
5
+ import { RequireAllReactDependencies } from '../require-all-react-dependencies';
6
+
7
+ jest.mock('../../utils', () => ({
8
+ ...jest.requireActual('../../utils'),
9
+ setVersion: jest.fn(),
10
+ }));
11
+
12
+ describe(`[startup] Review ${RequireAllReactDependencies.name}`, () => {
13
+ const id = 'require-all-react-dependencies';
14
+ const rule = new RequireAllReactDependencies();
15
+ let config: ReviewConfiguration;
16
+ let dependencies: Project['dependencies'];
17
+
18
+ beforeEach(() => {
19
+ config = {};
20
+ dependencies = {};
21
+ jest.clearAllMocks();
22
+ });
23
+
24
+ const project = () =>
25
+ mockProject({ config, dependencies, packages: mockPackages(dependencies) });
26
+
27
+ const subject = () => rule.run(project());
28
+
29
+ const fixSubject = () => {
30
+ const testProject = project();
31
+ rule.fix(rule.run(testProject)[0], testProject);
32
+ };
33
+
34
+ function itReturnsError({
35
+ packageName,
36
+ present,
37
+ absent,
38
+ }: {
39
+ packageName: string;
40
+ present: string;
41
+ absent: string;
42
+ }) {
43
+ test('returns error', () => {
44
+ expect(subject()).toEqual([
45
+ expect.objectContaining({
46
+ id,
47
+ message: `package "${packageName}" depends on ${present} but not on ${absent}`,
48
+ fixable: FixCategory.isolated,
49
+ }),
50
+ ]);
51
+ });
52
+ }
53
+
54
+ function itReturnsNothing() {
55
+ test(`returns nothing`, () => {
56
+ expect(subject()).toEqual([]);
57
+ });
58
+ }
59
+
60
+ itReturnsNothing();
61
+
62
+ describe('when package depends only on "react"', () => {
63
+ beforeEach(() => {
64
+ dependencies = {
65
+ react: { '^18': ['lib'] },
66
+ };
67
+ });
68
+
69
+ itReturnsError({ packageName: 'lib', present: 'react', absent: 'react-dom' });
70
+
71
+ test('fixes error', () => {
72
+ fixSubject();
73
+
74
+ expect(setVersion).toHaveBeenCalledWith({
75
+ project: project(),
76
+ packageName: 'lib',
77
+ dependency: 'react-dom',
78
+ version: '^18',
79
+ key: 'dependencies',
80
+ });
81
+ });
82
+
83
+ test('ignores invalid error', () => {
84
+ rule.fix({} as any, project());
85
+
86
+ expect(setVersion).not.toHaveBeenCalled();
87
+ });
88
+
89
+ describe('when config excludes package', () => {
90
+ beforeEach(() => (config = mockConfig({ id, exclude: 'lib' })));
91
+
92
+ itReturnsNothing();
93
+ });
94
+ });
95
+
96
+ describe('when package depends only on "react-dom"', () => {
97
+ beforeEach(() => {
98
+ dependencies = {
99
+ 'react-dom': { '^18': ['lib'] },
100
+ };
101
+ });
102
+
103
+ itReturnsError({ packageName: 'lib', present: 'react-dom', absent: 'react' });
104
+
105
+ test('fixes error', () => {
106
+ fixSubject();
107
+
108
+ expect(setVersion).toHaveBeenCalledWith({
109
+ project: project(),
110
+ packageName: 'lib',
111
+ dependency: 'react',
112
+ version: '^18',
113
+ key: 'dependencies',
114
+ });
115
+ });
116
+ });
117
+
118
+ describe('when package depends on both "react" and "react-dom"', () => {
119
+ beforeEach(() => {
120
+ dependencies = {
121
+ 'react': { '^18': ['lib'] },
122
+ 'react-dom': { '^18': ['lib'] },
123
+ };
124
+ });
125
+
126
+ itReturnsNothing();
127
+ });
128
+
129
+ test('returns nothing when package has no dependencies', () => {
130
+ expect(rule.run(mockProject({ packages: [{ name: 'foo' } as any] }))).toEqual([]);
131
+ });
132
+ });
@@ -6,7 +6,6 @@ import { mockConfig, mockPackages, mockProject } from '../__mocks__';
6
6
 
7
7
  import { RequireOnePackageVersion } from '../require-one-package-version';
8
8
 
9
- jest.mock('child_process', () => ({ execSync: jest.fn() }));
10
9
  jest.mock('../../utils', () => ({
11
10
  ...jest.requireActual('../../utils'),
12
11
  setVersion: jest.fn(),
@@ -0,0 +1,22 @@
1
+ import { RequireOneCollectionVersion } from '../require-one-collection-version';
2
+
3
+ import { RequireOneReactVersion } from '../require-one-react-version';
4
+
5
+ jest.mock('../require-one-collection-version');
6
+
7
+ describe(`[startup] Review ${RequireOneReactVersion.name}`, () => {
8
+ const reactPackages = ['react', 'react-dom'];
9
+
10
+ beforeEach(() => jest.clearAllMocks());
11
+
12
+ const subject = () => new RequireOneReactVersion();
13
+
14
+ test('calls RequireOneCollectionVersion with react packages', () => {
15
+ subject();
16
+
17
+ expect(RequireOneCollectionVersion).toHaveBeenCalledWith({
18
+ name: 'react',
19
+ packages: reactPackages,
20
+ });
21
+ });
22
+ });
@@ -7,6 +7,7 @@ jest.mock('../require-one-collection-version');
7
7
  describe(`[startup] Review ${RequireOneUikitVersion.name}`, () => {
8
8
  const uikitPackages = [
9
9
  '@servicetitan/ajax-handlers',
10
+ '@servicetitan/datadog-rum',
10
11
  '@servicetitan/error-boundary',
11
12
  '@servicetitan/eslint-config',
12
13
  '@servicetitan/eslint-plugin',
@@ -1,17 +1,21 @@
1
1
  import { PackageRule } from '../types';
2
2
  import { NoTypescriptEntryPoint } from './no-typescript-entry-point';
3
+ import { RequireAllReactDependencies } from './require-all-react-dependencies';
3
4
  import { RequireExplicitSideEffects } from './require-explicit-side-effects';
4
5
  import { RequireNpmrc } from './require-npmrc';
5
6
  import { RequireOneAnvilUikitContribVersion } from './require-one-anvil-uikit-contrib-version';
6
7
  import { RequireOnePackageVersion } from './require-one-package-version';
8
+ import { RequireOneReactVersion } from './require-one-react-version';
7
9
  import { RequireOneUikitVersion } from './require-one-uikit-version';
8
10
  import { RequireProjectVersionInRootNodeModules } from './require-project-version-in-root-node-modules';
9
11
  import { RequireServiceTitanScope } from './require-servicetitan-scope';
10
12
 
11
13
  export const rules: PackageRule[] = [
12
14
  new RequireOnePackageVersion(),
15
+ new RequireAllReactDependencies(),
13
16
  new RequireOneUikitVersion(),
14
17
  new RequireOneAnvilUikitContribVersion(),
18
+ new RequireOneReactVersion(),
15
19
  new RequireProjectVersionInRootNodeModules(),
16
20
  new NoTypescriptEntryPoint(),
17
21
  new RequireServiceTitanScope(),
@@ -0,0 +1,53 @@
1
+ import { FixCategory, Package, PackageError, PackageRule, Project } from '../types';
2
+ import { applyFilter, formatList, getMaxVersion, setVersion } from '../utils';
3
+
4
+ const REACT_DEPENDENCIES = ['react', 'react-dom'];
5
+
6
+ interface ErrorData {
7
+ missing: string[];
8
+ packageName: string;
9
+ version: string;
10
+ }
11
+
12
+ export class RequireAllReactDependencies implements PackageRule {
13
+ get id() {
14
+ return 'require-all-react-dependencies';
15
+ }
16
+
17
+ run({ config, packages }: Project) {
18
+ const ruleConfig = config.rules?.[this.id];
19
+ return applyFilter(ruleConfig, packages, ({ name }) => name)
20
+ .map(pkg => this.checkDependencies(pkg))
21
+ .filter(error => !!error);
22
+ }
23
+
24
+ fix({ data }: PackageError<ErrorData>, project: Project) {
25
+ const { missing, packageName, version } = data ?? {};
26
+ if (!(missing?.length && packageName && version)) {
27
+ return;
28
+ }
29
+
30
+ missing.forEach(dependency => {
31
+ setVersion({ project, packageName, dependency, version, key: 'dependencies' });
32
+ });
33
+ }
34
+
35
+ private checkDependencies(pkg: Package): PackageError<ErrorData> | undefined {
36
+ const { dependencies = {}, name } = pkg;
37
+ const present = REACT_DEPENDENCIES.filter(name => !!dependencies[name]);
38
+ if (present.length === 0 || present.length === REACT_DEPENDENCIES.length) {
39
+ return;
40
+ }
41
+
42
+ const missing = REACT_DEPENDENCIES.filter(name => !dependencies[name]);
43
+ const message = `package "${name}" depends on ${present[0]} but not on ${formatList(missing)}`;
44
+ const version = getMaxVersion(present.map(name => dependencies[name]));
45
+ return {
46
+ id: this.id,
47
+ message,
48
+ location: pkg.location,
49
+ fixable: FixCategory.isolated,
50
+ data: { missing, packageName: name, version },
51
+ };
52
+ }
53
+ }
@@ -0,0 +1,9 @@
1
+ import { RequireOneCollectionVersion } from './require-one-collection-version';
2
+
3
+ const REACT_PACKAGES = ['react', 'react-dom'];
4
+
5
+ export class RequireOneReactVersion extends RequireOneCollectionVersion {
6
+ constructor() {
7
+ super({ name: 'react', packages: REACT_PACKAGES });
8
+ }
9
+ }
@@ -2,6 +2,7 @@ import { RequireOneCollectionVersion } from './require-one-collection-version';
2
2
 
3
3
  const UIKIT_PACKAGES = [
4
4
  '@servicetitan/ajax-handlers',
5
+ '@servicetitan/datadog-rum',
5
6
  '@servicetitan/error-boundary',
6
7
  '@servicetitan/eslint-config',
7
8
  '@servicetitan/eslint-plugin',
@@ -7,25 +7,23 @@ import { setVersion } from '../set-version';
7
7
  jest.mock('child_process', () => ({ execSync: jest.fn() }));
8
8
 
9
9
  describe(`[startup] Review ${setVersion.name}`, () => {
10
+ const packageName = 'lib1';
11
+ const dependency = 'foo';
12
+ const version = '2.0.0';
10
13
  let pkg: Package;
11
- let packageName: string;
12
- let dependency: string;
13
- let version: string;
14
+ let params: Parameters<typeof setVersion>[0];
14
15
 
15
16
  beforeEach(() => {
16
- packageName = 'lib1';
17
- dependency = 'foo';
18
- version = '2.0.0';
19
17
  pkg = {
20
18
  name: packageName,
21
19
  location: path.normalize(`packages/${packageName}`),
22
20
  dependencies: { [dependency]: '1.0.0' },
23
21
  };
22
+ params = { project: { packages: [pkg] } as any, packageName, dependency, version };
24
23
  jest.clearAllMocks();
25
24
  });
26
25
 
27
- const subject = () =>
28
- setVersion({ project: { packages: [pkg] } as any, packageName, dependency, version });
26
+ const subject = () => setVersion(params);
29
27
 
30
28
  function itDoesNothing() {
31
29
  test('does nothing', () => {
@@ -74,6 +72,18 @@ describe(`[startup] Review ${setVersion.name}`, () => {
74
72
  beforeEach(() => delete pkg.dependencies);
75
73
 
76
74
  itDoesNothing();
75
+
76
+ describe('when key is specified', () => {
77
+ beforeEach(() => (params.key = 'dependencies'));
78
+
79
+ test('sets dependency version', () => {
80
+ subject();
81
+
82
+ expect(execSync).toHaveBeenCalledWith(
83
+ `npm pkg set ${params.key}["${dependency}"]="${version}" -w ${pkg.location}`
84
+ );
85
+ });
86
+ });
77
87
  });
78
88
 
79
89
  describe('when package is missing', () => {
@@ -2,10 +2,13 @@ const collator = new Intl.Collator();
2
2
 
3
3
  export function formatDependsOn(dependents: string[], dependency: string) {
4
4
  const { length } = dependents;
5
- if (length === 1) {
6
- return `${dependents[0]} depends on ${dependency}`;
7
- }
5
+ return length === 1
6
+ ? `${dependents[0]} depends on ${dependency}`
7
+ : `${formatList(dependents.toSorted((a, b) => collator.compare(a, b)))} depend on ${dependency}`;
8
+ }
8
9
 
9
- const sorted = dependents.toSorted((a, b) => collator.compare(a, b));
10
- return `${sorted.slice(0, -1).join(', ')} and ${sorted[length - 1]} depend on ${dependency}`;
10
+ export function formatList(items: string[]) {
11
+ return items.length > 1
12
+ ? `${items.slice(0, -1).join(', ')} and ${items[items.length - 1]}`
13
+ : items[0];
11
14
  }
@@ -8,14 +8,22 @@ interface Params {
8
8
  packageName: string;
9
9
  dependency: string;
10
10
  version: string;
11
+ key?: string;
11
12
  }
12
- export function setVersion({ project: { packages }, packageName, dependency, version }: Params) {
13
+
14
+ export function setVersion(params: Params) {
15
+ const {
16
+ project: { packages },
17
+ packageName,
18
+ dependency,
19
+ version,
20
+ } = params;
13
21
  const pkg = packages.find(({ name }) => name === packageName);
14
22
  if (!pkg) {
15
23
  return;
16
24
  }
17
25
 
18
- const key = getKey(pkg, dependency);
26
+ const key = params.key ?? getKey(pkg, dependency);
19
27
  if (!key) {
20
28
  return;
21
29
  }
@@ -31,7 +31,7 @@ enum StartProcesses {
31
31
  export class Start implements Command {
32
32
  readonly greedy = true;
33
33
 
34
- constructor(private args: Args) {}
34
+ constructor(private readonly args: Args) {}
35
35
 
36
36
  description() {
37
37
  return 'run project in development mode';
@@ -0,0 +1,78 @@
1
+ import { getConfiguration } from '../../../../utils';
2
+ import { Jest, Vitest } from '../runners';
3
+ import { Tests } from '../tests';
4
+
5
+ jest.mock('../runners');
6
+ jest.mock('../../../../utils', () => ({
7
+ ...jest.requireActual('../../../../utils'),
8
+ getConfiguration: jest.fn(),
9
+ }));
10
+
11
+ type TestArgs = ConstructorParameters<typeof Tests>[0];
12
+
13
+ describe(`[startup] ${Tests.name}`, () => {
14
+ let args: TestArgs;
15
+
16
+ beforeEach(() => {
17
+ // eslint-disable-next-line @typescript-eslint/naming-convention
18
+ args = { _: ['foo'], $0: 'bar' };
19
+ jest.mocked(getConfiguration).mockReturnValue({});
20
+ jest.clearAllMocks();
21
+ });
22
+
23
+ const subject = async () => new Tests(args).execute();
24
+
25
+ test('command is greedy', () => {
26
+ expect(new Tests(args).greedy).toBe(true);
27
+ });
28
+
29
+ function itRunsJest() {
30
+ test('runs Jest', async () => {
31
+ await subject();
32
+
33
+ const { runner, ...rest } = args;
34
+ expect(Jest).toHaveBeenCalledWith(rest);
35
+ expect(jest.mocked(Jest).mock.instances[0].run).toHaveBeenCalled();
36
+ });
37
+ }
38
+
39
+ function itRunsVitest() {
40
+ test('runs Vitest', async () => {
41
+ await subject();
42
+
43
+ expect(Vitest).toHaveBeenCalled();
44
+ expect(jest.mocked(Vitest).mock.instances[0].run).toHaveBeenCalled();
45
+ });
46
+ }
47
+
48
+ itRunsJest();
49
+
50
+ describe('with --runner=vitest', () => {
51
+ beforeEach(() => (args.runner = 'vitest'));
52
+
53
+ itRunsVitest();
54
+ });
55
+
56
+ describe('with unrecognized --runner', () => {
57
+ beforeEach(() => (args.runner = 'foo' as any));
58
+
59
+ test('throws error', () => {
60
+ jest.spyOn(process.stdout, 'write').mockImplementation(jest.fn()); // suppress error output
61
+ expect(subject).rejects.toThrow('unrecognized runner "foo"');
62
+ });
63
+ });
64
+
65
+ describe('with {testRunner: "vitest"}', () => {
66
+ beforeEach(() => {
67
+ jest.mocked(getConfiguration).mockReturnValue({ testRunner: 'vitest' });
68
+ });
69
+
70
+ itRunsVitest();
71
+
72
+ describe('with --runner=jest', () => {
73
+ beforeEach(() => (args.runner = 'jest'));
74
+
75
+ itRunsJest();
76
+ });
77
+ });
78
+ });
@@ -0,0 +1 @@
1
+ export * from './tests';
@@ -1,30 +1,27 @@
1
1
  import { runCLI } from '@jest/core';
2
- import { Config } from '@jest/types';
3
- import { getJestConfigCLI } from '../../../utils';
4
2
 
5
- import { Tests } from '../tests';
3
+ import { Jest } from '../jest';
4
+ import { getJestConfigCLI } from '../../../../../utils';
6
5
 
7
6
  jest.mock('@jest/core', () => ({ runCLI: jest.fn() }));
8
- jest.mock('../../../utils', () => ({
9
- ...jest.requireActual('../../../utils'),
7
+ jest.mock('../../../../../utils', () => ({
8
+ ...jest.requireActual('../../../../../utils'),
10
9
  getJestConfigCLI: jest.fn(),
11
10
  }));
12
11
 
13
- describe(`[startup] ${Tests.name}`, () => {
12
+ type JestArgs = ConstructorParameters<typeof Jest>[0];
13
+
14
+ describe(`[startup] Test ${Jest.name}`, () => {
14
15
  // eslint-disable-next-line @typescript-eslint/naming-convention
15
- const args: Config.Argv = { _: ['foo'], $0: 'bar' };
16
- const jestConfig: Config.Argv = { ...args, setupFilesAfterEnv: ['./setupTests.js'] };
16
+ const args: JestArgs = { _: ['foo'], $0: 'bar' };
17
+ const jestConfig: JestArgs = { ...args, setupFilesAfterEnv: ['./setupTests.js'] };
17
18
 
18
19
  beforeAll(() => {
19
20
  jest.mocked(getJestConfigCLI).mockReturnValue(jestConfig);
20
21
  jest.mocked(runCLI).mockResolvedValue({ results: { success: true } } as any);
21
22
  });
22
23
 
23
- const subject = async () => new Tests(args).execute();
24
-
25
- test('command is greedy', () => {
26
- expect(new Tests(args).greedy).toBe(true);
27
- });
24
+ const subject = async () => new Jest(args).run();
28
25
 
29
26
  test('runs Jest CLI', async () => {
30
27
  await subject();
@@ -0,0 +1,150 @@
1
+ import deepmerge from 'deepmerge';
2
+ import { fs, vol } from 'memfs';
3
+ import { configDefaults, coverageConfigDefaults, mergeConfig, ViteUserConfig } from 'vitest/config';
4
+ import { parseCLI, resolveConfig, startVitest } from 'vitest/node';
5
+ import { Vitest } from '../vitest';
6
+
7
+ /**
8
+ * Mocking "vitest/" includes all vite and vitest entry points because there is
9
+ * one identity-obj-proxy for "^(vite|vitest)/" @see cli.test.moduleNameMapper
10
+ * in {@link file://./../../../../../../../../package.json}
11
+ */
12
+ jest.mock('vitest/', () => ({
13
+ configDefaults: { exclude: ['node_modules'] },
14
+ coverageConfigDefaults: { exclude: ['node_modules'] },
15
+ mergeConfig: jest.fn(),
16
+ parseCLI: jest.fn(),
17
+ resolveConfig: jest.fn(),
18
+ startVitest: jest.fn(),
19
+ }));
20
+
21
+ jest.mock('fs', () => fs);
22
+
23
+ describe(`[startup] Test ${Vitest.name}`, () => {
24
+ const defaultConfig: ViteUserConfig['test'] = {
25
+ coverage: {
26
+ include: ['**/*.{ts,tsx}'],
27
+ exclude: [
28
+ ...coverageConfigDefaults.exclude,
29
+ '**/__mocks__/**',
30
+ '**/*.stories.*',
31
+ '\\.yalc',
32
+ ],
33
+ reporter: ['html-spa', 'text', 'json', 'cobertura', 'lcov'],
34
+ },
35
+ environment: 'jsdom',
36
+ exclude: [...configDefaults.exclude, '\\.yalc'],
37
+ restoreMocks: true,
38
+ server: { deps: { inline: ['@servicetitan/anvil2'] } },
39
+ };
40
+
41
+ beforeEach(() => {
42
+ jest.clearAllMocks();
43
+
44
+ jest.mocked(mergeConfig).mockImplementation((a, b) => deepmerge(a, b));
45
+ jest.mocked(parseCLI).mockReturnValue({ filter: [], options: {} });
46
+ jest.mocked(resolveConfig).mockResolvedValue({ viteConfig: {} } as any);
47
+ jest.mocked(startVitest).mockResolvedValue({ close: jest.fn() } as any);
48
+
49
+ vol.fromJSON({ 'package.json': JSON.stringify({}) });
50
+ });
51
+
52
+ afterEach(() => vol.reset());
53
+
54
+ const subject = async () => new Vitest().run();
55
+
56
+ test('runs startVitest with default config', async () => {
57
+ await subject();
58
+
59
+ expect(startVitest).toHaveBeenCalledWith('test', [], {}, { test: defaultConfig });
60
+ });
61
+
62
+ test('closes vitest', async () => {
63
+ await subject();
64
+
65
+ const vitest = await jest.mocked(startVitest).mock.results[0].value;
66
+ expect(vitest.close).toHaveBeenCalled();
67
+ });
68
+
69
+ describe('with package.json config', () => {
70
+ const packageJSONConfig: ViteUserConfig['test'] = { bail: 1, globals: false };
71
+
72
+ beforeEach(() => {
73
+ vol.fromJSON({
74
+ 'package.json': JSON.stringify({ cli: { vitest: packageJSONConfig } }),
75
+ });
76
+ });
77
+
78
+ test('merges package.json config with default config', async () => {
79
+ await subject();
80
+
81
+ expect(startVitest).toHaveBeenCalledWith(
82
+ 'test',
83
+ [],
84
+ {},
85
+ { test: { ...defaultConfig, ...packageJSONConfig } }
86
+ );
87
+ });
88
+ });
89
+
90
+ describe('with vite test config', () => {
91
+ const viteTestConfig: ViteUserConfig['test'] = { name: 'foo', watch: false };
92
+
93
+ beforeEach(() => {
94
+ jest.mocked(resolveConfig).mockResolvedValue({
95
+ viteConfig: { test: viteTestConfig },
96
+ } as any);
97
+ });
98
+
99
+ test('merges vite test config with default config', async () => {
100
+ await subject();
101
+
102
+ expect(startVitest).toHaveBeenCalledWith(
103
+ 'test',
104
+ [],
105
+ {},
106
+ { test: { ...defaultConfig, ...viteTestConfig } }
107
+ );
108
+ });
109
+ });
110
+
111
+ describe('with command line options', () => {
112
+ const originalArgv = process.argv;
113
+ const cliOptions = ['--mode=foo', 'foo'];
114
+
115
+ beforeEach(() => {
116
+ process.argv = ['node', 'startup', 'test', ...cliOptions];
117
+ });
118
+
119
+ afterEach(() => (process.argv = originalArgv));
120
+
121
+ test('runs startVitest with parsed options', async () => {
122
+ const parseCliResult: ReturnType<typeof parseCLI> = {
123
+ filter: ['foo'],
124
+ options: { mode: 'foo' },
125
+ };
126
+
127
+ jest.mocked(parseCLI).mockReturnValue(parseCliResult);
128
+
129
+ await subject();
130
+
131
+ expect(parseCLI).toHaveBeenCalledWith(['vitest', ...cliOptions]);
132
+ expect(startVitest).toHaveBeenCalledWith(
133
+ 'test',
134
+ parseCliResult.filter,
135
+ parseCliResult.options,
136
+ expect.anything()
137
+ );
138
+ });
139
+
140
+ describe.each(['--runner <name>', '--runner=<name>'])('with %s', args => {
141
+ beforeEach(() => (process.argv = [...process.argv, ...args.split(' ')]));
142
+
143
+ test('omits "runner" from parseCLI arguments', async () => {
144
+ await subject();
145
+
146
+ expect(parseCLI).toHaveBeenCalledWith(['vitest', ...cliOptions]);
147
+ });
148
+ });
149
+ });
150
+ });
@@ -0,0 +1,2 @@
1
+ export * from './jest';
2
+ export * from './vitest';