Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 16 additions & 7 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Versioning configuration.
| `allowFirstBump` | boolean | `false` | Acknowledge applying a bump on a first release with an already-stable manifest. On a first release (no prior tag), `--stable --bump <type>` applies the bump (e.g. 1.0.0 → 2.0.0) rather than graduating, which can silently overshoot the staged first version. By default this is flagged per `mismatchStrategy` (warn, or abort under "error"); set true (or pass --allow-first-bump) to apply the bump silently — legitimate when importing a package with prior external version history. |
| `strictReachable` | boolean | `false` | Only use reachable tags |
| `zeroMajor` | `"spec"` \| `"strict"` | `"spec"` | Pre-1.0 handling of commit-inferred breaking changes. 'spec' (default): bump the 0.x minor (0.24.0 → 0.25.0), per semver §4. 'strict': bump the next major (→ 1.0.0). Inferred path only — explicit overrides (--bump major, bump:major) always graduate to 1.0.0. |
| `npm` | object | — | npm/JavaScript version handling |
| `pub` | object | — | Dart/Flutter pub configuration |

### `version.zeroMajor`
Expand Down Expand Up @@ -127,13 +128,21 @@ ungrouped packages honor targets as-is.
**Workspace pins.** Intra-group `workspace:*` dependencies resolve to the group version within a
run, so a fixed group publishes internally consistent.

### `version.npm`

npm/JavaScript version handling.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `enabled` | boolean | `true` | Version package.json manifests. Detected npm packages are versioned by default; set false to opt out (e.g. npm versioning handled elsewhere). |

### `version.cargo`

Cargo/Rust configuration.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `enabled` | boolean | `true` | Enable Cargo.toml version handling |
| `enabled` | boolean | `true` | Version Cargo.toml manifests. Detected Rust packages are versioned by default; set false to opt out (e.g. a vendored crate, or Rust versioning handled elsewhere). |
| `paths` | `string[]` | — | Directories to search for Cargo.toml files |

---
Expand Down Expand Up @@ -162,7 +171,7 @@ NPM publishing configuration.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `enabled` | boolean | `true` | Enable NPM publishing |
| `enabled` | boolean | `true` | Publish to npm. Detected npm packages are published by default; set false to opt out (version and tag only). |
| `auth` | `"auto"` \| `"oidc"` \| `"token"` | `"auto"` | Authentication method |
| `provenance` | boolean | `true` | Enable npm provenance attestation |
| `access` | `"public"` \| `"restricted"` | `"public"` | Package access level |
Expand All @@ -177,7 +186,7 @@ Cargo publishing configuration.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `enabled` | boolean | `false` | Enable Cargo publishing |
| `enabled` | boolean | `true` | Publish to crates.io. Detected Rust packages are published by default; set false to opt out (version and tag only). |
| `noVerify` | boolean | `false` | Skip verification before publish |
| `publishOrder` | `string[]` | `[]` | Order in which to publish packages |
| `clean` | boolean | `false` | Clean before publishing |
Expand All @@ -188,7 +197,7 @@ Dart/Flutter publishing configuration via pub.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `enabled` | boolean | `false` | Enable pub.dev publishing |
| `enabled` | boolean | `true` | Publish to pub.dev. Detected Dart/Flutter packages are published by default; set false to opt out (version and tag only). |
| `publishOrder` | `string[]` | `[]` | Order in which to publish packages |

### `publish.githubRelease`
Expand Down Expand Up @@ -290,9 +299,9 @@ LLM configuration for release notes.

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `provider` | string | — | LLM provider |
| `model` | string | — | Model identifier |
| `baseURL` | string | — | Custom API base URL |
| `provider` | `"openai"` \| `"openai-compatible"` \| `"anthropic"` \| `"ollama"` | — | LLM provider |
| `model` | string | — | Model identifier for the selected provider. Required — releasekit ships no default, so enabling LLM enhancement needs an explicit, current model (a missing model fails config validation). |
| `baseURL` | string | — | Custom API base URL. Required for the openai-compatible provider. |
| `apiKey` | string | — | API key |
| `concurrency` | integer | — | Concurrent LLM requests |
| `style` | string | — | Writing style for LLM |
Expand Down
1 change: 1 addition & 0 deletions packages/config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export {
type GitHubReleaseConfig,
type LLMCategory,
type LLMConfig,
LLMConfigSchema,
type LLMPromptOverrides,
type LLMPromptsConfig,
type LocationMode,
Expand Down
77 changes: 66 additions & 11 deletions packages/config/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,32 @@ export const MonorepoConfigSchema = z.object({
packagesPath: z.string().optional().describe('Path to packages directory'),
});

export const VersionNpmConfigSchema = z.object({
enabled: z
.boolean()
.default(true)
.describe(
'Version package.json manifests. Detected npm packages are versioned by default; set false to opt out (e.g. npm versioning handled elsewhere).',
),
});

export const VersionCargoConfigSchema = z.object({
enabled: z.boolean().default(true).describe('Enable Cargo.toml version handling'),
enabled: z
.boolean()
.default(true)
.describe(
'Version Cargo.toml manifests. Detected Rust packages are versioned by default; set false to opt out (e.g. a vendored crate, or Rust versioning handled elsewhere).',
),
paths: z.array(z.string()).optional().describe('Directories to search for Cargo.toml files'),
});

export const VersionPubConfigSchema = z.object({
enabled: z.boolean().default(true).describe('Enable pubspec.yaml version handling'),
enabled: z
.boolean()
.default(true)
.describe(
'Version pubspec.yaml manifests. Detected Dart/Flutter packages are versioned by default; set false to opt out.',
),
paths: z.array(z.string()).optional().describe('Directories to search for pubspec.yaml files'),
});

Expand Down Expand Up @@ -129,12 +148,18 @@ export const VersionConfigSchema = z.object({
.describe(
"Pre-1.0 handling of commit-inferred breaking changes. 'spec' (default): bump the 0.x minor (0.24.0 → 0.25.0), per semver §4. 'strict': bump the next major (→ 1.0.0). Inferred path only — explicit overrides (--bump major, bump:major) always graduate to 1.0.0.",
),
npm: VersionNpmConfigSchema.optional().describe('npm/JavaScript version handling'),
cargo: VersionCargoConfigSchema.optional().describe('Cargo/Rust configuration'),
pub: VersionPubConfigSchema.optional().describe('Dart/Flutter pub configuration'),
});

export const NpmConfigSchema = z.object({
enabled: z.boolean().default(true).describe('Enable NPM publishing'),
enabled: z
.boolean()
.default(true)
.describe(
'Publish to npm. Detected npm packages are published by default; set false to opt out (version and tag only).',
),
auth: z.enum(['auto', 'oidc', 'token']).default('auto').describe('Authentication method'),
provenance: z.boolean().default(true).describe('Enable npm provenance attestation'),
access: z.enum(['public', 'restricted']).default('public').describe('Package access level'),
Expand All @@ -148,14 +173,24 @@ export const NpmConfigSchema = z.object({
});

export const CargoPublishConfigSchema = z.object({
enabled: z.boolean().default(false).describe('Enable Cargo publishing'),
enabled: z
.boolean()
.default(true)
.describe(
'Publish to crates.io. Detected Rust packages are published by default; set false to opt out (version and tag only).',
),
noVerify: z.boolean().default(false).describe('Skip verification before publish'),
publishOrder: z.array(z.string()).default([]).describe('Order in which to publish packages'),
clean: z.boolean().default(false).describe('Clean before publishing'),
});

export const PubPublishConfigSchema = z.object({
enabled: z.boolean().default(false).describe('Enable pub.dev publishing'),
enabled: z
.boolean()
.default(true)
.describe(
'Publish to pub.dev. Detected Dart/Flutter packages are published by default; set false to opt out (version and tag only).',
),
publishOrder: z.array(z.string()).default([]).describe('Order in which to publish packages'),
});

Expand Down Expand Up @@ -248,13 +283,13 @@ export const PublishConfigSchema = z.object({
publishOrder: [],
}).describe('NPM publishing configuration'),
cargo: CargoPublishConfigSchema.default({
enabled: false,
enabled: true,
noVerify: false,
publishOrder: [],
clean: false,
}).describe('Cargo publishing configuration'),
pub: PubPublishConfigSchema.default({
enabled: false,
enabled: true,
publishOrder: [],
}).describe('Dart/Flutter publishing configuration via pub'),
githubRelease: GitHubReleaseConfigSchema.default({
Expand Down Expand Up @@ -397,10 +432,17 @@ export const LLMPromptsConfigSchema = z.object({
),
});

export const LLMConfigSchema = z.object({
provider: z.string().describe('LLM provider'),
model: z.string().describe('Model identifier'),
baseURL: z.string().optional().describe('Custom API base URL'),
const LLMConfigBaseSchema = z.object({
provider: z.enum(['openai', 'openai-compatible', 'anthropic', 'ollama']).describe('LLM provider'),
model: z
.string()
// .min(1): an empty string passes z.string(), so "model": "" would slip through Zod at load and
// only fail deep in the pipeline (soft-caught). Reject it at config validation instead.
.min(1, 'model must be a non-empty string — set it to a model the provider serves.')
.describe(
'Model identifier for the selected provider. Required — releasekit ships no default, so enabling LLM enhancement needs an explicit, current model (a missing model fails config validation).',
),
Comment thread
greptile-apps[bot] marked this conversation as resolved.
baseURL: z.string().optional().describe('Custom API base URL. Required for the openai-compatible provider.'),
apiKey: z.string().optional().describe('API key'),
options: LLMOptionsSchema.optional(),
concurrency: z.number().int().positive().optional().describe('Concurrent LLM requests'),
Expand Down Expand Up @@ -448,6 +490,19 @@ export const LLMConfigSchema = z.object({
),
});

// The openai-compatible provider points at a custom endpoint, so releasekit can't assume a base URL:
// it must be set explicitly. Enforced at config-validation time (a Zod refinement) so a misconfig
// fails loudly here rather than silently degrading to non-LLM notes inside the notes soft-fail catch.
export const LLMConfigSchema = LLMConfigBaseSchema.superRefine((cfg, ctx) => {
if (cfg.provider === 'openai-compatible' && !cfg.baseURL) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['baseURL'],
message: 'baseURL is required when provider is "openai-compatible".',
});
}
});

export const ReleaseNotesConfigSchema = z
.object({
file: ReleaseNotesFileConfigSchema.optional().describe(
Expand Down
39 changes: 34 additions & 5 deletions packages/config/test/unit/schema.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
TemplateConfigSchema,
VerifyConfigSchema,
VersionConfigSchema,
VersionNpmConfigSchema,
} from '../../src/schema.js';

describe('GitConfigSchema', () => {
Expand Down Expand Up @@ -64,7 +65,6 @@ describe('MonorepoConfigSchema', () => {
describe('VersionConfigSchema', () => {
it('should apply defaults', () => {
const result = VersionConfigSchema.parse({});
// biome-ignore lint/suspicious/noTemplateCurlyInString: checking the literal default string
expect(result.tagTemplate).toBe('${prefix}${version}');
expect(result.packageSpecificTags).toBe(false);
expect(result.preset).toBe('conventional');
Expand Down Expand Up @@ -104,6 +104,11 @@ describe('VersionConfigSchema', () => {
expect(result.cargo?.paths).toEqual(['crates/core', 'crates/cli']);
});

it('should default version.npm.enabled to true and accept an opt-out', () => {
expect(VersionNpmConfigSchema.parse({}).enabled).toBe(true);
expect(VersionConfigSchema.parse({ npm: { enabled: false } }).npm?.enabled).toBe(false);
});

it('should accept version groups with fixed and linked sync modes', () => {
const result = VersionConfigSchema.parse({
sync: false,
Expand Down Expand Up @@ -172,7 +177,8 @@ describe('NpmConfigSchema', () => {
describe('CargoPublishConfigSchema', () => {
it('should apply defaults', () => {
const result = CargoPublishConfigSchema.parse({});
expect(result.enabled).toBe(false);
// Detection enables, config opts out: crates.io publishing defaults on.
expect(result.enabled).toBe(true);
expect(result.noVerify).toBe(false);
expect(result.publishOrder).toEqual([]);
expect(result.clean).toBe(false);
Expand Down Expand Up @@ -221,10 +227,11 @@ describe('VerifyConfigSchema', () => {
});

describe('PublishConfigSchema', () => {
it('should apply defaults for npm and cargo', () => {
it('should default publishing on for npm, cargo, and pub (detection enables, config opts out)', () => {
const result = PublishConfigSchema.parse({});
expect(result.npm.enabled).toBe(true);
expect(result.cargo.enabled).toBe(false);
expect(result.cargo.enabled).toBe(true);
expect(result.pub.enabled).toBe(true);
expect(result.githubRelease.enabled).toBe(true);
});
});
Expand Down Expand Up @@ -325,12 +332,34 @@ describe('LLMPromptsConfigSchema', () => {
});

describe('LLMConfigSchema', () => {
it('should require provider and model', () => {
it('should accept provider and model', () => {
const result = LLMConfigSchema.parse({ provider: 'openai', model: 'gpt-4' });
expect(result.provider).toBe('openai');
expect(result.model).toBe('gpt-4');
});

it('should require a non-empty model for every provider (releasekit ships no default)', () => {
expect(() => LLMConfigSchema.parse({ provider: 'anthropic' })).toThrow();
expect(() => LLMConfigSchema.parse({ provider: 'openai' })).toThrow();
expect(() => LLMConfigSchema.parse({ provider: 'ollama' })).toThrow();
// An empty string must not slip through z.string().
expect(() => LLMConfigSchema.parse({ provider: 'openai', model: '' })).toThrow(/non-empty/);
});

it('should reject an unknown provider (enum, not free-form string)', () => {
expect(() => LLMConfigSchema.parse({ provider: 'gpt', model: 'x' })).toThrow();
expect(() => LLMConfigSchema.parse({ provider: 'anthropicc', model: 'x' })).toThrow();
});

it('should require baseURL for openai-compatible', () => {
expect(() => LLMConfigSchema.parse({ provider: 'openai-compatible', model: 'local-model' })).toThrow(
/baseURL is required/,
);
const ok = LLMConfigSchema.parse({ provider: 'openai-compatible', baseURL: 'https://x/v1', model: 'local-model' });
expect(ok.provider).toBe('openai-compatible');
expect(ok.model).toBe('local-model');
});

it('should accept optional fields', () => {
const result = LLMConfigSchema.parse({
provider: 'openai',
Expand Down
6 changes: 3 additions & 3 deletions packages/notes/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ LLM configuration for release notes enhancement. Requires `provider` and `model`
"releaseNotes": {
"llm": {
"provider": "openai",
"model": "gpt-4o-mini",
"model": "<your-model>",
"tasks": { "enhance": true, "summarize": true }
}
}
Expand Down Expand Up @@ -207,7 +207,7 @@ How existing changelog files are updated when new entries are generated.
| Option | Type | Description |
|--------|------|-------------|
| `provider` | `string` | LLM provider key. See [LLM providers](./llm-providers.md). |
| `model` | `string` | Model identifier (e.g. `"gpt-4o-mini"`, `"claude-sonnet-4-5"`) |
| `model` | `string` | Model identifier for the selected provider (see [LLM providers](./llm-providers.md)). Required — releasekit ships no default, so pick a current model your provider serves. |

### Connection

Expand Down Expand Up @@ -243,7 +243,7 @@ How existing changelog files are updated when new entries are generated.
"releaseNotes": {
"llm": {
"provider": "openai",
"model": "gpt-4o",
"model": "<your-model>",
"options": { "temperature": 0.3, "maxTokens": 1000 }
}
}
Expand Down
8 changes: 4 additions & 4 deletions packages/notes/docs/llm-providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Model names change frequently — check your provider's documentation for the cu
"releaseNotes": {
"llm": {
"provider": "openai",
"model": "gpt-4o-mini",
"model": "<your-model>",
"tasks": { "enhance": true }
}
}
Expand All @@ -62,7 +62,7 @@ See the [OpenAI models documentation](https://platform.openai.com/docs/models) f
"releaseNotes": {
"llm": {
"provider": "anthropic",
"model": "claude-haiku-4-5",
"model": "<your-model>",
"tasks": { "releaseNotes": true }
}
}
Expand All @@ -89,7 +89,7 @@ ollama pull llama3.2
"releaseNotes": {
"llm": {
"provider": "ollama",
"model": "llama3.2",
"model": "<your-model>",
"tasks": { "enhance": true }
}
}
Expand All @@ -109,7 +109,7 @@ For self-hosted or third-party OpenAI-compatible APIs (LM Studio, vLLM, Azure Op
"releaseNotes": {
"llm": {
"provider": "openai-compatible",
"model": "your-model-name",
"model": "<your-model>",
"baseURL": "http://localhost:1234/v1",
"tasks": { "enhance": true }
}
Expand Down
4 changes: 2 additions & 2 deletions packages/notes/docs/llm-release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Add an `llm` block under `notes.releaseNotes` in `releasekit.config.json`:
"releaseNotes": {
"llm": {
"provider": "openai",
"model": "gpt-4o-mini",
"model": "<your-model>",
"tasks": { "enhance": true, "categorize": true }
}
}
Expand Down Expand Up @@ -418,7 +418,7 @@ chore: migrate bundler from webpack to esbuild 0.20
"releaseNotes": {
"llm": {
"provider": "anthropic",
"model": "claude-haiku-4-5",
"model": "<your-model>",
"tasks": { "enhance": true, "categorize": true },
"categoryOrder": ["Breaking", "New", "Fixed", "Changed", "Developer"]
},
Expand Down
2 changes: 1 addition & 1 deletion packages/notes/docs/monorepo.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ LLM tasks run per-package. Each package's entries are processed independently. W
"releaseNotes": {
"llm": {
"provider": "openai",
"model": "gpt-4o-mini",
"model": "<your-model>",
"concurrency": 5,
"tasks": { "enhance": true }
}
Expand Down
Loading