Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions args.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ const CLI_OPTIONS = {
'include-hooks': { type: 'boolean' },
'trust-proxy-enabled': { type: 'boolean' },
help: { type: 'boolean', short: 'h' },
'debug-port': { type: 'string', short: 'I' }
'debug-port': { type: 'string', short: 'I' },
yaml: { type: 'boolean' }
}

module.exports = function parseCliArgs (args) {
Expand All @@ -68,7 +69,11 @@ module.exports = function parseCliArgs (args) {
const configFileOptions = commandLineArguments.config ? requireModule(commandLineArguments.config) : undefined

const additionalArgs = commandLineArguments['--'] || []
const pluginParsed = parseArgs(additionalArgs, { options: {}, strict: false })
const pluginParsed = parseArgs(additionalArgs, {
coerceUnknownNumbers: true,
inferUnknownOptions: true,
strict: true
})
const { _, ...pluginOptions } = pluginParsed
const ignoreWatchArg = commandLineArguments.ignoreWatch || configFileOptions?.ignoreWatch || ''
const followWatchArg = commandLineArguments.followWatch || configFileOptions?.followWatch || ''
Expand Down
6 changes: 4 additions & 2 deletions generate-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const {
} = require('node:fs').promises
const { existsSync } = require('node:fs')
const path = require('node:path')
const chalk = require('chalk')
const chalk = require('chalk').default
const generify = require('generify')
const parseArgs = require('./lib/parse-args')
const cliPkg = require('./package')
Expand Down Expand Up @@ -69,7 +69,9 @@ async function generate (dir, template) {
pkg.scripts = Object.assign(pkg.scripts || {}, template.scripts)
pkg.dependencies = Object.assign(pkg.dependencies || {}, template.dependencies)
pkg.devDependencies = Object.assign(pkg.devDependencies || {}, template.devDependencies)
pkg.tstyche = Object.assign(pkg.tstyche || {}, template.tstyche)
if (template.tstyche) {
pkg.tstyche = Object.assign(pkg.tstyche || {}, template.tstyche)
}

log('debug', 'edited package.json, saving')

Expand Down
2 changes: 1 addition & 1 deletion generate.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const {
existsSync
} = require('node:fs')
const path = require('node:path')
const chalk = require('chalk')
const chalk = require('chalk').default
const generify = require('generify')
const parseArgs = require('./lib/parse-args')
const cliPkg = require('./package')
Expand Down
94 changes: 91 additions & 3 deletions lib/parse-args.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,21 @@ function normalizeArgs (args, options) {
}
} else {
// Non-boolean option: --key value
normalized.push(`--${keyKebab}`)
i++
if (i < args.length) {
// Convert to string because parseArgs requires string values
normalized.push(String(args[i]))
const value = String(args[i])
// util.parseArgs treats a dash-prefixed separate value as
// ambiguous; use the inline form for negative numeric values.
if (/^-\d/.test(value)) {
normalized.push(`--${keyKebab}=${value}`)
} else {
normalized.push(`--${keyKebab}`)
normalized.push(value)
}
i++
} else {
normalized.push(`--${keyKebab}`)
}
}
continue
Expand All @@ -100,8 +109,79 @@ function normalizeArgs (args, options) {
return normalized
}

// Infer option types for the plugin-specific arguments that are intentionally
// not known by fastify-cli. This preserves yargs-parser's `--key value`
// behavior while still using util.parseArgs for the actual parsing.
function inferUnknownOptions (args) {
const options = {}

function addOption (key, type, short) {
if (!key) return

const current = options[key]
if (current?.type === 'string' || (type === 'boolean' && current)) return

options[key] = { type }
if (short) options[key].short = short
}

for (let i = 0; i < args.length; i++) {
const arg = String(args[i])
if (arg === '--') break

if (arg.startsWith('--')) {
const equals = arg.indexOf('=')
if (equals > 2) {
addOption(arg.slice(2, equals), 'string')
continue
}

const key = arg.slice(2)
const next = args[i + 1]
const hasValue = next !== undefined &&
(!String(next).startsWith('-') || /^-\d/.test(String(next)))
addOption(key, hasValue ? 'string' : 'boolean')
if (hasValue) i++
continue
}

if (arg.startsWith('-') && arg.length > 1) {
const shortOptions = arg.slice(1)
if (shortOptions.length > 1 && !shortOptions.includes('=')) {
for (const short of shortOptions) addOption(short, 'boolean', short)
continue
}

const equals = shortOptions.indexOf('=')
const key = equals === -1 ? shortOptions : shortOptions.slice(0, equals)
if (equals !== -1) {
addOption(key, 'string', key)
} else {
const next = args[i + 1]
const hasValue = next !== undefined &&
(!String(next).startsWith('-') || /^-\d/.test(String(next)))
addOption(key, hasValue ? 'string' : 'boolean', key)
if (hasValue) i++
}
}
}

return options
}

function coerceUnknownNumber (value) {
if (Array.isArray(value)) return value.map(coerceUnknownNumber)
if (typeof value !== 'string' || value.length === 0) return value

const numeric = /^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(value) ||
/^0x[\da-f]+$/i.test(value)
return numeric ? Number(value) : value
}

function parseArgsStandard (args, config) {
const options = config.options || {}
const options = config.inferUnknownOptions
? inferUnknownOptions(args)
: config.options || {}

// Build full options map
const fullOptions = {}
Expand Down Expand Up @@ -186,6 +266,14 @@ function parseArgsStandard (args, config) {
}
}

if (config.coerceUnknownNumbers) {
for (const key of Object.keys(result)) {
if (key !== '_' && key !== '--') {
result[key] = coerceUnknownNumber(result[key])
}
}
}

return result
}

Expand Down
2 changes: 1 addition & 1 deletion lib/watch/fork.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const chalk = require('chalk')
const chalk = require('chalk').default
const { stop, runFastify } = require('../../start')

const {
Expand Down
2 changes: 1 addition & 1 deletion lib/watch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

const path = require('node:path')
const cp = require('node:child_process')
const chalk = require('chalk')
const chalk = require('chalk').default
const { arrayToRegExp, logWatchVerbose } = require('./utils')
const { GRACEFUL_SHUT } = require('./constants.js')

Expand Down
2 changes: 1 addition & 1 deletion lib/watch/utils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const chalk = require('chalk')
const chalk = require('chalk').default
const path = require('node:path')

const arrayToRegExp = (arr) => {
Expand Down
2 changes: 1 addition & 1 deletion log.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict'

const chalk = require('chalk')
const chalk = require('chalk').default

const levels = {
debug: 0,
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"unit:cjs": "node suite-runner.js \"templates/app/test/**/*.test.js\"",
"unit:esm": "node suite-runner.js \"templates/app-esm/test/**/*.test.js\"",
"unit:ts-cjs": "cross-env TS_NODE_PROJECT=./test/configs/ts-cjs.tsconfig.json node -r ts-node/register suite-runner.js \"templates/app-ts/test/**/*.test.ts\"",
"unit:ts-esm": "cross-env TS_NODE_PROJECT=./test/configs/ts-esm.tsconfig.json FASTIFY_AUTOLOAD_TYPESCRIPT=1 node -r ts-node/register --loader ts-node/esm suite-runner.js \"templates/app-ts-esm/test/**/*.test.ts\"",
"unit:ts-esm": "cross-env TS_NODE_PROJECT=./test/configs/ts-esm.tsconfig.json FASTIFY_AUTOLOAD_TYPESCRIPT=1 node suite-runner.js \"templates/app-ts-esm/test/**/*.test.ts\" \"--loader=ts-node/esm\"",
"unit:suites": "node should-skip-test-suites.js || npm run all-suites",
"all-suites": "npm run unit:cjs && npm run unit:esm && npm run unit:ts-cjs && npm run unit:ts-esm",
"unit:cli-js-esm": "node suite-runner.js \"test/esm/**/*.test.js\"",
Expand Down Expand Up @@ -83,4 +83,4 @@
"typescript": "~6.0.2",
"walker": "^1.0.8"
}
}
}
2 changes: 1 addition & 1 deletion start.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

const { loadEnvQuitely } = require('./env-loader')
loadEnvQuitely()
const isDocker = require('is-docker')
const isDocker = require('is-docker').default

const closeWithGrace = require('close-with-grace')
const deepmerge = require('@fastify/deepmerge')({
Expand Down
24 changes: 15 additions & 9 deletions suite-runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,26 @@ const { spec } = require('node:test/reporters')
const path = require('node:path')
const { glob } = require('glob')

const pattern = process.argv[process.argv.length - 1]
async function main () {
const [pattern, ...workerExecArgv] = process.argv.slice(2)

console.info(`Running tests matching ${pattern}`)
const timeout = 10 * 60 * 1000 // 10 minutes
glob(pattern, (err, matches) => {
if (err) {
console.error(err)
process.exit(1)
}
console.info(`Running tests matching ${pattern}`)
const timeout = 10 * 60 * 1000 // 10 minutes
const matches = await glob(pattern)
const resolved = matches.map(file => path.resolve(file))
const testRs = run({ files: resolved, timeout })
const runOptions = { files: resolved, timeout }
if (workerExecArgv.length > 0) {
runOptions.execArgv = workerExecArgv
}
const testRs = run(runOptions)
.on('test:fail', () => {
process.exitCode = 1
})
.compose(spec)
testRs.pipe(process.stdout)
}

main().catch(err => {
console.error(err)
process.exit(1)
})
13 changes: 12 additions & 1 deletion test/args.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,7 @@ test('should parse custom plugin options', t => {
a: true,
b: true,
c: true,
hello: true
hello: 'world'
},
bodyLimit: 5242880,
debug: true,
Expand All @@ -308,6 +308,17 @@ test('should parse custom plugin options', t => {
})
})

test('should parse plugin options with negative values', t => {
const parsedArgs = parseArgs([
'app.js',
'--',
'--offset',
'-1'
])

t.assert.equal(parsedArgs.pluginOptions.offset, -1)
})

test('should parse config file correctly and prefer config values over default ones', t => {
t.plan(1)

Expand Down
3 changes: 2 additions & 1 deletion test/configs/ts-cjs.tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "../../node_modules/fastify-tsconfig/tsconfig.json",
"compilerOptions": {
"outDir": "dist",
"sourceMap": true
"sourceMap": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}
3 changes: 2 additions & 1 deletion test/configs/ts-esm.tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"moduleResolution": "NodeNext",
"module": "NodeNext",
"target": "ES2022",
"esModuleInterop": true
"esModuleInterop": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}
2 changes: 1 addition & 1 deletion util.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const fs = require('node:fs')
const path = require('node:path')
const url = require('node:url')
const semver = require('semver')
const pkgUp = require('pkg-up')
const { pkgUp } = require('pkg-up')
const resolveFrom = require('resolve-from')

const moduleSupport = semver.satisfies(process.version, '>= 14 || >= 12.17.0 < 13.0.0')
Expand Down