@jcoreio/toolchain 4.9.1 → 4.11.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.
@@ -20,7 +20,7 @@ module.exports = async function runHook(hookName) {
20
20
  if (!hook) return
21
21
 
22
22
  if (typeof hook === 'function') {
23
- await hook()
23
+ await hook(...process.argv.slice(2))
24
24
  } else if (hook) {
25
25
  await execa(hook, {
26
26
  cwd: projDir,
@@ -28,6 +28,8 @@ module.exports = async function runHook(hookName) {
28
28
  })
29
29
  }
30
30
  } catch (error) {
31
+ // eslint-disable-next-line no-console
32
+ console.error(error)
31
33
  const { exitCode } = error
32
34
  process.exit(exitCode != null ? exitCode : 1)
33
35
  return
package/githooks.cjs CHANGED
@@ -1,3 +1,41 @@
1
1
  module.exports = {
2
2
  'pre-commit': 'lint-staged',
3
+ 'commit-msg': async (file) => {
4
+ const fs = require('./util/projectFs.cjs')
5
+ const parseCommitMessage = require('./util/parseCommitMessage.cjs')
6
+ const chalk = require('chalk')
7
+ const { monorepoSubpackageJsons } = require('./util/findUps.cjs')
8
+
9
+ let content
10
+ try {
11
+ content = await fs.readFile(file, 'utf8')
12
+ } catch (error) {
13
+ // eslint-disable-next-line no-console
14
+ console.error(
15
+ chalk`{red ✖} failed to read commit message from ${file}`,
16
+ error.message
17
+ )
18
+ process.exit(1)
19
+ }
20
+ try {
21
+ const parsed = parseCommitMessage(content)
22
+ const scope = parsed.scope && parsed.scope[0]
23
+ if (
24
+ scope &&
25
+ monorepoSubpackageJsons &&
26
+ !monorepoSubpackageJsons.some((j) => scope === j.name)
27
+ ) {
28
+ throw new Error(
29
+ `invalid scope: ${scope} - scope must be the name of a package in this monorepo`
30
+ )
31
+ }
32
+ // eslint-disable-next-line no-console
33
+ console.error(chalk`{green ✔} validated commit message`)
34
+ } catch (error) {
35
+ // eslint-disable-next-line no-console
36
+ console.error(chalk`{red ✖} invalid commit message: ${error.message}`)
37
+ process.exit(1)
38
+ return
39
+ }
40
+ },
3
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jcoreio/toolchain",
3
- "version": "4.9.1",
3
+ "version": "4.11.0",
4
4
  "description": "base JS build toolchain",
5
5
  "repository": {
6
6
  "type": "git",
@@ -7,7 +7,7 @@ const parseRepositoryUrl = require('../util/parseRepositoryUrl.cjs')
7
7
  const markdownBadges = require('../util/markdownBadges.cjs')
8
8
 
9
9
  async function create(args = []) {
10
- const prompt = require('prompts')
10
+ const prompt = require('../util/prompt.cjs')
11
11
 
12
12
  let monorepoPackageJson, monorepoProjectDir
13
13
  try {
package/scripts/init.cjs CHANGED
@@ -35,7 +35,7 @@ async function init(args = []) {
35
35
 
36
36
  let selectedToolchains = [...toolchains]
37
37
  if (isInteractive) {
38
- ;({ selectedToolchains } = await require('prompts')({
38
+ ;({ selectedToolchains } = await require('../uitl/prompt')({
39
39
  name: 'selectedToolchains',
40
40
  type: 'multiselect',
41
41
  message: 'Select toolchains to install',
@@ -0,0 +1,66 @@
1
+ const execa = require('../util/execa.cjs')
2
+ const {
3
+ packageJson,
4
+ isMonorepoRoot,
5
+ toolchainManaged,
6
+ } = require('../util/findUps.cjs')
7
+
8
+ async function installOptionalDeps() {
9
+ const choices = new Set()
10
+ const { dependencies = {}, devDependencies = {} } = packageJson
11
+ for (const dep of Object.keys({
12
+ ...toolchainManaged.optionalDependencies,
13
+ ...toolchainManaged.optionalDevDependencies,
14
+ })) {
15
+ if (!dependencies[dep] && !devDependencies[dep]) {
16
+ choices.add(dep)
17
+ }
18
+ }
19
+ if (!choices.size) {
20
+ // eslint-disable-next-line no-console
21
+ console.error('There are no additional optional dependencies to install')
22
+ process.exit(0)
23
+ }
24
+ const finalChoices = [...choices].sort()
25
+ const { selected } = await require('../util/prompt.cjs')({
26
+ name: 'selected',
27
+ type: 'multiselect',
28
+ message: 'Select optional dependencies to install',
29
+ choices: finalChoices,
30
+ })
31
+ const selectedSet = new Set(selected.map((i) => finalChoices[i]))
32
+
33
+ const selectedDeps = Object.keys(
34
+ toolchainManaged.optionalDependencies || {}
35
+ ).filter((dep) => selectedSet.has(dep))
36
+ const selectedDevDeps = Object.keys(
37
+ toolchainManaged.optionalDevDependencies || {}
38
+ ).filter((dep) => selectedSet.has(dep))
39
+
40
+ if (selectedDeps.length) {
41
+ await execa('pnpm', [
42
+ 'add',
43
+ '--prefer-offline',
44
+ ...(isMonorepoRoot ? ['-w'] : []),
45
+ ...selectedDeps.map(
46
+ (dep) => `${dep}@${toolchainManaged.optionalDependencies[dep]}`
47
+ ),
48
+ ])
49
+ }
50
+ if (selectedDevDeps.length) {
51
+ await execa('pnpm', [
52
+ 'add',
53
+ '-D',
54
+ '--prefer-offline',
55
+ ...(isMonorepoRoot ? ['-w'] : []),
56
+ ...selectedDevDeps.map(
57
+ (dep) => `${dep}@${toolchainManaged.optionalDevDependencies[dep]}`
58
+ ),
59
+ ])
60
+ }
61
+ }
62
+
63
+ exports.description =
64
+ 'select and install optional toolchain-managed dependencies'
65
+
66
+ exports.run = installOptionalDeps
@@ -37,6 +37,7 @@ const scripts = toolchainConfig
37
37
  },
38
38
  },
39
39
  'install-git-hooks': require('./install-git-hooks.cjs'),
40
+ 'install-optional-deps': require('./install-optional-deps.cjs'),
40
41
  ...(isMonorepoRoot ? { create: require('./create.cjs') } : {}),
41
42
  ...getPluginsObjectSync('scripts'),
42
43
  ...Object.fromEntries(
@@ -0,0 +1,10 @@
1
+ class ParseError extends Error {
2
+ constructor(message, from, to = from) {
3
+ super(message)
4
+ this.name = 'ParseError'
5
+ this.from = from
6
+ this.to = to
7
+ }
8
+ }
9
+
10
+ module.exports = ParseError
@@ -0,0 +1,69 @@
1
+ class ParseState {
2
+ constructor(
3
+ input,
4
+ {
5
+ /**
6
+ * The starting index (defaults to 0)
7
+ */
8
+ start = 0,
9
+ /**
10
+ * The ending index (defaults to input.length)
11
+ */
12
+ end = input.length,
13
+ /**
14
+ * Flags to add to all regexes
15
+ */
16
+ flags = 'g',
17
+ } = {}
18
+ ) {
19
+ this.input = input
20
+ this.start = start
21
+ this.index = start
22
+ this.end = end
23
+ this.flags = flags
24
+ }
25
+
26
+ get done() {
27
+ return this.index >= this.end
28
+ }
29
+
30
+ /**
31
+ * If pattern matches the input at the current index, returns the match,
32
+ * but doesn't advance the index.
33
+ */
34
+ peek(pattern) {
35
+ if (typeof pattern === 'string') pattern = toRegExp(pattern)
36
+ pattern = new RegExp(
37
+ pattern.source,
38
+ `${this.flags}${pattern.flags.replace(
39
+ new RegExp(`[${this.flags}]`, 'g'),
40
+ ''
41
+ )}`
42
+ )
43
+ pattern.lastIndex = this.index
44
+ const match = pattern.exec(this.input)
45
+ return match &&
46
+ match.index === this.index &&
47
+ match.index + match[0].length <= this.end
48
+ ? match
49
+ : undefined
50
+ }
51
+
52
+ /**
53
+ * If pattern matches the input at the current index, returns the match,
54
+ * and advances the index to the end of the match.
55
+ */
56
+ match(pattern) {
57
+ const match = this.peek(pattern)
58
+ if (match) {
59
+ this.index += match[0].length
60
+ }
61
+ return match
62
+ }
63
+ }
64
+
65
+ module.exports = ParseState
66
+
67
+ function toRegExp(s, flags = '') {
68
+ return new RegExp(s.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&'), flags)
69
+ }
package/util/confirm.cjs CHANGED
@@ -7,7 +7,7 @@ module.exports = async function confirm({
7
7
  }) {
8
8
  return isInteractive
9
9
  ? (
10
- await require('prompts')({
10
+ await require('./prompt')({
11
11
  type: 'confirm',
12
12
  message,
13
13
  initial,
@@ -0,0 +1,6 @@
1
+ const once = require('./once.cjs')
2
+ const { globExists } = require('./glob.cjs')
3
+
4
+ module.exports = once(async function hasJSSources() {
5
+ return await globExists('src/**/*.{js,cjs,mjs,jsx,cjsx,mjsx}')
6
+ })
@@ -0,0 +1,45 @@
1
+ const ParseState = require('./ParseState.cjs')
2
+ const ParseError = require('./ParseError.cjs')
3
+
4
+ function parseCommitMessage(message) {
5
+ const state = new ParseState(message)
6
+ const type = state.match(/[^(:! )]+/)
7
+ if (!type || !/^[-a-z]+$/.test(type)) {
8
+ throw new ParseError(
9
+ 'must begin with a type containing lowercase letters or - like `fix` or `feat`',
10
+ 0
11
+ )
12
+ }
13
+ let scope
14
+ if (state.match('(')) {
15
+ scope = state.match(/[^)]+/)
16
+ if (!scope) {
17
+ throw new ParseError('a scope is required after (', state.index)
18
+ }
19
+ if (!state.match(')')) {
20
+ throw new ParseError('missing ) after scope', state.index)
21
+ }
22
+ }
23
+ const bang = state.match(/!/)
24
+ if (!state.match(':')) {
25
+ throw new ParseError('missing : after type/scope', state.index)
26
+ }
27
+ if (!state.match(/\s+/)) {
28
+ throw new ParseError('missing space after :', state.index)
29
+ }
30
+
31
+ const description = state.match(/.+$/m)
32
+ if (!description || !/\S/.test(description)) {
33
+ throw new ParseError('missing description', state.index)
34
+ }
35
+ if (!state.match(/\n\s*(\n|$)/) && !state.done) {
36
+ throw new ParseError(
37
+ 'there must be a blank line before longer commit body',
38
+ state.index
39
+ )
40
+ }
41
+ const body = state.match(/(.|\n)+/)
42
+ return { type, scope, bang, description, body }
43
+ }
44
+
45
+ module.exports = parseCommitMessage
@@ -0,0 +1,15 @@
1
+ const prompts = require('prompts')
2
+
3
+ module.exports = async function prompt(...args) {
4
+ const handleKeypress = function (_chunk, key) {
5
+ if (key && key.name === 'c' && key.ctrl) {
6
+ process.kill(process.pid, 'SIGINT')
7
+ }
8
+ }
9
+ try {
10
+ process.stdin.on('keypress', handleKeypress)
11
+ return await prompts(...args)
12
+ } finally {
13
+ process.stdin.off('keypress', handleKeypress)
14
+ }
15
+ }