svedocs-cli 0.1.0-beta.1 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/{chunk-QD6TB2KV.js → chunk-VMZTD7VT.js} +999 -516
- package/dist/create-svedocs.js +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/svedocs.js +1 -1
- package/package.json +1 -1
- package/templates/cloudflare/src/app.d.ts +18 -0
- package/templates/cloudflare/src/routes/+page.svelte +2 -1
- package/templates/cloudflare/src/routes/+page.ts +16 -4
- package/templates/cloudflare/src/routes/[...path]/+page.svelte +2 -1
- package/templates/cloudflare/src/routes/[...path]/+page.ts +17 -6
- package/templates/cloudflare/svedocs.config.ts +6 -0
- package/templates/docs/src/app.d.ts +18 -0
- package/templates/docs/src/routes/+page.svelte +2 -1
- package/templates/docs/src/routes/+page.ts +16 -4
- package/templates/docs/src/routes/[...path]/+page.svelte +2 -1
- package/templates/docs/src/routes/[...path]/+page.ts +17 -6
- package/templates/docs/svedocs.config.ts +6 -0
- package/templates/minimal/src/app.d.ts +18 -0
- package/templates/minimal/src/routes/+page.svelte +2 -1
- package/templates/minimal/src/routes/+page.ts +16 -4
- package/templates/minimal/src/routes/[...path]/+page.svelte +2 -1
- package/templates/minimal/src/routes/[...path]/+page.ts +17 -6
- package/templates/minimal/svedocs.config.ts +6 -0
|
@@ -1,161 +1,125 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
3
|
-
import path6 from "path";
|
|
1
|
+
// src/commands/build.ts
|
|
4
2
|
import { loadSvedocsConfig } from "svedocs/config";
|
|
5
|
-
import { checkPackagePublication } from "svedocs/core";
|
|
6
|
-
import { createOgImage, createOgImageInput } from "svedocs/og";
|
|
7
|
-
import { syncCloudflareAiSearchIndex } from "svedocs/search";
|
|
8
|
-
|
|
9
|
-
// src/commands/create.ts
|
|
10
|
-
import { access, cp, mkdir, readFile as readFile3, readdir, writeFile } from "fs/promises";
|
|
11
|
-
import path3 from "path";
|
|
12
|
-
import { fileURLToPath } from "url";
|
|
13
|
-
import { Command } from "commander";
|
|
14
3
|
|
|
15
|
-
// src/
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
4
|
+
// src/project.ts
|
|
5
|
+
import { access } from "fs/promises";
|
|
6
|
+
import { createRequire } from "module";
|
|
18
7
|
import path from "path";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
for (const [index, name] of fallbackOrder.entries()) {
|
|
44
|
-
const version = await readVersion(name);
|
|
45
|
-
if (version) {
|
|
46
|
-
const detected = envManager ?? projectManager;
|
|
47
|
-
return {
|
|
48
|
-
name,
|
|
49
|
-
version,
|
|
50
|
-
source: index === 0 ? "default" : "fallback",
|
|
51
|
-
...detected ? { detected } : {}
|
|
52
|
-
};
|
|
8
|
+
import { pathToFileURL } from "url";
|
|
9
|
+
import { loadSvedocsContent } from "svedocs/core";
|
|
10
|
+
var defaultConfigFiles = [
|
|
11
|
+
"svedocs.config.ts",
|
|
12
|
+
"svedocs.config.mts",
|
|
13
|
+
"svedocs.config.js",
|
|
14
|
+
"svedocs.config.mjs"
|
|
15
|
+
];
|
|
16
|
+
async function loadProjectManifest(options = {}) {
|
|
17
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
18
|
+
const config = await loadProjectConfig(projectRoot, options.configFile);
|
|
19
|
+
const merged = mergeSvedocsConfig(config, options.configOverrides);
|
|
20
|
+
return loadSvedocsContent(merged ? { projectRoot, config: merged } : { projectRoot });
|
|
21
|
+
}
|
|
22
|
+
async function loadProjectConfig(projectRoot = process.cwd(), configFile) {
|
|
23
|
+
const resolvedConfigFile = configFile ? path.resolve(projectRoot, configFile) : await findConfigFile(projectRoot);
|
|
24
|
+
if (!resolvedConfigFile) return void 0;
|
|
25
|
+
try {
|
|
26
|
+
return await loadConfigWithVite(projectRoot, resolvedConfigFile);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (!/\.[cm]?js$/.test(resolvedConfigFile)) {
|
|
29
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
30
|
+
throw new Error(`Failed to load ${path.relative(projectRoot, resolvedConfigFile)}. Install project dependencies first so Vite can load TypeScript config files. ${message}`);
|
|
53
31
|
}
|
|
32
|
+
const module = await import(pathToFileURL(resolvedConfigFile).href);
|
|
33
|
+
return module.default ?? module.config;
|
|
54
34
|
}
|
|
55
|
-
throw new Error("No supported package manager was found. Install pnpm, npm, yarn, or bun and try again.");
|
|
56
|
-
}
|
|
57
|
-
function detectPackageManagerFromEnv(env = process.env) {
|
|
58
|
-
const userAgent = env.npm_config_user_agent;
|
|
59
|
-
const userAgentManager = userAgent ? normalizePackageManagerName(userAgent.split(/[ /]/)[0]) : void 0;
|
|
60
|
-
if (userAgentManager) return userAgentManager;
|
|
61
|
-
const execPath = `${env.npm_execpath ?? ""} ${env.npm_node_execpath ?? ""}`.toLowerCase();
|
|
62
|
-
if (execPath.includes("pnpm")) return "pnpm";
|
|
63
|
-
if (execPath.includes("yarn")) return "yarn";
|
|
64
|
-
if (execPath.includes("bun")) return "bun";
|
|
65
|
-
if (execPath.includes("npm")) return "npm";
|
|
66
|
-
return void 0;
|
|
67
35
|
}
|
|
68
|
-
async function
|
|
69
|
-
for (const
|
|
70
|
-
const
|
|
71
|
-
if (
|
|
72
|
-
if (await fileExists(path.join(directory, "pnpm-lock.yaml"))) return "pnpm";
|
|
73
|
-
if (await fileExists(path.join(directory, "package-lock.json")) || await fileExists(path.join(directory, "npm-shrinkwrap.json"))) return "npm";
|
|
74
|
-
if (await fileExists(path.join(directory, "yarn.lock"))) return "yarn";
|
|
75
|
-
if (await fileExists(path.join(directory, "bun.lock")) || await fileExists(path.join(directory, "bun.lockb"))) return "bun";
|
|
36
|
+
async function findConfigFile(projectRoot) {
|
|
37
|
+
for (const file of defaultConfigFiles) {
|
|
38
|
+
const candidate = path.join(projectRoot, file);
|
|
39
|
+
if (await fileExists(candidate)) return candidate;
|
|
76
40
|
}
|
|
77
41
|
return void 0;
|
|
78
42
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (choice.source === "explicit") return `${choice.name}${version} selected by --package-manager`;
|
|
96
|
-
if (choice.source === "environment") return `${choice.name}${version} detected from the invoking package manager`;
|
|
97
|
-
if (choice.source === "project") return `${choice.name}${version} detected from the current project`;
|
|
98
|
-
if (choice.source === "fallback") {
|
|
99
|
-
const detected = choice.detected ? ` after ${choice.detected} was unavailable` : "";
|
|
100
|
-
return `${choice.name}${version} selected as fallback${detected}`;
|
|
43
|
+
async function loadConfigWithVite(projectRoot, configFile) {
|
|
44
|
+
const require2 = createRequire(path.join(projectRoot, "package.json"));
|
|
45
|
+
const vitePath = require2.resolve("vite");
|
|
46
|
+
const vite = await import(pathToFileURL(vitePath).href);
|
|
47
|
+
const loaded = await vite.loadConfigFromFile(
|
|
48
|
+
{
|
|
49
|
+
command: "serve",
|
|
50
|
+
mode: process.env.NODE_ENV ?? "production"
|
|
51
|
+
},
|
|
52
|
+
configFile,
|
|
53
|
+
projectRoot,
|
|
54
|
+
"silent"
|
|
55
|
+
);
|
|
56
|
+
const config = loaded?.config;
|
|
57
|
+
if (!config || typeof config !== "object") {
|
|
58
|
+
throw new Error(`No config object was exported from ${configFile}.`);
|
|
101
59
|
}
|
|
102
|
-
return
|
|
103
|
-
}
|
|
104
|
-
function normalizePackageManagerName(value) {
|
|
105
|
-
if (!value || value === "auto") return void 0;
|
|
106
|
-
const normalized = value.toLowerCase();
|
|
107
|
-
return packageManagers.includes(normalized) ? normalized : void 0;
|
|
60
|
+
return config;
|
|
108
61
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
62
|
+
function mergeSvedocsConfig(base, overrides) {
|
|
63
|
+
if (!base) return overrides;
|
|
64
|
+
if (!overrides) return base;
|
|
65
|
+
const site = mergeObject(base.site, overrides.site);
|
|
66
|
+
const content = mergeObject(base.content, overrides.content);
|
|
67
|
+
const build = mergeObject(base.build, overrides.build);
|
|
68
|
+
const theme = mergeObject(base.theme, overrides.theme);
|
|
69
|
+
const palette = mergeObject(base.theme?.palette, overrides.theme?.palette);
|
|
70
|
+
const fonts = mergeObject(base.theme?.fonts, overrides.theme?.fonts);
|
|
71
|
+
const codeTheme = mergeObject(
|
|
72
|
+
typeof base.theme?.codeTheme === "object" ? base.theme.codeTheme : void 0,
|
|
73
|
+
typeof overrides.theme?.codeTheme === "object" ? overrides.theme.codeTheme : void 0
|
|
74
|
+
);
|
|
75
|
+
const markdown = mergeObject(base.markdown, overrides.markdown);
|
|
76
|
+
const seo = mergeObject(base.seo, overrides.seo);
|
|
77
|
+
const source = mergeObject(base.source, overrides.source);
|
|
78
|
+
const checks = mergeObject(base.checks, overrides.checks);
|
|
79
|
+
const cloudflare = mergeObject(base.cloudflare, overrides.cloudflare);
|
|
80
|
+
const aiSearch = mergeObject(base.cloudflare?.aiSearch, overrides.cloudflare?.aiSearch);
|
|
81
|
+
const search = overrides.search ?? base.search;
|
|
82
|
+
const ai = overrides.ai ?? base.ai;
|
|
83
|
+
const i18n = overrides.i18n ?? base.i18n;
|
|
84
|
+
return {
|
|
85
|
+
...base,
|
|
86
|
+
...overrides,
|
|
87
|
+
...site ? { site } : {},
|
|
88
|
+
...content ? { content } : {},
|
|
89
|
+
...build ? { build } : {},
|
|
90
|
+
...theme ? {
|
|
91
|
+
theme: {
|
|
92
|
+
...theme,
|
|
93
|
+
...palette ? { palette } : {},
|
|
94
|
+
...fonts ? { fonts } : {},
|
|
95
|
+
...codeTheme ? { codeTheme } : {}
|
|
96
|
+
}
|
|
97
|
+
} : {},
|
|
98
|
+
...markdown ? { markdown } : {},
|
|
99
|
+
...seo ? { seo } : {},
|
|
100
|
+
...source ? { source } : {},
|
|
101
|
+
...checks ? { checks } : {},
|
|
102
|
+
...cloudflare ? { cloudflare: { ...cloudflare, ...aiSearch ? { aiSearch } : {} } } : {},
|
|
103
|
+
...search !== void 0 ? { search } : {},
|
|
104
|
+
...ai !== void 0 ? { ai } : {},
|
|
105
|
+
...i18n !== void 0 ? { i18n } : {}
|
|
106
|
+
};
|
|
129
107
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
108
|
+
function mergeObject(base, overrides) {
|
|
109
|
+
if (!base && !overrides) return void 0;
|
|
110
|
+
return {
|
|
111
|
+
...base ?? {},
|
|
112
|
+
...overrides ?? {}
|
|
113
|
+
};
|
|
137
114
|
}
|
|
138
|
-
async function fileExists(
|
|
115
|
+
async function fileExists(filePath) {
|
|
139
116
|
try {
|
|
140
|
-
await
|
|
117
|
+
await access(filePath);
|
|
141
118
|
return true;
|
|
142
119
|
} catch {
|
|
143
120
|
return false;
|
|
144
121
|
}
|
|
145
122
|
}
|
|
146
|
-
function ancestors(cwd) {
|
|
147
|
-
const directories = [];
|
|
148
|
-
let current = path.resolve(cwd);
|
|
149
|
-
while (true) {
|
|
150
|
-
directories.push(current);
|
|
151
|
-
const parent = path.dirname(current);
|
|
152
|
-
if (parent === current) return directories;
|
|
153
|
-
current = parent;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function shellQuote(value) {
|
|
157
|
-
return /^[a-zA-Z0-9_./:@-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
158
|
-
}
|
|
159
123
|
|
|
160
124
|
// src/result.ts
|
|
161
125
|
function ok(command, args, message) {
|
|
@@ -166,12 +130,12 @@ function fail(command, args, message) {
|
|
|
166
130
|
}
|
|
167
131
|
|
|
168
132
|
// src/utils.ts
|
|
169
|
-
import { spawn
|
|
170
|
-
import { readFile
|
|
133
|
+
import { spawn } from "child_process";
|
|
134
|
+
import { readFile } from "fs/promises";
|
|
171
135
|
import path2 from "path";
|
|
172
136
|
async function spawnCommand(command, args, env = {}, options = {}) {
|
|
173
137
|
return new Promise((resolve) => {
|
|
174
|
-
const child =
|
|
138
|
+
const child = spawn(command, args, {
|
|
175
139
|
stdio: "inherit",
|
|
176
140
|
shell: process.platform === "win32",
|
|
177
141
|
cwd: options.cwd,
|
|
@@ -226,47 +190,347 @@ async function readOgFonts(args) {
|
|
|
226
190
|
return Promise.all(
|
|
227
191
|
fontPaths.map(async (fontPath, index) => ({
|
|
228
192
|
name: index === 0 ? "Inter" : `SvedocsFont${index + 1}`,
|
|
229
|
-
data: await
|
|
193
|
+
data: await readFile(path2.resolve(process.cwd(), fontPath)),
|
|
230
194
|
weight: 400,
|
|
231
195
|
style: "normal"
|
|
232
196
|
}))
|
|
233
197
|
);
|
|
234
198
|
}
|
|
235
199
|
|
|
236
|
-
// src/commands/
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
"
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
"
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
200
|
+
// src/commands/og.ts
|
|
201
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
202
|
+
import path3 from "path";
|
|
203
|
+
import { createOgImage, createOgImageInput } from "svedocs/og";
|
|
204
|
+
async function runOgCommand(args) {
|
|
205
|
+
const manifest = await loadProjectManifest({ configFile: readOption(args, "--config") });
|
|
206
|
+
const out = path3.resolve(process.cwd(), readOption(args, "--out") ?? (manifest.config.seo.ogImage === false ? "static/og" : manifest.config.seo.ogImage.outDir));
|
|
207
|
+
const format = readOption(args, "--format") ?? (manifest.config.seo.ogImage === false ? "svg" : manifest.config.seo.ogImage.format);
|
|
208
|
+
const renderer = readOption(args, "--renderer") ?? (manifest.config.seo.ogImage === false ? "svg" : manifest.config.seo.ogImage.renderer);
|
|
209
|
+
if (!["svg", "png"].includes(format)) {
|
|
210
|
+
return fail("og", args, "Invalid OG format. Use svg or png.");
|
|
211
|
+
}
|
|
212
|
+
if (!["svg", "satori"].includes(renderer)) {
|
|
213
|
+
return fail("og", args, "Invalid OG renderer. Use svg or satori.");
|
|
214
|
+
}
|
|
215
|
+
const fonts = await readOgFonts(args);
|
|
216
|
+
if (renderer === "satori" && fonts.length === 0) {
|
|
217
|
+
return fail("og", args, "Satori OG rendering requires at least one --font path.");
|
|
218
|
+
}
|
|
219
|
+
const configFile = readOption(args, "--config");
|
|
220
|
+
return generateOgAssets({
|
|
221
|
+
args,
|
|
222
|
+
out,
|
|
223
|
+
format,
|
|
224
|
+
renderer,
|
|
225
|
+
fonts,
|
|
226
|
+
...configFile ? { configFile } : {}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async function runConfiguredOgGeneration(configFile, buildArgs, ogConfig) {
|
|
230
|
+
if (buildArgs.includes("--no-og")) return void 0;
|
|
231
|
+
if (ogConfig === false) return void 0;
|
|
232
|
+
if (ogConfig.renderer === "satori") return void 0;
|
|
233
|
+
const manifest = await loadProjectManifest({ configFile });
|
|
234
|
+
return generateOgAssets({
|
|
235
|
+
args: ["og", "--auto"],
|
|
236
|
+
out: path3.resolve(process.cwd(), ogConfig.outDir),
|
|
237
|
+
format: ogConfig.format,
|
|
238
|
+
renderer: ogConfig.renderer,
|
|
239
|
+
fonts: [],
|
|
240
|
+
...configFile ? { configFile } : {},
|
|
241
|
+
manifest
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async function generateOgAssets(input) {
|
|
245
|
+
const manifest = input.manifest ?? await loadProjectManifest({ configFile: input.configFile });
|
|
246
|
+
const { out, format, renderer, fonts, args } = input;
|
|
247
|
+
await mkdir(out, { recursive: true });
|
|
248
|
+
const written = [];
|
|
249
|
+
for (const page of manifest.pages) {
|
|
250
|
+
if (page.hidden) continue;
|
|
251
|
+
const fileName = `${page.routePath === "/" ? "index" : page.routePath.replace(/^\/+/, "").replace(/[^a-zA-Z0-9]+/g, "-")}.${format}`;
|
|
252
|
+
const destination = path3.join(out, fileName);
|
|
253
|
+
const asset = await createOgImage(createOgImageInput(manifest.config, page), {
|
|
254
|
+
format,
|
|
255
|
+
renderer,
|
|
256
|
+
...manifest.config.seo.ogImage !== false && typeof manifest.config.seo.ogImage.template === "function" ? { template: manifest.config.seo.ogImage.template } : {},
|
|
257
|
+
...fonts.length ? { fonts } : {}
|
|
258
|
+
});
|
|
259
|
+
await writeFile(destination, asset);
|
|
260
|
+
written.push(destination);
|
|
261
|
+
}
|
|
262
|
+
return ok("og", args, `Generated ${written.length} OG ${format.toUpperCase()} files with ${renderer} renderer in ${out}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/commands/build.ts
|
|
266
|
+
async function runBuildCommand(args) {
|
|
267
|
+
const configFile = readOption(args, "--config");
|
|
268
|
+
const config = loadSvedocsConfig(await loadProjectConfig(process.cwd(), configFile) ?? {});
|
|
269
|
+
const mode = readOption(args, "--mode") ?? process.env.SVEDOCS_BUILD_MODE ?? config.build.mode;
|
|
270
|
+
if (!mode || !["edge", "static", "spa"].includes(mode)) {
|
|
271
|
+
return fail("build", args, "Invalid build mode. Use edge, static, or spa.");
|
|
272
|
+
}
|
|
273
|
+
const warning = mode === "spa" ? "SPA mode prerenders known docs pages and writes a static fallback; hosted Search, Ask AI, and other server-only features need an edge runtime.\n" : "";
|
|
274
|
+
const result = await spawnCommand("vite", ["build", ...createViteArgs(args)], {
|
|
275
|
+
SVEDOCS_BUILD_MODE: mode,
|
|
276
|
+
...createConfigEnv(configFile)
|
|
277
|
+
});
|
|
278
|
+
if (!result.ok) return fail("build", args, `${warning}${result.message}`);
|
|
279
|
+
const ogResult = await runConfiguredOgGeneration(configFile, args, config.seo.ogImage);
|
|
280
|
+
const message = ogResult ? `${result.message}
|
|
281
|
+
${ogResult.message}` : result.message;
|
|
282
|
+
return ok("build", args, `${warning}${message}`);
|
|
283
|
+
}
|
|
284
|
+
async function runViteCommand(command, args) {
|
|
285
|
+
const configFile = readOption(args, "--config");
|
|
286
|
+
const result = await spawnCommand("vite", [command, ...createViteArgs(args)], createConfigEnv(configFile));
|
|
287
|
+
return result.ok ? ok(command, args, result.message) : fail(command, args, result.message);
|
|
288
|
+
}
|
|
289
|
+
function createViteArgs(args) {
|
|
290
|
+
const output = [];
|
|
291
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
292
|
+
const arg = args[index];
|
|
293
|
+
if (!arg) continue;
|
|
294
|
+
if (arg === "--") {
|
|
295
|
+
output.push(...args.slice(index + 1));
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
if (arg === "--no-og") continue;
|
|
299
|
+
if (arg === "--mode" || arg === "--config") {
|
|
300
|
+
index += 1;
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
if (arg.startsWith("--mode=") || arg.startsWith("--config=")) continue;
|
|
304
|
+
output.push(arg);
|
|
305
|
+
}
|
|
306
|
+
return output;
|
|
307
|
+
}
|
|
308
|
+
function createConfigEnv(configFile) {
|
|
309
|
+
return configFile ? { SVEDOCS_CONFIG_FILE: configFile } : {};
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// src/commands/check.ts
|
|
313
|
+
import { checkPackagePublication } from "svedocs/core";
|
|
314
|
+
async function runCheckCommand(args) {
|
|
315
|
+
const manifest = await loadProjectManifest({
|
|
316
|
+
configFile: readOption(args, "--config"),
|
|
317
|
+
configOverrides: createCheckConfigOverrides(args)
|
|
318
|
+
});
|
|
319
|
+
const strict = args.includes("--strict");
|
|
320
|
+
const packageIssues = args.includes("--package") ? await checkPackagePublication(process.cwd()) : [];
|
|
321
|
+
const allIssues = [...manifest.issues, ...packageIssues];
|
|
322
|
+
const allErrors = allIssues.filter((issue) => issue.severity === "error");
|
|
323
|
+
const allWarnings = allIssues.filter((issue) => issue.severity === "warning");
|
|
324
|
+
const summary = `svedocs check: ${manifest.pages.length} pages, ${manifest.search.length} search records, ${allErrors.length} errors, ${allWarnings.length} warnings.`;
|
|
325
|
+
const details = allIssues.map((issue) => `${issue.severity.toUpperCase()} ${issue.code}: ${issue.message}`);
|
|
326
|
+
const message = [summary, ...details].join("\n");
|
|
327
|
+
if (allErrors.length > 0 || strict && allWarnings.length > 0) {
|
|
328
|
+
return fail("check", args, message);
|
|
329
|
+
}
|
|
330
|
+
return ok("check", args, message);
|
|
331
|
+
}
|
|
332
|
+
function createCheckConfigOverrides(args) {
|
|
333
|
+
const checks = {};
|
|
334
|
+
if (args.includes("--external-links")) checks.externalLinks = true;
|
|
335
|
+
if (args.includes("--no-assets")) checks.assets = false;
|
|
336
|
+
if (args.includes("--translations")) checks.translations = true;
|
|
337
|
+
return Object.keys(checks).length ? { checks } : void 0;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// src/commands/create.ts
|
|
341
|
+
import { access as access2, cp, mkdir as mkdir2, mkdtemp, readFile as readFile3, readdir, rm, writeFile as writeFile2 } from "fs/promises";
|
|
342
|
+
import { tmpdir } from "os";
|
|
343
|
+
import path5 from "path";
|
|
344
|
+
import { fileURLToPath } from "url";
|
|
345
|
+
import { Command } from "commander";
|
|
346
|
+
|
|
347
|
+
// src/package-manager.ts
|
|
348
|
+
import { spawn as spawn2 } from "child_process";
|
|
349
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
350
|
+
import path4 from "path";
|
|
351
|
+
var packageManagers = ["pnpm", "npm", "yarn", "bun"];
|
|
352
|
+
var fallbackOrder = ["pnpm", "npm", "yarn", "bun"];
|
|
353
|
+
async function resolvePackageManager(options = {}) {
|
|
354
|
+
const requested = normalizePackageManagerName(options.requested);
|
|
355
|
+
if (options.requested && !requested && options.requested !== "auto") {
|
|
356
|
+
throw new Error(`Invalid package manager "${options.requested}". Use auto, pnpm, npm, yarn, or bun.`);
|
|
357
|
+
}
|
|
358
|
+
const readVersion = options.readVersion ?? readPackageManagerVersion;
|
|
359
|
+
if (requested) {
|
|
360
|
+
const version = await readVersion(requested);
|
|
361
|
+
if (!version) throw new Error(`${requested} was requested but was not found on this machine.`);
|
|
362
|
+
return { name: requested, version, source: "explicit" };
|
|
363
|
+
}
|
|
364
|
+
const env = options.env ?? process.env;
|
|
365
|
+
const envManager = detectPackageManagerFromEnv(env);
|
|
366
|
+
const projectManager = await detectPackageManagerFromProject(options.cwd ?? process.cwd());
|
|
367
|
+
for (const candidate of [
|
|
368
|
+
envManager ? { name: envManager, source: "environment" } : void 0,
|
|
369
|
+
projectManager ? { name: projectManager, source: "project" } : void 0
|
|
370
|
+
]) {
|
|
371
|
+
if (!candidate) continue;
|
|
372
|
+
const version = await readVersion(candidate.name);
|
|
373
|
+
if (version) return { ...candidate, version, detected: candidate.name };
|
|
374
|
+
}
|
|
375
|
+
for (const [index, name] of fallbackOrder.entries()) {
|
|
376
|
+
const version = await readVersion(name);
|
|
377
|
+
if (version) {
|
|
378
|
+
const detected = envManager ?? projectManager;
|
|
379
|
+
return {
|
|
380
|
+
name,
|
|
381
|
+
version,
|
|
382
|
+
source: index === 0 ? "default" : "fallback",
|
|
383
|
+
...detected ? { detected } : {}
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
throw new Error("No supported package manager was found. Install pnpm, npm, yarn, or bun and try again.");
|
|
388
|
+
}
|
|
389
|
+
function detectPackageManagerFromEnv(env = process.env) {
|
|
390
|
+
const userAgent = env.npm_config_user_agent;
|
|
391
|
+
const userAgentManager = userAgent ? normalizePackageManagerName(userAgent.split(/[ /]/)[0]) : void 0;
|
|
392
|
+
if (userAgentManager) return userAgentManager;
|
|
393
|
+
const execPath = `${env.npm_execpath ?? ""} ${env.npm_node_execpath ?? ""}`.toLowerCase();
|
|
394
|
+
if (execPath.includes("pnpm")) return "pnpm";
|
|
395
|
+
if (execPath.includes("yarn")) return "yarn";
|
|
396
|
+
if (execPath.includes("bun")) return "bun";
|
|
397
|
+
if (execPath.includes("npm")) return "npm";
|
|
398
|
+
return void 0;
|
|
399
|
+
}
|
|
400
|
+
async function detectPackageManagerFromProject(cwd) {
|
|
401
|
+
for (const directory of ancestors(cwd)) {
|
|
402
|
+
const packageJsonManager = await readPackageManagerField(path4.join(directory, "package.json"));
|
|
403
|
+
if (packageJsonManager) return packageJsonManager;
|
|
404
|
+
if (await fileExists2(path4.join(directory, "pnpm-lock.yaml"))) return "pnpm";
|
|
405
|
+
if (await fileExists2(path4.join(directory, "package-lock.json")) || await fileExists2(path4.join(directory, "npm-shrinkwrap.json"))) return "npm";
|
|
406
|
+
if (await fileExists2(path4.join(directory, "yarn.lock"))) return "yarn";
|
|
407
|
+
if (await fileExists2(path4.join(directory, "bun.lock")) || await fileExists2(path4.join(directory, "bun.lockb"))) return "bun";
|
|
408
|
+
}
|
|
409
|
+
return void 0;
|
|
410
|
+
}
|
|
411
|
+
function createPackageManagerField(choice) {
|
|
412
|
+
return choice.version ? `${choice.name}@${choice.version}` : void 0;
|
|
413
|
+
}
|
|
414
|
+
function createInstallCommand(name) {
|
|
415
|
+
return name === "yarn" ? ["yarn", "install"] : [name, "install"];
|
|
416
|
+
}
|
|
417
|
+
function createRunCommand(name, script) {
|
|
418
|
+
if (name === "npm") return ["npm", "run", script];
|
|
419
|
+
if (name === "bun") return ["bun", "run", script];
|
|
420
|
+
return [name, script];
|
|
421
|
+
}
|
|
422
|
+
function formatCommand(parts) {
|
|
423
|
+
return parts.map(shellQuote).join(" ");
|
|
424
|
+
}
|
|
425
|
+
function describePackageManagerSource(choice) {
|
|
426
|
+
const version = choice.version ? ` ${choice.version}` : "";
|
|
427
|
+
if (choice.source === "explicit") return `${choice.name}${version} selected by --package-manager`;
|
|
428
|
+
if (choice.source === "environment") return `${choice.name}${version} detected from the invoking package manager`;
|
|
429
|
+
if (choice.source === "project") return `${choice.name}${version} detected from the current project`;
|
|
430
|
+
if (choice.source === "fallback") {
|
|
431
|
+
const detected = choice.detected ? ` after ${choice.detected} was unavailable` : "";
|
|
432
|
+
return `${choice.name}${version} selected as fallback${detected}`;
|
|
433
|
+
}
|
|
434
|
+
return `${choice.name}${version} selected as the default package manager`;
|
|
435
|
+
}
|
|
436
|
+
function normalizePackageManagerName(value) {
|
|
437
|
+
if (!value || value === "auto") return void 0;
|
|
438
|
+
const normalized = value.toLowerCase();
|
|
439
|
+
return packageManagers.includes(normalized) ? normalized : void 0;
|
|
440
|
+
}
|
|
441
|
+
async function readPackageManagerVersion(name) {
|
|
442
|
+
return new Promise((resolve) => {
|
|
443
|
+
const child = spawn2(name, ["--version"], {
|
|
444
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
445
|
+
shell: process.platform === "win32"
|
|
446
|
+
});
|
|
447
|
+
let output = "";
|
|
448
|
+
child.stdout.on("data", (chunk) => {
|
|
449
|
+
output += String(chunk);
|
|
450
|
+
});
|
|
451
|
+
child.stderr.on("data", (chunk) => {
|
|
452
|
+
output += String(chunk);
|
|
453
|
+
});
|
|
454
|
+
child.on("close", (code) => {
|
|
455
|
+
resolve(code === 0 ? output.trim().split(/\s+/)[0] : void 0);
|
|
456
|
+
});
|
|
457
|
+
child.on("error", () => {
|
|
458
|
+
resolve(void 0);
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
async function readPackageManagerField(packageJsonPath) {
|
|
463
|
+
try {
|
|
464
|
+
const packageJson = JSON.parse(await readFile2(packageJsonPath, "utf8"));
|
|
465
|
+
return normalizePackageManagerName(packageJson.packageManager?.split("@")[0]);
|
|
466
|
+
} catch {
|
|
467
|
+
return void 0;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
async function fileExists2(file) {
|
|
471
|
+
try {
|
|
472
|
+
await readFile2(file);
|
|
473
|
+
return true;
|
|
474
|
+
} catch {
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
function ancestors(cwd) {
|
|
479
|
+
const directories = [];
|
|
480
|
+
let current = path4.resolve(cwd);
|
|
481
|
+
while (true) {
|
|
482
|
+
directories.push(current);
|
|
483
|
+
const parent = path4.dirname(current);
|
|
484
|
+
if (parent === current) return directories;
|
|
485
|
+
current = parent;
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
function shellQuote(value) {
|
|
489
|
+
return /^[a-zA-Z0-9_./:@-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// src/commands/create.ts
|
|
493
|
+
var templateNames = ["minimal", "docs", "cloudflare"];
|
|
494
|
+
var defaultTemplateRepository = "backrunner/svedocs";
|
|
495
|
+
var defaultTemplateRef = "main";
|
|
496
|
+
function renderCreateSvedocsHelp() {
|
|
497
|
+
return [
|
|
498
|
+
"create-svedocs",
|
|
499
|
+
"",
|
|
500
|
+
"Usage:",
|
|
501
|
+
" create-svedocs [dir]",
|
|
502
|
+
"",
|
|
503
|
+
"Options:",
|
|
504
|
+
" --template <name> minimal, docs, or cloudflare",
|
|
505
|
+
" --package-manager <name>",
|
|
506
|
+
" auto, pnpm, npm, yarn, or bun",
|
|
507
|
+
" --pm <name> Alias for --package-manager",
|
|
508
|
+
" --install Install dependencies after creating the project",
|
|
509
|
+
" --force Allow writing into an existing directory"
|
|
510
|
+
].join("\n");
|
|
511
|
+
}
|
|
512
|
+
async function runCreateSvedocsCli(args, runtime = {}) {
|
|
513
|
+
if (args.includes("--help") || args.includes("-h")) return ok("help", args, renderCreateSvedocsHelp());
|
|
514
|
+
const program = new Command("create-svedocs").argument("[dir]", "target directory", "svedocs-app").option("--template <name>", "template name", "docs").option("--package-manager <name>", "package manager to use: auto, pnpm, npm, yarn, or bun", "auto").option("--pm <name>", "alias for --package-manager").option("--install", "install dependencies after creating the project", false).option("--force", "allow writing into an existing directory").exitOverride();
|
|
515
|
+
try {
|
|
516
|
+
program.parse(args, { from: "user" });
|
|
517
|
+
} catch (error) {
|
|
518
|
+
return fail("create", args, error instanceof Error ? error.message : String(error));
|
|
519
|
+
}
|
|
520
|
+
const target = program.args[0] ?? "svedocs-app";
|
|
521
|
+
const options = program.opts();
|
|
522
|
+
const template = options.template;
|
|
523
|
+
if (!templateNames.includes(template)) {
|
|
524
|
+
return fail("create", args, `Unknown template "${template}". Use minimal, docs, or cloudflare.`);
|
|
525
|
+
}
|
|
526
|
+
let source;
|
|
527
|
+
try {
|
|
528
|
+
source = await resolveTemplateSource(template, runtime);
|
|
529
|
+
} catch (error) {
|
|
530
|
+
return fail("create", args, error instanceof Error ? error.message : String(error));
|
|
531
|
+
}
|
|
532
|
+
const destination = path5.resolve(process.cwd(), target);
|
|
533
|
+
let packageManager;
|
|
270
534
|
try {
|
|
271
535
|
packageManager = await resolvePackageManager({
|
|
272
536
|
requested: options.pm ?? options.packageManager,
|
|
@@ -280,23 +544,28 @@ async function runCreateSvedocsCli(args, runtime = {}) {
|
|
|
280
544
|
if (!options.force && !await isEmptyOrMissingDirectory(destination)) {
|
|
281
545
|
return fail("create", args, `${destination} already exists and is not empty. Re-run with --force to merge template files.`);
|
|
282
546
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
547
|
+
try {
|
|
548
|
+
await mkdir2(destination, { recursive: true });
|
|
549
|
+
await cp(source.directory, destination, { recursive: true, force: Boolean(options.force), errorOnExist: false });
|
|
550
|
+
await rewriteTemplatePackageJson(destination, target, packageManager);
|
|
551
|
+
let installMessage;
|
|
552
|
+
if (options.install) {
|
|
553
|
+
const installCommand = createInstallCommand(packageManager.name);
|
|
554
|
+
const result = await spawnCommand(installCommand[0], installCommand.slice(1), {}, { cwd: destination });
|
|
555
|
+
if (!result.ok) return fail("create", args, result.message);
|
|
556
|
+
installMessage = `Installed dependencies with ${packageManager.name}.`;
|
|
557
|
+
}
|
|
558
|
+
return ok("create", args, renderCreateSuccess({
|
|
559
|
+
destination,
|
|
560
|
+
packageManager,
|
|
561
|
+
template,
|
|
562
|
+
templateSource: source.description,
|
|
563
|
+
installed: Boolean(options.install),
|
|
564
|
+
...installMessage ? { installMessage } : {}
|
|
565
|
+
}));
|
|
566
|
+
} finally {
|
|
567
|
+
await source.cleanup?.();
|
|
568
|
+
}
|
|
300
569
|
}
|
|
301
570
|
async function isEmptyOrMissingDirectory(directory) {
|
|
302
571
|
try {
|
|
@@ -306,34 +575,116 @@ async function isEmptyOrMissingDirectory(directory) {
|
|
|
306
575
|
}
|
|
307
576
|
}
|
|
308
577
|
async function rewriteTemplatePackageJson(directory, target, packageManager) {
|
|
309
|
-
const packageJsonPath =
|
|
578
|
+
const packageJsonPath = path5.join(directory, "package.json");
|
|
310
579
|
const pkg = JSON.parse(await readFile3(packageJsonPath, "utf8"));
|
|
311
580
|
pkg.name = createPackageName(target);
|
|
312
581
|
const packageManagerField = createPackageManagerField(packageManager);
|
|
313
582
|
if (packageManagerField) pkg.packageManager = packageManagerField;
|
|
314
|
-
await
|
|
583
|
+
await writeFile2(packageJsonPath, `${JSON.stringify(pkg, null, 2)}
|
|
315
584
|
`, "utf8");
|
|
316
585
|
}
|
|
317
586
|
function createPackageName(target) {
|
|
318
|
-
const name =
|
|
587
|
+
const name = path5.basename(target).toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
319
588
|
return name || "svedocs-app";
|
|
320
589
|
}
|
|
321
|
-
async function resolveTemplateSource(template) {
|
|
590
|
+
async function resolveTemplateSource(template, runtime) {
|
|
591
|
+
const env = { ...process.env, ...runtime.env };
|
|
592
|
+
const mode = readTemplateSourceMode(env);
|
|
593
|
+
if (mode !== "bundled") {
|
|
594
|
+
try {
|
|
595
|
+
return await downloadGitHubTemplate(template, env, runtime.fetch ?? fetch);
|
|
596
|
+
} catch (error) {
|
|
597
|
+
if (mode === "github") throw error;
|
|
598
|
+
const bundled = await resolveBundledTemplateSource(template);
|
|
599
|
+
return {
|
|
600
|
+
...bundled,
|
|
601
|
+
description: `${bundled.description} fallback (${formatErrorMessage(error)})`
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
return resolveBundledTemplateSource(template);
|
|
606
|
+
}
|
|
607
|
+
async function resolveBundledTemplateSource(template) {
|
|
322
608
|
const candidates = [
|
|
323
609
|
new URL(`../../templates/${template}`, import.meta.url),
|
|
324
610
|
new URL(`../templates/${template}`, import.meta.url)
|
|
325
611
|
].map((url) => fileURLToPath(url));
|
|
326
612
|
for (const candidate of candidates) {
|
|
327
613
|
try {
|
|
328
|
-
await
|
|
329
|
-
return candidate;
|
|
614
|
+
await access2(candidate);
|
|
615
|
+
return { directory: candidate, description: "bundled" };
|
|
330
616
|
} catch {
|
|
331
617
|
}
|
|
332
618
|
}
|
|
333
619
|
throw new Error(`Template "${template}" was not found. Checked: ${candidates.join(", ")}`);
|
|
334
620
|
}
|
|
621
|
+
function readTemplateSourceMode(env) {
|
|
622
|
+
const value = env.SVEDOCS_TEMPLATE_SOURCE ?? "auto";
|
|
623
|
+
if (value === "auto" || value === "github" || value === "bundled") return value;
|
|
624
|
+
throw new Error("Invalid SVEDOCS_TEMPLATE_SOURCE. Use auto, github, or bundled.");
|
|
625
|
+
}
|
|
626
|
+
async function downloadGitHubTemplate(template, env, fetchTemplate) {
|
|
627
|
+
const repository = env.SVEDOCS_TEMPLATE_REPOSITORY ?? defaultTemplateRepository;
|
|
628
|
+
const ref = env.SVEDOCS_TEMPLATE_REF ?? defaultTemplateRef;
|
|
629
|
+
const prefix = `packages/cli/templates/${template}/`;
|
|
630
|
+
const treeUrl = `https://api.github.com/repos/${repository}/git/trees/${encodeURIComponent(ref)}?recursive=1`;
|
|
631
|
+
const tree = await fetchJson(fetchTemplate, treeUrl);
|
|
632
|
+
if (tree.truncated) throw new Error(`GitHub template tree for ${repository}@${ref} is truncated.`);
|
|
633
|
+
const files = (tree.tree ?? []).filter((entry) => entry.type === "blob" && typeof entry.path === "string").filter((entry) => entry.path.startsWith(prefix));
|
|
634
|
+
if (files.length === 0) throw new Error(`GitHub template "${template}" was not found in ${repository}@${ref}.`);
|
|
635
|
+
const tempRoot = await mkdtemp(path5.join(tmpdir(), "svedocs-template-"));
|
|
636
|
+
const cleanup = async () => {
|
|
637
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
638
|
+
};
|
|
639
|
+
try {
|
|
640
|
+
for (const file of files) {
|
|
641
|
+
const relativePath = file.path.slice(prefix.length);
|
|
642
|
+
const outputPath = resolveInside(tempRoot, relativePath);
|
|
643
|
+
await mkdir2(path5.dirname(outputPath), { recursive: true });
|
|
644
|
+
const response = await fetchTemplate(createRawGitHubUrl(repository, ref, file.path), {
|
|
645
|
+
headers: { "User-Agent": "create-svedocs" }
|
|
646
|
+
});
|
|
647
|
+
if (!response.ok) {
|
|
648
|
+
throw new Error(`GitHub raw request failed for ${file.path}: ${response.status} ${response.statusText}`);
|
|
649
|
+
}
|
|
650
|
+
await writeFile2(outputPath, Buffer.from(await response.arrayBuffer()));
|
|
651
|
+
}
|
|
652
|
+
return {
|
|
653
|
+
directory: tempRoot,
|
|
654
|
+
description: `GitHub ${repository}@${ref}`,
|
|
655
|
+
cleanup
|
|
656
|
+
};
|
|
657
|
+
} catch (error) {
|
|
658
|
+
await cleanup();
|
|
659
|
+
throw error;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
async function fetchJson(fetchTemplate, url) {
|
|
663
|
+
const response = await fetchTemplate(url, {
|
|
664
|
+
headers: {
|
|
665
|
+
Accept: "application/vnd.github+json",
|
|
666
|
+
"User-Agent": "create-svedocs"
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
if (!response.ok) throw new Error(`GitHub request failed: ${response.status} ${response.statusText}`);
|
|
670
|
+
return await response.json();
|
|
671
|
+
}
|
|
672
|
+
function createRawGitHubUrl(repository, ref, filePath) {
|
|
673
|
+
const pathSegments = filePath.split("/").map(encodeURIComponent).join("/");
|
|
674
|
+
return `https://raw.githubusercontent.com/${repository}/${encodeURIComponent(ref)}/${pathSegments}`;
|
|
675
|
+
}
|
|
676
|
+
function resolveInside(root, relativePath) {
|
|
677
|
+
const resolved = path5.resolve(root, relativePath);
|
|
678
|
+
if (resolved !== root && !resolved.startsWith(`${root}${path5.sep}`)) {
|
|
679
|
+
throw new Error(`Template path escapes destination: ${relativePath}`);
|
|
680
|
+
}
|
|
681
|
+
return resolved;
|
|
682
|
+
}
|
|
683
|
+
function formatErrorMessage(error) {
|
|
684
|
+
return error instanceof Error ? error.message : String(error);
|
|
685
|
+
}
|
|
335
686
|
function renderCreateSuccess(input) {
|
|
336
|
-
const relativeDestination =
|
|
687
|
+
const relativeDestination = path5.relative(process.cwd(), input.destination) || ".";
|
|
337
688
|
const cdCommand = ["cd", relativeDestination.startsWith("..") ? input.destination : relativeDestination];
|
|
338
689
|
const installCommand = createInstallCommand(input.packageManager.name);
|
|
339
690
|
const devCommand = createRunCommand(input.packageManager.name, "dev");
|
|
@@ -341,6 +692,7 @@ function renderCreateSuccess(input) {
|
|
|
341
692
|
const ssgCommand = createRunCommand(input.packageManager.name, "build:ssg");
|
|
342
693
|
return [
|
|
343
694
|
`Created ${input.template} svedocs project at ${input.destination}`,
|
|
695
|
+
`Template source: ${input.templateSource}.`,
|
|
344
696
|
`Package manager: ${describePackageManagerSource(input.packageManager)}.`,
|
|
345
697
|
...input.installMessage ? [input.installMessage] : [],
|
|
346
698
|
"",
|
|
@@ -356,159 +708,469 @@ function renderCreateSuccess(input) {
|
|
|
356
708
|
}
|
|
357
709
|
|
|
358
710
|
// src/commands/deploy.ts
|
|
359
|
-
import { mkdir as
|
|
360
|
-
import
|
|
711
|
+
import { mkdir as mkdir3, writeFile as writeFile3 } from "fs/promises";
|
|
712
|
+
import path6 from "path";
|
|
361
713
|
import { createCloudflareEnvDts, createWranglerJsonc, createWranglerToml } from "svedocs/cloudflare";
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
"svedocs.config.mts",
|
|
372
|
-
"svedocs.config.js",
|
|
373
|
-
"svedocs.config.mjs"
|
|
374
|
-
];
|
|
375
|
-
async function loadProjectManifest(options = {}) {
|
|
376
|
-
const projectRoot = options.projectRoot ?? process.cwd();
|
|
377
|
-
const config = await loadProjectConfig(projectRoot, options.configFile);
|
|
378
|
-
const merged = mergeSvedocsConfig(config, options.configOverrides);
|
|
379
|
-
return loadSvedocsContent(merged ? { projectRoot, config: merged } : { projectRoot });
|
|
380
|
-
}
|
|
381
|
-
async function loadProjectConfig(projectRoot = process.cwd(), configFile) {
|
|
382
|
-
const resolvedConfigFile = configFile ? path4.resolve(projectRoot, configFile) : await findConfigFile(projectRoot);
|
|
383
|
-
if (!resolvedConfigFile) return void 0;
|
|
384
|
-
try {
|
|
385
|
-
return await loadConfigWithVite(projectRoot, resolvedConfigFile);
|
|
386
|
-
} catch (error) {
|
|
387
|
-
if (!/\.[cm]?js$/.test(resolvedConfigFile)) {
|
|
388
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
389
|
-
throw new Error(`Failed to load ${path4.relative(projectRoot, resolvedConfigFile)}. Install project dependencies first so Vite can load TypeScript config files. ${message}`);
|
|
390
|
-
}
|
|
391
|
-
const module = await import(pathToFileURL(resolvedConfigFile).href);
|
|
392
|
-
return module.default ?? module.config;
|
|
714
|
+
async function runDeployCommand(args) {
|
|
715
|
+
const target = args[0] ?? "cloudflare";
|
|
716
|
+
if (target !== "cloudflare") {
|
|
717
|
+
return fail("deploy", args, `Unknown deploy target "${target}". Use cloudflare.`);
|
|
718
|
+
}
|
|
719
|
+
const write = args.includes("--write");
|
|
720
|
+
const format = readOption(args, "--format") ?? "toml";
|
|
721
|
+
if (!["toml", "jsonc"].includes(format)) {
|
|
722
|
+
return fail("deploy", args, "Invalid Cloudflare config format. Use toml or jsonc.");
|
|
393
723
|
}
|
|
724
|
+
const manifest = await loadProjectManifest({ configFile: readOption(args, "--config") });
|
|
725
|
+
const wrangler = format === "jsonc" ? createWranglerJsonc(manifest.config) : createWranglerToml(manifest.config);
|
|
726
|
+
const wranglerFile = format === "jsonc" ? "wrangler.jsonc" : "wrangler.toml";
|
|
727
|
+
const envDts = createCloudflareEnvDts(manifest.config);
|
|
728
|
+
if (write) {
|
|
729
|
+
await mkdir3(path6.resolve(process.cwd(), "src"), { recursive: true });
|
|
730
|
+
await writeFile3(path6.resolve(process.cwd(), wranglerFile), wrangler, "utf8");
|
|
731
|
+
await writeFile3(path6.resolve(process.cwd(), "src/app.cloudflare.d.ts"), envDts, "utf8");
|
|
732
|
+
return ok("deploy", args, `Wrote ${wranglerFile} and src/app.cloudflare.d.ts for Cloudflare Pages.`);
|
|
733
|
+
}
|
|
734
|
+
return ok("deploy", args, [
|
|
735
|
+
"Cloudflare deploy dry-run passed.",
|
|
736
|
+
"",
|
|
737
|
+
`${wranglerFile}:`,
|
|
738
|
+
wrangler.trim(),
|
|
739
|
+
"",
|
|
740
|
+
`Run with --write to create ${wranglerFile} and Cloudflare platform types.`
|
|
741
|
+
].join("\n"));
|
|
394
742
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
743
|
+
|
|
744
|
+
// src/commands/search-index.ts
|
|
745
|
+
import { mkdir as mkdir4, writeFile as writeFile4 } from "fs/promises";
|
|
746
|
+
import path7 from "path";
|
|
747
|
+
import { syncCloudflareAiSearchIndex } from "svedocs/search";
|
|
748
|
+
async function runIndexCommand(args) {
|
|
749
|
+
const manifest = await loadProjectManifest({ configFile: readOption(args, "--config") });
|
|
750
|
+
const out = readOption(args, "--out");
|
|
751
|
+
const format = readOption(args, "--format") ?? "json";
|
|
752
|
+
const provider = readOption(args, "--provider") ?? manifest.config.search.provider;
|
|
753
|
+
if (!["json", "jsonl"].includes(format)) {
|
|
754
|
+
return fail("index", args, "Invalid index format. Use json or jsonl.");
|
|
399
755
|
}
|
|
400
|
-
|
|
756
|
+
const payload = format === "jsonl" ? manifest.search.map((record) => JSON.stringify(record)).join("\n") : JSON.stringify(manifest.search, null, 2);
|
|
757
|
+
if (provider === "cloudflare-ai-search" && !out) {
|
|
758
|
+
return runCloudflareAiSearchIndex(args, manifest);
|
|
759
|
+
}
|
|
760
|
+
if (out) {
|
|
761
|
+
const destination = path7.resolve(process.cwd(), out);
|
|
762
|
+
await mkdir4(path7.dirname(destination), { recursive: true });
|
|
763
|
+
await writeFile4(destination, `${payload}
|
|
764
|
+
`, "utf8");
|
|
765
|
+
return ok("index", args, `Indexed ${manifest.search.length} records for ${provider} at ${destination}`);
|
|
766
|
+
}
|
|
767
|
+
return ok("index", args, payload);
|
|
401
768
|
}
|
|
402
|
-
async function
|
|
403
|
-
const
|
|
404
|
-
const
|
|
405
|
-
const
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
mode: process.env.NODE_ENV ?? "production"
|
|
410
|
-
},
|
|
411
|
-
configFile,
|
|
412
|
-
projectRoot,
|
|
413
|
-
"silent"
|
|
414
|
-
);
|
|
415
|
-
const config = loaded?.config;
|
|
416
|
-
if (!config || typeof config !== "object") {
|
|
417
|
-
throw new Error(`No config object was exported from ${configFile}.`);
|
|
769
|
+
async function runCloudflareAiSearchIndex(args, manifest) {
|
|
770
|
+
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
771
|
+
const apiToken = process.env.CLOUDFLARE_API_TOKEN;
|
|
772
|
+
const namespace = readOption(args, "--namespace");
|
|
773
|
+
const strategy = readOption(args, "--strategy") ?? "append";
|
|
774
|
+
if (!["append", "replace"].includes(strategy)) {
|
|
775
|
+
return fail("index", args, "Invalid Cloudflare AI Search strategy. Use append or replace.");
|
|
418
776
|
}
|
|
419
|
-
|
|
777
|
+
const existingIds = readCsvOptions(args, "--existing");
|
|
778
|
+
const deleteIds = readOptions(args, "--delete");
|
|
779
|
+
const result = await syncCloudflareAiSearchIndex({
|
|
780
|
+
records: manifest.search,
|
|
781
|
+
instanceName: readOption(args, "--instance") ?? manifest.config.cloudflare.aiSearch.instanceName,
|
|
782
|
+
...accountId ? { accountId } : {},
|
|
783
|
+
...apiToken ? { apiToken } : {},
|
|
784
|
+
...namespace ? { namespace } : {},
|
|
785
|
+
...args.includes("--dry-run") ? { dryRun: true } : {},
|
|
786
|
+
...args.includes("--wait") ? { waitForCompletion: true } : {},
|
|
787
|
+
strategy,
|
|
788
|
+
batchSize: readPositiveIntegerOption(args, "--batch-size") ?? 10,
|
|
789
|
+
maxRetries: readNonNegativeIntegerOption(args, "--retries") ?? 2,
|
|
790
|
+
...existingIds.length ? { existingIds } : {},
|
|
791
|
+
...deleteIds.length ? { deleteIds } : {}
|
|
792
|
+
});
|
|
793
|
+
const details = result.errors.map((error) => `ERROR ${error.id}: ${error.message}`);
|
|
794
|
+
const message = [
|
|
795
|
+
result.dryRun ? `Cloudflare AI Search dry-run: ${result.indexed} uploads and ${result.deleted} deletes planned for ${result.instanceName}.` : `Cloudflare AI Search indexed ${result.indexed} records and deleted ${result.deleted} items for ${result.instanceName}; ${result.failed} failed.`,
|
|
796
|
+
`Strategy: ${strategy}`,
|
|
797
|
+
`Endpoint: ${result.endpoint}`,
|
|
798
|
+
...result.dryRun ? ["Set CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN to upload records."] : [],
|
|
799
|
+
...details
|
|
800
|
+
].join("\n");
|
|
801
|
+
return result.failed > 0 ? fail("index", args, message) : ok("index", args, message);
|
|
420
802
|
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
803
|
+
|
|
804
|
+
// src/commands/upgrade.ts
|
|
805
|
+
import { readFile as readFile5, writeFile as writeFile5 } from "fs/promises";
|
|
806
|
+
import path9 from "path";
|
|
807
|
+
import { Command as Command2 } from "commander";
|
|
808
|
+
|
|
809
|
+
// src/commands/upgrade-compatibility.ts
|
|
810
|
+
var upgradeCompatibilityRules = [];
|
|
811
|
+
function checkUpgradeCompatibility(input) {
|
|
812
|
+
const currentVersion = input.currentSpec ? parseVersionSpec(input.currentSpec) : void 0;
|
|
813
|
+
const targetVersion = parseVersionSpec(input.targetSpec);
|
|
814
|
+
const notices = [];
|
|
815
|
+
const notes = [];
|
|
816
|
+
for (const rule of upgradeCompatibilityRules) {
|
|
817
|
+
const introducedIn = parseVersionSpec(rule.introducedIn);
|
|
818
|
+
if (!introducedIn) continue;
|
|
819
|
+
if (!currentVersion || !targetVersion) {
|
|
820
|
+
notices.push({
|
|
821
|
+
code: "upgrade-span-unknown",
|
|
822
|
+
severity: "warning",
|
|
823
|
+
message: `Cannot fully evaluate ${rule.code} because the current or target version is not concrete.`
|
|
824
|
+
});
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
if (compareVersions(currentVersion, introducedIn) < 0 && compareVersions(targetVersion, introducedIn) >= 0) {
|
|
828
|
+
notices.push({
|
|
829
|
+
code: rule.code,
|
|
830
|
+
severity: rule.severity,
|
|
831
|
+
message: rule.message
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
if (!input.currentSpec) {
|
|
836
|
+
notes.push("No existing svedocs dependency was found; this is treated as a fresh install.");
|
|
837
|
+
} else if (!currentVersion) {
|
|
838
|
+
notes.push(`Current svedocs spec "${input.currentSpec}" from ${input.currentSpecSource} is not a concrete version, so version-span checks are limited.`);
|
|
839
|
+
} else {
|
|
840
|
+
notes.push(`Current svedocs version: ${formatVersion(currentVersion)} from ${input.currentSpecSource}.`);
|
|
841
|
+
}
|
|
842
|
+
if (!targetVersion) {
|
|
843
|
+
notes.push(`Target "${input.targetSpec}" is not a concrete version before installation, so version-span checks are limited.`);
|
|
844
|
+
} else {
|
|
845
|
+
notes.push(`Target svedocs version: ${formatVersion(targetVersion)}.`);
|
|
846
|
+
}
|
|
847
|
+
if (upgradeCompatibilityRules.length === 0) {
|
|
848
|
+
notes.push("No breaking upgrade rules are registered yet.");
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
...currentVersion ? { currentVersion } : {},
|
|
852
|
+
...targetVersion ? { targetVersion } : {},
|
|
853
|
+
notes,
|
|
854
|
+
notices
|
|
465
855
|
};
|
|
466
856
|
}
|
|
467
|
-
function
|
|
468
|
-
|
|
857
|
+
function parseVersionSpec(spec) {
|
|
858
|
+
const match = spec.match(/(?:^|[^0-9])v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/);
|
|
859
|
+
if (!match) return void 0;
|
|
860
|
+
const major = Number(match[1]);
|
|
861
|
+
const minor = Number(match[2]);
|
|
862
|
+
const patch = Number(match[3]);
|
|
863
|
+
if (!Number.isInteger(major) || !Number.isInteger(minor) || !Number.isInteger(patch)) return void 0;
|
|
469
864
|
return {
|
|
470
|
-
|
|
471
|
-
|
|
865
|
+
major,
|
|
866
|
+
minor,
|
|
867
|
+
patch,
|
|
868
|
+
...match[4] ? { prerelease: match[4] } : {}
|
|
472
869
|
};
|
|
473
870
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
return
|
|
478
|
-
} catch {
|
|
479
|
-
return false;
|
|
871
|
+
function compareVersions(left, right) {
|
|
872
|
+
for (const key of ["major", "minor", "patch"]) {
|
|
873
|
+
if (left[key] > right[key]) return 1;
|
|
874
|
+
if (left[key] < right[key]) return -1;
|
|
480
875
|
}
|
|
876
|
+
return 0;
|
|
877
|
+
}
|
|
878
|
+
function formatVersion(version) {
|
|
879
|
+
const core = `${version.major}.${version.minor}.${version.patch}`;
|
|
880
|
+
return version.prerelease ? `${core}-${version.prerelease}` : core;
|
|
481
881
|
}
|
|
482
882
|
|
|
483
|
-
// src/commands/
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
883
|
+
// src/commands/upgrade-dependencies.ts
|
|
884
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
885
|
+
import path8 from "path";
|
|
886
|
+
var dependencySections = [
|
|
887
|
+
"dependencies",
|
|
888
|
+
"devDependencies",
|
|
889
|
+
"optionalDependencies",
|
|
890
|
+
"peerDependencies"
|
|
891
|
+
];
|
|
892
|
+
function createDependencyChanges(pkg, target) {
|
|
893
|
+
return [
|
|
894
|
+
createDependencyChange(pkg, "svedocs", target, "dependencies"),
|
|
895
|
+
createDependencyChange(pkg, "svedocs-cli", target, "devDependencies")
|
|
896
|
+
];
|
|
897
|
+
}
|
|
898
|
+
function applyDependencyChanges(pkg, changes) {
|
|
899
|
+
let changed = false;
|
|
900
|
+
for (const change of changes) {
|
|
901
|
+
if (change.action === "keep") continue;
|
|
902
|
+
const dependencies = ensureDependencySection(pkg, change.section);
|
|
903
|
+
dependencies[change.name] = change.to;
|
|
904
|
+
changed = true;
|
|
488
905
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
906
|
+
return changed;
|
|
907
|
+
}
|
|
908
|
+
function findDependency(pkg, name) {
|
|
909
|
+
for (const section of dependencySections) {
|
|
910
|
+
const dependencies = readDependencySection(pkg, section);
|
|
911
|
+
const spec = dependencies?.[name];
|
|
912
|
+
if (typeof spec === "string") return { section, spec };
|
|
493
913
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
914
|
+
return void 0;
|
|
915
|
+
}
|
|
916
|
+
function createPackageManagerUpgradeCommands(packageManager, target) {
|
|
917
|
+
const framework = createPackageSpec("svedocs", target);
|
|
918
|
+
const cli = createPackageSpec("svedocs-cli", target);
|
|
919
|
+
if (packageManager === "npm") {
|
|
920
|
+
return [
|
|
921
|
+
["npm", "install", "--save", framework],
|
|
922
|
+
["npm", "install", "--save-dev", cli]
|
|
923
|
+
];
|
|
503
924
|
}
|
|
504
|
-
|
|
505
|
-
|
|
925
|
+
if (packageManager === "yarn") {
|
|
926
|
+
return [
|
|
927
|
+
["yarn", "add", framework],
|
|
928
|
+
["yarn", "add", "-D", cli]
|
|
929
|
+
];
|
|
930
|
+
}
|
|
931
|
+
if (packageManager === "bun") {
|
|
932
|
+
return [
|
|
933
|
+
["bun", "add", framework],
|
|
934
|
+
["bun", "add", "--dev", cli]
|
|
935
|
+
];
|
|
936
|
+
}
|
|
937
|
+
return [
|
|
938
|
+
["pnpm", "add", framework],
|
|
939
|
+
["pnpm", "add", "-D", cli]
|
|
940
|
+
];
|
|
941
|
+
}
|
|
942
|
+
async function readInstalledDependencyVersion(projectRoot, name) {
|
|
943
|
+
try {
|
|
944
|
+
const packageJson = JSON.parse(await readFile4(path8.join(projectRoot, "node_modules", name, "package.json"), "utf8"));
|
|
945
|
+
return typeof packageJson.version === "string" ? packageJson.version : void 0;
|
|
946
|
+
} catch {
|
|
947
|
+
return void 0;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
function createDependencyChange(pkg, name, target, fallbackSection) {
|
|
951
|
+
const existing = findDependency(pkg, name);
|
|
952
|
+
const to = createPackageJsonTargetSpec(existing?.spec, target);
|
|
953
|
+
if (!existing) return { action: "add", name, section: fallbackSection, to };
|
|
954
|
+
if (existing.spec === to) {
|
|
955
|
+
return { action: "keep", name, section: existing.section, from: existing.spec, to };
|
|
956
|
+
}
|
|
957
|
+
return { action: "update", name, section: existing.section, from: existing.spec, to };
|
|
958
|
+
}
|
|
959
|
+
function readDependencySection(pkg, section) {
|
|
960
|
+
const value = pkg[section];
|
|
961
|
+
if (!isStringRecord(value)) return void 0;
|
|
962
|
+
return value;
|
|
963
|
+
}
|
|
964
|
+
function ensureDependencySection(pkg, section) {
|
|
965
|
+
const existing = readDependencySection(pkg, section);
|
|
966
|
+
if (existing) return existing;
|
|
967
|
+
const next = {};
|
|
968
|
+
pkg[section] = next;
|
|
969
|
+
return next;
|
|
970
|
+
}
|
|
971
|
+
function createPackageJsonTargetSpec(currentSpec, target) {
|
|
972
|
+
if (!isPlainSemver(target)) return target;
|
|
973
|
+
const rangePrefix = currentSpec?.match(/^[~^]/)?.[0];
|
|
974
|
+
return rangePrefix ? `${rangePrefix}${target}` : target;
|
|
975
|
+
}
|
|
976
|
+
function createPackageSpec(name, target) {
|
|
977
|
+
return `${name}@${target}`;
|
|
978
|
+
}
|
|
979
|
+
function isPlainSemver(target) {
|
|
980
|
+
return /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(target);
|
|
981
|
+
}
|
|
982
|
+
function isStringRecord(value) {
|
|
983
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
984
|
+
return Object.values(value).every((item) => typeof item === "string");
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
// src/commands/upgrade.ts
|
|
988
|
+
function renderUpgradeHelp() {
|
|
989
|
+
return [
|
|
990
|
+
"svedocs upgrade",
|
|
506
991
|
"",
|
|
507
|
-
|
|
508
|
-
|
|
992
|
+
"Usage:",
|
|
993
|
+
" svedocs upgrade [target]",
|
|
509
994
|
"",
|
|
510
|
-
|
|
511
|
-
|
|
995
|
+
"Arguments:",
|
|
996
|
+
" target Version, range, or dist-tag to install. Defaults to latest.",
|
|
997
|
+
"",
|
|
998
|
+
"Options:",
|
|
999
|
+
" --package-manager <name>",
|
|
1000
|
+
" auto, pnpm, npm, yarn, or bun",
|
|
1001
|
+
" --pm <name> Alias for --package-manager",
|
|
1002
|
+
" --no-install Rewrite package.json without running the package manager",
|
|
1003
|
+
" --dry-run Print planned changes without writing files or installing",
|
|
1004
|
+
" --check-only Run compatibility checks without changing dependencies",
|
|
1005
|
+
" --force Continue through compatibility blockers"
|
|
1006
|
+
].join("\n");
|
|
1007
|
+
}
|
|
1008
|
+
async function runUpgradeCommand(args) {
|
|
1009
|
+
if (args.includes("--help") || args.includes("-h")) return ok("help", args, renderUpgradeHelp());
|
|
1010
|
+
const program = new Command2("svedocs upgrade").argument("[target]", "target svedocs version, range, or dist-tag", "latest").option("--package-manager <name>", "package manager to use: auto, pnpm, npm, yarn, or bun", "auto").option("--pm <name>", "alias for --package-manager").option("--no-install", "rewrite package.json without running the package manager").option("--dry-run", "print planned changes without writing files or installing", false).option("--check-only", "run compatibility checks without changing dependencies", false).option("--force", "continue through compatibility blockers", false).exitOverride();
|
|
1011
|
+
try {
|
|
1012
|
+
program.parse(args, { from: "user" });
|
|
1013
|
+
} catch (error) {
|
|
1014
|
+
return fail("upgrade", args, formatErrorMessage2(error));
|
|
1015
|
+
}
|
|
1016
|
+
const options = program.opts();
|
|
1017
|
+
const target = program.args[0] ?? "latest";
|
|
1018
|
+
const requestedPackageManager = options.pm ?? options.packageManager;
|
|
1019
|
+
if (!isValidTargetSpec(target)) {
|
|
1020
|
+
return fail("upgrade", args, "Invalid target. Use a version, dist-tag, or package spec without whitespace.");
|
|
1021
|
+
}
|
|
1022
|
+
if (requestedPackageManager !== "auto" && !normalizePackageManagerName(requestedPackageManager)) {
|
|
1023
|
+
return fail("upgrade", args, `Invalid package manager "${requestedPackageManager}". Use auto, pnpm, npm, yarn, or bun.`);
|
|
1024
|
+
}
|
|
1025
|
+
const project = await readUpgradeProject(process.cwd());
|
|
1026
|
+
if (!project.ok) return fail("upgrade", args, project.message);
|
|
1027
|
+
const currentDependency = findDependency(project.pkg, "svedocs");
|
|
1028
|
+
const installedVersion = await readInstalledDependencyVersion(project.root, "svedocs");
|
|
1029
|
+
const currentSpec = installedVersion ?? currentDependency?.spec;
|
|
1030
|
+
const compatibility = checkUpgradeCompatibility({
|
|
1031
|
+
currentSpecSource: installedVersion ? "installed package" : currentDependency ? "package.json" : "missing dependency",
|
|
1032
|
+
targetSpec: target,
|
|
1033
|
+
...currentSpec ? { currentSpec } : {}
|
|
1034
|
+
});
|
|
1035
|
+
const changes = createDependencyChanges(project.pkg, target);
|
|
1036
|
+
if (compatibility.notices.some((notice) => notice.severity === "error") && !options.force) {
|
|
1037
|
+
return fail("upgrade", args, renderUpgradeReport({
|
|
1038
|
+
heading: `svedocs upgrade blocked for ${project.packageJsonPath}.`,
|
|
1039
|
+
target,
|
|
1040
|
+
changes,
|
|
1041
|
+
compatibility,
|
|
1042
|
+
footer: "Re-run with --force after reviewing the compatibility notes."
|
|
1043
|
+
}));
|
|
1044
|
+
}
|
|
1045
|
+
if (options.checkOnly) {
|
|
1046
|
+
return ok("upgrade", args, renderUpgradeReport({
|
|
1047
|
+
heading: `svedocs upgrade check for ${project.packageJsonPath}.`,
|
|
1048
|
+
target,
|
|
1049
|
+
changes,
|
|
1050
|
+
compatibility,
|
|
1051
|
+
footer: "No dependency changes were written."
|
|
1052
|
+
}));
|
|
1053
|
+
}
|
|
1054
|
+
if (options.dryRun) {
|
|
1055
|
+
return ok("upgrade", args, renderUpgradeReport({
|
|
1056
|
+
heading: `svedocs upgrade dry-run for ${project.packageJsonPath}.`,
|
|
1057
|
+
target,
|
|
1058
|
+
changes,
|
|
1059
|
+
compatibility,
|
|
1060
|
+
footer: "No files were changed."
|
|
1061
|
+
}));
|
|
1062
|
+
}
|
|
1063
|
+
if (!options.install) {
|
|
1064
|
+
const changed = applyDependencyChanges(project.pkg, changes);
|
|
1065
|
+
if (changed) await writeFile5(project.packageJsonPath, `${JSON.stringify(project.pkg, null, 2)}
|
|
1066
|
+
`, "utf8");
|
|
1067
|
+
const installHint = await renderInstallHint(project.root);
|
|
1068
|
+
return ok("upgrade", args, renderUpgradeReport({
|
|
1069
|
+
heading: changed ? `Updated ${project.packageJsonPath}.` : `${project.packageJsonPath} already targets ${target}.`,
|
|
1070
|
+
target,
|
|
1071
|
+
changes,
|
|
1072
|
+
compatibility,
|
|
1073
|
+
footer: `Install skipped. ${installHint}`
|
|
1074
|
+
}));
|
|
1075
|
+
}
|
|
1076
|
+
return runPackageManagerUpgrade({
|
|
1077
|
+
args,
|
|
1078
|
+
changes,
|
|
1079
|
+
compatibility,
|
|
1080
|
+
project,
|
|
1081
|
+
requestedPackageManager,
|
|
1082
|
+
target
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
async function runPackageManagerUpgrade(input) {
|
|
1086
|
+
let packageManager;
|
|
1087
|
+
try {
|
|
1088
|
+
packageManager = await resolvePackageManager({
|
|
1089
|
+
requested: input.requestedPackageManager,
|
|
1090
|
+
cwd: input.project.root
|
|
1091
|
+
});
|
|
1092
|
+
} catch (error) {
|
|
1093
|
+
return fail("upgrade", input.args, formatErrorMessage2(error));
|
|
1094
|
+
}
|
|
1095
|
+
const commands = createPackageManagerUpgradeCommands(packageManager.name, input.target);
|
|
1096
|
+
for (const command of commands) {
|
|
1097
|
+
const [bin, ...commandArgs] = command;
|
|
1098
|
+
const result = await spawnCommand(bin, commandArgs, {}, { cwd: input.project.root });
|
|
1099
|
+
if (!result.ok) {
|
|
1100
|
+
return fail("upgrade", input.args, renderUpgradeReport({
|
|
1101
|
+
heading: `svedocs upgrade failed while running ${formatCommand(command)}.`,
|
|
1102
|
+
target: input.target,
|
|
1103
|
+
changes: input.changes,
|
|
1104
|
+
compatibility: input.compatibility,
|
|
1105
|
+
footer: result.message
|
|
1106
|
+
}));
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
return ok("upgrade", input.args, renderUpgradeReport({
|
|
1110
|
+
heading: `Updated svedocs dependencies with ${describePackageManagerSource(packageManager)}.`,
|
|
1111
|
+
target: input.target,
|
|
1112
|
+
changes: input.changes,
|
|
1113
|
+
compatibility: input.compatibility,
|
|
1114
|
+
footer: [
|
|
1115
|
+
"Ran:",
|
|
1116
|
+
...commands.map((command) => ` ${formatCommand(command)}`)
|
|
1117
|
+
].join("\n")
|
|
1118
|
+
}));
|
|
1119
|
+
}
|
|
1120
|
+
async function readUpgradeProject(cwd) {
|
|
1121
|
+
const packageJsonPath = await findProjectPackageJson(cwd);
|
|
1122
|
+
if (!packageJsonPath) return { ok: false, message: "No package.json was found in this directory or its parents." };
|
|
1123
|
+
try {
|
|
1124
|
+
const pkg = JSON.parse(await readFile5(packageJsonPath, "utf8"));
|
|
1125
|
+
return { ok: true, packageJsonPath, pkg, root: path9.dirname(packageJsonPath) };
|
|
1126
|
+
} catch (error) {
|
|
1127
|
+
return { ok: false, message: `Could not read ${packageJsonPath}: ${formatErrorMessage2(error)}` };
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
function renderUpgradeReport(input) {
|
|
1131
|
+
const notices = input.compatibility.notices.map((notice) => {
|
|
1132
|
+
return `${notice.severity.toUpperCase()} ${notice.code}: ${notice.message}`;
|
|
1133
|
+
});
|
|
1134
|
+
return [
|
|
1135
|
+
input.heading,
|
|
1136
|
+
`Target: ${input.target}`,
|
|
1137
|
+
"Dependency plan:",
|
|
1138
|
+
...input.changes.map(formatDependencyChange),
|
|
1139
|
+
"Compatibility:",
|
|
1140
|
+
...input.compatibility.notes.map((note) => ` ${note}`),
|
|
1141
|
+
...notices.map((notice) => ` ${notice}`),
|
|
1142
|
+
...input.footer ? [input.footer] : []
|
|
1143
|
+
].join("\n");
|
|
1144
|
+
}
|
|
1145
|
+
function formatDependencyChange(change) {
|
|
1146
|
+
const from = change.from ?? "(missing)";
|
|
1147
|
+
if (change.action === "keep") return ` ${change.section}.${change.name}: ${change.to} (already set)`;
|
|
1148
|
+
return ` ${change.section}.${change.name}: ${from} -> ${change.to}`;
|
|
1149
|
+
}
|
|
1150
|
+
async function renderInstallHint(projectRoot) {
|
|
1151
|
+
const packageManager = await detectPackageManagerFromProject(projectRoot);
|
|
1152
|
+
if (!packageManager) return "Run your package manager install command to refresh the lockfile.";
|
|
1153
|
+
return `Run ${formatCommand(createInstallCommand(packageManager))} to refresh the lockfile.`;
|
|
1154
|
+
}
|
|
1155
|
+
async function findProjectPackageJson(cwd) {
|
|
1156
|
+
let current = path9.resolve(cwd);
|
|
1157
|
+
while (true) {
|
|
1158
|
+
const packageJsonPath = path9.join(current, "package.json");
|
|
1159
|
+
try {
|
|
1160
|
+
await readFile5(packageJsonPath);
|
|
1161
|
+
return packageJsonPath;
|
|
1162
|
+
} catch {
|
|
1163
|
+
const parent = path9.dirname(current);
|
|
1164
|
+
if (parent === current) return void 0;
|
|
1165
|
+
current = parent;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
function isValidTargetSpec(target) {
|
|
1170
|
+
return target.trim().length > 0 && !/\s/.test(target);
|
|
1171
|
+
}
|
|
1172
|
+
function formatErrorMessage2(error) {
|
|
1173
|
+
return error instanceof Error ? error.message : String(error);
|
|
512
1174
|
}
|
|
513
1175
|
|
|
514
1176
|
// src/index.ts
|
|
@@ -518,6 +1180,7 @@ function renderSvedocsHelp() {
|
|
|
518
1180
|
"",
|
|
519
1181
|
"Commands:",
|
|
520
1182
|
" create Create a new svedocs project",
|
|
1183
|
+
" upgrade Upgrade svedocs dependencies",
|
|
521
1184
|
" dev Start the development server",
|
|
522
1185
|
" build Build the documentation site",
|
|
523
1186
|
" ssg Build a static SSG site",
|
|
@@ -548,6 +1211,7 @@ async function runSvedocsCli(args) {
|
|
|
548
1211
|
const command = args[0] ?? "help";
|
|
549
1212
|
if (command === "help" || command === "--help" || command === "-h") return ok("help", args, renderSvedocsHelp());
|
|
550
1213
|
if (command === "create") return runCreateSvedocsCli(args.slice(1));
|
|
1214
|
+
if (command === "upgrade") return runUpgradeCommand(args.slice(1));
|
|
551
1215
|
if (command === "dev") return runViteCommand("dev", args.slice(1));
|
|
552
1216
|
if (command === "preview") return runViteCommand("preview", args.slice(1));
|
|
553
1217
|
if (command === "build") return runBuildCommand(args.slice(1));
|
|
@@ -560,192 +1224,11 @@ async function runSvedocsCli(args) {
|
|
|
560
1224
|
|
|
561
1225
|
${renderSvedocsHelp()}`);
|
|
562
1226
|
}
|
|
563
|
-
async function runBuildCommand(args) {
|
|
564
|
-
const configFile = readOption(args, "--config");
|
|
565
|
-
const config = loadSvedocsConfig(await loadProjectConfig(process.cwd(), configFile) ?? {});
|
|
566
|
-
const mode = readOption(args, "--mode") ?? process.env.SVEDOCS_BUILD_MODE ?? config.build.mode;
|
|
567
|
-
if (!mode || !["edge", "static", "spa"].includes(mode)) {
|
|
568
|
-
return fail("build", args, "Invalid build mode. Use edge, static, or spa.");
|
|
569
|
-
}
|
|
570
|
-
const warning = mode === "spa" ? "SPA mode prerenders known docs pages and writes a static fallback; hosted Search, Ask AI, and other server-only features need an edge runtime.\n" : "";
|
|
571
|
-
const result = await spawnCommand("vite", ["build", ...createViteArgs(args)], {
|
|
572
|
-
SVEDOCS_BUILD_MODE: mode,
|
|
573
|
-
...createConfigEnv(configFile)
|
|
574
|
-
});
|
|
575
|
-
if (!result.ok) return fail("build", args, `${warning}${result.message}`);
|
|
576
|
-
const ogResult = await runConfiguredOgGeneration(configFile, args, config.seo.ogImage);
|
|
577
|
-
const message = ogResult ? `${result.message}
|
|
578
|
-
${ogResult.message}` : result.message;
|
|
579
|
-
return ok("build", args, `${warning}${message}`);
|
|
580
|
-
}
|
|
581
|
-
async function runViteCommand(command, args) {
|
|
582
|
-
const configFile = readOption(args, "--config");
|
|
583
|
-
const result = await spawnCommand("vite", [command, ...createViteArgs(args)], createConfigEnv(configFile));
|
|
584
|
-
return result.ok ? ok(command, args, result.message) : fail(command, args, result.message);
|
|
585
|
-
}
|
|
586
|
-
async function runCheckCommand(args) {
|
|
587
|
-
const manifest = await loadProjectManifest({
|
|
588
|
-
configFile: readOption(args, "--config"),
|
|
589
|
-
configOverrides: createCheckConfigOverrides(args)
|
|
590
|
-
});
|
|
591
|
-
const strict = args.includes("--strict");
|
|
592
|
-
const packageIssues = args.includes("--package") ? await checkPackagePublication(process.cwd()) : [];
|
|
593
|
-
const allIssues = [...manifest.issues, ...packageIssues];
|
|
594
|
-
const allErrors = allIssues.filter((issue) => issue.severity === "error");
|
|
595
|
-
const allWarnings = allIssues.filter((issue) => issue.severity === "warning");
|
|
596
|
-
const summary = `svedocs check: ${manifest.pages.length} pages, ${manifest.search.length} search records, ${allErrors.length} errors, ${allWarnings.length} warnings.`;
|
|
597
|
-
const details = allIssues.map((issue) => `${issue.severity.toUpperCase()} ${issue.code}: ${issue.message}`);
|
|
598
|
-
const message = [summary, ...details].join("\n");
|
|
599
|
-
if (allErrors.length > 0 || strict && allWarnings.length > 0) {
|
|
600
|
-
return fail("check", args, message);
|
|
601
|
-
}
|
|
602
|
-
return ok("check", args, message);
|
|
603
|
-
}
|
|
604
|
-
function createCheckConfigOverrides(args) {
|
|
605
|
-
const checks = {};
|
|
606
|
-
if (args.includes("--external-links")) checks.externalLinks = true;
|
|
607
|
-
if (args.includes("--no-assets")) checks.assets = false;
|
|
608
|
-
if (args.includes("--translations")) checks.translations = true;
|
|
609
|
-
return Object.keys(checks).length ? { checks } : void 0;
|
|
610
|
-
}
|
|
611
|
-
function createViteArgs(args) {
|
|
612
|
-
const output = [];
|
|
613
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
614
|
-
const arg = args[index];
|
|
615
|
-
if (!arg) continue;
|
|
616
|
-
if (arg === "--") {
|
|
617
|
-
output.push(...args.slice(index + 1));
|
|
618
|
-
break;
|
|
619
|
-
}
|
|
620
|
-
if (arg === "--no-og") continue;
|
|
621
|
-
if (arg === "--mode" || arg === "--config") {
|
|
622
|
-
index += 1;
|
|
623
|
-
continue;
|
|
624
|
-
}
|
|
625
|
-
if (arg.startsWith("--mode=") || arg.startsWith("--config=")) continue;
|
|
626
|
-
output.push(arg);
|
|
627
|
-
}
|
|
628
|
-
return output;
|
|
629
|
-
}
|
|
630
|
-
function createConfigEnv(configFile) {
|
|
631
|
-
return configFile ? { SVEDOCS_CONFIG_FILE: configFile } : {};
|
|
632
|
-
}
|
|
633
|
-
async function runIndexCommand(args) {
|
|
634
|
-
const manifest = await loadProjectManifest({ configFile: readOption(args, "--config") });
|
|
635
|
-
const out = readOption(args, "--out");
|
|
636
|
-
const format = readOption(args, "--format") ?? "json";
|
|
637
|
-
const provider = readOption(args, "--provider") ?? manifest.config.search.provider;
|
|
638
|
-
if (!["json", "jsonl"].includes(format)) {
|
|
639
|
-
return fail("index", args, "Invalid index format. Use json or jsonl.");
|
|
640
|
-
}
|
|
641
|
-
const payload = format === "jsonl" ? manifest.search.map((record) => JSON.stringify(record)).join("\n") : JSON.stringify(manifest.search, null, 2);
|
|
642
|
-
if (provider === "cloudflare-ai-search" && !out) {
|
|
643
|
-
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
644
|
-
const apiToken = process.env.CLOUDFLARE_API_TOKEN;
|
|
645
|
-
const namespace = readOption(args, "--namespace");
|
|
646
|
-
const strategy = readOption(args, "--strategy") ?? "append";
|
|
647
|
-
if (!["append", "replace"].includes(strategy)) {
|
|
648
|
-
return fail("index", args, "Invalid Cloudflare AI Search strategy. Use append or replace.");
|
|
649
|
-
}
|
|
650
|
-
const existingIds = readCsvOptions(args, "--existing");
|
|
651
|
-
const deleteIds = readOptions(args, "--delete");
|
|
652
|
-
const result = await syncCloudflareAiSearchIndex({
|
|
653
|
-
records: manifest.search,
|
|
654
|
-
instanceName: readOption(args, "--instance") ?? manifest.config.cloudflare.aiSearch.instanceName,
|
|
655
|
-
...accountId ? { accountId } : {},
|
|
656
|
-
...apiToken ? { apiToken } : {},
|
|
657
|
-
...namespace ? { namespace } : {},
|
|
658
|
-
...args.includes("--dry-run") ? { dryRun: true } : {},
|
|
659
|
-
...args.includes("--wait") ? { waitForCompletion: true } : {},
|
|
660
|
-
strategy,
|
|
661
|
-
batchSize: readPositiveIntegerOption(args, "--batch-size") ?? 10,
|
|
662
|
-
maxRetries: readNonNegativeIntegerOption(args, "--retries") ?? 2,
|
|
663
|
-
...existingIds.length ? { existingIds } : {},
|
|
664
|
-
...deleteIds.length ? { deleteIds } : {}
|
|
665
|
-
});
|
|
666
|
-
const details = result.errors.map((error) => `ERROR ${error.id}: ${error.message}`);
|
|
667
|
-
const message = [
|
|
668
|
-
result.dryRun ? `Cloudflare AI Search dry-run: ${result.indexed} uploads and ${result.deleted} deletes planned for ${result.instanceName}.` : `Cloudflare AI Search indexed ${result.indexed} records and deleted ${result.deleted} items for ${result.instanceName}; ${result.failed} failed.`,
|
|
669
|
-
`Strategy: ${strategy}`,
|
|
670
|
-
`Endpoint: ${result.endpoint}`,
|
|
671
|
-
...result.dryRun ? ["Set CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_API_TOKEN to upload records."] : [],
|
|
672
|
-
...details
|
|
673
|
-
].join("\n");
|
|
674
|
-
return result.failed > 0 ? fail("index", args, message) : ok("index", args, message);
|
|
675
|
-
}
|
|
676
|
-
if (out) {
|
|
677
|
-
const destination = path6.resolve(process.cwd(), out);
|
|
678
|
-
await mkdir3(path6.dirname(destination), { recursive: true });
|
|
679
|
-
await writeFile3(destination, `${payload}
|
|
680
|
-
`, "utf8");
|
|
681
|
-
return ok("index", args, `Indexed ${manifest.search.length} records for ${provider} at ${destination}`);
|
|
682
|
-
}
|
|
683
|
-
return ok("index", args, payload);
|
|
684
|
-
}
|
|
685
|
-
async function runOgCommand(args) {
|
|
686
|
-
const manifest = await loadProjectManifest({ configFile: readOption(args, "--config") });
|
|
687
|
-
const out = path6.resolve(process.cwd(), readOption(args, "--out") ?? (manifest.config.seo.ogImage === false ? "static/og" : manifest.config.seo.ogImage.outDir));
|
|
688
|
-
const format = readOption(args, "--format") ?? (manifest.config.seo.ogImage === false ? "svg" : manifest.config.seo.ogImage.format);
|
|
689
|
-
const renderer = readOption(args, "--renderer") ?? (manifest.config.seo.ogImage === false ? "svg" : manifest.config.seo.ogImage.renderer);
|
|
690
|
-
if (!["svg", "png"].includes(format)) {
|
|
691
|
-
return fail("og", args, "Invalid OG format. Use svg or png.");
|
|
692
|
-
}
|
|
693
|
-
if (!["svg", "satori"].includes(renderer)) {
|
|
694
|
-
return fail("og", args, "Invalid OG renderer. Use svg or satori.");
|
|
695
|
-
}
|
|
696
|
-
const fonts = await readOgFonts(args);
|
|
697
|
-
if (renderer === "satori" && fonts.length === 0) {
|
|
698
|
-
return fail("og", args, "Satori OG rendering requires at least one --font path.");
|
|
699
|
-
}
|
|
700
|
-
const configFile = readOption(args, "--config");
|
|
701
|
-
return generateOgAssets({
|
|
702
|
-
args,
|
|
703
|
-
out,
|
|
704
|
-
format,
|
|
705
|
-
renderer,
|
|
706
|
-
fonts,
|
|
707
|
-
...configFile ? { configFile } : {}
|
|
708
|
-
});
|
|
709
|
-
}
|
|
710
|
-
async function runConfiguredOgGeneration(configFile, buildArgs, ogConfig) {
|
|
711
|
-
if (buildArgs.includes("--no-og")) return void 0;
|
|
712
|
-
if (ogConfig === false) return void 0;
|
|
713
|
-
if (ogConfig.renderer === "satori") return void 0;
|
|
714
|
-
const manifest = await loadProjectManifest({ configFile });
|
|
715
|
-
return generateOgAssets({
|
|
716
|
-
args: ["og", "--auto"],
|
|
717
|
-
out: path6.resolve(process.cwd(), ogConfig.outDir),
|
|
718
|
-
format: ogConfig.format,
|
|
719
|
-
renderer: ogConfig.renderer,
|
|
720
|
-
fonts: [],
|
|
721
|
-
...configFile ? { configFile } : {},
|
|
722
|
-
manifest
|
|
723
|
-
});
|
|
724
|
-
}
|
|
725
|
-
async function generateOgAssets(input) {
|
|
726
|
-
const manifest = input.manifest ?? await loadProjectManifest({ configFile: input.configFile });
|
|
727
|
-
const { out, format, renderer, fonts, args } = input;
|
|
728
|
-
await mkdir3(out, { recursive: true });
|
|
729
|
-
const written = [];
|
|
730
|
-
for (const page of manifest.pages) {
|
|
731
|
-
if (page.hidden) continue;
|
|
732
|
-
const fileName = `${page.routePath === "/" ? "index" : page.routePath.replace(/^\/+/, "").replace(/[^a-zA-Z0-9]+/g, "-")}.${format}`;
|
|
733
|
-
const destination = path6.join(out, fileName);
|
|
734
|
-
const asset = await createOgImage(createOgImageInput(manifest.config, page), {
|
|
735
|
-
format,
|
|
736
|
-
renderer,
|
|
737
|
-
...manifest.config.seo.ogImage !== false && typeof manifest.config.seo.ogImage.template === "function" ? { template: manifest.config.seo.ogImage.template } : {},
|
|
738
|
-
...fonts.length ? { fonts } : {}
|
|
739
|
-
});
|
|
740
|
-
await writeFile3(destination, asset);
|
|
741
|
-
written.push(destination);
|
|
742
|
-
}
|
|
743
|
-
return ok("og", args, `Generated ${written.length} OG ${format.toUpperCase()} files with ${renderer} renderer in ${out}`);
|
|
744
|
-
}
|
|
745
1227
|
|
|
746
1228
|
export {
|
|
747
1229
|
renderCreateSvedocsHelp,
|
|
748
1230
|
runCreateSvedocsCli,
|
|
1231
|
+
renderUpgradeHelp,
|
|
749
1232
|
renderSvedocsHelp,
|
|
750
1233
|
runSvedocsCli
|
|
751
1234
|
};
|