feat: initialize pnpm workspace and add app structure

- Created pnpm workspace configuration in `pnpm-workspace.yaml`.
- Implemented basic app setup in `src/app.ts` using `@kevisual/router`.
- Generated documentation files in `src/generated/docs.ts` for showcase CMS templates.
- Added inline script to read and compile markdown files into TypeScript constants in `src/inline.ts`.
- Set up main application entry point in `src/main.ts` to integrate routing and plugins.
- Established routing structure in `src/routes/index.ts` and `src/routes/md.ts` for handling markdown-related tasks.
- Implemented a route for initializing web todo tasks with dynamic content generation based on selected template type.
This commit is contained in:
2026-01-20 22:54:40 +08:00
commit 7a99b69887
19 changed files with 6306 additions and 0 deletions

3
src/app.ts Normal file
View File

@@ -0,0 +1,3 @@
import { App } from '@kevisual/router'
import { useContextKey } from '@kevisual/context'
export const app = useContextKey('app', () => new App());

1307
src/generated/docs.ts Normal file

File diff suppressed because it is too large Load Diff

46
src/inline.ts Normal file
View File

@@ -0,0 +1,46 @@
import { BunFile } from 'bun';
import path from 'path';
import fs from 'fs';
async function inlineMarkdownFiles() {
// 读取所有 MD 文件
const files = [
// { path: '../README.md', name: 'readme' },
// { path: '../docs/api.md', name: 'apiDoc' },
];
const readme = path.join(import.meta.dir, '..', 'README.md');
files.push({ path: readme, name: 'readme' });
// list docs files dynamically if needed
const rootDir = path.join(import.meta.dir, '..', 'docs');
const dirEntries = fs.readdirSync(rootDir, { withFileTypes: true });
for (const entry of dirEntries) {
const filename = 'name' in entry ? entry.name : entry;
if (filename.endsWith('.md')) {
const name = path.basename(filename, '.md').replace(/[^a-zA-Z0-9_]/g, '_');
files.push({ path: path.join(rootDir, filename), name });
}
}
let generatedCode = '// Generated by build script\n';
for (const file of files) {
try {
const content = await Bun.file(file.path).text();
// 转义模板字符串中的特殊字符
const escapedContent = content
.replace(/\\/g, '\\\\')
.replace(/`/g, '\\`')
.replace(/\${/g, '\\${');
generatedCode += `export const ${file.name} = \`${escapedContent}\`;\n`;
} catch (error) {
console.warn(`Failed to read ${file.path}:`, error);
generatedCode += `export const ${file.name} = '';\n`;
}
}
// 写入生成的文件
await Bun.write('./src/generated/docs.ts', generatedCode);
}
await inlineMarkdownFiles();

9
src/main.ts Normal file
View File

@@ -0,0 +1,9 @@
import { app } from './app.ts';
import { createRouterAgentPluginFn } from '@kevisual/router/src/opencode.ts'
import { Plugin } from '@opencode-ai/plugin'
import './routes/index.ts';
export const docsAgentPlugin: Plugin = createRouterAgentPluginFn({
// @ts-ignore
router: app.router,
});

1
src/routes/index.ts Normal file
View File

@@ -0,0 +1 @@
import './md.ts';

41
src/routes/md.ts Normal file
View File

@@ -0,0 +1,41 @@
import * as docs from '../generated/docs.ts';
import { app } from '../app.ts'
import path from 'path';
import fs from 'fs';
import { createSkill, tool } from '@kevisual/router';
// 初始化init-web-todo 任务, 使用astro
app.route({
path: 'web-todo',
key: 'init',
description: '初始化需要做的任务',
metadata: {
tags: ['opencode'],
...createSkill({
skill: 'init-web-todo',
title: '初始化 Web Todo 项目',
summary: '初始化需要做的任务',
args: {
type: tool.schema.string().describe('初始化类型, astro 或者 nextjs'),
force: tool.schema.boolean().optional().describe('是否强制覆盖已存在的 TODO.md 文件'),
}
})
}
}).define(async (ctx) => {
const type = ctx.query?.type || 'astro';
const force = ctx.query?.force ?? false;
const todo = path.join(process.cwd(), 'TODO.md');
if (fs.existsSync(todo)) {
if (!force) {
ctx.body = {
content: 'TODO.md 文件已经存在。如需覆盖,请使用 --force 参数。',
}
return;
}
}
const template = type === 'nextjs' ? docs.enterprise_website_features : docs.website_features_breakdown;
ctx.body = {
content: `根据下面的文档生成一个任务清单写入到TODO.md 文件中, 使用技术:${type}:\n\n${template}`,
}
}).addTo(app);