@fungi.computer/shed 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,4 @@
1
+ import { resultRows } from "@fungi.computer/agar/method-presentation";
1
2
  import { processesGill } from "./gills.js";
2
3
  import {
3
4
  processPageInputSchema,
@@ -68,6 +69,23 @@ const bytes = z.strictObject({
68
69
  encoding: z.literal("base64"),
69
70
  truncated: z.boolean(),
70
71
  });
72
+ /** The script's command lines, without blanks and comments. */
73
+ const commandLines = (command: string) =>
74
+ command
75
+ .split("\n")
76
+ .map((line) => line.trim())
77
+ .filter((line) => line.length > 0 && !line.startsWith("#"));
78
+ /**
79
+ * A script reads by its last command: a script usually opens with setup
80
+ * (`set -eu`, `cd`) and ends with what it is for, such as a push.
81
+ */
82
+ const lastCommand = (command: string) =>
83
+ commandLines(command).at(-1)?.slice(0, 80) ?? "a command";
84
+ const scriptLength = (command: string) => {
85
+ const lines = commandLines(command).length;
86
+ return lines > 1 ? [`${String(lines)} commands`] : [];
87
+ };
88
+
71
89
  export const appBackend = defineAppBackend<ShedMethodContext>({
72
90
  gills: [processesGill],
73
91
  methods: {
@@ -202,6 +220,29 @@ export const appBackend = defineAppBackend<ShedMethodContext>({
202
220
  }),
203
221
  refusal,
204
222
  ]),
223
+ present: {
224
+ rows: resultRows,
225
+ verb: "ran",
226
+ row: ({ input, output }) =>
227
+ "error" in output
228
+ ? {
229
+ id: "refused",
230
+ title: "Shed could not run the command",
231
+ detail: output.error,
232
+ tone: "error",
233
+ }
234
+ : {
235
+ id: output.logPath,
236
+ title: `Shed ran ${lastCommand(input.command)}`,
237
+ detail: [
238
+ output.exitCode === 0
239
+ ? "Finished"
240
+ : `Exited with code ${String(output.exitCode)}`,
241
+ ...scriptLength(input.command),
242
+ ].join(" · "),
243
+ tone: output.exitCode === 0 ? "settled" : "error",
244
+ },
245
+ },
205
246
  handler: (input, context: ShedMethodContext, caller) => {
206
247
  const computer = context.env.COMPUTER;
207
248
  if (
@@ -0,0 +1,40 @@
1
+ import { expect, it } from "vitest";
2
+ import { appBackend } from "../methods.js";
3
+
4
+ const present = (command: string, exitCode: number) =>
5
+ appBackend.methods.run?.present?.(
6
+ { command },
7
+ { exitCode, logPath: "/logs/1" },
8
+ );
9
+
10
+ it("reads a finished command as a settled row and a failed one as an error row", () => {
11
+ expect(present("git push origin main", 0)).toEqual({
12
+ id: "/logs/1",
13
+ title: "Shed ran git push origin main",
14
+ detail: "Finished",
15
+ tone: "settled",
16
+ });
17
+ expect(present("git push origin main", 2)).toMatchObject({
18
+ detail: "Exited with code 2",
19
+ tone: "error",
20
+ });
21
+ });
22
+
23
+ it("names a script by its last command, so two scripts with the same setup read apart", () => {
24
+ const clone = present(
25
+ "set -eu\ngit init -q '/work/repo'\n\n# fetch the branch\ngit checkout -q main",
26
+ 0,
27
+ );
28
+ const push = present(
29
+ "set -eu\ncd '/work/repo'\ngit add 'board'\ngit push origin 'HEAD:refs/heads/main'",
30
+ 0,
31
+ );
32
+ expect(clone).toMatchObject({
33
+ title: "Shed ran git checkout -q main",
34
+ detail: "Finished · 3 commands",
35
+ });
36
+ expect(push).toMatchObject({
37
+ title: "Shed ran git push origin 'HEAD:refs/heads/main'",
38
+ detail: "Finished · 4 commands",
39
+ });
40
+ });