pgpm 0.0.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/README.md +416 -0
- package/commands/add.d.ts +7 -0
- package/commands/add.js +86 -0
- package/commands/admin-users/add.d.ts +4 -0
- package/commands/admin-users/add.js +89 -0
- package/commands/admin-users/bootstrap.d.ts +4 -0
- package/commands/admin-users/bootstrap.js +50 -0
- package/commands/admin-users/remove.d.ts +4 -0
- package/commands/admin-users/remove.js +82 -0
- package/commands/admin-users.d.ts +4 -0
- package/commands/admin-users.js +68 -0
- package/commands/analyze.d.ts +4 -0
- package/commands/analyze.js +21 -0
- package/commands/clear.d.ts +3 -0
- package/commands/clear.js +59 -0
- package/commands/deploy.d.ts +4 -0
- package/commands/deploy.js +146 -0
- package/commands/explorer.d.ts +3 -0
- package/commands/explorer.js +94 -0
- package/commands/export.d.ts +3 -0
- package/commands/export.js +129 -0
- package/commands/extension.d.ts +4 -0
- package/commands/extension.js +48 -0
- package/commands/init/index.d.ts +7 -0
- package/commands/init/index.js +47 -0
- package/commands/init/module.d.ts +4 -0
- package/commands/init/module.js +71 -0
- package/commands/init/workspace.d.ts +4 -0
- package/commands/init/workspace.js +52 -0
- package/commands/install.d.ts +4 -0
- package/commands/install.js +37 -0
- package/commands/kill.d.ts +3 -0
- package/commands/kill.js +107 -0
- package/commands/migrate/deps.d.ts +4 -0
- package/commands/migrate/deps.js +186 -0
- package/commands/migrate/init.d.ts +4 -0
- package/commands/migrate/init.js +65 -0
- package/commands/migrate/list.d.ts +4 -0
- package/commands/migrate/list.js +85 -0
- package/commands/migrate/status.d.ts +4 -0
- package/commands/migrate/status.js +94 -0
- package/commands/migrate.d.ts +4 -0
- package/commands/migrate.js +69 -0
- package/commands/package.d.ts +3 -0
- package/commands/package.js +65 -0
- package/commands/plan.d.ts +3 -0
- package/commands/plan.js +62 -0
- package/commands/remove.d.ts +3 -0
- package/commands/remove.js +42 -0
- package/commands/rename.d.ts +4 -0
- package/commands/rename.js +35 -0
- package/commands/revert.d.ts +3 -0
- package/commands/revert.js +107 -0
- package/commands/server.d.ts +3 -0
- package/commands/server.js +187 -0
- package/commands/tag.d.ts +6 -0
- package/commands/tag.js +168 -0
- package/commands/verify.d.ts +3 -0
- package/commands/verify.js +85 -0
- package/commands.d.ts +5 -0
- package/commands.js +118 -0
- package/dist/README.md +416 -0
- package/dist/package.json +77 -0
- package/esm/commands/add.js +51 -0
- package/esm/commands/admin-users/add.js +87 -0
- package/esm/commands/admin-users/bootstrap.js +48 -0
- package/esm/commands/admin-users/remove.js +80 -0
- package/esm/commands/admin-users.js +63 -0
- package/esm/commands/analyze.js +16 -0
- package/esm/commands/clear.js +54 -0
- package/esm/commands/deploy.js +144 -0
- package/esm/commands/explorer.js +92 -0
- package/esm/commands/export.js +127 -0
- package/esm/commands/extension.js +46 -0
- package/esm/commands/init/index.js +42 -0
- package/esm/commands/init/module.js +68 -0
- package/esm/commands/init/workspace.js +46 -0
- package/esm/commands/install.js +35 -0
- package/esm/commands/kill.js +105 -0
- package/esm/commands/migrate/deps.js +184 -0
- package/esm/commands/migrate/init.js +63 -0
- package/esm/commands/migrate/list.js +83 -0
- package/esm/commands/migrate/status.js +92 -0
- package/esm/commands/migrate.js +64 -0
- package/esm/commands/package.js +63 -0
- package/esm/commands/plan.js +60 -0
- package/esm/commands/remove.js +40 -0
- package/esm/commands/rename.js +30 -0
- package/esm/commands/revert.js +105 -0
- package/esm/commands/server.js +185 -0
- package/esm/commands/tag.js +133 -0
- package/esm/commands/verify.js +83 -0
- package/esm/commands.js +111 -0
- package/esm/index.js +20 -0
- package/esm/package.js +26 -0
- package/esm/utils/argv.js +92 -0
- package/esm/utils/cli-error.js +48 -0
- package/esm/utils/database.js +78 -0
- package/esm/utils/deployed-changes.js +68 -0
- package/esm/utils/display.js +58 -0
- package/esm/utils/index.js +3 -0
- package/esm/utils/module-utils.js +51 -0
- package/index.d.ts +3 -0
- package/index.js +23 -0
- package/package.d.ts +1 -0
- package/package.js +29 -0
- package/package.json +77 -0
- package/utils/argv.d.ts +46 -0
- package/utils/argv.js +100 -0
- package/utils/cli-error.d.ts +8 -0
- package/utils/cli-error.js +52 -0
- package/utils/database.d.ts +21 -0
- package/utils/database.js +83 -0
- package/utils/deployed-changes.d.ts +4 -0
- package/utils/deployed-changes.js +72 -0
- package/utils/display.d.ts +3 -0
- package/utils/display.js +66 -0
- package/utils/index.d.ts +3 -0
- package/utils/index.js +19 -0
- package/utils/module-utils.d.ts +8 -0
- package/utils/module-utils.js +54 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { LaunchQLPackage, sluggify } from '@launchql/core';
|
|
2
|
+
import { Logger } from '@launchql/logger';
|
|
3
|
+
import { errors, getGitConfigInfo } from '@launchql/types';
|
|
4
|
+
const log = new Logger('module-init');
|
|
5
|
+
export default async function runModuleSetup(argv, prompter) {
|
|
6
|
+
const { email, username } = getGitConfigInfo();
|
|
7
|
+
const { cwd = process.cwd() } = argv;
|
|
8
|
+
const project = new LaunchQLPackage(cwd);
|
|
9
|
+
if (!project.workspacePath) {
|
|
10
|
+
log.error('Not inside a LaunchQL workspace.');
|
|
11
|
+
throw errors.NOT_IN_WORKSPACE({});
|
|
12
|
+
}
|
|
13
|
+
if (!project.isInsideAllowedDirs(cwd) && !project.isInWorkspace()) {
|
|
14
|
+
log.error('You must be inside one of the workspace packages.');
|
|
15
|
+
throw errors.NOT_IN_WORKSPACE_MODULE({});
|
|
16
|
+
}
|
|
17
|
+
const availExtensions = project.getAvailableModules();
|
|
18
|
+
const moduleQuestions = [
|
|
19
|
+
{
|
|
20
|
+
name: 'MODULENAME',
|
|
21
|
+
message: 'Enter the module name',
|
|
22
|
+
required: true,
|
|
23
|
+
type: 'text',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: 'extensions',
|
|
27
|
+
message: 'Which extensions?',
|
|
28
|
+
options: availExtensions,
|
|
29
|
+
type: 'checkbox',
|
|
30
|
+
allowCustomOptions: true,
|
|
31
|
+
required: true,
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
const answers = await prompter.prompt(argv, moduleQuestions);
|
|
35
|
+
const modName = sluggify(answers.MODULENAME);
|
|
36
|
+
const extensions = answers.extensions
|
|
37
|
+
.filter((opt) => opt.selected)
|
|
38
|
+
.map((opt) => opt.name);
|
|
39
|
+
// Determine template source
|
|
40
|
+
let templateSource;
|
|
41
|
+
if (argv.repo) {
|
|
42
|
+
templateSource = {
|
|
43
|
+
type: 'github',
|
|
44
|
+
path: argv.repo,
|
|
45
|
+
branch: argv.fromBranch
|
|
46
|
+
};
|
|
47
|
+
log.info(`Loading templates from GitHub repository: ${argv.repo}`);
|
|
48
|
+
}
|
|
49
|
+
else if (argv.templatePath) {
|
|
50
|
+
templateSource = {
|
|
51
|
+
type: 'local',
|
|
52
|
+
path: argv.templatePath
|
|
53
|
+
};
|
|
54
|
+
log.info(`Loading templates from local path: ${argv.templatePath}`);
|
|
55
|
+
}
|
|
56
|
+
project.initModule({
|
|
57
|
+
...argv,
|
|
58
|
+
...answers,
|
|
59
|
+
name: modName,
|
|
60
|
+
// @ts-ignore
|
|
61
|
+
USERFULLNAME: username,
|
|
62
|
+
USEREMAIL: email,
|
|
63
|
+
extensions,
|
|
64
|
+
templateSource
|
|
65
|
+
});
|
|
66
|
+
log.success(`Initialized module: ${modName}`);
|
|
67
|
+
return { ...argv, ...answers };
|
|
68
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { sluggify } from '@launchql/core';
|
|
2
|
+
import { Logger } from '@launchql/logger';
|
|
3
|
+
// @ts-ignore - TypeScript module resolution issue with @launchql/templatizer
|
|
4
|
+
import { workspaceTemplate, writeRenderedTemplates, loadTemplates } from '@launchql/templatizer';
|
|
5
|
+
import { mkdirSync } from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
const log = new Logger('workspace-init');
|
|
8
|
+
export default async function runWorkspaceSetup(argv, prompter) {
|
|
9
|
+
const workspaceQuestions = [
|
|
10
|
+
{
|
|
11
|
+
name: 'name',
|
|
12
|
+
message: 'Enter workspace name',
|
|
13
|
+
required: true,
|
|
14
|
+
type: 'text',
|
|
15
|
+
}
|
|
16
|
+
];
|
|
17
|
+
const answers = await prompter.prompt(argv, workspaceQuestions);
|
|
18
|
+
const { cwd } = argv;
|
|
19
|
+
const targetPath = path.join(cwd, sluggify(answers.name));
|
|
20
|
+
mkdirSync(targetPath, { recursive: true });
|
|
21
|
+
log.success(`Created workspace directory: ${targetPath}`);
|
|
22
|
+
// Determine template source
|
|
23
|
+
let templates = workspaceTemplate;
|
|
24
|
+
if (argv.repo) {
|
|
25
|
+
const source = {
|
|
26
|
+
type: 'github',
|
|
27
|
+
path: argv.repo,
|
|
28
|
+
branch: argv.fromBranch
|
|
29
|
+
};
|
|
30
|
+
log.info(`Loading templates from GitHub repository: ${argv.repo}`);
|
|
31
|
+
const compiledTemplates = loadTemplates(source, 'workspace');
|
|
32
|
+
templates = compiledTemplates.map((t) => t.render);
|
|
33
|
+
}
|
|
34
|
+
else if (argv.templatePath) {
|
|
35
|
+
const source = {
|
|
36
|
+
type: 'local',
|
|
37
|
+
path: argv.templatePath
|
|
38
|
+
};
|
|
39
|
+
log.info(`Loading templates from local path: ${argv.templatePath}`);
|
|
40
|
+
const compiledTemplates = loadTemplates(source, 'workspace');
|
|
41
|
+
templates = compiledTemplates.map((t) => t.render);
|
|
42
|
+
}
|
|
43
|
+
writeRenderedTemplates(templates, targetPath, { ...argv, ...answers });
|
|
44
|
+
log.success('Workspace templates rendered.');
|
|
45
|
+
return { ...argv, ...answers, cwd: targetPath };
|
|
46
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { LaunchQLPackage } from '@launchql/core';
|
|
2
|
+
const installUsageText = `
|
|
3
|
+
LaunchQL Install Command:
|
|
4
|
+
|
|
5
|
+
lql install <package>...
|
|
6
|
+
|
|
7
|
+
Install LaunchQL modules into current module.
|
|
8
|
+
|
|
9
|
+
Arguments:
|
|
10
|
+
package One or more package names to install
|
|
11
|
+
|
|
12
|
+
Options:
|
|
13
|
+
--help, -h Show this help message
|
|
14
|
+
--cwd <directory> Working directory (default: current directory)
|
|
15
|
+
|
|
16
|
+
Examples:
|
|
17
|
+
lql install @launchql/base32 Install single package
|
|
18
|
+
lql install @launchql/base32 @launchql/utils Install multiple packages
|
|
19
|
+
`;
|
|
20
|
+
export default async (argv, prompter, _options) => {
|
|
21
|
+
// Show usage if explicitly requested
|
|
22
|
+
if (argv.help || argv.h) {
|
|
23
|
+
console.log(installUsageText);
|
|
24
|
+
process.exit(0);
|
|
25
|
+
}
|
|
26
|
+
const { cwd = process.cwd() } = argv;
|
|
27
|
+
const project = new LaunchQLPackage(cwd);
|
|
28
|
+
if (!project.isInModule()) {
|
|
29
|
+
throw new Error('You must run this command inside a LaunchQL module.');
|
|
30
|
+
}
|
|
31
|
+
if (argv._.length === 0) {
|
|
32
|
+
throw new Error('You must provide a package name to install, e.g. `@launchql/base32`');
|
|
33
|
+
}
|
|
34
|
+
await project.installModules(...argv._);
|
|
35
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Logger } from '@launchql/logger';
|
|
2
|
+
import { getPgPool } from 'pg-cache';
|
|
3
|
+
const log = new Logger('db-kill');
|
|
4
|
+
const killUsageText = `
|
|
5
|
+
LaunchQL Kill Command:
|
|
6
|
+
|
|
7
|
+
lql kill [OPTIONS]
|
|
8
|
+
|
|
9
|
+
Terminate database connections and optionally drop databases.
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--help, -h Show this help message
|
|
13
|
+
--drop Drop databases after killing connections (default: true)
|
|
14
|
+
--no-drop Only kill connections, don't drop databases
|
|
15
|
+
--pattern <pattern> Pattern to match database names (supports SQL LIKE syntax)
|
|
16
|
+
--cwd <directory> Working directory (default: current directory)
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
lql kill Kill connections and drop selected databases (interactive)
|
|
20
|
+
lql kill --no-drop Only kill connections, preserve databases (interactive)
|
|
21
|
+
lql kill --pattern test_% Kill connections to databases matching 'test_%' pattern
|
|
22
|
+
lql kill --pattern %dev --no-drop Kill connections to databases ending with 'dev' but don't drop
|
|
23
|
+
`;
|
|
24
|
+
export default async (argv, prompter, _options) => {
|
|
25
|
+
// Show usage if explicitly requested
|
|
26
|
+
if (argv.help || argv.h) {
|
|
27
|
+
console.log(killUsageText);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
const db = await getPgPool({
|
|
31
|
+
database: 'postgres'
|
|
32
|
+
});
|
|
33
|
+
let selectedDbNames;
|
|
34
|
+
if (argv.pattern) {
|
|
35
|
+
// Pattern mode: automatically find databases matching the pattern
|
|
36
|
+
const databasesResult = await db.query(`
|
|
37
|
+
SELECT datname FROM pg_catalog.pg_database
|
|
38
|
+
WHERE datistemplate = FALSE AND datname NOT IN ('postgres')
|
|
39
|
+
AND datname !~ '^pg_' AND datname LIKE $1;
|
|
40
|
+
`, [argv.pattern]);
|
|
41
|
+
if (!databasesResult.rows.length) {
|
|
42
|
+
log.info(`ā¹ļø No databases found matching pattern "${argv.pattern}". Exiting.`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
selectedDbNames = databasesResult.rows.map(row => row.datname);
|
|
46
|
+
log.info(`šÆ Found ${selectedDbNames.length} database(s) matching pattern "${argv.pattern}": ${selectedDbNames.join(', ')}`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
// Interactive mode: prompt user to select databases
|
|
50
|
+
const databasesResult = await db.query(`
|
|
51
|
+
SELECT datname FROM pg_catalog.pg_database
|
|
52
|
+
WHERE datistemplate = FALSE AND datname NOT IN ('postgres')
|
|
53
|
+
AND datname !~ '^pg_';
|
|
54
|
+
`);
|
|
55
|
+
if (!databasesResult.rows.length) {
|
|
56
|
+
log.info('ā¹ļø No databases found to process. Exiting.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
let databases;
|
|
60
|
+
({ databases } = await prompter.prompt(argv, [
|
|
61
|
+
{
|
|
62
|
+
type: 'checkbox',
|
|
63
|
+
name: 'databases',
|
|
64
|
+
message: 'Select database(s) to terminate connections and optionally drop',
|
|
65
|
+
options: databasesResult.rows.map(row => row.datname),
|
|
66
|
+
required: true
|
|
67
|
+
}
|
|
68
|
+
]));
|
|
69
|
+
selectedDbNames = databases.filter(d => d.selected).map(d => d.value);
|
|
70
|
+
}
|
|
71
|
+
const actionText = argv.drop === false ? 'kill connections to' : 'kill connections and DROP';
|
|
72
|
+
const patternText = argv.pattern ? ` (matched by pattern "${argv.pattern}")` : '';
|
|
73
|
+
const { yes } = await prompter.prompt(argv, [
|
|
74
|
+
{
|
|
75
|
+
type: 'confirm',
|
|
76
|
+
name: 'yes',
|
|
77
|
+
message: `Are you sure you want to ${actionText}: ${selectedDbNames.join(', ')}${patternText}?`,
|
|
78
|
+
default: false
|
|
79
|
+
}
|
|
80
|
+
]);
|
|
81
|
+
if (!yes) {
|
|
82
|
+
log.info('ā Aborted. No actions were taken.');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
for (const dbname of selectedDbNames) {
|
|
86
|
+
const killResult = await db.query(`
|
|
87
|
+
SELECT pg_terminate_backend(pid)
|
|
88
|
+
FROM pg_stat_activity
|
|
89
|
+
WHERE datname = $1 AND pid <> pg_backend_pid();
|
|
90
|
+
`, [dbname]);
|
|
91
|
+
log.warn(`š Terminated ${killResult.rowCount} connection(s) to "${dbname}".`);
|
|
92
|
+
if (argv.drop === false) {
|
|
93
|
+
log.info(`ā ļø Skipping DROP for "${dbname}" due to --no-drop flag.`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
await db.query(`DROP DATABASE "${dbname}";`);
|
|
98
|
+
log.success(`šļø Dropped database "${dbname}" successfully.`);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
log.error(`ā Failed to drop "${dbname}": ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
log.success('ā
Done processing databases.');
|
|
105
|
+
};
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { LaunchQLMigrate } from '@launchql/core';
|
|
2
|
+
import { parsePlanFile } from '@launchql/core';
|
|
3
|
+
import { Logger } from '@launchql/logger';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { getPgEnvOptions } from 'pg-env';
|
|
7
|
+
import { getTargetDatabase } from '../../utils/database';
|
|
8
|
+
const log = new Logger('migrate-deps');
|
|
9
|
+
export default async (argv, prompter, options) => {
|
|
10
|
+
const cwd = argv.cwd || process.cwd();
|
|
11
|
+
const planPath = join(cwd, 'launchql.plan');
|
|
12
|
+
if (!existsSync(planPath)) {
|
|
13
|
+
log.error(`No launchql.plan found in ${cwd}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
// Get specific change to analyze
|
|
17
|
+
let changeName = argv._?.[0] || argv.change;
|
|
18
|
+
const planResult = parsePlanFile(planPath);
|
|
19
|
+
if (!planResult.data || planResult.errors.length > 0) {
|
|
20
|
+
log.error('Failed to parse plan file:', planResult.errors);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
const plan = planResult.data;
|
|
24
|
+
const allChanges = plan.changes;
|
|
25
|
+
// If no change specified, prompt
|
|
26
|
+
if (!changeName && !argv.all) {
|
|
27
|
+
const answer = await prompter.prompt(argv, [
|
|
28
|
+
{
|
|
29
|
+
type: 'autocomplete',
|
|
30
|
+
name: 'change',
|
|
31
|
+
message: 'Which change do you want to analyze?',
|
|
32
|
+
options: allChanges.map(c => ({
|
|
33
|
+
name: c.name,
|
|
34
|
+
value: c.name,
|
|
35
|
+
description: c.dependencies.length > 0 ? `Depends on: ${c.dependencies.join(', ')}` : 'No dependencies'
|
|
36
|
+
}))
|
|
37
|
+
}
|
|
38
|
+
]);
|
|
39
|
+
changeName = answer.change;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
if (argv.all) {
|
|
43
|
+
// Show dependency graph for all changes
|
|
44
|
+
console.log('\nš Dependency Graph\n');
|
|
45
|
+
console.log(`Package: ${plan.package}`);
|
|
46
|
+
console.log(`Total Changes: ${allChanges.length}\n`);
|
|
47
|
+
// Build dependency tree
|
|
48
|
+
const dependencyTree = buildDependencyTree(allChanges);
|
|
49
|
+
// Show changes with no dependencies first (roots)
|
|
50
|
+
const roots = allChanges.filter(c => c.dependencies.length === 0);
|
|
51
|
+
console.log('š Root Changes (no dependencies):\n');
|
|
52
|
+
roots.forEach(change => {
|
|
53
|
+
console.log(` ⢠${change.name}`);
|
|
54
|
+
showDependents(change.name, dependencyTree, ' ');
|
|
55
|
+
});
|
|
56
|
+
// Show orphaned changes (have dependencies but aren't depended on)
|
|
57
|
+
const orphans = allChanges.filter(c => c.dependencies.length > 0 &&
|
|
58
|
+
!allChanges.some(other => other.dependencies.includes(c.name)));
|
|
59
|
+
if (orphans.length > 0) {
|
|
60
|
+
console.log('\nšø Leaf Changes (not depended on by others):\n');
|
|
61
|
+
orphans.forEach(change => {
|
|
62
|
+
console.log(` ⢠${change.name} ā [${change.dependencies.join(', ')}]`);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
// Show dependencies for specific change
|
|
68
|
+
const change = allChanges.find(c => c.name === changeName);
|
|
69
|
+
if (!change) {
|
|
70
|
+
log.error(`Change '${changeName}' not found in plan file`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
console.log(`\nš Dependency Analysis: ${changeName}\n`);
|
|
74
|
+
// Direct dependencies
|
|
75
|
+
if (change.dependencies.length > 0) {
|
|
76
|
+
console.log('š„ Direct Dependencies:');
|
|
77
|
+
change.dependencies.forEach(dep => {
|
|
78
|
+
console.log(` ⢠${dep}`);
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
console.log('š„ Direct Dependencies: None');
|
|
83
|
+
}
|
|
84
|
+
// All dependencies (recursive)
|
|
85
|
+
const allDeps = getAllDependencies(change.name, allChanges);
|
|
86
|
+
if (allDeps.size > 0) {
|
|
87
|
+
console.log(`\nš¦ All Dependencies (${allDeps.size} total):`);
|
|
88
|
+
Array.from(allDeps).forEach(dep => {
|
|
89
|
+
console.log(` ⢠${dep}`);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
// Dependents (what depends on this)
|
|
93
|
+
const dependents = allChanges.filter(c => c.dependencies.includes(changeName));
|
|
94
|
+
if (dependents.length > 0) {
|
|
95
|
+
console.log(`\nš¤ Depended on by (${dependents.length} changes):`);
|
|
96
|
+
dependents.forEach(dep => {
|
|
97
|
+
console.log(` ⢠${dep.name}`);
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
console.log('\nš¤ Depended on by: None');
|
|
102
|
+
}
|
|
103
|
+
// Check deployment status if connected to database
|
|
104
|
+
const pgEnv = getPgEnvOptions();
|
|
105
|
+
const targetDatabase = await getTargetDatabase(argv, prompter, {
|
|
106
|
+
message: 'Select database to check deployment status'
|
|
107
|
+
});
|
|
108
|
+
const client = new LaunchQLMigrate({
|
|
109
|
+
host: pgEnv.host,
|
|
110
|
+
port: pgEnv.port,
|
|
111
|
+
user: pgEnv.user,
|
|
112
|
+
password: pgEnv.password,
|
|
113
|
+
database: pgEnv.database
|
|
114
|
+
});
|
|
115
|
+
try {
|
|
116
|
+
const deployedChanges = await client.getDeployedChanges(targetDatabase, plan.package);
|
|
117
|
+
const deployedMap = new Map(deployedChanges.map(c => [c.change_name, c]));
|
|
118
|
+
console.log('\nš Deployment Status:');
|
|
119
|
+
// Check if this change is deployed
|
|
120
|
+
const isDeployed = deployedMap.has(changeName);
|
|
121
|
+
console.log(` This change: ${isDeployed ? 'ā
Deployed' : 'ā³ Not deployed'}`);
|
|
122
|
+
// Check dependencies
|
|
123
|
+
const undeployedDeps = Array.from(allDeps).filter(dep => !deployedMap.has(dep));
|
|
124
|
+
if (undeployedDeps.length > 0) {
|
|
125
|
+
console.log(` ā ļø Undeployed dependencies: ${undeployedDeps.join(', ')}`);
|
|
126
|
+
}
|
|
127
|
+
else if (allDeps.size > 0) {
|
|
128
|
+
console.log(' ā
All dependencies deployed');
|
|
129
|
+
}
|
|
130
|
+
// Check dependents
|
|
131
|
+
const deployedDependents = dependents.filter(d => deployedMap.has(d.name));
|
|
132
|
+
if (deployedDependents.length > 0) {
|
|
133
|
+
console.log(` ā ļø Deployed dependents: ${deployedDependents.map(d => d.name).join(', ')}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch (dbError) {
|
|
137
|
+
// Database connection optional for dependency analysis
|
|
138
|
+
log.debug('Could not connect to database for deployment status');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
log.error('Failed to analyze dependencies:', error);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
function buildDependencyTree(changes) {
|
|
148
|
+
const tree = new Map();
|
|
149
|
+
changes.forEach(change => {
|
|
150
|
+
change.dependencies.forEach((dep) => {
|
|
151
|
+
if (!tree.has(dep)) {
|
|
152
|
+
tree.set(dep, []);
|
|
153
|
+
}
|
|
154
|
+
tree.get(dep).push(change.name);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
return tree;
|
|
158
|
+
}
|
|
159
|
+
function showDependents(changeName, tree, indent) {
|
|
160
|
+
const dependents = tree.get(changeName) || [];
|
|
161
|
+
dependents.forEach(dep => {
|
|
162
|
+
console.log(`${indent}āā ${dep}`);
|
|
163
|
+
showDependents(dep, tree, indent + ' ');
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
function getAllDependencies(changeName, changes) {
|
|
167
|
+
const deps = new Set();
|
|
168
|
+
const change = changes.find(c => c.name === changeName);
|
|
169
|
+
if (!change)
|
|
170
|
+
return deps;
|
|
171
|
+
function addDeps(change) {
|
|
172
|
+
change.dependencies.forEach((dep) => {
|
|
173
|
+
if (!deps.has(dep)) {
|
|
174
|
+
deps.add(dep);
|
|
175
|
+
const depChange = changes.find(c => c.name === dep);
|
|
176
|
+
if (depChange) {
|
|
177
|
+
addDeps(depChange);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
addDeps(change);
|
|
183
|
+
return deps;
|
|
184
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { LaunchQLMigrate } from '@launchql/core';
|
|
2
|
+
import { Logger } from '@launchql/logger';
|
|
3
|
+
import { getPgEnvOptions } from 'pg-env';
|
|
4
|
+
import { getTargetDatabase } from '../../utils/database';
|
|
5
|
+
const log = new Logger('migrate-init');
|
|
6
|
+
export default async (argv, prompter, _options) => {
|
|
7
|
+
const pgEnv = getPgEnvOptions();
|
|
8
|
+
// Get target database
|
|
9
|
+
const database = await getTargetDatabase(argv, prompter, {
|
|
10
|
+
message: 'Select database to initialize migration tracking'
|
|
11
|
+
});
|
|
12
|
+
const questions = [
|
|
13
|
+
{
|
|
14
|
+
name: 'yes',
|
|
15
|
+
type: 'confirm',
|
|
16
|
+
message: `Initialize LaunchQL migration schema in database "${database}"?`,
|
|
17
|
+
required: true
|
|
18
|
+
}
|
|
19
|
+
];
|
|
20
|
+
const { yes } = await prompter.prompt(argv, questions);
|
|
21
|
+
if (!yes) {
|
|
22
|
+
log.info('Operation cancelled.');
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
log.info(`Initializing migration schema in database ${database}...`);
|
|
26
|
+
const config = {
|
|
27
|
+
host: pgEnv.host,
|
|
28
|
+
port: pgEnv.port,
|
|
29
|
+
user: pgEnv.user,
|
|
30
|
+
password: pgEnv.password,
|
|
31
|
+
database
|
|
32
|
+
};
|
|
33
|
+
const client = new LaunchQLMigrate(config);
|
|
34
|
+
try {
|
|
35
|
+
await client.initialize();
|
|
36
|
+
log.success('Migration schema initialized successfully.');
|
|
37
|
+
// Check if there's an existing Sqitch deployment to import
|
|
38
|
+
const hasSquitch = await client.hasSqitchTables();
|
|
39
|
+
if (hasSquitch) {
|
|
40
|
+
const { importSquitch } = await prompter.prompt(argv, [
|
|
41
|
+
{
|
|
42
|
+
name: 'importSquitch',
|
|
43
|
+
type: 'confirm',
|
|
44
|
+
message: 'Existing Sqitch deployment detected. Import it?',
|
|
45
|
+
required: true
|
|
46
|
+
}
|
|
47
|
+
]);
|
|
48
|
+
if (importSquitch) {
|
|
49
|
+
log.info('Importing Sqitch deployment history...');
|
|
50
|
+
await client.importFromSqitch();
|
|
51
|
+
log.success('Sqitch deployment imported successfully.');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
log.error(`Failed to initialize migration schema: ${error instanceof Error ? error.message : error}`);
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await client.close();
|
|
61
|
+
}
|
|
62
|
+
return argv;
|
|
63
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { LaunchQLMigrate } from '@launchql/core';
|
|
2
|
+
import { parsePlanFile } from '@launchql/core';
|
|
3
|
+
import { Logger } from '@launchql/logger';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { getPgEnvOptions } from 'pg-env';
|
|
7
|
+
import { getTargetDatabase } from '../../utils/database';
|
|
8
|
+
const log = new Logger('migrate-list');
|
|
9
|
+
export default async (argv, prompter, options) => {
|
|
10
|
+
const cwd = argv.cwd || process.cwd();
|
|
11
|
+
const planPath = join(cwd, 'launchql.plan');
|
|
12
|
+
if (!existsSync(planPath)) {
|
|
13
|
+
log.error(`No launchql.plan found in ${cwd}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
// Get database configuration
|
|
17
|
+
const pgEnv = getPgEnvOptions();
|
|
18
|
+
const targetDatabase = await getTargetDatabase(argv, prompter, {
|
|
19
|
+
message: 'Select database to list migrations'
|
|
20
|
+
});
|
|
21
|
+
const client = new LaunchQLMigrate({
|
|
22
|
+
host: pgEnv.host,
|
|
23
|
+
port: pgEnv.port,
|
|
24
|
+
user: pgEnv.user,
|
|
25
|
+
password: pgEnv.password,
|
|
26
|
+
database: pgEnv.database
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
// Get all changes from plan file
|
|
30
|
+
const planResult = parsePlanFile(planPath);
|
|
31
|
+
if (!planResult.data || planResult.errors.length > 0) {
|
|
32
|
+
log.error('Failed to parse plan file:', planResult.errors);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const plan = planResult.data;
|
|
36
|
+
const allChanges = plan.changes;
|
|
37
|
+
// Get deployed changes from database
|
|
38
|
+
const deployedChanges = await client.getDeployedChanges(targetDatabase, plan.package);
|
|
39
|
+
console.log('\nš All Changes\n');
|
|
40
|
+
console.log(`Package: ${plan.package}`);
|
|
41
|
+
console.log(`Total Changes: ${allChanges.length}`);
|
|
42
|
+
console.log(`Deployed: ${deployedChanges.length}`);
|
|
43
|
+
console.log(`Pending: ${allChanges.length - deployedChanges.length}\n`);
|
|
44
|
+
// Create a map for quick lookup
|
|
45
|
+
const deployedMap = new Map(deployedChanges.map(c => [c.change_name, c]));
|
|
46
|
+
// List all changes with their status
|
|
47
|
+
const showAll = argv.all || allChanges.length <= 20;
|
|
48
|
+
const changesToShow = showAll ? allChanges : allChanges.slice(0, 20);
|
|
49
|
+
console.log('Status Change Name Dependencies');
|
|
50
|
+
console.log('------ ----------------------------- --------------------------------');
|
|
51
|
+
changesToShow.forEach(change => {
|
|
52
|
+
const deployed = deployedMap.get(change.name);
|
|
53
|
+
const status = deployed ? 'ā
' : 'ā³';
|
|
54
|
+
const deps = change.dependencies.length > 0 ? change.dependencies.join(', ') : '-';
|
|
55
|
+
const depsDisplay = deps.length > 30 ? deps.substring(0, 27) + '...' : deps;
|
|
56
|
+
console.log(`${status} ${change.name.padEnd(30)} ${depsDisplay}`);
|
|
57
|
+
});
|
|
58
|
+
if (!showAll && allChanges.length > 20) {
|
|
59
|
+
console.log(`\n... and ${allChanges.length - 20} more changes. Use --all to see all changes.`);
|
|
60
|
+
}
|
|
61
|
+
// Show summary by status
|
|
62
|
+
if (argv.summary !== false) {
|
|
63
|
+
console.log('\nš Summary by Status:\n');
|
|
64
|
+
const pending = allChanges.filter(c => !deployedMap.has(c.name));
|
|
65
|
+
const deployed = allChanges.filter(c => deployedMap.has(c.name));
|
|
66
|
+
console.log(`ā
Deployed: ${deployed.length}`);
|
|
67
|
+
console.log(`ā³ Pending: ${pending.length}`);
|
|
68
|
+
// Show deployment timeline
|
|
69
|
+
if (deployedChanges.length > 0) {
|
|
70
|
+
const sortedDeployed = [...deployedChanges].sort((a, b) => new Date(a.deployed_at).getTime() - new Date(b.deployed_at).getTime());
|
|
71
|
+
const firstDeploy = new Date(sortedDeployed[0].deployed_at);
|
|
72
|
+
const lastDeploy = new Date(sortedDeployed[sortedDeployed.length - 1].deployed_at);
|
|
73
|
+
console.log(`\nš
Deployment Timeline:`);
|
|
74
|
+
console.log(` First: ${firstDeploy.toLocaleString()}`);
|
|
75
|
+
console.log(` Last: ${lastDeploy.toLocaleString()}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
log.error('Failed to list changes:', error);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { LaunchQLMigrate } from '@launchql/core';
|
|
2
|
+
import { parsePlanFile } from '@launchql/core';
|
|
3
|
+
import { Logger } from '@launchql/logger';
|
|
4
|
+
import { existsSync } from 'fs';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { getPgEnvOptions } from 'pg-env';
|
|
7
|
+
import { getTargetDatabase } from '../../utils/database';
|
|
8
|
+
const log = new Logger('migrate-status');
|
|
9
|
+
export default async (argv, prompter, options) => {
|
|
10
|
+
const cwd = argv.cwd || process.cwd();
|
|
11
|
+
const planPath = join(cwd, 'launchql.plan');
|
|
12
|
+
if (!existsSync(planPath)) {
|
|
13
|
+
log.error(`No launchql.plan found in ${cwd}`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
// Get database configuration
|
|
17
|
+
const pgEnv = getPgEnvOptions();
|
|
18
|
+
const targetDatabase = await getTargetDatabase(argv, prompter, {
|
|
19
|
+
message: 'Select database to check migration status'
|
|
20
|
+
});
|
|
21
|
+
const client = new LaunchQLMigrate({
|
|
22
|
+
host: pgEnv.host,
|
|
23
|
+
port: pgEnv.port,
|
|
24
|
+
user: pgEnv.user,
|
|
25
|
+
password: pgEnv.password,
|
|
26
|
+
database: pgEnv.database
|
|
27
|
+
});
|
|
28
|
+
try {
|
|
29
|
+
// Parse plan file to get package name
|
|
30
|
+
const planResult = parsePlanFile(planPath);
|
|
31
|
+
if (!planResult.data || planResult.errors.length > 0) {
|
|
32
|
+
log.error('Failed to parse plan file:', planResult.errors);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const plan = planResult.data;
|
|
36
|
+
// Switch to target database
|
|
37
|
+
const targetClient = new LaunchQLMigrate({
|
|
38
|
+
host: pgEnv.host,
|
|
39
|
+
port: pgEnv.port,
|
|
40
|
+
user: pgEnv.user,
|
|
41
|
+
password: pgEnv.password,
|
|
42
|
+
database: targetDatabase
|
|
43
|
+
});
|
|
44
|
+
const statusResults = await targetClient.status(plan.package);
|
|
45
|
+
console.log('\nš Migration Status\n');
|
|
46
|
+
console.log(`Database: ${targetDatabase}`);
|
|
47
|
+
if (statusResults.length > 0) {
|
|
48
|
+
const status = statusResults[0];
|
|
49
|
+
console.log(`Package: ${status.package}`);
|
|
50
|
+
console.log(`Total Deployed: ${status.totalDeployed}`);
|
|
51
|
+
if (status.lastChange) {
|
|
52
|
+
console.log(`Last Change: ${status.lastChange}`);
|
|
53
|
+
console.log(`Last Deployed: ${status.lastDeployed.toLocaleString()}`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log('No changes deployed yet');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log(`Package: ${plan.package}`);
|
|
61
|
+
console.log('No deployment history found');
|
|
62
|
+
}
|
|
63
|
+
// Show recent changes
|
|
64
|
+
const recentChanges = await targetClient.getRecentChanges(targetDatabase, 5);
|
|
65
|
+
if (recentChanges.length > 0) {
|
|
66
|
+
console.log('\nš Recent Changes:\n');
|
|
67
|
+
recentChanges.forEach((change) => {
|
|
68
|
+
const status = change.deployed_at ? 'ā
' : 'ā³';
|
|
69
|
+
const date = change.deployed_at ? new Date(change.deployed_at).toLocaleString() : 'Not deployed';
|
|
70
|
+
console.log(`${status} ${change.change_name.padEnd(30)} ${date}`);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Show pending changes
|
|
74
|
+
const pendingChanges = await targetClient.getPendingChanges(planPath, targetDatabase);
|
|
75
|
+
if (pendingChanges.length > 0) {
|
|
76
|
+
console.log(`\nā³ Pending Changes: ${pendingChanges.length}\n`);
|
|
77
|
+
pendingChanges.slice(0, 5).forEach((change) => {
|
|
78
|
+
console.log(` - ${change}`);
|
|
79
|
+
});
|
|
80
|
+
if (pendingChanges.length > 5) {
|
|
81
|
+
console.log(` ... and ${pendingChanges.length - 5} more`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log('\nā
All changes deployed');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
log.error('Failed to get migration status:', error);
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
};
|