Skip to content
Draft
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
7 changes: 7 additions & 0 deletions .changeset/fuzzy-modes-autoconfig.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@cloudflare/autoconfig": minor
---

Expose mode support for autoconfigured framework commands

Autoconfig details and summaries now describe resolved build and development commands as an executable and argument vector, together with whether each command supports `--mode`. Astro and Vite commands report mode support, while other framework commands remain unsupported unless configured individually. Summaries also expose whether Vite or Wrangler owns the Cloudflare-aware build step.
4 changes: 3 additions & 1 deletion packages/autoconfig/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,16 @@
"dependencies": {
"@cloudflare/cli-shared-helpers": "workspace:*",
"@cloudflare/config": "workspace:*",
"@cloudflare/workers-utils": "workspace:*"
"@cloudflare/workers-utils": "workspace:*",
"shell-quote": "^1.9.0"
},
"devDependencies": {
"@cloudflare/shared-ast-primitives": "workspace:*",
"@cloudflare/workers-tsconfig": "workspace:*",
"@netlify/build-info": "10.5.1",
"@types/esprima": "^4.0.3",
"@types/node": "catalog:default",
"@types/shell-quote": "^1.7.2",
"chalk": "catalog:default",
"empathic": "^2.0.0",
"esprima": "4.0.1",
Expand Down
22 changes: 17 additions & 5 deletions packages/autoconfig/src/details/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,16 +143,28 @@ export async function getDetailsForAutoConfig({
const outputDir =
detectedFramework?.dist ?? (await findAssetsDir(projectPath));

const devCommand = getProjectCommand(
detectedFramework.devCommand,
packageManager
);
const buildCommand = getProjectCommand(
detectedFramework.buildCommand,
packageManager
);
const resolvedDevCommand = framework.resolveCommand("dev", devCommand);
const resolvedBuildCommand = framework.resolveCommand("build", buildCommand);

const baseDetails = {
projectPath,
framework,
packageJson,
packageManager,
devCommand: getProjectCommand(detectedFramework.devCommand, packageManager),
buildCommand: getProjectCommand(
detectedFramework.buildCommand,
packageManager
),
devCommand,
buildCommand,
commands: {
...(resolvedDevCommand ? { dev: resolvedDevCommand } : {}),
...(resolvedBuildCommand ? { build: resolvedBuildCommand } : {}),
},
env: framework.env,
workerName: getWorkerName(packageJson?.name, projectPath),
};
Expand Down
7 changes: 7 additions & 0 deletions packages/autoconfig/src/frameworks/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ import type {
import type { PackageManager } from "@cloudflare/workers-utils";

export class Astro extends Framework {
override get commandCapabilities() {
return {
build: { supportsMode: true },
dev: { supportsMode: true },
} as const;
}

async configure({
outputDir,
dryRun,
Expand Down
64 changes: 63 additions & 1 deletion packages/autoconfig/src/frameworks/framework-class.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import assert from "node:assert";
import semiver from "semiver";
import shellquote from "shell-quote";
import { AutoConfigFrameworkConfigurationError } from "../errors";
import { getInstalledPackageVersion } from "./utils/packages";
import type { AutoConfigFrameworkPackageInfo, FrameworkInfo } from ".";
Expand All @@ -11,6 +12,9 @@ export abstract class Framework {
readonly id: FrameworkInfo["id"];
readonly name: FrameworkInfo["name"];
declare readonly env?: Readonly<Record<string, string>>;
get commandCapabilities(): FrameworkCommandCapabilities {
return {};
}

#frameworkVersion: string | undefined;
get frameworkVersion(): string {
Expand All @@ -26,6 +30,23 @@ export abstract class Framework {
this.name = frameworkInfo.name;
}

resolveCommand(
name: AutoConfigCommandName,
command: string | undefined
): AutoConfigCommand | undefined {
if (!command) {
return;
}
const [executable, ...args] = parseCommand(command);
assert(executable, "The resolved project command cannot be empty");

return {
executable,
args,
supportsMode: this.commandCapabilities[name]?.supportsMode ?? false,
};
}

isConfigured(
_projectPath: string,
{
Expand Down Expand Up @@ -118,9 +139,27 @@ export type BuildConfig = {
assetsDirectory?: string;
};

export type AutoConfigCommandName = "build" | "dev";

export type AutoConfigCommand = {
executable: string;
args: string[];
supportsMode: boolean;
};

export type AutoConfigCommands = Partial<
Record<AutoConfigCommandName, AutoConfigCommand>
>;

export type FrameworkCommandCapabilities = Partial<
Record<AutoConfigCommandName, Pick<AutoConfigCommand, "supportsMode">>
>;

export type BuildTool = "vite" | "wrangler";

export type ConfigurationResults = {
/** The tool that cf should delegate build and development commands to. */
buildTool?: "vite" | "wrangler";
buildTool?: BuildTool;
/** Worker configuration generated by the framework. `null` if an external tool generates it. */
workerConfig: Partial<WorkerConfigInput> | null;
/** Build configuration that complements the Worker configuration. */
Expand All @@ -134,3 +173,26 @@ export type ConfigurationResults = {
// Version command to override the standard one (`npx wrangler versions upload`)
versionCommandOverride?: string;
};

function parseCommand(command: string): string[] {
const entries = shellquote.parse(
process.platform === "win32" ? command.replaceAll("\\", "\\\\") : command
);
const argv: string[] = [];

for (const entry of entries) {
if (typeof entry === "string") {
argv.push(entry);
} else if ("comment" in entry) {
continue;
} else if (entry.op === "glob") {
argv.push(entry.pattern);
} else {
throw new Error(
`Only simple commands are supported, but found the ${JSON.stringify(entry.op)} operator in ${JSON.stringify(command)}.`
);
}
}

return argv;
}
7 changes: 7 additions & 0 deletions packages/autoconfig/src/frameworks/vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ import type {
} from "./framework-class";

export class Vite extends Framework {
override get commandCapabilities() {
return {
build: { supportsMode: true },
dev: { supportsMode: true },
} as const;
}

readonly env = {
CLOUDFLARE_VITE_FORCE_BUILD_OUTPUT: "true",
} as const;
Expand Down
4 changes: 4 additions & 0 deletions packages/autoconfig/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ export { runAutoConfig, buildOperationsSummary } from "./run";

export { Framework } from "./frameworks/framework-class";
export type {
AutoConfigCommand,
AutoConfigCommandName,
AutoConfigCommands,
BuildConfig,
BuildTool,
ConfigurationOptions,
ConfigurationResults,
PackageJsonScriptsOverrides,
Expand Down
17 changes: 17 additions & 0 deletions packages/autoconfig/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ export async function runAutoConfig(
dryRunConfigurationResults,
{
build: buildCommand,
dev: autoConfigDetails.devCommand,
deploy:
dryRunConfigurationResults.deployCommandOverride ??
`${npx} ${target} deploy`,
Expand Down Expand Up @@ -517,6 +518,7 @@ export async function buildOperationsSummary(
configurationResults: ConfigurationResults,
projectCommands: {
build?: string;
dev?: string;
deploy: string;
version?: string;
},
Expand All @@ -532,6 +534,14 @@ export async function buildOperationsSummary(
? getWranglerConfig(workerConfig, configurationResults)
: null;

const resolvedBuildCommand = autoConfigDetails.framework.resolveCommand(
"build",
projectCommands.build
);
const resolvedDevCommand = autoConfigDetails.framework.resolveCommand(
"dev",
projectCommands.dev
);
const summary: AutoConfigSummary = {
scripts: {},
...(target === "wrangler"
Expand All @@ -543,6 +553,13 @@ export async function buildOperationsSummary(
outputDir: autoConfigDetails.outputDir,
frameworkId: autoConfigDetails.framework.id,
buildCommand: projectCommands.build,
...(configurationResults.buildTool
? { buildTool: configurationResults.buildTool }
: {}),
commands: {
...(resolvedBuildCommand ? { build: resolvedBuildCommand } : {}),
...(resolvedDevCommand ? { dev: resolvedDevCommand } : {}),
},
deployCommand: projectCommands.deploy,
versionCommand: projectCommands.version,
};
Expand Down
12 changes: 11 additions & 1 deletion packages/autoconfig/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type { AutoConfigContext, AutoConfigTarget } from "./context";
import type { Framework } from "./frameworks/framework-class";
import type { BuildConfig } from "./frameworks/framework-class";
import type {
AutoConfigCommands,
BuildConfig,
BuildTool,
} from "./frameworks/framework-class";
import type { WorkerConfigInput } from "@cloudflare/config";
import type { PackageManager } from "@cloudflare/workers-utils";
import type { PackageJSON, RawConfig } from "@cloudflare/workers-utils";
Expand All @@ -23,6 +27,8 @@ type AutoConfigDetailsBase = {
devCommand?: string;
/** The build command used to build the project (if any) */
buildCommand?: string;
/** Resolved framework commands and the arguments each command supports. */
commands?: AutoConfigCommands;
/** Environment required when running the detected dev or build commands. */
env?: Readonly<Record<string, string>>;
/** The output directory (if no framework is used, points to the raw asset files) */
Expand Down Expand Up @@ -84,6 +90,10 @@ export type AutoConfigSummary = {
outputDir: string;
frameworkId?: string;
buildCommand?: string;
/** The Cloudflare-aware tool used by this framework setup, if any. */
buildTool?: BuildTool;
/** Resolved framework commands and the arguments each command supports. */
commands: AutoConfigCommands;
deployCommand?: string;
versionCommand?: string;
};
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,18 @@ describe("autoconfig details - getDetailsForAutoConfig()", () => {
framework: { id: "astro" },
buildCommand: "npx astro build",
devCommand: "npx astro dev",
commands: {
build: {
executable: "npx",
args: ["astro", "build"],
supportsMode: true,
},
dev: {
executable: "npx",
args: ["astro", "dev"],
supportsMode: true,
},
},
packageManager: { type: "npm" },
});
});
Expand Down Expand Up @@ -97,6 +109,18 @@ describe("autoconfig details - getDetailsForAutoConfig()", () => {
).resolves.toMatchObject({
buildCommand: pm === "pnpm" ? "pnpm astro build" : "npx astro build",
devCommand: pm === "pnpm" ? "pnpm astro dev" : "npx astro dev",
commands: {
build: {
executable: pm === "pnpm" ? "pnpm" : "npx",
args: ["astro", "build"],
supportsMode: true,
},
dev: {
executable: pm === "pnpm" ? "pnpm" : "npx",
args: ["astro", "dev"],
supportsMode: true,
},
},
configured: false,
outputDir: "dist",
packageJson: {
Expand Down
1 change: 1 addition & 0 deletions packages/autoconfig/tests/frameworks/angular.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ describe("Angular framework configure()", () => {
const result = await framework.configure(BASE_OPTIONS);

expect(result.workerConfig).toEqual({});
expect(result.buildTool).toBe("wrangler");
expect(result.buildConfig?.assetsDirectory).toBe("dist/my-angular-app/");
expect(result.workerConfig).not.toHaveProperty("entrypoint");
});
Expand Down
Loading
Loading