@meltstudio/meltctl 2.2.0 → 2.3.0
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 +22 -0
- package/dist/commands/project/update.js +24 -68
- package/dist/commands/version.d.ts +1 -0
- package/dist/commands/version.js +43 -0
- package/dist/index.js +20 -2
- package/dist/utils/package-manager.d.ts +7 -0
- package/dist/utils/package-manager.js +55 -0
- package/dist/utils/version-check.d.ts +5 -0
- package/dist/utils/version-check.js +114 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,6 +82,28 @@ Safely removes all Melt-generated files from your project while preserving user-
|
|
|
82
82
|
- Preserves user-created files in `.cursor/commands/`
|
|
83
83
|
- Provides interactive confirmation before deletion
|
|
84
84
|
|
|
85
|
+
### Version Check
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
meltctl version --check
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Checks for available updates to the meltctl CLI. This command:
|
|
92
|
+
- Compares your current version with the latest published version
|
|
93
|
+
- Provides update instructions based on your package manager (npm/yarn)
|
|
94
|
+
- Handles network errors gracefully
|
|
95
|
+
|
|
96
|
+
### CI/CD Usage
|
|
97
|
+
|
|
98
|
+
For automated environments (CI/CD pipelines), you can skip the update check that runs before every command:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
export MELTCTL_SKIP_UPDATE_CHECK=1
|
|
102
|
+
meltctl project init
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
This environment variable bypasses the automatic update enforcement that normally prevents running commands with outdated versions.
|
|
106
|
+
|
|
85
107
|
## 🛠️ Requirements
|
|
86
108
|
|
|
87
109
|
- Node.js 22+ (works with Node.js 18+ but 22+ recommended)
|
|
@@ -4,66 +4,15 @@ import { execSync } from 'child_process';
|
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
|
+
import { getCurrentCliVersion, getLatestCliVersion, compareVersions, } from '../../utils/version-check.js';
|
|
8
|
+
import { detectPackageManager } from '../../utils/package-manager.js';
|
|
7
9
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
async function getCurrentCliVersion() {
|
|
9
|
-
const packagePath = path.join(__dirname, '../../../package.json');
|
|
10
|
-
const packageJson = await fs.readJson(packagePath);
|
|
11
|
-
return packageJson.version;
|
|
12
|
-
}
|
|
13
|
-
async function getLatestCliVersion() {
|
|
14
|
-
try {
|
|
15
|
-
const result = execSync('npm view @meltstudio/meltctl version --json', {
|
|
16
|
-
encoding: 'utf-8',
|
|
17
|
-
stdio: 'pipe',
|
|
18
|
-
});
|
|
19
|
-
return JSON.parse(result.trim());
|
|
20
|
-
}
|
|
21
|
-
catch (error) {
|
|
22
|
-
console.error(chalk.red('Failed to check latest version from npm registry'));
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function compareVersions(current, latest) {
|
|
27
|
-
// Parse semver strings, handling pre-release tags
|
|
28
|
-
const parseVersion = (version) => {
|
|
29
|
-
const [base, prerelease] = version.split('-');
|
|
30
|
-
const parts = (base || '').split('.').map(Number);
|
|
31
|
-
return {
|
|
32
|
-
major: parts[0] || 0,
|
|
33
|
-
minor: parts[1] || 0,
|
|
34
|
-
patch: parts[2] || 0,
|
|
35
|
-
prerelease: prerelease || null,
|
|
36
|
-
};
|
|
37
|
-
};
|
|
38
|
-
const currentVer = parseVersion(current);
|
|
39
|
-
const latestVer = parseVersion(latest);
|
|
40
|
-
// Compare major.minor.patch first
|
|
41
|
-
if (latestVer.major !== currentVer.major) {
|
|
42
|
-
return latestVer.major > currentVer.major;
|
|
43
|
-
}
|
|
44
|
-
if (latestVer.minor !== currentVer.minor) {
|
|
45
|
-
return latestVer.minor > currentVer.minor;
|
|
46
|
-
}
|
|
47
|
-
if (latestVer.patch !== currentVer.patch) {
|
|
48
|
-
return latestVer.patch > currentVer.patch;
|
|
49
|
-
}
|
|
50
|
-
// If base versions are equal, handle pre-release comparison
|
|
51
|
-
// No pre-release (stable) > pre-release
|
|
52
|
-
if (!latestVer.prerelease && currentVer.prerelease)
|
|
53
|
-
return true;
|
|
54
|
-
if (latestVer.prerelease && !currentVer.prerelease)
|
|
55
|
-
return false;
|
|
56
|
-
// Both have pre-release or both are stable
|
|
57
|
-
if (latestVer.prerelease && currentVer.prerelease) {
|
|
58
|
-
return latestVer.prerelease > currentVer.prerelease;
|
|
59
|
-
}
|
|
60
|
-
return false; // Versions are equal
|
|
61
|
-
}
|
|
62
10
|
async function updateCliPackage() {
|
|
63
11
|
const s = spinner();
|
|
64
12
|
s.start('Updating @meltstudio/meltctl package...');
|
|
65
13
|
try {
|
|
66
|
-
|
|
14
|
+
const { updateCommand } = detectPackageManager();
|
|
15
|
+
execSync(updateCommand, {
|
|
67
16
|
stdio: 'pipe',
|
|
68
17
|
});
|
|
69
18
|
s.stop('CLI package updated successfully!');
|
|
@@ -115,22 +64,29 @@ export async function updateCommand() {
|
|
|
115
64
|
try {
|
|
116
65
|
const currentVersion = await getCurrentCliVersion();
|
|
117
66
|
const latestVersion = await getLatestCliVersion();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
console.log(chalk.yellow(`📦 New version available: ${latestVersion}`));
|
|
123
|
-
const shouldUpdate = await confirm({
|
|
124
|
-
message: 'Would you like to update the CLI package?',
|
|
125
|
-
});
|
|
126
|
-
if (shouldUpdate) {
|
|
127
|
-
await updateCliPackage();
|
|
128
|
-
console.log();
|
|
129
|
-
}
|
|
67
|
+
if (!latestVersion) {
|
|
68
|
+
console.log(chalk.yellow('⚠️ Unable to check for updates (network error)'));
|
|
69
|
+
console.log(chalk.gray(`Current CLI version: ${currentVersion}`));
|
|
70
|
+
console.log();
|
|
130
71
|
}
|
|
131
72
|
else {
|
|
132
|
-
console.log(chalk.
|
|
73
|
+
console.log(chalk.gray(`Current CLI version: ${currentVersion}`));
|
|
74
|
+
console.log(chalk.gray(`Latest CLI version: ${latestVersion}`));
|
|
133
75
|
console.log();
|
|
76
|
+
if (compareVersions(currentVersion, latestVersion)) {
|
|
77
|
+
console.log(chalk.yellow(`📦 New version available: ${latestVersion}`));
|
|
78
|
+
const shouldUpdate = await confirm({
|
|
79
|
+
message: 'Would you like to update the CLI package?',
|
|
80
|
+
});
|
|
81
|
+
if (shouldUpdate) {
|
|
82
|
+
await updateCliPackage();
|
|
83
|
+
console.log();
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
console.log(chalk.green('✅ CLI package is up to date'));
|
|
88
|
+
console.log();
|
|
89
|
+
}
|
|
134
90
|
}
|
|
135
91
|
const shouldUpdateCommands = await confirm({
|
|
136
92
|
message: 'Would you like to update .cursor/commands/ with latest prompts?',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function versionCheckCommand(): Promise<void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { getCurrentCliVersion, getLatestCliVersion, compareVersions, } from '../utils/version-check.js';
|
|
3
|
+
import { getUpdateInstructions } from '../utils/package-manager.js';
|
|
4
|
+
export async function versionCheckCommand() {
|
|
5
|
+
try {
|
|
6
|
+
const currentVersion = await getCurrentCliVersion();
|
|
7
|
+
const latestVersion = await getLatestCliVersion();
|
|
8
|
+
if (!latestVersion) {
|
|
9
|
+
console.log(chalk.yellow('⚠️ Unable to check for updates (network error)'));
|
|
10
|
+
console.log(chalk.gray(`Current version: ${currentVersion}`));
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (compareVersions(currentVersion, latestVersion)) {
|
|
14
|
+
// Update available
|
|
15
|
+
console.log(chalk.yellow(`⚠ Update available: ${currentVersion} → ${latestVersion}`));
|
|
16
|
+
console.log();
|
|
17
|
+
console.log(chalk.white('To update, run:'));
|
|
18
|
+
const instructions = getUpdateInstructions();
|
|
19
|
+
instructions.forEach((instruction, index) => {
|
|
20
|
+
if (index === 0) {
|
|
21
|
+
console.log(chalk.cyan(` ${instruction}`));
|
|
22
|
+
}
|
|
23
|
+
else if (instruction === '') {
|
|
24
|
+
console.log();
|
|
25
|
+
}
|
|
26
|
+
else if (instruction.startsWith('Or with')) {
|
|
27
|
+
console.log(chalk.gray(instruction));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
console.log(chalk.cyan(instruction));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Up to date
|
|
36
|
+
console.log(chalk.green(`✓ meltctl ${currentVersion} is up to date`));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error(chalk.red('Failed to check for updates:'), error instanceof Error ? error.message : String(error));
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ import { fileURLToPath } from 'url';
|
|
|
6
6
|
import { initCommand } from './commands/project/init.js';
|
|
7
7
|
import { updateCommand } from './commands/project/update.js';
|
|
8
8
|
import { cleanCommand } from './commands/project/clean.js';
|
|
9
|
+
import { checkAndEnforceUpdate } from './utils/version-check.js';
|
|
10
|
+
import { versionCheckCommand } from './commands/version.js';
|
|
9
11
|
// Read version from package.json
|
|
10
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
13
|
const __dirname = dirname(__filename);
|
|
@@ -14,7 +16,10 @@ const program = new Command();
|
|
|
14
16
|
program
|
|
15
17
|
.name('meltctl')
|
|
16
18
|
.description('CLI tool for Melt development process automation')
|
|
17
|
-
.version(packageJson.version)
|
|
19
|
+
.version(packageJson.version)
|
|
20
|
+
.hook('preAction', async () => {
|
|
21
|
+
await checkAndEnforceUpdate();
|
|
22
|
+
});
|
|
18
23
|
// Project management commands
|
|
19
24
|
const projectCommand = program
|
|
20
25
|
.command('project')
|
|
@@ -41,4 +46,17 @@ projectCommand
|
|
|
41
46
|
.action(() => {
|
|
42
47
|
return cleanCommand();
|
|
43
48
|
});
|
|
44
|
-
|
|
49
|
+
// Version check command
|
|
50
|
+
program
|
|
51
|
+
.command('version')
|
|
52
|
+
.description('Display version information')
|
|
53
|
+
.option('--check', 'check for updates')
|
|
54
|
+
.action(async (options) => {
|
|
55
|
+
if (options.check) {
|
|
56
|
+
await versionCheckCommand();
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
console.log(packageJson.version);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
program.parseAsync(process.argv);
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type PackageManager = 'npm' | 'yarn' | 'unknown';
|
|
2
|
+
export interface PackageManagerInfo {
|
|
3
|
+
type: PackageManager;
|
|
4
|
+
updateCommand: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function detectPackageManager(): PackageManagerInfo;
|
|
7
|
+
export declare function getUpdateInstructions(): string[];
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { execSync } from 'child_process';
|
|
2
|
+
export function detectPackageManager() {
|
|
3
|
+
// Try to detect if meltctl is installed via npm or yarn
|
|
4
|
+
try {
|
|
5
|
+
// Check npm global installation
|
|
6
|
+
execSync('npm list -g @meltstudio/meltctl', {
|
|
7
|
+
encoding: 'utf-8',
|
|
8
|
+
stdio: 'pipe',
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
type: 'npm',
|
|
12
|
+
updateCommand: 'npm install -g @meltstudio/meltctl@latest',
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
// npm check failed, try yarn
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
// Check yarn global installation
|
|
20
|
+
execSync('yarn global list @meltstudio/meltctl', {
|
|
21
|
+
encoding: 'utf-8',
|
|
22
|
+
stdio: 'pipe',
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
type: 'yarn',
|
|
26
|
+
updateCommand: 'yarn global add @meltstudio/meltctl@latest',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// yarn check failed
|
|
31
|
+
}
|
|
32
|
+
// Default to npm if detection fails
|
|
33
|
+
return {
|
|
34
|
+
type: 'unknown',
|
|
35
|
+
updateCommand: 'npm install -g @meltstudio/meltctl@latest',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function getUpdateInstructions() {
|
|
39
|
+
const { type, updateCommand } = detectPackageManager();
|
|
40
|
+
if (type === 'npm') {
|
|
41
|
+
return [updateCommand, '', 'Or with yarn:', ' yarn global add @meltstudio/meltctl@latest'];
|
|
42
|
+
}
|
|
43
|
+
else if (type === 'yarn') {
|
|
44
|
+
return [updateCommand, '', 'Or with npm:', ' npm install -g @meltstudio/meltctl@latest'];
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Unknown, show both options
|
|
48
|
+
return [
|
|
49
|
+
'npm install -g @meltstudio/meltctl@latest',
|
|
50
|
+
'',
|
|
51
|
+
'Or with yarn:',
|
|
52
|
+
' yarn global add @meltstudio/meltctl@latest',
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function getCurrentCliVersion(): Promise<string>;
|
|
2
|
+
export declare function getLatestCliVersion(): Promise<string | null>;
|
|
3
|
+
export declare function compareVersions(current: string, latest: string): boolean;
|
|
4
|
+
export declare function isCI(): boolean;
|
|
5
|
+
export declare function checkAndEnforceUpdate(): Promise<void>;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
export async function getCurrentCliVersion() {
|
|
8
|
+
const packagePath = path.join(__dirname, '../../package.json');
|
|
9
|
+
const packageJson = await fs.readJson(packagePath);
|
|
10
|
+
return packageJson.version;
|
|
11
|
+
}
|
|
12
|
+
export async function getLatestCliVersion() {
|
|
13
|
+
try {
|
|
14
|
+
const result = execSync('npm view @meltstudio/meltctl version --json', {
|
|
15
|
+
encoding: 'utf-8',
|
|
16
|
+
stdio: 'pipe',
|
|
17
|
+
timeout: 5000, // 5 second timeout
|
|
18
|
+
});
|
|
19
|
+
return JSON.parse(result.trim());
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Network error or timeout - return null to indicate check failed
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function compareVersions(current, latest) {
|
|
27
|
+
// Parse semver strings, handling pre-release tags
|
|
28
|
+
const parseVersion = (version) => {
|
|
29
|
+
const [base, prerelease] = version.split('-');
|
|
30
|
+
const parts = (base || '').split('.').map(Number);
|
|
31
|
+
return {
|
|
32
|
+
major: parts[0] || 0,
|
|
33
|
+
minor: parts[1] || 0,
|
|
34
|
+
patch: parts[2] || 0,
|
|
35
|
+
prerelease: prerelease || null,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const currentVer = parseVersion(current);
|
|
39
|
+
const latestVer = parseVersion(latest);
|
|
40
|
+
// Compare major.minor.patch first
|
|
41
|
+
if (latestVer.major !== currentVer.major) {
|
|
42
|
+
return latestVer.major > currentVer.major;
|
|
43
|
+
}
|
|
44
|
+
if (latestVer.minor !== currentVer.minor) {
|
|
45
|
+
return latestVer.minor > currentVer.minor;
|
|
46
|
+
}
|
|
47
|
+
if (latestVer.patch !== currentVer.patch) {
|
|
48
|
+
return latestVer.patch > currentVer.patch;
|
|
49
|
+
}
|
|
50
|
+
// If base versions are equal, handle pre-release comparison
|
|
51
|
+
// No pre-release (stable) > pre-release
|
|
52
|
+
if (!latestVer.prerelease && currentVer.prerelease)
|
|
53
|
+
return true;
|
|
54
|
+
if (latestVer.prerelease && !currentVer.prerelease)
|
|
55
|
+
return false;
|
|
56
|
+
// Both have pre-release or both are stable
|
|
57
|
+
if (latestVer.prerelease && currentVer.prerelease) {
|
|
58
|
+
return latestVer.prerelease > currentVer.prerelease;
|
|
59
|
+
}
|
|
60
|
+
return false; // Versions are equal
|
|
61
|
+
}
|
|
62
|
+
export function isCI() {
|
|
63
|
+
// Check common CI environment variables
|
|
64
|
+
return !!(process.env.CI || // Generic CI flag
|
|
65
|
+
process.env.GITHUB_ACTIONS || // GitHub Actions
|
|
66
|
+
process.env.GITLAB_CI || // GitLab CI
|
|
67
|
+
process.env.CIRCLECI || // CircleCI
|
|
68
|
+
process.env.TRAVIS || // Travis CI
|
|
69
|
+
process.env.JENKINS_URL || // Jenkins
|
|
70
|
+
process.env.BUILDKITE || // Buildkite
|
|
71
|
+
process.env.DRONE || // Drone
|
|
72
|
+
process.env.MELTCTL_SKIP_UPDATE_CHECK // Custom override
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
export async function checkAndEnforceUpdate() {
|
|
76
|
+
// Skip update check in CI environments
|
|
77
|
+
if (isCI()) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const currentVersion = await getCurrentCliVersion();
|
|
82
|
+
const latestVersion = await getLatestCliVersion();
|
|
83
|
+
// If we can't fetch latest version (network error), allow continuing
|
|
84
|
+
if (!latestVersion) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
// Check if update is needed
|
|
88
|
+
if (compareVersions(currentVersion, latestVersion)) {
|
|
89
|
+
console.log();
|
|
90
|
+
console.log(chalk.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
91
|
+
console.log(chalk.red.bold(' ⚠️ Update Required'));
|
|
92
|
+
console.log(chalk.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
|
|
93
|
+
console.log();
|
|
94
|
+
console.log(chalk.yellow(`Current version: ${currentVersion}`));
|
|
95
|
+
console.log(chalk.green(`Latest version: ${latestVersion}`));
|
|
96
|
+
console.log();
|
|
97
|
+
console.log(chalk.white('Please update meltctl to continue:'));
|
|
98
|
+
console.log();
|
|
99
|
+
console.log(chalk.cyan(' npm install -g @meltstudio/meltctl@latest'));
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(chalk.gray('Or with yarn:'));
|
|
102
|
+
console.log(chalk.cyan(' yarn global add @meltstudio/meltctl@latest'));
|
|
103
|
+
console.log();
|
|
104
|
+
console.log(chalk.gray('To skip this check (CI/CD), set MELTCTL_SKIP_UPDATE_CHECK=1'));
|
|
105
|
+
console.log();
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// If any error occurs during version check, allow continuing
|
|
111
|
+
// This ensures the CLI remains usable even if version check fails
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
package/package.json
CHANGED