forgemap 0.4.1-dev.66-903324b → 0.4.1-dev.68-66824fb
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/README.md +4 -3
- package/dist/bin/forgemap.mjs +166 -70
- package/dist/bin/forgemap.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,7 +83,8 @@ forgemap pick kirch # picker pre-filtered by fuzzy query
|
|
|
83
83
|
### Open the folder in the OS file manager
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
|
-
forgemap open kirchDev/laravel-pbac
|
|
86
|
+
forgemap open kirchDev/laravel-pbac # exact slug
|
|
87
|
+
forgemap open laravel # fuzzy single match → same folder
|
|
87
88
|
```
|
|
88
89
|
|
|
89
90
|
- **WSL** → launches `explorer.exe` against `\\wsl$\<distro>\…`, Explorer opens the folder
|
|
@@ -123,10 +124,10 @@ For each repo `import` compares the folder's `<owner>/<repo>` against the git `o
|
|
|
123
124
|
forgemap cleanup # list deletable clones, then type "yes" to confirm
|
|
124
125
|
forgemap cleanup --dry-run # show candidates + why every other idle repo is kept
|
|
125
126
|
forgemap cleanup --days 540 # idle threshold in days (default 365)
|
|
126
|
-
forgemap cleanup --include-dirty --include-unpushed # also delete repos with local-only work (lost!)
|
|
127
|
+
forgemap cleanup --include-dirty --include-unpushed --include-stashed # also delete repos with local-only work (lost!)
|
|
127
128
|
```
|
|
128
129
|
|
|
129
|
-
A repo is only deleted when it is idle for `--days`+ days (by last **local** commit), has a clean working tree, has nothing unpushed, **and** its remote still exists — so everything removed is provably backed up. Repos without a remote (or with a gone/unreachable one) are never touched; empty owner directories left behind are pruned automatically. Deletion needs an explicit typed `yes` (or `--yes`).
|
|
130
|
+
A repo is only deleted when it is idle for `--days`+ days (by last **local** commit), has a clean working tree, has nothing unpushed, has no stashed work, **and** its remote still exists — so everything removed is provably backed up. Repos without a remote (or with a gone/unreachable one) are never touched; empty owner directories left behind are pruned automatically. Deletion needs an explicit typed `yes` (or `--yes`).
|
|
130
131
|
|
|
131
132
|
### Preflight your config
|
|
132
133
|
|
package/dist/bin/forgemap.mjs
CHANGED
|
@@ -568,6 +568,7 @@ async function getRepoStatus(localPath) {
|
|
|
568
568
|
dirty: false,
|
|
569
569
|
ahead: 0,
|
|
570
570
|
behind: 0,
|
|
571
|
+
stashes: 0,
|
|
571
572
|
lastCommit: null
|
|
572
573
|
};
|
|
573
574
|
const branchResult = await gitIn(localPath, ["branch", "--show-current"]);
|
|
@@ -575,6 +576,7 @@ async function getRepoStatus(localPath) {
|
|
|
575
576
|
status.detached = !status.branch || status.branch === "HEAD";
|
|
576
577
|
const porcelain = await gitIn(localPath, ["status", "--porcelain"]);
|
|
577
578
|
status.dirty = porcelain.stdout.trim().length > 0;
|
|
579
|
+
status.stashes = await countStashes(localPath);
|
|
578
580
|
const aheadBehind = await gitIn(localPath, [
|
|
579
581
|
"rev-list",
|
|
580
582
|
"--left-right",
|
|
@@ -659,12 +661,20 @@ async function hasUnpushedCommits(localPath) {
|
|
|
659
661
|
if (result.code !== 0) return true;
|
|
660
662
|
return result.stdout.trim().length > 0;
|
|
661
663
|
}
|
|
664
|
+
async function countStashes(localPath) {
|
|
665
|
+
const result = await gitIn(localPath, ["stash", "list", "--format=%gd"]);
|
|
666
|
+
if (result.code !== 0) return 0;
|
|
667
|
+
return result.stdout.split("\n").filter((line) => line.trim().length > 0).length;
|
|
668
|
+
}
|
|
662
669
|
const SHORT_RE = /^([\w.-]+)\/([\w.-]+)$/;
|
|
663
670
|
const NAMED_RE = /^([\w.-]+):([\w.-]+)\/([\w.-]+)$/;
|
|
664
671
|
const SSH_RE = /^git@([\w.-]+):([\w.-]+)\/([\w.-]+?)(?:\.git)?$/;
|
|
665
672
|
function stripGitSuffix(repo) {
|
|
666
673
|
return repo.endsWith(".git") ? repo.slice(0, -4) : repo;
|
|
667
674
|
}
|
|
675
|
+
function looksLikeSlug(input) {
|
|
676
|
+
return input.trim().includes("/");
|
|
677
|
+
}
|
|
668
678
|
function parseSlug(input) {
|
|
669
679
|
const trimmed = input.trim();
|
|
670
680
|
if (!trimmed) {
|
|
@@ -723,6 +733,7 @@ async function evaluate(repo, cutoffUnix) {
|
|
|
723
733
|
if (lastCommitUnix === null || lastCommitUnix > cutoffUnix) return null;
|
|
724
734
|
const status = await getRepoStatus(repo.localPath);
|
|
725
735
|
const dirty = status.dirty;
|
|
736
|
+
const stashes = status.stashes;
|
|
726
737
|
const unpushed = await hasUnpushedCommits(repo.localPath);
|
|
727
738
|
let owner = repo.owner;
|
|
728
739
|
let name = repo.repo;
|
|
@@ -732,7 +743,16 @@ async function evaluate(repo, cutoffUnix) {
|
|
|
732
743
|
name = parsed.repo;
|
|
733
744
|
} catch {
|
|
734
745
|
}
|
|
735
|
-
return {
|
|
746
|
+
return {
|
|
747
|
+
repo,
|
|
748
|
+
origin,
|
|
749
|
+
owner,
|
|
750
|
+
name,
|
|
751
|
+
lastCommitUnix,
|
|
752
|
+
dirty,
|
|
753
|
+
unpushed,
|
|
754
|
+
stashes
|
|
755
|
+
};
|
|
736
756
|
}
|
|
737
757
|
async function classifyRemotes(candidates) {
|
|
738
758
|
const byType = /* @__PURE__ */ new Map();
|
|
@@ -832,6 +852,11 @@ const cleanupCommand = defineCommand({
|
|
|
832
852
|
description: "Also delete repos with unpushed commits (those commits are lost)",
|
|
833
853
|
default: false
|
|
834
854
|
},
|
|
855
|
+
"include-stashed": {
|
|
856
|
+
type: "boolean",
|
|
857
|
+
description: "Also delete repos with stashed work (that stash is lost)",
|
|
858
|
+
default: false
|
|
859
|
+
},
|
|
835
860
|
"no-cache": {
|
|
836
861
|
type: "boolean",
|
|
837
862
|
description: "Skip the scanned-repos cache",
|
|
@@ -865,7 +890,8 @@ const cleanupCommand = defineCommand({
|
|
|
865
890
|
)).filter((c) => c !== null);
|
|
866
891
|
const includeDirty = Boolean(args["include-dirty"]);
|
|
867
892
|
const includeUnpushed = Boolean(args["include-unpushed"]);
|
|
868
|
-
const
|
|
893
|
+
const includeStashed = Boolean(args["include-stashed"]);
|
|
894
|
+
const localOk = (c) => (!c.dirty || includeDirty) && (!c.unpushed || includeUnpushed) && (c.stashes === 0 || includeStashed);
|
|
869
895
|
const remoteStates = await classifyRemotes(stale.filter(localOk));
|
|
870
896
|
const candidates = [];
|
|
871
897
|
const kept = [];
|
|
@@ -874,6 +900,11 @@ const cleanupCommand = defineCommand({
|
|
|
874
900
|
kept.push({ repo: c, reason: "uncommitted changes" });
|
|
875
901
|
} else if (c.unpushed && !includeUnpushed) {
|
|
876
902
|
kept.push({ repo: c, reason: "unpushed commits" });
|
|
903
|
+
} else if (c.stashes > 0 && !includeStashed) {
|
|
904
|
+
kept.push({
|
|
905
|
+
repo: c,
|
|
906
|
+
reason: `stashed work (${c.stashes} stash${c.stashes === 1 ? "" : "es"})`
|
|
907
|
+
});
|
|
877
908
|
} else {
|
|
878
909
|
const state = remoteStates.get(c.repo.localPath)?.state;
|
|
879
910
|
if (state === "exists" || state === "moved") candidates.push(c);
|
|
@@ -896,7 +927,8 @@ const cleanupCommand = defineCommand({
|
|
|
896
927
|
for (const c of candidates) {
|
|
897
928
|
const flags = [
|
|
898
929
|
c.dirty ? colors.red("dirty") : "",
|
|
899
|
-
c.unpushed ? colors.red("unpushed") : ""
|
|
930
|
+
c.unpushed ? colors.red("unpushed") : "",
|
|
931
|
+
c.stashes > 0 ? colors.red(`stashed:${c.stashes}`) : ""
|
|
900
932
|
].filter(Boolean).join(" ");
|
|
901
933
|
process.stdout.write(
|
|
902
934
|
` ${colors.cyan(`${c.repo.forgeName}:${c.repo.slug}`)} ${colors.dim(`${ageDays(c.lastCommitUnix)}d idle`)}${flags ? ` ${flags}` : ""} ${colors.dim(c.repo.localPath)}
|
|
@@ -938,10 +970,12 @@ const cleanupCommand = defineCommand({
|
|
|
938
970
|
return;
|
|
939
971
|
}
|
|
940
972
|
if (candidates.length > 0) {
|
|
941
|
-
const losing = candidates.filter(
|
|
973
|
+
const losing = candidates.filter(
|
|
974
|
+
(c) => c.dirty || c.unpushed || c.stashes > 0
|
|
975
|
+
).length;
|
|
942
976
|
if (losing > 0) {
|
|
943
977
|
consola.warn(
|
|
944
|
-
`${losing} of these have uncommitted/unpushed work that will be permanently lost.`
|
|
978
|
+
`${losing} of these have uncommitted/unpushed/stashed work that will be permanently lost.`
|
|
945
979
|
);
|
|
946
980
|
}
|
|
947
981
|
let confirmed = args.yes;
|
|
@@ -2019,6 +2053,111 @@ const importCommand = defineCommand({
|
|
|
2019
2053
|
});
|
|
2020
2054
|
}
|
|
2021
2055
|
});
|
|
2056
|
+
const REPO_FUSE_OPTIONS = {
|
|
2057
|
+
keys: ["slug", "owner", "repo"],
|
|
2058
|
+
threshold: 0.3,
|
|
2059
|
+
ignoreLocation: true
|
|
2060
|
+
};
|
|
2061
|
+
function createRepoFuse(repos) {
|
|
2062
|
+
return new Fuse(repos, REPO_FUSE_OPTIONS);
|
|
2063
|
+
}
|
|
2064
|
+
function matchRepos(repos, query, limit) {
|
|
2065
|
+
const fuse = createRepoFuse(repos);
|
|
2066
|
+
const results = fuse.search(query, limit ? { limit } : void 0);
|
|
2067
|
+
return results.map((r) => r.item);
|
|
2068
|
+
}
|
|
2069
|
+
async function promptRepoChoice(candidates) {
|
|
2070
|
+
const out = process.stdout;
|
|
2071
|
+
const realWrite = out.write;
|
|
2072
|
+
const saved = {
|
|
2073
|
+
rows: Object.getOwnPropertyDescriptor(out, "rows"),
|
|
2074
|
+
columns: Object.getOwnPropertyDescriptor(out, "columns"),
|
|
2075
|
+
isTTY: Object.getOwnPropertyDescriptor(out, "isTTY")
|
|
2076
|
+
};
|
|
2077
|
+
const fake = (key, value) => {
|
|
2078
|
+
Object.defineProperty(out, key, { configurable: true, value });
|
|
2079
|
+
};
|
|
2080
|
+
const restore = (key) => {
|
|
2081
|
+
if (saved[key]) Object.defineProperty(out, key, saved[key]);
|
|
2082
|
+
else delete out[key];
|
|
2083
|
+
};
|
|
2084
|
+
out.write = process.stderr.write.bind(process.stderr);
|
|
2085
|
+
fake("rows", process.stderr.rows ?? 24);
|
|
2086
|
+
fake("columns", process.stderr.columns ?? 80);
|
|
2087
|
+
fake("isTTY", true);
|
|
2088
|
+
let choice;
|
|
2089
|
+
try {
|
|
2090
|
+
choice = await consola.prompt("Select a repo", {
|
|
2091
|
+
type: "select",
|
|
2092
|
+
options: candidates.map((r) => ({
|
|
2093
|
+
label: `${colors.gray(`${r.forgeName}:`)}${r.slug}`,
|
|
2094
|
+
value: r.localPath,
|
|
2095
|
+
hint: r.localPath
|
|
2096
|
+
}))
|
|
2097
|
+
});
|
|
2098
|
+
} finally {
|
|
2099
|
+
out.write = realWrite;
|
|
2100
|
+
restore("rows");
|
|
2101
|
+
restore("columns");
|
|
2102
|
+
restore("isTTY");
|
|
2103
|
+
}
|
|
2104
|
+
return typeof choice === "string" && choice ? choice : void 0;
|
|
2105
|
+
}
|
|
2106
|
+
function canPrompt() {
|
|
2107
|
+
return Boolean(process.stdin.isTTY);
|
|
2108
|
+
}
|
|
2109
|
+
async function locateRepo(input, options) {
|
|
2110
|
+
const { config, configDir } = options;
|
|
2111
|
+
if (!input.trim() || looksLikeSlug(input)) {
|
|
2112
|
+
const resolved = resolveSlug(parseSlug(input), { config, configDir });
|
|
2113
|
+
return { kind: "slug", localPath: resolved.localPath };
|
|
2114
|
+
}
|
|
2115
|
+
const repos = options.repos ?? await scanRepos({ config, configDir });
|
|
2116
|
+
const candidates = matchRepos(repos, input);
|
|
2117
|
+
if (candidates.length === 0) return { kind: "none", query: input };
|
|
2118
|
+
if (candidates.length === 1) {
|
|
2119
|
+
return {
|
|
2120
|
+
kind: "match",
|
|
2121
|
+
localPath: candidates[0].localPath,
|
|
2122
|
+
repo: candidates[0]
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
return { kind: "ambiguous", query: input, candidates };
|
|
2126
|
+
}
|
|
2127
|
+
async function resolveRepoPath(input, options) {
|
|
2128
|
+
const outcome = await locateRepo(input, options);
|
|
2129
|
+
switch (outcome.kind) {
|
|
2130
|
+
case "slug":
|
|
2131
|
+
case "match":
|
|
2132
|
+
return outcome.localPath;
|
|
2133
|
+
case "none":
|
|
2134
|
+
consola.error(`No cloned repo matches "${outcome.query}".`);
|
|
2135
|
+
process.stderr.write(
|
|
2136
|
+
`${colors.dim("Pass an explicit <owner>/<repo> for a repo that is not cloned yet.")}
|
|
2137
|
+
`
|
|
2138
|
+
);
|
|
2139
|
+
return null;
|
|
2140
|
+
case "ambiguous": {
|
|
2141
|
+
if (canPrompt()) {
|
|
2142
|
+
return await promptRepoChoice(outcome.candidates) ?? null;
|
|
2143
|
+
}
|
|
2144
|
+
consola.error(
|
|
2145
|
+
`"${outcome.query}" matches ${outcome.candidates.length} cloned repos:`
|
|
2146
|
+
);
|
|
2147
|
+
for (const c of outcome.candidates) {
|
|
2148
|
+
process.stderr.write(
|
|
2149
|
+
` ${colors.cyan(`${c.forgeName}:${c.slug}`)} ${colors.dim(c.localPath)}
|
|
2150
|
+
`
|
|
2151
|
+
);
|
|
2152
|
+
}
|
|
2153
|
+
process.stderr.write(
|
|
2154
|
+
`${colors.dim("Narrow the query, pass an explicit <owner>/<repo>, or run `forgemap pick` to choose interactively.")}
|
|
2155
|
+
`
|
|
2156
|
+
);
|
|
2157
|
+
return null;
|
|
2158
|
+
}
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2022
2161
|
function platformOpen(localPath) {
|
|
2023
2162
|
const distro = process.env.WSL_DISTRO_NAME;
|
|
2024
2163
|
if (distro) {
|
|
@@ -2038,7 +2177,7 @@ const openCommand = defineCommand({
|
|
|
2038
2177
|
args: {
|
|
2039
2178
|
slug: {
|
|
2040
2179
|
type: "positional",
|
|
2041
|
-
description: "owner/repo, forge:owner/repo, or
|
|
2180
|
+
description: "owner/repo, forge:owner/repo, full URL, or a fuzzy query matched against cloned repos",
|
|
2042
2181
|
required: true
|
|
2043
2182
|
},
|
|
2044
2183
|
config: {
|
|
@@ -2048,14 +2187,17 @@ const openCommand = defineCommand({
|
|
|
2048
2187
|
},
|
|
2049
2188
|
async run({ args }) {
|
|
2050
2189
|
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
2051
|
-
const parsed = parseSlug(args.slug);
|
|
2052
2190
|
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
2053
|
-
const
|
|
2191
|
+
const localPath = await resolveRepoPath(args.slug, {
|
|
2054
2192
|
config: loaded.config,
|
|
2055
2193
|
configDir
|
|
2056
2194
|
});
|
|
2057
|
-
|
|
2058
|
-
|
|
2195
|
+
if (!localPath) {
|
|
2196
|
+
process.exitCode = 1;
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
const { cmd, args: cmdArgs } = platformOpen(localPath);
|
|
2200
|
+
consola.info(`Opening ${localPath}`);
|
|
2059
2201
|
const child = spawn(cmd, cmdArgs, {
|
|
2060
2202
|
stdio: "ignore",
|
|
2061
2203
|
detached: true
|
|
@@ -2082,7 +2224,7 @@ const pathCommand = defineCommand({
|
|
|
2082
2224
|
args: {
|
|
2083
2225
|
slug: {
|
|
2084
2226
|
type: "positional",
|
|
2085
|
-
description: "owner/repo, forge:owner/repo, or
|
|
2227
|
+
description: "owner/repo, forge:owner/repo, full URL, or a fuzzy query matched against cloned repos",
|
|
2086
2228
|
required: true
|
|
2087
2229
|
},
|
|
2088
2230
|
config: {
|
|
@@ -2092,13 +2234,16 @@ const pathCommand = defineCommand({
|
|
|
2092
2234
|
},
|
|
2093
2235
|
async run({ args }) {
|
|
2094
2236
|
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
2095
|
-
const parsed = parseSlug(args.slug);
|
|
2096
2237
|
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
2097
|
-
const
|
|
2238
|
+
const localPath = await resolveRepoPath(args.slug, {
|
|
2098
2239
|
config: loaded.config,
|
|
2099
2240
|
configDir
|
|
2100
2241
|
});
|
|
2101
|
-
|
|
2242
|
+
if (!localPath) {
|
|
2243
|
+
process.exitCode = 1;
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
2246
|
+
process.stdout.write(`${localPath}
|
|
2102
2247
|
`);
|
|
2103
2248
|
}
|
|
2104
2249
|
});
|
|
@@ -2122,17 +2267,7 @@ const pickCommand = defineCommand({
|
|
|
2122
2267
|
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
2123
2268
|
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
2124
2269
|
const all = await scanRepos({ config: loaded.config, configDir });
|
|
2125
|
-
|
|
2126
|
-
if (args.query) {
|
|
2127
|
-
const fuse = new Fuse(all, {
|
|
2128
|
-
keys: ["slug", "owner", "repo"],
|
|
2129
|
-
threshold: 0.3,
|
|
2130
|
-
ignoreLocation: true
|
|
2131
|
-
});
|
|
2132
|
-
candidates = fuse.search(args.query).map((r) => r.item);
|
|
2133
|
-
} else {
|
|
2134
|
-
candidates = all;
|
|
2135
|
-
}
|
|
2270
|
+
const candidates = args.query ? matchRepos(all, args.query) : all;
|
|
2136
2271
|
if (candidates.length === 0) {
|
|
2137
2272
|
consola.error(
|
|
2138
2273
|
args.query ? `No repos match "${args.query}".` : "No repos found under the configured root."
|
|
@@ -2145,49 +2280,16 @@ const pickCommand = defineCommand({
|
|
|
2145
2280
|
`);
|
|
2146
2281
|
return;
|
|
2147
2282
|
}
|
|
2148
|
-
if (!
|
|
2283
|
+
if (!canPrompt()) {
|
|
2149
2284
|
consola.error(
|
|
2150
2285
|
"pick requires an interactive terminal. Use `forgemap search` for non-interactive output."
|
|
2151
2286
|
);
|
|
2152
2287
|
process.exitCode = 1;
|
|
2153
2288
|
return;
|
|
2154
2289
|
}
|
|
2155
|
-
const
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
rows: Object.getOwnPropertyDescriptor(out, "rows"),
|
|
2159
|
-
columns: Object.getOwnPropertyDescriptor(out, "columns"),
|
|
2160
|
-
isTTY: Object.getOwnPropertyDescriptor(out, "isTTY")
|
|
2161
|
-
};
|
|
2162
|
-
const fake = (key, value) => {
|
|
2163
|
-
Object.defineProperty(out, key, { configurable: true, value });
|
|
2164
|
-
};
|
|
2165
|
-
const restore = (key) => {
|
|
2166
|
-
if (saved[key]) Object.defineProperty(out, key, saved[key]);
|
|
2167
|
-
else delete out[key];
|
|
2168
|
-
};
|
|
2169
|
-
out.write = process.stderr.write.bind(process.stderr);
|
|
2170
|
-
fake("rows", process.stderr.rows ?? 24);
|
|
2171
|
-
fake("columns", process.stderr.columns ?? 80);
|
|
2172
|
-
fake("isTTY", true);
|
|
2173
|
-
let choice;
|
|
2174
|
-
try {
|
|
2175
|
-
choice = await consola.prompt("Select a repo", {
|
|
2176
|
-
type: "select",
|
|
2177
|
-
options: candidates.map((r) => ({
|
|
2178
|
-
label: `${colors.gray(`${r.forgeName}:`)}${r.slug}`,
|
|
2179
|
-
value: r.localPath,
|
|
2180
|
-
hint: r.localPath
|
|
2181
|
-
}))
|
|
2182
|
-
});
|
|
2183
|
-
} finally {
|
|
2184
|
-
out.write = realWrite;
|
|
2185
|
-
restore("rows");
|
|
2186
|
-
restore("columns");
|
|
2187
|
-
restore("isTTY");
|
|
2188
|
-
}
|
|
2189
|
-
if (typeof choice === "string" && choice) {
|
|
2190
|
-
realWrite.call(out, `${choice}
|
|
2290
|
+
const choice = await promptRepoChoice(candidates);
|
|
2291
|
+
if (choice) {
|
|
2292
|
+
process.stdout.write(`${choice}
|
|
2191
2293
|
`);
|
|
2192
2294
|
}
|
|
2193
2295
|
}
|
|
@@ -2245,15 +2347,8 @@ const searchCommand = defineCommand({
|
|
|
2245
2347
|
const loaded = await loadForgeMapConfig({ configFile: args.config });
|
|
2246
2348
|
const configDir = loaded.configFile ? dirname(loaded.configFile) : loaded.cwd;
|
|
2247
2349
|
const repos = await scanRepos({ config: loaded.config, configDir });
|
|
2248
|
-
const fuse = new Fuse(repos, {
|
|
2249
|
-
keys: ["slug", "owner", "repo"],
|
|
2250
|
-
threshold: 0.3,
|
|
2251
|
-
ignoreLocation: true,
|
|
2252
|
-
includeScore: true
|
|
2253
|
-
});
|
|
2254
2350
|
const limit = args.limit ? Number.parseInt(args.limit, 10) : void 0;
|
|
2255
|
-
const
|
|
2256
|
-
const items = results.map((r) => r.item);
|
|
2351
|
+
const items = matchRepos(repos, args.query, limit);
|
|
2257
2352
|
const allowed = ["auto", "pretty", "path", "slug"];
|
|
2258
2353
|
if (!allowed.includes(args.format)) {
|
|
2259
2354
|
consola.error(
|
|
@@ -2419,6 +2514,7 @@ function statusLine(row) {
|
|
|
2419
2514
|
if (s.behind > 0) aheadBehind.push(colors.yellow(`↓${s.behind}`));
|
|
2420
2515
|
if (aheadBehind.length > 0) parts.push(aheadBehind.join(" "));
|
|
2421
2516
|
parts.push(s.dirty ? colors.red("●") : colors.green("✓"));
|
|
2517
|
+
if (s.stashes > 0) parts.push(colors.yellow(`⚑${s.stashes}`));
|
|
2422
2518
|
parts.push(colors.gray(s.branch));
|
|
2423
2519
|
if (s.lastCommit) {
|
|
2424
2520
|
parts.push(colors.dim(`${s.lastCommit.sha} ${s.lastCommit.relativeDate}`));
|