diff --git a/README.md b/README.md index 746de0b..4a4dda4 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ By standardizing test results, reports can be validated, merged, compared, and a Use `npx` to run the CLI without installing: ```bash -npx ctrf-cli@0.3.0 validate report.json +npx ctrf-cli@0.3.1 validate report.json ``` ### Global Installation @@ -32,7 +32,7 @@ npx ctrf-cli@0.3.0 validate report.json Or install globally for repeated use: ```bash [npm] -npm install -g ctrf-cli@0.3.0 +npm install -g ctrf-cli@0.3.1 ``` After global installation, use the `ctrf` command: diff --git a/package-lock.json b/package-lock.json index 4350075..7b50666 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "ctrf-cli", - "version": "0.3.0", + "version": "0.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ctrf-cli", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "ctrf": "0.3.0", - "glob": "13.0.6", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", "yargs": "18.1.0" }, "bin": { @@ -23,6 +23,8 @@ "@types/node": "26.2.0", "@types/yargs": "17.0.35", "@vitest/coverage-v8": "4.1.10", + "ctrf": "0.3.0", + "esbuild": "0.28.2", "typescript": "7.0.2", "vitest": "4.1.10" }, @@ -260,6 +262,422 @@ "dev": true, "license": "MIT" }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -1226,6 +1644,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, "license": "MIT", "engines": { "node": "18 || 20 || >=22" @@ -1235,6 +1654,7 @@ "version": "5.0.9", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -1278,7 +1698,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/ctrf/-/ctrf-0.3.0.tgz", "integrity": "sha512-2luVgKCF/A/pgMKY54AUdicCNbU+Hy3Bl+xwcp98inASt/0fnoNC/A4Bwh/GO47cIuD5c7SS3MY/DY42dPf4zQ==", - "license": "MIT", + "dev": true, "dependencies": { "ajv": "8.20.0", "ajv-formats": "3.0.1", @@ -1315,6 +1735,47 @@ "dev": true, "license": "MIT" }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -1424,6 +1885,7 @@ "version": "13.0.6", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "minimatch": "^10.2.2", @@ -1771,6 +2233,7 @@ "version": "11.2.7", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -1818,6 +2281,7 @@ "version": "10.2.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.2" @@ -1833,6 +2297,7 @@ "version": "7.1.3", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -1872,6 +2337,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", diff --git a/package.json b/package.json index 4958193..722dbe3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "ctrf-cli", - "version": "0.3.0", + "version": "0.3.1", "description": "Various CTRF utilities available from the command line", "main": "dist/index.js", "bin": { @@ -9,15 +9,16 @@ "ctrf-cli": "dist/cli.js" }, "scripts": { - "build": "tsc", + "build": "tsc && node scripts/bundle.mjs", "test": "vitest run", "test:coverage": "vitest run --coverage", + "test:package": "node scripts/package-smoke.mjs", "lint": "biome lint --write . --unsafe", "lint:check": "biome lint .", "format": "biome format --write .", "format:check": "biome format .", "check": "biome check .", - "all": "npm run lint && npm run format:check && npm run test:coverage && npm run build" + "all": "npm run lint && npm run format:check && npm run test:coverage && npm run build && npm run test:package" }, "files": [ "dist/", @@ -54,8 +55,8 @@ "test-results" ], "dependencies": { - "ctrf": "0.3.0", - "glob": "13.0.6", + "ajv": "8.20.0", + "ajv-formats": "3.0.1", "yargs": "18.1.0" }, "devDependencies": { @@ -64,6 +65,8 @@ "@types/node": "26.2.0", "@types/yargs": "17.0.35", "@vitest/coverage-v8": "4.1.10", + "ctrf": "0.3.0", + "esbuild": "0.28.2", "typescript": "7.0.2", "vitest": "4.1.10" } diff --git a/scripts/bundle.mjs b/scripts/bundle.mjs new file mode 100644 index 0000000..2bfa1df --- /dev/null +++ b/scripts/bundle.mjs @@ -0,0 +1,60 @@ +import { copyFile, mkdir, readdir, readFile, rm } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { build } from "esbuild"; + +const projectRoot = fileURLToPath(new URL("..", import.meta.url)); +const distDirectory = path.join(projectRoot, "dist"); +const ctrfDistDirectory = path.dirname( + fileURLToPath(import.meta.resolve("ctrf")), +); +const schemaPattern = /^ctrf-schema-\d+\.\d+\.json$/; + +await mkdir(distDirectory, { recursive: true }); + +await build({ + entryPoints: [path.join(projectRoot, "src/cli.ts")], + outfile: path.join(distDirectory, "cli.js"), + bundle: true, + platform: "node", + format: "esm", + target: "node20.19", + external: ["ajv", "ajv-formats", "yargs", "yargs/*"], + legalComments: "linked", + logLevel: "info", +}); + +const existingSchemaFiles = (await readdir(distDirectory)).filter((file) => + schemaPattern.test(file), +); + +await Promise.all( + existingSchemaFiles.map((file) => rm(path.join(distDirectory, file))), +); + +const schemaFiles = (await readdir(ctrfDistDirectory)).filter((file) => + schemaPattern.test(file), +); + +if (schemaFiles.length === 0) { + throw new Error( + "No CTRF schema files were found to include in the CLI bundle", + ); +} + +for (const schemaFile of schemaFiles) { + const source = path.join(ctrfDistDirectory, schemaFile); + const destination = path.join(distDirectory, schemaFile); + await copyFile(source, destination); + + const [sourceBytes, destinationBytes] = await Promise.all([ + readFile(source), + readFile(destination), + ]); + + if (!sourceBytes.equals(destinationBytes)) { + throw new Error( + `Copied CTRF schema does not match its source: ${schemaFile}`, + ); + } +} diff --git a/scripts/package-smoke.mjs b/scripts/package-smoke.mjs new file mode 100644 index 0000000..646d56a --- /dev/null +++ b/scripts/package-smoke.mjs @@ -0,0 +1,129 @@ +import assert from "node:assert/strict"; +import { + existsSync, + mkdirSync, + mkdtempSync, + readFileSync, + realpathSync, + rmSync, + writeFileSync, +} from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { spawnSync } from "node:child_process"; +import { fileURLToPath } from "node:url"; + +const projectRoot = fileURLToPath(new URL("..", import.meta.url)); +const packageMetadata = JSON.parse( + readFileSync(path.join(projectRoot, "package.json"), "utf-8"), +); +const temporaryRoot = mkdtempSync(path.join(os.tmpdir(), "ctrf-cli-package-")); +const packDirectory = path.join(temporaryRoot, "pack"); +const installDirectory = path.join(temporaryRoot, "install"); +const npmCacheDirectory = path.join(temporaryRoot, "npm-cache"); + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: options.cwd ?? projectRoot, + encoding: "utf-8", + env: { + ...process.env, + npm_config_cache: npmCacheDirectory, + }, + }); + const expectedStatus = options.expectedStatus ?? 0; + + if (result.status !== expectedStatus) { + throw new Error( + [ + `Command failed: ${command} ${args.join(" ")}`, + `Expected exit status ${expectedStatus}, received ${result.status}`, + result.stdout, + result.stderr, + ].join("\n"), + ); + } + + return result; +} + +try { + mkdirSync(packDirectory); + mkdirSync(installDirectory); + mkdirSync(npmCacheDirectory); + + const packResult = run( + "npm", + ["pack", "--pack-destination", packDirectory, "--json"], + { cwd: projectRoot }, + ); + const [{ filename }] = JSON.parse(packResult.stdout); + const tarballPath = path.join(packDirectory, filename); + + writeFileSync( + path.join(installDirectory, "package.json"), + JSON.stringify({ private: true }), + ); + run( + "npm", + ["install", "--ignore-scripts", "--no-audit", "--no-fund", tarballPath], + { cwd: installDirectory }, + ); + + assert.equal( + existsSync(path.join(installDirectory, "node_modules/ctrf")), + false, + "ctrf must not be installed as a production dependency", + ); + + if (process.platform !== "win32") { + const expectedCli = realpathSync( + path.join(installDirectory, "node_modules/ctrf-cli/dist/cli.js"), + ); + for (const binary of ["ctrf", "ctrf-cli"]) { + assert.equal( + realpathSync( + path.join(installDirectory, `node_modules/.bin/${binary}`), + ), + expectedCli, + `${binary} must resolve to the standalone CLI`, + ); + } + } + + const versionResult = run( + "npx", + ["--yes", `--package=${tarballPath}`, "ctrf", "--version"], + { cwd: temporaryRoot }, + ); + assert.equal(versionResult.stdout.trim(), packageMetadata.version); + + const validReport = path.join(projectRoot, "examples/minimal.json"); + const validationResult = run( + "npx", + ["--yes", `--package=${tarballPath}`, "ctrf", "validate", validReport], + { cwd: temporaryRoot }, + ); + assert.match(validationResult.stdout, /is valid CTRF/); + + const invalidReport = path.join(temporaryRoot, "invalid.json"); + writeFileSync( + invalidReport, + JSON.stringify({ reportFormat: "CTRF", specVersion: "1.0.0" }), + ); + const invalidResult = run( + "npx", + ["--yes", `--package=${tarballPath}`, "ctrf", "validate", invalidReport], + { cwd: temporaryRoot, expectedStatus: 2 }, + ); + assert.match(invalidResult.stderr, /failed validation/); + + console.log( + `✓ Packed ctrf-cli ${packageMetadata.version} resolves both binaries to the standalone CLI`, + ); +} finally { + const expectedPrefix = path.join(os.tmpdir(), "ctrf-cli-package-"); + if (temporaryRoot.startsWith(expectedPrefix)) { + rmSync(temporaryRoot, { recursive: true, force: true }); + } +} diff --git a/src/cli.ts b/src/cli.ts index fe236b0..1b51e48 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,5 +1,6 @@ #!/usr/bin/env node +import fs from "node:fs"; import yargs from "yargs/yargs"; import { hideBin } from "yargs/helpers"; import { mergeReports } from "./merge.js"; @@ -10,7 +11,16 @@ import { generateTestIds } from "./generate-test-ids.js"; import { generateReportIdCommand } from "./generate-report-id.js"; import { addInsightsCommand } from "./add-insights.js"; +const packageMetadata = JSON.parse( + fs.readFileSync(new URL("../package.json", import.meta.url), "utf-8"), +) as { version?: unknown }; + +if (typeof packageMetadata.version !== "string") { + throw new Error("Unable to determine ctrf-cli version"); +} + const _argv = yargs(hideBin(process.argv)) + .version(packageMetadata.version) .command( "merge ", "Merge CTRF reports into a single report",