@farming-labs/docs 0.2.60 → 0.2.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +11 -1
- package/dist/agent-BFqyqEnC.mjs +0 -4547
- package/dist/agent-DlxriaTs.mjs +0 -624
- package/dist/agent-evals-kJs2Y9xR.mjs +0 -2144
- package/dist/agent-export-BgUaiW8f.mjs +0 -869
- package/dist/agent-scope-CCaIY1aK.mjs +0 -283
- package/dist/agents-Djh-HXih.mjs +0 -219
- package/dist/analytics-Bx44lg6d.mjs +0 -177
- package/dist/cli/index.d.mts +0 -15
- package/dist/cli/index.mjs +0 -452
- package/dist/client/react.d.mts +0 -45
- package/dist/client/react.mjs +0 -223
- package/dist/cloud-BH_sHX64.mjs +0 -1615
- package/dist/cloud-analytics-CSyFE6SS.mjs +0 -132
- package/dist/cloud-ask-ai-B2WnG4fF.d.mts +0 -23
- package/dist/cloud-ask-ai-hnJfj8-X.mjs +0 -382
- package/dist/code-blocks-qe0T8-xe.mjs +0 -871
- package/dist/codeblocks-Bq67u32v.mjs +0 -250
- package/dist/config-DASewQ0x.mjs +0 -363
- package/dist/dev-DgY5xGl9.mjs +0 -1333
- package/dist/docs-cloud-server.d.mts +0 -70
- package/dist/docs-cloud-server.mjs +0 -310
- package/dist/doctor-CO1VMcF_.mjs +0 -1906
- package/dist/downgrade-BZs86NVr.mjs +0 -184
- package/dist/errors-CVqZ3kOO.mjs +0 -20
- package/dist/golden-evaluations-BN9u2wxw.mjs +0 -1483
- package/dist/i18n-CAlj1ADU.mjs +0 -40
- package/dist/index.d.mts +0 -1099
- package/dist/index.mjs +0 -9
- package/dist/init-Bd_k06bR.mjs +0 -1233
- package/dist/mcp-B_yXL5G5.mjs +0 -137
- package/dist/mcp.d.mts +0 -287
- package/dist/mcp.mjs +0 -4135
- package/dist/metadata-BDuewuzq.mjs +0 -237
- package/dist/package-version-qik_4J6C.mjs +0 -128
- package/dist/reading-time-BkEft6SD.mjs +0 -741
- package/dist/review-NC-sOdXn.mjs +0 -665
- package/dist/robots-DskPvGPw.mjs +0 -178
- package/dist/robots-ltltiLJF.mjs +0 -197
- package/dist/search-C1JitPwi.d.mts +0 -397
- package/dist/search-D57JXQLj.mjs +0 -1758
- package/dist/search-o4Ud6OXv.mjs +0 -102
- package/dist/server.d.mts +0 -341
- package/dist/server.mjs +0 -11
- package/dist/sitemap-Cq-Yj_iA.mjs +0 -247
- package/dist/sitemap-server-C1ibVKOy.mjs +0 -1137
- package/dist/templates-DNw15P-x.mjs +0 -2373
- package/dist/types-XHABMh_f.d.mts +0 -3248
- package/dist/upgrade-BCJTCW3O.mjs +0 -56
- package/dist/utils-6UCLxv4B.mjs +0 -225
|
@@ -1,871 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { execFile } from "node:child_process";
|
|
4
|
-
import { tmpdir } from "node:os";
|
|
5
|
-
import { promisify } from "node:util";
|
|
6
|
-
|
|
7
|
-
//#region src/code-blocks.ts
|
|
8
|
-
const execFileAsync = promisify(execFile);
|
|
9
|
-
const DEFAULT_ENV_FILES = [
|
|
10
|
-
".env.local",
|
|
11
|
-
".env.test",
|
|
12
|
-
".env"
|
|
13
|
-
];
|
|
14
|
-
const DEFAULT_COMMAND_TIMEOUT_MS = 6e4;
|
|
15
|
-
function resolveDocsCodeBlocksValidateConfig(input) {
|
|
16
|
-
if (input === false || input === void 0) return {
|
|
17
|
-
enabled: false,
|
|
18
|
-
planner: { provider: "metadata" },
|
|
19
|
-
runner: {
|
|
20
|
-
provider: "local",
|
|
21
|
-
tokenEnv: "VERCEL_TOKEN",
|
|
22
|
-
projectIdEnv: "VERCEL_PROJECT_ID",
|
|
23
|
-
teamIdEnv: "VERCEL_TEAM_ID",
|
|
24
|
-
projectJson: ".vercel/project.json",
|
|
25
|
-
runtime: "node24",
|
|
26
|
-
apiUrlEnv: "DAYTONA_API_URL",
|
|
27
|
-
targetEnv: "DAYTONA_TARGET",
|
|
28
|
-
timeoutMs: DEFAULT_COMMAND_TIMEOUT_MS
|
|
29
|
-
},
|
|
30
|
-
envFile: DEFAULT_ENV_FILES,
|
|
31
|
-
env: {},
|
|
32
|
-
missingEnv: "skip",
|
|
33
|
-
unsupportedLanguage: "skip",
|
|
34
|
-
mode: "report"
|
|
35
|
-
};
|
|
36
|
-
const config = input === true ? {} : input;
|
|
37
|
-
const planner = normalizePlannerConfig(config.planner);
|
|
38
|
-
const runner = normalizeRunnerConfig(config.runner);
|
|
39
|
-
const envFile = Array.isArray(config.envFile) ? config.envFile : typeof config.envFile === "string" ? [config.envFile] : DEFAULT_ENV_FILES;
|
|
40
|
-
return {
|
|
41
|
-
enabled: config.enabled ?? true,
|
|
42
|
-
planner,
|
|
43
|
-
runner,
|
|
44
|
-
envFile,
|
|
45
|
-
env: config.env ?? {},
|
|
46
|
-
missingEnv: config.missingEnv ?? "skip",
|
|
47
|
-
unsupportedLanguage: config.unsupportedLanguage ?? "skip",
|
|
48
|
-
mode: config.mode ?? "report"
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
function parseCodeFenceInfo(info) {
|
|
52
|
-
const trimmed = info.trim();
|
|
53
|
-
if (!trimmed) return { meta: {} };
|
|
54
|
-
const firstToken = trimmed.match(/^(\S+)/)?.[1] ?? "";
|
|
55
|
-
const language = firstToken && !firstToken.includes("=") ? firstToken : void 0;
|
|
56
|
-
const attributeSource = language ? trimmed.slice(firstToken.length).trim() : trimmed;
|
|
57
|
-
const meta = {};
|
|
58
|
-
const attributePattern = /([A-Za-z_:][\w:.-]*)(?:=(?:"([^"]*)"|'([^']*)'|([^\s"']+)))?/g;
|
|
59
|
-
let match;
|
|
60
|
-
while (match = attributePattern.exec(attributeSource)) {
|
|
61
|
-
const key = match[1];
|
|
62
|
-
meta[key] = match[2] ?? match[3] ?? match[4] ?? true;
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
language,
|
|
66
|
-
meta
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function extractCodeBlocksFromMarkdown(input) {
|
|
70
|
-
const blocks = [];
|
|
71
|
-
const lines = input.source.split("\n");
|
|
72
|
-
let openFence;
|
|
73
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
74
|
-
const line = lines[index] ?? "";
|
|
75
|
-
const trimmed = line.trim();
|
|
76
|
-
if (!openFence) {
|
|
77
|
-
const openMatch = trimmed.match(/^(`{3,}|~{3,})(.*)$/);
|
|
78
|
-
if (!openMatch) continue;
|
|
79
|
-
openFence = {
|
|
80
|
-
marker: openMatch[1],
|
|
81
|
-
info: openMatch[2]?.trim() ?? "",
|
|
82
|
-
code: [],
|
|
83
|
-
lineStart: index + 1
|
|
84
|
-
};
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (isClosingFence(trimmed, openFence.marker)) {
|
|
88
|
-
const parsed = parseCodeFenceInfo(openFence.info);
|
|
89
|
-
const meta = parsed.meta;
|
|
90
|
-
const blockIndex = blocks.length + 1;
|
|
91
|
-
const relativePath = input.relativePath ?? input.filePath;
|
|
92
|
-
blocks.push({
|
|
93
|
-
id: `${relativePath}#code-${blockIndex}`,
|
|
94
|
-
filePath: input.filePath,
|
|
95
|
-
relativePath,
|
|
96
|
-
lineStart: openFence.lineStart,
|
|
97
|
-
lineEnd: index + 1,
|
|
98
|
-
language: parsed.language,
|
|
99
|
-
title: readStringMeta(meta, "title"),
|
|
100
|
-
framework: readStringMeta(meta, "framework"),
|
|
101
|
-
packageManager: readStringMeta(meta, "packageManager"),
|
|
102
|
-
runnable: readBooleanMeta(meta, "runnable") ?? false,
|
|
103
|
-
env: readEnvMeta(meta),
|
|
104
|
-
meta,
|
|
105
|
-
code: openFence.code.join("\n")
|
|
106
|
-
});
|
|
107
|
-
openFence = void 0;
|
|
108
|
-
continue;
|
|
109
|
-
}
|
|
110
|
-
openFence.code.push(line);
|
|
111
|
-
}
|
|
112
|
-
return blocks;
|
|
113
|
-
}
|
|
114
|
-
function collectCodeBlockTargets(options) {
|
|
115
|
-
const root = path.resolve(options.rootDir);
|
|
116
|
-
const contentRoot = path.resolve(root, options.contentDir);
|
|
117
|
-
if (!existsSync(contentRoot)) return [];
|
|
118
|
-
return walkMarkdownFiles(contentRoot).flatMap((filePath) => {
|
|
119
|
-
return extractCodeBlocksFromMarkdown({
|
|
120
|
-
source: readFileSync(filePath, "utf-8"),
|
|
121
|
-
filePath,
|
|
122
|
-
relativePath: path.relative(root, filePath).replace(/\\/g, "/")
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
async function planCodeBlockTargets(targets, config) {
|
|
127
|
-
if (config.planner.provider === "metadata") return targets.map((target) => buildMetadataExecutionPlan(target, config));
|
|
128
|
-
if (config.planner.provider === "cloud") throw new Error("Hosted cloud code block planning is not available in this package yet.");
|
|
129
|
-
return planWithOpenAICompatibleProvider(targets, config);
|
|
130
|
-
}
|
|
131
|
-
async function validateCodeBlockPlans(input) {
|
|
132
|
-
const validationEnv = loadValidationEnv(input.rootDir, input.config);
|
|
133
|
-
const preflight = input.plans.map((plan) => preflightPlan(plan, input.config, validationEnv));
|
|
134
|
-
const runnable = preflight.filter((result) => result.status !== "SKIP" && !result.reason);
|
|
135
|
-
const skippedOrFailed = preflight.filter((result) => result.status === "SKIP" || result.reason);
|
|
136
|
-
const plansToRun = runnable.map((result) => result.plan);
|
|
137
|
-
if (plansToRun.length === 0) return skippedOrFailed;
|
|
138
|
-
const runResults = await runPlansWithConfiguredRunner(plansToRun, input.rootDir, input.config, validationEnv.env);
|
|
139
|
-
return [...skippedOrFailed, ...runResults].sort((a, b) => a.id.localeCompare(b.id));
|
|
140
|
-
}
|
|
141
|
-
async function validateCodeBlocks(input) {
|
|
142
|
-
const targets = collectCodeBlockTargets({
|
|
143
|
-
rootDir: input.rootDir,
|
|
144
|
-
contentDir: input.contentDir
|
|
145
|
-
});
|
|
146
|
-
const plans = await planCodeBlockTargets(targets, input.config);
|
|
147
|
-
const results = input.planOnly || input.config.mode === "plan" ? plans.map((plan) => ({
|
|
148
|
-
id: plan.id,
|
|
149
|
-
target: plan.target,
|
|
150
|
-
plan,
|
|
151
|
-
status: plan.action === "skip" ? "SKIP" : "PLAN",
|
|
152
|
-
reason: plan.reason ?? (plan.action === "skip" ? "planned skip" : "planned")
|
|
153
|
-
})) : await validateCodeBlockPlans({
|
|
154
|
-
plans,
|
|
155
|
-
rootDir: input.rootDir,
|
|
156
|
-
config: input.config
|
|
157
|
-
});
|
|
158
|
-
return {
|
|
159
|
-
summary: results.reduce((acc, result) => {
|
|
160
|
-
acc.total += 1;
|
|
161
|
-
if (result.status === "PLAN") acc.planned += 1;
|
|
162
|
-
if (result.status === "PASS") acc.pass += 1;
|
|
163
|
-
if (result.status === "SKIP") acc.skip += 1;
|
|
164
|
-
if (result.status === "FAIL") acc.fail += 1;
|
|
165
|
-
return acc;
|
|
166
|
-
}, {
|
|
167
|
-
total: 0,
|
|
168
|
-
planned: 0,
|
|
169
|
-
pass: 0,
|
|
170
|
-
skip: 0,
|
|
171
|
-
fail: 0
|
|
172
|
-
}),
|
|
173
|
-
config: input.config,
|
|
174
|
-
targets,
|
|
175
|
-
plans,
|
|
176
|
-
results
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
function normalizePlannerConfig(input) {
|
|
180
|
-
if (!input) return { provider: "metadata" };
|
|
181
|
-
if (typeof input === "string") return { provider: input };
|
|
182
|
-
return {
|
|
183
|
-
provider: input.provider ?? "metadata",
|
|
184
|
-
...input
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
function normalizeRunnerConfig(input) {
|
|
188
|
-
const config = typeof input === "string" ? { provider: input } : input ?? {};
|
|
189
|
-
const provider = config.provider ?? "local";
|
|
190
|
-
return {
|
|
191
|
-
provider,
|
|
192
|
-
tokenEnv: config.tokenEnv ?? defaultRunnerTokenEnv(provider),
|
|
193
|
-
projectIdEnv: config.projectIdEnv ?? "VERCEL_PROJECT_ID",
|
|
194
|
-
teamIdEnv: config.teamIdEnv ?? "VERCEL_TEAM_ID",
|
|
195
|
-
projectJson: config.projectJson === void 0 ? ".vercel/project.json" : config.projectJson,
|
|
196
|
-
runtime: config.runtime ?? "node24",
|
|
197
|
-
apiUrlEnv: config.apiUrlEnv ?? "DAYTONA_API_URL",
|
|
198
|
-
targetEnv: config.targetEnv ?? "DAYTONA_TARGET",
|
|
199
|
-
timeoutMs: config.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
function defaultRunnerTokenEnv(provider) {
|
|
203
|
-
if (provider === "e2b") return "E2B_API_KEY";
|
|
204
|
-
if (provider === "daytona") return "DAYTONA_API_KEY";
|
|
205
|
-
return "VERCEL_TOKEN";
|
|
206
|
-
}
|
|
207
|
-
function buildMetadataExecutionPlan(target, config) {
|
|
208
|
-
const language = normalizeLanguage(target.language);
|
|
209
|
-
const template = target.framework ?? templateFromLanguage(language);
|
|
210
|
-
const requiredEnv = target.env;
|
|
211
|
-
if (readBooleanMeta(target.meta, "partial") || looksPartial(target.code)) return skipPlan(target, template, requiredEnv, "partial fragment", config.planner.provider);
|
|
212
|
-
if (!language) {
|
|
213
|
-
if (!target.runnable) return skipPlan(target, "unknown", requiredEnv, "code block is not marked runnable", config.planner.provider);
|
|
214
|
-
if (looksLikeShellCommand(target.code)) return shellPlan(target, "shell", requiredEnv, config.planner.provider);
|
|
215
|
-
return skipPlan(target, "unknown", requiredEnv, "missing language and not obviously runnable", config.planner.provider);
|
|
216
|
-
}
|
|
217
|
-
if (!target.runnable) return skipPlan(target, template, requiredEnv, "code block is not marked runnable", config.planner.provider);
|
|
218
|
-
if (isShellLanguage(language)) return shellPlan(target, template, requiredEnv, config.planner.provider);
|
|
219
|
-
if (language === "json") return syntaxPlan(target, "json", "node", requiredEnv, config.planner.provider);
|
|
220
|
-
if (language === "javascript" || language === "js" || language === "jsx") return executePlan(target, template, "node", "js", {
|
|
221
|
-
cmd: "node",
|
|
222
|
-
args: []
|
|
223
|
-
}, requiredEnv, config.planner.provider);
|
|
224
|
-
if (language === "typescript" || language === "ts" || language === "tsx") return executePlan(target, template, "tsx", "ts", {
|
|
225
|
-
cmd: "npx",
|
|
226
|
-
args: ["--yes", "tsx"]
|
|
227
|
-
}, requiredEnv, config.planner.provider);
|
|
228
|
-
if (language === "python" || language === "py") return executePlan(target, template, "python3", "py", {
|
|
229
|
-
cmd: "python3",
|
|
230
|
-
args: []
|
|
231
|
-
}, requiredEnv, config.planner.provider);
|
|
232
|
-
if (language === "ruby" || language === "rb") return executePlan(target, template, "ruby", "rb", {
|
|
233
|
-
cmd: "ruby",
|
|
234
|
-
args: []
|
|
235
|
-
}, requiredEnv, config.planner.provider);
|
|
236
|
-
if (language === "elixir" || language === "ex" || language === "exs") return executePlan(target, template, "elixir", "exs", {
|
|
237
|
-
cmd: "elixir",
|
|
238
|
-
args: []
|
|
239
|
-
}, requiredEnv, config.planner.provider);
|
|
240
|
-
if (language === "yaml" || language === "yml") return skipPlan(target, "yaml", requiredEnv, "YAML syntax validation requires a YAML parser", config.planner.provider);
|
|
241
|
-
if (!target.runnable) return skipPlan(target, template, requiredEnv, "code block is not marked runnable", config.planner.provider);
|
|
242
|
-
return skipPlan(target, template, requiredEnv, `unsupported language: ${target.language}`, config.planner.provider);
|
|
243
|
-
}
|
|
244
|
-
function executePlan(target, template, runtime, extension, command, requiredEnv, planner) {
|
|
245
|
-
const filePath = `snippet-${slugify(target.id)}.${extension}`;
|
|
246
|
-
return {
|
|
247
|
-
id: target.id,
|
|
248
|
-
target,
|
|
249
|
-
action: "execute",
|
|
250
|
-
template,
|
|
251
|
-
runtime,
|
|
252
|
-
filePath,
|
|
253
|
-
command: {
|
|
254
|
-
cmd: command.cmd,
|
|
255
|
-
args: [...command.args, filePath]
|
|
256
|
-
},
|
|
257
|
-
requiredEnv,
|
|
258
|
-
planner
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
function shellPlan(target, template, requiredEnv, planner) {
|
|
262
|
-
const filePath = `snippet-${slugify(target.id)}.sh`;
|
|
263
|
-
return {
|
|
264
|
-
id: target.id,
|
|
265
|
-
target,
|
|
266
|
-
action: "execute",
|
|
267
|
-
template,
|
|
268
|
-
runtime: "bash",
|
|
269
|
-
filePath,
|
|
270
|
-
command: {
|
|
271
|
-
cmd: "bash",
|
|
272
|
-
args: [filePath]
|
|
273
|
-
},
|
|
274
|
-
requiredEnv,
|
|
275
|
-
planner
|
|
276
|
-
};
|
|
277
|
-
}
|
|
278
|
-
function syntaxPlan(target, template, runtime, requiredEnv, planner) {
|
|
279
|
-
const filePath = `snippet-${slugify(target.id)}.json`;
|
|
280
|
-
return {
|
|
281
|
-
id: target.id,
|
|
282
|
-
target,
|
|
283
|
-
action: "validate-syntax",
|
|
284
|
-
template,
|
|
285
|
-
runtime,
|
|
286
|
-
filePath,
|
|
287
|
-
command: {
|
|
288
|
-
cmd: "node",
|
|
289
|
-
args: [
|
|
290
|
-
"-e",
|
|
291
|
-
`JSON.parse(require("node:fs").readFileSync(process.argv[1], "utf8"))`,
|
|
292
|
-
filePath
|
|
293
|
-
]
|
|
294
|
-
},
|
|
295
|
-
requiredEnv,
|
|
296
|
-
planner
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
function skipPlan(target, template, requiredEnv, reason, planner) {
|
|
300
|
-
return {
|
|
301
|
-
id: target.id,
|
|
302
|
-
target,
|
|
303
|
-
action: "skip",
|
|
304
|
-
template,
|
|
305
|
-
requiredEnv,
|
|
306
|
-
reason,
|
|
307
|
-
planner
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
async function planWithOpenAICompatibleProvider(targets, config) {
|
|
311
|
-
const baseUrl = config.planner.baseUrl ?? (config.planner.baseUrlEnv ? process.env[config.planner.baseUrlEnv] : void 0) ?? (config.planner.provider === "openai" ? "https://api.openai.com/v1" : void 0);
|
|
312
|
-
const apiKey = config.planner.apiKey ?? (config.planner.apiKeyEnv ? process.env[config.planner.apiKeyEnv] : void 0);
|
|
313
|
-
if (!baseUrl) throw new Error("codeBlocks.validate planner requires baseUrl or baseUrlEnv.");
|
|
314
|
-
if (!apiKey) throw new Error(`codeBlocks.validate planner requires an API key. Set ${config.planner.apiKeyEnv ?? "apiKeyEnv"} in your environment.`);
|
|
315
|
-
const metadataPlans = targets.map((target) => buildMetadataExecutionPlan(target, config));
|
|
316
|
-
const response = await fetch(`${baseUrl.replace(/\/+$/, "")}/chat/completions`, {
|
|
317
|
-
method: "POST",
|
|
318
|
-
headers: {
|
|
319
|
-
Authorization: `Bearer ${apiKey}`,
|
|
320
|
-
"Content-Type": "application/json"
|
|
321
|
-
},
|
|
322
|
-
body: JSON.stringify({
|
|
323
|
-
model: config.planner.model ?? "gpt-4.1-mini",
|
|
324
|
-
temperature: 0,
|
|
325
|
-
response_format: { type: "json_object" },
|
|
326
|
-
messages: [{
|
|
327
|
-
role: "system",
|
|
328
|
-
content: "You produce JSON execution plans for documentation code blocks. Do not rewrite snippets. If the snippet is partial, missing setup, or unsafe, choose action skip. Return only JSON."
|
|
329
|
-
}, {
|
|
330
|
-
role: "user",
|
|
331
|
-
content: JSON.stringify({
|
|
332
|
-
contract: { plans: "array of {id, action: execute|validate-syntax|skip, template, runtime?, reason?, requiredEnv?: string[]}" },
|
|
333
|
-
codeBlocks: targets.map((target) => ({
|
|
334
|
-
id: target.id,
|
|
335
|
-
language: target.language,
|
|
336
|
-
title: target.title,
|
|
337
|
-
framework: target.framework,
|
|
338
|
-
packageManager: target.packageManager,
|
|
339
|
-
runnable: target.runnable,
|
|
340
|
-
env: target.env,
|
|
341
|
-
meta: target.meta,
|
|
342
|
-
code: target.code.slice(0, 8e3)
|
|
343
|
-
}))
|
|
344
|
-
})
|
|
345
|
-
}]
|
|
346
|
-
})
|
|
347
|
-
});
|
|
348
|
-
if (!response.ok) throw new Error(`Planner request failed with ${response.status} ${response.statusText}.`);
|
|
349
|
-
const content = (await response.json()).choices?.[0]?.message?.content ?? "";
|
|
350
|
-
let parsed;
|
|
351
|
-
try {
|
|
352
|
-
parsed = JSON.parse(content);
|
|
353
|
-
} catch {
|
|
354
|
-
throw new Error("Planner returned non-JSON content.");
|
|
355
|
-
}
|
|
356
|
-
const byId = new Map(metadataPlans.map((plan) => [plan.id, plan]));
|
|
357
|
-
for (const plan of parsed.plans ?? []) {
|
|
358
|
-
if (!plan.id || !byId.has(plan.id)) continue;
|
|
359
|
-
const fallback = byId.get(plan.id);
|
|
360
|
-
byId.set(plan.id, {
|
|
361
|
-
...fallback,
|
|
362
|
-
action: isPlanAction(plan.action) ? plan.action : fallback.action,
|
|
363
|
-
template: typeof plan.template === "string" && plan.template ? plan.template : fallback.template,
|
|
364
|
-
runtime: typeof plan.runtime === "string" && plan.runtime ? plan.runtime : fallback.runtime,
|
|
365
|
-
requiredEnv: Array.isArray(plan.requiredEnv) ? plan.requiredEnv.filter((value) => typeof value === "string") : fallback.requiredEnv,
|
|
366
|
-
reason: typeof plan.reason === "string" ? plan.reason : fallback.reason,
|
|
367
|
-
planner: config.planner.provider
|
|
368
|
-
});
|
|
369
|
-
}
|
|
370
|
-
return metadataPlans.map((plan) => byId.get(plan.id));
|
|
371
|
-
}
|
|
372
|
-
function preflightPlan(plan, config, validationEnv) {
|
|
373
|
-
if (plan.action === "skip") return {
|
|
374
|
-
id: plan.id,
|
|
375
|
-
target: plan.target,
|
|
376
|
-
plan,
|
|
377
|
-
status: "SKIP",
|
|
378
|
-
reason: plan.reason
|
|
379
|
-
};
|
|
380
|
-
const missingEnv = plan.requiredEnv.filter((key) => !validationEnv.env[key]);
|
|
381
|
-
if (missingEnv.length > 0) {
|
|
382
|
-
const reason = `missing env: ${missingEnv.join(", ")}`;
|
|
383
|
-
return {
|
|
384
|
-
id: plan.id,
|
|
385
|
-
target: plan.target,
|
|
386
|
-
plan,
|
|
387
|
-
status: config.missingEnv === "error" ? "FAIL" : "SKIP",
|
|
388
|
-
reason
|
|
389
|
-
};
|
|
390
|
-
}
|
|
391
|
-
if (!plan.command || !plan.filePath) return {
|
|
392
|
-
id: plan.id,
|
|
393
|
-
target: plan.target,
|
|
394
|
-
plan,
|
|
395
|
-
status: config.unsupportedLanguage === "error" ? "FAIL" : "SKIP",
|
|
396
|
-
reason: "no executable command in plan"
|
|
397
|
-
};
|
|
398
|
-
return {
|
|
399
|
-
id: plan.id,
|
|
400
|
-
target: plan.target,
|
|
401
|
-
plan,
|
|
402
|
-
status: "PASS"
|
|
403
|
-
};
|
|
404
|
-
}
|
|
405
|
-
async function runPlansLocally(plans, config, env) {
|
|
406
|
-
const tempDir = mkdtempSync(path.join(tmpdir(), "docs-codeblocks-"));
|
|
407
|
-
try {
|
|
408
|
-
return await Promise.all(plans.map(async (plan) => {
|
|
409
|
-
if (!plan.command || !plan.filePath) return skippedResult(plan, "no executable command in plan");
|
|
410
|
-
writeFileSync(path.join(tempDir, plan.filePath), plan.target.code, "utf-8");
|
|
411
|
-
try {
|
|
412
|
-
const result = await execFileAsync(plan.command.cmd, plan.command.args, {
|
|
413
|
-
cwd: tempDir,
|
|
414
|
-
env: {
|
|
415
|
-
...process.env,
|
|
416
|
-
...env
|
|
417
|
-
},
|
|
418
|
-
timeout: config.runner.timeoutMs,
|
|
419
|
-
maxBuffer: 1024 * 1024
|
|
420
|
-
});
|
|
421
|
-
return {
|
|
422
|
-
id: plan.id,
|
|
423
|
-
target: plan.target,
|
|
424
|
-
plan,
|
|
425
|
-
status: "PASS",
|
|
426
|
-
stdout: result.stdout,
|
|
427
|
-
stderr: result.stderr,
|
|
428
|
-
exitCode: 0
|
|
429
|
-
};
|
|
430
|
-
} catch (error) {
|
|
431
|
-
const err = error;
|
|
432
|
-
return {
|
|
433
|
-
id: plan.id,
|
|
434
|
-
target: plan.target,
|
|
435
|
-
plan,
|
|
436
|
-
status: "FAIL",
|
|
437
|
-
stdout: err.stdout,
|
|
438
|
-
stderr: err.stderr,
|
|
439
|
-
exitCode: typeof err.code === "number" ? err.code : null,
|
|
440
|
-
reason: err.signal ? `terminated by ${err.signal}` : err.message
|
|
441
|
-
};
|
|
442
|
-
}
|
|
443
|
-
}));
|
|
444
|
-
} finally {
|
|
445
|
-
rmSync(tempDir, {
|
|
446
|
-
force: true,
|
|
447
|
-
recursive: true
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
async function runPlansWithConfiguredRunner(plans, rootDir, config, env) {
|
|
452
|
-
switch (config.runner.provider) {
|
|
453
|
-
case "local": return runPlansLocally(plans, config, env);
|
|
454
|
-
case "vercel-sandbox": return runPlansInVercelSandbox(plans, rootDir, config, env);
|
|
455
|
-
case "e2b": return runPlansInE2B(plans, config, env);
|
|
456
|
-
case "daytona": return runPlansInDaytona(plans, config, env);
|
|
457
|
-
case "cloud": return plans.map((plan) => skippedResult(plan, "cloud runner is not available in this package yet"));
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
async function runPlansInVercelSandbox(plans, rootDir, config, env) {
|
|
461
|
-
const credentials = await resolveVercelSandboxCredentials(rootDir, config);
|
|
462
|
-
if (credentials.missing.length > 0) return plans.map((plan) => skippedResult(plan, `missing ${credentials.missing.join(", ")}`));
|
|
463
|
-
try {
|
|
464
|
-
const { Sandbox } = await import("@vercel/sandbox");
|
|
465
|
-
const sandbox = await Sandbox.create({
|
|
466
|
-
runtime: config.runner.runtime,
|
|
467
|
-
timeout: Math.max(config.runner.timeoutMs * Math.max(1, plans.length), config.runner.timeoutMs),
|
|
468
|
-
env,
|
|
469
|
-
token: credentials.token,
|
|
470
|
-
projectId: credentials.projectId,
|
|
471
|
-
teamId: credentials.teamId
|
|
472
|
-
});
|
|
473
|
-
try {
|
|
474
|
-
return await Promise.all(plans.map(async (plan) => {
|
|
475
|
-
if (!plan.command || !plan.filePath) return skippedResult(plan, "no executable command in plan");
|
|
476
|
-
await sandbox.writeFiles([{
|
|
477
|
-
path: plan.filePath,
|
|
478
|
-
content: Buffer.from(plan.target.code)
|
|
479
|
-
}]);
|
|
480
|
-
const command = await sandbox.runCommand({
|
|
481
|
-
cmd: plan.command.cmd,
|
|
482
|
-
args: plan.command.args,
|
|
483
|
-
cwd: "/vercel/sandbox",
|
|
484
|
-
env
|
|
485
|
-
});
|
|
486
|
-
const [stdout, stderr] = await Promise.all([command.stdout(), command.stderr()]);
|
|
487
|
-
return {
|
|
488
|
-
id: plan.id,
|
|
489
|
-
target: plan.target,
|
|
490
|
-
plan,
|
|
491
|
-
status: command.exitCode === 0 ? "PASS" : "FAIL",
|
|
492
|
-
stdout,
|
|
493
|
-
stderr,
|
|
494
|
-
exitCode: command.exitCode,
|
|
495
|
-
reason: command.exitCode === 0 ? void 0 : `exit code ${command.exitCode}`
|
|
496
|
-
};
|
|
497
|
-
}));
|
|
498
|
-
} finally {
|
|
499
|
-
await sandbox.stop().catch(() => {});
|
|
500
|
-
}
|
|
501
|
-
} catch (error) {
|
|
502
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
503
|
-
return plans.map((plan) => skippedResult(plan, `vercel-sandbox unavailable: ${message}`));
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
async function runPlansInE2B(plans, config, env) {
|
|
507
|
-
const token = process.env[config.runner.tokenEnv];
|
|
508
|
-
if (!token) return plans.map((plan) => skippedResult(plan, `missing ${config.runner.tokenEnv}`));
|
|
509
|
-
const module = await importOptionalModule("e2b");
|
|
510
|
-
if (!module) return plans.map((plan) => skippedResult(plan, "e2b unavailable: install the \"e2b\" package"));
|
|
511
|
-
try {
|
|
512
|
-
const E2BSandbox = readProviderExport(module, "default") ?? readProviderExport(module, "Sandbox");
|
|
513
|
-
if (!hasCreateMethod(E2BSandbox)) return plans.map((plan) => skippedResult(plan, "e2b unavailable: missing Sandbox.create"));
|
|
514
|
-
const sandbox = await withTemporaryEnv("E2B_API_KEY", token, () => E2BSandbox.create());
|
|
515
|
-
try {
|
|
516
|
-
return await Promise.all(plans.map(async (plan) => {
|
|
517
|
-
if (!plan.command || !plan.filePath) return skippedResult(plan, "no executable command in plan");
|
|
518
|
-
const command = buildSandboxShellCommand(plan);
|
|
519
|
-
const runner = readObject(sandbox).commands;
|
|
520
|
-
if (!hasRunMethod(runner)) return skippedResult(plan, "e2b unavailable: missing commands.run");
|
|
521
|
-
try {
|
|
522
|
-
return sandboxCommandResult(plan, await runner.run(command, {
|
|
523
|
-
envs: env,
|
|
524
|
-
timeoutMs: config.runner.timeoutMs
|
|
525
|
-
}));
|
|
526
|
-
} catch (error) {
|
|
527
|
-
return failedSandboxResult(plan, error);
|
|
528
|
-
}
|
|
529
|
-
}));
|
|
530
|
-
} finally {
|
|
531
|
-
await cleanupProviderSandbox(sandbox);
|
|
532
|
-
}
|
|
533
|
-
} catch (error) {
|
|
534
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
535
|
-
return plans.map((plan) => skippedResult(plan, `e2b unavailable: ${message}`));
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
async function runPlansInDaytona(plans, config, env) {
|
|
539
|
-
const token = process.env[config.runner.tokenEnv];
|
|
540
|
-
if (!token) return plans.map((plan) => skippedResult(plan, `missing ${config.runner.tokenEnv}`));
|
|
541
|
-
const module = await importOptionalModule("@daytona/sdk");
|
|
542
|
-
if (!module) return plans.map((plan) => skippedResult(plan, "daytona unavailable: install the \"@daytona/sdk\" package"));
|
|
543
|
-
try {
|
|
544
|
-
const Daytona = readProviderExport(module, "Daytona");
|
|
545
|
-
if (!hasConstructor(Daytona)) return plans.map((plan) => skippedResult(plan, "daytona unavailable: missing Daytona SDK"));
|
|
546
|
-
const apiUrl = process.env[config.runner.apiUrlEnv];
|
|
547
|
-
const target = process.env[config.runner.targetEnv];
|
|
548
|
-
const daytona = new Daytona({
|
|
549
|
-
apiKey: token,
|
|
550
|
-
...apiUrl ? { apiUrl } : {},
|
|
551
|
-
...target ? { target } : {}
|
|
552
|
-
});
|
|
553
|
-
if (!hasCreateMethod(daytona)) return plans.map((plan) => skippedResult(plan, "daytona unavailable: missing create"));
|
|
554
|
-
const sandbox = await daytona.create({
|
|
555
|
-
ephemeral: true,
|
|
556
|
-
language: "typescript",
|
|
557
|
-
...Object.keys(env).length > 0 ? { envVars: env } : {}
|
|
558
|
-
});
|
|
559
|
-
try {
|
|
560
|
-
return await Promise.all(plans.map(async (plan) => {
|
|
561
|
-
if (!plan.command || !plan.filePath) return skippedResult(plan, "no executable command in plan");
|
|
562
|
-
const processApi = readObject(sandbox).process;
|
|
563
|
-
if (!hasExecuteCommandMethod(processApi)) return skippedResult(plan, "daytona unavailable: missing process.executeCommand");
|
|
564
|
-
try {
|
|
565
|
-
return sandboxCommandResult(plan, await processApi.executeCommand(buildSandboxShellCommand(plan)));
|
|
566
|
-
} catch (error) {
|
|
567
|
-
return failedSandboxResult(plan, error);
|
|
568
|
-
}
|
|
569
|
-
}));
|
|
570
|
-
} finally {
|
|
571
|
-
await cleanupProviderSandbox(sandbox);
|
|
572
|
-
await cleanupProviderSandbox(daytona);
|
|
573
|
-
}
|
|
574
|
-
} catch (error) {
|
|
575
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
576
|
-
return plans.map((plan) => skippedResult(plan, `daytona unavailable: ${message}`));
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
async function resolveVercelSandboxCredentials(rootDir, config) {
|
|
580
|
-
const projectJson = readVercelProjectJson(rootDir, config.runner.projectJson);
|
|
581
|
-
const token = process.env[config.runner.tokenEnv];
|
|
582
|
-
if (!token && process.env.VERCEL_OIDC_TOKEN) return { missing: [] };
|
|
583
|
-
if (!token) return { missing: [config.runner.tokenEnv] };
|
|
584
|
-
const envProjectId = process.env[config.runner.projectIdEnv];
|
|
585
|
-
const envTeamId = process.env[config.runner.teamIdEnv];
|
|
586
|
-
const discoveredProject = !(envProjectId && envTeamId) && !(projectJson?.projectId && projectJson?.orgId) ? await discoverVercelSandboxProject(token) : void 0;
|
|
587
|
-
const projectId = envProjectId ?? projectJson?.projectId ?? discoveredProject?.projectId;
|
|
588
|
-
const teamId = envTeamId ?? projectJson?.orgId ?? discoveredProject?.teamId;
|
|
589
|
-
return {
|
|
590
|
-
token,
|
|
591
|
-
projectId,
|
|
592
|
-
teamId,
|
|
593
|
-
missing: [projectId ? void 0 : "Vercel project id", teamId ? void 0 : "Vercel team id"].filter((value) => Boolean(value))
|
|
594
|
-
};
|
|
595
|
-
}
|
|
596
|
-
async function discoverVercelSandboxProject(token) {
|
|
597
|
-
try {
|
|
598
|
-
const response = await fetch("https://api.vercel.com/v9/projects?limit=1", { headers: { Authorization: `Bearer ${token}` } });
|
|
599
|
-
if (!response.ok) return void 0;
|
|
600
|
-
const project = (await response.json()).projects?.[0];
|
|
601
|
-
if (!project) return void 0;
|
|
602
|
-
return {
|
|
603
|
-
projectId: typeof project.id === "string" ? project.id : void 0,
|
|
604
|
-
teamId: typeof project.accountId === "string" ? project.accountId : void 0
|
|
605
|
-
};
|
|
606
|
-
} catch {
|
|
607
|
-
return;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
function readVercelProjectJson(rootDir, projectJson) {
|
|
611
|
-
if (projectJson === false) return void 0;
|
|
612
|
-
const fullPath = path.resolve(rootDir, projectJson);
|
|
613
|
-
if (!existsSync(fullPath)) return void 0;
|
|
614
|
-
try {
|
|
615
|
-
const parsed = JSON.parse(readFileSync(fullPath, "utf-8"));
|
|
616
|
-
return {
|
|
617
|
-
projectId: typeof parsed.projectId === "string" ? parsed.projectId : void 0,
|
|
618
|
-
orgId: typeof parsed.orgId === "string" ? parsed.orgId : void 0
|
|
619
|
-
};
|
|
620
|
-
} catch {
|
|
621
|
-
return;
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
async function importOptionalModule(specifier) {
|
|
625
|
-
const importer = globalThis.__DOCS_CODE_BLOCKS_MODULE_IMPORTER__ ?? ((id) => import(id));
|
|
626
|
-
try {
|
|
627
|
-
return await importer(specifier);
|
|
628
|
-
} catch {
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
function buildSandboxShellCommand(plan) {
|
|
633
|
-
const command = plan.command;
|
|
634
|
-
const filePath = plan.filePath;
|
|
635
|
-
if (!command || !filePath) return "";
|
|
636
|
-
const sandboxDir = "/tmp/docs-codeblocks";
|
|
637
|
-
const writeFileCommand = [
|
|
638
|
-
"printf",
|
|
639
|
-
"%s",
|
|
640
|
-
Buffer.from(plan.target.code, "utf-8").toString("base64"),
|
|
641
|
-
"|",
|
|
642
|
-
"base64",
|
|
643
|
-
"-d",
|
|
644
|
-
">",
|
|
645
|
-
filePath
|
|
646
|
-
].map((part) => part === "|" || part === ">" ? part : shellEscape(part)).join(" ");
|
|
647
|
-
return [
|
|
648
|
-
`mkdir -p ${shellEscape(sandboxDir)}`,
|
|
649
|
-
`cd ${shellEscape(sandboxDir)}`,
|
|
650
|
-
writeFileCommand,
|
|
651
|
-
shellJoin([command.cmd, ...command.args])
|
|
652
|
-
].join(" && ");
|
|
653
|
-
}
|
|
654
|
-
function shellJoin(parts) {
|
|
655
|
-
return parts.map(shellEscape).join(" ");
|
|
656
|
-
}
|
|
657
|
-
function shellEscape(value) {
|
|
658
|
-
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
659
|
-
}
|
|
660
|
-
async function withTemporaryEnv(key, value, callback) {
|
|
661
|
-
const previous = process.env[key];
|
|
662
|
-
process.env[key] = value;
|
|
663
|
-
try {
|
|
664
|
-
return await callback();
|
|
665
|
-
} finally {
|
|
666
|
-
if (previous === void 0) delete process.env[key];
|
|
667
|
-
else process.env[key] = previous;
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
function sandboxCommandResult(plan, result) {
|
|
671
|
-
const record = readObject(result);
|
|
672
|
-
const error = readStringValue(record.error);
|
|
673
|
-
const exitCode = readNumberValue(record.exitCode) ?? readNumberValue(record.code) ?? readNumberValue(record.exit_code) ?? (error ? 1 : 0);
|
|
674
|
-
const stdout = readStringValue(record.stdout) ?? readStringValue(record.result) ?? readStringValue(record.text) ?? "";
|
|
675
|
-
const stderr = readStringValue(record.stderr) ?? error ?? "";
|
|
676
|
-
return {
|
|
677
|
-
id: plan.id,
|
|
678
|
-
target: plan.target,
|
|
679
|
-
plan,
|
|
680
|
-
status: exitCode === 0 ? "PASS" : "FAIL",
|
|
681
|
-
stdout,
|
|
682
|
-
stderr,
|
|
683
|
-
exitCode,
|
|
684
|
-
reason: exitCode === 0 ? void 0 : `exit code ${exitCode}`
|
|
685
|
-
};
|
|
686
|
-
}
|
|
687
|
-
function failedSandboxResult(plan, error) {
|
|
688
|
-
const record = readObject(error);
|
|
689
|
-
return {
|
|
690
|
-
id: plan.id,
|
|
691
|
-
target: plan.target,
|
|
692
|
-
plan,
|
|
693
|
-
status: "FAIL",
|
|
694
|
-
stdout: readStringValue(record.stdout),
|
|
695
|
-
stderr: readStringValue(record.stderr),
|
|
696
|
-
exitCode: readNumberValue(record.exitCode) ?? readNumberValue(record.code) ?? null,
|
|
697
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
698
|
-
};
|
|
699
|
-
}
|
|
700
|
-
async function cleanupProviderSandbox(value) {
|
|
701
|
-
const record = readObject(value);
|
|
702
|
-
for (const methodName of [
|
|
703
|
-
"kill",
|
|
704
|
-
"close",
|
|
705
|
-
"stop",
|
|
706
|
-
"delete",
|
|
707
|
-
"remove",
|
|
708
|
-
"destroy"
|
|
709
|
-
]) {
|
|
710
|
-
const method = record[methodName];
|
|
711
|
-
if (typeof method !== "function") continue;
|
|
712
|
-
await Promise.resolve(method.call(value)).catch(() => {});
|
|
713
|
-
return;
|
|
714
|
-
}
|
|
715
|
-
}
|
|
716
|
-
function readProviderExport(module, name) {
|
|
717
|
-
return readObject(module)[name];
|
|
718
|
-
}
|
|
719
|
-
function readObject(value) {
|
|
720
|
-
return value !== null && (typeof value === "object" || typeof value === "function") ? value : {};
|
|
721
|
-
}
|
|
722
|
-
function readStringValue(value) {
|
|
723
|
-
return typeof value === "string" ? value : void 0;
|
|
724
|
-
}
|
|
725
|
-
function readNumberValue(value) {
|
|
726
|
-
return typeof value === "number" ? value : void 0;
|
|
727
|
-
}
|
|
728
|
-
function hasCreateMethod(value) {
|
|
729
|
-
return typeof readObject(value).create === "function";
|
|
730
|
-
}
|
|
731
|
-
function hasRunMethod(value) {
|
|
732
|
-
return typeof readObject(value).run === "function";
|
|
733
|
-
}
|
|
734
|
-
function hasExecuteCommandMethod(value) {
|
|
735
|
-
return typeof readObject(value).executeCommand === "function";
|
|
736
|
-
}
|
|
737
|
-
function hasConstructor(value) {
|
|
738
|
-
return typeof value === "function";
|
|
739
|
-
}
|
|
740
|
-
function loadValidationEnv(rootDir, config) {
|
|
741
|
-
const fileEnv = {};
|
|
742
|
-
for (const file of config.envFile) {
|
|
743
|
-
const fullPath = path.resolve(rootDir, file);
|
|
744
|
-
if (!existsSync(fullPath)) continue;
|
|
745
|
-
Object.assign(fileEnv, parseEnvFile(readFileSync(fullPath, "utf-8")));
|
|
746
|
-
}
|
|
747
|
-
const resolved = {};
|
|
748
|
-
const missing = [];
|
|
749
|
-
for (const [runtimeKey, sourceKey] of Object.entries(config.env)) {
|
|
750
|
-
const value = process.env[sourceKey] ?? fileEnv[sourceKey];
|
|
751
|
-
if (value === void 0) {
|
|
752
|
-
missing.push(runtimeKey);
|
|
753
|
-
continue;
|
|
754
|
-
}
|
|
755
|
-
resolved[runtimeKey] = value;
|
|
756
|
-
}
|
|
757
|
-
return {
|
|
758
|
-
env: resolved,
|
|
759
|
-
missing
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
function parseEnvFile(source) {
|
|
763
|
-
const env = {};
|
|
764
|
-
for (const line of source.split(/\r?\n/)) {
|
|
765
|
-
const trimmed = line.trim();
|
|
766
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
767
|
-
const equals = trimmed.indexOf("=");
|
|
768
|
-
if (equals <= 0) continue;
|
|
769
|
-
const key = trimmed.slice(0, equals).trim();
|
|
770
|
-
env[key] = trimmed.slice(equals + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
771
|
-
}
|
|
772
|
-
return env;
|
|
773
|
-
}
|
|
774
|
-
function readStringMeta(meta, key) {
|
|
775
|
-
const value = meta[key];
|
|
776
|
-
return typeof value === "string" && value.trim().length > 0 ? value : void 0;
|
|
777
|
-
}
|
|
778
|
-
function readBooleanMeta(meta, key) {
|
|
779
|
-
const value = meta[key];
|
|
780
|
-
if (typeof value === "boolean") return value;
|
|
781
|
-
if (typeof value !== "string") return void 0;
|
|
782
|
-
const normalized = value.trim().toLowerCase();
|
|
783
|
-
if (!normalized || normalized === "true" || normalized === "1" || normalized === "yes") return true;
|
|
784
|
-
if (normalized === "false" || normalized === "0" || normalized === "no") return false;
|
|
785
|
-
return true;
|
|
786
|
-
}
|
|
787
|
-
function readEnvMeta(meta) {
|
|
788
|
-
const direct = meta.env;
|
|
789
|
-
const values = /* @__PURE__ */ new Set();
|
|
790
|
-
if (typeof direct === "string") for (const item of direct.split(/[,\s]+/)) {
|
|
791
|
-
const trimmed = item.trim();
|
|
792
|
-
if (trimmed) values.add(trimmed);
|
|
793
|
-
}
|
|
794
|
-
for (const [key, value] of Object.entries(meta)) {
|
|
795
|
-
if (!key.startsWith("env:") && !key.startsWith("env.")) continue;
|
|
796
|
-
const envName = key.slice(4).trim();
|
|
797
|
-
if (envName && value !== false) values.add(envName);
|
|
798
|
-
}
|
|
799
|
-
return [...values];
|
|
800
|
-
}
|
|
801
|
-
function walkMarkdownFiles(root) {
|
|
802
|
-
const files = [];
|
|
803
|
-
const ignored = new Set([
|
|
804
|
-
".git",
|
|
805
|
-
".next",
|
|
806
|
-
".nuxt",
|
|
807
|
-
".svelte-kit",
|
|
808
|
-
"dist",
|
|
809
|
-
"node_modules",
|
|
810
|
-
"out"
|
|
811
|
-
]);
|
|
812
|
-
function visit(dir) {
|
|
813
|
-
for (const entry of readdirSync(dir)) {
|
|
814
|
-
if (ignored.has(entry)) continue;
|
|
815
|
-
const fullPath = path.join(dir, entry);
|
|
816
|
-
if (statSync(fullPath).isDirectory()) visit(fullPath);
|
|
817
|
-
else if (/\.(?:md|mdx)$/i.test(entry)) files.push(fullPath);
|
|
818
|
-
}
|
|
819
|
-
}
|
|
820
|
-
visit(root);
|
|
821
|
-
return files.sort();
|
|
822
|
-
}
|
|
823
|
-
function normalizeLanguage(language) {
|
|
824
|
-
return language?.trim().toLowerCase();
|
|
825
|
-
}
|
|
826
|
-
function isClosingFence(trimmedLine, marker) {
|
|
827
|
-
return new RegExp(`^${marker[0]}{${marker.length},}[ \\t]*$`).test(trimmedLine);
|
|
828
|
-
}
|
|
829
|
-
function isShellLanguage(language) {
|
|
830
|
-
return [
|
|
831
|
-
"bash",
|
|
832
|
-
"sh",
|
|
833
|
-
"shell",
|
|
834
|
-
"zsh",
|
|
835
|
-
"curl"
|
|
836
|
-
].includes(language);
|
|
837
|
-
}
|
|
838
|
-
function templateFromLanguage(language) {
|
|
839
|
-
if (!language) return "unknown";
|
|
840
|
-
if (isShellLanguage(language)) return "shell";
|
|
841
|
-
if (language === "js" || language === "javascript" || language === "jsx") return "node";
|
|
842
|
-
if (language === "ts" || language === "typescript" || language === "tsx") return "typescript";
|
|
843
|
-
if (language === "py" || language === "python") return "python";
|
|
844
|
-
return language;
|
|
845
|
-
}
|
|
846
|
-
function looksLikeShellCommand(code) {
|
|
847
|
-
return /^(?:\s*(?:npm|pnpm|yarn|bun|npx|curl|git|node|python3?|deno|uv|pip)\b)/m.test(code);
|
|
848
|
-
}
|
|
849
|
-
function looksPartial(code) {
|
|
850
|
-
const trimmed = code.trim();
|
|
851
|
-
if (!trimmed) return true;
|
|
852
|
-
return /^\.\.\.$/m.test(trimmed) || /\/\/\s*\.\.\.|#\s*\.\.\./.test(trimmed);
|
|
853
|
-
}
|
|
854
|
-
function slugify(value) {
|
|
855
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "snippet";
|
|
856
|
-
}
|
|
857
|
-
function isPlanAction(value) {
|
|
858
|
-
return value === "execute" || value === "validate-syntax" || value === "skip";
|
|
859
|
-
}
|
|
860
|
-
function skippedResult(plan, reason) {
|
|
861
|
-
return {
|
|
862
|
-
id: plan.id,
|
|
863
|
-
target: plan.target,
|
|
864
|
-
plan,
|
|
865
|
-
status: "SKIP",
|
|
866
|
-
reason
|
|
867
|
-
};
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
//#endregion
|
|
871
|
-
export { validateCodeBlocks as a, validateCodeBlockPlans as i, planCodeBlockTargets as n, resolveDocsCodeBlocksValidateConfig as r, extractCodeBlocksFromMarkdown as t };
|