@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.
Files changed (81) hide show
  1. package/dist/index.js +2178 -210
  2. package/package.json +4 -3
  3. package/dist/commands/audit.d.ts +0 -10
  4. package/dist/commands/audit.js +0 -191
  5. package/dist/commands/audit.test.d.ts +0 -1
  6. package/dist/commands/audit.test.js +0 -324
  7. package/dist/commands/coins.d.ts +0 -5
  8. package/dist/commands/coins.js +0 -51
  9. package/dist/commands/coins.test.d.ts +0 -1
  10. package/dist/commands/coins.test.js +0 -113
  11. package/dist/commands/feedback.d.ts +0 -7
  12. package/dist/commands/feedback.js +0 -90
  13. package/dist/commands/feedback.test.d.ts +0 -1
  14. package/dist/commands/feedback.test.js +0 -177
  15. package/dist/commands/init.d.ts +0 -8
  16. package/dist/commands/init.js +0 -520
  17. package/dist/commands/init.test.d.ts +0 -1
  18. package/dist/commands/init.test.js +0 -478
  19. package/dist/commands/login.d.ts +0 -1
  20. package/dist/commands/login.js +0 -90
  21. package/dist/commands/login.test.d.ts +0 -1
  22. package/dist/commands/login.test.js +0 -194
  23. package/dist/commands/logout.d.ts +0 -1
  24. package/dist/commands/logout.js +0 -12
  25. package/dist/commands/logout.test.d.ts +0 -1
  26. package/dist/commands/logout.test.js +0 -59
  27. package/dist/commands/plan.d.ts +0 -6
  28. package/dist/commands/plan.js +0 -123
  29. package/dist/commands/plan.test.d.ts +0 -1
  30. package/dist/commands/plan.test.js +0 -246
  31. package/dist/commands/standup.d.ts +0 -7
  32. package/dist/commands/standup.js +0 -74
  33. package/dist/commands/standup.test.d.ts +0 -1
  34. package/dist/commands/standup.test.js +0 -218
  35. package/dist/commands/templates.d.ts +0 -1
  36. package/dist/commands/templates.js +0 -37
  37. package/dist/commands/templates.test.d.ts +0 -1
  38. package/dist/commands/templates.test.js +0 -89
  39. package/dist/commands/update.d.ts +0 -2
  40. package/dist/commands/update.js +0 -74
  41. package/dist/commands/update.test.d.ts +0 -1
  42. package/dist/commands/update.test.js +0 -93
  43. package/dist/commands/version.d.ts +0 -1
  44. package/dist/commands/version.js +0 -43
  45. package/dist/commands/version.test.d.ts +0 -1
  46. package/dist/commands/version.test.js +0 -86
  47. package/dist/index.d.ts +0 -2
  48. package/dist/utils/analytics.d.ts +0 -1
  49. package/dist/utils/analytics.js +0 -54
  50. package/dist/utils/analytics.test.d.ts +0 -1
  51. package/dist/utils/analytics.test.js +0 -91
  52. package/dist/utils/api.d.ts +0 -3
  53. package/dist/utils/api.js +0 -23
  54. package/dist/utils/api.test.d.ts +0 -1
  55. package/dist/utils/api.test.js +0 -76
  56. package/dist/utils/auth.d.ts +0 -12
  57. package/dist/utils/auth.js +0 -54
  58. package/dist/utils/auth.test.d.ts +0 -1
  59. package/dist/utils/auth.test.js +0 -165
  60. package/dist/utils/banner.d.ts +0 -1
  61. package/dist/utils/banner.js +0 -22
  62. package/dist/utils/banner.test.d.ts +0 -1
  63. package/dist/utils/banner.test.js +0 -34
  64. package/dist/utils/debug.d.ts +0 -1
  65. package/dist/utils/debug.js +0 -6
  66. package/dist/utils/git.d.ts +0 -9
  67. package/dist/utils/git.js +0 -76
  68. package/dist/utils/git.test.d.ts +0 -1
  69. package/dist/utils/git.test.js +0 -184
  70. package/dist/utils/package-manager.d.ts +0 -7
  71. package/dist/utils/package-manager.js +0 -55
  72. package/dist/utils/package-manager.test.d.ts +0 -1
  73. package/dist/utils/package-manager.test.js +0 -76
  74. package/dist/utils/templates.d.ts +0 -2
  75. package/dist/utils/templates.js +0 -5
  76. package/dist/utils/templates.test.d.ts +0 -1
  77. package/dist/utils/templates.test.js +0 -38
  78. package/dist/utils/version-check.d.ts +0 -7
  79. package/dist/utils/version-check.js +0 -139
  80. package/dist/utils/version-check.test.d.ts +0 -1
  81. package/dist/utils/version-check.test.js +0 -189
@@ -1,86 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- vi.mock('../utils/version-check.js', () => ({
3
- getCurrentCliVersion: vi.fn(),
4
- getLatestCliVersion: vi.fn(),
5
- compareVersions: vi.fn(),
6
- }));
7
- vi.mock('../utils/package-manager.js', () => ({
8
- getUpdateInstructions: vi.fn(),
9
- }));
10
- import { getCurrentCliVersion, getLatestCliVersion, compareVersions, } from '../utils/version-check.js';
11
- import { getUpdateInstructions } from '../utils/package-manager.js';
12
- import { versionCheckCommand } from './version.js';
13
- beforeEach(() => {
14
- vi.clearAllMocks();
15
- vi.spyOn(console, 'log').mockImplementation(() => { });
16
- vi.spyOn(console, 'error').mockImplementation(() => { });
17
- vi.spyOn(process, 'exit').mockImplementation((code) => {
18
- throw new Error(`process.exit(${code})`);
19
- });
20
- });
21
- describe('versionCheckCommand', () => {
22
- it('displays up-to-date message when versions match', async () => {
23
- ;
24
- getCurrentCliVersion.mockResolvedValue('4.26.0');
25
- getLatestCliVersion.mockResolvedValue('4.26.0');
26
- compareVersions.mockReturnValue(false);
27
- await versionCheckCommand();
28
- const logCalls = console.log.mock.calls.map((c) => c[0]);
29
- expect(logCalls.some((msg) => typeof msg === 'string' && msg.includes('up to date'))).toBe(true);
30
- });
31
- it('displays update available when newer version exists', async () => {
32
- ;
33
- getCurrentCliVersion.mockResolvedValue('4.25.0');
34
- getLatestCliVersion.mockResolvedValue('4.26.0');
35
- compareVersions.mockReturnValue(true);
36
- getUpdateInstructions.mockReturnValue([
37
- 'npm install -g @meltstudio/meltctl@latest',
38
- '',
39
- 'Or with yarn:',
40
- ' yarn global add @meltstudio/meltctl@latest',
41
- ]);
42
- await versionCheckCommand();
43
- const logCalls = console.log.mock.calls.map((c) => c[0]);
44
- expect(logCalls.some((msg) => typeof msg === 'string' && msg.includes('Update available'))).toBe(true);
45
- });
46
- it('shows update instructions when update is available', async () => {
47
- ;
48
- getCurrentCliVersion.mockResolvedValue('4.25.0');
49
- getLatestCliVersion.mockResolvedValue('4.26.0');
50
- compareVersions.mockReturnValue(true);
51
- getUpdateInstructions.mockReturnValue(['npm install -g @meltstudio/meltctl@latest']);
52
- await versionCheckCommand();
53
- expect(getUpdateInstructions).toHaveBeenCalled();
54
- const logCalls = console.log.mock.calls.map((c) => c[0]);
55
- expect(logCalls.some((msg) => typeof msg === 'string' && msg.includes('To update, run'))).toBe(true);
56
- });
57
- it('shows warning when unable to check for updates (network error)', async () => {
58
- ;
59
- getCurrentCliVersion.mockResolvedValue('4.26.0');
60
- getLatestCliVersion.mockResolvedValue(null);
61
- await versionCheckCommand();
62
- const logCalls = console.log.mock.calls.map((c) => c[0]);
63
- expect(logCalls.some((msg) => typeof msg === 'string' && msg.includes('Unable to check for updates'))).toBe(true);
64
- });
65
- it('shows current version when network check fails', async () => {
66
- ;
67
- getCurrentCliVersion.mockResolvedValue('4.26.0');
68
- getLatestCliVersion.mockResolvedValue(null);
69
- await versionCheckCommand();
70
- const logCalls = console.log.mock.calls.map((c) => c[0]);
71
- expect(logCalls.some((msg) => typeof msg === 'string' && msg.includes('4.26.0'))).toBe(true);
72
- });
73
- it('exits with error when getCurrentCliVersion throws', async () => {
74
- ;
75
- getCurrentCliVersion.mockRejectedValue(new Error('Cannot read package.json'));
76
- await expect(versionCheckCommand()).rejects.toThrow('process.exit(1)');
77
- expect(console.error).toHaveBeenCalled();
78
- });
79
- it('exits with error when getLatestCliVersion throws', async () => {
80
- ;
81
- getCurrentCliVersion.mockResolvedValue('4.26.0');
82
- getLatestCliVersion.mockRejectedValue(new Error('Unexpected error'));
83
- await expect(versionCheckCommand()).rejects.toThrow('process.exit(1)');
84
- expect(console.error).toHaveBeenCalled();
85
- });
86
- });
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
@@ -1 +0,0 @@
1
- export declare function trackCommand(command: string, success: boolean, errorMessage?: string): Promise<void>;
@@ -1,54 +0,0 @@
1
- import { readFileSync } from 'fs';
2
- import { join, dirname } from 'path';
3
- import { fileURLToPath } from 'url';
4
- import { createMeltClient } from '@meltstudio/meltctl-sdk';
5
- import { getStoredAuth, API_BASE } from './auth.js';
6
- import { getGitRepository, getGitBranch, getProjectName } from './git.js';
7
- import { debugLog } from './debug.js';
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = dirname(__filename);
10
- function getVersion() {
11
- try {
12
- const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8'));
13
- return pkg.version ?? 'unknown';
14
- }
15
- catch {
16
- return 'unknown';
17
- }
18
- }
19
- export async function trackCommand(command, success, errorMessage) {
20
- try {
21
- if (process.env['MELTCTL_NO_ANALYTICS']) {
22
- debugLog('Analytics disabled via MELTCTL_NO_ANALYTICS');
23
- return;
24
- }
25
- const auth = await getStoredAuth();
26
- if (!auth || new Date(auth.expiresAt) <= new Date()) {
27
- debugLog('Analytics skipped: not authenticated or token expired');
28
- return;
29
- }
30
- const repo = getGitRepository();
31
- const client = createMeltClient({ baseUrl: API_BASE, token: auth.token });
32
- const controller = new AbortController();
33
- const timeout = setTimeout(() => controller.abort(), 3000);
34
- try {
35
- await client.events.submit({
36
- command,
37
- project: getProjectName(),
38
- repository: repo?.slug ?? null,
39
- branch: getGitBranch(),
40
- version: getVersion(),
41
- success,
42
- errorMessage: errorMessage?.slice(0, 500) ?? null,
43
- });
44
- debugLog(`Analytics event sent for "${command}"`);
45
- }
46
- catch (e) {
47
- debugLog(`Analytics error: ${e instanceof Error ? e.message : e}`);
48
- }
49
- clearTimeout(timeout);
50
- }
51
- catch (e) {
52
- debugLog(`Analytics error: ${e instanceof Error ? e.message : e}`);
53
- }
54
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,91 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- vi.mock('./auth.js', () => ({
3
- getStoredAuth: vi.fn(),
4
- API_BASE: 'https://api.test',
5
- }));
6
- vi.mock('./git.js', () => ({
7
- getGitRepository: vi
8
- .fn()
9
- .mockReturnValue({ slug: 'org/repo', url: 'https://github.com/org/repo' }),
10
- getGitBranch: vi.fn().mockReturnValue('main'),
11
- getProjectName: vi.fn().mockReturnValue('test-project'),
12
- }));
13
- const mockFetch = vi.fn().mockResolvedValue({ ok: true });
14
- vi.stubGlobal('fetch', mockFetch);
15
- import { getStoredAuth } from './auth.js';
16
- import { trackCommand } from './analytics.js';
17
- beforeEach(() => {
18
- vi.clearAllMocks();
19
- });
20
- describe('trackCommand', () => {
21
- it('sends event when authenticated', async () => {
22
- vi.mocked(getStoredAuth).mockResolvedValue({
23
- token: 'test-token',
24
- email: 'user@meltstudio.co',
25
- expiresAt: new Date(Date.now() + 86400000).toISOString(),
26
- });
27
- await trackCommand('standup', true);
28
- expect(mockFetch).toHaveBeenCalledWith('https://api.test/events', expect.objectContaining({
29
- method: 'POST',
30
- headers: expect.objectContaining({
31
- Authorization: 'Bearer test-token',
32
- }),
33
- }));
34
- const body = JSON.parse(mockFetch.mock.calls[0][1].body);
35
- expect(body.command).toBe('standup');
36
- expect(body.success).toBe(true);
37
- expect(body.project).toBe('test-project');
38
- expect(body.repository).toBe('org/repo');
39
- expect(body.branch).toBe('main');
40
- });
41
- it('does not send event when not authenticated', async () => {
42
- vi.mocked(getStoredAuth).mockResolvedValue(undefined);
43
- await trackCommand('standup', true);
44
- expect(mockFetch).not.toHaveBeenCalled();
45
- });
46
- it('does not send event when token is expired', async () => {
47
- vi.mocked(getStoredAuth).mockResolvedValue({
48
- token: 'test-token',
49
- email: 'user@meltstudio.co',
50
- expiresAt: new Date(Date.now() - 86400000).toISOString(),
51
- });
52
- await trackCommand('standup', true);
53
- expect(mockFetch).not.toHaveBeenCalled();
54
- });
55
- it('does not throw when fetch fails', async () => {
56
- vi.mocked(getStoredAuth).mockResolvedValue({
57
- token: 'test-token',
58
- email: 'user@meltstudio.co',
59
- expiresAt: new Date(Date.now() + 86400000).toISOString(),
60
- });
61
- mockFetch.mockRejectedValueOnce(new Error('Network error'));
62
- await expect(trackCommand('standup', true)).resolves.not.toThrow();
63
- });
64
- it('does not throw when auth check fails', async () => {
65
- vi.mocked(getStoredAuth).mockRejectedValue(new Error('fs error'));
66
- await expect(trackCommand('standup', true)).resolves.not.toThrow();
67
- });
68
- it('truncates error messages to 500 chars', async () => {
69
- vi.mocked(getStoredAuth).mockResolvedValue({
70
- token: 'test-token',
71
- email: 'user@meltstudio.co',
72
- expiresAt: new Date(Date.now() + 86400000).toISOString(),
73
- });
74
- const longError = 'x'.repeat(1000);
75
- await trackCommand('standup', false, longError);
76
- const body = JSON.parse(mockFetch.mock.calls[0][1].body);
77
- expect(body.errorMessage.length).toBe(500);
78
- });
79
- it('includes success false and error message on failure', async () => {
80
- vi.mocked(getStoredAuth).mockResolvedValue({
81
- token: 'test-token',
82
- email: 'user@meltstudio.co',
83
- expiresAt: new Date(Date.now() + 86400000).toISOString(),
84
- });
85
- await trackCommand('audit submit', false, 'Not authenticated');
86
- const body = JSON.parse(mockFetch.mock.calls[0][1].body);
87
- expect(body.command).toBe('audit submit');
88
- expect(body.success).toBe(false);
89
- expect(body.errorMessage).toBe('Not authenticated');
90
- });
91
- });
@@ -1,3 +0,0 @@
1
- import { type MeltClient } from '@meltstudio/meltctl-sdk';
2
- export declare function getToken(): Promise<string>;
3
- export declare function getClient(): Promise<MeltClient>;
package/dist/utils/api.js DELETED
@@ -1,23 +0,0 @@
1
- import chalk from 'chalk';
2
- import { createMeltClient } from '@meltstudio/meltctl-sdk';
3
- import { getStoredAuth, API_BASE } from './auth.js';
4
- export async function getToken() {
5
- const envToken = process.env['MELTCTL_TOKEN'];
6
- if (envToken) {
7
- return envToken;
8
- }
9
- const auth = await getStoredAuth();
10
- if (!auth) {
11
- console.error(chalk.red('Not authenticated. Run `npx @meltstudio/meltctl@latest login` or set MELTCTL_TOKEN for CI.'));
12
- process.exit(1);
13
- }
14
- if (new Date(auth.expiresAt) <= new Date()) {
15
- console.error(chalk.red('Session expired. Run `npx @meltstudio/meltctl@latest login` to re-authenticate, or set MELTCTL_TOKEN for CI.'));
16
- process.exit(1);
17
- }
18
- return auth.token;
19
- }
20
- export async function getClient() {
21
- const token = await getToken();
22
- return createMeltClient({ baseUrl: API_BASE, token });
23
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,76 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- vi.mock('./auth.js', () => ({
3
- getStoredAuth: vi.fn(),
4
- API_BASE: 'https://test-api.example.com',
5
- }));
6
- vi.mock('@meltstudio/meltctl-sdk', () => ({
7
- createMeltClient: vi.fn().mockReturnValue({ mock: true }),
8
- }));
9
- import { getStoredAuth } from './auth.js';
10
- import { createMeltClient } from '@meltstudio/meltctl-sdk';
11
- import { getToken, getClient } from './api.js';
12
- beforeEach(() => {
13
- vi.clearAllMocks();
14
- vi.spyOn(console, 'error').mockImplementation(() => { });
15
- vi.spyOn(process, 'exit').mockImplementation((code) => {
16
- throw new Error(`process.exit(${code})`);
17
- });
18
- delete process.env['MELTCTL_TOKEN'];
19
- });
20
- describe('getToken', () => {
21
- it('returns env token when MELTCTL_TOKEN is set', async () => {
22
- process.env['MELTCTL_TOKEN'] = 'env-token-123';
23
- const token = await getToken();
24
- expect(token).toBe('env-token-123');
25
- expect(getStoredAuth).not.toHaveBeenCalled();
26
- });
27
- it('returns stored auth token when valid', async () => {
28
- ;
29
- getStoredAuth.mockResolvedValue({
30
- token: 'stored-token',
31
- email: 'dev@meltstudio.co',
32
- expiresAt: '2099-12-31T00:00:00Z',
33
- });
34
- const token = await getToken();
35
- expect(token).toBe('stored-token');
36
- });
37
- it('exits when no auth is available', async () => {
38
- ;
39
- getStoredAuth.mockResolvedValue(undefined);
40
- await expect(getToken()).rejects.toThrow('process.exit(1)');
41
- expect(console.error).toHaveBeenCalled();
42
- });
43
- it('exits when stored token is expired', async () => {
44
- ;
45
- getStoredAuth.mockResolvedValue({
46
- token: 'expired-token',
47
- email: 'dev@meltstudio.co',
48
- expiresAt: '2020-01-01T00:00:00Z',
49
- });
50
- await expect(getToken()).rejects.toThrow('process.exit(1)');
51
- expect(console.error).toHaveBeenCalled();
52
- });
53
- });
54
- describe('getClient', () => {
55
- it('creates SDK client with token and base URL', async () => {
56
- ;
57
- getStoredAuth.mockResolvedValue({
58
- token: 'my-token',
59
- email: 'dev@meltstudio.co',
60
- expiresAt: '2099-12-31T00:00:00Z',
61
- });
62
- await getClient();
63
- expect(createMeltClient).toHaveBeenCalledWith({
64
- baseUrl: 'https://test-api.example.com',
65
- token: 'my-token',
66
- });
67
- });
68
- it('uses env token when MELTCTL_TOKEN is set', async () => {
69
- process.env['MELTCTL_TOKEN'] = 'env-token';
70
- await getClient();
71
- expect(createMeltClient).toHaveBeenCalledWith({
72
- baseUrl: 'https://test-api.example.com',
73
- token: 'env-token',
74
- });
75
- });
76
- });
@@ -1,12 +0,0 @@
1
- export declare const API_BASE: string;
2
- interface StoredAuth {
3
- token: string;
4
- email: string;
5
- expiresAt: string;
6
- }
7
- export declare function getStoredAuth(): Promise<StoredAuth | undefined>;
8
- export declare function storeAuth(auth: StoredAuth): Promise<void>;
9
- export declare function clearAuth(): Promise<void>;
10
- export declare function isAuthenticated(): Promise<boolean>;
11
- export declare function authenticatedFetch(urlPath: string, options?: RequestInit): Promise<Response>;
12
- export {};
@@ -1,54 +0,0 @@
1
- import fs from 'fs-extra';
2
- import path from 'path';
3
- import os from 'os';
4
- const AUTH_DIR = path.join(os.homedir(), '.meltctl');
5
- const AUTH_FILE = path.join(AUTH_DIR, 'auth.json');
6
- export const API_BASE = process.env['MELTCTL_API_URL'] ??
7
- 'https://ewszkw32he2ebgkwlbwmoubf5y0hllpx.lambda-url.us-east-1.on.aws';
8
- export async function getStoredAuth() {
9
- if (!(await fs.pathExists(AUTH_FILE))) {
10
- return undefined;
11
- }
12
- try {
13
- return (await fs.readJson(AUTH_FILE));
14
- }
15
- catch {
16
- return undefined;
17
- }
18
- }
19
- export async function storeAuth(auth) {
20
- await fs.ensureDir(AUTH_DIR);
21
- await fs.writeJson(AUTH_FILE, auth, { spaces: 2 });
22
- }
23
- export async function clearAuth() {
24
- if (await fs.pathExists(AUTH_FILE)) {
25
- await fs.remove(AUTH_FILE);
26
- }
27
- }
28
- export async function isAuthenticated() {
29
- const auth = await getStoredAuth();
30
- if (!auth) {
31
- return false;
32
- }
33
- return new Date(auth.expiresAt) > new Date();
34
- }
35
- export async function authenticatedFetch(urlPath, options = {}) {
36
- const auth = await getStoredAuth();
37
- if (!auth) {
38
- throw new Error('Not authenticated. Run `npx @meltstudio/meltctl@latest login` first.');
39
- }
40
- if (new Date(auth.expiresAt) <= new Date()) {
41
- throw new Error('Session expired. Run `npx @meltstudio/meltctl@latest login` to re-authenticate.');
42
- }
43
- const response = await fetch(`${API_BASE}${urlPath}`, {
44
- ...options,
45
- headers: {
46
- Authorization: `Bearer ${auth.token}`,
47
- ...options.headers,
48
- },
49
- });
50
- if (response.status === 401) {
51
- throw new Error('Session expired. Run `npx @meltstudio/meltctl@latest login` to re-authenticate.');
52
- }
53
- return response;
54
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,165 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- vi.mock('fs-extra', () => ({
3
- default: {
4
- pathExists: vi.fn(),
5
- readJson: vi.fn(),
6
- ensureDir: vi.fn(),
7
- writeJson: vi.fn(),
8
- remove: vi.fn(),
9
- },
10
- }));
11
- import fs from 'fs-extra';
12
- import { getStoredAuth, storeAuth, clearAuth, isAuthenticated, authenticatedFetch, API_BASE, } from './auth.js';
13
- beforeEach(() => {
14
- vi.clearAllMocks();
15
- });
16
- describe('API_BASE', () => {
17
- it('has a default value', () => {
18
- expect(API_BASE).toBeTruthy();
19
- expect(typeof API_BASE).toBe('string');
20
- });
21
- });
22
- describe('getStoredAuth', () => {
23
- it('returns undefined when auth file does not exist', async () => {
24
- ;
25
- fs.pathExists.mockResolvedValue(false);
26
- const result = await getStoredAuth();
27
- expect(result).toBeUndefined();
28
- });
29
- it('returns stored auth data when file exists', async () => {
30
- const mockAuth = { token: 'abc', email: 'dev@meltstudio.co', expiresAt: '2026-12-31' };
31
- fs.pathExists.mockResolvedValue(true);
32
- fs.readJson.mockResolvedValue(mockAuth);
33
- const result = await getStoredAuth();
34
- expect(result).toEqual(mockAuth);
35
- });
36
- it('returns undefined when readJson throws', async () => {
37
- ;
38
- fs.pathExists.mockResolvedValue(true);
39
- fs.readJson.mockRejectedValue(new Error('parse error'));
40
- const result = await getStoredAuth();
41
- expect(result).toBeUndefined();
42
- });
43
- });
44
- describe('storeAuth', () => {
45
- it('creates directory and writes auth file', async () => {
46
- const auth = { token: 'tok', email: 'a@meltstudio.co', expiresAt: '2026-12-31' };
47
- await storeAuth(auth);
48
- expect(fs.ensureDir).toHaveBeenCalled();
49
- expect(fs.writeJson).toHaveBeenCalledWith(expect.stringContaining('auth.json'), auth, {
50
- spaces: 2,
51
- });
52
- });
53
- });
54
- describe('clearAuth', () => {
55
- it('removes auth file when it exists', async () => {
56
- ;
57
- fs.pathExists.mockResolvedValue(true);
58
- await clearAuth();
59
- expect(fs.remove).toHaveBeenCalled();
60
- });
61
- it('does nothing when auth file does not exist', async () => {
62
- ;
63
- fs.pathExists.mockResolvedValue(false);
64
- await clearAuth();
65
- expect(fs.remove).not.toHaveBeenCalled();
66
- });
67
- });
68
- describe('isAuthenticated', () => {
69
- it('returns false when no stored auth', async () => {
70
- ;
71
- fs.pathExists.mockResolvedValue(false);
72
- expect(await isAuthenticated()).toBe(false);
73
- });
74
- it('returns false when token is expired', async () => {
75
- ;
76
- fs.pathExists.mockResolvedValue(true);
77
- fs.readJson.mockResolvedValue({
78
- token: 'tok',
79
- email: 'a@meltstudio.co',
80
- expiresAt: '2020-01-01T00:00:00Z',
81
- });
82
- expect(await isAuthenticated()).toBe(false);
83
- });
84
- it('returns true when token is valid and not expired', async () => {
85
- ;
86
- fs.pathExists.mockResolvedValue(true);
87
- fs.readJson.mockResolvedValue({
88
- token: 'tok',
89
- email: 'a@meltstudio.co',
90
- expiresAt: '2099-12-31T00:00:00Z',
91
- });
92
- expect(await isAuthenticated()).toBe(true);
93
- });
94
- });
95
- describe('authenticatedFetch', () => {
96
- it('throws when not authenticated', async () => {
97
- ;
98
- fs.pathExists.mockResolvedValue(false);
99
- await expect(authenticatedFetch('/test')).rejects.toThrow('Not authenticated');
100
- });
101
- it('throws when session expired', async () => {
102
- ;
103
- fs.pathExists.mockResolvedValue(true);
104
- fs.readJson.mockResolvedValue({
105
- token: 'tok',
106
- email: 'a@meltstudio.co',
107
- expiresAt: '2020-01-01T00:00:00Z',
108
- });
109
- await expect(authenticatedFetch('/test')).rejects.toThrow('expired');
110
- });
111
- it('makes authenticated request with bearer token', async () => {
112
- ;
113
- fs.pathExists.mockResolvedValue(true);
114
- fs.readJson.mockResolvedValue({
115
- token: 'my-jwt',
116
- email: 'a@meltstudio.co',
117
- expiresAt: '2099-12-31T00:00:00Z',
118
- });
119
- const mockResponse = { status: 200, ok: true };
120
- const fetchMock = vi.fn().mockResolvedValue(mockResponse);
121
- vi.stubGlobal('fetch', fetchMock);
122
- const result = await authenticatedFetch('/templates');
123
- expect(result).toBe(mockResponse);
124
- expect(fetchMock).toHaveBeenCalledWith(`${API_BASE}/templates`, expect.objectContaining({
125
- headers: expect.objectContaining({
126
- Authorization: 'Bearer my-jwt',
127
- }),
128
- }));
129
- vi.unstubAllGlobals();
130
- });
131
- it('throws when API returns 401', async () => {
132
- ;
133
- fs.pathExists.mockResolvedValue(true);
134
- fs.readJson.mockResolvedValue({
135
- token: 'expired-jwt',
136
- email: 'a@meltstudio.co',
137
- expiresAt: '2099-12-31T00:00:00Z',
138
- });
139
- const fetchMock = vi.fn().mockResolvedValue({ status: 401, ok: false });
140
- vi.stubGlobal('fetch', fetchMock);
141
- await expect(authenticatedFetch('/test')).rejects.toThrow('expired');
142
- vi.unstubAllGlobals();
143
- });
144
- it('merges custom headers with auth header', async () => {
145
- ;
146
- fs.pathExists.mockResolvedValue(true);
147
- fs.readJson.mockResolvedValue({
148
- token: 'jwt',
149
- email: 'a@meltstudio.co',
150
- expiresAt: '2099-12-31T00:00:00Z',
151
- });
152
- const fetchMock = vi.fn().mockResolvedValue({ status: 200, ok: true });
153
- vi.stubGlobal('fetch', fetchMock);
154
- await authenticatedFetch('/test', {
155
- headers: { 'Content-Type': 'application/json' },
156
- });
157
- expect(fetchMock).toHaveBeenCalledWith(expect.any(String), expect.objectContaining({
158
- headers: expect.objectContaining({
159
- Authorization: 'Bearer jwt',
160
- 'Content-Type': 'application/json',
161
- }),
162
- }));
163
- vi.unstubAllGlobals();
164
- });
165
- });
@@ -1 +0,0 @@
1
- export declare function printBanner(): void;
@@ -1,22 +0,0 @@
1
- import chalk from 'chalk';
2
- import gradient from 'gradient-string';
3
- import { readFileSync } from 'fs';
4
- import { join, dirname } from 'path';
5
- import { fileURLToPath } from 'url';
6
- const __dirname = dirname(fileURLToPath(import.meta.url));
7
- const pkg = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf8'));
8
- // Melt brand colors: yellow, magenta/pink, cyan, red, orange
9
- const meltGradient = gradient(['#FFC107', '#E91E63', '#00BCD4', '#FF5722']);
10
- const LOGO = `
11
- ███╗ ███╗███████╗██╗ ████████╗ ██████╗████████╗██╗
12
- ████╗ ████║██╔════╝██║ ╚══██╔══╝██╔════╝╚══██╔══╝██║
13
- ██╔████╔██║█████╗ ██║ ██║ ██║ ██║ ██║
14
- ██║╚██╔╝██║██╔══╝ ██║ ██║ ██║ ██║ ██║
15
- ██║ ╚═╝ ██║███████╗███████╗██║ ╚██████╗ ██║ ███████╗
16
- ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝ ╚═════╝ ╚═╝ ╚══════╝`;
17
- export function printBanner() {
18
- console.log(meltGradient(LOGO));
19
- console.log();
20
- console.log(` ${chalk.dim('v' + pkg.version)} ${chalk.dim('·')} ${chalk.dim('AI-first development tools for teams')}`);
21
- console.log();
22
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,34 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- vi.mock('fs', () => ({
3
- readFileSync: vi.fn().mockReturnValue(JSON.stringify({ version: '1.2.3' })),
4
- }));
5
- vi.mock('gradient-string', () => ({
6
- default: vi.fn().mockReturnValue(vi.fn((str) => str)),
7
- }));
8
- import { printBanner } from './banner.js';
9
- beforeEach(() => {
10
- vi.clearAllMocks();
11
- vi.spyOn(console, 'log').mockImplementation(() => { });
12
- });
13
- describe('printBanner', () => {
14
- it('does not throw', () => {
15
- expect(() => printBanner()).not.toThrow();
16
- });
17
- it('calls console.log at least once', () => {
18
- printBanner();
19
- expect(console.log).toHaveBeenCalled();
20
- });
21
- it('prints output containing MELTCTL logo text', () => {
22
- printBanner();
23
- const logCalls = console.log.mock.calls.map((c) => c[0]);
24
- const allOutput = logCalls.filter(Boolean).join('\n');
25
- // The logo contains block characters for MELTCTL
26
- expect(allOutput).toContain('███');
27
- });
28
- it('prints version info', () => {
29
- printBanner();
30
- const logCalls = console.log.mock.calls.map((c) => String(c[0] ?? ''));
31
- const hasVersion = logCalls.some((msg) => msg.includes('v1.2.3') || msg.includes('1.2.3'));
32
- expect(hasVersion).toBe(true);
33
- });
34
- });
@@ -1 +0,0 @@
1
- export declare function debugLog(message: string): void;
@@ -1,6 +0,0 @@
1
- import chalk from 'chalk';
2
- export function debugLog(message) {
3
- if (process.env['MELTCTL_DEBUG']) {
4
- console.error(chalk.dim(`[debug] ${message}`));
5
- }
6
- }