mirror of
https://github.com/HeyPuter/puter.git
synced 2025-01-23 06:00:21 +08:00
Implement git help
, git version
, and subcommand infrastructure
Each subcommand is its own file, modeled after Phoenix's coreutils.
This commit is contained in:
parent
b75c42b39f
commit
85b7587c42
163
package-lock.json
generated
163
package-lock.json
generated
@ -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",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
39
packages/git/src/git-command-definition.js
Normal file
39
packages/git/src/git-command-definition.js
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
143
packages/git/src/help.js
Normal file
143
packages/git/src/help.js
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
@ -16,6 +16,147 @@
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
window.main = () => {
|
||||
console.log('Well hello friends!');
|
||||
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);
|
||||
}
|
26
packages/git/src/subcommands/__exports__.js
Normal file
26
packages/git/src/subcommands/__exports__.js
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
// 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,
|
||||
};
|
67
packages/git/src/subcommands/help.js
Normal file
67
packages/git/src/subcommands/help.js
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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 <command>'],
|
||||
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 <command>' 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));
|
||||
}
|
||||
}
|
40
packages/git/src/subcommands/version.js
Normal file
40
packages/git/src/subcommands/version.js
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
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()})`);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user