Skip to content
7 changes: 7 additions & 0 deletions .changeset/viteplus-project-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/autoconfig": patch
---

Run the project-local Vite+ executable for Vite+ projects

Use `vp dev` and `vp build` when Vite+ is declared. Its Vite alias does not expose a `vite` executable, so invoking `vite` can fail with pnpm or cause npm to fetch a separate Vite. Preserve framework-owned commands and custom scripts.
31 changes: 27 additions & 4 deletions packages/autoconfig/src/details/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,18 @@ export async function getDetailsForAutoConfig({
packageManager,
devCommand: useStaticAssets
? undefined
: getProjectCommand(detectedFramework.devCommand, packageManager),
: getProjectCommand(
detectedFramework.devCommand,
packageManager,
packageJson
),
buildCommand: useStaticAssets
? undefined
: getProjectCommand(detectedFramework.buildCommand, packageManager),
: getProjectCommand(
detectedFramework.buildCommand,
packageManager,
packageJson
),
env: framework.env,
workerName: getWorkerName(packageJson?.name, projectPath),
};
Expand Down Expand Up @@ -285,17 +293,32 @@ export async function getDetailsForAutoConfig({
*
* @param command The detected project command
* @param packageManager The package manager to use for command prefixes
* @param packageJson The optional project manifest used to detect Vite+
* @returns A runnable project command if detected, undefined otherwise
*/
function getProjectCommand(
export function getProjectCommand(
command: string | undefined,
packageManager: PackageManager
packageManager: PackageManager,
packageJson?: PackageJSON
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
): string | undefined {
if (!command) {
return undefined;
}

const { type, dlx, npx } = packageManager;
if (
packageJson?.dependencies?.["vite-plus"] ||
packageJson?.devDependencies?.["vite-plus"]
) {
if (
["vite", "vite dev", `${npx} vite`, `${npx} vite dev`].includes(command)
) {
return `${npx} vp dev`;
}
if (command === "vite build" || command === `${npx} vite build`) {
return `${npx} vp build`;
}
Comment thread
devin-ai-integration[bot] marked this conversation as resolved.
Resolved
}

for (const packageManagerCommandPrefix of [type, dlx.join(" "), npx]) {
if (command.startsWith(packageManagerCommandPrefix)) {
Expand Down
11 changes: 10 additions & 1 deletion packages/autoconfig/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
assertNonConfigured,
confirmAutoConfigDetails,
displayAutoConfigDetails,
getProjectCommand,
} from "./details";
import {
isFrameworkSupported,
Expand Down Expand Up @@ -165,9 +166,17 @@ export async function runAutoConfig(
}

const { npx } = packageManager;
const buildCommand =
let buildCommand =
dryRunConfigurationResults.buildCommandOverride ??
autoConfigDetails.buildCommand;
// Preserve shell commands while normalizing Vite selected by framework overrides.
if (buildCommand === "vite build" || buildCommand === `${npx} vite build`) {
buildCommand = getProjectCommand(
buildCommand,
packageManager,
autoConfigDetails.packageJson
);
}
Comment thread
irvinebroque marked this conversation as resolved.
Resolved

const autoConfigSummary = await buildOperationsSummary(
{ ...autoConfigDetails, outputDir: autoConfigDetails.outputDir },
Expand Down
35 changes: 35 additions & 0 deletions packages/autoconfig/tests/details/vite-plus-commands.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { NpmPackageManager } from "@cloudflare/workers-utils";
import { describe, test } from "vitest";
import { getProjectCommand } from "../../src/details";

describe("Vite+ commands", () => {
test.for(["npx", "pnpm", "yarn", "bunx"])(
"uses the local toolchain through %s",
(npx, { expect }) => {
const manager = { ...NpmPackageManager, npx };
const pkg = { devDependencies: { "vite-plus": "1.4.0" } };
expect(getProjectCommand("vite", manager, pkg)).toBe(`${npx} vp dev`);
expect(getProjectCommand("vite dev", manager, pkg)).toBe(`${npx} vp dev`);
expect(getProjectCommand("vite build", manager, pkg)).toBe(
`${npx} vp build`
);
expect(getProjectCommand(`${npx} vite build`, manager, pkg)).toBe(
`${npx} vp build`
);
}
);
test("preserves framework scripts and projects without Vite+", ({
expect,
}) => {
const pkg = { dependencies: { "vite-plus": "1.4.0" } };
expect(getProjectCommand("astro build", NpmPackageManager, pkg)).toBe(
"npx astro build"
);
expect(getProjectCommand("npm run build", NpmPackageManager, pkg)).toBe(
"npm run build"
);
expect(getProjectCommand("vite build", NpmPackageManager, {})).toBe(
"npx vite build"
);
});
});
81 changes: 80 additions & 1 deletion packages/autoconfig/tests/run.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { existsSync, readFileSync } from "node:fs";
import * as cliPackages from "@cloudflare/cli-shared-helpers/packages";
import { NpmPackageManager } from "@cloudflare/workers-utils";
import {
NpmPackageManager,
PnpmPackageManager,
} from "@cloudflare/workers-utils";
import {
mockConsoleMethods,
runInTempDir,
Expand Down Expand Up @@ -59,6 +62,82 @@ describe("runAutoConfig()", () => {
runInTempDir();
const std = mockConsoleMethods();

it.for([
{ vitePlus: false, override: false },
{ vitePlus: true, override: false },
{ vitePlus: false, override: true },
{ vitePlus: true, override: true },
])(
"preserves custom build commands with Vite+=$vitePlus and override=$override",
async ({ vitePlus, override }, { expect }) => {
const buildCommand = "echo 'built' > build.txt";
const packageJson = {
name: "static-app",
devDependencies: vitePlus ? { "vite-plus": "1.4.0" } : {},
};
await seed({ "package.json": JSON.stringify(packageJson) });
const framework = getFrameworkClassInstance("static");
vi.spyOn(framework, "configure").mockReturnValue({
buildTool: "wrangler",
workerConfig: {},
buildConfig: { assetsDirectory: "public" },
...(override && { buildCommandOverride: buildCommand }),
});
const summary = await runAutoConfig(
{
configured: false,
projectPath: process.cwd(),
workerName: "static-app",
framework,
outputDir: "public",
buildCommand: override ? "npm run build" : buildCommand,
packageManager: NpmPackageManager,
packageJson,
},
{ context: createMockContext(), dryRun: true }
);
expect(summary.buildCommand).toBe(buildCommand);
}
);

it.for([
{ packageManager: NpmPackageManager, vitePlus: true },
{ packageManager: PnpmPackageManager, vitePlus: true },
{ packageManager: NpmPackageManager, vitePlus: false },
])(
"uses the vinext build override with $packageManager.type and Vite+=$vitePlus",
async ({ packageManager, vitePlus }, { expect }) => {
const packageJson = {
name: "next-app",
dependencies: { next: "16.1.0" },
devDependencies: vitePlus ? { "vite-plus": "1.4.0" } : {},
};
await seed({
"package.json": JSON.stringify(packageJson),
"node_modules/next/package.json": JSON.stringify({
name: "next",
version: "16.1.0",
}),
});
const summary = await runAutoConfig(
{
configured: false,
projectPath: process.cwd(),
workerName: "next-app",
framework: getFrameworkClassInstance("next"),
outputDir: ".next",
buildCommand: `${packageManager.npx} next build`,
packageManager,
packageJson,
},
{ context: createMockContext(), dryRun: true }
);
expect(summary.buildCommand).toBe(
`${packageManager.npx} ${vitePlus ? "vp" : "vite"} build`
);
}
);

it("rejects autoconfiguration for an unsupported framework", async ({
expect,
}) => {
Expand Down
Loading