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
40 changes: 38 additions & 2 deletions src/cli/subprocess_template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,13 @@ export async function resolveSubprocessTemplate(
// Build dependency graph to find all imports
const graph = await createGraph(templateUrl.href);

// Collect all local (file://) modules from the graph
// Determine which modules are "local" (should be bundled)
// For file:// URLs, all file:// modules are local
// For https:// URLs (JSR), modules under the same package are local
const localModulePrefix = getLocalModulePrefix(templateUrl);
Comment on lines +38 to +41
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is now outdated since the function handles more than just file:// URLs. The code now also processes JSR URLs and other remote URLs as "local" modules. Consider updating the comment to reflect the broader scope, such as: "Collect all local modules from the graph (based on the module prefix)"

Copilot uses AI. Check for mistakes.

const localModules = graph.modules.filter(
(m) => m.specifier.startsWith("file://") && !m.error,
(m) => m.specifier.startsWith(localModulePrefix) && !m.error,
);

if (localModules.length === 0) {
Expand Down Expand Up @@ -87,6 +91,31 @@ export async function resolveSubprocessTemplate(
return files;
}

/**
* Get the prefix for identifying "local" modules that should be bundled
*
* - For file:// URLs: all file:// modules are considered local
* - For JSR URLs (https://jsr.io/@scope/package/version/...): modules under the same package
* - For other remote URLs: modules in the same directory tree as the template
*/
function getLocalModulePrefix(templateUrl: URL): string {
if (templateUrl.protocol === "file:") {
return "file://";
}

// For JSR URLs: https://jsr.io/@scope/package/version/...
// Extract the package base URL to include all modules from the same package
const jsrMatch = templateUrl.href.match(
/^(https?:\/\/jsr\.io\/@[^/]+\/[^/]+\/[^/]+\/)/,
Copy link

Copilot AI Jan 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The regex requires a trailing slash after the version to match JSR URLs. If a JSR URL is provided without a trailing slash and without additional path segments (e.g., https://jsr.io/@scope/package/0.19.0), the regex won't match and the fallback logic will incorrectly strip the version part. While template URLs should typically point to actual files with paths, consider making the trailing slash optional in the regex pattern by changing the last \/ to \/? to handle edge cases more robustly: /^(https?:\/\/jsr\.io\/@[^/]+\/[^/]+\/[^/]+\/?)/

Suggested change
/^(https?:\/\/jsr\.io\/@[^/]+\/[^/]+\/[^/]+\/)/,
/^(https?:\/\/jsr\.io\/@[^/]+\/[^/]+\/[^/]+\/?)/,

Copilot uses AI. Check for mistakes.
);
if (jsrMatch) {
return jsrMatch[1];
}

// For other remote URLs, use the directory of the template
return templateUrl.href.substring(0, templateUrl.href.lastIndexOf("/") + 1);
}

/**
* Find the common base directory for a list of file:// URLs
*/
Expand Down Expand Up @@ -258,3 +287,10 @@ function resolveModuleDependencies(

return resolved;
}

/**
* @internal
*/
export const _internal = {
getLocalModulePrefix,
};
51 changes: 51 additions & 0 deletions src/cli/subprocess_template_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { assertEquals } from "@std/assert";
import { _internal } from "./subprocess_template.ts";

const { getLocalModulePrefix } = _internal;

Deno.test("getLocalModulePrefix", async (t) => {
await t.step("returns file:// for local file URLs", () => {
const url = new URL("file:///path/to/template.ts");
assertEquals(getLocalModulePrefix(url), "file://");
});

await t.step("extracts JSR package base for JSR URLs", () => {
const url = new URL(
"https://jsr.io/@probitas/probitas/0.19.0/src/cli/_templates/run.ts",
);
assertEquals(
getLocalModulePrefix(url),
"https://jsr.io/@probitas/probitas/0.19.0/",
);
});

await t.step("handles scoped JSR packages with different versions", () => {
const url = new URL(
"https://jsr.io/@std/path/1.0.0/mod.ts",
);
assertEquals(
getLocalModulePrefix(url),
"https://jsr.io/@std/path/1.0.0/",
);
});

await t.step("returns directory for non-JSR remote URLs", () => {
const url = new URL(
"https://example.com/modules/template.ts",
);
assertEquals(
getLocalModulePrefix(url),
"https://example.com/modules/",
);
});

await t.step("handles HTTP (non-HTTPS) JSR URLs", () => {
const url = new URL(
"http://jsr.io/@probitas/probitas/0.19.0/src/cli/_templates/run.ts",
);
assertEquals(
getLocalModulePrefix(url),
"http://jsr.io/@probitas/probitas/0.19.0/",
);
});
});
Loading