@mushi-mushi/cli 0.4.0 → 0.5.1

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/dist/init.js CHANGED
@@ -5,21 +5,27 @@ import {
5
5
  envVarsToWrite,
6
6
  installCommand,
7
7
  readPackageJson
8
- } from "./chunk-YZOGONU4.js";
8
+ } from "./chunk-ZZNVMBMG.js";
9
+ import {
10
+ MUSHI_CLI_VERSION
11
+ } from "./chunk-HLROA5KU.js";
9
12
 
10
13
  // src/init.ts
11
14
  import * as p from "@clack/prompts";
12
15
  import { spawn } from "child_process";
13
- import { appendFileSync, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
14
- import { join as join2 } from "path";
16
+ import { randomUUID } from "crypto";
17
+ import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
18
+ import { join as join3 } from "path";
15
19
 
16
20
  // src/config.ts
17
- import { readFileSync, writeFileSync, existsSync } from "fs";
21
+ import { chmodSync, readFileSync, statSync, writeFileSync, existsSync } from "fs";
18
22
  import { join } from "path";
19
23
  import { homedir } from "os";
20
24
  var CONFIG_PATH = join(homedir(), ".mushirc");
25
+ var SECURE_FILE_MODE = 384;
21
26
  function loadConfig(path = CONFIG_PATH) {
22
27
  if (!existsSync(path)) return {};
28
+ tightenPermissions(path);
23
29
  try {
24
30
  return JSON.parse(readFileSync(path, "utf-8"));
25
31
  } catch {
@@ -27,14 +33,193 @@ function loadConfig(path = CONFIG_PATH) {
27
33
  }
28
34
  }
29
35
  function saveConfig(config, path = CONFIG_PATH) {
30
- writeFileSync(path, JSON.stringify(config, null, 2));
36
+ writeFileSync(path, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
37
+ tightenPermissions(path);
38
+ }
39
+ function tightenPermissions(path) {
40
+ if (process.platform === "win32") return;
41
+ try {
42
+ const current = statSync(path).mode & 511;
43
+ if (current !== SECURE_FILE_MODE) chmodSync(path, SECURE_FILE_MODE);
44
+ } catch {
45
+ }
46
+ }
47
+
48
+ // src/endpoint.ts
49
+ var DEFAULT_ENDPOINT = "https://api.mushimushi.dev";
50
+ var TEST_REPORT_TIMEOUT_MS = 1e4;
51
+ var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
52
+ function normalizeEndpoint(url) {
53
+ const input = url ?? DEFAULT_ENDPOINT;
54
+ let end = input.length;
55
+ while (end > 0 && input.charCodeAt(end - 1) === 47) end--;
56
+ return input.slice(0, end);
57
+ }
58
+
59
+ // src/freshness.ts
60
+ var REGISTRY = "https://registry.npmjs.org";
61
+ var DEFAULT_TIMEOUT_MS = 2e3;
62
+ async function checkFreshness(packageName, currentVersion, opts = {}) {
63
+ if (process.env.MUSHI_NO_UPDATE_CHECK === "1") return null;
64
+ const registry = opts.registry ?? REGISTRY;
65
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
66
+ const controller = new AbortController();
67
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
68
+ try {
69
+ const res = await fetch(
70
+ `${registry}/${encodeURIComponent(packageName)}/latest`,
71
+ {
72
+ signal: controller.signal,
73
+ headers: { Accept: "application/json" }
74
+ }
75
+ );
76
+ if (!res.ok) return null;
77
+ const body = await res.json();
78
+ const latest = typeof body.version === "string" ? body.version : null;
79
+ if (!latest) return null;
80
+ return {
81
+ current: currentVersion,
82
+ latest,
83
+ isOutdated: isNewerStableVersion(latest, currentVersion)
84
+ };
85
+ } catch {
86
+ return null;
87
+ } finally {
88
+ clearTimeout(timer);
89
+ }
90
+ }
91
+ function isNewerStableVersion(latest, current) {
92
+ const latestCore = stripPreRelease(latest);
93
+ if (hasPreReleaseTag(latest)) return false;
94
+ const [la, lb, lc] = parse(latestCore);
95
+ const [ca, cb, cc] = parse(stripPreRelease(current));
96
+ if (la !== ca) return la > ca;
97
+ if (lb !== cb) return lb > cb;
98
+ return lc > cc;
99
+ }
100
+ function stripPreRelease(version) {
101
+ const idx = version.indexOf("-");
102
+ return idx === -1 ? version : version.slice(0, idx);
103
+ }
104
+ function hasPreReleaseTag(version) {
105
+ return version.includes("-");
106
+ }
107
+ function parse(version) {
108
+ const parts = version.split(".").map((part) => Number(part));
109
+ return [
110
+ Number.isFinite(parts[0]) ? parts[0] : 0,
111
+ Number.isFinite(parts[1]) ? parts[1] : 0,
112
+ Number.isFinite(parts[2]) ? parts[2] : 0
113
+ ];
114
+ }
115
+
116
+ // src/monorepo.ts
117
+ import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync as statSync2 } from "fs";
118
+ import { dirname, join as join2, resolve } from "path";
119
+ var WORKSPACE_GLOB_CANDIDATES = ["apps/*", "packages/*", "examples/*"];
120
+ var FRAMEWORK_DEPS = {
121
+ next: "Next.js",
122
+ nuxt: "Nuxt",
123
+ "@sveltejs/kit": "SvelteKit",
124
+ "@angular/core": "Angular",
125
+ expo: "Expo",
126
+ "react-native": "React Native",
127
+ "@capacitor/core": "Capacitor",
128
+ svelte: "Svelte",
129
+ vue: "Vue",
130
+ react: "React"
131
+ };
132
+ function detectWorkspaceHint(cwd) {
133
+ const root = findWorkspaceRoot(cwd);
134
+ if (!root) return null;
135
+ const rootPkg = readPackageJsonSafely(join2(root, "package.json"));
136
+ if (rootPkg && getFrameworkFromPkg(rootPkg)) return null;
137
+ const source = existsSync2(join2(root, "pnpm-workspace.yaml")) ? "pnpm-workspace" : root === cwd ? "package-json" : "parent";
138
+ const apps = collectAppsFromGlobs(root);
139
+ if (apps.length === 0) return null;
140
+ return { root, apps, source };
141
+ }
142
+ function findWorkspaceRoot(start) {
143
+ let dir = resolve(start);
144
+ for (let i = 0; i < 8; i++) {
145
+ if (isWorkspaceRoot(dir)) return dir;
146
+ const parent = dirname(dir);
147
+ if (parent === dir) break;
148
+ dir = parent;
149
+ }
150
+ return null;
151
+ }
152
+ function isWorkspaceRoot(dir) {
153
+ if (existsSync2(join2(dir, "pnpm-workspace.yaml"))) return true;
154
+ const pkg = readPackageJsonSafely(join2(dir, "package.json"));
155
+ if (!pkg) return false;
156
+ return Boolean(pkg.workspaces);
157
+ }
158
+ function collectAppsFromGlobs(root) {
159
+ const results = [];
160
+ for (const glob of WORKSPACE_GLOB_CANDIDATES) {
161
+ const prefix = glob.replace("/*", "");
162
+ const parentDir = join2(root, prefix);
163
+ if (!existsSync2(parentDir)) continue;
164
+ let entries;
165
+ try {
166
+ entries = readdirSync(parentDir);
167
+ } catch {
168
+ continue;
169
+ }
170
+ for (const entry of entries) {
171
+ const pkgPath = join2(parentDir, entry, "package.json");
172
+ if (!isFileSafe(pkgPath)) continue;
173
+ const pkg = readPackageJsonSafely(pkgPath);
174
+ if (!pkg) continue;
175
+ const framework = getFrameworkFromPkg(pkg);
176
+ if (!framework) continue;
177
+ results.push({
178
+ name: pkg.name ?? `${prefix}/${entry}`,
179
+ relativePath: `${prefix}/${entry}`,
180
+ framework
181
+ });
182
+ }
183
+ }
184
+ return results;
185
+ }
186
+ function readPackageJsonSafely(path) {
187
+ if (!isFileSafe(path)) return null;
188
+ try {
189
+ return JSON.parse(readFileSync2(path, "utf-8"));
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+ function isFileSafe(path) {
195
+ try {
196
+ return existsSync2(path) && statSync2(path).isFile();
197
+ } catch {
198
+ return false;
199
+ }
200
+ }
201
+ function getFrameworkFromPkg(pkg) {
202
+ const deps = {
203
+ ...pkg.dependencies ?? {},
204
+ ...pkg.devDependencies ?? {},
205
+ ...pkg.peerDependencies ?? {}
206
+ };
207
+ for (const dep of Object.keys(FRAMEWORK_DEPS)) {
208
+ if (dep in deps) return FRAMEWORK_DEPS[dep];
209
+ }
210
+ return void 0;
31
211
  }
32
212
 
33
213
  // src/init.ts
34
214
  var ENV_FILES = [".env.local", ".env"];
215
+ var PROJECT_ID_PATTERN = /^proj_[A-Za-z0-9_-]{10,}$/;
216
+ var API_KEY_PATTERN = /^mushi_[A-Za-z0-9_-]{10,}$/;
35
217
  async function runInit(options = {}) {
36
218
  const cwd = options.cwd ?? process.cwd();
219
+ ensureInteractiveOrBailOut(options);
37
220
  p.intro("\u{1F41B} Mushi Mushi setup wizard");
221
+ await printFreshnessHint();
222
+ warnIfWorkspaceRoot(cwd);
38
223
  const pkg = readPackageJson(cwd);
39
224
  if (!pkg) {
40
225
  p.log.warn("No package.json found in this directory.");
@@ -53,15 +238,28 @@ async function runInit(options = {}) {
53
238
  const pm = detectPackageManager(cwd);
54
239
  const packagesToInstall = framework.needsWebPackage ? [framework.packageName, "@mushi-mushi/web"] : [framework.packageName];
55
240
  if (!options.skipInstall) {
56
- await installPackages(pm, packagesToInstall);
241
+ await installPackages(pm, packagesToInstall, cwd);
57
242
  } else {
58
243
  p.log.info(`Skipped install. Run \`${installCommand(pm, packagesToInstall)}\` yourself.`);
59
244
  }
60
245
  writeEnvFile(cwd, credentials.apiKey, credentials.projectId, framework);
61
246
  persistCliConfig(credentials.apiKey, credentials.projectId);
62
247
  printNextSteps(framework, credentials.apiKey, credentials.projectId);
248
+ await maybeSendTestReport(credentials, options);
63
249
  p.outro("Setup complete. Happy bug squashing \u{1F41B}");
64
250
  }
251
+ function ensureInteractiveOrBailOut(options) {
252
+ const isTTY = Boolean(process.stdin.isTTY && process.stdout.isTTY);
253
+ if (isTTY) return;
254
+ const hasAllFlags = Boolean(
255
+ (options.framework || options.yes) && options.projectId && options.apiKey
256
+ );
257
+ if (hasAllFlags) return;
258
+ process.stderr.write(
259
+ "mushi-mushi: non-interactive terminal detected.\nPass all of --yes (or --framework), --project-id, and --api-key to run unattended.\nExample: npx mushi-mushi --yes --project-id proj_xxx --api-key mushi_xxx\n"
260
+ );
261
+ process.exit(1);
262
+ }
65
263
  async function chooseFramework(detected, options) {
66
264
  if (options.framework) {
67
265
  const explicit = FRAMEWORKS[options.framework];
@@ -90,23 +288,48 @@ async function chooseFramework(detected, options) {
90
288
  }
91
289
  async function collectCredentials(options) {
92
290
  const existing = loadConfig();
93
- const projectId = options.projectId ?? existing.projectId ?? await promptText({
291
+ const rawProjectId = options.projectId ?? existing.projectId ?? await promptText({
94
292
  message: "Project ID",
95
293
  placeholder: "proj_xxxxxxxxxxxx",
96
- hint: "Find this at https://kensaur.us/mushi-mushi/projects"
294
+ hint: "Find this at https://kensaur.us/mushi-mushi/projects",
295
+ validate: (v) => PROJECT_ID_PATTERN.test(v) ? void 0 : "Expected format: proj_ followed by 10+ alphanumeric characters"
97
296
  });
98
- const apiKey = options.apiKey ?? existing.apiKey ?? await promptText({
297
+ const rawApiKey = options.apiKey ?? existing.apiKey ?? await promptText({
99
298
  message: "API key",
100
299
  placeholder: "mushi_xxxxxxxxxxxx",
101
- hint: "Treat this like a password \u2014 it goes in your env file, not in source."
300
+ hint: "Treat this like a password \u2014 it goes in your env file, not in source.",
301
+ validate: (v) => API_KEY_PATTERN.test(v) ? void 0 : "Expected format: mushi_ followed by 10+ alphanumeric characters"
102
302
  });
303
+ const projectId = sanitizeSecret(rawProjectId);
304
+ const apiKey = sanitizeSecret(rawApiKey);
305
+ if (!PROJECT_ID_PATTERN.test(projectId)) {
306
+ throw new Error(
307
+ `Invalid project ID. Expected format: proj_[A-Za-z0-9_-]{10,}. Got: ${redact(projectId)}`
308
+ );
309
+ }
310
+ if (!API_KEY_PATTERN.test(apiKey)) {
311
+ throw new Error(
312
+ `Invalid API key. Expected format: mushi_[A-Za-z0-9_-]{10,}. Got: ${redact(apiKey)}`
313
+ );
314
+ }
103
315
  return { projectId, apiKey };
104
316
  }
317
+ function sanitizeSecret(raw) {
318
+ return raw.trim().replace(/^['"]|['"]$/g, "").replace(/[\r\n\0]/g, "");
319
+ }
320
+ function redact(value) {
321
+ if (value.length <= 8) return "***";
322
+ return `${value.slice(0, 4)}\u2026${value.slice(-2)}`;
323
+ }
105
324
  async function promptText(opts) {
106
325
  const value = await p.text({
107
326
  message: opts.message,
108
327
  placeholder: opts.placeholder,
109
- validate: (v) => v.length === 0 ? "Required" : void 0
328
+ validate: (v) => {
329
+ const clean = sanitizeSecret(v);
330
+ if (clean.length === 0) return "Required";
331
+ return opts.validate ? opts.validate(clean) : void 0;
332
+ }
110
333
  });
111
334
  if (p.isCancel(value)) {
112
335
  p.cancel("Aborted.");
@@ -115,34 +338,40 @@ async function promptText(opts) {
115
338
  if (opts.hint) p.log.info(opts.hint);
116
339
  return value;
117
340
  }
118
- async function installPackages(pm, packages) {
341
+ async function installPackages(pm, packages, cwd) {
119
342
  const command = installCommand(pm, packages);
120
343
  const spinner2 = p.spinner();
121
344
  spinner2.start(`Installing ${packages.join(", ")} via ${pm}\u2026`);
122
345
  try {
123
- await runCommand(pm, packages);
346
+ await runCommand(pm, packages, cwd);
124
347
  spinner2.stop(`Installed ${packages.join(", ")}`);
125
348
  } catch (err) {
126
349
  spinner2.stop(`Install failed \u2014 run \`${command}\` manually.`);
127
- p.log.error(err instanceof Error ? err.message : String(err));
350
+ p.log.error(err instanceof Error ? err.name + ": " + err.message : String(err));
128
351
  }
129
352
  }
130
- function runCommand(pm, packages) {
131
- return new Promise((resolve, reject) => {
132
- const verb = pm === "npm" ? "install" : "add";
133
- const child = spawn(pm, [verb, ...packages], { stdio: "inherit", shell: true });
353
+ function runCommand(pm, packages, cwd) {
354
+ const verb = pm === "npm" ? "install" : "add";
355
+ const command = process.platform === "win32" ? `${pm}.cmd` : pm;
356
+ return new Promise((resolve2, reject) => {
357
+ const child = spawn(command, [verb, ...packages], {
358
+ stdio: "inherit",
359
+ shell: false,
360
+ cwd,
361
+ env: process.env
362
+ });
134
363
  child.on("error", reject);
135
364
  child.on("exit", (code) => {
136
- if (code === 0) resolve();
137
- else reject(new Error(`${pm} exited with code ${code}`));
365
+ if (code === 0) resolve2();
366
+ else reject(new Error(`${pm} exited with code ${code ?? "null"}`));
138
367
  });
139
368
  });
140
369
  }
141
370
  function writeEnvFile(cwd, apiKey, projectId, framework) {
142
- const target = ENV_FILES.find((f) => existsSync2(join2(cwd, f))) ?? ENV_FILES[0];
143
- const targetPath = join2(cwd, target);
371
+ const target = ENV_FILES.find((f) => existsSync3(join3(cwd, f))) ?? ENV_FILES[0];
372
+ const targetPath = join3(cwd, target);
144
373
  const newVars = envVarsToWrite(apiKey, projectId, framework);
145
- const existing = existsSync2(targetPath) ? readFileSync2(targetPath, "utf-8") : "";
374
+ const existing = existsSync3(targetPath) ? readFileSync3(targetPath, "utf-8") : "";
146
375
  if (existing.includes("MUSHI_PROJECT_ID")) {
147
376
  p.log.warn(`Existing MUSHI_* vars found in ${target} \u2014 leaving them untouched.`);
148
377
  return;
@@ -152,20 +381,38 @@ function writeEnvFile(cwd, apiKey, projectId, framework) {
152
381
  # Mushi Mushi
153
382
  ${newVars}
154
383
  `);
155
- if (!existing) {
156
- writeFileSync2(targetPath, readFileSync2(targetPath, "utf-8"));
157
- }
158
384
  p.log.success(`Wrote env vars to ${target}`);
159
385
  warnIfMissingFromGitignore(cwd, target);
160
386
  }
387
+ function isEnvFileCoveredByGitignore(gitignoreContent, envFile) {
388
+ const lines = gitignoreContent.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && !line.startsWith("#"));
389
+ let covered = false;
390
+ for (const line of lines) {
391
+ if (line.startsWith("!")) {
392
+ if (matchesGitignorePattern(line.slice(1), envFile)) covered = false;
393
+ continue;
394
+ }
395
+ if (matchesGitignorePattern(line, envFile)) covered = true;
396
+ }
397
+ return covered;
398
+ }
399
+ function matchesGitignorePattern(pattern, filename) {
400
+ if (pattern.endsWith("/")) return false;
401
+ const normalized = pattern.startsWith("/") ? pattern.slice(1) : pattern;
402
+ const regexSource = normalized.split("").map((ch) => ch === "*" ? "[^/]*" : escapeRegexChar(ch)).join("");
403
+ return new RegExp(`^${regexSource}$`).test(filename);
404
+ }
405
+ function escapeRegexChar(ch) {
406
+ return /[-/\\^$+?.()|[\]{}]/.test(ch) ? `\\${ch}` : ch;
407
+ }
161
408
  function warnIfMissingFromGitignore(cwd, envFile) {
162
- const gitignorePath = join2(cwd, ".gitignore");
163
- if (!existsSync2(gitignorePath)) {
409
+ const gitignorePath = join3(cwd, ".gitignore");
410
+ if (!existsSync3(gitignorePath)) {
164
411
  p.log.warn(`No .gitignore found \u2014 make sure ${envFile} is not committed.`);
165
412
  return;
166
413
  }
167
- const content = readFileSync2(gitignorePath, "utf-8");
168
- if (!content.split("\n").some((line) => line.trim() === envFile || line.trim() === ".env*")) {
414
+ const content = readFileSync3(gitignorePath, "utf-8");
415
+ if (!isEnvFileCoveredByGitignore(content, envFile)) {
169
416
  p.log.warn(`${envFile} is not in .gitignore \u2014 add it before committing.`);
170
417
  }
171
418
  }
@@ -180,6 +427,103 @@ function printNextSteps(framework, apiKey, projectId) {
180
427
  p.log.message(" \u2022 Look for the \u{1F41B} button in the bottom-right corner (or shake on mobile)");
181
428
  p.log.message(" \u2022 Submit a test report \u2014 it should appear at https://kensaur.us/mushi-mushi/reports");
182
429
  }
430
+ async function maybeSendTestReport(credentials, options) {
431
+ if (options.sendTestReport === false) return;
432
+ let shouldSend;
433
+ if (options.sendTestReport === true || options.yes) {
434
+ shouldSend = true;
435
+ } else {
436
+ const answer = await p.confirm({
437
+ message: "Send a test report now to verify the pipeline?",
438
+ initialValue: true
439
+ });
440
+ if (p.isCancel(answer)) return;
441
+ shouldSend = answer;
442
+ }
443
+ if (!shouldSend) return;
444
+ const spinner2 = p.spinner();
445
+ spinner2.start("Sending test report\u2026");
446
+ const endpoint = normalizeEndpoint(options.endpoint);
447
+ const controller = new AbortController();
448
+ const timer = setTimeout(() => controller.abort(), TEST_REPORT_FETCH_TIMEOUT_MS);
449
+ try {
450
+ const res = await fetch(`${endpoint}/v1/reports`, {
451
+ method: "POST",
452
+ signal: controller.signal,
453
+ headers: {
454
+ "Content-Type": "application/json",
455
+ "X-Mushi-Api-Key": credentials.apiKey,
456
+ "X-Mushi-Project": credentials.projectId
457
+ },
458
+ body: JSON.stringify({
459
+ projectId: credentials.projectId,
460
+ description: "Test report from the mushi-mushi setup wizard",
461
+ category: "other",
462
+ reporterToken: `wizard-${randomUUID()}`,
463
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
464
+ environment: {
465
+ url: "cli://wizard",
466
+ userAgent: `mushi-wizard/${process.platform}-${process.arch}`,
467
+ platform: process.platform,
468
+ language: "en",
469
+ viewport: { width: 0, height: 0 },
470
+ referrer: "",
471
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
472
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
473
+ }
474
+ })
475
+ });
476
+ if (!res.ok) {
477
+ spinner2.stop(`Test report rejected (HTTP ${res.status}).`);
478
+ p.log.warn(
479
+ res.status === 401 || res.status === 403 ? "Credentials did not authenticate \u2014 double-check the project ID and API key." : "Skipping test report. You can retry with `mushi test`."
480
+ );
481
+ return;
482
+ }
483
+ spinner2.stop("Test report sent.");
484
+ p.log.success("View it at https://kensaur.us/mushi-mushi/reports");
485
+ } catch (err) {
486
+ const aborted = err instanceof Error && err.name === "AbortError";
487
+ spinner2.stop(aborted ? "Timed out reaching the Mushi API." : "Could not reach the Mushi API.");
488
+ p.log.warn(err instanceof Error ? err.message : String(err));
489
+ } finally {
490
+ clearTimeout(timer);
491
+ }
492
+ }
493
+ async function printFreshnessHint() {
494
+ const result = await checkFreshness("mushi-mushi", MUSHI_CLI_VERSION);
495
+ if (!result || !result.isOutdated) return;
496
+ p.log.info(
497
+ `A newer version of mushi-mushi is available: ${result.current} \u2192 ${result.latest}. Run \`npx mushi-mushi@latest\` to get the freshest wizard.`
498
+ );
499
+ }
500
+ function warnIfWorkspaceRoot(cwd) {
501
+ let hint;
502
+ try {
503
+ hint = detectWorkspaceHint(cwd);
504
+ } catch {
505
+ return;
506
+ }
507
+ if (!hint || hint.apps.length === 0) return;
508
+ const hasFrameworkAtCwd = hint.apps.some(
509
+ (app) => isSameDirectory(cwd, resolveWorkspaceAppPath(hint.root, app.relativePath))
510
+ );
511
+ if (hasFrameworkAtCwd) return;
512
+ const apps = hint.apps.slice(0, 5).map((app) => ` \u2022 ${app.relativePath} (${app.framework})`).join("\n");
513
+ p.log.warn(
514
+ `You appear to be at a workspace root (source: ${hint.source}). Mushi will install into the current directory, which has no framework dep. You probably meant one of these sub-packages:
515
+ ${apps}
516
+ Run \`mushi init --cwd <path>\` \u2014 or re-run the wizard from inside that package.`
517
+ );
518
+ }
519
+ function resolveWorkspaceAppPath(root, relativePath) {
520
+ return `${root}/${relativePath}`.replace(/\\/g, "/");
521
+ }
522
+ function isSameDirectory(a, b) {
523
+ return a.replace(/\\/g, "/").replace(/\/+$/, "") === b.replace(/\\/g, "/").replace(/\/+$/, "");
524
+ }
183
525
  export {
184
- runInit
526
+ isEnvFileCoveredByGitignore,
527
+ runInit,
528
+ sanitizeSecret
185
529
  };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * FILE: packages/cli/src/version.ts
3
+ * PURPOSE: Single source of truth for the CLI version at runtime.
4
+ *
5
+ * The bundler (tsup) replaces `__MUSHI_CLI_VERSION__` with the literal from
6
+ * `package.json` at build time. Falling back to `'0.0.0-dev'` keeps
7
+ * `tsc --noEmit` happy during development.
8
+ */
9
+ declare const MUSHI_CLI_VERSION: string;
10
+
11
+ export { MUSHI_CLI_VERSION };
@@ -0,0 +1,6 @@
1
+ import {
2
+ MUSHI_CLI_VERSION
3
+ } from "./chunk-HLROA5KU.js";
4
+ export {
5
+ MUSHI_CLI_VERSION
6
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/cli",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "license": "MIT",
5
5
  "description": "CLI for Mushi Mushi — `mushi init` wizard installs the right SDK for your framework, plus report triage and pipeline health commands",
6
6
  "bin": {
@@ -28,13 +28,21 @@
28
28
  "bugs": {
29
29
  "url": "https://github.com/kensaurus/mushi-mushi/issues"
30
30
  },
31
+ "funding": {
32
+ "type": "github",
33
+ "url": "https://github.com/sponsors/kensaurus"
34
+ },
31
35
  "publishConfig": {
32
- "access": "public"
36
+ "access": "public",
37
+ "provenance": true
33
38
  },
34
39
  "files": [
35
40
  "dist",
36
41
  "README.md",
37
- "LICENSE"
42
+ "LICENSE",
43
+ "CONTRIBUTING.md",
44
+ "CODE_OF_CONDUCT.md",
45
+ "SECURITY.md"
38
46
  ],
39
47
  "sideEffects": false,
40
48
  "keywords": [
@@ -78,6 +86,12 @@
78
86
  "types": "./dist/detect.d.ts",
79
87
  "default": "./dist/detect.js"
80
88
  }
89
+ },
90
+ "./version": {
91
+ "import": {
92
+ "types": "./dist/version.d.ts",
93
+ "default": "./dist/version.js"
94
+ }
81
95
  }
82
96
  },
83
97
  "type": "module",