safeword 0.25.13 → 0.26.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-JAQRD5VU.js → check-6763ESQ6.js} +4 -6
- package/dist/{check-JAQRD5VU.js.map → check-6763ESQ6.js.map} +1 -1
- package/dist/{chunk-GRUI64JZ.js → chunk-5IOM3XLW.js} +3 -5
- package/dist/{chunk-GRUI64JZ.js.map → chunk-5IOM3XLW.js.map} +1 -1
- package/dist/{chunk-MJCCBV7R.js → chunk-ES4B7HMP.js} +2 -2
- package/dist/{chunk-GWIDTSXV.js → chunk-IBCIBEYM.js} +26 -15
- package/dist/{chunk-GWIDTSXV.js.map → chunk-IBCIBEYM.js.map} +1 -1
- package/dist/{chunk-GXHO3A37.js → chunk-NINE7AWZ.js} +903 -277
- package/dist/chunk-NINE7AWZ.js.map +1 -0
- package/dist/cli.js +6 -6
- package/dist/{diff-P7ZV5RWW.js → diff-O6NACWXR.js} +3 -3
- package/dist/{reset-WIFAUYZR.js → reset-P5QGTALW.js} +3 -3
- package/dist/{setup-OCWWC5D5.js → setup-TGFZXS4G.js} +9 -11
- package/dist/{setup-OCWWC5D5.js.map → setup-TGFZXS4G.js.map} +1 -1
- package/dist/{sync-config-YE2ZUC5B.js → sync-config-CSCYCDJE.js} +3 -3
- package/dist/{upgrade-4UJRAAPU.js → upgrade-WL6K6B6O.js} +18 -18
- package/dist/upgrade-WL6K6B6O.js.map +1 -0
- package/package.json +2 -2
- package/templates/hooks/lib/active-ticket.ts +1 -1
- package/templates/hooks/lib/lint.ts +36 -31
- package/templates/hooks/lib/test-runner.ts +112 -0
- package/templates/hooks/post-tool-lint.ts +21 -4
- package/templates/hooks/post-tool-quality.ts +5 -5
- package/templates/hooks/pre-tool-quality.ts +3 -3
- package/templates/hooks/stop-quality.ts +127 -94
- package/dist/chunk-2RK4OKUL.js +0 -276
- package/dist/chunk-2RK4OKUL.js.map +0 -1
- package/dist/chunk-GXHO3A37.js.map +0 -1
- package/dist/upgrade-4UJRAAPU.js.map +0 -1
- /package/dist/{chunk-MJCCBV7R.js.map → chunk-ES4B7HMP.js.map} +0 -0
- /package/dist/{diff-P7ZV5RWW.js.map → diff-O6NACWXR.js.map} +0 -0
- /package/dist/{reset-WIFAUYZR.js.map → reset-P5QGTALW.js.map} +0 -0
- /package/dist/{sync-config-YE2ZUC5B.js.map → sync-config-CSCYCDJE.js.map} +0 -0
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
isNewerVersion
|
|
3
3
|
} from "./chunk-FJYRWU2V.js";
|
|
4
|
-
import {
|
|
5
|
-
getMissingPacks
|
|
6
|
-
} from "./chunk-2RK4OKUL.js";
|
|
7
4
|
import {
|
|
8
5
|
SAFEWORD_SCHEMA,
|
|
9
6
|
createProjectContext,
|
|
7
|
+
getMissingPacks,
|
|
10
8
|
reconcile
|
|
11
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-NINE7AWZ.js";
|
|
12
10
|
import "./chunk-Q4VPBNNZ.js";
|
|
13
11
|
import {
|
|
14
12
|
VERSION
|
|
@@ -22,7 +20,7 @@ import {
|
|
|
22
20
|
readFileSafe,
|
|
23
21
|
success,
|
|
24
22
|
warn
|
|
25
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-IBCIBEYM.js";
|
|
26
24
|
|
|
27
25
|
// src/commands/check.ts
|
|
28
26
|
import nodePath from "path";
|
|
@@ -189,4 +187,4 @@ async function check(options) {
|
|
|
189
187
|
export {
|
|
190
188
|
check
|
|
191
189
|
};
|
|
192
|
-
//# sourceMappingURL=check-
|
|
190
|
+
//# sourceMappingURL=check-6763ESQ6.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 { getMissingPacks } from '../packs/registry.js';\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, listItem, success, warn } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\ninterface 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 missingPacks: 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 missingPacks: [],\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, {\n dryRun: true,\n });\n\n // Collect issues from write actions and text patches\n // Filter out chmod (paths[] instead of path) and json-merge/unmerge (incompatible definition)\n const actionsWithPath = result.actions.filter(\n (\n a,\n ): a is Exclude<\n (typeof result.actions)[number],\n { type: 'chmod' } | { type: 'json-merge' } | { type: 'json-unmerge' }\n > => a.type !== 'chmod' && a.type !== 'json-merge' && a.type !== 'json-unmerge',\n );\n const issues: string[] = [\n ...findMissingFiles(cwd, actionsWithPath),\n ...findMissingPatches(cwd, actionsWithPath),\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 // Check for missing language packs\n const missingPacks = getMissingPacks(cwd);\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues,\n missingPackages: result.packagesToInstall,\n missingPacks,\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 `bunx safeword@latest upgrade` 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 * @returns true if there are issues requiring attention\n */\nfunction reportHealthSummary(health: HealthStatus): boolean {\n // Check missing packs first (highest priority - explains missing files)\n if (health.missingPacks.length > 0) {\n header('Missing Language Packs');\n for (const pack of health.missingPacks) {\n listItem(`${pack} pack not installed`);\n }\n info('\\nRun `safeword upgrade` to install missing packs');\n return true;\n }\n\n if (health.missingPackages.length > 0) {\n header('Missing Packages');\n for (const pkg of health.missingPackages) listItem(pkg);\n info('\\nRun `safeword upgrade` to install missing packages');\n return true;\n }\n\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 true;\n }\n\n success('\\nConfiguration is healthy');\n return false;\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 const hasIssues = reportHealthSummary(health);\n\n if (hasIssues) {\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|
|
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 { getMissingPacks } from '../packs/registry.js';\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, listItem, success, warn } from '../utils/output.js';\nimport { isNewerVersion } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\ninterface 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 missingPacks: 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 missingPacks: [],\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, {\n dryRun: true,\n });\n\n // Collect issues from write actions and text patches\n // Filter out chmod (paths[] instead of path) and json-merge/unmerge (incompatible definition)\n const actionsWithPath = result.actions.filter(\n (\n a,\n ): a is Exclude<\n (typeof result.actions)[number],\n { type: 'chmod' } | { type: 'json-merge' } | { type: 'json-unmerge' }\n > => a.type !== 'chmod' && a.type !== 'json-merge' && a.type !== 'json-unmerge',\n );\n const issues: string[] = [\n ...findMissingFiles(cwd, actionsWithPath),\n ...findMissingPatches(cwd, actionsWithPath),\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 // Check for missing language packs\n const missingPacks = getMissingPacks(cwd);\n\n return {\n configured: true,\n projectVersion,\n cliVersion: VERSION,\n updateAvailable: false,\n latestVersion: undefined,\n issues,\n missingPackages: result.packagesToInstall,\n missingPacks,\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 `bunx safeword@latest upgrade` 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 * @returns true if there are issues requiring attention\n */\nfunction reportHealthSummary(health: HealthStatus): boolean {\n // Check missing packs first (highest priority - explains missing files)\n if (health.missingPacks.length > 0) {\n header('Missing Language Packs');\n for (const pack of health.missingPacks) {\n listItem(`${pack} pack not installed`);\n }\n info('\\nRun `safeword upgrade` to install missing packs');\n return true;\n }\n\n if (health.missingPackages.length > 0) {\n header('Missing Packages');\n for (const pkg of health.missingPackages) listItem(pkg);\n info('\\nRun `safeword upgrade` to install missing packages');\n return true;\n }\n\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 true;\n }\n\n success('\\nConfiguration is healthy');\n return false;\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 const hasIssues = reportHealthSummary(health);\n\n if (hasIssues) {\n process.exit(1);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LANGUAGE_PACKS,
|
|
3
3
|
addInstalledPack,
|
|
4
|
+
isGitRepo,
|
|
4
5
|
isPackInstalled
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
isGitRepo
|
|
8
|
-
} from "./chunk-GXHO3A37.js";
|
|
6
|
+
} from "./chunk-NINE7AWZ.js";
|
|
9
7
|
|
|
10
8
|
// src/packs/install.ts
|
|
11
9
|
function installPack(packId, cwd) {
|
|
@@ -23,4 +21,4 @@ function installPack(packId, cwd) {
|
|
|
23
21
|
export {
|
|
24
22
|
installPack
|
|
25
23
|
};
|
|
26
|
-
//# sourceMappingURL=chunk-
|
|
24
|
+
//# sourceMappingURL=chunk-5IOM3XLW.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/packs/install.ts"],"sourcesContent":["/**\n * Pack Installation\n *\n * Install language packs and update config.\n */\n\nimport { isGitRepo } from '../utils/git.js';\nimport { addInstalledPack, isPackInstalled } from './config.js';\nimport { LANGUAGE_PACKS } from './registry.js';\n\n/**\n * Install a language pack.\n *\n * Runs the pack's setup function and updates config.json.\n * Idempotent - does nothing if pack is already installed.\n *\n * @param packId - Pack ID to install (e.g., 'python')\n * @param cwd - Project root directory\n * @throws Error if pack ID is unknown\n */\nexport function installPack(packId: string, cwd: string): void {\n // Idempotent - skip if already installed\n if (isPackInstalled(cwd, packId)) {\n return;\n }\n\n const pack = LANGUAGE_PACKS[packId];\n if (!pack) {\n throw new Error(`Unknown pack: ${packId}`);\n }\n\n pack.setup(cwd, { isGitRepo: isGitRepo(cwd) });\n addInstalledPack(cwd, packId);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/packs/install.ts"],"sourcesContent":["/**\n * Pack Installation\n *\n * Install language packs and update config.\n */\n\nimport { isGitRepo } from '../utils/git.js';\nimport { addInstalledPack, isPackInstalled } from './config.js';\nimport { LANGUAGE_PACKS } from './registry.js';\n\n/**\n * Install a language pack.\n *\n * Runs the pack's setup function and updates config.json.\n * Idempotent - does nothing if pack is already installed.\n *\n * @param packId - Pack ID to install (e.g., 'python')\n * @param cwd - Project root directory\n * @throws Error if pack ID is unknown\n */\nexport function installPack(packId: string, cwd: string): void {\n // Idempotent - skip if already installed\n if (isPackInstalled(cwd, packId)) {\n return;\n }\n\n const pack = LANGUAGE_PACKS[packId];\n if (!pack) {\n throw new Error(`Unknown pack: ${packId}`);\n }\n\n pack.setup(cwd, { isGitRepo: isGitRepo(cwd) });\n addInstalledPack(cwd, packId);\n}\n"],"mappings":";;;;;;;;AAoBO,SAAS,YAAY,QAAgB,KAAmB;AAE7D,MAAI,gBAAgB,KAAK,MAAM,GAAG;AAChC;AAAA,EACF;AAEA,QAAM,OAAO,eAAe,MAAM;AAClC,MAAI,CAAC,MAAM;AACT,UAAM,IAAI,MAAM,iBAAiB,MAAM,EAAE;AAAA,EAC3C;AAEA,OAAK,MAAM,KAAK,EAAE,WAAW,UAAU,GAAG,EAAE,CAAC;AAC7C,mBAAiB,KAAK,MAAM;AAC9B;","names":[]}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
info,
|
|
5
5
|
readJson,
|
|
6
6
|
success
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-IBCIBEYM.js";
|
|
8
8
|
|
|
9
9
|
// src/commands/sync-config.ts
|
|
10
10
|
import { writeFileSync } from "fs";
|
|
@@ -280,4 +280,4 @@ export {
|
|
|
280
280
|
hasArchitectureDetected,
|
|
281
281
|
syncConfig
|
|
282
282
|
};
|
|
283
|
-
//# sourceMappingURL=chunk-
|
|
283
|
+
//# sourceMappingURL=chunk-ES4B7HMP.js.map
|
|
@@ -49,24 +49,35 @@ var SUBDIRECTORY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
|
49
49
|
".venv",
|
|
50
50
|
"venv"
|
|
51
51
|
]);
|
|
52
|
-
function
|
|
53
|
-
return
|
|
52
|
+
function existsInTree(cwd, filename) {
|
|
53
|
+
return findInTree(cwd, filename) !== void 0;
|
|
54
54
|
}
|
|
55
|
-
function
|
|
55
|
+
function findInTree(cwd, filename, maxDepth = 10) {
|
|
56
56
|
if (existsSync(nodePath.join(cwd, filename))) {
|
|
57
57
|
return cwd;
|
|
58
58
|
}
|
|
59
|
+
return scanTreeForFile(cwd, filename, 1, maxDepth);
|
|
60
|
+
}
|
|
61
|
+
function getScannableSubdirectories(directory) {
|
|
59
62
|
try {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const subdirectory = nodePath.join(cwd, entry.name);
|
|
64
|
-
if (existsSync(nodePath.join(subdirectory, filename))) {
|
|
65
|
-
return subdirectory;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
63
|
+
return readdirSync(directory, { withFileTypes: true }).filter(
|
|
64
|
+
(entry) => entry.isDirectory() && !entry.name.startsWith(".") && !SUBDIRECTORY_EXCLUDE.has(entry.name)
|
|
65
|
+
).map((entry) => nodePath.join(directory, entry.name));
|
|
69
66
|
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
function scanTreeForFile(directory, filename, depth, maxDepth) {
|
|
71
|
+
if (depth > maxDepth) return void 0;
|
|
72
|
+
const subdirectories = getScannableSubdirectories(directory);
|
|
73
|
+
for (const subdirectory of subdirectories) {
|
|
74
|
+
if (existsSync(nodePath.join(subdirectory, filename))) {
|
|
75
|
+
return subdirectory;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
for (const subdirectory of subdirectories) {
|
|
79
|
+
const result = scanTreeForFile(subdirectory, filename, depth + 1, maxDepth);
|
|
80
|
+
if (result !== void 0) return result;
|
|
70
81
|
}
|
|
71
82
|
return void 0;
|
|
72
83
|
}
|
|
@@ -150,8 +161,8 @@ function keyValue(key, value) {
|
|
|
150
161
|
export {
|
|
151
162
|
getTemplatesDirectory,
|
|
152
163
|
exists,
|
|
153
|
-
|
|
154
|
-
|
|
164
|
+
existsInTree,
|
|
165
|
+
findInTree,
|
|
155
166
|
ensureDirectory,
|
|
156
167
|
readFile,
|
|
157
168
|
readFileSafe,
|
|
@@ -169,4 +180,4 @@ export {
|
|
|
169
180
|
listItem,
|
|
170
181
|
keyValue
|
|
171
182
|
};
|
|
172
|
-
//# sourceMappingURL=chunk-
|
|
183
|
+
//# sourceMappingURL=chunk-IBCIBEYM.js.map
|
|
@@ -1 +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';\n\n// Get the directory of this module (for locating templates)\nconst __dirname = import.meta.dirname;\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 * Directories to exclude when scanning subdirectories for language manifests.\n * These contain vendored/generated files that would cause false positives.\n */\nconst SUBDIRECTORY_EXCLUDE = new Set([\n 'node_modules',\n '.git',\n '.safeword',\n 'vendor',\n 'dist',\n 'build',\n 'target',\n 'coverage',\n 'dbt_packages',\n 'out',\n '.next',\n '.nuxt',\n '.output',\n '__pycache__',\n '.venv',\n 'venv',\n]);\n\n/**\n * Check if a file exists at the project root OR in any immediate subdirectory.\n * Useful for detecting language manifests in projects where a language lives\n * in a subdirectory (e.g., `dbt/pyproject.toml`).\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @returns true if found at root or in any immediate subdirectory\n */\nexport function existsShallow(cwd: string, filename: string): boolean {\n return findShallow(cwd, filename) !== undefined;\n}\n\n/**\n * Find a file at the project root OR in any immediate subdirectory.\n * Returns the directory containing the file, or undefined if not found.\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @returns Directory path where file was found, or undefined\n */\nexport function findShallow(cwd: string, filename: string): string | undefined {\n // Check root first\n if (existsSync(nodePath.join(cwd, filename))) {\n return cwd;\n }\n\n // Check immediate subdirectories\n try {\n const entries = readdirSync(cwd, { withFileTypes: true });\n for (const entry of entries) {\n if (\n entry.isDirectory() &&\n !entry.name.startsWith('.') &&\n !SUBDIRECTORY_EXCLUDE.has(entry.name)\n ) {\n const subdirectory = nodePath.join(cwd, entry.name);\n if (existsSync(nodePath.join(subdirectory, filename))) {\n return subdirectory;\n }\n }\n }\n } catch {\n // Ignore permission errors\n }\n\n return undefined;\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 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
|
+
{"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';\n\n// Get the directory of this module (for locating templates)\nconst __dirname = import.meta.dirname;\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 * Directories to exclude when scanning subdirectories for language manifests.\n * These contain vendored/generated files that would cause false positives.\n */\nconst SUBDIRECTORY_EXCLUDE = new Set([\n 'node_modules',\n '.git',\n '.safeword',\n 'vendor',\n 'dist',\n 'build',\n 'target',\n 'coverage',\n 'dbt_packages',\n 'out',\n '.next',\n '.nuxt',\n '.output',\n '__pycache__',\n '.venv',\n 'venv',\n]);\n\n/**\n * Check if a file exists anywhere in the project tree.\n * Recursively scans subdirectories, skipping excluded directories.\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @returns true if found anywhere in the project tree\n */\nexport function existsInTree(cwd: string, filename: string): boolean {\n return findInTree(cwd, filename) !== undefined;\n}\n\n/**\n * Find a file anywhere in the project tree.\n * Recursively scans subdirectories, skipping excluded directories.\n * Root is checked first; deeper matches use depth-first traversal.\n *\n * @param cwd - Project root directory\n * @param filename - File to search for (e.g., 'pyproject.toml')\n * @param maxDepth - Maximum directory depth to scan (default: 10)\n * @returns Directory path where file was found, or undefined\n */\nexport function findInTree(cwd: string, filename: string, maxDepth = 10): string | undefined {\n // Check root first\n if (existsSync(nodePath.join(cwd, filename))) {\n return cwd;\n }\n\n return scanTreeForFile(cwd, filename, 1, maxDepth);\n}\n\n/** Return scannable subdirectory paths (excludes hidden dirs and SUBDIRECTORY_EXCLUDE). */\nfunction getScannableSubdirectories(directory: string): string[] {\n try {\n return readdirSync(directory, { withFileTypes: true })\n .filter(\n entry =>\n entry.isDirectory() &&\n !entry.name.startsWith('.') &&\n !SUBDIRECTORY_EXCLUDE.has(entry.name),\n )\n .map(entry => nodePath.join(directory, entry.name));\n } catch {\n return [];\n }\n}\n\nfunction scanTreeForFile(\n directory: string,\n filename: string,\n depth: number,\n maxDepth: number,\n): string | undefined {\n if (depth > maxDepth) return undefined;\n\n const subdirectories = getScannableSubdirectories(directory);\n\n // Check all children at this level first\n for (const subdirectory of subdirectories) {\n if (existsSync(nodePath.join(subdirectory, filename))) {\n return subdirectory;\n }\n }\n\n // Then recurse deeper\n for (const subdirectory of subdirectories) {\n const result = scanTreeForFile(subdirectory, filename, depth + 1, maxDepth);\n if (result !== undefined) return result;\n }\n\n return undefined;\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 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":[]}
|