@michaelfromyeg/weft-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,998 @@
1
+ import { existsSync, statSync, mkdirSync, writeFileSync, readFileSync, mkdtempSync, cpSync, rmSync, readdirSync } from 'fs';
2
+ import { join, isAbsolute, resolve, dirname, relative } from 'path';
3
+ import { refOf, kindOf, leafNameOf, fqid, targetsOf, ALL_TARGETS, loadPlugin, loadManifest, Marketplace, validate, Lockfile, stringifyDocument } from '@michaelfromyeg/weft-schema';
4
+ import { parseFrontmatter } from '@michaelfromyeg/weft-adapter-kit';
5
+ import semver from 'semver';
6
+ import { homedir, tmpdir } from 'os';
7
+ import { createHash, generateKeyPairSync, sign, verify } from 'crypto';
8
+ import { execa } from 'execa';
9
+
10
+ // src/api.ts
11
+
12
+ // src/diagnostics.ts
13
+ var Diagnostics = class {
14
+ items = [];
15
+ error(where, message) {
16
+ this.items.push({ severity: "error", where, message });
17
+ }
18
+ warn(where, message) {
19
+ this.items.push({ severity: "warning", where, message });
20
+ }
21
+ info(where, message) {
22
+ this.items.push({ severity: "info", where, message });
23
+ }
24
+ get hasErrors() {
25
+ return this.items.some((d) => d.severity === "error");
26
+ }
27
+ get errors() {
28
+ return this.items.filter((d) => d.severity === "error");
29
+ }
30
+ };
31
+ var CompileError = class extends Error {
32
+ constructor(message, diagnostics) {
33
+ super(message);
34
+ this.diagnostics = diagnostics;
35
+ this.name = "CompileError";
36
+ }
37
+ diagnostics;
38
+ };
39
+
40
+ // src/namespace.ts
41
+ function resolveAliases(components) {
42
+ const byLeaf = /* @__PURE__ */ new Map();
43
+ for (const c of components) {
44
+ const ids = byLeaf.get(c.leaf) ?? [];
45
+ ids.push(c.id);
46
+ byLeaf.set(c.leaf, ids);
47
+ }
48
+ const aliases = {};
49
+ const collisions = [];
50
+ for (const [leaf, ids] of byLeaf) {
51
+ if (ids.length === 1) aliases[leaf] = ids[0];
52
+ else collisions.push({ leaf, ids: [...ids].sort() });
53
+ }
54
+ return { aliases, collisions };
55
+ }
56
+ function validatePlugin(fb, diags) {
57
+ fb.plugin.components.forEach((component, i) => {
58
+ const where = `components[${i}]`;
59
+ const ref = refOf(component);
60
+ const abs = join(fb.root, ref);
61
+ if (!existsSync(abs)) {
62
+ diags.error(where, `referenced path "${ref}" does not exist`);
63
+ return;
64
+ }
65
+ switch (kindOf(component)) {
66
+ case "skill":
67
+ validateSkill(fb, ref, abs, where, diags);
68
+ break;
69
+ case "mcp":
70
+ validateMcp(fb, ref, abs, where, diags);
71
+ break;
72
+ case "agent":
73
+ validateMarkdownComponent(fb, ref, abs, where, diags);
74
+ break;
75
+ }
76
+ });
77
+ }
78
+ function validateSkill(fb, ref, abs, where, diags) {
79
+ if (!statSync(abs).isDirectory()) {
80
+ diags.error(where, `skill "${ref}" must be a directory containing SKILL.md`);
81
+ return;
82
+ }
83
+ const skillMd = join(abs, "SKILL.md");
84
+ if (!existsSync(skillMd)) {
85
+ diags.error(where, `skill "${ref}" is missing SKILL.md`);
86
+ return;
87
+ }
88
+ const { data } = parseFrontmatter(fb.read(join(ref, "SKILL.md")).toString("utf8"));
89
+ if (!data.name) diags.error(`${where}.skill`, "SKILL.md frontmatter is missing `name`");
90
+ checkDescription(`${where}.skill`, data.description, diags);
91
+ }
92
+ function validateMcp(fb, ref, abs, where, diags) {
93
+ if (!statSync(abs).isDirectory()) {
94
+ diags.error(where, `mcp "${ref}" must be a directory containing server.json`);
95
+ return;
96
+ }
97
+ const serverJsonPath = join(abs, "server.json");
98
+ if (!existsSync(serverJsonPath)) {
99
+ diags.error(where, `mcp "${ref}" is missing server.json`);
100
+ return;
101
+ }
102
+ let server;
103
+ try {
104
+ server = JSON.parse(fb.read(join(ref, "server.json")).toString("utf8"));
105
+ } catch (err) {
106
+ diags.error(`${where}.mcp`, `server.json is not valid JSON: ${err.message}`);
107
+ return;
108
+ }
109
+ if (!server.name) diags.error(`${where}.mcp`, "server.json is missing `name`");
110
+ const hasRunnable = server.packages || server.remotes || server.command;
111
+ if (!hasRunnable) {
112
+ diags.warn(`${where}.mcp`, "server.json declares no `packages`, `remotes`, or `command`");
113
+ }
114
+ checkDescription(`${where}.mcp`, server.description, diags);
115
+ }
116
+ function validateMarkdownComponent(fb, ref, abs, where, diags) {
117
+ if (statSync(abs).isDirectory()) return;
118
+ const { data } = parseFrontmatter(fb.read(ref).toString("utf8"));
119
+ if (!data.name) diags.warn(`${where}.agent`, "agent frontmatter is missing `name`");
120
+ checkDescription(`${where}.agent`, data.description, diags);
121
+ }
122
+ function checkDescription(where, description, diags) {
123
+ if (!description) {
124
+ diags.error(where, "missing `description` (required for discovery + the tested badge)");
125
+ return;
126
+ }
127
+ if (String(description).trim().length < 16) {
128
+ diags.warn(where, "description is very short; harnesses route on it -- make it specific");
129
+ }
130
+ }
131
+ var WEFT_VERSION = "1.0.0";
132
+ function checkMinVersion(min) {
133
+ if (!min) return null;
134
+ const cleaned = semver.valid(semver.coerce(min) ?? min);
135
+ if (!cleaned) return `weft_min_version "${min}" is not a valid semver`;
136
+ if (semver.lt(WEFT_VERSION, cleaned)) {
137
+ return `plugin requires Weft >= ${min}, but this is ${WEFT_VERSION}`;
138
+ }
139
+ return null;
140
+ }
141
+
142
+ // src/compile.ts
143
+ function synthMarketplace(plugin) {
144
+ return {
145
+ name: plugin.name,
146
+ owner: plugin.owner,
147
+ ...plugin.description ? { description: plugin.description } : {},
148
+ entries: [
149
+ {
150
+ name: plugin.name,
151
+ source: `./plugins/${plugin.name}`,
152
+ ...plugin.description ? { description: plugin.description } : {},
153
+ version: plugin.version
154
+ }
155
+ ]
156
+ };
157
+ }
158
+ function staticPass(fb) {
159
+ const diagnostics = new Diagnostics();
160
+ const { plugin } = fb;
161
+ const id = `${plugin.owner.namespace}/${plugin.name}`;
162
+ const minErr = checkMinVersion(plugin.weft_min_version);
163
+ if (minErr) diagnostics.error("weft_min_version", minErr);
164
+ validatePlugin(fb, diagnostics);
165
+ const resolved = plugin.components.map((component) => ({
166
+ id: fqid(plugin.owner.namespace, plugin.name, leafNameOf(component)),
167
+ leaf: leafNameOf(component),
168
+ kind: kindOf(component),
169
+ component
170
+ }));
171
+ const { aliases, collisions } = resolveAliases(resolved);
172
+ for (const c of collisions) {
173
+ diagnostics.error(
174
+ `components.${c.leaf}`,
175
+ `ambiguous component name "${c.leaf}": ${c.ids.join(" vs ")}; qualify or choose which keeps the bare alias`
176
+ );
177
+ }
178
+ return { id, diagnostics, aliases, resolved };
179
+ }
180
+ function compile(fb, opts) {
181
+ const { plugin } = fb;
182
+ const pass = staticPass(fb);
183
+ const { id, diagnostics } = pass;
184
+ const onlySet = opts.only && opts.only.length > 0 ? new Set(opts.only) : null;
185
+ if (onlySet) {
186
+ for (const leaf of onlySet) {
187
+ if (!pass.resolved.some((rc) => rc.leaf === leaf)) {
188
+ diagnostics.error(
189
+ "only",
190
+ `component "${leaf}" not found; available: ${pass.resolved.map((r) => r.leaf).join(", ")}`
191
+ );
192
+ }
193
+ }
194
+ }
195
+ const selected = onlySet ? pass.resolved.filter((rc) => onlySet.has(rc.leaf)) : pass.resolved;
196
+ const effectivePlugin = onlySet ? { ...plugin, components: selected.map((rc) => rc.component) } : plugin;
197
+ const aliases = onlySet ? Object.fromEntries(
198
+ Object.entries(pass.aliases).filter(([, cid]) => selected.some((rc) => rc.id === cid))
199
+ ) : pass.aliases;
200
+ const reverseAlias = new Map(Object.entries(aliases).map(([leaf, cid]) => [cid, leaf]));
201
+ const requested = opts.targets ?? opts.registry.targets;
202
+ const targets = [];
203
+ for (const target of requested) {
204
+ const adapter = opts.registry.get(target);
205
+ if (!adapter) {
206
+ if (opts.targets) diagnostics.warn(target, `no adapter registered for target "${target}"`);
207
+ continue;
208
+ }
209
+ const ctx = {
210
+ plugin: effectivePlugin,
211
+ read: fb.read,
212
+ list: fb.list,
213
+ aliasFor: (componentId) => reverseAlias.get(componentId) ?? componentId
214
+ };
215
+ const pluginArtifacts = [];
216
+ for (const rc of selected) {
217
+ if (!targetsOf(rc.component, ALL_TARGETS).includes(target)) continue;
218
+ for (const a of adapter.transform(rc.component, ctx)) {
219
+ pluginArtifacts.push({ componentId: rc.id, artifact: a });
220
+ }
221
+ }
222
+ for (const a of adapter.emitManifest(effectivePlugin, ctx)) {
223
+ pluginArtifacts.push({ componentId: id, artifact: a });
224
+ }
225
+ targets.push({ target, adapter, artifacts: pluginArtifacts });
226
+ }
227
+ return { fb, id, components: selected, aliases, diagnostics, targets };
228
+ }
229
+ function configVarsOf(component) {
230
+ return "config" in component && component.config ? component.config : [];
231
+ }
232
+ function resolveConfig(plugin, cwd, env = process.env) {
233
+ const resolved = [];
234
+ const values = {};
235
+ for (const component of plugin.components) {
236
+ for (const v of configVarsOf(component)) {
237
+ const fromEnv = env[v.env];
238
+ if (fromEnv !== void 0) {
239
+ values[v.env] = fromEnv;
240
+ resolved.push({ env: v.env, source: "env", secret: v.secret });
241
+ } else if (v.default === void 0) {
242
+ resolved.push({ env: v.env, source: "missing", secret: v.secret });
243
+ } else {
244
+ values[v.env] = v.default;
245
+ resolved.push({ env: v.env, source: "default", secret: v.secret });
246
+ }
247
+ }
248
+ }
249
+ if (Object.keys(values).length === 0) return { resolved, path: null };
250
+ const dir = join(cwd, ".weft");
251
+ mkdirSync(dir, { recursive: true });
252
+ writeFileSync(join(dir, ".gitignore"), "*\n");
253
+ const path = join(dir, "secrets.local.json");
254
+ writeFileSync(path, `${JSON.stringify(values, null, 2)}
255
+ `);
256
+ return { resolved, path };
257
+ }
258
+ function walkFiles(root, dir) {
259
+ if (!(existsSync(dir) && statSync(dir).isDirectory())) return [];
260
+ const out = [];
261
+ for (const entry of readdirSync(dir).sort()) {
262
+ const abs = join(dir, entry);
263
+ if (statSync(abs).isDirectory()) out.push(...walkFiles(root, abs));
264
+ else out.push(relative(root, abs));
265
+ }
266
+ return out;
267
+ }
268
+ var MANIFEST_NAMES = ["weft.yaml", "weft.yml", "weft.json5", "weft.json"];
269
+ function fileAccessors(root) {
270
+ return {
271
+ read: (relPath) => readFileSync(join(root, relPath)),
272
+ list: (relDir) => walkFiles(root, join(root, relDir))
273
+ };
274
+ }
275
+ function loadPluginDir(dir) {
276
+ const manifestPath = MANIFEST_NAMES.map((n) => join(dir, n)).find((p) => existsSync(p));
277
+ if (!manifestPath) {
278
+ return {
279
+ ok: false,
280
+ issues: [
281
+ { path: dir, message: `no plugin manifest found (one of: ${MANIFEST_NAMES.join(", ")})` }
282
+ ]
283
+ };
284
+ }
285
+ const text = readFileSync(manifestPath, "utf8");
286
+ const parsed = loadPlugin(text, { filename: manifestPath });
287
+ if (!parsed.ok) return parsed;
288
+ return {
289
+ ok: true,
290
+ value: { plugin: parsed.value, root: dir, manifestPath, ...fileAccessors(dir) }
291
+ };
292
+ }
293
+ var MARKETPLACE_NAMES = [
294
+ "marketplace.yaml",
295
+ "marketplace.yml",
296
+ "marketplace.json5",
297
+ "marketplace.json"
298
+ ];
299
+ function hasMarketplaceManifest(dir) {
300
+ return MARKETPLACE_NAMES.some((n) => existsSync(join(dir, n)));
301
+ }
302
+ function loadMarketplaceDir(dir) {
303
+ const manifestPath = MARKETPLACE_NAMES.map((n) => join(dir, n)).find((p) => existsSync(p));
304
+ if (!manifestPath) {
305
+ return {
306
+ ok: false,
307
+ issues: [
308
+ {
309
+ path: dir,
310
+ message: `no marketplace manifest found (one of: ${MARKETPLACE_NAMES.join(", ")})`
311
+ }
312
+ ]
313
+ };
314
+ }
315
+ const text = readFileSync(manifestPath, "utf8");
316
+ const parsed = loadManifest(Marketplace, text, { filename: manifestPath });
317
+ if (!parsed.ok) return parsed;
318
+ return { ok: true, value: { marketplace: parsed.value, root: dir, manifestPath } };
319
+ }
320
+ var CACHE_DIR = join(homedir(), ".weft", "cache");
321
+ function splitRefAndSubdir(s) {
322
+ const hash = s.indexOf("#");
323
+ const ref = hash >= 0 ? s.slice(hash + 1) : void 0;
324
+ const head = hash >= 0 ? s.slice(0, hash) : s;
325
+ const proto = head.match(/^[a-z+]+:\/\//i)?.[0] ?? "";
326
+ const rest = head.slice(proto.length);
327
+ const slash = rest.indexOf("//");
328
+ if (slash === -1) return { base: head, ...ref ? { ref } : {} };
329
+ return {
330
+ base: proto + rest.slice(0, slash),
331
+ subdir: rest.slice(slash + 2),
332
+ ...ref ? { ref } : {}
333
+ };
334
+ }
335
+ function parseSource(src) {
336
+ if (src.startsWith("./") || src.startsWith("../") || isAbsolute(src)) {
337
+ return { kind: "local", path: src };
338
+ }
339
+ if (src.startsWith("npm:")) {
340
+ const raw = src.slice("npm:".length);
341
+ const slash = raw.indexOf("//");
342
+ const subdir2 = slash >= 0 ? raw.slice(slash + 2) : void 0;
343
+ const spec = slash >= 0 ? raw.slice(0, slash) : raw;
344
+ const at = spec.lastIndexOf("@");
345
+ const pkg = at > 0 ? spec.slice(0, at) : spec;
346
+ const version = at > 0 ? spec.slice(at + 1) : void 0;
347
+ return { kind: "npm", pkg, ...version ? { version } : {}, ...subdir2 ? { subdir: subdir2 } : {} };
348
+ }
349
+ if (src.startsWith("github:")) {
350
+ const { base: base2, subdir: subdir2, ref: ref2 } = splitRefAndSubdir(src.slice("github:".length));
351
+ return { kind: "github", repo: base2, ...ref2 ? { ref: ref2 } : {}, ...subdir2 ? { subdir: subdir2 } : {} };
352
+ }
353
+ if (src.startsWith("git@") || src.startsWith("git+") || src.startsWith("file://") || /^https?:\/\//.test(src)) {
354
+ const { base: base2, subdir: subdir2, ref: ref2 } = splitRefAndSubdir(src.replace(/^git\+/, ""));
355
+ return { kind: "git", url: base2, ...ref2 ? { ref: ref2 } : {}, ...subdir2 ? { subdir: subdir2 } : {} };
356
+ }
357
+ const { base, subdir, ref } = splitRefAndSubdir(src);
358
+ if (/^[\w.-]+\/[\w.-]+$/.test(base)) {
359
+ return { kind: "github", repo: base, ...ref ? { ref } : {}, ...subdir ? { subdir } : {} };
360
+ }
361
+ return { kind: "local", path: src };
362
+ }
363
+ function cacheDirFor(key) {
364
+ return join(CACHE_DIR, createHash("sha256").update(key).digest("hex").slice(0, 16));
365
+ }
366
+ var SHA_RE = /^[0-9a-f]{40}$/i;
367
+ async function gitFetch(url, ref) {
368
+ mkdirSync(CACHE_DIR, { recursive: true });
369
+ const dir = cacheDirFor(`${url}@${ref ?? "default"}`);
370
+ const isSha = ref !== void 0 && SHA_RE.test(ref);
371
+ if (existsSync(join(dir, ".git"))) {
372
+ await execa("git", ["fetch", "origin", ...ref ? [ref] : []], { cwd: dir });
373
+ await execa("git", ["checkout", ref ?? "FETCH_HEAD"], { cwd: dir, reject: false });
374
+ } else if (isSha) {
375
+ await execa("git", ["clone", url, dir]);
376
+ await execa("git", ["checkout", ref], { cwd: dir });
377
+ } else {
378
+ const branchArgs = ref ? ["--branch", ref] : [];
379
+ await execa("git", ["clone", "--depth", "1", ...branchArgs, url, dir]);
380
+ }
381
+ const sha = (await execa("git", ["rev-parse", "HEAD"], { cwd: dir })).stdout.trim();
382
+ return { dir, sha };
383
+ }
384
+ async function npmFetch(pkg, version) {
385
+ const spec = version ? `${pkg}@${version}` : pkg;
386
+ const dir = cacheDirFor(`npm:${spec}`);
387
+ const pkgDir = join(dir, "package");
388
+ if (version && existsSync(pkgDir)) return { dir: pkgDir, sha: version };
389
+ mkdirSync(dir, { recursive: true });
390
+ const { stdout } = await execa("npm", ["pack", spec, "--pack-destination", dir, "--json"], {
391
+ cwd: dir
392
+ });
393
+ const meta = JSON.parse(stdout);
394
+ const first = meta[0];
395
+ if (!first) throw new Error(`npm pack produced no tarball for "${spec}"`);
396
+ await execa("tar", ["-xzf", join(dir, first.filename), "-C", dir]);
397
+ return { dir: pkgDir, sha: first.version ?? version ?? "" };
398
+ }
399
+ async function fetchSourceDir(src) {
400
+ if (src.kind === "github" || src.kind === "git") {
401
+ const url = src.kind === "github" ? `https://github.com/${src.repo}.git` : src.url;
402
+ const { dir: dir2, sha: sha2 } = await gitFetch(url, src.ref);
403
+ return { dir: src.subdir ? join(dir2, src.subdir) : dir2, ref: src.ref ?? "default", sha: sha2 };
404
+ }
405
+ const { dir, sha } = await npmFetch(src.pkg, src.version);
406
+ return { dir: src.subdir ? join(dir, src.subdir) : dir, ref: src.version ?? "latest", sha };
407
+ }
408
+ async function resolvePluginRefFull(source, fromRoot) {
409
+ const src = parseSource(source);
410
+ if (src.kind === "local") {
411
+ const dir2 = isAbsolute(src.path) ? src.path : resolve(fromRoot, src.path);
412
+ return { fb: loadOrThrow(dir2, source), ref: "local", sha: "" };
413
+ }
414
+ const { dir, ref, sha } = await fetchSourceDir(src);
415
+ return { fb: loadOrThrow(dir, source), ref, sha };
416
+ }
417
+ async function resolveSourceDir(source, fromRoot) {
418
+ const localGuess = isAbsolute(source) ? source : resolve(fromRoot, source);
419
+ if (existsSync(localGuess)) return { dir: localGuess, ref: "local", sha: "" };
420
+ const src = parseSource(source);
421
+ if (src.kind === "local") return { dir: localGuess, ref: "local", sha: "" };
422
+ return await fetchSourceDir(src);
423
+ }
424
+ async function resolvePluginRef(source, fromRoot) {
425
+ return (await resolvePluginRefFull(source, fromRoot)).fb;
426
+ }
427
+ function loadOrThrow(dir, source) {
428
+ const loaded = loadPluginDir(dir);
429
+ if (!loaded.ok) {
430
+ throw new Error(
431
+ `failed to load "${source}":
432
+ ${loaded.issues.map((i) => ` ${i.path}: ${i.message}`).join("\n")}`
433
+ );
434
+ }
435
+ return loaded.value;
436
+ }
437
+ function resolveDependency(dep, fromRoot) {
438
+ return resolvePluginRefFull(dep.plugin, fromRoot);
439
+ }
440
+ async function gitInfo(dir) {
441
+ try {
442
+ const sha = (await execa("git", ["rev-parse", "HEAD"], { cwd: dir })).stdout.trim();
443
+ let ref = "HEAD";
444
+ try {
445
+ ref = (await execa("git", ["describe", "--tags", "--exact-match"], { cwd: dir })).stdout.trim();
446
+ } catch {
447
+ try {
448
+ ref = (await execa("git", ["rev-parse", "--abbrev-ref", "HEAD"], { cwd: dir })).stdout.trim();
449
+ } catch {
450
+ }
451
+ }
452
+ return { ref, sha };
453
+ } catch {
454
+ return { ref: "local", sha: "" };
455
+ }
456
+ }
457
+
458
+ // src/deps.ts
459
+ var KIND_KEY = {
460
+ skill: "skill",
461
+ mcp: "mcp",
462
+ agent: "agent",
463
+ hook: "hook",
464
+ command: "command",
465
+ passthrough: "passthrough"
466
+ };
467
+ function rewriteRef(c, newRef) {
468
+ return { ...c, [KIND_KEY[kindOf(c)]]: newRef };
469
+ }
470
+ var skipGit = (src) => !/\/\.git(\/|$)/.test(src);
471
+ async function resolveDependencies(fb, tmpRoot) {
472
+ const depends = fb.plugin.depends ?? [];
473
+ if (depends.length === 0) return { fb, dependencies: [] };
474
+ const merged = mkdtempSync(join(tmpRoot ?? tmpdir(), "weft-merged-"));
475
+ cpSync(fb.root, merged, { recursive: true, filter: skipGit });
476
+ const components = [...fb.plugin.components];
477
+ const dependencies = [];
478
+ const seen = /* @__PURE__ */ new Set([`${fb.plugin.owner.namespace}/${fb.plugin.name}`]);
479
+ for (const dep of depends) {
480
+ const resolved = await resolveDependency(dep, fb.root);
481
+ const dp = resolved.fb.plugin;
482
+ const depId = `${dp.owner.namespace}/${dp.name}`;
483
+ if (seen.has(depId)) throw new Error(`dependency cycle detected at ${depId}`);
484
+ seen.add(depId);
485
+ const want = dep.components;
486
+ if (want) {
487
+ const missing = want.filter((leaf) => !dp.components.some((c) => leafNameOf(c) === leaf));
488
+ if (missing.length > 0) {
489
+ throw new Error(`dependency ${depId} has no component(s): ${missing.join(", ")}`);
490
+ }
491
+ }
492
+ const selected = dp.components.filter((c) => !want || want.includes(leafNameOf(c)));
493
+ const destBase = join("_deps", dp.name);
494
+ cpSync(resolved.fb.root, join(merged, destBase), { recursive: true, filter: skipGit });
495
+ for (const c of selected) components.push(rewriteRef(c, join(destBase, refOf(c))));
496
+ dependencies.push({ id: depId, range: dep.version ?? "*", resolvedSha: resolved.sha });
497
+ }
498
+ const plugin = { ...fb.plugin, components };
499
+ const mergedFb = {
500
+ plugin,
501
+ root: merged,
502
+ manifestPath: join(merged, "weft.yaml"),
503
+ ...fileAccessors(merged)
504
+ };
505
+ return { fb: mergedFb, dependencies };
506
+ }
507
+ function buildLockEntry(input) {
508
+ const adapters = {};
509
+ for (const t of input.result.targets) {
510
+ adapters[t.target] = { version: t.adapter.version, targetSchema: t.adapter.targetSchema };
511
+ }
512
+ return {
513
+ pluginLock: {
514
+ id: input.result.id,
515
+ version: input.result.fb.plugin.version,
516
+ ref: input.ref,
517
+ sha: input.sha,
518
+ dependencies: input.dependencies ?? [],
519
+ aliases: input.result.aliases
520
+ },
521
+ artifacts: input.artifacts,
522
+ adapters
523
+ };
524
+ }
525
+ function mergeLock(existing, entries, generatedAt) {
526
+ const replacing = new Set(entries.map((e) => e.pluginLock.id));
527
+ const plugins = (existing?.plugins ?? []).filter((p) => !replacing.has(p.id));
528
+ const artifacts = (existing?.artifacts ?? []).filter((a) => !replacing.has(a.plugin));
529
+ const adapters = { ...existing?.adapters ?? {} };
530
+ for (const e of entries) {
531
+ plugins.push(e.pluginLock);
532
+ artifacts.push(...e.artifacts);
533
+ Object.assign(adapters, e.adapters);
534
+ }
535
+ return { weftVersion: WEFT_VERSION, generatedAt, plugins, artifacts, adapters };
536
+ }
537
+ function lockDirForScope(scope, cwd) {
538
+ return scope === "user" ? join(homedir(), ".weft") : cwd;
539
+ }
540
+ function serializeLock(lock) {
541
+ return `${JSON.stringify(lock, null, 2)}
542
+ `;
543
+ }
544
+ function writeLock(dir, lock) {
545
+ const p = join(dir, "weft.lock");
546
+ writeFileSync(p, serializeLock(lock));
547
+ return p;
548
+ }
549
+ function readLock(dir) {
550
+ try {
551
+ const text = readFileSync(join(dir, "weft.lock"), "utf8");
552
+ const res = validate(Lockfile, JSON.parse(text));
553
+ return res.ok ? res.value : null;
554
+ } catch {
555
+ return null;
556
+ }
557
+ }
558
+
559
+ // src/managed.ts
560
+ function checkManagedPolicy(policy, ctx) {
561
+ if (!policy) return null;
562
+ if (policy.allowNamespaces && !policy.allowNamespaces.includes(ctx.namespace)) {
563
+ return `namespace "${ctx.namespace}" is not allowlisted (managed mode allows: ${policy.allowNamespaces.join(", ")})`;
564
+ }
565
+ if (policy.requireBadges && policy.requireBadges.length > 0) {
566
+ const have = new Set(ctx.badges ?? []);
567
+ const missing = policy.requireBadges.filter((b) => !have.has(b));
568
+ if (missing.length > 0) return `plugin is missing required badge(s): ${missing.join(", ")}`;
569
+ }
570
+ return null;
571
+ }
572
+ function sha256(contents) {
573
+ return createHash("sha256").update(contents).digest("hex");
574
+ }
575
+
576
+ // src/place.ts
577
+ function writeArtifact(baseDir, relPath, contents) {
578
+ const abs = join(baseDir, relPath);
579
+ mkdirSync(dirname(abs), { recursive: true });
580
+ const buf = typeof contents === "string" ? Buffer.from(contents, "utf8") : contents;
581
+ writeFileSync(abs, buf);
582
+ return { abs, hash: sha256(buf) };
583
+ }
584
+ function placePluginArtifacts(target, baseDir, pluginName) {
585
+ const pluginPrefix = join("plugins", pluginName);
586
+ return target.artifacts.map(({ artifact }) => {
587
+ const rel = join(pluginPrefix, artifact.relPath);
588
+ const { abs, hash } = writeArtifact(baseDir, rel, artifact.contents);
589
+ return { target: target.target, relPath: rel, abs, hash, kind: artifact.kind };
590
+ });
591
+ }
592
+ function placeCatalog(adapter, marketplace, baseDir) {
593
+ return adapter.emitCatalog(marketplace).map((artifact) => {
594
+ const { abs, hash } = writeArtifact(baseDir, artifact.relPath, artifact.contents);
595
+ return { target: adapter.target, relPath: artifact.relPath, abs, hash, kind: artifact.kind };
596
+ });
597
+ }
598
+ function buildToDir(result, outDir) {
599
+ const written = [];
600
+ const { plugin } = result.fb;
601
+ const marketplace = synthMarketplace(plugin);
602
+ for (const t of result.targets) {
603
+ const base = join(outDir, t.target);
604
+ written.push(...placePluginArtifacts(t, base, plugin.name));
605
+ written.push(...placeCatalog(t.adapter, marketplace, base));
606
+ }
607
+ return written;
608
+ }
609
+ function toBuffer(contents) {
610
+ return typeof contents === "string" ? Buffer.from(contents, "utf8") : contents;
611
+ }
612
+ function planScopeArtifacts(result, scope, cwd) {
613
+ const planned = [];
614
+ const name = result.fb.plugin.name;
615
+ for (const t of result.targets) {
616
+ const paths = t.adapter.detect(scope, cwd);
617
+ const pluginDir = join(paths.plugins, name);
618
+ for (const { componentId, artifact } of t.artifacts) {
619
+ const contents = toBuffer(artifact.contents);
620
+ planned.push({
621
+ contents,
622
+ record: {
623
+ plugin: result.id,
624
+ component: componentId,
625
+ target: t.target,
626
+ scope,
627
+ path: join(pluginDir, artifact.relPath),
628
+ hash: sha256(contents),
629
+ placement: "copy",
630
+ enabled: artifact.executable !== true
631
+ }
632
+ });
633
+ }
634
+ }
635
+ return planned;
636
+ }
637
+ function installToScope(result, scope, cwd) {
638
+ return planScopeArtifacts(result, scope, cwd).map(({ record, contents }) => {
639
+ mkdirSync(dirname(record.path), { recursive: true });
640
+ writeFileSync(record.path, contents);
641
+ return record;
642
+ });
643
+ }
644
+
645
+ // src/api.ts
646
+ function loadResolved(pluginDir) {
647
+ return resolveDependencies(loadOrThrow2(pluginDir));
648
+ }
649
+ function issuesToDiagnostics(issues) {
650
+ return issues.map((i) => ({ severity: "error", where: i.path, message: i.message }));
651
+ }
652
+ function loadOrThrow2(pluginDir) {
653
+ const loaded = loadPluginDir(pluginDir);
654
+ if (!loaded.ok) {
655
+ throw new CompileError(
656
+ `failed to load plugin in ${pluginDir}`,
657
+ issuesToDiagnostics(loaded.issues)
658
+ );
659
+ }
660
+ return loaded.value;
661
+ }
662
+ function lint(pluginDir) {
663
+ const fb = loadOrThrow2(pluginDir);
664
+ const pass = staticPass(fb);
665
+ return { id: pass.id, plugin: fb.plugin, aliases: pass.aliases, diagnostics: pass.diagnostics };
666
+ }
667
+ async function build(opts) {
668
+ const { fb } = await loadResolved(opts.pluginDir);
669
+ const result = compile(fb, { registry: opts.registry, targets: opts.targets });
670
+ if (result.diagnostics.hasErrors) {
671
+ throw new CompileError("compile failed", result.diagnostics.errors);
672
+ }
673
+ const written = buildToDir(result, opts.outDir);
674
+ return { result, written };
675
+ }
676
+ async function buildMarketplace(opts) {
677
+ const loaded = loadMarketplaceDir(opts.marketplaceDir);
678
+ if (!loaded.ok) {
679
+ throw new CompileError(
680
+ `failed to load marketplace in ${opts.marketplaceDir}`,
681
+ issuesToDiagnostics(loaded.issues)
682
+ );
683
+ }
684
+ const { marketplace, root } = loaded.value;
685
+ const compiled = [];
686
+ for (const entry of marketplace.plugins) {
687
+ let fb;
688
+ try {
689
+ fb = (await resolvePluginRefFull(entry.plugin, root)).fb;
690
+ } catch (err) {
691
+ throw new CompileError(`marketplace entry "${entry.plugin}" failed`, [
692
+ { severity: "error", where: "plugins", message: err.message }
693
+ ]);
694
+ }
695
+ if (entry.version) fb = { ...fb, plugin: { ...fb.plugin, version: entry.version } };
696
+ const merged = (await resolveDependencies(fb)).fb;
697
+ const result = compile(merged, { registry: opts.registry, targets: opts.targets });
698
+ if (result.diagnostics.hasErrors) {
699
+ throw new CompileError(`plugin "${result.id}" failed`, result.diagnostics.errors);
700
+ }
701
+ compiled.push({ entry, result });
702
+ }
703
+ const targets = opts.targets ?? opts.registry.targets;
704
+ const written = [];
705
+ for (const target of targets) {
706
+ const adapter = opts.registry.get(target);
707
+ if (!adapter) continue;
708
+ const base = join(opts.outDir, target);
709
+ const entries = [];
710
+ for (const { entry, result } of compiled) {
711
+ const output = result.targets.find((t) => t.target === target);
712
+ if (!output) continue;
713
+ const p = result.fb.plugin;
714
+ written.push(...placePluginArtifacts(output, base, p.name));
715
+ entries.push({
716
+ name: p.name,
717
+ source: `./plugins/${p.name}`,
718
+ ...p.description ? { description: p.description } : {},
719
+ version: entry.version ?? p.version,
720
+ ...entry.category ? { category: entry.category } : {},
721
+ ...entry.tags ? { tags: entry.tags } : {}
722
+ });
723
+ }
724
+ const resolved = {
725
+ name: marketplace.name,
726
+ owner: marketplace.owner,
727
+ ...marketplace.description ? { description: marketplace.description } : {},
728
+ entries
729
+ };
730
+ written.push(...placeCatalog(adapter, resolved, base));
731
+ }
732
+ return { marketplace, plugins: compiled.map((c) => c.result), written };
733
+ }
734
+ function installResolved(fb, dependencies, opts) {
735
+ const result = compile(fb, { registry: opts.registry, targets: opts.targets, only: opts.only });
736
+ if (result.diagnostics.hasErrors) {
737
+ throw new CompileError("compile failed", result.diagnostics.errors);
738
+ }
739
+ const blocked = checkManagedPolicy(opts.managed, {
740
+ namespace: result.fb.plugin.owner.namespace,
741
+ badges: opts.badges
742
+ });
743
+ if (blocked) {
744
+ throw new CompileError("install blocked by managed policy", [
745
+ { severity: "error", where: "managed", message: blocked }
746
+ ]);
747
+ }
748
+ const artifacts = installToScope(result, opts.scope, opts.cwd);
749
+ const secrets = resolveConfig(result.fb.plugin, opts.cwd);
750
+ const entry = buildLockEntry({ result, artifacts, dependencies, ref: opts.ref, sha: opts.sha });
751
+ return { result, entry, secrets };
752
+ }
753
+ function commitLock(scope, cwd, entries, now, lockDirOverride) {
754
+ const dir = lockDirOverride ?? lockDirForScope(scope, cwd);
755
+ mkdirSync(dir, { recursive: true });
756
+ const lockfile = mergeLock(readLock(dir), entries, now ?? (/* @__PURE__ */ new Date()).toISOString());
757
+ return { lockfile, lockPath: writeLock(dir, lockfile) };
758
+ }
759
+ async function install(opts) {
760
+ const { fb, dependencies } = await loadResolved(opts.pluginDir);
761
+ const { ref, sha } = await gitInfo(opts.pluginDir);
762
+ const pi = installResolved(fb, dependencies, {
763
+ scope: opts.scope,
764
+ cwd: opts.cwd,
765
+ registry: opts.registry,
766
+ ref,
767
+ sha,
768
+ targets: opts.targets,
769
+ only: opts.only,
770
+ managed: opts.managed,
771
+ badges: opts.badges
772
+ });
773
+ const { lockfile, lockPath } = commitLock(
774
+ opts.scope,
775
+ opts.cwd,
776
+ [pi.entry],
777
+ opts.now,
778
+ opts.lockDir
779
+ );
780
+ return { ...pi, lockfile, lockPath };
781
+ }
782
+ async function installMarketplace(opts) {
783
+ const loaded = loadMarketplaceDir(opts.marketplaceDir);
784
+ if (!loaded.ok) {
785
+ throw new CompileError(
786
+ `failed to load marketplace in ${opts.marketplaceDir}`,
787
+ issuesToDiagnostics(loaded.issues)
788
+ );
789
+ }
790
+ const { marketplace, root } = loaded.value;
791
+ const installs = [];
792
+ for (const entry of marketplace.plugins) {
793
+ let resolved;
794
+ try {
795
+ resolved = await resolvePluginRefFull(entry.plugin, root);
796
+ } catch (err) {
797
+ throw new CompileError(`marketplace entry "${entry.plugin}" failed`, [
798
+ { severity: "error", where: "plugins", message: err.message }
799
+ ]);
800
+ }
801
+ let fb = resolved.fb;
802
+ if (entry.version) fb = { ...fb, plugin: { ...fb.plugin, version: entry.version } };
803
+ const { fb: merged, dependencies } = await resolveDependencies(fb);
804
+ installs.push(
805
+ installResolved(merged, dependencies, {
806
+ scope: opts.scope,
807
+ cwd: opts.cwd,
808
+ registry: opts.registry,
809
+ ref: resolved.ref,
810
+ sha: resolved.sha,
811
+ targets: opts.targets,
812
+ managed: opts.managed
813
+ })
814
+ );
815
+ }
816
+ const { lockfile, lockPath } = commitLock(
817
+ opts.scope,
818
+ opts.cwd,
819
+ installs.map((i) => i.entry),
820
+ opts.now,
821
+ opts.lockDir
822
+ );
823
+ return { marketplace, lockfile, lockPath, installs };
824
+ }
825
+ function pruneEmptyDirs(paths) {
826
+ const dirs = new Set(paths.map((p) => dirname(p)));
827
+ for (const start of dirs) {
828
+ let d = start;
829
+ while (d && d !== dirname(d)) {
830
+ try {
831
+ if (readdirSync(d).length > 0) break;
832
+ rmSync(d, { recursive: true, force: true });
833
+ } catch {
834
+ break;
835
+ }
836
+ d = dirname(d);
837
+ }
838
+ }
839
+ }
840
+ function uninstall(opts) {
841
+ const lock = readLock(opts.dir);
842
+ if (!lock) {
843
+ throw new CompileError("nothing to uninstall", [
844
+ { severity: "error", where: "weft.lock", message: `no weft.lock found in ${opts.dir}` }
845
+ ]);
846
+ }
847
+ const match = opts.plugin;
848
+ const ids = new Set(
849
+ (match ? lock.plugins.filter((p) => p.id === match || p.id.endsWith(`/${match}`)) : lock.plugins).map((p) => p.id)
850
+ );
851
+ if (match && ids.size === 0) {
852
+ throw new CompileError(`plugin "${match}" is not installed here`, [
853
+ { severity: "error", where: "weft.lock", message: `no plugin matching "${match}"` }
854
+ ]);
855
+ }
856
+ const removed = [];
857
+ for (const a of lock.artifacts) {
858
+ if (!ids.has(a.plugin)) continue;
859
+ if (existsSync(a.path)) {
860
+ rmSync(a.path, { force: true });
861
+ removed.push(a.path);
862
+ }
863
+ }
864
+ pruneEmptyDirs(removed);
865
+ const remaining = lock.plugins.filter((p) => !ids.has(p.id));
866
+ if (remaining.length === 0) {
867
+ rmSync(join(opts.dir, "weft.lock"), { force: true });
868
+ } else {
869
+ writeLock(opts.dir, {
870
+ ...lock,
871
+ plugins: remaining,
872
+ artifacts: lock.artifacts.filter((a) => !ids.has(a.plugin))
873
+ });
874
+ }
875
+ return { removed, plugins: [...ids] };
876
+ }
877
+ async function update(opts) {
878
+ const lockDir = opts.lockDir ?? lockDirForScope(opts.scope, opts.cwd);
879
+ const prev = readLock(lockDir);
880
+ const { fb, dependencies } = await loadResolved(opts.pluginDir);
881
+ const result = compile(fb, { registry: opts.registry, targets: opts.targets, only: opts.only });
882
+ if (result.diagnostics.hasErrors) {
883
+ throw new CompileError("compile failed", result.diagnostics.errors);
884
+ }
885
+ const prevHash = new Map(
886
+ (prev?.artifacts ?? []).filter((a) => a.plugin === result.id).map((a) => [a.path, a.hash])
887
+ );
888
+ const planned = planScopeArtifacts(result, opts.scope, opts.cwd);
889
+ const changed = [];
890
+ for (const { record, contents } of planned) {
891
+ if (prevHash.get(record.path) === record.hash) continue;
892
+ mkdirSync(dirname(record.path), { recursive: true });
893
+ writeFileSync(record.path, contents);
894
+ changed.push(record.path);
895
+ }
896
+ const { ref, sha } = await gitInfo(opts.pluginDir);
897
+ const entry = buildLockEntry({
898
+ result,
899
+ artifacts: planned.map((p) => p.record),
900
+ dependencies,
901
+ ref,
902
+ sha
903
+ });
904
+ mkdirSync(lockDir, { recursive: true });
905
+ const lockfile = mergeLock(prev, [entry], opts.now ?? (/* @__PURE__ */ new Date()).toISOString());
906
+ const lockPath = writeLock(lockDir, lockfile);
907
+ return { id: result.id, lockfile, lockPath, changed };
908
+ }
909
+ function importNativePlugin(opts) {
910
+ if (!opts.adapter.importNative) {
911
+ throw new Error(`adapter "${opts.adapter.target}" does not support import`);
912
+ }
913
+ const result = opts.adapter.importNative(opts.dir, { namespace: opts.namespace });
914
+ if (!result) {
915
+ throw new Error(`no ${opts.adapter.target} plugin or marketplace found in ${opts.dir}`);
916
+ }
917
+ mkdirSync(opts.outDir, { recursive: true });
918
+ if (result.kind === "marketplace") {
919
+ const manifestPath2 = join(opts.outDir, "marketplace.yaml");
920
+ writeFileSync(manifestPath2, stringifyDocument(result.marketplace));
921
+ return {
922
+ kind: "marketplace",
923
+ name: result.marketplace.name,
924
+ outDir: opts.outDir,
925
+ manifestPath: manifestPath2,
926
+ fileCount: 0
927
+ };
928
+ }
929
+ const manifestPath = join(opts.outDir, "weft.yaml");
930
+ writeFileSync(manifestPath, stringifyDocument(result.plugin));
931
+ for (const f of result.files) {
932
+ const abs = join(opts.outDir, f.relPath);
933
+ mkdirSync(dirname(abs), { recursive: true });
934
+ writeFileSync(
935
+ abs,
936
+ typeof f.contents === "string" ? Buffer.from(f.contents, "utf8") : f.contents
937
+ );
938
+ }
939
+ return {
940
+ kind: "plugin",
941
+ name: result.plugin.name,
942
+ outDir: opts.outDir,
943
+ manifestPath,
944
+ fileCount: result.files.length,
945
+ id: `${result.plugin.owner.namespace}/${result.plugin.name}`
946
+ };
947
+ }
948
+
949
+ // src/registry.ts
950
+ var AdapterRegistry = class {
951
+ adapters = /* @__PURE__ */ new Map();
952
+ register(adapter) {
953
+ this.adapters.set(adapter.target, adapter);
954
+ return this;
955
+ }
956
+ get(target) {
957
+ return this.adapters.get(target);
958
+ }
959
+ has(target) {
960
+ return this.adapters.has(target);
961
+ }
962
+ get targets() {
963
+ return [...this.adapters.keys()];
964
+ }
965
+ };
966
+ function artifactDigest(lock) {
967
+ const lines = lock.artifacts.map((a) => `${a.component} ${a.target} ${a.hash}`).sort();
968
+ return createHash("sha256").update(lines.join("\n")).digest();
969
+ }
970
+ function generateSigningKeys() {
971
+ return generateKeyPairSync("ed25519");
972
+ }
973
+ function signLock(lock, privateKey) {
974
+ return sign(null, artifactDigest(lock), privateKey).toString("base64");
975
+ }
976
+ function verifyLockSignature(lock, publicKey, signature) {
977
+ try {
978
+ return verify(null, artifactDigest(lock), publicKey, Buffer.from(signature, "base64"));
979
+ } catch {
980
+ return false;
981
+ }
982
+ }
983
+ function verifyArtifacts(lock, publicKey, signature) {
984
+ const tampered = [];
985
+ for (const a of lock.artifacts) {
986
+ if (!existsSync(a.path)) {
987
+ tampered.push(a.path);
988
+ continue;
989
+ }
990
+ const actual = createHash("sha256").update(readFileSync(a.path)).digest("hex");
991
+ if (actual !== a.hash) tampered.push(a.path);
992
+ }
993
+ return { signatureValid: verifyLockSignature(lock, publicKey, signature), tampered };
994
+ }
995
+
996
+ export { AdapterRegistry, CACHE_DIR, CompileError, Diagnostics, WEFT_VERSION, artifactDigest, build, buildLockEntry, buildMarketplace, buildToDir, checkManagedPolicy, checkMinVersion, compile, fileAccessors, generateSigningKeys, gitInfo, hasMarketplaceManifest, importNativePlugin, install, installMarketplace, installToScope, lint, loadMarketplaceDir, loadPluginDir, lockDirForScope, mergeLock, parseSource, placeCatalog, placePluginArtifacts, planScopeArtifacts, readLock, resolveAliases, resolveConfig, resolveDependencies, resolveDependency, resolvePluginRef, resolvePluginRefFull, resolveSourceDir, serializeLock, sha256, signLock, staticPass, synthMarketplace, uninstall, update, validatePlugin, verifyArtifacts, verifyLockSignature, writeLock };
997
+ //# sourceMappingURL=index.js.map
998
+ //# sourceMappingURL=index.js.map