@@ -37,6 +37,124 @@ bun run --filter '*' build
3737- Node >= 24.11.0
3838- Bun >= 1.3.7
3939
40+ ## Publishing packages to npm
41+
42+ - Always publish with `bun publish` (or `bun pm pack` for inspection).
43+ Never use `npm publish`, `yarn publish`, or `pnpm publish`.
44+ - Reason: `peerDependencies` use the `catalog:` protocol so every package
45+ tracks the same Tiptap / Hocuspocus / etc. version pinned in the root
46+ `package.json`'s `catalog` block. Only Bun resolves `catalog:` to a
47+ concrete semver range at pack time. `npm publish` would ship a literal
48+ `"catalog:"` string in the published `package.json` and break every
49+ consumer install with `Invalid Version: catalog:`.
50+ - Publishable packages enforce this with a `prepublishOnly` preflight
51+ (e.g. `packages/extension-hyperlink/scripts/preflight.ts`) that fails
52+ fast if the publisher is not Bun. When you add a new publishable
53+ package, copy that preflight or move it into a shared package.
54+
55+ ## Shared config / single source of truth
56+
57+ The monorepo prefers root-level shared config files referenced by relative
58+ path over per-package duplication. Three patterns are in active use:
59+
60+ ### `LICENSE` — root file + `prepack` sync
61+
62+ - The canonical `LICENSE` lives at the **monorepo root** (one committed
63+ file, one SPDX identity).
64+ - Each **publishable** package adds `/LICENSE` (anchored to the package
65+ root, so a vendored dep's nested `LICENSE` isn't silently ignored) to
66+ its `.gitignore` and a `scripts/prepack.ts` that copies the root
67+ `LICENSE` into the package directory before `bun publish` /
68+ `bun pm pack`. Wire it via:
69+
70+ ```jsonc
71+ // package.json
72+ "scripts": { "prepack": "bun run scripts/prepack.ts" }
73+ ```
74+
75+ - Symlinks and hard links **do not work** here:
76+ - Bun's pack silently drops symlinks from the tarball.
77+ - Hard links work locally but git stores them as two independent files,
78+ so contributors who clone get two copies that drift silently.
79+ - The `prepack` lifecycle (not `prepublishOnly`) is the right hook
80+ because both `bun publish` AND `bun pm pack` honor it, so dry-run
81+ packs still produce a realistic tarball.
82+ - Currently only `extension-hyperlink` is wired this way; copy the same
83+ three pieces (`/LICENSE` in `.gitignore`, `scripts/prepack.ts`, the
84+ `prepack` script entry) when a second package is prepared for publish.
85+
86+ ### `tsconfig.base.json` — root base + per-package `extends`
87+
88+ - `tsconfig.base.json` at the monorepo root holds the shared
89+ `compilerOptions` (target, module, strict, etc.). Per-package
90+ `tsconfig.json` files only declare what's actually different
91+ (`outDir`, `rootDir`, `include`, `exclude`):
92+
93+ ```jsonc
94+ {
95+ "extends": "../../tsconfig.base.json",
96+ "compilerOptions": { "outDir": "dist" },
97+ "include": ["src/**/*"],
98+ "exclude": ["node_modules", "dist"]
99+ }
100+ ```
101+
102+ ### `tsup.base.ts` — root factory + per-package call
103+
104+ - `tsup.base.ts` at the monorepo root exports a
105+ `defineTiptapExtensionConfig()` factory with the shared
106+ Tiptap-extension build shape (ESM+CJS, dts, `@tiptap/core` /
107+ `@tiptap/pm` external, prod sourcemaps/minify,
108+ `console.log/debug` stripped via `esbuildOptions.pure` while
109+ `console.warn/error` are preserved to match the eslint allowlist
110+ — do **not** use `drop: ['console']` which strips warn/error too).
111+ - The name follows the tsup ecosystem `define*` verb convention and is
112+ honestly scoped: the factory hardcodes Tiptap externals, so it is
113+ **not** a generic library factory. A future non-Tiptap library
114+ package should not reach for this factory; either add its own config
115+ or refactor the factory to take externals as a parameter.
116+ - A package's `tsup.config.ts` becomes a four-line file:
117+
118+ ```ts
119+ import { defineConfig } from 'tsup'
120+ import { defineTiptapExtensionConfig } from '../../tsup.base'
121+
122+ export default defineConfig(defineTiptapExtensionConfig())
123+ ```
124+
125+ - Pass overrides for genuinely package-specific concerns (e.g. an
126+ `onSuccess` hook to copy `styles.css` into `dist`).
127+ - **Override semantics — shallow spread, NOT deep merge.** Function-valued
128+ options (`esbuildOptions`, `external`, `dts`) passed to
129+ `defineTiptapExtensionConfig({...})` fully replace the base. If you
130+ ever override `esbuildOptions`, you must call the base's `pure` policy
131+ yourself or `console.log`/`debug` will silently survive into the
132+ production bundle. JSDoc on the factory spells out the recipe; add a
133+ deep-merge layer the day a second caller actually needs it, not
134+ before.
135+ - **Behavior change at adoption:** the prior per-package configs used
136+ `drop: ['console']` which stripped warn/error too. Switching to the
137+ factory restores `console.warn`/`console.error` in production.
138+ - 3 packages (`extension-indent`, `extension-inline-code`,
139+ `extension-placeholder`): no console calls in source → byte-identical
140+ dist trees, no observable change.
141+ - 1 package (`extension-hypermultimedia`): the `Logger` class in
142+ `src/utils/floating-toolbar.ts` wraps `console.warn`/`console.error`
143+ — those calls now reach consumer consoles in production. This was
144+ the author's clear intent; the prior behavior was an accidental
145+ over-strip. Note this in the next `extension-hypermultimedia`
146+ CHANGELOG entry when it's republished.
147+
148+ ### What we deliberately do **not** share
149+
150+ - `README.md`, `CHANGELOG.md`, package source — these are inherently
151+ per-package.
152+ - `eslint.config.js` per package — already a 3-line shim importing from
153+ `@docs.plus/eslint-config`; further dedup adds no value.
154+ - `package.json` itself — generators that synthesize `package.json`
155+ fields fight semver tooling, registries, and contributor expectations.
156+ Keep these manual.
157+
40158## Docker base image (single source of truth)
41159
42160- All Dockerfiles use **one tag**: `oven/bun:1-slim` (latest Bun 1.x, floating; no hardcoded patch version).
0 commit comments