kitfly 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +60 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -0
  4. package/VERSION +1 -0
  5. package/package.json +63 -0
  6. package/schemas/README.md +32 -0
  7. package/schemas/site.schema.json +5 -0
  8. package/schemas/theme.schema.json +5 -0
  9. package/schemas/v0/site.schema.json +172 -0
  10. package/schemas/v0/theme.schema.json +210 -0
  11. package/scripts/build-all.ts +121 -0
  12. package/scripts/build.ts +601 -0
  13. package/scripts/bundle.ts +781 -0
  14. package/scripts/dev.ts +777 -0
  15. package/scripts/generate-checksums.sh +78 -0
  16. package/scripts/release/export-release-key.sh +28 -0
  17. package/scripts/release/release-guard-tag-version.sh +79 -0
  18. package/scripts/release/sign-release-assets.sh +123 -0
  19. package/scripts/release/upload-release-assets.sh +76 -0
  20. package/scripts/release/upload-release-provenance.sh +52 -0
  21. package/scripts/release/verify-public-key.sh +48 -0
  22. package/scripts/release/verify-signatures.sh +117 -0
  23. package/scripts/version-sync.ts +82 -0
  24. package/src/__tests__/build.test.ts +240 -0
  25. package/src/__tests__/bundle.test.ts +786 -0
  26. package/src/__tests__/cli.test.ts +706 -0
  27. package/src/__tests__/crucible.test.ts +1043 -0
  28. package/src/__tests__/engine.test.ts +157 -0
  29. package/src/__tests__/init.test.ts +450 -0
  30. package/src/__tests__/pipeline.test.ts +1087 -0
  31. package/src/__tests__/productbook.test.ts +1206 -0
  32. package/src/__tests__/runbook.test.ts +974 -0
  33. package/src/__tests__/server-registry.test.ts +1251 -0
  34. package/src/__tests__/servicebook.test.ts +1248 -0
  35. package/src/__tests__/shared.test.ts +2005 -0
  36. package/src/__tests__/styles.test.ts +14 -0
  37. package/src/__tests__/theme-schema.test.ts +47 -0
  38. package/src/__tests__/theme.test.ts +554 -0
  39. package/src/cli.ts +582 -0
  40. package/src/commands/init.ts +92 -0
  41. package/src/commands/update.ts +444 -0
  42. package/src/engine.ts +20 -0
  43. package/src/logger.ts +15 -0
  44. package/src/migrations/0000_schema_versioning.ts +67 -0
  45. package/src/migrations/0001_server_port.ts +52 -0
  46. package/src/migrations/0002_brand_logo.ts +49 -0
  47. package/src/migrations/index.ts +26 -0
  48. package/src/migrations/schema.ts +24 -0
  49. package/src/server-registry.ts +405 -0
  50. package/src/shared.ts +1239 -0
  51. package/src/site/styles.css +931 -0
  52. package/src/site/template.html +193 -0
  53. package/src/templates/crucible.ts +1163 -0
  54. package/src/templates/driver.ts +876 -0
  55. package/src/templates/handbook.ts +339 -0
  56. package/src/templates/minimal.ts +139 -0
  57. package/src/templates/pipeline.ts +966 -0
  58. package/src/templates/productbook.ts +1032 -0
  59. package/src/templates/runbook.ts +829 -0
  60. package/src/templates/schema.ts +119 -0
  61. package/src/templates/servicebook.ts +1242 -0
  62. package/src/theme.ts +245 -0
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+
3
+ set -euo pipefail
4
+
5
+ # Generate SHA256SUMS and SHA512SUMS for release artifacts
6
+ #
7
+ # Usage: generate-checksums.sh [dir] <binary_name>
8
+ #
9
+ # Checksums:
10
+ # - npm tarball (if present)
11
+ # - source archives (if present)
12
+ # - platform binaries (bun build --compile output)
13
+
14
+ DIR=${1:-dist/release}
15
+ BINARY_NAME=${2:-}
16
+
17
+ if [ -z "${BINARY_NAME}" ]; then
18
+ echo "usage: $0 [dir] <binary_name>" >&2
19
+ exit 1
20
+ fi
21
+
22
+ if [ ! -d "${DIR}" ]; then
23
+ echo "error: directory ${DIR} not found" >&2
24
+ exit 1
25
+ fi
26
+
27
+ cd "${DIR}"
28
+
29
+ rm -f SHA256SUMS SHA256SUMS.* SHA512SUMS SHA512SUMS.*
30
+
31
+ # Find release artifacts
32
+ artifacts=()
33
+
34
+ # npm tarball pattern
35
+ for f in "${BINARY_NAME}-"*.tgz; do
36
+ [ -f "$f" ] && artifacts+=("$f")
37
+ done
38
+
39
+ # Source archive patterns
40
+ for f in "${BINARY_NAME}-"*.tar.gz "${BINARY_NAME}-"*.zip; do
41
+ [ -f "$f" ] && artifacts+=("$f")
42
+ done
43
+
44
+ # Platform binary patterns (bun build --compile output)
45
+ for f in "${BINARY_NAME}-"*; do
46
+ # Skip archives already handled above
47
+ case "$f" in *.tgz|*.tar.gz|*.zip) continue ;; esac
48
+ # Skip checksum/signature files
49
+ case "$f" in SHA*|*.sig|*.minisig|*.asc) continue ;; esac
50
+ [ -f "$f" ] && artifacts+=("$f")
51
+ done
52
+
53
+ if [ ${#artifacts[@]} -eq 0 ]; then
54
+ echo "error: no artifacts found matching ${BINARY_NAME}-* in ${DIR}" >&2
55
+ echo "Expected: npm tarball (*.tgz), source archive (*.tar.gz, *.zip), or platform bundles" >&2
56
+ exit 1
57
+ fi
58
+
59
+ echo "Generating checksums for ${#artifacts[@]} artifact(s)..."
60
+
61
+ if command -v sha256sum > /dev/null 2>&1; then
62
+ sha256sum "${artifacts[@]}" > SHA256SUMS
63
+ else
64
+ shasum -a 256 "${artifacts[@]}" > SHA256SUMS
65
+ fi
66
+
67
+ if command -v sha512sum > /dev/null 2>&1; then
68
+ sha512sum "${artifacts[@]}" > SHA512SUMS
69
+ else
70
+ shasum -a 512 "${artifacts[@]}" > SHA512SUMS
71
+ fi
72
+
73
+ echo "Wrote SHA256SUMS and SHA512SUMS"
74
+ echo ""
75
+ echo "Artifacts checksummed:"
76
+ for f in "${artifacts[@]}"; do
77
+ echo " - $f"
78
+ done
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Export PGP public key for release signing verification
5
+ #
6
+ # Usage: export-release-key.sh <key-id> [dest_dir]
7
+ #
8
+ # Environment variables:
9
+ # KITFLY_GPG_HOMEDIR - Custom GPG homedir (optional, defaults to ~/.gnupg)
10
+
11
+ KEY_ID=${1:?"usage: export-release-key.sh <key-id> [dest_dir]"}
12
+ DIR=${2:-dist/release}
13
+ KITFLY_GPG_HOMEDIR=${KITFLY_GPG_HOMEDIR:-}
14
+
15
+ if ! command -v gpg >/dev/null 2>&1; then
16
+ echo "gpg is required" >&2
17
+ exit 1
18
+ fi
19
+ mkdir -p "$DIR"
20
+ OUTPUT="$DIR/kitfly-release-signing-key.asc"
21
+
22
+ if [ -n "$KITFLY_GPG_HOMEDIR" ]; then
23
+ env GNUPGHOME="$KITFLY_GPG_HOMEDIR" gpg --armor --export "$KEY_ID" >"$OUTPUT"
24
+ else
25
+ gpg --armor --export "$KEY_ID" >"$OUTPUT"
26
+ fi
27
+
28
+ echo "[ok] Exported $KEY_ID to $OUTPUT"
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Release guard: ensure git tag matches VERSION file
5
+ #
6
+ # Environment variables:
7
+ # KITFLY_RELEASE_TAG - Override tag to check (e.g., v0.1.2)
8
+ # KITFLY_REQUIRE_TAG - Set to 1 to enforce tag presence in CI
9
+ #
10
+ # Usage:
11
+ # ./scripts/release/release-guard-tag-version.sh
12
+ # KITFLY_RELEASE_TAG=v0.1.2 ./scripts/release/release-guard-tag-version.sh
13
+ # KITFLY_REQUIRE_TAG=1 ./scripts/release/release-guard-tag-version.sh
14
+
15
+ repo_root() {
16
+ git rev-parse --show-toplevel
17
+ }
18
+
19
+ read_version() {
20
+ if [ ! -f VERSION ]; then
21
+ echo "error: VERSION file not found" >&2
22
+ exit 1
23
+ fi
24
+ tr -d ' \t\r\n' <VERSION
25
+ }
26
+
27
+ normalize_tag() {
28
+ local raw="${1:-}"
29
+ if [ -z "$raw" ]; then
30
+ printf '%s' ""
31
+ return 0
32
+ fi
33
+ if [[ "$raw" == v* ]]; then
34
+ printf '%s' "$raw"
35
+ else
36
+ printf 'v%s' "$raw"
37
+ fi
38
+ }
39
+
40
+ detect_tag() {
41
+ if [ -n "${KITFLY_RELEASE_TAG:-}" ]; then
42
+ normalize_tag "${KITFLY_RELEASE_TAG}"
43
+ return 0
44
+ fi
45
+ git describe --tags --exact-match 2>/dev/null || true
46
+ }
47
+
48
+ main() {
49
+ local root
50
+ root="$(repo_root)"
51
+ cd "$root"
52
+
53
+ local version
54
+ version="$(read_version)"
55
+
56
+ local expected="v${version}"
57
+ local tag
58
+ tag="$(detect_tag)"
59
+
60
+ if [ -z "$tag" ]; then
61
+ if [ "${KITFLY_REQUIRE_TAG:-}" = "1" ]; then
62
+ echo "error: no exact tag found for HEAD and no KITFLY_RELEASE_TAG provided" >&2
63
+ exit 1
64
+ fi
65
+ echo "→ release guard: no tag detected (set KITFLY_REQUIRE_TAG=1 to enforce in CI)"
66
+ exit 0
67
+ fi
68
+
69
+ if [ "$tag" != "$expected" ]; then
70
+ echo "error: release tag/version mismatch" >&2
71
+ echo " tag: $tag" >&2
72
+ echo " VERSION: $version (expected tag: $expected)" >&2
73
+ exit 1
74
+ fi
75
+
76
+ echo "✅ release guard: tag matches VERSION ($tag)"
77
+ }
78
+
79
+ main "$@"
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Dual-format release signing: minisign (.minisig) + PGP (.asc)
5
+ #
6
+ # Usage: sign-release-assets.sh <tag> [dir]
7
+ #
8
+ # Environment variables:
9
+ # KITFLY_MINISIGN_KEY - Path to minisign secret key file. Primary format.
10
+ # KITFLY_PGP_KEY_ID - GPG key ID for PGP signing. Optional secondary format.
11
+ # KITFLY_GPG_HOMEDIR - Custom GPG homedir (optional, defaults to ~/.gnupg)
12
+ #
13
+ # Minisign was chosen over raw ed25519 because:
14
+ # - Created by Frank Denis (libsodium author), well-audited
15
+ # - Trusted comments provide signed metadata (version, timestamp)
16
+ # - Password-protected keys by default
17
+ # - Compatible with OpenBSD signify
18
+ #
19
+ # Only SHA256SUMS is signed (not individual files). This is the standard pattern:
20
+ # verify signature on checksum file, then verify file checksums against that.
21
+ # This means one password prompt instead of N.
22
+
23
+ TAG=${1:?"usage: sign-release-assets.sh <tag> [dir]"}
24
+ DIR=${2:-dist/release}
25
+
26
+ KITFLY_MINISIGN_KEY=${KITFLY_MINISIGN_KEY:-}
27
+ KITFLY_PGP_KEY_ID=${KITFLY_PGP_KEY_ID:-}
28
+ KITFLY_GPG_HOMEDIR=${KITFLY_GPG_HOMEDIR:-}
29
+
30
+ # Validation
31
+ if [ ! -d "$DIR" ]; then
32
+ echo "error: directory $DIR not found" >&2
33
+ exit 1
34
+ fi
35
+
36
+ checksum_files=()
37
+ for file in SHA256SUMS SHA512SUMS; do
38
+ if [ -f "$DIR/$file" ]; then
39
+ checksum_files+=("$file")
40
+ fi
41
+ done
42
+
43
+ if [ ${#checksum_files[@]} -eq 0 ]; then
44
+ echo "error: no checksum files found (run make release-checksums first)" >&2
45
+ exit 1
46
+ fi
47
+
48
+ has_minisign=false
49
+ has_pgp=false
50
+
51
+ if [ -n "$KITFLY_MINISIGN_KEY" ]; then
52
+ if [ ! -f "$KITFLY_MINISIGN_KEY" ]; then
53
+ echo "error: KITFLY_MINISIGN_KEY=$KITFLY_MINISIGN_KEY not found" >&2
54
+ exit 1
55
+ fi
56
+ if ! command -v minisign >/dev/null 2>&1; then
57
+ echo "error: minisign not found in PATH" >&2
58
+ echo " Install: brew install minisign (macOS) or see https://jedisct1.github.io/minisign/" >&2
59
+ exit 1
60
+ fi
61
+ has_minisign=true
62
+ echo "minisign signing enabled (key: $KITFLY_MINISIGN_KEY)"
63
+ fi
64
+
65
+ # Only enable PGP if explicitly requested via KITFLY_PGP_KEY_ID
66
+ if [ -n "$KITFLY_PGP_KEY_ID" ]; then
67
+ if ! command -v gpg >/dev/null 2>&1; then
68
+ echo "error: KITFLY_PGP_KEY_ID set but gpg not found in PATH" >&2
69
+ exit 1
70
+ fi
71
+ has_pgp=true
72
+ echo "PGP signing enabled (key: $KITFLY_PGP_KEY_ID)"
73
+ if [ -n "$KITFLY_GPG_HOMEDIR" ]; then
74
+ echo "GPG homedir: $KITFLY_GPG_HOMEDIR"
75
+ fi
76
+ fi
77
+
78
+ if [ "$has_minisign" = false ] && [ "$has_pgp" = false ]; then
79
+ echo "error: no signing method available" >&2
80
+ echo " Set KITFLY_MINISIGN_KEY for minisign signing" >&2
81
+ echo " Set KITFLY_PGP_KEY_ID for PGP signing" >&2
82
+ exit 1
83
+ fi
84
+
85
+ # Sign checksum manifests (preferred workflow)
86
+ # Users verify: 1) signature on checksum file, 2) file checksums against it
87
+ #
88
+ # Signing is grouped by tool (all minisign first, then all PGP) to minimize
89
+ # password prompt switching during manual signing workflows.
90
+
91
+ if [ "$has_minisign" = true ]; then
92
+ echo ""
93
+ echo "=== Minisign signatures ==="
94
+ for file in "${checksum_files[@]}"; do
95
+ echo "[minisign] Signing $file"
96
+ rm -f "$DIR/$file.minisig"
97
+ minisign -S -s "$KITFLY_MINISIGN_KEY" -t "kitfly $TAG $(date -u +%Y-%m-%dT%H:%M:%SZ)" -m "$DIR/$file"
98
+ done
99
+ fi
100
+
101
+ if [ "$has_pgp" = true ]; then
102
+ echo ""
103
+ echo "=== PGP signatures ==="
104
+ for file in "${checksum_files[@]}"; do
105
+ echo "[PGP] Signing $file"
106
+ if [ -n "$KITFLY_GPG_HOMEDIR" ]; then
107
+ env GNUPGHOME="$KITFLY_GPG_HOMEDIR" gpg --batch --yes --armor --local-user "$KITFLY_PGP_KEY_ID" --detach-sign -o "$DIR/$file.asc" "$DIR/$file"
108
+ else
109
+ gpg --batch --yes --armor --local-user "$KITFLY_PGP_KEY_ID" --detach-sign -o "$DIR/$file.asc" "$DIR/$file"
110
+ fi
111
+ done
112
+ fi
113
+
114
+ echo ""
115
+ echo "[ok] Signing complete for $TAG"
116
+ for file in "${checksum_files[@]}"; do
117
+ if [ "$has_minisign" = true ]; then
118
+ echo " $file.minisig"
119
+ fi
120
+ if [ "$has_pgp" = true ]; then
121
+ echo " $file.asc"
122
+ fi
123
+ done
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Upload ALL release assets (binaries, tarballs, and provenance) to GitHub.
5
+ #
6
+ # CAUTION: Only use when a release was built locally and all assets need
7
+ # transfer. Each file is a separate API call — uploading many assets in
8
+ # rapid succession can trigger GitHub's secondary rate limits (HTTP 429).
9
+ #
10
+ # For the normal workflow use upload-release-provenance.sh instead — CI
11
+ # uploads binaries and tarballs, provenance-only upload keeps API calls minimal.
12
+ #
13
+ # Usage: upload-release-assets.sh <tag> [dir]
14
+
15
+ TAG=${1:?"usage: upload-release-assets.sh <tag> [dir]"}
16
+ DIR=${2:-dist/release}
17
+
18
+ if ! command -v gh >/dev/null 2>&1; then
19
+ echo "gh CLI is required" >&2
20
+ exit 1
21
+ fi
22
+
23
+ if [ ! -d "$DIR" ]; then
24
+ echo "directory $DIR not found" >&2
25
+ exit 1
26
+ fi
27
+
28
+ # Collect all release assets into a single upload
29
+ shopt -s nullglob
30
+
31
+ assets=()
32
+
33
+ # Platform binaries (kitfly-linux-amd64, kitfly-windows-amd64.exe, etc.)
34
+ for f in "$DIR"/kitfly-*; do
35
+ # Skip npm tarballs, checksums, signatures, keys — handled below
36
+ case "$f" in *.tgz|SHA*|*.sig|*.minisig|*.asc|*.pub|*.md) continue ;; esac
37
+ [ -f "$f" ] && assets+=("$f")
38
+ done
39
+
40
+ # Tarballs and checksums
41
+ assets+=("$DIR"/*.tgz)
42
+ assets+=("$DIR"/SHA256SUMS "$DIR"/SHA512SUMS)
43
+
44
+ # Signatures
45
+ assets+=("$DIR"/SHA256SUMS.* "$DIR"/SHA512SUMS.*)
46
+
47
+ # Public keys
48
+ assets+=("$DIR"/*-minisign.pub "$DIR"/*-signing-key.asc)
49
+
50
+ # Release notes
51
+ assets+=("$DIR"/release-notes-*.md)
52
+
53
+ # Filter to existing files
54
+ final_assets=()
55
+ for f in "${assets[@]}"; do
56
+ if [ -f "$f" ]; then
57
+ final_assets+=("$f")
58
+ fi
59
+ done
60
+
61
+ if [ ${#final_assets[@]} -eq 0 ]; then
62
+ echo "no assets found to upload from $DIR" >&2
63
+ exit 1
64
+ fi
65
+
66
+ echo "[..] Uploading ${#final_assets[@]} asset(s) to ${TAG} (clobber)"
67
+ gh release upload "$TAG" "${final_assets[@]}" --clobber
68
+
69
+ # Update release body with notes if present
70
+ NOTES_FILE="$DIR/release-notes-${TAG}.md"
71
+ if [ -f "$NOTES_FILE" ]; then
72
+ echo "[..] Updating release body from $NOTES_FILE"
73
+ gh release edit "$TAG" --notes-file "$NOTES_FILE"
74
+ fi
75
+
76
+ echo "[ok] Release assets uploaded for $TAG"
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Upload only provenance assets (checksums, signatures, keys, notes) to a
5
+ # GitHub release. Does NOT upload binaries or tarballs — CI handles those.
6
+ #
7
+ # Usage: upload-release-provenance.sh <tag> [dir]
8
+
9
+ TAG="${1:?"usage: upload-release-provenance.sh <tag> [dir]"}"
10
+ DIR="${2:-dist/release}"
11
+
12
+ if ! command -v gh >/dev/null 2>&1; then
13
+ echo "gh CLI is required" >&2
14
+ exit 1
15
+ fi
16
+
17
+ if [ ! -d "$DIR" ]; then
18
+ echo "directory $DIR not found" >&2
19
+ exit 1
20
+ fi
21
+
22
+ shopt -s nullglob
23
+
24
+ assets=()
25
+ assets+=("$DIR"/SHA256SUMS "$DIR"/SHA512SUMS)
26
+ assets+=("$DIR"/SHA256SUMS.* "$DIR"/SHA512SUMS.*)
27
+ assets+=("$DIR"/*-minisign.pub "$DIR"/*-signing-key.asc)
28
+ assets+=("$DIR"/release-notes-*.md)
29
+
30
+ final_assets=()
31
+ for f in "${assets[@]}"; do
32
+ if [ -f "$f" ]; then
33
+ final_assets+=("$f")
34
+ fi
35
+ done
36
+
37
+ if [ ${#final_assets[@]} -eq 0 ]; then
38
+ echo "no provenance assets found to upload from $DIR" >&2
39
+ exit 1
40
+ fi
41
+
42
+ echo "[..] Uploading ${#final_assets[@]} provenance asset(s) to ${TAG} (clobber)"
43
+ gh release upload "$TAG" "${final_assets[@]}" --clobber
44
+
45
+ # Update release body with notes if present
46
+ NOTES_FILE="$DIR/release-notes-${TAG}.md"
47
+ if [ -f "$NOTES_FILE" ]; then
48
+ echo "[..] Updating release body from $NOTES_FILE"
49
+ gh release edit "$TAG" --notes-file "$NOTES_FILE"
50
+ fi
51
+
52
+ echo "[ok] Provenance assets uploaded for $TAG"
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Verify that an exported GPG key file contains only public key material
5
+ #
6
+ # Usage: verify-public-key.sh <keyfile>
7
+ #
8
+ # This is a safety check before uploading keys to ensure we never
9
+ # accidentally publish secret key material.
10
+
11
+ KEY_FILE=${1:?"usage: verify-public-key.sh <keyfile>"}
12
+
13
+ if [ ! -f "$KEY_FILE" ]; then
14
+ echo "error: file $KEY_FILE not found" >&2
15
+ exit 1
16
+ fi
17
+
18
+ if ! command -v gpg >/dev/null 2>&1; then
19
+ echo "gpg is required" >&2
20
+ exit 1
21
+ fi
22
+
23
+ # Check if file contains "BEGIN PGP PRIVATE KEY" or similar
24
+ if grep -q "PRIVATE KEY" "$KEY_FILE"; then
25
+ echo "[!!] DANGER: $KEY_FILE appears to contain private key material!" >&2
26
+ echo "[!!] DO NOT upload this file!" >&2
27
+ exit 1
28
+ fi
29
+
30
+ # Verify it's a valid public key
31
+ if ! gpg --show-keys "$KEY_FILE" >/dev/null 2>&1; then
32
+ echo "[!!] $KEY_FILE is not a valid GPG key file" >&2
33
+ exit 1
34
+ fi
35
+
36
+ # Check that gpg reports it as public only
37
+ KEY_INFO=$(gpg --show-keys --with-colons "$KEY_FILE" 2>/dev/null)
38
+ if echo "$KEY_INFO" | grep -q "^sec:"; then
39
+ echo "[!!] DANGER: $KEY_FILE contains secret key material!" >&2
40
+ exit 1
41
+ fi
42
+
43
+ if ! echo "$KEY_INFO" | grep -q "^pub:"; then
44
+ echo "[!!] $KEY_FILE does not contain a public key" >&2
45
+ exit 1
46
+ fi
47
+
48
+ echo "[ok] $KEY_FILE is a valid public-only GPG key"
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Verify release signatures (minisign and optional PGP) on checksum manifests.
5
+ #
6
+ # Usage: verify-signatures.sh [dir]
7
+ #
8
+ # Environment variables:
9
+ # KITFLY_MINISIGN_PUB - path to minisign public key (required for minisign)
10
+ # KITFLY_GPG_HOMEDIR - isolated gpg homedir for PGP verification (optional)
11
+
12
+ DIR=${1:-dist/release}
13
+
14
+ if [ ! -d "$DIR" ]; then
15
+ echo "error: directory $DIR not found" >&2
16
+ exit 1
17
+ fi
18
+
19
+ KITFLY_MINISIGN_PUB=${KITFLY_MINISIGN_PUB:-}
20
+ KITFLY_GPG_HOMEDIR=${KITFLY_GPG_HOMEDIR:-}
21
+
22
+ verified=0
23
+ failed=0
24
+
25
+ verify_minisign() {
26
+ local manifest="$1"
27
+ local base="${DIR}/${manifest}"
28
+ local sig="${base}.minisig"
29
+
30
+ if [ ! -f "${sig}" ]; then
31
+ echo "[--] No minisign signature for ${manifest} (skipping)"
32
+ return 0
33
+ fi
34
+
35
+ if [ -z "${KITFLY_MINISIGN_PUB}" ]; then
36
+ echo "[!!] KITFLY_MINISIGN_PUB not set, cannot verify ${manifest}.minisig"
37
+ failed=$((failed + 1))
38
+ return 1
39
+ fi
40
+
41
+ if [ ! -f "${KITFLY_MINISIGN_PUB}" ]; then
42
+ echo "error: KITFLY_MINISIGN_PUB=${KITFLY_MINISIGN_PUB} not found" >&2
43
+ failed=$((failed + 1))
44
+ return 1
45
+ fi
46
+
47
+ if ! command -v minisign >/dev/null 2>&1; then
48
+ echo "error: minisign not found in PATH" >&2
49
+ failed=$((failed + 1))
50
+ return 1
51
+ fi
52
+
53
+ echo "[..] [minisign] Verifying ${manifest}"
54
+ if minisign -V -p "${KITFLY_MINISIGN_PUB}" -m "${base}"; then
55
+ echo "[ok] ${manifest}.minisig verified"
56
+ verified=$((verified + 1))
57
+ else
58
+ echo "[!!] ${manifest}.minisig verification FAILED"
59
+ failed=$((failed + 1))
60
+ fi
61
+ }
62
+
63
+ verify_pgp() {
64
+ local manifest="$1"
65
+ local base="${DIR}/${manifest}"
66
+ local sig="${base}.asc"
67
+
68
+ if [ ! -f "${sig}" ]; then
69
+ echo "[--] No PGP signature for ${manifest} (skipping)"
70
+ return 0
71
+ fi
72
+
73
+ if ! command -v gpg >/dev/null 2>&1; then
74
+ echo "[!!] gpg not found, cannot verify ${manifest}.asc"
75
+ failed=$((failed + 1))
76
+ return 1
77
+ fi
78
+
79
+ echo "[..] [PGP] Verifying ${manifest}"
80
+ local rc=0
81
+ if [ -n "${KITFLY_GPG_HOMEDIR}" ] && [ -d "${KITFLY_GPG_HOMEDIR}" ]; then
82
+ env GNUPGHOME="${KITFLY_GPG_HOMEDIR}" gpg --verify "${sig}" "${base}" 2>&1 || rc=$?
83
+ else
84
+ gpg --verify "${sig}" "${base}" 2>&1 || rc=$?
85
+ fi
86
+
87
+ if [ $rc -eq 0 ]; then
88
+ echo "[ok] ${manifest}.asc verified"
89
+ verified=$((verified + 1))
90
+ else
91
+ echo "[!!] ${manifest}.asc verification FAILED"
92
+ failed=$((failed + 1))
93
+ fi
94
+ }
95
+
96
+ echo "Verifying release signatures in ${DIR}..."
97
+ echo ""
98
+
99
+ verify_minisign "SHA256SUMS"
100
+ verify_minisign "SHA512SUMS"
101
+
102
+ echo ""
103
+
104
+ verify_pgp "SHA256SUMS"
105
+ verify_pgp "SHA512SUMS"
106
+
107
+ echo ""
108
+ echo "----------------------------------------------------------------------"
109
+ if [ $failed -gt 0 ]; then
110
+ echo "[!!] Signature verification: ${verified} passed, ${failed} FAILED"
111
+ exit 1
112
+ elif [ $verified -eq 0 ]; then
113
+ echo "[!!] No signatures found to verify"
114
+ exit 1
115
+ else
116
+ echo "[ok] Signature verification: ${verified} passed"
117
+ fi
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * version-sync.ts - Sync VERSION file to package.json
4
+ *
5
+ * VERSION file is the Single Source of Truth (SSOT) for kitfly versioning.
6
+ * This script propagates the version to package.json.
7
+ *
8
+ * Usage:
9
+ * bun run scripts/version-sync.ts # Sync and display result
10
+ * bun run scripts/version-sync.ts --check # Verify they match (CI)
11
+ * bun run scripts/version-sync.ts --set X.Y.Z # Set VERSION and sync
12
+ */
13
+
14
+ import { readFileSync, writeFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+
17
+ const ROOT = join(import.meta.dir, "..");
18
+ const VERSION_PATH = join(ROOT, "VERSION");
19
+ const PACKAGE_PATH = join(ROOT, "package.json");
20
+
21
+ function getVersionFromFile(): string {
22
+ return readFileSync(VERSION_PATH, "utf-8").trim();
23
+ }
24
+
25
+ function getPackageVersion(): string {
26
+ const pkg = JSON.parse(readFileSync(PACKAGE_PATH, "utf-8"));
27
+ return pkg.version;
28
+ }
29
+
30
+ function setVersionFile(version: string): void {
31
+ writeFileSync(VERSION_PATH, `${version}\n`);
32
+ }
33
+
34
+ function syncPackageJson(version: string): void {
35
+ const pkg = JSON.parse(readFileSync(PACKAGE_PATH, "utf-8"));
36
+ pkg.version = version;
37
+ writeFileSync(PACKAGE_PATH, `${JSON.stringify(pkg, null, 2)}\n`);
38
+ }
39
+
40
+ function main() {
41
+ const args = process.argv.slice(2);
42
+
43
+ // --check: Verify versions match (for CI)
44
+ if (args.includes("--check")) {
45
+ const fileVersion = getVersionFromFile();
46
+ const pkgVersion = getPackageVersion();
47
+ if (fileVersion !== pkgVersion) {
48
+ console.error(`Version mismatch: VERSION=${fileVersion}, package.json=${pkgVersion}`);
49
+ console.error("Run: bun run scripts/version-sync.ts");
50
+ process.exit(1);
51
+ }
52
+ console.log(`Versions match: ${fileVersion}`);
53
+ process.exit(0);
54
+ }
55
+
56
+ // --set X.Y.Z: Set VERSION file and sync
57
+ const setIndex = args.indexOf("--set");
58
+ if (setIndex !== -1) {
59
+ const newVersion = args[setIndex + 1];
60
+ if (!newVersion || !/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(newVersion)) {
61
+ console.error("Usage: bun run scripts/version-sync.ts --set X.Y.Z");
62
+ process.exit(1);
63
+ }
64
+ setVersionFile(newVersion);
65
+ syncPackageJson(newVersion);
66
+ console.log(`Version set to ${newVersion}`);
67
+ process.exit(0);
68
+ }
69
+
70
+ // Default: Sync VERSION → package.json
71
+ const version = getVersionFromFile();
72
+ const pkgVersion = getPackageVersion();
73
+
74
+ if (version === pkgVersion) {
75
+ console.log(`Already in sync: ${version}`);
76
+ } else {
77
+ syncPackageJson(version);
78
+ console.log(`Synced package.json: ${pkgVersion} → ${version}`);
79
+ }
80
+ }
81
+
82
+ main();