Skip to content

Packaging Layouts

Mehrdad Pajuhaan edited this page Oct 23, 2026 · 1 revision

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.

The contract

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.

Where your files end up

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.

Configure the bundler

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.

Vite example

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.

Validate before you deploy

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.json

The 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.

Shapes that fail

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

Troubleshooting order

  1. List the ZIP entries and compare exact names.
  2. Remove any wrapper above app/.
  3. Rename the main bundle to app/shop.js and keep it unhashed.
  4. Make sure app/ exists as a directory entry.
  5. Add app/shop.css or fix the CSS output name.
  6. Check generated imports and URLs for missing assets or a wrong public base.
  7. Rebuild, repackage, rerun the validator and deploy with a new version.

For coding agents

AI coding agents should read SKILL.md and the full artifact contract before changing a build configuration or creating an archive.

Next steps