oh-my-customcode 0.31.1 → 0.32.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/index.js +359 -59
- package/dist/index.js +285 -35
- package/package.json +1 -1
- package/templates/.claude/hooks/scripts/session-env-check.sh +52 -0
- package/templates/manifest.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -12097,6 +12097,10 @@ var en_default = {
|
|
|
12097
12097
|
doctor: {
|
|
12098
12098
|
description: "Check and fix configuration issues",
|
|
12099
12099
|
fixOption: "Automatically fix issues that can be fixed",
|
|
12100
|
+
updatesOption: "Check for oh-my-customcode updates",
|
|
12101
|
+
updateAvailable: "Update available: v{{current}} → v{{latest}}. Run 'omcustom update' to apply.",
|
|
12102
|
+
updateUpToDate: "oh-my-customcode is up to date (v{{version}})",
|
|
12103
|
+
updateCheckFailed: "Update check failed ({{reason}})",
|
|
12100
12104
|
checking: "Running diagnostic checks...",
|
|
12101
12105
|
applyingFixes: "Applying fixes...",
|
|
12102
12106
|
fixing: "Fixing: {{name}}...",
|
|
@@ -12413,6 +12417,10 @@ var ko_default = {
|
|
|
12413
12417
|
doctor: {
|
|
12414
12418
|
description: "설정 문제 확인 및 수정",
|
|
12415
12419
|
fixOption: "자동으로 수정 가능한 문제 수정",
|
|
12420
|
+
updatesOption: "oh-my-customcode 업데이트 확인",
|
|
12421
|
+
updateAvailable: "업데이트 가능: v{{current}} → v{{latest}}. 'omcustom update'를 실행하여 적용하세요.",
|
|
12422
|
+
updateUpToDate: "oh-my-customcode가 최신 상태입니다 (v{{version}})",
|
|
12423
|
+
updateCheckFailed: "업데이트 확인 실패 ({{reason}})",
|
|
12416
12424
|
checking: "진단 검사 실행 중...",
|
|
12417
12425
|
applyingFixes: "수정 사항 적용 중...",
|
|
12418
12426
|
fixing: "수정 중: {{name}}...",
|
|
@@ -12865,8 +12873,9 @@ async function maybeHandleSelfUpdateForInit(options) {
|
|
|
12865
12873
|
}
|
|
12866
12874
|
|
|
12867
12875
|
// src/cli/doctor.ts
|
|
12868
|
-
import { constants, promises as fs } from "node:fs";
|
|
12876
|
+
import { constants, promises as fs, readFileSync as readFileSync2 } from "node:fs";
|
|
12869
12877
|
import path from "node:path";
|
|
12878
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12870
12879
|
|
|
12871
12880
|
// node_modules/yaml/dist/index.js
|
|
12872
12881
|
var composer = require_composer();
|
|
@@ -12978,6 +12987,16 @@ var MESSAGES = {
|
|
|
12978
12987
|
"install.entry_md_installed": "{{entry}} installed ({{language}})",
|
|
12979
12988
|
"install.entry_md_not_found": "{{entry}} template not found for {{language}}",
|
|
12980
12989
|
"install.entry_md_skipped": "{{entry}} skipped ({{reason}})",
|
|
12990
|
+
"install.lockfile_generated": "Lockfile generated ({{files}} files tracked)",
|
|
12991
|
+
"install.lockfile_failed": "Failed to generate lockfile: {{error}}",
|
|
12992
|
+
"lockfile.not_found": "Lockfile not found: {{path}}",
|
|
12993
|
+
"lockfile.invalid_version": "Invalid lockfile version: {{path}}",
|
|
12994
|
+
"lockfile.invalid_structure": "Invalid lockfile structure: {{path}}",
|
|
12995
|
+
"lockfile.read_failed": "Failed to read lockfile: {{path}} — {{error}}",
|
|
12996
|
+
"lockfile.written": "Lockfile written: {{path}}",
|
|
12997
|
+
"lockfile.component_dir_missing": "Component directory missing: {{path}}",
|
|
12998
|
+
"lockfile.hash_failed": "Failed to hash file: {{path}} — {{error}}",
|
|
12999
|
+
"lockfile.entry_added": "Lockfile entry added: {{path}} ({{component}})",
|
|
12981
13000
|
"update.start": "Checking for updates...",
|
|
12982
13001
|
"update.success": "Updated from {{from}} to {{to}}",
|
|
12983
13002
|
"update.components_synced": "Components synced (version {{version}}): {{components}}",
|
|
@@ -12987,6 +13006,8 @@ var MESSAGES = {
|
|
|
12987
13006
|
"update.dry_run": "Would update {{component}}",
|
|
12988
13007
|
"update.component_updated": "Updated {{component}}",
|
|
12989
13008
|
"update.file_applied": "Applied update to {{path}}",
|
|
13009
|
+
"update.lockfile_regenerated": "Lockfile regenerated ({{files}} files tracked)",
|
|
13010
|
+
"update.lockfile_failed": "Failed to regenerate lockfile: {{error}}",
|
|
12990
13011
|
"config.load_failed": "Failed to load config: {{error}}",
|
|
12991
13012
|
"config.not_found": "Config not found at {{path}}, using defaults",
|
|
12992
13013
|
"config.saved": "Config saved to {{path}}",
|
|
@@ -13010,6 +13031,16 @@ var MESSAGES = {
|
|
|
13010
13031
|
"install.entry_md_installed": "{{entry}} 설치 완료 ({{language}})",
|
|
13011
13032
|
"install.entry_md_not_found": "{{language}}용 {{entry}} 템플릿 없음",
|
|
13012
13033
|
"install.entry_md_skipped": "{{entry}} 건너뜀 ({{reason}})",
|
|
13034
|
+
"install.lockfile_generated": "잠금 파일 생성 완료 ({{files}}개 파일 추적)",
|
|
13035
|
+
"install.lockfile_failed": "잠금 파일 생성 실패: {{error}}",
|
|
13036
|
+
"lockfile.not_found": "잠금 파일 없음: {{path}}",
|
|
13037
|
+
"lockfile.invalid_version": "잠금 파일 버전 유효하지 않음: {{path}}",
|
|
13038
|
+
"lockfile.invalid_structure": "잠금 파일 구조 유효하지 않음: {{path}}",
|
|
13039
|
+
"lockfile.read_failed": "잠금 파일 읽기 실패: {{path}} — {{error}}",
|
|
13040
|
+
"lockfile.written": "잠금 파일 기록됨: {{path}}",
|
|
13041
|
+
"lockfile.component_dir_missing": "컴포넌트 디렉토리 없음: {{path}}",
|
|
13042
|
+
"lockfile.hash_failed": "파일 해시 실패: {{path}} — {{error}}",
|
|
13043
|
+
"lockfile.entry_added": "잠금 파일 항목 추가: {{path}} ({{component}})",
|
|
13013
13044
|
"update.start": "업데이트 확인 중...",
|
|
13014
13045
|
"update.success": "{{from}}에서 {{to}}로 업데이트 완료",
|
|
13015
13046
|
"update.components_synced": "컴포넌트 동기화 완료 (버전 {{version}}): {{components}}",
|
|
@@ -13019,6 +13050,8 @@ var MESSAGES = {
|
|
|
13019
13050
|
"update.dry_run": "{{component}} 업데이트 예정",
|
|
13020
13051
|
"update.component_updated": "{{component}} 업데이트 완료",
|
|
13021
13052
|
"update.file_applied": "{{path}} 업데이트 적용",
|
|
13053
|
+
"update.lockfile_regenerated": "잠금 파일 재생성 완료 ({{files}}개 파일 추적)",
|
|
13054
|
+
"update.lockfile_failed": "잠금 파일 재생성 실패: {{error}}",
|
|
13022
13055
|
"config.load_failed": "설정 로드 실패: {{error}}",
|
|
13023
13056
|
"config.not_found": "{{path}}에 설정 없음, 기본값 사용",
|
|
13024
13057
|
"config.saved": "설정 저장: {{path}}",
|
|
@@ -13713,12 +13746,51 @@ function printCheck(check) {
|
|
|
13713
13746
|
}
|
|
13714
13747
|
}
|
|
13715
13748
|
}
|
|
13749
|
+
function readCurrentVersion() {
|
|
13750
|
+
try {
|
|
13751
|
+
const __filename2 = fileURLToPath2(import.meta.url);
|
|
13752
|
+
const packageJsonPath = path.resolve(path.dirname(__filename2), "../../package.json");
|
|
13753
|
+
const packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
|
|
13754
|
+
return packageJson.version;
|
|
13755
|
+
} catch {
|
|
13756
|
+
return "0.0.0";
|
|
13757
|
+
}
|
|
13758
|
+
}
|
|
13759
|
+
function checkUpdateAvailable(currentVersion) {
|
|
13760
|
+
const result = checkSelfUpdate({ currentVersion });
|
|
13761
|
+
if (!result.checked) {
|
|
13762
|
+
return {
|
|
13763
|
+
name: "Update",
|
|
13764
|
+
status: "warn",
|
|
13765
|
+
message: i18n.t("cli.doctor.updateCheckFailed", { reason: result.reason ?? "unknown" }),
|
|
13766
|
+
fixable: false
|
|
13767
|
+
};
|
|
13768
|
+
}
|
|
13769
|
+
if (result.updateAvailable && result.latestVersion !== null) {
|
|
13770
|
+
return {
|
|
13771
|
+
name: "Update",
|
|
13772
|
+
status: "warn",
|
|
13773
|
+
message: i18n.t("cli.doctor.updateAvailable", {
|
|
13774
|
+
current: currentVersion,
|
|
13775
|
+
latest: result.latestVersion
|
|
13776
|
+
}),
|
|
13777
|
+
fixable: false,
|
|
13778
|
+
details: result.usedCache ? ["(checked from cache)"] : ["(checked from npm registry)"]
|
|
13779
|
+
};
|
|
13780
|
+
}
|
|
13781
|
+
return {
|
|
13782
|
+
name: "Update",
|
|
13783
|
+
status: "pass",
|
|
13784
|
+
message: i18n.t("cli.doctor.updateUpToDate", { version: currentVersion }),
|
|
13785
|
+
fixable: false
|
|
13786
|
+
};
|
|
13787
|
+
}
|
|
13716
13788
|
async function doctorCommand(options = {}) {
|
|
13717
13789
|
const targetDir = process.cwd();
|
|
13718
13790
|
console.log(i18n.t("cli.doctor.checking"));
|
|
13719
13791
|
console.log("");
|
|
13720
13792
|
const layout = getProviderLayout();
|
|
13721
|
-
|
|
13793
|
+
const baseChecks = await Promise.all([
|
|
13722
13794
|
checkEntryDoc(targetDir, layout.entryFile),
|
|
13723
13795
|
checkRules(targetDir, layout.rootDir),
|
|
13724
13796
|
checkAgents(targetDir, layout.rootDir),
|
|
@@ -13730,12 +13802,14 @@ async function doctorCommand(options = {}) {
|
|
|
13730
13802
|
checkContexts(targetDir, layout.rootDir),
|
|
13731
13803
|
checkCustomComponents(targetDir, layout.rootDir)
|
|
13732
13804
|
]);
|
|
13805
|
+
const checksWithUpdate = options.updates ? [...baseChecks, checkUpdateAvailable(readCurrentVersion())] : baseChecks;
|
|
13806
|
+
let checks = checksWithUpdate;
|
|
13733
13807
|
if (options.fix) {
|
|
13734
|
-
const hasFixableIssues =
|
|
13808
|
+
const hasFixableIssues = checksWithUpdate.some((c) => c.status === "fail" && c.fixable);
|
|
13735
13809
|
if (hasFixableIssues) {
|
|
13736
13810
|
console.log(i18n.t("cli.doctor.applyingFixes"));
|
|
13737
13811
|
console.log("");
|
|
13738
|
-
checks = await fixIssues(
|
|
13812
|
+
checks = await fixIssues(checksWithUpdate, targetDir, layout.rootDir);
|
|
13739
13813
|
console.log("");
|
|
13740
13814
|
}
|
|
13741
13815
|
}
|
|
@@ -13777,18 +13851,37 @@ async function doctorCommand(options = {}) {
|
|
|
13777
13851
|
}
|
|
13778
13852
|
|
|
13779
13853
|
// src/cli/init.ts
|
|
13780
|
-
import { join as
|
|
13854
|
+
import { join as join8 } from "node:path";
|
|
13781
13855
|
|
|
13782
13856
|
// src/core/installer.ts
|
|
13783
13857
|
init_fs();
|
|
13784
13858
|
import { readFile as fsReadFile, writeFile as fsWriteFile, rename } from "node:fs/promises";
|
|
13785
|
-
import { basename as basename2, join as
|
|
13859
|
+
import { basename as basename2, join as join6 } from "node:path";
|
|
13786
13860
|
|
|
13787
13861
|
// src/core/file-preservation.ts
|
|
13788
13862
|
init_fs();
|
|
13789
13863
|
import { basename, join as join4 } from "node:path";
|
|
13790
13864
|
var DEFAULT_CRITICAL_FILES = ["settings.json", "settings.local.json"];
|
|
13791
13865
|
var DEFAULT_CRITICAL_DIRECTORIES = ["agent-memory", "agent-memory-local"];
|
|
13866
|
+
var PROTECTED_FRAMEWORK_FILES = ["CLAUDE.md", "AGENTS.md"];
|
|
13867
|
+
var PROTECTED_RULE_PATTERNS = ["rules/MUST-*.md"];
|
|
13868
|
+
function isProtectedFile(relativePath) {
|
|
13869
|
+
const basename2 = relativePath.split("/").pop() ?? "";
|
|
13870
|
+
if (PROTECTED_FRAMEWORK_FILES.includes(basename2)) {
|
|
13871
|
+
return true;
|
|
13872
|
+
}
|
|
13873
|
+
for (const pattern of PROTECTED_RULE_PATTERNS) {
|
|
13874
|
+
if (matchesGlobPattern(relativePath, pattern)) {
|
|
13875
|
+
return true;
|
|
13876
|
+
}
|
|
13877
|
+
}
|
|
13878
|
+
return false;
|
|
13879
|
+
}
|
|
13880
|
+
function matchesGlobPattern(filePath, pattern) {
|
|
13881
|
+
const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, "[^/]*");
|
|
13882
|
+
const regex = new RegExp(`(^|/)${regexStr}$`);
|
|
13883
|
+
return regex.test(filePath);
|
|
13884
|
+
}
|
|
13792
13885
|
async function extractSingleFile(fileName, rootDir, tempDir, result) {
|
|
13793
13886
|
const srcPath = join4(rootDir, fileName);
|
|
13794
13887
|
const destPath = join4(tempDir, fileName);
|
|
@@ -14160,11 +14253,139 @@ function getDefaultWorkflow() {
|
|
|
14160
14253
|
};
|
|
14161
14254
|
}
|
|
14162
14255
|
|
|
14256
|
+
// src/core/lockfile.ts
|
|
14257
|
+
init_fs();
|
|
14258
|
+
import { createHash } from "node:crypto";
|
|
14259
|
+
import { createReadStream } from "node:fs";
|
|
14260
|
+
import { readdir, stat } from "node:fs/promises";
|
|
14261
|
+
import { join as join5, relative as relative2 } from "node:path";
|
|
14262
|
+
var LOCKFILE_NAME = ".omcustom.lock.json";
|
|
14263
|
+
var LOCKFILE_VERSION = 1;
|
|
14264
|
+
var LOCKFILE_COMPONENTS = [
|
|
14265
|
+
"rules",
|
|
14266
|
+
"agents",
|
|
14267
|
+
"skills",
|
|
14268
|
+
"hooks",
|
|
14269
|
+
"contexts",
|
|
14270
|
+
"ontology",
|
|
14271
|
+
"guides"
|
|
14272
|
+
];
|
|
14273
|
+
var COMPONENT_PATHS = LOCKFILE_COMPONENTS.map((component) => [getComponentPath(component), component]);
|
|
14274
|
+
function computeFileHash(filePath) {
|
|
14275
|
+
return new Promise((resolve2, reject) => {
|
|
14276
|
+
const hash = createHash("sha256");
|
|
14277
|
+
const stream = createReadStream(filePath);
|
|
14278
|
+
stream.on("error", (err) => {
|
|
14279
|
+
reject(err);
|
|
14280
|
+
});
|
|
14281
|
+
stream.on("data", (chunk) => {
|
|
14282
|
+
hash.update(chunk);
|
|
14283
|
+
});
|
|
14284
|
+
stream.on("end", () => {
|
|
14285
|
+
resolve2(hash.digest("hex"));
|
|
14286
|
+
});
|
|
14287
|
+
});
|
|
14288
|
+
}
|
|
14289
|
+
async function writeLockfile(targetDir, lockfile) {
|
|
14290
|
+
const lockfilePath = join5(targetDir, LOCKFILE_NAME);
|
|
14291
|
+
await writeJsonFile(lockfilePath, lockfile);
|
|
14292
|
+
debug("lockfile.written", { path: lockfilePath });
|
|
14293
|
+
}
|
|
14294
|
+
function resolveComponent(relativePath) {
|
|
14295
|
+
const normalized = relativePath.replace(/\\/g, "/");
|
|
14296
|
+
for (const [prefix, component] of COMPONENT_PATHS) {
|
|
14297
|
+
if (normalized === prefix || normalized.startsWith(`${prefix}/`)) {
|
|
14298
|
+
return component;
|
|
14299
|
+
}
|
|
14300
|
+
}
|
|
14301
|
+
return "unknown";
|
|
14302
|
+
}
|
|
14303
|
+
async function collectFiles(dir2, projectRoot, isTopLevel) {
|
|
14304
|
+
const results = [];
|
|
14305
|
+
let entries;
|
|
14306
|
+
try {
|
|
14307
|
+
entries = await readdir(dir2);
|
|
14308
|
+
} catch {
|
|
14309
|
+
return results;
|
|
14310
|
+
}
|
|
14311
|
+
for (const entry of entries) {
|
|
14312
|
+
if (isTopLevel && entry.startsWith(".") && entry !== ".claude") {
|
|
14313
|
+
continue;
|
|
14314
|
+
}
|
|
14315
|
+
const fullPath = join5(dir2, entry);
|
|
14316
|
+
let fileStat;
|
|
14317
|
+
try {
|
|
14318
|
+
fileStat = await stat(fullPath);
|
|
14319
|
+
} catch {
|
|
14320
|
+
continue;
|
|
14321
|
+
}
|
|
14322
|
+
if (fileStat.isDirectory()) {
|
|
14323
|
+
const subFiles = await collectFiles(fullPath, projectRoot, false);
|
|
14324
|
+
results.push(...subFiles);
|
|
14325
|
+
} else if (fileStat.isFile()) {
|
|
14326
|
+
results.push(fullPath);
|
|
14327
|
+
}
|
|
14328
|
+
}
|
|
14329
|
+
return results;
|
|
14330
|
+
}
|
|
14331
|
+
async function generateLockfile(targetDir, generatorVersion, templateVersion) {
|
|
14332
|
+
const files = {};
|
|
14333
|
+
const componentRoots = COMPONENT_PATHS.map(([prefix]) => join5(targetDir, prefix));
|
|
14334
|
+
for (const componentRoot of componentRoots) {
|
|
14335
|
+
const exists2 = await fileExists(componentRoot);
|
|
14336
|
+
if (!exists2) {
|
|
14337
|
+
debug("lockfile.component_dir_missing", { path: componentRoot });
|
|
14338
|
+
continue;
|
|
14339
|
+
}
|
|
14340
|
+
const allFiles = await collectFiles(componentRoot, targetDir, false);
|
|
14341
|
+
for (const absolutePath of allFiles) {
|
|
14342
|
+
const relativePath = relative2(targetDir, absolutePath).replace(/\\/g, "/");
|
|
14343
|
+
let hash;
|
|
14344
|
+
let size;
|
|
14345
|
+
try {
|
|
14346
|
+
hash = await computeFileHash(absolutePath);
|
|
14347
|
+
const fileStat = await stat(absolutePath);
|
|
14348
|
+
size = fileStat.size;
|
|
14349
|
+
} catch (err) {
|
|
14350
|
+
warn("lockfile.hash_failed", { path: absolutePath, error: String(err) });
|
|
14351
|
+
continue;
|
|
14352
|
+
}
|
|
14353
|
+
const component = resolveComponent(relativePath);
|
|
14354
|
+
files[relativePath] = {
|
|
14355
|
+
templateHash: hash,
|
|
14356
|
+
size,
|
|
14357
|
+
component
|
|
14358
|
+
};
|
|
14359
|
+
debug("lockfile.entry_added", { path: relativePath, component });
|
|
14360
|
+
}
|
|
14361
|
+
}
|
|
14362
|
+
return {
|
|
14363
|
+
lockfileVersion: LOCKFILE_VERSION,
|
|
14364
|
+
generatorVersion,
|
|
14365
|
+
generatedAt: new Date().toISOString(),
|
|
14366
|
+
templateVersion,
|
|
14367
|
+
files
|
|
14368
|
+
};
|
|
14369
|
+
}
|
|
14370
|
+
async function generateAndWriteLockfileForDir(targetDir) {
|
|
14371
|
+
try {
|
|
14372
|
+
const packageRoot = getPackageRoot();
|
|
14373
|
+
const manifest = await readJsonFile(join5(packageRoot, "templates", "manifest.json"));
|
|
14374
|
+
const { version: generatorVersion } = await readJsonFile(join5(packageRoot, "package.json"));
|
|
14375
|
+
const lockfile = await generateLockfile(targetDir, generatorVersion, manifest.version);
|
|
14376
|
+
await writeLockfile(targetDir, lockfile);
|
|
14377
|
+
return { fileCount: Object.keys(lockfile.files).length };
|
|
14378
|
+
} catch (err) {
|
|
14379
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
14380
|
+
return { fileCount: 0, warning: `Lockfile generation failed: ${msg}` };
|
|
14381
|
+
}
|
|
14382
|
+
}
|
|
14383
|
+
|
|
14163
14384
|
// src/core/installer.ts
|
|
14164
14385
|
var DEFAULT_LANGUAGE2 = "en";
|
|
14165
14386
|
function getTemplateDir() {
|
|
14166
14387
|
const packageRoot = getPackageRoot();
|
|
14167
|
-
return
|
|
14388
|
+
return join6(packageRoot, "templates");
|
|
14168
14389
|
}
|
|
14169
14390
|
function createInstallResult(targetDir) {
|
|
14170
14391
|
return {
|
|
@@ -14186,7 +14407,7 @@ async function handleBackup(targetDir, shouldBackup, result) {
|
|
|
14186
14407
|
if (!shouldBackup)
|
|
14187
14408
|
return null;
|
|
14188
14409
|
const layout = getProviderLayout();
|
|
14189
|
-
const rootDir =
|
|
14410
|
+
const rootDir = join6(targetDir, layout.rootDir);
|
|
14190
14411
|
let preservation = null;
|
|
14191
14412
|
if (await fileExists(rootDir)) {
|
|
14192
14413
|
const { createTempDir: createTempDir2 } = await Promise.resolve().then(() => (init_fs(), exports_fs));
|
|
@@ -14243,8 +14464,8 @@ async function installSingleComponent(targetDir, component, options, result) {
|
|
|
14243
14464
|
}
|
|
14244
14465
|
async function installStatusline(targetDir, options, _result) {
|
|
14245
14466
|
const layout = getProviderLayout();
|
|
14246
|
-
const srcPath = resolveTemplatePath(
|
|
14247
|
-
const destPath =
|
|
14467
|
+
const srcPath = resolveTemplatePath(join6(layout.rootDir, "statusline.sh"));
|
|
14468
|
+
const destPath = join6(targetDir, layout.rootDir, "statusline.sh");
|
|
14248
14469
|
if (!await fileExists(srcPath)) {
|
|
14249
14470
|
debug("install.statusline_not_found", { path: srcPath });
|
|
14250
14471
|
return;
|
|
@@ -14262,7 +14483,7 @@ async function installStatusline(targetDir, options, _result) {
|
|
|
14262
14483
|
}
|
|
14263
14484
|
async function installSettingsLocal(targetDir, result) {
|
|
14264
14485
|
const layout = getProviderLayout();
|
|
14265
|
-
const settingsPath =
|
|
14486
|
+
const settingsPath = join6(targetDir, layout.rootDir, "settings.local.json");
|
|
14266
14487
|
const statusLineConfig = {
|
|
14267
14488
|
statusLine: {
|
|
14268
14489
|
type: "command",
|
|
@@ -14319,7 +14540,7 @@ async function install(options) {
|
|
|
14319
14540
|
await installEntryDocWithTracking(options.targetDir, options, result);
|
|
14320
14541
|
if (preservation) {
|
|
14321
14542
|
const layout = getProviderLayout();
|
|
14322
|
-
const rootDir =
|
|
14543
|
+
const rootDir = join6(options.targetDir, layout.rootDir);
|
|
14323
14544
|
const restoration = await restoreCriticalFiles(rootDir, preservation);
|
|
14324
14545
|
if (restoration.restoredFiles.length > 0 || restoration.restoredDirs.length > 0) {
|
|
14325
14546
|
info("install.restored", {
|
|
@@ -14335,6 +14556,13 @@ async function install(options) {
|
|
|
14335
14556
|
await cleanupPreservation(preservation.tempDir);
|
|
14336
14557
|
}
|
|
14337
14558
|
await updateInstallConfig(options.targetDir, options, result.installedComponents);
|
|
14559
|
+
const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir);
|
|
14560
|
+
if (lockfileResult.warning) {
|
|
14561
|
+
result.warnings.push(lockfileResult.warning);
|
|
14562
|
+
warn("install.lockfile_failed", { error: lockfileResult.warning });
|
|
14563
|
+
} else {
|
|
14564
|
+
info("install.lockfile_generated", { files: String(lockfileResult.fileCount) });
|
|
14565
|
+
}
|
|
14338
14566
|
result.success = true;
|
|
14339
14567
|
success("install.success");
|
|
14340
14568
|
} catch (err) {
|
|
@@ -14352,7 +14580,7 @@ async function installComponent(targetDir, component, options) {
|
|
|
14352
14580
|
return false;
|
|
14353
14581
|
}
|
|
14354
14582
|
const templatePath = getComponentPath(component);
|
|
14355
|
-
const destPath =
|
|
14583
|
+
const destPath = join6(targetDir, templatePath);
|
|
14356
14584
|
const destExists = await fileExists(destPath);
|
|
14357
14585
|
if (destExists && !options.force && !options.backup) {
|
|
14358
14586
|
debug("install.component_skipped", { component });
|
|
@@ -14380,7 +14608,7 @@ async function installEntryDoc(targetDir, language, overwrite = false) {
|
|
|
14380
14608
|
const layout = getProviderLayout();
|
|
14381
14609
|
const templateFile = getEntryTemplateName(language);
|
|
14382
14610
|
const srcPath = resolveTemplatePath(templateFile);
|
|
14383
|
-
const destPath =
|
|
14611
|
+
const destPath = join6(targetDir, layout.entryFile);
|
|
14384
14612
|
if (!await fileExists(srcPath)) {
|
|
14385
14613
|
warn("install.entry_md_not_found", { language, path: srcPath, entry: layout.entryFile });
|
|
14386
14614
|
return false;
|
|
@@ -14401,7 +14629,7 @@ async function installEntryDoc(targetDir, language, overwrite = false) {
|
|
|
14401
14629
|
}
|
|
14402
14630
|
async function backupExisting(sourcePath, backupDir) {
|
|
14403
14631
|
const name = basename2(sourcePath);
|
|
14404
|
-
const backupPath =
|
|
14632
|
+
const backupPath = join6(backupDir, name);
|
|
14405
14633
|
await rename(sourcePath, backupPath);
|
|
14406
14634
|
return backupPath;
|
|
14407
14635
|
}
|
|
@@ -14410,7 +14638,7 @@ async function checkExistingPaths(targetDir) {
|
|
|
14410
14638
|
const pathsToCheck = [layout.entryFile, layout.rootDir, "guides"];
|
|
14411
14639
|
const existingPaths = [];
|
|
14412
14640
|
for (const relativePath of pathsToCheck) {
|
|
14413
|
-
const fullPath =
|
|
14641
|
+
const fullPath = join6(targetDir, relativePath);
|
|
14414
14642
|
if (await fileExists(fullPath)) {
|
|
14415
14643
|
existingPaths.push(relativePath);
|
|
14416
14644
|
}
|
|
@@ -14424,11 +14652,11 @@ async function backupExistingInstallation(targetDir) {
|
|
|
14424
14652
|
return [];
|
|
14425
14653
|
}
|
|
14426
14654
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
14427
|
-
const backupDir =
|
|
14655
|
+
const backupDir = join6(targetDir, `${layout.backupDirPrefix}${timestamp}`);
|
|
14428
14656
|
await ensureDirectory(backupDir);
|
|
14429
14657
|
const backedUpPaths = [];
|
|
14430
14658
|
for (const relativePath of existingPaths) {
|
|
14431
|
-
const fullPath =
|
|
14659
|
+
const fullPath = join6(targetDir, relativePath);
|
|
14432
14660
|
try {
|
|
14433
14661
|
const backupPath = await backupExisting(fullPath, backupDir);
|
|
14434
14662
|
backedUpPaths.push(backupPath);
|
|
@@ -14445,12 +14673,12 @@ async function backupExistingInstallation(targetDir) {
|
|
|
14445
14673
|
init_fs();
|
|
14446
14674
|
import { execSync as execSync3 } from "node:child_process";
|
|
14447
14675
|
import { writeFile } from "node:fs/promises";
|
|
14448
|
-
import { join as
|
|
14676
|
+
import { join as join7 } from "node:path";
|
|
14449
14677
|
async function generateMCPConfig(targetDir) {
|
|
14450
14678
|
const layout = getProviderLayout();
|
|
14451
|
-
const mcpConfigPath =
|
|
14452
|
-
const ontologyDir =
|
|
14453
|
-
const ontologyExists = await fileExists(
|
|
14679
|
+
const mcpConfigPath = join7(targetDir, ".mcp.json");
|
|
14680
|
+
const ontologyDir = join7(layout.rootDir, "ontology");
|
|
14681
|
+
const ontologyExists = await fileExists(join7(targetDir, ontologyDir));
|
|
14454
14682
|
if (!ontologyExists) {
|
|
14455
14683
|
return;
|
|
14456
14684
|
}
|
|
@@ -14507,7 +14735,7 @@ async function checkUvAvailable() {
|
|
|
14507
14735
|
init_fs();
|
|
14508
14736
|
async function checkExistingInstallation(targetDir) {
|
|
14509
14737
|
const layout = getProviderLayout();
|
|
14510
|
-
const rootDir =
|
|
14738
|
+
const rootDir = join8(targetDir, layout.rootDir);
|
|
14511
14739
|
return fileExists(rootDir);
|
|
14512
14740
|
}
|
|
14513
14741
|
var PROVIDER_SUBDIR_COMPONENTS = new Set([
|
|
@@ -14521,13 +14749,13 @@ var PROVIDER_SUBDIR_COMPONENTS = new Set([
|
|
|
14521
14749
|
function componentToPath(targetDir, component) {
|
|
14522
14750
|
if (component === "entry-md") {
|
|
14523
14751
|
const layout = getProviderLayout();
|
|
14524
|
-
return
|
|
14752
|
+
return join8(targetDir, layout.entryFile);
|
|
14525
14753
|
}
|
|
14526
14754
|
if (PROVIDER_SUBDIR_COMPONENTS.has(component)) {
|
|
14527
14755
|
const layout = getProviderLayout();
|
|
14528
|
-
return
|
|
14756
|
+
return join8(targetDir, layout.rootDir, component);
|
|
14529
14757
|
}
|
|
14530
|
-
return
|
|
14758
|
+
return join8(targetDir, component);
|
|
14531
14759
|
}
|
|
14532
14760
|
function buildInstalledPaths(targetDir, components) {
|
|
14533
14761
|
return components.map((component) => componentToPath(targetDir, component));
|
|
@@ -14607,7 +14835,7 @@ async function initCommand(options) {
|
|
|
14607
14835
|
}
|
|
14608
14836
|
|
|
14609
14837
|
// src/cli/list.ts
|
|
14610
|
-
import { basename as basename3, dirname as dirname3, join as
|
|
14838
|
+
import { basename as basename3, dirname as dirname3, join as join9, relative as relative3 } from "node:path";
|
|
14611
14839
|
init_fs();
|
|
14612
14840
|
var ALLOWED_TOP_LEVEL_KEYS = new Set(["name", "type", "description", "version", "category"]);
|
|
14613
14841
|
function parseKeyValue(line) {
|
|
@@ -14672,12 +14900,12 @@ function extractAgentTypeFromFilename(filename) {
|
|
|
14672
14900
|
return prefixMap[prefix] || "unknown";
|
|
14673
14901
|
}
|
|
14674
14902
|
function extractSkillCategoryFromPath(skillPath, baseDir, rootDir) {
|
|
14675
|
-
const relativePath =
|
|
14903
|
+
const relativePath = relative3(join9(baseDir, rootDir, "skills"), skillPath);
|
|
14676
14904
|
const parts = relativePath.split("/").filter(Boolean);
|
|
14677
14905
|
return parts[0] || "unknown";
|
|
14678
14906
|
}
|
|
14679
14907
|
function extractGuideCategoryFromPath(guidePath, baseDir) {
|
|
14680
|
-
const relativePath =
|
|
14908
|
+
const relativePath = relative3(join9(baseDir, "guides"), guidePath);
|
|
14681
14909
|
const parts = relativePath.split("/").filter(Boolean);
|
|
14682
14910
|
return parts[0] || "unknown";
|
|
14683
14911
|
}
|
|
@@ -14771,7 +14999,7 @@ async function tryExtractMarkdownDescription(mdPath, options = {}) {
|
|
|
14771
14999
|
}
|
|
14772
15000
|
}
|
|
14773
15001
|
async function getAgents(targetDir, rootDir = ".claude", config) {
|
|
14774
|
-
const agentsDir =
|
|
15002
|
+
const agentsDir = join9(targetDir, rootDir, "agents");
|
|
14775
15003
|
if (!await fileExists(agentsDir))
|
|
14776
15004
|
return [];
|
|
14777
15005
|
try {
|
|
@@ -14783,7 +15011,7 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
|
|
|
14783
15011
|
const filename = basename3(agentMdPath);
|
|
14784
15012
|
const name = basename3(filename, ".md");
|
|
14785
15013
|
const description = await tryExtractMarkdownDescription(agentMdPath);
|
|
14786
|
-
const relativePath =
|
|
15014
|
+
const relativePath = relative3(targetDir, agentMdPath);
|
|
14787
15015
|
return {
|
|
14788
15016
|
name,
|
|
14789
15017
|
type: extractAgentTypeFromFilename(filename),
|
|
@@ -14799,7 +15027,7 @@ async function getAgents(targetDir, rootDir = ".claude", config) {
|
|
|
14799
15027
|
}
|
|
14800
15028
|
}
|
|
14801
15029
|
async function getSkills(targetDir, rootDir = ".claude", config) {
|
|
14802
|
-
const skillsDir =
|
|
15030
|
+
const skillsDir = join9(targetDir, rootDir, "skills");
|
|
14803
15031
|
if (!await fileExists(skillsDir))
|
|
14804
15032
|
return [];
|
|
14805
15033
|
try {
|
|
@@ -14809,9 +15037,9 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
|
|
|
14809
15037
|
const skillMdFiles = await listFiles(skillsDir, { recursive: true, pattern: "SKILL.md" });
|
|
14810
15038
|
const skills = await Promise.all(skillMdFiles.map(async (skillMdPath) => {
|
|
14811
15039
|
const skillDir = dirname3(skillMdPath);
|
|
14812
|
-
const indexYamlPath =
|
|
15040
|
+
const indexYamlPath = join9(skillDir, "index.yaml");
|
|
14813
15041
|
const { description, version } = await tryReadIndexYamlMetadata(indexYamlPath);
|
|
14814
|
-
const relativePath =
|
|
15042
|
+
const relativePath = relative3(targetDir, skillDir);
|
|
14815
15043
|
return {
|
|
14816
15044
|
name: basename3(skillDir),
|
|
14817
15045
|
type: "skill",
|
|
@@ -14828,7 +15056,7 @@ async function getSkills(targetDir, rootDir = ".claude", config) {
|
|
|
14828
15056
|
}
|
|
14829
15057
|
}
|
|
14830
15058
|
async function getGuides(targetDir, config) {
|
|
14831
|
-
const guidesDir =
|
|
15059
|
+
const guidesDir = join9(targetDir, "guides");
|
|
14832
15060
|
if (!await fileExists(guidesDir))
|
|
14833
15061
|
return [];
|
|
14834
15062
|
try {
|
|
@@ -14838,7 +15066,7 @@ async function getGuides(targetDir, config) {
|
|
|
14838
15066
|
const guideMdFiles = await listFiles(guidesDir, { recursive: true, pattern: "*.md" });
|
|
14839
15067
|
const guides = await Promise.all(guideMdFiles.map(async (guideMdPath) => {
|
|
14840
15068
|
const description = await tryExtractMarkdownDescription(guideMdPath, { maxLength: 100 });
|
|
14841
|
-
const relativePath =
|
|
15069
|
+
const relativePath = relative3(targetDir, guideMdPath);
|
|
14842
15070
|
return {
|
|
14843
15071
|
name: basename3(guideMdPath, ".md"),
|
|
14844
15072
|
type: "guide",
|
|
@@ -14855,7 +15083,7 @@ async function getGuides(targetDir, config) {
|
|
|
14855
15083
|
}
|
|
14856
15084
|
var RULE_PRIORITY_ORDER = { MUST: 0, SHOULD: 1, MAY: 2 };
|
|
14857
15085
|
async function getRules(targetDir, rootDir = ".claude", config) {
|
|
14858
|
-
const rulesDir =
|
|
15086
|
+
const rulesDir = join9(targetDir, rootDir, "rules");
|
|
14859
15087
|
if (!await fileExists(rulesDir))
|
|
14860
15088
|
return [];
|
|
14861
15089
|
try {
|
|
@@ -14868,7 +15096,7 @@ async function getRules(targetDir, rootDir = ".claude", config) {
|
|
|
14868
15096
|
const description = await tryExtractMarkdownDescription(ruleMdPath, {
|
|
14869
15097
|
cleanFormatting: true
|
|
14870
15098
|
});
|
|
14871
|
-
const relativePath =
|
|
15099
|
+
const relativePath = relative3(targetDir, ruleMdPath);
|
|
14872
15100
|
return {
|
|
14873
15101
|
name: basename3(ruleMdPath, ".md"),
|
|
14874
15102
|
type: extractRulePriorityFromFilename(filename),
|
|
@@ -14927,7 +15155,7 @@ function formatAsJson(components) {
|
|
|
14927
15155
|
console.log(JSON.stringify(components, null, 2));
|
|
14928
15156
|
}
|
|
14929
15157
|
async function getHooks(targetDir, rootDir = ".claude") {
|
|
14930
|
-
const hooksDir =
|
|
15158
|
+
const hooksDir = join9(targetDir, rootDir, "hooks");
|
|
14931
15159
|
if (!await fileExists(hooksDir))
|
|
14932
15160
|
return [];
|
|
14933
15161
|
try {
|
|
@@ -14938,14 +15166,14 @@ async function getHooks(targetDir, rootDir = ".claude") {
|
|
|
14938
15166
|
return allFiles.map((hookPath) => ({
|
|
14939
15167
|
name: basename3(hookPath),
|
|
14940
15168
|
type: "hook",
|
|
14941
|
-
path:
|
|
15169
|
+
path: relative3(targetDir, hookPath)
|
|
14942
15170
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
14943
15171
|
} catch {
|
|
14944
15172
|
return [];
|
|
14945
15173
|
}
|
|
14946
15174
|
}
|
|
14947
15175
|
async function getContexts(targetDir, rootDir = ".claude") {
|
|
14948
|
-
const contextsDir =
|
|
15176
|
+
const contextsDir = join9(targetDir, rootDir, "contexts");
|
|
14949
15177
|
if (!await fileExists(contextsDir))
|
|
14950
15178
|
return [];
|
|
14951
15179
|
try {
|
|
@@ -14958,7 +15186,7 @@ async function getContexts(targetDir, rootDir = ".claude") {
|
|
|
14958
15186
|
return {
|
|
14959
15187
|
name: basename3(ctxPath, ext),
|
|
14960
15188
|
type: "context",
|
|
14961
|
-
path:
|
|
15189
|
+
path: relative3(targetDir, ctxPath),
|
|
14962
15190
|
description
|
|
14963
15191
|
};
|
|
14964
15192
|
}));
|
|
@@ -15335,7 +15563,7 @@ async function securityCommand(_options = {}) {
|
|
|
15335
15563
|
|
|
15336
15564
|
// src/core/updater.ts
|
|
15337
15565
|
init_fs();
|
|
15338
|
-
import { join as
|
|
15566
|
+
import { join as join10 } from "node:path";
|
|
15339
15567
|
|
|
15340
15568
|
// src/core/entry-merger.ts
|
|
15341
15569
|
var MANAGED_START = "<!-- omcustom:start -->";
|
|
@@ -15584,7 +15812,7 @@ function resolveCustomizations(customizations, configPreserveFiles, targetDir) {
|
|
|
15584
15812
|
}
|
|
15585
15813
|
async function updateEntryDoc(targetDir, config, options) {
|
|
15586
15814
|
const layout = getProviderLayout();
|
|
15587
|
-
const entryPath =
|
|
15815
|
+
const entryPath = join10(targetDir, layout.entryFile);
|
|
15588
15816
|
const templateName = getEntryTemplateName2(config.language);
|
|
15589
15817
|
const templatePath = resolveTemplatePath(templateName);
|
|
15590
15818
|
if (!await fileExists(templatePath)) {
|
|
@@ -15664,6 +15892,15 @@ async function update(options) {
|
|
|
15664
15892
|
const components = options.components || getAllUpdateComponents();
|
|
15665
15893
|
await updateAllComponents(options.targetDir, components, updateCheck, customizations, options, result, config);
|
|
15666
15894
|
await runFullUpdatePostProcessing(options, result, config);
|
|
15895
|
+
const lockfileResult = await generateAndWriteLockfileForDir(options.targetDir);
|
|
15896
|
+
if (lockfileResult.warning) {
|
|
15897
|
+
result.warnings.push(lockfileResult.warning);
|
|
15898
|
+
warn("update.lockfile_failed", { error: lockfileResult.warning });
|
|
15899
|
+
} else {
|
|
15900
|
+
debug("update.lockfile_regenerated", {
|
|
15901
|
+
files: String(lockfileResult.fileCount)
|
|
15902
|
+
});
|
|
15903
|
+
}
|
|
15667
15904
|
} catch (err) {
|
|
15668
15905
|
const message = err instanceof Error ? err.message : String(err);
|
|
15669
15906
|
result.error = message;
|
|
@@ -15714,11 +15951,55 @@ async function componentHasUpdate(_targetDir, component, config) {
|
|
|
15714
15951
|
const latestVersion = await getLatestVersion();
|
|
15715
15952
|
return installedVersion !== latestVersion;
|
|
15716
15953
|
}
|
|
15954
|
+
async function collectProtectedSkipPaths(srcPath, destPath, componentPath, forceOverwriteAll) {
|
|
15955
|
+
if (forceOverwriteAll) {
|
|
15956
|
+
const warnedPaths = await findProtectedFilesInDir(srcPath, componentPath);
|
|
15957
|
+
return { skipPaths: [], warnedPaths };
|
|
15958
|
+
}
|
|
15959
|
+
const protectedRelative = await findProtectedFilesInDir(srcPath, componentPath);
|
|
15960
|
+
const path3 = await import("node:path");
|
|
15961
|
+
const skipPaths = protectedRelative.map((p) => path3.relative(destPath, join10(destPath, p)));
|
|
15962
|
+
return { skipPaths, warnedPaths: protectedRelative };
|
|
15963
|
+
}
|
|
15964
|
+
function isEntryProtected(relPath, componentRelativePrefix) {
|
|
15965
|
+
if (isProtectedFile(relPath)) {
|
|
15966
|
+
return true;
|
|
15967
|
+
}
|
|
15968
|
+
const componentPrefixed = componentRelativePrefix ? `${componentRelativePrefix}/${relPath}` : relPath;
|
|
15969
|
+
return isProtectedFile(componentPrefixed);
|
|
15970
|
+
}
|
|
15971
|
+
async function safeReaddir(dir2, fs3) {
|
|
15972
|
+
try {
|
|
15973
|
+
return await fs3.readdir(dir2, { withFileTypes: true });
|
|
15974
|
+
} catch {
|
|
15975
|
+
return [];
|
|
15976
|
+
}
|
|
15977
|
+
}
|
|
15978
|
+
async function findProtectedFilesInDir(dirPath, componentRelativePrefix) {
|
|
15979
|
+
const fs3 = await import("node:fs/promises");
|
|
15980
|
+
const path3 = await import("node:path");
|
|
15981
|
+
const protected_ = [];
|
|
15982
|
+
const queue = [{ dir: dirPath, relDir: "" }];
|
|
15983
|
+
while (queue.length > 0) {
|
|
15984
|
+
const { dir: dir2, relDir } = queue.shift();
|
|
15985
|
+
const entries = await safeReaddir(dir2, fs3);
|
|
15986
|
+
for (const entry of entries) {
|
|
15987
|
+
const relPath = relDir ? `${relDir}/${entry.name}` : entry.name;
|
|
15988
|
+
const fullPath = path3.join(dir2, entry.name);
|
|
15989
|
+
if (entry.isDirectory()) {
|
|
15990
|
+
queue.push({ dir: fullPath, relDir: relPath });
|
|
15991
|
+
} else if (entry.isFile() && isEntryProtected(relPath, componentRelativePrefix)) {
|
|
15992
|
+
protected_.push(relPath);
|
|
15993
|
+
}
|
|
15994
|
+
}
|
|
15995
|
+
}
|
|
15996
|
+
return protected_;
|
|
15997
|
+
}
|
|
15717
15998
|
async function updateComponent(targetDir, component, customizations, options, config) {
|
|
15718
15999
|
const preservedFiles = [];
|
|
15719
16000
|
const componentPath = getComponentPath2(component);
|
|
15720
16001
|
const srcPath = resolveTemplatePath(componentPath);
|
|
15721
|
-
const destPath =
|
|
16002
|
+
const destPath = join10(targetDir, componentPath);
|
|
15722
16003
|
const customComponents = config.customComponents || [];
|
|
15723
16004
|
const skipPaths = [];
|
|
15724
16005
|
if (customizations && !options.forceOverwriteAll) {
|
|
@@ -15731,15 +16012,34 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
15731
16012
|
skipPaths.push(cc.path);
|
|
15732
16013
|
}
|
|
15733
16014
|
}
|
|
16015
|
+
const { skipPaths: protectedSkipPaths, warnedPaths: protectedWarnedPaths } = await collectProtectedSkipPaths(srcPath, destPath, componentPath, !!options.forceOverwriteAll);
|
|
16016
|
+
for (const protectedPath of protectedWarnedPaths) {
|
|
16017
|
+
if (options.forceOverwriteAll) {
|
|
16018
|
+
warn("update.protected_file_force_overwrite", {
|
|
16019
|
+
file: protectedPath,
|
|
16020
|
+
component,
|
|
16021
|
+
hint: "File contains AI behavioral constraints. Overwriting because --force-overwrite-all was set."
|
|
16022
|
+
});
|
|
16023
|
+
} else {
|
|
16024
|
+
warn("update.protected_file_skipped", {
|
|
16025
|
+
file: protectedPath,
|
|
16026
|
+
component,
|
|
16027
|
+
hint: "File contains AI behavioral constraints and was not updated. Use --force-overwrite-all to override."
|
|
16028
|
+
});
|
|
16029
|
+
}
|
|
16030
|
+
}
|
|
16031
|
+
skipPaths.push(...protectedSkipPaths);
|
|
15734
16032
|
const path3 = await import("node:path");
|
|
15735
|
-
const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath,
|
|
16033
|
+
const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join10(targetDir, p)));
|
|
16034
|
+
const uniqueSkipPaths = [...new Set(normalizedSkipPaths)];
|
|
15736
16035
|
await copyDirectory(srcPath, destPath, {
|
|
15737
16036
|
overwrite: true,
|
|
15738
|
-
skipPaths:
|
|
16037
|
+
skipPaths: uniqueSkipPaths.length > 0 ? uniqueSkipPaths : undefined
|
|
15739
16038
|
});
|
|
15740
16039
|
debug("update.component_updated", {
|
|
15741
16040
|
component,
|
|
15742
|
-
skippedPaths: String(
|
|
16041
|
+
skippedPaths: String(uniqueSkipPaths.length),
|
|
16042
|
+
protectedSkipped: String(protectedSkipPaths.length)
|
|
15743
16043
|
});
|
|
15744
16044
|
return preservedFiles;
|
|
15745
16045
|
}
|
|
@@ -15752,12 +16052,12 @@ async function syncRootLevelFiles(targetDir, options) {
|
|
|
15752
16052
|
const layout = getProviderLayout();
|
|
15753
16053
|
const synced = [];
|
|
15754
16054
|
for (const fileName of ROOT_LEVEL_FILES) {
|
|
15755
|
-
const srcPath = resolveTemplatePath(
|
|
16055
|
+
const srcPath = resolveTemplatePath(join10(layout.rootDir, fileName));
|
|
15756
16056
|
if (!await fileExists(srcPath)) {
|
|
15757
16057
|
continue;
|
|
15758
16058
|
}
|
|
15759
|
-
const destPath =
|
|
15760
|
-
await ensureDirectory(
|
|
16059
|
+
const destPath = join10(targetDir, layout.rootDir, fileName);
|
|
16060
|
+
await ensureDirectory(join10(destPath, ".."));
|
|
15761
16061
|
await fs3.copyFile(srcPath, destPath);
|
|
15762
16062
|
if (fileName.endsWith(".sh")) {
|
|
15763
16063
|
await fs3.chmod(destPath, 493);
|
|
@@ -15792,7 +16092,7 @@ async function removeDeprecatedFiles(targetDir, options) {
|
|
|
15792
16092
|
});
|
|
15793
16093
|
continue;
|
|
15794
16094
|
}
|
|
15795
|
-
const fullPath =
|
|
16095
|
+
const fullPath = join10(targetDir, entry.path);
|
|
15796
16096
|
if (await fileExists(fullPath)) {
|
|
15797
16097
|
await fs3.unlink(fullPath);
|
|
15798
16098
|
removed.push(entry.path);
|
|
@@ -15816,26 +16116,26 @@ function getComponentPath2(component) {
|
|
|
15816
16116
|
}
|
|
15817
16117
|
async function backupInstallation(targetDir) {
|
|
15818
16118
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
15819
|
-
const backupDir =
|
|
16119
|
+
const backupDir = join10(targetDir, `.omcustom-backup-${timestamp}`);
|
|
15820
16120
|
const fs3 = await import("node:fs/promises");
|
|
15821
16121
|
await ensureDirectory(backupDir);
|
|
15822
16122
|
const layout = getProviderLayout();
|
|
15823
16123
|
const dirsToBackup = [layout.rootDir, "guides"];
|
|
15824
16124
|
for (const dir2 of dirsToBackup) {
|
|
15825
|
-
const srcPath =
|
|
16125
|
+
const srcPath = join10(targetDir, dir2);
|
|
15826
16126
|
if (await fileExists(srcPath)) {
|
|
15827
|
-
const destPath =
|
|
16127
|
+
const destPath = join10(backupDir, dir2);
|
|
15828
16128
|
await copyDirectory(srcPath, destPath, { overwrite: true });
|
|
15829
16129
|
}
|
|
15830
16130
|
}
|
|
15831
|
-
const entryPath =
|
|
16131
|
+
const entryPath = join10(targetDir, layout.entryFile);
|
|
15832
16132
|
if (await fileExists(entryPath)) {
|
|
15833
|
-
await fs3.copyFile(entryPath,
|
|
16133
|
+
await fs3.copyFile(entryPath, join10(backupDir, layout.entryFile));
|
|
15834
16134
|
}
|
|
15835
16135
|
return backupDir;
|
|
15836
16136
|
}
|
|
15837
16137
|
async function loadCustomizationManifest(targetDir) {
|
|
15838
|
-
const manifestPath =
|
|
16138
|
+
const manifestPath = join10(targetDir, CUSTOMIZATION_MANIFEST_FILE);
|
|
15839
16139
|
if (await fileExists(manifestPath)) {
|
|
15840
16140
|
return readJsonFile(manifestPath);
|
|
15841
16141
|
}
|
|
@@ -15938,7 +16238,7 @@ function createProgram() {
|
|
|
15938
16238
|
verbose: options.verbose
|
|
15939
16239
|
});
|
|
15940
16240
|
});
|
|
15941
|
-
program2.command("doctor").description(i18n.t("cli.doctor.description")).option("--fix", i18n.t("cli.doctor.fixOption")).action(async (options) => {
|
|
16241
|
+
program2.command("doctor").description(i18n.t("cli.doctor.description")).option("--fix", i18n.t("cli.doctor.fixOption")).option("--updates", i18n.t("cli.doctor.updatesOption")).action(async (options) => {
|
|
15942
16242
|
await doctorCommand(options);
|
|
15943
16243
|
});
|
|
15944
16244
|
program2.command("security").description(i18n.t("cli.security.description")).option("--verbose", i18n.t("cli.security.verboseOption")).action(async (options) => {
|