diff --git a/README.md b/README.md index 71e2ff9..8bffc2c 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,14 @@ npm install ## 主流程 -默认构建南台子湖创新谷样例: +交互式选择区域和构建阶段: ```bash npm run build ``` +使用方向键移动、空格多选阶段、回车开始构建;`intermediates` 与 `reimport` 不能同时选择。 + 指定区域配置: ```bash @@ -310,7 +312,7 @@ npm run compress:glb -- \ 南台子湖创新谷当前可直接使用下面这条命令: ```bash -npm run build -- --config config/areas/nantaizi-lake-innovation-valley.json --stages reimport,blender,cesium +npm run build:area -- --config config/areas/nantaizi-lake-innovation-valley.json --stages reimport,blender,cesium ``` `reimport` 阶段(`scripts/reimport-gpkg.js`)做两件事: diff --git a/package-lock.json b/package-lock.json index c180020..e627584 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,425 @@ "name": "osm-asset-pipeline", "version": "0.3.0", "dependencies": { + "@inquirer/prompts": "^8.5.2", "osm2streets-js-node": "0.1.4" } }, + "node_modules/@inquirer/ansi": { + "version": "2.0.7", + "resolved": "http://172.16.1.86:4873/@inquirer/ansi/-/ansi-2.0.7.tgz", + "integrity": "sha512-3eTuUO1vH2cZm2ZKHeQxnOqlTi9EfZDGgIe3BL3I4u+rJHocr9Fz86M4fjYABPvFnQG/gGK551HqDiIcETwU6Q==", + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.2.1", + "resolved": "http://172.16.1.86:4873/@inquirer/checkbox/-/checkbox-5.2.1.tgz", + "integrity": "sha512-b6xmA/VlTe0ZgDQHDui+Nav470u7u49nRd8/iuhOcQPO9Ch7lGuogydhi2VOmNlZ+zXcM8IcPuNSwQcdJaF/kw==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.1.1", + "resolved": "http://172.16.1.86:4873/@inquirer/confirm/-/confirm-6.1.1.tgz", + "integrity": "sha512-eb8DBZcz/2qHWQda4rk2JiQk5h9QV/cVHi1yjt0f69WFZMRFn0sJTye3EAP8icut8UDMjQPsaH5KbcOogefrFQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "11.2.1", + "resolved": "http://172.16.1.86:4873/@inquirer/core/-/core-11.2.1.tgz", + "integrity": "sha512-Qd6GJT1yVyrZZCfN8W2qKF5ApmqryXRhRKCuip8h01x2w/esJQ2XIYc6f9abMIHgKQdBfFTSOdbHRLAhuM09UA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7", + "cli-width": "^4.1.0", + "fast-wrap-ansi": "^0.2.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "5.2.2", + "resolved": "http://172.16.1.86:4873/@inquirer/editor/-/editor-5.2.2.tgz", + "integrity": "sha512-ZRVd/oD+sYsUd5zVm0NflqEzlqfYCyHNsqkHl2oWXEUHs12tCbcSFi+wVFEvD8+LGRaMUsVrE7qeo6lSG/S1Vg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/external-editor": "^3.0.3", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.1.1", + "resolved": "http://172.16.1.86:4873/@inquirer/expand/-/expand-5.1.1.tgz", + "integrity": "sha512-YmQpenjbFSHAK3sOd44puHh3V1KXXr+JiNpUztoSQ4drLh2rTVzTap/YtlAVu/5xavifIlBfNEzJ/neZJ1a/1g==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "3.0.3", + "resolved": "http://172.16.1.86:4873/@inquirer/external-editor/-/external-editor-3.0.3.tgz", + "integrity": "sha512-6thf5I8q7lZwzGLAxPaaGEREEkZ3nyePPDQ1oyobblxmEE8mqTLguScP7pDjUTAibiyb4hfXl+qjUEJ+di/aNA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.7", + "resolved": "http://172.16.1.86:4873/@inquirer/figures/-/figures-2.0.7.tgz", + "integrity": "sha512-aJ8TBPOGB6f/2qziPfElISTCEd5XOYTFckA2SGjhNmiKzfK/u4ot3v0DUzGVdUnKjN10EqnnEPck36BkyfLnJw==", + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.1.2", + "resolved": "http://172.16.1.86:4873/@inquirer/input/-/input-5.1.2.tgz", + "integrity": "sha512-9K/DDBSQpOyZSkt6sOVP9Vo0TR7atX2kuILsUu0x3wVcVbe97lJwIJKMLdMw25tDYuXl/qp6erT0Xs1rfmcfZg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.1.1", + "resolved": "http://172.16.1.86:4873/@inquirer/number/-/number-4.1.1.tgz", + "integrity": "sha512-XF4IXAbPnGPgw0wsbC/i2tPcyfdZgDpUlhsqU0SfT4IRIGWha6Xm9VRgN5yYxJq+jnyXlfXI/nQ3ulfk0iEICA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.1.1", + "resolved": "http://172.16.1.86:4873/@inquirer/password/-/password-5.1.1.tgz", + "integrity": "sha512-3XBfF7DAsp5qeDsvN5Rd1HmbNokVvEQoUM0QLrRcybC9nX96w3Pbmu7qUsb3IT3J3jBvs2+mTXaKHOUsgHMLzg==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.5.2", + "resolved": "http://172.16.1.86:4873/@inquirer/prompts/-/prompts-8.5.2.tgz", + "integrity": "sha512-IYR/3C/paEVVQYQvdDlFZVjRCJVYHHON0XXMH91KO9GSxs0TdKYWlUdvfQl2EfAHDxUaN3IBffkE/BDTh5nJ6g==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.2.1", + "@inquirer/confirm": "^6.1.1", + "@inquirer/editor": "^5.2.2", + "@inquirer/expand": "^5.1.1", + "@inquirer/input": "^5.1.2", + "@inquirer/number": "^4.1.1", + "@inquirer/password": "^5.1.1", + "@inquirer/rawlist": "^5.3.1", + "@inquirer/search": "^4.2.1", + "@inquirer/select": "^5.2.1" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.3.1", + "resolved": "http://172.16.1.86:4873/@inquirer/rawlist/-/rawlist-5.3.1.tgz", + "integrity": "sha512-QqdTqQddL3qPX/PPrjobpsO25NZ4dWXgTLenrR445L2ptLEYE6Z+PD5c5CNDJNx4ugRgELAIpSIJxZaO2jJ2Og==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.2.1", + "resolved": "http://172.16.1.86:4873/@inquirer/search/-/search-4.2.1.tgz", + "integrity": "sha512-xJj8QWKRSrfKoBIITLZK61dD3zwo0Rz11fgDImku30/Oe81zMdIdGgrLY2h6RkJ+KZ/GhNYIRMKnH/62qBTA5g==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.2.1", + "resolved": "http://172.16.1.86:4873/@inquirer/select/-/select-5.2.1.tgz", + "integrity": "sha512-FlDndEUww8m7BfukO2nJa25vhD+H5jxxCv4oGioKqzyWz3nPHhhw4LKdYRSlXuAx7DsdWia7iyaBPKKS95Evfw==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.7", + "@inquirer/core": "^11.2.1", + "@inquirer/figures": "^2.0.7", + "@inquirer/type": "^4.0.7" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.0.7", + "resolved": "http://172.16.1.86:4873/@inquirer/type/-/type-4.0.7.tgz", + "integrity": "sha512-t28inv14nMQ1PhKpsJPY+kEs/c00qzeCOS2gTNRyTjG5d6qsVA2fItxW4hkvGZ5lvanGLdtCzVIx5dwdRpN1+g==", + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^20.17.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/chardet": { + "version": "2.2.0", + "resolved": "http://172.16.1.86:4873/chardet/-/chardet-2.2.0.tgz", + "integrity": "sha512-rddelWYNPRrXq6PtNEN2S3f6t9ILzvqaN5pVgi4kqt9jHQaXIial9PznB5iSPVlQSLNaaH22ItWz3EJtQ10+OA==", + "license": "MIT" + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "http://172.16.1.86:4873/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/fast-string-truncated-width": { + "version": "3.0.3", + "resolved": "http://172.16.1.86:4873/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", + "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", + "license": "MIT" + }, + "node_modules/fast-string-width": { + "version": "3.0.2", + "resolved": "http://172.16.1.86:4873/fast-string-width/-/fast-string-width-3.0.2.tgz", + "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", + "license": "MIT", + "dependencies": { + "fast-string-truncated-width": "^3.0.2" + } + }, + "node_modules/fast-wrap-ansi": { + "version": "0.2.2", + "resolved": "http://172.16.1.86:4873/fast-wrap-ansi/-/fast-wrap-ansi-0.2.2.tgz", + "integrity": "sha512-7F2Fl+TjRSenLqlU3UjSH0iyqopqoZIu7eZVpEirP2g1GtWa2G/ecEmBdgz31+Mxr+ELclgg6sokpSFIQiZ02Q==", + "license": "MIT", + "dependencies": { + "fast-string-width": "^3.0.2" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "http://172.16.1.86:4873/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "http://172.16.1.86:4873/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, "node_modules/osm2streets-js-node": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/osm2streets-js-node/-/osm2streets-js-node-0.1.4.tgz", "integrity": "sha512-JjS6qJJjrrlEKEZxqV4s/N4qU0YfvdMUasdvfoyYCmZxEpNKAYjpuZeWJWeLEcGOqhu+YuV3YDIk9jgvMU0kKQ==", "license": "Apache-2.0" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "http://172.16.1.86:4873/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "http://172.16.1.86:4873/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } } } } diff --git a/package.json b/package.json index 7190021..e34ad7a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "type": "commonjs", "scripts": { - "build": "node scripts/build-area.js", + "build": "node scripts/interactive-build.js", "build:area": "node scripts/build-area.js", "build:qgis": "node scripts/build-osm2streets-qgis.js", "check:area": "node scripts/check-area.js", @@ -12,6 +12,7 @@ "diagnose:area": "node scripts/diagnose-area.js", "preflight:area": "node scripts/preflight-area.js", "test:preflight": "node scripts/test-area-preflight.js", + "test:build-stages": "node scripts/test-build-stages.js", "test:budgets": "node scripts/test-asset-budgets.js", "test:preview-assets": "node scripts/test-preview-assets.js", "test:compress-glb": "node scripts/test-compress-glb.js", @@ -20,6 +21,7 @@ "render:turn-lane-arrow-samples": "node scripts/render-turn-lane-arrow-samples.js" }, "dependencies": { + "@inquirer/prompts": "^8.5.2", "osm2streets-js-node": "0.1.4" } } diff --git a/scripts/build-area.js b/scripts/build-area.js index 6b690c6..8308a95 100755 --- a/scripts/build-area.js +++ b/scripts/build-area.js @@ -4,6 +4,7 @@ const fs = require("fs"); const path = require("path"); const { spawnSync } = require("child_process"); const { readAreaConfig } = require("./lib/area-config"); +const { resolveStages } = require("./lib/build-stages"); const { blenderExecutable: resolveBlenderExecutable } = require("./lib/tool-paths"); const { SCENE_LAYERS, @@ -41,15 +42,6 @@ const requestedStages = args.stages : null; const stages = resolveStages(area.stages, requestedStages); -// intermediates deletes and rebuilds the GeoPackage from OSM, which is exactly -// the manual work reimport exists to recover. Refuse the combination instead of -// silently letting one undo the other. -if (stages.intermediates && stages.reimport) { - throw new Error( - "Stages 'intermediates' and 'reimport' are mutually exclusive: " + - "intermediates rebuilds the GeoPackage from OSM and would discard the QGIS edits reimport reads back.", - ); -} console.log(`Area: ${area.id}`); console.log(`Config: ${configPath}`); @@ -100,49 +92,6 @@ function splitList(value) { .filter(Boolean); } -function resolveStages(defaults, requested) { - if (!requested) return defaults; - // 'reimport' is deliberately absent from 'all': it is a recovery step for - // hand-edited GeoPackages, never part of a full build. - // 'compress' is also absent from 'all': it creates an alternate Cesium GLB, - // not the baseline asset. - const aliases = { - all: ["intermediates", "blender", "cesium"], - qgis: ["intermediates"], - osm2streets: ["intermediates"], - geojson: ["intermediates"], - intermediate: ["intermediates"], - intermediates: ["intermediates"], - reimport: ["reimport"], - gpkg: ["reimport"], - blender: ["blender"], - scene: ["blender"], - cesium: ["cesium"], - glb: ["cesium"], - preview: ["preview"], - html: ["preview"], - cesiumPreview: ["preview"], - compress: ["compress"], - compression: ["compress"], - compressedCesium: ["compress"], - }; - const out = { - intermediates: false, - reimport: false, - blender: false, - cesium: false, - preview: false, - compress: false, - }; - for (const stage of requested) { - const mapped = aliases[stage]; - if (!mapped) { - throw new Error(`Unknown stage '${stage}'. Use intermediates, reimport, blender, cesium, preview, compress, or all.`); - } - for (const key of mapped) out[key] = true; - } - return out; -} function writeDerivedConfig(area) { fs.mkdirSync(area.outputs.pipelineDir, { recursive: true }); diff --git a/scripts/interactive-build.js b/scripts/interactive-build.js new file mode 100644 index 0000000..5e041e8 --- /dev/null +++ b/scripts/interactive-build.js @@ -0,0 +1,49 @@ +#!/usr/bin/env node +"use strict"; + +const fs = require("fs"); +const path = require("path"); +const { spawnSync } = require("child_process"); +const { STAGES, resolveStages, canonicalStages } = require("./lib/build-stages"); + +const repoRoot = path.resolve(__dirname, ".."); + +function areaConfigs() { + return fs.readdirSync(path.join(repoRoot, "config", "areas")) + .filter((file) => file.endsWith(".json")) + .sort() + .map((file) => path.join(repoRoot, "config", "areas", file)); +} + +function requireTty(input = process.stdin, output = process.stdout) { + if (!input.isTTY || !output.isTTY) throw new Error("Interactive build requires a TTY. Use npm run build:area -- --config --stages instead."); +} + +async function main() { + requireTty(); + const configs = areaConfigs(); + if (!configs.length) throw new Error("No area configs found in config/areas."); + const { select, checkbox } = await import("@inquirer/prompts"); + const config = await select({ + message: "Choose an area", + choices: configs.map((file) => ({ name: path.basename(file, ".json"), value: file })), + }); + const chosen = await checkbox({ + message: "Choose build stages", + choices: STAGES.map((stage) => ({ name: `${stage.id} - ${stage.description}`, value: stage.id })), + required: true, + }); + if (!chosen.length) throw new Error("Choose at least one build stage."); + const stages = canonicalStages(resolveStages({}, chosen)); + console.log(`Area: ${path.basename(config)}`); + console.log(`Stages: ${stages.join(", ")}`); + const result = spawnSync(process.execPath, [path.join(__dirname, "build-area.js"), "--config", config, "--stages", stages.join(",")], { stdio: "inherit" }); + if (result.error) throw result.error; + process.exitCode = result.status || 0; +} + +if (require.main === module) main().catch((error) => { + if (error.name !== "ExitPromptError") console.error(`Error: ${error.message}`); + process.exitCode = error.name === "ExitPromptError" ? 0 : 1; +}); +module.exports = { areaConfigs, requireTty }; diff --git a/scripts/lib/build-stages.js b/scripts/lib/build-stages.js new file mode 100644 index 0000000..53dff31 --- /dev/null +++ b/scripts/lib/build-stages.js @@ -0,0 +1,41 @@ +"use strict"; + +const STAGES = [ + { id: "intermediates", label: "OSM / QGIS intermediates", description: "Rebuild GeoJSON and GeoPackage from OSM" }, + { id: "reimport", label: "Reimport QGIS edits", description: "Copy GeoPackage layers back to GeoJSON" }, + { id: "blender", label: "Blender scene", description: "Generate the editable scene and render" }, + { id: "cesium", label: "Cesium export", description: "Export GLB, metadata, and preview" }, + { id: "preview", label: "Preview refresh", description: "Regenerate preview HTML and vehicles" }, + { id: "compress", label: "Compressed preview", description: "Create optional compressed GLB artifacts" }, +]; + +const ALIASES = { + all: ["intermediates", "blender", "cesium"], + qgis: ["intermediates"], osm2streets: ["intermediates"], geojson: ["intermediates"], intermediate: ["intermediates"], + intermediates: ["intermediates"], reimport: ["reimport"], gpkg: ["reimport"], blender: ["blender"], scene: ["blender"], + cesium: ["cesium"], glb: ["cesium"], preview: ["preview"], html: ["preview"], cesiumPreview: ["preview"], + compress: ["compress"], compression: ["compress"], compressedCesium: ["compress"], +}; + +function resolveStages(defaults, requested) { + const selected = requested || Object.keys(defaults).filter((key) => defaults[key]); + const result = Object.fromEntries(STAGES.map(({ id }) => [id, false])); + for (const stage of selected) { + const mapped = ALIASES[stage]; + if (!mapped) throw new Error(`Unknown stage '${stage}'. Use ${stageNames().join(", ")}, or all.`); + for (const id of mapped) result[id] = true; + } + assertCompatible(result); + return result; +} + +function assertCompatible(stages) { + if (stages.intermediates && stages.reimport) { + throw new Error("Stages 'intermediates' and 'reimport' are mutually exclusive: intermediates rebuilds the GeoPackage from OSM and would discard the QGIS edits reimport reads back."); + } +} + +function stageNames() { return STAGES.map(({ id }) => id); } +function canonicalStages(stages) { return stageNames().filter((id) => stages[id]); } + +module.exports = { STAGES, resolveStages, canonicalStages, stageNames }; diff --git a/scripts/test-build-stages.js b/scripts/test-build-stages.js new file mode 100644 index 0000000..bfd63e5 --- /dev/null +++ b/scripts/test-build-stages.js @@ -0,0 +1,8 @@ +#!/usr/bin/env node +"use strict"; +const assert = require("assert"); +const { resolveStages, canonicalStages } = require("./lib/build-stages"); +assert.deepEqual(canonicalStages(resolveStages({}, ["compress", "blender", "preview"])), ["blender", "preview", "compress"]); +assert.deepEqual(canonicalStages(resolveStages({}, ["all"])), ["intermediates", "blender", "cesium"]); +assert.throws(() => resolveStages({}, ["intermediates", "reimport"]), /mutually exclusive/); +console.log("Build stage tests passed.");