Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!doctype html>
<html>
<body>
<h1>web</h1>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "name": "web", "private": true }
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"name": "per-command-config",
"type": "module",
"private": true,
"workspaces": [
"apps/*",
"packages/*"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "name": "ui", "private": true }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ui = 'ui';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
defaultPackage: { build: './apps/web', pack: './packages/ui' },
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<!doctype html>
<html>
<body>
<h1>root app</h1>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "name": "per-command-fallthrough", "type": "module", "private": true, "workspaces": ["packages/*"] }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "name": "ui", "private": true }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ui = 'ui';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default {
defaultPackage: { pack: './packages/ui' },
};
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,30 @@ defaultPackage inside `defineConfig({ ... } satisfies UserConfig)` is also
honored: the wrapper on the defineConfig argument is unwrapped too.
"""
steps = [{ argv = ["vp", "build"], cwd = "dc_wrapper" }]

[[case]]
name = "default_package_per_command"
vp = ["local", "global"]
comment = """
The object form maps commands individually at a workspace root: `vp build`
targets ./apps/web while `vp pack` targets ./packages/ui, so one monorepo
can dev an app and pack a library (rfcs/cwd-flag.md).
"""
steps = [
{ argv = ["vp", "build"], cwd = "per_command" },
{ argv = ["vp", "pack"], cwd = "per_command" },
]

[[case]]
name = "default_package_per_command_fallthrough"
vp = ["local", "global"]
comment = """
A command absent from the defaultPackage object falls through to the
normal resolution: the map only declares `pack`, so bare `vp build` at
this workspace root runs in place at the (runnable) root with no note,
while `vp pack` still routes to the declared ./packages/ui.
"""
steps = [
{ argv = ["vp", "build"], cwd = "per_command_fallthrough" },
{ argv = ["vp", "pack"], cwd = "per_command_fallthrough" },
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# default_package_per_command

The object form maps commands individually at a workspace root: `vp build`
targets ./apps/web while `vp pack` targets ./packages/ui, so one monorepo
can dev an app and pack a library (rfcs/cwd-flag.md).

## `cd per_command && vp build`

```
VITE+ - The Unified Toolchain for the Web

note: vp build: using ./apps/web (defaultPackage in vite.config.ts)
✓ 2 modules transformed.
computing gzip size...
dist/index.html <size> kB │ gzip: <size> kB

✓ built in <duration>
```

## `cd per_command && vp pack`

```
VITE+ - The Unified Toolchain for the Web

note: vp pack: using ./packages/ui (defaultPackage in vite.config.ts)
ℹ entry: src/index.ts
ℹ Build start
ℹ dist/index.mjs <size> kB │ gzip: <size> kB
ℹ 1 files, total: <size> kB
✔ Build complete in <duration>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# default_package_per_command

The object form maps commands individually at a workspace root: `vp build`
targets ./apps/web while `vp pack` targets ./packages/ui, so one monorepo
can dev an app and pack a library (rfcs/cwd-flag.md).

## `cd per_command && vp build`

```
note: vp build: using ./apps/web (defaultPackage in vite.config.ts)
✓ 2 modules transformed.
computing gzip size...
dist/index.html <size> kB │ gzip: <size> kB

✓ built in <duration>
```

## `cd per_command && vp pack`

```
note: vp pack: using ./packages/ui (defaultPackage in vite.config.ts)
ℹ entry: src/index.ts
ℹ Build start
ℹ dist/index.mjs <size> kB │ gzip: <size> kB
ℹ 1 files, total: <size> kB
✔ Build complete in <duration>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# default_package_per_command_fallthrough

A command absent from the defaultPackage object falls through to the
normal resolution: the map only declares `pack`, so bare `vp build` at
this workspace root runs in place at the (runnable) root with no note,
while `vp pack` still routes to the declared ./packages/ui.

## `cd per_command_fallthrough && vp build`

```
VITE+ - The Unified Toolchain for the Web

✓ 2 modules transformed.
computing gzip size...
dist/index.html <size> kB │ gzip: <size> kB

✓ built in <duration>
```

## `cd per_command_fallthrough && vp pack`

```
VITE+ - The Unified Toolchain for the Web

note: vp pack: using ./packages/ui (defaultPackage in vite.config.ts)
ℹ entry: src/index.ts
ℹ Build start
ℹ dist/index.mjs <size> kB │ gzip: <size> kB
ℹ 1 files, total: <size> kB
✔ Build complete in <duration>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# default_package_per_command_fallthrough

A command absent from the defaultPackage object falls through to the
normal resolution: the map only declares `pack`, so bare `vp build` at
this workspace root runs in place at the (runnable) root with no note,
while `vp pack` still routes to the declared ./packages/ui.

## `cd per_command_fallthrough && vp build`

```
✓ 2 modules transformed.
computing gzip size...
dist/index.html <size> kB │ gzip: <size> kB

✓ built in <duration>
```

## `cd per_command_fallthrough && vp pack`

```
note: vp pack: using ./packages/ui (defaultPackage in vite.config.ts)
ℹ entry: src/index.ts
ℹ Build start
ℹ dist/index.mjs <size> kB │ gzip: <size> kB
ℹ 1 files, total: <size> kB
✔ Build complete in <duration>
```
2 changes: 1 addition & 1 deletion docs/config/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ export default {
};
```

vp reads the value without executing the config, so `defaultPackage` also works at repository roots with no vite-plus dependency (for example a Laravel or Rails repo whose Vite app lives in `frontend/`, with vite-plus installed only there). That static read is why the value must stay a plain string literal rather than an expression. An explicit `-C` or positional target always wins over the config.
vp reads the values without executing the config, so `defaultPackage` also works at repository roots with no vite-plus dependency (for example a Laravel or Rails repo whose Vite app lives in `frontend/`, with vite-plus installed only there). That static read is why the values must stay plain string literals rather than expressions. An explicit `-C` or positional target always wins over the config.
8 changes: 8 additions & 0 deletions docs/guide/monorepo.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,14 @@ note: vp dev: using ./apps/web (defaultPackage in vite.config.ts)

This is the right choice for framework monorepos that are not JavaScript workspaces, such as a Laravel or Rails app with a `frontend/` directory: there is no package list to resolve, so `defaultPackage` points vp straight at the app. Because vp reads it without executing the config, it works even when `vite-plus` is installed only inside that subdirectory.

An object form maps commands individually, so `vp pack` can target a library while `vp dev` targets an app; a command absent from the object falls through to the resolution above:

```ts [vite.config.ts]
export default {
defaultPackage: { dev: './apps/web', pack: './packages/ui' },
};
```

### Package scripts and workspace-wide tasks

Keep package-specific scripts in each package when the command differs per app:
Expand Down
24 changes: 22 additions & 2 deletions packages/cli/binding/src/cli/app_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,24 @@ enum Elicitation {
WorkspaceRoot(vite_workspace::WorkspaceRoot),
}

/// Applies a `defaultPackage` declaration to one command. A string covers
/// all four app commands; an object maps commands individually
/// (`{ dev: './apps/web', pack: './packages/ui' }`), and a command absent
/// from the object falls through to the picker/listing resolution. Every
/// other shape (a non-string, a non-static value) passes through for
/// [`resolve_default_package`] to report.
fn default_package_for_command(
command: &str,
value: vite_static_config::FieldValue,
) -> Option<vite_static_config::FieldValue> {
match value {
vite_static_config::FieldValue::Json(serde_json::Value::Object(map)) => {
map.get(command).cloned().map(vite_static_config::FieldValue::Json)
}
other => Some(other),
}
}

/// The RFC's resolution order, written once for both entry points: bare app
/// command, then `defaultPackage` at the invocation root, then the workspace
/// root itself. `defaultPackage` is a root-pointer concept: it applies where
Expand All @@ -356,8 +374,10 @@ fn classify(subcommand: &SynthesizableSubcommand, cwd: &AbsolutePath) -> Classif
// Resolved once and reused by `root_looks_runnable` below, so a bare
// command at a root reads and parses the config a single time.
let root_config = at_invocation_root.then(|| vite_static_config::resolve_static_config(cwd));
if let Some(value) =
root_config.as_ref().and_then(|config| config.get_declared("defaultPackage"))
if let Some(value) = root_config
.as_ref()
.and_then(|config| config.get_declared("defaultPackage"))
.and_then(|value| default_package_for_command(command, value))
{
return Classification::Elicit(command, Elicitation::DefaultPackage(value));
}
Expand Down
12 changes: 7 additions & 5 deletions packages/cli/src/define-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ declare module '@voidzero-dev/vite-plus-core' {
/**
* Default target directory for `vp dev` / `build` / `preview` / `pack`
* when invoked bare in the directory containing this config (an implicit
* `vp -C <dir>`). Relative to the config file's directory. vp reads the
* value without executing the config, so it also works at roots with no
* vite-plus dependency; the static read is why it must stay a plain
* string literal.
* `vp -C <dir>`). A string targets all four commands; an object maps
* commands individually, and a command absent from the object falls
* through to the normal picker/listing resolution. Paths are relative to
* the config file's directory. vp reads them without executing the
* config, so this also works at roots with no vite-plus dependency; the
* static read is why the values must stay plain string literals.
*/
defaultPackage?: string;
defaultPackage?: string | { dev?: string; build?: string; preview?: string; pack?: string };

run?: RunConfig;

Expand Down
2 changes: 1 addition & 1 deletion rfcs/cwd-flag.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ export default defineConfig({
});
```

- Type: `string`, a single directory. A per-command map can come later if real demand appears.
- Type: `string` (one directory for all four commands) or a per-command object (`{ dev: './apps/web', pack: './packages/ui' }`, added on review demand). A command absent from the object falls through to the picker/listing resolution.
- Consulted when a bare app command runs in the directory containing the root config: a workspace root, or a non-workspace repo root. The non-workspace shape has no package list, so `defaultPackage` is the only mechanism that covers it. An explicit `-C` always wins.
- A missing directory errors: `defaultPackage points to a missing directory: ./frontend`.
- Read via static extraction (`vite_static_config` + the loader in `packages/cli/binding/src/cli/handler.rs`), like `run` config. At a non-workspace root there is no install to execute the config, so the file must work unexecuted: a plain default-export object with a static string value.
Expand Down
Loading