@rness/cli 0.5.3 → 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.
@@ -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((resolve4) => {
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
- resolve4(probe);
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 resolveOrg(manifest, root) {
818
- if (manifest.org !== null) return { org: manifest.org, warning: null };
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) url = repoUrl(host, org, spec);
919
- else if (parts.length === 2 && parts[0] !== void 0 && parts[1] !== void 0)
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((resolve4) => {
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
- resolve4(access2);
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 { org, warning } = resolveOrg(manifest, ws.root);
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 { org, warning } = resolveOrg(manifest, ws.root);
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) ui.intro(`Add ${parsed.name} to workspace ${org}`);
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 !== "");
@@ -2389,9 +2403,6 @@ async function copyScaffold(dest, tokens) {
2389
2403
  }
2390
2404
  }
2391
2405
 
2392
- // src/version.ts
2393
- var VERSION = "0.5.3";
2394
-
2395
2406
  // src/commands/login.ts
2396
2407
  import { spawn as spawn3 } from "child_process";
2397
2408
  import { realpath } from "fs/promises";
@@ -2542,9 +2553,13 @@ function revokeUrl() {
2542
2553
  }
2543
2554
 
2544
2555
  // src/commands/create.ts
2545
- import { mkdir as mkdir3, mkdtemp, readdir as readdir2, rename as rename2, rm as rm4, stat as stat3 } from "fs/promises";
2556
+ import { mkdir as mkdir5, mkdtemp, rename as rename2, rm as rm5 } from "fs/promises";
2546
2557
  import { tmpdir } from "os";
2547
- import { dirname as dirname6, join as join15, relative as relative2, resolve as resolve3 } from "path";
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";
2548
2563
 
2549
2564
  // src/core/pinned.ts
2550
2565
  import { execFile as execFile4 } from "child_process";
@@ -2784,6 +2799,84 @@ async function syncPinned(root, shown, fallback) {
2784
2799
  }
2785
2800
  }
2786
2801
 
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;
2812
+ }
2813
+ }
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`;
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;
2827
+ }
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;
2833
+ }
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}`]
2839
+ );
2840
+ }
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 });
2846
+ try {
2847
+ await copyScaffold(rnessDir, {
2848
+ version: VERSION,
2849
+ packageManager: `${pm}@${pmVersion}`
2850
+ });
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;
2865
+ }
2866
+ }
2867
+ async function commitContext(input) {
2868
+ const { ui, rnessDir, shown } = input;
2869
+ await init(rnessDir);
2870
+ try {
2871
+ await commitAll(rnessDir, "chore: rness workspace context");
2872
+ ui.line("committed", `${shown}/.rness`);
2873
+ } catch (e) {
2874
+ ui.warn(
2875
+ `${e instanceof Error ? e.message : String(e)} \u2014 commit ${shown}/.rness yourself`
2876
+ );
2877
+ }
2878
+ }
2879
+
2787
2880
  // src/core/sync-blocks.ts
2788
2881
  async function syncBlocks(ui, root, shown, inPlace, sshWorks) {
2789
2882
  if (!ui.session)
@@ -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
- async function isEmptyDir(dir) {
2817
- return (await readdir2(dir)).length === 0;
2818
- }
2819
- async function insideWorkspace(dir) {
2820
- try {
2821
- await findWorkspace(dir);
2822
- return true;
2823
- } catch {
2824
- return false;
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;
2825
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;
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
- join15(input.root, "org", repo),
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 = resolve3(cwd, org);
3180
- const shown = relative2(cwd, root) || ".";
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(join15(tmpdir(), "rness-join-"));
3222
- return join15(staging, ".rness");
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 = join15(root, ".rness");
3273
- const install = async () => {
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 mkdir3(root, { recursive: true });
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 mkdir3(join15(root, "org"), { recursive: true });
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
- "rness sync --all"
3533
+ `${rness2} sync --all`
3326
3534
  ] : [],
3327
- ...failures2.map((f) => `rness add ${f.spec} # failed: ${f.message}`)
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 pmVersion = await packageManagerVersion(pm);
3337
- await mkdir3(root, { recursive: true });
3338
- try {
3339
- await copyScaffold(rnessDir, {
3340
- version: VERSION,
3341
- packageManager: `${pm}@${pmVersion}`
3342
- });
3343
- await writeManifest(rnessDir, catalogue);
3344
- ui.line("created", `${shown}/.rness (new workspace)`);
3345
- await install();
3346
- } catch (e) {
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 init(rnessDir);
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) => `rness add ${f.spec} # failed: ${f.message}`),
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: npm create rness ${org}`
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 rm4(staging, { recursive: true, force: true }).catch(() => void 0);
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 mkdir4, readFile as readFile9, readdir as readdir3 } from "fs/promises";
3466
- import { basename as basename5, dirname as dirname7, join as join16, relative as relative3 } from "path";
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 = join16(ws.rnessDir, "package.json");
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 = join16(ws.rnessDir, ".github", "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 = join16(workflows, name);
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 = join16(ws.rnessDir, ".github", "dependabot.yml");
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 mkdir4(dirname7(dependabot), { recursive: true });
3783
+ await mkdir6(dirname7(dependabot), { recursive: true });
3594
3784
  await writeFileAtomic(
3595
3785
  dependabot,
3596
- await readFile9(join16(scaffoldDir(), ".github", "dependabot.yml"), "utf8")
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 ${relative3(cwd, packageFile) || packageFile}`, "stderr");
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 = relative3(cwd, ws.rnessDir) || ".";
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 join17 } from "path";
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(join17(root, "org"))) return derive();
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: join17(root, ...segments),
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(join17(t.dir, "AGENTS.md"));
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 join18 } from "path";
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(join18(rnessDir, name));
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 { org, warning } = resolveOrg(manifest, ws.root);
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 a workspace for a GitHub organization, or join its existing .rness"
3924
- ).argument("[org]", "the GitHub organization, same as --org").option(
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("--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) => {
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}"
@@ -4058,6 +4257,7 @@ export {
4058
4257
  addCommand,
4059
4258
  renderMarkdown,
4060
4259
  contextCommand,
4260
+ VERSION,
4061
4261
  findDelegate,
4062
4262
  PACKAGE_MANAGERS,
4063
4263
  isPackageManager,
@@ -4067,7 +4267,6 @@ export {
4067
4267
  SCAFFOLD_FILES,
4068
4268
  scaffoldDir,
4069
4269
  copyScaffold,
4070
- VERSION,
4071
4270
  loginCommand,
4072
4271
  createCommand,
4073
4272
  logoutCommand,
@@ -4080,4 +4279,4 @@ export {
4080
4279
  OWN_COMMANDS,
4081
4280
  catchUp
4082
4281
  };
4083
- //# sourceMappingURL=chunk-CQ6YD63L.js.map
4282
+ //# sourceMappingURL=chunk-NY3WV7X5.js.map