@servicetitan/startup 24.0.4 → 24.1.1

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 (111) hide show
  1. package/dist/cli/commands/build.d.ts +1 -0
  2. package/dist/cli/commands/build.d.ts.map +1 -1
  3. package/dist/cli/commands/build.js +3 -0
  4. package/dist/cli/commands/build.js.map +1 -1
  5. package/dist/cli/commands/bundle-package.d.ts +1 -0
  6. package/dist/cli/commands/bundle-package.d.ts.map +1 -1
  7. package/dist/cli/commands/bundle-package.js +3 -0
  8. package/dist/cli/commands/bundle-package.js.map +1 -1
  9. package/dist/cli/commands/eslint.d.ts +1 -0
  10. package/dist/cli/commands/eslint.d.ts.map +1 -1
  11. package/dist/cli/commands/eslint.js +3 -0
  12. package/dist/cli/commands/eslint.js.map +1 -1
  13. package/dist/cli/commands/get-command.d.ts +6 -0
  14. package/dist/cli/commands/get-command.d.ts.map +1 -0
  15. package/dist/cli/commands/get-command.js +50 -0
  16. package/dist/cli/commands/get-command.js.map +1 -0
  17. package/dist/cli/commands/get-user-commands.d.ts +7 -0
  18. package/dist/cli/commands/get-user-commands.d.ts.map +1 -0
  19. package/dist/cli/commands/get-user-commands.js +17 -0
  20. package/dist/cli/commands/get-user-commands.js.map +1 -0
  21. package/dist/cli/commands/index.d.ts +3 -3
  22. package/dist/cli/commands/index.d.ts.map +1 -1
  23. package/dist/cli/commands/index.js +3 -0
  24. package/dist/cli/commands/index.js.map +1 -1
  25. package/dist/cli/commands/init.d.ts +2 -1
  26. package/dist/cli/commands/init.d.ts.map +1 -1
  27. package/dist/cli/commands/init.js +5 -2
  28. package/dist/cli/commands/init.js.map +1 -1
  29. package/dist/cli/commands/install.d.ts +1 -0
  30. package/dist/cli/commands/install.d.ts.map +1 -1
  31. package/dist/cli/commands/install.js +3 -0
  32. package/dist/cli/commands/install.js.map +1 -1
  33. package/dist/cli/commands/kendo-ui-license.d.ts +1 -0
  34. package/dist/cli/commands/kendo-ui-license.d.ts.map +1 -1
  35. package/dist/cli/commands/kendo-ui-license.js +3 -0
  36. package/dist/cli/commands/kendo-ui-license.js.map +1 -1
  37. package/dist/cli/commands/lint.d.ts +1 -0
  38. package/dist/cli/commands/lint.d.ts.map +1 -1
  39. package/dist/cli/commands/lint.js +3 -0
  40. package/dist/cli/commands/lint.js.map +1 -1
  41. package/dist/cli/commands/mfe-publish.d.ts +3 -0
  42. package/dist/cli/commands/mfe-publish.d.ts.map +1 -1
  43. package/dist/cli/commands/mfe-publish.js +14 -1
  44. package/dist/cli/commands/mfe-publish.js.map +1 -1
  45. package/dist/cli/commands/prepare-package.d.ts +1 -0
  46. package/dist/cli/commands/prepare-package.d.ts.map +1 -1
  47. package/dist/cli/commands/prepare-package.js +3 -0
  48. package/dist/cli/commands/prepare-package.js.map +1 -1
  49. package/dist/cli/commands/start.d.ts +1 -0
  50. package/dist/cli/commands/start.d.ts.map +1 -1
  51. package/dist/cli/commands/start.js +3 -0
  52. package/dist/cli/commands/start.js.map +1 -1
  53. package/dist/cli/commands/styles-check.d.ts +1 -0
  54. package/dist/cli/commands/styles-check.d.ts.map +1 -1
  55. package/dist/cli/commands/styles-check.js +3 -0
  56. package/dist/cli/commands/styles-check.js.map +1 -1
  57. package/dist/cli/commands/tests.d.ts +1 -0
  58. package/dist/cli/commands/tests.d.ts.map +1 -1
  59. package/dist/cli/commands/tests.js +3 -0
  60. package/dist/cli/commands/tests.js.map +1 -1
  61. package/dist/cli/commands/types.d.ts +5 -0
  62. package/dist/cli/commands/types.d.ts.map +1 -0
  63. package/dist/cli/commands/types.js +3 -0
  64. package/dist/cli/commands/types.js.map +1 -0
  65. package/dist/cli/index.js +24 -37
  66. package/dist/cli/index.js.map +1 -1
  67. package/dist/utils/find-packages.d.ts +6 -0
  68. package/dist/utils/find-packages.d.ts.map +1 -0
  69. package/dist/utils/find-packages.js +52 -0
  70. package/dist/utils/find-packages.js.map +1 -0
  71. package/dist/utils/get-configuration.d.ts +5 -4
  72. package/dist/utils/get-configuration.d.ts.map +1 -1
  73. package/dist/utils/get-configuration.js +15 -9
  74. package/dist/utils/get-configuration.js.map +1 -1
  75. package/dist/utils/get-packages.d.ts +1 -5
  76. package/dist/utils/get-packages.d.ts.map +1 -1
  77. package/dist/utils/get-packages.js +44 -26
  78. package/dist/utils/get-packages.js.map +1 -1
  79. package/package.json +14 -13
  80. package/src/cli/commands/__tests__/get-command.test.ts +17 -0
  81. package/src/cli/commands/__tests__/get-user-commands.test.ts +24 -0
  82. package/src/cli/commands/__tests__/init.test.ts +4 -4
  83. package/src/cli/commands/__tests__/mfe-package-publish.test.ts +8 -0
  84. package/src/cli/commands/build.ts +4 -0
  85. package/src/cli/commands/bundle-package.ts +4 -0
  86. package/src/cli/commands/eslint.ts +4 -0
  87. package/src/cli/commands/get-command.ts +50 -0
  88. package/src/cli/commands/get-user-commands.ts +19 -0
  89. package/src/cli/commands/index.ts +3 -4
  90. package/src/cli/commands/init.ts +7 -3
  91. package/src/cli/commands/install.ts +4 -0
  92. package/src/cli/commands/kendo-ui-license.ts +4 -0
  93. package/src/cli/commands/lint.ts +4 -0
  94. package/src/cli/commands/mfe-publish.ts +20 -1
  95. package/src/cli/commands/prepare-package.ts +4 -0
  96. package/src/cli/commands/start.ts +4 -0
  97. package/src/cli/commands/styles-check.ts +4 -0
  98. package/src/cli/commands/tests.ts +4 -0
  99. package/src/cli/commands/types.ts +4 -0
  100. package/src/cli/index.ts +27 -59
  101. package/src/utils/__tests__/get-configuration.test.ts +30 -8
  102. package/src/utils/__tests__/get-packages.test.ts +171 -93
  103. package/src/utils/find-packages.ts +61 -0
  104. package/src/utils/get-configuration.ts +18 -8
  105. package/src/utils/get-packages.ts +59 -42
  106. package/dist/utils/maybe-create-git-folder.d.ts +0 -10
  107. package/dist/utils/maybe-create-git-folder.d.ts.map +0 -1
  108. package/dist/utils/maybe-create-git-folder.js +0 -25
  109. package/dist/utils/maybe-create-git-folder.js.map +0 -1
  110. package/src/utils/__tests__/maybe-create-git-folder.test.ts +0 -41
  111. package/src/utils/maybe-create-git-folder.ts +0 -18
@@ -1,7 +1,5 @@
1
- import execa from 'execa';
2
- import { isBundle, isLegacy } from '../get-configuration';
3
- import { maybeCreateGitFolder } from '../maybe-create-git-folder';
4
-
1
+ import path from 'path';
2
+ import { fs, vol } from 'memfs';
5
3
  import {
6
4
  Package,
7
5
  PackageType,
@@ -10,142 +8,222 @@ import {
10
8
  splitPackagesByType,
11
9
  } from '../get-packages';
12
10
 
13
- jest.mock('execa', () => ({ sync: jest.fn() }));
14
- jest.mock('../get-configuration');
15
- jest.mock('../maybe-create-git-folder');
11
+ jest.mock('fs', () => fs);
16
12
 
17
13
  describe('[startup] Utils', () => {
18
- const packages: Pick<Package, 'name' | 'location' | 'type'>[] = [
19
- { name: 'foo', location: 'packages/foo', type: PackageType.Legacy },
20
- { name: 'bar', location: 'packages/bar', type: PackageType.TSC },
21
- { name: 'baz', location: 'packages/baz', type: PackageType.Webpack },
22
- ];
14
+ beforeEach(() => jest.clearAllMocks());
23
15
 
24
16
  describe(`${getPackages.name}`, () => {
25
17
  let options: Parameters<typeof getPackages>[0] | undefined;
26
- let dependencies: Record<string, string[]> | undefined;
27
-
28
- function findPackageByLocation(location?: string) {
29
- return packages.find(p => p.location === location);
30
- }
31
-
32
- function packageGraph() {
33
- return dependencies ?? Object.fromEntries(packages.map(({ name }) => [name, []]));
34
- }
35
-
36
- function scopedPackages() {
37
- let result = packages;
38
- if (options?.scope) {
39
- const arrayScope = Array.isArray(options.scope) ? options.scope : [options.scope];
40
- result = result.filter(({ name }) => arrayScope.includes(name));
41
- }
42
- return result;
43
- }
44
-
45
- function resolvePackages(args: string[]) {
46
- if (args.includes('--graph')) {
47
- return packageGraph();
48
- }
49
- if (args.includes('--scope')) {
50
- return scopedPackages().map(({ type, ...pkg }) => pkg);
51
- }
52
- return packages.map(({ type, ...pkg }) => pkg);
53
- }
54
18
 
55
19
  beforeEach(() => {
56
20
  options = undefined;
57
- dependencies = undefined;
58
-
59
- jest.mocked(isBundle).mockImplementation(
60
- location => findPackageByLocation(location)?.type === PackageType.Webpack
61
- );
62
- jest.mocked(isLegacy).mockImplementation(
63
- location => findPackageByLocation(location)?.type === PackageType.Legacy
64
- );
65
- // @ts-expect-error because implementation doesn't match all exec.sync signatures
66
- jest.mocked(execa.sync).mockImplementation((_: string, args: string[]): any => {
67
- return { stdout: JSON.stringify(resolvePackages(args)) };
21
+ vol.fromNestedJSON({
22
+ 'package.json': JSON.stringify({
23
+ workspaces: ['packages/*', 'examples/*', 'legacy/bar'],
24
+ }),
25
+ 'packages': {
26
+ foo: {
27
+ 'package.json': JSON.stringify({
28
+ name: 'foo',
29
+ version: '1.0.0',
30
+ private: true,
31
+ }),
32
+ },
33
+ },
34
+ 'legacy': {
35
+ bar: {
36
+ 'package.json': JSON.stringify({
37
+ name: 'bar',
38
+ version: '2.0.0',
39
+ cli: { legacy: true },
40
+ }),
41
+ },
42
+ },
43
+ 'examples': {
44
+ baz: {
45
+ 'package.json': JSON.stringify({
46
+ name: 'baz',
47
+ version: '3.0.0',
48
+ cli: { webpack: false },
49
+ }),
50
+ },
51
+ },
68
52
  });
69
53
  });
70
54
 
71
- const subject = () => getPackages(options);
72
-
73
- test('returns lerna packages with type metadata', () => {
74
- expect(subject()).toEqual(packages);
75
- expect(execa.sync).toHaveBeenCalledWith('npx', 'lerna la --json'.split(' '));
55
+ afterEach(() => vol.reset());
56
+
57
+ const subject = () =>
58
+ getPackages(options).sort((a, b) => a.version.localeCompare(b.version));
59
+
60
+ test('returns workspace packages', () => {
61
+ expect(subject()).toEqual([
62
+ {
63
+ name: 'foo',
64
+ version: '1.0.0',
65
+ private: true,
66
+ location: path.resolve('./packages/foo'),
67
+ type: PackageType.Webpack,
68
+ },
69
+ {
70
+ name: 'bar',
71
+ version: '2.0.0',
72
+ private: false,
73
+ location: path.resolve('./legacy/bar'),
74
+ type: PackageType.Legacy,
75
+ },
76
+ {
77
+ name: 'baz',
78
+ version: '3.0.0',
79
+ private: false,
80
+ location: path.resolve('./examples/baz'),
81
+ type: PackageType.TSC,
82
+ },
83
+ ]);
76
84
  });
77
85
 
78
- test('conditionally creates .git folder', () => {
79
- subject();
86
+ describe('with "scope"', () => {
87
+ beforeEach(() => (options = { scope: 'foo' }));
80
88
 
81
- expect(maybeCreateGitFolder).toHaveBeenCalled();
82
- });
89
+ test('returns only the specified packages', () => {
90
+ expect(subject()).toEqual([expect.objectContaining({ name: 'foo' })]);
91
+ });
83
92
 
84
- describe.each(['scope', 'ignore'])('with "%s"', option => {
85
- beforeEach(() => (options = { [option]: 'foo' }));
93
+ describe('when filtered package has dependencies', () => {
94
+ beforeEach(() => {
95
+ const fooPath = path.resolve('./packages/foo/package.json');
96
+ const fooJson = JSON.parse(fs.readFileSync(fooPath, 'utf8').toString());
97
+ fs.writeFileSync(
98
+ fooPath,
99
+ JSON.stringify({ ...fooJson, dependencies: { bar: '>=2.0' } })
100
+ );
101
+ });
102
+
103
+ test('also returns internal dependencies', () => {
104
+ expect(subject()).toEqual([
105
+ expect.objectContaining({ name: 'foo' }),
106
+ expect.objectContaining({ name: 'bar' }),
107
+ ]);
108
+ });
109
+ });
86
110
 
87
- test(`passes "${option}" option to lerna`, () => {
88
- subject();
111
+ describe('when "scope" is a glob expression', () => {
112
+ beforeEach(() => (options = { scope: 'ba*' }));
89
113
 
90
- expect(execa.sync).toHaveBeenCalledWith(
91
- 'npx',
92
- `lerna la --${option} foo --json`.split(' ')
93
- );
114
+ test('returns only the specified packages', () => {
115
+ expect(subject()).toEqual([
116
+ expect.objectContaining({ name: 'bar' }),
117
+ expect.objectContaining({ name: 'baz' }),
118
+ ]);
119
+ });
94
120
  });
95
121
  });
96
122
 
97
- describe('with "scope"', () => {
98
- beforeEach(() => (options = { scope: 'foo' }));
123
+ describe('with "ignore"', () => {
124
+ beforeEach(() => (options = { ignore: ['foo'] }));
99
125
 
100
- test('returns filtered packages', () => {
101
- expect(subject()).toEqual(packages.filter(({ name }) => name === 'foo'));
126
+ test('omits the specified packages', () => {
127
+ expect(subject()).not.toContainEqual(expect.objectContaining({ name: 'foo' }));
102
128
  });
103
129
 
104
- describe('when filtered package has dependencies', () => {
105
- beforeEach(() => (dependencies = { ...packageGraph(), foo: ['bar', 'external'] }));
130
+ describe('when "ignore" is a glob expression', () => {
131
+ beforeEach(() => (options = { ignore: ['ba*'] }));
106
132
 
107
- test('also returns internal dependencies', () => {
108
- expect(subject()).toEqual(
109
- packages.filter(({ name }) => ['foo', 'bar'].includes(name))
110
- );
133
+ test('returns only the specified packages', () => {
134
+ const result = subject();
135
+ expect(result).not.toContainEqual(expect.objectContaining({ name: 'bar' }));
136
+ expect(result).not.toContainEqual(expect.objectContaining({ name: 'baz' }));
111
137
  });
112
138
  });
113
139
  });
140
+
141
+ function itReturnsEmptyArray() {
142
+ test('returns empty array', () => {
143
+ expect(subject()).toEqual([]);
144
+ });
145
+ }
146
+
147
+ describe('with no packages', () => {
148
+ beforeEach(() => vol.fromJSON({ 'package.json': JSON.stringify({}) }));
149
+
150
+ itReturnsEmptyArray();
151
+ });
152
+
153
+ describe('with no package.json', () => {
154
+ beforeEach(() => vol.reset());
155
+
156
+ itReturnsEmptyArray();
157
+ });
114
158
  });
115
159
 
116
160
  describe(`${getPackagesGraph.name}`, () => {
117
- const packageGraph = Object.fromEntries(packages.map(({ name }) => [name, []]));
118
161
  let options: Parameters<typeof getPackagesGraph>[0] | undefined;
119
162
 
120
163
  beforeEach(() => {
121
164
  options = undefined;
122
- jest.mocked(execa.sync).mockImplementation((): any => ({
123
- stdout: JSON.stringify(packageGraph),
124
- }));
165
+ vol.fromNestedJSON({
166
+ 'package.json': JSON.stringify({ workspaces: ['packages/*'] }),
167
+ 'packages': {
168
+ foo: {
169
+ 'package.json': JSON.stringify({
170
+ name: 'foo',
171
+ dependencies: { bar: '^1.0.0', react: '>=18' },
172
+ devDependencies: {
173
+ '@testing-library/react': '>=16',
174
+ '@testing-library/dom': '>=10',
175
+ },
176
+ }),
177
+ },
178
+ bar: {
179
+ 'package.json': JSON.stringify({
180
+ name: 'bar',
181
+ dependencies: { foo: '*' },
182
+ }),
183
+ },
184
+ baz: {
185
+ 'package.json': JSON.stringify({
186
+ name: 'baz',
187
+ }),
188
+ },
189
+ },
190
+ });
125
191
  });
126
192
 
127
193
  const subject = () => getPackagesGraph(options);
128
194
 
129
- test('returns lerna package graph', () => {
130
- expect(subject()).toEqual(packageGraph);
131
- expect(execa.sync).toHaveBeenCalledWith('npx', 'lerna la --graph'.split(' '));
195
+ test('returns package graph', () => {
196
+ expect(subject()).toEqual({
197
+ foo: ['bar', 'react', '@testing-library/react', '@testing-library/dom'].sort(),
198
+ bar: ['foo'],
199
+ baz: [],
200
+ });
132
201
  });
133
202
 
134
- describe.each(['scope', 'ignore'])('with "%s"', option => {
135
- beforeEach(() => (options = { [option]: 'foo' }));
203
+ describe('with "scope"', () => {
204
+ beforeEach(() => (options = { scope: 'foo' }));
205
+
206
+ test('includes only the specified packages', () => {
207
+ expect(Object.keys(subject())).toEqual(['foo']);
208
+ });
209
+ });
136
210
 
137
- test(`passes "${option}" option to lerna`, () => {
138
- subject();
211
+ describe('with "ignore"', () => {
212
+ beforeEach(() => (options = { ignore: ['foo', 'bar'] }));
139
213
 
140
- expect(execa.sync).toHaveBeenCalledWith(
141
- 'npx',
142
- `lerna la --${option} foo --graph`.split(' ')
143
- );
214
+ test('omits the specified packages', () => {
215
+ expect(Object.keys(subject())).toEqual(['baz']);
144
216
  });
145
217
  });
146
218
  });
147
219
 
148
220
  describe(`${splitPackagesByType.name}`, () => {
221
+ const packages: Pick<Package, 'name' | 'location' | 'type'>[] = [
222
+ { name: 'foo', location: 'packages/foo', type: PackageType.Legacy },
223
+ { name: 'bar', location: 'packages/bar', type: PackageType.TSC },
224
+ { name: 'baz', location: 'packages/baz', type: PackageType.Webpack },
225
+ ];
226
+
149
227
  const subject = (packages: Package[]) => splitPackagesByType(packages);
150
228
 
151
229
  test('groups packages by type', () => {
@@ -0,0 +1,61 @@
1
+ import { globSync } from 'glob';
2
+ import path from 'path';
3
+ import { readJsonSafe } from './read-json';
4
+ import { log } from './log';
5
+
6
+ export interface ProjectPackage extends Record<string, any> {
7
+ name: string;
8
+ location: string;
9
+ }
10
+
11
+ export function findPackages(): ProjectPackage[] {
12
+ const workspaces = findWorkspaces();
13
+ if (!workspaces) {
14
+ return [];
15
+ }
16
+
17
+ const result = workspaces
18
+ .flatMap(pattern => globSync(pattern))
19
+ .map(location => {
20
+ const dir = path.resolve(location);
21
+ const file = path.join(dir, 'package.json');
22
+ return { ...readJsonSafe<ProjectPackage>(file), location: dir };
23
+ })
24
+ .filter(({ name }) => name !== undefined) as ProjectPackage[];
25
+
26
+ /* istanbul ignore next: debug only */
27
+ log.debug(
28
+ `find-packages`,
29
+ () => `found packages: ${JSON.stringify(result.map(({ name }) => name))}`
30
+ );
31
+
32
+ return result;
33
+ }
34
+
35
+ function findWorkspaces() {
36
+ let directory = path.resolve('./');
37
+ do {
38
+ const packageJson = path.resolve(path.join(directory, 'package.json'));
39
+
40
+ log.debug('find-packages', `reading: ${packageJson}`);
41
+ let workspaces = readJsonSafe<{ workspaces: string[] }>(packageJson)?.workspaces;
42
+
43
+ if (!workspaces) {
44
+ const lernaJson = path.resolve(path.join(directory, 'lerna.json'));
45
+ log.debug('find-packages', `reading: ${lernaJson}`);
46
+ workspaces = readJsonSafe<{ packages: string[] }>(lernaJson)?.packages;
47
+ }
48
+
49
+ if (workspaces) {
50
+ workspaces = workspaces.map(
51
+ // Note, glob requires "/" as path separator, even on Windows
52
+ workspace => path.join(directory, workspace).replace(/\\/g, '/')
53
+ );
54
+ /* istanbul ignore next: debug only */
55
+ log.debug('find-packages', () => `found workspaces: ${JSON.stringify(workspaces)}`);
56
+ return workspaces;
57
+ }
58
+
59
+ directory = path.resolve(path.join(directory, '../'));
60
+ } while (path.parse(directory).name);
61
+ }
@@ -71,12 +71,22 @@ type Configuration = {
71
71
  [key in CommandName]: NodeConfiguration;
72
72
  } & NodeConfiguration;
73
73
 
74
- export function getConfiguration(location = './'): Configuration {
75
- return readJson(path.join(location, 'package.json')).cli ?? {};
74
+ type LocationOrJson = string | Record<string, any>;
75
+
76
+ export function getConfiguration(locationOrJson: LocationOrJson = './'): Configuration {
77
+ const json =
78
+ typeof locationOrJson === 'string'
79
+ ? readJson(path.join(locationOrJson, 'package.json'))
80
+ : locationOrJson;
81
+ return json?.cli ?? {};
76
82
  }
77
83
 
78
- export function getConfigurationSafe(location = './'): Configuration {
79
- return readJsonSafe(path.join(location, 'package.json'))?.cli ?? {};
84
+ export function getConfigurationSafe(locationOrJson: LocationOrJson = './'): Configuration {
85
+ const json =
86
+ typeof locationOrJson === 'string'
87
+ ? readJsonSafe(path.join(locationOrJson, 'package.json'))
88
+ : locationOrJson;
89
+ return json?.cli ?? {};
80
90
  }
81
91
 
82
92
  export function getDevServerConfiguration() {
@@ -114,8 +124,8 @@ export function getStylelintConfiguration() {
114
124
  return getConfiguration().lint?.stylelint ?? {};
115
125
  }
116
126
 
117
- export function isBundle(location?: string) {
118
- return getConfiguration(location).webpack !== false;
127
+ export function isBundle(locationOrJson?: LocationOrJson) {
128
+ return getConfiguration(locationOrJson).webpack !== false;
119
129
  }
120
130
 
121
131
  export function isCustomStyleRules() {
@@ -138,8 +148,8 @@ export function isExposeSharedDependencies() {
138
148
  return configuration.webpack['expose-shared-dependencies'] === true;
139
149
  }
140
150
 
141
- export function isLegacy(location?: string) {
142
- return getConfiguration(location).legacy === true;
151
+ export function isLegacy(locationOrJson?: LocationOrJson) {
152
+ return getConfiguration(locationOrJson).legacy === true;
143
153
  }
144
154
 
145
155
  export function isLegacyRoot() {
@@ -1,6 +1,6 @@
1
- import execa from 'execa';
1
+ import multimatch from 'multimatch';
2
+ import { findPackages, ProjectPackage } from './find-packages';
2
3
  import { isBundle, isLegacy } from './get-configuration';
3
- import { maybeCreateGitFolder } from './maybe-create-git-folder';
4
4
  import { toArray } from './to-array';
5
5
 
6
6
  export enum PackageType {
@@ -17,8 +17,6 @@ export interface Package {
17
17
  location: string;
18
18
  }
19
19
 
20
- type RawPackage = Omit<Package, 'type'>;
21
-
22
20
  interface GetPackagesOptions {
23
21
  scope?: string | string[];
24
22
  ignore?: string | string[];
@@ -28,26 +26,13 @@ export function getPackages(options: GetPackagesOptions = {}) {
28
26
  const scope = toArray(options.scope);
29
27
  const ignore = toArray(options.ignore);
30
28
 
31
- maybeCreateGitFolder();
32
- const allPackages: RawPackage[] = JSON.parse(
33
- execa.sync('npx', ['lerna', 'la', '--json']).stdout
34
- );
35
-
36
- if (!scope.length && !ignore.length) {
29
+ const allPackages = findPackages();
30
+ if (scope.length === 0 && ignore.length === 0) {
37
31
  return withMetadata(allPackages);
38
32
  }
39
33
 
40
- const filteredPackages: RawPackage[] = JSON.parse(
41
- execa.sync('npx', [
42
- 'lerna',
43
- 'la',
44
- ...scope.map(v => ['--scope', v]).flat(),
45
- ...ignore.map(v => ['--ignore', v]).flat(),
46
- '--json',
47
- ]).stdout
48
- );
49
-
50
- const graph: Record<string, string[]> = getPackagesGraph();
34
+ const filteredPackages = filterPackages(allPackages, options);
35
+ const graph = buildPackageGraph(allPackages);
51
36
 
52
37
  // We don't need external dependencies
53
38
  for (const [vertex, dependencies] of Object.entries(graph)) {
@@ -77,39 +62,67 @@ export function getPackages(options: GetPackagesOptions = {}) {
77
62
  return withMetadata(allPackages.filter(({ name }) => used[name]));
78
63
  }
79
64
 
80
- interface GetPackagesGraphOptions {
81
- scope?: string | string[];
82
- ignore?: string | string[];
83
- }
84
-
85
65
  /**
86
66
  * Returns packages and their direct dependencies
87
67
  */
88
- export function getPackagesGraph(options: GetPackagesGraphOptions = {}): Record<string, string[]> {
89
- const scope = toArray(options.scope);
90
- const ignore = toArray(options.ignore);
68
+ export function getPackagesGraph(options: GetPackagesOptions = {}) {
69
+ return buildPackageGraph(filterPackages(findPackages(), options));
70
+ }
91
71
 
92
- return JSON.parse(
93
- execa.sync('npx', [
94
- 'lerna',
95
- 'la',
96
- ...scope.map(v => ['--scope', v]).flat(),
97
- ...ignore.map(v => ['--ignore', v]).flat(),
98
- '--graph',
99
- ]).stdout
72
+ function buildPackageGraph(packages: ProjectPackage[]) {
73
+ return packages.reduce<Record<string, string[]>>(
74
+ (result, { name, dependencies = {}, devDependencies = {} }) => {
75
+ result[name] = [...Object.keys(dependencies), ...Object.keys(devDependencies)].sort(
76
+ (a, b) => a.localeCompare(b)
77
+ );
78
+ return result;
79
+ },
80
+ {}
100
81
  );
101
82
  }
102
83
 
103
- function withMetadata(packages: RawPackage[]): Package[] {
104
- return packages.map(pkg => ({ ...pkg, type: getPackageType(pkg) }));
84
+ function filterPackages(packages: ProjectPackage[], options: GetPackagesOptions) {
85
+ if (options.ignore === undefined && options.scope === undefined) {
86
+ return packages;
87
+ }
88
+
89
+ const patterns = [...toArray(options.scope), ...negate(toArray(options.ignore))];
90
+
91
+ if (patterns.length) {
92
+ if (!options.scope?.length) {
93
+ patterns.unshift('**');
94
+ }
95
+ }
96
+
97
+ const chosen = new Set(
98
+ multimatch(
99
+ packages.map(item => item.name),
100
+ patterns
101
+ )
102
+ );
103
+
104
+ return packages.filter(({ name }) => chosen.has(name));
105
+ }
106
+
107
+ function withMetadata(packages: ProjectPackage[]): Package[] {
108
+ return packages.map(pkg => {
109
+ const { name, version, location } = pkg;
110
+ return {
111
+ name,
112
+ version,
113
+ private: pkg.private ?? false,
114
+ location,
115
+ type: getPackageType(pkg),
116
+ };
117
+ });
105
118
  }
106
119
 
107
- function getPackageType({ location }: RawPackage) {
108
- if (isLegacy(location)) {
120
+ function getPackageType(packageJson: ProjectPackage) {
121
+ if (isLegacy(packageJson)) {
109
122
  return PackageType.Legacy;
110
123
  }
111
124
 
112
- if (isBundle(location)) {
125
+ if (isBundle(packageJson)) {
113
126
  return PackageType.Webpack;
114
127
  }
115
128
 
@@ -129,3 +142,7 @@ export function splitPackagesByType(packages: Package[]) {
129
142
 
130
143
  return result;
131
144
  }
145
+
146
+ function negate(patterns: string[]) {
147
+ return patterns.map(pattern => `!${pattern}`);
148
+ }
@@ -1,10 +0,0 @@
1
- /**
2
- * Create empty .git folder to workaround issue where Lerna does not
3
- * detect workspace packages on Windows systems. The empty .git folder
4
- * causes nx to use the git-hasher when building the project graph.
5
- * Note this gets fixed (e.g., https://github.com/nrwl/nx/issues/8601) but
6
- * keeps reappearing (e.g., https://github.com/nrwl/nx/issues/9584 and
7
- * https://github.com/nrwl/nx/issues/18094)
8
- */
9
- export declare function maybeCreateGitFolder(): void;
10
- //# sourceMappingURL=maybe-create-git-folder.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"maybe-create-git-folder.d.ts","sourceRoot":"","sources":["../../src/utils/maybe-create-git-folder.ts"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,SAOnC"}
@@ -1,25 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.maybeCreateGitFolder = void 0;
7
- const fs_1 = __importDefault(require("fs"));
8
- /**
9
- * Create empty .git folder to workaround issue where Lerna does not
10
- * detect workspace packages on Windows systems. The empty .git folder
11
- * causes nx to use the git-hasher when building the project graph.
12
- * Note this gets fixed (e.g., https://github.com/nrwl/nx/issues/8601) but
13
- * keeps reappearing (e.g., https://github.com/nrwl/nx/issues/9584 and
14
- * https://github.com/nrwl/nx/issues/18094)
15
- */
16
- function maybeCreateGitFolder() {
17
- if (process.platform !== 'win32') {
18
- return;
19
- }
20
- if (!fs_1.default.existsSync('.git')) {
21
- fs_1.default.mkdirSync('.git');
22
- }
23
- }
24
- exports.maybeCreateGitFolder = maybeCreateGitFolder;
25
- //# sourceMappingURL=maybe-create-git-folder.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"maybe-create-git-folder.js","sourceRoot":"","sources":["../../src/utils/maybe-create-git-folder.ts"],"names":[],"mappings":";;;;;;AAAA,4CAAoB;AAEpB;;;;;;;GAOG;AACH,SAAgB,oBAAoB;IAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QAC/B,OAAO;IACX,CAAC;IACD,IAAI,CAAC,YAAE,CAAC,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,YAAE,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC;AACL,CAAC;AAPD,oDAOC"}
@@ -1,41 +0,0 @@
1
- import { fs, vol } from 'memfs';
2
-
3
- import { maybeCreateGitFolder } from '../maybe-create-git-folder';
4
-
5
- jest.mock('fs', () => fs);
6
-
7
- describe(`[startup] Utils`, () => {
8
- describe(`${maybeCreateGitFolder.name}`, () => {
9
- const mkdirSpy = jest.spyOn(fs, 'mkdirSync').mockImplementation(jest.fn());
10
-
11
- beforeEach(() => vol.fromJSON({}));
12
-
13
- afterEach(() => vol.reset);
14
-
15
- const subject = () => maybeCreateGitFolder();
16
-
17
- describe('when running on Windows', () => {
18
- beforeEach(() => {
19
- Object.defineProperty(process, 'platform', { value: 'win32' });
20
- });
21
-
22
- test('creates .git folder', () => {
23
- subject();
24
-
25
- expect(mkdirSpy).toHaveBeenCalledWith('.git');
26
- });
27
- });
28
-
29
- describe('when not running on Windows', () => {
30
- beforeEach(() => {
31
- Object.defineProperty(process, 'platform', { value: 'linux' });
32
- });
33
-
34
- test('does not create .git folder', () => {
35
- subject();
36
-
37
- expect(mkdirSpy).not.toHaveBeenCalledWith();
38
- });
39
- });
40
- });
41
- });