@mbc-cqrs-serverless/cli 1.0.26 → 1.1.0-beta.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.
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.VERSION_FILE_NAME = void 0;
6
+ exports.VERSION_CACHE_TTL_MS = exports.VERSION_CACHE_FILE_NAME = exports.VERSION_FILE_NAME = void 0;
7
7
  exports.getSkillsSourcePath = getSkillsSourcePath;
8
8
  exports.getPersonalSkillsPath = getPersonalSkillsPath;
9
9
  exports.getProjectSkillsPath = getProjectSkillsPath;
@@ -11,7 +11,9 @@ exports.copySkills = copySkills;
11
11
  exports.getInstalledVersion = getInstalledVersion;
12
12
  exports.getPackageVersion = getPackageVersion;
13
13
  exports.writeVersionFile = writeVersionFile;
14
+ exports.getLatestVersionFromRegistry = getLatestVersionFromRegistry;
14
15
  exports.default = installSkillsAction;
16
+ const child_process_1 = require("child_process");
15
17
  const fs_1 = require("fs");
16
18
  const os_1 = __importDefault(require("os"));
17
19
  const path_1 = __importDefault(require("path"));
@@ -20,6 +22,18 @@ const ui_1 = require("../ui");
20
22
  * Version file name for tracking installed skills version
21
23
  */
22
24
  exports.VERSION_FILE_NAME = '.mbc-skills-version';
25
+ /**
26
+ * Cache file name for storing latest version from npm registry
27
+ */
28
+ exports.VERSION_CACHE_FILE_NAME = '.mbc-version-cache.json';
29
+ /**
30
+ * Cache TTL in milliseconds (24 hours)
31
+ */
32
+ exports.VERSION_CACHE_TTL_MS = 24 * 60 * 60 * 1000;
33
+ /**
34
+ * npm package name for mcp-server
35
+ */
36
+ const MCP_SERVER_PACKAGE = '@mbc-cqrs-serverless/mcp-server';
23
37
  /**
24
38
  * Get the path to the mcp-server skills source directory
25
39
  */
@@ -130,6 +144,61 @@ function writeVersionFile(destPath, version) {
130
144
  const versionFilePath = path_1.default.join(destPath, exports.VERSION_FILE_NAME);
131
145
  (0, fs_1.writeFileSync)(versionFilePath, version, 'utf-8');
132
146
  }
147
+ /**
148
+ * Get the latest package version from npm registry with caching
149
+ * @param destPath - The destination path where cache file is stored
150
+ * @param forceRefresh - Force refresh from npm registry, ignoring cache
151
+ * @returns The latest version or null if not available
152
+ */
153
+ function getLatestVersionFromRegistry(destPath, forceRefresh = false) {
154
+ const cacheFilePath = path_1.default.join(destPath, exports.VERSION_CACHE_FILE_NAME);
155
+ // Check cache first (unless force refresh)
156
+ if (!forceRefresh && (0, fs_1.existsSync)(cacheFilePath)) {
157
+ try {
158
+ const cache = JSON.parse((0, fs_1.readFileSync)(cacheFilePath, 'utf-8'));
159
+ const cacheAge = Date.now() - new Date(cache.checkedAt).getTime();
160
+ if (cacheAge < exports.VERSION_CACHE_TTL_MS) {
161
+ // Cache is still valid
162
+ return cache.version;
163
+ }
164
+ }
165
+ catch {
166
+ // Cache read failed, continue to fetch from registry
167
+ }
168
+ }
169
+ // Fetch from npm registry
170
+ try {
171
+ const version = (0, child_process_1.execSync)(`npm view ${MCP_SERVER_PACKAGE} version`, {
172
+ encoding: 'utf-8',
173
+ timeout: 10000,
174
+ stdio: ['pipe', 'pipe', 'pipe'],
175
+ }).trim();
176
+ // Ensure destination directory exists before writing cache
177
+ if (!(0, fs_1.existsSync)(destPath)) {
178
+ (0, fs_1.mkdirSync)(destPath, { recursive: true });
179
+ }
180
+ // Save to cache
181
+ const cache = {
182
+ version,
183
+ checkedAt: new Date().toISOString(),
184
+ };
185
+ (0, fs_1.writeFileSync)(cacheFilePath, JSON.stringify(cache, null, 2), 'utf-8');
186
+ return version;
187
+ }
188
+ catch {
189
+ // Offline or error - try to use expired cache as fallback
190
+ if ((0, fs_1.existsSync)(cacheFilePath)) {
191
+ try {
192
+ const cache = JSON.parse((0, fs_1.readFileSync)(cacheFilePath, 'utf-8'));
193
+ return cache.version;
194
+ }
195
+ catch {
196
+ return null;
197
+ }
198
+ }
199
+ return null;
200
+ }
201
+ }
133
202
  /**
134
203
  * Install Claude Code skills for MBC CQRS Serverless
135
204
  */
@@ -170,23 +239,24 @@ async function installSkillsAction(options, command) {
170
239
  // Check mode - compare versions without installing
171
240
  if (check) {
172
241
  const installedVersion = getInstalledVersion(destPath);
173
- const packageVersion = getPackageVersion(sourcePath);
242
+ // Get latest version from npm registry (with 24h cache)
243
+ const latestVersion = getLatestVersionFromRegistry(destPath, force);
174
244
  if (!installedVersion) {
175
245
  ui_1.logger.warn('Skills are not installed.');
176
- ui_1.logger.info(`Available version: ${packageVersion || 'unknown'}`);
246
+ ui_1.logger.info(`Available version: ${latestVersion || 'unknown'}`);
177
247
  ui_1.logger.info('Run `mbc install-skills` to install.');
178
248
  return;
179
249
  }
180
- if (!packageVersion) {
181
- ui_1.logger.warn('Could not determine package version.');
250
+ if (!latestVersion) {
251
+ ui_1.logger.warn('Could not determine latest version. Check your network connection.');
182
252
  ui_1.logger.info(`Installed version: ${installedVersion}`);
183
253
  return;
184
254
  }
185
- if (installedVersion === packageVersion) {
255
+ if (installedVersion === latestVersion) {
186
256
  ui_1.logger.success(`Skills are up to date (${installedVersion}).`);
187
257
  }
188
258
  else {
189
- ui_1.logger.warn(`Update available: ${installedVersion} → ${packageVersion}`);
259
+ ui_1.logger.warn(`Update available: ${installedVersion} → ${latestVersion}`);
190
260
  ui_1.logger.info('Run `mbc install-skills --force` to update.');
191
261
  }
192
262
  return;
@@ -203,8 +273,10 @@ async function installSkillsAction(options, command) {
203
273
  // Copy skills
204
274
  ui_1.logger.title('install', `Installing skills to ${destPath}`);
205
275
  const copiedSkills = copySkills(sourcePath, destPath);
276
+ // Get version from npm registry (preferred) or fall back to local package.json
277
+ const latestVersion = getLatestVersionFromRegistry(destPath, true);
278
+ const packageVersion = latestVersion || getPackageVersion(sourcePath);
206
279
  // Write version file
207
- const packageVersion = getPackageVersion(sourcePath);
208
280
  if (packageVersion) {
209
281
  writeVersionFile(destPath, packageVersion);
210
282
  }
@@ -39,7 +39,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  const install_skills_action_1 = __importStar(require("./install-skills.action"));
40
40
  jest.mock('fs');
41
41
  jest.mock('os');
42
+ jest.mock('child_process');
42
43
  const fs_1 = require("fs");
44
+ const child_process_1 = require("child_process");
43
45
  const os_1 = __importDefault(require("os"));
44
46
  const mockExistsSync = fs_1.existsSync;
45
47
  const mockMkdirSync = fs_1.mkdirSync;
@@ -47,6 +49,7 @@ const mockCpSync = fs_1.cpSync;
47
49
  const mockReaddirSync = fs_1.readdirSync;
48
50
  const mockReadFileSync = fs_1.readFileSync;
49
51
  const mockWriteFileSync = fs_1.writeFileSync;
52
+ const mockExecSync = child_process_1.execSync;
50
53
  describe('Install Skills Action', () => {
51
54
  const mockCommand = {
52
55
  name: () => 'install-skills',
@@ -55,6 +58,8 @@ describe('Install Skills Action', () => {
55
58
  beforeEach(() => {
56
59
  jest.clearAllMocks();
57
60
  os_1.default.homedir.mockReturnValue('/home/user');
61
+ // Default mock for npm registry - returns a test version
62
+ mockExecSync.mockReturnValue('1.0.25\n');
58
63
  });
59
64
  describe('getSkillsSourcePath', () => {
60
65
  it('should return the path to mcp-server skills directory', () => {
@@ -278,6 +283,81 @@ describe('Install Skills Action', () => {
278
283
  expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining(install_skills_action_1.VERSION_FILE_NAME), '1.0.25', 'utf-8');
279
284
  });
280
285
  });
286
+ describe('getLatestVersionFromRegistry', () => {
287
+ it('should fetch version from npm registry', () => {
288
+ mockExistsSync.mockReturnValue(false);
289
+ mockExecSync.mockReturnValue('1.0.26\n');
290
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
291
+ expect(version).toBe('1.0.26');
292
+ expect(mockExecSync).toHaveBeenCalledWith('npm view @mbc-cqrs-serverless/mcp-server version', expect.objectContaining({ encoding: 'utf-8', timeout: 10000 }));
293
+ });
294
+ it('should use cached version if cache is valid', () => {
295
+ const cacheData = {
296
+ version: '1.0.25',
297
+ checkedAt: new Date().toISOString(), // Fresh cache
298
+ };
299
+ mockExistsSync.mockReturnValue(true);
300
+ mockReadFileSync.mockReturnValue(JSON.stringify(cacheData));
301
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
302
+ expect(version).toBe('1.0.25');
303
+ expect(mockExecSync).not.toHaveBeenCalled();
304
+ });
305
+ it('should fetch from registry if cache is expired', () => {
306
+ const cacheData = {
307
+ version: '1.0.24',
308
+ checkedAt: new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(), // 25 hours ago
309
+ };
310
+ mockExistsSync.mockReturnValue(true);
311
+ mockReadFileSync.mockReturnValue(JSON.stringify(cacheData));
312
+ mockExecSync.mockReturnValue('1.0.26\n');
313
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
314
+ expect(version).toBe('1.0.26');
315
+ expect(mockExecSync).toHaveBeenCalled();
316
+ });
317
+ it('should force refresh when forceRefresh is true', () => {
318
+ const cacheData = {
319
+ version: '1.0.24',
320
+ checkedAt: new Date().toISOString(), // Fresh cache
321
+ };
322
+ mockExistsSync.mockReturnValue(true);
323
+ mockReadFileSync.mockReturnValue(JSON.stringify(cacheData));
324
+ mockExecSync.mockReturnValue('1.0.26\n');
325
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills', true);
326
+ expect(version).toBe('1.0.26');
327
+ expect(mockExecSync).toHaveBeenCalled();
328
+ });
329
+ it('should use expired cache as fallback when offline', () => {
330
+ const cacheData = {
331
+ version: '1.0.24',
332
+ checkedAt: new Date(Date.now() - 25 * 60 * 60 * 1000).toISOString(),
333
+ };
334
+ mockExistsSync.mockReturnValue(true);
335
+ mockReadFileSync.mockReturnValue(JSON.stringify(cacheData));
336
+ mockExecSync.mockImplementation(() => {
337
+ throw new Error('Network error');
338
+ });
339
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
340
+ expect(version).toBe('1.0.24');
341
+ });
342
+ it('should return null when offline and no cache exists', () => {
343
+ mockExistsSync.mockReturnValue(false);
344
+ mockExecSync.mockImplementation(() => {
345
+ throw new Error('Network error');
346
+ });
347
+ const version = (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
348
+ expect(version).toBeNull();
349
+ });
350
+ it('should save version to cache after fetching', () => {
351
+ mockExistsSync.mockReturnValue(true);
352
+ mockExecSync.mockReturnValue('1.0.26\n');
353
+ // Make cache read fail to trigger fetch
354
+ mockReadFileSync.mockImplementation(() => {
355
+ throw new Error('No cache');
356
+ });
357
+ (0, install_skills_action_1.getLatestVersionFromRegistry)('/dest/skills');
358
+ expect(mockWriteFileSync).toHaveBeenCalledWith(expect.stringContaining(install_skills_action_1.VERSION_CACHE_FILE_NAME), expect.stringContaining('"version": "1.0.26"'), 'utf-8');
359
+ });
360
+ });
281
361
  describe('Check option', () => {
282
362
  beforeEach(() => {
283
363
  mockReaddirSync.mockReturnValue([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mbc-cqrs-serverless/cli",
3
- "version": "1.0.26",
3
+ "version": "1.1.0-beta.0",
4
4
  "description": "a CLI to get started with MBC CQRS serverless framework",
5
5
  "keywords": [
6
6
  "mbc",
@@ -58,5 +58,5 @@
58
58
  "@faker-js/faker": "^8.3.1",
59
59
  "copyfiles": "^2.4.1"
60
60
  },
61
- "gitHead": "0956dc696475fb769dd134aa619679ba5155e646"
61
+ "gitHead": "b16d8982c02933fd41a455d3e41f9750bbcf668c"
62
62
  }