@constructive-io/cli 0.0.2
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/LICENSE +23 -0
- package/README.md +412 -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/docker.d.ts +3 -0
- package/commands/docker.js +194 -0
- package/commands/env.d.ts +4 -0
- package/commands/env.js +124 -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/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 +6 -0
- package/commands.js +113 -0
- package/dist/README.md +412 -0
- package/dist/package.json +64 -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/docker.js +192 -0
- package/esm/commands/env.js +122 -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/tag.js +133 -0
- package/esm/commands/verify.js +83 -0
- package/esm/commands.js +105 -0
- package/esm/index.js +48 -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 +6 -0
- package/esm/utils/module-utils.js +51 -0
- package/index.d.ts +25 -0
- package/index.js +87 -0
- package/package.d.ts +1 -0
- package/package.js +29 -0
- package/package.json +64 -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 +6 -0
- package/utils/index.js +22 -0
- package/utils/module-utils.d.ts +8 -0
- package/utils/module-utils.js +54 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@launchql/core");
|
|
4
|
+
const core_2 = require("@launchql/core");
|
|
5
|
+
const logger_1 = require("@launchql/logger");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const pg_env_1 = require("pg-env");
|
|
9
|
+
const database_1 = require("../../utils/database");
|
|
10
|
+
const log = new logger_1.Logger('migrate-deps');
|
|
11
|
+
exports.default = async (argv, prompter, options) => {
|
|
12
|
+
const cwd = argv.cwd || process.cwd();
|
|
13
|
+
const planPath = (0, path_1.join)(cwd, 'pgpm.plan');
|
|
14
|
+
if (!(0, fs_1.existsSync)(planPath)) {
|
|
15
|
+
log.error(`No pgpm.plan found in ${cwd}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// Get specific change to analyze
|
|
19
|
+
let changeName = argv._?.[0] || argv.change;
|
|
20
|
+
const planResult = (0, core_2.parsePlanFile)(planPath);
|
|
21
|
+
if (!planResult.data || planResult.errors.length > 0) {
|
|
22
|
+
log.error('Failed to parse plan file:', planResult.errors);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const plan = planResult.data;
|
|
26
|
+
const allChanges = plan.changes;
|
|
27
|
+
// If no change specified, prompt
|
|
28
|
+
if (!changeName && !argv.all) {
|
|
29
|
+
const answer = await prompter.prompt(argv, [
|
|
30
|
+
{
|
|
31
|
+
type: 'autocomplete',
|
|
32
|
+
name: 'change',
|
|
33
|
+
message: 'Which change do you want to analyze?',
|
|
34
|
+
options: allChanges.map(c => ({
|
|
35
|
+
name: c.name,
|
|
36
|
+
value: c.name,
|
|
37
|
+
description: c.dependencies.length > 0 ? `Depends on: ${c.dependencies.join(', ')}` : 'No dependencies'
|
|
38
|
+
}))
|
|
39
|
+
}
|
|
40
|
+
]);
|
|
41
|
+
changeName = answer.change;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
if (argv.all) {
|
|
45
|
+
// Show dependency graph for all changes
|
|
46
|
+
console.log('\n🔗 Dependency Graph\n');
|
|
47
|
+
console.log(`Package: ${plan.package}`);
|
|
48
|
+
console.log(`Total Changes: ${allChanges.length}\n`);
|
|
49
|
+
// Build dependency tree
|
|
50
|
+
const dependencyTree = buildDependencyTree(allChanges);
|
|
51
|
+
// Show changes with no dependencies first (roots)
|
|
52
|
+
const roots = allChanges.filter(c => c.dependencies.length === 0);
|
|
53
|
+
console.log('📌 Root Changes (no dependencies):\n');
|
|
54
|
+
roots.forEach(change => {
|
|
55
|
+
console.log(` • ${change.name}`);
|
|
56
|
+
showDependents(change.name, dependencyTree, ' ');
|
|
57
|
+
});
|
|
58
|
+
// Show orphaned changes (have dependencies but aren't depended on)
|
|
59
|
+
const orphans = allChanges.filter(c => c.dependencies.length > 0 &&
|
|
60
|
+
!allChanges.some(other => other.dependencies.includes(c.name)));
|
|
61
|
+
if (orphans.length > 0) {
|
|
62
|
+
console.log('\n🔸 Leaf Changes (not depended on by others):\n');
|
|
63
|
+
orphans.forEach(change => {
|
|
64
|
+
console.log(` • ${change.name} → [${change.dependencies.join(', ')}]`);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Show dependencies for specific change
|
|
70
|
+
const change = allChanges.find(c => c.name === changeName);
|
|
71
|
+
if (!change) {
|
|
72
|
+
log.error(`Change '${changeName}' not found in plan file`);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
console.log(`\n🔍 Dependency Analysis: ${changeName}\n`);
|
|
76
|
+
// Direct dependencies
|
|
77
|
+
if (change.dependencies.length > 0) {
|
|
78
|
+
console.log('📥 Direct Dependencies:');
|
|
79
|
+
change.dependencies.forEach(dep => {
|
|
80
|
+
console.log(` • ${dep}`);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
console.log('📥 Direct Dependencies: None');
|
|
85
|
+
}
|
|
86
|
+
// All dependencies (recursive)
|
|
87
|
+
const allDeps = getAllDependencies(change.name, allChanges);
|
|
88
|
+
if (allDeps.size > 0) {
|
|
89
|
+
console.log(`\n📦 All Dependencies (${allDeps.size} total):`);
|
|
90
|
+
Array.from(allDeps).forEach(dep => {
|
|
91
|
+
console.log(` • ${dep}`);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
// Dependents (what depends on this)
|
|
95
|
+
const dependents = allChanges.filter(c => c.dependencies.includes(changeName));
|
|
96
|
+
if (dependents.length > 0) {
|
|
97
|
+
console.log(`\n📤 Depended on by (${dependents.length} changes):`);
|
|
98
|
+
dependents.forEach(dep => {
|
|
99
|
+
console.log(` • ${dep.name}`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
console.log('\n📤 Depended on by: None');
|
|
104
|
+
}
|
|
105
|
+
// Check deployment status if connected to database
|
|
106
|
+
const pgEnv = (0, pg_env_1.getPgEnvOptions)();
|
|
107
|
+
const targetDatabase = await (0, database_1.getTargetDatabase)(argv, prompter, {
|
|
108
|
+
message: 'Select database to check deployment status'
|
|
109
|
+
});
|
|
110
|
+
const client = new core_1.LaunchQLMigrate({
|
|
111
|
+
host: pgEnv.host,
|
|
112
|
+
port: pgEnv.port,
|
|
113
|
+
user: pgEnv.user,
|
|
114
|
+
password: pgEnv.password,
|
|
115
|
+
database: pgEnv.database
|
|
116
|
+
});
|
|
117
|
+
try {
|
|
118
|
+
const deployedChanges = await client.getDeployedChanges(targetDatabase, plan.package);
|
|
119
|
+
const deployedMap = new Map(deployedChanges.map(c => [c.change_name, c]));
|
|
120
|
+
console.log('\n📊 Deployment Status:');
|
|
121
|
+
// Check if this change is deployed
|
|
122
|
+
const isDeployed = deployedMap.has(changeName);
|
|
123
|
+
console.log(` This change: ${isDeployed ? '✅ Deployed' : '⏳ Not deployed'}`);
|
|
124
|
+
// Check dependencies
|
|
125
|
+
const undeployedDeps = Array.from(allDeps).filter(dep => !deployedMap.has(dep));
|
|
126
|
+
if (undeployedDeps.length > 0) {
|
|
127
|
+
console.log(` ⚠️ Undeployed dependencies: ${undeployedDeps.join(', ')}`);
|
|
128
|
+
}
|
|
129
|
+
else if (allDeps.size > 0) {
|
|
130
|
+
console.log(' ✅ All dependencies deployed');
|
|
131
|
+
}
|
|
132
|
+
// Check dependents
|
|
133
|
+
const deployedDependents = dependents.filter(d => deployedMap.has(d.name));
|
|
134
|
+
if (deployedDependents.length > 0) {
|
|
135
|
+
console.log(` ⚠️ Deployed dependents: ${deployedDependents.map(d => d.name).join(', ')}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (dbError) {
|
|
139
|
+
// Database connection optional for dependency analysis
|
|
140
|
+
log.debug('Could not connect to database for deployment status');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
log.error('Failed to analyze dependencies:', error);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
function buildDependencyTree(changes) {
|
|
150
|
+
const tree = new Map();
|
|
151
|
+
changes.forEach(change => {
|
|
152
|
+
change.dependencies.forEach((dep) => {
|
|
153
|
+
if (!tree.has(dep)) {
|
|
154
|
+
tree.set(dep, []);
|
|
155
|
+
}
|
|
156
|
+
tree.get(dep).push(change.name);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
return tree;
|
|
160
|
+
}
|
|
161
|
+
function showDependents(changeName, tree, indent) {
|
|
162
|
+
const dependents = tree.get(changeName) || [];
|
|
163
|
+
dependents.forEach(dep => {
|
|
164
|
+
console.log(`${indent}└─ ${dep}`);
|
|
165
|
+
showDependents(dep, tree, indent + ' ');
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function getAllDependencies(changeName, changes) {
|
|
169
|
+
const deps = new Set();
|
|
170
|
+
const change = changes.find(c => c.name === changeName);
|
|
171
|
+
if (!change)
|
|
172
|
+
return deps;
|
|
173
|
+
function addDeps(change) {
|
|
174
|
+
change.dependencies.forEach((dep) => {
|
|
175
|
+
if (!deps.has(dep)) {
|
|
176
|
+
deps.add(dep);
|
|
177
|
+
const depChange = changes.find(c => c.name === dep);
|
|
178
|
+
if (depChange) {
|
|
179
|
+
addDeps(depChange);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
addDeps(change);
|
|
185
|
+
return deps;
|
|
186
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@launchql/core");
|
|
4
|
+
const logger_1 = require("@launchql/logger");
|
|
5
|
+
const pg_env_1 = require("pg-env");
|
|
6
|
+
const database_1 = require("../../utils/database");
|
|
7
|
+
const log = new logger_1.Logger('migrate-init');
|
|
8
|
+
exports.default = async (argv, prompter, _options) => {
|
|
9
|
+
const pgEnv = (0, pg_env_1.getPgEnvOptions)();
|
|
10
|
+
// Get target database
|
|
11
|
+
const database = await (0, database_1.getTargetDatabase)(argv, prompter, {
|
|
12
|
+
message: 'Select database to initialize migration tracking'
|
|
13
|
+
});
|
|
14
|
+
const questions = [
|
|
15
|
+
{
|
|
16
|
+
name: 'yes',
|
|
17
|
+
type: 'confirm',
|
|
18
|
+
message: `Initialize constructive migration schema in database "${database}"?`,
|
|
19
|
+
required: true
|
|
20
|
+
}
|
|
21
|
+
];
|
|
22
|
+
const { yes } = await prompter.prompt(argv, questions);
|
|
23
|
+
if (!yes) {
|
|
24
|
+
log.info('Operation cancelled.');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
log.info(`Initializing migration schema in database ${database}...`);
|
|
28
|
+
const config = {
|
|
29
|
+
host: pgEnv.host,
|
|
30
|
+
port: pgEnv.port,
|
|
31
|
+
user: pgEnv.user,
|
|
32
|
+
password: pgEnv.password,
|
|
33
|
+
database
|
|
34
|
+
};
|
|
35
|
+
const client = new core_1.LaunchQLMigrate(config);
|
|
36
|
+
try {
|
|
37
|
+
await client.initialize();
|
|
38
|
+
log.success('Migration schema initialized successfully.');
|
|
39
|
+
// Check if there's an existing Sqitch deployment to import
|
|
40
|
+
const hasSquitch = await client.hasSqitchTables();
|
|
41
|
+
if (hasSquitch) {
|
|
42
|
+
const { importSquitch } = await prompter.prompt(argv, [
|
|
43
|
+
{
|
|
44
|
+
name: 'importSquitch',
|
|
45
|
+
type: 'confirm',
|
|
46
|
+
message: 'Existing Sqitch deployment detected. Import it?',
|
|
47
|
+
required: true
|
|
48
|
+
}
|
|
49
|
+
]);
|
|
50
|
+
if (importSquitch) {
|
|
51
|
+
log.info('Importing Sqitch deployment history...');
|
|
52
|
+
await client.importFromSqitch();
|
|
53
|
+
log.success('Sqitch deployment imported successfully.');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
log.error(`Failed to initialize migration schema: ${error instanceof Error ? error.message : error}`);
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
await client.close();
|
|
63
|
+
}
|
|
64
|
+
return argv;
|
|
65
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@launchql/core");
|
|
4
|
+
const core_2 = require("@launchql/core");
|
|
5
|
+
const logger_1 = require("@launchql/logger");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const pg_env_1 = require("pg-env");
|
|
9
|
+
const database_1 = require("../../utils/database");
|
|
10
|
+
const log = new logger_1.Logger('migrate-list');
|
|
11
|
+
exports.default = async (argv, prompter, options) => {
|
|
12
|
+
const cwd = argv.cwd || process.cwd();
|
|
13
|
+
const planPath = (0, path_1.join)(cwd, 'pgpm.plan');
|
|
14
|
+
if (!(0, fs_1.existsSync)(planPath)) {
|
|
15
|
+
log.error(`No pgpm.plan found in ${cwd}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// Get database configuration
|
|
19
|
+
const pgEnv = (0, pg_env_1.getPgEnvOptions)();
|
|
20
|
+
const targetDatabase = await (0, database_1.getTargetDatabase)(argv, prompter, {
|
|
21
|
+
message: 'Select database to list migrations'
|
|
22
|
+
});
|
|
23
|
+
const client = new core_1.LaunchQLMigrate({
|
|
24
|
+
host: pgEnv.host,
|
|
25
|
+
port: pgEnv.port,
|
|
26
|
+
user: pgEnv.user,
|
|
27
|
+
password: pgEnv.password,
|
|
28
|
+
database: pgEnv.database
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
// Get all changes from plan file
|
|
32
|
+
const planResult = (0, core_2.parsePlanFile)(planPath);
|
|
33
|
+
if (!planResult.data || planResult.errors.length > 0) {
|
|
34
|
+
log.error('Failed to parse plan file:', planResult.errors);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const plan = planResult.data;
|
|
38
|
+
const allChanges = plan.changes;
|
|
39
|
+
// Get deployed changes from database
|
|
40
|
+
const deployedChanges = await client.getDeployedChanges(targetDatabase, plan.package);
|
|
41
|
+
console.log('\n📋 All Changes\n');
|
|
42
|
+
console.log(`Package: ${plan.package}`);
|
|
43
|
+
console.log(`Total Changes: ${allChanges.length}`);
|
|
44
|
+
console.log(`Deployed: ${deployedChanges.length}`);
|
|
45
|
+
console.log(`Pending: ${allChanges.length - deployedChanges.length}\n`);
|
|
46
|
+
// Create a map for quick lookup
|
|
47
|
+
const deployedMap = new Map(deployedChanges.map(c => [c.change_name, c]));
|
|
48
|
+
// List all changes with their status
|
|
49
|
+
const showAll = argv.all || allChanges.length <= 20;
|
|
50
|
+
const changesToShow = showAll ? allChanges : allChanges.slice(0, 20);
|
|
51
|
+
console.log('Status Change Name Dependencies');
|
|
52
|
+
console.log('------ ----------------------------- --------------------------------');
|
|
53
|
+
changesToShow.forEach(change => {
|
|
54
|
+
const deployed = deployedMap.get(change.name);
|
|
55
|
+
const status = deployed ? '✅' : '⏳';
|
|
56
|
+
const deps = change.dependencies.length > 0 ? change.dependencies.join(', ') : '-';
|
|
57
|
+
const depsDisplay = deps.length > 30 ? deps.substring(0, 27) + '...' : deps;
|
|
58
|
+
console.log(`${status} ${change.name.padEnd(30)} ${depsDisplay}`);
|
|
59
|
+
});
|
|
60
|
+
if (!showAll && allChanges.length > 20) {
|
|
61
|
+
console.log(`\n... and ${allChanges.length - 20} more changes. Use --all to see all changes.`);
|
|
62
|
+
}
|
|
63
|
+
// Show summary by status
|
|
64
|
+
if (argv.summary !== false) {
|
|
65
|
+
console.log('\n📊 Summary by Status:\n');
|
|
66
|
+
const pending = allChanges.filter(c => !deployedMap.has(c.name));
|
|
67
|
+
const deployed = allChanges.filter(c => deployedMap.has(c.name));
|
|
68
|
+
console.log(`✅ Deployed: ${deployed.length}`);
|
|
69
|
+
console.log(`⏳ Pending: ${pending.length}`);
|
|
70
|
+
// Show deployment timeline
|
|
71
|
+
if (deployedChanges.length > 0) {
|
|
72
|
+
const sortedDeployed = [...deployedChanges].sort((a, b) => new Date(a.deployed_at).getTime() - new Date(b.deployed_at).getTime());
|
|
73
|
+
const firstDeploy = new Date(sortedDeployed[0].deployed_at);
|
|
74
|
+
const lastDeploy = new Date(sortedDeployed[sortedDeployed.length - 1].deployed_at);
|
|
75
|
+
console.log(`\n📅 Deployment Timeline:`);
|
|
76
|
+
console.log(` First: ${firstDeploy.toLocaleString()}`);
|
|
77
|
+
console.log(` Last: ${lastDeploy.toLocaleString()}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
log.error('Failed to list changes:', error);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@launchql/core");
|
|
4
|
+
const core_2 = require("@launchql/core");
|
|
5
|
+
const logger_1 = require("@launchql/logger");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
const pg_env_1 = require("pg-env");
|
|
9
|
+
const database_1 = require("../../utils/database");
|
|
10
|
+
const log = new logger_1.Logger('migrate-status');
|
|
11
|
+
exports.default = async (argv, prompter, options) => {
|
|
12
|
+
const cwd = argv.cwd || process.cwd();
|
|
13
|
+
const planPath = (0, path_1.join)(cwd, 'pgpm.plan');
|
|
14
|
+
if (!(0, fs_1.existsSync)(planPath)) {
|
|
15
|
+
log.error(`No pgpm.plan found in ${cwd}`);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
// Get database configuration
|
|
19
|
+
const pgEnv = (0, pg_env_1.getPgEnvOptions)();
|
|
20
|
+
const targetDatabase = await (0, database_1.getTargetDatabase)(argv, prompter, {
|
|
21
|
+
message: 'Select database to check migration status'
|
|
22
|
+
});
|
|
23
|
+
const client = new core_1.LaunchQLMigrate({
|
|
24
|
+
host: pgEnv.host,
|
|
25
|
+
port: pgEnv.port,
|
|
26
|
+
user: pgEnv.user,
|
|
27
|
+
password: pgEnv.password,
|
|
28
|
+
database: pgEnv.database
|
|
29
|
+
});
|
|
30
|
+
try {
|
|
31
|
+
// Parse plan file to get package name
|
|
32
|
+
const planResult = (0, core_2.parsePlanFile)(planPath);
|
|
33
|
+
if (!planResult.data || planResult.errors.length > 0) {
|
|
34
|
+
log.error('Failed to parse plan file:', planResult.errors);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
const plan = planResult.data;
|
|
38
|
+
// Switch to target database
|
|
39
|
+
const targetClient = new core_1.LaunchQLMigrate({
|
|
40
|
+
host: pgEnv.host,
|
|
41
|
+
port: pgEnv.port,
|
|
42
|
+
user: pgEnv.user,
|
|
43
|
+
password: pgEnv.password,
|
|
44
|
+
database: targetDatabase
|
|
45
|
+
});
|
|
46
|
+
const statusResults = await targetClient.status(plan.package);
|
|
47
|
+
console.log('\n📊 Migration Status\n');
|
|
48
|
+
console.log(`Database: ${targetDatabase}`);
|
|
49
|
+
if (statusResults.length > 0) {
|
|
50
|
+
const status = statusResults[0];
|
|
51
|
+
console.log(`Package: ${status.package}`);
|
|
52
|
+
console.log(`Total Deployed: ${status.totalDeployed}`);
|
|
53
|
+
if (status.lastChange) {
|
|
54
|
+
console.log(`Last Change: ${status.lastChange}`);
|
|
55
|
+
console.log(`Last Deployed: ${status.lastDeployed.toLocaleString()}`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log('No changes deployed yet');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
console.log(`Package: ${plan.package}`);
|
|
63
|
+
console.log('No deployment history found');
|
|
64
|
+
}
|
|
65
|
+
// Show recent changes
|
|
66
|
+
const recentChanges = await targetClient.getRecentChanges(targetDatabase, 5);
|
|
67
|
+
if (recentChanges.length > 0) {
|
|
68
|
+
console.log('\n📋 Recent Changes:\n');
|
|
69
|
+
recentChanges.forEach((change) => {
|
|
70
|
+
const status = change.deployed_at ? '✅' : '⏳';
|
|
71
|
+
const date = change.deployed_at ? new Date(change.deployed_at).toLocaleString() : 'Not deployed';
|
|
72
|
+
console.log(`${status} ${change.change_name.padEnd(30)} ${date}`);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
// Show pending changes
|
|
76
|
+
const pendingChanges = await targetClient.getPendingChanges(planPath, targetDatabase);
|
|
77
|
+
if (pendingChanges.length > 0) {
|
|
78
|
+
console.log(`\n⏳ Pending Changes: ${pendingChanges.length}\n`);
|
|
79
|
+
pendingChanges.slice(0, 5).forEach((change) => {
|
|
80
|
+
console.log(` - ${change}`);
|
|
81
|
+
});
|
|
82
|
+
if (pendingChanges.length > 5) {
|
|
83
|
+
console.log(` ... and ${pendingChanges.length - 5} more`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log('\n✅ All changes deployed');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
log.error('Failed to get migration status:', error);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
const deps_1 = __importDefault(require("./migrate/deps"));
|
|
8
|
+
// Migrate subcommands
|
|
9
|
+
const init_1 = __importDefault(require("./migrate/init"));
|
|
10
|
+
const list_1 = __importDefault(require("./migrate/list"));
|
|
11
|
+
const status_1 = __importDefault(require("./migrate/status"));
|
|
12
|
+
const subcommandMap = {
|
|
13
|
+
init: init_1.default,
|
|
14
|
+
status: status_1.default,
|
|
15
|
+
list: list_1.default,
|
|
16
|
+
deps: deps_1.default
|
|
17
|
+
};
|
|
18
|
+
const migrateUsageText = `
|
|
19
|
+
Migrate Commands:
|
|
20
|
+
|
|
21
|
+
launchql migrate init Initialize migration tracking in database
|
|
22
|
+
launchql migrate status Show current migration status
|
|
23
|
+
launchql migrate list List all changes (deployed and pending)
|
|
24
|
+
launchql migrate deps Show change dependencies
|
|
25
|
+
|
|
26
|
+
Options:
|
|
27
|
+
--help, -h Show this help message
|
|
28
|
+
--cwd Working directory (default: current directory)
|
|
29
|
+
`;
|
|
30
|
+
exports.default = async (argv, prompter, options) => {
|
|
31
|
+
let { first: subcommand, newArgv } = (0, utils_1.extractFirst)(argv);
|
|
32
|
+
// Show usage if explicitly requested
|
|
33
|
+
if (argv.help || argv.h || subcommand === 'help') {
|
|
34
|
+
console.log(migrateUsageText);
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
// Prompt if no subcommand provided
|
|
38
|
+
if (!subcommand) {
|
|
39
|
+
const answer = await prompter.prompt(argv, [
|
|
40
|
+
{
|
|
41
|
+
type: 'autocomplete',
|
|
42
|
+
name: 'subcommand',
|
|
43
|
+
message: 'What migrate operation do you want to perform?',
|
|
44
|
+
options: Object.keys(subcommandMap).map(cmd => ({
|
|
45
|
+
name: cmd,
|
|
46
|
+
value: cmd,
|
|
47
|
+
description: getSubcommandDescription(cmd)
|
|
48
|
+
}))
|
|
49
|
+
}
|
|
50
|
+
]);
|
|
51
|
+
subcommand = answer.subcommand;
|
|
52
|
+
}
|
|
53
|
+
const subcommandFn = subcommandMap[subcommand];
|
|
54
|
+
if (!subcommandFn) {
|
|
55
|
+
console.error(`Unknown migrate subcommand: ${subcommand}`);
|
|
56
|
+
console.log(migrateUsageText);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
await subcommandFn(newArgv, prompter, options);
|
|
60
|
+
};
|
|
61
|
+
function getSubcommandDescription(cmd) {
|
|
62
|
+
const descriptions = {
|
|
63
|
+
init: 'Initialize migration tracking in database',
|
|
64
|
+
status: 'Show current migration status',
|
|
65
|
+
list: 'List all changes (deployed and pending)',
|
|
66
|
+
deps: 'Show change dependencies'
|
|
67
|
+
};
|
|
68
|
+
return descriptions[cmd] || '';
|
|
69
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@launchql/core");
|
|
4
|
+
const packageUsageText = `
|
|
5
|
+
Package Command:
|
|
6
|
+
|
|
7
|
+
constructive package [OPTIONS]
|
|
8
|
+
|
|
9
|
+
Package module for distribution.
|
|
10
|
+
|
|
11
|
+
Options:
|
|
12
|
+
--help, -h Show this help message
|
|
13
|
+
--plan Include deployment plan (default: true)
|
|
14
|
+
--pretty Pretty-print output (default: true)
|
|
15
|
+
--functionDelimiter <delimiter> Function delimiter (default: $EOFCODE$)
|
|
16
|
+
--cwd <directory> Working directory (default: current directory)
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
constructive package Package with defaults
|
|
20
|
+
constructive package --no-plan Package without plan
|
|
21
|
+
`;
|
|
22
|
+
exports.default = async (argv, prompter, _options) => {
|
|
23
|
+
// Show usage if explicitly requested
|
|
24
|
+
if (argv.help || argv.h) {
|
|
25
|
+
console.log(packageUsageText);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
const questions = [
|
|
29
|
+
{
|
|
30
|
+
type: 'confirm',
|
|
31
|
+
name: 'plan',
|
|
32
|
+
default: true,
|
|
33
|
+
useDefault: true,
|
|
34
|
+
required: true
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: 'confirm',
|
|
38
|
+
name: 'pretty',
|
|
39
|
+
default: true,
|
|
40
|
+
useDefault: true,
|
|
41
|
+
required: true
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'text',
|
|
45
|
+
name: 'functionDelimiter',
|
|
46
|
+
default: '$EOFCODE$',
|
|
47
|
+
useDefault: true,
|
|
48
|
+
required: false
|
|
49
|
+
}
|
|
50
|
+
];
|
|
51
|
+
let { cwd, plan, pretty, functionDelimiter } = await prompter.prompt(argv, questions);
|
|
52
|
+
const project = new core_1.LaunchQLPackage(cwd);
|
|
53
|
+
project.ensureModule();
|
|
54
|
+
const info = project.getModuleInfo();
|
|
55
|
+
info.version;
|
|
56
|
+
await (0, core_1.writePackage)({
|
|
57
|
+
version: info.version,
|
|
58
|
+
extension: true,
|
|
59
|
+
usePlan: plan,
|
|
60
|
+
packageDir: project.modulePath,
|
|
61
|
+
pretty,
|
|
62
|
+
functionDelimiter
|
|
63
|
+
});
|
|
64
|
+
return argv;
|
|
65
|
+
};
|
package/commands/plan.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@launchql/core");
|
|
4
|
+
const logger_1 = require("@launchql/logger");
|
|
5
|
+
const log = new logger_1.Logger('plan');
|
|
6
|
+
const planUsageText = `
|
|
7
|
+
Plan Command:
|
|
8
|
+
|
|
9
|
+
constructive plan [OPTIONS]
|
|
10
|
+
|
|
11
|
+
Generate module deployment plans.
|
|
12
|
+
|
|
13
|
+
Options:
|
|
14
|
+
--help, -h Show this help message
|
|
15
|
+
--includePackages Include packages in plan (default: true)
|
|
16
|
+
--includeTags Prefer @tag references for external packages when available (default: true)
|
|
17
|
+
--cwd <directory> Working directory (default: current directory)
|
|
18
|
+
|
|
19
|
+
Examples:
|
|
20
|
+
constructive plan Generate deployment plan for current module with defaults
|
|
21
|
+
constructive plan --includePackages false Disable including external packages
|
|
22
|
+
constructive plan --includeTags false Do not prefer tags for external packages
|
|
23
|
+
`;
|
|
24
|
+
exports.default = async (argv, prompter, _options) => {
|
|
25
|
+
// Show usage if explicitly requested
|
|
26
|
+
if (argv.help || argv.h) {
|
|
27
|
+
console.log(planUsageText);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
const questions = [
|
|
31
|
+
{
|
|
32
|
+
type: 'confirm',
|
|
33
|
+
name: 'includePackages',
|
|
34
|
+
message: 'Include packages in plan?',
|
|
35
|
+
useDefault: true,
|
|
36
|
+
default: true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
type: 'confirm',
|
|
40
|
+
name: 'includeTags',
|
|
41
|
+
message: 'Prefer @tag references for external packages when available?',
|
|
42
|
+
useDefault: true,
|
|
43
|
+
default: true
|
|
44
|
+
}
|
|
45
|
+
];
|
|
46
|
+
let { cwd, includePackages, includeTags } = await prompter.prompt(argv, questions);
|
|
47
|
+
if (!cwd) {
|
|
48
|
+
cwd = process.cwd();
|
|
49
|
+
log.info(`Using current directory: ${cwd}`);
|
|
50
|
+
}
|
|
51
|
+
const pkg = new core_1.LaunchQLPackage(cwd);
|
|
52
|
+
if (!pkg.isInModule()) {
|
|
53
|
+
throw new Error('This command must be run inside a LaunchQL module.');
|
|
54
|
+
}
|
|
55
|
+
const includePackagesFlag = typeof includePackages === 'boolean' ? includePackages : true;
|
|
56
|
+
const includeTagsFlag = typeof includeTags === 'boolean' ? includeTags : true;
|
|
57
|
+
pkg.writeModulePlan({
|
|
58
|
+
includePackages: includePackagesFlag,
|
|
59
|
+
includeTags: includeTagsFlag
|
|
60
|
+
});
|
|
61
|
+
return argv;
|
|
62
|
+
};
|