@upleveled/preflight 7.0.9 → 8.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 (44) hide show
  1. package/bin/preflight.ts +3 -0
  2. package/package.json +21 -40
  3. package/src/checks/allChangesCommittedToGit.ts +2 -2
  4. package/src/checks/eslint.ts +1 -1
  5. package/src/checks/eslintConfigIsValid.ts +3 -2
  6. package/src/checks/linkOnGithubAbout.ts +1 -1
  7. package/src/checks/noDependencyProblems/noDependenciesWithoutTypes.ts +2 -2
  8. package/src/checks/noDependencyProblems/noUnusedDependencies.ts +2 -2
  9. package/src/checks/noExtraneousFilesCommittedToGit.ts +1 -1
  10. package/src/checks/noSecretsCommittedToGit.ts +1 -1
  11. package/src/checks/nodeModulesIgnoredFromGit.ts +2 -2
  12. package/src/checks/preflightIsLatestVersion.ts +2 -2
  13. package/src/checks/prettier.ts +1 -1
  14. package/src/checks/projectFolderNameMatchesCorrectFormat.ts +1 -1
  15. package/src/checks/stylelint.ts +1 -1
  16. package/src/checks/stylelintConfigIsValid.ts +7 -5
  17. package/src/checks/useSinglePackageManager.ts +1 -1
  18. package/src/index.ts +19 -19
  19. package/src/util/packageJson.ts +1 -1
  20. package/bin/preflight.js +0 -3
  21. package/dist/checks/allChangesCommittedToGit.d.ts +0 -2
  22. package/dist/checks/eslint.d.ts +0 -2
  23. package/dist/checks/eslintConfigIsValid.d.ts +0 -2
  24. package/dist/checks/linkOnGithubAbout.d.ts +0 -2
  25. package/dist/checks/noDependencyProblems/noDependenciesWithoutTypes.d.ts +0 -2
  26. package/dist/checks/noDependencyProblems/noUnusedDependencies.d.ts +0 -2
  27. package/dist/checks/noExtraneousFilesCommittedToGit.d.ts +0 -2
  28. package/dist/checks/noSecretsCommittedToGit.d.ts +0 -2
  29. package/dist/checks/nodeModulesIgnoredFromGit.d.ts +0 -2
  30. package/dist/checks/preflightIsLatestVersion.d.ts +0 -2
  31. package/dist/checks/prettier.d.ts +0 -2
  32. package/dist/checks/projectFolderNameMatchesCorrectFormat.d.ts +0 -2
  33. package/dist/checks/stylelint.d.ts +0 -3
  34. package/dist/checks/stylelintConfigIsValid.d.ts +0 -2
  35. package/dist/checks/useSinglePackageManager.d.ts +0 -2
  36. package/dist/index.d.ts +0 -1
  37. package/dist/preflight.esm.js +0 -722
  38. package/dist/preflight.esm.js.map +0 -1
  39. package/dist/util/commandExample.d.ts +0 -1
  40. package/dist/util/crossPlatform.d.ts +0 -1
  41. package/dist/util/drone.d.ts +0 -1
  42. package/dist/util/packageJson.d.ts +0 -53
  43. package/dist/util/preflightBinPath.d.ts +0 -1
  44. package/dist/util/randomUserAgent.d.ts +0 -1
@@ -1,722 +0,0 @@
1
- import { Listr } from 'listr2';
2
- import { promises, existsSync } from 'node:fs';
3
- import { execa } from 'execa';
4
- import chalk from 'chalk';
5
- import path, { sep, dirname } from 'node:path';
6
- import { createRequire } from 'node:module';
7
- import readdirp from 'readdirp';
8
- import semver from 'semver';
9
- import { load } from 'cheerio';
10
- import fetch from 'node-fetch';
11
- import userAgents from 'top-user-agents';
12
- import { algoliasearch } from 'algoliasearch';
13
- import pReduce from 'p-reduce';
14
- import { URL, fileURLToPath } from 'node:url';
15
- import os from 'node:os';
16
-
17
- // https://www.compart.com/en/unicode/U+2800
18
- // eslint-disable-next-line security/detect-bidi-characters -- Intentional use of unusual character for formatting
19
- const emptyBrailleCharacter = '‎';
20
- function commandExample(command) {
21
- return `${emptyBrailleCharacter} ${chalk.dim('$')} ${command}`;
22
- }
23
-
24
- async function isDrone() {
25
- const {
26
- stdout
27
- } = await execa({
28
- reject: false
29
- })`cat /etc/os-release`;
30
- return /Alpine Linux/.test(stdout);
31
- }
32
-
33
- const title = 'All changes committed to Git';
34
- async function allChangesCommittedToGit() {
35
- const {
36
- stdout: replSlug
37
- } = await execa`echo $REPL_SLUG`;
38
- const isRunningInReplIt = replSlug !== '';
39
- if (isRunningInReplIt) {
40
- await promises.writeFile('.git/info/exclude', '.replit\n');
41
- }
42
- const {
43
- stdout
44
- } = await execa`git status --porcelain`;
45
- if (stdout !== '') {
46
- const onlyPnpmLockModifiedOnDrone = stdout.trim() === 'M pnpm-lock.yaml' && (await isDrone());
47
- throw new Error(`Some changes have not been committed to Git:
48
- ${stdout}${onlyPnpmLockModifiedOnDrone ? `
49
-
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:
51
-
52
- ${commandExample('pnpm install --force')}` : ''}
53
- `);
54
- }
55
- }
56
-
57
- var allChangesCommittedToGit$1 = {
58
- __proto__: null,
59
- title: title,
60
- 'default': allChangesCommittedToGit
61
- };
62
-
63
- const title$1 = 'ESLint';
64
- async function eslintCheck() {
65
- try {
66
- await execa({
67
- // Execute binaries in ./node_modules/.bin to avoid pnpm overhead
68
- // https://github.com/sindresorhus/execa/blob/main/docs/environment.md#local-binaries
69
- preferLocal: true
70
- })`eslint . --max-warnings 0 --format json`;
71
- } catch (error) {
72
- const {
73
- stdout
74
- } = error;
75
- let eslintResults;
76
- try {
77
- eslintResults = JSON.parse(stdout)
78
- // Filter out results with no problems, which the ESLint CLI
79
- // still reports with the `--format json` flag
80
- .filter(eslintResult => {
81
- return eslintResult.errorCount > 0 || eslintResult.warningCount > 0;
82
- });
83
- } catch {
84
- throw error;
85
- }
86
- if (eslintResults.length < 1 || !eslintResults.every(result => 'errorCount' in result && 'warningCount' in result)) {
87
- throw new Error(`Unexpected shape of ESLint JSON related to .errorCount and .warningCount properties - please report this to the UpLeveled engineering team, including the following output:
88
- ${stdout}
89
- `);
90
- }
91
- throw new Error(`ESLint problems found in the following files:
92
- ${eslintResults
93
- // Make paths relative to the project:
94
- //
95
- // Before:
96
- // macOS / Linux: /home/projects/next-student-project/app/api/hello/route.js
97
- // Windows: C:\Users\Lukas\projects\next-student-project\app\api\hello\route.js
98
- //
99
- // After:
100
- // macOS / Linux: app/api/hello/route.js
101
- // Windows: app\api\hello\route.js
102
- .map(({
103
- filePath
104
- }) => filePath.replace(`${process.cwd()}${sep}`, '')).join('\n')}
105
-
106
- Open these files in your editor - there should be problems to fix
107
- `);
108
- }
109
- }
110
-
111
- var eslint = {
112
- __proto__: null,
113
- title: title$1,
114
- 'default': eslintCheck
115
- };
116
-
117
- const require$1 = /*#__PURE__*/createRequire(`${/*#__PURE__*/process.cwd()}/`);
118
- const title$2 = 'ESLint config is latest version';
119
- async function eslintConfigIsValid() {
120
- const {
121
- stdout: remoteVersion
122
- } = await execa`npm show eslint-config-upleveled version`;
123
- let localVersion;
124
- try {
125
- const eslintConfigPackageJsonPath = require$1.resolve('eslint-config-upleveled/package.json');
126
- localVersion =
127
- // Type assertion because we swallow the error anyway if
128
- // the .version property doesn't exist
129
- JSON.parse(await promises.readFile(eslintConfigPackageJsonPath, 'utf-8')).version;
130
- } catch {
131
- // Swallow error
132
- }
133
- if (typeof localVersion === 'undefined') {
134
- 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
135
- `);
136
- }
137
- if (semver.gt(remoteVersion, localVersion)) {
138
- 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
139
- `);
140
- }
141
- let eslintConfigMatches;
142
- try {
143
- eslintConfigMatches = (await promises.readFile('./eslint.config.js', 'utf-8')).trim() === "export { default } from 'eslint-config-upleveled';";
144
- } catch {
145
- throw new Error(`Error reading your eslint.config.js file - please delete the file if it exists and reinstall the config using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
146
- `);
147
- }
148
- if (!eslintConfigMatches) {
149
- throw new Error(`Your eslint.config.js 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
150
- `);
151
- }
152
- const eslintDisableOccurrences = [];
153
- for await (const {
154
- path
155
- } of readdirp('.', {
156
- directoryFilter: ['!.git', '!.next', '!node_modules'],
157
- fileFilter: ['*.js', '*.jsx', '*.ts', '*.tsx']
158
- })) {
159
- const fileContents = await promises.readFile(path, 'utf-8');
160
- if (/eslint-disable|eslint [a-z0-9@/-]+: (0|off)/.test(fileContents)) {
161
- eslintDisableOccurrences.push(path);
162
- }
163
- }
164
- if (eslintDisableOccurrences.length > 0) {
165
- throw new Error(`ESLint has been disabled in the following files:
166
- ${eslintDisableOccurrences.join('\n')}
167
-
168
- Remove all comments disabling or modifying ESLint rule configuration (eg. eslint-disable and eslint-disable-next-line comments) and fix the problems
169
- `);
170
- }
171
- }
172
-
173
- var eslintConfigIsValid$1 = {
174
- __proto__: null,
175
- title: title$2,
176
- 'default': eslintConfigIsValid
177
- };
178
-
179
- function randomUserAgent() {
180
- const randomIndex = Math.floor(Math.random() * (userAgents.length - 1));
181
- return userAgents[randomIndex];
182
- }
183
-
184
- const title$3 = 'GitHub repo has deployed project link under About';
185
- async function linkOnGithubAbout() {
186
- const {
187
- stdout
188
- } = await execa`git remote get-url origin`;
189
- const repoUrl = stdout.replace('git@github.com:', 'https://github.com/').replace('.git', '');
190
- const html = await (await fetch(repoUrl)).text();
191
- const $ = load(html);
192
- const urlInAboutSection = $('h2').filter(function () {
193
- return $(this).text().trim() === 'About';
194
- }).nextAll('div').filter(function () {
195
- return $(this).children('.octicon.octicon-link').length > 0;
196
- }).children('.octicon.octicon-link').next().children('a[href]').attr('href');
197
- if (!urlInAboutSection) {
198
- 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.`);
199
- }
200
- const response = await fetch(urlInAboutSection, {
201
- headers: {
202
- // For repl.it
203
- 'user-agent': randomUserAgent()
204
- }
205
- });
206
- if (!response.ok) {
207
- 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}).`);
208
- }
209
- }
210
-
211
- var linkOnGithubAbout$1 = {
212
- __proto__: null,
213
- title: title$3,
214
- 'default': linkOnGithubAbout
215
- };
216
-
217
- const CRLF = '\r\n';
218
- function normalizeNewlines(input) {
219
- if (typeof input !== 'string') {
220
- throw new TypeError(`Expected a \`string\`, got \`${typeof input}\``);
221
- }
222
- return input.replace(new RegExp(CRLF, 'g'), '\n');
223
- }
224
-
225
- const title$4 = 'node_modules/ folder ignored in Git';
226
- async function nodeModulesIgnoredFromGit() {
227
- if ((await execa`git ls-files node_modules/`).stdout !== '') {
228
- throw new Error(`node_modules/ folder committed to Git. Remove it using:
229
-
230
- ${commandExample('git rm -r --cached node_modules')}
231
- `);
232
- }
233
- if ((await execa`git ls-files .gitignore`).stdout !== '.gitignore') {
234
- throw new Error('.gitignore file not found');
235
- }
236
- const nodeModulesInGitignore = normalizeNewlines(await promises.readFile('./.gitignore', 'utf8')).split('\n').reduce((found, line) => found || /^\/?node_modules\/?$/.test(line), false);
237
- if (!nodeModulesInGitignore) {
238
- throw new Error('node_modules not found in .gitignore');
239
- }
240
- }
241
-
242
- var nodeModulesIgnoredFromGit$1 = {
243
- __proto__: null,
244
- title: title$4,
245
- 'default': nodeModulesIgnoredFromGit
246
- };
247
-
248
- const projectPackageJson = /*#__PURE__*/JSON.parse(await /*#__PURE__*/promises.readFile('package.json', 'utf-8'));
249
- const preflightPackageJson = /*#__PURE__*/JSON.parse(await /*#__PURE__*/promises.readFile( /*#__PURE__*/new URL('../package.json', import.meta.url), 'utf-8'));
250
-
251
- const client = /*#__PURE__*/algoliasearch(
252
- // Application ID and API key specific to UpLeveled
253
- // Preflight. Please don't use anywhere else without
254
- // asking Algolia's permission.
255
- 'OFCNCOG2CU',
256
- // Application ID
257
- 'ec73550aa8b2936dab436d4e02144784');
258
- const title$5 = 'No dependencies without types';
259
- // This is a naive check for matching @types/<pkg name> packages
260
- // that the student hasn't yet installed. It is not intended to
261
- // be an exhaustive check for any types for all packages.
262
- //
263
- // It attempts to address scenarios such as this with
264
- // `styled-components`:
265
- //
266
- // https://learn.upleveled.io/courses/btcmp-l-webfs-gen-0/modules/122-cheatsheet-css-in-js/#eslint-errors-with-styled-components
267
- async function noDependenciesWithoutTypes() {
268
- const dependenciesWithMissingTypes = await pReduce(Object.keys(projectPackageJson.dependencies || {}), async (filteredDependencies, dependency) => {
269
- var _results$types;
270
- try {
271
- const packageJsonPath = require.resolve(`${dependency}/package.json`);
272
- const modulePackageJson = JSON.parse(await promises.readFile(packageJsonPath, 'utf-8'));
273
- // If the keys "types" or "typings" are in the module's `package.json`, bail out
274
- if ('types' in modulePackageJson || 'typings' in modulePackageJson) {
275
- return filteredDependencies;
276
- }
277
- } catch {
278
- // Swallow error
279
- }
280
- let indexDTsPath;
281
- try {
282
- indexDTsPath = require.resolve(`${dependency}/index.d.ts`);
283
- } catch {
284
- // Swallow error
285
- }
286
- // If the index.d.ts file exists inside the module's directory, bail out
287
- if (indexDTsPath && existsSync(indexDTsPath)) {
288
- return filteredDependencies;
289
- }
290
- let results;
291
- try {
292
- results = await client.getObject({
293
- indexName: 'npm-search',
294
- objectID: dependency,
295
- attributesToRetrieve: ['types']
296
- });
297
- } catch (error) {
298
- // Show dependency name if Algolia's `client.getObject()` throws with an
299
- // error message (such as the error message "ObjectID does not exist"
300
- // when a package cannot be found in the index)
301
- throw new Error(`Algolia error for \`${dependency}\`: ${error.message}`);
302
- }
303
- const definitelyTypedPackageName = (_results$types = results.types) == null ? void 0 : _results$types.definitelyTyped;
304
- if (definitelyTypedPackageName) {
305
- // If a matching `@types/<package name>` has been already installed in devDependencies, bail out
306
- if (Object.keys(projectPackageJson.devDependencies || {}).includes(definitelyTypedPackageName)) {
307
- return filteredDependencies;
308
- }
309
- filteredDependencies.push([dependency, definitelyTypedPackageName]);
310
- }
311
- return filteredDependencies;
312
- }, []);
313
- if (dependenciesWithMissingTypes.length > 0) {
314
- throw new Error(`Dependencies found without types. Add the missing types with:
315
-
316
- ${commandExample(`pnpm add --save-dev ${dependenciesWithMissingTypes.map(([, definitelyTypedPackageName]) => definitelyTypedPackageName).join(' ')}`)}
317
-
318
- 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.
319
- `);
320
- }
321
- }
322
-
323
- const {
324
- stdout: preflightBinPath
325
- } = await /*#__PURE__*/execa({
326
- cwd: /*#__PURE__*/dirname( /*#__PURE__*/fileURLToPath(import.meta.url))
327
- })`pnpm bin`;
328
-
329
- const title$6 = 'No unused dependencies';
330
- async function noUnusedAndMissingDependencies() {
331
- const ignoredPackagePatterns = [
332
- // Unused dependency detected in https://github.com/upleveled/next-portfolio-dev
333
- '@graphql-codegen/cli',
334
- // Tailwind CSS
335
- '@tailwindcss/jit', 'autoprefixer', 'postcss', 'tailwindcss',
336
- // Sass (eg. in Next.js)
337
- 'sass',
338
- // Prettier and plugins
339
- 'prettier', 'prettier-plugin-*',
340
- // ESLint configuration
341
- '@ts-safeql/eslint-plugin', 'libpg-query',
342
- // TODO: Remove this once depcheck issue is fixed:
343
- // PR: https://github.com/depcheck/depcheck/pull/790
344
- // Issue: https://github.com/depcheck/depcheck/issues/791
345
- //
346
- // Stylelint configuration
347
- 'stylelint', 'stylelint-config-upleveled',
348
- // Testing
349
- '@testing-library/user-event', 'jest', 'jest-environment-jsdom', 'playwright',
350
- // `expect` required for proper types with `@testing-library/jest-dom` with `@jest/globals` and pnpm
351
- // https://github.com/testing-library/jest-dom/issues/123#issuecomment-1536828385
352
- // TODO: Remove when we switch from Jest to Vitest
353
- 'expect',
354
- // `ts-node` required for jest.config.ts
355
- // https://jestjs.io/docs/29.6/configuration#:~:text=To%20read%20TypeScript%20configuration%20files%20Jest%20requires%20ts%2Dnode.%20Make%20sure%20it%20is%20installed%20in%20your%20project
356
- // TODO: Remove when usage of tsx is allowed
357
- // https://github.com/jestjs/jest/issues/11989
358
- 'ts-node',
359
- // TypeScript
360
- 'typescript', '@types/*', 'tsx',
361
- // Next.js
362
- 'sharp'].join(',');
363
- try {
364
- await execa`${preflightBinPath}/depcheck --ignores="${ignoredPackagePatterns}"`;
365
- } catch (error) {
366
- const {
367
- stdout
368
- } = error;
369
- if (!stdout.startsWith('Unused dependencies') && !stdout.startsWith('Unused devDependencies') && !stdout.startsWith('Missing dependencies')) {
370
- throw error;
371
- }
372
- const [unusedDependenciesStdout, missingDependenciesStdout] = stdout.split('Missing dependencies');
373
- const messages = [];
374
- if (unusedDependenciesStdout) {
375
- messages.push(`Unused dependencies found:
376
- ${unusedDependenciesStdout.split('\n').filter(str => str.includes('* ')).join('\n')}
377
-
378
- Remove these dependencies by running the following command for each dependency:
379
-
380
- ${commandExample('pnpm remove <dependency name here>')}
381
- `);
382
- }
383
- if (missingDependenciesStdout) {
384
- messages.push(`Missing dependencies found:
385
- ${missingDependenciesStdout.split('\n').filter(str => str.includes('* ')).join('\n')}
386
-
387
- Add these missing dependencies by running the following command for each dependency:
388
-
389
- ${commandExample('pnpm add <dependency name here>')}
390
- `);
391
- }
392
- if (messages.length > 0) throw new Error(messages.join('\n\n'));
393
- }
394
- }
395
-
396
- const title$7 = 'No extraneous files committed to Git';
397
- async function noExtraneousFilesCommittedToGit() {
398
- const {
399
- stdout
400
- } = await execa`git ls-files .DS_Store yarn-error.log npm-debug.log`;
401
- if (stdout !== '') {
402
- throw new Error(`Extraneous files committed to Git:
403
- ${stdout}
404
-
405
- Remove these files from your repo by running the following command for each file:
406
-
407
- ${commandExample('git rm --cached <filename here>')}
408
-
409
- Once you've removed all files, make sure that it doesn't happen again by adding the filenames above to your .gitignore file.
410
- `);
411
- }
412
- }
413
-
414
- var noExtraneousFilesCommittedToGit$1 = {
415
- __proto__: null,
416
- title: title$7,
417
- 'default': noExtraneousFilesCommittedToGit
418
- };
419
-
420
- const title$8 = 'No secrets committed to Git';
421
- async function noSecretsCommittedToGit() {
422
- const {
423
- stdout
424
- } = await execa`git ls-files .env .env*.local`;
425
- if (stdout !== '') {
426
- throw new Error(`Secrets committed to Git 😱:
427
- ${stdout}
428
-
429
- Remove these files from your repo by installing BFG from the System Setup Guide (see Optional Software at the bottom) and running it on each of your files like this:
430
-
431
- ${commandExample('bfg --delete-files <filename here>')}
432
-
433
- Once you've done this for every secret file, then force push to your repository:
434
-
435
- ${commandExample('git push --force')}
436
-
437
- More info: https://docs.github.com/en/github/authenticating-to-github/removing-sensitive-data-from-a-repository
438
-
439
- Finally, make sure that this doesn't happen again by adding the filenames above to your .gitignore file.
440
- `);
441
- }
442
- }
443
-
444
- var noSecretsCommittedToGit$1 = {
445
- __proto__: null,
446
- title: title$8,
447
- 'default': noSecretsCommittedToGit
448
- };
449
-
450
- const title$9 = 'Preflight is latest version';
451
- async function preflightIsLatestVersion() {
452
- const {
453
- stdout: remoteVersion
454
- } = await execa`npm show @upleveled/preflight version`;
455
- if (semver.gt(remoteVersion, preflightPackageJson.version)) {
456
- throw new Error(`Your current version of Preflight (${preflightPackageJson.version}) is older than the latest version ${remoteVersion} - upgrade with:
457
-
458
- ${commandExample(`${os.platform() === 'linux' ? 'sudo ' : ''}pnpm add --global @upleveled/preflight`)}
459
- `);
460
- }
461
- }
462
-
463
- var preflightIsLatestVersion$1 = {
464
- __proto__: null,
465
- title: title$9,
466
- 'default': preflightIsLatestVersion
467
- };
468
-
469
- const title$a = 'Prettier';
470
- async function prettierCheck() {
471
- try {
472
- await execa({
473
- // Execute binaries in ./node_modules/.bin to avoid pnpm overhead
474
- // https://github.com/sindresorhus/execa/blob/main/docs/environment.md#local-binaries
475
- preferLocal: true
476
- })`prettier "**/*.{js,jsx,ts,tsx,css,scss,sql}" --list-different --end-of-line auto`;
477
- } catch (error) {
478
- const {
479
- stdout,
480
- stderr
481
- } = error;
482
- if (!stdout || stderr) {
483
- throw error;
484
- }
485
- const unformattedFiles = normalizeNewlines(stdout).split('\n');
486
- if (unformattedFiles.length > 0) {
487
- throw new Error(`Prettier has not been run in the following files:
488
- ${unformattedFiles.join('\n')}
489
-
490
- For each of the files above, open the file in your editor and save the file. This will format the file with Prettier, which will cause changes to appear in Git.
491
- `);
492
- }
493
- }
494
- }
495
-
496
- var prettier = {
497
- __proto__: null,
498
- title: title$a,
499
- 'default': prettierCheck
500
- };
501
-
502
- const title$b = 'Project folder name matches correct format';
503
- function projectFolderNameMatchesCorrectFormat() {
504
- const currentDirectoryName = path.basename(process.cwd());
505
- const lowercaseHyphenedDirectoryName = currentDirectoryName.toLowerCase().replaceAll(' ', '-');
506
- if (currentDirectoryName !== lowercaseHyphenedDirectoryName) {
507
- throw new Error(`Project directory name "${currentDirectoryName}" doesn't match the correct format (no spaces or uppercase letters).
508
-
509
- Rename the directory to the correct name "${lowercaseHyphenedDirectoryName}" with the following sequence of commands:
510
-
511
- ${commandExample('cd ..')}
512
- ${commandExample(`mv ${currentDirectoryName} ${lowercaseHyphenedDirectoryName}`)}
513
- ${commandExample(`cd ${lowercaseHyphenedDirectoryName}`)}
514
- `);
515
- }
516
- }
517
-
518
- var projectFolderNameMatchesCorrectFormat$1 = {
519
- __proto__: null,
520
- title: title$b,
521
- 'default': projectFolderNameMatchesCorrectFormat
522
- };
523
-
524
- const supportedStylelintFileExtensions = ['css', 'sass', 'scss', 'less', 'js', 'tsx', 'jsx'];
525
- const title$c = 'Stylelint';
526
- async function stylelintCheck() {
527
- try {
528
- await execa({
529
- // Execute binaries in ./node_modules/.bin to avoid pnpm overhead
530
- // https://github.com/sindresorhus/execa/blob/main/docs/environment.md#local-binaries
531
- preferLocal: true
532
- })`stylelint **/*.{${supportedStylelintFileExtensions.join(',')}} --max-warnings 0 --formatter json`;
533
- } catch (error) {
534
- const {
535
- stderr
536
- } = error;
537
- let stylelintResults;
538
- try {
539
- stylelintResults = JSON.parse(stderr).filter(stylelintResult => stylelintResult.errored === true);
540
- } catch {
541
- throw new Error(`Failed to parse Stylelint JSON output - please report this to the UpLeveled engineering team, including the following output:
542
-
543
- ${stderr}
544
- `);
545
- }
546
- if (stylelintResults.length < 1 || !stylelintResults.every(result => 'errored' in result)) {
547
- throw new Error(`Unexpected shape of Stylelint JSON related to .errored properties - please report this to the UpLeveled engineering team, including the following output:
548
- ${stderr}
549
- `);
550
- }
551
- throw new Error(`Stylelint problems found in the following files:
552
- ${stylelintResults
553
- // Make paths relative to the project:
554
- //
555
- // Before:
556
- // macOS / Linux: /home/projects/random-color-generator-react-app/src/index.css
557
- // Windows: C:\Users\Lukas\projects\random-color-generator-react-app\src\index.css
558
- //
559
- // After:
560
- // macOS / Linux: src/index.css
561
- // Windows: src\index.css
562
- .map(({
563
- source
564
- }) => source.replace(`${process.cwd()}${sep}`, '')).join('\n')}
565
-
566
- Open these files in your editor - there should be problems to fix
567
- `);
568
- }
569
- }
570
-
571
- var stylelint = {
572
- __proto__: null,
573
- supportedStylelintFileExtensions: supportedStylelintFileExtensions,
574
- title: title$c,
575
- 'default': stylelintCheck
576
- };
577
-
578
- const require$2 = /*#__PURE__*/createRequire(`${/*#__PURE__*/process.cwd()}/`);
579
- const title$d = 'Stylelint config is latest version';
580
- async function stylelintConfigIsValid() {
581
- const {
582
- stdout: remoteVersion
583
- } = await execa`npm show stylelint-config-upleveled version`;
584
- let localVersion;
585
- try {
586
- const stylelintConfigPackageJsonPath = require$2.resolve('stylelint-config-upleveled/package.json');
587
- localVersion =
588
- // Type assertion because we swallow the error anyway if
589
- // the .version property doesn't exist
590
- JSON.parse(await promises.readFile(stylelintConfigPackageJsonPath, 'utf-8')).version;
591
- } catch {
592
- // Swallow error
593
- }
594
- if (typeof localVersion === 'undefined') {
595
- 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
596
- `);
597
- }
598
- if (semver.gt(remoteVersion, localVersion)) {
599
- throw new Error(`Your current version of the UpLeveled Stylelint Config (${localVersion}) is older than the latest version ${remoteVersion} - upgrade by running:
600
-
601
- pnpm add stylelint-config-upleveled@${remoteVersion}`);
602
- }
603
- let stylelintConfigMatches;
604
- try {
605
- stylelintConfigMatches = (await promises.readFile('./stylelint.config.js', 'utf-8')).trim() === `/** @type {import('stylelint').Config} */
606
- const config = {
607
- extends: ['stylelint-config-upleveled'],
608
- };
609
-
610
- export default config;`;
611
- } catch {
612
- throw new Error(`Error reading your stylelint.config.js file - please delete the file if it exists and reinstall the config using the instructions on https://www.npmjs.com/package/eslint-config-upleveled
613
- `);
614
- }
615
- if (!stylelintConfigMatches) {
616
- throw new Error(`Your stylelint.config.js 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
617
- `);
618
- }
619
- const stylelintDisableOccurrences = [];
620
- for await (const {
621
- path
622
- } of readdirp('.', {
623
- directoryFilter: ['!.git', '!.next', '!node_modules'],
624
- fileFilter: supportedStylelintFileExtensions.map(fileExtension => `*.${fileExtension}`)
625
- })) {
626
- const fileContents = await promises.readFile(path, 'utf-8');
627
- if (fileContents.includes('stylelint-disable')) {
628
- stylelintDisableOccurrences.push(path);
629
- }
630
- }
631
- if (stylelintDisableOccurrences.length > 0) {
632
- throw new Error(`Stylelint has been disabled in the following files:
633
- ${stylelintDisableOccurrences.join('\n')}
634
-
635
- Remove all comments disabling or modifying Stylelint rule configuration (eg. stylelint-disable and stylelint-disable-next-line comments) and fix the problems
636
- `);
637
- }
638
- }
639
-
640
- var stylelintConfigIsValid$1 = {
641
- __proto__: null,
642
- title: title$d,
643
- 'default': stylelintConfigIsValid
644
- };
645
-
646
- const title$e = 'Use single package manager';
647
- async function useSinglePackageManager() {
648
- const {
649
- stdout
650
- } = await execa`git ls-files package-lock.json yarn.lock`;
651
- if (stdout !== '') {
652
- throw new Error(`package-lock.json or yarn.lock file committed to Git. Remove it with:
653
-
654
- ${commandExample('git rm --cached <filename>')}
655
-
656
- After you've removed it, you can delete the file with:
657
-
658
- ${commandExample('rm <filename>')}
659
-
660
- 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:
661
-
662
- ${commandExample('pnpm install --force')}
663
- `);
664
- }
665
- }
666
-
667
- var useSinglePackageManager$1 = {
668
- __proto__: null,
669
- title: title$e,
670
- 'default': useSinglePackageManager
671
- };
672
-
673
- const projectDependencies = projectPackageJson.dependencies || {};
674
- console.log(`🚀 UpLeveled Preflight v${preflightPackageJson.version}`);
675
- const listrTasks = /*#__PURE__*/[
676
- // ======= Sync Tasks =======
677
- // Git
678
- allChangesCommittedToGit$1, nodeModulesIgnoredFromGit$1, noExtraneousFilesCommittedToGit$1, noSecretsCommittedToGit$1,
679
- // Package managers
680
- useSinglePackageManager$1,
681
- // Project setup
682
- projectFolderNameMatchesCorrectFormat$1,
683
- // ======= Async Tasks =======
684
- // Dependencies
685
- {
686
- title: 'No dependency problems',
687
- task: (ctx, task) => task.newListr([{
688
- title: title$6,
689
- task: noUnusedAndMissingDependencies
690
- }, {
691
- title: title$5,
692
- task: noDependenciesWithoutTypes
693
- }])
694
- },
695
- // GitHub
696
- linkOnGithubAbout$1,
697
- // Linting
698
- eslint, ...(!('@upleveled/react-scripts' in projectDependencies || 'next' in projectDependencies) ? [] : [stylelint]), prettier,
699
- // Version and configuration checks
700
- eslintConfigIsValid$1, ...(!('@upleveled/react-scripts' in projectDependencies || 'next' in projectDependencies) ? [] : [stylelintConfigIsValid$1]), preflightIsLatestVersion$1].map(module => {
701
- if ('task' in module) return module;
702
- return {
703
- title: module.title,
704
- task: module.default
705
- };
706
- });
707
- const tasks = /*#__PURE__*/new Listr(listrTasks, {
708
- exitOnError: false,
709
- collectErrors: 'minimal',
710
- rendererOptions: {
711
- collapseErrors: false,
712
- removeEmptyLines: false,
713
- formatOutput: 'wrap'
714
- },
715
- fallbackRenderer: 'verbose',
716
- concurrent: 5
717
- });
718
- await tasks.run();
719
- if (tasks.errors.length > 0) {
720
- process.exit(1);
721
- }
722
- //# sourceMappingURL=preflight.esm.js.map