@upleveled/preflight 1.23.8 → 2.1.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.
@@ -2,6 +2,7 @@ import { Listr } from 'listr2';
2
2
  import { promises, existsSync } from 'node:fs';
3
3
  import { execaCommand } from 'execa';
4
4
  import chalk from 'chalk';
5
+ import path, { sep, dirname, relative } from 'node:path';
5
6
  import { createRequire } from 'node:module';
6
7
  import readdirp from 'readdirp';
7
8
  import semver from 'semver';
@@ -11,11 +12,10 @@ import userAgents from 'top-user-agents';
11
12
  import { URL, fileURLToPath } from 'node:url';
12
13
  import algoliasearch from 'algoliasearch';
13
14
  import pReduce from 'p-reduce';
14
- import path, { dirname } from 'node:path';
15
15
  import os from 'node:os';
16
16
 
17
+ // https://www.compart.com/en/unicode/U+2800
17
18
  // eslint-disable-next-line security/detect-bidi-characters -- Intentional use of unusual character for formatting
18
-
19
19
  const emptyBrailleCharacter = '‎';
20
20
  function commandExample(command) {
21
21
  return `${emptyBrailleCharacter} ${chalk.dim('$')} ${command}`;
@@ -36,23 +36,20 @@ async function allChangesCommittedToGit() {
36
36
  stdout: replSlug
37
37
  } = await execaCommand('echo $REPL_SLUG');
38
38
  const isRunningInReplIt = replSlug !== '';
39
-
40
39
  if (isRunningInReplIt) {
41
40
  await promises.writeFile('.git/info/exclude', '.replit\n');
42
41
  }
43
-
44
42
  const {
45
43
  stdout
46
44
  } = await execaCommand('git status --porcelain');
47
-
48
45
  if (stdout !== '') {
49
- const onlyYarnLockModifiedOnDrone = stdout.trim() === 'M yarn.lock' && (await isDrone());
46
+ const onlyPnpmLockModifiedOnDrone = stdout.trim() === 'M pnpm-lock.yaml' && (await isDrone());
50
47
  throw new Error(`Some changes have not been committed to Git:
51
- ${stdout}${onlyYarnLockModifiedOnDrone ? `
48
+ ${stdout}${onlyPnpmLockModifiedOnDrone ? `
52
49
 
53
- The only file with changes is the yarn.lock file, indicating that npm was incorrectly used in addition to Yarn (eg. an "npm install" command was run). To fix this, force regeneration of the yarn.lock file locally with the following command and then commit the changes:
50
+ The only file with changes is the pnpm-lock.yaml file, indicating that npm was incorrectly used in addition to pnpm (eg. an "npm install" command was run). To fix this, force regeneration of the pnpm-lock.yaml file locally with the following command and then commit the changes:
54
51
 
55
- ${commandExample('yarn --force')}` : ''}
52
+ ${commandExample('pnpm install --force')}` : ''}
56
53
  `);
57
54
  }
58
55
  }
@@ -63,44 +60,28 @@ var allChangesCommittedToGit$1 = {
63
60
  'default': allChangesCommittedToGit
64
61
  };
65
62
 
66
- const CRLF = '\r\n';
67
- function normalizeNewline(input) {
68
- if (typeof input !== 'string') {
69
- throw new TypeError(`Expected a \`string\`, got \`${typeof input}\``);
70
- }
71
-
72
- return input.replace(new RegExp(CRLF, 'g'), '\n');
73
- }
74
-
63
+ const errorKey = 'errorCount';
64
+ const warningKey = 'warningCount';
75
65
  const title$1 = 'ESLint';
76
66
  async function eslintCheck() {
77
67
  try {
78
- await execaCommand('yarn eslint . --max-warnings 0 --format compact');
68
+ await execaCommand('pnpm eslint . --max-warnings 0 --format json');
79
69
  } catch (error) {
80
70
  const {
81
71
  stdout
82
72
  } = error;
83
- const lines = stdout.split('\n'); // If no ESLint problems detected, throw the error
84
-
85
- if (!/^\d+ problems?$/.test(lines[lines.length - 2])) {
73
+ // If no ESLint problems detected, throw the error
74
+ if (!new RegExp(`"${errorKey}":|"${warningKey}":`).test(stdout)) {
86
75
  throw error;
87
76
  }
88
-
89
77
  throw new Error(`ESLint problems found in the following files:
90
- ${normalizeNewline(stdout).split('\n') // Match lines starting with slashes (macOS, Linux) or drive letters (Windows)
91
- .filter(line => /^(\/|[A-Z]:\\)/.test(line)) // Strip out the filename
92
- .map(line => {
93
- var _line$match;
94
-
95
- return (_line$match = line.match(/^(([A-Z]:)?[^:]+):/)) == null ? void 0 : _line$match[1];
96
- }) // Remove duplicate filenames
97
- .reduce((linesWithoutDuplicates, line) => {
98
- if (line && !linesWithoutDuplicates.includes(line)) {
99
- linesWithoutDuplicates.push(line);
100
- }
101
-
102
- return linesWithoutDuplicates;
103
- }, []).join('\n')}
78
+ ${JSON.parse(stdout).filter(eslintResult => !(eslintResult[errorKey] === 0 && eslintResult[warningKey] === 0))
79
+ // Make paths relative to the project:
80
+ // before: /home/projects/next-student-project/app/api/hello/route.js
81
+ // after: app/api/hello/route.js
82
+ .map(({
83
+ filePath
84
+ }) => filePath.replace(`${process.cwd()}${sep}`, '')).join('\n')}
104
85
 
105
86
  Open these files in your editor - there should be problems to fix
106
87
  `);
@@ -114,32 +95,25 @@ var eslint = {
114
95
  };
115
96
 
116
97
  const require$1 = /*#__PURE__*/createRequire(`${/*#__PURE__*/process.cwd()}/`);
117
-
118
98
  const title$2 = 'ESLint config is latest version';
119
99
  async function eslintConfigIsValid() {
120
100
  const {
121
101
  stdout: remoteVersion
122
102
  } = await execaCommand('npm show eslint-config-upleveled version');
123
103
  let localVersion;
124
-
125
104
  try {
126
105
  const eslintConfigPackageJsonPath = require$1.resolve('eslint-config-upleveled/package.json');
127
-
128
106
  localVersion = JSON.parse(await promises.readFile(eslintConfigPackageJsonPath, 'utf-8')).version;
129
107
  } catch (err) {}
130
-
131
108
  if (typeof localVersion === 'undefined') {
132
109
  throw new Error(`The UpLeveled ESLint Config has not been installed - please install using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
133
110
  `);
134
111
  }
135
-
136
112
  if (semver.gt(remoteVersion, localVersion)) {
137
113
  throw new Error(`Your current version of the UpLeveled ESLint Config (${localVersion}) is older than the latest version ${remoteVersion} - upgrade by running all lines of the install instructions on https://www.npmjs.com/package/eslint-config-upleveled
138
114
  `);
139
115
  }
140
-
141
116
  let eslintConfigMatches;
142
-
143
117
  try {
144
118
  eslintConfigMatches = (await promises.readFile('./.eslintrc.cjs', 'utf-8')).trim() === `/** @type {import('@typescript-eslint/utils').TSESLint.Linter.Config} */
145
119
  const config = {
@@ -148,17 +122,14 @@ const config = {
148
122
 
149
123
  module.exports = config;`;
150
124
  } catch (err) {
151
- throw new Error(`Error reading your .eslintrc.cjs file - please reinstall the config using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
125
+ throw new Error(`Error reading your .eslintrc.cjs file - please delete the file if it exists and reinstall the config using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
152
126
  `);
153
127
  }
154
-
155
128
  if (!eslintConfigMatches) {
156
- throw new Error(`Your ESLint config file .eslintrc.cjs does not match the configuration file template - please reinstall the config using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
129
+ throw new Error(`Your .eslintrc.cjs file does not match the configuration file template - please delete the file and reinstall the config using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
157
130
  `);
158
131
  }
159
-
160
132
  const eslintDisableOccurrences = [];
161
-
162
133
  for await (const {
163
134
  path
164
135
  } of readdirp('.', {
@@ -166,12 +137,10 @@ module.exports = config;`;
166
137
  fileFilter: ['*.js', '*.jsx', '*.ts', '*.tsx']
167
138
  })) {
168
139
  const fileContents = await promises.readFile(path, 'utf-8');
169
-
170
140
  if (/eslint-disable|eslint [a-z0-9@/-]+: (0|off)/.test(fileContents)) {
171
141
  eslintDisableOccurrences.push(path);
172
142
  }
173
143
  }
174
-
175
144
  if (eslintDisableOccurrences.length > 0) {
176
145
  throw new Error(`ESLint has been disabled in the following files:
177
146
  ${eslintDisableOccurrences.join('\n')}
@@ -205,18 +174,15 @@ async function linkOnGithubAbout() {
205
174
  }).nextAll('div').filter(function () {
206
175
  return $(this).children('.octicon.octicon-link').length > 0;
207
176
  }).children('.octicon.octicon-link').next().children('a[href]').attr('href');
208
-
209
177
  if (!urlInAboutSection) {
210
178
  throw new Error(`Deployed project link not found in About section on ${repoUrl}. Click on the cog symbol to the right of the About heading and paste the Repl.it / Netlify / Fly.io link in the Website box.`);
211
179
  }
212
-
213
180
  const response = await fetch(urlInAboutSection, {
214
181
  headers: {
215
182
  // For repl.it
216
183
  'user-agent': randomUserAgent()
217
184
  }
218
185
  });
219
-
220
186
  if (!response.ok) {
221
187
  throw new Error(`Project link in About section on ${repoUrl} is not returning a proper status code: the link returns status code ${response.status} (${response.statusText}).`);
222
188
  }
@@ -228,6 +194,14 @@ var linkOnGithubAbout$1 = {
228
194
  'default': linkOnGithubAbout
229
195
  };
230
196
 
197
+ const CRLF = '\r\n';
198
+ function normalizeNewline(input) {
199
+ if (typeof input !== 'string') {
200
+ throw new TypeError(`Expected a \`string\`, got \`${typeof input}\``);
201
+ }
202
+ return input.replace(new RegExp(CRLF, 'g'), '\n');
203
+ }
204
+
231
205
  const title$4 = 'node_modules/ folder ignored in Git';
232
206
  async function nodeModulesIgnoredFromGit() {
233
207
  if ((await execaCommand('git ls-files node_modules/')).stdout !== '') {
@@ -236,13 +210,10 @@ async function nodeModulesIgnoredFromGit() {
236
210
  ${commandExample('git rm -r --cached node_modules')}
237
211
  `);
238
212
  }
239
-
240
213
  if ((await execaCommand('git ls-files .gitignore')).stdout !== '.gitignore') {
241
214
  throw new Error('.gitignore file not found');
242
215
  }
243
-
244
216
  const nodeModulesInGitignore = normalizeNewline(await promises.readFile('./.gitignore', 'utf8')).split('\n').reduce((found, line) => found || /^\/?node_modules\/?$/.test(line), false);
245
-
246
217
  if (!nodeModulesInGitignore) {
247
218
  throw new Error('node_modules not found in .gitignore');
248
219
  }
@@ -260,22 +231,24 @@ const preflightPackageJson = /*#__PURE__*/JSON.parse(await /*#__PURE__*/promises
260
231
  const title$5 = 'Next.js project has sharp installed';
261
232
  function nextJsProjectHasSharpInstalled() {
262
233
  const dependenciesPackageNames = Object.keys(projectPackageJson.dependencies);
263
-
264
234
  if (dependenciesPackageNames.includes('next') && !dependenciesPackageNames.includes('sharp')) {
265
235
  throw new Error(`Next.js projects should have sharp installed for better image optimization. Install it with:
266
236
 
267
- ${commandExample('yarn add sharp')}
237
+ ${commandExample('pnpm add sharp')}
268
238
  `);
269
239
  }
270
240
  }
271
241
 
272
- const client = /*#__PURE__*/algoliasearch( // Application ID and API key specific to UpLeveled
242
+ const client = /*#__PURE__*/algoliasearch(
243
+ // Application ID and API key specific to UpLeveled
273
244
  // Preflight. Please don't use anywhere else without
274
245
  // asking Algolia's permission.
275
- 'OFCNCOG2CU', // Application ID
246
+ 'OFCNCOG2CU',
247
+ // Application ID
276
248
  'ec73550aa8b2936dab436d4e02144784');
277
249
  const index = /*#__PURE__*/client.initIndex('npm-search');
278
- const title$6 = 'No dependencies without types'; // This is a naive check for matching @types/<pkg name> packages
250
+ const title$6 = 'No dependencies without types';
251
+ // This is a naive check for matching @types/<pkg name> packages
279
252
  // that the student hasn't yet installed. It is not intended to
280
253
  // be an exhaustive check for any types for all packages.
281
254
  //
@@ -283,7 +256,6 @@ const title$6 = 'No dependencies without types'; // This is a naive check for ma
283
256
  // `styled-components`:
284
257
  //
285
258
  // https://learn.upleveled.io/courses/btcmp-l-webfs-gen-0/modules/122-cheatsheet-css-in-js/#eslint-errors-with-styled-components
286
-
287
259
  async function noDependenciesWithoutTypes() {
288
260
  const {
289
261
  devDependencies = {},
@@ -291,92 +263,93 @@ async function noDependenciesWithoutTypes() {
291
263
  } = projectPackageJson;
292
264
  const dependenciesWithMissingTypes = await pReduce(Object.keys(dependencies), async (filteredDependencies, dependency) => {
293
265
  var _results$types;
294
-
266
+ // Return early here because:
267
+ // - Algolia reports "ObjectID does not exist" for `@upleveled/react-scripts`
268
+ // - `@upleveled/react-scripts` has no types to install
269
+ if (dependency === '@upleveled/react-scripts') {
270
+ return filteredDependencies;
271
+ }
295
272
  try {
296
273
  const packageJsonPath = require.resolve(`${dependency}/package.json`);
297
-
298
- const modulePackageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf-8')); // If the keys "types" or "typings" are in the module's `package.json`, bail out
299
-
274
+ const modulePackageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf-8'));
275
+ // If the keys "types" or "typings" are in the module's `package.json`, bail out
300
276
  if ('types' in modulePackageJson || 'typings' in modulePackageJson) {
301
277
  return filteredDependencies;
302
278
  }
303
279
  } catch (err) {}
304
-
305
280
  let indexDTsPath;
306
-
307
281
  try {
308
282
  indexDTsPath = require.resolve(`${dependency}/index.d.ts`);
309
- } catch (err) {} // If the index.d.ts file exists inside the module's directory, bail out
310
-
311
-
283
+ } catch (err) {}
284
+ // If the index.d.ts file exists inside the module's directory, bail out
312
285
  if (indexDTsPath && existsSync(indexDTsPath)) {
313
286
  return filteredDependencies;
314
287
  }
315
-
316
288
  const results = await index.getObject(dependency, {
317
289
  attributesToRetrieve: ['types']
318
290
  });
319
291
  const definitelyTypedPackageName = (_results$types = results.types) == null ? void 0 : _results$types.definitelyTyped;
320
-
321
292
  if (definitelyTypedPackageName) {
322
293
  // If a matching `@types/<package name>` has been already installed in devDependencies, bail out
323
294
  if (Object.keys(devDependencies).includes(definitelyTypedPackageName)) {
324
295
  return filteredDependencies;
325
296
  }
326
-
327
297
  filteredDependencies.push([dependency, definitelyTypedPackageName]);
328
298
  }
329
-
330
299
  return filteredDependencies;
331
300
  }, []);
332
-
333
301
  if (dependenciesWithMissingTypes.length > 0) {
334
302
  throw new Error(`Dependencies found without types. Add the missing types with:
335
303
 
336
- ${commandExample(`yarn add --dev ${dependenciesWithMissingTypes.map(([, definitelyTypedPackageName]) => definitelyTypedPackageName).join(' ')}`)}
304
+ ${commandExample(`pnpm add --save-dev ${dependenciesWithMissingTypes.map(([, definitelyTypedPackageName]) => definitelyTypedPackageName).join(' ')}`)}
337
305
 
338
- If the dependencies above are already in your package.json, check that they have not been incorrectly installed as regular dependencies in the "dependencies" object - they should be installed inside "devDependencies" (using the --dev flag mentioned above). To fix this situation, remove the dependencies and run the command above exactly.
306
+ If the dependencies above are already in your package.json, check that they have not been incorrectly installed as regular dependencies in the "dependencies" object - they should be installed inside "devDependencies" (using the --save-dev flag mentioned above). To fix this situation, remove the dependencies and run the command above exactly.
339
307
  `);
340
308
  }
341
309
  }
342
310
 
343
311
  const {
344
312
  stdout: preflightBinPath
345
- } = await /*#__PURE__*/execaCommand(`yarn bin`, {
313
+ } = await /*#__PURE__*/execaCommand(`pnpm bin`, {
346
314
  cwd: /*#__PURE__*/dirname( /*#__PURE__*/fileURLToPath(import.meta.url))
347
315
  });
348
316
 
349
317
  const title$7 = 'No unused dependencies';
350
318
  async function noUnusedAndMissingDependencies() {
351
- const ignoredPackagePatterns = [// Unused dependency detected in https://github.com/upleveled/next-portfolio-dev
352
- '@graphql-codegen/cli', // Unused dependency detected in create-react-app
353
- '@testing-library/user-event', // Tailwind CSS
354
- '@tailwindcss/jit', 'autoprefixer', 'postcss', 'tailwindcss', // Sass (eg. in create-react-app)
355
- 'sass', // ESLint configuration
356
- 'babel-eslint', 'eslint-config-next', // TODO: Remove this once depcheck issue is fixed:
319
+ const ignoredPackagePatterns = [
320
+ // Unused dependency detected in https://github.com/upleveled/next-portfolio-dev
321
+ '@graphql-codegen/cli',
322
+ // Unused dependency detected in create-react-app
323
+ '@testing-library/user-event',
324
+ // Tailwind CSS
325
+ '@tailwindcss/jit', 'autoprefixer', 'postcss', 'tailwindcss',
326
+ // Sass (eg. in create-react-app)
327
+ 'sass',
328
+ // ESLint configuration
329
+ 'babel-eslint', 'eslint-config-next',
330
+ // TODO: Remove this once depcheck issue is fixed:
357
331
  // PR: https://github.com/depcheck/depcheck/pull/790
358
332
  // Issue: https://github.com/depcheck/depcheck/issues/791
359
333
  //
360
334
  // Stylelint configuration
361
- 'stylelint', 'stylelint-config-upleveled', // Testing
362
- 'babel-jest', 'cypress', 'jest', 'jest-environment-jsdom', 'jest-puppeteer', 'playwright', 'puppeteer', // TypeScript
363
- 'typescript', '@types/*', // Next.js
335
+ 'stylelint', 'stylelint-config-upleveled',
336
+ // Testing
337
+ 'babel-jest', 'cypress', 'jest', 'jest-environment-jsdom', 'jest-puppeteer', 'playwright', 'puppeteer',
338
+ // TypeScript
339
+ 'typescript', '@types/*',
340
+ // Next.js
364
341
  'sharp'].join(',');
365
-
366
342
  try {
367
343
  await execaCommand(`${preflightBinPath}/depcheck --ignores="${ignoredPackagePatterns}"`);
368
344
  } catch (error) {
369
345
  const {
370
346
  stdout
371
347
  } = error;
372
-
373
348
  if (!stdout.startsWith('Unused dependencies') && !stdout.startsWith('Unused devDependencies') && !stdout.startsWith('Missing dependencies')) {
374
349
  throw error;
375
350
  }
376
-
377
351
  const [unusedDependenciesStdout, missingDependenciesStdout] = stdout.split('Missing dependencies');
378
352
  const messages = [];
379
-
380
353
  if (unusedDependenciesStdout) {
381
354
  messages.push(`Unused dependencies found:
382
355
  ${unusedDependenciesStdout.split('\n').filter(str => str.includes('* ')).join('\n')}
@@ -386,7 +359,6 @@ async function noUnusedAndMissingDependencies() {
386
359
  ${commandExample('pnpm remove <dependency name here>')}
387
360
  `);
388
361
  }
389
-
390
362
  if (missingDependenciesStdout) {
391
363
  /**
392
364
  * Temporary workaround to filter out eslint-config-upleveled peer dependencies
@@ -398,7 +370,6 @@ async function noUnusedAndMissingDependencies() {
398
370
  const missingDependenciesStdoutFiltered = missingDependenciesStdout.split('\n').filter(missingDependency => {
399
371
  return !(missingDependency.includes('./.eslintrc.cjs') && ['@next/eslint-plugin-next', '@typescript-eslint/eslint-plugin', '@typescript-eslint/parser', 'eslint-plugin-upleveled', 'eslint-config-react-app', 'eslint-import-resolver-typescript', 'eslint-plugin-import', 'eslint-plugin-jsx-a11y', 'eslint-plugin-jsx-expressions', 'eslint-plugin-react-hooks', 'eslint-plugin-react', 'eslint-plugin-security', 'eslint-plugin-sonarjs', 'eslint-plugin-unicorn'].some(excludedDependency => missingDependency.includes(excludedDependency)));
400
372
  }).join('\n');
401
-
402
373
  if (missingDependenciesStdoutFiltered) {
403
374
  messages.push(`Missing dependencies found:
404
375
  ${missingDependenciesStdoutFiltered.split('\n').filter(str => str.includes('* ')).join('\n')}
@@ -409,7 +380,6 @@ async function noUnusedAndMissingDependencies() {
409
380
  `);
410
381
  }
411
382
  }
412
-
413
383
  if (messages.length > 0) throw new Error(messages.join('\n\n'));
414
384
  }
415
385
  }
@@ -419,7 +389,6 @@ async function noExtraneousFilesCommittedToGit() {
419
389
  const {
420
390
  stdout
421
391
  } = await execaCommand('git ls-files .DS_Store yarn-error.log npm-debug.log');
422
-
423
392
  if (stdout !== '') {
424
393
  throw new Error(`Extraneous files committed to Git:
425
394
  ${stdout}
@@ -444,7 +413,6 @@ async function noSecretsCommittedToGit() {
444
413
  const {
445
414
  stdout
446
415
  } = await execaCommand('git ls-files .env .env*.local');
447
-
448
416
  if (stdout !== '') {
449
417
  throw new Error(`Secrets committed to Git 😱:
450
418
  ${stdout}
@@ -475,11 +443,10 @@ async function preflightIsLatestVersion() {
475
443
  const {
476
444
  stdout: remoteVersion
477
445
  } = await execaCommand('npm show @upleveled/preflight version');
478
-
479
446
  if (semver.gt(remoteVersion, preflightPackageJson.version)) {
480
447
  throw new Error(`Your current version of Preflight (${preflightPackageJson.version}) is older than the latest version ${remoteVersion} - upgrade with:
481
448
 
482
- ${commandExample(`${os.platform() === 'linux' ? 'sudo ' : ''}yarn global add @upleveled/preflight`)}
449
+ ${commandExample(`${os.platform() === 'linux' ? 'sudo ' : ''}pnpm add --global @upleveled/preflight`)}
483
450
  `);
484
451
  }
485
452
  }
@@ -493,7 +460,7 @@ var preflightIsLatestVersion$1 = {
493
460
  const title$b = 'Prettier';
494
461
  async function prettierCheck() {
495
462
  try {
496
- await execaCommand(`yarn --silent prettier --list-different ${process.cwd()}/**/*.{js,jsx,ts,jsx} --ignore-path ${process.cwd()}/.eslintignore --config ${process.cwd()}/prettier.config.cjs --end-of-line auto`, {
463
+ await execaCommand(`../node_modules/.bin/prettier --list-different ${process.cwd()}/**/*.{js,jsx,ts,jsx} --ignore-path ${process.cwd()}/.eslintignore --config ${process.cwd()}/prettier.config.cjs --end-of-line auto`, {
497
464
  cwd: dirname(fileURLToPath(import.meta.url))
498
465
  });
499
466
  } catch (error) {
@@ -502,22 +469,25 @@ async function prettierCheck() {
502
469
  stderr
503
470
  } = error;
504
471
  const stderrWithoutPackageJsonWarning = stderr.replace(/warning [/.\\]*package\.json: No license field[\r\n]*/g, '');
505
-
506
472
  if (!stdout || stderrWithoutPackageJsonWarning) {
507
473
  throw error;
508
474
  }
509
-
510
- const ignoredFilePatterns = [// File from create-react-app not matching our formatting
475
+ const ignoredFilePatterns = [
476
+ // File from create-react-app not matching our formatting
511
477
  // https://github.com/facebook/create-react-app/blob/fdbde1f3c256b43d5386b5ae3a75083dbd8f0aff/packages/cra-template/template/src/reportWebVitals.js
512
- /src[/\\]reportWebVitals\.js$/, // Files from create-next-app not matching our formatting
478
+ /src[/\\]reportWebVitals\.js$/,
479
+ // Files from create-next-app not matching our formatting
513
480
  // https://github.com/vercel/next.js/blob/5a73859fe8d3659d52e169f97188ae422e0d2ace/packages/create-next-app/templates/default/pages/api/hello.js
514
- /pages[/\\]api[/\\]hello\.js$/, // https://github.com/vercel/next.js/blob/aacfa79ddf88e4e1488b63ac253af27110f0a151/packages/create-next-app/templates/default/pages/_app.js
481
+ /pages[/\\]api[/\\]hello\.js$/,
482
+ // https://github.com/vercel/next.js/blob/aacfa79ddf88e4e1488b63ac253af27110f0a151/packages/create-next-app/templates/default/pages/_app.js
515
483
  /pages[/\\]_app\.js$/];
516
- const unformattedFiles = normalizeNewline(stdout).split('\n').map(file => // Make paths relative to the project instead of Preflight, eg:
484
+ const unformattedFiles = normalizeNewline(stdout).split('\n')
485
+ // Make paths relative to the project instead of Preflight, eg:
517
486
  // before: ../../../../../../projects/random-color-generator-react-app/src/reportWebVitals.js
518
- // after: random-color-generator-react-app/src/reportWebVitals.js
519
- file.replace(/^([A-Z]:|\.\.[/\\])[a-zA-Z0-9-_/.\\ ]*projects[/\\]/, '')).filter(file => !ignoredFilePatterns.some(ignoredFilePattern => ignoredFilePattern.test(file)));
520
-
487
+ // after: src/reportWebVitals.js
488
+ .map(file => {
489
+ return file.replace(`${relative(dirname(fileURLToPath(import.meta.url)), process.cwd())}${sep}`, '');
490
+ }).filter(file => !ignoredFilePatterns.some(ignoredFilePattern => ignoredFilePattern.test(file)));
521
491
  if (unformattedFiles.length > 0) {
522
492
  throw new Error(`Prettier has not been run in the following files:
523
493
  ${unformattedFiles.join('\n')}
@@ -540,7 +510,6 @@ const title$c = 'Project folder name matches correct format';
540
510
  function projectFolderNameMatchesCorrectFormat() {
541
511
  const currentDirectoryName = path.basename(process.cwd());
542
512
  const lowercaseHyphenedDirectoryName = currentDirectoryName.toLowerCase().replaceAll(' ', '-');
543
-
544
513
  if (currentDirectoryName !== lowercaseHyphenedDirectoryName) {
545
514
  throw new Error(`Project directory name "${currentDirectoryName}" doesn't match the correct format (no spaces or uppercase letters).
546
515
 
@@ -559,14 +528,111 @@ var projectFolderNameMatchesCorrectFormat$1 = {
559
528
  'default': projectFolderNameMatchesCorrectFormat
560
529
  };
561
530
 
562
- const title$d = 'Use single package manager';
531
+ const errorKey$1 = 'errored';
532
+ const supportedStylelintFileExtensions = ['css', 'sass', 'scss', 'less', 'js', 'tsx', 'jsx'];
533
+ const title$d = 'Stylelint';
534
+ async function stylelintCheck() {
535
+ try {
536
+ await execaCommand(`pnpm stylelint **/*.{${supportedStylelintFileExtensions.join(',')}} --max-warnings 0 --formatter json`);
537
+ } catch (error) {
538
+ const {
539
+ stdout
540
+ } = error;
541
+ // If no Stylelint errors detected, throw the error
542
+ if (!new RegExp(`"${errorKey$1}":`).test(stdout)) {
543
+ throw error;
544
+ }
545
+ throw new Error(`Stylelint problems found in the following files:
546
+ ${JSON.parse(stdout).filter(stylelintResult => stylelintResult[errorKey$1] === true)
547
+ // Make paths relative to the project:
548
+ // before: /home/projects/random-color-generator-react-app/src/index.css
549
+ // after: src/index.css
550
+ .map(({
551
+ source
552
+ }) => source.replace(`${process.cwd()}${sep}`, '')).join('\n')}
553
+
554
+ Open these files in your editor - there should be problems to fix
555
+ `);
556
+ }
557
+ }
558
+
559
+ var stylelint = {
560
+ __proto__: null,
561
+ supportedStylelintFileExtensions: supportedStylelintFileExtensions,
562
+ title: title$d,
563
+ 'default': stylelintCheck
564
+ };
565
+
566
+ const require$2 = /*#__PURE__*/createRequire(`${/*#__PURE__*/process.cwd()}/`);
567
+ const title$e = 'Stylelint config is latest version';
568
+ async function stylelintConfigIsValid() {
569
+ const {
570
+ stdout: remoteVersion
571
+ } = await execaCommand('npm show stylelint-config-upleveled version');
572
+ let localVersion;
573
+ try {
574
+ const stylelintConfigPackageJsonPath = require$2.resolve('stylelint-config-upleveled/package.json');
575
+ localVersion = JSON.parse(await promises.readFile(stylelintConfigPackageJsonPath, 'utf-8')).version;
576
+ } catch (err) {}
577
+ if (typeof localVersion === 'undefined') {
578
+ throw new Error(`The UpLeveled Stylelint Config has not been installed - please install using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
579
+ `);
580
+ }
581
+ if (semver.gt(remoteVersion, localVersion)) {
582
+ throw new Error(`Your current version of the UpLeveled Stylelint Config (${localVersion}) is older than the latest version ${remoteVersion} - upgrade by running:
583
+
584
+ pnpm add stylelint-config-upleveled@${remoteVersion}`);
585
+ }
586
+ let stylelintConfigMatches;
587
+ try {
588
+ stylelintConfigMatches = (await promises.readFile('./stylelint.config.cjs', 'utf-8')).trim() === `/** @type { import('stylelint').Config } */
589
+ const config = {
590
+ extends: ['stylelint-config-upleveled'],
591
+ };
592
+
593
+ module.exports = config;`;
594
+ } catch (err) {
595
+ throw new Error(`Error reading your stylelint.config.cjs file - please delete the file if it exists and reinstall the config using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
596
+ `);
597
+ }
598
+ if (!stylelintConfigMatches) {
599
+ throw new Error(`Your stylelint.config.cjs file does not match the configuration file template - please delete the file and reinstall the config using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
600
+ `);
601
+ }
602
+ const stylelintDisableOccurrences = [];
603
+ for await (const {
604
+ path
605
+ } of readdirp('.', {
606
+ directoryFilter: ['!.git', '!.next', '!node_modules'],
607
+ fileFilter: supportedStylelintFileExtensions.map(fileExtension => `*.${fileExtension}`)
608
+ })) {
609
+ const fileContents = await promises.readFile(path, 'utf-8');
610
+ if (fileContents.includes('stylelint-disable')) {
611
+ stylelintDisableOccurrences.push(path);
612
+ }
613
+ }
614
+ if (stylelintDisableOccurrences.length > 0) {
615
+ throw new Error(`Stylelint has been disabled in the following files:
616
+ ${stylelintDisableOccurrences.join('\n')}
617
+
618
+ Remove all comments disabling or modifying Stylelint rule configuration (eg. stylelint-disable and stylelint-disable-next-line comments) and fix the problems
619
+ `);
620
+ }
621
+ }
622
+
623
+ var stylelintConfigIsValid$1 = {
624
+ __proto__: null,
625
+ title: title$e,
626
+ 'default': stylelintConfigIsValid
627
+ };
628
+
629
+ const title$f = 'Use single package manager';
563
630
  async function useSinglePackageManager() {
564
631
  const {
565
632
  stdout
566
- } = await execaCommand('git ls-files package-lock.json');
567
-
633
+ } = await execaCommand('git ls-files package-lock.json yarn.lock');
568
634
  if (stdout !== '') {
569
- throw new Error(`package-lock.json file committed to Git. Remove it with:
635
+ throw new Error(`package-lock.json or yarn.lock file committed to Git. Remove it with:
570
636
 
571
637
  ${commandExample('git rm --cached <filename>')}
572
638
 
@@ -574,29 +640,34 @@ async function useSinglePackageManager() {
574
640
 
575
641
  ${commandExample('rm <filename>')}
576
642
 
577
- The presence of this file indicates that npm was incorrectly used in addition to Yarn (eg. an "npm install" command was run). In order to avoid issues with the state of the yarn.lock file, we suggest also forcing regeneration this file with the following command:
643
+ The presence of this file indicates that another package manager was used in addition to pnpm (eg. "npm install" or "yarn add" was run). In order to avoid issues with the state of the pnpm-lock.yaml file, we suggest also forcing regeneration this file with the following command:
578
644
 
579
- ${commandExample('yarn --force')}
645
+ ${commandExample('pnpm install --force')}
580
646
  `);
581
647
  }
582
648
  }
583
649
 
584
650
  var useSinglePackageManager$1 = {
585
651
  __proto__: null,
586
- title: title$d,
652
+ title: title$f,
587
653
  'default': useSinglePackageManager
588
654
  };
589
655
 
656
+ const projectDependencies = projectPackageJson.dependencies || {};
590
657
  console.log(`🚀 UpLeveled Preflight v${preflightPackageJson.version}`);
591
- const listrTasks = /*#__PURE__*/[// ======= Sync Tasks =======
658
+ const listrTasks = /*#__PURE__*/[
659
+ // ======= Sync Tasks =======
592
660
  // Git
593
- allChangesCommittedToGit$1, nodeModulesIgnoredFromGit$1, noExtraneousFilesCommittedToGit$1, noSecretsCommittedToGit$1, // Package managers
594
- useSinglePackageManager$1, // Project setup
595
- projectFolderNameMatchesCorrectFormat$1, // ======= Async Tasks =======
661
+ allChangesCommittedToGit$1, nodeModulesIgnoredFromGit$1, noExtraneousFilesCommittedToGit$1, noSecretsCommittedToGit$1,
662
+ // Package managers
663
+ useSinglePackageManager$1,
664
+ // Project setup
665
+ projectFolderNameMatchesCorrectFormat$1,
666
+ // ======= Async Tasks =======
596
667
  // Dependencies
597
668
  {
598
669
  title: 'No dependency problems',
599
- task: (ctx, task) => task.newListr([...(!projectPackageJson.dependencies || !Object.keys(projectPackageJson.dependencies).includes('next') ? [] : [{
670
+ task: (ctx, task) => task.newListr([...(!Object.keys(projectDependencies).includes('next') ? [] : [{
600
671
  title: title$5,
601
672
  task: nextJsProjectHasSharpInstalled
602
673
  }]), {
@@ -606,10 +677,13 @@ projectFolderNameMatchesCorrectFormat$1, // ======= Async Tasks =======
606
677
  title: title$6,
607
678
  task: noDependenciesWithoutTypes
608
679
  }])
609
- }, // GitHub
610
- linkOnGithubAbout$1, // Linting
611
- eslint, prettier, // Version and configuration checks
612
- eslintConfigIsValid$1, preflightIsLatestVersion$1].map(module => {
680
+ },
681
+ // GitHub
682
+ linkOnGithubAbout$1,
683
+ // Linting
684
+ eslint, ...(!('@upleveled/react-scripts' in projectDependencies || 'next' in projectDependencies) ? [] : [stylelint]), prettier,
685
+ // Version and configuration checks
686
+ eslintConfigIsValid$1, ...(!('@upleveled/react-scripts' in projectDependencies || 'next' in projectDependencies) ? [] : [stylelintConfigIsValid$1]), preflightIsLatestVersion$1].map(module => {
613
687
  if ('task' in module) return module;
614
688
  return {
615
689
  title: module.title,