these docs are for production builds, for development see dev.md
The gro build task produces outputs for production:
gro buildThis runs the configured Gro plugins, setup -> adapt -> teardown, in production mode.
If your project has a SvelteKit frontend,
the default plugin calls vite build,
forwarding any -- vite [...] args:
gro build -- vite --config my-config.jsBy default, gro build runs gro sync before building
to generate code and update package.json exports,
and installs packages if needed:
gro build # runs sync + install + build
gro build --no-sync # skip sync step
gro build --no-install # skip package installNote: --no-sync is ignored if install runs (install requires sync to generate exports).
Gro caches builds to skip expensive rebuilds when nothing has changed.
The cache key has two components:
- git commit hash - tracks code, dependencies, and config files
build_cache_confighash - optional, for tracking external inputs like environment variables or feature flags (see cache configuration)
The cache invalidates when either component changes.
When the cache is valid:
gro build
# Build cache valid (from 2025-10-21T10:30:00.000Z) (use --force_build to rebuild)
# Skipping build, cache is validForce a rebuild:
gro build --force_buildThe build cache requires a clean git workspace to function correctly. This section covers workspace behavior, best practices, and CI/CD integration.
The build cache only works with a clean git workspace. With uncommitted changes, the git commit hash doesn't reflect your actual code state, so Gro disables caching to ensure builds always match your working directory.
This conservative approach prevents a subtle issue: uncommitted changes could be reverted after building, leaving cached outputs from code that no longer exists in your workspace.
Behavior with uncommitted changes:
- cache checking is skipped - builds always run
- cache is not saved - no
.gro/build.jsonwritten - all build outputs are deleted before building - cache file,
build/,dist/, anddist_*/removed to prevent stale state - you'll see:
workspace has uncommitted changes - skipping build cache
Outputs are deleted directly before running plugins.
For reliable caching:
- commit before building for production - cache requires a clean workspace
- use
build_cache_configfor external inputs - environment variables, remote configs, or feature flags that affect the build but aren't in git - use
gro check --workspacein CI - enforces clean git state before building - avoid concurrent builds - multiple simultaneous
gro buildprocesses can conflict
During development, uncommitted changes automatically disable caching, so builds always reflect your working directory.
After plugins finish building, Gro verifies the workspace didn't change during the build. If tracked files were modified or untracked files were created, the build fails:
Error: Build process modified tracked files or created untracked files.
Git status after build:
Modified: src/lib/foo.ts
Builds should only write to output directories (build/, dist/, etc.).
This usually indicates a plugin or build step is incorrectly modifying source files.
This ensures build reproducibility and prevents plugins from corrupting source code.
Don't commit
.gro/build.json- keep it in.gitignore.
Caching .gro/build.json between CI runs is not beneficial - each commit has a unique cache key.
Basic CI workflow:
gro check --workspace # ensure clean workspace
gro build # build (uses cache if valid)Cache not working as expected?
- workspace must be clean - check
git status - enable debug logging -
LOG_LEVEL=debug gro build - corrupted cache - delete
.gro/build.jsonand rebuild - platform differences - add platform/arch to
build_cache_config - non-deterministic builds - builds with timestamps/UUIDs/random data invalidate cache every run
- diagnostic: if cache never hits, run
gro buildtwice and compare outputs:gro build && cp -r build build_1 gro build && diff -r build build_1
- if files differ, your build is non-deterministic
- common causes:
new Date(),Math.random(),process.pidin build-time code - solution: use
import.meta.env.MODEchecks or build-time constants
- diagnostic: if cache never hits, run
⚠️ the cache is conservative - when in doubt, it rebuilds
For implementation details, see cache implementation.
For advanced use cases, you can customize cache behavior or extend it for plugins.
If your build depends on external factors (environment variables, data files, feature flags),
use build_cache_config in gro.config.ts to invalidate the cache when they change:
// gro.config.ts
export default {
build_cache_config: {
api_endpoint: process.env.PUBLIC_API_URL,
data_version: fs.readFileSync('data/version.txt', 'utf-8'),
features: {analytics: true, beta_ui: false},
},
} satisfies GroConfig;The config is hashed and never logged to protect sensitive values. Any change triggers a rebuild.
See config.md for more details on build_cache_config.
To influence the cache, plugins should contribute configuration
in gro.config.ts before config normalization.
Since build_cache_config is hashed during config load, plugins running later cannot modify it.
Instead, include any plugin-specific cache factors in your gro.config.ts:
// gro.config.ts
export default {
build_cache_config: {
my_plugin_version: '1.0.0', // plugin version affects cache
other_factor: process.env.MY_BUILD_FACTOR,
},
};See build.task.ts for integration with the build pipeline.
implementation details for advanced debugging
The build cache (src/lib/build_cache.ts) validates builds are current
by tracking git commits and optional user config.
flowchart TD
Start([gro build]) --> Force{--force_build?}
Force -->|yes| Rebuild[skip cache, rebuild]
Force -->|no| Dirty{workspace clean?}
Dirty -->|no| Clean[Delete cache & outputs]
Clean --> Rebuild
Dirty -->|yes| Load{cache exists?}
Load -->|no| Rebuild
Load -->|yes| Keys{git commit +<br/>config match?}
Keys -->|no| Rebuild
Keys -->|yes| Valid{files valid?}
Valid -->|no| Rebuild
Valid -->|yes| Skip[use cached build]
Rebuild --> Build[run build]
Build --> Verify{workspace<br/>status changed?}
Verify -->|yes| ThrowError[throw error]
Verify -->|no| Save{workspace<br/>still clean?}
Save -->|yes| Commit{git commit<br/>changed?}
Save -->|no| SkipSave[skip cache save]
Commit -->|yes| WarnSkip[warn, skip save]
Commit -->|no| SaveCache[save cache]
style Skip fill:#90EE90
style Rebuild fill:#FFB6C1
Cache metadata is stored at .gro/build.json.
This location is independent of build outputs, so the cache survives manual deletion of build/.
Running gro clean deletes the cache along with the .gro/ directory.
The .gro/build.json file stores:
{
"version": "1", // schema version
"git_commit": "abc123...", // commit at build time
"build_cache_config_hash": "...", // SHA-256 of user config
"timestamp": "ISO-8601", // build completion time
"outputs": [
// all output files with hashes and stats
{
"path": "build/index.html",
"hash": "...", // SHA-256 of file contents
"size": 1024, // file size in bytes
"mtime": 1729512000000, // modification time (ms since epoch)
"ctime": 1729512000000, // change/creation time (ms since epoch)
"mode": 33188 // Unix file permissions (0644)
},
{
"path": "dist/index.js",
"hash": "...",
"size": 2048,
"mtime": 1729512001000,
"ctime": 1729512001000,
"mode": 33188 // stored but not validated
}
]
}The cache validates builds through several checks:
- load
.gro/build.json- missing/corrupt = rebuild - check version = "1" - mismatch = rebuild
- compare git commit - different = rebuild
- compare config hash - different = rebuild
- verify files exist - missing = rebuild
- check file sizes - mismatch = rebuild (fast path optimization)
- hash files in parallel - mismatch = rebuild
- all pass = use cache
The hashing is usually fast but adds overhead proportional to output size.
Output discovery:
The cache automatically discovers all build output directories:
build/- SvelteKit app outputdist/- SvelteKit library outputdist_*- plugin outputs (e.g.,dist_server)
Within these directories:
- regular files - hashed with SHA-256 and validated on cache checks
- symlinks - skipped (not hashed or tracked)
- directories - recursively scanned for regular files
This ensures cache validation is comprehensive while avoiding issues with symlink handling across platforms.
The cache uses SHA-256 cryptographic hashing for:
- output file integrity - detects tampering or corruption
- config protection -
build_cache_configis hashed, never logged raw (may contain secrets) - cache key stability - deterministic hashes ensure consistent cache behavior
Hashes are computed via Node's webcrypto.subtle.digest().
Plugins are objects that customize the behavior of gro build and gro dev.
They try to defer to underlying tools as much as possible, and exist to glue everything together.
For example, the library plugin internally uses
svelte-package.
See gro_plugin_sveltekit_library.md to learn more.
Now that we can produce builds, how do we share them with the world?
The gro deploy task outputs builds to a branch,
like for static publishing to GitHub pages.
The gro publish task publishes packages to npm.
Both of these tasks call gro build internally,
and you can always run it manually if you're curious.