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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/playground/cli/src/mounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'] || [])];
Expand Down
49 changes: 37 additions & 12 deletions packages/playground/cli/src/run-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand Down Expand Up @@ -559,20 +559,28 @@ 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
) {
Comment thread
pento marked this conversation as resolved.
let autoMountIsDir = false;
try {
const autoMountStats = fs.statSync(
args['auto-mount'] as string
);
const autoMountStats = fs.statSync(autoMountArg);
autoMountIsDir = autoMountStats.isDirectory();
} catch {
autoMountIsDir = false;
}

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}'.`
);
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -878,7 +892,6 @@ export interface RunCLIArgs {
// --------- Start command args -----------
path?: string;
skipBrowser?: boolean;
noAutoMount?: boolean;
reset?: boolean;
}

Expand Down Expand Up @@ -1718,9 +1731,21 @@ function expandStartCommandArgs(
let newArgs = { ...args, command: 'server' };
Comment thread
pento marked this conversation as resolved.

/**
* 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;
Comment thread
pento marked this conversation as resolved.
delete (newArgs as Record<string, unknown>)['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.
Expand Down
50 changes: 50 additions & 0 deletions packages/playground/cli/tests/run-cli.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Comment thread
pento marked this conversation as resolved.
writeFileSync(
path.join(pluginDir, `${pluginDirName}.php`),
`<?php\n/*\nPlugin Name: Sample Plugin\n*/\n`
);

// Throw instead of no-op so any unexpected `process.exit` during
// startup fails the test loudly instead of silently continuing in
// an inconsistent state.
const exitSpy = vi.spyOn(process, 'exit').mockImplementation(((
code?: number | string | null
) => {
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', () => {
Expand Down
Loading