@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meltstudio/meltctl",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.39.0",
|
|
4
4
|
"description": "AI-first development tools for teams - set up AGENTS.md, Claude Code, Cursor, and OpenCode standards",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"directory": "packages/cli"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
|
-
"build": "
|
|
24
|
+
"build": "tsup",
|
|
25
25
|
"dev": "tsx src/index.ts",
|
|
26
26
|
"start": "node dist/index.js",
|
|
27
27
|
"clean": "rm -rf dist",
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
"author": "Melt Studio",
|
|
48
48
|
"license": "UNLICENSED",
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@meltstudio/meltctl-sdk": "*",
|
|
51
50
|
"@commander-js/extra-typings": "^12.1.0",
|
|
52
51
|
"@inquirer/prompts": "^8.2.1",
|
|
53
52
|
"chalk": "^5.4.1",
|
|
@@ -56,6 +55,7 @@
|
|
|
56
55
|
"gradient-string": "^3.0.0"
|
|
57
56
|
},
|
|
58
57
|
"devDependencies": {
|
|
58
|
+
"@meltstudio/meltctl-sdk": "*",
|
|
59
59
|
"@types/fs-extra": "^11.0.4",
|
|
60
60
|
"@types/node": "^24.0.10",
|
|
61
61
|
"@typescript-eslint/eslint-plugin": "^8.19.0",
|
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
"eslint-config-prettier": "^10.1.8",
|
|
65
65
|
"eslint-plugin-prettier": "^5.2.1",
|
|
66
66
|
"prettier": "^3.4.2",
|
|
67
|
+
"tsup": "^8.5.1",
|
|
67
68
|
"tsx": "^4.19.5",
|
|
68
69
|
"typescript": "^5.7.3"
|
|
69
70
|
},
|
package/dist/commands/audit.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export declare function auditSubmitCommand(file?: string): Promise<void>;
|
|
2
|
-
export declare function auditListCommand(options: {
|
|
3
|
-
type?: string;
|
|
4
|
-
repository?: string;
|
|
5
|
-
latest?: boolean;
|
|
6
|
-
limit?: string;
|
|
7
|
-
}): Promise<void>;
|
|
8
|
-
export declare function auditViewCommand(id: string, options: {
|
|
9
|
-
output?: string;
|
|
10
|
-
}): Promise<void>;
|
package/dist/commands/audit.js
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import fs from 'fs-extra';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { getClient } from '../utils/api.js';
|
|
5
|
-
import { getGitBranch, getGitCommit, getGitRepository, getProjectName, findMdFiles, } from '../utils/git.js';
|
|
6
|
-
function detectAuditType(filename) {
|
|
7
|
-
const lower = filename.toLowerCase();
|
|
8
|
-
if (lower.includes('security-audit'))
|
|
9
|
-
return 'security-audit';
|
|
10
|
-
if (lower.includes('ux-audit'))
|
|
11
|
-
return 'ux-audit';
|
|
12
|
-
return 'audit';
|
|
13
|
-
}
|
|
14
|
-
async function autoDetectAuditFile() {
|
|
15
|
-
const cwd = process.cwd();
|
|
16
|
-
const auditsDir = path.join(cwd, '.audits');
|
|
17
|
-
const auditFiles = await findMdFiles(auditsDir);
|
|
18
|
-
if (auditFiles.length > 0) {
|
|
19
|
-
return auditFiles[0] ?? null;
|
|
20
|
-
}
|
|
21
|
-
const candidates = ['AUDIT.md', 'UX-AUDIT.md', 'SECURITY-AUDIT.md'];
|
|
22
|
-
for (const name of candidates) {
|
|
23
|
-
const filePath = path.join(cwd, name);
|
|
24
|
-
if (await fs.pathExists(filePath)) {
|
|
25
|
-
return filePath;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
export async function auditSubmitCommand(file) {
|
|
31
|
-
const client = await getClient();
|
|
32
|
-
let filePath;
|
|
33
|
-
if (file) {
|
|
34
|
-
filePath = path.resolve(file);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
const detected = await autoDetectAuditFile();
|
|
38
|
-
if (!detected) {
|
|
39
|
-
console.error(chalk.red('No audit file found. Provide a file path or create an audit in the .audits/ directory.'));
|
|
40
|
-
process.exit(1);
|
|
41
|
-
}
|
|
42
|
-
filePath = detected;
|
|
43
|
-
console.log(chalk.dim(`Auto-detected audit file: ${path.relative(process.cwd(), filePath)}`));
|
|
44
|
-
}
|
|
45
|
-
if (!(await fs.pathExists(filePath))) {
|
|
46
|
-
console.error(chalk.red(`File not found: ${filePath}`));
|
|
47
|
-
process.exit(1);
|
|
48
|
-
}
|
|
49
|
-
const content = await fs.readFile(filePath, 'utf-8');
|
|
50
|
-
const filename = path.basename(filePath);
|
|
51
|
-
const auditType = detectAuditType(filename);
|
|
52
|
-
const project = getProjectName();
|
|
53
|
-
const branch = getGitBranch();
|
|
54
|
-
const commit = getGitCommit();
|
|
55
|
-
const repo = getGitRepository();
|
|
56
|
-
try {
|
|
57
|
-
const result = await client.audits.submit({
|
|
58
|
-
type: auditType,
|
|
59
|
-
project,
|
|
60
|
-
repository: repo?.slug ?? null,
|
|
61
|
-
repositoryUrl: repo?.url ?? null,
|
|
62
|
-
branch,
|
|
63
|
-
commit,
|
|
64
|
-
content,
|
|
65
|
-
metadata: { filename },
|
|
66
|
-
});
|
|
67
|
-
console.log(chalk.green(`\n ✓ Audit submitted! ID: ${result.id}\n`));
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
console.error(chalk.red(`\nFailed to submit audit: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
71
|
-
process.exit(1);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
export async function auditListCommand(options) {
|
|
75
|
-
const client = await getClient();
|
|
76
|
-
try {
|
|
77
|
-
const body = await client.audits.list({
|
|
78
|
-
type: options.type,
|
|
79
|
-
repository: options.repository,
|
|
80
|
-
latest: options.latest,
|
|
81
|
-
limit: options.limit ? parseInt(options.limit, 10) : undefined,
|
|
82
|
-
});
|
|
83
|
-
if (body.audits.length === 0) {
|
|
84
|
-
console.log(chalk.dim('\n No audits found.\n'));
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const typeLabels = {
|
|
88
|
-
audit: 'Tech Audit',
|
|
89
|
-
'ux-audit': 'UX Audit',
|
|
90
|
-
'security-audit': 'Security',
|
|
91
|
-
};
|
|
92
|
-
if (options.latest) {
|
|
93
|
-
console.log(chalk.bold(`\n Latest Audits (${body.count}):\n`));
|
|
94
|
-
console.log(chalk.dim(` ${'ID'.padEnd(10)} ${'TYPE'.padEnd(12)} ${'REPOSITORY'.padEnd(40)} ${'AGE'.padEnd(10)} ${'AUTHOR'.padEnd(30)} DATE`));
|
|
95
|
-
console.log();
|
|
96
|
-
for (const r of body.audits) {
|
|
97
|
-
printLatestAuditRow(r, typeLabels);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
console.log(chalk.bold(`\n Audits (${body.count}):\n`));
|
|
102
|
-
const hdr = ` ${'ID'.padEnd(10)} ${'TYPE'.padEnd(12)} ${'REPOSITORY'.padEnd(40)} ${'AUTHOR'.padEnd(30)} DATE`;
|
|
103
|
-
console.log(chalk.dim(hdr));
|
|
104
|
-
console.log();
|
|
105
|
-
for (const r of body.audits) {
|
|
106
|
-
printAuditRow(r, typeLabels);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
console.log();
|
|
110
|
-
}
|
|
111
|
-
catch (error) {
|
|
112
|
-
console.error(chalk.red(`Failed to list audits: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
function printLatestAuditRow(r, typeLabels) {
|
|
117
|
-
const createdAt = new Date(r.created_at ?? r.createdAt);
|
|
118
|
-
const daysAgo = Math.floor((Date.now() - createdAt.getTime()) / (1000 * 60 * 60 * 24));
|
|
119
|
-
const date = createdAt.toLocaleDateString('en-US', {
|
|
120
|
-
month: 'short',
|
|
121
|
-
day: 'numeric',
|
|
122
|
-
year: 'numeric',
|
|
123
|
-
});
|
|
124
|
-
const repo = r.repository ?? r.project;
|
|
125
|
-
const label = typeLabels[r.type] ?? r.type;
|
|
126
|
-
const typeColor = r.type === 'ux-audit' ? chalk.yellow : r.type === 'security-audit' ? chalk.red : chalk.magenta;
|
|
127
|
-
const ageText = daysAgo === 0 ? 'today' : `${daysAgo}d ago`;
|
|
128
|
-
const isSecurityAudit = r.type === 'security-audit';
|
|
129
|
-
const ageColor = isSecurityAudit
|
|
130
|
-
? daysAgo <= 30
|
|
131
|
-
? chalk.green
|
|
132
|
-
: daysAgo <= 90
|
|
133
|
-
? chalk.yellow
|
|
134
|
-
: chalk.red
|
|
135
|
-
: daysAgo <= 7
|
|
136
|
-
? chalk.green
|
|
137
|
-
: daysAgo <= 30
|
|
138
|
-
? chalk.yellow
|
|
139
|
-
: chalk.red;
|
|
140
|
-
console.log(` ${chalk.dim(r.id.slice(0, 8).padEnd(10))} ${typeColor(label.padEnd(12))} ${chalk.white(repo.padEnd(40))} ${ageColor(ageText.padEnd(10))} ${chalk.dim(r.author.padEnd(30))} ${chalk.dim(date)}`);
|
|
141
|
-
}
|
|
142
|
-
function printAuditRow(r, typeLabels) {
|
|
143
|
-
const date = new Date(r.createdAt).toLocaleDateString('en-US', {
|
|
144
|
-
month: 'short',
|
|
145
|
-
day: 'numeric',
|
|
146
|
-
year: 'numeric',
|
|
147
|
-
hour: '2-digit',
|
|
148
|
-
minute: '2-digit',
|
|
149
|
-
});
|
|
150
|
-
const repo = r.repository ?? r.project;
|
|
151
|
-
const label = typeLabels[r.type] ?? r.type;
|
|
152
|
-
const typeColor = r.type === 'ux-audit' ? chalk.yellow : r.type === 'security-audit' ? chalk.red : chalk.magenta;
|
|
153
|
-
console.log(` ${chalk.dim(r.id.slice(0, 8).padEnd(10))} ${typeColor(label.padEnd(12))} ${chalk.white(repo.padEnd(40))} ${chalk.dim(r.author.padEnd(30))} ${chalk.dim(date)}`);
|
|
154
|
-
if (r.branch && r.branch !== 'main') {
|
|
155
|
-
console.log(` ${' '.padEnd(10)} ${' '.padEnd(12)} ${chalk.dim(`branch: ${r.branch} commit: ${r.commit ?? 'N/A'}`)}`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
export async function auditViewCommand(id, options) {
|
|
159
|
-
const client = await getClient();
|
|
160
|
-
try {
|
|
161
|
-
const audit = await client.audits.get(id);
|
|
162
|
-
if (options.output) {
|
|
163
|
-
const outputPath = path.resolve(options.output);
|
|
164
|
-
await fs.ensureDir(path.dirname(outputPath));
|
|
165
|
-
await fs.writeFile(outputPath, audit.content, 'utf-8');
|
|
166
|
-
console.log(chalk.green(`\n ✓ Audit saved to ${path.relative(process.cwd(), outputPath)}\n`));
|
|
167
|
-
}
|
|
168
|
-
else {
|
|
169
|
-
const typeLabels = {
|
|
170
|
-
audit: 'Tech Audit',
|
|
171
|
-
'ux-audit': 'UX Audit',
|
|
172
|
-
'security-audit': 'Security Audit',
|
|
173
|
-
};
|
|
174
|
-
const label = typeLabels[audit.type] ?? audit.type;
|
|
175
|
-
const date = new Date(audit.createdAt).toLocaleDateString('en-US', {
|
|
176
|
-
month: 'short',
|
|
177
|
-
day: 'numeric',
|
|
178
|
-
year: 'numeric',
|
|
179
|
-
});
|
|
180
|
-
const repo = audit.repository ?? audit.project;
|
|
181
|
-
console.log();
|
|
182
|
-
console.log(chalk.dim(` ${label} · ${repo} · ${audit.author} · ${date}`));
|
|
183
|
-
console.log();
|
|
184
|
-
console.log(audit.content);
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
console.error(chalk.red(`Failed to fetch audit: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
-
vi.mock('fs-extra', () => ({
|
|
3
|
-
default: {
|
|
4
|
-
pathExists: vi.fn(),
|
|
5
|
-
readFile: vi.fn(),
|
|
6
|
-
ensureDir: vi.fn(),
|
|
7
|
-
writeFile: vi.fn(),
|
|
8
|
-
},
|
|
9
|
-
}));
|
|
10
|
-
const mockClient = vi.hoisted(() => ({
|
|
11
|
-
audits: {
|
|
12
|
-
submit: vi.fn(),
|
|
13
|
-
list: vi.fn(),
|
|
14
|
-
get: vi.fn(),
|
|
15
|
-
},
|
|
16
|
-
}));
|
|
17
|
-
vi.mock('../utils/api.js', () => ({
|
|
18
|
-
getClient: vi.fn().mockResolvedValue(mockClient),
|
|
19
|
-
}));
|
|
20
|
-
vi.mock('../utils/git.js', () => ({
|
|
21
|
-
getGitBranch: vi.fn(),
|
|
22
|
-
getGitCommit: vi.fn(),
|
|
23
|
-
getGitRepository: vi.fn(),
|
|
24
|
-
getProjectName: vi.fn(),
|
|
25
|
-
findMdFiles: vi.fn(),
|
|
26
|
-
}));
|
|
27
|
-
import fs from 'fs-extra';
|
|
28
|
-
import { getGitBranch, getGitCommit, getGitRepository, getProjectName, findMdFiles, } from '../utils/git.js';
|
|
29
|
-
import { auditSubmitCommand, auditListCommand, auditViewCommand } from './audit.js';
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
vi.clearAllMocks();
|
|
32
|
-
vi.spyOn(console, 'log').mockImplementation(() => { });
|
|
33
|
-
vi.spyOn(console, 'error').mockImplementation(() => { });
|
|
34
|
-
vi.spyOn(process, 'exit').mockImplementation((code) => {
|
|
35
|
-
throw new Error(`process.exit(${code})`);
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
describe('auditSubmitCommand', () => {
|
|
39
|
-
function setupGitMocks() {
|
|
40
|
-
;
|
|
41
|
-
getGitBranch.mockReturnValue('main');
|
|
42
|
-
getGitCommit.mockReturnValue('abc1234');
|
|
43
|
-
getGitRepository.mockReturnValue({
|
|
44
|
-
slug: 'Org/Repo',
|
|
45
|
-
url: 'https://github.com/Org/Repo.git',
|
|
46
|
-
});
|
|
47
|
-
getProjectName.mockReturnValue('test-project');
|
|
48
|
-
}
|
|
49
|
-
it('submits audit with type "security-audit" when filename contains security-audit', async () => {
|
|
50
|
-
setupGitMocks();
|
|
51
|
-
fs.pathExists.mockResolvedValue(true);
|
|
52
|
-
fs.readFile.mockResolvedValue('# Security Audit\nFindings here.');
|
|
53
|
-
mockClient.audits.submit.mockResolvedValue({
|
|
54
|
-
id: 'audit-123',
|
|
55
|
-
createdAt: '2026-03-26T00:00:00Z',
|
|
56
|
-
});
|
|
57
|
-
await auditSubmitCommand('2026-03-26-security-audit.md');
|
|
58
|
-
expect(mockClient.audits.submit).toHaveBeenCalledWith(expect.objectContaining({
|
|
59
|
-
type: 'security-audit',
|
|
60
|
-
}));
|
|
61
|
-
});
|
|
62
|
-
it('submits audit with type "ux-audit" when filename contains ux-audit', async () => {
|
|
63
|
-
setupGitMocks();
|
|
64
|
-
fs.pathExists.mockResolvedValue(true);
|
|
65
|
-
fs.readFile.mockResolvedValue('# UX Audit');
|
|
66
|
-
mockClient.audits.submit.mockResolvedValue({
|
|
67
|
-
id: 'audit-456',
|
|
68
|
-
createdAt: '2026-03-26T00:00:00Z',
|
|
69
|
-
});
|
|
70
|
-
await auditSubmitCommand('UX-AUDIT.md');
|
|
71
|
-
expect(mockClient.audits.submit).toHaveBeenCalledWith(expect.objectContaining({
|
|
72
|
-
type: 'ux-audit',
|
|
73
|
-
}));
|
|
74
|
-
});
|
|
75
|
-
it('submits audit with type "audit" for generic audit filenames', async () => {
|
|
76
|
-
setupGitMocks();
|
|
77
|
-
fs.pathExists.mockResolvedValue(true);
|
|
78
|
-
fs.readFile.mockResolvedValue('# Audit');
|
|
79
|
-
mockClient.audits.submit.mockResolvedValue({
|
|
80
|
-
id: 'audit-789',
|
|
81
|
-
createdAt: '2026-03-26T00:00:00Z',
|
|
82
|
-
});
|
|
83
|
-
await auditSubmitCommand('AUDIT.md');
|
|
84
|
-
expect(mockClient.audits.submit).toHaveBeenCalledWith(expect.objectContaining({
|
|
85
|
-
type: 'audit',
|
|
86
|
-
}));
|
|
87
|
-
});
|
|
88
|
-
it('submits audit with type "audit" for random filenames', async () => {
|
|
89
|
-
setupGitMocks();
|
|
90
|
-
fs.pathExists.mockResolvedValue(true);
|
|
91
|
-
fs.readFile.mockResolvedValue('# Random');
|
|
92
|
-
mockClient.audits.submit.mockResolvedValue({
|
|
93
|
-
id: 'audit-000',
|
|
94
|
-
createdAt: '2026-03-26T00:00:00Z',
|
|
95
|
-
});
|
|
96
|
-
await auditSubmitCommand('random-file.md');
|
|
97
|
-
expect(mockClient.audits.submit).toHaveBeenCalledWith(expect.objectContaining({
|
|
98
|
-
type: 'audit',
|
|
99
|
-
}));
|
|
100
|
-
});
|
|
101
|
-
it('exits with error when file not found', async () => {
|
|
102
|
-
setupGitMocks();
|
|
103
|
-
fs.pathExists.mockResolvedValue(false);
|
|
104
|
-
await expect(auditSubmitCommand('nonexistent.md')).rejects.toThrow('process.exit(1)');
|
|
105
|
-
expect(console.error).toHaveBeenCalled();
|
|
106
|
-
});
|
|
107
|
-
it('sends correct payload fields', async () => {
|
|
108
|
-
setupGitMocks();
|
|
109
|
-
vi.mocked(fs.pathExists).mockResolvedValue(true);
|
|
110
|
-
vi.mocked(fs.readFile).mockResolvedValue('audit content here');
|
|
111
|
-
mockClient.audits.submit.mockResolvedValue({
|
|
112
|
-
id: 'audit-100',
|
|
113
|
-
createdAt: '2026-03-26T00:00:00Z',
|
|
114
|
-
});
|
|
115
|
-
await auditSubmitCommand('AUDIT.md');
|
|
116
|
-
expect(mockClient.audits.submit).toHaveBeenCalledWith({
|
|
117
|
-
type: 'audit',
|
|
118
|
-
project: 'test-project',
|
|
119
|
-
repository: 'Org/Repo',
|
|
120
|
-
repositoryUrl: 'https://github.com/Org/Repo.git',
|
|
121
|
-
branch: 'main',
|
|
122
|
-
commit: 'abc1234',
|
|
123
|
-
content: 'audit content here',
|
|
124
|
-
metadata: { filename: 'AUDIT.md' },
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
it('exits with error when API returns failure', async () => {
|
|
128
|
-
setupGitMocks();
|
|
129
|
-
fs.pathExists.mockResolvedValue(true);
|
|
130
|
-
fs.readFile.mockResolvedValue('content');
|
|
131
|
-
mockClient.audits.submit.mockRejectedValue(new Error('Invalid content'));
|
|
132
|
-
await expect(auditSubmitCommand('AUDIT.md')).rejects.toThrow('process.exit(1)');
|
|
133
|
-
});
|
|
134
|
-
it('auto-detects audit file from .audits/ directory when no file provided', async () => {
|
|
135
|
-
setupGitMocks();
|
|
136
|
-
findMdFiles.mockResolvedValue(['/project/.audits/2026-03-26-security-audit.md']);
|
|
137
|
-
fs.pathExists.mockResolvedValue(true);
|
|
138
|
-
fs.readFile.mockResolvedValue('auto-detected content');
|
|
139
|
-
mockClient.audits.submit.mockResolvedValue({
|
|
140
|
-
id: 'audit-auto',
|
|
141
|
-
createdAt: '2026-03-26T00:00:00Z',
|
|
142
|
-
});
|
|
143
|
-
await auditSubmitCommand();
|
|
144
|
-
expect(mockClient.audits.submit).toHaveBeenCalledWith(expect.objectContaining({
|
|
145
|
-
type: 'security-audit',
|
|
146
|
-
}));
|
|
147
|
-
});
|
|
148
|
-
it('exits with error when no file provided and none auto-detected', async () => {
|
|
149
|
-
;
|
|
150
|
-
findMdFiles.mockResolvedValue([]);
|
|
151
|
-
fs.pathExists.mockResolvedValue(false);
|
|
152
|
-
await expect(auditSubmitCommand()).rejects.toThrow('process.exit(1)');
|
|
153
|
-
expect(console.error).toHaveBeenCalled();
|
|
154
|
-
});
|
|
155
|
-
});
|
|
156
|
-
describe('auditListCommand', () => {
|
|
157
|
-
it('calls API with correct query params', async () => {
|
|
158
|
-
mockClient.audits.list.mockResolvedValue({ audits: [], count: 0 });
|
|
159
|
-
await auditListCommand({ type: 'ux-audit', repository: 'Org/Repo', limit: '5' });
|
|
160
|
-
expect(mockClient.audits.list).toHaveBeenCalledWith({
|
|
161
|
-
type: 'ux-audit',
|
|
162
|
-
repository: 'Org/Repo',
|
|
163
|
-
latest: undefined,
|
|
164
|
-
limit: 5,
|
|
165
|
-
});
|
|
166
|
-
});
|
|
167
|
-
it('passes latest=true query param when option set', async () => {
|
|
168
|
-
mockClient.audits.list.mockResolvedValue({ audits: [], count: 0 });
|
|
169
|
-
await auditListCommand({ latest: true });
|
|
170
|
-
expect(mockClient.audits.list).toHaveBeenCalledWith(expect.objectContaining({
|
|
171
|
-
latest: true,
|
|
172
|
-
}));
|
|
173
|
-
});
|
|
174
|
-
it('exits with error on 403 response', async () => {
|
|
175
|
-
mockClient.audits.list.mockRejectedValue(new Error('Access denied. Only Team Managers can list audits.'));
|
|
176
|
-
await expect(auditListCommand({})).rejects.toThrow('process.exit(1)');
|
|
177
|
-
expect(console.error).toHaveBeenCalled();
|
|
178
|
-
});
|
|
179
|
-
it('displays audit list when audits exist', async () => {
|
|
180
|
-
mockClient.audits.list.mockResolvedValue({
|
|
181
|
-
audits: [
|
|
182
|
-
{
|
|
183
|
-
id: '1',
|
|
184
|
-
type: 'audit',
|
|
185
|
-
project: 'my-project',
|
|
186
|
-
repository: 'Org/Repo',
|
|
187
|
-
author: 'dev@meltstudio.co',
|
|
188
|
-
branch: 'main',
|
|
189
|
-
commit: 'abc1234',
|
|
190
|
-
createdAt: '2026-03-25T10:00:00Z',
|
|
191
|
-
},
|
|
192
|
-
],
|
|
193
|
-
count: 1,
|
|
194
|
-
});
|
|
195
|
-
await auditListCommand({});
|
|
196
|
-
expect(console.log).toHaveBeenCalled();
|
|
197
|
-
});
|
|
198
|
-
it('displays age-colored output when latest option is set', async () => {
|
|
199
|
-
const now = Date.now();
|
|
200
|
-
const threeDaysAgo = new Date(now - 3 * 24 * 60 * 60 * 1000).toISOString();
|
|
201
|
-
const fifteenDaysAgo = new Date(now - 15 * 24 * 60 * 60 * 1000).toISOString();
|
|
202
|
-
const sixtyDaysAgo = new Date(now - 60 * 24 * 60 * 60 * 1000).toISOString();
|
|
203
|
-
mockClient.audits.list.mockResolvedValue({
|
|
204
|
-
audits: [
|
|
205
|
-
{
|
|
206
|
-
id: '1',
|
|
207
|
-
type: 'audit',
|
|
208
|
-
project: 'project-a',
|
|
209
|
-
repository: 'Org/RepoA',
|
|
210
|
-
author: 'dev1@meltstudio.co',
|
|
211
|
-
branch: 'main',
|
|
212
|
-
commit: 'aaa1111',
|
|
213
|
-
createdAt: threeDaysAgo,
|
|
214
|
-
created_at: threeDaysAgo,
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
id: '2',
|
|
218
|
-
type: 'ux-audit',
|
|
219
|
-
project: 'project-b',
|
|
220
|
-
repository: 'Org/RepoB',
|
|
221
|
-
author: 'dev2@meltstudio.co',
|
|
222
|
-
branch: 'main',
|
|
223
|
-
commit: 'bbb2222',
|
|
224
|
-
createdAt: fifteenDaysAgo,
|
|
225
|
-
created_at: fifteenDaysAgo,
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
id: '3',
|
|
229
|
-
type: 'security-audit',
|
|
230
|
-
project: 'project-c',
|
|
231
|
-
repository: 'Org/RepoC',
|
|
232
|
-
author: 'dev3@meltstudio.co',
|
|
233
|
-
branch: 'main',
|
|
234
|
-
commit: 'ccc3333',
|
|
235
|
-
createdAt: sixtyDaysAgo,
|
|
236
|
-
created_at: sixtyDaysAgo,
|
|
237
|
-
},
|
|
238
|
-
],
|
|
239
|
-
count: 3,
|
|
240
|
-
});
|
|
241
|
-
await auditListCommand({ latest: true });
|
|
242
|
-
const logCalls = console.log.mock.calls.map((c) => String(c[0]));
|
|
243
|
-
// Should display "Latest Audits" header
|
|
244
|
-
expect(logCalls.some((msg) => msg.includes('Latest Audits'))).toBe(true);
|
|
245
|
-
// Should display AGE column header
|
|
246
|
-
expect(logCalls.some((msg) => msg.includes('AGE'))).toBe(true);
|
|
247
|
-
});
|
|
248
|
-
it('exits with error when auditListCommand throws', async () => {
|
|
249
|
-
mockClient.audits.list.mockRejectedValue(new Error('Network error'));
|
|
250
|
-
await expect(auditListCommand({})).rejects.toThrow('process.exit(1)');
|
|
251
|
-
const errorCalls = console.error.mock.calls.map((c) => String(c[0]));
|
|
252
|
-
expect(errorCalls.some((msg) => msg.includes('Network error'))).toBe(true);
|
|
253
|
-
});
|
|
254
|
-
it('exits with error when findMdFiles throws (e.g. .audits/ unreadable)', async () => {
|
|
255
|
-
;
|
|
256
|
-
findMdFiles.mockRejectedValue(new Error('EACCES: permission denied'));
|
|
257
|
-
fs.pathExists.mockResolvedValue(false);
|
|
258
|
-
await expect(auditSubmitCommand()).rejects.toThrow();
|
|
259
|
-
});
|
|
260
|
-
it('shows "No audits found" when list is empty', async () => {
|
|
261
|
-
mockClient.audits.list.mockResolvedValue({ audits: [], count: 0 });
|
|
262
|
-
await auditListCommand({});
|
|
263
|
-
const errorCalls = console.log.mock.calls.map((c) => c[0]);
|
|
264
|
-
expect(errorCalls.some((msg) => typeof msg === 'string' && msg.includes('No audits found'))).toBe(true);
|
|
265
|
-
});
|
|
266
|
-
});
|
|
267
|
-
describe('auditViewCommand', () => {
|
|
268
|
-
const sampleAudit = {
|
|
269
|
-
id: 'audit-abc123',
|
|
270
|
-
type: 'ux-audit',
|
|
271
|
-
project: 'my-project',
|
|
272
|
-
repository: 'Org/Repo',
|
|
273
|
-
author: 'dev@meltstudio.co',
|
|
274
|
-
branch: 'main',
|
|
275
|
-
commit: 'abc1234',
|
|
276
|
-
content: '# UX Audit\n\nFindings here.',
|
|
277
|
-
createdAt: '2026-03-25T10:00:00Z',
|
|
278
|
-
};
|
|
279
|
-
it('exits with "Access denied" on 403 response', async () => {
|
|
280
|
-
mockClient.audits.get.mockRejectedValue(new Error('Access denied. Only Team Managers can list audits.'));
|
|
281
|
-
await expect(auditViewCommand('audit-abc123', {})).rejects.toThrow('process.exit(1)');
|
|
282
|
-
const errorCalls = console.error.mock.calls.map((c) => String(c[0]));
|
|
283
|
-
expect(errorCalls.some((msg) => msg.includes('Access denied'))).toBe(true);
|
|
284
|
-
});
|
|
285
|
-
it('exits with "Audit not found" on 404 response', async () => {
|
|
286
|
-
mockClient.audits.get.mockRejectedValue(new Error('Audit not found: nonexistent-id'));
|
|
287
|
-
await expect(auditViewCommand('nonexistent-id', {})).rejects.toThrow('process.exit(1)');
|
|
288
|
-
const errorCalls = console.error.mock.calls.map((c) => String(c[0]));
|
|
289
|
-
expect(errorCalls.some((msg) => msg.includes('Audit not found'))).toBe(true);
|
|
290
|
-
});
|
|
291
|
-
it('exits with error from body on non-ok response', async () => {
|
|
292
|
-
mockClient.audits.get.mockRejectedValue(new Error('Database unavailable'));
|
|
293
|
-
await expect(auditViewCommand('audit-abc123', {})).rejects.toThrow('process.exit(1)');
|
|
294
|
-
const errorCalls = console.error.mock.calls.map((c) => String(c[0]));
|
|
295
|
-
expect(errorCalls.some((msg) => msg.includes('Database unavailable'))).toBe(true);
|
|
296
|
-
});
|
|
297
|
-
it('prints audit content with header when no --output flag', async () => {
|
|
298
|
-
mockClient.audits.get.mockResolvedValue(sampleAudit);
|
|
299
|
-
await auditViewCommand('audit-abc123', {});
|
|
300
|
-
const logCalls = console.log.mock.calls.map((c) => String(c[0]));
|
|
301
|
-
// Header should contain type label, repo, author, and date
|
|
302
|
-
expect(logCalls.some((msg) => msg.includes('UX Audit'))).toBe(true);
|
|
303
|
-
expect(logCalls.some((msg) => msg.includes('Org/Repo'))).toBe(true);
|
|
304
|
-
expect(logCalls.some((msg) => msg.includes('dev@meltstudio.co'))).toBe(true);
|
|
305
|
-
// Content should be printed
|
|
306
|
-
expect(logCalls.some((msg) => msg.includes('# UX Audit'))).toBe(true);
|
|
307
|
-
});
|
|
308
|
-
it('writes content to file with --output flag', async () => {
|
|
309
|
-
mockClient.audits.get.mockResolvedValue(sampleAudit);
|
|
310
|
-
fs.ensureDir.mockResolvedValue(undefined);
|
|
311
|
-
fs.writeFile.mockResolvedValue(undefined);
|
|
312
|
-
await auditViewCommand('audit-abc123', { output: 'output/audit.md' });
|
|
313
|
-
expect(fs.ensureDir).toHaveBeenCalled();
|
|
314
|
-
expect(fs.writeFile).toHaveBeenCalledWith(expect.stringContaining('audit.md'), '# UX Audit\n\nFindings here.', 'utf-8');
|
|
315
|
-
const logCalls = console.log.mock.calls.map((c) => String(c[0]));
|
|
316
|
-
expect(logCalls.some((msg) => msg.includes('Audit saved to'))).toBe(true);
|
|
317
|
-
});
|
|
318
|
-
it('exits with error message on network error', async () => {
|
|
319
|
-
mockClient.audits.get.mockRejectedValue(new Error('Network error'));
|
|
320
|
-
await expect(auditViewCommand('audit-abc123', {})).rejects.toThrow('process.exit(1)');
|
|
321
|
-
const errorCalls = console.error.mock.calls.map((c) => String(c[0]));
|
|
322
|
-
expect(errorCalls.some((msg) => msg.includes('Network error'))).toBe(true);
|
|
323
|
-
});
|
|
324
|
-
});
|
package/dist/commands/coins.d.ts
DELETED
package/dist/commands/coins.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
|
-
import { isAuthenticated } from '../utils/auth.js';
|
|
3
|
-
import { getClient } from '../utils/api.js';
|
|
4
|
-
export async function coinsCommand(options) {
|
|
5
|
-
if (!(await isAuthenticated())) {
|
|
6
|
-
console.error(chalk.red('Not authenticated. Run `npx @meltstudio/meltctl@latest login` first.'));
|
|
7
|
-
process.exit(1);
|
|
8
|
-
}
|
|
9
|
-
if (options.leaderboard) {
|
|
10
|
-
await showLeaderboard();
|
|
11
|
-
}
|
|
12
|
-
else {
|
|
13
|
-
await showBalance();
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
async function showBalance() {
|
|
17
|
-
const client = await getClient();
|
|
18
|
-
try {
|
|
19
|
-
const data = await client.coins.getBalance();
|
|
20
|
-
console.log(chalk.bold.cyan('\n Your Coins (last 28 days)'));
|
|
21
|
-
console.log(` ${chalk.bold(String(data.coins))} coin${data.coins !== 1 ? 's' : ''} received\n`);
|
|
22
|
-
}
|
|
23
|
-
catch (error) {
|
|
24
|
-
console.error(chalk.red(`Failed to fetch coins: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
async function showLeaderboard() {
|
|
29
|
-
const client = await getClient();
|
|
30
|
-
try {
|
|
31
|
-
const entries = await client.coins.getLeaderboard();
|
|
32
|
-
if (entries.length === 0) {
|
|
33
|
-
console.log(chalk.yellow('\n No coins have been sent in the last 28 days.\n'));
|
|
34
|
-
return;
|
|
35
|
-
}
|
|
36
|
-
console.log(chalk.bold.cyan('\n Leaderboard (last 28 days)\n'));
|
|
37
|
-
const maxNameLen = Math.max(...entries.map(e => e.name.length), 4);
|
|
38
|
-
console.log(chalk.dim(` ${'#'.padEnd(4)} ${'Name'.padEnd(maxNameLen)} Coins`));
|
|
39
|
-
entries.forEach((entry, i) => {
|
|
40
|
-
const rank = String(i + 1).padEnd(4);
|
|
41
|
-
const name = entry.name.padEnd(maxNameLen);
|
|
42
|
-
const coins = String(entry.coins);
|
|
43
|
-
console.log(` ${rank} ${name} ${coins}`);
|
|
44
|
-
});
|
|
45
|
-
console.log();
|
|
46
|
-
}
|
|
47
|
-
catch (error) {
|
|
48
|
-
console.error(chalk.red(`Failed to fetch leaderboard: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
49
|
-
process.exit(1);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|