safeword 0.46.0 → 0.46.1
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.js
CHANGED
|
@@ -26,7 +26,7 @@ program.command("upgrade").description("Upgrade safeword configuration to latest
|
|
|
26
26
|
"--migrate-namespace",
|
|
27
27
|
"Move the legacy .safeword-project/ namespace to .project/ (recommended) without prompting"
|
|
28
28
|
).option("--no-migrate-namespace", "Keep the legacy namespace; skip the migration prompt").action(async (options) => {
|
|
29
|
-
const { upgrade } = await import("./upgrade-
|
|
29
|
+
const { upgrade } = await import("./upgrade-BQUEV44T.js");
|
|
30
30
|
await upgrade({
|
|
31
31
|
noModify: options.modify === false,
|
|
32
32
|
// Commander leaves the tri-state undefined when neither flag is passed.
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
untrackIgnoredFiles
|
|
23
23
|
} from "./chunk-6RATQAVM.js";
|
|
24
24
|
import {
|
|
25
|
+
NAMESPACE_ROOT_LEGACY,
|
|
25
26
|
readConfiguredPath
|
|
26
27
|
} from "./chunk-3BMVTFFM.js";
|
|
27
28
|
import "./chunk-LODQOJEK.js";
|
|
@@ -43,7 +44,7 @@ import {
|
|
|
43
44
|
} from "./chunk-445LAX4Y.js";
|
|
44
45
|
|
|
45
46
|
// src/commands/upgrade.ts
|
|
46
|
-
import
|
|
47
|
+
import nodePath3 from "path";
|
|
47
48
|
|
|
48
49
|
// src/utils/namespace-migration.ts
|
|
49
50
|
import { execSync } from "child_process";
|
|
@@ -112,13 +113,92 @@ function executeNamespaceMigration(cwd) {
|
|
|
112
113
|
return { method, rewrittenKeys: rewriteLegacyPathOverrides(cwd) };
|
|
113
114
|
}
|
|
114
115
|
|
|
116
|
+
// src/utils/stale-config-scan.ts
|
|
117
|
+
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
|
|
118
|
+
import nodePath2 from "path";
|
|
119
|
+
var LEGACY_REFERENCE = `${NAMESPACE_ROOT_LEGACY}/`;
|
|
120
|
+
var MANAGED_PRETTIER_MARKER = "# Safeword - managed prettier exclusions";
|
|
121
|
+
var WORKFLOWS_SUBPATH = [".github", "workflows"];
|
|
122
|
+
var CURATED_ROOT_CONFIGS = [
|
|
123
|
+
"eslint.config.ts",
|
|
124
|
+
"eslint.config.mjs",
|
|
125
|
+
"eslint.config.cjs",
|
|
126
|
+
"eslint.config.js",
|
|
127
|
+
".eslintrc",
|
|
128
|
+
".eslintrc.js",
|
|
129
|
+
".eslintrc.cjs",
|
|
130
|
+
".eslintrc.json",
|
|
131
|
+
".eslintrc.yml",
|
|
132
|
+
".eslintrc.yaml",
|
|
133
|
+
".prettierignore",
|
|
134
|
+
".prettierrc",
|
|
135
|
+
".prettierrc.json",
|
|
136
|
+
".prettierrc.js",
|
|
137
|
+
".prettierrc.cjs",
|
|
138
|
+
".prettierrc.yml",
|
|
139
|
+
".prettierrc.yaml",
|
|
140
|
+
"tsconfig.json",
|
|
141
|
+
"knip.json",
|
|
142
|
+
"knip.ts",
|
|
143
|
+
".dependency-cruiser.cjs",
|
|
144
|
+
".dependency-cruiser.js",
|
|
145
|
+
".jscpd.json"
|
|
146
|
+
];
|
|
147
|
+
function prettierignoreHasCustomerReference(content) {
|
|
148
|
+
let insideManagedBlock = false;
|
|
149
|
+
for (const line of content.split("\n")) {
|
|
150
|
+
if (line.includes(MANAGED_PRETTIER_MARKER)) {
|
|
151
|
+
insideManagedBlock = true;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (insideManagedBlock && line.trim() === "") {
|
|
155
|
+
insideManagedBlock = false;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (!insideManagedBlock && line.includes(LEGACY_REFERENCE)) return true;
|
|
159
|
+
}
|
|
160
|
+
return false;
|
|
161
|
+
}
|
|
162
|
+
function fileHasStaleReference(relativePath, content) {
|
|
163
|
+
if (nodePath2.basename(relativePath) === ".prettierignore") {
|
|
164
|
+
return prettierignoreHasCustomerReference(content);
|
|
165
|
+
}
|
|
166
|
+
return content.includes(LEGACY_REFERENCE);
|
|
167
|
+
}
|
|
168
|
+
function workflowConfigPaths(cwd) {
|
|
169
|
+
const directory = nodePath2.join(cwd, ...WORKFLOWS_SUBPATH);
|
|
170
|
+
let names;
|
|
171
|
+
try {
|
|
172
|
+
names = readdirSync(directory);
|
|
173
|
+
} catch {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
return names.filter((name) => name.endsWith(".yml") || name.endsWith(".yaml")).map((name) => `${WORKFLOWS_SUBPATH.join("/")}/${name}`);
|
|
177
|
+
}
|
|
178
|
+
function scanStaleNamespaceConfigs(cwd) {
|
|
179
|
+
const candidates = [...CURATED_ROOT_CONFIGS, ...workflowConfigPaths(cwd)];
|
|
180
|
+
const stale = [];
|
|
181
|
+
for (const relativePath of candidates) {
|
|
182
|
+
const fullPath = nodePath2.join(cwd, relativePath);
|
|
183
|
+
if (!existsSync2(fullPath)) continue;
|
|
184
|
+
let content;
|
|
185
|
+
try {
|
|
186
|
+
content = readFileSync2(fullPath, "utf8");
|
|
187
|
+
} catch {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (fileHasStaleReference(relativePath, content)) stale.push(relativePath);
|
|
191
|
+
}
|
|
192
|
+
return stale;
|
|
193
|
+
}
|
|
194
|
+
|
|
115
195
|
// src/commands/upgrade.ts
|
|
116
196
|
function getProjectVersion(safewordDirectory) {
|
|
117
|
-
const versionPath =
|
|
197
|
+
const versionPath = nodePath3.join(safewordDirectory, "version");
|
|
118
198
|
return readFileSafe(versionPath)?.trim() ?? "0.0.0";
|
|
119
199
|
}
|
|
120
200
|
function stripDeadConfigVersion(safewordDirectory) {
|
|
121
|
-
const configPath =
|
|
201
|
+
const configPath = nodePath3.join(safewordDirectory, "config.json");
|
|
122
202
|
const content = readFileSafe(configPath);
|
|
123
203
|
if (!content) return;
|
|
124
204
|
const parsed = JSON.parse(content);
|
|
@@ -242,11 +322,21 @@ async function maybeMigrateNamespace(cwd, options) {
|
|
|
242
322
|
warn(
|
|
243
323
|
`${migrationError instanceof Error ? migrationError.message : String(migrationError)} \u2014 continuing upgrade on .safeword-project/.`
|
|
244
324
|
);
|
|
325
|
+
return;
|
|
245
326
|
}
|
|
327
|
+
warnStaleToolingConfigs(cwd);
|
|
328
|
+
}
|
|
329
|
+
function warnStaleToolingConfigs(cwd) {
|
|
330
|
+
const stale = scanStaleNamespaceConfigs(cwd);
|
|
331
|
+
if (stale.length === 0) return;
|
|
332
|
+
warn(
|
|
333
|
+
"\nThese tooling configs still reference the old namespace (.safeword-project/ \u2192 .project/) \u2014 update them so your lint/CI keeps working:"
|
|
334
|
+
);
|
|
335
|
+
for (const file of stale) listItem(file);
|
|
246
336
|
}
|
|
247
337
|
async function upgrade(options) {
|
|
248
338
|
const cwd = process.cwd();
|
|
249
|
-
const safewordDirectory =
|
|
339
|
+
const safewordDirectory = nodePath3.join(cwd, ".safeword");
|
|
250
340
|
if (!exists(safewordDirectory)) {
|
|
251
341
|
error("Not configured. Run `safeword setup` first.");
|
|
252
342
|
process.exit(1);
|
|
@@ -299,4 +389,4 @@ export {
|
|
|
299
389
|
promptYesDefault,
|
|
300
390
|
upgrade
|
|
301
391
|
};
|
|
302
|
-
//# sourceMappingURL=upgrade-
|
|
392
|
+
//# sourceMappingURL=upgrade-BQUEV44T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/commands/upgrade.ts","../src/utils/namespace-migration.ts","../src/utils/stale-config-scan.ts"],"sourcesContent":["/**\n * Upgrade command - Update safeword configuration to latest version\n *\n * Uses reconcile() with mode='upgrade' to update all managed files.\n */\n\nimport nodePath from 'node:path';\n\nimport { migratePackId } from '../packs/config.js';\nimport { installPack } from '../packs/install.js';\nimport {\n detectPythonPackageManager,\n getPythonInstallCommand,\n hasRuffDependency,\n installPythonDependencies,\n} from '../packs/python/setup.js';\nimport { getMissingPacks } from '../packs/registry.js';\nimport { reconcile, type ReconcileResult } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA, SAFEWORD_TRANSIENT_PATHS } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { getEslintPeerMismatchWarning } from '../utils/eslint-peer-check.js';\nimport { exists, findInTree, readFileSafe, writeFile } from '../utils/fs.js';\nimport { untrackIgnoredFiles } from '../utils/git.js';\nimport { detectPackageManager, installDependencies } from '../utils/install.js';\nimport {\n executeNamespaceMigration,\n type MigrationPlan,\n type MigrationResult,\n planNamespaceMigration,\n} from '../utils/namespace-migration.js';\nimport { error, header, info, listItem, success, warn } from '../utils/output.js';\nimport { scanStaleNamespaceConfigs } from '../utils/stale-config-scan.js';\nimport { maybeAutoPatchOrNudge } from '../utils/vendored-ignores-nudge.js';\nimport { compareVersions } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\nfunction getProjectVersion(safewordDirectory: string): string {\n const versionPath = nodePath.join(safewordDirectory, 'version');\n return readFileSafe(versionPath)?.trim() ?? '0.0.0';\n}\n\n// Ticket 154: strip the inert `version` field from .safeword/config.json.\n// Plaintext `.safeword/version` is the source of truth — the JSON field was\n// only ever written, never read, and confused reasoning-LLMs into flagging\n// stale projects. Safe to delete this block once no projects-in-the-wild\n// carry the field (likely several minor versions out).\nfunction stripDeadConfigVersion(safewordDirectory: string): void {\n const configPath = nodePath.join(safewordDirectory, 'config.json');\n const content = readFileSafe(configPath);\n if (!content) return;\n const parsed = JSON.parse(content) as Record<string, unknown>;\n if (!('version' in parsed)) return;\n delete parsed.version;\n writeFile(configPath, JSON.stringify(parsed, undefined, 2));\n}\n\nfunction printUpgradeSummary(result: ReconcileResult, projectVersion: string, cwd: string): void {\n header('Upgrade Complete');\n info(`\\nVersion: v${projectVersion} → v${VERSION}`);\n\n if (result.created.length > 0) {\n info('\\nCreated:');\n for (const file of result.created) listItem(file);\n }\n\n if (result.updated.length > 0) {\n info('\\nUpdated:');\n for (const file of result.updated) listItem(file);\n }\n\n if (result.packagesToRemove.length > 0) {\n const pm = detectPackageManager(cwd);\n const uninstallCmd = pm === 'yarn' ? 'yarn remove' : `${pm} uninstall`;\n warn(`\\n${result.packagesToRemove.length} package(s) are now bundled in safeword:`);\n for (const pkg of result.packagesToRemove) listItem(pkg);\n info(\"\\nIf you don't use these elsewhere, you can remove them:\");\n listItem(`${uninstallCmd} ${result.packagesToRemove.join(' ')}`);\n }\n\n success(`\\nSafeword upgraded to v${VERSION}`);\n}\n\nfunction installPythonTools(cwd: string): void {\n const pythonDirectory = findInTree(cwd, 'pyproject.toml') ?? cwd;\n if (hasRuffDependency(pythonDirectory)) return;\n\n const pm = detectPythonPackageManager(pythonDirectory);\n if (pm === 'pip') {\n warn('\\nPython tools not auto-installed (pip). Install manually:');\n listItem(getPythonInstallCommand(pythonDirectory));\n return;\n }\n\n info('\\nInstalling Python tools (ruff, mypy)...');\n const installed = installPythonDependencies(pythonDirectory, ['ruff', 'mypy']);\n if (installed) {\n success('Python tools installed');\n } else {\n warn('Python tools install failed. Install manually:');\n listItem(getPythonInstallCommand(pythonDirectory));\n }\n}\n\nfunction installSqlTools(cwd: string): void {\n // SQL projects use Python for SQLFluff — find pyproject.toml near dbt_project.yml\n const sqlDirectory = findInTree(cwd, 'dbt_project.yml') ?? cwd;\n const pythonDirectory = findInTree(sqlDirectory, 'pyproject.toml') ?? sqlDirectory;\n\n const pm = detectPythonPackageManager(pythonDirectory);\n if (pm === 'pip') {\n warn('\\nSQL tools not auto-installed (pip). Install manually:');\n listItem(getPythonInstallCommand(pythonDirectory, ['sqlfluff']));\n return;\n }\n\n info('\\nInstalling SQL tools (sqlfluff)...');\n const installed = installPythonDependencies(pythonDirectory, ['sqlfluff']);\n if (installed) {\n success('SQL tools installed');\n } else {\n warn('SQL tools install failed. Install manually:');\n listItem(getPythonInstallCommand(pythonDirectory, ['sqlfluff']));\n }\n}\n\nexport interface UpgradeOptions {\n /** When true, skip auto-editing the project's eslint config; fall through to the print-only nudge. */\n noModify?: boolean;\n /** Explicit namespace-migration consent: true = migrate, false = decline. Unset → prompt (TTY) or nudge. */\n migrateNamespace?: boolean;\n /** Injected confirm seam for the TTY prompt (tests). Defaults to a readline [Y/n] prompt. */\n confirmMigration?: (question: string) => Promise<boolean>;\n}\n\n/**\n * Default confirm seam: one-line readline [Y/n] prompt, Enter = yes,\n * stdin EOF/close = decline. `rl.question()`'s promise never settles when\n * the input closes (nodejs/node#53497), so the close event is raced in\n * explicitly — otherwise a Ctrl+D mid-prompt would hang the upgrade.\n * Streams injectable for tests.\n */\nexport async function promptYesDefault(\n question: string,\n input: NodeJS.ReadableStream = process.stdin,\n output: NodeJS.WritableStream = process.stdout,\n): Promise<boolean> {\n const { createInterface } = await import('node:readline/promises');\n const rl = createInterface({ input, output });\n try {\n const answer = await Promise.race([\n rl.question(question),\n new Promise<string>(resolve =>\n rl.once('close', () => {\n resolve('n');\n }),\n ),\n ]);\n return !/^n/i.test(answer.trim());\n } catch {\n return false; // defensive — treat any prompt failure as decline\n } finally {\n rl.close();\n }\n}\n\n/**\n * Namespace-migration step (ticket 9MMWS7) — runs BEFORE project-context\n * creation so the same upgrade reconciles on the post-move root. Consent:\n * explicit flag → obey; interactive TTY → prompt defaulting to yes;\n * otherwise → one-line nudge. Never silent, never forced; a failed move\n * reports and the upgrade continues on the legacy root.\n */\nconst UNMOVABLE_PLAN_WARNINGS: Partial<Record<MigrationPlan, string>> = {\n 'both-dirs':\n 'Namespace migration skipped: .project/ already exists alongside .safeword-project/ — merge manually, then remove the legacy directory.',\n blocked:\n 'Namespace migration skipped: .project exists but is not a directory — remove or rename it, then re-run with --migrate-namespace.',\n};\n\n/** Resolve consent for the offered move: flag → prompt (TTY or seam) → nudge. */\nasync function resolveMigrationConsent(options: UpgradeOptions): Promise<boolean> {\n if (options.migrateNamespace !== undefined) return options.migrateNamespace;\n\n // An injected confirm seam counts as interactive — it IS the TTY stand-in.\n const interactive =\n options.confirmMigration !== undefined ||\n (process.stdin.isTTY === true && process.stdout.isTTY === true);\n if (!interactive) {\n info(\n 'Namespace: this project uses the legacy .safeword-project/ — run `safeword upgrade --migrate-namespace` to converge on .project/ (recommended).',\n );\n return false;\n }\n const confirm = options.confirmMigration ?? promptYesDefault;\n return confirm(\n 'Move project namespace from .safeword-project/ to .project/ (recommended)? [Y/n] ',\n );\n}\n\nfunction reportMigrationSuccess(result: MigrationResult): void {\n const how = result.method === 'git' ? 'git history preserved' : 'directory renamed';\n const rewrites =\n result.rewrittenKeys.length > 0\n ? `; rewrote paths.${result.rewrittenKeys.join(', paths.')}`\n : '';\n success(`Namespace moved to .project/ (${how})${rewrites}`);\n}\n\nexport async function maybeMigrateNamespace(cwd: string, options: UpgradeOptions): Promise<void> {\n const plan = planNamespaceMigration(cwd);\n\n if (plan !== 'offer') {\n // Warn only when the user explicitly asked for a move that can't happen.\n const warning = UNMOVABLE_PLAN_WARNINGS[plan];\n if (warning !== undefined && options.migrateNamespace === true) warn(warning);\n return;\n }\n\n if (!(await resolveMigrationConsent(options))) return;\n\n try {\n reportMigrationSuccess(executeNamespaceMigration(cwd));\n } catch (migrationError) {\n warn(\n `${migrationError instanceof Error ? migrationError.message : String(migrationError)} — continuing upgrade on .safeword-project/.`,\n );\n return;\n }\n\n // After a confirmed move only — kept out of the try above so a scan hiccup\n // can never be reported as a migration failure (the move already succeeded).\n warnStaleToolingConfigs(cwd);\n}\n\n/**\n * After a successful move, name customer tooling configs still referencing the\n * legacy namespace so the developer can fix their lint/CI in the same review\n * (ticket JYWZG1). Read-only — safeword never edits these files.\n */\nfunction warnStaleToolingConfigs(cwd: string): void {\n const stale = scanStaleNamespaceConfigs(cwd);\n if (stale.length === 0) return;\n warn(\n '\\nThese tooling configs still reference the old namespace (.safeword-project/ → .project/) — update them so your lint/CI keeps working:',\n );\n for (const file of stale) listItem(file);\n}\n\nexport async function upgrade(options: UpgradeOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n if (!exists(safewordDirectory)) {\n error('Not configured. Run `safeword setup` first.');\n process.exit(1);\n }\n\n const projectVersion = getProjectVersion(safewordDirectory);\n\n if (compareVersions(VERSION, projectVersion) < 0) {\n const pm = detectPackageManager(cwd);\n error(`CLI v${VERSION} is older than project v${projectVersion}.`);\n error(`Update the CLI first: ${pm} install -g safeword`);\n process.exit(1);\n }\n\n header('Safeword Upgrade');\n info(`Upgrading from v${projectVersion} to v${VERSION}`);\n\n const eslintWarning = getEslintPeerMismatchWarning(cwd);\n if (eslintWarning) warn(`\\n${eslintWarning}`);\n\n await maybeMigrateNamespace(cwd, options);\n\n try {\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx);\n installDependencies(cwd, result.packagesToInstall, 'missing packages');\n\n // Migrate renamed pack IDs (dbt → sql)\n migratePackId(cwd, 'dbt', 'sql');\n\n // Ticket 154: drop dead `version` field from .safeword/config.json\n stripDeadConfigVersion(safewordDirectory);\n\n // Untrack transient state files a customer may have committed before the\n // gitignore rule existed (keep them on disk) — stops the perpetual-dirty\n // churn and the blocked branch switches it causes. Behaviour-neutral: the\n // hooks read/write these paths from the working tree, not from git.\n untrackIgnoredFiles(cwd, SAFEWORD_TRANSIENT_PATHS);\n\n // Install missing language packs\n const missingPacks = getMissingPacks(cwd);\n for (const packId of missingPacks) {\n installPack(packId, cwd);\n info(`Installed ${packId} pack`);\n }\n\n // Install language-specific tools for newly added packs\n if (missingPacks.includes('python')) {\n installPythonTools(cwd);\n }\n if (missingPacks.includes('sql')) {\n installSqlTools(cwd);\n }\n\n printUpgradeSummary(result, projectVersion, cwd);\n\n maybeAutoPatchOrNudge({\n cwd,\n existingEslintConfig: ctx.projectType.existingEslintConfig,\n hasJavaScript: Boolean(ctx.languages?.javascript),\n noModify: options.noModify,\n });\n } catch (error_) {\n error(`Upgrade failed: ${error_ instanceof Error ? error_.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n","/**\n * Namespace migration — the consensual move of a legacy `.safeword-project/`\n * onto `.project/` (ticket 9MMWS7, epic AQJ95G).\n *\n * `planNamespaceMigration` classifies the install; `executeNamespaceMigration`\n * performs the move (git mv when the directory is tracked, so history is\n * preserved; filesystem rename otherwise) and rewrites stale per-file\n * `paths.*` overrides that pointed into the legacy root. Consent lives in the\n * caller (`safeword upgrade`) — this module never decides to move.\n */\n\nimport { execSync } from 'node:child_process';\nimport { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { readConfiguredPath } from './configured-paths.js';\nimport { isDirectory } from './fs.js';\n\nconst LEGACY_ROOT = '.safeword-project';\nconst DEFAULT_ROOT = '.project';\n\nexport type MigrationPlan =\n | 'offer' // legacy-only install — offer the move\n | 'already-current' // .project/ present (or nothing to move) — no offer\n | 'both-dirs' // both roots exist — refuse, manual merge needed\n | 'custom-root' // explicit paths.projectRoot — user opted out of defaults\n | 'blocked'; // target exists but is not a directory — cannot move\n\nexport interface MigrationResult {\n method: 'git' | 'rename';\n rewrittenKeys: string[];\n}\n\n/** Classify the install for the migration offer. */\nexport function planNamespaceMigration(cwd: string): MigrationPlan {\n if (readConfiguredPath(cwd, 'projectRoot') !== undefined) return 'custom-root';\n\n const legacyPath = nodePath.join(cwd, LEGACY_ROOT);\n if (!isDirectory(legacyPath)) return 'already-current';\n\n const targetPath = nodePath.join(cwd, DEFAULT_ROOT);\n if (isDirectory(targetPath)) return 'both-dirs';\n if (existsSync(targetPath)) return 'blocked';\n\n return 'offer';\n}\n\n/** True when git tracks anything under the legacy directory. */\nfunction isGitTracked(cwd: string): boolean {\n try {\n const output = execSync(`git ls-files --error-unmatch \"${LEGACY_ROOT}\" 2>/dev/null | head -1`, {\n cwd,\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n return output.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Rewrite per-file `paths.*` values prefixed with the legacy root so they\n * follow the moved namespace. Surgical: only string values under `paths`\n * that start with `.safeword-project/` are touched.\n */\nfunction rewriteLegacyPathOverrides(cwd: string): string[] {\n const configPath = nodePath.join(cwd, '.safeword', 'config.json');\n if (!existsSync(configPath)) return [];\n\n let parsed: { paths?: Record<string, unknown> };\n try {\n parsed = JSON.parse(readFileSync(configPath, 'utf8')) as { paths?: Record<string, unknown> };\n } catch {\n return [];\n }\n if (!parsed.paths) return [];\n\n const rewritten: string[] = [];\n for (const [key, value] of Object.entries(parsed.paths)) {\n if (typeof value === 'string' && value.startsWith(`${LEGACY_ROOT}/`)) {\n parsed.paths[key] = `${DEFAULT_ROOT}${value.slice(LEGACY_ROOT.length)}`;\n rewritten.push(key);\n }\n }\n if (rewritten.length > 0) {\n writeFileSync(configPath, `${JSON.stringify(parsed, undefined, 2)}\\n`);\n }\n return rewritten.toSorted((a, b) => a.localeCompare(b));\n}\n\n/**\n * Move the legacy namespace to `.project/` and rewrite stale config\n * overrides. Caller must have confirmed consent and a plan of 'offer'.\n * Throws with context when the move itself fails — the tree is unchanged\n * in that case (a directory rename is atomic on one filesystem).\n */\nexport function executeNamespaceMigration(cwd: string): MigrationResult {\n const method: MigrationResult['method'] = isGitTracked(cwd) ? 'git' : 'rename';\n\n try {\n if (method === 'git') {\n execSync(`git mv \"${LEGACY_ROOT}\" \"${DEFAULT_ROOT}\"`, { cwd, stdio: 'pipe' });\n } else {\n renameSync(nodePath.join(cwd, LEGACY_ROOT), nodePath.join(cwd, DEFAULT_ROOT));\n }\n } catch (error) {\n throw new Error(\n `Failed to move ${LEGACY_ROOT}/ to ${DEFAULT_ROOT}/: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n return { method, rewrittenKeys: rewriteLegacyPathOverrides(cwd) };\n}\n","/**\n * Stale tooling-config scanner (ticket JYWZG1, epic AQJ95G follow-up).\n *\n * After `safeword upgrade --migrate-namespace` moves a repo from the legacy\n * `.safeword-project/` namespace to `.project/`, a customer's hand-authored\n * tooling config (eslint, prettier, tsconfig, CI, …) that referenced the old\n * path silently goes stale. This scanner NAMES those files so the migration\n * can warn — it never edits anything.\n *\n * Scope is deliberately narrow (ticket JYWZG1 / the /figure-it-out call):\n * - Only a curated set of *functional* tooling configs is read — never a\n * blanket repo grep, because the moved `.project/` dir legitimately holds\n * hundreds of documentary `.safeword-project` references (tickets, learnings).\n * - The match is path-boundary anchored (`.safeword-project/`) so unrelated\n * tokens like `.safeword-projectile/` don't false-positive.\n * - In `.prettierignore`, lines inside safeword's own managed block are\n * skipped — safeword writes BOTH roots there by design; a customer's stale\n * line elsewhere in the file is still flagged.\n */\n\nimport { existsSync, readdirSync, readFileSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { NAMESPACE_ROOT_LEGACY } from './configured-paths.js';\n\n/** The legacy namespace path, anchored with a trailing slash (boundary match). */\nconst LEGACY_REFERENCE = `${NAMESPACE_ROOT_LEGACY}/`;\n\n/** Marker for safeword's managed `.prettierignore` block (mirrors schema.ts). */\nconst MANAGED_PRETTIER_MARKER = '# Safeword - managed prettier exclusions';\n\n/** Workflows directory — the one nested config location safeword scans. */\nconst WORKFLOWS_SUBPATH = ['.github', 'workflows'];\n\n/**\n * Curated root-level tooling-config files. Extend here as new config types\n * surface; `.github/workflows/*` is handled separately (nested).\n */\nconst CURATED_ROOT_CONFIGS: readonly string[] = [\n 'eslint.config.ts',\n 'eslint.config.mjs',\n 'eslint.config.cjs',\n 'eslint.config.js',\n '.eslintrc',\n '.eslintrc.js',\n '.eslintrc.cjs',\n '.eslintrc.json',\n '.eslintrc.yml',\n '.eslintrc.yaml',\n '.prettierignore',\n '.prettierrc',\n '.prettierrc.json',\n '.prettierrc.js',\n '.prettierrc.cjs',\n '.prettierrc.yml',\n '.prettierrc.yaml',\n 'tsconfig.json',\n 'knip.json',\n 'knip.ts',\n '.dependency-cruiser.cjs',\n '.dependency-cruiser.js',\n '.jscpd.json',\n];\n\n/** True when any line outside the managed prettier block references the legacy root. */\nfunction prettierignoreHasCustomerReference(content: string): boolean {\n let insideManagedBlock = false;\n for (const line of content.split('\\n')) {\n if (line.includes(MANAGED_PRETTIER_MARKER)) {\n insideManagedBlock = true;\n continue;\n }\n // The managed block is safeword-written and contiguous; a blank line ends it.\n if (insideManagedBlock && line.trim() === '') {\n insideManagedBlock = false;\n continue;\n }\n if (!insideManagedBlock && line.includes(LEGACY_REFERENCE)) return true;\n }\n return false;\n}\n\nfunction fileHasStaleReference(relativePath: string, content: string): boolean {\n if (nodePath.basename(relativePath) === '.prettierignore') {\n return prettierignoreHasCustomerReference(content);\n }\n return content.includes(LEGACY_REFERENCE);\n}\n\n/** Workflow files under `.github/workflows/`, repo-relative; [] when absent. */\nfunction workflowConfigPaths(cwd: string): string[] {\n const directory = nodePath.join(cwd, ...WORKFLOWS_SUBPATH);\n let names: string[];\n try {\n // Defensive: a non-directory `.github/workflows` (ENOTDIR) must not crash\n // the scan — this runs after a successful migration and must never abort it.\n names = readdirSync(directory);\n } catch {\n return [];\n }\n return names\n .filter(name => name.endsWith('.yml') || name.endsWith('.yaml'))\n .map(name => `${WORKFLOWS_SUBPATH.join('/')}/${name}`);\n}\n\n/**\n * Return the repo-relative paths of curated tooling configs that still\n * reference the legacy `.safeword-project/` namespace. Empty when none —\n * including a clean repo, a managed-only `.prettierignore`, and documentary\n * references (those live under `.project/`, which is never in the curated set).\n */\nexport function scanStaleNamespaceConfigs(cwd: string): string[] {\n const candidates = [...CURATED_ROOT_CONFIGS, ...workflowConfigPaths(cwd)];\n const stale: string[] = [];\n for (const relativePath of candidates) {\n const fullPath = nodePath.join(cwd, relativePath);\n if (!existsSync(fullPath)) continue;\n let content: string;\n try {\n content = readFileSync(fullPath, 'utf8');\n } catch {\n continue;\n }\n if (fileHasStaleReference(relativePath, content)) stale.push(relativePath);\n }\n return stale;\n}\n"],"mappings":"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","names":["nodePath","error","existsSync","readFileSync","nodePath","nodePath","existsSync","readFileSync","nodePath"]}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/commands/upgrade.ts","../src/utils/namespace-migration.ts"],"sourcesContent":["/**\n * Upgrade command - Update safeword configuration to latest version\n *\n * Uses reconcile() with mode='upgrade' to update all managed files.\n */\n\nimport nodePath from 'node:path';\n\nimport { migratePackId } from '../packs/config.js';\nimport { installPack } from '../packs/install.js';\nimport {\n detectPythonPackageManager,\n getPythonInstallCommand,\n hasRuffDependency,\n installPythonDependencies,\n} from '../packs/python/setup.js';\nimport { getMissingPacks } from '../packs/registry.js';\nimport { reconcile, type ReconcileResult } from '../reconcile.js';\nimport { SAFEWORD_SCHEMA, SAFEWORD_TRANSIENT_PATHS } from '../schema.js';\nimport { createProjectContext } from '../utils/context.js';\nimport { getEslintPeerMismatchWarning } from '../utils/eslint-peer-check.js';\nimport { exists, findInTree, readFileSafe, writeFile } from '../utils/fs.js';\nimport { untrackIgnoredFiles } from '../utils/git.js';\nimport { detectPackageManager, installDependencies } from '../utils/install.js';\nimport {\n executeNamespaceMigration,\n type MigrationPlan,\n type MigrationResult,\n planNamespaceMigration,\n} from '../utils/namespace-migration.js';\nimport { error, header, info, listItem, success, warn } from '../utils/output.js';\nimport { maybeAutoPatchOrNudge } from '../utils/vendored-ignores-nudge.js';\nimport { compareVersions } from '../utils/version.js';\nimport { VERSION } from '../version.js';\n\nfunction getProjectVersion(safewordDirectory: string): string {\n const versionPath = nodePath.join(safewordDirectory, 'version');\n return readFileSafe(versionPath)?.trim() ?? '0.0.0';\n}\n\n// Ticket 154: strip the inert `version` field from .safeword/config.json.\n// Plaintext `.safeword/version` is the source of truth — the JSON field was\n// only ever written, never read, and confused reasoning-LLMs into flagging\n// stale projects. Safe to delete this block once no projects-in-the-wild\n// carry the field (likely several minor versions out).\nfunction stripDeadConfigVersion(safewordDirectory: string): void {\n const configPath = nodePath.join(safewordDirectory, 'config.json');\n const content = readFileSafe(configPath);\n if (!content) return;\n const parsed = JSON.parse(content) as Record<string, unknown>;\n if (!('version' in parsed)) return;\n delete parsed.version;\n writeFile(configPath, JSON.stringify(parsed, undefined, 2));\n}\n\nfunction printUpgradeSummary(result: ReconcileResult, projectVersion: string, cwd: string): void {\n header('Upgrade Complete');\n info(`\\nVersion: v${projectVersion} → v${VERSION}`);\n\n if (result.created.length > 0) {\n info('\\nCreated:');\n for (const file of result.created) listItem(file);\n }\n\n if (result.updated.length > 0) {\n info('\\nUpdated:');\n for (const file of result.updated) listItem(file);\n }\n\n if (result.packagesToRemove.length > 0) {\n const pm = detectPackageManager(cwd);\n const uninstallCmd = pm === 'yarn' ? 'yarn remove' : `${pm} uninstall`;\n warn(`\\n${result.packagesToRemove.length} package(s) are now bundled in safeword:`);\n for (const pkg of result.packagesToRemove) listItem(pkg);\n info(\"\\nIf you don't use these elsewhere, you can remove them:\");\n listItem(`${uninstallCmd} ${result.packagesToRemove.join(' ')}`);\n }\n\n success(`\\nSafeword upgraded to v${VERSION}`);\n}\n\nfunction installPythonTools(cwd: string): void {\n const pythonDirectory = findInTree(cwd, 'pyproject.toml') ?? cwd;\n if (hasRuffDependency(pythonDirectory)) return;\n\n const pm = detectPythonPackageManager(pythonDirectory);\n if (pm === 'pip') {\n warn('\\nPython tools not auto-installed (pip). Install manually:');\n listItem(getPythonInstallCommand(pythonDirectory));\n return;\n }\n\n info('\\nInstalling Python tools (ruff, mypy)...');\n const installed = installPythonDependencies(pythonDirectory, ['ruff', 'mypy']);\n if (installed) {\n success('Python tools installed');\n } else {\n warn('Python tools install failed. Install manually:');\n listItem(getPythonInstallCommand(pythonDirectory));\n }\n}\n\nfunction installSqlTools(cwd: string): void {\n // SQL projects use Python for SQLFluff — find pyproject.toml near dbt_project.yml\n const sqlDirectory = findInTree(cwd, 'dbt_project.yml') ?? cwd;\n const pythonDirectory = findInTree(sqlDirectory, 'pyproject.toml') ?? sqlDirectory;\n\n const pm = detectPythonPackageManager(pythonDirectory);\n if (pm === 'pip') {\n warn('\\nSQL tools not auto-installed (pip). Install manually:');\n listItem(getPythonInstallCommand(pythonDirectory, ['sqlfluff']));\n return;\n }\n\n info('\\nInstalling SQL tools (sqlfluff)...');\n const installed = installPythonDependencies(pythonDirectory, ['sqlfluff']);\n if (installed) {\n success('SQL tools installed');\n } else {\n warn('SQL tools install failed. Install manually:');\n listItem(getPythonInstallCommand(pythonDirectory, ['sqlfluff']));\n }\n}\n\nexport interface UpgradeOptions {\n /** When true, skip auto-editing the project's eslint config; fall through to the print-only nudge. */\n noModify?: boolean;\n /** Explicit namespace-migration consent: true = migrate, false = decline. Unset → prompt (TTY) or nudge. */\n migrateNamespace?: boolean;\n /** Injected confirm seam for the TTY prompt (tests). Defaults to a readline [Y/n] prompt. */\n confirmMigration?: (question: string) => Promise<boolean>;\n}\n\n/**\n * Default confirm seam: one-line readline [Y/n] prompt, Enter = yes,\n * stdin EOF/close = decline. `rl.question()`'s promise never settles when\n * the input closes (nodejs/node#53497), so the close event is raced in\n * explicitly — otherwise a Ctrl+D mid-prompt would hang the upgrade.\n * Streams injectable for tests.\n */\nexport async function promptYesDefault(\n question: string,\n input: NodeJS.ReadableStream = process.stdin,\n output: NodeJS.WritableStream = process.stdout,\n): Promise<boolean> {\n const { createInterface } = await import('node:readline/promises');\n const rl = createInterface({ input, output });\n try {\n const answer = await Promise.race([\n rl.question(question),\n new Promise<string>(resolve =>\n rl.once('close', () => {\n resolve('n');\n }),\n ),\n ]);\n return !/^n/i.test(answer.trim());\n } catch {\n return false; // defensive — treat any prompt failure as decline\n } finally {\n rl.close();\n }\n}\n\n/**\n * Namespace-migration step (ticket 9MMWS7) — runs BEFORE project-context\n * creation so the same upgrade reconciles on the post-move root. Consent:\n * explicit flag → obey; interactive TTY → prompt defaulting to yes;\n * otherwise → one-line nudge. Never silent, never forced; a failed move\n * reports and the upgrade continues on the legacy root.\n */\nconst UNMOVABLE_PLAN_WARNINGS: Partial<Record<MigrationPlan, string>> = {\n 'both-dirs':\n 'Namespace migration skipped: .project/ already exists alongside .safeword-project/ — merge manually, then remove the legacy directory.',\n blocked:\n 'Namespace migration skipped: .project exists but is not a directory — remove or rename it, then re-run with --migrate-namespace.',\n};\n\n/** Resolve consent for the offered move: flag → prompt (TTY or seam) → nudge. */\nasync function resolveMigrationConsent(options: UpgradeOptions): Promise<boolean> {\n if (options.migrateNamespace !== undefined) return options.migrateNamespace;\n\n // An injected confirm seam counts as interactive — it IS the TTY stand-in.\n const interactive =\n options.confirmMigration !== undefined ||\n (process.stdin.isTTY === true && process.stdout.isTTY === true);\n if (!interactive) {\n info(\n 'Namespace: this project uses the legacy .safeword-project/ — run `safeword upgrade --migrate-namespace` to converge on .project/ (recommended).',\n );\n return false;\n }\n const confirm = options.confirmMigration ?? promptYesDefault;\n return confirm(\n 'Move project namespace from .safeword-project/ to .project/ (recommended)? [Y/n] ',\n );\n}\n\nfunction reportMigrationSuccess(result: MigrationResult): void {\n const how = result.method === 'git' ? 'git history preserved' : 'directory renamed';\n const rewrites =\n result.rewrittenKeys.length > 0\n ? `; rewrote paths.${result.rewrittenKeys.join(', paths.')}`\n : '';\n success(`Namespace moved to .project/ (${how})${rewrites}`);\n}\n\nexport async function maybeMigrateNamespace(cwd: string, options: UpgradeOptions): Promise<void> {\n const plan = planNamespaceMigration(cwd);\n\n if (plan !== 'offer') {\n // Warn only when the user explicitly asked for a move that can't happen.\n const warning = UNMOVABLE_PLAN_WARNINGS[plan];\n if (warning !== undefined && options.migrateNamespace === true) warn(warning);\n return;\n }\n\n if (!(await resolveMigrationConsent(options))) return;\n\n try {\n reportMigrationSuccess(executeNamespaceMigration(cwd));\n } catch (migrationError) {\n warn(\n `${migrationError instanceof Error ? migrationError.message : String(migrationError)} — continuing upgrade on .safeword-project/.`,\n );\n }\n}\n\nexport async function upgrade(options: UpgradeOptions): Promise<void> {\n const cwd = process.cwd();\n const safewordDirectory = nodePath.join(cwd, '.safeword');\n\n if (!exists(safewordDirectory)) {\n error('Not configured. Run `safeword setup` first.');\n process.exit(1);\n }\n\n const projectVersion = getProjectVersion(safewordDirectory);\n\n if (compareVersions(VERSION, projectVersion) < 0) {\n const pm = detectPackageManager(cwd);\n error(`CLI v${VERSION} is older than project v${projectVersion}.`);\n error(`Update the CLI first: ${pm} install -g safeword`);\n process.exit(1);\n }\n\n header('Safeword Upgrade');\n info(`Upgrading from v${projectVersion} to v${VERSION}`);\n\n const eslintWarning = getEslintPeerMismatchWarning(cwd);\n if (eslintWarning) warn(`\\n${eslintWarning}`);\n\n await maybeMigrateNamespace(cwd, options);\n\n try {\n const ctx = createProjectContext(cwd);\n const result = await reconcile(SAFEWORD_SCHEMA, 'upgrade', ctx);\n installDependencies(cwd, result.packagesToInstall, 'missing packages');\n\n // Migrate renamed pack IDs (dbt → sql)\n migratePackId(cwd, 'dbt', 'sql');\n\n // Ticket 154: drop dead `version` field from .safeword/config.json\n stripDeadConfigVersion(safewordDirectory);\n\n // Untrack transient state files a customer may have committed before the\n // gitignore rule existed (keep them on disk) — stops the perpetual-dirty\n // churn and the blocked branch switches it causes. Behaviour-neutral: the\n // hooks read/write these paths from the working tree, not from git.\n untrackIgnoredFiles(cwd, SAFEWORD_TRANSIENT_PATHS);\n\n // Install missing language packs\n const missingPacks = getMissingPacks(cwd);\n for (const packId of missingPacks) {\n installPack(packId, cwd);\n info(`Installed ${packId} pack`);\n }\n\n // Install language-specific tools for newly added packs\n if (missingPacks.includes('python')) {\n installPythonTools(cwd);\n }\n if (missingPacks.includes('sql')) {\n installSqlTools(cwd);\n }\n\n printUpgradeSummary(result, projectVersion, cwd);\n\n maybeAutoPatchOrNudge({\n cwd,\n existingEslintConfig: ctx.projectType.existingEslintConfig,\n hasJavaScript: Boolean(ctx.languages?.javascript),\n noModify: options.noModify,\n });\n } catch (error_) {\n error(`Upgrade failed: ${error_ instanceof Error ? error_.message : 'Unknown error'}`);\n process.exit(1);\n }\n}\n","/**\n * Namespace migration — the consensual move of a legacy `.safeword-project/`\n * onto `.project/` (ticket 9MMWS7, epic AQJ95G).\n *\n * `planNamespaceMigration` classifies the install; `executeNamespaceMigration`\n * performs the move (git mv when the directory is tracked, so history is\n * preserved; filesystem rename otherwise) and rewrites stale per-file\n * `paths.*` overrides that pointed into the legacy root. Consent lives in the\n * caller (`safeword upgrade`) — this module never decides to move.\n */\n\nimport { execSync } from 'node:child_process';\nimport { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs';\nimport nodePath from 'node:path';\n\nimport { readConfiguredPath } from './configured-paths.js';\nimport { isDirectory } from './fs.js';\n\nconst LEGACY_ROOT = '.safeword-project';\nconst DEFAULT_ROOT = '.project';\n\nexport type MigrationPlan =\n | 'offer' // legacy-only install — offer the move\n | 'already-current' // .project/ present (or nothing to move) — no offer\n | 'both-dirs' // both roots exist — refuse, manual merge needed\n | 'custom-root' // explicit paths.projectRoot — user opted out of defaults\n | 'blocked'; // target exists but is not a directory — cannot move\n\nexport interface MigrationResult {\n method: 'git' | 'rename';\n rewrittenKeys: string[];\n}\n\n/** Classify the install for the migration offer. */\nexport function planNamespaceMigration(cwd: string): MigrationPlan {\n if (readConfiguredPath(cwd, 'projectRoot') !== undefined) return 'custom-root';\n\n const legacyPath = nodePath.join(cwd, LEGACY_ROOT);\n if (!isDirectory(legacyPath)) return 'already-current';\n\n const targetPath = nodePath.join(cwd, DEFAULT_ROOT);\n if (isDirectory(targetPath)) return 'both-dirs';\n if (existsSync(targetPath)) return 'blocked';\n\n return 'offer';\n}\n\n/** True when git tracks anything under the legacy directory. */\nfunction isGitTracked(cwd: string): boolean {\n try {\n const output = execSync(`git ls-files --error-unmatch \"${LEGACY_ROOT}\" 2>/dev/null | head -1`, {\n cwd,\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'ignore'],\n });\n return output.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Rewrite per-file `paths.*` values prefixed with the legacy root so they\n * follow the moved namespace. Surgical: only string values under `paths`\n * that start with `.safeword-project/` are touched.\n */\nfunction rewriteLegacyPathOverrides(cwd: string): string[] {\n const configPath = nodePath.join(cwd, '.safeword', 'config.json');\n if (!existsSync(configPath)) return [];\n\n let parsed: { paths?: Record<string, unknown> };\n try {\n parsed = JSON.parse(readFileSync(configPath, 'utf8')) as { paths?: Record<string, unknown> };\n } catch {\n return [];\n }\n if (!parsed.paths) return [];\n\n const rewritten: string[] = [];\n for (const [key, value] of Object.entries(parsed.paths)) {\n if (typeof value === 'string' && value.startsWith(`${LEGACY_ROOT}/`)) {\n parsed.paths[key] = `${DEFAULT_ROOT}${value.slice(LEGACY_ROOT.length)}`;\n rewritten.push(key);\n }\n }\n if (rewritten.length > 0) {\n writeFileSync(configPath, `${JSON.stringify(parsed, undefined, 2)}\\n`);\n }\n return rewritten.toSorted((a, b) => a.localeCompare(b));\n}\n\n/**\n * Move the legacy namespace to `.project/` and rewrite stale config\n * overrides. Caller must have confirmed consent and a plan of 'offer'.\n * Throws with context when the move itself fails — the tree is unchanged\n * in that case (a directory rename is atomic on one filesystem).\n */\nexport function executeNamespaceMigration(cwd: string): MigrationResult {\n const method: MigrationResult['method'] = isGitTracked(cwd) ? 'git' : 'rename';\n\n try {\n if (method === 'git') {\n execSync(`git mv \"${LEGACY_ROOT}\" \"${DEFAULT_ROOT}\"`, { cwd, stdio: 'pipe' });\n } else {\n renameSync(nodePath.join(cwd, LEGACY_ROOT), nodePath.join(cwd, DEFAULT_ROOT));\n }\n } catch (error) {\n throw new Error(\n `Failed to move ${LEGACY_ROOT}/ to ${DEFAULT_ROOT}/: ${error instanceof Error ? error.message : String(error)}`,\n { cause: error },\n );\n }\n\n return { method, rewrittenKeys: rewriteLegacyPathOverrides(cwd) };\n}\n"],"mappings":"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","names":["nodePath","error","nodePath"]}
|