@actiondock/builder 2.0.12 → 2.2.1
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 +46 -30
- package/dist/archive.d.ts +24 -3
- package/dist/archive.js +519 -0
- package/dist/build.d.ts +5 -37
- package/dist/build.js +576 -0
- package/dist/dependency-check.d.ts +14 -0
- package/dist/dependency-check.js +246 -0
- package/dist/errors.d.ts +3 -30
- package/dist/errors.js +22 -0
- package/dist/exporter.d.ts +24 -14
- package/dist/exporter.js +761 -0
- package/dist/fs-utils.d.ts +16 -0
- package/dist/fs-utils.js +146 -0
- package/dist/index.d.ts +16 -7
- package/dist/index.js +21 -1875
- package/dist/manifest.d.ts +75 -0
- package/dist/manifest.js +213 -0
- package/dist/pack.d.ts +13 -0
- package/dist/pack.js +339 -0
- package/dist/planner.d.ts +51 -9
- package/dist/planner.js +681 -0
- package/dist/types.d.ts +204 -73
- package/dist/types.js +1 -0
- package/package.json +8 -9
- package/dist/compiler.d.ts +0 -22
package/dist/index.js
CHANGED
|
@@ -1,1875 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
super(message);
|
|
23
|
-
this.name = "CompilerValidationError";
|
|
24
|
-
this.code = options.code || "COMPILER_VALIDATION_ERROR";
|
|
25
|
-
this.target = options.target;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
class CompilerError extends BuilderError {
|
|
30
|
-
code;
|
|
31
|
-
exitCode;
|
|
32
|
-
rawError;
|
|
33
|
-
details;
|
|
34
|
-
target;
|
|
35
|
-
constructor(message, options) {
|
|
36
|
-
super(message);
|
|
37
|
-
this.name = "CompilerError";
|
|
38
|
-
this.code = options.code;
|
|
39
|
-
this.exitCode = options.exitCode;
|
|
40
|
-
this.rawError = options.rawError;
|
|
41
|
-
this.details = options.details || [];
|
|
42
|
-
this.target = options.target;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
// packages/builder/src/build.ts
|
|
46
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
47
|
-
import { join as join2, resolve as resolve3 } from "node:path";
|
|
48
|
-
import { generateStandaloneEntrypoint, getPackageSlug } from "@actiondock/core";
|
|
49
|
-
|
|
50
|
-
// packages/builder/src/compiler.ts
|
|
51
|
-
import { spawn } from "node:child_process";
|
|
52
|
-
import { createHash } from "node:crypto";
|
|
53
|
-
import { createReadStream, existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
54
|
-
import { basename, dirname, resolve } from "node:path";
|
|
55
|
-
async function execCommandAsync(command, args, options) {
|
|
56
|
-
return new Promise((res) => {
|
|
57
|
-
const child = spawn(command, args, {
|
|
58
|
-
cwd: options.cwd,
|
|
59
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
60
|
-
env: options.env
|
|
61
|
-
});
|
|
62
|
-
const stdoutChunks = [];
|
|
63
|
-
const stderrChunks = [];
|
|
64
|
-
child.stdout?.on("data", (chunk) => stdoutChunks.push(Buffer.from(chunk)));
|
|
65
|
-
child.stderr?.on("data", (chunk) => stderrChunks.push(Buffer.from(chunk)));
|
|
66
|
-
child.on("error", (err) => {
|
|
67
|
-
res({
|
|
68
|
-
status: null,
|
|
69
|
-
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
70
|
-
stderr: Buffer.concat(stderrChunks).toString("utf-8"),
|
|
71
|
-
error: err
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
child.on("close", (code) => {
|
|
75
|
-
res({
|
|
76
|
-
status: code,
|
|
77
|
-
stdout: Buffer.concat(stdoutChunks).toString("utf-8"),
|
|
78
|
-
stderr: Buffer.concat(stderrChunks).toString("utf-8")
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
async function computeFileSha256(filePath) {
|
|
84
|
-
return new Promise((res, rej) => {
|
|
85
|
-
const hash = createHash("sha256");
|
|
86
|
-
const stream = createReadStream(filePath);
|
|
87
|
-
stream.on("data", (chunk) => hash.update(chunk));
|
|
88
|
-
stream.on("end", () => res(hash.digest("hex")));
|
|
89
|
-
stream.on("error", rej);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
async function getBunVersion() {
|
|
93
|
-
if (typeof globalThis.Bun !== "undefined" && globalThis.Bun.version) {
|
|
94
|
-
return globalThis.Bun.version;
|
|
95
|
-
}
|
|
96
|
-
try {
|
|
97
|
-
const res = await execCommandAsync("bun", ["--version"], { cwd: process.cwd(), env: process.env });
|
|
98
|
-
if (res.status === 0) {
|
|
99
|
-
return res.stdout.trim();
|
|
100
|
-
}
|
|
101
|
-
} catch {}
|
|
102
|
-
return "unknown";
|
|
103
|
-
}
|
|
104
|
-
var SUPPORTED_TARGETS = new Set([
|
|
105
|
-
"bun",
|
|
106
|
-
"host",
|
|
107
|
-
"bun-linux-x64",
|
|
108
|
-
"linux-x64",
|
|
109
|
-
"bun-linux-arm64",
|
|
110
|
-
"linux-arm64",
|
|
111
|
-
"bun-darwin-x64",
|
|
112
|
-
"darwin-x64",
|
|
113
|
-
"bun-darwin-arm64",
|
|
114
|
-
"darwin-arm64",
|
|
115
|
-
"bun-windows-x64",
|
|
116
|
-
"windows-x64",
|
|
117
|
-
"bun-linux-x64-baseline",
|
|
118
|
-
"linux-x64-baseline",
|
|
119
|
-
"bun-linux-x64-modern",
|
|
120
|
-
"linux-x64-modern"
|
|
121
|
-
]);
|
|
122
|
-
function normalizeAndValidateTarget(target) {
|
|
123
|
-
if (!target || target === "host" || target === "bun") {
|
|
124
|
-
return "bun";
|
|
125
|
-
}
|
|
126
|
-
const cleanTarget = target.trim();
|
|
127
|
-
if (!SUPPORTED_TARGETS.has(cleanTarget)) {
|
|
128
|
-
throw new CompilerValidationError(`Unsupported compilation target: '${target}'. Standalone compilation only supports Bun targets (e.g. linux-x64, linux-arm64, darwin-x64, darwin-arm64, windows-x64).`, { code: "UNSUPPORTED_TARGET", target });
|
|
129
|
-
}
|
|
130
|
-
return cleanTarget.startsWith("bun-") ? cleanTarget : `bun-${cleanTarget}`;
|
|
131
|
-
}
|
|
132
|
-
function normalizeCompilerError(rawStderr, rawStdout, exitCode, target) {
|
|
133
|
-
const combined = `${rawStderr}
|
|
134
|
-
${rawStdout}`.trim();
|
|
135
|
-
const lines = combined.split(/\r?\n/).map((l) => l.trim()).filter((l) => l.length > 0);
|
|
136
|
-
let code = "COMPILATION_FAILED";
|
|
137
|
-
if (combined.includes("Unsupported target")) {
|
|
138
|
-
code = "UNSUPPORTED_TARGET";
|
|
139
|
-
} else if (combined.includes("Cannot find package") || combined.includes("Cannot find module") || combined.includes("Could not resolve")) {
|
|
140
|
-
code = "MODULE_NOT_FOUND";
|
|
141
|
-
} else if (combined.includes("SyntaxError") || combined.includes("Parse error") || combined.includes("Expected") || combined.includes("Unexpected")) {
|
|
142
|
-
code = "SYNTAX_ERROR";
|
|
143
|
-
} else if (combined.includes("EACCES") || combined.includes("Permission denied")) {
|
|
144
|
-
code = "PERMISSION_DENIED";
|
|
145
|
-
} else if (combined.includes("ENOSPC") || combined.includes("no space left")) {
|
|
146
|
-
code = "NO_SPACE";
|
|
147
|
-
}
|
|
148
|
-
const summaryLine = lines.find((l) => l.startsWith("error:")) || lines[0] || "Bun compilation failed";
|
|
149
|
-
return new CompilerError(`Bun compilation failed with exit code ${exitCode} (${code}): ${summaryLine}`, {
|
|
150
|
-
code,
|
|
151
|
-
exitCode,
|
|
152
|
-
rawError: combined,
|
|
153
|
-
details: lines,
|
|
154
|
-
target
|
|
155
|
-
});
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
class BunCompiler {
|
|
159
|
-
async compile(options) {
|
|
160
|
-
const startTime = Date.now();
|
|
161
|
-
if (!options.entrypoint) {
|
|
162
|
-
throw new CompilerValidationError("Entrypoint must be specified for standalone compilation", {
|
|
163
|
-
code: "INVALID_ENTRYPOINT"
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
const resolvedEntry = resolve(options.entrypoint);
|
|
167
|
-
if (!existsSync(resolvedEntry)) {
|
|
168
|
-
throw new CompilerValidationError(`Entrypoint file not found: ${options.entrypoint}`, { code: "ENTRYPOINT_NOT_FOUND" });
|
|
169
|
-
}
|
|
170
|
-
if (!options.outfile) {
|
|
171
|
-
throw new CompilerValidationError("Outfile path must be specified for standalone compilation", {
|
|
172
|
-
code: "INVALID_OUTFILE"
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
const resolvedOutfile = resolve(options.outfile);
|
|
176
|
-
const outDir = dirname(resolvedOutfile);
|
|
177
|
-
mkdirSync(outDir, { recursive: true });
|
|
178
|
-
const normalizedTarget = normalizeAndValidateTarget(options.target);
|
|
179
|
-
const isWindowsTarget = normalizedTarget.includes("windows") || !options.target && process.platform === "win32";
|
|
180
|
-
if (options.minify !== undefined && typeof options.minify !== "boolean") {
|
|
181
|
-
throw new CompilerValidationError("Option 'minify' must be a boolean", {
|
|
182
|
-
code: "INVALID_MINIFY_OPTION"
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
if (options.bytecode !== undefined && typeof options.bytecode !== "boolean") {
|
|
186
|
-
throw new CompilerValidationError("Option 'bytecode' must be a boolean", {
|
|
187
|
-
code: "INVALID_BYTECODE_OPTION"
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
const args = [
|
|
191
|
-
"bun",
|
|
192
|
-
"build",
|
|
193
|
-
resolvedEntry,
|
|
194
|
-
"--compile",
|
|
195
|
-
"--outfile",
|
|
196
|
-
resolvedOutfile
|
|
197
|
-
];
|
|
198
|
-
const bytecode = options.bytecode !== false;
|
|
199
|
-
if (bytecode) {
|
|
200
|
-
args.push("--bytecode");
|
|
201
|
-
}
|
|
202
|
-
const minify = options.minify !== false;
|
|
203
|
-
if (minify) {
|
|
204
|
-
args.push("--minify");
|
|
205
|
-
}
|
|
206
|
-
if (normalizedTarget !== "bun") {
|
|
207
|
-
args.push(`--target=${normalizedTarget}`);
|
|
208
|
-
}
|
|
209
|
-
const cwd = options.cwd ? resolve(options.cwd) : dirname(resolvedEntry);
|
|
210
|
-
const proc = await execCommandAsync(args[0], args.slice(1), {
|
|
211
|
-
cwd,
|
|
212
|
-
env: {
|
|
213
|
-
...process.env,
|
|
214
|
-
...options.env
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
if (proc.error) {
|
|
218
|
-
if (proc.error.code === "ENOENT") {
|
|
219
|
-
throw new CompilerError("Bun binary not found in PATH. Standalone compilation requires Bun to be installed (e.g. via 'curl -fsSL https://bun.sh/install | bash' or 'npm install -g bun').", {
|
|
220
|
-
code: "BUN_NOT_FOUND",
|
|
221
|
-
exitCode: 1,
|
|
222
|
-
rawError: proc.error.message,
|
|
223
|
-
target: options.target
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
throw new CompilerError(`Failed to spawn Bun compiler: ${proc.error.message}`, {
|
|
227
|
-
code: "SPAWN_FAILED",
|
|
228
|
-
exitCode: 1,
|
|
229
|
-
rawError: proc.error.message,
|
|
230
|
-
target: options.target
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
if (proc.status !== 0) {
|
|
234
|
-
throw normalizeCompilerError(proc.stderr, proc.stdout, proc.status ?? 1, options.target);
|
|
235
|
-
}
|
|
236
|
-
let actualExecutablePath = resolvedOutfile;
|
|
237
|
-
if (!existsSync(actualExecutablePath) && isWindowsTarget) {
|
|
238
|
-
const withExe = `${resolvedOutfile}.exe`;
|
|
239
|
-
if (existsSync(withExe)) {
|
|
240
|
-
actualExecutablePath = withExe;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
if (!existsSync(actualExecutablePath)) {
|
|
244
|
-
throw new CompilerError(`Compiler completed with exit code 0 but output executable not found at ${resolvedOutfile}`, {
|
|
245
|
-
code: "OUTPUT_NOT_FOUND",
|
|
246
|
-
exitCode: 0,
|
|
247
|
-
rawError: "Output executable file not found on disk",
|
|
248
|
-
target: options.target
|
|
249
|
-
});
|
|
250
|
-
}
|
|
251
|
-
const sizeBytes = statSync(actualExecutablePath).size;
|
|
252
|
-
const sha256 = await computeFileSha256(actualExecutablePath);
|
|
253
|
-
const durationMs = Date.now() - startTime;
|
|
254
|
-
const compiledAt = new Date().toISOString();
|
|
255
|
-
let metadataPath;
|
|
256
|
-
if (options.emitMetadata !== false) {
|
|
257
|
-
const targetSlug = (options.target || "host").replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
258
|
-
const targetMetadataPath = resolve(outDir, `artifact.${targetSlug}.json`);
|
|
259
|
-
metadataPath = resolve(outDir, "artifact.json");
|
|
260
|
-
const bunVersion = await getBunVersion();
|
|
261
|
-
const metadata = {
|
|
262
|
-
packageId: options.packageId,
|
|
263
|
-
version: options.version,
|
|
264
|
-
target: options.target || "host",
|
|
265
|
-
executable: basename(actualExecutablePath),
|
|
266
|
-
sizeBytes,
|
|
267
|
-
sha256,
|
|
268
|
-
bunVersion,
|
|
269
|
-
minify,
|
|
270
|
-
bytecode,
|
|
271
|
-
actions: options.actions || [],
|
|
272
|
-
compiledAt
|
|
273
|
-
};
|
|
274
|
-
const metadataContent = JSON.stringify(metadata, null, 2) + `
|
|
275
|
-
`;
|
|
276
|
-
writeFileSync(targetMetadataPath, metadataContent, "utf-8");
|
|
277
|
-
writeFileSync(metadataPath, metadataContent, "utf-8");
|
|
278
|
-
metadataPath = targetMetadataPath;
|
|
279
|
-
}
|
|
280
|
-
return {
|
|
281
|
-
packageId: options.packageId,
|
|
282
|
-
version: options.version,
|
|
283
|
-
target: options.target || "host",
|
|
284
|
-
executablePath: actualExecutablePath,
|
|
285
|
-
sizeBytes,
|
|
286
|
-
sha256,
|
|
287
|
-
metadataPath,
|
|
288
|
-
minify,
|
|
289
|
-
bytecode,
|
|
290
|
-
durationMs,
|
|
291
|
-
compiledAt
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
static async compile(options) {
|
|
295
|
-
const compiler = new BunCompiler;
|
|
296
|
-
return compiler.compile(options);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
async function compileBinary(options) {
|
|
300
|
-
return BunCompiler.compile(options);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// packages/builder/src/planner.ts
|
|
304
|
-
import { existsSync as existsSync2, readdirSync, readFileSync, realpathSync, statSync as statSync2 } from "node:fs";
|
|
305
|
-
import { basename as basename2, dirname as dirname2, isAbsolute, join, relative, resolve as resolve2 } from "node:path";
|
|
306
|
-
import ts from "typescript";
|
|
307
|
-
import YAML from "yaml";
|
|
308
|
-
import {
|
|
309
|
-
assertPathWithinRoot,
|
|
310
|
-
loadManifest,
|
|
311
|
-
loadProjectConfig,
|
|
312
|
-
resolveActionProjectSync,
|
|
313
|
-
validateManifest
|
|
314
|
-
} from "@actiondock/core";
|
|
315
|
-
function walkDirectory(dir, rootDir = dir, visited = new Set) {
|
|
316
|
-
if (!existsSync2(dir))
|
|
317
|
-
return [];
|
|
318
|
-
const results = [];
|
|
319
|
-
const entries = readdirSync(dir);
|
|
320
|
-
for (const entry of entries) {
|
|
321
|
-
const fullPath = join(dir, entry);
|
|
322
|
-
if (rootDir) {
|
|
323
|
-
try {
|
|
324
|
-
assertPathWithinRoot(rootDir, fullPath, "planner path");
|
|
325
|
-
} catch {
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
let real;
|
|
330
|
-
try {
|
|
331
|
-
real = existsSync2(fullPath) ? realpathSync(fullPath) : fullPath;
|
|
332
|
-
} catch {
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
if (rootDir) {
|
|
336
|
-
try {
|
|
337
|
-
assertPathWithinRoot(rootDir, real, "planner path");
|
|
338
|
-
} catch {
|
|
339
|
-
continue;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
if (visited.has(real)) {
|
|
343
|
-
continue;
|
|
344
|
-
}
|
|
345
|
-
visited.add(real);
|
|
346
|
-
try {
|
|
347
|
-
const stat = statSync2(fullPath);
|
|
348
|
-
if (stat.isDirectory()) {
|
|
349
|
-
results.push(...walkDirectory(fullPath, rootDir, visited));
|
|
350
|
-
} else if (stat.isFile()) {
|
|
351
|
-
results.push(fullPath);
|
|
352
|
-
}
|
|
353
|
-
} catch {
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return results;
|
|
358
|
-
}
|
|
359
|
-
function isIgnoredModulePath(relPath) {
|
|
360
|
-
const normalized = relPath.replace(/\\/g, "/");
|
|
361
|
-
return normalized.startsWith("node_modules/") || normalized.includes("/node_modules/") || normalized.startsWith(".git/") || normalized.startsWith("dist/") || normalized.startsWith(".actiondock/") || normalized.endsWith(".test.ts") || normalized.endsWith(".test.js") || normalized.endsWith(".test.tsx") || normalized.endsWith(".test.jsx") || normalized.endsWith(".spec.ts") || normalized.endsWith(".spec.js") || normalized.endsWith(".spec.tsx") || normalized.endsWith(".spec.jsx") || normalized.endsWith(".d.ts");
|
|
362
|
-
}
|
|
363
|
-
function extractImportsUsingAst(source, fileName) {
|
|
364
|
-
try {
|
|
365
|
-
let visit = function(node) {
|
|
366
|
-
if (ts.isImportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
367
|
-
specifiers.add(node.moduleSpecifier.text);
|
|
368
|
-
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
|
|
369
|
-
specifiers.add(node.moduleSpecifier.text);
|
|
370
|
-
} else if (ts.isCallExpression(node)) {
|
|
371
|
-
if (node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments.length > 0 && ts.isStringLiteral(node.arguments[0])) {
|
|
372
|
-
specifiers.add(node.arguments[0].text);
|
|
373
|
-
} else if (ts.isIdentifier(node.expression) && node.expression.text === "require" && node.arguments.length > 0 && ts.isStringLiteral(node.arguments[0])) {
|
|
374
|
-
specifiers.add(node.arguments[0].text);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
ts.forEachChild(node, visit);
|
|
378
|
-
};
|
|
379
|
-
const sourceFile = ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true);
|
|
380
|
-
const specifiers = new Set;
|
|
381
|
-
visit(sourceFile);
|
|
382
|
-
return Array.from(specifiers);
|
|
383
|
-
} catch {
|
|
384
|
-
const stripped = source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, "");
|
|
385
|
-
const specifiers = new Set;
|
|
386
|
-
const fromRegex = /\bfrom\s*["'`]([^"'`]+)["'`]/g;
|
|
387
|
-
let match;
|
|
388
|
-
while ((match = fromRegex.exec(stripped)) !== null) {
|
|
389
|
-
specifiers.add(match[1].trim());
|
|
390
|
-
}
|
|
391
|
-
const importRegex = /\bimport\s*(?:\(\s*)?["'`]([^"'`]+)["'`]/g;
|
|
392
|
-
while ((match = importRegex.exec(stripped)) !== null) {
|
|
393
|
-
specifiers.add(match[1].trim());
|
|
394
|
-
}
|
|
395
|
-
const requireRegex = /\brequire\s*\(\s*["'`]([^"'`]+)["'`]/g;
|
|
396
|
-
while ((match = requireRegex.exec(stripped)) !== null) {
|
|
397
|
-
specifiers.add(match[1].trim());
|
|
398
|
-
}
|
|
399
|
-
return Array.from(specifiers);
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
var CANDIDATE_EXTENSIONS = [
|
|
403
|
-
"",
|
|
404
|
-
".ts",
|
|
405
|
-
".tsx",
|
|
406
|
-
".js",
|
|
407
|
-
".jsx",
|
|
408
|
-
".mjs",
|
|
409
|
-
".cjs",
|
|
410
|
-
".mts",
|
|
411
|
-
".cts",
|
|
412
|
-
".json"
|
|
413
|
-
];
|
|
414
|
-
function resolveLocalModulePath(baseDir, specifier) {
|
|
415
|
-
const candidate = resolve2(baseDir, specifier);
|
|
416
|
-
if (existsSync2(candidate)) {
|
|
417
|
-
try {
|
|
418
|
-
if (statSync2(candidate).isFile()) {
|
|
419
|
-
return candidate;
|
|
420
|
-
}
|
|
421
|
-
} catch {}
|
|
422
|
-
}
|
|
423
|
-
if (candidate.endsWith(".js")) {
|
|
424
|
-
const tsPath = candidate.slice(0, -3) + ".ts";
|
|
425
|
-
if (existsSync2(tsPath))
|
|
426
|
-
return tsPath;
|
|
427
|
-
const tsxPath = candidate.slice(0, -3) + ".tsx";
|
|
428
|
-
if (existsSync2(tsxPath))
|
|
429
|
-
return tsxPath;
|
|
430
|
-
} else if (candidate.endsWith(".mjs")) {
|
|
431
|
-
const mtsPath = candidate.slice(0, -4) + ".mts";
|
|
432
|
-
if (existsSync2(mtsPath))
|
|
433
|
-
return mtsPath;
|
|
434
|
-
} else if (candidate.endsWith(".cjs")) {
|
|
435
|
-
const ctsPath = candidate.slice(0, -4) + ".cts";
|
|
436
|
-
if (existsSync2(ctsPath))
|
|
437
|
-
return ctsPath;
|
|
438
|
-
}
|
|
439
|
-
for (const ext of CANDIDATE_EXTENSIONS) {
|
|
440
|
-
if (!ext)
|
|
441
|
-
continue;
|
|
442
|
-
const withExt = candidate + ext;
|
|
443
|
-
if (existsSync2(withExt)) {
|
|
444
|
-
try {
|
|
445
|
-
if (statSync2(withExt).isFile()) {
|
|
446
|
-
return withExt;
|
|
447
|
-
}
|
|
448
|
-
} catch {}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
if (existsSync2(candidate)) {
|
|
452
|
-
try {
|
|
453
|
-
if (statSync2(candidate).isDirectory()) {
|
|
454
|
-
for (const ext of [".ts", ".tsx", ".js", ".mjs", ".json"]) {
|
|
455
|
-
const indexFile = join(candidate, `index${ext}`);
|
|
456
|
-
if (existsSync2(indexFile)) {
|
|
457
|
-
return indexFile;
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
} catch {}
|
|
462
|
-
}
|
|
463
|
-
return null;
|
|
464
|
-
}
|
|
465
|
-
function loadTsConfigInfo(projectRoot) {
|
|
466
|
-
const tsconfigPath = join(projectRoot, "tsconfig.json");
|
|
467
|
-
if (!existsSync2(tsconfigPath))
|
|
468
|
-
return null;
|
|
469
|
-
try {
|
|
470
|
-
const configFile = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
|
|
471
|
-
if (configFile.error)
|
|
472
|
-
return null;
|
|
473
|
-
const parsed = ts.parseJsonConfigFileContent(configFile.config, ts.sys, projectRoot);
|
|
474
|
-
return {
|
|
475
|
-
baseUrl: parsed.options.baseUrl || projectRoot,
|
|
476
|
-
paths: parsed.options.paths || {},
|
|
477
|
-
options: parsed.options
|
|
478
|
-
};
|
|
479
|
-
} catch {
|
|
480
|
-
return null;
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
function resolveModulePathWithAliases(currentFile, specifier, projectRoot, tsconfigInfo) {
|
|
484
|
-
const dir = dirname2(currentFile);
|
|
485
|
-
if (specifier.startsWith("./") || specifier.startsWith("../")) {
|
|
486
|
-
return resolveLocalModulePath(dir, specifier);
|
|
487
|
-
}
|
|
488
|
-
if (tsconfigInfo && Object.keys(tsconfigInfo.paths).length > 0) {
|
|
489
|
-
const host = {
|
|
490
|
-
fileExists: ts.sys.fileExists,
|
|
491
|
-
readFile: ts.sys.readFile,
|
|
492
|
-
directoryExists: ts.sys.directoryExists,
|
|
493
|
-
getCurrentDirectory: () => projectRoot,
|
|
494
|
-
getDirectories: ts.sys.getDirectories
|
|
495
|
-
};
|
|
496
|
-
try {
|
|
497
|
-
const resolved = ts.resolveModuleName(specifier, currentFile, tsconfigInfo.options, host);
|
|
498
|
-
if (resolved.resolvedModule && !resolved.resolvedModule.isExternalLibraryImport) {
|
|
499
|
-
const found = resolve2(resolved.resolvedModule.resolvedFileName);
|
|
500
|
-
if (existsSync2(found)) {
|
|
501
|
-
return found;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
} catch {}
|
|
505
|
-
for (const [pattern, targets] of Object.entries(tsconfigInfo.paths)) {
|
|
506
|
-
let matched = false;
|
|
507
|
-
let star = "";
|
|
508
|
-
if (pattern.endsWith("*")) {
|
|
509
|
-
const prefix = pattern.slice(0, -1);
|
|
510
|
-
if (specifier.startsWith(prefix)) {
|
|
511
|
-
matched = true;
|
|
512
|
-
star = specifier.slice(prefix.length);
|
|
513
|
-
}
|
|
514
|
-
} else if (pattern === specifier) {
|
|
515
|
-
matched = true;
|
|
516
|
-
}
|
|
517
|
-
if (matched) {
|
|
518
|
-
for (const target of targets) {
|
|
519
|
-
const replaced = target.endsWith("*") ? target.slice(0, -1) + star : target;
|
|
520
|
-
const candidate = resolve2(tsconfigInfo.baseUrl, replaced);
|
|
521
|
-
const resolved = resolveLocalModulePath(dirname2(candidate), `./${basename2(candidate)}`);
|
|
522
|
-
if (resolved) {
|
|
523
|
-
return resolved;
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
function traceLocalModuleDependencies(entryFiles, projectRoot) {
|
|
532
|
-
const visited = new Set;
|
|
533
|
-
const modules = new Set;
|
|
534
|
-
const queue = [...entryFiles];
|
|
535
|
-
const tsconfigInfo = loadTsConfigInfo(projectRoot);
|
|
536
|
-
while (queue.length > 0) {
|
|
537
|
-
const current = queue.shift();
|
|
538
|
-
if (visited.has(current))
|
|
539
|
-
continue;
|
|
540
|
-
visited.add(current);
|
|
541
|
-
const relToRoot = relative(projectRoot, current);
|
|
542
|
-
if (relToRoot.startsWith("..") || isAbsolute(relToRoot)) {
|
|
543
|
-
continue;
|
|
544
|
-
}
|
|
545
|
-
let content = "";
|
|
546
|
-
try {
|
|
547
|
-
content = readFileSync(current, "utf-8");
|
|
548
|
-
} catch {
|
|
549
|
-
continue;
|
|
550
|
-
}
|
|
551
|
-
const specifiers = extractImportsUsingAst(content, current);
|
|
552
|
-
for (const spec of specifiers) {
|
|
553
|
-
const resolved = resolveModulePathWithAliases(current, spec, projectRoot, tsconfigInfo);
|
|
554
|
-
if (resolved) {
|
|
555
|
-
const rel = relative(projectRoot, resolved);
|
|
556
|
-
if (!rel.startsWith("..") && !isAbsolute(rel) && !isIgnoredModulePath(rel)) {
|
|
557
|
-
modules.add(resolved);
|
|
558
|
-
if (!visited.has(resolved)) {
|
|
559
|
-
queue.push(resolved);
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
return Array.from(modules);
|
|
566
|
-
}
|
|
567
|
-
function parsePlaybookFile(filePath) {
|
|
568
|
-
try {
|
|
569
|
-
const content = readFileSync(filePath, "utf-8");
|
|
570
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
571
|
-
if (match) {
|
|
572
|
-
const parsed = YAML.parse(match[1]) || {};
|
|
573
|
-
const id = parsed.id || basename2(filePath.replace(/\\/g, "/"), ".md");
|
|
574
|
-
return {
|
|
575
|
-
id,
|
|
576
|
-
filePath,
|
|
577
|
-
actions: Array.isArray(parsed.actions) ? parsed.actions : [],
|
|
578
|
-
description: parsed.description
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
return {
|
|
582
|
-
id: basename2(filePath.replace(/\\/g, "/"), ".md"),
|
|
583
|
-
filePath,
|
|
584
|
-
actions: []
|
|
585
|
-
};
|
|
586
|
-
} catch {
|
|
587
|
-
return null;
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
function loadProjectPlaybooks(projectRoot, playbooksDir = "playbooks") {
|
|
591
|
-
const map = new Map;
|
|
592
|
-
const dir = join(projectRoot, playbooksDir);
|
|
593
|
-
if (!existsSync2(dir))
|
|
594
|
-
return map;
|
|
595
|
-
const files = walkDirectory(dir, projectRoot).filter((f) => f.endsWith(".md"));
|
|
596
|
-
for (const file of files) {
|
|
597
|
-
const pb = parsePlaybookFile(file);
|
|
598
|
-
if (pb) {
|
|
599
|
-
map.set(pb.id, pb);
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
return map;
|
|
603
|
-
}
|
|
604
|
-
function generateStaticManifest(projectRoot, actionsDir = "actions") {
|
|
605
|
-
const dir = join(projectRoot, actionsDir);
|
|
606
|
-
const actions = {};
|
|
607
|
-
if (existsSync2(dir)) {
|
|
608
|
-
const files = walkDirectory(dir, projectRoot).filter((f) => (f.endsWith(".ts") || f.endsWith(".js")) && !f.endsWith(".d.ts") && !f.endsWith(".test.ts") && !f.endsWith(".spec.ts"));
|
|
609
|
-
for (const file of files) {
|
|
610
|
-
const relPath = relative(projectRoot, file);
|
|
611
|
-
const filename = basename2(file);
|
|
612
|
-
const actionId = filename.replace(/\.(ts|js)$/, "");
|
|
613
|
-
let parsedId = actionId;
|
|
614
|
-
const uses = [];
|
|
615
|
-
try {
|
|
616
|
-
const source = readFileSync(file, "utf-8");
|
|
617
|
-
const idMatch = source.match(/id\s*:\s*["'`]([^"'`]+)["'`]/);
|
|
618
|
-
if (idMatch && idMatch[1]) {
|
|
619
|
-
parsedId = idMatch[1];
|
|
620
|
-
}
|
|
621
|
-
const usesMatch = source.match(/uses\s*:\s*\[([^\]]*)\]/);
|
|
622
|
-
if (usesMatch && usesMatch[1]) {
|
|
623
|
-
const rawItems = usesMatch[1].split(",");
|
|
624
|
-
for (const raw of rawItems) {
|
|
625
|
-
const clean = raw.trim().replace(/^["'`]|["'`]$/g, "");
|
|
626
|
-
if (clean) {
|
|
627
|
-
uses.push(clean);
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
} catch {}
|
|
632
|
-
actions[parsedId] = {
|
|
633
|
-
entry: relPath,
|
|
634
|
-
description: `Action ${parsedId}`,
|
|
635
|
-
uses
|
|
636
|
-
};
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
return {
|
|
640
|
-
schemaVersion: 1,
|
|
641
|
-
actions
|
|
642
|
-
};
|
|
643
|
-
}
|
|
644
|
-
function extractExternalDependencies(projectRoot) {
|
|
645
|
-
const pkgPath = join(projectRoot, "package.json");
|
|
646
|
-
if (!existsSync2(pkgPath))
|
|
647
|
-
return [];
|
|
648
|
-
try {
|
|
649
|
-
const raw = readFileSync(pkgPath, "utf-8");
|
|
650
|
-
const parsed = JSON.parse(raw);
|
|
651
|
-
const deps = [];
|
|
652
|
-
if (parsed.dependencies && typeof parsed.dependencies === "object") {
|
|
653
|
-
for (const [name, versionRange] of Object.entries(parsed.dependencies)) {
|
|
654
|
-
deps.push({
|
|
655
|
-
name,
|
|
656
|
-
versionRange: String(versionRange),
|
|
657
|
-
isDev: false
|
|
658
|
-
});
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
if (parsed.devDependencies && typeof parsed.devDependencies === "object") {
|
|
662
|
-
for (const [name, versionRange] of Object.entries(parsed.devDependencies)) {
|
|
663
|
-
deps.push({
|
|
664
|
-
name,
|
|
665
|
-
versionRange: String(versionRange),
|
|
666
|
-
isDev: true
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
return deps;
|
|
671
|
-
} catch {
|
|
672
|
-
return [];
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
class BuildPlanner {
|
|
677
|
-
projectRoot;
|
|
678
|
-
constructor(options) {
|
|
679
|
-
this.projectRoot = resolve2(options?.projectRoot || process.cwd());
|
|
680
|
-
}
|
|
681
|
-
plan(options) {
|
|
682
|
-
const root = resolve2(options?.projectRoot || this.projectRoot);
|
|
683
|
-
let config = options?.config;
|
|
684
|
-
if (!config) {
|
|
685
|
-
try {
|
|
686
|
-
config = loadProjectConfig(root);
|
|
687
|
-
} catch (err) {
|
|
688
|
-
throw new PlannerError(`Failed to load project config: ${err.message}`, "CONFIG_LOAD_ERROR");
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
let manifest = options?.manifest;
|
|
692
|
-
if (!manifest) {
|
|
693
|
-
const loaded = loadManifest(root);
|
|
694
|
-
if (loaded) {
|
|
695
|
-
manifest = loaded;
|
|
696
|
-
} else {
|
|
697
|
-
manifest = generateStaticManifest(root, config.actionsDir || "actions");
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
const validation = validateManifest(manifest, { projectRoot: root });
|
|
701
|
-
if (!validation.valid) {
|
|
702
|
-
throw new PlannerError(`Invalid actiondock.manifest.json: ${(validation.errors || []).join("; ")}`, "INVALID_MANIFEST");
|
|
703
|
-
}
|
|
704
|
-
const playbooksMap = loadProjectPlaybooks(root, config.playbooksDir || "playbooks");
|
|
705
|
-
const initialActionIds = new Set;
|
|
706
|
-
let selectedPlaybooks = [];
|
|
707
|
-
if (options?.playbooks && options.playbooks.length > 0) {
|
|
708
|
-
for (const pbId of options.playbooks) {
|
|
709
|
-
const pb = playbooksMap.get(pbId);
|
|
710
|
-
if (!pb) {
|
|
711
|
-
throw new PlannerError(`Playbook '${pbId}' specified in build options was not found in project`, "PLAYBOOK_NOT_FOUND");
|
|
712
|
-
}
|
|
713
|
-
selectedPlaybooks.push(pb);
|
|
714
|
-
if (pb.actions && pb.actions.length > 0) {
|
|
715
|
-
for (const act of pb.actions) {
|
|
716
|
-
initialActionIds.add(act);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
} else {
|
|
721
|
-
selectedPlaybooks = Array.from(playbooksMap.values());
|
|
722
|
-
}
|
|
723
|
-
if (options?.actions && options.actions.length > 0) {
|
|
724
|
-
for (const actId of options.actions) {
|
|
725
|
-
if (!manifest.actions[actId]) {
|
|
726
|
-
throw new PlannerError(`Action '${actId}' specified in build options was not found in manifest`, "ACTION_NOT_FOUND");
|
|
727
|
-
}
|
|
728
|
-
initialActionIds.add(actId);
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
if ((!options?.actions || options.actions.length === 0) && (!options?.playbooks || options.playbooks.length === 0)) {
|
|
732
|
-
for (const actId of Object.keys(manifest.actions)) {
|
|
733
|
-
initialActionIds.add(actId);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
const resolvedActionIds = new Set;
|
|
737
|
-
const queue = Array.from(initialActionIds);
|
|
738
|
-
const externalActionRoots = new Map;
|
|
739
|
-
const externalActionEntries = new Map;
|
|
740
|
-
while (queue.length > 0) {
|
|
741
|
-
const currentId = queue.shift();
|
|
742
|
-
if (resolvedActionIds.has(currentId)) {
|
|
743
|
-
continue;
|
|
744
|
-
}
|
|
745
|
-
let entry = manifest.actions[currentId];
|
|
746
|
-
let entryRoot = root;
|
|
747
|
-
if (!entry) {
|
|
748
|
-
try {
|
|
749
|
-
const resolvedExternal = resolveActionProjectSync(currentId, root);
|
|
750
|
-
if (resolvedExternal && existsSync2(resolvedExternal.projectRoot)) {
|
|
751
|
-
let externalManifest = loadManifest(resolvedExternal.projectRoot);
|
|
752
|
-
let externalEntry = externalManifest?.actions?.[resolvedExternal.actionId];
|
|
753
|
-
if (!externalEntry) {
|
|
754
|
-
const extConfig = loadProjectConfig(resolvedExternal.projectRoot);
|
|
755
|
-
const staticManifest = generateStaticManifest(resolvedExternal.projectRoot, extConfig.actionsDir || "actions");
|
|
756
|
-
externalEntry = staticManifest.actions[resolvedExternal.actionId];
|
|
757
|
-
}
|
|
758
|
-
if (externalEntry) {
|
|
759
|
-
entry = externalEntry;
|
|
760
|
-
entryRoot = resolvedExternal.projectRoot;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
} catch {}
|
|
764
|
-
}
|
|
765
|
-
if (!entry) {
|
|
766
|
-
throw new PlannerError(`Action '${currentId}' referenced in dependency closure (uses) was not found in manifest or linked packages`, "MISSING_DEPENDENCY");
|
|
767
|
-
}
|
|
768
|
-
if (entryRoot !== root) {
|
|
769
|
-
externalActionRoots.set(currentId, entryRoot);
|
|
770
|
-
externalActionEntries.set(currentId, entry);
|
|
771
|
-
}
|
|
772
|
-
resolvedActionIds.add(currentId);
|
|
773
|
-
if (entry.uses && Array.isArray(entry.uses)) {
|
|
774
|
-
for (const depId of entry.uses) {
|
|
775
|
-
if (!resolvedActionIds.has(depId)) {
|
|
776
|
-
queue.push(depId);
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
if (options?.actions && options.actions.length > 0 && (!options.playbooks || options.playbooks.length === 0)) {
|
|
782
|
-
selectedPlaybooks = selectedPlaybooks.filter((pb) => {
|
|
783
|
-
if (!pb.actions || pb.actions.length === 0)
|
|
784
|
-
return true;
|
|
785
|
-
return pb.actions.every((a) => resolvedActionIds.has(a));
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
const actionDependencies = [];
|
|
789
|
-
for (const actId of resolvedActionIds) {
|
|
790
|
-
const entry = manifest.actions[actId] || externalActionEntries.get(actId);
|
|
791
|
-
const entryRoot = externalActionRoots.get(actId) || root;
|
|
792
|
-
if (!entry) {
|
|
793
|
-
throw new PlannerError(`Action '${actId}' entry not found`, "ENTRY_FILE_NOT_FOUND");
|
|
794
|
-
}
|
|
795
|
-
const resolvedPath = resolve2(entryRoot, entry.entry);
|
|
796
|
-
if (!existsSync2(resolvedPath)) {
|
|
797
|
-
throw new PlannerError(`Action '${actId}' entry file not found on disk: ${entry.entry}`, "ENTRY_FILE_NOT_FOUND");
|
|
798
|
-
}
|
|
799
|
-
actionDependencies.push({
|
|
800
|
-
id: actId,
|
|
801
|
-
entry: entry.entry,
|
|
802
|
-
resolvedPath,
|
|
803
|
-
uses: entry.uses || [],
|
|
804
|
-
description: entry.description,
|
|
805
|
-
inputSchema: entry.inputSchema,
|
|
806
|
-
outputSchema: entry.outputSchema,
|
|
807
|
-
tags: entry.tags,
|
|
808
|
-
annotations: entry.annotations
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
|
-
const modulesAndAssets = [];
|
|
812
|
-
const assetPathSet = new Set;
|
|
813
|
-
const modulePathSet = new Set;
|
|
814
|
-
const actionPathSet = new Set(actionDependencies.map((a) => a.resolvedPath));
|
|
815
|
-
const rootActionsMap = new Map;
|
|
816
|
-
for (const act of actionDependencies) {
|
|
817
|
-
const actRoot = externalActionRoots.get(act.id) || root;
|
|
818
|
-
if (!rootActionsMap.has(actRoot)) {
|
|
819
|
-
rootActionsMap.set(actRoot, []);
|
|
820
|
-
}
|
|
821
|
-
rootActionsMap.get(actRoot).push(act.resolvedPath);
|
|
822
|
-
}
|
|
823
|
-
for (const [actRoot, files] of rootActionsMap) {
|
|
824
|
-
const tracedModulePaths = traceLocalModuleDependencies(files, actRoot);
|
|
825
|
-
for (const modPath of tracedModulePaths) {
|
|
826
|
-
if (!actionPathSet.has(modPath)) {
|
|
827
|
-
const rel = relative(actRoot, modPath).replace(/\\/g, "/");
|
|
828
|
-
if (!modulePathSet.has(rel) && !isIgnoredModulePath(rel)) {
|
|
829
|
-
modulePathSet.add(rel);
|
|
830
|
-
modulesAndAssets.push({
|
|
831
|
-
path: rel,
|
|
832
|
-
resolvedPath: modPath,
|
|
833
|
-
type: "module"
|
|
834
|
-
});
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
const isSelective = Boolean(options?.actions && options.actions.length > 0) || Boolean(options?.playbooks && options.playbooks.length > 0);
|
|
840
|
-
if (!isSelective) {
|
|
841
|
-
const defaultLibDir = join(root, "lib");
|
|
842
|
-
if (existsSync2(defaultLibDir)) {
|
|
843
|
-
const libFiles = walkDirectory(defaultLibDir, root);
|
|
844
|
-
for (const file of libFiles) {
|
|
845
|
-
const rel = relative(root, file).replace(/\\/g, "/");
|
|
846
|
-
if (!isIgnoredModulePath(rel) && !actionPathSet.has(file) && !modulePathSet.has(rel)) {
|
|
847
|
-
modulePathSet.add(rel);
|
|
848
|
-
modulesAndAssets.push({
|
|
849
|
-
path: rel,
|
|
850
|
-
resolvedPath: file,
|
|
851
|
-
type: "module"
|
|
852
|
-
});
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
if (manifest.assets && Array.isArray(manifest.assets)) {
|
|
858
|
-
for (const assetRel of manifest.assets) {
|
|
859
|
-
const resolvedAsset = resolve2(root, assetRel);
|
|
860
|
-
assertPathWithinRoot(root, resolvedAsset, "asset");
|
|
861
|
-
if (!assetPathSet.has(assetRel)) {
|
|
862
|
-
assetPathSet.add(assetRel);
|
|
863
|
-
modulesAndAssets.push({
|
|
864
|
-
path: assetRel,
|
|
865
|
-
resolvedPath: resolvedAsset,
|
|
866
|
-
type: "asset"
|
|
867
|
-
});
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
const defaultAssetsDir = join(root, "assets");
|
|
872
|
-
if (existsSync2(defaultAssetsDir)) {
|
|
873
|
-
const assetFiles = walkDirectory(defaultAssetsDir, root);
|
|
874
|
-
for (const file of assetFiles) {
|
|
875
|
-
const rel = relative(root, file);
|
|
876
|
-
if (!assetPathSet.has(rel)) {
|
|
877
|
-
assetPathSet.add(rel);
|
|
878
|
-
modulesAndAssets.push({
|
|
879
|
-
path: rel,
|
|
880
|
-
resolvedPath: file,
|
|
881
|
-
type: "asset"
|
|
882
|
-
});
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
}
|
|
886
|
-
for (const pb of selectedPlaybooks) {
|
|
887
|
-
const rel = relative(root, pb.filePath);
|
|
888
|
-
modulesAndAssets.push({
|
|
889
|
-
path: rel,
|
|
890
|
-
resolvedPath: pb.filePath,
|
|
891
|
-
type: "playbook"
|
|
892
|
-
});
|
|
893
|
-
}
|
|
894
|
-
const configPath = join(root, "actiondock.json");
|
|
895
|
-
if (existsSync2(configPath)) {
|
|
896
|
-
modulesAndAssets.push({
|
|
897
|
-
path: "actiondock.json",
|
|
898
|
-
resolvedPath: configPath,
|
|
899
|
-
type: "config"
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
const manifestPath = join(root, "actiondock.manifest.json");
|
|
903
|
-
if (existsSync2(manifestPath)) {
|
|
904
|
-
modulesAndAssets.push({
|
|
905
|
-
path: "actiondock.manifest.json",
|
|
906
|
-
resolvedPath: manifestPath,
|
|
907
|
-
type: "config"
|
|
908
|
-
});
|
|
909
|
-
}
|
|
910
|
-
const externalDependencies = extractExternalDependencies(root);
|
|
911
|
-
const dependencies = {
|
|
912
|
-
actions: actionDependencies,
|
|
913
|
-
modulesAndAssets,
|
|
914
|
-
external: externalDependencies
|
|
915
|
-
};
|
|
916
|
-
return {
|
|
917
|
-
packageId: config.id,
|
|
918
|
-
packageName: config.name || config.id,
|
|
919
|
-
version: config.version || "0.1.0",
|
|
920
|
-
description: config.description,
|
|
921
|
-
projectRoot: root,
|
|
922
|
-
actionsDir: config.actionsDir || "actions",
|
|
923
|
-
playbooksDir: config.playbooksDir || "playbooks",
|
|
924
|
-
actions: actionDependencies,
|
|
925
|
-
playbooks: selectedPlaybooks,
|
|
926
|
-
dependencies,
|
|
927
|
-
assets: Array.from(assetPathSet),
|
|
928
|
-
configDefs: config.config,
|
|
929
|
-
metadata: {
|
|
930
|
-
plannedAt: new Date().toISOString(),
|
|
931
|
-
schemaVersion: 1,
|
|
932
|
-
actionCount: actionDependencies.length,
|
|
933
|
-
playbookCount: selectedPlaybooks.length
|
|
934
|
-
}
|
|
935
|
-
};
|
|
936
|
-
}
|
|
937
|
-
static plan(options) {
|
|
938
|
-
const planner = new BuildPlanner({ projectRoot: options.projectRoot });
|
|
939
|
-
return planner.plan(options);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
function buildPlan(options) {
|
|
943
|
-
return BuildPlanner.plan(options);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
// packages/builder/src/build.ts
|
|
947
|
-
async function buildProject(options) {
|
|
948
|
-
const root = resolve3(options.projectRoot);
|
|
949
|
-
const plan = BuildPlanner.plan({
|
|
950
|
-
projectRoot: root,
|
|
951
|
-
actions: options.actions
|
|
952
|
-
});
|
|
953
|
-
if (plan.actions.length === 0) {
|
|
954
|
-
throw new BuilderError("No actions resolved for standalone compilation");
|
|
955
|
-
}
|
|
956
|
-
const buildDir = join2(root, ".actiondock", ".build");
|
|
957
|
-
mkdirSync2(buildDir, { recursive: true });
|
|
958
|
-
const entryCode = generateStandaloneEntrypoint(plan.packageId, plan.version, plan.description, plan.actions.map((a) => ({ id: a.id, filePath: a.resolvedPath })), plan.configDefs);
|
|
959
|
-
const entryFileName = `entry-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.ts`;
|
|
960
|
-
const entryPath = join2(buildDir, entryFileName);
|
|
961
|
-
writeFileSync2(entryPath, entryCode, "utf-8");
|
|
962
|
-
const binaryName = getPackageSlug(plan.packageId);
|
|
963
|
-
const defaultOutfile = join2(root, "dist", binaryName);
|
|
964
|
-
const outfile = resolve3(options.outfile || defaultOutfile);
|
|
965
|
-
try {
|
|
966
|
-
const result = await BunCompiler.compile({
|
|
967
|
-
entrypoint: entryPath,
|
|
968
|
-
outfile,
|
|
969
|
-
target: options.target,
|
|
970
|
-
minify: options.minify,
|
|
971
|
-
bytecode: options.bytecode,
|
|
972
|
-
cwd: root,
|
|
973
|
-
packageId: plan.packageId,
|
|
974
|
-
version: plan.version,
|
|
975
|
-
actions: plan.actions.map((a) => a.id)
|
|
976
|
-
});
|
|
977
|
-
return {
|
|
978
|
-
packageId: result.packageId || plan.packageId,
|
|
979
|
-
version: result.version || plan.version,
|
|
980
|
-
target: result.target,
|
|
981
|
-
executablePath: result.executablePath,
|
|
982
|
-
metadataPath: result.metadataPath || "",
|
|
983
|
-
actions: plan.actions.map((a) => a.id)
|
|
984
|
-
};
|
|
985
|
-
} finally {
|
|
986
|
-
if (existsSync3(entryPath)) {
|
|
987
|
-
rmSync(entryPath, { force: true });
|
|
988
|
-
}
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
// packages/builder/src/exporter.ts
|
|
992
|
-
import { copyFileSync, cpSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, readdirSync as readdirSync3, renameSync, rmSync as rmSync2, statSync as statSync4, writeFileSync as writeFileSync4 } from "node:fs";
|
|
993
|
-
import { basename as basename4, dirname as dirname4, join as join4, relative as relative3, resolve as resolve4 } from "node:path";
|
|
994
|
-
import {
|
|
995
|
-
ACTIONDOCK_VERSION,
|
|
996
|
-
generateCompositeSkillMd,
|
|
997
|
-
generateSourceSkillMd,
|
|
998
|
-
generateStandaloneEntrypoint as generateStandaloneEntrypoint2,
|
|
999
|
-
generateStandaloneSkillMd,
|
|
1000
|
-
getPackageSlug as getPackageSlug2,
|
|
1001
|
-
loadManifest as loadManifest2,
|
|
1002
|
-
loadPlaybooks,
|
|
1003
|
-
loadProjectConfig as loadProjectConfig2
|
|
1004
|
-
} from "@actiondock/core";
|
|
1005
|
-
|
|
1006
|
-
// packages/builder/src/archive.ts
|
|
1007
|
-
import { closeSync, createReadStream as createReadStream2, createWriteStream, openSync, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync as statSync3, writeFileSync as writeFileSync3, writeSync } from "node:fs";
|
|
1008
|
-
import { deflateRawSync, gzipSync, createGzip } from "node:zlib";
|
|
1009
|
-
import { basename as basename3, join as join3, relative as relative2, sep } from "node:path";
|
|
1010
|
-
import { pipeline } from "node:stream/promises";
|
|
1011
|
-
function collectEntries(dir, baseDir = dir) {
|
|
1012
|
-
const entries = [];
|
|
1013
|
-
for (const name of readdirSync2(dir).sort()) {
|
|
1014
|
-
const fullPath = join3(dir, name);
|
|
1015
|
-
const stat = statSync3(fullPath);
|
|
1016
|
-
if (stat.isDirectory()) {
|
|
1017
|
-
entries.push({ relPath: relative2(baseDir, fullPath).split(sep).join("/"), isDir: true });
|
|
1018
|
-
entries.push(...collectEntries(fullPath, baseDir));
|
|
1019
|
-
} else if (stat.isFile()) {
|
|
1020
|
-
entries.push({ relPath: relative2(baseDir, fullPath).split(sep).join("/"), isDir: false });
|
|
1021
|
-
}
|
|
1022
|
-
}
|
|
1023
|
-
return entries;
|
|
1024
|
-
}
|
|
1025
|
-
var CRC32_TABLE = (() => {
|
|
1026
|
-
const table = new Uint32Array(256);
|
|
1027
|
-
for (let n = 0;n < 256; n++) {
|
|
1028
|
-
let c = n;
|
|
1029
|
-
for (let k = 0;k < 8; k++) {
|
|
1030
|
-
c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1;
|
|
1031
|
-
}
|
|
1032
|
-
table[n] = c >>> 0;
|
|
1033
|
-
}
|
|
1034
|
-
return table;
|
|
1035
|
-
})();
|
|
1036
|
-
function crc32(buf) {
|
|
1037
|
-
let crc = 4294967295;
|
|
1038
|
-
for (let i = 0;i < buf.length; i++) {
|
|
1039
|
-
crc = CRC32_TABLE[(crc ^ buf[i]) & 255] ^ crc >>> 8;
|
|
1040
|
-
}
|
|
1041
|
-
return (crc ^ 4294967295) >>> 0;
|
|
1042
|
-
}
|
|
1043
|
-
function dosDateTime(ms) {
|
|
1044
|
-
const d = new Date(ms);
|
|
1045
|
-
const year = Math.max(d.getFullYear(), 1980);
|
|
1046
|
-
const time = d.getHours() << 11 | d.getMinutes() << 5 | d.getSeconds() >> 1;
|
|
1047
|
-
const date = year - 1980 << 9 | d.getMonth() + 1 << 5 | d.getDate();
|
|
1048
|
-
return { date, time };
|
|
1049
|
-
}
|
|
1050
|
-
function createZipArchive(dir, outPath) {
|
|
1051
|
-
const rootName = basename3(dir);
|
|
1052
|
-
const entries = collectEntries(dir);
|
|
1053
|
-
const fd = openSync(outPath, "w");
|
|
1054
|
-
const centralRecords = [];
|
|
1055
|
-
let offset = 0;
|
|
1056
|
-
try {
|
|
1057
|
-
for (const entry of entries) {
|
|
1058
|
-
const name = `${rootName}/${entry.relPath}${entry.isDir ? "/" : ""}`;
|
|
1059
|
-
const nameBuf = Buffer.from(name, "utf8");
|
|
1060
|
-
const stat = statSync3(join3(dir, entry.relPath));
|
|
1061
|
-
const { date, time } = dosDateTime(stat.mtimeMs);
|
|
1062
|
-
const content = entry.isDir ? Buffer.alloc(0) : readFileSync2(join3(dir, entry.relPath));
|
|
1063
|
-
let method = 0;
|
|
1064
|
-
let payload = content;
|
|
1065
|
-
if (!entry.isDir && content.length > 0) {
|
|
1066
|
-
const deflated = deflateRawSync(content, { level: 9 });
|
|
1067
|
-
if (deflated.length < content.length) {
|
|
1068
|
-
method = 8;
|
|
1069
|
-
payload = deflated;
|
|
1070
|
-
}
|
|
1071
|
-
}
|
|
1072
|
-
const crc = crc32(content);
|
|
1073
|
-
const local = Buffer.alloc(30);
|
|
1074
|
-
local.writeUInt32LE(67324752, 0);
|
|
1075
|
-
local.writeUInt16LE(20, 4);
|
|
1076
|
-
local.writeUInt16LE(2048, 6);
|
|
1077
|
-
local.writeUInt16LE(method, 8);
|
|
1078
|
-
local.writeUInt16LE(time, 10);
|
|
1079
|
-
local.writeUInt16LE(date, 12);
|
|
1080
|
-
local.writeUInt32LE(crc, 14);
|
|
1081
|
-
local.writeUInt32LE(payload.length, 18);
|
|
1082
|
-
local.writeUInt32LE(content.length, 22);
|
|
1083
|
-
local.writeUInt16LE(nameBuf.length, 26);
|
|
1084
|
-
local.writeUInt16LE(0, 28);
|
|
1085
|
-
writeSync(fd, local);
|
|
1086
|
-
writeSync(fd, nameBuf);
|
|
1087
|
-
if (payload.length > 0) {
|
|
1088
|
-
writeSync(fd, payload);
|
|
1089
|
-
}
|
|
1090
|
-
const central = Buffer.alloc(46);
|
|
1091
|
-
central.writeUInt32LE(33639248, 0);
|
|
1092
|
-
central.writeUInt16LE(3 << 8 | 20, 4);
|
|
1093
|
-
central.writeUInt16LE(20, 6);
|
|
1094
|
-
central.writeUInt16LE(2048, 8);
|
|
1095
|
-
central.writeUInt16LE(method, 10);
|
|
1096
|
-
central.writeUInt16LE(time, 12);
|
|
1097
|
-
central.writeUInt16LE(date, 14);
|
|
1098
|
-
central.writeUInt32LE(crc, 16);
|
|
1099
|
-
central.writeUInt32LE(payload.length, 20);
|
|
1100
|
-
central.writeUInt32LE(content.length, 24);
|
|
1101
|
-
central.writeUInt16LE(nameBuf.length, 28);
|
|
1102
|
-
central.writeUInt16LE(0, 30);
|
|
1103
|
-
central.writeUInt16LE(0, 32);
|
|
1104
|
-
central.writeUInt16LE(0, 34);
|
|
1105
|
-
central.writeUInt16LE(0, 36);
|
|
1106
|
-
const isExec = !entry.isDir && (Boolean(stat.mode & 73) || entry.relPath.includes("bin/") || entry.relPath.startsWith("bin/"));
|
|
1107
|
-
const fileMode = isExec ? 33261 : 33188;
|
|
1108
|
-
const extAttrs = (entry.isDir ? 16877 : fileMode) << 16 | (entry.isDir ? 16 : 0);
|
|
1109
|
-
central.writeUInt32LE(extAttrs >>> 0, 38);
|
|
1110
|
-
central.writeUInt32LE(offset, 42);
|
|
1111
|
-
centralRecords.push({ header: Buffer.concat([central, nameBuf]), localOffset: offset });
|
|
1112
|
-
offset += local.length + nameBuf.length + payload.length;
|
|
1113
|
-
}
|
|
1114
|
-
const centralDirOffset = offset;
|
|
1115
|
-
let centralDirSize = 0;
|
|
1116
|
-
for (const record of centralRecords) {
|
|
1117
|
-
writeSync(fd, record.header);
|
|
1118
|
-
centralDirSize += record.header.length;
|
|
1119
|
-
}
|
|
1120
|
-
const eocd = Buffer.alloc(22);
|
|
1121
|
-
eocd.writeUInt32LE(101010256, 0);
|
|
1122
|
-
eocd.writeUInt16LE(0, 4);
|
|
1123
|
-
eocd.writeUInt16LE(0, 6);
|
|
1124
|
-
eocd.writeUInt16LE(centralRecords.length, 8);
|
|
1125
|
-
eocd.writeUInt16LE(centralRecords.length, 10);
|
|
1126
|
-
eocd.writeUInt32LE(centralDirSize, 12);
|
|
1127
|
-
eocd.writeUInt32LE(centralDirOffset, 16);
|
|
1128
|
-
eocd.writeUInt16LE(0, 20);
|
|
1129
|
-
writeSync(fd, eocd);
|
|
1130
|
-
} finally {
|
|
1131
|
-
closeSync(fd);
|
|
1132
|
-
}
|
|
1133
|
-
}
|
|
1134
|
-
async function createZipArchiveAsync(dir, outPath) {
|
|
1135
|
-
createZipArchive(dir, outPath);
|
|
1136
|
-
}
|
|
1137
|
-
function tarHeader(path, size, mtimeSec, isDir, modeOrExec = false) {
|
|
1138
|
-
let name = path;
|
|
1139
|
-
let prefix = "";
|
|
1140
|
-
if (Buffer.byteLength(name, "utf8") > 100) {
|
|
1141
|
-
const trailingSlash = name.endsWith("/");
|
|
1142
|
-
const cleanPath = trailingSlash ? name.slice(0, -1) : name;
|
|
1143
|
-
const split = cleanPath.lastIndexOf("/");
|
|
1144
|
-
prefix = cleanPath.slice(0, split);
|
|
1145
|
-
name = cleanPath.slice(split + 1) + (trailingSlash ? "/" : "");
|
|
1146
|
-
if (split <= 0 || Buffer.byteLength(prefix, "utf8") > 155 || Buffer.byteLength(name, "utf8") > 100) {
|
|
1147
|
-
throw new Error(`归档路径超出 USTAR 字段容量: ${path}`);
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
const buf = Buffer.alloc(512);
|
|
1151
|
-
buf.write(name, 0, 100, "utf8");
|
|
1152
|
-
let mode;
|
|
1153
|
-
if (typeof modeOrExec === "string") {
|
|
1154
|
-
mode = modeOrExec;
|
|
1155
|
-
} else {
|
|
1156
|
-
mode = isDir ? "0000755" : modeOrExec ? "0000755" : "0000644";
|
|
1157
|
-
}
|
|
1158
|
-
buf.write(mode, 100, 8, "ascii");
|
|
1159
|
-
buf.write("0000000", 108, 8, "ascii");
|
|
1160
|
-
buf.write("0000000", 116, 8, "ascii");
|
|
1161
|
-
buf.write(size.toString(8).padStart(11, "0"), 124, 12, "ascii");
|
|
1162
|
-
buf.write(Math.floor(mtimeSec).toString(8).padStart(11, "0"), 136, 12, "ascii");
|
|
1163
|
-
buf.fill(" ", 148, 156);
|
|
1164
|
-
buf.write(isDir ? "5" : "0", 156, 1, "ascii");
|
|
1165
|
-
buf.write("ustar\x00", 257, 6, "ascii");
|
|
1166
|
-
buf.write("00", 263, 2, "ascii");
|
|
1167
|
-
if (prefix)
|
|
1168
|
-
buf.write(prefix, 345, 155, "utf8");
|
|
1169
|
-
let sum = 0;
|
|
1170
|
-
for (let i = 0;i < 512; i++)
|
|
1171
|
-
sum += buf[i];
|
|
1172
|
-
buf.write(sum.toString(8).padStart(6, "0"), 148, 7, "ascii");
|
|
1173
|
-
buf.write("\x00", 154, 1, "ascii");
|
|
1174
|
-
buf.write(" ", 155, 1, "ascii");
|
|
1175
|
-
return buf;
|
|
1176
|
-
}
|
|
1177
|
-
function pad512(length) {
|
|
1178
|
-
const rem = length % 512;
|
|
1179
|
-
return rem === 0 ? Buffer.alloc(0) : Buffer.alloc(512 - rem);
|
|
1180
|
-
}
|
|
1181
|
-
function createTarGzArchive(dir, outPath) {
|
|
1182
|
-
const rootName = basename3(dir);
|
|
1183
|
-
const entries = collectEntries(dir);
|
|
1184
|
-
const chunks = [];
|
|
1185
|
-
for (const entry of entries) {
|
|
1186
|
-
const path = `${rootName}/${entry.relPath}${entry.isDir ? "/" : ""}`;
|
|
1187
|
-
const fullPath = join3(dir, entry.relPath);
|
|
1188
|
-
const stat = statSync3(fullPath);
|
|
1189
|
-
const content = entry.isDir ? Buffer.alloc(0) : readFileSync2(fullPath);
|
|
1190
|
-
const isExec = !entry.isDir && (Boolean(stat.mode & 73) || entry.relPath.includes("bin/") || entry.relPath.startsWith("bin/"));
|
|
1191
|
-
chunks.push(tarHeader(path, content.length, Math.floor(stat.mtimeMs / 1000), entry.isDir, isExec));
|
|
1192
|
-
if (!entry.isDir) {
|
|
1193
|
-
chunks.push(content, pad512(content.length));
|
|
1194
|
-
}
|
|
1195
|
-
}
|
|
1196
|
-
chunks.push(Buffer.alloc(1024));
|
|
1197
|
-
writeFileSync3(outPath, gzipSync(Buffer.concat(chunks), { level: 9 }));
|
|
1198
|
-
}
|
|
1199
|
-
async function createTarGzArchiveAsync(dir, outPath) {
|
|
1200
|
-
const rootName = basename3(dir);
|
|
1201
|
-
const entries = collectEntries(dir);
|
|
1202
|
-
const gzip = createGzip({ level: 9 });
|
|
1203
|
-
const writeStream = createWriteStream(outPath);
|
|
1204
|
-
const pipePromise = pipeline(gzip, writeStream);
|
|
1205
|
-
for (const entry of entries) {
|
|
1206
|
-
const path = `${rootName}/${entry.relPath}${entry.isDir ? "/" : ""}`;
|
|
1207
|
-
const fullPath = join3(dir, entry.relPath);
|
|
1208
|
-
const stat = statSync3(fullPath);
|
|
1209
|
-
const isExec = !entry.isDir && (Boolean(stat.mode & 73) || entry.relPath.includes("bin/") || entry.relPath.startsWith("bin/"));
|
|
1210
|
-
const header = tarHeader(path, entry.isDir ? 0 : stat.size, Math.floor(stat.mtimeMs / 1000), entry.isDir, isExec);
|
|
1211
|
-
gzip.write(header);
|
|
1212
|
-
if (!entry.isDir && stat.size > 0) {
|
|
1213
|
-
await new Promise((resolve, reject) => {
|
|
1214
|
-
const fileStream = createReadStream2(fullPath);
|
|
1215
|
-
fileStream.on("data", (chunk) => gzip.write(chunk));
|
|
1216
|
-
fileStream.on("end", () => {
|
|
1217
|
-
const rem = stat.size % 512;
|
|
1218
|
-
if (rem !== 0) {
|
|
1219
|
-
gzip.write(Buffer.alloc(512 - rem));
|
|
1220
|
-
}
|
|
1221
|
-
resolve();
|
|
1222
|
-
});
|
|
1223
|
-
fileStream.on("error", reject);
|
|
1224
|
-
});
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
gzip.write(Buffer.alloc(1024));
|
|
1228
|
-
gzip.end();
|
|
1229
|
-
await pipePromise;
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
// packages/builder/src/exporter.ts
|
|
1233
|
-
function moveDirAtomic(src, dest) {
|
|
1234
|
-
try {
|
|
1235
|
-
renameSync(src, dest);
|
|
1236
|
-
} catch (err) {
|
|
1237
|
-
if (err?.code === "EXDEV") {
|
|
1238
|
-
cpSync(src, dest, { recursive: true });
|
|
1239
|
-
rmSync2(src, { recursive: true, force: true });
|
|
1240
|
-
} else {
|
|
1241
|
-
throw err;
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1244
|
-
}
|
|
1245
|
-
function getInternalDependencyVersion(version = ACTIONDOCK_VERSION) {
|
|
1246
|
-
if (version.includes("-")) {
|
|
1247
|
-
return version;
|
|
1248
|
-
}
|
|
1249
|
-
return `^${version}`;
|
|
1250
|
-
}
|
|
1251
|
-
function resolveWorkspaceDepVersion(root, depName, ver) {
|
|
1252
|
-
if (depName.startsWith("@actiondock/")) {
|
|
1253
|
-
return getInternalDependencyVersion();
|
|
1254
|
-
}
|
|
1255
|
-
const stripped = ver.replace(/^workspace:/, "").trim();
|
|
1256
|
-
if (stripped && stripped !== "*" && stripped !== "^" && stripped !== "~") {
|
|
1257
|
-
return stripped;
|
|
1258
|
-
}
|
|
1259
|
-
const candidates = [
|
|
1260
|
-
resolve4(root, "node_modules", depName, "package.json"),
|
|
1261
|
-
resolve4(root, "packages", depName, "package.json"),
|
|
1262
|
-
resolve4(root, "packages", depName.split("/").pop(), "package.json"),
|
|
1263
|
-
resolve4(root, "..", depName, "package.json"),
|
|
1264
|
-
resolve4(root, "..", depName.split("/").pop(), "package.json"),
|
|
1265
|
-
resolve4(root, "..", "packages", depName, "package.json"),
|
|
1266
|
-
resolve4(root, "..", "packages", depName.split("/").pop(), "package.json"),
|
|
1267
|
-
resolve4(root, "..", "..", "packages", depName, "package.json"),
|
|
1268
|
-
resolve4(root, "..", "..", "packages", depName.split("/").pop(), "package.json")
|
|
1269
|
-
];
|
|
1270
|
-
for (const cand of candidates) {
|
|
1271
|
-
if (existsSync4(cand)) {
|
|
1272
|
-
try {
|
|
1273
|
-
const pkgData = JSON.parse(readFileSync3(cand, "utf-8"));
|
|
1274
|
-
if (pkgData.version) {
|
|
1275
|
-
const prefix = stripped === "~" ? "~" : "^";
|
|
1276
|
-
return `${prefix}${pkgData.version}`;
|
|
1277
|
-
}
|
|
1278
|
-
} catch {}
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
throw new BuilderError(`Failed to resolve workspace dependency '${depName}' (${ver}) in '${root}'. Target package version could not be found.`);
|
|
1282
|
-
}
|
|
1283
|
-
function scanRelativeFiles(dir, baseDir = dir) {
|
|
1284
|
-
if (!existsSync4(dir))
|
|
1285
|
-
return [];
|
|
1286
|
-
const results = [];
|
|
1287
|
-
const entries = readdirSync3(dir);
|
|
1288
|
-
for (const entry of entries) {
|
|
1289
|
-
const fullPath = join4(dir, entry);
|
|
1290
|
-
const stat = statSync4(fullPath);
|
|
1291
|
-
if (stat.isDirectory()) {
|
|
1292
|
-
results.push(...scanRelativeFiles(fullPath, baseDir));
|
|
1293
|
-
} else if (stat.isFile()) {
|
|
1294
|
-
results.push(relative3(baseDir, fullPath));
|
|
1295
|
-
}
|
|
1296
|
-
}
|
|
1297
|
-
return results;
|
|
1298
|
-
}
|
|
1299
|
-
async function createArchive(skillDir, format) {
|
|
1300
|
-
const parentDir = dirname4(skillDir);
|
|
1301
|
-
const folderName = basename4(skillDir);
|
|
1302
|
-
const archiveName = `${folderName}.${format === "tar.gz" ? "tar.gz" : "zip"}`;
|
|
1303
|
-
const archivePath = join4(parentDir, archiveName);
|
|
1304
|
-
if (existsSync4(archivePath)) {
|
|
1305
|
-
rmSync2(archivePath, { force: true });
|
|
1306
|
-
}
|
|
1307
|
-
try {
|
|
1308
|
-
if (format === "tar.gz") {
|
|
1309
|
-
await createTarGzArchiveAsync(skillDir, archivePath);
|
|
1310
|
-
} else {
|
|
1311
|
-
await createZipArchiveAsync(skillDir, archivePath);
|
|
1312
|
-
}
|
|
1313
|
-
} catch (err) {
|
|
1314
|
-
throw new BuilderError(`Failed to create ${format} archive: ${err?.message || String(err)}`);
|
|
1315
|
-
}
|
|
1316
|
-
return archivePath;
|
|
1317
|
-
}
|
|
1318
|
-
function findExistingSingleSkillMd(projectRoot, explicitPath, pkgSlug) {
|
|
1319
|
-
if (explicitPath && existsSync4(explicitPath)) {
|
|
1320
|
-
try {
|
|
1321
|
-
if (statSync4(explicitPath).isFile())
|
|
1322
|
-
return resolve4(explicitPath);
|
|
1323
|
-
} catch {}
|
|
1324
|
-
}
|
|
1325
|
-
const candidates = [
|
|
1326
|
-
join4(projectRoot, "SKILL.md"),
|
|
1327
|
-
join4(projectRoot, "skill.md"),
|
|
1328
|
-
join4(projectRoot, "skills", "SKILL.md"),
|
|
1329
|
-
join4(projectRoot, "skills", "skill.md")
|
|
1330
|
-
];
|
|
1331
|
-
if (pkgSlug) {
|
|
1332
|
-
candidates.push(join4(projectRoot, "skills", pkgSlug, "SKILL.md"), join4(projectRoot, "skills", pkgSlug, "skill.md"));
|
|
1333
|
-
}
|
|
1334
|
-
for (const candidate of candidates) {
|
|
1335
|
-
if (existsSync4(candidate)) {
|
|
1336
|
-
try {
|
|
1337
|
-
if (statSync4(candidate).isFile())
|
|
1338
|
-
return resolve4(candidate);
|
|
1339
|
-
} catch {}
|
|
1340
|
-
}
|
|
1341
|
-
}
|
|
1342
|
-
return;
|
|
1343
|
-
}
|
|
1344
|
-
function findExistingCompositeSkillMd(options) {
|
|
1345
|
-
if (options.skillMdPath && existsSync4(options.skillMdPath)) {
|
|
1346
|
-
try {
|
|
1347
|
-
if (statSync4(options.skillMdPath).isFile())
|
|
1348
|
-
return resolve4(options.skillMdPath);
|
|
1349
|
-
} catch {}
|
|
1350
|
-
}
|
|
1351
|
-
const bundleSlug = getPackageSlug2(options.bundleName);
|
|
1352
|
-
const searchDirs = new Set;
|
|
1353
|
-
if (options.workspaceRoot && existsSync4(options.workspaceRoot)) {
|
|
1354
|
-
searchDirs.add(resolve4(options.workspaceRoot));
|
|
1355
|
-
}
|
|
1356
|
-
searchDirs.add(process.cwd());
|
|
1357
|
-
for (const root of options.projectRoots) {
|
|
1358
|
-
const absRoot = resolve4(root);
|
|
1359
|
-
const parentDir = dirname4(absRoot);
|
|
1360
|
-
searchDirs.add(parentDir);
|
|
1361
|
-
searchDirs.add(dirname4(parentDir));
|
|
1362
|
-
}
|
|
1363
|
-
const candidateRelativePaths = [
|
|
1364
|
-
join4("skills", options.bundleName, "SKILL.md"),
|
|
1365
|
-
join4("skills", bundleSlug, "SKILL.md"),
|
|
1366
|
-
join4("skills", "SKILL.md"),
|
|
1367
|
-
"SKILL.md",
|
|
1368
|
-
join4("skills", options.bundleName, "skill.md"),
|
|
1369
|
-
join4("skills", bundleSlug, "skill.md"),
|
|
1370
|
-
join4("skills", "skill.md"),
|
|
1371
|
-
"skill.md"
|
|
1372
|
-
];
|
|
1373
|
-
for (const dir of searchDirs) {
|
|
1374
|
-
for (const rel of candidateRelativePaths) {
|
|
1375
|
-
const fullPath = join4(dir, rel);
|
|
1376
|
-
if (existsSync4(fullPath)) {
|
|
1377
|
-
try {
|
|
1378
|
-
if (statSync4(fullPath).isFile())
|
|
1379
|
-
return fullPath;
|
|
1380
|
-
} catch {}
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
return;
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
class SkillExporter {
|
|
1388
|
-
async export(options) {
|
|
1389
|
-
const root = resolve4(options.projectRoot);
|
|
1390
|
-
const mode = options.standalone || options.mode === "standalone" ? "standalone" : "source";
|
|
1391
|
-
const target = options.target ? String(options.target) : "host";
|
|
1392
|
-
const planner = new BuildPlanner({ projectRoot: root });
|
|
1393
|
-
const plan = planner.plan({
|
|
1394
|
-
projectRoot: root,
|
|
1395
|
-
config: options.config,
|
|
1396
|
-
manifest: options.manifest,
|
|
1397
|
-
actions: options.actions,
|
|
1398
|
-
playbooks: options.playbooks
|
|
1399
|
-
});
|
|
1400
|
-
const pkgSlug = getPackageSlug2(plan.packageId);
|
|
1401
|
-
const targetSuffix = mode === "standalone" && target !== "host" && target !== "bun" ? `-${target}` : "";
|
|
1402
|
-
const defaultFolderName = `${pkgSlug}-skill${targetSuffix}`;
|
|
1403
|
-
const defaultSkillDir = join4(root, "dist", defaultFolderName);
|
|
1404
|
-
const targetSkillDir = resolve4(options.outDir || defaultSkillDir);
|
|
1405
|
-
const parentDir = dirname4(targetSkillDir);
|
|
1406
|
-
mkdirSync3(parentDir, { recursive: true });
|
|
1407
|
-
const stagingDir = join4(parentDir, `.tmp-${basename4(targetSkillDir)}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
1408
|
-
mkdirSync3(stagingDir, { recursive: true });
|
|
1409
|
-
const actionsDir = plan.actionsDir || "actions";
|
|
1410
|
-
const playbooksDir = plan.playbooksDir || "playbooks";
|
|
1411
|
-
const playbooksDestDir = join4(stagingDir, playbooksDir);
|
|
1412
|
-
if (plan.playbooks.length > 0) {
|
|
1413
|
-
mkdirSync3(playbooksDestDir, { recursive: true });
|
|
1414
|
-
}
|
|
1415
|
-
const skillDir = stagingDir;
|
|
1416
|
-
const configForTemplates = {
|
|
1417
|
-
id: plan.packageId,
|
|
1418
|
-
name: plan.packageName,
|
|
1419
|
-
version: plan.version,
|
|
1420
|
-
description: plan.description,
|
|
1421
|
-
actionsDir,
|
|
1422
|
-
playbooksDir,
|
|
1423
|
-
config: plan.configDefs
|
|
1424
|
-
};
|
|
1425
|
-
let usedExistingSkillMd;
|
|
1426
|
-
try {
|
|
1427
|
-
if (mode === "source") {
|
|
1428
|
-
const existingSkill = !options.skipSkillMd ? findExistingSingleSkillMd(root, options.skillMdPath, pkgSlug) : undefined;
|
|
1429
|
-
if (existingSkill) {
|
|
1430
|
-
usedExistingSkillMd = existingSkill;
|
|
1431
|
-
const destSkillMd = join4(skillDir, "SKILL.md");
|
|
1432
|
-
if (resolve4(existingSkill) !== resolve4(destSkillMd)) {
|
|
1433
|
-
copyFileSync(existingSkill, destSkillMd);
|
|
1434
|
-
}
|
|
1435
|
-
} else if (!options.skipSkillMd) {
|
|
1436
|
-
const skillMd = generateSourceSkillMd(configForTemplates, plan.actions, plan.playbooks);
|
|
1437
|
-
writeFileSync4(join4(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
1438
|
-
}
|
|
1439
|
-
const manifestActions = {};
|
|
1440
|
-
for (const a of plan.actions) {
|
|
1441
|
-
manifestActions[a.id] = {
|
|
1442
|
-
entry: a.entry,
|
|
1443
|
-
description: a.description,
|
|
1444
|
-
inputSchema: a.inputSchema,
|
|
1445
|
-
outputSchema: a.outputSchema,
|
|
1446
|
-
uses: a.uses,
|
|
1447
|
-
tags: a.tags,
|
|
1448
|
-
annotations: a.annotations
|
|
1449
|
-
};
|
|
1450
|
-
if (a.id.includes("/")) {
|
|
1451
|
-
const shortId = a.id.slice(a.id.lastIndexOf("/") + 1);
|
|
1452
|
-
if (!manifestActions[shortId]) {
|
|
1453
|
-
manifestActions[shortId] = manifestActions[a.id];
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
const exportedManifest = {
|
|
1458
|
-
schemaVersion: 1,
|
|
1459
|
-
actions: manifestActions,
|
|
1460
|
-
assets: plan.assets
|
|
1461
|
-
};
|
|
1462
|
-
writeFileSync4(join4(skillDir, "actiondock.manifest.json"), JSON.stringify(exportedManifest, null, 2) + `
|
|
1463
|
-
`, "utf-8");
|
|
1464
|
-
const exportedConfig = {
|
|
1465
|
-
id: plan.packageId,
|
|
1466
|
-
name: plan.packageName,
|
|
1467
|
-
version: plan.version,
|
|
1468
|
-
description: plan.description,
|
|
1469
|
-
actionsDir,
|
|
1470
|
-
playbooksDir,
|
|
1471
|
-
config: plan.configDefs
|
|
1472
|
-
};
|
|
1473
|
-
writeFileSync4(join4(skillDir, "actiondock.json"), JSON.stringify(exportedConfig, null, 2) + `
|
|
1474
|
-
`, "utf-8");
|
|
1475
|
-
const projectPkgPath = join4(root, "package.json");
|
|
1476
|
-
const sanitizeDependencies = (deps) => {
|
|
1477
|
-
const result = {};
|
|
1478
|
-
if (deps && typeof deps === "object") {
|
|
1479
|
-
for (const [k, v] of Object.entries(deps)) {
|
|
1480
|
-
const verStr = String(v);
|
|
1481
|
-
if (verStr.startsWith("file:")) {
|
|
1482
|
-
throw new BuilderError(`Unsupported file: dependency for '${k}'. Runtime dependencies must not use file: protocol in exported skill packages.`);
|
|
1483
|
-
}
|
|
1484
|
-
if (k.startsWith("@actiondock/")) {
|
|
1485
|
-
result[k] = getInternalDependencyVersion();
|
|
1486
|
-
} else if (verStr.startsWith("workspace:")) {
|
|
1487
|
-
result[k] = resolveWorkspaceDepVersion(root, k, verStr);
|
|
1488
|
-
} else {
|
|
1489
|
-
result[k] = verStr;
|
|
1490
|
-
}
|
|
1491
|
-
}
|
|
1492
|
-
}
|
|
1493
|
-
if (!result["@actiondock/sdk"]) {
|
|
1494
|
-
result["@actiondock/sdk"] = getInternalDependencyVersion();
|
|
1495
|
-
}
|
|
1496
|
-
return result;
|
|
1497
|
-
};
|
|
1498
|
-
let exportedPkg;
|
|
1499
|
-
if (existsSync4(projectPkgPath)) {
|
|
1500
|
-
try {
|
|
1501
|
-
const raw = readFileSync3(projectPkgPath, "utf-8");
|
|
1502
|
-
const parsed = JSON.parse(raw);
|
|
1503
|
-
exportedPkg = {
|
|
1504
|
-
name: parsed.name || pkgSlug,
|
|
1505
|
-
version: plan.version || parsed.version || "0.1.0",
|
|
1506
|
-
description: plan.description || parsed.description,
|
|
1507
|
-
type: "module",
|
|
1508
|
-
dependencies: sanitizeDependencies(parsed.dependencies)
|
|
1509
|
-
};
|
|
1510
|
-
} catch (err) {
|
|
1511
|
-
if (err instanceof BuilderError) {
|
|
1512
|
-
throw err;
|
|
1513
|
-
}
|
|
1514
|
-
exportedPkg = {
|
|
1515
|
-
name: pkgSlug,
|
|
1516
|
-
version: plan.version,
|
|
1517
|
-
description: plan.description,
|
|
1518
|
-
type: "module",
|
|
1519
|
-
dependencies: { "@actiondock/sdk": getInternalDependencyVersion() }
|
|
1520
|
-
};
|
|
1521
|
-
}
|
|
1522
|
-
} else {
|
|
1523
|
-
exportedPkg = {
|
|
1524
|
-
name: pkgSlug,
|
|
1525
|
-
version: plan.version,
|
|
1526
|
-
description: plan.description,
|
|
1527
|
-
type: "module",
|
|
1528
|
-
dependencies: { "@actiondock/sdk": getInternalDependencyVersion() }
|
|
1529
|
-
};
|
|
1530
|
-
}
|
|
1531
|
-
writeFileSync4(join4(skillDir, "package.json"), JSON.stringify(exportedPkg, null, 2) + `
|
|
1532
|
-
`, "utf-8");
|
|
1533
|
-
const tsconfigPath = join4(root, "tsconfig.json");
|
|
1534
|
-
if (existsSync4(tsconfigPath)) {
|
|
1535
|
-
copyFileSync(tsconfigPath, join4(skillDir, "tsconfig.json"));
|
|
1536
|
-
}
|
|
1537
|
-
for (const act of plan.actions) {
|
|
1538
|
-
if (existsSync4(act.resolvedPath)) {
|
|
1539
|
-
const destFile = join4(skillDir, act.entry);
|
|
1540
|
-
mkdirSync3(dirname4(destFile), { recursive: true });
|
|
1541
|
-
copyFileSync(act.resolvedPath, destFile);
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
for (const dep of plan.dependencies.modulesAndAssets) {
|
|
1545
|
-
if ((dep.type === "asset" || dep.type === "module") && existsSync4(dep.resolvedPath)) {
|
|
1546
|
-
const destAsset = join4(skillDir, dep.path);
|
|
1547
|
-
mkdirSync3(dirname4(destAsset), { recursive: true });
|
|
1548
|
-
copyFileSync(dep.resolvedPath, destAsset);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
for (const pb of plan.playbooks) {
|
|
1552
|
-
if (existsSync4(pb.filePath)) {
|
|
1553
|
-
const destPb = join4(playbooksDestDir, basename4(pb.filePath));
|
|
1554
|
-
copyFileSync(pb.filePath, destPb);
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
} else {
|
|
1558
|
-
const binDir = join4(skillDir, "bin");
|
|
1559
|
-
mkdirSync3(binDir, { recursive: true });
|
|
1560
|
-
const binaryName = pkgSlug;
|
|
1561
|
-
const binaryPath = join4(binDir, binaryName);
|
|
1562
|
-
const buildDir = join4(root, ".actiondock", ".build");
|
|
1563
|
-
mkdirSync3(buildDir, { recursive: true });
|
|
1564
|
-
const entryCode = generateStandaloneEntrypoint2(plan.packageId, plan.version, plan.description, plan.actions.map((a) => ({ id: a.id, filePath: a.resolvedPath })), plan.configDefs);
|
|
1565
|
-
const entryFileName = `entry-${Date.now()}-${Math.random().toString(36).slice(2, 8)}.ts`;
|
|
1566
|
-
const entryPath = join4(buildDir, entryFileName);
|
|
1567
|
-
writeFileSync4(entryPath, entryCode, "utf-8");
|
|
1568
|
-
let compileRes;
|
|
1569
|
-
try {
|
|
1570
|
-
compileRes = await BunCompiler.compile({
|
|
1571
|
-
entrypoint: entryPath,
|
|
1572
|
-
outfile: binaryPath,
|
|
1573
|
-
target: options.target,
|
|
1574
|
-
minify: options.minify,
|
|
1575
|
-
bytecode: options.bytecode,
|
|
1576
|
-
cwd: root,
|
|
1577
|
-
packageId: plan.packageId,
|
|
1578
|
-
version: plan.version,
|
|
1579
|
-
actions: plan.actions.map((a) => a.id)
|
|
1580
|
-
});
|
|
1581
|
-
} finally {
|
|
1582
|
-
if (existsSync4(entryPath)) {
|
|
1583
|
-
rmSync2(entryPath, { force: true });
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
const actualBinaryName = basename4(compileRes.executablePath);
|
|
1587
|
-
const existingSkill = !options.skipSkillMd ? findExistingSingleSkillMd(root, options.skillMdPath, pkgSlug) : undefined;
|
|
1588
|
-
if (existingSkill) {
|
|
1589
|
-
usedExistingSkillMd = existingSkill;
|
|
1590
|
-
const destSkillMd = join4(skillDir, "SKILL.md");
|
|
1591
|
-
if (resolve4(existingSkill) !== resolve4(destSkillMd)) {
|
|
1592
|
-
copyFileSync(existingSkill, destSkillMd);
|
|
1593
|
-
}
|
|
1594
|
-
} else if (!options.skipSkillMd) {
|
|
1595
|
-
const skillMd = generateStandaloneSkillMd(configForTemplates, plan.actions, plan.playbooks, `./bin/${actualBinaryName}`);
|
|
1596
|
-
writeFileSync4(join4(skillDir, "SKILL.md"), skillMd, "utf-8");
|
|
1597
|
-
}
|
|
1598
|
-
for (const pb of plan.playbooks) {
|
|
1599
|
-
if (existsSync4(pb.filePath)) {
|
|
1600
|
-
const destPb = join4(playbooksDestDir, basename4(pb.filePath));
|
|
1601
|
-
copyFileSync(pb.filePath, destPb);
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
for (const dep of plan.dependencies.modulesAndAssets) {
|
|
1605
|
-
if (dep.type === "asset" && existsSync4(dep.resolvedPath)) {
|
|
1606
|
-
const destAsset = join4(skillDir, dep.path);
|
|
1607
|
-
mkdirSync3(dirname4(destAsset), { recursive: true });
|
|
1608
|
-
copyFileSync(dep.resolvedPath, destAsset);
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
if (existsSync4(targetSkillDir)) {
|
|
1613
|
-
const backupDir = `${targetSkillDir}.old-${Date.now()}`;
|
|
1614
|
-
try {
|
|
1615
|
-
moveDirAtomic(targetSkillDir, backupDir);
|
|
1616
|
-
moveDirAtomic(stagingDir, targetSkillDir);
|
|
1617
|
-
rmSync2(backupDir, { recursive: true, force: true });
|
|
1618
|
-
} catch {
|
|
1619
|
-
rmSync2(targetSkillDir, { recursive: true, force: true });
|
|
1620
|
-
moveDirAtomic(stagingDir, targetSkillDir);
|
|
1621
|
-
}
|
|
1622
|
-
} else {
|
|
1623
|
-
moveDirAtomic(stagingDir, targetSkillDir);
|
|
1624
|
-
}
|
|
1625
|
-
} finally {
|
|
1626
|
-
if (existsSync4(stagingDir)) {
|
|
1627
|
-
rmSync2(stagingDir, { recursive: true, force: true });
|
|
1628
|
-
}
|
|
1629
|
-
}
|
|
1630
|
-
let archivePath;
|
|
1631
|
-
if (options.archive) {
|
|
1632
|
-
let format = "zip";
|
|
1633
|
-
if (options.archiveFormat === "tar.gz" || options.archive === "tar.gz") {
|
|
1634
|
-
format = "tar.gz";
|
|
1635
|
-
} else if (options.archiveFormat === "zip" || options.archive === "zip") {
|
|
1636
|
-
format = "zip";
|
|
1637
|
-
}
|
|
1638
|
-
archivePath = await createArchive(targetSkillDir, format);
|
|
1639
|
-
}
|
|
1640
|
-
const files = scanRelativeFiles(targetSkillDir);
|
|
1641
|
-
return {
|
|
1642
|
-
packageId: plan.packageId,
|
|
1643
|
-
version: plan.version,
|
|
1644
|
-
mode,
|
|
1645
|
-
target,
|
|
1646
|
-
skillDir: targetSkillDir,
|
|
1647
|
-
archivePath,
|
|
1648
|
-
actionsCount: plan.actions.length,
|
|
1649
|
-
playbooksCount: plan.playbooks.length,
|
|
1650
|
-
actions: plan.actions.map((a) => a.id),
|
|
1651
|
-
playbooks: plan.playbooks.map((p) => p.id),
|
|
1652
|
-
files,
|
|
1653
|
-
usedExistingSkillMd
|
|
1654
|
-
};
|
|
1655
|
-
}
|
|
1656
|
-
async exportBatch(options) {
|
|
1657
|
-
if (!options.projectRoots || options.projectRoots.length === 0) {
|
|
1658
|
-
throw new BuilderError("No project roots provided for batch skill export.");
|
|
1659
|
-
}
|
|
1660
|
-
const results = [];
|
|
1661
|
-
const baseOutDir = resolve4(options.outDir || join4(process.cwd(), "dist", "skills"));
|
|
1662
|
-
mkdirSync3(baseOutDir, { recursive: true });
|
|
1663
|
-
const usedDirNames = new Set;
|
|
1664
|
-
for (const projectRoot of options.projectRoots) {
|
|
1665
|
-
const config = loadProjectConfig2(projectRoot);
|
|
1666
|
-
let pkgSlug = getPackageSlug2(config.id);
|
|
1667
|
-
if (usedDirNames.has(pkgSlug)) {
|
|
1668
|
-
pkgSlug = config.id.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-+|-+$/g, "");
|
|
1669
|
-
}
|
|
1670
|
-
usedDirNames.add(pkgSlug);
|
|
1671
|
-
const pkgOutDir = join4(baseOutDir, `${pkgSlug}-skill`);
|
|
1672
|
-
const res = await this.export({
|
|
1673
|
-
...options,
|
|
1674
|
-
projectRoot,
|
|
1675
|
-
outDir: pkgOutDir
|
|
1676
|
-
});
|
|
1677
|
-
results.push(res);
|
|
1678
|
-
}
|
|
1679
|
-
const totalActions = results.reduce((sum, r) => sum + r.actionsCount, 0);
|
|
1680
|
-
const totalPlaybooks = results.reduce((sum, r) => sum + r.playbooksCount, 0);
|
|
1681
|
-
return {
|
|
1682
|
-
results,
|
|
1683
|
-
outDir: baseOutDir,
|
|
1684
|
-
totalActions,
|
|
1685
|
-
totalPlaybooks
|
|
1686
|
-
};
|
|
1687
|
-
}
|
|
1688
|
-
static async exportBatch(options) {
|
|
1689
|
-
const exporter = new SkillExporter;
|
|
1690
|
-
return exporter.exportBatch(options);
|
|
1691
|
-
}
|
|
1692
|
-
async exportComposite(options) {
|
|
1693
|
-
if (!options.projectRoots || options.projectRoots.length === 0) {
|
|
1694
|
-
throw new BuilderError("No project roots provided for composite skill export.");
|
|
1695
|
-
}
|
|
1696
|
-
if (!options.bundleName || !options.bundleName.trim()) {
|
|
1697
|
-
throw new BuilderError("bundleName is required for composite skill export.");
|
|
1698
|
-
}
|
|
1699
|
-
const bundleSlug = getPackageSlug2(options.bundleName);
|
|
1700
|
-
const defaultSkillDir = join4(process.cwd(), "dist", `${bundleSlug}-skill`);
|
|
1701
|
-
const targetSkillDir = resolve4(options.outDir || defaultSkillDir);
|
|
1702
|
-
const parentDir = dirname4(targetSkillDir);
|
|
1703
|
-
mkdirSync3(parentDir, { recursive: true });
|
|
1704
|
-
const stagingDir = join4(parentDir, `.tmp-composite-${bundleSlug}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
|
|
1705
|
-
mkdirSync3(stagingDir, { recursive: true });
|
|
1706
|
-
const packagesDestDir = join4(stagingDir, "packages");
|
|
1707
|
-
mkdirSync3(packagesDestDir, { recursive: true });
|
|
1708
|
-
const packageInfos = [];
|
|
1709
|
-
const packageSummaries = [];
|
|
1710
|
-
try {
|
|
1711
|
-
const usedDirNames = new Set;
|
|
1712
|
-
for (const projectRoot of options.projectRoots) {
|
|
1713
|
-
const config = loadProjectConfig2(projectRoot);
|
|
1714
|
-
let pkgSlug = getPackageSlug2(config.id);
|
|
1715
|
-
if (usedDirNames.has(pkgSlug)) {
|
|
1716
|
-
pkgSlug = config.id.replace(/[^a-zA-Z0-9-_]/g, "-").replace(/^-+|-+$/g, "");
|
|
1717
|
-
}
|
|
1718
|
-
usedDirNames.add(pkgSlug);
|
|
1719
|
-
const destPkgDir = join4(packagesDestDir, pkgSlug);
|
|
1720
|
-
const singleExport = await this.export({
|
|
1721
|
-
projectRoot,
|
|
1722
|
-
outDir: destPkgDir,
|
|
1723
|
-
archive: false,
|
|
1724
|
-
skipSkillMd: true
|
|
1725
|
-
});
|
|
1726
|
-
const manifest = loadManifest2(destPkgDir) || loadManifest2(projectRoot);
|
|
1727
|
-
const playbooks = loadPlaybooks(projectRoot, config.playbooksDir);
|
|
1728
|
-
const actionEntries = singleExport.actions.map((actId) => ({
|
|
1729
|
-
id: actId,
|
|
1730
|
-
description: manifest?.actions?.[actId]?.description
|
|
1731
|
-
}));
|
|
1732
|
-
packageInfos.push({
|
|
1733
|
-
config,
|
|
1734
|
-
actions: actionEntries,
|
|
1735
|
-
playbooks: Array.from(playbooks.values()),
|
|
1736
|
-
packageDir: pkgSlug
|
|
1737
|
-
});
|
|
1738
|
-
packageSummaries.push({
|
|
1739
|
-
packageId: config.id,
|
|
1740
|
-
actions: singleExport.actions,
|
|
1741
|
-
playbooks: singleExport.playbooks
|
|
1742
|
-
});
|
|
1743
|
-
}
|
|
1744
|
-
const existingSkillPath = findExistingCompositeSkillMd(options);
|
|
1745
|
-
if (existingSkillPath) {
|
|
1746
|
-
const destSkillMdPath = join4(stagingDir, "SKILL.md");
|
|
1747
|
-
if (resolve4(existingSkillPath) !== resolve4(destSkillMdPath)) {
|
|
1748
|
-
copyFileSync(existingSkillPath, destSkillMdPath);
|
|
1749
|
-
}
|
|
1750
|
-
} else {
|
|
1751
|
-
const description = options.description || `ActionDock 复合技能套件,聚合 ${packageInfos.map((p) => p.config.name).join("、")}`;
|
|
1752
|
-
const compositeSkillMd = generateCompositeSkillMd(options.bundleName, description, packageInfos);
|
|
1753
|
-
writeFileSync4(join4(stagingDir, "SKILL.md"), compositeSkillMd, "utf-8");
|
|
1754
|
-
}
|
|
1755
|
-
const aggregatedDeps = {
|
|
1756
|
-
"@actiondock/sdk": getInternalDependencyVersion()
|
|
1757
|
-
};
|
|
1758
|
-
for (const info of packageInfos) {
|
|
1759
|
-
const pkgJsonPath = join4(packagesDestDir, info.packageDir, "package.json");
|
|
1760
|
-
if (existsSync4(pkgJsonPath)) {
|
|
1761
|
-
try {
|
|
1762
|
-
const raw = readFileSync3(pkgJsonPath, "utf-8");
|
|
1763
|
-
const parsed = JSON.parse(raw);
|
|
1764
|
-
if (parsed.dependencies && typeof parsed.dependencies === "object") {
|
|
1765
|
-
for (const [dep, ver] of Object.entries(parsed.dependencies)) {
|
|
1766
|
-
const verStr = String(ver);
|
|
1767
|
-
if (verStr.startsWith("file:")) {
|
|
1768
|
-
throw new BuilderError(`Unsupported file: dependency for '${dep}'. Runtime dependencies must not use file: protocol in exported skill packages.`);
|
|
1769
|
-
}
|
|
1770
|
-
if (dep.startsWith("@actiondock/")) {
|
|
1771
|
-
aggregatedDeps[dep] = getInternalDependencyVersion();
|
|
1772
|
-
} else if (verStr.startsWith("workspace:")) {
|
|
1773
|
-
aggregatedDeps[dep] = resolveWorkspaceDepVersion(join4(packagesDestDir, info.packageDir), dep, verStr);
|
|
1774
|
-
} else {
|
|
1775
|
-
aggregatedDeps[dep] = verStr;
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1778
|
-
}
|
|
1779
|
-
} catch (err) {
|
|
1780
|
-
if (err instanceof BuilderError)
|
|
1781
|
-
throw err;
|
|
1782
|
-
}
|
|
1783
|
-
}
|
|
1784
|
-
}
|
|
1785
|
-
writeFileSync4(join4(stagingDir, "package.json"), JSON.stringify({
|
|
1786
|
-
name: bundleSlug,
|
|
1787
|
-
version: "1.0.0",
|
|
1788
|
-
description: options.description || "Composite ActionDock Skill bundle",
|
|
1789
|
-
type: "module",
|
|
1790
|
-
dependencies: aggregatedDeps
|
|
1791
|
-
}, null, 2) + `
|
|
1792
|
-
`, "utf-8");
|
|
1793
|
-
if (existsSync4(targetSkillDir)) {
|
|
1794
|
-
const backupDir = `${targetSkillDir}.old-${Date.now()}`;
|
|
1795
|
-
try {
|
|
1796
|
-
moveDirAtomic(targetSkillDir, backupDir);
|
|
1797
|
-
moveDirAtomic(stagingDir, targetSkillDir);
|
|
1798
|
-
rmSync2(backupDir, { recursive: true, force: true });
|
|
1799
|
-
} catch {
|
|
1800
|
-
rmSync2(targetSkillDir, { recursive: true, force: true });
|
|
1801
|
-
moveDirAtomic(stagingDir, targetSkillDir);
|
|
1802
|
-
}
|
|
1803
|
-
} else {
|
|
1804
|
-
moveDirAtomic(stagingDir, targetSkillDir);
|
|
1805
|
-
}
|
|
1806
|
-
} finally {
|
|
1807
|
-
if (existsSync4(stagingDir)) {
|
|
1808
|
-
rmSync2(stagingDir, { recursive: true, force: true });
|
|
1809
|
-
}
|
|
1810
|
-
}
|
|
1811
|
-
let archivePath;
|
|
1812
|
-
if (options.archive) {
|
|
1813
|
-
let format = "zip";
|
|
1814
|
-
if (options.archiveFormat === "tar.gz" || options.archive === "tar.gz") {
|
|
1815
|
-
format = "tar.gz";
|
|
1816
|
-
} else if (options.archiveFormat === "zip" || options.archive === "zip") {
|
|
1817
|
-
format = "zip";
|
|
1818
|
-
}
|
|
1819
|
-
archivePath = await createArchive(targetSkillDir, format);
|
|
1820
|
-
}
|
|
1821
|
-
const files = scanRelativeFiles(targetSkillDir);
|
|
1822
|
-
const totalActions = packageSummaries.reduce((sum, p) => sum + p.actions.length, 0);
|
|
1823
|
-
const totalPlaybooks = packageSummaries.reduce((sum, p) => sum + p.playbooks.length, 0);
|
|
1824
|
-
return {
|
|
1825
|
-
bundleName: options.bundleName,
|
|
1826
|
-
skillDir: targetSkillDir,
|
|
1827
|
-
archivePath,
|
|
1828
|
-
packagesCount: packageInfos.length,
|
|
1829
|
-
actionsCount: totalActions,
|
|
1830
|
-
playbooksCount: totalPlaybooks,
|
|
1831
|
-
packages: packageSummaries,
|
|
1832
|
-
files,
|
|
1833
|
-
usedExistingSkillMd: findExistingCompositeSkillMd(options)
|
|
1834
|
-
};
|
|
1835
|
-
}
|
|
1836
|
-
static async exportComposite(options) {
|
|
1837
|
-
const exporter = new SkillExporter;
|
|
1838
|
-
return exporter.exportComposite(options);
|
|
1839
|
-
}
|
|
1840
|
-
static async export(options) {
|
|
1841
|
-
const exporter = new SkillExporter;
|
|
1842
|
-
return exporter.export(options);
|
|
1843
|
-
}
|
|
1844
|
-
}
|
|
1845
|
-
async function exportSkill(options) {
|
|
1846
|
-
return SkillExporter.export(options);
|
|
1847
|
-
}
|
|
1848
|
-
async function exportSkillBatch(options) {
|
|
1849
|
-
return SkillExporter.exportBatch(options);
|
|
1850
|
-
}
|
|
1851
|
-
async function exportCompositeSkill(options) {
|
|
1852
|
-
return SkillExporter.exportComposite(options);
|
|
1853
|
-
}
|
|
1854
|
-
export {
|
|
1855
|
-
BuildPlanner,
|
|
1856
|
-
BuilderError,
|
|
1857
|
-
BunCompiler,
|
|
1858
|
-
CompilerError,
|
|
1859
|
-
CompilerValidationError,
|
|
1860
|
-
PlannerError,
|
|
1861
|
-
SkillExporter,
|
|
1862
|
-
buildPlan,
|
|
1863
|
-
buildProject,
|
|
1864
|
-
compileBinary,
|
|
1865
|
-
createTarGzArchive,
|
|
1866
|
-
createTarGzArchiveAsync,
|
|
1867
|
-
createZipArchive,
|
|
1868
|
-
createZipArchiveAsync,
|
|
1869
|
-
exportCompositeSkill,
|
|
1870
|
-
exportSkill,
|
|
1871
|
-
exportSkillBatch,
|
|
1872
|
-
findExistingCompositeSkillMd,
|
|
1873
|
-
findExistingSingleSkillMd,
|
|
1874
|
-
getInternalDependencyVersion
|
|
1875
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* @actiondock/builder 公共导出面。
|
|
3
|
+
* 采用显式命名导出替代全量 export *,保证公开 API 边界清晰可控。
|
|
4
|
+
*/
|
|
5
|
+
// 错误类型
|
|
6
|
+
export { BuilderError, PlannerError } from "./errors.js";
|
|
7
|
+
// 共享基础设施(内部复用的通用能力,随包公开便于下游校验)
|
|
8
|
+
export { collectRelativeFiles, getInternalDependencyVersion, moveDirAtomic, replaceDirAtomic, } from "./fs-utils.js";
|
|
9
|
+
// 清单组装与依赖协议校验
|
|
10
|
+
export { assertNoFileProtocolDeps, assertValidManifestActionIds, serializeManifestAction, serializeManifestPlaybooks, serializePlanManifest, } from "./manifest.js";
|
|
11
|
+
// 归档压缩
|
|
12
|
+
export { createTarGzArchive, createTarGzArchiveAsync, createZipArchive, createZipArchiveAsync, writeToStream, } from "./archive.js";
|
|
13
|
+
// 构建规划
|
|
14
|
+
export { BuildPlanner, buildPlan, SelectionPlanner, selectionPlan } from "./planner.js";
|
|
15
|
+
// 相对依赖完整性校验
|
|
16
|
+
export { assertRelativeDependenciesIntegrity, extractRelativeSpecifiers, resolveRelativeModule, } from "./dependency-check.js";
|
|
17
|
+
// 目录型构建与 npm 打包
|
|
18
|
+
export { buildProject } from "./build.js";
|
|
19
|
+
export { packProject } from "./pack.js";
|
|
20
|
+
// Skill 导出
|
|
21
|
+
export { exportCompositeSkill, exportSkill, exportSkillBatch, findExistingCompositeSkillMd, findExistingSingleSkillMd, SkillExporter, } from "./exporter.js";
|