UNPKG

@t1mmen/srtd

Version:

Supabase Repeatable Template Definitions (srtd): 🪄 Live-reloading SQL templates for Supabase DX. Make your database changes reviewable and migrations maintainable! 🚀

234 lines • 10.3 kB
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) { if (value !== null && value !== void 0) { if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected."); var dispose, inner; if (async) { if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined."); dispose = value[Symbol.asyncDispose]; } if (dispose === void 0) { if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined."); dispose = value[Symbol.dispose]; if (async) inner = dispose; } if (typeof dispose !== "function") throw new TypeError("Object not disposable."); if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; env.stack.push({ value: value, dispose: dispose, async: async }); } else if (async) { env.stack.push({ async: true }); } return value; }; var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) { return function (env) { function fail(e) { env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e; env.hasError = true; } var r, s = 0; function next() { while (r = env.stack.pop()) { try { if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next); if (r.dispose) { var result = r.dispose.call(r.value); if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); }); } else s |= 1; } catch (e) { fail(e); } } if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve(); if (env.hasError) throw env.error; } return next(); }; })(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }); // src/commands/promote.ts import path from 'node:path'; import { select } from '@inquirer/prompts'; import chalk from 'chalk'; import { Command } from 'commander'; import figures from 'figures'; import { glob } from 'glob'; import { createBaseJsonOutput, writeJson } from '../output/index.js'; import { Orchestrator } from '../services/Orchestrator.js'; import { createSpinner, renderBranding } from '../ui/index.js'; import { getConfig } from '../utils/config.js'; import { findProjectRoot } from '../utils/findProjectRoot.js'; import { getErrorMessage, isPromptExit } from '../utils/getErrorMessage.js'; function formatPromoteJsonOutput(success, promoted, error) { return { ...createBaseJsonOutput('promote', success, error), ...(promoted && { promoted }), }; } async function findWipTemplates(config, projectRoot) { const templatePath = path.join(projectRoot, config.templateDir); const pattern = `**/*${config.wipIndicator}*.sql`; const matches = await glob(pattern, { cwd: templatePath }); return matches.map(m => path.join(templatePath, m)); } async function promoteTemplateAction(templatePath, orchestrator, jsonMode = false) { const spinner = jsonMode ? null : createSpinner('').start(); try { // Use Orchestrator for promotion (single source of truth) const newPath = await orchestrator.promoteTemplate(templatePath); const templateName = path.basename(newPath, '.sql'); if (spinner) { spinner.succeed(`Successfully promoted ${templateName}`); console.log(chalk.dim('Run `build` command to generate migrations')); } return { exitCode: 0, result: { from: path.basename(templatePath), to: path.basename(newPath) }, }; } catch (err) { const errorMsg = getErrorMessage(err); if (spinner) { spinner.fail('Failed to promote template'); console.log(chalk.red(`${figures.cross} ${errorMsg}`)); } return { exitCode: 1, error: errorMsg }; } } async function handleTemplatePromotion(templateName, config, projectRoot, orchestrator, jsonMode = false) { const templateDir = path.join(projectRoot, config.templateDir); const pattern = `**/*${templateName}*`; const matches = await glob(pattern, { cwd: templateDir }); const isWip = config.wipIndicator && templateName?.includes(config.wipIndicator); if (matches.length === 0 || !isWip) { const errorMsg = `No WIP template found matching: ${templateName} in ${config.templateDir}`; if (!jsonMode) { console.log(); console.log(chalk.red(`${figures.cross} ${errorMsg}`)); } return { exitCode: 1, error: errorMsg }; } if (!isWip) { const errorMsg = `Template is not a WIP template: ${templateName}`; if (!jsonMode) { console.log(); console.log(chalk.red(`${figures.cross} ${errorMsg}`)); } return { exitCode: 1, error: errorMsg }; } const match = matches[0] ? path.join(templateDir, matches[0]) : ''; if (!match) { const errorMsg = `No valid match found for template: ${templateName} in ${config.templateDir}`; if (!jsonMode) { console.log(); console.log(chalk.red(`${figures.cross} ${errorMsg}`)); } return { exitCode: 1, error: errorMsg }; } return promoteTemplateAction(match, orchestrator, jsonMode); } export const promoteCommand = new Command('promote') .description('Promote a WIP template by removing the WIP indicator from its filename') .argument('[template]', 'Template file to promote (optional)') .option('--json', 'Output results as JSON') .action(async (templateArg, options) => { let exitCode = 0; let promoteResult; let errorMsg; try { const env_1 = { stack: [], error: void 0, hasError: false }; try { // Skip branding in JSON mode if (!options.json) { await renderBranding({ subtitle: 'Promote WIP template' }); } // Get configuration and initialize Orchestrator const projectRoot = await findProjectRoot(); const { config } = await getConfig(projectRoot); const orchestrator = __addDisposableResource(env_1, await Orchestrator.create(projectRoot, config, { silent: true }), true); // If template was provided as argument, promote it directly if (templateArg) { const res = await handleTemplatePromotion(templateArg, config, projectRoot, orchestrator, options.json); exitCode = res.exitCode; promoteResult = res.result; errorMsg = res.error; } else { // Find WIP templates for interactive selection const wipTemplates = await findWipTemplates(config, projectRoot); if (wipTemplates.length === 0) { if (!options.json) { console.log(chalk.yellow(`${figures.warning} No WIP templates found in ${config.templateDir} (${config.wipIndicator})`)); } exitCode = 0; } else if (!process.stdin.isTTY) { // Interactive mode requires TTY if (!options.json) { console.log(chalk.red(`${figures.cross} Interactive mode requires a TTY.`)); console.log(chalk.dim('Provide a template name as argument: srtd promote <template>')); } else { errorMsg = 'Interactive mode requires a TTY. Provide a template name as argument.'; } exitCode = 1; } else { // Show interactive selection const choices = wipTemplates.map(templatePath => ({ name: path.basename(templatePath), value: templatePath, })); const selectedTemplate = await select({ message: `Select a template to promote (removes ${config.wipIndicator} in filename):`, choices, }); const res = await promoteTemplateAction(selectedTemplate, orchestrator, options.json); exitCode = res.exitCode; promoteResult = res.result; errorMsg = res.error; } } // Output JSON if in JSON mode if (options.json) { const jsonOutput = formatPromoteJsonOutput(exitCode === 0 && !errorMsg, promoteResult, errorMsg); writeJson(jsonOutput); } } catch (e_1) { env_1.error = e_1; env_1.hasError = true; } finally { const result_1 = __disposeResources(env_1); if (result_1) await result_1; } } catch (error) { // Handle Ctrl+C gracefully if (isPromptExit(error)) { exitCode = 0; } else { const errMsg = getErrorMessage(error); if (options.json) { const jsonOutput = formatPromoteJsonOutput(false, undefined, errMsg); writeJson(jsonOutput); } else { console.log(); console.log(chalk.red(`${figures.cross} Error promoting template:`)); console.log(chalk.red(errMsg)); } exitCode = 1; } } // Exit AFTER the await using block has completed, ensuring dispose() runs process.exit(exitCode); }); //# sourceMappingURL=promote.js.map