sku 12.4.11 → 12.5.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,65 @@
1
1
  # sku
2
2
 
3
+ ## 12.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Remove `rimraf` dependency in favour of Node.js's `rm` ([#961](https://github.com/seek-oss/sku/pull/961))
8
+
9
+ - Unpin and bump `@pmmmwh/react-refresh-webpack-plugin` ([#959](https://github.com/seek-oss/sku/pull/959))
10
+
11
+ - Ensure all sku-generated gitignored files are present in `.prettierignore` and `.eslintignore` too ([#957](https://github.com/seek-oss/sku/pull/957))
12
+
13
+ Consumers should notice a few new files being added to the sku-managed sections within `.prettierignore` and `.eslintignore` the next time a `sku` command is run:
14
+
15
+ ```diff
16
+ # managed by sku
17
+ *.less.d.ts
18
+ +.eslintcache
19
+ +.eslintrc
20
+ +.prettierrc
21
+ .storybook/main.js
22
+ coverage/
23
+ dist-storybook/
24
+ dist/
25
+ report/
26
+ # end managed by sku
27
+ ```
28
+
29
+ These changes should be committed to your repo.
30
+
31
+ - Disable peer dependency validation for PNPM ([#952](https://github.com/seek-oss/sku/pull/952))
32
+
33
+ The method we currently use to validate peer dependencies and warn users about duplicate package is not compatible with how PNPM organizes dependencies in `node_modules`. This feature has been disabled for PNPM repos until further notice.
34
+
35
+ - Replace `memoizee` dependency with `nano-memoize` ([#953](https://github.com/seek-oss/sku/pull/953))
36
+
37
+ - Replace `fast-glob` with `fdir` and `picomatch` ([#952](https://github.com/seek-oss/sku/pull/952))
38
+
39
+ - Replace `validate-npm-package-name` dependency with a regexp ([#954](https://github.com/seek-oss/sku/pull/954))
40
+
41
+ - Improve performance of peer dependency validation ([#952](https://github.com/seek-oss/sku/pull/952))
42
+
43
+ Peer dependency validation shoould now complete within a few seconds, rather than a few minutes.
44
+
45
+ ## 12.5.0
46
+
47
+ ### Minor Changes
48
+
49
+ - Update TypeScript to 5.3 ([#938](https://github.com/seek-oss/sku/pull/938))
50
+
51
+ This release includes breaking changes. See the [TypeScript 5.3 announcement] for more information.
52
+
53
+ [TypeScript 5.3 announcement]: https://devblogs.microsoft.com/typescript/announcing-typescript-5-3/
54
+
55
+ ### Patch Changes
56
+
57
+ - Remove `empty-dir` dependency ([#935](https://github.com/seek-oss/sku/pull/935))
58
+
59
+ - Replace `command-line-args` with `minimist` for parsing CLI arguments ([#940](https://github.com/seek-oss/sku/pull/940))
60
+
61
+ - Emit incremental TypeScript build info for faster subsequent type checking ([#938](https://github.com/seek-oss/sku/pull/938))
62
+
3
63
  ## 12.4.11
4
64
 
5
65
  ### Patch Changes
package/README.md CHANGED
@@ -6,7 +6,8 @@
6
6
 
7
7
  Front-end development toolkit, powered by [Webpack](https://webpack.js.org/), [Babel](https://babeljs.io/), [Vanilla Extract](https://vanilla-extract.style/), [CSS Modules](https://github.com/css-modules/css-modules), [Less](http://lesscss.org/), [ESLint](http://eslint.org/), [Prettier](https://prettier.io/), [Jest](https://facebook.github.io/jest/) and [Storybook](https://storybook.js.org/).
8
8
 
9
- Quickly get up and running with a zero-config development environment, or optionally add minimal config when needed. Designed for usage with [braid-design-system](https://github.com/seek-oss/braid-design-system), although this isn't a requirement.
9
+ Quickly get up and running with a zero-config development environment, or optionally add minimal config when needed.
10
+ Designed for usage with [braid-design-system](https://github.com/seek-oss/braid-design-system), although this isn't a requirement.
10
11
 
11
12
  This tool is heavily inspired by other work, most notably:
12
13
 
@@ -14,13 +15,19 @@ This tool is heavily inspired by other work, most notably:
14
15
  - [insin/nwb](https://github.com/insin/nwb)
15
16
  - [NYTimes/kyt](https://github.com/NYTimes/kyt)
16
17
 
17
- **WARNING: While this software is open source, its primary purpose is to improve consistency, cross-team collaboration and code quality at SEEK. As a result, it’s likely that we will introduce more breaking API changes to this project than you’ll find in its alternatives.**
18
+ > [!WARNING]
19
+ > While this software is open source, its primary purpose is to improve consistency, cross-team collaboration and code quality at SEEK.
20
+ > As a result, it’s likely that this tool may not exactly suit your needs, or may be overkill for your use case.
21
+ > It may be worth considering alternatives such as [Vite] or [Parcel].
22
+
23
+ [Vite]: https://vitejs.dev/
24
+ [Parcel]: https://parceljs.org/
18
25
 
19
26
  ## Getting Started
20
27
 
21
28
  Create a new project and start a local development environment:
22
29
 
23
- ```bash
30
+ ```sh
24
31
  $ npx sku init my-app
25
32
  $ cd my-app
26
33
  $ yarn start
@@ -30,7 +37,7 @@ By default, a new project's dependencies will be installed with the first suppor
30
37
  Package managers are detected in the following order: `yarn` -> `pnpm` -> `npm`.
31
38
  This can be overridden via the `--packageManager` flag:
32
39
 
33
- ```bash
40
+ ```sh
34
41
  $ pnpm dlx sku init --packageManager pnpm my-app
35
42
  $ cd my-app
36
43
  $ pnpm start
@@ -40,7 +47,8 @@ $ pnpm start
40
47
 
41
48
  ## Contributing
42
49
 
43
- Refer to [CONTRIBUTING.md](/CONTRIBUTING.md). If you're planning to change the public API, please [open a new issue](https://github.com/seek-oss/sku/issues/new).
50
+ Refer to [CONTRIBUTING.md](/CONTRIBUTING.md).
51
+ If you're planning to change the public API, please [open a new issue](https://github.com/seek-oss/sku/issues/new).
44
52
 
45
53
  ## License
46
54
 
@@ -4,11 +4,15 @@ const { rootResolution, tsconfigDecorator } = require('../../context');
4
4
  module.exports = () => {
5
5
  const config = {
6
6
  compilerOptions: {
7
- // This flag allows tsc to be invoked directly by VS Code (via Cmd+Shift+B),
8
- // otherwise it would emit a bunch of useless JS/JSX files in your project.
9
- // We emit compiled JavaScript into `dist` via webpack + Babel, not tsc.
7
+ // Don't compile anything, only perform type checking
10
8
  noEmit: true,
11
9
 
10
+ // Emit build information for faster subsequent type checking
11
+ incremental: true,
12
+ // Emit build information to `node_modules` to avoid bloating the project root
13
+ // and ignore files
14
+ outDir: 'node_modules',
15
+
12
16
  // Fixes https://github.com/cypress-io/cypress/issues/1087
13
17
  skipLibCheck: true,
14
18
 
@@ -1,5 +1,5 @@
1
1
  const { HtmlRenderPlugin } = require('html-render-webpack-plugin');
2
- const memoize = require('memoizee/weak');
2
+ const { default: memoize } = require('nano-memoize');
3
3
  const debug = require('debug');
4
4
 
5
5
  const {
@@ -1,5 +1,5 @@
1
1
  const path = require('node:path');
2
- const memoize = require('memoizee');
2
+ const { default: memoize } = require('nano-memoize');
3
3
  const debug = require('debug')('sku:resolvePackage');
4
4
  const { cwd } = require('../../../lib/cwd');
5
5
 
@@ -37,6 +37,7 @@ const createPackageResolver = (fs, resolve) => {
37
37
  *
38
38
  * Throws if a package is listed in the project's dependencies and cannot be resolved.
39
39
  */
40
+ /** @param {string} packageName */
40
41
  function resolvePackage(packageName) {
41
42
  try {
42
43
  // First, try to use require.resolve to find the package.
@@ -1,57 +1,83 @@
1
1
  const { posix: path } = require('node:path');
2
2
  const chalk = require('chalk');
3
- const glob = require('fast-glob');
3
+ const { fdir: Fdir } = require('fdir');
4
4
 
5
- const { cwd: skuCwd } = require('../lib/cwd');
5
+ const { cwd } = require('../lib/cwd');
6
6
  const toPosixPath = require('../lib/toPosixPath');
7
7
 
8
8
  const { rootDir, isPnpm } = require('../lib/packageManager');
9
+ const debug = require('debug')('sku:compilePackages');
9
10
 
10
11
  /** @type {string[]} */
11
12
  let detectedCompilePackages = [];
12
13
 
13
- try {
14
- const globs = ['node_modules/@seek/*/package.json'];
15
- const cwd = skuCwd();
14
+ // If there's no rootDir, we're either inside `sku init`, or we can't determine the user's
15
+ // package manager. In either case, we can't correctly detect compile packages.
16
+ if (rootDir) {
17
+ try {
18
+ let crawler = new Fdir();
16
19
 
17
- if (isPnpm && rootDir) {
18
- const pnpmVirtualStorePath = path.join(
19
- toPosixPath(rootDir),
20
- 'node_modules/.pnpm',
21
- );
22
- const pnpmVirtualStoreRelativePath = path.relative(
23
- '.',
24
- pnpmVirtualStorePath,
25
- );
26
- const pnpmVirtualStoreGlob = path.join(
27
- pnpmVirtualStoreRelativePath,
28
- '@seek*/node_modules/@seek/*/package.json',
29
- );
20
+ if (isPnpm) {
21
+ // Follow symlinks inside node_modules into the pnpm virtual store
22
+ crawler = crawler.withSymlinks().withRelativePaths();
23
+ } else {
24
+ crawler = crawler.withBasePath();
25
+ }
30
26
 
31
- globs.push(pnpmVirtualStoreGlob);
32
- }
27
+ const seekDependencyGlob = '**/@seek/*/package.json';
28
+
29
+ let results = crawler
30
+ .glob(seekDependencyGlob)
31
+ .crawl('./node_modules/@seek')
32
+ .sync();
33
+
34
+ if (isPnpm) {
35
+ const pnpmVirtualStorePath = path.join(
36
+ toPosixPath(rootDir),
37
+ 'node_modules/.pnpm',
38
+ );
39
+
40
+ const pnpmVirtualStoreRelativePath = path.relative(
41
+ '.',
42
+ pnpmVirtualStorePath,
43
+ );
33
44
 
34
- detectedCompilePackages = glob
35
- .sync(globs, {
36
- cwd,
37
- })
38
- .map((packagePath) => {
39
- const packageJson = require(path.join(cwd, packagePath));
40
-
41
- return {
42
- isCompilePackage: Boolean(packageJson.skuCompilePackage),
43
- packageName: packageJson.name,
44
- };
45
- })
46
- .filter(({ isCompilePackage }) => isCompilePackage)
47
- .map(({ packageName }) => packageName);
48
- } catch (e) {
49
- console.log(
50
- chalk.red`Warning: Failed to detect compile packages. Contact #sku-support.`,
51
- );
52
- console.error(e);
45
+ const pnpmVirtualStoreResults = new Fdir()
46
+ .withRelativePaths()
47
+ .glob(seekDependencyGlob)
48
+ .crawl(pnpmVirtualStoreRelativePath)
49
+ .sync();
50
+
51
+ results.push(...pnpmVirtualStoreResults);
52
+
53
+ // All results will be relative to the virtual store directory, so we need
54
+ // to prepend the relative path from the current directory to the virtual store
55
+ results = results.map((file) =>
56
+ path.join(pnpmVirtualStoreRelativePath, file),
57
+ );
58
+ }
59
+
60
+ detectedCompilePackages = results
61
+ .map((packagePath) => {
62
+ const packageJson = require(path.join(cwd(), packagePath));
63
+
64
+ return {
65
+ isCompilePackage: Boolean(packageJson.skuCompilePackage),
66
+ packageName: packageJson.name,
67
+ };
68
+ })
69
+ .filter(({ isCompilePackage }) => isCompilePackage)
70
+ .map(({ packageName }) => packageName);
71
+ } catch (e) {
72
+ console.log(
73
+ chalk.red`Warning: Failed to detect compile packages. Contact #sku-support.`,
74
+ );
75
+ console.error(e);
76
+ }
53
77
  }
54
78
 
79
+ debug(detectedCompilePackages);
80
+
55
81
  module.exports = [
56
82
  'sku',
57
83
  'braid-design-system',
@@ -1,20 +1,14 @@
1
1
  import fs from 'node:fs';
2
2
  import http from 'node:http';
3
3
  import https from 'node:https';
4
- import commandLineArgs from 'command-line-args';
5
4
  import { app, onStart } from './server';
5
+ import minimist from 'minimist';
6
6
 
7
- const { port } = commandLineArgs(
8
- [
9
- {
10
- name: 'port',
11
- alias: 'p',
12
- type: Number,
13
- defaultValue: __SKU_DEFAULT_SERVER_PORT__, // eslint-disable-line no-undef
14
- },
15
- ],
16
- { partial: true },
17
- );
7
+ const { port } = minimist(process.argv.slice(2), {
8
+ alias: { p: 'port' },
9
+ // eslint-disable-next-line no-undef
10
+ default: { port: __SKU_DEFAULT_SERVER_PORT__ },
11
+ });
18
12
 
19
13
  const startCallback = () => {
20
14
  console.log('Server started on port', port);
@@ -1,9 +1,10 @@
1
+ // @ts-check
1
2
  const { yellow, bold } = require('chalk');
2
3
  const getPort = require('get-port');
3
4
  const debug = require('debug')('sku:allocatePort');
4
5
 
5
6
  /**
6
- * @param {{ port?: number, host?: string }}
7
+ * @param {{ port?: number, host?: string }} options
7
8
  */
8
9
  const allocatePort = async ({ port, host }) => {
9
10
  debug(`Finding available port with request for ${port}`);
@@ -1,12 +1,15 @@
1
+ // @ts-check
1
2
  const path = require('node:path');
2
3
  const fs = require('node:fs/promises');
3
- const { rimraf } = require('rimraf');
4
+ const { fdir: Fdir } = require('fdir');
4
5
 
5
6
  const { paths } = require('../context');
6
7
  const exists = require('./exists');
7
8
  const copyDirContents = require('./copyDirContents');
8
9
 
9
- const cleanTargetDirectory = () => rimraf(`${paths.target}/*`, { glob: true });
10
+ const cleanTargetDirectory = async () => {
11
+ fs.rm(paths.target, { recursive: true, force: true });
12
+ };
10
13
 
11
14
  const copyPublicFiles = async () => {
12
15
  if (await exists(paths.public)) {
@@ -18,14 +21,21 @@ const ensureTargetDirectory = async () => {
18
21
  await fs.mkdir(paths.target, { recursive: true });
19
22
  };
20
23
 
21
- const cleanRenderJs = async () => {
22
- const renderFileGlob = path.join(paths.target, '*render.js');
23
- await rimraf(renderFileGlob, { glob: true });
24
+ const cleanStaticRenderEntry = async () => {
25
+ const files = await new Fdir()
26
+ .withBasePath()
27
+ .filter((file) => file.endsWith('render.js'))
28
+ .crawl(paths.target)
29
+ .withPromise();
30
+
31
+ for (const file of files) {
32
+ await fs.rm(file);
33
+ }
24
34
  };
25
35
 
26
36
  module.exports = {
27
37
  cleanTargetDirectory,
28
38
  copyPublicFiles,
29
39
  ensureTargetDirectory,
30
- cleanRenderJs,
40
+ cleanStaticRenderEntry,
31
41
  };
package/lib/configure.js CHANGED
@@ -32,28 +32,23 @@ const writeFileToCWD = async (fileName, content, { banner = true } = {}) => {
32
32
  };
33
33
 
34
34
  module.exports = async () => {
35
- // Ignore webpack bundle report output
35
+ // Ignore target directories
36
+ const webpackTargetDirectory = addSep(
37
+ paths.target.replace(addSep(cwd()), ''),
38
+ );
39
+ const storybookTargetDirectory = addSep(
40
+ paths.storybookTarget.replace(addSep(cwd()), ''),
41
+ );
42
+
36
43
  const gitIgnorePatterns = [
44
+ // Ignore webpack bundle report output
37
45
  addSep(bundleReportFolder),
38
46
  addSep(coverageFolder),
39
- storybookMainConfigPath,
40
- ];
41
- const lintIgnorePatterns = [
42
- addSep(bundleReportFolder),
43
- addSep(coverageFolder),
44
- '*.less.d.ts',
47
+ webpackTargetDirectory,
48
+ storybookTargetDirectory,
45
49
  storybookMainConfigPath,
46
50
  ];
47
51
 
48
- // Ignore webpack target directories
49
- const targetDirectory = addSep(paths.target.replace(addSep(cwd()), ''));
50
- const storybookTargetDirectory = addSep(
51
- paths.storybookTarget.replace(addSep(cwd()), ''),
52
- );
53
-
54
- gitIgnorePatterns.push(targetDirectory, storybookTargetDirectory);
55
- lintIgnorePatterns.push(targetDirectory, storybookTargetDirectory);
56
-
57
52
  // Generate ESLint configuration
58
53
  const eslintConfigFilename = '.eslintrc';
59
54
  const eslintCacheFilename = '.eslintcache';
@@ -71,6 +66,8 @@ module.exports = async () => {
71
66
  });
72
67
  gitIgnorePatterns.push(prettierConfigFilename);
73
68
 
69
+ const lintIgnorePatterns = [...gitIgnorePatterns, '*.less.d.ts'];
70
+
74
71
  if (languages) {
75
72
  const generatedVocabFileGlob = '**/*.vocab/index.ts';
76
73
  gitIgnorePatterns.push(generatedVocabFileGlob);
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const path = require('node:path');
2
3
  const fs = require('node:fs/promises');
3
4
  const exists = require('./exists');
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const http = require('node:http');
2
3
  const https = require('node:https');
3
4
 
package/lib/cwd.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const path = require('node:path');
2
3
 
3
4
  let currentCwd = process.cwd();
package/lib/env.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const args = require('../config/args');
2
3
 
3
4
  /**
package/lib/exists.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const { access } = require('node:fs/promises');
2
3
 
3
4
  /**
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const { sites } = require('../context');
2
3
 
3
4
  /**
package/lib/install.js CHANGED
@@ -1,9 +1,11 @@
1
+ // @ts-check
1
2
  const { getAddCommand } = require('./packageManager');
2
3
 
3
4
  const spawn = require('cross-spawn');
4
5
 
5
6
  /**
6
7
  * @param {import("../lib/packageManager").GetAddCommandOptions} options
8
+ * @returns {Promise<void>}
7
9
  */
8
10
  module.exports = ({ deps, type, logLevel, exact = true }) =>
9
11
  new Promise((resolve, reject) => {
package/lib/isCI.js CHANGED
@@ -1 +1,2 @@
1
+ // @ts-check
1
2
  module.exports = Boolean(process.env.CI || process.env.BUILDKITE_BUILD_ID);
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const { getPathFromCwd } = require('./cwd');
2
3
 
3
4
  const isCompilePackage = () => {
@@ -0,0 +1,21 @@
1
+ // @ts-check
2
+ const { statSync, readdirSync } = require('node:fs');
3
+
4
+ /**
5
+ * Returns whether the given directory is empty, throwing if the path is not a directory
6
+ * @typedef {import('fs').PathLike} PathLike
7
+ * @param {PathLike} path A path to a directory
8
+ */
9
+ const isEmptyDir = (path) => {
10
+ const isDirectory = statSync(path).isDirectory();
11
+
12
+ if (!isDirectory) {
13
+ throw new Error(`${path} is not a directory`);
14
+ }
15
+
16
+ const files = readdirSync(path);
17
+
18
+ return files.length === 0;
19
+ };
20
+
21
+ module.exports = { isEmptyDir };
package/lib/parseArgs.js CHANGED
@@ -1,75 +1,61 @@
1
- const commandLineArgs = require('command-line-args');
2
-
3
- const optionDefinitions = [
4
- {
5
- name: 'script',
6
- defaultOption: true,
7
- },
8
- {
9
- name: 'env',
10
- alias: 'e',
11
- type: String,
12
- defaultValue: 'production',
13
- },
14
- {
15
- name: 'tenant',
16
- alias: 't',
17
- type: String,
18
- defaultValue: '',
19
- },
20
- {
21
- name: 'build',
22
- alias: 'b',
23
- type: String,
24
- },
25
- {
26
- name: 'config',
27
- alias: 'c',
28
- type: String,
29
- },
30
- {
31
- name: 'debug',
32
- alias: 'D',
33
- type: Boolean,
34
- },
35
- {
36
- name: 'environment',
37
- type: String,
38
- },
39
- {
40
- name: 'packageManager',
41
- type: String,
42
- },
43
- {
44
- name: 'port',
45
- type: Number,
46
- },
47
- {
48
- name: 'site',
49
- type: String,
50
- },
51
- {
52
- name: 'stats',
53
- type: String,
54
- },
55
- ];
1
+ // @ts-check
2
+ const minimist = require('minimist');
56
3
 
57
4
  /**
58
5
  * Supports parsing args that look like:
59
6
  * [/path/to/node/node, /path/to/sku, scriptName, arg1, arg2]
60
7
  *
61
- * @param {string[]} args - should look like process.argv
8
+ * @param {string[]} processArgv - should look like process.argv
62
9
  */
63
- module.exports = (args) => {
64
- const options = commandLineArgs(optionDefinitions, {
10
+ module.exports = (processArgv) => {
11
+ /**
12
+ * @type {string[]}
13
+ */
14
+ const unknown = [];
15
+
16
+ // We are tracking unknown arguments ourselves, so we ignore `minimist`'s unknown property `_`
17
+ const { _, ...options } = minimist(
65
18
  // The first 2 items in process.argv are /path/to/node and /path/to/sku.
66
- // We need the first arg we give to command-line-args to be the script name.
67
- argv: args.slice(2),
68
- stopAtFirstUnknown: false,
69
- partial: true,
70
- });
19
+ // We need the first arg we give to minimist to be the script name.
20
+ processArgv.slice(2),
21
+ {
22
+ string: [
23
+ 'env',
24
+ 'tenant',
25
+ 'build',
26
+ 'config',
27
+ 'environment',
28
+ 'packageManager',
29
+ 'site',
30
+ 'stats',
31
+ ],
32
+ default: {
33
+ env: 'production',
34
+ tenant: '',
35
+ },
36
+ alias: {
37
+ e: 'env',
38
+ t: 'tenant',
39
+ b: 'build',
40
+ c: 'config',
41
+ D: 'debug',
42
+ },
43
+ boolean: [
44
+ 'debug',
45
+ // Passed to Vocab in the `translations` script
46
+ 'delete-unused-keys',
47
+ ],
48
+ // `minimist` does not push unknown flags to `_` even if this function returns `true`, so we
49
+ // need to track them ourselves
50
+ unknown: (arg) => {
51
+ unknown.push(arg);
71
52
 
72
- const { script, _unknown: argv = [] } = options;
53
+ return true;
54
+ },
55
+ },
56
+ );
57
+
58
+ const [script, ...argv] = unknown;
73
59
 
74
60
  // Backwards compatibility for unnamed build name argument, to be deprecated
75
61
  const buildName = () => {
@@ -78,11 +64,13 @@ module.exports = (args) => {
78
64
  } else if (argv.length) {
79
65
  return argv[0];
80
66
  }
81
- return undefined; // eslint-disable-line no-undefined
67
+
68
+ return undefined;
82
69
  };
83
70
 
84
71
  return {
85
72
  ...options,
73
+ script,
86
74
  buildName: script === 'start' ? buildName() : null,
87
75
  env: script === 'start' ? 'development' : options.env,
88
76
  argv,
@@ -1,20 +1,28 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+
1
5
  const parseArgs = require('./parseArgs');
2
6
 
3
- describe('arg parsing', () => {
4
- test('sku exec', () => {
5
- const { script, argv, env } = parseArgs([
7
+ describe('parseArgs', () => {
8
+ test('sku script with short and long flag', () => {
9
+ const { script, argv, env, config } = parseArgs([
6
10
  '/path/to/node',
7
11
  '/path/to/bin/sku',
8
12
  'lint',
9
13
  '-e',
10
14
  'test',
15
+ '--config',
16
+ 'custom.sku.config.ts',
11
17
  ]);
18
+
12
19
  expect(script).toEqual('lint');
13
20
  expect(argv).toEqual([]);
14
21
  expect(env).toEqual('test');
22
+ expect(config).toEqual('custom.sku.config.ts');
15
23
  });
16
24
 
17
- test('sku exec with args', () => {
25
+ test('sku script with flag and argument', () => {
18
26
  const { script, argv, env } = parseArgs([
19
27
  '/path/to/node',
20
28
  '/path/to/bin/sku',
@@ -23,15 +31,32 @@ describe('arg parsing', () => {
23
31
  '-e',
24
32
  'test',
25
33
  ]);
34
+
26
35
  expect(script).toEqual('lint');
27
36
  expect(argv).toEqual(['src/components/**']);
28
37
  expect(env).toEqual('test');
29
38
  });
30
39
 
40
+ test('sku script with argument, known flag and unknown flag', () => {
41
+ const { script, argv, env } = parseArgs([
42
+ '/path/to/node',
43
+ '/path/to/bin/sku',
44
+ 'test',
45
+ '-e',
46
+ 'test',
47
+ 'testFilter',
48
+ '--someJestFlag',
49
+ ]);
50
+
51
+ expect(script).toEqual('test');
52
+ expect(argv).toEqual(['testFilter', '--someJestFlag']);
53
+ expect(env).toEqual('test');
54
+ });
55
+
31
56
  test('debug', () => {
32
57
  expect(
33
58
  parseArgs(['/path/to/node', '/path/to/bin/sku', 'build']).debug,
34
- ).toBeUndefined();
59
+ ).toBe(false);
35
60
 
36
61
  expect(
37
62
  parseArgs(['/path/to/node', '/path/to/bin/sku', '-D', 'build']).debug,
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const { match } = require('path-to-regexp');
2
3
 
3
4
  /**
package/lib/runBin.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const path = require('node:path');
2
3
  const spawn = require('cross-spawn');
3
4
 
@@ -45,13 +46,13 @@ const spawnPromise = (commandPath, args, options) => {
45
46
  */
46
47
 
47
48
  /**
48
- * @param {Options}
49
+ * @param {Options} options
49
50
  */
50
51
  const runBin = ({ packageName, binName, args, options }) =>
51
52
  spawnPromise(resolveBin(packageName, binName), args, options);
52
53
 
53
54
  /**
54
- * @param {Options}
55
+ * @param {Options} options
55
56
  */
56
57
  const startBin = ({ packageName, binName, args, options }) => {
57
58
  const childProcess = spawn(resolveBin(packageName, binName), args, options);
package/lib/runTsc.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const chalk = require('chalk');
2
3
  const { runBin } = require('./runBin');
3
4
  const { cwd } = require('./cwd');
package/lib/runVocab.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const { compile } = require('@vocab/core');
2
3
  const { getVocabConfig } = require('../config/vocab/vocab');
3
4
 
package/lib/storybook.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const path = require('node:path');
2
3
  const fs = require('node:fs/promises');
3
4
  const debug = require('debug');
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const { getRunCommand, getExecuteCommand } = require('./packageManager');
2
3
 
3
4
  const chalk = require('chalk');
@@ -1,3 +1,7 @@
1
+ /**
2
+ * @jest-environment node
3
+ */
4
+
1
5
  const toPosixPath = require('./toPosixPath');
2
6
 
3
7
  describe('toPosixPath', () => {
@@ -1,7 +1,6 @@
1
1
  const fs = require('node:fs');
2
- const path = require('node:path');
3
- const glob = require('fast-glob');
4
2
  const chalk = require('chalk');
3
+ const { fdir: Fdir } = require('fdir');
5
4
 
6
5
  const printBanner = require('./banner');
7
6
  const exists = require('./exists');
@@ -14,11 +13,18 @@ module.exports = async () => {
14
13
  const lessFileGlobResults = await Promise.all(
15
14
  srcPathsExist
16
15
  .filter((srcPath) => srcPath && fs.statSync(srcPath).isDirectory())
17
- .map(async (srcPath) => await glob(path.join(srcPath, '**/*.less'))),
16
+ .map(async (srcPath) =>
17
+ new Fdir()
18
+ .filter((file) => file.endsWith('.less'))
19
+ .crawl(srcPath)
20
+ .withPromise(),
21
+ ),
18
22
  );
23
+
19
24
  const srcHasLessFiles = lessFileGlobResults.some(
20
25
  (fileArray) => fileArray.length > 0,
21
26
  );
27
+
22
28
  if (srcHasLessFiles) {
23
29
  printBanner('warning', 'LESS styles detected', [
24
30
  `Support for ${chalk.bold('LESS')} has been deprecated.`,
@@ -1,13 +1,13 @@
1
- const { getWhyCommand } = require('./packageManager');
1
+ const { getWhyCommand, isPnpm } = require('./packageManager');
2
2
 
3
3
  const { readFile } = require('node:fs/promises');
4
- const glob = require('fast-glob');
4
+ const { fdir: Fdir } = require('fdir');
5
5
  const semver = require('semver');
6
6
  const chalk = require('chalk');
7
7
 
8
8
  const banner = require('./banner');
9
9
  const track = require('../telemetry');
10
- const { cwd, getPathFromCwd } = require('../lib/cwd');
10
+ const { getPathFromCwd } = require('../lib/cwd');
11
11
  const { paths } = require('../context');
12
12
 
13
13
  /**
@@ -21,30 +21,42 @@ const asyncMap = (list, fn) => {
21
21
  const singletonPackages = ['@vanilla-extract/css'];
22
22
 
23
23
  module.exports = async () => {
24
+ if (isPnpm) {
25
+ // pnpm doesn't nest dependencies in the same way as yarn or npm, so the method used below won't
26
+ // work for detecting duplicate packages
27
+ return;
28
+ }
29
+
24
30
  try {
25
- const packages = [];
26
-
27
- for (const packageName of [
28
- ...paths.compilePackages,
29
- ...singletonPackages,
30
- ]) {
31
- const results = await glob(
32
- [
33
- `node_modules/${packageName}/package.json`,
34
- `node_modules/**/node_modules/${packageName}/package.json`,
35
- ],
36
- {
37
- cwd: cwd(),
38
- },
31
+ /** @type {string[]} */
32
+ const duplicatePackages = [];
33
+ const packagesToCheck = [...paths.compilePackages, ...singletonPackages];
34
+
35
+ const packagePatterns = packagesToCheck.map((packageName) => [
36
+ packageName,
37
+ `node_modules/${packageName}/package.json`,
38
+ ]);
39
+
40
+ const patterns = packagePatterns.map(([, pattern]) => pattern);
41
+
42
+ const results = await new Fdir()
43
+ .withBasePath()
44
+ .filter((file) => patterns.some((pattern) => file.endsWith(pattern)))
45
+ .crawl('./node_modules')
46
+ .withPromise();
47
+
48
+ for (const [packageName, pattern] of packagePatterns) {
49
+ const resultsForPackage = results.filter((result) =>
50
+ result.endsWith(pattern),
39
51
  );
40
52
 
41
- if (results.length > 1) {
53
+ if (resultsForPackage.length > 1) {
42
54
  const messages = [
43
55
  chalk`Multiple copies of {bold ${packageName}} are present in node_modules. This is likely to cause errors, but even if it works, it will probably result in an unnecessarily large bundle size.`,
44
56
  ];
45
57
 
46
58
  messages.push(
47
- results
59
+ resultsForPackage
48
60
  .map((depLocation) => {
49
61
  const { version } = require(getPathFromCwd(depLocation));
50
62
 
@@ -64,14 +76,14 @@ module.exports = async () => {
64
76
  compile_package: packageName,
65
77
  });
66
78
  banner('error', 'Error: Duplicate packages detected', messages);
67
- }
68
79
 
69
- packages.push(...results);
80
+ duplicatePackages.push(...resultsForPackage);
81
+ }
70
82
  }
71
83
 
72
84
  const compilePackages = new Map();
73
85
 
74
- await asyncMap(packages, async (p) => {
86
+ await asyncMap(duplicatePackages, async (p) => {
75
87
  const contents = await readFile(getPathFromCwd(p), {
76
88
  encoding: 'utf8',
77
89
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sku",
3
- "version": "12.4.11",
3
+ "version": "12.5.1",
4
4
  "description": "Front-end development toolkit, powered by Webpack, Babel, CSS Modules, Less and Jest",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -39,7 +39,7 @@
39
39
  "@loadable/server": "^5.14.0",
40
40
  "@loadable/webpack-plugin": "^5.14.0",
41
41
  "@manypkg/find-root": "^2.2.0",
42
- "@pmmmwh/react-refresh-webpack-plugin": "0.5.11",
42
+ "@pmmmwh/react-refresh-webpack-plugin": "^0.5.12",
43
43
  "@storybook/builder-webpack5": "^7.0.17",
44
44
  "@storybook/cli": "^7.0.17",
45
45
  "@storybook/react": "^7.0.17",
@@ -64,7 +64,6 @@
64
64
  "browserslist": "^4.16.1",
65
65
  "browserslist-config-seek": "^2.1.0",
66
66
  "chalk": "^4.1.0",
67
- "command-line-args": "^5.1.1",
68
67
  "cross-spawn": "^7.0.3",
69
68
  "css-loader": "^6.7.1",
70
69
  "css-modules-typescript-loader": "4.0.1",
@@ -74,7 +73,6 @@
74
73
  "dedent": "^1.5.1",
75
74
  "didyoumean2": "^6.0.1",
76
75
  "ejs": "^3.1.8",
77
- "empty-dir": "^3.0.0",
78
76
  "ensure-gitignore": "^1.1.2",
79
77
  "env-ci": "^7.0.0",
80
78
  "esbuild": "^0.19.7",
@@ -84,8 +82,8 @@
84
82
  "eslint-config-seek": "^12.0.1",
85
83
  "exception-formatter": "^2.1.2",
86
84
  "express": "^4.16.3",
87
- "fast-glob": "^3.2.5",
88
85
  "fastest-validator": "^1.9.0",
86
+ "fdir": "^6.1.1",
89
87
  "find-up": "^5.0.0",
90
88
  "get-port": "^5.0.0",
91
89
  "hostile": "^1.3.3",
@@ -97,17 +95,18 @@
97
95
  "less": "^4.1.0",
98
96
  "less-loader": "^12.0.0",
99
97
  "lint-staged": "^11.1.1",
100
- "memoizee": "^0.4.15",
101
98
  "mini-css-extract-plugin": "^2.6.1",
99
+ "minimist": "^1.2.8",
100
+ "nano-memoize": "^3.0.16",
102
101
  "node-html-parser": "^6.1.1",
103
102
  "open": "^7.3.1",
104
103
  "path-to-regexp": "^6.2.0",
104
+ "picomatch": "^3.0.1",
105
105
  "postcss": "^8.4.0",
106
106
  "postcss-loader": "^8.0.0",
107
107
  "prettier": "^2.8.8",
108
108
  "pretty-ms": "^7.0.1",
109
109
  "react-refresh": "^0.14.0",
110
- "rimraf": "^5.0.0",
111
110
  "selfsigned": "^2.1.1",
112
111
  "semver": "^7.3.4",
113
112
  "serialize-javascript": "^6.0.0",
@@ -115,8 +114,7 @@
115
114
  "svgo-loader": "^4.0.0",
116
115
  "terser-webpack-plugin": "^5.1.4",
117
116
  "tree-kill": "^1.2.1",
118
- "typescript": "~5.2.0",
119
- "validate-npm-package-name": "^5.0.0",
117
+ "typescript": "~5.3.0",
120
118
  "webpack": "^5.52.0",
121
119
  "webpack-bundle-analyzer": "^4.6.1",
122
120
  "webpack-dev-server": "^5.0.2",
@@ -128,7 +126,10 @@
128
126
  },
129
127
  "devDependencies": {
130
128
  "@types/cross-spawn": "^6.0.3",
129
+ "@types/debug": "^4.1.12",
131
130
  "@types/express": "^4.17.11",
131
+ "@types/minimist": "^1.2.5",
132
+ "@types/picomatch": "^2.3.3",
132
133
  "@types/react": "^18.2.3",
133
134
  "@types/react-dom": "^18.2.3",
134
135
  "@types/which": "^3.0.0",
@@ -1,7 +1,7 @@
1
1
  // First, ensure the build is running in production mode
2
2
  process.env.NODE_ENV = 'production';
3
3
 
4
- const { rimraf } = require('rimraf');
4
+ const { rm } = require('node:fs/promises');
5
5
  const { argv, config } = require('../config/args');
6
6
  const gracefulSpawn = require('../lib/gracefulSpawn');
7
7
  const { storybookTarget } = require('../context');
@@ -11,7 +11,7 @@ const { setUpStorybookConfigDirectory } = require('../lib/storybook');
11
11
 
12
12
  (async () => {
13
13
  await runVocabCompile();
14
- await rimraf(storybookTarget);
14
+ await rm(storybookTarget, { recursive: true, force: true });
15
15
  await setUpStorybookConfigDirectory();
16
16
 
17
17
  argv.push('build');
package/scripts/build.js CHANGED
@@ -10,7 +10,7 @@ const {
10
10
  copyPublicFiles,
11
11
  cleanTargetDirectory,
12
12
  ensureTargetDirectory,
13
- cleanRenderJs,
13
+ cleanStaticRenderEntry,
14
14
  } = require('../lib/buildFileUtils');
15
15
  const { run } = require('../lib/runWebpack');
16
16
  const createHtmlRenderPlugin = require('../config/webpack/plugins/createHtmlRenderPlugin');
@@ -31,7 +31,7 @@ const { runVocabCompile } = require('../lib/runVocab');
31
31
  }),
32
32
  ),
33
33
  );
34
- await cleanRenderJs();
34
+ await cleanStaticRenderEntry();
35
35
  await copyPublicFiles();
36
36
 
37
37
  const timeTaken = performance.now();
package/scripts/init.js CHANGED
@@ -10,8 +10,7 @@ const {
10
10
  const chalk = require('chalk');
11
11
  const fs = require('node:fs/promises');
12
12
  const { posix: path } = require('node:path');
13
- const emptyDir = require('empty-dir');
14
- const validatePackageName = require('validate-npm-package-name');
13
+ const { isEmptyDir } = require('../lib/isEmptyDir');
15
14
  const dedent = require('dedent');
16
15
  const { setCwd } = require('../lib/cwd');
17
16
  const prettierWrite = require('../lib/runPrettier').write;
@@ -21,7 +20,7 @@ const install = require('../lib/install');
21
20
  const banner = require('../lib/banner');
22
21
  const toPosixPath = require('../lib/toPosixPath');
23
22
  const trace = require('debug')('sku:init');
24
- const glob = require('fast-glob');
23
+ const { fdir: Fdir } = require('fdir');
25
24
  const ejs = require('ejs');
26
25
 
27
26
  const args = require('../config/args');
@@ -48,6 +47,11 @@ const getTemplateFileDestinationFromRoot =
48
47
  return path.join(projectRoot, filePathRelativeToTemplate);
49
48
  };
50
49
 
50
+ // Copied from `package-name-regex@4.0.0`
51
+ // See https://github.com/dword-design/package-name-regex/blob/acae7d482b1d03379003899df4d484238625364d/src/index.js#L1-L2
52
+ const packageNameRegex =
53
+ /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
54
+
51
55
  (async () => {
52
56
  const projectName = args.argv[0];
53
57
 
@@ -89,21 +93,16 @@ const getTemplateFileDestinationFromRoot =
89
93
  'braid-design-system',
90
94
  ].sort();
91
95
 
92
- const {
93
- validForNewPackages,
94
- errors = [],
95
- warnings = [],
96
- } = validatePackageName(appName);
96
+ const isValidPackageName = packageNameRegex.test(appName);
97
97
 
98
- if (!validForNewPackages) {
98
+ if (!isValidPackageName) {
99
99
  console.error(dedent`
100
- Could not create a project called ${chalk.red(`"${appName}"`)} \
101
- because of npm naming restrictions:
100
+ Could not create a project called ${chalk.red(
101
+ `"${appName}"`,
102
+ )} because of npm naming restrictions. \
103
+ Please see https://docs.npmjs.com/cli/configuring-npm/package-json for package name rules.
102
104
  `);
103
105
 
104
- const results = [...errors, ...warnings];
105
- results.forEach((result) => console.error(chalk.red(` * ${result}`)));
106
-
107
106
  process.exit(1);
108
107
  }
109
108
 
@@ -125,7 +124,7 @@ const getTemplateFileDestinationFromRoot =
125
124
 
126
125
  await fs.mkdir(projectName, { recursive: true });
127
126
 
128
- if (!emptyDir.sync(root)) {
127
+ if (!isEmptyDir(root)) {
129
128
  console.log(`The directory ${chalk.green(projectName)} is not empty.`);
130
129
  process.exit(1);
131
130
  }
@@ -152,9 +151,11 @@ const getTemplateFileDestinationFromRoot =
152
151
  process.chdir(root);
153
152
 
154
153
  const templateDirectory = path.join(toPosixPath(__dirname), '../template');
155
- const templateFiles = await glob(`${templateDirectory}/**/*`, {
156
- onlyFiles: true,
157
- });
154
+
155
+ const templateFiles = await new Fdir()
156
+ .withBasePath()
157
+ .crawl(templateDirectory)
158
+ .withPromise();
158
159
 
159
160
  const getTemplateFileDestination = getTemplateFileDestinationFromRoot(
160
161
  root,
package/tsconfig.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "include": ["@loadable/**/*", "@storybook/**/*", "sku-types.d.ts"]
4
- }