@asteroidai/cli 1.9.2 → 1.9.11
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/dist/asteroid.js +1140 -316
- package/dist/core.js +869 -59
- package/package.json +3 -2
- package/src/commands.ts +13 -0
- package/src/core.ts +31 -0
- package/src/main.ts +2 -2
- package/src/output.test.ts +19 -0
- package/src/output.ts +11 -9
- package/src/program.test.ts +19 -0
- package/src/program.ts +15 -1
- package/src/reference.ts +1 -0
- package/src/run.test.ts +55 -2
- package/src/run.ts +62 -21
- package/src/sync/activities.ts +80 -0
- package/src/sync/commands.ts +111 -0
- package/src/sync/execution-pull.ts +273 -0
- package/src/sync/node-files.ts +47 -0
- package/src/sync/paths.ts +45 -0
- package/src/sync/run.ts +102 -0
- package/src/sync/sync.test.ts +463 -0
- package/src/sync/types.ts +58 -0
- package/src/sync/workflow-pull.ts +182 -0
- package/src/sync/workflow-push.ts +155 -0
- package/src/types.ts +2 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import { type Activity, formatActivitiesMarkdown } from "./activities";
|
|
2
|
+
import {
|
|
3
|
+
executionDirName,
|
|
4
|
+
isContained,
|
|
5
|
+
join,
|
|
6
|
+
matchesGlob,
|
|
7
|
+
workflowRoot,
|
|
8
|
+
} from "./paths";
|
|
9
|
+
import { get, type Sync } from "./types";
|
|
10
|
+
|
|
11
|
+
export interface Execution {
|
|
12
|
+
id: string;
|
|
13
|
+
status: string;
|
|
14
|
+
workflowId: string;
|
|
15
|
+
workflowVersionId?: string;
|
|
16
|
+
workflowName?: string;
|
|
17
|
+
inputs?: unknown;
|
|
18
|
+
executionResult?: { outcome?: string; reasoning?: string } | null;
|
|
19
|
+
createdAt: string;
|
|
20
|
+
terminalAt?: string | null;
|
|
21
|
+
duration?: number | null;
|
|
22
|
+
scriptFailures?: { nodeName?: string; error?: string }[] | null;
|
|
23
|
+
purgedAt?: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface ExecutionFile {
|
|
27
|
+
id: string;
|
|
28
|
+
fileName: string;
|
|
29
|
+
filePath: string;
|
|
30
|
+
fileSize: number;
|
|
31
|
+
directory: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const DIRECTORIES = ["workspace", "downloads", "shared"] as const;
|
|
35
|
+
|
|
36
|
+
export interface ExecutionPullOptions {
|
|
37
|
+
executionId: string;
|
|
38
|
+
root: string;
|
|
39
|
+
dir?: (execution: Execution) => string | undefined;
|
|
40
|
+
files?: boolean;
|
|
41
|
+
activities?: boolean;
|
|
42
|
+
glob?: string;
|
|
43
|
+
maxFileBytes?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface ExecutionPullResult {
|
|
47
|
+
execution: Execution;
|
|
48
|
+
dir: string;
|
|
49
|
+
written: string[];
|
|
50
|
+
downloaded: number;
|
|
51
|
+
candidates: number;
|
|
52
|
+
skipped: string[];
|
|
53
|
+
failed: string[];
|
|
54
|
+
skippedBy?: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function relativeFilePath(
|
|
58
|
+
file: Pick<ExecutionFile, "filePath" | "directory">,
|
|
59
|
+
): string {
|
|
60
|
+
const at = file.filePath.indexOf(`/${file.directory}/`);
|
|
61
|
+
if (at === -1)
|
|
62
|
+
return `${file.directory}/${file.filePath.split("/").pop() ?? "unknown"}`;
|
|
63
|
+
return file.filePath
|
|
64
|
+
.slice(at + 1)
|
|
65
|
+
.replace(/^workspace\/\.execution-context\//, "");
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function executionDir(
|
|
69
|
+
options: ExecutionPullOptions,
|
|
70
|
+
execution: Execution,
|
|
71
|
+
): string {
|
|
72
|
+
const chosen = options.dir?.(execution);
|
|
73
|
+
if (chosen) return chosen;
|
|
74
|
+
const root = workflowRoot(
|
|
75
|
+
options.root,
|
|
76
|
+
execution.workflowName ?? execution.workflowId,
|
|
77
|
+
);
|
|
78
|
+
return join(root, "executions", executionDirName(execution.id));
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function writeText(sync: Sync, path: string, text: string) {
|
|
82
|
+
await sync.files.write(path, Buffer.from(text, "utf8"));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function writeActivities(sync: Sync, dir: string, execution: Execution) {
|
|
86
|
+
try {
|
|
87
|
+
const activities = (await sync.remote.json(
|
|
88
|
+
get(`/agents/v2/executions/${execution.id}/activities`, { order: "asc" }),
|
|
89
|
+
)) as Activity[];
|
|
90
|
+
if (activities.length === 0) return false;
|
|
91
|
+
const markdown = formatActivitiesMarkdown(
|
|
92
|
+
activities,
|
|
93
|
+
execution.status,
|
|
94
|
+
execution.createdAt,
|
|
95
|
+
);
|
|
96
|
+
await writeText(sync, join(dir, "activities.md"), markdown);
|
|
97
|
+
return true;
|
|
98
|
+
} catch {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function listFiles(
|
|
104
|
+
sync: Sync,
|
|
105
|
+
executionId: string,
|
|
106
|
+
): Promise<ExecutionFile[]> {
|
|
107
|
+
const listing = (await sync.remote.json(
|
|
108
|
+
get(`/agents/v2/executions/${executionId}/files`),
|
|
109
|
+
)) as {
|
|
110
|
+
directories: Partial<Record<string, ExecutionFile[]>>;
|
|
111
|
+
};
|
|
112
|
+
return DIRECTORIES.flatMap((name) => listing.directories[name] ?? []);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function download(sync: Sync, executionId: string, file: ExecutionFile) {
|
|
116
|
+
const path = `/agents/v2/executions/${executionId}/files/${file.id}/signed-url`;
|
|
117
|
+
const { url } = (await sync.remote.json(get(path))) as { url: string };
|
|
118
|
+
return sync.remote.download(url);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function selectFiles(
|
|
122
|
+
files: ExecutionFile[],
|
|
123
|
+
options: ExecutionPullOptions,
|
|
124
|
+
skipped: string[],
|
|
125
|
+
) {
|
|
126
|
+
return files.filter((file) => {
|
|
127
|
+
const path = relativeFilePath(file);
|
|
128
|
+
if (!isContained(path)) skipped.push(`${path} (unsafe path)`);
|
|
129
|
+
else if (options.glob !== undefined && !matchesGlob(path, options.glob))
|
|
130
|
+
skipped.push(`${path} (excluded by --files)`);
|
|
131
|
+
else if (
|
|
132
|
+
options.maxFileBytes !== undefined &&
|
|
133
|
+
file.fileSize > options.maxFileBytes
|
|
134
|
+
)
|
|
135
|
+
skipped.push(`${path} (${file.fileSize} bytes, over the size limit)`);
|
|
136
|
+
else return true;
|
|
137
|
+
return false;
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function pullFiles(
|
|
142
|
+
sync: Sync,
|
|
143
|
+
dir: string,
|
|
144
|
+
options: ExecutionPullOptions,
|
|
145
|
+
) {
|
|
146
|
+
const skipped: string[] = [];
|
|
147
|
+
const failed: string[] = [];
|
|
148
|
+
let files: ExecutionFile[];
|
|
149
|
+
try {
|
|
150
|
+
files = await listFiles(sync, options.executionId);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
153
|
+
return {
|
|
154
|
+
downloaded: 0,
|
|
155
|
+
candidates: 0,
|
|
156
|
+
skipped,
|
|
157
|
+
failed: [`file listing (${reason})`],
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
let downloaded = 0;
|
|
161
|
+
await Promise.all(
|
|
162
|
+
selectFiles(files, options, skipped).map(async (file) => {
|
|
163
|
+
try {
|
|
164
|
+
const bytes = await download(sync, options.executionId, file);
|
|
165
|
+
await sync.files.write(join(dir, relativeFilePath(file)), bytes);
|
|
166
|
+
downloaded++;
|
|
167
|
+
} catch (error) {
|
|
168
|
+
failed.push(
|
|
169
|
+
`${file.fileName} (${error instanceof Error ? error.message : String(error)})`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
}),
|
|
173
|
+
);
|
|
174
|
+
return { downloaded, candidates: files.length, skipped, failed };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function pullExecution(
|
|
178
|
+
sync: Sync,
|
|
179
|
+
options: ExecutionPullOptions,
|
|
180
|
+
): Promise<ExecutionPullResult> {
|
|
181
|
+
const execution = (await sync.remote.json(
|
|
182
|
+
get(`/agents/v2/executions/${options.executionId}`),
|
|
183
|
+
)) as Execution;
|
|
184
|
+
const dir = executionDir(options, execution);
|
|
185
|
+
await writeText(
|
|
186
|
+
sync,
|
|
187
|
+
join(dir, "summary.json"),
|
|
188
|
+
JSON.stringify(execution, null, 2),
|
|
189
|
+
);
|
|
190
|
+
const result = {
|
|
191
|
+
execution,
|
|
192
|
+
dir,
|
|
193
|
+
written: ["summary.json"],
|
|
194
|
+
downloaded: 0,
|
|
195
|
+
candidates: 0,
|
|
196
|
+
skipped: [],
|
|
197
|
+
failed: [],
|
|
198
|
+
};
|
|
199
|
+
if (execution.purgedAt) return result;
|
|
200
|
+
if (
|
|
201
|
+
options.activities !== false &&
|
|
202
|
+
(await writeActivities(sync, dir, execution))
|
|
203
|
+
)
|
|
204
|
+
result.written.push("activities.md");
|
|
205
|
+
if (options.files === false) {
|
|
206
|
+
return {
|
|
207
|
+
...result,
|
|
208
|
+
skippedBy: options.activities === false ? "--summary-only" : "--no-files",
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
return { ...result, ...(await pullFiles(sync, dir, options)) };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function filesLine(result: ExecutionPullResult): string {
|
|
215
|
+
const parts = [...result.written];
|
|
216
|
+
if (result.downloaded > 0) parts.push(`${result.downloaded} downloaded`);
|
|
217
|
+
const counted = `Files: ${result.written.length + result.downloaded} written (${parts.join(", ")})`;
|
|
218
|
+
if (result.skippedBy)
|
|
219
|
+
return `${counted}; downloads skipped (${result.skippedBy})`;
|
|
220
|
+
if (result.candidates === 0) return `${counted}; no execution files matched`;
|
|
221
|
+
return counted;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function failures(execution: Execution): string | null {
|
|
225
|
+
const list = execution.scriptFailures ?? [];
|
|
226
|
+
if (list.length === 0) return null;
|
|
227
|
+
const lines = list.map(
|
|
228
|
+
(f) =>
|
|
229
|
+
` - ${f.nodeName ?? "(unknown node)"}: ${(f.error ?? "").split("\n")[0]}`,
|
|
230
|
+
);
|
|
231
|
+
return `Script failures: ${list.length}\n${lines.join("\n")}`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function purged(result: ExecutionPullResult): string {
|
|
235
|
+
const { execution } = result;
|
|
236
|
+
return [
|
|
237
|
+
`Execution: ${execution.id}`,
|
|
238
|
+
`Status: ${execution.status}`,
|
|
239
|
+
`Purged: ${execution.purgedAt}`,
|
|
240
|
+
"This run was retention-purged. Activities, files, inputs and result are gone. Do not diagnose it.",
|
|
241
|
+
`Written to: ${result.dir}/`,
|
|
242
|
+
].join("\n");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function formatExecutionPull(result: ExecutionPullResult): string {
|
|
246
|
+
const { execution } = result;
|
|
247
|
+
if (execution.purgedAt) return purged(result);
|
|
248
|
+
const outcome = execution.executionResult;
|
|
249
|
+
return [
|
|
250
|
+
`Execution: ${execution.id}`,
|
|
251
|
+
`Status: ${execution.status}`,
|
|
252
|
+
`Workflow: ${execution.workflowName ?? execution.workflowId}`,
|
|
253
|
+
execution.workflowVersionId
|
|
254
|
+
? `Workflow version: ${execution.workflowVersionId}`
|
|
255
|
+
: null,
|
|
256
|
+
execution.inputs ? `Inputs: ${JSON.stringify(execution.inputs)}` : null,
|
|
257
|
+
outcome ? `Result: ${outcome.outcome}: ${outcome.reasoning}` : null,
|
|
258
|
+
`Created: ${execution.createdAt}`,
|
|
259
|
+
execution.terminalAt ? `Completed: ${execution.terminalAt}` : null,
|
|
260
|
+
execution.duration != null
|
|
261
|
+
? `Duration: ${execution.duration.toFixed(1)}s`
|
|
262
|
+
: null,
|
|
263
|
+
failures(execution),
|
|
264
|
+
filesLine(result),
|
|
265
|
+
result.skipped.length > 0
|
|
266
|
+
? `Skipped:\n${result.skipped.map((s) => ` - ${s}`).join("\n")}`
|
|
267
|
+
: null,
|
|
268
|
+
result.failed.length > 0 ? `Failed: ${result.failed.join(", ")}` : null,
|
|
269
|
+
`Written to: ${result.dir}/`,
|
|
270
|
+
]
|
|
271
|
+
.filter(Boolean)
|
|
272
|
+
.join("\n");
|
|
273
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, relative, resolve, sep } from "node:path";
|
|
3
|
+
|
|
4
|
+
import type { LocalFiles } from "./types";
|
|
5
|
+
|
|
6
|
+
function missing(error: unknown): boolean {
|
|
7
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT";
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async function walk(root: string, dir: string, out: string[]): Promise<void> {
|
|
11
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
12
|
+
const path = resolve(dir, entry.name);
|
|
13
|
+
if (entry.isDirectory()) await walk(root, path, out);
|
|
14
|
+
else if (entry.isFile())
|
|
15
|
+
out.push(relative(root, path).split(sep).join("/"));
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function nodeFiles(cwd: string): LocalFiles {
|
|
20
|
+
const at = (path: string) => resolve(cwd, path);
|
|
21
|
+
return {
|
|
22
|
+
async read(path) {
|
|
23
|
+
try {
|
|
24
|
+
return new Uint8Array(await readFile(at(path)));
|
|
25
|
+
} catch (error) {
|
|
26
|
+
if (missing(error)) return undefined;
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
async write(path, bytes) {
|
|
31
|
+
await mkdir(dirname(at(path)), { recursive: true });
|
|
32
|
+
await writeFile(at(path), bytes);
|
|
33
|
+
},
|
|
34
|
+
async remove(path) {
|
|
35
|
+
await rm(at(path), { force: true });
|
|
36
|
+
},
|
|
37
|
+
async list(dir) {
|
|
38
|
+
const out: string[] = [];
|
|
39
|
+
try {
|
|
40
|
+
await walk(at(dir), at(dir), out);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
if (!missing(error)) throw error;
|
|
43
|
+
}
|
|
44
|
+
return out.sort();
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { agentDirName } from "@asteroid/workflow-files/names";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
export {
|
|
5
|
+
agentDirName,
|
|
6
|
+
executionDirName,
|
|
7
|
+
workflowDirName,
|
|
8
|
+
} from "@asteroid/workflow-files/names";
|
|
9
|
+
|
|
10
|
+
export function join(...parts: string[]): string {
|
|
11
|
+
return parts
|
|
12
|
+
.filter((part) => part !== "")
|
|
13
|
+
.join("/")
|
|
14
|
+
.replace(/\/{2,}/g, "/");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function workflowRoot(root: string, workflowName: string): string {
|
|
18
|
+
return join(root, agentDirName(workflowName));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isContained(path: string): boolean {
|
|
22
|
+
if (path === "" || path.startsWith("/") || path.includes("\\")) return false;
|
|
23
|
+
return path.split("/").every((segment) => segment !== ".." && segment !== "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function sha256(bytes: Uint8Array): string {
|
|
27
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function matchesGlob(path: string, pattern: string): boolean {
|
|
31
|
+
let source = "";
|
|
32
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
33
|
+
const ch = pattern[i];
|
|
34
|
+
if (ch === "*" && pattern[i + 1] === "*" && pattern[i + 2] === "/") {
|
|
35
|
+
source += "(?:.*/)?";
|
|
36
|
+
i += 2;
|
|
37
|
+
} else if (ch === "*" && pattern[i + 1] === "*") {
|
|
38
|
+
source += ".*";
|
|
39
|
+
i += 1;
|
|
40
|
+
} else if (ch === "*") source += "[^/]*";
|
|
41
|
+
else if (ch === "?") source += "[^/]";
|
|
42
|
+
else source += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
43
|
+
}
|
|
44
|
+
return new RegExp(`^${source}$`).test(path);
|
|
45
|
+
}
|
package/src/sync/run.ts
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { type Parsed, UsageError } from "../args";
|
|
2
|
+
import type { CliCommand } from "../types";
|
|
3
|
+
import {
|
|
4
|
+
type ExecutionPullOptions,
|
|
5
|
+
formatExecutionPull,
|
|
6
|
+
pullExecution,
|
|
7
|
+
} from "./execution-pull";
|
|
8
|
+
import { nodeFiles } from "./node-files";
|
|
9
|
+
import type { LocalFiles, Remote, Sync } from "./types";
|
|
10
|
+
import {
|
|
11
|
+
formatWorkflowPull,
|
|
12
|
+
pullWorkflow,
|
|
13
|
+
type WorkflowPullOptions,
|
|
14
|
+
} from "./workflow-pull";
|
|
15
|
+
import { formatWorkflowPush, pushWorkflow } from "./workflow-push";
|
|
16
|
+
|
|
17
|
+
export { SYNC_COMMANDS } from "./commands";
|
|
18
|
+
|
|
19
|
+
export interface SyncDeps {
|
|
20
|
+
cwd: string;
|
|
21
|
+
remote: Remote;
|
|
22
|
+
files?: LocalFiles;
|
|
23
|
+
stdout: (text: string) => void;
|
|
24
|
+
root?: string;
|
|
25
|
+
workflowPull?: Partial<WorkflowPullOptions>;
|
|
26
|
+
executionPull?: Partial<ExecutionPullOptions>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function flag(parsed: Parsed, name: string): string | undefined {
|
|
30
|
+
return parsed.flags.get(name)?.at(-1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function integer(parsed: Parsed, name: string): number | undefined {
|
|
34
|
+
const raw = flag(parsed, name);
|
|
35
|
+
if (raw === undefined) return undefined;
|
|
36
|
+
if (!/^\d+$/.test(raw))
|
|
37
|
+
throw new UsageError(`--${name} must be a whole number`);
|
|
38
|
+
return Number(raw);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function executionOptions(parsed: Parsed): Omit<ExecutionPullOptions, "root"> {
|
|
42
|
+
const summaryOnly = flag(parsed, "summary-only") === "true";
|
|
43
|
+
const glob = flag(parsed, "files");
|
|
44
|
+
const dir = flag(parsed, "dir");
|
|
45
|
+
return {
|
|
46
|
+
executionId: parsed.positionals[0],
|
|
47
|
+
activities: !summaryOnly,
|
|
48
|
+
files: !summaryOnly && glob !== "false",
|
|
49
|
+
glob: glob === "false" ? undefined : glob,
|
|
50
|
+
maxFileBytes: integer(parsed, "max-file-size"),
|
|
51
|
+
dir: dir ? () => dir : undefined,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function execute(
|
|
56
|
+
command: CliCommand,
|
|
57
|
+
parsed: Parsed,
|
|
58
|
+
sync: Sync,
|
|
59
|
+
deps: SyncDeps,
|
|
60
|
+
) {
|
|
61
|
+
const root = deps.root ?? "";
|
|
62
|
+
switch (command.command) {
|
|
63
|
+
case "workflow pull": {
|
|
64
|
+
const result = await pullWorkflow(sync, {
|
|
65
|
+
workflowId: parsed.positionals[0],
|
|
66
|
+
versionId: flag(parsed, "version-id"),
|
|
67
|
+
dir: flag(parsed, "dir"),
|
|
68
|
+
root,
|
|
69
|
+
...deps.workflowPull,
|
|
70
|
+
});
|
|
71
|
+
return { result, text: formatWorkflowPull(result) };
|
|
72
|
+
}
|
|
73
|
+
case "workflow push": {
|
|
74
|
+
const result = await pushWorkflow(sync, flag(parsed, "dir") ?? ".");
|
|
75
|
+
return { result, text: formatWorkflowPush(result) };
|
|
76
|
+
}
|
|
77
|
+
case "execution pull": {
|
|
78
|
+
const result = await pullExecution(sync, {
|
|
79
|
+
...executionOptions(parsed),
|
|
80
|
+
root,
|
|
81
|
+
...deps.executionPull,
|
|
82
|
+
});
|
|
83
|
+
return { result, text: formatExecutionPull(result) };
|
|
84
|
+
}
|
|
85
|
+
default:
|
|
86
|
+
throw new UsageError(`unknown command ${command.command}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function runSync(
|
|
91
|
+
command: CliCommand,
|
|
92
|
+
parsed: Parsed,
|
|
93
|
+
deps: SyncDeps,
|
|
94
|
+
) {
|
|
95
|
+
const sync = {
|
|
96
|
+
remote: deps.remote,
|
|
97
|
+
files: deps.files ?? nodeFiles(deps.cwd),
|
|
98
|
+
};
|
|
99
|
+
const { result, text } = await execute(command, parsed, sync, deps);
|
|
100
|
+
deps.stdout(`${parsed.json ? JSON.stringify(result, null, 2) : text}\n`);
|
|
101
|
+
return 0;
|
|
102
|
+
}
|