diff --git a/packages/playground/cli/src/mounts.ts b/packages/playground/cli/src/mounts.ts index 2ae487294c..b8aceeddeb 100644 --- a/packages/playground/cli/src/mounts.ts +++ b/packages/playground/cli/src/mounts.ts @@ -107,7 +107,10 @@ const ACTIVATE_FIRST_THEME_STEP = { * Auto-mounts resolution logic: */ export function expandAutoMounts(args: RunCLIArgs): RunCLIArgs { - const path = args.autoMount!; + if (typeof args.autoMount !== 'string') { + return args; + } + const path = args.autoMount; const mount = [...(args.mount || [])]; const mountBeforeInstall = [...(args['mount-before-install'] || [])]; diff --git a/packages/playground/cli/src/run-cli.ts b/packages/playground/cli/src/run-cli.ts index 3424d33af8..b991937cf6 100644 --- a/packages/playground/cli/src/run-cli.ts +++ b/packages/playground/cli/src/run-cli.ts @@ -433,11 +433,11 @@ export async function parseOptionsAndRunCLI(argsToParse: string[]) { type: 'boolean', default: false, }, - 'no-auto-mount': { + 'auto-mount': { describe: - 'Disable automatic project type detection. Use --mount to manually specify mounts instead.', + 'Automatically detect project type (plugin, theme, wp-content, or WordPress) and mount accordingly. Use --no-auto-mount to disable and --mount to manually specify mounts instead.', type: 'boolean', - default: false, + default: true, }, // Define constants define: sharedOptions['define'], @@ -559,12 +559,20 @@ export async function parseOptionsAndRunCLI(argsToParse: string[]) { } } - if (args['auto-mount']) { + // For the `start` command, `--auto-mount` is a boolean + // toggle (path is taken from `--path`), so skip the + // directory validation here. Read the camelCase form for + // consistency with the rest of the codebase — yargs-parser + // emits both dashed and camelCase keys. + const autoMountArg = args['autoMount']; + if ( + args._[0] !== 'start' && + typeof autoMountArg === 'string' && + autoMountArg + ) { let autoMountIsDir = false; try { - const autoMountStats = fs.statSync( - args['auto-mount'] as string - ); + const autoMountStats = fs.statSync(autoMountArg); autoMountIsDir = autoMountStats.isDirectory(); } catch { autoMountIsDir = false; @@ -572,7 +580,7 @@ export async function parseOptionsAndRunCLI(argsToParse: string[]) { if (!autoMountIsDir) { throw new Error( - `The specified --auto-mount path is not a directory: '${args['auto-mount']}'.` + `The specified --auto-mount path is not a directory: '${autoMountArg}'.` ); } } @@ -827,7 +835,13 @@ export interface RunCLIArgs { quiet?: boolean; verbosity?: LogVerbosity; wp?: string; - autoMount?: string; + /** + * For the `server` command (and other long-form commands), this is the + * host path to auto-detect and mount. For the `start` command, this is a + * boolean toggle: `true` (default) enables auto-detection on the + * `--path` directory; `false` (i.e. `--no-auto-mount`) disables it. + */ + autoMount?: string | boolean; pathAliases?: PathAlias[]; experimentalTrace?: boolean; internalCookieStore?: boolean; @@ -878,7 +892,6 @@ export interface RunCLIArgs { // --------- Start command args ----------- path?: string; skipBrowser?: boolean; - noAutoMount?: boolean; reset?: boolean; } @@ -1718,9 +1731,21 @@ function expandStartCommandArgs( let newArgs = { ...args, command: 'server' }; /** - * Enable auto-mount unless explicitly disabled + * Enable auto-mount unless explicitly disabled via `--no-auto-mount`. + * + * yargs-parser's boolean-negation turns `--no-auto-mount` into + * `{ autoMount: false }`, so `args.autoMount === false` is how we detect + * the disabled case. The boolean form is start-command only — downstream + * code treats `autoMount` as a string path, so drop it either way and + * then re-populate it with a resolved path when enabled. */ - if (!args.noAutoMount) { + const autoMountEnabled = args.autoMount !== false; + // Scrub both the camelCase and dashed forms yargs-parser emits so a + // stale boolean can't leak into downstream consumers that read either. + delete newArgs.autoMount; + delete (newArgs as Record)['auto-mount']; + + if (autoMountEnabled) { newArgs.autoMount = path.resolve(process.cwd(), newArgs['path'] ?? ''); newArgs = expandAutoMounts(newArgs as RunCLIArgs); // Delete the autoMount argument to avoid double expansion later on. diff --git a/packages/playground/cli/tests/run-cli.spec.ts b/packages/playground/cli/tests/run-cli.spec.ts index b4648b767b..308878393d 100644 --- a/packages/playground/cli/tests/run-cli.spec.ts +++ b/packages/playground/cli/tests/run-cli.spec.ts @@ -1067,6 +1067,56 @@ describe('start command', () => { expect(existsSync(wpContentPath)).toBe(true); expect(lstatSync(wpContentPath).isDirectory()).toBe(true); }, 120000); + + test('should accept --no-auto-mount and skip auto-detection', async () => { + // Regression test: yargs-parser's boolean-negation turns + // `--no-auto-mount` into `{ autoMount: false }`. When the start + // command declared a literal `no-auto-mount` option (with no + // matching `auto-mount`), strictOptions rejected the negated key + // with `Unknown arguments: auto-mount, autoMount`. + const tmpDir = await mkdtemp( + path.join(tmpdir(), 'playground-test-no-auto-mount-') + ); + const pluginDirName = 'sample-plugin'; + const pluginDir = path.join(tmpDir, pluginDirName); + mkdirSync(pluginDir, { recursive: true }); + writeFileSync( + path.join(pluginDir, `${pluginDirName}.php`), + ` { + throw new Error(`process.exit unexpectedly called with "${code}"`); + }) as any); + + try { + await using cliResult = await parseOptionsAndRunCLI([ + 'start', + `--path=${pluginDir}`, + '--no-auto-mount', + '--skip-browser', + ]); + const cliServer = cliResult[internalsKeyForTesting].cliServer; + + // Server started → yargs accepted `--no-auto-mount`. + expect(cliServer.serverUrl).toMatch(/^http:\/\/127\.0\.0\.1:\d+$/); + + // Auto-mount did not fire → the plugin is not present under + // /wordpress/wp-content/plugins/. + const autoMountedPluginExists = await cliServer.playground.isDir( + `/wordpress/wp-content/plugins/${pluginDirName}` + ); + expect(autoMountedPluginExists).toBe(false); + } finally { + exitSpy.mockRestore(); + rmSync(tmpDir, { recursive: true, force: true }); + } + }, 180000); }); describe('php command', () => {