@pipelex/sdk 0.32.0 → 0.34.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.
Files changed (87) hide show
  1. package/README.md +40 -5
  2. package/dist/artifacts.js +23 -9
  3. package/dist/artifacts.js.map +1 -1
  4. package/dist/cli/args.d.ts +41 -0
  5. package/dist/cli/args.js +96 -0
  6. package/dist/cli/args.js.map +1 -0
  7. package/dist/cli/bundle.d.ts +45 -0
  8. package/dist/cli/bundle.js +148 -0
  9. package/dist/cli/bundle.js.map +1 -0
  10. package/dist/cli/environment.d.ts +36 -0
  11. package/dist/cli/environment.js +72 -0
  12. package/dist/cli/environment.js.map +1 -0
  13. package/dist/cli/help.d.ts +4 -0
  14. package/dist/cli/help.js +56 -0
  15. package/dist/cli/help.js.map +1 -0
  16. package/dist/cli/inputs.d.ts +13 -0
  17. package/dist/cli/inputs.js +70 -0
  18. package/dist/cli/inputs.js.map +1 -0
  19. package/dist/cli/io.d.ts +79 -0
  20. package/dist/cli/io.js +96 -0
  21. package/dist/cli/io.js.map +1 -0
  22. package/dist/cli/main.d.ts +18 -0
  23. package/dist/cli/main.js +58 -0
  24. package/dist/cli/main.js.map +1 -0
  25. package/dist/cli/method.d.ts +56 -0
  26. package/dist/cli/method.js +108 -0
  27. package/dist/cli/method.js.map +1 -0
  28. package/dist/cli/present.d.ts +43 -0
  29. package/dist/cli/present.js +191 -0
  30. package/dist/cli/present.js.map +1 -0
  31. package/dist/cli/run.d.ts +17 -0
  32. package/dist/cli/run.js +290 -0
  33. package/dist/cli/run.js.map +1 -0
  34. package/dist/cli/script.d.ts +23 -0
  35. package/dist/cli/script.js +212 -0
  36. package/dist/cli/script.js.map +1 -0
  37. package/dist/cli/source.d.ts +49 -0
  38. package/dist/cli/source.js +71 -0
  39. package/dist/cli/source.js.map +1 -0
  40. package/dist/cli.d.ts +10 -0
  41. package/dist/cli.js +65 -0
  42. package/dist/cli.js.map +1 -0
  43. package/dist/client.d.ts +125 -17
  44. package/dist/client.js +383 -109
  45. package/dist/client.js.map +1 -1
  46. package/dist/codegen-check.d.ts +9 -0
  47. package/dist/codegen-check.js +12 -6
  48. package/dist/codegen-check.js.map +1 -1
  49. package/dist/error-models.d.ts +60 -2
  50. package/dist/error-models.js +169 -3
  51. package/dist/error-models.js.map +1 -1
  52. package/dist/error-verdicts.d.ts +34 -0
  53. package/dist/error-verdicts.js +69 -0
  54. package/dist/error-verdicts.js.map +1 -0
  55. package/dist/errors.d.ts +250 -48
  56. package/dist/errors.js +330 -64
  57. package/dist/errors.js.map +1 -1
  58. package/dist/hooks/validate-client.d.ts +3 -3
  59. package/dist/hooks/validate-client.js +3 -3
  60. package/dist/index.d.ts +4 -4
  61. package/dist/index.js +3 -2
  62. package/dist/index.js.map +1 -1
  63. package/dist/prepare-inputs.d.ts +6 -0
  64. package/dist/prepare-inputs.js +46 -14
  65. package/dist/prepare-inputs.js.map +1 -1
  66. package/dist/runs.d.ts +28 -1
  67. package/dist/runs.js +2 -1
  68. package/dist/runs.js.map +1 -1
  69. package/dist/upload-grant.d.ts +15 -10
  70. package/dist/upload-grant.js +36 -17
  71. package/dist/upload-grant.js.map +1 -1
  72. package/dist/upload.d.ts +6 -0
  73. package/dist/upload.js +25 -7
  74. package/dist/upload.js.map +1 -1
  75. package/dist/user-agent.d.ts +3 -2
  76. package/dist/user-agent.js +3 -2
  77. package/dist/user-agent.js.map +1 -1
  78. package/dist/version.d.ts +1 -1
  79. package/dist/version.js +1 -1
  80. package/docs/architecture.md +15 -9
  81. package/docs/artifact-download.md +3 -3
  82. package/docs/cli.md +123 -0
  83. package/docs/client-identification.md +1 -1
  84. package/docs/errors.md +96 -11
  85. package/docs/input-preparation.md +16 -8
  86. package/docs/run-results.md +20 -0
  87. package/package.json +5 -1
@@ -0,0 +1,148 @@
1
+ /**
2
+ * The bundle reader: a `--method` path read from disk into the files `run` sends inline.
3
+ *
4
+ * The rules are the command's contract, recorded as cases in `tests/fixtures/cli-cases.json` so
5
+ * that the Python twin reads a bundle the same way (`docs/cli.md`, "Reading a bundle"):
6
+ *
7
+ * - **A `.mthds` file** is a one-file bundle, sent under its own file name. A file of any other
8
+ * name is refused.
9
+ * - **A directory** is every `.mthds` file under it, at any depth, each sent under its path
10
+ * relative to the directory, with `/` between segments.
11
+ * - **The order** is the order of those names compared code point by code point, which is the
12
+ * order of their UTF-8 bytes: `Zeta.mthds` before `main.mthds`, and `steps/score.mthds` before
13
+ * `steps2.mthds`. A run sends the contents in this order, and the first of two files to declare
14
+ * the same metadata is the one kept.
15
+ * - **A hidden entry**, a file or a directory whose name starts with `.`, is skipped, so a
16
+ * `.venv/` or a `.git/` inside a bundle is never read.
17
+ * - **Any file not named `.mthds`** is ignored, Python files included: the command sends only
18
+ * the MTHDS sources.
19
+ * - **A symbolic link** is judged by its own name: one named `.mthds` that leads to a file is read
20
+ * through, under the link's name; one that leads to a directory is not descended into, which
21
+ * keeps a link cycle from looping; and one that leads nowhere is refused. The `--method` path
22
+ * itself may be a link, and is followed.
23
+ * - **A directory with no `.mthds` file** under it is refused, as is a file that is not UTF-8
24
+ * text and a directory that cannot be listed. A byte-order mark is kept as the file's first
25
+ * character, as it is on disk.
26
+ *
27
+ * Every refusal is a usage error, raised before any request.
28
+ */
29
+ import { readdir, readFile, stat } from "node:fs/promises";
30
+ import { basename, join, resolve } from "node:path";
31
+ import { usageError } from "./io.js";
32
+ /** The extension of a bundle's files. */
33
+ export const BUNDLE_SUFFIX = ".mthds";
34
+ const UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
35
+ /**
36
+ * Read the bundle a `--method` path names, in the order the rules above give.
37
+ *
38
+ * @throws {CommandError} A usage error for a path that names no bundle.
39
+ */
40
+ export async function readBundle(given) {
41
+ const absolute = resolve(given);
42
+ let found;
43
+ try {
44
+ found = await stat(absolute);
45
+ }
46
+ catch (error) {
47
+ throw usageError(`--method "${given}" is not a catalog id (mt_...), not an address (github.com/...), and no file or directory of that name exists.`, [`Reason: ${systemReason(error)}`]);
48
+ }
49
+ if (found.isDirectory()) {
50
+ const shown = given.replace(/\/+$/, "") || "/";
51
+ const entries = [];
52
+ await collect(absolute, "", shown, entries);
53
+ if (entries.length === 0) {
54
+ throw usageError(`--method "${given}" is a directory with no .mthds file under it.`);
55
+ }
56
+ entries.sort((left, right) => compareCodePoints(left.name, right.name));
57
+ const files = [];
58
+ for (const entry of entries) {
59
+ files.push({
60
+ name: entry.name,
61
+ content: await readText(entry.path, under(shown, entry.name)),
62
+ });
63
+ }
64
+ return files;
65
+ }
66
+ if (!found.isFile()) {
67
+ throw usageError(`--method "${given}" is neither a file nor a directory.`);
68
+ }
69
+ if (!basename(absolute).endsWith(BUNDLE_SUFFIX)) {
70
+ throw usageError(`--method "${given}" is not a .mthds file.`, [
71
+ "Name a .mthds file, a directory of them, an address (github.com/...) or a catalog id (mt_...).",
72
+ ]);
73
+ }
74
+ return [{ name: basename(absolute), content: await readText(absolute, given) }];
75
+ }
76
+ /** Gather the `.mthds` files under `directory`, each with its name relative to the bundle's root. */
77
+ async function collect(directory, prefix, shownRoot, into) {
78
+ let entries;
79
+ try {
80
+ entries = await readdir(directory, { withFileTypes: true });
81
+ }
82
+ catch (error) {
83
+ throw usageError(`cannot list "${prefix === "" ? shownRoot : under(shownRoot, prefix.slice(0, -1))}".`, [`Reason: ${systemReason(error)}`]);
84
+ }
85
+ for (const entry of entries) {
86
+ if (entry.name.startsWith("."))
87
+ continue;
88
+ const name = `${prefix}${entry.name}`;
89
+ const path = join(directory, entry.name);
90
+ if (entry.isDirectory()) {
91
+ await collect(path, `${name}/`, shownRoot, into);
92
+ }
93
+ else if (entry.isSymbolicLink()) {
94
+ if (!entry.name.endsWith(BUNDLE_SUFFIX))
95
+ continue;
96
+ let target;
97
+ try {
98
+ target = await stat(path);
99
+ }
100
+ catch (error) {
101
+ throw usageError(`"${under(shownRoot, name)}" is a symbolic link that leads to no file.`, [
102
+ `Reason: ${systemReason(error)}`,
103
+ ]);
104
+ }
105
+ if (target.isFile())
106
+ into.push({ name, path });
107
+ }
108
+ else if (entry.isFile() && entry.name.endsWith(BUNDLE_SUFFIX)) {
109
+ into.push({ name, path });
110
+ }
111
+ }
112
+ }
113
+ /**
114
+ * A path inside the bundle as the person would type it: the `--method` path as given, without
115
+ * its trailing `/`, then `/`, then the file's name in the bundle.
116
+ */
117
+ function under(root, name) {
118
+ return `${root === "/" ? "" : root}/${name}`;
119
+ }
120
+ /** A bundle file's text, which must be UTF-8. */
121
+ async function readText(path, shown) {
122
+ let bytes;
123
+ try {
124
+ bytes = await readFile(path);
125
+ }
126
+ catch (error) {
127
+ throw usageError(`cannot read "${shown}".`, [`Reason: ${systemReason(error)}`]);
128
+ }
129
+ try {
130
+ return UTF8.decode(bytes);
131
+ }
132
+ catch {
133
+ throw usageError(`"${shown}" is not UTF-8 text.`);
134
+ }
135
+ }
136
+ /** Compare two strings code point by code point, which is comparing their UTF-8 bytes. */
137
+ export function compareCodePoints(left, right) {
138
+ return Buffer.compare(Buffer.from(left, "utf8"), Buffer.from(right, "utf8"));
139
+ }
140
+ /** The system's reason for a failed file operation: its code, else its message. */
141
+ export function systemReason(error) {
142
+ if (error instanceof Error) {
143
+ const code = error.code;
144
+ return code ?? error.message;
145
+ }
146
+ return String(error);
147
+ }
148
+ //# sourceMappingURL=bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../src/cli/bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAGH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpD,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,yCAAyC;AACzC,MAAM,CAAC,MAAM,aAAa,GAAG,QAAQ,CAAC;AAQtC,MAAM,IAAI,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAExE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,KAAa;IAC5C,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IAChC,IAAI,KAAuC,CAAC;IAC5C,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,UAAU,CACd,aAAa,KAAK,gHAAgH,EAClI,CAAC,WAAW,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CACnC,CAAC;IACJ,CAAC;IACD,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;QAC/C,MAAM,OAAO,GAA0C,EAAE,CAAC;QAC1D,MAAM,OAAO,CAAC,QAAQ,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;QAC5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM,UAAU,CAAC,aAAa,KAAK,gDAAgD,CAAC,CAAC;QACvF,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACxE,MAAM,KAAK,GAAiB,EAAE,CAAC;QAC/B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,KAAK,CAAC,IAAI,CAAC;gBACT,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO,EAAE,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;aAC9D,CAAC,CAAC;QACL,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC;QACpB,MAAM,UAAU,CAAC,aAAa,KAAK,sCAAsC,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QAChD,MAAM,UAAU,CAAC,aAAa,KAAK,yBAAyB,EAAE;YAC5D,gGAAgG;SACjG,CAAC,CAAC;IACL,CAAC;IACD,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,CAAC;AAClF,CAAC;AAED,qGAAqG;AACrG,KAAK,UAAU,OAAO,CACpB,SAAiB,EACjB,MAAc,EACd,SAAiB,EACjB,IAA2C;IAE3C,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,UAAU,CACd,gBAAgB,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EACrF,CAAC,WAAW,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CACnC,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QACzC,MAAM,IAAI,GAAG,GAAG,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACzC,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAC;YACxB,MAAM,OAAO,CAAC,IAAI,EAAE,GAAG,IAAI,GAAG,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;QACnD,CAAC;aAAM,IAAI,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;gBAAE,SAAS;YAClD,IAAI,MAAwC,CAAC;YAC7C,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,UAAU,CAAC,IAAI,KAAK,CAAC,SAAS,EAAE,IAAI,CAAC,6CAA6C,EAAE;oBACxF,WAAW,YAAY,CAAC,KAAK,CAAC,EAAE;iBACjC,CAAC,CAAC;YACL,CAAC;YACD,IAAI,MAAM,CAAC,MAAM,EAAE;gBAAE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;YAChE,IAAI,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,SAAS,KAAK,CAAC,IAAY,EAAE,IAAY;IACvC,OAAO,GAAG,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,EAAE,CAAC;AAC/C,CAAC;AAED,iDAAiD;AACjD,KAAK,UAAU,QAAQ,CAAC,IAAY,EAAE,KAAa;IACjD,IAAI,KAAiB,CAAC;IACtB,IAAI,CAAC;QACH,KAAK,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,UAAU,CAAC,gBAAgB,KAAK,IAAI,EAAE,CAAC,WAAW,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IAClF,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,UAAU,CAAC,IAAI,KAAK,sBAAsB,CAAC,CAAC;IACpD,CAAC;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,KAAa;IAC3D,OAAO,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAI,KAA+B,CAAC,IAAI,CAAC;QACnD,OAAO,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC;IAC/B,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * What the command reads from its environment, and the client it builds from it.
3
+ *
4
+ * The key comes from `PIPELEX_API_KEY` and nowhere else: a flag would put it in the shell's history
5
+ * and the process list. The API's address comes from `PIPELEX_BASE_URL`, the hosted API when it
6
+ * is not set. An empty variable counts as unset, except `PIPELEX_BASE_URL`: an empty one, such as
7
+ * an unfilled CI secret, reaches the SDK, which refuses it, rather than send the key to the hosted
8
+ * API by default. No `.env` file is read: a command started through `npx` from wherever the person
9
+ * stands would otherwise pick a credential from whichever file it found there.
10
+ */
11
+ import { PipelexApiClient } from "../index.js";
12
+ import type { CommandIO } from "./io.js";
13
+ export declare const API_KEY_VARIABLE = "PIPELEX_API_KEY";
14
+ export declare const BASE_URL_VARIABLE = "PIPELEX_BASE_URL";
15
+ /**
16
+ * The poll interval in milliseconds, for tests only: the recorded case table sets it to `0`, so
17
+ * that a run answered "still running" is polled again at once. A person has no reason to set it;
18
+ * unset, the SDK's own interval applies. The Python command reads the same variable.
19
+ */
20
+ export declare const POLL_INTERVAL_VARIABLE = "PIPELEX_SDK_POLL_INTERVAL_MS";
21
+ /** The name the command gives itself in the `User-Agent` of every request, before the SDK's own. */
22
+ export declare const CLIENT_APP_NAME = "pipelex-sdk-cli";
23
+ /**
24
+ * The test-only poll interval, or `undefined` for the SDK's own.
25
+ *
26
+ * @throws {CommandError} A usage error when it is set to anything but a whole number.
27
+ */
28
+ export declare function readPollInterval(io: CommandIO): number | undefined;
29
+ /**
30
+ * The client every request goes through, built from the key and the base URL.
31
+ *
32
+ * @throws {CommandError} A usage error when the key is missing or the base URL is refused. The
33
+ * refusal never quotes the key, and the SDK's refusal of a base URL never quotes the parts of it
34
+ * that can carry a secret.
35
+ */
36
+ export declare function makeClient(io: CommandIO): PipelexApiClient;
@@ -0,0 +1,72 @@
1
+ /**
2
+ * What the command reads from its environment, and the client it builds from it.
3
+ *
4
+ * The key comes from `PIPELEX_API_KEY` and nowhere else: a flag would put it in the shell's history
5
+ * and the process list. The API's address comes from `PIPELEX_BASE_URL`, the hosted API when it
6
+ * is not set. An empty variable counts as unset, except `PIPELEX_BASE_URL`: an empty one, such as
7
+ * an unfilled CI secret, reaches the SDK, which refuses it, rather than send the key to the hosted
8
+ * API by default. No `.env` file is read: a command started through `npx` from wherever the person
9
+ * stands would otherwise pick a credential from whichever file it found there.
10
+ */
11
+ import { DEFAULT_API_BASE_URL, PipelexApiClient, RequestArgumentError, SDK_VERSION, } from "../index.js";
12
+ import { usageError } from "./io.js";
13
+ export const API_KEY_VARIABLE = "PIPELEX_API_KEY";
14
+ export const BASE_URL_VARIABLE = "PIPELEX_BASE_URL";
15
+ /**
16
+ * The poll interval in milliseconds, for tests only: the recorded case table sets it to `0`, so
17
+ * that a run answered "still running" is polled again at once. A person has no reason to set it;
18
+ * unset, the SDK's own interval applies. The Python command reads the same variable.
19
+ */
20
+ export const POLL_INTERVAL_VARIABLE = "PIPELEX_SDK_POLL_INTERVAL_MS";
21
+ /** The name the command gives itself in the `User-Agent` of every request, before the SDK's own. */
22
+ export const CLIENT_APP_NAME = "pipelex-sdk-cli";
23
+ function variable(io, name) {
24
+ const value = io.env[name];
25
+ return value === undefined || value === "" ? undefined : value;
26
+ }
27
+ /**
28
+ * The test-only poll interval, or `undefined` for the SDK's own.
29
+ *
30
+ * @throws {CommandError} A usage error when it is set to anything but a whole number.
31
+ */
32
+ export function readPollInterval(io) {
33
+ const value = variable(io, POLL_INTERVAL_VARIABLE);
34
+ if (value === undefined)
35
+ return undefined;
36
+ if (!/^[0-9]+$/.test(value)) {
37
+ throw usageError(`${POLL_INTERVAL_VARIABLE} must be a whole number of milliseconds.`);
38
+ }
39
+ return Number(value);
40
+ }
41
+ /**
42
+ * The client every request goes through, built from the key and the base URL.
43
+ *
44
+ * @throws {CommandError} A usage error when the key is missing or the base URL is refused. The
45
+ * refusal never quotes the key, and the SDK's refusal of a base URL never quotes the parts of it
46
+ * that can carry a secret.
47
+ */
48
+ export function makeClient(io) {
49
+ const apiKey = variable(io, API_KEY_VARIABLE);
50
+ if (apiKey === undefined) {
51
+ throw usageError(`${API_KEY_VARIABLE} is not set.`, [
52
+ `Get a key at https://app.pipelex.com and export it: export ${API_KEY_VARIABLE}=<your key>`,
53
+ "This command reads no .env file; to load one into the shell, run: set -a; . ./.env; set +a",
54
+ ]);
55
+ }
56
+ try {
57
+ return new PipelexApiClient({
58
+ apiKey,
59
+ // Absent, not empty, means the hosted API: the SDK refuses an empty base URL on purpose, so
60
+ // an unfilled CI secret never sends the key to the hosted API by default.
61
+ baseUrl: io.env[BASE_URL_VARIABLE] ?? DEFAULT_API_BASE_URL,
62
+ appInfo: { name: CLIENT_APP_NAME, version: SDK_VERSION },
63
+ });
64
+ }
65
+ catch (error) {
66
+ if (error instanceof RequestArgumentError) {
67
+ throw usageError(`${BASE_URL_VARIABLE} is refused.`, [`Reason: ${error.message}`]);
68
+ }
69
+ throw error;
70
+ }
71
+ }
72
+ //# sourceMappingURL=environment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment.js","sourceRoot":"","sources":["../../src/cli/environment.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,oBAAoB,EACpB,WAAW,GACZ,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGrC,MAAM,CAAC,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAClD,MAAM,CAAC,MAAM,iBAAiB,GAAG,kBAAkB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,8BAA8B,CAAC;AAErE,oGAAoG;AACpG,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAC;AAEjD,SAAS,QAAQ,CAAC,EAAa,EAAE,IAAY;IAC3C,MAAM,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC3B,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC;AACjE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAAa;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,EAAE,sBAAsB,CAAC,CAAC;IACnD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,UAAU,CAAC,GAAG,sBAAsB,0CAA0C,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,EAAa;IACtC,MAAM,MAAM,GAAG,QAAQ,CAAC,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAC9C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,MAAM,UAAU,CAAC,GAAG,gBAAgB,cAAc,EAAE;YAClD,8DAA8D,gBAAgB,aAAa;YAC3F,4FAA4F;SAC7F,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC;QACH,OAAO,IAAI,gBAAgB,CAAC;YAC1B,MAAM;YACN,4FAA4F;YAC5F,0EAA0E;YAC1E,OAAO,EAAE,EAAE,CAAC,GAAG,CAAC,iBAAiB,CAAC,IAAI,oBAAoB;YAC1D,OAAO,EAAE,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,WAAW,EAAE;SACzD,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,oBAAoB,EAAE,CAAC;YAC1C,MAAM,UAAU,CAAC,GAAG,iBAAiB,cAAc,EAAE,CAAC,WAAW,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACrF,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** The command's help texts, printed on stdout by `--help`. */
2
+ export declare const MAIN_HELP = "pipelex-sdk: run an MTHDS method on the Pipelex API, with nothing to install.\n\nUsage:\n pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> | --inputs-template]\n pipelex-sdk script --method <address | catalog id> [--pipe <domain.pipe_code>] [--name <name>] [--dir <dir>]\n pipelex-sdk --version\n pipelex-sdk <command> --help\n\nCommands:\n run Run a method and print its main output as JSON on stdout.\n script Write a shell script that runs one method with this SDK's version pinned.\n\nA method is a published address, github.com/<owner>/<repo>[/<package>][@<tag>], a catalog\nid, mt_..., or, for run only, a .mthds file or a bundle directory.\n\nThe API key is read from PIPELEX_API_KEY, and the API's address from PIPELEX_BASE_URL\n(https://api.pipelex.com when unset). No .env file is read.\n\nExit codes: 0 done, 1 the run failed or the API refused it, 2 a usage error, 130 interrupted.\n";
3
+ export declare const RUN_HELP = "Usage: pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> | --inputs-template]\n\nRun a method on the Pipelex API, wait for it however long it takes, and print its main\noutput as indented JSON on stdout. The run id, each uploaded file and every error go to\nstderr. Ctrl-C stops waiting; the run keeps going on the server.\n\nOptions:\n --method <method> A published address (github.com/<owner>/<repo>[/<package>][@<tag>]),\n a catalog id (mt_...), or a path to a .mthds file or a bundle directory.\n --pipe <domain.pipe_code> The pipe to run, by its qualified ref. Default: the method's main pipe.\n --inputs <file> The inputs, a JSON object with one entry per input; - reads stdin.\n A local path or a data: URL at a file input is uploaded first.\n --inputs-template Print the inputs template, the object --inputs takes, and run nothing.\n -h, --help Show this help.\n\nNeeds PIPELEX_API_KEY in the environment.\n";
4
+ export declare const SCRIPT_HELP = "Usage: pipelex-sdk script --method <address | catalog id> [--pipe <domain.pipe_code>] [--name <name>] [--dir <dir>]\n\nCheck a method with the API, then write an executable shell script that runs it through\nthis SDK, its version pinned. The script passes its own arguments on to run:\n./<name> --inputs inputs.json, or ./<name> --inputs-template. It never overwrites a file.\n\nOptions:\n --method <method> A published address (github.com/<owner>/<repo>[/<package>][@<tag>])\n or a catalog id (mt_...). A local bundle is refused.\n --pipe <domain.pipe_code> The pipe the script runs. Default: the method's main pipe.\n --name <name> The script's file name. Default: the address's last segment, or the\n catalog method's name in kebab-case.\n --dir <dir> Where to write it. Default: the current directory.\n -h, --help Show this help.\n\nNeeds PIPELEX_API_KEY in the environment. The script needs a POSIX shell.\n";
@@ -0,0 +1,56 @@
1
+ /** The command's help texts, printed on stdout by `--help`. */
2
+ export const MAIN_HELP = `pipelex-sdk: run an MTHDS method on the Pipelex API, with nothing to install.
3
+
4
+ Usage:
5
+ pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> | --inputs-template]
6
+ pipelex-sdk script --method <address | catalog id> [--pipe <domain.pipe_code>] [--name <name>] [--dir <dir>]
7
+ pipelex-sdk --version
8
+ pipelex-sdk <command> --help
9
+
10
+ Commands:
11
+ run Run a method and print its main output as JSON on stdout.
12
+ script Write a shell script that runs one method with this SDK's version pinned.
13
+
14
+ A method is a published address, github.com/<owner>/<repo>[/<package>][@<tag>], a catalog
15
+ id, mt_..., or, for run only, a .mthds file or a bundle directory.
16
+
17
+ The API key is read from PIPELEX_API_KEY, and the API's address from PIPELEX_BASE_URL
18
+ (https://api.pipelex.com when unset). No .env file is read.
19
+
20
+ Exit codes: 0 done, 1 the run failed or the API refused it, 2 a usage error, 130 interrupted.
21
+ `;
22
+ export const RUN_HELP = `Usage: pipelex-sdk run --method <method> [--pipe <domain.pipe_code>] [--inputs <file> | --inputs-template]
23
+
24
+ Run a method on the Pipelex API, wait for it however long it takes, and print its main
25
+ output as indented JSON on stdout. The run id, each uploaded file and every error go to
26
+ stderr. Ctrl-C stops waiting; the run keeps going on the server.
27
+
28
+ Options:
29
+ --method <method> A published address (github.com/<owner>/<repo>[/<package>][@<tag>]),
30
+ a catalog id (mt_...), or a path to a .mthds file or a bundle directory.
31
+ --pipe <domain.pipe_code> The pipe to run, by its qualified ref. Default: the method's main pipe.
32
+ --inputs <file> The inputs, a JSON object with one entry per input; - reads stdin.
33
+ A local path or a data: URL at a file input is uploaded first.
34
+ --inputs-template Print the inputs template, the object --inputs takes, and run nothing.
35
+ -h, --help Show this help.
36
+
37
+ Needs PIPELEX_API_KEY in the environment.
38
+ `;
39
+ export const SCRIPT_HELP = `Usage: pipelex-sdk script --method <address | catalog id> [--pipe <domain.pipe_code>] [--name <name>] [--dir <dir>]
40
+
41
+ Check a method with the API, then write an executable shell script that runs it through
42
+ this SDK, its version pinned. The script passes its own arguments on to run:
43
+ ./<name> --inputs inputs.json, or ./<name> --inputs-template. It never overwrites a file.
44
+
45
+ Options:
46
+ --method <method> A published address (github.com/<owner>/<repo>[/<package>][@<tag>])
47
+ or a catalog id (mt_...). A local bundle is refused.
48
+ --pipe <domain.pipe_code> The pipe the script runs. Default: the method's main pipe.
49
+ --name <name> The script's file name. Default: the address's last segment, or the
50
+ catalog method's name in kebab-case.
51
+ --dir <dir> Where to write it. Default: the current directory.
52
+ -h, --help Show this help.
53
+
54
+ Needs PIPELEX_API_KEY in the environment. The script needs a POSIX shell.
55
+ `;
56
+ //# sourceMappingURL=help.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"help.js","sourceRoot":"","sources":["../../src/cli/help.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAE/D,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;;;;;;;;;;;;;CAmBxB,CAAC;AAEF,MAAM,CAAC,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;CAgBvB,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;CAgB1B,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The inputs `--inputs` names: a JSON object, one entry per input, read from a file or, for `-`,
3
+ * from stdin. It is the shape `mthds run --inputs` and `cli-python`'s `--inputs` take, and the
4
+ * shape `--inputs-template` prints.
5
+ */
6
+ import type { CommandIO } from "./io.js";
7
+ /**
8
+ * Read and parse the inputs. A relative path resolves against the current directory.
9
+ *
10
+ * @throws {CommandError} A usage error for an unreadable file or stdin, text that is not UTF-8 or
11
+ * not JSON, and JSON that is not an object.
12
+ */
13
+ export declare function readInputs(source: string, io: CommandIO): Promise<Record<string, unknown>>;
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The inputs `--inputs` names: a JSON object, one entry per input, read from a file or, for `-`,
3
+ * from stdin. It is the shape `mthds run --inputs` and `cli-python`'s `--inputs` take, and the
4
+ * shape `--inputs-template` prints.
5
+ */
6
+ import { readFile } from "node:fs/promises";
7
+ import { resolve } from "node:path";
8
+ import { systemReason } from "./bundle.js";
9
+ import { untilInterrupted, usageError } from "./io.js";
10
+ // A leading byte-order mark is dropped, as RFC 8259 lets a JSON reader do: Windows PowerShell 5.1
11
+ // writes one with `Out-File -Encoding utf8`. A bundle's mark is kept, since it is sent as written.
12
+ const UTF8 = new TextDecoder("utf-8", { fatal: true });
13
+ /**
14
+ * Read and parse the inputs. A relative path resolves against the current directory.
15
+ *
16
+ * @throws {CommandError} A usage error for an unreadable file or stdin, text that is not UTF-8 or
17
+ * not JSON, and JSON that is not an object.
18
+ */
19
+ export async function readInputs(source, io) {
20
+ const label = source === "-" ? "stdin" : `the inputs file "${source}"`;
21
+ const read = source === "-" ? () => io.readStdin() : () => readFile(resolve(source));
22
+ // Raced with the interrupt: stdin, a named pipe or a file on a stalled mount can keep the read
23
+ // waiting.
24
+ const bytes = await untilInterrupted(async () => {
25
+ try {
26
+ return await read();
27
+ }
28
+ catch (error) {
29
+ throw usageError(`cannot read ${label}.`, [`Reason: ${systemReason(error)}`]);
30
+ }
31
+ }, io.interrupt);
32
+ let text;
33
+ try {
34
+ text = UTF8.decode(bytes);
35
+ }
36
+ catch {
37
+ throw usageError(`${label} is not UTF-8 text.`);
38
+ }
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(text);
42
+ }
43
+ catch (error) {
44
+ throw usageError(`${label} is not valid JSON.`, [
45
+ `Reason: ${error instanceof Error ? error.message : String(error)}`,
46
+ ]);
47
+ }
48
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
49
+ throw usageError(`${label} must hold a JSON object, one entry per input, and holds ${jsonKind(parsed)}.`, ["Run with --inputs-template to see the object this method takes."]);
50
+ }
51
+ return parsed;
52
+ }
53
+ /** What a JSON value is, said the way the refusal says it. */
54
+ function jsonKind(value) {
55
+ if (value === null)
56
+ return "null";
57
+ if (Array.isArray(value))
58
+ return "an array";
59
+ switch (typeof value) {
60
+ case "string":
61
+ return "a string";
62
+ case "number":
63
+ return "a number";
64
+ case "boolean":
65
+ return "a boolean";
66
+ default:
67
+ return "not an object";
68
+ }
69
+ }
70
+ //# sourceMappingURL=inputs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"inputs.js","sourceRoot":"","sources":["../../src/cli/inputs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAGvD,kGAAkG;AAClG,mGAAmG;AACnG,MAAM,IAAI,GAAG,IAAI,WAAW,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAc,EAAE,EAAa;IAC5D,MAAM,KAAK,GAAG,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,oBAAoB,MAAM,GAAG,CAAC;IACvE,MAAM,IAAI,GAAG,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IACrF,+FAA+F;IAC/F,WAAW;IACX,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,KAAK,IAAI,EAAE;QAC9C,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,EAAE,CAAC;QACtB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,UAAU,CAAC,eAAe,KAAK,GAAG,EAAE,CAAC,WAAW,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;QAChF,CAAC;IACH,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC;IACjB,IAAI,IAAY,CAAC;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,UAAU,CAAC,GAAG,KAAK,qBAAqB,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC5B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,UAAU,CAAC,GAAG,KAAK,qBAAqB,EAAE;YAC9C,WAAW,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE;SACpE,CAAC,CAAC;IACL,CAAC;IACD,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,UAAU,CACd,GAAG,KAAK,4DAA4D,QAAQ,CAAC,MAAM,CAAC,GAAG,EACvF,CAAC,iEAAiE,CAAC,CACpE,CAAC;IACJ,CAAC;IACD,OAAO,MAAiC,CAAC;AAC3C,CAAC;AAED,8DAA8D;AAC9D,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,UAAU,CAAC;IAC5C,QAAQ,OAAO,KAAK,EAAE,CAAC;QACrB,KAAK,QAAQ;YACX,OAAO,UAAU,CAAC;QACpB,KAAK,QAAQ;YACX,OAAO,UAAU,CAAC;QACpB,KAAK,SAAS;YACZ,OAAO,WAAW,CAAC;QACrB;YACE,OAAO,eAAe,CAAC;IAC3B,CAAC;AACH,CAAC"}
@@ -0,0 +1,79 @@
1
+ /**
2
+ * What the `pipelex-sdk` command reads from and writes to, and the ways it stops early.
3
+ *
4
+ * The command never touches `process` directly, apart from the current directory that relative
5
+ * paths resolve against: `src/cli.ts`, the executable, hands it the real streams, environment and
6
+ * Ctrl-C, and the test suite hands it its own. That is what lets one recorded case table drive
7
+ * the command in-process, in this SDK and in its Python twin (`docs/cli.md`).
8
+ */
9
+ /** The command's world: its environment, its three streams and the interrupt. */
10
+ export interface CommandIO {
11
+ /**
12
+ * The environment the command reads: `PIPELEX_API_KEY`, `PIPELEX_BASE_URL` and the test-only
13
+ * `PIPELEX_SDK_POLL_INTERVAL_MS`. Nothing else is read from it, and no `.env` file is read.
14
+ */
15
+ readonly env: Readonly<Record<string, string | undefined>>;
16
+ /**
17
+ * All of stdin, as bytes. Read only for `--inputs -`. It rejects with the system's error when
18
+ * stdin cannot be read; a process started with stdin closed reads it as empty, since Node opens
19
+ * the null device on a standard stream it starts without.
20
+ */
21
+ readStdin(): Promise<Uint8Array>;
22
+ /** Write to stdout, which carries the result and nothing else. */
23
+ writeStdout(text: string): void;
24
+ /** Write to stderr, which carries everything that is not the result. */
25
+ writeStderr(text: string): void;
26
+ /** Aborted when the person interrupts the command (Ctrl-C). */
27
+ readonly interrupt: AbortSignal;
28
+ }
29
+ /** The command's exit codes (`docs/cli.md`): a script reads stdout, and these are presentation. */
30
+ export declare const EXIT_OK = 0;
31
+ export declare const EXIT_FAILED = 1;
32
+ export declare const EXIT_USAGE = 2;
33
+ export declare const EXIT_INTERRUPTED = 130;
34
+ /**
35
+ * A failure the command words itself: a usage error (exit code 2), a refusal it reads off an
36
+ * answer (exit code 1), or an interrupt (exit code 130). Its message is the sentence after
37
+ * `Error: `, or the whole first line when `bare`, as an interrupt's is; `details` are the lines
38
+ * printed under it, as they are.
39
+ */
40
+ export declare class CommandError extends Error {
41
+ readonly exitCode: number;
42
+ readonly details: readonly string[];
43
+ readonly bare: boolean;
44
+ constructor(message: string, options: {
45
+ exitCode: number;
46
+ details?: readonly string[];
47
+ bare?: boolean;
48
+ });
49
+ }
50
+ /** A usage error: something the person can fix on the command line or in the environment. */
51
+ export declare function usageError(message: string, details?: readonly string[]): CommandError;
52
+ /** The person interrupted the command while it was waiting on something. */
53
+ export declare class Interrupted extends Error {
54
+ constructor();
55
+ }
56
+ /**
57
+ * Start a step and wait for it, unless the person interrupts first, which rejects with
58
+ * {@link Interrupted}.
59
+ *
60
+ * The step is given as a function, so that an interrupt that has already landed starts nothing:
61
+ * a request is never sent once the person has asked to stop, and what the command then says ("No
62
+ * run was started") stays true. An interrupt that lands while the step starts is caught as soon
63
+ * as it returns.
64
+ *
65
+ * Several of the SDK's requests take no abort signal (the input preparation, the start request,
66
+ * the blocking execute), so the command stops waiting for them rather than cancelling them; the
67
+ * executable exits right after it has said so. The promise left behind is given a handler, so its
68
+ * later failure is never reported as unhandled.
69
+ */
70
+ export declare function untilInterrupted<T>(start: () => Promise<T>, signal: AbortSignal): Promise<T>;
71
+ /** Write each line to stderr, each ended by a newline. */
72
+ export declare function writeLines(io: CommandIO, lines: readonly string[]): void;
73
+ /**
74
+ * Let a reader of one of the process's output streams stop early: `| head` closes the pipe, and
75
+ * the next write fails with `EPIPE`, which, unhandled, would end the process mid-run, a run it
76
+ * started included. There is nothing left to tell that reader, so the error is dropped; any other
77
+ * error on the stream is thrown as it would have been.
78
+ */
79
+ export declare function ignoreClosedPipe(stream: NodeJS.EventEmitter): void;
package/dist/cli/io.js ADDED
@@ -0,0 +1,96 @@
1
+ /**
2
+ * What the `pipelex-sdk` command reads from and writes to, and the ways it stops early.
3
+ *
4
+ * The command never touches `process` directly, apart from the current directory that relative
5
+ * paths resolve against: `src/cli.ts`, the executable, hands it the real streams, environment and
6
+ * Ctrl-C, and the test suite hands it its own. That is what lets one recorded case table drive
7
+ * the command in-process, in this SDK and in its Python twin (`docs/cli.md`).
8
+ */
9
+ /** The command's exit codes (`docs/cli.md`): a script reads stdout, and these are presentation. */
10
+ export const EXIT_OK = 0;
11
+ export const EXIT_FAILED = 1;
12
+ export const EXIT_USAGE = 2;
13
+ export const EXIT_INTERRUPTED = 130;
14
+ /**
15
+ * A failure the command words itself: a usage error (exit code 2), a refusal it reads off an
16
+ * answer (exit code 1), or an interrupt (exit code 130). Its message is the sentence after
17
+ * `Error: `, or the whole first line when `bare`, as an interrupt's is; `details` are the lines
18
+ * printed under it, as they are.
19
+ */
20
+ export class CommandError extends Error {
21
+ exitCode;
22
+ details;
23
+ bare;
24
+ constructor(message, options) {
25
+ super(message);
26
+ this.name = "CommandError";
27
+ this.exitCode = options.exitCode;
28
+ this.details = options.details ?? [];
29
+ this.bare = options.bare ?? false;
30
+ }
31
+ }
32
+ /** A usage error: something the person can fix on the command line or in the environment. */
33
+ export function usageError(message, details = []) {
34
+ return new CommandError(message, { exitCode: EXIT_USAGE, details });
35
+ }
36
+ /** The person interrupted the command while it was waiting on something. */
37
+ export class Interrupted extends Error {
38
+ constructor() {
39
+ super("Interrupted.");
40
+ this.name = "Interrupted";
41
+ }
42
+ }
43
+ /**
44
+ * Start a step and wait for it, unless the person interrupts first, which rejects with
45
+ * {@link Interrupted}.
46
+ *
47
+ * The step is given as a function, so that an interrupt that has already landed starts nothing:
48
+ * a request is never sent once the person has asked to stop, and what the command then says ("No
49
+ * run was started") stays true. An interrupt that lands while the step starts is caught as soon
50
+ * as it returns.
51
+ *
52
+ * Several of the SDK's requests take no abort signal (the input preparation, the start request,
53
+ * the blocking execute), so the command stops waiting for them rather than cancelling them; the
54
+ * executable exits right after it has said so. The promise left behind is given a handler, so its
55
+ * later failure is never reported as unhandled.
56
+ */
57
+ export function untilInterrupted(start, signal) {
58
+ if (signal.aborted)
59
+ return Promise.reject(new Interrupted());
60
+ const promise = start();
61
+ if (signal.aborted) {
62
+ promise.catch(() => undefined);
63
+ return Promise.reject(new Interrupted());
64
+ }
65
+ return new Promise((resolve, reject) => {
66
+ const onAbort = () => {
67
+ promise.catch(() => undefined);
68
+ reject(new Interrupted());
69
+ };
70
+ signal.addEventListener("abort", onAbort, { once: true });
71
+ promise.then((value) => {
72
+ signal.removeEventListener("abort", onAbort);
73
+ resolve(value);
74
+ }, (error) => {
75
+ signal.removeEventListener("abort", onAbort);
76
+ reject(error);
77
+ });
78
+ });
79
+ }
80
+ /** Write each line to stderr, each ended by a newline. */
81
+ export function writeLines(io, lines) {
82
+ io.writeStderr(lines.map((line) => `${line}\n`).join(""));
83
+ }
84
+ /**
85
+ * Let a reader of one of the process's output streams stop early: `| head` closes the pipe, and
86
+ * the next write fails with `EPIPE`, which, unhandled, would end the process mid-run, a run it
87
+ * started included. There is nothing left to tell that reader, so the error is dropped; any other
88
+ * error on the stream is thrown as it would have been.
89
+ */
90
+ export function ignoreClosedPipe(stream) {
91
+ stream.on("error", (error) => {
92
+ if (error.code !== "EPIPE")
93
+ throw error;
94
+ });
95
+ }
96
+ //# sourceMappingURL=io.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"io.js","sourceRoot":"","sources":["../../src/cli/io.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The `pipelex-sdk` command: `run` runs a method and prints its main output, `script` writes the
3
+ * per-method shell script. `docs/cli.md` describes both, and `tests/fixtures/cli-cases.json`
4
+ * records what each prints for every case, which the Python twin answers too.
5
+ *
6
+ * Nothing in the SDK imports this module or its siblings, and the package entry does not export
7
+ * them (`.dependency-cruiser.cjs`): the command sits at the top of the dependency graph, and reaches
8
+ * the SDK only through its public barrel, as any other caller would.
9
+ */
10
+ import type { CommandIO } from "./io.js";
11
+ export type { CommandIO } from "./io.js";
12
+ /**
13
+ * Run the command on `argv`, the arguments after the command's own name, and return its exit
14
+ * code: 0 when it did what it was asked, 1 when the run failed or the API refused it, 2 for a
15
+ * usage error and 130 when interrupted. Every failure is printed on stderr before it returns; it
16
+ * never throws.
17
+ */
18
+ export declare function runCommand(argv: readonly string[], io: CommandIO): Promise<number>;