Skip to content

Commit

Permalink
Refactor configuration logic
Browse files Browse the repository at this point in the history
  • Loading branch information
ehmicky committed Jan 16, 2022
1 parent 0853615 commit db7a10b
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 134 deletions.
17 changes: 17 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"p-map": "^5.3.0",
"p-map-series": "^3.0.0",
"p-props": "^5.0.0",
"p-reduce": "^3.0.0",
"path-exists": "^5.0.0",
"path-type": "^5.0.0",
"precise-now": "^0.3.1",
Expand Down
4 changes: 2 additions & 2 deletions src/combination/tasks/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { basename } from 'path'
import { isNotJunk } from 'junk'

import { lookupFiles } from '../../config/lookup.js'
import { DEFAULT_TASKS_BASE } from '../../config/path.js'

// Apply default value for `tasks`. Applied on each runner.
// This only applies when `tasks` is `undefined`
Expand All @@ -16,7 +17,7 @@ export const applyDefaultTasks = async function ({ config }) {
}

const resolveDefaultTasks = async function () {
return await lookupFiles(isTaskPath, TOP_LEVEL_BASE)
return await lookupFiles(isTaskPath, DEFAULT_TASKS_BASE)
}

const isTaskPath = function (filePath) {
Expand All @@ -25,4 +26,3 @@ const isTaskPath = function (filePath) {
}

const TASKS_BASENAME = 'tasks.'
const TOP_LEVEL_BASE = '.'
20 changes: 20 additions & 0 deletions src/config/load/default.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { basename } from 'path'

import { lookupFiles } from '../lookup.js'
import { CLI_FLAGS_BASE } from '../path.js'

import { getConfigFilenames } from './contents.js'
import { isNpxCall } from './npx.js'

// Retrieve the default value for the `config` CLI flag
export const getDefaultConfig = async function () {
if (isNpxCall()) {
return []
}

const defaultConfigFilenames = getConfigFilenames()
return await lookupFiles(
(filePath) => defaultConfigFilenames.includes(basename(filePath)),
CLI_FLAGS_BASE,
)
}
37 changes: 0 additions & 37 deletions src/config/load/file.js

This file was deleted.

20 changes: 8 additions & 12 deletions src/config/load/info.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
import { dirname } from 'path'

import {
checkArrayItems,
checkDefinedString,
normalizeOptionalArray,
} from '../check.js'

import { loadConfigContents } from './contents.js'
import { resolveConfigPath } from './resolve.js'

// Load the main configuration file `spyd.*` and any parents.
// The configuration file is optional, so this can return an empty array.
// This allows benchmarking on-the-fly in a terminal without having to create a
// configuration file.
// The `config` property can optionally be an array.
// - This allow merging a shared configuration with a non-shared one
// - It can be an empty array. This is useful to remove the default value for
// the `config` top-level flag programmatically.
export const getConfigsInfos = async function (config, base) {
const configs = normalizeOptionalArray(config)
checkArrayItems(configs, 'config', checkDefinedString)
export const getConfigsInfos = async function (configOpts, base) {
const configInfos = await Promise.all(
configs.map((configB) => getConfigInfos(configB, base)),
configOpts.map((configOpt) => getConfigInfos(configOpt, base)),
)
return configInfos.flat()
}

const getConfigInfos = async function (config, base) {
const configPath = await resolveConfigPath(config, base)
const getConfigInfos = async function (configOpt, base) {
const configPath = await resolveConfigPath(configOpt, base)

if (configPath === undefined) {
return []
Expand Down
17 changes: 10 additions & 7 deletions src/config/load/main.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { mergeConfigs } from '../merge/main.js'
import { CLI_FLAGS_BASE } from '../path.js'

import { loadConfigFile } from './file.js'
import { getConfigsInfos } from './info.js'
import { addNpxShortcut } from './npx.js'

// Load the configuration, shallow merged in priority order:
// - any CLI or programmatic flags
Expand All @@ -11,15 +13,16 @@ import { loadConfigFile } from './file.js'
// case-sensitiveness (due to Windows) and fewer allowed delimiters (due
// to underscores only being allowed in Unix)
// We purposely remove the `config` property during this step.
export const loadConfig = async function ({ config, ...configFlags }) {
const configInfos = await loadConfigFile(config)
export const loadConfig = async function (configOpts, configFlags) {
const configOptsA = addNpxShortcut(configOpts)
const configInfos = await getConfigsInfos(configOptsA, CLI_FLAGS_BASE)
const configInfosA = [
...configInfos,
{ configContents: configFlags, base: '.' },
{ configContents: configFlags, base: CLI_FLAGS_BASE },
]
const configs = configInfosA.map(getConfigContents)
const configA = mergeConfigs(configs)
return { config: configA, configInfos: configInfosA }
const configsB = configInfosA.map(getConfigContents)
const configB = mergeConfigs(configsB)
return { config: configB, configInfos: configInfosA }
}

const getConfigContents = function ({ configContents }) {
Expand Down
16 changes: 7 additions & 9 deletions src/config/load/npx.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { env } from 'process'

import { normalizeOptionalArray } from '../check.js'
import { CONFIG_PLUGIN_TYPE } from '../plugin/types.js'

// In principle, users can use `npx` with the "npm" resolver by doing:
Expand All @@ -15,23 +14,22 @@ import { CONFIG_PLUGIN_TYPE } from '../plugin/types.js'
// This behaves as if `--config=spyd-config-{name}` has been specified:
// - Additional `--config` flags are kept
// - The `--config` flag does not use its default value
export const addNpxShortcut = function (config) {
if (!isNpxCall()) {
return config
}

const npxConfigs = env.npm_config_package.split('\n').filter(isSharedConfig)
return [...npxConfigs, ...normalizeOptionalArray(config)]
export const addNpxShortcut = function (configOpts) {
return isNpxCall() ? [...getNpxConfigs(), ...configOpts] : configOpts
}

const isNpxCall = function () {
export const isNpxCall = function () {
return (
env.npm_command === 'exec' &&
env.npm_config_package !== undefined &&
env.npm_config_package !== ''
)
}

const getNpxConfigs = function () {
return env.npm_config_package.split('\n').filter(isSharedConfig)
}

const isSharedConfig = function (npxPackage) {
return npxPackage.startsWith(CONFIG_PLUGIN_TYPE.modulePrefix)
}
40 changes: 20 additions & 20 deletions src/config/load/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,49 +10,49 @@ import { CONFIG_PLUGIN_TYPE } from '../plugin/types.js'
// - a file path
// - a Node module name starting with "spyd-config-"
// - a "resolver:arg" string which applies resolver-specific logic
export const resolveConfigPath = async function (config, base) {
if (isNpmResolver(config)) {
return resolveNpm(config, base)
export const resolveConfigPath = async function (configOpt, base) {
if (isNpmResolver(configOpt)) {
return resolveNpm(configOpt, base)
}

if (isResolver(config)) {
return await useResolver(config, base)
if (isResolver(configOpt)) {
return await useResolver(configOpt, base)
}

return await resolveFile(config, base)
return await resolveFile(configOpt, base)
}

// Configs can be Node modules.
// They must be named "spyd-config-{name}" to enforce naming convention and
// allow distinguishing them from file paths.
// We do not use a shorter id like "npm:{name}" so users do not need two
// different ids: one for `npm install` and one for the `config` property.
const isNpmResolver = function (config) {
return config.startsWith(CONFIG_PLUGIN_TYPE.modulePrefix)
const isNpmResolver = function (configOpt) {
return configOpt.startsWith(CONFIG_PLUGIN_TYPE.modulePrefix)
}

const resolveNpm = function (config, base) {
return getPluginPath(config, CONFIG_PLUGIN_TYPE.type, base)
const resolveNpm = function (configOpt, base) {
return getPluginPath(configOpt, CONFIG_PLUGIN_TYPE.type, base)
}

// Additional resolvers.
// We don't have any of them yet.
// Their name must be namespaced with "{resolver}:".
// We do not use this type of namespaces for the other resolvers since they are
// more commonly used.
const isResolver = function (config) {
return RESOLVER_REGEXP.test(config)
const isResolver = function (configOpt) {
return RESOLVER_REGEXP.test(configOpt)
}

const useResolver = async function (config, base) {
const useResolver = async function (configOpt, base) {
const {
groups: { name, arg },
} = RESOLVER_REGEXP.exec(config)
} = RESOLVER_REGEXP.exec(configOpt)

const resolverFunc = RESOLVERS[name]

if (resolverFunc === undefined) {
throw new UserError(`Resolver "${name}" does not exist: "${config}"`)
throw new UserError(`Resolver "${name}" does not exist: "${configOpt}"`)
}

return await resolverFunc(arg, base)
Expand All @@ -62,12 +62,12 @@ const RESOLVER_REGEXP = /^(?<name>[a-z]+):(?<arg>.*)$/u

const RESOLVERS = {}

const resolveFile = async function (config, base) {
const configA = resolve(base, config)
const resolveFile = async function (configOpt, base) {
const configPath = resolve(base, configOpt)

if (!(await isFile(configA))) {
throw new UserError(`"config" file does not exist: ${configA}`)
if (!(await isFile(configPath))) {
throw new UserError(`"config" file does not exist: ${configPath}`)
}

return configA
return configPath
}
21 changes: 16 additions & 5 deletions src/config/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,20 @@ import { normalizeConfig } from './normalize.js'
import { addPlugins } from './plugin/add.js'

// Retrieve configuration
export const getConfig = async function (command, configFlags = {}) {
const { config, configInfos } = await loadConfig(configFlags)
const configA = normalizeConfig(config, command, configInfos)
const configB = await addPlugins(configA, command)
return configB
export const getConfig = async function (
command,
{ config: configOpt, ...configFlags } = {},
) {
const { config: configOpts } = await normalizeConfig(
{ config: configOpt },
command,
[],
)
const { config: configA, configInfos } = await loadConfig(
configOpts,
configFlags,
)
const configB = await normalizeConfig(configA, command, configInfos)
const configC = await addPlugins(configB, command)
return configC
}
Loading

0 comments on commit db7a10b

Please sign in to comment.