diff --git a/CHANGES.md b/CHANGES.md index 1236c5b4d..84c547f89 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -57,6 +57,11 @@ To be released. [[#206], [#797], [#798], [#799], [#803], [#806], [#812], [#923] by ChanHaeng Lee] + - Fixed CommonJS distribution files that use Temporal so they no longer + require `@js-temporal/polyfill` at runtime. The CommonJS build now + bundles `temporal-polyfill`, while type declarations rely on the standard + `esnext.temporal` lib reference. [[#823], [#925]] + [FEP-8b32]: https://w3id.org/fep/8b32 [FEP-ef61]: https://w3id.org/fep/ef61 [Standard Schema]: https://standardschema.dev/ @@ -67,9 +72,11 @@ To be released. [#803]: https://github.com/fedify-dev/fedify/pull/803 [#806]: https://github.com/fedify-dev/fedify/pull/806 [#812]: https://github.com/fedify-dev/fedify/pull/812 +[#823]: https://github.com/fedify-dev/fedify/issues/823 [#827]: https://github.com/fedify-dev/fedify/issues/827 [#915]: https://github.com/fedify-dev/fedify/pull/915 [#923]: https://github.com/fedify-dev/fedify/pull/923 +[#925]: https://github.com/fedify-dev/fedify/pull/925 ### @fedify/vocab @@ -84,6 +91,11 @@ To be released. `FeatureAuthorization`, plus actor `featuredCollections` and `InteractionPolicy.canFeature` properties. [[#810], [#914]] + - Fixed the CommonJS vocabulary build so it no longer requires + `@js-temporal/polyfill` at runtime. The build now bundles + `temporal-polyfill`, while type declarations rely on the standard + `esnext.temporal` lib reference. [[#823], [#925]] + [FEP-7aa9]: https://w3id.org/fep/7aa9 [#810]: https://github.com/fedify-dev/fedify/issues/810 [#826]: https://github.com/fedify-dev/fedify/issues/826 @@ -111,6 +123,60 @@ To be released. [#912]: https://github.com/fedify-dev/fedify/issues/912 [#913]: https://github.com/fedify-dev/fedify/pull/913 +### @fedify/cli + + - Switched the CLI's Temporal runtime dependency from + `@js-temporal/polyfill` to `temporal-polyfill`. [[#823], [#925]] + +### @fedify/debugger + + - Fixed the CommonJS debugger build so it no longer requires + `@js-temporal/polyfill` at runtime. The build now bundles + `temporal-polyfill`, while type declarations rely on the standard + `esnext.temporal` lib reference. [[#823], [#925]] + +### @fedify/mysql + + - Fixed the CommonJS MySQL adapter build so it no longer requires + `@js-temporal/polyfill` at runtime. The build now bundles + `temporal-polyfill`, while type declarations rely on the standard + `esnext.temporal` lib reference. [[#823], [#925]] + +### @fedify/postgres + + - Fixed the CommonJS PostgreSQL adapter build so it no longer requires + `@js-temporal/polyfill` at runtime. The build now bundles + `temporal-polyfill`, while type declarations rely on the standard + `esnext.temporal` lib reference. [[#823], [#925]] + +### @fedify/redis + + - Fixed the CommonJS Redis adapter build so it no longer requires + `@js-temporal/polyfill` at runtime. The build now bundles + `temporal-polyfill`, while type declarations rely on the standard + `esnext.temporal` lib reference. [[#823], [#925]] + +### @fedify/relay + + - Fixed the CommonJS relay build so it no longer requires + `@js-temporal/polyfill` at runtime. The build now bundles + `temporal-polyfill`, while type declarations rely on the standard + `esnext.temporal` lib reference. [[#823], [#925]] + +### @fedify/sqlite + + - Fixed the CommonJS SQLite adapter build so it no longer requires + `@js-temporal/polyfill` at runtime. The build now bundles + `temporal-polyfill`, while type declarations rely on the standard + `esnext.temporal` lib reference. [[#823], [#925]] + +### @fedify/testing + + - Fixed the CommonJS testing utilities build so it no longer requires + `@js-temporal/polyfill` at runtime. The build now bundles + `temporal-polyfill`, while type declarations rely on the standard + `esnext.temporal` lib reference. [[#823], [#925]] + Version 2.3.1 ------------- diff --git a/deno.json b/deno.json index 4755d4c7f..c72225a36 100644 --- a/deno.json +++ b/deno.json @@ -84,6 +84,7 @@ "mysql2/promise": "npm:mysql2@^3.22.3/promise", "postgres": "npm:postgres@^3.4.7", "preact": "npm:preact@10.19.6", + "temporal-polyfill": "npm:temporal-polyfill@^1.0.1", "tsdown": "npm:tsdown@^0.22.0" }, "unstable": [ diff --git a/deno.lock b/deno.lock index 0f12de536..c47740704 100644 --- a/deno.lock +++ b/deno.lock @@ -169,6 +169,7 @@ "npm:smol-toml@^1.6.1": "1.6.1", "npm:srvx@~0.8.7": "0.8.16", "npm:structured-field-values@^2.0.4": "2.0.4", + "npm:temporal-polyfill@^1.0.1": "1.0.1", "npm:tsdown@0.22": "0.22.0_tsx@4.21.0", "npm:tsx@^4.21.0": "4.21.0", "npm:uri-template-router@1": "1.0.0", @@ -8474,6 +8475,19 @@ "streamx" ] }, + "temporal-polyfill@1.0.1": { + "integrity": "sha512-N2SoI9olnW7BUsU8RosDphZQl9s+WJ8O7PoJMFCr/e5/1rFkVI4GNOWaSeySG+UoP04foPYsnLWbJmbXOiShZg==", + "dependencies": [ + "temporal-spec", + "temporal-utils" + ] + }, + "temporal-spec@1.0.0": { + "integrity": "sha512-00Ahj1e1ifaERTMOIIGpOCdOo9IEk2m6GGSMedsn9a2SIsGLdOTbmME1Htv6IM82b6VHrzSUTIVc7YHy6hdhFQ==" + }, + "temporal-utils@1.0.1": { + "integrity": "sha512-HAixuesxFQIUaQk3ptX2jhfO/FsOkgVkDDMawvp6n/fkB1q6BKfs3lURw9I+pK/2e2e/q/vrLrxmeqauBoyGMQ==" + }, "terracotta@1.1.0_solid-js@1.9.12": { "integrity": "sha512-kfQciWUBUBgYkXu7gh3CK3FAJng/iqZslAaY08C+k1Hdx17aVEpcFFb/WPaysxAfcupNH3y53s/pc53xxZauww==", "dependencies": [ @@ -9497,6 +9511,7 @@ "npm:mysql2@^3.22.3", "npm:postgres@^3.4.7", "npm:preact@10.19.6", + "npm:temporal-polyfill@^1.0.1", "npm:tsdown@0.22" ], "members": { @@ -9640,7 +9655,6 @@ ], "packageJson": { "dependencies": [ - "npm:@js-temporal/polyfill@~0.5.1", "npm:@jsr/std__assert@0.226", "npm:@types/node@^24.2.1", "npm:devalue@^5.8.1", @@ -9727,7 +9741,8 @@ "dependencies": [ "npm:fast-check@^3.22.0", "npm:fetch-mock@^12.5.2", - "npm:jsonld@9" + "npm:jsonld@9", + "npm:temporal-polyfill@^1.0.1" ], "packageJson": { "dependencies": [ diff --git a/docs/package.json b/docs/package.json index 9fdd43087..45865af46 100644 --- a/docs/package.json +++ b/docs/package.json @@ -30,7 +30,6 @@ "@fontsource-variable/bricolage-grotesque": "^5.2.10", "@hackmd/markdown-it-task-lists": "^2.1.4", "@hono/node-server": "^1.13.7", - "@js-temporal/polyfill": "catalog:", "@logtape/file": "catalog:", "@logtape/logtape": "catalog:", "@lucide/vue": "^1.20.0", @@ -67,6 +66,7 @@ "postgres": "catalog:", "srvx": "^0.11.15", "stringify-entities": "^4.0.4", + "temporal-polyfill": "catalog:", "typescript": "catalog:", "vitepress": "^2.0.0-alpha.17", "vitepress-plugin-group-icons": "^1.7.5", diff --git a/docs/tutorial/astro-blog.md b/docs/tutorial/astro-blog.md index e99a1e88f..d83c560d8 100644 --- a/docs/tutorial/astro-blog.md +++ b/docs/tutorial/astro-blog.md @@ -1911,14 +1911,14 @@ as the HTML page URL since we'll share the path `/posts/{slug}` between Astro and Fedify via content negotiation. `content_hash` is a SHA-256 digest of the title and body, used to detect edits. -### Adding `@js-temporal/polyfill` +### Adding `temporal-polyfill` The `Article` object uses `Temporal.Instant` from the [TC39 Temporal proposal] polyfill. Although `@fedify/vocab` already depends on this package, you should list it directly in your project so the version stays under your control: ~~~~ sh -bun add @js-temporal/polyfill +bun add temporal-polyfill ~~~~ [TC39 Temporal proposal]: https://tc39.es/proposal-temporal/ @@ -1967,7 +1967,7 @@ import { Person, Undo, } from "@fedify/vocab"; -import { Temporal } from "@js-temporal/polyfill"; +import { Temporal } from "temporal-polyfill"; import { getLogger } from "@logtape/logtape"; import { addFollower, @@ -2114,7 +2114,7 @@ Create *src/lib/publish.ts*: // @noErrors import type { RequestContext } from "@fedify/fedify"; import { Article, Create, Delete, Update } from "@fedify/vocab"; -import { Temporal } from "@js-temporal/polyfill"; +import { Temporal } from "temporal-polyfill"; import { getCollection } from "astro:content"; import { BLOG_IDENTIFIER } from "../federation.ts"; import db from "./db.ts"; @@ -2519,7 +2519,7 @@ import { Undo, Update, } from "@fedify/vocab"; -import { Temporal } from "@js-temporal/polyfill"; +import { Temporal } from "temporal-polyfill"; import { getLogger } from "@logtape/logtape"; import { addComment, diff --git a/docs/tutorial/content-sharing.md b/docs/tutorial/content-sharing.md index eef00b083..e14700e33 100644 --- a/docs/tutorial/content-sharing.md +++ b/docs/tutorial/content-sharing.md @@ -3482,7 +3482,7 @@ import { type Recipient, Undo, } from "@fedify/vocab"; -import { Temporal } from "@js-temporal/polyfill"; +import { Temporal } from "temporal-polyfill"; // ... import { actorKeys, followers, posts, users } from "./db/schema"; ~~~~ @@ -3490,7 +3490,7 @@ import { actorKeys, followers, posts, users } from "./db/schema"; We also need the polyfill itself in *package.json*: ~~~~ sh -npm install @js-temporal/polyfill +npm install temporal-polyfill ~~~~ Node.js 22 does not yet ship `Temporal` natively, and Fedify @@ -6528,7 +6528,7 @@ import { Note, PUBLIC_COLLECTION, } from "@fedify/vocab"; -import { Temporal } from "@js-temporal/polyfill"; +import { Temporal } from "temporal-polyfill"; import { and, eq } from "drizzle-orm"; import { createError, diff --git a/docs/tutorial/microblog.md b/docs/tutorial/microblog.md index 85e7b9be7..6251a12ed 100644 --- a/docs/tutorial/microblog.md +++ b/docs/tutorial/microblog.md @@ -3137,16 +3137,16 @@ object. Before implementation, we need to install a necessary library. Because the [Temporal API] used by Fedify to represent time is not yet built into -Node.js, we need the *[@js-temporal/polyfill]* package to polyfill it: +Node.js, we need the *[temporal-polyfill]* package to polyfill it: ~~~~ sh -npm add @js-temporal/polyfill +npm add temporal-polyfill ~~~~ Open the *src/federation.ts* file and `import` the installed package: ~~~~ typescript [src/federation.ts] -import { Temporal } from "@js-temporal/polyfill"; +import { Temporal } from "temporal-polyfill"; ~~~~ Also `import` the `Post` type and the `PUBLIC_COLLECTION` constant provided by @@ -3259,7 +3259,7 @@ temporary domain name) in the Mastodon search box again: This time, our created post appears properly in the search results! [Temporal API]: https://tc39.es/proposal-temporal/docs/ -[@js-temporal/polyfill]: https://github.com/js-temporal/temporal-polyfill +[temporal-polyfill]: https://github.com/fullcalendar/temporal-polyfill ### Sending `Create(Note)` activity diff --git a/mise.toml b/mise.toml index 703bb6940..2d3760e05 100644 --- a/mise.toml +++ b/mise.toml @@ -268,7 +268,12 @@ run = "pnpm run --recursive --parallel --filter '!{docs}' --config.enable-pre-po [tasks.test] description = "Run the test suite across all environments (Deno, Node.js, Bun)" -depends = ["check", "test:deno", "test:node", "test:bun"] +depends = ["check", "test:deno", "test:node", "test:bun", "test:temporal-polyfill"] + +[tasks."test:temporal-polyfill"] +description = "Check Temporal polyfill packaging" +depends = ["build"] +run = "deno run --allow-read --allow-write --allow-run scripts/check_temporal_polyfill.ts" [tasks.test-each] description = "Run tests for a specific package across all environments" diff --git a/packages/cli/package.json b/packages/cli/package.json index 676ceb97d..4de15ea4d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -88,7 +88,6 @@ "@inquirer/prompts": "^7.8.4", "@jimp/core": "^1.6.1", "@jimp/wasm-webp": "^1.6.1", - "@js-temporal/polyfill": "catalog:", "@logtape/file": "catalog:", "@logtape/logtape": "catalog:", "@poppanator/http-constants": "^1.1.1", @@ -109,6 +108,7 @@ "shiki": "^1.6.4", "smol-toml": "^1.6.1", "srvx": "^0.8.7", + "temporal-polyfill": "catalog:", "valibot": "^1.4.0", "yaml": "^2.9.0" }, diff --git a/packages/cli/tsdown.config.ts b/packages/cli/tsdown.config.ts index 541aa5413..7c488f6ba 100644 --- a/packages/cli/tsdown.config.ts +++ b/packages/cli/tsdown.config.ts @@ -1,4 +1,5 @@ import { defineConfig } from "tsdown"; +import { temporalPolyfillIntro } from "../../scripts/tsdown/temporal.mts"; export default defineConfig({ entry: [ @@ -24,9 +25,7 @@ export default defineConfig({ }, }, outputOptions(outputOptions) { - outputOptions.intro = ` - import { Temporal } from "@js-temporal/polyfill"; - `; + outputOptions.intro = temporalPolyfillIntro; return outputOptions; }, }); diff --git a/packages/debugger/package.json b/packages/debugger/package.json index 684d91acf..cea565924 100644 --- a/packages/debugger/package.json +++ b/packages/debugger/package.json @@ -32,13 +32,13 @@ "@fedify/fedify": "workspace:^" }, "dependencies": { - "@js-temporal/polyfill": "catalog:", "@logtape/logtape": "catalog:", "@opentelemetry/api": "catalog:", "@opentelemetry/context-async-hooks": "catalog:", "@opentelemetry/core": "catalog:", "@opentelemetry/sdk-trace-base": "catalog:", - "hono": "catalog:" + "hono": "catalog:", + "temporal-polyfill": "catalog:" }, "devDependencies": { "tsdown": "catalog:", diff --git a/packages/debugger/tsdown.config.ts b/packages/debugger/tsdown.config.ts index 9010f25ad..35286a6e9 100644 --- a/packages/debugger/tsdown.config.ts +++ b/packages/debugger/tsdown.config.ts @@ -1,9 +1,35 @@ import { defineConfig } from "tsdown"; +import { + temporalPolyfillCjsBanner, + temporalPolyfillCjsDeps, + temporalPolyfillEsmBanner, + temporalPolyfillImportPlugin, +} from "../../scripts/tsdown/temporal.mts"; export default defineConfig({ entry: ["src/mod.tsx"], dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, - format: ["esm", "cjs"], + format: { + esm: { + banner: temporalPolyfillEsmBanner(), + }, + cjs: { + deps: temporalPolyfillCjsDeps({ + neverBundle: [ + "@fedify/fedify", + "@fedify/fedify/federation", + "@fedify/fedify/otel", + "@logtape/logtape", + /^@logtape\//, + /^@opentelemetry\//, + "hono", + /^hono\//, + ], + }), + plugins: [temporalPolyfillImportPlugin], + banner: temporalPolyfillCjsBanner(), + }, + }, platform: "node", outExtensions({ format }) { return { @@ -23,13 +49,4 @@ export default defineConfig({ /^hono\//, ], }, - banner({ format }) { - const js = format === "cjs" - ? `const { Temporal } = require("@js-temporal/polyfill");` - : `import { Temporal } from "@js-temporal/polyfill";`; - return { - js, - dts: `/// `, - }; - }, }); diff --git a/packages/fedify/package.json b/packages/fedify/package.json index 9549e3f68..c70e69a31 100644 --- a/packages/fedify/package.json +++ b/packages/fedify/package.json @@ -140,7 +140,6 @@ "@fedify/vocab": "workspace:*", "@fedify/vocab-runtime": "workspace:*", "@fedify/webfinger": "workspace:*", - "@js-temporal/polyfill": "^0.5.1", "@logtape/logtape": "catalog:", "@opentelemetry/api": "catalog:", "@opentelemetry/core": "catalog:", @@ -154,6 +153,7 @@ "json-canon": "^1.0.1", "jsonld": "^9.0.0", "structured-field-values": "^2.0.4", + "temporal-polyfill": "catalog:", "urlpattern-polyfill": "catalog:" }, "devDependencies": { diff --git a/packages/fedify/src/vocab/cjs.test.ts b/packages/fedify/src/vocab/cjs.test.ts index 59b819cbf..c942d8cac 100644 --- a/packages/fedify/src/vocab/cjs.test.ts +++ b/packages/fedify/src/vocab/cjs.test.ts @@ -10,3 +10,20 @@ test("CommonJS vocab entry exports Object", { strictEqual(typeof vocab.Object, "function"); strictEqual(typeof vocab.Update, "function"); }); + +test("CommonJS entries load Temporal-backed modules", { + ignore: "Deno" in globalThis, +}, () => { + const originalTemporal = globalThis.Temporal; + const require = createRequire(import.meta.url); + const fedify = require("../mod.cjs"); + const federation = require("../federation/mod.cjs"); + const sig = require("../sig/mod.cjs"); + const utils = require("../utils/mod.cjs"); + + strictEqual(typeof fedify.createFederation, "function"); + strictEqual(typeof federation.InProcessMessageQueue, "function"); + strictEqual(typeof sig.signRequest, "function"); + strictEqual(typeof utils.kvCache, "function"); + strictEqual(globalThis.Temporal, originalTemporal); +}); diff --git a/packages/fedify/tsdown.config.ts b/packages/fedify/tsdown.config.ts index 428ab9ff4..5f0a90776 100644 --- a/packages/fedify/tsdown.config.ts +++ b/packages/fedify/tsdown.config.ts @@ -1,6 +1,13 @@ import { glob } from "node:fs/promises"; import { join, sep } from "node:path"; import { defineConfig } from "tsdown"; +import { + temporalPolyfillCjsBanner, + temporalPolyfillCjsDeps, + temporalPolyfillEsmBanner, + temporalPolyfillImportPlugin, + temporalPolyfillIntro, +} from "../../scripts/tsdown/temporal.mts"; function isTestingHelperImporter(importer: string | undefined): boolean { const normalized = importer?.replaceAll(sep, "/"); @@ -21,24 +28,24 @@ export default [ "./src/vocab/mod.ts", ], dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, - format: ["esm", "cjs"], - platform: "neutral", - deps: { neverBundle: [/^node:/] }, - banner({ format }) { - const js = format === "cjs" - ? [ - `const { Temporal } = require("@js-temporal/polyfill");`, - `const { URLPattern } = require("urlpattern-polyfill");`, - ].join("\n") - : [ - `import { Temporal } from "@js-temporal/polyfill";`, + format: { + esm: { + banner: temporalPolyfillEsmBanner( `import { URLPattern } from "urlpattern-polyfill";`, - ].join("\n"); - return { - js, - dts: `/// `, - }; + ), + }, + cjs: { + deps: temporalPolyfillCjsDeps({ + neverBundle: [/^node:/], + }), + plugins: [temporalPolyfillImportPlugin], + banner: temporalPolyfillCjsBanner( + `const { URLPattern } = require("urlpattern-polyfill");`, + ), + }, }, + platform: "neutral", + deps: { neverBundle: [/^node:/] }, }), defineConfig({ entry: [ @@ -73,8 +80,7 @@ export default [ }, }, outputOptions: { - intro: ` - import { Temporal } from "@js-temporal/polyfill"; + intro: `${temporalPolyfillIntro} import { URLPattern } from "urlpattern-polyfill"; globalThis.addEventListener = () => {}; `, diff --git a/packages/mysql/package.json b/packages/mysql/package.json index 137c8ab74..12edad69c 100644 --- a/packages/mysql/package.json +++ b/packages/mysql/package.json @@ -66,9 +66,9 @@ "README.md" ], "dependencies": { - "@js-temporal/polyfill": "catalog:", "@logtape/logtape": "catalog:", - "es-toolkit": "catalog:" + "es-toolkit": "catalog:", + "temporal-polyfill": "catalog:" }, "peerDependencies": { "@fedify/fedify": "workspace:^", @@ -77,6 +77,7 @@ "devDependencies": { "@fedify/fixture": "workspace:^", "@fedify/testing": "workspace:^", + "@js-temporal/polyfill": "catalog:", "tsdown": "catalog:", "typescript": "catalog:" }, diff --git a/packages/mysql/tsdown.config.ts b/packages/mysql/tsdown.config.ts index 1dc5f83fb..2f59540f8 100644 --- a/packages/mysql/tsdown.config.ts +++ b/packages/mysql/tsdown.config.ts @@ -1,22 +1,28 @@ import { defineConfig } from "tsdown"; +import { + temporalPolyfillCjsBanner, + temporalPolyfillCjsDeps, + temporalPolyfillEsmBanner, + temporalPolyfillImportPlugin, +} from "../../scripts/tsdown/temporal.mts"; export default defineConfig({ entry: ["src/mod.ts", "src/kv.ts", "src/mq.ts"], dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, unbundle: true, - format: ["esm", "cjs"], + format: { + esm: { + banner: temporalPolyfillEsmBanner(), + }, + cjs: { + deps: temporalPolyfillCjsDeps(), + plugins: [temporalPolyfillImportPlugin], + banner: temporalPolyfillCjsBanner(), + }, + }, platform: "node", outExtensions({ format }) { if (format === "cjs") return { js: ".cjs", dts: ".d.cts" }; return { js: ".js", dts: ".d.ts" }; }, - banner({ format }) { - const js = format === "cjs" - ? `const { Temporal } = require("@js-temporal/polyfill");` - : `import { Temporal } from "@js-temporal/polyfill";`; - return { - js, - dts: `/// `, - }; - }, }); diff --git a/packages/postgres/package.json b/packages/postgres/package.json index 5114af06e..9506b8eb2 100644 --- a/packages/postgres/package.json +++ b/packages/postgres/package.json @@ -66,8 +66,8 @@ "README.md" ], "dependencies": { - "@js-temporal/polyfill": "catalog:", - "@logtape/logtape": "catalog:" + "@logtape/logtape": "catalog:", + "temporal-polyfill": "catalog:" }, "peerDependencies": { "@fedify/fedify": "workspace:^", @@ -76,6 +76,7 @@ "devDependencies": { "@fedify/fixture": "workspace:^", "@fedify/testing": "workspace:^", + "@js-temporal/polyfill": "catalog:", "@std/async": "catalog:", "tsdown": "catalog:", "typescript": "catalog:" diff --git a/packages/postgres/tsdown.config.ts b/packages/postgres/tsdown.config.ts index fe377e44b..7d8a8e4b6 100644 --- a/packages/postgres/tsdown.config.ts +++ b/packages/postgres/tsdown.config.ts @@ -1,10 +1,25 @@ import { defineConfig } from "tsdown"; +import { + temporalPolyfillCjsBanner, + temporalPolyfillCjsDeps, + temporalPolyfillEsmBanner, + temporalPolyfillImportPlugin, +} from "../../scripts/tsdown/temporal.mts"; export default defineConfig({ entry: ["src/mod.ts", "src/kv.ts", "src/mq.ts"], dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, unbundle: true, - format: ["esm", "cjs"], + format: { + esm: { + banner: temporalPolyfillEsmBanner(), + }, + cjs: { + deps: temporalPolyfillCjsDeps(), + plugins: [temporalPolyfillImportPlugin], + banner: temporalPolyfillCjsBanner(), + }, + }, platform: "node", outExtensions({ format }) { return { @@ -12,13 +27,4 @@ export default defineConfig({ dts: format === "cjs" ? ".d.cts" : ".d.ts", }; }, - banner({ format }) { - const js = format === "cjs" - ? `const { Temporal } = require("@js-temporal/polyfill");` - : `import { Temporal } from "@js-temporal/polyfill";`; - return { - js, - dts: `/// `, - }; - }, }); diff --git a/packages/redis/package.json b/packages/redis/package.json index f8da72e49..795ea1c8b 100644 --- a/packages/redis/package.json +++ b/packages/redis/package.json @@ -74,8 +74,8 @@ "package.json" ], "dependencies": { - "@js-temporal/polyfill": "catalog:", - "@logtape/logtape": "catalog:" + "@logtape/logtape": "catalog:", + "temporal-polyfill": "catalog:" }, "peerDependencies": { "@fedify/fedify": "workspace:^", @@ -85,6 +85,7 @@ "@std/async": "catalog:", "@fedify/fixture": "workspace:^", "@fedify/testing": "workspace:^", + "@js-temporal/polyfill": "catalog:", "@types/node": "catalog:", "tsdown": "catalog:", "typescript": "catalog:" diff --git a/packages/redis/tsdown.config.ts b/packages/redis/tsdown.config.ts index 0527386b5..291033dea 100644 --- a/packages/redis/tsdown.config.ts +++ b/packages/redis/tsdown.config.ts @@ -1,10 +1,25 @@ import { defineConfig } from "tsdown"; +import { + temporalPolyfillCjsBanner, + temporalPolyfillCjsDeps, + temporalPolyfillEsmBanner, + temporalPolyfillImportPlugin, +} from "../../scripts/tsdown/temporal.mts"; export default defineConfig({ entry: ["src/mod.ts", "src/codec.ts", "src/kv.ts", "src/mq.ts"], dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, unbundle: true, - format: ["esm", "cjs"], + format: { + esm: { + banner: temporalPolyfillEsmBanner(), + }, + cjs: { + deps: temporalPolyfillCjsDeps(), + plugins: [temporalPolyfillImportPlugin], + banner: temporalPolyfillCjsBanner(), + }, + }, platform: "node", outExtensions({ format }) { return { @@ -12,13 +27,4 @@ export default defineConfig({ dts: format === "cjs" ? ".d.cts" : ".d.ts", }; }, - banner({ format }) { - const js = format === "cjs" - ? `const { Temporal } = require("@js-temporal/polyfill");` - : `import { Temporal } from "@js-temporal/polyfill";`; - return { - js, - dts: `/// `, - }; - }, }); diff --git a/packages/relay/package.json b/packages/relay/package.json index 703200be6..8322d9b41 100644 --- a/packages/relay/package.json +++ b/packages/relay/package.json @@ -47,8 +47,8 @@ "dependencies": { "@fedify/fedify": "workspace:^", "@fedify/vocab": "workspace:*", - "@js-temporal/polyfill": "catalog:", - "@logtape/logtape": "catalog:" + "@logtape/logtape": "catalog:", + "temporal-polyfill": "catalog:" }, "devDependencies": { "@fedify/vocab-runtime": "workspace:^", diff --git a/packages/relay/tsdown.config.ts b/packages/relay/tsdown.config.ts index bdbaab38b..76fd38b99 100644 --- a/packages/relay/tsdown.config.ts +++ b/packages/relay/tsdown.config.ts @@ -1,12 +1,28 @@ import { glob } from "node:fs/promises"; import { sep } from "node:path"; import { defineConfig } from "tsdown"; +import { + temporalPolyfillCjsBanner, + temporalPolyfillCjsDeps, + temporalPolyfillEsmBanner, + temporalPolyfillImportPlugin, + temporalPolyfillIntro, +} from "../../scripts/tsdown/temporal.mts"; export default [ defineConfig({ entry: ["src/mod.ts"], dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, - format: ["esm", "cjs"], + format: { + esm: { + banner: temporalPolyfillEsmBanner(), + }, + cjs: { + deps: temporalPolyfillCjsDeps(), + plugins: [temporalPolyfillImportPlugin], + banner: temporalPolyfillCjsBanner(), + }, + }, platform: "node", outExtensions({ format }) { return { @@ -14,15 +30,6 @@ export default [ dts: format === "cjs" ? ".d.cts" : ".d.ts", }; }, - banner({ format }) { - const js = format === "cjs" - ? `const { Temporal } = require("@js-temporal/polyfill");` - : `import { Temporal } from "@js-temporal/polyfill";`; - return { - js, - dts: `/// `, - }; - }, }), defineConfig({ entry: (await Array.fromAsync(glob(`src/**/*.test.ts`))) @@ -35,8 +42,7 @@ export default [ }, deps: { neverBundle: [/^node:/] }, outputOptions: { - intro: ` - import { Temporal } from "@js-temporal/polyfill"; + intro: `${temporalPolyfillIntro} import { URLPattern } from "urlpattern-polyfill"; globalThis.addEventListener = () => {}; `, diff --git a/packages/sqlite/package.json b/packages/sqlite/package.json index 40afd2176..ff49d4e26 100644 --- a/packages/sqlite/package.json +++ b/packages/sqlite/package.json @@ -64,15 +64,16 @@ } }, "dependencies": { - "@js-temporal/polyfill": "catalog:", "@logtape/logtape": "catalog:", - "es-toolkit": "^1.46.1" + "es-toolkit": "^1.46.1", + "temporal-polyfill": "catalog:" }, "peerDependencies": { "@fedify/fedify": "workspace:^" }, "devDependencies": { "@fedify/testing": "workspace:^", + "@js-temporal/polyfill": "catalog:", "@std/async": "catalog:", "tsdown": "catalog:", "typescript": "catalog:" diff --git a/packages/sqlite/tsdown.config.ts b/packages/sqlite/tsdown.config.ts index 3dc353314..eb72ca332 100644 --- a/packages/sqlite/tsdown.config.ts +++ b/packages/sqlite/tsdown.config.ts @@ -1,4 +1,10 @@ import { defineConfig } from "tsdown"; +import { + temporalPolyfillCjsBanner, + temporalPolyfillCjsDeps, + temporalPolyfillEsmBanner, + temporalPolyfillImportPlugin, +} from "../../scripts/tsdown/temporal.mts"; export default defineConfig({ entry: [ @@ -10,7 +16,16 @@ export default defineConfig({ ], dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, unbundle: true, - format: ["esm", "cjs"], + format: { + esm: { + banner: temporalPolyfillEsmBanner(), + }, + cjs: { + deps: temporalPolyfillCjsDeps(), + plugins: [temporalPolyfillImportPlugin], + banner: temporalPolyfillCjsBanner(), + }, + }, platform: "node", outExtensions({ format }) { return { @@ -29,13 +44,4 @@ export default defineConfig({ defaultHandler(warning); }, }, - banner({ format }) { - const js = format === "cjs" - ? `const { Temporal } = require("@js-temporal/polyfill");` - : `import { Temporal } from "@js-temporal/polyfill";`; - return { - js, - dts: `/// `, - }; - }, }); diff --git a/packages/testing/package.json b/packages/testing/package.json index 39efce142..0a4f17a51 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -50,11 +50,11 @@ "@fedify/fedify": "workspace:^" }, "dependencies": { - "es-toolkit": "catalog:" + "es-toolkit": "catalog:", + "temporal-polyfill": "catalog:" }, "devDependencies": { "@fedify/fixture": "workspace:^", - "@js-temporal/polyfill": "catalog:", "@standard-schema/spec": "catalog:", "@std/assert": "catalog:", "@std/async": "catalog:", diff --git a/packages/testing/tsdown.config.ts b/packages/testing/tsdown.config.ts index c7b2a22c2..54a75d6f3 100644 --- a/packages/testing/tsdown.config.ts +++ b/packages/testing/tsdown.config.ts @@ -1,9 +1,32 @@ import { defineConfig } from "tsdown"; +import { + temporalPolyfillCjsBanner, + temporalPolyfillCjsDeps, + temporalPolyfillEsmBanner, + temporalPolyfillImportPlugin, +} from "../../scripts/tsdown/temporal.mts"; export default defineConfig({ entry: "src/mod.ts", dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, - format: ["esm", "cjs"], + format: { + esm: { + banner: temporalPolyfillEsmBanner(), + }, + cjs: { + deps: temporalPolyfillCjsDeps({ + neverBundle: [ + "@fedify/fedify", + "@fedify/fedify/federation", + "@fedify/fixture", + "@fedify/vocab", + "@fedify/vocab-runtime", + ], + }), + plugins: [temporalPolyfillImportPlugin], + banner: temporalPolyfillCjsBanner(), + }, + }, platform: "node", outExtensions({ format }) { return { @@ -15,17 +38,9 @@ export default defineConfig({ neverBundle: [ "@fedify/fedify", "@fedify/fedify/federation", + "@fedify/fixture", "@fedify/vocab", "@fedify/vocab-runtime", ], }, - banner({ format }) { - const js = format === "cjs" - ? `const { Temporal } = require("@js-temporal/polyfill");` - : `import { Temporal } from "@js-temporal/polyfill";`; - return { - js, - dts: `/// `, - }; - }, }); diff --git a/packages/vocab/deno.json b/packages/vocab/deno.json index 90cb3d676..9a19a59df 100644 --- a/packages/vocab/deno.json +++ b/packages/vocab/deno.json @@ -14,7 +14,8 @@ "imports": { "fast-check": "npm:fast-check@^3.22.0", "fetch-mock": "npm:fetch-mock@^12.5.2", - "jsonld": "npm:jsonld@^9.0.0" + "jsonld": "npm:jsonld@^9.0.0", + "temporal-polyfill": "npm:temporal-polyfill@^1.0.1" }, "exclude": [ "dist/", diff --git a/packages/vocab/package.json b/packages/vocab/package.json index b0bf7dd88..4af7b7f07 100644 --- a/packages/vocab/package.json +++ b/packages/vocab/package.json @@ -37,7 +37,6 @@ "dependencies": { "@fedify/vocab-tools": "workspace:*", "@fedify/webfinger": "workspace:*", - "@js-temporal/polyfill": "catalog:", "@multiformats/base-x": "catalog:", "@logtape/logtape": "catalog:", "@opentelemetry/api": "catalog:", @@ -45,7 +44,8 @@ "es-toolkit": "catalog:", "@fedify/vocab-runtime": "workspace:*", "jsonld": "^9.0.0", - "pkijs": "catalog:" + "pkijs": "catalog:", + "temporal-polyfill": "catalog:" }, "devDependencies": { "@fedify/fixture": "workspace:*", diff --git a/packages/vocab/src/cjs.test.ts b/packages/vocab/src/cjs.test.ts new file mode 100644 index 000000000..9cdc2b3f6 --- /dev/null +++ b/packages/vocab/src/cjs.test.ts @@ -0,0 +1,31 @@ +import { ok, strictEqual, throws } from "node:assert"; +import { createRequire } from "node:module"; +import { mockDocumentLoader, test } from "@fedify/fixture"; + +test("CommonJS entry bundles temporal-polyfill internally", { + ignore: "Deno" in globalThis, +}, async () => { + const originalTemporal = globalThis.Temporal; + + const require = createRequire(import.meta.url); + throws( + () => require("temporal-polyfill"), + (error: unknown) => { + if (error == null || typeof error !== "object") return false; + const { code } = error as { code?: unknown }; + return code === "ERR_PACKAGE_PATH_NOT_EXPORTED" || + code === "MODULE_NOT_FOUND"; + }, + ); + + const vocab = require("../dist/mod.cjs"); + const obj = await vocab.Object.fromJsonLd({ + "@context": "https://www.w3.org/ns/activitystreams", + type: "Object", + published: "2025-01-01T12:34:56Z", + }, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader }); + + ok(obj.published != null); + strictEqual(obj.published.toString(), "2025-01-01T12:34:56Z"); + strictEqual(globalThis.Temporal, originalTemporal); +}); diff --git a/packages/vocab/tsdown.config.ts b/packages/vocab/tsdown.config.ts index fbb9d469b..761499881 100644 --- a/packages/vocab/tsdown.config.ts +++ b/packages/vocab/tsdown.config.ts @@ -1,6 +1,13 @@ import { glob, mkdir, readFile, writeFile } from "node:fs/promises"; import { dirname, join, sep } from "node:path"; import { defineConfig } from "tsdown"; +import { + temporalPolyfillCjsBanner, + temporalPolyfillCjsDeps, + temporalPolyfillEsmBanner, + temporalPolyfillImportPlugin, + temporalPolyfillIntro, +} from "../../scripts/tsdown/temporal.mts"; async function copyFileSafely( source: string, @@ -16,17 +23,21 @@ export default [ "./src/mod.ts", ], dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } }, - format: ["esm", "cjs"], + format: { + esm: { + banner: temporalPolyfillEsmBanner(), + }, + cjs: { + deps: temporalPolyfillCjsDeps({ + neverBundle: [/^node:/], + }), + plugins: [temporalPolyfillImportPlugin], + banner: temporalPolyfillCjsBanner(), + }, + }, platform: "neutral", - deps: { neverBundle: [/^node:/] }, - banner({ format }) { - const js = format === "cjs" - ? `const { Temporal } = require("@js-temporal/polyfill");` - : `import { Temporal } from "@js-temporal/polyfill";`; - return { - js, - dts: `/// `, - }; + deps: { + neverBundle: [/^node:/], }, }), defineConfig({ @@ -49,8 +60,7 @@ export default [ }, }, outputOptions: { - intro: ` - import { Temporal } from "@js-temporal/polyfill"; + intro: `${temporalPolyfillIntro} globalThis.addEventListener = () => {}; `, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe81f5cef..acb0e15a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -174,6 +174,9 @@ catalogs: postgres: specifier: ^3.4.7 version: 3.4.7 + temporal-polyfill: + specifier: ^1.0.1 + version: 1.0.1 tsdown: specifier: ^0.22.0 version: 0.22.0 @@ -280,9 +283,6 @@ importers: '@hono/node-server': specifier: ^1.13.7 version: 1.14.4(hono@4.8.3) - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@logtape/file': specifier: 'catalog:' version: 2.2.0(@logtape/logtape@2.2.0) @@ -391,6 +391,9 @@ importers: stringify-entities: specifier: ^4.0.4 version: 4.0.4 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 typescript: specifier: 'catalog:' version: 6.0.3 @@ -983,9 +986,6 @@ importers: '@jimp/wasm-webp': specifier: ^1.6.1 version: 1.6.1 - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@logtape/file': specifier: 'catalog:' version: 2.2.0(@logtape/logtape@2.2.0) @@ -1061,6 +1061,9 @@ importers: srvx: specifier: ^0.8.7 version: 0.8.16 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 valibot: specifier: ^1.4.0 version: 1.4.0(typescript@6.0.3) @@ -1114,9 +1117,6 @@ importers: '@fedify/fedify': specifier: workspace:^ version: link:../fedify - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@logtape/logtape': specifier: 'catalog:' version: 2.2.0 @@ -1135,6 +1135,9 @@ importers: hono: specifier: 'catalog:' version: 4.8.3 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 devDependencies: tsdown: specifier: 'catalog:' @@ -1220,9 +1223,6 @@ importers: '@fedify/webfinger': specifier: workspace:* version: link:../webfinger - '@js-temporal/polyfill': - specifier: ^0.5.1 - version: 0.5.1 '@logtape/logtape': specifier: 'catalog:' version: 2.2.0 @@ -1262,6 +1262,9 @@ importers: structured-field-values: specifier: ^2.0.4 version: 2.0.4 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 urlpattern-polyfill: specifier: 'catalog:' version: 10.1.0 @@ -1470,9 +1473,6 @@ importers: '@fedify/fedify': specifier: workspace:^ version: link:../fedify - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@logtape/logtape': specifier: 'catalog:' version: 2.2.0 @@ -1482,6 +1482,9 @@ importers: mysql2: specifier: 'catalog:' version: 3.22.3(@types/node@24.3.0) + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 devDependencies: '@fedify/fixture': specifier: workspace:^ @@ -1489,6 +1492,9 @@ importers: '@fedify/testing': specifier: workspace:^ version: link:../testing + '@js-temporal/polyfill': + specifier: 'catalog:' + version: 0.5.1 tsdown: specifier: 'catalog:' version: 0.22.0(tsx@4.21.0)(typescript@6.0.3)(unrun@0.2.34(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)) @@ -1573,15 +1579,15 @@ importers: '@fedify/fedify': specifier: workspace:^ version: link:../fedify - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@logtape/logtape': specifier: 'catalog:' version: 2.2.0 postgres: specifier: 'catalog:' version: 3.4.7 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 devDependencies: '@fedify/fixture': specifier: workspace:^ @@ -1589,6 +1595,9 @@ importers: '@fedify/testing': specifier: workspace:^ version: link:../testing + '@js-temporal/polyfill': + specifier: 'catalog:' + version: 0.5.1 '@std/async': specifier: 'catalog:' version: '@jsr/std__async@1.0.13' @@ -1604,15 +1613,15 @@ importers: '@fedify/fedify': specifier: workspace:^ version: link:../fedify - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@logtape/logtape': specifier: 'catalog:' version: 2.2.0 ioredis: specifier: 'catalog:' version: 5.10.1 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 devDependencies: '@fedify/fixture': specifier: workspace:^ @@ -1620,6 +1629,9 @@ importers: '@fedify/testing': specifier: workspace:^ version: link:../testing + '@js-temporal/polyfill': + specifier: 'catalog:' + version: 0.5.1 '@std/async': specifier: 'catalog:' version: '@jsr/std__async@1.0.13' @@ -1641,12 +1653,12 @@ importers: '@fedify/vocab': specifier: workspace:* version: link:../vocab - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@logtape/logtape': specifier: 'catalog:' version: 2.2.0 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 devDependencies: '@fedify/vocab-runtime': specifier: workspace:^ @@ -1682,19 +1694,22 @@ importers: '@fedify/fedify': specifier: workspace:^ version: link:../fedify - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@logtape/logtape': specifier: 'catalog:' version: 2.2.0 es-toolkit: specifier: ^1.46.1 version: 1.46.1 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 devDependencies: '@fedify/testing': specifier: workspace:^ version: link:../testing + '@js-temporal/polyfill': + specifier: 'catalog:' + version: 0.5.1 '@std/async': specifier: 'catalog:' version: '@jsr/std__async@1.0.13' @@ -1729,13 +1744,13 @@ importers: es-toolkit: specifier: 'catalog:' version: 1.46.1 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 devDependencies: '@fedify/fixture': specifier: workspace:^ version: link:../fixture - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@standard-schema/spec': specifier: 'catalog:' version: 1.1.0 @@ -1778,9 +1793,6 @@ importers: '@fedify/webfinger': specifier: workspace:* version: link:../webfinger - '@js-temporal/polyfill': - specifier: 'catalog:' - version: 0.5.1 '@logtape/logtape': specifier: 'catalog:' version: 2.2.0 @@ -1802,6 +1814,9 @@ importers: pkijs: specifier: 'catalog:' version: 3.3.3 + temporal-polyfill: + specifier: 'catalog:' + version: 1.0.1 devDependencies: '@fedify/fixture': specifier: workspace:* @@ -11968,6 +11983,15 @@ packages: teex@1.0.1: resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + temporal-polyfill@1.0.1: + resolution: {integrity: sha512-N2SoI9olnW7BUsU8RosDphZQl9s+WJ8O7PoJMFCr/e5/1rFkVI4GNOWaSeySG+UoP04foPYsnLWbJmbXOiShZg==} + + temporal-spec@1.0.0: + resolution: {integrity: sha512-00Ahj1e1ifaERTMOIIGpOCdOo9IEk2m6GGSMedsn9a2SIsGLdOTbmME1Htv6IM82b6VHrzSUTIVc7YHy6hdhFQ==} + + temporal-utils@1.0.1: + resolution: {integrity: sha512-HAixuesxFQIUaQk3ptX2jhfO/FsOkgVkDDMawvp6n/fkB1q6BKfs3lURw9I+pK/2e2e/q/vrLrxmeqauBoyGMQ==} + terracotta@1.1.0: resolution: {integrity: sha512-kfQciWUBUBgYkXu7gh3CK3FAJng/iqZslAaY08C+k1Hdx17aVEpcFFb/WPaysxAfcupNH3y53s/pc53xxZauww==} engines: {node: '>=10'} @@ -24900,6 +24924,15 @@ snapshots: - bare-abort-controller - react-native-b4a + temporal-polyfill@1.0.1: + dependencies: + temporal-spec: 1.0.0 + temporal-utils: 1.0.1 + + temporal-spec@1.0.0: {} + + temporal-utils@1.0.1: {} + terracotta@1.1.0(solid-js@1.9.12): dependencies: solid-js: 1.9.12 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 267f7841e..e746f204e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -112,6 +112,7 @@ catalog: nuxt: ^4.4.2 oxlint: ^1.66.0 postgres: ^3.4.7 + temporal-polyfill: ^1.0.1 tsdown: ^0.22.0 typescript: ^6.0.0 urlpattern-polyfill: "^10.1.0" diff --git a/scripts/check_temporal_polyfill.ts b/scripts/check_temporal_polyfill.ts new file mode 100644 index 000000000..23be2e7bc --- /dev/null +++ b/scripts/check_temporal_polyfill.ts @@ -0,0 +1,287 @@ +// Regression check for Temporal packaging in published artifacts. +// +// The packages below publish files that use Temporal. Their CommonJS outputs +// must be self-contained because temporal-polyfill cannot be required directly +// from CJS consumers, and declaration files should expose only the standard +// esnext.temporal library reference rather than polyfill-specific imports. +// The final type-consumer checks make sure the generated declarations work +// under the TypeScript module resolution modes used by modern app templates. + +import { join } from "node:path"; + +const root = Deno.cwd(); + +const packages = [ + "cli", + "debugger", + "fedify", + "mysql", + "postgres", + "redis", + "relay", + "sqlite", + "testing", + "vocab", +] as const; + +// @fedify/cli is still scanned above, but it does not expose a library type +// surface for TypeScript consumers. +const typeConsumerPackages = packages.filter((pkg) => pkg !== "cli"); + +const forbiddenCjsRequires = [ + `require("@js-temporal/polyfill")`, + `require('@js-temporal/polyfill')`, + `require("temporal-polyfill")`, + `require('temporal-polyfill')`, +]; + +const forbiddenDeclarationText = [ + "@js-temporal/polyfill", + "temporal-polyfill", + "temporal-spec", +]; + +async function* walk(dir: string): AsyncGenerator { + try { + for await (const entry of Deno.readDir(dir)) { + const path = `${dir}/${entry.name}`; + if (entry.isDirectory) { + yield* walk(path); + } else if (entry.isFile) { + yield path; + } + } + } catch (error) { + if (error instanceof Deno.errors.NotFound) return; + throw error; + } +} + +let failures = 0; + +async function findTypeScriptCompiler(): Promise { + const candidates = [ + join(root, "node_modules", "typescript", "bin", "tsc"), + ]; + for (const store of [".deno", ".pnpm"]) { + const storeDir = join(root, "node_modules", store); + try { + for await (const entry of Deno.readDir(storeDir)) { + if (!entry.isDirectory || !entry.name.startsWith("typescript@")) { + continue; + } + candidates.push( + join( + storeDir, + entry.name, + "node_modules", + "typescript", + "bin", + "tsc", + ), + ); + } + } catch (error) { + if (!(error instanceof Deno.errors.NotFound)) throw error; + } + } + for (const candidate of candidates) { + try { + if ((await Deno.stat(candidate)).isFile) return candidate; + } catch (error) { + if (!(error instanceof Deno.errors.NotFound)) throw error; + } + } + throw new Error("Could not find the TypeScript compiler."); +} + +for (const pkg of packages) { + const dist = `packages/${pkg}/dist`; + const files = []; + for await (const file of walk(dist)) files.push(file); + if (files.length < 1) { + console.error(`Missing build output: ${dist}`); + failures++; + continue; + } + + for (const file of files) { + if (file.includes(".test.")) continue; + if ( + !file.endsWith(".js") && + !file.endsWith(".cjs") && + !file.endsWith(".d.ts") && + !file.endsWith(".d.cts") + ) { + continue; + } + + const text = await Deno.readTextFile(file); + if (file.endsWith(".cjs")) { + for (const forbidden of forbiddenCjsRequires) { + if (text.includes(forbidden)) { + console.error(`${file} contains forbidden ${forbidden}`); + failures++; + } + } + } else if (file.endsWith(".d.ts") || file.endsWith(".d.cts")) { + for (const forbidden of forbiddenDeclarationText) { + if (text.includes(forbidden)) { + console.error(`${file} exposes ${forbidden}`); + failures++; + } + } + if ( + /\bTemporal\b/.test(text) && + !text.includes(`/// `) + ) { + console.error(`${file} is missing the Temporal lib reference`); + failures++; + } + } + } +} + +async function prepareTypeConsumerProject( + name: string, + compilerOptions: Record, + sourceFile: string, +): Promise { + await Deno.mkdir(join(root, "tmp"), { recursive: true }); + const dir = await Deno.makeTempDir({ + dir: join(root, "tmp"), + prefix: `temporal-polyfill-${name}-`, + }); + await Deno.mkdir(join(dir, "node_modules", "@fedify"), { recursive: true }); + for (const pkg of typeConsumerPackages) { + // Symlink the package root so package.json exports resolve to the dist + // artifacts produced by the earlier build check. + await Deno.symlink( + join(root, "packages", pkg), + join(dir, "node_modules", "@fedify", pkg), + { type: Deno.build.os === "windows" ? "junction" : "dir" }, + ); + } + await Deno.writeTextFile( + join(dir, "package.json"), + `${JSON.stringify({ type: "module", private: true }, null, 2)}\n`, + ); + await Deno.writeTextFile( + join(dir, "tsconfig.json"), + `${ + JSON.stringify( + { + compilerOptions: { + target: "ES2022", + strict: true, + noEmit: true, + skipLibCheck: false, + lib: ["ESNext", "ESNext.Temporal", "DOM"], + types: ["node"], + ...compilerOptions, + }, + include: [sourceFile], + }, + null, + 2, + ) + }\n`, + ); + await Deno.writeTextFile( + join(dir, sourceFile), + ` + import { Object as ActivityObject } from "@fedify/vocab"; + + import type * as debuggerModule from "@fedify/debugger"; + import type * as fedify from "@fedify/fedify"; + import type * as mysql from "@fedify/mysql"; + import type * as postgres from "@fedify/postgres"; + import type * as redis from "@fedify/redis"; + import type * as relay from "@fedify/relay"; + import type * as sqlite from "@fedify/sqlite"; + import type * as testing from "@fedify/testing"; + import type * as vocab from "@fedify/vocab"; + + const instant: Temporal.Instant = Temporal.Instant.from( + "2025-01-01T00:00:00Z", + ); + const object = new ActivityObject({ published: instant }); + + const modules: [ + typeof debuggerModule | undefined, + typeof fedify | undefined, + typeof mysql | undefined, + typeof postgres | undefined, + typeof redis | undefined, + typeof relay | undefined, + typeof sqlite | undefined, + typeof testing | undefined, + typeof vocab | undefined, + ] = [ + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + ]; + + void object; + void modules; + `, + ); + return dir; +} + +async function checkTypeConsumerProject( + name: string, + compilerOptions: Record, + sourceFile: string, +): Promise { + const dir = await prepareTypeConsumerProject( + name, + compilerOptions, + sourceFile, + ); + try { + const command = new Deno.Command( + "node", + { + args: [ + await findTypeScriptCompiler(), + "-p", + "tsconfig.json", + ], + cwd: dir, + stdout: "piped", + stderr: "piped", + }, + ); + const result = await command.output(); + if (!result.success) { + const decoder = new TextDecoder(); + console.error(`TypeScript ${name} consumer check failed.`); + console.error(decoder.decode(result.stdout)); + console.error(decoder.decode(result.stderr)); + failures++; + } + } finally { + await Deno.remove(dir, { recursive: true }); + } +} + +await checkTypeConsumerProject( + "bundler", + { module: "ESNext", moduleResolution: "Bundler" }, + "index.ts", +); +await checkTypeConsumerProject( + "nodenext", + { module: "NodeNext", moduleResolution: "NodeNext" }, + "index.mts", +); + +if (failures > 0) Deno.exit(1); diff --git a/scripts/tsdown/temporal.mts b/scripts/tsdown/temporal.mts new file mode 100644 index 000000000..0b7654b51 --- /dev/null +++ b/scripts/tsdown/temporal.mts @@ -0,0 +1,74 @@ +import { sep } from "node:path"; +import type { DepsConfig, ResolvedConfig } from "tsdown"; + +// Shared tsdown helpers for packages that use Temporal in published output. +// +// ESM builds can import temporal-polyfill normally, but CommonJS builds cannot +// require temporal-polyfill directly because its package exports are ESM-first. +// For CJS, the build injects a local Temporal binding into source chunks and +// bundles temporal-polyfill and temporal-utils so consumers do not need either +// @js-temporal/polyfill or a global Temporal at runtime. + +export function isTemporalPolyfillDependency(id: string): boolean { + return /^temporal-polyfill(\/.*)?$/.test(id) || + /^temporal-utils(\/.*)?$/.test(id); +} + +export function temporalPolyfillCjsDeps( + deps: Omit = {}, +): ResolvedConfig["deps"] { + // tsdown's nested format type currently expects resolved deps, even though + // user config deps are accepted at runtime. + return ({ + ...deps, + alwaysBundle: isTemporalPolyfillDependency, + } as unknown) as ResolvedConfig["deps"]; +} + +export const temporalPolyfillImportPlugin = { + name: "fedify-temporal-polyfill-import", + transform(code: string, id: string) { + if (!id.replaceAll(sep, "/").includes("/src/")) return null; + if (!/\.[cm]?[jt]sx?$/.test(id)) return null; + if (!/\bTemporal\./.test(code)) return null; + if (/from\s+["']temporal-polyfill["']/.test(code)) return null; + return { + code: [ + `import { Temporal as __FedifyTemporal } from "temporal-polyfill";`, + `const Temporal: typeof globalThis.Temporal = __FedifyTemporal;`, + code, + ].join("\n"), + map: null, + }; + }, +}; + +export function temporalPolyfillEsmBanner(extraJs?: string): { + js: string; + dts: string; +} { + return { + js: [ + `import { Temporal } from "temporal-polyfill";`, + extraJs, + ].filter((line) => line != null && line !== "").join("\n"), + dts: `/// `, + }; +} + +export function temporalPolyfillCjsBanner(extraJs?: string): { + js?: string; + dts: string; +} { + return { + js: extraJs, + dts: `/// `, + }; +} + +// ESM-only intro for configs that emit test or CLI artifacts without CJS. +// CJS outputs must use temporalPolyfillCjsBanner() and +// temporalPolyfillImportPlugin instead. +export const temporalPolyfillIntro = ` + import { Temporal } from "temporal-polyfill"; + `;