@servicetitan/startup 32.0.1 → 32.2.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 (103) hide show
  1. package/dist/cli/commands/review/rules/index.d.ts.map +1 -1
  2. package/dist/cli/commands/review/rules/index.js +10 -2
  3. package/dist/cli/commands/review/rules/index.js.map +1 -1
  4. package/dist/cli/commands/review/rules/no-deprecated-content-base.d.ts +13 -0
  5. package/dist/cli/commands/review/rules/no-deprecated-content-base.d.ts.map +1 -0
  6. package/dist/cli/commands/review/rules/no-deprecated-content-base.js +71 -0
  7. package/dist/cli/commands/review/rules/no-deprecated-content-base.js.map +1 -0
  8. package/dist/cli/commands/review/rules/no-direct-peer-dependencies.d.ts +15 -0
  9. package/dist/cli/commands/review/rules/no-direct-peer-dependencies.d.ts.map +1 -0
  10. package/dist/cli/commands/review/rules/no-direct-peer-dependencies.js +75 -0
  11. package/dist/cli/commands/review/rules/no-direct-peer-dependencies.js.map +1 -0
  12. package/dist/cli/commands/review/rules/no-typescript-entry-point.d.ts +2 -2
  13. package/dist/cli/commands/review/rules/no-typescript-entry-point.d.ts.map +1 -1
  14. package/dist/cli/commands/review/rules/no-typescript-entry-point.js +58 -45
  15. package/dist/cli/commands/review/rules/no-typescript-entry-point.js.map +1 -1
  16. package/dist/cli/commands/review/rules/prefer-open-ended-peer-dependencies.d.ts +20 -0
  17. package/dist/cli/commands/review/rules/prefer-open-ended-peer-dependencies.d.ts.map +1 -0
  18. package/dist/cli/commands/review/rules/prefer-open-ended-peer-dependencies.js +145 -0
  19. package/dist/cli/commands/review/rules/prefer-open-ended-peer-dependencies.js.map +1 -0
  20. package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.d.ts +22 -0
  21. package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.d.ts.map +1 -0
  22. package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.js +189 -0
  23. package/dist/cli/commands/review/rules/require-compatible-launch-darkly-sdk.js.map +1 -0
  24. package/dist/cli/commands/review/rules/require-explicit-side-effects.d.ts +2 -1
  25. package/dist/cli/commands/review/rules/require-explicit-side-effects.d.ts.map +1 -1
  26. package/dist/cli/commands/review/rules/require-explicit-side-effects.js +24 -11
  27. package/dist/cli/commands/review/rules/require-explicit-side-effects.js.map +1 -1
  28. package/dist/cli/commands/review/rules/require-one-collection-version.d.ts.map +1 -1
  29. package/dist/cli/commands/review/rules/require-one-collection-version.js +1 -1
  30. package/dist/cli/commands/review/rules/require-one-collection-version.js.map +1 -1
  31. package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.d.ts.map +1 -1
  32. package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.js +5 -7
  33. package/dist/cli/commands/review/rules/require-project-version-in-root-node-modules.js.map +1 -1
  34. package/dist/cli/commands/review/rules/require-servicetitan-scope.d.ts +2 -1
  35. package/dist/cli/commands/review/rules/require-servicetitan-scope.d.ts.map +1 -1
  36. package/dist/cli/commands/review/rules/require-servicetitan-scope.js +24 -11
  37. package/dist/cli/commands/review/rules/require-servicetitan-scope.js.map +1 -1
  38. package/dist/cli/commands/review/types.d.ts +4 -1
  39. package/dist/cli/commands/review/types.d.ts.map +1 -1
  40. package/dist/cli/commands/review/types.js.map +1 -1
  41. package/dist/cli/commands/review/utils/check-packages.d.ts +7 -0
  42. package/dist/cli/commands/review/utils/check-packages.d.ts.map +1 -0
  43. package/dist/cli/commands/review/utils/check-packages.js +30 -0
  44. package/dist/cli/commands/review/utils/check-packages.js.map +1 -0
  45. package/dist/cli/commands/review/utils/index.d.ts +1 -0
  46. package/dist/cli/commands/review/utils/index.d.ts.map +1 -1
  47. package/dist/cli/commands/review/utils/index.js +1 -0
  48. package/dist/cli/commands/review/utils/index.js.map +1 -1
  49. package/dist/cli/commands/test/runners/vitest.js +2 -1
  50. package/dist/cli/commands/test/runners/vitest.js.map +1 -1
  51. package/dist/cli/utils/bundle.js +1 -1
  52. package/dist/cli/utils/bundle.js.map +1 -1
  53. package/dist/cli/utils/cli-git.d.ts.map +1 -1
  54. package/dist/cli/utils/cli-git.js +11 -8
  55. package/dist/cli/utils/cli-git.js.map +1 -1
  56. package/dist/cli/utils/maybe-create-git-folder.d.ts.map +1 -1
  57. package/dist/cli/utils/maybe-create-git-folder.js +7 -1
  58. package/dist/cli/utils/maybe-create-git-folder.js.map +1 -1
  59. package/dist/utils/get-configuration.d.ts +9 -3
  60. package/dist/utils/get-configuration.d.ts.map +1 -1
  61. package/dist/utils/get-configuration.js.map +1 -1
  62. package/dist/utils/get-jest-config.d.ts.map +1 -1
  63. package/dist/utils/get-jest-config.js +20 -9
  64. package/dist/utils/get-jest-config.js.map +1 -1
  65. package/dist/utils/index.d.ts +1 -0
  66. package/dist/utils/index.d.ts.map +1 -1
  67. package/dist/utils/index.js +1 -0
  68. package/dist/utils/index.js.map +1 -1
  69. package/dist/utils/omit.d.ts +2 -0
  70. package/dist/utils/omit.d.ts.map +1 -0
  71. package/dist/utils/omit.js +28 -0
  72. package/dist/utils/omit.js.map +1 -0
  73. package/package.json +22 -17
  74. package/src/cli/commands/review/rules/__mocks__/mock-config.ts +8 -2
  75. package/src/cli/commands/review/rules/__tests__/no-deprecated-content-base.test.ts +123 -0
  76. package/src/cli/commands/review/rules/__tests__/no-direct-peer-dependencies.test.ts +130 -0
  77. package/src/cli/commands/review/rules/__tests__/prefer-open-ended-peer-dependencies.test.ts +121 -0
  78. package/src/cli/commands/review/rules/__tests__/require-compatible-launch-darkly-sdk.test.ts +256 -0
  79. package/src/cli/commands/review/rules/index.ts +10 -2
  80. package/src/cli/commands/review/rules/no-deprecated-content-base.ts +50 -0
  81. package/src/cli/commands/review/rules/no-direct-peer-dependencies.ts +58 -0
  82. package/src/cli/commands/review/rules/no-typescript-entry-point.ts +5 -8
  83. package/src/cli/commands/review/rules/prefer-open-ended-peer-dependencies.ts +89 -0
  84. package/src/cli/commands/review/rules/require-compatible-launch-darkly-sdk.ts +141 -0
  85. package/src/cli/commands/review/rules/require-explicit-side-effects.ts +14 -14
  86. package/src/cli/commands/review/rules/require-one-collection-version.ts +1 -3
  87. package/src/cli/commands/review/rules/require-project-version-in-root-node-modules.ts +5 -9
  88. package/src/cli/commands/review/rules/require-servicetitan-scope.ts +14 -14
  89. package/src/cli/commands/review/types.ts +16 -2
  90. package/src/cli/commands/review/utils/check-packages.ts +19 -0
  91. package/src/cli/commands/review/utils/index.ts +1 -0
  92. package/src/cli/commands/test/runners/__tests__/vitest.test.ts +82 -13
  93. package/src/cli/commands/test/runners/vitest.ts +4 -2
  94. package/src/cli/utils/__tests__/cli-git.test.ts +20 -18
  95. package/src/cli/utils/__tests__/maybe-create-git-folder.test.ts +1 -1
  96. package/src/cli/utils/bundle.ts +1 -1
  97. package/src/cli/utils/cli-git.ts +8 -7
  98. package/src/cli/utils/maybe-create-git-folder.ts +6 -1
  99. package/src/utils/__tests__/get-jest-config.test.ts +44 -0
  100. package/src/utils/get-configuration.ts +6 -2
  101. package/src/utils/get-jest-config.ts +36 -18
  102. package/src/utils/index.ts +1 -0
  103. package/src/utils/omit.ts +12 -0
@@ -1,26 +1,26 @@
1
1
  import { ErrorSeverity, Package, PackageError, PackageRule, Project } from '../types';
2
- import { applyFilter, isLibrary } from '../utils';
2
+ import { checkPackages, isLibrary } from '../utils';
3
3
 
4
4
  export class RequireExplicitSideEffects implements PackageRule {
5
5
  get id() {
6
6
  return 'require-explicit-side-effects';
7
7
  }
8
8
 
9
- run({ config, packages }: Project): PackageError[] {
10
- const ruleConfig = config.rules?.[this.id];
11
- return applyFilter(ruleConfig, packages, ({ name }) => name).reduce((result, pkg) => {
12
- if (this.needsSideEffects(pkg)) {
13
- result.push({
14
- id: this.id,
15
- message: `package "${pkg.name}" omits sideEffects property`,
16
- severity: ErrorSeverity.warning,
17
- location: pkg.location,
18
- });
19
- }
20
- return result;
21
- }, [] as PackageError[]);
9
+ run(project: Project): PackageError[] {
10
+ return checkPackages(this, project, this.checkSideEffects);
22
11
  }
23
12
 
13
+ private readonly checkSideEffects = (pkg: Package) => {
14
+ if (this.needsSideEffects(pkg)) {
15
+ return {
16
+ id: this.id,
17
+ message: `package "${pkg.name}" omits sideEffects property`,
18
+ severity: ErrorSeverity.warning,
19
+ location: pkg.location,
20
+ };
21
+ }
22
+ };
23
+
24
24
  private needsSideEffects(pkg: Package) {
25
25
  return (
26
26
  isLibrary(pkg) &&
@@ -132,9 +132,7 @@ export class RequireOneCollectionVersion implements PackageRule {
132
132
  return new Set(
133
133
  Object.entries(versions)
134
134
  .slice(1)
135
- .flatMap(([_version, dependencies]) =>
136
- Object.entries(dependencies).map(([pkg]) => pkg)
137
- )
135
+ .flatMap(([_version, dependencies]) => Object.keys(dependencies))
138
136
  );
139
137
  }
140
138
 
@@ -65,15 +65,11 @@ export class RequireProjectVersionInRootNodeModules implements PackageRule {
65
65
  /* istanbul ignore next: debug only */
66
66
  log.debug(`review:${this.id}`, () => JSON.stringify(dependencies, null, 2));
67
67
 
68
- entries.forEach(([name, version]) =>
69
- execSync(`npm pkg set dependencies["${name}"]="${version}"`, { stdio: 'inherit' })
70
- );
71
-
72
- execSync('npx startup install --fix --quiet', { stdio: 'inherit' });
73
-
74
- entries.forEach(([name]) =>
75
- execSync(`npm pkg delete dependencies["${name}"]`, { stdio: 'inherit' })
76
- );
68
+ [
69
+ ...entries.map(([name, version]) => `npm pkg set dependencies["${name}"]="${version}"`),
70
+ 'npx startup install --fix --quiet',
71
+ ...entries.map(([name]) => `npm pkg delete dependencies["${name}"]`),
72
+ ].forEach(command => execSync(command, { stdio: 'inherit' }));
77
73
  }
78
74
 
79
75
  private formatDetails(mismatches: Mismatch[]) {
@@ -1,26 +1,26 @@
1
1
  import { ErrorSeverity, Package, PackageError, PackageRule, Project } from '../types';
2
- import { applyFilter, isLibrary } from '../utils';
2
+ import { checkPackages, isLibrary } from '../utils';
3
3
 
4
4
  export class RequireServiceTitanScope implements PackageRule {
5
5
  get id() {
6
6
  return 'require-servicetitan-scope';
7
7
  }
8
8
 
9
- run({ config, packages }: Project): PackageError[] {
10
- const ruleConfig = config.rules?.[this.id];
11
- return applyFilter(ruleConfig, packages, ({ name }) => name).reduce((result, pkg) => {
12
- if (this.needsScope(pkg)) {
13
- result.push({
14
- id: this.id,
15
- message: `package "${pkg.name}" should have @servicetitan scope`,
16
- severity: ErrorSeverity.warning,
17
- location: pkg.location,
18
- });
19
- }
20
- return result;
21
- }, [] as PackageError[]);
9
+ run(project: Project): PackageError[] {
10
+ return checkPackages(this, project, this.checkScope);
22
11
  }
23
12
 
13
+ private readonly checkScope = (pkg: Package) => {
14
+ if (this.needsScope(pkg)) {
15
+ return {
16
+ id: this.id,
17
+ message: `package "${pkg.name}" should have @servicetitan scope`,
18
+ severity: ErrorSeverity.warning,
19
+ location: pkg.location,
20
+ };
21
+ }
22
+ };
23
+
24
24
  private needsScope(pkg: Package) {
25
25
  return isLibrary(pkg) && pkg.private !== true && !pkg.name.startsWith('@servicetitan/');
26
26
  }
@@ -8,7 +8,17 @@ export interface Project {
8
8
  config: ReviewConfiguration;
9
9
  dependencies: Dependencies;
10
10
  // Only using this subset of package-lock.json
11
- packageLock: { packages: Record<string, { version: string }>; location: string };
11
+ packageLock: {
12
+ packages: Record<
13
+ string,
14
+ {
15
+ version: string;
16
+ dependencies?: Record<string, string>;
17
+ peerDependencies?: Record<string, string>;
18
+ }
19
+ >;
20
+ location: string;
21
+ };
12
22
  packages: Package[];
13
23
  }
14
24
 
@@ -33,6 +43,7 @@ export interface Package {
33
43
  main?: string;
34
44
  module?: string;
35
45
  name: string;
46
+ peerDependencies?: Record<string, string>;
36
47
  private?: boolean;
37
48
  sideEffects?: any;
38
49
  workspaces?: string[];
@@ -67,6 +78,9 @@ export interface ReviewConfiguration {
67
78
  rules?: { [id: string]: RuleConfiguration };
68
79
  }
69
80
 
70
- export type RuleConfiguration = Level | [Level] | [Level, { exclude?: string[] }];
81
+ export type RuleConfiguration =
82
+ | Level
83
+ | [Level]
84
+ | [Level, { exclude?: string[] | Record<string, string[]> }];
71
85
 
72
86
  export type Level = 'warn' | 'error' | 'off';
@@ -0,0 +1,19 @@
1
+ import { Package, PackageError, Project } from '../types';
2
+ import { applyFilter } from './apply-filter';
3
+
4
+ type CheckFn<T> = (pkg: Package) => PackageError<T>[] | PackageError<T> | undefined;
5
+
6
+ export function checkPackages<T>(
7
+ { id }: { id?: string },
8
+ { config, packages }: Project,
9
+ fn: CheckFn<T>
10
+ ) {
11
+ const ruleConfig = id ? config.rules?.[id] : undefined;
12
+ return applyFilter(ruleConfig, packages, ({ name }) => name).reduce((result, pkg) => {
13
+ const error = fn(pkg);
14
+ if (Array.isArray(error)) {
15
+ return [...result, ...error];
16
+ }
17
+ return error ? [...result, error] : result;
18
+ }, []);
19
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './apply-filter';
2
+ export * from './check-packages';
2
3
  export * from './collate-dependencies';
3
4
  export * from './compare-version';
4
5
  export * from './format-depends-on';
@@ -66,6 +66,10 @@ describe(`[startup] Test ${Vitest.name}`, () => {
66
66
  expect(vitest.close).toHaveBeenCalled();
67
67
  });
68
68
 
69
+ function expectCallsVitestWithConfig(config: Record<string, any>) {
70
+ expect(startVitest).toHaveBeenCalledWith('test', [], {}, { test: config });
71
+ }
72
+
69
73
  describe('with package.json config', () => {
70
74
  const packageJSONConfig: ViteUserConfig['test'] = { bail: 1, globals: false };
71
75
 
@@ -78,19 +82,40 @@ describe(`[startup] Test ${Vitest.name}`, () => {
78
82
  test('merges package.json config with default config', async () => {
79
83
  await subject();
80
84
 
81
- expect(startVitest).toHaveBeenCalledWith(
82
- 'test',
83
- [],
84
- {},
85
- { test: { ...defaultConfig, ...packageJSONConfig } }
86
- );
85
+ expectCallsVitestWithConfig({ ...defaultConfig, ...packageJSONConfig });
86
+ });
87
+
88
+ describe('with omitDefault', () => {
89
+ beforeEach(() => {
90
+ vol.fromJSON({
91
+ 'package.json': JSON.stringify({
92
+ cli: {
93
+ vitest: {
94
+ ...packageJSONConfig,
95
+ omitDefault: ['coverage.include', 'environment'],
96
+ },
97
+ },
98
+ }),
99
+ });
100
+ });
101
+
102
+ test('omits specified defaults', async () => {
103
+ await subject();
104
+
105
+ const { environment, coverage, ...restDefault } = defaultConfig;
106
+ const { include, ...restCoverage } = coverage as any;
107
+ const defaultSansOmitted = { coverage: restCoverage, ...restDefault };
108
+
109
+ expectCallsVitestWithConfig({ ...defaultSansOmitted, ...packageJSONConfig });
110
+ });
87
111
  });
88
112
  });
89
113
 
90
114
  describe('with vite test config', () => {
91
- const viteTestConfig: ViteUserConfig['test'] = { name: 'foo', watch: false };
115
+ let viteTestConfig: NonNullable<ViteUserConfig['test']>;
92
116
 
93
117
  beforeEach(() => {
118
+ viteTestConfig = { name: 'foo', watch: false };
94
119
  jest.mocked(resolveConfig).mockResolvedValue({
95
120
  viteConfig: { test: viteTestConfig },
96
121
  } as any);
@@ -99,13 +124,57 @@ describe(`[startup] Test ${Vitest.name}`, () => {
99
124
  test('merges vite test config with default config', async () => {
100
125
  await subject();
101
126
 
102
- expect(startVitest).toHaveBeenCalledWith(
103
- 'test',
104
- [],
105
- {},
106
- { test: { ...defaultConfig, ...viteTestConfig } }
107
- );
127
+ expectCallsVitestWithConfig({ ...defaultConfig, ...viteTestConfig });
108
128
  });
129
+
130
+ describe('with "coverage.include"', () => {
131
+ const defaultCoverageInclude = (defaultConfig.coverage as any).include;
132
+ const include = ['packages/foo'];
133
+
134
+ beforeEach(() => {
135
+ Object.assign(viteTestConfig, {
136
+ coverage: { include },
137
+ });
138
+ });
139
+
140
+ test('merges default and custom "coverage.include"', async () => {
141
+ await subject();
142
+
143
+ expectCallsVitestWithConfig(
144
+ expect.objectContaining({
145
+ coverage: {
146
+ ...defaultConfig.coverage,
147
+ include: [...defaultCoverageInclude, ...include],
148
+ },
149
+ })
150
+ );
151
+ });
152
+
153
+ describe('when omitDefault contains "coverage.include"', () => {
154
+ beforeEach(() => {
155
+ vol.fromJSON({
156
+ 'package.json': JSON.stringify({
157
+ cli: { vitest: { omitDefault: ['coverage.include'] } },
158
+ }),
159
+ });
160
+ });
161
+
162
+ test('omits default "coverage.include"', async () => {
163
+ await subject();
164
+
165
+ expectCallsVitestWithConfig(
166
+ expect.objectContaining({
167
+ coverage: {
168
+ ...defaultConfig.coverage,
169
+ include,
170
+ },
171
+ })
172
+ );
173
+ });
174
+ });
175
+ });
176
+
177
+ test('can use omitDefault to replace coverage.include', () => {});
109
178
  });
110
179
 
111
180
  describe('with command line options', () => {
@@ -1,6 +1,6 @@
1
1
  import { configDefaults, coverageConfigDefaults, mergeConfig, ViteUserConfig } from 'vitest/config';
2
2
  import { startVitest, parseCLI, resolveConfig } from 'vitest/node';
3
- import { getVitestConfiguration, log } from '../../../../utils';
3
+ import { getVitestConfiguration, log, omit } from '../../../../utils';
4
4
 
5
5
  type VitestConfig = NonNullable<ViteUserConfig['test']>;
6
6
 
@@ -58,8 +58,10 @@ function getDefaultConfig(): VitestConfig {
58
58
  async function getUserConfig(): Promise<ViteUserConfig> {
59
59
  const { viteConfig } = await resolveConfig();
60
60
 
61
+ const { omitDefault = [], ...config } = getVitestConfiguration();
62
+
61
63
  const result = mergeConfig(
62
- mergeConfig(getDefaultConfig(), getVitestConfiguration()),
64
+ mergeConfig(omit(getDefaultConfig(), omitDefault), config),
63
65
  viteConfig.test ?? {}
64
66
  );
65
67
 
@@ -50,31 +50,33 @@ describe('[startup] Cli Utils (Git)', () => {
50
50
  });
51
51
  }
52
52
 
53
- test(`clones ${webUrl} to destination directory`, async () => {
53
+ test(`clones ${sshUrl} to destination directory`, async () => {
54
54
  await subject();
55
55
 
56
- expect(runCommand).toHaveBeenCalledWith(`git clone -q ${webUrl} ${destination}`, {
57
- quiet: true,
58
- });
56
+ expect(runCommand).toHaveBeenCalledWith(
57
+ `git clone -q ${sshUrl} ${destination}`,
58
+ expect.anything()
59
+ );
59
60
  });
60
61
 
61
62
  itReturns(true);
62
63
 
63
- describe(`when cloning ${webUrl} fails`, () => {
64
+ describe(`when cloning ${sshUrl} fails`, () => {
64
65
  beforeEach(() => jest.mocked(runCommand).mockRejectedValueOnce('Nope!'));
65
66
 
66
- test(`clones ${sshUrl} to destination directory`, async () => {
67
+ test(`clones ${webUrl} to destination directory`, async () => {
67
68
  await subject();
68
69
 
69
- expect(runCommand).toHaveBeenCalledWith(
70
- `git clone -q ${sshUrl} ${destination}`,
71
- expect.anything()
72
- );
70
+ expect(runCommand).toHaveBeenCalledWith(`git clone -q ${webUrl} ${destination}`, {
71
+ quiet: true,
72
+ // eslint-disable-next-line @typescript-eslint/naming-convention
73
+ env: expect.objectContaining({ GIT_TERMINAL_PROMPT: '0' }),
74
+ });
73
75
  });
74
76
 
75
77
  itReturns(true);
76
78
 
77
- describe(`when cloning ${sshUrl} also fails`, () => {
79
+ describe(`when cloning ${webUrl} also fails`, () => {
78
80
  beforeEach(() => {
79
81
  jest.mocked(runCommand).mockRejectedValue('Nope!');
80
82
  });
@@ -93,7 +95,7 @@ describe('[startup] Cli Utils (Git)', () => {
93
95
  });
94
96
  afterEach(() => (process.env = originalEnv));
95
97
 
96
- test(`adds token to ${webUrl}`, async () => {
98
+ test(`clones ${webUrl} with token to destination directory`, async () => {
97
99
  await subject();
98
100
 
99
101
  const urlWithToken = webUrl.replace('github.com', `oauth2:${token}@github.com`);
@@ -123,34 +125,34 @@ describe('[startup] Cli Utils (Git)', () => {
123
125
  const webUrl = `https://github.com/servicetitan/${name}.git`;
124
126
  const sshUrl = `git@github.com:servicetitan/${name}.git`;
125
127
 
126
- test(`checks if ${webUrl} is reachable`, () => {
128
+ test(`checks if ${sshUrl} is reachable`, () => {
127
129
  subject();
128
130
 
129
- expect(runCommandOutput).toHaveBeenCalledWith(`git ls-remote -qt ${webUrl}`, {
131
+ expect(runCommandOutput).toHaveBeenCalledWith(`git ls-remote -qt ${sshUrl}`, {
130
132
  quiet: true,
131
133
  });
132
134
  });
133
135
 
134
136
  itReturns(true);
135
137
 
136
- describe(`when ${webUrl} is not reachable`, () => {
138
+ describe(`when ${sshUrl} is not reachable`, () => {
137
139
  beforeEach(() => {
138
140
  jest.mocked(runCommandOutput).mockImplementationOnce(() => {
139
141
  throw new Error('Oops!');
140
142
  });
141
143
  });
142
144
 
143
- test(`checks if ${sshUrl} is reachable`, () => {
145
+ test(`checks if ${webUrl} is reachable`, () => {
144
146
  subject();
145
147
 
146
- expect(runCommandOutput).toHaveBeenCalledWith(`git ls-remote -qt ${sshUrl}`, {
148
+ expect(runCommandOutput).toHaveBeenCalledWith(`git ls-remote -qt ${webUrl}`, {
147
149
  quiet: true,
148
150
  });
149
151
  });
150
152
 
151
153
  itReturns(true);
152
154
 
153
- describe(`when ${sshUrl} is also unreachable`, () => {
155
+ describe(`when ${webUrl} is also unreachable`, () => {
154
156
  beforeEach(() =>
155
157
  jest.mocked(runCommandOutput).mockImplementation(() => {
156
158
  throw new Error('Oops');
@@ -36,7 +36,7 @@ describe(`[startup] Utils`, () => {
36
36
  test('creates .git folder', () => {
37
37
  subject();
38
38
 
39
- expect(mkdirSpy).toHaveBeenCalledWith('.git');
39
+ expect(mkdirSpy).toHaveBeenCalledWith('.git', { recursive: true });
40
40
  });
41
41
 
42
42
  describe('when command is Init', () => {
@@ -180,7 +180,7 @@ async function runWatch(config: Configuration) {
180
180
  return new Promise<void>((_0, reject) => {
181
181
  const watching = compiler.watch({}, e => {
182
182
  if (e) {
183
- watching.close(() => {
183
+ watching?.close(() => {
184
184
  reject(e);
185
185
  });
186
186
  }
@@ -25,7 +25,11 @@ export async function gitCloneRepo(params: Repo & { destination: string }) {
25
25
  log.debug('git:clone-repo', `running ${command}`);
26
26
 
27
27
  // eslint-disable-next-line no-await-in-loop
28
- await runCommand(command, { quiet: true });
28
+ await runCommand(command, {
29
+ quiet: true,
30
+ // eslint-disable-next-line @typescript-eslint/naming-convention
31
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
32
+ });
29
33
  return true;
30
34
  } catch {
31
35
  // ignore error
@@ -50,10 +54,7 @@ function getGitUrls({ owner, name }: Repo) {
50
54
  const webUrl = `https://github.com/${owner}/${name}.git`;
51
55
  const sshUrl = `git@github.com:${owner}/${name}.git`;
52
56
 
53
- const urls = [webUrl, sshUrl];
54
- if (isCI() && !!process.env.GITHUB_TOKEN) {
55
- urls.unshift(webUrl.replace('github.com', `oauth2:${process.env.GITHUB_TOKEN}@github.com`));
56
- }
57
-
58
- return urls;
57
+ return isCI() && !!process.env.GITHUB_TOKEN
58
+ ? [webUrl.replace('github.com', `oauth2:${process.env.GITHUB_TOKEN}@github.com`)]
59
+ : [sshUrl, webUrl];
59
60
  }
@@ -19,6 +19,11 @@ export function maybeCreateGitFolder(command: Newable<Command>) {
19
19
  return;
20
20
  }
21
21
  if (!fs.existsSync('.git')) {
22
- fs.mkdirSync('.git');
22
+ /*
23
+ * Using {recursive: true} to ignore if directory exists. This happens
24
+ * when parallel process creates the directory after we've checked
25
+ * whether it exists.
26
+ */
27
+ fs.mkdirSync('.git', { recursive: true });
23
28
  }
24
29
  }
@@ -2,6 +2,7 @@ import { Config } from '@jest/types';
2
2
  import path from 'path';
3
3
  import { getJestConfiguration } from '../get-configuration';
4
4
  import { getDestinationFolders } from '../get-destination-folders';
5
+ import { pick } from '../pick';
5
6
 
6
7
  import { getJestConfigCLI } from '../get-jest-config';
7
8
 
@@ -76,5 +77,48 @@ describe('[startup] Utils', () => {
76
77
  expect.objectContaining({ transform: JSON.stringify(transform) })
77
78
  );
78
79
  });
80
+
81
+ describe('with "omitDefault"', () => {
82
+ beforeEach(() => {
83
+ jest.mocked(getJestConfiguration).mockReturnValue({
84
+ omitDefault: Object.keys(defaultConfig),
85
+ });
86
+ });
87
+
88
+ test('omits specified defaults', () => {
89
+ expect(pick(subject(), getJestConfiguration().omitDefault!)).toEqual({});
90
+ });
91
+ });
92
+
93
+ describe.each(['coveragePathIgnorePatterns', 'setupFiles', 'testPathIgnorePatterns'])(
94
+ 'with custom "%s"',
95
+ key => {
96
+ let customConfig: Record<string, string[]>;
97
+
98
+ beforeEach(() => {
99
+ customConfig = { [key]: ['foo'] };
100
+ jest.mocked(getJestConfiguration).mockReturnValue(customConfig);
101
+ });
102
+
103
+ test('appends custom value to default', () => {
104
+ expect(subject()).toEqual(
105
+ expect.objectContaining({
106
+ [key]: [
107
+ ...defaultConfig[key],
108
+ ...(getJestConfiguration()[key] as string[]),
109
+ ],
110
+ })
111
+ );
112
+ });
113
+
114
+ describe('when default is omitted', () => {
115
+ beforeEach(() => (customConfig.omitDefault = [key]));
116
+
117
+ test('returns only custom value', () => {
118
+ expect(subject()[key]).toEqual(customConfig[key]);
119
+ });
120
+ });
121
+ }
122
+ );
79
123
  });
80
124
  });
@@ -51,7 +51,9 @@ export interface StylelintConfiguration extends Partial<LinterOptions> {
51
51
  disabled?: boolean;
52
52
  }
53
53
 
54
- export type JestConfiguration = Omit<Config.Argv, '_' | '$0'>;
54
+ export interface JestConfiguration extends Omit<Config.Argv, '_' | '$0'> {
55
+ omitDefault?: string[];
56
+ }
55
57
 
56
58
  export interface NodeConfiguration {
57
59
  // eslint-disable-next-line @typescript-eslint/naming-convention
@@ -82,7 +84,9 @@ export enum CommandName {
82
84
  }
83
85
  /* eslint-enable @typescript-eslint/naming-convention */
84
86
 
85
- export type VitestConfiguration = ViteUserConfig['test'];
87
+ export type VitestConfiguration = ViteUserConfig['test'] & {
88
+ omitDefault?: string[];
89
+ };
86
90
 
87
91
  export interface WebComponentBranchConfigs {
88
92
  publishTag?: string;
@@ -3,16 +3,9 @@ import path from 'path';
3
3
  import { getJestConfiguration } from './get-configuration';
4
4
  import { getDestinationFolders } from './get-destination-folders';
5
5
  import { toArray } from './to-array';
6
+ import { omit } from './omit';
6
7
 
7
- function getDefaultJestConfiguration({
8
- coveragePathIgnorePatterns = [],
9
- setupFiles = [],
10
- testPathIgnorePatterns = [],
11
- }: {
12
- coveragePathIgnorePatterns?: string[];
13
- setupFiles?: string[];
14
- testPathIgnorePatterns?: string[];
15
- }) {
8
+ function getDefaultJestConfiguration() {
16
9
  const moduleNameMapper = {
17
10
  '\\.(css|scss|less|png|svg|svg\\?\\w+|jpg|jpeg|gif|woff|woff2|eot|ttf|otf)$':
18
11
  'identity-obj-proxy',
@@ -20,21 +13,21 @@ function getDefaultJestConfiguration({
20
13
 
21
14
  return {
22
15
  collectCoverageFrom: ['**/*.{ts,tsx}'],
23
- coveragePathIgnorePatterns: ['^.+\\.d\\.ts$', ...coveragePathIgnorePatterns],
16
+ coveragePathIgnorePatterns: ['^.+\\.d\\.ts$'],
24
17
  coverageReporters: ['html-spa', 'text', 'json', 'cobertura', 'lcov'],
25
18
  moduleNameMapper,
26
19
  modulePathIgnorePatterns: ['<rootDir>/.*/__mocks__'],
27
20
  preset: path.join(__dirname, '../../jest'),
28
- setupFiles: [path.join(__dirname, '../../jest/setup.js'), ...toArray(setupFiles)],
21
+ setupFiles: [path.join(__dirname, '../../jest/setup.js')],
29
22
  testEnvironment: 'jsdom',
30
- testPathIgnorePatterns: [
31
- '\\.yalc',
32
- ...getDestinationFolders(),
33
- ...toArray(testPathIgnorePatterns),
34
- ],
23
+ testPathIgnorePatterns: ['\\.yalc', ...getDestinationFolders()],
35
24
  testRunner: 'jest-circus/runner',
36
25
  transformIgnorePatterns: ['node_modules/(?!(@servicetitan|@react-hook|nanoid|axios)/)'],
37
26
  verbose: true,
27
+ } as Omit<Config.Argv, 'collectCoverageFrom' | 'moduleNameMapper' | 'setupFiles'> & {
28
+ collectCoverageFrom: string[];
29
+ moduleNameMapper: Record<string, string>;
30
+ setupFiles: string[];
38
31
  };
39
32
  }
40
33
 
@@ -42,13 +35,21 @@ function getDefaultJestConfiguration({
42
35
  * Get Jest config for running it using jest CLI (see jest runCLI function)
43
36
  */
44
37
  export function getJestConfigCLI(args: Config.Argv): Config.Argv {
45
- const { coveragePathIgnorePatterns, setupFiles, testPathIgnorePatterns, ...config } = {
38
+ const {
39
+ coveragePathIgnorePatterns,
40
+ omitDefault = [],
41
+ setupFiles,
42
+ testPathIgnorePatterns,
43
+ ...config
44
+ } = {
46
45
  ...getJestConfiguration(),
47
46
  ...args,
48
47
  };
49
48
 
49
+ const defaultConfig = omit(getDefaultJestConfiguration(), omitDefault);
50
+
50
51
  return stringifyForCLI({
51
- ...getDefaultJestConfiguration({
52
+ ...mergeArrayValues(defaultConfig, {
52
53
  coveragePathIgnorePatterns,
53
54
  setupFiles,
54
55
  testPathIgnorePatterns,
@@ -57,6 +58,23 @@ export function getJestConfigCLI(args: Config.Argv): Config.Argv {
57
58
  });
58
59
  }
59
60
 
61
+ function mergeArrayValues(
62
+ config: any,
63
+ arrayValues: {
64
+ coveragePathIgnorePatterns?: string | string[];
65
+ setupFiles?: string | string[];
66
+ testPathIgnorePatterns?: string | string[];
67
+ }
68
+ ) {
69
+ return Object.keys(arrayValues).reduce((result, key: keyof typeof arrayValues) => {
70
+ const newValue = arrayValues[key];
71
+ if (newValue) {
72
+ result[key] = [...toArray(result[key]), ...toArray(newValue)];
73
+ }
74
+ return result;
75
+ }, config);
76
+ }
77
+
60
78
  function stringifyForCLI(config: any): Config.Argv {
61
79
  return ['collectCoverageFrom', 'globals', 'moduleNameMapper', 'transform'].reduce(
62
80
  (result, key) => {
@@ -14,6 +14,7 @@ export * from './get-tsconfig';
14
14
  export * from './load-shared-dependencies';
15
15
  export * from './log';
16
16
  export * from './log-errors';
17
+ export * from './omit';
17
18
  export * from './pick';
18
19
  export * from './read-json';
19
20
  export * from './to-array';