diff --git a/package-lock.json b/package-lock.json index 713fe8402..912ca559c 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", @@ -4547,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", @@ -5148,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", @@ -5499,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", @@ -5768,6 +5797,11 @@ "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", @@ -7226,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" } @@ -7615,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", @@ -8402,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", @@ -9303,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", @@ -9423,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", @@ -10280,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", @@ -11947,6 +12050,11 @@ "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", @@ -11956,6 +12064,48 @@ "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", @@ -11988,13 +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/@sinonjs/fake-timers": { "version": "11.2.2", "license": "BSD-3-Clause", @@ -12079,10 +12222,6 @@ "version": "3.0.9", "license": "MIT" }, - "packages/phoenix/node_modules/path-browserify": { - "version": "1.0.1", - "license": "MIT" - }, "packages/phoenix/node_modules/randomstring": { "version": "1.3.0", "license": "MIT", diff --git a/packages/git/package.json b/packages/git/package.json index 2e0f90e45..30cac607d 100644 --- a/packages/git/package.json +++ b/packages/git/package.json @@ -16,5 +16,10 @@ "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/src/git-command-definition.js b/packages/git/src/git-command-definition.js new file mode 100644 index 000000000..6ff37f0ab --- /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/help.js b/packages/git/src/help.js new file mode 100644 index 000000000..bd0b9c02a --- /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 index e871d539d..adb1f744b 100644 --- a/packages/git/src/main.js +++ b/packages/git/src/main.js @@ -16,6 +16,147 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -window.main = () => { - console.log('Well hello friends!'); -} \ No newline at end of file +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 000000000..1633f161f --- /dev/null +++ b/packages/git/src/subcommands/__exports__.js @@ -0,0 +1,26 @@ +/* + * 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_help from './help.js' +import module_version from './version.js' + +export default { + "help": module_help, + "version": module_version, +}; diff --git a/packages/git/src/subcommands/help.js b/packages/git/src/subcommands/help.js new file mode 100644 index 000000000..1a27f8315 --- /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/version.js b/packages/git/src/subcommands/version.js new file mode 100644 index 000000000..2e639f0d0 --- /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()})`); + } +}