-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Expand file tree
/
Copy pathbundleSdkDocs.ts
More file actions
131 lines (113 loc) · 5.1 KB
/
Copy pathbundleSdkDocs.ts
File metadata and controls
131 lines (113 loc) · 5.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import * as fs from "node:fs/promises";
import * as path from "node:path";
// Snapshots the user-facing docs into the SDK package, so AI coding agents can read the
// version-pinned reference directly from node_modules (zero drift). Run as part of
// `@trigger.dev/sdk`'s build, from the package dir.
//
// The manifest is the entire "Documentation" dropdown in `docs/docs.json` (the
// "Resources for Trigger.dev" tab) — every page under it is bundled. Add a page to that
// nav and it ships automatically; nothing else to edit. The API reference and
// Guides & examples dropdowns are intentionally not bundled. Skills reference into this
// set by path; their `sources:` frontmatter is informational and no longer drives bundling.
//
// Layout: nav page `tasks/overview` is copied from `docs/tasks/overview.mdx` to
// `<sdk>/docs/tasks/overview.mdx`, so a skill at `<sdk>/skills/<name>/SKILL.md` reaches it
// at `../../docs/tasks/overview.mdx` and an agent reaches it at `@trigger.dev/sdk/docs/...`.
const packageDir = process.cwd(); // packages/trigger-sdk when run from the SDK build
const repoRoot = path.resolve(packageDir, "..", "..");
const docsRoot = path.join(repoRoot, "docs");
const outDir = path.join(packageDir, "docs");
const DROPDOWN = "Documentation";
/** Recursively collect every page path under a docs.json nav node (groups -> pages, nested). */
function collectPages(node: unknown): string[] {
const out: string[] = [];
if (node && typeof node === "object") {
const n = node as { groups?: unknown[]; pages?: unknown[] };
for (const g of n.groups ?? []) out.push(...collectPages(g));
for (const p of n.pages ?? []) {
if (typeof p === "string") out.push(p);
else out.push(...collectPages(p));
}
}
return out;
}
async function collectManifest(): Promise<string[]> {
const docsJson = JSON.parse(await fs.readFile(path.join(docsRoot, "docs.json"), "utf8"));
const dropdowns: Array<{ dropdown?: string }> = docsJson?.navigation?.dropdowns ?? [];
const documentation = dropdowns.find((d) => d.dropdown === DROPDOWN);
if (!documentation) {
throw new Error(`[bundleSdkDocs] "${DROPDOWN}" dropdown not found in docs/docs.json`);
}
// Page paths are root-relative without extension (e.g. "tasks/overview"); map to docs/*.mdx.
return [...new Set(collectPages(documentation))];
}
async function bundleSdkDocs() {
// When the SDK is built as a dependency inside a pruned workspace (e.g. the webapp Docker
// image), the repo-level docs/ tree is a separate workspace package that isn't part of that
// build's dependency graph, so it isn't present. The SDK isn't being published there, so
// there's nothing to bundle: skip rather than fail. Publishing always runs from the full
// monorepo where docs/ exists, so the guards below still protect releases.
try {
await fs.access(docsRoot);
} catch {
console.log(`[bundleSdkDocs] docs/ not present at ${docsRoot}; skipping (pruned build)`);
return;
}
const manifest = await collectManifest();
if (manifest.length === 0) {
// The nav structure changed shape; refuse to ship the SDK with no docs.
throw new Error(`[bundleSdkDocs] no pages found under the "${DROPDOWN}" dropdown`);
}
// Rebuild from scratch so removed pages don't linger in the package.
await fs.rm(outDir, { recursive: true, force: true });
const missing: string[] = [];
let copied = 0;
for (const rel of manifest) {
// Defensive: nav paths come from our own docs.json and are URL-style, but a
// fat-fingered `../`, a backslash, or an absolute path shouldn't be able to copy a
// file from outside docs/ into the package. Reject backslashes (Windows separator)
// and both POSIX and Windows absolute forms, then the normalized `..` traversal.
const safeRel = path.posix.normalize(rel);
if (
rel.includes("\\") ||
path.posix.isAbsolute(rel) ||
path.win32.isAbsolute(rel) ||
safeRel.startsWith("..")
) {
throw new Error(`[bundleSdkDocs] invalid nav path "${rel}" under "${DROPDOWN}"`);
}
const src = path.join(docsRoot, `${safeRel}.mdx`);
try {
await fs.access(src);
} catch {
// A nav entry pointing at a nonexistent page is a docs-nav issue, not a bundler one.
// Warn and skip rather than fail the SDK build.
missing.push(rel);
continue;
}
const dest = path.join(outDir, `${safeRel}.mdx`);
await fs.mkdir(path.dirname(dest), { recursive: true });
await fs.copyFile(src, dest);
copied++;
}
if (missing.length > 0) {
console.warn(
`[bundleSdkDocs] ${missing.length} "${DROPDOWN}" nav page(s) have no .mdx and were skipped:\n` +
missing.map((m) => ` - ${m}`).join("\n")
);
}
if (copied === 0) {
// Every nav page was missing on disk; refuse to ship the SDK with an empty docs bundle.
throw new Error(`[bundleSdkDocs] 0 docs copied from the "${DROPDOWN}" nav; refusing empty docs bundle`);
}
console.log(
`[bundleSdkDocs] bundled ${copied} docs from the "${DROPDOWN}" nav into ${path.relative(
repoRoot,
outDir
)}`
);
}
bundleSdkDocs().catch((e) => {
console.error(e);
process.exit(1);
});