Skip to content

feat(npm): ship TypeScript types, so a ferrflow.ts config is actually typed #1020

Description

@BryanFRD

ferrflow.ts and ferrflow.js are supported config formats, and there is not a single .d.ts anywhere in this repo. Someone writing a config in TypeScript gets no completion, no checking, and no way to discover a field short of reading the docs. The ferrflow npm package declares only bin, no types.

Two shapes are missing, and only one of them can be generated

The config object. schema/ferrflow.json is the source of truth and already describes it, so this half can be generated rather than hand-written and kept in sync by CI, the way the schema-parity job already does between here and FerrFlow-Cloud.

The hook context, which the schema cannot express. In a JSON config, hooks are shell strings. In ferrflow.ts they are functions, and src/config/loader_js.rs:34 builds the object they receive:

package, oldVersion, newVersion, bumpType, tag, packagePath, changelog
dryRun, isPrerelease, monorepo   (booleans)
channel, releaseUrl              (nullable)
commits, bumpedFiles, allPackages (arrays)

Fifteen fields that exist only as a template string in the Rust loader. The docs show ctx.newVersion, ctx.package and ctx.commits in examples; the other twelve are undiscoverable. That is the part a user actually stumbles on, and it is also the part most likely to drift, since nothing today ties that string to anything.

The loader also accepts a function as the default export: typeof m.default === 'function' ? await m.default() : m.default. So the exported type has to allow an object, a function returning one, and an async function returning one.

Where to put it

Either a new @ferrflow/types, or types on the existing ferrflow package. I lean to the second: anyone writing ferrflow.ts has the CLI installed already, import type { FerrflowConfig } from 'ferrflow' needs no second dependency, and it is one less package to publish and keep versioned. A separate package earns its place only if the types are wanted without the binary, which is not obviously a case here.

Worth adding a defineConfig() helper either way, since that is what people expect from a TS config file and it gives inference without an explicit annotation.

Keeping it honest

Generating from the schema is only half the job, and the generated half is the half that cannot rot silently. The hook context needs a test that fails when loader_js.rs and the type disagree, otherwise this becomes another pair of things that drift, like the schema and the served copy did in FerrLabs/FerrFlow-Cloud#912.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions