Skip to content

Package & Publish

The package lifecycle method bundles your lexicon’s generated artifacts into a distributable format. Core provides packagePipeline to assemble the bundle and writeBundleSpec to put it on disk.

packagePipeline returns a PackageResult in memory. It writes nothing. writeBundleSpec(spec, distDir) is the step that creates dist/, and forgetting it is why chant dev check-lexicon reports dist/manifest.json exists as failing.

src/codegen/package.ts
import { dirname } from "path";
import { fileURLToPath } from "url";
import { packagePipeline, collectSkills } from "@intentius/chant/codegen/package";
import type { PackageOptions, PackageResult } from "@intentius/chant/codegen/package";
import { myPlugin } from "../plugin";
import { generate } from "./generate";
// Lexicon packages are ESM, so `__dirname` does not exist.
const pkgDir = dirname(dirname(fileURLToPath(import.meta.url)));
export async function packageLexicon(opts: PackageOptions = {}): Promise<PackageResult> {
return packagePipeline(
{
generate: (genOpts) => generate(genOpts),
buildManifest: (genResult) => ({
name: "my-lexicon",
version: "1.0.0",
chantVersion: ">=0.1.0",
}),
srcDir: pkgDir,
collectSkills: () => collectSkills(myPlugin.skills?.() ?? []),
},
opts,
);
}
// src/package-cli.ts, run by `npm run bundle`
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { writeBundleSpec } from "@intentius/chant/codegen/package";
import { packageLexicon } from "./codegen/package";
const pkgDir = dirname(fileURLToPath(import.meta.url));
const { spec, stats } = await packageLexicon({ verbose: true });
writeBundleSpec(spec, join(dirname(pkgDir), "dist"));
console.error(`Packaged ${stats.resources} entities, ${stats.ruleCount} rules`);

See lexicons/aws/src/codegen/package.ts and lexicons/k3s/src/package-cli.ts for the shipped pair.

FieldRequiredWhat it does
generateYesRuns your generation pipeline and returns the GenerateResult
buildManifestYesBuilds the LexiconManifest from that result
srcDirYesPackage root, scanned for rule files
collectSkillsYesReturns Map<filename, content>. collectSkills(defs) maps SkillDefinition[] to <name>.md
ruleDirsNoRule directories relative to srcDir. Defaults to ["lint/rules", "lint/post-synth"]
versionNoRecorded as metadata.generatorVersion. Defaults to "0.0.0"

writeBundleSpec creates the dist/ directory:

ArtifactDescription
manifest.jsonLexicon metadata (name, version, intrinsics, pseudo-parameters)
meta.jsonRecord<shortName, LexiconEntry> resource registry
integrity.jsonPer-artifact sha256 hashes plus a composite hash over the sorted path:hash pairs
types/index.d.tsTypeScript declarations for all resource and property types
rules/*.tsLint rule implementations
skills/*.mdAI assistant skill definitions
okf/**OKF knowledge bundle derived from the same registry and rules (chant #1060), replaced wholesale on each write

The integrity record covers manifest.json, meta.json, types/index.d.ts and every file under rules/ and skills/. okf/ is not hashed.

The buildManifest callback must return a LexiconManifest:

FieldTypeRequiredDescription
namestringYesLexicon identifier (e.g. "aws", "k8s")
versionstringYesLexicon version (e.g. "1.0.0")
chantVersionstringNoMinimum chant version required
namespacestringNoType namespace prefix (e.g. "AWS")
intrinsicsIntrinsicDef[]NoIntrinsic function definitions
pseudoParametersRecord<string, string>NoPseudo-parameter names and descriptions

chantVersion is optional to the type and required in practice: chant dev check-lexicon fails tier 1 when the packaged manifest omits it. It is checked for presence and shape only, never for compatibility with the running core.

Core provides docsPipeline and writeDocsSite for generating a standalone Starlight docs site from your lexicon’s dist/ artifacts:

import { docsPipeline, writeDocsSite, type DocsConfig } from "@intentius/chant/codegen/docs";
const config: DocsConfig = {
name: "my-lexicon",
displayName: "My Lexicon",
description: "Description for page metadata",
distDir: "./dist",
outDir: "./docs",
serviceFromType: (type) => type.split("::")[1] ?? "Other",
};
const result = docsPipeline(config);
writeDocsSite(config, result);

This creates a self-contained Starlight site with pages for resources, intrinsics, pseudo-parameters, rules, and serialization. Build it with cd docs && npm install && npm run build.

Registering docs() on the plugin is a tier-1 requirement, so wire the method as well as the "docs" script in package.json. The full field list for DocsConfig, including the authored pagesDir and the Diátaxis quadrants the sidebar is grouped from, is in Docs Site.

Once the bundle is built:

  1. Run chant dev check-lexicon <dir> and confirm tier 1 is clean
  2. Set files to ["src/", "dist/"]. Consumers import TypeScript source through exports["."].default, so shipping only dist/ breaks them
  3. Route exports["."].default at ./src/index.ts, and have the build script delete emitted .js from dist/. Both are tier-1 checks
  4. Chain the bundle step into prepack, so dist/ is rebuilt from the pinned spec at publish time rather than from whatever is on disk
  5. Publish with npm pack (for testing) or npm publish

lexicons/k3s/package.json is the shape to copy, including its "prepack": "npm run generate && npm run bundle && npm run validate && npm run build".

Users install your lexicon with:

Terminal window
npm install --save-dev @intentius/chant-lexicon-<name>

Once the bundle is ready, run chant dev onboard to wire the lexicon into CI, Docker smoke tests, and the npm publish workflow. See CI & Distribution for the full checklist.