From 664b54abff87c0fbcae012c0483ed6779286f6aa Mon Sep 17 00:00:00 2001 From: Hong Minhee Date: Tue, 6 Oct 2026 19:41:01 +0900 Subject: [PATCH] Keep pnpm links intact when running Deno Keep Deno's npm cache separate from pnpm's node_modules so Deno 2.9 cannot remove workspace links before packing. Declare the development dependencies previously supplied by Deno's root links, and load the Fresh Vite config natively through an explicit npm entry point. Add an isolated regression task that uses the root setting and checks link preservation, prepack execution, and workspace version rewriting. Check the cache setting in CI without installing fixture dependencies. Codex implemented and tested the changes; Claude Code reviewed the plan and the implementation. Fixes https://github.com/fedify-dev/fedify/issues/1259 Changelog: none Assisted-by: Codex:gpt-6.1-sol Assisted-by: Claude Code:claude-fable-5-1 --- deno.json | 2 +- docs/package.json | 3 + examples/fresh/deno.json | 4 +- mise.toml | 14 ++++ packages/fastify/package.json | 1 + packages/sqlite/package.json | 1 + packages/testing/package.json | 2 + pnpm-lock.yaml | 21 ++++++ scripts/test_pack.mjs | 118 ++++++++++++++++++++++++++++++++++ 9 files changed, 163 insertions(+), 3 deletions(-) create mode 100644 scripts/test_pack.mjs diff --git a/deno.json b/deno.json index 0eed3f0c4..6f9d94247 100644 --- a/deno.json +++ b/deno.json @@ -90,7 +90,7 @@ "packages/vocab-runtime/src/contexts/fep-22cd.json" ] }, - "nodeModulesDir": "auto", + "nodeModulesDir": "none", "tasks": { "build": { "command": "pnpm --recursive --filter '@fedify/*' --parallel --silent build", diff --git a/docs/package.json b/docs/package.json index 4b04a1e13..b27984966 100644 --- a/docs/package.json +++ b/docs/package.json @@ -23,12 +23,15 @@ "@fedify/vocab": "workspace:^", "@fedify/vocab-runtime": "workspace:^", "@fedify/relay": "workspace:^", + "@fedify/webfinger": "workspace:^", "@hono/node-server": "^1.13.7", "@js-temporal/polyfill": "catalog:", "@logtape/file": "catalog:", "@logtape/logtape": "catalog:", "@nestjs/common": "catalog:", "@opentelemetry/api": "catalog:", + "@opentelemetry/context-async-hooks": "catalog:", + "@opentelemetry/core": "catalog:", "@opentelemetry/exporter-trace-otlp-proto": "catalog:", "@opentelemetry/sdk-node": "catalog:", "@opentelemetry/sdk-trace-base": "catalog:", diff --git a/examples/fresh/deno.json b/examples/fresh/deno.json index e36f9c41b..36138585b 100644 --- a/examples/fresh/deno.json +++ b/examples/fresh/deno.json @@ -1,8 +1,8 @@ { "tasks": { "check": "deno fmt --check . && deno lint . && deno check", - "dev": "vite", - "build": "vite build", + "dev": "deno run -A npm:vite@^7.1.3 --configLoader native", + "build": "deno run -A npm:vite@^7.1.3 build --configLoader native", "start": "deno serve -A _fresh/server.js", "update": "deno run -A -r jsr:@fresh/update ." }, diff --git a/mise.toml b/mise.toml index 4cf727f5e..d45b38da4 100644 --- a/mise.toml +++ b/mise.toml @@ -70,9 +70,19 @@ depends = [ "check:md", "check-versions", "check:manifest:workspace-protocol", + "check:node-modules", ] run = "sacho check" +[tasks."check:node-modules"] +description = "Check that Deno leaves pnpm's node_modules directories alone" +run = ''' +if ! jq -e '.nodeModulesDir == "none"' deno.json > /dev/null; then + echo 'Set nodeModulesDir to "none" so Deno preserves pnpm workspace links.' + exit 1 +fi +''' + [tasks."check:fmt"] description = "Check code formatting" run = "deno fmt --check" @@ -159,6 +169,10 @@ done ''' # Testing +[tasks."test:pack"] +description = "Check that Deno preserves pnpm links and package packing" +run = "node scripts/test_pack.mjs" + [tasks."test:deno"] description = "Run the test suite using Deno" depends = ["prepare"] diff --git a/packages/fastify/package.json b/packages/fastify/package.json index bb8fbcca8..b0d0d3bf8 100644 --- a/packages/fastify/package.json +++ b/packages/fastify/package.json @@ -51,6 +51,7 @@ "fastify": "catalog:" }, "devDependencies": { + "@fedify/vocab": "workspace:^", "@types/node": "catalog:", "tsdown": "catalog:", "typescript": "catalog:" diff --git a/packages/sqlite/package.json b/packages/sqlite/package.json index 89961a58b..81ed52f00 100644 --- a/packages/sqlite/package.json +++ b/packages/sqlite/package.json @@ -75,6 +75,7 @@ "@fedify/fedify": "workspace:^" }, "devDependencies": { + "@fedify/fixture": "workspace:^", "@fedify/testing": "workspace:^", "@std/async": "catalog:", "tsdown": "catalog:", diff --git a/packages/testing/package.json b/packages/testing/package.json index 642c7e23a..96a1d0452 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -57,6 +57,8 @@ }, "devDependencies": { "@fedify/fixture": "workspace:^", + "@fedify/vocab": "workspace:^", + "@fedify/vocab-runtime": "workspace:^", "@js-temporal/polyfill": "catalog:", "@std/assert": "catalog:", "@std/async": "catalog:", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4986aaf0..24be96f09 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -225,6 +225,9 @@ importers: '@fedify/vocab-runtime': specifier: workspace:^ version: link:../packages/vocab-runtime + '@fedify/webfinger': + specifier: workspace:^ + version: link:../packages/webfinger '@hono/node-server': specifier: ^1.13.7 version: 1.14.4(hono@4.8.3) @@ -243,6 +246,12 @@ importers: '@opentelemetry/api': specifier: 'catalog:' version: 1.9.0 + '@opentelemetry/context-async-hooks': + specifier: 'catalog:' + version: 2.5.0(@opentelemetry/api@1.9.0) + '@opentelemetry/core': + specifier: 'catalog:' + version: 2.5.0(@opentelemetry/api@1.9.0) '@opentelemetry/exporter-trace-otlp-proto': specifier: 'catalog:' version: 0.211.0(@opentelemetry/api@1.9.0) @@ -995,6 +1004,9 @@ importers: specifier: 'catalog:' version: 5.1.0 devDependencies: + '@fedify/vocab': + specifier: workspace:^ + version: link:../vocab '@types/node': specifier: 'catalog:' version: 22.19.1 @@ -1392,6 +1404,9 @@ importers: specifier: ^1.31.0 version: 1.39.5 devDependencies: + '@fedify/fixture': + specifier: workspace:^ + version: link:../fixture '@fedify/testing': specifier: workspace:^ version: link:../testing @@ -1433,6 +1448,12 @@ importers: '@fedify/fixture': specifier: workspace:^ version: link:../fixture + '@fedify/vocab': + specifier: workspace:^ + version: link:../vocab + '@fedify/vocab-runtime': + specifier: workspace:^ + version: link:../vocab-runtime '@js-temporal/polyfill': specifier: 'catalog:' version: 0.5.1 diff --git a/scripts/test_pack.mjs b/scripts/test_pack.mjs new file mode 100644 index 000000000..c239a701f --- /dev/null +++ b/scripts/test_pack.mjs @@ -0,0 +1,118 @@ +import { deepStrictEqual, strictEqual } from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { + mkdirSync, + mkdtempSync, + readdirSync, + readFileSync, + readlinkSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import process from "node:process"; +import { gunzipSync } from "node:zlib"; + +// Use the repository's setting, but never let the negative control damage its +// installed dependencies. Run through mise; DENO_TEST_BINARY may select a +// different installed Deno without changing the pinned toolchain. +const config = JSON.parse( + readFileSync(new URL("../deno.json", import.meta.url), "utf8"), +); +const deno = process.env.DENO_TEST_BINARY ?? "deno"; +const root = mkdtempSync(join(tmpdir(), "fedify-pack-")); +function write(path, data) { + writeFileSync(join(root, path), data); +} +function json(path, value) { + write(path, JSON.stringify(value)); +} +function run(command, args, cwd = root) { + return execFileSync(command, args, { cwd, encoding: "utf8", stdio: "pipe" }); +} +function links() { + return [ + readlinkSync(join(root, "packages/adapter/node_modules/@fedify/pack-core")), + readlinkSync(join(root, "packages/adapter/node_modules/chalk")), + readdirSync(join(root, "node_modules")).sort(), + ]; +} +function packedManifest(path) { + const tar = gunzipSync(readFileSync(path)); + for (let offset = 0; offset < tar.length;) { + const name = tar.subarray(offset, offset + 100).toString().split("\0")[0]; + const size = parseInt( + tar.subarray(offset + 124, offset + 136).toString().replace(/\0/g, ""), + 8, + ); + if (!name) break; + if (name === "package/package.json") { + return JSON.parse(tar.subarray(offset + 512, offset + 512 + size)); + } + offset += 512 + Math.ceil(size / 512) * 512; + } + throw new Error("Packed package.json not found"); +} + +try { + mkdirSync(join(root, "packages/core"), { recursive: true }); + mkdirSync(join(root, "packages/adapter"), { recursive: true }); + json("package.json", { private: true }); + write("pnpm-workspace.yaml", "packages:\n - packages/*\n"); + json("deno.json", { + nodeModulesDir: config.nodeModulesDir, + workspace: ["./packages/core", "./packages/adapter"], + imports: { chalk: "npm:chalk@5.6.2" }, + }); + json("packages/core/deno.json", { + name: "@fedify/pack-core", + version: "1.2.3", + exports: "./mod.js", + }); + json("packages/core/package.json", { + name: "@fedify/pack-core", + version: "1.2.3", + type: "module", + exports: "./mod.js", + }); + write("packages/core/mod.js", "export const value = 42;\n"); + json("packages/adapter/deno.json", { exports: "./mod.js" }); + json("packages/adapter/package.json", { + name: "@fedify/pack-adapter", + version: "1.2.3", + type: "module", + peerDependencies: { "@fedify/pack-core": "workspace:^" }, + devDependencies: { chalk: "5.6.2" }, + scripts: { prepack: "node prepack.js" }, + }); + write( + "packages/adapter/mod.js", + 'import chalk from "chalk";\nimport { value } from "@fedify/pack-core";\nconsole.log(chalk.green(String(value)));\n', + ); + write( + "packages/adapter/prepack.js", + 'import "./mod.js";\nimport { writeFileSync } from "node:fs";\nwriteFileSync("lifecycle.txt", "prepack ran");\n', + ); + run("pnpm", ["install", "--ignore-scripts", "--no-frozen-lockfile"]); + const before = links(); + console.log(run(deno, ["--version"]).trim()); + run(deno, ["check", "packages/adapter/mod.js"]); + deepStrictEqual(links(), before, "Deno changed pnpm's node_modules"); + run( + "pnpm", + ["pack", "--pack-destination", root], + join(root, "packages/adapter"), + ); + strictEqual( + readFileSync(join(root, "packages/adapter/lifecycle.txt"), "utf8"), + "prepack ran", + ); + const manifest = packedManifest(join(root, "fedify-pack-adapter-1.2.3.tgz")); + strictEqual(manifest.peerDependencies["@fedify/pack-core"], "^1.2.3"); + console.log( + "Deno preserved pnpm links and lifecycle-enabled packing passed.", + ); +} finally { + rmSync(root, { recursive: true, force: true }); +}