@mushi-mushi/cli 0.6.1 → 0.8.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.
- package/CONTRIBUTING.md +27 -0
- package/README.md +409 -59
- package/SECURITY.md +167 -4
- package/dist/chunk-KUQZQFOL.js +6 -0
- package/dist/index.js +867 -167
- package/dist/init.js +70 -23
- package/dist/version.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-ZL3BTW32.js +0 -6
package/dist/init.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "./chunk-XHD3H54W.js";
|
|
9
9
|
import {
|
|
10
10
|
MUSHI_CLI_VERSION
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-KUQZQFOL.js";
|
|
12
12
|
|
|
13
13
|
// src/init.ts
|
|
14
14
|
import * as p from "@clack/prompts";
|
|
@@ -24,13 +24,20 @@ import { homedir } from "os";
|
|
|
24
24
|
var CONFIG_PATH = join(homedir(), ".mushirc");
|
|
25
25
|
var SECURE_FILE_MODE = 384;
|
|
26
26
|
function loadConfig(path = CONFIG_PATH) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
27
|
+
let file = {};
|
|
28
|
+
if (existsSync(path)) {
|
|
29
|
+
tightenPermissions(path);
|
|
30
|
+
try {
|
|
31
|
+
file = JSON.parse(readFileSync(path, "utf-8"));
|
|
32
|
+
} catch {
|
|
33
|
+
}
|
|
33
34
|
}
|
|
35
|
+
const fromEnv = {
|
|
36
|
+
...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
|
|
37
|
+
...process.env["MUSHI_PROJECT_ID"] ? { projectId: process.env["MUSHI_PROJECT_ID"] } : {},
|
|
38
|
+
...process.env["MUSHI_API_ENDPOINT"] ? { endpoint: process.env["MUSHI_API_ENDPOINT"] } : {}
|
|
39
|
+
};
|
|
40
|
+
return { ...file, ...fromEnv };
|
|
34
41
|
}
|
|
35
42
|
function saveConfig(config, path = CONFIG_PATH) {
|
|
36
43
|
writeFileSync(path, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
|
|
@@ -46,14 +53,17 @@ function tightenPermissions(path) {
|
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
// src/endpoint.ts
|
|
49
|
-
var DEFAULT_ENDPOINT = "https://api.mushimushi.dev";
|
|
50
56
|
var TEST_REPORT_TIMEOUT_MS = 1e4;
|
|
51
57
|
var TEST_REPORT_FETCH_TIMEOUT_MS = TEST_REPORT_TIMEOUT_MS;
|
|
52
58
|
function normalizeEndpoint(url) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
59
|
+
if (!url) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
"No API endpoint configured. Run `mushi init` or set MUSHI_API_ENDPOINT. Set endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api"
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
let end = url.length;
|
|
65
|
+
while (end > 0 && url.charCodeAt(end - 1) === 47) end--;
|
|
66
|
+
return url.slice(0, end);
|
|
57
67
|
}
|
|
58
68
|
|
|
59
69
|
// src/freshness.ts
|
|
@@ -212,7 +222,7 @@ function getFrameworkFromPkg(pkg) {
|
|
|
212
222
|
|
|
213
223
|
// src/init.ts
|
|
214
224
|
var ENV_FILES = [".env.local", ".env"];
|
|
215
|
-
var PROJECT_ID_PATTERN = /^proj_[A-Za-z0-9_-]{10,}
|
|
225
|
+
var PROJECT_ID_PATTERN = /^(?:proj_[A-Za-z0-9_-]{10,}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i;
|
|
216
226
|
var API_KEY_PATTERN = /^mushi_[A-Za-z0-9_-]{10,}$/;
|
|
217
227
|
async function runInit(options = {}) {
|
|
218
228
|
const cwd = options.cwd ?? process.cwd();
|
|
@@ -244,7 +254,8 @@ async function runInit(options = {}) {
|
|
|
244
254
|
}
|
|
245
255
|
writeEnvFile(cwd, credentials.apiKey, credentials.projectId, framework);
|
|
246
256
|
persistCliConfig(credentials.apiKey, credentials.projectId);
|
|
247
|
-
|
|
257
|
+
const enableRewards = await maybeEnableRewards(options);
|
|
258
|
+
printNextSteps(framework, credentials.apiKey, credentials.projectId, enableRewards);
|
|
248
259
|
await maybeSendTestReport(credentials, options);
|
|
249
260
|
p.outro("Setup complete. Happy bug squashing \u{1F41B}");
|
|
250
261
|
}
|
|
@@ -256,7 +267,7 @@ function ensureInteractiveOrBailOut(options) {
|
|
|
256
267
|
);
|
|
257
268
|
if (hasAllFlags) return;
|
|
258
269
|
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
|
|
270
|
+
"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 xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --api-key mushi_xxx\nYour project ID is the UUID shown in the Projects page of the Mushi admin console.\n"
|
|
260
271
|
);
|
|
261
272
|
process.exit(1);
|
|
262
273
|
}
|
|
@@ -290,21 +301,22 @@ async function collectCredentials(options) {
|
|
|
290
301
|
const existing = loadConfig();
|
|
291
302
|
const rawProjectId = options.projectId ?? existing.projectId ?? await promptText({
|
|
292
303
|
message: "Project ID",
|
|
293
|
-
placeholder: "
|
|
294
|
-
hint: "
|
|
295
|
-
validate: (v) => PROJECT_ID_PATTERN.test(v) ? void 0 : "Expected
|
|
304
|
+
placeholder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
305
|
+
hint: "Where to find it: https://kensaur.us/mushi-mushi/projects \u2192 click your project \u2192 copy the UUID below the project name.",
|
|
306
|
+
validate: (v) => PROJECT_ID_PATTERN.test(v) ? void 0 : "Expected a UUID (xxxxxxxx-xxxx-...) \u2014 copy it from the Mushi admin console Projects page."
|
|
296
307
|
});
|
|
297
308
|
const rawApiKey = options.apiKey ?? existing.apiKey ?? await promptText({
|
|
298
309
|
message: "API key",
|
|
299
310
|
placeholder: "mushi_xxxxxxxxxxxx",
|
|
300
|
-
hint: "Treat
|
|
311
|
+
hint: "Where to find it: https://kensaur.us/mushi-mushi/settings \u2192 API Keys tab. Treat it like a password \u2014 env file only, never commit it.",
|
|
301
312
|
validate: (v) => API_KEY_PATTERN.test(v) ? void 0 : "Expected format: mushi_ followed by 10+ alphanumeric characters"
|
|
302
313
|
});
|
|
303
314
|
const projectId = sanitizeSecret(rawProjectId);
|
|
304
315
|
const apiKey = sanitizeSecret(rawApiKey);
|
|
305
316
|
if (!PROJECT_ID_PATTERN.test(projectId)) {
|
|
306
317
|
throw new Error(
|
|
307
|
-
`Invalid project ID.
|
|
318
|
+
`Invalid project ID. Got: ${redact(projectId)}
|
|
319
|
+
Expected a UUID (e.g. 542b34e0-019e-41fe-b900-7b637717bb86) \u2014 copy it from the Projects page in the Mushi console at https://kensaur.us/mushi-mushi/projects`
|
|
308
320
|
);
|
|
309
321
|
}
|
|
310
322
|
if (!API_KEY_PATTERN.test(apiKey)) {
|
|
@@ -322,6 +334,7 @@ function redact(value) {
|
|
|
322
334
|
return `${value.slice(0, 4)}\u2026${value.slice(-2)}`;
|
|
323
335
|
}
|
|
324
336
|
async function promptText(opts) {
|
|
337
|
+
if (opts.hint) p.log.info(opts.hint);
|
|
325
338
|
const value = await p.text({
|
|
326
339
|
message: opts.message,
|
|
327
340
|
placeholder: opts.placeholder,
|
|
@@ -338,7 +351,6 @@ async function promptText(opts) {
|
|
|
338
351
|
p.cancel("Aborted.");
|
|
339
352
|
process.exit(0);
|
|
340
353
|
}
|
|
341
|
-
if (opts.hint) p.log.info(opts.hint);
|
|
342
354
|
return value;
|
|
343
355
|
}
|
|
344
356
|
async function installPackages(pm, packages, cwd) {
|
|
@@ -423,13 +435,34 @@ function persistCliConfig(apiKey, projectId) {
|
|
|
423
435
|
const existing = loadConfig();
|
|
424
436
|
saveConfig({ ...existing, apiKey, projectId });
|
|
425
437
|
}
|
|
426
|
-
function printNextSteps(framework, apiKey, projectId) {
|
|
438
|
+
function printNextSteps(framework, apiKey, projectId, enableRewards = false) {
|
|
427
439
|
p.note(framework.snippet(apiKey, projectId), "Add this to your app:");
|
|
440
|
+
if (enableRewards) {
|
|
441
|
+
const badgeSnippet = framework.id === "react" ? `// Add to your user menu or profile UI:
|
|
442
|
+
import { MushiRewardsBadge } from '@mushi-mushi/react';
|
|
443
|
+
|
|
444
|
+
// Inside your component:
|
|
445
|
+
<MushiRewardsBadge showPoints />` : `// Add to your user menu:
|
|
446
|
+
// import { MushiRewardsBadge } from '@mushi-mushi/react';
|
|
447
|
+
// <MushiRewardsBadge showPoints />`;
|
|
448
|
+
p.note(badgeSnippet, "Rewards badge snippet:");
|
|
449
|
+
p.log.info("Enable rewards in your project settings at https://kensaur.us/mushi-mushi/rewards");
|
|
450
|
+
p.log.info("Users will earn points for bug reports, screen navigation, and app activity.");
|
|
451
|
+
}
|
|
428
452
|
p.log.message("Verify the install:");
|
|
429
453
|
p.log.message(" \u2022 Start your dev server");
|
|
430
454
|
p.log.message(" \u2022 Look for the \u{1F41B} button in the bottom-right corner (or shake on mobile)");
|
|
431
455
|
p.log.message(" \u2022 Submit a test report \u2014 it should appear at https://kensaur.us/mushi-mushi/reports");
|
|
432
456
|
}
|
|
457
|
+
async function maybeEnableRewards(options) {
|
|
458
|
+
if (options.yes) return false;
|
|
459
|
+
const answer = await p.confirm({
|
|
460
|
+
message: "Enable Mushi Rewards? (users earn points for bug reports + app activity)",
|
|
461
|
+
initialValue: false
|
|
462
|
+
});
|
|
463
|
+
if (p.isCancel(answer)) return false;
|
|
464
|
+
return Boolean(answer);
|
|
465
|
+
}
|
|
433
466
|
async function maybeSendTestReport(credentials, options) {
|
|
434
467
|
if (options.sendTestReport === false) return;
|
|
435
468
|
let shouldSend;
|
|
@@ -444,6 +477,13 @@ async function maybeSendTestReport(credentials, options) {
|
|
|
444
477
|
shouldSend = answer;
|
|
445
478
|
}
|
|
446
479
|
if (!shouldSend) return;
|
|
480
|
+
if (!options.endpoint) {
|
|
481
|
+
p.note(
|
|
482
|
+
"No endpoint configured \u2014 skipping test report.\nSet endpoint to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api",
|
|
483
|
+
"Skipped"
|
|
484
|
+
);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
447
487
|
const spinner2 = p.spinner();
|
|
448
488
|
spinner2.start("Sending test report\u2026");
|
|
449
489
|
const endpoint = normalizeEndpoint(options.endpoint);
|
|
@@ -484,7 +524,14 @@ async function maybeSendTestReport(credentials, options) {
|
|
|
484
524
|
return;
|
|
485
525
|
}
|
|
486
526
|
spinner2.stop("Test report sent.");
|
|
487
|
-
|
|
527
|
+
let reportId;
|
|
528
|
+
try {
|
|
529
|
+
const body = await res.json();
|
|
530
|
+
reportId = body.data?.reportId;
|
|
531
|
+
} catch {
|
|
532
|
+
}
|
|
533
|
+
const reportPath = reportId ? `/reports/${reportId}` : "/reports";
|
|
534
|
+
p.log.success(`View it at https://kensaur.us/mushi-mushi/admin${reportPath}`);
|
|
488
535
|
} catch (err) {
|
|
489
536
|
const aborted = err instanceof Error && err.name === "AbortError";
|
|
490
537
|
spinner2.stop(aborted ? "Timed out reaching the Mushi API." : "Could not reach the Mushi API.");
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
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": {
|
|
7
7
|
"mushi": "./dist/index.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"@clack/prompts": "^1.
|
|
10
|
+
"@clack/prompts": "^1.3.0",
|
|
11
11
|
"commander": "^14.0.3"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@types/node": "^22.
|
|
15
|
-
"eslint": "^10.
|
|
14
|
+
"@types/node": "^22.19.17",
|
|
15
|
+
"eslint": "^10.3.0",
|
|
16
16
|
"tsup": "^8.5.1",
|
|
17
|
-
"typescript": "^6.0.
|
|
18
|
-
"vitest": "^4.1.
|
|
17
|
+
"typescript": "^6.0.3",
|
|
18
|
+
"vitest": "^4.1.5",
|
|
19
19
|
"@mushi-mushi/eslint-config": "0.0.0"
|
|
20
20
|
},
|
|
21
21
|
"author": "Kenji Sakuramoto",
|