diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2b79949..56a1e31 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,4 @@ -# Generated by runtime_ci_tooling v0.13.1 +# Generated by runtime_ci_tooling v0.12.2 # Configured via .runtime_ci/config.json — run 'dart run runtime_ci_tooling:manage_cicd update --workflows' to regenerate. name: CI @@ -89,12 +89,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -126,7 +127,7 @@ jobs: strategy: fail-fast: false matrix: - include: [{"platform_id":"ubuntu-x64","runner":"runtime-ubuntu-24.04-x64-256gb-64core","os_family":"linux","arch":"x64"},{"platform_id":"ubuntu-arm64","runner":"runtime-ubuntu-24.04-arm64-208gb-64core","os_family":"linux","arch":"arm64"},{"platform_id":"macos-arm64","runner":"macos-latest","os_family":"macos","arch":"arm64"},{"platform_id":"macos-x64","runner":"macos-15-intel","os_family":"macos","arch":"x64"},{"platform_id":"windows-x64","runner":"runtime-windows-2025-x64-256gb-64core","os_family":"windows","arch":"x64"},{"platform_id":"windows-arm64","runner":"runtime-windows-11-arm64-208gb-64core","os_family":"windows","arch":"arm64"}] + include: [{"platform_id":"ubuntu-x64","runner":"ubuntu-latest","os_family":"linux","arch":"x64"},{"platform_id":"ubuntu-arm64","runner":"runtime-ubuntu-24.04-arm64-208gb-64core","os_family":"linux","arch":"arm64"},{"platform_id":"macos-arm64","runner":"macos-latest","os_family":"macos","arch":"arm64"},{"platform_id":"macos-x64","runner":"macos-15-large","os_family":"macos","arch":"x64"},{"platform_id":"windows-x64","runner":"windows-latest","os_family":"windows","arch":"x64"},{"platform_id":"windows-arm64","runner":"runtime-windows-11-arm64-208gb-64core","os_family":"windows","arch":"arm64"}] steps: - uses: actions/checkout@v6.0.2 with: @@ -135,12 +136,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: diff --git a/.runtime_ci/template_versions.json b/.runtime_ci/template_versions.json index 2f9fd51..2c22233 100644 --- a/.runtime_ci/template_versions.json +++ b/.runtime_ci/template_versions.json @@ -1,6 +1,6 @@ { - "tooling_version": "0.12.1", - "updated_at": "2026-02-24T16:04:43.651023Z", + "tooling_version": "0.12.2", + "updated_at": "2026-02-24T18:13:35.782823Z", "templates": { "gemini_settings": { "hash": "93983f49dd2f40d2ed245271854946d8916b8f0698ed2cfaf12058305baa0b08", @@ -23,9 +23,9 @@ "updated_at": "2026-02-24T00:59:57.620091Z" }, "workflow_ci": { - "hash": "92c5c82c94e96022d4c7bd7372b1d04273e927223b9f8726a182871d89d1ef77", - "consumer_hash": "1f33e89a0ccffc4ec34838b4e76f9c4d6d7ab5eefb1d9731b554dfc0ed752696", - "updated_at": "2026-02-24T16:04:43.655468Z" + "hash": "c2a764d5e225b04729ba616bd63726da7f5a62d3e0c425fd04e69008900baf52", + "consumer_hash": "bed69d3318a843809cb7ed73821251859deb3be6735726f9503df34378679317", + "updated_at": "2026-02-24T18:13:35.784301Z" }, "workflow_release": { "hash": "326627cf41fdeb6cd61dae2fda98599d5815a34e63e4a8af1aaa8f7ad18435d3", diff --git a/lib/src/cli/commands/audit_all_command.dart b/lib/src/cli/commands/audit_all_command.dart new file mode 100644 index 0000000..09f993b --- /dev/null +++ b/lib/src/cli/commands/audit_all_command.dart @@ -0,0 +1,330 @@ +// ignore_for_file: avoid_print + +import 'dart:async'; +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:path/path.dart' as p; + +import '../manage_cicd_cli.dart'; +import '../utils/audit/audit_finding.dart'; +import '../utils/audit/package_registry.dart'; +import '../utils/audit/pubspec_auditor.dart'; +import '../utils/logger.dart'; + +/// Recursively audit all pubspec.yaml files under a directory against the +/// package registry. +/// +/// Discovers every `pubspec.yaml` in the tree, loads the shared package +/// registry once, and runs the auditor against each pubspec with configurable +/// concurrency. Produces a per-file summary and aggregated totals. +class AuditAllCommand extends Command { + @override + final String name = 'audit-all'; + + @override + final String description = 'Recursively audit all pubspec.yaml files under a directory against the package registry.'; + + AuditAllCommand() { + argParser + ..addOption('path', help: 'Root directory to scan. Defaults to current working directory.') + ..addOption( + 'registry', + help: + 'Path to external_workspace_packages.yaml. ' + 'Auto-detected from monorepo root when omitted.', + ) + ..addFlag('fix', defaultsTo: false, help: 'Automatically fix found issues in-place.') + ..addOption( + 'severity', + defaultsTo: 'error', + allowed: ['error', 'warning', 'info'], + help: 'Minimum severity to report.', + ) + ..addOption('concurrency', defaultsTo: '4', help: 'Max concurrent audit operations.') + ..addMultiOption( + 'exclude', + defaultsTo: ['.dart_tool', 'build', 'node_modules', '.git'], + help: 'Directory names to exclude from scan.', + ); + } + + @override + Future run() async { + final global = ManageCicdCli.parseGlobalOptions(globalResults); + final dryRun = global.dryRun; + final verbose = global.verbose; + + final scanRoot = argResults!['path'] as String? ?? Directory.current.path; + final registryPath = argResults!['registry'] as String?; + final fix = argResults!['fix'] as bool; + final severityFilter = _parseSeverity(argResults!['severity'] as String); + final concurrency = int.tryParse(argResults!['concurrency'] as String) ?? 4; + final excludeDirs = (argResults!['exclude'] as List).toSet(); + + // ── 1. Resolve scan root ────────────────────────────────────────────── + final rootDir = Directory(scanRoot); + if (!rootDir.existsSync()) { + Logger.error('Scan root does not exist: $scanRoot'); + exit(1); + } + + Logger.header('audit-all: Scanning ${rootDir.path}'); + + // ── 2. Resolve registry ─────────────────────────────────────────────── + final resolvedRegistry = registryPath ?? _autoDetectRegistry(scanRoot); + if (resolvedRegistry == null) { + Logger.error( + 'Could not auto-detect external_workspace_packages.yaml. ' + 'Specify --registry explicitly.', + ); + exit(1); + } + + if (!File(resolvedRegistry).existsSync()) { + Logger.error('Registry file not found: $resolvedRegistry'); + exit(1); + } + + if (verbose) { + Logger.info('Registry: $resolvedRegistry'); + } + + // ── 3. Discover pubspec.yaml files ──────────────────────────────────── + final pubspecs = _discoverPubspecs(rootDir, excludeDirs); + + if (pubspecs.isEmpty) { + Logger.warn('No pubspec.yaml files found under ${rootDir.path}'); + return; + } + + Logger.info('Found ${pubspecs.length} pubspec.yaml files\n'); + + // ── 4. Load registry & create auditor ───────────────────────────────── + final registry = PackageRegistry.load(resolvedRegistry); + final auditor = PubspecAuditor(registry: registry); + + // ── 5. Audit with worker pool ───────────────────────────────────────── + final effectiveConcurrency = concurrency.clamp(1, pubspecs.length); + final results = <_AuditResult>[]; + var index = 0; + + Future worker() async { + while (true) { + if (index >= pubspecs.length) break; + final currentIndex = index; + 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)); + } + } + + final workers = >[]; + for (var i = 0; i < effectiveConcurrency; i++) { + workers.add(worker()); + } + await Future.wait(workers); + + // Sort by original discovery order. + results.sort((a, b) => a.index.compareTo(b.index)); + + // ── 6. Per-pubspec status lines ─────────────────────────────────────── + final total = pubspecs.length; + final pubspecsWithFindings = <_AuditResult>[]; + + for (final result in results) { + final relative = p.relative(result.pubspecPath, from: rootDir.path); + final label = '[${result.index + 1}/$total]'; + + if (result.findings.isEmpty) { + Logger.success('$label $relative ${'.' * _dots(relative, label, total)} OK'); + } else { + final errors = result.findings.where((f) => f.severity == AuditSeverity.error).length; + final warnings = result.findings.where((f) => f.severity == AuditSeverity.warning).length; + final infos = result.findings.where((f) => f.severity == AuditSeverity.info).length; + + final parts = []; + if (errors > 0) parts.add('$errors error${errors == 1 ? '' : 's'}'); + if (warnings > 0) parts.add('$warnings warning${warnings == 1 ? '' : 's'}'); + if (infos > 0) parts.add('$infos info'); + + Logger.warn('$label $relative ${'.' * _dots(relative, label, total)} ${parts.join(', ')}'); + pubspecsWithFindings.add(result); + } + } + + // ── 7. Detailed findings ────────────────────────────────────────────── + if (pubspecsWithFindings.isNotEmpty) { + Logger.header('DETAILS (pubspecs with findings):'); + + for (final result in pubspecsWithFindings) { + final relative = p.relative(result.pubspecPath, from: rootDir.path); + Logger.info('\n $relative:'); + + for (final finding in result.findings) { + final severityTag = finding.severity.name.toUpperCase(); + final pad = severityTag.length < 5 ? ' ' * (5 - severityTag.length) : ''; + + switch (finding.severity) { + case AuditSeverity.error: + Logger.error(' $severityTag$pad ${finding.dependencyName}: ${finding.message}'); + case AuditSeverity.warning: + Logger.warn(' $severityTag$pad ${finding.dependencyName}: ${finding.message}'); + case AuditSeverity.info: + Logger.info(' $severityTag$pad ${finding.dependencyName}: ${finding.message}'); + } + } + } + } + + // ── 8. Fix pass ─────────────────────────────────────────────────────── + var fixedCount = 0; + if (fix && !dryRun && pubspecsWithFindings.isNotEmpty) { + Logger.header('Applying fixes...'); + + for (final result in pubspecsWithFindings) { + final relative = p.relative(result.pubspecPath, from: rootDir.path); + final didFix = auditor.fixPubspec(result.pubspecPath, result.findings); + if (didFix) { + fixedCount++; + Logger.success(' Fixed: $relative'); + } else { + Logger.warn(' No auto-fix available: $relative'); + } + } + } else if (fix && dryRun && pubspecsWithFindings.isNotEmpty) { + Logger.header('[DRY-RUN] Would fix ${pubspecsWithFindings.length} pubspec(s)'); + for (final result in pubspecsWithFindings) { + final relative = p.relative(result.pubspecPath, from: rootDir.path); + Logger.info(' $relative'); + } + } + + // ── 9. Summary ──────────────────────────────────────────────────────── + final totalErrors = results.fold( + 0, + (sum, r) => sum + r.findings.where((f) => f.severity == AuditSeverity.error).length, + ); + final totalWarnings = results.fold( + 0, + (sum, r) => sum + r.findings.where((f) => f.severity == AuditSeverity.warning).length, + ); + final totalInfos = results.fold( + 0, + (sum, r) => sum + r.findings.where((f) => f.severity == AuditSeverity.info).length, + ); + final cleanCount = results.where((r) => r.findings.isEmpty).length; + final issueCount = results.where((r) => r.findings.isNotEmpty).length; + + Logger.header('Summary'); + Logger.info(' ${pubspecs.length} pubspecs scanned'); + Logger.info(' $cleanCount clean'); + + if (issueCount > 0) { + final parts = []; + if (totalErrors > 0) parts.add('$totalErrors error${totalErrors == 1 ? '' : 's'}'); + if (totalWarnings > 0) { + parts.add('$totalWarnings warning${totalWarnings == 1 ? '' : 's'}'); + } + if (totalInfos > 0) parts.add('$totalInfos info'); + Logger.warn(' $issueCount with issues (${parts.join(', ')})'); + } + + if (fixedCount > 0) { + Logger.success(' $fixedCount pubspec(s) fixed'); + } + + // ── 10. Exit code ───────────────────────────────────────────────────── + if (totalErrors > 0) { + exit(1); + } + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + /// Recursively discover `pubspec.yaml` files, respecting exclusions. + List _discoverPubspecs(Directory root, Set excludeDirs) { + final results = []; + _scanForPubspecs(root, results, excludeDirs); + results.sort(); + return results; + } + + void _scanForPubspecs(Directory dir, List results, Set excludeDirs) { + List children; + try { + children = dir.listSync(followLinks: false); + } catch (_) { + return; + } + + for (final child in children) { + if (child is File) { + final name = p.basename(child.path); + if (name == 'pubspec.yaml') { + results.add(child.path); + } + // Skip pubspec_overrides.yaml — not relevant for audit. + } else if (child is Directory) { + final name = p.basename(child.path); + // Skip hidden directories and explicitly excluded names. + if (name.startsWith('.') || excludeDirs.contains(name)) { + continue; + } + _scanForPubspecs(child, results, excludeDirs); + } + } + } + + /// Walk up from [startPath] looking for `configs/external_workspace_packages.yaml`. + String? _autoDetectRegistry(String startPath) { + var current = p.canonicalize(startPath); + while (true) { + final candidate = p.join(current, 'configs', 'external_workspace_packages.yaml'); + if (File(candidate).existsSync()) { + return candidate; + } + final parent = p.dirname(current); + if (parent == current) break; + current = parent; + } + return null; + } + + /// Parse a severity string into an [AuditSeverity]. + AuditSeverity _parseSeverity(String value) { + switch (value) { + case 'error': + return AuditSeverity.error; + case 'warning': + return AuditSeverity.warning; + case 'info': + return AuditSeverity.info; + default: + return AuditSeverity.error; + } + } + + /// Compute number of dots for alignment in status output. + int _dots(String relative, String label, int total) { + // Target ~80 chars wide. Adjust padding for the label and path. + const lineWidth = 80; + final used = label.length + 1 + relative.length + 1; + final remaining = lineWidth - used; + return remaining > 2 ? remaining : 2; + } +} + +class _AuditResult { + final int index; + final String pubspecPath; + final List findings; + + const _AuditResult({required this.index, required this.pubspecPath, required this.findings}); +} diff --git a/lib/src/cli/commands/audit_command.dart b/lib/src/cli/commands/audit_command.dart new file mode 100644 index 0000000..e534b5e --- /dev/null +++ b/lib/src/cli/commands/audit_command.dart @@ -0,0 +1,267 @@ +// ignore_for_file: avoid_print + +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:path/path.dart' as p; + +import '../manage_cicd_cli.dart'; +import '../utils/audit/audit_finding.dart'; +import '../utils/audit/package_registry.dart'; +import '../utils/audit/pubspec_auditor.dart'; +import '../utils/logger.dart'; + +/// Audit a pubspec.yaml against the package registry for dependency issues. +/// +/// Loads the external workspace packages registry and validates that all +/// dependencies in the target pubspec.yaml conform to expected git URLs, +/// version constraints, tag patterns, and organizational ownership. +class AuditCommand extends Command { + @override + final String name = 'audit'; + + @override + final String description = 'Audit a pubspec.yaml against the package registry for dependency issues.'; + + AuditCommand() { + argParser + ..addOption( + 'path', + help: + 'Path to a specific pubspec.yaml file. ' + 'Defaults to pubspec.yaml in the current directory.', + ) + ..addOption( + 'registry', + help: + 'Path to external_workspace_packages.yaml. ' + 'Defaults to configs/external_workspace_packages.yaml ' + 'relative to the auto-detected monorepo root.', + ) + ..addFlag('fix', defaultsTo: false, help: 'Automatically fix found issues in-place.') + ..addOption( + 'severity', + defaultsTo: 'error', + allowed: ['error', 'warning', 'info'], + help: 'Minimum severity to report.', + ); + } + + @override + Future run() async { + final global = ManageCicdCli.parseGlobalOptions(globalResults); + final dryRun = global.dryRun; + final verbose = global.verbose; + + final fix = argResults!['fix'] as bool; + final minSeverity = _parseSeverity(argResults!['severity'] as String); + + // ── Resolve pubspec path ────────────────────────────────────────────── + final pubspecPath = _resolvePubspecPath(argResults!['path'] as String?); + if (pubspecPath == null) { + Logger.error( + 'Could not find pubspec.yaml. ' + 'Specify --path or run from a directory containing pubspec.yaml.', + ); + exit(1); + } + + // ── Resolve registry path ───────────────────────────────────────────── + final registryPath = _resolveRegistryPath(explicit: argResults!['registry'] as String?, pubspecPath: pubspecPath); + if (registryPath == null) { + Logger.error( + 'Could not find external_workspace_packages.yaml. ' + 'Specify --registry or run from within the monorepo.', + ); + exit(1); + } + + // ── Load registry and create auditor ────────────────────────────────── + Logger.header('audit: Scanning pubspec.yaml'); + + final PackageRegistry registry; + try { + registry = PackageRegistry.load(registryPath); + } catch (e) { + Logger.error('Failed to load registry at $registryPath: $e'); + exit(1); + } + + final auditor = PubspecAuditor(registry: registry); + + Logger.info(''); + Logger.info( + ' Auditing $pubspecPath against registry ' + '(${registry.entries.length} packages)...', + ); + + // ── Run audit ───────────────────────────────────────────────────────── + final allFindings = auditor.auditPubspec(pubspecPath); + + // Filter by minimum severity. + final findings = allFindings.where((f) => f.severity.index <= minSeverity.index).toList(); + + if (findings.isEmpty) { + Logger.info(''); + Logger.success(' No issues found.'); + return; + } + + // ── Print findings grouped by severity ──────────────────────────────── + _printFindings(findings, verbose: verbose); + + // ── Fix mode ────────────────────────────────────────────────────────── + if (fix) { + final fixableFindings = allFindings.where((f) => f.severity.index <= minSeverity.index).toList(); + + if (dryRun) { + Logger.info(''); + Logger.warn( + ' [DRY-RUN] Would fix ${fixableFindings.length} finding(s) ' + 'in $pubspecPath', + ); + } else { + Logger.info(''); + Logger.info(' Applying fixes...'); + final fixed = auditor.fixPubspec(pubspecPath, fixableFindings); + if (fixed) { + Logger.success(' Fixes applied to $pubspecPath'); + + // Re-audit to check remaining issues. + final remaining = auditor.auditPubspec(pubspecPath); + final remainingErrors = remaining.where((f) => f.severity == AuditSeverity.error).toList(); + if (remainingErrors.isEmpty) { + Logger.success(' All errors resolved.'); + return; + } else { + Logger.warn(' ${remainingErrors.length} error(s) remain after fixes.'); + _printFindings(remaining, verbose: verbose); + } + } else { + Logger.warn(' No fixes were applied (nothing fixable).'); + } + } + } + + // ── Exit code ───────────────────────────────────────────────────────── + final errorCount = findings.where((f) => f.severity == AuditSeverity.error).length; + if (errorCount > 0) { + exit(1); + } + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + /// Resolve the target pubspec.yaml path. + String? _resolvePubspecPath(String? explicit) { + if (explicit != null) { + final file = File(explicit); + if (file.existsSync()) return p.canonicalize(file.path); + // Allow passing a directory — look for pubspec.yaml inside it. + final inDir = File(p.join(explicit, 'pubspec.yaml')); + if (inDir.existsSync()) return p.canonicalize(inDir.path); + return null; + } + + final defaultPath = p.join(Directory.current.path, 'pubspec.yaml'); + if (File(defaultPath).existsSync()) return p.canonicalize(defaultPath); + return null; + } + + /// Resolve the registry YAML path. + /// + /// If not provided explicitly, walk up from the pubspec's parent directory + /// looking for `configs/external_workspace_packages.yaml` — the standard + /// monorepo root detection pattern. + String? _resolveRegistryPath({required String? explicit, required String pubspecPath}) { + if (explicit != null) { + final file = File(explicit); + if (file.existsSync()) return p.canonicalize(file.path); + return null; + } + + // Auto-detect: walk up from pubspec's directory. + var current = Directory(p.dirname(pubspecPath)); + while (true) { + final candidate = File(p.join(current.path, 'configs', 'external_workspace_packages.yaml')); + if (candidate.existsSync()) return p.canonicalize(candidate.path); + + final parent = current.parent; + if (parent.path == current.path) break; // Reached filesystem root. + current = parent; + } + + return null; + } + + /// Parse a severity string into an [AuditSeverity]. + AuditSeverity _parseSeverity(String value) { + switch (value) { + case 'error': + return AuditSeverity.error; + case 'warning': + return AuditSeverity.warning; + case 'info': + return AuditSeverity.info; + default: + return AuditSeverity.error; + } + } + + /// Print findings grouped by severity using appropriate Logger methods. + void _printFindings(List findings, {required bool verbose}) { + final errors = findings.where((f) => f.severity == AuditSeverity.error).toList(); + final warnings = findings.where((f) => f.severity == AuditSeverity.warning).toList(); + final infos = findings.where((f) => f.severity == AuditSeverity.info).toList(); + + Logger.info(''); + + if (errors.isNotEmpty) { + Logger.error(' ERRORS (${errors.length}):'); + for (final finding in errors) { + _printFinding(finding, verbose: verbose, printer: Logger.error); + } + } + + if (warnings.isNotEmpty) { + Logger.warn(' WARNINGS (${warnings.length}):'); + for (final finding in warnings) { + _printFinding(finding, verbose: verbose, printer: Logger.warn); + } + } + + if (infos.isNotEmpty) { + Logger.info(' INFO (${infos.length}):'); + for (final finding in infos) { + _printFinding(finding, verbose: verbose, printer: Logger.info); + } + } + + // Summary line. + final parts = []; + if (errors.isNotEmpty) parts.add('${errors.length} error(s)'); + if (warnings.isNotEmpty) parts.add('${warnings.length} warning(s)'); + if (infos.isNotEmpty) parts.add('${infos.length} info'); + + final pubspecPath = findings.isNotEmpty ? findings.first.pubspecPath : 'pubspec.yaml'; + + Logger.info(''); + Logger.info(' Summary: ${parts.join(', ')} in $pubspecPath'); + } + + /// Print a single finding with optional verbose detail. + void _printFinding(AuditFinding finding, {required bool verbose, required void Function(String) printer}) { + printer(' ${finding.dependencyName}: ${finding.message}'); + if (finding.currentValue != null) { + printer(' Current: ${finding.currentValue}'); + } + if (finding.expectedValue != null) { + printer(' Expected: ${finding.expectedValue}'); + } + if (verbose) { + printer(' Category: ${finding.category.name}'); + printer(' File: ${finding.pubspecPath}'); + } + printer(''); + } +} diff --git a/lib/src/cli/commands/create_release_command.dart b/lib/src/cli/commands/create_release_command.dart index 1a64633..fee7d15 100644 --- a/lib/src/cli/commands/create_release_command.dart +++ b/lib/src/cli/commands/create_release_command.dart @@ -127,6 +127,18 @@ class CreateReleaseCommand extends Command { } } + // Step 2c: Convert sibling deps to git format + strip resolution: workspace + final siblingConversions = SubPackageUtils.convertSiblingDepsForRelease( + repoRoot: repoRoot, + newVersion: newVersion, + effectiveRepo: effectiveRepo, + subPackages: subPackages, + verbose: global.verbose, + ); + if (siblingConversions > 0) { + Logger.success('Converted $siblingConversions sibling dep(s) to git format'); + } + // Step 3: Assemble release notes folder from Stage 3 artifacts final releaseDir = Directory('$repoRoot/$kReleaseNotesDir/v$newVersion'); releaseDir.createSync(recursive: true); @@ -303,7 +315,8 @@ class CreateReleaseCommand extends Command { } // Step 5: Create git tag (verify it doesn't already exist) - final tagCheck = Process.runSync('git', ['rev-parse', tag], workingDirectory: repoRoot); + // Use refs/tags/ prefix to avoid matching branches with the same name. + final tagCheck = Process.runSync('git', ['rev-parse', 'refs/tags/$tag'], workingDirectory: repoRoot); if (tagCheck.exitCode == 0) { Logger.error('Tag $tag already exists. Cannot create release.'); exit(1); @@ -318,6 +331,40 @@ class CreateReleaseCommand extends Command { CiProcessRunner.exec('git', ['push', 'origin', tag], cwd: repoRoot, fatal: true, verbose: global.verbose); Logger.success('Created tag: $tag'); + // Step 5b: Create per-package tags for sub-packages with tag_pattern + final pkgTagsCreated = []; + for (final pkg in subPackages) { + final tp = pkg['tag_pattern'] as String?; + if (tp == null) continue; + final pkgTag = tp.replaceAll('{{version}}', newVersion); + // Use refs/tags/ prefix to avoid matching branches with the same name. + final pkgTagCheck = Process.runSync('git', ['rev-parse', 'refs/tags/$pkgTag'], workingDirectory: repoRoot); + if (pkgTagCheck.exitCode == 0) { + Logger.warn('Per-package tag $pkgTag already exists -- skipping'); + continue; + } + try { + CiProcessRunner.exec( + 'git', + ['tag', '-a', pkgTag, '-m', '${pkg['name']} v$newVersion'], + cwd: repoRoot, + fatal: true, + verbose: global.verbose, + ); + CiProcessRunner.exec('git', ['push', 'origin', pkgTag], cwd: repoRoot, fatal: true, verbose: global.verbose); + pkgTagsCreated.add(pkgTag); + } catch (e) { + Logger.error('Failed to create per-package tag $pkgTag: $e'); + Logger.error(' Recovery: git tag -a $pkgTag -m "${pkg['name']} v$newVersion" && git push origin $pkgTag'); + } + } + if (pkgTagsCreated.isNotEmpty) { + Logger.success( + 'Created ${pkgTagsCreated.length} per-package tag(s): ' + '${pkgTagsCreated.join(', ')}', + ); + } + // Step 6: Create GitHub Release using Stage 3 release notes var releaseBody = ''; final bodyFile = File('${releaseDir.path}/release_notes.md'); @@ -359,6 +406,8 @@ class CreateReleaseCommand extends Command { | Repository | `$effectiveRepo` | | pubspec.yaml | Bumped to `$newVersion` | ${subPackages.isNotEmpty ? '| Sub-packages | ${subPackages.map((p) => '`${p['name']}`').join(', ')} bumped to `$newVersion` |' : ''} +${siblingConversions > 0 ? '| Sibling Deps | $siblingConversions dep(s) converted to git format |' : ''} +${pkgTagsCreated.isNotEmpty ? '| Per-Package Tags | ${pkgTagsCreated.map((t) => '`$t`').join(', ')} |' : ''} ### Links diff --git a/lib/src/cli/commands/test_command.dart b/lib/src/cli/commands/test_command.dart index 79bdeeb..18c73ed 100644 --- a/lib/src/cli/commands/test_command.dart +++ b/lib/src/cli/commands/test_command.dart @@ -26,7 +26,7 @@ class TestCommand extends Command { Logger.header('Running dart test'); - const processTimeout = Duration(minutes: 30); + const processTimeout = Duration(minutes: 45); final failures = []; // Skip gracefully if no test/ directory exists @@ -44,7 +44,7 @@ class TestCommand extends Command { mode: ProcessStartMode.inheritStdio, ); - // Process-level timeout: kill the test process if it exceeds 30 minutes. + // Process-level timeout: kill the test process if it exceeds 45 minutes. // Individual test timeouts should catch hangs, but this is a safety net // for cases where the test process itself doesn't exit (e.g., leaked // isolates, open sockets keeping the event loop alive). diff --git a/lib/src/cli/manage_cicd_cli.dart b/lib/src/cli/manage_cicd_cli.dart index cd4b3c1..84471c8 100644 --- a/lib/src/cli/manage_cicd_cli.dart +++ b/lib/src/cli/manage_cicd_cli.dart @@ -3,6 +3,8 @@ import 'package:args/command_runner.dart'; import 'commands/analyze_command.dart'; import 'commands/archive_run_command.dart'; +import 'commands/audit_all_command.dart'; +import 'commands/audit_command.dart'; import 'commands/autodoc_command.dart'; import 'commands/compose_command.dart'; import 'commands/configure_mcp_command.dart'; @@ -51,6 +53,8 @@ class ManageCicdCli extends CommandRunner { void _addCommands() { addCommand(AnalyzeCommand()); addCommand(ArchiveRunCommand()); + addCommand(AuditAllCommand()); + addCommand(AuditCommand()); addCommand(AutodocCommand()); addCommand(ComposeCommand()); addCommand(ConfigureMcpCommand()); diff --git a/lib/src/cli/utils/audit/pubspec_auditor.dart b/lib/src/cli/utils/audit/pubspec_auditor.dart index 33628ea..1e2e088 100644 --- a/lib/src/cli/utils/audit/pubspec_auditor.dart +++ b/lib/src/cli/utils/audit/pubspec_auditor.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:pub_semver/pub_semver.dart'; import 'package:yaml/yaml.dart'; import 'package:yaml_edit/yaml_edit.dart'; @@ -13,6 +14,21 @@ 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)?$'); +bool _constraintsEquivalent(String left, String right) { + final a = left.trim(); + final b = right.trim(); + if (a == b) return true; + try { + final ca = VersionConstraint.parse(a); + final cb = VersionConstraint.parse(b); + // Treat equivalent ranges as "matching" even if rendered differently. + return ca.allowsAll(cb) && cb.allowsAll(ca); + } catch (_) { + // Fall back to a whitespace-insensitive compare. + return a.replaceAll(RegExp(r'\s+'), '') == b.replaceAll(RegExp(r'\s+'), ''); + } +} + /// Audits pubspec.yaml dependency declarations against a [PackageRegistry]. /// /// For every dependency that matches a registry entry, the auditor checks: @@ -127,7 +143,7 @@ class PubspecAuditor { ); // Also flag stale version if the bare constraint doesn't match. - if (value != entry.version) { + if (!_constraintsEquivalent(value, entry.version)) { findings.add( AuditFinding( pubspecPath: pubspecPath, @@ -311,8 +327,8 @@ class PubspecAuditor { } // --- Rule 6: stale_version ----------------------------------------------- - final versionValue = depMap['version'] as String?; - if (versionValue != null && versionValue != entry.version) { + final versionValue = depMap['version']?.toString(); + if (versionValue != null && !_constraintsEquivalent(versionValue, entry.version)) { findings.add( AuditFinding( pubspecPath: pubspecPath, @@ -324,12 +340,12 @@ class PubspecAuditor { expectedValue: entry.version, ), ); - } else if (versionValue == null) { + } else if (versionValue == null || versionValue.trim().isEmpty) { findings.add( AuditFinding( pubspecPath: pubspecPath, dependencyName: depName, - severity: AuditSeverity.warning, + severity: AuditSeverity.error, category: AuditCategory.staleVersion, message: 'No version constraint specified -- should have version: ' @@ -429,10 +445,37 @@ class PubspecAuditor { final updated = editor.toString(); if (updated == original) return false; + // Guard against producing an invalid pubspec.yaml. + try { + loadYaml(updated); + } on YamlException catch (e) { + Logger.error('Refusing to write invalid YAML for $pubspecPath: $e'); + return false; + } + + _writeBackup(pubspecPath, original); file.writeAsStringSync(updated); return true; } + void _writeBackup(String pubspecPath, String originalContent) { + // Keep the backup next to the file so developers can revert quickly. + // Use a stable name, but avoid overwriting an existing backup. + final base = '$pubspecPath.runtime_ci_tooling.bak'; + var backupPath = base; + if (File(base).existsSync()) { + final ts = DateTime.now().toUtc().toIso8601String().replaceAll(':', '-').replaceAll('.', '-'); + backupPath = '$pubspecPath.runtime_ci_tooling.$ts.bak'; + } + + try { + File(backupPath).writeAsStringSync(originalContent); + Logger.info(' Backup written: $backupPath'); + } catch (e) { + Logger.warn(' Could not write backup for $pubspecPath: $e'); + } + } + /// Rewrite a dependency to the full git format using registry values. void _rewriteToFullGitDep(YamlEditor editor, String sectionKey, String depName, RegistryEntry entry) { final gitBlock = {'url': entry.expectedGitUrl, 'tag_pattern': entry.tagPattern}; diff --git a/lib/src/cli/utils/sub_package_utils.dart b/lib/src/cli/utils/sub_package_utils.dart index 13b1bee..00e0ece 100644 --- a/lib/src/cli/utils/sub_package_utils.dart +++ b/lib/src/cli/utils/sub_package_utils.dart @@ -1,5 +1,8 @@ import 'dart:io'; +import 'package:yaml/yaml.dart'; +import 'package:yaml_edit/yaml_edit.dart'; + import 'logger.dart'; import 'process_runner.dart'; import 'workflow_generator.dart'; @@ -277,6 +280,103 @@ Rules: return subPackages; } + /// Convert bare sibling dependencies to git format and strip + /// `resolution: workspace`. + /// + /// For each sub-package pubspec, any dependency whose name matches another + /// sub-package (with a `tag_pattern`) is rewritten from a bare version + /// constraint to a full git dep block with `url`, `tag_pattern`, `path`, + /// and `version: ^newVersion`. + /// + /// Returns the total number of dependency conversions across all pubspecs. + static int convertSiblingDepsForRelease({ + required String repoRoot, + required String newVersion, + required String effectiveRepo, + required List> subPackages, + bool verbose = false, + }) { + // Build sibling lookup: {packageName -> {tag_pattern, path}} + // Only packages WITH tag_pattern participate. + final siblingMap = >{}; + 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 = { + 'url': gitUrl, + 'tag_pattern': sibling['tag_pattern']!, + 'path': sibling['path']!, + }; + final newValue = {'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; + } + /// Truncate a string to a maximum length, appending an indicator. static String _truncate(String input, int maxChars) { if (input.length <= maxChars) return input; diff --git a/lib/src/cli/utils/version_detection.dart b/lib/src/cli/utils/version_detection.dart index d23ce87..655d6f2 100644 --- a/lib/src/cli/utils/version_detection.dart +++ b/lib/src/cli/utils/version_detection.dart @@ -13,8 +13,11 @@ const String kGeminiProModel = 'gemini-3.1-pro-preview'; /// Version detection and semantic versioning utilities. abstract final class VersionDetection { /// Detect the previous release tag from git history. - static String detectPrevTag(String repoRoot, {bool verbose = false}) { - final result = CiProcessRunner.runSync( + /// + /// When [excludeTag] is provided (e.g. the tag about to be created), the + /// method returns the second-newest tag instead so the diff range is correct. + static String detectPrevTag(String repoRoot, {String? excludeTag, bool verbose = false}) { + var result = CiProcessRunner.runSync( "git tag -l 'v*' --sort=-version:refname | head -1", repoRoot, verbose: verbose, @@ -23,6 +26,17 @@ abstract final class VersionDetection { // No tags yet -- use the first commit return CiProcessRunner.runSync('git rev-list --max-parents=0 HEAD | head -1', repoRoot, verbose: verbose); } + // If the newest tag matches excludeTag, pick the second-newest. + if (excludeTag != null && result == excludeTag) { + result = CiProcessRunner.runSync( + "git tag -l 'v*' --sort=-version:refname | head -2 | tail -1", + repoRoot, + verbose: verbose, + ); + if (result.isEmpty || result == excludeTag) { + return CiProcessRunner.runSync('git rev-list --max-parents=0 HEAD | head -1', repoRoot, verbose: verbose); + } + } return result; } @@ -133,7 +147,7 @@ abstract final class VersionDetection { final promptPath = '${versionAnalysisDir.path}/prompt.txt'; File(promptPath).writeAsStringSync(prompt); final geminiResult = CiProcessRunner.runSync( - 'cat $promptPath | gemini ' + 'cat "$promptPath" | gemini ' '-o json --yolo ' '-m $kGeminiProModel ' "--allowed-tools 'run_shell_command(git),run_shell_command(gh)' " @@ -153,7 +167,7 @@ abstract final class VersionDetection { try { final bumpData = json.decode(File(bumpJsonPath).readAsStringSync()) as Map; final rawBump = (bumpData['bump'] as String?)?.trim().toLowerCase().replaceAll(RegExp(r'[^a-z]'), ''); - if (rawBump == 'major' || rawBump == 'minor' || rawBump == 'patch' || rawBump == 'none') { + if (rawBump == 'major' || rawBump == 'minor' || rawBump == 'patch') { Logger.info(' Gemini analysis: $rawBump (overriding regex: $bump)'); bump = rawBump!; } else { @@ -202,14 +216,27 @@ abstract final class VersionDetection { /// Compare two semver versions. Returns negative if a < b, 0 if equal, /// positive if a > b. + /// + /// Handles pre-release suffixes: `1.0.0-beta.1 < 1.0.0` per semver spec. static int compareVersions(String a, String b) { - final aParts = a.split('.').map((p) => int.tryParse(p) ?? 0).toList(); - final bParts = b.split('.').map((p) => int.tryParse(p) ?? 0).toList(); + // Split off pre-release suffix (e.g. "1.0.0-beta.1" -> "1.0.0", "beta.1") + final aSplit = a.split('-'); + final bSplit = b.split('-'); + final aBase = aSplit.first; + final bBase = bSplit.first; + final aPreRelease = aSplit.length > 1 ? aSplit.sublist(1).join('-') : null; + final bPreRelease = bSplit.length > 1 ? bSplit.sublist(1).join('-') : null; + + final aParts = aBase.split('.').map((p) => int.tryParse(p) ?? 0).toList(); + final bParts = bBase.split('.').map((p) => int.tryParse(p) ?? 0).toList(); for (var i = 0; i < 3; i++) { final av = i < aParts.length ? aParts[i] : 0; final bv = i < bParts.length ? bParts[i] : 0; if (av != bv) return av - bv; } + // When base versions are equal, pre-release < release per semver spec. + if (aPreRelease != null && bPreRelease == null) return -1; + if (aPreRelease == null && bPreRelease != null) return 1; return 0; } } diff --git a/lib/src/cli/utils/workflow_generator.dart b/lib/src/cli/utils/workflow_generator.dart index f7ca95e..9f3016c 100644 --- a/lib/src/cli/utils/workflow_generator.dart +++ b/lib/src/cli/utils/workflow_generator.dart @@ -31,7 +31,7 @@ const _platformDefinitions = { // macOS — standard GitHub-hosted runners (no org-managed equivalents) 'macos': _PlatformDefinition(osFamily: 'macos', arch: 'arm64', runner: 'macos-latest'), 'macos-arm64': _PlatformDefinition(osFamily: 'macos', arch: 'arm64', runner: 'macos-latest'), - 'macos-x64': _PlatformDefinition(osFamily: 'macos', arch: 'x64', runner: 'macos-15-intel'), + 'macos-x64': _PlatformDefinition(osFamily: 'macos', arch: 'x64', runner: 'macos-15-large'), // Windows — org-managed runners 'windows': _PlatformDefinition(osFamily: 'windows', arch: 'x64', runner: 'runtime-windows-2025-x64-256gb-64core'), diff --git a/pubspec.yaml b/pubspec.yaml index 008ab38..4de44ea 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,9 +12,9 @@ executables: environment: sdk: ^3.9.0 -# NOTE: `resolution: workspace` is intentionally absent here so this package -# resolves correctly in its own standalone CI pipeline. Add it only in your -# local monorepo workspace (never commit it to this repo). +# NOTE: `resolution: workspace` is intentionally absent in this repo so this package +# resolves correctly in its own standalone CI pipeline. In your local monorepo +# workspace you may add it, but never commit it back here. dependencies: args: ^2.7.0 diff --git a/templates/config.json b/templates/config.json index a2c2839..060ce3f 100644 --- a/templates/config.json +++ b/templates/config.json @@ -78,10 +78,10 @@ "sub_packages": [], "_comment_platforms": "Optional: CI platform matrix. When 2+ entries are provided, CI splits into analyze + matrix test jobs.", "platforms": ["ubuntu-x64", "ubuntu-arm64", "macos-arm64", "macos-x64", "windows-x64", "windows-arm64"], - "_comment_runner_overrides": "Optional: override default org-managed runners. Defaults: ubuntu-x64=runtime-ubuntu-24.04-x64-256gb-64core, ubuntu-arm64=runtime-ubuntu-24.04-arm64-208gb-64core, windows-x64=runtime-windows-2025-x64-256gb-64core, windows-arm64=runtime-windows-11-arm64-208gb-64core, macos-arm64=macos-latest, macos-x64=macos-15-intel. Only specify overrides if you need larger runners.", + "_comment_runner_overrides": "Optional: override platform IDs to custom runs-on labels (e.g. org-managed GitHub-hosted runners). Keys must match ci.platforms entries.", "runner_overrides": { - "ubuntu-x64": "runtime-ubuntu-24.04-x64-640gb-160core", - "windows-x64": "runtime-windows-2025-x64-640gb-160core" + "ubuntu-arm64": "runtime-ubuntu-24.04-arm64-208gb-64core", + "windows-arm64": "runtime-windows-11-arm64-208gb-64core" } } } diff --git a/templates/github/workflows/ci.skeleton.yaml b/templates/github/workflows/ci.skeleton.yaml index 2954b31..0c9c94c 100644 --- a/templates/github/workflows/ci.skeleton.yaml +++ b/templates/github/workflows/ci.skeleton.yaml @@ -104,12 +104,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -212,12 +213,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -308,12 +310,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: diff --git a/templates/github/workflows/issue-triage.template.yaml b/templates/github/workflows/issue-triage.template.yaml index 80c74f4..3924e03 100644 --- a/templates/github/workflows/issue-triage.template.yaml +++ b/templates/github/workflows/issue-triage.template.yaml @@ -50,12 +50,13 @@ jobs: - name: Configure Git for HTTPS with Token if: steps.trigger.outputs.run == 'true' shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 if: steps.trigger.outputs.run == 'true' @@ -93,4 +94,5 @@ jobs: env: GEMINI_API_KEY: ${{ secrets.CICD_GEMINI_API_KEY_OPEN_RUNTIME }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: dart run runtime_ci_tooling:triage_cli ${{ github.event.issue.number }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + run: dart run runtime_ci_tooling:triage_cli "$ISSUE_NUMBER" diff --git a/templates/github/workflows/release.template.yaml b/templates/github/workflows/release.template.yaml index 2f1b797..1f693b9 100644 --- a/templates/github/workflows/release.template.yaml +++ b/templates/github/workflows/release.template.yaml @@ -69,12 +69,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -142,12 +143,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -180,17 +182,19 @@ jobs: env: GEMINI_API_KEY: ${{ secrets.CICD_GEMINI_API_KEY_OPEN_RUNTIME }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PREV_TAG: ${{ needs.determine-version.outputs.prev_tag }} + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} run: | dart run runtime_ci_tooling:manage_cicd triage pre-release \ - --prev-tag "${{ needs.determine-version.outputs.prev_tag }}" \ - --version "${{ needs.determine-version.outputs.new_version }}" + --prev-tag "$PREV_TAG" \ + --version "$NEW_VERSION" # Find manifest from .runtime_ci/runs/ audit trail MANIFEST=$(find .runtime_ci/runs -name "issue_manifest.json" -type f 2>/dev/null | sort -r | head -1) if [ -n "$MANIFEST" ]; then cp "$MANIFEST" /tmp/issue_manifest.json else - echo '{"version":"${{ needs.determine-version.outputs.new_version }}","github_issues":[],"sentry_issues":[],"cross_repo_issues":[]}' > /tmp/issue_manifest.json + echo "{\"version\":\"${NEW_VERSION}\",\"github_issues\":[],\"sentry_issues\":[],\"cross_repo_issues\":[]}" > /tmp/issue_manifest.json fi - uses: actions/upload-artifact@v6.0.0 @@ -223,12 +227,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -266,10 +271,12 @@ jobs: env: GEMINI_API_KEY: ${{ secrets.CICD_GEMINI_API_KEY_OPEN_RUNTIME }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PREV_TAG: ${{ needs.determine-version.outputs.prev_tag }} + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} run: | dart run runtime_ci_tooling:manage_cicd explore \ - --prev-tag "${{ needs.determine-version.outputs.prev_tag }}" \ - --version "${{ needs.determine-version.outputs.new_version }}" + --prev-tag "$PREV_TAG" \ + --version "$NEW_VERSION" - name: Create fallback stage1 artifacts if missing run: | @@ -311,12 +318,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -359,19 +367,23 @@ jobs: env: GEMINI_API_KEY: ${{ secrets.CICD_GEMINI_API_KEY_OPEN_RUNTIME }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PREV_TAG: ${{ needs.determine-version.outputs.prev_tag }} + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} run: | dart run runtime_ci_tooling:manage_cicd compose \ - --prev-tag "${{ needs.determine-version.outputs.prev_tag }}" \ - --version "${{ needs.determine-version.outputs.new_version }}" + --prev-tag "$PREV_TAG" \ + --version "$NEW_VERSION" - name: Documentation update env: GEMINI_API_KEY: ${{ secrets.CICD_GEMINI_API_KEY_OPEN_RUNTIME }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PREV_TAG: ${{ needs.determine-version.outputs.prev_tag }} + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} run: | dart run runtime_ci_tooling:manage_cicd documentation \ - --prev-tag "${{ needs.determine-version.outputs.prev_tag }}" \ - --version "${{ needs.determine-version.outputs.new_version }}" + --prev-tag "$PREV_TAG" \ + --version "$NEW_VERSION" - uses: actions/upload-artifact@v6.0.0 with: @@ -410,12 +422,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - name: Set up Dart uses: dart-lang/setup-dart@v1.7.1 @@ -510,12 +523,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -585,30 +599,34 @@ jobs: env: GEMINI_API_KEY: ${{ secrets.CICD_GEMINI_API_KEY_OPEN_RUNTIME }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PREV_TAG: ${{ needs.determine-version.outputs.prev_tag }} + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} run: | dart run runtime_ci_tooling:manage_cicd release-notes \ - --prev-tag "${{ needs.determine-version.outputs.prev_tag }}" \ - --version "${{ needs.determine-version.outputs.new_version }}" + --prev-tag "$PREV_TAG" \ + --version "$NEW_VERSION" # Consolidate all release notes files under .runtime_ci/release_notes/ before upload. # Mixing relative and absolute paths in upload-artifact causes path # resolution issues. Keep everything under one root. - name: Consolidate release notes + env: + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} run: | - VERSION="${{ needs.determine-version.outputs.new_version }}" - mkdir -p ".runtime_ci/release_notes/v${VERSION}" - cp /tmp/release_notes_body.md ".runtime_ci/release_notes/v${VERSION}/" 2>/dev/null || true - cp /tmp/migration_guide.md ".runtime_ci/release_notes/v${VERSION}/" 2>/dev/null || true - echo "Contents of .runtime_ci/release_notes/v${VERSION}/:" - ls -la ".runtime_ci/release_notes/v${VERSION}/" 2>/dev/null || echo "(empty)" + mkdir -p ".runtime_ci/release_notes/v${NEW_VERSION}" + cp /tmp/release_notes_body.md ".runtime_ci/release_notes/v${NEW_VERSION}/" 2>/dev/null || true + cp /tmp/migration_guide.md ".runtime_ci/release_notes/v${NEW_VERSION}/" 2>/dev/null || true + echo "Contents of .runtime_ci/release_notes/v${NEW_VERSION}/:" + ls -la ".runtime_ci/release_notes/v${NEW_VERSION}/" 2>/dev/null || echo "(empty)" - name: Ensure release notes artifact is non-empty shell: bash + env: + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} run: | - VERSION="${{ needs.determine-version.outputs.new_version }}" - mkdir -p ".runtime_ci/release_notes/v${VERSION}" - if [ ! -f ".runtime_ci/release_notes/v${VERSION}/release_notes_body.md" ]; then - echo "Release notes unavailable for v${VERSION}." > ".runtime_ci/release_notes/v${VERSION}/release_notes_body.md" + mkdir -p ".runtime_ci/release_notes/v${NEW_VERSION}" + if [ ! -f ".runtime_ci/release_notes/v${NEW_VERSION}/release_notes_body.md" ]; then + echo "Release notes unavailable for v${NEW_VERSION}." > ".runtime_ci/release_notes/v${NEW_VERSION}/release_notes_body.md" echo "Created fallback release_notes_body.md" fi @@ -646,12 +664,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -693,34 +712,35 @@ jobs: merge-multiple: false - name: Prepare artifacts + env: + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} run: | - VERSION="${{ needs.determine-version.outputs.new_version }}" - mkdir -p ./artifacts ./.runtime_ci/version_bumps "./.runtime_ci/release_notes/v${VERSION}" + mkdir -p ./artifacts ./.runtime_ci/version_bumps "./.runtime_ci/release_notes/v${NEW_VERSION}" # Stage 3 release notes: downloaded artifact has release_notes/ root # so files land at ./release-notes-artifacts/vX.X.X/release_notes.md - if [ -d "./release-notes-artifacts/v${VERSION}" ]; then - cp -r "./release-notes-artifacts/v${VERSION}/"* "./.runtime_ci/release_notes/v${VERSION}/" 2>/dev/null || true - echo "Copied Stage 3 artifacts from release-notes-artifacts/v${VERSION}/" + if [ -d "./release-notes-artifacts/v${NEW_VERSION}" ]; then + cp -r "./release-notes-artifacts/v${NEW_VERSION}/"* "./.runtime_ci/release_notes/v${NEW_VERSION}/" 2>/dev/null || true + echo "Copied Stage 3 artifacts from release-notes-artifacts/v${NEW_VERSION}/" elif [ -d "./release-notes-artifacts" ]; then # Fallback: search recursively for release_notes.md FOUND=$(find ./release-notes-artifacts -name "release_notes.md" -type f 2>/dev/null | head -1) if [ -n "$FOUND" ]; then - cp "$(dirname "$FOUND")"/* "./.runtime_ci/release_notes/v${VERSION}/" 2>/dev/null || true + cp "$(dirname "$FOUND")"/* "./.runtime_ci/release_notes/v${NEW_VERSION}/" 2>/dev/null || true echo "Found release notes via recursive search: $FOUND" fi fi # Copy release_notes_body.md to /tmp/ for Dart script - if [ -f "./.runtime_ci/release_notes/v${VERSION}/release_notes_body.md" ]; then - cp "./.runtime_ci/release_notes/v${VERSION}/release_notes_body.md" /tmp/release_notes_body.md - elif [ -f "./.runtime_ci/release_notes/v${VERSION}/release_notes.md" ]; then - cp "./.runtime_ci/release_notes/v${VERSION}/release_notes.md" /tmp/release_notes_body.md + if [ -f "./.runtime_ci/release_notes/v${NEW_VERSION}/release_notes_body.md" ]; then + cp "./.runtime_ci/release_notes/v${NEW_VERSION}/release_notes_body.md" /tmp/release_notes_body.md + elif [ -f "./.runtime_ci/release_notes/v${NEW_VERSION}/release_notes.md" ]; then + cp "./.runtime_ci/release_notes/v${NEW_VERSION}/release_notes.md" /tmp/release_notes_body.md fi # List what we found echo "Release notes contents:" - ls -la "./.runtime_ci/release_notes/v${VERSION}/" 2>/dev/null || echo "(empty)" + ls -la "./.runtime_ci/release_notes/v${NEW_VERSION}/" 2>/dev/null || echo "(empty)" # Merge all downloaded audit trail artifacts from different jobs into # a single .runtime_ci/runs/ directory so archive-run can find them. @@ -735,20 +755,25 @@ jobs: # the release. This replaces the old post-release archive that could # never work because .runtime_ci/runs/ didn't exist on the fresh runner. - name: Archive audit trail + env: + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} run: | dart run runtime_ci_tooling:manage_cicd archive-run \ - --version "${{ needs.determine-version.outputs.new_version }}" + --version "$NEW_VERSION" - name: Create release env: GH_TOKEN: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN }} GITHUB_TOKEN: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN }} + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} + PREV_TAG: ${{ needs.determine-version.outputs.prev_tag }} + REPO_NAME: ${{ github.repository }} run: | dart run runtime_ci_tooling:manage_cicd create-release \ - --version "${{ needs.determine-version.outputs.new_version }}" \ - --prev-tag "${{ needs.determine-version.outputs.prev_tag }}" \ + --version "$NEW_VERSION" \ + --prev-tag "$PREV_TAG" \ --artifacts-dir ./artifacts \ - --repo "${{ github.repository }}" + --repo "$REPO_NAME" # ============================================================================ # Job 7: Post-Release Triage @@ -765,12 +790,13 @@ jobs: - name: Configure Git for HTTPS with Token shell: bash + env: + GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:" - git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/" - git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" - git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "git@github.com:" + git config --global url."https://x-access-token:${GH_PAT}@github.com/".insteadOf "ssh://git@github.com/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/" + git config --global url."https://x-access-token:${GH_PAT}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -808,9 +834,11 @@ jobs: env: GEMINI_API_KEY: ${{ secrets.CICD_GEMINI_API_KEY_OPEN_RUNTIME }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NEW_VERSION: ${{ needs.determine-version.outputs.new_version }} + REPO_NAME: ${{ github.repository }} run: | dart run runtime_ci_tooling:manage_cicd triage post-release \ - --version "${{ needs.determine-version.outputs.new_version }}" \ - --release-tag "v${{ needs.determine-version.outputs.new_version }}" \ - --release-url "https://github.com/${{ github.repository }}/releases/tag/v${{ needs.determine-version.outputs.new_version }}" \ + --version "$NEW_VERSION" \ + --release-tag "v${NEW_VERSION}" \ + --release-url "https://github.com/${REPO_NAME}/releases/tag/v${NEW_VERSION}" \ --manifest /tmp/issue_manifest.json