Merge pull request #435 from sivdead/fix/dev-assets-directory
Create missing Wrangler assets directories before development startup. Add regression coverage for creation before CLI invocation, preservation of existing assets, and non-dev command behavior. Validation: typecheck, typecheck:mobile, build:web, and all 2244 non-E2E tests passed.
This commit is contained in:
commit
509d523f00
3 files changed
+77
-3
No files matched your search
@@ -19,6 +19,7 @@ import {
|
||||
PLACEHOLDER_D1_ID,
|
||||
productionVersionIds,
|
||||
repositoryWranglerConfigError,
|
||||
resolveWranglerAssetsDirectory,
|
||||
runWranglerSync,
|
||||
shouldCaptureDeploymentTargets,
|
||||
} from "./wrangler-runner.mjs";
|
||||
@@ -166,8 +167,9 @@ const envValue = (name) => {
|
||||
const isRemoteCommand =
|
||||
wranglerArgs.includes("deploy") || wranglerArgs.includes("--remote");
|
||||
const isDeployCommand = wranglerArgs.includes("deploy");
|
||||
const isRemoteDevCommand = wranglerArgs.includes("dev") && wranglerArgs.includes("--remote");
|
||||
const isLocalDevCommand = wranglerArgs.includes("dev") && wranglerArgs.includes("--local");
|
||||
const isDevCommand = wranglerArgs.includes("dev");
|
||||
const isRemoteDevCommand = isDevCommand && wranglerArgs.includes("--remote");
|
||||
const isLocalDevCommand = isDevCommand && wranglerArgs.includes("--local");
|
||||
// Any --local command rewrites .wrangler.generated.toml. Keep local-only vars
|
||||
// (especially auth-free access) so `d1 migrations apply --local` cannot strip
|
||||
// them and leave a later wrangler reload requiring login mid-session.
|
||||
@@ -442,6 +444,12 @@ if (isDeployCommand && Object.keys(authSecrets).length === 0 && useExistingAuthS
|
||||
writeWranglerNotice("info", "using the authentication Secret provisioned by Cloudflare");
|
||||
}
|
||||
|
||||
if (isDevCommand) {
|
||||
// Local dev serves the web app from Vite, so an empty assets directory is enough.
|
||||
const assetsDirectory = resolveWranglerAssetsDirectory(config, dirname(configPath));
|
||||
if (assetsDirectory) mkdirSync(assetsDirectory, { recursive: true });
|
||||
}
|
||||
|
||||
if (isLocalDevCommand && !hasEnvFileArg) {
|
||||
writeFileSync(generatedLocalDevEnvPath, buildLocalDevEnvironmentFile());
|
||||
finalWranglerArgs.push("--env-file", generatedLocalDevEnvPath);
|
||||
|
||||
@@ -116,6 +116,13 @@ export const shouldCaptureDeploymentTargets = (env = process.env) =>
|
||||
export const LOCAL_DEV_CREDENTIALS_ENCRYPTION_KEY =
|
||||
"edgeever-local-development-credentials-key-v1";
|
||||
|
||||
// Wrangler refuses to start `dev` when the assets directory is missing and has no
|
||||
// option to create it. Like Wrangler, resolve `[assets].directory` from the config's directory.
|
||||
export const resolveWranglerAssetsDirectory = (config, configDirectory) => {
|
||||
const match = config.match(/^\s*\[assets\][^[]*?^\s*directory\s*=\s*["']([^"']+)["']/m);
|
||||
return match ? resolve(configDirectory, match[1]) : null;
|
||||
};
|
||||
|
||||
export const buildLocalDevEnvironmentFile = () => [
|
||||
"# Local-only values. Remote instance secrets are intentionally excluded.",
|
||||
`EDGE_EVER_CREDENTIALS_ENCRYPTION_KEY=${LOCAL_DEV_CREDENTIALS_ENCRYPTION_KEY}`,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { globSync, readFileSync } from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { existsSync, globSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { resolve, sep } from "node:path";
|
||||
import {
|
||||
buildLocalDevEnvironmentFile,
|
||||
@@ -12,6 +14,7 @@ import {
|
||||
normalizeD1MigrationSql,
|
||||
productionVersionIds,
|
||||
LOCAL_DEV_CREDENTIALS_ENCRYPTION_KEY,
|
||||
resolveWranglerAssetsDirectory,
|
||||
resolveWranglerCliPath,
|
||||
resolveWranglerRuntimeExecutable,
|
||||
runWranglerSync,
|
||||
@@ -73,6 +76,62 @@ describe("cross-platform Wrangler runner", () => {
|
||||
expect(envFile).not.toContain("EDGE_EVER_AUTH_PASSWORD_HASH=");
|
||||
});
|
||||
|
||||
test("resolves the configured assets directory so local dev can create it", () => {
|
||||
const config = '[vars]\ndirectory = "wrong"\n\n[assets]\ndirectory = "public"\n';
|
||||
|
||||
expect(resolveWranglerAssetsDirectory(config, "/repo")).toBe(resolve("/repo", "public"));
|
||||
expect(resolveWranglerAssetsDirectory('name = "x"\n', "/repo")).toBeNull();
|
||||
});
|
||||
|
||||
test("prepares missing dev assets before Wrangler starts and preserves existing files", () => {
|
||||
const workingDirectory = mkdtempSync(resolve(tmpdir(), "edgeever-dev-assets-"));
|
||||
const assetsDirectory = resolve(workingDirectory, "custom-assets", "dist");
|
||||
const configPath = resolve(workingDirectory, "custom.toml");
|
||||
const cliDirectory = resolve(workingDirectory, "node_modules", "wrangler", "bin");
|
||||
const env = Object.fromEntries(
|
||||
["PATH", "Path", "PATHEXT", "SystemRoot", "ComSpec", "TEMP", "TMP"]
|
||||
.map((name) => [name, process.env[name]])
|
||||
.filter((entry): entry is [string, string] => Boolean(entry[1])),
|
||||
);
|
||||
const run = (...args: string[]) => spawnSync(
|
||||
"node",
|
||||
[resolve(import.meta.dir, "../scripts/run-wrangler.mjs"), ...args],
|
||||
{
|
||||
cwd: workingDirectory,
|
||||
encoding: "utf8",
|
||||
env: { ...env, WRANGLER_CONFIG: configPath },
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
mkdirSync(cliDirectory, { recursive: true });
|
||||
writeFileSync(configPath, 'name = "dev-assets-test"\n[assets]\ndirectory = "custom-assets/dist"\n');
|
||||
writeFileSync(resolve(cliDirectory, "wrangler.js"), [
|
||||
'const { statSync } = require("node:fs");',
|
||||
'if (process.argv.includes("dev") && !statSync("custom-assets/dist").isDirectory()) process.exit(1);',
|
||||
].join("\n"));
|
||||
|
||||
// Non-dev commands must not manufacture a missing build directory.
|
||||
const nonDev = run("d1", "execute", "DB", "--local", "--command", "SELECT 1");
|
||||
expect(nonDev.error).toBeUndefined();
|
||||
expect(nonDev.status).toBe(0);
|
||||
expect(existsSync(assetsDirectory)).toBe(false);
|
||||
|
||||
const firstStart = run("dev", "--local");
|
||||
expect(firstStart.error).toBeUndefined();
|
||||
expect(firstStart.status).toBe(0);
|
||||
expect(existsSync(assetsDirectory)).toBe(true);
|
||||
|
||||
const existingAsset = resolve(assetsDirectory, "index.html");
|
||||
writeFileSync(existingAsset, "existing build must survive dev startup");
|
||||
const secondStart = run("dev", "--local");
|
||||
expect(secondStart.status).toBe(0);
|
||||
expect(readFileSync(existingAsset, "utf8")).toBe("existing build must survive dev startup");
|
||||
} finally {
|
||||
rmSync(workingDirectory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("resolves an exact D1 database name from Wrangler JSON", () => {
|
||||
const databases = JSON.stringify([
|
||||
{ uuid: "11111111-1111-1111-1111-111111111111", name: "another-database" },
|
||||
|
||||
Reference in new issue
Block a user