Repository navigation
Packaging Layouts
Selldone loads a storefront Layout from fixed file names inside the uploaded ZIP. This page describes that contract,
how to configure your bundler to meet it and how to validate a build before you deploy. The rules come from the
package-selldone-storefront-layout
skill bundled in this repository.
Minimum build tree:
dist/
└── app/
├── shop.js
└── shop.css
Minimum ZIP tree (the CLI archives the contents of dist/):
app/
app/shop.js
app/shop.css
| Rule | Why |
|---|---|
app/shop.js exists, is not empty, is an ES module and is not hashed
|
The storefront runtime loads it with <script type="module" crossorigin>. |
app/shop.css exists (it may be empty) |
The live storefront always requests it. |
The ZIP has an explicit app/ directory entry |
The extractor checks both app/ and app/shop.js as separate entries. |
No wrapper folder above app/ (dist/, project name, package or version) |
The extractor does not strip a leading folder. |
Lowercase names and / separators |
Entry names are compared exactly and case-sensitively. |
| ZIP size at most 50 MiB | Upload limit (51,200 KiB); the file must also pass malware scanning. |
The app mounts into the existing <div id="app"></div>
|
Selldone supplies the production HTML shell; a generated index.html is not used. |
| A static client bundle | A server-only SSR bundle or Node runtime is not a deployable Layout. |
Hashed lazy chunks, images, fonts, media and secondary CSS are fine, as long as their references resolve.
Given "package": "my-custom-storefront" and "version": "1.0.0":
ZIP entry: app/shop.js
stored path: my-custom-storefront/1.0.0/app/shop.js
runtime URL: https://layouts.selldone.com/my-custom-storefront/1.0.0/app/shop.js
This is why neither the package nor the version belongs inside dist/ or the ZIP. Do not use a
<version>/shop.js output pattern.
| Output | Required pattern |
|---|---|
| Build root | dist/ |
| Main JavaScript entry | app/shop.js |
| Primary CSS | app/shop.css |
| Lazy JavaScript chunks | for example app/js/[name].[hash].js
|
| Secondary CSS | for example app/css/[name].[hash].css
|
| Other assets | for example app/assets/[name].[hash][extname]
|
Prefer a relative public base (./). If your tool requires an absolute base, use
https://layouts.selldone.com/<package>/<version>/ with a trailing slash, and do not add app/ to it. Avoid
root-relative URLs such as /assets/..., which resolve against the merchant's storefront domain.
import { defineConfig } from "vite";
import { resolve } from "node:path";
export default defineConfig({
base: "./",
build: {
outDir: "dist",
emptyOutDir: true,
cssCodeSplit: false,
rollupOptions: {
input: {
shop: resolve(__dirname, "index.html"),
},
output: {
entryFileNames: (chunk) =>
chunk.name === "shop"
? "app/shop.js"
: "app/js/[name].[hash].js",
chunkFileNames: "app/js/[name].[hash].js",
assetFileNames: (asset) => {
const sourceName = asset.names?.[0] ?? asset.name ?? "";
return sourceName.endsWith(".css")
? "app/shop.css"
: "app/assets/[name].[hash][extname]";
},
},
},
},
});Keep cssCodeSplit: false so all CSS goes to the single shop.css. Define the script the CLI runs:
{
"scripts": {
"build-production": "vite build"
}
}For Rollup, Webpack, esbuild, Parcel or framework wrappers, set the equivalent output directory, entry file name, chunk file name, CSS file name and public path. More recipes: bundler-recipes.md.
The CLI does not inspect artifact paths before uploading, and a successful upload response does not prove the asynchronous extraction succeeded. Validate locally with the bundled scripts (Python 3.8+, standard library only):
# Validate the build folder
python .agents/skills/package-selldone-storefront-layout/scripts/validate_layout_package.py dist --manifest manifest.json
# Validate any ZIP, including one made by another tool
python .agents/skills/package-selldone-storefront-layout/scripts/validate_layout_package.py dist.zip --manifest manifest.json
# Build a ZIP with an explicit app/ entry when you need the archive only (add --force to overwrite)
python .agents/skills/package-selldone-storefront-layout/scripts/package_layout.py dist dist.zip --manifest manifest.jsonThe validator rejects unsafe entries (absolute paths, .., backslashes, drive prefixes, encrypted entries, symlinks
and duplicates) and reports missing entry files, wrapper folders and archives over 50 MiB.
dist/app/shop.js # dist wrapper included
my-project/app/shop.js # project wrapper included
my-custom-storefront/1.0.0/app/... # package/version wrappers included
1.0.0/shop.js # version used instead of app
shop.js # entry at ZIP root
assets/shop.js # wrong entry directory
app/shop.a1b2c3.js # hashed main entry
App/shop.js # wrong case
app\shop.js # wrong ZIP separator/name
- List the ZIP entries and compare exact names.
- Remove any wrapper above
app/. - Rename the main bundle to
app/shop.jsand keep it unhashed. - Make sure
app/exists as a directory entry. - Add
app/shop.cssor fix the CSS output name. - Check generated imports and URLs for missing assets or a wrong public base.
- Rebuild, repackage, rerun the validator and deploy with a new version.
AI coding agents should read
SKILL.md
and the full
artifact contract
before changing a build configuration or creating an archive.