diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 496ed7c..70aae34 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,41 +1,38 @@ name: ci -on: [push, pull_request] +on: + push: + branches: + - main + pull_request: + branches: + - main + +permissions: + contents: read jobs: - deno: - name: deno_import_content-${{ matrix.os }} - if: | - github.event_name == 'push' || - !startsWith(github.event.pull_request.head.label, 'denoland:') - runs-on: ${{ matrix.os }} - timeout-minutes: 30 - strategy: - matrix: - os: [macOS-latest, ubuntu-latest, windows-2019] - - env: - GH_ACTIONS: 1 + test: + runs-on: ubuntu-latest steps: - - name: โ˜‘๏ธ clone repository - uses: actions/checkout@v2 + - name: Checkout + uses: actions/checkout@v4 - - name: โžก๏ธ install Deno + - name: Install Deno uses: denoland/setup-deno@v1 - with: - deno-version: v1.x - - - name: ๐Ÿ’„ format - if: contains(matrix.os, 'ubuntu') - run: | - deno fmt --check - - - name: ๐Ÿ’„ lint - if: contains(matrix.os, 'ubuntu') - run: | - deno lint - - - name: ๐Ÿงช test - run: | - deno task test + + - name: Verify formatting + run: deno fmt --check + + - name: Run linter + run: deno task lint + + - name: Run TypeScript checking + run: deno task check + + - name: Run tests + run: deno task test + + - name: Publish dry run + run: deno publish --dry-run diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..586fe74 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,27 @@ +name: Publish + +on: + workflow_run: + workflows: [ci] + types: [completed] + branches: [main] + +jobs: + publish: + runs-on: ubuntu-latest + + if: ${{ github.event.workflow_run.conclusion == 'success' }} + + permissions: + contents: read + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Deno + uses: denoland/setup-deno@v1 + + - name: Publish package + run: deno publish diff --git a/.gitignore b/.gitignore index 1331696..722d5e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ .vscode -deno.lock diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..0a1cfdc --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to +[Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.0.0] + +### Added + +- `importBlob()` to import a binary file as a + [Blob](https://developer.mozilla.org/en-US/docs/Web/API/Blob). +- `importBinary()` to import a binary file as a + [Uint8Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) + +## [1.0.0] + +- Initial release of `importText()` function. diff --git a/LICENSE b/LICENSE index d92eda2..d816bdc 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Mark Gibson +Copyright (c) 2024 Mark Gibson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 8d11aa7..b391de4 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,16 @@ -# deno_import_content - -[![deno doc](https://doc.deno.land/badge.svg)](https://deno.land/x/import_content/mod.ts) +# Deno Import Content Import arbitrary file content in Deno using module resolution algorithms. This includes resolving via an import map, authentication using `DENO_AUTH_TOKENS`, and use of the Deno cache. -The idea is that `importText()` should act as similar to dynamic `import()` as -possible, but just returning the raw text content instead of an evaluated JS or -TS module. +The idea is that `importText()`/`importBlob()`/`importBinary()` should act as +similar to dynamic `import()` as possible, but just returning the raw text or +binary content instead of an evaluated JS or TS module. -Unfortunately it's not possible for `importText` to resolve a relative specifier -against the calling module, so they will need to be pre-resolved using +Unfortunately it's not possible for these functions to resolve a relative +specifier against the calling module, so they will need to be pre-resolved using `import.meta.resolve()`. (See the example below, and the tests) If anyone knows how we could get around this, please raise an issue or better @@ -20,9 +18,8 @@ still, a PR. ## Permissions -This module makes use of [`deno_cache`](https://deno.land/x/deno_cache) to -perform fetching, and therefore requires all permissions that it requires. See -those [docs](https://deno.land/x/deno_cache#permissions) for full details. +This module makes use of [`@deno/cache-dir`](https://jsr.io/@deno/cache-dir) to +perform fetching, and therefore requires all permissions that it requires. Although, `--allow-write` permission for the cache dir is optional. If it is granted, then remote content will be cached, otherwise it will be fetched every @@ -31,7 +28,7 @@ time if the content isn't already present in the cache. ## Example ```ts -import { importText } from "https://deno.land/x/import_content/mod.ts"; +import { importText } from "jsr:@jollytoad/import-content"; // Fetch the text content of a remote file const remoteReadme = await importText( @@ -45,8 +42,18 @@ const localReadme = await importText(import.meta.resolve("./README.md")); const mappedReadme = await importText("bare/README.md"); ``` -## Fixed Dependencies +## Planned Obsolescence + +Hopefully this entire package will become completely obsolete once Deno fully +supports importing of arbitrary files via import attributes... + +```ts +const remoteReadme = await import( + "https://deno.land/x/import_content/README.md", + { with: { type: "text" } } +); + +const localReadme = await import("./README.md", { with: { type: "text" } }); -Previous versions of this module depended on my patched version of `deno_cache`, -due to bugs in that module. These have since been fixed, so this module now -depends on the official [`deno_cache`](https://deno.land/x/deno_cache@0.6.1). +const mappedReadme = await import("bare/README.md", { with: { type: "text" } }); +``` diff --git a/deno.json b/deno.json index 50a0a76..9c21fa9 100644 --- a/deno.json +++ b/deno.json @@ -1,6 +1,31 @@ { + "name": "@jollytoad/import-content", + "version": "1.1.0", + "exports": { + ".": "./mod.ts", + "./import-text": "./import_text.ts", + "./import-binary": "./import_binary.ts", + "./import-blob": "./import_blob.ts" + }, "tasks": { - "test": "deno test --allow-read --allow-write --allow-net --allow-env --import-map=test/import_map.json", - "check": "deno fmt && deno lint && deno check **/*.ts" + "test": "deno test --allow-read --allow-write --allow-net --allow-env", + "check": "deno check **/*.ts", + "lint": "deno lint && deno doc --lint **/*.ts", + "ok": "deno fmt && deno task lint && deno task check && deno task test && deno publish --dry-run --allow-dirty", + "outdated": "deno run --allow-read=. --allow-net=jsr.io,registry.npmjs.org jsr:@check/deps", + "lock": "rm -f deno.lock && deno task check" + }, + "imports": { + "$test/": "http://localhost:8910/", + "@deno/cache-dir": "jsr:@deno/cache-dir@^0.10.1", + "@http/host-deno-local": "jsr:@http/host-deno-local@^0.18.0", + "@std/assert": "jsr:@std/assert@^1.0.0-rc.2", + "@std/ulid": "jsr:@std/ulid@^1.0.0-rc.2" + }, + "publish": { + "exclude": [ + ".github", + "deno.lock" + ] } } diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..4577422 --- /dev/null +++ b/deno.lock @@ -0,0 +1,83 @@ +{ + "version": "3", + "packages": { + "specifiers": { + "jsr:@deno/cache-dir@^0.10.1": "jsr:@deno/cache-dir@0.10.1", + "jsr:@deno/graph@^0.73.1": "jsr:@deno/graph@0.73.1", + "jsr:@http/host-deno-local@^0.18.0": "jsr:@http/host-deno-local@0.18.0", + "jsr:@std/assert@^0.223.0": "jsr:@std/assert@0.223.0", + "jsr:@std/assert@^1.0.0-rc.2": "jsr:@std/assert@1.0.0-rc.2", + "jsr:@std/bytes@^0.223.0": "jsr:@std/bytes@0.223.0", + "jsr:@std/fmt@^0.223": "jsr:@std/fmt@0.223.0", + "jsr:@std/fs@^0.223": "jsr:@std/fs@0.223.0", + "jsr:@std/internal@^1.0.0": "jsr:@std/internal@1.0.0", + "jsr:@std/io@^0.223": "jsr:@std/io@0.223.0", + "jsr:@std/path@^0.223": "jsr:@std/path@0.223.0", + "jsr:@std/ulid@^1.0.0-rc.2": "jsr:@std/ulid@1.0.0-rc.2" + }, + "jsr": { + "@deno/cache-dir@0.10.1": { + "integrity": "30f0a3b975df62adebd859a56f285873c56c6e68190e4370c7a539c6664559ad", + "dependencies": [ + "jsr:@deno/graph@^0.73.1", + "jsr:@std/fmt@^0.223", + "jsr:@std/fs@^0.223", + "jsr:@std/io@^0.223", + "jsr:@std/path@^0.223" + ] + }, + "@deno/graph@0.73.1": { + "integrity": "cd69639d2709d479037d5ce191a422eabe8d71bb68b0098344f6b07411c84d41" + }, + "@http/host-deno-local@0.18.0": { + "integrity": "2d493914ca19df226680cab2d15891212294b70f24ca256d9f4cc245cdfe5a4e" + }, + "@std/assert@0.223.0": { + "integrity": "eb8d6d879d76e1cc431205bd346ed4d88dc051c6366365b1af47034b0670be24" + }, + "@std/assert@1.0.0-rc.2": { + "integrity": "0484eab1d76b55fca1c3beaff485a274e67dd3b9f065edcbe70030dfc0b964d3", + "dependencies": [ + "jsr:@std/internal@^1.0.0" + ] + }, + "@std/bytes@0.223.0": { + "integrity": "84b75052cd8680942c397c2631318772b295019098f40aac5c36cead4cba51a8" + }, + "@std/fmt@0.223.0": { + "integrity": "6deb37794127dfc7d7bded2586b9fc6f5d50e62a8134846608baf71ffc1a5208" + }, + "@std/fs@0.223.0": { + "integrity": "3b4b0550b2c524cbaaa5a9170c90e96cbb7354e837ad1bdaf15fc9df1ae9c31c" + }, + "@std/internal@1.0.0": { + "integrity": "ac6a6dfebf838582c4b4f61a6907374e27e05bedb6ce276e0f1608fe84e7cd9a" + }, + "@std/io@0.223.0": { + "integrity": "2d8c3c2ab3a515619b90da2c6ff5ea7b75a94383259ef4d02116b228393f84f1", + "dependencies": [ + "jsr:@std/assert@^0.223.0", + "jsr:@std/bytes@^0.223.0" + ] + }, + "@std/path@0.223.0": { + "integrity": "593963402d7e6597f5a6e620931661053572c982fc014000459edc1f93cc3989", + "dependencies": [ + "jsr:@std/assert@^0.223.0" + ] + }, + "@std/ulid@1.0.0-rc.2": { + "integrity": "9b224c358c7a8d65f62bb1db2fb76e80278827b43a91866a4bbdd93028d7a333" + } + } + }, + "remote": {}, + "workspace": { + "dependencies": [ + "jsr:@deno/cache-dir@^0.10.1", + "jsr:@http/host-deno-local@^0.18.0", + "jsr:@std/assert@^1.0.0-rc.2", + "jsr:@std/ulid@^1.0.0-rc.2" + ] + } +} diff --git a/deps.ts b/deps.ts deleted file mode 100644 index f1fd3b1..0000000 --- a/deps.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { DenoDir } from "https://deno.land/x/deno_cache@0.6.1/deno_dir.ts"; -export { FileFetcher } from "https://deno.land/x/deno_cache@0.6.1/file_fetcher.ts"; diff --git a/file_fetcher.ts b/file_fetcher.ts new file mode 100644 index 0000000..6469869 --- /dev/null +++ b/file_fetcher.ts @@ -0,0 +1,56 @@ +import { DenoDir, FileFetcher } from "@deno/cache-dir"; + +let fileFetcher: FileFetcher | undefined; + +/** + * INTERNAL: For test usage only + * @private + * @deprecated + */ +export function __reset__() { + fileFetcher = undefined; +} + +/** + * Get a cached instance of the FileFetcher + */ +async function getFileFetcher() { + if (!fileFetcher) { + const denoDir = new DenoDir(); + const writeGranted = + (await Deno.permissions.query({ name: "write", path: denoDir.root })) + .state === "granted"; + fileFetcher = new FileFetcher(() => + denoDir.createHttpCache({ readOnly: !writeGranted }) + ); + } + return fileFetcher; +} + +/** + * Fetch the file using the FileFetcher + */ +export async function fetchFile( + specifier: string, +): Promise { + const fileFetcher = await getFileFetcher(); + + if (isRelative(specifier)) { + throw new TypeError( + `Cannot import a relative module "${specifier}", use import.meta.resolve() to first resolve to an absolute URL.`, + ); + } + + const resolved = new URL(import.meta.resolve(specifier)); + const response = await fileFetcher.fetch(resolved); + + if (response?.kind === "module") { + return response.content; + } else { + throw new TypeError(`Module content not found "${specifier.toString()}".`); + } +} + +function isRelative(specifier: string) { + return specifier.startsWith("./") || specifier.startsWith("../"); +} diff --git a/import_binary.ts b/import_binary.ts new file mode 100644 index 0000000..e18b6d4 --- /dev/null +++ b/import_binary.ts @@ -0,0 +1,17 @@ +import { fetchFile } from "./file_fetcher.ts"; + +/** + * Import an arbitrary binary file via a Deno module specifier, as similar to a dynamic `import()` + * as possible, but just returns the binary content rather than evaluating it as a JS/TS module. + * + * @param specifier An absolute URL, or bare module specifier, but not a relative URL, use import.meta.resolve(), + * to first resolve those to an absolute URL. Can be to any file, not just JS/TS, + * @returns The content of the file as a Uint8Array. + */ +export async function importBinary(specifier: string): Promise { + const content = await fetchFile(specifier); + + return typeof content === "string" + ? new TextEncoder().encode(content) + : content; +} diff --git a/import_blob.ts b/import_blob.ts new file mode 100644 index 0000000..80d5f80 --- /dev/null +++ b/import_blob.ts @@ -0,0 +1,13 @@ +import { fetchFile } from "./file_fetcher.ts"; + +/** + * Import an arbitrary binary file via a Deno module specifier, as similar to a dynamic `import()` + * as possible, but just returns the binary content rather than evaluating it as a JS/TS module. + * + * @param specifier An absolute URL, or bare module specifier, but not a relative URL, use import.meta.resolve(), + * to first resolve those to an absolute URL. Can be to any file, not just JS/TS, + * @returns The content of the file as a Blob. + */ +export async function importBlob(specifier: string): Promise { + return new Blob([await fetchFile(specifier)]); +} diff --git a/import_text.ts b/import_text.ts new file mode 100644 index 0000000..6b54c6d --- /dev/null +++ b/import_text.ts @@ -0,0 +1,17 @@ +import { fetchFile } from "./file_fetcher.ts"; + +/** + * Import an arbitrary text file via a Deno module specifier, as similar to a dynamic `import()` + * as possible, but just returns the plain text (UTF-8) content rather than evaluating it as a JS/TS module. + * + * @param specifier An absolute URL, or bare module specifier, but not a relative URL, use import.meta.resolve(), + * to first resolve those to an absolute URL. Can be to any file, not just JS/TS, + * @returns The content of the file decoded as UTF-8 text. + */ +export async function importText(specifier: string): Promise { + const content = await fetchFile(specifier); + + return typeof content === "string" + ? content + : new TextDecoder().decode(content); +} diff --git a/mod.ts b/mod.ts index 0652b80..94fb4c2 100644 --- a/mod.ts +++ b/mod.ts @@ -1,52 +1,3 @@ -import { DenoDir, FileFetcher } from "./deps.ts"; - -let fileFetcher: FileFetcher | undefined; - -/** - * Import an arbitrary text file via a Deno module specifier, as similar to a dynamic `import()` - * as possible, but just returns the plain text (UTF-8) content rather than evaluating it as a JS/TS module. - * - * @param specifier An absolute URL, or bare module specifier, but not a relative URL, use import.meta.resolve(), - * to first resolve those to an absolute URL. Can be to any file, not just JS/TS, - * @returns The content of the file decoded as UTF-8 text. - */ -export async function importText(specifier: string): Promise { - if (!fileFetcher) { - const denoDir = new DenoDir(); - const writeGranted = - (await Deno.permissions.query({ name: "write", path: denoDir.root })) - .state === "granted"; - fileFetcher = new FileFetcher( - denoDir.createHttpCache({ readOnly: !writeGranted }), - ); - } - - if (isRelative(specifier)) { - throw new TypeError( - `Cannot import a relative module "${specifier}", use import.meta.resolve() to first resolve to an absolute URL.`, - ); - } - - const resolved = new URL(import.meta.resolve(specifier)); - const response = await fileFetcher.fetch(resolved); - - if (response?.kind === "module") { - return response.content; - } else { - throw new TypeError(`Module content not found "${specifier.toString()}".`); - } -} - -/** - * INTERNAL: For test usage only - * @deprecated - */ -export function __reset__() { - fileFetcher = undefined; -} - -// TODO: Consider adding importBlob to support binary imports - -function isRelative(specifier: string) { - return specifier.startsWith("./") || specifier.startsWith("../"); -} +export { importText } from "./import_text.ts"; +export { importBlob } from "./import_blob.ts"; +export { importBinary } from "./import_binary.ts"; diff --git a/test/deno.png b/test/deno.png new file mode 100644 index 0000000..b1300c1 Binary files /dev/null and b/test/deno.png differ diff --git a/test/deps.ts b/test/deps.ts deleted file mode 100644 index 17ec536..0000000 --- a/test/deps.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { assertEquals } from "https://deno.land/std@0.204.0/assert/assert_equals.ts"; -export { assertRejects } from "https://deno.land/std@0.204.0/assert/assert_rejects.ts"; -export { assertStringIncludes } from "https://deno.land/std@0.204.0/assert/assert_string_includes.ts"; - -export { ulid as randomString } from "https://deno.land/std@0.204.0/ulid/mod.ts"; diff --git a/test/import_binary_test.ts b/test/import_binary_test.ts new file mode 100644 index 0000000..7d53f0d --- /dev/null +++ b/test/import_binary_test.ts @@ -0,0 +1,67 @@ +import { __reset__ } from "../file_fetcher.ts"; +import { assertEquals } from "@std/assert"; +import { ulid as randomString } from "@std/ulid"; +import { withServer } from "./with_server.ts"; +import { importBinary } from "../import_binary.ts"; + +const CONTENT = Deno.readFileSync(new URL(import.meta.resolve("./deno.png"))); + +// NOTE: It's important that these tests are NOT in the same folder as the importBinary module +// itself, otherwise we can't be sure that the relative import tests are correct. + +Deno.test("import resolved local binary content", async () => { + __reset__(); + const content = await importBinary(import.meta.resolve("./deno.png")); + assertEquals(content, CONTENT); +}); + +Deno.test({ + name: "import remote binary content", + fn: withServer( + respondWithContent, + { port: 8910 }, + async (url) => { + __reset__(); + const content = await importBinary(`${url}/${randomString()}`); + assertEquals(content, CONTENT); + }, + ), +}); + +Deno.test({ + name: "import remote binary content with no write permission", + permissions: { + env: true, + net: true, + read: true, + write: false, + }, + fn: withServer( + respondWithContent, + { port: 8910 }, + async (url) => { + __reset__(); + const content = await importBinary(`${url}/${randomString()}`); + assertEquals(content, CONTENT); + }, + ), +}); + +Deno.test({ + name: "import remote binary content resolved via import map", + fn: withServer( + respondWithContent, + { port: 8910 }, + async () => { + __reset__(); + const content = await importBinary(`$test/${randomString()}`); + assertEquals(content, CONTENT); + }, + ), +}); + +function respondWithContent() { + return new Response(CONTENT, { + status: 200, + }); +} diff --git a/test/import_blob_test.ts b/test/import_blob_test.ts new file mode 100644 index 0000000..e527ab6 --- /dev/null +++ b/test/import_blob_test.ts @@ -0,0 +1,71 @@ +import { __reset__ } from "../file_fetcher.ts"; +import { assertEquals } from "@std/assert"; +import { ulid as randomString } from "@std/ulid"; +import { withServer } from "./with_server.ts"; +import { importBlob } from "../import_blob.ts"; + +const CONTENT = Deno.readFileSync(new URL(import.meta.resolve("./deno.png"))); + +// NOTE: It's important that these tests are NOT in the same folder as the importBlob module +// itself, otherwise we can't be sure that the relative import tests are correct. + +Deno.test("import resolved local blob content", async () => { + __reset__(); + const blob = await importBlob(import.meta.resolve("./deno.png")); + const content = new Uint8Array(await blob.arrayBuffer()); + assertEquals(content, CONTENT); +}); + +Deno.test({ + name: "import remote blob content", + fn: withServer( + respondWithContent, + { port: 8910 }, + async (url) => { + __reset__(); + const blob = await importBlob(`${url}/${randomString()}`); + const content = new Uint8Array(await blob.arrayBuffer()); + assertEquals(content, CONTENT); + }, + ), +}); + +Deno.test({ + name: "import remote blob content with no write permission", + permissions: { + env: true, + net: true, + read: true, + write: false, + }, + fn: withServer( + respondWithContent, + { port: 8910 }, + async (url) => { + __reset__(); + const blob = await importBlob(`${url}/${randomString()}`); + const content = new Uint8Array(await blob.arrayBuffer()); + assertEquals(content, CONTENT); + }, + ), +}); + +Deno.test({ + name: "import remote blob content resolved via import map", + fn: withServer( + respondWithContent, + { port: 8910 }, + async () => { + __reset__(); + const blob = await importBlob(`$test/${randomString()}`); + const content = new Uint8Array(await blob.arrayBuffer()); + assertEquals(content, CONTENT); + }, + ), +}); + +function respondWithContent() { + return new Response(CONTENT, { + status: 200, + }); +} diff --git a/test/import_map.json b/test/import_map.json deleted file mode 100644 index d64c8a1..0000000 --- a/test/import_map.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "imports": { - "test/": "http://localhost:8910/" - } -} diff --git a/test/import_text_test.ts b/test/import_text_test.ts index 1954b27..f45e7ca 100644 --- a/test/import_text_test.ts +++ b/test/import_text_test.ts @@ -1,5 +1,7 @@ -import { __reset__, importText } from "../mod.ts"; -import { assertEquals, assertRejects, randomString } from "./deps.ts"; +import { importText } from "../import_text.ts"; +import { __reset__ } from "../file_fetcher.ts"; +import { assertEquals, assertRejects } from "@std/assert"; +import { ulid as randomString } from "@std/ulid"; import { withServer } from "./with_server.ts"; Deno.env.set( @@ -29,7 +31,7 @@ Deno.test({ name: "import remote text content", fn: withServer( respondWithContent, - { hostname: "localhost", port: 8910 }, + { port: 8910 }, async (url) => { __reset__(); const content = await importText(`${url}/${randomString()}`); @@ -48,7 +50,7 @@ Deno.test({ }, fn: withServer( respondWithContent, - { hostname: "localhost", port: 8910 }, + { port: 8910 }, async (url) => { __reset__(); const content = await importText(`${url}/${randomString()}`); @@ -61,10 +63,10 @@ Deno.test({ name: "import remote text content resolved via import map", fn: withServer( respondWithContent, - { hostname: "localhost", port: 8910 }, + { port: 8910 }, async () => { __reset__(); - const content = await importText(`test/${randomString()}`); + const content = await importText(`$test/${randomString()}`); assertEquals(content, CONTENT); }, ), @@ -73,7 +75,6 @@ Deno.test({ Deno.test({ name: "import remote text content using bearer token", fn: withServer(respondWithAuthorization, { - hostname: "localhost", port: 8911, }, async (url) => { __reset__(); @@ -85,7 +86,6 @@ Deno.test({ Deno.test({ name: "import remote text content using basic auth", fn: withServer(respondWithAuthorization, { - hostname: "localhost", port: 8912, }, async (url) => { __reset__(); diff --git a/test/with_server.ts b/test/with_server.ts index 0fc7001..188d1b4 100644 --- a/test/with_server.ts +++ b/test/with_server.ts @@ -1,3 +1,5 @@ +import { getServerUrl } from "@http/host-deno-local/server-url"; + /** * Create a basic HTTP server to run a test against. * @@ -8,32 +10,19 @@ */ export const withServer = ( handler: Deno.ServeHandler, - opts: Pick, + opts: Pick, test: (url: string, t: Deno.TestContext) => void | Promise, ) => async (t: Deno.TestContext) => { - const controller = new AbortController(); - - let caught: unknown; - - await Deno.serve({ - handler, + const server = Deno.serve({ ...opts, - signal: controller.signal, - onListen: ({ hostname, port }) => { - queueMicrotask(async () => { - try { - await test(`http://${hostname}:${port}`, t); - } catch (e) { - caught = e; - } finally { - controller.abort(); - } - }); - }, - }).finished; + handler, + hostname: "::", + }); - if (caught) { - throw caught; + try { + await test(getServerUrl(server.addr.hostname, server.addr.port), t); + } finally { + await server.shutdown(); } };