feat: pubspec auditing, sibling dep conversion, and CI template updates#22
feat: pubspec auditing, sibling dep conversion, and CI template updates#22tsavo-at-pieces merged 6 commits intomainfrom
Conversation
PR SummaryMedium Risk Overview Enhances Updates CI/workflow templates and generator (tooling version bump, standardized Written by Cursor Bugbot for commit 89ec243. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
| workers.add(worker()); | ||
| } | ||
| await Future.wait(workers); | ||
|
|
There was a problem hiding this comment.
Audit-all concurrency never actually runs
Medium Severity
The worker-pool in AuditAllCommand doesn’t yield inside worker(), so the first scheduled worker processes the entire pubspecs list synchronously and blocks the event loop; the other workers effectively do nothing. This makes --concurrency misleading and can significantly slow large scans.
| ); | ||
| CiProcessRunner.exec('git', ['push', 'origin', pkgTag], cwd: repoRoot, fatal: true, verbose: global.verbose); | ||
| pkgTagsCreated.add(pkgTag); | ||
| } |
There was a problem hiding this comment.
Per-package tag check ignores remote tags
Medium Severity
The per-package tag existence check uses git rev-parse locally, so an existing remote-only tag isn’t detected. In that case, git tag -a may succeed locally but git push origin <tag> can fail due to the remote tag already existing, interrupting the release pipeline mid-flight.
| index++; | ||
|
|
||
| final pubspecPath = pubspecs[currentIndex]; | ||
| final findings = auditor.auditPubspec(pubspecPath); | ||
|
|
||
| // Filter by minimum severity. | ||
| final filtered = findings.where((f) => f.severity.index <= severityFilter.index).toList(); | ||
|
|
||
| results.add(_AuditResult(index: currentIndex, pubspecPath: pubspecPath, findings: filtered)); | ||
| } | ||
| } |
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, something went wrong.
There was a problem hiding this comment.
Pull request overview
This PR adds comprehensive pubspec auditing infrastructure to validate git-sourced dependencies, introduces automatic sibling dependency conversion for multi-package releases, and refactors CI templates for improved security and maintainability.
Changes:
- New
auditandaudit-allcommands validate pubspec.yaml files against a package registry, checking git URLs, tag patterns, version constraints, and repository ownership - During release creation, sibling dependencies between sub-packages are automatically converted from bare version constraints to full git dependency blocks with tag resolution, enabling standalone package consumption
- CI workflow templates now use step-level environment variables instead of inline secret interpolation, improving security and reducing accidental exposure risk
Reviewed changes
Copilot reviewed 17 out of 17 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
lib/src/cli/utils/audit/audit_finding.dart |
Data model for audit findings with severity levels and category enums |
lib/src/cli/utils/audit/package_registry.dart |
Registry loading and lookup logic for external workspace packages |
lib/src/cli/utils/audit/pubspec_auditor.dart |
Core audit validation and fix logic for pubspec dependencies |
lib/src/cli/commands/audit_command.dart |
Single-package pubspec audit CLI command |
lib/src/cli/commands/audit_all_command.dart |
Bulk recursive pubspec audit CLI command with concurrency support |
lib/src/cli/utils/sub_package_utils.dart |
Added sibling dependency conversion function for multi-package releases |
lib/src/cli/commands/create_release_command.dart |
Integrated sibling dep conversion and per-package tag creation into release flow |
lib/src/cli/manage_cicd_cli.dart |
Registered new audit commands |
templates/github/workflows/release.template.yaml |
Refactored to use env vars for secrets, improved shell variable handling |
templates/github/workflows/issue-triage.template.yaml |
Refactored to use env vars for secrets |
templates/github/workflows/ci.skeleton.yaml |
Refactored to use env vars for secrets |
lib/src/cli/utils/workflow_generator.dart |
Changed macOS x64 runner from macos-15-intel to macos-15-large |
lib/src/cli/commands/test_command.dart |
Increased test timeout from 30 to 45 minutes |
templates/config.json |
Removed example runner overrides for ubuntu-x64 and windows-x64 |
pubspec.yaml |
Added yaml and yaml_edit dependencies |
.github/workflows/ci.yaml |
Generated workflow reflecting template updates |
.runtime_ci/template_versions.json |
Updated version tracking metadata |
Comments suppressed due to low confidence (3)
lib/src/cli/utils/sub_package_utils.dart:346
- The sibling dependency conversion always uses
^$newVersionas the version constraint, which may not preserve the original constraint semantics. If the original constraint was a bare string like^0.8.2and the new version is0.8.3, the converted constraint^0.8.3is reasonable. However, if the original constraint was more specific (e.g.,>=0.8.2 <0.9.0), this conversion loses that information.
Consider preserving the original version constraint when it's a string, or at least documenting this behavior change in the PR description, since it could affect consumers who relied on more specific version ranges between sibling packages.
final newValue = <String, Object>{'git': gitBlock, 'version': '^$newVersion'};
lib/src/cli/utils/audit/pubspec_auditor.dart:482
- The new audit infrastructure (AuditCommand, AuditAllCommand, PubspecAuditor, PackageRegistry) lacks test coverage. The codebase demonstrates comprehensive testing patterns for command utilities (see test/consumers_command_test.dart, test/hook_installer_test.dart), and similar test coverage should be added for the audit functionality.
Consider adding unit tests for:
- PackageRegistry.loadFromString() with various YAML inputs
- PubspecAuditor.auditPubspec() with different dependency configurations
- The various audit categories (bare dependency, wrong URL format, stale version, etc.)
- Fix application logic in PubspecAuditor.fixPubspec()
These tests would help prevent regressions and validate edge cases in the audit logic.
import 'dart:io';
import 'package:yaml/yaml.dart';
import 'package:yaml_edit/yaml_edit.dart';
import '../logger.dart';
import 'audit_finding.dart';
import 'package_registry.dart';
/// RegExp matching SSH-format GitHub URLs: `git@github.com:org/repo.git`.
final _sshUrlPattern = RegExp(r'^git@github\.com:([^/]+)/([^/]+)\.git$');
/// RegExp matching HTTPS-format GitHub URLs so we can detect and flag them.
final _httpsUrlPattern = RegExp(r'^https?://github\.com/([^/]+)/([^/]+?)(?:\.git)?$');
/// Audits pubspec.yaml dependency declarations against a [PackageRegistry].
///
/// For every dependency that matches a registry entry, the auditor checks:
///
/// 1. **bare_dependency** -- plain version string with no git source
/// 2. **wrong_org** -- git URL points to wrong GitHub org
/// 3. **wrong_repo** -- git URL points to wrong repo name
/// 4. **missing_tag_pattern** -- no `tag_pattern` in git block
/// 5. **wrong_tag_pattern** -- `tag_pattern` differs from registry
/// 6. **stale_version** -- version constraint differs from registry
/// 7. **wrong_url_format** -- git URL uses HTTPS instead of SSH
///
/// Dependencies that do not appear in the registry (pub.dev, workspace-
/// internal, path deps) are silently skipped.
class PubspecAuditor {
/// The package registry to validate against.
final PackageRegistry registry;
const PubspecAuditor({required this.registry});
// ---------------------------------------------------------------------------
// Audit
// ---------------------------------------------------------------------------
/// Audit a single pubspec.yaml file and return all findings.
///
/// Returns an empty list when the pubspec is fully compliant.
List<AuditFinding> auditPubspec(String pubspecPath) {
final file = File(pubspecPath);
if (!file.existsSync()) {
Logger.error('Pubspec not found: $pubspecPath');
return [
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: '<file>',
severity: AuditSeverity.error,
category: AuditCategory.bareDependency,
message: 'Pubspec file does not exist',
),
];
}
final String content;
try {
content = file.readAsStringSync();
} on FileSystemException catch (e) {
Logger.error('Failed to read pubspec: $e');
return [];
}
final YamlMap doc;
try {
doc = loadYaml(content) as YamlMap;
} on YamlException catch (e) {
Logger.error('Failed to parse pubspec YAML at $pubspecPath: $e');
return [];
}
final findings = <AuditFinding>[];
// Audit both `dependencies` and `dev_dependencies` sections.
final deps = doc['dependencies'] as YamlMap?;
if (deps != null) {
findings.addAll(_auditDependencyMap(pubspecPath, deps));
}
final devDeps = doc['dev_dependencies'] as YamlMap?;
if (devDeps != null) {
findings.addAll(_auditDependencyMap(pubspecPath, devDeps));
}
return findings;
}
/// Walk a dependency map and check each entry that exists in the registry.
List<AuditFinding> _auditDependencyMap(String pubspecPath, YamlMap deps) {
final findings = <AuditFinding>[];
for (final key in deps.keys) {
final name = key as String;
final entry = registry.lookup(name);
if (entry == null) continue; // Not a registry package -- skip.
final value = deps[name];
findings.addAll(_auditDependency(pubspecPath, name, value, entry));
}
return findings;
}
/// Audit a single dependency against its registry entry.
List<AuditFinding> _auditDependency(String pubspecPath, String depName, Object? value, RegistryEntry entry) {
final findings = <AuditFinding>[];
// --- Path dependency -- skip entirely (workspace-local override) ---------
if (value is YamlMap && value.containsKey('path')) {
return findings;
}
// --- Bare dependency (just a version string or `any`) --------------------
if (value is String) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.error,
category: AuditCategory.bareDependency,
message: 'Bare dependency -- should use git source with tag_pattern',
currentValue: value,
expectedValue: entry.expectedGitUrl,
),
);
// Also flag stale version if the bare constraint doesn't match.
if (value != entry.version) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.warning,
category: AuditCategory.staleVersion,
message: 'Version constraint does not match registry',
currentValue: value,
expectedValue: entry.version,
),
);
}
return findings;
}
// --- Null value (workspace member ref, e.g. `runtime_native_io_core:`) ---
if (value == null) {
// A null value means the dep relies on workspace resolution or has no
// constraints at all. We still flag it as bare if it's in the registry.
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.error,
category: AuditCategory.bareDependency,
message:
'Empty dependency (no source, no version) -- should use git '
'source with tag_pattern',
currentValue: null,
expectedValue: entry.expectedGitUrl,
),
);
return findings;
}
// --- Map dependency (expected for git deps) ------------------------------
if (value is! YamlMap) {
Logger.warn(
'Unexpected dependency type for "$depName" in $pubspecPath: '
'${value.runtimeType}',
);
return findings;
}
final depMap = value;
// Check for git block.
final gitBlock = depMap['git'];
if (gitBlock == null) {
// Has a map (possibly with `version:` or `sdk:` or `hosted:`) but no
// `git:` block. If it doesn't have `path:` (already handled above),
// treat it as bare.
if (!depMap.containsKey('path')) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.error,
category: AuditCategory.bareDependency,
message:
'Dependency has no git source -- should use git source '
'with tag_pattern',
currentValue: depMap.toString(),
expectedValue: entry.expectedGitUrl,
),
);
}
return findings;
}
// The git block can be either a string (shorthand URL) or a YamlMap.
String? gitUrl;
String? tagPattern;
String? ref;
if (gitBlock is String) {
gitUrl = gitBlock;
} else if (gitBlock is YamlMap) {
gitUrl = gitBlock['url'] as String?;
tagPattern = gitBlock['tag_pattern'] as String?;
ref = gitBlock['ref'] as String?;
}
// --- Rule 7: wrong_url_format (HTTPS instead of SSH) --------------------
if (gitUrl != null && !_sshUrlPattern.hasMatch(gitUrl)) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.error,
category: AuditCategory.wrongUrlFormat,
message: 'Git URL is not SSH format',
currentValue: gitUrl,
expectedValue: entry.expectedGitUrl,
),
);
}
// Parse org/repo from the git URL for org/repo checks.
final sshMatch = gitUrl != null ? _sshUrlPattern.firstMatch(gitUrl) : null;
final httpsMatch = gitUrl != null ? _httpsUrlPattern.firstMatch(gitUrl) : null;
final urlOrg = sshMatch?.group(1) ?? httpsMatch?.group(1);
final urlRepo = sshMatch?.group(2) ?? httpsMatch?.group(2);
// --- Rule 2: wrong_org ---------------------------------------------------
if (urlOrg != null && urlOrg != entry.githubOrg) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.error,
category: AuditCategory.wrongOrg,
message: 'Git URL org does not match registry',
currentValue: urlOrg,
expectedValue: entry.githubOrg,
),
);
}
// --- Rule 3: wrong_repo --------------------------------------------------
if (urlRepo != null && urlRepo != entry.githubRepo) {
// Strip trailing `.git` from parsed HTTPS repos for comparison.
final cleanedRepo = urlRepo.replaceAll(RegExp(r'\.git$'), '');
if (cleanedRepo != entry.githubRepo) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.error,
category: AuditCategory.wrongRepo,
message: 'Git URL repo does not match registry',
currentValue: urlRepo,
expectedValue: entry.githubRepo,
),
);
}
}
// --- Rule 4 & 5: missing/wrong tag_pattern -------------------------------
if (tagPattern == null) {
// Legacy format might use `ref` instead of `tag_pattern`.
if (ref != null) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.warning,
category: AuditCategory.missingTagPattern,
message: 'Git dep uses legacy "ref" instead of "tag_pattern"',
currentValue: 'ref: $ref',
expectedValue: 'tag_pattern: ${entry.tagPattern}',
),
);
} else {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.error,
category: AuditCategory.missingTagPattern,
message: 'Git dep is missing "tag_pattern" field',
currentValue: null,
expectedValue: entry.tagPattern,
),
);
}
} else if (tagPattern != entry.tagPattern) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.error,
category: AuditCategory.wrongTagPattern,
message: 'tag_pattern does not match registry',
currentValue: tagPattern,
expectedValue: entry.tagPattern,
),
);
}
// --- Rule 6: stale_version -----------------------------------------------
final versionValue = depMap['version'] as String?;
if (versionValue != null && versionValue != entry.version) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.warning,
category: AuditCategory.staleVersion,
message: 'Version constraint does not match registry',
currentValue: versionValue,
expectedValue: entry.version,
),
);
} else if (versionValue == null) {
findings.add(
AuditFinding(
pubspecPath: pubspecPath,
dependencyName: depName,
severity: AuditSeverity.warning,
category: AuditCategory.staleVersion,
message:
'No version constraint specified -- should have version: '
'${entry.version}',
currentValue: null,
expectedValue: entry.version,
),
);
}
// NOTE: We do not validate `git_path` presence here because not all
// pubspecs currently use it for multi-package repos. The fix logic will
// add it when creating new git dep blocks from bare deps.
return findings;
}
// ---------------------------------------------------------------------------
// Fix
// ---------------------------------------------------------------------------
/// Apply fixes for the given [findings] to the pubspec at [pubspecPath].
///
/// Returns `true` if the file was modified, `false` otherwise.
///
/// The fixer uses [YamlEditor] so comments and formatting are preserved
/// as much as possible.
bool fixPubspec(String pubspecPath, List<AuditFinding> findings) {
if (findings.isEmpty) return false;
final file = File(pubspecPath);
if (!file.existsSync()) {
Logger.error('Cannot fix -- pubspec not found: $pubspecPath');
return false;
}
final original = file.readAsStringSync();
final editor = YamlEditor(original);
// Track which deps we've already fully rewritten so we don't try to
// patch individual fields on top of a wholesale replacement.
final rewritten = <String>{};
// Group findings by dependency name for efficient processing.
final byDep = <String, List<AuditFinding>>{};
for (final f in findings) {
byDep.putIfAbsent(f.dependencyName, () => []).add(f);
}
// Parse the current doc to determine which section each dep lives in.
final doc = loadYaml(original) as YamlMap;
for (final depName in byDep.keys) {
final depFindings = byDep[depName]!;
final entry = registry.lookup(depName);
if (entry == null) continue;
// Determine the section path (`dependencies` or `dev_dependencies`).
final sectionKey = _findSectionKey(doc, depName);
if (sectionKey == null) {
Logger.warn(
'Could not locate "$depName" in dependencies or '
'dev_dependencies of $pubspecPath -- skipping fix',
);
continue;
}
final categories = depFindings.map((f) => f.category).toSet();
// If the dep is bare (no git source at all), do a full rewrite.
if (categories.contains(AuditCategory.bareDependency)) {
_rewriteToFullGitDep(editor, sectionKey, depName, entry);
rewritten.add(depName);
continue;
}
// Otherwise, patch individual fields.
if (rewritten.contains(depName)) continue;
if (categories.contains(AuditCategory.wrongUrlFormat) ||
categories.contains(AuditCategory.wrongOrg) ||
categories.contains(AuditCategory.wrongRepo)) {
_tryUpdate(editor, [sectionKey, depName, 'git', 'url'], entry.expectedGitUrl);
}
if (categories.contains(AuditCategory.missingTagPattern) || categories.contains(AuditCategory.wrongTagPattern)) {
// If the dep has a legacy `ref` field, remove it first.
_tryRemove(editor, [sectionKey, depName, 'git', 'ref']);
_tryUpdate(editor, [sectionKey, depName, 'git', 'tag_pattern'], entry.tagPattern);
}
if (categories.contains(AuditCategory.staleVersion)) {
_tryUpdate(editor, [sectionKey, depName, 'version'], entry.version);
}
}
final updated = editor.toString();
if (updated == original) return false;
file.writeAsStringSync(updated);
return true;
}
/// Rewrite a dependency to the full git format using registry values.
void _rewriteToFullGitDep(YamlEditor editor, String sectionKey, String depName, RegistryEntry entry) {
final gitBlock = <String, Object>{'url': entry.expectedGitUrl, 'tag_pattern': entry.tagPattern};
// Include `path:` if the registry specifies a git_path (multi-package
// repos like dart_custom_lint).
if (entry.gitPath != null) {
gitBlock['path'] = entry.gitPath!;
}
final newValue = <String, Object>{'git': gitBlock, 'version': entry.version};
_tryUpdate(editor, [sectionKey, depName], newValue);
}
/// Safely attempt a [YamlEditor.update]; log and swallow errors.
void _tryUpdate(YamlEditor editor, List<Object> path, Object value) {
try {
editor.update(path, value);
} on Exception catch (e) {
Logger.warn('yaml_edit: failed to update $path -- $e');
}
}
/// Safely attempt a [YamlEditor.remove]; log and swallow errors.
void _tryRemove(YamlEditor editor, List<Object> path) {
try {
editor.remove(path);
} on Exception catch (_) {
// The key may not exist -- that's fine.
}
}
/// Determine whether [depName] lives under `dependencies` or
/// `dev_dependencies` in the parsed YAML document.
String? _findSectionKey(YamlMap doc, String depName) {
final deps = doc['dependencies'] as YamlMap?;
if (deps != null && deps.containsKey(depName)) return 'dependencies';
final devDeps = doc['dev_dependencies'] as YamlMap?;
if (devDeps != null && devDeps.containsKey(depName)) {
return 'dev_dependencies';
}
return null;
}
}
lib/src/cli/utils/sub_package_utils.dart:378
- The sibling dependency conversion function lacks test coverage. Given the complexity of the YAML manipulation and the critical nature of dependency management in releases, this functionality should have comprehensive unit tests.
Consider adding tests for:
- Converting bare string constraints to git format
- Converting null/workspace dependencies to git format
- Preserving non-sibling dependencies unchanged
- Handling packages without tag_pattern
- Stripping
resolution: workspacefrom pubspecs - Edge cases like self-references and missing pubspec files
The codebase already has good test coverage patterns (see test/ directory), and this critical release functionality would benefit from similar coverage.
static int convertSiblingDepsForRelease({
required String repoRoot,
required String newVersion,
required String effectiveRepo,
required List<Map<String, dynamic>> subPackages,
bool verbose = false,
}) {
// Build sibling lookup: {packageName -> {tag_pattern, path}}
// Only packages WITH tag_pattern participate.
final siblingMap = <String, Map<String, String>>{};
for (final pkg in subPackages) {
final tp = pkg['tag_pattern'] as String?;
if (tp == null) continue;
siblingMap[pkg['name'] as String] = {'tag_pattern': tp, 'path': pkg['path'] as String};
}
if (siblingMap.isEmpty) return 0;
final gitUrl = 'git@github.com:$effectiveRepo.git';
var totalConversions = 0;
for (final pkg in subPackages) {
final pkgName = pkg['name'] as String;
final pubspecFile = File('$repoRoot/${pkg['path']}/pubspec.yaml');
if (!pubspecFile.existsSync()) {
Logger.warn('Sub-package pubspec not found: ${pkg['path']}/pubspec.yaml');
continue;
}
final original = pubspecFile.readAsStringSync();
final editor = YamlEditor(original);
final doc = loadYaml(original) as YamlMap;
var conversions = 0;
// Scan both dependency sections.
for (final sectionKey in ['dependencies', 'dev_dependencies']) {
final section = doc[sectionKey] as YamlMap?;
if (section == null) continue;
for (final key in section.keys) {
final depName = key as String;
if (depName == pkgName) continue; // skip self
final sibling = siblingMap[depName];
if (sibling == null) continue; // not a sibling
final depValue = section[depName];
// Only convert bare string constraints (e.g., "^0.8.2") and
// null values (workspace refs). Map deps are already structured.
if (depValue is! String && depValue != null) continue;
final gitBlock = <String, Object>{
'url': gitUrl,
'tag_pattern': sibling['tag_pattern']!,
'path': sibling['path']!,
};
final newValue = <String, Object>{'git': gitBlock, 'version': '^$newVersion'};
try {
editor.update([sectionKey, depName], newValue);
conversions++;
} on Exception catch (e) {
Logger.warn('yaml_edit: failed to update $sectionKey.$depName -- $e');
}
}
}
// Strip `resolution: workspace` if present.
if (doc.containsKey('resolution')) {
try {
editor.remove(['resolution']);
if (verbose) {
Logger.info('Stripped resolution: workspace from ${pkg['name']}');
}
} on Exception catch (_) {}
}
final updated = editor.toString();
if (updated != original) {
pubspecFile.writeAsStringSync(updated);
totalConversions += conversions;
if (verbose) {
Logger.info('Converted $conversions sibling dep(s) in ${pkg['name']}');
}
}
}
return totalConversions;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Add two new manage_cicd commands that validate pubspec.yaml dependency declarations against the external_workspace_packages.yaml registry. `manage_cicd audit` — validate a single pubspec.yaml: - 7 validation rules: bare deps, wrong org/repo, missing/wrong tag_pattern, stale version, wrong URL format (HTTPS vs SSH) - Auto-detects registry by walking up directory tree - --fix flag for auto-remediation using yaml_edit - --severity filter (error/warning/info) `manage_cicd audit-all` — batch validate all pubspecs: - Recursive discovery with configurable exclusions - Worker pool concurrency (default 4) - Per-file status lines + detailed findings + aggregate summary - --fix applies fixes across all discovered pubspecs Shared engine in lib/src/cli/utils/audit/: - PackageRegistry — loads registry YAML, O(1) lookup by dep name - PubspecAuditor — audit + fix logic with yaml_edit - AuditFinding — severity/category data model ref open-runtime/aot_monorepo#411 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Windows named pipe tests need more than 30 minutes on CI runners when running the full stress test suite (13 large payload tests + 500 sequential RPCs + adversarial scenarios). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…eases Add convertSiblingDepsForRelease() to SubPackageUtils that automatically converts bare sibling dependencies (e.g., `custom_lint_core: ^0.8.2`) to full git dep blocks with url, tag_pattern, path, and version constraint during the release pipeline. Also strips `resolution: workspace` from sub-package pubspecs so standalone consumers can resolve. Integrates into create-release as Step 2c (after version bumping) and Step 5b (per-package tag creation after main tag). Driven by optional `tag_pattern` field in sub_packages config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update release template, CI skeleton, issue-triage template, and workflow generator with latest patterns. Remove deprecated config entries from templates/config.json. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…for per-package tags
89ec243 to
f8c7e09
Compare
## Changelog ## [0.14.0] - 2026-02-24 ### Added - Added `build_runner` feature flag for CI codegen to generate `.g.dart` files in CI instead of relying on local developer builds - Added `manage_cicd audit` and `audit-all` commands for `pubspec.yaml` dependency validation against `external_workspace_packages.yaml` registry (refs open-runtime/aot_monorepo#411) (#22) - Added sibling dependency conversion and per-package tag creation for multi-package releases, automatically converting bare sibling dependencies during release (#22) - Used org-managed runners as default platform definitions for Linux (`ubuntu-x64`/`arm64`) and Windows (`windows-x64`/`arm64`) ### Changed - Updated CI workflow templates and generator with latest patterns and removed deprecated config entries from templates (#22) ### Fixed - Fixed cross-platform hashing in `template_manifest.dart` using pure-Dart crypto and normalized CRLF to LF to prevent data loss on Windows (#23, fixes #5, #8, #10, #11, #12, #13) - Added strict input validation for `dart_sdk`, `feature` keys, `sub_packages`, and hardened triage config `require_file` to prevent path traversal - Replaced shell-based hashing with pure-Dart crypto in autodoc to ensure caching works on macOS, Windows, and minimal CI images - Normalized version input by stripping the leading 'v' prefix and validated SemVer in `create-release` - Fixed passing `allFindings` to `fixPubspec` and added `pub_semver` dependency - Validated YAML before writing and created backups in pubspec auditor to prevent corruption (#22) - Fixed tag existence checks by using `refs/tags/` prefix and handled errors for per-package tags (#22) - Increased test process timeout from 30 to 45 minutes to fix Windows named pipe tests on CI runners (#22) - Added `yaml_edit` dependency required for pubspec auditor ### Security - Added `::add-mask::` token masking before `git config` in all CI workflow locations to prevent Personal Access Token (PAT) leaks in logs, added `fetch-depth: 1` to checkout ... ## Files Modified ``` .../audit/v0.14.0/explore/breaking_changes.json | 4 + .../audit/v0.14.0/explore/commit_analysis.json | 122 +++++++++++++++++++++ .runtime_ci/audit/v0.14.0/explore/pr_data.json | 30 +++++ .runtime_ci/audit/v0.14.0/meta.json | 82 ++++++++++++++ .../v0.14.0/version_analysis/version_bump.json | 1 + .../version_analysis/version_bump_rationale.md | 26 +++++ .../release_notes/v0.14.0/changelog_entry.md | 24 ++++ .../release_notes/v0.14.0/contributors.json | 5 + .../release_notes/v0.14.0/linked_issues.json | 1 + .runtime_ci/release_notes/v0.14.0/release_notes.md | 59 ++++++++++ .../release_notes/v0.14.0/release_notes_body.md | 59 ++++++++++ .runtime_ci/version_bumps/v0.14.0.md | 26 +++++ CHANGELOG.md | 26 +++++ README.md | 14 ++- pubspec.yaml | 2 +- 15 files changed, 475 insertions(+), 6 deletions(-) ``` ## Version Bump Rationale # Version Bump Rationale **Decision**: minor **Why**: The recent commits introduce new capabilities, including a new `build_runner` feature flag for CI codegen and a new `audit-all` CLI command. These are backwards-compatible additive changes that expand the capabilities of the tooling without introducing breaking changes to existing public APIs or workflows, thereby warranting a minor version bump. **Key Changes**: - **New Feature**: Added `build_runner` feature flag for generating `.g.dart` files in CI to avoid environment drift and stale codegen risks. - **New Feature**: Added `audit-all` command to recursively audit all `pubspec.yaml` files under a directory against the package registry. - **Fix/Improvement**: Replaced shell-based file hashing with pure-Dart crypto for better cross-platform (Windows) compatibility. - **Security/CI Hardening**: Masked sensitive tokens in CI logs (`::add-mask::`), reduced checkout depth to 1 for faster executions, and migrated away from hardcoded... ## Contributors - @tsavo-at-pieces --- Automated release by CI/CD pipeline (Gemini CLI + GitHub Actions) Commits since v0.13.0: 15 Generated: 2026-02-24T20:32:27.206709Z


Summary
auditandaudit-allcommands that validate pubspec.yaml files against a package registry — checks git URL format, tag patterns, version constraints, andresolution: workspacestatuscreate-release, bare version constraints between sub-packages (e.g.,custom_lint_core: ^0.8.2) are automatically converted to full git dep blocks withurl,tag_pattern,path, andversion— enabling standalone consumers to resolve dependencies. Also creates per-package annotated tags (e.g.,custom_lint_core-v0.8.3) fortag_patternresolution.New Files
lib/src/cli/commands/audit_command.dartlib/src/cli/commands/audit_all_command.dartlib/src/cli/utils/audit/pubspec_auditor.dartlib/src/cli/utils/audit/package_registry.dartlib/src/cli/utils/audit/audit_finding.dartModified Files
sub_package_utils.dartconvertSiblingDepsForRelease()— converts bare sibling deps to git format + stripsresolution: workspacecreate_release_command.dartmanage_cicd_cli.dartauditandaudit-allcommandspubspec.yamlyaml: ^3.1.3andyaml_edit: ^2.2.1dependenciesRelease Pipeline Flow (with new steps)
Config Schema
Sub-packages gain an optional
tag_patternfield in.runtime_ci/config.json:Test plan
dart analyze— zero issuesresolution: workspaceis stripped from all sub-package pubspecsglob) remain untouchedtag_patterndart_custom_lintwith.runtime_ci/config.jsonand run release dry-run🤖 Generated with Claude Code