From 9735449330fbc644d9897a57c3856fcbf93ce1a2 Mon Sep 17 00:00:00 2001 From: Dongdong Kong Date: Mon, 24 Aug 2026 10:00:06 +0800 Subject: [PATCH 1/5] feat (npm_package): pack core and map into npm package --- package-lock.json | 772 ++++++++++++++++++++++++++++++++++- packages/core/package.json | 16 +- packages/map/package.json | 21 +- packages/map/src/headless.ts | 138 +++++++ 4 files changed, 941 insertions(+), 6 deletions(-) create mode 100644 packages/map/src/headless.ts diff --git a/package-lock.json b/package-lock.json index bd5de42ff..dbd1b95b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6943,6 +6943,36 @@ "generate_style": "src/cli.ts" } }, + "node_modules/@quansync/fs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@quansync/fs/-/fs-1.0.0.tgz", + "integrity": "sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, + "node_modules/@quansync/fs/node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/@radix-ui/number": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.3.tgz", @@ -12225,6 +12255,385 @@ } } }, + "node_modules/@yuku-codegen/binding-android-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-android-arm64/-/binding-android-arm64-0.8.7.tgz", + "integrity": "sha512-C/0zV5IhgVdYhGJTwrY0v8dknxlhiKwtVJkMUaexu9/QvRmzlV4vfU3hZlUSgqc2BxQHntL1mCVbDq8j0FRFDw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@yuku-codegen/binding-darwin-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-darwin-arm64/-/binding-darwin-arm64-0.8.7.tgz", + "integrity": "sha512-/u+REDMI4a0/lsJXTM4c53/w31OGZLOReZIyg62uhgLs0kc8NHsj/nOcxTdlQjq5gi0zhdkccD9LaLTXcdzPvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@yuku-codegen/binding-darwin-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-darwin-x64/-/binding-darwin-x64-0.8.7.tgz", + "integrity": "sha512-sMMzFOwCo4WXR+/6zIBThOocSC50iIIZZdfiIDbaLvj0Ax/rWt/iavyfEAqajyvzydLyCqR/ZItdLWSRlu1umw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@yuku-codegen/binding-freebsd-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-freebsd-x64/-/binding-freebsd-x64-0.8.7.tgz", + "integrity": "sha512-MpdpKXix9P+Y1rKgjvcNeNtGjXeL1CmttNhYINrWls8kRpm4xM/oBGTmn6w7to8lAlwj5jm8q03dQPl5mRv4Qw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@yuku-codegen/binding-linux-arm-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm-gnu/-/binding-linux-arm-gnu-0.8.7.tgz", + "integrity": "sha512-rr1srFLlPAmC1vtxfc9C1YLDe3iH09YjfSeeIidBqKhzx1MATjOAq4mjlRUOnhr/L27MotWIYOFKwVsd5JZFOg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-codegen/binding-linux-arm-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm-musl/-/binding-linux-arm-musl-0.8.7.tgz", + "integrity": "sha512-eAufXh8qBRpiSO6ueaMDL+yyoXIGLhpUce72YbcACtZU2qhExwBIJyEtQf5kH2Ki0X2aqkSjSfog4OPtKXan3Q==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-codegen/binding-linux-arm64-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.8.7.tgz", + "integrity": "sha512-gw4w6wPoHObBrdIC4duVWLmJOvpdE25j5D7yrM5mACNlK4klRz/lv8hK+ssQk9EJHBgZjSaqZIJVFgqNYbfv7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-codegen/binding-linux-arm64-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.8.7.tgz", + "integrity": "sha512-L68N6Y4XkqcIaKo3Ra88JEvBEH4AHff44A4INcrxeVWZ8CZtu2tCpfVxe3hR8qQQMcvBSQlDn24KpkCKEhVvfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-codegen/binding-linux-x64-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.8.7.tgz", + "integrity": "sha512-dzyAbltJmf3Cqlb8HcFuYIf5Yn0fl1vTr3XJ9HiVNNvOlhqPSArOqtw9vI1p6/VTXTjrMLXlU+s+/kNHiIy/Cw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-codegen/binding-linux-x64-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-linux-x64-musl/-/binding-linux-x64-musl-0.8.7.tgz", + "integrity": "sha512-Otw4MH3404q0Bbvl+YTdW9aoUV5vXmUw8260bWvt1XlaoIX/ceSgI4ygheRDMfBPoHt6FDayQaO9OLVUZAgkFA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-codegen/binding-win32-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-win32-arm64/-/binding-win32-arm64-0.8.7.tgz", + "integrity": "sha512-qo/jyrzryiBuKEsFiuWaBCBe3tRMynQ0qFWFgOEjcCMQeZfBm+wKiVEUEFXXLc7bh8YezguAWp0Mtnhq4ARNyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yuku-codegen/binding-win32-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-codegen/binding-win32-x64/-/binding-win32-x64-0.8.7.tgz", + "integrity": "sha512-D5lDsVDx6m00E6bWySlWdH72Ca4TPSaphDqB6QjU6MpuNLIJqoGoatYyq2rOmBE8Zv/kunot/o58KGL03P3eiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yuku-parser/binding-android-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-android-arm64/-/binding-android-arm64-0.8.7.tgz", + "integrity": "sha512-eGKYiUDX7Y0V7tDTmg+JTVnXnjMqfXXsorZ+EDf5kxwchQ3Or1HS14MzI2fw+jFhHR85fCWt+mtX33Yao73hIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@yuku-parser/binding-darwin-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.8.7.tgz", + "integrity": "sha512-Re0RHelKLnjEURulY2/KxW+Ngb8zuNA4BRZuMwgGQNzVumT6u4U2N2hc01oeYVNVof0i7GrXE4UCNBgbpRRnjQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@yuku-parser/binding-darwin-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-darwin-x64/-/binding-darwin-x64-0.8.7.tgz", + "integrity": "sha512-Hn8DROtQkjlA1ACbPgj4a7eP9IuVOI504oiTwpkWPbpaDWD9KdmnVYCqW+1LfenNK/g7O9NhWGpXEdaCNX7lIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@yuku-parser/binding-freebsd-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.8.7.tgz", + "integrity": "sha512-bAP2OV8wRuzplX/jYxv9+vvqQT8JxyNphI8fLfXGL054Xs+4/J5u33cIm3y4rxY8rdoLmmdiJs2Tq7r7lrDRfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@yuku-parser/binding-linux-arm-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm-gnu/-/binding-linux-arm-gnu-0.8.7.tgz", + "integrity": "sha512-kTYwJQQgmZeAWdDIWabiReIZMpmfLueIj1tCmjStUtFGhR1Z0qwxonKVfUC4N7h/VhGGzLZ//7O1kgt1QKqgCg==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-arm-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm-musl/-/binding-linux-arm-musl-0.8.7.tgz", + "integrity": "sha512-uL4jE8HPT2BLlxAXyD10LqgPuXa9eDa0BKpCdSANmzIJghq/2eZo3/gQNtaxPZMupWoxjYzSad9IXrwu7aYPXQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-arm64-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.8.7.tgz", + "integrity": "sha512-3gVN4pWSKZmXiNX7cU164dR9MPvesCHnlH6nPfpK+yQsCuYphjKKplcb4SnZBrhiqmXbgb2HR0c2TS0IZUPhgA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-arm64-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.8.7.tgz", + "integrity": "sha512-S0mwfEjoLpxzXeZw802Wa4RaELsQiPtWqG6INcy8j4GtvNFtl4LCX3eGO1XLn9pyLAISLzTRyU3zUCBUPin8lg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-x64-gnu": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.8.7.tgz", + "integrity": "sha512-lnbWdPmerE5D1uH1G4IEZKnPzCrWCStRGrtgpSIe1RibAo5bZIjDbbbPYXmMHCEh4F+x/JaJpElh26a3r+BPbg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-linux-x64-musl": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.8.7.tgz", + "integrity": "sha512-769uwndMvMzUvATWbAcEvyLHKA+DzhHSCl/obBUrRdYfRo26yxui6S8y3z7uJ+Naup7UKrDxrpK7OnQkxkl9KQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@yuku-parser/binding-win32-arm64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-win32-arm64/-/binding-win32-arm64-0.8.7.tgz", + "integrity": "sha512-mEB/9PlaAkisJ6KWGz0zvywXoU6+80dTlR2LwS7s/jcXXoU6fm2+sitBZXtqu3+Q4DcDgPxM45uWMCzPs0TSRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yuku-parser/binding-win32-x64": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-parser/binding-win32-x64/-/binding-win32-x64-0.8.7.tgz", + "integrity": "sha512-8vNB2DP0ou61nGb8tc/qfi41gfyDXz1MHr2zqL3nR+cJ6CEbiuWV/l/a/vv151gCgiZLLAyGkQGENpozdg716w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@yuku-toolchain/types": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/@yuku-toolchain/types/-/types-0.8.7.tgz", + "integrity": "sha512-2Z53dNxAJL6UvFoIrDZvYf3zlO8s4VJK4O2hhaB4mXVwwpX/7ajtss3cmfqKvamlNLWyt9FSWs4eoYdlbxpnHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@zarrita/storage": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@zarrita/storage/-/storage-0.2.0.tgz", @@ -12457,6 +12866,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.3.1.tgz", + "integrity": "sha512-BJ8/l4R5LRE7hW9WdSuGYrLSHi2ynxeFpDFbH0K/CgNeY/tyhk+vO6TYxXC5r5CpUhNVX310xzPsN/H9lCdfOA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/anynum": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.1.tgz", @@ -12979,6 +13398,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cac/-/cac-7.0.0.tgz", + "integrity": "sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/call-bind": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", @@ -13687,6 +14116,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", + "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/delaunator": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", @@ -13880,6 +14316,27 @@ "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", "license": "Apache-2.0" }, + "node_modules/dts-resolver": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dts-resolver/-/dts-resolver-3.0.0.tgz", + "integrity": "sha512-1T1f+z+4tl9XD+m+0HBgWoL/nm0bOIffyWaUuUSBlFg/86IWvfx+wjNaO/ybU0AJzG9/Mi5hBUgGV6zCmWEN7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "oxc-resolver": ">=11.0.0" + }, + "peerDependenciesMeta": { + "oxc-resolver": { + "optional": true + } + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -13938,6 +14395,16 @@ "dev": true, "license": "ISC" }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -15360,6 +15827,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "5.0.0-beta.5", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-5.0.0-beta.5.tgz", + "integrity": "sha512-/6gFNr0N04nob252sTQxyFLi3eKFRqIg1I87YcqAMT1i6SQrSF6KujUEQrtrjMV0H/eejTCltLdDSTEMzHbnsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "engines": { + "node": ">=20.20.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/gl-matrix": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/gl-matrix/-/gl-matrix-3.4.4.tgz", @@ -15992,6 +16475,13 @@ "node": ">=16.9.0" } }, + "node_modules/hookable": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-6.1.1.tgz", + "integrity": "sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==", + "dev": true, + "license": "MIT" + }, "node_modules/html-escaper": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-3.0.3.tgz", @@ -16308,6 +16798,19 @@ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", "license": "MIT" }, + "node_modules/import-without-cache": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/import-without-cache/-/import-without-cache-0.4.0.tgz", + "integrity": "sha512-NkJQA7oZ4YHQhd2+H3BoRFKF3d/XNsiKpHZCQEMH9pDX27hQQLsTyOocyRgaIVtf8gHX3Nt3LPkR4e5EdtPAGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^22.18.0 || >=24.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -18917,6 +19420,20 @@ "integrity": "sha512-9gRK4+sRWzeN6AOewNBTLXir7Zl/i3GB6Yl26gK4flxz8BXVpD3kt8amREmWNb0mxYOGDotvE5a4N+PtGGKdkg==", "license": "MIT" }, + "node_modules/obug": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/ol": { "version": "10.9.0", "resolved": "https://registry.npmjs.org/ol/-/ol-10.9.0.tgz", @@ -20139,6 +20656,16 @@ "node": ">=8" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve-protobuf-schema": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz", @@ -20216,6 +20743,48 @@ "@rolldown/binding-win32-x64-msvc": "1.2.2" } }, + "node_modules/rolldown-plugin-dts": { + "version": "0.27.14", + "resolved": "https://registry.npmjs.org/rolldown-plugin-dts/-/rolldown-plugin-dts-0.27.14.tgz", + "integrity": "sha512-ZvuDDwoIpRK9RPxDXratCpklFO9QZZWndf/sd0VBFb4LEj0jj07UcHK9OCh7V4XiFz2Z89ziyBC2K6tJiDjrbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dts-resolver": "^3.0.0", + "get-tsconfig": "5.0.0-beta.5", + "obug": "^2.1.4", + "yuku-ast": "^0.8.0", + "yuku-codegen": "^0.8.0", + "yuku-parser": "^0.8.0" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@typescript/native-preview": "*", + "@volar/typescript": "~2.4.0", + "rolldown": "^1.0.0", + "typescript": "^5.0.0 || ^6.0.0 || ~7.0.0", + "vue-tsc": "~3.2.0 || ~3.3.0" + }, + "peerDependenciesMeta": { + "@typescript/native-preview": { + "optional": true + }, + "@volar/typescript": { + "optional": true + }, + "typescript": { + "optional": true + }, + "vue-tsc": { + "optional": true + } + } + }, "node_modules/rollup": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", @@ -21279,6 +21848,16 @@ "esm": "^3.2.25" } }, + "node_modules/tinyexec": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", @@ -21355,6 +21934,16 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", @@ -21374,6 +21963,79 @@ "typescript": ">=4.8.4" } }, + "node_modules/tsdown": { + "version": "0.22.14", + "resolved": "https://registry.npmjs.org/tsdown/-/tsdown-0.22.14.tgz", + "integrity": "sha512-ule7Y+fsAN2iZbLDoo7C4KYljFJNJJ+fLshyn+9gozeTspVersWHxwdGB+Dm2hzA38s6muFnUTl0jK3vJm9ifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansis": "^4.3.1", + "cac": "^7.0.0", + "defu": "^6.1.7", + "empathic": "^2.0.1", + "hookable": "^6.1.1", + "import-without-cache": "^0.4.0", + "obug": "^2.1.4", + "picomatch": "^4.0.5", + "rolldown": "~1.2.0", + "rolldown-plugin-dts": "^0.27.13", + "tinyexec": "^1.2.4", + "tinyglobby": "^0.2.17", + "tree-kill": "^1.2.2", + "unconfig-core": "^7.5.0", + "verkit": "^0.3.0" + }, + "bin": { + "tsdown": "dist/run.mjs" + }, + "engines": { + "node": "^22.18.0 || >=24.11.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + }, + "peerDependencies": { + "@arethetypeswrong/core": "^0.18.1", + "@tsdown/css": "0.22.14", + "@tsdown/exe": "0.22.14", + "@vitejs/devtools": "*", + "publint": "^0.3.8", + "tsx": "*", + "typescript": "^5.0.0 || ^6.0.0 || ^7.0.0", + "unplugin-unused": "^0.5.0", + "unrun": "*" + }, + "peerDependenciesMeta": { + "@arethetypeswrong/core": { + "optional": true + }, + "@tsdown/css": { + "optional": true + }, + "@tsdown/exe": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "publint": { + "optional": true + }, + "tsx": { + "optional": true + }, + "typescript": { + "optional": true + }, + "unplugin-unused": { + "optional": true + }, + "unrun": { + "optional": true + } + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -21634,6 +22296,37 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unconfig-core": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/unconfig-core/-/unconfig-core-7.5.0.tgz", + "integrity": "sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@quansync/fs": "^1.0.0", + "quansync": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unconfig-core/node_modules/quansync": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-1.0.0.tgz", + "integrity": "sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/undici": { "version": "7.29.0", "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz", @@ -21906,6 +22599,19 @@ "node": ">= 0.8" } }, + "node_modules/verkit": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/verkit/-/verkit-0.3.2.tgz", + "integrity": "sha512-zj/ob3UsvJGN0whEAKFp53REA5X66hvffVqoCtVQAakJKnKlH+/PcOfMoFwIG/o4rElqLv/ycAFlx8ZlXUorCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/sxzz" + } + }, "node_modules/virtual-dom": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/virtual-dom/-/virtual-dom-2.1.1.tgz", @@ -23037,6 +23743,65 @@ "url": "https://opencollective.com/express" } }, + "node_modules/yuku-ast": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/yuku-ast/-/yuku-ast-0.8.7.tgz", + "integrity": "sha512-h6+4bDfyootiMB9vckk5uKo5r5j0GHrkr17FQTDNfEsFT3DWlN9uu1HJwQwc64pgmLCI945fWM3lbTIqxjT3GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yuku-toolchain/types": "^0.8.7" + } + }, + "node_modules/yuku-codegen": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/yuku-codegen/-/yuku-codegen-0.8.7.tgz", + "integrity": "sha512-adwDZSh8oVDzhE6Du9PwVWxcOxeV0e2EVhUuMKWfhSY4wkrDq9eqixlxFF3l/XGUV1E7UFzhpz9393MUumkyNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yuku-toolchain/types": "^0.8.7" + }, + "optionalDependencies": { + "@yuku-codegen/binding-android-arm64": "0.8.7", + "@yuku-codegen/binding-darwin-arm64": "0.8.7", + "@yuku-codegen/binding-darwin-x64": "0.8.7", + "@yuku-codegen/binding-freebsd-x64": "0.8.7", + "@yuku-codegen/binding-linux-arm-gnu": "0.8.7", + "@yuku-codegen/binding-linux-arm-musl": "0.8.7", + "@yuku-codegen/binding-linux-arm64-gnu": "0.8.7", + "@yuku-codegen/binding-linux-arm64-musl": "0.8.7", + "@yuku-codegen/binding-linux-x64-gnu": "0.8.7", + "@yuku-codegen/binding-linux-x64-musl": "0.8.7", + "@yuku-codegen/binding-win32-arm64": "0.8.7", + "@yuku-codegen/binding-win32-x64": "0.8.7" + } + }, + "node_modules/yuku-parser": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/yuku-parser/-/yuku-parser-0.8.7.tgz", + "integrity": "sha512-vRD9nwt4L3aYpxNqeSC4WqLv58xrXef0Ong1Mc45CTXTIpvLafx7JO05sczmQZwdLEZvywrLOGdNC5+Rp5N1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@yuku-toolchain/types": "^0.8.7", + "yuku-ast": "^0.8.7" + }, + "optionalDependencies": { + "@yuku-parser/binding-android-arm64": "0.8.7", + "@yuku-parser/binding-darwin-arm64": "0.8.7", + "@yuku-parser/binding-darwin-x64": "0.8.7", + "@yuku-parser/binding-freebsd-x64": "0.8.7", + "@yuku-parser/binding-linux-arm-gnu": "0.8.7", + "@yuku-parser/binding-linux-arm-musl": "0.8.7", + "@yuku-parser/binding-linux-arm64-gnu": "0.8.7", + "@yuku-parser/binding-linux-arm64-musl": "0.8.7", + "@yuku-parser/binding-linux-x64-gnu": "0.8.7", + "@yuku-parser/binding-linux-x64-musl": "0.8.7", + "@yuku-parser/binding-win32-arm64": "0.8.7", + "@yuku-parser/binding-win32-x64": "0.8.7" + } + }, "node_modules/zarrita": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/zarrita/-/zarrita-0.7.3.tgz", @@ -23183,6 +23948,7 @@ "packages/core": { "name": "@geolibre/core", "version": "2.7.0", + "license": "MIT", "dependencies": { "@maplibre/maplibre-gl-style-spec": "^26.2.1", "uuid": "^14.0.1", @@ -23275,6 +24041,7 @@ "packages/map": { "name": "@geolibre/map", "version": "2.7.0", + "license": "MIT", "dependencies": { "@geolibre/core": "*", "@maplibre/geojson-vt": "^6.1.1", @@ -23294,13 +24061,14 @@ "maplibre-gl": "^6.3.0", "maplibre-gl-layer-control": "^0.17.4", "pmtiles": "^4.5.0", - "react": "^19.2.8", - "react-dom": "^19.2.8" + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "devDependencies": { "@types/geojson": "^7946.0.16", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", + "tsdown": "^0.22.14", "typescript": "^7.0.2" }, "peerDependencies": { diff --git a/packages/core/package.json b/packages/core/package.json index 9c53efb97..678510537 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,13 +1,27 @@ { "name": "@geolibre/core", "version": "2.7.0", - "private": true, "type": "module", + "license": "MIT", "main": "./src/index.ts", "types": "./src/index.ts", "exports": { ".": "./src/index.ts" }, + "files": ["dist"], + "scripts": { + "build": "tsdown src/index.ts --format esm --dts" + }, + "publishConfig": { + "main": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs" + } + } + }, "dependencies": { "@maplibre/maplibre-gl-style-spec": "^26.2.1", "uuid": "^14.0.1", diff --git a/packages/map/package.json b/packages/map/package.json index 0946f6c6e..c1459769d 100644 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -1,8 +1,8 @@ { "name": "@geolibre/map", "version": "2.7.0", - "private": true, "type": "module", + "license": "MIT", "main": "./src/index.ts", "types": "./src/index.ts", "exports": { @@ -10,6 +10,20 @@ "./derived-geometry": "./src/derived-geometry.ts", "./pmtiles-layer": "./src/pmtiles-layer.ts" }, + "files": ["dist"], + "scripts": { + "build": "tsdown src/headless.ts --format esm --dts" + }, + "publishConfig": { + "main": "./dist/headless.mjs", + "types": "./dist/headless.d.mts", + "exports": { + ".": { + "types": "./dist/headless.d.mts", + "import": "./dist/headless.mjs" + } + } + }, "dependencies": { "@geolibre/core": "*", "@maplibre/geojson-vt": "^6.1.1", @@ -29,13 +43,14 @@ "maplibre-gl": "^6.3.0", "maplibre-gl-layer-control": "^0.17.4", "pmtiles": "^4.5.0", - "react": "^19.2.8", - "react-dom": "^19.2.8" + "react": "^19.0.0", + "react-dom": "^19.0.0" }, "devDependencies": { "@types/geojson": "^7946.0.16", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", + "tsdown": "^0.22.14", "typescript": "^7.0.2" }, "peerDependencies": { diff --git a/packages/map/src/headless.ts b/packages/map/src/headless.ts new file mode 100644 index 000000000..5bd893eea --- /dev/null +++ b/packages/map/src/headless.ts @@ -0,0 +1,138 @@ +/** + * Headless entry (`@geolibre/map/headless`): data loading + layer + * synchronization without React, the zustand store, Cesium, or map controls. + * + * Feed a `GeoLibreLayer[]` to {@link createLayerSync}'s `sync()` whenever the + * layer model changes; it diffs against the previous array and drives + * MapLibre's addSource/addLayer through the same `syncLayer()` used by the + * full app. + */ +import type { GeoLibreLayer } from "@geolibre/core"; +import { removeLayerFromMap, syncLayer } from "./layer-sync"; + +export interface LayerSync { + /** Diff-sync the full layer list (bottom-to-top stacking order). */ + sync(layers: GeoLibreLayer[]): void; + /** Remove every layer this sync previously added. */ + dispose(): void; +} + +/** + * ponytail: bottom-up re-add ordering gives correct stacking on insert and + * append, but moving an existing layer mid-stack does not restack until it is + * removed and re-added. Full anchor logic lives in MapController. + * getBeforeStyleLayerId; port it here if in-place reordering matters. + */ +export function createLayerSync(map: maplibregl.Map): LayerSync { + let synced: GeoLibreLayer[] = []; + + return { + sync(layers) { + const nextIds = new Set(layers.map((layer) => layer.id)); + for (const previous of synced) { + if (!nextIds.has(previous.id)) removeLayerFromMap(map, previous.id, previous); + } + // Input order is bottom-to-top: each addLayer without an anchor lands on + // top of the previous one, so the final stack matches the input order. + for (const layer of layers) syncLayer(map, layer); + synced = [...layers]; + }, + dispose() { + for (const layer of synced) removeLayerFromMap(map, layer.id, layer); + synced = []; + }, + }; +} + +// Data-loading helpers (COG / PMTiles protocols must be registered before use). +export { + registerCogDemSource, + encodeTerrariumDem, + CogDemError, + type CogDemErrorCode, +} from "./cog-dem-source"; +export { + registerPMTilesArchive, + unregisterPMTilesArchive, + ensureRemotePMTilesArchive, +} from "./layer-sync"; +export { readRemotePMTilesInfo, readPMTilesArchiveInfo } from "./pmtiles-layer"; +// Style engine outputs (standard MapLibre style-spec objects). +export { + fillPaint, + linePaint, + circlePaint, + heatmapPaint, + clusterCirclePaint, + fillExtrusionPaint, + rasterPaint, +} from "./style-mapper"; +// Vector geometry/ID conventions shared by the paths above. +export { + detectGeometryProfile, + getLayerBounds, + sourceId, + fillLayerId, + lineLayerId, + circleLayerId, +} from "./geojson-loader"; +export { + buildGeneratedGeometry, + buildInvertedMask, + generatedGeometryKinds, + lineDecorationColorValue, +} from "./derived-geometry"; +export { + buildMapboxStyle, + mapboxStyleToJson, + type ExportableLayer, + type MapboxStyleExportOptions, + type MapboxStyleExportResult, +} from "./mapbox-style-export"; +export { + applyMapboxStyleImport, + parseMapboxStyle, + type MapboxStyleImportResult, +} from "./mapbox-style-import"; +export { buildGeoLibreQueryStyle, geoLibreStyleSourceName } from "./query-param-style"; +export { + buildSld, + OGC_SCALE_DENOMINATOR_AT_ZOOM_0, + type SldExportableLayer, + type SldExportOptions, + type SldExportResult, +} from "./sld-export"; +export { applySldImport, parseSld, type SldImportResult } from "./sld-import"; +export { + buildQml, + type QmlExportableLayer, + type QmlExportOptions, + type QmlExportResult, +} from "./qml-export"; +export { applyQmlImport, parseQml, type QmlImportResult } from "./qml-import"; +export { + isMapboxStyleUrl, + loadMapboxStyle, + mapboxAccessTokenFromStyleUrl, + redactMapboxStyleUrl, + resolveMapboxInternalUrl, + transformMapboxStyle, +} from "./mapbox-style"; +export { + buildProtomapsBasemapStyle, + evictOfflineBasemapStyle, + getOfflineBasemapStyle, + isOfflineBasemapSentinel, + registerOfflineBasemapStyle, + OFFLINE_BASEMAP_SENTINEL_PREFIX, + PROTOMAPS_FLAVORS, + type ProtomapsBasemapStyleOptions, + type ProtomapsFlavor, +} from "./protomaps-basemap"; +export { + featuresIntersectingPolygon, + selectionModeFromModifiers, + type FeatureSelectionRequest, + type FeatureSelectionShape, +} from "./feature-selection"; +export { isPlaceholderLayer, placeholderMessage } from "./placeholders"; From 0aa032cf1bb5f557a28042e8da39ecdde41e96e5 Mon Sep 17 00:00:00 2001 From: Dongdong Kong Date: Mon, 24 Aug 2026 10:37:33 +0800 Subject: [PATCH 2/5] fix maplibregl and desktop config --- apps/geolibre-desktop/tsconfig.json | 6 ++-- apps/geolibre-desktop/vite.config.ts | 2 ++ package-lock.json | 8 +++--- packages/map/package.json | 43 +++++++++++++++------------- packages/map/src/headless.ts | 8 +++--- 5 files changed, 37 insertions(+), 30 deletions(-) diff --git a/apps/geolibre-desktop/tsconfig.json b/apps/geolibre-desktop/tsconfig.json index a4d45813f..bb98115c0 100644 --- a/apps/geolibre-desktop/tsconfig.json +++ b/apps/geolibre-desktop/tsconfig.json @@ -3,9 +3,11 @@ "compilerOptions": { "composite": true, "paths": { - "@/*": ["./src/*"] + "@/*": ["./src/*"], + "@geolibre/map": ["../../packages/map/src/index.ts"], + "@geolibre/map/*": ["../../packages/map/src/*"] } }, - "include": ["src"], + "include": ["src", "../../packages/map/src"], "references": [] } diff --git a/apps/geolibre-desktop/vite.config.ts b/apps/geolibre-desktop/vite.config.ts index 8252e59dd..a7e348468 100644 --- a/apps/geolibre-desktop/vite.config.ts +++ b/apps/geolibre-desktop/vite.config.ts @@ -1059,6 +1059,8 @@ export default defineConfig({ dedupe: ["react", "react-dom", "maplibre-gl", "@anthropic-ai/sdk", "openai", "@google/genai"], alias: { "@": path.resolve(__dirname, "./src"), + // Monorepo development uses map source; npm consumers use dist. + "@geolibre/map": path.resolve(__dirname, "../../packages/map/src"), // The published package resolves to dist, but the monorepo app should // hot-reload SDK source during development. "@geolibre/embed": path.resolve(__dirname, "../../packages/embed/src/index.ts"), diff --git a/package-lock.json b/package-lock.json index dbd1b95b2..2beffd199 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24061,8 +24061,8 @@ "maplibre-gl": "^6.3.0", "maplibre-gl-layer-control": "^0.17.4", "pmtiles": "^4.5.0", - "react": "^19.0.0", - "react-dom": "^19.0.0" + "react": "^19.2.8", + "react-dom": "^19.2.8" }, "devDependencies": { "@types/geojson": "^7946.0.16", @@ -24072,8 +24072,8 @@ "typescript": "^7.0.2" }, "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "react": "^19.2.8", + "react-dom": "^19.2.8" } }, "packages/map/node_modules/@turf/bbox": { diff --git a/packages/map/package.json b/packages/map/package.json index c1459769d..13f4c18c4 100644 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -3,26 +3,29 @@ "version": "2.7.0", "type": "module", "license": "MIT", - "main": "./src/index.ts", - "types": "./src/index.ts", + "main": "./dist/headless.mjs", + "types": "./dist/headless.d.mts", "exports": { - ".": "./src/index.ts", - "./derived-geometry": "./src/derived-geometry.ts", - "./pmtiles-layer": "./src/pmtiles-layer.ts" + ".": { + "types": "./dist/headless.d.mts", + "import": "./dist/headless.mjs" + }, + "./headless": { + "types": "./dist/headless.d.mts", + "import": "./dist/headless.mjs" + }, + "./derived-geometry": { + "types": "./dist/derived-geometry.d.mts", + "import": "./dist/derived-geometry.mjs" + }, + "./pmtiles-layer": { + "types": "./dist/pmtiles-layer.d.mts", + "import": "./dist/pmtiles-layer.mjs" + } }, "files": ["dist"], "scripts": { - "build": "tsdown src/headless.ts --format esm --dts" - }, - "publishConfig": { - "main": "./dist/headless.mjs", - "types": "./dist/headless.d.mts", - "exports": { - ".": { - "types": "./dist/headless.d.mts", - "import": "./dist/headless.mjs" - } - } + "build": "tsdown src/headless.ts src/derived-geometry.ts src/pmtiles-layer.ts --format esm --dts" }, "dependencies": { "@geolibre/core": "*", @@ -43,8 +46,8 @@ "maplibre-gl": "^6.3.0", "maplibre-gl-layer-control": "^0.17.4", "pmtiles": "^4.5.0", - "react": "^19.0.0", - "react-dom": "^19.0.0" + "react": "^19.2.8", + "react-dom": "^19.2.8" }, "devDependencies": { "@types/geojson": "^7946.0.16", @@ -54,7 +57,7 @@ "typescript": "^7.0.2" }, "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "react": "^19.2.8", + "react-dom": "^19.2.8" } } diff --git a/packages/map/src/headless.ts b/packages/map/src/headless.ts index 5bd893eea..0f262853b 100644 --- a/packages/map/src/headless.ts +++ b/packages/map/src/headless.ts @@ -8,6 +8,7 @@ * full app. */ import type { GeoLibreLayer } from "@geolibre/core"; +import type * as maplibregl from "maplibre-gl"; import { removeLayerFromMap, syncLayer } from "./layer-sync"; export interface LayerSync { @@ -18,10 +19,9 @@ export interface LayerSync { } /** - * ponytail: bottom-up re-add ordering gives correct stacking on insert and - * append, but moving an existing layer mid-stack does not restack until it is - * removed and re-added. Full anchor logic lives in MapController. - * getBeforeStyleLayerId; port it here if in-place reordering matters. + * Reapplies the input order among synchronized layers. Unlike MapController, + * this headless sync does not compute style-layer anchors, so it cannot preserve + * their placement relative to unmanaged or asynchronously inserted layers. */ export function createLayerSync(map: maplibregl.Map): LayerSync { let synced: GeoLibreLayer[] = []; From 6d7e3cb628c5288e99dd3774296a5be8f0f45ba5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 24 Aug 2026 02:38:11 +0000 Subject: [PATCH 3/5] style: auto-format (ruff + oxfmt) [pre-commit.ci] --- packages/core/package.json | 12 +++++++----- packages/map/package.json | 6 ++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 678510537..4955249b1 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,17 +1,16 @@ { "name": "@geolibre/core", "version": "2.7.0", - "type": "module", "license": "MIT", + "files": [ + "dist" + ], + "type": "module", "main": "./src/index.ts", "types": "./src/index.ts", "exports": { ".": "./src/index.ts" }, - "files": ["dist"], - "scripts": { - "build": "tsdown src/index.ts --format esm --dts" - }, "publishConfig": { "main": "./dist/index.mjs", "types": "./dist/index.d.ts", @@ -22,6 +21,9 @@ } } }, + "scripts": { + "build": "tsdown src/index.ts --format esm --dts" + }, "dependencies": { "@maplibre/maplibre-gl-style-spec": "^26.2.1", "uuid": "^14.0.1", diff --git a/packages/map/package.json b/packages/map/package.json index 13f4c18c4..8e8495273 100644 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -1,8 +1,11 @@ { "name": "@geolibre/map", "version": "2.7.0", - "type": "module", "license": "MIT", + "files": [ + "dist" + ], + "type": "module", "main": "./dist/headless.mjs", "types": "./dist/headless.d.mts", "exports": { @@ -23,7 +26,6 @@ "import": "./dist/pmtiles-layer.mjs" } }, - "files": ["dist"], "scripts": { "build": "tsdown src/headless.ts src/derived-geometry.ts src/pmtiles-layer.ts --format esm --dts" }, From 198b5ffd3be236d6694ddcc5c15b9500ec26bbba Mon Sep 17 00:00:00 2001 From: giswqs Date: Sun, 23 Aug 2026 23:37:20 -0400 Subject: [PATCH 4/5] Address review feedback - packages/core/package.json: move the dist `main`/`types`/`exports` out of `publishConfig` semantics that npm ignores. npm only honors publish-time *config* there (npm/cli#7586), so the tarball advertised `./src/index.ts` while `files` shipped only `dist`. The published entries now go through `scripts/prepare-npm-package.mjs`, which hoists them just before publish; `publishConfig` also gains the missing `access: public` for a scoped package. Fixes the Copilot and CodeRabbit threads on this file. - packages/core/package.json: `types` pointed at `./dist/index.d.ts`, but `tsdown --dts` emits `index.d.mts`. Published consumers would have gotten no types at all. Answers the Claude review question about the extension. - packages/map/package.json: point `main`/`types`/`exports` back at TypeScript source. Pointing them at `dist` is what broke CI: `dist` is gitignored and nothing builds it, so 12 frontend tests failed with ERR_MODULE_NOT_FOUND on `@geolibre/map/derived-geometry` and friends. The published dist entries live under `publishConfig` and register every subpath the repo exports (`.`, `./headless`, `./derived-geometry`, `./pmtiles-layer`), so nothing 404s on npm. - packages/map/package.json: restore the `^19.0.0` react/react-dom peer ranges. Narrowing them to `^19.2.8` was unrelated to packaging and only constrains consumers. - apps/geolibre-desktop/tsconfig.json, vite.config.ts: revert the `@geolibre/map` path/alias and the `../../packages/map/src` include. With the manifest resolving to source again they are unnecessary, and the include made `tsc -b` emit `.js`/`.d.ts` beside the map sources. Fixes the CodeRabbit thread on tsconfig.json. - packages/map/src/headless.ts: restore the requested stack order when a layer moves. `syncLayer` on an existing layer leaves it where MapLibre put it, so a reorder was silently ignored and the documented bottom-to-top order stopped holding. Rebuild from the lowest position whose occupant changed. - Add scripts/prepare-npm-package.mjs plus tests/prepare-npm-package.test.ts, which also guards that every published path is one the package's own tsdown entries actually emit. - Add tests/headless-layer-sync.test.ts covering ordering, insertion, removal and dispose. - Add .github/workflows/publish-packages.yml: build, prepare and publish @geolibre/core then @geolibre/map on each GitHub Release, via npm Trusted Publishing with provenance, skipping a version already on the registry. - Add READMEs for both packages (npm renders them) and document the source-vs-dist split in CLAUDE.md. --- .github/workflows/publish-packages.yml | 71 +++++++++++++++ CLAUDE.md | 23 ++++- apps/geolibre-desktop/tsconfig.json | 6 +- apps/geolibre-desktop/vite.config.ts | 2 - package-lock.json | 5 +- packages/core/README.md | 20 +++++ packages/core/package.json | 27 +++++- packages/map/README.md | 34 ++++++++ packages/map/package.json | 69 ++++++++++----- packages/map/src/headless.ts | 25 +++++- scripts/prepare-npm-package.mjs | 91 +++++++++++++++++++ tests/headless-layer-sync.test.ts | 109 +++++++++++++++++++++++ tests/prepare-npm-package.test.ts | 116 +++++++++++++++++++++++++ 13 files changed, 564 insertions(+), 34 deletions(-) create mode 100644 .github/workflows/publish-packages.yml create mode 100644 packages/core/README.md create mode 100644 packages/map/README.md create mode 100644 scripts/prepare-npm-package.mjs create mode 100644 tests/headless-layer-sync.test.ts create mode 100644 tests/prepare-npm-package.test.ts diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 000000000..11758b9e5 --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,71 @@ +name: Publish core and map packages + +on: + release: + types: [published] + +permissions: + contents: read + +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # Required for npm trusted publishing and provenance. + steps: + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: actions/setup-node@v7 + with: + node-version: 24 + registry-url: https://registry.npmjs.org + + # Trusted Publishing (OIDC) requires npm >= 11.5.1, which the runner's + # bundled npm is not guaranteed to be. Pin to a known-good version rather + # than `npm@latest`: that tag briefly shipped a build missing the bundled + # `sigstore` module, which breaks `npm publish --provenance`. + - name: Upgrade npm for Trusted Publishing + run: npm install -g npm@11.5.1 + + - run: npm ci + + - name: Build the distributed bundles + run: npm run build -w @geolibre/core -w @geolibre/map + + # The manifests in git point at TypeScript source so the monorepo needs no + # build step; npm consumers need `dist`. npm ignores entry fields nested + # under `publishConfig`, so hoist them here, just before publishing. + # @geolibre/core is prepared alongside @geolibre/map so the `"*"` + # dependency between them is pinned to this release's version. + - name: Rewrite the manifests for publishing + run: node scripts/prepare-npm-package.mjs packages/core packages/map + + - id: version + run: | + version=$(node -p "require('./packages/core/package.json').version") + echo "version=$version" >> "$GITHUB_OUTPUT" + for pkg in core map; do + if npm view "@geolibre/$pkg@$version" version >/dev/null 2>&1; then + echo "$pkg=false" >> "$GITHUB_OUTPUT" + else + echo "$pkg=true" >> "$GITHUB_OUTPUT" + fi + done + + # No NODE_AUTH_TOKEN: npm authenticates via the OIDC id-token from the + # trusted publisher configured on each package (opengeos/GeoLibre, this + # workflow file) and attaches build provenance. npm has no PyPI-style + # pending publisher, so a package has to exist before that connection can + # be made: the first version is published from a maintainer's machine + # once, then every later release goes through here. + - name: Publish @geolibre/core + if: steps.version.outputs.core == 'true' + run: npm publish -w @geolibre/core --provenance --access public + + # Published after core so a consumer installing @geolibre/map never + # resolves a pinned @geolibre/core that is not on the registry yet. + - name: Publish @geolibre/map + if: steps.version.outputs.map == 'true' + run: npm publish -w @geolibre/map --provenance --access public diff --git a/CLAUDE.md b/CLAUDE.md index fa39e0d1b..378b1be58 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -110,7 +110,7 @@ The app is **store-driven**. `@geolibre/core` holds the Zustand store, domain ty Rendering is MapLibre GL JS in the webview, with **deck.gl** for raster/point-cloud/3D overlays. -**Packages:** `@geolibre/core` (types, project format, store) · `@geolibre/map` (MapLibre lifecycle + layer sync) · `@geolibre/ui` (shadcn-style primitives) · `@geolibre/processing` (client-side algorithm registry) · `@geolibre/plugins` (plugin interface + built-in plugins) · `@geolibre/embed` (typed iframe embed client, the one package published to npm — `.github/workflows/publish-embed.yml` publishes it on each GitHub Release, skipping a version already there) · `geolibre-desktop` (shell layout, Tauri I/O, composition). +**Packages:** `@geolibre/core` (types, project format, store) · `@geolibre/map` (MapLibre lifecycle + layer sync) · `@geolibre/ui` (shadcn-style primitives) · `@geolibre/processing` (client-side algorithm registry) · `@geolibre/plugins` (plugin interface + built-in plugins) · `@geolibre/embed` (typed iframe embed client — `.github/workflows/publish-embed.yml` publishes it on each GitHub Release, skipping a version already there) · `geolibre-desktop` (shell layout, Tauri I/O, composition). **Plugins:** Built-in plugins live in `packages/plugins/src/plugins/`, are exported from that package's `index.ts`, and registered in `apps/geolibre-desktop/src/hooks/usePlugins.ts`. External plugins load from zips or a `plugin.json` manifest; bundled drop-ins under `apps/geolibre-desktop/public/plugins//` bake into both web and desktop builds. See `docs/plugin-api.md`. @@ -123,6 +123,27 @@ The browser build proxies the sidecar at `/sidecar` (same-origin, no CORS); conf ## Conventions - Never commit directly to `main`; branch and open a PR. +- **`@geolibre/core` and `@geolibre/map` are published to npm** by + `.github/workflows/publish-packages.yml` on each GitHub Release, alongside + `@geolibre/embed`. Their checked-in `main`/`types`/`exports` point at + TypeScript **source**, because that is how the monorepo consumes them: Vite, + `tsc` and tsx all resolve `./src/index.ts` through the package's own + `exports`, so `npm run dev` and `node --import tsx --test tests/.test.ts` + need no build step. The npm tarball ships `dist` instead, and npm cannot + express that split on its own: unlike pnpm and Yarn it deliberately **ignores + entry fields nested under `publishConfig`** (npm/cli#7586), so a manifest that + only states its dist entries there publishes `./src/index.ts` to consumers who + never receive `src`. The published entries therefore live under + `publishConfig`, and `scripts/prepare-npm-package.mjs` hoists them (and pins + the `"*"` `@geolibre/core` dependency to the release version) just before + `npm publish`. Point those top-level fields at `dist` and every frontend test + that imports a `@geolibre/map` subpath fails with `ERR_MODULE_NOT_FOUND`, + because `dist` is gitignored and nothing builds it before the suite. + `tests/prepare-npm-package.test.ts` guards both halves, including that each + published path is one the package's own `tsdown` entries actually emit + (`--format esm --dts` writes `.mjs` and `.d.mts`, **not** + `.d.ts`) — no workflow runs those builds, so nothing else would notice the + drift. - **`backend/geolibre_server/uv.lock` is committed** (the root `.gitignore` ignores `uv.lock` everywhere else and negates it for this one path). That project is bundled into the desktop installers and launched with `uv run --frozen --project ` from `src-tauri/src/lib.rs` — a directory the user cannot write (`C:\Program Files\…`, `/usr/lib/GeoLibre Desktop/…`). Ship it lockless and uv resolves, then tries to _write_ `uv.lock` there, fails with "Permission denied" and exits 2 — which reaches the user as "Jupyter server exited before it was ready (exit code: 2)" with the cause invisible. So: any edit to that `pyproject.toml`'s dependencies must land with a refreshed lock (`uv lock --project backend/geolibre_server`). CI's "Check the bundled sidecar lockfile is in sync" step (`uv lock --check`) fails if they drift. - Tauri CSP allowlists tile/style hosts (OpenFreeMap, CARTO) — new external map/tile hosts must be added there. - Map/tile-host CORS for selected release assets is handled by a dev-server raster proxy. diff --git a/apps/geolibre-desktop/tsconfig.json b/apps/geolibre-desktop/tsconfig.json index bb98115c0..a4d45813f 100644 --- a/apps/geolibre-desktop/tsconfig.json +++ b/apps/geolibre-desktop/tsconfig.json @@ -3,11 +3,9 @@ "compilerOptions": { "composite": true, "paths": { - "@/*": ["./src/*"], - "@geolibre/map": ["../../packages/map/src/index.ts"], - "@geolibre/map/*": ["../../packages/map/src/*"] + "@/*": ["./src/*"] } }, - "include": ["src", "../../packages/map/src"], + "include": ["src"], "references": [] } diff --git a/apps/geolibre-desktop/vite.config.ts b/apps/geolibre-desktop/vite.config.ts index a7e348468..8252e59dd 100644 --- a/apps/geolibre-desktop/vite.config.ts +++ b/apps/geolibre-desktop/vite.config.ts @@ -1059,8 +1059,6 @@ export default defineConfig({ dedupe: ["react", "react-dom", "maplibre-gl", "@anthropic-ai/sdk", "openai", "@google/genai"], alias: { "@": path.resolve(__dirname, "./src"), - // Monorepo development uses map source; npm consumers use dist. - "@geolibre/map": path.resolve(__dirname, "../../packages/map/src"), // The published package resolves to dist, but the monorepo app should // hot-reload SDK source during development. "@geolibre/embed": path.resolve(__dirname, "../../packages/embed/src/index.ts"), diff --git a/package-lock.json b/package-lock.json index 2beffd199..b6c8053cf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23957,6 +23957,7 @@ }, "devDependencies": { "@types/geojson": "^7946.0.16", + "tsdown": "^0.22.14", "typescript": "^7.0.2" } }, @@ -24072,8 +24073,8 @@ "typescript": "^7.0.2" }, "peerDependencies": { - "react": "^19.2.8", - "react-dom": "^19.2.8" + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, "packages/map/node_modules/@turf/bbox": { diff --git a/packages/core/README.md b/packages/core/README.md new file mode 100644 index 000000000..5bbf75267 --- /dev/null +++ b/packages/core/README.md @@ -0,0 +1,20 @@ +# @geolibre/core + +Domain types, the `.geolibre.json` project schema, and the Zustand store that +[GeoLibre](https://geolibre.app) is built on. + +```bash +npm install @geolibre/core +``` + +```ts +import type { GeoLibreLayer, GeoLibreProject } from "@geolibre/core"; +``` + +The package is ESM-only and ships TypeScript declarations. See +[`docs/project-format.md`](https://github.com/opengeos/GeoLibre/blob/main/docs/project-format.md) +for the project schema and +[`docs/architecture.md`](https://github.com/opengeos/GeoLibre/blob/main/docs/architecture.md) +for how the store drives the app. + +MIT licensed. diff --git a/packages/core/package.json b/packages/core/package.json index 4955249b1..fbd6f70b5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,9 +1,26 @@ { "name": "@geolibre/core", "version": "2.7.0", + "description": "GeoLibre domain types, project schema, and Zustand store", + "keywords": [ + "geolibre", + "geospatial", + "gis", + "maplibre", + "zustand" + ], + "homepage": "https://geolibre.app", + "bugs": "https://github.com/opengeos/GeoLibre/issues", "license": "MIT", + "author": "Qiusheng Wu", + "repository": { + "type": "git", + "url": "https://github.com/opengeos/GeoLibre", + "directory": "packages/core" + }, "files": [ - "dist" + "dist", + "README.md" ], "type": "module", "main": "./src/index.ts", @@ -13,13 +30,14 @@ }, "publishConfig": { "main": "./dist/index.mjs", - "types": "./dist/index.d.ts", + "types": "./dist/index.d.mts", "exports": { ".": { - "types": "./dist/index.d.ts", + "types": "./dist/index.d.mts", "import": "./dist/index.mjs" } - } + }, + "access": "public" }, "scripts": { "build": "tsdown src/index.ts --format esm --dts" @@ -32,6 +50,7 @@ }, "devDependencies": { "@types/geojson": "^7946.0.16", + "tsdown": "^0.22.14", "typescript": "^7.0.2" } } diff --git a/packages/map/README.md b/packages/map/README.md new file mode 100644 index 000000000..ffb6c46c2 --- /dev/null +++ b/packages/map/README.md @@ -0,0 +1,34 @@ +# @geolibre/map + +The headless half of [GeoLibre](https://geolibre.app)'s map layer: data loading, +layer synchronization, and the style engine, without React, the Zustand store, +Cesium, or any map controls. + +```bash +npm install @geolibre/map maplibre-gl +``` + +```ts +import maplibregl from "maplibre-gl"; +import { createLayerSync } from "@geolibre/map"; + +const map = new maplibregl.Map({ container: "map", style: "https://tiles.openfreemap.org/styles/liberty" }); +const sync = createLayerSync(map); + +map.on("load", () => { + // Bottom-to-top stacking order; call again whenever the layer list changes. + sync.sync(layers); +}); +``` + +Alongside `createLayerSync` the entry point exports the paint builders +(`fillPaint`, `linePaint`, `circlePaint`, ...), the COG DEM and PMTiles protocol +registration helpers, and the Mapbox Style / SLD / QML import and export +functions. See +[`docs/architecture.md`](https://github.com/opengeos/GeoLibre/blob/main/docs/architecture.md). + +The package is ESM-only and ships TypeScript declarations. `maplibre-gl` is a +dependency; `react` and `react-dom` are peer dependencies of the full +in-repository entry point and are not needed by the headless surface. + +MIT licensed. diff --git a/packages/map/package.json b/packages/map/package.json index 8e8495273..6022728e2 100644 --- a/packages/map/package.json +++ b/packages/map/package.json @@ -1,30 +1,59 @@ { "name": "@geolibre/map", "version": "2.7.0", + "description": "Headless MapLibre layer synchronization and style engine from GeoLibre", + "keywords": [ + "geolibre", + "geospatial", + "gis", + "headless", + "maplibre", + "pmtiles" + ], + "homepage": "https://geolibre.app", + "bugs": "https://github.com/opengeos/GeoLibre/issues", "license": "MIT", + "author": "Qiusheng Wu", + "repository": { + "type": "git", + "url": "https://github.com/opengeos/GeoLibre", + "directory": "packages/map" + }, "files": [ - "dist" + "dist", + "README.md" ], "type": "module", - "main": "./dist/headless.mjs", - "types": "./dist/headless.d.mts", + "main": "./src/index.ts", + "types": "./src/index.ts", "exports": { - ".": { - "types": "./dist/headless.d.mts", - "import": "./dist/headless.mjs" - }, - "./headless": { - "types": "./dist/headless.d.mts", - "import": "./dist/headless.mjs" - }, - "./derived-geometry": { - "types": "./dist/derived-geometry.d.mts", - "import": "./dist/derived-geometry.mjs" + ".": "./src/index.ts", + "./headless": "./src/headless.ts", + "./derived-geometry": "./src/derived-geometry.ts", + "./pmtiles-layer": "./src/pmtiles-layer.ts" + }, + "publishConfig": { + "main": "./dist/headless.mjs", + "types": "./dist/headless.d.mts", + "exports": { + ".": { + "types": "./dist/headless.d.mts", + "import": "./dist/headless.mjs" + }, + "./headless": { + "types": "./dist/headless.d.mts", + "import": "./dist/headless.mjs" + }, + "./derived-geometry": { + "types": "./dist/derived-geometry.d.mts", + "import": "./dist/derived-geometry.mjs" + }, + "./pmtiles-layer": { + "types": "./dist/pmtiles-layer.d.mts", + "import": "./dist/pmtiles-layer.mjs" + } }, - "./pmtiles-layer": { - "types": "./dist/pmtiles-layer.d.mts", - "import": "./dist/pmtiles-layer.mjs" - } + "access": "public" }, "scripts": { "build": "tsdown src/headless.ts src/derived-geometry.ts src/pmtiles-layer.ts --format esm --dts" @@ -59,7 +88,7 @@ "typescript": "^7.0.2" }, "peerDependencies": { - "react": "^19.2.8", - "react-dom": "^19.2.8" + "react": "^19.0.0", + "react-dom": "^19.0.0" } } diff --git a/packages/map/src/headless.ts b/packages/map/src/headless.ts index 0f262853b..1ac4e49e2 100644 --- a/packages/map/src/headless.ts +++ b/packages/map/src/headless.ts @@ -29,11 +29,34 @@ export function createLayerSync(map: maplibregl.Map): LayerSync { return { sync(layers) { const nextIds = new Set(layers.map((layer) => layer.id)); + const previousById = new Map(synced.map((layer) => [layer.id, layer])); for (const previous of synced) { if (!nextIds.has(previous.id)) removeLayerFromMap(map, previous.id, previous); } + // Input order is bottom-to-top: each addLayer without an anchor lands on - // top of the previous one, so the final stack matches the input order. + // top of the previous one, so re-adding the whole tail in order rebuilds + // the stack. MapLibre leaves an existing layer exactly where it is, so a + // layer that only moved has to be removed first or it keeps its old + // position and the documented order silently stops holding. Find the + // lowest position whose occupant changed -- a layer that moved, or a new + // layer inserted below existing ones -- and rebuild from there up. + const keptIds = synced.map((layer) => layer.id).filter((id) => nextIds.has(id)); + let rebuildFrom = layers.length; + let keptIndex = 0; + for (let index = 0; index < layers.length; index += 1) { + const id = layers[index].id; + if (!previousById.has(id) || id !== keptIds[keptIndex]) { + rebuildFrom = index; + break; + } + keptIndex += 1; + } + for (let index = rebuildFrom; index < layers.length; index += 1) { + const previous = previousById.get(layers[index].id); + if (previous) removeLayerFromMap(map, layers[index].id, previous); + } + for (const layer of layers) syncLayer(map, layer); synced = [...layers]; }, diff --git a/scripts/prepare-npm-package.mjs b/scripts/prepare-npm-package.mjs new file mode 100644 index 000000000..4fd5869f0 --- /dev/null +++ b/scripts/prepare-npm-package.mjs @@ -0,0 +1,91 @@ +#!/usr/bin/env node +/** + * Rewrite a workspace manifest into the form it should be published in. + * + * The monorepo consumes `@geolibre/core` and `@geolibre/map` straight from + * TypeScript source: Vite, tsx and `tsc` all resolve `./src/index.ts` through + * the package's own `exports`, so nothing has to be built before `npm run dev`, + * `npm run test:frontend`, or a single `node --import tsx --test` run. npm + * consumers need the built `dist` bundle instead. + * + * npm cannot express that split on its own. `publishConfig` only overrides npm + * *config* (registry, access, tag) at publish time -- npm deliberately ignores + * entry fields nested there, unlike pnpm and Yarn (npm/cli#7586). So the split + * lives here: the manifest keeps the source entries that the repo needs, states + * the published entries under `publishConfig`, and the publish workflow runs + * this script to hoist them just before `npm publish`. + * + * It also pins `"*"` workspace dependencies to the exact version being + * published. npm resolves `"*"` to the local workspace during development but + * publishes it verbatim, which would let a consumer install any `@geolibre/core` + * at all alongside a given `@geolibre/map`. + * + * Usage: node scripts/prepare-npm-package.mjs packages/map [...more] + */ +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import process from "node:process"; + +/** Entry fields npm ignores inside `publishConfig` and that we hoist by hand. */ +const HOISTED_FIELDS = ["main", "module", "types", "typings", "browser", "exports", "files"]; + +/** + * @param {Record} manifest Parsed `package.json`. + * @param {(name: string) => string | undefined} workspaceVersion Version of a + * sibling workspace, or undefined when the name is not a workspace. + * @returns {Record} The manifest as it should be published. + */ +export function toPublishManifest(manifest, workspaceVersion = () => undefined) { + const published = { ...manifest }; + const publishConfig = { ...(manifest.publishConfig ?? {}) }; + + for (const field of HOISTED_FIELDS) { + if (field in publishConfig) { + published[field] = publishConfig[field]; + delete publishConfig[field]; + } + } + published.publishConfig = publishConfig; + delete published.private; + + for (const kind of ["dependencies", "peerDependencies", "optionalDependencies"]) { + const deps = manifest[kind]; + if (!deps) continue; + const pinned = { ...deps }; + for (const [name, range] of Object.entries(deps)) { + if (range !== "*") continue; + const version = workspaceVersion(name); + if (version) pinned[name] = `^${version}`; + } + published[kind] = pinned; + } + + return published; +} + +function main(dirs) { + if (dirs.length === 0) { + console.error("usage: node scripts/prepare-npm-package.mjs [...]"); + process.exit(1); + } + + const manifests = new Map( + dirs.map((dir) => { + const file = path.join(dir, "package.json"); + return [dir, JSON.parse(readFileSync(file, "utf8"))]; + }), + ); + const versions = new Map( + [...manifests.values()].map((manifest) => [manifest.name, manifest.version]), + ); + + for (const [dir, manifest] of manifests) { + const published = toPublishManifest(manifest, (name) => versions.get(name)); + writeFileSync(path.join(dir, "package.json"), `${JSON.stringify(published, null, 2)}\n`); + console.log(`prepared ${manifest.name}@${manifest.version} for publishing (${dir})`); + } +} + +if (process.argv[1] && import.meta.url === new URL(`file://${process.argv[1]}`).href) { + main(process.argv.slice(2)); +} diff --git a/tests/headless-layer-sync.test.ts b/tests/headless-layer-sync.test.ts new file mode 100644 index 000000000..3e0996c10 --- /dev/null +++ b/tests/headless-layer-sync.test.ts @@ -0,0 +1,109 @@ +import assert from "node:assert/strict"; +import { describe, it } from "node:test"; +import { DEFAULT_LAYER_STYLE, type GeoLibreLayer } from "@geolibre/core"; +import { createLayerSync } from "../packages/map/src/headless"; + +/** + * Stateful maplibre stub that keeps a real style-layer order, so a test can + * assert the stack `createLayerSync` leaves behind rather than the calls it + * made getting there. + */ +function makeMapStub() { + const layers: { id: string; source?: string }[] = []; + const sources = new Set(); + const map = { + getStyle: () => ({ layers: [...layers] }), + getLayersOrder: () => layers.map(({ id }) => id), + getLayer: (id: string) => layers.find((layer) => layer.id === id), + getSource: (id: string) => (sources.has(id) ? { id } : undefined), + addSource: (id: string) => { + sources.add(id); + }, + addLayer: (spec: { id: string; source?: string }, beforeId?: string) => { + const index = beforeId ? layers.findIndex((layer) => layer.id === beforeId) : -1; + if (index >= 0) layers.splice(index, 0, spec); + else layers.push(spec); + }, + removeLayer: (id: string) => { + const index = layers.findIndex((layer) => layer.id === id); + if (index >= 0) layers.splice(index, 1); + }, + removeSource: (id: string) => { + sources.delete(id); + }, + moveLayer: () => {}, + setLayoutProperty: () => {}, + setPaintProperty: () => {}, + setLayerZoomRange: () => {}, + }; + return { map, order: () => layers.map(({ id }) => id) }; +} + +function tileLayer(id: string): GeoLibreLayer { + return { + id, + name: id, + type: "xyz", + source: { + type: "raster", + tiles: [`https://tiles.example.com/${id}/{z}/{x}/{y}.png`], + tileSize: 256, + }, + visible: true, + opacity: 1, + style: { ...DEFAULT_LAYER_STYLE }, + metadata: {}, + }; +} + +describe("createLayerSync", () => { + it("stacks the first sync bottom-to-top in input order", () => { + const { map, order } = makeMapStub(); + const sync = createLayerSync(map as never); + + sync.sync([tileLayer("a"), tileLayer("b"), tileLayer("c")]); + + assert.deepEqual(order(), ["layer-a-raster", "layer-b-raster", "layer-c-raster"]); + }); + + it("restores the requested order when a layer moves", () => { + const { map, order } = makeMapStub(); + const sync = createLayerSync(map as never); + + sync.sync([tileLayer("a"), tileLayer("b"), tileLayer("c")]); + // "c" is moved to the bottom of the stack. + sync.sync([tileLayer("c"), tileLayer("a"), tileLayer("b")]); + + assert.deepEqual(order(), ["layer-c-raster", "layer-a-raster", "layer-b-raster"]); + }); + + it("puts a layer inserted in the middle below the layers above it", () => { + const { map, order } = makeMapStub(); + const sync = createLayerSync(map as never); + + sync.sync([tileLayer("a"), tileLayer("c")]); + sync.sync([tileLayer("a"), tileLayer("b"), tileLayer("c")]); + + assert.deepEqual(order(), ["layer-a-raster", "layer-b-raster", "layer-c-raster"]); + }); + + it("removes a layer dropped from the input", () => { + const { map, order } = makeMapStub(); + const sync = createLayerSync(map as never); + + sync.sync([tileLayer("a"), tileLayer("b")]); + sync.sync([tileLayer("b")]); + + assert.deepEqual(order(), ["layer-b-raster"]); + }); + + it("removes every layer it added on dispose", () => { + const { map, order } = makeMapStub(); + const sync = createLayerSync(map as never); + + sync.sync([tileLayer("a"), tileLayer("b")]); + sync.dispose(); + + assert.deepEqual(order(), []); + }); +}); diff --git a/tests/prepare-npm-package.test.ts b/tests/prepare-npm-package.test.ts new file mode 100644 index 000000000..a54b201b6 --- /dev/null +++ b/tests/prepare-npm-package.test.ts @@ -0,0 +1,116 @@ +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { describe, it } from "node:test"; +import { toPublishManifest } from "../scripts/prepare-npm-package.mjs"; + +const repoRoot = path.resolve(import.meta.dirname, ".."); + +function manifest(workspace: string): Record { + return JSON.parse(readFileSync(path.join(repoRoot, workspace, "package.json"), "utf8")); +} + +describe("toPublishManifest", () => { + it("hoists the entry fields npm ignores inside publishConfig", () => { + const published = toPublishManifest({ + name: "@scope/pkg", + version: "1.0.0", + main: "./src/index.ts", + types: "./src/index.ts", + exports: { ".": "./src/index.ts" }, + publishConfig: { + access: "public", + main: "./dist/index.mjs", + types: "./dist/index.d.mts", + exports: { ".": { types: "./dist/index.d.mts", import: "./dist/index.mjs" } }, + }, + }); + + assert.equal(published.main, "./dist/index.mjs"); + assert.equal(published.types, "./dist/index.d.mts"); + assert.deepEqual(published.exports, { + ".": { types: "./dist/index.d.mts", import: "./dist/index.mjs" }, + }); + // Only publish-time npm config is left behind. + assert.deepEqual(published.publishConfig, { access: "public" }); + }); + + it("pins a '*' workspace dependency to the version being published", () => { + const published = toPublishManifest( + { + name: "@scope/b", + version: "2.0.0", + dependencies: { "@scope/a": "*", "maplibre-gl": "^6.3.0" }, + }, + (name) => (name === "@scope/a" ? "2.0.0" : undefined), + ); + + assert.deepEqual(published.dependencies, { "@scope/a": "^2.0.0", "maplibre-gl": "^6.3.0" }); + }); + + it("leaves a '*' range alone when it names no workspace", () => { + const published = toPublishManifest({ + name: "@scope/b", + version: "2.0.0", + dependencies: { "some-package": "*" }, + }); + + assert.deepEqual(published.dependencies, { "some-package": "*" }); + }); + + it("publishes core and map from dist, with core pinned to the same version", () => { + const core = manifest("packages/core"); + const map = manifest("packages/map"); + const version = (name: string) => (name === core.name ? (core.version as string) : undefined); + + const publishedCore = toPublishManifest(core, version) as Record; + const publishedMap = toPublishManifest(map, version) as Record & { + dependencies: Record; + exports: Record; + }; + + assert.equal(publishedCore.main, "./dist/index.mjs"); + assert.equal(publishedMap.main, "./dist/headless.mjs"); + assert.equal(publishedMap.dependencies["@geolibre/core"], `^${core.version}`); + // Every subpath the repo exports stays reachable from the published package. + assert.deepEqual(Object.keys(publishedMap.exports), Object.keys(map.exports as object)); + }); + + // The build scripts are not part of `npm run ci`, so nothing else notices if + // a published entry names a file tsdown does not emit -- which would leave a + // consumer with no types (or no module at all) and no error anywhere here. + it("only publishes files the package's own tsdown entries produce", () => { + for (const workspace of ["packages/core", "packages/map"]) { + const pkg = manifest(workspace) as { + scripts: { build: string }; + publishConfig: Record; + }; + const entries = pkg.scripts.build + .split(/\s+/) + .filter((token) => token.startsWith("src/") && token.endsWith(".ts")) + .map((token) => path.basename(token, ".ts")); + assert.ok(entries.length > 0, `${workspace}: no tsdown entries found`); + // `tsdown --format esm --dts` writes dist/.mjs and .d.mts. + const emitted = new Set( + entries.flatMap((name) => [`./dist/${name}.mjs`, `./dist/${name}.d.mts`]), + ); + + const published = toPublishManifest(manifest(workspace)) as Record; + const targets = JSON.stringify([published.main, published.types, published.exports]).match( + /\.\/dist\/[^"]+/g, + ); + for (const target of targets ?? []) { + assert.ok(emitted.has(target), `${workspace}: ${target} is not emitted by the build`); + } + } + }); + + it("declares public access so the scoped packages are not published private", () => { + for (const workspace of ["packages/core", "packages/map"]) { + const published = toPublishManifest(manifest(workspace)) as { + publishConfig: { access?: string }; + }; + assert.equal(published.publishConfig.access, "public", workspace); + } + }); +}); From fe6510fcbdf3a3a70d2636b73d6c20d6a3f66043 Mon Sep 17 00:00:00 2001 From: giswqs Date: Sun, 23 Aug 2026 23:46:07 -0400 Subject: [PATCH 5/5] Address CodeRabbit review feedback - .github/workflows/publish-packages.yml: read each package's own version for the registry check instead of using core's for both. The two move in lockstep today, but the moment they diverge the map check would skip an existing release or attempt one npm has already seen. - CLAUDE.md: correct the claim that no workflow runs the package builds. The release workflow does build both; what it does not do is verify that every path the manifest publishes names a file the build emitted, which is what tests/prepare-npm-package.test.ts covers. --- .github/workflows/publish-packages.yml | 7 +++++-- CLAUDE.md | 5 +++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml index 11758b9e5..b0a26b66d 100644 --- a/.github/workflows/publish-packages.yml +++ b/.github/workflows/publish-packages.yml @@ -42,11 +42,14 @@ jobs: - name: Rewrite the manifests for publishing run: node scripts/prepare-npm-package.mjs packages/core packages/map + # Each package is checked against its own version: the two move in + # lockstep today, but reading core's version for map would silently skip + # a map release (or attempt one npm has already seen) the moment they + # diverge. - id: version run: | - version=$(node -p "require('./packages/core/package.json').version") - echo "version=$version" >> "$GITHUB_OUTPUT" for pkg in core map; do + version=$(node -p "require('./packages/$pkg/package.json').version") if npm view "@geolibre/$pkg@$version" version >/dev/null 2>&1; then echo "$pkg=false" >> "$GITHUB_OUTPUT" else diff --git a/CLAUDE.md b/CLAUDE.md index 378b1be58..010438d39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -142,8 +142,9 @@ The browser build proxies the sidecar at `/sidecar` (same-origin, no CORS); conf `tests/prepare-npm-package.test.ts` guards both halves, including that each published path is one the package's own `tsdown` entries actually emit (`--format esm --dts` writes `.mjs` and `.d.mts`, **not** - `.d.ts`) — no workflow runs those builds, so nothing else would notice the - drift. + `.d.ts`). The release workflow does build both packages, but a green build + proves only that the bundles were written, not that every path the manifest + publishes names one of them, so nothing else would notice that drift. - **`backend/geolibre_server/uv.lock` is committed** (the root `.gitignore` ignores `uv.lock` everywhere else and negates it for this one path). That project is bundled into the desktop installers and launched with `uv run --frozen --project ` from `src-tauri/src/lib.rs` — a directory the user cannot write (`C:\Program Files\…`, `/usr/lib/GeoLibre Desktop/…`). Ship it lockless and uv resolves, then tries to _write_ `uv.lock` there, fails with "Permission denied" and exits 2 — which reaches the user as "Jupyter server exited before it was ready (exit code: 2)" with the cause invisible. So: any edit to that `pyproject.toml`'s dependencies must land with a refreshed lock (`uv lock --project backend/geolibre_server`). CI's "Check the bundled sidecar lockfile is in sync" step (`uv lock --check`) fails if they drift. - Tauri CSP allowlists tile/style hosts (OpenFreeMap, CARTO) — new external map/tile hosts must be added there. - Map/tile-host CORS for selected release assets is handled by a dev-server raster proxy.