import { readFileSync } from "node:fs"; import { join } from "node:path"; import ts from "typescript"; import { describe, expect, expectTypeOf, it } from "vitest"; import * as ToolcraftRuntime from "../index"; import * as ToolcraftProductModules from "./public"; import { canvasEditingModule, defineToolcraft, imageExportModule, layersModule, mediaSourceModule, model3dModule, spatialViewModule, svgExportModule, timelineModule, videoExportModule, } from "../index"; import type { ResolvedCapabilityProvider, ResolvedToolcraftAppSchema, ResolvedProductModule, ResolvedProductModulePlan, ToolcraftProductBase, ToolcraftProductCapabilityId, ToolcraftProductDefinition, ToolcraftProductIntegrationPortId, ToolcraftProductIntegrationPortRequirement, ToolcraftProductModuleDefinition, ToolcraftProductModuleId, } from "../index"; import { getModuleSpecifiers } from "./testing/module-import-boundary-test-support"; function getNamedExportInventory(fileName: string, source: string) { const sourceFile = ts.createSourceFile( fileName, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS, ); return sourceFile.statements .flatMap((statement) => { if (!ts.isExportDeclaration(statement)) { return ts.canHaveModifiers(statement) && ts .getModifiers(statement) ?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ? [`unsupported-declaration:${ts.SyntaxKind[statement.kind]}`] : []; } if ( statement.exportClause === undefined || !ts.isNamedExports(statement.exportClause) ) { const specifier = statement.moduleSpecifier !== undefined && ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "local"; return [`unsupported-export:${specifier}`]; } if (statement.exportClause.elements.length === 0) { return []; } const moduleSpecifier = statement.moduleSpecifier !== undefined && ts.isStringLiteral(statement.moduleSpecifier) ? statement.moduleSpecifier.text : "local"; return statement.exportClause.elements.map((element) => [ statement.isTypeOnly || element.isTypeOnly ? "type" : "value", element.name.text, element.propertyName?.text ?? element.name.text, moduleSpecifier, ].join(":"), ); }) .sort(); } const publicFactories = { canvasEditingModule, imageExportModule, layersModule, mediaSourceModule, model3dModule, spatialViewModule, svgExportModule, timelineModule, videoExportModule, }; // @ts-expect-error Structural module records remain private. type PrivateModuleRecord = import("./public").ToolcraftProductModuleRecord; type PrivateModuleContribution = // @ts-expect-error Module contributions remain private implementation details. import("./public").ToolcraftProductModuleContribution; type PrivateModuleDefinitionInput = // @ts-expect-error Definition-constructor input remains private. import("./public").ToolcraftProductModuleDefinitionInput; type PrivateResolvedProductModules = // @ts-expect-error Resolver result wrappers remain private. import("./public").ResolvedToolcraftProductModules; // @ts-expect-error The old complete authored schema is not public. type LegacyToolcraftAppSchema = import("../index").ToolcraftAppSchema; type PrivateMaterializedAppSchema = // @ts-expect-error The intermediate materialized schema is module-private. import("../index").ToolcraftMaterializedAppSchema; void (null as unknown as PrivateModuleRecord); void (null as unknown as PrivateModuleContribution); void (null as unknown as PrivateModuleDefinitionInput); void (null as unknown as PrivateResolvedProductModules); void (null as unknown as LegacyToolcraftAppSchema); void (null as unknown as PrivateMaterializedAppSchema); describe("Toolcraft product module public API", () => { it("publishes the product-only defineToolcraft contract and embedded plan", () => { const base: ToolcraftProductBase = { canvas: { enabled: true }, identity: { id: "public-product", title: "Public Product" }, panels: { controls: { sections: [], title: "Public Product" } }, }; const definition: ToolcraftProductDefinition = { base, modules: [imageExportModule()], }; const schema: ResolvedToolcraftAppSchema = defineToolcraft(definition); expect(schema.modulePlan.capabilities).toEqual([ { capabilityId: "artifact.image-export", moduleId: "image-export" }, ]); expect(Object.isFrozen(schema.modulePlan)).toBe(true); expect(Object.isFrozen(schema)).toBe(true); if (false) { // @ts-expect-error The complete authored schema is no longer a public input. defineToolcraft({ canvas: { enabled: true }, panels: {} }); defineToolcraft({ // @ts-expect-error Product identity is mandatory. base: { canvas: { enabled: true }, panels: {} }, modules: [], }); defineToolcraft({ base: { ...base, // @ts-expect-error Standard export settings are module-owned. export: { png: {} }, }, modules: [], }); defineToolcraft({ base: { ...base, settingsTransfer: { // @ts-expect-error Settings transfer cannot author a second identity. appId: "legacy", }, }, modules: [], }); defineToolcraft({ base: { ...base, // @ts-expect-error Layers are module-owned. panels: { layers: true }, }, modules: [], }); defineToolcraft({ base: { ...base, // @ts-expect-error Timeline is module-owned. panels: { timeline: true }, }, modules: [], }); defineToolcraft({ base, // @ts-expect-error Copied module records lose opaque factory identity. modules: [{ ...imageExportModule() }], }); } }); it("publishes the nine opaque factories from the package root", () => { expect(Object.keys(ToolcraftProductModules).sort()).toEqual([ "canvasEditingModule", "imageExportModule", "layersModule", "mediaSourceModule", "model3dModule", "spatialViewModule", "svgExportModule", "timelineModule", "videoExportModule", ]); expect(Object.keys(publicFactories).sort()).toEqual([ "canvasEditingModule", "imageExportModule", "layersModule", "mediaSourceModule", "model3dModule", "spatialViewModule", "svgExportModule", "timelineModule", "videoExportModule", ]); for (const factory of Object.values(publicFactories)) { expect(factory).toEqual(expect.any(Function)); } expect(Object.isFrozen(imageExportModule())).toBe(true); expect([ canvasEditingModule().id, imageExportModule().id, layersModule().id, mediaSourceModule().id, model3dModule().id, spatialViewModule().id, svgExportModule().id, timelineModule({ mode: "playback" }).id, videoExportModule().id, ]).toEqual([ "canvas-editing", "image-export", "layers", "media-source", "model-3d", "spatial-view", "svg-export", "timeline", "video-export", ]); }); it("keeps the public barrel and root route on an exact named-export inventory", () => { const publicSource = readFileSync( join(process.cwd(), "src/modules/public.ts"), "utf8", ); expect(getNamedExportInventory("public.ts", publicSource)).toEqual([ "type:ResolvedCapabilityProvider:ResolvedCapabilityProvider:./contract/module-plan", "type:ResolvedModuleOrigin:ResolvedModuleOrigin:./contract/module-plan", "type:ResolvedProductModule:ResolvedProductModule:./contract/module-plan", "type:ResolvedProductModulePlan:ResolvedProductModulePlan:./contract/module-plan", "type:ToolcraftProductCapabilityId:ToolcraftProductCapabilityId:./contract/capability", "type:ToolcraftProductIntegrationPortId:ToolcraftProductIntegrationPortId:./contract/integration-port", "type:ToolcraftProductIntegrationPortRequirement:ToolcraftProductIntegrationPortRequirement:./contract/integration-port", "type:ToolcraftProductModuleDefinition:ToolcraftProductModuleDefinition:./contract/module-definition", "type:ToolcraftProductModuleId:ToolcraftProductModuleId:./contract/capability", "value:canvasEditingModule:canvasEditingModule:./built-ins/canvas-editing/canvas-editing-module", "value:imageExportModule:imageExportModule:./built-ins/image-export/image-export-module", "value:layersModule:layersModule:./built-ins/layers/layers-module", "value:mediaSourceModule:mediaSourceModule:./built-ins/media-source/media-source-module", "value:model3dModule:model3dModule:./built-ins/model-3d/model-3d-module", "value:spatialViewModule:spatialViewModule:./built-ins/spatial-view/spatial-view-module", "value:svgExportModule:svgExportModule:./built-ins/svg-export/svg-export-module", "value:timelineModule:timelineModule:./built-ins/timeline/timeline-module", "value:videoExportModule:videoExportModule:./built-ins/video-export/video-export-module", ]); const rootSource = readFileSync( join(process.cwd(), "src/index.ts"), "utf8", ); expect( getModuleSpecifiers("index.ts", rootSource).filter((specifier) => specifier.startsWith("./modules/"), ), ).toEqual(["./modules/public"]); }); it("fails the source inventory closed for stars, aliases, and local exports", () => { expect( getNamedExportInventory( "fixture.ts", [ 'export type * from "./contract/capability";', 'export { videoExportModule as imageExportModule } from "./built-ins/video-export/video-export-module";', "export type LocalLeak = string;", ].join("\n"), ), ).toEqual([ "unsupported-declaration:TypeAliasDeclaration", "unsupported-export:./contract/capability", "value:imageExportModule:videoExportModule:./built-ins/video-export/video-export-module", ]); }); it("publishes only declaration and resolved-plan types", () => { expectTypeOf().toMatchTypeOf<{ id: ToolcraftProductModuleId; provides: readonly ToolcraftProductCapabilityId[]; }>(); expectTypeOf().toEqualTypeOf< readonly ResolvedProductModule[] >(); expectTypeOf().toEqualTypeOf< readonly ResolvedCapabilityProvider[] >(); expectTypeOf< ToolcraftProductIntegrationPortRequirement["id"] >().toEqualTypeOf(); }); it("keeps catalogs, resolution, constructors, identity assertions, and test helpers private", () => { for (const internalName of [ "assertToolcraftProductModuleDefinition", "assertToolcraftProductModuleDefinitions", "createBuiltInToolcraftProductModuleDefinition", "createTestToolcraftProductModuleDefinition", "getBuiltInToolcraftProductModule", "getToolcraftModulePortErrors", "resolveToolcraftModuleContributions", "resolveToolcraftModuleDependencies", "resolveToolcraftProductModules", ]) { expect(ToolcraftRuntime).not.toHaveProperty(internalName); } if (false) { // @ts-expect-error Resolution remains private to the runtime. void ToolcraftRuntime.resolveToolcraftProductModules; // @ts-expect-error Definition constructors remain private to the runtime. void ToolcraftRuntime.createBuiltInToolcraftProductModuleDefinition; } }); });