Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/nix-flake-improvements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@fission-ai/openspec": patch
---

### Improvements

- **Nix flake maintenance** — Version now read dynamically from package.json, reducing manual sync issues
- **Nix build optimization** — Source filtering excludes node_modules and artifacts, improving build times
- **update-flake.sh script** — Detects when hash is already correct, skipping unnecessary rebuilds

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention the output improvement, including colored text for updates and warnings.

### Other

- Updated Nix CI actions to latest versions (nix-installer v21, magic-nix-cache v13)
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,10 @@ jobs:
uses: actions/checkout@v4

- name: Install Nix
uses: DeterminateSystems/nix-installer-action@v13
uses: DeterminateSystems/nix-installer-action@v21

- name: Setup Nix cache
uses: DeterminateSystems/magic-nix-cache-action@v8
uses: DeterminateSystems/magic-nix-cache-action@v13

- name: Build with Nix
run: nix build
Expand Down Expand Up @@ -229,7 +229,7 @@ jobs:
- name: Check flake.nix modifications
run: |
if git diff --quiet flake.nix; then
echo "⚠️ Warning: flake.nix was not modified by update script"
echo "ℹ️ flake.nix unchanged (hash already up-to-date)"
else
echo "✅ flake.nix was updated by script"
git diff flake.nix
Expand Down
43 changes: 35 additions & 8 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,47 @@
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
};

outputs = { self, nixpkgs }:
outputs =
{ self, nixpkgs }:
let
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];

forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
in
{
packages = forAllSystems (system:
packages = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
inherit (pkgs) lib;
in
{
default = pkgs.stdenv.mkDerivation (finalAttrs: {
pname = "openspec";
version = "0.23.0";
version = (builtins.fromJSON (builtins.readFile ./package.json)).version;

src = ./.;
src = lib.fileset.toSource {
root = ./.;
fileset = lib.fileset.unions [
./src
./bin
./schemas
./scripts
./test
./package.json
./pnpm-lock.yaml
./tsconfig.json
./build.js
./vitest.config.ts
./vitest.setup.ts
./eslint.config.js
];
};

pnpmDeps = pkgs.fetchPnpmDeps {
inherit (finalAttrs) pname version src;
Expand Down Expand Up @@ -55,7 +79,8 @@
mainProgram = "openspec";
};
});
});
}
);

apps = forAllSystems (system: {
default = {
Expand All @@ -64,7 +89,8 @@
};
});

devShells = forAllSystems (system:
devShells = forAllSystems (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
Expand All @@ -82,6 +108,7 @@
echo "Run 'pnpm install' to install dependencies"
'';
};
});
}
);
};
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
## ADDED Requirements

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

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

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

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

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

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

#### Scenario: Success summary
- **WHEN** script completes successfully
- **THEN** summary shows updated version and hash
- **AND** next steps are displayed (test, commit, etc.)
- **THEN** summary shows version and hash changes
- **AND** next steps are displayed (test, verify, commit)

#### Scenario: No changes needed
- **WHEN** hash is already up-to-date
- **THEN** script reports no changes needed
- **AND** exits with success code 0

### Requirement: Script Safety
The script SHALL fail fast on errors and use safe defaults.
Expand Down
4 changes: 2 additions & 2 deletions openspec/specs/ci-nix-validation/spec.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ The CI system SHALL validate that the update-flake.sh script executes successful

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

#### Scenario: Update script with mock hash

Expand Down
17 changes: 8 additions & 9 deletions scripts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,28 @@ Utility scripts for OpenSpec maintenance and development.

## update-flake.sh

Updates `flake.nix` version and dependency hash automatically.
Updates `flake.nix` pnpm dependency hash automatically.

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

**Usage**:
```bash
./scripts/update-flake.sh
```

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

**Example workflow**:
```bash
# After version bump and dependency updates
# After dependency updates
pnpm install
./scripts/update-flake.sh
git add flake.nix
git commit -m "chore: update flake.nix for v0.18.0"
git commit -m "chore: update flake.nix dependency hash"
```

## postinstall.js
Expand Down
113 changes: 83 additions & 30 deletions scripts/update-flake.sh
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail

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

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

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color

# Detect OS and set sed in-place flag
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS (BSD sed) requires empty string argument for -i
Expand All @@ -18,58 +26,103 @@ else
SED_INPLACE=(-i)
fi

echo "==> Updating flake.nix..."
echo -e "${BLUE}==> Updating flake.nix pnpm dependency hash...${NC}"
echo ""

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

# Update version in flake.nix
if ! grep -q "version = \"$VERSION\"" "$FLAKE_FILE"; then
echo " Updating version in flake.nix..."
sed "${SED_INPLACE[@]}" "s|version = \"[^\"]*\"|version = \"$VERSION\"|" "$FLAKE_FILE"
else
echo " Version already up-to-date in flake.nix"
# Verify flake.nix uses dynamic version
if ! grep -q "(builtins.fromJSON (builtins.readFile ./package.json)).version" "$FLAKE_FILE"; then
echo -e "${YELLOW}⚠️ Warning: flake.nix doesn't use dynamic version from package.json${NC}"
echo -e " Expected pattern: version = (builtins.fromJSON (builtins.readFile ./package.json)).version;"
echo ""
fi

# Check if pnpm-lock.yaml exists
if [ ! -f "$PROJECT_ROOT/pnpm-lock.yaml" ]; then
echo -e "${RED}❌ Error: pnpm-lock.yaml not found${NC}"
exit 1
fi

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")"
echo ""

# Get current hash from flake.nix
CURRENT_HASH=$(sed -nE 's/.*hash = "(sha256-[^"]+)".*/\1/p' "$FLAKE_FILE" | head -1)
echo -e "${BLUE}📌 Current hash:${NC} $CURRENT_HASH"
echo ""

# Set placeholder hash to trigger error
echo " Setting placeholder hash..."
echo -e "${YELLOW}⏳ Setting placeholder hash to calculate correct value...${NC}"
PLACEHOLDER="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
sed "${SED_INPLACE[@]}" "s|hash = \"sha256-[^\"]*\"|hash = \"$PLACEHOLDER\"|" "$FLAKE_FILE"

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

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

if [ -z "$CORRECT_HASH" ]; then
echo "❌ Error: Could not extract hash from build output"
echo "Build output:"
echo -e "${RED}❌ Error: Could not extract hash from build output${NC}"
echo ""
echo -e "${YELLOW}Build output:${NC}"
echo "$BUILD_OUTPUT"
echo ""
echo -e "${YELLOW}Restoring original hash...${NC}"
sed "${SED_INPLACE[@]}" "s|hash = \"$PLACEHOLDER\"|hash = \"$CURRENT_HASH\"|" "$FLAKE_FILE"
exit 1
fi

echo " Detected hash: $CORRECT_HASH"
echo -e "${GREEN}✓ Calculated hash:${NC} $CORRECT_HASH"
echo ""

# Check if hash changed
if [ "$CURRENT_HASH" = "$CORRECT_HASH" ]; then
echo -e "${GREEN}✓ Hash is already up-to-date!${NC}"
sed "${SED_INPLACE[@]}" "s|hash = \"$PLACEHOLDER\"|hash = \"$CORRECT_HASH\"|" "$FLAKE_FILE"
echo ""
echo -e "${BLUE}ℹ️ No changes needed. Your flake is in sync with pnpm-lock.yaml${NC}"
exit 0
fi

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

# Verify the build works
echo " Verifying build..."
if nix build 2>&1 | grep -q "warning: Git tree.*is dirty"; then
echo "⚠️ Warning: Git tree is dirty, but build succeeded"
echo -e "${BLUE}🔍 Verifying build with new hash...${NC}"
BUILD_OUTPUT=$(nix build --no-link 2>&1) && BUILD_SUCCESS=true || BUILD_SUCCESS=false
if [ "$BUILD_SUCCESS" = false ]; then
echo -e "${RED}❌ Build verification failed!${NC}"
echo ""
echo "$BUILD_OUTPUT"
exit 1
fi
if echo "$BUILD_OUTPUT" | grep -q "warning: Git tree.*is dirty"; then
echo -e "${YELLOW}⚠️ Git tree is dirty, but build succeeded${NC}"
else
echo "✅ Build successful"
echo -e "${GREEN}✓ Build verification successful${NC}"
fi
echo ""

echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}✅ flake.nix updated successfully!${NC}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo ""
echo -e "${BLUE}📋 Summary:${NC}"
echo -e " Version: $VERSION ${YELLOW}(read dynamically from package.json)${NC}"
echo -e " Old hash: $CURRENT_HASH"
echo -e " New hash: $CORRECT_HASH"
echo ""
echo "✅ flake.nix updated successfully!"
echo " Version: $VERSION"
echo " Hash: $CORRECT_HASH"
echo -e "${BLUE}📝 Next steps:${NC}"
echo -e " 1. Test: ${GREEN}nix run . -- --version${NC}"
echo -e " 2. Verify: ${GREEN}nix flake check${NC}"
echo -e " 3. Commit: ${GREEN}git add flake.nix${NC}"
echo ""
echo "Next steps:"
echo " 1. Test: nix run . -- --version"
echo " 2. Commit: git add flake.nix"
echo " 3. Include in version bump commit"