@mushi-mushi/cli 0.8.0 → 0.10.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 +11 -0
- package/dist/chunk-GHDS4VGP.js +6 -0
- package/dist/index.js +234 -7
- package/dist/init.js +62 -6
- package/dist/version.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-KUQZQFOL.js +0 -6
package/CONTRIBUTING.md
CHANGED
|
@@ -101,6 +101,17 @@ Releases are fully automated. Maintainers don't run `npm publish` by hand.
|
|
|
101
101
|
|
|
102
102
|
If GitHub's anti-loop protection suppresses the auto re-fire (the squash merge can be attributed to `github-actions[bot]`), trigger the workflow manually: **Actions → release → Run workflow → master**.
|
|
103
103
|
|
|
104
|
+
### Known CI/CD quirks and their automatic safeguards
|
|
105
|
+
|
|
106
|
+
A handful of GitHub-Actions × Changesets edge cases have caused release-pipeline stalls in the past. Each is now caught automatically — keep these in mind when you see the symptom:
|
|
107
|
+
|
|
108
|
+
| Symptom | Root cause | Automatic safeguard |
|
|
109
|
+
| --- | --- | --- |
|
|
110
|
+
| The `Build & Test` required check never registers on the `chore: version packages` PR — the PR stays "Required check missing" forever | `changeset-release/master` is pushed by `github-actions[bot]`. GitHub silently drops the downstream `pull_request` event to prevent bot loops (observed on PR #45, #102, #124). | `ci.yml` now also triggers on `push` to `changeset-release/master`, so `Build & Test` reports against the head commit directly. No empty-commit nudge needed. |
|
|
111
|
+
| Release workflow fails with `No commits between master and changeset-release/master` after merging a PR with a new changeset. | A `.changeset/*.md` file whose YAML frontmatter only targets packages listed in `.changeset/config.json#ignore` (e.g. `@mushi-mushi/server`, `@mushi-mushi/admin`). `changeset version` produces no bumps, the version PR is empty, the next push errors (PR #102 / #121, 2026-05-19). | `pnpm check:changeset-orphans` runs in both `ci.yml` and `release.yml`. PR CI fails with an actionable message naming the orphan file *before* it can reach master. If you legitimately need to record an internal-only change, omit the changeset entirely — the diff lives in git history. |
|
|
112
|
+
| Release workflow's `Audit signatures of installed dependencies` step fails with `npm ETARGET / No matching version found for @mushi-mushi/<pkg>@<ver>` seconds after the publish step succeeded. | npm's registry CDN can take up to ~30s to propagate a freshly-published manifest. The audit step shells out to `npm install` against the just-published version and races the CDN (observed 2026-05-20, run 26149167393). | The audit step retries with exponential backoff (1, 2, 4, 8, 16, 32s — 63s total) before failing. Sigstore signatures are written at publish time, so a one-off audit failure never indicates a corrupted package — `pnpm view <pkg> version` is the ground truth. |
|
|
113
|
+
| Push to `master` after merging a PR doesn't fire the `Release` workflow. | Same anti-loop protection: when a squash merge is attributed to `github-actions[bot]`, GitHub may suppress the downstream `push` trigger. Sporadic — observed twice in the last 60 days. | `release.yml` keeps `workflow_dispatch` as the manual fallback. Recovery: **Actions → Release → Run workflow → master**. The `Build & Verify` job re-runs identically to the auto-fired path. |
|
|
114
|
+
|
|
104
115
|
### Adding a brand-new publishable package
|
|
105
116
|
|
|
106
117
|
Trusted Publisher bindings are configured **per package** on `npmjs.com` and require the package to already exist on the registry. New packages therefore need a one-time bootstrap before OIDC can take over.
|
package/dist/index.js
CHANGED
|
@@ -4,11 +4,26 @@
|
|
|
4
4
|
import { Command } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/config.ts
|
|
7
|
-
import { chmodSync, readFileSync, statSync,
|
|
8
|
-
import { join } from "path";
|
|
7
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
9
8
|
import { homedir } from "os";
|
|
10
|
-
|
|
9
|
+
import { dirname, join } from "path";
|
|
11
10
|
var SECURE_FILE_MODE = 384;
|
|
11
|
+
var SECURE_DIR_MODE = 448;
|
|
12
|
+
function resolveXdgConfigPath() {
|
|
13
|
+
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
14
|
+
if (xdg && xdg.length > 0) {
|
|
15
|
+
return join(xdg, "mushi", "config.json");
|
|
16
|
+
}
|
|
17
|
+
if (process.platform === "win32") {
|
|
18
|
+
const appData = process.env["APPDATA"];
|
|
19
|
+
if (appData && appData.length > 0) {
|
|
20
|
+
return join(appData, "mushi", "config.json");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return join(homedir(), ".config", "mushi", "config.json");
|
|
24
|
+
}
|
|
25
|
+
var LEGACY_CONFIG_PATH = join(homedir(), ".mushirc");
|
|
26
|
+
var CONFIG_PATH = resolveXdgConfigPath();
|
|
12
27
|
function loadConfig(path = CONFIG_PATH) {
|
|
13
28
|
let file = {};
|
|
14
29
|
if (existsSync(path)) {
|
|
@@ -17,6 +32,8 @@ function loadConfig(path = CONFIG_PATH) {
|
|
|
17
32
|
file = JSON.parse(readFileSync(path, "utf-8"));
|
|
18
33
|
} catch {
|
|
19
34
|
}
|
|
35
|
+
} else if (path === CONFIG_PATH && existsSync(LEGACY_CONFIG_PATH)) {
|
|
36
|
+
file = migrateLegacyConfig() ?? {};
|
|
20
37
|
}
|
|
21
38
|
const fromEnv = {
|
|
22
39
|
...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
|
|
@@ -26,9 +43,40 @@ function loadConfig(path = CONFIG_PATH) {
|
|
|
26
43
|
return { ...file, ...fromEnv };
|
|
27
44
|
}
|
|
28
45
|
function saveConfig(config, path = CONFIG_PATH) {
|
|
46
|
+
const dir = dirname(path);
|
|
47
|
+
if (!existsSync(dir)) {
|
|
48
|
+
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
49
|
+
} else {
|
|
50
|
+
tightenDirPermissions(dir);
|
|
51
|
+
}
|
|
29
52
|
writeFileSync(path, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
|
|
30
53
|
tightenPermissions(path);
|
|
31
54
|
}
|
|
55
|
+
function migrateLegacyConfig(legacyPath = LEGACY_CONFIG_PATH, destPath = CONFIG_PATH) {
|
|
56
|
+
if (!existsSync(legacyPath)) return null;
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(readFileSync(legacyPath, "utf-8"));
|
|
60
|
+
} catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const dir = dirname(destPath);
|
|
64
|
+
if (!existsSync(dir)) {
|
|
65
|
+
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
renameSync(legacyPath, destPath);
|
|
69
|
+
} catch {
|
|
70
|
+
writeFileSync(destPath, JSON.stringify(parsed, null, 2), { mode: SECURE_FILE_MODE });
|
|
71
|
+
try {
|
|
72
|
+
unlinkSync(legacyPath);
|
|
73
|
+
} catch {
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
tightenPermissions(destPath);
|
|
77
|
+
tightenDirPermissions(dir);
|
|
78
|
+
return parsed;
|
|
79
|
+
}
|
|
32
80
|
function tightenPermissions(path) {
|
|
33
81
|
if (process.platform === "win32") return;
|
|
34
82
|
try {
|
|
@@ -37,6 +85,14 @@ function tightenPermissions(path) {
|
|
|
37
85
|
} catch {
|
|
38
86
|
}
|
|
39
87
|
}
|
|
88
|
+
function tightenDirPermissions(path) {
|
|
89
|
+
if (process.platform === "win32") return;
|
|
90
|
+
try {
|
|
91
|
+
const current = statSync(path).mode & 511;
|
|
92
|
+
if (current !== SECURE_DIR_MODE) chmodSync(path, SECURE_DIR_MODE);
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
}
|
|
40
96
|
|
|
41
97
|
// src/init.ts
|
|
42
98
|
import * as p from "@clack/prompts";
|
|
@@ -368,7 +424,7 @@ function parse(version) {
|
|
|
368
424
|
|
|
369
425
|
// src/monorepo.ts
|
|
370
426
|
import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync, statSync as statSync2 } from "fs";
|
|
371
|
-
import { dirname, join as join3, resolve } from "path";
|
|
427
|
+
import { dirname as dirname2, join as join3, resolve } from "path";
|
|
372
428
|
var WORKSPACE_GLOB_CANDIDATES = ["apps/*", "packages/*", "examples/*"];
|
|
373
429
|
var FRAMEWORK_DEPS = {
|
|
374
430
|
next: "Next.js",
|
|
@@ -396,7 +452,7 @@ function findWorkspaceRoot(start) {
|
|
|
396
452
|
let dir = resolve(start);
|
|
397
453
|
for (let i = 0; i < 8; i++) {
|
|
398
454
|
if (isWorkspaceRoot(dir)) return dir;
|
|
399
|
-
const parent =
|
|
455
|
+
const parent = dirname2(dir);
|
|
400
456
|
if (parent === dir) break;
|
|
401
457
|
dir = parent;
|
|
402
458
|
}
|
|
@@ -464,7 +520,7 @@ function getFrameworkFromPkg(pkg) {
|
|
|
464
520
|
}
|
|
465
521
|
|
|
466
522
|
// src/version.ts
|
|
467
|
-
var MUSHI_CLI_VERSION = true ? "0.
|
|
523
|
+
var MUSHI_CLI_VERSION = true ? "0.10.0" : "0.0.0-dev";
|
|
468
524
|
|
|
469
525
|
// src/init.ts
|
|
470
526
|
var ENV_FILES = [".env.local", ".env"];
|
|
@@ -1097,7 +1153,87 @@ async function runSourcemapsUpload(opts) {
|
|
|
1097
1153
|
if (failed > 0) process.exit(1);
|
|
1098
1154
|
}
|
|
1099
1155
|
|
|
1156
|
+
// src/errors.ts
|
|
1157
|
+
var EXIT_CODE_MAP = {
|
|
1158
|
+
E_AUTH_MISSING: 2,
|
|
1159
|
+
E_AUTH_INVALID: 2,
|
|
1160
|
+
E_PROJECT_MISSING: 2,
|
|
1161
|
+
E_ENDPOINT_INVALID: 2,
|
|
1162
|
+
E_INVALID_INPUT: 2,
|
|
1163
|
+
E_NOT_INTERACTIVE: 2,
|
|
1164
|
+
E_NETWORK: 3,
|
|
1165
|
+
E_TIMEOUT: 3,
|
|
1166
|
+
E_API_ERROR: 3,
|
|
1167
|
+
E_RATE_LIMITED: 3,
|
|
1168
|
+
E_FILE_NOT_FOUND: 1,
|
|
1169
|
+
E_FILE_PERMISSION: 1,
|
|
1170
|
+
E_FRESHNESS_STALE: 1,
|
|
1171
|
+
E_INTERRUPTED: 130,
|
|
1172
|
+
// 128 + SIGINT (2). POSIX convention.
|
|
1173
|
+
E_INTERNAL: 1
|
|
1174
|
+
};
|
|
1175
|
+
var MushiCliError = class extends Error {
|
|
1176
|
+
code;
|
|
1177
|
+
hint;
|
|
1178
|
+
cause;
|
|
1179
|
+
constructor(code, message, hint, cause) {
|
|
1180
|
+
super(message);
|
|
1181
|
+
this.name = "MushiCliError";
|
|
1182
|
+
this.code = code;
|
|
1183
|
+
if (hint) this.hint = hint;
|
|
1184
|
+
if (cause !== void 0) this.cause = cause;
|
|
1185
|
+
}
|
|
1186
|
+
/** Exit code POSIX-aware shell scripts should branch on. */
|
|
1187
|
+
get exitCode() {
|
|
1188
|
+
return EXIT_CODE_MAP[this.code];
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Format for `--json` output. Used by the JSON writer when a CLI
|
|
1192
|
+
* subcommand was invoked with `--json` and an error escapes.
|
|
1193
|
+
*/
|
|
1194
|
+
toJSON() {
|
|
1195
|
+
return {
|
|
1196
|
+
error: {
|
|
1197
|
+
code: this.code,
|
|
1198
|
+
message: this.message,
|
|
1199
|
+
...this.hint ? { hint: this.hint } : {}
|
|
1200
|
+
}
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
// src/signals.ts
|
|
1206
|
+
var installed = false;
|
|
1207
|
+
var activeController = null;
|
|
1208
|
+
function installSignalHandlers() {
|
|
1209
|
+
if (installed) return;
|
|
1210
|
+
installed = true;
|
|
1211
|
+
const abortAndExit = (signal) => {
|
|
1212
|
+
activeController?.abort(
|
|
1213
|
+
new MushiCliError(
|
|
1214
|
+
"E_INTERRUPTED",
|
|
1215
|
+
`aborted by ${signal}`,
|
|
1216
|
+
"partial state on the server is safe to retry \u2014 re-run the same command"
|
|
1217
|
+
)
|
|
1218
|
+
);
|
|
1219
|
+
const code = signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 1;
|
|
1220
|
+
process.nextTick(() => {
|
|
1221
|
+
process.exit(code);
|
|
1222
|
+
});
|
|
1223
|
+
};
|
|
1224
|
+
process.on("SIGINT", () => abortAndExit("SIGINT"));
|
|
1225
|
+
process.on("SIGTERM", () => abortAndExit("SIGTERM"));
|
|
1226
|
+
}
|
|
1227
|
+
function getAbortSignal(external) {
|
|
1228
|
+
if (external) return external.signal;
|
|
1229
|
+
if (!activeController || activeController.signal.aborted) {
|
|
1230
|
+
activeController = new AbortController();
|
|
1231
|
+
}
|
|
1232
|
+
return activeController.signal;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1100
1235
|
// src/index.ts
|
|
1236
|
+
installSignalHandlers();
|
|
1101
1237
|
var API_TIMEOUT_MS = 15e3;
|
|
1102
1238
|
async function apiCall(path, config, options = {}) {
|
|
1103
1239
|
const endpoint = config.endpoint;
|
|
@@ -1112,10 +1248,12 @@ async function apiCall(path, config, options = {}) {
|
|
|
1112
1248
|
}
|
|
1113
1249
|
const controller = new AbortController();
|
|
1114
1250
|
const timer = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
1251
|
+
const signals = [controller.signal, getAbortSignal()];
|
|
1252
|
+
const compositeSignal = AbortSignal.any ? AbortSignal.any(signals) : controller.signal;
|
|
1115
1253
|
try {
|
|
1116
1254
|
const res = await fetch(`${endpoint}${path}`, {
|
|
1117
1255
|
...options,
|
|
1118
|
-
signal:
|
|
1256
|
+
signal: compositeSignal,
|
|
1119
1257
|
headers: {
|
|
1120
1258
|
"Content-Type": "application/json",
|
|
1121
1259
|
// `apiKeyAuth` reads X-Mushi-Api-Key; the Authorization header is a
|
|
@@ -1807,4 +1945,93 @@ Examples:
|
|
|
1807
1945
|
silent: opts.silent
|
|
1808
1946
|
});
|
|
1809
1947
|
});
|
|
1948
|
+
var fixCmd = program.command("fix").description("Dispatch an agentic fix for a report");
|
|
1949
|
+
fixCmd.argument("<reportId>", "Report UUID to fix").option(
|
|
1950
|
+
"--agent <name>",
|
|
1951
|
+
"Agent adapter: claude_code (default), cursor_cloud, codex, mcp",
|
|
1952
|
+
"claude_code"
|
|
1953
|
+
).option(
|
|
1954
|
+
"--model <slug>",
|
|
1955
|
+
"Model override for cursor_cloud (e.g. composer-latest)"
|
|
1956
|
+
).option(
|
|
1957
|
+
"--no-auto-pr",
|
|
1958
|
+
"For cursor_cloud: skip automatic PR creation (branch only)"
|
|
1959
|
+
).option(
|
|
1960
|
+
"--wait",
|
|
1961
|
+
"Poll until terminal state and exit non-zero on error/cancelled (CI-friendly)"
|
|
1962
|
+
).option("-e, --endpoint <url>", "API endpoint (overrides MUSHI_API_ENDPOINT)").option("--api-key <key>", "API key (overrides MUSHI_API_KEY)").option("--project-id <id>", "Project ID (overrides MUSHI_PROJECT_ID)").addHelpText("after", `
|
|
1963
|
+
Examples:
|
|
1964
|
+
mushi fix abc123 --agent cursor_cloud --wait
|
|
1965
|
+
mushi fix abc123 --agent cursor_cloud --model composer-latest --no-auto-pr
|
|
1966
|
+
mushi fix abc123 --agent claude_code
|
|
1967
|
+
|
|
1968
|
+
# CI: fail the pipeline if the fix errors
|
|
1969
|
+
mushi fix $REPORT_ID --agent cursor_cloud --wait && echo "Fix PR opened"`).action(async (reportId, opts) => {
|
|
1970
|
+
const cfg = loadConfig();
|
|
1971
|
+
if (opts.endpoint) cfg.endpoint = opts.endpoint;
|
|
1972
|
+
if (opts.apiKey) cfg.apiKey = opts.apiKey;
|
|
1973
|
+
if (opts.projectId) cfg.projectId = opts.projectId;
|
|
1974
|
+
const isTTY = process.stdout.isTTY;
|
|
1975
|
+
const emitEvent = (type, data) => {
|
|
1976
|
+
if (isTTY) {
|
|
1977
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
1978
|
+
console.log(`[${ts}] ${type}`, JSON.stringify(data));
|
|
1979
|
+
} else {
|
|
1980
|
+
process.stdout.write(JSON.stringify({ type, ...data }) + "\n");
|
|
1981
|
+
}
|
|
1982
|
+
};
|
|
1983
|
+
emitEvent("dispatch.start", { reportId, agent: opts.agent, model: opts.model ?? null });
|
|
1984
|
+
const body = {
|
|
1985
|
+
reportId,
|
|
1986
|
+
agent: opts.agent
|
|
1987
|
+
};
|
|
1988
|
+
if (opts.agent === "cursor_cloud") {
|
|
1989
|
+
if (opts.model) body.cursorModel = opts.model;
|
|
1990
|
+
if (!opts.autoPr) body.cursorAutoCreatePR = false;
|
|
1991
|
+
}
|
|
1992
|
+
const result = await apiCall("/v1/admin/fixes/dispatch", cfg, {
|
|
1993
|
+
method: "POST",
|
|
1994
|
+
headers: { "Content-Type": "application/json" },
|
|
1995
|
+
body: JSON.stringify(body)
|
|
1996
|
+
});
|
|
1997
|
+
if (!result.ok) {
|
|
1998
|
+
console.error("Error dispatching fix:", result.error.message);
|
|
1999
|
+
process.exit(1);
|
|
2000
|
+
}
|
|
2001
|
+
const { fixId, status, agentId, runId, prUrl } = result.data;
|
|
2002
|
+
emitEvent("dispatch.ok", { fixId, status, agentId, runId, prUrl });
|
|
2003
|
+
if (!opts.wait) {
|
|
2004
|
+
process.exit(0);
|
|
2005
|
+
}
|
|
2006
|
+
if (!fixId) {
|
|
2007
|
+
console.error("No fixId returned \u2014 cannot poll.");
|
|
2008
|
+
process.exit(1);
|
|
2009
|
+
}
|
|
2010
|
+
const POLL_MS = 5e3;
|
|
2011
|
+
const MAX_POLLS = 120;
|
|
2012
|
+
const TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "error", "cancelled", "skipped", "skipped_unsupported_agent", "skipped_no_sandbox"]);
|
|
2013
|
+
for (let i = 0; i < MAX_POLLS; i++) {
|
|
2014
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
2015
|
+
const pollResult = await apiCall(
|
|
2016
|
+
`/v1/admin/fixes/${fixId}`,
|
|
2017
|
+
cfg
|
|
2018
|
+
);
|
|
2019
|
+
if (!pollResult.ok) {
|
|
2020
|
+
emitEvent("poll.error", { error: pollResult.error.message });
|
|
2021
|
+
continue;
|
|
2022
|
+
}
|
|
2023
|
+
const s = pollResult.data.status;
|
|
2024
|
+
emitEvent("fix.status", { status: s, pr_url: pollResult.data.pr_url, cursor_agent_id: pollResult.data.cursor_agent_id });
|
|
2025
|
+
if (s && TERMINAL.has(s)) {
|
|
2026
|
+
const success = s === "completed";
|
|
2027
|
+
if (!success) {
|
|
2028
|
+
console.error(`Fix ended with status: ${s}${pollResult.data.error ? ` \u2014 ${pollResult.data.error}` : ""}`);
|
|
2029
|
+
process.exit(1);
|
|
2030
|
+
}
|
|
2031
|
+
process.exit(0);
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
console.error("Polling timed out after 10 minutes. The fix may still be running.");
|
|
2035
|
+
process.exit(1);
|
|
2036
|
+
});
|
|
1810
2037
|
program.parse();
|
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-GHDS4VGP.js";
|
|
12
12
|
|
|
13
13
|
// src/init.ts
|
|
14
14
|
import * as p from "@clack/prompts";
|
|
@@ -18,11 +18,26 @@ import { appendFileSync, existsSync as existsSync3, readFileSync as readFileSync
|
|
|
18
18
|
import { join as join3 } from "path";
|
|
19
19
|
|
|
20
20
|
// src/config.ts
|
|
21
|
-
import { chmodSync, readFileSync, statSync,
|
|
22
|
-
import { join } from "path";
|
|
21
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
23
22
|
import { homedir } from "os";
|
|
24
|
-
|
|
23
|
+
import { dirname, join } from "path";
|
|
25
24
|
var SECURE_FILE_MODE = 384;
|
|
25
|
+
var SECURE_DIR_MODE = 448;
|
|
26
|
+
function resolveXdgConfigPath() {
|
|
27
|
+
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
28
|
+
if (xdg && xdg.length > 0) {
|
|
29
|
+
return join(xdg, "mushi", "config.json");
|
|
30
|
+
}
|
|
31
|
+
if (process.platform === "win32") {
|
|
32
|
+
const appData = process.env["APPDATA"];
|
|
33
|
+
if (appData && appData.length > 0) {
|
|
34
|
+
return join(appData, "mushi", "config.json");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return join(homedir(), ".config", "mushi", "config.json");
|
|
38
|
+
}
|
|
39
|
+
var LEGACY_CONFIG_PATH = join(homedir(), ".mushirc");
|
|
40
|
+
var CONFIG_PATH = resolveXdgConfigPath();
|
|
26
41
|
function loadConfig(path = CONFIG_PATH) {
|
|
27
42
|
let file = {};
|
|
28
43
|
if (existsSync(path)) {
|
|
@@ -31,6 +46,8 @@ function loadConfig(path = CONFIG_PATH) {
|
|
|
31
46
|
file = JSON.parse(readFileSync(path, "utf-8"));
|
|
32
47
|
} catch {
|
|
33
48
|
}
|
|
49
|
+
} else if (path === CONFIG_PATH && existsSync(LEGACY_CONFIG_PATH)) {
|
|
50
|
+
file = migrateLegacyConfig() ?? {};
|
|
34
51
|
}
|
|
35
52
|
const fromEnv = {
|
|
36
53
|
...process.env["MUSHI_API_KEY"] ? { apiKey: process.env["MUSHI_API_KEY"] } : {},
|
|
@@ -40,9 +57,40 @@ function loadConfig(path = CONFIG_PATH) {
|
|
|
40
57
|
return { ...file, ...fromEnv };
|
|
41
58
|
}
|
|
42
59
|
function saveConfig(config, path = CONFIG_PATH) {
|
|
60
|
+
const dir = dirname(path);
|
|
61
|
+
if (!existsSync(dir)) {
|
|
62
|
+
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
63
|
+
} else {
|
|
64
|
+
tightenDirPermissions(dir);
|
|
65
|
+
}
|
|
43
66
|
writeFileSync(path, JSON.stringify(config, null, 2), { mode: SECURE_FILE_MODE });
|
|
44
67
|
tightenPermissions(path);
|
|
45
68
|
}
|
|
69
|
+
function migrateLegacyConfig(legacyPath = LEGACY_CONFIG_PATH, destPath = CONFIG_PATH) {
|
|
70
|
+
if (!existsSync(legacyPath)) return null;
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(readFileSync(legacyPath, "utf-8"));
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const dir = dirname(destPath);
|
|
78
|
+
if (!existsSync(dir)) {
|
|
79
|
+
mkdirSync(dir, { recursive: true, mode: SECURE_DIR_MODE });
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
renameSync(legacyPath, destPath);
|
|
83
|
+
} catch {
|
|
84
|
+
writeFileSync(destPath, JSON.stringify(parsed, null, 2), { mode: SECURE_FILE_MODE });
|
|
85
|
+
try {
|
|
86
|
+
unlinkSync(legacyPath);
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
tightenPermissions(destPath);
|
|
91
|
+
tightenDirPermissions(dir);
|
|
92
|
+
return parsed;
|
|
93
|
+
}
|
|
46
94
|
function tightenPermissions(path) {
|
|
47
95
|
if (process.platform === "win32") return;
|
|
48
96
|
try {
|
|
@@ -51,6 +99,14 @@ function tightenPermissions(path) {
|
|
|
51
99
|
} catch {
|
|
52
100
|
}
|
|
53
101
|
}
|
|
102
|
+
function tightenDirPermissions(path) {
|
|
103
|
+
if (process.platform === "win32") return;
|
|
104
|
+
try {
|
|
105
|
+
const current = statSync(path).mode & 511;
|
|
106
|
+
if (current !== SECURE_DIR_MODE) chmodSync(path, SECURE_DIR_MODE);
|
|
107
|
+
} catch {
|
|
108
|
+
}
|
|
109
|
+
}
|
|
54
110
|
|
|
55
111
|
// src/endpoint.ts
|
|
56
112
|
var TEST_REPORT_TIMEOUT_MS = 1e4;
|
|
@@ -125,7 +181,7 @@ function parse(version) {
|
|
|
125
181
|
|
|
126
182
|
// src/monorepo.ts
|
|
127
183
|
import { existsSync as existsSync2, readFileSync as readFileSync2, readdirSync, statSync as statSync2 } from "fs";
|
|
128
|
-
import { dirname, join as join2, resolve } from "path";
|
|
184
|
+
import { dirname as dirname2, join as join2, resolve } from "path";
|
|
129
185
|
var WORKSPACE_GLOB_CANDIDATES = ["apps/*", "packages/*", "examples/*"];
|
|
130
186
|
var FRAMEWORK_DEPS = {
|
|
131
187
|
next: "Next.js",
|
|
@@ -153,7 +209,7 @@ function findWorkspaceRoot(start) {
|
|
|
153
209
|
let dir = resolve(start);
|
|
154
210
|
for (let i = 0; i < 8; i++) {
|
|
155
211
|
if (isWorkspaceRoot(dir)) return dir;
|
|
156
|
-
const parent =
|
|
212
|
+
const parent = dirname2(dir);
|
|
157
213
|
if (parent === dir) break;
|
|
158
214
|
dir = parent;
|
|
159
215
|
}
|
package/dist/version.js
CHANGED
package/package.json
CHANGED