@asteroidai/cli 1.9.4 → 1.9.12
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 +1302 -469
- package/dist/core.js +1074 -260
- package/package.json +3 -2
- package/src/commands.ts +13 -0
- package/src/core.ts +31 -0
- package/src/generated/commands.ts +199 -182
- package/src/main.ts +2 -2
- package/src/program.test.ts +19 -0
- package/src/program.ts +25 -1
- package/src/reference.ts +13 -1
- package/src/run.test.ts +100 -3
- package/src/run.ts +57 -19
- package/src/sync/activities.ts +80 -0
- package/src/sync/commands.ts +63 -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 +136 -0
- package/src/sync/sync.test.ts +502 -0
- package/src/sync/types.ts +58 -0
- package/src/sync/workflow-pull.ts +185 -0
- package/src/sync/workflow-push.ts +161 -0
- package/src/types.ts +4 -0
|
@@ -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,136 @@
|
|
|
1
|
+
import { type Parsed, UsageError } from "../args";
|
|
2
|
+
import { buildRequest, type InputReader } from "../request";
|
|
3
|
+
import type { CliCommand } from "../types";
|
|
4
|
+
import {
|
|
5
|
+
type ExecutionPullOptions,
|
|
6
|
+
formatExecutionPull,
|
|
7
|
+
pullExecution,
|
|
8
|
+
} from "./execution-pull";
|
|
9
|
+
import { nodeFiles } from "./node-files";
|
|
10
|
+
import type { LocalFiles, Remote, Sync } from "./types";
|
|
11
|
+
import {
|
|
12
|
+
formatWorkflowPull,
|
|
13
|
+
pullWorkflow,
|
|
14
|
+
type WorkflowPullOptions,
|
|
15
|
+
} from "./workflow-pull";
|
|
16
|
+
import { formatWorkflowPush, pushWorkflow } from "./workflow-push";
|
|
17
|
+
|
|
18
|
+
export { SYNC_COMMANDS } from "./commands";
|
|
19
|
+
|
|
20
|
+
export interface SyncDeps {
|
|
21
|
+
cwd: string;
|
|
22
|
+
remote: Remote;
|
|
23
|
+
files?: LocalFiles;
|
|
24
|
+
stdout: (text: string) => void;
|
|
25
|
+
root?: string;
|
|
26
|
+
io?: InputReader;
|
|
27
|
+
workflowPull?: Partial<WorkflowPullOptions>;
|
|
28
|
+
executionPull?: Partial<ExecutionPullOptions>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function flag(parsed: Parsed, name: string): string | undefined {
|
|
32
|
+
return parsed.flags.get(name)?.at(-1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function integer(parsed: Parsed, name: string): number | undefined {
|
|
36
|
+
const raw = flag(parsed, name);
|
|
37
|
+
if (raw === undefined) return undefined;
|
|
38
|
+
if (!/^\d+$/.test(raw))
|
|
39
|
+
throw new UsageError(`--${name} must be a whole number`);
|
|
40
|
+
return Number(raw);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function executionOptions(parsed: Parsed): Omit<ExecutionPullOptions, "root"> {
|
|
44
|
+
const summaryOnly = flag(parsed, "summary-only") === "true";
|
|
45
|
+
const glob = flag(parsed, "files");
|
|
46
|
+
const dir = flag(parsed, "dir");
|
|
47
|
+
return {
|
|
48
|
+
executionId: parsed.positionals[0],
|
|
49
|
+
activities: !summaryOnly,
|
|
50
|
+
files: !summaryOnly && glob !== "false",
|
|
51
|
+
glob: glob === "false" ? undefined : glob,
|
|
52
|
+
maxFileBytes: integer(parsed, "max-file-size"),
|
|
53
|
+
dir: dir ? () => dir : undefined,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function withoutFlag(parsed: Parsed, name: string): Parsed {
|
|
58
|
+
const flags = new Map(parsed.flags);
|
|
59
|
+
flags.delete(name);
|
|
60
|
+
return { ...parsed, flags };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function runsLocally(command: CliCommand, parsed: Parsed): boolean {
|
|
64
|
+
if (command.local) return true;
|
|
65
|
+
if (command.files === "pull") return !parsed.json;
|
|
66
|
+
if (command.files === "push") return parsed.body === undefined;
|
|
67
|
+
return false;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const PARTIAL = ["paths", "variants"];
|
|
71
|
+
|
|
72
|
+
function rejectPartial(parsed: Parsed) {
|
|
73
|
+
const used = PARTIAL.filter((name) => parsed.flags.has(name));
|
|
74
|
+
if (used.length === 0) return;
|
|
75
|
+
throw new UsageError(
|
|
76
|
+
`pull writes the whole tree, so it cannot take ${used.map((name) => `--${name}`).join(" or ")}. Add --json to list those files instead.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function pull(
|
|
81
|
+
command: CliCommand,
|
|
82
|
+
parsed: Parsed,
|
|
83
|
+
sync: Sync,
|
|
84
|
+
deps: SyncDeps,
|
|
85
|
+
) {
|
|
86
|
+
rejectPartial(parsed);
|
|
87
|
+
const result = await pullWorkflow(sync, {
|
|
88
|
+
workflowId: parsed.positionals[0],
|
|
89
|
+
request: await buildRequest(command, withoutFlag(parsed, "out"), {
|
|
90
|
+
io: deps.io,
|
|
91
|
+
}),
|
|
92
|
+
dir: flag(parsed, "out"),
|
|
93
|
+
root: deps.root ?? "",
|
|
94
|
+
...deps.workflowPull,
|
|
95
|
+
});
|
|
96
|
+
return { result, text: formatWorkflowPull(result) };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function execute(
|
|
100
|
+
command: CliCommand,
|
|
101
|
+
parsed: Parsed,
|
|
102
|
+
sync: Sync,
|
|
103
|
+
deps: SyncDeps,
|
|
104
|
+
) {
|
|
105
|
+
if (command.files === "pull") return pull(command, parsed, sync, deps);
|
|
106
|
+
if (command.files === "push") {
|
|
107
|
+
const result = await pushWorkflow(
|
|
108
|
+
sync,
|
|
109
|
+
flag(parsed, "from") ?? ".",
|
|
110
|
+
parsed.positionals[0] || undefined,
|
|
111
|
+
);
|
|
112
|
+
return { result, text: formatWorkflowPush(result) };
|
|
113
|
+
}
|
|
114
|
+
if (command.command !== "execution pull")
|
|
115
|
+
throw new UsageError(`unknown command ${command.command}`);
|
|
116
|
+
const result = await pullExecution(sync, {
|
|
117
|
+
...executionOptions(parsed),
|
|
118
|
+
root: deps.root ?? "",
|
|
119
|
+
...deps.executionPull,
|
|
120
|
+
});
|
|
121
|
+
return { result, text: formatExecutionPull(result) };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export async function runSync(
|
|
125
|
+
command: CliCommand,
|
|
126
|
+
parsed: Parsed,
|
|
127
|
+
deps: SyncDeps,
|
|
128
|
+
) {
|
|
129
|
+
const sync = {
|
|
130
|
+
remote: deps.remote,
|
|
131
|
+
files: deps.files ?? nodeFiles(deps.cwd),
|
|
132
|
+
};
|
|
133
|
+
const { result, text } = await execute(command, parsed, sync, deps);
|
|
134
|
+
deps.stdout(`${parsed.json ? JSON.stringify(result, null, 2) : text}\n`);
|
|
135
|
+
return 0;
|
|
136
|
+
}
|