@khanhcan148/mk 0.1.4 → 0.1.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanhcan148/mk",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "CLI to install and manage MyClaudeKit (.claude/) in your projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,7 @@ import { MANIFEST_FILENAME } from '../lib/constants.js';
10
10
  import { resolveTokenOrLogin } from '../lib/auth.js';
11
11
  import { writeToken, readStoredToken } from '../lib/config.js';
12
12
  import { downloadAndExtractKit, cleanupTempDir } from '../lib/download.js';
13
+ import { fetchLatestRelease } from '../lib/releases.js';
13
14
 
14
15
  /**
15
16
  * Run the init command.
@@ -19,7 +20,8 @@ import { downloadAndExtractKit, cleanupTempDir } from '../lib/download.js';
19
20
  * targetDir?: string,
20
21
  * manifestPath?: string,
21
22
  * scope?: 'project'|'global',
22
- * dryRun?: boolean
23
+ * dryRun?: boolean,
24
+ * version?: string - Kit version to store in manifest (defaults to pkg.version when omitted)
23
25
  * }} params
24
26
  * @returns {Promise<{ files: Array, totalSize: number }>}
25
27
  */
@@ -29,7 +31,8 @@ export async function runInit(params = {}) {
29
31
  targetDir = resolveTargetDir({ global: false }),
30
32
  manifestPath = resolveManifestPath({ global: false }),
31
33
  scope = 'project',
32
- dryRun = false
34
+ dryRun = false,
35
+ version: explicitVersion
33
36
  } = params;
34
37
 
35
38
  // Guard: abort if already installed
@@ -61,8 +64,10 @@ export async function runInit(params = {}) {
61
64
  }
62
65
  }
63
66
 
64
- // Write manifest
65
- writeManifest(manifestPath, files, pkg.version, scope);
67
+ // Write manifest.
68
+ // Use explicitVersion when provided (e.g. release.version from initAction);
69
+ // fall back to pkg.version for direct runInit calls without a known release version.
70
+ writeManifest(manifestPath, files, explicitVersion ?? pkg.version, scope);
66
71
 
67
72
  const totalSize = fileList.reduce((s, f) => s + f.size, 0);
68
73
  return { files: fileList, totalSize, fileCount: fileList.length };
@@ -81,11 +86,15 @@ export async function initAction(options = {}, deps = {}) {
81
86
  downloadAndExtractKit: download = downloadAndExtractKit,
82
87
  cleanupTempDir: cleanup = cleanupTempDir,
83
88
  writeToken: storeToken = writeToken,
84
- readStoredToken: readToken = readStoredToken
89
+ readStoredToken: readToken = readStoredToken,
90
+ fetchLatestRelease: fetchRelease = fetchLatestRelease,
91
+ // Injectable for tests — allows overriding resolved paths without touching CWD
92
+ targetDir: injectedTargetDir,
93
+ manifestPath: injectedManifestPath
85
94
  } = deps;
86
95
 
87
- const targetDir = resolveTargetDir(options);
88
- const manifestPath = resolveManifestPath(options);
96
+ const targetDir = injectedTargetDir ?? resolveTargetDir(options);
97
+ const manifestPath = injectedManifestPath ?? resolveManifestPath(options);
89
98
  const scope = options.global ? 'global' : 'project';
90
99
 
91
100
  // Pre-flight: warn if target exists with files
@@ -115,11 +124,23 @@ export async function initAction(options = {}, deps = {}) {
115
124
  }
116
125
  });
117
126
 
127
+ // Determine installed version from latest release (more accurate than pkg.version).
128
+ // Falls back silently to pkg.version if no release is available (new repo, API error, etc.)
129
+ let installedVersion;
130
+ if (!options.dryRun) {
131
+ try {
132
+ const release = await fetchRelease(token);
133
+ installedVersion = release.available ? release.version : undefined;
134
+ } catch {
135
+ // Ignore errors — version fallback handled by runInit
136
+ }
137
+ }
138
+
118
139
  process.stdout.write('Downloading kit from GitHub...\n');
119
140
  tempDir = await download(token);
120
141
  const sourceDir = join(tempDir, '.claude');
121
142
 
122
- const result = await runInit({ sourceDir, targetDir, manifestPath, scope, dryRun: options.dryRun });
143
+ const result = await runInit({ sourceDir, targetDir, manifestPath, scope, dryRun: options.dryRun, version: installedVersion });
123
144
 
124
145
  if (options.dryRun) {
125
146
  process.stdout.write(`Would install ${result.files.length} files:\n`);
@@ -40,7 +40,8 @@ async function defaultPromptUser(question) {
40
40
  * sourceDir?: string,
41
41
  * targetDir?: string,
42
42
  * manifestPath?: string,
43
- * force?: boolean
43
+ * force?: boolean,
44
+ * version?: string - Kit version to store in manifest (defaults to pkg.version when omitted)
44
45
  * }} params
45
46
  * @returns {Promise<{ updated: string[], added: string[], removed: string[], conflicts: string[], unchanged: string[], upToDate: boolean }>}
46
47
  */
@@ -49,7 +50,8 @@ export async function runUpdate(params = {}) {
49
50
  sourceDir = resolveSourceDir(),
50
51
  targetDir = resolveTargetDir({ global: false }),
51
52
  manifestPath = resolveManifestPath({ global: false }),
52
- force = false
53
+ force = false,
54
+ version: explicitVersion
53
55
  } = params;
54
56
 
55
57
  // Read existing manifest
@@ -159,8 +161,10 @@ export async function runUpdate(params = {}) {
159
161
  delete newFiles[relPath];
160
162
  }
161
163
 
162
- // Update manifest with new file map
163
- updateManifest(manifestPath, newFiles, pkg.version);
164
+ // Update manifest with new file map.
165
+ // Use explicitVersion when provided (e.g. release.version from updateAction);
166
+ // fall back to pkg.version for direct runUpdate calls or main-branch fallback downloads.
167
+ updateManifest(manifestPath, newFiles, explicitVersion ?? pkg.version);
164
168
 
165
169
  return {
166
170
  updated: diff.updated,
@@ -190,16 +194,18 @@ export async function updateAction(options = {}, deps = {}) {
190
194
  readStoredToken: readToken = readStoredToken,
191
195
  fetchLatestRelease: fetchRelease = fetchLatestRelease,
192
196
  compareVersions: cmpVersions = compareVersions,
193
- promptUser = defaultPromptUser
197
+ promptUser = defaultPromptUser,
198
+ // Injectable for tests — allows overriding resolved paths without touching CWD
199
+ manifestPath: injectedManifestPath
194
200
  } = deps;
195
201
 
196
- // Read local package version for comparison
202
+ // Read local package version (used as fallback when manifest has no version)
197
203
  const pkg = JSON.parse(
198
204
  readFileSync(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8')
199
205
  );
200
206
 
201
207
  const targetDir = resolveTargetDir(options);
202
- const manifestPath = resolveManifestPath(options);
208
+ const manifestPath = injectedManifestPath ?? resolveManifestPath(options);
203
209
 
204
210
  let tempDir = null;
205
211
  try {
@@ -220,7 +226,20 @@ export async function updateAction(options = {}, deps = {}) {
220
226
  process.stdout.write('Checking for updates...\n');
221
227
  const release = await fetchRelease(token);
222
228
 
229
+ // Read installed version from manifest (more accurate than pkg.version which reflects
230
+ // the CLI package, not the downloaded kit files). Fall back to pkg.version if no manifest.
231
+ let installedVersion = pkg.version;
232
+ if (existsSync(manifestPath)) {
233
+ try {
234
+ const existingManifest = readManifest(manifestPath);
235
+ if (existingManifest?.version) installedVersion = existingManifest.version;
236
+ } catch {
237
+ // Manifest unreadable — proceed with pkg.version fallback
238
+ }
239
+ }
240
+
223
241
  let downloadUrl; // undefined = use default (main branch)
242
+ let releaseVersion; // set when downloading from a specific release tarball
224
243
 
225
244
  if (!release.available) {
226
245
  // No release found or API error — warn and fall back to main branch
@@ -228,7 +247,7 @@ export async function updateAction(options = {}, deps = {}) {
228
247
  chalk.yellow(`Warning: Could not check latest release (${release.reason}). Falling back to main branch.\n`)
229
248
  );
230
249
  } else {
231
- const { needsUpdate, local, remote } = cmpVersions(pkg.version, release.version);
250
+ const { needsUpdate, local, remote } = cmpVersions(installedVersion, release.version);
232
251
 
233
252
  if (!needsUpdate) {
234
253
  process.stdout.write(chalk.green(`Already up to date (v${local}).\n`));
@@ -256,6 +275,7 @@ export async function updateAction(options = {}, deps = {}) {
256
275
  }
257
276
 
258
277
  downloadUrl = release.tarballUrl;
278
+ releaseVersion = release.version;
259
279
  }
260
280
 
261
281
  // --- Download and apply ---
@@ -263,7 +283,10 @@ export async function updateAction(options = {}, deps = {}) {
263
283
  tempDir = await download(token, downloadUrl !== undefined ? { url: downloadUrl } : {});
264
284
  const sourceDir = join(tempDir, '.claude');
265
285
 
266
- const result = await runUpdate({ sourceDir, targetDir, manifestPath, force: options.force });
286
+ // Pass releaseVersion so runUpdate stores it in the manifest.
287
+ // When falling back to main branch (no release), releaseVersion is undefined and
288
+ // runUpdate falls back to pkg.version — which is the correct behaviour for that path.
289
+ const result = await runUpdate({ sourceDir, targetDir, manifestPath, force: options.force, version: releaseVersion });
267
290
 
268
291
  if (result.upToDate) {
269
292
  process.stdout.write(chalk.green('Already up to date.\n'));