Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/ci-superdoc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@ jobs:
- name: Run slow tests
run: pnpm test:slow

- name: Consumer typecheck (skipLibCheck off)
run: bash packages/superdoc/tests/consumer-types/run.sh

- name: Install Playwright for UMD smoke test
run: pnpm --filter @superdoc/umd-smoke-test exec playwright install --with-deps chromium

Expand Down
8 changes: 3 additions & 5 deletions packages/super-editor/src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
* This file provides TypeScript types for the JavaScript exports in index.js
*/

// Re-export prosemirror types for consumers AND import for local use
import type { EditorView } from 'prosemirror-view';
import type { EditorState, Transaction } from 'prosemirror-state';
import type { Schema } from 'prosemirror-model';
export type { EditorView, EditorState, Transaction, Schema };
export type { EditorView } from 'prosemirror-view';
export type { EditorState, Transaction } from 'prosemirror-state';
export type { Schema } from 'prosemirror-model';
Comment on lines +6 to +8

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Re-import the ProseMirror types before using them locally

export type { ... } from 'prosemirror-*' only re-exports those names; it does not put EditorView, EditorState, Transaction, or Schema into local scope for this file. The declarations below still use those symbols in CommandProps, Editor, EditorView-related members, etc., so the published superdoc/super-editor entrypoint becomes invalid for consumers even before they reference any runtime code. The earlier import type ...; export type ...; form was needed specifically because this .d.ts file uses those names later.

Useful? React with 👍 / 👎.


// ============================================
// COMMAND TYPES (inlined from ChainedCommands.ts)
Expand Down
287 changes: 0 additions & 287 deletions packages/superdoc/scripts/ensure-types.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -58,291 +58,4 @@ if (hadWorkspaceImport) {
console.log('[ensure-types] ✓ Inlined @superdoc/common types');
}

// ---------------------------------------------------------------------------
// Fix pnpm node_modules paths in ALL .d.ts files (SD-2227)
//
// vite-plugin-dts resolves bare specifiers like 'prosemirror-view' to physical
// pnpm paths like '../../node_modules/.pnpm/prosemirror-view@1.41.5/node_modules/prosemirror-view/dist/index.js'.
// Consumers don't have these paths — rewrite them back to bare specifiers.
// ---------------------------------------------------------------------------

/**
* Recursively find all .d.ts files under a directory.
*/
function findDtsFiles(dir) {
const results = [];
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
results.push(...findDtsFiles(fullPath));
} else if (entry.name.endsWith('.d.ts')) {
results.push(fullPath);
}
}
return results;
}

// Match pnpm node_modules paths in both `from '...'` and `import('...')` contexts.
// Captures the bare package name from the pnpm structure:
// .../node_modules/.pnpm/<pkg>@<ver>/node_modules/<pkg>/dist/index.js
// ^^^^^ capture this
const PNPM_PATH_RE = /(['"])([^'"]*\/node_modules\/\.pnpm\/[^/]+\/node_modules\/(@[^/]+\/[^/]+|[^/]+)\/dist\/index\.js)\1/g;

// Match broken absolute-looking paths like 'packages/superdoc/src/types.js'
// that vite-plugin-dts sometimes emits from path alias resolution.
const BAD_ABSOLUTE_PATH_RE = /(['"])packages\/superdoc\/src\/([^'"]+)\1/g;

// vite-plugin-dts incorrectly resolves subpath exports (e.g. @superdoc/super-editor/types)
// by appending the subpath to the main entry: '../../super-editor/src/index.js/types'
// Fix: rewrite index.js/<subpath> → <subpath>.js
const BAD_SUBPATH_RE = /(['"])([^'"]*\/index\.js)(\/[^'"]+)\1/g;

let fixedFiles = 0;
let totalReplacements = 0;

const dtsFiles = findDtsFiles(distRoot);
for (const filePath of dtsFiles) {
let fileContent = fs.readFileSync(filePath, 'utf8');
let changed = false;

// Fix pnpm node_modules paths → bare specifiers
fileContent = fileContent.replace(PNPM_PATH_RE, (match, quote, _fullPath, packageName) => {
changed = true;
totalReplacements++;
return `${quote}${packageName}${quote}`;
});

// Fix broken absolute-looking paths → relative paths
const relDir = path.relative(path.dirname(filePath), path.join(distRoot, 'superdoc/src'));
fileContent = fileContent.replace(BAD_ABSOLUTE_PATH_RE, (match, quote, rest) => {
changed = true;
totalReplacements++;
let relativePath = path.posix.join(
relDir.split(path.sep).join('/'),
rest,
);
// Ensure relative paths start with ./ (bare names are treated as package specifiers)
if (!relativePath.startsWith('.') && !relativePath.startsWith('/')) {
relativePath = './' + relativePath;
}
return `${quote}${relativePath}${quote}`;
});

// Fix broken subpath exports (index.js/types → types.js)
fileContent = fileContent.replace(BAD_SUBPATH_RE, (match, quote, basePath, subpath) => {
changed = true;
totalReplacements++;
// Replace 'foo/index.js/types' with 'foo/types.js'
const dir = basePath.replace(/\/index\.js$/, '');
return `${quote}${dir}${subpath}.js${quote}`;
});


if (changed) {
fs.writeFileSync(filePath, fileContent);
fixedFiles++;
}
}

if (fixedFiles > 0) {
console.log(`[ensure-types] ✓ Fixed ${totalReplacements} import paths in ${fixedFiles} .d.ts files`);
}

// ---------------------------------------------------------------------------
// Generate ambient module declarations for private workspace packages (SD-2227)
//
// Internal .d.ts files reference @superdoc/* workspace packages that consumers
// can't install. Generate a shim so TypeScript can resolve these imports.
// Also shim prosemirror peer deps that are bundled (not in consumer node_modules).
// ---------------------------------------------------------------------------

// Collect @superdoc/* workspace module specifiers and their named imports from
// all .d.ts files. These are private packages consumers can't install — we
// generate ambient `declare module` shims for them. External packages
// (prosemirror, vue, yjs, etc.) are handled by the hand-written shims below.
const workspaceImports = new Map(); // module → Set<name>

for (const filePath of dtsFiles) {
const fileContent = fs.readFileSync(filePath, 'utf8');

// Match: import { Foo, Bar } from '...' and import type { Foo } from '...'
const namedImports = fileContent.matchAll(/import\s+(?:type\s+)?\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g);
for (const m of namedImports) {
const mod = m[2];

// Skip relative imports and already-handled packages
if (mod.startsWith('.') || mod.startsWith('@superdoc/common') || mod.startsWith('@superdoc/super-editor')) continue;

if (mod.startsWith('@superdoc/')) {
if (!workspaceImports.has(mod)) workspaceImports.set(mod, new Set());
const names = m[1].split(',').map(n => n.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
for (const name of names) workspaceImports.get(mod).add(name);
}
}

// Match: import('...').SomeName — dynamic import type references
const dynamicImports = fileContent.matchAll(/import\(['"]([^'"]+)['"]\)\.(\w+)/g);
for (const m of dynamicImports) {
const mod = m[1];
if (mod.startsWith('.') || mod.startsWith('@superdoc/common') || mod.startsWith('@superdoc/super-editor')) continue;

if (mod.startsWith('@superdoc/')) {
if (!workspaceImports.has(mod)) workspaceImports.set(mod, new Set());
workspaceImports.get(mod).add(m[2]);
}
}

// Match bare @superdoc/* module references
const bareRefs = fileContent.matchAll(/['"](@superdoc\/[^'"]+)['"]/g);
for (const m of bareRefs) {
const mod = m[1];
if (mod.startsWith('@superdoc/common') || mod.startsWith('@superdoc/super-editor')) continue;
if (!workspaceImports.has(mod)) workspaceImports.set(mod, new Set());
}
}

// ---------------------------------------------------------------------------
// Write _internal-shims.d.ts
//
// Two sections:
// 1. Hand-written shims for external packages (prosemirror-*, vue, yjs,
// eventemitter3, @hocuspocus/provider). See KNOWN LIMITATION note in the
// generated file about ambient shims overriding real package types.
// 2. Auto-generated shims for @superdoc/* workspace packages.
// ---------------------------------------------------------------------------

const shimLines = [
'// Auto-generated ambient declarations for internal/bundled packages.',
'// These packages are bundled into superdoc or are internal workspace packages.',
'// Consumers do not need to install them. This file prevents TypeScript errors',
'// when skipLibCheck is false.',
'//',
'// KNOWN LIMITATION: ambient `declare module` with `export type X = any`',
'// overrides real package types when both are present. This affects:',
'// - vue, eventemitter3: direct deps of superdoc — ALWAYS in consumer',
'// node_modules, so real types are always replaced by `any`.',
'// - yjs, @hocuspocus/provider: peer deps — affected when installed.',
'// - prosemirror-*: bundled (not in consumer node_modules) — no conflict.',
'// The proper fix is adding prosemirror-* as peerDependencies and removing',
'// shims for packages consumers already have installed.',
'//',
'// NOTE: This is a script file (no exports), so `declare module` creates',
'// global ambient declarations and top-level declarations are global.',
'',
'// --- Well-known external packages (hand-written for correctness) ---',
'',
"declare module 'prosemirror-model' {",
' export type DOMOutputSpec = any;',
' export type Fragment = any;',
' export type Mark = any;',
' export type MarkType = any;',
' export type Node = any;',
' export type NodeType = any;',
' export type ParseRule = any;',
' export type ResolvedPos = any;',
' export type Schema<N extends string = any, M extends string = any> = any;',
' export type Slice = any;',
'}',
'',
"declare module 'prosemirror-state' {",
' export type EditorState = any;',
' export type Plugin<T = any> = any;',
' export type PluginKey<T = any> = any;',
' export type TextSelection = any;',
' export type Transaction = any;',
'}',
'',
"declare module 'prosemirror-transform' {",
' export type Mapping = any;',
' export type ReplaceAroundStep = any;',
' export type ReplaceStep = any;',
' export type Step = any;',
'}',
'',
"declare module 'prosemirror-view' {",
' export type Decoration = any;',
' export type DecorationSet = any;',
' export type DecorationSource = any;',
' export type EditorProps = any;',
' export type EditorView = any;',
' export type NodeView = any;',
'}',
'',
"declare module 'eventemitter3' {",
' export class EventEmitter<EventTypes extends string | symbol = string | symbol, Context = any> {',
' on(event: EventTypes, fn: (...args: any[]) => void, context?: Context): this;',
' off(event: EventTypes, fn: (...args: any[]) => void, context?: Context): this;',
' emit(event: EventTypes, ...args: any[]): boolean;',
' removeAllListeners(event?: EventTypes): this;',
' }',
' export default EventEmitter;',
'}',
'',
"declare module 'vue' {",
' export type App<T = any> = any;',
' export type ComponentOptionsBase<P = any, B = any, D = any, C = any, M = any, Mixin = any, Extends = any, E = any, EE = any, Defaults = any, I = any, II = any, S = any, LC = any, Directives = any, Exposed = any, Provide = any> = any;',
' export type ComponentOptionsMixin = any;',
' export type ComponentProvideOptions = any;',
' export type ComponentPublicInstance<P = any, B = any, D = any, C = any, M = any, E = any, S = any, Options = any, Defaults = any, MakeDefaultsOptional = any, I = any, PublicMixin = any, A = any, B2 = any, C2 = any> = any;',
' export type ComputedRef<T = any> = any;',
' export type CreateComponentPublicInstanceWithMixins<T = any, S = any, U = any, V = any, W = any, X = any, Y = any, Z = any, A = any, B = any, C = any, D = any> = any;',
' export type DefineComponent<P = any, B = any, D = any, C = any, M = any, Mixin = any, Extends = any, E = any, EE = any, PP = any, Props = any, Defaults = any, S = any> = any;',
' export type ExtractPropTypes<T = any> = any;',
' export type GlobalComponents = any;',
' export type GlobalDirectives = any;',
' export type PublicProps = any;',
' export type Ref<T = any> = any;',
' export type RendererElement = any;',
' export type RendererNode = any;',
' export type ShallowRef<T = any> = any;',
' export type VNode = any;',
'}',
'',
"declare module 'yjs' {",
' export type Doc = any;',
' export type XmlFragment = any;',
' export type RelativePosition = any;',
'}',
'',
"declare module '@hocuspocus/provider' {",
' export type HocuspocusProvider = any;',
'}',
'',
];

// --- Auto-generated @superdoc/* workspace package shims ---

let wsCount = 0;
if (workspaceImports.size > 0) {
shimLines.push('// --- Internal workspace packages (auto-generated) ---');
shimLines.push('');
for (const [mod, names] of [...workspaceImports.entries()].sort((a, b) => a[0].localeCompare(b[0]))) {
wsCount++;
const sortedNames = [...names].sort();
const exportLines = sortedNames
.map(n => ` export type ${n} = any;`);
if (exportLines.length > 0) {
shimLines.push(`declare module '${mod}' {\n${exportLines.join('\n')}\n}`);
} else {
shimLines.push(`declare module '${mod}' { const _: any; export default _; }`);
}
}
}
shimLines.push('');

const shimPath = path.join(distRoot, '_internal-shims.d.ts');
fs.writeFileSync(shimPath, shimLines.join('\n'));

// Add reference directive to entry points so TypeScript includes the shims
const shimRef = '/// <reference path="../../_internal-shims.d.ts" />\n';
for (const entry of requiredEntryPoints) {
const entryPath = path.join(distRoot, entry);
const entryContent = fs.readFileSync(entryPath, 'utf8');
if (!entryContent.includes('_internal-shims.d.ts')) {
fs.writeFileSync(entryPath, shimRef + entryContent);
}
}

console.log(`[ensure-types] ✓ Generated ambient shims for ${wsCount} workspace + 8 external modules`);

console.log('[ensure-types] ✓ Verified type entry points');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the declaration-file rewrite/shim pass in postbuild

Removing this block reintroduces the packaging bug from SD-2227: vite-plugin-dts still emits declaration imports that only resolve inside this monorepo (physical pnpm paths plus private workspace modules such as the @superdoc/common imports coming from packages/superdoc/src/index.js:13-15 and the @superdoc/super-editor/types re-export in packages/superdoc/src/types.ts:1). With no rewrite/shim step left in ensure-types.cjs, the npm tarball can build successfully here but fail in any consumer project that runs TypeScript with skipLibCheck: false.

Useful? React with 👍 / 👎.

38 changes: 0 additions & 38 deletions packages/superdoc/tests/consumer-types/run.sh

This file was deleted.

23 changes: 0 additions & 23 deletions packages/superdoc/tests/consumer-types/test.ts

This file was deleted.

12 changes: 0 additions & 12 deletions packages/superdoc/tests/consumer-types/tsconfig.json

This file was deleted.

Loading