longcelot-sheet-db
Version:
Google Sheets-backed staging database adapter for Node.js with schema-first design
172 lines • 8.29 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.renameColumnCommand = renameColumnCommand;
const chalk_1 = __importDefault(require("chalk"));
const inquirer_1 = __importDefault(require("inquirer"));
const adminAdapter_1 = require("../lib/adminAdapter");
const schemaLoader_1 = require("../lib/schemaLoader");
const resolveTable_1 = require("../lib/resolveTable");
const actorConfig_1 = require("../../utils/actorConfig");
const schemaFileMutator_1 = require("../../utils/schemaFileMutator");
const reservedColumns_1 = require("../../schema/reservedColumns");
const schemaHash_1 = require("../../utils/schemaHash");
const suggest_1 = require("../../utils/suggest");
const NAME_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
function findReferencingColumns(all, tableName, columnName) {
const referencing = [];
for (const { schema } of all) {
for (const col of Object.values(schema.columns)) {
if (col.ref === `${tableName}.${columnName}`) {
referencing.push(schema.name);
break;
}
}
}
return referencing;
}
function validateNewName(newName, table) {
if (!NAME_PATTERN.test(newName)) {
return `'${newName}' is not a valid column name — use letters, numbers, and underscores, starting with a letter or underscore`;
}
if (reservedColumns_1.RESERVED_COLUMN_NAMES.includes(newName)) {
return `'${newName}' is a reserved column name and cannot be used`;
}
if (newName in table.schema.columns) {
return `'${newName}' already exists on '${table.schema.name}'`;
}
return undefined;
}
async function renameColumnCommand(tableName, oldName, newName, options) {
console.log(chalk_1.default.blue.bold('✏️ Rename column...\n'));
const config = (0, adminAdapter_1.loadCLIConfig)();
const all = (0, schemaLoader_1.loadSchemasWithPaths)(config);
if (all.length === 0) {
console.log(chalk_1.default.yellow('⚠️ No schemas found. Nothing to rename.'));
return;
}
const table = await (0, resolveTable_1.promptOrResolveTable)(all, tableName);
const selectable = Object.keys(table.schema.columns).filter((name) => !reservedColumns_1.RESERVED_COLUMN_NAMES.includes(name));
if (selectable.length === 0) {
console.log(chalk_1.default.yellow(`⚠️ '${table.schema.name}' has no renameable columns (only reserved columns present).`));
return;
}
let resolvedOldName;
if (oldName) {
if (reservedColumns_1.RESERVED_COLUMN_NAMES.includes(oldName)) {
console.error(chalk_1.default.red(`❌ '${oldName}' is a reserved auto-generated column and cannot be renamed`));
process.exit(1);
}
if (!(oldName in table.schema.columns)) {
const suggestion = (0, suggest_1.closestMatch)(oldName, selectable);
console.error(chalk_1.default.red(`❌ Column '${oldName}' not found on '${table.schema.name}'.${suggestion ? ` Did you mean '${suggestion}'?` : ''} Available: ${selectable.join(', ')}`));
process.exit(1);
}
resolvedOldName = oldName;
}
else {
const { chosen } = await inquirer_1.default.prompt([
{ type: 'list', name: 'chosen', message: `Which column on '${table.schema.name}'?`, choices: selectable },
]);
resolvedOldName = chosen;
}
let resolvedNewName;
if (newName) {
const error = validateNewName(newName, table);
if (error) {
console.error(chalk_1.default.red(`❌ ${error}`));
process.exit(1);
}
resolvedNewName = newName;
}
else {
const { chosen } = await inquirer_1.default.prompt([
{
type: 'input',
name: 'chosen',
message: `New name for '${resolvedOldName}':`,
validate: (v) => validateNewName(v.trim(), table) ?? true,
},
]);
resolvedNewName = chosen.trim();
}
if (resolvedOldName === resolvedNewName) {
console.log(chalk_1.default.yellow('New name is the same as the old name. Nothing to do.'));
return;
}
console.log(chalk_1.default.bold('Plan:\n'));
console.log(` Table: ${chalk_1.default.white(`${table.actor}/${table.schema.name}`)}`);
console.log(` Rename: ${chalk_1.default.white(resolvedOldName)} → ${chalk_1.default.white(resolvedNewName)}`);
const referencing = findReferencingColumns(all, table.schema.name, resolvedOldName);
if (referencing.length > 0) {
console.log(chalk_1.default.yellow(` ⚠ referenced by ref('${table.schema.name}.${resolvedOldName}') in: ${referencing.join(', ')} — update those ref() strings manually`));
}
console.log();
if (options.dryRun) {
console.log(chalk_1.default.yellow('[DRY RUN] No changes made.'));
return;
}
if (!options.yes) {
const { confirmed } = await inquirer_1.default.prompt([
{ type: 'confirm', name: 'confirmed', message: `Rename '${resolvedOldName}' to '${resolvedNewName}' on '${table.schema.name}'? This updates the schema file and the live Google Sheet header — existing row data is preserved.`, default: true },
]);
if (!confirmed) {
console.log(chalk_1.default.gray('Cancelled.'));
return;
}
}
const fileResult = (0, schemaFileMutator_1.renameColumnKey)(table.filePath, resolvedOldName, resolvedNewName);
if (!fileResult.ok) {
console.error(chalk_1.default.red(`❌ ${fileResult.reason}`));
process.exit(1);
}
const { adminCtx, adminSheetId } = await (0, adminAdapter_1.buildAdminAdapter)({
config,
schemas: all.map((s) => s.schema),
tokenFile: options.tokenFile,
});
const updatedColumns = {};
for (const [key, value] of Object.entries(table.schema.columns)) {
updatedColumns[key === resolvedOldName ? resolvedNewName : key] = value;
}
const updatedSchema = {
...table.schema,
columns: updatedColumns,
pkColumn: table.schema.pkColumn === resolvedOldName ? resolvedNewName : table.schema.pkColumn,
};
const newHash = (0, schemaHash_1.computeSchemaHash)(updatedSchema);
const actorCfg = config.actors.find((a) => (0, actorConfig_1.resolveActorName)(a) === table.actor);
const targets = await (0, adminAdapter_1.resolveSheetTargets)(adminCtx, table.actor, adminSheetId, actorCfg, options.allUsers ?? false);
const resultRows = [];
for (const target of targets) {
try {
const rows = await adminCtx.getClient().getAllRows(target.sheetId, table.schema.name);
if (rows.length === 0) {
resultRows.push({ sheet: target.label, status: chalk_1.default.gray('– tab not synced yet, nothing to rename') });
continue;
}
const headers = rows[0];
const index = headers.indexOf(resolvedOldName);
if (index < 0) {
resultRows.push({ sheet: target.label, status: chalk_1.default.gray('– column already absent from sheet') });
continue;
}
await adminCtx.getClient().updateHeaderCell(target.sheetId, table.schema.name, index, resolvedNewName);
await adminCtx.upsertSchemaVersion(target.sheetId, table.schema.name, newHash, Object.keys(updatedColumns).length);
resultRows.push({ sheet: target.label, status: chalk_1.default.green('✅ renamed (data preserved)') });
}
catch (err) {
const message = err instanceof Error ? err.message : String(err);
resultRows.push({ sheet: target.label, status: chalk_1.default.red(`❌ ${message}`) });
}
}
console.log(chalk_1.default.bold(`\nResult (${table.schema.name}: ${resolvedOldName} → ${resolvedNewName}):\n`));
for (const row of resultRows) {
console.log(` ${chalk_1.default.gray(row.sheet.padEnd(28))} ${row.status}`);
}
console.log();
}
exports.default = renameColumnCommand;
//# sourceMappingURL=rename-column.js.map