Skip to content

Commit c441ba6

Browse files
committed
feat(create)!: switch manifest-entry syntax from @org/name to @org:name
BREAKING (unpublished feature): the manifest-entry selector uses `:` instead of `/`. `vp create @your-org:web` now picks the `web` entry from `@your-org/create`'s `createConfig.templates` manifest. The old `@your-org/web` form is no longer a manifest lookup — it reverts to the pre-feature `expandCreateShorthand` path that turns it into `@your-org/create-web`, exactly as it worked before this feature. Rationale (cpojer + fengmk2, PR #1398 review): the `/` form collides with real npm packages. If `@your-org/web` exists on npm as a genuine package, `vp create @your-org/web` would silently prefer the manifest entry over the real package — confusing, and fragile. The `:` separator mirrors the existing `vite:monorepo` / `vite:library` builtin-template syntax, which users already read as "namespace:entry". It also keeps manifest entries lexically distinct from any real npm package path. Miss behavior: `vp create @org:foo` where `foo` isn't in the manifest (or the manifest is absent) is a HARD ERROR listing the available entries. No silent fall-through — the `:` form is an explicit manifest lookup, and the fallback shorthand is reserved for the `/` form. Updated: `parseOrgScopedSpec` parses only `@scope` and `@scope:name` (returns `null` for `@scope/anything`), `resolveOrgManifestForCreate` errors on direct-selection miss, CLI help text, RFC command matrix + examples + error-handling table + compatibility section, docs guide + config page, snap-test fixture commands (`create-org-bundled`, `create-org-monorepo-direct-in-monorepo`), `--no-interactive` hint line, and unit tests for `parseOrgScopedSpec` (new test pinning the null return for the `/` form).
1 parent f4506d4 commit c441ba6

14 files changed

Lines changed: 116 additions & 81 deletions

File tree

docs/config/create.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export default defineConfig({
1616
});
1717
```
1818

19-
Any value accepted by `vp create` as a first argument works here — `@your-org` for an org picker, `@your-org/web` for a direct manifest entry, `vite:application` for a built-in, etc.
19+
Any value accepted by `vp create` as a first argument works here — `@your-org` for an org picker, `@your-org:web` for a direct manifest entry, `vite:application` for a built-in, etc.
2020

2121
## Precedence
2222

docs/guide/create.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ An organization can publish a curated set of templates under a single npm scope
9898
vp create @your-org
9999

100100
# Run a specific manifest entry directly
101-
vp create @your-org/web
101+
vp create @your-org:web
102102

103103
# Set the org as the default for a repo (see create.defaultTemplate config)
104104
vp create
@@ -171,7 +171,7 @@ Each entry supports:
171171

172172
| Field | Required | Notes |
173173
| ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
174-
| `name` | yes | Kebab-case identifier. Used by `vp create @org/<name>` for direct selection. Must be unique within the array. |
174+
| `name` | yes | Kebab-case identifier. Used by `vp create @org:<name>` for direct selection. Must be unique within the array. |
175175
| `description` | yes | One-line description shown in the picker. |
176176
| `template` | yes | An npm specifier (`@org/template-foo`, optionally `@version`), a GitHub URL (`github:user/repo`), a `vite:*` builtin, a local workspace package name, or a relative path (`./templates/foo`) that resolves against the `@org/create` root. |
177177
| `keywords` | no | Filter terms for picker search. |
@@ -215,7 +215,7 @@ Available templates in @your-org/create:
215215
216216
Examples:
217217
# Scaffold a specific template from the org
218-
vp create @your-org/web --no-interactive
218+
vp create @your-org:web --no-interactive
219219
220220
# Or use a Vite+ built-in template
221221
vp create vite:application --no-interactive

packages/cli/snap-tests/create-org-bundled/snap.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
> node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org/demo --no-interactive --directory my-demo-app # bundled template: extract tarball, copy subdir
1+
> node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org:demo --no-interactive --directory my-demo-app # bundled template: extract tarball, copy subdir
22

33
selected 'demo' from @your-org/create
44
◇ Scaffolded my-demo-app

packages/cli/snap-tests/create-org-bundled/steps.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"commands": [
3-
"node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org/demo --no-interactive --directory my-demo-app # bundled template: extract tarball, copy subdir",
3+
"node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org:demo --no-interactive --directory my-demo-app # bundled template: extract tarball, copy subdir",
44
"cat my-demo-app/package.json # verify package.json name was rewritten",
55
"cat my-demo-app/src/index.ts # verify bundled source copied",
66
"ls my-demo-app/README.md # verify README copied"

packages/cli/snap-tests/create-org-config-default/snap.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Available templates in @your-org/create:
99

1010
Examples:
1111
# Scaffold a specific template from the org
12-
vp create @your-org/web --no-interactive
12+
vp create @your-org:web --no-interactive
1313

1414
# Or use a Vite+ built-in template
1515
vp create vite:application --no-interactive

packages/cli/snap-tests/create-org-monorepo-direct-in-monorepo/snap.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[1]> node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org/monorepo --no-interactive # direct-selecting a monorepo entry inside a monorepo -> refuse
1+
[1]> node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org:monorepo --no-interactive # direct-selecting a monorepo entry inside a monorepo -> refuse
22

33
You are already in a monorepo workspace.
44
Use a different template or run this command outside the monorepo
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"commands": [
3-
"node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org/monorepo --no-interactive # direct-selecting a monorepo entry inside a monorepo -> refuse"
3+
"node $SNAP_CASES_DIR/.shared/mock-npm-registry.mjs -- vp create @your-org:monorepo --no-interactive # direct-selecting a monorepo entry inside a monorepo -> refuse"
44
]
55
}

packages/cli/snap-tests/create-org-monorepo-filter/snap.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Available templates in @your-org/create:
1212

1313
Examples:
1414
# Scaffold a specific template from the org
15-
vp create @your-org/web --no-interactive
15+
vp create @your-org:web --no-interactive
1616

1717
# Or use a Vite+ built-in template
1818
vp create vite:application --no-interactive

packages/cli/snap-tests/create-org-no-interactive-error/snap.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Available templates in @your-org/create:
1010

1111
Examples:
1212
# Scaffold a specific template from the org
13-
vp create @your-org/web --no-interactive
13+
vp create @your-org:web --no-interactive
1414

1515
# Or use a Vite+ built-in template
1616
vp create vite:application --no-interactive

packages/cli/src/create/__tests__/org-manifest.spec.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,22 @@ describe('parseOrgScopedSpec', () => {
2424
expect(parseOrgScopedSpec('@your-org@latest')).toEqual({ scope: '@your-org' });
2525
});
2626

27-
it('parses @scope/name', () => {
28-
expect(parseOrgScopedSpec('@your-org/web')).toEqual({ scope: '@your-org', name: 'web' });
27+
it('parses @scope:name', () => {
28+
expect(parseOrgScopedSpec('@your-org:web')).toEqual({ scope: '@your-org', name: 'web' });
2929
});
3030

31-
it('parses @scope/name@version', () => {
32-
expect(parseOrgScopedSpec('@your-org/web@1.2.3')).toEqual({ scope: '@your-org', name: 'web' });
31+
it('parses @scope:name@version', () => {
32+
expect(parseOrgScopedSpec('@your-org:web@1.2.3')).toEqual({ scope: '@your-org', name: 'web' });
3333
});
3434

35-
it('treats a trailing slash as scope-only', () => {
36-
expect(parseOrgScopedSpec('@your-org/')).toEqual({ scope: '@your-org' });
35+
it('treats @scope: (empty name) as scope-only', () => {
36+
expect(parseOrgScopedSpec('@your-org:')).toEqual({ scope: '@your-org' });
37+
});
38+
39+
it('returns null for the @scope/name slash form (reserved for existing shorthand)', () => {
40+
expect(parseOrgScopedSpec('@your-org/web')).toBeNull();
41+
expect(parseOrgScopedSpec('@your-org/create-web')).toBeNull();
42+
expect(parseOrgScopedSpec('@your-org/')).toBeNull();
3743
});
3844
});
3945

0 commit comments

Comments
 (0)