oh-my-customcode 0.13.2 → 0.13.3
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 +374 -8
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -11852,6 +11852,30 @@ var en_default = {
|
|
|
11852
11852
|
fail: "Some index.yaml files are invalid"
|
|
11853
11853
|
}
|
|
11854
11854
|
}
|
|
11855
|
+
},
|
|
11856
|
+
security: {
|
|
11857
|
+
description: "Scan for security issues in hooks, configs, and templates",
|
|
11858
|
+
verboseOption: "Show detailed scan results",
|
|
11859
|
+
scanning: "Running security scan...",
|
|
11860
|
+
passed: "Security scan passed! No issues found.",
|
|
11861
|
+
failed: "Security issues detected.",
|
|
11862
|
+
summary: "Security: {{pass}} passed, {{warn}} warnings, {{fail}} failed",
|
|
11863
|
+
checks: {
|
|
11864
|
+
hooks: {
|
|
11865
|
+
pass: "Hook scripts are safe",
|
|
11866
|
+
warn: "Hook scripts have potential concerns",
|
|
11867
|
+
fail: "Hook scripts contain dangerous patterns"
|
|
11868
|
+
},
|
|
11869
|
+
secrets: {
|
|
11870
|
+
pass: "No secrets found in configuration files",
|
|
11871
|
+
fail: "Secrets or credentials found in configuration"
|
|
11872
|
+
},
|
|
11873
|
+
integrity: {
|
|
11874
|
+
pass: "Template file permissions are secure",
|
|
11875
|
+
warn: "Some files have overly permissive settings",
|
|
11876
|
+
fail: "Security-sensitive files found in project"
|
|
11877
|
+
}
|
|
11878
|
+
}
|
|
11855
11879
|
}
|
|
11856
11880
|
},
|
|
11857
11881
|
init: {
|
|
@@ -12144,6 +12168,30 @@ var ko_default = {
|
|
|
12144
12168
|
fail: "일부 index.yaml 파일이 잘못됨"
|
|
12145
12169
|
}
|
|
12146
12170
|
}
|
|
12171
|
+
},
|
|
12172
|
+
security: {
|
|
12173
|
+
description: "훅, 설정, 템플릿의 보안 문제 검사",
|
|
12174
|
+
verboseOption: "상세 검사 결과 표시",
|
|
12175
|
+
scanning: "보안 검사 실행 중...",
|
|
12176
|
+
passed: "보안 검사 통과! 문제가 발견되지 않았습니다.",
|
|
12177
|
+
failed: "보안 문제가 발견되었습니다.",
|
|
12178
|
+
summary: "보안: {{pass}}개 통과, {{warn}}개 경고, {{fail}}개 실패",
|
|
12179
|
+
checks: {
|
|
12180
|
+
hooks: {
|
|
12181
|
+
pass: "훅 스크립트가 안전합니다",
|
|
12182
|
+
warn: "훅 스크립트에 잠재적 우려사항이 있습니다",
|
|
12183
|
+
fail: "훅 스크립트에 위험한 패턴이 포함되어 있습니다"
|
|
12184
|
+
},
|
|
12185
|
+
secrets: {
|
|
12186
|
+
pass: "설정 파일에 비밀 정보가 없습니다",
|
|
12187
|
+
fail: "설정 파일에서 비밀 정보 또는 인증 정보가 발견되었습니다"
|
|
12188
|
+
},
|
|
12189
|
+
integrity: {
|
|
12190
|
+
pass: "템플릿 파일 권한이 안전합니다",
|
|
12191
|
+
warn: "일부 파일의 권한이 과도하게 허용되어 있습니다",
|
|
12192
|
+
fail: "보안에 민감한 파일이 프로젝트에 존재합니다"
|
|
12193
|
+
}
|
|
12194
|
+
}
|
|
12147
12195
|
}
|
|
12148
12196
|
},
|
|
12149
12197
|
init: {
|
|
@@ -14635,6 +14683,320 @@ async function listCommand(type = "all", options = {}) {
|
|
|
14635
14683
|
}
|
|
14636
14684
|
}
|
|
14637
14685
|
|
|
14686
|
+
// src/cli/security.ts
|
|
14687
|
+
import { constants as constants2, promises as fs2 } from "node:fs";
|
|
14688
|
+
import path2 from "node:path";
|
|
14689
|
+
async function pathExists2(targetPath) {
|
|
14690
|
+
try {
|
|
14691
|
+
await fs2.access(targetPath, constants2.F_OK);
|
|
14692
|
+
return true;
|
|
14693
|
+
} catch {
|
|
14694
|
+
return false;
|
|
14695
|
+
}
|
|
14696
|
+
}
|
|
14697
|
+
function isValidUtf8Text(content) {
|
|
14698
|
+
try {
|
|
14699
|
+
content.toString("utf-8");
|
|
14700
|
+
return true;
|
|
14701
|
+
} catch {
|
|
14702
|
+
return false;
|
|
14703
|
+
}
|
|
14704
|
+
}
|
|
14705
|
+
async function findAllFiles(dir2) {
|
|
14706
|
+
const results = [];
|
|
14707
|
+
try {
|
|
14708
|
+
const entries = await fs2.readdir(dir2, { withFileTypes: true });
|
|
14709
|
+
for (const entry of entries) {
|
|
14710
|
+
const fullPath = path2.join(dir2, entry.name);
|
|
14711
|
+
if (entry.isDirectory()) {
|
|
14712
|
+
const subResults = await findAllFiles(fullPath);
|
|
14713
|
+
results.push(...subResults);
|
|
14714
|
+
} else if (entry.isFile()) {
|
|
14715
|
+
results.push(fullPath);
|
|
14716
|
+
}
|
|
14717
|
+
}
|
|
14718
|
+
} catch {}
|
|
14719
|
+
return results;
|
|
14720
|
+
}
|
|
14721
|
+
var DANGEROUS_PATTERNS = [
|
|
14722
|
+
{
|
|
14723
|
+
pattern: /rm\s+-rf\s+[/~]/,
|
|
14724
|
+
name: "rm -rf with root/home path",
|
|
14725
|
+
severity: "fail"
|
|
14726
|
+
},
|
|
14727
|
+
{
|
|
14728
|
+
pattern: /curl\s+.*\|\s*(bash|sh|eval)/,
|
|
14729
|
+
name: "curl pipe to shell",
|
|
14730
|
+
severity: "fail"
|
|
14731
|
+
},
|
|
14732
|
+
{
|
|
14733
|
+
pattern: /wget\s+.*\|\s*(bash|sh|eval)/,
|
|
14734
|
+
name: "wget pipe to shell",
|
|
14735
|
+
severity: "fail"
|
|
14736
|
+
},
|
|
14737
|
+
{ pattern: /\bsudo\b/, name: "sudo usage", severity: "warn" },
|
|
14738
|
+
{ pattern: /chmod\s+777/, name: "chmod 777", severity: "warn" },
|
|
14739
|
+
{ pattern: /\beval\s*\(/, name: "eval() usage", severity: "warn" },
|
|
14740
|
+
{
|
|
14741
|
+
pattern: /\$\{.*:-.*\}.*>\s*\/etc/,
|
|
14742
|
+
name: "write to /etc",
|
|
14743
|
+
severity: "fail"
|
|
14744
|
+
},
|
|
14745
|
+
{
|
|
14746
|
+
pattern: /base64\s+(-d|--decode).*\|\s*(bash|sh)/,
|
|
14747
|
+
name: "base64 decode to shell",
|
|
14748
|
+
severity: "fail"
|
|
14749
|
+
}
|
|
14750
|
+
];
|
|
14751
|
+
function extractCommands(hooks) {
|
|
14752
|
+
const commands = [];
|
|
14753
|
+
if (!hooks || typeof hooks !== "object")
|
|
14754
|
+
return commands;
|
|
14755
|
+
for (const hookName in hooks) {
|
|
14756
|
+
const hook = hooks[hookName];
|
|
14757
|
+
if (hook && typeof hook === "object") {
|
|
14758
|
+
for (const eventName in hook) {
|
|
14759
|
+
const event = hook[eventName];
|
|
14760
|
+
if (Array.isArray(event)) {
|
|
14761
|
+
for (const item of event) {
|
|
14762
|
+
if (typeof item === "object" && item && "command" in item) {
|
|
14763
|
+
commands.push(String(item.command));
|
|
14764
|
+
}
|
|
14765
|
+
}
|
|
14766
|
+
}
|
|
14767
|
+
}
|
|
14768
|
+
}
|
|
14769
|
+
}
|
|
14770
|
+
return commands;
|
|
14771
|
+
}
|
|
14772
|
+
function scanCommands(commands) {
|
|
14773
|
+
const findings = [];
|
|
14774
|
+
let worstSeverity = "pass";
|
|
14775
|
+
for (const command of commands) {
|
|
14776
|
+
for (const { pattern, name, severity } of DANGEROUS_PATTERNS) {
|
|
14777
|
+
if (pattern.test(command)) {
|
|
14778
|
+
findings.push(`${name}: ${command.substring(0, 80)}${command.length > 80 ? "..." : ""}`);
|
|
14779
|
+
if (severity === "fail") {
|
|
14780
|
+
worstSeverity = "fail";
|
|
14781
|
+
} else if (severity === "warn" && worstSeverity === "pass") {
|
|
14782
|
+
worstSeverity = "warn";
|
|
14783
|
+
}
|
|
14784
|
+
}
|
|
14785
|
+
}
|
|
14786
|
+
}
|
|
14787
|
+
return { findings, worstSeverity };
|
|
14788
|
+
}
|
|
14789
|
+
async function checkHookScripts(targetDir, rootDir = ".claude") {
|
|
14790
|
+
const hooksFile = path2.join(targetDir, rootDir, "hooks", "hooks.json");
|
|
14791
|
+
const exists2 = await pathExists2(hooksFile);
|
|
14792
|
+
if (!exists2) {
|
|
14793
|
+
return {
|
|
14794
|
+
name: "Hook scripts",
|
|
14795
|
+
status: "pass",
|
|
14796
|
+
message: i18n.t("cli.security.checks.hooks.pass"),
|
|
14797
|
+
fixable: false
|
|
14798
|
+
};
|
|
14799
|
+
}
|
|
14800
|
+
try {
|
|
14801
|
+
const content = await fs2.readFile(hooksFile, "utf-8");
|
|
14802
|
+
const hooks = JSON.parse(content);
|
|
14803
|
+
const commands = extractCommands(hooks);
|
|
14804
|
+
const { findings, worstSeverity } = scanCommands(commands);
|
|
14805
|
+
if (findings.length > 0) {
|
|
14806
|
+
const message = worstSeverity === "fail" ? i18n.t("cli.security.checks.hooks.fail") : i18n.t("cli.security.checks.hooks.warn");
|
|
14807
|
+
return {
|
|
14808
|
+
name: "Hook scripts",
|
|
14809
|
+
status: worstSeverity,
|
|
14810
|
+
message: `${message} (${findings.length} issues)`,
|
|
14811
|
+
fixable: false,
|
|
14812
|
+
details: findings
|
|
14813
|
+
};
|
|
14814
|
+
}
|
|
14815
|
+
return {
|
|
14816
|
+
name: "Hook scripts",
|
|
14817
|
+
status: "pass",
|
|
14818
|
+
message: i18n.t("cli.security.checks.hooks.pass"),
|
|
14819
|
+
fixable: false
|
|
14820
|
+
};
|
|
14821
|
+
} catch (error2) {
|
|
14822
|
+
return {
|
|
14823
|
+
name: "Hook scripts",
|
|
14824
|
+
status: "warn",
|
|
14825
|
+
message: `Failed to parse hooks.json: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
14826
|
+
fixable: false
|
|
14827
|
+
};
|
|
14828
|
+
}
|
|
14829
|
+
}
|
|
14830
|
+
async function checkConfigSecrets(targetDir, rootDir = ".claude") {
|
|
14831
|
+
const configDir = path2.join(targetDir, rootDir);
|
|
14832
|
+
const exists2 = await pathExists2(configDir);
|
|
14833
|
+
if (!exists2) {
|
|
14834
|
+
return {
|
|
14835
|
+
name: "Config secrets",
|
|
14836
|
+
status: "pass",
|
|
14837
|
+
message: i18n.t("cli.security.checks.secrets.pass"),
|
|
14838
|
+
fixable: false
|
|
14839
|
+
};
|
|
14840
|
+
}
|
|
14841
|
+
const SECRET_PATTERNS = [
|
|
14842
|
+
{
|
|
14843
|
+
pattern: /(?:AWS_SECRET|AWS_ACCESS_KEY|AWS_SESSION)[_A-Z]*\s*[=:]\s*['"]?[A-Za-z0-9/+=]{20,}/,
|
|
14844
|
+
name: "AWS credential"
|
|
14845
|
+
},
|
|
14846
|
+
{
|
|
14847
|
+
pattern: /(?:GITHUB_TOKEN|GH_TOKEN|GITHUB_PAT)\s*[=:]\s*['"]?(?:ghp_|gho_|ghs_|ghr_|github_pat_)[A-Za-z0-9_]+/,
|
|
14848
|
+
name: "GitHub token"
|
|
14849
|
+
},
|
|
14850
|
+
{
|
|
14851
|
+
pattern: /(?:sk-|sk_live_|sk_test_)[A-Za-z0-9]{20,}/,
|
|
14852
|
+
name: "API secret key (sk-*)"
|
|
14853
|
+
},
|
|
14854
|
+
{
|
|
14855
|
+
pattern: /(?:password|passwd|secret)\s*[=:]\s*['"]?[^\s'"]{8,}/i,
|
|
14856
|
+
name: "Hardcoded password/secret"
|
|
14857
|
+
},
|
|
14858
|
+
{
|
|
14859
|
+
pattern: /-----BEGIN (?:RSA |EC |DSA )?PRIVATE KEY-----/,
|
|
14860
|
+
name: "Private key"
|
|
14861
|
+
}
|
|
14862
|
+
];
|
|
14863
|
+
const files = await findAllFiles(configDir);
|
|
14864
|
+
const findings = [];
|
|
14865
|
+
for (const file of files) {
|
|
14866
|
+
try {
|
|
14867
|
+
const content = await fs2.readFile(file);
|
|
14868
|
+
if (!isValidUtf8Text(content)) {
|
|
14869
|
+
continue;
|
|
14870
|
+
}
|
|
14871
|
+
const text = content.toString("utf-8");
|
|
14872
|
+
for (const { pattern, name } of SECRET_PATTERNS) {
|
|
14873
|
+
if (pattern.test(text)) {
|
|
14874
|
+
const relativePath = path2.relative(targetDir, file);
|
|
14875
|
+
findings.push(`${relativePath}: ${name}`);
|
|
14876
|
+
}
|
|
14877
|
+
}
|
|
14878
|
+
} catch {}
|
|
14879
|
+
}
|
|
14880
|
+
if (findings.length > 0) {
|
|
14881
|
+
return {
|
|
14882
|
+
name: "Config secrets",
|
|
14883
|
+
status: "fail",
|
|
14884
|
+
message: `${i18n.t("cli.security.checks.secrets.fail")} (${findings.length} found)`,
|
|
14885
|
+
fixable: false,
|
|
14886
|
+
details: findings
|
|
14887
|
+
};
|
|
14888
|
+
}
|
|
14889
|
+
return {
|
|
14890
|
+
name: "Config secrets",
|
|
14891
|
+
status: "pass",
|
|
14892
|
+
message: i18n.t("cli.security.checks.secrets.pass"),
|
|
14893
|
+
fixable: false
|
|
14894
|
+
};
|
|
14895
|
+
}
|
|
14896
|
+
async function checkEnvFiles(targetDir) {
|
|
14897
|
+
const findings = [];
|
|
14898
|
+
let severity = "pass";
|
|
14899
|
+
const envFiles = [".env", ".env.local", ".env.production", ".env.development"];
|
|
14900
|
+
for (const envFile of envFiles) {
|
|
14901
|
+
const envPath = path2.join(targetDir, envFile);
|
|
14902
|
+
if (await pathExists2(envPath)) {
|
|
14903
|
+
findings.push(`Security-sensitive file found: ${envFile}`);
|
|
14904
|
+
severity = "fail";
|
|
14905
|
+
}
|
|
14906
|
+
}
|
|
14907
|
+
return { findings, severity };
|
|
14908
|
+
}
|
|
14909
|
+
async function checkShellPermissions(targetDir, shellScripts) {
|
|
14910
|
+
const findings = [];
|
|
14911
|
+
let severity = "pass";
|
|
14912
|
+
for (const script of shellScripts) {
|
|
14913
|
+
try {
|
|
14914
|
+
const stats = await fs2.stat(script);
|
|
14915
|
+
const mode = stats.mode & 511;
|
|
14916
|
+
const relativePath = path2.relative(targetDir, script);
|
|
14917
|
+
if (mode === 511) {
|
|
14918
|
+
findings.push(`Overly permissive permissions (777): ${relativePath}`);
|
|
14919
|
+
if (severity === "pass") {
|
|
14920
|
+
severity = "warn";
|
|
14921
|
+
}
|
|
14922
|
+
} else if (mode & 2) {
|
|
14923
|
+
findings.push(`World-writable: ${relativePath}`);
|
|
14924
|
+
if (severity === "pass") {
|
|
14925
|
+
severity = "warn";
|
|
14926
|
+
}
|
|
14927
|
+
}
|
|
14928
|
+
} catch {}
|
|
14929
|
+
}
|
|
14930
|
+
return { findings, severity };
|
|
14931
|
+
}
|
|
14932
|
+
async function checkTemplateIntegrity(targetDir) {
|
|
14933
|
+
let worstSeverity = "pass";
|
|
14934
|
+
const allFindings = [];
|
|
14935
|
+
const envCheck = await checkEnvFiles(targetDir);
|
|
14936
|
+
allFindings.push(...envCheck.findings);
|
|
14937
|
+
if (envCheck.severity === "fail") {
|
|
14938
|
+
worstSeverity = "fail";
|
|
14939
|
+
}
|
|
14940
|
+
const allFiles = await findAllFiles(targetDir);
|
|
14941
|
+
const shellScripts = allFiles.filter((f) => f.endsWith(".sh"));
|
|
14942
|
+
const permCheck = await checkShellPermissions(targetDir, shellScripts);
|
|
14943
|
+
allFindings.push(...permCheck.findings);
|
|
14944
|
+
if (permCheck.severity === "warn" && worstSeverity === "pass") {
|
|
14945
|
+
worstSeverity = "warn";
|
|
14946
|
+
}
|
|
14947
|
+
if (allFindings.length > 0) {
|
|
14948
|
+
const message = worstSeverity === "fail" ? i18n.t("cli.security.checks.integrity.fail") : i18n.t("cli.security.checks.integrity.warn");
|
|
14949
|
+
return {
|
|
14950
|
+
name: "Template integrity",
|
|
14951
|
+
status: worstSeverity,
|
|
14952
|
+
message: `${message} (${allFindings.length} issues)`,
|
|
14953
|
+
fixable: false,
|
|
14954
|
+
details: allFindings
|
|
14955
|
+
};
|
|
14956
|
+
}
|
|
14957
|
+
return {
|
|
14958
|
+
name: "Template integrity",
|
|
14959
|
+
status: "pass",
|
|
14960
|
+
message: i18n.t("cli.security.checks.integrity.pass"),
|
|
14961
|
+
fixable: false
|
|
14962
|
+
};
|
|
14963
|
+
}
|
|
14964
|
+
async function securityCommand(_options = {}) {
|
|
14965
|
+
const targetDir = process.cwd();
|
|
14966
|
+
console.log(i18n.t("cli.security.scanning"));
|
|
14967
|
+
console.log("");
|
|
14968
|
+
const layout = getProviderLayout();
|
|
14969
|
+
const checks = await Promise.all([
|
|
14970
|
+
checkHookScripts(targetDir, layout.rootDir),
|
|
14971
|
+
checkConfigSecrets(targetDir, layout.rootDir),
|
|
14972
|
+
checkTemplateIntegrity(targetDir)
|
|
14973
|
+
]);
|
|
14974
|
+
for (const check of checks) {
|
|
14975
|
+
printCheck(check);
|
|
14976
|
+
}
|
|
14977
|
+
const passCount = checks.filter((c) => c.status === "pass").length;
|
|
14978
|
+
const warnCount = checks.filter((c) => c.status === "warn").length;
|
|
14979
|
+
const failCount = checks.filter((c) => c.status === "fail").length;
|
|
14980
|
+
console.log("");
|
|
14981
|
+
if (failCount === 0 && warnCount === 0) {
|
|
14982
|
+
console.log(i18n.t("cli.security.passed"));
|
|
14983
|
+
} else {
|
|
14984
|
+
console.log(i18n.t("cli.security.failed"));
|
|
14985
|
+
}
|
|
14986
|
+
console.log(i18n.t("cli.security.summary", {
|
|
14987
|
+
pass: passCount,
|
|
14988
|
+
warn: warnCount,
|
|
14989
|
+
fail: failCount
|
|
14990
|
+
}));
|
|
14991
|
+
return {
|
|
14992
|
+
success: failCount === 0,
|
|
14993
|
+
checks,
|
|
14994
|
+
passCount,
|
|
14995
|
+
warnCount,
|
|
14996
|
+
failCount
|
|
14997
|
+
};
|
|
14998
|
+
}
|
|
14999
|
+
|
|
14638
15000
|
// src/core/updater.ts
|
|
14639
15001
|
import { join as join8 } from "node:path";
|
|
14640
15002
|
|
|
@@ -14807,11 +15169,11 @@ function getEntryTemplateName2(language) {
|
|
|
14807
15169
|
return language === "ko" ? `${baseName}.md.ko` : `${baseName}.md.en`;
|
|
14808
15170
|
}
|
|
14809
15171
|
async function backupFile(filePath) {
|
|
14810
|
-
const
|
|
15172
|
+
const fs3 = await import("node:fs/promises");
|
|
14811
15173
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
14812
15174
|
const backupPath = `${filePath}.backup-${timestamp}`;
|
|
14813
15175
|
if (await fileExists(filePath)) {
|
|
14814
|
-
await
|
|
15176
|
+
await fs3.copyFile(filePath, backupPath);
|
|
14815
15177
|
debug("update.file_backed_up", { path: filePath, backup: backupPath });
|
|
14816
15178
|
}
|
|
14817
15179
|
}
|
|
@@ -15023,8 +15385,8 @@ async function updateComponent(targetDir, component, customizations, options, co
|
|
|
15023
15385
|
skipPaths.push(cc.path);
|
|
15024
15386
|
}
|
|
15025
15387
|
}
|
|
15026
|
-
const
|
|
15027
|
-
const normalizedSkipPaths = skipPaths.map((p) =>
|
|
15388
|
+
const path3 = await import("node:path");
|
|
15389
|
+
const normalizedSkipPaths = skipPaths.map((p) => path3.relative(destPath, join8(targetDir, p)));
|
|
15028
15390
|
await copyDirectory(srcPath, destPath, {
|
|
15029
15391
|
overwrite: true,
|
|
15030
15392
|
skipPaths: normalizedSkipPaths.length > 0 ? normalizedSkipPaths : undefined
|
|
@@ -15045,7 +15407,7 @@ function getComponentPath2(component) {
|
|
|
15045
15407
|
async function backupInstallation(targetDir) {
|
|
15046
15408
|
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
15047
15409
|
const backupDir = join8(targetDir, `.omcustom-backup-${timestamp}`);
|
|
15048
|
-
const
|
|
15410
|
+
const fs3 = await import("node:fs/promises");
|
|
15049
15411
|
await ensureDirectory(backupDir);
|
|
15050
15412
|
const layout = getProviderLayout();
|
|
15051
15413
|
const dirsToBackup = [layout.rootDir, "guides"];
|
|
@@ -15058,7 +15420,7 @@ async function backupInstallation(targetDir) {
|
|
|
15058
15420
|
}
|
|
15059
15421
|
const entryPath = join8(targetDir, layout.entryFile);
|
|
15060
15422
|
if (await fileExists(entryPath)) {
|
|
15061
|
-
await
|
|
15423
|
+
await fs3.copyFile(entryPath, join8(backupDir, layout.entryFile));
|
|
15062
15424
|
}
|
|
15063
15425
|
return backupDir;
|
|
15064
15426
|
}
|
|
@@ -15131,8 +15493,8 @@ function printUpdateResults(result) {
|
|
|
15131
15493
|
console.log(i18n.t("cli.update.preservedFiles", { count: result.preservedFiles.length }));
|
|
15132
15494
|
}
|
|
15133
15495
|
if (result.backedUpPaths.length > 0) {
|
|
15134
|
-
for (const
|
|
15135
|
-
console.log(i18n.t("cli.update.backupCreated", { path:
|
|
15496
|
+
for (const path3 of result.backedUpPaths) {
|
|
15497
|
+
console.log(i18n.t("cli.update.backupCreated", { path: path3 }));
|
|
15136
15498
|
}
|
|
15137
15499
|
}
|
|
15138
15500
|
for (const warning of result.warnings) {
|
|
@@ -15169,6 +15531,10 @@ function createProgram() {
|
|
|
15169
15531
|
program2.command("doctor").description(i18n.t("cli.doctor.description")).option("--fix", i18n.t("cli.doctor.fixOption")).action(async (options) => {
|
|
15170
15532
|
await doctorCommand(options);
|
|
15171
15533
|
});
|
|
15534
|
+
program2.command("security").description(i18n.t("cli.security.description")).option("--verbose", i18n.t("cli.security.verboseOption")).action(async (options) => {
|
|
15535
|
+
const result = await securityCommand(options);
|
|
15536
|
+
process.exitCode = result.success ? 0 : 1;
|
|
15537
|
+
});
|
|
15172
15538
|
program2.hook("preAction", async (thisCommand, actionCommand) => {
|
|
15173
15539
|
const opts = thisCommand.optsWithGlobals();
|
|
15174
15540
|
const skipCheck = opts.skipVersionCheck || false;
|