From 9a5ee107eff2867d0ed24aa497b188236dd4ce79 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Tue, 21 Jul 2026 15:42:41 -0500 Subject: [PATCH 1/3] fix type exports to work with strictly typed projects --- CHANGELOG.md | 4 +++ eslint.config.mjs | 1 + package.json | 18 ++++++------- scripts/emit-type-shims.js | 42 +++++++++++++++++++++++++++++ src/actions.ts | 2 +- src/annotation.ts | 2 +- src/annotation_operators.ts | 2 +- src/canvas_utils.ts | 2 +- src/configuration.ts | 2 +- src/html_builder.ts | 2 +- src/index.d.ts | 2 +- src/listeners.ts | 2 +- src/overlays.ts | 2 +- src/subtask.ts | 2 +- src/toolbox.ts | 2 +- src/toolbox_items/submit_buttons.ts | 2 +- tests/types/tsconfig.json | 1 + tsconfig.json | 1 + tsconfig.types.json | 16 +++++++++++ 19 files changed, 85 insertions(+), 22 deletions(-) create mode 100644 scripts/emit-type-shims.js create mode 100644 tsconfig.types.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 37f24804..23739dfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented here. ## [unreleased] +## [0.23.7] - July 21st, 2026 +- Ship generated `.d.ts` declarations (`dist/types/`) and a generated type entry (`dist/index.d.ts`) instead of pointing consumers' type resolution at the raw `.ts` source. This stops downstream TypeScript projects from compiling ULabel's source under their own (stricter) `tsconfig`. +- Stop publishing the `src/` directory in the npm package (`files` now ships `dist/` only). + ## [0.23.6] - May 11th, 2026 - Fix `AnnotationList` class ordering to match the `AnnotationID` toolbox item ordering, which is based on the configured class definition order in the subtask's `classes` array - Add local storage of checkbox options for the `AnnotationList` toolbox item diff --git a/eslint.config.mjs b/eslint.config.mjs index f9c13cd5..6337fc1a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,6 +11,7 @@ export default [ "node_modules", "dist", "build", + "scripts", "demo.js", "webpack.config.js", ], diff --git a/package.json b/package.json index 9989afa6..7a210dfd 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,24 @@ { "name": "ulabel", "description": "An image annotation tool.", - "version": "0.23.6", + "version": "0.23.7", "main": "dist/ulabel.min.js", "module": "dist/ulabel.min.js", - "types": "index.d.ts", + "types": "dist/index.d.ts", "files": [ - "dist/", - "src/", - "index.d.ts" + "dist/" ], "exports": { ".": { - "types": "./index.d.ts", + "types": "./dist/index.d.ts", "default": "./dist/ulabel.min.js" }, "./min": { - "types": "./index.d.ts", + "types": "./dist/index.d.ts", "default": "./dist/ulabel.min.js" }, "./debug": { - "types": "./index.d.ts", + "types": "./dist/index.d.ts", "default": "./dist/ulabel.js" } }, @@ -33,8 +31,8 @@ "test:e2e": "playwright test", "demo": "node demo.js", "clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"", - "build": "npm run clean && tsc && webpack --mode production", - "build-dev": "npm run clean && tsc && webpack --mode development", + "build": "npm run clean && tsc && tsc -p tsconfig.types.json && node scripts/emit-type-shims.js && webpack --mode production", + "build-dev": "npm run clean && tsc && tsc -p tsconfig.types.json && node scripts/emit-type-shims.js && webpack --mode development", "build-and-demo": "npm run build && npm run demo", "build-dev-and-demo": "npm run build-dev && npm run demo", "build-and-test": "npm run build && npm run test:both", diff --git a/scripts/emit-type-shims.js b/scripts/emit-type-shims.js new file mode 100644 index 00000000..cd519546 --- /dev/null +++ b/scripts/emit-type-shims.js @@ -0,0 +1,42 @@ +// Build helper: assemble the published type declarations under dist/. +// +// The hand-written root index.d.ts is the source of truth for the public API, +// but it imports the concrete classes from ./src/* (so ULabel's own source +// build type-checks against a single identity). Publishing it as-is would drag +// raw .ts source into consumers, so instead we: +// +// 1. Emit generated declarations from src into dist/types/ (see +// tsconfig.types.json), and +// 2. Write a published entry dist/index.d.ts that is the root index.d.ts with +// its "./src/" imports rewritten to the generated "./types/" siblings. +// +// The generated declarations preserve their source import specifiers, so files +// under dist/types/ still reference "..", "../index" and "../src/index" (which +// point at the root index.d.ts / src bridge in the source tree). We add small +// shims so those specifiers resolve to the published entry within dist/. +// +// eslint-disable-next-line @typescript-eslint/no-require-imports +const fs = require("fs"); +// eslint-disable-next-line @typescript-eslint/no-require-imports +const path = require("path"); + +const root_dir = path.resolve(__dirname, ".."); +const dist_dir = path.join(root_dir, "dist"); +const types_dir = path.join(dist_dir, "types"); +const src_dir = path.join(dist_dir, "src"); + +fs.mkdirSync(types_dir, { recursive: true }); +fs.mkdirSync(src_dir, { recursive: true }); + +// Published entry: root index.d.ts with concrete-class imports repointed at the +// generated declarations. Nothing here resolves back into raw .ts source. +const root_types = fs.readFileSync(path.join(root_dir, "index.d.ts"), "utf8"); +const published_entry = root_types.replace(/\.\/src\//g, "./types/"); +fs.writeFileSync(path.join(dist_dir, "index.d.ts"), published_entry); + +// Shims so generated declarations resolve to the published entry: +// - dist/types/*.d.ts import shared types from ".." and ULabel from "../index". +// - dist/types/**/*.d.ts import ULabel from "../src/index" (and "../../src/index"). +fs.writeFileSync(path.join(types_dir, "index.d.ts"), "export * from \"..\";\n"); +fs.writeFileSync(path.join(src_dir, "index.d.ts"), "export * from \"../index\";\n"); + diff --git a/src/actions.ts b/src/actions.ts index 82fe2c2d..05d41044 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -3,7 +3,7 @@ import type { ULabelAction, ULabelActionRaw, ULabelActionType, -} from ".."; +} from "../index"; // Import ULabel from ../src/index - TypeScript will find ../src/index.d.ts for types // and resolve to ../src/index.js at runtime after compilation import { ULabel } from "../src/index"; diff --git a/src/annotation.ts b/src/annotation.ts index dd41137f..8bb496b5 100644 --- a/src/annotation.ts +++ b/src/annotation.ts @@ -4,7 +4,7 @@ import type { ULabelClassificationPayload, ULabelContainingBox, ULabelSpatialType, -} from ".."; +} from "../index"; import { GeometricUtils } from "./geometric_utils"; import { log_message, LogLevel } from "./error_logging"; diff --git a/src/annotation_operators.ts b/src/annotation_operators.ts index ab7e7750..49e4cb93 100644 --- a/src/annotation_operators.ts +++ b/src/annotation_operators.ts @@ -6,7 +6,7 @@ import type { FilterDistanceOverride, ValidDeprecatedBy, ClassDefinition, -} from ".."; +} from "../index"; // Import ULabel from ../src/index - TypeScript will find ../src/index.d.ts for types import { ULabel } from "../src/index"; diff --git a/src/canvas_utils.ts b/src/canvas_utils.ts index 0830024c..6a51700e 100644 --- a/src/canvas_utils.ts +++ b/src/canvas_utils.ts @@ -3,7 +3,7 @@ * TODO (joshua-dean): Pull the rest of the canvas functions into here */ -import type { ULabel, ULabelSubtasks } from ".."; +import type { ULabel, ULabelSubtasks } from "../index"; import { NONSPATIAL_MODES } from "./annotation"; import { Configuration, DEFAULT_N_ANNOS_PER_CANVAS, TARGET_MAX_N_CANVASES_PER_SUBTASK } from "./configuration"; diff --git a/src/configuration.ts b/src/configuration.ts index 9f4b00df..47473c04 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -6,7 +6,7 @@ import type { RecolorActiveConfig, ULabelSubmitButton, AnnoScalingMode, -} from ".."; +} from "../index"; import { ModeSelectionToolboxItem, ZoomPanToolboxItem, diff --git a/src/html_builder.ts b/src/html_builder.ts index 660a3595..fe2ffd13 100644 --- a/src/html_builder.ts +++ b/src/html_builder.ts @@ -1,4 +1,4 @@ -import type { SliderInfo } from ".."; +import type { SliderInfo } from "../index"; // Import ULabel from ../src/index - TypeScript will find ../src/index.d.ts for types import { ULabel } from "../src/index"; import { Toolbox, ZoomPanToolboxItem } from "./toolbox"; diff --git a/src/index.d.ts b/src/index.d.ts index 2b7bb02a..460cfcc5 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,3 +1,3 @@ // Type declarations for src/index.js // Re-export the ULabel class type from the root index.d.ts -export { ULabel } from ".."; +export { ULabel } from "../index"; diff --git a/src/listeners.ts b/src/listeners.ts index 8a6d32cf..2ab75dab 100644 --- a/src/listeners.ts +++ b/src/listeners.ts @@ -6,7 +6,7 @@ * Long handlers are broken out into separate functions. */ -import type { ULabel } from ".."; +import type { ULabel } from "../index"; import { NightModeCookie } from "./cookies"; import { DELETE_CLASS_ID, DELETE_MODES, NONSPATIAL_MODES } from "./annotation"; import { set_local_storage_item } from "./utilities"; diff --git a/src/overlays.ts b/src/overlays.ts index 75ebc283..4c73ff0a 100644 --- a/src/overlays.ts +++ b/src/overlays.ts @@ -1,4 +1,4 @@ -import type { AbstractPoint, DistanceFromPolylineClasses, Offset } from ".."; +import type { AbstractPoint, DistanceFromPolylineClasses, Offset } from "../index"; import { ULabelAnnotation } from "./annotation"; import { get_annotation_class_id } from "./annotation_operators"; import { log_message, LogLevel } from "./error_logging"; diff --git a/src/subtask.ts b/src/subtask.ts index 01c3eca3..a106fb03 100644 --- a/src/subtask.ts +++ b/src/subtask.ts @@ -1,4 +1,4 @@ -import type { ULabelSpatialType, ClassDefinition, ULabelAction, ULabelActionCandidate } from ".."; +import type { ULabelSpatialType, ClassDefinition, ULabelAction, ULabelActionCandidate } from "../index"; import { ULabelAnnotation } from "./annotation"; /** diff --git a/src/toolbox.ts b/src/toolbox.ts index b3b17685..06c453e1 100644 --- a/src/toolbox.ts +++ b/src/toolbox.ts @@ -3,7 +3,7 @@ import type { FilterDistanceConfig, RecolorActiveConfig, ValidDeprecatedBy, -} from ".."; +} from "../index"; // Import ULabel from ../src/index - TypeScript will find ../src/index.d.ts for types import { ULabel } from "../src/index"; import { DEFAULT_FILTER_DISTANCE_CONFIG } from "./configuration"; diff --git a/src/toolbox_items/submit_buttons.ts b/src/toolbox_items/submit_buttons.ts index b328b48e..2a03b0b7 100644 --- a/src/toolbox_items/submit_buttons.ts +++ b/src/toolbox_items/submit_buttons.ts @@ -1,4 +1,4 @@ -import type { ULabelSubmitButton } from "../.."; +import type { ULabelSubmitButton } from "../../index"; // Import ULabel from ../../src/index - TypeScript will find ../../src/index.d.ts for types import { ULabel } from "../../src/index"; import { ULabelAnnotation, DELETE_MODES, NONSPATIAL_MODES } from "../annotation"; diff --git a/tests/types/tsconfig.json b/tests/types/tsconfig.json index 91aa1874..8a1b9801 100644 --- a/tests/types/tsconfig.json +++ b/tests/types/tsconfig.json @@ -5,6 +5,7 @@ "noEmit": true, "esModuleInterop": true, "skipLibCheck": true, + "strict": true, "types": ["jquery"] }, "files": ["index.test-d.ts"] diff --git a/tsconfig.json b/tsconfig.json index 26cc96a8..f1a23b3b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,5 +12,6 @@ }, "exclude": [ "tests/types", + "dist", ], } \ No newline at end of file diff --git a/tsconfig.types.json b/tsconfig.types.json new file mode 100644 index 00000000..0d5bb21c --- /dev/null +++ b/tsconfig.types.json @@ -0,0 +1,16 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": true, + "emitDeclarationOnly": true, + "outDir": "./dist/types", + "rootDir": "./src" + }, + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "tests", + "dist" + ] +} From d3d6d5c6f3a311f82eca6401b1fa725e8dad5ba8 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Tue, 21 Jul 2026 15:43:36 -0500 Subject: [PATCH 2/3] Bump version --- package-lock.json | 4 ++-- src/version.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0c6e12f7..1a6b629d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ulabel", - "version": "0.23.3", + "version": "0.23.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ulabel", - "version": "0.23.3", + "version": "0.23.7", "license": "MIT", "devDependencies": { "@eslint/config-inspector": "^1.3.0", diff --git a/src/version.js b/src/version.js index 7b86168c..655de88a 100644 --- a/src/version.js +++ b/src/version.js @@ -1 +1 @@ -export const ULABEL_VERSION = "0.23.6"; +export const ULABEL_VERSION = "0.23.7"; From 3827688c5f0b2d5ca4c0c2a262a4d8083f5714d4 Mon Sep 17 00:00:00 2001 From: TrevorBurgoyne Date: Tue, 21 Jul 2026 15:58:06 -0500 Subject: [PATCH 3/3] apply suggestion from review --- eslint.config.mjs | 14 +++++++++++++- scripts/emit-type-shims.js | 5 +---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 6337fc1a..5ee3af48 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,7 +11,6 @@ export default [ "node_modules", "dist", "build", - "scripts", "demo.js", "webpack.config.js", ], @@ -100,4 +99,17 @@ export default [ "@typescript-eslint/no-unused-expressions": "off", }, }, + { + // Node-specific config for build/release tooling scripts + files: ["scripts/**/*.{js,mjs,cjs}"], + languageOptions: { + globals: { + ...globals.node, + }, + }, + rules: { + // Build scripts use CommonJS require() + "@typescript-eslint/no-require-imports": "off", + }, + }, ]; diff --git a/scripts/emit-type-shims.js b/scripts/emit-type-shims.js index cd519546..02abd98a 100644 --- a/scripts/emit-type-shims.js +++ b/scripts/emit-type-shims.js @@ -14,10 +14,8 @@ // under dist/types/ still reference "..", "../index" and "../src/index" (which // point at the root index.d.ts / src bridge in the source tree). We add small // shims so those specifiers resolve to the published entry within dist/. -// -// eslint-disable-next-line @typescript-eslint/no-require-imports + const fs = require("fs"); -// eslint-disable-next-line @typescript-eslint/no-require-imports const path = require("path"); const root_dir = path.resolve(__dirname, ".."); @@ -39,4 +37,3 @@ fs.writeFileSync(path.join(dist_dir, "index.d.ts"), published_entry); // - dist/types/**/*.d.ts import ULabel from "../src/index" (and "../../src/index"). fs.writeFileSync(path.join(types_dir, "index.d.ts"), "export * from \"..\";\n"); fs.writeFileSync(path.join(src_dir, "index.d.ts"), "export * from \"../index\";\n"); -