openmanual 0.1.0
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/LICENSE +21 -0
- package/README.md +248 -0
- package/dist/bin.js +733 -0
- package/dist/bin.js.map +1 -0
- package/dist/index.d.ts +186 -0
- package/dist/index.js +127 -0
- package/dist/index.js.map +1 -0
- package/package.json +80 -0
package/dist/bin.js
ADDED
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
// src/cli/bin.ts
|
|
5
|
+
import { Command as Command4 } from "commander";
|
|
6
|
+
|
|
7
|
+
// src/cli/commands/build.ts
|
|
8
|
+
import { spawn as spawn2 } from "child_process";
|
|
9
|
+
import { cp, mkdir as mkdir3 } from "fs/promises";
|
|
10
|
+
import { resolve as resolve3 } from "path";
|
|
11
|
+
import { Command } from "commander";
|
|
12
|
+
|
|
13
|
+
// src/core/config/loader.ts
|
|
14
|
+
import { readFile } from "fs/promises";
|
|
15
|
+
import { join } from "path";
|
|
16
|
+
|
|
17
|
+
// src/core/config/schema.ts
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
var NavbarSchema = z.object({
|
|
20
|
+
logo: z.string().optional(),
|
|
21
|
+
github: z.string().url().optional(),
|
|
22
|
+
links: z.array(
|
|
23
|
+
z.object({
|
|
24
|
+
label: z.string(),
|
|
25
|
+
href: z.string()
|
|
26
|
+
})
|
|
27
|
+
).optional()
|
|
28
|
+
});
|
|
29
|
+
var FooterSchema = z.object({
|
|
30
|
+
text: z.string().optional()
|
|
31
|
+
});
|
|
32
|
+
var SidebarPageSchema = z.object({
|
|
33
|
+
slug: z.string(),
|
|
34
|
+
title: z.string(),
|
|
35
|
+
icon: z.string().optional()
|
|
36
|
+
});
|
|
37
|
+
var SidebarGroupSchema = z.object({
|
|
38
|
+
group: z.string(),
|
|
39
|
+
icon: z.string().optional(),
|
|
40
|
+
collapsed: z.boolean().optional(),
|
|
41
|
+
pages: z.array(SidebarPageSchema)
|
|
42
|
+
});
|
|
43
|
+
var ThemeSchema = z.object({
|
|
44
|
+
primaryHue: z.number().min(0).max(360).optional(),
|
|
45
|
+
darkMode: z.boolean().optional()
|
|
46
|
+
});
|
|
47
|
+
var SearchSchema = z.object({
|
|
48
|
+
enabled: z.boolean().optional()
|
|
49
|
+
});
|
|
50
|
+
var MdxSchema = z.object({
|
|
51
|
+
latex: z.boolean().optional()
|
|
52
|
+
});
|
|
53
|
+
var OpenManualConfigSchema = z.object({
|
|
54
|
+
name: z.string().min(1),
|
|
55
|
+
description: z.string().optional(),
|
|
56
|
+
contentDir: z.string().optional(),
|
|
57
|
+
outputDir: z.string().optional(),
|
|
58
|
+
siteUrl: z.string().url().optional(),
|
|
59
|
+
locale: z.string().optional(),
|
|
60
|
+
navbar: NavbarSchema.optional(),
|
|
61
|
+
footer: FooterSchema.optional(),
|
|
62
|
+
sidebar: z.array(SidebarGroupSchema).optional(),
|
|
63
|
+
theme: ThemeSchema.optional(),
|
|
64
|
+
search: SearchSchema.optional(),
|
|
65
|
+
mdx: MdxSchema.optional()
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// src/core/config/loader.ts
|
|
69
|
+
var DEFAULT_CONFIG = {
|
|
70
|
+
contentDir: "content",
|
|
71
|
+
outputDir: "dist",
|
|
72
|
+
locale: "zh",
|
|
73
|
+
navbar: {},
|
|
74
|
+
footer: {},
|
|
75
|
+
theme: {
|
|
76
|
+
primaryHue: 220,
|
|
77
|
+
darkMode: true
|
|
78
|
+
},
|
|
79
|
+
search: {
|
|
80
|
+
enabled: true
|
|
81
|
+
},
|
|
82
|
+
mdx: {}
|
|
83
|
+
};
|
|
84
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
85
|
+
const configPath = join(cwd, "openmanual.json");
|
|
86
|
+
let rawJson;
|
|
87
|
+
try {
|
|
88
|
+
rawJson = await readFile(configPath, "utf-8");
|
|
89
|
+
} catch {
|
|
90
|
+
throw new Error(`openmanual.json not found in ${cwd}. Please create one.`);
|
|
91
|
+
}
|
|
92
|
+
let parsed;
|
|
93
|
+
try {
|
|
94
|
+
parsed = JSON.parse(rawJson);
|
|
95
|
+
} catch {
|
|
96
|
+
throw new Error("openmanual.json is not valid JSON.");
|
|
97
|
+
}
|
|
98
|
+
const result = OpenManualConfigSchema.safeParse(parsed);
|
|
99
|
+
if (!result.success) {
|
|
100
|
+
const errors = result.error.issues.map((i) => ` - ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
101
|
+
throw new Error(`openmanual.json validation failed:
|
|
102
|
+
${errors}`);
|
|
103
|
+
}
|
|
104
|
+
return mergeDefaults(result.data);
|
|
105
|
+
}
|
|
106
|
+
function mergeDefaults(config) {
|
|
107
|
+
return {
|
|
108
|
+
...config,
|
|
109
|
+
contentDir: config.contentDir ?? DEFAULT_CONFIG.contentDir ?? "content",
|
|
110
|
+
outputDir: config.outputDir ?? DEFAULT_CONFIG.outputDir ?? "dist",
|
|
111
|
+
locale: config.locale ?? DEFAULT_CONFIG.locale ?? "zh",
|
|
112
|
+
navbar: {
|
|
113
|
+
...DEFAULT_CONFIG.navbar,
|
|
114
|
+
...config.navbar,
|
|
115
|
+
logo: config.navbar?.logo ?? config.name
|
|
116
|
+
},
|
|
117
|
+
footer: {
|
|
118
|
+
...DEFAULT_CONFIG.footer,
|
|
119
|
+
...config.footer,
|
|
120
|
+
text: config.footer?.text ?? `MIT ${(/* @__PURE__ */ new Date()).getFullYear()} \xA9 ${config.name}.`
|
|
121
|
+
},
|
|
122
|
+
theme: {
|
|
123
|
+
...DEFAULT_CONFIG.theme,
|
|
124
|
+
...config.theme
|
|
125
|
+
},
|
|
126
|
+
search: {
|
|
127
|
+
...DEFAULT_CONFIG.search,
|
|
128
|
+
...config.search
|
|
129
|
+
},
|
|
130
|
+
mdx: {
|
|
131
|
+
...DEFAULT_CONFIG.mdx,
|
|
132
|
+
...config.mdx
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// src/core/generator/index.ts
|
|
138
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
139
|
+
import { join as join2 } from "path";
|
|
140
|
+
|
|
141
|
+
// src/core/generator/global-css.ts
|
|
142
|
+
function generateGlobalCss(ctx) {
|
|
143
|
+
const { config } = ctx;
|
|
144
|
+
const primaryHue = config.theme?.primaryHue ?? 220;
|
|
145
|
+
return `@import 'tailwindcss';
|
|
146
|
+
@import 'fumadocs-ui/style.css';
|
|
147
|
+
|
|
148
|
+
:root {
|
|
149
|
+
--primary-hue: ${primaryHue};
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// src/core/generator/layout.ts
|
|
155
|
+
function generateLayout(ctx) {
|
|
156
|
+
const { config } = ctx;
|
|
157
|
+
const navTitle = config.navbar?.logo ?? config.name;
|
|
158
|
+
return `import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
|
|
159
|
+
|
|
160
|
+
export function baseOptions(): BaseLayoutProps {
|
|
161
|
+
return {
|
|
162
|
+
nav: {
|
|
163
|
+
title: '${navTitle}',
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// src/core/generator/lib-source.ts
|
|
171
|
+
function generateLibSource() {
|
|
172
|
+
return `import { docs } from '@/.source/server';
|
|
173
|
+
import { loader } from 'fumadocs-core/source';
|
|
174
|
+
|
|
175
|
+
export const source = loader({
|
|
176
|
+
baseUrl: '/',
|
|
177
|
+
source: docs.toFumadocsSource(),
|
|
178
|
+
});
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/core/generator/next-config.ts
|
|
183
|
+
function generateNextConfig(ctx) {
|
|
184
|
+
const { config } = ctx;
|
|
185
|
+
const siteUrl = config.siteUrl ?? "";
|
|
186
|
+
return `import { createMDX } from 'fumadocs-mdx/next';
|
|
187
|
+
|
|
188
|
+
const withMDX = createMDX();
|
|
189
|
+
|
|
190
|
+
/** @type {import('next').NextConfig} */
|
|
191
|
+
const config = {
|
|
192
|
+
reactStrictMode: true,${siteUrl ? `
|
|
193
|
+
output: 'export',` : ""}
|
|
194
|
+
images: {
|
|
195
|
+
unoptimized: true,
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
export default withMDX(config);
|
|
200
|
+
`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/core/generator/package-json.ts
|
|
204
|
+
function generatePackageJson(_ctx) {
|
|
205
|
+
const pkg = {
|
|
206
|
+
name: "openmanual-app",
|
|
207
|
+
type: "module",
|
|
208
|
+
private: true,
|
|
209
|
+
scripts: {
|
|
210
|
+
dev: "next dev",
|
|
211
|
+
build: "next build",
|
|
212
|
+
start: "next start"
|
|
213
|
+
},
|
|
214
|
+
dependencies: {
|
|
215
|
+
"@tailwindcss/postcss": "^4.1.15",
|
|
216
|
+
"fumadocs-core": "^16.7.7",
|
|
217
|
+
"fumadocs-mdx": "^14.2.11",
|
|
218
|
+
"fumadocs-ui": "^16.7.7",
|
|
219
|
+
next: "^16.2.1",
|
|
220
|
+
postcss: "^8.5.8",
|
|
221
|
+
react: "^19.1.0",
|
|
222
|
+
"react-dom": "^19.1.0",
|
|
223
|
+
tailwindcss: "^4.1.15"
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
return `${JSON.stringify(pkg, null, 2)}
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/core/generator/page.ts
|
|
231
|
+
function generatePage(_ctx) {
|
|
232
|
+
return `import { source } from '@/lib/source';
|
|
233
|
+
import { notFound } from 'next/navigation';
|
|
234
|
+
import { DocsPage, DocsBody, DocsTitle, DocsDescription } from 'fumadocs-ui/page';
|
|
235
|
+
import defaultMdxComponents from 'fumadocs-ui/mdx';
|
|
236
|
+
|
|
237
|
+
export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) {
|
|
238
|
+
const { slug } = await params;
|
|
239
|
+
const page = source.getPage(slug);
|
|
240
|
+
|
|
241
|
+
if (!page) {
|
|
242
|
+
notFound();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const MDX = page.data.body;
|
|
246
|
+
|
|
247
|
+
return (
|
|
248
|
+
<DocsPage toc={page.data.toc}>
|
|
249
|
+
<DocsTitle>{page.data.title}</DocsTitle>
|
|
250
|
+
{page.data.description && (
|
|
251
|
+
<DocsDescription>{page.data.description}</DocsDescription>
|
|
252
|
+
)}
|
|
253
|
+
<DocsBody>
|
|
254
|
+
<MDX components={{ ...defaultMdxComponents }} />
|
|
255
|
+
</DocsBody>
|
|
256
|
+
</DocsPage>
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function generateStaticParams() {
|
|
261
|
+
return source.generateParams();
|
|
262
|
+
}
|
|
263
|
+
`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/core/generator/postcss-config.ts
|
|
267
|
+
function generatePostcssConfig() {
|
|
268
|
+
return `/** @type {import('postcss-load-config').Config} */
|
|
269
|
+
const config = {
|
|
270
|
+
plugins: {
|
|
271
|
+
'@tailwindcss/postcss': {},
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
export default config;
|
|
276
|
+
`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/core/generator/provider.ts
|
|
280
|
+
function generateProvider(ctx) {
|
|
281
|
+
const searchEnabled = ctx.config.search?.enabled !== false;
|
|
282
|
+
return `'use client';
|
|
283
|
+
|
|
284
|
+
import { RootProvider } from 'fumadocs-ui/provider/next';
|
|
285
|
+
import type { ReactNode } from 'react';
|
|
286
|
+
|
|
287
|
+
export function Provider({ children }: { children: ReactNode }) {
|
|
288
|
+
return (
|
|
289
|
+
<RootProvider
|
|
290
|
+
search={{
|
|
291
|
+
enabled: ${searchEnabled},
|
|
292
|
+
}}
|
|
293
|
+
>
|
|
294
|
+
{children}
|
|
295
|
+
</RootProvider>
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
`;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/core/generator/source-config.ts
|
|
302
|
+
function generateSourceConfig(_ctx) {
|
|
303
|
+
return `import { defineDocs, defineConfig } from 'fumadocs-mdx/config';
|
|
304
|
+
|
|
305
|
+
export const docs = defineDocs({
|
|
306
|
+
dir: 'content',
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
export default defineConfig();
|
|
310
|
+
`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// src/core/generator/tsconfig.ts
|
|
314
|
+
function generateTsconfig() {
|
|
315
|
+
return `${JSON.stringify(
|
|
316
|
+
{
|
|
317
|
+
compilerOptions: {
|
|
318
|
+
target: "ES2022",
|
|
319
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
320
|
+
module: "ESNext",
|
|
321
|
+
moduleResolution: "Bundler",
|
|
322
|
+
strict: true,
|
|
323
|
+
esModuleInterop: true,
|
|
324
|
+
skipLibCheck: true,
|
|
325
|
+
jsx: "react-jsx",
|
|
326
|
+
noEmit: true,
|
|
327
|
+
allowJs: true,
|
|
328
|
+
resolveJsonModule: true,
|
|
329
|
+
isolatedModules: true,
|
|
330
|
+
incremental: true,
|
|
331
|
+
plugins: [{ name: "next" }],
|
|
332
|
+
paths: {
|
|
333
|
+
"@/*": ["./*"]
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
include: ["**/*.ts", "**/*.tsx", "next-env.d.ts", ".next/types/**/*.ts", ".next/dev/types/**/*.ts"],
|
|
337
|
+
exclude: ["node_modules"]
|
|
338
|
+
},
|
|
339
|
+
null,
|
|
340
|
+
2
|
|
341
|
+
)}
|
|
342
|
+
`;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/core/generator/index.ts
|
|
346
|
+
async function generateAll(ctx) {
|
|
347
|
+
const files = [
|
|
348
|
+
{
|
|
349
|
+
path: "source.config.ts",
|
|
350
|
+
content: generateSourceConfig(ctx)
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
path: "next.config.mjs",
|
|
354
|
+
content: generateNextConfig(ctx)
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
path: "global.css",
|
|
358
|
+
content: generateGlobalCss(ctx)
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
path: "package.json",
|
|
362
|
+
content: generatePackageJson(ctx)
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
path: "tsconfig.json",
|
|
366
|
+
content: generateTsconfig()
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
path: "postcss.config.mjs",
|
|
370
|
+
content: generatePostcssConfig()
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
path: "lib/source.ts",
|
|
374
|
+
content: generateLibSource()
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
path: "lib/layout.ts",
|
|
378
|
+
content: generateLayout(ctx)
|
|
379
|
+
},
|
|
380
|
+
{
|
|
381
|
+
path: "app/layout.tsx",
|
|
382
|
+
content: generateRootLayout()
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
path: "app/provider.tsx",
|
|
386
|
+
content: generateProvider(ctx)
|
|
387
|
+
},
|
|
388
|
+
{
|
|
389
|
+
path: "app/[[...slug]]/layout.tsx",
|
|
390
|
+
content: generateDocsLayout(ctx)
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
path: "app/[[...slug]]/page.tsx",
|
|
394
|
+
content: generatePage(ctx)
|
|
395
|
+
}
|
|
396
|
+
];
|
|
397
|
+
for (const file of files) {
|
|
398
|
+
const fullPath = join2(ctx.appDir, file.path);
|
|
399
|
+
const dir = join2(fullPath, "..");
|
|
400
|
+
await mkdir(dir, { recursive: true });
|
|
401
|
+
await writeFile(fullPath, file.content, "utf-8");
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
function generateRootLayout() {
|
|
405
|
+
return `import { Provider } from './provider';
|
|
406
|
+
import type { ReactNode } from 'react';
|
|
407
|
+
import '../global.css';
|
|
408
|
+
|
|
409
|
+
export default function RootLayout({ children }: { children: ReactNode }) {
|
|
410
|
+
return (
|
|
411
|
+
<html lang="zh" suppressHydrationWarning>
|
|
412
|
+
<body className="flex flex-col min-h-screen">
|
|
413
|
+
<Provider>{children}</Provider>
|
|
414
|
+
</body>
|
|
415
|
+
</html>
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
`;
|
|
419
|
+
}
|
|
420
|
+
function generateDocsLayout(ctx) {
|
|
421
|
+
const { config } = ctx;
|
|
422
|
+
const githubLink = config.navbar?.github ?? "";
|
|
423
|
+
const navLinks = config.navbar?.links ?? [];
|
|
424
|
+
const footerText = config.footer?.text ?? "";
|
|
425
|
+
const linksArray = navLinks.map((l) => ({
|
|
426
|
+
text: l.label,
|
|
427
|
+
url: l.href,
|
|
428
|
+
external: true
|
|
429
|
+
}));
|
|
430
|
+
const githubLine = githubLink ? `
|
|
431
|
+
github: '${githubLink}',` : "";
|
|
432
|
+
const linksLine = linksArray.length > 0 ? `
|
|
433
|
+
links: ${JSON.stringify(linksArray)},` : "";
|
|
434
|
+
const footerLine = footerText ? `
|
|
435
|
+
footer: { children: '${footerText.replace(/'/g, "\\'")}' },` : "";
|
|
436
|
+
return `import { DocsLayout } from 'fumadocs-ui/layouts/docs';
|
|
437
|
+
import { baseOptions } from '@/lib/layout';
|
|
438
|
+
import { source } from '@/lib/source';
|
|
439
|
+
import type { ReactNode } from 'react';
|
|
440
|
+
|
|
441
|
+
const docsOptions = {
|
|
442
|
+
...baseOptions(),
|
|
443
|
+
tree: source.getPageTree(),${githubLine}${linksLine}${footerLine}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
export default function DocsLayoutWrapper({ children }: { children: ReactNode }) {
|
|
447
|
+
return (
|
|
448
|
+
<DocsLayout {...docsOptions}>
|
|
449
|
+
{children}
|
|
450
|
+
</DocsLayout>
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
`;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/utils/install-deps.ts
|
|
457
|
+
import { spawn } from "child_process";
|
|
458
|
+
import { existsSync } from "fs";
|
|
459
|
+
import { resolve } from "path";
|
|
460
|
+
async function installDeps(appDir) {
|
|
461
|
+
const nodeModules = resolve(appDir, "node_modules");
|
|
462
|
+
if (existsSync(nodeModules)) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
return new Promise((resolve6, reject) => {
|
|
466
|
+
const child = spawn("pnpm", ["install", "--no-frozen-lockfile"], {
|
|
467
|
+
cwd: appDir,
|
|
468
|
+
stdio: "pipe",
|
|
469
|
+
env: { ...process.env }
|
|
470
|
+
});
|
|
471
|
+
let stderr = "";
|
|
472
|
+
child.stderr?.on("data", (data) => {
|
|
473
|
+
stderr += data.toString();
|
|
474
|
+
});
|
|
475
|
+
child.on("error", reject);
|
|
476
|
+
child.on("exit", (code) => {
|
|
477
|
+
if (code === 0) {
|
|
478
|
+
resolve6();
|
|
479
|
+
} else {
|
|
480
|
+
reject(new Error(`pnpm install failed: ${stderr}`));
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// src/utils/logger.ts
|
|
487
|
+
var COLORS = {
|
|
488
|
+
reset: "\x1B[0m",
|
|
489
|
+
green: "\x1B[32m",
|
|
490
|
+
yellow: "\x1B[33m",
|
|
491
|
+
red: "\x1B[31m",
|
|
492
|
+
cyan: "\x1B[36m",
|
|
493
|
+
gray: "\x1B[90m",
|
|
494
|
+
bold: "\x1B[1m"
|
|
495
|
+
};
|
|
496
|
+
function timestamp() {
|
|
497
|
+
return (/* @__PURE__ */ new Date()).toLocaleTimeString("zh-CN", { hour12: false });
|
|
498
|
+
}
|
|
499
|
+
var logger = {
|
|
500
|
+
info(msg) {
|
|
501
|
+
console.log(
|
|
502
|
+
`${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.cyan}info${COLORS.reset} ${msg}`
|
|
503
|
+
);
|
|
504
|
+
},
|
|
505
|
+
success(msg) {
|
|
506
|
+
console.log(
|
|
507
|
+
`${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.green}done${COLORS.reset} ${msg}`
|
|
508
|
+
);
|
|
509
|
+
},
|
|
510
|
+
warn(msg) {
|
|
511
|
+
console.warn(
|
|
512
|
+
`${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.yellow}warn${COLORS.reset} ${msg}`
|
|
513
|
+
);
|
|
514
|
+
},
|
|
515
|
+
error(msg) {
|
|
516
|
+
console.error(
|
|
517
|
+
`${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.red}error${COLORS.reset} ${msg}`
|
|
518
|
+
);
|
|
519
|
+
},
|
|
520
|
+
step(msg) {
|
|
521
|
+
console.log(
|
|
522
|
+
`${COLORS.gray}[${timestamp()}]${COLORS.reset} ${COLORS.bold}\u2192${COLORS.reset} ${msg}`
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
// src/utils/temp-dir.ts
|
|
528
|
+
import { existsSync as existsSync2 } from "fs";
|
|
529
|
+
import { lstat, mkdir as mkdir2, rm, symlink } from "fs/promises";
|
|
530
|
+
import { join as join3, resolve as resolve2 } from "path";
|
|
531
|
+
var TEMP_DIR_NAME = ".openmanual";
|
|
532
|
+
function getTempDir(cwd) {
|
|
533
|
+
return join3(cwd, TEMP_DIR_NAME);
|
|
534
|
+
}
|
|
535
|
+
function getAppDir(cwd) {
|
|
536
|
+
return join3(getTempDir(cwd), "app");
|
|
537
|
+
}
|
|
538
|
+
async function ensureTempDir(cwd) {
|
|
539
|
+
const tempDir = getTempDir(cwd);
|
|
540
|
+
const appDir = getAppDir(cwd);
|
|
541
|
+
await mkdir2(tempDir, { recursive: true });
|
|
542
|
+
await mkdir2(join3(appDir, "app"), { recursive: true });
|
|
543
|
+
return tempDir;
|
|
544
|
+
}
|
|
545
|
+
async function cleanTempDir(cwd) {
|
|
546
|
+
const tempDir = getTempDir(cwd);
|
|
547
|
+
if (existsSync2(tempDir)) {
|
|
548
|
+
await rm(tempDir, { recursive: true, force: true });
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async function createSymlink(target, linkPath) {
|
|
552
|
+
const resolvedTarget = resolve2(target);
|
|
553
|
+
const resolvedLink = resolve2(linkPath);
|
|
554
|
+
try {
|
|
555
|
+
await lstat(resolvedLink);
|
|
556
|
+
await rm(resolvedLink, { recursive: true, force: true });
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
await symlink(resolvedTarget, resolvedLink, "junction");
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// src/cli/commands/build.ts
|
|
563
|
+
var buildCommand = new Command("build").description("\u6784\u5EFA\u9759\u6001\u7AD9\u70B9").action(async () => {
|
|
564
|
+
const cwd = process.cwd();
|
|
565
|
+
try {
|
|
566
|
+
logger.step("\u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6...");
|
|
567
|
+
const config = await loadConfig(cwd);
|
|
568
|
+
logger.step("\u751F\u6210\u4E34\u65F6\u5E94\u7528...");
|
|
569
|
+
const appDir = getAppDir(cwd);
|
|
570
|
+
const contentDir = resolve3(cwd, config.contentDir ?? "content");
|
|
571
|
+
await ensureTempDir(cwd);
|
|
572
|
+
const ctx = {
|
|
573
|
+
config,
|
|
574
|
+
projectDir: cwd,
|
|
575
|
+
appDir,
|
|
576
|
+
contentDir: config.contentDir ?? "content"
|
|
577
|
+
};
|
|
578
|
+
await generateAll(ctx);
|
|
579
|
+
await createSymlink(contentDir, resolve3(appDir, "content"));
|
|
580
|
+
const publicDir = resolve3(cwd, "public");
|
|
581
|
+
try {
|
|
582
|
+
const { stat } = await import("fs/promises");
|
|
583
|
+
await stat(publicDir);
|
|
584
|
+
await createSymlink(publicDir, resolve3(appDir, "public"));
|
|
585
|
+
} catch {
|
|
586
|
+
}
|
|
587
|
+
logger.step("\u5B89\u88C5\u4F9D\u8D56...");
|
|
588
|
+
await installDeps(appDir);
|
|
589
|
+
logger.step("\u6784\u5EFA\u9759\u6001\u7AD9\u70B9...");
|
|
590
|
+
const buildResult = spawn2("npx", ["next", "build"], {
|
|
591
|
+
cwd: appDir,
|
|
592
|
+
stdio: "inherit",
|
|
593
|
+
env: { ...process.env }
|
|
594
|
+
});
|
|
595
|
+
await new Promise((resolve6, reject) => {
|
|
596
|
+
buildResult.on("error", reject);
|
|
597
|
+
buildResult.on("exit", (code) => {
|
|
598
|
+
if (code === 0) {
|
|
599
|
+
resolve6();
|
|
600
|
+
} else {
|
|
601
|
+
reject(new Error(`Build failed with code ${code}`));
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
});
|
|
605
|
+
const outputDir = resolve3(cwd, config.outputDir ?? "dist");
|
|
606
|
+
await mkdir3(outputDir, { recursive: true });
|
|
607
|
+
const nextOutput = resolve3(appDir, "out");
|
|
608
|
+
try {
|
|
609
|
+
await cp(nextOutput, outputDir, { recursive: true });
|
|
610
|
+
logger.success(`\u9759\u6001\u7AD9\u70B9\u5DF2\u8F93\u51FA\u5230: ${outputDir}`);
|
|
611
|
+
} catch {
|
|
612
|
+
logger.warn('\u672A\u627E\u5230\u9759\u6001\u5BFC\u51FA\u4EA7\u7269\uFF0C\u8BF7\u68C0\u67E5 next.config.mjs \u4E2D output: "export" \u914D\u7F6E');
|
|
613
|
+
}
|
|
614
|
+
logger.step("\u6E05\u7406\u4E34\u65F6\u6587\u4EF6...");
|
|
615
|
+
await cleanTempDir(cwd);
|
|
616
|
+
logger.success("\u6784\u5EFA\u5B8C\u6210\uFF01");
|
|
617
|
+
} catch (err) {
|
|
618
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
619
|
+
logger.error(message);
|
|
620
|
+
process.exit(1);
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
// src/cli/commands/dev.ts
|
|
625
|
+
import { spawn as spawn3 } from "child_process";
|
|
626
|
+
import { resolve as resolve4 } from "path";
|
|
627
|
+
import { Command as Command2 } from "commander";
|
|
628
|
+
var devCommand = new Command2("dev").description("\u542F\u52A8\u5F00\u53D1\u670D\u52A1\u5668").option("-p, --port <port>", "\u7AEF\u53E3\u53F7", "3000").action(async (options) => {
|
|
629
|
+
const cwd = process.cwd();
|
|
630
|
+
try {
|
|
631
|
+
logger.step("\u8BFB\u53D6\u914D\u7F6E\u6587\u4EF6...");
|
|
632
|
+
const config = await loadConfig(cwd);
|
|
633
|
+
logger.step("\u751F\u6210\u4E34\u65F6\u5E94\u7528...");
|
|
634
|
+
const tempDir = await ensureTempDir(cwd);
|
|
635
|
+
const appDir = getAppDir(cwd);
|
|
636
|
+
const contentDir = resolve4(cwd, config.contentDir ?? "content");
|
|
637
|
+
const ctx = {
|
|
638
|
+
config,
|
|
639
|
+
projectDir: cwd,
|
|
640
|
+
appDir,
|
|
641
|
+
contentDir: config.contentDir ?? "content"
|
|
642
|
+
};
|
|
643
|
+
await generateAll(ctx);
|
|
644
|
+
await createSymlink(contentDir, resolve4(appDir, "content"));
|
|
645
|
+
const publicDir = resolve4(cwd, "public");
|
|
646
|
+
try {
|
|
647
|
+
const { stat } = await import("fs/promises");
|
|
648
|
+
await stat(publicDir);
|
|
649
|
+
await createSymlink(publicDir, resolve4(appDir, "public"));
|
|
650
|
+
} catch {
|
|
651
|
+
}
|
|
652
|
+
logger.step("\u5B89\u88C5\u4F9D\u8D56...");
|
|
653
|
+
await installDeps(appDir);
|
|
654
|
+
logger.success(`\u5F00\u53D1\u670D\u52A1\u5668\u542F\u52A8\u4E2D...`);
|
|
655
|
+
logger.info(`\u5185\u5BB9\u76EE\u5F55: ${contentDir}`);
|
|
656
|
+
logger.info(`\u4E34\u65F6\u76EE\u5F55: ${tempDir}`);
|
|
657
|
+
logger.info(`\u7AEF\u53E3: ${options.port}`);
|
|
658
|
+
const child = spawn3("npx", ["next", "dev", "--port", options.port], {
|
|
659
|
+
cwd: appDir,
|
|
660
|
+
stdio: "inherit",
|
|
661
|
+
env: { ...process.env }
|
|
662
|
+
});
|
|
663
|
+
child.on("error", (err) => {
|
|
664
|
+
logger.error(`\u542F\u52A8\u5931\u8D25: ${err.message}`);
|
|
665
|
+
process.exit(1);
|
|
666
|
+
});
|
|
667
|
+
child.on("exit", (code) => {
|
|
668
|
+
if (code !== 0 && code !== null) {
|
|
669
|
+
process.exit(code);
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
const cleanup = () => {
|
|
673
|
+
child.kill();
|
|
674
|
+
process.exit(0);
|
|
675
|
+
};
|
|
676
|
+
process.on("SIGINT", cleanup);
|
|
677
|
+
process.on("SIGTERM", cleanup);
|
|
678
|
+
} catch (err) {
|
|
679
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
680
|
+
logger.error(message);
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
// src/cli/commands/preview.ts
|
|
686
|
+
import { spawn as spawn4 } from "child_process";
|
|
687
|
+
import { existsSync as existsSync3 } from "fs";
|
|
688
|
+
import { resolve as resolve5 } from "path";
|
|
689
|
+
import { Command as Command3 } from "commander";
|
|
690
|
+
var previewCommand = new Command3("preview").description("\u9884\u89C8\u6784\u5EFA\u4EA7\u7269").option("-p, --port <port>", "\u7AEF\u53E3\u53F7", "8080").option("-d, --dir <dir>", "\u4EA7\u7269\u76EE\u5F55").action(async (options) => {
|
|
691
|
+
const cwd = process.cwd();
|
|
692
|
+
try {
|
|
693
|
+
let outputDir = options.dir;
|
|
694
|
+
if (!outputDir) {
|
|
695
|
+
const config = await loadConfig(cwd);
|
|
696
|
+
outputDir = resolve5(cwd, config.outputDir ?? "dist");
|
|
697
|
+
}
|
|
698
|
+
if (!existsSync3(outputDir)) {
|
|
699
|
+
logger.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${outputDir}`);
|
|
700
|
+
logger.info("\u8BF7\u5148\u8FD0\u884C openmanual build");
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
logger.info(`\u9884\u89C8\u76EE\u5F55: ${outputDir}`);
|
|
704
|
+
logger.info(`\u9884\u89C8\u5730\u5740: http://localhost:${options.port}`);
|
|
705
|
+
const child = spawn4("npx", ["serve", outputDir, "-p", options.port], {
|
|
706
|
+
stdio: "inherit",
|
|
707
|
+
env: { ...process.env }
|
|
708
|
+
});
|
|
709
|
+
child.on("error", (err) => {
|
|
710
|
+
logger.error(`\u542F\u52A8\u5931\u8D25: ${err.message}`);
|
|
711
|
+
process.exit(1);
|
|
712
|
+
});
|
|
713
|
+
const cleanup = () => {
|
|
714
|
+
child.kill();
|
|
715
|
+
process.exit(0);
|
|
716
|
+
};
|
|
717
|
+
process.on("SIGINT", cleanup);
|
|
718
|
+
process.on("SIGTERM", cleanup);
|
|
719
|
+
} catch (err) {
|
|
720
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
721
|
+
logger.error(message);
|
|
722
|
+
process.exit(1);
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// src/cli/bin.ts
|
|
727
|
+
var program = new Command4();
|
|
728
|
+
program.name("openmanual").description("AI \u53CB\u597D\u7684\u5F00\u6E90\u6587\u6863\u7CFB\u7EDF\u6846\u67B6").version("0.1.0");
|
|
729
|
+
program.addCommand(devCommand);
|
|
730
|
+
program.addCommand(buildCommand);
|
|
731
|
+
program.addCommand(previewCommand);
|
|
732
|
+
program.parse();
|
|
733
|
+
//# sourceMappingURL=bin.js.map
|