@marcoscale98/piewf-cli 5.14.1-fork.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/dist/src/bundles.d.ts +63 -0
- package/dist/src/bundles.js +619 -0
- package/dist/src/cli.d.ts +34 -0
- package/dist/src/cli.js +912 -0
- package/dist/src/doctor-cleanup.d.ts +41 -0
- package/dist/src/doctor-cleanup.js +659 -0
- package/dist/src/doctor.d.ts +113 -0
- package/dist/src/doctor.js +668 -0
- package/dist/src/session-inspector.d.ts +82 -0
- package/dist/src/session-inspector.js +454 -0
- package/package.json +52 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import { chmodSync, cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
4
|
+
import { builtinModules } from "node:module";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
7
|
+
import { CORE_PACKAGE_NAME, CORE_PACKAGE_NAMES } from "@marcoscale98/pi-extensible-workflows";
|
|
8
|
+
export const CLI_PACKAGE_NAME = "@marcoscale98/piewf-cli";
|
|
9
|
+
function isJsonObject(value) {
|
|
10
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11
|
+
}
|
|
12
|
+
function readJsonObject(path) {
|
|
13
|
+
try {
|
|
14
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
15
|
+
return isJsonObject(value) ? value : undefined;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function readPackageMetadata(path) {
|
|
22
|
+
const value = readJsonObject(path);
|
|
23
|
+
if (!value)
|
|
24
|
+
return undefined;
|
|
25
|
+
const metadata = {};
|
|
26
|
+
if (typeof value.name === "string")
|
|
27
|
+
metadata.name = value.name;
|
|
28
|
+
if (typeof value.version === "string")
|
|
29
|
+
metadata.version = value.version;
|
|
30
|
+
if (typeof value.bin === "string")
|
|
31
|
+
metadata.bin = value.bin;
|
|
32
|
+
else if (isJsonObject(value.bin)) {
|
|
33
|
+
const bin = {};
|
|
34
|
+
let valid = true;
|
|
35
|
+
for (const [name, entry] of Object.entries(value.bin)) {
|
|
36
|
+
if (typeof entry !== "string") {
|
|
37
|
+
valid = false;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
bin[name] = entry;
|
|
41
|
+
}
|
|
42
|
+
if (valid)
|
|
43
|
+
metadata.bin = bin;
|
|
44
|
+
}
|
|
45
|
+
return metadata;
|
|
46
|
+
}
|
|
47
|
+
function packageJson() {
|
|
48
|
+
const directory = dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
for (const path of [join(directory, "../package.json"), join(directory, "../../package.json")]) {
|
|
50
|
+
const metadata = readPackageMetadata(path);
|
|
51
|
+
if (metadata)
|
|
52
|
+
return metadata;
|
|
53
|
+
}
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
export function portableEngineVersion() {
|
|
57
|
+
const version = packageJson().version;
|
|
58
|
+
return typeof version === "string" && version.trim() ? version.trim() : "unknown";
|
|
59
|
+
}
|
|
60
|
+
export function portablePiVersion() {
|
|
61
|
+
const command = process.platform === "win32" ? "pi.cmd" : "pi";
|
|
62
|
+
const result = spawnSync(command, ["--version"], { encoding: "utf8" });
|
|
63
|
+
if (result.error || result.status !== 0)
|
|
64
|
+
return "unknown";
|
|
65
|
+
return result.stdout.trim().split(/\r?\n/, 1)[0] ?? "unknown";
|
|
66
|
+
}
|
|
67
|
+
function shellLauncher() {
|
|
68
|
+
return "#!/bin/sh\nset -eu\nROOT=$(CDPATH= cd -- \"$(dirname -- \"$0\")\" && pwd)\nexec node \"$ROOT/payload/runner.mjs\" \"$@\"\n";
|
|
69
|
+
}
|
|
70
|
+
function windowsLauncher() {
|
|
71
|
+
return "@echo off\r\nnode \"%~dp0payload\\runner.mjs\" %*\r\n";
|
|
72
|
+
}
|
|
73
|
+
function runnerSource() {
|
|
74
|
+
return [
|
|
75
|
+
"import { accessSync, constants, existsSync, readFileSync, realpathSync, writeFileSync } from 'node:fs';",
|
|
76
|
+
"import { homedir } from 'node:os';",
|
|
77
|
+
"import { delimiter, dirname, join, sep } from 'node:path';",
|
|
78
|
+
"import { createInterface } from 'node:readline/promises';",
|
|
79
|
+
"import { spawnSync } from 'node:child_process';",
|
|
80
|
+
"import { createRequire } from 'node:module';",
|
|
81
|
+
"import { fileURLToPath, pathToFileURL } from 'node:url';",
|
|
82
|
+
"const bundleRoot = dirname(dirname(fileURLToPath(import.meta.url)));",
|
|
83
|
+
"const manifest = JSON.parse(readFileSync(join(bundleRoot, 'manifest.json'), 'utf8'));",
|
|
84
|
+
"function bundleSkillPaths() { return (manifest.payload?.skills ?? []).map((name) => join(bundleRoot, 'payload', 'skills', name)); }",
|
|
85
|
+
"function run(command, args) {",
|
|
86
|
+
" const result = spawnSync(command, args, { encoding: 'utf8' });",
|
|
87
|
+
" if (result.error) throw result.error;",
|
|
88
|
+
" return { status: result.status, stdout: String(result.stdout ?? ''), stderr: String(result.stderr ?? '') };",
|
|
89
|
+
"}",
|
|
90
|
+
"function piCommand() {",
|
|
91
|
+
" const names = process.platform === 'win32' ? ['pi.cmd', 'pi'] : ['pi'];",
|
|
92
|
+
" for (const entry of (process.env.PATH ?? '').split(delimiter).filter(Boolean)) {",
|
|
93
|
+
" for (const name of names) {",
|
|
94
|
+
" const candidate = join(entry, name);",
|
|
95
|
+
" try { accessSync(candidate, constants.X_OK); return candidate; } catch { /* Continue searching PATH. */ }",
|
|
96
|
+
" }",
|
|
97
|
+
" }",
|
|
98
|
+
" throw new Error('Pi was not found on PATH. Install Pi through npm before running this bundle.');",
|
|
99
|
+
"}",
|
|
100
|
+
"function packageRoot(start) {",
|
|
101
|
+
" let current = dirname(realpathSync(start));",
|
|
102
|
+
" while (true) {",
|
|
103
|
+
" const candidates = [current, join(current, '..', '@earendil-works', 'pi-coding-agent'), join(current, '..', 'pi-coding-agent')];",
|
|
104
|
+
" for (const candidate of candidates) { try { const pkg = JSON.parse(readFileSync(join(candidate, 'package.json'), 'utf8')); if (pkg.name === '@earendil-works/pi-coding-agent') return candidate; } catch { /* Continue to the next package candidate. */ } }",
|
|
105
|
+
" const parent = dirname(current);",
|
|
106
|
+
" if (parent === current) return undefined;",
|
|
107
|
+
" current = parent;",
|
|
108
|
+
" }",
|
|
109
|
+
"}",
|
|
110
|
+
"function assertNpmPi(pi) {",
|
|
111
|
+
" const resolved = realpathSync(pi);",
|
|
112
|
+
" if (!resolved.includes(`${sep}node_modules${sep}`) || !packageRoot(pi)) throw new Error('The pi executable is not an npm installation. Install Pi through npm and retry.');",
|
|
113
|
+
"}",
|
|
114
|
+
"function codingAgentIndex(pi) {",
|
|
115
|
+
" const root = packageRoot(pi);",
|
|
116
|
+
" const index = root && join(root, 'dist', 'index.js');",
|
|
117
|
+
" if (!index || !existsSync(index)) throw new Error('The npm Pi installation does not expose its SDK. Reinstall Pi through npm and retry.');",
|
|
118
|
+
" return index;",
|
|
119
|
+
"}",
|
|
120
|
+
"function packageVersion(root) {",
|
|
121
|
+
" try { return JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version; } catch { return undefined; }",
|
|
122
|
+
"}",
|
|
123
|
+
"function versionParts(value) {",
|
|
124
|
+
" const match = /(?:^|[^0-9])(\\d+)\\.(\\d+)\\.(\\d+)(?:$|[^0-9])/.exec(String(value));",
|
|
125
|
+
" return match ? [Number(match[1]), Number(match[2]), Number(match[3])] : undefined;",
|
|
126
|
+
"}",
|
|
127
|
+
"function compare(left, right) { return left[0] - right[0] || left[1] - right[1] || left[2] - right[2]; }",
|
|
128
|
+
"function rangeClauses(range) {",
|
|
129
|
+
" const clauses = String(range).trim().split(/\\s+/).map((token) => /^(>=|<=|>|<|=|~|\\^)?(\\d+)\\.(\\d+)\\.(\\d+)$/.exec(token));",
|
|
130
|
+
" return clauses.every(Boolean) ? clauses.map((match) => ({ operator: match[1] ?? '=', version: [Number(match[2]), Number(match[3]), Number(match[4])] })) : undefined;",
|
|
131
|
+
"}",
|
|
132
|
+
"function satisfies(value, range) {",
|
|
133
|
+
" if (range === 'unknown') return true;",
|
|
134
|
+
" const actual = versionParts(value);",
|
|
135
|
+
" const clauses = rangeClauses(range);",
|
|
136
|
+
" if (!actual || !clauses) return false;",
|
|
137
|
+
" return clauses.every(({ operator, version }) => {",
|
|
138
|
+
" if (operator === '=') return compare(actual, version) === 0;",
|
|
139
|
+
" if (operator === '>') return compare(actual, version) > 0;",
|
|
140
|
+
" if (operator === '>=') return compare(actual, version) >= 0;",
|
|
141
|
+
" if (operator === '<') return compare(actual, version) < 0;",
|
|
142
|
+
" if (operator === '<=') return compare(actual, version) <= 0;",
|
|
143
|
+
" const upper = operator === '~' ? [version[0], version[1] + 1, 0] : version[0] > 0 ? [version[0] + 1, 0, 0] : version[1] > 0 ? [0, version[1] + 1, 0] : [0, 0, version[2] + 1];",
|
|
144
|
+
" return compare(actual, version) >= 0 && compare(actual, upper) < 0;",
|
|
145
|
+
" });",
|
|
146
|
+
"}",
|
|
147
|
+
"function installationVersion(range) { return String(range).match(/\\d+\\.\\d+\\.\\d+/)?.[0] ?? 'unknown'; }",
|
|
148
|
+
"async function engineCandidates(pi) {",
|
|
149
|
+
" const agent = await import(pathToFileURL(codingAgentIndex(pi)).href);",
|
|
150
|
+
" const agentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), '.pi', 'agent');",
|
|
151
|
+
" const settings = agent.SettingsManager.create(process.cwd(), agentDir, { projectTrusted: false });",
|
|
152
|
+
" const manager = new agent.DefaultPackageManager({ cwd: process.cwd(), agentDir, settingsManager: settings });",
|
|
153
|
+
" const configured = manager.listConfiguredPackages();",
|
|
154
|
+
` const roots = configured.filter((entry) => new RegExp('^npm:' + ${JSON.stringify(CLI_PACKAGE_NAME)} + '(?:@|$)').test(entry.source)).map((entry) => entry.installedPath);`,
|
|
155
|
+
` roots.push(join(agentDir, 'npm', 'node_modules', ...${JSON.stringify(CLI_PACKAGE_NAME.split('/'))}));`,
|
|
156
|
+
" return [...new Set(roots.filter((root) => typeof root === 'string' && existsSync(join(root, 'package.json'))))];",
|
|
157
|
+
"}",
|
|
158
|
+
`function engineRange() { return manifest.runtime[${JSON.stringify(CLI_PACKAGE_NAME)}] ?? 'unknown'; }`,
|
|
159
|
+
"async function findEngine(pi) {",
|
|
160
|
+
" for (const root of await engineCandidates(pi)) {",
|
|
161
|
+
" const version = packageVersion(root);",
|
|
162
|
+
" if (typeof version === 'string' && satisfies(version, engineRange())) return { root: realpathSync(root), version };",
|
|
163
|
+
" }",
|
|
164
|
+
" return undefined;",
|
|
165
|
+
"}",
|
|
166
|
+
"async function confirmInstall(pi, expected) {",
|
|
167
|
+
` const spec = \`npm:${CLI_PACKAGE_NAME}@\${installationVersion(expected)}\`;`,
|
|
168
|
+
" if (!(process.stdin.isTTY && process.stderr.isTTY)) throw new Error(`The compatible workflow CLI package is missing. Re-run '${manifest.command} setup --yes' to approve: ${pi} install ${spec}`);",
|
|
169
|
+
" const prompt = createInterface({ input: process.stdin, output: process.stderr });",
|
|
170
|
+
" try { const answer = await prompt.question(`Install ${spec} through Pi now? [y/N] `); return /^y(es)?$/i.test(answer.trim()); } finally { prompt.close(); }",
|
|
171
|
+
"}",
|
|
172
|
+
"async function ensureEngine(pi, allowInstall, approve) {",
|
|
173
|
+
" const expected = engineRange();",
|
|
174
|
+
" let engine = await findEngine(pi);",
|
|
175
|
+
" if (engine) return engine;",
|
|
176
|
+
" if (!allowInstall) throw new Error(`Compatible workflow CLI${expected === 'unknown' ? '' : `@${expected}`} is not installed through Pi. Run '${manifest.command} setup' first; no installation is performed during launch.`);",
|
|
177
|
+
" if (expected === 'unknown') throw new Error('The bundle does not record a compatible workflow CLI version. Re-export the bundle.');",
|
|
178
|
+
" if (!approve && !(await confirmInstall(pi, expected))) throw new Error('Installation was not approved.');",
|
|
179
|
+
` const spec = \`npm:${CLI_PACKAGE_NAME}@\${installationVersion(expected)}\`;`,
|
|
180
|
+
" if (spec.endsWith('@unknown')) throw new Error('The bundle does not record a compatible workflow CLI version. Re-export the bundle.');",
|
|
181
|
+
" const result = run(pi, ['install', spec]);",
|
|
182
|
+
" if (result.status !== 0) throw new Error(`Pi could not install ${spec}: ${result.stderr.trim() || 'installation failed'}`);",
|
|
183
|
+
" engine = await findEngine(pi);",
|
|
184
|
+
" if (!engine) throw new Error(`Pi installed an incompatible workflow CLI version; expected ${expected}.`);",
|
|
185
|
+
" return engine;",
|
|
186
|
+
"}",
|
|
187
|
+
"function readJson(path) { try { return JSON.parse(readFileSync(path, 'utf8')); } catch { return {}; } }",
|
|
188
|
+
"function projectTrusted(agent, agentDir) {",
|
|
189
|
+
" const settings = agent.SettingsManager.create(process.cwd(), agentDir, { projectTrusted: false });",
|
|
190
|
+
" if (!agent.hasTrustRequiringProjectResources(process.cwd())) return true;",
|
|
191
|
+
" const saved = new agent.ProjectTrustStore(agentDir).get(process.cwd());",
|
|
192
|
+
" return saved === true || saved === null && settings.getDefaultProjectTrust() === 'always';",
|
|
193
|
+
"}",
|
|
194
|
+
"function workflowSettings(agent, agentDir) {",
|
|
195
|
+
" const global = readJson(join(agentDir, 'pi-extensible-workflows', 'settings.json'));",
|
|
196
|
+
" const project = projectTrusted(agent, agentDir) ? readJson(join(process.cwd(), '.pi', 'pi-extensible-workflows', 'settings.json')) : {};",
|
|
197
|
+
" return { ...(global.modelAliases ?? {}), ...(project.modelAliases ?? {}) };",
|
|
198
|
+
"}",
|
|
199
|
+
"function concreteModel(value) { return String(value).split(':', 1)[0]; }",
|
|
200
|
+
"function qualifyModel(value, known) {",
|
|
201
|
+
" const concrete = concreteModel(value);",
|
|
202
|
+
" if (known.has(concrete)) return concrete;",
|
|
203
|
+
" const matches = [...known].filter((model) => model.endsWith('/' + concrete));",
|
|
204
|
+
" return matches.length === 1 ? matches[0] : concrete;",
|
|
205
|
+
"}",
|
|
206
|
+
"function resolveAlias(name, targets, settings, known, chain = []) {",
|
|
207
|
+
" const target = targets[name] ?? settings[name] ?? (known.has(name) ? name : undefined);",
|
|
208
|
+
" if (!target) { const matches = [...known].filter((model) => model.endsWith('/' + name)); return matches.length === 1 ? matches[0] : undefined; }",
|
|
209
|
+
" if (chain.includes(name)) throw new Error(`Model alias cycle: ${[...chain, name].join(' -> ')}`);",
|
|
210
|
+
" const concrete = concreteModel(target);",
|
|
211
|
+
" return targets[concrete] || settings[concrete] ? resolveAlias(concrete, targets, settings, known, [...chain, name]) : qualifyModel(concrete, known);",
|
|
212
|
+
"}",
|
|
213
|
+
"async function recipientInventory(pi) {",
|
|
214
|
+
" const agent = await import(pathToFileURL(codingAgentIndex(pi)).href);",
|
|
215
|
+
" const agentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), '.pi', 'agent');",
|
|
216
|
+
" const modelRuntime = await agent.ModelRuntime.create({ authPath: join(agentDir, 'auth.json'), modelsPath: join(agentDir, 'models.json') });",
|
|
217
|
+
" const services = await agent.createAgentSessionServices({ cwd: process.cwd(), agentDir, modelRuntime, resourceLoaderOptions: { additionalSkillPaths: bundleSkillPaths(), noPromptTemplates: true, noThemes: true, noContextFiles: true } });",
|
|
218
|
+
" const knownModels = new Set(services.modelRuntime.getModels().map((model) => `${model.provider}/${model.id}`));",
|
|
219
|
+
" const availableModels = new Set((await services.modelRuntime.getAvailable()).map((model) => `${model.provider}/${model.id}`));",
|
|
220
|
+
" const sdkRoot = packageRoot(pi);",
|
|
221
|
+
" const toolsIndex = sdkRoot && join(sdkRoot, 'dist', 'core', 'tools', 'index.js');",
|
|
222
|
+
" if (!toolsIndex || !existsSync(toolsIndex)) throw new Error('The npm Pi installation does not expose its built-in tool inventory. Reinstall Pi through npm and retry.');",
|
|
223
|
+
" const toolsModule = await import(pathToFileURL(toolsIndex).href);",
|
|
224
|
+
" if (!(toolsModule.allToolNames instanceof Set)) throw new Error('The npm Pi installation does not expose its built-in tool inventory. Reinstall Pi through npm and retry.');",
|
|
225
|
+
" const tools = new Set(toolsModule.allToolNames);",
|
|
226
|
+
" for (const extension of services.resourceLoader.getExtensions().extensions) for (const tool of extension.tools.keys()) tools.add(tool);",
|
|
227
|
+
" return { agent, agentDir, knownModels, availableModels, tools };",
|
|
228
|
+
"}",
|
|
229
|
+
"async function dynamicAliasTargets(api, inventory, settings) {",
|
|
230
|
+
" const targets = {};",
|
|
231
|
+
" const registry = typeof api.loadingRegistry === 'function' ? api.loadingRegistry() : undefined;",
|
|
232
|
+
" const aliases = new Map((registry?.modelAliases?.() ?? []).map((alias) => [alias.name, alias]));",
|
|
233
|
+
" const first = [...inventory.knownModels][0] ?? 'unknown/unknown';",
|
|
234
|
+
" const separator = first.indexOf('/');",
|
|
235
|
+
" const rootModel = { provider: separator < 0 ? '' : first.slice(0, separator), model: separator < 0 ? first : first.slice(separator + 1) };",
|
|
236
|
+
" for (const name of manifest.requirements.aliases) {",
|
|
237
|
+
" if (manifest.aliasTargets?.[name] || settings[name]) continue;",
|
|
238
|
+
" const alias = aliases.get(name);",
|
|
239
|
+
" if (!alias) continue;",
|
|
240
|
+
" const target = await alias.resolve({ cwd: process.cwd(), projectTrusted: projectTrusted(inventory.agent, inventory.agentDir), rootModel, knownModels: new Set(inventory.knownModels), availableModels: new Set(inventory.availableModels), signal: new AbortController().signal });",
|
|
241
|
+
" if (typeof target !== 'string' || !target.trim()) throw new Error(`Model alias resolver returned an invalid target for ${name}.`);",
|
|
242
|
+
" targets[name] = target.trim();",
|
|
243
|
+
" }",
|
|
244
|
+
" return targets;",
|
|
245
|
+
"}",
|
|
246
|
+
"async function checkRequirements(pi, api) {",
|
|
247
|
+
" const inventory = await recipientInventory(pi);",
|
|
248
|
+
" for (const command of manifest.requirements.commands) {",
|
|
249
|
+
" const result = spawnSync(command, ['--version'], { stdio: 'ignore' });",
|
|
250
|
+
" if (result.error || result.status !== 0) throw new Error(`Missing required external command: ${command}`);",
|
|
251
|
+
" }",
|
|
252
|
+
" for (const name of manifest.requirements.environment) if (!process.env[name]) throw new Error(`Missing required environment variable: ${name}`);",
|
|
253
|
+
" for (const tool of manifest.requirements.tools) if (!inventory.tools.has(tool)) throw new Error(`Required Pi tool is unavailable: ${tool}. Enable the tool or install the extension that provides it.`);",
|
|
254
|
+
" const settings = workflowSettings(inventory.agent, inventory.agentDir);",
|
|
255
|
+
" const dynamicTargets = await dynamicAliasTargets(api, inventory, settings);",
|
|
256
|
+
" const targets = { ...dynamicTargets, ...(manifest.aliasTargets ?? {}) };",
|
|
257
|
+
" for (const name of manifest.requirements.aliases) {",
|
|
258
|
+
" const target = resolveAlias(name, targets, settings, inventory.knownModels);",
|
|
259
|
+
" if (!target || !inventory.knownModels.has(target)) throw new Error(`Required model alias is unknown: ${name}${target ? ` (resolved target: ${target})` : ''}. Pi does not recognize this model.`);",
|
|
260
|
+
" if (!inventory.availableModels.has(target)) throw new Error(`Required model alias is unavailable: ${name} -> ${target}. Configure authentication for this model before launching the bundle.`);",
|
|
261
|
+
" }",
|
|
262
|
+
"}",
|
|
263
|
+
"function piVersion(pi) {",
|
|
264
|
+
" const result = run(pi, ['--version']);",
|
|
265
|
+
" return result.status === 0 ? result.stdout.trim().split(/\\r?\\n/, 1)[0] : 'unknown';",
|
|
266
|
+
"}",
|
|
267
|
+
"function assertPiVersion(pi) {",
|
|
268
|
+
" const expected = manifest.runtime.pi;",
|
|
269
|
+
" const actual = piVersion(pi);",
|
|
270
|
+
" if (!satisfies(actual, expected)) throw new Error(`Bundle requires Pi ${expected}; found ${actual}.`);",
|
|
271
|
+
"}",
|
|
272
|
+
"function saveState(pi, engine) {",
|
|
273
|
+
" writeFileSync(join(bundleRoot, 'bundle-state.json'), JSON.stringify({ format: manifest.format, version: manifest.version, pi: piVersion(pi), engine: engine.version, checkedAt: new Date().toISOString() }, null, 2) + '\\n', { mode: 0o600 });",
|
|
274
|
+
"}",
|
|
275
|
+
"function assertSetupState(pi, engine) {",
|
|
276
|
+
" const state = readJson(join(bundleRoot, 'bundle-state.json'));",
|
|
277
|
+
" if (state.format !== manifest.format || state.version !== manifest.version || typeof state.checkedAt !== 'string' || !satisfies(state.pi, manifest.runtime.pi) || !satisfies(state.engine, engineRange())) throw new Error(`Bundle setup is missing or stale. Run '${manifest.command} setup' before launching.`);",
|
|
278
|
+
"}",
|
|
279
|
+
"async function loadPayload(engine) {",
|
|
280
|
+
` const engineIndex = pathToFileURL(createRequire(pathToFileURL(join(engine.root, 'dist', 'src', 'cli.js'))).resolve(${JSON.stringify(CORE_PACKAGE_NAME)})).href;`,
|
|
281
|
+
" const api = await import(engineIndex);",
|
|
282
|
+
" globalThis.__pi_bundle_api = api;",
|
|
283
|
+
" const payload = await import(pathToFileURL(join(bundleRoot, 'payload', 'workflow.mjs')).href + '?bundle=' + String(Date.now()));",
|
|
284
|
+
" await payload.register(api.registerWorkflowExtension);",
|
|
285
|
+
" return { api, payload };",
|
|
286
|
+
"}",
|
|
287
|
+
"async function setup(argv) {",
|
|
288
|
+
" if (argv.some((arg) => arg !== '--yes' && arg !== '--help' && arg !== '-h')) throw new Error('Usage: ' + manifest.command + ' setup [--yes]');",
|
|
289
|
+
" if (argv.includes('--help') || argv.includes('-h')) { console.log('Usage: ' + manifest.command + ' setup [--yes]'); return; }",
|
|
290
|
+
" const approve = argv.includes('--yes');",
|
|
291
|
+
" const pi = piCommand();",
|
|
292
|
+
" assertNpmPi(pi);",
|
|
293
|
+
" assertPiVersion(pi);",
|
|
294
|
+
" const engine = await ensureEngine(pi, true, approve);",
|
|
295
|
+
" const { api } = await loadPayload(engine);",
|
|
296
|
+
" await checkRequirements(pi, api);",
|
|
297
|
+
" saveState(pi, engine);",
|
|
298
|
+
" console.log('Bundle setup complete.');",
|
|
299
|
+
" console.log('Pi: ' + piVersion(pi));",
|
|
300
|
+
` console.log(${JSON.stringify(CLI_PACKAGE_NAME)} + ': ' + engine.version);`,
|
|
301
|
+
"}",
|
|
302
|
+
"async function launch(argv) {",
|
|
303
|
+
" const pi = piCommand();",
|
|
304
|
+
" assertNpmPi(pi);",
|
|
305
|
+
" assertPiVersion(pi);",
|
|
306
|
+
" const engine = await ensureEngine(pi, false, false);",
|
|
307
|
+
" assertSetupState(pi, engine);",
|
|
308
|
+
" const { api } = await loadPayload(engine);",
|
|
309
|
+
" await checkRequirements(pi, api);",
|
|
310
|
+
" const cli = await import(pathToFileURL(join(engine.root, 'dist', 'src', 'cli.js')).href);",
|
|
311
|
+
" const agentDir = process.env.PI_CODING_AGENT_DIR ?? join(homedir(), '.pi', 'agent');",
|
|
312
|
+
" return cli.runCli(['run', manifest.workflow.name, ...argv], { cwd: process.cwd(), agentDir, skillPaths: bundleSkillPaths(), stderr: (text) => process.stderr.write(text) });",
|
|
313
|
+
"}",
|
|
314
|
+
"const argv = process.argv.slice(2);",
|
|
315
|
+
"try {",
|
|
316
|
+
" if (argv[0] === 'setup') await setup(argv.slice(1));",
|
|
317
|
+
" else process.exitCode = await launch(argv);",
|
|
318
|
+
"} catch (error) {",
|
|
319
|
+
" console.error('Bundle error: ' + (error instanceof Error ? error.message : String(error)));",
|
|
320
|
+
" process.exitCode = 1;",
|
|
321
|
+
"}",
|
|
322
|
+
].join("\n") + "\n";
|
|
323
|
+
}
|
|
324
|
+
function bundledWorkflowModule(workflow, source, withRoles, aliasTargets, extensionModules) {
|
|
325
|
+
const aliases = Object.entries(aliasTargets);
|
|
326
|
+
const modules = ["./extension.mjs", ...extensionModules.map((name) => `./extensions/${name}`)];
|
|
327
|
+
return [
|
|
328
|
+
"export async function register(registerWorkflowExtension) {",
|
|
329
|
+
" const captured = [];",
|
|
330
|
+
" const previousCapture = globalThis.__pi_bundle_capture;",
|
|
331
|
+
" globalThis.__pi_bundle_capture = (extension) => { captured.push(extension); };",
|
|
332
|
+
" try {",
|
|
333
|
+
...modules.map((name, index) => ` const extension${String(index)} = await import(${JSON.stringify(name)});`),
|
|
334
|
+
` const factory = extension0[${JSON.stringify(source.export)}];`,
|
|
335
|
+
` if (typeof factory !== "function") throw new Error(${JSON.stringify(`Workflow extension export ${source.export} is not a function`)});`,
|
|
336
|
+
" await factory();",
|
|
337
|
+
...modules.slice(1).map((_name, index) => ` if (typeof extension${String(index + 1)}.default === "function") await extension${String(index + 1)}.default();`),
|
|
338
|
+
" } finally {",
|
|
339
|
+
" if (previousCapture === undefined) delete globalThis.__pi_bundle_capture; else globalThis.__pi_bundle_capture = previousCapture;",
|
|
340
|
+
" }",
|
|
341
|
+
` const extension = captured.find((candidate) => candidate?.functions?.[${JSON.stringify(workflow.name)}]);`,
|
|
342
|
+
` if (!extension) throw new Error("Bundled extension does not register workflow ${workflow.name}");`,
|
|
343
|
+
" for (const candidate of captured) if (candidate !== extension) registerWorkflowExtension(candidate);",
|
|
344
|
+
" registerWorkflowExtension({",
|
|
345
|
+
" ...extension,",
|
|
346
|
+
" source: new URL(\"./extension.mjs\", import.meta.url).href,",
|
|
347
|
+
...(aliases.length ? [` modelAliases: { ...(extension.modelAliases ?? {}), ${aliases.map(([name, target]) => `${JSON.stringify(name)}: { resolve: () => ${JSON.stringify(target)} }`).join(", ")} },`] : []),
|
|
348
|
+
` functions: { [${JSON.stringify(workflow.name)}]: extension.functions[${JSON.stringify(workflow.name)}] },`,
|
|
349
|
+
` roleDirectories: ${withRoles ? "[new URL(\"./roles\", import.meta.url)]" : "[]"},`,
|
|
350
|
+
" });",
|
|
351
|
+
"}",
|
|
352
|
+
"",
|
|
353
|
+
].join("\n");
|
|
354
|
+
}
|
|
355
|
+
const nodeBuiltins = new Set(builtinModules);
|
|
356
|
+
function packageName(specifier) {
|
|
357
|
+
const parts = specifier.split("/");
|
|
358
|
+
return specifier.startsWith("@") ? parts.slice(0, 2).join("/") : parts[0] ?? specifier;
|
|
359
|
+
}
|
|
360
|
+
function isPackageSpecifier(specifier) {
|
|
361
|
+
return !specifier.startsWith(".") && !specifier.startsWith("/") && !specifier.startsWith("#") && !specifier.startsWith("node:") && !specifier.includes(":");
|
|
362
|
+
}
|
|
363
|
+
function isAllowedExternal(specifier) {
|
|
364
|
+
return nodeBuiltins.has(specifier) || CORE_PACKAGE_NAMES.includes(packageName(specifier));
|
|
365
|
+
}
|
|
366
|
+
const packageNamePattern = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
367
|
+
async function loadEsbuild() {
|
|
368
|
+
let modulePath;
|
|
369
|
+
try {
|
|
370
|
+
modulePath = createRequire(join(process.cwd(), "package.json")).resolve("esbuild");
|
|
371
|
+
}
|
|
372
|
+
catch (error) {
|
|
373
|
+
throw new Error("Portable workflow bundling requires optional esbuild. Install esbuild in the project where you run piewf bundle, for example with `npm install --save-dev esbuild`, and retry.", { cause: error });
|
|
374
|
+
}
|
|
375
|
+
try {
|
|
376
|
+
const loaded = await import(pathToFileURL(modulePath).href);
|
|
377
|
+
return loaded;
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
throw new Error("Portable workflow bundling could not load esbuild from the current project. Install it with `npm install --save-dev esbuild` and retry.", { cause: error });
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
function sourceModulePath(source) {
|
|
384
|
+
try {
|
|
385
|
+
const url = new URL(source);
|
|
386
|
+
if (url.protocol !== "file:")
|
|
387
|
+
throw new Error("only file URLs are supported");
|
|
388
|
+
return fileURLToPath(url);
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
throw new Error(`Workflow source must be a file URL: ${source}`, { cause: error });
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
function dependencyNames(dependencies) {
|
|
395
|
+
const rawDependencies = dependencies;
|
|
396
|
+
if (rawDependencies !== undefined && !Array.isArray(rawDependencies))
|
|
397
|
+
throw new Error("Workflow bundle dependencies must be non-empty package names");
|
|
398
|
+
const values = dependencies ?? [];
|
|
399
|
+
if (values.some((dependency) => typeof dependency !== "string" || !dependency.trim()))
|
|
400
|
+
throw new Error("Workflow bundle dependencies must be non-empty package names");
|
|
401
|
+
const names = [...new Set(values.map((dependency) => dependency.trim()))];
|
|
402
|
+
if (names.some((dependency) => !packageNamePattern.test(dependency)))
|
|
403
|
+
throw new Error("Workflow bundle dependencies must be package names");
|
|
404
|
+
return names.sort();
|
|
405
|
+
}
|
|
406
|
+
function isBuildFailure(error) {
|
|
407
|
+
return error instanceof Error && Array.isArray(error.errors);
|
|
408
|
+
}
|
|
409
|
+
function bundleFailure(error) {
|
|
410
|
+
if (!isBuildFailure(error))
|
|
411
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
412
|
+
const dynamicImport = error.errors.find((message) => message.id === "unsupported-dynamic-import");
|
|
413
|
+
if (!dynamicImport)
|
|
414
|
+
return error;
|
|
415
|
+
const file = dynamicImport.location ? resolve(dynamicImport.location.file) : "the workflow extension";
|
|
416
|
+
return new Error(`Unsupported dynamic import in ${file}: dynamic imports must use a string-literal module path, otherwise the module is not bundleable`, { cause: error });
|
|
417
|
+
}
|
|
418
|
+
async function bundleExtension(sourcePath, sourceExport, dependencies) {
|
|
419
|
+
const esbuild = await loadEsbuild();
|
|
420
|
+
const undeclared = new Set();
|
|
421
|
+
const result = await esbuild.build({
|
|
422
|
+
entryPoints: [sourcePath],
|
|
423
|
+
bundle: true,
|
|
424
|
+
format: "esm",
|
|
425
|
+
platform: "node",
|
|
426
|
+
nodePaths: [
|
|
427
|
+
join(process.cwd(), "node_modules"),
|
|
428
|
+
join(dirname(fileURLToPath(import.meta.url)), "../../node_modules"),
|
|
429
|
+
join(dirname(fileURLToPath(import.meta.url)), "../../../node_modules"),
|
|
430
|
+
join(dirname(fileURLToPath(import.meta.url)), "../../../../node_modules"),
|
|
431
|
+
],
|
|
432
|
+
write: false,
|
|
433
|
+
metafile: true,
|
|
434
|
+
logLevel: "silent",
|
|
435
|
+
logOverride: { "unsupported-dynamic-import": "error" },
|
|
436
|
+
plugins: [{
|
|
437
|
+
name: "portable-workflow-dependencies",
|
|
438
|
+
setup(build) {
|
|
439
|
+
build.onResolve({ filter: /.*/ }, (args) => {
|
|
440
|
+
if (!isPackageSpecifier(args.path))
|
|
441
|
+
return undefined;
|
|
442
|
+
const name = packageName(args.path);
|
|
443
|
+
if (isAllowedExternal(args.path))
|
|
444
|
+
return { path: args.path, external: true };
|
|
445
|
+
if (name.startsWith("@earendil-works/"))
|
|
446
|
+
return { errors: [{ text: `Pi packages (@earendil-works/*) cannot be bundled; use the pi-extensible-workflows API instead: ${name}` }] };
|
|
447
|
+
if (dependencies.includes(name))
|
|
448
|
+
return undefined;
|
|
449
|
+
undeclared.add(name);
|
|
450
|
+
return { path: args.path, external: true };
|
|
451
|
+
});
|
|
452
|
+
},
|
|
453
|
+
}],
|
|
454
|
+
}).catch((error) => { throw bundleFailure(error); });
|
|
455
|
+
if (undeclared.size)
|
|
456
|
+
throw new Error(`Undeclared dependencies: ${[...undeclared].sort().join(", ")}`);
|
|
457
|
+
const output = result.outputFiles[0];
|
|
458
|
+
if (!output)
|
|
459
|
+
throw new Error("esbuild produced no workflow extension output");
|
|
460
|
+
const exports = Object.values(result.metafile.outputs).flatMap(({ exports: names }) => names);
|
|
461
|
+
if (!exports.includes(sourceExport))
|
|
462
|
+
throw new Error(`Bundled workflow extension does not export ${sourceExport}`);
|
|
463
|
+
return { source: output.text, esbuild: esbuild.version };
|
|
464
|
+
}
|
|
465
|
+
function roleMarkdown(role) {
|
|
466
|
+
const metadata = ["---"];
|
|
467
|
+
if (role.description !== undefined)
|
|
468
|
+
metadata.push(`description: ${JSON.stringify(role.description)}`);
|
|
469
|
+
if (role.model !== undefined)
|
|
470
|
+
metadata.push(`model: ${JSON.stringify(role.model)}`);
|
|
471
|
+
if (role.thinking !== undefined)
|
|
472
|
+
metadata.push(`thinking: ${JSON.stringify(role.thinking)}`);
|
|
473
|
+
if (role.tools !== undefined)
|
|
474
|
+
metadata.push(`tools: ${JSON.stringify(role.tools)}`);
|
|
475
|
+
if (role.overrideSystemPrompt !== undefined)
|
|
476
|
+
metadata.push(`overrideSystemPrompt: ${String(role.overrideSystemPrompt)}`);
|
|
477
|
+
if (role.contextFiles !== undefined)
|
|
478
|
+
metadata.push(`contextFiles: ${JSON.stringify(role.contextFiles)}`);
|
|
479
|
+
if (role.skills !== undefined)
|
|
480
|
+
metadata.push(`skills: ${JSON.stringify(role.skills)}`);
|
|
481
|
+
if (role.extensions !== undefined)
|
|
482
|
+
metadata.push(`extensions: ${JSON.stringify(role.extensions)}`);
|
|
483
|
+
metadata.push("---");
|
|
484
|
+
return `${metadata.join("\n")}\n${role.prompt ?? ""}\n`;
|
|
485
|
+
}
|
|
486
|
+
function copyResources(root, resources) {
|
|
487
|
+
if (!resources)
|
|
488
|
+
return undefined;
|
|
489
|
+
const payload = {};
|
|
490
|
+
const sensitive = (source) => {
|
|
491
|
+
const name = basename(source).toLowerCase();
|
|
492
|
+
return ["auth.json", "models.json", ".env", ".npmrc"].includes(name) || name.endsWith(".pem") || name.endsWith(".key");
|
|
493
|
+
};
|
|
494
|
+
const copy = (kind, paths) => {
|
|
495
|
+
if (!paths?.length)
|
|
496
|
+
return;
|
|
497
|
+
const names = [];
|
|
498
|
+
for (const source of paths) {
|
|
499
|
+
if (!existsSync(source))
|
|
500
|
+
throw new Error(`Bundle resource does not exist: ${source}`);
|
|
501
|
+
if (sensitive(source))
|
|
502
|
+
throw new Error(`Bundle resource may contain credentials and cannot be selected: ${source}`);
|
|
503
|
+
let name = basename(source);
|
|
504
|
+
if (kind === "dependencies") {
|
|
505
|
+
const packageName = readPackageMetadata(join(source, "package.json"))?.name;
|
|
506
|
+
if (typeof packageName === "string" && packageName.trim())
|
|
507
|
+
name = packageName;
|
|
508
|
+
}
|
|
509
|
+
if (!name || name === "." || name === ".." || name.includes("\\") || name.startsWith("/"))
|
|
510
|
+
throw new Error(`Invalid bundle resource name: ${name}`);
|
|
511
|
+
if (names.includes(name))
|
|
512
|
+
throw new Error(`Duplicate bundle resource name: ${name}`);
|
|
513
|
+
const destination = kind === "dependencies" ? join(root, "payload", "node_modules", ...name.split("/")) : join(root, "payload", kind === "static" ? "resources" : kind, name);
|
|
514
|
+
mkdirSync(dirname(destination), { recursive: true });
|
|
515
|
+
cpSync(source, destination, { recursive: true });
|
|
516
|
+
names.push(name);
|
|
517
|
+
}
|
|
518
|
+
payload[kind] = names;
|
|
519
|
+
};
|
|
520
|
+
copy("extensions", resources.extensions);
|
|
521
|
+
copy("skills", resources.skills);
|
|
522
|
+
copy("static", resources.static);
|
|
523
|
+
copy("dependencies", resources.dependencies);
|
|
524
|
+
return Object.keys(payload).length ? payload : undefined;
|
|
525
|
+
}
|
|
526
|
+
function extensionPackageShim(paths, bundledSource) {
|
|
527
|
+
const importedNames = new Set();
|
|
528
|
+
const sources = paths.map((path) => {
|
|
529
|
+
if (!/\.(?:c|m)?js$/.test(path))
|
|
530
|
+
throw new Error(`Selected extension must be a JavaScript module file: ${path}`);
|
|
531
|
+
return readFileSync(path, "utf8");
|
|
532
|
+
});
|
|
533
|
+
sources.push(bundledSource);
|
|
534
|
+
for (const source of sources) {
|
|
535
|
+
const packagePattern = CORE_PACKAGE_NAMES.map((name) => name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|");
|
|
536
|
+
for (const match of source.matchAll(new RegExp(`import\\s+(?:type\\s+)?\\{([^}]+)\\}\\s+from\\s+["'](?:${packagePattern})["']`, "g"))) {
|
|
537
|
+
for (const part of (match[1] ?? "").split(",")) {
|
|
538
|
+
const imported = part.trim().split(/\s+as\s+/, 1)[0]?.trim();
|
|
539
|
+
if (imported && /^[A-Za-z_$][\w$]*$/.test(imported))
|
|
540
|
+
importedNames.add(imported);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return [...importedNames].map((name) => name === "registerWorkflowExtension" ? "export const registerWorkflowExtension = (extension) => globalThis.__pi_bundle_capture ? globalThis.__pi_bundle_capture(extension) : globalThis.__pi_bundle_api.registerWorkflowExtension(extension);" : `export const ${name} = globalThis.__pi_bundle_api.${name};`).join("\n") + "\n";
|
|
545
|
+
}
|
|
546
|
+
function baseManifest(input, version) {
|
|
547
|
+
const engineVersion = input.engineVersion ?? portableEngineVersion();
|
|
548
|
+
return {
|
|
549
|
+
format: "pi-extensible-workflows-bundle",
|
|
550
|
+
version,
|
|
551
|
+
command: input.command,
|
|
552
|
+
workflow: { name: input.workflow.name, description: input.workflow.description, input: input.workflow.input, output: input.workflow.output },
|
|
553
|
+
runtime: { pi: input.piVersion?.trim() || "unknown", [CLI_PACKAGE_NAME]: engineVersion.trim() || "unknown" },
|
|
554
|
+
requirements: {
|
|
555
|
+
roles: input.requirements?.roles ?? Object.keys(input.roles ?? {}),
|
|
556
|
+
aliases: input.requirements?.aliases ?? [],
|
|
557
|
+
tools: input.requirements?.tools ?? [],
|
|
558
|
+
commands: input.requirements?.commands ?? [],
|
|
559
|
+
environment: input.requirements?.environment ?? [],
|
|
560
|
+
},
|
|
561
|
+
...(input.aliasTargets && Object.keys(input.aliasTargets).length ? { aliasTargets: Object.freeze({ ...input.aliasTargets }) } : {}),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function writeBundleFiles(input, manifest, workflowSource, bundledExtensionSource) {
|
|
565
|
+
const parent = dirname(input.destination);
|
|
566
|
+
mkdirSync(parent, { recursive: true });
|
|
567
|
+
if (existsSync(input.destination) && !input.force)
|
|
568
|
+
throw new Error(`Destination already exists: ${input.destination}; use --force to replace it`);
|
|
569
|
+
const temporary = mkdtempSync(join(parent, ".pi-extensible-workflows-bundle-"));
|
|
570
|
+
try {
|
|
571
|
+
const payload = join(temporary, "payload");
|
|
572
|
+
mkdirSync(payload);
|
|
573
|
+
const roles = input.roles ?? {};
|
|
574
|
+
if (Object.keys(roles).length) {
|
|
575
|
+
const roleDirectory = join(payload, "roles");
|
|
576
|
+
mkdirSync(roleDirectory);
|
|
577
|
+
for (const [name, role] of Object.entries(roles)) {
|
|
578
|
+
if (!name || name === "." || name === ".." || name.includes("/") || name.includes("\\"))
|
|
579
|
+
throw new Error(`Invalid role name for bundle: ${name}`);
|
|
580
|
+
writeFileSync(join(roleDirectory, `${name}.md`), roleMarkdown(role), { encoding: "utf8", mode: 0o600 });
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const copiedPayload = copyResources(temporary, input.resources);
|
|
584
|
+
if (copiedPayload)
|
|
585
|
+
manifest.payload = copiedPayload;
|
|
586
|
+
const extensionPaths = input.resources?.extensions ?? [];
|
|
587
|
+
writeFileSync(join(payload, "extension.mjs"), bundledExtensionSource, { encoding: "utf8", mode: 0o600 });
|
|
588
|
+
const shim = extensionPackageShim(extensionPaths, bundledExtensionSource);
|
|
589
|
+
for (const packageName of CORE_PACKAGE_NAMES) {
|
|
590
|
+
const packageDirectory = join(payload, "node_modules", ...packageName.split("/"));
|
|
591
|
+
mkdirSync(packageDirectory, { recursive: true });
|
|
592
|
+
writeFileSync(join(packageDirectory, "package.json"), '{"type":"module","exports":"./index.mjs"}\n', { encoding: "utf8", mode: 0o600 });
|
|
593
|
+
writeFileSync(join(packageDirectory, "index.mjs"), shim, { encoding: "utf8", mode: 0o600 });
|
|
594
|
+
}
|
|
595
|
+
writeFileSync(join(temporary, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
596
|
+
writeFileSync(join(payload, "workflow.mjs"), workflowSource, { encoding: "utf8", mode: 0o600 });
|
|
597
|
+
writeFileSync(join(payload, "runner.mjs"), runnerSource(), { encoding: "utf8", mode: 0o700 });
|
|
598
|
+
const launcher = join(temporary, input.command);
|
|
599
|
+
writeFileSync(launcher, shellLauncher(), { encoding: "utf8", mode: 0o755 });
|
|
600
|
+
chmodSync(launcher, 0o755);
|
|
601
|
+
writeFileSync(join(temporary, `${input.command}.cmd`), windowsLauncher(), { encoding: "utf8", mode: 0o644 });
|
|
602
|
+
if (input.force)
|
|
603
|
+
rmSync(input.destination, { recursive: true, force: true });
|
|
604
|
+
renameSync(temporary, input.destination);
|
|
605
|
+
}
|
|
606
|
+
finally {
|
|
607
|
+
rmSync(temporary, { recursive: true, force: true });
|
|
608
|
+
}
|
|
609
|
+
return manifest;
|
|
610
|
+
}
|
|
611
|
+
export async function writePortableWorkflowBundle(input) {
|
|
612
|
+
if (typeof input.source.export !== "string" || !input.source.export.trim())
|
|
613
|
+
throw new Error("Workflow source export must be a non-empty name");
|
|
614
|
+
const sourcePath = sourceModulePath(input.source.module);
|
|
615
|
+
const dependencies = dependencyNames(input.dependencies);
|
|
616
|
+
const bundled = await bundleExtension(sourcePath, input.source.export, dependencies);
|
|
617
|
+
const manifest = { ...baseManifest(input, 2), source: Object.freeze({ module: basename(sourcePath), export: input.source.export }), bundler: { esbuild: bundled.esbuild }, dependencies: Object.freeze([...dependencies]) };
|
|
618
|
+
return writeBundleFiles(input, manifest, bundledWorkflowModule(input.workflow, input.source, Object.keys(input.roles ?? {}).length > 0, input.aliasTargets ?? {}, input.resources?.extensions?.map((source) => basename(source)) ?? []), bundled.source);
|
|
619
|
+
}
|