QUESTPIE
CodeModules

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.

View markdown

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 above

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

packages/blog/questpie.config.ts
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
});
OptionWhat it does
modulesDirThe directory to scan. Every subdirectory becomes one module.
modulePrefixJoined to the directory name with a hyphen. Defaults to questpie.
pluginsCodegen plugins applied while generating every module here.

Subdirectories starting with a dot are skipped. The rest are generated in name order.

Generate

questpie generate

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

src/server/modules/blog/.generated/module.ts (abridged)
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.

packages/blog/src/exports/modules/blog.ts
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.

On this page