@meltstudio/meltctl 4.38.0 → 4.39.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/dist/index.js +2178 -210
- package/package.json +4 -3
- package/dist/commands/audit.d.ts +0 -10
- package/dist/commands/audit.js +0 -191
- package/dist/commands/audit.test.d.ts +0 -1
- package/dist/commands/audit.test.js +0 -324
- package/dist/commands/coins.d.ts +0 -5
- package/dist/commands/coins.js +0 -51
- package/dist/commands/coins.test.d.ts +0 -1
- package/dist/commands/coins.test.js +0 -113
- package/dist/commands/feedback.d.ts +0 -7
- package/dist/commands/feedback.js +0 -90
- package/dist/commands/feedback.test.d.ts +0 -1
- package/dist/commands/feedback.test.js +0 -177
- package/dist/commands/init.d.ts +0 -8
- package/dist/commands/init.js +0 -520
- package/dist/commands/init.test.d.ts +0 -1
- package/dist/commands/init.test.js +0 -478
- package/dist/commands/login.d.ts +0 -1
- package/dist/commands/login.js +0 -90
- package/dist/commands/login.test.d.ts +0 -1
- package/dist/commands/login.test.js +0 -194
- package/dist/commands/logout.d.ts +0 -1
- package/dist/commands/logout.js +0 -12
- package/dist/commands/logout.test.d.ts +0 -1
- package/dist/commands/logout.test.js +0 -59
- package/dist/commands/plan.d.ts +0 -6
- package/dist/commands/plan.js +0 -123
- package/dist/commands/plan.test.d.ts +0 -1
- package/dist/commands/plan.test.js +0 -246
- package/dist/commands/standup.d.ts +0 -7
- package/dist/commands/standup.js +0 -74
- package/dist/commands/standup.test.d.ts +0 -1
- package/dist/commands/standup.test.js +0 -218
- package/dist/commands/templates.d.ts +0 -1
- package/dist/commands/templates.js +0 -37
- package/dist/commands/templates.test.d.ts +0 -1
- package/dist/commands/templates.test.js +0 -89
- package/dist/commands/update.d.ts +0 -2
- package/dist/commands/update.js +0 -74
- package/dist/commands/update.test.d.ts +0 -1
- package/dist/commands/update.test.js +0 -93
- package/dist/commands/version.d.ts +0 -1
- package/dist/commands/version.js +0 -43
- package/dist/commands/version.test.d.ts +0 -1
- package/dist/commands/version.test.js +0 -86
- package/dist/index.d.ts +0 -2
- package/dist/utils/analytics.d.ts +0 -1
- package/dist/utils/analytics.js +0 -54
- package/dist/utils/analytics.test.d.ts +0 -1
- package/dist/utils/analytics.test.js +0 -91
- package/dist/utils/api.d.ts +0 -3
- package/dist/utils/api.js +0 -23
- package/dist/utils/api.test.d.ts +0 -1
- package/dist/utils/api.test.js +0 -76
- package/dist/utils/auth.d.ts +0 -12
- package/dist/utils/auth.js +0 -54
- package/dist/utils/auth.test.d.ts +0 -1
- package/dist/utils/auth.test.js +0 -165
- package/dist/utils/banner.d.ts +0 -1
- package/dist/utils/banner.js +0 -22
- package/dist/utils/banner.test.d.ts +0 -1
- package/dist/utils/banner.test.js +0 -34
- package/dist/utils/debug.d.ts +0 -1
- package/dist/utils/debug.js +0 -6
- package/dist/utils/git.d.ts +0 -9
- package/dist/utils/git.js +0 -76
- package/dist/utils/git.test.d.ts +0 -1
- package/dist/utils/git.test.js +0 -184
- package/dist/utils/package-manager.d.ts +0 -7
- package/dist/utils/package-manager.js +0 -55
- package/dist/utils/package-manager.test.d.ts +0 -1
- package/dist/utils/package-manager.test.js +0 -76
- package/dist/utils/templates.d.ts +0 -2
- package/dist/utils/templates.js +0 -5
- package/dist/utils/templates.test.d.ts +0 -1
- package/dist/utils/templates.test.js +0 -38
- package/dist/utils/version-check.d.ts +0 -7
- package/dist/utils/version-check.js +0 -139
- package/dist/utils/version-check.test.d.ts +0 -1
- package/dist/utils/version-check.test.js +0 -189
package/dist/utils/git.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
export declare function getGitBranch(): string;
|
|
2
|
-
export declare function getGitCommit(): string;
|
|
3
|
-
export declare function getGitRepository(): {
|
|
4
|
-
slug: string;
|
|
5
|
-
url: string;
|
|
6
|
-
} | null;
|
|
7
|
-
export declare function getProjectName(): string;
|
|
8
|
-
export declare function extractTicketId(branch: string): string | null;
|
|
9
|
-
export declare function findMdFiles(dir: string): Promise<string[]>;
|
package/dist/utils/git.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import fs from 'fs-extra';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { execSync } from 'child_process';
|
|
4
|
-
export function getGitBranch() {
|
|
5
|
-
try {
|
|
6
|
-
return execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
|
|
7
|
-
}
|
|
8
|
-
catch {
|
|
9
|
-
return 'unknown';
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export function getGitCommit() {
|
|
13
|
-
try {
|
|
14
|
-
return execSync('git rev-parse --short HEAD', { encoding: 'utf-8' }).trim();
|
|
15
|
-
}
|
|
16
|
-
catch {
|
|
17
|
-
return 'unknown';
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
export function getGitRepository() {
|
|
21
|
-
try {
|
|
22
|
-
const url = execSync('git remote get-url origin', { encoding: 'utf-8' }).trim();
|
|
23
|
-
// Extract owner/repo from various URL formats:
|
|
24
|
-
// git@github.com:Owner/Repo.git -> Owner/Repo
|
|
25
|
-
// https://github.com/Owner/Repo.git -> Owner/Repo
|
|
26
|
-
// git@gitlab.com:Owner/Repo.git -> Owner/Repo
|
|
27
|
-
const match = url.match(/[/:]([\w.-]+\/[\w.-]+?)(?:\.git)?$/);
|
|
28
|
-
const slug = match ? match[1] : url;
|
|
29
|
-
return { slug, url };
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
return null;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
export function getProjectName() {
|
|
36
|
-
const cwd = process.cwd();
|
|
37
|
-
try {
|
|
38
|
-
const pkgPath = path.join(cwd, 'package.json');
|
|
39
|
-
if (fs.pathExistsSync(pkgPath)) {
|
|
40
|
-
const pkg = fs.readJsonSync(pkgPath);
|
|
41
|
-
if (pkg.name) {
|
|
42
|
-
return pkg.name;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// fall through
|
|
48
|
-
}
|
|
49
|
-
return path.basename(cwd);
|
|
50
|
-
}
|
|
51
|
-
export function extractTicketId(branch) {
|
|
52
|
-
const match = branch.match(/([A-Z]+-\d+)/i);
|
|
53
|
-
return match ? match[1] : null;
|
|
54
|
-
}
|
|
55
|
-
export async function findMdFiles(dir) {
|
|
56
|
-
if (!(await fs.pathExists(dir))) {
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
const results = [];
|
|
60
|
-
async function walk(current) {
|
|
61
|
-
const entries = await fs.readdir(current, { withFileTypes: true });
|
|
62
|
-
for (const entry of entries) {
|
|
63
|
-
const fullPath = path.join(current, entry.name);
|
|
64
|
-
if (entry.isDirectory()) {
|
|
65
|
-
await walk(fullPath);
|
|
66
|
-
}
|
|
67
|
-
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
68
|
-
const stat = await fs.stat(fullPath);
|
|
69
|
-
results.push({ path: fullPath, mtime: stat.mtimeMs });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
await walk(dir);
|
|
74
|
-
results.sort((a, b) => b.mtime - a.mtime);
|
|
75
|
-
return results.map(r => r.path);
|
|
76
|
-
}
|
package/dist/utils/git.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/utils/git.test.js
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
vi.mock('child_process', () => ({
|
|
3
|
-
execSync: vi.fn(),
|
|
4
|
-
}));
|
|
5
|
-
vi.mock('fs-extra', () => ({
|
|
6
|
-
default: {
|
|
7
|
-
pathExistsSync: vi.fn(),
|
|
8
|
-
readJsonSync: vi.fn(),
|
|
9
|
-
pathExists: vi.fn(),
|
|
10
|
-
readdir: vi.fn(),
|
|
11
|
-
stat: vi.fn(),
|
|
12
|
-
},
|
|
13
|
-
}));
|
|
14
|
-
import { execSync } from 'child_process';
|
|
15
|
-
import fs from 'fs-extra';
|
|
16
|
-
import { getGitBranch, getGitCommit, getGitRepository, getProjectName, extractTicketId, findMdFiles, } from './git.js';
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
vi.clearAllMocks();
|
|
19
|
-
});
|
|
20
|
-
describe('getGitBranch', () => {
|
|
21
|
-
it('returns trimmed branch name', () => {
|
|
22
|
-
;
|
|
23
|
-
execSync.mockReturnValue(' feature/my-branch\n');
|
|
24
|
-
expect(getGitBranch()).toBe('feature/my-branch');
|
|
25
|
-
});
|
|
26
|
-
it('returns "unknown" when git command fails', () => {
|
|
27
|
-
;
|
|
28
|
-
execSync.mockImplementation(() => {
|
|
29
|
-
throw new Error('not a git repo');
|
|
30
|
-
});
|
|
31
|
-
expect(getGitBranch()).toBe('unknown');
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
describe('getGitCommit', () => {
|
|
35
|
-
it('returns trimmed commit hash', () => {
|
|
36
|
-
;
|
|
37
|
-
execSync.mockReturnValue('abc1234\n');
|
|
38
|
-
expect(getGitCommit()).toBe('abc1234');
|
|
39
|
-
});
|
|
40
|
-
it('returns "unknown" when git command fails', () => {
|
|
41
|
-
;
|
|
42
|
-
execSync.mockImplementation(() => {
|
|
43
|
-
throw new Error('not a git repo');
|
|
44
|
-
});
|
|
45
|
-
expect(getGitCommit()).toBe('unknown');
|
|
46
|
-
});
|
|
47
|
-
});
|
|
48
|
-
describe('getGitRepository', () => {
|
|
49
|
-
it('parses SSH URL format', () => {
|
|
50
|
-
;
|
|
51
|
-
execSync.mockReturnValue('git@github.com:Owner/Repo.git\n');
|
|
52
|
-
const result = getGitRepository();
|
|
53
|
-
expect(result).toEqual({
|
|
54
|
-
slug: 'Owner/Repo',
|
|
55
|
-
url: 'git@github.com:Owner/Repo.git',
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
it('parses HTTPS URL format', () => {
|
|
59
|
-
;
|
|
60
|
-
execSync.mockReturnValue('https://github.com/Owner/Repo.git\n');
|
|
61
|
-
const result = getGitRepository();
|
|
62
|
-
expect(result).toEqual({
|
|
63
|
-
slug: 'Owner/Repo',
|
|
64
|
-
url: 'https://github.com/Owner/Repo.git',
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
it('parses HTTPS URL without .git suffix', () => {
|
|
68
|
-
;
|
|
69
|
-
execSync.mockReturnValue('https://github.com/Owner/Repo\n');
|
|
70
|
-
const result = getGitRepository();
|
|
71
|
-
expect(result).toEqual({
|
|
72
|
-
slug: 'Owner/Repo',
|
|
73
|
-
url: 'https://github.com/Owner/Repo',
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
it('parses GitLab SSH URL', () => {
|
|
77
|
-
;
|
|
78
|
-
execSync.mockReturnValue('git@gitlab.com:My-Org/My-Project.git\n');
|
|
79
|
-
const result = getGitRepository();
|
|
80
|
-
expect(result).toEqual({
|
|
81
|
-
slug: 'My-Org/My-Project',
|
|
82
|
-
url: 'git@gitlab.com:My-Org/My-Project.git',
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
it('returns null when git command fails', () => {
|
|
86
|
-
;
|
|
87
|
-
execSync.mockImplementation(() => {
|
|
88
|
-
throw new Error('no remote');
|
|
89
|
-
});
|
|
90
|
-
expect(getGitRepository()).toBeNull();
|
|
91
|
-
});
|
|
92
|
-
it('falls back to raw URL when regex does not match', () => {
|
|
93
|
-
;
|
|
94
|
-
execSync.mockReturnValue('some-weird-url\n');
|
|
95
|
-
const result = getGitRepository();
|
|
96
|
-
expect(result).toEqual({
|
|
97
|
-
slug: 'some-weird-url',
|
|
98
|
-
url: 'some-weird-url',
|
|
99
|
-
});
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
describe('getProjectName', () => {
|
|
103
|
-
it('returns name from package.json when available', () => {
|
|
104
|
-
;
|
|
105
|
-
fs.pathExistsSync.mockReturnValue(true);
|
|
106
|
-
fs.readJsonSync.mockReturnValue({ name: '@meltstudio/meltctl' });
|
|
107
|
-
expect(getProjectName()).toBe('@meltstudio/meltctl');
|
|
108
|
-
});
|
|
109
|
-
it('returns directory basename when package.json has no name', () => {
|
|
110
|
-
;
|
|
111
|
-
fs.pathExistsSync.mockReturnValue(true);
|
|
112
|
-
fs.readJsonSync.mockReturnValue({});
|
|
113
|
-
const result = getProjectName();
|
|
114
|
-
// Should be the basename of cwd
|
|
115
|
-
expect(typeof result).toBe('string');
|
|
116
|
-
expect(result.length).toBeGreaterThan(0);
|
|
117
|
-
});
|
|
118
|
-
it('returns directory basename when package.json does not exist', () => {
|
|
119
|
-
;
|
|
120
|
-
fs.pathExistsSync.mockReturnValue(false);
|
|
121
|
-
const result = getProjectName();
|
|
122
|
-
expect(typeof result).toBe('string');
|
|
123
|
-
expect(result.length).toBeGreaterThan(0);
|
|
124
|
-
});
|
|
125
|
-
it('returns directory basename when readJsonSync throws', () => {
|
|
126
|
-
;
|
|
127
|
-
fs.pathExistsSync.mockReturnValue(true);
|
|
128
|
-
fs.readJsonSync.mockImplementation(() => {
|
|
129
|
-
throw new Error('parse error');
|
|
130
|
-
});
|
|
131
|
-
const result = getProjectName();
|
|
132
|
-
expect(typeof result).toBe('string');
|
|
133
|
-
expect(result.length).toBeGreaterThan(0);
|
|
134
|
-
});
|
|
135
|
-
});
|
|
136
|
-
describe('extractTicketId', () => {
|
|
137
|
-
it('extracts ticket ID from branch name', () => {
|
|
138
|
-
expect(extractTicketId('feature/PROJ-123-add-login')).toBe('PROJ-123');
|
|
139
|
-
});
|
|
140
|
-
it('extracts ticket ID case-insensitively', () => {
|
|
141
|
-
expect(extractTicketId('fix/proj-456')).toBe('proj-456');
|
|
142
|
-
});
|
|
143
|
-
it('returns null when no ticket ID found', () => {
|
|
144
|
-
expect(extractTicketId('main')).toBeNull();
|
|
145
|
-
expect(extractTicketId('feature/add-login')).toBeNull();
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
describe('findMdFiles', () => {
|
|
149
|
-
it('returns empty array when directory does not exist', async () => {
|
|
150
|
-
;
|
|
151
|
-
fs.pathExists.mockResolvedValue(false);
|
|
152
|
-
const result = await findMdFiles('/nonexistent');
|
|
153
|
-
expect(result).toEqual([]);
|
|
154
|
-
});
|
|
155
|
-
it('finds and sorts .md files by mtime descending', async () => {
|
|
156
|
-
;
|
|
157
|
-
fs.pathExists.mockResolvedValue(true);
|
|
158
|
-
fs.readdir.mockResolvedValue([
|
|
159
|
-
{ name: 'old.md', isDirectory: () => false, isFile: () => true },
|
|
160
|
-
{ name: 'new.md', isDirectory: () => false, isFile: () => true },
|
|
161
|
-
{ name: 'not-md.txt', isDirectory: () => false, isFile: () => true },
|
|
162
|
-
]);
|
|
163
|
-
fs.stat
|
|
164
|
-
.mockResolvedValueOnce({ mtimeMs: 1000 }) // old.md
|
|
165
|
-
.mockResolvedValueOnce({ mtimeMs: 2000 }); // new.md
|
|
166
|
-
const result = await findMdFiles('/test-dir');
|
|
167
|
-
expect(result).toEqual(['/test-dir/new.md', '/test-dir/old.md']);
|
|
168
|
-
});
|
|
169
|
-
it('recurses into subdirectories', async () => {
|
|
170
|
-
;
|
|
171
|
-
fs.pathExists.mockResolvedValue(true);
|
|
172
|
-
fs.readdir
|
|
173
|
-
.mockResolvedValueOnce([
|
|
174
|
-
{ name: 'sub', isDirectory: () => true, isFile: () => false },
|
|
175
|
-
{ name: 'root.md', isDirectory: () => false, isFile: () => true },
|
|
176
|
-
])
|
|
177
|
-
.mockResolvedValueOnce([{ name: 'nested.md', isDirectory: () => false, isFile: () => true }]);
|
|
178
|
-
fs.stat
|
|
179
|
-
.mockResolvedValueOnce({ mtimeMs: 4000 }) // sub/nested.md
|
|
180
|
-
.mockResolvedValueOnce({ mtimeMs: 3000 }); // root.md
|
|
181
|
-
const result = await findMdFiles('/project');
|
|
182
|
-
expect(result).toEqual(['/project/sub/nested.md', '/project/root.md']);
|
|
183
|
-
});
|
|
184
|
-
});
|
|
@@ -1,7 +0,0 @@
|
|
|
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[];
|
|
@@ -1,55 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
vi.mock('child_process', () => ({
|
|
3
|
-
execSync: vi.fn(),
|
|
4
|
-
}));
|
|
5
|
-
import { execSync } from 'child_process';
|
|
6
|
-
import { detectPackageManager, getUpdateInstructions } from './package-manager.js';
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
vi.clearAllMocks();
|
|
9
|
-
});
|
|
10
|
-
describe('detectPackageManager', () => {
|
|
11
|
-
it('returns npm when npm list succeeds', () => {
|
|
12
|
-
vi.mocked(execSync).mockReturnValueOnce('');
|
|
13
|
-
const result = detectPackageManager();
|
|
14
|
-
expect(result).toEqual({
|
|
15
|
-
type: 'npm',
|
|
16
|
-
updateCommand: 'npm install -g @meltstudio/meltctl@latest',
|
|
17
|
-
});
|
|
18
|
-
expect(execSync).toHaveBeenCalledWith('npm list -g @meltstudio/meltctl', expect.objectContaining({ encoding: 'utf-8', stdio: 'pipe' }));
|
|
19
|
-
});
|
|
20
|
-
it('returns yarn when npm fails but yarn succeeds', () => {
|
|
21
|
-
vi.mocked(execSync)
|
|
22
|
-
.mockImplementationOnce(() => {
|
|
23
|
-
throw new Error('npm not found');
|
|
24
|
-
})
|
|
25
|
-
.mockReturnValueOnce('');
|
|
26
|
-
const result = detectPackageManager();
|
|
27
|
-
expect(result).toEqual({
|
|
28
|
-
type: 'yarn',
|
|
29
|
-
updateCommand: 'yarn global add @meltstudio/meltctl@latest',
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
it('returns unknown when both npm and yarn fail', () => {
|
|
33
|
-
vi.mocked(execSync)
|
|
34
|
-
.mockImplementationOnce(() => {
|
|
35
|
-
throw new Error('npm not found');
|
|
36
|
-
})
|
|
37
|
-
.mockImplementationOnce(() => {
|
|
38
|
-
throw new Error('yarn not found');
|
|
39
|
-
});
|
|
40
|
-
const result = detectPackageManager();
|
|
41
|
-
expect(result).toEqual({
|
|
42
|
-
type: 'unknown',
|
|
43
|
-
updateCommand: 'npm install -g @meltstudio/meltctl@latest',
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
});
|
|
47
|
-
describe('getUpdateInstructions', () => {
|
|
48
|
-
it('returns npm-first instructions when detected via npm', () => {
|
|
49
|
-
vi.mocked(execSync).mockReturnValueOnce('');
|
|
50
|
-
const instructions = getUpdateInstructions();
|
|
51
|
-
expect(instructions[0]).toBe('npm install -g @meltstudio/meltctl@latest');
|
|
52
|
-
expect(instructions.some(l => l.includes('yarn'))).toBe(true);
|
|
53
|
-
});
|
|
54
|
-
it('returns yarn-first instructions when detected via yarn', () => {
|
|
55
|
-
vi.mocked(execSync)
|
|
56
|
-
.mockImplementationOnce(() => {
|
|
57
|
-
throw new Error('npm not found');
|
|
58
|
-
})
|
|
59
|
-
.mockReturnValueOnce('');
|
|
60
|
-
const instructions = getUpdateInstructions();
|
|
61
|
-
expect(instructions[0]).toBe('yarn global add @meltstudio/meltctl@latest');
|
|
62
|
-
expect(instructions.some(l => l.includes('npm'))).toBe(true);
|
|
63
|
-
});
|
|
64
|
-
it('returns npm default instructions when detection fails', () => {
|
|
65
|
-
vi.mocked(execSync)
|
|
66
|
-
.mockImplementationOnce(() => {
|
|
67
|
-
throw new Error('npm not found');
|
|
68
|
-
})
|
|
69
|
-
.mockImplementationOnce(() => {
|
|
70
|
-
throw new Error('yarn not found');
|
|
71
|
-
});
|
|
72
|
-
const instructions = getUpdateInstructions();
|
|
73
|
-
expect(instructions[0]).toBe('npm install -g @meltstudio/meltctl@latest');
|
|
74
|
-
expect(instructions.some(l => l.includes('yarn'))).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
});
|
package/dist/utils/templates.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
const mockClient = vi.hoisted(() => ({
|
|
3
|
-
templates: {
|
|
4
|
-
fetch: vi.fn(),
|
|
5
|
-
},
|
|
6
|
-
}));
|
|
7
|
-
vi.mock('./api.js', () => ({
|
|
8
|
-
getClient: vi.fn().mockResolvedValue(mockClient),
|
|
9
|
-
}));
|
|
10
|
-
import { fetchTemplates } from './templates.js';
|
|
11
|
-
beforeEach(() => {
|
|
12
|
-
vi.clearAllMocks();
|
|
13
|
-
});
|
|
14
|
-
describe('fetchTemplates', () => {
|
|
15
|
-
it('returns template files on success', async () => {
|
|
16
|
-
const mockFiles = {
|
|
17
|
-
'AGENTS.md': '# Agents content',
|
|
18
|
-
'.claude/skills/setup.md': '# Setup skill',
|
|
19
|
-
};
|
|
20
|
-
mockClient.templates.fetch.mockResolvedValue(mockFiles);
|
|
21
|
-
const result = await fetchTemplates();
|
|
22
|
-
expect(mockClient.templates.fetch).toHaveBeenCalled();
|
|
23
|
-
expect(result).toEqual(mockFiles);
|
|
24
|
-
});
|
|
25
|
-
it('throws error when API returns failure', async () => {
|
|
26
|
-
mockClient.templates.fetch.mockRejectedValue(new Error('Failed to fetch templates: Unauthorized'));
|
|
27
|
-
await expect(fetchTemplates()).rejects.toThrow('Failed to fetch templates: Unauthorized');
|
|
28
|
-
});
|
|
29
|
-
it('throws error when API returns 500', async () => {
|
|
30
|
-
mockClient.templates.fetch.mockRejectedValue(new Error('Failed to fetch templates: Internal Server Error'));
|
|
31
|
-
await expect(fetchTemplates()).rejects.toThrow('Failed to fetch templates: Internal Server Error');
|
|
32
|
-
});
|
|
33
|
-
it('calls client.templates.fetch', async () => {
|
|
34
|
-
mockClient.templates.fetch.mockResolvedValue({});
|
|
35
|
-
await fetchTemplates();
|
|
36
|
-
expect(mockClient.templates.fetch).toHaveBeenCalledTimes(1);
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,7 +0,0 @@
|
|
|
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 type UpdateSeverity = 'none' | 'patch' | 'minor' | 'major';
|
|
6
|
-
export declare function getUpdateSeverity(current: string, latest: string): UpdateSeverity;
|
|
7
|
-
export declare function checkAndEnforceUpdate(): Promise<void>;
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { confirm } from '@inquirer/prompts';
|
|
3
|
-
import { execSync } from 'child_process';
|
|
4
|
-
import fs from 'fs-extra';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import { fileURLToPath } from 'url';
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
export async function getCurrentCliVersion() {
|
|
9
|
-
const packagePath = path.join(__dirname, '../../package.json');
|
|
10
|
-
const packageJson = await fs.readJson(packagePath);
|
|
11
|
-
return packageJson.version;
|
|
12
|
-
}
|
|
13
|
-
export async function getLatestCliVersion() {
|
|
14
|
-
try {
|
|
15
|
-
const result = execSync('npm view @meltstudio/meltctl version --json', {
|
|
16
|
-
encoding: 'utf-8',
|
|
17
|
-
stdio: 'pipe',
|
|
18
|
-
timeout: 5000, // 5 second timeout
|
|
19
|
-
});
|
|
20
|
-
return JSON.parse(result.trim());
|
|
21
|
-
}
|
|
22
|
-
catch {
|
|
23
|
-
// Network error or timeout - return null to indicate check failed
|
|
24
|
-
return null;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export function compareVersions(current, latest) {
|
|
28
|
-
// Parse semver strings, handling pre-release tags
|
|
29
|
-
const parseVersion = (version) => {
|
|
30
|
-
const [base, prerelease] = version.split('-');
|
|
31
|
-
const parts = (base || '').split('.').map(Number);
|
|
32
|
-
return {
|
|
33
|
-
major: parts[0] || 0,
|
|
34
|
-
minor: parts[1] || 0,
|
|
35
|
-
patch: parts[2] || 0,
|
|
36
|
-
prerelease: prerelease || null,
|
|
37
|
-
};
|
|
38
|
-
};
|
|
39
|
-
const currentVer = parseVersion(current);
|
|
40
|
-
const latestVer = parseVersion(latest);
|
|
41
|
-
// Compare major.minor.patch first
|
|
42
|
-
if (latestVer.major !== currentVer.major) {
|
|
43
|
-
return latestVer.major > currentVer.major;
|
|
44
|
-
}
|
|
45
|
-
if (latestVer.minor !== currentVer.minor) {
|
|
46
|
-
return latestVer.minor > currentVer.minor;
|
|
47
|
-
}
|
|
48
|
-
if (latestVer.patch !== currentVer.patch) {
|
|
49
|
-
return latestVer.patch > currentVer.patch;
|
|
50
|
-
}
|
|
51
|
-
// If base versions are equal, handle pre-release comparison
|
|
52
|
-
// No pre-release (stable) > pre-release
|
|
53
|
-
if (!latestVer.prerelease && currentVer.prerelease)
|
|
54
|
-
return true;
|
|
55
|
-
if (latestVer.prerelease && !currentVer.prerelease)
|
|
56
|
-
return false;
|
|
57
|
-
// Both have pre-release or both are stable
|
|
58
|
-
if (latestVer.prerelease && currentVer.prerelease) {
|
|
59
|
-
return latestVer.prerelease > currentVer.prerelease;
|
|
60
|
-
}
|
|
61
|
-
return false; // Versions are equal
|
|
62
|
-
}
|
|
63
|
-
export function isCI() {
|
|
64
|
-
// Check common CI environment variables
|
|
65
|
-
return !!(process.env.CI || // Generic CI flag
|
|
66
|
-
process.env.GITHUB_ACTIONS || // GitHub Actions
|
|
67
|
-
process.env.GITLAB_CI || // GitLab CI
|
|
68
|
-
process.env.CIRCLECI || // CircleCI
|
|
69
|
-
process.env.TRAVIS || // Travis CI
|
|
70
|
-
process.env.JENKINS_URL || // Jenkins
|
|
71
|
-
process.env.BUILDKITE || // Buildkite
|
|
72
|
-
process.env.DRONE || // Drone
|
|
73
|
-
process.env.MELTCTL_SKIP_UPDATE_CHECK // Custom override
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
export function getUpdateSeverity(current, latest) {
|
|
77
|
-
const parseCurrent = current.split('-')[0].split('.').map(Number);
|
|
78
|
-
const parseLatest = latest.split('-')[0].split('.').map(Number);
|
|
79
|
-
const [curMajor = 0, curMinor = 0, curPatch = 0] = parseCurrent;
|
|
80
|
-
const [latMajor = 0, latMinor = 0, latPatch = 0] = parseLatest;
|
|
81
|
-
if (latMajor > curMajor)
|
|
82
|
-
return 'major';
|
|
83
|
-
if (latMajor === curMajor && latMinor > curMinor)
|
|
84
|
-
return 'minor';
|
|
85
|
-
if (latMajor === curMajor && latMinor === curMinor && latPatch > curPatch)
|
|
86
|
-
return 'patch';
|
|
87
|
-
return 'none';
|
|
88
|
-
}
|
|
89
|
-
export async function checkAndEnforceUpdate() {
|
|
90
|
-
// Skip update check in CI environments
|
|
91
|
-
if (isCI()) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
try {
|
|
95
|
-
const currentVersion = await getCurrentCliVersion();
|
|
96
|
-
const latestVersion = await getLatestCliVersion();
|
|
97
|
-
// If we can't fetch latest version (network error), allow continuing
|
|
98
|
-
if (!latestVersion) {
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const severity = getUpdateSeverity(currentVersion, latestVersion);
|
|
102
|
-
if (severity === 'none') {
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
105
|
-
if (severity === 'patch') {
|
|
106
|
-
// Patch updates: warn but allow continuing
|
|
107
|
-
console.log();
|
|
108
|
-
console.log(chalk.yellow(` Update available: ${currentVersion} → ${latestVersion} (run: meltctl update)`));
|
|
109
|
-
console.log();
|
|
110
|
-
return;
|
|
111
|
-
}
|
|
112
|
-
// Minor/major updates: offer to update, block if declined
|
|
113
|
-
console.log();
|
|
114
|
-
console.log(chalk.yellow(` Update required: ${currentVersion} → ${latestVersion}`));
|
|
115
|
-
console.log();
|
|
116
|
-
const shouldUpdate = await confirm({
|
|
117
|
-
message: 'Update now?',
|
|
118
|
-
default: true,
|
|
119
|
-
});
|
|
120
|
-
if (shouldUpdate) {
|
|
121
|
-
const { updateCommand } = await import('../commands/update.js');
|
|
122
|
-
await updateCommand();
|
|
123
|
-
// Re-run the original command after update
|
|
124
|
-
console.log();
|
|
125
|
-
console.log(chalk.dim(' Please re-run your command.'));
|
|
126
|
-
console.log();
|
|
127
|
-
process.exit(0);
|
|
128
|
-
}
|
|
129
|
-
console.log();
|
|
130
|
-
console.log(chalk.gray('To skip this check (CI/CD), set MELTCTL_SKIP_UPDATE_CHECK=1'));
|
|
131
|
-
console.log();
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
// If any error occurs during version check, allow continuing
|
|
136
|
-
// This ensures the CLI remains usable even if version check fails
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|