add-coder 0.1.8 → 0.1.9
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 +3 -1
- package/dist/index.js +425 -117
- package/package.json +32 -5
- package/templates/core/podman-compose.yml +19 -0
- package/templates/core/prisma/add.prisma +12 -5
- package/templates/core/scripts/db-ensure.sh +83 -0
- package/templates/core/scripts/mcp-server.ts +27 -13
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# add-coder
|
|
2
2
|
|
|
3
|
-
**AI 代码治理的落地方案** — [codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。[本项目NPM包地址](https://www.npmjs.com/package/add-coder)
|
|
3
|
+
**AI 代码治理的落地方案** — [codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。[本项目NPM包地址](https://www.npmjs.com/package/add-coder)[本项目GITHUB地址](https://github.com/xiaomingming92/add-coder)
|
|
4
|
+
|
|
5
|
+
> 🧭 **从零上手实操?** 请参见 [GUIDE.md](./GUIDE.md) — 包含触发词速查、需求转 Plan、完整链路演练。
|
|
4
6
|
|
|
5
7
|
```bash
|
|
6
8
|
npx add-coder init
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli/index.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/caijuehub/strategies/detect.strategy.ts
|
|
@@ -33,9 +33,7 @@ function detectIDE(projectRoot) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
// src/caijuehub/strategies/adapter.strategy.ts
|
|
36
|
-
var AUTO_DEPLOY_ADAPTERS = ["claude", "qoder", "vscode"];
|
|
37
36
|
function resolveAdapters(target) {
|
|
38
|
-
if (target === "auto") return AUTO_DEPLOY_ADAPTERS;
|
|
39
37
|
return [target];
|
|
40
38
|
}
|
|
41
39
|
|
|
@@ -53,7 +51,7 @@ import { join as join2 } from "path";
|
|
|
53
51
|
|
|
54
52
|
// src/config/schema.ts
|
|
55
53
|
import { z } from "zod";
|
|
56
|
-
var AdapterEnum = z.enum(["claude", "qoder", "vscode"
|
|
54
|
+
var AdapterEnum = z.enum(["claude", "qoder", "vscode"]);
|
|
57
55
|
var AddCoderConfigSchema = z.object({
|
|
58
56
|
projectName: z.string().min(1, "\u9879\u76EE\u540D\u4E0D\u80FD\u4E3A\u7A7A"),
|
|
59
57
|
projectRoot: z.string().default(""),
|
|
@@ -64,7 +62,8 @@ var AddCoderConfigSchema = z.object({
|
|
|
64
62
|
auditLoggerPath: z.string().default("src/lib/agent-audit-logger.ts"),
|
|
65
63
|
mcpServerCommand: z.string().default("tsx"),
|
|
66
64
|
agentAuditImport: z.string().default("@/lib/agent-audit-logger"),
|
|
67
|
-
|
|
65
|
+
magicDir: z.string(),
|
|
66
|
+
adapters: z.array(AdapterEnum).default([]),
|
|
68
67
|
overrides: z.record(z.string(), z.string()).default({})
|
|
69
68
|
});
|
|
70
69
|
|
|
@@ -79,7 +78,8 @@ var defaults = {
|
|
|
79
78
|
auditLoggerPath: "src/lib/agent-audit-logger.ts",
|
|
80
79
|
mcpServerCommand: "tsx",
|
|
81
80
|
agentAuditImport: "@/lib/agent-audit-logger",
|
|
82
|
-
|
|
81
|
+
magicDir: "",
|
|
82
|
+
adapters: [],
|
|
83
83
|
overrides: {}
|
|
84
84
|
};
|
|
85
85
|
|
|
@@ -113,11 +113,13 @@ ${errors}`);
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// src/caijuehub/strategies/writer.strategy.ts
|
|
116
|
-
import { existsSync as
|
|
116
|
+
import { existsSync as existsSync4, mkdirSync, writeFileSync, chmodSync } from "fs";
|
|
117
117
|
import { join as join3, dirname } from "path";
|
|
118
118
|
|
|
119
119
|
// src/lib/utils.ts
|
|
120
120
|
import { createInterface } from "readline";
|
|
121
|
+
import { existsSync as existsSync3 } from "fs";
|
|
122
|
+
import { resolve } from "path";
|
|
121
123
|
function ask(q) {
|
|
122
124
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
123
125
|
return new Promise((r) => {
|
|
@@ -127,6 +129,9 @@ function ask(q) {
|
|
|
127
129
|
});
|
|
128
130
|
});
|
|
129
131
|
}
|
|
132
|
+
function detectPm(projectRoot) {
|
|
133
|
+
return existsSync3(resolve(projectRoot, "pnpm-lock.yaml")) ? "pnpm" : "npm";
|
|
134
|
+
}
|
|
130
135
|
|
|
131
136
|
// src/caijuehub/strategies/writer.strategy.ts
|
|
132
137
|
var WRITER_CONFIG = {
|
|
@@ -137,21 +142,27 @@ var WRITER_CONFIG = {
|
|
|
137
142
|
async function writeFiles(projectRoot, files, options = {}) {
|
|
138
143
|
const C = WRITER_CONFIG;
|
|
139
144
|
let created = 0, skipped = 0, overwritten = 0;
|
|
145
|
+
let skipAll = false;
|
|
140
146
|
for (const [relPath, content] of files) {
|
|
141
147
|
const dest = join3(projectRoot, relPath);
|
|
142
148
|
if (options.dryRun) {
|
|
143
|
-
console.log(`[dry-run] ${
|
|
149
|
+
console.log(`[dry-run] ${existsSync4(dest) ? "\u8986\u76D6" : "\u65B0\u5EFA"}: ${relPath}`);
|
|
144
150
|
created++;
|
|
145
151
|
continue;
|
|
146
152
|
}
|
|
147
|
-
if (
|
|
153
|
+
if (existsSync4(dest)) {
|
|
148
154
|
if (options.force) {
|
|
149
155
|
overwritten++;
|
|
150
|
-
} else if (options.yes || C.onExisting === "skip") {
|
|
156
|
+
} else if (options.yes || skipAll || C.onExisting === "skip") {
|
|
151
157
|
skipped++;
|
|
152
158
|
continue;
|
|
153
159
|
} else {
|
|
154
|
-
const choice = await ask(`\u6587\u4EF6\u5DF2\u5B58\u5728 ${relPath}\uFF1A[s]\u8DF3\u8FC7 / [o]\u8986\u76D6\uFF08\u9ED8\u8BA4 s\uFF09: `);
|
|
160
|
+
const choice = await ask(`\u6587\u4EF6\u5DF2\u5B58\u5728 ${relPath}\uFF1A[s]\u8DF3\u8FC7 / [o]\u8986\u76D6 / [a]\u5168\u90E8\u8DF3\u8FC7\uFF08\u9ED8\u8BA4 s\uFF09: `);
|
|
161
|
+
if (choice === "a") {
|
|
162
|
+
skipAll = true;
|
|
163
|
+
skipped++;
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
155
166
|
if (choice !== "o" && choice !== "overwrite") {
|
|
156
167
|
skipped++;
|
|
157
168
|
continue;
|
|
@@ -199,20 +210,21 @@ var PLACEHOLDERS = {
|
|
|
199
210
|
function render(content, config) {
|
|
200
211
|
let result = content;
|
|
201
212
|
for (const [placeholder, key] of Object.entries(PLACEHOLDERS)) {
|
|
202
|
-
result = result.replaceAll(placeholder, config[key]);
|
|
213
|
+
result = result.replaceAll(placeholder, String(config[key]));
|
|
203
214
|
}
|
|
204
215
|
return result;
|
|
205
216
|
}
|
|
206
217
|
var TEMPLATES_ROOT = join4(__dirname, "../templates");
|
|
207
218
|
var CORE_DIR = join4(TEMPLATES_ROOT, "core");
|
|
208
219
|
var CORE_TARGET = ".add";
|
|
220
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["prisma"]);
|
|
209
221
|
function renderCore(config, dryRun) {
|
|
210
222
|
const files = /* @__PURE__ */ new Map();
|
|
211
223
|
function walk(dir, base) {
|
|
212
224
|
for (const name of readdirSync(dir)) {
|
|
213
225
|
const full = join4(dir, name);
|
|
214
226
|
if (statSync(full).isDirectory()) {
|
|
215
|
-
walk(full, join4(base, name));
|
|
227
|
+
if (!SKIP_DIRS.has(name)) walk(full, join4(base, name));
|
|
216
228
|
} else {
|
|
217
229
|
const content = readFileSync2(full, "utf-8");
|
|
218
230
|
const rendered = render(content, config);
|
|
@@ -322,13 +334,13 @@ function renderAdapter3(config, targetDir, dryRun) {
|
|
|
322
334
|
}
|
|
323
335
|
|
|
324
336
|
// src/cli/prisma-injector.ts
|
|
325
|
-
import { resolve as
|
|
337
|
+
import { resolve as resolve3, dirname as dirname6 } from "path";
|
|
326
338
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
327
339
|
|
|
328
340
|
// src/caijuehub/strategies/prisma.strategy.ts
|
|
329
341
|
import { spawnSync } from "child_process";
|
|
330
|
-
import { copyFileSync, existsSync as
|
|
331
|
-
import { resolve } from "path";
|
|
342
|
+
import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync6, unlinkSync, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
343
|
+
import { resolve as resolve2 } from "path";
|
|
332
344
|
var PRISMA_CONFIG = {
|
|
333
345
|
onMissing: "ask",
|
|
334
346
|
onExistingAddPrisma: "ask",
|
|
@@ -336,70 +348,118 @@ var PRISMA_CONFIG = {
|
|
|
336
348
|
autoGenerate: true,
|
|
337
349
|
migrationName: "add_workflow_init",
|
|
338
350
|
schemaArg: "--schema=prisma/",
|
|
339
|
-
requiresUserModel:
|
|
351
|
+
requiresUserModel: false
|
|
340
352
|
};
|
|
353
|
+
function ensurePrismaConfig(projectRoot) {
|
|
354
|
+
const configPath = resolve2(projectRoot, "prisma.config.ts");
|
|
355
|
+
writeFileSync2(configPath, [
|
|
356
|
+
'import dotenv from "dotenv";',
|
|
357
|
+
'import { existsSync } from "fs";',
|
|
358
|
+
'for (const f of [".env.development.local", ".env.development", ".env.local", ".env"]) {',
|
|
359
|
+
" if (existsSync(f)) { dotenv.config({ path: f }); break; }",
|
|
360
|
+
"}",
|
|
361
|
+
'import { defineConfig, env } from "prisma/config";',
|
|
362
|
+
"export default defineConfig({",
|
|
363
|
+
' schema: "prisma",',
|
|
364
|
+
" datasource: {",
|
|
365
|
+
' url: env("DATABASE_URL"),',
|
|
366
|
+
" },",
|
|
367
|
+
"});"
|
|
368
|
+
].join("\n") + "\n", "utf-8");
|
|
369
|
+
}
|
|
370
|
+
function backupAddTables(projectRoot) {
|
|
371
|
+
const pgDump = spawnSync("which", ["pg_dump"], { timeout: 2e3 });
|
|
372
|
+
if (pgDump.status !== 0) return null;
|
|
373
|
+
const bak = resolve2(projectRoot, `add-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.sql`);
|
|
374
|
+
const r = spawnSync("pg_dump", ["--table=AddUser", "--table=DevOperation", "--table=AuditLog", "--if-exists"], {
|
|
375
|
+
cwd: projectRoot,
|
|
376
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
377
|
+
timeout: 3e4
|
|
378
|
+
});
|
|
379
|
+
if (r.stdout.length > 0) {
|
|
380
|
+
writeFileSync2(bak, r.stdout, "utf-8");
|
|
381
|
+
console.log(`>>> \u5907\u4EFD ADD \u8868\u5230 ${bak}`);
|
|
382
|
+
return bak;
|
|
383
|
+
}
|
|
384
|
+
return null;
|
|
385
|
+
}
|
|
386
|
+
function runPrismaInit(projectRoot, provider, schemaPath) {
|
|
387
|
+
console.log("\u6267\u884C npx prisma init ...");
|
|
388
|
+
const pm = detectPm(projectRoot);
|
|
389
|
+
const initArgs = pm === "pnpm" ? ["dlx", "prisma", "init", "--datasource-provider", provider] : ["prisma", "init", "--datasource-provider", provider];
|
|
390
|
+
const initResult = spawnSync(pm, initArgs, {
|
|
391
|
+
cwd: projectRoot,
|
|
392
|
+
stdio: "inherit",
|
|
393
|
+
shell: false
|
|
394
|
+
});
|
|
395
|
+
if (initResult.status !== 0 || !existsSync5(schemaPath)) {
|
|
396
|
+
console.log("prisma init \u5931\u8D25\uFF0C\u624B\u52A8\u521B\u5EFA schema.prisma ...");
|
|
397
|
+
const prismaDir = resolve2(projectRoot, "prisma");
|
|
398
|
+
if (!existsSync5(prismaDir)) mkdirSync2(prismaDir, { recursive: true });
|
|
399
|
+
const content = `generator client {
|
|
400
|
+
provider = "prisma-client-js"
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
datasource db {
|
|
404
|
+
provider = "${provider}"
|
|
405
|
+
}
|
|
406
|
+
`;
|
|
407
|
+
writeFileSync2(schemaPath, content, "utf-8");
|
|
408
|
+
const devEnvPath = resolve2(projectRoot, ".env.development");
|
|
409
|
+
if (!existsSync5(devEnvPath)) {
|
|
410
|
+
const defaultUrl = provider === "sqlite" ? 'DATABASE_URL="file:./data/dev.db"' : '# \u8BF7\u7F16\u8F91\u4E3A\u4F60\u7684\u6570\u636E\u5E93\u8FDE\u63A5\u4FE1\u606F\nDATABASE_URL="postgresql://USER:PASSWORD@HOST:PORT/DB?schema=public"';
|
|
411
|
+
writeFileSync2(devEnvPath, defaultUrl + "\n", "utf-8");
|
|
412
|
+
console.log("\u5DF2\u521B\u5EFA .env.development");
|
|
413
|
+
}
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
function postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath) {
|
|
419
|
+
const envPath = resolve2(projectRoot, ".env");
|
|
420
|
+
const devEnvPath = resolve2(projectRoot, ".env.development");
|
|
421
|
+
if (existsSync5(envPath)) {
|
|
422
|
+
const envContent = readFileSync6(envPath, "utf-8");
|
|
423
|
+
const dbUrl = envContent.match(/DATABASE_URL=.*/);
|
|
424
|
+
if (dbUrl) {
|
|
425
|
+
const existing = existsSync5(devEnvPath) ? readFileSync6(devEnvPath, "utf-8") : "";
|
|
426
|
+
if (!existing.includes("DATABASE_URL=")) {
|
|
427
|
+
writeFileSync2(devEnvPath, `${existing}${existing ? "\n" : ""}${dbUrl[0]}
|
|
428
|
+
`, "utf-8");
|
|
429
|
+
}
|
|
430
|
+
if (existsSync5(envPath)) unlinkSync(envPath);
|
|
431
|
+
console.log("\u5DF2\u5C06 DATABASE_URL \u8FC1\u79FB\u5230 .env.development");
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
copyFileSync(addPrismaTemplate, destPath);
|
|
435
|
+
console.log("\u5DF2\u590D\u5236 add.prisma");
|
|
436
|
+
}
|
|
341
437
|
async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
342
438
|
const C = PRISMA_CONFIG;
|
|
343
|
-
const prismaDir =
|
|
344
|
-
const schemaPath =
|
|
345
|
-
const destPath =
|
|
346
|
-
|
|
439
|
+
const prismaDir = resolve2(projectRoot, "prisma");
|
|
440
|
+
const schemaPath = resolve2(prismaDir, "schema.prisma");
|
|
441
|
+
const destPath = resolve2(prismaDir, "add.prisma");
|
|
442
|
+
let justInited = false;
|
|
443
|
+
if (!existsSync5(prismaDir) || !existsSync5(schemaPath)) {
|
|
347
444
|
if (C.onMissing === "skip") {
|
|
348
445
|
console.log("\u8DF3\u8FC7\uFF1A\u7F3A\u5C11 Prisma");
|
|
349
446
|
return true;
|
|
350
447
|
}
|
|
351
|
-
|
|
448
|
+
const shouldInit = options.force || options.yes;
|
|
449
|
+
if (!shouldInit && C.onMissing === "ask") {
|
|
352
450
|
const a = await ask("\u9879\u76EE\u7F3A\u5C11 Prisma\uFF0C\u662F\u5426\u6267\u884C prisma init\uFF1F[Y/n] ");
|
|
353
|
-
if (a
|
|
354
|
-
|
|
355
|
-
spawnSync("npx", ["prisma", "init", "--datasource-provider", "postgresql"], { cwd: projectRoot, stdio: "inherit", shell: false });
|
|
356
|
-
const envPath = resolve(projectRoot, ".env");
|
|
357
|
-
const devEnvPath = resolve(projectRoot, ".env.development");
|
|
358
|
-
if (existsSync4(envPath)) {
|
|
359
|
-
const envContent = readFileSync6(envPath, "utf-8");
|
|
360
|
-
const dbUrl = envContent.match(/DATABASE_URL=.*/);
|
|
361
|
-
if (dbUrl) {
|
|
362
|
-
const existing = existsSync4(devEnvPath) ? readFileSync6(devEnvPath, "utf-8") : "";
|
|
363
|
-
if (!existing.includes("DATABASE_URL=")) {
|
|
364
|
-
writeFileSync2(devEnvPath, `${existing}${existing ? "\n" : ""}${dbUrl[0]}
|
|
365
|
-
`, "utf-8");
|
|
366
|
-
}
|
|
367
|
-
if (existsSync4(envPath)) unlinkSync(envPath);
|
|
368
|
-
console.log("\u5DF2\u5C06 DATABASE_URL \u8FC1\u79FB\u5230 .env.development");
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
console.log("\u8BF7\u7F16\u8F91 .env.development \u914D\u7F6E DATABASE_URL\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C add-coder init \u5B8C\u6210\u8FC1\u79FB");
|
|
372
|
-
console.log(" \u53EF\u9009\u6587\u4EF6\u4F18\u5148\u7EA7: .env.development.local > .env.development > .env.local > .env");
|
|
373
|
-
const schemaContent = readFileSync6(schemaPath, "utf-8");
|
|
374
|
-
if (!/model\s+User\s*\{/.test(schemaContent)) {
|
|
375
|
-
writeFileSync2(schemaPath, "model User {\n id String @id @default(cuid())\n}\n\n" + schemaContent, "utf-8");
|
|
376
|
-
console.log("\u5DF2\u6CE8\u5165 User \u6A21\u578B");
|
|
377
|
-
}
|
|
378
|
-
copyFileSync(addPrismaTemplate, destPath);
|
|
379
|
-
console.log("\u5DF2\u590D\u5236 add.prisma");
|
|
380
|
-
return true;
|
|
451
|
+
if (a === "n" || a === "no") {
|
|
452
|
+
throw new Error("\u9879\u76EE\u7F3A\u5C11 Prisma \u914D\u7F6E\u3002ADD \u5DE5\u4F5C\u6D41\u4F9D\u8D56 Prisma + PostgreSQL\u3002");
|
|
381
453
|
}
|
|
454
|
+
} else if (!shouldInit) {
|
|
455
|
+
throw new Error("\u9879\u76EE\u7F3A\u5C11 Prisma \u914D\u7F6E\u3002ADD \u5DE5\u4F5C\u6D41\u4F9D\u8D56 Prisma + PostgreSQL\u3002");
|
|
382
456
|
}
|
|
383
|
-
|
|
457
|
+
const provider = options.datasource || "postgresql";
|
|
458
|
+
runPrismaInit(projectRoot, provider, schemaPath);
|
|
459
|
+
postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath);
|
|
460
|
+
justInited = true;
|
|
384
461
|
}
|
|
385
|
-
if (
|
|
386
|
-
const userModel = "model User {\n id String @id @default(cuid())\n}\n";
|
|
387
|
-
if (options.yes || options.force) {
|
|
388
|
-
const schema = readFileSync6(schemaPath, "utf-8");
|
|
389
|
-
writeFileSync2(schemaPath, userModel + "\n" + schema, "utf-8");
|
|
390
|
-
console.log("\u5DF2\u6CE8\u5165 User \u6A21\u578B\u5230 schema.prisma");
|
|
391
|
-
} else {
|
|
392
|
-
const a = await ask("schema.prisma \u7F3A\u5C11 User \u6A21\u578B\uFF0C\u662F\u5426\u81EA\u52A8\u6CE8\u5165\uFF1F[Y/n] ");
|
|
393
|
-
if (a !== "n" && a !== "no") {
|
|
394
|
-
const schema = readFileSync6(schemaPath, "utf-8");
|
|
395
|
-
writeFileSync2(schemaPath, userModel + "\n" + schema, "utf-8");
|
|
396
|
-
console.log("\u5DF2\u6CE8\u5165 User \u6A21\u578B");
|
|
397
|
-
} else {
|
|
398
|
-
throw new Error("\u9700\u8981 User \u6A21\u578B\uFF08id: String\uFF09\uFF0C\u8BF7\u5728 prisma/schema.prisma \u4E2D\u521B\u5EFA\u540E\u91CD\u8BD5\u3002");
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
if (existsSync4(destPath)) {
|
|
462
|
+
if (existsSync5(destPath) && !justInited) {
|
|
403
463
|
if (options.dryRun) {
|
|
404
464
|
console.log("[dry-run] \u5DF2\u6709 add.prisma");
|
|
405
465
|
return true;
|
|
@@ -429,17 +489,19 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
|
429
489
|
}
|
|
430
490
|
}
|
|
431
491
|
if (options.dryRun) {
|
|
432
|
-
console.log("[dry-run] \u5C06\u6267\u884C prisma
|
|
492
|
+
console.log("[dry-run] \u5C06\u6267\u884C prisma db push");
|
|
433
493
|
return true;
|
|
434
494
|
}
|
|
435
|
-
copyFileSync(addPrismaTemplate, destPath);
|
|
436
|
-
console.log("\u5DF2\u590D\u5236 add.prisma");
|
|
495
|
+
if (!justInited) copyFileSync(addPrismaTemplate, destPath);
|
|
437
496
|
try {
|
|
438
|
-
|
|
497
|
+
ensurePrismaConfig(projectRoot);
|
|
498
|
+
backupAddTables(projectRoot);
|
|
499
|
+
const pm = detectPm(projectRoot);
|
|
500
|
+
const args = pm === "pnpm" ? ["dlx", "prisma", "db", "push"] : ["prisma", "db", "push"];
|
|
439
501
|
if (C.schemaArg) args.push(C.schemaArg);
|
|
440
|
-
console.log(`\u6267\u884C
|
|
441
|
-
const r = spawnSync(
|
|
442
|
-
if (r.status !== 0) throw new Error(`prisma
|
|
502
|
+
console.log(`\u6267\u884C ${pm} ${args.join(" ")} ...`);
|
|
503
|
+
const r = spawnSync(pm, args, { cwd: projectRoot, stdio: "inherit", shell: false });
|
|
504
|
+
if (r.status !== 0) throw new Error(`prisma db push \u9000\u51FA\u7801: ${r.status}`);
|
|
443
505
|
} catch (err) {
|
|
444
506
|
if (C.onMigrateFail === "keep") {
|
|
445
507
|
console.log("\u8FC1\u79FB\u5931\u8D25\uFF0C\u4FDD\u7559\u6587\u4EF6");
|
|
@@ -453,8 +515,9 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
|
453
515
|
throw new Error(`\u8FC1\u79FB\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`);
|
|
454
516
|
}
|
|
455
517
|
if (C.autoGenerate) {
|
|
518
|
+
const pm = detectPm(projectRoot);
|
|
456
519
|
console.log("\u6267\u884C prisma generate ...");
|
|
457
|
-
spawnSync("
|
|
520
|
+
spawnSync(pm, pm === "pnpm" ? ["dlx", "prisma", "generate"] : ["prisma", "generate"], { cwd: projectRoot, stdio: "inherit", shell: false });
|
|
458
521
|
}
|
|
459
522
|
console.log("ADD \u6CBB\u7406\u6A21\u578B\u5DF2\u5C31\u7EEA");
|
|
460
523
|
return true;
|
|
@@ -463,69 +526,314 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
|
463
526
|
// src/cli/prisma-injector.ts
|
|
464
527
|
var __filename5 = fileURLToPath5(import.meta.url);
|
|
465
528
|
var __dirname5 = dirname6(__filename5);
|
|
466
|
-
var ADD_PRISMA_TEMPLATE =
|
|
529
|
+
var ADD_PRISMA_TEMPLATE = resolve3(__dirname5, "../templates/core/prisma/add.prisma");
|
|
467
530
|
async function injectPrisma2(projectRoot, options = {}) {
|
|
468
531
|
return injectPrisma(projectRoot, ADD_PRISMA_TEMPLATE, options);
|
|
469
532
|
}
|
|
470
533
|
|
|
471
534
|
// src/cli/commands/init.ts
|
|
535
|
+
import { readFileSync as readFileSync7, writeFileSync as writeFileSync3, existsSync as existsSync6, mkdirSync as mkdirSync3 } from "fs";
|
|
536
|
+
import { resolve as resolve4 } from "path";
|
|
537
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
538
|
+
import { createConnection } from "net";
|
|
472
539
|
var ADAPTER_RENDERERS = {
|
|
473
540
|
claude: renderAdapter,
|
|
474
541
|
qoder: renderAdapter2,
|
|
475
542
|
vscode: renderAdapter3
|
|
476
543
|
};
|
|
544
|
+
var MAGIC_DIR_MAP = { claude: ".claude", qoder: ".qoder", vscode: ".vscode" };
|
|
545
|
+
async function resolveAdapter(projectRoot, specified) {
|
|
546
|
+
if (specified) {
|
|
547
|
+
if (!MAGIC_DIR_MAP[specified]) throw new Error(`\u672A\u77E5 adapter: ${specified}`);
|
|
548
|
+
console.log(`\u76EE\u6807 IDE: ${specified} (--adapter)`);
|
|
549
|
+
return specified;
|
|
550
|
+
}
|
|
551
|
+
const detected = detectIDE2(projectRoot);
|
|
552
|
+
if (detected !== "auto") {
|
|
553
|
+
console.log(`\u68C0\u6D4B\u5230 IDE: ${detected} (\u81EA\u52A8)`);
|
|
554
|
+
return detected;
|
|
555
|
+
}
|
|
556
|
+
console.log("\u672A\u68C0\u6D4B\u5230 IDE \u73AF\u5883");
|
|
557
|
+
const a = (await ask("\u8BF7\u9009\u62E9\u76EE\u6807 IDE: [1] Qoder [2] Claude [3] VS Code \u2192 ")).trim();
|
|
558
|
+
if (a === "1" || a === "qoder") return "qoder";
|
|
559
|
+
if (a === "2" || a === "claude") return "claude";
|
|
560
|
+
if (a === "3" || a === "vscode") return "vscode";
|
|
561
|
+
console.log("\u8F93\u5165\u65E0\u6CD5\u8BC6\u522B\uFF0C\u9ED8\u8BA4 qoder");
|
|
562
|
+
return "qoder";
|
|
563
|
+
}
|
|
564
|
+
async function resolveDbEngine(force) {
|
|
565
|
+
if (force) {
|
|
566
|
+
console.log("\u6570\u636E\u5E93\u5F15\u64CE: PostgreSQL (--force \u9ED8\u8BA4)");
|
|
567
|
+
return { engine: "postgresql", container: "podman" };
|
|
568
|
+
}
|
|
569
|
+
console.log(["", "\u6570\u636E\u5E93\u5F15\u64CE:", " [1] PostgreSQL (\u63A8\u8350)", " [2] SQLite \u2014 \u96F6\u4F9D\u8D56", " [3] \u81EA\u884C\u7BA1\u7406"].join("\n"));
|
|
570
|
+
const a = (await ask("\u8BF7\u9009\u62E9 [1/2/3] \u2192 ")).trim();
|
|
571
|
+
if (a === "2" || a === "sqlite") return { engine: "sqlite" };
|
|
572
|
+
if (a === "3" || a === "manual") return { engine: "manual" };
|
|
573
|
+
if (a !== "" && a !== "1" && !a.startsWith("postgres")) console.log("\u8F93\u5165\u65E0\u6CD5\u8BC6\u522B\uFF0C\u9ED8\u8BA4 PostgreSQL");
|
|
574
|
+
return { engine: "postgresql" };
|
|
575
|
+
}
|
|
576
|
+
async function resolveContainer(force) {
|
|
577
|
+
if (force) return "podman";
|
|
578
|
+
console.log(["", "\u5BB9\u5668\u8FD0\u884C\u65F6:", " [1] podman (\u63A8\u8350)", " [2] docker", " [3] \u81EA\u884C\u7BA1\u7406"].join("\n"));
|
|
579
|
+
const a = (await ask("\u8BF7\u9009\u62E9 [1/2/3] \u2192 ")).trim();
|
|
580
|
+
if (a === "2" || a === "docker") return "docker";
|
|
581
|
+
if (a === "3" || a === "manual") return "manual";
|
|
582
|
+
if (a !== "" && a !== "1" && a !== "podman") console.log("\u8F93\u5165\u65E0\u6CD5\u8BC6\u522B\uFF0C\u9ED8\u8BA4 podman");
|
|
583
|
+
return "podman";
|
|
584
|
+
}
|
|
585
|
+
function portInUse(port) {
|
|
586
|
+
return new Promise((r) => {
|
|
587
|
+
const s = createConnection({ port, host: "127.0.0.1" }, () => {
|
|
588
|
+
s.destroy();
|
|
589
|
+
r(true);
|
|
590
|
+
});
|
|
591
|
+
s.on("error", () => r(false));
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
function hasPgIsready() {
|
|
595
|
+
try {
|
|
596
|
+
const containers = spawnSync2("podman", ["ps", "--filter", "publish=5433", "--format", "{{.Names}}"], { timeout: 3e3 });
|
|
597
|
+
const name = containers.stdout.toString().trim().split("\n")[0];
|
|
598
|
+
if (name && spawnSync2("podman", ["exec", name, "pg_isready", "--version"], { timeout: 2e3 }).status === 0) return true;
|
|
599
|
+
} catch {
|
|
600
|
+
}
|
|
601
|
+
return spawnSync2("which", ["pg_isready"], { timeout: 2e3 }).status === 0;
|
|
602
|
+
}
|
|
603
|
+
function testPostgresConnection(port, user, password, dbName) {
|
|
604
|
+
if (!hasPgIsready()) {
|
|
605
|
+
console.log(" \u26A0\uFE0F \u65E0\u6CD5\u9A8C\u8BC1\u51ED\u636E\uFF08\u5BB9\u5668\u672A\u8FD0\u884C\u4E14 pg_isready \u672A\u5B89\u88C5\uFF09\uFF0C\u4FE1\u4EFB\u8F93\u5165");
|
|
606
|
+
return true;
|
|
607
|
+
}
|
|
608
|
+
const containers = spawnSync2("podman", ["ps", "--filter", "publish=5433", "--format", "{{.Names}}"], { timeout: 3e3 });
|
|
609
|
+
const containerName = containers.stdout.toString().trim().split("\n")[0];
|
|
610
|
+
const args = containerName ? ["exec", containerName, "pg_isready", "-U", user, "-d", dbName] : ["-h", "localhost", "-p", port, "-U", user, "-d", dbName];
|
|
611
|
+
const cmd = containerName ? "podman" : "pg_isready";
|
|
612
|
+
const r = spawnSync2(cmd, args, {
|
|
613
|
+
timeout: 5e3,
|
|
614
|
+
env: containerName ? process.env : { ...process.env, PGPASSWORD: password }
|
|
615
|
+
});
|
|
616
|
+
return r.status === 0;
|
|
617
|
+
}
|
|
618
|
+
async function resolveDbCredentials(force) {
|
|
619
|
+
const d = { user: "admin", password: "change-me-in-production", port: "5433" };
|
|
620
|
+
if (force) return d;
|
|
621
|
+
console.log("", "\u6570\u636E\u5E93\u51ED\u636E\uFF08\u56DE\u8F66\u4F7F\u7528\u9884\u8BBE\u503C\uFF09\uFF1A");
|
|
622
|
+
let port = (await ask(`DATABASE_PORT [${d.port}]: `)).trim() || d.port;
|
|
623
|
+
while (true) {
|
|
624
|
+
const portNum = parseInt(port);
|
|
625
|
+
if (!isNaN(portNum) && await portInUse(portNum)) {
|
|
626
|
+
console.log(`
|
|
627
|
+
\u26A0\uFE0F \u7AEF\u53E3 ${port} \u5DF2\u88AB\u5360\u7528`);
|
|
628
|
+
const choice = (await ask(" [1] \u6362\u7AEF\u53E3 [2] \u8FDE\u63A5\u5DF2\u6709\u5B9E\u4F8B\uFF08\u8F93\u5165\u5176\u7528\u6237/\u5BC6\u7801\uFF09\u2192 ")).trim();
|
|
629
|
+
if (choice === "2") {
|
|
630
|
+
const existingUser = (await ask(` \u7528\u6237: `)).trim() || "admin";
|
|
631
|
+
const existingPass = (await ask(` \u5BC6\u7801: `)).trim() || "change-me-in-production";
|
|
632
|
+
const testDb = (await ask(` \u6D4B\u8BD5\u6570\u636E\u540D (\u9ED8\u8BA4 postgres): `)).trim() || "postgres";
|
|
633
|
+
const ok = testPostgresConnection(port, existingUser, existingPass, testDb);
|
|
634
|
+
if (ok) {
|
|
635
|
+
console.log(` \u2705 \u8FDE\u63A5\u6210\u529F`);
|
|
636
|
+
return { user: existingUser, password: existingPass, port, reuseExisting: true };
|
|
637
|
+
} else {
|
|
638
|
+
console.log(` \u274C \u8FDE\u63A5\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u51ED\u636E`);
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
} else {
|
|
642
|
+
break;
|
|
643
|
+
}
|
|
644
|
+
port = (await ask(`DATABASE_PORT: `)).trim() || d.port;
|
|
645
|
+
}
|
|
646
|
+
return { user: (await ask(`DATABASE_USER [${d.user}]: `)).trim() || d.user, password: (await ask(`DATABASE_PASSWORD [${d.password}]: `)).trim() || d.password, port };
|
|
647
|
+
}
|
|
648
|
+
function composeContent(projectName) {
|
|
649
|
+
return `services:
|
|
650
|
+
postgres:
|
|
651
|
+
image: docker.io/postgres:16-alpine
|
|
652
|
+
container_name: \${PROJECT_NAME:-${projectName}}-postgres
|
|
653
|
+
restart: unless-stopped
|
|
654
|
+
ports:
|
|
655
|
+
- "127.0.0.1:\${DATABASE_PORT:-5433}:5432"
|
|
656
|
+
volumes:
|
|
657
|
+
- ./data/postgres/\${PROJECT_NAME:-${projectName}}:/var/lib/postgresql/data
|
|
658
|
+
env_file:
|
|
659
|
+
- .env.development
|
|
660
|
+
environment:
|
|
661
|
+
POSTGRES_USER: \${DATABASE_USER:-admin}
|
|
662
|
+
POSTGRES_PASSWORD: \${DATABASE_PASSWORD:-change-me-in-production}
|
|
663
|
+
POSTGRES_DB: \${PROJECT_NAME:-${projectName}}
|
|
664
|
+
TZ: "Asia/Shanghai"
|
|
665
|
+
networks:
|
|
666
|
+
- \${PROJECT_NAME:-${projectName}}-network
|
|
667
|
+
healthcheck:
|
|
668
|
+
test: ["CMD-SHELL", "pg_isready -U \${DATABASE_USER:-admin} -d \${PROJECT_NAME:-${projectName}}"]
|
|
669
|
+
interval: 10s
|
|
670
|
+
timeout: 5s
|
|
671
|
+
retries: 5
|
|
672
|
+
|
|
673
|
+
networks:
|
|
674
|
+
\${PROJECT_NAME:-${projectName}}-network:
|
|
675
|
+
driver: bridge
|
|
676
|
+
`;
|
|
677
|
+
}
|
|
678
|
+
function writeSqliteExportScript(projectRoot, dryRun) {
|
|
679
|
+
const scriptsDir = resolve4(projectRoot, "scripts");
|
|
680
|
+
const scriptPath = resolve4(scriptsDir, "export-db.ts");
|
|
681
|
+
const content = `import { PrismaClient } from "@prisma/client";
|
|
682
|
+
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
683
|
+
import { resolve } from "path";
|
|
684
|
+
|
|
685
|
+
const prisma = new PrismaClient();
|
|
686
|
+
const EXPORTS_DIR = resolve(process.cwd(), "data/exports");
|
|
687
|
+
|
|
688
|
+
async function main() {
|
|
689
|
+
if (!existsSync(EXPORTS_DIR)) mkdirSync(EXPORTS_DIR, { recursive: true });
|
|
690
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
691
|
+
const auditLogs = await prisma.auditLog.findMany({ orderBy: { createdAt: "desc" } });
|
|
692
|
+
const devOps = await prisma.devOperation.findMany({ orderBy: { createdAt: "desc" } });
|
|
693
|
+
writeFileSync(resolve(EXPORTS_DIR, \`audit-export-\${ts}.json\`), JSON.stringify({ exportedAt: new Date().toISOString(), auditLogs: { count: auditLogs.length, rows: auditLogs }, devOperations: { count: devOps.length, rows: devOps } }, null, 2), "utf-8");
|
|
694
|
+
console.log(\`\u5DF2\u5BFC\u51FA \${auditLogs.length} AuditLog + \${devOps.length} DevOperation\`);
|
|
695
|
+
await prisma.\\$disconnect();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
main().catch((e) => { console.error(e); process.exit(1); });
|
|
699
|
+
`;
|
|
700
|
+
if (dryRun) {
|
|
701
|
+
console.log(`[dry-run] \u5C06\u5199\u5165 ${scriptPath}`);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
if (!existsSync6(scriptsDir)) mkdirSync3(scriptsDir, { recursive: true });
|
|
705
|
+
writeFileSync3(scriptPath, content, "utf-8");
|
|
706
|
+
console.log("\u5DF2\u751F\u6210 scripts/export-db.ts");
|
|
707
|
+
}
|
|
708
|
+
function injectDbExportScript(projectRoot, dryRun) {
|
|
709
|
+
const pkgPath = resolve4(projectRoot, "package.json");
|
|
710
|
+
if (!existsSync6(pkgPath)) return;
|
|
711
|
+
if (dryRun) {
|
|
712
|
+
console.log("[dry-run] \u5C06\u6CE8\u5165 db:export");
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
716
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
717
|
+
if (!pkg.scripts["db:export"]) {
|
|
718
|
+
pkg.scripts["db:export"] = "npx tsx scripts/export-db.ts";
|
|
719
|
+
writeFileSync3(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
720
|
+
console.log("\u5DF2\u5728 package.json \u6CE8\u5165 db:export");
|
|
721
|
+
}
|
|
722
|
+
}
|
|
477
723
|
async function initCommand(options) {
|
|
478
724
|
const projectRoot = process.cwd();
|
|
479
|
-
const
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
const config = await loadConfig(projectRoot, options.config, { yes: options.yes, force: options.force });
|
|
725
|
+
const target = await resolveAdapter(projectRoot, options.adapter);
|
|
726
|
+
const magicDir = MAGIC_DIR_MAP[target];
|
|
727
|
+
const config = await loadConfig(projectRoot, options.config, { force: options.force });
|
|
483
728
|
config.projectRoot = projectRoot;
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
729
|
+
config.magicDir = magicDir;
|
|
730
|
+
const db = await resolveDbEngine(!!options.force);
|
|
731
|
+
if (db.engine === "postgresql") {
|
|
732
|
+
db.container = await resolveContainer(!!options.force);
|
|
733
|
+
if (db.container && db.container !== "manual") {
|
|
734
|
+
Object.assign(db, await resolveDbCredentials(!!options.force));
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (db.engine === "postgresql" && db.container && db.container !== "manual") {
|
|
738
|
+
if (!db.reuseExisting) {
|
|
739
|
+
const composeName = db.container === "podman" ? "podman-compose.yml" : "docker-compose.yml";
|
|
740
|
+
const composePath = resolve4(projectRoot, composeName);
|
|
741
|
+
if (!options.dryRun && (!existsSync6(composePath) || options.force)) {
|
|
742
|
+
writeFileSync3(composePath, composeContent(config.projectName || "add-project"), "utf-8");
|
|
743
|
+
console.log(`\u5DF2\u521B\u5EFA ${composeName}`);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
const devEnvPath = resolve4(projectRoot, ".env.development");
|
|
747
|
+
if (!options.dryRun && existsSync6(devEnvPath)) {
|
|
748
|
+
const existing = readFileSync7(devEnvPath, "utf-8");
|
|
749
|
+
if (!/^DATABASE_USER=/m.test(existing)) {
|
|
750
|
+
writeFileSync3(devEnvPath, existing + `
|
|
751
|
+
DATABASE_USER=${db.user || "admin"}
|
|
752
|
+
DATABASE_PASSWORD=${db.password || "change-me-in-production"}
|
|
753
|
+
DATABASE_PORT=${db.port || "5433"}
|
|
754
|
+
PROJECT_NAME=${config.projectName || "add-project"}
|
|
755
|
+
`, "utf-8");
|
|
756
|
+
console.log("\u5DF2\u5C06\u51ED\u636E\u8FFD\u52A0\u5230 .env.development");
|
|
757
|
+
}
|
|
758
|
+
}
|
|
490
759
|
}
|
|
491
|
-
const coreFiles = renderCore(
|
|
760
|
+
const coreFiles = renderCore(config, !!options.dryRun);
|
|
492
761
|
console.log(`Core \u6A21\u677F: ${coreFiles.size} \u6587\u4EF6`);
|
|
493
|
-
const CORE_TARGETS = [".add", ".qoder", ".claude"];
|
|
762
|
+
const CORE_TARGETS = [".add", ".qoder", ".claude", ".vscode"];
|
|
494
763
|
const allFiles = /* @__PURE__ */ new Map();
|
|
495
764
|
for (const [relPath, content] of coreFiles) {
|
|
496
|
-
for (const
|
|
497
|
-
const targetPath = relPath.replace(/^\.add/,
|
|
498
|
-
if (!allFiles.has(targetPath))
|
|
499
|
-
allFiles.set(targetPath, content);
|
|
500
|
-
}
|
|
765
|
+
for (const t of CORE_TARGETS) {
|
|
766
|
+
const targetPath = relPath.replace(/^\.add/, t);
|
|
767
|
+
if (!allFiles.has(targetPath)) allFiles.set(targetPath, content);
|
|
501
768
|
}
|
|
502
769
|
}
|
|
503
|
-
const
|
|
504
|
-
for (const adapter of adapters) {
|
|
770
|
+
for (const adapter of resolveAdapters2(target)) {
|
|
505
771
|
const renderFn = ADAPTER_RENDERERS[adapter];
|
|
506
772
|
if (renderFn) {
|
|
507
773
|
const adapterFiles = renderFn(config, projectRoot, !!options.dryRun);
|
|
508
|
-
for (const [
|
|
509
|
-
allFiles.set(path, content);
|
|
510
|
-
}
|
|
774
|
+
for (const [p, c] of adapterFiles) allFiles.set(p, c);
|
|
511
775
|
console.log(`${adapter} adapter: ${adapterFiles.size} \u6587\u4EF6`);
|
|
512
776
|
}
|
|
513
777
|
}
|
|
514
|
-
const result = await writeFiles2(projectRoot, allFiles, {
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
778
|
+
const result = await writeFiles2(projectRoot, allFiles, { force: options.force, dryRun: options.dryRun });
|
|
779
|
+
if (db.engine === "postgresql" && db.container && db.container !== "manual") {
|
|
780
|
+
if (!options.dryRun) {
|
|
781
|
+
const dbScript = resolve4(projectRoot, ".qoder", "scripts", "db-ensure.sh");
|
|
782
|
+
const dbEnv = { ...process.env, DATABASE_USER: db.user, DATABASE_PASSWORD: db.password, DATABASE_PORT: db.port, PROJECT_NAME: config.projectName };
|
|
783
|
+
const mode = db.reuseExisting ? "manual" : db.container;
|
|
784
|
+
console.log(db.reuseExisting ? "\u590D\u7528\u5DF2\u6709 PostgreSQL ..." : `\u90E8\u7F72\u6570\u636E\u5E93 (${db.container}) ...`);
|
|
785
|
+
spawnSync2("bash", [dbScript, "postgresql", mode, "--migrate"], { cwd: projectRoot, stdio: "inherit", env: dbEnv });
|
|
786
|
+
try {
|
|
787
|
+
await injectPrisma2(projectRoot, { force: !!options.force });
|
|
788
|
+
} catch (e) {
|
|
789
|
+
console.log(`Prisma \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
if (db.engine === "postgresql" && db.container === "manual") {
|
|
794
|
+
if (!options.dryRun) {
|
|
795
|
+
const dbScript = resolve4(projectRoot, ".qoder", "scripts", "db-ensure.sh");
|
|
796
|
+
if (existsSync6(dbScript)) spawnSync2("bash", [dbScript, "postgresql", "manual"], { cwd: projectRoot, stdio: "inherit" });
|
|
797
|
+
console.log(["", "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501", "ADD \u6A21\u677F\u5DF2\u5C31\u4F4D\u3002\u5728\u5B8C\u6210\u4EE5\u4E0B\u64CD\u4F5C\u524D MCP \u4E0D\u53EF\u7528\uFF1A", "", "1. \u7F16\u8F91 .env.development\uFF0C\u914D\u7F6E DATABASE_URL", "2. \u91CD\u65B0\u8FD0\u884C add-coder init \u5B8C\u6210\u8FC1\u79FB", "", "\u26A0\uFE0F \u975E PG \u6570\u636E\u5E93\u9700\u7F16\u8F91 .qoder/scripts/mcp-server.ts \u624B\u52A8\u914D Prisma 7 adapter", "\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501"].join("\n"));
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
if (db.engine === "manual") {
|
|
801
|
+
if (!options.dryRun) {
|
|
802
|
+
console.log(["", "Prisma \u652F\u6301\u7684 datasource: postgresql / mysql / sqlite / sqlserver / cockroachdb", "\u81EA\u884C prisma init + \u7F16\u8F91 .env.development\uFF0C\u91CD\u65B0 run init \u5B8C\u6210\u8FC1\u79FB\u3002", "", "\u26A0\uFE0F Prisma 7 adapter \u9700\u624B\u52A8\u914D \u2192 .qoder/scripts/mcp-server.ts"].join("\n"));
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
if (db.engine === "sqlite") {
|
|
806
|
+
writeSqliteExportScript(projectRoot, !!options.dryRun);
|
|
807
|
+
injectDbExportScript(projectRoot, !!options.dryRun);
|
|
808
|
+
if (!options.dryRun) {
|
|
809
|
+
try {
|
|
810
|
+
await injectPrisma2(projectRoot, { force: !!options.force, datasource: "sqlite" });
|
|
811
|
+
} catch (e) {
|
|
812
|
+
console.log(`SQLite \u540C\u6B65\u5931\u8D25: ${e instanceof Error ? e.message : String(e)}`);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
519
816
|
console.log(`
|
|
520
817
|
\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}, \u8986\u76D6 ${result.overwritten}`);
|
|
521
818
|
if (!options.dryRun) {
|
|
522
|
-
console.log("\
|
|
819
|
+
if (db.engine === "sqlite") console.log("\u6570\u636E\u5907\u4EFD: npm run db:export \u2192 data/exports/");
|
|
820
|
+
const pkg = JSON.parse(readFileSync7(resolve4(import.meta.dirname, "../package.json"), "utf-8"));
|
|
821
|
+
const peerNames = Object.keys(pkg.peerDependencies || {});
|
|
822
|
+
if (peerNames.length > 0) {
|
|
823
|
+
console.log(`
|
|
824
|
+
\u5B89\u88C5 peer \u4F9D\u8D56 (${peerNames.join(" ")}) ...`);
|
|
825
|
+
const pm = detectPm(projectRoot);
|
|
826
|
+
spawnSync2(pm, pm === "pnpm" ? ["add", ...peerNames] : ["install", ...peerNames], { cwd: projectRoot, stdio: "inherit" });
|
|
827
|
+
}
|
|
828
|
+
if (db.engine !== "manual" && (db.engine !== "postgresql" || db.container !== "manual")) {
|
|
829
|
+
console.log("\u63D0\u793A: \u91CD\u542F IDE \u4EE5\u52A0\u8F7D hook \u914D\u7F6E");
|
|
830
|
+
}
|
|
523
831
|
}
|
|
524
832
|
}
|
|
525
833
|
|
|
526
834
|
// src/cli/commands/sync.ts
|
|
527
|
-
import { existsSync as
|
|
528
|
-
import { resolve as
|
|
835
|
+
import { existsSync as existsSync7 } from "fs";
|
|
836
|
+
import { resolve as resolve5 } from "path";
|
|
529
837
|
async function syncCommand() {
|
|
530
838
|
const projectRoot = process.cwd();
|
|
531
839
|
const config = await loadConfig(projectRoot);
|
|
@@ -533,7 +841,7 @@ async function syncCommand() {
|
|
|
533
841
|
const coreFiles = renderCore(config, false);
|
|
534
842
|
const missing = /* @__PURE__ */ new Map();
|
|
535
843
|
for (const [relPath, content] of coreFiles) {
|
|
536
|
-
if (!
|
|
844
|
+
if (!existsSync7(resolve5(projectRoot, relPath))) {
|
|
537
845
|
missing.set(relPath, content);
|
|
538
846
|
}
|
|
539
847
|
}
|
|
@@ -541,13 +849,13 @@ async function syncCommand() {
|
|
|
541
849
|
console.log("\u6240\u6709 ADD \u6A21\u677F\u6587\u4EF6\u5DF2\u5C31\u4F4D\u3002");
|
|
542
850
|
return;
|
|
543
851
|
}
|
|
544
|
-
const result = await writeFiles2(projectRoot, missing, {
|
|
852
|
+
const result = await writeFiles2(projectRoot, missing, {});
|
|
545
853
|
console.log(`\u540C\u6B65\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}`);
|
|
546
854
|
}
|
|
547
855
|
|
|
548
856
|
// src/cli/commands/status.ts
|
|
549
|
-
import { existsSync as
|
|
550
|
-
import { resolve as
|
|
857
|
+
import { existsSync as existsSync8 } from "fs";
|
|
858
|
+
import { resolve as resolve6 } from "path";
|
|
551
859
|
async function statusCommand() {
|
|
552
860
|
const projectRoot = process.cwd();
|
|
553
861
|
const config = await loadConfig(projectRoot);
|
|
@@ -556,29 +864,29 @@ async function statusCommand() {
|
|
|
556
864
|
const missing = [];
|
|
557
865
|
const present = [];
|
|
558
866
|
for (const [relPath] of coreFiles) {
|
|
559
|
-
if (
|
|
867
|
+
if (existsSync8(resolve6(projectRoot, relPath))) {
|
|
560
868
|
present.push(relPath);
|
|
561
869
|
} else {
|
|
562
870
|
missing.push(relPath);
|
|
563
871
|
}
|
|
564
872
|
}
|
|
565
|
-
console.log(
|
|
873
|
+
console.log("ADD \u6A21\u677F\u5B8C\u6574\u6027\u68C0\u67E5:");
|
|
566
874
|
console.log(` \u5DF2\u5C31\u4F4D: ${present.length} \u6587\u4EF6`);
|
|
567
875
|
if (missing.length > 0) {
|
|
568
876
|
console.log(` \u7F3A\u5931: ${missing.length} \u6587\u4EF6`);
|
|
569
877
|
missing.forEach((f) => console.log(` - ${f}`));
|
|
570
878
|
} else {
|
|
571
|
-
console.log(
|
|
879
|
+
console.log(" \u6240\u6709\u6587\u4EF6\u5B8C\u6574\u3002");
|
|
572
880
|
}
|
|
573
881
|
}
|
|
574
882
|
|
|
575
883
|
// src/cli/index.ts
|
|
576
884
|
var { version } = JSON.parse(
|
|
577
|
-
|
|
885
|
+
readFileSync8(new URL("../package.json", import.meta.url), "utf-8")
|
|
578
886
|
);
|
|
579
887
|
var program = new Command();
|
|
580
888
|
program.name("add-coder").description("\u521D\u59CB\u5316 ADD \u8303\u5F0F\u5DE5\u4F5C\u6D41\u6A21\u677F").version(version);
|
|
581
|
-
program.command("init").description("\u521D\u59CB\u5316 ADD \u6A21\u677F\u5230\u5F53\u524D\u9879\u76EE").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode
|
|
889
|
+
program.command("init").description("\u521D\u59CB\u5316 ADD \u6A21\u677F\u5230\u5F53\u524D\u9879\u76EE").option("--adapter <type>", "\u76EE\u6807 IDE: claude | qoder | vscode").option("--config <path>", "\u6307\u5B9A\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84").option("--force", "\u8986\u76D6\u5DF2\u6709\u6587\u4EF6\uFF0C\u4E0D\u4EA4\u4E92").option("--dry-run", "\u9884\u89C8\u6A21\u5F0F\uFF0C\u4E0D\u5B9E\u9645\u5199\u5165").action(initCommand);
|
|
582
890
|
program.command("sync").description("\u589E\u91CF\u540C\u6B65\u7F3A\u5931\u6587\u4EF6").action(syncCommand);
|
|
583
891
|
program.command("status").description("\u68C0\u67E5 ADD \u6A21\u677F\u5B8C\u6574\u6027").action(statusCommand);
|
|
584
892
|
program.parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "add-coder",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "[codein2027](https://github.com/xiaomingming92/codein2027) 快速构建 ADD 编程范式的完整脚手架——AI 代码治理的落地方案。以「审计即基础设施」为核心,彻底打破编程过程黑盒与跨轮失忆,让编程范式进化为可审计、可追溯、可收敛的新时代。npx 即用,人人可体验。",
|
|
6
6
|
"type": "module",
|
|
@@ -39,8 +39,14 @@
|
|
|
39
39
|
"zod": "^3.24.0"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"@
|
|
43
|
-
"prisma": "^
|
|
42
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
43
|
+
"@prisma/adapter-libsql": "^7.0.0",
|
|
44
|
+
"@prisma/adapter-pg": "^7.0.0",
|
|
45
|
+
"@prisma/client": "^7.0.0",
|
|
46
|
+
"dotenv": "*",
|
|
47
|
+
"prisma": "^7.0.0",
|
|
48
|
+
"tsx": "*",
|
|
49
|
+
"zod": "^3.24.0"
|
|
44
50
|
},
|
|
45
51
|
"peerDependenciesMeta": {
|
|
46
52
|
"@prisma/client": {
|
|
@@ -48,6 +54,24 @@
|
|
|
48
54
|
},
|
|
49
55
|
"prisma": {
|
|
50
56
|
"optional": false
|
|
57
|
+
},
|
|
58
|
+
"@modelcontextprotocol/sdk": {
|
|
59
|
+
"optional": false
|
|
60
|
+
},
|
|
61
|
+
"zod": {
|
|
62
|
+
"optional": false
|
|
63
|
+
},
|
|
64
|
+
"dotenv": {
|
|
65
|
+
"optional": false
|
|
66
|
+
},
|
|
67
|
+
"tsx": {
|
|
68
|
+
"optional": false
|
|
69
|
+
},
|
|
70
|
+
"@prisma/adapter-pg": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"@prisma/adapter-libsql": {
|
|
74
|
+
"optional": true
|
|
51
75
|
}
|
|
52
76
|
},
|
|
53
77
|
"devDependencies": {
|
|
@@ -56,14 +80,17 @@
|
|
|
56
80
|
"eslint": "^9.39.4",
|
|
57
81
|
"tsup": "^8.4.0",
|
|
58
82
|
"typescript": "^6.0.3",
|
|
59
|
-
"typescript-eslint": "^8.62.1"
|
|
83
|
+
"typescript-eslint": "^8.62.1",
|
|
84
|
+
"vitest": "^4.1.10"
|
|
60
85
|
},
|
|
61
86
|
"scripts": {
|
|
62
87
|
"build": "tsup",
|
|
88
|
+
"predev": "npm run db:ensure",
|
|
63
89
|
"dev": "tsup --watch",
|
|
90
|
+
"db:ensure": "bash scripts/db-ensure.sh",
|
|
64
91
|
"lint": "eslint src/",
|
|
65
92
|
"lint:fix": "eslint src/ --fix",
|
|
66
93
|
"generate": "tsx src/caijuehub/transcribe.ts",
|
|
67
|
-
"test": "
|
|
94
|
+
"test": "vitest run"
|
|
68
95
|
}
|
|
69
96
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
services:
|
|
2
|
+
postgres:
|
|
3
|
+
image: docker.io/postgres:16-alpine
|
|
4
|
+
container_name: {{projectName}}-postgres
|
|
5
|
+
restart: unless-stopped
|
|
6
|
+
ports:
|
|
7
|
+
- "127.0.0.1:5433:5432"
|
|
8
|
+
volumes:
|
|
9
|
+
- ./data/postgres:/var/lib/postgresql/data
|
|
10
|
+
environment:
|
|
11
|
+
POSTGRES_DB: {{projectName}}
|
|
12
|
+
POSTGRES_USER: admin
|
|
13
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
|
14
|
+
TZ: "Asia/Shanghai"
|
|
15
|
+
healthcheck:
|
|
16
|
+
test: ["CMD-SHELL", "pg_isready -U admin -d {{projectName}}"]
|
|
17
|
+
interval: 10s
|
|
18
|
+
timeout: 5s
|
|
19
|
+
retries: 5
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
// add.prisma — ADD
|
|
2
|
-
|
|
1
|
+
// add.prisma — ADD 治理模型,独立于用户业务表
|
|
2
|
+
|
|
3
|
+
model AddUser {
|
|
4
|
+
id String @id @default(cuid())
|
|
5
|
+
username String @unique
|
|
6
|
+
email String?
|
|
7
|
+
devOperations DevOperation[]
|
|
8
|
+
auditLogs AuditLog[]
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
model DevOperation {
|
|
5
12
|
id String @id @default(cuid())
|
|
6
13
|
userId String
|
|
7
|
-
user
|
|
14
|
+
user AddUser @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
8
15
|
planKeyword String
|
|
9
16
|
action String
|
|
10
17
|
targetType String
|
|
@@ -20,7 +27,7 @@ model DevOperation {
|
|
|
20
27
|
model AuditLog {
|
|
21
28
|
id String @id @default(cuid())
|
|
22
29
|
userId String
|
|
23
|
-
user
|
|
30
|
+
user AddUser @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
24
31
|
action String
|
|
25
32
|
targetType String
|
|
26
33
|
targetId String
|
|
@@ -31,4 +38,4 @@ model AuditLog {
|
|
|
31
38
|
createdAt DateTime @default(now())
|
|
32
39
|
|
|
33
40
|
@@index([traceId])
|
|
34
|
-
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# db-ensure.sh — 容器启动 + 环境准备
|
|
3
|
+
# prisma init/copy/push/generate 由 init.ts → injectPrisma() 裁决层处理
|
|
4
|
+
# 用法: bash db-ensure.sh <engine> <container> [--migrate]
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
ENGINE="${1:-postgresql}"
|
|
8
|
+
CONTAINER="${2:-none}"
|
|
9
|
+
DO_MIGRATE="false"
|
|
10
|
+
[[ "${3:-}" == "--migrate" ]] && DO_MIGRATE="true"
|
|
11
|
+
|
|
12
|
+
PROJECT_DIR="${PROJECT_DIR:-$(pwd)}"
|
|
13
|
+
PROJECT_NAME="${PROJECT_NAME:-$(basename "$PROJECT_DIR")}"
|
|
14
|
+
DB_USER="${DATABASE_USER:-admin}"
|
|
15
|
+
DB_PASS="${DATABASE_PASSWORD:-change-me-in-production}"
|
|
16
|
+
DB_PORT="${DATABASE_PORT:-5433}"
|
|
17
|
+
DB_URL="postgresql://${DB_USER}:${DB_PASS}@localhost:${DB_PORT}/${PROJECT_NAME}?schema=public"
|
|
18
|
+
|
|
19
|
+
# ADD 表备份
|
|
20
|
+
backup_add_tables() {
|
|
21
|
+
if ! command -v pg_dump > /dev/null 2>&1; then return; fi
|
|
22
|
+
local bak="add-backup-$(date +%Y%m%d_%H%M%S).sql"
|
|
23
|
+
echo ">>> 备份 ADD 表到 $bak ..."
|
|
24
|
+
PGPASSWORD="$DB_PASS" pg_dump -h localhost -p "$DB_PORT" -U "$DB_USER" -d "$PROJECT_NAME" \
|
|
25
|
+
--table=AddUser --table=DevOperation --table=AuditLog --if-exists > "$bak" 2>/dev/null || true
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
# 0. 确保 .env.development 存在
|
|
29
|
+
if [ ! -f "$PROJECT_DIR/.env.development" ]; then
|
|
30
|
+
cat > "$PROJECT_DIR/.env.development" <<EOF
|
|
31
|
+
DATABASE_URL="${DB_URL}"
|
|
32
|
+
DATABASE_USER=${DB_USER}
|
|
33
|
+
DATABASE_PASSWORD=${DB_PASS}
|
|
34
|
+
DATABASE_PORT=${DB_PORT}
|
|
35
|
+
PROJECT_NAME=${PROJECT_NAME}
|
|
36
|
+
EOF
|
|
37
|
+
echo ">>> 已创建 .env.development"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# ── SQLite:无需容器 ──
|
|
41
|
+
if [ "$ENGINE" = "sqlite" ]; then exit 0; fi
|
|
42
|
+
|
|
43
|
+
# ── 自行管理 PostgreSQL ──
|
|
44
|
+
if [ "$CONTAINER" = "none" ] || [ "$CONTAINER" = "manual" ]; then
|
|
45
|
+
echo ">>> 自行管理 PostgreSQL,跳过容器 ..."
|
|
46
|
+
if [ "$DO_MIGRATE" = "true" ]; then
|
|
47
|
+
backup_add_tables
|
|
48
|
+
fi
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# ── 容器模式 ──
|
|
53
|
+
COMPOSE_CMD=""
|
|
54
|
+
if [ "$CONTAINER" = "podman" ]; then COMPOSE_CMD="podman-compose"
|
|
55
|
+
elif [ "$CONTAINER" = "docker" ]; then COMPOSE_CMD="docker-compose"
|
|
56
|
+
else echo "未知容器: $CONTAINER"; exit 1
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
echo ">>> 启动 PostgreSQL ($COMPOSE_CMD up -d) ..."
|
|
60
|
+
$COMPOSE_CMD up -d || {
|
|
61
|
+
echo "容器启动失败,请检查 $COMPOSE_CMD 是否已安装或端口是否冲突"
|
|
62
|
+
exit 1
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
# 等待 PostgreSQL 就绪
|
|
66
|
+
echo ">>> 等待 PostgreSQL 就绪 ..."
|
|
67
|
+
MAX_RETRIES=30
|
|
68
|
+
RETRY=0
|
|
69
|
+
while [ $RETRY -lt $MAX_RETRIES ]; do
|
|
70
|
+
if $COMPOSE_CMD exec -T postgres pg_isready -U "$DB_USER" > /dev/null 2>&1; then
|
|
71
|
+
echo "PostgreSQL 已就绪"; break
|
|
72
|
+
fi
|
|
73
|
+
sleep 1
|
|
74
|
+
RETRY=$((RETRY + 1))
|
|
75
|
+
done
|
|
76
|
+
if [ $RETRY -ge $MAX_RETRIES ]; then
|
|
77
|
+
echo "PostgreSQL 启动超时,请检查: $COMPOSE_CMD logs postgres"
|
|
78
|
+
exit 1
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
if [ "$DO_MIGRATE" = "true" ]; then
|
|
82
|
+
backup_add_tables
|
|
83
|
+
fi
|
|
@@ -11,7 +11,7 @@ const MAGIC_DIR = basename(dirname(__dirname)) // ".qoder" or ".claude"
|
|
|
11
11
|
const ENV_CANDIDATES = [".env.development.local", ".env.development", ".env.local", ".env"];
|
|
12
12
|
for (const f of ENV_CANDIDATES) {
|
|
13
13
|
const p = resolve(PROJECT_ROOT, f);
|
|
14
|
-
if (existsSync(p)) { dotenv.config({ path: p }); break; }
|
|
14
|
+
if (existsSync(p)) { dotenv.config({ path: p, override: true }); break; }
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
const DATABASE_URL = process.env.DATABASE_URL
|
|
@@ -26,10 +26,25 @@ import { readFile, readdir, stat, mkdir, writeFile } from "fs/promises"
|
|
|
26
26
|
import { join, relative } from "path"
|
|
27
27
|
import { existsSync } from "fs"
|
|
28
28
|
import { spawnSync } from "child_process"
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
const { PrismaClient } = await import("@prisma/client").catch(() =>
|
|
31
|
+
import("../../generated/prisma/client.js").catch(() =>
|
|
32
|
+
import("../../src/generated/prisma/client.js"))
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
// Prisma 7 按 DATABASE_URL 前缀自动选 adapter
|
|
36
|
+
let adapter = undefined
|
|
37
|
+
if (DATABASE_URL.startsWith("postgresql://") || DATABASE_URL.startsWith("postgres://")) {
|
|
38
|
+
const { PrismaPg } = await import("@prisma/adapter-pg")
|
|
39
|
+
adapter = new PrismaPg({ connectionString: DATABASE_URL })
|
|
40
|
+
} else if (DATABASE_URL.startsWith("file:") || DATABASE_URL.startsWith("sqlite:")) {
|
|
41
|
+
const { PrismaLibSQL } = await import("@prisma/adapter-libsql")
|
|
42
|
+
const url = DATABASE_URL.replace(/^file:/, "")
|
|
43
|
+
adapter = new PrismaLibSQL({ url })
|
|
44
|
+
}
|
|
30
45
|
|
|
31
46
|
const prisma = new PrismaClient({
|
|
32
|
-
|
|
47
|
+
...(adapter ? { adapter } : {}),
|
|
33
48
|
log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"],
|
|
34
49
|
})
|
|
35
50
|
|
|
@@ -1108,11 +1123,11 @@ server.registerTool(
|
|
|
1108
1123
|
}
|
|
1109
1124
|
}
|
|
1110
1125
|
|
|
1111
|
-
let parsedBefore:
|
|
1112
|
-
let parsedAfter:
|
|
1126
|
+
let parsedBefore: any | undefined
|
|
1127
|
+
let parsedAfter: any | undefined
|
|
1113
1128
|
try {
|
|
1114
|
-
if (beforeState) parsedBefore = JSON.parse(beforeState)
|
|
1115
|
-
if (afterState) parsedAfter = JSON.parse(afterState)
|
|
1129
|
+
if (beforeState) parsedBefore = JSON.parse(beforeState)
|
|
1130
|
+
if (afterState) parsedAfter = JSON.parse(afterState)
|
|
1116
1131
|
} catch {
|
|
1117
1132
|
const beforePreview = beforeState ? beforeState.slice(0, 80) : "(未传)"
|
|
1118
1133
|
const afterPreview = afterState ? afterState.slice(0, 80) : "(未传)"
|
|
@@ -1123,18 +1138,17 @@ server.registerTool(
|
|
|
1123
1138
|
)
|
|
1124
1139
|
}
|
|
1125
1140
|
|
|
1126
|
-
let systemUser = await prisma.
|
|
1141
|
+
let systemUser = await prisma.addUser.findUnique({
|
|
1127
1142
|
where: { username: "ai-assistant" },
|
|
1128
1143
|
select: { id: true },
|
|
1129
1144
|
})
|
|
1130
1145
|
|
|
1131
1146
|
if (!systemUser) {
|
|
1132
|
-
systemUser = await prisma.
|
|
1147
|
+
systemUser = await prisma.addUser.create({
|
|
1133
1148
|
data: {
|
|
1134
1149
|
id: "ai-assistant",
|
|
1135
1150
|
username: "ai-assistant",
|
|
1136
|
-
email: "ai-assistant@internal"
|
|
1137
|
-
password: "internal",
|
|
1151
|
+
email: "ai-assistant@internal"
|
|
1138
1152
|
},
|
|
1139
1153
|
select: { id: true },
|
|
1140
1154
|
})
|
|
@@ -1147,8 +1161,8 @@ server.registerTool(
|
|
|
1147
1161
|
action,
|
|
1148
1162
|
targetType,
|
|
1149
1163
|
targetId: targetId || "unknown",
|
|
1150
|
-
beforeState: parsedBefore ??
|
|
1151
|
-
afterState: parsedAfter ??
|
|
1164
|
+
beforeState: parsedBefore ?? null,
|
|
1165
|
+
afterState: parsedAfter ?? null,
|
|
1152
1166
|
reason: reason || null,
|
|
1153
1167
|
},
|
|
1154
1168
|
})
|