everything-dev 1.3.7 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/init.cjs +34 -5
- package/dist/cli/init.cjs.map +1 -1
- package/dist/cli/init.d.cts +10 -7
- package/dist/cli/init.d.cts.map +1 -1
- package/dist/cli/init.d.mts +10 -7
- package/dist/cli/init.d.mts.map +1 -1
- package/dist/cli/init.mjs +34 -6
- package/dist/cli/init.mjs.map +1 -1
- package/dist/cli/prompts.cjs +19 -9
- package/dist/cli/prompts.cjs.map +1 -1
- package/dist/cli/prompts.mjs +19 -9
- package/dist/cli/prompts.mjs.map +1 -1
- package/dist/cli/snapshot.cjs +35 -0
- package/dist/cli/snapshot.cjs.map +1 -0
- package/dist/cli/snapshot.mjs +33 -0
- package/dist/cli/snapshot.mjs.map +1 -0
- package/dist/cli/status.cjs +80 -0
- package/dist/cli/status.cjs.map +1 -0
- package/dist/cli/status.mjs +79 -0
- package/dist/cli/status.mjs.map +1 -0
- package/dist/cli/sync.cjs +170 -0
- package/dist/cli/sync.cjs.map +1 -0
- package/dist/cli/sync.mjs +169 -0
- package/dist/cli/sync.mjs.map +1 -0
- package/dist/cli/upgrade.cjs +123 -0
- package/dist/cli/upgrade.cjs.map +1 -0
- package/dist/cli/upgrade.mjs +122 -0
- package/dist/cli/upgrade.mjs.map +1 -0
- package/dist/cli.cjs +101 -5
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +101 -5
- package/dist/cli.mjs.map +1 -1
- package/dist/contract.cjs +81 -8
- package/dist/contract.cjs.map +1 -1
- package/dist/contract.d.cts +156 -15
- package/dist/contract.d.cts.map +1 -1
- package/dist/contract.d.mts +156 -15
- package/dist/contract.d.mts.map +1 -1
- package/dist/contract.meta.cjs +32 -9
- package/dist/contract.meta.cjs.map +1 -1
- package/dist/contract.meta.d.cts +50 -11
- package/dist/contract.meta.d.mts +50 -11
- package/dist/contract.meta.mjs +32 -9
- package/dist/contract.meta.mjs.map +1 -1
- package/dist/contract.mjs +77 -9
- package/dist/contract.mjs.map +1 -1
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/plugin.cjs +123 -43
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.d.cts +75 -8
- package/dist/plugin.d.cts.map +1 -1
- package/dist/plugin.d.mts +75 -8
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +126 -46
- package/dist/plugin.mjs.map +1 -1
- package/dist/types.d.cts +2 -2
- package/dist/types.d.mts +2 -2
- package/dist/utils/theme.cjs +1 -0
- package/dist/utils/theme.cjs.map +1 -1
- package/dist/utils/theme.mjs +1 -0
- package/dist/utils/theme.mjs.map +1 -1
- package/package.json +1 -1
- package/src/cli/init.ts +60 -11
- package/src/cli/prompts.ts +34 -16
- package/src/cli/snapshot.ts +46 -0
- package/src/cli/status.ts +85 -0
- package/src/cli/sync.ts +239 -0
- package/src/cli/upgrade.ts +165 -0
- package/src/cli.ts +152 -5
- package/src/contract.meta.ts +36 -6
- package/src/contract.ts +74 -7
- package/src/plugin.ts +156 -45
- package/src/utils/theme.ts +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
let node_fs = require("node:fs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
|
|
5
|
+
//#region src/cli/snapshot.ts
|
|
6
|
+
const SNAPSHOT_DIR = ".bos";
|
|
7
|
+
const SNAPSHOT_FILE = "sync-snapshot.json";
|
|
8
|
+
function snapshotPath(projectDir) {
|
|
9
|
+
return (0, node_path.join)(projectDir, SNAPSHOT_DIR, SNAPSHOT_FILE);
|
|
10
|
+
}
|
|
11
|
+
async function readSnapshot(projectDir) {
|
|
12
|
+
const path = snapshotPath(projectDir);
|
|
13
|
+
if (!(0, node_fs.existsSync)(path)) return null;
|
|
14
|
+
try {
|
|
15
|
+
const content = (0, node_fs.readFileSync)(path, "utf-8");
|
|
16
|
+
return JSON.parse(content);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function writeSnapshot(projectDir, data) {
|
|
22
|
+
const dir = (0, node_path.join)(projectDir, SNAPSHOT_DIR);
|
|
23
|
+
if (!(0, node_fs.existsSync)(dir)) (0, node_fs.mkdirSync)(dir, { recursive: true });
|
|
24
|
+
const snapshot = {
|
|
25
|
+
parentRef: data.parentRef,
|
|
26
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
27
|
+
files: data.files
|
|
28
|
+
};
|
|
29
|
+
(0, node_fs.writeFileSync)(snapshotPath(projectDir), `${JSON.stringify(snapshot, null, 2)}\n`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
exports.readSnapshot = readSnapshot;
|
|
34
|
+
exports.writeSnapshot = writeSnapshot;
|
|
35
|
+
//# sourceMappingURL=snapshot.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.cjs","names":[],"sources":["../../src/cli/snapshot.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface SyncSnapshot {\n parentRef: string;\n timestamp: string;\n files: Record<string, string>;\n}\n\nconst SNAPSHOT_DIR = \".bos\";\nconst SNAPSHOT_FILE = \"sync-snapshot.json\";\n\nfunction snapshotPath(projectDir: string): string {\n return join(projectDir, SNAPSHOT_DIR, SNAPSHOT_FILE);\n}\n\nexport async function readSnapshot(projectDir: string): Promise<SyncSnapshot | null> {\n const path = snapshotPath(projectDir);\n if (!existsSync(path)) {\n return null;\n }\n try {\n const content = readFileSync(path, \"utf-8\");\n return JSON.parse(content) as SyncSnapshot;\n } catch {\n return null;\n }\n}\n\nexport async function writeSnapshot(\n projectDir: string,\n data: { parentRef: string; files: Record<string, string> },\n): Promise<void> {\n const dir = join(projectDir, SNAPSHOT_DIR);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const snapshot: SyncSnapshot = {\n parentRef: data.parentRef,\n timestamp: new Date().toISOString(),\n files: data.files,\n };\n\n writeFileSync(snapshotPath(projectDir), `${JSON.stringify(snapshot, null, 2)}\\n`);\n}\n"],"mappings":";;;;;AASA,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAEtB,SAAS,aAAa,YAA4B;AAChD,4BAAY,YAAY,cAAc,cAAc;;AAGtD,eAAsB,aAAa,YAAkD;CACnF,MAAM,OAAO,aAAa,WAAW;AACrC,KAAI,yBAAY,KAAK,CACnB,QAAO;AAET,KAAI;EACF,MAAM,oCAAuB,MAAM,QAAQ;AAC3C,SAAO,KAAK,MAAM,QAAQ;SACpB;AACN,SAAO;;;AAIX,eAAsB,cACpB,YACA,MACe;CACf,MAAM,0BAAW,YAAY,aAAa;AAC1C,KAAI,yBAAY,IAAI,CAClB,wBAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CAGrC,MAAM,WAAyB;EAC7B,WAAW,KAAK;EAChB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,OAAO,KAAK;EACb;AAED,4BAAc,aAAa,WAAW,EAAE,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,IAAI"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/cli/snapshot.ts
|
|
5
|
+
const SNAPSHOT_DIR = ".bos";
|
|
6
|
+
const SNAPSHOT_FILE = "sync-snapshot.json";
|
|
7
|
+
function snapshotPath(projectDir) {
|
|
8
|
+
return join(projectDir, SNAPSHOT_DIR, SNAPSHOT_FILE);
|
|
9
|
+
}
|
|
10
|
+
async function readSnapshot(projectDir) {
|
|
11
|
+
const path = snapshotPath(projectDir);
|
|
12
|
+
if (!existsSync(path)) return null;
|
|
13
|
+
try {
|
|
14
|
+
const content = readFileSync(path, "utf-8");
|
|
15
|
+
return JSON.parse(content);
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function writeSnapshot(projectDir, data) {
|
|
21
|
+
const dir = join(projectDir, SNAPSHOT_DIR);
|
|
22
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
23
|
+
const snapshot = {
|
|
24
|
+
parentRef: data.parentRef,
|
|
25
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
26
|
+
files: data.files
|
|
27
|
+
};
|
|
28
|
+
writeFileSync(snapshotPath(projectDir), `${JSON.stringify(snapshot, null, 2)}\n`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { readSnapshot, writeSnapshot };
|
|
33
|
+
//# sourceMappingURL=snapshot.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot.mjs","names":[],"sources":["../../src/cli/snapshot.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface SyncSnapshot {\n parentRef: string;\n timestamp: string;\n files: Record<string, string>;\n}\n\nconst SNAPSHOT_DIR = \".bos\";\nconst SNAPSHOT_FILE = \"sync-snapshot.json\";\n\nfunction snapshotPath(projectDir: string): string {\n return join(projectDir, SNAPSHOT_DIR, SNAPSHOT_FILE);\n}\n\nexport async function readSnapshot(projectDir: string): Promise<SyncSnapshot | null> {\n const path = snapshotPath(projectDir);\n if (!existsSync(path)) {\n return null;\n }\n try {\n const content = readFileSync(path, \"utf-8\");\n return JSON.parse(content) as SyncSnapshot;\n } catch {\n return null;\n }\n}\n\nexport async function writeSnapshot(\n projectDir: string,\n data: { parentRef: string; files: Record<string, string> },\n): Promise<void> {\n const dir = join(projectDir, SNAPSHOT_DIR);\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n\n const snapshot: SyncSnapshot = {\n parentRef: data.parentRef,\n timestamp: new Date().toISOString(),\n files: data.files,\n };\n\n writeFileSync(snapshotPath(projectDir), `${JSON.stringify(snapshot, null, 2)}\\n`);\n}\n"],"mappings":";;;;AASA,MAAM,eAAe;AACrB,MAAM,gBAAgB;AAEtB,SAAS,aAAa,YAA4B;AAChD,QAAO,KAAK,YAAY,cAAc,cAAc;;AAGtD,eAAsB,aAAa,YAAkD;CACnF,MAAM,OAAO,aAAa,WAAW;AACrC,KAAI,CAAC,WAAW,KAAK,CACnB,QAAO;AAET,KAAI;EACF,MAAM,UAAU,aAAa,MAAM,QAAQ;AAC3C,SAAO,KAAK,MAAM,QAAQ;SACpB;AACN,SAAO;;;AAIX,eAAsB,cACpB,YACA,MACe;CACf,MAAM,MAAM,KAAK,YAAY,aAAa;AAC1C,KAAI,CAAC,WAAW,IAAI,CAClB,WAAU,KAAK,EAAE,WAAW,MAAM,CAAC;CAGrC,MAAM,WAAyB;EAC7B,WAAW,KAAK;EAChB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,OAAO,KAAK;EACb;AAED,eAAc,aAAa,WAAW,EAAE,GAAG,KAAK,UAAU,UAAU,MAAM,EAAE,CAAC,IAAI"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
const require_fastkv = require('../fastkv.cjs');
|
|
3
|
+
const require_snapshot = require('./snapshot.cjs');
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
|
|
7
|
+
//#region src/cli/status.ts
|
|
8
|
+
const FRAMEWORK_PACKAGES = ["everything-dev", "every-plugin"];
|
|
9
|
+
async function fetchLatestNpmVersion(packageName) {
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
12
|
+
headers: { Accept: "application/json" },
|
|
13
|
+
signal: AbortSignal.timeout(1e4)
|
|
14
|
+
});
|
|
15
|
+
if (!response.ok) return null;
|
|
16
|
+
return (await response.json()).version;
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function readInstalledVersion(projectDir, packageName) {
|
|
22
|
+
const pkgPath = (0, node_path.join)(projectDir, "package.json");
|
|
23
|
+
if (!(0, node_fs.existsSync)(pkgPath)) return void 0;
|
|
24
|
+
const pkg = JSON.parse((0, node_fs.readFileSync)(pkgPath, "utf-8"));
|
|
25
|
+
const deps = pkg.dependencies ?? {};
|
|
26
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
27
|
+
const version = deps[packageName] || devDeps[packageName];
|
|
28
|
+
if (!version) return void 0;
|
|
29
|
+
return version.replace(/^[\^~>=]+/, "");
|
|
30
|
+
}
|
|
31
|
+
function checkEnvFile(projectDir) {
|
|
32
|
+
if ((0, node_fs.existsSync)((0, node_path.join)(projectDir, ".env"))) return "found";
|
|
33
|
+
if ((0, node_fs.existsSync)((0, node_path.join)(projectDir, ".env.example"))) return "example-only";
|
|
34
|
+
return "missing";
|
|
35
|
+
}
|
|
36
|
+
async function checkParentReachable(extendsRef) {
|
|
37
|
+
if (!extendsRef?.startsWith("bos://")) return void 0;
|
|
38
|
+
try {
|
|
39
|
+
return await require_fastkv.fetchBosConfigFromFastKv(extendsRef) !== null;
|
|
40
|
+
} catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function getStatus(projectDir) {
|
|
45
|
+
const configPath = (0, node_path.join)(projectDir, "bos.config.json");
|
|
46
|
+
if (!(0, node_fs.existsSync)(configPath)) return {
|
|
47
|
+
status: "error",
|
|
48
|
+
error: "No bos.config.json found in current directory",
|
|
49
|
+
packages: [],
|
|
50
|
+
envFile: "missing"
|
|
51
|
+
};
|
|
52
|
+
const config = JSON.parse((0, node_fs.readFileSync)(configPath, "utf-8"));
|
|
53
|
+
const packages = [];
|
|
54
|
+
for (const name of FRAMEWORK_PACKAGES) {
|
|
55
|
+
const installed = readInstalledVersion(projectDir, name);
|
|
56
|
+
const latest = await fetchLatestNpmVersion(name);
|
|
57
|
+
packages.push({
|
|
58
|
+
name,
|
|
59
|
+
installed,
|
|
60
|
+
latest: latest ?? void 0
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const snapshot = await require_snapshot.readSnapshot(projectDir);
|
|
64
|
+
const extendsRef = config.extends;
|
|
65
|
+
const parentReachable = await checkParentReachable(extendsRef);
|
|
66
|
+
return {
|
|
67
|
+
status: "ok",
|
|
68
|
+
extends: extendsRef,
|
|
69
|
+
account: config.account,
|
|
70
|
+
domain: config.domain,
|
|
71
|
+
packages,
|
|
72
|
+
lastSync: snapshot?.timestamp,
|
|
73
|
+
envFile: checkEnvFile(projectDir),
|
|
74
|
+
parentReachable
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
//#endregion
|
|
79
|
+
exports.getStatus = getStatus;
|
|
80
|
+
//# sourceMappingURL=status.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.cjs","names":["fetchBosConfigFromFastKv","readSnapshot"],"sources":["../../src/cli/status.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { StatusResult } from \"../contract\";\nimport { fetchBosConfigFromFastKv } from \"../fastkv\";\nimport { readSnapshot } from \"./snapshot\";\n\nconst FRAMEWORK_PACKAGES = [\"everything-dev\", \"every-plugin\"];\n\nasync function fetchLatestNpmVersion(packageName: string): Promise<string | null> {\n try {\n const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {\n headers: { Accept: \"application/json\" },\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) return null;\n const data = (await response.json()) as { version: string };\n return data.version;\n } catch {\n return null;\n }\n}\n\nfunction readInstalledVersion(projectDir: string, packageName: string): string | undefined {\n const pkgPath = join(projectDir, \"package.json\");\n if (!existsSync(pkgPath)) return undefined;\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as Record<string, unknown>;\n const deps = (pkg.dependencies ?? {}) as Record<string, string>;\n const devDeps = (pkg.devDependencies ?? {}) as Record<string, string>;\n const version = deps[packageName] || devDeps[packageName];\n if (!version) return undefined;\n return version.replace(/^[\\^~>=]+/, \"\");\n}\n\nfunction checkEnvFile(projectDir: string): \"found\" | \"missing\" | \"example-only\" {\n if (existsSync(join(projectDir, \".env\"))) return \"found\";\n if (existsSync(join(projectDir, \".env.example\"))) return \"example-only\";\n return \"missing\";\n}\n\nasync function checkParentReachable(extendsRef: string | undefined): Promise<boolean | undefined> {\n if (!extendsRef?.startsWith(\"bos://\")) return undefined;\n try {\n const config = await fetchBosConfigFromFastKv(extendsRef);\n return config !== null;\n } catch {\n return false;\n }\n}\n\nexport async function getStatus(projectDir: string): Promise<StatusResult> {\n const configPath = join(projectDir, \"bos.config.json\");\n if (!existsSync(configPath)) {\n return {\n status: \"error\",\n error: \"No bos.config.json found in current directory\",\n packages: [],\n envFile: \"missing\",\n };\n }\n\n const config = JSON.parse(readFileSync(configPath, \"utf-8\")) as Record<string, unknown>;\n\n const packages = [];\n for (const name of FRAMEWORK_PACKAGES) {\n const installed = readInstalledVersion(projectDir, name);\n const latest = await fetchLatestNpmVersion(name);\n packages.push({ name, installed, latest: latest ?? undefined });\n }\n\n const snapshot = await readSnapshot(projectDir);\n\n const extendsRef = config.extends as string | undefined;\n const parentReachable = await checkParentReachable(extendsRef);\n\n return {\n status: \"ok\",\n extends: extendsRef,\n account: config.account as string | undefined,\n domain: config.domain as string | undefined,\n packages,\n lastSync: snapshot?.timestamp,\n envFile: checkEnvFile(projectDir),\n parentReachable,\n };\n}\n"],"mappings":";;;;;;;AAMA,MAAM,qBAAqB,CAAC,kBAAkB,eAAe;AAE7D,eAAe,sBAAsB,aAA6C;AAChF,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,8BAA8B,YAAY,UAAU;GAC/E,SAAS,EAAE,QAAQ,oBAAoB;GACvC,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC;AACF,MAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UADc,MAAM,SAAS,MAAM,EACvB;SACN;AACN,SAAO;;;AAIX,SAAS,qBAAqB,YAAoB,aAAyC;CACzF,MAAM,8BAAe,YAAY,eAAe;AAChD,KAAI,yBAAY,QAAQ,CAAE,QAAO;CACjC,MAAM,MAAM,KAAK,gCAAmB,SAAS,QAAQ,CAAC;CACtD,MAAM,OAAQ,IAAI,gBAAgB,EAAE;CACpC,MAAM,UAAW,IAAI,mBAAmB,EAAE;CAC1C,MAAM,UAAU,KAAK,gBAAgB,QAAQ;AAC7C,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO,QAAQ,QAAQ,aAAa,GAAG;;AAGzC,SAAS,aAAa,YAA0D;AAC9E,iDAAoB,YAAY,OAAO,CAAC,CAAE,QAAO;AACjD,iDAAoB,YAAY,eAAe,CAAC,CAAE,QAAO;AACzD,QAAO;;AAGT,eAAe,qBAAqB,YAA8D;AAChG,KAAI,CAAC,YAAY,WAAW,SAAS,CAAE,QAAO;AAC9C,KAAI;AAEF,SADe,MAAMA,wCAAyB,WAAW,KACvC;SACZ;AACN,SAAO;;;AAIX,eAAsB,UAAU,YAA2C;CACzE,MAAM,iCAAkB,YAAY,kBAAkB;AACtD,KAAI,yBAAY,WAAW,CACzB,QAAO;EACL,QAAQ;EACR,OAAO;EACP,UAAU,EAAE;EACZ,SAAS;EACV;CAGH,MAAM,SAAS,KAAK,gCAAmB,YAAY,QAAQ,CAAC;CAE5D,MAAM,WAAW,EAAE;AACnB,MAAK,MAAM,QAAQ,oBAAoB;EACrC,MAAM,YAAY,qBAAqB,YAAY,KAAK;EACxD,MAAM,SAAS,MAAM,sBAAsB,KAAK;AAChD,WAAS,KAAK;GAAE;GAAM;GAAW,QAAQ,UAAU;GAAW,CAAC;;CAGjE,MAAM,WAAW,MAAMC,8BAAa,WAAW;CAE/C,MAAM,aAAa,OAAO;CAC1B,MAAM,kBAAkB,MAAM,qBAAqB,WAAW;AAE9D,QAAO;EACL,QAAQ;EACR,SAAS;EACT,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf;EACA,UAAU,UAAU;EACpB,SAAS,aAAa,WAAW;EACjC;EACD"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { fetchBosConfigFromFastKv } from "../fastkv.mjs";
|
|
2
|
+
import { readSnapshot } from "./snapshot.mjs";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
//#region src/cli/status.ts
|
|
7
|
+
const FRAMEWORK_PACKAGES = ["everything-dev", "every-plugin"];
|
|
8
|
+
async function fetchLatestNpmVersion(packageName) {
|
|
9
|
+
try {
|
|
10
|
+
const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {
|
|
11
|
+
headers: { Accept: "application/json" },
|
|
12
|
+
signal: AbortSignal.timeout(1e4)
|
|
13
|
+
});
|
|
14
|
+
if (!response.ok) return null;
|
|
15
|
+
return (await response.json()).version;
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function readInstalledVersion(projectDir, packageName) {
|
|
21
|
+
const pkgPath = join(projectDir, "package.json");
|
|
22
|
+
if (!existsSync(pkgPath)) return void 0;
|
|
23
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
24
|
+
const deps = pkg.dependencies ?? {};
|
|
25
|
+
const devDeps = pkg.devDependencies ?? {};
|
|
26
|
+
const version = deps[packageName] || devDeps[packageName];
|
|
27
|
+
if (!version) return void 0;
|
|
28
|
+
return version.replace(/^[\^~>=]+/, "");
|
|
29
|
+
}
|
|
30
|
+
function checkEnvFile(projectDir) {
|
|
31
|
+
if (existsSync(join(projectDir, ".env"))) return "found";
|
|
32
|
+
if (existsSync(join(projectDir, ".env.example"))) return "example-only";
|
|
33
|
+
return "missing";
|
|
34
|
+
}
|
|
35
|
+
async function checkParentReachable(extendsRef) {
|
|
36
|
+
if (!extendsRef?.startsWith("bos://")) return void 0;
|
|
37
|
+
try {
|
|
38
|
+
return await fetchBosConfigFromFastKv(extendsRef) !== null;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function getStatus(projectDir) {
|
|
44
|
+
const configPath = join(projectDir, "bos.config.json");
|
|
45
|
+
if (!existsSync(configPath)) return {
|
|
46
|
+
status: "error",
|
|
47
|
+
error: "No bos.config.json found in current directory",
|
|
48
|
+
packages: [],
|
|
49
|
+
envFile: "missing"
|
|
50
|
+
};
|
|
51
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
52
|
+
const packages = [];
|
|
53
|
+
for (const name of FRAMEWORK_PACKAGES) {
|
|
54
|
+
const installed = readInstalledVersion(projectDir, name);
|
|
55
|
+
const latest = await fetchLatestNpmVersion(name);
|
|
56
|
+
packages.push({
|
|
57
|
+
name,
|
|
58
|
+
installed,
|
|
59
|
+
latest: latest ?? void 0
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
const snapshot = await readSnapshot(projectDir);
|
|
63
|
+
const extendsRef = config.extends;
|
|
64
|
+
const parentReachable = await checkParentReachable(extendsRef);
|
|
65
|
+
return {
|
|
66
|
+
status: "ok",
|
|
67
|
+
extends: extendsRef,
|
|
68
|
+
account: config.account,
|
|
69
|
+
domain: config.domain,
|
|
70
|
+
packages,
|
|
71
|
+
lastSync: snapshot?.timestamp,
|
|
72
|
+
envFile: checkEnvFile(projectDir),
|
|
73
|
+
parentReachable
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { getStatus };
|
|
79
|
+
//# sourceMappingURL=status.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.mjs","names":[],"sources":["../../src/cli/status.ts"],"sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { StatusResult } from \"../contract\";\nimport { fetchBosConfigFromFastKv } from \"../fastkv\";\nimport { readSnapshot } from \"./snapshot\";\n\nconst FRAMEWORK_PACKAGES = [\"everything-dev\", \"every-plugin\"];\n\nasync function fetchLatestNpmVersion(packageName: string): Promise<string | null> {\n try {\n const response = await fetch(`https://registry.npmjs.org/${packageName}/latest`, {\n headers: { Accept: \"application/json\" },\n signal: AbortSignal.timeout(10_000),\n });\n if (!response.ok) return null;\n const data = (await response.json()) as { version: string };\n return data.version;\n } catch {\n return null;\n }\n}\n\nfunction readInstalledVersion(projectDir: string, packageName: string): string | undefined {\n const pkgPath = join(projectDir, \"package.json\");\n if (!existsSync(pkgPath)) return undefined;\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf-8\")) as Record<string, unknown>;\n const deps = (pkg.dependencies ?? {}) as Record<string, string>;\n const devDeps = (pkg.devDependencies ?? {}) as Record<string, string>;\n const version = deps[packageName] || devDeps[packageName];\n if (!version) return undefined;\n return version.replace(/^[\\^~>=]+/, \"\");\n}\n\nfunction checkEnvFile(projectDir: string): \"found\" | \"missing\" | \"example-only\" {\n if (existsSync(join(projectDir, \".env\"))) return \"found\";\n if (existsSync(join(projectDir, \".env.example\"))) return \"example-only\";\n return \"missing\";\n}\n\nasync function checkParentReachable(extendsRef: string | undefined): Promise<boolean | undefined> {\n if (!extendsRef?.startsWith(\"bos://\")) return undefined;\n try {\n const config = await fetchBosConfigFromFastKv(extendsRef);\n return config !== null;\n } catch {\n return false;\n }\n}\n\nexport async function getStatus(projectDir: string): Promise<StatusResult> {\n const configPath = join(projectDir, \"bos.config.json\");\n if (!existsSync(configPath)) {\n return {\n status: \"error\",\n error: \"No bos.config.json found in current directory\",\n packages: [],\n envFile: \"missing\",\n };\n }\n\n const config = JSON.parse(readFileSync(configPath, \"utf-8\")) as Record<string, unknown>;\n\n const packages = [];\n for (const name of FRAMEWORK_PACKAGES) {\n const installed = readInstalledVersion(projectDir, name);\n const latest = await fetchLatestNpmVersion(name);\n packages.push({ name, installed, latest: latest ?? undefined });\n }\n\n const snapshot = await readSnapshot(projectDir);\n\n const extendsRef = config.extends as string | undefined;\n const parentReachable = await checkParentReachable(extendsRef);\n\n return {\n status: \"ok\",\n extends: extendsRef,\n account: config.account as string | undefined,\n domain: config.domain as string | undefined,\n packages,\n lastSync: snapshot?.timestamp,\n envFile: checkEnvFile(projectDir),\n parentReachable,\n };\n}\n"],"mappings":";;;;;;AAMA,MAAM,qBAAqB,CAAC,kBAAkB,eAAe;AAE7D,eAAe,sBAAsB,aAA6C;AAChF,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,8BAA8B,YAAY,UAAU;GAC/E,SAAS,EAAE,QAAQ,oBAAoB;GACvC,QAAQ,YAAY,QAAQ,IAAO;GACpC,CAAC;AACF,MAAI,CAAC,SAAS,GAAI,QAAO;AAEzB,UADc,MAAM,SAAS,MAAM,EACvB;SACN;AACN,SAAO;;;AAIX,SAAS,qBAAqB,YAAoB,aAAyC;CACzF,MAAM,UAAU,KAAK,YAAY,eAAe;AAChD,KAAI,CAAC,WAAW,QAAQ,CAAE,QAAO;CACjC,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC;CACtD,MAAM,OAAQ,IAAI,gBAAgB,EAAE;CACpC,MAAM,UAAW,IAAI,mBAAmB,EAAE;CAC1C,MAAM,UAAU,KAAK,gBAAgB,QAAQ;AAC7C,KAAI,CAAC,QAAS,QAAO;AACrB,QAAO,QAAQ,QAAQ,aAAa,GAAG;;AAGzC,SAAS,aAAa,YAA0D;AAC9E,KAAI,WAAW,KAAK,YAAY,OAAO,CAAC,CAAE,QAAO;AACjD,KAAI,WAAW,KAAK,YAAY,eAAe,CAAC,CAAE,QAAO;AACzD,QAAO;;AAGT,eAAe,qBAAqB,YAA8D;AAChG,KAAI,CAAC,YAAY,WAAW,SAAS,CAAE,QAAO;AAC9C,KAAI;AAEF,SADe,MAAM,yBAAyB,WAAW,KACvC;SACZ;AACN,SAAO;;;AAIX,eAAsB,UAAU,YAA2C;CACzE,MAAM,aAAa,KAAK,YAAY,kBAAkB;AACtD,KAAI,CAAC,WAAW,WAAW,CACzB,QAAO;EACL,QAAQ;EACR,OAAO;EACP,UAAU,EAAE;EACZ,SAAS;EACV;CAGH,MAAM,SAAS,KAAK,MAAM,aAAa,YAAY,QAAQ,CAAC;CAE5D,MAAM,WAAW,EAAE;AACnB,MAAK,MAAM,QAAQ,oBAAoB;EACrC,MAAM,YAAY,qBAAqB,YAAY,KAAK;EACxD,MAAM,SAAS,MAAM,sBAAsB,KAAK;AAChD,WAAS,KAAK;GAAE;GAAM;GAAW,QAAQ,UAAU;GAAW,CAAC;;CAGjE,MAAM,WAAW,MAAM,aAAa,WAAW;CAE/C,MAAM,aAAa,OAAO;CAC1B,MAAM,kBAAkB,MAAM,qBAAqB,WAAW;AAE9D,QAAO;EACL,QAAQ;EACR,SAAS;EACT,SAAS,OAAO;EAChB,QAAQ,OAAO;EACf;EACA,UAAU,UAAU;EACpB,SAAS,aAAa,WAAW;EACjC;EACD"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const require_runtime = require('../_virtual/_rolldown/runtime.cjs');
|
|
2
|
+
const require_snapshot = require('./snapshot.cjs');
|
|
3
|
+
const require_cli_init = require('./init.cjs');
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
let node_path = require("node:path");
|
|
6
|
+
let node_crypto = require("node:crypto");
|
|
7
|
+
let glob = require("glob");
|
|
8
|
+
|
|
9
|
+
//#region src/cli/sync.ts
|
|
10
|
+
function readExcludeFile(filePath) {
|
|
11
|
+
if (!(0, node_fs.existsSync)(filePath)) return [];
|
|
12
|
+
return (0, node_fs.readFileSync)(filePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
13
|
+
}
|
|
14
|
+
async function readTemplatesyncExclude(sourceDir) {
|
|
15
|
+
return readExcludeFile((0, node_path.join)(sourceDir, ".templatesync-exclude"));
|
|
16
|
+
}
|
|
17
|
+
function readLocalSyncExcludes(projectDir) {
|
|
18
|
+
return readExcludeFile((0, node_path.join)(projectDir, ".bos", "sync-local-exclude"));
|
|
19
|
+
}
|
|
20
|
+
function isExcluded(filePath, excludePatterns) {
|
|
21
|
+
for (const pattern of excludePatterns) if (pattern.endsWith("/**")) {
|
|
22
|
+
const prefix = pattern.slice(0, -3);
|
|
23
|
+
if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;
|
|
24
|
+
} else if (pattern.endsWith("/*")) {
|
|
25
|
+
const prefix = pattern.slice(0, -2);
|
|
26
|
+
const slashIdx = filePath.indexOf("/", prefix.length + 1);
|
|
27
|
+
if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;
|
|
28
|
+
} else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) return true;
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
function computeLocalHash(projectDir, filePath) {
|
|
32
|
+
const fullPath = (0, node_path.join)(projectDir, filePath);
|
|
33
|
+
if (!(0, node_fs.existsSync)(fullPath)) return null;
|
|
34
|
+
try {
|
|
35
|
+
const content = (0, node_fs.readFileSync)(fullPath);
|
|
36
|
+
return (0, node_crypto.createHash)("sha256").update(content).digest("hex").substring(0, 16);
|
|
37
|
+
} catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function backupFiles(projectDir, filePaths) {
|
|
42
|
+
const filesToBackup = filePaths.filter((f) => (0, node_fs.existsSync)((0, node_path.join)(projectDir, f)));
|
|
43
|
+
if (filesToBackup.length === 0) return null;
|
|
44
|
+
const backupDir = (0, node_path.join)(projectDir, ".bos", "sync-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
|
|
45
|
+
for (const filePath of filesToBackup) {
|
|
46
|
+
const src = (0, node_path.join)(projectDir, filePath);
|
|
47
|
+
const dest = (0, node_path.join)(backupDir, filePath);
|
|
48
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(dest), { recursive: true });
|
|
49
|
+
(0, node_fs.copyFileSync)(src, dest);
|
|
50
|
+
}
|
|
51
|
+
return backupDir;
|
|
52
|
+
}
|
|
53
|
+
async function syncTemplate(projectDir, options) {
|
|
54
|
+
const localConfig = JSON.parse((0, node_fs.readFileSync)((0, node_path.join)(projectDir, "bos.config.json"), "utf-8"));
|
|
55
|
+
const extendsRef = localConfig.extends;
|
|
56
|
+
if (!extendsRef?.startsWith("bos://")) return {
|
|
57
|
+
status: "error",
|
|
58
|
+
updated: [],
|
|
59
|
+
skipped: [],
|
|
60
|
+
added: [],
|
|
61
|
+
error: "No extends field found in bos.config.json — cannot determine parent"
|
|
62
|
+
};
|
|
63
|
+
const extendsMatch = extendsRef.match(/^bos:\/\/([^/]+)\/(.+)$/);
|
|
64
|
+
if (!extendsMatch) return {
|
|
65
|
+
status: "error",
|
|
66
|
+
updated: [],
|
|
67
|
+
skipped: [],
|
|
68
|
+
added: [],
|
|
69
|
+
error: `Invalid extends reference: ${extendsRef}`
|
|
70
|
+
};
|
|
71
|
+
const extendsAccount = extendsMatch[1];
|
|
72
|
+
const extendsGateway = extendsMatch[2];
|
|
73
|
+
const { sourceDir, cleanup } = await require_cli_init.resolveSourceDir({
|
|
74
|
+
extendsAccount,
|
|
75
|
+
extendsGateway
|
|
76
|
+
});
|
|
77
|
+
try {
|
|
78
|
+
const patterns = await require_cli_init.readTemplatekeep(sourceDir);
|
|
79
|
+
if (patterns.length === 0) return {
|
|
80
|
+
status: "error",
|
|
81
|
+
updated: [],
|
|
82
|
+
skipped: [],
|
|
83
|
+
added: [],
|
|
84
|
+
error: "No .templatekeep found in template source"
|
|
85
|
+
};
|
|
86
|
+
const parentExcludes = await readTemplatesyncExclude(sourceDir);
|
|
87
|
+
const localExcludes = readLocalSyncExcludes(projectDir);
|
|
88
|
+
const excludePatterns = [...parentExcludes, ...localExcludes];
|
|
89
|
+
const allTemplateFiles = /* @__PURE__ */ new Set();
|
|
90
|
+
for (const pattern of patterns) {
|
|
91
|
+
const matches = await (0, glob.glob)(pattern, {
|
|
92
|
+
cwd: sourceDir,
|
|
93
|
+
nodir: true,
|
|
94
|
+
dot: true,
|
|
95
|
+
absolute: false
|
|
96
|
+
});
|
|
97
|
+
for (const match of matches) allTemplateFiles.add(match);
|
|
98
|
+
}
|
|
99
|
+
const snapshot = await require_snapshot.readSnapshot(projectDir);
|
|
100
|
+
const updated = [];
|
|
101
|
+
const skipped = [];
|
|
102
|
+
const added = [];
|
|
103
|
+
for (const filePath of allTemplateFiles) {
|
|
104
|
+
if (isExcluded(filePath, excludePatterns)) continue;
|
|
105
|
+
const localHash = computeLocalHash(projectDir, filePath);
|
|
106
|
+
const sourceContent = (0, node_fs.readFileSync)((0, node_path.join)(sourceDir, filePath));
|
|
107
|
+
const sourceHash = (0, node_crypto.createHash)("sha256").update(sourceContent).digest("hex").substring(0, 16);
|
|
108
|
+
if (localHash === null) {
|
|
109
|
+
added.push(filePath);
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (localHash === sourceHash) continue;
|
|
113
|
+
const snapshotHash = snapshot?.files[filePath];
|
|
114
|
+
if (snapshotHash === void 0) {
|
|
115
|
+
updated.push(filePath);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
if (localHash === snapshotHash) updated.push(filePath);
|
|
119
|
+
else if (options.force) updated.push(filePath);
|
|
120
|
+
else skipped.push(filePath);
|
|
121
|
+
}
|
|
122
|
+
if (options.dryRun) return {
|
|
123
|
+
status: "dry-run",
|
|
124
|
+
updated,
|
|
125
|
+
skipped,
|
|
126
|
+
added
|
|
127
|
+
};
|
|
128
|
+
const filesToWrite = [...updated, ...added].filter((f) => !isExcluded(f, excludePatterns));
|
|
129
|
+
if (filesToWrite.length > 0) {
|
|
130
|
+
backupFiles(projectDir, filesToWrite);
|
|
131
|
+
for (const filePath of filesToWrite) {
|
|
132
|
+
const src = (0, node_path.join)(sourceDir, filePath);
|
|
133
|
+
const dest = (0, node_path.join)(projectDir, filePath);
|
|
134
|
+
(0, node_fs.mkdirSync)((0, node_path.dirname)(dest), { recursive: true });
|
|
135
|
+
(0, node_fs.writeFileSync)(dest, (0, node_fs.readFileSync)(src));
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const newSnapshotFiles = {};
|
|
139
|
+
for (const filePath of allTemplateFiles) {
|
|
140
|
+
const src = (0, node_path.join)(sourceDir, filePath);
|
|
141
|
+
if (!(0, node_fs.lstatSync)(src).isFile()) continue;
|
|
142
|
+
const content = (0, node_fs.readFileSync)(src);
|
|
143
|
+
newSnapshotFiles[filePath] = (0, node_crypto.createHash)("sha256").update(content).digest("hex").substring(0, 16);
|
|
144
|
+
}
|
|
145
|
+
await require_snapshot.writeSnapshot(projectDir, {
|
|
146
|
+
parentRef: `bos://${extendsAccount}/${extendsGateway}`,
|
|
147
|
+
files: newSnapshotFiles
|
|
148
|
+
});
|
|
149
|
+
await require_cli_init.personalizeConfig(projectDir, {
|
|
150
|
+
extendsAccount,
|
|
151
|
+
extendsGateway,
|
|
152
|
+
account: localConfig.account || extendsAccount,
|
|
153
|
+
domain: localConfig.domain || extendsGateway,
|
|
154
|
+
workspaceOpts: { sourceDir }
|
|
155
|
+
});
|
|
156
|
+
if (!options.noInstall) await require_cli_init.runBunInstall(projectDir);
|
|
157
|
+
return {
|
|
158
|
+
status: "synced",
|
|
159
|
+
updated,
|
|
160
|
+
skipped,
|
|
161
|
+
added
|
|
162
|
+
};
|
|
163
|
+
} finally {
|
|
164
|
+
await cleanup();
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
//#endregion
|
|
169
|
+
exports.syncTemplate = syncTemplate;
|
|
170
|
+
//# sourceMappingURL=sync.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.cjs","names":["resolveSourceDir","readTemplatekeep","readSnapshot","writeSnapshot","personalizeConfig","runBunInstall"],"sources":["../../src/cli/sync.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n copyFileSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { glob } from \"glob\";\nimport type { SyncOptions, SyncResult } from \"../contract\";\nimport { personalizeConfig, readTemplatekeep, resolveSourceDir, runBunInstall } from \"./init\";\nimport { readSnapshot, writeSnapshot } from \"./snapshot\";\n\nfunction readExcludeFile(filePath: string): string[] {\n if (!existsSync(filePath)) return [];\n const content = readFileSync(filePath, \"utf-8\");\n return content\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0 && !line.startsWith(\"#\"));\n}\n\nexport async function readTemplatesyncExclude(sourceDir: string): Promise<string[]> {\n return readExcludeFile(join(sourceDir, \".templatesync-exclude\"));\n}\n\nexport function readLocalSyncExcludes(projectDir: string): string[] {\n return readExcludeFile(join(projectDir, \".bos\", \"sync-local-exclude\"));\n}\n\nfunction isExcluded(filePath: string, excludePatterns: string[]): boolean {\n for (const pattern of excludePatterns) {\n if (pattern.endsWith(\"/**\")) {\n const prefix = pattern.slice(0, -3);\n if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;\n } else if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n const slashIdx = filePath.indexOf(\"/\", prefix.length + 1);\n if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;\n } else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) {\n return true;\n }\n }\n return false;\n}\n\nfunction computeLocalHash(projectDir: string, filePath: string): string | null {\n const fullPath = join(projectDir, filePath);\n if (!existsSync(fullPath)) return null;\n try {\n const content = readFileSync(fullPath);\n return createHash(\"sha256\").update(content).digest(\"hex\").substring(0, 16);\n } catch {\n return null;\n }\n}\n\nfunction backupFiles(projectDir: string, filePaths: string[]): string | null {\n const filesToBackup = filePaths.filter((f) => existsSync(join(projectDir, f)));\n if (filesToBackup.length === 0) return null;\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupDir = join(projectDir, \".bos\", \"sync-backup\", timestamp);\n\n for (const filePath of filesToBackup) {\n const src = join(projectDir, filePath);\n const dest = join(backupDir, filePath);\n mkdirSync(dirname(dest), { recursive: true });\n copyFileSync(src, dest);\n }\n\n return backupDir;\n}\n\nexport async function syncTemplate(projectDir: string, options: SyncOptions): Promise<SyncResult> {\n const localConfig = JSON.parse(\n readFileSync(join(projectDir, \"bos.config.json\"), \"utf-8\"),\n ) as Record<string, unknown>;\n\n const extendsRef = localConfig.extends as string | undefined;\n if (!extendsRef?.startsWith(\"bos://\")) {\n return {\n status: \"error\",\n updated: [],\n skipped: [],\n added: [],\n error: \"No extends field found in bos.config.json — cannot determine parent\",\n };\n }\n\n const extendsMatch = extendsRef.match(/^bos:\\/\\/([^/]+)\\/(.+)$/);\n if (!extendsMatch) {\n return {\n status: \"error\",\n updated: [],\n skipped: [],\n added: [],\n error: `Invalid extends reference: ${extendsRef}`,\n };\n }\n\n const extendsAccount = extendsMatch[1];\n const extendsGateway = extendsMatch[2];\n\n const { sourceDir, cleanup } = await resolveSourceDir({ extendsAccount, extendsGateway });\n\n try {\n const patterns = await readTemplatekeep(sourceDir);\n if (patterns.length === 0) {\n return {\n status: \"error\",\n updated: [],\n skipped: [],\n added: [],\n error: \"No .templatekeep found in template source\",\n };\n }\n\n const parentExcludes = await readTemplatesyncExclude(sourceDir);\n const localExcludes = readLocalSyncExcludes(projectDir);\n const excludePatterns = [...parentExcludes, ...localExcludes];\n\n const allTemplateFiles = new Set<string>();\n for (const pattern of patterns) {\n const matches = await glob(pattern, {\n cwd: sourceDir,\n nodir: true,\n dot: true,\n absolute: false,\n });\n for (const match of matches) {\n allTemplateFiles.add(match);\n }\n }\n\n const snapshot = await readSnapshot(projectDir);\n\n const updated: string[] = [];\n const skipped: string[] = [];\n const added: string[] = [];\n\n for (const filePath of allTemplateFiles) {\n if (isExcluded(filePath, excludePatterns)) continue;\n\n const localHash = computeLocalHash(projectDir, filePath);\n const sourceContent = readFileSync(join(sourceDir, filePath));\n const sourceHash = createHash(\"sha256\").update(sourceContent).digest(\"hex\").substring(0, 16);\n\n if (localHash === null) {\n added.push(filePath);\n continue;\n }\n\n if (localHash === sourceHash) continue;\n\n const snapshotHash = snapshot?.files[filePath];\n\n if (snapshotHash === undefined) {\n updated.push(filePath);\n continue;\n }\n\n if (localHash === snapshotHash) {\n updated.push(filePath);\n } else {\n if (options.force) {\n updated.push(filePath);\n } else {\n skipped.push(filePath);\n }\n }\n }\n\n if (options.dryRun) {\n return {\n status: \"dry-run\",\n updated,\n skipped,\n added,\n };\n }\n\n const filesToWrite = [...updated, ...added].filter((f) => !isExcluded(f, excludePatterns));\n\n if (filesToWrite.length > 0) {\n backupFiles(projectDir, filesToWrite);\n\n for (const filePath of filesToWrite) {\n const src = join(sourceDir, filePath);\n const dest = join(projectDir, filePath);\n mkdirSync(dirname(dest), { recursive: true });\n writeFileSync(dest, readFileSync(src));\n }\n }\n\n const newSnapshotFiles: Record<string, string> = {};\n for (const filePath of allTemplateFiles) {\n const src = join(sourceDir, filePath);\n const stat = lstatSync(src);\n if (!stat.isFile()) continue;\n const content = readFileSync(src);\n newSnapshotFiles[filePath] = createHash(\"sha256\")\n .update(content)\n .digest(\"hex\")\n .substring(0, 16);\n }\n\n await writeSnapshot(projectDir, {\n parentRef: `bos://${extendsAccount}/${extendsGateway}`,\n files: newSnapshotFiles,\n });\n\n const account = (localConfig.account as string) || extendsAccount;\n const domain = (localConfig.domain as string) || extendsGateway;\n\n await personalizeConfig(projectDir, {\n extendsAccount,\n extendsGateway,\n account,\n domain,\n workspaceOpts: { sourceDir },\n });\n\n if (!options.noInstall) {\n await runBunInstall(projectDir);\n }\n\n return {\n status: \"synced\",\n updated,\n skipped,\n added,\n };\n } finally {\n await cleanup();\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { readSnapshot, writeSnapshot } from "./snapshot.mjs";
|
|
2
|
+
import { personalizeConfig, readTemplatekeep, resolveSourceDir, runBunInstall } from "./init.mjs";
|
|
3
|
+
import { copyFileSync, existsSync, lstatSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
import { glob } from "glob";
|
|
7
|
+
|
|
8
|
+
//#region src/cli/sync.ts
|
|
9
|
+
function readExcludeFile(filePath) {
|
|
10
|
+
if (!existsSync(filePath)) return [];
|
|
11
|
+
return readFileSync(filePath, "utf-8").split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
|
|
12
|
+
}
|
|
13
|
+
async function readTemplatesyncExclude(sourceDir) {
|
|
14
|
+
return readExcludeFile(join(sourceDir, ".templatesync-exclude"));
|
|
15
|
+
}
|
|
16
|
+
function readLocalSyncExcludes(projectDir) {
|
|
17
|
+
return readExcludeFile(join(projectDir, ".bos", "sync-local-exclude"));
|
|
18
|
+
}
|
|
19
|
+
function isExcluded(filePath, excludePatterns) {
|
|
20
|
+
for (const pattern of excludePatterns) if (pattern.endsWith("/**")) {
|
|
21
|
+
const prefix = pattern.slice(0, -3);
|
|
22
|
+
if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;
|
|
23
|
+
} else if (pattern.endsWith("/*")) {
|
|
24
|
+
const prefix = pattern.slice(0, -2);
|
|
25
|
+
const slashIdx = filePath.indexOf("/", prefix.length + 1);
|
|
26
|
+
if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;
|
|
27
|
+
} else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) return true;
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
function computeLocalHash(projectDir, filePath) {
|
|
31
|
+
const fullPath = join(projectDir, filePath);
|
|
32
|
+
if (!existsSync(fullPath)) return null;
|
|
33
|
+
try {
|
|
34
|
+
const content = readFileSync(fullPath);
|
|
35
|
+
return createHash("sha256").update(content).digest("hex").substring(0, 16);
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function backupFiles(projectDir, filePaths) {
|
|
41
|
+
const filesToBackup = filePaths.filter((f) => existsSync(join(projectDir, f)));
|
|
42
|
+
if (filesToBackup.length === 0) return null;
|
|
43
|
+
const backupDir = join(projectDir, ".bos", "sync-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
|
|
44
|
+
for (const filePath of filesToBackup) {
|
|
45
|
+
const src = join(projectDir, filePath);
|
|
46
|
+
const dest = join(backupDir, filePath);
|
|
47
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
48
|
+
copyFileSync(src, dest);
|
|
49
|
+
}
|
|
50
|
+
return backupDir;
|
|
51
|
+
}
|
|
52
|
+
async function syncTemplate(projectDir, options) {
|
|
53
|
+
const localConfig = JSON.parse(readFileSync(join(projectDir, "bos.config.json"), "utf-8"));
|
|
54
|
+
const extendsRef = localConfig.extends;
|
|
55
|
+
if (!extendsRef?.startsWith("bos://")) return {
|
|
56
|
+
status: "error",
|
|
57
|
+
updated: [],
|
|
58
|
+
skipped: [],
|
|
59
|
+
added: [],
|
|
60
|
+
error: "No extends field found in bos.config.json — cannot determine parent"
|
|
61
|
+
};
|
|
62
|
+
const extendsMatch = extendsRef.match(/^bos:\/\/([^/]+)\/(.+)$/);
|
|
63
|
+
if (!extendsMatch) return {
|
|
64
|
+
status: "error",
|
|
65
|
+
updated: [],
|
|
66
|
+
skipped: [],
|
|
67
|
+
added: [],
|
|
68
|
+
error: `Invalid extends reference: ${extendsRef}`
|
|
69
|
+
};
|
|
70
|
+
const extendsAccount = extendsMatch[1];
|
|
71
|
+
const extendsGateway = extendsMatch[2];
|
|
72
|
+
const { sourceDir, cleanup } = await resolveSourceDir({
|
|
73
|
+
extendsAccount,
|
|
74
|
+
extendsGateway
|
|
75
|
+
});
|
|
76
|
+
try {
|
|
77
|
+
const patterns = await readTemplatekeep(sourceDir);
|
|
78
|
+
if (patterns.length === 0) return {
|
|
79
|
+
status: "error",
|
|
80
|
+
updated: [],
|
|
81
|
+
skipped: [],
|
|
82
|
+
added: [],
|
|
83
|
+
error: "No .templatekeep found in template source"
|
|
84
|
+
};
|
|
85
|
+
const parentExcludes = await readTemplatesyncExclude(sourceDir);
|
|
86
|
+
const localExcludes = readLocalSyncExcludes(projectDir);
|
|
87
|
+
const excludePatterns = [...parentExcludes, ...localExcludes];
|
|
88
|
+
const allTemplateFiles = /* @__PURE__ */ new Set();
|
|
89
|
+
for (const pattern of patterns) {
|
|
90
|
+
const matches = await glob(pattern, {
|
|
91
|
+
cwd: sourceDir,
|
|
92
|
+
nodir: true,
|
|
93
|
+
dot: true,
|
|
94
|
+
absolute: false
|
|
95
|
+
});
|
|
96
|
+
for (const match of matches) allTemplateFiles.add(match);
|
|
97
|
+
}
|
|
98
|
+
const snapshot = await readSnapshot(projectDir);
|
|
99
|
+
const updated = [];
|
|
100
|
+
const skipped = [];
|
|
101
|
+
const added = [];
|
|
102
|
+
for (const filePath of allTemplateFiles) {
|
|
103
|
+
if (isExcluded(filePath, excludePatterns)) continue;
|
|
104
|
+
const localHash = computeLocalHash(projectDir, filePath);
|
|
105
|
+
const sourceContent = readFileSync(join(sourceDir, filePath));
|
|
106
|
+
const sourceHash = createHash("sha256").update(sourceContent).digest("hex").substring(0, 16);
|
|
107
|
+
if (localHash === null) {
|
|
108
|
+
added.push(filePath);
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (localHash === sourceHash) continue;
|
|
112
|
+
const snapshotHash = snapshot?.files[filePath];
|
|
113
|
+
if (snapshotHash === void 0) {
|
|
114
|
+
updated.push(filePath);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (localHash === snapshotHash) updated.push(filePath);
|
|
118
|
+
else if (options.force) updated.push(filePath);
|
|
119
|
+
else skipped.push(filePath);
|
|
120
|
+
}
|
|
121
|
+
if (options.dryRun) return {
|
|
122
|
+
status: "dry-run",
|
|
123
|
+
updated,
|
|
124
|
+
skipped,
|
|
125
|
+
added
|
|
126
|
+
};
|
|
127
|
+
const filesToWrite = [...updated, ...added].filter((f) => !isExcluded(f, excludePatterns));
|
|
128
|
+
if (filesToWrite.length > 0) {
|
|
129
|
+
backupFiles(projectDir, filesToWrite);
|
|
130
|
+
for (const filePath of filesToWrite) {
|
|
131
|
+
const src = join(sourceDir, filePath);
|
|
132
|
+
const dest = join(projectDir, filePath);
|
|
133
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
134
|
+
writeFileSync(dest, readFileSync(src));
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const newSnapshotFiles = {};
|
|
138
|
+
for (const filePath of allTemplateFiles) {
|
|
139
|
+
const src = join(sourceDir, filePath);
|
|
140
|
+
if (!lstatSync(src).isFile()) continue;
|
|
141
|
+
const content = readFileSync(src);
|
|
142
|
+
newSnapshotFiles[filePath] = createHash("sha256").update(content).digest("hex").substring(0, 16);
|
|
143
|
+
}
|
|
144
|
+
await writeSnapshot(projectDir, {
|
|
145
|
+
parentRef: `bos://${extendsAccount}/${extendsGateway}`,
|
|
146
|
+
files: newSnapshotFiles
|
|
147
|
+
});
|
|
148
|
+
await personalizeConfig(projectDir, {
|
|
149
|
+
extendsAccount,
|
|
150
|
+
extendsGateway,
|
|
151
|
+
account: localConfig.account || extendsAccount,
|
|
152
|
+
domain: localConfig.domain || extendsGateway,
|
|
153
|
+
workspaceOpts: { sourceDir }
|
|
154
|
+
});
|
|
155
|
+
if (!options.noInstall) await runBunInstall(projectDir);
|
|
156
|
+
return {
|
|
157
|
+
status: "synced",
|
|
158
|
+
updated,
|
|
159
|
+
skipped,
|
|
160
|
+
added
|
|
161
|
+
};
|
|
162
|
+
} finally {
|
|
163
|
+
await cleanup();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
export { syncTemplate };
|
|
169
|
+
//# sourceMappingURL=sync.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.mjs","names":[],"sources":["../../src/cli/sync.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport {\n copyFileSync,\n existsSync,\n lstatSync,\n mkdirSync,\n readFileSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { glob } from \"glob\";\nimport type { SyncOptions, SyncResult } from \"../contract\";\nimport { personalizeConfig, readTemplatekeep, resolveSourceDir, runBunInstall } from \"./init\";\nimport { readSnapshot, writeSnapshot } from \"./snapshot\";\n\nfunction readExcludeFile(filePath: string): string[] {\n if (!existsSync(filePath)) return [];\n const content = readFileSync(filePath, \"utf-8\");\n return content\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line.length > 0 && !line.startsWith(\"#\"));\n}\n\nexport async function readTemplatesyncExclude(sourceDir: string): Promise<string[]> {\n return readExcludeFile(join(sourceDir, \".templatesync-exclude\"));\n}\n\nexport function readLocalSyncExcludes(projectDir: string): string[] {\n return readExcludeFile(join(projectDir, \".bos\", \"sync-local-exclude\"));\n}\n\nfunction isExcluded(filePath: string, excludePatterns: string[]): boolean {\n for (const pattern of excludePatterns) {\n if (pattern.endsWith(\"/**\")) {\n const prefix = pattern.slice(0, -3);\n if (filePath.startsWith(`${prefix}/`) || filePath === prefix) return true;\n } else if (pattern.endsWith(\"/*\")) {\n const prefix = pattern.slice(0, -2);\n const slashIdx = filePath.indexOf(\"/\", prefix.length + 1);\n if (filePath.startsWith(`${prefix}/`) && slashIdx === -1) return true;\n } else if (filePath === pattern || filePath.startsWith(`${pattern}/`)) {\n return true;\n }\n }\n return false;\n}\n\nfunction computeLocalHash(projectDir: string, filePath: string): string | null {\n const fullPath = join(projectDir, filePath);\n if (!existsSync(fullPath)) return null;\n try {\n const content = readFileSync(fullPath);\n return createHash(\"sha256\").update(content).digest(\"hex\").substring(0, 16);\n } catch {\n return null;\n }\n}\n\nfunction backupFiles(projectDir: string, filePaths: string[]): string | null {\n const filesToBackup = filePaths.filter((f) => existsSync(join(projectDir, f)));\n if (filesToBackup.length === 0) return null;\n\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupDir = join(projectDir, \".bos\", \"sync-backup\", timestamp);\n\n for (const filePath of filesToBackup) {\n const src = join(projectDir, filePath);\n const dest = join(backupDir, filePath);\n mkdirSync(dirname(dest), { recursive: true });\n copyFileSync(src, dest);\n }\n\n return backupDir;\n}\n\nexport async function syncTemplate(projectDir: string, options: SyncOptions): Promise<SyncResult> {\n const localConfig = JSON.parse(\n readFileSync(join(projectDir, \"bos.config.json\"), \"utf-8\"),\n ) as Record<string, unknown>;\n\n const extendsRef = localConfig.extends as string | undefined;\n if (!extendsRef?.startsWith(\"bos://\")) {\n return {\n status: \"error\",\n updated: [],\n skipped: [],\n added: [],\n error: \"No extends field found in bos.config.json — cannot determine parent\",\n };\n }\n\n const extendsMatch = extendsRef.match(/^bos:\\/\\/([^/]+)\\/(.+)$/);\n if (!extendsMatch) {\n return {\n status: \"error\",\n updated: [],\n skipped: [],\n added: [],\n error: `Invalid extends reference: ${extendsRef}`,\n };\n }\n\n const extendsAccount = extendsMatch[1];\n const extendsGateway = extendsMatch[2];\n\n const { sourceDir, cleanup } = await resolveSourceDir({ extendsAccount, extendsGateway });\n\n try {\n const patterns = await readTemplatekeep(sourceDir);\n if (patterns.length === 0) {\n return {\n status: \"error\",\n updated: [],\n skipped: [],\n added: [],\n error: \"No .templatekeep found in template source\",\n };\n }\n\n const parentExcludes = await readTemplatesyncExclude(sourceDir);\n const localExcludes = readLocalSyncExcludes(projectDir);\n const excludePatterns = [...parentExcludes, ...localExcludes];\n\n const allTemplateFiles = new Set<string>();\n for (const pattern of patterns) {\n const matches = await glob(pattern, {\n cwd: sourceDir,\n nodir: true,\n dot: true,\n absolute: false,\n });\n for (const match of matches) {\n allTemplateFiles.add(match);\n }\n }\n\n const snapshot = await readSnapshot(projectDir);\n\n const updated: string[] = [];\n const skipped: string[] = [];\n const added: string[] = [];\n\n for (const filePath of allTemplateFiles) {\n if (isExcluded(filePath, excludePatterns)) continue;\n\n const localHash = computeLocalHash(projectDir, filePath);\n const sourceContent = readFileSync(join(sourceDir, filePath));\n const sourceHash = createHash(\"sha256\").update(sourceContent).digest(\"hex\").substring(0, 16);\n\n if (localHash === null) {\n added.push(filePath);\n continue;\n }\n\n if (localHash === sourceHash) continue;\n\n const snapshotHash = snapshot?.files[filePath];\n\n if (snapshotHash === undefined) {\n updated.push(filePath);\n continue;\n }\n\n if (localHash === snapshotHash) {\n updated.push(filePath);\n } else {\n if (options.force) {\n updated.push(filePath);\n } else {\n skipped.push(filePath);\n }\n }\n }\n\n if (options.dryRun) {\n return {\n status: \"dry-run\",\n updated,\n skipped,\n added,\n };\n }\n\n const filesToWrite = [...updated, ...added].filter((f) => !isExcluded(f, excludePatterns));\n\n if (filesToWrite.length > 0) {\n backupFiles(projectDir, filesToWrite);\n\n for (const filePath of filesToWrite) {\n const src = join(sourceDir, filePath);\n const dest = join(projectDir, filePath);\n mkdirSync(dirname(dest), { recursive: true });\n writeFileSync(dest, readFileSync(src));\n }\n }\n\n const newSnapshotFiles: Record<string, string> = {};\n for (const filePath of allTemplateFiles) {\n const src = join(sourceDir, filePath);\n const stat = lstatSync(src);\n if (!stat.isFile()) continue;\n const content = readFileSync(src);\n newSnapshotFiles[filePath] = createHash(\"sha256\")\n .update(content)\n .digest(\"hex\")\n .substring(0, 16);\n }\n\n await writeSnapshot(projectDir, {\n parentRef: `bos://${extendsAccount}/${extendsGateway}`,\n files: newSnapshotFiles,\n });\n\n const account = (localConfig.account as string) || extendsAccount;\n const domain = (localConfig.domain as string) || extendsGateway;\n\n await personalizeConfig(projectDir, {\n extendsAccount,\n extendsGateway,\n account,\n domain,\n workspaceOpts: { sourceDir },\n });\n\n if (!options.noInstall) {\n await runBunInstall(projectDir);\n }\n\n return {\n status: \"synced\",\n updated,\n skipped,\n added,\n };\n } finally {\n await cleanup();\n }\n}\n"],"mappings":"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"}
|