Skip to content

Commit 86d2e04

Browse files
chore(nix): improve flake with dynamic version and build optimization (Fission-AI#550)
* chore(nix): improve flake with dynamic version and source filtering - Read version dynamically from package.json instead of hardcoding - Add lib.fileset source filtering to exclude node_modules and build artifacts - Update update-flake.sh to support dynamic version pattern - Add hash change detection to skip unnecessary rebuilds - Improve error handling with automatic rollback on failure - Update specs to reflect dynamic version behavior * chore(ci): bump Nix actions to latest versions - nix-installer-action: v13 → v21 - magic-nix-cache-action: v8 → v13 - Update validation message for unchanged flake.nix * chore: add changeset for Nix improvements * fix(nix): make update-flake.sh portable to macOS - Fix grep pattern on line 37 to include opening parenthesis - Replace GNU grep -oP with portable sed alternatives (lines 53, 68, 70) - Ensures script works on both Linux and macOS (BSD sed/grep) * fix(nix): properly check build verification exit status Fix logic bug where build failures were incorrectly reported as success. The script now: - Captures build exit code and output separately - Fails fast if build returns non-zero exit code - Only checks for 'dirty tree' warning if build succeeded This addresses CodeRabbit review feedback on line 101-107. --------- Co-authored-by: Tabish Bidiwale <30385142+TabishB@users.noreply.github.com>
1 parent f6b415c commit 86d2e04

7 files changed

Lines changed: 162 additions & 63 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@fission-ai/openspec": patch
3+
---
4+
5+
### Improvements
6+
7+
- **Nix flake maintenance** — Version now read dynamically from package.json, reducing manual sync issues
8+
- **Nix build optimization** — Source filtering excludes node_modules and artifacts, improving build times
9+
- **update-flake.sh script** — Detects when hash is already correct, skipping unnecessary rebuilds
10+
11+
### Other
12+
13+
- Updated Nix CI actions to latest versions (nix-installer v21, magic-nix-cache v13)

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,10 @@ jobs:
190190
uses: actions/checkout@v4
191191

192192
- name: Install Nix
193-
uses: DeterminateSystems/nix-installer-action@v13
193+
uses: DeterminateSystems/nix-installer-action@v21
194194

195195
- name: Setup Nix cache
196-
uses: DeterminateSystems/magic-nix-cache-action@v8
196+
uses: DeterminateSystems/magic-nix-cache-action@v13
197197

198198
- name: Build with Nix
199199
run: nix build
@@ -229,7 +229,7 @@ jobs:
229229
- name: Check flake.nix modifications
230230
run: |
231231
if git diff --quiet flake.nix; then
232-
echo "⚠️ Warning: flake.nix was not modified by update script"
232+
echo "ℹ️ flake.nix unchanged (hash already up-to-date)"
233233
else
234234
echo "✅ flake.nix was updated by script"
235235
git diff flake.nix

flake.nix

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,23 +5,47 @@
55
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
66
};
77

8-
outputs = { self, nixpkgs }:
8+
outputs =
9+
{ self, nixpkgs }:
910
let
10-
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
11+
supportedSystems = [
12+
"x86_64-linux"
13+
"aarch64-linux"
14+
"x86_64-darwin"
15+
"aarch64-darwin"
16+
];
1117

1218
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
1319
in
1420
{
15-
packages = forAllSystems (system:
21+
packages = forAllSystems (
22+
system:
1623
let
1724
pkgs = nixpkgs.legacyPackages.${system};
25+
inherit (pkgs) lib;
1826
in
1927
{
2028
default = pkgs.stdenv.mkDerivation (finalAttrs: {
2129
pname = "openspec";
22-
version = "0.23.0";
30+
version = (builtins.fromJSON (builtins.readFile ./package.json)).version;
2331

24-
src = ./.;
32+
src = lib.fileset.toSource {
33+
root = ./.;
34+
fileset = lib.fileset.unions [
35+
./src
36+
./bin
37+
./schemas
38+
./scripts
39+
./test
40+
./package.json
41+
./pnpm-lock.yaml
42+
./tsconfig.json
43+
./build.js
44+
./vitest.config.ts
45+
./vitest.setup.ts
46+
./eslint.config.js
47+
];
48+
};
2549

2650
pnpmDeps = pkgs.fetchPnpmDeps {
2751
inherit (finalAttrs) pname version src;
@@ -55,7 +79,8 @@
5579
mainProgram = "openspec";
5680
};
5781
});
58-
});
82+
}
83+
);
5984

6085
apps = forAllSystems (system: {
6186
default = {
@@ -64,7 +89,8 @@
6489
};
6590
});
6691

67-
devShells = forAllSystems (system:
92+
devShells = forAllSystems (
93+
system:
6894
let
6995
pkgs = nixpkgs.legacyPackages.${system};
7096
in
@@ -82,6 +108,7 @@
82108
echo "Run 'pnpm install' to install dependencies"
83109
'';
84110
};
85-
});
111+
}
112+
);
86113
};
87114
}

openspec/changes/archive/2026-01-09-add-flake-update-script/specs/flake-update-script/spec.md

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
## ADDED Requirements
22

3-
### Requirement: Automatic Version Update
4-
The script SHALL automatically update the version in flake.nix to match package.json.
3+
### Requirement: Dynamic Version Support
4+
The script SHALL support flake.nix configurations that read version dynamically from package.json.
55

6-
#### Scenario: Version extraction from package.json
6+
#### Scenario: Version validation
77
- **WHEN** script runs
88
- **THEN** version is read from package.json using Node.js
9-
- **AND** version field in flake.nix is updated to match
9+
- **AND** script verifies flake.nix uses dynamic version pattern
10+
- **AND** warns if hardcoded version is detected
1011

11-
#### Scenario: Version already up-to-date
12-
- **WHEN** script runs and flake.nix version already matches package.json
13-
- **THEN** script reports version is up-to-date
14-
- **AND** continues to hash update
12+
#### Scenario: Version display
13+
- **WHEN** script runs
14+
- **THEN** script displays current package version
15+
- **AND** indicates version is read dynamically by flake.nix
1516

1617
### Requirement: Automatic Hash Determination
1718
The script SHALL automatically determine and update the correct pnpm dependency hash.
@@ -29,7 +30,8 @@ The script SHALL automatically determine and update the correct pnpm dependency
2930

3031
#### Scenario: Hash update failure
3132
- **WHEN** script cannot extract hash from build output
32-
- **THEN** script exits with error
33+
- **THEN** script restores original hash to flake.nix
34+
- **AND** exits with error code 1
3335
- **AND** displays build output for debugging
3436

3537
### Requirement: Build Verification
@@ -55,8 +57,13 @@ The script SHALL provide clear progress information and next steps.
5557

5658
#### Scenario: Success summary
5759
- **WHEN** script completes successfully
58-
- **THEN** summary shows updated version and hash
59-
- **AND** next steps are displayed (test, commit, etc.)
60+
- **THEN** summary shows version and hash changes
61+
- **AND** next steps are displayed (test, verify, commit)
62+
63+
#### Scenario: No changes needed
64+
- **WHEN** hash is already up-to-date
65+
- **THEN** script reports no changes needed
66+
- **AND** exits with success code 0
6067

6168
### Requirement: Script Safety
6269
The script SHALL fail fast on errors and use safe defaults.

openspec/specs/ci-nix-validation/spec.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ The CI system SHALL validate that the update-flake.sh script executes successful
3333

3434
- **WHEN** the CI runs the update script validation
3535
- **THEN** the script SHALL execute without errors
36-
- **AND** the script SHALL correctly extract the version from package.json
37-
- **AND** the script SHALL update flake.nix with the correct version
36+
- **AND** the script SHALL correctly read the version from package.json
37+
- **AND** the script SHALL validate that flake.nix uses dynamic version from package.json
3838

3939
#### Scenario: Update script with mock hash
4040

scripts/README.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,28 @@ Utility scripts for OpenSpec maintenance and development.
44

55
## update-flake.sh
66

7-
Updates `flake.nix` version and dependency hash automatically.
7+
Updates `flake.nix` pnpm dependency hash automatically.
88

9-
**When to use**: After updating dependencies or releasing a new version.
9+
**When to use**: After updating dependencies (`pnpm install`, `pnpm update`).
1010

1111
**Usage**:
1212
```bash
1313
./scripts/update-flake.sh
1414
```
1515

1616
**What it does**:
17-
1. Extracts version from `package.json`
18-
2. Updates version in `flake.nix`
19-
3. Automatically determines the correct pnpm dependency hash
20-
4. Updates the hash in `flake.nix`
21-
5. Verifies the build succeeds
17+
1. Reads version from `package.json` (dynamically used by `flake.nix`)
18+
2. Automatically determines the correct pnpm dependency hash
19+
3. Updates the hash in `flake.nix`
20+
4. Verifies the build succeeds
2221

2322
**Example workflow**:
2423
```bash
25-
# After version bump and dependency updates
24+
# After dependency updates
2625
pnpm install
2726
./scripts/update-flake.sh
2827
git add flake.nix
29-
git commit -m "chore: update flake.nix for v0.18.0"
28+
git commit -m "chore: update flake.nix dependency hash"
3029
```
3130

3231
## postinstall.js

scripts/update-flake.sh

Lines changed: 83 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
#!/usr/bin/env bash
22
set -euo pipefail
33

4-
# Script to update flake.nix version and dependency hash
5-
# Run this after updating package.json version
4+
# Updates pnpm dependency hash in flake.nix after pnpm-lock.yaml changes.
5+
# Version is read dynamically from package.json.
6+
# Usage: ./scripts/update-flake.sh
67

78
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
89
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
910
FLAKE_FILE="$PROJECT_ROOT/flake.nix"
1011
PACKAGE_JSON="$PROJECT_ROOT/package.json"
1112

13+
# Colors for output
14+
RED='\033[0;31m'
15+
GREEN='\033[0;32m'
16+
YELLOW='\033[1;33m'
17+
BLUE='\033[0;34m'
18+
NC='\033[0m' # No Color
19+
1220
# Detect OS and set sed in-place flag
1321
if [[ "$OSTYPE" == "darwin"* ]]; then
1422
# macOS (BSD sed) requires empty string argument for -i
@@ -18,58 +26,103 @@ else
1826
SED_INPLACE=(-i)
1927
fi
2028

21-
echo "==> Updating flake.nix..."
29+
echo -e "${BLUE}==> Updating flake.nix pnpm dependency hash...${NC}"
30+
echo ""
2231

2332
# Extract version from package.json
2433
VERSION=$(node -p "require('$PACKAGE_JSON').version")
25-
echo " Detected version: $VERSION"
34+
echo -e "${BLUE}📦 Detected package version:${NC} $VERSION"
2635

27-
# Update version in flake.nix
28-
if ! grep -q "version = \"$VERSION\"" "$FLAKE_FILE"; then
29-
echo " Updating version in flake.nix..."
30-
sed "${SED_INPLACE[@]}" "s|version = \"[^\"]*\"|version = \"$VERSION\"|" "$FLAKE_FILE"
31-
else
32-
echo " Version already up-to-date in flake.nix"
36+
# Verify flake.nix uses dynamic version
37+
if ! grep -q "(builtins.fromJSON (builtins.readFile ./package.json)).version" "$FLAKE_FILE"; then
38+
echo -e "${YELLOW}⚠️ Warning: flake.nix doesn't use dynamic version from package.json${NC}"
39+
echo -e " Expected pattern: version = (builtins.fromJSON (builtins.readFile ./package.json)).version;"
40+
echo ""
41+
fi
42+
43+
# Check if pnpm-lock.yaml exists
44+
if [ ! -f "$PROJECT_ROOT/pnpm-lock.yaml" ]; then
45+
echo -e "${RED}❌ Error: pnpm-lock.yaml not found${NC}"
46+
exit 1
3347
fi
3448

49+
echo -e "${BLUE}🔧 Current pnpm-lock.yaml:${NC} $(stat -c%y "$PROJECT_ROOT/pnpm-lock.yaml" 2>/dev/null || stat -f%Sm "$PROJECT_ROOT/pnpm-lock.yaml")"
50+
echo ""
51+
52+
# Get current hash from flake.nix
53+
CURRENT_HASH=$(sed -nE 's/.*hash = "(sha256-[^"]+)".*/\1/p' "$FLAKE_FILE" | head -1)
54+
echo -e "${BLUE}📌 Current hash:${NC} $CURRENT_HASH"
55+
echo ""
56+
3557
# Set placeholder hash to trigger error
36-
echo " Setting placeholder hash..."
58+
echo -e "${YELLOW}Setting placeholder hash to calculate correct value...${NC}"
3759
PLACEHOLDER="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
3860
sed "${SED_INPLACE[@]}" "s|hash = \"sha256-[^\"]*\"|hash = \"$PLACEHOLDER\"|" "$FLAKE_FILE"
3961

4062
# Try to build and capture the correct hash
41-
echo " Building to get correct hash (this will fail)..."
42-
BUILD_OUTPUT=$(nix build 2>&1 || true)
63+
echo -e "${BLUE}🔨 Building to determine correct hash (expected to fail)...${NC}"
64+
BUILD_OUTPUT=$(nix build --no-link 2>&1 || true)
4365

44-
# Extract the correct hash from error output (portable - works on macOS and Linux)
45-
CORRECT_HASH=$(echo "$BUILD_OUTPUT" | grep -o 'got:[[:space:]]*sha256-[A-Za-z0-9+/=]*' | head -1 | sed 's/got:[[:space:]]*//')
66+
# Extract the correct hash from error output
67+
# Try multiple patterns for compatibility with different Nix versions
68+
CORRECT_HASH=$(echo "$BUILD_OUTPUT" | sed -nE 's/.*got:[[:space:]]*(sha256-[A-Za-z0-9+/=]+).*/\1/p' | head -1)
69+
if [ -z "$CORRECT_HASH" ]; then
70+
CORRECT_HASH=$(echo "$BUILD_OUTPUT" | sed -nE 's/.*got:.*(sha256-[A-Za-z0-9+/=]+).*/\1/p' | head -1)
71+
fi
4672

4773
if [ -z "$CORRECT_HASH" ]; then
48-
echo "❌ Error: Could not extract hash from build output"
49-
echo "Build output:"
74+
echo -e "${RED}❌ Error: Could not extract hash from build output${NC}"
75+
echo ""
76+
echo -e "${YELLOW}Build output:${NC}"
5077
echo "$BUILD_OUTPUT"
78+
echo ""
79+
echo -e "${YELLOW}Restoring original hash...${NC}"
80+
sed "${SED_INPLACE[@]}" "s|hash = \"$PLACEHOLDER\"|hash = \"$CURRENT_HASH\"|" "$FLAKE_FILE"
5181
exit 1
5282
fi
5383

54-
echo " Detected hash: $CORRECT_HASH"
84+
echo -e "${GREEN}✓ Calculated hash:${NC} $CORRECT_HASH"
85+
echo ""
86+
87+
# Check if hash changed
88+
if [ "$CURRENT_HASH" = "$CORRECT_HASH" ]; then
89+
echo -e "${GREEN}✓ Hash is already up-to-date!${NC}"
90+
sed "${SED_INPLACE[@]}" "s|hash = \"$PLACEHOLDER\"|hash = \"$CORRECT_HASH\"|" "$FLAKE_FILE"
91+
echo ""
92+
echo -e "${BLUE}ℹ️ No changes needed. Your flake is in sync with pnpm-lock.yaml${NC}"
93+
exit 0
94+
fi
5595

56-
# Update flake.nix with correct hash
96+
echo -e "${YELLOW}🔄 Updating hash in flake.nix...${NC}"
5797
sed "${SED_INPLACE[@]}" "s|hash = \"$PLACEHOLDER\"|hash = \"$CORRECT_HASH\"|" "$FLAKE_FILE"
5898

5999
# Verify the build works
60-
echo " Verifying build..."
61-
if nix build 2>&1 | grep -q "warning: Git tree.*is dirty"; then
62-
echo "⚠️ Warning: Git tree is dirty, but build succeeded"
100+
echo -e "${BLUE}🔍 Verifying build with new hash...${NC}"
101+
BUILD_OUTPUT=$(nix build --no-link 2>&1) && BUILD_SUCCESS=true || BUILD_SUCCESS=false
102+
if [ "$BUILD_SUCCESS" = false ]; then
103+
echo -e "${RED}❌ Build verification failed!${NC}"
104+
echo ""
105+
echo "$BUILD_OUTPUT"
106+
exit 1
107+
fi
108+
if echo "$BUILD_OUTPUT" | grep -q "warning: Git tree.*is dirty"; then
109+
echo -e "${YELLOW}⚠️ Git tree is dirty, but build succeeded${NC}"
63110
else
64-
echo " Build successful"
111+
echo -e "${GREEN} Build verification successful${NC}"
65112
fi
113+
echo ""
66114

115+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
116+
echo -e "${GREEN}✅ flake.nix updated successfully!${NC}"
117+
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
118+
echo ""
119+
echo -e "${BLUE}📋 Summary:${NC}"
120+
echo -e " Version: $VERSION ${YELLOW}(read dynamically from package.json)${NC}"
121+
echo -e " Old hash: $CURRENT_HASH"
122+
echo -e " New hash: $CORRECT_HASH"
67123
echo ""
68-
echo "✅ flake.nix updated successfully!"
69-
echo " Version: $VERSION"
70-
echo " Hash: $CORRECT_HASH"
124+
echo -e "${BLUE}📝 Next steps:${NC}"
125+
echo -e " 1. Test: ${GREEN}nix run . -- --version${NC}"
126+
echo -e " 2. Verify: ${GREEN}nix flake check${NC}"
127+
echo -e " 3. Commit: ${GREEN}git add flake.nix${NC}"
71128
echo ""
72-
echo "Next steps:"
73-
echo " 1. Test: nix run . -- --version"
74-
echo " 2. Commit: git add flake.nix"
75-
echo " 3. Include in version bump commit"

0 commit comments

Comments
 (0)