aberlaas 2.1.0 → 2.3.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 (51) hide show
  1. package/README.md +125 -20
  2. package/commands/ci/index.js +14 -99
  3. package/commands/compress/dummy.js +11 -0
  4. package/commands/compress/index.js +5 -14
  5. package/commands/compress/png.js +18 -9
  6. package/commands/init/index.js +23 -7
  7. package/commands/lint/css.js +27 -21
  8. package/commands/lint/helpers/prettier.js +40 -12
  9. package/commands/lint/index.js +1 -1
  10. package/commands/lint/js.js +3 -4
  11. package/commands/lint/json.js +6 -5
  12. package/commands/lint/yml.js +6 -5
  13. package/commands/precommit/index.js +4 -3
  14. package/commands/setup/index.js +0 -12
  15. package/commands/test/index.js +50 -49
  16. package/configs/eslint.cjs +20 -3
  17. package/configs/lintstaged.js +25 -0
  18. package/configs/node.cjs +1 -0
  19. package/configs/{prettier.cjs → prettier.js} +1 -1
  20. package/configs/{stylelint.cjs → stylelint.js} +1 -1
  21. package/configs/vite.js +9 -2
  22. package/helper.js +13 -0
  23. package/main.js +0 -2
  24. package/package.json +10 -16
  25. package/templates/_circleci/config.yml +0 -1
  26. package/templates/_eslintignore.conf +4 -1
  27. package/templates/_gitattributes +3 -0
  28. package/templates/_gitignore +28 -0
  29. package/templates/_yarnrc.yml +9 -0
  30. package/templates/lintstaged.config.js +4 -0
  31. package/templates/prettier.config.js +4 -0
  32. package/templates/scripts/compress +4 -0
  33. package/templates/scripts/hooks/pre-commit +1 -1
  34. package/templates/stylelint.config.js +4 -0
  35. package/commands/ci/autoRelease.js +0 -143
  36. package/commands/release/index.js +0 -76
  37. package/commands/setup/autoRelease/envVars.js +0 -56
  38. package/commands/setup/autoRelease/index.js +0 -59
  39. package/commands/setup/autoRelease/privateKey.js +0 -41
  40. package/commands/setup/autoRelease/publicKey.js +0 -55
  41. package/configs/jest/index.cjs +0 -52
  42. package/configs/jest/jest-extended.cjs +0 -3
  43. package/configs/jest/setupFileAfterEnv.cjs +0 -13
  44. package/configs/jest/sharedState.cjs +0 -14
  45. package/configs/jest/testEnvironment.cjs +0 -46
  46. package/configs/jest.cjs +0 -1
  47. package/configs/lintstaged.cjs +0 -25
  48. package/templates/_lintstagedrc.cjs +0 -4
  49. package/templates/_prettierrc.cjs +0 -4
  50. package/templates/_stylelintrc.cjs +0 -4
  51. package/templates/jest.config.cjs +0 -4
package/README.md CHANGED
@@ -161,24 +161,6 @@ what most free CI tier offer). If you have access to higher end machines, you
161
161
  can update this value by passing the `--cpu-count=X` flag to your `aberlaas ci`
162
162
  call.
163
163
 
164
- ### Auto-Releasing
165
-
166
- As an optional feature, you can have aberlaas automatically release a new
167
- version of your module from the CI environment when relevant.
168
-
169
- The CI will then check all the commits since the last release. If any commit is
170
- a `feat()` it will release a new minor version; it any commit is a `fix()` it
171
- will release a new patch version. For major release, you'll have to do it
172
- manually.
173
-
174
- This option is not enabled by default. If you need it, you need to follow those
175
- steps:
176
-
177
- - Run `aberlaas setup --auto-release`. It will setup the required `ENV` variables
178
- and ssh keys
179
- - Update your `aberlaas ci` script to `aberlaas ci --auto-release`
180
- - Uncomment the `add_ssh_keys` in your `.circleci.yml` file
181
-
182
164
  ## File structure
183
165
 
184
166
  `./lib/configs` contain the default configuration for all the tools. They are
@@ -188,10 +170,133 @@ exported by the package and thus can be `require`d in userland.
188
170
  extends the configuration exported in the previous files. Copying files to
189
171
  userland allows user to change the files if they want to change the behavior.
190
172
 
191
- `.eslintrc.js`, `.stylelintrc.js` and `jest.config.js` are local
173
+ `.eslintrc.js`, `stylelint.config.js` and `vite.config.js` are local
192
174
  configuration files for `aberlaas` itself. They eat their own dog food by
193
175
  referencing the same configs as above.
194
176
 
177
+ ## Tools used and their future
178
+
179
+ ### ESLint
180
+
181
+ ESLint doesn't yet support ESM config files. We'll upgrade to the latest ESLint
182
+ when it does. This should also help fix the issue with Yarn PnP (see below).
183
+
184
+ ### Yarn
185
+
186
+ **tl;dr; We'll move to Yarn PnP once ESLint has support for Flat Configs, and
187
+ default `yarn run` doesn't add a ~1s delay overhead**
188
+
189
+ #### PnP
190
+
191
+
192
+ Aberlaas is using Yarn Berry (v2+) as its main package management tool.
193
+
194
+ Yarn Berry comes with a Plug And Play (PnP) feature that replaces the usage of
195
+ `node_modules` in favor of a `.pnp.cjs` file. Instead of having a very large
196
+ `node_modules` folder, a unique copy of each dependency is stored in the user
197
+ home folder and the `.pnp.cjs` file only keeps references to those folder. This
198
+ makes installing dependencies faster as it needs way less I/O.
199
+
200
+ By ditching the whole `node_modules` principle, it also removes concepts like
201
+ hoisting of dependencies in a monorepo. This, unfortunately, breaks ESLint.
202
+
203
+ ESLint expect all its plugins to be defined as `peerDependencies` at the root of
204
+ a mono-repo, as it will always try to include them from there. It works more or
205
+ less correctly at the best of times, and `aberlaas` already has some hacks
206
+ (including `resolvePluginsRelativeTo`) to work around that.
207
+
208
+ But with PnP, there is no way to make it work correctly, so I will need to wait
209
+ for a better compatibility between ESLint and Yarn 2 before using it.
210
+
211
+ Sources:
212
+ - [#8](https://github.com/yarnpkg/berry/issues/8), the initial case study of Yarn + ESLint
213
+ - [GitHub issue that explicitly explain the problem](https://github.com/yarnpkg/berry/discussions/3909)
214
+ - [`eslint-patch`, an ESLint plugin that hacks around this issue](https://yarnpkg.com/package?name=@rushstack/eslint-patch)
215
+ - [The `resolvePluginsRelativeTo` bandaid from ESLint](https://eslint.org/docs/latest/use/command-line-interface#--resolve-plugins-relative-to)
216
+ - [A nice postmortem of moving to Yarn 2](https://www.dolthub.com/blog/2022-03-18-migrating-to-yarn-2/)
217
+ - [The Flat Config feature in ESLint that should solve the problem](https://eslint.org/docs/latest/use/configure/configuration-files-new)
218
+
219
+ #### Calling binaries from the host
220
+
221
+ In yarn v1, if you install `aberlaas` in your project, all of `aberlaas`
222
+ dependencies (including binaries) were hoisted to the root. This was a design
223
+ flaw, as if several dependencies defined binaries by the same name, they would
224
+ fight in a race condition to take the slot in `node_modules/.bin`.
225
+
226
+ This has been fixed in Yarn Berry, but it also means it's no longer possible to
227
+ call `yarn run eslint` from a repository that includes `aberlaas`, because
228
+ `eslint` is not a direct dependency of the repo.
229
+
230
+ It might be possible to define proxy binaries inside of `aberlaas`, but those
231
+ will incur performance issues as they will need to spawn one more yarn context
232
+ (see below).
233
+
234
+ #### Calling binaries from aberlaas
235
+
236
+ In Yarn V1, I was calling binaries that `aberlaas` depends on (`eslint`, `vitest`,
237
+ etc) by calling `yarn run`. This was adding some overhead but was acceptable.
238
+ Yarn Berry adds even more overhead and it is becoming noticeable.
239
+
240
+ Now, my prefered way it to use the NodeJS API of the dependencies instead of
241
+ their CLI, to limit the overhead. I managed to move most tools to this new
242
+ approach, but sometimes I still need to use the CLI (`vitest` for example has
243
+ awesome live watching and display reloading that I don't think I can easily
244
+ replicate through code).
245
+
246
+ I ran some performance tests to see what would be the fastest way to call
247
+ `vitest` from `aberlaas`
248
+
249
+ ```
250
+ hyperfine \
251
+ "zsh -i -c 'yarn bin vitest && /home/tim/local/www/projects/aberlaas/node_modules/vitest/vitest.mjs --version'" \
252
+ "zsh -i -c 'yarn run vitest --version'" \
253
+ "/home/tim/local/www/projects/aberlaas/node_modules/vitest/vitest.mjs --version" \
254
+ "/home/tim/local/www/projects/aberlaas/node_modules/.bin/vitest --version"
255
+ Benchmark 1: zsh -i -c 'yarn run vitest --version'
256
+ Time (mean ± σ): 1.945 s ± 0.051 s [User: 1.986 s, System: 0.850 s]
257
+ Range (min … max): 1.859 s … 2.018 s 10 runs
258
+
259
+ Benchmark 2: zsh -i -c 'yarn bin vitest && /home/tim/local/www/projects/aberlaas/node_modules/vitest/vitest.mjs --version'
260
+ Time (mean ± σ): 2.108 s ± 0.150 s [User: 2.108 s, System: 0.843 s]
261
+ Range (min … max): 1.930 s … 2.289 s 10 runs
262
+
263
+ Benchmark 3: /home/tim/local/www/projects/aberlaas/node_modules/vitest/vitest.m
264
+ js --version
265
+ Time (mean ± σ): 482.5 ms ± 40.9 ms [User: 448.4 ms, System: 327.2 ms]
266
+ Range (min … max): 442.1 ms … 553.3 ms 10 runs
267
+
268
+ Benchmark 4: /home/tim/local/www/projects/aberlaas/node_modules/.bin/vitest --version
269
+ Time (mean ± σ): 491.9 ms ± 29.6 ms [User: 454.1 ms, System: 331.2 ms]
270
+ Range (min … max): 453.8 ms … 535.4 ms 10 runs
271
+ ```
272
+
273
+ Finding the binary through `yarn bin` then calling it is the slowest, but `yarn
274
+ run` isn't much faster. Directly calling the binary is the fastest, but it's
275
+ path can't be easily guessed (apart from reading and parsing a `package.json`).
276
+ But using the symlinks in `node_modules/.bin` is consistent, barely slower than
277
+ calling the binary directly and much faster than using yarn.
278
+
279
+ This is what I'll be using. Of course, this will break when I'll move to PnP as
280
+ the `node_modules` folder won't be there, but hopefully Yarn will be faster by
281
+ then and I can use `yarn run` reliably.
282
+
283
+ #### Speed
284
+
285
+ With Yarn 2+, calling `yarn run` seem to add ~1s of overhead each time. This is
286
+ a known issue (due to the fact Yarn 2 needs to spawn yarn 1, node and a few
287
+ other layers).
288
+
289
+ 1s is not much in itself, but grows quickly when you have nested `yarn run
290
+ aberlaas` calls that call `yarn run eslint`, and even more when you need to deal
291
+ with monorepos and multiple packages that each have their own yarn scope.
292
+
293
+ There are open issues on the topic, but nothing merged yet:
294
+
295
+ - [#3732](https://github.com/yarnpkg/berry/issues/3732), where it is discussed to make yarn run aware that it's running yarn run
296
+ and keep the same state without spawning new yarns
297
+ - [#2575](https://github.com/yarnpkg/berry/issues/2575), which is the main issue about Yarn performance, with benchmarks against
298
+ npm/npx/yarn v1.
299
+
195
300
  ## Where does the name Aberlaas come from?
196
301
 
197
302
  Aberlaas is the base camp from which all great expedition start in the _La Horde
@@ -205,4 +310,4 @@ For your convenience, `aberlass` and `aberlas` are added as aliases by default.
205
310
 
206
311
  ## Documentation
207
312
 
208
- The complete documentation can be found on https://projects.pixelastic.com/aberlaas/
313
+ The complete documentation can be found on https://projects.pixelastic.com/aberlaas/
@@ -1,21 +1,10 @@
1
- import helper from '../../helper.js';
2
1
  import ciInfo from 'ci-info';
3
- import _ from 'golgoth/lodash.js';
4
- import pMap from 'golgoth/pMap.js';
5
- import readJson from 'firost/readJson.js';
6
2
  import run from 'firost/run.js';
3
+ import commandTest from '../test/index.js';
4
+ import commandLint from '../lint/index.js';
7
5
  import consoleInfo from 'firost/consoleInfo.js';
8
- import autoRelease from './autoRelease.js';
9
6
 
10
7
  export default {
11
- /**
12
- * Return the value of an environment variable
13
- * @param {string} key Name of the variable
14
- * @returns {*} Key value
15
- **/
16
- getEnv(key) {
17
- return _.get(process, `env.${key}`);
18
- },
19
8
  /**
20
9
  * Checks if currently running on a CI server
21
10
  * @returns {boolean} True if on a CI server
@@ -23,111 +12,37 @@ export default {
23
12
  isCI() {
24
13
  return ciInfo.isCI;
25
14
  },
26
- /**
27
- * Checks if currently running on CircleCI
28
- * @returns {boolean} True if on CircleCI
29
- **/
30
- isCircleCI() {
31
- return ciInfo.CIRCLE;
32
- },
33
- /**
34
- * Checks if currently on a PR
35
- * @returns {boolean} True if on a PR
36
- **/
37
- isPR() {
38
- return ciInfo.isPR;
39
- },
40
- /**
41
- * Return the name of the originating branch of the PR
42
- * @returns {string} Name of the PR branch
43
- **/
44
- prBranch() {
45
- if (!this.isPR()) {
46
- return false;
47
- }
48
- if (this.isCircleCI()) {
49
- return this.getEnv('CIRCLE_BRANCH');
50
- }
51
- return false;
52
- },
53
- /**
54
- * Returns the list of scripts defined in the package.json
55
- * @returns {Array} List of scripts defined
56
- **/
57
- async availableScripts() {
58
- // Get scripts in package.json
59
- const currentPackage = await readJson(helper.hostPath('package.json'));
60
- return _.chain(currentPackage).get('scripts').keys().value();
61
- },
62
- /**
63
- * Returns a list of available scripts to run
64
- * @returns {Array} List of scripts to run
65
- **/
66
- async scriptsToRun() {
67
- const availableScripts = await this.availableScripts();
68
-
69
- // Get potential scripts to run
70
- const potentialScripts = ['test', 'lint', 'build:prod'];
71
-
72
- return _.intersection(potentialScripts, availableScripts);
73
- },
74
- /**
75
- * Display the current node and yarn versions
76
- **/
77
- async displayVersion() {
78
- const { stdout: nodeVersion } = await this.__run('node --version', {
79
- stdout: false,
80
- });
81
- const { stdout: yarnVersion } = await this.__run('yarn --version', {
82
- stdout: false,
83
- });
84
- this.__consoleInfo(`node ${nodeVersion}, yarn v${yarnVersion}`);
85
- },
86
15
  /**
87
16
  * Run CI scripts and fail the job if any fails
17
+ * Runs lint and test by default, but can be changed with --no-test and
18
+ * --no-lint
88
19
  * @param {object} cliArgs CLI Argument object, as created by minimist
89
20
  * @returns {boolean} True on success, throws on error
90
21
  **/
91
22
  async run(cliArgs = {}) {
92
23
  const args = {
93
- 'auto-release': false,
94
- 'cpu-count': 2,
24
+ test: true,
25
+ lint: true,
95
26
  ...cliArgs,
96
27
  };
97
28
 
98
29
  if (!this.isCI()) {
30
+ this.__consoleInfo('Current system is not a CI, skipping');
99
31
  return true;
100
32
  }
101
33
 
102
- await this.displayVersion();
103
-
104
- const scripts = await this.scriptsToRun();
105
- await pMap(
106
- scripts,
107
- async (scriptName) => {
108
- let command = scriptName;
109
- if (command === 'test') {
110
- command = `test --maxWorkers=${args['cpu-count']}`;
111
- }
112
-
113
- await helper.yarnRun(command);
114
- },
115
- { concurrency: 1 },
116
- );
34
+ if (args.test) {
35
+ await this.__runTest();
36
+ }
117
37
 
118
- // Attempt to release the package if --auto-release is set
119
- if (args['auto-release']) {
120
- await this.autoRelease();
38
+ if (args.lint) {
39
+ await this.__runLint();
121
40
  }
122
41
 
123
42
  return true;
124
43
  },
125
- /**
126
- * Attempt to perform an auto-release
127
- **/
128
- async autoRelease() {
129
- await autoRelease.run();
130
- },
44
+ __runTest: commandTest.run.bind(commandTest),
45
+ __runLint: commandLint.run.bind(commandLint),
131
46
  __run: run,
132
47
  __consoleInfo: consoleInfo,
133
48
  };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * This is a temporary, dummy file to be able to test the top-level
3
+ * compress.run() function. I assume I'll add more compression types in addition
4
+ * to png, so this dummy compress is just to test that all compress are
5
+ * correctly called, but it doesn't do anything
6
+ **/
7
+ export default {
8
+ async run() {
9
+ return true;
10
+ },
11
+ };
@@ -2,11 +2,13 @@ import _ from 'golgoth/lodash.js';
2
2
  import pMap from 'golgoth/pMap.js';
3
3
  import consoleError from 'firost/consoleError.js';
4
4
  import firostError from 'firost/error.js';
5
- import helper from '../../helper.js';
5
+ import compressPng from './png.js';
6
+ import compressDummy from './dummy.js';
6
7
 
7
8
  export default {
8
9
  types: {
9
- png: './png.js',
10
+ png: compressPng,
11
+ dummy: compressDummy,
10
12
  },
11
13
  /**
12
14
  * Wrapper to compress all supported formats
@@ -22,7 +24,7 @@ export default {
22
24
  await pMap(typesToCompress, async (type) => {
23
25
  try {
24
26
  const userPatterns = _.get(cliArgs, '_');
25
- const compresser = require(this.types[type]);
27
+ const compresser = this.types[type];
26
28
 
27
29
  await compresser.run(userPatterns);
28
30
  } catch (error) {
@@ -37,16 +39,5 @@ export default {
37
39
 
38
40
  return true;
39
41
  },
40
- /**
41
- * Find all relevant files of the specified extension in the host
42
- * Note: Should be used by child classes
43
- * @param {Array} safeListExtension List of allowed extensions to keep
44
- * @param {Array} userPatterns Patterns to narrow the search down
45
- * @returns {Array} Array of files
46
- **/
47
- async getInputFiles(safeListExtension, userPatterns = []) {
48
- const inputPatterns = _.isEmpty(userPatterns) ? '.' : userPatterns;
49
- return await helper.findHostFiles(inputPatterns, safeListExtension);
50
- },
51
42
  __consoleError: consoleError,
52
43
  };
@@ -1,4 +1,5 @@
1
- import lint from './index.js';
1
+ import helper from '../../helper.js';
2
+ import _ from 'golgoth/lodash.js';
2
3
  import run from 'firost/run.js';
3
4
  import which from 'firost/which.js';
4
5
  import firostError from 'firost/error.js';
@@ -10,15 +11,19 @@ export default {
10
11
  * @returns {Array} Array of files
11
12
  **/
12
13
  async getInputFiles(userPatterns) {
13
- return await lint.getInputFiles(['.png'], userPatterns);
14
+ const filePatterns = _.isEmpty(userPatterns)
15
+ ? ['./**/*.png']
16
+ : userPatterns;
17
+ return await helper.findHostFiles(filePatterns, ['.png']);
14
18
  },
19
+
15
20
  /**
16
- * Check if the binary is available in the $PATH
17
- * @returns {boolean} True if available, false otherwise
21
+
22
+ * Returns path to the binary to execute
23
+ * @returns {string|boolean} Path to the binary, or false if not found
18
24
  **/
19
- async hasBin() {
20
- const binary = await which('pngmin');
21
- return !!binary;
25
+ async getBinaryPath() {
26
+ return await this.__which('pngmin');
22
27
  },
23
28
  /**
24
29
  * Compress files
@@ -27,16 +32,20 @@ export default {
27
32
  **/
28
33
  async run(userPatterns) {
29
34
  // Stop early if no bin
30
- if (!(await this.hasBin())) {
35
+ const binaryPath = await this.getBinaryPath();
36
+ if (!binaryPath) {
31
37
  return true;
32
38
  }
33
39
 
34
40
  try {
35
41
  const files = await this.getInputFiles(userPatterns);
36
- const command = `pngmin ${files.join(' ')}`;
42
+ const command = `${binaryPath} ${files.join(' ')}`;
37
43
  await run(command, { stdout: false });
38
44
  } catch (error) {
39
45
  throw firostError('PngCompressError', error.message);
40
46
  }
47
+
48
+ return true;
41
49
  },
50
+ __which: which,
42
51
  };
@@ -45,21 +45,34 @@ export default {
45
45
  * default rules and overwrite them as they see fit
46
46
  **/
47
47
  async addConfigFiles() {
48
+ // Git
49
+ await this.copyToHost('./templates/_gitignore', './.gitignore');
50
+ await this.copyToHost('./templates/_gitattributes', './.gitattributes');
51
+
52
+ // Yarn
53
+ await this.copyToHost('templates/_yarnrc.yml', '.yarnrc.yml');
54
+
48
55
  // ESLint
49
56
  await this.copyToHost('templates/_eslintrc.cjs', '.eslintrc.cjs');
50
57
  await this.copyToHost('templates/_eslintignore.conf', '.eslintignore');
51
58
 
52
59
  // Lint-staged
53
- await this.copyToHost('templates/_lintstagedrc.cjs', '.lintstagedrc.cjs');
60
+ await this.copyToHost(
61
+ 'templates/lintstaged.config.js',
62
+ 'lintstaged.config.js',
63
+ );
54
64
 
55
65
  // Vite
56
66
  await this.copyToHost('templates/vite.config.js', 'vite.config.js');
57
67
 
58
68
  // Prettier
59
- await this.copyToHost('templates/_prettierrc.cjs', '.prettierrc.cjs');
69
+ await this.copyToHost('templates/prettier.config.js', 'prettier.config.js');
60
70
 
61
71
  // Stylelint
62
- await this.copyToHost('templates/_stylelintrc.cjs', '.stylelintrc.cjs');
72
+ await this.copyToHost(
73
+ 'templates/stylelint.config.js',
74
+ 'stylelint.config.js',
75
+ );
63
76
 
64
77
  // Renovate
65
78
  await this.copyToHost(
@@ -106,6 +119,7 @@ export default {
106
119
  async addScripts() {
107
120
  const defaultScripts = [
108
121
  { key: 'ci', value: 'scripts/ci' },
122
+ { key: 'compress', value: 'scripts/compress' },
109
123
  { key: 'lint', value: 'scripts/lint' },
110
124
  { key: 'lint:fix', value: 'scripts/lint-fix' },
111
125
  { key: 'release', value: 'scripts/release' },
@@ -248,7 +262,9 @@ export default {
248
262
  await write(nodeConfig.nodeVersion, nvmrcPath);
249
263
 
250
264
  // Download latest yarn version
251
- await this.__run('yarn set version', { stdout: false });
265
+ await this.__run(`yarn set version ${nodeConfig.yarnVersion}`, {
266
+ stdout: false,
267
+ });
252
268
  },
253
269
  /**
254
270
  * Configure git hooks to use scripts/hooks instead of .git/hooks
@@ -270,6 +286,9 @@ export default {
270
286
  async run() {
271
287
  const progress = spinner();
272
288
 
289
+ progress.tick('Configuring Git');
290
+ await this.configureGit();
291
+
273
292
  progress.tick('Pinning node and yarn versions');
274
293
  await this.pinNodeAndYarn();
275
294
  await this.addEngineNodeVersion();
@@ -280,9 +299,6 @@ export default {
280
299
  progress.tick('Adding yarn scripts');
281
300
  await this.addScripts();
282
301
 
283
- progress.tick('Configuring git hooks');
284
- await this.configureGitHooks();
285
-
286
302
  progress.tick('Updating LICENSE');
287
303
  await this.addLicenseFile();
288
304
  await this.addLicenseField();
@@ -1,8 +1,8 @@
1
1
  import helper from '../../helper.js';
2
2
  import { fix as prettierFix } from './helpers/prettier.js';
3
+ import stylelint from 'stylelint';
3
4
  import _ from 'golgoth/lodash.js';
4
5
  import firostError from 'firost/error.js';
5
- import run from 'firost/run.js';
6
6
 
7
7
  export default {
8
8
  /**
@@ -11,37 +11,45 @@ export default {
11
11
  * @returns {Array} Array of files
12
12
  **/
13
13
  async getInputFiles(userPatterns) {
14
- return await helper.findHostFiles(userPatterns, ['.css']);
14
+ const filePatterns = _.isEmpty(userPatterns)
15
+ ? ['./**/*.css']
16
+ : userPatterns;
17
+ return await helper.findHostFiles(filePatterns, ['.css']);
15
18
  },
16
19
  /**
17
20
  * Lint all files and display results.
18
21
  * @param {Array} userPatterns Patterns to narrow the search down
19
22
  * @param {string} userConfigFile Custom config file to use
23
+ * @param {object} userOptions Options to pass to ESLint, including fix
20
24
  * @returns {boolean} True on success
21
25
  **/
22
- async run(userPatterns, userConfigFile) {
26
+ async run(userPatterns, userConfigFile, userOptions = {}) {
27
+ // Options
28
+ const options = { fix: false, ...userOptions };
29
+
30
+ // Files
23
31
  const files = await this.getInputFiles(userPatterns);
24
32
  if (_.isEmpty(files)) {
25
33
  return true;
26
34
  }
27
35
 
36
+ // Config
28
37
  const configFile = await helper.configFile(
29
38
  userConfigFile,
30
- '.stylelintrc.cjs',
31
- 'configs/stylelint.cjs',
39
+ 'stylelint.config.js',
40
+ 'configs/stylelint.js',
32
41
  );
33
- const binary = await helper.which('stylelint');
34
- const options = [...files, '--color', '--config', configFile];
35
- try {
36
- await run(`${binary} ${options.join(' ')}`, { stdout: false });
37
- } catch (error) {
38
- // If it fails because no files passed actually exists, it's not really
39
- // a failure
40
- const errorMessage = error.stdout;
41
- if (_.startsWith(errorMessage, 'Error: No files matching the pattern')) {
42
- return true;
43
- }
44
- throw firostError('ERROR_CSS_LINT', error.stdout);
42
+ const config = await helper.import(configFile);
43
+
44
+ const result = await stylelint.lint({
45
+ config,
46
+ files,
47
+ formatter: 'string',
48
+ ...options,
49
+ });
50
+
51
+ if (result.errored) {
52
+ throw firostError('ERROR_CSS_LINT', result.output);
45
53
  }
46
54
  return true;
47
55
  },
@@ -57,11 +65,9 @@ export default {
57
65
  return true;
58
66
  }
59
67
  // Try to pretiffy as much as we can
60
- await this.__prettierFix(files);
68
+ await prettierFix(files);
61
69
  // Still run a lint on it so it can fail if not everything is fixed
62
- await this.run(userPatterns, userConfigFile);
70
+ await this.run(userPatterns, userConfigFile, { fix: true });
63
71
  return true;
64
72
  },
65
- __prettierFix: prettierFix,
66
- __run: run,
67
73
  };
@@ -1,25 +1,53 @@
1
+ import * as prettier from 'prettier';
1
2
  import helper from '../../../helper.js';
2
- import run from 'firost/run.js';
3
3
  import firostError from 'firost/error.js';
4
+ import write from 'firost/write.js';
5
+ import read from 'firost/read.js';
6
+ import pMap from 'golgoth/pMap.js';
7
+ import _ from 'golgoth/lodash.js';
4
8
 
5
9
  /**
6
10
  * Fix all files using prettier
7
- * Note: Will be called by child classes
8
- * Note: Prettier does not output any information as to why it failed, so
9
- * we'll manually run the command on each file individually so we can catch
10
- * the file that errors and display it
11
11
  * @param {Array} inputFiles Files to auto-fix
12
12
  **/
13
13
  export async function fix(inputFiles) {
14
- const binary = await helper.which('prettier');
15
- const options = ['--write', ...inputFiles];
16
- try {
17
- const command = `${binary} ${options.join(' ')}`;
18
- await run(command, { stdout: false });
19
- } catch (err) {
14
+ // Config file
15
+ const configFile = await helper.configFile(
16
+ null,
17
+ 'prettier.config.js',
18
+ 'configs/prettier.js',
19
+ );
20
+ const config = await prettier.resolveConfig(configFile);
21
+
22
+ const errors = [];
23
+
24
+ // Read all files, run them through the formatter and save them back to disk
25
+ // If any emits error, store the errors and display them all in one output
26
+ await pMap(
27
+ inputFiles,
28
+ async (filepath) => {
29
+ try {
30
+ const content = await read(filepath);
31
+ const options = { ...config, filepath };
32
+ const result = await prettier.format(content, options);
33
+ await write(result, filepath);
34
+ } catch (error) {
35
+ const message = error.toString();
36
+ errors.push({ filepath, message });
37
+ }
38
+ },
39
+ { concurrency: 10 },
40
+ );
41
+
42
+ if (!_.isEmpty(errors)) {
43
+ let formattedErrors = '';
44
+ _.each(errors, (error) => {
45
+ formattedErrors = `${formattedErrors}${error.filepath}\n\n${error.message}\n\n`;
46
+ });
47
+
20
48
  throw firostError(
21
49
  'LINT_ERROR_FIX_PRETTIER',
22
- 'Some files could not be automatically fixed.\nPlease run `yarn run lint` to further debug',
50
+ `Some files could not be automatically fixed:\n\n${formattedErrors}`,
23
51
  );
24
52
  }
25
53
  }
@@ -21,7 +21,7 @@ export default {
21
21
  * @param {object} cliArgs CLI Argument object, as created by minimist
22
22
  * @returns {boolean} True on success
23
23
  **/
24
- async run(cliArgs) {
24
+ async run(cliArgs = {}) {
25
25
  const allTypesKeys = _.keys(this.linters);
26
26
  const userTypes = _.intersection(_.keys(cliArgs), allTypesKeys);
27
27
  const typesToLint = _.isEmpty(userTypes) ? allTypesKeys : userTypes;