From b0ece88ff666e383637dfcdd2d6a52bee95bdbc9 Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Thu, 25 Sep 2025 14:56:39 -0700 Subject: [PATCH] chore: use commander.js to parse options --- package-lock.json | 17 +++++++++ package.json | 2 ++ src/cli.ts | 83 ++++++++++++++++++++----------------------- src/generator.ts | 44 ++++++++++++----------- src/packageManager.ts | 16 +++++++++ 5 files changed, 97 insertions(+), 65 deletions(-) diff --git a/package-lock.json b/package-lock.json index f4dcae3..6a4b60f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@types/ini": "^4.1.1", "@types/node": "^18.19.33", "ansi-colors": "^4.1.1", + "commander": "^14.0.1", "enquirer": "^2.3.6", "esbuild": "^0.25.0", "ini": "^4.1.3", @@ -490,6 +491,16 @@ "node": ">=6" } }, + "node_modules/commander": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", + "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -825,6 +836,12 @@ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "dev": true }, + "commander": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", + "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", + "dev": true + }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", diff --git a/package.json b/package.json index 6ff3e06..518ef55 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "license": "Apache-2.0", "scripts": { "build": "esbuild --bundle src/cli.ts --outfile=lib/index.js --platform=node --target=ES2019", + "clean": "rm -rf lib", "watch": "npm run build -- --watch", "test": "playwright test", "prepublish": "npm run build" @@ -29,6 +30,7 @@ "@types/ini": "^4.1.1", "@types/node": "^18.19.33", "ansi-colors": "^4.1.1", + "commander": "^14.0.1", "enquirer": "^2.3.6", "esbuild": "^0.25.0", "ini": "^4.1.3", diff --git a/src/cli.ts b/src/cli.ts index 4c210cf..278d6b9 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,52 +15,45 @@ */ import path from 'path'; -import { Generator } from './generator'; +import { Command } from 'commander'; +import { CliOptions, Generator } from './generator'; -(async () => { - const argv = process.argv.slice(2); - const args = argv.filter(a => !a.startsWith('--')); - const options: { [key: string]: string[] } = {}; - for (const token of argv.filter(a => a.startsWith('--'))) { - const match = token.match(/--([^=]+)(?:=(.*))?/); - if (!match) - continue; - const [, name, value] = match; - if (!name) - continue; - const oldValue = options[name]; - if (oldValue && value) - oldValue.push(value); - else if (value) - options[name] = [value]; - else - options[name] = []; - } - if (options.help) { - _printHelp(); - process.exit(0); - } - const rootDir = path.resolve(process.cwd(), args[0] || ''); - const generator = new Generator(rootDir, options); - await generator.run(); -})().catch(error => { +const program = new Command(); + +program + .name('create-playwright') + .description('Getting started with writing end-to-end tests with Playwright.') + .argument('[rootDir]', 'Target directory for the Playwright project', '.') + .option('--browser ', 'browsers to use in default config', ['chromium', 'firefox', 'webkit']) + .option('--no-browsers', 'do not download browsers (can be done manually via \'npx playwright install\')') + .option('--no-examples', 'do not create example test files') + .option('--install-deps', 'install dependencies') + .option('--next', 'install @next version of Playwright') + .option('--beta', 'install @beta version of Playwright') + .option('--ct', 'install Playwright Component testing') + .option('--quiet', 'do not ask for interactive input prompts') + .option('--gha', 'install GitHub Actions') + .option('--lang ', 'language to use (js, TypeScript)', 'TypeScript') + .action(async (rootDir, options) => { + + const cliOptions: CliOptions = { + browser: options.browser, + noBrowsers: !options.browsers, + noExamples: !options.examples, + installDeps: options.installDeps, + next: options.next, + beta: options.beta, + ct: options.ct, + quiet: options.quiet, + gha: options.gha, + lang: options.lang, + }; + const resolvedRootDir = path.resolve(process.cwd(), rootDir || '.'); + const generator = new Generator(resolvedRootDir, cliOptions); + await generator.run(); + }); + +program.parseAsync().catch(error => { console.error(error); process.exit(1); }); - -function _printHelp() { - console.log(`Usage: npx create-playwright@latest [options] [rootDir] - Available options are: - --help: print this message - --browser=: browsers to use in default config (default: 'chromium,firefox,webkit') - --no-browsers: do not download browsers (can be done manually via 'npx playwright install') - --no-examples: do not create example test files - --install-deps: install dependencies (default: false) - --next: install @next version of Playwright - --beta: install @beta version of Playwright - --ct: install Playwright Component testing - --quiet: do not ask for interactive input prompts - --gha: install GitHub Actions - --lang=: language to use (default: 'TypeScript'. Potential values: 'js', 'TypeScript') - `); -} diff --git a/src/generator.ts b/src/generator.ts index 7801f6e..8c2d1ed 100644 --- a/src/generator.ts +++ b/src/generator.ts @@ -34,21 +34,25 @@ export type PromptOptions = { const assetsDir = path.join(__dirname, '..', 'assets'); -type CliArgumentKey = 'browser' - | 'no-browsers' - | 'no-examples' - | 'next' - | 'beta' - | 'ct' - | 'quiet' - | 'gha' - | 'install-deps' - | 'lang'; +export type CliOptions = { + lang: string; + browser: string[]; + noBrowsers?: boolean; + noExamples?: boolean; + installDeps?: boolean; + next?: boolean; + beta?: boolean; + ct?: boolean; + quiet?: boolean; + gha?: boolean; +}; export class Generator { + private readonly options: CliOptions private packageManager: PackageManager; - constructor(private readonly rootDir: string, private readonly options: Partial>) { + constructor(private readonly rootDir: string, options: CliOptions) { + this.options = options; if (!fs.existsSync(rootDir)) fs.mkdirSync(rootDir); this.packageManager = determinePackageManager(rootDir); @@ -84,11 +88,11 @@ export class Generator { if (this.options.quiet) { return { installGitHubActions: !!this.options.gha, - language: this.options.lang?.[0] === 'js' ? 'JavaScript' : 'TypeScript', - installPlaywrightDependencies: !!this.options['install-deps'], + language: this.options.lang === 'js' ? 'JavaScript' : 'TypeScript', + installPlaywrightDependencies: !!this.options.installDeps, testDir: fs.existsSync(path.join(this.rootDir, 'tests')) ? 'e2e' : 'tests', framework: undefined, - installPlaywrightBrowsers: !this.options['no-browsers'], + installPlaywrightBrowsers: !this.options.noBrowsers, }; } @@ -103,7 +107,7 @@ export class Generator { { name: 'TypeScript' }, { name: 'JavaScript' }, ], - initial: this.options.lang?.[0] === 'js' ? 'JavaScript' : 'TypeScript', + initial: this.options.lang === 'js' ? 'JavaScript' : 'TypeScript', skip: !!this.options.lang, }, this.options.ct && { @@ -136,8 +140,8 @@ export class Generator { type: 'confirm', name: 'installPlaywrightBrowsers', message: `Install Playwright browsers (can be done manually via '${this.packageManager.npx('playwright', 'install')}')?`, - initial: !this.options['no-browsers'] || !!this.options.browser, - skip: !!this.options['no-browsers'] || !!this.options.browser, + initial: !this.options.noBrowsers || !!this.options.browser, + skip: !!this.options.noBrowsers || !!this.options.browser, }, // Avoid installing dependencies on Windows (vast majority does not run create-playwright on Windows) // Avoid installing dependencies on Mac (there are no dependencies) @@ -145,8 +149,8 @@ export class Generator { type: 'confirm', name: 'installPlaywrightDependencies', message: `Install Playwright operating system dependencies (requires sudo / root - can be done manually via 'sudo ${this.packageManager.npx('playwright', 'install-deps')}')?`, - initial: !!this.options['install-deps'], - skip: !!this.options['install-deps'], + initial: !!this.options.installDeps, + skip: !!this.options.installDeps, }, ]; const result = await prompt( @@ -170,7 +174,7 @@ export class Generator { sections.set(browserName, !this.options.browser || this.options.browser.includes(browserName) ? 'show' : 'comment'); let ctPackageName; - let installExamples = !this.options['no-examples']; + let installExamples = !this.options.noExamples; if (answers.framework) { ctPackageName = `@playwright/experimental-ct-${answers.framework}`; installExamples = false; diff --git a/src/packageManager.ts b/src/packageManager.ts index 01214d3..b35a417 100644 --- a/src/packageManager.ts +++ b/src/packageManager.ts @@ -1,3 +1,19 @@ +/** + * Copyright (c) Microsoft Corporation. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import path from 'path'; import fs from 'fs';