From 163ba78216a50da98a438f68ffa7269d32f00bbc Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Tue, 22 Mar 2022 22:34:20 +0100 Subject: [PATCH] feat: add support for import maps (#105) --- .github/workflows/test.yml | 1 + action.yml | 3 +++ action/deps.js | 8 ++++---- action/index.js | 18 +++++++++++++++++- deployctl.ts | 1 + examples/hello.ts | 2 +- examples/import_map.json | 5 +++++ src/subcommands/deploy.ts | 18 ++++++++++++++++++ src/utils/api_types.ts | 2 ++ src/utils/entrypoint.ts | 5 +++-- 10 files changed, 55 insertions(+), 8 deletions(-) create mode 100644 examples/import_map.json diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1cb0db50..5f2719fb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,3 +22,4 @@ jobs: project: happy-rat-64 root: examples entrypoint: hello.ts + import-map: ./import_map.json diff --git a/action.yml b/action.yml index 7f190454..41f7aee8 100644 --- a/action.yml +++ b/action.yml @@ -13,6 +13,9 @@ inputs: entrypoint: description: The path or URL to the entrypoint file required: true + import-map: + description: The path or URL to an import map file + required: false root: description: The path to the directory containing the code and assets to upload required: false diff --git a/action/deps.js b/action/deps.js index 1f94e423..94eaefab 100644 --- a/action/deps.js +++ b/action/deps.js @@ -2439,22 +2439,22 @@ new TextEncoder(); white: mod2.white, gray: mod2.gray }); -async function parseEntrypoint(entrypoint, cwd) { +async function parseEntrypoint(entrypoint, root, diagnosticName = "entrypoint") { let entrypointSpecifier; try { if (entrypoint.startsWith("https://") || entrypoint.startsWith("http://") || entrypoint.startsWith("file://")) { entrypointSpecifier = new URL(entrypoint); } else { - entrypointSpecifier = toFileUrl2(resolve2(cwd ?? Deno.cwd(), entrypoint)); + entrypointSpecifier = toFileUrl2(resolve2(root ?? Deno.cwd(), entrypoint)); } } catch (err) { - throw `Failed to parse entrypoint specifier '${entrypoint}': ${err.message}`; + throw `Failed to parse ${diagnosticName} specifier '${entrypoint}': ${err.message}`; } if (entrypointSpecifier.protocol == "file:") { try { await Deno.lstat(entrypointSpecifier); } catch (err) { - throw `Failed to open entrypoint file at '${entrypointSpecifier}': ${err.message}`; + throw `Failed to open ${diagnosticName} file at '${entrypointSpecifier}': ${err.message}`; } } return entrypointSpecifier; diff --git a/action/index.js b/action/index.js index 55bfe952..4badd404 100644 --- a/action/index.js +++ b/action/index.js @@ -9,6 +9,7 @@ const ORIGIN = process.env.DEPLOY_API_ENDPOINT ?? "https://dash.deno.com"; async function main() { const projectId = core.getInput("project", { required: true }); const entrypoint = core.getInput("entrypoint", { required: true }); + const importMap = core.getInput("import-map", {}); const cwd = resolve(process.cwd(), core.getInput("root", {})); if (github.context.eventName === "pull_request") { @@ -40,13 +41,27 @@ async function main() { if (url.protocol === "file:") { const path = fromFileUrl(url); if (!path.startsWith(cwd)) { - throw "Entrypoint must be in the current working directory."; + throw "Entrypoint must be in the working directory (cwd, or specified root directory)."; } const entrypoint = path.slice(cwd.length); url = new URL(`file:///src${entrypoint}`); } core.info(`Entrypoint: ${url.href}`); + let importMapUrl = null; + if (importMap) { + importMapUrl = await parseEntrypoint(importMap, cwd, "import map"); + if (importMapUrl.protocol === "file:") { + const path = fromFileUrl(importMapUrl); + if (!path.startsWith(cwd)) { + throw "Import map must be in the working directory (cwd, or specified root directory)."; + } + const importMap = path.slice(cwd.length); + importMapUrl = new URL(`file:///src${importMap}`); + } + core.info(`Import map: ${importMapUrl.href}`); + } + core.debug(`Discovering assets in "${cwd}"`); const assets = new Map(); const entries = await walk(cwd, cwd, assets, { @@ -81,6 +96,7 @@ async function main() { const req = { url: url.href, + importMapUrl: importMapUrl?.href ?? null, manifest, event: github.context.payload, }; diff --git a/deployctl.ts b/deployctl.ts index e650f86b..b495686a 100755 --- a/deployctl.ts +++ b/deployctl.ts @@ -46,6 +46,7 @@ const args = parseArgs(Deno.args, { "token", "include", "exclude", + "import-map", ], default: { static: true, diff --git a/examples/hello.ts b/examples/hello.ts index d5f8c279..805c71ca 100644 --- a/examples/hello.ts +++ b/examples/hello.ts @@ -1,4 +1,4 @@ -import { serve } from "https://deno.land/std@0.114.0/http/server.ts"; +import { serve } from "std/http/server.ts"; async function handler(_req: Request) { const text = await Deno.readTextFile(new URL(import.meta.url)); diff --git a/examples/import_map.json b/examples/import_map.json new file mode 100644 index 00000000..62266803 --- /dev/null +++ b/examples/import_map.json @@ -0,0 +1,5 @@ +{ + "imports": { + "std/": "https://deno.land/std@0.128.0/" + } +} diff --git a/src/subcommands/deploy.ts b/src/subcommands/deploy.ts index f753da69..05654626 100644 --- a/src/subcommands/deploy.ts +++ b/src/subcommands/deploy.ts @@ -28,6 +28,7 @@ USAGE: OPTIONS: --exclude= Exclude files that match this pattern --include= Only upload files that match this pattern + --import-map= Use import map file -h, --help Prints help information --no-static Don't include the files in the CWD as static files --prod Create a production deployment (default is preview deployment) @@ -43,6 +44,7 @@ export interface Args { include?: string[]; token: string | null; project: string | null; + importMap: string | null; } // deno-lint-ignore no-explicit-any @@ -53,6 +55,7 @@ export default async function (rawArgs: Record): Promise { prod: !!rawArgs.prod, token: rawArgs.token ? String(rawArgs.token) : null, project: rawArgs.project ? String(rawArgs.project) : null, + importMap: rawArgs["import-map"] ? String(rawArgs["import-map"]) : null, exclude: rawArgs.exclude?.split(","), include: rawArgs.include?.split(","), }; @@ -83,6 +86,10 @@ export default async function (rawArgs: Record): Promise { const opts = { entrypoint: await parseEntrypoint(entrypoint).catch((e) => error(e)), + importMapUrl: args.importMap === null + ? null + : await parseEntrypoint(args.importMap, undefined, "import map") + .catch((e) => error(e)), static: args.static, prod: args.prod, token, @@ -96,6 +103,7 @@ export default async function (rawArgs: Record): Promise { interface DeployOpts { entrypoint: URL; + importMapUrl: URL | null; static: boolean; prod: boolean; exclude?: string[]; @@ -124,6 +132,15 @@ async function deploy(opts: DeployOpts): Promise { const entrypoint = path.slice(cwd.length); url = new URL(`file:///src${entrypoint}`); } + let importMapUrl = opts.importMapUrl; + if (importMapUrl && importMapUrl.protocol === "file:") { + const path = fromFileUrl(importMapUrl); + if (!path.startsWith(cwd)) { + error("Import map must be in the current working directory."); + } + const entrypoint = path.slice(cwd.length); + importMapUrl = new URL(`file:///src${entrypoint}`); + } let uploadSpinner: Spinner | null = null; const files = []; @@ -169,6 +186,7 @@ async function deploy(opts: DeployOpts): Promise { let deploySpinner: Spinner | null = null; const req = { url: url.href, + importMapUrl: importMapUrl ? importMapUrl.href : null, production: opts.prod, manifest, }; diff --git a/src/utils/api_types.ts b/src/utils/api_types.ts index 1425187f..63f691bc 100644 --- a/src/utils/api_types.ts +++ b/src/utils/api_types.ts @@ -49,12 +49,14 @@ export type ManifestEntry = export interface PushDeploymentRequest { url: string; + importMapUrl: string | null; production: boolean; manifest?: { entries: Record }; } export interface GitHubActionsDeploymentRequest { url: string; + importMapUrl: string | null; manifest: { entries: Record }; event?: unknown; } diff --git a/src/utils/entrypoint.ts b/src/utils/entrypoint.ts index d370be76..006cac09 100644 --- a/src/utils/entrypoint.ts +++ b/src/utils/entrypoint.ts @@ -7,6 +7,7 @@ import { resolve, toFileUrl } from "../../deps.ts"; export async function parseEntrypoint( entrypoint: string, root?: string, + diagnosticName = "entrypoint", ): Promise { let entrypointSpecifier: URL; try { @@ -19,14 +20,14 @@ export async function parseEntrypoint( entrypointSpecifier = toFileUrl(resolve(root ?? Deno.cwd(), entrypoint)); } } catch (err) { - throw `Failed to parse entrypoint specifier '${entrypoint}': ${err.message}`; + throw `Failed to parse ${diagnosticName} specifier '${entrypoint}': ${err.message}`; } if (entrypointSpecifier.protocol == "file:") { try { await Deno.lstat(entrypointSpecifier); } catch (err) { - throw `Failed to open entrypoint file at '${entrypointSpecifier}': ${err.message}`; + throw `Failed to open ${diagnosticName} file at '${entrypointSpecifier}': ${err.message}`; } }