@mushi-mushi/cli 0.23.1 → 0.25.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/dist/{chunk-NZ64LBLU.js → chunk-2WHQONHK.js} +1 -1
- package/dist/{chunk-M6L22NJU.js → chunk-3JRQN6S4.js} +1 -1
- package/dist/chunk-SS42XQ57.js +6 -0
- package/dist/{connect-5VPUYE6H.js → connect-22IHGT2L.js} +1 -1
- package/dist/{connect-IWFKKXGZ.js → connect-XFWRSEMW.js} +2 -2
- package/dist/index.js +484 -296
- package/dist/init.js +109 -41
- package/dist/version.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-I4UK3WTN.js +0 -6
package/dist/init.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
resolveConsoleUrl,
|
|
15
15
|
resolveConsoleUrlSync,
|
|
16
16
|
saveConfig
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-2WHQONHK.js";
|
|
18
18
|
import {
|
|
19
19
|
FRAMEWORKS,
|
|
20
20
|
detectFramework,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
} from "./chunk-AIZRI665.js";
|
|
26
26
|
import {
|
|
27
27
|
MUSHI_CLI_VERSION
|
|
28
|
-
} from "./chunk-
|
|
28
|
+
} from "./chunk-SS42XQ57.js";
|
|
29
29
|
|
|
30
30
|
// src/init.ts
|
|
31
31
|
import * as p from "@clack/prompts";
|
|
@@ -60,7 +60,9 @@ async function startDeviceAuth(endpoint, clientId) {
|
|
|
60
60
|
});
|
|
61
61
|
const json = await res.json().catch(() => null);
|
|
62
62
|
if (!res.ok || !json?.ok || !json.data) {
|
|
63
|
-
throw new Error(
|
|
63
|
+
throw new Error(
|
|
64
|
+
json?.error?.message ?? `Could not start browser sign-in (HTTP ${res.status}).`
|
|
65
|
+
);
|
|
64
66
|
}
|
|
65
67
|
return json.data;
|
|
66
68
|
}
|
|
@@ -101,10 +103,15 @@ async function pollDeviceToken(endpoint, deviceCode) {
|
|
|
101
103
|
};
|
|
102
104
|
}
|
|
103
105
|
} catch (err) {
|
|
104
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
status: "error",
|
|
108
|
+
message: err instanceof Error ? err.message : String(err),
|
|
109
|
+
retryable: true
|
|
110
|
+
};
|
|
105
111
|
}
|
|
106
112
|
}
|
|
107
113
|
var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
114
|
+
var deviceAuthRetryDelayMs = 1e3;
|
|
108
115
|
async function waitForCliToken(endpoint, session, opts = {}) {
|
|
109
116
|
const sleep = opts.sleep ?? defaultSleep;
|
|
110
117
|
const now = opts.now ?? Date.now;
|
|
@@ -155,19 +162,51 @@ function cliAuthHeaders(cliToken) {
|
|
|
155
162
|
Authorization: `Bearer ${cliToken}`
|
|
156
163
|
};
|
|
157
164
|
}
|
|
165
|
+
var DeviceAuthRequestError = class extends Error {
|
|
166
|
+
step;
|
|
167
|
+
status;
|
|
168
|
+
retryable;
|
|
169
|
+
constructor(step, message, status) {
|
|
170
|
+
super(message);
|
|
171
|
+
this.name = "DeviceAuthRequestError";
|
|
172
|
+
this.step = step;
|
|
173
|
+
this.status = status;
|
|
174
|
+
this.retryable = status === void 0 || status >= 500 || status === 408;
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
async function withOneRetry(run) {
|
|
178
|
+
try {
|
|
179
|
+
return await run();
|
|
180
|
+
} catch (err) {
|
|
181
|
+
if (err instanceof DeviceAuthRequestError && err.retryable) {
|
|
182
|
+
if (deviceAuthRetryDelayMs > 0) {
|
|
183
|
+
await defaultSleep(deviceAuthRetryDelayMs);
|
|
184
|
+
}
|
|
185
|
+
return run();
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
158
190
|
async function listProjects(endpoint, cliToken) {
|
|
159
191
|
const base = trimTrailingSlash(endpoint);
|
|
160
|
-
|
|
161
|
-
|
|
192
|
+
return withOneRetry(async () => {
|
|
193
|
+
let res;
|
|
194
|
+
try {
|
|
195
|
+
res = await deviceFetch(`${base}/v1/cli/projects`, { headers: cliAuthHeaders(cliToken) });
|
|
196
|
+
} catch (err) {
|
|
197
|
+
throw new DeviceAuthRequestError(
|
|
198
|
+
"list_projects",
|
|
199
|
+
`Could not reach ${base}/v1/cli/projects: ${err instanceof Error ? err.message : String(err)}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
162
202
|
const json = await res.json().catch(() => null);
|
|
163
203
|
if (res.ok && json?.ok) return json.data?.projects ?? [];
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
204
|
+
throw new DeviceAuthRequestError(
|
|
205
|
+
"list_projects",
|
|
206
|
+
json?.error?.message ?? `Could not list projects (HTTP ${res.status}).`,
|
|
207
|
+
res.status
|
|
168
208
|
);
|
|
169
|
-
}
|
|
170
|
-
return [];
|
|
209
|
+
});
|
|
171
210
|
}
|
|
172
211
|
async function createProject(endpoint, cliToken, name) {
|
|
173
212
|
const base = trimTrailingSlash(endpoint);
|
|
@@ -185,17 +224,26 @@ async function createProject(endpoint, cliToken, name) {
|
|
|
185
224
|
}
|
|
186
225
|
async function mintProjectKey(endpoint, cliToken, projectId) {
|
|
187
226
|
const base = trimTrailingSlash(endpoint);
|
|
227
|
+
let res;
|
|
188
228
|
try {
|
|
189
|
-
|
|
229
|
+
res = await deviceFetch(`${base}/v1/cli/projects/${projectId}/keys`, {
|
|
190
230
|
method: "POST",
|
|
191
231
|
headers: cliAuthHeaders(cliToken),
|
|
192
232
|
body: JSON.stringify({})
|
|
193
233
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
234
|
+
} catch (err) {
|
|
235
|
+
throw new DeviceAuthRequestError(
|
|
236
|
+
"mint_key",
|
|
237
|
+
`Could not reach ${base}/v1/cli/projects/\u2026/keys: ${err instanceof Error ? err.message : String(err)}`
|
|
238
|
+
);
|
|
197
239
|
}
|
|
198
|
-
|
|
240
|
+
const json = await res.json().catch(() => null);
|
|
241
|
+
if (res.ok && json?.ok && json.data?.key) return json.data.key;
|
|
242
|
+
throw new DeviceAuthRequestError(
|
|
243
|
+
"mint_key",
|
|
244
|
+
json?.error?.message ?? `Could not mint an SDK key (HTTP ${res.status}).`,
|
|
245
|
+
res.status
|
|
246
|
+
);
|
|
199
247
|
}
|
|
200
248
|
|
|
201
249
|
// src/freshness.ts
|
|
@@ -419,8 +467,16 @@ async function runInit(options = {}) {
|
|
|
419
467
|
} else {
|
|
420
468
|
p.log.info(`Skipped install. Run \`${installCommand(pm, packagesToInstall)}\` yourself.`);
|
|
421
469
|
}
|
|
422
|
-
await writeEnvFile(
|
|
470
|
+
await writeEnvFile(
|
|
471
|
+
cwd,
|
|
472
|
+
credentials.apiKey,
|
|
473
|
+
credentials.projectId,
|
|
474
|
+
framework,
|
|
475
|
+
endpoint,
|
|
476
|
+
Boolean(options.yes)
|
|
477
|
+
);
|
|
423
478
|
persistCliConfig(credentials.apiKey, credentials.projectId, endpoint);
|
|
479
|
+
emitWizardFunnelEvent(credentials, endpoint, "wizard_env_written", { framework: framework.id });
|
|
424
480
|
const enableRewards = await maybeEnableRewards(options);
|
|
425
481
|
printNextSteps(framework, consoleBase, enableRewards);
|
|
426
482
|
await maybeSendTestReport(credentials, { ...options, endpoint, consoleBase });
|
|
@@ -492,7 +548,9 @@ async function acquireCredentials(options, consoleBase, endpoint) {
|
|
|
492
548
|
if (options.yes) {
|
|
493
549
|
const creds = await runBrowserSignIn(options, endpoint, consoleBase);
|
|
494
550
|
if (creds) return creds;
|
|
495
|
-
p.log.warn(
|
|
551
|
+
p.log.warn(
|
|
552
|
+
"Browser sign-in didn't complete \u2014 switching to manual entry. Run `npx mushi-mushi doctor --auth` to diagnose the sign-in path."
|
|
553
|
+
);
|
|
496
554
|
} else {
|
|
497
555
|
const method = await p.select({
|
|
498
556
|
message: "Connect this app to Mushi",
|
|
@@ -517,7 +575,9 @@ async function acquireCredentials(options, consoleBase, endpoint) {
|
|
|
517
575
|
if (method === "browser") {
|
|
518
576
|
const creds = await runBrowserSignIn(options, endpoint, consoleBase);
|
|
519
577
|
if (creds) return creds;
|
|
520
|
-
p.log.warn(
|
|
578
|
+
p.log.warn(
|
|
579
|
+
"Browser sign-in didn't complete \u2014 switching to manual entry. Run `npx mushi-mushi doctor --auth` to diagnose the sign-in path."
|
|
580
|
+
);
|
|
521
581
|
}
|
|
522
582
|
}
|
|
523
583
|
return collectCredentialsManually(options, consoleBase, endpoint);
|
|
@@ -558,7 +618,9 @@ async function runBrowserSignIn(options, endpoint, consoleBase) {
|
|
|
558
618
|
fetchSpin.start("Loading your projects\u2026");
|
|
559
619
|
try {
|
|
560
620
|
projects = await listProjects(endpoint, cliToken);
|
|
561
|
-
fetchSpin.stop(
|
|
621
|
+
fetchSpin.stop(
|
|
622
|
+
projects.length > 0 ? `Found ${projects.length} project(s).` : "No projects yet."
|
|
623
|
+
);
|
|
562
624
|
} catch (err) {
|
|
563
625
|
fetchSpin.stop("Could not load projects \u2014 you can still create a new one.");
|
|
564
626
|
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
@@ -606,19 +668,14 @@ async function runBrowserSignIn(options, endpoint, consoleBase) {
|
|
|
606
668
|
const keySpin = p.spinner();
|
|
607
669
|
keySpin.start("Minting SDK key\u2026");
|
|
608
670
|
try {
|
|
609
|
-
|
|
610
|
-
apiKey = minted ?? void 0;
|
|
611
|
-
if (!apiKey) {
|
|
612
|
-
keySpin.stop("Could not mint an API key.");
|
|
613
|
-
p.log.warn(
|
|
614
|
-
`Open the console Verify tab to generate a key manually: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
|
|
615
|
-
);
|
|
616
|
-
return null;
|
|
617
|
-
}
|
|
671
|
+
apiKey = await mintProjectKey(endpoint, cliToken, projectId);
|
|
618
672
|
keySpin.stop("SDK key ready.");
|
|
619
673
|
} catch (err) {
|
|
620
674
|
keySpin.stop("Could not mint an API key.");
|
|
621
675
|
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
676
|
+
p.log.warn(
|
|
677
|
+
`You're signed in, but key minting failed. Generate one manually in the console Verify tab: ${consoleUrl(consoleBase, "/onboarding?tab=verify")}`
|
|
678
|
+
);
|
|
622
679
|
return null;
|
|
623
680
|
}
|
|
624
681
|
}
|
|
@@ -678,10 +735,11 @@ async function verifyCredentials(credentials, options, consoleBase) {
|
|
|
678
735
|
const endpoint = resolveCloudEndpoint(options.endpoint);
|
|
679
736
|
const spinner2 = p.spinner();
|
|
680
737
|
spinner2.start("Verifying credentials\u2026");
|
|
681
|
-
const result = await apiCall(
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
738
|
+
const result = await apiCall("/v1/sync/whoami", {
|
|
739
|
+
apiKey: credentials.apiKey,
|
|
740
|
+
projectId: credentials.projectId,
|
|
741
|
+
endpoint
|
|
742
|
+
});
|
|
685
743
|
if (!result.ok) {
|
|
686
744
|
spinner2.stop("Credentials could not be verified.");
|
|
687
745
|
p.log.error(result.error?.message ?? "Authentication failed.");
|
|
@@ -836,6 +894,20 @@ function persistCliConfig(apiKey, projectId, endpoint) {
|
|
|
836
894
|
const existing = loadConfig();
|
|
837
895
|
saveConfig({ ...existing, apiKey, projectId, endpoint });
|
|
838
896
|
}
|
|
897
|
+
function emitWizardFunnelEvent(credentials, endpoint, event, metadata = {}) {
|
|
898
|
+
if (process.env.MUSHI_NO_TELEMETRY) return;
|
|
899
|
+
void fetch(`${endpoint.replace(/\/$/, "")}/v1/cli/funnel`, {
|
|
900
|
+
method: "POST",
|
|
901
|
+
headers: {
|
|
902
|
+
"Content-Type": "application/json",
|
|
903
|
+
"X-Mushi-Api-Key": credentials.apiKey,
|
|
904
|
+
"X-Mushi-Project": credentials.projectId
|
|
905
|
+
},
|
|
906
|
+
body: JSON.stringify({ event, source: "cli", metadata }),
|
|
907
|
+
signal: AbortSignal.timeout(4e3)
|
|
908
|
+
}).catch(() => {
|
|
909
|
+
});
|
|
910
|
+
}
|
|
839
911
|
function printNextSteps(framework, consoleBase, enableRewards = false) {
|
|
840
912
|
p.note(framework.snippet(), "Add this to your app:");
|
|
841
913
|
if (enableRewards) {
|
|
@@ -869,7 +941,7 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
|
|
|
869
941
|
if (p.isCancel(answer) || !answer) return;
|
|
870
942
|
const endpoint = resolveCloudEndpoint(options.endpoint);
|
|
871
943
|
try {
|
|
872
|
-
const { runConnect } = await import("./connect-
|
|
944
|
+
const { runConnect } = await import("./connect-XFWRSEMW.js");
|
|
873
945
|
await runConnect({
|
|
874
946
|
apiKey: credentials.apiKey,
|
|
875
947
|
projectId: credentials.projectId,
|
|
@@ -880,12 +952,8 @@ async function maybeOfferConnect(credentials, options, _consoleBase) {
|
|
|
880
952
|
wait: true
|
|
881
953
|
});
|
|
882
954
|
} catch (err) {
|
|
883
|
-
p.log.warn(
|
|
884
|
-
|
|
885
|
-
);
|
|
886
|
-
p.log.info(
|
|
887
|
-
"You can run manually: mushi connect --write-env --wire-ide --wait"
|
|
888
|
-
);
|
|
955
|
+
p.log.warn(err instanceof Error ? err.message : String(err));
|
|
956
|
+
p.log.info("You can run manually: mushi connect --write-env --wire-ide --wait");
|
|
889
957
|
}
|
|
890
958
|
}
|
|
891
959
|
async function maybeEnableRewards(options) {
|
package/dist/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.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": {
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
"commander": "^15.0.0"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@types/node": "^22.
|
|
15
|
-
"eslint": "^10.
|
|
14
|
+
"@types/node": "^22.20.0",
|
|
15
|
+
"eslint": "^10.6.0",
|
|
16
16
|
"tsup": "^8.5.1",
|
|
17
17
|
"typescript": "^6.0.3",
|
|
18
|
-
"vitest": "^4.1.
|
|
18
|
+
"vitest": "^4.1.9",
|
|
19
19
|
"@mushi-mushi/eslint-config": "0.0.0"
|
|
20
20
|
},
|
|
21
21
|
"author": "Kenji Sakuramoto (https://bsky.app/profile/mushimushi.dev)",
|