@orchestraworks/cli 0.8.0 → 0.9.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.
- package/README.md +1 -1
- package/dist/artifacts/prelude.js +416 -88
- package/dist/cli/api/execute.d.ts +2 -5
- package/dist/cli/api/execute.js +67 -40
- package/dist/cli/api/raw.d.ts +3 -1
- package/dist/cli/api/raw.js +33 -6
- package/dist/cli/api/register.js +6 -1
- package/dist/cli/preview.d.ts +5 -4
- package/dist/cli/preview.js +15 -9
- package/dist/cli/upload.d.ts +26 -0
- package/dist/cli/upload.js +64 -16
- package/dist/generated/cli-commands.d.ts +5 -0
- package/dist/generated/cli-commands.json +31100 -17245
- package/dist/generated/cli-dispatch.js +99 -0
- package/dist/generated/managed-skills.js +1 -0
- package/node_modules/@orchestraworks/client/dist/apis/ArtifactsApi.d.ts +3 -3
- package/node_modules/@orchestraworks/client/dist/apis/ArtifactsApi.js +2 -2
- package/node_modules/@orchestraworks/client/dist/apis/FilesApi.d.ts +292 -2
- package/node_modules/@orchestraworks/client/dist/apis/FilesApi.js +988 -30
- package/node_modules/@orchestraworks/client/dist/esm/apis/ArtifactsApi.d.ts +3 -3
- package/node_modules/@orchestraworks/client/dist/esm/apis/ArtifactsApi.js +2 -2
- package/node_modules/@orchestraworks/client/dist/esm/apis/FilesApi.d.ts +292 -2
- package/node_modules/@orchestraworks/client/dist/esm/apis/FilesApi.js +986 -28
- package/node_modules/@orchestraworks/client/dist/esm/models/Artifact.d.ts +12 -53
- package/node_modules/@orchestraworks/client/dist/esm/models/Artifact.js +7 -29
- package/node_modules/@orchestraworks/client/dist/esm/models/Comment.d.ts +2 -2
- package/node_modules/@orchestraworks/client/dist/esm/models/Comment.js +3 -3
- package/node_modules/@orchestraworks/client/dist/esm/models/CommentSummary.d.ts +2 -2
- package/node_modules/@orchestraworks/client/dist/esm/models/CommentSummary.js +3 -3
- package/node_modules/@orchestraworks/client/dist/esm/models/CopyFileRequest.d.ts +38 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/CopyFileRequest.js +47 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/CreateArtifactRequest.d.ts +7 -7
- package/node_modules/@orchestraworks/client/dist/esm/models/CreateAuthHandoffRequest.d.ts +1 -1
- package/node_modules/@orchestraworks/client/dist/esm/models/CreateFileRequest.d.ts +44 -2
- package/node_modules/@orchestraworks/client/dist/esm/models/CreateFileRequest.js +14 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/CreateRoomTokenRequest.d.ts +1 -1
- package/node_modules/@orchestraworks/client/dist/esm/models/FileMatch.d.ts +38 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/FileMatch.js +47 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/FileVersion.d.ts +101 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/FileVersion.js +83 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/ModelFile.d.ts +69 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/ModelFile.js +36 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/PageArtifact.d.ts +51 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/{PageArtifactSummary.js → PageArtifact.js} +11 -11
- package/node_modules/@orchestraworks/client/dist/esm/models/PageFileVersion.d.ts +51 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/PageFileVersion.js +56 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/RoomTokenRoom.d.ts +2 -2
- package/node_modules/@orchestraworks/client/dist/esm/models/RoomTokenRoom.js +2 -2
- package/node_modules/@orchestraworks/client/dist/esm/models/RoomTokenRoomRequest.d.ts +3 -3
- package/node_modules/@orchestraworks/client/dist/esm/models/RoomTokenRoomRequest.js +2 -2
- package/node_modules/@orchestraworks/client/dist/esm/models/SelectorAnchor.d.ts +24 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/SelectorAnchor.js +8 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/SnapshotAnchor.d.ts +1 -1
- package/node_modules/@orchestraworks/client/dist/esm/models/UpdateArtifactRequest.d.ts +0 -8
- package/node_modules/@orchestraworks/client/dist/esm/models/UpdateArtifactRequest.js +0 -2
- package/node_modules/@orchestraworks/client/dist/esm/models/UpdateFileRequest.d.ts +40 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/UpdateFileRequest.js +43 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/WorkItem.d.ts +3 -3
- package/node_modules/@orchestraworks/client/dist/esm/models/WorkItem.js +3 -3
- package/node_modules/@orchestraworks/client/dist/esm/models/WriteFileDataRequest.d.ts +50 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/WriteFileDataRequest.js +51 -0
- package/node_modules/@orchestraworks/client/dist/esm/models/index.d.ts +7 -3
- package/node_modules/@orchestraworks/client/dist/esm/models/index.js +7 -3
- package/node_modules/@orchestraworks/client/dist/models/Artifact.d.ts +12 -53
- package/node_modules/@orchestraworks/client/dist/models/Artifact.js +8 -30
- package/node_modules/@orchestraworks/client/dist/models/Comment.d.ts +2 -2
- package/node_modules/@orchestraworks/client/dist/models/Comment.js +3 -3
- package/node_modules/@orchestraworks/client/dist/models/CommentSummary.d.ts +2 -2
- package/node_modules/@orchestraworks/client/dist/models/CommentSummary.js +3 -3
- package/node_modules/@orchestraworks/client/dist/models/CopyFileRequest.d.ts +38 -0
- package/node_modules/@orchestraworks/client/dist/models/CopyFileRequest.js +54 -0
- package/node_modules/@orchestraworks/client/dist/models/CreateArtifactRequest.d.ts +7 -7
- package/node_modules/@orchestraworks/client/dist/models/CreateAuthHandoffRequest.d.ts +1 -1
- package/node_modules/@orchestraworks/client/dist/models/CreateFileRequest.d.ts +44 -2
- package/node_modules/@orchestraworks/client/dist/models/CreateFileRequest.js +14 -0
- package/node_modules/@orchestraworks/client/dist/models/CreateRoomTokenRequest.d.ts +1 -1
- package/node_modules/@orchestraworks/client/dist/models/FileMatch.d.ts +38 -0
- package/node_modules/@orchestraworks/client/dist/models/FileMatch.js +54 -0
- package/node_modules/@orchestraworks/client/dist/models/FileVersion.d.ts +101 -0
- package/node_modules/@orchestraworks/client/dist/models/FileVersion.js +91 -0
- package/node_modules/@orchestraworks/client/dist/models/ModelFile.d.ts +69 -0
- package/node_modules/@orchestraworks/client/dist/models/ModelFile.js +37 -1
- package/node_modules/@orchestraworks/client/dist/models/PageArtifact.d.ts +51 -0
- package/node_modules/@orchestraworks/client/dist/models/{PageArtifactSummary.js → PageArtifact.js} +16 -16
- package/node_modules/@orchestraworks/client/dist/models/PageFileVersion.d.ts +51 -0
- package/node_modules/@orchestraworks/client/dist/models/PageFileVersion.js +63 -0
- package/node_modules/@orchestraworks/client/dist/models/RoomTokenRoom.d.ts +2 -2
- package/node_modules/@orchestraworks/client/dist/models/RoomTokenRoom.js +2 -2
- package/node_modules/@orchestraworks/client/dist/models/RoomTokenRoomRequest.d.ts +3 -3
- package/node_modules/@orchestraworks/client/dist/models/RoomTokenRoomRequest.js +2 -2
- package/node_modules/@orchestraworks/client/dist/models/SelectorAnchor.d.ts +24 -0
- package/node_modules/@orchestraworks/client/dist/models/SelectorAnchor.js +8 -0
- package/node_modules/@orchestraworks/client/dist/models/SnapshotAnchor.d.ts +1 -1
- package/node_modules/@orchestraworks/client/dist/models/UpdateArtifactRequest.d.ts +0 -8
- package/node_modules/@orchestraworks/client/dist/models/UpdateArtifactRequest.js +0 -2
- package/node_modules/@orchestraworks/client/dist/models/UpdateFileRequest.d.ts +40 -0
- package/node_modules/@orchestraworks/client/dist/models/UpdateFileRequest.js +50 -0
- package/node_modules/@orchestraworks/client/dist/models/WorkItem.d.ts +3 -3
- package/node_modules/@orchestraworks/client/dist/models/WorkItem.js +3 -3
- package/node_modules/@orchestraworks/client/dist/models/WriteFileDataRequest.d.ts +50 -0
- package/node_modules/@orchestraworks/client/dist/models/WriteFileDataRequest.js +58 -0
- package/node_modules/@orchestraworks/client/dist/models/index.d.ts +7 -3
- package/node_modules/@orchestraworks/client/dist/models/index.js +7 -3
- package/node_modules/@orchestraworks/client/package.json +1 -1
- package/package.json +4 -4
- package/templates/engineering-team/package.json +1 -1
- package/templates/todo-factory/package.json +1 -1
- package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactSummary.d.ts +0 -139
- package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactSummary.js +0 -95
- package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactVersion.d.ts +0 -110
- package/node_modules/@orchestraworks/client/dist/esm/models/ArtifactVersion.js +0 -77
- package/node_modules/@orchestraworks/client/dist/esm/models/PageArtifactSummary.d.ts +0 -51
- package/node_modules/@orchestraworks/client/dist/models/ArtifactSummary.d.ts +0 -139
- package/node_modules/@orchestraworks/client/dist/models/ArtifactSummary.js +0 -103
- package/node_modules/@orchestraworks/client/dist/models/ArtifactVersion.d.ts +0 -110
- package/node_modules/@orchestraworks/client/dist/models/ArtifactVersion.js +0 -84
- package/node_modules/@orchestraworks/client/dist/models/PageArtifactSummary.d.ts +0 -51
package/dist/cli/api/execute.js
CHANGED
|
@@ -13,7 +13,7 @@ import { findCommand } from "../../generated/cli-commands.js";
|
|
|
13
13
|
import { CLI_DISPATCH } from "../../generated/cli-dispatch.js";
|
|
14
14
|
import { authFlagsFrom, platformTargetFrom, resolveAnonymousBaseUrl, resolveAuthWithWarnings, } from "../core/auth.js";
|
|
15
15
|
import { readCredentialFromCommandLine, readCredentialFromEnv } from "../core/credential-input.js";
|
|
16
|
-
import { contentSourceFieldsIn, uploadFile } from "../upload.js";
|
|
16
|
+
import { contentSourceFieldsIn, inlineFileOf, storeInlineFile, uploadFile } from "../upload.js";
|
|
17
17
|
import { readBodyDocument } from "../core/invocation.js";
|
|
18
18
|
import { defaultIo, parseOutputMode, printResponse, printWarnings, } from "../core/output.js";
|
|
19
19
|
import { createPlatformApis } from "../core/platform-client.js";
|
|
@@ -24,25 +24,6 @@ import { ensureWriteApproved, IMPERATIVE_APPROVAL_DEFAULT, managedResourceWarnin
|
|
|
24
24
|
const ANONYMOUS_NOUN = "health";
|
|
25
25
|
/** The item mutations that pre-read for the managed marker: neither carries it in request or response. */
|
|
26
26
|
const MANAGED_WARNING_VERBS = new Set(["update", "delete"]);
|
|
27
|
-
/**
|
|
28
|
-
* Operations whose answer is a redirect to the bytes themselves. `fetch` follows it, so what this executor
|
|
29
|
-
* reads back IS the content — but the operation declares a body of its own nowhere, and without this the
|
|
30
|
-
* shared reader treats an unvouched byte body as material to withhold and prints a sentinel instead of the
|
|
31
|
-
* file. Hand-maintained: every entry is an operation whose success response is a bare-content redirect.
|
|
32
|
-
*/
|
|
33
|
-
const BARE_CONTENT_OPERATIONS = new Set(["getFileContent"]);
|
|
34
|
-
/** What those operations effectively answer: the content itself, which streams raw and is never masked. */
|
|
35
|
-
const BARE_CONTENT_SCHEMA = { kind: "scalar" };
|
|
36
|
-
/**
|
|
37
|
-
* The schema the reader and the redactor are both handed — `orchestra api` included, so the raw caller and the
|
|
38
|
-
* typed command cannot disagree about which responses are the bare content.
|
|
39
|
-
*/
|
|
40
|
-
export function responseSchemaOf(row) {
|
|
41
|
-
if (row === undefined) {
|
|
42
|
-
return undefined;
|
|
43
|
-
}
|
|
44
|
-
return BARE_CONTENT_OPERATIONS.has(row.operationId) ? BARE_CONTENT_SCHEMA : row.responseSchema;
|
|
45
|
-
}
|
|
46
27
|
/** Named so the error mapper prints the sentence and nothing else — no platform framing, no stack. */
|
|
47
28
|
export class CommandUsageError extends Error {
|
|
48
29
|
constructor(message) {
|
|
@@ -80,9 +61,7 @@ export async function executeCommand(row, invocation, deps = {}, ctx = {}) {
|
|
|
80
61
|
}
|
|
81
62
|
// The WHOLE target goes to the shared client, including the credential session, so a command that
|
|
82
63
|
// outlives its access token renews it in flight instead of failing halfway through.
|
|
83
|
-
const apis = createPlatformApis(target, deps.fetchApi, {
|
|
84
|
-
warn: (lines) => printWarnings(lines, io),
|
|
85
|
-
});
|
|
64
|
+
const apis = createPlatformApis(target, invocation.conventions.markdown === true ? acceptingMarkdown(deps.fetchApi) : deps.fetchApi, { warn: (lines) => printWarnings(lines, io) });
|
|
86
65
|
// (2, 3) Compose and validate the invocation.
|
|
87
66
|
const composed = composeRequest(row, invocation, env, deps);
|
|
88
67
|
refuseDoubledContentSource(row, invocation, composed);
|
|
@@ -105,8 +84,8 @@ export async function executeCommand(row, invocation, deps = {}, ctx = {}) {
|
|
|
105
84
|
// file that does not exist yet. It runs after the gate because storing bytes is itself a write.
|
|
106
85
|
const stored = await storeDeclaredFile(row, invocation, composed.body, apis, target, deps, io);
|
|
107
86
|
if (stored !== undefined) {
|
|
108
|
-
// The
|
|
109
|
-
//
|
|
87
|
+
// The store already made this row's own create, so there is no second call to make: `--file` stores
|
|
88
|
+
// one file, and the command answers the one it stored.
|
|
110
89
|
writeOutputFile(invocation, stored.body, io);
|
|
111
90
|
const storedRedaction = redactBody(stored.body, stored.schema, invocation.conventions.reveal === true);
|
|
112
91
|
printResponse(storedRedaction === undefined ? stored.body : storedRedaction.rendered, {
|
|
@@ -133,7 +112,7 @@ export async function executeCommand(row, invocation, deps = {}, ctx = {}) {
|
|
|
133
112
|
return;
|
|
134
113
|
}
|
|
135
114
|
const response = await dispatch(row.operationId)(apis, dispatchArgs);
|
|
136
|
-
const responseSchema =
|
|
115
|
+
const responseSchema = row.responseSchema;
|
|
137
116
|
const raw = await readRawBody(response.raw, responseSchema);
|
|
138
117
|
if (writeOutputFile(invocation, raw, io) && raw instanceof Uint8Array) {
|
|
139
118
|
return;
|
|
@@ -312,34 +291,69 @@ function refuseDoubledContentSource(row, invocation, composed) {
|
|
|
312
291
|
* Nothing happens without `--file`.
|
|
313
292
|
*
|
|
314
293
|
* Where the row names a file by id, the create that follows is handed that id and this answers nothing.
|
|
315
|
-
* Where the row DECLARES the upload itself — `sha256`, `sizeBytes` — the
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
* `--
|
|
294
|
+
* Where the row DECLARES the upload itself — `sha256`, `sizeBytes` — the store IS that create, so this
|
|
295
|
+
* answers the stored row and the command prints it. Writing those fields into a create that then runs again
|
|
296
|
+
* declared a SECOND file, which nothing completed and which `--output json` printed: two rows for one
|
|
297
|
+
* `--file`, and the wrong one shown.
|
|
319
298
|
*/
|
|
320
299
|
async function storeDeclaredFile(row, invocation, body, apis, target, deps, io) {
|
|
321
300
|
const path = invocation.conventions.file;
|
|
322
301
|
if (path === undefined || body === undefined) {
|
|
323
302
|
return undefined;
|
|
324
303
|
}
|
|
325
|
-
const
|
|
304
|
+
const namesFile = row.flags.some((flag) => flag.name === "file-id");
|
|
305
|
+
// The one create describes one file: what it says about the name and the type governs the store too, so
|
|
306
|
+
// the bytes cannot land as something the create did not ask for.
|
|
307
|
+
const named = { filename: stringField(body, "filename"), contentType: stringField(body, "contentType") };
|
|
308
|
+
const inline = inlineFileOf(path, named);
|
|
309
|
+
if (inline !== undefined && !namesFile) {
|
|
310
|
+
Object.assign(body, inline);
|
|
311
|
+
return await createInline(row, invocation, body, apis, target);
|
|
312
|
+
}
|
|
313
|
+
const placement = {
|
|
326
314
|
apis,
|
|
327
|
-
fetchApi: (deps.fetchApi ?? fetch),
|
|
328
315
|
io,
|
|
329
316
|
organizationId: target.organizationId,
|
|
330
|
-
teamId: stringField(body, "teamId") ??
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
317
|
+
teamId: stringField(body, "teamId") ?? (await ownerTeam(row, invocation, apis, target.organizationId)),
|
|
318
|
+
};
|
|
319
|
+
if (inline !== undefined) {
|
|
320
|
+
body["fileId"] = await storeInlineFile(inline, placement);
|
|
321
|
+
return undefined;
|
|
322
|
+
}
|
|
323
|
+
const uploaded = await uploadFile(path, {
|
|
324
|
+
...placement,
|
|
325
|
+
...named,
|
|
326
|
+
fetchApi: (deps.fetchApi ?? fetch),
|
|
327
|
+
...(namesFile ? {} : { request: body }),
|
|
336
328
|
});
|
|
337
|
-
if (
|
|
329
|
+
if (namesFile) {
|
|
338
330
|
body["fileId"] = uploaded.fileId;
|
|
339
331
|
return undefined;
|
|
340
332
|
}
|
|
333
|
+
if (stringField(body, "path") !== undefined && uploaded.completion !== undefined) {
|
|
334
|
+
// The complete saved the library file at the path and answered it, so there is nothing to read back.
|
|
335
|
+
const schema = findCommand(row.noun, "complete-upload")?.responseSchema;
|
|
336
|
+
return { body: await readRawBody(uploaded.completion, schema), schema };
|
|
337
|
+
}
|
|
341
338
|
return await readStoredFile(row, invocation, apis, target, uploaded.fileId);
|
|
342
339
|
}
|
|
340
|
+
/** The row's own create, carrying the file's text as `content`; it answers `{file}`, and the file is printed. */
|
|
341
|
+
async function createInline(row, invocation, body, apis, target) {
|
|
342
|
+
const response = await dispatch(row.operationId)(apis, {
|
|
343
|
+
pathParams: invocation.pathParams,
|
|
344
|
+
query: {},
|
|
345
|
+
body: toWireValue(body),
|
|
346
|
+
...(target.organizationId !== undefined ? { organizationId: target.organizationId } : {}),
|
|
347
|
+
});
|
|
348
|
+
const answer = await readRawBody(response.raw, row.responseSchema);
|
|
349
|
+
const file = typeof answer === "object" && answer !== null && !Array.isArray(answer)
|
|
350
|
+
? answer["file"]
|
|
351
|
+
: undefined;
|
|
352
|
+
if (file === undefined || row.responseSchema?.kind !== "object") {
|
|
353
|
+
return { body: answer, schema: row.responseSchema };
|
|
354
|
+
}
|
|
355
|
+
return { body: file, schema: row.responseSchema.fields["file"]?.schema };
|
|
356
|
+
}
|
|
343
357
|
/**
|
|
344
358
|
* The stored row as this noun's own read answers it, printed bare as every other create prints what it
|
|
345
359
|
* made, so `.id` names the new file. There is no upload locator to print beside it: the bytes are pushed.
|
|
@@ -354,7 +368,7 @@ async function readStoredFile(row, invocation, apis, target, fileId) {
|
|
|
354
368
|
query: {},
|
|
355
369
|
...(target.organizationId !== undefined ? { organizationId: target.organizationId } : {}),
|
|
356
370
|
});
|
|
357
|
-
const schema =
|
|
371
|
+
const schema = read.responseSchema;
|
|
358
372
|
return { body: await readRawBody(response.raw, schema), schema };
|
|
359
373
|
}
|
|
360
374
|
/**
|
|
@@ -505,6 +519,19 @@ function sameItemPath(sibling, row) {
|
|
|
505
519
|
}
|
|
506
520
|
return sibling.positionals.every((positional, index) => positional.param === row.positionals[index]?.param);
|
|
507
521
|
}
|
|
522
|
+
/**
|
|
523
|
+
* The platform answers an HTML or Markdown file's content as Markdown when the request accepts it, and the
|
|
524
|
+
* generated client sends no `Accept` of its own, so this one header is the whole of `--markdown`.
|
|
525
|
+
*/
|
|
526
|
+
function acceptingMarkdown(fetchApi) {
|
|
527
|
+
const inner = (fetchApi ?? fetch);
|
|
528
|
+
const wrapped = (input, init) => {
|
|
529
|
+
const headers = new Headers(init?.headers);
|
|
530
|
+
headers.set("Accept", "text/markdown");
|
|
531
|
+
return inner(input, { ...init, headers });
|
|
532
|
+
};
|
|
533
|
+
return wrapped;
|
|
534
|
+
}
|
|
508
535
|
function dispatch(operationId) {
|
|
509
536
|
const entry = CLI_DISPATCH[operationId];
|
|
510
537
|
if (entry === undefined) {
|
package/dist/cli/api/raw.d.ts
CHANGED
|
@@ -57,7 +57,9 @@ export declare function assertPlatformOrigin(baseUrl: string, path: string): str
|
|
|
57
57
|
/**
|
|
58
58
|
* `undefined` when this build cannot resolve the path — an endpoint newer than the CLI, or one outside the
|
|
59
59
|
* public document. Matching is method-sensitive and template-aware, with query string and fragment dropped
|
|
60
|
-
* first: they select data, never an operation.
|
|
60
|
+
* first: they select data, never an operation. Where two templates match, a literal segment beats a
|
|
61
|
+
* parameter at the first place they differ, so `/files/matches` is the search and not a read of a file
|
|
62
|
+
* whose id is `matches`.
|
|
61
63
|
*/
|
|
62
64
|
export declare function resolveRawCommand(method: string, path: string): CommandRow | undefined;
|
|
63
65
|
/**
|
package/dist/cli/api/raw.js
CHANGED
|
@@ -17,7 +17,7 @@ import { sendRawPlatformRequest } from "../core/platform-client.js";
|
|
|
17
17
|
import { redact, redactBinaryBody, writeResponseFile, } from "../core/redaction.js";
|
|
18
18
|
import { resolveApprovalPolicy } from "../core/approval-policy.js";
|
|
19
19
|
import { classifyWrite, ensureApproved, IMPERATIVE_APPROVAL_DEFAULT, printApprovalBypassNotice, } from "./approval.js";
|
|
20
|
-
import { readRawBody
|
|
20
|
+
import { readRawBody } from "./execute.js";
|
|
21
21
|
/**
|
|
22
22
|
* Deliberately the five that address resources: `OPTIONS`/`HEAD`/`TRACE` answer questions about the
|
|
23
23
|
* transport rather than the platform, and accepting one only converts a typo into a confusing 405.
|
|
@@ -92,8 +92,8 @@ async function sendRawCall(method, path, options, deps, ctx) {
|
|
|
92
92
|
});
|
|
93
93
|
// The reader and {@link redactRawBody} have to agree about which responses are bare content, or the body
|
|
94
94
|
// the redactor would stream raw is one the reader has already parsed into something else — so both are
|
|
95
|
-
// handed the SAME schema, the
|
|
96
|
-
const responseSchema =
|
|
95
|
+
// handed the SAME schema, the row's own, exactly as the typed executor is.
|
|
96
|
+
const responseSchema = row?.responseSchema;
|
|
97
97
|
const body = await readRawBody(response, responseSchema);
|
|
98
98
|
if (options.outputFile !== undefined) {
|
|
99
99
|
writeResponseFile(options.outputFile, body, io);
|
|
@@ -189,11 +189,38 @@ function readRequestDocument(method, options, deps) {
|
|
|
189
189
|
/**
|
|
190
190
|
* `undefined` when this build cannot resolve the path — an endpoint newer than the CLI, or one outside the
|
|
191
191
|
* public document. Matching is method-sensitive and template-aware, with query string and fragment dropped
|
|
192
|
-
* first: they select data, never an operation.
|
|
192
|
+
* first: they select data, never an operation. Where two templates match, a literal segment beats a
|
|
193
|
+
* parameter at the first place they differ, so `/files/matches` is the search and not a read of a file
|
|
194
|
+
* whose id is `matches`.
|
|
193
195
|
*/
|
|
194
196
|
export function resolveRawCommand(method, path) {
|
|
195
197
|
const segments = pathSegments(path);
|
|
196
|
-
|
|
198
|
+
let best;
|
|
199
|
+
for (const row of CLI_COMMANDS) {
|
|
200
|
+
if (row.method !== method || !matchesTemplate(row.path, segments)) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (best === undefined || moreLiteral(row.path, best.path)) {
|
|
204
|
+
best = row;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return best;
|
|
208
|
+
}
|
|
209
|
+
/** True when `template` has a literal segment where `other`, of the same length, first has a parameter. */
|
|
210
|
+
function moreLiteral(template, other) {
|
|
211
|
+
const parts = pathSegments(template);
|
|
212
|
+
const otherParts = pathSegments(other);
|
|
213
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
214
|
+
const literal = !isParameter(parts[index] ?? "");
|
|
215
|
+
const otherLiteral = !isParameter(otherParts[index] ?? "");
|
|
216
|
+
if (literal !== otherLiteral) {
|
|
217
|
+
return literal;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
function isParameter(segment) {
|
|
223
|
+
return segment.startsWith("{") && segment.endsWith("}");
|
|
197
224
|
}
|
|
198
225
|
function pathSegments(path) {
|
|
199
226
|
const withoutQuery = path.split(/[?#]/)[0] ?? "";
|
|
@@ -205,7 +232,7 @@ function matchesTemplate(template, segments) {
|
|
|
205
232
|
if (parts.length !== segments.length) {
|
|
206
233
|
return false;
|
|
207
234
|
}
|
|
208
|
-
return parts.every((part, index) =>
|
|
235
|
+
return parts.every((part, index) => isParameter(part) || part === segments[index]);
|
|
209
236
|
}
|
|
210
237
|
/**
|
|
211
238
|
* Reported as FULL paths, so the refusal names a property a reader can find in their file.
|
package/dist/cli/api/register.js
CHANGED
|
@@ -49,6 +49,7 @@ function registerRow(group, row, options) {
|
|
|
49
49
|
reveal: values["reveal"] === true,
|
|
50
50
|
outputFile: optional(values["outputFile"]),
|
|
51
51
|
file: optional(values["file"]),
|
|
52
|
+
markdown: values["markdown"] === true,
|
|
52
53
|
approveSecrets: values["approveSecrets"] === true ? true : undefined,
|
|
53
54
|
requireApproval: optional(values["requireApproval"]),
|
|
54
55
|
profile: optional(values["profile"]),
|
|
@@ -99,11 +100,15 @@ function registerConventionFlags(command, row) {
|
|
|
99
100
|
command.option("--reveal", "print credential material in full instead of masking it");
|
|
100
101
|
command.option("--output-file <path>", "write the full raw response to <path> (mode 0600; JSON, or the bytes for file content)");
|
|
101
102
|
if (rowTakesFileOption(row)) {
|
|
102
|
-
command.option("--file <path>", "
|
|
103
|
+
command.option("--file <path>", "store the file at <path> (text up to 256 KiB inline, anything else uploaded) and let this create " +
|
|
104
|
+
"name it, instead of saying where its bytes are");
|
|
103
105
|
}
|
|
104
106
|
if (row.pageWindow !== undefined) {
|
|
105
107
|
command.option("--all-pages", "follow every page and concatenate the items");
|
|
106
108
|
}
|
|
109
|
+
if (row.markdownResponse === true) {
|
|
110
|
+
command.option("--markdown", "ask for the content as Markdown (sends Accept: text/markdown)");
|
|
111
|
+
}
|
|
107
112
|
if (row.method !== "GET") {
|
|
108
113
|
command.option("--approve-secrets", "pre-approve the sensitive-write confirmation");
|
|
109
114
|
// A read verb gets NEITHER of these: it has no approval to configure, so the flag is an unknown option
|
package/dist/cli/preview.d.ts
CHANGED
|
@@ -39,8 +39,9 @@ export declare function previewCommand(file: string, options?: PreviewCommandOpt
|
|
|
39
39
|
/**
|
|
40
40
|
* The policy the artifacts host serves a published page under, mirrored here so a page that reaches for
|
|
41
41
|
* something production forbids — an outbound fetch, a script from an unlisted source, a nested frame —
|
|
42
|
-
* fails in the rehearsal rather than after it is published.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
42
|
+
* fails in the rehearsal rather than after it is published. Two substitutions: the frame ancestor is the
|
|
43
|
+
* console's origin there and the preview's own here, and the store origin, where the host points what a page
|
|
44
|
+
* loads by a relative path, is blank here, as a rehearsal has no store and points nothing at one.
|
|
45
|
+
* `test/cli/preview.test.ts` reads the platform's own source and fails when the two drift.
|
|
45
46
|
*/
|
|
46
|
-
export declare function pageContentSecurityPolicy(consoleOrigin: string): string;
|
|
47
|
+
export declare function pageContentSecurityPolicy(consoleOrigin: string, storeOrigin?: string): string;
|
package/dist/cli/preview.js
CHANGED
|
@@ -180,12 +180,15 @@ const OPENING_BYTES = 1024;
|
|
|
180
180
|
/**
|
|
181
181
|
* The policy the artifacts host serves a published page under, mirrored here so a page that reaches for
|
|
182
182
|
* something production forbids — an outbound fetch, a script from an unlisted source, a nested frame —
|
|
183
|
-
* fails in the rehearsal rather than after it is published.
|
|
184
|
-
*
|
|
185
|
-
*
|
|
183
|
+
* fails in the rehearsal rather than after it is published. Two substitutions: the frame ancestor is the
|
|
184
|
+
* console's origin there and the preview's own here, and the store origin, where the host points what a page
|
|
185
|
+
* loads by a relative path, is blank here, as a rehearsal has no store and points nothing at one.
|
|
186
|
+
* `test/cli/preview.test.ts` reads the platform's own source and fails when the two drift.
|
|
186
187
|
*/
|
|
187
|
-
export function pageContentSecurityPolicy(consoleOrigin) {
|
|
188
|
+
export function pageContentSecurityPolicy(consoleOrigin, storeOrigin = "") {
|
|
188
189
|
const ancestor = consoleOrigin.trim() === "" ? "'none'" : consoleOrigin.trim();
|
|
190
|
+
const store = storeOrigin.trim();
|
|
191
|
+
const media = store === "" ? "'none'" : store;
|
|
189
192
|
return [
|
|
190
193
|
"default-src 'none'",
|
|
191
194
|
"sandbox allow-scripts allow-popups",
|
|
@@ -194,11 +197,14 @@ export function pageContentSecurityPolicy(consoleOrigin) {
|
|
|
194
197
|
"frame-src 'none'",
|
|
195
198
|
`frame-ancestors ${ancestor}`,
|
|
196
199
|
"script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net" +
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
200
|
+
` https://cdn.tailwindcss.com https://code.jquery.com ${store}`,
|
|
201
|
+
`style-src 'self' 'unsafe-inline' https://fonts.googleapis.com ${store}`,
|
|
202
|
+
`img-src 'self' data: ${store}`,
|
|
203
|
+
`media-src ${media}`,
|
|
204
|
+
`font-src https://fonts.gstatic.com ${store}`,
|
|
205
|
+
]
|
|
206
|
+
.map((directive) => directive.trim())
|
|
207
|
+
.join("; ");
|
|
202
208
|
}
|
|
203
209
|
function originOf(server, host) {
|
|
204
210
|
return `http://${host ?? "127.0.0.1"}:${server.address().port}`;
|
package/dist/cli/upload.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { CommandRow } from "../generated/cli-commands.js";
|
|
2
2
|
import type { CliApis } from "../generated/cli-dispatch.js";
|
|
3
3
|
import type { CliIo } from "./core/output.js";
|
|
4
|
+
/** The most `content` the platform takes inline, counted in UTF-8 bytes. */
|
|
5
|
+
export declare const INLINE_LIMIT_BYTES: number;
|
|
4
6
|
/**
|
|
5
7
|
* A row `--file` attaches to, decided by what the ROW declares rather than by its noun and verb, because
|
|
6
8
|
* this file has no business knowing either.
|
|
@@ -21,6 +23,14 @@ export interface UploadedFile {
|
|
|
21
23
|
readonly filename: string;
|
|
22
24
|
/** True when the platform already held these bytes, so this call stored nothing. */
|
|
23
25
|
readonly alreadyStored: boolean;
|
|
26
|
+
/** The complete call's unread answer, when one was sent: for a save by path, the library file it saved. */
|
|
27
|
+
readonly completion?: Response;
|
|
28
|
+
}
|
|
29
|
+
/** A local file a create can carry as its own `content`, with the name and type the create gives it. */
|
|
30
|
+
export interface InlineFile {
|
|
31
|
+
readonly content: string;
|
|
32
|
+
readonly filename: string;
|
|
33
|
+
readonly contentType: string;
|
|
24
34
|
}
|
|
25
35
|
export interface UploadOptions {
|
|
26
36
|
/**
|
|
@@ -36,7 +46,23 @@ export interface UploadOptions {
|
|
|
36
46
|
readonly teamId?: string | undefined;
|
|
37
47
|
readonly filename?: string | undefined;
|
|
38
48
|
readonly contentType?: string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* The create's own fields when the upload is that create. A `path` among them makes the declare an upload
|
|
51
|
+
* intent, which is completed even when the platform answers no plan, since only the complete saves it.
|
|
52
|
+
*/
|
|
53
|
+
readonly request?: Readonly<Record<string, unknown>> | undefined;
|
|
39
54
|
}
|
|
55
|
+
/**
|
|
56
|
+
* The file's text when a create can carry it inline, unchanged: at most {@link INLINE_LIMIT_BYTES} of UTF-8,
|
|
57
|
+
* with no NUL, which the platform cannot store as text, and not blank. A byte order mark stays in the text,
|
|
58
|
+
* so the stored bytes are the file's own.
|
|
59
|
+
*/
|
|
60
|
+
export declare function inlineFileOf(path: string, named?: {
|
|
61
|
+
filename?: string | undefined;
|
|
62
|
+
contentType?: string | undefined;
|
|
63
|
+
}): InlineFile | undefined;
|
|
64
|
+
/** Stores the text as a fixed file in one create, for a create that names it by id next. */
|
|
65
|
+
export declare function storeInlineFile(file: InlineFile, options: Pick<UploadOptions, "apis" | "io" | "organizationId" | "teamId">): Promise<string>;
|
|
40
66
|
export declare function uploadFile(path: string, options: UploadOptions): Promise<UploadedFile>;
|
|
41
67
|
/**
|
|
42
68
|
* What the file is, read off its name. The store keeps whatever it is told, and the console decides how to
|
package/dist/cli/upload.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
// The whole behaviour of `--file`: bytes on disk become a stored platform file
|
|
2
|
-
//
|
|
3
|
-
// and hand the caller the file id the
|
|
1
|
+
// The whole behaviour of `--file`: bytes on disk become a stored platform file. A small text travels inline
|
|
2
|
+
// as the create's own `content`; anything else takes the four steps the platform's upload contract names —
|
|
3
|
+
// declare the address, push the bytes the answer authorizes, complete, and hand the caller the file id the
|
|
4
|
+
// create then names.
|
|
4
5
|
//
|
|
5
|
-
// Two rules here are not style.
|
|
6
|
-
// memory uploads exactly like a small one. And a presigned request is sent through the BARE
|
|
7
|
-
// than the platform client: the signed headers travel verbatim, the platform bearer never
|
|
8
|
-
// object store, and the CLI's 60-second request bound — right for an API call, wrong for a long
|
|
9
|
-
// does not apply.
|
|
6
|
+
// Two rules here are not style. A declared upload's bytes are STREAMED from disk in both shapes, so a file
|
|
7
|
+
// larger than memory uploads exactly like a small one. And a presigned request is sent through the BARE
|
|
8
|
+
// fetch rather than the platform client: the signed headers travel verbatim, the platform bearer never
|
|
9
|
+
// reaches the object store, and the CLI's 60-second request bound — right for an API call, wrong for a long
|
|
10
|
+
// transfer — does not apply.
|
|
10
11
|
import { createHash } from "node:crypto";
|
|
11
|
-
import { createReadStream, statSync } from "node:fs";
|
|
12
|
+
import { createReadStream, readFileSync, statSync } from "node:fs";
|
|
12
13
|
import { basename, extname } from "node:path";
|
|
13
14
|
import { Readable } from "node:stream";
|
|
14
15
|
/** Parts in flight at once. */
|
|
@@ -24,6 +25,13 @@ const DECLARE_ATTEMPTS = 2;
|
|
|
24
25
|
const CONTENT_SOURCE_FIELDS = ["content", "fileId", "url"];
|
|
25
26
|
/** The platform's own default for a declared upload that named no type. */
|
|
26
27
|
const DEFAULT_CONTENT_TYPE = "application/octet-stream";
|
|
28
|
+
/** The most `content` the platform takes inline, counted in UTF-8 bytes. */
|
|
29
|
+
export const INLINE_LIMIT_BYTES = 256 * 1024;
|
|
30
|
+
/**
|
|
31
|
+
* Blank as the platform judges `content`, widened to JavaScript's own whitespace, so a text the platform
|
|
32
|
+
* would refuse as blank goes through a declared upload instead.
|
|
33
|
+
*/
|
|
34
|
+
const BLANK = /^[\s\u001c-\u001f]*$/;
|
|
27
35
|
/**
|
|
28
36
|
* A row `--file` attaches to, decided by what the ROW declares rather than by its noun and verb, because
|
|
29
37
|
* this file has no business knowing either.
|
|
@@ -50,19 +58,52 @@ class UploadError extends Error {
|
|
|
50
58
|
/** Raised by a push whose signature the store no longer honours; the only failure a fresh declare repairs. */
|
|
51
59
|
class PlanExpiredError extends Error {
|
|
52
60
|
}
|
|
61
|
+
/**
|
|
62
|
+
* The file's text when a create can carry it inline, unchanged: at most {@link INLINE_LIMIT_BYTES} of UTF-8,
|
|
63
|
+
* with no NUL, which the platform cannot store as text, and not blank. A byte order mark stays in the text,
|
|
64
|
+
* so the stored bytes are the file's own.
|
|
65
|
+
*/
|
|
66
|
+
export function inlineFileOf(path, named = {}) {
|
|
67
|
+
if (statOf(path).size > INLINE_LIMIT_BYTES) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
const bytes = readFileSync(path);
|
|
71
|
+
if (bytes.byteLength > INLINE_LIMIT_BYTES) {
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
let content;
|
|
75
|
+
try {
|
|
76
|
+
content = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
if (content.includes("\u0000") || BLANK.test(content)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const filename = named.filename ?? basename(path);
|
|
85
|
+
return { content, filename, contentType: named.contentType ?? contentTypeOf(filename) };
|
|
86
|
+
}
|
|
87
|
+
/** Stores the text as a fixed file in one create, for a create that names it by id next. */
|
|
88
|
+
export async function storeInlineFile(file, options) {
|
|
89
|
+
const stored = await options.apis.files.createFile({
|
|
90
|
+
createFileRequest: { ...file, ...(options.teamId !== undefined ? { teamId: options.teamId } : {}) },
|
|
91
|
+
...(options.organizationId !== undefined ? { xOrganizationId: options.organizationId } : {}),
|
|
92
|
+
});
|
|
93
|
+
options.io.err(`note: stored ${file.filename} (${Buffer.byteLength(file.content, "utf8")} bytes) inline as ${stored.file.id}.`);
|
|
94
|
+
return stored.file.id;
|
|
95
|
+
}
|
|
53
96
|
export async function uploadFile(path, options) {
|
|
54
97
|
const stats = statOf(path);
|
|
55
|
-
if (stats.size === 0) {
|
|
56
|
-
throw new UploadError(`--file ${path} is empty; there are no bytes to upload.`);
|
|
57
|
-
}
|
|
58
98
|
const filename = options.filename ?? basename(path);
|
|
59
99
|
const contentType = options.contentType ?? contentTypeOf(filename);
|
|
60
100
|
const sha256 = await sha256Of(path);
|
|
101
|
+
const intent = typeof options.request?.["path"] === "string";
|
|
61
102
|
for (let attempt = 1;; attempt += 1) {
|
|
62
103
|
const declared = await declare(path, { ...options, filename, contentType }, sha256, stats.size);
|
|
63
104
|
try {
|
|
64
105
|
const parts = await push(declared, path, stats.size, options);
|
|
65
|
-
await complete(declared, parts, options);
|
|
106
|
+
const completion = await complete(declared, intent ? (parts ?? []) : parts, options);
|
|
66
107
|
report(declared, parts, filename, stats.size, options.io);
|
|
67
108
|
return {
|
|
68
109
|
fileId: declared.file.id,
|
|
@@ -71,6 +112,7 @@ export async function uploadFile(path, options) {
|
|
|
71
112
|
contentType,
|
|
72
113
|
filename,
|
|
73
114
|
alreadyStored: parts === undefined,
|
|
115
|
+
...(completion !== undefined ? { completion } : {}),
|
|
74
116
|
};
|
|
75
117
|
}
|
|
76
118
|
catch (failure) {
|
|
@@ -106,6 +148,7 @@ async function sha256Of(path) {
|
|
|
106
148
|
async function declare(path, options, sha256, sizeBytes) {
|
|
107
149
|
return await options.apis.files.createFile({
|
|
108
150
|
createFileRequest: {
|
|
151
|
+
...options.request,
|
|
109
152
|
sha256,
|
|
110
153
|
sizeBytes,
|
|
111
154
|
filename: options.filename,
|
|
@@ -185,7 +228,10 @@ async function putRange(locator, path, start, end, length, options) {
|
|
|
185
228
|
for (const [name, value] of Object.entries(locator.headers)) {
|
|
186
229
|
headers.set(name, value);
|
|
187
230
|
}
|
|
188
|
-
|
|
231
|
+
// A read stream refuses an empty range, so an empty file pushes an empty body.
|
|
232
|
+
const body = length === 0
|
|
233
|
+
? new Uint8Array(0)
|
|
234
|
+
: Readable.toWeb(createReadStream(path, { start, end }));
|
|
189
235
|
let response;
|
|
190
236
|
try {
|
|
191
237
|
response = await options.fetchApi(locator.url, {
|
|
@@ -213,14 +259,15 @@ async function putRange(locator, path, start, end, length, options) {
|
|
|
213
259
|
}
|
|
214
260
|
async function complete(declared, parts, options) {
|
|
215
261
|
if (parts === undefined) {
|
|
216
|
-
return;
|
|
262
|
+
return undefined;
|
|
217
263
|
}
|
|
218
264
|
// `files complete-upload` generates no flags of its own, so this is the one caller of the SDK method.
|
|
219
|
-
await options.apis.files.
|
|
265
|
+
const response = await options.apis.files.completeFileUploadRaw({
|
|
220
266
|
id: declared.file.id,
|
|
221
267
|
completeFileUploadRequest: { parts },
|
|
222
268
|
...(options.organizationId !== undefined ? { xOrganizationId: options.organizationId } : {}),
|
|
223
269
|
});
|
|
270
|
+
return response.raw;
|
|
224
271
|
}
|
|
225
272
|
/** One line for the whole transfer, whatever it took — a line per part would bury the command's own output. */
|
|
226
273
|
function report(declared, parts, filename, sizeBytes, io) {
|
|
@@ -241,6 +288,7 @@ export function contentTypeOf(filename) {
|
|
|
241
288
|
return CONTENT_TYPES[extname(filename).toLowerCase()] ?? DEFAULT_CONTENT_TYPE;
|
|
242
289
|
}
|
|
243
290
|
const CONTENT_TYPES = {
|
|
291
|
+
".uri": "text/uri-list",
|
|
244
292
|
".html": "text/html",
|
|
245
293
|
".htm": "text/html",
|
|
246
294
|
".css": "text/css",
|
|
@@ -55,6 +55,11 @@ export interface CommandRow {
|
|
|
55
55
|
readonly kind: "page" | "cursor";
|
|
56
56
|
readonly param: string;
|
|
57
57
|
};
|
|
58
|
+
/**
|
|
59
|
+
* Present when a success response declares `text/markdown`, so the command takes `--markdown`, which sends
|
|
60
|
+
* `Accept: text/markdown`.
|
|
61
|
+
*/
|
|
62
|
+
readonly markdownResponse?: true;
|
|
58
63
|
}
|
|
59
64
|
/** One flag. A credential field registers the `-from-env`/`-from-command-line` PAIR and no plain flag. */
|
|
60
65
|
export interface FlagSpec {
|