From 2129b0b881c2aa6e35b70068a2ef6c341ce82a58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Kuna?= <1282324+codecalm@users.noreply.github.com> Date: Mon, 14 Sep 2026 20:13:06 +0200 Subject: [PATCH] Check the open source attribution list against what ships (#3058) --- .build/check-open-source.ts | 115 +++++++++++++++++ .changeset/check-open-source.md | 5 + package.json | 3 +- shared/data/open-source.json | 220 ++++++++++++++++++++------------ 4 files changed, 258 insertions(+), 85 deletions(-) create mode 100644 .build/check-open-source.ts create mode 100644 .changeset/check-open-source.md diff --git a/.build/check-open-source.ts b/.build/check-open-source.ts new file mode 100644 index 000000000..773615b71 --- /dev/null +++ b/.build/check-open-source.ts @@ -0,0 +1,115 @@ +#!/usr/bin/env node +// Keeps shared/data/open-source.json — the attribution table on the docs page +// /ui/getting-started/references — in step with the libraries Tabler actually +// ships. The list is hand-written, so without this check a new dependency +// lands unattributed and a removed one lingers as a claim that is no longer true. +// +// Two sources say what ships: +// - core/libs.json: libraries copied to dist/libs or loaded from a CDN +// - bare imports in core/js/src/**: packages bundled into tabler.js +// +// An attribution names its library with "npm" (a package) or "lib" (a key in +// core/libs.json, for a library loaded straight from a CDN). Vendored code has +// neither, so it is listed in BUNDLED_SOURCE; a libs.json entry that is not +// open source at all is listed in NOT_OPEN_SOURCE. +// Run: pnpm run check:open-source + +import { readFileSync } from 'node:fs' +import { join, dirname, relative } from 'node:path' +import { fileURLToPath } from 'node:url' +import { sync } from 'glob' + +const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '..') + +// Attributed without a package: the source lives in this repo. +const BUNDLED_SOURCE = new Set(['Bootstrap']) + +// Shipped, but nothing to attribute: a proprietary service loaded by url. +const NOT_OPEN_SOURCE = new Set(['google-maps']) + +type Attribution = { name: string; npm?: string; lib?: string; url: string; description: string; license: string } +type Lib = { npm?: string } + +const attributionsPath = join(repoRoot, 'shared', 'data', 'open-source.json') +const attributions = JSON.parse(readFileSync(attributionsPath, 'utf8')) as Attribution[] +const libs = JSON.parse(readFileSync(join(repoRoot, 'core', 'libs.json'), 'utf8')) as Record + +// A bare import names a package: `imask`, `@popperjs/core`, `plyr/dist/plyr.js`. +// Scoped packages keep two segments, everything else keeps the first. +const packageOf = (specifier: string): string => { + const parts = specifier.split('/') + return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : (parts[0] ?? specifier) +} + +// A library is identified by its package name, or by its libs.json key when it +// has none, so the two kinds never collide. +const shipped = new Map() +const ship = (id: string, source: string): void => { + if (!shipped.has(id)) { + shipped.set(id, source) + } +} + +for (const [key, lib] of Object.entries(libs)) { + if (NOT_OPEN_SOURCE.has(key)) { + continue + } + + ship(lib.npm ? `npm:${lib.npm}` : `lib:${key}`, `core/libs.json (${key})`) +} + +for (const file of sync(join(repoRoot, 'core', 'js', 'src', '**', '*.ts'))) { + const source = readFileSync(file, 'utf8') + for (const match of source.matchAll(/^\s*import\s[^'"]*from\s+['"]([^'".][^'"]*)['"]/gm)) { + const specifier = match[1] ?? '' + if (!specifier.startsWith('node:')) { + ship(`npm:${packageOf(specifier)}`, relative(repoRoot, file)) + } + } +} + +const errors: string[] = [] +const attributed = new Map() + +for (const entry of attributions) { + if (!entry.name || !entry.url || !entry.description || !entry.license) { + errors.push(`${entry.name || '(unnamed entry)'}: name, url, description and license are all required`) + continue + } + + const id = entry.npm ? `npm:${entry.npm}` : entry.lib ? `lib:${entry.lib}` : null + + if (!id) { + if (!BUNDLED_SOURCE.has(entry.name)) { + errors.push(`${entry.name}: add an "npm" or "lib" field, or add the name to BUNDLED_SOURCE in this script`) + } + + continue + } + + if (attributed.has(id)) { + errors.push(`${entry.name}: ${id} is attributed twice`) + continue + } + + attributed.set(id, entry) + + if (!shipped.has(id)) { + errors.push(`${entry.name}: ${id} is not shipped any more — remove the entry`) + } +} + +for (const [id, source] of shipped) { + if (!attributed.has(id)) { + errors.push(`${id} ships from ${source} but has no entry in shared/data/open-source.json`) + } +} + +if (errors.length > 0) { + console.error(`Found ${errors.length} attribution problem(s):\n`) + for (const error of errors.sort()) console.error(` ${error}`) + console.error('\nThe list renders on the docs page /ui/getting-started/references.') + process.exit(1) +} + +console.log(`OK — ${attributions.length} attributions, ${shipped.size} shipped packages, nothing missing or stale.`) diff --git a/.changeset/check-open-source.md b/.changeset/check-open-source.md new file mode 100644 index 000000000..09cc33fb4 --- /dev/null +++ b/.changeset/check-open-source.md @@ -0,0 +1,5 @@ +--- +"@tabler/docs": patch +--- + +Added a `check:open-source` gate and the five missing attributions, including Popper, which ships inside `tabler.js`. diff --git a/package.json b/package.json index bceb152a1..a327fa33a 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,10 @@ "check:tokens": "tsx .build/generate-tokens.ts check", "check:docs-links": "tsx .build/check-docs-links.ts", "check:css-vars": "tsx .build/check-css-vars.ts", + "check:open-source": "tsx .build/check-open-source.ts", "check:html": "tsx .build/check-html.ts", "check:markup-classes": "tsx .build/check-markup-classes.ts", - "lint": "pnpm run lint:md && pnpm run check:docs-links && pnpm run lint:prettier && pnpm run lint:scss && pnpm run check:css-vars && pnpm run check:tokens", + "lint": "pnpm run lint:md && pnpm run check:docs-links && pnpm run lint:prettier && pnpm run lint:scss && pnpm run check:css-vars && pnpm run check:open-source && pnpm run check:tokens", "lint:fix": "pnpm run lint:md:fix && pnpm run lint:scss:fix && pnpm run format:prettier", "lint:scss": "pnpm --filter @tabler/core lint", "lint:scss:fix": "pnpm --filter @tabler/core lint:scss:fix", diff --git a/shared/data/open-source.json b/shared/data/open-source.json index f47284e20..7b2e03360 100644 --- a/shared/data/open-source.json +++ b/shared/data/open-source.json @@ -1,4 +1,11 @@ [ + { + "name": "ApexCharts", + "npm": "apexcharts", + "url": "https://apexcharts.com/", + "description": "Modern charting library offering interactive and responsive charts.", + "license": "MIT" + }, { "name": "Bootstrap", "url": "https://getbootstrap.com/", @@ -6,105 +13,150 @@ "license": "MIT" }, { - "name": "IMask", - "url": "https://imask.js.org/", - "description": "Vanilla JavaScript input masking library with support for various mask types and dynamic behavior.", - "license": "MIT" - }, - { - "name": "ApexCharts", - "url": "https://apexcharts.com/", - "description": "Modern charting library offering interactive and responsive charts.", - "license": "MIT" - }, - { - "name": "noUiSlider", - "url": "https://refreshless.com/nouislider/", - "description": "Lightweight JavaScript range slider with multi-touch support and extensive customization options.", - "license": "MIT" - }, - { - "name": "List.js", - "url": "https://listjs.com/", - "description": "Adds search, sort, and flexibility to tables, lists, and various HTML elements.", - "license": "MIT" - }, - { - "name": "Masonry", - "url": "https://masonry.desandro.com/", - "description": "JavaScript grid layout library that arranges elements optimally based on available vertical space.", - "license": "MIT" - }, - { - "name": "Mapbox GL JS", - "url": "https://docs.mapbox.com/mapbox-gl-js/", - "description": "JavaScript library for interactive, customizable vector maps on the web.", - "license": "BSD-3-Clause" - }, - { - "name": "Litepicker", - "url": "https://github.com/wakirin/Litepicker", - "description": "Lightweight, no-dependency date range picker.", - "license": "MIT" - }, - { - "name": "Tom Select", - "url": "https://tom-select.js.org/", - "description": "Dynamic and framework-agnostic UI control with autocomplete and keyboard navigation.", + "license": "Apache-2.0" + }, + { + "name": "Typed.js", + "npm": "typed.js", + "url": "https://mattboldt.com/demos/typed-js/", + "description": "JavaScript library that types out sentences in a human-like manner.", + "license": "MIT" } ]