0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-16 21:46:22 -05:00

Use node parseArgs instead of yargs-parser and arg (#11645)

* wip

* done

* Add changeset

* Format

* Update

* Fix houston

* Fix test

* Fix test
This commit is contained in:
Bjorn Lu 2024-08-14 18:05:50 +08:00 committed by GitHub
parent a23c69d0d0
commit 849e4c6c23
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 224 additions and 216 deletions

View file

@ -0,0 +1,6 @@
---
'astro': patch
'@astrojs/db': patch
---
Refactors internally to use `node:util` `parseArgs` instead of `yargs-parser`

View file

@ -0,0 +1,7 @@
---
'create-astro': patch
'@astrojs/upgrade': patch
---
Refactors internally to use `node:util` `parseArgs` instead of `arg`

View file

@ -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",

View file

@ -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'

View file

@ -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"
}
}

View file

@ -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<UpdateResult> {
@ -717,7 +716,7 @@ async function tryToInstallIntegrations({
}: {
integrations: IntegrationInfo[];
cwd?: string;
flags: yargs.Arguments;
flags: Flags;
logger: Logger;
}): Promise<UpdateResult> {
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<UpdateResult> {
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<boolean> {
async function askToContinue({ flags }: { flags: Flags }): Promise<boolean> {
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;

View file

@ -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 });
}

View file

@ -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<typeof import('@astrojs/check')>(
'@astrojs/check',
logger,

View file

@ -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<string | number>;
'--'?: Array<string | number>;
[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<DBPackage>('@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 });
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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<typeof parseArgs>;
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:

View file

@ -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);

View file

@ -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({

View file

@ -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') {

View file

@ -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) {

View file

@ -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) {

View file

@ -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() {

View file

@ -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<Flags>): 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;
}

View file

@ -2,7 +2,6 @@ export {
configPaths,
resolveConfig,
resolveConfigPath,
resolveFlags,
resolveRoot,
} from './config.js';
export { createNodeLogger } from './logging.js';

View file

@ -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<Context> {
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<Context> {
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<Context> {
install: install ?? (noInstall ? false : undefined),
git: git ?? (noGit ? false : undefined),
typescript,
cwd,
cwd: projectName,
exit(code) {
process.exit(code);
},

View file

@ -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",

View file

@ -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') {

View file

@ -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;

View file

@ -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;

View file

@ -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) {

View file

@ -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);

View file

@ -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[];

View file

@ -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<string | number>;
'--'?: Array<string | number>;
[argName: string]: any;
}

View file

@ -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();

View file

@ -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<Context> {
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,

View file

@ -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']);

View file

@ -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

View file

@ -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 });

View file

@ -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,