From f97b6b635eb64a0341feb763154385bdc37f451b Mon Sep 17 00:00:00 2001 From: sinedied Date: Fri, 23 Jan 2026 14:41:22 +0100 Subject: [PATCH 1/5] chore: cleanup --- packages/cli/src/commands/build.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/build.ts b/packages/cli/src/commands/build.ts index 822642a4..cc909f26 100644 --- a/packages/cli/src/commands/build.ts +++ b/packages/cli/src/commands/build.ts @@ -2,7 +2,7 @@ import fs from 'node:fs/promises'; import process from 'node:process'; import { dirname, parse } from 'node:path'; import createDebug from 'debug'; -import { pathExists, readJson } from '../util.js'; +import { pathExists } from '../util.js'; import { defaultWorkshopFile } from '../constants.js'; import { processFileIncludes } from '../include.js'; From 6f978413157ee3f8c2b89215006c0a4fc1e90f57 Mon Sep 17 00:00:00 2001 From: sinedied Date: Fri, 23 Jan 2026 14:42:03 +0100 Subject: [PATCH 2/5] feat(cli): add translate command using Copilot SDK --- package-lock.json | 204 ++++++++++++++++++++++--- packages/cli/package.json | 1 + packages/cli/src/cli.ts | 22 ++- packages/cli/src/commands/index.ts | 1 + packages/cli/src/commands/translate.ts | 96 ++++++++++++ packages/cli/tsconfig.json | 3 +- 6 files changed, 303 insertions(+), 24 deletions(-) create mode 100644 packages/cli/src/commands/translate.ts diff --git a/package-lock.json b/package-lock.json index e8668c09..245a54c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -323,6 +323,7 @@ "version": "17.3.10", "resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.10.tgz", "integrity": "sha512-6SfD21M3LujymmZsZQIxAsV8Bj5u6He6ImZ+p2rr7FAhFxpVJyKldK8LCmJcFsBD4srpQcxEZ0iDxXvg+0ihAw==", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -358,6 +359,7 @@ "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.10.tgz", "integrity": "sha512-85SBphqRj3szac3FbeYgEZ+I6WaAlo5h7JX06BdjOLLiaoIwlFhLeAuG+jVekseV+95grFUxIsCMphWHi2e6hQ==", "dev": true, + "peer": true, "dependencies": { "@babel/core": "7.23.9", "@jridgewell/sourcemap-codec": "^1.4.14", @@ -430,6 +432,7 @@ "version": "17.3.10", "resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.10.tgz", "integrity": "sha512-ocEKu7X0yFCOvgJn1uZy76qjhsjKvULrO1k/BuIX0nwhp61DTGYTvCqKmwCBLM8/gvcKYH5vMKMHoQKtiSGE0A==", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -445,6 +448,7 @@ "version": "17.3.10", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.10.tgz", "integrity": "sha512-LEhBDOKm2A7nRmZqsafVp6OinRDG1OYZBSqjnT1jZ+f0CRRFIXz6aJ0TMPoU6vq9SLRJ7vrGD9P/eBf2hW00NQ==", + "peer": true, "dependencies": { "tslib": "^2.3.0" }, @@ -576,6 +580,7 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@asciidoctor/core/-/core-3.0.4.tgz", "integrity": "sha512-41SDMi7iRRBViPe0L6VWFTe55bv6HEOJeRqMj5+E5wB1YPdUPuTucL4UAESPZM6OWmn4t/5qM5LusXomFUVwVQ==", + "peer": true, "dependencies": { "@asciidoctor/opal-runtime": "3.0.1", "unxhr": "1.2.0" @@ -626,6 +631,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz", "integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==", "dev": true, + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.23.5", @@ -3018,6 +3024,133 @@ "node": ">=14" } }, + "node_modules/@github/copilot": { + "version": "0.0.389", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-0.0.389.tgz", + "integrity": "sha512-XCHMCd8fu7g9WAp+ZepXBF1ud8vdfxDG4ajstGJqHfbdz0RxQktB35R5s/vKizpYXSZogFqwjxl41qX8DypY6g==", + "license": "MIT", + "bin": { + "copilot": "npm-loader.js" + }, + "optionalDependencies": { + "@github/copilot-darwin-arm64": "0.0.389", + "@github/copilot-darwin-x64": "0.0.389", + "@github/copilot-linux-arm64": "0.0.389", + "@github/copilot-linux-x64": "0.0.389", + "@github/copilot-win32-arm64": "0.0.389", + "@github/copilot-win32-x64": "0.0.389" + } + }, + "node_modules/@github/copilot-darwin-arm64": { + "version": "0.0.389", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-0.0.389.tgz", + "integrity": "sha512-4Crm/C9//ZPsK+NP5E5BEjltAGuij9XkvRILvZ/mqlaiDXRncFvUtdOoV+/Of+i4Zva/1sWnc7CrS7PHGJDyFg==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "copilot-darwin-arm64": "copilot" + } + }, + "node_modules/@github/copilot-darwin-x64": { + "version": "0.0.389", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-0.0.389.tgz", + "integrity": "sha512-w0LB+lw29UmRS9oW8ENyZhrf3S5LQ3Pz796dQY8LZybp7WxEGtQhvXN48mye9gGzOHNoHxQ2+10+OzsjC/mLUQ==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "darwin" + ], + "bin": { + "copilot-darwin-x64": "copilot" + } + }, + "node_modules/@github/copilot-linux-arm64": { + "version": "0.0.389", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-0.0.389.tgz", + "integrity": "sha512-8QNvfs4r6nrbQrT4llu0CbJHcCJosyj+ZgLSpA+lqIiO/TiTQ48kV41uNjzTz1RmR6/qBKcz81FB7HcHXpT3xw==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linux-arm64": "copilot" + } + }, + "node_modules/@github/copilot-linux-x64": { + "version": "0.0.389", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-0.0.389.tgz", + "integrity": "sha512-ls42wSzspC7sLiweoqu2zT75mqMsLWs+IZBfCqcuH1BV+C/j/XSEHsSrJxAI3TPtIsOTolPbTAa8jye1nGDxeg==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linux-x64": "copilot" + } + }, + "node_modules/@github/copilot-sdk": { + "version": "0.1.16", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.1.16.tgz", + "integrity": "sha512-yEZrrUl9w6rvKmjJpzpqovL39GzFrHxnIXOSK/bQfFwk7Ak/drmBk2gOwJqDVJcbhUm2dsoeLIfok7vtyjAxTw==", + "license": "MIT", + "dependencies": { + "@github/copilot": "^0.0.389", + "vscode-jsonrpc": "^8.2.1", + "zod": "^4.3.5" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@github/copilot-win32-arm64": { + "version": "0.0.389", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-0.0.389.tgz", + "integrity": "sha512-loniaCnrty9okQMl3EhxeeyDhnrJ/lJK0Q0r7wkLf1d/TM2swp3tsGZyIRlhDKx5lgcnCPm1m0BqauMo8Vs34g==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ], + "bin": { + "copilot-win32-arm64": "copilot.exe" + } + }, + "node_modules/@github/copilot-win32-x64": { + "version": "0.0.389", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-0.0.389.tgz", + "integrity": "sha512-L1ZzwV/vsxnrz0WO4qLDUlXXFQQ9fOFuBGKWy6TXS7aniaxI/7mdRQR1YjIEqy+AzRw9BaXR2UUUUDk0gb1+kw==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "win32" + ], + "bin": { + "copilot-win32-x64": "copilot.exe" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -3556,6 +3689,7 @@ "integrity": "sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@octokit/auth-token": "^6.0.0", "@octokit/graphql": "^9.0.3", @@ -5206,6 +5340,7 @@ "version": "18.19.34", "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.34.tgz", "integrity": "sha512-eXF4pfBNV5DAMKGbI02NnDtWrQ40hAN558/2vvS4gMpMIxaf6JmD7YjnZbq0Q9TDSSkKBamime8ewRoomHdt4g==", + "peer": true, "dependencies": { "undici-types": "~5.26.4" } @@ -5330,6 +5465,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.12.0.tgz", "integrity": "sha512-7F91fcbuDf/d3S8o21+r3ZncGIke/+eWk0EpO21LXhDfLahriZF9CGj4fbAetEjlaBdjdSm9a6VeXbpbT6Z40Q==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "7.12.0", @@ -5363,6 +5499,7 @@ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.12.0.tgz", "integrity": "sha512-dm/J2UDY3oV3TKius2OUZIFHsomQmpHtsV0FTh1WO8EKgHLQ1QCADUqscPgTpU+ih1e21FQSRjXckHn3txn6kQ==", "dev": true, + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "7.12.0", "@typescript-eslint/types": "7.12.0", @@ -5766,6 +5903,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5846,6 +5984,7 @@ "version": "8.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -6879,6 +7018,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001587", "electron-to-chromium": "^1.4.668", @@ -7158,6 +7298,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "peer": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -8361,15 +8502,6 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, - "node_modules/cytoscape": { - "version": "3.29.2", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.29.2.tgz", - "integrity": "sha512-2G1ycU28Nh7OHT9rkXRLpCDP30MKH1dXJORZuBhtEhEW7pKwgPi77ImqlCWinouyE1PNepIOGZBOrE84DG7LyQ==", - "optional": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/cytoscape-cose-bilkent": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", @@ -8755,15 +8887,6 @@ "node": ">=12" } }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "optional": true, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-shape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", @@ -9982,6 +10105,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", "dev": true, + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -10052,6 +10176,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "peer": true, "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -10488,6 +10613,7 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, + "peer": true, "dependencies": { "array-includes": "^3.1.7", "array.prototype.findlastindex": "^1.2.3", @@ -14243,6 +14369,7 @@ "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz", "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==", "dev": true, + "peer": true, "dependencies": { "copy-anything": "^2.0.1", "parse-node-version": "^1.0.1", @@ -14668,6 +14795,7 @@ "version": "9.1.6", "resolved": "https://registry.npmjs.org/marked/-/marked-9.1.6.tgz", "integrity": "sha512-jcByLnIFkd5gSXZmjNvS1TlmRhCXZjIzHYlaGkPlLIekG55JDR2Z4va9tZwCiP+/RDERiNhMOFu01xd6O5ct1Q==", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -18182,6 +18310,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -19097,6 +19226,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", "dev": true, + "peer": true, "engines": { "node": ">=12" }, @@ -19373,6 +19503,7 @@ "url": "https://github.com/sponsors/ai" } ], + "peer": true, "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.0.0", @@ -19511,6 +19642,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.0.tgz", "integrity": "sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g==", "dev": true, + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -20521,6 +20653,7 @@ "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -20602,6 +20735,7 @@ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz", "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==", "dev": true, + "peer": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -20728,6 +20862,7 @@ "integrity": "sha512-phCkJ6pjDi9ANdhuF5ElS10GGdAKY6R1Pvt9lT3SFhOwM4T7QZE7MLpBDbNruUx/Q3gFD92/UOFringGipRqZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@semantic-release/commit-analyzer": "^13.0.0-beta.1", "@semantic-release/error": "^4.0.0", @@ -23573,6 +23708,7 @@ "dev": true, "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -25057,6 +25193,7 @@ "resolved": "https://registry.npmjs.org/terser/-/terser-5.29.1.tgz", "integrity": "sha512-lZQ/fyaIGxsbGxApKmoPTODIzELy3++mXhS5hOqaAWZjQtpq/hFHAc+rm29NND1rYRxRWKcjuARNwULNXa5RtQ==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.8.2", @@ -25109,6 +25246,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -25716,6 +25854,7 @@ "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -26123,6 +26262,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz", "integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==", "dev": true, + "peer": true, "dependencies": { "esbuild": "^0.19.3", "postcss": "^8.4.35", @@ -26587,6 +26727,15 @@ "node": ">=0.10.0" } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.1.tgz", + "integrity": "sha512-kdjOSJ2lLIn7r1rtrMbbNCHjyMPfRnowdKjBQ+mGq6NAW5QY2bEZC/khaC5OR8svbbjvLEaIXkOq45e2X9BIbQ==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -26637,6 +26786,7 @@ "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.90.3.tgz", "integrity": "sha512-h6uDYlWCctQRuXBs1oYpVe6sFcWedl0dpcVaTf/YF67J9bKvwJajFulMVSYKHrksMB3I/pIagRzDxwxkebuzKA==", "dev": true, + "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", @@ -26712,6 +26862,7 @@ "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.15.1.tgz", "integrity": "sha512-5hbAst3h3C3L8w6W4P96L5vaV0PxSmJhxZvWKYIdgxOQm8pNZ5dEOmmSLBVpP85ReeyRt6AS1QJNyo/oFFPeVA==", "dev": true, + "peer": true, "dependencies": { "@types/bonjour": "^3.5.9", "@types/connect-history-api-fallback": "^1.3.5", @@ -26868,6 +27019,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -26889,6 +27041,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -27657,16 +27810,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zone.js": { "version": "0.14.6", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.6.tgz", - "integrity": "sha512-vyRNFqofdaHVdWAy7v3Bzmn84a1JHWSjpuTZROT/uYn8I3p2cmo7Ro9twFmYRQDPhiYOV7QLk0hhY4JJQVqS6Q==" + "integrity": "sha512-vyRNFqofdaHVdWAy7v3Bzmn84a1JHWSjpuTZROT/uYn8I3p2cmo7Ro9twFmYRQDPhiYOV7QLk0hhY4JJQVqS6Q==", + "peer": true }, "packages/cli": { "name": "@moaw/cli", - "version": "1.5.1", + "version": "1.5.2", "license": "MIT", "dependencies": { + "@github/copilot-sdk": "^0.1.16", "asciidoctor": "^3.0.2", "browser-sync": "^3.0.2", "debug": "^4.3.4", diff --git a/packages/cli/package.json b/packages/cli/package.json index 5058be15..490f760d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -38,6 +38,7 @@ "xo": "^0.58.0" }, "dependencies": { + "@github/copilot-sdk": "^0.1.16", "asciidoctor": "^3.0.2", "browser-sync": "^3.0.2", "debug": "^4.3.4", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index fe4f0c24..b34ef227 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -2,7 +2,7 @@ import process from 'node:process'; import debug from 'debug'; import updateNotifier, { type Package } from 'update-notifier'; import minimist from 'minimist'; -import { convert, createNew, link, serve, build } from './commands/index.js'; +import { convert, createNew, link, serve, build, translate } from './commands/index.js'; import { getPackageJson } from './util.js'; const help = `Usage: moaw [options] @@ -21,6 +21,9 @@ Commands: l, link [] Get link to target file (default: workshop.md) -r, --repo Set GitHub repo instead of fetching it from git -b, --branch Set branch name (default: current branch) + t, translate [] Translate workshop to different languages + -l, --languages Comma-separated list of target languages + -m, --model Copilot model to use (default: gpt-5.2) General options: -v, --version Show version @@ -29,7 +32,7 @@ General options: export async function run(args: string[]) { const options = minimist(args, { - string: ['host', 'attr', 'dest', 'repo', 'branch'], + string: ['host', 'attr', 'dest', 'repo', 'branch', 'languages', 'model'], boolean: ['verbose', 'version', 'help', 'open'], alias: { v: 'version', @@ -39,7 +42,9 @@ export async function run(args: string[]) { a: 'attr', d: 'dest', r: 'repo', - b: 'branch' + b: 'branch', + l: 'languages', + m: 'model' } }); @@ -112,6 +117,17 @@ export async function run(args: string[]) { break; } + case 't': + case 'translate': { + await translate({ + file: parameters[0], + languages: options.languages as string, + model: options.model as string, + verbose: Boolean(options.verbose) + }); + break; + } + default: { if (command) { console.error(`Unknown command: ${command}`); diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index b6891449..45eb4fc6 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -3,3 +3,4 @@ export * from './build.js'; export * from './serve.js'; export * from './convert.js'; export * from './link.js'; +export * from './translate.js'; diff --git a/packages/cli/src/commands/translate.ts b/packages/cli/src/commands/translate.ts new file mode 100644 index 00000000..1689b972 --- /dev/null +++ b/packages/cli/src/commands/translate.ts @@ -0,0 +1,96 @@ +import process from "node:process"; +import createDebug from "debug"; +import { CopilotClient } from "@github/copilot-sdk"; +import { pathExists } from "../util.js"; +import { defaultWorkshopFile } from "../constants.js"; + +const debug = createDebug("translate"); +const translationsFolder = "translations"; + +const translatePrompt = (file: string, languages: string) => `## Role +You are an expert translator for technical documents. Your task is to translate the provided workshop content into the specified target language while preserving the original formatting, code snippets, and technical terminology. Ensure that the translated content is complete, clear, accurate, and maintains the instructional tone of the original text. + +## Task +1. Translate the workshop file \`${file}\` in languages: \`${languages}\`. +2. Create one translated file per language. Each new translated file must be created as \`${translationsFolder}/..md\`. +3. Return the list of created files without any additional explanation. + +## Instructions +- Preserve ALL markdown, HTML and frontmatter formatting, including headings, lists, links, and code blocks. +- Update the paths in any relative links or image references to point to the correct locations in the translated file. +- Keep technical terms and code snippets unchanged. +- Keep frontmatter metadata properties names unchanged. Only translate titles and descriptions in the frontmatter. +`; + +export type TranslateOptions = { + file?: string; + languages?: string; + model?: string; + verbose?: boolean; +}; + +export async function translate(options: TranslateOptions = {}): Promise { + try { + options.file = options.file?.trim() || defaultWorkshopFile; + options.model = options.model?.trim() || "gpt-5.2"; + debug("Options %o", options); + + const { file, model, languages } = options; + if (!languages) { + throw new Error("No target languages specified. Use the --languages option to specify target languages."); + } + + if (!(await pathExists(file))) { + throw new Error(`File not found: ${file}`); + } + + const client = new CopilotClient(); + try { + await client.start(); + const session = await client.createSession({ model }); + const auth = await client.getAuthStatus(); + if (!auth.isAuthenticated) { + throw new Error( + 'GitHub Copilot CLI is not authenticated.\nPlease run "copilot" and use "/login" to authenticate.' + ); + } + debug("Connected to GitHub Copilot CLI"); + console.info(`Started translation agent for file "${file}" to languages: ${languages}...`); + + session.on((event) => { + if (event.type === "assistant.message") { + debug(`Copilot: ${event.data.content}`); + } + }); + + const response = await session.sendAndWait( + { + prompt: translatePrompt(file, languages), + attachments: [{ type: "file", path: file }], + }, + 15 * 60 * 1000 // 15 minutes timeout + ); + console.info(`Translation agent task completed:`); + console.log(response?.data.content); + + await session.destroy(); + debug("Copilot CLI session destroyed"); + } catch (error: any) { + if (error.message.includes("ENOENT")) { + throw new Error( + `GitHub Copilot CLI is not installed.\nSee https://docs.github.com/copilot/how-tos/set-up/install-copilot-cli for installation instructions.` + ); + } + throw error; + } finally { + await client.stop(); + debug("Copilot CLI client stopped"); + } + // Temp workaround to avoid process hanging + process.exit(); + } catch (error: unknown) { + const error_ = error as Error; + console.error(error_.message); + process.exitCode = 1; + } +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index e32604c9..776dad60 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -10,7 +10,8 @@ "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "skipLibCheck": true }, "include": ["./src"] } From 1978e53eb44f068f3dfd76cba154daa8231eff46 Mon Sep 17 00:00:00 2001 From: sinedied Date: Fri, 23 Jan 2026 14:51:18 +0100 Subject: [PATCH 3/5] chore: lint fix and add timings --- packages/cli/src/commands/translate.ts | 47 +++++++++++++++----------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/packages/cli/src/commands/translate.ts b/packages/cli/src/commands/translate.ts index 1689b972..4d469c98 100644 --- a/packages/cli/src/commands/translate.ts +++ b/packages/cli/src/commands/translate.ts @@ -1,11 +1,11 @@ -import process from "node:process"; -import createDebug from "debug"; -import { CopilotClient } from "@github/copilot-sdk"; -import { pathExists } from "../util.js"; -import { defaultWorkshopFile } from "../constants.js"; +import process from 'node:process'; +import createDebug from 'debug'; +import { CopilotClient } from '@github/copilot-sdk'; +import { pathExists } from '../util.js'; +import { defaultWorkshopFile } from '../constants.js'; -const debug = createDebug("translate"); -const translationsFolder = "translations"; +const debug = createDebug('translate'); +const translationsFolder = 'translations'; const translatePrompt = (file: string, languages: string) => `## Role You are an expert translator for technical documents. Your task is to translate the provided workshop content into the specified target language while preserving the original formatting, code snippets, and technical terminology. Ensure that the translated content is complete, clear, accurate, and maintains the instructional tone of the original text. @@ -32,18 +32,19 @@ export type TranslateOptions = { export async function translate(options: TranslateOptions = {}): Promise { try { options.file = options.file?.trim() || defaultWorkshopFile; - options.model = options.model?.trim() || "gpt-5.2"; - debug("Options %o", options); + options.model = options.model?.trim() || 'gpt-5.2'; + debug('Options %o', options); const { file, model, languages } = options; if (!languages) { - throw new Error("No target languages specified. Use the --languages option to specify target languages."); + throw new Error('No target languages specified. Use the --languages option to specify target languages.'); } if (!(await pathExists(file))) { throw new Error(`File not found: ${file}`); } + const startTime = Date.now(); const client = new CopilotClient(); try { await client.start(); @@ -54,11 +55,12 @@ export async function translate(options: TranslateOptions = {}): Promise { 'GitHub Copilot CLI is not authenticated.\nPlease run "copilot" and use "/login" to authenticate.' ); } - debug("Connected to GitHub Copilot CLI"); + + debug('Connected to GitHub Copilot CLI'); console.info(`Started translation agent for file "${file}" to languages: ${languages}...`); session.on((event) => { - if (event.type === "assistant.message") { + if (event.type === 'assistant.message') { debug(`Copilot: ${event.data.content}`); } }); @@ -66,27 +68,34 @@ export async function translate(options: TranslateOptions = {}): Promise { const response = await session.sendAndWait( { prompt: translatePrompt(file, languages), - attachments: [{ type: "file", path: file }], + attachments: [{ type: 'file', path: file }] }, 15 * 60 * 1000 // 15 minutes timeout ); - console.info(`Translation agent task completed:`); + + const elapsed = (Date.now() - startTime) / 1000; + const timeStr = elapsed > 60 ? `${(elapsed / 60).toFixed(1)}m` : `${elapsed.toFixed(1)}s`; + console.info(`Translation agent task completed in ${timeStr}:`); console.log(response?.data.content); await session.destroy(); - debug("Copilot CLI session destroyed"); - } catch (error: any) { - if (error.message.includes("ENOENT")) { + debug('Copilot CLI session destroyed'); + } catch (error: unknown) { + const error_ = error as Error; + if (error_.message?.includes('ENOENT')) { throw new Error( `GitHub Copilot CLI is not installed.\nSee https://docs.github.com/copilot/how-tos/set-up/install-copilot-cli for installation instructions.` ); } - throw error; + + throw error_; } finally { await client.stop(); - debug("Copilot CLI client stopped"); + debug('Copilot CLI client stopped'); } + // Temp workaround to avoid process hanging + // eslint-disable-next-line unicorn/no-process-exit process.exit(); } catch (error: unknown) { const error_ = error as Error; From e64f26b0ecb763da762a59ac8da2a2f3d7b62df0 Mon Sep 17 00:00:00 2001 From: sinedied Date: Fri, 23 Jan 2026 15:02:21 +0100 Subject: [PATCH 4/5] chore: bump timeout to 30m --- packages/cli/src/commands/translate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/commands/translate.ts b/packages/cli/src/commands/translate.ts index 4d469c98..d4ae2882 100644 --- a/packages/cli/src/commands/translate.ts +++ b/packages/cli/src/commands/translate.ts @@ -70,7 +70,7 @@ export async function translate(options: TranslateOptions = {}): Promise { prompt: translatePrompt(file, languages), attachments: [{ type: 'file', path: file }] }, - 15 * 60 * 1000 // 15 minutes timeout + 30 * 60 * 1000 // 30 minutes timeout ); const elapsed = (Date.now() - startTime) / 1000; From f66f0a8df3478425d24a4685f6e6fd08b55c8312 Mon Sep 17 00:00:00 2001 From: sinedied Date: Fri, 23 Jan 2026 15:07:11 +0100 Subject: [PATCH 5/5] docs(cli): update readme --- packages/cli/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/cli/README.md b/packages/cli/README.md index f863ea05..2aa5174a 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -32,6 +32,9 @@ Commands: l, link [] Get link to target file (default: workshop.md) -r, --repo Set GitHub repo instead of fetching it from git -b, --branch Set branch name (default: current branch) + t, translate [] Translate workshop to different languages + -l, --languages Comma-separated list of target languages + -m, --model Copilot model to use (default: gpt-5.2) General options: -v, --version Show version @@ -64,3 +67,19 @@ As MOAW requires specific [frontmatter metadata in the Markdown file](../../temp ``` Note that not all AsciiDoc features are supported, and fall back to HTML generation will be used in that case. You can use the `--verbose` option to see if any unsupported feature is used. + +#### AI-Generated translations + +The `translate` command allows you to translate your workshop to different languages using GitHub Copilot CLI. + +GitHub Copilot CLI must be installed and authenticated before you can use this command (see [installation guide](https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli)). + +You can then use AI models from Copilot to translate your workshop. For example, to translate to Spanish and French: + +```bash +moaw translate workshop.md -l es,fr +``` + +> [!NOTE] +> Translation may take some time depending on the size of your workshop and the number of target languages. +> There's a hard timeout of 30 minutes per translation request, so for very large workshops you may need to separate the translation task into multiple commands.