diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ddf74c6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.13" + + - name: Test (supply chain + unit tests) + run: bun run test:github + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + submodules: true + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: "1.3.13" + + - name: Build (supply chain + build) + run: bun run build:github diff --git a/bun.lock b/bun.lock index 045d849..f1b30ee 100644 --- a/bun.lock +++ b/bun.lock @@ -7,75 +7,75 @@ "dependencies": { "@cornerstonejs/dicom-codec": "1.0.9", "canvas": "3.1.0", - "dcmjs": "0.50.0", - "typescript": "^5.8.2", + "dcmjs": "0.50.1", + "typescript": "5.9.3", }, "devDependencies": { - "@babel/core": "^7.26.0", - "@babel/eslint-parser": "^7.25.9", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-runtime": "^7.17.0", - "@babel/preset-env": "^7.16.11", - "@babel/preset-typescript": "^7.28.5", - "@babel/runtime": "^7.17.0", - "babel-plugin-transform-import-meta": "^2.3.3", - "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-jest": "^28.8.3", - "eslint-plugin-prettier": "^5.2.1", + "@babel/core": "7.28.6", + "@babel/eslint-parser": "7.28.6", + "@babel/plugin-proposal-class-properties": "7.18.6", + "@babel/plugin-transform-class-properties": "7.28.6", + "@babel/plugin-transform-class-static-block": "7.28.6", + "@babel/plugin-transform-modules-commonjs": "7.28.6", + "@babel/plugin-transform-private-methods": "7.28.6", + "@babel/plugin-transform-runtime": "7.28.5", + "@babel/preset-env": "7.28.6", + "@babel/preset-typescript": "7.28.5", + "@babel/runtime": "7.28.6", + "babel-plugin-transform-import-meta": "2.3.3", + "eslint": "9.39.2", + "eslint-config-prettier": "9.1.2", + "eslint-plugin-jest": "28.14.0", + "eslint-plugin-prettier": "5.5.5", "has-flag": "4.0.0", "jest": "29.7.0", "jest-config": "29.7.0", "jest-watcher": "29.7.0", - "lerna": "^9.0.7", - "p-locate": "^5.0.0", - "prettier": "^3.3.3", - "rimraf": "^6.0.1", - "tsx": "^4.19.3", - "webpack-cli": "^5.1.4", + "lerna": "9.0.7", + "p-locate": "5.0.0", + "prettier": "3.8.1", + "rimraf": "6.1.2", + "tsx": "4.21.0", + "webpack-cli": "5.1.4", }, }, "packages/create-dicomweb": { "name": "@radicalimaging/create-dicomweb", - "version": "1.7.4", + "version": "1.7.6", "bin": { "createdicomweb": "bin/createdicomweb.mjs", }, "dependencies": { - "@radicalimaging/cs3d": ">=1.7.0", - "@radicalimaging/static-wado-creator": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "adm-zip": "^0.5.16", - "canvas": ">=3.1.0", - "commander": "^12.0.0", - "config-point": ">=0.5.1", - "dcmjs": "0.50.0", - "dicom-parser": "^1.8.13", - "glob": "^11.0.0", - "node-object-hash": "^3.0.0", - "uuid": "^11.0.2", + "@radicalimaging/cs3d": ">=1.7.6", + "@radicalimaging/static-wado-creator": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", + "adm-zip": "0.5.16", + "canvas": "3.1.0", + "commander": "12.1.0", + "config-point": "0.8.0", + "dcmjs": "0.50.1", + "dicom-parser": "1.8.21", + "glob": "11.1.0", + "node-object-hash": "3.1.1", + "uuid": "11.1.0", }, "devDependencies": { - "must": "^0.13.4", + "must": "0.13.4", }, }, "packages/cs3d": { "name": "@radicalimaging/cs3d", - "version": "1.7.4", + "version": "1.7.6", "dependencies": { - "@cornerstonejs/core": "^4.22.3", + "@cornerstonejs/core": "4.22.3", "canvas": "3.1.0", - "dcmjs": "0.50.0", - "jsdom": "^26.0.0", - "jsdom-context-require": "^5.2.4", + "dcmjs": "0.50.1", + "jsdom": "26.1.0", + "jsdom-context-require": "5.2.7", }, "devDependencies": { - "@babel/plugin-proposal-class-properties": "^7.18.6", - "babel-plugin-transform-import-meta": "^2.3.3", + "@babel/plugin-proposal-class-properties": "7.18.6", + "babel-plugin-transform-import-meta": "2.3.3", }, }, "packages/healthlake": { @@ -85,169 +85,175 @@ "curiestore": "bin/healthlakestore.mjs", }, "dependencies": { - "@radicalimaging/s3-deploy": ">=1.7.0", - "@radicalimaging/static-wado-deploy": ">=1.7.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "config-point": ">=0.5.1", - "cross-env": "^7.0.3", - "dcmjs": "0.50.0", - "dicom-parser": "^1.8.13", - "node-object-hash": "^3.0.0", + "@radicalimaging/s3-deploy": "workspace:*", + "@radicalimaging/static-wado-deploy": "workspace:*", + "@radicalimaging/static-wado-plugins": "workspace:*", + "@radicalimaging/static-wado-util": "workspace:*", + "config-point": "0.8.0", + "cross-env": "7.0.3", + "dcmjs": "0.50.1", + "dicom-parser": "1.8.21", + "node-object-hash": "3.1.1", }, "devDependencies": { - "must": "^0.13.4", - "ts-jest": "^28.0.2", + "must": "0.13.4", + "ts-jest": "28.0.8", }, }, "packages/s3-deploy": { "name": "@radicalimaging/s3-deploy", - "version": "1.7.4", + "version": "1.7.6", "dependencies": { - "@aws-sdk/client-s3": "^3.374.0", - "@aws-sdk/lib-storage": "^3.862.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "aws-cdk-lib": "^2.251.0", - "config-point": ">=0.5.1", - "constructs": "^10.0.94", - "mime-types": "^2.1.35", + "@aws-sdk/client-s3": "3.975.0", + "@aws-sdk/lib-storage": "3.975.0", + "@radicalimaging/static-wado-plugins": "workspace:*", + "@radicalimaging/static-wado-util": "workspace:*", + "aws-cdk-lib": "2.251.0", + "config-point": "0.8.0", + "constructs": "10.4.5", + "mime-types": "2.1.35", }, "devDependencies": { - "aws-cdk": "^2.144.0", + "aws-cdk": "2.1103.0", }, }, "packages/static-wado-creator": { "name": "@radicalimaging/static-wado-creator", - "version": "1.7.4", + "version": "1.7.6", "bin": { "mkdicomweb": "bin/mkdicomweb.mjs", }, "dependencies": { - "@radicalimaging/cs3d": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "canvas": ">=3.1.0", - "config-point": ">=0.5.1", - "dcmjs": "0.50.0", - "dicom-parser": "^1.8.13", - "glob": "^11.0.0", - "node-object-hash": "^3.0.0", - "uuid": "^11.0.2", + "@radicalimaging/cs3d": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", + "canvas": "3.1.0", + "config-point": "0.8.0", + "dcmjs": "0.50.1", + "dicom-parser": "1.8.21", + "glob": "11.1.0", + "node-object-hash": "3.1.1", + "uuid": "11.1.0", }, "devDependencies": { - "must": "^0.13.4", + "must": "0.13.4", }, }, "packages/static-wado-deploy": { "name": "@radicalimaging/static-wado-deploy", - "version": "1.7.4", + "version": "1.7.6", "bin": { "deploydicomweb": "bin/deploydicomweb.mjs", }, "dependencies": { - "@radicalimaging/s3-deploy": ">=1.7.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "config-point": ">=0.5.1", - "node-object-hash": "^3.0.0", + "@radicalimaging/s3-deploy": "workspace:*", + "@radicalimaging/static-wado-plugins": "workspace:*", + "@radicalimaging/static-wado-util": "workspace:*", + "config-point": "0.8.0", + "node-object-hash": "3.1.1", }, "devDependencies": { - "must": "^0.13.4", - "ts-jest": "^28.0.2", + "must": "0.13.4", + "ts-jest": "28.0.8", }, }, "packages/static-wado-plugins": { "name": "@radicalimaging/static-wado-plugins", - "version": "1.7.4", + "version": "1.7.6", "dependencies": { - "@radicalimaging/static-wado-util": ">=1.7.0", - "config-point": ">=0.5.1", - "dcmjs-dimse": "^0.1.30", - "http-proxy-middleware": "^2.0.6", + "@radicalimaging/static-wado-util": ">=1.7.6", + "config-point": "0.8.0", + "dcmjs-dimse": "0.1.31", + "http-proxy-middleware": "2.0.9", }, "devDependencies": { - "must": "^0.13.4", + "must": "0.13.4", }, }, "packages/static-wado-scp": { "name": "@radicalimaging/static-wado-scp", - "version": "1.7.4", + "version": "1.7.6", "bin": { "dicomwebscp": "bin/dicomwebscp.mjs", }, "dependencies": { - "@radicalimaging/static-wado-creator": ">=1.7.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "config-point": ">=0.5.1", - "dcmjs": "0.50.0", - "dcmjs-dimse": "^0.1.30", + "@radicalimaging/static-wado-creator": "workspace:*", + "@radicalimaging/static-wado-plugins": "workspace:*", + "@radicalimaging/static-wado-util": "workspace:*", + "config-point": "0.8.0", + "dcmjs": "0.50.1", + "dcmjs-dimse": "0.1.31", }, "devDependencies": { - "must": "^0.13.4", + "must": "0.13.4", }, }, "packages/static-wado-util": { "name": "@radicalimaging/static-wado-util", - "version": "1.7.4", + "version": "1.7.6", "dependencies": { - "@cornerstonejs/codec-openjpeg": ">=1.2.3", - "@cornerstonejs/codec-openjph": ">=2.4.5", - "@cornerstonejs/dicom-codec": ">=1.0.5", - "commander": "^10.0.0", - "config-point": ">=0.5.1", - "dcmjs": "0.50.0", - "imurmurhash": "^0.1.4", - "jpeg-lossless-decoder-js": ">=2.0.4", - "json5": "^2.2.1", + "@cornerstonejs/codec-openjpeg": "1.3.0", + "@cornerstonejs/codec-openjph": "2.4.7", + "@cornerstonejs/dicom-codec": "1.0.9", + "commander": "10.0.1", + "config-point": "0.8.0", + "dcmjs": "0.50.1", + "imurmurhash": "0.1.4", + "jpeg-lossless-decoder-js": "2.1.2", + "json5": "2.2.3", "loglevel": "1.9.2", - "must": "^0.13.4", - "node-object-hash": "^3.0.0", + "must": "0.13.4", + "node-object-hash": "3.1.1", }, "devDependencies": { - "must": "^0.13.4", + "must": "0.13.4", }, }, "packages/static-wado-webserver": { "name": "@radicalimaging/static-wado-webserver", - "version": "1.7.4", + "version": "1.7.6", "bin": { "dicomwebserver": "bin/dicomwebserver.mjs", "monitordicomwebserver": "bin/monitordicomwebserver.mjs", }, "dependencies": { "@cornerstonejs/dicom-codec": "1.0.9", - "@radicalimaging/create-dicomweb": ">=1.7.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "@radicalimaging/static-wado-webserver": ">=1.7.0", + "@radicalimaging/create-dicomweb": ">=1.7.6", + "@radicalimaging/static-wado-plugins": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", "@saga-bus/core": "0.2.2", - "config-point": ">=0.5.1", + "config-point": "0.8.0", "content-type": "1.0.5", - "cors": "^2.8.5", - "dcmjs": "0.50.0", - "dcmjs-dimse": "^0.1.30", - "dicer": "0.3.1", - "express": "^5.2.1", - "formidable": "^3.5.4", - "morgan": "^1.10.0", - "regenerator-runtime": "^0.14.1", - "rxjs": "^7.5.4", - "ws": "^8.18.0", + "cors": "2.8.6", + "dcmjs": "0.50.1", + "dcmjs-dimse": "0.1.31", + "express": "5.2.1", + "formidable": "3.5.4", + "morgan": "1.10.1", + "regenerator-runtime": "0.14.1", + "rxjs": "7.8.2", + "streamsearch": "1.1.0", + "ws": "8.19.0", }, "devDependencies": { - "@babel/plugin-transform-runtime": "^7.17.0", + "@babel/plugin-transform-runtime": "7.28.5", "jest": "29.7.0", - "must": "^0.13.4", + "must": "0.13.4", }, }, }, + "trustedDependencies": [ + "canvas", + ], "overrides": { - "@cornerstonejs/codec-openjph": ">=2.4.5", + "@babel/plugin-transform-modules-systemjs": "7.29.4", + "@cornerstonejs/codec-openjph": "2.4.7", "@cornerstonejs/dicom-codec": "1.0.9", "@isaacs/brace-expansion": "5.0.1", + "axios": "1.16.1", "canvas": "3.1.0", "dcmjs": "0.50.1", + "fast-uri": "3.1.2", + "fast-xml-builder": "1.2.0", "fast-xml-parser": "5.7.2", "flatted": "3.4.2", "handlebars": "4.7.9", @@ -506,7 +512,7 @@ "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="], - "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.28.5", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew=="], + "@babel/plugin-transform-modules-systemjs": ["@babel/plugin-transform-modules-systemjs@7.29.4", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w=="], "@babel/plugin-transform-modules-umd": ["@babel/plugin-transform-modules-umd@7.27.1", "", { "dependencies": { "@babel/helper-module-transforms": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w=="], @@ -1210,7 +1216,7 @@ "aws-cdk-lib": ["aws-cdk-lib@2.251.0", "", { "dependencies": { "@aws-cdk/asset-awscli-v1": "2.2.273", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.1", "@aws-cdk/cloud-assembly-api": "^2.2.2", "@aws-cdk/cloud-assembly-schema": "^53.18.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.3.3", "ignore": "^5.3.2", "jsonschema": "^1.5.0", "mime-types": "^2.1.35", "minimatch": "^10.2.3", "punycode": "^2.3.1", "semver": "^7.7.4", "table": "^6.9.0", "yaml": "1.10.3" }, "peerDependencies": { "constructs": "^10.5.0" } }, "sha512-H1Jfz2Oyejn+yG24i+By9fZpYfg+E3h1XnFCF2wnt/MyGOTIePRph7MRGkX73ap10ERSpmd0Ly58OLVykFoSQA=="], - "axios": ["axios@1.15.0", "", { "dependencies": { "follow-redirects": "^1.15.11", "form-data": "^4.0.5", "proxy-from-env": "^2.1.0" } }, "sha512-wWyJDlAatxk30ZJer+GeCWS209sA42X+N5jU2jy6oHTp7ufw8uzUTVFBX9+wTfAlhiJXGS0Bq7X6efruWjuK9Q=="], + "axios": ["axios@1.16.1", "", { "dependencies": { "follow-redirects": "^1.16.0", "form-data": "^4.0.5", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } }, "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A=="], "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="], @@ -1468,8 +1474,6 @@ "dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="], - "dicer": ["dicer@0.3.1", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-ObioMtXnmjYs3aRtpIJt9rgQSPCIhKVkFPip+E9GUDyWl8N435znUxK/JfNwGZJ2wnn5JKQ7Ly3vOK5Q5dylGA=="], - "dicom-parser": ["dicom-parser@1.8.21", "", {}, "sha512-lYCweHQDsC8UFpXErPlg86Px2A8bay0HiUY+wzoG3xv5GzgqVHU3lziwSc/Gzn7VV7y2KeP072SzCviuOoU02w=="], "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="], @@ -1590,9 +1594,9 @@ "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], - "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], - "fast-xml-builder": ["fast-xml-builder@1.1.5", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-4TJn/8FKLeslLAH3dnohXqE3QSoxkhvaMzepOIZytwJXZO69Bfz0HBdDHzOTOon6G59Zrk6VQ2bEiv1t61rfkA=="], + "fast-xml-builder": ["fast-xml-builder@1.2.0", "", { "dependencies": { "path-expression-matcher": "^1.5.0", "xml-naming": "^0.1.0" } }, "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q=="], "fast-xml-parser": ["fast-xml-parser@5.7.2", "", { "dependencies": { "@nodable/entities": "^2.1.0", "fast-xml-builder": "^1.1.5", "path-expression-matcher": "^1.5.0", "strnum": "^2.2.3" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-P7oW7tLbYnhOLQk/Gv7cZgzgMPP/XN03K02/Jy6Y/NHzyIAIpxuZIM/YqAkfiXFPxA2CTm7NtCijK9EDu09u2w=="], @@ -2608,6 +2612,8 @@ "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], + "xml-naming": ["xml-naming@0.1.0", "", {}, "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw=="], + "xmlbuilder2": ["xmlbuilder2@3.0.2", "", { "dependencies": { "@oozcitak/dom": "1.15.10", "@oozcitak/infra": "1.0.8", "@oozcitak/util": "8.3.8", "@types/node": "*", "js-yaml": "3.14.0" } }, "sha512-h4MUawGY21CTdhV4xm3DG9dgsqyhDkZvVJBx88beqX8wJs3VgyGQgAn5VreHuae6unTQxh115aMK5InCVmOIKw=="], "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], @@ -2650,6 +2656,8 @@ "@aws-sdk/util-endpoints/@aws-sdk/types": ["@aws-sdk/types@3.972.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug=="], + "@babel/plugin-transform-modules-systemjs/@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], "@inquirer/core/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], @@ -2766,6 +2774,10 @@ "aws-cdk-lib/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "axios/follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "axios/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + "babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="], "basic-auth/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], @@ -3022,6 +3034,14 @@ "@aws-sdk/signature-v4-multi-region/@aws-sdk/middleware-sdk-s3/@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.972.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-RM5Mmo/KJ593iMSrALlHEOcc9YOIyOsDmS5x2NLOMdEmzv1o00fcpAkCQ02IGu1eFneBFT7uX0Mpag0HI+Cz2g=="], + "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], + + "@babel/plugin-transform-modules-systemjs/@babel/traverse/@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + "@isaacs/cliui/string-width/emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], @@ -3078,6 +3098,8 @@ "aws-cdk-lib/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="], + "axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + "bin-links/write-file-atomic/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], "cacache/@npmcli/fs/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..429cd77 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,7 @@ +# https://bun.sh/docs/runtime/bunfig +[install] +frozenLockfile = true +exact = true +saveTextLockfile = true +# Supply-chain knob: skip package versions published in the last 2 days (seconds). +minimumReleaseAge = 172800 diff --git a/bunfig.update-lockfile.toml b/bunfig.update-lockfile.toml new file mode 100644 index 0000000..aa482b6 --- /dev/null +++ b/bunfig.update-lockfile.toml @@ -0,0 +1,3 @@ +# Used only by `bun run install:update-lockfile` — default `bunfig.toml` keeps frozen lockfile. +[install] +frozenLockfile = false diff --git a/package.json b/package.json index 5bbd94a..80be6a5 100644 --- a/package.json +++ b/package.json @@ -1,61 +1,69 @@ { "name": "static-dicomweb", "private": true, + "packageManager": "bun@1.3.13", "engines": { "node": ">=14.18.1", "npm": ">=6.14.15" }, "devDependencies": { - "@babel/core": "^7.26.0", - "@babel/eslint-parser": "^7.25.9", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-transform-class-properties": "^7.25.9", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-modules-commonjs": "^7.16.8", - "@babel/plugin-transform-private-methods": "^7.28.6", - "@babel/plugin-transform-runtime": "^7.17.0", - "@babel/preset-env": "^7.16.11", - "@babel/preset-typescript": "^7.28.5", - "@babel/runtime": "^7.17.0", - "babel-plugin-transform-import-meta": "^2.3.3", - "eslint": "^9.14.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-jest": "^28.8.3", - "eslint-plugin-prettier": "^5.2.1", + "@babel/core": "7.28.6", + "@babel/eslint-parser": "7.28.6", + "@babel/plugin-proposal-class-properties": "7.18.6", + "@babel/plugin-transform-class-properties": "7.28.6", + "@babel/plugin-transform-class-static-block": "7.28.6", + "@babel/plugin-transform-modules-commonjs": "7.28.6", + "@babel/plugin-transform-private-methods": "7.28.6", + "@babel/plugin-transform-runtime": "7.28.5", + "@babel/preset-env": "7.28.6", + "@babel/preset-typescript": "7.28.5", + "@babel/runtime": "7.28.6", + "babel-plugin-transform-import-meta": "2.3.3", + "eslint": "9.39.2", + "eslint-config-prettier": "9.1.2", + "eslint-plugin-jest": "28.14.0", + "eslint-plugin-prettier": "5.5.5", "has-flag": "4.0.0", "jest": "29.7.0", "jest-config": "29.7.0", "jest-watcher": "29.7.0", - "lerna": "^9.0.7", - "p-locate": "^5.0.0", - "prettier": "^3.3.3", - "rimraf": "^6.0.1", - "tsx": "^4.19.3", - "webpack-cli": "^5.1.4" + "lerna": "9.0.7", + "p-locate": "5.0.0", + "prettier": "3.8.1", + "rimraf": "6.1.2", + "tsx": "4.21.0", + "webpack-cli": "5.1.4" }, "dependencies": { "@cornerstonejs/dicom-codec": "1.0.9", "canvas": "3.1.0", - "dcmjs": "0.50.0", - "typescript": "^5.8.2" + "dcmjs": "0.50.1", + "typescript": "5.9.3" }, - "resolutions": { + "overrides": { + "@babel/plugin-transform-modules-systemjs": "7.29.4", + "@cornerstonejs/codec-openjph": "2.4.7", "@cornerstonejs/dicom-codec": "1.0.9", - "@cornerstonejs/codec-openjph": ">=2.4.5", - "dcmjs": "0.50.1", + "@isaacs/brace-expansion": "5.0.1", + "axios": "1.16.1", "canvas": "3.1.0", - "has-flag": "4.0.0", - "node-object-hash": "3.1.1", + "dcmjs": "0.50.1", + "fast-uri": "3.1.2", + "fast-xml-builder": "1.2.0", "fast-xml-parser": "5.7.2", + "flatted": "3.4.2", "handlebars": "4.7.9", + "has-flag": "4.0.0", "lodash": "4.18.1", - "@isaacs/brace-expansion": "5.0.1", - "picomatch": "2.3.2", - "flatted": "3.4.2", + "node-object-hash": "3.1.1", "path-to-regexp": "8.4.2", + "picomatch": "2.3.2", "serialize-javascript": "7.0.5", "yallist": "4.0.0" }, + "trustedDependencies": [ + "canvas" + ], "workspaces": [ "packages/*" ], @@ -66,12 +74,15 @@ "docker:run": "docker run -it --mount type=bind,source=/c/dicomweb,target=/dicomweb --mount type=bind,source=/a/dicom,target=/dicom -p 25080:5000 --rm --entrypoint /bin/bash braveheartsoftware/static-dicomweb:latest", "docker:dicomwebserver": "docker run -it --mount type=bind,source=/c/dicomweb,target=/dicomweb -p 25080:5000 --rm braveheartsoftware/static-dicomweb:latest", "test": "lerna run test --parallel --stream --", - "test:ci": "yarn run test", + "supply-chain:ci": "bash ./scripts/ci-supply-chain.sh", + "test:github": "bun run supply-chain:ci && bun run test", + "test:ci": "bun run test:github", "build": "lerna run build --stream --concurrency 1", "build:parallel": "lerna run build --stream", "build:serial": "lerna run build --stream --concurrency 1", "build:esm": "lerna run build:esm --stream", - "build:ci": "yarn run build", + "build:github": "bun run supply-chain:ci && bun run build", + "build:ci": "bun run build:github", "clean": "lerna run clean --stream", "link:exec": "lerna run link:exec --stream", "lint": "lerna run lint --parallel --stream", @@ -82,6 +93,7 @@ "publish": "echo Use yarn publishJS --new-version VERSION --otp XXXXXX", "publishJS": "lerna run publishJS --", "pack:js": "lerna run pack:js --", - "reset:hooks": "git config core.hooksPath '.git' && echo 'git hooks was reset'" + "reset:hooks": "git config core.hooksPath '.git' && echo 'git hooks was reset'", + "install:update-lockfile": "bun install --config=bunfig.update-lockfile.toml" } -} \ No newline at end of file +} diff --git a/packages/create-dicomweb/package.json b/packages/create-dicomweb/package.json index 12d8005..bf80247 100644 --- a/packages/create-dicomweb/package.json +++ b/packages/create-dicomweb/package.json @@ -50,21 +50,21 @@ "verbose": true }, "dependencies": { - "@radicalimaging/cs3d": ">=1.7.0", - "@radicalimaging/static-wado-creator": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "adm-zip": "^0.5.16", - "canvas": ">=3.1.0", - "commander": "^12.0.0", - "config-point": ">=0.5.1", - "dcmjs": "0.50.0", - "dicom-parser": "^1.8.13", - "glob": "^11.0.0", - "node-object-hash": "^3.0.0", - "uuid": "^11.0.2" + "@radicalimaging/cs3d": ">=1.7.6", + "@radicalimaging/static-wado-creator": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", + "adm-zip": "0.5.16", + "canvas": "3.1.0", + "commander": "12.1.0", + "config-point": "0.8.0", + "dcmjs": "0.50.1", + "dicom-parser": "1.8.21", + "glob": "11.1.0", + "node-object-hash": "3.1.1", + "uuid": "11.1.0" }, "devDependencies": { - "must": "^0.13.4" + "must": "0.13.4" }, "scripts": { "test": "jest --config ./jest.config.cjs", diff --git a/packages/cs3d/package.json b/packages/cs3d/package.json index aec9e85..b6b720d 100644 --- a/packages/cs3d/package.json +++ b/packages/cs3d/package.json @@ -74,14 +74,14 @@ "publishJS": "yarn publish --no-git-tag-version" }, "dependencies": { - "@cornerstonejs/core": "^4.22.3", + "@cornerstonejs/core": "4.22.3", "canvas": "3.1.0", - "dcmjs": "0.50.0", - "jsdom": "^26.0.0", - "jsdom-context-require": "^5.2.4" + "dcmjs": "0.50.1", + "jsdom": "26.1.0", + "jsdom-context-require": "5.2.7" }, "devDependencies": { - "@babel/plugin-proposal-class-properties": "^7.18.6", - "babel-plugin-transform-import-meta": "^2.3.3" + "@babel/plugin-proposal-class-properties": "7.18.6", + "babel-plugin-transform-import-meta": "2.3.3" } } diff --git a/packages/healthlake/package.json b/packages/healthlake/package.json index 1d64795..d994f53 100644 --- a/packages/healthlake/package.json +++ b/packages/healthlake/package.json @@ -55,18 +55,18 @@ "verbose": true }, "dependencies": { - "@radicalimaging/s3-deploy": ">=1.7.0", - "@radicalimaging/static-wado-deploy": ">=1.7.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "config-point": ">=0.5.1", - "cross-env": "^7.0.3", - "dcmjs": "0.50.0", - "dicom-parser": "^1.8.13", - "node-object-hash": "^3.0.0" + "@radicalimaging/s3-deploy": ">=1.7.6", + "@radicalimaging/static-wado-deploy": ">=1.7.6", + "@radicalimaging/static-wado-plugins": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", + "config-point": "0.8.0", + "cross-env": "7.0.3", + "dcmjs": "0.50.1", + "dicom-parser": "1.8.21", + "node-object-hash": "3.1.1" }, "devDependencies": { - "must": "^0.13.4", - "ts-jest": "^28.0.2" + "must": "0.13.4", + "ts-jest": "28.0.8" } } \ No newline at end of file diff --git a/packages/s3-deploy/package.json b/packages/s3-deploy/package.json index 068f771..79bc247 100644 --- a/packages/s3-deploy/package.json +++ b/packages/s3-deploy/package.json @@ -82,16 +82,16 @@ "verbose": true }, "dependencies": { - "@aws-sdk/client-s3": "^3.374.0", - "@aws-sdk/lib-storage": "^3.862.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "aws-cdk-lib": "^2.251.0", - "config-point": ">=0.5.1", - "constructs": "^10.0.94", - "mime-types": "^2.1.35" + "@aws-sdk/client-s3": "3.975.0", + "@aws-sdk/lib-storage": "3.975.0", + "@radicalimaging/static-wado-plugins": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", + "aws-cdk-lib": "2.251.0", + "config-point": "0.8.0", + "constructs": "10.4.5", + "mime-types": "2.1.35" }, "devDependencies": { - "aws-cdk": "^2.144.0" + "aws-cdk": "2.1103.0" } } diff --git a/packages/static-wado-creator/package.json b/packages/static-wado-creator/package.json index 62e4586..c35976f 100644 --- a/packages/static-wado-creator/package.json +++ b/packages/static-wado-creator/package.json @@ -66,17 +66,17 @@ "verbose": true }, "dependencies": { - "@radicalimaging/cs3d": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "canvas": ">=3.1.0", - "config-point": ">=0.5.1", - "dcmjs": "0.50.0", - "dicom-parser": "^1.8.13", - "glob": "^11.0.0", - "node-object-hash": "^3.0.0", - "uuid": "^11.0.2" + "@radicalimaging/cs3d": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", + "canvas": "3.1.0", + "config-point": "0.8.0", + "dcmjs": "0.50.1", + "dicom-parser": "1.8.21", + "glob": "11.1.0", + "node-object-hash": "3.1.1", + "uuid": "11.1.0" }, "devDependencies": { - "must": "^0.13.4" + "must": "0.13.4" } } diff --git a/packages/static-wado-deploy/package.json b/packages/static-wado-deploy/package.json index 8eba369..620dc49 100644 --- a/packages/static-wado-deploy/package.json +++ b/packages/static-wado-deploy/package.json @@ -53,15 +53,15 @@ "verbose": true }, "dependencies": { - "@radicalimaging/s3-deploy": ">=1.7.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "config-point": ">=0.5.1", - "node-object-hash": "^3.0.0" + "@radicalimaging/s3-deploy": ">=1.7.6", + "@radicalimaging/static-wado-plugins": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", + "config-point": "0.8.0", + "node-object-hash": "3.1.1" }, "devDependencies": { - "must": "^0.13.4", - "ts-jest": "^28.0.2" + "must": "0.13.4", + "ts-jest": "28.0.8" }, "scripts": { "test": "jest --config ./jest.config.js", diff --git a/packages/static-wado-plugins/package.json b/packages/static-wado-plugins/package.json index 74ca112..25701f3 100644 --- a/packages/static-wado-plugins/package.json +++ b/packages/static-wado-plugins/package.json @@ -69,12 +69,12 @@ "verbose": true }, "dependencies": { - "@radicalimaging/static-wado-util": ">=1.7.0", - "config-point": ">=0.5.1", - "dcmjs-dimse": "^0.1.30", - "http-proxy-middleware": "^2.0.6" + "@radicalimaging/static-wado-util": ">=1.7.6", + "config-point": "0.8.0", + "dcmjs-dimse": "0.1.31", + "http-proxy-middleware": "2.0.9" }, "devDependencies": { - "must": "^0.13.4" + "must": "0.13.4" } } diff --git a/packages/static-wado-scp/package.json b/packages/static-wado-scp/package.json index 0efe29a..714d522 100644 --- a/packages/static-wado-scp/package.json +++ b/packages/static-wado-scp/package.json @@ -63,14 +63,14 @@ "publishJS": "yarn publish --no-git-tag-version" }, "dependencies": { - "@radicalimaging/static-wado-creator": ">=1.7.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "config-point": ">=0.5.1", - "dcmjs": "0.50.0", - "dcmjs-dimse": "^0.1.30" + "@radicalimaging/static-wado-creator": ">=1.7.6", + "@radicalimaging/static-wado-plugins": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", + "config-point": "0.8.0", + "dcmjs": "0.50.1", + "dcmjs-dimse": "0.1.31" }, "devDependencies": { - "must": "^0.13.4" + "must": "0.13.4" } } diff --git a/packages/static-wado-util/package.json b/packages/static-wado-util/package.json index 105f2b5..13fb2e3 100644 --- a/packages/static-wado-util/package.json +++ b/packages/static-wado-util/package.json @@ -56,18 +56,18 @@ "bin" ], "dependencies": { - "@cornerstonejs/codec-openjpeg": ">=1.2.3", - "@cornerstonejs/codec-openjph": ">=2.4.5", - "@cornerstonejs/dicom-codec": ">=1.0.5", - "commander": "^10.0.0", - "config-point": ">=0.5.1", - "dcmjs": "0.50.0", - "imurmurhash": "^0.1.4", - "jpeg-lossless-decoder-js": ">=2.0.4", - "json5": "^2.2.1", + "@cornerstonejs/codec-openjpeg": "1.3.0", + "@cornerstonejs/codec-openjph": "2.4.7", + "@cornerstonejs/dicom-codec": "1.0.9", + "commander": "10.0.1", + "config-point": "0.8.0", + "dcmjs": "0.50.1", + "imurmurhash": "0.1.4", + "jpeg-lossless-decoder-js": "2.1.2", + "json5": "2.2.3", "loglevel": "1.9.2", - "must": "^0.13.4", - "node-object-hash": "^3.0.0" + "must": "0.13.4", + "node-object-hash": "3.1.1" }, "scripts": { "test": "jest --config ./jest.config.cjs", @@ -81,6 +81,6 @@ "publishJS": "yarn publish --no-git-tag-version" }, "devDependencies": { - "must": "^0.13.4" + "must": "0.13.4" } } diff --git a/packages/static-wado-webserver/jest.config.js b/packages/static-wado-webserver/jest.config.js index 7aca018..0df36f7 100644 --- a/packages/static-wado-webserver/jest.config.js +++ b/packages/static-wado-webserver/jest.config.js @@ -2,6 +2,11 @@ const baseConfig = require('../../.config/jest/jest.config.cjs'); module.exports = { ...baseConfig, + moduleNameMapper: { + ...baseConfig.moduleNameMapper, + // Vendored dicer: compiled to dist/ by tsc (see pretest); lib/ keeps TypeScript only + '^\\.\\./\\.\\./vendor/dicer/(.*)\\.js$': '/dist/vendor/dicer/$1.js', + }, moduleFileExtensions: ['js', 'jsx', 'ts', 'tsx', 'mjs'], transformIgnorePatterns: [ // Transform all ESM modules in node_modules, including Bun's .bun directory structure diff --git a/packages/static-wado-webserver/lib/controllers/server/multipartStream.mjs b/packages/static-wado-webserver/lib/controllers/server/multipartStream.mjs index 10706b8..df654d1 100644 --- a/packages/static-wado-webserver/lib/controllers/server/multipartStream.mjs +++ b/packages/static-wado-webserver/lib/controllers/server/multipartStream.mjs @@ -1,4 +1,4 @@ -import Dicer from 'dicer'; +import Dicer from '../../vendor/dicer/Dicer.js'; import { randomUUID } from 'node:crypto'; import { data } from 'dcmjs'; import { parse as parseContentType } from 'content-type'; diff --git a/packages/static-wado-webserver/lib/vendor/dicer/Dicer.ts b/packages/static-wado-webserver/lib/vendor/dicer/Dicer.ts new file mode 100644 index 0000000..f3dc4b1 --- /dev/null +++ b/packages/static-wado-webserver/lib/vendor/dicer/Dicer.ts @@ -0,0 +1,233 @@ +import { Writable, type ReadableOptions } from 'node:stream'; +import StreamSearch from 'streamsearch'; +import PartStream from './PartStream.js'; +import HeaderParser from './HeaderParser.js'; +import type { DicerConfig, MultipartHeaders } from './types.js'; + +const DASH = 45; +const B_ONEDASH = Buffer.from('-'); +const B_CRLF = Buffer.from('\r\n'); +const EMPTY_FN = () => {}; + +export default class Dicer extends Writable { + _bparser?: StreamSearch; + _hparser?: HeaderParser; + _headerFirst?: boolean; + _dashes = 0; + _parts = 0; + _finished = false; + _realFinish = false; + _isPreamble = true; + _justMatched = false; + _firstWrite = true; + _inHeader = true; + _part?: PartStream; + _cb?: (error?: Error | null) => void; + _ignoreData = false; + _partOpts: ReadableOptions = {}; + _pause = false; + + constructor(cfg?: DicerConfig) { + super(cfg); + + if (!cfg || (!cfg.headerFirst && typeof cfg.boundary !== 'string')) + throw new TypeError('Boundary required'); + + if (typeof cfg.boundary === 'string') this.setBoundary(cfg.boundary); + else this._bparser = undefined; + + this._headerFirst = cfg.headerFirst; + + this._partOpts = typeof cfg.partHwm === 'number' ? { highWaterMark: cfg.partHwm } : {}; + + this._hparser = new HeaderParser(cfg); + this._hparser.on('header', (header: MultipartHeaders) => { + this._inHeader = false; + this._part?.emit('header', header); + }); + this._hparser.on('error', (err: Error) => { + if (this._part && !this._ignoreData) { + this._part.emit('error', err); + this._part.push(null); + } + this.emit('error', err); + }); + } + + emit(event: string | symbol, ...args: unknown[]): boolean { + if (event !== 'finish' || this._realFinish) { + return super.emit(event, ...args); + } + + if (this._finished) return false; + + process.nextTick(() => { + this.emit('error', new Error('Unexpected end of multipart data')); + + if (this._part && !this._ignoreData) { + const type = this._isPreamble ? 'Preamble' : 'Part'; + this._part.emit( + 'error', + new Error( + `${type} terminated early due to unexpected end of multipart data` + ) + ); + this._part.push(null); + process.nextTick(() => { + this._realFinish = true; + this.emit('finish'); + this._realFinish = false; + }); + return; + } + + this._realFinish = true; + this.emit('finish'); + this._realFinish = false; + }); + + return false; + } + + _write(data: Buffer, _encoding: BufferEncoding, cb: (error?: Error | null) => void): void { + if (!this._hparser || !this._bparser) return cb(); + + if (this._headerFirst && this._isPreamble) { + if (!this._part) { + this._part = new PartStream(this._partOpts); + if (this.listenerCount('preamble') > 0) this.emit('preamble', this._part); + else ignore(this); + } + const r = this._hparser.push(data); + if (!this._inHeader && r !== undefined && r < data.length) data = data.subarray(r); + else return cb(); + } + + if (this._firstWrite) { + this._bparser.push(B_CRLF); + this._firstWrite = false; + } + + this._bparser.push(data); + + if (this._pause) this._cb = cb; + else cb(); + } + + reset(): void { + this._part = undefined; + this._bparser = undefined; + this._hparser = undefined; + } + + setBoundary(boundary: string): void { + this._bparser = new StreamSearch(`\r\n--${boundary}`, onInfo.bind(this)); + } +} + +function onInfo( + this: Dicer, + isMatch: boolean, + data: Buffer | false, + start: number, + end: number +): void { + let buf: Buffer | undefined; + let i = 0; + let r: number | undefined; + let ev: 'preamble' | 'part'; + let shouldWriteMore = true; + + if (!this._part && this._justMatched && data) { + while (this._dashes < 2 && start + i < end) { + if (data[start + i] === DASH) { + ++i; + ++this._dashes; + } else { + if (this._dashes) buf = B_ONEDASH; + this._dashes = 0; + break; + } + } + if (this._dashes === 2) { + if (start + i < end && this.listenerCount('trailer') > 0) + this.emit('trailer', data.subarray(start + i, end)); + this.reset(); + this._finished = true; + if (this._parts === 0) { + this._realFinish = true; + this.emit('finish'); + this._realFinish = false; + } + } + if (this._dashes) return; + } + if (this._justMatched) this._justMatched = false; + if (!this._part) { + this._part = new PartStream(this._partOpts); + this._part._read = () => { + unpause(this); + }; + ev = this._isPreamble ? 'preamble' : 'part'; + if (this.listenerCount(ev) > 0) this.emit(ev, this._part); + else ignore(this); + if (!this._isPreamble) this._inHeader = true; + } + const part = this._part; + const hparser = this._hparser; + if (data && start < end && !this._ignoreData && part && hparser) { + if (this._isPreamble || !this._inHeader) { + if (buf) shouldWriteMore = part.push(buf) !== false; + shouldWriteMore = part.push(data.subarray(start, end)) !== false; + if (!shouldWriteMore) this._pause = true; + } else if (!this._isPreamble && this._inHeader) { + if (buf) hparser.push(buf); + r = hparser.push(data.subarray(start, end)); + if (!this._inHeader && r !== undefined && r < end) + onInfo.call(this, false, data, start + r, end); + } + } + if (isMatch && part && hparser) { + hparser.reset(); + if (this._isPreamble) { + this._isPreamble = false; + } else { + ++this._parts; + part.on('end', () => { + if (--this._parts === 0) { + if (this._finished) { + this._realFinish = true; + this.emit('finish'); + this._realFinish = false; + } else { + unpause(this); + } + } + }); + } + part.push(null); + this._part = undefined; + this._ignoreData = false; + this._justMatched = true; + this._dashes = 0; + } +} + +function ignore(self: Dicer): void { + if (self._part && !self._ignoreData) { + self._ignoreData = true; + self._part.on('error', EMPTY_FN); + self._part.resume(); + } +} + +function unpause(self: Dicer): void { + if (!self._pause) return; + + self._pause = false; + if (self._cb) { + const cb = self._cb; + self._cb = undefined; + cb(); + } +} diff --git a/packages/static-wado-webserver/lib/vendor/dicer/HeaderParser.ts b/packages/static-wado-webserver/lib/vendor/dicer/HeaderParser.ts new file mode 100644 index 0000000..22aeb14 --- /dev/null +++ b/packages/static-wado-webserver/lib/vendor/dicer/HeaderParser.ts @@ -0,0 +1,117 @@ +import { EventEmitter } from 'node:events'; +import StreamSearch from 'streamsearch'; +import type { HeaderParserConfig, MultipartHeaders } from './types.js'; + +const B_DCRLF = Buffer.from('\r\n\r\n'); +const RE_CRLF = /\r\n/g; +// eslint-disable-next-line no-control-regex +const RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/; +const MAX_HEADER_PAIRS = 2000; // From node's http.js +const MAX_HEADER_SIZE = 64 * 1024; + +export default class HeaderParser extends EventEmitter { + nread = 0; + npairs = 0; + maxHeaderPairs: number; + buffer = ''; + header: MultipartHeaders = {}; + finished = false; + ss: StreamSearch; + + constructor(cfg?: HeaderParserConfig) { + super(); + + this.maxHeaderPairs = + cfg && typeof cfg.maxHeaderPairs === 'number' ? cfg.maxHeaderPairs : MAX_HEADER_PAIRS; + this.ss = new StreamSearch(B_DCRLF, (isMatch, data, start, end) => { + if (this.finished) return; + + if (data) { + const chunkLen = end - start; + if (this.nread + chunkLen > MAX_HEADER_SIZE) { + this._abortOversized(); + return; + } + this.nread += chunkLen; + this.buffer += data.toString('latin1', start, end); + } + if (isMatch) this._finish(); + }); + } + + _abortOversized(): void { + if (this.finished) return; + this.finished = true; + this.emit( + 'error', + new Error(`Multipart part header exceeds maximum size of ${MAX_HEADER_SIZE} bytes`) + ); + } + + push(data: Buffer): number | undefined { + const r = this.ss.push(data); + if (this.finished) return r; + } + + reset(): void { + this.finished = false; + this.buffer = ''; + this.header = {}; + this.ss.reset(); + } + + _finish(): void { + let hadError = false; + if (this.buffer) hadError = !parseHeader(this); + this.ss.matches = this.ss.maxMatches; + const header = this.header; + this.header = {}; + this.buffer = ''; + this.finished = true; + this.nread = this.npairs = 0; + if (!hadError) this.emit('header', header); + } +} + +function parseHeader(self: HeaderParser): boolean { + if (self.npairs === self.maxHeaderPairs) return true; + + const lines = self.buffer.split(RE_CRLF); + const len = lines.length; + let m: RegExpExecArray | null; + let h: string | undefined; + let modded = false; + + for (let i = 0; i < len; ++i) { + const line = lines[i]; + if (line.length === 0) continue; + + if (line[0] === '\t' || line[0] === ' ') { + if (!h) { + self.emit('error', new Error('Unexpected folded header value')); + return false; + } + const values = self.header[h]; + if (values) values[values.length - 1] += line; + } else { + m = RE_HDR.exec(line); + if (m) { + h = m[1].toLowerCase(); + if (m[2]) { + if (self.header[h] === undefined) self.header[h] = [m[2]]; + else self.header[h].push(m[2]); + } else { + self.header[h] = ['']; + } + if (++self.npairs === self.maxHeaderPairs) break; + } else { + self.buffer = line; + modded = true; + break; + } + } + } + if (!modded) self.buffer = ''; + + return true; +} diff --git a/packages/static-wado-webserver/lib/vendor/dicer/LICENSE b/packages/static-wado-webserver/lib/vendor/dicer/LICENSE new file mode 100644 index 0000000..f261aae --- /dev/null +++ b/packages/static-wado-webserver/lib/vendor/dicer/LICENSE @@ -0,0 +1,31 @@ +DICER COMPONENT LICENSE + +The MIT License below applies only to the dicer multipart parser files +in this directory (the "Dicer Component"). It does not apply to +@radicalimaging/static-wado-webserver, the static-dicomweb project, or +any other software outside this directory. + +This component is derived from dicer by Brian White +(https://github.com/mscdex/dicer). + +--- + +Copyright Brian White. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/packages/static-wado-webserver/lib/vendor/dicer/LICENSE.dicer.md b/packages/static-wado-webserver/lib/vendor/dicer/LICENSE.dicer.md new file mode 100644 index 0000000..474d499 --- /dev/null +++ b/packages/static-wado-webserver/lib/vendor/dicer/LICENSE.dicer.md @@ -0,0 +1,5 @@ +# Dicer component license + +The MIT license in [LICENSE](./LICENSE) applies **only** to the dicer multipart parser in this directory. It does **not** apply to `@radicalimaging/static-wado-webserver`, the static-dicomweb monorepo, or any other bundled software. + +Maintained fork of [dicer](https://github.com/mscdex/dicer) by Brian White. TypeScript sources live in this directory; `bun run build` (`tsc` on `lib/`) emits compiled dicer to `dist/vendor/dicer/`. Standalone package: `z:\src\dicer` (releases TBD). diff --git a/packages/static-wado-webserver/lib/vendor/dicer/PartStream.ts b/packages/static-wado-webserver/lib/vendor/dicer/PartStream.ts new file mode 100644 index 0000000..6c62708 --- /dev/null +++ b/packages/static-wado-webserver/lib/vendor/dicer/PartStream.ts @@ -0,0 +1,5 @@ +import { Readable } from 'node:stream'; + +export default class PartStream extends Readable { + _read(_size: number): void {} +} diff --git a/packages/static-wado-webserver/lib/vendor/dicer/index.ts b/packages/static-wado-webserver/lib/vendor/dicer/index.ts new file mode 100644 index 0000000..e0260e5 --- /dev/null +++ b/packages/static-wado-webserver/lib/vendor/dicer/index.ts @@ -0,0 +1,5 @@ +export { default } from './Dicer.js'; +export { default as Dicer } from './Dicer.js'; +export { default as HeaderParser } from './HeaderParser.js'; +export { default as PartStream } from './PartStream.js'; +export type { DicerConfig, HeaderParserConfig, MultipartHeaders } from './types.js'; diff --git a/packages/static-wado-webserver/lib/vendor/dicer/streamsearch.d.ts b/packages/static-wado-webserver/lib/vendor/dicer/streamsearch.d.ts new file mode 100644 index 0000000..020f271 --- /dev/null +++ b/packages/static-wado-webserver/lib/vendor/dicer/streamsearch.d.ts @@ -0,0 +1,16 @@ +declare module 'streamsearch' { + type StreamSearchCallback = ( + isMatch: boolean, + data: Buffer | false, + start: number, + end: number + ) => void; + + export default class StreamSearch { + constructor(needle: string | Buffer, cb: StreamSearchCallback); + push(data: Buffer): number | undefined; + reset(): void; + matches: number; + maxMatches: number; + } +} diff --git a/packages/static-wado-webserver/lib/vendor/dicer/types.ts b/packages/static-wado-webserver/lib/vendor/dicer/types.ts new file mode 100644 index 0000000..ef82b40 --- /dev/null +++ b/packages/static-wado-webserver/lib/vendor/dicer/types.ts @@ -0,0 +1,14 @@ +import type { WritableOptions } from 'node:stream'; + +/** Parsed multipart part headers (lowercase names, array values). */ +export type MultipartHeaders = Record; + +export interface HeaderParserConfig { + maxHeaderPairs?: number; +} + +export interface DicerConfig extends WritableOptions, HeaderParserConfig { + boundary?: string; + headerFirst?: boolean; + partHwm?: number; +} diff --git a/packages/static-wado-webserver/package.json b/packages/static-wado-webserver/package.json index 8e3deae..2b515d9 100644 --- a/packages/static-wado-webserver/package.json +++ b/packages/static-wado-webserver/package.json @@ -52,40 +52,40 @@ "monitordicomwebserver": "bin/monitordicomwebserver.mjs" }, "scripts": { + "pretest": "bun run build:esm", "test": "jest --config ./jest.config.js", "unlink:exec": "bun unlink || echo Already unlinked", "link:exec": "bun run unlink:exec && bun link", "build": "bun run build:esm", - "build:esm": "bun -e \"import { mkdirSync, cpSync } from 'node:fs'; mkdirSync('dist', { recursive: true }); cpSync('lib', 'dist', { recursive: true });\"", + "build:esm": "tsc --project ./tsconfig.json", "clean": "rimraf dist", - "lint": "npx eslint --color \"**/*.{js,mjs,cjs}\"", - "lint:fix": "npx eslint --fix --color \"**/*.{js,mjs,cjs}\"", + "lint": "npx eslint --color \"**/*.{js,mjs,cjs,ts}\"", + "lint:fix": "npx eslint --fix --color \"**/*.{js,mjs,cjs,ts}\"", "pack:js": "npm pack --", "publishJS": "yarn publish" }, "dependencies": { "@cornerstonejs/dicom-codec": "1.0.9", - "@radicalimaging/create-dicomweb": ">=1.7.0", - "@radicalimaging/static-wado-plugins": ">=1.7.0", - "@radicalimaging/static-wado-util": ">=1.7.0", - "@radicalimaging/static-wado-webserver": ">=1.7.0", + "@radicalimaging/create-dicomweb": ">=1.7.6", + "@radicalimaging/static-wado-plugins": ">=1.7.6", + "@radicalimaging/static-wado-util": ">=1.7.6", "@saga-bus/core": "0.2.2", - "config-point": ">=0.5.1", + "config-point": "0.8.0", "content-type": "1.0.5", - "cors": "^2.8.5", - "dcmjs": "0.50.0", - "dcmjs-dimse": "^0.1.30", - "dicer": "0.3.1", - "express": "^5.2.1", - "formidable": "^3.5.4", - "morgan": "^1.10.0", - "regenerator-runtime": "^0.14.1", - "rxjs": "^7.5.4", - "ws": "^8.18.0" + "cors": "2.8.6", + "dcmjs": "0.50.1", + "dcmjs-dimse": "0.1.31", + "express": "5.2.1", + "formidable": "3.5.4", + "morgan": "1.10.1", + "regenerator-runtime": "0.14.1", + "rxjs": "7.8.2", + "streamsearch": "1.1.0", + "ws": "8.19.0" }, "devDependencies": { - "@babel/plugin-transform-runtime": "^7.17.0", + "@babel/plugin-transform-runtime": "7.28.5", "jest": "29.7.0", - "must": "^0.13.4" + "must": "0.13.4" } } diff --git a/packages/static-wado-webserver/tsconfig.json b/packages/static-wado-webserver/tsconfig.json new file mode 100644 index 0000000..60e4f24 --- /dev/null +++ b/packages/static-wado-webserver/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./lib", + "module": "ESNext", + "moduleResolution": "bundler", + "allowJs": true, + "declaration": false, + "skipLibCheck": true, + "noCheck": true + }, + "include": ["./lib/**/*"] +} diff --git a/scripts/check-pinned-versions.sh b/scripts/check-pinned-versions.sh new file mode 100644 index 0000000..4ad6f6e --- /dev/null +++ b/scripts/check-pinned-versions.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +# Fails if any package.json dependency version is not pinned (no ^ ~ * >= ranges). +# Allows workspace:, file:, npm:, and http(s): specifiers. +# Allows >=X.Y.Z when X.Y.Z matches a monorepo workspace package version. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +declare -A WORKSPACE_VERSIONS=() + +load_workspace_versions() { + local pkg name version + while IFS= read -r -d '' pkg; do + name="$(jq -r '.name // empty' "$pkg" 2>/dev/null || true)" + version="$(jq -r '.version // empty' "$pkg" 2>/dev/null || true)" + [[ -z "$name" || -z "$version" ]] && continue + WORKSPACE_VERSIONS["$name"]="$version" + done < <(find "$REPO_ROOT/packages" -mindepth 2 -maxdepth 2 -name package.json -print0 2>/dev/null || true) +} + +is_workspace_gte() { + local dep_name="$1" + local version="$2" + local ws_version min_version + + ws_version="${WORKSPACE_VERSIONS[$dep_name]:-}" + [[ -z "$ws_version" ]] && return 1 + [[ "$version" =~ ^\>=([0-9]+\.[0-9]+\.[0-9]+(-[[:alnum:].-]+)?(\+[[:alnum:].-]+)?)$ ]] || return 1 + min_version="${BASH_REMATCH[1]}" + [[ "$min_version" == "$ws_version" ]] +} + +is_pinned() { + local dep_name="${1:-}" + local version="$2" + [[ -z "$version" ]] && return 1 + case "$version" in + workspace:*|file:*|npm:*) return 0 ;; + http://*|https://*) return 0 ;; + esac + if [[ -n "$dep_name" ]] && is_workspace_gte "$dep_name" "$version"; then + return 0 + fi + case "$version" in + ^*) return 1 ;; + *'~'*) return 1 ;; + *'*'*) return 1 ;; + *'>='*) return 1 ;; + *'<='*) return 1 ;; + esac + return 0 +} + +check_dep_block() { + local pkg="$1" + local block="$2" + local entry name version + local block_failed=0 + + while IFS= read -r entry || [[ -n "${entry:-}" ]]; do + [[ -z "$entry" ]] && continue + name="${entry%%$'\t'*}" + version="${entry#*$'\t'}" + if ! is_pinned "$name" "$version"; then + echo "Unpinned dependency in ${pkg} ${block}: ${name} -> ${version}" >&2 + block_failed=1 + fi + done < <(jq -r --arg b "$block" '.[$b] // {} | to_entries[] | "\(.key)\t\(.value|tostring)"' "$pkg" 2>/dev/null || true) + + [[ "$block_failed" -eq 0 ]] +} + +load_workspace_versions + +failed=0 +while IFS= read -r -d '' pkg; do + for block in dependencies devDependencies optionalDependencies peerDependencies overrides; do + if ! check_dep_block "$pkg" "$block"; then + failed=1 + fi + done +done < <(find "$REPO_ROOT" -name package.json -not -path '*/node_modules/*' -print0) + +exit "$failed" diff --git a/scripts/ci-supply-chain.sh b/scripts/ci-supply-chain.sh new file mode 100644 index 0000000..0716e8d --- /dev/null +++ b/scripts/ci-supply-chain.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash +# CI supply-chain checks: pinned versions, frozen lockfile sync, audit only when bun.lock changed. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$REPO_ROOT" + +lockfile_changed_in_range() { + local range="$1" + [[ -z "$range" ]] && return 1 + git diff --name-only "$range" -- bun.lock | grep -q . +} + +should_run_bun_audit() { + if [[ "${FORCE_BUN_AUDIT:-}" == "1" ]]; then + return 0 + fi + + if [[ "${GITHUB_EVENT_NAME:-}" == "pull_request" && -n "${GITHUB_BASE_REF:-}" ]]; then + git fetch --no-tags --depth=1 origin "$GITHUB_BASE_REF" 2>/dev/null || true + lockfile_changed_in_range "origin/${GITHUB_BASE_REF}...HEAD" + return $? + fi + + if [[ "${GITHUB_EVENT_NAME:-}" == "push" ]]; then + if git rev-parse HEAD~1 >/dev/null 2>&1; then + lockfile_changed_in_range 'HEAD~1..HEAD' + return $? + fi + return 0 + fi + + lockfile_changed_in_range HEAD && return 0 + lockfile_changed_in_range --cached && return 0 + return 1 +} + +echo 'Checking pinned dependency versions...' +bash "$SCRIPT_DIR/check-pinned-versions.sh" + +echo 'Installing with frozen lockfile...' +bun install --frozen-lockfile + +echo 'Verifying bun.lock matches package.json (no drift after install)...' +if ! git diff --exit-code bun.lock; then + echo 'bun.lock is out of sync. Run: bun run install:update-lockfile' >&2 + exit 1 +fi + +if should_run_bun_audit; then + echo 'bun.lock changed in this change set; running bun audit (high/critical)...' + bun audit --audit-level=high +else + echo 'bun.lock unchanged; skipping bun audit.' +fi