diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8f8017bf3dd7..bef859bd321f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,6 @@ jobs: - landings - languages - links - # - metrics - observability # - pages - products diff --git a/eslint.config.ts b/eslint.config.ts index f38f66badae7..3aea131e7c70 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -195,7 +195,6 @@ export default [ 'src/events/components/**/*.{ts,js}', 'src/fixtures/**/*.{ts,js}', 'src/journeys/**/*.{ts,js}', - 'src/metrics/**/*.{ts,js}', 'src/observability/lib/handle-package-not-found.ts', ], rules: { diff --git a/package-lock.json b/package-lock.json index 03b452aef1bd..26cee73d6447 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,6 @@ "ajv": "^8.18.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", - "azure-kusto-data": "^7.0.4", "chalk": "^5.0.1", "cheerio": "^1.2.0", "clsx": "^2.1.1", @@ -313,153 +312,6 @@ "playwright-core": ">= 1.0.0" } }, - "node_modules/@azure/abort-controller": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-2.1.2.tgz", - "integrity": "sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-auth": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.9.0.tgz", - "integrity": "sha512-FPwHpZywuyasDSLMqJ6fhbOK3TqUdviZNF8OqRGA4W5Ewib2lEEZ+pBsYcBa88B2NGO/SEnYPGhyBqNlE8ilSw==", - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-util": "^1.11.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-client": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.9.4.tgz", - "integrity": "sha512-f7IxTD15Qdux30s2qFARH+JxgwxWLG2Rlr4oSkPGuLWm+1p5y1+C04XGLA0vmX6EtqfutmjvpNmAfgwVIS5hpw==", - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-auth": "^1.4.0", - "@azure/core-rest-pipeline": "^1.20.0", - "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.6.1", - "@azure/logger": "^1.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-rest-pipeline": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.21.0.tgz", - "integrity": "sha512-a4MBwe/5WKbq9MIxikzgxLBbruC5qlkFYlBdI7Ev50Y7ib5Vo/Jvt5jnJo7NaWeJ908LCHL0S1Us4UMf1VoTfg==", - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-auth": "^1.8.0", - "@azure/core-tracing": "^1.0.1", - "@azure/core-util": "^1.11.0", - "@azure/logger": "^1.0.0", - "@typespec/ts-http-runtime": "^0.2.3", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-tracing": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@azure/core-tracing/-/core-tracing-1.2.0.tgz", - "integrity": "sha512-UKTiEJPkWcESPYJz3X5uKRYyOcJD+4nYph+KpfdPRnQJVrZfk0KJgdnaAWKfhsBBtAf/D58Az4AvCJEmWgIBAg==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/core-util": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.12.0.tgz", - "integrity": "sha512-13IyjTQgABPARvG90+N2dXpC+hwp466XCdQXPCRlbWHgd3SJd5Q1VvaBGv6k1BIa4MQm6hAF1UBU1m8QUxV8sQ==", - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@typespec/ts-http-runtime": "^0.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/identity": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.10.1.tgz", - "integrity": "sha512-YM/z6RxRtFlXUH2egAYF/FDPes+MUE6ZoknjEdaq7ebJMMNUzn9zCJ3bd2ZZZlkP0r1xKa88kolhFH/FGV7JnA==", - "dependencies": { - "@azure/abort-controller": "^2.0.0", - "@azure/core-auth": "^1.9.0", - "@azure/core-client": "^1.9.2", - "@azure/core-rest-pipeline": "^1.17.0", - "@azure/core-tracing": "^1.0.0", - "@azure/core-util": "^1.11.0", - "@azure/logger": "^1.0.0", - "@azure/msal-browser": "^4.2.0", - "@azure/msal-node": "^3.5.0", - "open": "^10.1.0", - "tslib": "^2.2.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/logger": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@azure/logger/-/logger-1.2.0.tgz", - "integrity": "sha512-0hKEzLhpw+ZTAfNJyRrn6s+V0nDWzXk9OjBr2TiGIu0OfMr5s2V4FpKLTAK3Ca5r5OKLbf4hkOGDPyiRjie/jA==", - "dependencies": { - "@typespec/ts-http-runtime": "^0.2.2", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@azure/msal-browser": { - "version": "4.13.2", - "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.13.2.tgz", - "integrity": "sha512-lS75bF6FYZRwsacKLXc8UYu/jb+gOB7dtZq5938chCvV/zKTFDnzuXxCXhsSUh0p8s/P8ztgbfdueD9lFARQlQ==", - "dependencies": { - "@azure/msal-common": "15.7.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-common": { - "version": "15.7.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.7.1.tgz", - "integrity": "sha512-a0eowoYfRfKZEjbiCoA5bPT3IlWRAdGSvi63OU23Hv+X6EI8gbvXCoeqokUceFMoT9NfRUWTJSx5FiuzruqT8g==", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/@azure/msal-node": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.6.1.tgz", - "integrity": "sha512-ctcVz4xS+st5KxOlQqgpvA+uDFAa59CvkmumnuhlD2XmNczloKBdCiMQG7/TigSlaeHe01qoOlDjz3TyUAmKUg==", - "dependencies": { - "@azure/msal-common": "15.7.1", - "jsonwebtoken": "^9.0.0", - "uuid": "^8.3.0" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/@babel/runtime": { "version": "7.26.10", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.10.tgz", @@ -4967,11 +4819,6 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" - }, "node_modules/@types/website-scraper": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/@types/website-scraper/-/website-scraper-1.2.11.tgz", @@ -5225,19 +5072,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@typespec/ts-http-runtime": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.2.3.tgz", - "integrity": "sha512-oRhjSzcVjX8ExyaF8hC0zzTqxlVuRlgMHL/Bh4w3xB9+wjbm0FpXylVU/lBrn+kgphwYTrOk3tp+AVShGmlYCg==", - "dependencies": { - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -5651,14 +5485,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", - "engines": { - "node": ">= 14" - } - }, "node_modules/ajv": { "version": "8.18.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", @@ -5920,7 +5746,8 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true }, "node_modules/available-typed-arrays": { "version": "1.0.7", @@ -5952,6 +5779,7 @@ "version": "1.18.1", "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", @@ -5964,6 +5792,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "license": "MIT", "dependencies": { "debug": "4" @@ -5976,6 +5805,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, "license": "MIT", "dependencies": { "agent-base": "6", @@ -5995,25 +5825,6 @@ "node": ">= 0.4" } }, - "node_modules/azure-kusto-data": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/azure-kusto-data/-/azure-kusto-data-7.0.4.tgz", - "integrity": "sha512-1b1sW/xhqepgUsyY0JeSCQam/Zgbq0aAOsvdFxx5AZhf4gi8j87FngSIlkCSuhC8fvee7Q4cael3n/JrUa82Xg==", - "license": "ISC", - "dependencies": { - "@azure/core-util": "^1.10.0", - "@azure/identity": "^4.0.1", - "@types/uuid": "^8.3.4", - "axios": "^1.8.4", - "follow-redirects": "^1.15.1", - "https-browserify": "^1.0.0", - "stream-http": "^3.2.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 20.0.0" - } - }, "node_modules/bail": { "version": "2.0.1", "license": "MIT", @@ -6350,30 +6161,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==" - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/byte-counter": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/byte-counter/-/byte-counter-0.1.0.tgz", @@ -6946,6 +6733,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -7294,32 +7082,6 @@ "node": ">=0.10.0" } }, - "node_modules/default-browser": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.2.1.tgz", - "integrity": "sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.0.tgz", - "integrity": "sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -7337,17 +7099,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", @@ -7369,6 +7120,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "engines": { "node": ">=0.4.0" } @@ -7570,14 +7322,6 @@ "node": ">= 0.4" } }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, "node_modules/ee-first": { "version": "1.1.1", "license": "MIT" @@ -7766,6 +7510,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -9006,6 +8751,7 @@ "version": "1.16.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "dev": true, "funding": [ { "type": "individual", @@ -9042,6 +8788,7 @@ "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -9442,6 +9189,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -9910,18 +9658,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/http-proxy-middleware": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-4.1.1.tgz", @@ -9965,23 +9701,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==" - }, - "node_modules/https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/httpxy": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/httpxy/-/httpxy-0.5.3.tgz", @@ -10338,20 +10057,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-extendable": { "version": "0.1.1", "license": "MIT", @@ -10428,23 +10133,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -10719,20 +10407,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/isarray": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", @@ -10868,27 +10542,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" - } - }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -10904,26 +10557,6 @@ "node": ">=4.0" } }, - "node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", - "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.2", - "safe-buffer": "^5.0.1" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -11188,46 +10821,16 @@ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "license": "MIT" }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, "node_modules/lodash.isempty": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", "integrity": "sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==" }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, "node_modules/lodash.isobject": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==" }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, "node_modules/lodash.iteratee": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.iteratee/-/lodash.iteratee-4.7.0.tgz", @@ -11245,11 +10848,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" - }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", @@ -12454,6 +12052,7 @@ }, "node_modules/mime-db": { "version": "1.52.0", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -12461,6 +12060,7 @@ }, "node_modules/mime-types": { "version": "2.1.35", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -12940,23 +12540,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/open": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/open/-/open-10.1.2.tgz", - "integrity": "sha512-cxN6aIDPz6rm8hbebcP7vrQNhvRcveZoJU72Y7vskh4oIm+BZwBECnx5nTmrlres1Qapvx27Qo1Auukpf8PKXw==", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -13545,6 +13128,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -13703,19 +13287,6 @@ "react": ">=18" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -14137,17 +13708,6 @@ "node": ">= 18" } }, - "node_modules/run-applescript": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.0.0.tgz", - "integrity": "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -14178,10 +13738,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "license": "MIT" - }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -14827,17 +14383,6 @@ "node": ">= 0.4" } }, - "node_modules/stream-http": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", - "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "xtend": "^4.0.2" - } - }, "node_modules/strict-event-emitter": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.5.1.tgz", @@ -14845,33 +14390,6 @@ "dev": true, "license": "MIT" }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -16022,24 +15540,6 @@ "dev": true, "license": "WTFPL" }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" - }, - "node_modules/uuid": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", - "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, "node_modules/vary": { "version": "1.1.2", "license": "MIT", @@ -16706,14 +16206,6 @@ "version": "1.0.2", "license": "ISC" }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 044b03a788c1..b3dbd41c47a5 100644 --- a/package.json +++ b/package.json @@ -29,8 +29,6 @@ "cta-builder": "tsx src/content-render/scripts/cta-builder.ts", "debug": "cross-env NODE_ENV=development ENABLED_LANGUAGES=en nodemon --inspect src/frame/server.ts", "delete-orphan-translation-files": "tsx src/workflows/delete-orphan-translation-files.ts", - "docsaudit": "tsx src/metrics/scripts/docsaudit.ts", - "docstat": "tsx --disable-warning=DEP0190 src/metrics/scripts/docstat.ts", "deleted-assets-pr-comment": "tsx src/assets/scripts/deleted-assets-pr-comment.ts", "deleted-features-pr-comment": "tsx src/data-directory/scripts/deleted-features-pr-comment.ts", "deprecate-ghes": "tsx src/ghes-releases/scripts/deprecate/index.ts", @@ -193,7 +191,6 @@ "ajv": "^8.18.0", "ajv-errors": "^3.0.0", "ajv-formats": "^3.0.1", - "azure-kusto-data": "^7.0.4", "chalk": "^5.0.1", "cheerio": "^1.2.0", "clsx": "^2.1.1", diff --git a/src/github-apps/lib/config.json b/src/github-apps/lib/config.json index 68e16c4fc1f2..f7a83d2242e6 100644 --- a/src/github-apps/lib/config.json +++ b/src/github-apps/lib/config.json @@ -60,5 +60,5 @@ "2022-11-28" ] }, - "sha": "67abf4515e6df41e32e1c182fd097b9599e1a32f" + "sha": "2b78fb0c53617f188e45979ecefd83b521c5428a" } \ No newline at end of file diff --git a/src/metrics/README.md b/src/metrics/README.md deleted file mode 100644 index 18c31210db77..000000000000 --- a/src/metrics/README.md +++ /dev/null @@ -1,210 +0,0 @@ -# Metrics - -The metrics subject provides CLI tools for fetching analytics data from Kusto (Azure Data Explorer) about GitHub Docs usage. These tools help content strategists, writers, and engineers understand page performance, user behavior, and content effectiveness. - -## Purpose & Scope - -This subject is responsible for: -- Providing CLI tools to query Kusto for docs analytics -- `docstat` - Get metrics for a single URL (views, users, bounces, etc.) -- `docsaudit` - Get metrics for an entire content directory -- Kusto query abstractions for common metrics -- Authentication and connection to Azure Kusto -- Date range calculations for time-series queries - -## Architecture & Key Assets - -### Key capabilities and their locations - -- `lib/kusto-client.ts` - `getKustoClient()`: Creates authenticated Kusto client using Azure CLI -- `lib/kusto-client.ts` - `runQuery()`: Executes Kusto queries and returns results -- `scripts/docstat.ts` - CLI tool: Fetches metrics for a single docs URL -- `scripts/docsaudit.ts` - CLI tool: Audits entire content directories with CSV output -- `queries/*.ts` - Pre-defined Kusto queries for specific metrics - -## Setup & Usage - -### Installation and authentication - -1. Install Azure CLI: - ```bash - brew install azure-cli - ``` - -2. Login with Azure credentials: - ```bash - az login - ``` - Use your `@githubazure.com` credentials. - -3. Add Kusto configuration to `.env` file (values pinned in Slack): - ``` - KUSTO_CLUSTER='' - KUSTO_DATABASE='' - ``` - -### docstat usage - -Get metrics for a single URL: - -```bash -npm run docstat -- -``` - -Example: -```bash -npm run docstat -- https://docs.github.com/copilot/tutorials/modernize-legacy-code -``` - -Default metrics returned: -- 30-day views -- 30-day unique users -- Average view duration -- Bounce rate -- Helpfulness score (survey data) -- Exits to support - -#### Options - -```bash -# Compare with previous period -npm run docstat -- --compare - -# Custom date range (60 days) -npm run docstat -- --range 60 - -# Include redirects from frontmatter -npm run docstat -- --redirects - -# FPT data only (default includes all versions) -npm run docstat -- --fptOnly - -# JSON output -npm run docstat -- --json - -# Combine options -npm run docstat -- --compare --range 60 --redirects -``` - -#### JSON output with jq - -```bash -npm run --silent docstat -- --json | jq .data.users -``` - -### docsaudit usage - -Audit an entire content directory: - -```bash -npm run docsaudit -- -``` - -Example: -```bash -npm run docsaudit -- actions -``` - -Output includes: -- Title -- Path -- Versions -- 30-day views -- 30-day unique users - -Results are saved to a CSV file in the project root. - -## Data & External Dependencies - -### Data sources -- Kusto (Azure Data Explorer) - GitHub's data warehouse for analytics -- Docs event data - Page views, user interactions, surveys -- Content frontmatter - For path resolution and redirect detection - -### Dependencies -- `azure-kusto-data` - Official Azure Kusto SDK -- Azure CLI - For authentication (`az login`) -- Environment variables: `KUSTO_CLUSTER`, `KUSTO_DATABASE` - -### Authentication -- Uses Azure CLI identity via `withAzLoginIdentity()` -- Sessions are long-lasting but expire periodically -- Re-run `az login` when session expires - -### Queries -Pre-defined queries in `queries/` directory: -- `views.ts` - Total page views -- `users.ts` - Unique users -- `view-duration.ts` - Average session duration -- `bounces.ts` - Percentage of single-page sessions -- `survey-score.ts` - Helpfulness rating from surveys -- `exits-to-support.ts` - Clicks on support links - -## Cross-links & Ownership - -### Related subjects -- [`src/events`](../events/README.md) - Source of analytics event data -- [`src/frame`](../frame/README.md) - Frontmatter reading for path resolution -- Kusto database - Contains aggregated event data - -### Internal documentation -For Kusto cluster details and database schema, see [`analytics/kusto.md`](https://github.com/github/technical-content/blob/main/analytics/kusto.md). Credentials are pinned in the #technical-content Slack channel. - -### Ownership -- Team: Docs Content (with engineering support and reviews) -- Data questions: #docs-data - -## Current State & Next Steps - -### Known limitations -- Date range only accepts start date (end date is always current) -- Only English (`en`) language data is supported -- Queries are hardcoded in `queries/` directory -- URLs without version include all versions (FPT, GHEC, GHES combined) - -### Metrics available -Current metrics: -- Views (page view count) -- Users (unique user count) -- View duration (average time on page) -- Bounces (single-page sessions) -- Survey score (helpfulness rating) -- Exits to support (support link clicks) - -### Adding a new query - -1. Create new file in `src/metrics/queries/` -2. Export a function that returns a Kusto query string -3. Import and call in `docstat.ts` or `docsaudit.ts` -4. Update CLI options if needed - -Example: -```typescript -// queries/my-metric.ts -export function getMyMetric(path: string, startDate: string, endDate: string): string { - return ` - PageViews - | where Timestamp between (datetime(${startDate}) .. datetime(${endDate})) - | where Path == "${path}" - | summarize Count = count() - ` -} -``` - -### Troubleshooting - -**Azure login expired:** -```bash -az login -``` - -**Missing environment variables:** -Check `.env` file has `KUSTO_CLUSTER` and `KUSTO_DATABASE` (values in Slack) - -**No data found:** -- Verify URL is correct and includes `https://docs.github.com` -- Check date range (older content may have limited data) -- Try `--redirects` if article was recently moved - -**Permission errors:** -Ensure your Azure account has read access to the Kusto database. Contact #docs-data if needed. diff --git a/src/metrics/lib/dates.ts b/src/metrics/lib/dates.ts deleted file mode 100644 index 190fad5a0e6c..000000000000 --- a/src/metrics/lib/dates.ts +++ /dev/null @@ -1,32 +0,0 @@ -const dateOpts: Intl.DateTimeFormatOptions = { - year: 'numeric', - month: 'long', - day: 'numeric', -} - -export interface DateRange { - endDate: string - startDate: string - friendlyRange: string -} - -// `range` is a number of days. -export function getDates(range: string | number = '30'): DateRange { - const today = new Date() - const todayISO = today.toISOString() - - const daysAgo = getDaysAgo(Number(range)) - const daysAgoISO = daysAgo.toISOString() - - return { - endDate: todayISO, - startDate: daysAgoISO, - friendlyRange: `${daysAgo.toLocaleDateString('en-US', dateOpts)} - ${today.toLocaleDateString('en-US', dateOpts)}`, - } -} - -function getDaysAgo(range: number): Date { - const daysAgo = new Date() - daysAgo.setDate(daysAgo.getDate() - range) - return daysAgo -} diff --git a/src/metrics/lib/kusto-client.ts b/src/metrics/lib/kusto-client.ts deleted file mode 100644 index 3e9518ccbf30..000000000000 --- a/src/metrics/lib/kusto-client.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { - Client as KustoClient, - KustoConnectionStringBuilder, - KustoResultTable, -} from 'azure-kusto-data' - -import 'dotenv/config' - -if (!(process.env.KUSTO_CLUSTER || process.env.KUSTO_DATABASE)) { - console.error(`Add KUSTO_CLUSTER and KUSTO_DATABASE to your .env file`) - process.exit(0) -} - -const KUSTO_CLUSTER = process.env.KUSTO_CLUSTER! -const KUSTO_DATABASE = process.env.KUSTO_DATABASE! - -export interface KustoQueryResult { - primaryResults: KustoResultTable[] -} - -export function getKustoClient(): KustoClient | undefined { - let client: KustoClient | undefined - - try { - const kcsb = KustoConnectionStringBuilder.withAzLoginIdentity(KUSTO_CLUSTER) - client = new KustoClient(kcsb) - } catch (error) { - console.error('Error connecting to Kusto') - console.error(error) - } - - return client -} - -export async function runQuery( - pathToFetch: string | string[], - query: string, - client: KustoClient, - queryType: string, - verbose: boolean = false, -): Promise { - if (verbose) { - console.log(`\n--- EXECUTING QUERY FOR "${queryType.toUpperCase()}" ---`) - console.log(query) - console.log('----------------------\n') - } - - const results = await client.execute(KUSTO_DATABASE, query) - - if (results.primaryResults.length === 0) { - console.log(`No data found for URL: ${pathToFetch}`) - return null - } - - return results -} diff --git a/src/metrics/queries/bounces.ts b/src/metrics/queries/bounces.ts deleted file mode 100644 index adf61bd64be8..000000000000 --- a/src/metrics/queries/bounces.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { runQuery } from '@/metrics/lib/kusto-client' -import { SHARED_DECLARATIONS, SHARED_FILTERS } from '@/metrics/queries/constants' -import type { DateRange } from '@/metrics/lib/dates' -import type { Client as KustoClient } from 'azure-kusto-data' - -const QUERY_TYPE = 'bounces' - -export async function getBounces( - pathToFetch: string | string[], - client: KustoClient, - dates: DateRange, - version: string | null = null, - verbose: boolean = false, - queryType: string = QUERY_TYPE, -): Promise { - const query = getBouncesQuery(pathToFetch, dates, version) - const results = await runQuery(pathToFetch, query, client, queryType, verbose) - - if (!results) { - return '0%' - } - - const data = JSON.parse(results.primaryResults[0].toString()).data[0] - const bounces = data.Bounces - return bounces -} - -export function getBouncesQuery( - pathToFetch: string | string[], - dates: DateRange, - version: string | null, -): string { - return ` - ${SHARED_DECLARATIONS(pathToFetch, dates, version)} - let _exits = () { - docs_v0_exit_event - ${SHARED_FILTERS} - }; - _exits - | summarize Bounces=round( - countif(exit_scroll_length < 0.1 and exit_visit_duration < 5) / - toreal(count()), - 2 - ) - | project Bounces=strcat(toint( - Bounces * 100 - ), '%') - ` -} diff --git a/src/metrics/queries/constants.ts b/src/metrics/queries/constants.ts deleted file mode 100644 index 2400c6c4eeb0..000000000000 --- a/src/metrics/queries/constants.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type { DateRange } from '@/metrics/lib/dates' - -export const SHARED_DECLARATIONS = ( - path: string | string[], - dates: DateRange, - version: string | null, -): string => - ` - let _article = dynamic(['${Array.isArray(path) ? path.join("', '") : path}']); - let _articleType = dynamic(null); - let _endTime = datetime(${dates.endDate || null}); - let _language = dynamic(null); - let _pageType = dynamic(null); - let _product = dynamic(null); - let _startTime = datetime(${dates.startDate || null}); - let _version = dynamic(${version ? `['${version}']` : null}); -`.trim() - -export const SHARED_FILTERS = ` - | where timestamp between (_startTime .. _endTime) - | where context.hostname == 'docs.github.com' - | where abs(totimespan(context.created - timestamp)) < 1h - | where isempty(_language) or tostring(context.path_language) in (_language) - | where isempty(_version) or tostring(context.path_version) in (_version) - | where isempty(_article) or context.path_article has_any (_article) - | where isempty(_product) or tostring(context.path_product) in (_product) - | where isempty(_pageType) or tostring(context.page_document_type) in (_pageType) - | where isempty(_articleType) or tostring(context.page_type) in (_articleType) -`.trim() diff --git a/src/metrics/queries/exits-to-support.ts b/src/metrics/queries/exits-to-support.ts deleted file mode 100644 index bf83db7f2089..000000000000 --- a/src/metrics/queries/exits-to-support.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { runQuery } from '@/metrics/lib/kusto-client' -import { SHARED_DECLARATIONS, SHARED_FILTERS } from '@/metrics/queries/constants' -import type { DateRange } from '@/metrics/lib/dates' -import type { Client as KustoClient } from 'azure-kusto-data' - -const QUERY_TYPE = 'exits' - -export async function getExitsToSupport( - pathToFetch: string | string[], - client: KustoClient, - dates: DateRange, - version: string | null = null, - verbose: boolean = false, - queryType: string = QUERY_TYPE, -): Promise { - const query = getExitsQueryStatement(pathToFetch, dates, version) - const results = await runQuery(pathToFetch, query, client, queryType, verbose) - - if (!results) { - return '0%' - } - - const data = JSON.parse(results.primaryResults[0].toString()).data[0] - const exitsToSupport = data.Column1 - return exitsToSupport -} - -export function getExitsQueryStatement( - pathToFetch: string | string[], - dates: DateRange, - version: string | null, -): string { - return ` - ${SHARED_DECLARATIONS(pathToFetch, dates, version)} - let _links = () { - docs_v0_link_event - ${SHARED_FILTERS} - }; - _links - | where isempty(link_samesite) or link_samesite == false - | where link_samepage != true // allow false or null - | extend link_url_parsed=parse_url(link_url) - | extend IsSupport=tostring(link_url_parsed.Host) == "support.github.com" - and tostring(link_url_parsed.path) != "/enterprise/server-upgrade" - | summarize Ratio=round(countif(IsSupport) / toreal(count()), 2) - | project strcat(toint(Ratio * 100), '%') - ` -} diff --git a/src/metrics/queries/survey-score.ts b/src/metrics/queries/survey-score.ts deleted file mode 100644 index e0a6af1f23ac..000000000000 --- a/src/metrics/queries/survey-score.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { runQuery } from '@/metrics/lib/kusto-client' -import { SHARED_DECLARATIONS, SHARED_FILTERS } from '@/metrics/queries/constants' -import type { DateRange } from '@/metrics/lib/dates' -import type { Client as KustoClient } from 'azure-kusto-data' - -const QUERY_TYPE = 'score' - -export async function getScore( - pathToFetch: string | string[], - client: KustoClient, - dates: DateRange, - version: string | null = null, - verbose: boolean = false, - queryType: string = QUERY_TYPE, -): Promise { - const query = getScoreQuery(pathToFetch, dates, version) - const results = await runQuery(pathToFetch, query, client, queryType, verbose) - - if (!results) { - return '0%' - } - - const data = JSON.parse(results.primaryResults[0].toString()).data[0] - const score = data.Score - return score -} - -export function getScoreQuery( - pathToFetch: string | string[], - dates: DateRange, - version: string | null, -): string { - return ` - ${SHARED_DECLARATIONS(pathToFetch, dates, version)} - let _surveys = () { - docs_v0_survey_event - ${SHARED_FILTERS} - // Filter out Copilot response thumbs up/down events - | where context.event_group_key != "ask-ai" - // UNIQUE DO NOT DELETE - | summarize timestamp=arg_max(timestamp, *) - by User=toguid(context.user), Path=tostring(context.path_article) - }; - _surveys - | summarize Score=round( - (countif(survey_vote) + 0.75 * 30) - / (count() + 30), - 2 - ) - | project Score=strcat(toint(Score * 100), '%') - ` -} diff --git a/src/metrics/queries/users.ts b/src/metrics/queries/users.ts deleted file mode 100644 index a3e6e69d05bd..000000000000 --- a/src/metrics/queries/users.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { runQuery } from '@/metrics/lib/kusto-client' -import { SHARED_DECLARATIONS, SHARED_FILTERS } from '@/metrics/queries/constants' -import type { DateRange } from '@/metrics/lib/dates' -import type { Client as KustoClient } from 'azure-kusto-data' - -const QUERY_TYPE = 'users' - -export async function getUsers( - pathToFetch: string | string[], - client: KustoClient, - dates: DateRange, - version: string | null = null, - verbose: boolean = false, - queryType: string = QUERY_TYPE, -): Promise { - const query = getUsersQuery(pathToFetch, dates, version) - const results = await runQuery(pathToFetch, query, client, queryType, verbose) - - if (!results) { - return '0' - } - - const data = JSON.parse(results.primaryResults[0].toString()).data[0] - const users = data.Users - return users.toLocaleString() -} - -export function getUsersQuery( - pathToFetch: string | string[], - dates: DateRange, - version: string | null, -): string { - return ` - ${SHARED_DECLARATIONS(pathToFetch, dates, version)} - let _pages = () { - docs_v0_page_event - ${SHARED_FILTERS} - }; - _pages - | summarize Users=dcount(tostring(context.user)) - ` -} diff --git a/src/metrics/queries/view-duration.ts b/src/metrics/queries/view-duration.ts deleted file mode 100644 index e774961be674..000000000000 --- a/src/metrics/queries/view-duration.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { runQuery } from '@/metrics/lib/kusto-client' -import { SHARED_DECLARATIONS, SHARED_FILTERS } from '@/metrics/queries/constants' -import type { DateRange } from '@/metrics/lib/dates' -import type { Client as KustoClient } from 'azure-kusto-data' - -const QUERY_TYPE = 'view duration' - -export async function getViewDuration( - pathToFetch: string | string[], - client: KustoClient, - dates: DateRange, - version: string | null = null, - verbose: boolean = false, - queryType: string = QUERY_TYPE, -): Promise { - const query = getViewDurationQuery(pathToFetch, dates, version) - const results = await runQuery(pathToFetch, query, client, queryType, verbose) - - if (!results) { - return '0 seconds' - } - - const data = JSON.parse(results.primaryResults[0].toString()).data[0] - const viewDuration = data.avg_VisitDuration - return `${viewDuration} seconds` -} - -export function getViewDurationQuery( - pathToFetch: string | string[], - dates: DateRange, - version: string | null, -): string { - return ` - ${SHARED_DECLARATIONS(pathToFetch, dates, version)} - let _exits = () { - docs_v0_exit_event - ${SHARED_FILTERS} - }; - _exits - | summarize VisitDuration=round( - percentile(toreal(exit_visit_duration), 50), - 2 - ) - by bin(timestamp, 1d) - | summarize round(avg(VisitDuration), 2) - ` -} diff --git a/src/metrics/queries/views.ts b/src/metrics/queries/views.ts deleted file mode 100644 index 931b7389dce9..000000000000 --- a/src/metrics/queries/views.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { runQuery } from '@/metrics/lib/kusto-client' -import { SHARED_DECLARATIONS, SHARED_FILTERS } from '@/metrics/queries/constants' -import type { DateRange } from '@/metrics/lib/dates' -import type { Client as KustoClient } from 'azure-kusto-data' - -const QUERY_TYPE = 'views' - -export async function getViews( - pathToFetch: string | string[], - client: KustoClient, - dates: DateRange, - version: string | null = null, - verbose: boolean = false, - queryType: string = QUERY_TYPE, -): Promise { - const query = getViewsQuery(pathToFetch, dates, version) - const results = await runQuery(pathToFetch, query, client, queryType, verbose) - - if (!results) { - return '0' - } - - const data = JSON.parse(results.primaryResults[0].toString()).data[0] - const views = data.Views - return views.toLocaleString() -} - -export function getViewsQuery( - pathToFetch: string | string[], - dates: DateRange, - version: string | null, -): string { - return ` - ${SHARED_DECLARATIONS(pathToFetch, dates, version)} - let _pages = () { - docs_v0_page_event - ${SHARED_FILTERS} - }; - _pages - | summarize Views=count() - ` -} diff --git a/src/metrics/scripts/docsaudit.ts b/src/metrics/scripts/docsaudit.ts deleted file mode 100644 index d420333c88e1..000000000000 --- a/src/metrics/scripts/docsaudit.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @purpose Writer tool - * @description Get data about a top-level docs product and output a CSV - */ - -import fs from 'fs' -import path from 'path' -import { fileURLToPath } from 'url' -import { Command } from 'commander' -import walkFiles from '@/workflows/walk-files' -import readFrontmatter from '@/frame/lib/read-frontmatter' -import { getKustoClient } from '@/metrics/lib/kusto-client' -import { getDates, type DateRange } from '@/metrics/lib/dates' -import { getViews } from '@/metrics/queries/views' -import { getUsers } from '@/metrics/queries/users' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) -const ROOTDIR = process.cwd() - -interface CliOptions { - range?: string - verbose?: boolean -} - -const program = new Command() - -program - .name('docsaudit') - .description('Get data about a top-level docs product and output a CSV') - .argument('', 'Name of the content directory you want to audit, e.g., actions') - .option('-r, --range ', 'Number of days to look back', '30') - .option('--verbose', 'Display Kusto queries being executed') - .parse(process.argv) - -const options = program.opts() -const [auditDirName] = program.args -const contentDir = path.join(ROOTDIR, 'content') -const auditDir = path.join(contentDir, auditDirName) -const outputFile = path.join(__dirname, `${auditDirName}-audit.csv`) - -if (!fs.existsSync(auditDir)) { - console.error(`${auditDirName} not found`) - process.exit(1) -} - -const dates: DateRange = getDates(options.range) - -const files = walkFiles(auditDir, ['.md']) -console.log(`Auditing the ${files.length} "${auditDirName}" files. This may take a while.\n`) - -main() - -async function main(): Promise { - const client = getKustoClient() - - if (!client) { - console.error('Failed to initialize Kusto client') - process.exit(1) - } - - let csvString = `title,path,versions,${options.range}d views,${options.range}d users\n` - console.log(`Assembling data for these CSV columns: ${csvString}`) - - // Title, path, and versions come from the filesystem. - // Views and users come from Kusto. - const results: string[] = [] - for (const file of files) { - const contents = await fs.promises.readFile(file) - const contentPath = path.relative(ROOTDIR, file) - const { data } = readFrontmatter(contents.toString()) - const versionString = JSON.stringify(data?.versions || {}).replaceAll('"', "'") - const pathToQuery = getPathToQuery(file) - // null means all versions. - const version = null - const isFirst = results.length === 0 - const views = await getViews(pathToQuery, client, dates, version, options.verbose && isFirst) - const users = await getUsers(pathToQuery, client, dates, version, options.verbose && isFirst) - const csvEntry = `"${data?.title || 'Unknown'}","${contentPath}","${versionString}","${views}","${users}"` - console.log(csvEntry) - results.push(csvEntry) - } - csvString += `${results.join('\n')}\n` - - fs.writeFileSync(outputFile, csvString.trim(), 'utf8') - console.log(`Done! Wrote ${outputFile}`) -} - -function getPathToQuery(file: string): string { - return path.relative(contentDir, file).replace('/index.md', '').replace('.md', '') -} diff --git a/src/metrics/scripts/docstat.ts b/src/metrics/scripts/docstat.ts deleted file mode 100644 index 0ce9c2780f7e..000000000000 --- a/src/metrics/scripts/docstat.ts +++ /dev/null @@ -1,509 +0,0 @@ -/** - * @purpose Writer tool - * @description Get a data snapshot of a given Docs URL for the last 30 days or specified period - */ - -import fs from 'fs' -import path from 'path' -import { Command } from 'commander' -import chalk from 'chalk' -import ora from 'ora' -import frontmatter from '@/frame/lib/read-frontmatter' -import { getKustoClient } from '@/metrics/lib/kusto-client' -import { getDates, type DateRange } from '@/metrics/lib/dates' -import { getViews } from '@/metrics/queries/views' -import { getUsers } from '@/metrics/queries/users' -import { getViewDuration } from '@/metrics/queries/view-duration' -import { getBounces } from '@/metrics/queries/bounces' -import { getScore } from '@/metrics/queries/survey-score' -import { getExitsToSupport } from '@/metrics/queries/exits-to-support' - -const { green, white, red, blue } = chalk - -const DOCS_ROOT = 'https://docs.github.com' -const DOCS_API_PATH = 'https://docs.github.com/api/pagelist/en' -const FREE_PRO_TEAM = 'free-pro-team@latest' -const ENTERPRISE_REGEX = /enterprise-(server|cloud)@/ - -interface CliOptions { - range?: string - compare?: boolean - views?: boolean - users?: boolean - viewDuration?: boolean - bounces?: boolean - score?: boolean - exits?: boolean - json?: boolean - skipValidation?: boolean - redirects?: boolean - fptOnly?: boolean - verbose?: boolean - defaultToAll?: boolean - showDocset?: boolean - allVersions?: boolean - quiet?: boolean -} - -interface JsonOutput { - daysRange: string - startDate: string - endDate: string - dateRange: string - inputUrl: string - data: { - path: string - views?: string - users?: string - viewDuration?: string - bounces?: string - score?: string - exits?: string - } - docset?: { - path: string - data: { - views?: string - users?: string - viewDuration?: string - bounces?: string - score?: string - exits?: string - } - } -} - -const program = new Command() - -program - .name('docstat') - .description( - `Get a data snapshot of a given Docs URL for the last 30 days or specified period. By default, it looks up: - -- Views -- Users -- View duration per day average -- Bounces -- Helpfulness score -- Exits to support`, - ) - .argument('', 'URL to query data for') - .option('-r, --range ', 'Number of days to look back', '30') - .option('-c, --compare', 'Compare with top-level docset data') - .option('-v, --views', 'Get page views') - .option('-u, --users', 'Get unique users') - .option('-d, --viewDuration', 'Get view duration per day average') - .option('-b, --bounces', 'Get bounces') - .option('-s, --score', 'Get helpfulness survey score') - .option('-e, --exits', 'Get exits to support percentage') - .option('-j, --json', 'Output results in JSON format') - .option('-k, --skipValidation', 'Skip path validation against Docs API pagelist') - .option('--redirects', 'Include all redirected URLs for the given URL in the queries') - .option( - '--fptOnly', - 'Get data for free-pro-team@latest only (default: all versions if URL is versionless)', - ) - .option('--verbose', 'Display Kusto queries being executed') - .option('-q, --quiet', 'Suppress all output except results (automatically enabled with --json)') - .parse(process.argv) - -const options = program.opts() - -if (options.json) { - options.quiet = true -} - -options.defaultToAll = !( - options.views || - options.users || - options.viewDuration || - options.bounces || - options.score || - options.exits -) - -if (options.defaultToAll) { - options.views = true - options.users = true - options.viewDuration = true - options.bounces = true - options.score = true - options.exits = true -} - -const providedPath = program.args[0] -let cleanPath = getCleanPath(providedPath) - -let version: string | null = getVersion(cleanPath) -const usingFptOnly = !!options.fptOnly - -// getVersion() returns FREE_PRO_TEAM when the URL names no version, -// so this branch is really "no version given". -if (version === FREE_PRO_TEAM) { - if (usingFptOnly) { - if (!options.quiet) { - console.log( - '\nFetching data for free-pro-team@latest only. To get all versions, omit the --fptOnly flag.\n', - ) - } - } else { - version = null - if (!options.quiet) { - console.log( - '\nFetching data for all versions (no version specified in URL). To get only free-pro-team@latest, pass "--fptOnly".\n', - ) - } - } -} else { - if (!options.quiet) { - console.log( - `\nFetching data for version "${version}" as specified in the URL. To get data for all versions, remove the version segment from the URL.\n`, - ) - } - if (usingFptOnly) { - if (!options.quiet) { - console.log( - `You specified a version in the URL (${version}), but also passed --fptOnly. Only the version in the URL will be used.\n`, - ) - } - } -} - -const VERSIONED_DOCS_API_PATH = path.join(DOCS_API_PATH, version || FREE_PRO_TEAM) -cleanPath = removeVersionSegment(cleanPath, version || FREE_PRO_TEAM) -if (!options.skipValidation) await validatePath(cleanPath, version || FREE_PRO_TEAM) - -if (options.allVersions) version = null - -const docsetPath = cleanPath.split('/')[0] - -// Queries cover the page's old paths too, so traffic isn't undercounted. -let redirects: string[] = [] -if (options.redirects) { - let contentPath = path.join('content', cleanPath) - contentPath = fs.existsSync(contentPath) - ? path.join(contentPath, 'index.md') - : `${contentPath}.md` - const { data } = frontmatter(fs.readFileSync(contentPath, 'utf8')) - redirects = (data?.redirect_from || []).map((oldPath: string) => oldPath.replace('/', '')) // remove leading '/' -} - -const queryPaths = [cleanPath].concat(redirects) - -const dates: DateRange = getDates(options.range) - -async function main(): Promise { - const spinner = options.quiet ? null : ora('Connecting to Kusto...').start() - - try { - const client = getKustoClient() - - if (!client) { - if (spinner) spinner.fail('Failed to connect to Kusto') - else if (!options.quiet) console.error('Failed to connect to Kusto') - process.exit(1) - } - - if (spinner) spinner.text = 'Connected! Querying Kusto...' - - options.showDocset = !(cleanPath === docsetPath) && options.compare - if (options.compare && cleanPath === docsetPath && !options.quiet) { - console.log(`\n\nSkipping comparison, since '${cleanPath}' is already a docset.\n`) - } - - const [ - views, - viewsDocset, - users, - usersDocset, - viewDuration, - viewDurationDocset, - bounces, - bouncesDocset, - score, - scoreDocset, - exits, - exitsDocset, - ] = await Promise.all([ - options.views - ? getViews(queryPaths, client, dates, version, options.verbose, 'views') - : undefined, - options.views && options.showDocset - ? getViews(docsetPath, client, dates, version, options.verbose, 'docset views') - : undefined, - options.users - ? getUsers(queryPaths, client, dates, version, options.verbose, 'users') - : undefined, - options.users && options.showDocset - ? getUsers(docsetPath, client, dates, version, options.verbose, 'docset users') - : undefined, - options.viewDuration - ? getViewDuration(queryPaths, client, dates, version, options.verbose, 'view duration') - : undefined, - options.viewDuration && options.showDocset - ? getViewDuration( - docsetPath, - client, - dates, - version, - options.verbose, - 'docset view duration', - ) - : undefined, - options.bounces - ? getBounces(queryPaths, client, dates, version, options.verbose, 'bounces') - : undefined, - options.bounces && options.showDocset - ? getBounces(docsetPath, client, dates, version, options.verbose, 'docset bounces') - : undefined, - options.score - ? getScore(queryPaths, client, dates, version, options.verbose, 'score') - : undefined, - options.score && options.showDocset - ? getScore(docsetPath, client, dates, version, options.verbose, 'docset score') - : undefined, - options.exits - ? getExitsToSupport(queryPaths, client, dates, version, options.verbose, 'exits') - : undefined, - options.exits && options.showDocset - ? getExitsToSupport(docsetPath, client, dates, version, options.verbose, 'docset exits') - : undefined, - ]) - - if (spinner) spinner.succeed('Data retrieved successfully!\n') - - if (options.json) { - const jsonOutput: JsonOutput = { - daysRange: options.range || '30', - startDate: dates.startDate, - endDate: dates.endDate, - dateRange: dates.friendlyRange, - inputUrl: program.args[0], - data: { - path: cleanPath, - }, - } - - if (options.views) { - jsonOutput.data.views = views - } - if (options.users) { - jsonOutput.data.users = users - } - if (options.viewDuration) { - jsonOutput.data.viewDuration = viewDuration - } - if (options.bounces) { - jsonOutput.data.bounces = bounces - } - if (options.score) { - jsonOutput.data.score = score - } - if (options.exits) { - jsonOutput.data.exits = exits - } - - if (options.showDocset) { - jsonOutput.docset = { - path: docsetPath, - data: {}, - } - - if (options.views) { - jsonOutput.docset.data.views = viewsDocset - } - if (options.users) { - jsonOutput.docset.data.users = usersDocset - } - if (options.viewDuration) { - jsonOutput.docset.data.viewDuration = viewDurationDocset - } - if (options.bounces) { - jsonOutput.docset.data.bounces = bouncesDocset - } - if (options.score) { - jsonOutput.docset.data.score = scoreDocset - } - if (options.exits) { - jsonOutput.docset.data.exits = exitsDocset - } - } - - console.log(JSON.stringify(jsonOutput, null, 2)) - return - } - - console.log(white(`Last ${options.range || '30'} days:`), blue(dates.friendlyRange)) - console.log(green('-------------------------------------------')) - console.log(green('Path:'), white(cleanPath)) - if (options.redirects) { - console.log( - green('Redirects included:'), - white(redirects.length ? redirects.join(', ') : 'none found'), - ) - } - console.log(green('Version:'), white(version || 'all versions')) - console.log('') - - if (options.views) { - console.log(green('Views:'), white(views)) - } - if (options.users) { - console.log(green('Users:'), white(users)) - } - if (options.viewDuration) { - console.log(green('View duration per day average:'), white(viewDuration)) - } - if (options.bounces) { - console.log(green('Bounces:'), white(`${bounces}`)) - } - if (options.score) { - console.log(green('Score:'), white(`${score}`)) - } - if (options.exits) { - console.log(green(`Exits to support:`), white(`${exits}`)) - } - - if (options.showDocset) { - console.log('') - console.log(white('Comparing to...')) - console.log(green('-------------------------------------------')) - console.log(green('Docset:'), white(docsetPath)) - console.log(green('Version:'), white(version || 'all versions')) - console.log('') - - if (options.views) { - console.log(green('Views:'), white(viewsDocset)) - } - if (options.users) { - console.log(green('Users:'), white(usersDocset)) - } - if (options.viewDuration) { - console.log(green(`View duration per day average:`), white(`${viewDurationDocset}`)) - } - if (options.bounces) { - console.log(green(`Bounces:`), white(`${bouncesDocset}`)) - } - if (options.score) { - console.log(green(`Score:`), white(`${scoreDocset}`)) - } - if (options.exits) { - console.log(green(`Exits to support:`), white(`${exitsDocset}`)) - } - } - - console.log(green('-------------------------------------------')) - } catch (error) { - if (spinner) spinner.fail('Error getting data') - - if (options.json) { - console.log( - JSON.stringify( - { - error: true, - message: 'Error getting data', - details: error instanceof Error ? error.message : String(error), - }, - null, - 2, - ), - ) - } else { - console.error(red('Error details:')) - console.error(error) - } - } -} - -try { - await main() -} catch (error) { - if (options.json) { - console.log( - JSON.stringify( - { - error: true, - message: 'Unexpected error', - details: error instanceof Error ? error.message : String(error), - }, - null, - 2, - ), - ) - } else { - console.error(red('Unexpected error:')) - console.error(error) - } - process.exit(1) -} - -// Given input: https://docs.github.com/en/copilot/managing-copilot/ -// Use: copilot/managing-copilot -function getCleanPath(inputPath: string): string { - let clean = inputPath - const cleanArr = clean.split('?') // remove query params - if (cleanArr.length > 1) cleanArr.pop() - clean = cleanArr.join('/') - const cleanArr2 = clean.split('#') // remove hash fragments - if (cleanArr2.length > 1) cleanArr2.pop() - clean = cleanArr2.join('/') - if (clean === DOCS_ROOT || clean === `${DOCS_ROOT}/en`) { - // Kusto uses 'index' for the homepage - return 'index' - } - const pathParts = clean.replace(DOCS_ROOT, '').split('/').filter(Boolean) - if (pathParts[0] === 'en') pathParts.shift() // remove English lang code - clean = pathParts.join('/') - - return clean -} - -function getVersion(pathToCheck: string): string { - const pathParts = pathToCheck.split('/') - const versionString = ENTERPRISE_REGEX.test(pathParts[0]) ? pathParts[0] : FREE_PRO_TEAM - return versionString -} - -function removeVersionSegment(pathToProcess: string, versionString: string): string { - if (versionString === FREE_PRO_TEAM) return pathToProcess - const pathParts = pathToProcess.split('/') - pathParts.shift() - if (!pathParts.length) return 'index' - return pathParts.join('/') -} - -// Try to find the path in the list of valid pages at https://docs.github.com/api/pagelist/en -async function validatePath(pathToValidate: string, versionToValidate: string): Promise { - // Only Kusto uses 'index' for the homepage; the Docs API uses '/en' - const basePath = pathToValidate === 'index' ? '' : pathToValidate - - const pathToCheck = - versionToValidate === FREE_PRO_TEAM - ? path.join('/', 'en', basePath) - : path.join('/', 'en', versionToValidate, basePath) - - let data: string - try { - const response = await fetch(VERSIONED_DOCS_API_PATH) - data = await response.text() - } catch (err) { - console.error(`Error fetching data from ${VERSIONED_DOCS_API_PATH}`) - throw err - } - - if (data.startsWith('{')) { - const parsedData = JSON.parse(data) - if (parsedData.error) { - console.error(data) - process.exit(1) - } - } - - const isValid = data.includes(pathToCheck) - if (!isValid) { - console.error( - `Error! Provided URL is not in Docs API list of valid paths at ${VERSIONED_DOCS_API_PATH}`, - ) - process.exit(1) - } -} diff --git a/src/rest/data/fpt-2022-11-28/pulls.json b/src/rest/data/fpt-2022-11-28/pulls.json index 9da510c162dc..2654989c36e7 100644 --- a/src/rest/data/fpt-2022-11-28/pulls.json +++ b/src/rest/data/fpt-2022-11-28/pulls.json @@ -16539,6 +16539,12 @@ "direct_merge", "merge_queue" ] + }, + { + "type": "boolean", + "name": "bypass_rules", + "description": "

Whether to bypass repository rules that the authenticated actor is permitted to bypass.

", + "default": false } ], "descriptionHTML": "

Merges a pull request into the base branch in the background or adds it to a merge queue. Background processing allows certain types of errors to be retried and reduces the risk of timeouts for complex merges.

\n

This is the required API for merging stacked pull requests. For a stacked pull request, the operation includes all open downstack pull requests.

\n

A new asynchronous merge request returns a 202 response with a UUID that can be used to fetch the result of the merge. If another asynchronous merge request is already pending for this pull request, a 409 response returns that request's UUID and merge options instead.

\n

If the pull request is already merged or already in a merge queue, a 200 response is returned immediately. A merged result includes the merge commit OID. An enqueued result means the pull request was added to the merge queue, not that it has merged.

\n

If the pull request cannot be merged (e.g. because it is closed, or still a draft) this result will be returned immediately with a 400 response status. Branch protection rules and repository rules are not run at this stage, only basic pull request state checks are performed.

", @@ -16603,6 +16609,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16742,6 +16753,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16882,6 +16898,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17025,6 +17046,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17215,7 +17241,8 @@ "uuid": "630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42", "merge_method": "merge", "merge_action": "default", - "expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + "expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "bypass_rules": false } }, "schema": { @@ -17266,6 +17293,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17398,6 +17430,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17531,6 +17568,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17663,6 +17705,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ diff --git a/src/rest/data/fpt-2026-03-10/pulls.json b/src/rest/data/fpt-2026-03-10/pulls.json index 2ad26847ae0b..8bb8088b8df6 100644 --- a/src/rest/data/fpt-2026-03-10/pulls.json +++ b/src/rest/data/fpt-2026-03-10/pulls.json @@ -15844,6 +15844,12 @@ "direct_merge", "merge_queue" ] + }, + { + "type": "boolean", + "name": "bypass_rules", + "description": "

Whether to bypass repository rules that the authenticated actor is permitted to bypass.

", + "default": false } ], "descriptionHTML": "

Merges a pull request into the base branch in the background or adds it to a merge queue. Background processing allows certain types of errors to be retried and reduces the risk of timeouts for complex merges.

\n

This is the required API for merging stacked pull requests. For a stacked pull request, the operation includes all open downstack pull requests.

\n

A new asynchronous merge request returns a 202 response with a UUID that can be used to fetch the result of the merge. If another asynchronous merge request is already pending for this pull request, a 409 response returns that request's UUID and merge options instead.

\n

If the pull request is already merged or already in a merge queue, a 200 response is returned immediately. A merged result includes the merge commit OID. An enqueued result means the pull request was added to the merge queue, not that it has merged.

\n

If the pull request cannot be merged (e.g. because it is closed, or still a draft) this result will be returned immediately with a 400 response status. Branch protection rules and repository rules are not run at this stage, only basic pull request state checks are performed.

", @@ -15908,6 +15914,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16047,6 +16058,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16187,6 +16203,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16330,6 +16351,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16520,7 +16546,8 @@ "uuid": "630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42", "merge_method": "merge", "merge_action": "default", - "expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + "expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "bypass_rules": false } }, "schema": { @@ -16571,6 +16598,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16703,6 +16735,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16836,6 +16873,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16968,6 +17010,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ diff --git a/src/rest/data/ghec-2022-11-28/pulls.json b/src/rest/data/ghec-2022-11-28/pulls.json index 7af88560fb12..d2a660bed669 100644 --- a/src/rest/data/ghec-2022-11-28/pulls.json +++ b/src/rest/data/ghec-2022-11-28/pulls.json @@ -16539,6 +16539,12 @@ "direct_merge", "merge_queue" ] + }, + { + "type": "boolean", + "name": "bypass_rules", + "description": "

Whether to bypass repository rules that the authenticated actor is permitted to bypass.

", + "default": false } ], "descriptionHTML": "

Merges a pull request into the base branch in the background or adds it to a merge queue. Background processing allows certain types of errors to be retried and reduces the risk of timeouts for complex merges.

\n

This is the required API for merging stacked pull requests. For a stacked pull request, the operation includes all open downstack pull requests.

\n

A new asynchronous merge request returns a 202 response with a UUID that can be used to fetch the result of the merge. If another asynchronous merge request is already pending for this pull request, a 409 response returns that request's UUID and merge options instead.

\n

If the pull request is already merged or already in a merge queue, a 200 response is returned immediately. A merged result includes the merge commit OID. An enqueued result means the pull request was added to the merge queue, not that it has merged.

\n

If the pull request cannot be merged (e.g. because it is closed, or still a draft) this result will be returned immediately with a 400 response status. Branch protection rules and repository rules are not run at this stage, only basic pull request state checks are performed.

", @@ -16603,6 +16609,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16742,6 +16753,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16882,6 +16898,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17025,6 +17046,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17215,7 +17241,8 @@ "uuid": "630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42", "merge_method": "merge", "merge_action": "default", - "expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + "expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "bypass_rules": false } }, "schema": { @@ -17266,6 +17293,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17398,6 +17430,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17531,6 +17568,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -17663,6 +17705,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ diff --git a/src/rest/data/ghec-2026-03-10/pulls.json b/src/rest/data/ghec-2026-03-10/pulls.json index b4ea9123b21b..51942c76cfd8 100644 --- a/src/rest/data/ghec-2026-03-10/pulls.json +++ b/src/rest/data/ghec-2026-03-10/pulls.json @@ -15844,6 +15844,12 @@ "direct_merge", "merge_queue" ] + }, + { + "type": "boolean", + "name": "bypass_rules", + "description": "

Whether to bypass repository rules that the authenticated actor is permitted to bypass.

", + "default": false } ], "descriptionHTML": "

Merges a pull request into the base branch in the background or adds it to a merge queue. Background processing allows certain types of errors to be retried and reduces the risk of timeouts for complex merges.

\n

This is the required API for merging stacked pull requests. For a stacked pull request, the operation includes all open downstack pull requests.

\n

A new asynchronous merge request returns a 202 response with a UUID that can be used to fetch the result of the merge. If another asynchronous merge request is already pending for this pull request, a 409 response returns that request's UUID and merge options instead.

\n

If the pull request is already merged or already in a merge queue, a 200 response is returned immediately. A merged result includes the merge commit OID. An enqueued result means the pull request was added to the merge queue, not that it has merged.

\n

If the pull request cannot be merged (e.g. because it is closed, or still a draft) this result will be returned immediately with a 400 response status. Branch protection rules and repository rules are not run at this stage, only basic pull request state checks are performed.

", @@ -15908,6 +15914,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16047,6 +16058,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16187,6 +16203,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16330,6 +16351,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16520,7 +16546,8 @@ "uuid": "630b9d5e-3f2a-4f7e-8b0c-2d5f9a8c1e42", "merge_method": "merge", "merge_action": "default", - "expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e" + "expected_head_sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e", + "bypass_rules": false } }, "schema": { @@ -16571,6 +16598,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16703,6 +16735,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16836,6 +16873,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ @@ -16968,6 +17010,11 @@ "expected_head_sha": { "type": "string", "description": "SHA that the pull request head must match for the enqueued merge to proceed." + }, + "bypass_rules": { + "type": "boolean", + "default": false, + "description": "Whether the asynchronous merge request will attempt to bypass repository rules that the authenticated actor is permitted to bypass." } }, "required": [ diff --git a/src/rest/lib/config.json b/src/rest/lib/config.json index 7c97a09bc885..4f0093549e05 100644 --- a/src/rest/lib/config.json +++ b/src/rest/lib/config.json @@ -48,5 +48,5 @@ ] } }, - "sha": "67abf4515e6df41e32e1c182fd097b9599e1a32f" + "sha": "2b78fb0c53617f188e45979ecefd83b521c5428a" } \ No newline at end of file diff --git a/src/webhooks/lib/config.json b/src/webhooks/lib/config.json index a493570fc65f..3d5480812f35 100644 --- a/src/webhooks/lib/config.json +++ b/src/webhooks/lib/config.json @@ -1,3 +1,3 @@ { - "sha": "67abf4515e6df41e32e1c182fd097b9599e1a32f" + "sha": "2b78fb0c53617f188e45979ecefd83b521c5428a" } \ No newline at end of file diff --git a/src/workflows/writers-help-metadata.ts b/src/workflows/writers-help-metadata.ts index 31293f9b837d..482982a44b92 100644 --- a/src/workflows/writers-help-metadata.ts +++ b/src/workflows/writers-help-metadata.ts @@ -175,7 +175,6 @@ function prioritizeOrder(tools: WriterToolsCollection) { 'move-content': 1, 'cta-builder': 2, 'lint-content': 1, - docstat: 1, dev: 1, } @@ -219,6 +218,16 @@ async function main(): Promise { } console.log('') } + + console.log('Moved to github/technical-content, in .github/scripts:') + for (const [name, description] of [ + ['docstat', 'Page metrics for a docs URL'], + ['docsaudit', 'Metrics for a set of pages'], + ]) { + const padding = ' '.repeat(Math.max(0, 34 - name.length)) + console.log(` npm run ${name}${padding}# ${description}`) + } + console.log('Run "npm install" in that directory once before using them.\n') } if (import.meta.url === `file://${process.argv[1]}`) {