const COMMONLIB_PATHS = { "@vrtmrz/livesync-commonlib": ["./dist/type-resolution-compat/@vrtmrz/livesync-commonlib/index"], "@vrtmrz/livesync-commonlib/*": ["./dist/type-resolution-compat/@vrtmrz/livesync-commonlib/*"], }; const COMPATIBILITY_PATHS = { ...COMMONLIB_PATHS, "octagonal-wheels": ["./dist/type-resolution-compat/octagonal-wheels/index"], "octagonal-wheels/*": ["./dist/type-resolution-compat/octagonal-wheels/*"], }; function assert(condition: unknown, message: string): asserts condition { if (!condition) { throw new Error(message); } } async function runTypeScript(projectPath: string, repositoryRoot: string): Promise { const executable = `${repositoryRoot}/node_modules/.bin/tsc${Deno.build.os === "windows" ? ".cmd" : ""}`; return await new Deno.Command(executable, { args: ["--pretty", "false", "--project", projectPath], cwd: repositoryRoot, stdout: "piped", stderr: "piped", }).output(); } async function writeProject( projectPath: string, repositoryRoot: string, fixturePath: string, options: { moduleResolution: "Bundler" | "Node10"; paths?: Record; skipLibCheck: boolean; } ): Promise { await Deno.writeTextFile( projectPath, JSON.stringify( { compilerOptions: { allowImportingTsExtensions: true, baseUrl: repositoryRoot, lib: ["ES2022", "DOM"], module: "ESNext", moduleResolution: options.moduleResolution, noEmit: true, ...(options.paths === undefined ? {} : { paths: options.paths }), skipLibCheck: options.skipLibCheck, strict: true, target: "ES2022", types: [], }, files: [fixturePath], }, null, 2 ) ); } async function makeTemporaryDirectory(repositoryRoot: string): Promise { return await Deno.makeTempDir({ dir: repositoryRoot, prefix: ".livesync-type-resolution-", }); } function commandOutput(result: Deno.CommandOutput): string { return new TextDecoder().decode(result.stdout) + new TextDecoder().decode(result.stderr); } Deno.test("legacy resolver reaches representative Commonlib entry points", async () => { const repositoryRoot = await Deno.realPath(new URL("../../", import.meta.url)); const temporaryDirectory = await makeTemporaryDirectory(repositoryRoot); const fixturePath = `${temporaryDirectory}/consumer.ts`; const projectPath = `${temporaryDirectory}/tsconfig.json`; try { await Deno.writeTextFile( fixturePath, [ 'import type { FilePath, ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";', 'import type { UseP2PReplicatorResult } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";', 'import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";', "", "export type Proof = [", " FilePath,", " ObsidianLiveSyncSettings,", " InjectableServiceHub,", " UseP2PReplicatorResult,", "];", "", ].join("\n") ); await writeProject(projectPath, repositoryRoot, fixturePath, { moduleResolution: "Node10", paths: COMMONLIB_PATHS, skipLibCheck: true, }); const result = await runTypeScript(projectPath, repositoryRoot); assert(result.success, `legacy Commonlib entry-point resolution failed:\n${commandOutput(result)}`); } finally { await Deno.remove(temporaryDirectory, { recursive: true }); } }); Deno.test("compatibility mirror preserves representative bundler type facts", async () => { const repositoryRoot = await Deno.realPath(new URL("../../", import.meta.url)); const temporaryDirectory = await makeTemporaryDirectory(repositoryRoot); const fixturePath = `${temporaryDirectory}/consumer.ts`; try { await Deno.writeTextFile( fixturePath, [ 'import type { FilePath, ObsidianLiveSyncSettings } from "@vrtmrz/livesync-commonlib/compat/common/types";', 'import type { UseP2PReplicatorResult } from "@vrtmrz/livesync-commonlib/compat/replication/trystero/UseP2PReplicatorResult";', 'import type { InjectableServiceHub } from "@vrtmrz/livesync-commonlib/compat/services/implements/injectable/InjectableServiceHub";', 'import type { TaggedType } from "octagonal-wheels/common/types";', 'import type { ReactiveValue } from "octagonal-wheels/dataobject/reactive";', 'import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";', "", "type IsAny = 0 extends 1 & T ? true : false;", "type ExpectFalse = T;", "type ExpectTrue = T;", 'type TaggedPath = TaggedType;', "type FilePathIsTyped = ExpectFalse>;", "type FilePathIsString = ExpectTrue;", "type SettingsAreTyped = ExpectFalse>;", 'type SettingsRetainRemoteType = ExpectTrue<"remoteType" extends keyof ObsidianLiveSyncSettings ? true : false>;', "type ServiceHubIsTyped = ExpectFalse>;", 'type ServiceHubRetainsSetting = ExpectTrue<"setting" extends keyof InjectableServiceHub ? true : false>;', "type P2PResultIsTyped = ExpectFalse>;", 'type P2PResultRetainsReplicator = ExpectTrue<"replicator" extends keyof UseP2PReplicatorResult ? true : false>;', "type TaggedPathIsTyped = ExpectFalse>;", "type TaggedPathIsString = ExpectTrue;", "type StringIsNotTaggedPath = ExpectFalse;", "type SimpleStoreIsTyped = ExpectFalse>>;", 'type SimpleStoreRetainsGet = ExpectTrue<"get" extends keyof SimpleStore ? true : false>;', "type ReactiveValueIsTyped = ExpectFalse>>;", 'type ReactiveValueRetainsValue = ExpectTrue<"value" extends keyof ReactiveValue ? true : false>;', "", "export type Proof = [", " FilePathIsTyped,", " FilePathIsString,", " SettingsAreTyped,", " SettingsRetainRemoteType,", " ServiceHubIsTyped,", " ServiceHubRetainsSetting,", " P2PResultIsTyped,", " P2PResultRetainsReplicator,", " TaggedPathIsTyped,", " TaggedPathIsString,", " StringIsNotTaggedPath,", " SimpleStoreIsTyped,", " SimpleStoreRetainsGet,", " ReactiveValueIsTyped,", " ReactiveValueRetainsValue,", "];", "", ].join("\n") ); const projects = [ { name: "published package", paths: undefined }, { name: "compatibility mirror", paths: COMPATIBILITY_PATHS }, ] as const; for (const [index, project] of projects.entries()) { const projectPath = `${temporaryDirectory}/tsconfig-${index}.json`; await writeProject(projectPath, repositoryRoot, fixturePath, { moduleResolution: "Bundler", paths: project.paths, skipLibCheck: true, }); const result = await runTypeScript(projectPath, repositoryRoot); assert(result.success, `${project.name} bundler resolution failed:\n${commandOutput(result)}`); } } finally { await Deno.remove(temporaryDirectory, { recursive: true }); } }); Deno.test("legacy compatibility mirrors preserve Commonlib and Octagonal Wheels type facts", async () => { const repositoryRoot = await Deno.realPath(new URL("../../", import.meta.url)); const temporaryDirectory = await makeTemporaryDirectory(repositoryRoot); const fixturePath = `${temporaryDirectory}/consumer.ts`; const projectPath = `${temporaryDirectory}/tsconfig.json`; try { await Deno.writeTextFile( fixturePath, [ 'import type { FilePath } from "@vrtmrz/livesync-commonlib/compat/common/types";', 'import type { TaggedType } from "octagonal-wheels/common/types";', 'import type { ReactiveValue } from "octagonal-wheels/dataobject/reactive";', 'import type { SimpleStore } from "octagonal-wheels/databases/SimpleStoreBase";', "", "type IsAny = 0 extends 1 & T ? true : false;", "type ExpectFalse = T;", "type ExpectTrue = T;", 'type TaggedPath = TaggedType;', "type FilePathIsTyped = ExpectFalse>;", "type FilePathIsString = ExpectTrue;", "type TaggedPathIsTyped = ExpectFalse>;", "type TaggedPathIsString = ExpectTrue;", "type StringIsNotTaggedPath = ExpectFalse;", "type SimpleStoreIsTyped = ExpectFalse>>;", 'type SimpleStoreRetainsGet = ExpectTrue<"get" extends keyof SimpleStore ? true : false>;', "type ReactiveValueIsTyped = ExpectFalse>>;", 'type ReactiveValueRetainsValue = ExpectTrue<"value" extends keyof ReactiveValue ? true : false>;', "", "export type Proof = [", " FilePathIsTyped,", " FilePathIsString,", " TaggedPathIsTyped,", " TaggedPathIsString,", " StringIsNotTaggedPath,", " SimpleStoreIsTyped,", " SimpleStoreRetainsGet,", " ReactiveValueIsTyped,", " ReactiveValueRetainsValue,", "];", "", ].join("\n") ); await writeProject(projectPath, repositoryRoot, fixturePath, { moduleResolution: "Node10", paths: COMPATIBILITY_PATHS, skipLibCheck: true, }); const result = await runTypeScript(projectPath, repositoryRoot); assert(result.success, `legacy compatibility resolution lost package type facts:\n${commandOutput(result)}`); } finally { await Deno.remove(temporaryDirectory, { recursive: true }); } }); Deno.test("legacy compatibility mirror exposes every Octagonal Wheels typed export entry point", async () => { const repositoryRoot = await Deno.realPath(new URL("../../", import.meta.url)); const temporaryDirectory = await makeTemporaryDirectory(repositoryRoot); const fixturePath = `${temporaryDirectory}/consumer.ts`; const projectPath = `${temporaryDirectory}/tsconfig.json`; try { const packageJson = JSON.parse( await Deno.readTextFile(`${repositoryRoot}/node_modules/octagonal-wheels/package.json`) ) as { exports: Record }; const typedExportSubpaths = Object.entries(packageJson.exports) .filter( ([, definition]) => typeof definition === "object" && definition !== null && "types" in definition && typeof definition.types === "string" ) .map(([subpath]) => subpath); const imports = typedExportSubpaths.map((subpath, index) => { const specifier = subpath === "." ? "octagonal-wheels" : `octagonal-wheels/${subpath.slice(2)}`; return `import type * as PackageExport${index} from ${JSON.stringify(specifier)};`; }); const proofTypes = typedExportSubpaths.map((_, index) => `typeof PackageExport${index}`); await Deno.writeTextFile( fixturePath, [...imports, "", `export type Proof = [${proofTypes.join(", ")}];`, ""].join("\n") ); await writeProject(projectPath, repositoryRoot, fixturePath, { moduleResolution: "Node10", paths: COMPATIBILITY_PATHS, skipLibCheck: true, }); const result = await runTypeScript(projectPath, repositoryRoot); assert(result.success, `legacy resolution failed for an Octagonal Wheels export:\n${commandOutput(result)}`); } finally { await Deno.remove(temporaryDirectory, { recursive: true }); } });