@dyyz1993/create-agent 1.9.0 → 2.0.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.
Files changed (42) hide show
  1. package/package.json +23 -23
  2. package/src/lib/copy.ts +236 -243
  3. package/templates/agent/eslint-plugin-rpc/index.js +50 -0
  4. package/templates/agent/eslint-plugin-rpc/package.json +6 -0
  5. package/templates/agent/eslint-plugin-rpc/rules/module-file-naming.js +99 -0
  6. package/templates/agent/eslint-plugin-rpc/rules/no-bare-method.js +78 -0
  7. package/templates/agent/eslint-plugin-rpc/rules/no-direct-register.js +65 -0
  8. package/templates/agent/eslint-plugin-rpc/rules/no-hardcoded-strings.js +69 -0
  9. package/templates/agent/eslint-plugin-rpc/rules/require-api-client.js +58 -0
  10. package/templates/agent/eslint-plugin-rpc/rules/require-typed-register.js +78 -0
  11. package/templates/agent/eslint-plugin-rpc/rules/schema-merge-only.js +55 -0
  12. package/templates/agent/eslint.config.mjs +2 -1
  13. package/templates/agent/package.json +1 -2
  14. package/templates/agent/src/mainview/components/layout/AppLayout.tsx +3 -1
  15. package/templates/chat/eslint-plugin-rpc/index.js +50 -0
  16. package/templates/chat/eslint-plugin-rpc/package.json +6 -0
  17. package/templates/chat/eslint-plugin-rpc/rules/module-file-naming.js +99 -0
  18. package/templates/chat/eslint-plugin-rpc/rules/no-bare-method.js +78 -0
  19. package/templates/chat/eslint-plugin-rpc/rules/no-direct-register.js +65 -0
  20. package/templates/chat/eslint-plugin-rpc/rules/no-hardcoded-strings.js +69 -0
  21. package/templates/chat/eslint-plugin-rpc/rules/require-api-client.js +58 -0
  22. package/templates/chat/eslint-plugin-rpc/rules/require-typed-register.js +78 -0
  23. package/templates/chat/eslint-plugin-rpc/rules/schema-merge-only.js +55 -0
  24. package/templates/chat/eslint.config.mjs +2 -1
  25. package/templates/chat/package.json +1 -2
  26. package/templates/general/eslint-plugin-rpc/index.js +50 -0
  27. package/templates/general/eslint-plugin-rpc/package.json +6 -0
  28. package/templates/general/eslint-plugin-rpc/rules/module-file-naming.js +99 -0
  29. package/templates/general/eslint-plugin-rpc/rules/no-bare-method.js +78 -0
  30. package/templates/general/eslint-plugin-rpc/rules/no-direct-register.js +65 -0
  31. package/templates/general/eslint-plugin-rpc/rules/no-hardcoded-strings.js +69 -0
  32. package/templates/general/eslint-plugin-rpc/rules/require-api-client.js +58 -0
  33. package/templates/general/eslint-plugin-rpc/rules/require-typed-register.js +78 -0
  34. package/templates/general/eslint-plugin-rpc/rules/schema-merge-only.js +55 -0
  35. package/templates/general/eslint.config.mjs +2 -1
  36. package/templates/general/package.json +1 -2
  37. package/templates/agent/bun.lock +0 -1279
  38. package/templates/agent/package-lock.json +0 -3670
  39. package/templates/chat/bun.lock +0 -1203
  40. package/templates/chat/package-lock.json +0 -3670
  41. package/templates/general/bun.lock +0 -1266
  42. package/templates/general/package-lock.json +0 -3670
package/package.json CHANGED
@@ -1,25 +1,25 @@
1
1
  {
2
- "name": "@dyyz1993/create-agent",
3
- "version": "1.9.0",
4
- "description": "Create Agent project scaffolding CLI",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/dyyz1993/pi-agent-template"
8
- },
9
- "bin": {
10
- "create-agent": "./src/cli.ts"
11
- },
12
- "files": [
13
- "src/",
14
- "templates/"
15
- ],
16
- "scripts": {
17
- "sync-templates": "rm -rf templates/general templates/chat templates/agent && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/general/ templates/general/ && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/chat/ templates/chat/ && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/agent/ templates/agent/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/agent/eslint-plugin-rpc/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/chat/eslint-plugin-rpc/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/general/eslint-plugin-rpc/ && node scripts/post-sync-templates.mjs",
18
- "test": "bun test src/__tests__/",
19
- "prepublishOnly": "test -d templates/general/src && test -d templates/chat/src && test -d templates/agent/src"
20
- },
21
- "engines": {
22
- "bun": ">=1.0.0"
23
- },
24
- "license": "MIT"
2
+ "name": "@dyyz1993/create-agent",
3
+ "version": "2.0.0",
4
+ "description": "Create Agent project scaffolding CLI",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/dyyz1993/pi-agent-template"
8
+ },
9
+ "bin": {
10
+ "create-agent": "./src/cli.ts"
11
+ },
12
+ "files": [
13
+ "src/",
14
+ "templates/"
15
+ ],
16
+ "scripts": {
17
+ "sync-templates": "rm -rf templates/general templates/chat templates/agent && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/general/ templates/general/ && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/chat/ templates/chat/ && rsync -a --exclude='node_modules' --exclude='dist' --exclude='build' --exclude='.server-port' --exclude='logs' --exclude='bun.lock' ../../templates/agent/ templates/agent/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/agent/eslint-plugin-rpc/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/chat/eslint-plugin-rpc/ && rsync -a ../../packages/eslint-plugin-rpc/ templates/general/eslint-plugin-rpc/ && node scripts/post-sync-templates.mjs",
18
+ "test": "bun test src/__tests__/",
19
+ "prepublishOnly": "test -d templates/general/src && test -d templates/chat/src && test -d templates/agent/src"
20
+ },
21
+ "engines": {
22
+ "bun": ">=1.0.0"
23
+ },
24
+ "license": "MIT"
25
25
  }
package/src/lib/copy.ts CHANGED
@@ -1,285 +1,278 @@
1
- import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'fs';
2
- import { join, resolve } from 'path';
3
- import { execSync } from 'child_process';
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from "fs";
2
+ import { join, resolve } from "path";
3
+ import { execSync } from "child_process";
4
4
 
5
- const SKIP_DIRS = new Set(['node_modules', 'build', 'dist', '.git', '.husky', '.trae']);
6
- const SKIP_FILES = new Set(['bun.lock', 'rpc-browser.js']);
5
+ const SKIP_DIRS = new Set(["node_modules", "build", "dist", ".git", ".husky", ".trae"]);
6
+ const SKIP_FILES = new Set(["bun.lock", "rpc-browser.js"]);
7
7
 
8
8
  export interface CopyOptions {
9
- projectName: string;
10
- templateDir: string;
11
- targetDir: string;
9
+ projectName: string;
10
+ templateDir: string;
11
+ targetDir: string;
12
12
  }
13
13
 
14
14
  export function deriveNames(projectName: string) {
15
- const pascalName = projectName.replace(/(^|-)([a-z])/g, (_, _sep, c) => c.toUpperCase());
16
- const identifier = `com.${projectName.replace(/-/g, '')}.app`;
17
- const shortId = `com.${projectName.replace(/-/g, '')}`;
18
- return { pascalName, identifier, shortId };
15
+ const pascalName = projectName.replace(/(^|-)([a-z])/g, (_, _sep, c) => c.toUpperCase());
16
+ const identifier = `com.${projectName.replace(/-/g, "")}.app`;
17
+ const shortId = `com.${projectName.replace(/-/g, "")}`;
18
+ return { pascalName, identifier, shortId };
19
19
  }
20
20
 
21
21
  export function copyAndReplace(srcDir: string, destDir: string, projectName: string): void {
22
- const { pascalName, identifier, shortId } = deriveNames(projectName);
22
+ const { pascalName, identifier, shortId } = deriveNames(projectName);
23
23
 
24
- mkdirSync(destDir, { recursive: true });
24
+ mkdirSync(destDir, { recursive: true });
25
25
 
26
- const entries = readdirSync(srcDir, { withFileTypes: true });
26
+ const entries = readdirSync(srcDir, { withFileTypes: true });
27
27
 
28
- for (const entry of entries) {
29
- const srcPath = join(srcDir, entry.name);
30
- const destPath = join(destDir, entry.name);
28
+ for (const entry of entries) {
29
+ const srcPath = join(srcDir, entry.name);
30
+ const destPath = join(destDir, entry.name);
31
31
 
32
- if (entry.isDirectory()) {
33
- if (SKIP_DIRS.has(entry.name)) continue;
34
- copyAndReplace(srcPath, destPath, projectName);
35
- } else {
36
- if (SKIP_FILES.has(entry.name)) continue;
32
+ if (entry.isDirectory()) {
33
+ if (SKIP_DIRS.has(entry.name)) continue;
34
+ copyAndReplace(srcPath, destPath, projectName);
35
+ } else {
36
+ if (SKIP_FILES.has(entry.name)) continue;
37
37
 
38
- let content = readFileSync(srcPath, 'utf-8');
38
+ let content = readFileSync(srcPath, "utf-8");
39
39
 
40
- content = content.replace(/pi-agent-template/g, projectName);
41
- content = content.replace(/Pi Agent Template/g, pascalName);
42
- content = content.replace(/Pi Agent/g, pascalName);
43
- content = content.replace(/com\.piagent\.template/g, identifier);
44
- content = content.replace(/com\.piagent/g, shortId);
45
- content = content.replace(/@pi-agent\//g, `@${projectName}/`);
40
+ content = content.replace(/pi-agent-template/g, projectName);
41
+ content = content.replace(/Pi Agent Template/g, pascalName);
42
+ content = content.replace(/Pi Agent/g, pascalName);
43
+ content = content.replace(/com\.piagent\.template/g, identifier);
44
+ content = content.replace(/com\.piagent/g, shortId);
45
+ content = content.replace(/@pi-agent\//g, `@${projectName}/`);
46
46
 
47
- writeFileSync(destPath, content);
48
- }
49
- }
47
+ writeFileSync(destPath, content);
48
+ }
49
+ }
50
50
  }
51
51
 
52
52
  function copyTraeRules(monorepoRoot: string, targetDir: string, projectName: string): void {
53
- const traeRulesDir = resolve(monorepoRoot, '.trae', 'rules');
54
- if (!existsSync(traeRulesDir)) return;
55
-
56
- const destRulesDir = join(targetDir, '.trae', 'rules');
57
- mkdirSync(destRulesDir, { recursive: true });
58
-
59
- const files = readdirSync(traeRulesDir).filter((f) => f !== 'memory.md');
60
- for (const file of files) {
61
- const src = join(traeRulesDir, file);
62
- const dest = join(destRulesDir, file);
63
- let content = readFileSync(src, 'utf-8');
64
- content = content.replace(/pi-agent-template/g, projectName);
65
- writeFileSync(dest, content);
66
- }
53
+ const traeRulesDir = resolve(monorepoRoot, ".trae", "rules");
54
+ if (!existsSync(traeRulesDir)) return;
55
+
56
+ const destRulesDir = join(targetDir, ".trae", "rules");
57
+ mkdirSync(destRulesDir, { recursive: true });
58
+
59
+ const files = readdirSync(traeRulesDir).filter((f) => f !== "memory.md");
60
+ for (const file of files) {
61
+ const src = join(traeRulesDir, file);
62
+ const dest = join(destRulesDir, file);
63
+ let content = readFileSync(src, "utf-8");
64
+ content = content.replace(/pi-agent-template/g, projectName);
65
+ writeFileSync(dest, content);
66
+ }
67
67
  }
68
68
 
69
69
  function copySharedModules(monorepoRoot: string, targetDir: string, projectName: string): void {
70
- const sharedDir = resolve(monorepoRoot, 'templates', 'shared');
71
- if (!existsSync(sharedDir)) return;
70
+ const sharedDir = resolve(monorepoRoot, "templates", "shared");
71
+ if (!existsSync(sharedDir)) return;
72
72
 
73
- const destSharedDir = join(targetDir, 'shared');
74
- copyAndReplace(sharedDir, destSharedDir, projectName);
73
+ const destSharedDir = join(targetDir, "shared");
74
+ copyAndReplace(sharedDir, destSharedDir, projectName);
75
75
 
76
- const tsconfigPath = join(targetDir, 'tsconfig.json');
77
- if (existsSync(tsconfigPath)) {
78
- let tsconfig = readFileSync(tsconfigPath, 'utf-8');
79
- tsconfig = tsconfig.replace(/"\.\.\/shared\/\*"/g, '"./shared/*"');
80
- tsconfig = tsconfig.replace(/"\.\.\/shared"/g, '"./shared"');
81
- writeFileSync(tsconfigPath, tsconfig);
82
- }
76
+ const tsconfigPath = join(targetDir, "tsconfig.json");
77
+ if (existsSync(tsconfigPath)) {
78
+ let tsconfig = readFileSync(tsconfigPath, "utf-8");
79
+ tsconfig = tsconfig.replace(/"\.\.\/shared\/\*"/g, '"./shared/*"');
80
+ tsconfig = tsconfig.replace(/"\.\.\/shared"/g, '"./shared"');
81
+ writeFileSync(tsconfigPath, tsconfig);
82
+ }
83
83
 
84
- cleanSharedViteConfig(destSharedDir);
84
+ cleanSharedViteConfig(destSharedDir);
85
85
  }
86
86
 
87
87
  function cleanViteConfig(targetDir: string): void {
88
- const viteConfigPath = join(targetDir, "vite.config.ts");
89
- const vitestConfigPath = join(targetDir, "vitest.config.ts");
90
-
91
- for (const configPath of [viteConfigPath, vitestConfigPath]) {
92
- if (!existsSync(configPath)) continue;
93
-
94
- let config = readFileSync(configPath, "utf-8");
95
-
96
- config = config.replace(
97
- /,\n\s+resolve:\s*\{\n\s+alias:\s*\{[^}]*\}[,\s]*\}[,;]?\n/g,
98
- ""
99
- );
100
-
101
- if (!config.includes('from "path"') && config.includes("resolve(")) {
102
- config = 'import { resolve } from "path";\n' + config;
103
- }
104
-
105
- config = config.replace(/from ["']\.\.\/shared\/vite-base\.config["']/g, 'from "./shared/vite-base.config"');
106
- config = config.replace(/from ["']\.\.\/shared\/vitest-base\.config["']/g, 'from "./shared/vitest-base.config"');
107
-
108
- writeFileSync(configPath, config);
109
- }
88
+ const viteConfigPath = join(targetDir, "vite.config.ts");
89
+ const vitestConfigPath = join(targetDir, "vitest.config.ts");
90
+
91
+ for (const configPath of [viteConfigPath, vitestConfigPath]) {
92
+ if (!existsSync(configPath)) continue;
93
+
94
+ let config = readFileSync(configPath, "utf-8");
95
+
96
+ config = config.replace(/,\n\s+resolve:\s*\{\n\s+alias:\s*\{[^}]*\}[,\s]*\}[,;]?\n/g, "");
97
+
98
+ if (!config.includes('from "path"') && config.includes("resolve(")) {
99
+ config = 'import { resolve } from "path";\n' + config;
100
+ }
101
+
102
+ config = config.replace(
103
+ /from ["']\.\.\/shared\/vite-base\.config["']/g,
104
+ 'from "./shared/vite-base.config"'
105
+ );
106
+ config = config.replace(
107
+ /from ["']\.\.\/shared\/vitest-base\.config["']/g,
108
+ 'from "./shared/vitest-base.config"'
109
+ );
110
+
111
+ writeFileSync(configPath, config);
112
+ }
110
113
  }
111
114
 
112
115
  function cleanSharedViteConfig(sharedDir: string): void {
113
- const viteBaseConfigPath = join(sharedDir, "vite-base.config.ts");
114
- const vitestBaseConfigPath = join(sharedDir, "vitest-base.config.ts");
115
-
116
- for (const configPath of [viteBaseConfigPath, vitestBaseConfigPath]) {
117
- if (!existsSync(configPath)) continue;
118
-
119
- let config = readFileSync(configPath, "utf-8");
120
- config = config.replace(
121
- /,\n\s+resolve:\s*\{\n\s+alias:\s*\{[^}]*\}[,\s]*\}[,;]?\n/g,
122
- ""
123
- );
124
- config = config.replace(
125
- /,\n\s+alias:\s*\{[^}]*\}[,\s]*[,;]?\n/g,
126
- ""
127
- );
128
- if (!config.includes("resolve(")) {
129
- config = config.replace(/import\s*\{\s*resolve\s*\}\s*from\s*["']path["'];?\n?/g, "");
130
- config = config.replace(
131
- /export function create(Vite|Vitest)Config\(dirname: string\)/g,
132
- "export function create$1Config()"
133
- );
134
- }
135
- writeFileSync(configPath, config);
136
- }
116
+ const viteBaseConfigPath = join(sharedDir, "vite-base.config.ts");
117
+ const vitestBaseConfigPath = join(sharedDir, "vitest-base.config.ts");
118
+
119
+ for (const configPath of [viteBaseConfigPath, vitestBaseConfigPath]) {
120
+ if (!existsSync(configPath)) continue;
121
+
122
+ let config = readFileSync(configPath, "utf-8");
123
+ config = config.replace(
124
+ /,?\s*\n?\s*"@dyyz1993\/rpc-core":\s*resolve\(\s*\n?\s*dirname,\s*\n?\s*"\.\.",\s*\n?\s*"\.\.",\s*\n?\s*"packages",\s*\n?\s*"rpc-core",\s*\n?\s*"src",\s*\n?\s*"index\.ts"\s*\n?\s*\),?\s*\n?/g,
125
+ ""
126
+ );
127
+ config = config.replace(
128
+ /resolve\(\s*\n?\s*dirname,\s*\n?\s*"\.\.",\s*\n?\s*"shared"\s*\n?\s*\)/g,
129
+ 'resolve(dirname, "shared")'
130
+ );
131
+ writeFileSync(configPath, config);
132
+ }
137
133
  }
138
134
 
139
135
  function resolvePackageVersion(packageName: string): string {
140
- try {
141
- return execSync(`npm view ${packageName} version`, { encoding: 'utf-8' }).trim();
142
- } catch {
143
- return '1.0.0';
144
- }
136
+ try {
137
+ return execSync(`npm view ${packageName} version`, { encoding: "utf-8" }).trim();
138
+ } catch {
139
+ return "1.0.0";
140
+ }
145
141
  }
146
142
 
147
- const WORKSPACE_PACKAGES = [
148
- '@dyyz1993/rpc-core',
149
- '@dyyz1993/eslint-plugin-rpc',
150
- ];
143
+ const WORKSPACE_PACKAGES = ["@dyyz1993/rpc-core", "@dyyz1993/eslint-plugin-rpc"];
151
144
 
152
145
  function updatePackageJson(targetDir: string, _projectName: string): void {
153
- const rootPkgPath = join(targetDir, 'package.json');
154
- if (!existsSync(rootPkgPath)) return;
155
-
156
- const rootPkg = JSON.parse(readFileSync(rootPkgPath, 'utf-8'));
157
-
158
- delete rootPkg.workspaces;
159
-
160
- for (const depKey of ['dependencies', 'devDependencies'] as const) {
161
- if (!rootPkg[depKey]) continue;
162
- for (const pkgName of WORKSPACE_PACKAGES) {
163
- if (!rootPkg[depKey][pkgName]) continue;
164
- if (pkgName === '@dyyz1993/rpc-core') {
165
- const version = resolvePackageVersion(pkgName);
166
- rootPkg[depKey][pkgName] = `^${version}`;
167
- } else {
168
- delete rootPkg[depKey][pkgName];
169
- }
170
- }
171
- }
172
-
173
- writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, '\t') + '\n');
174
-
175
- const eslintConfigPath = join(targetDir, 'eslint.config.mjs');
176
- if (existsSync(eslintConfigPath)) {
177
- const lines = readFileSync(eslintConfigPath, 'utf-8').split('\n');
178
- const filteredLines: string[] = [];
179
-
180
- let inRpcSection = false;
181
- for (let i = 0; i < lines.length; i++) {
182
- const line = lines[i];
183
-
184
- if (line.includes('import rpcPlugin') && line.includes('@dyyz1993/eslint-plugin-rpc')) {
185
- continue;
186
- }
187
-
188
- if (line.includes('RPC') && line.includes('规范规则')) {
189
- inRpcSection = true;
190
- continue;
191
- }
192
-
193
- if (inRpcSection) {
194
- if (line.trim().startsWith("'rpc/") || line.trim().startsWith('"rpc/')) {
195
- continue;
196
- }
197
- inRpcSection = false;
198
- }
199
-
200
- if (line.includes('rpc: rpcPlugin')) {
201
- continue;
202
- }
203
-
204
- filteredLines.push(line);
205
- }
206
-
207
- let content = filteredLines.join('\n');
208
- content = content.replace(/\n\s+plugins:\s*\{\s*\},?\s*\n/g, '\n');
209
-
210
- writeFileSync(eslintConfigPath, content);
211
- }
146
+ const rootPkgPath = join(targetDir, "package.json");
147
+ if (!existsSync(rootPkgPath)) return;
148
+
149
+ const rootPkg = JSON.parse(readFileSync(rootPkgPath, "utf-8"));
150
+
151
+ delete rootPkg.workspaces;
152
+
153
+ for (const depKey of ["dependencies", "devDependencies"] as const) {
154
+ if (!rootPkg[depKey]) continue;
155
+ for (const pkgName of WORKSPACE_PACKAGES) {
156
+ if (!rootPkg[depKey][pkgName]) continue;
157
+ if (pkgName === "@dyyz1993/rpc-core") {
158
+ const version = resolvePackageVersion(pkgName);
159
+ rootPkg[depKey][pkgName] = `^${version}`;
160
+ } else {
161
+ delete rootPkg[depKey][pkgName];
162
+ }
163
+ }
164
+ }
165
+
166
+ writeFileSync(rootPkgPath, JSON.stringify(rootPkg, null, "\t") + "\n");
167
+
168
+ const eslintConfigPath = join(targetDir, "eslint.config.mjs");
169
+ if (existsSync(eslintConfigPath)) {
170
+ const lines = readFileSync(eslintConfigPath, "utf-8").split("\n");
171
+ const filteredLines: string[] = [];
172
+
173
+ let inRpcSection = false;
174
+ for (let i = 0; i < lines.length; i++) {
175
+ const line = lines[i];
176
+
177
+ if (line.includes("import rpcPlugin") && line.includes("@dyyz1993/eslint-plugin-rpc")) {
178
+ continue;
179
+ }
180
+
181
+ if (line.includes("RPC") && line.includes("规范规则")) {
182
+ inRpcSection = true;
183
+ continue;
184
+ }
185
+
186
+ if (inRpcSection) {
187
+ if (line.trim().startsWith("'rpc/") || line.trim().startsWith('"rpc/')) {
188
+ continue;
189
+ }
190
+ inRpcSection = false;
191
+ }
192
+
193
+ if (line.includes("rpc: rpcPlugin")) {
194
+ continue;
195
+ }
196
+
197
+ filteredLines.push(line);
198
+ }
199
+
200
+ let content = filteredLines.join("\n");
201
+ content = content.replace(/\n\s+plugins:\s*\{\s*\},?\s*\n/g, "\n");
202
+
203
+ writeFileSync(eslintConfigPath, content);
204
+ }
212
205
  }
213
206
 
214
207
  export async function copyTemplate(options: CopyOptions): Promise<void> {
215
- const { projectName, templateDir, targetDir } = options;
216
- const localRoot = resolve(import.meta.dir, '..', '..', '..', '..');
217
- const isMonorepo = existsSync(resolve(localRoot, 'templates', 'general'));
218
-
219
- if (existsSync(targetDir)) {
220
- throw new Error(`Directory "${targetDir}" already exists.`);
221
- }
222
-
223
- const { identifier } = deriveNames(projectName);
224
-
225
- console.log(`Creating project: ${projectName}`);
226
- console.log(`Target directory: ${targetDir}`);
227
- console.log(`App identifier: ${identifier}`);
228
- console.log('');
229
-
230
- copyAndReplace(templateDir, targetDir, projectName);
231
- if (isMonorepo) {
232
- copyTraeRules(localRoot, targetDir, projectName);
233
- copySharedModules(localRoot, targetDir, projectName);
234
- }
235
- cleanViteConfig(targetDir);
236
- updatePackageJson(targetDir, projectName);
237
-
238
- console.log('Initializing git...');
239
- execSync('git init', { cwd: targetDir, stdio: 'pipe' });
240
-
241
- console.log('Installing dependencies...');
242
- execSync('bun install', { cwd: targetDir, stdio: 'inherit' });
243
-
244
- console.log('Building browser bundle...');
245
- try {
246
- execSync('bun run build:browser', { cwd: targetDir, stdio: 'pipe' });
247
- } catch {
248
- console.log('(build:browser skipped - script not found)');
249
- }
250
-
251
- const huskyDir = join(targetDir, '.husky');
252
- mkdirSync(huskyDir, { recursive: true });
253
- writeFileSync(
254
- join(huskyDir, 'pre-commit'),
255
- [
256
- '#!/bin/sh',
257
- 'bun run lint',
258
- 'ERRORS=$(bunx tsc --noEmit 2>&1 | grep "error TS" | grep -v "node_modules" || true)',
259
- 'if [ -n "$ERRORS" ]; then',
260
- ' echo "$ERRORS"',
261
- ' exit 1',
262
- 'fi',
263
- ].join('\n') + '\n',
264
- );
265
-
266
- execSync('git add -A', { cwd: targetDir, stdio: 'pipe' });
267
-
268
- try {
269
- execSync(`git commit --no-verify -m "feat: init ${projectName} from pi-agent-template"`, {
270
- cwd: targetDir,
271
- stdio: 'pipe',
272
- });
273
- } catch {
274
- console.log('(git commit skipped - no files to commit)');
275
- }
276
-
277
- console.log('');
278
- console.log('Project created successfully!');
279
- console.log('');
280
- console.log('Next steps:');
281
- console.log(` cd ${targetDir}`);
282
- console.log(' bun run dev # Start desktop app (Electrobun)');
283
- console.log(' bun run dev:web # Start web mode (Vite + Gateway)');
284
- console.log('');
208
+ const { projectName, templateDir, targetDir } = options;
209
+ const localRoot = resolve(import.meta.dir, "..", "..", "..", "..");
210
+ const isMonorepo = existsSync(resolve(localRoot, "templates", "general"));
211
+
212
+ if (existsSync(targetDir)) {
213
+ throw new Error(`Directory "${targetDir}" already exists.`);
214
+ }
215
+
216
+ const { identifier } = deriveNames(projectName);
217
+
218
+ console.log(`Creating project: ${projectName}`);
219
+ console.log(`Target directory: ${targetDir}`);
220
+ console.log(`App identifier: ${identifier}`);
221
+ console.log("");
222
+
223
+ copyAndReplace(templateDir, targetDir, projectName);
224
+ if (isMonorepo) {
225
+ copyTraeRules(localRoot, targetDir, projectName);
226
+ copySharedModules(localRoot, targetDir, projectName);
227
+ }
228
+ cleanViteConfig(targetDir);
229
+ updatePackageJson(targetDir, projectName);
230
+
231
+ console.log("Initializing git...");
232
+ execSync("git init", { cwd: targetDir, stdio: "pipe" });
233
+
234
+ console.log("Installing dependencies...");
235
+ execSync("bun install", { cwd: targetDir, stdio: "inherit" });
236
+
237
+ console.log("Building browser bundle...");
238
+ try {
239
+ execSync("bun run build:browser", { cwd: targetDir, stdio: "pipe" });
240
+ } catch {
241
+ console.log("(build:browser skipped - script not found)");
242
+ }
243
+
244
+ const huskyDir = join(targetDir, ".husky");
245
+ mkdirSync(huskyDir, { recursive: true });
246
+ writeFileSync(
247
+ join(huskyDir, "pre-commit"),
248
+ [
249
+ "#!/bin/sh",
250
+ "bun run lint",
251
+ 'ERRORS=$(bunx tsc --noEmit 2>&1 | grep "error TS" | grep -v "node_modules" || true)',
252
+ 'if [ -n "$ERRORS" ]; then',
253
+ ' echo "$ERRORS"',
254
+ " exit 1",
255
+ "fi",
256
+ ].join("\n") + "\n"
257
+ );
258
+
259
+ execSync("git add -A", { cwd: targetDir, stdio: "pipe" });
260
+
261
+ try {
262
+ execSync(`git commit --no-verify -m "feat: init ${projectName} from pi-agent-template"`, {
263
+ cwd: targetDir,
264
+ stdio: "pipe",
265
+ });
266
+ } catch {
267
+ console.log("(git commit skipped - no files to commit)");
268
+ }
269
+
270
+ console.log("");
271
+ console.log("Project created successfully!");
272
+ console.log("");
273
+ console.log("Next steps:");
274
+ console.log(` cd ${targetDir}`);
275
+ console.log(" bun run dev # Start desktop app (Electrobun)");
276
+ console.log(" bun run dev:web # Start web mode (Vite + Gateway)");
277
+ console.log("");
285
278
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * eslint-plugin-rpc — RPC 模块化规范 ESLint 插件(单注册架构)
3
+ *
4
+ * 规则列表:
5
+ * - rpc/no-bare-method : 方法名必须使用 module.action 格式
6
+ * - rpc/no-direct-register : server.register() 只允许在 handlers/ 目录内使用
7
+ * - rpc/schema-merge-only : rpc-schema.ts 禁止直接定义方法
8
+ * - rpc/module-file-naming : 模块文件命名、导出、方法前缀强制规范
9
+ * - rpc/require-typed-register : 入口文件必须导入 registerAllHandlers
10
+ * - rpc/require-api-client : 前端必须通过 apiClient 调用 RPC
11
+ */
12
+ "use strict";
13
+
14
+ const noBareMethod = require("./rules/no-bare-method");
15
+ const noDirectRegister = require("./rules/no-direct-register");
16
+ const schemaMergeOnly = require("./rules/schema-merge-only");
17
+ const moduleFileNaming = require("./rules/module-file-naming");
18
+ const requireTypedRegister = require("./rules/require-typed-register");
19
+ const requireApiClient = require("./rules/require-api-client");
20
+ const noHardcodedStrings = require("./rules/no-hardcoded-strings");
21
+
22
+ module.exports = {
23
+ meta: {
24
+ name: "eslint-plugin-rpc",
25
+ version: "1.0.0",
26
+ },
27
+ rules: {
28
+ "no-bare-method": noBareMethod,
29
+ "no-direct-register": noDirectRegister,
30
+ "schema-merge-only": schemaMergeOnly,
31
+ "module-file-naming": moduleFileNaming,
32
+ "require-typed-register": requireTypedRegister,
33
+ "require-api-client": requireApiClient,
34
+ "no-hardcoded-strings": noHardcodedStrings,
35
+ },
36
+ configs: {
37
+ recommended: {
38
+ plugins: ["rpc"],
39
+ rules: {
40
+ "rpc/no-bare-method": "error",
41
+ "rpc/no-direct-register": "error",
42
+ "rpc/schema-merge-only": "error",
43
+ "rpc/module-file-naming": "error",
44
+ "rpc/require-typed-register": "error",
45
+ "rpc/require-api-client": "error",
46
+ "rpc/no-hardcoded-strings": "warn",
47
+ },
48
+ },
49
+ },
50
+ };
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "@dyyz1993/eslint-plugin-rpc",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "main": "index.js"
6
+ }