@servicetitan/startup 32.3.2 → 32.5.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 (91) hide show
  1. package/dist/cli/commands/get-command.d.ts.map +1 -1
  2. package/dist/cli/commands/get-command.js +2 -0
  3. package/dist/cli/commands/get-command.js.map +1 -1
  4. package/dist/cli/commands/init.d.ts +1 -1
  5. package/dist/cli/commands/init.d.ts.map +1 -1
  6. package/dist/cli/commands/init.js +6 -5
  7. package/dist/cli/commands/init.js.map +1 -1
  8. package/dist/cli/commands/install.d.ts +1 -4
  9. package/dist/cli/commands/install.d.ts.map +1 -1
  10. package/dist/cli/commands/install.js +12 -118
  11. package/dist/cli/commands/install.js.map +1 -1
  12. package/dist/cli/commands/lint.d.ts.map +1 -1
  13. package/dist/cli/commands/lint.js +7 -55
  14. package/dist/cli/commands/lint.js.map +1 -1
  15. package/dist/cli/commands/mfe-list.d.ts.map +1 -1
  16. package/dist/cli/commands/mfe-list.js +2 -1
  17. package/dist/cli/commands/mfe-list.js.map +1 -1
  18. package/dist/cli/commands/review/rules/index.d.ts.map +1 -1
  19. package/dist/cli/commands/review/rules/index.js +2 -0
  20. package/dist/cli/commands/review/rules/index.js.map +1 -1
  21. package/dist/cli/commands/review/rules/no-deprecated-startup-install.d.ts +7 -0
  22. package/dist/cli/commands/review/rules/no-deprecated-startup-install.d.ts.map +1 -0
  23. package/dist/cli/commands/review/rules/no-deprecated-startup-install.js +38 -0
  24. package/dist/cli/commands/review/rules/no-deprecated-startup-install.js.map +1 -0
  25. package/dist/cli/commands/review/types.d.ts +1 -0
  26. package/dist/cli/commands/review/types.d.ts.map +1 -1
  27. package/dist/cli/commands/review/types.js.map +1 -1
  28. package/dist/cli/commands/stylelint.d.ts +17 -0
  29. package/dist/cli/commands/stylelint.d.ts.map +1 -0
  30. package/dist/cli/commands/stylelint.js +63 -0
  31. package/dist/cli/commands/stylelint.js.map +1 -0
  32. package/dist/cli/commands/upload-sourcemaps.d.ts.map +1 -1
  33. package/dist/cli/commands/upload-sourcemaps.js +2 -1
  34. package/dist/cli/commands/upload-sourcemaps.js.map +1 -1
  35. package/dist/cli/utils/cli-git.d.ts +0 -9
  36. package/dist/cli/utils/cli-git.d.ts.map +1 -1
  37. package/dist/cli/utils/cli-git.js +0 -59
  38. package/dist/cli/utils/cli-git.js.map +1 -1
  39. package/dist/cli/utils/cli-npm.d.ts +0 -3
  40. package/dist/cli/utils/cli-npm.d.ts.map +1 -1
  41. package/dist/cli/utils/cli-npm.js +0 -22
  42. package/dist/cli/utils/cli-npm.js.map +1 -1
  43. package/dist/cli/utils/index.d.ts +0 -1
  44. package/dist/cli/utils/index.d.ts.map +1 -1
  45. package/dist/cli/utils/index.js +0 -1
  46. package/dist/cli/utils/index.js.map +1 -1
  47. package/dist/cli/utils/lerna-exec.d.ts.map +1 -1
  48. package/dist/cli/utils/lerna-exec.js +2 -2
  49. package/dist/cli/utils/lerna-exec.js.map +1 -1
  50. package/dist/cli/utils/stylelint.d.ts +8 -0
  51. package/dist/cli/utils/stylelint.d.ts.map +1 -0
  52. package/dist/cli/utils/stylelint.js +67 -0
  53. package/dist/cli/utils/stylelint.js.map +1 -0
  54. package/dist/utils/get-configuration.d.ts +1 -0
  55. package/dist/utils/get-configuration.d.ts.map +1 -1
  56. package/dist/utils/get-configuration.js +1 -0
  57. package/dist/utils/get-configuration.js.map +1 -1
  58. package/package.json +8 -9
  59. package/src/cli/commands/__tests__/init.test.ts +11 -14
  60. package/src/cli/commands/__tests__/install.test.ts +19 -224
  61. package/src/cli/commands/__tests__/lint.test.ts +52 -10
  62. package/src/cli/commands/__tests__/mfe-list.test.ts +5 -4
  63. package/src/cli/commands/__tests__/stylelint.test.ts +32 -0
  64. package/src/cli/commands/__tests__/upload-sourcemaps.test.ts +4 -7
  65. package/src/cli/commands/get-command.ts +2 -0
  66. package/src/cli/commands/init.ts +6 -4
  67. package/src/cli/commands/install.ts +13 -116
  68. package/src/cli/commands/lint.ts +5 -57
  69. package/src/cli/commands/mfe-list.ts +2 -1
  70. package/src/cli/commands/review/rules/__tests__/no-deprecated-startup-install.test.ts +81 -0
  71. package/src/cli/commands/review/rules/index.ts +2 -0
  72. package/src/cli/commands/review/rules/no-deprecated-startup-install.ts +30 -0
  73. package/src/cli/commands/review/types.ts +1 -0
  74. package/src/cli/commands/stylelint.ts +26 -0
  75. package/src/cli/commands/upload-sourcemaps.ts +2 -1
  76. package/src/cli/utils/__tests__/cli-git.test.ts +4 -140
  77. package/src/cli/utils/__tests__/cli-npm.test.ts +0 -43
  78. package/src/cli/utils/__tests__/lerna-exec.test.ts +2 -2
  79. package/src/cli/utils/__tests__/stylelint.test.ts +164 -0
  80. package/src/cli/utils/cli-git.ts +1 -52
  81. package/src/cli/utils/cli-npm.ts +0 -12
  82. package/src/cli/utils/index.ts +1 -1
  83. package/src/cli/utils/lerna-exec.ts +1 -1
  84. package/src/cli/utils/stylelint.ts +55 -0
  85. package/src/utils/get-configuration.ts +1 -0
  86. package/dist/cli/utils/is-ci.d.ts +0 -2
  87. package/dist/cli/utils/is-ci.d.ts.map +0 -1
  88. package/dist/cli/utils/is-ci.js +0 -15
  89. package/dist/cli/utils/is-ci.js.map +0 -1
  90. package/src/cli/utils/__tests__/is-ci.test.ts +0 -40
  91. package/src/cli/utils/is-ci.ts +0 -3
@@ -0,0 +1,32 @@
1
+ import { stylelint } from '../../utils/stylelint';
2
+ import { Stylelint } from '../stylelint';
3
+
4
+ jest.mock('../../utils/stylelint', () => ({ stylelint: jest.fn() }));
5
+
6
+ describe(`[startup] ${Stylelint.name}`, () => {
7
+ let args: ConstructorParameters<typeof Stylelint>[0];
8
+
9
+ // eslint-disable-next-line @typescript-eslint/naming-convention
10
+ beforeEach(() => (args = { _: ['foo'], fix: false }));
11
+
12
+ const subject = async () => new Stylelint(args).execute();
13
+
14
+ test('runs stylelint with specified args', async () => {
15
+ await subject();
16
+
17
+ expect(stylelint).toHaveBeenCalledWith({ fix: args.fix, paths: args._ });
18
+ });
19
+
20
+ describe('with paths', () => {
21
+ beforeEach(() => (args.paths = ['bar']));
22
+
23
+ test('appends paths to args', async () => {
24
+ await subject();
25
+
26
+ expect(stylelint).toHaveBeenCalledWith({
27
+ fix: args.fix,
28
+ paths: [...args._, ...args.paths!],
29
+ });
30
+ });
31
+ });
32
+ });
@@ -1,17 +1,14 @@
1
+ import { isCI } from '@servicetitan/install';
1
2
  import { execSync } from 'child_process';
2
- import { vol, fs } from 'memfs';
3
+ import { vol } from 'memfs';
3
4
  import { inspect } from 'node:util';
4
5
  import path from 'path';
5
- import { isCI } from '../../utils';
6
6
  import { log } from '../../../utils';
7
7
  import { UploadSourcemaps } from '../upload-sourcemaps';
8
8
 
9
+ jest.mock('@servicetitan/install');
10
+ jest.mock('fs', () => require('memfs').fs);
9
11
  jest.mock('child_process', () => ({ execSync: jest.fn() }));
10
- jest.mock('fs', () => fs);
11
- jest.mock('../../utils', () => ({
12
- ...jest.requireActual('../../utils'),
13
- isCI: jest.fn(),
14
- }));
15
12
  jest.mock('../../../utils', () => ({
16
13
  ...jest.requireActual('../../../utils'),
17
14
  log: { info: jest.fn(), warning: jest.fn() },
@@ -17,6 +17,7 @@ import { PreparePackage } from './prepare-package';
17
17
  import { Review } from './review';
18
18
  import { RunTask } from './run-task';
19
19
  import { Start } from './start';
20
+ import { Stylelint } from './stylelint';
20
21
  import { StylesCheck } from './styles-check';
21
22
  import { Tests } from './test';
22
23
  import { Command, Newable } from './types';
@@ -39,6 +40,7 @@ const commands: Record<CommandName, Newable<Command>> = {
39
40
  [CommandName['prepare-package']]: PreparePackage,
40
41
  [CommandName.review]: Review,
41
42
  [CommandName.start]: Start,
43
+ [CommandName.stylelint]: Stylelint,
42
44
  [CommandName['styles-check']]: StylesCheck,
43
45
  [CommandName.test]: Tests,
44
46
  [CommandName.task]: RunTask,
@@ -1,8 +1,8 @@
1
+ import { gitCloneRepo, gitIsReachable } from '@servicetitan/install';
1
2
  import fs from 'fs';
2
3
  import path from 'path';
3
4
 
4
5
  import { log, logErrors } from '../../utils';
5
- import { gitCloneRepo, gitIsReachable } from '../utils';
6
6
  import { Command, CommandArgs } from './types';
7
7
 
8
8
  interface Args extends CommandArgs {
@@ -28,7 +28,7 @@ export class Init extends Command<Args> {
28
28
  throw new Error(`${destination} is not an empty directory`);
29
29
  }
30
30
 
31
- if (await this.cloneRepo(destination)) {
31
+ if (this.cloneRepo(destination)) {
32
32
  log.info(`copied example project to ${destination}`);
33
33
  return;
34
34
  }
@@ -36,10 +36,12 @@ export class Init extends Command<Args> {
36
36
  if (!gitIsReachable({ name: REPO_NAME })) {
37
37
  throw new Error(`could not read servicetitan/${REPO_NAME} repository`);
38
38
  }
39
+
40
+ return Promise.resolve();
39
41
  }
40
42
 
41
- async cloneRepo(destination: string) {
42
- const ok = await gitCloneRepo({ destination, name: REPO_NAME });
43
+ cloneRepo(destination: string) {
44
+ const ok = gitCloneRepo({ destination, name: REPO_NAME });
43
45
  if (!ok) {
44
46
  return false;
45
47
  }
@@ -1,142 +1,39 @@
1
1
  import { execSync } from 'child_process';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import os from 'os';
5
- import { log, logErrors, getStartupVersion, readJsonSafe } from '../../utils';
2
+ import { log, logErrors, getStartupVersion } from '../../utils';
6
3
  import { Command, CommandArgs } from './types';
7
- import { gitCloneRepo, isCI, npmWhoAmI } from '../utils';
8
-
9
4
  interface Args extends CommandArgs {
10
5
  fix?: boolean;
11
6
  quiet?: boolean;
12
7
  token?: boolean;
13
8
  }
14
9
 
15
- const REPO_NAME = 'frontend-dev-config';
16
- const AUTH_TOKEN_KEY = '//registry.npmjs.org/:_authToken';
17
- const AUTH_TOKEN_REGEX = /^\/\/registry\.npmjs\.org\/:_authToken=\s*\${([^}]+)}/m;
18
-
19
10
  export class Install extends Command<Args> {
20
11
  static readonly description = 'Install project dependencies';
21
12
  static readonly options = {
22
13
  fix: { boolean: true, describe: 'Update and dedupe package-lock.json', hidden: true },
23
- quite: { boolean: true, describe: 'Suppress output', hidden: true },
14
+ quiet: { boolean: true, describe: 'Suppress output', hidden: true },
24
15
  token: { boolean: true, describe: 'Configure npm token' },
25
16
  };
26
17
 
27
18
  @logErrors
28
19
  async execute() {
29
- if (!this.args?.quiet) {
20
+ if (!this.args.quiet) {
30
21
  log.info(`startup cli v${getStartupVersion()}`);
31
22
  }
32
23
 
33
- const env = await this.configureNpmToken();
24
+ const options = [
25
+ this.args.fix ? '--fix' : '',
26
+ this.args.quiet ? '--quiet' : '',
27
+ this.args.token === true ? '--token' : '',
28
+ this.args.token === false ? '--no-token' : '',
29
+ ].filter(option => !!option);
34
30
 
35
- if (this.args?.token !== true) {
36
- this.installPackages(env);
37
- }
31
+ const command = `npx @servicetitan/install ${options.join(' ')}`.trim();
38
32
 
39
- return Promise.resolve(); // stops "async method has no 'await' expression" lint error
40
- }
41
-
42
- private async configureNpmToken() {
43
- if (isCI() || this.args?.fix || this.args?.token === false) {
44
- return;
45
- }
46
-
47
- if (!this.args?.quiet) {
48
- log.info('Configuring NPM token ...');
49
- }
33
+ log.debug('install', command);
50
34
 
51
- if (this.args?.token !== true) {
52
- const npmUser = npmWhoAmI();
53
- /* istanbul ignore next: debug only */
54
- log.debug('install:npm-user', () => JSON.stringify({ npmUser }));
55
- if (npmUser === 'st-team') {
56
- return;
57
- }
58
- }
59
-
60
- const token = await this.fetchNpmToken();
61
- if (!token) {
62
- return;
63
- }
64
-
65
- execSync(`npm config set "${AUTH_TOKEN_KEY}"="${token}"`);
66
-
67
- if (!fs.existsSync('.npmrc')) {
68
- return;
69
- }
70
-
71
- const npmrc = fs.readFileSync('.npmrc', 'utf-8');
72
- const match = AUTH_TOKEN_REGEX.exec(npmrc);
73
- if (match) {
74
- return { [match[1]]: token };
75
- }
35
+ execSync(command, { stdio: 'inherit' });
76
36
 
77
- execSync(`npm config delete --location=project "${AUTH_TOKEN_KEY}"`);
78
- }
79
-
80
- private async fetchNpmToken() {
81
- const tempDirPath = fs.mkdtempSync(path.join(os.tmpdir(), 'st-install-'));
82
- try {
83
- if (!(await gitCloneRepo({ destination: tempDirPath, name: REPO_NAME }))) {
84
- throw new Error(`could not clone servicetitan/${REPO_NAME}`);
85
- }
86
-
87
- const npmJson = readJsonSafe(path.join(tempDirPath, '.npm.json'));
88
-
89
- /* istanbul ignore next: debug only */
90
- log.debug('install:fetch-token', () => JSON.stringify(npmJson, null, 2));
91
-
92
- if (!((npmJson && typeof npmJson === 'object') || Array.isArray(npmJson))) {
93
- throw new Error('.npm.json is not an object');
94
- }
95
-
96
- const { readOnlyToken: authToken } = npmJson;
97
- if (!authToken) {
98
- throw new Error('.npm.json does not contain auth token');
99
- }
100
-
101
- if (typeof authToken !== 'string') {
102
- throw new Error('.npm.json auth token is not a string');
103
- }
104
-
105
- return Buffer.from(authToken, 'base64').toString('utf-8');
106
- } catch (e) {
107
- log.warning(String(e));
108
- } finally {
109
- try {
110
- fs.rmSync(tempDirPath, { recursive: true, force: true });
111
- } catch {
112
- // ignore
113
- }
114
- }
115
- }
116
-
117
- private installPackages(env?: Record<string, string>) {
118
- /* istanbul ignore next: debug only */
119
- log.debug('install:install-packages', () => JSON.stringify({ env }));
120
-
121
- /**
122
- * Note, if these are changed, update bootstrap.js to match
123
- * @see {@link file://./../../../../../bootstrap.js}
124
- */
125
- const npmArguments = [
126
- isCI() ? 'ci' : 'i',
127
- '--audit=false',
128
- '--fund=false',
129
- '--legacy-peer-deps',
130
- ...(this.args?.fix ? ['--package-lock-only', '--prefer-dedupe'] : []),
131
- ].join(' ');
132
-
133
- if (!this.args?.quiet) {
134
- log.info(`Running npm ${npmArguments} ...`);
135
- }
136
-
137
- execSync(`npm ${npmArguments}`, {
138
- ...(env ? { env: { ...process.env, ...env } } : {}),
139
- stdio: 'inherit',
140
- });
37
+ return Promise.resolve(); // stops "async method has no 'await' expression" lint error
141
38
  }
142
39
  }
@@ -1,17 +1,8 @@
1
1
  /* eslint-disable @typescript-eslint/naming-convention */
2
- import path from 'path';
3
-
4
- import stylelint from 'stylelint';
5
-
6
- import {
7
- getDestinationFolders,
8
- getPackages,
9
- getStylelintConfiguration,
10
- log,
11
- logErrors,
12
- } from '../../utils';
2
+ import { getPackages, log, logErrors } from '../../utils';
13
3
  import { Command, CommandArgs } from './types';
14
4
  import { eslint, lernaExec } from '../utils';
5
+ import { stylelint } from '../utils/stylelint';
15
6
 
16
7
  interface Args extends CommandArgs {
17
8
  _: string[];
@@ -61,58 +52,19 @@ export class Lint extends Command<Args> {
61
52
 
62
53
  @logErrors
63
54
  private async stylelint() {
64
- const { disabled, ...config } = getStylelintConfiguration();
65
- if (disabled) {
66
- return;
67
- }
68
-
69
- const { fix } = this.args;
70
- const { paths } = this;
71
-
72
55
  log.info('Running stylelint...');
73
- const allowedExtensions = ['css', 'scss', 'less'];
74
- const glob = `**/*.{${allowedExtensions.join(',')}}`;
75
- let files = paths.reduce((result, path) => {
76
- const extension = path.split('.').pop();
77
- if (extension) {
78
- if (allowedExtensions.includes(extension.toLowerCase())) {
79
- result.push(path);
80
- }
81
- } else {
82
- result.push(pathUniJoin(path, glob));
83
- }
84
- return result;
85
- }, new Array<string>());
86
-
87
- if (!files.length) {
88
- files = [glob];
89
- }
90
-
91
- const { report, errored } = await stylelint.lint({
92
- files,
93
- ignorePattern: ['node_modules', ...getDestinationFolders()],
94
- formatter: 'string',
95
- fix,
96
- quietDeprecationWarnings: true,
97
- ...config,
98
- });
99
-
100
- process.stdout.write(report);
101
-
102
- if (errored) {
103
- process.exitCode = 1;
104
- }
56
+ await stylelint({ fix: this.args.fix, paths: this.paths });
105
57
  }
106
58
 
107
59
  @logErrors
108
60
  private async checkStyles() {
109
61
  const { ignore, scope } = this.args;
110
62
 
111
- const packages = getPackages({ ignore, scope });
63
+ const packages = ignore || scope ? getPackages({ ignore, scope }) : undefined;
112
64
 
113
65
  await lernaExec({
114
66
  cmd: 'startup styles-check',
115
- scope: packages.map(({ name }) => name),
67
+ scope: packages?.map(({ name }) => name),
116
68
  stream: true,
117
69
  });
118
70
  }
@@ -128,7 +80,3 @@ export class Lint extends Command<Args> {
128
80
  return [..._, ...paths];
129
81
  }
130
82
  }
131
-
132
- function pathUniJoin(...chunks: string[]) {
133
- return path.join(...chunks).replace(/\\/g, '/');
134
- }
@@ -1,4 +1,5 @@
1
1
  /* eslint-disable @typescript-eslint/naming-convention */
2
+ import { npmWhoAmI } from '@servicetitan/install';
2
3
  import chalk from 'chalk';
3
4
  import Table from 'cli-table3';
4
5
  import readline from 'readline/promises';
@@ -11,7 +12,7 @@ import {
11
12
  PackageType,
12
13
  } from '../../utils';
13
14
  import { Command, CommandArgs } from './types';
14
- import { isTTY, NPMPackageInfo, npmView, npmWhoAmI, runCommand } from '../utils';
15
+ import { isTTY, NPMPackageInfo, npmView, runCommand } from '../utils';
15
16
 
16
17
  interface Args extends CommandArgs {
17
18
  _: string[];
@@ -0,0 +1,81 @@
1
+ import { execSync } from 'child_process';
2
+ import { FixCategory, Package, PackageError, ReviewConfiguration } from '../../types';
3
+ import { mockProject } from '../__mocks__';
4
+
5
+ import { NoDeprecatedStartupInstall } from '../no-deprecated-startup-install';
6
+
7
+ jest.mock('child_process', () => ({ execSync: jest.fn() }));
8
+
9
+ describe(`[startup] Review ${NoDeprecatedStartupInstall.name}`, () => {
10
+ const id = 'no-deprecated-startup-install';
11
+ const rule = new NoDeprecatedStartupInstall();
12
+ let config: ReviewConfiguration;
13
+ let pkg: Package;
14
+ let packages: Package[];
15
+
16
+ beforeEach(() => {
17
+ config = {};
18
+ pkg = { name: 'project', location: '.' };
19
+ packages = [pkg];
20
+ jest.clearAllMocks();
21
+ });
22
+
23
+ const subject = () => rule.run(mockProject({ config, packages }));
24
+
25
+ const fixSubject = () => rule.fix(subject()!);
26
+
27
+ function itReturnsNothing() {
28
+ test('returns nothing', () => {
29
+ expect(subject()).toBeUndefined();
30
+ });
31
+ }
32
+
33
+ function itReturnsError() {
34
+ test('returns error', () => {
35
+ expect(subject()).toEqual({
36
+ id,
37
+ message: 'project uses deprecated "@servicetitan/startup install" script',
38
+ location: pkg.location,
39
+ fixable: FixCategory.isolated,
40
+ } satisfies PackageError);
41
+ });
42
+ }
43
+
44
+ itReturnsNothing();
45
+
46
+ describe('when project uses deprecated bootstrap script', () => {
47
+ beforeEach(() => {
48
+ pkg.scripts = { bootstrap: 'npx --yes @servicetitan/startup install' };
49
+ });
50
+
51
+ itReturnsError();
52
+
53
+ test('fixes error', () => {
54
+ fixSubject();
55
+
56
+ expect(execSync).toHaveBeenCalledWith(
57
+ 'npm pkg set scripts.bootstrap="npx --yes @servicetitan/install"'
58
+ );
59
+ });
60
+
61
+ describe('when script includes version tag', () => {
62
+ beforeEach(() => {
63
+ pkg.scripts!.bootstrap = 'npx --yes @servicetitan/startup@30.0.0 install';
64
+ });
65
+
66
+ itReturnsError();
67
+ });
68
+ });
69
+
70
+ describe('with no root package', () => {
71
+ beforeEach(() => (packages = []));
72
+
73
+ itReturnsNothing();
74
+ });
75
+
76
+ test('ignores invalid error', () => {
77
+ rule.fix({} as any);
78
+
79
+ expect(execSync).not.toHaveBeenCalled();
80
+ });
81
+ });
@@ -1,5 +1,6 @@
1
1
  import { PackageRule } from '../types';
2
2
  import { NoDeprecatedContentBase } from './no-deprecated-content-base';
3
+ import { NoDeprecatedStartupInstall } from './no-deprecated-startup-install';
3
4
  import { NoDirectPeerDependencies } from './no-direct-peer-dependencies';
4
5
  import { NoTypescriptEntryPoint } from './no-typescript-entry-point';
5
6
  import { PreferOpenEndedPeerDependencies } from './prefer-open-ended-peer-dependencies';
@@ -24,6 +25,7 @@ export const rules: PackageRule[] = [
24
25
  new RequireCompatibleLaunchDarklySdk(),
25
26
  new NoDeprecatedContentBase(),
26
27
  new NoDirectPeerDependencies(),
28
+ new NoDeprecatedStartupInstall(),
27
29
  new NoTypescriptEntryPoint(),
28
30
  new PreferOpenEndedPeerDependencies(),
29
31
  new RequireServiceTitanScope(),
@@ -0,0 +1,30 @@
1
+ import { execSync } from 'child_process';
2
+ import { FixCategory, PackageError, PackageRule, Project } from '../types';
3
+
4
+ export class NoDeprecatedStartupInstall implements PackageRule {
5
+ get id() {
6
+ return 'no-deprecated-startup-install';
7
+ }
8
+
9
+ run(project: Project): PackageError | undefined {
10
+ const root = project.packages.find(({ location }) => location === '.');
11
+ const bootstrap = root?.scripts?.bootstrap;
12
+
13
+ if (bootstrap && /@servicetitan\/startup.* install/.test(bootstrap)) {
14
+ return {
15
+ id: this.id,
16
+ message: 'project uses deprecated "@servicetitan/startup install" script',
17
+ location: root.location,
18
+ fixable: FixCategory.isolated,
19
+ };
20
+ }
21
+ }
22
+
23
+ fix({ location }: PackageError) {
24
+ if (!location) {
25
+ return;
26
+ }
27
+
28
+ execSync('npm pkg set scripts.bootstrap="npx --yes @servicetitan/install"');
29
+ }
30
+ }
@@ -45,6 +45,7 @@ export interface Package {
45
45
  name: string;
46
46
  peerDependencies?: Record<string, string>;
47
47
  private?: boolean;
48
+ scripts?: Record<string, string>;
48
49
  sideEffects?: any;
49
50
  workspaces?: string[];
50
51
  }
@@ -0,0 +1,26 @@
1
+ /* eslint-disable @typescript-eslint/naming-convention */
2
+ import { logErrors } from '../../utils';
3
+ import { stylelint } from '../utils/stylelint';
4
+ import { Command, CommandArgs } from './types';
5
+
6
+ interface Args extends CommandArgs {
7
+ _: string[];
8
+ fix?: boolean;
9
+ paths?: string[];
10
+ }
11
+
12
+ export class Stylelint extends Command<Args> {
13
+ static readonly options = {
14
+ _: { description: '[paths...]' },
15
+ };
16
+
17
+ @logErrors
18
+ async execute() {
19
+ await stylelint({ fix: this.args.fix, paths: this.paths });
20
+ }
21
+
22
+ private get paths() {
23
+ const { _, paths = [] } = this.args;
24
+ return [..._, ...paths];
25
+ }
26
+ }
@@ -1,7 +1,8 @@
1
+ import { isCI } from '@servicetitan/install';
1
2
  import path from 'path';
2
3
  import { execSync } from 'child_process';
3
4
  import { getTsConfig, isWebComponent, log, logErrors, readJson } from '../../utils';
4
- import { isCI, TSConfig } from '../utils';
5
+ import { TSConfig } from '../utils';
5
6
  import { Command, CommandArgs } from './types';
6
7
 
7
8
  interface Args extends CommandArgs {