@rness/cli 0.5.2 → 0.6.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/README.md +57 -2
- package/dist/bin/rness.js +6 -40
- package/dist/bin/rness.js.map +1 -1
- package/dist/{chunk-P4YG6P7I.js → chunk-NY3WV7X5.js} +587 -345
- package/dist/chunk-NY3WV7X5.js.map +1 -0
- package/dist/index.js +8 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/scaffold/.github/dependabot.yml +8 -0
- package/dist/chunk-P4YG6P7I.js.map +0 -1
|
@@ -725,7 +725,7 @@ function classifyProbe(url, exitCode, stderr) {
|
|
|
725
725
|
var STDERR_FLUSH_MS = 200;
|
|
726
726
|
async function probeRemote(url, timeoutMs = 6e4, credentials) {
|
|
727
727
|
positional(url, "repository url");
|
|
728
|
-
return new Promise((
|
|
728
|
+
return new Promise((resolve5) => {
|
|
729
729
|
const auth = withCredentials(credentials);
|
|
730
730
|
const child = spawn(
|
|
731
731
|
"git",
|
|
@@ -744,7 +744,7 @@ async function probeRemote(url, timeoutMs = 6e4, credentials) {
|
|
|
744
744
|
settled = true;
|
|
745
745
|
clearTimeout(timer);
|
|
746
746
|
child.stderr?.destroy();
|
|
747
|
-
|
|
747
|
+
resolve5(probe);
|
|
748
748
|
};
|
|
749
749
|
const timer = setTimeout(() => {
|
|
750
750
|
child.kill("SIGKILL");
|
|
@@ -814,13 +814,8 @@ function readOrg(value) {
|
|
|
814
814
|
}
|
|
815
815
|
return value;
|
|
816
816
|
}
|
|
817
|
-
function
|
|
818
|
-
|
|
819
|
-
const name = basename2(root);
|
|
820
|
-
return {
|
|
821
|
-
org: name,
|
|
822
|
-
warning: `rness.json: no "org"; using the directory name "${name}"`
|
|
823
|
-
};
|
|
817
|
+
function workspaceName(manifest, root) {
|
|
818
|
+
return manifest.org ?? basename2(root);
|
|
824
819
|
}
|
|
825
820
|
function readRepos(value) {
|
|
826
821
|
const raw = value ?? {};
|
|
@@ -915,8 +910,13 @@ function parseRepoSpec(spec, org, host) {
|
|
|
915
910
|
url = spec;
|
|
916
911
|
} else {
|
|
917
912
|
const parts = spec.split("/");
|
|
918
|
-
if (parts.length === 1)
|
|
919
|
-
|
|
913
|
+
if (parts.length === 1) {
|
|
914
|
+
if (org === null)
|
|
915
|
+
throw new Error(
|
|
916
|
+
"this workspace has no GitHub organization: give <owner>/<repo> or a git URL"
|
|
917
|
+
);
|
|
918
|
+
url = repoUrl(host, org, spec);
|
|
919
|
+
} else if (parts.length === 2 && parts[0] !== void 0 && parts[1] !== void 0)
|
|
920
920
|
url = repoUrl(host, parts[0], parts[1]);
|
|
921
921
|
else
|
|
922
922
|
throw new Error(
|
|
@@ -1162,7 +1162,7 @@ async function detectGithubSsh(opts) {
|
|
|
1162
1162
|
...opts.interactive ? [] : ["-o", "BatchMode=yes"],
|
|
1163
1163
|
"git@github.com"
|
|
1164
1164
|
];
|
|
1165
|
-
return new Promise((
|
|
1165
|
+
return new Promise((resolve5) => {
|
|
1166
1166
|
const child = spawn2(opts.command ?? "ssh", args, {
|
|
1167
1167
|
stdio: [opts.interactive ? "inherit" : "ignore", "ignore", "pipe"]
|
|
1168
1168
|
});
|
|
@@ -1175,7 +1175,7 @@ async function detectGithubSsh(opts) {
|
|
|
1175
1175
|
settled = true;
|
|
1176
1176
|
clearTimeout(timer);
|
|
1177
1177
|
child.stderr?.destroy();
|
|
1178
|
-
|
|
1178
|
+
resolve5(access2);
|
|
1179
1179
|
};
|
|
1180
1180
|
const timer = setTimeout(() => {
|
|
1181
1181
|
child.kill("SIGKILL");
|
|
@@ -1828,9 +1828,8 @@ async function syncCommand(opts, deps = {}) {
|
|
|
1828
1828
|
try {
|
|
1829
1829
|
const ws = await findWorkspace(cwd);
|
|
1830
1830
|
const manifest = await loadManifest(ws.rnessDir);
|
|
1831
|
-
const
|
|
1831
|
+
const org = workspaceName(manifest, ws.root);
|
|
1832
1832
|
if (ownUi) ui.intro(`Sync workspace ${org}`);
|
|
1833
|
-
if (warning !== null) ui.warn(warning);
|
|
1834
1833
|
if (opts.scope !== void 0 && !Object.hasOwn(manifest.scopes, opts.scope)) {
|
|
1835
1834
|
process.stderr.write(`unknown scope: ${opts.scope}
|
|
1836
1835
|
`);
|
|
@@ -2035,9 +2034,7 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
2035
2034
|
try {
|
|
2036
2035
|
const ws = await findWorkspace(cwd);
|
|
2037
2036
|
const manifest = await loadManifest(ws.rnessDir);
|
|
2038
|
-
const
|
|
2039
|
-
if (warning !== null) process.stderr.write(`${warn(warning)}
|
|
2040
|
-
`);
|
|
2037
|
+
const org = manifest.org;
|
|
2041
2038
|
let parsed;
|
|
2042
2039
|
try {
|
|
2043
2040
|
parsed = parseRepoSpec(spec, org, transport.hosts.https);
|
|
@@ -2062,7 +2059,10 @@ async function addCommand(spec, opts, deps = {}) {
|
|
|
2062
2059
|
return 2;
|
|
2063
2060
|
}
|
|
2064
2061
|
const ui = deps.ui ?? (interactive ? await makeUi(terminal) : { ...plainUi });
|
|
2065
|
-
if (ownUi)
|
|
2062
|
+
if (ownUi)
|
|
2063
|
+
ui.intro(
|
|
2064
|
+
`Add ${parsed.name} to workspace ${workspaceName(manifest, ws.root)}`
|
|
2065
|
+
);
|
|
2066
2066
|
if (opts.host !== void 0 || opts.https === true) ui.gitIsSilent = true;
|
|
2067
2067
|
let host = opts.host;
|
|
2068
2068
|
if (host === void 0 && opts.ssh === true) host = transport.hosts.ssh;
|
|
@@ -2229,6 +2229,9 @@ async function contextCommand(opts) {
|
|
|
2229
2229
|
}
|
|
2230
2230
|
}
|
|
2231
2231
|
|
|
2232
|
+
// src/version.ts
|
|
2233
|
+
var VERSION = "0.6.0";
|
|
2234
|
+
|
|
2232
2235
|
// src/core/delegate.ts
|
|
2233
2236
|
import { readFile as readFile6 } from "fs/promises";
|
|
2234
2237
|
import { join as join11 } from "path";
|
|
@@ -2304,6 +2307,17 @@ async function packageManagerVersion(pm) {
|
|
|
2304
2307
|
throw new Error(`${pm} is not available on PATH`, { cause: e });
|
|
2305
2308
|
}
|
|
2306
2309
|
}
|
|
2310
|
+
function rnessRunner(pm, version) {
|
|
2311
|
+
if (pm === "pnpm") return "pnpm dlx @rness/cli";
|
|
2312
|
+
if (pm === "bun") return "bunx @rness/cli";
|
|
2313
|
+
if (pm === "yarn" && Number(version?.split(".")[0]) >= 2)
|
|
2314
|
+
return "yarn dlx @rness/cli";
|
|
2315
|
+
return "npx @rness/cli";
|
|
2316
|
+
}
|
|
2317
|
+
async function rnessRunnerFor(pm) {
|
|
2318
|
+
const version = pm === "yarn" ? await packageManagerVersion(pm).catch(() => null) : null;
|
|
2319
|
+
return rnessRunner(pm, version);
|
|
2320
|
+
}
|
|
2307
2321
|
var BARE_NPM_CODE = /^npm (error|ERR!) code \S+$/;
|
|
2308
2322
|
function installErrorLine(stderr) {
|
|
2309
2323
|
const lines = stderr.split("\n").map((l) => l.trim()).filter((l) => l !== "");
|
|
@@ -2331,12 +2345,228 @@ async function installDependencies(pm, dir, opts = {}) {
|
|
|
2331
2345
|
}
|
|
2332
2346
|
}
|
|
2333
2347
|
|
|
2334
|
-
// src/core/
|
|
2348
|
+
// src/core/scaffold.ts
|
|
2349
|
+
import { statSync } from "fs";
|
|
2350
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
2351
|
+
import { fileURLToPath } from "url";
|
|
2352
|
+
var SCAFFOLD_FILES = [
|
|
2353
|
+
"AGENTS.md",
|
|
2354
|
+
"CONVENTIONS.md",
|
|
2355
|
+
"README.md",
|
|
2356
|
+
"WORKSPACE.md",
|
|
2357
|
+
"rness.json",
|
|
2358
|
+
"package.json",
|
|
2359
|
+
"_gitignore",
|
|
2360
|
+
".githooks/pre-commit",
|
|
2361
|
+
".github/workflows/validate.yml",
|
|
2362
|
+
".github/dependabot.yml",
|
|
2363
|
+
"pnpm-workspace.yaml",
|
|
2364
|
+
"adr/0000-template.md",
|
|
2365
|
+
"standards/.gitkeep",
|
|
2366
|
+
"specs/.gitkeep",
|
|
2367
|
+
"plans/.gitkeep",
|
|
2368
|
+
"skills/.gitkeep",
|
|
2369
|
+
"docs/.gitkeep"
|
|
2370
|
+
];
|
|
2371
|
+
function scaffoldDir() {
|
|
2372
|
+
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
2373
|
+
for (let i = 0; i < 4; i += 1) {
|
|
2374
|
+
const candidate = join12(dir, "scaffold");
|
|
2375
|
+
try {
|
|
2376
|
+
if (statSync(join12(candidate, "rness.json")).isFile()) return candidate;
|
|
2377
|
+
} catch {
|
|
2378
|
+
}
|
|
2379
|
+
dir = dirname4(dir);
|
|
2380
|
+
}
|
|
2381
|
+
throw new Error("scaffold directory not found next to the @rness/cli package");
|
|
2382
|
+
}
|
|
2383
|
+
|
|
2384
|
+
// src/core/scaffold-copy.ts
|
|
2385
|
+
import { chmod as chmod2, mkdir as mkdir2, readFile as readFile7, stat as stat2, writeFile as writeFile2 } from "fs/promises";
|
|
2386
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
2387
|
+
var TOKENISED = /* @__PURE__ */ new Set(["package.json"]);
|
|
2388
|
+
async function copyScaffold(dest, tokens) {
|
|
2389
|
+
const source = scaffoldDir();
|
|
2390
|
+
for (const rel of SCAFFOLD_FILES) {
|
|
2391
|
+
if (rel === "rness.json") continue;
|
|
2392
|
+
const from = join13(source, ...rel.split("/"));
|
|
2393
|
+
const targetRel = rel === "_gitignore" ? ".gitignore" : rel;
|
|
2394
|
+
const to = join13(dest, ...targetRel.split("/"));
|
|
2395
|
+
await mkdir2(dirname5(to), { recursive: true });
|
|
2396
|
+
let content = await readFile7(from, "utf8");
|
|
2397
|
+
if (TOKENISED.has(rel)) {
|
|
2398
|
+
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
2399
|
+
}
|
|
2400
|
+
await writeFile2(to, content, "utf8");
|
|
2401
|
+
const mode = (await stat2(from)).mode & 511;
|
|
2402
|
+
if (mode & 73) await chmod2(to, mode);
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
|
|
2406
|
+
// src/commands/login.ts
|
|
2407
|
+
import { spawn as spawn3 } from "child_process";
|
|
2408
|
+
import { realpath } from "fs/promises";
|
|
2409
|
+
|
|
2410
|
+
// src/core/setup-git.ts
|
|
2335
2411
|
import { execFile as execFile3 } from "child_process";
|
|
2336
|
-
import { readFile as readFile7 } from "fs/promises";
|
|
2337
|
-
import { basename as basename4, join as join12 } from "path";
|
|
2338
2412
|
import { promisify as promisify3 } from "util";
|
|
2339
2413
|
var execFileP3 = promisify3(execFile3);
|
|
2414
|
+
var KEY = "credential.https://github.com.helper";
|
|
2415
|
+
function helperValue(node, bin) {
|
|
2416
|
+
return `!'${node}' '${bin}' git-credential`;
|
|
2417
|
+
}
|
|
2418
|
+
var isOurs = (value) => /^!'[^']+' '[^']*rness[^']*' git-credential$/.test(value);
|
|
2419
|
+
async function config(args) {
|
|
2420
|
+
const { stdout } = await execFileP3("git", ["config", "--global", ...args]);
|
|
2421
|
+
return stdout;
|
|
2422
|
+
}
|
|
2423
|
+
async function values() {
|
|
2424
|
+
try {
|
|
2425
|
+
const out = await config(["--get-all", KEY]);
|
|
2426
|
+
return out.replace(/\n$/, "").split("\n");
|
|
2427
|
+
} catch {
|
|
2428
|
+
return [];
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
function isStableInstall(bin) {
|
|
2432
|
+
return !/[\\/]_npx[\\/]/.test(bin);
|
|
2433
|
+
}
|
|
2434
|
+
async function setupGit(node, bin) {
|
|
2435
|
+
const ours = helperValue(node, bin);
|
|
2436
|
+
if ((await values()).includes(ours)) return false;
|
|
2437
|
+
await removeGitSetup();
|
|
2438
|
+
await config(["--add", KEY, ""]);
|
|
2439
|
+
await config(["--add", KEY, ours]);
|
|
2440
|
+
return true;
|
|
2441
|
+
}
|
|
2442
|
+
async function removeGitSetup() {
|
|
2443
|
+
const current = await values();
|
|
2444
|
+
if (!current.some(isOurs)) return false;
|
|
2445
|
+
const kept = current.filter(
|
|
2446
|
+
(value, i) => !isOurs(value) && !(value === "" && isOurs(current[i + 1] ?? ""))
|
|
2447
|
+
);
|
|
2448
|
+
await config(["--unset-all", KEY]);
|
|
2449
|
+
for (const value of kept) await config(["--add", KEY, value]);
|
|
2450
|
+
return true;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
// src/commands/login.ts
|
|
2454
|
+
function openInBrowser(url) {
|
|
2455
|
+
const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url]] : ["xdg-open", [url]];
|
|
2456
|
+
try {
|
|
2457
|
+
const child = spawn3(command, args, { stdio: "ignore", detached: true });
|
|
2458
|
+
child.on("error", () => void 0);
|
|
2459
|
+
child.unref();
|
|
2460
|
+
} catch {
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
async function offerSetupGit(opts, terminal, bin, ui) {
|
|
2464
|
+
if (opts.setupGit === false) return;
|
|
2465
|
+
if (!isStableInstall(bin)) {
|
|
2466
|
+
ui.hint(
|
|
2467
|
+
"install rness globally to let git use your login: npm i -g @rness/cli"
|
|
2468
|
+
);
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2471
|
+
if (opts.setupGit !== true) {
|
|
2472
|
+
if (!terminal.isTty()) return;
|
|
2473
|
+
const p = await terminal.prompts();
|
|
2474
|
+
const ok = await p.confirm({
|
|
2475
|
+
message: "Use rness to authenticate git over HTTPS for github.com?",
|
|
2476
|
+
initialValue: true
|
|
2477
|
+
});
|
|
2478
|
+
if (p.isCancel(ok) || ok !== true) return;
|
|
2479
|
+
}
|
|
2480
|
+
const changed = await setupGit(process.execPath, bin);
|
|
2481
|
+
ui.line(
|
|
2482
|
+
changed ? "updated" : "unchanged",
|
|
2483
|
+
"git: rness answers for https://github.com (rness logout undoes it)",
|
|
2484
|
+
"git now asks rness for your github.com login over HTTPS (rness logout undoes it)"
|
|
2485
|
+
);
|
|
2486
|
+
}
|
|
2487
|
+
async function loginCommand(opts, deps = {}) {
|
|
2488
|
+
const terminal = deps.terminal ?? defaultTerminal;
|
|
2489
|
+
const own = deps.ui === void 0;
|
|
2490
|
+
const ui = deps.ui ?? await makeUi(terminal);
|
|
2491
|
+
if (own) ui.intro("Log in to GitHub");
|
|
2492
|
+
try {
|
|
2493
|
+
const bin = deps.bin ?? await realpath(process.argv[1] ?? "").catch(() => "");
|
|
2494
|
+
const env = envToken();
|
|
2495
|
+
if (env !== null)
|
|
2496
|
+
ui.hint(`${env.source} is set and wins over a stored login`);
|
|
2497
|
+
const stored = await readAuth();
|
|
2498
|
+
if (stored !== null && await resolveToken() !== null) {
|
|
2499
|
+
ui.line(
|
|
2500
|
+
"logged in",
|
|
2501
|
+
`as ${stored.login} (github.com); rness logout to switch`
|
|
2502
|
+
);
|
|
2503
|
+
await offerSetupGit(opts, terminal, bin, ui);
|
|
2504
|
+
if (own) ui.outro("Nothing to do", "idle");
|
|
2505
|
+
return 0;
|
|
2506
|
+
}
|
|
2507
|
+
const code = await requestDeviceCode();
|
|
2508
|
+
if (ui.session)
|
|
2509
|
+
ui.line(
|
|
2510
|
+
"open",
|
|
2511
|
+
code.verificationUri,
|
|
2512
|
+
`Open ${code.verificationUri} and enter the code ${code.userCode}`
|
|
2513
|
+
);
|
|
2514
|
+
else {
|
|
2515
|
+
ui.line("open", code.verificationUri);
|
|
2516
|
+
ui.line("code", code.userCode);
|
|
2517
|
+
}
|
|
2518
|
+
if (terminal.isTty() && process.env["SSH_CONNECTION"] === void 0)
|
|
2519
|
+
(deps.open ?? openInBrowser)(code.verificationUri);
|
|
2520
|
+
if (!ui.session)
|
|
2521
|
+
ui.line("waiting", "for you to approve rness on github.com\u2026");
|
|
2522
|
+
const login = await ui.step(
|
|
2523
|
+
{
|
|
2524
|
+
doing: "waiting for you to approve rness on github.com",
|
|
2525
|
+
sentence: "Waiting for your approval on github.com",
|
|
2526
|
+
quiet: true
|
|
2527
|
+
},
|
|
2528
|
+
async () => {
|
|
2529
|
+
const tokens = await pollForToken(code, deps);
|
|
2530
|
+
const user = await getUser({
|
|
2531
|
+
token: tokens.accessToken,
|
|
2532
|
+
apiBase: opts.githubApi ?? DEFAULT_GITHUB_API
|
|
2533
|
+
});
|
|
2534
|
+
await writeAuth({ login: user.login, tokens });
|
|
2535
|
+
return user.login;
|
|
2536
|
+
},
|
|
2537
|
+
(name) => ["logged in", `as ${name} (github.com)`]
|
|
2538
|
+
);
|
|
2539
|
+
if (!ui.session) ui.line("logged in", `as ${login} (github.com)`);
|
|
2540
|
+
await offerSetupGit(opts, terminal, bin, ui);
|
|
2541
|
+
if (own) ui.outro(`You are logged in as ${login}`);
|
|
2542
|
+
return 0;
|
|
2543
|
+
} catch (e) {
|
|
2544
|
+
if (e instanceof LoginError) {
|
|
2545
|
+
ui.error(e.message);
|
|
2546
|
+
return 1;
|
|
2547
|
+
}
|
|
2548
|
+
return reportError(e);
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
function revokeUrl() {
|
|
2552
|
+
return `https://github.com/settings/connections/applications/${clientId()}`;
|
|
2553
|
+
}
|
|
2554
|
+
|
|
2555
|
+
// src/commands/create.ts
|
|
2556
|
+
import { mkdir as mkdir5, mkdtemp, rename as rename2, rm as rm5 } from "fs/promises";
|
|
2557
|
+
import { tmpdir } from "os";
|
|
2558
|
+
import { join as join17, relative as relative4, resolve as resolve4 } from "path";
|
|
2559
|
+
|
|
2560
|
+
// src/core/new-workspace.ts
|
|
2561
|
+
import { mkdir as mkdir3, readdir as readdir2, rm as rm4, stat as stat3 } from "fs/promises";
|
|
2562
|
+
import { dirname as dirname6, join as join15, relative as relative2 } from "path";
|
|
2563
|
+
|
|
2564
|
+
// src/core/pinned.ts
|
|
2565
|
+
import { execFile as execFile4 } from "child_process";
|
|
2566
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
2567
|
+
import { basename as basename4, join as join14 } from "path";
|
|
2568
|
+
import { promisify as promisify4 } from "util";
|
|
2569
|
+
var execFileP4 = promisify4(execFile4);
|
|
2340
2570
|
var PACKAGE = "@rness/cli";
|
|
2341
2571
|
var EXACT_VERSION = /^\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/;
|
|
2342
2572
|
function compareVersions(a, b) {
|
|
@@ -2359,7 +2589,7 @@ function compareVersions(a, b) {
|
|
|
2359
2589
|
async function readPackageJson(rnessDir) {
|
|
2360
2590
|
try {
|
|
2361
2591
|
const parsed = JSON.parse(
|
|
2362
|
-
await
|
|
2592
|
+
await readFile8(join14(rnessDir, "package.json"), "utf8")
|
|
2363
2593
|
);
|
|
2364
2594
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
2365
2595
|
} catch {
|
|
@@ -2378,8 +2608,8 @@ async function readPin(rnessDir) {
|
|
|
2378
2608
|
return null;
|
|
2379
2609
|
}
|
|
2380
2610
|
async function writePin(rnessDir, from, to) {
|
|
2381
|
-
const file =
|
|
2382
|
-
const text = await
|
|
2611
|
+
const file = join14(rnessDir, "package.json");
|
|
2612
|
+
const text = await readFile8(file, "utf8");
|
|
2383
2613
|
const entry = new RegExp(
|
|
2384
2614
|
`("${PACKAGE}"\\s*:\\s*")${from.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(")`
|
|
2385
2615
|
);
|
|
@@ -2401,12 +2631,12 @@ async function workspacePackageManager(rnessDir) {
|
|
|
2401
2631
|
if (isPackageManager(name)) return name;
|
|
2402
2632
|
}
|
|
2403
2633
|
for (const [lockfile, pm] of LOCKFILES)
|
|
2404
|
-
if (await exists(
|
|
2634
|
+
if (await exists(join14(rnessDir, lockfile))) return pm;
|
|
2405
2635
|
return "npm";
|
|
2406
2636
|
}
|
|
2407
2637
|
async function hasLockfile(rnessDir, pm) {
|
|
2408
2638
|
for (const [lockfile, owner] of LOCKFILES)
|
|
2409
|
-
if (owner === pm && await exists(
|
|
2639
|
+
if (owner === pm && await exists(join14(rnessDir, lockfile))) return true;
|
|
2410
2640
|
return false;
|
|
2411
2641
|
}
|
|
2412
2642
|
async function pinDrift(rnessDir) {
|
|
@@ -2462,9 +2692,9 @@ async function ensureInstalled(rnessDir, opts = {}) {
|
|
|
2462
2692
|
}
|
|
2463
2693
|
}
|
|
2464
2694
|
async function runPinnedSync(root) {
|
|
2465
|
-
const installed = await readPinnedCli(
|
|
2695
|
+
const installed = await readPinnedCli(join14(root, ".rness"));
|
|
2466
2696
|
if (installed === null) return null;
|
|
2467
|
-
const pinned =
|
|
2697
|
+
const pinned = join14(installed.dir, ...installed.bin.split("/"));
|
|
2468
2698
|
if (!await exists(pinned))
|
|
2469
2699
|
return {
|
|
2470
2700
|
code: 1,
|
|
@@ -2473,7 +2703,7 @@ async function runPinnedSync(root) {
|
|
|
2473
2703
|
`
|
|
2474
2704
|
};
|
|
2475
2705
|
try {
|
|
2476
|
-
const { stdout, stderr } = await
|
|
2706
|
+
const { stdout, stderr } = await execFileP4(
|
|
2477
2707
|
process.execPath,
|
|
2478
2708
|
[pinned, "sync", "--yes"],
|
|
2479
2709
|
{
|
|
@@ -2526,7 +2756,7 @@ function syncOutcome(result, opts = {}) {
|
|
|
2526
2756
|
};
|
|
2527
2757
|
}
|
|
2528
2758
|
async function syncPinned(root, shown, fallback) {
|
|
2529
|
-
const rnessDir =
|
|
2759
|
+
const rnessDir = join14(root, ".rness");
|
|
2530
2760
|
const installed = await readPinnedCli(rnessDir);
|
|
2531
2761
|
if (installed === null) {
|
|
2532
2762
|
if (fallback !== void 0) return fallback();
|
|
@@ -2536,7 +2766,7 @@ async function syncPinned(root, shown, fallback) {
|
|
|
2536
2766
|
);
|
|
2537
2767
|
return 1;
|
|
2538
2768
|
}
|
|
2539
|
-
const pinned =
|
|
2769
|
+
const pinned = join14(installed.dir, ...installed.bin.split("/"));
|
|
2540
2770
|
if (!await exists(pinned)) {
|
|
2541
2771
|
process.stderr.write(
|
|
2542
2772
|
`@rness/cli in ${shown}/.rness is not installed correctly (missing ${installed.bin}); reinstall in .rness/
|
|
@@ -2545,7 +2775,7 @@ async function syncPinned(root, shown, fallback) {
|
|
|
2545
2775
|
return 1;
|
|
2546
2776
|
}
|
|
2547
2777
|
try {
|
|
2548
|
-
const { stdout, stderr } = await
|
|
2778
|
+
const { stdout, stderr } = await execFileP4(
|
|
2549
2779
|
process.execPath,
|
|
2550
2780
|
[pinned, "sync", "--yes"],
|
|
2551
2781
|
{
|
|
@@ -2569,220 +2799,83 @@ async function syncPinned(root, shown, fallback) {
|
|
|
2569
2799
|
}
|
|
2570
2800
|
}
|
|
2571
2801
|
|
|
2572
|
-
// src/core/
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
"package.json",
|
|
2583
|
-
"_gitignore",
|
|
2584
|
-
".githooks/pre-commit",
|
|
2585
|
-
".github/workflows/validate.yml",
|
|
2586
|
-
".github/dependabot.yml",
|
|
2587
|
-
"pnpm-workspace.yaml",
|
|
2588
|
-
"adr/0000-template.md",
|
|
2589
|
-
"standards/.gitkeep",
|
|
2590
|
-
"specs/.gitkeep",
|
|
2591
|
-
"plans/.gitkeep",
|
|
2592
|
-
"skills/.gitkeep",
|
|
2593
|
-
"docs/.gitkeep"
|
|
2594
|
-
];
|
|
2595
|
-
function scaffoldDir() {
|
|
2596
|
-
let dir = dirname4(fileURLToPath(import.meta.url));
|
|
2597
|
-
for (let i = 0; i < 4; i += 1) {
|
|
2598
|
-
const candidate = join13(dir, "scaffold");
|
|
2599
|
-
try {
|
|
2600
|
-
if (statSync(join13(candidate, "rness.json")).isFile()) return candidate;
|
|
2601
|
-
} catch {
|
|
2602
|
-
}
|
|
2603
|
-
dir = dirname4(dir);
|
|
2802
|
+
// src/core/new-workspace.ts
|
|
2803
|
+
async function isEmptyDir(dir) {
|
|
2804
|
+
return (await readdir2(dir)).length === 0;
|
|
2805
|
+
}
|
|
2806
|
+
async function insideWorkspace(dir) {
|
|
2807
|
+
try {
|
|
2808
|
+
await findWorkspace(dir);
|
|
2809
|
+
return true;
|
|
2810
|
+
} catch {
|
|
2811
|
+
return false;
|
|
2604
2812
|
}
|
|
2605
|
-
throw new Error("scaffold directory not found next to the @rness/cli package");
|
|
2606
2813
|
}
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
const from = join14(source, ...rel.split("/"));
|
|
2617
|
-
const targetRel = rel === "_gitignore" ? ".gitignore" : rel;
|
|
2618
|
-
const to = join14(dest, ...targetRel.split("/"));
|
|
2619
|
-
await mkdir2(dirname5(to), { recursive: true });
|
|
2620
|
-
let content = await readFile8(from, "utf8");
|
|
2621
|
-
if (TOKENISED.has(rel)) {
|
|
2622
|
-
content = content.replaceAll("__RNESS_VERSION__", tokens.version).replaceAll("__RNESS_PM__", tokens.packageManager);
|
|
2623
|
-
}
|
|
2624
|
-
await writeFile2(to, content, "utf8");
|
|
2625
|
-
const mode = (await stat2(from)).mode & 511;
|
|
2626
|
-
if (mode & 73) await chmod2(to, mode);
|
|
2814
|
+
async function targetProblem(cwd, root, pm) {
|
|
2815
|
+
const shown = relative2(cwd, root) || ".";
|
|
2816
|
+
if (await insideWorkspace(cwd) || await insideWorkspace(dirname6(root)))
|
|
2817
|
+
return "already inside an rness workspace; create runs from outside one";
|
|
2818
|
+
if (!await exists(root)) return null;
|
|
2819
|
+
if (await exists(join15(root, ".rness", "rness.json"))) {
|
|
2820
|
+
if (!await exists(join15(root, ".rness", "node_modules")))
|
|
2821
|
+
return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then rness sync`;
|
|
2822
|
+
return `${shown} already holds an rness workspace (.rness/); run rness add inside it to add repositories`;
|
|
2627
2823
|
}
|
|
2824
|
+
if (!(await stat3(root)).isDirectory()) return `${shown} is not a directory`;
|
|
2825
|
+
if (!await isEmptyDir(root)) return `${shown} is not empty`;
|
|
2826
|
+
return null;
|
|
2628
2827
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
import { spawn as spawn3 } from "child_process";
|
|
2635
|
-
import { realpath } from "fs/promises";
|
|
2636
|
-
|
|
2637
|
-
// src/core/setup-git.ts
|
|
2638
|
-
import { execFile as execFile4 } from "child_process";
|
|
2639
|
-
import { promisify as promisify4 } from "util";
|
|
2640
|
-
var execFileP4 = promisify4(execFile4);
|
|
2641
|
-
var KEY = "credential.https://github.com.helper";
|
|
2642
|
-
function helperValue(node, bin) {
|
|
2643
|
-
return `!'${node}' '${bin}' git-credential`;
|
|
2644
|
-
}
|
|
2645
|
-
var isOurs = (value) => /^!'[^']+' '[^']*rness[^']*' git-credential$/.test(value);
|
|
2646
|
-
async function config(args) {
|
|
2647
|
-
const { stdout } = await execFileP4("git", ["config", "--global", ...args]);
|
|
2648
|
-
return stdout;
|
|
2649
|
-
}
|
|
2650
|
-
async function values() {
|
|
2651
|
-
try {
|
|
2652
|
-
const out = await config(["--get-all", KEY]);
|
|
2653
|
-
return out.replace(/\n$/, "").split("\n");
|
|
2654
|
-
} catch {
|
|
2655
|
-
return [];
|
|
2828
|
+
async function installContext(input) {
|
|
2829
|
+
const { ui, pm, rnessDir } = input;
|
|
2830
|
+
if (input.skip) {
|
|
2831
|
+
ui.line("skipped", `install with ${pm} (--skip-install)`);
|
|
2832
|
+
return;
|
|
2656
2833
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
}
|
|
2661
|
-
|
|
2662
|
-
const ours = helperValue(node, bin);
|
|
2663
|
-
if ((await values()).includes(ours)) return false;
|
|
2664
|
-
await removeGitSetup();
|
|
2665
|
-
await config(["--add", KEY, ""]);
|
|
2666
|
-
await config(["--add", KEY, ours]);
|
|
2667
|
-
return true;
|
|
2668
|
-
}
|
|
2669
|
-
async function removeGitSetup() {
|
|
2670
|
-
const current = await values();
|
|
2671
|
-
if (!current.some(isOurs)) return false;
|
|
2672
|
-
const kept = current.filter(
|
|
2673
|
-
(value, i) => !isOurs(value) && !(value === "" && isOurs(current[i + 1] ?? ""))
|
|
2834
|
+
const frozen = await hasLockfile(rnessDir, pm);
|
|
2835
|
+
await ui.step(
|
|
2836
|
+
{ doing: `installing dependencies with ${pm}` },
|
|
2837
|
+
() => installDependencies(pm, rnessDir, { frozen }),
|
|
2838
|
+
() => ["installed", `dependencies with ${pm}`]
|
|
2674
2839
|
);
|
|
2675
|
-
await config(["--unset-all", KEY]);
|
|
2676
|
-
for (const value of kept) await config(["--add", KEY, value]);
|
|
2677
|
-
return true;
|
|
2678
2840
|
}
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
const
|
|
2841
|
+
async function buildContext(input) {
|
|
2842
|
+
const { ui, root, shown, pm } = input;
|
|
2843
|
+
const rnessDir = join15(root, ".rness");
|
|
2844
|
+
const pmVersion = await packageManagerVersion(pm);
|
|
2845
|
+
await mkdir3(root, { recursive: true });
|
|
2683
2846
|
try {
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
} catch {
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
async function offerSetupGit(opts, terminal, bin, ui) {
|
|
2691
|
-
if (opts.setupGit === false) return;
|
|
2692
|
-
if (!isStableInstall(bin)) {
|
|
2693
|
-
ui.hint(
|
|
2694
|
-
"install rness globally to let git use your login: npm i -g @rness/cli"
|
|
2695
|
-
);
|
|
2696
|
-
return;
|
|
2697
|
-
}
|
|
2698
|
-
if (opts.setupGit !== true) {
|
|
2699
|
-
if (!terminal.isTty()) return;
|
|
2700
|
-
const p = await terminal.prompts();
|
|
2701
|
-
const ok = await p.confirm({
|
|
2702
|
-
message: "Use rness to authenticate git over HTTPS for github.com?",
|
|
2703
|
-
initialValue: true
|
|
2847
|
+
await copyScaffold(rnessDir, {
|
|
2848
|
+
version: VERSION,
|
|
2849
|
+
packageManager: `${pm}@${pmVersion}`
|
|
2704
2850
|
});
|
|
2705
|
-
|
|
2851
|
+
await writeManifest(rnessDir, input.manifest);
|
|
2852
|
+
ui.line("created", `${shown}/.rness (${input.kind})`);
|
|
2853
|
+
await installContext({ ui, pm, rnessDir, skip: input.skipInstall });
|
|
2854
|
+
return true;
|
|
2855
|
+
} catch (e) {
|
|
2856
|
+
reportError(e);
|
|
2857
|
+
await rm4(rnessDir, { recursive: true, force: true }).catch(() => void 0);
|
|
2858
|
+
if (await isEmptyDir(root).catch(() => false))
|
|
2859
|
+
await rm4(root, { recursive: true, force: true }).catch(() => void 0);
|
|
2860
|
+
process.stderr.write(
|
|
2861
|
+
`removed ${shown}/.rness after the failure; fix it and retry
|
|
2862
|
+
`
|
|
2863
|
+
);
|
|
2864
|
+
return false;
|
|
2706
2865
|
}
|
|
2707
|
-
const changed = await setupGit(process.execPath, bin);
|
|
2708
|
-
ui.line(
|
|
2709
|
-
changed ? "updated" : "unchanged",
|
|
2710
|
-
"git: rness answers for https://github.com (rness logout undoes it)",
|
|
2711
|
-
"git now asks rness for your github.com login over HTTPS (rness logout undoes it)"
|
|
2712
|
-
);
|
|
2713
2866
|
}
|
|
2714
|
-
async function
|
|
2715
|
-
const
|
|
2716
|
-
|
|
2717
|
-
const ui = deps.ui ?? await makeUi(terminal);
|
|
2718
|
-
if (own) ui.intro("Log in to GitHub");
|
|
2867
|
+
async function commitContext(input) {
|
|
2868
|
+
const { ui, rnessDir, shown } = input;
|
|
2869
|
+
await init(rnessDir);
|
|
2719
2870
|
try {
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
if (env !== null)
|
|
2723
|
-
ui.hint(`${env.source} is set and wins over a stored login`);
|
|
2724
|
-
const stored = await readAuth();
|
|
2725
|
-
if (stored !== null && await resolveToken() !== null) {
|
|
2726
|
-
ui.line(
|
|
2727
|
-
"logged in",
|
|
2728
|
-
`as ${stored.login} (github.com); rness logout to switch`
|
|
2729
|
-
);
|
|
2730
|
-
await offerSetupGit(opts, terminal, bin, ui);
|
|
2731
|
-
if (own) ui.outro("Nothing to do", "idle");
|
|
2732
|
-
return 0;
|
|
2733
|
-
}
|
|
2734
|
-
const code = await requestDeviceCode();
|
|
2735
|
-
if (ui.session)
|
|
2736
|
-
ui.line(
|
|
2737
|
-
"open",
|
|
2738
|
-
code.verificationUri,
|
|
2739
|
-
`Open ${code.verificationUri} and enter the code ${code.userCode}`
|
|
2740
|
-
);
|
|
2741
|
-
else {
|
|
2742
|
-
ui.line("open", code.verificationUri);
|
|
2743
|
-
ui.line("code", code.userCode);
|
|
2744
|
-
}
|
|
2745
|
-
if (terminal.isTty() && process.env["SSH_CONNECTION"] === void 0)
|
|
2746
|
-
(deps.open ?? openInBrowser)(code.verificationUri);
|
|
2747
|
-
if (!ui.session)
|
|
2748
|
-
ui.line("waiting", "for you to approve rness on github.com\u2026");
|
|
2749
|
-
const login = await ui.step(
|
|
2750
|
-
{
|
|
2751
|
-
doing: "waiting for you to approve rness on github.com",
|
|
2752
|
-
sentence: "Waiting for your approval on github.com",
|
|
2753
|
-
quiet: true
|
|
2754
|
-
},
|
|
2755
|
-
async () => {
|
|
2756
|
-
const tokens = await pollForToken(code, deps);
|
|
2757
|
-
const user = await getUser({
|
|
2758
|
-
token: tokens.accessToken,
|
|
2759
|
-
apiBase: opts.githubApi ?? DEFAULT_GITHUB_API
|
|
2760
|
-
});
|
|
2761
|
-
await writeAuth({ login: user.login, tokens });
|
|
2762
|
-
return user.login;
|
|
2763
|
-
},
|
|
2764
|
-
(name) => ["logged in", `as ${name} (github.com)`]
|
|
2765
|
-
);
|
|
2766
|
-
if (!ui.session) ui.line("logged in", `as ${login} (github.com)`);
|
|
2767
|
-
await offerSetupGit(opts, terminal, bin, ui);
|
|
2768
|
-
if (own) ui.outro(`You are logged in as ${login}`);
|
|
2769
|
-
return 0;
|
|
2871
|
+
await commitAll(rnessDir, "chore: rness workspace context");
|
|
2872
|
+
ui.line("committed", `${shown}/.rness`);
|
|
2770
2873
|
} catch (e) {
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
}
|
|
2775
|
-
return reportError(e);
|
|
2874
|
+
ui.warn(
|
|
2875
|
+
`${e instanceof Error ? e.message : String(e)} \u2014 commit ${shown}/.rness yourself`
|
|
2876
|
+
);
|
|
2776
2877
|
}
|
|
2777
2878
|
}
|
|
2778
|
-
function revokeUrl() {
|
|
2779
|
-
return `https://github.com/settings/connections/applications/${clientId()}`;
|
|
2780
|
-
}
|
|
2781
|
-
|
|
2782
|
-
// src/commands/create.ts
|
|
2783
|
-
import { mkdir as mkdir3, mkdtemp, readdir as readdir2, rename as rename2, rm as rm4, stat as stat3 } from "fs/promises";
|
|
2784
|
-
import { tmpdir } from "os";
|
|
2785
|
-
import { dirname as dirname6, join as join15, relative as relative2, resolve as resolve3 } from "path";
|
|
2786
2879
|
|
|
2787
2880
|
// src/core/sync-blocks.ts
|
|
2788
2881
|
async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
@@ -2812,38 +2905,113 @@ async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
|
|
|
2812
2905
|
return outcome.code;
|
|
2813
2906
|
}
|
|
2814
2907
|
|
|
2815
|
-
// src/commands/create.ts
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
}
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
}
|
|
2824
|
-
|
|
2908
|
+
// src/commands/create-blank.ts
|
|
2909
|
+
import { mkdir as mkdir4 } from "fs/promises";
|
|
2910
|
+
import { join as join16, relative as relative3, resolve as resolve3 } from "path";
|
|
2911
|
+
var WORKSPACE_NAME = /^(?!\.{1,2}$)[A-Za-z0-9._][A-Za-z0-9._-]*$/;
|
|
2912
|
+
function workspaceNameError(name) {
|
|
2913
|
+
return `workspace name "${name}" may only contain letters, digits, '.', '_' and '-', not starting with '-'`;
|
|
2914
|
+
}
|
|
2915
|
+
async function createBlank(input) {
|
|
2916
|
+
const { cwd, pm, ui } = input;
|
|
2917
|
+
let name = input.name;
|
|
2918
|
+
let prompted = false;
|
|
2919
|
+
if (name === void 0) {
|
|
2920
|
+
if (!input.interactive) {
|
|
2921
|
+
process.stderr.write(
|
|
2922
|
+
"rness create --blank needs a <name> without a prompt\n"
|
|
2923
|
+
);
|
|
2924
|
+
return 2;
|
|
2925
|
+
}
|
|
2926
|
+
const p = await input.prompts();
|
|
2927
|
+
const answer = await p.text({
|
|
2928
|
+
message: "What should the workspace be called?",
|
|
2929
|
+
placeholder: "my-workspace",
|
|
2930
|
+
validate: (value) => {
|
|
2931
|
+
const v = value?.trim() ?? "";
|
|
2932
|
+
return WORKSPACE_NAME.test(v) ? void 0 : workspaceNameError(v);
|
|
2933
|
+
}
|
|
2934
|
+
});
|
|
2935
|
+
if (p.isCancel(answer) || typeof answer !== "string") {
|
|
2936
|
+
ui.cancelled();
|
|
2937
|
+
return 0;
|
|
2938
|
+
}
|
|
2939
|
+
name = answer.trim();
|
|
2940
|
+
prompted = true;
|
|
2941
|
+
}
|
|
2942
|
+
const root = resolve3(cwd, name);
|
|
2943
|
+
const shown = relative3(cwd, root) || ".";
|
|
2944
|
+
const problem = await targetProblem(cwd, root, pm);
|
|
2945
|
+
if (problem !== null) {
|
|
2946
|
+
process.stderr.write(`${problem}
|
|
2947
|
+
`);
|
|
2948
|
+
return 1;
|
|
2949
|
+
}
|
|
2950
|
+
if (!input.mayWrite) {
|
|
2951
|
+
process.stderr.write(
|
|
2952
|
+
"rness create writes files; pass --yes to run without a prompt\n"
|
|
2953
|
+
);
|
|
2954
|
+
return 2;
|
|
2825
2955
|
}
|
|
2956
|
+
if (input.interactive && !prompted) {
|
|
2957
|
+
const p = await input.prompts();
|
|
2958
|
+
const ok = await p.confirm({
|
|
2959
|
+
message: `Create blank workspace ${name} in ./${shown}?`
|
|
2960
|
+
});
|
|
2961
|
+
if (p.isCancel(ok) || ok !== true) {
|
|
2962
|
+
ui.cancelled();
|
|
2963
|
+
return 0;
|
|
2964
|
+
}
|
|
2965
|
+
}
|
|
2966
|
+
const built = await buildContext({
|
|
2967
|
+
ui,
|
|
2968
|
+
root,
|
|
2969
|
+
shown,
|
|
2970
|
+
manifest: { contract: 1, org: null, repos: {}, scopes: {} },
|
|
2971
|
+
pm,
|
|
2972
|
+
skipInstall: input.skipInstall,
|
|
2973
|
+
kind: "blank workspace"
|
|
2974
|
+
});
|
|
2975
|
+
if (!built) return 1;
|
|
2976
|
+
await mkdir4(join16(root, "org"), { recursive: true });
|
|
2977
|
+
await commitContext({ ui, rnessDir: join16(root, ".rness"), shown });
|
|
2978
|
+
const code = await syncBlocks(
|
|
2979
|
+
ui,
|
|
2980
|
+
root,
|
|
2981
|
+
shown,
|
|
2982
|
+
() => syncCommand({ yes: true, cwd: root })
|
|
2983
|
+
);
|
|
2984
|
+
if (code !== 0) return code;
|
|
2985
|
+
const next = [
|
|
2986
|
+
`cd ${shown}`,
|
|
2987
|
+
"# bring a repository in: <owner>/<repo> on GitHub, or any git URL",
|
|
2988
|
+
`${await rnessRunnerFor(pm)} add <owner>/<repo>`,
|
|
2989
|
+
'# to share it: set "org" in .rness/rness.json, then push .rness to github.com/<org>/.rness'
|
|
2990
|
+
];
|
|
2991
|
+
if (ui.session) {
|
|
2992
|
+
ui.note("Next steps", next);
|
|
2993
|
+
ui.outro(`Workspace ${name} is ready in ${shown}/`);
|
|
2994
|
+
} else
|
|
2995
|
+
process.stdout.write(
|
|
2996
|
+
[
|
|
2997
|
+
"",
|
|
2998
|
+
`Workspace \`${name}\` is ready in ${shown}/.`,
|
|
2999
|
+
"",
|
|
3000
|
+
"Next:",
|
|
3001
|
+
...next.map((line) => ` ${line}`),
|
|
3002
|
+
""
|
|
3003
|
+
].join("\n")
|
|
3004
|
+
);
|
|
3005
|
+
return 0;
|
|
2826
3006
|
}
|
|
3007
|
+
|
|
3008
|
+
// src/commands/create.ts
|
|
2827
3009
|
function firstLine4(text) {
|
|
2828
3010
|
return text.split("\n").find((l) => l.trim() !== "")?.trim() ?? "unknown error";
|
|
2829
3011
|
}
|
|
2830
3012
|
function splitSpecs(list) {
|
|
2831
3013
|
return list.split(",").map((s) => s.trim()).filter((s) => s !== "");
|
|
2832
3014
|
}
|
|
2833
|
-
async function targetProblem(cwd, root, pm) {
|
|
2834
|
-
const shown = relative2(cwd, root) || ".";
|
|
2835
|
-
if (await insideWorkspace(cwd) || await insideWorkspace(dirname6(root)))
|
|
2836
|
-
return "already inside an rness workspace; create runs from outside one";
|
|
2837
|
-
if (!await exists(root)) return null;
|
|
2838
|
-
if (await exists(join15(root, ".rness", "rness.json"))) {
|
|
2839
|
-
if (!await exists(join15(root, ".rness", "node_modules")))
|
|
2840
|
-
return `${shown} holds an rness workspace whose dependencies are not installed; cd ${shown}/.rness && ${pm} install, then rness sync`;
|
|
2841
|
-
return `${shown} already holds an rness workspace (.rness/); run rness add inside it to add repositories`;
|
|
2842
|
-
}
|
|
2843
|
-
if (!(await stat3(root)).isDirectory()) return `${shown} is not a directory`;
|
|
2844
|
-
if (!await isEmptyDir(root)) return `${shown} is not empty`;
|
|
2845
|
-
return null;
|
|
2846
|
-
}
|
|
2847
3015
|
function cancelled(ui) {
|
|
2848
3016
|
ui.cancelled();
|
|
2849
3017
|
return 0;
|
|
@@ -2966,7 +3134,7 @@ async function cloneSelection(input) {
|
|
|
2966
3134
|
{ doing: `cloning org/${repo}`, git: true },
|
|
2967
3135
|
() => clone(
|
|
2968
3136
|
entry.url,
|
|
2969
|
-
|
|
3137
|
+
join17(input.root, "org", repo),
|
|
2970
3138
|
input.provider.credentialsFor(entry.url)
|
|
2971
3139
|
),
|
|
2972
3140
|
() => ["cloned", `org/${repo}`]
|
|
@@ -3006,6 +3174,25 @@ async function placeContext(staged, rnessDir, url, credentials) {
|
|
|
3006
3174
|
await clone(url, rnessDir, credentials);
|
|
3007
3175
|
}
|
|
3008
3176
|
}
|
|
3177
|
+
function blankConflict(opts) {
|
|
3178
|
+
if (opts.org !== void 0) return "--org";
|
|
3179
|
+
if (opts.repos !== void 0) return "--repos";
|
|
3180
|
+
if (opts.ssh === true) return "--ssh";
|
|
3181
|
+
if (opts.https === true) return "--https";
|
|
3182
|
+
if (opts.host !== void 0) return "--host";
|
|
3183
|
+
return null;
|
|
3184
|
+
}
|
|
3185
|
+
async function chooseStart(prompts) {
|
|
3186
|
+
const answer = await prompts.select({
|
|
3187
|
+
message: "How do you want to start?",
|
|
3188
|
+
options: [
|
|
3189
|
+
{ value: "github", label: "From a GitHub organization" },
|
|
3190
|
+
{ value: "blank", label: "Blank local workspace" }
|
|
3191
|
+
]
|
|
3192
|
+
});
|
|
3193
|
+
if (prompts.isCancel(answer)) return null;
|
|
3194
|
+
return answer === "blank" ? "blank" : "github";
|
|
3195
|
+
}
|
|
3009
3196
|
var OTHER = /* @__PURE__ */ Symbol("another organization");
|
|
3010
3197
|
async function chooseOrganization(provider, prompts) {
|
|
3011
3198
|
if (!provider.authenticated) return OTHER;
|
|
@@ -3082,6 +3269,19 @@ async function createCommand(opts, deps = {}) {
|
|
|
3082
3269
|
process.stderr.write("--ssh and --https cannot be combined\n");
|
|
3083
3270
|
return 2;
|
|
3084
3271
|
}
|
|
3272
|
+
if (opts.blank === true) {
|
|
3273
|
+
const clash = blankConflict(opts);
|
|
3274
|
+
if (clash !== null) {
|
|
3275
|
+
process.stderr.write(`--blank cannot be combined with ${clash}
|
|
3276
|
+
`);
|
|
3277
|
+
return 2;
|
|
3278
|
+
}
|
|
3279
|
+
if (opts.name !== void 0 && !WORKSPACE_NAME.test(opts.name)) {
|
|
3280
|
+
process.stderr.write(`${workspaceNameError(opts.name)}
|
|
3281
|
+
`);
|
|
3282
|
+
return 2;
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3085
3285
|
if (opts.org !== void 0 && !ORG_NAME.test(opts.org)) {
|
|
3086
3286
|
process.stderr.write(
|
|
3087
3287
|
`organization name "${opts.org}" is not a valid GitHub organization name
|
|
@@ -3090,6 +3290,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
3090
3290
|
return 2;
|
|
3091
3291
|
}
|
|
3092
3292
|
let pm = opts.pm !== void 0 && isPackageManager(opts.pm) ? opts.pm : detectPackageManager();
|
|
3293
|
+
const launchedWith = pm;
|
|
3093
3294
|
const interactive = opts.yes !== true && terminal.isTty();
|
|
3094
3295
|
const ui = interactive ? await makeUi(terminal) : { ...plainUi };
|
|
3095
3296
|
let sshTest;
|
|
@@ -3129,6 +3330,23 @@ async function createCommand(opts, deps = {}) {
|
|
|
3129
3330
|
);
|
|
3130
3331
|
return 1;
|
|
3131
3332
|
}
|
|
3333
|
+
let blank = opts.blank === true;
|
|
3334
|
+
if (!blank && interactive && opts.org === void 0 && opts.repos === void 0) {
|
|
3335
|
+
const start = await chooseStart(await prompts());
|
|
3336
|
+
if (start === null) return cancelled(ui);
|
|
3337
|
+
blank = start === "blank";
|
|
3338
|
+
}
|
|
3339
|
+
if (blank)
|
|
3340
|
+
return await createBlank({
|
|
3341
|
+
name: opts.name,
|
|
3342
|
+
cwd,
|
|
3343
|
+
pm,
|
|
3344
|
+
interactive,
|
|
3345
|
+
mayWrite: opts.yes === true || terminal.isTty(),
|
|
3346
|
+
skipInstall: opts.skipInstall === true,
|
|
3347
|
+
ui,
|
|
3348
|
+
prompts
|
|
3349
|
+
});
|
|
3132
3350
|
let prompted = false;
|
|
3133
3351
|
let org = opts.org;
|
|
3134
3352
|
if (org === void 0 && !interactive) {
|
|
@@ -3176,8 +3394,8 @@ async function createCommand(opts, deps = {}) {
|
|
|
3176
3394
|
org = answer.trim();
|
|
3177
3395
|
prompted = true;
|
|
3178
3396
|
}
|
|
3179
|
-
const root =
|
|
3180
|
-
const shown =
|
|
3397
|
+
const root = resolve4(cwd, org);
|
|
3398
|
+
const shown = relative4(cwd, root) || ".";
|
|
3181
3399
|
const problem = await targetProblem(cwd, root, pm);
|
|
3182
3400
|
if (problem !== null) {
|
|
3183
3401
|
process.stderr.write(`${problem}
|
|
@@ -3218,8 +3436,8 @@ async function createCommand(opts, deps = {}) {
|
|
|
3218
3436
|
);
|
|
3219
3437
|
let catalogue = { contract: 1, org, repos: {}, scopes: {} };
|
|
3220
3438
|
const staged = async () => {
|
|
3221
|
-
staging ??= await mkdtemp(
|
|
3222
|
-
return
|
|
3439
|
+
staging ??= await mkdtemp(join17(tmpdir(), "rness-join-"));
|
|
3440
|
+
return join17(staging, ".rness");
|
|
3223
3441
|
};
|
|
3224
3442
|
if (joining) {
|
|
3225
3443
|
const stagedDir = await staged();
|
|
@@ -3269,21 +3487,10 @@ async function createCommand(opts, deps = {}) {
|
|
|
3269
3487
|
});
|
|
3270
3488
|
if (p.isCancel(ok) || ok !== true) return cancelled(ui);
|
|
3271
3489
|
}
|
|
3272
|
-
const rnessDir =
|
|
3273
|
-
const install =
|
|
3274
|
-
if (opts.skipInstall === true) {
|
|
3275
|
-
ui.line("skipped", `install with ${pm} (--skip-install)`);
|
|
3276
|
-
return;
|
|
3277
|
-
}
|
|
3278
|
-
const frozen = await hasLockfile(rnessDir, pm);
|
|
3279
|
-
await ui.step(
|
|
3280
|
-
{ doing: `installing dependencies with ${pm}` },
|
|
3281
|
-
() => installDependencies(pm, rnessDir, { frozen }),
|
|
3282
|
-
() => ["installed", `dependencies with ${pm}`]
|
|
3283
|
-
);
|
|
3284
|
-
};
|
|
3490
|
+
const rnessDir = join17(root, ".rness");
|
|
3491
|
+
const install = () => installContext({ ui, pm, rnessDir, skip: opts.skipInstall === true });
|
|
3285
3492
|
if (joining) {
|
|
3286
|
-
await
|
|
3493
|
+
await mkdir5(root, { recursive: true });
|
|
3287
3494
|
await placeContext(
|
|
3288
3495
|
await staged(),
|
|
3289
3496
|
rnessDir,
|
|
@@ -3293,7 +3500,7 @@ async function createCommand(opts, deps = {}) {
|
|
|
3293
3500
|
ui.line("cloned", `${shown}/.rness (joined ${org})`);
|
|
3294
3501
|
if (opts.pm === void 0) pm = await workspacePackageManager(rnessDir);
|
|
3295
3502
|
await install();
|
|
3296
|
-
await
|
|
3503
|
+
await mkdir5(join17(root, "org"), { recursive: true });
|
|
3297
3504
|
const failures2 = await cloneSelection({
|
|
3298
3505
|
root,
|
|
3299
3506
|
rnessDir,
|
|
@@ -3316,15 +3523,18 @@ async function createCommand(opts, deps = {}) {
|
|
|
3316
3523
|
const remaining = Object.keys(catalogue.repos).filter(
|
|
3317
3524
|
(name2) => !cloned.has(name2)
|
|
3318
3525
|
);
|
|
3526
|
+
const rness2 = await rnessRunnerFor(launchedWith);
|
|
3319
3527
|
const next2 = [
|
|
3320
3528
|
`cd ${shown}`,
|
|
3321
3529
|
// The comment above its command, as the new-workspace note does: a
|
|
3322
3530
|
// line that wraps in a narrow terminal is a line nobody reads.
|
|
3323
3531
|
...remaining.length > 0 ? [
|
|
3324
3532
|
`# clone the catalogue repositories you did not pick: ${remaining.join(", ")}`,
|
|
3325
|
-
|
|
3533
|
+
`${rness2} sync --all`
|
|
3326
3534
|
] : [],
|
|
3327
|
-
...failures2.map(
|
|
3535
|
+
...failures2.map(
|
|
3536
|
+
(f) => `${rness2} add ${f.spec} # failed: ${f.message}`
|
|
3537
|
+
)
|
|
3328
3538
|
];
|
|
3329
3539
|
if (ui.session) ui.note("Next steps", next2);
|
|
3330
3540
|
else process.stdout.write(`
|
|
@@ -3333,30 +3543,17 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
3333
3543
|
ui.outro(`You joined ${org} \u2014 your workspace is in ${shown}/`);
|
|
3334
3544
|
return failures2.length > 0 ? 1 : 0;
|
|
3335
3545
|
}
|
|
3336
|
-
const
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
reportError(e);
|
|
3348
|
-
await rm4(rnessDir, { recursive: true, force: true }).catch(
|
|
3349
|
-
() => void 0
|
|
3350
|
-
);
|
|
3351
|
-
if (await isEmptyDir(root).catch(() => false))
|
|
3352
|
-
await rm4(root, { recursive: true, force: true }).catch(() => void 0);
|
|
3353
|
-
process.stderr.write(
|
|
3354
|
-
`removed ${shown}/.rness after the failure; fix it and retry
|
|
3355
|
-
`
|
|
3356
|
-
);
|
|
3357
|
-
return 1;
|
|
3358
|
-
}
|
|
3359
|
-
await mkdir3(join15(root, "org"), { recursive: true });
|
|
3546
|
+
const built = await buildContext({
|
|
3547
|
+
ui,
|
|
3548
|
+
root,
|
|
3549
|
+
shown,
|
|
3550
|
+
manifest: catalogue,
|
|
3551
|
+
pm,
|
|
3552
|
+
skipInstall: opts.skipInstall === true,
|
|
3553
|
+
kind: "new workspace"
|
|
3554
|
+
});
|
|
3555
|
+
if (!built) return 1;
|
|
3556
|
+
await mkdir5(join17(root, "org"), { recursive: true });
|
|
3360
3557
|
const failures = await cloneSelection({
|
|
3361
3558
|
root,
|
|
3362
3559
|
rnessDir,
|
|
@@ -3367,15 +3564,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
3367
3564
|
provider: gitProvider,
|
|
3368
3565
|
ui
|
|
3369
3566
|
});
|
|
3370
|
-
await
|
|
3371
|
-
try {
|
|
3372
|
-
await commitAll(rnessDir, "chore: rness workspace context");
|
|
3373
|
-
ui.line("committed", `${shown}/.rness`);
|
|
3374
|
-
} catch (e) {
|
|
3375
|
-
ui.warn(
|
|
3376
|
-
`${e instanceof Error ? e.message : String(e)} \u2014 commit ${shown}/.rness yourself`
|
|
3377
|
-
);
|
|
3378
|
-
}
|
|
3567
|
+
await commitContext({ ui, rnessDir, shown });
|
|
3379
3568
|
const code = await syncBlocks(
|
|
3380
3569
|
ui,
|
|
3381
3570
|
root,
|
|
@@ -3394,11 +3583,12 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
3394
3583
|
url: contextUrl
|
|
3395
3584
|
}) : false;
|
|
3396
3585
|
if (published === null) return cancelled(ui);
|
|
3586
|
+
const rness = await rnessRunnerFor(launchedWith);
|
|
3397
3587
|
const next = [
|
|
3398
3588
|
`cd ${shown}/.rness`,
|
|
3399
3589
|
// What was asked for and did not happen, as the command that retries
|
|
3400
3590
|
// it — the workspace is complete otherwise.
|
|
3401
|
-
...failures.map((f) =>
|
|
3591
|
+
...failures.map((f) => `${rness} add ${f.spec} # failed: ${f.message}`),
|
|
3402
3592
|
...published ? [] : [
|
|
3403
3593
|
`# create the empty private repository ${org}/.rness on github.com, then:`,
|
|
3404
3594
|
`git remote add origin ${contextUrl} && git push -u origin main`,
|
|
@@ -3407,7 +3597,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
3407
3597
|
"# or, with the GitHub CLI (gh) installed, in one step:",
|
|
3408
3598
|
`gh repo create ${org}/.rness --private --source . --push`
|
|
3409
3599
|
],
|
|
3410
|
-
`# then, for every teammate:
|
|
3600
|
+
`# then, for every teammate: ${launchedWith} create rness ${org}`
|
|
3411
3601
|
];
|
|
3412
3602
|
if (ui.session) {
|
|
3413
3603
|
ui.note("Next steps", next);
|
|
@@ -3428,7 +3618,7 @@ ${next2.map((l) => ` ${l}`).join("\n")}
|
|
|
3428
3618
|
return reportError(e);
|
|
3429
3619
|
} finally {
|
|
3430
3620
|
if (staging !== void 0)
|
|
3431
|
-
await
|
|
3621
|
+
await rm5(staging, { recursive: true, force: true }).catch(() => void 0);
|
|
3432
3622
|
}
|
|
3433
3623
|
}
|
|
3434
3624
|
|
|
@@ -3462,8 +3652,8 @@ async function logoutCommand() {
|
|
|
3462
3652
|
|
|
3463
3653
|
// src/commands/upgrade.ts
|
|
3464
3654
|
import { execFile as execFile5 } from "child_process";
|
|
3465
|
-
import { mkdir as
|
|
3466
|
-
import { basename as basename5, dirname as dirname7, join as
|
|
3655
|
+
import { mkdir as mkdir6, readFile as readFile9, readdir as readdir3 } from "fs/promises";
|
|
3656
|
+
import { basename as basename5, dirname as dirname7, join as join18, relative as relative5 } from "path";
|
|
3467
3657
|
import { promisify as promisify5 } from "util";
|
|
3468
3658
|
var execFileP5 = promisify5(execFile5);
|
|
3469
3659
|
var NOTES = "https://github.com/rness-dev/rness/tree/main/packages/cli#readme";
|
|
@@ -3560,15 +3750,15 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3560
3750
|
return 1;
|
|
3561
3751
|
}
|
|
3562
3752
|
}
|
|
3563
|
-
const packageFile =
|
|
3753
|
+
const packageFile = join18(ws.rnessDir, "package.json");
|
|
3564
3754
|
const restore = /* @__PURE__ */ new Map([
|
|
3565
3755
|
[packageFile, await readFile9(packageFile, "utf8")]
|
|
3566
3756
|
]);
|
|
3567
3757
|
if (pin.spec !== target) await writePin(ws.rnessDir, pin.spec, target);
|
|
3568
|
-
const workflows =
|
|
3758
|
+
const workflows = join18(ws.rnessDir, ".github", "workflows");
|
|
3569
3759
|
for (const name of await readdir3(workflows).catch(() => [])) {
|
|
3570
3760
|
if (!/\.ya?ml$/.test(name)) continue;
|
|
3571
|
-
const file =
|
|
3761
|
+
const file = join18(workflows, name);
|
|
3572
3762
|
const text = await readOrNull(file);
|
|
3573
3763
|
if (text === null) continue;
|
|
3574
3764
|
const shown = `.github/workflows/${name}`;
|
|
@@ -3586,14 +3776,14 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3586
3776
|
ui.warn(`${shown} names @rness/cli@${pin.spec}; update it by hand`);
|
|
3587
3777
|
}
|
|
3588
3778
|
}
|
|
3589
|
-
const dependabot =
|
|
3779
|
+
const dependabot = join18(ws.rnessDir, ".github", "dependabot.yml");
|
|
3590
3780
|
if (await exists(dependabot)) {
|
|
3591
3781
|
ui.hint(".github/dependabot.yml is already there \u2014 left as it is");
|
|
3592
3782
|
} else {
|
|
3593
|
-
await
|
|
3783
|
+
await mkdir6(dirname7(dependabot), { recursive: true });
|
|
3594
3784
|
await writeFileAtomic(
|
|
3595
3785
|
dependabot,
|
|
3596
|
-
await readFile9(
|
|
3786
|
+
await readFile9(join18(scaffoldDir(), ".github", "dependabot.yml"), "utf8")
|
|
3597
3787
|
);
|
|
3598
3788
|
ui.line(
|
|
3599
3789
|
"wrote",
|
|
@@ -3610,7 +3800,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3610
3800
|
for (const [file, text] of restore) await writeFileAtomic(file, text);
|
|
3611
3801
|
const message = e instanceof Error ? e.message : String(e);
|
|
3612
3802
|
ui.error(message);
|
|
3613
|
-
ui.hint(`restored ${
|
|
3803
|
+
ui.hint(`restored ${relative5(cwd, packageFile) || packageFile}`, "stderr");
|
|
3614
3804
|
if (/minimum.?release.?age|NO_MATURE/i.test(message))
|
|
3615
3805
|
ui.hint(
|
|
3616
3806
|
"pnpm refuses versions published less than 24 h ago: add '@rness/cli' under minimumReleaseAgeExclude in .rness/pnpm-workspace.yaml",
|
|
@@ -3629,7 +3819,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3629
3819
|
ui.gitIsSilent = true;
|
|
3630
3820
|
const code = await syncBlocks(ui, ws.root, basename5(ws.root));
|
|
3631
3821
|
if (code !== 0) return code;
|
|
3632
|
-
const rel =
|
|
3822
|
+
const rel = relative5(cwd, ws.rnessDir) || ".";
|
|
3633
3823
|
const next = [
|
|
3634
3824
|
`git -C ${rel} add -A && git -C ${rel} commit -m "chore: rness ${target}"`,
|
|
3635
3825
|
"# teammates: git pull \u2014 the next rness command installs it"
|
|
@@ -3655,7 +3845,7 @@ async function upgradeCommand(version, opts, terminal = defaultTerminal) {
|
|
|
3655
3845
|
}
|
|
3656
3846
|
|
|
3657
3847
|
// src/core/blocks.ts
|
|
3658
|
-
import { join as
|
|
3848
|
+
import { join as join19 } from "path";
|
|
3659
3849
|
var PROBLEM = /* @__PURE__ */ new Set(["stale", "malformed"]);
|
|
3660
3850
|
async function checkBlocks({
|
|
3661
3851
|
root,
|
|
@@ -3672,13 +3862,13 @@ async function checkBlocks({
|
|
|
3672
3862
|
warnings: checks.filter((c) => !PROBLEM.has(c.status) && c.message !== null).map((c) => c.message),
|
|
3673
3863
|
checks
|
|
3674
3864
|
});
|
|
3675
|
-
if (!await exists(
|
|
3865
|
+
if (!await exists(join19(root, "org"))) return derive();
|
|
3676
3866
|
const targets = [{ scope: null, dir: root, depth: 0, label: "AGENTS.md" }];
|
|
3677
3867
|
for (const [name, entry] of Object.entries(manifest.scopes)) {
|
|
3678
3868
|
const segments = entry.path.split("/");
|
|
3679
3869
|
targets.push({
|
|
3680
3870
|
scope: name,
|
|
3681
|
-
dir:
|
|
3871
|
+
dir: join19(root, ...segments),
|
|
3682
3872
|
depth: segments.length,
|
|
3683
3873
|
label: `${entry.path}/AGENTS.md`
|
|
3684
3874
|
});
|
|
@@ -3692,7 +3882,7 @@ async function checkBlocks({
|
|
|
3692
3882
|
);
|
|
3693
3883
|
continue;
|
|
3694
3884
|
}
|
|
3695
|
-
const text = await readOrNull(
|
|
3885
|
+
const text = await readOrNull(join19(t.dir, "AGENTS.md"));
|
|
3696
3886
|
const lines = text === null ? [] : text.split(/\r?\n/);
|
|
3697
3887
|
const found = findBlock(lines);
|
|
3698
3888
|
if (found.kind === "none") {
|
|
@@ -3727,7 +3917,7 @@ async function checkBlocks({
|
|
|
3727
3917
|
}
|
|
3728
3918
|
|
|
3729
3919
|
// src/core/contract.ts
|
|
3730
|
-
import { join as
|
|
3920
|
+
import { join as join20 } from "path";
|
|
3731
3921
|
var STATUSES = {
|
|
3732
3922
|
adr: ["Proposed", "Accepted", "Rejected", "Superseded"],
|
|
3733
3923
|
specs: [
|
|
@@ -3746,7 +3936,7 @@ async function checkContract(rnessDir) {
|
|
|
3746
3936
|
const problems = [];
|
|
3747
3937
|
for (const name of COLLECTIONS) {
|
|
3748
3938
|
const allowed = STATUSES[name];
|
|
3749
|
-
const items = await collectMarkdown(
|
|
3939
|
+
const items = await collectMarkdown(join20(rnessDir, name));
|
|
3750
3940
|
for (const item of items) {
|
|
3751
3941
|
const where = `${name}/${item.rel}`;
|
|
3752
3942
|
if (SKIP.has(where)) continue;
|
|
@@ -3789,8 +3979,7 @@ async function validateCommand(opts, deps = {}) {
|
|
|
3789
3979
|
const ws = await findWorkspace(cwd);
|
|
3790
3980
|
const manifest = await loadManifest(ws.rnessDir);
|
|
3791
3981
|
for (const s of Object.keys(manifest.scopes)) scopeChain(manifest, s);
|
|
3792
|
-
const
|
|
3793
|
-
const warnings = warning === null ? [] : [warning];
|
|
3982
|
+
const org = workspaceName(manifest, ws.root);
|
|
3794
3983
|
const problems = await checkContract(ws.rnessDir);
|
|
3795
3984
|
const blocks = await checkBlocks({
|
|
3796
3985
|
root: ws.root,
|
|
@@ -3799,12 +3988,11 @@ async function validateCommand(opts, deps = {}) {
|
|
|
3799
3988
|
org
|
|
3800
3989
|
});
|
|
3801
3990
|
problems.push(...blocks.problems);
|
|
3802
|
-
warnings.push(...blocks.warnings);
|
|
3803
3991
|
const ui = await makeUi(deps.terminal ?? defaultTerminal);
|
|
3804
3992
|
if (!ui.session) {
|
|
3805
3993
|
for (const p of problems) process.stderr.write(`${p}
|
|
3806
3994
|
`);
|
|
3807
|
-
for (const w of warnings) process.stderr.write(`${warn(w)}
|
|
3995
|
+
for (const w of blocks.warnings) process.stderr.write(`${warn(w)}
|
|
3808
3996
|
`);
|
|
3809
3997
|
if (problems.length > 0) return 1;
|
|
3810
3998
|
process.stdout.write("context ok\n");
|
|
@@ -3817,7 +4005,6 @@ async function validateCommand(opts, deps = {}) {
|
|
|
3817
4005
|
ui.error(c.message);
|
|
3818
4006
|
else ui.warn(c.message);
|
|
3819
4007
|
for (const p of problems) if (!blocks.problems.includes(p)) ui.error(p);
|
|
3820
|
-
for (const w of warnings) if (!blocks.warnings.includes(w)) ui.warn(w);
|
|
3821
4008
|
ui.outro(
|
|
3822
4009
|
problems.length > 0 ? "Context mismatch" : "Context OK",
|
|
3823
4010
|
problems.length > 0 ? "error" : "done"
|
|
@@ -3920,8 +4107,11 @@ function buildProgram(state) {
|
|
|
3920
4107
|
state.code = await upgradeCommand(version, opts);
|
|
3921
4108
|
});
|
|
3922
4109
|
program.command("create").description(
|
|
3923
|
-
"Create
|
|
3924
|
-
).argument(
|
|
4110
|
+
"Create or join a GitHub organization's workspace, or a blank one with --blank"
|
|
4111
|
+
).argument(
|
|
4112
|
+
"[name]",
|
|
4113
|
+
"the GitHub organization, same as --org; with --blank, the workspace directory"
|
|
4114
|
+
).option(
|
|
3925
4115
|
"--org <name>",
|
|
3926
4116
|
"the GitHub organization, exact name (github.com/<name>)"
|
|
3927
4117
|
).option(
|
|
@@ -3933,7 +4123,16 @@ function buildProgram(state) {
|
|
|
3933
4123
|
).option(
|
|
3934
4124
|
"--ssh",
|
|
3935
4125
|
"force git@github.com: (default: SSH when it works, else HTTPS)"
|
|
3936
|
-
).option("--https", "force https://github.com/").option(
|
|
4126
|
+
).option("--https", "force https://github.com/").option(
|
|
4127
|
+
"--blank",
|
|
4128
|
+
"a local workspace with no GitHub organization: .rness/, an empty org/, the root files"
|
|
4129
|
+
).option("--skip-install", "do not install .rness/ dependencies").option("-y, --yes", "do not ask for confirmation").option("--template <name>", "reserved").addOption(new Option("--host <base>").hideHelp()).addOption(new Option("--github-api <base>").hideHelp()).addOption(new Option("--cwd <dir>").hideHelp()).action(async (orgArg, opts) => {
|
|
4130
|
+
if (opts.blank === true) {
|
|
4131
|
+
state.code = await createCommand(
|
|
4132
|
+
orgArg === void 0 ? opts : { ...opts, name: orgArg }
|
|
4133
|
+
);
|
|
4134
|
+
return;
|
|
4135
|
+
}
|
|
3937
4136
|
if (orgArg !== void 0 && opts.org !== void 0 && orgArg !== opts.org) {
|
|
3938
4137
|
process.stderr.write(
|
|
3939
4138
|
`organization given twice: "${orgArg}" and --org "${opts.org}"
|
|
@@ -3967,6 +4166,51 @@ async function run(argv) {
|
|
|
3967
4166
|
return state.code;
|
|
3968
4167
|
}
|
|
3969
4168
|
|
|
4169
|
+
// src/core/catch-up.ts
|
|
4170
|
+
import { spawnSync } from "child_process";
|
|
4171
|
+
var OWN_COMMANDS = [
|
|
4172
|
+
"create",
|
|
4173
|
+
"upgrade",
|
|
4174
|
+
"update",
|
|
4175
|
+
"login",
|
|
4176
|
+
"logout",
|
|
4177
|
+
"git-credential"
|
|
4178
|
+
];
|
|
4179
|
+
var CATCH_UP_DONE = "RNESS_CATCH_UP_DONE";
|
|
4180
|
+
async function catchUp(argv, deps = {}) {
|
|
4181
|
+
const env = deps.env ?? process.env;
|
|
4182
|
+
const cwd = deps.cwd ?? process.cwd();
|
|
4183
|
+
const command = argv[0];
|
|
4184
|
+
if (command === void 0 || command.startsWith("-") || OWN_COMMANDS.includes(command) || env["RNESS_NO_DELEGATE"] === "1" || env[CATCH_UP_DONE] === "1")
|
|
4185
|
+
return null;
|
|
4186
|
+
const ws = await findWorkspace(cwd).catch(() => null);
|
|
4187
|
+
if (ws === null) return null;
|
|
4188
|
+
env[CATCH_UP_DONE] = "1";
|
|
4189
|
+
const stderr = deps.stderr ?? ((text) => void process.stderr.write(text));
|
|
4190
|
+
const outcome = await ensureInstalled(ws.rnessDir, {
|
|
4191
|
+
env,
|
|
4192
|
+
announce: (pin) => stderr(`installing @rness/cli ${pin} in .rness\u2026
|
|
4193
|
+
`),
|
|
4194
|
+
...deps.install === void 0 ? {} : { install: deps.install }
|
|
4195
|
+
});
|
|
4196
|
+
if (outcome === null) return null;
|
|
4197
|
+
if (!outcome.installed) {
|
|
4198
|
+
const warning = await driftWarning(cwd);
|
|
4199
|
+
if (warning !== null) stderr(`${warning}
|
|
4200
|
+
`);
|
|
4201
|
+
return null;
|
|
4202
|
+
}
|
|
4203
|
+
if (await pinDrift(ws.rnessDir) !== null) return null;
|
|
4204
|
+
const bin = await pinnedBinPath(ws.rnessDir);
|
|
4205
|
+
if (bin === null) return null;
|
|
4206
|
+
const spawn4 = deps.spawn ?? ((file, args, dir) => spawnSync(process.execPath, [file, ...args], {
|
|
4207
|
+
cwd: dir,
|
|
4208
|
+
env,
|
|
4209
|
+
stdio: "inherit"
|
|
4210
|
+
}).status ?? 1);
|
|
4211
|
+
return spawn4(bin, argv, cwd);
|
|
4212
|
+
}
|
|
4213
|
+
|
|
3970
4214
|
export {
|
|
3971
4215
|
exists,
|
|
3972
4216
|
readOrNull,
|
|
@@ -4013,20 +4257,16 @@ export {
|
|
|
4013
4257
|
addCommand,
|
|
4014
4258
|
renderMarkdown,
|
|
4015
4259
|
contextCommand,
|
|
4016
|
-
|
|
4260
|
+
VERSION,
|
|
4017
4261
|
findDelegate,
|
|
4018
4262
|
PACKAGE_MANAGERS,
|
|
4019
4263
|
isPackageManager,
|
|
4020
4264
|
detectPackageManager,
|
|
4021
4265
|
packageManagerVersion,
|
|
4022
4266
|
installDependencies,
|
|
4023
|
-
pinDrift,
|
|
4024
|
-
driftWarning,
|
|
4025
|
-
ensureInstalled,
|
|
4026
4267
|
SCAFFOLD_FILES,
|
|
4027
4268
|
scaffoldDir,
|
|
4028
4269
|
copyScaffold,
|
|
4029
|
-
VERSION,
|
|
4030
4270
|
loginCommand,
|
|
4031
4271
|
createCommand,
|
|
4032
4272
|
logoutCommand,
|
|
@@ -4035,6 +4275,8 @@ export {
|
|
|
4035
4275
|
STATUSES,
|
|
4036
4276
|
checkContract,
|
|
4037
4277
|
validateCommand,
|
|
4038
|
-
run
|
|
4278
|
+
run,
|
|
4279
|
+
OWN_COMMANDS,
|
|
4280
|
+
catchUp
|
|
4039
4281
|
};
|
|
4040
|
-
//# sourceMappingURL=chunk-
|
|
4282
|
+
//# sourceMappingURL=chunk-NY3WV7X5.js.map
|