Skip to content
atarashi
Documentation menu

Plugin API reference

@atarashi/plugin-kit is the only Atarashi package a blueprint author needs. It carries the types you write against, a manifest builder, the hook types, the conformance suite, and a test harness.

npm install --save-dev @atarashi/plugin-kit

Everything here is ESM and typed. Nothing in it is loaded by a generated project at runtime; it is a build- and test-time dependency of the blueprint, not of the project the blueprint produces.

Defining a manifest in TypeScript

blueprint.json is the shipped artefact, but you can author it in TypeScript and emit the JSON, which gets you completion and a validation error at build time rather than a diagnostic at generation time.

import { defineBlueprint, toManifestJson } from '@atarashi/plugin-kit';

export const redis = defineBlueprint({
    id: 'acme/redis',
    name: 'Redis',
    version: '1.0.0',
    description: 'A shared Redis client with connection handling.',
    category: 'cache',
    provides: ['cache', 'cache:redis'],
    requires: ['runtime:node', 'config:env'],
    conflicts: ['cache'],
    files: [{ from: 'files/client.ts.hbs', to: 'src/lib/redis.ts' }],
    dependencies: { ioredis: '^5.4.1' },
});

writeFileSync('blueprint.json', toManifestJson(redis));
ExportSignatureNotes
defineBlueprint(definition) => BlueprintManifestValidates and returns the fully defaulted manifest. Throws on an invalid definition: an author's bug at build time, not a runtime condition. Fills in manifestVersion and kind.
definePreset(definition) => PresetManifestThe same for a preset.
toManifestJson(manifest) => stringStable, formatted JSON, ready to write.

Types

Re-exported from @atarashi/schema, so a blueprint package never depends on it directly:

BlueprintManifest, PresetManifest, ProjectSpec, GenerationPlan, PlannedFile, FileEntry, Contribution, DependencySpec, EnvEntry, MergeStrategy, Prompt, PromptType.

Hooks

import { defineHooks } from '@atarashi/plugin-kit';

export default defineHooks({
    beforeRender(subject, context) { … },
    afterRender(subject, context) { … },
    afterPlan(subject, context) { … },
});

A hook either mutates the subject in place and returns nothing, or returns a replacement. Both are accepted; mutation is the common case.

HookSubjectWhen it runsUse it to
beforeRenderBeforeRenderSubjectAfter answers are collected, before any template rendersDerive or default an answer
afterRenderAfterRenderSubjectOn rendered files, before merges are assembledRewrite generated content
afterPlanAfterPlanSubjectOn the assembled planAdd warnings and next steps

context is a HookContextView: the render context, read-only. It is passed only when the manifest declares the read-context capability; otherwise it is undefined.

The sandbox accepts the hooks as individual named exports, under a hooks export, or as the default export.

Capabilities and limits

CapabilityGrants
read-contextThe context argument
read-filesReading files in the plan
write-filesAdding to or modifying files in the plan

Anything not declared is denied. Hooks run in a worker thread with no network, and are killed past timeoutMs (default 5 s, maximum 30 s). Hooks from an untrusted source (anything loaded from npm or a third-party registry) require explicit consent before they run, and --no-hooks disables them entirely.

Validating

import { validateBlueprint } from '@atarashi/plugin-kit';

const report = await validateBlueprint('./acme/redis', {
    knownPromptNames: ['db.name'],
    knownEnvKeys: ['DATABASE_URL'],
    versionManifest,
});

if (!report.ok) {
    for (const problem of report.problems) {
        console.error(`${problem.severity}: [${problem.check}] ${problem.message}`);
    }
}

ConformanceReport is { id, dir, ok, problems }, where each problem is { check, severity, message, path? }. ok is false when any problem is an error; warnings do not fail it.

The options exist because a blueprint validated on its own cannot know what it will compose with. Pass what its siblings declare and references to their prompts and env keys stop being reported. Omit them and those become warnings rather than errors. versionManifest is only needed if your dependencies take their ranges from the registry rather than pinning their own.

atarashi create-blueprint <id> --validate <dir> is this function behind a CLI.

Testing

The harness generates a real plan from real blueprints, in memory, with no network and no writes.

import { Catalogue, expectPlan, fileAt, hasFile, makeSpec } from '@atarashi/plugin-kit';

const catalogue = new Catalogue('./');

it('writes a client that reads REDIS_URL', async () => {
    const plan = await expectPlan(catalogue, makeSpec(['core/node-ts', 'acme/redis']));
    expect(hasFile(plan, 'src/lib/redis.ts')).toBe(true);
    expect(fileAt(plan, 'src/lib/redis.ts')).toContain('env.REDIS_URL');
});
ExportWhat it does
CatalogueA BlueprintSource over a directory of blueprints, exactly as the bundled catalogue is loaded
makeSpecA ProjectSpec from a list of ids, with sane defaults
specForPresetA ProjectSpec from a preset, as --preset would build it
planForPromise<Result<GenerationPlan>>, for asserting on failures
expectPlanThe plan, throwing with the diagnostics attached, for the happy path
fileAt / hasFileRead one generated file's contents / check it exists
dependencyRangeThe resolved range for one dependency in a plan
FIXED_VERSION / FIXED_NOWPinned so snapshots are byte-identical across runs

Because the version and timestamp are pinned, a snapshot test over the whole plan is stable, which is how the first-party catalogue is tested.

it('produces the same output every time', async () => {
    const plan = await expectPlan(catalogue, makeSpec(['core/node-ts', 'acme/redis']));
    expect(plan.files.map((f) => [f.path, f.contents])).toMatchSnapshot();
});

Package layout for publishing

{
  "name": "atarashi-blueprint-acme-redis",
  "version": "1.0.0",
  "atarashi": "./blueprint",
  "files": ["blueprint"],
  "devDependencies": { "@atarashi/plugin-kit": "^1.0.0" }
}

The atarashi field must point inside the package at the directory holding blueprint.json. The directory name itself is free; the id comes from the manifest.

See Authoring blueprints for the full walkthrough and the trust model.

Part of the atarashi documentation, written and maintained by Gautam Suthar.