Publishing
Ship a module on npm the way QUESTPIE ships its own. You describe the package once with packageConfig, and codegen writes the static module object from the same file convention an app uses.
You have a feature that works, and you want it in the next project too. This page is the directory layout, the one config file it needs, and what you export at the end.
Lay out the package
One directory per module, under a directory you choose.
packages/blog/
questpie.config.ts # the package config, below
src/server/plugin.ts # your CodegenPlugin factory, optional
src/server/modules/
blog/
collections/posts.ts # same convention as an app
jobs/digest.ts
config/blog.ts # needs a discover entry, see below
fields.ts # your field factories
modules.ts # this module's own dependencies
plugin.ts # re-exports the factory aboveInside a module directory the app conventions apply. collections/ holds
collections, routes/ holds routes, jobs/ holds jobs.
Three files mean something extra here. modules.ts default-exports this
module's dependencies. fields.ts default-exports your field factories.
plugin.ts default-exports a CodegenPlugin, and codegen puts it on the
module's plugin key.
Two things need that plugin. config/blog.ts becomes config.blog only when
the plugin declares it in discover. Out of the box codegen finds
config/app.ts and config/auth.ts and nothing else. Your field factories
reach a consumer's f only through the plugin. It has to list them under
factoryImports on the fieldTypes category.
Describe the package
Add a questpie.config.ts at the package root. It exports packageConfig()
instead of runtimeConfig(), and that is what puts codegen in package mode.
import { packageConfig } from "questpie/cli";
import { blogPlugin } from "./src/server/plugin.js";
export default packageConfig({
modulesDir: "src/server/modules", // relative to this file
modulePrefix: "acme", // dir "blog" becomes module "acme-blog"
plugins: [blogPlugin()], // shared by every module in the package
});| Option | What it does |
|---|---|
modulesDir | The directory to scan. Every subdirectory becomes one module. |
modulePrefix | Joined to the directory name with a hyphen. Defaults to questpie. |
plugins | Codegen plugins applied while generating every module here. |
Subdirectories starting with a dot are skipped. The rest are generated in name order.
Generate
questpie generateFor each subdirectory codegen writes .generated/module.ts, a plain object
holding everything it found. It writes .generated/registries.ts beside it when
the module augments a global registry. A fields.ts does that.
import _modules from "../modules";
import _coll_posts from "../collections/posts";
import _blogConfig from "../config/blog";
const _module = {
name: "acme-blog" as const,
modules: _modules,
collections: { posts: _coll_posts },
config: { blog: _blogConfig },
// every other category, empty
};
export default _module;Commit that file. It is the module, and it is what your users import.
Export it
Give the generated object a named export from your package.
export { default as blogModule } from "../../server/modules/blog/.generated/module.js";Someone installs @acme/blog and adds blogModule to their modules.ts. They
run questpie generate. Every collection, job and config key you wrote is now
in their app, typed.
`packageConfig` is for building, not consuming
It is dev tooling for your repo. It never ships, and nobody who installs your package writes one. Consuming a module is an import and one array entry.
Two generated modules from one directory
A codegen plugin can declare a target with its own moduleRoot. Codegen then
generates a second module from that subdirectory of each module directory.
That is how @questpie/admin ships two modules. The second is the client module
you list in the admin app's own modules.ts. You need this only when your
package ships UI. See Building a plugin.
Merging
Every module you list is flattened into one ordered list, then folded left to right into a single app. This is that order, and the rule each key follows when two modules touch it.
Discovery
The rules that turn a file in a convention directory into an entry on the app object. What becomes the key, which exports count, what codegen ignores, and when it stops with an error.