Skip to content

[WIP] Release agent tooling#2176

Open
r0b1n wants to merge 3 commits into
mainfrom
ai/release-info-script
Open

[WIP] Release agent tooling#2176
r0b1n wants to merge 3 commits into
mainfrom
ai/release-info-script

Conversation

@r0b1n
Copy link
Copy Markdown
Collaborator

@r0b1n r0b1n commented Apr 16, 2026

No description provided.

@r0b1n r0b1n requested a review from a team as a code owner April 16, 2026 13:58
@r0b1n r0b1n force-pushed the ai/release-info-script branch from 07920ac to 7f00790 Compare April 17, 2026 10:45
Comment on lines +83 to +89
const unreleased = changelog.changelog.content[0];
return unreleased.sections.flatMap(section =>
section.logs.map(log => ({
type: section.type,
description: log
}))
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a changelog has no entries at all, content[0] is undefined and this throws. Should guard: if (!unreleased) return []

}

interface ChangelogEntry {
type: "Fixed" | "Added" | "Changed" | "Removed" | "Breaking changes";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here you mention the breaking changes type, but the release-candidates.ts file does not have it https://github.com/mendix/web-widgets/pull/2176/changes#diff-455469ccfc1eb8c253d3958fc6b9424447d549592886eb89d125c08ff6cf60e6R14

@r0b1n r0b1n force-pushed the ai/release-info-script branch 2 times, most recently from 7f00790 to 9e7554d Compare May 11, 2026 12:55
@github-actions
Copy link
Copy Markdown

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
automation/utils/bin/rui-release-info.ts New CLI entry-point for querying release candidates
automation/utils/package.json Registers new rui-release-info bin script
automation/utils/src/index.ts Exports new release-candidates and io/filesystem modules
automation/utils/src/io/filesystem.ts New FileSystem interface + NodeFileSystem implementation
automation/utils/src/release-candidates.ts Core logic: loadAllPackages, loadReleaseCandidates, extractChangelogEntries
docs/release-process/package-types.md New docs: package type taxonomy
docs/release-process/release-info-tool.md New docs: tool reference
docs/release-process/release-info-tool-usage.md New docs: usage guide with examples
docs/release-process/release-workflow.md New docs: end-to-end release workflow

Skipped (out of scope): dist/, pnpm-lock.yaml


Findings

🔶 Medium — ChangelogEntry.type is narrower than the actual parser output

File: automation/utils/src/release-candidates.ts lines 165–167
Problem: ChangelogEntry.type is declared as "Fixed" | "Added" | "Changed" | "Removed", but the PEG grammar parsers (widget.pegjs:44, module.pegjs:58) also emit "Deprecated", "Security", "Breaking changes", and "Documentation". LogSection.type itself is typed as "Fixed" | "Added" | "Changed" | "Removed" in types.ts:44 — meaning the parser outputs those extra tokens but the TypeScript type silently drops them. Any changelog entry with ### Breaking changes will crash at runtime or be silently omitted.
Fix: Either widen ChangelogEntry.type to match the full grammar, or assert and handle the union properly:

export interface ChangelogEntry {
    type: "Fixed" | "Added" | "Changed" | "Removed" | "Deprecated" | "Security" | "Breaking changes" | "Documentation";
    description: string;
}

Also update LogSection.type in changelog-parser/types.ts to match.


🔶 Medium — extractChangelogEntries silently ignores module subcomponent entries

File: automation/utils/src/release-candidates.ts lines 82–90
Problem: For a ModuleChangelogFileWrapper, changelog.changelog.content[0] is a ModuleUnreleasedVersionEntry which has both sections (top-level entries) and subcomponents (per-widget sections). extractChangelogEntries only reads unreleased.sections, so any entries nested inside subcomponents are silently dropped. Since module changelogs aggregate widget entries there, callers will get empty unreleasedEntries for modules that use the subcomponent format.
Fix: For module changelogs, also flatten subcomponents:

export function extractChangelogEntries(
    changelog: WidgetChangelogFileWrapper | ModuleChangelogFileWrapper
): ChangelogEntry[] {
    const unreleased = changelog.changelog.content[0];
    const topLevel = unreleased.sections.flatMap(section =>
        section.logs.map(log => ({ type: section.type, description: log }))
    );
    const subcomponentEntries =
        "subcomponents" in unreleased
            ? unreleased.subcomponents.flatMap(sc =>
                  sc.sections.flatMap(section =>
                      section.logs.map(log => ({ type: section.type, description: log }))
                  )
              )
            : [];
    return [...topLevel, ...subcomponentEntries];
}

🔶 Medium — scanPackagesDirectory doesn't filter non-directories

File: automation/utils/src/release-candidates.ts lines 123–146
Problem: fs.readdir(dirPath) returns all entries (files and directories) using readdir without { withFileTypes: true }. For each entry it checks for package.json, which works for packages but will silently scan any stray files. More critically, if the directory contains a non-directory file (e.g. a README or .DS_Store), join(dirPath, entry) becomes a file path, fs.exists(join(filePath, "package.json")) returns false and it's skipped — correct but fragile. Using withFileTypes: true is more robust and avoids unnecessary exists calls.
Fix: Add { withFileTypes: true } to readdir and filter on dirent.isDirectory(). This requires updating the FileSystem interface and NodeFileSystem:

readdir(path: string): Promise<string[]> {
    return readdir(path, { withFileTypes: true }).then(entries =>
        entries.filter(e => e.isDirectory()).map(e => e.name)
    );
}

⚠️ Low — Stale cross-reference in docs

File: docs/release-process/release-info-tool-usage.md line 122
Note: References docs/requirements/release-info-tool-summary.md which does not exist in the repo. The file was added under docs/release-process/release-info-tool.md. Update the link.


⚠️ Low — --summary output keys don't match the jq example in docs

File: automation/utils/bin/rui-release-info.ts lines 23–40 vs docs/release-process/release-info-tool.md lines 975–982
Note: The actual summary JSON uses keys widgets and modules, but the jq example in the docs queries .independentWidgets, .independentModules, .widgetAggregators, .moduleAggregators — keys that don't exist in the output. The jq command will silently return null. Align the docs example with the real output or expand the summary object to include those keys.


⚠️ Low — No unit tests for new logic

File: automation/utils/src/release-candidates.ts
Note: loadAllPackages, loadReleaseCandidates, and extractChangelogEntries are non-trivial (two-pass scan, aggregator detection, changelog extraction). There are no .spec.ts files anywhere under automation/utils/. The FileSystem interface was deliberately designed for testability (dependency injection), so adding tests is straightforward. Consider adding coverage for at least: aggregator detection, dependent widget exclusion from top-level candidates, and the extractChangelogEntries edge cases.


Positives

  • FileSystem interface with constructor-injectable default (defaultFS) is a clean, testable design — enables future in-memory or MCP-backed implementations without touching core logic.
  • Two-pass scan (identify aggregators first, then filter dependents) correctly avoids double-counting dependent widgets as standalone candidates.
  • Per-operation console.warn with try/catch isolation means a single bad package doesn't abort the whole scan.
  • The workflow docs (release-workflow.md) are thorough and include concrete step-by-step examples with before/after changelog state — genuinely useful for onboarding.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants