diff --git a/addlicense.yml b/addlicense.yml new file mode 100644 index 00000000..c826c8da --- /dev/null +++ b/addlicense.yml @@ -0,0 +1 @@ +header: doc/license_header.txt diff --git a/package-lock.json b/package-lock.json index 45d091cc..89380bb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1024,6 +1024,102 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -6021,6 +6117,10 @@ "node": ">=14" } }, + "node_modules/comment-parser": { + "resolved": "tools/comment-parser", + "link": true + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -6183,6 +6283,15 @@ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "license": "ISC" }, + "node_modules/console-table-printer": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/console-table-printer/-/console-table-printer-2.12.1.tgz", + "integrity": "sha512-wKGOQRRvdnd89pCeH96e2Fn4wkbenSP6LMHfjfyNLMbGuHEFbMqQNuxXqd0oXG9caIOQ1FTvc5Uijp9/4jujnQ==", + "license": "MIT", + "dependencies": { + "simple-wcswidth": "^1.0.1" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -6327,7 +6436,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -6617,6 +6725,12 @@ "node": ">=0.3.1" } }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz", + "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==", + "license": "Apache-2.0" + }, "node_modules/diff3": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", @@ -6707,6 +6821,12 @@ "url": "https://dotenvx.com" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -8001,6 +8121,10 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/google-license": { + "resolved": "tools/google-license", + "link": true + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -8682,8 +8806,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isobject": { "version": "3.0.1", @@ -8889,6 +9012,24 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -8973,6 +9114,15 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", "license": "BSD-3-Clause" }, + "node_modules/js-levenshtein": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", + "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/js-parse-and-output": { "resolved": "experiments/js-parse-and-output", "link": true @@ -9458,6 +9608,10 @@ "decamelize": "^1.2.0" } }, + "node_modules/license-headers": { + "resolved": "tools/license-headers", + "link": true + }, "node_modules/load-bmfont": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", @@ -10760,6 +10914,12 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -10855,7 +11015,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -10865,6 +11024,40 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/path-scurry/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -11965,7 +12158,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -11977,7 +12169,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -12116,6 +12307,12 @@ "node": ">=10" } }, + "node_modules/simple-wcswidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==", + "license": "MIT" + }, "node_modules/sinon": { "version": "15.2.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.2.0.tgz", @@ -12435,6 +12632,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12446,6 +12658,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -13383,7 +13608,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -13525,6 +13749,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -13638,6 +13880,18 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", + "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -14213,9 +14467,196 @@ "version": "1.0.0", "license": "AGPL-3.0-only" }, + "tools/comment-parser": { + "version": "1.0.0", + "license": "AGPL-3.0-only", + "devDependencies": { + "chai": "^5.1.1" + } + }, + "tools/comment-parser/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "tools/comment-parser/node_modules/chai": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz", + "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "tools/comment-parser/node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "tools/comment-parser/node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "tools/comment-parser/node_modules/loupe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.1.tgz", + "integrity": "sha512-edNu/8D5MKVfGVFRhFf8aAxiTM6Wumfz5XsaatSxlD3w4R1d/WEKUTydCdPGbl9K7QG/Ca3GnDV2sIKIpXRQcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "tools/comment-parser/node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "tools/file-walker": { "version": "1.0.0", "license": "AGPL-3.0-only" + }, + "tools/google-license": { + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "commander": "^12.1.0", + "glob": "^11.0.0", + "handlebars": "^4.7.8" + } + }, + "tools/google-license/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "tools/google-license/node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "tools/google-license/node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "tools/google-license/node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "tools/google-license/node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "tools/google-license/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "tools/google-license/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "tools/license-headers": { + "version": "1.0.0", + "license": "AGPL-3.0-only", + "dependencies": { + "console-table-printer": "^2.12.1", + "dedent": "^1.5.3", + "diff-match-patch": "^1.0.5", + "js-levenshtein": "^1.1.6", + "yaml": "^2.4.5" + } } } } diff --git a/tools/comment-parser/main.js b/tools/comment-parser/main.js new file mode 100644 index 00000000..04412915 --- /dev/null +++ b/tools/comment-parser/main.js @@ -0,0 +1,345 @@ +const lib = {}; +lib.dedent_lines = lines => { + // If any lines are just spaces, remove the spaces + for ( let i=0 ; i < lines.length ; i++ ) { + if ( /^\s+$/.test(lines[i]) ) lines[i] = ''; + } + + // Remove leading and trailing blanks + while ( lines[0] === '' ) lines.shift(); + while ( lines[lines.length-1] === '' ) lines.pop(); + + let min_indent = Number.MAX_SAFE_INTEGER; + for ( let i=0 ; i < lines.length ; i++ ) { + if ( lines[i] === '' ) continue; + let n_spaces = 0; + for ( let j=0 ; j < lines[i].length ; j++ ) { + if ( lines[i][j] === ' ' ) n_spaces++; + else break; + } + if ( n_spaces < min_indent ) min_indent = n_spaces; + } + for ( let i=0 ; i < lines.length ; i++ ) { + if ( lines[i] === '' ) continue; + lines[i] = lines[i].slice(min_indent); + } +}; + +const StringStream = (str, { state_ } = {}) => { + const state = state_ ?? { pos: 0 }; + return { + skip_whitespace () { + while ( /^\s/.test(str[state.pos]) ) state.pos++; + }, + // INCOMPLETE: only handles single chars + skip_matching (items) { + while ( items.some(item => { + return str[state.pos] === item; + }) ) state.pos++; + }, + fwd (amount) { + state.pos += amount ?? 1; + }, + fork () { + return StringStream(str, { state_: { pos: state.pos } }); + }, + async get_pos () { + return state.pos; + }, + async get_char () { + return str[state.pos]; + }, + async matches (re_or_lit) { + if ( re_or_lit instanceof RegExp ) { + const re = re_or_lit; + return re.test(str.slice(state.pos)); + } + + const lit = re_or_lit; + return lit === str.slice(state.pos, state.pos + lit.length); + }, + async get_until (re_or_lit) { + let index; + if ( re_or_lit instanceof RegExp ) { + const re = re_or_lit; + const result = re.exec(str.slice(state.pos)); + if ( ! result ) return; + index = state.pos + result.index; + } else { + const lit = re_or_lit; + const ind = str.slice(state.pos).indexOf(lit); + // TODO: parser warnings? + if ( ind === -1 ) return; + index = state.pos + ind; + } + const start_pos = state.pos; + state.pos = index; + return str.slice(start_pos, index); + }, + async debug () { + const l1 = str.length; + const l2 = str.length - state.pos; + const clean = s => s.replace(/\n/, '{LF}'); + return `[stream : "${ + clean(str.slice(0, Math.min(6, l1))) + }"... |${state.pos}| ..."${ + clean(str.slice(state.pos, state.pos + Math.min(6, l2))) + }"]` + } + }; +}; + +const LinesCommentParser = ({ + prefix +}) => { + return { + parse: async (stream) => { + stream.skip_whitespace(); + const lines = []; + while ( await stream.matches(prefix) ) { + const line = await stream.get_until('\n'); + if ( ! line ) return; + lines.push(line); + stream.fwd(); + stream.skip_matching([' ', '\t']); + if ( await stream.get_char() === '\n' ){ + stream.fwd(); + break; + } + stream.skip_whitespace(); + } + if ( lines.length === 0 ) return; + for ( let i=0 ; i < lines.length ; i++ ) { + lines[i] = lines[i].slice(prefix.length); + } + lib.dedent_lines(lines); + return { + lines, + }; + } + }; +}; + +const BlockCommentParser = ({ + start, + end, + ignore_line_prefix, +}) => { + return { + parse: async (stream) => { + stream.skip_whitespace(); + stream.debug('starting at', await stream.debug()) + if ( ! stream.matches(start) ) return; + stream.fwd(start.length); + const contents = await stream.get_until(end); + if ( ! contents ) return; + stream.fwd(end.length); + // console.log('ending at', await stream.debug()) + const lines = contents.split('\n'); + + // === Formatting Time! === // + + // Special case: remove the last '*' after '/**' + if ( lines[0].trim() === ignore_line_prefix ) { + lines.shift(); + } + + // First dedent pass + lib.dedent_lines(lines); + + // If all the lines start with asterisks, remove + let allofem = true; + for ( let i=0 ; i < lines.length ; i++ ) { + if ( lines[i] === '' ) continue; + if ( ! lines[i].startsWith(ignore_line_prefix) ) { + allofem = false; + break + } + } + + if ( allofem ) { + for ( let i=0 ; i < lines.length ; i++ ) { + if ( lines[i] === '' ) continue; + lines[i] = lines[i].slice(ignore_line_prefix.length); + } + + // Second dedent pass + lib.dedent_lines(lines); + } + + return { lines }; + } + }; +}; + +const LinesCommentWriter = ({ prefix }) => { + return { + write: (lines) => { + lib.dedent_lines(lines); + for ( let i=0 ; i < lines.length ; i++ ) { + lines[i] = prefix + lines[i]; + } + return lines.join('\n') + '\n'; + } + }; +}; + +const BlockCommentWriter = ({ start, end, prefix }) => { + return { + write: (lines) => { + lib.dedent_lines(lines); + for ( let i=0 ; i < lines.length ; i++ ) { + lines[i] = prefix + lines[i]; + } + let s = start + '\n'; + s += lines.join('\n') + '\n'; + s += end + '\n'; + return s; + } + }; +}; + +const CommentParser = () => { + const registry_ = { + object: { + parsers: { + lines: LinesCommentParser, + block: BlockCommentParser, + }, + writers: { + lines: LinesCommentWriter, + block: BlockCommentWriter, + }, + }, + data: { + extensions: { + js: 'javascript', + cjs: 'javascript', + mjs: 'javascript', + }, + languages: { + javascript: { + parsers: [ + ['lines', { + prefix: '// ', + }], + ['block', { + start: '/*', + end: '*/', + ignore_line_prefix: '*', + }], + ], + writers: { + lines: ['lines', { + prefix: '//' + }], + block: ['block', { + start: '/*', + end: '*/', + prefix: ' * ', + }] + }, + } + }, + } + + }; + + const get_language_by_filename = ({ filename }) => { + const { language } = (({ filename }) => { + const { language_id } = (({ filename }) => { + const { extension } = (({ filename }) => { + const components = ('' + filename).split('.'); + const extension = components[components.length - 1]; + return { extension }; + })({ filename }); + + const language_id = registry_.data.extensions[extension]; + + if ( ! language_id ) { + throw new Error(`unrecognized language id: ` + + language_id); + } + return { language_id }; + })({ filename }); + + const language = registry_.data.languages[language_id]; + return { language }; + })({ filename }); + + if ( ! language ) { + // TODO: use strutil quot here + throw new Error(`unrecognized language: ${language}`) + } + + return { language }; + } + + const supports = ({ filename }) => { + try { + get_language_by_filename({ filename }); + } catch (e) { + return false; + } + return true; + }; + + const extract_top_comments = async ({ filename, source }) => { + const { language } = get_language_by_filename({ filename }); + + // TODO: registry has `data` and `object`... + // ... maybe add `virt` (virtual), which will + // behave in the way the above code is written. + + const inst_ = spec => registry_.object.parsers[spec[0]](spec[1]); + + let ss = StringStream(source); + const results = []; + for (;;) { + let comment; + for ( let parser of language.parsers ) { + const parser_name = parser[0]; + parser = inst_(parser); + + const ss_ = ss.fork(); + const start_pos = await ss_.get_pos(); + comment = await parser.parse(ss_); + const end_pos = await ss_.get_pos(); + if ( comment ) { + ss = ss_; + comment.type = parser_name; + comment.range = [start_pos, end_pos]; + break; + } + } + if ( ! comment ) break; + results.push(comment); + } + + return results; + } + + const output_comment = ({ filename, style, text }) => { + const { language } = get_language_by_filename({ filename }); + + const inst_ = spec => registry_.object.writers[spec[0]](spec[1]); + let writer = language.writers[style]; + writer = inst_(writer); + const lines = text.split('\n'); + const s = writer.write(lines); + return s; + } + + return { + supports, + extract_top_comments, + output_comment, + }; +}; + +module.exports = { + StringStream, + LinesCommentParser, + BlockCommentParser, + CommentParser, +}; diff --git a/tools/comment-parser/package.json b/tools/comment-parser/package.json new file mode 100644 index 00000000..4f4aa82f --- /dev/null +++ b/tools/comment-parser/package.json @@ -0,0 +1,15 @@ +{ + "name": "comment-parser", + "version": "1.0.0", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "AGPL-3.0-only", + "description": "", + "devDependencies": { + "chai": "^5.1.1" + } +} diff --git a/tools/comment-parser/test/test.js b/tools/comment-parser/test/test.js new file mode 100644 index 00000000..67ef095c --- /dev/null +++ b/tools/comment-parser/test/test.js @@ -0,0 +1,127 @@ +const { + StringStream, + LinesCommentParser, + BlockCommentParser, + CommentParser +} = require('../main'); + +const assert = async (label, fn) => { + if ( ! await fn() ) { + // TODO: strutil quot + throw new Error(`assert: '${label}' failed`) + } +}; + +describe('parsers', () => { + describe('lines-comment-parser', () => { + it ('basic test', async () => { + const parser = LinesCommentParser({ prefix: '//' }); + let lines; + const ss = StringStream(` + // first line of first block + // second line of first block + + // first line of second block + + function () {} + `); + const results = []; + for (;;) { + comment = await parser.parse(ss); + if ( ! comment ) break; + results.push(comment.lines); + } + console.log('results?', results); + }) + }) + describe('block-comment-parser', () => { + it ('basic test', async () => { + const parser = BlockCommentParser({ + start: '/*', + end: '*/', + ignore_line_prefix: '*', + }); + let lines; + const ss = StringStream(` + /* + First block + comment + */ + /* + * second block + * comment + */ + + /** + * third block + * comment + */ + function () {} + `); + const results = []; + for (;;) { + comment = await parser.parse(ss); + if ( ! comment ) break; + results.push(comment.lines); + } + console.log('results?', results); + }) + it ('doesn\'t return anything for line comments', async () => { + const parser = BlockCommentParser({ + start: '/*', + end: '*/', + ignore_line_prefix: '*', + }); + let lines; + const ss = StringStream(` + // this comment should not be parsed + // by the block comment parser + function () {} + `); + const results = []; + for (;;) { + comment = await parser.parse(ss); + if ( ! comment ) break; + results.push(comment.lines); + } + console.log('results?', results); + }) + }) + describe('extract_top_comments', () => { + it ('basic test', async () => { + const parser = CommentParser(); + + const filename = 'test.js'; + const source = ` + // First lines comment + // second line of lines comment + + /* + First block comment + second line of block comment + */ + `; + + const results = await parser.extract_top_comments({ + filename, + source, + }); + console.log('results?', results); + }) + }) + describe('StringStream', () => { + describe('fork', () => { + it('works', async () => { + const ss = StringStream('asdf'); + const ss_ = ss.fork(); + ss_.fwd(); + await assert('fwd worked', async () => { + return await ss_.get_char() === 's'; + }); + await assert('upstream state is same', async () => { + return await ss.get_char() === 'a'; + }); + }) + }) + }) +}); diff --git a/tools/file-walker/test.js b/tools/file-walker/test.js index 784e7e28..603dd4bb 100644 --- a/tools/file-walker/test.js +++ b/tools/file-walker/test.js @@ -20,6 +20,19 @@ const fs = require('fs'); const fsp = fs.promises; const path_ = require('path'); +const EXCLUDE_LISTS = { + NOT_SOURCE: [ + /^\.git/, + /^volatile\//, + /^node_modules\//, + /\/node_modules$/, + /^node_modules$/, + /package-lock\.json/, + /src\/backend\/src\/public\/assets/, + /^src\/gui\/src\/lib/ + ] +}; + const hl_readdir = async path => { const names = await fs.promises.readdir(path); const entries = []; @@ -130,15 +143,7 @@ const blame = async (path) => { const walk_test = async () => { // console.log(await hl_readdir('.')); for await ( const value of walk({ - excludes: [ - /^\.git/, - /^volatile\//, - /^node_modules\//, - /\/node_modules$/, - /^node_modules$/, - /package-lock\.json/, - /^src\/gui\/dist/, - ] + excludes: EXCLUDE_LISTS.NOT_SOURCE, }, '.') ) { if ( ! value.is_dir ) continue; console.log('value', value.path); @@ -170,16 +175,7 @@ git blame parsing. const walk_and_blame = async () => { // console.log(await hl_readdir('.')); for await ( const value of walk({ - excludes: [ - /^\.git/, - /^volatile\//, - /^node_modules\//, - /\/node_modules$/, - /^node_modules$/, - /package-lock\.json/, - /src\/backend\/src\/public\/assets/, - /^src\/gui\/src\/lib/ - ] + excludes: EXCLUDE_LISTS.NOT_SOURCE, }, '.') ) { if ( value.is_dir ) continue; console.log('value', value.path); @@ -194,6 +190,12 @@ const walk_and_blame = async () => { console.log('AUTHORS', authors); } -const main = walk_and_blame; +if ( require.main === module ) { + const main = walk_and_blame; + main(); +} -main(); +module.exports = { + walk, + EXCLUDE_LISTS, +}; diff --git a/tools/license-headers/main.js b/tools/license-headers/main.js new file mode 100644 index 00000000..8c4a6bbe --- /dev/null +++ b/tools/license-headers/main.js @@ -0,0 +1,356 @@ +const levenshtein = require('js-levenshtein'); +const DiffMatchPatch = require('diff-match-patch'); +const dmp = new DiffMatchPatch(); +const dedent = require('dedent'); + +const { walk, EXCLUDE_LISTS } = require('file-walker'); +const { CommentParser } = require('../comment-parser/main'); + +const fs = require('fs'); +const path_ = require('path'); + +const CompareFn = ({ header1, header2, distance_only = false }) => { + + // Calculate Levenshtein distance + const distance = levenshtein(header1, header2); + // console.log(`Levenshtein distance: ${distance}`); + + if ( distance_only ) return { distance }; + + // Generate diffs using diff-match-patch + const diffs = dmp.diff_main(header1, header2); + dmp.diff_cleanupSemantic(diffs); + + let term_diff = ''; + + // Manually format diffs for terminal display + diffs.forEach(([type, text]) => { + switch (type) { + case DiffMatchPatch.DIFF_INSERT: + term_diff += `\x1b[32m${text}\x1b[0m`; // Green for insertions + break; + case DiffMatchPatch.DIFF_DELETE: + term_diff += `\x1b[31m${text}\x1b[0m`; // Red for deletions + break; + case DiffMatchPatch.DIFF_EQUAL: + term_diff += text; // No color for equal parts + break; + } + }); + + return { + distance, + term_diff, + }; +} + +const LicenseChecker = ({ + comment_parser, + desired_header, +}) => { + const supports = ({ filename }) => { + return comment_parser.supports({ filename }); + }; + const compare = async ({ filename, source }) => { + const headers = await comment_parser.extract_top_comments( + { filename, source }); + const headers_lines = headers.map(h => h.lines); + + if ( headers.length < 1 ) { + return { + has_header: false, + }; + } + + // console.log('headers', headers); + + let top = 0; + let bottom = 0; + let current_distance = Number.MAX_SAFE_INTEGER; + + // "wah" + for ( let i=1 ; i <= headers.length ; i++ ) { + const combined = headers_lines.slice(top, i).flat(); + const combined_txt = combined.join('\n'); + const { distance } = + CompareFn({ + header1: desired_header, + header2: combined_txt, + distance_only: true, + }); + if ( distance < current_distance ) { + current_distance = distance; + bottom = i; + } else { + break; + } + } + // "woop" + for ( let i=1 ; i < headers.length ; i++ ) { + const combined = headers_lines.slice(i, bottom).flat(); + const combined_txt = combined.join('\n'); + const { distance } = + CompareFn({ + header1: desired_header, + header2: combined_txt, + distance_only: true, + }); + if ( distance < current_distance ) { + current_distance = distance; + top = i; + } else { + break; + } + } + + const combined = headers_lines.slice(top, bottom).flat(); + const combined_txt = combined.join('\n'); + + const diff_info = CompareFn({ + header1: desired_header, + header2: combined_txt, + }) + + diff_info.range = [ + headers[top].range[0], + headers[bottom-1].range[1], + ]; + + diff_info.has_header = true; + + return diff_info; + }; + return { + compare, + supports, + }; +}; + +const license_check_test = async ({ options }) => { + const comment_parser = CommentParser(); + const license_checker = LicenseChecker({ + comment_parser, + desired_header: fs.readFileSync( + path_.join(__dirname, '../../doc/license_header.txt'), + 'utf-8', + ), + }); + + const walk_iterator = walk({ + excludes: EXCLUDE_LISTS.NOT_SOURCE, + }, path_.join(__dirname, '../..')); + for await ( const value of walk_iterator ) { + if ( value.is_dir ) continue; + if ( value.name !== 'dev-console-ui-utils.js' ) continue; + console.log(value.path); + const source = fs.readFileSync(value.path, 'utf-8'); + const diff_info = await license_checker.compare({ + filename: value.name, + source, + }) + if ( diff_info ) { + process.stdout.write('\x1B[36;1m=======\x1B[0m\n'); + process.stdout.write(diff_info.term_diff); + process.stdout.write('\n\x1B[36;1m=======\x1B[0m\n'); + // console.log('headers', headers); + } else { + console.log('NO COMMENT'); + } + + console.log('RANGE', diff_info.range) + + const new_comment = comment_parser.output_comment({ + filename: value.name, + style: 'block', + text: 'some text\nto display' + }); + + console.log('NEW COMMENT?', new_comment); + } +}; + +const cmd_check_fn = async () => { + const comment_parser = CommentParser(); + const license_checker = LicenseChecker({ + comment_parser, + desired_header: fs.readFileSync( + path_.join(__dirname, '../../doc/license_header.txt'), + 'utf-8', + ), + }); + + const counts = { + ok: 0, + missing: 0, + conflict: 0, + error: 0, + unsupported: 0, + }; + + const walk_iterator = walk({ + excludes: EXCLUDE_LISTS.NOT_SOURCE, + }, path_.join(__dirname, '../..')); + for await ( const value of walk_iterator ) { + if ( value.is_dir ) continue; + + process.stdout.write(value.path + ' ... '); + + if ( ! license_checker.supports({ filename: value.name }) ) { + process.stdout.write(`\x1B[37;1mUNSUPPORTED\x1B[0m\n`); + counts.unsupported++; + continue; + } + + const source = fs.readFileSync(value.path, 'utf-8'); + const diff_info = await license_checker.compare({ + filename: value.name, + source, + }) + if ( ! diff_info ) { + counts.error++; + continue; + } + if ( ! diff_info.has_header ) { + counts.missing++; + process.stdout.write(`\x1B[33;1mMISSING\x1B[0m\n`); + continue; + } + if ( diff_info ) { + if ( diff_info.distance !== 0 ) { + counts.conflict++; + process.stdout.write(`\x1B[31;1mCONFLICT\x1B[0m\n`); + } else { + counts.ok++; + process.stdout.write(`\x1B[32;1mOK\x1B[0m\n`); + } + } else { + console.log('NO COMMENT'); + } + } + + const { Table } = require('console-table-printer'); + const t = new Table({ + columns: [ + { + title: 'License Header', + name: 'situation', alignment: 'left', color: 'white_bold' }, + { + title: 'Number of Files', + name: 'count', alignment: 'right' }, + ], + colorMap: { + green: '\x1B[32;1m', + yellow: '\x1B[33;1m', + red: '\x1B[31;1m', + } + }); + + console.log(''); + + if ( counts.error > 0 ) { + console.log(`\x1B[31;1mTHERE WERE SOME ERRORS!\x1B[0m`); + console.log('check the log above for the stack trace'); + console.log(''); + t.addRow({ situation: 'error', count: counts.error }, + { color: 'red' }); + } + + console.log(dedent(` + \x1B[31;1mAny text below is mostly lies!\x1B[0m + This tool is still being developed and most of what's + described is "the plan" rather than a thing that will + actually happen. + \x1B[31;1m^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\x1B[0m + `)); + + if ( counts.conflict ) { + console.log(dedent(` + \x1B[37;1mIt looks like you have some conflicts!\x1B[0m + Run the following command to update license headers: + + \x1B[36;1maddlicense sync\x1B[0m + + This will begin an interactive license update. + Any time the license doesn't quite match you will + be given the option to replace it or skip the file. + \x1B[90mSee \`addlicense help sync\` for other options.\x1B[0m + + You will also be able to choose + "remember for headers matching this one" + if you know the same issue will come up later. + `)); + } else if ( counts.missing ) { + console.log(dedent(` + \x1B[37;1mSome missing license headers!\x1B[0m + Run the following command to add the missing license headers: + + \x1B[36;1maddlicense sync\x1B[0m + `)); + } else { + console.log(dedent(` + \x1B[37;1mNo action to perform!\x1B[0m + Run the following command to do absolutely nothing: + + \x1B[36;1maddlicense sync\x1B[0m + `)); + } + + console.log(''); + + t.addRow({ situation: 'ok', count: counts.ok }, + { color: 'green' }); + t.addRow({ situation: 'missing', count: counts.missing }, + { color: 'yellow' }); + t.addRow({ situation: 'conflict', count: counts.conflict }, + { color: 'red' }); + t.addRow({ situation: 'unsupported', count: counts.unsupported }); + t.printTable(); +}; + +const main = async () => { + const { program } = require('commander'); + const helptext = dedent(` + Usage: usage text + `); + + const run_command = async ({ cmd, cmd_fn }) => { + const options = { + program: program.opts(), + command: cmd.opts(), + }; + console.log('options', options); + + if ( ! fs.existsSync(options.program.config) ) { + // TODO: configuration wizard + fs.writeFileSync(options.program.config, ''); + } + + await cmd_fn({ options }); + }; + + program + .name('addlicense') + .option('-c, --config', 'configuration file', 'addlicense.yml') + .addHelpText('before', helptext) + ; + const cmd_check = program.command('check') + .description('check license headers') + .option('-n, --non-interactive', 'disable prompting') + .action(() => { + run_command({ cmd: cmd_check, cmd_fn: cmd_check_fn }); + }) + const cmd_sync = program.command('sync') + .description('synchronize files with license header rules') + .option('-n, --non-interactive', 'disable prompting') + .action(() => { + console.log('called sync'); + console.log(program.opts()); + console.log(cmd_sync.opts()); + }) + program.parse(process.argv); + +}; + +if ( require.main === module ) { + main(); +} \ No newline at end of file diff --git a/tools/license-headers/package.json b/tools/license-headers/package.json new file mode 100644 index 00000000..05ced1f1 --- /dev/null +++ b/tools/license-headers/package.json @@ -0,0 +1,19 @@ +{ + "name": "license-headers", + "version": "1.0.0", + "main": "main.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "AGPL-3.0-only", + "description": "", + "dependencies": { + "console-table-printer": "^2.12.1", + "dedent": "^1.5.3", + "diff-match-patch": "^1.0.5", + "js-levenshtein": "^1.1.6", + "yaml": "^2.4.5" + } +}