@theholocron/cli 5.0.0-alpha.145 → 5.0.0-alpha.147
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/cli.mjs +58 -129
- package/dist/cli.mjs.map +1 -1
- package/dist/index.mjs +4 -24
- package/package.json +9 -6
package/dist/cli.mjs
CHANGED
|
@@ -17,9 +17,12 @@ import { errorMessage } from "@theholocron/misc-utils";
|
|
|
17
17
|
import { createLogger, isCI, parseLogLevel, resolveAxiomFromEnv } from "@theholocron/observability/logger";
|
|
18
18
|
import chalk from "chalk";
|
|
19
19
|
import ora from "ora";
|
|
20
|
-
import {
|
|
20
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
21
|
+
import { stringifyJson } from "@theholocron/json-utils";
|
|
22
|
+
import { readGitRemote, updateGitignore } from "@theholocron/git-utils";
|
|
21
23
|
import { access, copyFile, mkdir, readFile, readdir, rename, rm, stat, symlink, unlink, writeFile } from "node:fs/promises";
|
|
22
24
|
import { createHash, randomUUID } from "node:crypto";
|
|
25
|
+
import { findRootSync } from "@theholocron/fs-utils";
|
|
23
26
|
import { generateReadme } from "@theholocron/components-doc/markdown";
|
|
24
27
|
import { getClients, getConfigs, getDocs, getPlugins, getSkills, getThemes, getUtils } from "@theholocron/registry-doc";
|
|
25
28
|
import { createGitHubClient } from "@theholocron/github-client";
|
|
@@ -27,7 +30,6 @@ import { PostHogSink } from "@theholocron/observability/analytics";
|
|
|
27
30
|
import { SentrySink } from "@theholocron/observability/errors";
|
|
28
31
|
import load from "@commitlint/load";
|
|
29
32
|
import lint from "@commitlint/lint";
|
|
30
|
-
import { promisify } from "node:util";
|
|
31
33
|
import { ConfigFileError, loadConfigFile } from "@theholocron/datapad";
|
|
32
34
|
import search from "@inquirer/search";
|
|
33
35
|
//#region src/env.ts
|
|
@@ -2275,19 +2277,21 @@ async function runNew(input) {
|
|
|
2275
2277
|
const filesPatched = patchFiles(repoDir, variants, input.description, input.homepage, input.runtimeEnvironment, print, readFn, writeFn, walkFn);
|
|
2276
2278
|
print(` ${filesPatched.length} file${filesPatched.length === 1 ? "" : "s"} patched`);
|
|
2277
2279
|
if (input.description !== void 0 || input.homepage !== void 0) try {
|
|
2278
|
-
const
|
|
2280
|
+
const raw = readFn(pkgJsonPath);
|
|
2281
|
+
const pkg = JSON.parse(raw);
|
|
2279
2282
|
if (input.description !== void 0) pkg["description"] = input.description;
|
|
2280
2283
|
if (input.homepage !== void 0) pkg["homepage"] = input.homepage;
|
|
2281
|
-
writeFn(pkgJsonPath,
|
|
2284
|
+
writeFn(pkgJsonPath, stringifyJson(pkg, { like: raw }));
|
|
2282
2285
|
print(` ✓ package.json (description/homepage)`);
|
|
2283
2286
|
} catch {}
|
|
2284
2287
|
const displayName = input.name.split("-").map(cap).join(" ");
|
|
2285
2288
|
const rootTsconfigPath = path.join(repoDir, "tsconfig.json");
|
|
2286
2289
|
try {
|
|
2287
|
-
const
|
|
2290
|
+
const rawTsconfig = readFn(rootTsconfigPath);
|
|
2291
|
+
const tsconfig = JSON.parse(rawTsconfig);
|
|
2288
2292
|
if (typeof tsconfig["display"] === "string") {
|
|
2289
2293
|
tsconfig["display"] = displayName;
|
|
2290
|
-
writeFn(rootTsconfigPath,
|
|
2294
|
+
writeFn(rootTsconfigPath, stringifyJson(tsconfig, { like: rawTsconfig }));
|
|
2291
2295
|
print(` ✓ tsconfig.json (display)`);
|
|
2292
2296
|
}
|
|
2293
2297
|
} catch {}
|
|
@@ -2407,8 +2411,10 @@ async function runNpmBumpVersions(input) {
|
|
|
2407
2411
|
}, "npm bump-versions: start");
|
|
2408
2412
|
function bumpFile(absPath, label) {
|
|
2409
2413
|
let pkg;
|
|
2414
|
+
let raw;
|
|
2410
2415
|
try {
|
|
2411
|
-
|
|
2416
|
+
raw = readFile(absPath);
|
|
2417
|
+
pkg = JSON.parse(raw);
|
|
2412
2418
|
} catch {
|
|
2413
2419
|
print(` ✗ could not parse ${label}`);
|
|
2414
2420
|
return;
|
|
@@ -2416,7 +2422,7 @@ async function runNpmBumpVersions(input) {
|
|
|
2416
2422
|
const old = pkg.version;
|
|
2417
2423
|
if (!dryRun) {
|
|
2418
2424
|
pkg.version = version;
|
|
2419
|
-
writeFile(absPath,
|
|
2425
|
+
writeFile(absPath, stringifyJson(pkg, { like: raw }));
|
|
2420
2426
|
}
|
|
2421
2427
|
print(` ${dryRun ? "~" : "✓"} ${label}: ${old} → ${version}`);
|
|
2422
2428
|
bumped.push(label);
|
|
@@ -4154,13 +4160,11 @@ function namesInWorkspaceDir(cwd, dirName) {
|
|
|
4154
4160
|
});
|
|
4155
4161
|
}
|
|
4156
4162
|
async function resolveRepoName(cwd, exec) {
|
|
4157
|
-
|
|
4158
|
-
"
|
|
4159
|
-
"
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
if (result.exitCode !== 0) return "unknown";
|
|
4163
|
-
return /[/:]([^/:]+?)(?:\.git)?$/.exec(result.stdout.trim())?.[1] ?? "unknown";
|
|
4163
|
+
return (await readGitRemote(cwd, { exec: async (args, options) => {
|
|
4164
|
+
const result = await exec("git", args, options);
|
|
4165
|
+
if (result.exitCode !== 0) throw new Error(`git ${args.join(" ")} exited ${result.exitCode}`);
|
|
4166
|
+
return result.stdout;
|
|
4167
|
+
} }))?.repo ?? "unknown";
|
|
4164
4168
|
}
|
|
4165
4169
|
function printNextSteps(print, env, packageNames, repoName) {
|
|
4166
4170
|
print("");
|
|
@@ -4710,7 +4714,7 @@ var alexrc_default = { allow: [
|
|
|
4710
4714
|
//#region src/templates/configs/alexjs/create-config.ts
|
|
4711
4715
|
const { workflowHeader: workflowHeader$9 } = createHeader({ source: "packages/cli/src/templates/configs/alexjs/create-config.ts" });
|
|
4712
4716
|
function createRcConfig() {
|
|
4713
|
-
return
|
|
4717
|
+
return stringifyJson(alexrc_default);
|
|
4714
4718
|
}
|
|
4715
4719
|
function createIgnoreConfig$1() {
|
|
4716
4720
|
return `${workflowHeader$9()}${alexignore_default}`;
|
|
@@ -4794,7 +4798,7 @@ var editorconfig_checker_default = {
|
|
|
4794
4798
|
* buys nothing.
|
|
4795
4799
|
*/
|
|
4796
4800
|
function createConfig$4() {
|
|
4797
|
-
return
|
|
4801
|
+
return stringifyJson(editorconfig_checker_default);
|
|
4798
4802
|
}
|
|
4799
4803
|
//#endregion
|
|
4800
4804
|
//#region src/templates/configs/pre-commit/pre-commit
|
|
@@ -5264,21 +5268,11 @@ async function installAgentPrompts({ repoRoot }) {
|
|
|
5264
5268
|
const promptsDir = join(repoRoot, AGENTS_PROMPTS_ROOT);
|
|
5265
5269
|
await mkdir(promptsDir, { recursive: true });
|
|
5266
5270
|
for (const [filename, content] of Object.entries(AGENT_PROMPTS)) await writeFile(join(promptsDir, filename), content, "utf8");
|
|
5267
|
-
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
PROMPTS_GITIGNORE_END
|
|
5273
|
-
].join("\n");
|
|
5274
|
-
let updated;
|
|
5275
|
-
if (existing.includes(PROMPTS_GITIGNORE_START)) {
|
|
5276
|
-
const start = existing.indexOf(PROMPTS_GITIGNORE_START);
|
|
5277
|
-
const end = existing.indexOf(PROMPTS_GITIGNORE_END, start);
|
|
5278
|
-
const afterBlock = end !== -1 ? existing.slice(end + 41) : "\n";
|
|
5279
|
-
updated = existing.slice(0, start) + block + afterBlock;
|
|
5280
|
-
} else updated = (existing.trimEnd() ? existing.trimEnd() + "\n\n" : "") + block + "\n";
|
|
5281
|
-
await writeFile(gitignorePath, updated, "utf8");
|
|
5271
|
+
await updateGitignore(join(repoRoot, ".gitignore"), {
|
|
5272
|
+
start: PROMPTS_GITIGNORE_START,
|
|
5273
|
+
end: PROMPTS_GITIGNORE_END,
|
|
5274
|
+
lines: [`/${AGENTS_PROMPTS_ROOT}/`]
|
|
5275
|
+
});
|
|
5282
5276
|
return `wrote ${Object.keys(AGENT_PROMPTS).length} prompt files to ${AGENTS_PROMPTS_ROOT}/`;
|
|
5283
5277
|
}
|
|
5284
5278
|
//#endregion
|
|
@@ -5548,8 +5542,7 @@ async function installSkills({ agent, skills, repoRoot }) {
|
|
|
5548
5542
|
}
|
|
5549
5543
|
}
|
|
5550
5544
|
const gitignorePath = join(repoRoot, ".gitignore");
|
|
5551
|
-
const
|
|
5552
|
-
const previouslyInstalled = parsePreviousSkills(existingContent, symlinkFn);
|
|
5545
|
+
const previouslyInstalled = parsePreviousSkills(await readFile(gitignorePath, "utf8").catch(() => ""), symlinkFn);
|
|
5553
5546
|
const currentSet = new Set(skills);
|
|
5554
5547
|
const stale = previouslyInstalled.filter((n) => !currentSet.has(n));
|
|
5555
5548
|
for (const name of stale) {
|
|
@@ -5609,7 +5602,7 @@ async function installSkills({ agent, skills, repoRoot }) {
|
|
|
5609
5602
|
}
|
|
5610
5603
|
}
|
|
5611
5604
|
}
|
|
5612
|
-
if (installed.length > 0 || stale.length > 0 || missing.length > 0 || externalFailed.length > 0) await updateSkillsGitignore(gitignorePath,
|
|
5605
|
+
if (installed.length > 0 || stale.length > 0 || missing.length > 0 || externalFailed.length > 0) await updateSkillsGitignore(gitignorePath, [
|
|
5613
5606
|
...installed,
|
|
5614
5607
|
...missing,
|
|
5615
5608
|
...externalFailed
|
|
@@ -5621,14 +5614,24 @@ async function installSkills({ agent, skills, repoRoot }) {
|
|
|
5621
5614
|
if (missing.length > 0) parts.push(`unknown: ${missing.join(", ")}`);
|
|
5622
5615
|
return parts.join("; ");
|
|
5623
5616
|
}
|
|
5617
|
+
/**
|
|
5618
|
+
* The skill names a previous `holocron setup` recorded in the `.gitignore`
|
|
5619
|
+
* managed block. A block missing its end marker (hand-edited, truncated) runs
|
|
5620
|
+
* only to the next blank line — the same rule `updateGitignore` uses to
|
|
5621
|
+
* replace it — so user-written ignore rules further down are never mistaken
|
|
5622
|
+
* for installed skills and pruned.
|
|
5623
|
+
*/
|
|
5624
5624
|
function parsePreviousSkills(gitignoreContent, symlinkFn) {
|
|
5625
|
-
if (!gitignoreContent.includes(GITIGNORE_BLOCK_START)) return [];
|
|
5626
5625
|
const startIdx = gitignoreContent.indexOf(GITIGNORE_BLOCK_START);
|
|
5626
|
+
if (startIdx === -1) return [];
|
|
5627
5627
|
const endIdx = gitignoreContent.indexOf(GITIGNORE_BLOCK_END, startIdx);
|
|
5628
|
-
const
|
|
5628
|
+
const blankAt = /\r?\n[ \t]*\r?\n/.exec(gitignoreContent.slice(startIdx));
|
|
5629
|
+
const blankIdx = blankAt ? startIdx + blankAt.index : -1;
|
|
5630
|
+
const blockEnd = endIdx !== -1 && (blankIdx === -1 || endIdx < blankIdx) ? endIdx : blankIdx === -1 ? gitignoreContent.length : blankIdx;
|
|
5631
|
+
const block = gitignoreContent.slice(startIdx, blockEnd);
|
|
5629
5632
|
const placeholder = "__placeholder__";
|
|
5630
5633
|
const symlinkPrefix = `/${symlinkFn(placeholder)}`.replace(placeholder, "");
|
|
5631
|
-
return block.split(
|
|
5634
|
+
return block.split(/\r?\n/).filter((line) => line.startsWith(symlinkPrefix)).map((line) => line.slice(symlinkPrefix.length));
|
|
5632
5635
|
}
|
|
5633
5636
|
async function copyDirRecursive(src, dest) {
|
|
5634
5637
|
await mkdir(dest, { recursive: true });
|
|
@@ -5640,21 +5643,12 @@ async function copyDirRecursive(src, dest) {
|
|
|
5640
5643
|
else await copyFile(srcPath, destPath);
|
|
5641
5644
|
}
|
|
5642
5645
|
}
|
|
5643
|
-
async function updateSkillsGitignore(gitignorePath,
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
...
|
|
5648
|
-
|
|
5649
|
-
].join("\n");
|
|
5650
|
-
let content;
|
|
5651
|
-
if (existingContent.includes(GITIGNORE_BLOCK_START)) {
|
|
5652
|
-
const start = existingContent.indexOf(GITIGNORE_BLOCK_START);
|
|
5653
|
-
const end = existingContent.indexOf(GITIGNORE_BLOCK_END, start);
|
|
5654
|
-
const afterBlock = end !== -1 ? existingContent.slice(end + 40) : "\n";
|
|
5655
|
-
content = existingContent.slice(0, start) + block + afterBlock;
|
|
5656
|
-
} else content = (existingContent.trimEnd() ? existingContent.trimEnd() + "\n\n" : "") + block + "\n";
|
|
5657
|
-
await writeFile(gitignorePath, content, "utf8");
|
|
5646
|
+
async function updateSkillsGitignore(gitignorePath, skills, symlinkFn) {
|
|
5647
|
+
await updateGitignore(gitignorePath, {
|
|
5648
|
+
start: GITIGNORE_BLOCK_START,
|
|
5649
|
+
end: GITIGNORE_BLOCK_END,
|
|
5650
|
+
lines: [`/${AGENTS_SKILLS_ROOT}/`, ...skills.map((n) => `/${symlinkFn(n)}`)]
|
|
5651
|
+
});
|
|
5658
5652
|
}
|
|
5659
5653
|
//#endregion
|
|
5660
5654
|
//#region src/commands/setup/run-setup.ts
|
|
@@ -6359,7 +6353,7 @@ async function ensurePrepareScript(repoRoot) {
|
|
|
6359
6353
|
if (/(^|[\s&|;])husky($|[\s&|;])/.test(current)) return false;
|
|
6360
6354
|
scripts.prepare = current ? `${current} && husky` : "husky";
|
|
6361
6355
|
pkg.scripts = scripts;
|
|
6362
|
-
await writeFile(pkgPath,
|
|
6356
|
+
await writeFile(pkgPath, stringifyJson(pkg, { like: content }), "utf8");
|
|
6363
6357
|
return true;
|
|
6364
6358
|
}
|
|
6365
6359
|
//#endregion
|
|
@@ -6462,55 +6456,6 @@ function runSkillsUpdate(input) {
|
|
|
6462
6456
|
return { status: exitCode === 0 ? "ok" : "fail" };
|
|
6463
6457
|
}
|
|
6464
6458
|
//#endregion
|
|
6465
|
-
//#region src/utils/find-git-root.ts
|
|
6466
|
-
/**
|
|
6467
|
-
* Walks up from `startDir` looking for a `.git` entry — a directory for a
|
|
6468
|
-
* normal checkout, a file for a worktree/submodule (contains a `gitdir:`
|
|
6469
|
-
* pointer, never followed here; its presence is enough to mark the root).
|
|
6470
|
-
*
|
|
6471
|
-
* `--cwd` picks which `holocron.config.ts` to load, which is NOT
|
|
6472
|
-
* necessarily the git repository root — a monorepo subpackage (e.g.
|
|
6473
|
-
* `packages/sentinel`) has its own config but no `.git` of its own.
|
|
6474
|
-
* Anything inherently repo-wide (`.github/workflows`, `.github/CODEOWNERS`,
|
|
6475
|
-
* `turbo.json`, "is this repo a monorepo") must resolve against the real
|
|
6476
|
-
* root, not `--cwd`, or it silently writes into a GitHub-invisible nested
|
|
6477
|
-
* `.github/` instead (holocron#1014).
|
|
6478
|
-
*
|
|
6479
|
-
* Falls back to the resolved start dir when no `.git` is found walking all
|
|
6480
|
-
* the way to the filesystem root — soft-skip, not a hard failure; every
|
|
6481
|
-
* caller already treats `--cwd` itself as the root today, so an
|
|
6482
|
-
* unexpected non-git checkout just keeps today's behavior.
|
|
6483
|
-
*
|
|
6484
|
-
* Resolves `startDir` to an absolute, symlink-free path before walking:
|
|
6485
|
-
* a relative `--cwd` (e.g. `"packages/sentinel"`) would otherwise hit
|
|
6486
|
-
* `dirname`'s fixed point at `"."` after only two steps, far short of the
|
|
6487
|
-
* real root, and a `--cwd` reached through a symlink would walk the
|
|
6488
|
-
* symlink's own lexical ancestors instead of the physical directory
|
|
6489
|
-
* tree its target actually lives in — in both cases landing on the
|
|
6490
|
-
* wrong directory, not genuinely "no `.git` found anywhere."
|
|
6491
|
-
*
|
|
6492
|
-
* `realpathSync` itself throws when `startDir` doesn't exist on disk at
|
|
6493
|
-
* all (a real possibility in tests, which often use a synthetic path
|
|
6494
|
-
* like `"/tmp/test"` that's never actually created) — caught and treated
|
|
6495
|
-
* the same as "no `.git` found," since this function's whole contract is
|
|
6496
|
-
* soft-skip, never a hard failure.
|
|
6497
|
-
*/
|
|
6498
|
-
function findGitRoot(startDir) {
|
|
6499
|
-
let resolvedStart;
|
|
6500
|
-
try {
|
|
6501
|
-
resolvedStart = realpathSync(startDir);
|
|
6502
|
-
} catch {
|
|
6503
|
-
return startDir;
|
|
6504
|
-
}
|
|
6505
|
-
let dir = resolvedStart;
|
|
6506
|
-
for (;;) {
|
|
6507
|
-
if (existsSync(`${dir}/.git`)) return dir;
|
|
6508
|
-
const parent = dirname(dir);
|
|
6509
|
-
if (parent === dir) return resolvedStart;
|
|
6510
|
-
dir = parent;
|
|
6511
|
-
}
|
|
6512
|
-
}
|
|
6513
|
-
//#endregion
|
|
6514
6459
|
//#region src/commands/sync-readme.ts
|
|
6515
6460
|
const MARKER_INSTALL_START = "<!-- holocron:installation -->";
|
|
6516
6461
|
const MARKER_INSTALL_END = "<!-- /holocron:installation -->";
|
|
@@ -6917,7 +6862,10 @@ async function runSync(input) {
|
|
|
6917
6862
|
const dryRun = input.context.dryRun ?? false;
|
|
6918
6863
|
const requestedSteps = input.steps;
|
|
6919
6864
|
const steps = [];
|
|
6920
|
-
const gitRoot =
|
|
6865
|
+
const gitRoot = findRootSync(input.context.repoRoot, ".git", {
|
|
6866
|
+
fallback: "start",
|
|
6867
|
+
realpath: true
|
|
6868
|
+
});
|
|
6921
6869
|
let resolvedRepoRoot;
|
|
6922
6870
|
try {
|
|
6923
6871
|
resolvedRepoRoot = realpathSync(input.context.repoRoot);
|
|
@@ -7365,7 +7313,7 @@ async function writePackageJsonField(repoRoot, field, value) {
|
|
|
7365
7313
|
}
|
|
7366
7314
|
const pkg = JSON.parse(content);
|
|
7367
7315
|
pkg[field] = value;
|
|
7368
|
-
await writeFile(pkgPath,
|
|
7316
|
+
await writeFile(pkgPath, stringifyJson(pkg, { like: content }), "utf8");
|
|
7369
7317
|
return true;
|
|
7370
7318
|
}
|
|
7371
7319
|
/**
|
|
@@ -7407,7 +7355,7 @@ async function mergePackageJsonScripts(repoRoot, desired, turboEligible) {
|
|
|
7407
7355
|
}
|
|
7408
7356
|
if (changed.length === 0) return [];
|
|
7409
7357
|
pkg.scripts = scripts;
|
|
7410
|
-
await writeFile(pkgPath,
|
|
7358
|
+
await writeFile(pkgPath, stringifyJson(pkg, { like: content }), "utf8");
|
|
7411
7359
|
return changed.sort();
|
|
7412
7360
|
}
|
|
7413
7361
|
/**
|
|
@@ -8146,7 +8094,7 @@ function patchPackageJson(content, from, to) {
|
|
|
8146
8094
|
changed = true;
|
|
8147
8095
|
}
|
|
8148
8096
|
}
|
|
8149
|
-
return changed ?
|
|
8097
|
+
return changed ? stringifyJson(pkg, { like: content }) : null;
|
|
8150
8098
|
}
|
|
8151
8099
|
function patchYaml(content, from, to) {
|
|
8152
8100
|
const updated = content.replace(/node-version:\s+['"]?(\d+)['"]?/g, (match, ver) => ver === String(from) ? match.replace(String(from), String(to)) : match);
|
|
@@ -8369,7 +8317,6 @@ async function lintCommitMsgFile(filePath, cwd = process.cwd()) {
|
|
|
8369
8317
|
* Probe order is TS-first (`.ts` → `.js` → `.mjs` → `.cjs` → `.json`) —
|
|
8370
8318
|
* see ADR-0010 / issue #75 / #81.
|
|
8371
8319
|
*/
|
|
8372
|
-
const execFileAsync = promisify(execFile);
|
|
8373
8320
|
/**
|
|
8374
8321
|
* Read + parse + resolve `holocron.config.*` starting from the given
|
|
8375
8322
|
* directory, walking up through ancestors to the nearest `.git` boundary
|
|
@@ -8395,10 +8342,10 @@ async function deriveDefaults(configDir, raw) {
|
|
|
8395
8342
|
const result = { ...raw };
|
|
8396
8343
|
if (!result.name) result.name = await readPackageJsonName(configDir) ?? basename(configDir);
|
|
8397
8344
|
if (result.repo && !result.repo.name) {
|
|
8398
|
-
const
|
|
8399
|
-
if (
|
|
8345
|
+
const remote = await readGitRemote(configDir);
|
|
8346
|
+
if (remote?.host === "github.com") result.repo = {
|
|
8400
8347
|
...result.repo,
|
|
8401
|
-
name:
|
|
8348
|
+
name: remote.slug
|
|
8402
8349
|
};
|
|
8403
8350
|
}
|
|
8404
8351
|
const manifest = await loadTasksConfig(configDir);
|
|
@@ -8418,24 +8365,6 @@ async function readPackageJsonName(dir) {
|
|
|
8418
8365
|
return;
|
|
8419
8366
|
}
|
|
8420
8367
|
}
|
|
8421
|
-
async function readGitRemote(dir) {
|
|
8422
|
-
try {
|
|
8423
|
-
const { stdout } = await execFileAsync("git", [
|
|
8424
|
-
"remote",
|
|
8425
|
-
"get-url",
|
|
8426
|
-
"origin"
|
|
8427
|
-
], { cwd: dir });
|
|
8428
|
-
return parseGitRemoteUrl(stdout.trim());
|
|
8429
|
-
} catch {
|
|
8430
|
-
return;
|
|
8431
|
-
}
|
|
8432
|
-
}
|
|
8433
|
-
function parseGitRemoteUrl(url) {
|
|
8434
|
-
const httpsMatch = url.match(/github\.com\/([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
8435
|
-
if (httpsMatch) return httpsMatch[1];
|
|
8436
|
-
const sshMatch = url.match(/github\.com:([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
8437
|
-
if (sshMatch) return sshMatch[1];
|
|
8438
|
-
}
|
|
8439
8368
|
//#endregion
|
|
8440
8369
|
//#region src/interactive-menu.ts
|
|
8441
8370
|
/**
|