@servicetitan/startup 29.0.0 → 30.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 (106) hide show
  1. package/bin/index.js +1 -1
  2. package/dist/cli/commands/convert-eslint-config.d.ts +21 -0
  3. package/dist/cli/commands/convert-eslint-config.d.ts.map +1 -0
  4. package/dist/cli/commands/convert-eslint-config.js +235 -0
  5. package/dist/cli/commands/convert-eslint-config.js.map +1 -0
  6. package/dist/cli/commands/get-command.d.ts.map +1 -1
  7. package/dist/cli/commands/get-command.js +6 -0
  8. package/dist/cli/commands/get-command.js.map +1 -1
  9. package/dist/cli/commands/init.d.ts.map +1 -1
  10. package/dist/cli/commands/init.js +4 -3
  11. package/dist/cli/commands/init.js.map +1 -1
  12. package/dist/cli/commands/lint.d.ts +1 -1
  13. package/dist/cli/commands/lint.js +2 -2
  14. package/dist/cli/commands/run-task.d.ts +13 -0
  15. package/dist/cli/commands/run-task.d.ts.map +1 -0
  16. package/dist/cli/commands/run-task.js +53 -0
  17. package/dist/cli/commands/run-task.js.map +1 -0
  18. package/dist/cli/index.js +13 -4
  19. package/dist/cli/index.js.map +1 -1
  20. package/dist/cli/tasks/cli-task.d.ts +16 -0
  21. package/dist/cli/tasks/cli-task.d.ts.map +1 -0
  22. package/dist/cli/tasks/cli-task.js +58 -0
  23. package/dist/cli/tasks/cli-task.js.map +1 -0
  24. package/dist/cli/tasks/swc-compile-package.d.ts +12 -0
  25. package/dist/cli/tasks/swc-compile-package.d.ts.map +1 -0
  26. package/dist/cli/tasks/swc-compile-package.js +66 -0
  27. package/dist/cli/tasks/swc-compile-package.js.map +1 -0
  28. package/dist/cli/tasks/task.d.ts +42 -0
  29. package/dist/cli/tasks/task.d.ts.map +1 -0
  30. package/dist/cli/tasks/task.js +113 -0
  31. package/dist/cli/tasks/task.js.map +1 -0
  32. package/dist/cli/tasks/tsc-compile-package.d.ts +12 -0
  33. package/dist/cli/tasks/tsc-compile-package.d.ts.map +1 -0
  34. package/dist/cli/tasks/tsc-compile-package.js +42 -0
  35. package/dist/cli/tasks/tsc-compile-package.js.map +1 -0
  36. package/dist/cli/tasks/tsc-compile.d.ts +16 -0
  37. package/dist/cli/tasks/tsc-compile.d.ts.map +1 -0
  38. package/dist/cli/tasks/tsc-compile.js +48 -0
  39. package/dist/cli/tasks/tsc-compile.js.map +1 -0
  40. package/dist/cli/utils/bundle.js +1 -1
  41. package/dist/cli/utils/bundle.js.map +1 -1
  42. package/dist/cli/utils/cli-os.js +2 -2
  43. package/dist/cli/utils/cli-os.js.map +1 -1
  44. package/dist/cli/utils/eslint.d.ts.map +1 -1
  45. package/dist/cli/utils/eslint.js +13 -12
  46. package/dist/cli/utils/eslint.js.map +1 -1
  47. package/dist/cli/utils/is-module-installed.js +1 -1
  48. package/dist/cli/utils/is-module-installed.js.map +1 -1
  49. package/dist/cli/utils/tsc.d.ts +3 -2
  50. package/dist/cli/utils/tsc.d.ts.map +1 -1
  51. package/dist/cli/utils/tsc.js +20 -16
  52. package/dist/cli/utils/tsc.js.map +1 -1
  53. package/dist/utils/get-configuration.d.ts +3 -1
  54. package/dist/utils/get-configuration.d.ts.map +1 -1
  55. package/dist/utils/get-configuration.js +2 -0
  56. package/dist/utils/get-configuration.js.map +1 -1
  57. package/dist/utils/get-destination-folders.d.ts.map +1 -1
  58. package/dist/utils/get-destination-folders.js +9 -13
  59. package/dist/utils/get-destination-folders.js.map +1 -1
  60. package/dist/utils/get-folders.js +2 -2
  61. package/dist/utils/get-folders.js.map +1 -1
  62. package/dist/utils/log.d.ts +1 -0
  63. package/dist/utils/log.d.ts.map +1 -1
  64. package/dist/utils/log.js +3 -0
  65. package/dist/utils/log.js.map +1 -1
  66. package/dist/webpack/configs/plugins/ignore-plugin/check-resource.js +1 -1
  67. package/dist/webpack/configs/plugins/ignore-plugin/check-resource.js.map +1 -1
  68. package/package.json +15 -9
  69. package/src/cli/commands/__tests__/convert-eslint-config.test.ts +455 -0
  70. package/src/cli/commands/__tests__/lint.test.ts +2 -2
  71. package/src/cli/commands/convert-eslint-config.ts +289 -0
  72. package/src/cli/commands/get-command.ts +6 -0
  73. package/src/cli/commands/init.ts +4 -3
  74. package/src/cli/commands/lint.ts +3 -3
  75. package/src/cli/commands/run-task.ts +41 -0
  76. package/src/cli/index.ts +16 -4
  77. package/src/cli/tasks/__tests__/cli-task.test.ts +115 -0
  78. package/src/cli/tasks/__tests__/swc-compile.test.ts +192 -0
  79. package/src/cli/tasks/__tests__/task.test.ts +88 -0
  80. package/src/cli/tasks/__tests__/tsc-compile-package.test.ts +72 -0
  81. package/src/cli/tasks/__tests__/tsc-compile.test.ts +156 -0
  82. package/src/cli/tasks/cli-task.ts +64 -0
  83. package/src/cli/tasks/swc-cli.d.ts +3 -0
  84. package/src/cli/tasks/swc-compile-package.ts +70 -0
  85. package/src/cli/tasks/task.ts +112 -0
  86. package/src/cli/tasks/tsc-compile-package.ts +47 -0
  87. package/src/cli/tasks/tsc-compile.ts +64 -0
  88. package/src/cli/utils/__tests__/assets-copy.test.ts +1 -1
  89. package/src/cli/utils/__tests__/bundle.test.ts +1 -1
  90. package/src/cli/utils/__tests__/cli-os.test.ts +2 -2
  91. package/src/cli/utils/__tests__/eslint.test.ts +37 -8
  92. package/src/cli/utils/__tests__/styles-copy.test.ts +1 -1
  93. package/src/cli/utils/__tests__/tsc.test.ts +34 -55
  94. package/src/cli/utils/bundle.ts +1 -1
  95. package/src/cli/utils/cli-os.ts +2 -2
  96. package/src/cli/utils/eslint.ts +16 -13
  97. package/src/cli/utils/is-module-installed.ts +1 -1
  98. package/src/cli/utils/tsc.ts +25 -20
  99. package/src/utils/__tests__/get-destination-folders.test.ts +1 -1
  100. package/src/utils/__tests__/get-folders.test.ts +6 -18
  101. package/src/utils/__tests__/log.test.ts +6 -0
  102. package/src/utils/get-configuration.ts +2 -0
  103. package/src/utils/get-destination-folders.ts +11 -17
  104. package/src/utils/get-folders.ts +2 -2
  105. package/src/utils/log.ts +4 -0
  106. package/src/webpack/configs/plugins/ignore-plugin/check-resource.ts +1 -1
@@ -0,0 +1,112 @@
1
+ import { log, readJsonSafe } from '../../utils';
2
+
3
+ class TaskTimer {
4
+ private counter = 0;
5
+
6
+ private get currentMarkName() {
7
+ return `${this.name}[${this.counter}]`;
8
+ }
9
+
10
+ private get currentMarkList() {
11
+ return globalThis.performance.getEntriesByName(this.currentMarkName, 'mark');
12
+ }
13
+
14
+ constructor(private readonly name: string) {
15
+ this.start();
16
+ }
17
+
18
+ start() {
19
+ if (this.currentMarkList.length > 0) {
20
+ this.cancel();
21
+ }
22
+
23
+ globalThis.performance.mark(this.currentMarkName);
24
+ }
25
+
26
+ end() {
27
+ if (this.currentMarkList.length === 0) {
28
+ throw new Error(`There are no tasks in progress for ${this.currentMarkName}`);
29
+ }
30
+
31
+ const measure = globalThis.performance.measure(this.currentMarkName, this.currentMarkName);
32
+ return { measure, counter: this.counter++ };
33
+ }
34
+
35
+ cancel() {
36
+ if (this.currentMarkList.length === 0) {
37
+ throw new Error(`There are no tasks in progress for ${this.currentMarkName}`);
38
+ }
39
+
40
+ const measure = globalThis.performance.measure(this.currentMarkName, {
41
+ start: this.currentMarkName,
42
+ detail: {
43
+ status: 'cancelled',
44
+ },
45
+ });
46
+
47
+ return { measure, counter: this.counter++ };
48
+ }
49
+
50
+ add(duration: number) {
51
+ const measure = globalThis.performance.measure(this.currentMarkName, {
52
+ duration,
53
+ end: globalThis.performance.now(),
54
+ });
55
+ return { measure, counter: this.counter++ };
56
+ }
57
+ }
58
+
59
+ export interface TaskParameters {
60
+ name: string;
61
+ watch?: boolean;
62
+ global: boolean;
63
+ }
64
+
65
+ export abstract class Task {
66
+ protected readonly watch: boolean;
67
+ protected readonly taskTimer: TaskTimer;
68
+ private readonly name: string;
69
+ private readonly global: boolean;
70
+
71
+ constructor({ name, watch, global }: TaskParameters) {
72
+ this.name = name;
73
+ this.watch = !!watch;
74
+ this.global = global;
75
+ this.taskTimer = new TaskTimer(name);
76
+ this.checkRunLocation();
77
+ }
78
+
79
+ protected logCompletionResults({
80
+ measure,
81
+ counter,
82
+ }: {
83
+ measure: PerformanceMeasure;
84
+ counter: number;
85
+ }) {
86
+ const enumerator = counter === 0 ? 'Initial ' : 'Subsequent ';
87
+ log.info(
88
+ `${this.watch ? enumerator : ''}${this.name} task completed in ${this.formatDuration(measure.duration)}`
89
+ );
90
+ }
91
+
92
+ private formatDuration(duration: number) {
93
+ if (duration < 1000) {
94
+ return `${duration.toFixed(0)}ms`;
95
+ }
96
+ return `${(duration / 1000).toFixed(2)}s`;
97
+ }
98
+
99
+ private checkRunLocation() {
100
+ const packageJson = readJsonSafe('package.json');
101
+ if (!packageJson?.workspaces && this.global) {
102
+ throw new Error(`Task ${this.name} has to run inside the workspace root directory`);
103
+ }
104
+ if ((!packageJson || packageJson.workspaces) && !this.global) {
105
+ throw new Error(`Task ${this.name} has to run inside the package directory`);
106
+ }
107
+ }
108
+
109
+ abstract description(): string;
110
+
111
+ abstract execute(): Promise<void>;
112
+ }
@@ -0,0 +1,47 @@
1
+ import { CliTask } from './cli-task';
2
+ import { getTsConfig } from '../../utils';
3
+ interface Args {
4
+ [key: string]: unknown;
5
+ watch?: boolean;
6
+ }
7
+
8
+ export class TscCompilePackage extends CliTask {
9
+ constructor({ watch }: Args) {
10
+ super({
11
+ name: 'tsc-compile-package',
12
+ global: false,
13
+ indicators: {
14
+ end: 'Watching for file changes.',
15
+ watchStart: 'Starting incremental compilation.',
16
+ },
17
+ watch,
18
+ });
19
+ }
20
+
21
+ description() {
22
+ return 'Compiles TypeScript files to JavaScript with TSC within package folder';
23
+ }
24
+
25
+ async execute() {
26
+ const tsConfig = getTsConfig();
27
+ await this.runChildProcess(
28
+ 'tsc',
29
+ [
30
+ this.watch ? '-w' : undefined,
31
+ '--pretty',
32
+ '--preserveWatchOutput',
33
+ '--noCheck',
34
+ '--composite',
35
+ 'false',
36
+ '--declaration',
37
+ 'false',
38
+ '--declarationMap',
39
+ 'false',
40
+ '--tsBuildInfoFile',
41
+ 'tsconfig.compile.tsbuildinfo',
42
+ '--project',
43
+ tsConfig,
44
+ ].filter(i => i !== undefined)
45
+ );
46
+ }
47
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ getPackages,
3
+ getPackagesGraph,
4
+ getTsConfig,
5
+ Package,
6
+ PackageType,
7
+ splitPackagesByType,
8
+ } from '../../utils';
9
+ import { CliTask } from './cli-task';
10
+
11
+ interface Args {
12
+ [key: string]: unknown;
13
+ ignore?: string | string[];
14
+ scope?: string | string[];
15
+ watch?: boolean;
16
+ typeCheckOnly?: boolean;
17
+ }
18
+
19
+ export class TscCompile extends CliTask {
20
+ constructor(private readonly args: Args) {
21
+ super({
22
+ name: 'tsc-compile',
23
+ watch: args.watch,
24
+ global: true,
25
+ indicators: {
26
+ end: 'Watching for file changes.',
27
+ watchStart: 'Starting incremental compilation.',
28
+ },
29
+ });
30
+ }
31
+
32
+ description() {
33
+ return 'Compiles one or more TypeScript projects with optional type checking';
34
+ }
35
+
36
+ async execute() {
37
+ const packages = splitPackagesByType(
38
+ getPackages({ scope: this.args.scope, ignore: this.args.ignore })
39
+ )[PackageType.TSC];
40
+ const tsConfigs = collapsePackages(packages).map(({ location }) => getTsConfig(location));
41
+ await this.runChildProcess(
42
+ 'tsc',
43
+ [
44
+ '-b',
45
+ this.watch ? '-w' : undefined,
46
+ '--pretty',
47
+ '--preserveWatchOutput',
48
+ this.args.typeCheckOnly ? '--emitDeclarationOnly' : undefined,
49
+ ...tsConfigs,
50
+ ].filter(i => i !== undefined)
51
+ );
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Exclude dependant packages as they will be built through project references
57
+ */
58
+ function collapsePackages(packages: Package[]) {
59
+ const dependencies = new Set(
60
+ Object.values(getPackagesGraph({ scope: packages.map(({ name }) => name) })).flat()
61
+ );
62
+
63
+ return packages.filter(({ name }) => !dependencies.has(name));
64
+ }
@@ -38,7 +38,7 @@ describe('[startup] Cli Utils (Assets)', () => {
38
38
  test('copies assets from source to destination in watch mode', async () => {
39
39
  try {
40
40
  await subject();
41
- } catch (error: any) {
41
+ } catch {
42
42
  // ignore rejected promise that terminates watch()
43
43
  }
44
44
 
@@ -106,7 +106,7 @@ describe('[startup] Cli Utils', () => {
106
106
 
107
107
  test('returns errors', () => {
108
108
  const toStringSpy = jest.spyOn(stats, 'toString');
109
- expect(subject).rejects.toBe(stats.toString());
109
+ expect(subject).rejects.toThrow(stats.toString());
110
110
  expect(toStringSpy).toHaveBeenCalledWith('errors-only');
111
111
  });
112
112
  });
@@ -57,13 +57,13 @@ describe('[startup] Cli Utils (OS)', () => {
57
57
  beforeEach(() => (exitCode = 2));
58
58
 
59
59
  test('rejects promise', () => {
60
- expect(subject('foo')).rejects.toBeUndefined();
60
+ expect(subject('foo')).rejects.toThrow(`command exited with code: ${exitCode}`);
61
61
  });
62
62
  });
63
63
 
64
64
  ['', [], ['']].forEach(command =>
65
65
  test(`rejects promise when called with ${JSON.stringify(command)}`, () => {
66
- expect(subject(command)).rejects.toBeUndefined();
66
+ expect(subject(command)).rejects.toThrow('invalid command');
67
67
  })
68
68
  );
69
69
 
@@ -2,11 +2,12 @@ import { fs, vol } from 'memfs';
2
2
  import { ESLint } from 'eslint';
3
3
 
4
4
  import { eslint } from '../eslint';
5
+ import { getDestinationFolders } from '../../../utils/get-destination-folders';
5
6
 
6
7
  jest.mock('fs', () => fs);
7
8
  jest.mock('eslint');
8
9
  jest.mock('../../../utils/get-destination-folders', () => ({
9
- getDestinationFolders: () => [],
10
+ getDestinationFolders: jest.fn(),
10
11
  }));
11
12
 
12
13
  describe(`[startup] utils:${eslint.name}`, () => {
@@ -37,19 +38,30 @@ describe(`[startup] utils:${eslint.name}`, () => {
37
38
  eslintArgs = { paths: ['foo', 'bar'] };
38
39
  eslintResults = [mockResult];
39
40
  eslintErrors = [];
41
+ jest.mocked(getDestinationFolders).mockReturnValue([]);
40
42
  });
41
43
 
42
44
  const subject = async () => eslint(eslintArgs);
43
45
 
44
- test('lints .ts and .tsx files', async () => {
46
+ test.each(['.vscode/', '**/.yalc/', '**/*.css.d.ts', '**/*.scss.d.ts', '**/*.less.d.ts'])(
47
+ 'ignores %s',
48
+ async pattern => {
49
+ await subject();
50
+
51
+ const ignorePatterns = jest.mocked(ESLint).mock.calls[0][0]?.ignorePatterns;
52
+ expect(ignorePatterns).toContain(pattern);
53
+ }
54
+ );
55
+
56
+ test('ignores output folders', async () => {
57
+ const outputFolders = ['../foo/out/', 'dist/', 'packages/bar/out/', 'packages/foo/dist/'];
58
+
59
+ jest.mocked(getDestinationFolders).mockReturnValue(outputFolders);
60
+
45
61
  await subject();
46
62
 
47
- expect(ESLint).toHaveBeenCalledWith(
48
- expect.objectContaining({
49
- extensions: ['.ts', '.tsx'],
50
- })
51
- );
52
- expect(mockESLint.lintFiles).toHaveBeenCalledWith(eslintArgs.paths);
63
+ const ignorePatterns = jest.mocked(ESLint).mock.calls[0][0]?.ignorePatterns;
64
+ outputFolders.forEach(dir => expect(ignorePatterns).toContain(dir));
53
65
  });
54
66
 
55
67
  describe('when instructed to fix issues', () => {
@@ -58,6 +70,7 @@ describe(`[startup] utils:${eslint.name}`, () => {
58
70
  test('applies fixes', async () => {
59
71
  await subject();
60
72
 
73
+ expect(ESLint).toHaveBeenCalledWith(expect.objectContaining({ fix: true }));
61
74
  expect(ESLint.outputFixes).toHaveBeenCalledWith(eslintResults);
62
75
  });
63
76
  });
@@ -99,4 +112,20 @@ describe(`[startup] utils:${eslint.name}`, () => {
99
112
  expect(mockESLint.lintFiles).not.toHaveBeenCalled();
100
113
  });
101
114
  });
115
+
116
+ describe('with custom ESLint config', () => {
117
+ const eslintConfig: any = { foo: 'bar' };
118
+
119
+ const config = JSON.stringify({
120
+ cli: { lint: { eslint: { ...eslintConfig } } },
121
+ });
122
+
123
+ beforeEach(() => vol.fromJSON({ 'package.json': config }));
124
+
125
+ test('passes through custom config', async () => {
126
+ await subject();
127
+
128
+ expect(ESLint).toHaveBeenCalledWith(expect.objectContaining(eslintConfig));
129
+ });
130
+ });
102
131
  });
@@ -38,7 +38,7 @@ describe('[startup] Cli Utils (Styles)', () => {
38
38
  test('copies styles from source to destination in watch mode', async () => {
39
39
  try {
40
40
  await subject();
41
- } catch (error: any) {
41
+ } catch {
42
42
  // ignore rejected promise that terminates watch()
43
43
  }
44
44
 
@@ -1,84 +1,63 @@
1
1
  import execa from 'execa';
2
- import { fs, vol } from 'memfs';
3
- import { Package, getPackagesGraph, getTsConfig } from '../../../utils';
4
- import { createPackage } from '../../../__mocks__';
2
+ import { lernaExec } from '../lerna-exec';
5
3
 
6
4
  import { tsc, tscWatch } from '../tsc';
5
+ import { createPackage } from '../../../__mocks__';
7
6
 
8
- jest.mock('fs', () => fs);
9
7
  jest.mock('execa', () => jest.fn());
10
- jest.mock('../../../utils', () => ({
11
- ...jest.requireActual('../../../utils'),
12
- log: { info: jest.fn(), debug: jest.fn() }, // suppress test output
13
- getPackagesGraph: jest.fn(),
8
+ jest.mock('../lerna-exec', () => ({
9
+ lernaExec: jest.fn(),
14
10
  }));
15
11
 
16
12
  describe('[startup] Cli Utils', () => {
17
- let packages: Package[];
13
+ const packages = ['foo', 'bar'].map(name => createPackage({ name }));
18
14
 
19
15
  beforeEach(() => {
20
- jest.resetAllMocks();
21
- jest.mocked(getPackagesGraph).mockReturnValue({});
22
- packages = [
23
- createPackage({ name: 'foo', location: 'packages/foo' }),
24
- createPackage({ name: 'bar', location: 'packages/bar' }),
25
- ];
26
- vol.fromJSON({
27
- 'packages/foo/tsconfig.json': JSON.stringify({}),
28
- 'packages/bar/tsconfig.json': JSON.stringify({}),
29
- });
16
+ jest.clearAllMocks();
30
17
  });
31
18
 
32
- afterEach(() => vol.reset());
19
+ function itRunsTscCompile(watch = false, typeCheckOnly = false) {
20
+ expect(execa).toHaveBeenCalledWith(
21
+ 'startup',
22
+ [
23
+ 'task',
24
+ 'tsc-compile',
25
+ ...packages.map(({ name }) => `--scope=${name}`),
26
+ watch ? '--watch' : undefined,
27
+ typeCheckOnly ? '--type-check-only' : undefined,
28
+ ].filter(i => i !== undefined),
29
+ { stdio: 'inherit' }
30
+ );
31
+ }
32
+
33
+ function itRunsTscCompilePackage(watch = false) {
34
+ expect(lernaExec).toHaveBeenCalledWith({
35
+ cmd: 'startup task tsc-compile-package',
36
+ scope: packages.map(({ name }) => name),
37
+ parallel: true,
38
+ stream: true,
39
+ ...(watch ? { '--': ['--watch'] } : {}),
40
+ });
41
+ }
33
42
 
34
43
  describe(`${tsc.name}`, () => {
35
44
  const subject = async () => tsc(packages);
36
45
 
37
- test('transpiles packages', async () => {
46
+ test('compiles and type checks packages', async () => {
38
47
  await subject();
39
48
 
40
- expect(execa).toHaveBeenCalledWith(
41
- 'tsc',
42
- ['-b', ...packages.map(({ location }) => getTsConfig(location))],
43
- { stdio: 'inherit' }
44
- );
45
- });
46
-
47
- describe('when package is dependency of another', () => {
48
- beforeEach(() => {
49
- jest.mocked(getPackagesGraph).mockReturnValue({
50
- [packages[0].name]: [packages[1].name], // packages[0] depends on packages[1]
51
- });
52
- });
53
-
54
- test('transpiles only parent packages', async () => {
55
- await subject();
56
-
57
- expect(execa).toHaveBeenCalledWith(
58
- expect.anything(),
59
- ['-b', getTsConfig(packages[0].location)],
60
- expect.anything()
61
- );
62
- });
49
+ itRunsTscCompile();
63
50
  });
64
51
  });
65
52
 
66
53
  describe(`${tscWatch.name}`, () => {
67
54
  const subject = async () => tscWatch(packages);
68
55
 
69
- test('transpiles packages in watch mode', async () => {
56
+ test('compiles and type checks packages in watch mode', async () => {
70
57
  await subject();
71
58
 
72
- expect(execa).toHaveBeenCalledWith(
73
- 'tsc',
74
- [
75
- '-b',
76
- '-w',
77
- '--preserveWatchOutput',
78
- ...packages.map(({ location }) => getTsConfig(location)),
79
- ],
80
- { stdio: 'inherit' }
81
- );
59
+ itRunsTscCompilePackage(true);
60
+ itRunsTscCompile(true, true);
82
61
  });
83
62
  });
84
63
  });
@@ -113,7 +113,7 @@ async function run(config: Configuration) {
113
113
  }
114
114
 
115
115
  if (stats.hasErrors()) {
116
- return reject(stats.toString('errors-only'));
116
+ return reject(new Error(stats.toString('errors-only')));
117
117
  }
118
118
 
119
119
  compiler.close(() => resolve(stats));
@@ -25,7 +25,7 @@ export const runCommand = (
25
25
  const commandName = commandArray.shift();
26
26
 
27
27
  if (!commandName) {
28
- reject();
28
+ reject(new Error('invalid command'));
29
29
 
30
30
  return;
31
31
  }
@@ -48,7 +48,7 @@ export const runCommand = (
48
48
  }
49
49
 
50
50
  if (code) {
51
- reject();
51
+ reject(new Error(`command exited with code: ${code}`));
52
52
  } else {
53
53
  resolve();
54
54
  }
@@ -1,6 +1,6 @@
1
1
  import { ESLint } from 'eslint';
2
2
 
3
- import { getDestinationFolders, getESLintConfiguration } from '../../utils';
3
+ import { getDestinationFolders, getESLintConfiguration, log } from '../../utils';
4
4
 
5
5
  interface Args {
6
6
  fix?: boolean;
@@ -14,21 +14,24 @@ export async function eslint({ fix, paths }: Args) {
14
14
  return;
15
15
  }
16
16
 
17
- const eslint = new ESLint({
17
+ const options: ESLint.Options = {
18
18
  errorOnUnmatchedPattern: false,
19
- extensions: ['.ts', '.tsx'],
20
- baseConfig: {
21
- ignorePatterns: [
22
- 'node_modules',
23
- '*.css.d.ts',
24
- '*.scss.d.ts',
25
- '*.less.d.ts',
26
- ...getDestinationFolders(),
27
- ],
28
- },
19
+ ignorePatterns: [
20
+ // **/node_modules/ is excluded by default
21
+ '.vscode/',
22
+ '**/.yalc/',
23
+ '**/*.css.d.ts',
24
+ '**/*.scss.d.ts',
25
+ '**/*.less.d.ts',
26
+ ...getDestinationFolders(),
27
+ ],
29
28
  fix,
30
29
  ...config,
31
- });
30
+ };
31
+
32
+ log.debug('eslint-options', () => JSON.stringify(options, null, 2));
33
+
34
+ const eslint = new ESLint(options);
32
35
 
33
36
  let files = paths;
34
37
  if (files.length === 0) {
@@ -2,7 +2,7 @@ export function isModuleInstalled(name: string): boolean {
2
2
  try {
3
3
  require.resolve(name);
4
4
  return true;
5
- } catch (e) {
5
+ } catch {
6
6
  return false;
7
7
  }
8
8
  }
@@ -1,6 +1,7 @@
1
1
  import execa from 'execa';
2
2
 
3
- import { getPackagesGraph, getTsConfig, log, Package } from '../../utils';
3
+ import { log, Package } from '../../utils';
4
+ import { lernaExec } from './lerna-exec';
4
5
 
5
6
  export function tsc(packages: Package[]) {
6
7
  log.info('Building TypeScript files...');
@@ -8,26 +9,30 @@ export function tsc(packages: Package[]) {
8
9
  }
9
10
 
10
11
  export function tscWatch(packages: Package[]) {
11
- return transpile(packages, ['-w', '--preserveWatchOutput']);
12
+ return transpile(packages, true);
12
13
  }
13
14
 
14
- async function transpile(packages: Package[], options: string[] = []) {
15
- const args = [
16
- '-b',
17
- ...options,
18
- ...collapsePackages(packages).map(({ location }) => getTsConfig(location)),
19
- ];
20
- log.debug('tsc', `Running tsc ${args.join(' ')}`);
21
- await execa('tsc', args, { stdio: 'inherit' });
22
- }
23
-
24
- /**
25
- * Exclude dependant packages as they will be built through project references
26
- */
27
- function collapsePackages(packages: Package[]) {
28
- const dependencies = new Set(
29
- Object.values(getPackagesGraph({ scope: packages.map(({ name }) => name) })).flat()
30
- );
15
+ async function transpile(packages: Package[], watch = false) {
16
+ const packageNames = packages.map(({ name }) => name);
17
+ const packageScopes = packageNames.map(name => `--scope=${name}`);
18
+ if (watch) {
19
+ return Promise.all([
20
+ lernaExec({
21
+ 'cmd': 'startup task tsc-compile-package',
22
+ 'scope': packageNames,
23
+ 'parallel': true,
24
+ 'stream': true,
25
+ '--': ['--watch'],
26
+ }),
27
+ execa(
28
+ 'startup',
29
+ ['task', 'tsc-compile', ...packageScopes, '--watch', '--type-check-only'],
30
+ { stdio: 'inherit' }
31
+ ),
32
+ ]);
33
+ }
31
34
 
32
- return packages.filter(({ name }) => !dependencies.has(name));
35
+ return execa('startup', ['task', 'tsc-compile', ...packageScopes], {
36
+ stdio: 'inherit',
37
+ });
33
38
  }
@@ -35,7 +35,7 @@ describe('[startup] Utils', () => {
35
35
  }
36
36
 
37
37
  function expectedOutDirs(locations: string[]) {
38
- return locations.map(location => `/packages/${location}/${location}outDir/`);
38
+ return locations.map(location => `packages/${location}/${location}outDir/`);
39
39
  }
40
40
 
41
41
  test('returns outDir path for all packages', () => {
@@ -15,15 +15,9 @@ describe('[Startup] utils:get-folders', () => {
15
15
  'tsconfig.json': JSON.stringify({ compilerOptions: { ourDir: 'dist' } }),
16
16
  });
17
17
 
18
- let error;
19
-
20
- try {
21
- getFolders();
22
- } catch (e) {
23
- error = e;
24
- }
25
-
26
- expect(error).toBe('"compilerOptions.rootDir" must be defined in the "tsconfig.json"!');
18
+ expect(() => getFolders()).toThrow(
19
+ '"compilerOptions.rootDir" must be defined in the "tsconfig.json"!'
20
+ );
27
21
  });
28
22
 
29
23
  test('getFolders throws an exception if "compilerOptions.outDir" isn\'t defined', () => {
@@ -32,15 +26,9 @@ describe('[Startup] utils:get-folders', () => {
32
26
  'tsconfig.json': JSON.stringify({ compilerOptions: { rootDir: 'src' } }),
33
27
  });
34
28
 
35
- let error;
36
-
37
- try {
38
- getFolders();
39
- } catch (e) {
40
- error = e;
41
- }
42
-
43
- expect(error).toBe('"compilerOptions.outDir" must be defined in the "tsconfig.json"!');
29
+ expect(() => getFolders()).toThrow(
30
+ '"compilerOptions.outDir" must be defined in the "tsconfig.json"!'
31
+ );
44
32
  });
45
33
 
46
34
  test('"compilerOptions.outDir" used as the destination', () => {
@@ -14,6 +14,12 @@ describe(`[startup] Utils`, () => {
14
14
  stdoutSpy = jest.spyOn(process.stdout, 'write').mockImplementation(jest.fn());
15
15
  });
16
16
 
17
+ test('text() writes default text', () => {
18
+ log.text(message);
19
+
20
+ expect(stdoutSpy).toHaveBeenCalledWith(`${chalk(message)}\n`);
21
+ });
22
+
17
23
  test('info() writes cyan text', () => {
18
24
  log.info(message);
19
25