diff --git a/PLAN.md b/PLAN.md index 2ec8d8cfd..131c51b7a 100644 --- a/PLAN.md +++ b/PLAN.md @@ -240,6 +240,8 @@ For existing LaunchQL projects: - Command-line compatibility layer - Unit test suite - Test project setup +- Transaction support with --tx/--no-tx flags +- Cross-project dependency support (project:change format) **IN PROGRESS:** - Integration testing diff --git a/packages/cli/src/commands/revert.ts b/packages/cli/src/commands/revert.ts index 232be1a32..37a1f3e5f 100644 --- a/packages/cli/src/commands/revert.ts +++ b/packages/cli/src/commands/revert.ts @@ -1,5 +1,5 @@ import { CLIOptions, Inquirerer, Question } from 'inquirerer'; -import { listModules, revert } from '@launchql/core'; +import { LaunchQLProject, revert } from '@launchql/core'; import { errors, getEnvOptions, LaunchQLOptions } from '@launchql/types'; import { getPgEnvOptions, getSpawnEnvWithPg } from 'pg-env'; import { Logger } from '@launchql/logger'; @@ -43,34 +43,38 @@ export default async ( return; } + log.debug(`Using current directory: ${cwd}`); + + const project = new LaunchQLProject(cwd); + if (recursive) { - const modules = await listModules(cwd); - const mods = Object.keys(modules); + const modules = await project.getModules(); + const moduleNames = modules.map(mod => mod.getModuleName()); - if (!mods.length) { - log.error('No modules found to revert.'); + if (!moduleNames.length) { + log.error('No modules found in the specified directory.'); prompter.close(); - throw errors.NOT_FOUND({}, 'No modules found to revert.'); + throw errors.NOT_FOUND({}, 'No modules found in the specified directory.'); } - const { project } = await prompter.prompt(argv, [ + const { project: selectedProject } = await prompter.prompt(argv, [ { type: 'autocomplete', name: 'project', message: 'Choose a project to revert', - options: mods, + options: moduleNames, required: true } ]); - log.success(`Reverting project ${project} on database ${database}...`); + log.success(`Reverting project ${selectedProject} on database ${database}...`); const options: LaunchQLOptions = getEnvOptions({ pg: { database } }); - await revert(options, project, database, cwd, { useSqitch, useTransaction: tx }); + await revert(options, selectedProject, database, cwd, { useSqitch, useTransaction: tx }); log.success('Revert complete.'); } else { const pgEnv = getPgEnvOptions(); diff --git a/packages/cli/src/commands/verify.ts b/packages/cli/src/commands/verify.ts index 47ece9dd5..d404c640b 100644 --- a/packages/cli/src/commands/verify.ts +++ b/packages/cli/src/commands/verify.ts @@ -1,10 +1,11 @@ import { CLIOptions, Inquirerer, Question } from 'inquirerer'; -import { listModules, verify } from '@launchql/core'; +import { LaunchQLProject, verify } from '@launchql/core'; import { errors, getEnvOptions, LaunchQLOptions } from '@launchql/types'; import { getPgEnvOptions, getSpawnEnvWithPg } from 'pg-env'; import { Logger } from '@launchql/logger'; import { verifyCommand } from '@launchql/migrate'; import { execSync } from 'child_process'; +import { getTargetDatabase } from '../utils'; const log = new Logger('verify'); @@ -13,38 +14,34 @@ export default async ( prompter: Inquirerer, _options: CLIOptions ) => { - const questions: Question[] = [ - { - name: 'database', - message: 'Database name', - type: 'text', - required: true - } - ]; + const database = await getTargetDatabase(argv, prompter, { + message: 'Select database' + }); - let { database, recursive, cwd, 'use-sqitch': useSqitch } = await prompter.prompt(argv, questions); + const questions: Question[] = []; - if (!cwd) { - cwd = process.cwd(); - log.debug(`Using current directory: ${cwd}`); - } + let { recursive, cwd, 'use-sqitch': useSqitch } = await prompter.prompt(argv, questions); + + log.debug(`Using current directory: ${cwd}`); + + const project = new LaunchQLProject(cwd); if (recursive) { - const modules = await listModules(cwd); - const mods = Object.keys(modules); + const modules = await project.getModules(); + const moduleNames = modules.map(mod => mod.getModuleName()); - if (!mods.length) { - log.error('No modules found to verify.'); + if (!moduleNames.length) { + log.error('No modules found in the specified directory.'); prompter.close(); - throw errors.NOT_FOUND({}, 'No modules found to verify.'); + throw errors.NOT_FOUND({}, 'No modules found in the specified directory.'); } - const { project } = await prompter.prompt(argv, [ + const { project: selectedProject } = await prompter.prompt(argv, [ { type: 'autocomplete', name: 'project', message: 'Choose a project to verify', - options: mods, + options: moduleNames, required: true } ]); @@ -55,8 +52,8 @@ export default async ( } }); - log.info(`Verifying project ${project} on database ${database}...`); - await verify(options, project, database, cwd, { useSqitch }); + log.info(`Verifying project ${selectedProject} on database ${database}...`); + await verify(options, selectedProject, database, cwd, { useSqitch }); log.success('Verify complete.'); } else { const pgEnv = getPgEnvOptions(); diff --git a/packages/migrate/src/client.ts b/packages/migrate/src/client.ts index 199df7550..5501f7021 100644 --- a/packages/migrate/src/client.ts +++ b/packages/migrate/src/client.ts @@ -72,7 +72,7 @@ export class LaunchQLMigrate { } else { log.success('Migration schema found and ready'); } - + this.initialized = true; } catch (error) { log.error('Failed to initialize migration schema:', error); diff --git a/packages/migrate/src/sql/procedures.sql b/packages/migrate/src/sql/procedures.sql index 4e055c205..d45e3175f 100644 --- a/packages/migrate/src/sql/procedures.sql +++ b/packages/migrate/src/sql/procedures.sql @@ -8,18 +8,37 @@ BEGIN END; $$; --- Check if a change is deployed +-- Check if a change is deployed (handles both local and cross-project dependencies) CREATE FUNCTION launchql_migrate.is_deployed( p_project TEXT, p_change_name TEXT ) RETURNS BOOLEAN -LANGUAGE sql STABLE AS $$ - SELECT EXISTS ( +LANGUAGE plpgsql STABLE AS $$ +DECLARE + v_actual_project TEXT; + v_actual_change TEXT; + v_colon_pos INT; +BEGIN + -- Check if change_name contains a project prefix (cross-project dependency) + v_colon_pos := position(':' in p_change_name); + + IF v_colon_pos > 0 THEN + -- Split into project and change name + v_actual_project := substring(p_change_name from 1 for v_colon_pos - 1); + v_actual_change := substring(p_change_name from v_colon_pos + 1); + ELSE + -- Use provided project as default + v_actual_project := p_project; + v_actual_change := p_change_name; + END IF; + + RETURN EXISTS ( SELECT 1 FROM launchql_migrate.changes - WHERE project = p_project - AND change_name = p_change_name + WHERE project = v_actual_project + AND change_name = v_actual_change ); +END; $$; -- Deploy a change