diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 56a1e31..4a87757 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -85,17 +85,18 @@ jobs: - uses: actions/checkout@v6.0.2 with: ref: ${{ needs.auto-format.outputs.sha }} + fetch-depth: 1 persist-credentials: false - name: Configure Git for HTTPS with Token shell: bash - env: - GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - 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/" + TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" + echo "::add-mask::${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/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -114,8 +115,8 @@ jobs: - name: Analyze run: | - dart analyze 2>&1 | tee /tmp/analysis.txt - if grep -q "^ error -" /tmp/analysis.txt; then + dart analyze 2>&1 | tee "$RUNNER_TEMP/analysis.txt" + if grep -q "^ error -" "$RUNNER_TEMP/analysis.txt"; then echo "::error::Analysis errors found" exit 1 fi @@ -132,17 +133,18 @@ jobs: - uses: actions/checkout@v6.0.2 with: ref: ${{ needs.auto-format.outputs.sha }} + fetch-depth: 1 persist-credentials: false - name: Configure Git for HTTPS with Token shell: bash - env: - GH_PAT: ${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }} run: | - 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/" + TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" + echo "::add-mask::${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/" - uses: dart-lang/setup-dart@v1.7.1 with: @@ -165,6 +167,16 @@ jobs: - name: Test run: dart test + - name: Upload test artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-artifacts-${{ matrix.platform_id }} + path: | + test/integration/fixtures/bin/ + **/test-results/ + retention-days: 7 + # --- BEGIN USER: post-test --- # --- END USER: post-test --- diff --git a/lib/src/cli/commands/autodoc_command.dart b/lib/src/cli/commands/autodoc_command.dart index c179abf..492810c 100644 --- a/lib/src/cli/commands/autodoc_command.dart +++ b/lib/src/cli/commands/autodoc_command.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:typed_data'; import 'package:args/command_runner.dart'; import 'package:crypto/crypto.dart'; @@ -482,26 +483,24 @@ Write the corrected file to the same path: $absOutputFile filePaths.sort(); - final sink = AccumulatorSink(); - final input = sha256.startChunkedConversion(sink); + final builder = BytesBuilder(copy: false); for (final path in filePaths) { // Include the path name in the digest so renames affect the hash. - input.add(utf8.encode(path)); - input.add(const [0]); + builder.add(utf8.encode(path)); + builder.addByte(0); if (!path.startsWith('missing_dir:')) { try { - input.add(File(path).readAsBytesSync()); + builder.add(File(path).readAsBytesSync()); } catch (e) { Logger.warn('Could not read $path for module hash: $e'); } } - input.add(const [0]); + builder.addByte(0); } - input.close(); - return sink.events.single.toString(); + return sha256.convert(builder.takeBytes()).toString(); } } diff --git a/lib/src/cli/utils/template_manifest.dart b/lib/src/cli/utils/template_manifest.dart index a333d94..7640cf4 100644 --- a/lib/src/cli/utils/template_manifest.dart +++ b/lib/src/cli/utils/template_manifest.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:io'; +import 'package:crypto/crypto.dart'; + import '../../triage/utils/run_context.dart'; /// Represents one template entry from manifest.json. @@ -96,18 +98,12 @@ class TemplateVersionTracker { } /// Compute SHA256 hash of a file's contents. +/// +/// Uses pure Dart [sha256] from `package:crypto` so it works on all platforms +/// (macOS, Linux, Windows) without shelling out to external tools. String computeFileHash(String filePath) { final file = File(filePath); if (!file.existsSync()) return ''; - // Try shasum (macOS) first, then sha256sum (Linux) - final macResult = Process.runSync('sh', ['-c', 'shasum -a 256 "$filePath" | cut -d" " -f1']); - if (macResult.exitCode == 0) { - final hash = (macResult.stdout as String).trim(); - if (hash.isNotEmpty) return hash; - } - final linuxResult = Process.runSync('sh', ['-c', 'sha256sum "$filePath" | cut -d" " -f1']); - if (linuxResult.exitCode == 0) { - return (linuxResult.stdout as String).trim(); - } - return ''; + final bytes = file.readAsBytesSync(); + return sha256.convert(bytes).toString(); } diff --git a/lib/src/cli/utils/workflow_generator.dart b/lib/src/cli/utils/workflow_generator.dart index 5088674..952bb61 100644 --- a/lib/src/cli/utils/workflow_generator.dart +++ b/lib/src/cli/utils/workflow_generator.dart @@ -204,6 +204,11 @@ class WorkflowGenerator { /// `# --- BEGIN USER: ---` /// `# --- END USER: ---` String _preserveUserSections(String rendered, String existing) { + // Normalize CRLF → LF so the regex matches regardless of line-ending style + // (Windows checkouts with core.autocrlf=true produce \r\n). + existing = existing.replaceAll('\r\n', '\n'); + rendered = rendered.replaceAll('\r\n', '\n'); + final sectionPattern = RegExp(r'# --- BEGIN USER: (\S+) ---\n(.*?)# --- END USER: \1 ---', dotAll: true); // Extract user content from existing file diff --git a/templates/github/workflows/ci.skeleton.yaml b/templates/github/workflows/ci.skeleton.yaml index 9afccb1..7aabc78 100644 --- a/templates/github/workflows/ci.skeleton.yaml +++ b/templates/github/workflows/ci.skeleton.yaml @@ -92,30 +92,35 @@ jobs: <%/secrets_list%> <%/has_secrets%> steps: + # ── shared:checkout ── keep in sync with multi_platform ── - uses: actions/checkout@v6.0.2 with: <%#format_check%> ref: ${{ needs.auto-format.outputs.sha }} <%/format_check%> + fetch-depth: 1 persist-credentials: false <%#lfs%> lfs: true <%/lfs%> + # ── shared:git-config ── keep in sync with multi_platform ── - name: Configure Git for HTTPS with Token shell: bash - env: - GH_PAT: ${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }} run: | - 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/" - + TOKEN="${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }}" + echo "::add-mask::${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/" + + # ── shared:dart-setup ── keep in sync with multi_platform ── - uses: dart-lang/setup-dart@v1.7.1 with: sdk: "<%dart_sdk%>" + # ── shared:pub-cache ── keep in sync with multi_platform ── - name: Cache Dart pub dependencies uses: actions/cache@v5.0.3 with: @@ -123,6 +128,7 @@ jobs: key: ${{ runner.os }}-${{ runner.arch }}-dart-pub-${{ hashFiles('**/pubspec.yaml') }} restore-keys: ${{ runner.os }}-${{ runner.arch }}-dart-pub- + # ── shared:proto-setup ── keep in sync with multi_platform ── <%#proto%> - name: Install protoc uses: arduino/setup-protoc@v3.0.0 @@ -130,6 +136,7 @@ jobs: - run: dart pub global activate protoc_plugin 25.0.0 <%/proto%> + # ── shared:pub-get ── keep in sync with multi_platform ── - run: dart pub get env: GIT_LFS_SKIP_SMUDGE: "1" @@ -139,6 +146,7 @@ jobs: run: dart run build_runner build --delete-conflicting-outputs <%/build_runner%> + # ── shared:analysis-cache ── keep in sync with multi_platform ── <%#analysis_cache%> - name: Cache Dart analysis uses: actions/cache@v5.0.3 @@ -148,11 +156,13 @@ jobs: restore-keys: ${{ runner.os }}-${{ runner.arch }}-dart-analysis- <%/analysis_cache%> + # ── shared:proto-verify ── keep in sync with multi_platform ── <%#proto%> - name: Verify proto files run: dart run runtime_ci_tooling:manage_cicd verify-protos <%/proto%> + # ── shared:analyze ── keep in sync with multi_platform ── <%#managed_analyze%> - name: Analyze run: dart run runtime_ci_tooling:manage_cicd analyze @@ -160,20 +170,21 @@ jobs: <%^managed_analyze%> - name: Analyze run: | - dart analyze 2>&1 | tee /tmp/analysis.txt - if grep -q "^ error -" /tmp/analysis.txt; then + dart analyze 2>&1 | tee "$RUNNER_TEMP/analysis.txt" + if grep -q "^ error -" "$RUNNER_TEMP/analysis.txt"; then echo "::error::Analysis errors found" exit 1 fi <%/managed_analyze%> + # ── shared:sub-packages ── keep in sync with multi_platform ── <%#sub_packages%> - name: Analyze (<%name%>) working-directory: <%path%> run: | GIT_LFS_SKIP_SMUDGE=1 dart pub get - dart analyze 2>&1 | tee /tmp/sub_analysis.txt - if grep -q "^ error -" /tmp/sub_analysis.txt; then + dart analyze 2>&1 | tee "$RUNNER_TEMP/sub_analysis.txt" + if grep -q "^ error -" "$RUNNER_TEMP/sub_analysis.txt"; then echo "::error::Errors found in <%name%>" exit 1 fi @@ -182,6 +193,7 @@ jobs: # --- BEGIN USER: pre-test --- # --- END USER: pre-test --- + # ── shared:test ── keep in sync with multi_platform ── <%#managed_test%> - name: Test run: dart run runtime_ci_tooling:manage_cicd test @@ -191,6 +203,16 @@ jobs: run: dart test <%/managed_test%> + - name: Upload test artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-artifacts-<%runner%> + path: | + test/integration/fixtures/bin/ + **/test-results/ + retention-days: 7 + # --- BEGIN USER: post-test --- # --- END USER: post-test --- <%/single_platform%> @@ -206,30 +228,35 @@ jobs: <%/secrets_list%> <%/has_secrets%> steps: + # ── shared:checkout ── keep in sync with single_platform ── - uses: actions/checkout@v6.0.2 with: <%#format_check%> ref: ${{ needs.auto-format.outputs.sha }} <%/format_check%> + fetch-depth: 1 persist-credentials: false <%#lfs%> lfs: true <%/lfs%> + # ── shared:git-config ── keep in sync with single_platform ── - name: Configure Git for HTTPS with Token shell: bash - env: - GH_PAT: ${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }} run: | - 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/" - + TOKEN="${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }}" + echo "::add-mask::${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/" + + # ── shared:dart-setup ── keep in sync with single_platform ── - uses: dart-lang/setup-dart@v1.7.1 with: sdk: "<%dart_sdk%>" + # ── shared:pub-cache ── keep in sync with single_platform ── - name: Cache Dart pub dependencies uses: actions/cache@v5.0.3 with: @@ -237,6 +264,7 @@ jobs: key: ${{ runner.os }}-${{ runner.arch }}-dart-pub-${{ hashFiles('**/pubspec.yaml') }} restore-keys: ${{ runner.os }}-${{ runner.arch }}-dart-pub- + # ── shared:proto-setup ── keep in sync with single_platform ── <%#proto%> - name: Install protoc uses: arduino/setup-protoc@v3.0.0 @@ -244,6 +272,7 @@ jobs: - run: dart pub global activate protoc_plugin 25.0.0 <%/proto%> + # ── shared:pub-get ── keep in sync with single_platform ── - run: dart pub get env: GIT_LFS_SKIP_SMUDGE: "1" @@ -253,20 +282,23 @@ jobs: run: dart run build_runner build --delete-conflicting-outputs <%/build_runner%> + # ── shared:analysis-cache ── keep in sync with single_platform ── <%#analysis_cache%> - name: Cache Dart analysis uses: actions/cache@v5.0.3 with: path: ~/.dartServer - key: ${{ runner.os }}-dart-analysis-${{ hashFiles('**/*.dart', '**/pubspec.yaml') }} - restore-keys: ${{ runner.os }}-dart-analysis- + key: ${{ runner.os }}-${{ runner.arch }}-dart-analysis-${{ hashFiles('**/*.dart', '**/pubspec.yaml') }} + restore-keys: ${{ runner.os }}-${{ runner.arch }}-dart-analysis- <%/analysis_cache%> + # ── shared:proto-verify ── keep in sync with single_platform ── <%#proto%> - name: Verify proto files run: dart run runtime_ci_tooling:manage_cicd verify-protos <%/proto%> + # ── shared:analyze ── keep in sync with single_platform ── <%#managed_analyze%> - name: Analyze run: dart run runtime_ci_tooling:manage_cicd analyze @@ -274,20 +306,21 @@ jobs: <%^managed_analyze%> - name: Analyze run: | - dart analyze 2>&1 | tee /tmp/analysis.txt - if grep -q "^ error -" /tmp/analysis.txt; then + dart analyze 2>&1 | tee "$RUNNER_TEMP/analysis.txt" + if grep -q "^ error -" "$RUNNER_TEMP/analysis.txt"; then echo "::error::Analysis errors found" exit 1 fi <%/managed_analyze%> + # ── shared:sub-packages ── keep in sync with single_platform ── <%#sub_packages%> - name: Analyze (<%name%>) working-directory: <%path%> run: | GIT_LFS_SKIP_SMUDGE=1 dart pub get - dart analyze 2>&1 | tee /tmp/sub_analysis.txt - if grep -q "^ error -" /tmp/sub_analysis.txt; then + dart analyze 2>&1 | tee "$RUNNER_TEMP/sub_analysis.txt" + if grep -q "^ error -" "$RUNNER_TEMP/sub_analysis.txt"; then echo "::error::Errors found in <%name%>" exit 1 fi @@ -308,30 +341,35 @@ jobs: <%/secrets_list%> <%/has_secrets%> steps: + # ── shared:checkout ── keep in sync with single_platform ── - uses: actions/checkout@v6.0.2 with: <%#format_check%> ref: ${{ needs.auto-format.outputs.sha }} <%/format_check%> + fetch-depth: 1 persist-credentials: false <%#lfs%> lfs: true <%/lfs%> + # ── shared:git-config ── keep in sync with single_platform ── - name: Configure Git for HTTPS with Token shell: bash - env: - GH_PAT: ${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }} run: | - 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/" - + TOKEN="${{ secrets.<%pat_secret%> || secrets.GITHUB_TOKEN }}" + echo "::add-mask::${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/" + + # ── shared:dart-setup ── keep in sync with single_platform ── - uses: dart-lang/setup-dart@v1.7.1 with: sdk: "<%dart_sdk%>" + # ── shared:pub-cache ── keep in sync with single_platform ── - name: Cache Dart pub dependencies uses: actions/cache@v5.0.3 with: @@ -339,6 +377,7 @@ jobs: key: ${{ runner.os }}-${{ runner.arch }}-dart-pub-${{ hashFiles('**/pubspec.yaml') }} restore-keys: ${{ runner.os }}-${{ runner.arch }}-dart-pub- + # ── shared:pub-get ── keep in sync with single_platform ── - run: dart pub get env: GIT_LFS_SKIP_SMUDGE: "1" @@ -351,6 +390,7 @@ jobs: # --- BEGIN USER: pre-test --- # --- END USER: pre-test --- + # ── shared:test ── keep in sync with single_platform ── <%#managed_test%> - name: Test run: dart run runtime_ci_tooling:manage_cicd test @@ -360,6 +400,16 @@ jobs: run: dart test <%/managed_test%> + - name: Upload test artifacts on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: test-artifacts-${{ matrix.os }} + path: | + test/integration/fixtures/bin/ + **/test-results/ + retention-days: 7 + # --- BEGIN USER: post-test --- # --- END USER: post-test --- <%/multi_platform%>