diff --git a/package-lock.json b/package-lock.json index 505fa887..fc0e82d3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3357,6 +3357,14 @@ "node": ">=10.0.0" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "engines": { + "node": ">=14" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -3411,6 +3419,108 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@rollup/plugin-commonjs": { + "version": "24.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.1.0.tgz", + "integrity": "sha512-eSL45hjhCWI0jCCXcNtLVqM5N1JlBGvlFfY0m6oOYnLCJ6N0qEXoZql4sY2MOUArzhH4SA/qBpTxvvZp2Sc+DQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^8.0.3", + "is-reference": "1.2.1", + "magic-string": "^0.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.5.tgz", + "integrity": "sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace/node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz", + "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -3676,6 +3786,25 @@ "@types/send": "*" } }, + "node_modules/@types/fs-extra": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.5.tgz", + "integrity": "sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", + "dev": true, + "dependencies": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, "node_modules/@types/hapi__catbox": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/@types/hapi__catbox/-/hapi__catbox-10.2.6.tgz", @@ -3791,6 +3920,12 @@ "resolved": "https://registry.npmjs.org/@types/mime-db/-/mime-db-1.43.5.tgz", "integrity": "sha512-/bfTiIUTNPUBnwnYvUxXAre5MhD88jgagLEQiQtIASjU+bwxd8kS/ASDA4a8ufd8m0Lheu6eeMJHEUpLHoJ28A==" }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "dev": true + }, "node_modules/@types/mysql": { "version": "2.15.22", "resolved": "https://registry.npmjs.org/@types/mysql/-/mysql-2.15.22.tgz", @@ -3844,6 +3979,12 @@ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==" }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true + }, "node_modules/@types/send": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", @@ -4384,6 +4525,15 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -4405,6 +4555,11 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==" }, + "node_modules/async-lock": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", + "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -4764,6 +4919,18 @@ "node": ">=10.0.0" } }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -4994,6 +5161,11 @@ "node": ">= 10.0" } }, + "node_modules/clean-git-ref": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", + "integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==" + }, "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", @@ -5345,6 +5517,17 @@ "node": ">=10.0.0" } }, + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "bin": { + "crc32": "bin/crc32.njs" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/cross-fetch": { "version": "3.1.8", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.8.tgz", @@ -5527,6 +5710,15 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-require-extensions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", @@ -5605,6 +5797,23 @@ "node": ">=0.3.1" } }, + "node_modules/diff3": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz", + "integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -6039,6 +6248,12 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -6177,6 +6392,22 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -6479,6 +6710,20 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "node_modules/fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -6667,6 +6912,10 @@ "omggif": "^1.0.10" } }, + "node_modules/git": { + "resolved": "packages/git", + "link": true + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -6751,6 +7000,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globby": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.1.tgz", + "integrity": "sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==", + "dev": true, + "dependencies": { + "@types/glob": "^7.1.1", + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.0.3", + "glob": "^7.1.3", + "ignore": "^5.1.1", + "merge2": "^1.2.3", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/globby/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6972,7 +7260,6 @@ "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "dev": true, "engines": { "node": ">= 4" } @@ -7136,6 +7423,21 @@ "node": ">=8" } }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -7204,6 +7506,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7242,6 +7550,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -7331,6 +7648,43 @@ "whatwg-fetch": "^3.4.1" } }, + "node_modules/isomorphic-git": { + "version": "1.25.10", + "resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.25.10.tgz", + "integrity": "sha512-IxGiaKBwAdcgBXwIcxJU6rHLk+NrzYaaPKXXQffcA0GW3IUrQXdUPDXDo+hkGVcYruuz/7JlGBiuaeTCgIgivQ==", + "dependencies": { + "async-lock": "^1.4.1", + "clean-git-ref": "^2.0.1", + "crc-32": "^1.2.0", + "diff3": "0.0.3", + "ignore": "^5.1.4", + "minimisted": "^2.0.0", + "pako": "^1.0.10", + "pify": "^4.0.1", + "readable-stream": "^3.4.0", + "sha.js": "^2.4.9", + "simple-get": "^4.0.1" + }, + "bin": { + "isogit": "cli.cjs" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/isomorphic-git/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/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -7631,6 +7985,15 @@ "node": ">=6" } }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dev": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -7955,6 +8318,18 @@ "yallist": "^3.0.2" } }, + "node_modules/magic-string": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz", + "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.13" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -8001,6 +8376,15 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -8088,6 +8472,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minimisted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz", + "integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==", + "dependencies": { + "minimist": "^1.2.5" + } + }, "node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -8989,6 +9381,11 @@ "node": ">= 0.8" } }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -9025,6 +9422,15 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pathval": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", @@ -9100,6 +9506,14 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "engines": { + "node": ">=6" + } + }, "node_modules/pixelmatch": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", @@ -9712,6 +10126,53 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rollup": { + "version": "3.29.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", + "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-copy": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-copy/-/rollup-plugin-copy-3.5.0.tgz", + "integrity": "sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==", + "dev": true, + "dependencies": { + "@types/fs-extra": "^8.0.1", + "colorette": "^1.1.0", + "fs-extra": "^8.1.0", + "globby": "10.0.1", + "is-plain-object": "^3.0.0" + }, + "engines": { + "node": ">=8.3" + } + }, + "node_modules/rollup-plugin-copy/node_modules/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true + }, + "node_modules/rollup-plugin-copy/node_modules/is-plain-object": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-3.0.1.tgz", + "integrity": "sha512-Xnpx182SBMrr/aBik8y+GuR4U1L9FqMSojwDQwPMmxyC6bvEqly9UBCxhauBF5vNh2gwWJNX6oDV7O+OM4z34g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -9910,6 +10371,18 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/sha.js": { + "version": "2.4.11", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "dependencies": { + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + }, + "bin": { + "sha.js": "bin.js" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -10090,6 +10563,15 @@ "node": ">=8" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/socket.io": { "version": "4.7.5", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz", @@ -10841,6 +11323,15 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -11556,6 +12047,65 @@ "typescript": "^5.1.6" } }, + "packages/git": { + "version": "1.0.0", + "license": "AGPL-3.0-only", + "dependencies": { + "@pkgjs/parseargs": "^0.11.0", + "buffer": "^6.0.3", + "isomorphic-git": "^1.25.10" + }, + "devDependencies": { + "@rollup/plugin-commonjs": "^24.1.0", + "@rollup/plugin-node-resolve": "^15.0.2", + "@rollup/plugin-replace": "^5.0.2", + "mocha": "^10.2.0", + "rollup": "^3.21.4", + "rollup-plugin-copy": "^3.4.0" + } + }, + "packages/git/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "packages/git/node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "packages/phoenix": { "name": "@heyputer/phoenix", "version": "0.0.0", @@ -11588,139 +12138,6 @@ "node-pty": "^1.0.0" } }, - "packages/phoenix/node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "packages/phoenix/node_modules/@rollup/plugin-commonjs": { - "version": "24.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.27.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "packages/phoenix/node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "packages/phoenix/node_modules/@rollup/plugin-commonjs/node_modules/glob": { - "version": "8.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/phoenix/node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { - "version": "5.1.6", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "packages/phoenix/node_modules/@rollup/plugin-node-resolve": { - "version": "15.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "packages/phoenix/node_modules/@rollup/plugin-replace": { - "version": "5.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.27.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "packages/phoenix/node_modules/@rollup/pluginutils": { - "version": "5.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, "packages/phoenix/node_modules/@sinonjs/fake-timers": { "version": "11.2.2", "license": "BSD-3-Clause", @@ -11728,33 +12145,6 @@ "@sinonjs/commons": "^3.0.0" } }, - "packages/phoenix/node_modules/@types/fs-extra": { - "version": "8.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "packages/phoenix/node_modules/@types/glob": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "packages/phoenix/node_modules/@types/minimatch": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "packages/phoenix/node_modules/@types/resolve": { - "version": "1.20.2", - "dev": true, - "license": "MIT" - }, "packages/phoenix/node_modules/argle": { "version": "1.1.2", "license": "MIT", @@ -11763,34 +12153,6 @@ "lodash.isnumber": "^3.0.3" } }, - "packages/phoenix/node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "packages/phoenix/node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "packages/phoenix/node_modules/builtin-modules": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "packages/phoenix/node_modules/capture-console": { "version": "1.0.2", "license": "MIT", @@ -11818,11 +12180,6 @@ "node": ">=0.8" } }, - "packages/phoenix/node_modules/colorette": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, "packages/phoenix/node_modules/columnify": { "version": "1.6.0", "license": "MIT", @@ -11834,14 +12191,6 @@ "node": ">=8.0.0" } }, - "packages/phoenix/node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "packages/phoenix/node_modules/defaults": { "version": "1.0.4", "license": "MIT", @@ -11852,91 +12201,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/phoenix/node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/phoenix/node_modules/estree-walker": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "packages/phoenix/node_modules/fast-glob": { - "version": "3.2.12", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "packages/phoenix/node_modules/fs-extra": { - "version": "8.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, "packages/phoenix/node_modules/fs-mode-to-string": { "version": "0.0.2", "license": "MIT" }, - "packages/phoenix/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/phoenix/node_modules/globby": { - "version": "10.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "packages/phoenix/node_modules/has-flag": { "version": "4.0.0", "license": "MIT", @@ -11944,101 +12212,16 @@ "node": ">=8" } }, - "packages/phoenix/node_modules/is-builtin-module": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/phoenix/node_modules/is-module": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "packages/phoenix/node_modules/is-plain-object": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "packages/phoenix/node_modules/is-reference": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, "packages/phoenix/node_modules/json-query": { "version": "2.2.2", "engines": { "node": "*" } }, - "packages/phoenix/node_modules/jsonfile": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, "packages/phoenix/node_modules/lodash.isfunction": { "version": "3.0.9", "license": "MIT" }, - "packages/phoenix/node_modules/magic-string": { - "version": "0.27.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, - "engines": { - "node": ">=12" - } - }, - "packages/phoenix/node_modules/merge2": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "packages/phoenix/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "packages/phoenix/node_modules/path-browserify": { - "version": "1.0.1", - "license": "MIT" - }, - "packages/phoenix/node_modules/path-type": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "packages/phoenix/node_modules/randomstring": { "version": "1.3.0", "license": "MIT", @@ -12056,36 +12239,6 @@ "version": "2.0.3", "license": "MIT" }, - "packages/phoenix/node_modules/rollup": { - "version": "3.21.4", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "packages/phoenix/node_modules/rollup-plugin-copy": { - "version": "3.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/fs-extra": "^8.0.1", - "colorette": "^1.1.0", - "fs-extra": "^8.1.0", - "globby": "10.0.1", - "is-plain-object": "^3.0.0" - }, - "engines": { - "node": ">=8.3" - } - }, "packages/phoenix/node_modules/sinon": { "version": "17.0.1", "license": "BSD-3-Clause", @@ -12119,22 +12272,6 @@ "node": ">=8" } }, - "packages/phoenix/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "packages/phoenix/node_modules/universalify": { - "version": "0.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "packages/phoenix/node_modules/wcwidth": { "version": "1.0.1", "license": "MIT", @@ -12193,130 +12330,6 @@ "rollup-plugin-copy": "^3.4.0" } }, - "packages/terminal/node_modules/@rollup/plugin-commonjs": { - "version": "24.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^8.0.3", - "is-reference": "1.2.1", - "magic-string": "^0.27.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "packages/terminal/node_modules/@rollup/plugin-node-resolve": { - "version": "15.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "packages/terminal/node_modules/@rollup/plugin-replace": { - "version": "5.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.27.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "packages/terminal/node_modules/@rollup/pluginutils": { - "version": "5.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "packages/terminal/node_modules/@types/fs-extra": { - "version": "8.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "packages/terminal/node_modules/@types/glob": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "packages/terminal/node_modules/@types/minimatch": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "packages/terminal/node_modules/@types/resolve": { - "version": "1.20.2", - "dev": true, - "license": "MIT" - }, - "packages/terminal/node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "packages/terminal/node_modules/async": { "version": "2.6.4", "dev": true, @@ -12325,22 +12338,6 @@ "lodash": "^4.17.14" } }, - "packages/terminal/node_modules/builtin-modules": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/terminal/node_modules/colorette": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, "packages/terminal/node_modules/corser": { "version": "2.0.1", "dev": true, @@ -12349,120 +12346,11 @@ "node": ">= 0.4.0" } }, - "packages/terminal/node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "packages/terminal/node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/terminal/node_modules/estree-walker": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, "packages/terminal/node_modules/eventemitter3": { "version": "4.0.7", "dev": true, "license": "MIT" }, - "packages/terminal/node_modules/fast-glob": { - "version": "3.2.12", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "packages/terminal/node_modules/fs-extra": { - "version": "8.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "packages/terminal/node_modules/globby": { - "version": "10.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/glob": "^7.1.1", - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "packages/terminal/node_modules/globby/node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "packages/terminal/node_modules/globby/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "packages/terminal/node_modules/globby/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "packages/terminal/node_modules/html-encoding-sniffer": { "version": "3.0.0", "dev": true, @@ -12524,68 +12412,6 @@ "node": ">=0.10.0" } }, - "packages/terminal/node_modules/is-builtin-module": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "packages/terminal/node_modules/is-module": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "packages/terminal/node_modules/is-plain-object": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "packages/terminal/node_modules/is-reference": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "packages/terminal/node_modules/jsonfile": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "packages/terminal/node_modules/magic-string": { - "version": "0.27.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.13" - }, - "engines": { - "node": ">=12" - } - }, - "packages/terminal/node_modules/merge2": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, "packages/terminal/node_modules/ms": { "version": "2.1.3", "dev": true, @@ -12599,14 +12425,6 @@ "opener": "bin/opener-bin.js" } }, - "packages/terminal/node_modules/path-type": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "packages/terminal/node_modules/portfinder": { "version": "1.0.32", "dev": true, @@ -12647,49 +12465,11 @@ "dev": true, "license": "MIT" }, - "packages/terminal/node_modules/rollup": { - "version": "3.23.0", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "packages/terminal/node_modules/rollup-plugin-copy": { - "version": "3.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/fs-extra": "^8.0.1", - "colorette": "^1.1.0", - "fs-extra": "^8.1.0", - "globby": "10.0.1", - "is-plain-object": "^3.0.0" - }, - "engines": { - "node": ">=8.3" - } - }, "packages/terminal/node_modules/secure-compare": { "version": "3.0.1", "dev": true, "license": "MIT" }, - "packages/terminal/node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "packages/terminal/node_modules/union": { "version": "0.5.0", "dev": true, @@ -12700,14 +12480,6 @@ "node": ">= 0.8.0" } }, - "packages/terminal/node_modules/universalify": { - "version": "0.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, "packages/terminal/node_modules/url-join": { "version": "4.0.1", "dev": true, diff --git a/packages/backend/src/SelfHostedModule.js b/packages/backend/src/SelfHostedModule.js index adc375d0..268ed094 100644 --- a/packages/backend/src/SelfHostedModule.js +++ b/packages/backend/src/SelfHostedModule.js @@ -40,6 +40,15 @@ class SelfHostedModule extends AdvancedBase { PUTER_JS_URL: ({ global_config: config }) => config.origin + '/sdk/puter.dev.js', } }, + { + name: 'git:rollup-watch', + directory: 'packages/git', + command: 'npx', + args: ['rollup', '-c', 'rollup.config.js', '--watch'], + env: { + PUTER_JS_URL: ({ global_config: config }) => config.origin + '/sdk/puter.dev.js', + } + }, ], }); @@ -58,6 +67,10 @@ class SelfHostedModule extends AdvancedBase { prefix: '/builtin/phoenix', path: path_.resolve(__dirname, '../../../packages/phoenix/dist'), }, + { + prefix: '/builtin/git', + path: path_.resolve(__dirname, '../../../packages/git/dist'), + }, ], }); } diff --git a/packages/backend/src/services/database/SqliteDatabaseAccessService.js b/packages/backend/src/services/database/SqliteDatabaseAccessService.js index c39e4eff..b1e76eb3 100644 --- a/packages/backend/src/services/database/SqliteDatabaseAccessService.js +++ b/packages/backend/src/services/database/SqliteDatabaseAccessService.js @@ -42,7 +42,7 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService { this.db = new Database(this.config.path); // Database upgrade logic - const TARGET_VERSION = 7; + const TARGET_VERSION = 8; if ( do_setup ) { this.log.noticeme(`SETUP: creating database at ${this.config.path}`); @@ -56,6 +56,7 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService { '0007_sessions.sql', '0008_otp.sql', '0009_app-prefix-fix.sql', + '0010_add-git-app.sql', ].map(p => path_.join(__dirname, 'sqlite_setup', p)); const fs = require('fs'); for ( const filename of sql_files ) { @@ -100,6 +101,10 @@ class SqliteDatabaseAccessService extends BaseDatabaseAccessService { upgrade_files.push('0009_app-prefix-fix.sql'); } + if ( user_version <= 7 ) { + upgrade_files.push('0010_add-git-app.sql'); + } + if ( upgrade_files.length > 0 ) { this.log.noticeme(`Database out of date: ${this.config.path}`); this.log.noticeme(`UPGRADING DATABASE: ${user_version} -> ${TARGET_VERSION}`); diff --git a/packages/backend/src/services/database/sqlite_setup/0010_add-git-app.sql b/packages/backend/src/services/database/sqlite_setup/0010_add-git-app.sql new file mode 100644 index 00000000..d18583be --- /dev/null +++ b/packages/backend/src/services/database/sqlite_setup/0010_add-git-app.sql @@ -0,0 +1,4 @@ +INSERT INTO `apps` + (`uid`, `owner_user_id`, `icon`, `name`, `title`, `description`, `godmode`, `background`, `maximize_on_start`, `index_url`, `approved_for_listing`, `approved_for_opening_items`, `approved_for_incentive_program`, `timestamp`, `last_review`, `tags`, `app_owner`) +VALUES + ('app-e3ac5486-da8c-42ad-8377-8728086e0980', 1, 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5MnB0IiBoZWlnaHQ9IjkycHQiIHZpZXdCb3g9IjAgMCA5MiA5MiI+PGRlZnM+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBkPSJNMCAuMTEzaDkxLjg4N1Y5MkgwWm0wIDAiLz48L2NsaXBQYXRoPjwvZGVmcz48ZyBjbGlwLXBhdGg9InVybCgjYSkiPjxwYXRoIHN0eWxlPSJzdHJva2U6bm9uZTtmaWxsLXJ1bGU6bm9uemVybztmaWxsOiNmMDNjMmU7ZmlsbC1vcGFjaXR5OjEiIGQ9Ik05MC4xNTYgNDEuOTY1IDUwLjAzNiAxLjg0OGE1LjkxOCA1LjkxOCAwIDAgMC04LjM3MiAwbC04LjMyOCA4LjMzMiAxMC41NjYgMTAuNTY2YTcuMDMgNy4wMyAwIDAgMSA3LjIzIDEuNjg0IDcuMDM0IDcuMDM0IDAgMCAxIDEuNjY5IDcuMjc3bDEwLjE4NyAxMC4xODRhNy4wMjggNy4wMjggMCAwIDEgNy4yNzggMS42NzIgNy4wNCA3LjA0IDAgMCAxIDAgOS45NTcgNy4wNSA3LjA1IDAgMCAxLTkuOTY1IDAgNy4wNDQgNy4wNDQgMCAwIDEtMS41MjgtNy42NmwtOS41LTkuNDk3VjU5LjM2YTcuMDQgNy4wNCAwIDAgMSAxLjg2IDExLjI5IDcuMDQgNy4wNCAwIDAgMS05Ljk1NyAwIDcuMDQgNy4wNCAwIDAgMSAwLTkuOTU4IDcuMDYgNy4wNiAwIDAgMSAyLjMwNC0xLjUzOVYzMy45MjZhNy4wNDkgNy4wNDkgMCAwIDEtMy44Mi05LjIzNEwyOS4yNDIgMTQuMjcyIDEuNzMgNDEuNzc3YTUuOTI1IDUuOTI1IDAgMCAwIDAgOC4zNzFMNDEuODUyIDkwLjI3YTUuOTI1IDUuOTI1IDAgMCAwIDguMzcgMGwzOS45MzQtMzkuOTM0YTUuOTI1IDUuOTI1IDAgMCAwIDAtOC4zNzEiLz48L2c+PC9zdmc+', 'git', 'Git', 'Puter Git client', 0, 1, 0, 'https://builtins.namespaces.puter.com/git', 1, 0, 0, '2024-05-15 10:33:00', NULL, 'productivity', NULL); \ No newline at end of file diff --git a/packages/git/.gitignore b/packages/git/.gitignore new file mode 100644 index 00000000..04c01ba7 --- /dev/null +++ b/packages/git/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +dist/ \ No newline at end of file diff --git a/packages/git/LICENSE b/packages/git/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/packages/git/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/packages/git/README.md b/packages/git/README.md new file mode 100644 index 00000000..bd2a9290 --- /dev/null +++ b/packages/git/README.md @@ -0,0 +1,3 @@ +# Puter Git Client + +This is a git client running on the Puter filesystem, using [isomorphic-git](https://isomorphic-git.org). \ No newline at end of file diff --git a/packages/git/assets/index.html b/packages/git/assets/index.html new file mode 100644 index 00000000..185253db --- /dev/null +++ b/packages/git/assets/index.html @@ -0,0 +1,18 @@ + + + + + Git client + + + + + + + This is a cli app so you shouldn't see this + + \ No newline at end of file diff --git a/packages/git/config/dev.js b/packages/git/config/dev.js new file mode 100644 index 00000000..568e1875 --- /dev/null +++ b/packages/git/config/dev.js @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Phoenix Shell. + * + * Phoenix Shell is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +globalThis.__CONFIG__ = { + sdk_url: 'http://puter.localhost:4100/sdk/puter.js', +}; diff --git a/packages/git/config/release.js b/packages/git/config/release.js new file mode 100644 index 00000000..094a08d4 --- /dev/null +++ b/packages/git/config/release.js @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Phoenix Shell. + * + * Phoenix Shell is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +globalThis.__CONFIG__ = { + sdk_url: 'https://puter.com/puter.js/v2', +}; diff --git a/packages/git/package.json b/packages/git/package.json new file mode 100644 index 00000000..30cac607 --- /dev/null +++ b/packages/git/package.json @@ -0,0 +1,25 @@ +{ + "name": "git", + "version": "1.0.0", + "description": "Git Client for Puter", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Puter Technologies Inc.", + "license": "AGPL-3.0-only", + "type": "module", + "devDependencies": { + "@rollup/plugin-commonjs": "^24.1.0", + "@rollup/plugin-node-resolve": "^15.0.2", + "@rollup/plugin-replace": "^5.0.2", + "mocha": "^10.2.0", + "rollup": "^3.21.4", + "rollup-plugin-copy": "^3.4.0" + }, + "dependencies": { + "@pkgjs/parseargs": "^0.11.0", + "buffer": "^6.0.3", + "isomorphic-git": "^1.25.10" + } +} diff --git a/packages/git/rollup.config.js b/packages/git/rollup.config.js new file mode 100644 index 00000000..9c0c6732 --- /dev/null +++ b/packages/git/rollup.config.js @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Phoenix Shell. + * + * Phoenix Shell is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { nodeResolve } from '@rollup/plugin-node-resolve' +import commonjs from '@rollup/plugin-commonjs'; +import copy from 'rollup-plugin-copy'; +import process from 'node:process'; + +const configFile = process.env.CONFIG_FILE ?? 'config/dev.js'; +await import(`./${configFile}`); + +export default { + input: "src/main.js", + output: { + file: "dist/bundle.js", + format: "iife" + }, + plugins: [ + nodeResolve({ + browser: true, + preferBuiltins: false, + }), + commonjs(), + copy({ + targets: [ + { + src: 'assets/index.html', + dest: 'dist', + transform: (contents, name) => { + return contents.toString().replace('__SDK_URL__', + process.env.PUTER_JS_URL ?? globalThis.__CONFIG__.sdk_url); + } + }, + { src: configFile, dest: 'dist', rename: 'config.js' } + ] + }), + ] +} diff --git a/packages/git/src/filesystem.js b/packages/git/src/filesystem.js new file mode 100644 index 00000000..88bf74db --- /dev/null +++ b/packages/git/src/filesystem.js @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { PosixError } from '@heyputer/puter-js-common/src/PosixError.js'; +import path_ from 'path-browserify'; + +let debug = false; + +// Takes a Puter stat() result and converts it to the format expected by isomorphic-git +const convert_stat = (stat, options) => { + // Puter returns the times as timestamps in seconds + const timestamp_date = (timestamp) => new Date(timestamp * 1000); + const timestamp_ms = (timestamp) => options.bigint ? BigInt(timestamp) * 1000n : timestamp * 1000; + const timestamp_ns = (timestamp) => options.bigint ? BigInt(timestamp) * 1000000n : undefined; + + // We don't record ctime, but the most recent of atime and mtime is a reasonable approximation + const ctime = Math.max(stat.accessed, stat.modified); + + const mode = (() => { + // Puter doesn't expose this, but we can approximate it based on the stats we have. + let user = stat.immutable ? 4 : 6; + let group = stat.immutable ? 4 : 6; + let other = stat.is_public ? 4 : 0; + // Octal number + return user << 6 | group << 3 | other; + })(); + + return { + dev: 1, // Puter doesn't expose this + ino: stat.id, + mode: mode, + nlink: 1, // Definition of hard-link number is platform-defined. Linux includes subdir count, Mac includes child count. + uid: stat.uid, + gid: stat.uid, // Puter doesn't have gids + rdev: 0, + size: stat.size, + blksize: 4096, // Abitrary! + blocks: Math.ceil(stat.size / 4096), + atime: timestamp_date(stat.accessed), + mtime: timestamp_date(stat.modified), + ctime: timestamp_date(ctime), + birthtime: timestamp_date(stat.created), + atimeMs: timestamp_ms(stat.accessed), + mtimeMs: timestamp_ms(stat.modified), + ctimeMs: timestamp_ms(ctime), + birthtimeMs: timestamp_ms(stat.created), + atimeNs: timestamp_ns(stat.accessed), + mtimeNs: timestamp_ns(stat.modified), + ctimeNs: timestamp_ns(ctime), + birthtimeNs: timestamp_ns(stat.created), + + isBlockDevice: () => false, + isCharacterDevice: () => false, + isDirectory: () => stat.is_dir, + isFIFO: () => false, + isFile: () => !stat.is_dir, + isSocket: () => false, + isSymbolicLink: () => stat.is_symlink, + }; +}; + +const adapt_path = (input_path) => { + if (input_path[0] === '/') return input_path; + return path_.relative(window.process.cwd(), input_path); +}; + +// Implements the API expected by isomorphic-git +// See: https://isomorphic-git.org/docs/en/fs#using-the-promise-api-preferred +export default { + enable_debugging: () => { debug = true; }, + promises: { + readFile: async (path, options = {}) => { + if (debug) console.trace('readFile', path, options); + // TODO: Obey options + try { + const blob = await puter.fs.read(adapt_path(path)); + if (options.encoding === 'utf8') + return await blob.text(); + return blob.arrayBuffer(); + } catch (e) { + throw PosixError.fromPuterAPIError(e); + } + }, + writeFile: async (path, data, options = {}) => { + if (debug) console.trace('writeFile', path, data, options); + // TODO: Obey options + + // Convert data into a type puter.fs.write() understands. + // Can be: | | | + // Puter supports: | | + if ( + data instanceof window.Buffer // Buffer + || ArrayBuffer.isView(data) // TypedArray + || data instanceof DataView // DataView + ) { + data = new File([data], path_.basename(path)); + } + + try { + return await puter.fs.write(adapt_path(path), data); + } catch (e) { + throw PosixError.fromPuterAPIError(e); + } + }, + unlink: async (path) => { + if (debug) console.trace('unlink', path); + // TODO: If `path` is a symlink, only remove the link + try { + return await puter.fs.delete(adapt_path(path), { recursive: false }); + } catch (e) { + throw PosixError.fromPuterAPIError(e); + } + }, + readdir: async (path, options = {}) => { + if (debug) console.trace('readdir', path, options); + // TODO: Obey options + try { + const results = await puter.fs.readdir(adapt_path(path)); + // Puter returns an array of stat entries, but we only want the file names + return results.map(it => path_.basename(it.path)); + } catch (e) { + throw PosixError.fromPuterAPIError(e); + } + }, + mkdir: async (path, mode) => { + if (debug) console.trace('mkdir', path, mode); + // NOTE: Puter filesystem doesn't have file permissions + try { + return await puter.fs.mkdir(adapt_path(path)); + } catch (e) { + throw PosixError.fromPuterAPIError(e); + } + }, + rmdir: async (path) => { + if (debug) console.trace('rmdir', path); + // TODO: Only delete dir if it's empty + try { + return await puter.fs.delete(adapt_path(path), { recursive: true }); + } catch (e) { + throw PosixError.fromPuterAPIError(e); + } + }, + stat: async (path, options = {}) => { + if (debug) console.trace('stat', path, options); + // TODO: Obey options + try { + return convert_stat(await puter.fs.stat(adapt_path(path)), options); + } catch (e) { + throw PosixError.fromPuterAPIError(e); + } + }, + lstat: async (path, options = {}) => { + if (debug) console.trace('lstat', path, options); + // TODO: Obey options + // TODO: Stat the link itself. + try { + return convert_stat(await puter.fs.stat(adapt_path(path)), options); + } catch (e) { + throw PosixError.fromPuterAPIError(e); + } + }, + readlink: async (path, options = {}) => { + if (debug) console.trace('readlink', path, options); + try { + const stat = await puter.fs.stat(adapt_path(path)); + return stat.symlink_path; + } catch (e) { + throw PosixError.fromPuterAPIError(e); + } + }, + symlink: async (target, path, type) => { + if (debug) console.trace('symlink', target, path, type); + // TODO: Add symlink creation to puter.fs API + throw PosixError.OperationNotPermitted({ message: 'Puter.fs API does not support creating symlinks' }); + }, + chmod: async (path, mode) => { + if (debug) console.trace('chmod', path, mode); + // NOTE: No-op, Puter doesn't have file permissions + }, + }, +}; \ No newline at end of file diff --git a/packages/git/src/format.js b/packages/git/src/format.js new file mode 100644 index 00000000..967919f2 --- /dev/null +++ b/packages/git/src/format.js @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { shorten_hash } from './git-helpers.js'; + +export const commit_formatting_options = { + 'abbrev-commit': { + description: 'Display commit hashes in abbreviated form.', + type: 'boolean', + }, + 'no-abbrev-commit': { + description: 'Always show full commit hashes.', + type: 'boolean', + }, + 'format': { + description: 'Format to use for commits.', + type: 'string', + }, + 'oneline': { + description: 'Shorthand for "--format=oneline --abbrev-commit".', + type: 'boolean', + }, +}; + +/** + * Process command-line options related to commit formatting, and modify them in place. + * May throw if the options are in some way invalid. + * @param options Parsed command-line options, which will be modified in place. + */ +export const process_commit_formatting_options = (options) => { + if (options.oneline) { + options.format = 'oneline'; + options['abbrev-commit'] = true; + } + + options.short_hashes = (options['abbrev-commit'] === true) && (options['no-abbrev-commit'] !== true); + delete options['abbrev-commit']; + delete options['no-abbrev-commit']; + + if (!options.format) { + options.format = 'medium'; + } + if (!['oneline', 'short', 'medium', 'full', 'fuller', 'raw'].includes(options.format)) { + throw new Error(`Invalid --format format: ${options.format}`); + } +} + +/** + * Format the given oid hash, followed by any refs that point to it + * @param oid + * @param short_hashes Whwther to shorten the hash + * @returns {String} + */ +export const format_oid = (oid, { short_hashes = false } = {}) => { + // TODO: List refs at this commit, after the hash + return short_hashes ? shorten_hash(oid) : oid; +} + +/** + * Format the person's name and email as `${name} <${email}>` + * @param person + * @returns {`${string} <${string}>`} + */ +export const format_person = (person) => { + return `${person.name} <${person.email}>`; +} + +/** + * Format a date + * @param date + * @param options + * @returns {string} + */ +export const format_date = (date, options = {}) => { + // TODO: This needs to obey date-format options, and should show the correct timezone not UTC + return new Date(date.timestamp * 1000).toUTCString(); +} + +/** + * Format the date, according to the "raw" display format. + * @param owner + * @returns {`${string} ${string}${string}${string}`} + */ +export const format_timestamp_and_offset = (owner) => { + // FIXME: The timezone offset is inverted. + // Either this is correct here, or we should be inverting it when creating the commit - + // Isomorphic git uses (new Date()).timezoneOffset() there, which returns -60 for BST, which is UTC+0100 + const offset = -owner.timezoneOffset; + const offset_hours = Math.floor(offset / 60); + const offset_minutes = offset % 60; + const pad = (number) => `${Math.abs(number) < 10 ? '0' : ''}${Math.abs(number)}`; + return `${owner.timestamp} ${offset < 0 ? '-' : '+'}${pad(offset_hours)}${pad(offset_minutes)}`; +} + +/** + * Produce a string representation of a commit. + * @param commit A CommitObject + * @param oid Commit hash + * @param options Options returned by parsing the command arguments in `commit_formatting_options` + * @returns {string} + */ +export const format_commit = (commit, oid, options = {}) => { + const title_line = () => commit.message.split('\n')[0]; + + switch (options.format || 'medium') { + // TODO: Other formats + case 'oneline': + return `${format_oid(oid, options)} ${title_line()}`; + case 'short': { + let s = ''; + s += `commit ${format_oid(oid, options)}\n`; + s += `Author: ${format_person(commit.author)}\n`; + s += '\n'; + s += title_line(); + return s; + } + case 'medium': { + let s = ''; + s += `commit ${format_oid(oid, options)}\n`; + s += `Author: ${format_person(commit.author)}\n`; + s += `Date: ${format_date(commit.author)}\n`; + s += '\n'; + s += commit.message; + return s; + } + case 'full': { + let s = ''; + s += `commit ${format_oid(oid, options)}\n`; + s += `Author: ${format_person(commit.author)}\n`; + s += `Commit: ${format_person(commit.committer)}\n`; + s += '\n'; + s += commit.message; + return s; + } + case 'fuller': { + let s = ''; + s += `commit ${format_oid(oid, options)}\n`; + s += `Author: ${format_person(commit.author)}\n`; + s += `AuthorDate: ${format_date(commit.author)}\n`; + s += `Commit: ${format_person(commit.committer)}\n`; + s += `CommitDate: ${format_date(commit.committer)}\n`; + s += '\n'; + s += commit.message; + return s; + } + case 'raw': { + let s = ''; + s += `commit ${oid}\n`; + s += `tree ${commit.tree}\n`; + if (commit.parent[0]) + s += `parent ${commit.parent[0]}\n`; + s += `author ${format_person(commit.author)} ${format_timestamp_and_offset(commit.author)}\n`; + s += `committer ${format_person(commit.committer)} ${format_timestamp_and_offset(commit.committer)}\n`; + s += '\n'; + s += commit.message; + return s; + } + default: { + throw new Error(`Invalid --format format: ${options.format}`); + } + } +} + +/** + * Produce a string representation of a tree. + * @param oid + * @param tree + * @param options + * @returns {string} + */ +export const format_tree = (oid, tree, options = {}) => { + let s = ''; + s += `tree ${oid}\n`; + s += '\n'; + for (const tree_entry of tree) { + s += `${tree_entry.path}\n`; + } + s += '\n'; + return s; +} + +/** + * Produce a string representation of a tag. + * Note that this only includes the tag itself, and not the tag's target, + * which must be separately retrieved and formatted. + * @param tag + * @param options + * @returns {string} + */ +export const format_tag = (tag, options = {}) => { + let s = ''; + s += `tag ${tag.tag}\n`; + s += `Tagger: ${format_person(tag.tagger)}\n`; + s += `Date: ${format_date(tag.tagger, options)}\n`; + if (tag.message) { + s += `\n${tag.message}\n\n`; + } + return s; +} diff --git a/packages/git/src/git-command-definition.js b/packages/git/src/git-command-definition.js new file mode 100644 index 00000000..6ff37f0a --- /dev/null +++ b/packages/git/src/git-command-definition.js @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * The command definition for `git` itself, in the same format as subcommands. + */ +export default { + name: 'git', + usage: 'git [--version] [--help] [command] [command-args...]', + description: 'Git version-control client for Puter.', + args: { + options: { + help: { + description: 'Display help information for git itself, or a subcommand.', + type: 'boolean', + }, + version: { + description: 'Display version information about git.', + type: 'boolean', + }, + }, + }, +}; diff --git a/packages/git/src/git-helpers.js b/packages/git/src/git-helpers.js new file mode 100644 index 00000000..87e00862 --- /dev/null +++ b/packages/git/src/git-helpers.js @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import path from 'path-browserify'; + +/** + * Attempt to locate the git repository directory. + * @throws Error If no git repository could be found, or another error occurred. + * @param fs Filesystem API + * @param pwd Directory to search from + * @returns {Promise<{repository_dir: (string|*), git_dir: (string|string)}>} + */ +export const find_repo_root = async (fs, pwd) => { + if (!path.isAbsolute(pwd)) + throw new Error(`PWD is not absolute: ${pwd}`); + + let current_path = path.normalize(pwd); + while (true) { + let stat; + const current_git_path = path.resolve(current_path, './.git'); + try { + stat = await fs.promises.stat(current_git_path); + } catch (e) { + if (e.code === 'ENOENT') { + if (current_path === '/') + break; + + current_path = path.dirname(current_path); + continue; + } + + throw e; + } + + // If .git exists, we're probably in a git repo so call that good. + // TODO: The git cli seems to check other things, maybe try to match that behaviour. + + const result = { + repository_dir: current_path, + git_dir: current_git_path, + }; + + // Non-default-git-folder repos have .git as a text file containing the git dir path. + if (stat.isFile()) { + const contents = await fs.promises.readFile(current_git_path, { encoding: 'utf8' }); + // The format of .git is `gitdir: /path/to/git/dir` + const prefix = 'gitdir:'; + if (!contents.startsWith(prefix)) + throw new Error(`invalid gitfile format: ${current_git_path}`); + result.git_dir = contents.slice(prefix.length).trim(); + } + + return result; + } + + throw new Error('not a git repository (or any of the parent directories): .git'); +} + +/** + * Produce a shortened version of the given hash, which is still unique within the repo. + * TODO: Ensure that whatever we produce is unique within the repo. + * For now this is just a convenience function, so there's one place to change later. + * @param hash + * @returns {String} The shortened hash + */ +export const shorten_hash = (hash) => { + // TODO: Ensure that whatever we produce is unique within the repo + return hash.slice(0, 7); +} diff --git a/packages/git/src/help.js b/packages/git/src/help.js new file mode 100644 index 00000000..bd0b9c02 --- /dev/null +++ b/packages/git/src/help.js @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +/** + * Throw this from a subcommand's execute() in order to print its usage text to stderr. + * @type {symbol} + */ +export const SHOW_USAGE = Symbol('SHOW_USAGE'); + +/** + * Full manual page for the command. + * @param command + * @returns {string} + */ +export const produce_help_string = (command) => { + const { name, usage, description, args } = command; + const options = args?.options; + + let s = ''; + const indent = ' '; + + const heading = (text) => { + s += `\n\x1B[34;1m${text}:\x1B[0m\n` + }; + + heading('SYNOPSIS'); + if (!usage) { + s += `${indent}git ${name}\n`; + } else if (typeof usage === 'string') { + s += `${indent}${usage}\n`; + } else { + let first = true; + for (const usage_line of usage) { + if (first) { + first = false; + s += `${indent}${usage_line}\n`; + } else { + s += `${indent}${usage_line}\n`; + } + } + } + + if (description) { + heading('DESCRIPTION'); + s += `${indent}${description}\n`; + } + + if (typeof options === 'object' && Object.keys(options).length > 0) { + heading('OPTIONS'); + // Figure out how long each invocation is, so we can align the descriptions + for (const [name, option] of Object.entries(options)) { + // Invocation + s += indent; + if (option.short) + s += `-${option.short}, `; + s += `--${name}`; + if (option.type !== 'boolean') + s += ` <${option.type}>`; + s += '\n'; + + // Description + s += `${indent}${indent}${option.description}\n\n`; + } + } + + if (!s.endsWith('\n\n')) + s += '\n'; + + return s; +} + +/** + * Usage for the command, which is a short summary. + * @param command + * @returns {string} + */ +export const produce_usage_string = (command) => { + const { name, usage, args } = command; + const options = args?.options; + + let s = ''; + + // Usage + if (!usage) { + s += `usage: git ${name}\n`; + } else if (typeof usage === 'string') { + s += `usage: ${usage}\n`; + } else { + let first = true; + for (const usage_line of usage) { + if (first) { + first = false; + s += `usage: ${usage_line}\n`; + } else { + s += ` or: ${usage_line}\n`; + } + } + } + + // List of options + if (typeof options === 'object' && Object.keys(options).length > 0) { + // Figure out how long each invocation is, so we can align the descriptions + const option_strings = Object.entries(options).map(([name, option]) => { + let invocation = ''; + if (option.short) + invocation += `-${option.short}, `; + invocation += `--${name}`; + if (option.type !== 'boolean') + invocation += ` <${option.type}>`; + + return [invocation, option.description]; + }); + + const indent_size = 2 + option_strings.reduce( + (max_length, option) => Math.max(max_length, option[0].length), 0); + + s += '\n'; + for (const [invocation, description] of option_strings) { + s += ` ${invocation}`; + if (indent_size - invocation.length > 0) + s += ' '.repeat(indent_size - invocation.length); + s += `${description}\n`; + } + } + + return s; +} diff --git a/packages/git/src/main.js b/packages/git/src/main.js new file mode 100644 index 00000000..adb1f744 --- /dev/null +++ b/packages/git/src/main.js @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import { parseArgs } from '@pkgjs/parseargs'; +import subcommands from './subcommands/__exports__.js'; +import git_command from './git-command-definition.js'; +import fs from './filesystem.js'; +import git from 'isomorphic-git'; +import { Buffer } from 'buffer'; +import { produce_usage_string, SHOW_USAGE } from './help.js'; + +const encoder = new TextEncoder(); + +window.Buffer = Buffer; + +window.main = async () => { + const shell = puter.ui.parentApp(); + if (!shell) { + await puter.ui.alert('Git must be run from a terminal. Try `git --help`'); + puter.exit(); + return; + } + + shell.on('close', () => { + console.log('Shell closed; exiting git...'); + puter.exit(); + }); + + const stdout = (message) => { + shell.postMessage({ + $: 'stdout', + data: encoder.encode(message + '\n'), + }); + }; + // TODO: Separate stderr message? + const stderr = stdout; + + const url_params = new URL(document.location).searchParams; + const puter_args = JSON.parse(url_params.get('puter.args')) ?? {}; + const { command_line, env } = puter_args; + + // isomorphic-git assumes the Node.js process object exists, + // so fill-in the parts it uses. + window.process = { + cwd: () => env.PWD, + platform: 'puter', + } + + // Git's command structure is a little unusual: + // > git [options-for-git] [subcommand [options-and-args-for-subcommand]] + // Also, a couple of options (--help and --version) are syntactic sugar for `help` and `version` subcommands. + // The approach here is to first try and parse these top-level options, and then based on that, run a subcommand. + + // If no raw args, just print help and exit + const raw_args = command_line?.args ?? []; + if (raw_args.length === 0) { + stdout(produce_usage_string(git_command)); + puter.exit(); + return; + } + + const { values: global_options, positionals: global_positionals } = parseArgs({ + options: git_command.args.options, + allowPositionals: true, + args: raw_args, + strict: false, + }); + + let subcommand_name = null; + let first_positional_is_subcommand = false; + if (global_options.help) { + subcommand_name = 'help'; + } else if (global_options.version) { + subcommand_name = 'version'; + } + + if (!subcommand_name) { + subcommand_name = global_positionals[0]; + first_positional_is_subcommand = true; + } + + // See if we're running a subcommand we recognize + let exit_code = 0; + const subcommand = subcommands[subcommand_name]; + if (!subcommand) { + stderr(`git: '${subcommand_name}' is not a recognized git command. See 'git --help'`); + puter.exit(1); + return; + } + + // Try and remove the subcommand positional arg, and any global options, from args. + const subcommand_args = raw_args; + const remove_arg = (arg) => { + const index = subcommand_args.indexOf(arg); + if (index >= 0) + subcommand_args.splice(index, 1); + + } + remove_arg('--help'); + remove_arg('--version'); + if (first_positional_is_subcommand) { + // TODO: This is not a 100% reliable way to do this, as it may also match the value of `--option-with-value value` + // But that's not a problem until we add some global options that take a value. + remove_arg(subcommand_name); + } + + // Parse the remaining args scoped to this subcommand, and run it. + let parsed_args; + try { + parsed_args = parseArgs({ + ...subcommand.args, + args: subcommand_args, + }); + } catch (e) { + stderr(produce_usage_string(subcommand)); + puter.exit(1); + return; + } + + const ctx = { + io: { + stdout, + stderr, + }, + fs, + args: { + options: parsed_args.values, + positionals: parsed_args.positionals, + }, + env, + }; + + try { + exit_code = await subcommand.execute(ctx) ?? 0; + } catch (e) { + if (e === SHOW_USAGE) { + stderr(produce_usage_string(subcommand)); + } else { + stderr(`fatal: ${e.message}`); + console.error(e); + } + exit_code = 1; + } + + // TODO: Support passing an exit code to puter.exit(); + puter.exit(exit_code); +} diff --git a/packages/git/src/subcommands/__exports__.js b/packages/git/src/subcommands/__exports__.js new file mode 100644 index 00000000..789c592a --- /dev/null +++ b/packages/git/src/subcommands/__exports__.js @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Phoenix Shell. + * + * Phoenix Shell is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +// Generated by /tools/gen.js +import module_add from './add.js' +import module_commit from './commit.js' +import module_config from './config.js' +import module_help from './help.js' +import module_init from './init.js' +import module_log from './log.js' +import module_show from './show.js' +import module_status from './status.js' +import module_version from './version.js' + +export default { + "add": module_add, + "commit": module_commit, + "config": module_config, + "help": module_help, + "init": module_init, + "log": module_log, + "show": module_show, + "status": module_status, + "version": module_version, +}; diff --git a/packages/git/src/subcommands/add.js b/packages/git/src/subcommands/add.js new file mode 100644 index 00000000..441576d0 --- /dev/null +++ b/packages/git/src/subcommands/add.js @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import path from 'path-browserify'; +import { ErrorCodes } from '@heyputer/puter-js-common/src/PosixError.js'; +import { find_repo_root } from '../git-helpers.js'; + +export default { + name: 'add', + usage: 'git add [--] [...]', + description: 'Add file contents to the index.', + args: { + allowPositionals: true, + options: { + }, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + const pathspecs = [...positionals]; + if (pathspecs.length === 0) { + stdout('Nothing specified, nothing added.'); + return; + } + + const { repository_dir, git_dir } = await find_repo_root(fs, env.PWD); + + await git.add({ + fs, + dir: repository_dir, + gitdir: git_dir, + ignored: false, + filepath: pathspecs, + parallel: true, + }); + } +} diff --git a/packages/git/src/subcommands/commit.js b/packages/git/src/subcommands/commit.js new file mode 100644 index 00000000..35ab9042 --- /dev/null +++ b/packages/git/src/subcommands/commit.js @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import path from 'path-browserify'; +import { ErrorCodes } from '@heyputer/puter-js-common/src/PosixError.js'; +import { find_repo_root, shorten_hash } from '../git-helpers.js'; + +export default { + name: 'commit', + usage: 'git commit [-m|--message ] [-a|--author ]', + description: 'Commit staged changes to the repository.', + args: { + allowPositionals: false, + options: { + message: { + description: 'Specify the commit message', + type: 'string', + short: 'm', + }, + author: { + description: 'Specify the commit author, as `A U Thor `', + type: 'string', + }, + }, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + if (!options.message) { + // TODO: Support opening a temporary file in an editor, + // where the user can edit the commit message if it's not specified. + stderr('You must specify a commit message with --message or -m'); + return 1; + } + + const { repository_dir, git_dir } = await find_repo_root(fs, env.PWD); + + let user_name; + let user_email; + + if (options.author) { + const author_regex = /(.+?)\s+<(.+)>/; + const matches = options.author.match(author_regex); + if (!matches) + throw new Error('Failed to parse author string'); + user_name = matches[1]; + user_email = matches[2]; + } else { + user_name = await git.getConfig({ + fs, + dir: repository_dir, + gitdir: git_dir, + path: 'user.name', + }); + user_email = await git.getConfig({ + fs, + dir: repository_dir, + gitdir: git_dir, + path: 'user.email', + }); + } + + if (!user_name || !user_email) { + throw new Error('Missing author information. Either provide --author="A " or set user.name and user.email in the git config'); + } + + const commit_hash = await git.commit({ + fs, + dir: repository_dir, + gitdir: git_dir, + message: options.message, + author: { + name: user_name, + email: user_email, + }, + }); + + const branch = await git.currentBranch({ + fs, + dir: repository_dir, + gitdir: git_dir, + }); + const commit_title = options.message.split('\n')[0]; + const short_hash = shorten_hash(commit_hash); + let output = `[${branch} ${short_hash}] ${commit_title}\n`; + // TODO: --amend prints out the date of the original commit here, as: + // ` Date: Fri May 17 15:45:47 2024 +0100` + // TODO: Print out file change count, insertion count, and deletion count + // (Seems if insertions and deletions are both 0, we should print both. + // Otherwise we just print nonzero ones.) + // TODO: Print out each file created or deleted. eg: + // create mode 100644 bar + // delete mode 100644 foo.txt + stdout(output); + } +} diff --git a/packages/git/src/subcommands/config.js b/packages/git/src/subcommands/config.js new file mode 100644 index 00000000..4c9da23a --- /dev/null +++ b/packages/git/src/subcommands/config.js @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import { find_repo_root } from '../git-helpers.js'; +import { SHOW_USAGE } from '../help.js'; + +export default { + name: 'config', + usage: ['git config name', 'git config name value', 'git config --unset name'], + description: 'Get or set git configuration options.', + args: { + allowPositionals: true, + options: { + 'unset': { + description: 'Remove the matching line from the config.', + type: 'boolean', + }, + // TODO: --list, which doesn't have a isomorphic-git command yet. + // See https://github.com/isomorphic-git/isomorphic-git/issues/1917 + }, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + if (positionals.length === 0 || positionals.length > 2) + throw SHOW_USAGE; + + const key = positionals.shift(); + const value = positionals.shift(); + + const { repository_dir, git_dir } = await find_repo_root(fs, env.PWD); + + if (value || options.unset) { + // Set it + // TODO: If --unset AND we have a value, we should only remove an entry that has that value + await git.setConfig({ + fs, + dir: repository_dir, + gitdir: git_dir, + path: key, + value: options.unset ? undefined : value, + }); + return; + } + + // Get it + const result = await git.getConfig({ + fs, + dir: repository_dir, + gitdir: git_dir, + path: key, + }); + if (result === undefined) { + // Not found, so return 1 + return 1; + } + stdout(result); + } +} diff --git a/packages/git/src/subcommands/help.js b/packages/git/src/subcommands/help.js new file mode 100644 index 00000000..1a27f831 --- /dev/null +++ b/packages/git/src/subcommands/help.js @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import { ErrorCodes } from '@heyputer/puter-js-common/src/PosixError.js'; +import subcommands from './__exports__.js'; +import git_command from '../git-command-definition.js'; +import { produce_help_string } from '../help.js'; + +export default { + name: 'help', + usage: ['git help [-a|--all]', 'git help '], + description: `Display help information for git itself, or a subcommand.`, + args: { + allowPositionals: true, + options: { + all: { + description: 'List all available subcommands.', + type: 'boolean', + } + }, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + if (options.all) { + stdout(`See 'git help ' for more information.\n`); + const max_name_length = Object.keys(subcommands).reduce((max, name) => Math.max(max, name.length), 0); + for (const [name, command] of Object.entries(subcommands)) { + stdout(` ${name} ${' '.repeat(Math.max(max_name_length - name.length, 0))} ${command.description || ''}`); + } + return; + } + + if (positionals.length > 0) { + // Try and display help page for the subcommand + const subcommand_name = positionals[0]; + const subcommand = subcommands[subcommand_name]; + if (!subcommand) + throw new Error(`No manual entry for ${subcommand_name}`); + + stdout(produce_help_string(subcommand)); + + return; + } + + // No subcommand name, so show general help + stdout(produce_help_string(git_command)); + } +} diff --git a/packages/git/src/subcommands/init.js b/packages/git/src/subcommands/init.js new file mode 100644 index 00000000..cf211309 --- /dev/null +++ b/packages/git/src/subcommands/init.js @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import path from 'path-browserify'; +import { ErrorCodes } from '@heyputer/puter-js-common/src/PosixError.js'; + +export default { + name: 'init', + usage: 'git init [--bare] [--initial-branch=BRANCH] [--separate-git-dir=DIR]', + description: `Initialize or re-initialize a git repository.`, + args: { + allowPositionals: true, + options: { + 'bare': { + description: 'Create a bare repository', + type: 'boolean', + default: false, + }, + 'initial-branch': { + description: 'Name of the initial branch', + type: 'string', + short: 'b', + }, + 'separate-git-dir': { + description: 'Name of directory to store the git repository, instead of ./.git/', + type: 'string', + default: './.git', + }, + }, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + const [ directory, ...extra_positionals ] = positionals ?? []; + if (extra_positionals.length) { + stderr('Too many parameters'); + return 1; + } + + const dir = directory ? path.resolve(env.PWD, directory) : env.PWD; + const gitdir = path.resolve(dir, options['separate-git-dir']); + const dot_git_path = path.resolve(dir, './.git'); + + // Check if repo already initialized + let repo_exists = false; + try { + const stat = await fs.promises.stat(dot_git_path); + repo_exists = true; + } catch (e) { + if (e.posixCode === ErrorCodes.ENOENT) { + repo_exists = false; + } + } + + if (repo_exists) { + // TODO: `git init` in an existing repo re-initializes it without erasing anything. + // If the git dir provided is different than the existing one, it moves the contents to the new location. + // isomorphic-git does not seem to do this, and it's an unusual case, so for now we just prevent `git init` + // for existing repos. + stderr('Git repository already initialized'); + return 1; + } + + await git.init({ + fs, + bare: options.bare, + dir, + gitdir, + defaultBranch: options['initial-branch'], + }).then(() => { + // If we're using a different git dir, create a .git file pointing to it + if (gitdir !== dot_git_path) { + return fs.promises.writeFile(dot_git_path, `gitdir: ${gitdir}\n`, { encoding: 'utf8' }); + } + }); + + stdout(`Initialized empty git repository in ${gitdir}`); + } +} diff --git a/packages/git/src/subcommands/log.js b/packages/git/src/subcommands/log.js new file mode 100644 index 00000000..dfe6f240 --- /dev/null +++ b/packages/git/src/subcommands/log.js @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import { find_repo_root } from '../git-helpers.js'; +import { commit_formatting_options, format_commit, process_commit_formatting_options } from '../format.js'; + +export default { + name: 'log', + usage: 'git log [...] [--max-count ] ', + description: 'Show commit logs, starting at the given revision.', + args: { + allowPositionals: false, + options: { + ...commit_formatting_options, + 'max-count': { + description: 'Maximum number of commits to output.', + type: 'string', + short: 'n', + }, + }, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + process_commit_formatting_options(options); + + // TODO: Log of a specific file + // TODO: Log of a specific branch + // TODO: Log of a specific commit + + const depth = Number(options['max-count']) || undefined; + + const { repository_dir, git_dir } = await find_repo_root(fs, env.PWD); + + const log = await git.log({ + fs, + dir: repository_dir, + gitdir: git_dir, + depth, + }); + + for (const commit of log) { + stdout(format_commit(commit.commit, commit.oid, options)); + } + } +} diff --git a/packages/git/src/subcommands/show.js b/packages/git/src/subcommands/show.js new file mode 100644 index 00000000..ef89e23b --- /dev/null +++ b/packages/git/src/subcommands/show.js @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import { find_repo_root } from '../git-helpers.js'; +import { commit_formatting_options, process_commit_formatting_options, format_commit, format_tag, format_tree } from '../format.js'; + +export default { + name: 'show', + usage: 'git show [...] ', + description: 'Show information about an object (commit, tree, tag, blob, etc.) in git.', + args: { + allowPositionals: true, + options: { + ...commit_formatting_options, + }, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + process_commit_formatting_options(options); + + const { repository_dir, git_dir } = await find_repo_root(fs, env.PWD); + + const objects = [...positionals]; + + const cache = {}; + + const format_object = async (parsed_object, options) => { + switch (parsed_object.type) { + case 'blob': + return parsed_object.object; + case 'commit': + return format_commit(parsed_object.object, parsed_object.oid, options); + case 'tree': + return format_tree(parsed_object.oid, parsed_object.object, options); + case 'tag': { + const tag = parsed_object.object; + let s = format_tag(tag, options); + // Formatting a tag also outputs the formatted object it points to. + // That may also be a tag, so we recurse. + const target = await git.readObject({ + fs, + dir: repository_dir, + gitdir: git_dir, + oid: tag.object, + format: 'parsed', + cache, + }); + s += await format_object(target, options); + return s; + } + } + } + + for (const ref of objects) { + // Could be any ref, so first get the oid that's referred to. + const oid = await git.resolveRef({ + fs, + dir: repository_dir, + gitdir: git_dir, + ref, + }); + + // Then obtain the object and parse it. + const parsed_object = await git.readObject({ + fs, + dir: repository_dir, + gitdir: git_dir, + oid, + format: 'parsed', + cache, + }); + + // Then, print it out + stdout(await format_object(parsed_object, options)); + } + } +} diff --git a/packages/git/src/subcommands/status.js b/packages/git/src/subcommands/status.js new file mode 100644 index 00000000..3ad136be --- /dev/null +++ b/packages/git/src/subcommands/status.js @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import path from 'path-browserify'; +import { ErrorCodes } from '@heyputer/puter-js-common/src/PosixError.js'; +import { find_repo_root } from '../git-helpers.js'; + +export default { + name: 'status', + usage: 'git status', + description: 'Describe the status of the git working tree.', + args: { + allowPositionals: false, + options: { + }, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + const { repository_dir, git_dir } = await find_repo_root(fs, env.PWD); + + // Gather up file differences + const file_status = await git.statusMatrix({ + fs, + dir: repository_dir, + gitdir: git_dir, + ignored: false, + }); + + const staged = []; + const unstaged = []; + const untracked = []; + + const HEAD = 1; + const WORKDIR = 2; + const STAGE = 3; + + for (const file of file_status) { + const absolute_path = path.resolve(repository_dir, file[0]); + const relative_path = path.relative(env.PWD, absolute_path); + + const status_string = `${file[1]}${file[2]}${file[3]}`; + switch (status_string) { + case '020': // new, untracked + untracked.push(relative_path); + break; + case '022': // added, staged + staged.push([relative_path, 'added']); + break; + case '023': // added, staged, with unstaged changes + staged.push([relative_path, 'added']); + unstaged.push([relative_path, 'modified']); + break; + case '111': // unmodified + // Ignore it + break; + case '121': // modified, unstaged + unstaged.push([relative_path, 'modified']); + break; + case '122': // modified, staged + staged.push([relative_path, 'modified']); + break; + case '123': // modified, staged, with unstaged changes + staged.push([relative_path, 'modified']); + unstaged.push([relative_path, 'modified']); + break; + case '101': // deleted, unstaged + unstaged.push([relative_path, 'deleted']); + break; + case '100': // deleted, staged + staged.push([relative_path, 'deleted']); + break; + case '120': // deleted, staged, with unstaged-modified changes (new file of the same name) + case '110': // deleted, staged, with unstaged changes (new file of the same name) + staged.push([relative_path, 'deleted']); + unstaged.push([relative_path, 'added']); + break; + } + } + + // TODO: Short-format output + + const padding = (length) => { + if (length <= 0) return ''; + return ' '.repeat(length); + } + + const current_branch = await git.currentBranch({ + fs, + dir: repository_dir, + gitdir: git_dir, + }); + stdout(`On branch ${current_branch}\n`); + + if (staged.length) { + stdout('Changes to be committed:'); + for (const [file, change] of staged) { + stdout(` ${change}: ${padding(10 - change.length)}${file}`); + } + stdout(''); + } + + if (unstaged.length) { + stdout('Changes not staged for commit:'); + for (const [file, change] of unstaged) { + stdout(` ${change}: ${padding(10 - change.length)}${file}`); + } + stdout(''); + } + + if (untracked.length) { + stdout('Untracked files:'); + // TODO: Native git is smart enough to only list a top-level directory if all its contents are untracked + for (const file of untracked) { + stdout(` ${file}`); + } + } + + if (staged.length + unstaged.length + untracked.length === 0) { + stdout('nothing to commit, working tree clean'); + } + } +} diff --git a/packages/git/src/subcommands/version.js b/packages/git/src/subcommands/version.js new file mode 100644 index 00000000..2e639f0d --- /dev/null +++ b/packages/git/src/subcommands/version.js @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter's Git client. + * + * Puter's Git client is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +import git from 'isomorphic-git'; +import path from 'path-browserify'; +import { ErrorCodes } from '@heyputer/puter-js-common/src/PosixError.js'; + +const VERSION = '1.0.0'; + +export default { + name: 'version', + usage: 'git version', + description: `Display version information about git.`, + args: { + allowPositionals: false, + options: {}, + }, + execute: async (ctx) => { + const { io, fs, env, args } = ctx; + const { stdout, stderr } = io; + const { options, positionals } = args; + + stdout(`Puter git version ${VERSION} (isomorphic git version ${git.version()})`); + } +} diff --git a/packages/phoenix/src/platform/PosixError.js b/packages/phoenix/src/platform/PosixError.js deleted file mode 100644 index 0fc136a5..00000000 --- a/packages/phoenix/src/platform/PosixError.js +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (C) 2024 Puter Technologies Inc. - * - * This file is part of Phoenix Shell. - * - * Phoenix Shell is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -export const ErrorCodes = { - EACCES: Symbol.for('EACCES'), - EADDRINUSE: Symbol.for('EADDRINUSE'), - ECONNREFUSED: Symbol.for('ECONNREFUSED'), - ECONNRESET: Symbol.for('ECONNRESET'), - EEXIST: Symbol.for('EEXIST'), - EFBIG: Symbol.for('EFBIG'), - EINVAL: Symbol.for('EINVAL'), - EIO: Symbol.for('EIO'), - EISDIR: Symbol.for('EISDIR'), - EMFILE: Symbol.for('EMFILE'), - ENOENT: Symbol.for('ENOENT'), - ENOSPC: Symbol.for('ENOSPC'), - ENOTDIR: Symbol.for('ENOTDIR'), - ENOTEMPTY: Symbol.for('ENOTEMPTY'), - EPERM: Symbol.for('EPERM'), - EPIPE: Symbol.for('EPIPE'), - ETIMEDOUT: Symbol.for('ETIMEDOUT'), -}; - -// Codes taken from `errno` on Linux. -export const ErrorMetadata = new Map([ - [ErrorCodes.EPERM, { code: 1, description: 'Operation not permitted' }], - [ErrorCodes.ENOENT, { code: 2, description: 'File or directory not found' }], - [ErrorCodes.EIO, { code: 5, description: 'IO error' }], - [ErrorCodes.EACCES, { code: 13, description: 'Permission denied' }], - [ErrorCodes.EEXIST, { code: 17, description: 'File already exists' }], - [ErrorCodes.ENOTDIR, { code: 20, description: 'Is not a directory' }], - [ErrorCodes.EISDIR, { code: 21, description: 'Is a directory' }], - [ErrorCodes.EINVAL, { code: 22, description: 'Argument invalid' }], - [ErrorCodes.EMFILE, { code: 24, description: 'Too many open files' }], - [ErrorCodes.EFBIG, { code: 27, description: 'File too big' }], - [ErrorCodes.ENOSPC, { code: 28, description: 'Device out of space' }], - [ErrorCodes.EPIPE, { code: 32, description: 'Pipe broken' }], - [ErrorCodes.ENOTEMPTY, { code: 39, description: 'Directory is not empty' }], - [ErrorCodes.EADDRINUSE, { code: 98, description: 'Address already in use' }], - [ErrorCodes.ECONNRESET, { code: 104, description: 'Connection reset'}], - [ErrorCodes.ETIMEDOUT, { code: 110, description: 'Connection timed out' }], - [ErrorCodes.ECONNREFUSED, { code: 111, description: 'Connection refused' }], -]); - -export const errorFromIntegerCode = (code) => { - for (const [errorCode, metadata] of ErrorMetadata) { - if (metadata.code === code) { - return errorCode; - } - } - return undefined; -}; - -export class PosixError extends Error { - // posixErrorCode can be either a string, or one of the ErrorCodes above. - // If message is undefined, a default message will be used. - constructor(posixErrorCode, message) { - let posixCode; - if (typeof posixErrorCode === 'symbol') { - if (ErrorCodes[Symbol.keyFor(posixErrorCode)] !== posixErrorCode) { - throw new Error(`Unrecognized POSIX error code: '${posixErrorCode}'`); - } - posixCode = posixErrorCode; - } else { - const code = ErrorCodes[posixErrorCode]; - if (!code) throw new Error(`Unrecognized POSIX error code: '${posixErrorCode}'`); - posixCode = code; - } - - super(message ?? ErrorMetadata.get(posixCode).description); - this.posixCode = posixCode; - } - - // - // Helpers for constructing a PosixError when you don't already have an error message. - // - static AccessNotPermitted({ message, path } = {}) { - return new PosixError(ErrorCodes.EACCES, message ?? (path ? `Access not permitted to: '${path}'` : undefined)); - } - static AddressInUse({ message, address } = {}) { - return new PosixError(ErrorCodes.EADDRINUSE, message ?? (address ? `Address '${address}' in use` : undefined)); - } - static ConnectionRefused({ message } = {}) { - return new PosixError(ErrorCodes.ECONNREFUSED, message); - } - static ConnectionReset({ message } = {}) { - return new PosixError(ErrorCodes.ECONNRESET, message); - } - static PathAlreadyExists({ message, path } = {}) { - return new PosixError(ErrorCodes.EEXIST, message ?? (path ? `Path already exists: '${path}'` : undefined)); - } - static FileTooLarge({ message } = {}) { - return new PosixError(ErrorCodes.EFBIG, message); - } - static InvalidArgument({ message } = {}) { - return new PosixError(ErrorCodes.EINVAL, message); - } - static IO({ message } = {}) { - return new PosixError(ErrorCodes.EIO, message); - } - static IsDirectory({ message, path } = {}) { - return new PosixError(ErrorCodes.EISDIR, message ?? (path ? `Path is directory: '${path}'` : undefined)); - } - static TooManyOpenFiles({ message } = {}) { - return new PosixError(ErrorCodes.EMFILE, message); - } - static DoesNotExist({ message, path } = {}) { - return new PosixError(ErrorCodes.ENOENT, message ?? (path ? `Path not found: '${path}'` : undefined)); - } - static NotEnoughSpace({ message } = {}) { - return new PosixError(ErrorCodes.ENOSPC, message); - } - static IsNotDirectory({ message, path } = {}) { - return new PosixError(ErrorCodes.ENOTDIR, message ?? (path ? `Path is not a directory: '${path}'` : undefined)); - } - static DirectoryIsNotEmpty({ message, path } = {}) { - return new PosixError(ErrorCodes.ENOTEMPTY, message ?? (path ?`Directory is not empty: '${path}'` : undefined)); - } - static OperationNotPermitted({ message } = {}) { - return new PosixError(ErrorCodes.EPERM, message); - } - static BrokenPipe({ message } = {}) { - return new PosixError(ErrorCodes.EPIPE, message); - } - static TimedOut({ message } = {}) { - return new PosixError(ErrorCodes.ETIMEDOUT, message); - } -} diff --git a/packages/phoenix/src/platform/node/filesystem.js b/packages/phoenix/src/platform/node/filesystem.js index 85b9e84c..17e30710 100644 --- a/packages/phoenix/src/platform/node/filesystem.js +++ b/packages/phoenix/src/platform/node/filesystem.js @@ -20,30 +20,7 @@ import fs from 'fs'; import path_ from 'path'; import modeString from 'fs-mode-to-string'; -import { ErrorCodes, PosixError } from '../PosixError.js'; - -function convertNodeError(e) { - switch (e.code) { - case 'EACCES': return new PosixError(ErrorCodes.EACCES, e.message); - case 'EADDRINUSE': return new PosixError(ErrorCodes.EADDRINUSE, e.message); - case 'ECONNREFUSED': return new PosixError(ErrorCodes.ECONNREFUSED, e.message); - case 'ECONNRESET': return new PosixError(ErrorCodes.ECONNRESET, e.message); - case 'EEXIST': return new PosixError(ErrorCodes.EEXIST, e.message); - case 'EIO': return new PosixError(ErrorCodes.EIO, e.message); - case 'EISDIR': return new PosixError(ErrorCodes.EISDIR, e.message); - case 'EMFILE': return new PosixError(ErrorCodes.EMFILE, e.message); - case 'ENOENT': return new PosixError(ErrorCodes.ENOENT, e.message); - case 'ENOTDIR': return new PosixError(ErrorCodes.ENOTDIR, e.message); - case 'ENOTEMPTY': return new PosixError(ErrorCodes.ENOTEMPTY, e.message); - // ENOTFOUND is Node-specific. ECONNREFUSED is similar enough. - case 'ENOTFOUND': return new PosixError(ErrorCodes.ECONNREFUSED, e.message); - case 'EPERM': return new PosixError(ErrorCodes.EPERM, e.message); - case 'EPIPE': return new PosixError(ErrorCodes.EPIPE, e.message); - case 'ETIMEDOUT': return new PosixError(ErrorCodes.ETIMEDOUT, e.message); - } - // Some other kind of error - return e; -} +import { ErrorCodes, PosixError } from '@heyputer/puter-js-common/src/PosixError.js'; // DRY: Almost the same as puter/filesystem.js function wrapAPIs(apis) { @@ -56,7 +33,7 @@ function wrapAPIs(apis) { try { return await original(...args); } catch (e) { - throw convertNodeError(e); + throw PosixError.fromNodeJSError(e); } }; } diff --git a/packages/phoenix/src/platform/puter/filesystem.js b/packages/phoenix/src/platform/puter/filesystem.js index ca60e2fe..3ce632ec 100644 --- a/packages/phoenix/src/platform/puter/filesystem.js +++ b/packages/phoenix/src/platform/puter/filesystem.js @@ -16,107 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { ErrorCodes, PosixError } from '../PosixError.js'; - -function convertPuterError(e) { - // Handle Puter SDK errors - switch (e.code) { - case 'item_with_same_name_exists': return new PosixError(ErrorCodes.EEXIST, e.message); - case 'cannot_move_item_into_itself': return new PosixError(ErrorCodes.EPERM, e.message); - case 'cannot_copy_item_into_itself': return new PosixError(ErrorCodes.EPERM, e.message); - case 'cannot_move_to_root': return new PosixError(ErrorCodes.EACCES, e.message); - case 'cannot_copy_to_root': return new PosixError(ErrorCodes.EACCES, e.message); - case 'cannot_write_to_root': return new PosixError(ErrorCodes.EACCES, e.message); - case 'cannot_overwrite_a_directory': return new PosixError(ErrorCodes.EPERM, e.message); - case 'cannot_read_a_directory': return new PosixError(ErrorCodes.EISDIR, e.message); - case 'source_and_dest_are_the_same': return new PosixError(ErrorCodes.EPERM, e.message); - case 'dest_is_not_a_directory': return new PosixError(ErrorCodes.ENOTDIR, e.message); - case 'dest_does_not_exist': return new PosixError(ErrorCodes.ENOENT, e.message); - case 'source_does_not_exist': return new PosixError(ErrorCodes.ENOENT, e.message); - case 'subject_does_not_exist': return new PosixError(ErrorCodes.ENOENT, e.message); - case 'shortcut_target_not_found': return new PosixError(ErrorCodes.ENOENT, e.message); - case 'shortcut_target_is_a_directory': return new PosixError(ErrorCodes.EISDIR, e.message); - case 'shortcut_target_is_a_file': return new PosixError(ErrorCodes.ENOTDIR, e.message); - case 'forbidden': return new PosixError(ErrorCodes.EPERM, e.message); - case 'immutable': return new PosixError(ErrorCodes.EACCES, e.message); - case 'field_empty': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'field_missing': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'xor_field_missing': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'field_only_valid_with_other_field': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'invalid_id': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'field_invalid': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'field_immutable': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'field_too_long': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'field_too_short': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'already_in_use': return new PosixError(ErrorCodes.EINVAL, e.message); // Not sure what this one is - case 'invalid_file_name': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'storage_limit_reached': return new PosixError(ErrorCodes.ENOSPC, e.message); - case 'internal_error': return new PosixError(ErrorCodes.ECONNRESET, e.message); // This isn't quite right - case 'response_timeout': return new PosixError(ErrorCodes.ETIMEDOUT, e.message); - case 'file_too_large': return new PosixError(ErrorCodes.EFBIG, e.message); - case 'thumbnail_too_large': return new PosixError(ErrorCodes.EFBIG, e.message); - case 'upload_failed': return new PosixError(ErrorCodes.ECONNRESET, e.message); // This isn't quite right - case 'missing_expected_metadata': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'overwrite_and_dedupe_exclusive': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'not_empty': return new PosixError(ErrorCodes.ENOTEMPTY, e.message); - - // Write - case 'offset_without_existing_file': return new PosixError(ErrorCodes.ENOENT, e.message); - case 'offset_requires_overwrite': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'offset_requires_stream': return new PosixError(ErrorCodes.EPERM, e.message); - - // Batch - case 'batch_too_many_files': return new PosixError(ErrorCodes.EINVAL, e.message); - case 'batch_missing_file': return new PosixError(ErrorCodes.EINVAL, e.message); - - // Open - case 'no_suitable_app': break; - case 'app_does_not_exist': break; - - // Apps - case 'app_name_already_in_use': break; - - // Subdomains - case 'subdomain_limit_reached': break; - case 'subdomain_reserved': break; - - // Users - case 'email_already_in_use': break; - case 'username_already_in_use': break; - case 'too_many_username_changes': break; - case 'token_invalid': break; - - // drivers - case 'interface_not_found': break; - case 'no_implementation_available': break; - case 'method_not_found': break; - case 'missing_required_argument': break; - case 'argument_consolidation_failed': break; - - // SLA - case 'rate_limit_exceeded': break; - case 'monthly_limit_exceeded': break; - case 'server_rate_exceeded': break; - - // auth - case 'token_missing': break; - case 'token_auth_failed': break; - case 'token_unsupported': break; - case 'account_suspended': break; - case 'permission_denied': break; - case 'access_token_empty_permissions': break; - - // Object Mapping - case 'field_not_allowed_for_create': break; - case 'field_required_for_update': break; - case 'entity_not_found': break; - - // Chat - case 'max_tokens_exceeded': break; - } - // Some other kind of error - return e; -} +import { ErrorCodes, PosixError } from '@heyputer/puter-js-common/src/PosixError.js'; // DRY: Almost the same as node/filesystem.js function wrapAPIs(apis) { @@ -129,7 +29,7 @@ function wrapAPIs(apis) { try { return await original(...args); } catch (e) { - throw convertPuterError(e); + throw PosixError.fromPuterAPIError(e); } }; } diff --git a/packages/phoenix/src/puter-shell/coreutils/errno.js b/packages/phoenix/src/puter-shell/coreutils/errno.js index dd36d8dd..db74b2f6 100644 --- a/packages/phoenix/src/puter-shell/coreutils/errno.js +++ b/packages/phoenix/src/puter-shell/coreutils/errno.js @@ -16,7 +16,7 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -import { ErrorCodes, ErrorMetadata, errorFromIntegerCode } from '../../platform/PosixError.js'; +import { ErrorCodes, ErrorMetadata, errorFromIntegerCode } from '@heyputer/puter-js-common/src/PosixError.js'; import { Exit } from './coreutil_lib/exit.js'; const maxErrorNameLength = Object.keys(ErrorCodes) diff --git a/packages/phoenix/src/puter-shell/coreutils/touch.js b/packages/phoenix/src/puter-shell/coreutils/touch.js index 8f15f13c..5b32067a 100644 --- a/packages/phoenix/src/puter-shell/coreutils/touch.js +++ b/packages/phoenix/src/puter-shell/coreutils/touch.js @@ -18,7 +18,7 @@ */ import { Exit } from './coreutil_lib/exit.js'; import { resolveRelativePath } from '../../util/path.js'; -import { ErrorCodes } from '../../platform/PosixError.js'; +import { ErrorCodes } from '@heyputer/puter-js-common/src/PosixError.js'; export default { name: 'touch', diff --git a/packages/phoenix/src/puter-shell/providers/PathCommandProvider.js b/packages/phoenix/src/puter-shell/providers/PathCommandProvider.js index 7fd4eb7d..6b55a044 100644 --- a/packages/phoenix/src/puter-shell/providers/PathCommandProvider.js +++ b/packages/phoenix/src/puter-shell/providers/PathCommandProvider.js @@ -83,7 +83,6 @@ function spawn_process(ctx, executablePath) { // Repeatedly copy data from stdin to the child, while it's running. let data, done; const next_data = async () => { - // FIXME: This waits for one more read() after we finish. ({ value: data, done } = await Promise.race([ exit_promise, sigint_promise, ctx.externs.in_.read(), ])); @@ -135,7 +134,6 @@ function spawn_pty(ctx, executablePath) { // Repeatedly copy data from stdin to the child, while it's running. let data, done; const next_data = async () => { - // FIXME: This waits for one more read() after we finish. ({ value: data, done } = await Promise.race([ exit_promise, sigint_promise, ctx.externs.in_.read(), ])); diff --git a/packages/phoenix/src/puter-shell/providers/PuterAppCommandProvider.js b/packages/phoenix/src/puter-shell/providers/PuterAppCommandProvider.js index 2467d780..2c9e862b 100644 --- a/packages/phoenix/src/puter-shell/providers/PuterAppCommandProvider.js +++ b/packages/phoenix/src/puter-shell/providers/PuterAppCommandProvider.js @@ -50,9 +50,14 @@ export class PuterAppCommandProvider { return { name: id, path: path ?? 'Built-in Puter app', - // TODO: Parameters and options? + // TODO: Let apps expose option/positional definitions like builtins do, and parse them here? async execute(ctx) { - const args = {}; // TODO: Passed-in parameters and options would go here + const args = { + command_line: { + args: ctx.locals.args, + }, + env: {...ctx.env}, + }; const child = await puter.ui.launchApp(id, args); // Wait for app to close. @@ -86,7 +91,6 @@ export class PuterAppCommandProvider { // DRY: Initially copied from PathCommandProvider let data, done; const next_data = async () => { - // FIXME: This waits for one more read() after we finish. ({ value: data, done } = await Promise.race([ app_close_promise, sigint_promise, ctx.externs.in_.read(), ])); diff --git a/packages/phoenix/test/coreutils/errno.js b/packages/phoenix/test/coreutils/errno.js index fbb63b53..0191125b 100644 --- a/packages/phoenix/test/coreutils/errno.js +++ b/packages/phoenix/test/coreutils/errno.js @@ -19,7 +19,7 @@ import assert from 'assert'; import { MakeTestContext } from './harness.js' import builtins from '../../src/puter-shell/coreutils/__exports__.js'; -import { ErrorCodes, ErrorMetadata } from '../../src/platform/PosixError.js'; +import { ErrorCodes, ErrorMetadata } from '@heyputer/puter-js-common/src/PosixError.js'; export const runErrnoTests = () => { describe('errno', function () { @@ -100,7 +100,8 @@ export const runErrnoTests = () => { 'EADDRINUSE 98 Address already in use\n' + 'ECONNRESET 104 Connection reset\n' + 'ETIMEDOUT 110 Connection timed out\n' + - 'ECONNREFUSED 111 Connection refused\n', + 'ECONNREFUSED 111 Connection refused\n' + + 'EUNKNOWN -1 Unknown error\n', expectedStderr: '', expectedFail: false, }, diff --git a/packages/puter-js-common/src/PosixError.js b/packages/puter-js-common/src/PosixError.js new file mode 100644 index 00000000..7b94d837 --- /dev/null +++ b/packages/puter-js-common/src/PosixError.js @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2024 Puter Technologies Inc. + * + * This file is part of Puter. + * + * Puter is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +const ErrorCodes = { + EACCES: Symbol.for('EACCES'), + EADDRINUSE: Symbol.for('EADDRINUSE'), + ECONNREFUSED: Symbol.for('ECONNREFUSED'), + ECONNRESET: Symbol.for('ECONNRESET'), + EEXIST: Symbol.for('EEXIST'), + EFBIG: Symbol.for('EFBIG'), + EINVAL: Symbol.for('EINVAL'), + EIO: Symbol.for('EIO'), + EISDIR: Symbol.for('EISDIR'), + EMFILE: Symbol.for('EMFILE'), + ENOENT: Symbol.for('ENOENT'), + ENOSPC: Symbol.for('ENOSPC'), + ENOTDIR: Symbol.for('ENOTDIR'), + ENOTEMPTY: Symbol.for('ENOTEMPTY'), + EPERM: Symbol.for('EPERM'), + EPIPE: Symbol.for('EPIPE'), + ETIMEDOUT: Symbol.for('ETIMEDOUT'), + + // For when we need to convert errors that we don't recognise + EUNKNOWN: Symbol.for('EUNKNOWN'), +}; + +// Codes taken from `errno` on Linux. +const ErrorMetadata = new Map([ + [ErrorCodes.EPERM, { code: 1, description: 'Operation not permitted' }], + [ErrorCodes.ENOENT, { code: 2, description: 'File or directory not found' }], + [ErrorCodes.EIO, { code: 5, description: 'IO error' }], + [ErrorCodes.EACCES, { code: 13, description: 'Permission denied' }], + [ErrorCodes.EEXIST, { code: 17, description: 'File already exists' }], + [ErrorCodes.ENOTDIR, { code: 20, description: 'Is not a directory' }], + [ErrorCodes.EISDIR, { code: 21, description: 'Is a directory' }], + [ErrorCodes.EINVAL, { code: 22, description: 'Argument invalid' }], + [ErrorCodes.EMFILE, { code: 24, description: 'Too many open files' }], + [ErrorCodes.EFBIG, { code: 27, description: 'File too big' }], + [ErrorCodes.ENOSPC, { code: 28, description: 'Device out of space' }], + [ErrorCodes.EPIPE, { code: 32, description: 'Pipe broken' }], + [ErrorCodes.ENOTEMPTY, { code: 39, description: 'Directory is not empty' }], + [ErrorCodes.EADDRINUSE, { code: 98, description: 'Address already in use' }], + [ErrorCodes.ECONNRESET, { code: 104, description: 'Connection reset'}], + [ErrorCodes.ETIMEDOUT, { code: 110, description: 'Connection timed out' }], + [ErrorCodes.ECONNREFUSED, { code: 111, description: 'Connection refused' }], + + [ErrorCodes.EUNKNOWN, { code: -1, description: 'Unknown error' }], +]); + +const errorFromIntegerCode = (code) => { + for (const [errorCode, metadata] of ErrorMetadata) { + if (metadata.code === code) { + return errorCode; + } + } + return undefined; +}; + +class PosixError extends Error { + // posixErrorCode can be either a string, or one of the ErrorCodes above. + // If message is undefined, a default message will be used. + constructor(posixErrorCode, message) { + let posixCode; + if (typeof posixErrorCode === 'symbol') { + if (ErrorCodes[Symbol.keyFor(posixErrorCode)] !== posixErrorCode) { + throw new Error(`Unrecognized POSIX error code: '${posixErrorCode}'`); + } + posixCode = posixErrorCode; + } else { + const code = ErrorCodes[posixErrorCode]; + if (!code) throw new Error(`Unrecognized POSIX error code: '${posixErrorCode}'`); + posixCode = code; + } + + super(message ?? ErrorMetadata.get(posixCode).description); + this.posixCode = posixCode; + this.code = posixCode.description; + } + + static fromNodeJSError(e) { + switch (e.code) { + case 'EACCES': return new PosixError(ErrorCodes.EACCES, e.message); + case 'EADDRINUSE': return new PosixError(ErrorCodes.EADDRINUSE, e.message); + case 'ECONNREFUSED': return new PosixError(ErrorCodes.ECONNREFUSED, e.message); + case 'ECONNRESET': return new PosixError(ErrorCodes.ECONNRESET, e.message); + case 'EEXIST': return new PosixError(ErrorCodes.EEXIST, e.message); + case 'EIO': return new PosixError(ErrorCodes.EIO, e.message); + case 'EISDIR': return new PosixError(ErrorCodes.EISDIR, e.message); + case 'EMFILE': return new PosixError(ErrorCodes.EMFILE, e.message); + case 'ENOENT': return new PosixError(ErrorCodes.ENOENT, e.message); + case 'ENOTDIR': return new PosixError(ErrorCodes.ENOTDIR, e.message); + case 'ENOTEMPTY': return new PosixError(ErrorCodes.ENOTEMPTY, e.message); + // ENOTFOUND is Node-specific. ECONNREFUSED is similar enough. + case 'ENOTFOUND': return new PosixError(ErrorCodes.ECONNREFUSED, e.message); + case 'EPERM': return new PosixError(ErrorCodes.EPERM, e.message); + case 'EPIPE': return new PosixError(ErrorCodes.EPIPE, e.message); + case 'ETIMEDOUT': return new PosixError(ErrorCodes.ETIMEDOUT, e.message); + } + // Some other kind of error + return new PosixError(ErrorCodes.EUNKNOWN, e.message); + } + + static fromPuterAPIError(e) { + // Handle Puter SDK errors + switch (e.code) { + case 'item_with_same_name_exists': return new PosixError(ErrorCodes.EEXIST, e.message); + case 'cannot_move_item_into_itself': return new PosixError(ErrorCodes.EPERM, e.message); + case 'cannot_copy_item_into_itself': return new PosixError(ErrorCodes.EPERM, e.message); + case 'cannot_move_to_root': return new PosixError(ErrorCodes.EACCES, e.message); + case 'cannot_copy_to_root': return new PosixError(ErrorCodes.EACCES, e.message); + case 'cannot_write_to_root': return new PosixError(ErrorCodes.EACCES, e.message); + case 'cannot_overwrite_a_directory': return new PosixError(ErrorCodes.EPERM, e.message); + case 'cannot_read_a_directory': return new PosixError(ErrorCodes.EISDIR, e.message); + case 'source_and_dest_are_the_same': return new PosixError(ErrorCodes.EPERM, e.message); + case 'dest_is_not_a_directory': return new PosixError(ErrorCodes.ENOTDIR, e.message); + case 'dest_does_not_exist': return new PosixError(ErrorCodes.ENOENT, e.message); + case 'source_does_not_exist': return new PosixError(ErrorCodes.ENOENT, e.message); + case 'subject_does_not_exist': return new PosixError(ErrorCodes.ENOENT, e.message); + case 'shortcut_target_not_found': return new PosixError(ErrorCodes.ENOENT, e.message); + case 'shortcut_target_is_a_directory': return new PosixError(ErrorCodes.EISDIR, e.message); + case 'shortcut_target_is_a_file': return new PosixError(ErrorCodes.ENOTDIR, e.message); + case 'forbidden': return new PosixError(ErrorCodes.EPERM, e.message); + case 'immutable': return new PosixError(ErrorCodes.EACCES, e.message); + case 'field_empty': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'field_missing': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'xor_field_missing': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'field_only_valid_with_other_field': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'invalid_id': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'field_invalid': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'field_immutable': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'field_too_long': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'field_too_short': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'already_in_use': return new PosixError(ErrorCodes.EINVAL, e.message); // Not sure what this one is + case 'invalid_file_name': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'storage_limit_reached': return new PosixError(ErrorCodes.ENOSPC, e.message); + case 'internal_error': return new PosixError(ErrorCodes.ECONNRESET, e.message); // This isn't quite right + case 'response_timeout': return new PosixError(ErrorCodes.ETIMEDOUT, e.message); + case 'file_too_large': return new PosixError(ErrorCodes.EFBIG, e.message); + case 'thumbnail_too_large': return new PosixError(ErrorCodes.EFBIG, e.message); + case 'upload_failed': return new PosixError(ErrorCodes.ECONNRESET, e.message); // This isn't quite right + case 'missing_expected_metadata': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'overwrite_and_dedupe_exclusive': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'not_empty': return new PosixError(ErrorCodes.ENOTEMPTY, e.message); + + // Write + case 'offset_without_existing_file': return new PosixError(ErrorCodes.ENOENT, e.message); + case 'offset_requires_overwrite': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'offset_requires_stream': return new PosixError(ErrorCodes.EPERM, e.message); + + // Batch + case 'batch_too_many_files': return new PosixError(ErrorCodes.EINVAL, e.message); + case 'batch_missing_file': return new PosixError(ErrorCodes.EINVAL, e.message); + + // TODO: Associate more of these with posix error codes + + // Open + case 'no_suitable_app': break; + case 'app_does_not_exist': break; + + // Apps + case 'app_name_already_in_use': break; + + // Subdomains + case 'subdomain_limit_reached': break; + case 'subdomain_reserved': break; + + // Users + case 'email_already_in_use': break; + case 'username_already_in_use': break; + case 'too_many_username_changes': break; + case 'token_invalid': break; + + // drivers + case 'interface_not_found': break; + case 'no_implementation_available': break; + case 'method_not_found': break; + case 'missing_required_argument': break; + case 'argument_consolidation_failed': break; + + // SLA + case 'rate_limit_exceeded': break; + case 'monthly_limit_exceeded': break; + case 'server_rate_exceeded': break; + + // auth + case 'token_missing': break; + case 'token_auth_failed': break; + case 'token_unsupported': break; + case 'account_suspended': break; + case 'permission_denied': break; + case 'access_token_empty_permissions': break; + + // Object Mapping + case 'field_not_allowed_for_create': break; + case 'field_required_for_update': break; + case 'entity_not_found': break; + + // Chat + case 'max_tokens_exceeded': break; + } + // Some other kind of error + return new PosixError(ErrorCodes.EUNKNOWN, e.message); + } + + // + // Helpers for constructing a PosixError when you don't already have an error message. + // + static AccessNotPermitted({ message, path } = {}) { + return new PosixError(ErrorCodes.EACCES, message ?? (path ? `Access not permitted to: '${path}'` : undefined)); + } + static AddressInUse({ message, address } = {}) { + return new PosixError(ErrorCodes.EADDRINUSE, message ?? (address ? `Address '${address}' in use` : undefined)); + } + static ConnectionRefused({ message } = {}) { + return new PosixError(ErrorCodes.ECONNREFUSED, message); + } + static ConnectionReset({ message } = {}) { + return new PosixError(ErrorCodes.ECONNRESET, message); + } + static PathAlreadyExists({ message, path } = {}) { + return new PosixError(ErrorCodes.EEXIST, message ?? (path ? `Path already exists: '${path}'` : undefined)); + } + static FileTooLarge({ message } = {}) { + return new PosixError(ErrorCodes.EFBIG, message); + } + static InvalidArgument({ message } = {}) { + return new PosixError(ErrorCodes.EINVAL, message); + } + static IO({ message } = {}) { + return new PosixError(ErrorCodes.EIO, message); + } + static IsDirectory({ message, path } = {}) { + return new PosixError(ErrorCodes.EISDIR, message ?? (path ? `Path is directory: '${path}'` : undefined)); + } + static TooManyOpenFiles({ message } = {}) { + return new PosixError(ErrorCodes.EMFILE, message); + } + static DoesNotExist({ message, path } = {}) { + return new PosixError(ErrorCodes.ENOENT, message ?? (path ? `Path not found: '${path}'` : undefined)); + } + static NotEnoughSpace({ message } = {}) { + return new PosixError(ErrorCodes.ENOSPC, message); + } + static IsNotDirectory({ message, path } = {}) { + return new PosixError(ErrorCodes.ENOTDIR, message ?? (path ? `Path is not a directory: '${path}'` : undefined)); + } + static DirectoryIsNotEmpty({ message, path } = {}) { + return new PosixError(ErrorCodes.ENOTEMPTY, message ?? (path ?`Directory is not empty: '${path}'` : undefined)); + } + static OperationNotPermitted({ message } = {}) { + return new PosixError(ErrorCodes.EPERM, message); + } + static BrokenPipe({ message } = {}) { + return new PosixError(ErrorCodes.EPIPE, message); + } + static TimedOut({ message } = {}) { + return new PosixError(ErrorCodes.ETIMEDOUT, message); + } +} + +module.exports = { + ErrorCodes, + ErrorMetadata, + errorFromIntegerCode, + PosixError, +}