diff --git a/.changeset/fifty-stingrays-flow.md b/.changeset/fifty-stingrays-flow.md new file mode 100644 index 000000000000..3f4b96b04969 --- /dev/null +++ b/.changeset/fifty-stingrays-flow.md @@ -0,0 +1,6 @@ +--- +'astro': patch +'@astrojs/db': patch +--- + +Refactors internally to use `node:util` `parseArgs` instead of `yargs-parser` diff --git a/.changeset/rude-queens-shop.md b/.changeset/rude-queens-shop.md new file mode 100644 index 000000000000..6610b16a5320 --- /dev/null +++ b/.changeset/rude-queens-shop.md @@ -0,0 +1,7 @@ +--- +'create-astro': patch +'@astrojs/upgrade': patch +--- + +Refactors internally to use `node:util` `parseArgs` instead of `arg` + diff --git a/packages/astro/package.json b/packages/astro/package.json index aaca3b8b0aa9..aaae869e51e3 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -181,7 +181,6 @@ "vite": "^5.4.0", "vitefu": "^0.2.5", "which-pm": "^3.0.0", - "yargs-parser": "^21.1.1", "zod": "^3.23.8", "zod-to-json-schema": "^3.23.2" }, @@ -206,7 +205,6 @@ "@types/js-yaml": "^4.0.9", "@types/prompts": "^2.4.9", "@types/semver": "^7.5.8", - "@types/yargs-parser": "^21.0.3", "astro-scripts": "workspace:*", "cheerio": "1.0.0", "eol": "^0.9.1", diff --git a/packages/astro/performance/content-benchmark.mjs b/packages/astro/performance/content-benchmark.mjs index a710bd762500..98ef5f0eac32 100644 --- a/packages/astro/performance/content-benchmark.mjs +++ b/packages/astro/performance/content-benchmark.mjs @@ -1,8 +1,8 @@ /* eslint-disable no-console */ import { fileURLToPath } from 'node:url'; +import { parseArgs } from 'node:util'; import { bold, cyan, dim } from 'kleur/colors'; -import yargs from 'yargs-parser'; import { loadFixture } from '../test/test-utils.js'; import { generatePosts } from './scripts/generate-posts.mjs'; @@ -40,7 +40,7 @@ async function benchmark({ fixtures, templates, numPosts }) { // Test the build performance for content collections across multiple file types (md, mdx, mdoc) (async function benchmarkAll() { try { - const flags = yargs(process.argv.slice(2)); + const { values: flags } = parseArgs({ strict: false }); const test = Array.isArray(flags.test) ? flags.test : typeof flags.test === 'string' diff --git a/packages/astro/performance/package.json b/packages/astro/performance/package.json index c0833b952288..36d62528176b 100644 --- a/packages/astro/performance/package.json +++ b/packages/astro/performance/package.json @@ -11,8 +11,6 @@ "author": "", "license": "ISC", "devDependencies": { - "@types/yargs-parser": "^21.0.3", - "kleur": "^4.1.5", - "yargs-parser": "^21.1.1" + "kleur": "^4.1.5" } } diff --git a/packages/astro/src/cli/add/index.ts b/packages/astro/src/cli/add/index.ts index 7d33fe33a5be..f710184d2cb9 100644 --- a/packages/astro/src/cli/add/index.ts +++ b/packages/astro/src/cli/add/index.ts @@ -9,7 +9,6 @@ import ora from 'ora'; import preferredPM from 'preferred-pm'; import prompts from 'prompts'; import maxSatisfying from 'semver/ranges/max-satisfying.js'; -import type yargs from 'yargs-parser'; import { loadTSConfig, resolveConfig, @@ -29,14 +28,14 @@ import { appendForwardSlash } from '../../core/path.js'; import { apply as applyPolyfill } from '../../core/polyfill.js'; import { ensureProcessNodeEnv, parseNpmName } from '../../core/util.js'; import { eventCliSession, telemetry } from '../../events/index.js'; -import { createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; import { fetchPackageJson, fetchPackageVersions } from '../install-package.js'; import { generate, parse, t, visit } from './babel.js'; import { ensureImport } from './imports.js'; import { wrapDefaultExport } from './wrapper.js'; interface AddOptions { - flags: yargs.Arguments; + flags: Flags; } interface IntegrationInfo { @@ -143,7 +142,7 @@ export async function add(names: string[], { flags }: AddOptions) { } // Some packages might have a common alias! We normalize those here. - const cwd = flags.root; + const cwd = inlineConfig.root; const logger = createLoggerFromFlags(flags); const integrationNames = names.map((name) => (ALIASES.has(name) ? ALIASES.get(name)! : name)); const integrations = await validateIntegrations(integrationNames); @@ -249,7 +248,7 @@ export async function add(names: string[], { flags }: AddOptions) { const rawConfigPath = await resolveConfigPath({ root: rootPath, - configFile: flags.config, + configFile: inlineConfig.configFile, fs: fsMod, }); let configURL = rawConfigPath ? pathToFileURL(rawConfigPath) : undefined; @@ -580,7 +579,7 @@ async function updateAstroConfig({ }: { configURL: URL; ast: t.File; - flags: yargs.Arguments; + flags: Flags; logger: Logger; logAdapterInstructions: boolean; }): Promise { @@ -717,7 +716,7 @@ async function tryToInstallIntegrations({ }: { integrations: IntegrationInfo[]; cwd?: string; - flags: yargs.Arguments; + flags: Flags; logger: Logger; }): Promise { const installCommand = await getInstallIntegrationsCommand({ integrations, cwd, logger }); @@ -893,7 +892,7 @@ async function updateTSConfig( cwd = process.cwd(), logger: Logger, integrationsInfo: IntegrationInfo[], - flags: yargs.Arguments, + flags: Flags, ): Promise { const integrations = integrationsInfo.map( (integration) => integration.id as frameworkWithTSSettings, @@ -996,7 +995,7 @@ function parseIntegrationName(spec: string) { return { scope, name, tag }; } -async function askToContinue({ flags }: { flags: yargs.Arguments }): Promise { +async function askToContinue({ flags }: { flags: Flags }): Promise { if (flags.yes || flags.y) return true; const response = await prompts({ @@ -1038,7 +1037,7 @@ function getDiffContent(input: string, output: string): string | null { async function setupIntegrationConfig(opts: { root: URL; logger: Logger; - flags: yargs.Arguments; + flags: Flags; integrationName: string; possibleConfigFiles: string[]; defaultConfigFile: string; diff --git a/packages/astro/src/cli/build/index.ts b/packages/astro/src/cli/build/index.ts index 15ff584317d9..9c10aff1569e 100644 --- a/packages/astro/src/cli/build/index.ts +++ b/packages/astro/src/cli/build/index.ts @@ -1,10 +1,9 @@ -import type yargs from 'yargs-parser'; import _build from '../../core/build/index.js'; import { printHelp } from '../../core/messages.js'; -import { flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, flagsToAstroInlineConfig } from '../flags.js'; interface BuildOptions { - flags: yargs.Arguments; + flags: Flags; } export async function build({ flags }: BuildOptions) { @@ -25,5 +24,5 @@ export async function build({ flags }: BuildOptions) { const inlineConfig = flagsToAstroInlineConfig(flags); - await _build(inlineConfig, { force: flags.force ?? false }); + await _build(inlineConfig, { force: !!flags.force }); } diff --git a/packages/astro/src/cli/check/index.ts b/packages/astro/src/cli/check/index.ts index a95e1074a59a..490d23970b51 100644 --- a/packages/astro/src/cli/check/index.ts +++ b/packages/astro/src/cli/check/index.ts @@ -1,13 +1,15 @@ import path from 'node:path'; -import type { Arguments } from 'yargs-parser'; import { ensureProcessNodeEnv } from '../../core/util.js'; -import { createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; import { getPackage } from '../install-package.js'; -export async function check(flags: Arguments) { +export async function check(flags: Flags) { ensureProcessNodeEnv('production'); const logger = createLoggerFromFlags(flags); - const getPackageOpts = { skipAsk: flags.yes || flags.y, cwd: flags.root }; + const getPackageOpts = { + skipAsk: !!flags.yes || !!flags.y, + cwd: typeof flags.root == 'string' ? flags.root : undefined, + }; const checkPackage = await getPackage( '@astrojs/check', logger, diff --git a/packages/astro/src/cli/db/index.ts b/packages/astro/src/cli/db/index.ts index dc6da36e1dff..c6be7411bb2a 100644 --- a/packages/astro/src/cli/db/index.ts +++ b/packages/astro/src/cli/db/index.ts @@ -1,18 +1,26 @@ -import type { Arguments } from 'yargs-parser'; import type { AstroConfig } from '../../@types/astro.js'; import { resolveConfig } from '../../core/config/config.js'; import { apply as applyPolyfill } from '../../core/polyfill.js'; -import { createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; import { getPackage } from '../install-package.js'; +interface YargsArguments { + _: Array; + '--'?: Array; + [argName: string]: any; +} + type DBPackage = { - cli: (args: { flags: Arguments; config: AstroConfig }) => unknown; + cli: (args: { flags: YargsArguments; config: AstroConfig }) => unknown; }; -export async function db({ flags }: { flags: Arguments }) { +export async function db({ positionals, flags }: { positionals: string[]; flags: Flags }) { applyPolyfill(); const logger = createLoggerFromFlags(flags); - const getPackageOpts = { skipAsk: flags.yes || flags.y, cwd: flags.root }; + const getPackageOpts = { + skipAsk: !!flags.yes || !!flags.y, + cwd: typeof flags.root == 'string' ? flags.root : undefined, + }; const dbPackage = await getPackage('@astrojs/db', logger, getPackageOpts, []); if (!dbPackage) { @@ -27,5 +35,10 @@ export async function db({ flags }: { flags: Arguments }) { const inlineConfig = flagsToAstroInlineConfig(flags); const { astroConfig } = await resolveConfig(inlineConfig, 'build'); - await cli({ flags, config: astroConfig }); + const yargsArgs: YargsArguments = { + _: positionals, + ...flags, + }; + + await cli({ flags: yargsArgs, config: astroConfig }); } diff --git a/packages/astro/src/cli/dev/index.ts b/packages/astro/src/cli/dev/index.ts index 531cddde4ce9..2bd22b7c6aeb 100644 --- a/packages/astro/src/cli/dev/index.ts +++ b/packages/astro/src/cli/dev/index.ts @@ -1,11 +1,10 @@ import { cyan } from 'kleur/colors'; -import type yargs from 'yargs-parser'; import devServer from '../../core/dev/index.js'; import { printHelp } from '../../core/messages.js'; -import { flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, flagsToAstroInlineConfig } from '../flags.js'; interface DevOptions { - flags: yargs.Arguments; + flags: Flags; } export async function dev({ flags }: DevOptions) { diff --git a/packages/astro/src/cli/docs/index.ts b/packages/astro/src/cli/docs/index.ts index cd6325577b37..afb5a1c622cf 100644 --- a/packages/astro/src/cli/docs/index.ts +++ b/packages/astro/src/cli/docs/index.ts @@ -1,9 +1,9 @@ -import type yargs from 'yargs-parser'; import { printHelp } from '../../core/messages.js'; +import type { Flags } from '../flags.js'; import { openInBrowser } from './open.js'; interface DocsOptions { - flags: yargs.Arguments; + flags: Flags; } export async function docs({ flags }: DocsOptions) { diff --git a/packages/astro/src/cli/flags.ts b/packages/astro/src/cli/flags.ts index 0af16806df44..c8180708f7a3 100644 --- a/packages/astro/src/cli/flags.ts +++ b/packages/astro/src/cli/flags.ts @@ -1,8 +1,11 @@ -import type { Arguments as Flags } from 'yargs-parser'; +import type { parseArgs } from 'node:util'; import type { AstroInlineConfig } from '../@types/astro.js'; import { type LogOptions, Logger } from '../core/logger/core.js'; import { nodeLogDestination } from '../core/logger/node.js'; +export type ParsedArgsResult = ReturnType; +export type Flags = ParsedArgsResult['values']; + export function flagsToAstroInlineConfig(flags: Flags): AstroInlineConfig { return { // Inline-only configs @@ -16,7 +19,7 @@ export function flagsToAstroInlineConfig(flags: Flags): AstroInlineConfig { base: typeof flags.base === 'string' ? flags.base : undefined, outDir: typeof flags.outDir === 'string' ? flags.outDir : undefined, server: { - port: typeof flags.port === 'number' ? flags.port : undefined, + port: typeof flags.port === 'string' ? Number(flags.port) : undefined, host: typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined, open: diff --git a/packages/astro/src/cli/index.ts b/packages/astro/src/cli/index.ts index 2d37132ac3b3..b3a819e586eb 100644 --- a/packages/astro/src/cli/index.ts +++ b/packages/astro/src/cli/index.ts @@ -1,7 +1,8 @@ +import { parseArgs } from 'node:util'; /* eslint-disable no-console */ import * as colors from 'kleur/colors'; -import yargs from 'yargs-parser'; import { ASTRO_VERSION } from '../core/constants.js'; +import type { ParsedArgsResult } from './flags.js'; type CLICommand = | 'help' @@ -65,9 +66,9 @@ function printVersion() { } /** Determine which command the user requested */ -function resolveCommand(flags: yargs.Arguments): CLICommand { - const cmd = flags._[2] as string; - if (flags.version) return 'version'; +function resolveCommand(args: ParsedArgsResult): CLICommand { + const cmd = args.positionals[2] as string; + if (args.values.version) return 'version'; const supportedCommands = new Set([ 'add', @@ -97,7 +98,9 @@ function resolveCommand(flags: yargs.Arguments): CLICommand { * NOTE: This function provides no error handling, so be sure * to present user-friendly error output where the fn is called. **/ -async function runCommand(cmd: string, flags: yargs.Arguments) { +async function runCommand(cmd: string, args: ParsedArgsResult) { + const flags = args.values; + // These commands can run directly without parsing the user config. switch (cmd) { case 'help': @@ -120,7 +123,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) { // Do not track session start, since the user may be trying to enable, // disable, or modify telemetry settings. const { update } = await import('./telemetry/index.js'); - const subcommand = flags._[3]?.toString(); + const subcommand = args.positionals[3]; await update(subcommand, { flags }); return; } @@ -131,7 +134,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) { } case 'preferences': { const { preferences } = await import('./preferences/index.js'); - const [subcommand, key, value] = flags._.slice(3).map((v) => v.toString()); + const [subcommand, key, value] = args.positionals.slice(3); const exitCode = await preferences(subcommand, key, value, { flags }); return process.exit(exitCode); } @@ -151,7 +154,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) { switch (cmd) { case 'add': { const { add } = await import('./add/index.js'); - const packages = flags._.slice(3) as string[]; + const packages = args.positionals.slice(3); await add(packages, { flags }); return; } @@ -161,7 +164,7 @@ async function runCommand(cmd: string, flags: yargs.Arguments) { case 'link': case 'init': { const { db } = await import('./db/index.js'); - await db({ flags }); + await db({ positionals: args.positionals, flags }); return; } case 'dev': { @@ -201,11 +204,21 @@ async function runCommand(cmd: string, flags: yargs.Arguments) { } /** The primary CLI action */ -export async function cli(args: string[]) { - const flags = yargs(args, { boolean: ['global'], alias: { g: 'global' } }); - const cmd = resolveCommand(flags); +export async function cli(argv: string[]) { + const args = parseArgs({ + args: argv, + allowPositionals: true, + strict: false, + options: { + global: { type: 'boolean', short: 'g' }, + host: { type: 'string' }, // Can be boolean too, which is covered by `strict: false` + open: { type: 'string' }, // Can be boolean too, which is covered by `strict: false` + // TODO: Add more flags here + }, + }); + const cmd = resolveCommand(args); try { - await runCommand(cmd, flags); + await runCommand(cmd, args); } catch (err) { const { throwAndExit } = await import('./throw-and-exit.js'); await throwAndExit(cmd, err); diff --git a/packages/astro/src/cli/info/index.ts b/packages/astro/src/cli/info/index.ts index cb61e45bfc06..3fa91802f26e 100644 --- a/packages/astro/src/cli/info/index.ts +++ b/packages/astro/src/cli/info/index.ts @@ -3,15 +3,14 @@ import { arch, platform } from 'node:os'; /* eslint-disable no-console */ import * as colors from 'kleur/colors'; import prompts from 'prompts'; -import type yargs from 'yargs-parser'; import type { AstroConfig, AstroUserConfig } from '../../@types/astro.js'; import { resolveConfig } from '../../core/config/index.js'; import { ASTRO_VERSION } from '../../core/constants.js'; import { apply as applyPolyfill } from '../../core/polyfill.js'; -import { flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, flagsToAstroInlineConfig } from '../flags.js'; interface InfoOptions { - flags: yargs.Arguments; + flags: Flags; } export async function getInfoOutput({ diff --git a/packages/astro/src/cli/preferences/index.ts b/packages/astro/src/cli/preferences/index.ts index 5735a9b6c26e..3811e7f48708 100644 --- a/packages/astro/src/cli/preferences/index.ts +++ b/packages/astro/src/cli/preferences/index.ts @@ -1,5 +1,4 @@ /* eslint-disable no-console */ -import type yargs from 'yargs-parser'; import type { AstroSettings } from '../../@types/astro.js'; import { fileURLToPath } from 'node:url'; @@ -15,10 +14,10 @@ import * as msg from '../../core/messages.js'; import { apply as applyPolyfill } from '../../core/polyfill.js'; import { DEFAULT_PREFERENCES } from '../../preferences/defaults.js'; import { type PreferenceKey, coerce, isValidKey } from '../../preferences/index.js'; -import { createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js'; interface PreferencesOptions { - flags: yargs.Arguments; + flags: Flags; } const PREFERENCES_SUBCOMMANDS = [ @@ -77,7 +76,7 @@ export async function preferences( const settings = await createSettings(astroConfig, fileURLToPath(astroConfig.root)); const opts: SubcommandOptions = { location: flags.global ? 'global' : undefined, - json: flags.json, + json: !!flags.json, }; if (subcommand === 'list') { diff --git a/packages/astro/src/cli/preview/index.ts b/packages/astro/src/cli/preview/index.ts index 387c1f241a49..468332ce3b97 100644 --- a/packages/astro/src/cli/preview/index.ts +++ b/packages/astro/src/cli/preview/index.ts @@ -1,11 +1,10 @@ import { cyan } from 'kleur/colors'; -import type yargs from 'yargs-parser'; import { printHelp } from '../../core/messages.js'; import previewServer from '../../core/preview/index.js'; -import { flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, flagsToAstroInlineConfig } from '../flags.js'; interface PreviewOptions { - flags: yargs.Arguments; + flags: Flags; } export async function preview({ flags }: PreviewOptions) { diff --git a/packages/astro/src/cli/sync/index.ts b/packages/astro/src/cli/sync/index.ts index 6849fee70844..89623e6a800b 100644 --- a/packages/astro/src/cli/sync/index.ts +++ b/packages/astro/src/cli/sync/index.ts @@ -1,10 +1,9 @@ -import type yargs from 'yargs-parser'; import { printHelp } from '../../core/messages.js'; import _sync from '../../core/sync/index.js'; -import { flagsToAstroInlineConfig } from '../flags.js'; +import { type Flags, flagsToAstroInlineConfig } from '../flags.js'; interface SyncOptions { - flags: yargs.Arguments; + flags: Flags; } export async function sync({ flags }: SyncOptions) { diff --git a/packages/astro/src/cli/telemetry/index.ts b/packages/astro/src/cli/telemetry/index.ts index 277b1cab6721..276f00ef1970 100644 --- a/packages/astro/src/cli/telemetry/index.ts +++ b/packages/astro/src/cli/telemetry/index.ts @@ -1,11 +1,10 @@ /* eslint-disable no-console */ -import type yargs from 'yargs-parser'; import * as msg from '../../core/messages.js'; import { telemetry } from '../../events/index.js'; -import { createLoggerFromFlags } from '../flags.js'; +import { type Flags, createLoggerFromFlags } from '../flags.js'; interface TelemetryOptions { - flags: yargs.Arguments; + flags: Flags; } export async function notify() { diff --git a/packages/astro/src/core/config/config.ts b/packages/astro/src/core/config/config.ts index 2e43661a437a..c10066ce3273 100644 --- a/packages/astro/src/core/config/config.ts +++ b/packages/astro/src/core/config/config.ts @@ -2,14 +2,12 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import * as colors from 'kleur/colors'; -import type { Arguments as Flags } from 'yargs-parser'; import { ZodError } from 'zod'; import type { AstroConfig, AstroInlineConfig, AstroInlineOnlyConfig, AstroUserConfig, - CLIFlags, } from '../../@types/astro.js'; import { eventConfigError, telemetry } from '../../events/index.js'; import { trackAstroConfigZodError } from '../errors/errors.js'; @@ -19,23 +17,6 @@ import { mergeConfig } from './merge.js'; import { validateConfig } from './validate.js'; import { loadConfigWithVite } from './vite-load.js'; -/** Convert the generic "yargs" flag object into our own, custom TypeScript object. */ -// NOTE: This function will be removed in a later PR. Use `flagsToAstroInlineConfig` instead. -// All CLI related flow should be located in the `packages/astro/src/cli` directory. -export function resolveFlags(flags: Partial): CLIFlags { - return { - root: typeof flags.root === 'string' ? flags.root : undefined, - site: typeof flags.site === 'string' ? flags.site : undefined, - base: typeof flags.base === 'string' ? flags.base : undefined, - port: typeof flags.port === 'number' ? flags.port : undefined, - config: typeof flags.config === 'string' ? flags.config : undefined, - host: - typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined, - open: - typeof flags.open === 'string' || typeof flags.open === 'boolean' ? flags.open : undefined, - }; -} - export function resolveRoot(cwd?: string | URL): string { if (cwd instanceof URL) { cwd = fileURLToPath(cwd); @@ -66,7 +47,7 @@ async function search(fsMod: typeof fs, root: string) { interface ResolveConfigPathOptions { root: string; - configFile?: string; + configFile?: string | false; fs: typeof fs; } diff --git a/packages/astro/src/core/config/index.ts b/packages/astro/src/core/config/index.ts index 3beaa5663523..7ffc290141cc 100644 --- a/packages/astro/src/core/config/index.ts +++ b/packages/astro/src/core/config/index.ts @@ -2,7 +2,6 @@ export { configPaths, resolveConfig, resolveConfigPath, - resolveFlags, resolveRoot, } from './config.js'; export { createNodeLogger } from './logging.js'; diff --git a/packages/create-astro/src/actions/context.ts b/packages/create-astro/src/actions/context.ts index 83a13eda7c94..bf1b522bd3bd 100644 --- a/packages/create-astro/src/actions/context.ts +++ b/packages/create-astro/src/actions/context.ts @@ -1,7 +1,7 @@ import os from 'node:os'; +import { parseArgs } from 'node:util'; import { type Task, prompt } from '@astrojs/cli-kit'; import { random } from '@astrojs/cli-kit/utils'; -import arg from 'arg'; import getSeasonalData from '../data/seasonal.js'; import { getName, getVersion } from '../messages.js'; @@ -33,47 +33,44 @@ export interface Context { } export async function getContext(argv: string[]): Promise { - const flags = arg( - { - '--template': String, - '--ref': String, - '--yes': Boolean, - '--no': Boolean, - '--install': Boolean, - '--no-install': Boolean, - '--git': Boolean, - '--no-git': Boolean, - '--typescript': String, - '--skip-houston': Boolean, - '--dry-run': Boolean, - '--help': Boolean, - '--fancy': Boolean, - - '-y': '--yes', - '-n': '--no', - '-h': '--help', + const args = parseArgs({ + args: argv, + allowPositionals: true, + strict: false, + options: { + template: { type: 'string' }, + ref: { type: 'string' }, + yes: { type: 'boolean', short: 'y' }, + no: { type: 'boolean', short: 'n' }, + install: { type: 'boolean' }, + 'no-install': { type: 'boolean' }, + git: { type: 'boolean' }, + 'no-git': { type: 'boolean' }, + typescript: { type: 'string' }, + 'skip-houston': { type: 'boolean' }, + 'dry-run': { type: 'boolean' }, + help: { type: 'boolean', short: 'h' }, + fancy: { type: 'boolean' }, }, - { argv, permissive: true }, - ); + }); const packageManager = detectPackageManager() ?? 'npm'; - let cwd = flags['_'][0]; + const projectName = args.positionals[0]; let { - '--help': help = false, - '--template': template, - '--no': no, - '--yes': yes, - '--install': install, - '--no-install': noInstall, - '--git': git, - '--no-git': noGit, - '--typescript': typescript, - '--fancy': fancy, - '--skip-houston': skipHouston, - '--dry-run': dryRun, - '--ref': ref, - } = flags; - let projectName = cwd; + help, + template, + no, + yes, + install, + 'no-install': noInstall, + git, + 'no-git': noGit, + typescript, + fancy, + 'skip-houston': skipHouston, + 'dry-run': dryRun, + ref, + } = args.values; if (no) { yes = false; @@ -82,10 +79,26 @@ export async function getContext(argv: string[]): Promise { if (typescript == undefined) typescript = 'strict'; } + skipHouston = typeof skipHouston == 'boolean' ? skipHouston : undefined; skipHouston = ((os.platform() === 'win32' && !fancy) || skipHouston) ?? [yes, no, install, git, typescript].some((v) => v !== undefined); + // We use `strict: false` in `parseArgs` to allow unknown options, but Node also + // simply doesn't guarantee the types anymore, so we need to validate ourselves :( + help = !!help; + template = typeof template == 'string' ? template : undefined; + no = !!no; + yes = !!yes; + install = !!install; + noInstall = !!noInstall; + git = !!git; + noGit = !!noGit; + typescript = typeof typescript == 'string' ? typescript : undefined; + fancy = !!fancy; + dryRun = !!dryRun; + ref = typeof ref == 'string' ? ref : undefined; + const { messages, hats, ties } = getSeasonalData({ fancy }); const context: Context = { @@ -107,7 +120,7 @@ export async function getContext(argv: string[]): Promise { install: install ?? (noInstall ? false : undefined), git: git ?? (noGit ? false : undefined), typescript, - cwd, + cwd: projectName, exit(code) { process.exit(code); }, diff --git a/packages/db/package.json b/packages/db/package.json index 850c6c94775b..6dd5d58238f0 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -81,13 +81,11 @@ "ora": "^8.0.1", "prompts": "^2.4.2", "strip-ansi": "^7.1.0", - "yargs-parser": "^21.1.1", "zod": "^3.23.8" }, "devDependencies": { "@types/deep-diff": "^1.0.5", "@types/prompts": "^2.4.9", - "@types/yargs-parser": "^21.0.3", "astro": "workspace:*", "astro-scripts": "workspace:*", "cheerio": "1.0.0", diff --git a/packages/db/src/core/cli/commands/execute/index.ts b/packages/db/src/core/cli/commands/execute/index.ts index c6c11cdbb6b9..03b3d00adda2 100644 --- a/packages/db/src/core/cli/commands/execute/index.ts +++ b/packages/db/src/core/cli/commands/execute/index.ts @@ -3,7 +3,6 @@ import { getManagedAppTokenOrExit } from '@astrojs/studio'; import { LibsqlError } from '@libsql/client'; import type { AstroConfig } from 'astro'; import { green } from 'kleur/colors'; -import type { Arguments } from 'yargs-parser'; import { EXEC_DEFAULT_EXPORT_ERROR, EXEC_ERROR, @@ -16,6 +15,7 @@ import { } from '../../../integration/vite-plugin-db.js'; import { bundleFile, importBundledFile } from '../../../load-file.js'; import type { DBConfig } from '../../../types.js'; +import type { YargsArguments } from '../../types.js'; export async function cmd({ astroConfig, @@ -24,7 +24,7 @@ export async function cmd({ }: { astroConfig: AstroConfig; dbConfig: DBConfig; - flags: Arguments; + flags: YargsArguments; }) { const filePath = flags._[4]; if (typeof filePath !== 'string') { diff --git a/packages/db/src/core/cli/commands/login/index.ts b/packages/db/src/core/cli/commands/login/index.ts index 61f7e0275b15..251d61e05433 100644 --- a/packages/db/src/core/cli/commands/login/index.ts +++ b/packages/db/src/core/cli/commands/login/index.ts @@ -7,8 +7,8 @@ import { cyan } from 'kleur/colors'; import open from 'open'; import ora from 'ora'; import prompt from 'prompts'; -import type { Arguments } from 'yargs-parser'; import type { DBConfig } from '../../../types.js'; +import type { YargsArguments } from '../../types.js'; const isWebContainer = // Stackblitz heuristic @@ -21,7 +21,7 @@ export async function cmd({ }: { astroConfig: AstroConfig; dbConfig: DBConfig; - flags: Arguments; + flags: YargsArguments; }) { let session = flags.session; diff --git a/packages/db/src/core/cli/commands/push/index.ts b/packages/db/src/core/cli/commands/push/index.ts index ecd101ecea79..237f7f6ea29e 100644 --- a/packages/db/src/core/cli/commands/push/index.ts +++ b/packages/db/src/core/cli/commands/push/index.ts @@ -1,7 +1,6 @@ import { getManagedAppTokenOrExit } from '@astrojs/studio'; import type { AstroConfig } from 'astro'; import prompts from 'prompts'; -import type { Arguments } from 'yargs-parser'; import { safeFetch } from '../../../../runtime/utils.js'; import { MIGRATION_VERSION } from '../../../consts.js'; import { type DBConfig, type DBSnapshot } from '../../../types.js'; @@ -13,6 +12,7 @@ import { getMigrationQueries, getProductionCurrentSnapshot, } from '../../migration-queries.js'; +import type { YargsArguments } from '../../types.js'; export async function cmd({ dbConfig, @@ -20,7 +20,7 @@ export async function cmd({ }: { astroConfig: AstroConfig; dbConfig: DBConfig; - flags: Arguments; + flags: YargsArguments; }) { const isDryRun = flags.dryRun; const isForceReset = flags.forceReset; diff --git a/packages/db/src/core/cli/commands/shell/index.ts b/packages/db/src/core/cli/commands/shell/index.ts index e0a1a6086c4f..5a6bcaa53d1b 100644 --- a/packages/db/src/core/cli/commands/shell/index.ts +++ b/packages/db/src/core/cli/commands/shell/index.ts @@ -1,7 +1,6 @@ import { getManagedAppTokenOrExit } from '@astrojs/studio'; import type { AstroConfig } from 'astro'; import { sql } from 'drizzle-orm'; -import type { Arguments } from 'yargs-parser'; import { createLocalDatabaseClient, createRemoteDatabaseClient, @@ -11,6 +10,7 @@ import { DB_PATH } from '../../../consts.js'; import { SHELL_QUERY_MISSING_ERROR } from '../../../errors.js'; import type { DBConfigInput } from '../../../types.js'; import { getAstroEnv, getRemoteDatabaseUrl } from '../../../utils.js'; +import type { YargsArguments } from '../../types.js'; export async function cmd({ flags, @@ -18,7 +18,7 @@ export async function cmd({ }: { dbConfig: DBConfigInput; astroConfig: AstroConfig; - flags: Arguments; + flags: YargsArguments; }) { const query = flags.query; if (!query) { diff --git a/packages/db/src/core/cli/commands/verify/index.ts b/packages/db/src/core/cli/commands/verify/index.ts index 950fb661567c..081c8ae3f8ec 100644 --- a/packages/db/src/core/cli/commands/verify/index.ts +++ b/packages/db/src/core/cli/commands/verify/index.ts @@ -1,6 +1,5 @@ import { getManagedAppTokenOrExit } from '@astrojs/studio'; import type { AstroConfig } from 'astro'; -import type { Arguments } from 'yargs-parser'; import type { DBConfig } from '../../../types.js'; import { createCurrentSnapshot, @@ -9,6 +8,7 @@ import { getMigrationQueries, getProductionCurrentSnapshot, } from '../../migration-queries.js'; +import type { YargsArguments } from '../../types.js'; export async function cmd({ dbConfig, @@ -16,7 +16,7 @@ export async function cmd({ }: { astroConfig: AstroConfig; dbConfig: DBConfig; - flags: Arguments; + flags: YargsArguments; }) { const isJson = flags.json; const appToken = await getManagedAppTokenOrExit(flags.token); diff --git a/packages/db/src/core/cli/index.ts b/packages/db/src/core/cli/index.ts index 531b016a6b77..5d9aa10e97dd 100644 --- a/packages/db/src/core/cli/index.ts +++ b/packages/db/src/core/cli/index.ts @@ -1,13 +1,13 @@ import type { AstroConfig } from 'astro'; -import type { Arguments } from 'yargs-parser'; import { resolveDbConfig } from '../load-file.js'; import { printHelp } from './print-help.js'; +import type { YargsArguments } from './types.js'; export async function cli({ flags, config: astroConfig, }: { - flags: Arguments; + flags: YargsArguments; config: AstroConfig; }) { const args = flags._ as string[]; diff --git a/packages/db/src/core/cli/types.ts b/packages/db/src/core/cli/types.ts new file mode 100644 index 000000000000..9d8aad56b78f --- /dev/null +++ b/packages/db/src/core/cli/types.ts @@ -0,0 +1,7 @@ +// Copy of `yargs-parser` `Arguments` type. We don't use `yargs-parser` +// in runtime anymore, but our exposed API still relies on this shape. +export interface YargsArguments { + _: Array; + '--'?: Array; + [argName: string]: any; +} diff --git a/packages/db/src/core/integration/index.ts b/packages/db/src/core/integration/index.ts index 19c712c802aa..6573c838af13 100644 --- a/packages/db/src/core/integration/index.ts +++ b/packages/db/src/core/integration/index.ts @@ -1,4 +1,5 @@ import { existsSync } from 'fs'; +import { parseArgs } from 'node:util'; import { dirname } from 'path'; import { fileURLToPath } from 'url'; import { type ManagedAppToken, getManagedAppTokenOrExit } from '@astrojs/studio'; @@ -14,7 +15,6 @@ import { loadEnv, mergeConfig, } from 'vite'; -import parseArgs from 'yargs-parser'; import { AstroDbError } from '../../runtime/utils.js'; import { CONFIG_FILE_NAMES, DB_PATH } from '../consts.js'; import { EXEC_DEFAULT_EXPORT_ERROR, EXEC_ERROR } from '../errors.js'; @@ -72,8 +72,8 @@ function astroDBIntegration(): AstroIntegration { if (command === 'preview') return; let dbPlugin: VitePlugin | undefined = undefined; - const args = parseArgs(process.argv.slice(3)); - connectToStudio = process.env.ASTRO_INTERNAL_TEST_REMOTE || args['remote']; + const args = parseArgs({ strict: false }); + connectToStudio = !!process.env.ASTRO_INTERNAL_TEST_REMOTE || !!args.values.remote; if (connectToStudio) { appToken = await getManagedAppTokenOrExit(); diff --git a/packages/upgrade/src/actions/context.ts b/packages/upgrade/src/actions/context.ts index 2103a53277e2..cd9028e85a54 100644 --- a/packages/upgrade/src/actions/context.ts +++ b/packages/upgrade/src/actions/context.ts @@ -1,13 +1,13 @@ import { pathToFileURL } from 'node:url'; +import { parseArgs } from 'node:util'; import { prompt } from '@astrojs/cli-kit'; -import arg from 'arg'; import detectPackageManager from 'preferred-pm'; export interface Context { help: boolean; prompt: typeof prompt; version: string; - dryRun?: boolean; + dryRun: boolean; cwd: URL; stdin?: typeof process.stdin; stdout?: typeof process.stdout; @@ -28,22 +28,20 @@ export interface PackageInfo { } export async function getContext(argv: string[]): Promise { - const flags = arg( - { - '--dry-run': Boolean, - '--help': Boolean, - - '-h': '--help', + const args = parseArgs({ + args: argv, + allowPositionals: true, + strict: false, + options: { + 'dry-run': { type: 'boolean' }, + help: { type: 'boolean', short: 'h' }, }, - { argv, permissive: true }, - ); + }); const packageManager = (await detectPackageManager(process.cwd()))?.name ?? 'npm'; - const { - _: [version = 'latest'] = [], - '--help': help = false, - '--dry-run': dryRun, - } = flags; + const version = args.positionals[0] ?? 'latest'; + const help = !!args.values.help; + const dryRun = !!args.values['dry-run']; return { help, diff --git a/packages/upgrade/test/context.test.js b/packages/upgrade/test/context.test.js index bbc887c2ae3c..5ba484740f89 100644 --- a/packages/upgrade/test/context.test.js +++ b/packages/upgrade/test/context.test.js @@ -6,12 +6,12 @@ describe('context', () => { it('no arguments', async () => { const ctx = await getContext([]); assert.equal(ctx.version, 'latest'); - assert.equal(ctx.dryRun, undefined); + assert.equal(ctx.dryRun, false); }); it('tag', async () => { const ctx = await getContext(['beta']); assert.equal(ctx.version, 'beta'); - assert.equal(ctx.dryRun, undefined); + assert.equal(ctx.dryRun, false); }); it('dry run', async () => { const ctx = await getContext(['--dry-run']); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6332cd00ff98..923aa7923c0b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -729,9 +729,6 @@ importers: which-pm: specifier: ^3.0.0 version: 3.0.0 - yargs-parser: - specifier: ^21.1.1 - version: 21.1.1 zod: specifier: ^3.23.8 version: 3.23.8 @@ -794,9 +791,6 @@ importers: '@types/semver': specifier: ^7.5.8 version: 7.5.8 - '@types/yargs-parser': - specifier: ^21.0.3 - version: 21.0.3 astro-scripts: specifier: workspace:* version: link:../../scripts @@ -1720,15 +1714,9 @@ importers: packages/astro/performance: devDependencies: - '@types/yargs-parser': - specifier: ^21.0.3 - version: 21.0.3 kleur: specifier: ^4.1.5 version: 4.1.5 - yargs-parser: - specifier: ^21.1.1 - version: 21.1.1 packages/astro/performance/fixtures/md: dependencies: @@ -4252,9 +4240,6 @@ importers: strip-ansi: specifier: ^7.1.0 version: 7.1.0 - yargs-parser: - specifier: ^21.1.1 - version: 21.1.1 zod: specifier: ^3.23.8 version: 3.23.8 @@ -4265,9 +4250,6 @@ importers: '@types/prompts': specifier: ^2.4.9 version: 2.4.9 - '@types/yargs-parser': - specifier: ^21.0.3 - version: 21.0.3 astro: specifier: workspace:* version: link:../astro @@ -7529,9 +7511,6 @@ packages: '@types/xml2js@0.4.14': resolution: {integrity: sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==} - '@types/yargs-parser@21.0.3': - resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - '@typescript-eslint/eslint-plugin@8.0.1': resolution: {integrity: sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -13410,8 +13389,6 @@ snapshots: dependencies: '@types/node': 18.19.31 - '@types/yargs-parser@21.0.3': {} - '@typescript-eslint/eslint-plugin@8.0.1(@typescript-eslint/parser@8.0.1(eslint@9.9.0(jiti@1.21.0))(typescript@5.5.4))(eslint@9.9.0(jiti@1.21.0))(typescript@5.5.4)': dependencies: '@eslint-community/regexpp': 4.11.0 diff --git a/scripts/cmd/copy.js b/scripts/cmd/copy.js index 943c998dfb4f..948ed114fe9b 100644 --- a/scripts/cmd/copy.js +++ b/scripts/cmd/copy.js @@ -1,19 +1,20 @@ -import arg from 'arg'; import { globby as glob } from 'globby'; import { promises as fs, readFileSync } from 'node:fs'; import { posix } from 'node:path'; +import { parseArgs } from 'node:util'; import * as tar from 'tar/create'; const { resolve, dirname, sep, join } = posix; -/** @type {import('arg').Spec} */ -const spec = { - '--tgz': Boolean, -}; - export default async function copy() { - let { _: patterns, ['--tgz']: isCompress } = arg(spec); - patterns = patterns.slice(1); + const args = parseArgs({ + allowPositionals: true, + options: { + tgz: { type: 'boolean' }, + }, + }); + const patterns = args.positionals.slice(1); + const isCompress = args.values.tgz; if (isCompress) { const files = await glob(patterns, { gitignore: true }); diff --git a/scripts/cmd/test.js b/scripts/cmd/test.js index 04f02f73afc0..17f6ecd04101 100644 --- a/scripts/cmd/test.js +++ b/scripts/cmd/test.js @@ -3,30 +3,32 @@ import { spec } from 'node:test/reporters'; import fs from 'node:fs/promises'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; -import arg from 'arg'; +import { parseArgs } from 'node:util'; import glob from 'tiny-glob'; const isCI = !!process.env.CI; const defaultTimeout = isCI ? 1400000 : 600000; export default async function test() { - const args = arg({ - '--match': String, // aka --test-name-pattern: https://nodejs.org/api/test.html#filtering-tests-by-name - '--only': Boolean, // aka --test-only: https://nodejs.org/api/test.html#only-tests - '--parallel': Boolean, // aka --test-concurrency: https://nodejs.org/api/test.html#test-runner-execution-model - '--watch': Boolean, // experimental: https://nodejs.org/api/test.html#watch-mode - '--timeout': Number, // Test timeout in milliseconds (default: 30000ms) - '--setup': String, // Test setup file - // Aliases - '-m': '--match', - '-o': '--only', - '-p': '--parallel', - '-w': '--watch', - '-t': '--timeout', - '-s': '--setup', + const args = parseArgs({ + allowPositionals: true, + options: { + // aka --test-name-pattern: https://nodejs.org/api/test.html#filtering-tests-by-name + match: { type: 'string', alias: 'm' }, + // aka --test-only: https://nodejs.org/api/test.html#only-tests + only: { type: 'boolean', alias: 'o' }, + // aka --test-concurrency: https://nodejs.org/api/test.html#test-runner-execution-model + parallel: { type: 'boolean', alias: 'p' }, + // experimental: https://nodejs.org/api/test.html#watch-mode + watch: { type: 'boolean', alias: 'w' }, + // Test timeout in milliseconds (default: 30000ms) + timeout: { type: 'string', alias: 't' }, + // Test setup file + setup: { type: 'string', alias: 's' }, + }, }); - const pattern = args._[1]; + const pattern = args.positionals[1]; if (!pattern) throw new Error('Missing test glob pattern'); const files = await glob(pattern, { filesOnly: true, absolute: true }); @@ -34,12 +36,12 @@ export default async function test() { // For some reason, the `only` option does not work and we need to explicitly set the CLI flag instead. // Node.js requires opt-in to run .only tests :( // https://nodejs.org/api/test.html#only-tests - if (args['--only']) { + if (args.values.only) { process.env.NODE_OPTIONS ??= ''; process.env.NODE_OPTIONS += ' --test-only'; } - if (!args['--parallel']) { + if (!args.values.parallel) { // If not parallel, we create a temporary file that imports all the test files // so that it all runs in a single process. const tempTestFile = path.resolve('./node_modules/.astro/test.mjs'); @@ -56,12 +58,12 @@ export default async function test() { // https://nodejs.org/api/test.html#runoptions run({ files, - testNamePatterns: args['--match'], - concurrency: args['--parallel'], - only: args['--only'], - setup: args['--setup'], - watch: args['--watch'], - timeout: args['--timeout'] ?? defaultTimeout, // Node.js defaults to Infinity, so set better fallback + testNamePatterns: args.values.match, + concurrency: args.values.parallel, + only: args.values.only, + setup: args.values.setup, + watch: args.values.watch, + timeout: args.values.timeout ? Number(args.values.timeout) : defaultTimeout, // Node.js defaults to Infinity, so set better fallback }) .on('test:fail', () => { // For some reason, a test fail using the JS API does not set an exit code of 1,