Skip to content
Open
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: 5 additions & 0 deletions .changeset/calm-validator-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"emdash": patch
---

Fixes `emdash plugin bundle` and `emdash plugin validate` on Windows by importing generated plugin artifacts through portable file URLs.
6 changes: 6 additions & 0 deletions packages/core/src/cli/commands/bundle-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { createWriteStream } from "node:fs";
import { readdir, stat, access } from "node:fs/promises";
import { resolve, join } from "node:path";
import { pipeline } from "node:stream/promises";
import { pathToFileURL } from "node:url";

import { imageSize } from "image-size";
import { packTar } from "modern-tar/fs";
Expand Down Expand Up @@ -105,6 +106,11 @@ export async function fileExists(path: string): Promise<boolean> {
}
}

/** Convert an absolute build artifact path into a portable ESM import specifier. */
export function toFileImportSpecifier(path: string): string {
return pathToFileURL(path).href;
}

// ── Image dimension readers ──────────────────────────────────────────────────

/**
Expand Down
10 changes: 8 additions & 2 deletions packages/core/src/cli/commands/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
MAX_SCREENSHOT_HEIGHT,
readImageDimensions,
resolveSourceEntry,
toFileImportSpecifier,
totalBundleBytes,
validateBundleSize,
} from "./bundle-utils.js";
Expand Down Expand Up @@ -202,7 +203,10 @@ export const bundleCommand = defineCommand({
}

// Dynamic import of the built plugin
const pluginModule = (await import(mainOutputPath)) as Record<string, unknown>;
const pluginModule = (await import(toFileImportSpecifier(mainOutputPath))) as Record<
string,
unknown
>;

// Extract manifest from the imported module.
// Supports three patterns:
Expand Down Expand Up @@ -269,7 +273,9 @@ export const bundleCommand = defineCommand({
const backendBaseName = basename(backendEntry).replace(TS_EXT_RE, "");
const backendProbePath = await findBuildOutput(backendProbeDir, backendBaseName);
if (backendProbePath) {
const backendModule = (await import(backendProbePath)) as Record<string, unknown>;
const backendModule = (await import(
toFileImportSpecifier(backendProbePath)
)) as Record<string, unknown>;
const standardDef = (backendModule.default ?? {}) as Record<string, unknown>;
const hooks = standardDef.hooks as Record<string, unknown> | undefined;
const routes = standardDef.routes as Record<string, unknown> | undefined;
Expand Down
11 changes: 11 additions & 0 deletions packages/core/tests/unit/cli/bundle-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
findNodeBuiltinImports,
findBuildOutput,
findSourceExports,
toFileImportSpecifier,
} from "../../../src/cli/commands/bundle-utils.js";
import type { ResolvedPlugin } from "../../../src/plugins/types.js";

Expand Down Expand Up @@ -277,6 +278,16 @@ describe("findBuildOutput", () => {
});
});

describe("toFileImportSpecifier", () => {
it("returns a file URL for an absolute build artifact path", () => {
const artifactPath = join(tmpdir(), "emdash-plugin", "index.mjs");
const specifier = toFileImportSpecifier(artifactPath);

expect(new URL(specifier).protocol).toBe("file:");
expect(specifier).not.toBe(artifactPath);
});
});

describe("findNodeBuiltinImports", () => {
it("detects require('node:fs') in bundled output", () => {
expect(findNodeBuiltinImports(`const fs = require("node:fs");`)).toEqual(["fs"]);
Expand Down
Loading