diff --git a/.github/workflows/examples.yaml b/.github/workflows/examples.yaml index 337f857ac..c62167986 100644 --- a/.github/workflows/examples.yaml +++ b/.github/workflows/examples.yaml @@ -11,6 +11,15 @@ permissions: contents: read jobs: + test-sveltekit-compat: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + with: + persist-credentials: false + - uses: ./.github/actions/setup-mise + - run: mise run --skip-deps test:sveltekit-compat + test-astro-compat: runs-on: ubuntu-latest steps: diff --git a/CHANGES.md b/CHANGES.md index 3f160a9da..73bcd438e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,20 @@ Version 2.4.2 To be released. +### @fedify/init + + - Migrated the initialization template script to SvelteKit version 3, by + replacing the `$lib` syntax to `#lib`. [[#1222], [#1224] by Jungmin Yoon\] + +[#1222]: https://github.com/fedify-dev/fedify/issues/1222 +[#1224]: https://github.com/fedify-dev/fedify/issues/1224 + +### @fedify/sveltekit + + - Fixed the hook types returned by `fedifyHook()` to support both SvelteKit 2 + and 3, so SvelteKit 3 applications can type-check the integration. + [[#1222], [#1224]] + Version 2.4.1 ------------- diff --git a/changes.d/init/init-sv-3-migration.md b/changes.d/init/init-sv-3-migration.md new file mode 100644 index 000000000..c62366fe4 --- /dev/null +++ b/changes.d/init/init-sv-3-migration.md @@ -0,0 +1,5 @@ + - Migrated the initialization template script to SvelteKit version 3, by + replacing the `$lib` syntax to `#lib`. [[#1222], [#1224] by Jungmin Yoon] + +[#1222]: https://github.com/fedify-dev/fedify/issues/1222 +[#1224]: https://github.com/fedify-dev/fedify/pull/1224 diff --git a/changes.d/sveltekit/sveltekit-hook-types.md b/changes.d/sveltekit/sveltekit-hook-types.md new file mode 100644 index 000000000..22d31dc48 --- /dev/null +++ b/changes.d/sveltekit/sveltekit-hook-types.md @@ -0,0 +1,3 @@ + - Fixed the hook types returned by `fedifyHook()` to support both SvelteKit 2 + and 3, so SvelteKit 3 applications can type-check the integration. + [[#1222], [#1224]] diff --git a/mise.toml b/mise.toml index 49dd9b531..fcfe0c035 100644 --- a/mise.toml +++ b/mise.toml @@ -292,6 +292,7 @@ depends = [ "test:node", "test:bun", "test:astro-compat", + "test:sveltekit-compat", "test:temporal-polyfill", ] @@ -335,6 +336,11 @@ description = "Test @fedify/astro against supported Astro and runtime versions" depends = ["build"] run = "deno run -A packages/astro/compat/run.ts" +[tasks."test:sveltekit-compat"] +description = "Check @fedify/sveltekit consumer types against SvelteKit 2 and 3" +depends = ["build"] +run = "deno run -A packages/sveltekit/compat/run.ts" + [tasks."test:monitoring"] description = "Validate the monitoring example configuration" run = "deno run -A examples/monitoring/validate.ts" diff --git a/packages/init/src/json/deps.json b/packages/init/src/json/deps.json index 64ec5aefa..ad9d6693d 100644 --- a/packages/init/src/json/deps.json +++ b/packages/init/src/json/deps.json @@ -33,6 +33,7 @@ "npm:solid-js": "^1.9.14", "npm:tsx": "^4.23.1", "npm:typescript": "^5.9.3", + "npm:typescript@6": "^6.0.0", "npm:vinxi": "^0.5.11", "npm:x-forwarded-fetch": "^0.2.0" } diff --git a/packages/init/src/templates/sveltekit/hooks.server.ts.tpl b/packages/init/src/templates/sveltekit/hooks.server.ts.tpl index 47a60734e..af91e975e 100644 --- a/packages/init/src/templates/sveltekit/hooks.server.ts.tpl +++ b/packages/init/src/templates/sveltekit/hooks.server.ts.tpl @@ -1,6 +1,6 @@ import { fedifyHook } from "@fedify/sveltekit"; import { sequence } from "@sveltejs/kit/hooks"; -import federation from "$lib/federation"; +import federation from "#lib/federation.ts"; export const handle = sequence( fedifyHook(federation), diff --git a/packages/init/src/webframeworks/sveltekit.ts b/packages/init/src/webframeworks/sveltekit.ts index d69ae64ee..401cd925d 100644 --- a/packages/init/src/webframeworks/sveltekit.ts +++ b/packages/init/src/webframeworks/sveltekit.ts @@ -23,7 +23,7 @@ const sveltekitDescription: WebFrameworkDescription = { }, devDependencies: { ...defaultDevDependencies, - "typescript": deps["npm:typescript"], + "typescript": deps["npm:typescript@6"], "@types/node": deps["npm:@types/node@25"], ...(pmToRt(pm) === "deno" ? {} diff --git a/packages/sveltekit/compat/consumer.ts b/packages/sveltekit/compat/consumer.ts new file mode 100644 index 000000000..59971be80 --- /dev/null +++ b/packages/sveltekit/compat/consumer.ts @@ -0,0 +1,19 @@ +import type { Federation } from "@fedify/fedify/federation"; +import { fedifyHook } from "@fedify/sveltekit"; +import { sequence } from "@sveltejs/kit/hooks"; + +declare const federation: Federation<{ request: Request }>; + +// sequence accepts Handle in both major versions, even though the named type +// is exported from different modules. Check the consumer's hook assignment, +// context callback, and composition without skipping library declarations. +const hook: ReturnType = fedifyHook( + federation, + (event) => ({ request: event.request }), +); +const asyncHook: ReturnType = fedifyHook( + federation, + (event) => Promise.resolve({ request: event.request }), +); +const composed: ReturnType = sequence(hook, asyncHook); +void composed; diff --git a/packages/sveltekit/compat/run.ts b/packages/sveltekit/compat/run.ts new file mode 100644 index 000000000..18a5e9c17 --- /dev/null +++ b/packages/sveltekit/compat/run.ts @@ -0,0 +1,115 @@ +import { dirname, fromFileUrl, join, resolve } from "@std/path"; + +const compatDir = dirname(fromFileUrl(import.meta.url)); +const repoRoot = resolve(compatDir, "../../.."); +const cases = [ + { kit: "2.0.0", svelte: "^4.0.0", vite: "^5.0.0", plugin: "^3.0.0" }, + { kit: "2.36.2", svelte: "^5.0.0", vite: "^7.0.0", plugin: "^6.0.0" }, + { kit: "3.0.0", svelte: "^5.57.1", vite: "^8.0.12", plugin: "^7.0.0" }, +]; + +const tempDir = await Deno.makeTempDir({ prefix: "fedify-sveltekit-compat-" }); +try { + const tarballs = await packFedifyPackages(tempDir); + const consumer = await Deno.readTextFile(join(compatDir, "consumer.ts")); + for (const testCase of cases) { + console.log(`Checking SvelteKit ${testCase.kit} consumer types...`); + const dir = join(tempDir, testCase.kit); + await Deno.mkdir(dir); + await Deno.writeTextFile( + join(dir, "package.json"), + JSON.stringify({ + name: "fedify-sveltekit-type-consumer", + private: true, + type: "module", + dependencies: { + "@fedify/sveltekit": tarballs.get("@fedify/sveltekit"), + "@fedify/fedify": tarballs.get("@fedify/fedify"), + "@sveltejs/kit": testCase.kit, + "@sveltejs/vite-plugin-svelte": testCase.plugin, + svelte: testCase.svelte, + vite: testCase.vite, + typescript: "^6.0.0", + "@types/node": "^22.0.0", + }, + pnpm: { overrides: Object.fromEntries(tarballs) }, + }), + ); + await Deno.writeTextFile( + join(dir, "tsconfig.json"), + JSON.stringify({ + compilerOptions: { + strict: true, + noEmit: true, + skipLibCheck: false, + target: "ESNext", + module: "NodeNext", + moduleResolution: "NodeNext", + types: ["node"], + }, + files: ["consumer.mts", "consumer.cts"], + }), + ); + await Deno.writeTextFile(join(dir, "consumer.mts"), consumer); + await Deno.writeTextFile(join(dir, "consumer.cts"), consumer); + // Install packed artifacts rather than workspace links so declarations + // resolve SvelteKit from this consumer, not the workspace's pinned 2.x. + await run([ + "pnpm", + "install", + "--strict-peer-dependencies", + "--ignore-scripts", + ], dir); + await run(["pnpm", "exec", "tsc", "--project", "tsconfig.json"], dir); + console.log(`Passed SvelteKit ${testCase.kit} (ESM and CommonJS).`); + } +} finally { + await Deno.remove(tempDir, { recursive: true }); +} + +async function packFedifyPackages(dir: string): Promise> { + const tarballs = new Map(); + for ( + const name of [ + "sveltekit", + "fedify", + "uri-template", + "vocab", + "vocab-runtime", + "vocab-tools", + "webfinger", + ] + ) { + const packageDir = join(repoRoot, "packages", name); + const outputDir = join(dir, "packages", name); + await Deno.mkdir(outputDir, { recursive: true }); + await run([ + "pnpm", + "pack", + "--config.ignore-scripts=true", + "--pack-destination", + outputDir, + ], packageDir); + for await (const entry of Deno.readDir(outputDir)) { + if (entry.isFile && entry.name.endsWith(".tgz")) { + tarballs.set(`@fedify/${name}`, `file:${join(outputDir, entry.name)}`); + } + } + if (!tarballs.has(`@fedify/${name}`)) { + throw new Error(`No tarball produced for @fedify/${name}`); + } + } + return tarballs; +} + +async function run(args: string[], cwd: string): Promise { + const result = await new Deno.Command(args[0], { + args: args.slice(1), + cwd, + stdout: "inherit", + stderr: "inherit", + }).output(); + if (!result.success) { + throw new Error(`Command failed (${result.code}): ${args.join(" ")}`); + } +} diff --git a/packages/sveltekit/src/mod.ts b/packages/sveltekit/src/mod.ts index b8bc3ad1e..9d5edb0bb 100644 --- a/packages/sveltekit/src/mod.ts +++ b/packages/sveltekit/src/mod.ts @@ -14,7 +14,12 @@ import type { Federation, FederationFetchOptions, } from "@fedify/fedify/federation"; -import type { Handle, RequestEvent } from "@sveltejs/kit"; +import type { RequestEvent } from "@sveltejs/kit"; +import type { sequence } from "@sveltejs/kit/hooks"; + +// Handle moved from @sveltejs/kit to @sveltejs/kit/hooks in SvelteKit 3. +// sequence has the same public location in both supported major versions. +type Handle = ReturnType; /** * Create a SvelteKit hook handler to integrate with the {@link Federation} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 92f03442a..56ce9381e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -103,7 +103,7 @@ catalogs: specifier: jsr:^1.0.6 version: 1.1.0 '@sveltejs/kit': - specifier: ^2.0.0 + specifier: ^2.0.0 || ^3.0.0 version: 2.36.2 '@types/amqplib': specifier: ^0.10.8 @@ -39912,7 +39912,7 @@ snapshots: '@poppinss/colors': 4.1.6 '@poppinss/dumper': 0.6.5 '@speed-highlight/core': 1.2.15 - cookie: 1.0.2 + cookie: 1.1.1 youch-core: 0.3.3 youch@4.1.1: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 2ed6b43cd..a687b346b 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -109,7 +109,7 @@ catalog: "@std/assert": "jsr:^1.0.13" "@std/async": "jsr:^1.0.13" "@std/path": "jsr:^1.0.6" - "@sveltejs/kit": ^2.0.0 + "@sveltejs/kit": ^2.0.0 || ^3.0.0 "@types/amqplib": ^0.10.8 "@types/better-sqlite3": ^7.6.13 "@types/bun": ^1.2.23