safeword 0.10.1 → 0.11.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/{check-IBMCNPDE.js → check-EQ3IJPBM.js} +8 -6
- package/dist/{check-IBMCNPDE.js.map → check-EQ3IJPBM.js.map} +1 -1
- package/dist/chunk-DYLHQBW3.js +132 -0
- package/dist/chunk-DYLHQBW3.js.map +1 -0
- package/dist/{chunk-H7PCVPAC.js → chunk-KL2JTWK6.js} +73 -154
- package/dist/chunk-KL2JTWK6.js.map +1 -0
- package/dist/chunk-NDY7IUE7.js +229 -0
- package/dist/chunk-NDY7IUE7.js.map +1 -0
- package/dist/cli.js +9 -5
- package/dist/cli.js.map +1 -1
- package/dist/{diff-7AXUOVBF.js → diff-6EDSOICP.js} +8 -6
- package/dist/{diff-7AXUOVBF.js.map → diff-6EDSOICP.js.map} +1 -1
- package/dist/{reset-AEEUFODC.js → reset-HJBALSYG.js} +6 -4
- package/dist/{reset-AEEUFODC.js.map → reset-HJBALSYG.js.map} +1 -1
- package/dist/{setup-R5IC3GHJ.js → setup-SOWUS7ZI.js} +41 -13
- package/dist/setup-SOWUS7ZI.js.map +1 -0
- package/dist/sync-config-XTMQBCIH.js +14 -0
- package/dist/sync-config-XTMQBCIH.js.map +1 -0
- package/dist/{upgrade-QXP3ONNQ.js → upgrade-KJLOX4DD.js} +8 -6
- package/dist/{upgrade-QXP3ONNQ.js.map → upgrade-KJLOX4DD.js.map} +1 -1
- package/package.json +2 -2
- package/templates/commands/audit.md +41 -0
- package/templates/skills/safeword-debugging/SKILL.md +15 -15
- package/dist/chunk-H7PCVPAC.js.map +0 -1
- package/dist/setup-R5IC3GHJ.js.map +0 -1
|
@@ -4,18 +4,20 @@ import {
|
|
|
4
4
|
import {
|
|
5
5
|
SAFEWORD_SCHEMA,
|
|
6
6
|
createProjectContext,
|
|
7
|
+
reconcile
|
|
8
|
+
} from "./chunk-KL2JTWK6.js";
|
|
9
|
+
import {
|
|
10
|
+
VERSION
|
|
11
|
+
} from "./chunk-ORQHKDT2.js";
|
|
12
|
+
import {
|
|
7
13
|
exists,
|
|
8
14
|
header,
|
|
9
15
|
info,
|
|
10
16
|
keyValue,
|
|
11
17
|
readFileSafe,
|
|
12
|
-
reconcile,
|
|
13
18
|
success,
|
|
14
19
|
warn
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import {
|
|
17
|
-
VERSION
|
|
18
|
-
} from "./chunk-ORQHKDT2.js";
|
|
20
|
+
} from "./chunk-DYLHQBW3.js";
|
|
19
21
|
|
|
20
22
|
// src/commands/check.ts
|
|
21
23
|
import nodePath from "path";
|
|
@@ -162,4 +164,4 @@ async function check(options) {
|
|
|
162
164
|
export {
|
|
163
165
|
check
|
|
164
166
|
};
|
|
165
|
-
//# sourceMappingURL=check-
|
|
167
|
+
//# sourceMappingURL=check-EQ3IJPBM.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n *\n * Uses reconcile() with dryRun to detect missing files and configuration issues.\n */\n\nimport nodePath from 'node:path';\n\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { header, info, keyValue, success, warn } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\n/**\n * Check for missing files from write actions\n * @param cwd\n * @param actions\n */\nfunction findMissingFiles(cwd: string, actions: { type: string; path: string }[]): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type === 'write' && !exists(nodePath.join(cwd, action.path))) {\n issues.push(`Missing: ${action.path}`);\n }\n }\n return issues;\n}\n\n/**\n * Check for missing text patch markers\n * @param cwd\n * @param actions\n */\nfunction findMissingPatches(\n cwd: string,\n actions: { type: string; path: string; definition?: { marker: string } }[],\n): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type !== 'text-patch') continue;\n\n const fullPath = nodePath.join(cwd, action.path);\n if (exists(fullPath)) {\n const content = readFileSafe(fullPath) ?? '';\n if (action.definition && !content.includes(action.definition.marker)) {\n issues.push(`${action.path} missing safeword link`);\n }\n } else {\n issues.push(`${action.path} file missing`);\n }\n }\n return issues;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | undefined;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | undefined;\n issues: string[];\n missingPackages: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n * @param timeout\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | undefined> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return undefined;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check project configuration health using reconcile dryRun\n * @param cwd\n */\nasync function checkHealth(cwd: string): Promise<HealthStatus> {\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDirectory)) {\n return {\n configured: false,\n projectVersion: undefined,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues: [],\n missingPackages: [],\n };\n }\n\n // Read project version\n const versionPath = nodePath.join(safewordDirectory, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? undefined;\n\n // Use reconcile with dryRun to detect issues\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx, { dryRun: true });\n\n // Collect issues from write actions and text patches\n const issues: string[] = [\n ...findMissingFiles(cwd, result.actions),\n ...findMissingPatches(cwd, result.actions),\n ];\n\n // Check for missing .claude/settings.json\n if (!exists(nodePath.join(cwd, '.claude', 'settings.json'))) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues,\n missingPackages: result.packagesToInstall,\n };\n}\n\n/**\n * Check for CLI updates and report status\n * @param health\n */\nasync function reportUpdateStatus(health: HealthStatus): Promise<void> {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (!latestVersion) {\n warn(\"Couldn't check for updates (offline?)\");\n return;\n }\n\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `npm install -g safeword` to upgrade');\n } else {\n success('CLI is up to date');\n }\n}\n\n/**\n * Compare project version vs CLI version and report\n * @param health\n */\nfunction reportVersionMismatch(health: HealthStatus): void {\n if (!health.projectVersion) return;\n\n if (isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n}\n\n/**\n * Report issues or success\n * @param health\n */\nfunction reportHealthSummary(health: HealthStatus): void {\n if (health.issues.length > 0) {\n header('Issues Found');\n for (const issue of health.issues) {\n warn(issue);\n }\n info('\\nRun `safeword upgrade` to repair configuration');\n return;\n }\n\n if (health.missingPackages.length > 0) {\n header('Missing Packages');\n info(`${health.missingPackages.length} linting packages not installed`);\n info('Run `safeword upgrade` to install missing packages');\n return;\n }\n\n success('\\nConfiguration is healthy');\n}\n\n/**\n *\n * @param options\n */\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = await checkHealth(cwd);\n\n // Not configured\n if (!health.configured) {\n info('Not configured. Run `safeword setup` to initialize.');\n return;\n }\n\n // Show versions\n keyValue('Safeword CLI', `v${health.cliVersion}`);\n keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');\n\n // Check for updates (unless offline)\n if (options.offline) {\n info('\\nSkipped update check (offline mode)');\n } else {\n await reportUpdateStatus(health);\n }\n\n reportVersionMismatch(health);\n reportHealthSummary(health);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/commands/check.ts"],"sourcesContent":["/**\n * Check command - Verify project health and configuration\n *\n * Uses reconcile() with dryRun to detect missing files and configuration issues.\n */\n\nimport nodePath from 'node:path';\n\nimport { reconcile } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { exists, readFileSafe } from '../utils/fs.js';\nimport { header, info, keyValue, success, warn } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\nexport interface CheckOptions {\n offline?: boolean;\n}\n\n/**\n * Check for missing files from write actions\n * @param cwd\n * @param actions\n */\nfunction findMissingFiles(cwd: string, actions: { type: string; path: string }[]): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type === 'write' && !exists(nodePath.join(cwd, action.path))) {\n issues.push(`Missing: ${action.path}`);\n }\n }\n return issues;\n}\n\n/**\n * Check for missing text patch markers\n * @param cwd\n * @param actions\n */\nfunction findMissingPatches(\n cwd: string,\n actions: { type: string; path: string; definition?: { marker: string } }[],\n): string[] {\n const issues: string[] = [];\n for (const action of actions) {\n if (action.type !== 'text-patch') continue;\n\n const fullPath = nodePath.join(cwd, action.path);\n if (exists(fullPath)) {\n const content = readFileSafe(fullPath) ?? '';\n if (action.definition && !content.includes(action.definition.marker)) {\n issues.push(`${action.path} missing safeword link`);\n }\n } else {\n issues.push(`${action.path} file missing`);\n }\n }\n return issues;\n}\n\ninterface HealthStatus {\n configured: boolean;\n projectVersion: string | undefined;\n cliVersion: string;\n updateAvailable: boolean;\n latestVersion: string | undefined;\n issues: string[];\n missingPackages: string[];\n}\n\n/**\n * Check for latest version from npm (with timeout)\n * @param timeout\n */\nasync function checkLatestVersion(timeout = 3000): Promise<string | undefined> {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => {\n controller.abort();\n }, timeout);\n\n const response = await fetch('https://registry.npmjs.org/safeword/latest', {\n signal: controller.signal,\n });\n\n clearTimeout(timeoutId);\n\n if (!response.ok) return undefined;\n\n const data = (await response.json()) as { version?: string };\n return data.version ?? undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Check project configuration health using reconcile dryRun\n * @param cwd\n */\nasync function checkHealth(cwd: string): Promise<HealthStatus> {\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n // Check if configured\n if (!exists(safewordDirectory)) {\n return {\n configured: false,\n projectVersion: undefined,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues: [],\n missingPackages: [],\n };\n }\n\n // Read project version\n const versionPath = nodePath.join(safewordDirectory, 'version');\n const projectVersion = readFileSafe(versionPath)?.trim() ?? undefined;\n\n // Use reconcile with dryRun to detect issues\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx, { dryRun: true });\n\n // Collect issues from write actions and text patches\n const issues: string[] = [\n ...findMissingFiles(cwd, result.actions),\n ...findMissingPatches(cwd, result.actions),\n ];\n\n // Check for missing .claude/settings.json\n if (!exists(nodePath.join(cwd, '.claude', 'settings.json'))) {\n issues.push('Missing: .claude/settings.json');\n }\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues,\n missingPackages: result.packagesToInstall,\n };\n}\n\n/**\n * Check for CLI updates and report status\n * @param health\n */\nasync function reportUpdateStatus(health: HealthStatus): Promise<void> {\n info('\\nChecking for updates...');\n const latestVersion = await checkLatestVersion();\n\n if (!latestVersion) {\n warn(\"Couldn't check for updates (offline?)\");\n return;\n }\n\n health.latestVersion = latestVersion;\n health.updateAvailable = isNewerVersion(health.cliVersion, latestVersion);\n\n if (health.updateAvailable) {\n warn(`Update available: v${latestVersion}`);\n info('Run `npm install -g safeword` to upgrade');\n } else {\n success('CLI is up to date');\n }\n}\n\n/**\n * Compare project version vs CLI version and report\n * @param health\n */\nfunction reportVersionMismatch(health: HealthStatus): void {\n if (!health.projectVersion) return;\n\n if (isNewerVersion(health.cliVersion, health.projectVersion)) {\n warn(`Project config (v${health.projectVersion}) is newer than CLI (v${health.cliVersion})`);\n info('Consider upgrading the CLI');\n } else if (isNewerVersion(health.projectVersion, health.cliVersion)) {\n info(`\\nUpgrade available for project config`);\n info(\n `Run \\`safeword upgrade\\` to update from v${health.projectVersion} to v${health.cliVersion}`,\n );\n }\n}\n\n/**\n * Report issues or success\n * @param health\n */\nfunction reportHealthSummary(health: HealthStatus): void {\n if (health.issues.length > 0) {\n header('Issues Found');\n for (const issue of health.issues) {\n warn(issue);\n }\n info('\\nRun `safeword upgrade` to repair configuration');\n return;\n }\n\n if (health.missingPackages.length > 0) {\n header('Missing Packages');\n info(`${health.missingPackages.length} linting packages not installed`);\n info('Run `safeword upgrade` to install missing packages');\n return;\n }\n\n success('\\nConfiguration is healthy');\n}\n\n/**\n *\n * @param options\n */\nexport async function check(options: CheckOptions): Promise<void> {\n const cwd = process.cwd();\n\n header('Safeword Health Check');\n\n const health = await checkHealth(cwd);\n\n // Not configured\n if (!health.configured) {\n info('Not configured. Run `safeword setup` to initialize.');\n return;\n }\n\n // Show versions\n keyValue('Safeword CLI', `v${health.cliVersion}`);\n keyValue('Project config', health.projectVersion ? `v${health.projectVersion}` : 'unknown');\n\n // Check for updates (unless offline)\n if (options.offline) {\n info('\\nSkipped update check (offline mode)');\n } else {\n await reportUpdateStatus(health);\n }\n\n reportVersionMismatch(health);\n reportHealthSummary(health);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// src/utils/fs.ts
|
|
2
|
+
import {
|
|
3
|
+
chmodSync,
|
|
4
|
+
existsSync,
|
|
5
|
+
mkdirSync,
|
|
6
|
+
readdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
rmdirSync,
|
|
9
|
+
rmSync,
|
|
10
|
+
writeFileSync
|
|
11
|
+
} from "fs";
|
|
12
|
+
import nodePath from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
14
|
+
var __dirname = nodePath.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
function getTemplatesDirectory() {
|
|
16
|
+
const knownTemplateFile = "SAFEWORD.md";
|
|
17
|
+
const candidates = [
|
|
18
|
+
nodePath.join(__dirname, "..", "templates"),
|
|
19
|
+
// From dist/ (flat bundled)
|
|
20
|
+
nodePath.join(__dirname, "..", "..", "templates"),
|
|
21
|
+
// From src/utils/ or dist/utils/
|
|
22
|
+
nodePath.join(__dirname, "templates")
|
|
23
|
+
// Direct sibling (unlikely but safe)
|
|
24
|
+
];
|
|
25
|
+
for (const candidate of candidates) {
|
|
26
|
+
if (existsSync(nodePath.join(candidate, knownTemplateFile))) {
|
|
27
|
+
return candidate;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
throw new Error("Templates directory not found");
|
|
31
|
+
}
|
|
32
|
+
function exists(path) {
|
|
33
|
+
return existsSync(path);
|
|
34
|
+
}
|
|
35
|
+
function ensureDirectory(path) {
|
|
36
|
+
if (!existsSync(path)) {
|
|
37
|
+
mkdirSync(path, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function readFile(path) {
|
|
41
|
+
return readFileSync(path, "utf8");
|
|
42
|
+
}
|
|
43
|
+
function readFileSafe(path) {
|
|
44
|
+
if (!existsSync(path)) return void 0;
|
|
45
|
+
return readFileSync(path, "utf8");
|
|
46
|
+
}
|
|
47
|
+
function writeFile(path, content) {
|
|
48
|
+
ensureDirectory(nodePath.dirname(path));
|
|
49
|
+
writeFileSync(path, content);
|
|
50
|
+
}
|
|
51
|
+
function remove(path) {
|
|
52
|
+
if (existsSync(path)) {
|
|
53
|
+
rmSync(path, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function removeIfEmpty(path) {
|
|
57
|
+
if (!existsSync(path)) return false;
|
|
58
|
+
try {
|
|
59
|
+
rmdirSync(path);
|
|
60
|
+
return true;
|
|
61
|
+
} catch {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
function makeScriptsExecutable(dirPath) {
|
|
66
|
+
if (!existsSync(dirPath)) return;
|
|
67
|
+
for (const file of readdirSync(dirPath)) {
|
|
68
|
+
if (file.endsWith(".sh")) {
|
|
69
|
+
chmodSync(nodePath.join(dirPath, file), 493);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function readJson(path) {
|
|
74
|
+
const content = readFileSafe(path);
|
|
75
|
+
if (!content) return void 0;
|
|
76
|
+
try {
|
|
77
|
+
return JSON.parse(content);
|
|
78
|
+
} catch {
|
|
79
|
+
return void 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function writeJson(path, data) {
|
|
83
|
+
writeFile(path, `${JSON.stringify(data, void 0, 2)}
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// src/utils/output.ts
|
|
88
|
+
function info(message) {
|
|
89
|
+
console.log(message);
|
|
90
|
+
}
|
|
91
|
+
function success(message) {
|
|
92
|
+
console.log(`\u2713 ${message}`);
|
|
93
|
+
}
|
|
94
|
+
function warn(message) {
|
|
95
|
+
console.warn(`\u26A0 ${message}`);
|
|
96
|
+
}
|
|
97
|
+
function error(message) {
|
|
98
|
+
console.error(`\u2717 ${message}`);
|
|
99
|
+
}
|
|
100
|
+
function header(title) {
|
|
101
|
+
console.log(`
|
|
102
|
+
${title}`);
|
|
103
|
+
console.log("\u2500".repeat(title.length));
|
|
104
|
+
}
|
|
105
|
+
function listItem(item, indent = 2) {
|
|
106
|
+
console.log(`${" ".repeat(indent)}\u2022 ${item}`);
|
|
107
|
+
}
|
|
108
|
+
function keyValue(key, value) {
|
|
109
|
+
console.log(` ${key}: ${value}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
getTemplatesDirectory,
|
|
114
|
+
exists,
|
|
115
|
+
ensureDirectory,
|
|
116
|
+
readFile,
|
|
117
|
+
readFileSafe,
|
|
118
|
+
writeFile,
|
|
119
|
+
remove,
|
|
120
|
+
removeIfEmpty,
|
|
121
|
+
makeScriptsExecutable,
|
|
122
|
+
readJson,
|
|
123
|
+
writeJson,
|
|
124
|
+
info,
|
|
125
|
+
success,
|
|
126
|
+
warn,
|
|
127
|
+
error,
|
|
128
|
+
header,
|
|
129
|
+
listItem,
|
|
130
|
+
keyValue
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=chunk-DYLHQBW3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/fs.ts","../src/utils/output.ts"],"sourcesContent":["/**\n * File system utilities for CLI operations\n */\n\nimport {\n chmodSync,\n existsSync,\n mkdirSync,\n readdirSync,\n readFileSync,\n rmdirSync,\n rmSync,\n writeFileSync,\n} from 'node:fs';\nimport nodePath from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n// Get the directory of this module (for locating templates)\nconst __dirname = nodePath.dirname(fileURLToPath(import.meta.url));\n\n/**\n * Get path to bundled templates directory.\n * Works in both development (src/) and production (dist/) contexts.\n *\n * Note: We check for SAFEWORD.md to distinguish from src/templates/ which\n * contains TypeScript source files (config.ts, content.ts).\n *\n * Path resolution (bundled with tsup):\n * - From dist/chunk-*.js: __dirname = packages/cli/dist/ → ../templates\n */\nexport function getTemplatesDirectory(): string {\n const knownTemplateFile = 'SAFEWORD.md';\n\n // Try different relative paths - the bundled code ends up in dist/ directly (flat)\n // while source is in src/utils/\n const candidates = [\n nodePath.join(__dirname, '..', 'templates'), // From dist/ (flat bundled)\n nodePath.join(__dirname, '..', '..', 'templates'), // From src/utils/ or dist/utils/\n nodePath.join(__dirname, 'templates'), // Direct sibling (unlikely but safe)\n ];\n\n for (const candidate of candidates) {\n if (existsSync(nodePath.join(candidate, knownTemplateFile))) {\n return candidate;\n }\n }\n\n throw new Error('Templates directory not found');\n}\n\n/**\n * Check if a path exists\n * @param path\n */\nexport function exists(path: string): boolean {\n return existsSync(path);\n}\n\n/**\n * Create directory recursively\n * @param path\n */\nexport function ensureDirectory(path: string): void {\n if (!existsSync(path)) {\n mkdirSync(path, { recursive: true });\n }\n}\n\n/**\n * Read file as string\n * @param path\n */\nexport function readFile(path: string): string {\n return readFileSync(path, 'utf8');\n}\n\n/**\n * Read file as string, return null if not exists\n * @param path\n */\nexport function readFileSafe(path: string): string | undefined {\n if (!existsSync(path)) return undefined;\n return readFileSync(path, 'utf8');\n}\n\n/**\n * Write file, creating parent directories if needed\n * @param path\n * @param content\n */\nexport function writeFile(path: string, content: string): void {\n ensureDirectory(nodePath.dirname(path));\n writeFileSync(path, content);\n}\n\n/**\n * Remove file or directory recursively\n * @param path\n */\nexport function remove(path: string): void {\n if (existsSync(path)) {\n rmSync(path, { recursive: true, force: true });\n }\n}\n\n/**\n * Remove directory only if empty, returns true if removed\n * @param path\n */\nexport function removeIfEmpty(path: string): boolean {\n if (!existsSync(path)) return false;\n try {\n rmdirSync(path); // Non-recursive, throws if not empty\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Make all shell scripts in a directory executable\n * @param dirPath\n */\nexport function makeScriptsExecutable(dirPath: string): void {\n if (!existsSync(dirPath)) return;\n for (const file of readdirSync(dirPath)) {\n if (file.endsWith('.sh')) {\n chmodSync(nodePath.join(dirPath, file), 0o755);\n }\n }\n}\n\n/**\n * Read JSON file\n * @param path\n */\nexport function readJson(path: string): unknown {\n const content = readFileSafe(path);\n if (!content) return undefined;\n try {\n return JSON.parse(content) as unknown;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Write JSON file with formatting\n * @param path\n * @param data\n */\nexport function writeJson(path: string, data: unknown): void {\n writeFile(path, `${JSON.stringify(data, undefined, 2)}\\n`);\n}\n","/**\n * Console output utilities for consistent CLI messaging\n */\n\n/**\n * Print info message\n * @param message\n */\nexport function info(message: string): void {\n console.log(message);\n}\n\n/**\n * Print success message\n * @param message\n */\nexport function success(message: string): void {\n console.log(`✓ ${message}`);\n}\n\n/**\n * Print warning message\n * @param message\n */\nexport function warn(message: string): void {\n console.warn(`⚠ ${message}`);\n}\n\n/**\n * Print error message to stderr\n * @param message\n */\nexport function error(message: string): void {\n console.error(`✗ ${message}`);\n}\n\n/**\n * Print a blank line\n */\nexport function blank(): void {\n console.log('');\n}\n\n/**\n * Print a section header\n * @param title\n */\nexport function header(title: string): void {\n console.log(`\\n${title}`);\n console.log('─'.repeat(title.length));\n}\n\n/**\n * Print a list item\n * @param item\n * @param indent\n */\nexport function listItem(item: string, indent = 2): void {\n console.log(`${' '.repeat(indent)}• ${item}`);\n}\n\n/**\n * Print key-value pair\n * @param key\n * @param value\n */\nexport function keyValue(key: string, value: string): void {\n console.log(` ${key}: ${value}`);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,95 +1,22 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VERSION
|
|
3
3
|
} from "./chunk-ORQHKDT2.js";
|
|
4
|
-
|
|
5
|
-
// src/utils/fs.ts
|
|
6
4
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
function getTemplatesDirectory() {
|
|
20
|
-
const knownTemplateFile = "SAFEWORD.md";
|
|
21
|
-
const candidates = [
|
|
22
|
-
nodePath.join(__dirname, "..", "templates"),
|
|
23
|
-
// From dist/ (flat bundled)
|
|
24
|
-
nodePath.join(__dirname, "..", "..", "templates"),
|
|
25
|
-
// From src/utils/ or dist/utils/
|
|
26
|
-
nodePath.join(__dirname, "templates")
|
|
27
|
-
// Direct sibling (unlikely but safe)
|
|
28
|
-
];
|
|
29
|
-
for (const candidate of candidates) {
|
|
30
|
-
if (existsSync(nodePath.join(candidate, knownTemplateFile))) {
|
|
31
|
-
return candidate;
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
throw new Error("Templates directory not found");
|
|
35
|
-
}
|
|
36
|
-
function exists(path) {
|
|
37
|
-
return existsSync(path);
|
|
38
|
-
}
|
|
39
|
-
function ensureDirectory(path) {
|
|
40
|
-
if (!existsSync(path)) {
|
|
41
|
-
mkdirSync(path, { recursive: true });
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
function readFile(path) {
|
|
45
|
-
return readFileSync(path, "utf8");
|
|
46
|
-
}
|
|
47
|
-
function readFileSafe(path) {
|
|
48
|
-
if (!existsSync(path)) return void 0;
|
|
49
|
-
return readFileSync(path, "utf8");
|
|
50
|
-
}
|
|
51
|
-
function writeFile(path, content) {
|
|
52
|
-
ensureDirectory(nodePath.dirname(path));
|
|
53
|
-
writeFileSync(path, content);
|
|
54
|
-
}
|
|
55
|
-
function remove(path) {
|
|
56
|
-
if (existsSync(path)) {
|
|
57
|
-
rmSync(path, { recursive: true, force: true });
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function removeIfEmpty(path) {
|
|
61
|
-
if (!existsSync(path)) return false;
|
|
62
|
-
try {
|
|
63
|
-
rmdirSync(path);
|
|
64
|
-
return true;
|
|
65
|
-
} catch {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function makeScriptsExecutable(dirPath) {
|
|
70
|
-
if (!existsSync(dirPath)) return;
|
|
71
|
-
for (const file of readdirSync(dirPath)) {
|
|
72
|
-
if (file.endsWith(".sh")) {
|
|
73
|
-
chmodSync(nodePath.join(dirPath, file), 493);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function readJson(path) {
|
|
78
|
-
const content = readFileSafe(path);
|
|
79
|
-
if (!content) return void 0;
|
|
80
|
-
try {
|
|
81
|
-
return JSON.parse(content);
|
|
82
|
-
} catch {
|
|
83
|
-
return void 0;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
function writeJson(path, data) {
|
|
87
|
-
writeFile(path, `${JSON.stringify(data, void 0, 2)}
|
|
88
|
-
`);
|
|
89
|
-
}
|
|
5
|
+
ensureDirectory,
|
|
6
|
+
exists,
|
|
7
|
+
getTemplatesDirectory,
|
|
8
|
+
makeScriptsExecutable,
|
|
9
|
+
readFile,
|
|
10
|
+
readFileSafe,
|
|
11
|
+
readJson,
|
|
12
|
+
remove,
|
|
13
|
+
removeIfEmpty,
|
|
14
|
+
writeFile,
|
|
15
|
+
writeJson
|
|
16
|
+
} from "./chunk-DYLHQBW3.js";
|
|
90
17
|
|
|
91
18
|
// src/reconcile.ts
|
|
92
|
-
import
|
|
19
|
+
import nodePath from "path";
|
|
93
20
|
var HUSKY_DIR = ".husky";
|
|
94
21
|
function shouldSkipForNonGit(path, isGitRepo2) {
|
|
95
22
|
return path.startsWith(HUSKY_DIR) && !isGitRepo2;
|
|
@@ -99,7 +26,7 @@ function planMissingDirectories(directories, cwd, isGitRepo2) {
|
|
|
99
26
|
const created = [];
|
|
100
27
|
for (const dir of directories) {
|
|
101
28
|
if (shouldSkipForNonGit(dir, isGitRepo2)) continue;
|
|
102
|
-
if (!exists(
|
|
29
|
+
if (!exists(nodePath.join(cwd, dir))) {
|
|
103
30
|
actions.push({ type: "mkdir", path: dir });
|
|
104
31
|
created.push(dir);
|
|
105
32
|
}
|
|
@@ -110,7 +37,7 @@ function planTextPatches(patches, cwd, isGitRepo2) {
|
|
|
110
37
|
const actions = [];
|
|
111
38
|
for (const [filePath, definition] of Object.entries(patches)) {
|
|
112
39
|
if (shouldSkipForNonGit(filePath, isGitRepo2)) continue;
|
|
113
|
-
const content = readFileSafe(
|
|
40
|
+
const content = readFileSafe(nodePath.join(cwd, filePath)) ?? "";
|
|
114
41
|
if (!content.includes(definition.marker)) {
|
|
115
42
|
actions.push({ type: "text-patch", path: filePath, definition });
|
|
116
43
|
}
|
|
@@ -132,7 +59,7 @@ function planManagedFileWrites(files, ctx) {
|
|
|
132
59
|
const actions = [];
|
|
133
60
|
const created = [];
|
|
134
61
|
for (const [filePath, definition] of Object.entries(files)) {
|
|
135
|
-
if (exists(
|
|
62
|
+
if (exists(nodePath.join(ctx.cwd, filePath))) continue;
|
|
136
63
|
const content = resolveFileContent(definition, ctx);
|
|
137
64
|
actions.push({ type: "write", path: filePath, content });
|
|
138
65
|
created.push(filePath);
|
|
@@ -145,7 +72,7 @@ function planTextPatchesWithCreation(patches, ctx) {
|
|
|
145
72
|
for (const [filePath, definition] of Object.entries(patches)) {
|
|
146
73
|
if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
|
|
147
74
|
actions.push({ type: "text-patch", path: filePath, definition });
|
|
148
|
-
if (definition.createIfMissing && !exists(
|
|
75
|
+
if (definition.createIfMissing && !exists(nodePath.join(ctx.cwd, filePath))) {
|
|
149
76
|
created.push(filePath);
|
|
150
77
|
}
|
|
151
78
|
}
|
|
@@ -155,7 +82,7 @@ function planExistingDirectoriesRemoval(directories, cwd) {
|
|
|
155
82
|
const actions = [];
|
|
156
83
|
const removed = [];
|
|
157
84
|
for (const dir of directories) {
|
|
158
|
-
if (exists(
|
|
85
|
+
if (exists(nodePath.join(cwd, dir))) {
|
|
159
86
|
actions.push({ type: "rmdir", path: dir });
|
|
160
87
|
removed.push(dir);
|
|
161
88
|
}
|
|
@@ -166,7 +93,7 @@ function planExistingFilesRemoval(files, cwd) {
|
|
|
166
93
|
const actions = [];
|
|
167
94
|
const removed = [];
|
|
168
95
|
for (const filePath of files) {
|
|
169
|
-
if (exists(
|
|
96
|
+
if (exists(nodePath.join(cwd, filePath))) {
|
|
170
97
|
actions.push({ type: "rm", path: filePath });
|
|
171
98
|
removed.push(filePath);
|
|
172
99
|
}
|
|
@@ -210,7 +137,7 @@ function planDeprecatedFilesRemoval(deprecatedFiles, cwd) {
|
|
|
210
137
|
const actions = [];
|
|
211
138
|
const removed = [];
|
|
212
139
|
for (const filePath of deprecatedFiles) {
|
|
213
|
-
if (exists(
|
|
140
|
+
if (exists(nodePath.join(cwd, filePath))) {
|
|
214
141
|
actions.push({ type: "rm", path: filePath });
|
|
215
142
|
removed.push(filePath);
|
|
216
143
|
}
|
|
@@ -272,7 +199,7 @@ function computeUpgradePlan(schema, ctx) {
|
|
|
272
199
|
wouldCreate.push(...missingDirectories.created);
|
|
273
200
|
for (const [filePath, definition] of Object.entries(schema.ownedFiles)) {
|
|
274
201
|
if (shouldSkipForNonGit(filePath, ctx.isGitRepo)) continue;
|
|
275
|
-
const fullPath =
|
|
202
|
+
const fullPath = nodePath.join(ctx.cwd, filePath);
|
|
276
203
|
const newContent = resolveFileContent(definition, ctx);
|
|
277
204
|
if (!fileNeedsUpdate(fullPath, newContent)) continue;
|
|
278
205
|
actions.push({ type: "write", path: filePath, content: newContent });
|
|
@@ -283,7 +210,7 @@ function computeUpgradePlan(schema, ctx) {
|
|
|
283
210
|
}
|
|
284
211
|
}
|
|
285
212
|
for (const [filePath, definition] of Object.entries(schema.managedFiles)) {
|
|
286
|
-
const fullPath =
|
|
213
|
+
const fullPath = nodePath.join(ctx.cwd, filePath);
|
|
287
214
|
const newContent = resolveFileContent(definition, ctx);
|
|
288
215
|
if (!exists(fullPath)) {
|
|
289
216
|
actions.push({ type: "write", path: filePath, content: newContent });
|
|
@@ -341,7 +268,7 @@ function computeUninstallPlan(schema, ctx, full) {
|
|
|
341
268
|
actions.push({ type: "json-unmerge", path: filePath, definition });
|
|
342
269
|
}
|
|
343
270
|
for (const [filePath, definition] of Object.entries(schema.textPatches)) {
|
|
344
|
-
const fullPath =
|
|
271
|
+
const fullPath = nodePath.join(ctx.cwd, filePath);
|
|
345
272
|
if (exists(fullPath)) {
|
|
346
273
|
const content = readFileSafe(fullPath) ?? "";
|
|
347
274
|
if (content.includes(definition.marker)) {
|
|
@@ -382,17 +309,17 @@ function executePlan(plan, ctx) {
|
|
|
382
309
|
}
|
|
383
310
|
function executeChmod(cwd, paths) {
|
|
384
311
|
for (const path of paths) {
|
|
385
|
-
const fullPath =
|
|
312
|
+
const fullPath = nodePath.join(cwd, path);
|
|
386
313
|
if (exists(fullPath)) makeScriptsExecutable(fullPath);
|
|
387
314
|
}
|
|
388
315
|
}
|
|
389
316
|
function executeRmdir(cwd, path, result) {
|
|
390
|
-
if (removeIfEmpty(
|
|
317
|
+
if (removeIfEmpty(nodePath.join(cwd, path))) result.removed.push(path);
|
|
391
318
|
}
|
|
392
319
|
function executeAction(action, ctx, result) {
|
|
393
320
|
switch (action.type) {
|
|
394
321
|
case "mkdir":
|
|
395
|
-
ensureDirectory(
|
|
322
|
+
ensureDirectory(nodePath.join(ctx.cwd, action.path));
|
|
396
323
|
result.created.push(action.path);
|
|
397
324
|
break;
|
|
398
325
|
case "rmdir":
|
|
@@ -402,7 +329,7 @@ function executeAction(action, ctx, result) {
|
|
|
402
329
|
executeWrite(ctx.cwd, action.path, action.content, result);
|
|
403
330
|
break;
|
|
404
331
|
case "rm":
|
|
405
|
-
remove(
|
|
332
|
+
remove(nodePath.join(ctx.cwd, action.path));
|
|
406
333
|
result.removed.push(action.path);
|
|
407
334
|
break;
|
|
408
335
|
case "chmod":
|
|
@@ -423,7 +350,7 @@ function executeAction(action, ctx, result) {
|
|
|
423
350
|
}
|
|
424
351
|
}
|
|
425
352
|
function executeWrite(cwd, path, content, result) {
|
|
426
|
-
const fullPath =
|
|
353
|
+
const fullPath = nodePath.join(cwd, path);
|
|
427
354
|
const existed = exists(fullPath);
|
|
428
355
|
writeFile(fullPath, content);
|
|
429
356
|
(existed ? result.updated : result.created).push(path);
|
|
@@ -431,7 +358,7 @@ function executeWrite(cwd, path, content, result) {
|
|
|
431
358
|
function resolveFileContent(definition, ctx) {
|
|
432
359
|
if (definition.template) {
|
|
433
360
|
const templatesDirectory = getTemplatesDirectory();
|
|
434
|
-
return readFile(
|
|
361
|
+
return readFile(nodePath.join(templatesDirectory, definition.template));
|
|
435
362
|
}
|
|
436
363
|
if (definition.content) {
|
|
437
364
|
return typeof definition.content === "function" ? definition.content() : definition.content;
|
|
@@ -469,14 +396,14 @@ function computePackagesToRemove(schema, projectType, installedDevelopmentDeps)
|
|
|
469
396
|
return safewordPackages.filter((pkg) => pkg in installedDevelopmentDeps);
|
|
470
397
|
}
|
|
471
398
|
function executeJsonMerge(cwd, path, definition, ctx) {
|
|
472
|
-
const fullPath =
|
|
399
|
+
const fullPath = nodePath.join(cwd, path);
|
|
473
400
|
const existing = readJson(fullPath) ?? {};
|
|
474
401
|
const merged = definition.merge(existing, ctx);
|
|
475
402
|
if (JSON.stringify(existing) === JSON.stringify(merged)) return;
|
|
476
403
|
writeJson(fullPath, merged);
|
|
477
404
|
}
|
|
478
405
|
function executeJsonUnmerge(cwd, path, definition) {
|
|
479
|
-
const fullPath =
|
|
406
|
+
const fullPath = nodePath.join(cwd, path);
|
|
480
407
|
if (!exists(fullPath)) return;
|
|
481
408
|
const existing = readJson(fullPath);
|
|
482
409
|
if (!existing) return;
|
|
@@ -491,14 +418,14 @@ function executeJsonUnmerge(cwd, path, definition) {
|
|
|
491
418
|
writeJson(fullPath, unmerged);
|
|
492
419
|
}
|
|
493
420
|
function executeTextPatch(cwd, path, definition) {
|
|
494
|
-
const fullPath =
|
|
421
|
+
const fullPath = nodePath.join(cwd, path);
|
|
495
422
|
let content = readFileSafe(fullPath) ?? "";
|
|
496
423
|
if (content.includes(definition.marker)) return;
|
|
497
424
|
content = definition.operation === "prepend" ? definition.content + content : content + definition.content;
|
|
498
425
|
writeFile(fullPath, content);
|
|
499
426
|
}
|
|
500
427
|
function executeTextUnpatch(cwd, path, definition) {
|
|
501
|
-
const fullPath =
|
|
428
|
+
const fullPath = nodePath.join(cwd, path);
|
|
502
429
|
const content = readFileSafe(fullPath);
|
|
503
430
|
if (!content) return;
|
|
504
431
|
let unpatched = content.replace(definition.content, "");
|
|
@@ -549,13 +476,26 @@ const configs = [
|
|
|
549
476
|
...baseConfig,
|
|
550
477
|
];
|
|
551
478
|
|
|
552
|
-
// Add
|
|
479
|
+
// Add configs for detected tools/frameworks
|
|
553
480
|
if (deps["vitest"]) {
|
|
554
481
|
configs.push(...safeword.configs.vitest);
|
|
555
482
|
}
|
|
556
483
|
if (deps["playwright"] || deps["@playwright/test"]) {
|
|
557
484
|
configs.push(...safeword.configs.playwright);
|
|
558
485
|
}
|
|
486
|
+
if (deps["tailwindcss"]) {
|
|
487
|
+
configs.push(...safeword.configs.tailwind);
|
|
488
|
+
}
|
|
489
|
+
const tanstackQueryPackages = [
|
|
490
|
+
"@tanstack/react-query",
|
|
491
|
+
"@tanstack/vue-query",
|
|
492
|
+
"@tanstack/solid-query",
|
|
493
|
+
"@tanstack/svelte-query",
|
|
494
|
+
"@tanstack/angular-query-experimental",
|
|
495
|
+
];
|
|
496
|
+
if (tanstackQueryPackages.some(pkg => deps[pkg])) {
|
|
497
|
+
configs.push(...safeword.configs.tanstackQuery);
|
|
498
|
+
}
|
|
559
499
|
|
|
560
500
|
// eslint-config-prettier must be last to disable conflicting rules
|
|
561
501
|
configs.push(eslintConfigPrettier);
|
|
@@ -822,6 +762,7 @@ var SAFEWORD_SCHEMA = {
|
|
|
822
762
|
template: "skills/safeword-writing-plans/SKILL.md"
|
|
823
763
|
},
|
|
824
764
|
".claude/commands/architecture.md": { template: "commands/architecture.md" },
|
|
765
|
+
".claude/commands/audit.md": { template: "commands/audit.md" },
|
|
825
766
|
".claude/commands/cleanup-zombies.md": { template: "commands/cleanup-zombies.md" },
|
|
826
767
|
".claude/commands/lint.md": { template: "commands/lint.md" },
|
|
827
768
|
".claude/commands/quality-review.md": { template: "commands/quality-review.md" },
|
|
@@ -845,8 +786,9 @@ var SAFEWORD_SCHEMA = {
|
|
|
845
786
|
".cursor/rules/safeword-writing-plans.mdc": {
|
|
846
787
|
template: "cursor/rules/safeword-writing-plans.mdc"
|
|
847
788
|
},
|
|
848
|
-
// Cursor commands (
|
|
789
|
+
// Cursor commands (5 files - same as Claude)
|
|
849
790
|
".cursor/commands/architecture.md": { template: "commands/architecture.md" },
|
|
791
|
+
".cursor/commands/audit.md": { template: "commands/audit.md" },
|
|
850
792
|
".cursor/commands/cleanup-zombies.md": { template: "commands/cleanup-zombies.md" },
|
|
851
793
|
".cursor/commands/lint.md": { template: "commands/lint.md" },
|
|
852
794
|
".cursor/commands/quality-review.md": { template: "commands/quality-review.md" },
|
|
@@ -883,6 +825,17 @@ var SAFEWORD_SCHEMA = {
|
|
|
883
825
|
2
|
|
884
826
|
);
|
|
885
827
|
}
|
|
828
|
+
},
|
|
829
|
+
// Knip config for dead code detection (used by /audit)
|
|
830
|
+
"knip.json": {
|
|
831
|
+
generator: () => JSON.stringify(
|
|
832
|
+
{
|
|
833
|
+
ignore: [".safeword/**"],
|
|
834
|
+
ignoreDependencies: ["eslint-plugin-safeword"]
|
|
835
|
+
},
|
|
836
|
+
void 0,
|
|
837
|
+
2
|
|
838
|
+
)
|
|
886
839
|
}
|
|
887
840
|
},
|
|
888
841
|
// JSON files where we merge specific keys
|
|
@@ -1092,7 +1045,8 @@ var SAFEWORD_SCHEMA = {
|
|
|
1092
1045
|
"prettier",
|
|
1093
1046
|
// Safeword plugin (bundles eslint-config-prettier + all ESLint plugins)
|
|
1094
1047
|
"eslint-plugin-safeword",
|
|
1095
|
-
//
|
|
1048
|
+
// Architecture and dead code tools (used by /audit)
|
|
1049
|
+
"dependency-cruiser",
|
|
1096
1050
|
"knip"
|
|
1097
1051
|
],
|
|
1098
1052
|
conditional: {
|
|
@@ -1107,28 +1061,28 @@ var SAFEWORD_SCHEMA = {
|
|
|
1107
1061
|
};
|
|
1108
1062
|
|
|
1109
1063
|
// src/utils/git.ts
|
|
1110
|
-
import
|
|
1064
|
+
import nodePath2 from "path";
|
|
1111
1065
|
function isGitRepo(cwd) {
|
|
1112
|
-
return exists(
|
|
1066
|
+
return exists(nodePath2.join(cwd, ".git"));
|
|
1113
1067
|
}
|
|
1114
1068
|
|
|
1115
1069
|
// src/utils/context.ts
|
|
1116
|
-
import
|
|
1070
|
+
import nodePath4 from "path";
|
|
1117
1071
|
|
|
1118
1072
|
// src/utils/project-detector.ts
|
|
1119
|
-
import { readdirSync
|
|
1120
|
-
import
|
|
1073
|
+
import { readdirSync } from "fs";
|
|
1074
|
+
import nodePath3 from "path";
|
|
1121
1075
|
function hasShellScripts(cwd, maxDepth = 4) {
|
|
1122
1076
|
const excludeDirectories = /* @__PURE__ */ new Set(["node_modules", ".git", ".safeword"]);
|
|
1123
1077
|
function scan(dir, depth) {
|
|
1124
1078
|
if (depth > maxDepth) return false;
|
|
1125
1079
|
try {
|
|
1126
|
-
const entries =
|
|
1080
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
1127
1081
|
for (const entry of entries) {
|
|
1128
1082
|
if (entry.isFile() && entry.name.endsWith(".sh")) {
|
|
1129
1083
|
return true;
|
|
1130
1084
|
}
|
|
1131
|
-
if (entry.isDirectory() && !excludeDirectories.has(entry.name) && scan(
|
|
1085
|
+
if (entry.isDirectory() && !excludeDirectories.has(entry.name) && scan(nodePath3.join(dir, entry.name), depth + 1)) {
|
|
1132
1086
|
return true;
|
|
1133
1087
|
}
|
|
1134
1088
|
}
|
|
@@ -1168,7 +1122,7 @@ function detectProjectType(packageJson, cwd) {
|
|
|
1168
1122
|
|
|
1169
1123
|
// src/utils/context.ts
|
|
1170
1124
|
function createProjectContext(cwd) {
|
|
1171
|
-
const packageJson = readJson(
|
|
1125
|
+
const packageJson = readJson(nodePath4.join(cwd, "package.json"));
|
|
1172
1126
|
return {
|
|
1173
1127
|
cwd,
|
|
1174
1128
|
projectType: detectProjectType(packageJson ?? {}, cwd),
|
|
@@ -1177,45 +1131,10 @@ function createProjectContext(cwd) {
|
|
|
1177
1131
|
};
|
|
1178
1132
|
}
|
|
1179
1133
|
|
|
1180
|
-
// src/utils/output.ts
|
|
1181
|
-
function info(message) {
|
|
1182
|
-
console.log(message);
|
|
1183
|
-
}
|
|
1184
|
-
function success(message) {
|
|
1185
|
-
console.log(`\u2713 ${message}`);
|
|
1186
|
-
}
|
|
1187
|
-
function warn(message) {
|
|
1188
|
-
console.warn(`\u26A0 ${message}`);
|
|
1189
|
-
}
|
|
1190
|
-
function error(message) {
|
|
1191
|
-
console.error(`\u2717 ${message}`);
|
|
1192
|
-
}
|
|
1193
|
-
function header(title) {
|
|
1194
|
-
console.log(`
|
|
1195
|
-
${title}`);
|
|
1196
|
-
console.log("\u2500".repeat(title.length));
|
|
1197
|
-
}
|
|
1198
|
-
function listItem(item, indent = 2) {
|
|
1199
|
-
console.log(`${" ".repeat(indent)}\u2022 ${item}`);
|
|
1200
|
-
}
|
|
1201
|
-
function keyValue(key, value) {
|
|
1202
|
-
console.log(` ${key}: ${value}`);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
1134
|
export {
|
|
1206
|
-
exists,
|
|
1207
|
-
readFileSafe,
|
|
1208
|
-
writeJson,
|
|
1209
1135
|
reconcile,
|
|
1210
1136
|
SAFEWORD_SCHEMA,
|
|
1211
1137
|
isGitRepo,
|
|
1212
|
-
createProjectContext
|
|
1213
|
-
info,
|
|
1214
|
-
success,
|
|
1215
|
-
warn,
|
|
1216
|
-
error,
|
|
1217
|
-
header,
|
|
1218
|
-
listItem,
|
|
1219
|
-
keyValue
|
|
1138
|
+
createProjectContext
|
|
1220
1139
|
};
|
|
1221
|
-
//# sourceMappingURL=chunk-
|
|
1140
|
+
//# sourceMappingURL=chunk-KL2JTWK6.js.map
|