diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 03154b7..6650eea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,6 +56,24 @@ jobs: - name: Build run: pnpm build + # Packaging breakage — an unresolvable subpath, a missing types target — + # is invisible to lint, typecheck, and the test suite, and only surfaces + # for a consumer after publish. attw resolves every entry point the way + # node10, node16, and bundlers would; publint checks the manifest itself. + # + # This step is the only thing that can verify the node10 rows: TypeScript 7 + # removed `moduleResolution: node10` (TS5108), so the repo's own compiler + # cannot express the scenario `typesVersions` exists to serve. Dropping + # this step would make that block unverifiable in-repo. + # + # `cjs-resolves-to-esm` is ignored because it is inherent to an ESM-only + # package, not a defect — and it fired before the `default` conditions + # existed, so reverting those would not retire the flag. Do not swap it for + # `--profile esm-only`: that profile suppresses whole resolution modes, + # including node10, and would report green on unresolvable subpath types. + - name: Check package publishing metadata + run: pnpm check:package + pandoc-integration: name: Pandoc Integration runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 917b334..84ab110 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ to the release entry by hand. This project follows - Add SharePoint transport - Add GoogleDriveTransport for uploading docx as Google Docs - Add docx-to-markdown reverse conversion (Tier-2) ([#16](https://github.com/agentic-tooling/polydoc-core/pull/16)) +- Split the public surface into subpath exports ([#13](https://github.com/agentic-tooling/polydoc-core/issues/13)) ### Miscellaneous @@ -36,6 +37,28 @@ to the release entry by hand. This project follows ### Notes +- The transports are subpath-only. `SharePointTransport` is imported from + `@agentic-tooling/polydoc-core/sharepoint` and `GoogleDriveTransport` from + `@agentic-tooling/polydoc-core/google`; neither is re-exported from the root + entry point, types included. The root entry point loads no third-party SDK — + Node builtins plus `execa` and `fflate` — so a Markdown-to-DOCX consumer never + pays for a cloud SDK it will not call. Both transport entry points re-export + the shared contract (`Transport`, `TransportUploadResult`, `TransportError`, + `TransportErrorCode`, `DOCX_MIME_TYPE`) as the same bindings the root entry + point exposes. +- `instanceof TransportError` holds across entry points under Node's resolver, + `require()` included, because every entry point resolves to one + `transport.js` module instance. That is a property of module identity, not of + the package layout: a bundler that emits the root and a transport entry point + into separate bundles with no shared chunk produces two distinct classes and + `instanceof` then evaluates false. `TransportError.code` is the + resolution-independent discriminant. If CJS output is ever added, `transport.js` + must remain a single shared artifact rather than shipping alongside a + `transport.cjs`, or this becomes the dual-package hazard. +- The package ships only the entry points listed above plus `./package.json`. + Subpath types are declared through both `exports` and `typesVersions`, so they + resolve under `moduleResolution: "node"` as well as `node16`/`nodenext` and + bundler resolution. `attw` and `publint` run in CI to keep that true. - A Pandoc 3.x binary must be on `PATH`. It is not bundled. `doctor()` probes for it and reports actionable failures. - `PandocErrorCode` and `TransportErrorCode` are open unions that grow as diff --git a/README.md b/README.md index e33b515..dd3f88a 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,58 @@ experience, remote service integrations, and sidecar storage stay outside this repository. The library does include the shared transport contract and a local file transport for consumers that need deterministic DOCX writes. +## Entry Points + +The package is ESM-only and publishes three entry points. Cloud transports are +opt-in: importing the root entry point never loads a cloud SDK. + +| Entry point | Contents | Third-party load | +| ------------------------------------------ | ------------------------------------------------------------------------------------- | ------------------------------------------- | +| `@agentic-tooling/polydoc-core` | Conversion core, the `Transport` contract, `TransportError`, and `LocalFileTransport` | `execa` and `fflate` only | +| `@agentic-tooling/polydoc-core/sharepoint` | `SharePointTransport` and its auth helpers | `@azure/msal-node` | +| `@agentic-tooling/polydoc-core/google` | `GoogleDriveTransport` and its Drive helpers | `@googleapis/drive`, loaded on first upload | + +A consumer doing pure Markdown-to-DOCX conversion should never pay to load a +SharePoint or Drive SDK it will not call, so the transports live behind their +own entry points rather than in the root barrel. The difference is measurable +and enforced: importing the root entry point loads a small fraction of the +modules it did when the cloud transports were part of the barrel, and the test +suite fails if the root entry point regains an SDK dependency — including a +type-only one, and including one behind a lazy `import()`. + +Both transport entry points re-export the shared contract — `Transport`, +`TransportUploadResult`, `TransportError`, `TransportErrorCode`, and +`DOCX_MIME_TYPE` — so code that imports one transport never has to reach back +into the root entry point for the error type it needs to catch. These are +re-exports of the same bindings rather than copies. Under Node's resolver every +entry point resolves to a single `transport.js` module instance, so +`instanceof TransportError` holds for a `SharePointTransportError` or +`GoogleDriveTransportError` no matter which entry point it was imported from — +`require()` included, on Node versions that can require ESM. + +That guarantee comes from module identity, and a build step can break it. If a +bundler emits the root entry point and a transport entry point into separate +bundles with no shared chunk — two independent Rollup builds, or a framework +that splits server and client graphs — a consumer ends up with two distinct +`TransportError` classes and `instanceof` silently evaluates false, letting the +error escape the handler. Where that is a possibility, branch on the error code, +which is stable and resolution-independent: + +```ts +try { + await transport.upload(canonicalId, docx); +} catch (error) { + if (error instanceof Error && "code" in error && error.code === "SHAREPOINT_AUTH_FAILED") { + // Handle it without depending on which module instance built the error. + } + + throw error; +} +``` + ## API -The package is ESM-only and exports the core Pandoc contract from -`@agentic-tooling/polydoc-core`. +The root entry point exports the core Pandoc contract. ```ts import { @@ -50,7 +98,10 @@ library. ## Transports Transports are side-effecting adapters that publish DOCX bytes for a canonical -document ID and return a stable destination handle for later consumers. +document ID and return a stable destination handle for later consumers. The +contract and `LocalFileTransport` live in the root entry point; the two cloud +transports are imported from `@agentic-tooling/polydoc-core/sharepoint` and +`@agentic-tooling/polydoc-core/google`. ```ts import { @@ -101,10 +152,8 @@ Microsoft Graph app-only auth. It implements the same create-or-update `Transport.upload(canonicalId, docx)` contract as `LocalFileTransport`. ```ts -import { - SharePointTransport, - convertMarkdownToDocx, -} from "@agentic-tooling/polydoc-core"; +import { convertMarkdownToDocx } from "@agentic-tooling/polydoc-core"; +import { SharePointTransport } from "@agentic-tooling/polydoc-core/sharepoint"; const docx = await convertMarkdownToDocx({ markdown, @@ -129,8 +178,10 @@ console.log(destination.webUrl); // optional Graph driveItem webUrl ``` Auth uses `@azure/msal-node` with the Microsoft Graph scope -`https://graph.microsoft.com/.default`. `Sites.Selected` is the required Entra -application permission to admin-consent on the app registration, and a separate +`https://graph.microsoft.com/.default`. The SDK is a static import here, which is +why this is a separate entry point: importing `@agentic-tooling/polydoc-core` +loads no part of it. `Sites.Selected` is the required Entra application +permission to admin-consent on the app registration, and a separate site-specific `write` grant must be provisioned out of band. The library does not request `Sites.Selected` as an OAuth scope, does not require tenant-wide `Sites.ReadWrite.All`, does not provision site grants, and does not @@ -206,11 +257,11 @@ contract as the other transports. ```ts import { OAuth2Client } from "google-auth-library"; +import { convertMarkdownToDocx } from "@agentic-tooling/polydoc-core"; import { GOOGLE_DRIVE_FILE_SCOPE, GoogleDriveTransport, - convertMarkdownToDocx, -} from "@agentic-tooling/polydoc-core"; +} from "@agentic-tooling/polydoc-core/google"; const docx = await convertMarkdownToDocx({ markdown, @@ -256,8 +307,9 @@ one, is your responsibility. Consumers that use a service account, workload identity federation, or their own token cache can pass any auth client the Drive v3 client accepts. -The `@googleapis/drive` SDK is imported lazily on the first upload, so consumers -that only use another transport never pay to load it. +The `@googleapis/drive` SDK is imported lazily on the first upload, so even +importing this entry point costs nothing until a document is published. +Consumers that only use another transport never import it at all. Uploads use the Drive v3 files resource: diff --git a/package.json b/package.json index 9959f91..afa4031 100644 --- a/package.json +++ b/package.json @@ -28,34 +28,59 @@ "exports": { ".": { "types": "./dist/index.d.ts", - "import": "./dist/index.js" - } + "import": "./dist/index.js", + "default": "./dist/index.js" + }, + "./google": { + "types": "./dist/google.d.ts", + "import": "./dist/google.js", + "default": "./dist/google.js" + }, + "./sharepoint": { + "types": "./dist/sharepoint.d.ts", + "import": "./dist/sharepoint.js", + "default": "./dist/sharepoint.js" + }, + "./package.json": "./package.json" }, "types": "./dist/index.d.ts", + "typesVersions": { + "*": { + "google": [ + "./dist/google.d.ts" + ], + "sharepoint": [ + "./dist/sharepoint.d.ts" + ] + } + }, "files": [ "dist", "README.md", "LICENSE" ], "scripts": { - "build": "tsc -p tsconfig.build.json", + "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json", "typecheck": "tsc -p tsconfig.json --noEmit", "test": "vitest run", "test:watch": "vitest", "lint": "biome check .", "format": "biome format --write .", "format:check": "biome format .", - "check": "biome check . && tsc -p tsconfig.json --noEmit && vitest run && tsc -p tsconfig.build.json", + "check:package": "pnpm build && attw --pack . --ignore-rules cjs-resolves-to-esm && publint --strict", + "check": "biome check . && tsc -p tsconfig.json --noEmit && vitest run && pnpm check:package", "changelog": "git cliff -o CHANGELOG.md", "changelog:preview": "git cliff --unreleased", "changelog:release": "git cliff --unreleased --prepend CHANGELOG.md", "prepack": "pnpm build" }, "devDependencies": { + "@arethetypeswrong/cli": "^0.18.5", "@biomejs/biome": "^2.5.5", "@types/node": "^20.19.43", "@vitest/coverage-v8": "^4.1.10", "google-auth-library": "10.5.0", + "publint": "^0.3.22", "typescript": "^7.0.2", "vitest": "^4.1.10" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a98f643..3fbb798 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,6 +21,9 @@ importers: specifier: 0.8.3 version: 0.8.3 devDependencies: + '@arethetypeswrong/cli': + specifier: ^0.18.5 + version: 0.18.5 '@biomejs/biome': specifier: ^2.5.5 version: 2.5.5 @@ -33,6 +36,9 @@ importers: google-auth-library: specifier: 10.5.0 version: 10.5.0 + publint: + specifier: ^0.3.22 + version: 0.3.22 typescript: specifier: ^7.0.2 version: 7.0.2 @@ -42,6 +48,18 @@ importers: packages: + '@andrewbranch/untar.js@1.0.3': + resolution: {integrity: sha512-Jh15/qVmrLGhkKJBdXlK1+9tY4lZruYjsgkDFj08ZmDiWVBLJcqkok7Z0/R0In+i1rScBpJlSvrTS2Lm41Pbnw==} + + '@arethetypeswrong/cli@0.18.5': + resolution: {integrity: sha512-gM+8vRsQOD/Uc7EnBedUhkG5OCsDWE4uoak5QvomGpMpaky0Eh41p04nIMgrWb8EOmqZUJGc6zz9hsP6E56R7g==} + engines: {node: '>=20'} + hasBin: true + + '@arethetypeswrong/core@0.18.5': + resolution: {integrity: sha512-9ytjzGwxjm9Uz7I9avfbt5vlQt6uk9uRRESzJjqrznl6WKvI6dwYTo+vJ3U02Wrq/mR3iql/PzhvHhKdJIAjDQ==} + engines: {node: '>=20'} + '@azure/msal-common@16.11.2': resolution: {integrity: sha512-yDhtBOGDCdK9ipQ9g3+wmlMEPnZx2pXaDicDd9jYyR1L+7lEbvEohTDmF5qejZDutZY3m9pWPxeYxzNC701A2w==} engines: {node: '>=0.8.0'} @@ -128,6 +146,13 @@ packages: cpu: [x64] os: [win32] + '@braidai/lang@1.1.2': + resolution: {integrity: sha512-qBcknbBufNHlui137Hft8xauQMTZDKdophmLFv05r2eNmdIv/MlPuP4TdUknHG68UdWLgVZwgxVe735HzJNIwA==} + + '@colors/colors@1.5.0': + resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} + engines: {node: '>=0.1.90'} + '@emnapi/core@1.11.1': resolution: {integrity: sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==} @@ -155,6 +180,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@loaderkit/resolve@1.0.6': + resolution: {integrity: sha512-G8FdIoF5CypfwmD9rl8BXod5HDn8JqB0CCNBXDTaRZ+yRYhARrrSToX1zg1zy9jX3zLqigsELwhT4gNtkdQAUg==} + '@napi-rs/wasm-runtime@1.1.6': resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} peerDependencies: @@ -168,6 +196,10 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} + '@publint/pack@0.1.6': + resolution: {integrity: sha512-3uVNyGcVplhPZSLVyeIpL7+cIRn1YCSNHLG/rUIlBQMVH8YuN9++YF+5+UDIIO9RW98dujiUoTltO7RDB5bFJA==} + engines: {node: '>=18'} + '@rolldown/binding-android-arm64@1.1.5': resolution: {integrity: sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -266,6 +298,10 @@ packages: '@rolldown/pluginutils@1.0.1': resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -446,6 +482,10 @@ packages: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -462,6 +502,9 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -496,6 +539,33 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + cli-highlight@2.1.11: + resolution: {integrity: sha512-9KDcoEVwyUXrjcJNvHD0NFc/hiwe/WPVYIleQh2O1N2Zro5gWJZ/K+3DGn8w8P/F6FxOgzyC5bxDyHIgCSPhGg==} + engines: {node: '>=8.0.0', npm: '>=5.0.0'} + hasBin: true + + cli-table3@0.6.5: + resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} + engines: {node: 10.* || >= 12.*} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -503,6 +573,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -543,6 +617,13 @@ packages: emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + emojilib@2.4.0: + resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -558,6 +639,10 @@ packages: resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -616,6 +701,10 @@ packages: resolution: {integrity: sha512-iJ9KMsiu+xKtNRX0PmGLSaIU3bUBAyzWTyqKemKPzNPsmmsBCQYmlNg+brEbES7IHSXtdVwzBPzx1vz3FAaipw==} engines: {node: '>=18'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -665,6 +754,9 @@ packages: resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} + highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -816,6 +908,10 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.2: + resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} + engines: {node: 20 || >=22} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -826,6 +922,17 @@ packages: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} + marked-terminal@7.3.0: + resolution: {integrity: sha512-t4rBvPsHc57uE/2nJOLmMbZCQ4tgAccAED3ngXQqW6g+TxA488JzJ+FK3lQkzBQOI1mRV/r/Kq+1ZlJ4D0owQw==} + engines: {node: '>=16.0.0'} + peerDependencies: + marked: '>=1 <16' + + marked@9.1.6: + resolution: {integrity: sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==} + engines: {node: '>= 16'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -845,9 +952,16 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nanoid@3.3.16: resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -858,6 +972,10 @@ packages: engines: {node: '>=10.5.0'} deprecated: Use your platform's native DOMException instead + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} + engines: {node: '>=18'} + node-fetch@3.3.2: resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -866,6 +984,10 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} @@ -881,6 +1003,18 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + package-manager-detector@1.8.0: + resolution: {integrity: sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A==} + + parse5-htmlparser2-tree-adapter@6.0.1: + resolution: {integrity: sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==} + + parse5@5.1.1: + resolution: {integrity: sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==} + + parse5@6.0.1: + resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} + path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} @@ -907,10 +1041,19 @@ packages: resolution: {integrity: sha512-KBDEIpLrvpv16pp3K0Fw+UCoZfopFjjgeB+0tA/aaThfEE74kKDLrgg603YvOWJyg3+WYtyq3xYsQWsIyZlPqQ==} engines: {node: ^10 || ^12 || >=14} + publint@0.3.22: + resolution: {integrity: sha512-6Z/scsr5CA7APdwyF35EY88CqgDj1textWuY788DVTJYPCWVv/Wn9G6KmLnrVRnStgYcahqN4wCDLZGSbQJ69w==} + engines: {node: '>=18'} + hasBin: true + qs@6.15.3: resolution: {integrity: sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==} engines: {node: '>=0.6'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + rimraf@5.0.10: resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} hasBin: true @@ -920,6 +1063,10 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -959,6 +1106,10 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + skin-tone@2.0.0: + resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} + engines: {node: '>=8'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -993,6 +1144,17 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -1011,6 +1173,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + typescript@5.6.1-rc: + resolution: {integrity: sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ==} + engines: {node: '>=14.17'} + hasBin: true + typescript@7.0.2: resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} engines: {node: '>=16.20.0'} @@ -1019,9 +1186,17 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + unicode-emoji-modifier-base@1.0.0: + resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} + engines: {node: '>=4'} + url-template@2.0.8: resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==} + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + vite@8.1.5: resolution: {integrity: sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1128,8 +1303,43 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs@16.2.2: + resolution: {integrity: sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==} + engines: {node: '>=10'} + snapshots: + '@andrewbranch/untar.js@1.0.3': {} + + '@arethetypeswrong/cli@0.18.5': + dependencies: + '@arethetypeswrong/core': 0.18.5 + chalk: 4.1.2 + cli-table3: 0.6.5 + commander: 10.0.1 + marked: 9.1.6 + marked-terminal: 7.3.0(marked@9.1.6) + semver: 7.8.5 + + '@arethetypeswrong/core@0.18.5': + dependencies: + '@andrewbranch/untar.js': 1.0.3 + '@loaderkit/resolve': 1.0.6 + cjs-module-lexer: 1.4.3 + fflate: 0.8.3 + lru-cache: 11.5.2 + semver: 7.8.5 + typescript: 5.6.1-rc + validate-npm-package-name: 5.0.1 + '@azure/msal-common@16.11.2': {} '@azure/msal-node@5.4.2': @@ -1187,6 +1397,11 @@ snapshots: '@biomejs/cli-win32-x64@2.5.5': optional: true + '@braidai/lang@1.1.2': {} + + '@colors/colors@1.5.0': + optional: true + '@emnapi/core@1.11.1': dependencies: '@emnapi/wasi-threads': 1.2.2 @@ -1227,6 +1442,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@loaderkit/resolve@1.0.6': + dependencies: + '@braidai/lang': 1.1.2 + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.11.1)(@emnapi/runtime@1.11.1)': dependencies: '@emnapi/core': 1.11.1 @@ -1239,6 +1458,10 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true + '@publint/pack@0.1.6': + dependencies: + tinyexec: 1.2.4 + '@rolldown/binding-android-arm64@1.1.5': optional: true @@ -1290,6 +1513,8 @@ snapshots: '@rolldown/pluginutils@1.0.1': {} + '@sindresorhus/is@4.6.0': {} + '@standard-schema/spec@1.1.0': {} '@tybys/wasm-util@0.10.3': @@ -1427,6 +1652,10 @@ snapshots: agent-base@7.1.4: {} + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + ansi-regex@5.0.1: {} ansi-regex@6.2.2: {} @@ -1437,6 +1666,8 @@ snapshots: ansi-styles@6.2.3: {} + any-promise@1.3.0: {} + assertion-error@2.0.1: {} ast-v8-to-istanbul@1.0.5: @@ -1469,12 +1700,46 @@ snapshots: chai@6.2.2: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chalk@5.6.2: {} + + char-regex@1.0.2: {} + + cjs-module-lexer@1.4.3: {} + + cli-highlight@2.1.11: + dependencies: + chalk: 4.1.2 + highlight.js: 10.7.3 + mz: 2.7.0 + parse5: 5.1.1 + parse5-htmlparser2-tree-adapter: 6.0.1 + yargs: 16.2.2 + + cli-table3@0.6.5: + dependencies: + string-width: 4.2.3 + optionalDependencies: + '@colors/colors': 1.5.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + commander@10.0.1: {} + convert-source-map@2.0.0: {} cross-spawn@7.0.6: @@ -1507,6 +1772,10 @@ snapshots: emoji-regex@9.2.2: {} + emojilib@2.4.0: {} + + environment@1.1.0: {} + es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -1517,6 +1786,8 @@ snapshots: dependencies: es-errors: 1.3.0 + escalade@3.2.0: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 @@ -1587,6 +1858,8 @@ snapshots: transitivePeerDependencies: - supports-color + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -1658,6 +1931,8 @@ snapshots: dependencies: function-bind: 1.1.2 + highlight.js@10.7.3: {} + html-escaper@2.0.2: {} https-proxy-agent@7.0.6: @@ -1789,6 +2064,8 @@ snapshots: lru-cache@10.4.3: {} + lru-cache@11.5.2: {} + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -1803,6 +2080,19 @@ snapshots: dependencies: semver: 7.8.5 + marked-terminal@7.3.0(marked@9.1.6): + dependencies: + ansi-escapes: 7.3.0 + ansi-regex: 6.2.2 + chalk: 5.6.2 + cli-highlight: 2.1.11 + cli-table3: 0.6.5 + marked: 9.1.6 + node-emoji: 2.2.0 + supports-hyperlinks: 3.2.0 + + marked@9.1.6: {} + math-intrinsics@1.1.0: {} merge-stream@2.0.0: {} @@ -1815,12 +2105,27 @@ snapshots: minipass@7.1.3: {} + mri@1.2.0: {} + ms@2.1.3: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + nanoid@3.3.16: {} node-domexception@1.0.0: {} + node-emoji@2.2.0: + dependencies: + '@sindresorhus/is': 4.6.0 + char-regex: 1.0.2 + emojilib: 2.4.0 + skin-tone: 2.0.0 + node-fetch@3.3.2: dependencies: data-uri-to-buffer: 4.0.1 @@ -1831,6 +2136,8 @@ snapshots: dependencies: path-key: 4.0.0 + object-assign@4.1.1: {} + object-inspect@1.13.4: {} obug@2.1.4: {} @@ -1841,6 +2148,16 @@ snapshots: package-json-from-dist@1.0.1: {} + package-manager-detector@1.8.0: {} + + parse5-htmlparser2-tree-adapter@6.0.1: + dependencies: + parse5: 6.0.1 + + parse5@5.1.1: {} + + parse5@6.0.1: {} + path-key@3.1.1: {} path-key@4.0.0: {} @@ -1862,11 +2179,20 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 + publint@0.3.22: + dependencies: + '@publint/pack': 0.1.6 + package-manager-detector: 1.8.0 + picocolors: 1.1.1 + sade: 1.8.1 + qs@6.15.3: dependencies: es-define-property: 1.0.1 side-channel: 1.1.1 + require-directory@2.1.1: {} + rimraf@5.0.10: dependencies: glob: 10.5.0 @@ -1892,6 +2218,10 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.1.5 '@rolldown/binding-win32-x64-msvc': 1.1.5 + sade@1.8.1: + dependencies: + mri: 1.2.0 + safe-buffer@5.2.1: {} semver@7.8.5: {} @@ -1934,6 +2264,10 @@ snapshots: signal-exit@4.1.0: {} + skin-tone@2.0.0: + dependencies: + unicode-emoji-modifier-base: 1.0.0 + source-map-js@1.2.1: {} stackback@0.0.2: {} @@ -1966,6 +2300,19 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-hyperlinks@3.2.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + tinybench@2.9.0: {} tinyexec@1.2.4: {} @@ -1980,6 +2327,8 @@ snapshots: tslib@2.8.1: optional: true + typescript@5.6.1-rc: {} + typescript@7.0.2: optionalDependencies: '@typescript/typescript-aix-ppc64': 7.0.2 @@ -2005,8 +2354,12 @@ snapshots: undici-types@6.21.0: {} + unicode-emoji-modifier-base@1.0.0: {} + url-template@2.0.8: {} + validate-npm-package-name@5.0.1: {} + vite@8.1.5(@types/node@20.19.43): dependencies: lightningcss: 1.33.0 @@ -2068,3 +2421,17 @@ snapshots: ansi-styles: 6.2.3 string-width: 5.1.2 strip-ansi: 7.2.0 + + y18n@5.0.8: {} + + yargs-parser@20.2.9: {} + + yargs@16.2.2: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 diff --git a/src/google.ts b/src/google.ts index c83e7cd..4c525b9 100644 --- a/src/google.ts +++ b/src/google.ts @@ -5,6 +5,17 @@ import type { BaseExternalAccountClient, GoogleAuth, OAuth2Client } from "google import type { Transport, TransportUploadResult } from "./transport.js"; import { DOCX_MIME_TYPE, TransportError, type TransportErrorCode } from "./transport.js"; +/** + * The shared transport contract is re-exported here so a consumer that only + * imports this entry point can type a `Transport` and catch a `TransportError` + * without also importing the root barrel. These are re-exports of the same + * bindings the barrel exposes, not copies: both entry points resolve to one + * `dist/transport.js` module instance, so `instanceof TransportError` holds + * across entry points. + */ +export type { Transport, TransportErrorCode, TransportUploadResult } from "./transport.js"; +export { DOCX_MIME_TYPE, TransportError } from "./transport.js"; + /** * Least-privilege Drive scope: the app may only see and manage files it created * itself. This library never requests a broader Drive scope. diff --git a/src/index.ts b/src/index.ts index 43a40d1..647208e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,20 @@ +/** + * Root entry point: conversion core, the transport contract, and the local file + * transport. + * + * This barrel deliberately pulls in no third-party SDK. Its whole dependency + * closure is Node builtins plus `execa` and `fflate`, so a consumer that only + * converts Markdown to DOCX never loads a cloud SDK. The cloud transports live + * behind their own entry points and are opt-in: + * + * - `@agentic-tooling/polydoc-core/sharepoint` — `SharePointTransport` + * - `@agentic-tooling/polydoc-core/google` — `GoogleDriveTransport` + * + * Both re-export the shared transport contract, so a consumer of one transport + * never has to reach back into this barrel for the types it needs. Adding a + * cloud transport export here would undo the split; `tests/entrypoints.test.ts` + * fails if the built barrel regains an SDK dependency. + */ export type { DocxImportReport, DocxTrackChangesMode, @@ -15,31 +32,6 @@ export { DOCX_MAX_TOTAL_BYTES, DOCX_TRACK_CHANGES_MODES, } from "./docx.js"; -export type { - GoogleDriveAuthClient, - GoogleDriveCreateFileParams, - GoogleDriveExistingFileIdResolver, - GoogleDriveFileMetadata, - GoogleDriveFileResponse, - GoogleDriveFilesClient, - GoogleDriveMediaBody, - GoogleDriveNameMapper, - GoogleDriveTransportDestination, - GoogleDriveTransportErrorCode, - GoogleDriveTransportErrorContext, - GoogleDriveTransportOptions, - GoogleDriveTransportOptionsBase, - GoogleDriveUpdateFileParams, -} from "./google.js"; -export { - defaultGoogleDocName, - GOOGLE_DOC_MIME_TYPE, - GOOGLE_DRIVE_DOCX_IMPORT_MAX_BYTES, - GOOGLE_DRIVE_FILE_SCOPE, - GoogleDriveTransport, - GoogleDriveTransportError, - validateGoogleDriveDocxSize, -} from "./google.js"; export type { ConvertDocxToMarkdownOptions, ConvertMarkdownToDocxOptions, @@ -69,31 +61,6 @@ export { PandocError, SUPPORTED_PANDOC_MAJOR, } from "./pandoc.js"; -export type { - SharePointAccessTokenProvider, - SharePointAccessTokenRequest, - SharePointClientSecretAccessTokenProviderOptions, - SharePointClientSecretCredentials, - SharePointConfidentialClient, - SharePointDestinationMapper, - SharePointTransportDestination, - SharePointTransportErrorCode, - SharePointTransportErrorContext, - SharePointTransportOptions, - SharePointTransportOptionsBase, -} from "./sharepoint.js"; -export { - createSharePointClientSecretAccessTokenProvider, - DOCX_MIME_TYPE, - encodeSharePointRelativePath, - MICROSOFT_GRAPH_DEFAULT_SCOPE, - MICROSOFT_GRAPH_V1_BASE_URL, - SHAREPOINT_REQUIRED_APPLICATION_PERMISSION, - SHAREPOINT_SIMPLE_UPLOAD_MAX_BYTES, - SharePointTransport, - SharePointTransportError, - validateSharePointDocxSize, -} from "./sharepoint.js"; export type { LocalFileDestinationMapper, LocalFileTransportDestination, @@ -102,4 +69,4 @@ export type { TransportErrorCode, TransportUploadResult, } from "./transport.js"; -export { LocalFileTransport, TransportError } from "./transport.js"; +export { DOCX_MIME_TYPE, LocalFileTransport, TransportError } from "./transport.js"; diff --git a/src/sharepoint.ts b/src/sharepoint.ts index 8200c5f..466f8f8 100644 --- a/src/sharepoint.ts +++ b/src/sharepoint.ts @@ -3,7 +3,16 @@ import { ConfidentialClientApplication } from "@azure/msal-node"; import type { Transport, TransportUploadResult } from "./transport.js"; import { DOCX_MIME_TYPE, TransportError, type TransportErrorCode } from "./transport.js"; -export { DOCX_MIME_TYPE }; +/** + * The shared transport contract is re-exported here so a consumer that only + * imports this entry point can type a `Transport` and catch a `TransportError` + * without also importing the root barrel. These are re-exports of the same + * bindings the barrel exposes, not copies: both entry points resolve to one + * `dist/transport.js` module instance, so `instanceof TransportError` holds + * across entry points. + */ +export type { Transport, TransportErrorCode, TransportUploadResult } from "./transport.js"; +export { DOCX_MIME_TYPE, TransportError } from "./transport.js"; export const MICROSOFT_GRAPH_DEFAULT_SCOPE = "https://graph.microsoft.com/.default"; export const SHAREPOINT_REQUIRED_APPLICATION_PERMISSION = "Sites.Selected"; diff --git a/tests/entrypoints.test.ts b/tests/entrypoints.test.ts new file mode 100644 index 0000000..53d64c9 --- /dev/null +++ b/tests/entrypoints.test.ts @@ -0,0 +1,357 @@ +import { execFile } from "node:child_process"; +import { access, readFile, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { promisify } from "node:util"; +import { beforeAll, describe, expect, it } from "vitest"; + +/** + * Entry-point guard. + * + * The package publishes three entry points and the root one is contractually + * SDK-free: node builtins plus `execa` and `fflate`, nothing else. A single + * re-export added back to `src/index.ts` would silently undo that, and neither + * `tsc` nor the behavioral suite would notice. These tests assert against the + * built output and the real module graph, so source-level shuffling cannot + * satisfy them. + */ + +const execFileAsync = promisify(execFile); + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const distDir = join(repoRoot, "dist"); +const supportDir = join(repoRoot, "tests", "support"); + +const PACKAGE_NAME = "@agentic-tooling/polydoc-core"; + +/** + * Every third-party package the root entry point may load. This list is the + * contract; widening it is a deliberate act, not an accident. + */ +const CORE_ALLOWED_PACKAGES = ["execa", "fflate"]; + +/** Cloud SDKs that must stay behind their own entry points. */ +const SDK_PACKAGE_PATTERN = /^(?:@azure\/|@googleapis\/|google-auth-library$|googleapis)/; + +/** + * Ceiling on how many modules the root entry point may load. The exact count is + * a moving target — it tracks `execa`'s own dependency closure — so this guards + * the order of magnitude the README describes rather than a specific number. + * Importing either cloud SDK blows straight through it. + */ +const CORE_MODULE_CEILING = 80; + +interface EntryPointProbe { + readonly specifier: string; + readonly elapsedMs: number; + readonly totalModules: number; + readonly builtinModules: number; + readonly thirdPartyModules: number; + readonly packages: readonly string[]; + readonly exports: readonly string[]; +} + +type ExportConditions = Readonly>; + +interface PackageManifest { + readonly name: string; + readonly files: readonly string[]; + readonly exports: Readonly>; + readonly typesVersions?: Readonly>>>; +} + +let manifest: PackageManifest; + +beforeAll(async () => { + manifest = JSON.parse(await readFile(join(repoRoot, "package.json"), "utf8")) as PackageManifest; + + // The assertions below only mean something against real build output, so + // build it here rather than trusting whatever dist/ happens to hold. tsc never + // removes stale output, so a renamed source file would otherwise leave a + // resolvable target behind locally and publish a broken one from CI's fresh + // checkout. The compiler entry script is invoked directly instead of the .bin + // shim so this does not depend on shell semantics. + await rm(distDir, { recursive: true, force: true }); + await execFileAsync( + process.execPath, + [join(repoRoot, "node_modules", "typescript", "bin", "tsc"), "-p", "tsconfig.build.json"], + { cwd: repoRoot }, + ); +}, 60_000); + +describe("package entry points", () => { + it("publishes the cloud transports behind their own subpaths", () => { + expect(Object.keys(manifest.exports)).toEqual( + expect.arrayContaining([".", "./google", "./sharepoint"]), + ); + }); + + it("gives every entry point a types target and a resolvable JS target", () => { + for (const [subpath, conditions] of libraryEntryPoints()) { + const conditionNames = Object.keys(conditions); + + // Resolution order, not cosmetics: `types` has to win before any runtime + // condition, and `default` has to be the last fallback so a resolver that + // matches none of the earlier conditions still lands on a file. + expect(conditionNames.at(0), `exports["${subpath}"] first condition`).toBe("types"); + expect(conditionNames.at(-1), `exports["${subpath}"] last condition`).toBe("default"); + + expect(conditions.types, `exports["${subpath}"].types`).toMatch(/^\.\/dist\/.+\.d\.ts$/); + + const runtimeTargets = conditionNames + .filter((name) => name !== "types") + .map((name) => conditions[name]); + + expect(runtimeTargets.length, `exports["${subpath}"] runtime targets`).toBeGreaterThan(0); + + for (const target of runtimeTargets) { + expect(target, `exports["${subpath}"] runtime target`).toMatch(/^\.\/dist\/.+\.js$/); + } + } + }); + + it("keeps subpath types resolvable under node10 module resolution", () => { + // `exports` is invisible to `moduleResolution: "node"`, which is still the + // default for plain tsc and for older toolchains. Without typesVersions a + // consumer there gets TS2307 on a subpath that their bundler resolves fine, + // and the diagnostic points at their config rather than at this package. + const typesVersions = manifest.typesVersions?.["*"] ?? {}; + + for (const [subpath, conditions] of libraryEntryPoints()) { + if (subpath === ".") { + continue; + } + + expect(typesVersions[subpath.replace(/^\.\//, "")], `typesVersions for ${subpath}`).toEqual([ + conditions.types, + ]); + } + }); + + it("ships every file the exports map resolves to", async () => { + expect(manifest.files).toContain("dist"); + + for (const [subpath, target] of Object.entries(manifest.exports)) { + const targets = typeof target === "string" ? [target] : Object.values(target); + + for (const resolved of targets) { + expect(resolved, `exports["${subpath}"] target`).toBeDefined(); + await expect( + access(join(repoRoot, String(resolved))), + `${String(resolved)} is missing from the packed output`, + ).resolves.toBeUndefined(); + } + } + }); +}); + +describe("root entry point isolation", () => { + it("reaches no third-party package other than execa and fflate in the built graph", async () => { + const specifiers = await reachableBareSpecifiers(join(distDir, "index.js")); + + expect([...specifiers].sort()).toEqual([...CORE_ALLOWED_PACKAGES].sort()); + }); + + it("keeps SDK types out of the root declaration graph as well", async () => { + // The runtime walk above cannot see `export type { … } from "./google.js"`, + // because a type-only re-export is erased from the emitted JS. It survives + // in index.d.ts, and the contract covers types too: a consumer of the root + // entry point should not need the SDK's types installed to typecheck. + // Declarations reference their siblings by `.js` specifier under NodeNext, + // so this walk lands in the same built graph and has the same closure. + const specifiers = await reachableBareSpecifiers(join(distDir, "index.d.ts")); + + expect([...specifiers].sort()).toEqual([...CORE_ALLOWED_PACKAGES].sort()); + }); + + it("loads no cloud SDK when imported through the exports map", async () => { + const probe = await probeEntryPoint(PACKAGE_NAME); + + expect(probe.packages.filter((name) => SDK_PACKAGE_PATTERN.test(name))).toEqual([]); + // The allowed dependencies really are loaded, so the assertion above is + // measuring a populated module graph rather than an empty one. + expect(probe.packages).toEqual(expect.arrayContaining(CORE_ALLOWED_PACKAGES)); + expect(probe.totalModules).toBeLessThanOrEqual(CORE_MODULE_CEILING); + }, 30_000); + + it("exports the conversion core and the transport contract, and no cloud transport", async () => { + const probe = await probeEntryPoint(PACKAGE_NAME); + + expect(probe.exports).toEqual( + expect.arrayContaining([ + "convertMarkdownToDocx", + "convertDocxToMarkdown", + "doctor", + "DOCX_MIME_TYPE", + "LocalFileTransport", + "TransportError", + ]), + ); + expect(probe.exports).not.toContain("GoogleDriveTransport"); + expect(probe.exports).not.toContain("SharePointTransport"); + expect(probe.exports).not.toContain("GoogleDriveTransportError"); + expect(probe.exports).not.toContain("SharePointTransportError"); + }, 30_000); +}); + +describe("transport entry points", () => { + it("resolves the google entry point and keeps the Drive SDK lazy", async () => { + const probe = await probeEntryPoint(`${PACKAGE_NAME}/google`); + + expect(probe.exports).toEqual( + expect.arrayContaining([ + "GoogleDriveTransport", + "GoogleDriveTransportError", + "DOCX_MIME_TYPE", + "TransportError", + ]), + ); + // @googleapis/drive is imported inside upload(), so importing the entry + // point alone must still load no third-party module at all — while the + // built graph does reach the SDK, which is why it lives behind this entry + // point and not in the barrel. The module count keeps the empty-package + // assertion from passing on a probe that recorded nothing at all. + expect(probe.totalModules).toBeGreaterThan(0); + expect(probe.packages).toEqual([]); + expect([...(await reachableBareSpecifiers(join(distDir, "google.js")))]).toContain( + "@googleapis/drive", + ); + }, 30_000); + + it("resolves the sharepoint entry point and owns the MSAL dependency", async () => { + const probe = await probeEntryPoint(`${PACKAGE_NAME}/sharepoint`); + + expect(probe.exports).toEqual( + expect.arrayContaining([ + "SharePointTransport", + "SharePointTransportError", + "DOCX_MIME_TYPE", + "TransportError", + ]), + ); + expect(probe.packages).toContain("@azure/msal-node"); + }, 30_000); + + it("re-exports the shared transport contract as one binding, not a copy", async () => { + const { stdout } = await execFileAsync( + process.execPath, + [join(supportDir, "shared-bindings-probe.mjs")], + { cwd: repoRoot }, + ); + + expect(JSON.parse(stdout)).toEqual({ + googleReExportsSameTransportError: true, + sharePointReExportsSameTransportError: true, + googleReExportsSameDocxMimeType: true, + sharePointReExportsSameDocxMimeType: true, + googleErrorIsCoreTransportError: true, + sharePointErrorIsCoreTransportError: true, + }); + }, 30_000); +}); + +/** + * Entry points declared with a condition object, which is every entry point + * except the bare `"./package.json": "./package.json"` passthrough. + */ +function libraryEntryPoints(): [string, ExportConditions][] { + return Object.entries(manifest.exports).flatMap(([subpath, target]) => + typeof target === "string" ? [] : [[subpath, target] satisfies [string, ExportConditions]], + ); +} + +const probeCache = new Map>(); + +function probeEntryPoint(specifier: string): Promise { + const cached = probeCache.get(specifier); + + if (cached !== undefined) { + return cached; + } + + const probe = runEntryPointProbe(specifier); + probeCache.set(specifier, probe); + + return probe; +} + +async function runEntryPointProbe(specifier: string): Promise { + const recordPath = join( + tmpdir(), + `polydoc-core-module-loads-${process.pid}-${Math.random().toString(36).slice(2)}.txt`, + ); + const { stdout } = await execFileAsync( + process.execPath, + [join(supportDir, "entrypoint-probe.mjs"), specifier, recordPath], + { cwd: repoRoot }, + ); + + return JSON.parse(stdout) as EntryPointProbe; +} + +/** + * Walks the built module graph from one entry file and returns every bare + * specifier it can reach, static or dynamic. A lazily imported SDK counts: + * deferring the cost of a dependency does not remove the dependency. + */ +async function reachableBareSpecifiers(entryFile: string): Promise> { + const visited = new Set(); + const bareSpecifiers = new Set(); + const pending = [entryFile]; + + while (pending.length > 0) { + const file = pending.pop(); + + if (file === undefined || visited.has(file)) { + continue; + } + + visited.add(file); + + for (const specifier of moduleSpecifiers(await readFile(file, "utf8"))) { + if (specifier.startsWith("node:")) { + continue; + } + + if (specifier.startsWith(".")) { + pending.push(resolve(dirname(file), specifier)); + continue; + } + + bareSpecifiers.add(specifier); + } + } + + return bareSpecifiers; +} + +/** + * Recovers module specifiers from emitted output by pattern, which assumes what + * this build actually produces: tsc-shaped output, top-level import and export + * statements at column zero, and biome's double-quote style. + * + * It does not see template-literal dynamic imports, single-quoted specifiers, or + * `createRequire(import.meta.url)("…")`. That last one is the plausible escape + * hatch — CJS-only SDK interop — and it is invisible to the runtime probe too, + * since nothing is loaded at import time either way. The tradeoff is accepted + * because the caller asserts exact equality against the allowlist: a walker that + * stops matching anything fails loudly rather than passing silently, so only a + * deliberately evasive import shape slips through, and that is a code review + * problem rather than a test problem. + */ +function moduleSpecifiers(source: string): string[] { + const patterns = [ + // `import ... from "x";` / `export ... from "x";`, including the multi-line + // specifier lists tsc emits for a barrel. + /^(?:import|export)\b[\s\S]*?from\s*"([^"]+)";/gm, + // Bare side-effect import. + /^import\s*"([^"]+)";/gm, + // Dynamic import, which is how the Drive SDK is loaded. + /\bimport\(\s*"([^"]+)"\s*\)/g, + ]; + + return patterns.flatMap((pattern) => + [...source.matchAll(pattern)].flatMap((match) => (match[1] === undefined ? [] : [match[1]])), + ); +} diff --git a/tests/index.test.ts b/tests/index.test.ts index 5557521..a89f554 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -6,6 +6,20 @@ import { strFromU8, strToU8, unzipSync, zipSync } from "fflate"; import { OAuth2Client } from "google-auth-library"; import { afterEach, describe, expect, it, vi } from "vitest"; +import { + defaultGoogleDocName, + GOOGLE_DOC_MIME_TYPE, + GOOGLE_DRIVE_DOCX_IMPORT_MAX_BYTES, + GOOGLE_DRIVE_FILE_SCOPE, + type GoogleDriveCreateFileParams, + type GoogleDriveFileMetadata, + type GoogleDriveFileResponse, + type GoogleDriveFilesClient, + GoogleDriveTransport, + GoogleDriveTransportError, + type GoogleDriveTransportOptions, + type GoogleDriveUpdateFileParams, +} from "../src/google.js"; import { applyMarkdownPostprocessors, applyMarkdownPreprocessors, @@ -13,7 +27,6 @@ import { type ConvertMarkdownToDocxOptions, convertDocxToMarkdown, convertMarkdownToDocx, - createSharePointClientSecretAccessTokenProvider, DEFAULT_DOCX_TRACK_CHANGES, DEFAULT_SOURCE_DATE_EPOCH, DOCX_MAX_ARCHIVE_ENTRIES, @@ -25,25 +38,17 @@ import { type DocxImportReport, type DocxUnmappableItem, type DocxUnmappableKind, - defaultGoogleDocName, doctor, - encodeSharePointRelativePath, - GOOGLE_DOC_MIME_TYPE, - GOOGLE_DRIVE_DOCX_IMPORT_MAX_BYTES, - GOOGLE_DRIVE_FILE_SCOPE, - type GoogleDriveCreateFileParams, - type GoogleDriveFileMetadata, - type GoogleDriveFileResponse, - type GoogleDriveFilesClient, - GoogleDriveTransport, - GoogleDriveTransportError, - type GoogleDriveTransportOptions, - type GoogleDriveUpdateFileParams, LocalFileTransport, - MICROSOFT_GRAPH_DEFAULT_SCOPE, PandocError, type PandocRunner, type PandocRunnerOptions, + SUPPORTED_PANDOC_MAJOR, +} from "../src/index.js"; +import { + createSharePointClientSecretAccessTokenProvider, + encodeSharePointRelativePath, + MICROSOFT_GRAPH_DEFAULT_SCOPE, SHAREPOINT_REQUIRED_APPLICATION_PERMISSION, SHAREPOINT_SIMPLE_UPLOAD_MAX_BYTES, type SharePointAccessTokenProvider, @@ -51,9 +56,8 @@ import { SharePointTransport, SharePointTransportError, type SharePointTransportOptions, - SUPPORTED_PANDOC_MAJOR, validateSharePointDocxSize, -} from "../src/index.js"; +} from "../src/sharepoint.js"; // The only module mock in this suite. GoogleDriveTransport imports // @googleapis/drive lazily and memoizes the client it builds, so that path is diff --git a/tests/support/entrypoint-probe.mjs b/tests/support/entrypoint-probe.mjs new file mode 100644 index 0000000..0eaeff9 --- /dev/null +++ b/tests/support/entrypoint-probe.mjs @@ -0,0 +1,59 @@ +/** + * Imports one published entry point in a fresh process and reports what Node + * actually loaded. + * + * node tests/support/entrypoint-probe.mjs + * + * The specifier is the public one — `@agentic-tooling/polydoc-core/google`, not + * a guessed `dist/` path — so the probe exercises the package.json `exports` + * map through Node's own resolver by self-reference. A typo in an export key or + * target fails here the same way it would fail for a consumer after publish. + */ +import { readFileSync, rmSync, writeFileSync } from "node:fs"; +import { register } from "node:module"; + +const [specifier, recordPath] = process.argv.slice(2); + +if (specifier === undefined || recordPath === undefined) { + throw new Error("Usage: entrypoint-probe.mjs "); +} + +writeFileSync(recordPath, ""); +register("./record-module-loads.mjs", import.meta.url, { data: { recordPath } }); + +let namespace; +let elapsedMs; +let urls; + +try { + const startedAt = performance.now(); + namespace = await import(specifier); + elapsedMs = performance.now() - startedAt; + urls = readFileSync(recordPath, "utf8").split("\n").filter(Boolean); +} finally { + // A failed import still has to clean up after itself, or a resolution error + // silently litters the temp directory on every run. + rmSync(recordPath, { force: true }); +} + +const thirdPartyUrls = urls.filter((url) => url.includes("/node_modules/")); +const packages = new Set(thirdPartyUrls.map(packageNameFromUrl).filter((name) => name !== "")); + +process.stdout.write( + JSON.stringify({ + specifier, + elapsedMs: Number(elapsedMs.toFixed(1)), + totalModules: urls.length, + builtinModules: urls.filter((url) => url.startsWith("node:")).length, + thirdPartyModules: thirdPartyUrls.length, + packages: [...packages].sort(), + exports: Object.keys(namespace).sort(), + }), +); + +function packageNameFromUrl(url) { + const afterLastNodeModules = url.split("/node_modules/").pop() ?? ""; + const [first = "", second = ""] = afterLastNodeModules.split("/"); + + return first.startsWith("@") ? `${first}/${second}` : first; +} diff --git a/tests/support/record-module-loads.mjs b/tests/support/record-module-loads.mjs new file mode 100644 index 0000000..f18abfc --- /dev/null +++ b/tests/support/record-module-loads.mjs @@ -0,0 +1,19 @@ +/** + * Module customization hooks that record every module URL Node loads. + * + * Hooks run on their own thread, so the record is appended synchronously to a + * file rather than posted over a message port: there is nothing to drain, and + * the file is complete by the time the probe process reads it back. + */ +import { appendFileSync } from "node:fs"; + +let recordPath = ""; + +export async function initialize(data) { + recordPath = data.recordPath; +} + +export async function load(url, context, nextLoad) { + appendFileSync(recordPath, `${url}\n`); + return nextLoad(url, context); +} diff --git a/tests/support/shared-bindings-probe.mjs b/tests/support/shared-bindings-probe.mjs new file mode 100644 index 0000000..2f84bc5 --- /dev/null +++ b/tests/support/shared-bindings-probe.mjs @@ -0,0 +1,34 @@ +/** + * Imports all three published entry points in one process and reports whether + * the transport contract they share is one binding or several copies. + * + * The root barrel and both transport entry points re-export `TransportError` + * and `DOCX_MIME_TYPE`. That is only safe if every entry point resolves to the + * same `dist/transport.js` module instance — otherwise `instanceof` would fail + * across entry points and the duplicate exports would be a real hazard. + */ +import * as core from "@agentic-tooling/polydoc-core"; +import * as google from "@agentic-tooling/polydoc-core/google"; +import * as sharepoint from "@agentic-tooling/polydoc-core/sharepoint"; + +const googleError = new google.GoogleDriveTransportError( + "GOOGLE_DRIVE_API_FAILED", + "probe", + "probe", +); +const sharePointError = new sharepoint.SharePointTransportError( + "SHAREPOINT_HTTP_FAILED", + "probe", + "probe", +); + +process.stdout.write( + JSON.stringify({ + googleReExportsSameTransportError: google.TransportError === core.TransportError, + sharePointReExportsSameTransportError: sharepoint.TransportError === core.TransportError, + googleReExportsSameDocxMimeType: google.DOCX_MIME_TYPE === core.DOCX_MIME_TYPE, + sharePointReExportsSameDocxMimeType: sharepoint.DOCX_MIME_TYPE === core.DOCX_MIME_TYPE, + googleErrorIsCoreTransportError: googleError instanceof core.TransportError, + sharePointErrorIsCoreTransportError: sharePointError instanceof core.TransportError, + }), +);