verify-core-api.mjs 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687
  1. import { existsSync } from 'node:fs'
  2. import { readdir, readFile } from 'node:fs/promises'
  3. import { dirname, extname, join, resolve } from 'node:path'
  4. import { fileURLToPath } from 'node:url'
  5. const scriptDir = dirname(fileURLToPath(import.meta.url))
  6. const sourceRoot = resolve(scriptDir, '..')
  7. const coreDir = join(sourceRoot, 'packages', 'core')
  8. const coreSrcDir = join(coreDir, 'src')
  9. const requiredValueExports = [
  10. 'createRendererRegistry',
  11. 'coreBrowserRendererHandlers',
  12. 'createFileViewerCoreRendererRegistry',
  13. 'fileViewerCoreRendererDispatcher',
  14. 'fileViewerCoreRendererRegistry',
  15. 'fileViewerCoreRendererRegistryBridge',
  16. 'missingFileViewerCoreRendererHandlers',
  17. 'renderFileViewerArchive',
  18. 'renderFileViewerAudio',
  19. 'renderFileViewerCad',
  20. 'renderFileViewerCode',
  21. 'renderFileViewerDataAsset',
  22. 'renderFileViewerDrawing',
  23. 'renderFileViewerEda',
  24. 'renderFileViewerEmail',
  25. 'renderFileViewerEpub',
  26. 'renderFileViewerGeo',
  27. 'renderFileViewerImage',
  28. 'renderFileViewerMarkdown',
  29. 'renderFileViewerModel',
  30. 'renderFileViewerOfd',
  31. 'renderFileViewerOpenDocument',
  32. 'renderFileViewerPdf',
  33. 'parseEdaFile',
  34. 'renderFileViewerPptx',
  35. 'renderFileViewerSpreadsheet',
  36. 'renderFileViewerTypst',
  37. 'renderFileViewerUmd',
  38. 'renderFileViewerVideo',
  39. 'renderFileViewerWordDoc',
  40. 'renderFileViewerWordDocx',
  41. 'createFileViewerRendererDispatcher',
  42. 'createFileRenderHandlerRegistry',
  43. 'normalizeSource',
  44. 'resolveFileViewerRemoteSourcePlan',
  45. 'resolveFileViewerSourceFilename',
  46. 'readFileViewerBuffer',
  47. 'readFileViewerDataUrl',
  48. 'readFileViewerText',
  49. 'sanitizeFileViewerOptions',
  50. 'serializeFileViewerOptions',
  51. 'parseFileViewerOptions',
  52. 'normalizeFileViewerTheme',
  53. 'resolveFileViewerPresentationState',
  54. 'cancelFileViewerPreviewRequest',
  55. 'DEFAULT_FILE_VIEWER_SOURCE_FILENAME',
  56. 'DEFAULT_FILE_VIEWER_STREAMING_PDF_FILENAME',
  57. 'createFileViewerEmptyPreviewState',
  58. 'createFileViewerPreviewRequestResetState',
  59. 'createFileViewerPreviewStateTarget',
  60. 'applyFileViewerPreviewRequestResetState',
  61. 'applyFileViewerEmptyPreviewState',
  62. 'resolveFileViewerFileRefSourcePlan',
  63. 'applyFileViewerPreviewFilenameState',
  64. 'createFileViewerReadPreviewState',
  65. 'applyFileViewerReadPreviewState',
  66. 'applyFileViewerPreviewSourceUrlState',
  67. 'applyFileViewerRenderReadinessState',
  68. 'createFileViewerRenderCompleteState',
  69. 'commitFileViewerEmptyPreviewResetState',
  70. 'commitFileViewerLoadStartState',
  71. 'commitFileViewerPreviewRequestStartState',
  72. 'commitFileViewerRenderCompleteState',
  73. 'commitFileViewerRemoteDownloadState',
  74. 'createFileViewerLoadStartState',
  75. 'createFileViewerRequestScope',
  76. 'createFileViewerSourceLoadingActionHandlers',
  77. 'createFileViewerStreamingPdfPlaceholderFile',
  78. 'finalizeFileViewerPreviewLoadState',
  79. 'hasFileViewerPreviewSource',
  80. 'normalizeFileViewerSourceUrl',
  81. 'resolveFileViewerLoadStartMessage',
  82. 'DEFAULT_FILE_VIEWER_PREVIEW_LOAD_ERROR_LOGGER',
  83. 'FILE_VIEWER_PREVIEW_LOAD_ERROR_PREFIXES',
  84. 'FILE_VIEWER_REMOTE_MISSING_DATA_ERROR_MESSAGE',
  85. 'resolveFileViewerMissingRemoteDataErrorMessage',
  86. 'resolveFileViewerPreviewLoadErrorMessage',
  87. 'resolveFileViewerPageHref',
  88. 'resolveFileViewerPreviewRequestReason',
  89. 'reportFileViewerMissingRemoteData',
  90. 'reportFileViewerPreviewLoadError',
  91. 'runFileViewerLocalFilePreview',
  92. 'runFileViewerPreviewComponentUnmount',
  93. 'runFileViewerPreviewRequest',
  94. 'runFileViewerPreviewSourceChange',
  95. 'runFileViewerRemoteFilePreview',
  96. 'runFileViewerReadAndRenderFile',
  97. 'runFileViewerStreamingPdfPreview',
  98. 'DEFAULT_FILE_VIEWER_DOWNLOAD_FILENAME',
  99. 'DEFAULT_FILE_VIEWER_EXPORT_FILENAME',
  100. 'DEFAULT_FILE_VIEWER_PREVIEW_TITLE',
  101. 'DEFAULT_FILE_VIEWER_DATA_SQL_WASM_URL',
  102. 'DEFAULT_FILE_VIEWER_DOCX_WORKER_JSZIP_PATH',
  103. 'DEFAULT_FILE_VIEWER_DOCX_WORKER_PATH',
  104. 'DEFAULT_FILE_VIEWER_SPREADSHEET_WORKER_PATH',
  105. 'resolveFileViewerDataSqlWasmUrl',
  106. 'resolveFileViewerDocxWorkerJsZipUrl',
  107. 'resolveFileViewerDocxWorkerUrl',
  108. 'resolveFileViewerSpreadsheetWorkerUrl',
  109. 'createFileViewerOriginalSourceState',
  110. 'createFileViewerOriginalSourceStateFromNormalizedSource',
  111. 'resolveFileViewerDisplayFilename',
  112. 'resolveFileViewerOperationFilename',
  113. 'resolveFileViewerOperationAvailability',
  114. 'cloneFileViewerOperationAvailability',
  115. 'resolveVisibleFileViewerToolbar',
  116. 'hasVisibleFileViewerToolbarActions',
  117. 'isFileViewerZoomButtonDisabled',
  118. 'resolveFileViewerToolbarState',
  119. 'createFileViewerPublicApi',
  120. 'buildFileViewerLifecycleContextFromNormalizedSource',
  121. 'buildFileViewerOperationContextFromLifecycleState',
  122. 'createFileViewerLifecycleActions',
  123. 'createFileViewerLifecycleStateController',
  124. 'createFileViewerToolbarActions',
  125. 'createFileViewerToolbarControllerActionHandlers',
  126. 'createFileViewerToolbarZoomSyncSnapshot',
  127. 'dispatchFileViewerLifecycleEvent',
  128. 'dispatchFileViewerOperationContextEvent',
  129. 'dispatchFileViewerOperationAvailabilityChange',
  130. 'dispatchFileViewerZoomChange',
  131. 'emitFileViewerComponentLifecycleEvent',
  132. 'FILE_VIEWER_BEFORE_OPERATION_ERROR_PREFIX',
  133. 'FILE_VIEWER_LIFECYCLE_HOOK_ERROR_MESSAGE_PREFIX',
  134. 'DEFAULT_FILE_VIEWER_LIFECYCLE_HOOK_ERROR_LOGGER',
  135. 'DEFAULT_FILE_VIEWER_OPERATION_ERROR_LOGGER',
  136. 'resolveFileViewerBeforeOperationErrorMessage',
  137. 'resolveFileViewerLifecycleHookErrorMessage',
  138. 'reportFileViewerLifecycleHookError',
  139. 'reportFileViewerOperationError',
  140. 'runFileViewerActiveUnloadComplete',
  141. 'runFileViewerActiveUnloadStart',
  142. 'runFileViewerBeforeOperation',
  143. 'runFileViewerLifecycleHook',
  144. 'createFileViewerLifecycleFacade',
  145. 'runFileViewerToolbarAvailabilitySync',
  146. 'runFileViewerToolbarZoomSync',
  147. 'resolveFileViewerLifecycleFallbackSource',
  148. 'applyFileViewerLoadingState',
  149. 'syncFileViewerLoadingControllerState',
  150. 'runFileViewerLoadingControllerAction',
  151. 'runFileViewerLoadingExtensionSync',
  152. 'createFileViewerLoadingControllerActionHandlers',
  153. 'FILE_VIEWER_OPERATION_ACTION_ERROR_PREFIXES',
  154. 'resolveFileViewerOperationActionErrorMessage',
  155. 'createFileViewerRenderReadinessTarget',
  156. 'createFileViewerRenderSurfaceState',
  157. 'createFileViewerRenderSurfaceStateTarget',
  158. 'disposeActiveFileViewerRendererSession',
  159. 'disposeFileViewerRendererSession',
  160. 'DEFAULT_FILE_VIEWER_RENDER_SESSION_DISPOSE_ERROR_LOGGER',
  161. 'FILE_VIEWER_RENDER_SESSION_DISPOSE_ERROR_MESSAGE',
  162. 'reportFileViewerRenderSessionDisposeError',
  163. 'resolveFileViewerRenderSessionDisposeErrorMessage',
  164. 'applyFileViewerSearchState',
  165. 'cloneFileViewerSearchState',
  166. 'createFileViewerSearchChangeState',
  167. 'dispatchFileViewerSearchChange',
  168. 'dispatchFileViewerLocationChange',
  169. 'applyFileViewerZoomState',
  170. 'createFileViewerZoomChangeState',
  171. 'createFileViewerZoomChangeEmitter',
  172. 'buildFileViewerWatermarkStyle',
  173. 'resolveFileViewerWatermarkPresentationState',
  174. 'createFileViewerWorkerController',
  175. 'resolveFileViewerRendererAssets',
  176. 'DEFAULT_RENDERER_DEFINITIONS',
  177. 'DEFAULT_SUPPORTED_EXTENSIONS'
  178. ]
  179. const requiredTypeExports = [
  180. 'CancelFileViewerPreviewRequestInput',
  181. 'FileViewerOptions',
  182. 'FileViewerReadResult',
  183. 'FileViewerSource',
  184. 'FileViewerLocationLike',
  185. 'FileViewerFileRefSourcePlan',
  186. 'FileViewerFileOperationType',
  187. 'FileViewerOperationActionErrorContext',
  188. 'FileViewerOperationActionErrorFormatter',
  189. 'FileViewerOperationActionErrorPrefixes',
  190. 'FileViewerLoadingControllerActionHandlers',
  191. 'FileViewerLifecycleFacade',
  192. 'FileViewerLocalFilePreviewState',
  193. 'ResolveFileViewerFileRefSourcePlanInput',
  194. 'FileViewerOriginalSourceState',
  195. 'FileViewerInstance',
  196. 'FileViewerLifecycleContext',
  197. 'FileViewerLifecycleHooks',
  198. 'FileViewerLifecycleActions',
  199. 'FileViewerLifecycleComponentEmit',
  200. 'FileViewerLifecycleHookErrorLogger',
  201. 'FileViewerOperationErrorLogger',
  202. 'FileViewerErrorMessageFormatter',
  203. 'FileViewerToolbarActions',
  204. 'FileViewerToolbarControllerActionHandlers',
  205. 'FileViewerToolbarZoomSyncSnapshot',
  206. 'FileViewerZoomButtonAction',
  207. 'FileViewerOperationContext',
  208. 'FileViewerActiveUnloadState',
  209. 'FileViewerBeforeOperation',
  210. 'FileViewerPreviewLoadErrorKind',
  211. 'FileViewerPreviewLoadErrorPrefixes',
  212. 'FileViewerPreviewLoadErrorLogger',
  213. 'FileViewerPreviewRequestResetState',
  214. 'FileViewerPreviewRequestRunState',
  215. 'FileViewerRequestController',
  216. 'FileViewerRequestScope',
  217. 'FileViewerSourceLoadingActionHandlers',
  218. 'FileViewerReadAndRenderFileState',
  219. 'FileViewerReadPreviewState',
  220. 'FileViewerRenderReadinessState',
  221. 'FileViewerRenderCompleteState',
  222. 'FileViewerRemoteDownloadState',
  223. 'FileViewerRemoteFileDownloadInput',
  224. 'FileViewerRemoteFilePreviewErrorKind',
  225. 'FileViewerRemoteFilePreviewState',
  226. 'FileViewerStreamingPdfPreviewState',
  227. 'FileViewerLoadStartState',
  228. 'CreateFileViewerLifecycleActionsInput',
  229. 'CreateFileViewerPublicApiInput',
  230. 'CreateFileViewerToolbarActionsInput',
  231. 'CreateFileViewerToolbarControllerActionHandlersInput',
  232. 'ResolveFileViewerBeforeOperationErrorMessageInput',
  233. 'ResolveFileViewerLifecycleHookErrorMessageInput',
  234. 'ReportFileViewerLifecycleHookErrorInput',
  235. 'ReportFileViewerOperationErrorInput',
  236. 'ResolveFileViewerMissingRemoteDataErrorMessageInput',
  237. 'ResolveFileViewerPreviewLoadErrorMessageInput',
  238. 'ReportFileViewerMissingRemoteDataInput',
  239. 'ReportFileViewerPreviewLoadErrorInput',
  240. 'FileViewerDocumentFeatureActionOptions',
  241. 'CreateFileViewerDocumentFeatureActionsInput',
  242. 'CreateFileViewerDocumentFeatureControllerActionHandlersInput',
  243. 'FileViewerDocumentFeatureControllerActionHandlers',
  244. 'FileViewerPreviewComponentUnmountState',
  245. 'FileViewerRenderSurfaceActionHandlers',
  246. 'FileViewerRenderSurfaceState',
  247. 'FileViewerRenderSurfaceMountContext',
  248. 'FileViewerRenderSurfaceClearState',
  249. 'CreateFileViewerRenderSurfaceActionHandlersInput',
  250. 'CreateFileViewerRenderReadinessTargetInput',
  251. 'CreateFileViewerRenderSurfaceStateTargetInput',
  252. 'FinalizeFileViewerPreviewLoadStateInput',
  253. 'RunFileViewerActiveUnloadCompleteInput',
  254. 'RunFileViewerActiveUnloadStartInput',
  255. 'RunFileViewerToolbarAvailabilitySyncInput',
  256. 'RunFileViewerToolbarZoomSyncInput',
  257. 'RunFileViewerRenderSurfaceClearInput',
  258. 'RunFileViewerRenderSurfaceMountInput',
  259. 'RunFileViewerLocalFilePreviewInput',
  260. 'RunFileViewerPreviewComponentUnmountInput',
  261. 'RunFileViewerPreviewRequestInput',
  262. 'RunFileViewerPreviewSourceChangeInput',
  263. 'RunFileViewerRemoteFilePreviewInput',
  264. 'RunFileViewerReadAndRenderFileInput',
  265. 'RunFileViewerStreamingPdfPreviewInput',
  266. 'CreateFileViewerSourceLoadingActionHandlersInput',
  267. 'CreateFileViewerReadPreviewStateInput',
  268. 'CreateFileViewerRenderCompleteStateInput',
  269. 'CommitFileViewerEmptyPreviewResetStateInput',
  270. 'CommitFileViewerLoadStartStateInput',
  271. 'CommitFileViewerPreviewRequestStartStateInput',
  272. 'CommitFileViewerRenderCompleteStateInput',
  273. 'CommitFileViewerRemoteDownloadStateInput',
  274. 'CreateFileViewerLoadStartStateInput',
  275. 'CreateFileViewerPreviewStateTargetInput',
  276. 'FileViewerMutableAccessor',
  277. 'MutableFileViewerPreviewRequestState',
  278. 'MutableFileViewerPreviewState',
  279. 'MutableFileViewerPreviewFilenameState',
  280. 'MutableFileViewerRenderReadinessState',
  281. 'MutableFileViewerRenderSurfaceState',
  282. 'ResetFileViewerRenderSurfaceInput',
  283. 'ReportFileViewerRenderSessionDisposeErrorInput',
  284. 'FileViewerRenderSessionDisposeErrorLogger',
  285. 'ResolveFileViewerRenderSessionDisposeErrorMessageInput',
  286. 'MutableFileViewerReadPreviewState',
  287. 'MutableFileViewerPreviewSourceUrlState',
  288. 'FileViewerLifecycleStateController',
  289. 'FileViewerOperationAvailability',
  290. 'FileViewerToolbarState',
  291. 'ResolveFileViewerToolbarStateInput',
  292. 'FileViewerSearchState',
  293. 'FileViewerZoomState',
  294. 'FileViewerDocumentAnchor',
  295. 'FileViewerDocumentChunk',
  296. 'ResolveFileViewerOperationFilenameInput',
  297. 'ResolveFileViewerOperationActionErrorMessageInput',
  298. 'DisposeFileViewerRendererSessionOptions',
  299. 'RendererPlugin',
  300. 'RendererSession',
  301. 'RendererRegistry',
  302. 'RendererLoadContext',
  303. 'ViewerCapabilityState',
  304. 'ViewerLifecycleContext',
  305. 'ViewerOperationContext',
  306. 'FileRenderContext',
  307. 'FileRenderExportAdapter',
  308. 'FileViewerSerializableOptions',
  309. 'FileViewerRenderedInstance',
  310. 'FileViewerComponentProps',
  311. 'FileViewerComponentEmits',
  312. 'FileViewerComponentEventMap',
  313. 'FileViewerPublicApi',
  314. 'BuildFileViewerLifecycleContextFromNormalizedSourceInput',
  315. 'BuildFileViewerOperationContextFromLifecycleStateInput',
  316. 'BuildFileViewerLifecycleFacadeLoadStartStateInput',
  317. 'BuildFileViewerLifecycleFacadeRenderCompleteStateInput',
  318. 'CreateFileViewerLifecycleFacadeInput',
  319. 'DispatchFileViewerLifecycleEventInput',
  320. 'DispatchFileViewerOperationContextEventInput',
  321. 'DispatchFileViewerOperationAvailabilityChangeInput',
  322. 'DispatchFileViewerZoomChangeInput',
  323. 'DispatchFileViewerSearchChangeInput',
  324. 'DispatchFileViewerLocationChangeInput',
  325. 'FileViewerPresentationState',
  326. 'ResolveFileViewerPresentationStateInput',
  327. 'FileViewerWorkerController',
  328. 'FileViewerLoadingState',
  329. 'MutableFileViewerLoadingState',
  330. 'RunFileViewerLoadingExtensionSyncInput',
  331. 'MutableFileViewerSearchState',
  332. 'MutableFileViewerZoomState',
  333. 'FileViewerWatermarkPresentationState',
  334. 'FileViewerWatermarkStyle',
  335. ]
  336. const requiredInstanceMethods = [
  337. 'load',
  338. 'destroy',
  339. 'updateOptions',
  340. 'getCapabilities',
  341. 'getRenderer',
  342. 'getSource',
  343. 'download',
  344. 'exportHtml',
  345. 'print',
  346. 'zoomIn',
  347. 'zoomOut',
  348. 'resetZoom',
  349. 'getZoomState',
  350. 'search',
  351. 'nextSearchResult',
  352. 'previousSearchResult',
  353. 'clearSearch',
  354. 'getSearchState',
  355. 'collectDocumentAnchors',
  356. 'getCurrentDocumentAnchor',
  357. 'scrollToDocumentAnchor',
  358. 'scrollToLine',
  359. 'getDocumentTextChunks'
  360. ]
  361. const requiredHeadlessValueExports = [
  362. 'DEFAULT_RENDERER_DEFINITIONS',
  363. 'DEFAULT_SUPPORTED_EXTENSIONS',
  364. 'normalizeSource',
  365. 'readFileViewerBuffer',
  366. 'sanitizeFileViewerOptions',
  367. 'serializeFileViewerOptions',
  368. 'parseFileViewerOptions',
  369. 'resolveFileViewerOperationAvailability',
  370. 'buildFileViewerDocumentTextChunks',
  371. 'createRendererRegistry',
  372. 'createFileViewerRendererDispatcher',
  373. 'createFileRenderHandlerRegistry'
  374. ]
  375. const requiredHeadlessTypeExports = [
  376. 'FileViewerOptions',
  377. 'FileViewerSource',
  378. 'RendererDefinition',
  379. 'RendererRegistry',
  380. 'RendererSession',
  381. 'FileViewerOperationAvailability',
  382. 'FileViewerLifecycleContext',
  383. 'FileViewerSearchState',
  384. 'FileViewerZoomState'
  385. ]
  386. const forbiddenHeadlessApiTokens = [
  387. 'createViewer',
  388. 'coreBrowserRendererHandlers',
  389. 'fileViewerCoreRendererDispatcher',
  390. 'renderFileViewer',
  391. 'createFileViewerDomSearchController',
  392. 'createFileViewerZoomController',
  393. 'collectFileViewerDocumentAnchors',
  394. 'buildFileViewerRenderedHtmlDocument',
  395. 'triggerFileViewerBlobDownload',
  396. 'waitForFileViewerPrintWindowReady'
  397. ]
  398. const requiredBrowserApiTokens = [
  399. 'createViewer',
  400. 'coreBrowserRendererHandlers',
  401. 'renderFileViewerPdf',
  402. 'renderFileViewerWordDocx',
  403. 'renderFileViewerSpreadsheet',
  404. 'createFileViewerDomSearchController',
  405. 'createFileViewerZoomController',
  406. 'buildFileViewerRenderedHtmlDocument'
  407. ]
  408. const forbiddenCoreSourceExtensions = new Set(['.jsx', '.tsx', '.vue', '.svelte'])
  409. const allowedCoreDevDependencies = new Set([
  410. 'typescript',
  411. '@types/three',
  412. '@types/pako'
  413. ])
  414. const allowedCoreRendererDependencies = new Set([])
  415. const forbiddenCoreDependencyPrefixes = [
  416. 'vue',
  417. '@vue/',
  418. '@vitejs/plugin-vue',
  419. 'vue-tsc',
  420. '@lucide/vue',
  421. 'react',
  422. 'react-dom',
  423. 'svelte',
  424. '@file-viewer/',
  425. '@flyfish-group/',
  426. 'file-viewer3'
  427. ]
  428. const forbiddenCoreApiTokens = [
  429. 'mountViewer',
  430. 'ViewerMountOptions',
  431. 'ViewerController',
  432. 'ViewerControllerHandle',
  433. 'createViewerControllerHandle',
  434. 'createFileViewerNativeController',
  435. 'resolveFileViewerNativeLoadSource',
  436. 'FileViewerNativeController',
  437. 'FileViewerNativeFetchFile',
  438. 'FileViewerNativeFetchInput',
  439. 'FileViewerNativeSource'
  440. ]
  441. function isForbiddenCoreDependency(name) {
  442. if (allowedCoreRendererDependencies.has(name)) {
  443. return false
  444. }
  445. return forbiddenCoreDependencyPrefixes.some(prefix =>
  446. prefix.endsWith('/')
  447. ? name.startsWith(prefix)
  448. : name === prefix
  449. )
  450. }
  451. const forbiddenCoreFilenames = new Set([
  452. 'nativeController.ts',
  453. 'frame.ts'
  454. ])
  455. const forbiddenCoreBrowserGlobalPatterns = [
  456. /document\.baseURI/,
  457. /location\.href/,
  458. /typeof\s+location/,
  459. /typeof\s+document/,
  460. ]
  461. function assert(condition, message) {
  462. if (!condition) {
  463. throw new Error(message)
  464. }
  465. }
  466. async function readJson(path) {
  467. return JSON.parse(await readFile(path, 'utf8'))
  468. }
  469. function hasToken(source, token) {
  470. return new RegExp(`\\b${token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\b`).test(source)
  471. }
  472. async function readAllSourceFiles(dir) {
  473. const entries = await readdir(dir, { withFileTypes: true })
  474. const files = []
  475. for (const entry of entries) {
  476. const path = join(dir, entry.name)
  477. if (entry.isDirectory()) {
  478. files.push(...await readAllSourceFiles(path))
  479. continue
  480. }
  481. if (entry.isFile()) {
  482. files.push(path)
  483. }
  484. }
  485. return files
  486. }
  487. function assertCorePackageMetadata(packageJson) {
  488. assert(packageJson.name === '@file-viewer/core', 'Core package name must be @file-viewer/core')
  489. assert(packageJson.private !== true, 'Core package must be publishable')
  490. assert(packageJson.type === 'module', 'Core package must publish as ESM')
  491. assert(packageJson.main === './dist/index.js', 'Core package main entry drifted')
  492. assert(packageJson.module === './dist/index.js', 'Core package module entry drifted')
  493. assert(packageJson.types === './dist/index.d.ts', 'Core package types entry drifted')
  494. assert(packageJson.exports?.['.']?.import === './dist/index.js', 'Core package exports["."].import drifted')
  495. assert(packageJson.exports?.['.']?.types === './dist/index.d.ts', 'Core package exports["."].types drifted')
  496. assert(packageJson.exports?.['./assets']?.import === './dist/assets.js', 'Core package exports["./assets"].import drifted')
  497. assert(packageJson.exports?.['./headless']?.import === './dist/headless.js', 'Core package exports["./headless"].import drifted')
  498. assert(packageJson.exports?.['./headless']?.types === './dist/headless.d.ts', 'Core package exports["./headless"].types drifted')
  499. assert(packageJson.exports?.['./browser']?.import === './dist/browser.js', 'Core package exports["./browser"].import drifted')
  500. assert(packageJson.exports?.['./browser']?.types === './dist/browser.d.ts', 'Core package exports["./browser"].types drifted')
  501. assert(!packageJson.peerDependencies, 'Core package must not have peer dependencies')
  502. assert(!packageJson.optionalDependencies, 'Core package must not have optional dependencies')
  503. for (const dependencyName of Object.keys(packageJson.dependencies || {})) {
  504. assert(
  505. !isForbiddenCoreDependency(dependencyName),
  506. `Core package dependencies must stay framework-neutral; unexpected ${dependencyName}`
  507. )
  508. }
  509. for (const dependencyName of Object.keys(packageJson.devDependencies || {})) {
  510. assert(
  511. allowedCoreDevDependencies.has(dependencyName),
  512. `Core package devDependencies must stay tooling-only; unexpected ${dependencyName}`
  513. )
  514. }
  515. }
  516. function assertCoreTsConfig(tsconfig) {
  517. assert(tsconfig.compilerOptions?.declaration === true, 'Core tsconfig must emit declarations')
  518. assert(tsconfig.compilerOptions?.strict === true, 'Core tsconfig must stay strict')
  519. assert(tsconfig.compilerOptions?.rootDir === 'src', 'Core tsconfig rootDir must be src')
  520. assert(tsconfig.compilerOptions?.outDir === 'dist', 'Core tsconfig outDir must be dist')
  521. assert(
  522. (tsconfig.include || []).includes('src/**/*.ts'),
  523. 'Core tsconfig must include src/**/*.ts'
  524. )
  525. assert(
  526. (tsconfig.include || []).includes('src/**/*.d.ts'),
  527. 'Core tsconfig must include src/**/*.d.ts for renderer ambient module declarations'
  528. )
  529. }
  530. function assertCoreEntrypoint(indexSource) {
  531. for (const exportName of requiredValueExports) {
  532. assert(hasToken(indexSource, exportName), `Core entrypoint must export ${exportName}`)
  533. }
  534. for (const typeName of requiredTypeExports) {
  535. assert(hasToken(indexSource, typeName), `Core entrypoint must export type ${typeName}`)
  536. }
  537. }
  538. function assertCoreHeadlessEntrypoint(headlessSource) {
  539. for (const exportName of requiredHeadlessValueExports) {
  540. assert(hasToken(headlessSource, exportName), `Core headless entrypoint must export ${exportName}`)
  541. }
  542. for (const typeName of requiredHeadlessTypeExports) {
  543. assert(hasToken(headlessSource, typeName), `Core headless entrypoint must export type ${typeName}`)
  544. }
  545. for (const token of forbiddenHeadlessApiTokens) {
  546. assert(
  547. !hasToken(headlessSource, token),
  548. `Core headless entrypoint must not expose browser renderer token ${token}`
  549. )
  550. }
  551. }
  552. function assertCoreBrowserEntrypoint(browserSource, indexSource) {
  553. assert(
  554. /export\s+\*\s+from\s+['"]\.\/index['"]/.test(browserSource),
  555. 'Core browser entrypoint must explicitly re-export the browser-capable root API'
  556. )
  557. for (const token of requiredBrowserApiTokens) {
  558. assert(hasToken(indexSource, token), `Core browser entrypoint must expose ${token} through the root API`)
  559. }
  560. }
  561. function assertCoreInstanceContract(typesSource) {
  562. const match = /export\s+interface\s+FileViewerInstance\s*{([\s\S]*?)\n}/.exec(typesSource)
  563. assert(match, 'Core types must declare FileViewerInstance')
  564. const instanceContract = match[1]
  565. for (const methodName of requiredInstanceMethods) {
  566. assert(
  567. new RegExp(`\\b${methodName}\\s*\\(`).test(instanceContract),
  568. `FileViewerInstance must expose ${methodName}()`
  569. )
  570. }
  571. }
  572. function collectBareImportSpecifiers(source) {
  573. const imports = new Set()
  574. const patterns = [
  575. /\bimport\s+(?:type\s+)?(?:[^'"]+\s+from\s+)?['"]([^'"]+)['"]/g,
  576. /\bexport\s+(?:type\s+)?(?:\*|\{[^}]*})\s+from\s+['"]([^'"]+)['"]/g,
  577. /\bimport\s*\(\s*['"]([^'"]+)['"]\s*\)/g
  578. ]
  579. for (const pattern of patterns) {
  580. let match
  581. while ((match = pattern.exec(source))) {
  582. const specifier = match[1]
  583. if (
  584. !specifier.startsWith('.') &&
  585. !specifier.startsWith('/') &&
  586. !specifier.startsWith('node:')
  587. ) {
  588. imports.add(specifier)
  589. }
  590. }
  591. }
  592. return [...imports]
  593. }
  594. function packageNameFromSpecifier(specifier) {
  595. if (specifier.startsWith('@')) {
  596. return specifier.split('/').slice(0, 2).join('/')
  597. }
  598. return specifier.split('/')[0]
  599. }
  600. async function assertCoreSourceBoundary(files) {
  601. for (const file of files) {
  602. const extension = extname(file)
  603. const relativePath = file.slice(coreDir.length + 1)
  604. assert(
  605. !forbiddenCoreFilenames.has(relativePath),
  606. `Core source must not include wrapper or iframe orchestration file ${relativePath}`
  607. )
  608. assert(
  609. !forbiddenCoreSourceExtensions.has(extension),
  610. `Core source must remain pure TypeScript; unexpected ${relativePath}`
  611. )
  612. if (extension !== '.ts') {
  613. continue
  614. }
  615. const source = await readFile(file, 'utf8')
  616. for (const token of forbiddenCoreApiTokens) {
  617. assert(
  618. !hasToken(source, token),
  619. `Core source must not expose wrapper/browser mount API token ${token}: ${relativePath}`
  620. )
  621. }
  622. for (const pattern of forbiddenCoreBrowserGlobalPatterns) {
  623. assert(
  624. !pattern.test(source),
  625. `Core source must not read browser document/location globals: ${relativePath}`
  626. )
  627. }
  628. const declaredDependencies = new Set(Object.keys(packageJson.dependencies || {}))
  629. for (const specifier of collectBareImportSpecifiers(source)) {
  630. const packageName = packageNameFromSpecifier(specifier)
  631. assert(
  632. !isForbiddenCoreDependency(packageName),
  633. `Core source must not import framework or wrapper package ${specifier}: ${relativePath}`
  634. )
  635. assert(
  636. declaredDependencies.has(packageName),
  637. `Core source imports ${specifier} but ${packageName} is not declared in core dependencies: ${relativePath}`
  638. )
  639. }
  640. }
  641. }
  642. assert(existsSync(coreDir), 'Missing packages/core')
  643. assert(existsSync(coreSrcDir), 'Missing packages/core/src')
  644. const packageJson = await readJson(join(coreDir, 'package.json'))
  645. const tsconfig = await readJson(join(coreDir, 'tsconfig.json'))
  646. const indexSource = await readFile(join(coreSrcDir, 'index.ts'), 'utf8')
  647. const headlessSource = await readFile(join(coreSrcDir, 'headless.ts'), 'utf8')
  648. const browserSource = await readFile(join(coreSrcDir, 'browser.ts'), 'utf8')
  649. const typesSource = await readFile(join(coreSrcDir, 'contracts', 'types.ts'), 'utf8')
  650. const sourceFiles = await readAllSourceFiles(coreSrcDir)
  651. assertCorePackageMetadata(packageJson)
  652. assertCoreTsConfig(tsconfig)
  653. assertCoreEntrypoint(indexSource)
  654. assertCoreHeadlessEntrypoint(headlessSource)
  655. assertCoreBrowserEntrypoint(browserSource, indexSource)
  656. assertCoreInstanceContract(typesSource)
  657. await assertCoreSourceBoundary(sourceFiles)
  658. console.log(
  659. `[core-api] Verified ${requiredValueExports.length} root value exports, ${requiredTypeExports.length} root type exports, ${requiredHeadlessValueExports.length} headless value exports, ${requiredBrowserApiTokens.length} browser API tokens, ${requiredInstanceMethods.length} instance methods, and ${sourceFiles.length} pure TS core files.`
  660. )