@kb-labs/scaffold-core 2.29.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.d.ts +143 -0
- package/dist/index.js +664 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { EntityDefinition, BlockDefinition, RenderContext, RenderedFile, ManifestPatch, ManifestSnippets } from '@kb-labs/scaffold-contracts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Load an entity definition from a templates directory.
|
|
5
|
+
*
|
|
6
|
+
* @param templatesRoot directory containing `<entity>/entity.yaml` and `<entity>/blocks/*`
|
|
7
|
+
* @param entityId e.g. 'plugin', 'adapter'
|
|
8
|
+
*/
|
|
9
|
+
declare function loadEntity(templatesRoot: string, entityId: string): Promise<EntityDefinition>;
|
|
10
|
+
/**
|
|
11
|
+
* List entity ids available in the templates root (directories with `entity.yaml`).
|
|
12
|
+
*/
|
|
13
|
+
declare function listEntities(templatesRoot: string): Promise<string[]>;
|
|
14
|
+
|
|
15
|
+
interface ResolveResult {
|
|
16
|
+
/** Block ids in topological order (dependencies first). */
|
|
17
|
+
ordered: string[];
|
|
18
|
+
/** Full block definitions in the same order. */
|
|
19
|
+
blocks: BlockDefinition[];
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a user-selected set of block ids into a topologically sorted list,
|
|
23
|
+
* expanding `requires` and rejecting cycles or `conflicts` violations.
|
|
24
|
+
*/
|
|
25
|
+
declare function resolveBlocks(all: BlockDefinition[], selected: string[]): ResolveResult;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Render a single template string through eta using the given context.
|
|
29
|
+
*/
|
|
30
|
+
declare function renderString(template: string, ctx: RenderContext): string;
|
|
31
|
+
declare function renderFilesDir(filesDir: string, ctx: RenderContext): Promise<RenderedFile[]>;
|
|
32
|
+
|
|
33
|
+
declare function deepMerge(target: Record<string, unknown>, patch: Record<string, unknown>): Record<string, unknown>;
|
|
34
|
+
/**
|
|
35
|
+
* Load a manifest.patch.yaml from disk, render it through eta with the
|
|
36
|
+
* render context, parse as YAML, and return the resulting object.
|
|
37
|
+
*/
|
|
38
|
+
declare function loadPatch(path: string, ctx: RenderContext): Promise<ManifestPatch>;
|
|
39
|
+
declare function loadSnippets(path: string, ctx: RenderContext): Promise<ManifestSnippets>;
|
|
40
|
+
declare function mergeSnippets(a: ManifestSnippets, b: ManifestSnippets): ManifestSnippets;
|
|
41
|
+
|
|
42
|
+
interface WriteOptions {
|
|
43
|
+
outRoot: string;
|
|
44
|
+
files: RenderedFile[];
|
|
45
|
+
/** If set, wipe the root first. */
|
|
46
|
+
force?: boolean;
|
|
47
|
+
}
|
|
48
|
+
interface TargetState {
|
|
49
|
+
exists: boolean;
|
|
50
|
+
empty: boolean;
|
|
51
|
+
}
|
|
52
|
+
declare function inspectTarget(outRoot: string): Promise<TargetState>;
|
|
53
|
+
/**
|
|
54
|
+
* Detect intra-batch path collisions. Two blocks writing the same path is a
|
|
55
|
+
* configuration bug, not a merge case.
|
|
56
|
+
*/
|
|
57
|
+
declare function detectCollisions(files: RenderedFile[]): string[];
|
|
58
|
+
/**
|
|
59
|
+
* Write the rendered file set to disk. If `force` is set and the root
|
|
60
|
+
* exists, it is wiped first. Otherwise the caller is responsible for
|
|
61
|
+
* ensuring the root is absent or empty — see `inspectTarget`.
|
|
62
|
+
*/
|
|
63
|
+
declare function writeFiles(opts: WriteOptions): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Produce a human-readable tree of the files that would be written.
|
|
66
|
+
*/
|
|
67
|
+
declare function formatTree(files: RenderedFile[]): string;
|
|
68
|
+
|
|
69
|
+
interface BuildInputs {
|
|
70
|
+
entity: EntityDefinition;
|
|
71
|
+
selectedBlockIds: string[];
|
|
72
|
+
context: RenderContext;
|
|
73
|
+
}
|
|
74
|
+
interface BuildResult {
|
|
75
|
+
blocks: BlockDefinition[];
|
|
76
|
+
files: RenderedFile[];
|
|
77
|
+
manifest: ManifestPatch;
|
|
78
|
+
snippets: ManifestSnippets;
|
|
79
|
+
outRoot: string;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Compose the full render result from a resolved entity + user selections.
|
|
83
|
+
*
|
|
84
|
+
* Steps:
|
|
85
|
+
* 1. Resolve & topo-sort blocks (expanding requires, checking conflicts).
|
|
86
|
+
* 2. Render every block's `files/` directory into in-memory RenderedFile[].
|
|
87
|
+
* 3. Fold manifest patches and snippets in topological order.
|
|
88
|
+
* 4. Compute `outRoot` from `entity.output` (also eta-rendered).
|
|
89
|
+
*
|
|
90
|
+
* No disk writes happen here — call `writeFiles()` afterward.
|
|
91
|
+
*/
|
|
92
|
+
declare function build(inputs: BuildInputs): Promise<BuildResult>;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Emit a TypeScript source file for the plugin V3 manifest.
|
|
96
|
+
*
|
|
97
|
+
* The manifest object is stringified as a JSON literal. A `permissions`
|
|
98
|
+
* expression is assembled from `combinePermissions()` snippets contributed
|
|
99
|
+
* by blocks and spread in alongside. Imports are deduped at the top.
|
|
100
|
+
*/
|
|
101
|
+
declare function emitManifest(manifest: ManifestPatch, snippets: ManifestSnippets): string;
|
|
102
|
+
|
|
103
|
+
type Severity = 'info' | 'warn' | 'error';
|
|
104
|
+
interface Finding {
|
|
105
|
+
severity: Severity;
|
|
106
|
+
package: string;
|
|
107
|
+
message: string;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Scan a single package directory (one that contains `package.json`).
|
|
111
|
+
*/
|
|
112
|
+
declare function scanPackage(pkgDir: string): Promise<Finding[]>;
|
|
113
|
+
interface ScanResult {
|
|
114
|
+
findings: Finding[];
|
|
115
|
+
packagesScanned: number;
|
|
116
|
+
}
|
|
117
|
+
interface ScanOptions {
|
|
118
|
+
/**
|
|
119
|
+
* Workspace root containing `.kb/marketplace.lock`. When provided, each
|
|
120
|
+
* scaffolded plugin found is cross-checked against the lock — missing
|
|
121
|
+
* entries surface as warnings so the user knows to `kb marketplace
|
|
122
|
+
* plugins link` or rerun `kb scaffold`.
|
|
123
|
+
*/
|
|
124
|
+
workspaceRoot?: string;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Scan a root directory containing one or more plugin packages. A plugin
|
|
128
|
+
* package has either:
|
|
129
|
+
* - its own `package.json` at the root, or
|
|
130
|
+
* - a `packages/` directory with child packages.
|
|
131
|
+
*
|
|
132
|
+
* When `opts.workspaceRoot` is given, also cross-checks every discovered
|
|
133
|
+
* scaffolded plugin against `.kb/marketplace.lock`.
|
|
134
|
+
*/
|
|
135
|
+
declare function scanRoot(root: string, opts?: ScanOptions): Promise<ScanResult>;
|
|
136
|
+
|
|
137
|
+
declare function validatePackageName(name: string): string | null;
|
|
138
|
+
declare function validateScope(scope: string): string | null;
|
|
139
|
+
declare function validateSemver(v: string): string | null;
|
|
140
|
+
declare function validateIdentifier(v: string): string | null;
|
|
141
|
+
declare function runValidator(kind: string, value: unknown): string | null;
|
|
142
|
+
|
|
143
|
+
export { type BuildInputs, type BuildResult, type Finding, type ResolveResult, type ScanResult, type Severity, type TargetState, type WriteOptions, build, deepMerge, detectCollisions, emitManifest, formatTree, inspectTarget, listEntities, loadEntity, loadPatch, loadSnippets, mergeSnippets, renderFilesDir, renderString, resolveBlocks, runValidator, scanPackage, scanRoot, validateIdentifier, validatePackageName, validateScope, validateSemver, writeFiles };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
import { readFile, readdir, stat, rm, mkdir, writeFile, chmod } from 'fs/promises';
|
|
2
|
+
import { resolve, join, relative, dirname } from 'path';
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
|
+
import { Eta } from 'eta';
|
|
5
|
+
|
|
6
|
+
// src/loader/load-entity.ts
|
|
7
|
+
async function pathExists(p) {
|
|
8
|
+
try {
|
|
9
|
+
await stat(p);
|
|
10
|
+
return true;
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function loadBlock(blockDir) {
|
|
16
|
+
const blockYamlPath = join(blockDir, "block.yaml");
|
|
17
|
+
const raw = await readFile(blockYamlPath, "utf8");
|
|
18
|
+
const parsed = parse(raw);
|
|
19
|
+
if (!parsed.id) {
|
|
20
|
+
throw new Error(`block.yaml at ${blockYamlPath} is missing "id"`);
|
|
21
|
+
}
|
|
22
|
+
const filesDir = join(blockDir, "files");
|
|
23
|
+
const manifestPatchFile = parsed.manifestPatch ?? "manifest.patch.yaml";
|
|
24
|
+
const snippetsFile = parsed.snippets ?? "manifest.snippets.yaml";
|
|
25
|
+
const manifestPatchPath = join(blockDir, manifestPatchFile);
|
|
26
|
+
const snippetsPath = join(blockDir, snippetsFile);
|
|
27
|
+
const [hasFiles, hasPatch, hasSnippets] = await Promise.all([
|
|
28
|
+
pathExists(filesDir),
|
|
29
|
+
pathExists(manifestPatchPath),
|
|
30
|
+
pathExists(snippetsPath)
|
|
31
|
+
]);
|
|
32
|
+
return {
|
|
33
|
+
id: parsed.id,
|
|
34
|
+
describe: parsed.describe ?? parsed.id,
|
|
35
|
+
requires: parsed.requires,
|
|
36
|
+
conflicts: parsed.conflicts,
|
|
37
|
+
variables: parsed.variables,
|
|
38
|
+
filesDir: hasFiles ? filesDir : blockDir,
|
|
39
|
+
manifestPatch: hasPatch ? manifestPatchPath : void 0,
|
|
40
|
+
snippetsPath: hasSnippets ? snippetsPath : void 0
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async function loadEntity(templatesRoot, entityId) {
|
|
44
|
+
const entityDir = resolve(templatesRoot, entityId);
|
|
45
|
+
const entityYamlPath = join(entityDir, "entity.yaml");
|
|
46
|
+
if (!await pathExists(entityYamlPath)) {
|
|
47
|
+
throw new Error(
|
|
48
|
+
`Entity "${entityId}" not found: ${entityYamlPath} does not exist`
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
const raw = await readFile(entityYamlPath, "utf8");
|
|
52
|
+
const parsed = parse(raw) ?? {};
|
|
53
|
+
const id = parsed.id ?? entityId;
|
|
54
|
+
const blocksDir = join(entityDir, "blocks");
|
|
55
|
+
const blocks = [];
|
|
56
|
+
if (await pathExists(blocksDir)) {
|
|
57
|
+
const entries = await readdir(blocksDir, { withFileTypes: true });
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (!entry.isDirectory()) continue;
|
|
60
|
+
const blockDir = join(blocksDir, entry.name);
|
|
61
|
+
if (!await pathExists(join(blockDir, "block.yaml"))) continue;
|
|
62
|
+
blocks.push(await loadBlock(blockDir));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
id,
|
|
67
|
+
displayName: parsed.displayName ?? id,
|
|
68
|
+
description: parsed.description,
|
|
69
|
+
variables: parsed.variables ?? [],
|
|
70
|
+
blocks,
|
|
71
|
+
defaults: parsed.defaults,
|
|
72
|
+
output: parsed.output,
|
|
73
|
+
manifestTarget: parsed.manifestTarget
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
async function listEntities(templatesRoot) {
|
|
77
|
+
if (!await pathExists(templatesRoot)) return [];
|
|
78
|
+
const entries = await readdir(templatesRoot, { withFileTypes: true });
|
|
79
|
+
const result = [];
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
if (!entry.isDirectory()) continue;
|
|
82
|
+
if (await pathExists(join(templatesRoot, entry.name, "entity.yaml"))) {
|
|
83
|
+
result.push(entry.name);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return result.sort();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// src/resolver/resolve-blocks.ts
|
|
90
|
+
function resolveBlocks(all, selected) {
|
|
91
|
+
const byId = /* @__PURE__ */ new Map();
|
|
92
|
+
for (const b of all) byId.set(b.id, b);
|
|
93
|
+
const missing = selected.filter((id) => !byId.has(id));
|
|
94
|
+
if (missing.length > 0) {
|
|
95
|
+
throw new Error(`Unknown block(s): ${missing.join(", ")}`);
|
|
96
|
+
}
|
|
97
|
+
const expanded = /* @__PURE__ */ new Set();
|
|
98
|
+
const queue = [...selected];
|
|
99
|
+
while (queue.length > 0) {
|
|
100
|
+
const id = queue.shift();
|
|
101
|
+
if (expanded.has(id)) continue;
|
|
102
|
+
expanded.add(id);
|
|
103
|
+
const block = byId.get(id);
|
|
104
|
+
if (!block) continue;
|
|
105
|
+
for (const dep of block.requires ?? []) {
|
|
106
|
+
if (!byId.has(dep)) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Block "${id}" requires unknown block "${dep}"`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
if (!expanded.has(dep)) queue.push(dep);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (const id of expanded) {
|
|
115
|
+
const block = byId.get(id);
|
|
116
|
+
for (const other of block.conflicts ?? []) {
|
|
117
|
+
if (expanded.has(other)) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`Block "${id}" conflicts with "${other}" \u2014 both selected`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const inDegree = /* @__PURE__ */ new Map();
|
|
125
|
+
const edges = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const id of expanded) {
|
|
127
|
+
inDegree.set(id, 0);
|
|
128
|
+
edges.set(id, /* @__PURE__ */ new Set());
|
|
129
|
+
}
|
|
130
|
+
for (const id of expanded) {
|
|
131
|
+
const block = byId.get(id);
|
|
132
|
+
for (const dep of block.requires ?? []) {
|
|
133
|
+
if (!expanded.has(dep)) continue;
|
|
134
|
+
edges.get(dep).add(id);
|
|
135
|
+
inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const ready = [];
|
|
139
|
+
for (const [id, deg] of inDegree) if (deg === 0) ready.push(id);
|
|
140
|
+
ready.sort();
|
|
141
|
+
const ordered = [];
|
|
142
|
+
while (ready.length > 0) {
|
|
143
|
+
const id = ready.shift();
|
|
144
|
+
ordered.push(id);
|
|
145
|
+
for (const next of edges.get(id) ?? []) {
|
|
146
|
+
const d = (inDegree.get(next) ?? 0) - 1;
|
|
147
|
+
inDegree.set(next, d);
|
|
148
|
+
if (d === 0) {
|
|
149
|
+
ready.push(next);
|
|
150
|
+
ready.sort();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
if (ordered.length !== expanded.size) {
|
|
155
|
+
throw new Error("Cycle detected in block requires graph");
|
|
156
|
+
}
|
|
157
|
+
return { ordered, blocks: ordered.map((id) => byId.get(id)) };
|
|
158
|
+
}
|
|
159
|
+
var eta = new Eta({
|
|
160
|
+
autoEscape: false,
|
|
161
|
+
useWith: true,
|
|
162
|
+
tags: ["<%", "%>"]
|
|
163
|
+
});
|
|
164
|
+
var ETA_SUFFIX = ".eta";
|
|
165
|
+
function renderString(template, ctx) {
|
|
166
|
+
return eta.renderString(template, ctx);
|
|
167
|
+
}
|
|
168
|
+
async function walk(dir) {
|
|
169
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
170
|
+
const out = [];
|
|
171
|
+
for (const entry of entries) {
|
|
172
|
+
const p = join(dir, entry.name);
|
|
173
|
+
if (entry.isDirectory()) {
|
|
174
|
+
out.push(...await walk(p));
|
|
175
|
+
} else if (entry.isFile()) {
|
|
176
|
+
out.push(p);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return out;
|
|
180
|
+
}
|
|
181
|
+
function renderPath(relPath, ctx) {
|
|
182
|
+
const substituted = relPath.replace(/__name__/g, ctx.name).replace(/__scope__/g, ctx.scope.replace(/^@/, ""));
|
|
183
|
+
return substituted.endsWith(ETA_SUFFIX) ? substituted.slice(0, -ETA_SUFFIX.length) : substituted;
|
|
184
|
+
}
|
|
185
|
+
var SKIP_MARKER = "<!--@@scaffold:skip@@-->";
|
|
186
|
+
async function renderFilesDir(filesDir, ctx) {
|
|
187
|
+
const absolutes = await walk(filesDir);
|
|
188
|
+
const rendered = [];
|
|
189
|
+
for (const abs of absolutes) {
|
|
190
|
+
const rel = relative(filesDir, abs);
|
|
191
|
+
const renderedRel = renderPath(rel, ctx);
|
|
192
|
+
const raw = await readFile(abs, "utf8");
|
|
193
|
+
const body = abs.endsWith(ETA_SUFFIX) ? renderString(raw, ctx) : raw;
|
|
194
|
+
if (body.trim() === SKIP_MARKER) continue;
|
|
195
|
+
const st = await stat(abs);
|
|
196
|
+
rendered.push({
|
|
197
|
+
path: renderedRel,
|
|
198
|
+
contents: body,
|
|
199
|
+
executable: (st.mode & 73) !== 0
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
return rendered;
|
|
203
|
+
}
|
|
204
|
+
function isPlainObject(v) {
|
|
205
|
+
return typeof v === "object" && v !== null && !Array.isArray(v) && Object.getPrototypeOf(v) === Object.prototype;
|
|
206
|
+
}
|
|
207
|
+
function mergeArrays(a, b) {
|
|
208
|
+
const allObjects = a.every(isPlainObject) && b.every(isPlainObject) && a.length + b.length > 0;
|
|
209
|
+
if (allObjects) {
|
|
210
|
+
const keyOf = (x) => {
|
|
211
|
+
if (typeof x.id === "string") return `id:${x.id}`;
|
|
212
|
+
if (typeof x.name === "string") return `name:${x.name}`;
|
|
213
|
+
return null;
|
|
214
|
+
};
|
|
215
|
+
const allKeyed = a.every((x) => keyOf(x) !== null) && b.every((x) => keyOf(x) !== null);
|
|
216
|
+
if (allKeyed) {
|
|
217
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
218
|
+
for (const item of [...a, ...b]) {
|
|
219
|
+
const k = keyOf(item);
|
|
220
|
+
const prev = byKey.get(k);
|
|
221
|
+
byKey.set(k, prev ? deepMerge(prev, item) : item);
|
|
222
|
+
}
|
|
223
|
+
return [...byKey.values()];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const seen = /* @__PURE__ */ new Set();
|
|
227
|
+
const out = [];
|
|
228
|
+
for (const item of [...a, ...b]) {
|
|
229
|
+
const key = JSON.stringify(item);
|
|
230
|
+
if (seen.has(key)) continue;
|
|
231
|
+
seen.add(key);
|
|
232
|
+
out.push(item);
|
|
233
|
+
}
|
|
234
|
+
return out;
|
|
235
|
+
}
|
|
236
|
+
function deepMerge(target, patch) {
|
|
237
|
+
const out = { ...target };
|
|
238
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
239
|
+
const existing = out[key];
|
|
240
|
+
if (isPlainObject(existing) && isPlainObject(value)) {
|
|
241
|
+
out[key] = deepMerge(existing, value);
|
|
242
|
+
} else if (Array.isArray(existing) && Array.isArray(value)) {
|
|
243
|
+
out[key] = mergeArrays(existing, value);
|
|
244
|
+
} else {
|
|
245
|
+
out[key] = value;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return out;
|
|
249
|
+
}
|
|
250
|
+
async function loadPatch(path, ctx) {
|
|
251
|
+
const raw = await readFile(path, "utf8");
|
|
252
|
+
const rendered = renderString(raw, ctx);
|
|
253
|
+
const parsed = parse(rendered);
|
|
254
|
+
if (parsed == null) return {};
|
|
255
|
+
if (!isPlainObject(parsed)) {
|
|
256
|
+
throw new Error(`Patch at ${path} must be a YAML object`);
|
|
257
|
+
}
|
|
258
|
+
return parsed;
|
|
259
|
+
}
|
|
260
|
+
async function loadSnippets(path, ctx) {
|
|
261
|
+
const raw = await readFile(path, "utf8");
|
|
262
|
+
const rendered = renderString(raw, ctx);
|
|
263
|
+
const parsed = parse(rendered);
|
|
264
|
+
if (parsed == null) return {};
|
|
265
|
+
if (!isPlainObject(parsed)) {
|
|
266
|
+
throw new Error(`Snippets at ${path} must be a YAML object`);
|
|
267
|
+
}
|
|
268
|
+
return parsed;
|
|
269
|
+
}
|
|
270
|
+
function mergeSnippets(a, b) {
|
|
271
|
+
const mergedExtras = { ...a.extras ?? {} };
|
|
272
|
+
for (const [k, v] of Object.entries(b.extras ?? {})) {
|
|
273
|
+
mergedExtras[k] = dedupe([...mergedExtras[k] ?? [], ...v]);
|
|
274
|
+
}
|
|
275
|
+
return {
|
|
276
|
+
imports: dedupe([...a.imports ?? [], ...b.imports ?? []]),
|
|
277
|
+
permissions: dedupe([
|
|
278
|
+
...a.permissions ?? [],
|
|
279
|
+
...b.permissions ?? []
|
|
280
|
+
]),
|
|
281
|
+
extras: Object.keys(mergedExtras).length > 0 ? mergedExtras : void 0
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function dedupe(items) {
|
|
285
|
+
return [...new Set(items)];
|
|
286
|
+
}
|
|
287
|
+
async function inspectTarget(outRoot) {
|
|
288
|
+
try {
|
|
289
|
+
const st = await stat(outRoot);
|
|
290
|
+
if (!st.isDirectory()) return { exists: true, empty: false };
|
|
291
|
+
const entries = await readdir(outRoot);
|
|
292
|
+
return { exists: true, empty: entries.length === 0 };
|
|
293
|
+
} catch {
|
|
294
|
+
return { exists: false, empty: true };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
function detectCollisions(files) {
|
|
298
|
+
const seen = /* @__PURE__ */ new Map();
|
|
299
|
+
for (const f of files) {
|
|
300
|
+
seen.set(f.path, (seen.get(f.path) ?? 0) + 1);
|
|
301
|
+
}
|
|
302
|
+
return [...seen.entries()].filter(([, n]) => n > 1).map(([p]) => p);
|
|
303
|
+
}
|
|
304
|
+
async function writeFiles(opts) {
|
|
305
|
+
const { outRoot, files, force } = opts;
|
|
306
|
+
const abs = resolve(outRoot);
|
|
307
|
+
const collisions = detectCollisions(files);
|
|
308
|
+
if (collisions.length > 0) {
|
|
309
|
+
throw new Error(
|
|
310
|
+
`Block path collisions detected: ${collisions.join(", ")}`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
if (force) {
|
|
314
|
+
await rm(abs, { recursive: true, force: true });
|
|
315
|
+
}
|
|
316
|
+
await mkdir(abs, { recursive: true });
|
|
317
|
+
for (const file of files) {
|
|
318
|
+
const target = join(abs, file.path);
|
|
319
|
+
await mkdir(dirname(target), { recursive: true });
|
|
320
|
+
await writeFile(target, file.contents, "utf8");
|
|
321
|
+
if (file.executable) {
|
|
322
|
+
await chmod(target, 493);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
function formatTree(files) {
|
|
327
|
+
return files.map((f) => f.path).sort().map((p) => ` ${p}`).join("\n");
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/emit/emit-manifest.ts
|
|
331
|
+
function emitManifest(manifest, snippets) {
|
|
332
|
+
const importLines = [...snippets.imports ?? []];
|
|
333
|
+
const hasCombine = importLines.some(
|
|
334
|
+
(l) => /\bcombinePermissions\b/.test(l)
|
|
335
|
+
);
|
|
336
|
+
if (!hasCombine) {
|
|
337
|
+
importLines.unshift(
|
|
338
|
+
"import { combinePermissions } from '@kb-labs/sdk';"
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
const importSet = new Set(importLines);
|
|
342
|
+
const permLines = snippets.permissions ?? [];
|
|
343
|
+
const permExpr = permLines.length > 0 ? `combinePermissions()
|
|
344
|
+
${permLines.map((l) => `.${l}`).join("\n ")}
|
|
345
|
+
.build()` : "combinePermissions().build()";
|
|
346
|
+
const body = JSON.stringify(manifest, null, 2);
|
|
347
|
+
return `/**
|
|
348
|
+
* V3 plugin manifest. Scaffolded by @kb-labs/scaffold.
|
|
349
|
+
*
|
|
350
|
+
* Edit freely \u2014 this is your plugin's public surface. Add commands by
|
|
351
|
+
* appending to \`cli.commands[]\`, REST routes under \`rest.routes[]\`,
|
|
352
|
+
* Studio pages under \`studio\`. Consult neighbouring plugins
|
|
353
|
+
* (\`plugins/commit\`, \`plugins/review\`) for reference shapes.
|
|
354
|
+
*/
|
|
355
|
+
${[...importSet].join("\n")}
|
|
356
|
+
|
|
357
|
+
const permissions = ${permExpr};
|
|
358
|
+
|
|
359
|
+
const manifest = ${body} as const;
|
|
360
|
+
|
|
361
|
+
const withPermissions = { ...manifest, permissions };
|
|
362
|
+
|
|
363
|
+
export default withPermissions;
|
|
364
|
+
`;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// src/engine.ts
|
|
368
|
+
async function build(inputs) {
|
|
369
|
+
const { entity, selectedBlockIds, context } = inputs;
|
|
370
|
+
const resolved = resolveBlocks(entity.blocks, selectedBlockIds);
|
|
371
|
+
const files = [];
|
|
372
|
+
let manifest = {};
|
|
373
|
+
let snippets = {};
|
|
374
|
+
for (const block of resolved.blocks) {
|
|
375
|
+
const blockFiles = await renderFilesDir(block.filesDir, context);
|
|
376
|
+
files.push(...blockFiles);
|
|
377
|
+
if (block.manifestPatch) {
|
|
378
|
+
const patch = await loadPatch(block.manifestPatch, context);
|
|
379
|
+
manifest = deepMerge(manifest, patch);
|
|
380
|
+
}
|
|
381
|
+
if (block.snippetsPath) {
|
|
382
|
+
const s = await loadSnippets(block.snippetsPath, context);
|
|
383
|
+
snippets = mergeSnippets(snippets, s);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const outTemplate = entity.output ?? join(".kb", "plugins", "<%= it.name %>");
|
|
387
|
+
const outRoot = renderString(outTemplate, context);
|
|
388
|
+
if (entity.manifestTarget) {
|
|
389
|
+
const manifestTarget = renderString(entity.manifestTarget, context);
|
|
390
|
+
files.push({
|
|
391
|
+
path: manifestTarget,
|
|
392
|
+
contents: emitManifest(manifest, snippets)
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
return { blocks: resolved.blocks, files, manifest, snippets, outRoot };
|
|
396
|
+
}
|
|
397
|
+
var INTERNAL_IMPORT_RE = /from\s+['"](@kb-labs\/(?:core-[a-z0-9-]+|platform-[a-z0-9-]+|core-platform(?:\/[^'"]+)?))['"]/g;
|
|
398
|
+
async function exists(p) {
|
|
399
|
+
try {
|
|
400
|
+
await stat(p);
|
|
401
|
+
return true;
|
|
402
|
+
} catch {
|
|
403
|
+
return false;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
async function readJson(p) {
|
|
407
|
+
const raw = await readFile(p, "utf8");
|
|
408
|
+
return JSON.parse(raw);
|
|
409
|
+
}
|
|
410
|
+
async function walkSources(dir) {
|
|
411
|
+
const out = [];
|
|
412
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
413
|
+
for (const entry of entries) {
|
|
414
|
+
if (entry.name === "node_modules" || entry.name === "dist") continue;
|
|
415
|
+
const p = join(dir, entry.name);
|
|
416
|
+
if (entry.isDirectory()) {
|
|
417
|
+
out.push(...await walkSources(p));
|
|
418
|
+
} else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx"))) {
|
|
419
|
+
out.push(p);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return out;
|
|
423
|
+
}
|
|
424
|
+
async function scanPackage(pkgDir) {
|
|
425
|
+
const findings = [];
|
|
426
|
+
const pkgJsonPath = join(pkgDir, "package.json");
|
|
427
|
+
if (!await exists(pkgJsonPath)) {
|
|
428
|
+
return [
|
|
429
|
+
{
|
|
430
|
+
severity: "error",
|
|
431
|
+
package: pkgDir,
|
|
432
|
+
message: "missing package.json"
|
|
433
|
+
}
|
|
434
|
+
];
|
|
435
|
+
}
|
|
436
|
+
const pkg = await readJson(pkgJsonPath);
|
|
437
|
+
const pkgName = typeof pkg.name === "string" ? pkg.name : pkgDir;
|
|
438
|
+
const kb = pkg.kb;
|
|
439
|
+
const isEntry = kb && typeof kb.manifest === "string";
|
|
440
|
+
if (isEntry) {
|
|
441
|
+
const manifestPath = join(pkgDir, kb.manifest);
|
|
442
|
+
if (!await exists(manifestPath)) {
|
|
443
|
+
findings.push({
|
|
444
|
+
severity: "warn",
|
|
445
|
+
package: pkgName,
|
|
446
|
+
message: `manifest not built: ${kb.manifest} missing (run pnpm build)`
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
const deps = pkg.dependencies ?? {};
|
|
451
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
452
|
+
if (isEntry && !deps["@kb-labs/sdk"] && !devDeps["@kb-labs/sdk"]) {
|
|
453
|
+
findings.push({
|
|
454
|
+
severity: "error",
|
|
455
|
+
package: pkgName,
|
|
456
|
+
message: "@kb-labs/sdk not listed as a dependency"
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
const srcDir = join(pkgDir, "src");
|
|
460
|
+
if (await exists(srcDir)) {
|
|
461
|
+
const sources = await walkSources(srcDir);
|
|
462
|
+
const offenders = /* @__PURE__ */ new Set();
|
|
463
|
+
for (const file of sources) {
|
|
464
|
+
const raw = await readFile(file, "utf8");
|
|
465
|
+
for (const match of raw.matchAll(INTERNAL_IMPORT_RE)) {
|
|
466
|
+
if (match[1]) offenders.add(match[1]);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
if (offenders.size > 0) {
|
|
470
|
+
findings.push({
|
|
471
|
+
severity: "warn",
|
|
472
|
+
package: pkgName,
|
|
473
|
+
message: `imports internal KB Labs packages: ${[...offenders].join(", ")} \u2014 use @kb-labs/sdk instead`
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const tsconfigPath = join(pkgDir, "tsconfig.json");
|
|
478
|
+
if (await exists(tsconfigPath)) {
|
|
479
|
+
const tsconfig = await readJson(tsconfigPath);
|
|
480
|
+
const extendsField = tsconfig.extends;
|
|
481
|
+
if (typeof extendsField !== "string" || !extendsField.includes("@kb-labs/devkit")) {
|
|
482
|
+
findings.push({
|
|
483
|
+
severity: "info",
|
|
484
|
+
package: pkgName,
|
|
485
|
+
message: "tsconfig does not extend @kb-labs/devkit \u2014 custom configs may drift"
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return findings;
|
|
490
|
+
}
|
|
491
|
+
async function readLock(workspaceRoot) {
|
|
492
|
+
const lockPath = join(workspaceRoot, ".kb", "marketplace.lock");
|
|
493
|
+
if (!await exists(lockPath)) return null;
|
|
494
|
+
try {
|
|
495
|
+
const raw = await readFile(lockPath, "utf8");
|
|
496
|
+
return JSON.parse(raw);
|
|
497
|
+
} catch {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
async function findEntryPackageDir(pkgDir) {
|
|
502
|
+
const pkgJsonPath = join(pkgDir, "package.json");
|
|
503
|
+
if (await exists(pkgJsonPath)) {
|
|
504
|
+
const pkg = await readJson(pkgJsonPath);
|
|
505
|
+
if (pkg.kb?.manifest) return pkgDir;
|
|
506
|
+
}
|
|
507
|
+
const packagesDir = join(pkgDir, "packages");
|
|
508
|
+
if (!await exists(packagesDir)) return null;
|
|
509
|
+
const entries = await readdir(packagesDir, { withFileTypes: true });
|
|
510
|
+
for (const entry of entries) {
|
|
511
|
+
if (!entry.isDirectory()) continue;
|
|
512
|
+
if (!entry.name.endsWith("-entry")) continue;
|
|
513
|
+
const candidate = join(packagesDir, entry.name);
|
|
514
|
+
if (await exists(join(candidate, "package.json"))) return candidate;
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
async function scanRoot(root, opts = {}) {
|
|
519
|
+
const findings = [];
|
|
520
|
+
let packagesScanned = 0;
|
|
521
|
+
async function visit(dir) {
|
|
522
|
+
const pkgJson = join(dir, "package.json");
|
|
523
|
+
const packagesDir = join(dir, "packages");
|
|
524
|
+
if (await exists(pkgJson)) {
|
|
525
|
+
findings.push(...await scanPackage(dir));
|
|
526
|
+
packagesScanned += 1;
|
|
527
|
+
}
|
|
528
|
+
if (await exists(packagesDir)) {
|
|
529
|
+
const entries2 = await readdir(packagesDir, { withFileTypes: true });
|
|
530
|
+
for (const entry of entries2) {
|
|
531
|
+
if (entry.isDirectory()) await visit(join(packagesDir, entry.name));
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
if (!await exists(root)) {
|
|
536
|
+
return {
|
|
537
|
+
findings: [
|
|
538
|
+
{
|
|
539
|
+
severity: "warn",
|
|
540
|
+
package: root,
|
|
541
|
+
message: "scan root does not exist"
|
|
542
|
+
}
|
|
543
|
+
],
|
|
544
|
+
packagesScanned: 0
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const entries = await readdir(root, { withFileTypes: true });
|
|
548
|
+
for (const entry of entries) {
|
|
549
|
+
if (entry.isDirectory()) await visit(join(root, entry.name));
|
|
550
|
+
}
|
|
551
|
+
if (opts.workspaceRoot) {
|
|
552
|
+
findings.push(...await checkLockSync(root, opts.workspaceRoot));
|
|
553
|
+
}
|
|
554
|
+
return { findings, packagesScanned };
|
|
555
|
+
}
|
|
556
|
+
async function checkLockSync(scaffoldRoot, workspaceRoot) {
|
|
557
|
+
const out = [];
|
|
558
|
+
const lock = await readLock(workspaceRoot);
|
|
559
|
+
const entries = await readdir(scaffoldRoot, { withFileTypes: true });
|
|
560
|
+
for (const entry of entries) {
|
|
561
|
+
if (!entry.isDirectory()) continue;
|
|
562
|
+
const pluginDir = join(scaffoldRoot, entry.name);
|
|
563
|
+
const entryPkgDir = await findEntryPackageDir(pluginDir);
|
|
564
|
+
if (!entryPkgDir) continue;
|
|
565
|
+
const pkg = await readJson(join(entryPkgDir, "package.json"));
|
|
566
|
+
const id = typeof pkg.name === "string" ? pkg.name : entry.name;
|
|
567
|
+
if (!lock || !lock.installed) {
|
|
568
|
+
out.push({
|
|
569
|
+
severity: "warn",
|
|
570
|
+
package: id,
|
|
571
|
+
message: "no .kb/marketplace.lock \u2014 scaffolded plugins will not be discovered until registered"
|
|
572
|
+
});
|
|
573
|
+
return out;
|
|
574
|
+
}
|
|
575
|
+
const lockEntry = lock.installed[id];
|
|
576
|
+
if (!lockEntry) {
|
|
577
|
+
const hint = relative(workspaceRoot, entryPkgDir);
|
|
578
|
+
out.push({
|
|
579
|
+
severity: "warn",
|
|
580
|
+
package: id,
|
|
581
|
+
message: `not registered in marketplace.lock \u2014 run: kb marketplace plugins link ${hint}`
|
|
582
|
+
});
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (lockEntry.enabled === false) {
|
|
586
|
+
out.push({
|
|
587
|
+
severity: "info",
|
|
588
|
+
package: id,
|
|
589
|
+
message: "registered but disabled in marketplace.lock"
|
|
590
|
+
});
|
|
591
|
+
}
|
|
592
|
+
if (typeof lockEntry.resolvedPath === "string") {
|
|
593
|
+
const lockAbs = resolve(workspaceRoot, lockEntry.resolvedPath);
|
|
594
|
+
if (lockAbs !== entryPkgDir) {
|
|
595
|
+
out.push({
|
|
596
|
+
severity: "warn",
|
|
597
|
+
package: id,
|
|
598
|
+
message: `lock points at ${lockEntry.resolvedPath} but scaffolded package is at ${relative(workspaceRoot, entryPkgDir)}`
|
|
599
|
+
});
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return out;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// src/validators.ts
|
|
607
|
+
var NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
608
|
+
var SCOPE_RE = /^@[a-z][a-z0-9-]*$/;
|
|
609
|
+
var SEMVER_RE = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
610
|
+
var IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
611
|
+
var RESERVED_NAMES = /* @__PURE__ */ new Set([
|
|
612
|
+
"kb",
|
|
613
|
+
"core",
|
|
614
|
+
"sdk",
|
|
615
|
+
"test",
|
|
616
|
+
"tests",
|
|
617
|
+
"node_modules",
|
|
618
|
+
"dist",
|
|
619
|
+
"src",
|
|
620
|
+
"con",
|
|
621
|
+
"prn",
|
|
622
|
+
"nul"
|
|
623
|
+
]);
|
|
624
|
+
function validatePackageName(name) {
|
|
625
|
+
if (!name) return "name is required";
|
|
626
|
+
if (name.length > 214) return "name must be <= 214 chars";
|
|
627
|
+
if (!NAME_RE.test(name)) {
|
|
628
|
+
return "name must be lowercase, start with a letter, and contain only a-z 0-9 -";
|
|
629
|
+
}
|
|
630
|
+
if (RESERVED_NAMES.has(name)) return `name "${name}" is reserved`;
|
|
631
|
+
return null;
|
|
632
|
+
}
|
|
633
|
+
function validateScope(scope) {
|
|
634
|
+
if (!scope) return null;
|
|
635
|
+
if (!SCOPE_RE.test(scope)) {
|
|
636
|
+
return 'scope must look like "@acme" (lowercase, starts with @, a-z 0-9 -)';
|
|
637
|
+
}
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
function validateSemver(v) {
|
|
641
|
+
return SEMVER_RE.test(v) ? null : "expected semver (e.g. 1.2.3)";
|
|
642
|
+
}
|
|
643
|
+
function validateIdentifier(v) {
|
|
644
|
+
return IDENTIFIER_RE.test(v) ? null : "expected a JS identifier";
|
|
645
|
+
}
|
|
646
|
+
function runValidator(kind, value) {
|
|
647
|
+
if (typeof value !== "string") return "expected a string";
|
|
648
|
+
switch (kind) {
|
|
649
|
+
case "npmName":
|
|
650
|
+
return validatePackageName(value);
|
|
651
|
+
case "npmScope":
|
|
652
|
+
return validateScope(value);
|
|
653
|
+
case "semver":
|
|
654
|
+
return validateSemver(value);
|
|
655
|
+
case "identifier":
|
|
656
|
+
return validateIdentifier(value);
|
|
657
|
+
default:
|
|
658
|
+
return null;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
export { build, deepMerge, detectCollisions, emitManifest, formatTree, inspectTarget, listEntities, loadEntity, loadPatch, loadSnippets, mergeSnippets, renderFilesDir, renderString, resolveBlocks, runValidator, scanPackage, scanRoot, validateIdentifier, validatePackageName, validateScope, validateSemver, writeFiles };
|
|
663
|
+
//# sourceMappingURL=index.js.map
|
|
664
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/loader/load-entity.ts","../src/resolver/resolve-blocks.ts","../src/render/eta-renderer.ts","../src/patch/apply-patch.ts","../src/writer/write-files.ts","../src/emit/emit-manifest.ts","../src/engine.ts","../src/doctor/scan.ts","../src/validators.ts"],"names":["parseYaml","readdir","join","readFile","stat","resolve","entries","relative","resolvePath"],"mappings":"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","file":"index.js","sourcesContent":["import { readFile, readdir, stat } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport type {\n BlockDefinition,\n EntityDefinition,\n Variable,\n} from '@kb-labs/scaffold-contracts';\n\ninterface RawBlockYaml {\n id?: string;\n describe?: string;\n requires?: string[];\n conflicts?: string[];\n variables?: Variable[];\n manifestPatch?: string;\n snippets?: string;\n}\n\ninterface RawEntityYaml {\n id?: string;\n displayName?: string;\n description?: string;\n variables?: Variable[];\n defaults?: { blocks?: string[] };\n output?: string;\n manifestTarget?: string;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function loadBlock(blockDir: string): Promise<BlockDefinition> {\n const blockYamlPath = join(blockDir, 'block.yaml');\n const raw = await readFile(blockYamlPath, 'utf8');\n const parsed = parseYaml(raw) as RawBlockYaml;\n\n if (!parsed.id) {\n throw new Error(`block.yaml at ${blockYamlPath} is missing \"id\"`);\n }\n\n const filesDir = join(blockDir, 'files');\n const manifestPatchFile =\n parsed.manifestPatch ?? 'manifest.patch.yaml';\n const snippetsFile = parsed.snippets ?? 'manifest.snippets.yaml';\n\n const manifestPatchPath = join(blockDir, manifestPatchFile);\n const snippetsPath = join(blockDir, snippetsFile);\n\n const [hasFiles, hasPatch, hasSnippets] = await Promise.all([\n pathExists(filesDir),\n pathExists(manifestPatchPath),\n pathExists(snippetsPath),\n ]);\n\n return {\n id: parsed.id,\n describe: parsed.describe ?? parsed.id,\n requires: parsed.requires,\n conflicts: parsed.conflicts,\n variables: parsed.variables,\n filesDir: hasFiles ? filesDir : blockDir,\n manifestPatch: hasPatch ? manifestPatchPath : undefined,\n snippetsPath: hasSnippets ? snippetsPath : undefined,\n };\n}\n\n/**\n * Load an entity definition from a templates directory.\n *\n * @param templatesRoot directory containing `<entity>/entity.yaml` and `<entity>/blocks/*`\n * @param entityId e.g. 'plugin', 'adapter'\n */\nexport async function loadEntity(\n templatesRoot: string,\n entityId: string,\n): Promise<EntityDefinition> {\n const entityDir = resolve(templatesRoot, entityId);\n const entityYamlPath = join(entityDir, 'entity.yaml');\n\n if (!(await pathExists(entityYamlPath))) {\n throw new Error(\n `Entity \"${entityId}\" not found: ${entityYamlPath} does not exist`,\n );\n }\n\n const raw = await readFile(entityYamlPath, 'utf8');\n const parsed = (parseYaml(raw) ?? {}) as RawEntityYaml;\n\n const id = parsed.id ?? entityId;\n const blocksDir = join(entityDir, 'blocks');\n const blocks: BlockDefinition[] = [];\n\n if (await pathExists(blocksDir)) {\n const entries = await readdir(blocksDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const blockDir = join(blocksDir, entry.name);\n if (!(await pathExists(join(blockDir, 'block.yaml')))) continue;\n blocks.push(await loadBlock(blockDir));\n }\n }\n\n return {\n id,\n displayName: parsed.displayName ?? id,\n description: parsed.description,\n variables: parsed.variables ?? [],\n blocks,\n defaults: parsed.defaults,\n output: parsed.output,\n manifestTarget: parsed.manifestTarget,\n };\n}\n\n/**\n * List entity ids available in the templates root (directories with `entity.yaml`).\n */\nexport async function listEntities(templatesRoot: string): Promise<string[]> {\n if (!(await pathExists(templatesRoot))) return [];\n const entries = await readdir(templatesRoot, { withFileTypes: true });\n const result: string[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (await pathExists(join(templatesRoot, entry.name, 'entity.yaml'))) {\n result.push(entry.name);\n }\n }\n return result.sort();\n}\n","import type { BlockDefinition } from '@kb-labs/scaffold-contracts';\n\nexport interface ResolveResult {\n /** Block ids in topological order (dependencies first). */\n ordered: string[];\n /** Full block definitions in the same order. */\n blocks: BlockDefinition[];\n}\n\n/**\n * Resolve a user-selected set of block ids into a topologically sorted list,\n * expanding `requires` and rejecting cycles or `conflicts` violations.\n */\nexport function resolveBlocks(\n all: BlockDefinition[],\n selected: string[],\n): ResolveResult {\n const byId = new Map<string, BlockDefinition>();\n for (const b of all) byId.set(b.id, b);\n\n const missing = selected.filter((id) => !byId.has(id));\n if (missing.length > 0) {\n throw new Error(`Unknown block(s): ${missing.join(', ')}`);\n }\n\n // Expand requires (BFS).\n const expanded = new Set<string>();\n const queue = [...selected];\n while (queue.length > 0) {\n const id = queue.shift()!;\n if (expanded.has(id)) continue;\n expanded.add(id);\n const block = byId.get(id);\n if (!block) continue;\n for (const dep of block.requires ?? []) {\n if (!byId.has(dep)) {\n throw new Error(\n `Block \"${id}\" requires unknown block \"${dep}\"`,\n );\n }\n if (!expanded.has(dep)) queue.push(dep);\n }\n }\n\n // Conflict check against the final expanded set.\n for (const id of expanded) {\n const block = byId.get(id)!;\n for (const other of block.conflicts ?? []) {\n if (expanded.has(other)) {\n throw new Error(\n `Block \"${id}\" conflicts with \"${other}\" — both selected`,\n );\n }\n }\n }\n\n // Topological sort (Kahn's algorithm).\n const inDegree = new Map<string, number>();\n const edges = new Map<string, Set<string>>();\n for (const id of expanded) {\n inDegree.set(id, 0);\n edges.set(id, new Set());\n }\n for (const id of expanded) {\n const block = byId.get(id)!;\n for (const dep of block.requires ?? []) {\n if (!expanded.has(dep)) continue;\n edges.get(dep)!.add(id);\n inDegree.set(id, (inDegree.get(id) ?? 0) + 1);\n }\n }\n\n const ready: string[] = [];\n for (const [id, deg] of inDegree) if (deg === 0) ready.push(id);\n ready.sort();\n\n const ordered: string[] = [];\n while (ready.length > 0) {\n const id = ready.shift()!;\n ordered.push(id);\n for (const next of edges.get(id) ?? []) {\n const d = (inDegree.get(next) ?? 0) - 1;\n inDegree.set(next, d);\n if (d === 0) {\n ready.push(next);\n ready.sort();\n }\n }\n }\n\n if (ordered.length !== expanded.size) {\n throw new Error('Cycle detected in block requires graph');\n }\n\n return { ordered, blocks: ordered.map((id) => byId.get(id)!) };\n}\n","import { readFile, readdir, stat } from 'node:fs/promises';\nimport { join, relative } from 'node:path';\nimport { Eta } from 'eta';\nimport type {\n RenderContext,\n RenderedFile,\n} from '@kb-labs/scaffold-contracts';\n\nconst eta = new Eta({\n autoEscape: false,\n useWith: true,\n tags: ['<%', '%>'],\n});\n\nconst ETA_SUFFIX = '.eta';\n\n/**\n * Render a single template string through eta using the given context.\n */\nexport function renderString(template: string, ctx: RenderContext): string {\n return eta.renderString(template, ctx);\n}\n\nasync function walk(dir: string): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const out: string[] = [];\n for (const entry of entries) {\n const p = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(...(await walk(p)));\n } else if (entry.isFile()) {\n out.push(p);\n }\n }\n return out;\n}\n\nfunction renderPath(relPath: string, ctx: RenderContext): string {\n // Use simple __var__ substitution for path segments — eta's `<% %>`\n // tags are illegal on Windows and awkward on Unix.\n const substituted = relPath\n .replace(/__name__/g, ctx.name)\n .replace(/__scope__/g, ctx.scope.replace(/^@/, ''));\n return substituted.endsWith(ETA_SUFFIX)\n ? substituted.slice(0, -ETA_SUFFIX.length)\n : substituted;\n}\n\n/**\n * Render all files under `filesDir`. File bodies AND path segments go through\n * eta. A trailing `.eta` extension on the file name is stripped after render.\n *\n * Returned paths are relative to `filesDir`. The caller decides the output root.\n */\nconst SKIP_MARKER = '<!--@@scaffold:skip@@-->';\n\nexport async function renderFilesDir(\n filesDir: string,\n ctx: RenderContext,\n): Promise<RenderedFile[]> {\n const absolutes = await walk(filesDir);\n const rendered: RenderedFile[] = [];\n for (const abs of absolutes) {\n const rel = relative(filesDir, abs);\n const renderedRel = renderPath(rel, ctx);\n const raw = await readFile(abs, 'utf8');\n const body = abs.endsWith(ETA_SUFFIX)\n ? renderString(raw, ctx)\n : raw;\n\n // Templates can opt out of a given file by emitting a skip marker.\n // Useful for files that should only exist in some modes (e.g.\n // pnpm-workspace.yaml only for standalone mode).\n if (body.trim() === SKIP_MARKER) continue;\n\n const st = await stat(abs);\n rendered.push({\n path: renderedRel,\n contents: body,\n executable: (st.mode & 0o111) !== 0,\n });\n }\n return rendered;\n}\n","import { readFile } from 'node:fs/promises';\nimport { parse as parseYaml } from 'yaml';\nimport type {\n ManifestPatch,\n ManifestSnippets,\n RenderContext,\n} from '@kb-labs/scaffold-contracts';\nimport { renderString } from '../render/eta-renderer.js';\n\nfunction isPlainObject(v: unknown): v is Record<string, unknown> {\n return (\n typeof v === 'object' &&\n v !== null &&\n !Array.isArray(v) &&\n Object.getPrototypeOf(v) === Object.prototype\n );\n}\n\nfunction mergeArrays(a: unknown[], b: unknown[]): unknown[] {\n const allObjects =\n a.every(isPlainObject) && b.every(isPlainObject) && a.length + b.length > 0;\n\n if (allObjects) {\n const keyOf = (x: Record<string, unknown>): string | null => {\n if (typeof x.id === 'string') return `id:${x.id}`;\n if (typeof x.name === 'string') return `name:${x.name}`;\n return null;\n };\n const allKeyed =\n a.every((x) => keyOf(x as Record<string, unknown>) !== null) &&\n b.every((x) => keyOf(x as Record<string, unknown>) !== null);\n\n if (allKeyed) {\n const byKey = new Map<string, Record<string, unknown>>();\n for (const item of [...a, ...b] as Record<string, unknown>[]) {\n const k = keyOf(item)!;\n const prev = byKey.get(k);\n byKey.set(k, prev ? deepMerge(prev, item) : item);\n }\n return [...byKey.values()];\n }\n }\n\n // Primitive or mixed: Set-dedupe where possible.\n const seen = new Set<string>();\n const out: unknown[] = [];\n for (const item of [...a, ...b]) {\n const key = JSON.stringify(item);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(item);\n }\n return out;\n}\n\nexport function deepMerge(\n target: Record<string, unknown>,\n patch: Record<string, unknown>,\n): Record<string, unknown> {\n const out: Record<string, unknown> = { ...target };\n for (const [key, value] of Object.entries(patch)) {\n const existing = out[key];\n if (isPlainObject(existing) && isPlainObject(value)) {\n out[key] = deepMerge(existing, value);\n } else if (Array.isArray(existing) && Array.isArray(value)) {\n out[key] = mergeArrays(existing, value);\n } else {\n out[key] = value;\n }\n }\n return out;\n}\n\n/**\n * Load a manifest.patch.yaml from disk, render it through eta with the\n * render context, parse as YAML, and return the resulting object.\n */\nexport async function loadPatch(\n path: string,\n ctx: RenderContext,\n): Promise<ManifestPatch> {\n const raw = await readFile(path, 'utf8');\n const rendered = renderString(raw, ctx);\n const parsed = parseYaml(rendered);\n if (parsed == null) return {};\n if (!isPlainObject(parsed)) {\n throw new Error(`Patch at ${path} must be a YAML object`);\n }\n return parsed;\n}\n\nexport async function loadSnippets(\n path: string,\n ctx: RenderContext,\n): Promise<ManifestSnippets> {\n const raw = await readFile(path, 'utf8');\n const rendered = renderString(raw, ctx);\n const parsed = parseYaml(rendered);\n if (parsed == null) return {};\n if (!isPlainObject(parsed)) {\n throw new Error(`Snippets at ${path} must be a YAML object`);\n }\n return parsed as ManifestSnippets;\n}\n\nexport function mergeSnippets(\n a: ManifestSnippets,\n b: ManifestSnippets,\n): ManifestSnippets {\n const mergedExtras: Record<string, string[]> = { ...(a.extras ?? {}) };\n for (const [k, v] of Object.entries(b.extras ?? {})) {\n mergedExtras[k] = dedupe([...(mergedExtras[k] ?? []), ...v]);\n }\n return {\n imports: dedupe([...(a.imports ?? []), ...(b.imports ?? [])]),\n permissions: dedupe([\n ...(a.permissions ?? []),\n ...(b.permissions ?? []),\n ]),\n extras: Object.keys(mergedExtras).length > 0 ? mergedExtras : undefined,\n };\n}\n\nfunction dedupe(items: string[]): string[] {\n return [...new Set(items)];\n}\n","import { mkdir, writeFile, chmod, stat, readdir, rm } from 'node:fs/promises';\nimport { dirname, join, resolve } from 'node:path';\nimport type { RenderedFile } from '@kb-labs/scaffold-contracts';\n\nexport interface WriteOptions {\n outRoot: string;\n files: RenderedFile[];\n /** If set, wipe the root first. */\n force?: boolean;\n}\n\nexport interface TargetState {\n exists: boolean;\n empty: boolean;\n}\n\nexport async function inspectTarget(outRoot: string): Promise<TargetState> {\n try {\n const st = await stat(outRoot);\n if (!st.isDirectory()) return { exists: true, empty: false };\n const entries = await readdir(outRoot);\n return { exists: true, empty: entries.length === 0 };\n } catch {\n return { exists: false, empty: true };\n }\n}\n\n/**\n * Detect intra-batch path collisions. Two blocks writing the same path is a\n * configuration bug, not a merge case.\n */\nexport function detectCollisions(files: RenderedFile[]): string[] {\n const seen = new Map<string, number>();\n for (const f of files) {\n seen.set(f.path, (seen.get(f.path) ?? 0) + 1);\n }\n return [...seen.entries()]\n .filter(([, n]) => n > 1)\n .map(([p]) => p);\n}\n\n/**\n * Write the rendered file set to disk. If `force` is set and the root\n * exists, it is wiped first. Otherwise the caller is responsible for\n * ensuring the root is absent or empty — see `inspectTarget`.\n */\nexport async function writeFiles(opts: WriteOptions): Promise<void> {\n const { outRoot, files, force } = opts;\n const abs = resolve(outRoot);\n\n const collisions = detectCollisions(files);\n if (collisions.length > 0) {\n throw new Error(\n `Block path collisions detected: ${collisions.join(', ')}`,\n );\n }\n\n if (force) {\n await rm(abs, { recursive: true, force: true });\n }\n\n await mkdir(abs, { recursive: true });\n\n for (const file of files) {\n const target = join(abs, file.path);\n await mkdir(dirname(target), { recursive: true });\n await writeFile(target, file.contents, 'utf8');\n if (file.executable) {\n await chmod(target, 0o755);\n }\n }\n}\n\n/**\n * Produce a human-readable tree of the files that would be written.\n */\nexport function formatTree(files: RenderedFile[]): string {\n return files\n .map((f) => f.path)\n .sort()\n .map((p) => ` ${p}`)\n .join('\\n');\n}\n","import type {\n ManifestPatch,\n ManifestSnippets,\n} from '@kb-labs/scaffold-contracts';\n\n/**\n * Emit a TypeScript source file for the plugin V3 manifest.\n *\n * The manifest object is stringified as a JSON literal. A `permissions`\n * expression is assembled from `combinePermissions()` snippets contributed\n * by blocks and spread in alongside. Imports are deduped at the top.\n */\nexport function emitManifest(\n manifest: ManifestPatch,\n snippets: ManifestSnippets,\n): string {\n const importLines = [...(snippets.imports ?? [])];\n const hasCombine = importLines.some((l) =>\n /\\bcombinePermissions\\b/.test(l),\n );\n if (!hasCombine) {\n importLines.unshift(\n \"import { combinePermissions } from '@kb-labs/sdk';\",\n );\n }\n const importSet = new Set<string>(importLines);\n\n const permLines = snippets.permissions ?? [];\n const permExpr =\n permLines.length > 0\n ? `combinePermissions()\\n ${permLines.map((l) => `.${l}`).join('\\n ')}\\n .build()`\n : 'combinePermissions().build()';\n\n const body = JSON.stringify(manifest, null, 2);\n\n return `/**\n * V3 plugin manifest. Scaffolded by @kb-labs/scaffold.\n *\n * Edit freely — this is your plugin's public surface. Add commands by\n * appending to \\`cli.commands[]\\`, REST routes under \\`rest.routes[]\\`,\n * Studio pages under \\`studio\\`. Consult neighbouring plugins\n * (\\`plugins/commit\\`, \\`plugins/review\\`) for reference shapes.\n */\n${[...importSet].join('\\n')}\n\nconst permissions = ${permExpr};\n\nconst manifest = ${body} as const;\n\nconst withPermissions = { ...manifest, permissions };\n\nexport default withPermissions;\n`;\n}\n","import { join } from 'node:path';\nimport type {\n BlockDefinition,\n EntityDefinition,\n ManifestPatch,\n ManifestSnippets,\n RenderContext,\n RenderedFile,\n} from '@kb-labs/scaffold-contracts';\nimport { resolveBlocks } from './resolver/resolve-blocks.js';\nimport {\n renderFilesDir,\n renderString,\n} from './render/eta-renderer.js';\nimport {\n deepMerge,\n loadPatch,\n loadSnippets,\n mergeSnippets,\n} from './patch/apply-patch.js';\nimport { emitManifest } from './emit/emit-manifest.js';\n\nexport interface BuildInputs {\n entity: EntityDefinition;\n selectedBlockIds: string[];\n context: RenderContext;\n}\n\nexport interface BuildResult {\n blocks: BlockDefinition[];\n files: RenderedFile[];\n manifest: ManifestPatch;\n snippets: ManifestSnippets;\n outRoot: string;\n}\n\n/**\n * Compose the full render result from a resolved entity + user selections.\n *\n * Steps:\n * 1. Resolve & topo-sort blocks (expanding requires, checking conflicts).\n * 2. Render every block's `files/` directory into in-memory RenderedFile[].\n * 3. Fold manifest patches and snippets in topological order.\n * 4. Compute `outRoot` from `entity.output` (also eta-rendered).\n *\n * No disk writes happen here — call `writeFiles()` afterward.\n */\nexport async function build(inputs: BuildInputs): Promise<BuildResult> {\n const { entity, selectedBlockIds, context } = inputs;\n\n const resolved = resolveBlocks(entity.blocks, selectedBlockIds);\n\n const files: RenderedFile[] = [];\n let manifest: ManifestPatch = {};\n let snippets: ManifestSnippets = {};\n\n for (const block of resolved.blocks) {\n const blockFiles = await renderFilesDir(block.filesDir, context);\n files.push(...blockFiles);\n\n if (block.manifestPatch) {\n const patch = await loadPatch(block.manifestPatch, context);\n manifest = deepMerge(manifest, patch);\n }\n if (block.snippetsPath) {\n const s = await loadSnippets(block.snippetsPath, context);\n snippets = mergeSnippets(snippets, s);\n }\n }\n\n const outTemplate = entity.output ?? join('.kb', 'plugins', '<%= it.name %>');\n const outRoot = renderString(outTemplate, context);\n\n if (entity.manifestTarget) {\n const manifestTarget = renderString(entity.manifestTarget, context);\n files.push({\n path: manifestTarget,\n contents: emitManifest(manifest, snippets),\n });\n }\n\n return { blocks: resolved.blocks, files, manifest, snippets, outRoot };\n}\n","import { readFile, readdir, stat } from 'node:fs/promises';\nimport { join, relative, resolve as resolvePath } from 'node:path';\n\nexport type Severity = 'info' | 'warn' | 'error';\n\nexport interface Finding {\n severity: Severity;\n package: string;\n message: string;\n}\n\nconst INTERNAL_IMPORT_RE =\n /from\\s+['\"](@kb-labs\\/(?:core-[a-z0-9-]+|platform-[a-z0-9-]+|core-platform(?:\\/[^'\"]+)?))['\"]/g;\n\nasync function exists(p: string): Promise<boolean> {\n try {\n await stat(p);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readJson(p: string): Promise<Record<string, unknown>> {\n const raw = await readFile(p, 'utf8');\n return JSON.parse(raw) as Record<string, unknown>;\n}\n\nasync function walkSources(dir: string): Promise<string[]> {\n const out: string[] = [];\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name === 'dist') continue;\n const p = join(dir, entry.name);\n if (entry.isDirectory()) {\n out.push(...(await walkSources(p)));\n } else if (\n entry.isFile() &&\n (entry.name.endsWith('.ts') || entry.name.endsWith('.tsx'))\n ) {\n out.push(p);\n }\n }\n return out;\n}\n\n/**\n * Scan a single package directory (one that contains `package.json`).\n */\nexport async function scanPackage(pkgDir: string): Promise<Finding[]> {\n const findings: Finding[] = [];\n const pkgJsonPath = join(pkgDir, 'package.json');\n if (!(await exists(pkgJsonPath))) {\n return [\n {\n severity: 'error',\n package: pkgDir,\n message: 'missing package.json',\n },\n ];\n }\n const pkg = await readJson(pkgJsonPath);\n const pkgName = typeof pkg.name === 'string' ? pkg.name : pkgDir;\n\n const kb = pkg.kb as Record<string, unknown> | undefined;\n const isEntry = kb && typeof kb.manifest === 'string';\n\n if (isEntry) {\n const manifestPath = join(pkgDir, kb.manifest as string);\n if (!(await exists(manifestPath))) {\n findings.push({\n severity: 'warn',\n package: pkgName,\n message: `manifest not built: ${kb.manifest} missing (run pnpm build)`,\n });\n }\n }\n\n const deps = (pkg.dependencies ?? {}) as Record<string, string>;\n const devDeps = (pkg.devDependencies ?? {}) as Record<string, string>;\n\n if (isEntry && !deps['@kb-labs/sdk'] && !devDeps['@kb-labs/sdk']) {\n findings.push({\n severity: 'error',\n package: pkgName,\n message: '@kb-labs/sdk not listed as a dependency',\n });\n }\n\n const srcDir = join(pkgDir, 'src');\n if (await exists(srcDir)) {\n const sources = await walkSources(srcDir);\n const offenders = new Set<string>();\n for (const file of sources) {\n const raw = await readFile(file, 'utf8');\n for (const match of raw.matchAll(INTERNAL_IMPORT_RE)) {\n if (match[1]) offenders.add(match[1]);\n }\n }\n if (offenders.size > 0) {\n findings.push({\n severity: 'warn',\n package: pkgName,\n message: `imports internal KB Labs packages: ${[...offenders].join(', ')} — use @kb-labs/sdk instead`,\n });\n }\n }\n\n const tsconfigPath = join(pkgDir, 'tsconfig.json');\n if (await exists(tsconfigPath)) {\n const tsconfig = await readJson(tsconfigPath);\n const extendsField = tsconfig.extends;\n if (\n typeof extendsField !== 'string' ||\n !extendsField.includes('@kb-labs/devkit')\n ) {\n findings.push({\n severity: 'info',\n package: pkgName,\n message:\n 'tsconfig does not extend @kb-labs/devkit — custom configs may drift',\n });\n }\n }\n\n return findings;\n}\n\nexport interface ScanResult {\n findings: Finding[];\n packagesScanned: number;\n}\n\ninterface LockEntry {\n resolvedPath?: string;\n enabled?: boolean;\n}\n\ninterface MarketplaceLockShape {\n installed?: Record<string, LockEntry>;\n}\n\nasync function readLock(\n workspaceRoot: string,\n): Promise<MarketplaceLockShape | null> {\n const lockPath = join(workspaceRoot, '.kb', 'marketplace.lock');\n if (!(await exists(lockPath))) return null;\n try {\n const raw = await readFile(lockPath, 'utf8');\n return JSON.parse(raw) as MarketplaceLockShape;\n } catch {\n return null;\n }\n}\n\nasync function findEntryPackageDir(pkgDir: string): Promise<string | null> {\n // If pkgDir itself has a kb-manifest entry, it's already the entry package.\n const pkgJsonPath = join(pkgDir, 'package.json');\n if (await exists(pkgJsonPath)) {\n const pkg = (await readJson(pkgJsonPath)) as { kb?: { manifest?: string } };\n if (pkg.kb?.manifest) return pkgDir;\n }\n // Otherwise look in packages/*-entry.\n const packagesDir = join(pkgDir, 'packages');\n if (!(await exists(packagesDir))) return null;\n const entries = await readdir(packagesDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (!entry.name.endsWith('-entry')) continue;\n const candidate = join(packagesDir, entry.name);\n if (await exists(join(candidate, 'package.json'))) return candidate;\n }\n return null;\n}\n\nexport interface ScanOptions {\n /**\n * Workspace root containing `.kb/marketplace.lock`. When provided, each\n * scaffolded plugin found is cross-checked against the lock — missing\n * entries surface as warnings so the user knows to `kb marketplace\n * plugins link` or rerun `kb scaffold`.\n */\n workspaceRoot?: string;\n}\n\n/**\n * Scan a root directory containing one or more plugin packages. A plugin\n * package has either:\n * - its own `package.json` at the root, or\n * - a `packages/` directory with child packages.\n *\n * When `opts.workspaceRoot` is given, also cross-checks every discovered\n * scaffolded plugin against `.kb/marketplace.lock`.\n */\nexport async function scanRoot(\n root: string,\n opts: ScanOptions = {},\n): Promise<ScanResult> {\n const findings: Finding[] = [];\n let packagesScanned = 0;\n\n async function visit(dir: string): Promise<void> {\n const pkgJson = join(dir, 'package.json');\n const packagesDir = join(dir, 'packages');\n if (await exists(pkgJson)) {\n findings.push(...(await scanPackage(dir)));\n packagesScanned += 1;\n }\n if (await exists(packagesDir)) {\n const entries = await readdir(packagesDir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) await visit(join(packagesDir, entry.name));\n }\n }\n }\n\n if (!(await exists(root))) {\n return {\n findings: [\n {\n severity: 'warn',\n package: root,\n message: 'scan root does not exist',\n },\n ],\n packagesScanned: 0,\n };\n }\n\n const entries = await readdir(root, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.isDirectory()) await visit(join(root, entry.name));\n }\n\n if (opts.workspaceRoot) {\n findings.push(...(await checkLockSync(root, opts.workspaceRoot)));\n }\n\n return { findings, packagesScanned };\n}\n\n/**\n * For each scaffolded plugin root under `scaffoldRoot`, verify that the\n * plugin's entry package is registered in `.kb/marketplace.lock`.\n *\n * Produces:\n * - warn \"plugin not registered in marketplace.lock (run `kb marketplace plugins link ...`)\"\n * - warn \"lock entry points elsewhere\"\n * - info \"plugin disabled in lock\"\n */\nasync function checkLockSync(\n scaffoldRoot: string,\n workspaceRoot: string,\n): Promise<Finding[]> {\n const out: Finding[] = [];\n const lock = await readLock(workspaceRoot);\n\n const entries = await readdir(scaffoldRoot, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const pluginDir = join(scaffoldRoot, entry.name);\n const entryPkgDir = await findEntryPackageDir(pluginDir);\n if (!entryPkgDir) continue;\n\n const pkg = (await readJson(join(entryPkgDir, 'package.json'))) as {\n name?: string;\n };\n const id = typeof pkg.name === 'string' ? pkg.name : entry.name;\n\n if (!lock || !lock.installed) {\n out.push({\n severity: 'warn',\n package: id,\n message:\n 'no .kb/marketplace.lock — scaffolded plugins will not be discovered until registered',\n });\n return out;\n }\n\n const lockEntry = lock.installed[id];\n if (!lockEntry) {\n const hint = relative(workspaceRoot, entryPkgDir);\n out.push({\n severity: 'warn',\n package: id,\n message: `not registered in marketplace.lock — run: kb marketplace plugins link ${hint}`,\n });\n continue;\n }\n\n if (lockEntry.enabled === false) {\n out.push({\n severity: 'info',\n package: id,\n message: 'registered but disabled in marketplace.lock',\n });\n }\n\n if (typeof lockEntry.resolvedPath === 'string') {\n const lockAbs = resolvePath(workspaceRoot, lockEntry.resolvedPath);\n if (lockAbs !== entryPkgDir) {\n out.push({\n severity: 'warn',\n package: id,\n message: `lock points at ${lockEntry.resolvedPath} but scaffolded package is at ${relative(workspaceRoot, entryPkgDir)}`,\n });\n }\n }\n }\n\n return out;\n}\n","const NAME_RE = /^[a-z][a-z0-9-]*$/;\nconst SCOPE_RE = /^@[a-z][a-z0-9-]*$/;\nconst SEMVER_RE = /^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/;\nconst IDENTIFIER_RE = /^[A-Za-z_$][A-Za-z0-9_$]*$/;\n\nconst RESERVED_NAMES = new Set([\n 'kb',\n 'core',\n 'sdk',\n 'test',\n 'tests',\n 'node_modules',\n 'dist',\n 'src',\n 'con',\n 'prn',\n 'nul',\n]);\n\nexport function validatePackageName(name: string): string | null {\n if (!name) return 'name is required';\n if (name.length > 214) return 'name must be <= 214 chars';\n if (!NAME_RE.test(name)) {\n return 'name must be lowercase, start with a letter, and contain only a-z 0-9 -';\n }\n if (RESERVED_NAMES.has(name)) return `name \"${name}\" is reserved`;\n return null;\n}\n\nexport function validateScope(scope: string): string | null {\n if (!scope) return null;\n if (!SCOPE_RE.test(scope)) {\n return 'scope must look like \"@acme\" (lowercase, starts with @, a-z 0-9 -)';\n }\n return null;\n}\n\nexport function validateSemver(v: string): string | null {\n return SEMVER_RE.test(v) ? null : 'expected semver (e.g. 1.2.3)';\n}\n\nexport function validateIdentifier(v: string): string | null {\n return IDENTIFIER_RE.test(v) ? null : 'expected a JS identifier';\n}\n\nexport function runValidator(\n kind: string,\n value: unknown,\n): string | null {\n if (typeof value !== 'string') return 'expected a string';\n switch (kind) {\n case 'npmName':\n return validatePackageName(value);\n case 'npmScope':\n return validateScope(value);\n case 'semver':\n return validateSemver(value);\n case 'identifier':\n return validateIdentifier(value);\n default:\n return null;\n }\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kb-labs/scaffold-core",
|
|
3
|
+
"version": "2.29.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Pure engine for the KB Labs scaffolder: loader, resolver, renderer, patch merger, writer.",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
},
|
|
13
|
+
"./dist/*": "./dist/*"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"sideEffects": false,
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"eta": "^3.5.0",
|
|
21
|
+
"yaml": "^2.6.1",
|
|
22
|
+
"@kb-labs/scaffold-contracts": "2.29.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^24.3.3",
|
|
26
|
+
"eslint": "^9",
|
|
27
|
+
"rimraf": "^6.0.1",
|
|
28
|
+
"tsup": "^8.5.0",
|
|
29
|
+
"typescript": "^5.6.3",
|
|
30
|
+
"vitest": "^3.2.4",
|
|
31
|
+
"@kb-labs/devkit": "2.29.0"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=20.0.0",
|
|
35
|
+
"pnpm": ">=9.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"clean": "rimraf dist",
|
|
39
|
+
"build": "tsup --config tsup.config.ts",
|
|
40
|
+
"dev": "tsup --config tsup.config.ts --watch",
|
|
41
|
+
"lint": "eslint src --ext .ts",
|
|
42
|
+
"lint:fix": "eslint . --fix",
|
|
43
|
+
"type-check": "tsc --noEmit",
|
|
44
|
+
"test": "vitest run --passWithNoTests",
|
|
45
|
+
"test:watch": "vitest"
|
|
46
|
+
}
|
|
47
|
+
}
|