diff --git a/.archgate/adrs/ARCH-013-version-synchronization.rules.ts b/.archgate/adrs/ARCH-013-version-synchronization.rules.ts index a322b15c..36ecce34 100644 --- a/.archgate/adrs/ARCH-013-version-synchronization.rules.ts +++ b/.archgate/adrs/ARCH-013-version-synchronization.rules.ts @@ -59,6 +59,11 @@ export default { pattern: /([^<]+)<\/Version>/u, label: "NuGet .csproj", }, + { + file: "shims/nuget/Archgate.Tool/Program.cs", + pattern: /private const string Version = "([^"]+)"/u, + label: "NuGet Program.cs", + }, { file: "shims/go/internal/shim/shim.go", pattern: /const Version = "([^"]+)"/u, @@ -70,6 +75,11 @@ export default { /archgate-cli<\/artifactId>\s*([^<]+)<\/version>/u, label: "Maven pom.xml", }, + { + file: "shims/maven/src/main/java/dev/archgate/cli/Shim.java", + pattern: /private static final String VERSION = "([^"]+)"/u, + label: "Maven Shim.java", + }, { file: "shims/rubygem/lib/archgate/version.rb", pattern: /VERSION\s*=\s*"([^"]+)"/u, diff --git a/.simple-release.js b/.simple-release.js index 36d8d10a..df59a555 100644 --- a/.simple-release.js +++ b/.simple-release.js @@ -70,6 +70,20 @@ class ArchgateProject extends NpmProject { } } + // NuGet: Program.cs version constant (download URL) + const csProgramPath = "shims/nuget/Archgate.Tool/Program.cs"; + if (existsSync(csProgramPath)) { + const content = readFileSync(csProgramPath, "utf8"); + const updated = content.replace( + /private const string Version = "[^"]+"/u, + `private const string Version = "${version}"` + ); + if (updated !== content) { + writeFileSync(csProgramPath, updated); + this.changedFiles.push(csProgramPath); + } + } + // Go: shim.go version constant const goShimPath = "shims/go/internal/shim/shim.go"; if (existsSync(goShimPath)) { @@ -98,6 +112,21 @@ class ArchgateProject extends NpmProject { } } + // Maven: Shim.java version constant (download URL) + const javaShimPath = + "shims/maven/src/main/java/dev/archgate/cli/Shim.java"; + if (existsSync(javaShimPath)) { + const content = readFileSync(javaShimPath, "utf8"); + const updated = content.replace( + /private static final String VERSION = "[^"]+"/u, + `private static final String VERSION = "${version}"` + ); + if (updated !== content) { + writeFileSync(javaShimPath, updated); + this.changedFiles.push(javaShimPath); + } + } + // RubyGem: version.rb const rubyVersionPath = "shims/rubygem/lib/archgate/version.rb"; if (existsSync(rubyVersionPath)) { diff --git a/docs/public/llms-full.txt b/docs/public/llms-full.txt index 3a3448e2..2447fbff 100644 --- a/docs/public/llms-full.txt +++ b/docs/public/llms-full.txt @@ -4083,10 +4083,10 @@ Re-running `archgate init` does **not** overwrite a manually configured `baseBra ``` .archgate/ adrs/ - ARCH-001-example.md # Example ADR - ARCH-001-example.rules.ts # Example rules file + GEN-001-example.md # Example ADR + GEN-001-example.rules.ts # Example rules file lint/ - archgate.config.ts # Archgate configuration + README.md # Linter rules guide ``` --- @@ -5003,12 +5003,12 @@ interface GrepMatch { } ``` -| Field | Type | Description | -| --------- | -------- | ------------------------------------ | -| `file` | `string` | Absolute path to the matched file | -| `line` | `number` | Line number of the match (1-based) | -| `column` | `number` | Column number of the match (1-based) | -| `content` | `string` | Full content of the matched line | +| Field | Type | Description | +| --------- | -------- | ----------------------------------------- | +| `file` | `string` | Project-relative path to the matched file | +| `line` | `number` | Line number of the match (1-based) | +| `column` | `number` | Column number of the match (1-based) | +| `content` | `string` | Full content of the matched line | --- @@ -5071,16 +5071,6 @@ A reason is required. File-level suppression uses `archgate-ignore-file`. Stack --- -## RuleSet - -The type used with `satisfies` to type-check your rules object. Export a plain object that conforms to this shape. - -```typescript -type RuleSet = { rules: Record }; -``` - ---- - ## Reference: Telemetry Source: https://cli.archgate.dev/reference/telemetry/ diff --git a/docs/src/content/docs/nb/reference/cli/init.mdx b/docs/src/content/docs/nb/reference/cli/init.mdx index c6b19ff5..693dc991 100644 --- a/docs/src/content/docs/nb/reference/cli/init.mdx +++ b/docs/src/content/docs/nb/reference/cli/init.mdx @@ -54,8 +54,8 @@ Deteksjonen prøver `origin/HEAD`, `origin/main`, `origin/master`, lokal `main` ``` .archgate/ adrs/ - ARCH-001-example.md # Example ADR - ARCH-001-example.rules.ts # Example rules file + GEN-001-example.md # Example ADR + GEN-001-example.rules.ts # Example rules file lint/ - archgate.config.ts # Archgate configuration + README.md # Linter rules guide ``` diff --git a/docs/src/content/docs/nb/reference/rule-api.mdx b/docs/src/content/docs/nb/reference/rule-api.mdx index 0e16c724..8b489597 100644 --- a/docs/src/content/docs/nb/reference/rule-api.mdx +++ b/docs/src/content/docs/nb/reference/rule-api.mdx @@ -246,12 +246,12 @@ interface GrepMatch { } ``` -| Felt | Type | Beskrivelse | -| --------- | -------- | ------------------------------------ | -| `file` | `string` | Absolutt sti til den matchede filen | -| `line` | `number` | Linjenummer for treffet (1-basert) | -| `column` | `number` | Kolonnenummer for treffet (1-basert) | -| `content` | `string` | Fullt innhold av den matchede linjen | +| Felt | Type | Beskrivelse | +| --------- | -------- | ------------------------------------------ | +| `file` | `string` | Prosjektrelativ sti til den matchede filen | +| `line` | `number` | Linjenummer for treffet (1-basert) | +| `column` | `number` | Kolonnenummer for treffet (1-basert) | +| `content` | `string` | Fullt innhold av den matchede linjen | --- @@ -311,13 +311,3 @@ import chalk from "chalk"; ``` En begrunnelse er påkrevd. Filnivå-undertrykkelse bruker `archgate-ignore-file`. Stable flere kommentarer for å undertrykke mer enn én regel på samme linje. Se [Opt-out-direktiver](/guides/writing-rules/#opt-out-direktiver) for fullstendige detaljer og egendefinerte direktivmønstre. - ---- - -## RuleSet - -Typen brukt med `satisfies` for å typekontrollere regelobjektet ditt. Eksporter et vanlig objekt som samsvarer med denne formen. - -```typescript -type RuleSet = { rules: Record }; -``` diff --git a/docs/src/content/docs/pt-br/reference/cli/init.mdx b/docs/src/content/docs/pt-br/reference/cli/init.mdx index c6b6eba3..c655659e 100644 --- a/docs/src/content/docs/pt-br/reference/cli/init.mdx +++ b/docs/src/content/docs/pt-br/reference/cli/init.mdx @@ -54,8 +54,8 @@ Re-executar `archgate init` **não** sobrescreve um `baseBranch` configurado man ``` .archgate/ adrs/ - ARCH-001-example.md # Example ADR - ARCH-001-example.rules.ts # Example rules file + GEN-001-example.md # Example ADR + GEN-001-example.rules.ts # Example rules file lint/ - archgate.config.ts # Archgate configuration + README.md # Linter rules guide ``` diff --git a/docs/src/content/docs/pt-br/reference/rule-api.mdx b/docs/src/content/docs/pt-br/reference/rule-api.mdx index e06bff21..1cab467d 100644 --- a/docs/src/content/docs/pt-br/reference/rule-api.mdx +++ b/docs/src/content/docs/pt-br/reference/rule-api.mdx @@ -246,12 +246,12 @@ interface GrepMatch { } ``` -| Campo | Tipo | Descrição | -| --------- | -------- | -------------------------------------------- | -| `file` | `string` | Caminho absoluto do arquivo correspondente | -| `line` | `number` | Número da linha da correspondência (base 1) | -| `column` | `number` | Número da coluna da correspondência (base 1) | -| `content` | `string` | Conteúdo completo da linha correspondente | +| Campo | Tipo | Descrição | +| --------- | -------- | ----------------------------------------------------- | +| `file` | `string` | Caminho relativo ao projeto do arquivo correspondente | +| `line` | `number` | Número da linha da correspondência (base 1) | +| `column` | `number` | Número da coluna da correspondência (base 1) | +| `content` | `string` | Conteúdo completo da linha correspondente | --- @@ -311,13 +311,3 @@ import chalk from "chalk"; ``` Um motivo é obrigatório. A supressão no nível do arquivo usa `archgate-ignore-file`. Empilhe múltiplos comentários para suprimir mais de uma regra na mesma linha. Veja [Diretivas de opt-out](/guides/writing-rules/#diretivas-de-opt-out) para detalhes completos e padrões de diretivas customizadas. - ---- - -## RuleSet - -O tipo usado com `satisfies` para verificar a tipagem do seu objeto de regras. Exporte um objeto simples que esteja em conformidade com esta forma. - -```typescript -type RuleSet = { rules: Record }; -``` diff --git a/docs/src/content/docs/reference/cli/init.mdx b/docs/src/content/docs/reference/cli/init.mdx index 54f1cc96..ab94a249 100644 --- a/docs/src/content/docs/reference/cli/init.mdx +++ b/docs/src/content/docs/reference/cli/init.mdx @@ -54,8 +54,8 @@ Re-running `archgate init` does **not** overwrite a manually configured `baseBra ``` .archgate/ adrs/ - ARCH-001-example.md # Example ADR - ARCH-001-example.rules.ts # Example rules file + GEN-001-example.md # Example ADR + GEN-001-example.rules.ts # Example rules file lint/ - archgate.config.ts # Archgate configuration + README.md # Linter rules guide ``` diff --git a/docs/src/content/docs/reference/rule-api.mdx b/docs/src/content/docs/reference/rule-api.mdx index a9280f2b..0953b8e7 100644 --- a/docs/src/content/docs/reference/rule-api.mdx +++ b/docs/src/content/docs/reference/rule-api.mdx @@ -246,12 +246,12 @@ interface GrepMatch { } ``` -| Field | Type | Description | -| --------- | -------- | ------------------------------------ | -| `file` | `string` | Absolute path to the matched file | -| `line` | `number` | Line number of the match (1-based) | -| `column` | `number` | Column number of the match (1-based) | -| `content` | `string` | Full content of the matched line | +| Field | Type | Description | +| --------- | -------- | ----------------------------------------- | +| `file` | `string` | Project-relative path to the matched file | +| `line` | `number` | Line number of the match (1-based) | +| `column` | `number` | Column number of the match (1-based) | +| `content` | `string` | Full content of the matched line | --- @@ -311,13 +311,3 @@ import chalk from "chalk"; ``` A reason is required. File-level suppression uses `archgate-ignore-file`. Stack multiple comments to suppress more than one rule on the same line. See [Opt-out directives](/guides/writing-rules/#opt-out-directives) for full details and custom directive patterns. - ---- - -## RuleSet - -The type used with `satisfies` to type-check your rules object. Export a plain object that conforms to this shape. - -```typescript -type RuleSet = { rules: Record }; -``` diff --git a/shims/go/internal/shim/shim.go b/shims/go/internal/shim/shim.go index a61f887a..7e39a147 100644 --- a/shims/go/internal/shim/shim.go +++ b/shims/go/internal/shim/shim.go @@ -71,20 +71,23 @@ func download(artifact, cacheDir, binaryPath string) int { checksumURL := fmt.Sprintf("%s/v%s/%s.%s.sha256", releaseBaseURL, Version, artifact, ext) checksumBytes, checksumErr := httpGet(checksumURL) if checksumErr != nil { - fmt.Fprintf(os.Stderr, "archgate: checksum file unavailable, skipping verification.\n") - } else { - expected := strings.TrimSpace(string(checksumBytes)) - if len(expected) >= 64 { - expected = expected[:64] - } + fmt.Fprintf(os.Stderr, "archgate: failed to download checksum file: %v\n", checksumErr) + fmt.Fprintf(os.Stderr, "archgate: refusing to install without checksum verification.\n") + fmt.Fprintf(os.Stderr, "Visit %s for alternative install methods.\n", installHelpURL) + return 2 + } - hash := sha256.Sum256(archiveBytes) - actual := hex.EncodeToString(hash[:]) + expected := strings.TrimSpace(string(checksumBytes)) + if len(expected) >= 64 { + expected = expected[:64] + } - if !strings.EqualFold(expected, actual) { - fmt.Fprintf(os.Stderr, "archgate: checksum verification failed for v%s (expected %s, got %s)\n", Version, expected, actual) - return 2 - } + hash := sha256.Sum256(archiveBytes) + actual := hex.EncodeToString(hash[:]) + + if !strings.EqualFold(expected, actual) { + fmt.Fprintf(os.Stderr, "archgate: checksum verification failed for v%s (expected %s, got %s)\n", Version, expected, actual) + return 2 } // Ensure cache directory exists diff --git a/shims/maven/src/main/java/dev/archgate/cli/Shim.java b/shims/maven/src/main/java/dev/archgate/cli/Shim.java index de51e371..9fe2c913 100644 --- a/shims/maven/src/main/java/dev/archgate/cli/Shim.java +++ b/shims/maven/src/main/java/dev/archgate/cli/Shim.java @@ -30,7 +30,7 @@ */ public final class Shim { - private static final String VERSION = "0.39.0"; + private static final String VERSION = "0.41.1"; private static final String BASE_URL = "https://github.com/archgate/cli/releases/download/v" + VERSION + "/"; private Shim() {} @@ -194,7 +194,16 @@ static byte[] extractFromTarGz(byte[] archiveBytes, String binaryName) throws IO // Parse size (bytes 124-136, octal) String sizeStr = stripNulls(new String(header, 124, 12, StandardCharsets.UTF_8)).trim(); - long size = sizeStr.isEmpty() ? 0 : Long.parseLong(sizeStr, 8); + long size; + if (sizeStr.isEmpty()) { + size = 0; + } else { + try { + size = Long.parseLong(sizeStr, 8); + } catch (NumberFormatException e) { + throw new IOException("Corrupt tar header: invalid octal size '" + sizeStr + "'", e); + } + } // File data follows, padded to 512-byte boundary int blocks = (int) ((size + 511) / 512); diff --git a/shims/nuget/Archgate.Tool/Program.cs b/shims/nuget/Archgate.Tool/Program.cs index 0d508524..abf0f334 100644 --- a/shims/nuget/Archgate.Tool/Program.cs +++ b/shims/nuget/Archgate.Tool/Program.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using System.IO.Compression; +using System.Linq; using System.Runtime.InteropServices; using System.Security.Cryptography; @@ -7,9 +8,9 @@ namespace Archgate.Tool; internal static class Program { - private const string Version = "0.39.0"; + private const string Version = "0.41.1"; - private static readonly string CacheDir = Path.Combine( + private static readonly string CacheDir = Path.Join( Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".archgate", "bin" @@ -25,7 +26,31 @@ internal static int Main(string[] args) { DownloadBinary(binaryPath).GetAwaiter().GetResult(); } - catch (Exception ex) + catch (HttpRequestException ex) + { + Console.Error.WriteLine( + $"archgate: failed to download binary: {ex.Message}\n" + + "Visit https://cli.archgate.dev/getting-started/installation/ for alternative install methods." + ); + return 2; + } + catch (IOException ex) + { + Console.Error.WriteLine( + $"archgate: failed to download binary: {ex.Message}\n" + + "Visit https://cli.archgate.dev/getting-started/installation/ for alternative install methods." + ); + return 2; + } + catch (InvalidOperationException ex) + { + Console.Error.WriteLine( + $"archgate: failed to download binary: {ex.Message}\n" + + "Visit https://cli.archgate.dev/getting-started/installation/ for alternative install methods." + ); + return 2; + } + catch (PlatformNotSupportedException ex) { Console.Error.WriteLine( $"archgate: failed to download binary: {ex.Message}\n" @@ -73,7 +98,7 @@ internal static string GetBinaryName() private static string GetBinaryPath() { - return Path.Combine(CacheDir, GetBinaryName()); + return Path.Join(CacheDir, GetBinaryName()); } // ------------------------------------------------------------------------- @@ -137,10 +162,11 @@ private static async Task VerifyChecksum(HttpClient http, byte[] archiveBytes, s string checksumContent = await http.GetStringAsync(checksumUrl); expectedHash = checksumContent.Trim().Split(' ', StringSplitOptions.RemoveEmptyEntries)[0]; } - catch + catch (HttpRequestException) { - Console.Error.WriteLine("archgate: warning: checksum file not available, skipping verification"); - return; + throw new InvalidOperationException( + $"checksum verification failed for v{Version}: unable to fetch checksum file" + ); } byte[] hashBytes = SHA256.HashData(archiveBytes); @@ -159,16 +185,8 @@ private static void ExtractFromZip(byte[] archiveBytes, string binaryName, strin using var stream = new MemoryStream(archiveBytes); using var archive = new ZipArchive(stream, ZipArchiveMode.Read); - ZipArchiveEntry? entry = null; - foreach (var e in archive.Entries) - { - // Match exact name or nested path ending with the binary name - if (string.Equals(e.Name, binaryName, StringComparison.OrdinalIgnoreCase)) - { - entry = e; - break; - } - } + ZipArchiveEntry? entry = archive.Entries + .FirstOrDefault(e => string.Equals(e.Name, binaryName, StringComparison.OrdinalIgnoreCase)); if (entry is null) { @@ -182,7 +200,7 @@ private static void ExtractFromZip(byte[] archiveBytes, string binaryName, strin private static void ExtractFromTarGz(byte[] archiveBytes, string binaryName, string destPath) { - string tempDir = Path.Combine(CacheDir, "archgate-extract"); + string tempDir = Path.Join(CacheDir, "archgate-extract"); Directory.CreateDirectory(tempDir); try @@ -204,7 +222,7 @@ private static void ExtractFromTarGz(byte[] archiveBytes, string binaryName, str } finally { - try { Directory.Delete(tempDir, recursive: true); } catch { /* cleanup */ } + try { Directory.Delete(tempDir, recursive: true); } catch (IOException) { /* best-effort cleanup */ } } } diff --git a/shims/pypi/archgate/_shim.py b/shims/pypi/archgate/_shim.py index f471daad..8985fb6b 100644 --- a/shims/pypi/archgate/_shim.py +++ b/shims/pypi/archgate/_shim.py @@ -4,7 +4,6 @@ from __future__ import annotations import hashlib -import os import platform import stat import subprocess diff --git a/shims/pypi/tests/test_shim.py b/shims/pypi/tests/test_shim.py index 79e46edd..4d7b0b64 100644 --- a/shims/pypi/tests/test_shim.py +++ b/shims/pypi/tests/test_shim.py @@ -4,8 +4,7 @@ import hashlib import sys -import unittest -from unittest import mock +from unittest import TestCase, main, mock # --------------------------------------------------------------------------- # Ensure the package is importable regardless of working directory. @@ -25,7 +24,7 @@ ) -class TestPlatformDetection(unittest.TestCase): +class TestPlatformDetection(TestCase): """Verify the platform → artifact mapping.""" def test_darwin_arm64(self): @@ -73,7 +72,7 @@ def test_unsupported_platform_exits(self): self.assertEqual(ctx.exception.code, 2) -class TestArtifactNaming(unittest.TestCase): +class TestArtifactNaming(TestCase): """Verify artifact name, binary name, and archive extension.""" def test_artifact_names_in_platform_map(self): @@ -107,7 +106,7 @@ def test_archive_ext_unix(self): self.assertEqual(_archive_ext(), "tar.gz") -class TestChecksumVerification(unittest.TestCase): +class TestChecksumVerification(TestCase): """Verify SHA256 checksum logic.""" def test_checksum_pass(self): @@ -141,4 +140,4 @@ def test_checksum_unavailable_warns(self): if __name__ == "__main__": - unittest.main() + main()