From 98f33d380fe9cda02f6def9a9024585c031cc997 Mon Sep 17 00:00:00 2001 From: Luccas Correa Date: Tue, 17 Mar 2026 14:39:43 -0300 Subject: [PATCH 1/2] fix: seed base docx package for collaboration exports --- packages/super-editor/src/core/DocxZipper.js | 4 ++ .../super-editor/src/core/DocxZipper.test.js | 16 +++++ packages/super-editor/src/core/Editor.ts | 68 ++++++++++++++----- .../src/core/super-converter/exporter.js | 6 +- .../src/tests/export/jsonDeclaration.test.js | 16 +++++ 5 files changed, 92 insertions(+), 18 deletions(-) diff --git a/packages/super-editor/src/core/DocxZipper.js b/packages/super-editor/src/core/DocxZipper.js index 3c1d6b6d54..29b17d10cb 100644 --- a/packages/super-editor/src/core/DocxZipper.js +++ b/packages/super-editor/src/core/DocxZipper.js @@ -409,6 +409,10 @@ class DocxZipper { * @returns {Promise} The unzipped but updated docx file ready for zipping */ async exportFromCollaborativeDocx(docx, updatedDocs, media, fonts) { + if (!Array.isArray(docx)) { + throw new Error('Collaborative DOCX export requires base package entries'); + } + const zip = new JSZip(); // Rebuild original files diff --git a/packages/super-editor/src/core/DocxZipper.test.js b/packages/super-editor/src/core/DocxZipper.test.js index 5d262a03f1..902994963c 100644 --- a/packages/super-editor/src/core/DocxZipper.test.js +++ b/packages/super-editor/src/core/DocxZipper.test.js @@ -431,6 +431,22 @@ describe('DocxZipper - updateContentTypes', () => { }); describe('DocxZipper - exportFromCollaborativeDocx media handling', () => { + it('throws when collaborative export has no original docx entries to rebuild from', async () => { + const zipper = new DocxZipper(); + + await expect( + zipper.updateZip({ + docx: null, + updatedDocs: { + 'word/document.xml': '', + }, + media: {}, + fonts: {}, + isHeadless: true, + }), + ).rejects.toThrow('Collaborative DOCX export requires base package entries'); + }); + it('handles both base64 string and ArrayBuffer media values', async () => { const zipper = new DocxZipper(); diff --git a/packages/super-editor/src/core/Editor.ts b/packages/super-editor/src/core/Editor.ts index d1e5769bf0..0e38a63582 100644 --- a/packages/super-editor/src/core/Editor.ts +++ b/packages/super-editor/src/core/Editor.ts @@ -805,22 +805,8 @@ export class Editor extends EventEmitter { resolvedMode === 'docx' && !options?.content && !options?.html && !options?.markdown; if (shouldLoadBlankDocx) { - // Decode base64 blank.docx without fetch - const arrayBuffer = await getArrayBufferFromUrl(BLANK_DOCX_DATA_URI); - const isNodeRuntime = typeof process !== 'undefined' && !!process.versions?.node; - const canUseBuffer = isNodeRuntime && typeof Buffer !== 'undefined'; - // Use Uint8Array to ensure compatibility with both Node Buffer and browser Blob - const uint8Array = new Uint8Array(arrayBuffer); - let fileSource: File | Blob | Buffer; - if (canUseBuffer) { - fileSource = Buffer.from(uint8Array); - } else if (typeof Blob !== 'undefined') { - fileSource = new Blob([uint8Array as BlobPart]); - } else { - throw new Error('Blob is not available to create blank DOCX'); - } - const [docx, _media, mediaFiles, fonts] = (await Editor.loadXmlData(fileSource, canUseBuffer))!; - resolvedOptions.content = docx; + const { content, mediaFiles, fonts, fileSource } = await this.#loadBlankDocxTemplate(); + resolvedOptions.content = content; resolvedOptions.mediaFiles = { ...mediaFiles, ...(options?.mediaFiles ?? {}), @@ -1736,6 +1722,49 @@ export class Editor extends EventEmitter { } } + async #loadBlankDocxTemplate(): Promise<{ + content: DocxFileEntry[]; + mediaFiles: Record; + fonts: Record; + fileSource: File | Blob | Buffer; + }> { + const arrayBuffer = await getArrayBufferFromUrl(BLANK_DOCX_DATA_URI); + const isNodeRuntime = typeof process !== 'undefined' && !!process.versions?.node; + const canUseBuffer = isNodeRuntime && typeof Buffer !== 'undefined'; + const uint8Array = new Uint8Array(arrayBuffer); + + let fileSource: File | Blob | Buffer; + if (canUseBuffer) { + fileSource = Buffer.from(uint8Array); + } else if (typeof Blob !== 'undefined') { + fileSource = new Blob([uint8Array as BlobPart]); + } else { + throw new Error('Blob is not available to create blank DOCX'); + } + + const [content, _media, mediaFiles, fonts] = (await Editor.loadXmlData(fileSource, canUseBuffer))!; + return { content, mediaFiles, fonts, fileSource }; + } + + async #getBaseDocxEntriesForExport(): Promise { + if (Array.isArray(this.options.content)) { + return this.options.content as DocxFileEntry[]; + } + + const blankDocx = await this.#loadBlankDocxTemplate(); + this.options.content = blankDocx.content; + this.options.mediaFiles = { + ...blankDocx.mediaFiles, + ...(this.options.mediaFiles ?? {}), + }; + this.options.fonts = { + ...blankDocx.fonts, + ...(this.options.fonts ?? {}), + }; + + return blankDocx.content; + } + /** * Initialize media. */ @@ -2808,8 +2837,13 @@ export class Editor extends EventEmitter { return updatedDocs; } + const baseDocxEntries = + !this.options.fileSource && !Array.isArray(this.options.content) + ? await this.#getBaseDocxEntriesForExport() + : this.options.content; + const result = await zipper.updateZip({ - docx: this.options.content, + docx: baseDocxEntries, updatedDocs: updatedDocs, originalDocxFile: this.options.fileSource, media, diff --git a/packages/super-editor/src/core/super-converter/exporter.js b/packages/super-editor/src/core/super-converter/exporter.js index c13c45afe8..ef5288efbb 100644 --- a/packages/super-editor/src/core/super-converter/exporter.js +++ b/packages/super-editor/src/core/super-converter/exporter.js @@ -577,7 +577,11 @@ export class DocxExporter { #generate_xml_as_list(data, debug = false) { const json = JSON.parse(JSON.stringify(data)); - const declaration = this.converter.declaration.attributes; + const declaration = this.converter.declaration?.attributes ?? { + version: '1.0', + encoding: 'UTF-8', + standalone: 'yes', + }; const xmlTag = ` ` ${key}="${value}"`) .join('')}?>`; diff --git a/packages/super-editor/src/tests/export/jsonDeclaration.test.js b/packages/super-editor/src/tests/export/jsonDeclaration.test.js index 166666236b..4687055a42 100644 --- a/packages/super-editor/src/tests/export/jsonDeclaration.test.js +++ b/packages/super-editor/src/tests/export/jsonDeclaration.test.js @@ -53,4 +53,20 @@ describe('Json override export', () => { editor.destroy(); } }); + + it('exports a DOCX when base package entries are missing before export', async () => { + const editor = await Editor.open(undefined, { json: SAMPLE_JSON }); + + try { + editor.options.fileSource = null; + editor.options.content = ''; + + const exported = await editor.exportDocx(); + expect(Buffer.isBuffer(exported)).toBe(true); + expect(exported.length).toBeGreaterThan(0); + expect(Array.isArray(editor.options.content)).toBe(true); + } finally { + editor.destroy(); + } + }); }); From 756b12cf6cc33df2bdc41d1bae268eb627186762 Mon Sep 17 00:00:00 2001 From: Matthew Connelly Date: Thu, 19 Mar 2026 00:39:58 -0400 Subject: [PATCH 2/2] fix(types): expand definitions and exports to pass compilation --- packages/super-editor/src/index.d.ts | 214 +++++++++++++++++- packages/superdoc/src/index.js | 35 +++ tests/consumer-typecheck/package.json | 14 ++ tests/consumer-typecheck/src/index.ts | 299 +++++++++++++++++++++++++ tests/consumer-typecheck/tsconfig.json | 12 + 5 files changed, 569 insertions(+), 5 deletions(-) create mode 100644 tests/consumer-typecheck/package.json create mode 100644 tests/consumer-typecheck/src/index.ts create mode 100644 tests/consumer-typecheck/tsconfig.json diff --git a/packages/super-editor/src/index.d.ts b/packages/super-editor/src/index.d.ts index 273822b4cf..e03d7f4074 100644 --- a/packages/super-editor/src/index.d.ts +++ b/packages/super-editor/src/index.d.ts @@ -45,7 +45,20 @@ export type Command = (props: CommandProps) => boolean; * Chainable command object returned by editor.chain() */ export interface ChainableCommandObject { - run: () => boolean; + /** Execute the chained commands */ + run(): boolean; + /** Chain any command - returns self for further chaining */ + toggleBold(): ChainableCommandObject; + toggleItalic(): ChainableCommandObject; + toggleUnderline(): ChainableCommandObject; + toggleStrike(): ChainableCommandObject; + setFontSize(size: string | number): ChainableCommandObject; + setFontFamily(family: string): ChainableCommandObject; + setTextColor(color: string): ChainableCommandObject; + setTextAlign(alignment: 'left' | 'center' | 'right' | 'justify'): ChainableCommandObject; + insertContent(content: any): ChainableCommandObject; + focus(position?: 'start' | 'end' | 'all' | number | boolean | null): ChainableCommandObject; + /** Allow any other command */ [commandName: string]: ((...args: any[]) => ChainableCommandObject) | (() => boolean); } @@ -167,6 +180,67 @@ export interface OpenOptions { fonts?: Record; } +// ============================================ +// COMMENT TYPES +// ============================================ + +/** A comment element (paragraph, text run, etc.) */ +export interface CommentElement { + type: string; + content?: CommentElement[]; + text?: string; +} + +/** A comment in the document */ +export interface Comment { + /** Unique comment identifier */ + commentId: string; + /** Timestamp when comment was created */ + createdTime: number; + /** Email of the comment author */ + creatorEmail: string; + /** Display name of the comment author */ + creatorName: string; + /** Comment content elements */ + elements: CommentElement[]; + /** Original ID from imported document */ + importedId?: string; + /** Whether the comment is resolved */ + isDone: boolean; + /** Parent comment ID for replies */ + parentCommentId?: string; + /** Raw JSON representation */ + commentJSON?: unknown; +} + +/** Event data for comments loaded event */ +export interface CommentsLoadedEventData { + comments: Comment[]; +} + +/** Event data for content error event */ +export interface ContentErrorEventData { + error: Error; +} + +/** Font configuration */ +export interface FontConfig { + key: string; + label: string; + fontWeight?: number; + props?: { + style?: { + fontFamily?: string; + }; + }; +} + +/** Font support information */ +export interface FontSupportInfo { + documentFonts: string[]; + unsupportedFonts: string[]; +} + // ============================================ // PRESENTATION EDITOR TYPES // ============================================ @@ -554,8 +628,131 @@ export declare class Editor { */ isEmpty: boolean; - /** Allow additional properties */ - [key: string]: any; + // ============================================ + // EVENT METHODS + // ============================================ + + /** + * Register an event listener. + * @param event - Event name ('update', 'create', 'transaction', etc.) + * @param handler - Event handler function + */ + on(event: string, handler: (...args: any[]) => void): void; + + /** + * Remove an event listener. + * @param event - Event name + * @param handler - Event handler function to remove + */ + off(event: string, handler: (...args: any[]) => void): void; + + /** + * Emit an event. + * @param event - Event name + * @param args - Arguments to pass to handlers + */ + emit(event: string, ...args: any[]): void; + + // ============================================ + // DOCUMENT EXPORT METHODS + // ============================================ + + /** + * Export the document to DOCX format. + * @param options - Export options + * @returns Promise resolving to Blob (browser) or Buffer (Node.js) + */ + exportDocx(options?: { + isFinalDoc?: boolean; + commentsType?: string; + comments?: Comment[]; + fieldsHighlightColor?: string | null; + compression?: 'DEFLATE' | 'STORE'; + }): Promise; + + /** + * Export the document (alias for exportDocx). + */ + exportDocument(options?: { + isFinalDoc?: boolean; + commentsType?: string; + comments?: Comment[]; + }): Promise; + + /** + * Save the document to the original source path (Node.js only). + */ + save(options?: { isFinalDoc?: boolean; commentsType?: string; comments?: Comment[] }): Promise; + + /** + * Save the document to a specific path (Node.js only). + */ + saveTo( + path: string, + options?: { + isFinalDoc?: boolean; + commentsType?: string; + comments?: Comment[]; + }, + ): Promise; + + // ============================================ + // TOOLBAR & UI METHODS + // ============================================ + + /** + * Set the toolbar for this editor. + */ + setToolbar(toolbar: any): void; + + /** + * Set whether the editor is editable. + */ + setEditable(editable: boolean, emitUpdate?: boolean): void; + + /** + * Set the document mode. + */ + setDocumentMode(mode: 'editing' | 'viewing' | 'suggesting'): void; + + /** + * Focus the editor. + */ + focus(): void; + + /** + * Blur the editor. + */ + blur(): void; + + // ============================================ + // DOCUMENT METHODS + // ============================================ + + /** + * Open a document. + */ + open(source?: string | File | Blob | BinaryData, options?: OpenOptions): Promise; + + /** + * Close the current document. + */ + close(): void; + + /** + * Replace the current file. + */ + replaceFile(newFile: File | Blob | BinaryData): Promise; + + /** + * Get the document as Markdown. + */ + getMarkdown(): Promise; + + /** + * Check if the editor is currently active/focused. + */ + isFocused: boolean; } // ============================================ @@ -909,8 +1106,15 @@ export declare class PresentationEditor { */ emit(event: string, ...args: any[]): void; - /** Allow additional properties */ - [key: string]: any; + /** + * Replace the current file. + */ + replaceFile(newFile: File | Blob | BinaryData): Promise; + + /** + * Set the toolbar for the editor. + */ + setToolbar(toolbar: any): void; } // ============================================ diff --git a/packages/superdoc/src/index.js b/packages/superdoc/src/index.js index 34047a9504..f5b87828c3 100644 --- a/packages/superdoc/src/index.js +++ b/packages/superdoc/src/index.js @@ -1,6 +1,8 @@ import { SuperConverter, Editor, + PresentationEditor, + getStarterExtensions, getRichTextExtensions, createZip, Extensions, @@ -15,6 +17,37 @@ import { DOCX, PDF, HTML, getFileObject, compareVersions } from '@superdoc/commo import BlankDOCX from '@superdoc/common/data/blank.docx?url'; import { getSchemaIntrospection } from './helpers/schema-introspection.js'; +// ============================================ +// TYPE RE-EXPORTS +// These types are defined in @superdoc/super-editor and re-exported for consumers +// ============================================ + +/** + * @typedef {import('@superdoc/super-editor').EditorState} EditorState + * @typedef {import('@superdoc/super-editor').Transaction} Transaction + * @typedef {import('@superdoc/super-editor').Schema} Schema + * @typedef {import('@superdoc/super-editor').EditorView} EditorView + * @typedef {import('@superdoc/super-editor').EditorCommands} EditorCommands + * @typedef {import('@superdoc/super-editor').ChainedCommand} ChainedCommand + * @typedef {import('@superdoc/super-editor').ChainableCommandObject} ChainableCommandObject + * @typedef {import('@superdoc/super-editor').PresentationEditorOptions} PresentationEditorOptions + * @typedef {import('@superdoc/super-editor').LayoutEngineOptions} LayoutEngineOptions + * @typedef {import('@superdoc/super-editor').PageSize} PageSize + * @typedef {import('@superdoc/super-editor').PageMargins} PageMargins + * @typedef {import('@superdoc/super-editor').Layout} Layout + * @typedef {import('@superdoc/super-editor').LayoutPage} LayoutPage + * @typedef {import('@superdoc/super-editor').LayoutFragment} LayoutFragment + * @typedef {import('@superdoc/super-editor').RangeRect} RangeRect + * @typedef {import('@superdoc/super-editor').BoundingRect} BoundingRect + * @typedef {import('@superdoc/super-editor').OpenOptions} OpenOptions + * @typedef {import('@superdoc/super-editor').DocxFileEntry} DocxFileEntry + * @typedef {import('@superdoc/super-editor').Comment} Comment + * @typedef {import('@superdoc/super-editor').CommentElement} CommentElement + * @typedef {import('@superdoc/super-editor').CommentsLoadedEventData} CommentsLoadedEventData + * @typedef {import('@superdoc/super-editor').FontConfig} FontConfig + * @typedef {import('@superdoc/super-editor').FontSupportInfo} FontSupportInfo + */ + // Public exports export { SuperDoc } from './core/SuperDoc.js'; export { @@ -22,6 +55,8 @@ export { getFileObject, compareVersions, Editor, + PresentationEditor, + getStarterExtensions, getRichTextExtensions, getSchemaIntrospection, diff --git a/tests/consumer-typecheck/package.json b/tests/consumer-typecheck/package.json new file mode 100644 index 0000000000..fd02e045d9 --- /dev/null +++ b/tests/consumer-typecheck/package.json @@ -0,0 +1,14 @@ +{ + "name": "consumer-typecheck", + "private": true, + "type": "module", + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "superdoc": "file:../../packages/superdoc/superdoc.tgz" + }, + "devDependencies": { + "typescript": "^5.0.0" + } +} diff --git a/tests/consumer-typecheck/src/index.ts b/tests/consumer-typecheck/src/index.ts new file mode 100644 index 0000000000..5464479e3a --- /dev/null +++ b/tests/consumer-typecheck/src/index.ts @@ -0,0 +1,299 @@ +/** + * Consumer TypeScript compilation test. + * + * This file attempts to use SuperDoc's public API the way a real customer would. + * If this file compiles, the types are sufficient. If it fails, we know what's missing. + * + * Based on customer workaround from SD-2227. + */ + +// ============================================ +// BASIC IMPORTS +// These should all resolve from the main entry point +// ============================================ + +import { + SuperDoc, + Editor, + SuperConverter, + PresentationEditor, + getRichTextExtensions, + getStarterExtensions, + DOCX, + PDF, + HTML, + Extensions, + createZip, +} from 'superdoc'; + +// ============================================ +// EDITOR CLASS - STATIC METHODS +// Customer uses Editor.loadXmlData() for headless processing +// ============================================ + +async function testEditorStaticMethods() { + const file = new File([''], 'test.docx'); + + // Static method to load DOCX data + const xmlData = await Editor.loadXmlData(file); + + // Static method to open a document + const editor = await Editor.open(file); +} + +// ============================================ +// EDITOR CLASS - INSTANCE METHODS +// Customer needs exportDocx, on/off event handlers, setToolbar +// ============================================ + +async function testEditorInstanceMethods() { + const editor = new Editor(); + + // Export to DOCX - customer's primary use case + const docxBlob = await editor.exportDocx({ + comments: [], + commentsType: 'none', + }); + + // Event handlers - customer uses these extensively + editor.on('update', () => {}); + editor.on('create', () => {}); + editor.off('update', () => {}); + + // Toolbar integration + editor.setToolbar({}); + + // Basic properties + const state = editor.state; + const schema = editor.schema; + const isEditable = editor.isEditable; + const commands = editor.commands; + + // Commands + editor.commands.toggleBold(); + editor.commands.setFontSize('14pt'); + + // Chain commands + editor.chain().toggleBold().toggleItalic().run(); + + // Content methods + const html = editor.getHTML(); + const json = editor.getJSON(); + const text = editor.getText(); + + // Cleanup + editor.destroy(); +} + +// ============================================ +// PRESENTATION EDITOR CLASS +// Customer's main entry point for paginated editing +// ============================================ + +async function testPresentationEditor() { + const container = document.createElement('div'); + + // Constructor with options - customer passes many options + const presentationEditor = new PresentationEditor({ + element: container, + documentMode: 'editing', + content: {}, + extensions: [], + editable: true, + isCommentsEnabled: true, + }); + + // Access underlying editor + const editor = presentationEditor.editor; + + // State and schema access + const state = presentationEditor.state; + const isEditable = presentationEditor.isEditable; + + // Commands - customer uses comment commands + presentationEditor.commands.insertComment?.({ id: '123' }); + presentationEditor.commands.insertContent?.('Hello'); + + // Event handlers + presentationEditor.on('update', () => {}); + presentationEditor.on('create', () => {}); + presentationEditor.off('update', () => {}); + + // Document mode switching + presentationEditor.setDocumentMode('viewing'); + presentationEditor.setDocumentMode('suggesting'); + presentationEditor.setDocumentMode('editing'); + + // File replacement + const newFile = new File([''], 'new.docx'); + await presentationEditor.replaceFile?.(newFile); + + // Zoom + presentationEditor.setZoom(1.5); + + // Layout methods + const pages = presentationEditor.getPages(); + const rects = presentationEditor.getSelectionRects(); + + // Cleanup + presentationEditor.destroy(); +} + +// ============================================ +// SUPER TOOLBAR CLASS +// Customer creates toolbar with specific options +// ============================================ + +function testSuperToolbar() { + const editor = new Editor(); + + // Toolbar construction - customer uses these options + // const toolbar = new SuperToolbar({ + // editor, + // selector: '#toolbar', + // fonts: [], + // toolbarGroups: ['formatting', 'lists'], + // hideButtons: false, + // pagination: true, + // icons: {}, + // }); + + // Note: SuperToolbar is typed as `any` in hand-written types + // This section tests if it's exported at all +} + +// ============================================ +// COMMENT TYPES +// Customer works extensively with comments +// ============================================ + +interface ExpectedCommentShape { + commentId: string; + createdTime: number; + creatorEmail: string; + creatorName: string; + elements: Array<{ + type: string; + content?: unknown[]; + text?: string; + }>; + importedId: string; + isDone: boolean; + parentCommentId: string; +} + +function testCommentTypes() { + // This tests if Comment type is exported + // Customer needs to type their comment handlers + + const handleCommentsLoaded = (data: { comments: ExpectedCommentShape[] }) => { + for (const comment of data.comments) { + console.log(comment.commentId, comment.creatorName); + } + }; +} + +// ============================================ +// EXTENSION HELPERS +// Customer uses these to configure the editor +// ============================================ + +function testExtensionHelpers() { + // Get default extensions + const starterExtensions = getStarterExtensions(); + const richTextExtensions = getRichTextExtensions(); + + // Extensions namespace + const { Node, Mark, Extension, Plugin, PluginKey } = Extensions; +} + +// ============================================ +// SUPERDOC CLASS (Vue component wrapper) +// ============================================ + +function testSuperDoc() { + // SuperDoc is the main Vue component + // Type should exist even if it's `any` + const superdoc: typeof SuperDoc = SuperDoc; +} + +// ============================================ +// UTILITY FUNCTIONS +// ============================================ + +async function testUtilities() { + // createZip for manual DOCX assembly + const files = [{ name: 'document.xml', content: '' }]; + const zip = await createZip(files); + + // MIME type constants + const docxMime: typeof DOCX = DOCX; + const pdfMime: typeof PDF = PDF; + const htmlMime: typeof HTML = HTML; +} + +// ============================================ +// TYPE EXPORTS +// These types should be importable by consumers +// ============================================ + +import type { + // Editor types + EditorState, + Transaction, + Schema, + EditorView, + + // Command types + EditorCommands, + ChainedCommand, + + // Presentation types + PresentationEditorOptions, + LayoutEngineOptions, + PageSize, + PageMargins, + + // Layout types + Layout, + LayoutPage, + LayoutFragment, + RangeRect, + BoundingRect, + + // Data types + OpenOptions, + DocxFileEntry, +} from 'superdoc'; + +function testTypeImports() { + // Verify types are usable + const options: PresentationEditorOptions = { + element: document.createElement('div'), + }; + + const pageSize: PageSize = { w: 612, h: 792 }; + const margins: PageMargins = { top: 72, right: 72, bottom: 72, left: 72 }; + + const layoutOptions: LayoutEngineOptions = { + pageSize, + margins, + zoom: 1, + }; +} + +// ============================================ +// Run type checks (these are just for TypeScript, not runtime) +// ============================================ + +export { + testEditorStaticMethods, + testEditorInstanceMethods, + testPresentationEditor, + testSuperToolbar, + testCommentTypes, + testExtensionHelpers, + testSuperDoc, + testUtilities, + testTypeImports, +}; diff --git a/tests/consumer-typecheck/tsconfig.json b/tests/consumer-typecheck/tsconfig.json new file mode 100644 index 0000000000..c38e3f9f65 --- /dev/null +++ b/tests/consumer-typecheck/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "noEmit": true, + "esModuleInterop": true + }, + "include": ["src/**/*.ts"] +}