@meltstudio/meltctl 4.34.0 → 4.36.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.
@@ -1,9 +1,5 @@
1
- import { authenticatedFetch } from './auth.js';
1
+ import { getClient } from './api.js';
2
2
  export async function fetchTemplates() {
3
- const response = await authenticatedFetch('/templates');
4
- if (!response.ok) {
5
- throw new Error(`Failed to fetch templates: ${response.statusText}`);
6
- }
7
- const data = (await response.json());
8
- return data.files;
3
+ const client = await getClient();
4
+ return client.templates.fetch();
9
5
  }
@@ -1,8 +1,12 @@
1
1
  import { describe, it, expect, vi, beforeEach } from 'vitest';
2
- vi.mock('./auth.js', () => ({
3
- authenticatedFetch: vi.fn(),
2
+ const mockClient = vi.hoisted(() => ({
3
+ templates: {
4
+ fetch: vi.fn(),
5
+ },
6
+ }));
7
+ vi.mock('./api.js', () => ({
8
+ getClient: vi.fn().mockResolvedValue(mockClient),
4
9
  }));
5
- import { authenticatedFetch } from './auth.js';
6
10
  import { fetchTemplates } from './templates.js';
7
11
  beforeEach(() => {
8
12
  vi.clearAllMocks();
@@ -13,38 +17,22 @@ describe('fetchTemplates', () => {
13
17
  'AGENTS.md': '# Agents content',
14
18
  '.claude/skills/setup.md': '# Setup skill',
15
19
  };
16
- authenticatedFetch.mockResolvedValue({
17
- ok: true,
18
- json: vi.fn().mockResolvedValue({ files: mockFiles }),
19
- });
20
+ mockClient.templates.fetch.mockResolvedValue(mockFiles);
20
21
  const result = await fetchTemplates();
21
- expect(authenticatedFetch).toHaveBeenCalledWith('/templates');
22
+ expect(mockClient.templates.fetch).toHaveBeenCalled();
22
23
  expect(result).toEqual(mockFiles);
23
24
  });
24
25
  it('throws error when API returns failure', async () => {
25
- ;
26
- authenticatedFetch.mockResolvedValue({
27
- ok: false,
28
- statusText: 'Unauthorized',
29
- });
26
+ mockClient.templates.fetch.mockRejectedValue(new Error('Failed to fetch templates: Unauthorized'));
30
27
  await expect(fetchTemplates()).rejects.toThrow('Failed to fetch templates: Unauthorized');
31
28
  });
32
29
  it('throws error when API returns 500', async () => {
33
- ;
34
- authenticatedFetch.mockResolvedValue({
35
- ok: false,
36
- statusText: 'Internal Server Error',
37
- });
30
+ mockClient.templates.fetch.mockRejectedValue(new Error('Failed to fetch templates: Internal Server Error'));
38
31
  await expect(fetchTemplates()).rejects.toThrow('Failed to fetch templates: Internal Server Error');
39
32
  });
40
- it('calls authenticatedFetch with /templates path', async () => {
41
- ;
42
- authenticatedFetch.mockResolvedValue({
43
- ok: true,
44
- json: vi.fn().mockResolvedValue({ files: {} }),
45
- });
33
+ it('calls client.templates.fetch', async () => {
34
+ mockClient.templates.fetch.mockResolvedValue({});
46
35
  await fetchTemplates();
47
- expect(authenticatedFetch).toHaveBeenCalledTimes(1);
48
- expect(authenticatedFetch).toHaveBeenCalledWith('/templates');
36
+ expect(mockClient.templates.fetch).toHaveBeenCalledTimes(1);
49
37
  });
50
38
  });
@@ -1,4 +1,5 @@
1
1
  import chalk from 'chalk';
2
+ import { confirm } from '@inquirer/prompts';
2
3
  import { execSync } from 'child_process';
3
4
  import fs from 'fs-extra';
4
5
  import path from 'path';
@@ -101,29 +102,30 @@ export async function checkAndEnforceUpdate() {
101
102
  if (severity === 'none') {
102
103
  return;
103
104
  }
104
- const updateCmd = 'npm install -g @meltstudio/meltctl@latest';
105
105
  if (severity === 'patch') {
106
106
  // Patch updates: warn but allow continuing
107
107
  console.log();
108
- console.log(chalk.yellow(` Update available: ${currentVersion} → ${latestVersion} (run: ${updateCmd})`));
108
+ console.log(chalk.yellow(` Update available: ${currentVersion} → ${latestVersion} (run: meltctl update)`));
109
109
  console.log();
110
110
  return;
111
111
  }
112
- // Minor/major updates: block until updated
112
+ // Minor/major updates: offer to update, block if declined
113
113
  console.log();
114
- console.log(chalk.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
115
- console.log(chalk.red.bold(' ⚠️ Update Required'));
116
- console.log(chalk.red('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
114
+ console.log(chalk.yellow(` Update required: ${currentVersion} → ${latestVersion}`));
117
115
  console.log();
118
- console.log(chalk.yellow(`Current version: ${currentVersion}`));
119
- console.log(chalk.green(`Latest version: ${latestVersion}`));
120
- console.log();
121
- console.log(chalk.white('Please update meltctl to continue:'));
122
- console.log();
123
- console.log(chalk.cyan(` ${updateCmd}`));
124
- console.log();
125
- console.log(chalk.gray('Or with yarn:'));
126
- console.log(chalk.cyan(' yarn global add @meltstudio/meltctl@latest'));
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
+ }
127
129
  console.log();
128
130
  console.log(chalk.gray('To skip this check (CI/CD), set MELTCTL_SKIP_UPDATE_CHECK=1'));
129
131
  console.log();
@@ -7,8 +7,15 @@ vi.mock('fs-extra', () => ({
7
7
  readJson: vi.fn(),
8
8
  },
9
9
  }));
10
+ vi.mock('@inquirer/prompts', () => ({
11
+ confirm: vi.fn(),
12
+ }));
13
+ vi.mock('../commands/update.js', () => ({
14
+ updateCommand: vi.fn().mockResolvedValue(undefined),
15
+ }));
10
16
  import { execSync } from 'child_process';
11
17
  import fs from 'fs-extra';
18
+ import { confirm } from '@inquirer/prompts';
12
19
  import { getCurrentCliVersion, getLatestCliVersion, compareVersions, getUpdateSeverity, isCI, checkAndEnforceUpdate, } from './version-check.js';
13
20
  beforeEach(() => {
14
21
  vi.clearAllMocks();
@@ -139,17 +146,28 @@ describe('checkAndEnforceUpdate', () => {
139
146
  await checkAndEnforceUpdate();
140
147
  // Should not throw or exit
141
148
  });
142
- it('blocks on minor version update', async () => {
149
+ it('exits when user declines update on minor bump', async () => {
143
150
  const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
144
151
  fs.readJson.mockResolvedValue({ version: '4.25.0' });
145
152
  execSync.mockReturnValue('"4.26.0"\n');
153
+ vi.mocked(confirm).mockResolvedValue(false);
146
154
  await checkAndEnforceUpdate();
155
+ expect(confirm).toHaveBeenCalled();
147
156
  expect(exitSpy).toHaveBeenCalledWith(1);
148
157
  });
149
- it('blocks on major version update', async () => {
158
+ it('runs update when user accepts on minor bump', async () => {
159
+ const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
160
+ fs.readJson.mockResolvedValue({ version: '4.25.0' });
161
+ execSync.mockReturnValue('"4.26.0"\n');
162
+ vi.mocked(confirm).mockResolvedValue(true);
163
+ await checkAndEnforceUpdate();
164
+ expect(exitSpy).toHaveBeenCalledWith(0);
165
+ });
166
+ it('exits when user declines update on major bump', async () => {
150
167
  const exitSpy = vi.spyOn(process, 'exit').mockImplementation(() => undefined);
151
168
  fs.readJson.mockResolvedValue({ version: '4.33.0' });
152
169
  execSync.mockReturnValue('"5.0.0"\n');
170
+ vi.mocked(confirm).mockResolvedValue(false);
153
171
  await checkAndEnforceUpdate();
154
172
  expect(exitSpy).toHaveBeenCalledWith(1);
155
173
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meltstudio/meltctl",
3
- "version": "4.34.0",
3
+ "version": "4.36.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",
@@ -47,6 +47,7 @@
47
47
  "author": "Melt Studio",
48
48
  "license": "UNLICENSED",
49
49
  "dependencies": {
50
+ "@meltstudio/meltctl-sdk": "*",
50
51
  "@commander-js/extra-typings": "^12.1.0",
51
52
  "@inquirer/prompts": "^8.2.1",
52
53
  "chalk": "^5.4.1",
@@ -60,7 +61,7 @@
60
61
  "@typescript-eslint/eslint-plugin": "^8.19.0",
61
62
  "@typescript-eslint/parser": "^8.19.0",
62
63
  "eslint": "^9.17.0",
63
- "eslint-config-prettier": "^9.1.0",
64
+ "eslint-config-prettier": "^10.1.8",
64
65
  "eslint-plugin-prettier": "^5.2.1",
65
66
  "prettier": "^3.4.2",
66
67
  "tsx": "^4.19.5",