@vincentt-xr/cli 0.12.2 → 0.12.3

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.
@@ -51,3 +51,22 @@ export declare function refusedFlag(args: string[]): string | undefined;
51
51
  * the worst kind for an identifier that decides which project a folder points at.
52
52
  */
53
53
  export declare function positionalArgs(args: string[]): string[];
54
+ /**
55
+ * Does this `create` invocation name a folder that `--dir` already points at?
56
+ *
57
+ * `--dir` says WHERE TO WORK; the positional says WHAT TO CREATE INSIDE it. They
58
+ * compose, so `--dir ~/apps/foo foo` writes `~/apps/foo/foo` — correct by the
59
+ * rules and almost never what was meant. The help said `--dir <path> (project
60
+ * dir)` under "Common options", which reads as *the project's own directory*
61
+ * rather than *the parent of it*, so an agent handed a folder path and a name
62
+ * passes both and gets a doubled path.
63
+ *
64
+ * Returns the resolved `--dir` when the collision is present, so the caller can
65
+ * name both correct forms; `undefined` otherwise.
66
+ *
67
+ * EXACT match on the last path segment only. A near miss (`foo-app` in `foo/`)
68
+ * is left alone: inferring intent from a similar name would refuse work that was
69
+ * never ambiguous. Trailing separators are normalised by `resolve` first, so
70
+ * `--dir ~/apps/foo/` behaves the same as without.
71
+ */
72
+ export declare function createDirCollision(dirFlag: string | undefined, name: string, resolve: (p: string) => string, basename: (p: string) => string): string | undefined;
package/dist/cli/args.js CHANGED
@@ -142,3 +142,27 @@ export function positionalArgs(args) {
142
142
  }
143
143
  return out;
144
144
  }
145
+ /**
146
+ * Does this `create` invocation name a folder that `--dir` already points at?
147
+ *
148
+ * `--dir` says WHERE TO WORK; the positional says WHAT TO CREATE INSIDE it. They
149
+ * compose, so `--dir ~/apps/foo foo` writes `~/apps/foo/foo` — correct by the
150
+ * rules and almost never what was meant. The help said `--dir <path> (project
151
+ * dir)` under "Common options", which reads as *the project's own directory*
152
+ * rather than *the parent of it*, so an agent handed a folder path and a name
153
+ * passes both and gets a doubled path.
154
+ *
155
+ * Returns the resolved `--dir` when the collision is present, so the caller can
156
+ * name both correct forms; `undefined` otherwise.
157
+ *
158
+ * EXACT match on the last path segment only. A near miss (`foo-app` in `foo/`)
159
+ * is left alone: inferring intent from a similar name would refuse work that was
160
+ * never ambiguous. Trailing separators are normalised by `resolve` first, so
161
+ * `--dir ~/apps/foo/` behaves the same as without.
162
+ */
163
+ export function createDirCollision(dirFlag, name, resolve, basename) {
164
+ if (dirFlag === undefined)
165
+ return undefined;
166
+ const resolved = resolve(dirFlag);
167
+ return basename(resolved) === name ? resolved : undefined;
168
+ }
package/dist/cli/index.js CHANGED
@@ -48,7 +48,7 @@ import { nudgeFor } from "../dx/nudge.js";
48
48
  import { FETCH_TIMEOUT_MS, isFresh, updateNotice } from "../dx/updateCheck.js";
49
49
  import { fetchLatest, loadCache, saveCache } from "../dx/updateCheckIo.js";
50
50
  import { verbOf } from "../dx/skeleton.js";
51
- import { createSourceFlagRefusal, fromFlagConflict, isHelpInvocation, isVersionInvocation, positionalArgs, refusedFlag, } from "./args.js";
51
+ import { createDirCollision, createSourceFlagRefusal, fromFlagConflict, isHelpInvocation, isVersionInvocation, positionalArgs, refusedFlag, } from "./args.js";
52
52
  import { parseSourceRef } from "../lib/sourceRef.js";
53
53
  import { printCreateSourceFlagRefusal, printFlagConflict, printSourceRefFailure, } from "../lib/sourceRefCopy.js";
54
54
  import { registerAfterScaffold } from "../lib/initFrom.js";
@@ -291,6 +291,34 @@ async function create() {
291
291
  process.exitCode = 1;
292
292
  return;
293
293
  }
294
+ // `--dir` says WHERE TO WORK; the positional says WHAT TO CREATE INSIDE it.
295
+ // They compose, so `--dir ~/apps/foo foo` writes `~/apps/foo/foo` — correct
296
+ // by the rules and almost never what was meant.
297
+ //
298
+ // Refused rather than silently resolved, because both readings are defensible
299
+ // and the CLI cannot tell them apart: someone who genuinely wants a nested
300
+ // `foo/foo` has a way to say so (below), and someone who does not gets the
301
+ // mistake at the moment it is cheap instead of after a scaffold and a
302
+ // registration.
303
+ //
304
+ // This fires on an EXACT match of the last path segment only. A near miss is
305
+ // left alone: guessing at intent from a similar name would refuse work that
306
+ // was never ambiguous.
307
+ const collided = createDirCollision(flag("--dir"), safe, path.resolve, path.basename);
308
+ if (collided !== undefined) {
309
+ const parent = path.dirname(collided);
310
+ print(`✗ That would create "${safe}" inside a folder already called "${safe}".`);
311
+ print("");
312
+ print(" --dir is where to work; the name is the folder to create inside it.");
313
+ print("");
314
+ print(` For ${path.join(parent, safe)}:`);
315
+ print(` vincentt create --dir ${parent} ${safe}`);
316
+ print("");
317
+ print(` For ${path.join(collided, safe)}, which is what this asked for:`);
318
+ print(` cd ${collided} && vincentt create ${safe}`);
319
+ process.exitCode = 1;
320
+ return;
321
+ }
294
322
  dir = safe;
295
323
  }
296
324
  const interactive = Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY);
@@ -21,7 +21,12 @@ export const USAGE_TEXT = `Vincentt CLI — local-first AR dev loop\n\n` +
21
21
  ` [--from <owner>/<repo>@<tag>] Clone a gallery template\n` +
22
22
  ` and register it [--name <s>] [--slug <s>]\n` +
23
23
  ` create [name] Register a project and reserve its address; scaffolds first\n` +
24
- ` if the folder is empty [--name <s>] [--slug <s>]\n` +
24
+ ` if the folder is empty. The name is a NEW folder created\n` +
25
+ ` inside the working dir, not the working dir itself:\n` +
26
+ ` vincentt create my-app -> ./my-app\n` +
27
+ ` vincentt create --dir ~/code my-app -> ~/code/my-app\n` +
28
+ ` Omit the name to use the current folder as-is.\n` +
29
+ ` [--name <s>] [--slug <s>]\n` +
25
30
  ` [--org <s>] [--preview] [--force] [--no-install]\n` +
26
31
  ` link <project-id> Link this folder to an existing project\n` +
27
32
  ` unlink Disconnect this folder from its project\n` +
@@ -93,5 +98,6 @@ export const USAGE_TEXT = `Vincentt CLI — local-first AR dev loop\n\n` +
93
98
  ` disable it, and is shown on \`vincentt dx\` and\n` +
94
99
  ` \`vincentt version\` while on.\n` +
95
100
  ` dx disable Turn that off again.\n\n` +
96
- `Common options: --dir <path> (project dir, default cwd)\n` +
101
+ `Common options: --dir <path> (where to work, default cwd — for \`create\` this is\n` +
102
+ ` the PARENT of the new folder, not the folder itself)\n` +
97
103
  ` --allow-custom-api honor a binding's apiUrl override\n`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vincentt-xr/cli",
3
- "version": "0.12.2",
3
+ "version": "0.12.3",
4
4
  "description": "Vincentt CLI — the vincentt command-line dev loop (login, create, publish, preview, diagnostics, feedback, relay)",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",