add-coder 0.3.23 → 0.3.24
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 +58 -5
- package/dist/index.js +674 -168
- package/package.json +2 -1
- package/templates/.add-coder-src-hash.json +3 -2
- package/templates/core/scripts/db-ensure.sh +95 -0
- package/templates/core/templates/ports.example.md +49 -0
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 readFileSync12 } from "fs";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/caijuehub/strategies/detect.strategy.ts
|
|
@@ -184,7 +184,7 @@ async function selectFiles(projectRoot, files) {
|
|
|
184
184
|
process.stdout.write(renderList(items, selected, scrollIdx, VISIBLE));
|
|
185
185
|
}
|
|
186
186
|
draw();
|
|
187
|
-
return new Promise((
|
|
187
|
+
return new Promise((resolve12) => {
|
|
188
188
|
input.on("keypress", (_char, key) => {
|
|
189
189
|
if (!key) return;
|
|
190
190
|
if (key.name === "up") {
|
|
@@ -225,7 +225,7 @@ async function selectFiles(projectRoot, files) {
|
|
|
225
225
|
if (selected.has(idx)) result.set(relPath, content);
|
|
226
226
|
idx++;
|
|
227
227
|
}
|
|
228
|
-
|
|
228
|
+
resolve12(result);
|
|
229
229
|
});
|
|
230
230
|
});
|
|
231
231
|
}
|
|
@@ -645,12 +645,12 @@ function detectPm(projectRoot) {
|
|
|
645
645
|
}
|
|
646
646
|
|
|
647
647
|
// src/cli/prisma-injector.ts
|
|
648
|
-
import { resolve as
|
|
648
|
+
import { resolve as resolve6, dirname as dirname4 } from "path";
|
|
649
649
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
650
650
|
|
|
651
651
|
// src/caijuehub/strategies/prisma.strategy.ts
|
|
652
|
-
import { copyFileSync, existsSync as
|
|
653
|
-
import { resolve as
|
|
652
|
+
import { copyFileSync, existsSync as existsSync9, readFileSync as readFileSync7, unlinkSync, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
|
|
653
|
+
import { resolve as resolve5, join as join6, dirname as dirname3 } from "path";
|
|
654
654
|
|
|
655
655
|
// src/lib/run-command.ts
|
|
656
656
|
import { spawnSync } from "child_process";
|
|
@@ -680,6 +680,205 @@ function commandExists(cmd, platform) {
|
|
|
680
680
|
return !r.error && r.status === 0;
|
|
681
681
|
}
|
|
682
682
|
|
|
683
|
+
// src/lib/db-backup.ts
|
|
684
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3, readdirSync as readdirSync2, rmSync } from "fs";
|
|
685
|
+
import { join as join5, resolve as resolve3 } from "path";
|
|
686
|
+
var ADD_TABLES = ["AddUser", "DevOperation", "AuditLog", "HitlRecord", "PlanRecord", "ReviewRecord", "CollabContract"];
|
|
687
|
+
function ensureGitignoreRule(projectRoot) {
|
|
688
|
+
const gitignorePath = resolve3(projectRoot, ".gitignore");
|
|
689
|
+
if (!existsSync7(gitignorePath)) return;
|
|
690
|
+
const content = readFileSync5(gitignorePath, "utf-8");
|
|
691
|
+
if (content.includes(".add/backups/")) return;
|
|
692
|
+
writeFileSync3(gitignorePath, `${content}${content.endsWith("\n") ? "" : "\n"}
|
|
693
|
+
# add-coder \u6570\u636E\u5E93\u540C\u6B65\u5907\u4EFD
|
|
694
|
+
.add/backups/
|
|
695
|
+
`, "utf-8");
|
|
696
|
+
console.log("\u{1F4CB} \u5DF2\u6CE8\u5165\u5BBF\u4E3B .gitignore: .add/backups/");
|
|
697
|
+
}
|
|
698
|
+
async function backupBeforeSync(projectRoot, opts) {
|
|
699
|
+
const dir = resolve3(projectRoot, opts.backupDir);
|
|
700
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
701
|
+
const bakDir = join5(dir, ts);
|
|
702
|
+
ensureGitignoreRule(projectRoot);
|
|
703
|
+
if (opts.datasource === "sqlite" || opts.dbUrl.startsWith("file:")) {
|
|
704
|
+
const dbFile = opts.dbUrl.replace(/^file:/, "");
|
|
705
|
+
if (!existsSync7(dbFile)) return handleBackupFailure(opts, `sqlite \u6587\u4EF6\u4E0D\u5B58\u5728: ${dbFile}`);
|
|
706
|
+
mkdirSync3(bakDir, { recursive: true });
|
|
707
|
+
const copy = join5(bakDir, "dev.db");
|
|
708
|
+
writeFileSync3(copy, readFileSync5(dbFile), "utf-8");
|
|
709
|
+
writeFileSync3(join5(bakDir, "manifest.json"), JSON.stringify({ createdAt: ts, dbUrl: opts.dbUrl, files: ["dev.db"] }, null, 2), "utf-8");
|
|
710
|
+
console.log(`\u2705 \u5907\u4EFD\u5B8C\u6210: ${bakDir}`);
|
|
711
|
+
pruneBackups(dir, opts.backupKeep);
|
|
712
|
+
return bakDir;
|
|
713
|
+
}
|
|
714
|
+
if (!commandExists("pg_dump")) {
|
|
715
|
+
return handleBackupFailure(opts, "pg_dump \u672A\u5B89\u88C5\uFF08postgresql-client \u7F3A\u5931\uFF09");
|
|
716
|
+
}
|
|
717
|
+
mkdirSync3(bakDir, { recursive: true });
|
|
718
|
+
const schemaFile = join5(bakDir, "schema.sql");
|
|
719
|
+
const tablesFile = join5(bakDir, "add-tables.sql");
|
|
720
|
+
const r1 = runCommand("pg_dump", ["--schema-only", "--no-owner", `--dbname=${opts.dbUrl}`], { timeout: 6e4 });
|
|
721
|
+
if (r1.status !== 0) {
|
|
722
|
+
return handleBackupFailure(opts, `pg_dump schema \u5931\u8D25: ${r1.stderr.trim().split("\n").slice(0, 3).join(" | ")}`);
|
|
723
|
+
}
|
|
724
|
+
writeFileSync3(schemaFile, r1.stdout, "utf-8");
|
|
725
|
+
const r2 = runCommand("pg_dump", ["--no-owner", "--if-exists", `--dbname=${opts.dbUrl}`, ...ADD_TABLES.map((t) => `--table=${t}`)], { timeout: 6e4 });
|
|
726
|
+
if (r2.status !== 0) {
|
|
727
|
+
return handleBackupFailure(opts, `pg_dump ADD \u8868\u5931\u8D25: ${r2.stderr.trim().split("\n").slice(0, 3).join(" | ")}`);
|
|
728
|
+
}
|
|
729
|
+
writeFileSync3(tablesFile, r2.stdout, "utf-8");
|
|
730
|
+
writeFileSync3(join5(bakDir, "manifest.json"), JSON.stringify({ createdAt: ts, dbUrl: opts.dbUrl, files: ["schema.sql", "add-tables.sql"] }, null, 2), "utf-8");
|
|
731
|
+
console.log(`\u2705 \u5907\u4EFD\u5B8C\u6210: ${bakDir}`);
|
|
732
|
+
pruneBackups(dir, opts.backupKeep);
|
|
733
|
+
return bakDir;
|
|
734
|
+
}
|
|
735
|
+
async function handleBackupFailure(opts, reason) {
|
|
736
|
+
if (opts.yes) {
|
|
737
|
+
throw new Error(`\u26D4 \u5907\u4EFD\u5931\u8D25\uFF08--yes \u786C\u963B\u65AD\uFF0C\u540C\u6B65\u4E2D\u6B62\uFF09: ${reason}`);
|
|
738
|
+
}
|
|
739
|
+
const a = await ask(`
|
|
740
|
+
\u26A0\uFE0F \u5907\u4EFD\u5931\u8D25\uFF08${reason}\uFF09\u3002\u81EA\u62C5\u98CE\u9669\u7EE7\u7EED\u540C\u6B65\uFF1F[y/N] `);
|
|
741
|
+
if (a !== "y" && a !== "yes") {
|
|
742
|
+
throw new Error(`\u5907\u4EFD\u5931\u8D25\uFF0C\u5DF2\u4E2D\u6B62\u540C\u6B65: ${reason}`);
|
|
743
|
+
}
|
|
744
|
+
console.warn("\u26A0\uFE0F \u7528\u6237\u63A5\u53D7\u81EA\u62C5\u98CE\u9669\u7EE7\u7EED\uFF08manifest \u8BB0 riskAccepted\uFF09");
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
function pruneBackups(dir, backupKeep) {
|
|
748
|
+
if (!existsSync7(dir)) return;
|
|
749
|
+
const entries = readdirSync2(dir).filter((n) => /^\d{4}-\d{2}-\d{2}T/.test(n)).sort();
|
|
750
|
+
const excess = entries.length - backupKeep;
|
|
751
|
+
for (let i = 0; i < excess; i++) {
|
|
752
|
+
rmSync(join5(dir, entries[i]), { recursive: true, force: true });
|
|
753
|
+
console.log(`\u{1F9F9} \u6E05\u7406\u65E7\u5907\u4EFD: ${entries[i]}`);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// src/lib/ports-contract.ts
|
|
758
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, readdirSync as readdirSync3 } from "fs";
|
|
759
|
+
import { resolve as resolve4, basename } from "path";
|
|
760
|
+
|
|
761
|
+
// src/caijuehub/strategies/ports.strategy.ts
|
|
762
|
+
var PORTS_CONFIG = {
|
|
763
|
+
pg: {
|
|
764
|
+
startHint: 5433,
|
|
765
|
+
scanLimit: 100
|
|
766
|
+
},
|
|
767
|
+
behavior: {
|
|
768
|
+
reuseRegistered: true,
|
|
769
|
+
readCrossProject: true,
|
|
770
|
+
onConflict: "ask"
|
|
771
|
+
}
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
// src/lib/ports-contract.ts
|
|
775
|
+
import { createConnection } from "net";
|
|
776
|
+
var PORTS_EXAMPLE_REL = "../templates/core/templates/ports.example.md";
|
|
777
|
+
function ensurePortsContract(projectRoot, config, dryRun = false) {
|
|
778
|
+
const target = resolve4(projectRoot, "docs", "ports.md");
|
|
779
|
+
if (existsSync8(target)) return;
|
|
780
|
+
let content;
|
|
781
|
+
try {
|
|
782
|
+
content = readFileSync6(resolve4(import.meta.dirname, PORTS_EXAMPLE_REL), "utf-8");
|
|
783
|
+
} catch {
|
|
784
|
+
console.warn(`\u26A0\uFE0F \u7AEF\u53E3\u5951\u7EA6\u6A21\u677F\u7F3A\u5931\uFF08\u8DF3\u8FC7\u751F\u6210 docs/ports.md\uFF09: ${PORTS_EXAMPLE_REL}`);
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const pn = config.projectName || "add-project";
|
|
788
|
+
const rendered = render(content, { ...config, projectName: pn });
|
|
789
|
+
if (dryRun) {
|
|
790
|
+
console.log("[dry-run] \u5C06\u751F\u6210 docs/ports.md");
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
mkdirSync4(resolve4(projectRoot, "docs"), { recursive: true });
|
|
794
|
+
writeFileSync4(target, rendered, "utf-8");
|
|
795
|
+
console.log("\u2705 \u5DF2\u751F\u6210\u7AEF\u53E3\u5951\u7EA6 docs/ports.md\uFF08\u8BF7\u6309\u9879\u76EE\u5B9E\u9645\u767B\u8BB0\u7AEF\u53E3\uFF0C\u793A\u4F8B\u72B6\u6001\u5217\u52FF\u76F4\u63A5\u63D0\u4EA4\uFF09");
|
|
796
|
+
}
|
|
797
|
+
function portInUse(port) {
|
|
798
|
+
return new Promise((r) => {
|
|
799
|
+
const s = createConnection({ port, host: "127.0.0.1" }, () => {
|
|
800
|
+
s.destroy();
|
|
801
|
+
r(true);
|
|
802
|
+
});
|
|
803
|
+
s.on("error", () => r(false));
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
function parseContractPorts(content) {
|
|
807
|
+
const out = [];
|
|
808
|
+
const re = /^\|\s*(\d{2,5})\s*\|/gm;
|
|
809
|
+
let m;
|
|
810
|
+
while ((m = re.exec(content)) !== null) out.push(parseInt(m[1], 10));
|
|
811
|
+
return out;
|
|
812
|
+
}
|
|
813
|
+
function readRegisteredPorts(projectRoot) {
|
|
814
|
+
const p = resolve4(projectRoot, "docs", "ports.md");
|
|
815
|
+
if (!existsSync8(p)) return /* @__PURE__ */ new Set();
|
|
816
|
+
return new Set(parseContractPorts(readFileSync6(p, "utf-8")));
|
|
817
|
+
}
|
|
818
|
+
function readCrossProjectPorts(projectRoot) {
|
|
819
|
+
const ports = /* @__PURE__ */ new Set();
|
|
820
|
+
const parent = resolve4(projectRoot, "..");
|
|
821
|
+
let names = [];
|
|
822
|
+
try {
|
|
823
|
+
names = readdirSync3(parent, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name !== basename(projectRoot)).map((d) => d.name);
|
|
824
|
+
} catch {
|
|
825
|
+
return ports;
|
|
826
|
+
}
|
|
827
|
+
for (const name of names) {
|
|
828
|
+
const p = resolve4(parent, name, "docs", "ports.md");
|
|
829
|
+
if (existsSync8(p)) {
|
|
830
|
+
parseContractPorts(readFileSync6(p, "utf-8")).forEach((x) => ports.add(x));
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
return ports;
|
|
834
|
+
}
|
|
835
|
+
function scanPodmanPorts() {
|
|
836
|
+
const ports = /* @__PURE__ */ new Set();
|
|
837
|
+
if (!commandExists("podman")) return ports;
|
|
838
|
+
try {
|
|
839
|
+
const r = runCommand("podman", ["ps", "--format", "{{.Ports}}"]);
|
|
840
|
+
const re = /(\d{1,5})->\d{1,5}\/tcp/g;
|
|
841
|
+
let m;
|
|
842
|
+
while ((m = re.exec(r.stdout)) !== null) ports.add(parseInt(m[1], 10));
|
|
843
|
+
} catch {
|
|
844
|
+
}
|
|
845
|
+
return ports;
|
|
846
|
+
}
|
|
847
|
+
function registerPort(projectRoot, port, svc) {
|
|
848
|
+
const contract = resolve4(projectRoot, "docs", "ports.md");
|
|
849
|
+
if (!existsSync8(contract)) return;
|
|
850
|
+
const env = svc.envKey ? ` \`${svc.envKey}\`` : "";
|
|
851
|
+
const line = `| ${port} | PostgreSQL\uFF08${svc.containerName}\uFF09 | ${svc.name}\uFF08add-coder \u81EA\u52A8\u767B\u8BB0\uFF09 | \u{1F7E2} \u4F7F\u7528\u4E2D | .env.development${env} |
|
|
852
|
+
`;
|
|
853
|
+
writeFileSync4(contract, readFileSync6(contract, "utf-8") + line, "utf-8");
|
|
854
|
+
console.log(`\u{1F4CB} \u7AEF\u53E3\u5951\u7EA6\u767B\u8BB0: ${port}\uFF08${svc.containerName}\uFF09\u2192 docs/ports.md`);
|
|
855
|
+
}
|
|
856
|
+
async function allocatePortsWithContract(projectRoot, services) {
|
|
857
|
+
const cfg = PORTS_CONFIG;
|
|
858
|
+
const used = /* @__PURE__ */ new Set();
|
|
859
|
+
readRegisteredPorts(projectRoot).forEach((p) => used.add(p));
|
|
860
|
+
if (cfg.behavior.readCrossProject) readCrossProjectPorts(projectRoot).forEach((p) => used.add(p));
|
|
861
|
+
scanPodmanPorts().forEach((p) => used.add(p));
|
|
862
|
+
const result = {};
|
|
863
|
+
for (const svc of services) {
|
|
864
|
+
let port = 0;
|
|
865
|
+
for (let p = cfg.pg.startHint; p < cfg.pg.startHint + cfg.pg.scanLimit; p++) {
|
|
866
|
+
if (used.has(p)) continue;
|
|
867
|
+
if (await portInUse(p)) {
|
|
868
|
+
used.add(p);
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
port = p;
|
|
872
|
+
break;
|
|
873
|
+
}
|
|
874
|
+
if (!port) throw new Error(`\u7AEF\u53E3\u5951\u7EA6\u5206\u914D\u5931\u8D25: ${cfg.pg.startHint}-${cfg.pg.startHint + cfg.pg.scanLimit} \u65E0\u7A7A\u95F2\u7AEF\u53E3\uFF08${svc.name}\uFF09`);
|
|
875
|
+
used.add(port);
|
|
876
|
+
result[svc.name] = port;
|
|
877
|
+
registerPort(projectRoot, port, svc);
|
|
878
|
+
}
|
|
879
|
+
return result;
|
|
880
|
+
}
|
|
881
|
+
|
|
683
882
|
// src/caijuehub/strategies/prisma.strategy.ts
|
|
684
883
|
var PRISMA_CONFIG = {
|
|
685
884
|
onMissing: "ask",
|
|
@@ -688,11 +887,295 @@ var PRISMA_CONFIG = {
|
|
|
688
887
|
autoGenerate: true,
|
|
689
888
|
migrationName: "add_workflow_init",
|
|
690
889
|
schemaArg: "--schema=prisma/",
|
|
691
|
-
requiresUserModel: true
|
|
890
|
+
requiresUserModel: true,
|
|
891
|
+
sync: {
|
|
892
|
+
strategy: "atlas",
|
|
893
|
+
addDatabaseUrl: "",
|
|
894
|
+
atlasDevUrl: "",
|
|
895
|
+
backupDir: ".add/backups/prisma-sync",
|
|
896
|
+
backupKeep: 5,
|
|
897
|
+
backupRequiredForPush: true
|
|
898
|
+
}
|
|
692
899
|
};
|
|
900
|
+
var ADD_TABLES2 = ["AddUser", "DevOperation", "AuditLog", "HitlRecord", "PlanRecord", "ReviewRecord", "CollabContract"];
|
|
901
|
+
function readEnvValue(projectRoot, key) {
|
|
902
|
+
const envPath = resolve5(projectRoot, ".env.development");
|
|
903
|
+
if (!existsSync9(envPath)) return "";
|
|
904
|
+
const m = readFileSync7(envPath, "utf-8").match(new RegExp(`^${key}=(.*)$`, "m"));
|
|
905
|
+
return m?.[1]?.trim() || "";
|
|
906
|
+
}
|
|
907
|
+
function appendEnvValue(projectRoot, key, value) {
|
|
908
|
+
const envPath = resolve5(projectRoot, ".env.development");
|
|
909
|
+
const existing = existsSync9(envPath) ? readFileSync7(envPath, "utf-8") : "";
|
|
910
|
+
if (new RegExp(`^${key}=`, "m").test(existing)) return;
|
|
911
|
+
writeFileSync5(envPath, `${existing}${existing.endsWith("\n") ? "" : "\n"}${key}=${value}
|
|
912
|
+
`, "utf-8");
|
|
913
|
+
}
|
|
914
|
+
async function ensureSplitDb(projectRoot, opts) {
|
|
915
|
+
const existing = readEnvValue(projectRoot, "ADD_DATABASE_URL");
|
|
916
|
+
if (existing) {
|
|
917
|
+
console.log("\u5206\u5E93\u6A21\u5F0F\uFF1A\u68C0\u6D4B\u5230 ADD_DATABASE_URL\uFF0CADD \u6CBB\u7406\u6A21\u578B\u5165\u72EC\u7ACB\u5E93");
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
920
|
+
if (opts.dryRun) {
|
|
921
|
+
console.log("[dry-run] \u5C06\u8BE2\u95EE\u662F\u5426\u5206\u5E93\uFF08\u72EC\u7ACB ADD \u6570\u636E\u5E93\uFF0C\u63A8\u8350\uFF09");
|
|
922
|
+
return false;
|
|
923
|
+
}
|
|
924
|
+
let choice = "y";
|
|
925
|
+
if (!opts.force && !opts.yes) {
|
|
926
|
+
const a = await ask("\u662F\u5426\u5C06 ADD \u6CBB\u7406\u6A21\u578B\u653E\u5165\u72EC\u7ACB\u6570\u636E\u5E93\uFF08\u63A8\u8350\uFF0C\u9694\u79BB\u4E1A\u52A1\u5E93\uFF09\uFF1F[Y/n] ");
|
|
927
|
+
if (a === "n" || a === "no") choice = "n";
|
|
928
|
+
}
|
|
929
|
+
if (choice === "n") {
|
|
930
|
+
console.log("\u5171\u5E93\u6A21\u5F0F\uFF1AADD \u6CBB\u7406\u6A21\u578B\u8FDB\u5165\u5BBF\u4E3B\u5E93\uFF08diff \u975E ADD \u8868\u53D8\u66F4\u9ED8\u8BA4\u62D2\u7EDD\uFF09");
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
const projectName = readEnvValue(projectRoot, "PROJECT_NAME") || "add-project";
|
|
934
|
+
const dbUser = readEnvValue(projectRoot, "DATABASE_USER") || "admin";
|
|
935
|
+
const dbPass = readEnvValue(projectRoot, "DATABASE_PASSWORD") || "change-me-in-production";
|
|
936
|
+
const dbName = `${projectName}-add`;
|
|
937
|
+
const container = `${projectName}-add-postgres`;
|
|
938
|
+
const port = (await allocatePortsWithContract(projectRoot, [{ name: "add", containerName: `${projectName}-add-postgres`, envKey: "ADD_DATABASE_URL" }])).add;
|
|
939
|
+
console.log(`\u542F\u52A8\u72EC\u7ACB ADD \u5E93\u5BB9\u5668 ${container}\uFF08\u7AEF\u53E3 ${port}\uFF09...`);
|
|
940
|
+
const r = runCommand("podman", [
|
|
941
|
+
"run",
|
|
942
|
+
"-d",
|
|
943
|
+
"--name",
|
|
944
|
+
container,
|
|
945
|
+
"--restart",
|
|
946
|
+
"unless-stopped",
|
|
947
|
+
"-e",
|
|
948
|
+
`POSTGRES_USER=${dbUser}`,
|
|
949
|
+
"-e",
|
|
950
|
+
`POSTGRES_PASSWORD=${dbPass}`,
|
|
951
|
+
"-e",
|
|
952
|
+
`POSTGRES_DB=${dbName}`,
|
|
953
|
+
"-p",
|
|
954
|
+
`127.0.0.1:${port}:5432`,
|
|
955
|
+
"docker.io/postgres:16-alpine"
|
|
956
|
+
], { timeout: 6e4 });
|
|
957
|
+
if (r.status !== 0) {
|
|
958
|
+
throw new Error(`\u72EC\u7ACB ADD \u5E93\u5BB9\u5668\u542F\u52A8\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${r.status}\uFF09: ${r.stderr.trim().slice(0, 200)}`);
|
|
959
|
+
}
|
|
960
|
+
appendEnvValue(projectRoot, "ADD_DATABASE_URL", `postgresql://${dbUser}:${dbPass}@127.0.0.1:${port}/${dbName}?schema=public`);
|
|
961
|
+
const addPrismaPath = resolve5(projectRoot, "prisma", "add.prisma");
|
|
962
|
+
if (existsSync9(addPrismaPath)) {
|
|
963
|
+
const content = readFileSync7(addPrismaPath, "utf-8");
|
|
964
|
+
if (!content.includes("datasource db")) {
|
|
965
|
+
writeFileSync5(addPrismaPath, `datasource db {
|
|
966
|
+
provider = "postgresql"
|
|
967
|
+
url = env("ADD_DATABASE_URL")
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
${content}`, "utf-8");
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
console.log(`\u2705 \u5DF2\u521B\u5EFA\u72EC\u7ACB ADD \u5E93 ${container}\uFF08\u7AEF\u53E3 ${port}\uFF09\uFF0CADD_DATABASE_URL \u5DF2\u5199\u5165 .env.development`);
|
|
974
|
+
return true;
|
|
975
|
+
}
|
|
976
|
+
function prismaDiffArgs(pm, schemaTo, fromUrl) {
|
|
977
|
+
const base = pm === "pnpm" ? ["dlx", "prisma", "migrate", "diff"] : ["exec", "prisma", "--", "migrate", "diff"];
|
|
978
|
+
const toFlag = "--to-schema";
|
|
979
|
+
const args = [...base, "--from-empty", toFlag, schemaTo, "--script"];
|
|
980
|
+
if (fromUrl) args.splice(args.indexOf("--from-empty"), 1, "--from-url", fromUrl);
|
|
981
|
+
return args;
|
|
982
|
+
}
|
|
983
|
+
async function provisionDevUrl(projectRoot) {
|
|
984
|
+
const existing = readEnvValue(projectRoot, "ATLAS_DEV_URL");
|
|
985
|
+
if (existing) return existing;
|
|
986
|
+
if (!commandExists("podman")) return null;
|
|
987
|
+
const projectName = readEnvValue(projectRoot, "PROJECT_NAME") || "add-project";
|
|
988
|
+
const container = `${projectName}-add-dev`;
|
|
989
|
+
try {
|
|
990
|
+
const ps = runCommand("podman", ["ps", "--filter", `name=^/${container}$`, "--format", "{{.Names}}"]);
|
|
991
|
+
if (ps.stdout.trim()) {
|
|
992
|
+
const portR = runCommand("podman", ["port", container, "5432/tcp"]);
|
|
993
|
+
const pm = portR.stdout.match(/127\.0\.0\.1:(\d{2,5})/);
|
|
994
|
+
if (pm) {
|
|
995
|
+
const url2 = `postgresql://postgres:postgres@127.0.0.1:${pm[1]}/dev?schema=public`;
|
|
996
|
+
appendEnvValue(projectRoot, "ATLAS_DEV_URL", url2);
|
|
997
|
+
return url2;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
} catch {
|
|
1001
|
+
}
|
|
1002
|
+
const ports = await allocatePortsWithContract(projectRoot, [{ name: "dev", containerName: container, envKey: "ATLAS_DEV_URL" }]);
|
|
1003
|
+
const port = ports.dev;
|
|
1004
|
+
const r = runCommand("podman", [
|
|
1005
|
+
"run",
|
|
1006
|
+
"-d",
|
|
1007
|
+
"--name",
|
|
1008
|
+
container,
|
|
1009
|
+
"--restart",
|
|
1010
|
+
"unless-stopped",
|
|
1011
|
+
"-e",
|
|
1012
|
+
"POSTGRES_USER=postgres",
|
|
1013
|
+
"-e",
|
|
1014
|
+
"POSTGRES_PASSWORD=postgres",
|
|
1015
|
+
"-e",
|
|
1016
|
+
"POSTGRES_DB=dev",
|
|
1017
|
+
"-p",
|
|
1018
|
+
`127.0.0.1:${port}:5432`,
|
|
1019
|
+
"docker.io/postgres:16-alpine"
|
|
1020
|
+
], { timeout: 6e4 });
|
|
1021
|
+
if (r.status !== 0) return null;
|
|
1022
|
+
const url = `postgresql://postgres:postgres@127.0.0.1:${port}/dev?schema=public`;
|
|
1023
|
+
appendEnvValue(projectRoot, "ATLAS_DEV_URL", url);
|
|
1024
|
+
console.log(`\u2705 \u5E38\u9A7B dev \u7A7A\u5E93\u5DF2\u521B\u5EFA: ${container}\uFF08\u7AEF\u53E3 ${port}\uFF0C\u4E0D\u9500\u6BC1\uFF0C\u53EF\u968F\u65F6\u91CD\u7F6E\uFF09`);
|
|
1025
|
+
return url;
|
|
1026
|
+
}
|
|
1027
|
+
function resolveAtlasBin(projectRoot) {
|
|
1028
|
+
const pkgBin = resolve5(projectRoot, "node_modules", "add-coder", "node_modules", ".bin", "atlas");
|
|
1029
|
+
if (existsSync9(pkgBin)) return pkgBin;
|
|
1030
|
+
const local = resolve5(projectRoot, "node_modules", ".bin", "atlas");
|
|
1031
|
+
if (existsSync9(local)) return local;
|
|
1032
|
+
return commandExists("atlas") ? "atlas" : null;
|
|
1033
|
+
}
|
|
1034
|
+
function hasNonAddTableChanges(diffSql) {
|
|
1035
|
+
const re = /(?:DROP|ALTER|CREATE)\s+TABLE(?:\s+IF\s+EXISTS)?\s+"?([a-zA-Z_][a-zA-Z0-9_]*)"?/gi;
|
|
1036
|
+
let m;
|
|
1037
|
+
while ((m = re.exec(diffSql)) !== null) {
|
|
1038
|
+
if (!ADD_TABLES2.includes(m[1])) return true;
|
|
1039
|
+
}
|
|
1040
|
+
return false;
|
|
1041
|
+
}
|
|
1042
|
+
function buildExcludeArgs(projectRoot, splitDb) {
|
|
1043
|
+
if (splitDb) return [];
|
|
1044
|
+
const projectName = readEnvValue(projectRoot, "PROJECT_NAME") || "add-project";
|
|
1045
|
+
const dbUser = readEnvValue(projectRoot, "DATABASE_USER") || "admin";
|
|
1046
|
+
try {
|
|
1047
|
+
const r = runCommand("podman", [
|
|
1048
|
+
"exec",
|
|
1049
|
+
`${projectName}-postgres`,
|
|
1050
|
+
"psql",
|
|
1051
|
+
"-U",
|
|
1052
|
+
dbUser,
|
|
1053
|
+
"-d",
|
|
1054
|
+
projectName,
|
|
1055
|
+
"-tAc",
|
|
1056
|
+
"SELECT string_agg('public.' || table_name, ',') FROM information_schema.tables WHERE table_schema='public' AND table_name NOT IN ('AddUser','DevOperation','AuditLog','HitlRecord','PlanRecord','ReviewRecord','CollabContract');"
|
|
1057
|
+
], { timeout: 3e4 });
|
|
1058
|
+
const tables = r.stdout.trim();
|
|
1059
|
+
return tables ? ["--exclude", tables] : [];
|
|
1060
|
+
} catch {
|
|
1061
|
+
return [];
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
async function runAtlasSync(projectRoot, targetUrl, splitDb, sync, opts) {
|
|
1065
|
+
const pm = detectPm(projectRoot);
|
|
1066
|
+
const schemaTo = splitDb ? resolve5(projectRoot, "prisma", "add.prisma") : resolve5(projectRoot, "prisma");
|
|
1067
|
+
const baselinePath = join6(projectRoot, PRISMA_CONFIG.sync.backupDir, "baseline.sql");
|
|
1068
|
+
mkdirSync5(dirname3(baselinePath), { recursive: true });
|
|
1069
|
+
const diffR = runCommand(pm, prismaDiffArgs(pm, schemaTo, null), { cwd: projectRoot, timeout: 6e4 });
|
|
1070
|
+
if (diffR.status !== 0) {
|
|
1071
|
+
console.warn(`\u26A0\uFE0F baseline \u751F\u6210\u5931\u8D25\uFF08${diffR.stderr.trim().slice(0, 150)}\uFF09\uFF0C\u964D\u7EA7 prisma-diff`);
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
writeFileSync5(baselinePath, diffR.stdout, "utf-8");
|
|
1075
|
+
const atlasBin = resolveAtlasBin(projectRoot);
|
|
1076
|
+
if (!atlasBin) {
|
|
1077
|
+
console.warn("\u26A0\uFE0F atlas \u4E0D\u53EF\u7528\uFF08\u4F9D\u8D56\u672A\u5B89\u88C5\u6216\u5168\u5C40\u7F3A\u5931\uFF09");
|
|
1078
|
+
return false;
|
|
1079
|
+
}
|
|
1080
|
+
let devUrl = await provisionDevUrl(projectRoot);
|
|
1081
|
+
if (!devUrl && sync.atlasDevUrl) devUrl = sync.atlasDevUrl;
|
|
1082
|
+
if (!devUrl) {
|
|
1083
|
+
console.warn("\u26A0\uFE0F dev-url \u4E0D\u53EF\u7528\uFF08\u65E0 podman \u4E14\u672A\u914D\u7F6E atlas_dev_url\uFF09\uFF0C\u964D\u7EA7 prisma-diff");
|
|
1084
|
+
return false;
|
|
1085
|
+
}
|
|
1086
|
+
{
|
|
1087
|
+
const diffArgs = ["schema", "diff", "--from", targetUrl, "--to", `file://${baselinePath}`, "--dev-url", devUrl, ...buildExcludeArgs(projectRoot, splitDb)];
|
|
1088
|
+
const diffSqlR = runCommand(atlasBin, diffArgs, { cwd: projectRoot, timeout: 6e4 });
|
|
1089
|
+
if (diffSqlR.status !== 0) {
|
|
1090
|
+
console.warn(`\u26A0\uFE0F atlas diff \u5931\u8D25\uFF08${diffSqlR.stderr.trim().slice(0, 150)}\uFF09\uFF0C\u964D\u7EA7 prisma-diff`);
|
|
1091
|
+
return false;
|
|
1092
|
+
}
|
|
1093
|
+
const diffSql = diffSqlR.stdout;
|
|
1094
|
+
const hasSql = /(?:^|\n)\s*(?:CREATE|ALTER|DROP|COMMENT|--\s*(?:Create|Modify|Drop))\b/i.test(diffSql);
|
|
1095
|
+
if (!hasSql) {
|
|
1096
|
+
console.log("\u2705 \u6570\u636E\u5E93\u4E0E\u76EE\u6807 schema \u4E00\u81F4\uFF08\u5E42\u7B49\u51FA\u53E3\uFF09");
|
|
1097
|
+
return true;
|
|
1098
|
+
}
|
|
1099
|
+
if (!splitDb && hasNonAddTableChanges(diffSql)) {
|
|
1100
|
+
console.error("\u26D4 \u5171\u5E93\u6A21\u5F0F\u68C0\u6D4B\u5230\u975E ADD \u8868\u53D8\u66F4\uFF08\u9ED8\u8BA4\u62D2\u7EDD\uFF09\u3002\u8BF7\u4EBA\u5DE5\u5BA1\u6838 diff.sql \u6216\u9009\u62E9\u5206\u5E93\u6A21\u5F0F\u3002");
|
|
1101
|
+
console.error(diffSql.split("\n").slice(0, 30).join("\n"));
|
|
1102
|
+
throw new Error("\u5171\u5E93\u6A21\u5F0F\u975E ADD \u8868\u53D8\u66F4\u9ED8\u8BA4\u62D2\u7EDD\uFF08\u53EF\u7528 --yes \u6216\u5206\u5E93\u6A21\u5F0F\u7ED5\u8FC7\uFF09");
|
|
1103
|
+
}
|
|
1104
|
+
if (!opts.yes) {
|
|
1105
|
+
console.log("=== \u5F85\u5E94\u7528 diff SQL ===");
|
|
1106
|
+
console.log(diffSql.split("\n").slice(0, 60).join("\n"));
|
|
1107
|
+
const c = await ask("\u5E94\u7528\u4EE5\u4E0A schema \u53D8\u66F4\uFF1F[y/N] ");
|
|
1108
|
+
if (c !== "y" && c !== "yes") {
|
|
1109
|
+
console.log("\u5DF2\u53D6\u6D88\uFF0C\u672A\u5E94\u7528");
|
|
1110
|
+
return true;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
const applyArgs = ["schema", "apply", "--url", targetUrl, "--to", `file://${baselinePath}`, "--dev-url", devUrl, ...buildExcludeArgs(projectRoot, splitDb)];
|
|
1114
|
+
const applyR = runCommand(atlasBin, applyArgs, { cwd: projectRoot, timeout: 12e4 });
|
|
1115
|
+
if (applyR.status !== 0) {
|
|
1116
|
+
console.warn(`\u26A0\uFE0F atlas apply \u5931\u8D25\uFF08${applyR.stderr.trim().slice(0, 150)}\uFF09`);
|
|
1117
|
+
return false;
|
|
1118
|
+
}
|
|
1119
|
+
console.log("\u2705 Atlas schema \u540C\u6B65\u5B8C\u6210");
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
function runPrismaDiffSync(projectRoot, targetUrl, splitDb) {
|
|
1124
|
+
const pm = detectPm(projectRoot);
|
|
1125
|
+
const schemaTo = splitDb ? resolve5(projectRoot, "prisma", "add.prisma") : resolve5(projectRoot, "prisma");
|
|
1126
|
+
const diffPath = join6(projectRoot, PRISMA_CONFIG.sync.backupDir, "diff.sql");
|
|
1127
|
+
mkdirSync5(dirname3(diffPath), { recursive: true });
|
|
1128
|
+
const r = runCommand(pm, prismaDiffArgs(pm, schemaTo, targetUrl), { cwd: projectRoot, timeout: 6e4 });
|
|
1129
|
+
if (r.status !== 0) throw new Error(`prisma migrate diff \u5931\u8D25: ${r.stderr.trim().slice(0, 200)}`);
|
|
1130
|
+
writeFileSync5(diffPath, r.stdout, "utf-8");
|
|
1131
|
+
if (!r.stdout.trim()) {
|
|
1132
|
+
console.log("\u2705 \u6570\u636E\u5E93\u4E0E\u76EE\u6807 schema \u4E00\u81F4\uFF08\u5E42\u7B49\u51FA\u53E3\uFF09");
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
const execArgs = pm === "pnpm" ? ["dlx", "prisma", "db", "execute", "--file", diffPath] : ["exec", "prisma", "--", "db", "execute", "--file", diffPath];
|
|
1136
|
+
const e = runCommand(pm, execArgs, { cwd: projectRoot, timeout: 12e4 });
|
|
1137
|
+
if (e.status !== 0) throw new Error(`prisma db execute \u5931\u8D25: ${e.stderr.trim().slice(0, 200)}`);
|
|
1138
|
+
console.log("\u2705 prisma-diff \u540C\u6B65\u5B8C\u6210\uFF08\u514D shadow\uFF09");
|
|
1139
|
+
}
|
|
1140
|
+
function runDbPush(projectRoot) {
|
|
1141
|
+
const pm = detectPm(projectRoot);
|
|
1142
|
+
const args = pm === "pnpm" ? ["dlx", "prisma", "db", "push"] : ["exec", "prisma", "--", "db", "push"];
|
|
1143
|
+
if (PRISMA_CONFIG.schemaArg) args.push(PRISMA_CONFIG.schemaArg);
|
|
1144
|
+
console.log(`\u6267\u884C ${pm} ${args.join(" ")} ...`);
|
|
1145
|
+
const r = runCommand(pm, args, { cwd: projectRoot });
|
|
1146
|
+
if (r.status !== 0) throw new Error(`prisma db push \u9000\u51FA\u7801: ${r.status}`);
|
|
1147
|
+
}
|
|
1148
|
+
async function syncDatabase(projectRoot, opts, splitDb) {
|
|
1149
|
+
const sync = PRISMA_CONFIG.sync;
|
|
1150
|
+
const targetUrl = splitDb ? readEnvValue(projectRoot, "ADD_DATABASE_URL") || sync.addDatabaseUrl : readEnvValue(projectRoot, "DATABASE_URL");
|
|
1151
|
+
if (!targetUrl) {
|
|
1152
|
+
console.warn("\u26A0\uFE0F \u672A\u627E\u5230\u76EE\u6807\u5E93\u8FDE\u63A5\u4E32\uFF08\u5206\u5E93\u9700 ADD_DATABASE_URL / \u5171\u5E93\u9700 DATABASE_URL\uFF09\uFF0C\u8DF3\u8FC7\u540C\u6B65");
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
const bak = await backupBeforeSync(projectRoot, { dbUrl: targetUrl, backupDir: sync.backupDir, backupKeep: sync.backupKeep, yes: opts.yes });
|
|
1156
|
+
if (bak === null) {
|
|
1157
|
+
console.warn("\u26A0\uFE0F \u5907\u4EFD\u672A\u5B8C\u6210\uFF08\u7528\u6237\u81EA\u62C5\u98CE\u9669\uFF09\uFF0C\u7EE7\u7EED\u540C\u6B65");
|
|
1158
|
+
}
|
|
1159
|
+
if (sync.strategy === "db-push") {
|
|
1160
|
+
runDbPush(projectRoot);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
const atlasBin = resolveAtlasBin(projectRoot);
|
|
1164
|
+
if (atlasBin) {
|
|
1165
|
+
const ok = await runAtlasSync(projectRoot, targetUrl, splitDb, sync, opts);
|
|
1166
|
+
if (ok) return;
|
|
1167
|
+
} else {
|
|
1168
|
+
console.warn("\u26A0\uFE0F atlas \u4E0D\u53EF\u7528\u3002add-coder \u4F9D\u8D56\u81EA\u5E26\uFF1A\u9879\u76EE\u5B89\u88C5 @ariga/atlas\uFF08pnpm add -D @ariga/atlas\uFF09\uFF0C\u6216 npm \u5168\u5C40\u5B89\u88C5");
|
|
1169
|
+
if (!opts.yes) {
|
|
1170
|
+
const a = await ask("\u7EE7\u7EED\u964D\u7EA7 prisma-diff\uFF08\u514D shadow\uFF09\uFF1F[Y/n] ");
|
|
1171
|
+
if (a === "n" || a === "no") throw new Error("\u5DF2\u53D6\u6D88\u540C\u6B65\uFF08atlas \u7F3A\u5931\uFF09");
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
runPrismaDiffSync(projectRoot, targetUrl, splitDb);
|
|
1175
|
+
}
|
|
693
1176
|
function ensurePrismaConfig(projectRoot) {
|
|
694
|
-
const configPath =
|
|
695
|
-
|
|
1177
|
+
const configPath = resolve5(projectRoot, "prisma.config.ts");
|
|
1178
|
+
writeFileSync5(configPath, [
|
|
696
1179
|
'import dotenv from "dotenv";',
|
|
697
1180
|
'import { existsSync } from "fs";',
|
|
698
1181
|
'for (const f of [".env.development.local", ".env.development", ".env.local", ".env"]) {',
|
|
@@ -707,20 +1190,6 @@ function ensurePrismaConfig(projectRoot) {
|
|
|
707
1190
|
"});"
|
|
708
1191
|
].join("\n") + "\n", "utf-8");
|
|
709
1192
|
}
|
|
710
|
-
function backupAddTables(projectRoot) {
|
|
711
|
-
if (!commandExists("pg_dump")) return null;
|
|
712
|
-
const bak = resolve3(projectRoot, `add-backup-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19)}.sql`);
|
|
713
|
-
const r = runCommand("pg_dump", ["--table=AddUser", "--table=DevOperation", "--table=AuditLog", "--if-exists"], {
|
|
714
|
-
cwd: projectRoot,
|
|
715
|
-
timeout: 3e4
|
|
716
|
-
});
|
|
717
|
-
if (r.stdout.length > 0) {
|
|
718
|
-
writeFileSync3(bak, r.stdout, "utf-8");
|
|
719
|
-
console.log(`>>> \u5907\u4EFD ADD \u8868\u5230 ${bak}`);
|
|
720
|
-
return bak;
|
|
721
|
-
}
|
|
722
|
-
return null;
|
|
723
|
-
}
|
|
724
1193
|
function runPrismaInit(projectRoot, provider, schemaPath) {
|
|
725
1194
|
console.log("\u6267\u884C prisma init ...");
|
|
726
1195
|
const pm = detectPm(projectRoot);
|
|
@@ -732,10 +1201,10 @@ function runPrismaInit(projectRoot, provider, schemaPath) {
|
|
|
732
1201
|
console.error(`\u2717 prisma init \u65E0\u6CD5\u6267\u884C: ${e instanceof Error ? e.message : String(e)}`);
|
|
733
1202
|
initResult = { status: null, stdout: "", stderr: "" };
|
|
734
1203
|
}
|
|
735
|
-
if (initResult.status !== 0 || !
|
|
1204
|
+
if (initResult.status !== 0 || !existsSync9(schemaPath)) {
|
|
736
1205
|
console.error(`\u26A0\uFE0F prisma init \u672A\u5B8C\u6210\uFF08\u9000\u51FA\u7801: ${initResult.status}\uFF09\uFF0C\u56DE\u9000\u624B\u52A8\u521B\u5EFA schema.prisma\u2014\u2014db push \u5C06\u9A8C\u8BC1\u5176\u53EF\u7528\u6027`);
|
|
737
|
-
const prismaDir =
|
|
738
|
-
if (!
|
|
1206
|
+
const prismaDir = resolve5(projectRoot, "prisma");
|
|
1207
|
+
if (!existsSync9(prismaDir)) mkdirSync5(prismaDir, { recursive: true });
|
|
739
1208
|
const content = `generator client {
|
|
740
1209
|
provider = "prisma-client-js"
|
|
741
1210
|
}
|
|
@@ -744,11 +1213,11 @@ datasource db {
|
|
|
744
1213
|
provider = "${provider}"
|
|
745
1214
|
}
|
|
746
1215
|
`;
|
|
747
|
-
|
|
748
|
-
const devEnvPath =
|
|
749
|
-
if (!
|
|
1216
|
+
writeFileSync5(schemaPath, content, "utf-8");
|
|
1217
|
+
const devEnvPath = resolve5(projectRoot, ".env.development");
|
|
1218
|
+
if (!existsSync9(devEnvPath)) {
|
|
750
1219
|
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"';
|
|
751
|
-
|
|
1220
|
+
writeFileSync5(devEnvPath, defaultUrl + "\n", "utf-8");
|
|
752
1221
|
console.log("\u5DF2\u521B\u5EFA .env.development");
|
|
753
1222
|
}
|
|
754
1223
|
return false;
|
|
@@ -756,18 +1225,18 @@ datasource db {
|
|
|
756
1225
|
return true;
|
|
757
1226
|
}
|
|
758
1227
|
function postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath) {
|
|
759
|
-
const envPath =
|
|
760
|
-
const devEnvPath =
|
|
761
|
-
if (
|
|
762
|
-
const envContent =
|
|
1228
|
+
const envPath = resolve5(projectRoot, ".env");
|
|
1229
|
+
const devEnvPath = resolve5(projectRoot, ".env.development");
|
|
1230
|
+
if (existsSync9(envPath)) {
|
|
1231
|
+
const envContent = readFileSync7(envPath, "utf-8");
|
|
763
1232
|
const dbUrl = envContent.match(/DATABASE_URL=.*/);
|
|
764
1233
|
if (dbUrl) {
|
|
765
|
-
const existing =
|
|
1234
|
+
const existing = existsSync9(devEnvPath) ? readFileSync7(devEnvPath, "utf-8") : "";
|
|
766
1235
|
if (!existing.includes("DATABASE_URL=")) {
|
|
767
|
-
|
|
1236
|
+
writeFileSync5(devEnvPath, `${existing}${existing ? "\n" : ""}${dbUrl[0]}
|
|
768
1237
|
`, "utf-8");
|
|
769
1238
|
}
|
|
770
|
-
if (
|
|
1239
|
+
if (existsSync9(envPath)) unlinkSync(envPath);
|
|
771
1240
|
console.log("\u5DF2\u5C06 DATABASE_URL \u8FC1\u79FB\u5230 .env.development");
|
|
772
1241
|
}
|
|
773
1242
|
}
|
|
@@ -776,8 +1245,8 @@ function postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath) {
|
|
|
776
1245
|
patchGeneratorOutput(schemaPath);
|
|
777
1246
|
}
|
|
778
1247
|
function patchGeneratorOutput(schemaPath) {
|
|
779
|
-
if (!
|
|
780
|
-
let content =
|
|
1248
|
+
if (!existsSync9(schemaPath)) return;
|
|
1249
|
+
let content = readFileSync7(schemaPath, "utf-8");
|
|
781
1250
|
const genBlock = content.match(/generator\s+\w+\s*\{[\s\S]*?\}/);
|
|
782
1251
|
if (!genBlock) {
|
|
783
1252
|
content += `
|
|
@@ -786,23 +1255,23 @@ generator client {
|
|
|
786
1255
|
output = "../src/generated/prisma"
|
|
787
1256
|
}
|
|
788
1257
|
`;
|
|
789
|
-
|
|
1258
|
+
writeFileSync5(schemaPath, content, "utf-8");
|
|
790
1259
|
console.log("\u5DF2\u8FFD\u52A0 generator client\uFF08\u542B output \u2192 src/generated/prisma\uFF09");
|
|
791
1260
|
return;
|
|
792
1261
|
}
|
|
793
1262
|
if (genBlock[0].includes("output")) return;
|
|
794
1263
|
const patched = genBlock[0].replace(/\}\s*$/, ` output = "../src/generated/prisma"
|
|
795
1264
|
}`);
|
|
796
|
-
|
|
1265
|
+
writeFileSync5(schemaPath, content.replace(genBlock[0], patched), "utf-8");
|
|
797
1266
|
console.log("\u5DF2\u6CE8\u5165 generator output \u2192 src/generated/prisma");
|
|
798
1267
|
}
|
|
799
1268
|
async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
800
1269
|
const C = PRISMA_CONFIG;
|
|
801
|
-
const prismaDir =
|
|
802
|
-
const schemaPath =
|
|
803
|
-
const destPath =
|
|
1270
|
+
const prismaDir = resolve5(projectRoot, "prisma");
|
|
1271
|
+
const schemaPath = resolve5(prismaDir, "schema.prisma");
|
|
1272
|
+
const destPath = resolve5(prismaDir, "add.prisma");
|
|
804
1273
|
let justInited = false;
|
|
805
|
-
if (!
|
|
1274
|
+
if (!existsSync9(prismaDir) || !existsSync9(schemaPath)) {
|
|
806
1275
|
if (C.onMissing === "skip") {
|
|
807
1276
|
console.log("\u8DF3\u8FC7\uFF1A\u7F3A\u5C11 Prisma");
|
|
808
1277
|
return true;
|
|
@@ -821,7 +1290,7 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
|
821
1290
|
postInitSetup(projectRoot, schemaPath, addPrismaTemplate, destPath);
|
|
822
1291
|
justInited = true;
|
|
823
1292
|
}
|
|
824
|
-
if (
|
|
1293
|
+
if (existsSync9(destPath) && !justInited) {
|
|
825
1294
|
if (options.dryRun) {
|
|
826
1295
|
console.log("[dry-run] \u5DF2\u6709 add.prisma");
|
|
827
1296
|
return true;
|
|
@@ -838,8 +1307,8 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
|
838
1307
|
console.log("\u8986\u76D6");
|
|
839
1308
|
} else if (choice === "d") {
|
|
840
1309
|
copyFileSync(destPath, destPath + ".bak");
|
|
841
|
-
console.log("=== \u5F53\u524D\uFF08\u5DF2\u5907\u4EFD\uFF09===\n" +
|
|
842
|
-
console.log("=== \u6A21\u677F ===\n" +
|
|
1310
|
+
console.log("=== \u5F53\u524D\uFF08\u5DF2\u5907\u4EFD\uFF09===\n" + readFileSync7(destPath, "utf-8"));
|
|
1311
|
+
console.log("=== \u6A21\u677F ===\n" + readFileSync7(addPrismaTemplate, "utf-8"));
|
|
843
1312
|
if (await ask("\u786E\u8BA4\u8986\u76D6\uFF1F[y/N] ") !== "y") {
|
|
844
1313
|
console.log("\u5DF2\u8DF3\u8FC7");
|
|
845
1314
|
return true;
|
|
@@ -851,19 +1320,14 @@ async function injectPrisma(projectRoot, addPrismaTemplate, options = {}) {
|
|
|
851
1320
|
}
|
|
852
1321
|
}
|
|
853
1322
|
if (options.dryRun) {
|
|
854
|
-
console.log("[dry-run] \u5C06\u6267\u884C
|
|
1323
|
+
console.log("[dry-run] \u5C06\u6267\u884C\u6570\u636E\u5E93\u540C\u6B65\uFF08v2 Atlas \u5F15\u64CE\uFF09");
|
|
855
1324
|
return true;
|
|
856
1325
|
}
|
|
857
1326
|
if (!justInited) copyFileSync(addPrismaTemplate, destPath);
|
|
858
1327
|
try {
|
|
859
1328
|
ensurePrismaConfig(projectRoot);
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
const args = pm === "pnpm" ? ["dlx", "prisma", "db", "push"] : ["exec", "prisma", "--", "db", "push"];
|
|
863
|
-
if (C.schemaArg) args.push(C.schemaArg);
|
|
864
|
-
console.log(`\u6267\u884C ${pm} ${args.join(" ")} ...`);
|
|
865
|
-
const r = runCommand(pm, args, { cwd: projectRoot });
|
|
866
|
-
if (r.status !== 0) throw new Error(`prisma db push \u9000\u51FA\u7801: ${r.status}`);
|
|
1329
|
+
const splitDb = await ensureSplitDb(projectRoot, options);
|
|
1330
|
+
await syncDatabase(projectRoot, { yes: options.yes }, splitDb);
|
|
867
1331
|
} catch (err) {
|
|
868
1332
|
if (C.onMigrateFail === "keep") {
|
|
869
1333
|
console.log("\u8FC1\u79FB\u5931\u8D25\uFF0C\u4FDD\u7559\u6587\u4EF6");
|
|
@@ -893,36 +1357,36 @@ ${detail}` : ""}`);
|
|
|
893
1357
|
|
|
894
1358
|
// src/cli/prisma-injector.ts
|
|
895
1359
|
var __filename2 = fileURLToPath2(import.meta.url);
|
|
896
|
-
var __dirname2 =
|
|
897
|
-
var ADD_PRISMA_TEMPLATE =
|
|
1360
|
+
var __dirname2 = dirname4(__filename2);
|
|
1361
|
+
var ADD_PRISMA_TEMPLATE = resolve6(__dirname2, "../templates/core/prisma/add.prisma");
|
|
898
1362
|
async function injectPrisma2(projectRoot, options = {}) {
|
|
899
1363
|
return injectPrisma(projectRoot, ADD_PRISMA_TEMPLATE, options);
|
|
900
1364
|
}
|
|
901
1365
|
|
|
902
1366
|
// src/cli/commands/init.ts
|
|
903
|
-
import { readFileSync as
|
|
1367
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, existsSync as existsSync11, mkdirSync as mkdirSync6, copyFileSync as copyFileSync2, readdirSync as readdirSync4 } from "fs";
|
|
904
1368
|
import { createHash } from "crypto";
|
|
905
|
-
import { resolve as
|
|
906
|
-
import { createConnection } from "net";
|
|
1369
|
+
import { resolve as resolve8 } from "path";
|
|
1370
|
+
import { createConnection as createConnection2 } from "net";
|
|
907
1371
|
|
|
908
1372
|
// src/lib/model-predownload.ts
|
|
909
|
-
import { existsSync as
|
|
910
|
-
import { join as
|
|
1373
|
+
import { existsSync as existsSync10, readFileSync as readFileSync8 } from "fs";
|
|
1374
|
+
import { join as join7, resolve as resolve7 } from "path";
|
|
911
1375
|
import { homedir } from "os";
|
|
912
1376
|
import { parse as parse2 } from "smol-toml";
|
|
913
1377
|
var DEFAULT_DOWNLOAD_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
914
1378
|
var TOML_CANDIDATES = [
|
|
915
|
-
|
|
916
|
-
|
|
1379
|
+
resolve7(import.meta.dirname, "caijuehub/dps-scoring-rules.toml"),
|
|
1380
|
+
resolve7(import.meta.dirname, "../caijuehub/dps-scoring-rules.toml")
|
|
917
1381
|
];
|
|
918
1382
|
function resolveEmbeddingModel() {
|
|
919
|
-
const tomlPath = TOML_CANDIDATES.find((p) =>
|
|
1383
|
+
const tomlPath = TOML_CANDIDATES.find((p) => existsSync10(p));
|
|
920
1384
|
if (!tomlPath) {
|
|
921
1385
|
throw new Error(
|
|
922
1386
|
`dps-scoring-rules.toml \u672A\u627E\u5230\uFF08\u671F\u671B\u8DEF\u5F84: ${TOML_CANDIDATES.join(" \u6216 ")}\uFF09`
|
|
923
1387
|
);
|
|
924
1388
|
}
|
|
925
|
-
const cfg = parse2(
|
|
1389
|
+
const cfg = parse2(readFileSync8(tomlPath, "utf-8"));
|
|
926
1390
|
const model = cfg.embedding?.model;
|
|
927
1391
|
if (typeof model !== "string" || model.length === 0) {
|
|
928
1392
|
throw new Error(`dps-scoring-rules.toml [embedding] model \u672A\u914D\u7F6E\uFF08${tomlPath}\uFF09`);
|
|
@@ -932,8 +1396,8 @@ function resolveEmbeddingModel() {
|
|
|
932
1396
|
function resolveCacheDir() {
|
|
933
1397
|
const hubCache = process.env.HF_HUB_CACHE;
|
|
934
1398
|
if (hubCache) return hubCache;
|
|
935
|
-
const home = process.env.HF_HOME ||
|
|
936
|
-
return
|
|
1399
|
+
const home = process.env.HF_HOME || join7(homedir(), ".cache", "huggingface");
|
|
1400
|
+
return join7(home, "hub");
|
|
937
1401
|
}
|
|
938
1402
|
function modelCacheName(model) {
|
|
939
1403
|
const parts = model.split("/");
|
|
@@ -943,7 +1407,7 @@ function modelCacheName(model) {
|
|
|
943
1407
|
}
|
|
944
1408
|
function isModelCached(model) {
|
|
945
1409
|
const cacheDir = resolveCacheDir();
|
|
946
|
-
return
|
|
1410
|
+
return existsSync10(join7(cacheDir, modelCacheName(model), "snapshots"));
|
|
947
1411
|
}
|
|
948
1412
|
async function ensureEmbeddingModel(options) {
|
|
949
1413
|
const force = options?.force ?? false;
|
|
@@ -956,8 +1420,8 @@ async function ensureEmbeddingModel(options) {
|
|
|
956
1420
|
const { pipeline, env } = await import("@huggingface/transformers");
|
|
957
1421
|
env.cacheDir = resolveCacheDir();
|
|
958
1422
|
const cacheDir = env.cacheDir;
|
|
959
|
-
const snapshotsDir =
|
|
960
|
-
if (!force &&
|
|
1423
|
+
const snapshotsDir = join7(cacheDir, modelCacheName(model), "snapshots");
|
|
1424
|
+
if (!force && existsSync10(snapshotsDir)) {
|
|
961
1425
|
return { status: "already-cached", model, cacheDir };
|
|
962
1426
|
}
|
|
963
1427
|
env.remoteHost = "https://hf-mirror.com";
|
|
@@ -1001,6 +1465,7 @@ async function initCommand(options) {
|
|
|
1001
1465
|
}
|
|
1002
1466
|
const result = await renderAndWrite(ctx);
|
|
1003
1467
|
const dbFail = await deployDatabase(ctx);
|
|
1468
|
+
ensurePortsContract(ctx.projectRoot, ctx.config, !!options.dryRun);
|
|
1004
1469
|
deployDocs(ctx);
|
|
1005
1470
|
finalize(ctx, result, dbFail);
|
|
1006
1471
|
if (!options.dryRun) {
|
|
@@ -1054,9 +1519,9 @@ async function resolveContainer(force) {
|
|
|
1054
1519
|
if (a !== "" && a !== "1" && a !== "podman") console.log("\u8F93\u5165\u65E0\u6CD5\u8BC6\u522B\uFF0C\u9ED8\u8BA4 podman");
|
|
1055
1520
|
return "podman";
|
|
1056
1521
|
}
|
|
1057
|
-
function
|
|
1522
|
+
function portInUse2(port) {
|
|
1058
1523
|
return new Promise((r) => {
|
|
1059
|
-
const s =
|
|
1524
|
+
const s = createConnection2({ port, host: "127.0.0.1" }, () => {
|
|
1060
1525
|
s.destroy();
|
|
1061
1526
|
r(true);
|
|
1062
1527
|
});
|
|
@@ -1098,7 +1563,7 @@ async function resolveDbCredentials(force) {
|
|
|
1098
1563
|
let port = (await ask(`DATABASE_PORT [${d.port}]: `)).trim() || d.port;
|
|
1099
1564
|
while (true) {
|
|
1100
1565
|
const portNum = parseInt(port);
|
|
1101
|
-
if (!isNaN(portNum) && await
|
|
1566
|
+
if (!isNaN(portNum) && await portInUse2(portNum)) {
|
|
1102
1567
|
console.log(`
|
|
1103
1568
|
\u26A0\uFE0F \u7AEF\u53E3 ${port} \u5DF2\u88AB\u5360\u7528`);
|
|
1104
1569
|
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();
|
|
@@ -1152,8 +1617,8 @@ networks:
|
|
|
1152
1617
|
`;
|
|
1153
1618
|
}
|
|
1154
1619
|
function writeSqliteExportScript(projectRoot, dryRun) {
|
|
1155
|
-
const scriptsDir =
|
|
1156
|
-
const scriptPath =
|
|
1620
|
+
const scriptsDir = resolve8(projectRoot, "scripts");
|
|
1621
|
+
const scriptPath = resolve8(scriptsDir, "export-db.ts");
|
|
1157
1622
|
const content = `import { PrismaClient } from "@prisma/client";
|
|
1158
1623
|
import { writeFileSync, mkdirSync, existsSync } from "fs";
|
|
1159
1624
|
import { resolve } from "path";
|
|
@@ -1177,22 +1642,22 @@ main().catch((e) => { console.error(e); process.exit(1); });
|
|
|
1177
1642
|
console.log(`[dry-run] \u5C06\u5199\u5165 ${scriptPath}`);
|
|
1178
1643
|
return;
|
|
1179
1644
|
}
|
|
1180
|
-
if (!
|
|
1181
|
-
|
|
1645
|
+
if (!existsSync11(scriptsDir)) mkdirSync6(scriptsDir, { recursive: true });
|
|
1646
|
+
writeFileSync6(scriptPath, content, "utf-8");
|
|
1182
1647
|
console.log("\u5DF2\u751F\u6210 scripts/export-db.ts");
|
|
1183
1648
|
}
|
|
1184
1649
|
function injectDbExportScript(projectRoot, dryRun) {
|
|
1185
|
-
const pkgPath =
|
|
1186
|
-
if (!
|
|
1650
|
+
const pkgPath = resolve8(projectRoot, "package.json");
|
|
1651
|
+
if (!existsSync11(pkgPath)) return;
|
|
1187
1652
|
if (dryRun) {
|
|
1188
1653
|
console.log("[dry-run] \u5C06\u6CE8\u5165 db:export");
|
|
1189
1654
|
return;
|
|
1190
1655
|
}
|
|
1191
|
-
const pkg = JSON.parse(
|
|
1656
|
+
const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
|
|
1192
1657
|
if (!pkg.scripts) pkg.scripts = {};
|
|
1193
1658
|
if (!pkg.scripts["db:export"]) {
|
|
1194
1659
|
pkg.scripts["db:export"] = "npx tsx scripts/export-db.ts";
|
|
1195
|
-
|
|
1660
|
+
writeFileSync6(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
1196
1661
|
console.log("\u5DF2\u5728 package.json \u6CE8\u5165 db:export");
|
|
1197
1662
|
}
|
|
1198
1663
|
}
|
|
@@ -1235,17 +1700,17 @@ function writeComposeEnv(ctx) {
|
|
|
1235
1700
|
if (db.engine !== "postgresql" || !db.container || db.container === "manual") return;
|
|
1236
1701
|
if (!db.reuseExisting) {
|
|
1237
1702
|
const composeName = db.container === "podman" ? "podman-compose.add.yml" : "docker-compose.add.yml";
|
|
1238
|
-
const composePath =
|
|
1239
|
-
if (!options.dryRun && (!
|
|
1240
|
-
|
|
1703
|
+
const composePath = resolve8(projectRoot, composeName);
|
|
1704
|
+
if (!options.dryRun && (!existsSync11(composePath) || options.force)) {
|
|
1705
|
+
writeFileSync6(composePath, composeContent(config.projectName || "add-project"), "utf-8");
|
|
1241
1706
|
console.log(`\u5DF2\u521B\u5EFA ${composeName}`);
|
|
1242
1707
|
}
|
|
1243
1708
|
}
|
|
1244
|
-
const devEnvPath =
|
|
1245
|
-
if (!options.dryRun &&
|
|
1246
|
-
const existing =
|
|
1709
|
+
const devEnvPath = resolve8(projectRoot, ".env.development");
|
|
1710
|
+
if (!options.dryRun && existsSync11(devEnvPath)) {
|
|
1711
|
+
const existing = readFileSync9(devEnvPath, "utf-8");
|
|
1247
1712
|
if (!/^DATABASE_USER=/m.test(existing)) {
|
|
1248
|
-
|
|
1713
|
+
writeFileSync6(devEnvPath, existing + `
|
|
1249
1714
|
DATABASE_USER=${db.user || "admin"}
|
|
1250
1715
|
DATABASE_PASSWORD=${db.password || "change-me-in-production"}
|
|
1251
1716
|
DATABASE_PORT=${db.port || "5433"}
|
|
@@ -1282,12 +1747,12 @@ async function renderAndWrite(ctx) {
|
|
|
1282
1747
|
console.log(`claude adapter (via Agent Host): ${claudeFiles.size} \u6587\u4EF6`);
|
|
1283
1748
|
}
|
|
1284
1749
|
for (const d of [".add", magicDir]) {
|
|
1285
|
-
const reviewsDir =
|
|
1286
|
-
if (!
|
|
1750
|
+
const reviewsDir = resolve8(projectRoot, d, "reviews");
|
|
1751
|
+
if (!existsSync11(reviewsDir)) {
|
|
1287
1752
|
if (dry) {
|
|
1288
1753
|
console.log(`[dry-run] \u5C06\u521B\u5EFA ${reviewsDir}/`);
|
|
1289
1754
|
} else {
|
|
1290
|
-
|
|
1755
|
+
mkdirSync6(reviewsDir, { recursive: true });
|
|
1291
1756
|
}
|
|
1292
1757
|
}
|
|
1293
1758
|
}
|
|
@@ -1295,16 +1760,16 @@ async function renderAndWrite(ctx) {
|
|
|
1295
1760
|
const hashMap = {};
|
|
1296
1761
|
let npmVer = "";
|
|
1297
1762
|
try {
|
|
1298
|
-
npmVer = JSON.parse(
|
|
1763
|
+
npmVer = JSON.parse(readFileSync9(resolve8(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json"), "utf-8"))._version ?? "";
|
|
1299
1764
|
} catch {
|
|
1300
1765
|
}
|
|
1301
1766
|
for (const [rp, c] of allFiles) {
|
|
1302
1767
|
hashMap[rp] = createHash("sha256").update(c).digest("hex").slice(0, 8);
|
|
1303
1768
|
}
|
|
1304
|
-
const hashOut =
|
|
1305
|
-
|
|
1769
|
+
const hashOut = resolve8(projectRoot, magicDir, ".add-coder-hash.json");
|
|
1770
|
+
writeFileSync6(hashOut, JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
|
|
1306
1771
|
if (npmVer) {
|
|
1307
|
-
|
|
1772
|
+
writeFileSync6(resolve8(projectRoot, magicDir, ".add-coder-version"), npmVer + "\n", "utf-8");
|
|
1308
1773
|
}
|
|
1309
1774
|
console.log(`hash: ${Object.keys(hashMap).length} entries \u2192 ${magicDir}/.add-coder-hash.json`);
|
|
1310
1775
|
}
|
|
@@ -1315,7 +1780,7 @@ async function deployDatabase(ctx) {
|
|
|
1315
1780
|
if (options.dryRun) return null;
|
|
1316
1781
|
let fail = null;
|
|
1317
1782
|
if (db.engine === "postgresql" && db.container && db.container !== "manual") {
|
|
1318
|
-
const dbScript =
|
|
1783
|
+
const dbScript = resolve8(projectRoot, magicDir, "scripts", "db-ensure.sh");
|
|
1319
1784
|
const dbEnv = { ...process.env, DATABASE_USER: db.user, DATABASE_PASSWORD: db.password, DATABASE_PORT: db.port, PROJECT_NAME: config.projectName };
|
|
1320
1785
|
const mode = db.reuseExisting ? "manual" : db.container;
|
|
1321
1786
|
console.log(db.reuseExisting ? "\u590D\u7528\u5DF2\u6709 PostgreSQL ..." : `\u90E8\u7F72\u6570\u636E\u5E93 (${db.container}) ...`);
|
|
@@ -1333,8 +1798,8 @@ ${bashRun.stderr.trim().split("\n").slice(0, 5).join("\n")}` : ""}`;
|
|
|
1333
1798
|
}
|
|
1334
1799
|
}
|
|
1335
1800
|
if (db.engine === "postgresql" && db.container === "manual") {
|
|
1336
|
-
const dbScript =
|
|
1337
|
-
if (
|
|
1801
|
+
const dbScript = resolve8(projectRoot, magicDir, "scripts", "db-ensure.sh");
|
|
1802
|
+
if (existsSync11(dbScript)) {
|
|
1338
1803
|
try {
|
|
1339
1804
|
const bashRun = runCommand("bash", [dbScript, "postgresql", "manual"], { cwd: projectRoot, stdio: "inherit" });
|
|
1340
1805
|
if (bashRun.status !== 0) fail = `db-ensure.sh \u9000\u51FA\u7801: ${bashRun.status}${bashRun.stderr ? `
|
|
@@ -1379,17 +1844,17 @@ function deployDocs(ctx) {
|
|
|
1379
1844
|
const { projectRoot, options, config } = ctx;
|
|
1380
1845
|
if (options.dryRun) return;
|
|
1381
1846
|
const pn = config.projectName || "add-project";
|
|
1382
|
-
const docsBase =
|
|
1383
|
-
const groundingSrc =
|
|
1847
|
+
const docsBase = resolve8(projectRoot, "docs", pn, "knowledge");
|
|
1848
|
+
const groundingSrc = resolve8(import.meta.dirname, "../templates/core/templates");
|
|
1384
1849
|
for (const d of ["00-\u9700\u6C42", "01-\u67B6\u6784", "02-\u89C4\u8303"]) {
|
|
1385
|
-
const srcDir =
|
|
1386
|
-
const destDir =
|
|
1387
|
-
if (!
|
|
1388
|
-
if (!
|
|
1389
|
-
for (const f of
|
|
1390
|
-
const src =
|
|
1391
|
-
const dest =
|
|
1392
|
-
if (
|
|
1850
|
+
const srcDir = resolve8(groundingSrc, d);
|
|
1851
|
+
const destDir = resolve8(docsBase, d);
|
|
1852
|
+
if (!existsSync11(destDir)) mkdirSync6(destDir, { recursive: true });
|
|
1853
|
+
if (!existsSync11(srcDir)) continue;
|
|
1854
|
+
for (const f of readdirSync4(srcDir)) {
|
|
1855
|
+
const src = resolve8(srcDir, f);
|
|
1856
|
+
const dest = resolve8(destDir, f);
|
|
1857
|
+
if (existsSync11(dest)) continue;
|
|
1393
1858
|
try {
|
|
1394
1859
|
copyFileSync2(src, dest);
|
|
1395
1860
|
} catch {
|
|
@@ -1405,7 +1870,7 @@ function finalize(ctx, result, dbFail) {
|
|
|
1405
1870
|
return;
|
|
1406
1871
|
}
|
|
1407
1872
|
if (db.engine === "sqlite") console.log("\u6570\u636E\u5907\u4EFD: npm run db:export \u2192 data/exports/");
|
|
1408
|
-
const pkg = JSON.parse(
|
|
1873
|
+
const pkg = JSON.parse(readFileSync9(resolve8(import.meta.dirname, "../package.json"), "utf-8"));
|
|
1409
1874
|
const peerNames = Object.keys(pkg.peerDependencies || {});
|
|
1410
1875
|
if (peerNames.length > 0) {
|
|
1411
1876
|
console.log(`
|
|
@@ -1433,8 +1898,8 @@ function finalize(ctx, result, dbFail) {
|
|
|
1433
1898
|
}
|
|
1434
1899
|
|
|
1435
1900
|
// src/cli/commands/sync.ts
|
|
1436
|
-
import { existsSync as
|
|
1437
|
-
import { resolve as
|
|
1901
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
|
|
1902
|
+
import { resolve as resolve9, dirname as dirname5 } from "path";
|
|
1438
1903
|
import { createHash as createHash2 } from "crypto";
|
|
1439
1904
|
|
|
1440
1905
|
// src/lib/path-normalize.ts
|
|
@@ -1492,7 +1957,7 @@ function hash8(c) {
|
|
|
1492
1957
|
}
|
|
1493
1958
|
function loadHashFile(root, magic) {
|
|
1494
1959
|
try {
|
|
1495
|
-
const raw = JSON.parse(
|
|
1960
|
+
const raw = JSON.parse(readFileSync10(resolve9(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), "utf-8"));
|
|
1496
1961
|
const normalized = {};
|
|
1497
1962
|
for (const [k, v] of Object.entries(raw)) normalized[normalizeRelPath(k)] = v;
|
|
1498
1963
|
return normalized;
|
|
@@ -1502,18 +1967,18 @@ function loadHashFile(root, magic) {
|
|
|
1502
1967
|
}
|
|
1503
1968
|
function loadVersionFile(root, magic) {
|
|
1504
1969
|
try {
|
|
1505
|
-
return
|
|
1970
|
+
return readFileSync10(resolve9(root, magic, SYNC_CONFIG.VERSION_SENTINEL), "utf-8").trim();
|
|
1506
1971
|
} catch {
|
|
1507
1972
|
return "";
|
|
1508
1973
|
}
|
|
1509
1974
|
}
|
|
1510
1975
|
function saveVersionFile(root, magic, version2) {
|
|
1511
|
-
|
|
1976
|
+
writeFileSync7(resolve9(root, magic, SYNC_CONFIG.VERSION_SENTINEL), version2 + "\n", "utf-8");
|
|
1512
1977
|
}
|
|
1513
1978
|
function saveHashFile(root, magic, files) {
|
|
1514
1979
|
const m = {};
|
|
1515
1980
|
for (const [p, c] of files) m[p] = c;
|
|
1516
|
-
|
|
1981
|
+
writeFileSync7(resolve9(root, magic, SYNC_CONFIG.HASH_OUTPUT_FILE), JSON.stringify(m, null, 2) + "\n", "utf-8");
|
|
1517
1982
|
}
|
|
1518
1983
|
function mergeFullHash(outHash, candidates, readDiskHash) {
|
|
1519
1984
|
const finalHash = /* @__PURE__ */ new Map();
|
|
@@ -1586,10 +2051,10 @@ async function syncCommand(options = {}) {
|
|
|
1586
2051
|
candidates.set(p, c);
|
|
1587
2052
|
}
|
|
1588
2053
|
const outHash = loadHashFile(projectRoot, magicDir);
|
|
1589
|
-
const srcHashPath =
|
|
2054
|
+
const srcHashPath = resolve9(projectRoot, "node_modules", "add-coder", "templates", ".add-coder-src-hash.json");
|
|
1590
2055
|
let npmVersion = "";
|
|
1591
2056
|
try {
|
|
1592
|
-
npmVersion = JSON.parse(
|
|
2057
|
+
npmVersion = JSON.parse(readFileSync10(srcHashPath, "utf-8"))._version ?? "";
|
|
1593
2058
|
} catch {
|
|
1594
2059
|
}
|
|
1595
2060
|
const installedVersion = loadVersionFile(projectRoot, magicDir);
|
|
@@ -1609,13 +2074,13 @@ async function syncCommand(options = {}) {
|
|
|
1609
2074
|
let sameCount = 0;
|
|
1610
2075
|
for (const [relPath, content] of candidates) {
|
|
1611
2076
|
const key = normalizeRelPath(relPath);
|
|
1612
|
-
const absPath =
|
|
1613
|
-
if (!
|
|
2077
|
+
const absPath = resolve9(projectRoot, relPath);
|
|
2078
|
+
if (!existsSync12(absPath)) {
|
|
1614
2079
|
missingFiles.set(key, content);
|
|
1615
2080
|
} else if (establishBaseline) {
|
|
1616
2081
|
missingFiles.set(key, content);
|
|
1617
2082
|
} else {
|
|
1618
|
-
const curH = hash8(
|
|
2083
|
+
const curH = hash8(readFileSync10(absPath, "utf-8"));
|
|
1619
2084
|
const storedH = outHash[key];
|
|
1620
2085
|
if (storedH && curH === storedH) {
|
|
1621
2086
|
sameCount++;
|
|
@@ -1643,18 +2108,19 @@ async function syncCommand(options = {}) {
|
|
|
1643
2108
|
}
|
|
1644
2109
|
const finalHash = mergeFullHash(
|
|
1645
2110
|
outHash,
|
|
1646
|
-
[...candidates].map(([relPath]) => ({ relPath, absPath:
|
|
1647
|
-
(absPath) =>
|
|
2111
|
+
[...candidates].map(([relPath]) => ({ relPath, absPath: resolve9(projectRoot, relPath) })),
|
|
2112
|
+
(absPath) => existsSync12(absPath) ? hash8(readFileSync10(absPath, "utf-8")) : null
|
|
1648
2113
|
);
|
|
1649
2114
|
saveHashFile(projectRoot, magicDir, finalHash);
|
|
1650
2115
|
saveVersionFile(projectRoot, magicDir, npmVersion);
|
|
1651
2116
|
await checkPrismaDiff(projectRoot, options);
|
|
2117
|
+
ensurePortsContract(projectRoot, config);
|
|
1652
2118
|
await maybeModelDownload(options);
|
|
1653
2119
|
return;
|
|
1654
2120
|
}
|
|
1655
2121
|
const missing = /* @__PURE__ */ new Map();
|
|
1656
2122
|
for (const [relPath, content] of allFiles) {
|
|
1657
|
-
if (!
|
|
2123
|
+
if (!existsSync12(resolve9(projectRoot, relPath))) {
|
|
1658
2124
|
missing.set(relPath, content);
|
|
1659
2125
|
}
|
|
1660
2126
|
}
|
|
@@ -1673,11 +2139,12 @@ async function syncCommand(options = {}) {
|
|
|
1673
2139
|
const result = await writeFiles2(projectRoot, filesToWrite, { yes: true });
|
|
1674
2140
|
console.log(`\u540C\u6B65\u5B8C\u6210: \u65B0\u5EFA ${result.created}, \u8DF3\u8FC7 ${result.skipped}`);
|
|
1675
2141
|
await checkPrismaDiff(projectRoot, options);
|
|
2142
|
+
ensurePortsContract(projectRoot, config);
|
|
1676
2143
|
await maybeModelDownload(options);
|
|
1677
2144
|
}
|
|
1678
2145
|
function printMigrateGuidance(targetPath, changed) {
|
|
1679
2146
|
const g = SYNC_PRISMA_CONFIG.POST_SYNC;
|
|
1680
|
-
const isManaged =
|
|
2147
|
+
const isManaged = existsSync12(resolve9(dirname5(targetPath), "migrations"));
|
|
1681
2148
|
const actions = isManaged ? g.MANAGED_ACTIONS : g.UNMANAGED_ACTIONS;
|
|
1682
2149
|
console.log(` \u25B6 \u5DF2\u5199\u5165 ${changed} \u5904\u53D8\u66F4\uFF0C${g.HEADER}`);
|
|
1683
2150
|
for (const a of actions) {
|
|
@@ -1697,23 +2164,62 @@ function printMigrateGuidance(targetPath, changed) {
|
|
|
1697
2164
|
}
|
|
1698
2165
|
console.log(` \u26A0\uFE0F \u6240\u6709\u573A\u666F\u6536\u5C3E: ${g.FINAL}`);
|
|
1699
2166
|
}
|
|
2167
|
+
async function ensureAtlasCapability(projectRoot) {
|
|
2168
|
+
const bin = resolveAtlasBin(projectRoot);
|
|
2169
|
+
if (bin) {
|
|
2170
|
+
console.log(` \u2705 Atlas \u80FD\u529B\u5C31\u7EEA: ${bin}`);
|
|
2171
|
+
return;
|
|
2172
|
+
}
|
|
2173
|
+
console.warn(" \u26A0\uFE0F Atlas \u4E0D\u53EF\u7528\u2014\u2014\u6570\u636E\u5E93\u540C\u6B65\uFF08Atlas diff/apply\uFF09\u80FD\u529B\u7F3A\u5931");
|
|
2174
|
+
const a = await ask(" \u662F\u5426\u81EA\u52A8\u5B89\u88C5 @ariga/atlas\uFF08npm \u4F9D\u8D56\uFF0C\u8D70 registry\uFF09\uFF1F[Y/n] ");
|
|
2175
|
+
if (a === "n" || a === "no") {
|
|
2176
|
+
console.log(" \u5DF2\u8DF3\u8FC7\u3002\u6570\u636E\u5E93\u540C\u6B65\u5C06\u964D\u7EA7 prisma-diff\uFF08\u514D shadow\uFF09\uFF1B\u53EF\u968F\u65F6\u8865\u88C5\u6062\u590D Atlas");
|
|
2177
|
+
console.log(" \u6587\u6863: README.md \u2192 \u7AE0\u8282\u300CAtlas \u6570\u636E\u5E93\u540C\u6B65\u80FD\u529B\u300D/ DEVELOPMENT.md \xA7\u4E5D");
|
|
2178
|
+
return;
|
|
2179
|
+
}
|
|
2180
|
+
const pm = existsSync12(resolve9(projectRoot, "pnpm-lock.yaml")) ? "pnpm" : "npm";
|
|
2181
|
+
console.log(` \u5B89\u88C5 ${pm} add -D @ariga/atlas ...`);
|
|
2182
|
+
const r = runCommand(pm, ["add", "-D", "@ariga/atlas"], { cwd: projectRoot, timeout: 18e4 });
|
|
2183
|
+
if (r.status !== 0) {
|
|
2184
|
+
console.error(" \u5B89\u88C5\u5931\u8D25\u3002\u8BF7\u624B\u52A8\u6267\u884C:");
|
|
2185
|
+
console.error(` ${pm} add -D @ariga/atlas`);
|
|
2186
|
+
console.error(" pnpm 11 \u6CE8\u610F: \u9700\u5728 pnpm-workspace.yaml allowBuilds \u653E\u884C '@ariga/atlas': true");
|
|
2187
|
+
return;
|
|
2188
|
+
}
|
|
2189
|
+
console.log(" \u2705 @ariga/atlas \u5DF2\u5B89\u88C5\uFF0CAtlas \u80FD\u529B\u5C31\u7EEA\uFF08node_modules/.bin/atlas\uFF09");
|
|
2190
|
+
}
|
|
2191
|
+
function checkHostAtlasSegment(projectRoot) {
|
|
2192
|
+
const script = resolve9(projectRoot, "scripts", "db-ensure.sh");
|
|
2193
|
+
if (!existsSync12(script)) return;
|
|
2194
|
+
const content = readFileSync10(script, "utf-8");
|
|
2195
|
+
if (content.includes("atlas_sync")) return;
|
|
2196
|
+
console.warn(" \u26A0\uFE0F \u5BBF\u4E3B scripts/db-ensure.sh \u672A\u5305\u542B Atlas \u540C\u6B65\u6BB5\uFF08\u65E5\u5E38 db-ensure \u5C06\u7F3A\u5C11 ADD \u6CBB\u7406\u6A21\u578B\u540C\u6B65\uFF09");
|
|
2197
|
+
console.warn(" \u804C\u8D23\u8FB9\u754C: add-coder \u53EA\u540C\u6B65 ADD \u6CBB\u7406\u6A21\u578B(7 \u8868)\uFF1B\u5BBF\u4E3B\u4E1A\u52A1\u8868 diff \u63A8\u8350 Atlas \u4F46\u4E0D\u5F3A\u6C42");
|
|
2198
|
+
console.warn(" \u5408\u5165\u4E09\u6B65\uFF1A");
|
|
2199
|
+
console.warn(" \u2460 \u590D\u5236\u6A21\u677F Atlas \u6A21\u5757\u6BB5: sed -n '/# \u2550\u2550\u2550\u2550 Atlas \u58F0\u660E\u5F0F\u540C\u6B65\u6A21\u5757/,/^fi$/p' node_modules/add-coder/templates/core/scripts/db-ensure.sh");
|
|
2200
|
+
console.warn(" \u2461 \u53D8\u91CF\u9002\u914D\uFF08DB_URL\u2192DATABASE_URL \u7B49\uFF0C\u89C1\u6587\u6863\u53D8\u91CF\u5BF9\u7167\u8868\uFF09");
|
|
2201
|
+
console.warn(" \u2462 \u7C98\u8D34\u5230\u811A\u672C\u672B\u5C3E\uFF08\u8FC1\u79FB/generate \u4E4B\u540E\uFF09\uFF0C\u89E6\u53D1: bash scripts/db-ensure.sh <engine> <container> --migrate");
|
|
2202
|
+
console.warn(" \u5BBF\u4E3B\u4E1A\u52A1\u8868\u63A8\u8350\u505A\u6CD5: \u89C1 DEVELOPMENT.md \xA7\u4E5D 9.5\uFF08\u63A8\u8350 Atlas \u53EF\u9009\uFF1B\u4FDD\u6301 migrate dev/deploy \u4EA6\u53EF\uFF09");
|
|
2203
|
+
}
|
|
1700
2204
|
async function checkPrismaDiff(projectRoot, options) {
|
|
1701
2205
|
if (!options.patch) return;
|
|
1702
|
-
const basePath =
|
|
1703
|
-
const targetPath =
|
|
1704
|
-
if (!
|
|
2206
|
+
const basePath = resolve9(projectRoot, SYNC_PRISMA_CONFIG.BASE_SCHEMA);
|
|
2207
|
+
const targetPath = resolve9(projectRoot, SYNC_PRISMA_CONFIG.TARGET_PATTERN);
|
|
2208
|
+
if (!existsSync12(basePath)) {
|
|
1705
2209
|
console.log(`
|
|
1706
2210
|
\u26A0\uFE0F \u57FA\u51C6 schema \u4E0D\u5B58\u5728: ${basePath}`);
|
|
1707
2211
|
console.log(` \u8BF7\u786E\u4FDD add-coder \u5DF2\u6B63\u786E\u5B89\u88C5\u3002`);
|
|
1708
2212
|
return;
|
|
1709
2213
|
}
|
|
2214
|
+
await ensureAtlasCapability(projectRoot);
|
|
2215
|
+
checkHostAtlasSegment(projectRoot);
|
|
1710
2216
|
const result = diffPrisma(basePath, targetPath);
|
|
1711
2217
|
if (!result.hasDiff) {
|
|
1712
2218
|
console.log(`
|
|
1713
2219
|
\u2705 Prisma schema \u4E0E add-coder \u6807\u51C6\u4E00\u81F4\uFF0C\u65E0\u9700\u540C\u6B65\u3002`);
|
|
1714
2220
|
return;
|
|
1715
2221
|
}
|
|
1716
|
-
const targetExists =
|
|
2222
|
+
const targetExists = existsSync12(targetPath);
|
|
1717
2223
|
let modifiedCount = 0;
|
|
1718
2224
|
console.log(`
|
|
1719
2225
|
\u26A0\uFE0F Prisma schema \u5DEE\u5F02\u68C0\u6D4B:`);
|
|
@@ -1832,8 +2338,8 @@ async function checkPrismaDiff(projectRoot, options) {
|
|
|
1832
2338
|
} else {
|
|
1833
2339
|
console.log(`
|
|
1834
2340
|
\u76EE\u6807 schema \u6587\u4EF6\u4E0D\u5B58\u5728: ${result.targetPath}`);
|
|
1835
|
-
|
|
1836
|
-
|
|
2341
|
+
mkdirSync7(dirname5(targetPath), { recursive: true });
|
|
2342
|
+
writeFileSync7(targetPath, "// add.prisma \u2014 ADD \u6CBB\u7406\u6A21\u578B\n\n", "utf-8");
|
|
1837
2343
|
const n = injectMissingModels(targetPath, result.missing);
|
|
1838
2344
|
console.log(` \u2705 \u5DF2\u521B\u5EFA\u5E76\u6CE8\u5165 ${n} \u4E2A\u6A21\u578B/\u679A\u4E3E`);
|
|
1839
2345
|
if (n > 0) {
|
|
@@ -1864,7 +2370,7 @@ async function handleDiffAction(action, ctx) {
|
|
|
1864
2370
|
}
|
|
1865
2371
|
}
|
|
1866
2372
|
function injectMissingModels(targetPath, models) {
|
|
1867
|
-
let content =
|
|
2373
|
+
let content = readFileSync10(targetPath, "utf-8");
|
|
1868
2374
|
content = content.replace(/\n*$/, "\n");
|
|
1869
2375
|
content += `
|
|
1870
2376
|
// ===== \u7531 add-coder sync --patch \u81EA\u52A8\u6CE8\u5165 (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}) =====
|
|
@@ -1873,11 +2379,11 @@ function injectMissingModels(targetPath, models) {
|
|
|
1873
2379
|
for (const m of models) {
|
|
1874
2380
|
content += m.body + "\n\n";
|
|
1875
2381
|
}
|
|
1876
|
-
|
|
2382
|
+
writeFileSync7(targetPath, content, "utf-8");
|
|
1877
2383
|
return models.length;
|
|
1878
2384
|
}
|
|
1879
2385
|
function getBaseFieldLines(basePath, modelName) {
|
|
1880
|
-
const content =
|
|
2386
|
+
const content = readFileSync10(basePath, "utf-8");
|
|
1881
2387
|
const blocks = parseSchemaBlocks(content);
|
|
1882
2388
|
const block = blocks.get(`model:${modelName}`) ?? blocks.get(`enum:${modelName}`);
|
|
1883
2389
|
if (!block) return {};
|
|
@@ -1900,7 +2406,7 @@ function injectFieldLines(targetPath, basePath, modelName, fieldKeys) {
|
|
|
1900
2406
|
console.warn(`\u26A0\uFE0F \u6CE8\u5165\u5931\u8D25\uFF1A${modelName} \u5728\u57FA\u51C6\u4E2D\u672A\u627E\u5230\u5B57\u6BB5\u5B9A\u4E49\uFF08${fieldKeys.length} \u4E2A\u5B57\u6BB5\u672A\u5199\u5165\uFF09`);
|
|
1901
2407
|
return 0;
|
|
1902
2408
|
}
|
|
1903
|
-
const content =
|
|
2409
|
+
const content = readFileSync10(targetPath, "utf-8");
|
|
1904
2410
|
const lines = content.split("\n");
|
|
1905
2411
|
const blocks = parseSchemaBlocks(content);
|
|
1906
2412
|
const target = blocks.get(`model:${modelName}`) ?? blocks.get(`enum:${modelName}`);
|
|
@@ -1941,13 +2447,13 @@ function injectFieldLines(targetPath, basePath, modelName, fieldKeys) {
|
|
|
1941
2447
|
...newFieldLines,
|
|
1942
2448
|
...lines.slice(insertIdx)
|
|
1943
2449
|
];
|
|
1944
|
-
|
|
2450
|
+
writeFileSync7(targetPath, merged.join("\n"), "utf-8");
|
|
1945
2451
|
return newFieldLines.length;
|
|
1946
2452
|
}
|
|
1947
2453
|
function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
|
|
1948
2454
|
const baseFields = getBaseFieldLines(basePath, modelName);
|
|
1949
2455
|
if (Object.keys(baseFields).length === 0) return 0;
|
|
1950
|
-
let content =
|
|
2456
|
+
let content = readFileSync10(targetPath, "utf-8");
|
|
1951
2457
|
let count = 0;
|
|
1952
2458
|
for (const { fieldName } of conflicts) {
|
|
1953
2459
|
const baseLine = baseFields[fieldName];
|
|
@@ -1958,13 +2464,13 @@ function overwriteFieldLines(targetPath, basePath, modelName, conflicts) {
|
|
|
1958
2464
|
content = content.replace(fieldRegex, `${match[1]}${baseLine}`);
|
|
1959
2465
|
count++;
|
|
1960
2466
|
}
|
|
1961
|
-
if (count > 0)
|
|
2467
|
+
if (count > 0) writeFileSync7(targetPath, content, "utf-8");
|
|
1962
2468
|
return count;
|
|
1963
2469
|
}
|
|
1964
2470
|
|
|
1965
2471
|
// src/cli/commands/status.ts
|
|
1966
|
-
import { existsSync as
|
|
1967
|
-
import { resolve as
|
|
2472
|
+
import { existsSync as existsSync13 } from "fs";
|
|
2473
|
+
import { resolve as resolve10 } from "path";
|
|
1968
2474
|
async function statusCommand() {
|
|
1969
2475
|
const projectRoot = process.cwd();
|
|
1970
2476
|
const config = await loadConfig(projectRoot);
|
|
@@ -1973,7 +2479,7 @@ async function statusCommand() {
|
|
|
1973
2479
|
const missing = [];
|
|
1974
2480
|
const present = [];
|
|
1975
2481
|
for (const [relPath] of coreFiles) {
|
|
1976
|
-
if (
|
|
2482
|
+
if (existsSync13(resolve10(projectRoot, relPath))) {
|
|
1977
2483
|
present.push(relPath);
|
|
1978
2484
|
} else {
|
|
1979
2485
|
missing.push(relPath);
|
|
@@ -1991,8 +2497,8 @@ async function statusCommand() {
|
|
|
1991
2497
|
}
|
|
1992
2498
|
|
|
1993
2499
|
// src/cli/commands/stack.ts
|
|
1994
|
-
import { readdirSync as
|
|
1995
|
-
import { resolve as
|
|
2500
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync8, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
|
|
2501
|
+
import { resolve as resolve11, join as join8 } from "path";
|
|
1996
2502
|
import { createHash as createHash3 } from "crypto";
|
|
1997
2503
|
var MAGIC_DIR_MAP3 = { claude: ".claude", qoder: ".qoder", vscode: ".vscode", trae: ".trae", codex: ".codex" };
|
|
1998
2504
|
var HASH_OUTPUT_FILE = ".add-coder-hash.json";
|
|
@@ -2009,14 +2515,14 @@ function resolveMagicDir(projectRoot, specified) {
|
|
|
2009
2515
|
return MAGIC_DIR_MAP3[adapter];
|
|
2010
2516
|
}
|
|
2011
2517
|
function listCustomProfiles(projectRoot, magicDir, registryNames) {
|
|
2012
|
-
const dir =
|
|
2013
|
-
if (!
|
|
2014
|
-
return
|
|
2518
|
+
const dir = resolve11(projectRoot, magicDir, "rules", "profiles");
|
|
2519
|
+
if (!existsSync14(dir)) return [];
|
|
2520
|
+
return readdirSync5(dir).filter((f) => f.endsWith(".md") && !registryNames.has(f.replace(/-profile\.md$/, ""))).sort();
|
|
2015
2521
|
}
|
|
2016
2522
|
function profileExists(projectRoot, magicDir, name) {
|
|
2017
2523
|
const registry = loadProfileRegistry();
|
|
2018
2524
|
if (registry.some((p) => p.name === name)) return true;
|
|
2019
|
-
return
|
|
2525
|
+
return existsSync14(resolve11(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`));
|
|
2020
2526
|
}
|
|
2021
2527
|
function stackCommand(sub, name, options = {}) {
|
|
2022
2528
|
const projectRoot = process.cwd();
|
|
@@ -2053,12 +2559,12 @@ function stackCommand(sub, name, options = {}) {
|
|
|
2053
2559
|
console.log("\u6280\u672F\u6808: \u672A\u8BBE\u7F6E\uFF08\u4E2D\u6027\uFF0C\u65E0\u6280\u672F\u6808\u5047\u8BBE\uFF09");
|
|
2054
2560
|
return;
|
|
2055
2561
|
}
|
|
2056
|
-
const profilePath =
|
|
2057
|
-
const stat =
|
|
2562
|
+
const profilePath = resolve11(projectRoot, magicDir, "rules", "profiles", `${current}-profile.md`);
|
|
2563
|
+
const stat = existsSync14(profilePath) ? readFileSync11(profilePath, "utf-8").length : 0;
|
|
2058
2564
|
console.log(`\u6280\u672F\u6808: ${current}`);
|
|
2059
|
-
console.log(`profile \u6587\u4EF6: ${profilePath}${
|
|
2565
|
+
console.log(`profile \u6587\u4EF6: ${profilePath}${existsSync14(profilePath) ? ` (${stat} \u5B57\u7B26)` : "\uFF08\u7F3A\u5931\uFF09"}`);
|
|
2060
2566
|
try {
|
|
2061
|
-
const raw = JSON.parse(
|
|
2567
|
+
const raw = JSON.parse(readFileSync11(resolve11(projectRoot, magicDir, "stack.json"), "utf-8"));
|
|
2062
2568
|
if (raw.updatedAt) console.log(`\u66F4\u65B0\u65F6\u95F4: ${raw.updatedAt}`);
|
|
2063
2569
|
} catch {
|
|
2064
2570
|
}
|
|
@@ -2083,9 +2589,9 @@ function buildConfig(projectRoot, magicDir, stack) {
|
|
|
2083
2589
|
stack
|
|
2084
2590
|
};
|
|
2085
2591
|
try {
|
|
2086
|
-
const pkgPath =
|
|
2087
|
-
if (
|
|
2088
|
-
const pkg = JSON.parse(
|
|
2592
|
+
const pkgPath = resolve11(projectRoot, "package.json");
|
|
2593
|
+
if (existsSync14(pkgPath)) {
|
|
2594
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
2089
2595
|
if (pkg.name) config.projectName = pkg.name;
|
|
2090
2596
|
}
|
|
2091
2597
|
} catch {
|
|
@@ -2111,15 +2617,15 @@ function applyStack(projectRoot, magicDir, name) {
|
|
|
2111
2617
|
}
|
|
2112
2618
|
const hashMap = {};
|
|
2113
2619
|
try {
|
|
2114
|
-
Object.assign(hashMap, JSON.parse(
|
|
2620
|
+
Object.assign(hashMap, JSON.parse(readFileSync11(resolve11(projectRoot, magicDir, HASH_OUTPUT_FILE), "utf-8")));
|
|
2115
2621
|
} catch {
|
|
2116
2622
|
}
|
|
2117
2623
|
let written = 0;
|
|
2118
2624
|
for (const [relPath, content] of stackRelated) {
|
|
2119
2625
|
for (const t of [".add", magicDir]) {
|
|
2120
|
-
const targetPath =
|
|
2121
|
-
|
|
2122
|
-
|
|
2626
|
+
const targetPath = resolve11(projectRoot, relPath.replace(/^\.add/, t));
|
|
2627
|
+
mkdirSync8(join8(targetPath, ".."), { recursive: true });
|
|
2628
|
+
writeFileSync8(targetPath, content, "utf-8");
|
|
2123
2629
|
hashMap[relPath.replace(/^\.add/, t)] = hash82(content);
|
|
2124
2630
|
written++;
|
|
2125
2631
|
}
|
|
@@ -2130,24 +2636,24 @@ function applyStack(projectRoot, magicDir, name) {
|
|
|
2130
2636
|
};
|
|
2131
2637
|
const registry = loadProfileRegistry();
|
|
2132
2638
|
const isBuiltin = registry.some((p) => p.name === name);
|
|
2133
|
-
const profilePathMagic =
|
|
2134
|
-
const profilePathAdd =
|
|
2135
|
-
const projectRulesPath =
|
|
2136
|
-
const projectRulesContent =
|
|
2639
|
+
const profilePathMagic = resolve11(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`);
|
|
2640
|
+
const profilePathAdd = resolve11(projectRoot, ".add", "rules", "profiles", `${name}-profile.md`);
|
|
2641
|
+
const projectRulesPath = resolve11(projectRoot, magicDir, "rules", "project_rules.md");
|
|
2642
|
+
const projectRulesContent = existsSync14(projectRulesPath) ? readFileSync11(projectRulesPath, "utf-8") : "";
|
|
2137
2643
|
if (written === 0) fail(`\u672A\u6E32\u67D3\u4EFB\u4F55 stack \u76F8\u5173\u6587\u4EF6\uFF08${name}\uFF09\u2014\u2014Windows \u8DEF\u5F84\u5339\u914D\u5931\u6548\u9057\u7559\u95EE\u9898`);
|
|
2138
|
-
if (isBuiltin && !
|
|
2139
|
-
if (!
|
|
2644
|
+
if (isBuiltin && !existsSync14(profilePathAdd)) fail(`profile \u672A\u5199\u5165 .add: ${profilePathAdd}`);
|
|
2645
|
+
if (!existsSync14(profilePathMagic)) fail(`profile \u672A\u5199\u5165 ${magicDir}: ${profilePathMagic}`);
|
|
2140
2646
|
if (!projectRulesContent.includes("**\u5F53\u524D\u6280\u672F\u6808**") || !projectRulesContent.includes(name)) fail(`project_rules.md \u672A\u5305\u542B ${name} \u5F15\u7528`);
|
|
2141
|
-
|
|
2647
|
+
writeFileSync8(resolve11(projectRoot, magicDir, HASH_OUTPUT_FILE), JSON.stringify(hashMap, null, 2) + "\n", "utf-8");
|
|
2142
2648
|
console.log(`\u2705 \u6280\u672F\u6808\u5DF2\u8BBE\u7F6E\u4E3A ${name}`);
|
|
2143
2649
|
console.log(` ${magicDir}/stack.json \u2192 ${name}`);
|
|
2144
|
-
console.log(` ${magicDir}/rules/profiles/${name}-profile.md ${
|
|
2650
|
+
console.log(` ${magicDir}/rules/profiles/${name}-profile.md ${existsSync14(resolve11(projectRoot, magicDir, "rules", "profiles", `${name}-profile.md`)) ? "\u5DF2\u5C31\u4F4D" : "\uFF08\u81EA\u5B9A\u4E49 profile\uFF0C\u9879\u76EE\u4FA7\u6587\u4EF6\uFF09"}`);
|
|
2145
2651
|
console.log(` project_rules.md \u5F15\u7528\u884C\u5DF2\u66F4\u65B0 + hash \u5DF2\u5237\u65B0\uFF08${written} \u4E2A\u6587\u4EF6\uFF09`);
|
|
2146
2652
|
}
|
|
2147
2653
|
|
|
2148
2654
|
// src/cli/index.ts
|
|
2149
2655
|
var { version } = JSON.parse(
|
|
2150
|
-
|
|
2656
|
+
readFileSync12(new URL("../package.json", import.meta.url), "utf-8")
|
|
2151
2657
|
);
|
|
2152
2658
|
async function modelDownloadCommand(options) {
|
|
2153
2659
|
try {
|