Skip to content

Commit c9d18ee

Browse files
author
abrulic
committed
test new generate-docs??
1 parent 9cd4f41 commit c9d18ee

1 file changed

Lines changed: 99 additions & 123 deletions

File tree

scripts/generate-docs.ts

Lines changed: 99 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { type ExecSyncOptions, execSync } from "node:child_process"
22
import { cpSync, existsSync, mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
33
import os from "node:os"
4-
import { join, relative, resolve } from "node:path"
4+
import path, { join, resolve, posix as pathPosix } from "node:path"
55
import { parseArgs } from "node:util"
66
import chalk from "chalk"
77
import semver from "semver"
8+
import { getServerEnv } from "~/env.server"
89

910
type RunOpts = { cwd?: string; inherit?: boolean }
1011
function run(cmd: string, opts: RunOpts = {}) {
@@ -31,32 +32,19 @@ const resetDir = (p: string) => {
3132
}
3233

3334
const contentDir = "content"
34-
const workspaceRoot = process.cwd()
35-
const outputDir = resolve(workspaceRoot, "generated-docs")
36-
37-
// If this script is executed from a subdirectory of the repository (for example
38-
// the `docs/` package inside a monorepo), we need the path of that subdir
39-
// relative to the repository root so worktrees point at the right package.
40-
let repoRoot = workspaceRoot
41-
let workspaceRelativePath = ""
35+
const outputDir = "generated-docs"
36+
const APP_ENV = getServerEnv().APP_ENV as "development" | "production"
37+
const currentDocsWorkspace = process.cwd()
38+
39+
let docsRelative = ""
4240
try {
43-
// This will return the repository top-level directory.
44-
repoRoot = run("git rev-parse --show-toplevel")
45-
// Compute the path from repo root to the current working dir. If empty,
46-
// we are at repo root and no adjustment is needed.
47-
// Use posix-style join/resolve via path utilities already imported.
48-
workspaceRelativePath = repoRoot === workspaceRoot ? "" : relative(repoRoot, workspaceRoot)
41+
docsRelative = run("git rev-parse --show-prefix", { cwd: currentDocsWorkspace }).replace(/\/?$/, "")
4942
} catch {
50-
// If git is not available or the command fails, fall back to assuming the
51-
// current working directory is the repository root.
52-
repoRoot = workspaceRoot
53-
workspaceRelativePath = ""
43+
docsRelative = ""
5444
}
5545

56-
// biome-ignore lint/suspicious/noConsole: TODO remove this
57-
console.log(chalk.cyan(`Docs workspace root: ${workspaceRoot}`))
58-
// biome-ignore lint/suspicious/noConsole: TODO remove this
59-
console.log("outputDir:", outputDir)
46+
const repoPath = (...segs: string[]) => path.normalize(path.join(...segs.filter(Boolean)))
47+
6048
const allTags = () => run("git tag --list").split("\n").filter(Boolean)
6149

6250
function resolveTagsFromSpec(spec: string) {
@@ -71,6 +59,16 @@ function resolveTagsFromSpec(spec: string) {
7159
return matched.sort(semver.rcompare)
7260
}
7361

62+
function detectDefaultBranch() {
63+
try {
64+
const ref = run("git symbolic-ref refs/remotes/origin/HEAD")
65+
const parts = ref.split("/")
66+
return parts[parts.length - 1]
67+
} catch {
68+
throw new Error("Cannot detect default branch from origin/HEAD")
69+
}
70+
}
71+
7472
function hasLocalRef(ref: string) {
7573
try {
7674
run(`git show-ref --verify --quiet ${ref}`)
@@ -80,51 +78,37 @@ function hasLocalRef(ref: string) {
8078
}
8179
}
8280

83-
function buildDocs(sourceDir: string, outDir: string) {
84-
if (!existsSync(sourceDir)) {
85-
throw new Error(
86-
`❌ Documentation workspace not found at: ${sourceDir}
87-
Cannot build documentation without a valid workspace directory.`
88-
)
81+
const REPO_ROOT = run("git rev-parse --show-toplevel", { cwd: currentDocsWorkspace })
82+
83+
function refHasPath(ref: string, pathFromRepoRoot: string): boolean {
84+
const p = pathFromRepoRoot.replace(/^\/+/, "").replace(/\/+$/, "")
85+
try {
86+
run(`git -C "${REPO_ROOT}" rev-parse --verify --quiet "${ref}:${p}"`)
87+
return true
88+
} catch {
89+
return false
8990
}
91+
}
92+
93+
function buildDocs(sourceDir: string, outDir: string) {
94+
if (!existsSync(sourceDir)) throw new Error(`Docs workspace not found: ${sourceDir}`)
9095

91-
// biome-ignore lint/suspicious/noConsole: TODO remove this
92-
console.log(chalk.cyan(`Building docs from: ${sourceDir}${outDir}`))
9396
const docsContentDir = resolve(sourceDir, contentDir)
9497
if (!existsSync(docsContentDir)) {
95-
throw new Error(
96-
`❌ Content directory "${contentDir}" not found at: ${docsContentDir}
97-
Cannot build documentation without content files.
98-
Please ensure you have a "${contentDir}/" directory with your documentation content.`
99-
)
100-
}
101-
102-
const packageJsonPath = resolve(sourceDir, "package.json")
103-
if (!existsSync(packageJsonPath)) {
104-
throw new Error(
105-
`❌ package.json not found at: ${packageJsonPath}
106-
Cannot build documentation without package.json.
107-
Please ensure your workspace has a valid package.json file.`
108-
)
98+
throw new Error(`Docs content directory "${contentDir}" not found at ${docsContentDir}`)
10999
}
110100

111101
resetDir(outDir)
112102
run("pnpm run content-collections:build", { cwd: sourceDir, inherit: true })
113103

114104
const ccSrc = resolve(sourceDir, ".content-collections")
115105
const ccDest = join(outDir, ".content-collections")
116-
if (!existsSync(ccSrc)) {
117-
throw new Error(
118-
`❌ Build output missing at: ${ccSrc}
119-
Content collections build failed or did not produce output.
120-
Please check the build logs above for errors.`
121-
)
122-
}
106+
if (!existsSync(ccSrc)) throw new Error(`Build output missing at ${ccSrc}`)
123107

124108
resetDir(ccDest)
125109
cpSync(ccSrc, ccDest, { recursive: true })
126110

127-
// biome-ignore lint/suspicious/noConsole: keep for debugging
111+
// biome-ignore lint/suspicious/noConsole: <explanation>
128112
console.log(chalk.green(`✔ Built docs → ${ccDest}`))
129113
}
130114

@@ -134,12 +118,13 @@ function buildRef(ref: string, labelForOutDir: string) {
134118
const worktreePath = resolve(tmpBase, safeLabel)
135119

136120
run(`git worktree add --detach "${worktreePath}" "${ref}"`, {
137-
cwd: workspaceRoot,
121+
cwd: currentDocsWorkspace,
138122
inherit: true,
139123
})
140124

141125
try {
142-
// Install dependencies at root if package.json exists
126+
const docsWorkspace = docsRelative ? resolve(worktreePath, docsRelative) : worktreePath
127+
143128
const rootPkg = existsSync(resolve(worktreePath, "package.json"))
144129
const rootLock = existsSync(resolve(worktreePath, "pnpm-lock.yaml"))
145130
if (rootPkg) {
@@ -149,15 +134,17 @@ function buildRef(ref: string, labelForOutDir: string) {
149134
})
150135
}
151136

152-
// If this script was run from a subdirectory (for example `docs/` inside
153-
// a monorepo), adjust the sourceDir inside the created worktree so we
154-
// build the correct package.
155-
const sourceDir = workspaceRelativePath ? resolve(worktreePath, workspaceRelativePath) : worktreePath
137+
const docsPkg = existsSync(resolve(docsWorkspace, "package.json"))
138+
const docsLock = existsSync(resolve(docsWorkspace, "pnpm-lock.yaml"))
139+
if (docsPkg && docsLock) {
140+
run("pnpm install --frozen-lockfile", { cwd: docsWorkspace, inherit: true })
141+
}
142+
156143
const outDir = resolve(outputDir, labelForOutDir)
157-
buildDocs(sourceDir, outDir)
144+
buildDocs(docsWorkspace, outDir)
158145
} finally {
159146
run(`git worktree remove "${worktreePath}" --force`, {
160-
cwd: workspaceRoot,
147+
cwd: currentDocsWorkspace,
161148
inherit: true,
162149
})
163150
rmSync(tmpBase, { recursive: true, force: true })
@@ -166,7 +153,7 @@ function buildRef(ref: string, labelForOutDir: string) {
166153

167154
function buildBranch(branch: string, labelForOutDir: string) {
168155
run(`git fetch --tags --prune origin ${branch}`, {
169-
cwd: workspaceRoot,
156+
cwd: currentDocsWorkspace,
170157
inherit: true,
171158
})
172159
const localRef = `refs/heads/${branch}`
@@ -178,99 +165,88 @@ function buildTag(tag: string) {
178165
return buildRef(`refs/tags/${tag}`, tag)
179166
}
180167

181-
function getCurrentBranch(): string {
182-
try {
183-
return run("git rev-parse --abbrev-ref HEAD")
184-
} catch {
185-
throw new Error("Failed to get current branch")
186-
}
168+
function isPullRequestCI() {
169+
return process.env.GITHUB_EVENT_NAME === "pull_request" || !!process.env.GITHUB_HEAD_REF
187170
}
188171

189-
function isOnDefaultBranch(defaultBranch: string): boolean {
190-
const currentBranch = getCurrentBranch()
191-
return currentBranch === defaultBranch
192-
}
193172
;(async () => {
194173
const { values } = parseArgs({
195174
args: process.argv.slice(2),
196175
options: {
197-
versions: { type: "string" }, // optional: comma/space list of semver ranges or exact tags
198-
branch: { type: "string" }, // required: specify default branch name (e.g., main, master, develop)
176+
versions: { type: "string" }, // optional: comma/space list of semver ranges or exact tags (e.g. "v1.0.0, v1.1.x")
177+
branch: { type: "string" }, // optional: default branch override (e.g. main)
199178
},
200179
})
201180

202-
// Get default branch from --branch flag (required)
203-
const defaultBranch = (values.branch as string | undefined)?.trim()
204-
if (!defaultBranch) {
205-
throw new Error(
206-
`❌ Missing required --branch flag.
207-
Please specify the default branch name (e.g., --branch main)
208-
Example: pnpm run generate:docs --branch main`
209-
)
210-
}
181+
const defaultBranch =
182+
(values.branch as string | undefined)?.trim() || detectDefaultBranch()
211183

212184
const rawVersions = (values.versions as string | undefined)?.trim() ?? ""
213185
const hasVersionsArg = rawVersions.length > 0
214186

215187
let builtVersions: string[] = []
216188

217-
// Determine which branch to build as "current"
218-
const onDefaultBranch = isOnDefaultBranch(defaultBranch)
219-
const currentBranchToBuild = onDefaultBranch ? defaultBranch : getCurrentBranch()
220-
221-
// biome-ignore lint/suspicious/noConsole: keep for logging
222-
console.log(chalk.cyan(`Building from branch: ${currentBranchToBuild}`))
223-
224-
if (hasVersionsArg) {
225-
// Build specified version tags + current branch
226-
const tags = resolveTagsFromSpec(rawVersions)
227-
if (!tags.length) throw new Error(`No tags matched spec "${rawVersions}".`)
228-
229-
// biome-ignore lint/suspicious/noConsole: keep for logging
230-
console.log(chalk.cyan(`Building tags: ${tags.join(", ")}`))
231-
for (const t of tags) buildTag(t)
232-
233-
// Build current branch
234-
if (onDefaultBranch) {
235-
// biome-ignore lint/suspicious/noConsole: keep for logging
236-
console.log(chalk.cyan(`Building default branch '${defaultBranch}' → current`))
237-
buildBranch(defaultBranch, "current")
189+
if (APP_ENV === "development") {
190+
// Local dev: always build the current workspace → current
191+
console.log(chalk.cyan(`(dev) Building docs from current workspace: ${currentDocsWorkspace} → current`))
192+
buildDocs(currentDocsWorkspace, join(outputDir, "current"))
193+
builtVersions = ["current"]
194+
} else if (isPullRequestCI()) {
195+
// PR builds
196+
if (hasVersionsArg) {
197+
// Build PR content as "current" + requested tags
198+
console.log(chalk.cyan(`(pr) Building PR docs → current`))
199+
buildDocs(currentDocsWorkspace, join(outputDir, "current"))
200+
201+
const tags = resolveTagsFromSpec(rawVersions)
202+
if (!tags.length) throw new Error(`No tags matched spec "${rawVersions}".`)
203+
console.log(chalk.cyan(`(pr) Also building tags: ${tags.join(", ")}`))
204+
for (const t of tags) buildTag(t)
205+
206+
builtVersions = ["current", ...tags]
238207
} else {
239-
// biome-ignore lint/suspicious/noConsole: keep for logging
240-
console.log(chalk.cyan("Building current workspace → current"))
241-
buildDocs(workspaceRoot, join(outputDir, "current"))
208+
// Only PR content as "current"
209+
console.log(chalk.cyan(`(pr) Building PR docs → current`))
210+
buildDocs(currentDocsWorkspace, join(outputDir, "current"))
211+
builtVersions = ["current"]
242212
}
243-
244-
builtVersions = ["current", ...tags]
245213
} else {
246-
// Build only current branch
247-
if (onDefaultBranch) {
248-
// biome-ignore lint/suspicious/noConsole: keep for logging
249-
console.log(chalk.cyan(`Building default branch '${defaultBranch}' → current`))
250-
buildBranch(defaultBranch, "current")
214+
// Non-PR (e.g., release)
215+
if (hasVersionsArg) {
216+
// Build exactly the versions provided (this keeps older docs available if you list them)
217+
const tags = resolveTagsFromSpec(rawVersions)
218+
if (!tags.length) throw new Error(`No tags matched spec "${rawVersions}".`)
219+
console.log(chalk.cyan(`(ci) Building tags: ${tags.join(", ")}`))
220+
for (const t of tags) buildTag(t)
221+
builtVersions = [...tags]
251222
} else {
252-
// biome-ignore lint/suspicious/noConsole: keep for logging
253-
console.log(chalk.cyan("Building current workspace → current"))
254-
buildDocs(workspaceRoot, join(outputDir, "current"))
223+
// Fallback: build default branch (useful if you want a "main" channel)
224+
const checkPath = repoPath(docsRelative, contentDir) // "docs/content"
225+
run(`git fetch --prune origin ${defaultBranch}`, { cwd: currentDocsWorkspace, inherit: true })
226+
227+
const hasOnDefault = refHasPath(`origin/${defaultBranch}`, checkPath)
228+
if (!hasOnDefault) {
229+
throw new Error(
230+
`Default branch 'origin/${defaultBranch}' has no '${checkPath}'. Pass --versions to build tags.`
231+
)
232+
}
233+
console.log(chalk.cyan(`(ci) Building docs from '${defaultBranch}' → ${defaultBranch}`))
234+
buildBranch(defaultBranch, defaultBranch)
235+
builtVersions = [defaultBranch]
255236
}
256-
257-
builtVersions = ["current"]
258237
}
259238

260239
// Write versions file for the app
261-
const versionsFile = resolve(workspaceRoot, "app/utils/versions.ts")
240+
const versionsFile = resolve("app/utils/versions.ts")
262241
writeFileSync(
263242
versionsFile,
264243
`// Auto-generated file. Do not edit manually.
265244
export const versions = ${JSON.stringify(builtVersions, null, 2)} as const
266245
`
267246
)
268-
// biome-ignore lint/suspicious/noConsole: keep for logging
269247
console.log(chalk.green(`✔ Wrote versions.ts → ${versionsFile}`))
270-
// biome-ignore lint/suspicious/noConsole: keep for logging
271248
console.log(chalk.green("✅ Done"))
272249
})().catch((e) => {
273-
// biome-ignore lint/suspicious/noConsole: keep for logging
274250
console.error(chalk.red("❌ Build failed:"), e)
275251
process.exit(1)
276252
})

0 commit comments

Comments
 (0)