@khanhcan148/mk 0.1.4 → 0.1.6

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.6",
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
@@ -96,13 +98,19 @@ export async function runUpdate(params = {}) {
96
98
  diff.removed.length === 0 &&
97
99
  diff.conflicts.length === 0;
98
100
 
101
+ // Read package version (fileURLToPath handles Windows drive-letter prefix correctly)
102
+ const pkg = JSON.parse(readFileSync(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8'));
103
+
99
104
  if (upToDate) {
105
+ // Files are unchanged but we may still need to record the release version.
106
+ // Without this, manifest.version stays at the old value and the next `mk update`
107
+ // will always report "Update available" even though nothing changed on disk.
108
+ if (explicitVersion && explicitVersion !== manifest.version) {
109
+ updateManifest(manifestPath, manifest.files, explicitVersion);
110
+ }
100
111
  return { ...diff, upToDate: true };
101
112
  }
102
113
 
103
- // Read package version (fileURLToPath handles Windows drive-letter prefix correctly)
104
- const pkg = JSON.parse(readFileSync(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8'));
105
-
106
114
  const newFiles = { ...manifest.files };
107
115
 
108
116
  /**
@@ -159,8 +167,10 @@ export async function runUpdate(params = {}) {
159
167
  delete newFiles[relPath];
160
168
  }
161
169
 
162
- // Update manifest with new file map
163
- updateManifest(manifestPath, newFiles, pkg.version);
170
+ // Update manifest with new file map.
171
+ // Use explicitVersion when provided (e.g. release.version from updateAction);
172
+ // fall back to pkg.version for direct runUpdate calls or main-branch fallback downloads.
173
+ updateManifest(manifestPath, newFiles, explicitVersion ?? pkg.version);
164
174
 
165
175
  return {
166
176
  updated: diff.updated,
@@ -190,16 +200,18 @@ export async function updateAction(options = {}, deps = {}) {
190
200
  readStoredToken: readToken = readStoredToken,
191
201
  fetchLatestRelease: fetchRelease = fetchLatestRelease,
192
202
  compareVersions: cmpVersions = compareVersions,
193
- promptUser = defaultPromptUser
203
+ promptUser = defaultPromptUser,
204
+ // Injectable for tests — allows overriding resolved paths without touching CWD
205
+ manifestPath: injectedManifestPath
194
206
  } = deps;
195
207
 
196
- // Read local package version for comparison
208
+ // Read local package version (used as fallback when manifest has no version)
197
209
  const pkg = JSON.parse(
198
210
  readFileSync(fileURLToPath(new URL('../../package.json', import.meta.url)), 'utf8')
199
211
  );
200
212
 
201
213
  const targetDir = resolveTargetDir(options);
202
- const manifestPath = resolveManifestPath(options);
214
+ const manifestPath = injectedManifestPath ?? resolveManifestPath(options);
203
215
 
204
216
  let tempDir = null;
205
217
  try {
@@ -220,7 +232,20 @@ export async function updateAction(options = {}, deps = {}) {
220
232
  process.stdout.write('Checking for updates...\n');
221
233
  const release = await fetchRelease(token);
222
234
 
235
+ // Read installed version from manifest (more accurate than pkg.version which reflects
236
+ // the CLI package, not the downloaded kit files). Fall back to pkg.version if no manifest.
237
+ let installedVersion = pkg.version;
238
+ if (existsSync(manifestPath)) {
239
+ try {
240
+ const existingManifest = readManifest(manifestPath);
241
+ if (existingManifest?.version) installedVersion = existingManifest.version;
242
+ } catch {
243
+ // Manifest unreadable — proceed with pkg.version fallback
244
+ }
245
+ }
246
+
223
247
  let downloadUrl; // undefined = use default (main branch)
248
+ let releaseVersion; // set when downloading from a specific release tarball
224
249
 
225
250
  if (!release.available) {
226
251
  // No release found or API error — warn and fall back to main branch
@@ -228,7 +253,7 @@ export async function updateAction(options = {}, deps = {}) {
228
253
  chalk.yellow(`Warning: Could not check latest release (${release.reason}). Falling back to main branch.\n`)
229
254
  );
230
255
  } else {
231
- const { needsUpdate, local, remote } = cmpVersions(pkg.version, release.version);
256
+ const { needsUpdate, local, remote } = cmpVersions(installedVersion, release.version);
232
257
 
233
258
  if (!needsUpdate) {
234
259
  process.stdout.write(chalk.green(`Already up to date (v${local}).\n`));
@@ -256,6 +281,7 @@ export async function updateAction(options = {}, deps = {}) {
256
281
  }
257
282
 
258
283
  downloadUrl = release.tarballUrl;
284
+ releaseVersion = release.version;
259
285
  }
260
286
 
261
287
  // --- Download and apply ---
@@ -263,7 +289,10 @@ export async function updateAction(options = {}, deps = {}) {
263
289
  tempDir = await download(token, downloadUrl !== undefined ? { url: downloadUrl } : {});
264
290
  const sourceDir = join(tempDir, '.claude');
265
291
 
266
- const result = await runUpdate({ sourceDir, targetDir, manifestPath, force: options.force });
292
+ // Pass releaseVersion so runUpdate stores it in the manifest.
293
+ // When falling back to main branch (no release), releaseVersion is undefined and
294
+ // runUpdate falls back to pkg.version — which is the correct behaviour for that path.
295
+ const result = await runUpdate({ sourceDir, targetDir, manifestPath, force: options.force, version: releaseVersion });
267
296
 
268
297
  if (result.upToDate) {
269
298
  process.stdout.write(chalk.green('Already up to date.\n'));