@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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/package.json +11 -1
  3. package/dist/agent-BFqyqEnC.mjs +0 -4547
  4. package/dist/agent-DlxriaTs.mjs +0 -624
  5. package/dist/agent-evals-kJs2Y9xR.mjs +0 -2144
  6. package/dist/agent-export-BgUaiW8f.mjs +0 -869
  7. package/dist/agent-scope-CCaIY1aK.mjs +0 -283
  8. package/dist/agents-Djh-HXih.mjs +0 -219
  9. package/dist/analytics-Bx44lg6d.mjs +0 -177
  10. package/dist/cli/index.d.mts +0 -15
  11. package/dist/cli/index.mjs +0 -452
  12. package/dist/client/react.d.mts +0 -45
  13. package/dist/client/react.mjs +0 -223
  14. package/dist/cloud-BH_sHX64.mjs +0 -1615
  15. package/dist/cloud-analytics-CSyFE6SS.mjs +0 -132
  16. package/dist/cloud-ask-ai-B2WnG4fF.d.mts +0 -23
  17. package/dist/cloud-ask-ai-hnJfj8-X.mjs +0 -382
  18. package/dist/code-blocks-qe0T8-xe.mjs +0 -871
  19. package/dist/codeblocks-Bq67u32v.mjs +0 -250
  20. package/dist/config-DASewQ0x.mjs +0 -363
  21. package/dist/dev-DgY5xGl9.mjs +0 -1333
  22. package/dist/docs-cloud-server.d.mts +0 -70
  23. package/dist/docs-cloud-server.mjs +0 -310
  24. package/dist/doctor-CO1VMcF_.mjs +0 -1906
  25. package/dist/downgrade-BZs86NVr.mjs +0 -184
  26. package/dist/errors-CVqZ3kOO.mjs +0 -20
  27. package/dist/golden-evaluations-BN9u2wxw.mjs +0 -1483
  28. package/dist/i18n-CAlj1ADU.mjs +0 -40
  29. package/dist/index.d.mts +0 -1099
  30. package/dist/index.mjs +0 -9
  31. package/dist/init-Bd_k06bR.mjs +0 -1233
  32. package/dist/mcp-B_yXL5G5.mjs +0 -137
  33. package/dist/mcp.d.mts +0 -287
  34. package/dist/mcp.mjs +0 -4135
  35. package/dist/metadata-BDuewuzq.mjs +0 -237
  36. package/dist/package-version-qik_4J6C.mjs +0 -128
  37. package/dist/reading-time-BkEft6SD.mjs +0 -741
  38. package/dist/review-NC-sOdXn.mjs +0 -665
  39. package/dist/robots-DskPvGPw.mjs +0 -178
  40. package/dist/robots-ltltiLJF.mjs +0 -197
  41. package/dist/search-C1JitPwi.d.mts +0 -397
  42. package/dist/search-D57JXQLj.mjs +0 -1758
  43. package/dist/search-o4Ud6OXv.mjs +0 -102
  44. package/dist/server.d.mts +0 -341
  45. package/dist/server.mjs +0 -11
  46. package/dist/sitemap-Cq-Yj_iA.mjs +0 -247
  47. package/dist/sitemap-server-C1ibVKOy.mjs +0 -1137
  48. package/dist/templates-DNw15P-x.mjs +0 -2373
  49. package/dist/types-XHABMh_f.d.mts +0 -3248
  50. package/dist/upgrade-BCJTCW3O.mjs +0 -56
  51. 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 };