@lukoweb/apitogo 0.1.15 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/cli.js +117 -50
- package/package.json +1 -1
- package/src/vite/build.ts +237 -237
- package/src/vite/llms.ts +99 -99
- package/src/vite/prerender/prerender.ts +3 -1
package/dist/cli/cli.js
CHANGED
|
@@ -3822,7 +3822,7 @@ import {
|
|
|
3822
3822
|
// package.json
|
|
3823
3823
|
var package_default = {
|
|
3824
3824
|
name: "@lukoweb/apitogo",
|
|
3825
|
-
version: "0.1.
|
|
3825
|
+
version: "0.1.16",
|
|
3826
3826
|
type: "module",
|
|
3827
3827
|
sideEffects: [
|
|
3828
3828
|
"**/*.css",
|
|
@@ -8006,7 +8006,9 @@ var prerender = async ({
|
|
|
8006
8006
|
}
|
|
8007
8007
|
if (pagefindWriteResult?.outputPath) {
|
|
8008
8008
|
logger.info(
|
|
8009
|
-
colors7.blue(
|
|
8009
|
+
colors7.blue(
|
|
8010
|
+
`\u2713 pagefind index built: ${pagefindWriteResult.outputPath}`
|
|
8011
|
+
)
|
|
8010
8012
|
);
|
|
8011
8013
|
}
|
|
8012
8014
|
}
|
|
@@ -8455,8 +8457,8 @@ var build_default = {
|
|
|
8455
8457
|
};
|
|
8456
8458
|
|
|
8457
8459
|
// src/cli/configure-github-workflow/handler.ts
|
|
8458
|
-
import { access, mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
|
|
8459
8460
|
import { constants } from "node:fs";
|
|
8461
|
+
import { access, mkdir as mkdir6, writeFile as writeFile6 } from "node:fs/promises";
|
|
8460
8462
|
import path25 from "node:path";
|
|
8461
8463
|
var APITOGO_DEPLOY_WORKFLOW_YAML = `name: Build and Deploy
|
|
8462
8464
|
|
|
@@ -8602,9 +8604,7 @@ function isDeployJsonOnlyArgv() {
|
|
|
8602
8604
|
if (deployIdx === -1) {
|
|
8603
8605
|
return false;
|
|
8604
8606
|
}
|
|
8605
|
-
return args.slice(deployIdx).some(
|
|
8606
|
-
(a) => a === "--json-only" || a.startsWith("--json-only=")
|
|
8607
|
-
);
|
|
8607
|
+
return args.slice(deployIdx).some((a) => a === "--json-only" || a.startsWith("--json-only="));
|
|
8608
8608
|
}
|
|
8609
8609
|
function isJsonOnlyDeployEnv() {
|
|
8610
8610
|
return process.env.APITOGO_JSON_ONLY === "1";
|
|
@@ -9144,29 +9144,7 @@ var dev_default = {
|
|
|
9144
9144
|
}
|
|
9145
9145
|
};
|
|
9146
9146
|
|
|
9147
|
-
// src/cli/
|
|
9148
|
-
var previewCommand = {
|
|
9149
|
-
desc: "Preview production build",
|
|
9150
|
-
command: "preview",
|
|
9151
|
-
builder: (yargs2) => yargs2.option("dir", {
|
|
9152
|
-
type: "string",
|
|
9153
|
-
describe: "The directory containing your project",
|
|
9154
|
-
default: ".",
|
|
9155
|
-
normalize: true,
|
|
9156
|
-
hidden: true
|
|
9157
|
-
}).option("port", {
|
|
9158
|
-
type: "number",
|
|
9159
|
-
describe: "The port to run the local server on"
|
|
9160
|
-
}),
|
|
9161
|
-
handler: async (argv) => {
|
|
9162
|
-
process.env.NODE_ENV = "production";
|
|
9163
|
-
await captureEvent({ argv, event: "apitogo preview" });
|
|
9164
|
-
await preview(argv);
|
|
9165
|
-
}
|
|
9166
|
-
};
|
|
9167
|
-
var preview_default = previewCommand;
|
|
9168
|
-
|
|
9169
|
-
// src/cli/sync-config/handler.ts
|
|
9147
|
+
// src/cli/make-config/handler.ts
|
|
9170
9148
|
import { readFile as readFile5, writeFile as writeFile7 } from "node:fs/promises";
|
|
9171
9149
|
import path30 from "node:path";
|
|
9172
9150
|
import { glob as glob4 } from "glob";
|
|
@@ -9182,6 +9160,7 @@ var CONFIG_FILENAMES = [
|
|
|
9182
9160
|
"zudoku.config.js",
|
|
9183
9161
|
"zudoku.config.mjs"
|
|
9184
9162
|
];
|
|
9163
|
+
var DEFAULT_NEW_CONFIG_FILENAME = "apitogo.config.tsx";
|
|
9185
9164
|
var createTreeNode = () => ({ docs: [], children: /* @__PURE__ */ new Map() });
|
|
9186
9165
|
var toPosixPath2 = (value) => value.split(path30.sep).join(path30.posix.sep);
|
|
9187
9166
|
var toRoutePath = (relativeFile, pagesDir) => {
|
|
@@ -9349,19 +9328,78 @@ var findMatchingBracket = (source, startIndex) => {
|
|
|
9349
9328
|
}
|
|
9350
9329
|
throw new Error("Could not find matching closing bracket.");
|
|
9351
9330
|
};
|
|
9352
|
-
var
|
|
9353
|
-
const
|
|
9354
|
-
|
|
9331
|
+
var findPropertyArrayBounds = (source, propertyName) => {
|
|
9332
|
+
const re = new RegExp(`\\b${propertyName}\\s*:`);
|
|
9333
|
+
const match = source.match(re);
|
|
9334
|
+
if (!match || match.index === void 0) {
|
|
9355
9335
|
throw new Error(`Could not find '${propertyName}' in config file.`);
|
|
9356
9336
|
}
|
|
9357
|
-
const arrayStart = source.indexOf("[",
|
|
9337
|
+
const arrayStart = source.indexOf("[", match.index);
|
|
9358
9338
|
if (arrayStart === -1) {
|
|
9359
|
-
throw new Error(
|
|
9339
|
+
throw new Error(
|
|
9340
|
+
`Could not find array value for '${propertyName}' (expected [...]).`
|
|
9341
|
+
);
|
|
9360
9342
|
}
|
|
9361
9343
|
const arrayEnd = findMatchingBracket(source, arrayStart);
|
|
9344
|
+
return { arrayStart, arrayEnd };
|
|
9345
|
+
};
|
|
9346
|
+
var replaceArrayProperty = (source, propertyName, replacement) => {
|
|
9347
|
+
const { arrayStart, arrayEnd } = findPropertyArrayBounds(
|
|
9348
|
+
source,
|
|
9349
|
+
propertyName
|
|
9350
|
+
);
|
|
9362
9351
|
return `${source.slice(0, arrayStart)}${replacement}${source.slice(arrayEnd + 1)}`;
|
|
9363
9352
|
};
|
|
9364
|
-
var
|
|
9353
|
+
var insertNavigationAndRedirectsKeys = (source) => {
|
|
9354
|
+
const m = source.match(/(const\s+config\s*:\s*ApitogoConfig\s*=\s*\{)/);
|
|
9355
|
+
if (!m || m.index === void 0) {
|
|
9356
|
+
throw new Error(
|
|
9357
|
+
"Could not find 'const config: ApitogoConfig = {' \u2014 add navigation and redirects arrays manually."
|
|
9358
|
+
);
|
|
9359
|
+
}
|
|
9360
|
+
const insertAt = m.index + m[0].length;
|
|
9361
|
+
return `${source.slice(0, insertAt)}
|
|
9362
|
+
navigation: [],
|
|
9363
|
+
redirects: [],${source.slice(insertAt)}`;
|
|
9364
|
+
};
|
|
9365
|
+
var insertRedirectsAfterNavigation = (source) => {
|
|
9366
|
+
if (/\bredirects\s*:/.test(source)) return source;
|
|
9367
|
+
const { arrayEnd } = findPropertyArrayBounds(source, "navigation");
|
|
9368
|
+
let i = arrayEnd + 1;
|
|
9369
|
+
while (i < source.length && /\s/.test(source[i])) i++;
|
|
9370
|
+
if (source[i] === ",") {
|
|
9371
|
+
return `${source.slice(0, i + 1)}
|
|
9372
|
+
redirects: [],${source.slice(i + 1)}`;
|
|
9373
|
+
}
|
|
9374
|
+
return `${source.slice(0, arrayEnd + 1)},
|
|
9375
|
+
redirects: [],${source.slice(arrayEnd + 1)}`;
|
|
9376
|
+
};
|
|
9377
|
+
var ensureNavigationAndRedirectsForUpdate = (source) => {
|
|
9378
|
+
const hasNav = /\bnavigation\s*:/.test(source);
|
|
9379
|
+
const hasRedirects = /\bredirects\s*:/.test(source);
|
|
9380
|
+
if (hasNav && hasRedirects) return source;
|
|
9381
|
+
if (!hasNav && !hasRedirects) return insertNavigationAndRedirectsKeys(source);
|
|
9382
|
+
if (hasNav && !hasRedirects) return insertRedirectsAfterNavigation(source);
|
|
9383
|
+
throw new Error(
|
|
9384
|
+
"Found 'redirects' but not 'navigation'. Add a navigation: [...] array manually."
|
|
9385
|
+
);
|
|
9386
|
+
};
|
|
9387
|
+
var buildNewConfigContents = (pagesDir) => {
|
|
9388
|
+
const filesGlob = `/${pagesDir}/**/*.mdx`;
|
|
9389
|
+
return `import type { ApitogoConfig } from "@lukoweb/apitogo";
|
|
9390
|
+
|
|
9391
|
+
const config: ApitogoConfig = {
|
|
9392
|
+
docs: {
|
|
9393
|
+
files: "${filesGlob}",
|
|
9394
|
+
},
|
|
9395
|
+
navigation: [],
|
|
9396
|
+
redirects: [],
|
|
9397
|
+
};
|
|
9398
|
+
|
|
9399
|
+
export default config;
|
|
9400
|
+
`;
|
|
9401
|
+
};
|
|
9402
|
+
var findExistingConfigPath = async (dir) => {
|
|
9365
9403
|
for (const filename of CONFIG_FILENAMES) {
|
|
9366
9404
|
const fullPath = path30.join(dir, filename);
|
|
9367
9405
|
try {
|
|
@@ -9370,15 +9408,22 @@ var findConfigPath = async (dir) => {
|
|
|
9370
9408
|
} catch {
|
|
9371
9409
|
}
|
|
9372
9410
|
}
|
|
9373
|
-
|
|
9411
|
+
return null;
|
|
9374
9412
|
};
|
|
9375
|
-
async function
|
|
9413
|
+
async function makeConfig(argv) {
|
|
9376
9414
|
const dir = path30.resolve(process.cwd(), argv.dir);
|
|
9377
9415
|
const pagesDir = toPosixPath2(
|
|
9378
9416
|
argv.pagesDir.replace(/^[./]+/, "").replace(/\/+$/, "")
|
|
9379
9417
|
);
|
|
9380
9418
|
try {
|
|
9381
|
-
|
|
9419
|
+
let configPath = await findExistingConfigPath(dir);
|
|
9420
|
+
let createdNew = false;
|
|
9421
|
+
if (!configPath) {
|
|
9422
|
+
configPath = path30.join(dir, DEFAULT_NEW_CONFIG_FILENAME);
|
|
9423
|
+
await writeFile7(configPath, buildNewConfigContents(pagesDir), "utf8");
|
|
9424
|
+
createdNew = true;
|
|
9425
|
+
printDiagnosticsToConsole(`Created ${DEFAULT_NEW_CONFIG_FILENAME}`);
|
|
9426
|
+
}
|
|
9382
9427
|
const pageFiles = await glob4(`${pagesDir}/**/*.{md,mdx}`, {
|
|
9383
9428
|
cwd: dir,
|
|
9384
9429
|
ignore: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
|
|
@@ -9390,13 +9435,12 @@ async function syncConfig(argv) {
|
|
|
9390
9435
|
if (routePaths.length === 0) {
|
|
9391
9436
|
throw new Error(`No .md or .mdx files found under '${pagesDir}'.`);
|
|
9392
9437
|
}
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
const originalConfig = await readFile5(configPath, "utf8");
|
|
9438
|
+
let originalConfig = await readFile5(configPath, "utf8");
|
|
9439
|
+
originalConfig = ensureNavigationAndRedirectsForUpdate(originalConfig);
|
|
9396
9440
|
const withNavigation = replaceArrayProperty(
|
|
9397
9441
|
originalConfig,
|
|
9398
9442
|
"navigation",
|
|
9399
|
-
buildNavigationCode(
|
|
9443
|
+
buildNavigationCode(treeToNavigationItems(buildTree(routePaths)))
|
|
9400
9444
|
);
|
|
9401
9445
|
const withRedirects = replaceArrayProperty(
|
|
9402
9446
|
withNavigation,
|
|
@@ -9404,8 +9448,9 @@ async function syncConfig(argv) {
|
|
|
9404
9448
|
buildRedirectsCode(routePaths[0])
|
|
9405
9449
|
);
|
|
9406
9450
|
await writeFile7(configPath, withRedirects, "utf8");
|
|
9451
|
+
const action = createdNew ? "Initialized" : "Updated";
|
|
9407
9452
|
printResultToConsole(
|
|
9408
|
-
|
|
9453
|
+
`${action} ${path30.basename(configPath)} from ${pagesDir}/ (navigation and redirects only; other settings unchanged)`
|
|
9409
9454
|
);
|
|
9410
9455
|
} catch (error) {
|
|
9411
9456
|
await printCriticalFailureToConsoleAndExit(
|
|
@@ -9414,10 +9459,10 @@ async function syncConfig(argv) {
|
|
|
9414
9459
|
}
|
|
9415
9460
|
}
|
|
9416
9461
|
|
|
9417
|
-
// src/cli/cmds/
|
|
9418
|
-
var
|
|
9419
|
-
desc: "
|
|
9420
|
-
command: "
|
|
9462
|
+
// src/cli/cmds/make-config.ts
|
|
9463
|
+
var make_config_default = {
|
|
9464
|
+
desc: "Create or update config navigation from the pages folder (other settings preserved)",
|
|
9465
|
+
command: "make-config",
|
|
9421
9466
|
builder: (yargs2) => yargs2.option("dir", {
|
|
9422
9467
|
type: "string",
|
|
9423
9468
|
describe: "The directory containing your project",
|
|
@@ -9430,11 +9475,33 @@ var sync_config_default = {
|
|
|
9430
9475
|
default: "pages"
|
|
9431
9476
|
}),
|
|
9432
9477
|
handler: async (argv) => {
|
|
9433
|
-
await captureEvent({ argv, event: "apitogo
|
|
9434
|
-
await
|
|
9478
|
+
await captureEvent({ argv, event: "apitogo make-config" });
|
|
9479
|
+
await makeConfig(argv);
|
|
9435
9480
|
}
|
|
9436
9481
|
};
|
|
9437
9482
|
|
|
9483
|
+
// src/cli/cmds/preview.ts
|
|
9484
|
+
var previewCommand = {
|
|
9485
|
+
desc: "Preview production build",
|
|
9486
|
+
command: "preview",
|
|
9487
|
+
builder: (yargs2) => yargs2.option("dir", {
|
|
9488
|
+
type: "string",
|
|
9489
|
+
describe: "The directory containing your project",
|
|
9490
|
+
default: ".",
|
|
9491
|
+
normalize: true,
|
|
9492
|
+
hidden: true
|
|
9493
|
+
}).option("port", {
|
|
9494
|
+
type: "number",
|
|
9495
|
+
describe: "The port to run the local server on"
|
|
9496
|
+
}),
|
|
9497
|
+
handler: async (argv) => {
|
|
9498
|
+
process.env.NODE_ENV = "production";
|
|
9499
|
+
await captureEvent({ argv, event: "apitogo preview" });
|
|
9500
|
+
await preview(argv);
|
|
9501
|
+
}
|
|
9502
|
+
};
|
|
9503
|
+
var preview_default = previewCommand;
|
|
9504
|
+
|
|
9438
9505
|
// src/cli/common/outdated.ts
|
|
9439
9506
|
import { existsSync as existsSync2, mkdirSync } from "node:fs";
|
|
9440
9507
|
import { readFile as readFile6, writeFile as writeFile8 } from "node:fs/promises";
|
|
@@ -9679,7 +9746,7 @@ var cli = yargs(hideBin(process.argv)).option("zuplo", {
|
|
|
9679
9746
|
process.env.ZUPLO = "1";
|
|
9680
9747
|
printDiagnosticsToConsole("Running in Zuplo mode");
|
|
9681
9748
|
}
|
|
9682
|
-
}).middleware(warnPackageVersionMismatch).command(build_default).command(configure_github_workflow_default).command(dev_default).command(deploy_default).command(preview_default).command(
|
|
9749
|
+
}).middleware(warnPackageVersionMismatch).command(build_default).command(configure_github_workflow_default).command(dev_default).command(deploy_default).command(preview_default).command(make_config_default).demandCommand().strictCommands().version(packageJson?.version).fail(false).help();
|
|
9683
9750
|
try {
|
|
9684
9751
|
void warnIfOutdatedVersion(packageJson?.version);
|
|
9685
9752
|
await cli.argv;
|
package/package.json
CHANGED
package/src/vite/build.ts
CHANGED
|
@@ -1,237 +1,237 @@
|
|
|
1
|
-
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { build as esbuild } from "esbuild";
|
|
4
|
-
import type { Rollup } from "vite";
|
|
5
|
-
import { build as viteBuild, mergeConfig } from "vite";
|
|
6
|
-
import { ZuploEnv } from "../app/env.js";
|
|
7
|
-
import { getZudokuRootDir } from "../cli/common/package-json.js";
|
|
8
|
-
import {
|
|
9
|
-
findOutputPathOfServerConfig,
|
|
10
|
-
loadZudokuConfig,
|
|
11
|
-
} from "../config/loader.js";
|
|
12
|
-
import { getIssuer } from "../lib/auth/issuer.js";
|
|
13
|
-
import invariant from "../lib/util/invariant.js";
|
|
14
|
-
import { joinUrl } from "../lib/util/joinUrl.js";
|
|
15
|
-
import { getViteConfig } from "./config.js";
|
|
16
|
-
import { getBuildHtml } from "./html.js";
|
|
17
|
-
import { writeOutput } from "./output.js";
|
|
18
|
-
import { prerender } from "./prerender/prerender.js";
|
|
19
|
-
|
|
20
|
-
const DIST_DIR = "dist";
|
|
21
|
-
|
|
22
|
-
const extractAssets = (result: Rollup.RollupOutput) => {
|
|
23
|
-
const jsEntry = result.output.find(
|
|
24
|
-
(o) => "isEntry" in o && o.isEntry,
|
|
25
|
-
)?.fileName;
|
|
26
|
-
const cssEntries = result.output
|
|
27
|
-
.filter((o) => o.fileName.endsWith(".css"))
|
|
28
|
-
.map((o) => o.fileName);
|
|
29
|
-
|
|
30
|
-
if (!jsEntry || cssEntries.length === 0) {
|
|
31
|
-
throw new Error("Build failed. No js or css assets found");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return { jsEntry, cssEntries };
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export type BuildOptions = {
|
|
38
|
-
dir: string;
|
|
39
|
-
ssr?: boolean;
|
|
40
|
-
adapter?: "node" | "cloudflare" | "vercel";
|
|
41
|
-
/** Suppress Vite build table and most build logs (used with `apitogo deploy --json-only`). */
|
|
42
|
-
jsonOnly?: boolean;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
export async function runBuild(options: BuildOptions) {
|
|
46
|
-
const { dir, ssr, adapter = "node", jsonOnly = false } = options;
|
|
47
|
-
|
|
48
|
-
// Build client and server bundles
|
|
49
|
-
const viteClientConfig = await getViteConfig(dir, {
|
|
50
|
-
mode: "production",
|
|
51
|
-
command: "build",
|
|
52
|
-
});
|
|
53
|
-
const viteServerConfig = await getViteConfig(dir, {
|
|
54
|
-
mode: "production",
|
|
55
|
-
command: "build",
|
|
56
|
-
isSsrBuild: true,
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const clientResult = await viteBuild(
|
|
60
|
-
jsonOnly
|
|
61
|
-
? mergeConfig(viteClientConfig, { logLevel: "silent" as const })
|
|
62
|
-
: viteClientConfig,
|
|
63
|
-
);
|
|
64
|
-
const serverResult = await viteBuild({
|
|
65
|
-
...viteServerConfig,
|
|
66
|
-
logLevel: "silent",
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
if (Array.isArray(clientResult) || !("output" in clientResult)) {
|
|
70
|
-
throw new Error("Client build failed");
|
|
71
|
-
}
|
|
72
|
-
if (Array.isArray(serverResult) || !("output" in serverResult)) {
|
|
73
|
-
throw new Error("Server build failed");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const { config } = await loadZudokuConfig(
|
|
77
|
-
{ mode: "production", command: "build" },
|
|
78
|
-
dir,
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const { jsEntry, cssEntries } = extractAssets(clientResult);
|
|
82
|
-
|
|
83
|
-
const html = getBuildHtml({
|
|
84
|
-
jsEntry: joinUrl(viteClientConfig.base, jsEntry),
|
|
85
|
-
cssEntries: cssEntries.map((css) => joinUrl(viteClientConfig.base, css)),
|
|
86
|
-
dir: config.site?.dir,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
invariant(viteClientConfig.build?.outDir, "Client build outDir is missing");
|
|
90
|
-
invariant(viteServerConfig.build?.outDir, "Server build outDir is missing");
|
|
91
|
-
|
|
92
|
-
const clientOutDir = viteClientConfig.build.outDir;
|
|
93
|
-
const serverOutDir = viteServerConfig.build.outDir;
|
|
94
|
-
const serverConfigFilename = findOutputPathOfServerConfig(serverResult);
|
|
95
|
-
|
|
96
|
-
if (ssr) {
|
|
97
|
-
// SSR: bundle entry.js and remove index.html
|
|
98
|
-
await bundleSSREntry({
|
|
99
|
-
dir,
|
|
100
|
-
adapter,
|
|
101
|
-
serverOutDir,
|
|
102
|
-
serverConfigFilename,
|
|
103
|
-
html,
|
|
104
|
-
basePath: config.basePath,
|
|
105
|
-
});
|
|
106
|
-
await rm(path.join(clientOutDir, "index.html"), { force: true });
|
|
107
|
-
} else {
|
|
108
|
-
// SSG: prerender and clean up server
|
|
109
|
-
await runPrerender({
|
|
110
|
-
dir,
|
|
111
|
-
config,
|
|
112
|
-
html,
|
|
113
|
-
clientOutDir,
|
|
114
|
-
serverOutDir,
|
|
115
|
-
serverResult,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
type PrerenderOptions = {
|
|
121
|
-
dir: string;
|
|
122
|
-
config: Awaited<ReturnType<typeof loadZudokuConfig>>["config"];
|
|
123
|
-
html: string;
|
|
124
|
-
clientOutDir: string;
|
|
125
|
-
serverOutDir: string;
|
|
126
|
-
serverResult: Rollup.RollupOutput;
|
|
127
|
-
};
|
|
128
|
-
|
|
129
|
-
const runPrerender = async (options: PrerenderOptions) => {
|
|
130
|
-
const { dir, config, html, clientOutDir, serverOutDir, serverResult } =
|
|
131
|
-
options;
|
|
132
|
-
const issuer = await getIssuer(config);
|
|
133
|
-
const serverConfigFilename = findOutputPathOfServerConfig(serverResult);
|
|
134
|
-
|
|
135
|
-
try {
|
|
136
|
-
const { workerResults, rewrites } = await prerender({
|
|
137
|
-
html,
|
|
138
|
-
dir,
|
|
139
|
-
basePath: config.basePath,
|
|
140
|
-
serverConfigFilename,
|
|
141
|
-
writeRedirects: process.env.VERCEL === undefined,
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
const indexHtml = path.join(clientOutDir, "index.html");
|
|
145
|
-
if (!workerResults.find((r) => r.outputPath === indexHtml)) {
|
|
146
|
-
await writeFile(indexHtml, html, "utf-8");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Move status pages (400, 404, 500) to root path
|
|
150
|
-
const statusPages = workerResults.flatMap((r) =>
|
|
151
|
-
/400|404|500\.html$/.test(r.outputPath) ? r.outputPath : [],
|
|
152
|
-
);
|
|
153
|
-
for (const statusPage of statusPages) {
|
|
154
|
-
await rename(
|
|
155
|
-
statusPage,
|
|
156
|
-
path.join(dir, DIST_DIR, path.basename(statusPage)),
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Delete server build - not needed after prerender
|
|
161
|
-
await rm(serverOutDir, { recursive: true, force: true });
|
|
162
|
-
|
|
163
|
-
if (process.env.VERCEL) {
|
|
164
|
-
await mkdir(path.join(dir, ".vercel/output/static"), { recursive: true });
|
|
165
|
-
await rename(
|
|
166
|
-
path.join(dir, DIST_DIR),
|
|
167
|
-
path.join(dir, ".vercel/output/static"),
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
await writeOutput(dir, {
|
|
172
|
-
config,
|
|
173
|
-
redirects: workerResults.flatMap((r) => r.redirect ?? []),
|
|
174
|
-
rewrites,
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
if (ZuploEnv.isZuplo && issuer) {
|
|
178
|
-
await writeFile(
|
|
179
|
-
path.join(dir, DIST_DIR, ".output/zuplo.json"),
|
|
180
|
-
JSON.stringify({ issuer }, null, 2),
|
|
181
|
-
"utf-8",
|
|
182
|
-
);
|
|
183
|
-
}
|
|
184
|
-
} catch (e) {
|
|
185
|
-
// biome-ignore lint/suspicious/noConsole: Logging allowed here
|
|
186
|
-
console.error(e);
|
|
187
|
-
throw e;
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
type SSREntryOptions = {
|
|
192
|
-
dir: string;
|
|
193
|
-
adapter: "node" | "cloudflare" | "vercel";
|
|
194
|
-
serverOutDir: string;
|
|
195
|
-
serverConfigFilename: string;
|
|
196
|
-
html: string;
|
|
197
|
-
basePath?: string;
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const bundleSSREntry = async (options: SSREntryOptions) => {
|
|
201
|
-
const { dir, adapter, serverOutDir, serverConfigFilename, html, basePath } =
|
|
202
|
-
options;
|
|
203
|
-
const tempEntryPath = path.join(dir, "__ssr-entry.ts");
|
|
204
|
-
|
|
205
|
-
const packageRoot = getZudokuRootDir();
|
|
206
|
-
|
|
207
|
-
const templateContent = await readFile(
|
|
208
|
-
path.join(packageRoot, "src/vite/ssr-templates", `${adapter}.ts`),
|
|
209
|
-
"utf-8",
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
const entryContent = templateContent
|
|
213
|
-
.replace('"__TEMPLATE__"', JSON.stringify(html))
|
|
214
|
-
.replace('"__CONFIG_FILE__"', JSON.stringify(`./${serverConfigFilename}`))
|
|
215
|
-
.replace(
|
|
216
|
-
'"__BASE_PATH__"',
|
|
217
|
-
basePath ? JSON.stringify(basePath) : "undefined",
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
await writeFile(tempEntryPath, entryContent, "utf-8");
|
|
221
|
-
|
|
222
|
-
try {
|
|
223
|
-
await esbuild({
|
|
224
|
-
entryPoints: [tempEntryPath],
|
|
225
|
-
bundle: true,
|
|
226
|
-
platform: adapter === "node" ? "node" : "neutral",
|
|
227
|
-
target: "es2022",
|
|
228
|
-
format: "esm",
|
|
229
|
-
outfile: path.join(serverOutDir, "entry.js"),
|
|
230
|
-
external: ["./entry.server.js", `./${serverConfigFilename}`],
|
|
231
|
-
nodePaths: [path.join(packageRoot, "node_modules")],
|
|
232
|
-
banner: { js: "// Bundled SSR entry" },
|
|
233
|
-
});
|
|
234
|
-
} finally {
|
|
235
|
-
await rm(tempEntryPath, { force: true });
|
|
236
|
-
}
|
|
237
|
-
};
|
|
1
|
+
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { build as esbuild } from "esbuild";
|
|
4
|
+
import type { Rollup } from "vite";
|
|
5
|
+
import { build as viteBuild, mergeConfig } from "vite";
|
|
6
|
+
import { ZuploEnv } from "../app/env.js";
|
|
7
|
+
import { getZudokuRootDir } from "../cli/common/package-json.js";
|
|
8
|
+
import {
|
|
9
|
+
findOutputPathOfServerConfig,
|
|
10
|
+
loadZudokuConfig,
|
|
11
|
+
} from "../config/loader.js";
|
|
12
|
+
import { getIssuer } from "../lib/auth/issuer.js";
|
|
13
|
+
import invariant from "../lib/util/invariant.js";
|
|
14
|
+
import { joinUrl } from "../lib/util/joinUrl.js";
|
|
15
|
+
import { getViteConfig } from "./config.js";
|
|
16
|
+
import { getBuildHtml } from "./html.js";
|
|
17
|
+
import { writeOutput } from "./output.js";
|
|
18
|
+
import { prerender } from "./prerender/prerender.js";
|
|
19
|
+
|
|
20
|
+
const DIST_DIR = "dist";
|
|
21
|
+
|
|
22
|
+
const extractAssets = (result: Rollup.RollupOutput) => {
|
|
23
|
+
const jsEntry = result.output.find(
|
|
24
|
+
(o) => "isEntry" in o && o.isEntry,
|
|
25
|
+
)?.fileName;
|
|
26
|
+
const cssEntries = result.output
|
|
27
|
+
.filter((o) => o.fileName.endsWith(".css"))
|
|
28
|
+
.map((o) => o.fileName);
|
|
29
|
+
|
|
30
|
+
if (!jsEntry || cssEntries.length === 0) {
|
|
31
|
+
throw new Error("Build failed. No js or css assets found");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { jsEntry, cssEntries };
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type BuildOptions = {
|
|
38
|
+
dir: string;
|
|
39
|
+
ssr?: boolean;
|
|
40
|
+
adapter?: "node" | "cloudflare" | "vercel";
|
|
41
|
+
/** Suppress Vite build table and most build logs (used with `apitogo deploy --json-only`). */
|
|
42
|
+
jsonOnly?: boolean;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export async function runBuild(options: BuildOptions) {
|
|
46
|
+
const { dir, ssr, adapter = "node", jsonOnly = false } = options;
|
|
47
|
+
|
|
48
|
+
// Build client and server bundles
|
|
49
|
+
const viteClientConfig = await getViteConfig(dir, {
|
|
50
|
+
mode: "production",
|
|
51
|
+
command: "build",
|
|
52
|
+
});
|
|
53
|
+
const viteServerConfig = await getViteConfig(dir, {
|
|
54
|
+
mode: "production",
|
|
55
|
+
command: "build",
|
|
56
|
+
isSsrBuild: true,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const clientResult = await viteBuild(
|
|
60
|
+
jsonOnly
|
|
61
|
+
? mergeConfig(viteClientConfig, { logLevel: "silent" as const })
|
|
62
|
+
: viteClientConfig,
|
|
63
|
+
);
|
|
64
|
+
const serverResult = await viteBuild({
|
|
65
|
+
...viteServerConfig,
|
|
66
|
+
logLevel: "silent",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (Array.isArray(clientResult) || !("output" in clientResult)) {
|
|
70
|
+
throw new Error("Client build failed");
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(serverResult) || !("output" in serverResult)) {
|
|
73
|
+
throw new Error("Server build failed");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const { config } = await loadZudokuConfig(
|
|
77
|
+
{ mode: "production", command: "build" },
|
|
78
|
+
dir,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const { jsEntry, cssEntries } = extractAssets(clientResult);
|
|
82
|
+
|
|
83
|
+
const html = getBuildHtml({
|
|
84
|
+
jsEntry: joinUrl(viteClientConfig.base, jsEntry),
|
|
85
|
+
cssEntries: cssEntries.map((css) => joinUrl(viteClientConfig.base, css)),
|
|
86
|
+
dir: config.site?.dir,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
invariant(viteClientConfig.build?.outDir, "Client build outDir is missing");
|
|
90
|
+
invariant(viteServerConfig.build?.outDir, "Server build outDir is missing");
|
|
91
|
+
|
|
92
|
+
const clientOutDir = viteClientConfig.build.outDir;
|
|
93
|
+
const serverOutDir = viteServerConfig.build.outDir;
|
|
94
|
+
const serverConfigFilename = findOutputPathOfServerConfig(serverResult);
|
|
95
|
+
|
|
96
|
+
if (ssr) {
|
|
97
|
+
// SSR: bundle entry.js and remove index.html
|
|
98
|
+
await bundleSSREntry({
|
|
99
|
+
dir,
|
|
100
|
+
adapter,
|
|
101
|
+
serverOutDir,
|
|
102
|
+
serverConfigFilename,
|
|
103
|
+
html,
|
|
104
|
+
basePath: config.basePath,
|
|
105
|
+
});
|
|
106
|
+
await rm(path.join(clientOutDir, "index.html"), { force: true });
|
|
107
|
+
} else {
|
|
108
|
+
// SSG: prerender and clean up server
|
|
109
|
+
await runPrerender({
|
|
110
|
+
dir,
|
|
111
|
+
config,
|
|
112
|
+
html,
|
|
113
|
+
clientOutDir,
|
|
114
|
+
serverOutDir,
|
|
115
|
+
serverResult,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
type PrerenderOptions = {
|
|
121
|
+
dir: string;
|
|
122
|
+
config: Awaited<ReturnType<typeof loadZudokuConfig>>["config"];
|
|
123
|
+
html: string;
|
|
124
|
+
clientOutDir: string;
|
|
125
|
+
serverOutDir: string;
|
|
126
|
+
serverResult: Rollup.RollupOutput;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const runPrerender = async (options: PrerenderOptions) => {
|
|
130
|
+
const { dir, config, html, clientOutDir, serverOutDir, serverResult } =
|
|
131
|
+
options;
|
|
132
|
+
const issuer = await getIssuer(config);
|
|
133
|
+
const serverConfigFilename = findOutputPathOfServerConfig(serverResult);
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const { workerResults, rewrites } = await prerender({
|
|
137
|
+
html,
|
|
138
|
+
dir,
|
|
139
|
+
basePath: config.basePath,
|
|
140
|
+
serverConfigFilename,
|
|
141
|
+
writeRedirects: process.env.VERCEL === undefined,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const indexHtml = path.join(clientOutDir, "index.html");
|
|
145
|
+
if (!workerResults.find((r) => r.outputPath === indexHtml)) {
|
|
146
|
+
await writeFile(indexHtml, html, "utf-8");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Move status pages (400, 404, 500) to root path
|
|
150
|
+
const statusPages = workerResults.flatMap((r) =>
|
|
151
|
+
/400|404|500\.html$/.test(r.outputPath) ? r.outputPath : [],
|
|
152
|
+
);
|
|
153
|
+
for (const statusPage of statusPages) {
|
|
154
|
+
await rename(
|
|
155
|
+
statusPage,
|
|
156
|
+
path.join(dir, DIST_DIR, path.basename(statusPage)),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Delete server build - not needed after prerender
|
|
161
|
+
await rm(serverOutDir, { recursive: true, force: true });
|
|
162
|
+
|
|
163
|
+
if (process.env.VERCEL) {
|
|
164
|
+
await mkdir(path.join(dir, ".vercel/output/static"), { recursive: true });
|
|
165
|
+
await rename(
|
|
166
|
+
path.join(dir, DIST_DIR),
|
|
167
|
+
path.join(dir, ".vercel/output/static"),
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
await writeOutput(dir, {
|
|
172
|
+
config,
|
|
173
|
+
redirects: workerResults.flatMap((r) => r.redirect ?? []),
|
|
174
|
+
rewrites,
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
if (ZuploEnv.isZuplo && issuer) {
|
|
178
|
+
await writeFile(
|
|
179
|
+
path.join(dir, DIST_DIR, ".output/zuplo.json"),
|
|
180
|
+
JSON.stringify({ issuer }, null, 2),
|
|
181
|
+
"utf-8",
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
} catch (e) {
|
|
185
|
+
// biome-ignore lint/suspicious/noConsole: Logging allowed here
|
|
186
|
+
console.error(e);
|
|
187
|
+
throw e;
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
type SSREntryOptions = {
|
|
192
|
+
dir: string;
|
|
193
|
+
adapter: "node" | "cloudflare" | "vercel";
|
|
194
|
+
serverOutDir: string;
|
|
195
|
+
serverConfigFilename: string;
|
|
196
|
+
html: string;
|
|
197
|
+
basePath?: string;
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const bundleSSREntry = async (options: SSREntryOptions) => {
|
|
201
|
+
const { dir, adapter, serverOutDir, serverConfigFilename, html, basePath } =
|
|
202
|
+
options;
|
|
203
|
+
const tempEntryPath = path.join(dir, "__ssr-entry.ts");
|
|
204
|
+
|
|
205
|
+
const packageRoot = getZudokuRootDir();
|
|
206
|
+
|
|
207
|
+
const templateContent = await readFile(
|
|
208
|
+
path.join(packageRoot, "src/vite/ssr-templates", `${adapter}.ts`),
|
|
209
|
+
"utf-8",
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const entryContent = templateContent
|
|
213
|
+
.replace('"__TEMPLATE__"', JSON.stringify(html))
|
|
214
|
+
.replace('"__CONFIG_FILE__"', JSON.stringify(`./${serverConfigFilename}`))
|
|
215
|
+
.replace(
|
|
216
|
+
'"__BASE_PATH__"',
|
|
217
|
+
basePath ? JSON.stringify(basePath) : "undefined",
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
await writeFile(tempEntryPath, entryContent, "utf-8");
|
|
221
|
+
|
|
222
|
+
try {
|
|
223
|
+
await esbuild({
|
|
224
|
+
entryPoints: [tempEntryPath],
|
|
225
|
+
bundle: true,
|
|
226
|
+
platform: adapter === "node" ? "node" : "neutral",
|
|
227
|
+
target: "es2022",
|
|
228
|
+
format: "esm",
|
|
229
|
+
outfile: path.join(serverOutDir, "entry.js"),
|
|
230
|
+
external: ["./entry.server.js", `./${serverConfigFilename}`],
|
|
231
|
+
nodePaths: [path.join(packageRoot, "node_modules")],
|
|
232
|
+
banner: { js: "// Bundled SSR entry" },
|
|
233
|
+
});
|
|
234
|
+
} finally {
|
|
235
|
+
await rm(tempEntryPath, { force: true });
|
|
236
|
+
}
|
|
237
|
+
};
|
package/src/vite/llms.ts
CHANGED
|
@@ -1,99 +1,99 @@
|
|
|
1
|
-
import { writeFile } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import colors from "picocolors";
|
|
4
|
-
import { joinUrl } from "../lib/util/joinUrl.js";
|
|
5
|
-
import type { MarkdownFileInfo } from "./plugin-markdown-export.js";
|
|
6
|
-
export async function generateLlmsTxtFiles({
|
|
7
|
-
markdownFileInfos,
|
|
8
|
-
outputUrls,
|
|
9
|
-
baseOutputDir,
|
|
10
|
-
basePath,
|
|
11
|
-
siteName,
|
|
12
|
-
llmsTxt,
|
|
13
|
-
llmsTxtFull,
|
|
14
|
-
redirectUrls,
|
|
15
|
-
}: {
|
|
16
|
-
markdownFileInfos: MarkdownFileInfo[];
|
|
17
|
-
basePath: string | undefined;
|
|
18
|
-
outputUrls: string[];
|
|
19
|
-
baseOutputDir: string;
|
|
20
|
-
siteName?: string;
|
|
21
|
-
llmsTxt?: boolean;
|
|
22
|
-
llmsTxtFull?: boolean;
|
|
23
|
-
redirectUrls: Set<string>;
|
|
24
|
-
}) {
|
|
25
|
-
const nonRedirectUrls = outputUrls.filter((url) => !redirectUrls.has(url));
|
|
26
|
-
|
|
27
|
-
const baseUrl = basePath ?? "";
|
|
28
|
-
const title = siteName ?? "Documentation";
|
|
29
|
-
|
|
30
|
-
const markdownMap = new Map(
|
|
31
|
-
markdownFileInfos.map((info) => [info.routePath, info]),
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
// Generate llms.txt
|
|
35
|
-
if (llmsTxt) {
|
|
36
|
-
const llmsTxtParts: string[] = [];
|
|
37
|
-
|
|
38
|
-
llmsTxtParts.push(`# ${title}\n`);
|
|
39
|
-
llmsTxtParts.push("> Documentation files for Large Language Models\n");
|
|
40
|
-
|
|
41
|
-
// Add documentation section with links to all pages (matching sitemap structure)
|
|
42
|
-
llmsTxtParts.push("## Documentation\n");
|
|
43
|
-
|
|
44
|
-
for (const url of nonRedirectUrls) {
|
|
45
|
-
// Skip error pages
|
|
46
|
-
if (/(400|404|500)$/.test(url)) continue;
|
|
47
|
-
|
|
48
|
-
const mdInfo = markdownMap.get(url);
|
|
49
|
-
|
|
50
|
-
// Only include pages that have markdown content
|
|
51
|
-
if (mdInfo) {
|
|
52
|
-
// If we have markdown for this page, link to the .md file
|
|
53
|
-
const mdUrl = joinUrl(baseUrl, `${url}.md`);
|
|
54
|
-
const linkTitle = mdInfo.title ?? url;
|
|
55
|
-
const description = mdInfo.description ? `: ${mdInfo.description}` : "";
|
|
56
|
-
llmsTxtParts.push(`- [${linkTitle}](${mdUrl})${description}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const llmsTxt = llmsTxtParts.join("\n");
|
|
61
|
-
await writeFile(path.join(baseOutputDir, "llms.txt"), llmsTxt, "utf-8");
|
|
62
|
-
|
|
63
|
-
if (process.env.APITOGO_JSON_ONLY !== "1") {
|
|
64
|
-
// biome-ignore lint/suspicious/noConsole: Logging allowed here
|
|
65
|
-
console.log(colors.blue("✓ generated llms.txt"));
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Generate llms-full.txt (full content of all markdown documents)
|
|
70
|
-
if (llmsTxtFull) {
|
|
71
|
-
const llmsFullParts: string[] = [];
|
|
72
|
-
|
|
73
|
-
llmsFullParts.push(`# ${title}\n`);
|
|
74
|
-
llmsFullParts.push("> Complete documentation for Large Language Models\n");
|
|
75
|
-
|
|
76
|
-
// Add each markdown document's full content
|
|
77
|
-
for (const info of markdownFileInfos) {
|
|
78
|
-
llmsFullParts.push(`\n---\n`);
|
|
79
|
-
llmsFullParts.push(`## Document: ${info.title ?? info.routePath}\n`);
|
|
80
|
-
if (info.description) {
|
|
81
|
-
llmsFullParts.push(`${info.description}\n`);
|
|
82
|
-
}
|
|
83
|
-
llmsFullParts.push(`URL: ${joinUrl(baseUrl, info.routePath)}\n`);
|
|
84
|
-
llmsFullParts.push(`\n${info.content}\n`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const llmsFull = llmsFullParts.join("\n");
|
|
88
|
-
await writeFile(
|
|
89
|
-
path.join(baseOutputDir, "llms-full.txt"),
|
|
90
|
-
llmsFull,
|
|
91
|
-
"utf-8",
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
if (process.env.APITOGO_JSON_ONLY !== "1") {
|
|
95
|
-
// biome-ignore lint/suspicious/noConsole: Allowed here
|
|
96
|
-
console.log(colors.blue("✓ generated llms-full.txt"));
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
1
|
+
import { writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import colors from "picocolors";
|
|
4
|
+
import { joinUrl } from "../lib/util/joinUrl.js";
|
|
5
|
+
import type { MarkdownFileInfo } from "./plugin-markdown-export.js";
|
|
6
|
+
export async function generateLlmsTxtFiles({
|
|
7
|
+
markdownFileInfos,
|
|
8
|
+
outputUrls,
|
|
9
|
+
baseOutputDir,
|
|
10
|
+
basePath,
|
|
11
|
+
siteName,
|
|
12
|
+
llmsTxt,
|
|
13
|
+
llmsTxtFull,
|
|
14
|
+
redirectUrls,
|
|
15
|
+
}: {
|
|
16
|
+
markdownFileInfos: MarkdownFileInfo[];
|
|
17
|
+
basePath: string | undefined;
|
|
18
|
+
outputUrls: string[];
|
|
19
|
+
baseOutputDir: string;
|
|
20
|
+
siteName?: string;
|
|
21
|
+
llmsTxt?: boolean;
|
|
22
|
+
llmsTxtFull?: boolean;
|
|
23
|
+
redirectUrls: Set<string>;
|
|
24
|
+
}) {
|
|
25
|
+
const nonRedirectUrls = outputUrls.filter((url) => !redirectUrls.has(url));
|
|
26
|
+
|
|
27
|
+
const baseUrl = basePath ?? "";
|
|
28
|
+
const title = siteName ?? "Documentation";
|
|
29
|
+
|
|
30
|
+
const markdownMap = new Map(
|
|
31
|
+
markdownFileInfos.map((info) => [info.routePath, info]),
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Generate llms.txt
|
|
35
|
+
if (llmsTxt) {
|
|
36
|
+
const llmsTxtParts: string[] = [];
|
|
37
|
+
|
|
38
|
+
llmsTxtParts.push(`# ${title}\n`);
|
|
39
|
+
llmsTxtParts.push("> Documentation files for Large Language Models\n");
|
|
40
|
+
|
|
41
|
+
// Add documentation section with links to all pages (matching sitemap structure)
|
|
42
|
+
llmsTxtParts.push("## Documentation\n");
|
|
43
|
+
|
|
44
|
+
for (const url of nonRedirectUrls) {
|
|
45
|
+
// Skip error pages
|
|
46
|
+
if (/(400|404|500)$/.test(url)) continue;
|
|
47
|
+
|
|
48
|
+
const mdInfo = markdownMap.get(url);
|
|
49
|
+
|
|
50
|
+
// Only include pages that have markdown content
|
|
51
|
+
if (mdInfo) {
|
|
52
|
+
// If we have markdown for this page, link to the .md file
|
|
53
|
+
const mdUrl = joinUrl(baseUrl, `${url}.md`);
|
|
54
|
+
const linkTitle = mdInfo.title ?? url;
|
|
55
|
+
const description = mdInfo.description ? `: ${mdInfo.description}` : "";
|
|
56
|
+
llmsTxtParts.push(`- [${linkTitle}](${mdUrl})${description}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const llmsTxt = llmsTxtParts.join("\n");
|
|
61
|
+
await writeFile(path.join(baseOutputDir, "llms.txt"), llmsTxt, "utf-8");
|
|
62
|
+
|
|
63
|
+
if (process.env.APITOGO_JSON_ONLY !== "1") {
|
|
64
|
+
// biome-ignore lint/suspicious/noConsole: Logging allowed here
|
|
65
|
+
console.log(colors.blue("✓ generated llms.txt"));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Generate llms-full.txt (full content of all markdown documents)
|
|
70
|
+
if (llmsTxtFull) {
|
|
71
|
+
const llmsFullParts: string[] = [];
|
|
72
|
+
|
|
73
|
+
llmsFullParts.push(`# ${title}\n`);
|
|
74
|
+
llmsFullParts.push("> Complete documentation for Large Language Models\n");
|
|
75
|
+
|
|
76
|
+
// Add each markdown document's full content
|
|
77
|
+
for (const info of markdownFileInfos) {
|
|
78
|
+
llmsFullParts.push(`\n---\n`);
|
|
79
|
+
llmsFullParts.push(`## Document: ${info.title ?? info.routePath}\n`);
|
|
80
|
+
if (info.description) {
|
|
81
|
+
llmsFullParts.push(`${info.description}\n`);
|
|
82
|
+
}
|
|
83
|
+
llmsFullParts.push(`URL: ${joinUrl(baseUrl, info.routePath)}\n`);
|
|
84
|
+
llmsFullParts.push(`\n${info.content}\n`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const llmsFull = llmsFullParts.join("\n");
|
|
88
|
+
await writeFile(
|
|
89
|
+
path.join(baseOutputDir, "llms-full.txt"),
|
|
90
|
+
llmsFull,
|
|
91
|
+
"utf-8",
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (process.env.APITOGO_JSON_ONLY !== "1") {
|
|
95
|
+
// biome-ignore lint/suspicious/noConsole: Allowed here
|
|
96
|
+
console.log(colors.blue("✓ generated llms-full.txt"));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -181,7 +181,9 @@ export const prerender = async ({
|
|
|
181
181
|
}
|
|
182
182
|
if (pagefindWriteResult?.outputPath) {
|
|
183
183
|
logger.info(
|
|
184
|
-
colors.blue(
|
|
184
|
+
colors.blue(
|
|
185
|
+
`✓ pagefind index built: ${pagefindWriteResult.outputPath}`,
|
|
186
|
+
),
|
|
185
187
|
);
|
|
186
188
|
}
|
|
187
189
|
}
|