mirror of
https://github.com/HeyPuter/puter
synced 2024-11-15 06:15:47 +00:00
Merge pull request #421 from AtkinsSJ/exit-status
Add exit status codes to `puter.exit()`, and an `exit` builtin to Phoenix
This commit is contained in:
commit
d57980c6cb
@ -160,6 +160,7 @@ export class ANSIShell extends EventTarget {
|
||||
}
|
||||
this.ctx.externs.out.write('error: ' + e.message + '\n');
|
||||
console.log(e);
|
||||
this.ctx.locals.exit = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -225,6 +226,13 @@ export class ANSIShell extends EventTarget {
|
||||
const pipeline = await Pipeline.createFromAST(executionCtx, ast);
|
||||
|
||||
await pipeline.execute(executionCtx);
|
||||
|
||||
// Store exit code for the next pipeline
|
||||
// TODO: This feels like a hacky way of doing this.
|
||||
this.ctx.locals.exit = executionCtx.locals.exit;
|
||||
if ( this.ctx.locals.exit ) {
|
||||
this.ctx.externs.out.write(`Exited with code ${this.ctx.locals.exit}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
expandPromptString (str) {
|
||||
|
@ -247,14 +247,14 @@ export class PreparedCommand {
|
||||
if ( ! ctx.cmdExecState.valid ) {
|
||||
ctx.locals.exit = -1;
|
||||
await ctx.externs.out.close();
|
||||
return;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ( ctx.cmdExecState.printHelpAndExit ) {
|
||||
ctx.locals.exit = 0;
|
||||
await printUsage(command, ctx.externs.out, ctx.vars);
|
||||
await ctx.externs.out.close();
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
let execute = command.execute.bind(command);
|
||||
@ -291,13 +291,14 @@ export class PreparedCommand {
|
||||
command.name + ': ' +
|
||||
e.message + '\x1B[0m\n'
|
||||
);
|
||||
exit_code = -1;
|
||||
} else {
|
||||
await ctx.externs.err.write(
|
||||
'\x1B[31;1m' +
|
||||
command.name + ': ' +
|
||||
e.toString() + '\x1B[0m\n'
|
||||
);
|
||||
ctx.locals.exit = -1;
|
||||
exit_code = -1;
|
||||
}
|
||||
if ( ! (e instanceof Exit) ) console.error(e);
|
||||
}
|
||||
@ -316,6 +317,8 @@ export class PreparedCommand {
|
||||
|
||||
await filesystem.write(path, outputMemWriters[i].getAsBlob());
|
||||
}
|
||||
|
||||
return exit_code;
|
||||
}
|
||||
}
|
||||
|
||||
@ -348,7 +351,7 @@ export class Pipeline {
|
||||
const valve = new Coupler(nextIn, pipeline_input_pipe.in);
|
||||
nextIn = pipeline_input_pipe.out;
|
||||
|
||||
// TOOD: this will eventually defer piping of certain
|
||||
// TODO: this will eventually defer piping of certain
|
||||
// sub-pipelines to the Puter Shell.
|
||||
|
||||
for ( let i=0 ; i < preparedCommands.length ; i++ ) {
|
||||
@ -381,7 +384,9 @@ export class Pipeline {
|
||||
const command = preparedCommands[i];
|
||||
commandPromises.push(command.execute());
|
||||
}
|
||||
await Promise.all(commandPromises);
|
||||
const results = await Promise.all(commandPromises);
|
||||
// TODO: Consider what to do about intermediate exit codes
|
||||
ctx.locals.exit = results[results.length-1];
|
||||
await coupler.isDone;
|
||||
|
||||
valve.close();
|
||||
|
@ -21,6 +21,7 @@ import { launchPuterShell } from './puter-shell/main.js';
|
||||
import { NodeStdioPTT } from './pty/NodeStdioPTT.js';
|
||||
import { CreateFilesystemProvider } from './platform/node/filesystem.js';
|
||||
import { CreateEnvProvider } from './platform/node/env.js';
|
||||
import { CreateSystemProvider } from './platform/node/system.js';
|
||||
import { parseArgs } from '@pkgjs/parseargs';
|
||||
import capcon from 'capture-console';
|
||||
import fs from 'fs';
|
||||
@ -64,6 +65,7 @@ const ctx = new Context({
|
||||
name: 'node',
|
||||
filesystem: CreateFilesystemProvider(),
|
||||
env: CreateEnvProvider(),
|
||||
system: CreateSystemProvider(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
@ -22,6 +22,7 @@ import { CreateFilesystemProvider } from './platform/puter/filesystem.js';
|
||||
import { CreateDriversProvider } from './platform/puter/drivers.js';
|
||||
import { XDocumentPTT } from './pty/XDocumentPTT.js';
|
||||
import { CreateEnvProvider } from './platform/puter/env.js';
|
||||
import { CreateSystemProvider } from './platform/puter/system.js';
|
||||
|
||||
window.main_shell = async () => {
|
||||
const config = {};
|
||||
@ -73,6 +74,7 @@ window.main_shell = async () => {
|
||||
filesystem: CreateFilesystemProvider({ puterSDK }),
|
||||
drivers: CreateDriversProvider({ puterSDK }),
|
||||
env: CreateEnvProvider({ config }),
|
||||
system: CreateSystemProvider({ puterSDK })
|
||||
}),
|
||||
}));
|
||||
};
|
||||
|
9
packages/phoenix/src/platform/node/system.js
Normal file
9
packages/phoenix/src/platform/node/system.js
Normal file
@ -0,0 +1,9 @@
|
||||
import process from 'node:process';
|
||||
|
||||
export const CreateSystemProvider = () => {
|
||||
return {
|
||||
exit: (code) => {
|
||||
process.exit(code);
|
||||
},
|
||||
}
|
||||
}
|
7
packages/phoenix/src/platform/puter/system.js
Normal file
7
packages/phoenix/src/platform/puter/system.js
Normal file
@ -0,0 +1,7 @@
|
||||
export const CreateSystemProvider = ({ puterSDK }) => {
|
||||
return {
|
||||
exit: (code) => {
|
||||
puterSDK.exit(code);
|
||||
},
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ import module_dirname from './dirname.js'
|
||||
import module_echo from './echo.js'
|
||||
import module_env from './env.js'
|
||||
import module_errno from './errno.js'
|
||||
import module_exit from './exit.js'
|
||||
import module_false from './false.js'
|
||||
import module_grep from './grep.js'
|
||||
import module_head from './head.js'
|
||||
@ -75,6 +76,7 @@ export default {
|
||||
"echo": module_echo,
|
||||
"env": module_env,
|
||||
"errno": module_errno,
|
||||
"exit": module_exit,
|
||||
"false": module_false,
|
||||
"grep": module_grep,
|
||||
"head": module_head,
|
||||
|
48
packages/phoenix/src/puter-shell/coreutils/exit.js
Normal file
48
packages/phoenix/src/puter-shell/coreutils/exit.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
import { Exit } from './coreutil_lib/exit.js';
|
||||
|
||||
export default {
|
||||
name: 'exit',
|
||||
usage: 'exit [CODE]',
|
||||
description: 'Exit the shell and return the given CODE. If no argument is given, uses the most recent return code.',
|
||||
args: {
|
||||
$: 'simple-parser',
|
||||
allowPositionals: true
|
||||
},
|
||||
execute: async ctx => {
|
||||
const { positionals, exit } = ctx.locals;
|
||||
|
||||
let status_code = 0;
|
||||
|
||||
if (positionals.length === 0) {
|
||||
status_code = exit;
|
||||
} else if (positionals.length === 1) {
|
||||
const maybe_number = Number(positionals[0]);
|
||||
if (Number.isInteger(maybe_number)) {
|
||||
status_code = maybe_number;
|
||||
}
|
||||
} else {
|
||||
await ctx.externs.err.write('exit: Too many arguments');
|
||||
throw new Exit(1);
|
||||
}
|
||||
|
||||
ctx.platform.system.exit(status_code);
|
||||
}
|
||||
};
|
@ -62,9 +62,12 @@ export class PuterAppCommandProvider {
|
||||
|
||||
// Wait for app to close.
|
||||
const app_close_promise = new Promise((resolve, reject) => {
|
||||
child.on('close', () => {
|
||||
// TODO: Exit codes for apps
|
||||
child.on('close', (data) => {
|
||||
if ((data.statusCode ?? 0) != 0) {
|
||||
reject(new Exit(data.statusCode));
|
||||
} else {
|
||||
resolve({ done: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -262,10 +262,16 @@ window.puter = (function() {
|
||||
this.updateSubmodules();
|
||||
}
|
||||
|
||||
exit = function() {
|
||||
exit = function(statusCode = 0) {
|
||||
if (statusCode && (typeof statusCode !== 'number')) {
|
||||
console.warn('puter.exit() requires status code to be a number. Treating it as 1');
|
||||
statusCode = 1;
|
||||
}
|
||||
|
||||
window.parent.postMessage({
|
||||
msg: "exit",
|
||||
appInstanceID: this.appInstanceID,
|
||||
statusCode,
|
||||
}, '*');
|
||||
}
|
||||
|
||||
|
@ -54,6 +54,7 @@ class AppConnection extends EventListener {
|
||||
this.#isOpen = false;
|
||||
this.emit('close', {
|
||||
appInstanceID: this.targetAppInstanceID,
|
||||
statusCode: event.data.statusCode,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
11
src/IPC.js
11
src/IPC.js
@ -1201,6 +1201,15 @@ window.addEventListener('message', async (event) => {
|
||||
// exit
|
||||
//--------------------------------------------------------
|
||||
else if(event.data.msg === 'exit'){
|
||||
$(window.window_for_app_instance(event.data.appInstanceID)).close({bypass_iframe_messaging: true});
|
||||
// Ensure status code is a number. Convert any truthy non-numbers to 1.
|
||||
let status_code = event.data.statusCode ?? 0;
|
||||
if (status_code && (typeof status_code !== 'number')) {
|
||||
status_code = 1;
|
||||
}
|
||||
|
||||
$(window.window_for_app_instance(event.data.appInstanceID)).close({
|
||||
bypass_iframe_messaging: true,
|
||||
status_code,
|
||||
});
|
||||
}
|
||||
});
|
@ -2887,7 +2887,7 @@ $.fn.close = async function(options) {
|
||||
$(`.window[data-parent_uuid="${window_uuid}"]`).close();
|
||||
|
||||
// notify other apps that we're closing
|
||||
window.report_app_closed(window_uuid);
|
||||
window.report_app_closed(window_uuid, options.status_code ?? 0);
|
||||
|
||||
// remove backdrop
|
||||
$(this).closest('.window-backdrop').remove();
|
||||
|
@ -3511,7 +3511,7 @@ window.report_app_launched = (instance_id, { uses_sdk = true }) => {
|
||||
};
|
||||
|
||||
// Run any callbacks to say that the app has closed
|
||||
window.report_app_closed = (instance_id) => {
|
||||
window.report_app_closed = (instance_id, status_code) => {
|
||||
const el_window = window.window_for_app_instance(instance_id);
|
||||
|
||||
// notify parent app, if we have one, that we're closing
|
||||
@ -3521,6 +3521,7 @@ window.report_app_closed = (instance_id) => {
|
||||
parent.contentWindow.postMessage({
|
||||
msg: 'appClosed',
|
||||
appInstanceID: instance_id,
|
||||
statusCode: status_code ?? 0,
|
||||
}, '*');
|
||||
}
|
||||
|
||||
@ -3530,6 +3531,7 @@ window.report_app_closed = (instance_id) => {
|
||||
child.contentWindow.postMessage({
|
||||
msg: 'appClosed',
|
||||
appInstanceID: instance_id,
|
||||
statusCode: status_code ?? 0,
|
||||
}, '*');
|
||||
});
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user