Skip to content

Commit ed4d965

Browse files
mipmipTabishB
andauthored
feat: add nix flake support (sorry for this duplicate) (Fission-AI#459)
* add nix flake support * feat: add Nix flake maintenance automation * Add Nix Flake CI Validation * fix updatescript, update flake * make update-script compatible with macos --------- Co-authored-by: Tabish Bidiwale <30385142+TabishB@users.noreply.github.com>
1 parent c86985d commit ed4d965

24 files changed

Lines changed: 1387 additions & 2 deletions

File tree

.actrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-P ubuntu-latest=catthehacker/ubuntu:act-latest

.github/workflows/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Github Workflows
2+
3+
## Testing CI Locally
4+
5+
Test GitHub Actions workflows locally using [act](https://nektosact.com/):
6+
7+
```bash
8+
# Test all PR checks
9+
act pull_request
10+
11+
# Test specific job
12+
act pull_request -j nix-flake-validate
13+
14+
# Dry run to see what would execute
15+
act pull_request --dryrun
16+
```
17+
18+
The `.actrc` file configures act to use the appropriate Docker image.
19+
20+

.github/workflows/ci.yml

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,64 @@ jobs:
156156
exit 1
157157
fi
158158
159+
nix-flake-validate:
160+
name: Nix Flake Validation
161+
runs-on: ubuntu-latest
162+
timeout-minutes: 10
163+
steps:
164+
- name: Checkout code
165+
uses: actions/checkout@v4
166+
167+
- name: Install Nix
168+
uses: DeterminateSystems/nix-installer-action@v13
169+
170+
- name: Setup Nix cache
171+
uses: DeterminateSystems/magic-nix-cache-action@v8
172+
173+
- name: Build with Nix
174+
run: nix build
175+
176+
- name: Verify build output
177+
run: |
178+
if [ ! -e "result" ]; then
179+
echo "Error: Nix build output 'result' symlink not found"
180+
exit 1
181+
fi
182+
if [ ! -f "result/bin/openspec" ]; then
183+
echo "Error: openspec binary not found in build output"
184+
exit 1
185+
fi
186+
echo "✅ Build output verified"
187+
188+
- name: Test binary execution
189+
run: |
190+
VERSION=$(nix run . -- --version)
191+
echo "OpenSpec version: $VERSION"
192+
if [ -z "$VERSION" ]; then
193+
echo "Error: Version command returned empty output"
194+
exit 1
195+
fi
196+
echo "✅ Binary execution successful"
197+
198+
- name: Validate update script
199+
run: |
200+
echo "Testing update-flake.sh script..."
201+
bash scripts/update-flake.sh
202+
echo "✅ Update script executed successfully"
203+
204+
- name: Check flake.nix modifications
205+
run: |
206+
if git diff --quiet flake.nix; then
207+
echo "⚠️ Warning: flake.nix was not modified by update script"
208+
else
209+
echo "✅ flake.nix was updated by script"
210+
git diff flake.nix
211+
fi
212+
213+
- name: Restore flake.nix
214+
if: always()
215+
run: git checkout -- flake.nix || true
216+
159217
validate-changesets:
160218
name: Validate Changesets
161219
runs-on: ubuntu-latest
@@ -191,7 +249,7 @@ jobs:
191249
required-checks-pr:
192250
name: All checks passed
193251
runs-on: ubuntu-latest
194-
needs: [test_pr, lint]
252+
needs: [test_pr, lint, nix-flake-validate]
195253
if: always() && github.event_name == 'pull_request'
196254
steps:
197255
- name: Verify all checks passed
@@ -204,12 +262,16 @@ jobs:
204262
echo "Lint job failed"
205263
exit 1
206264
fi
265+
if [[ "${{ needs.nix-flake-validate.result }}" != "success" ]]; then
266+
echo "Nix flake validation job failed"
267+
exit 1
268+
fi
207269
echo "All required checks passed!"
208270
209271
required-checks-main:
210272
name: All checks passed
211273
runs-on: ubuntu-latest
212-
needs: [test_matrix, lint]
274+
needs: [test_matrix, lint, nix-flake-validate]
213275
if: always() && github.event_name != 'pull_request'
214276
steps:
215277
- name: Verify all checks passed
@@ -222,4 +284,8 @@ jobs:
222284
echo "Lint job failed"
223285
exit 1
224286
fi
287+
if [[ "${{ needs.nix-flake-validate.result }}" != "success" ]]; then
288+
echo "Nix flake validation job failed"
289+
exit 1
290+
fi
225291
echo "All required checks passed!"

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,4 @@ CLAUDE.md
148148

149149
# Pnpm
150150
.pnpm-store/
151+
result

README.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ These tools automatically read workflow instructions from `openspec/AGENTS.md`.
140140

141141
#### Step 1: Install the CLI globally
142142

143+
**Option A: Using npm**
144+
143145
```bash
144146
npm install -g @fission-ai/openspec@latest
145147
```
@@ -149,6 +151,39 @@ Verify installation:
149151
openspec --version
150152
```
151153

154+
**Option B: Using Nix (NixOS and Nix package manager)**
155+
156+
Run OpenSpec directly without installation:
157+
```bash
158+
nix run github:Fission-AI/OpenSpec -- init
159+
```
160+
161+
Or install to your profile:
162+
```bash
163+
nix profile install github:Fission-AI/OpenSpec
164+
```
165+
166+
Or add to your development environment in `flake.nix`:
167+
```nix
168+
{
169+
inputs = {
170+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
171+
openspec.url = "github:Fission-AI/OpenSpec";
172+
};
173+
174+
outputs = { nixpkgs, openspec, ... }: {
175+
devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {
176+
buildInputs = [ openspec.packages.x86_64-linux.default ];
177+
};
178+
};
179+
}
180+
```
181+
182+
Verify installation:
183+
```bash
184+
openspec --version
185+
```
186+
152187
#### Step 2: Initialize OpenSpec in your project
153188

154189
Navigate to your project directory:

flake.lock

Lines changed: 27 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

flake.nix

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
{
2+
description = "OpenSpec - AI-native system for spec-driven development";
3+
4+
inputs = {
5+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6+
};
7+
8+
outputs = { self, nixpkgs }:
9+
let
10+
supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
11+
12+
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
13+
in
14+
{
15+
packages = forAllSystems (system:
16+
let
17+
pkgs = nixpkgs.legacyPackages.${system};
18+
in
19+
{
20+
default = pkgs.stdenv.mkDerivation (finalAttrs: {
21+
pname = "openspec";
22+
version = "0.20.0";
23+
24+
src = ./.;
25+
26+
pnpmDeps = pkgs.fetchPnpmDeps {
27+
inherit (finalAttrs) pname version src;
28+
pnpm = pkgs.pnpm_9;
29+
fetcherVersion = 3;
30+
hash = "sha256-m/7IdY1ou9ljjYAcx3W8AyEJvIZfCBWIWxproQ/INPA=";
31+
};
32+
33+
nativeBuildInputs = with pkgs; [
34+
nodejs_20
35+
npmHooks.npmInstallHook
36+
pnpmConfigHook
37+
pnpm_9
38+
];
39+
40+
buildPhase = ''
41+
runHook preBuild
42+
43+
pnpm run build
44+
45+
runHook postBuild
46+
'';
47+
48+
dontNpmPrune = true;
49+
50+
meta = with pkgs.lib; {
51+
description = "AI-native system for spec-driven development";
52+
homepage = "https://github.com/Fission-AI/OpenSpec";
53+
license = licenses.mit;
54+
maintainers = [ ];
55+
mainProgram = "openspec";
56+
};
57+
});
58+
});
59+
60+
apps = forAllSystems (system: {
61+
default = {
62+
type = "app";
63+
program = "${self.packages.${system}.default}/bin/openspec";
64+
};
65+
});
66+
67+
devShells = forAllSystems (system:
68+
let
69+
pkgs = nixpkgs.legacyPackages.${system};
70+
in
71+
{
72+
default = pkgs.mkShell {
73+
buildInputs = with pkgs; [
74+
nodejs_20
75+
pnpm_9
76+
];
77+
78+
shellHook = ''
79+
echo "OpenSpec development environment"
80+
echo "Node version: $(node --version)"
81+
echo "pnpm version: $(pnpm --version)"
82+
echo "Run 'pnpm install' to install dependencies"
83+
'';
84+
};
85+
});
86+
};
87+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-01-07
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
## Context
2+
3+
OpenSpec is a TypeScript CLI tool using pnpm for dependency management. The project requires Node.js ≥20.19.0. Nix uses its own build system that needs to understand how to fetch dependencies and build the project reproducibly.
4+
5+
The Nix ecosystem has specific patterns for packaging Node.js/pnpm projects that differ from the traditional npm ecosystem.
6+
7+
## Goals
8+
9+
- Enable OpenSpec to be run directly via `nix run github:Fission-AI/OpenSpec`
10+
- Support all major platforms (Linux x86/ARM, macOS x86/ARM)
11+
- Use existing pnpm-lock.yaml for reproducible builds
12+
- Provide development environment for Nix users
13+
14+
## Non-Goals
15+
16+
- Replace existing npm/pnpm publishing workflow
17+
- Publish to nixpkgs (can be done later as separate effort)
18+
- Support Windows (Nix doesn't run natively on Windows)
19+
20+
## Decisions
21+
22+
### Use stdenv.mkDerivation instead of buildNpmPackage
23+
24+
**Decision**: Package OpenSpec using `stdenv.mkDerivation` with pnpm hooks.
25+
26+
**Rationale**: The zigbee2mqtt package in nixpkgs demonstrates the current best practice for pnpm projects. Using `buildNpmPackage` with pnpm requires complex configuration, while `mkDerivation` with the right hooks is more straightforward and better supported.
27+
28+
**Alternative considered**: Using `buildNpmPackage` with `npmConfigHook = pkgs.pnpmConfigHook` - this is the older pattern and causes issues with dependency fetching.
29+
30+
### Use fetchPnpmDeps with explicit pnpm version
31+
32+
**Decision**: Use `pkgs.fetchPnpmDeps` with `pnpm = pkgs.pnpm_9` and `fetcherVersion = 3`.
33+
34+
**Rationale**:
35+
- pnpm lockfile version 9.0 requires fetcherVersion 3
36+
- Explicit pnpm_9 ensures consistency between fetch and build
37+
- This is the documented way to handle pnpm projects in nixpkgs
38+
39+
### Multi-platform support without flake-utils
40+
41+
**Decision**: Implement multi-platform support using plain Nix with `nixpkgs.lib.genAttrs`.
42+
43+
**Rationale**: Per user request, avoid extra dependencies. The `genAttrs` pattern is simple and well-understood in the Nix community.
44+
45+
### Node.js 20 instead of latest
46+
47+
**Decision**: Pin to nodejs_20 to match package.json engines requirement.
48+
49+
**Rationale**: Ensures consistency with development environment and npm package requirements. Avoids potential compatibility issues with newer Node versions.
50+
51+
## Key Implementation Details
52+
53+
### Dependency Hash Management
54+
55+
The `pnpmDeps.hash` field must be updated whenever dependencies change. The workflow:
56+
1. Set hash to fake value (all zeros)
57+
2. Run `nix build`
58+
3. Nix fails with actual hash
59+
4. Update flake.nix with correct hash
60+
61+
This is standard Nix workflow for fixed-output derivations.
62+
63+
### Build Inputs
64+
65+
Required nativeBuildInputs:
66+
- `nodejs_20` - runtime
67+
- `npmHooks.npmInstallHook` - handles installation phase
68+
- `pnpmConfigHook` - configures pnpm environment
69+
- `pnpm_9` - pnpm executable
70+
71+
The `dontNpmPrune = true` is important to keep all dependencies after build.
72+
73+
## Risks / Trade-offs
74+
75+
**[Risk]** Hash needs updating when dependencies change → **Mitigation**: Document this clearly; error message from Nix provides correct hash
76+
77+
**[Risk]** Nix builds might lag behind npm releases → **Mitigation**: This is fine; Nix users can still use npm if they need bleeding edge
78+
79+
**[Trade-off]** Additional maintenance burden for hash updates → **Benefit**: Better experience for Nix ecosystem users
80+
81+
## Migration Plan
82+
83+
1. Add flake.nix to repository
84+
2. Test builds on multiple platforms (can use GitHub Actions with Nix)
85+
3. Update README with Nix installation instructions
86+
4. Optionally add to CI pipeline to catch hash mismatches early
87+
88+
No breaking changes - this is purely additive.
89+
90+
## Open Questions
91+
92+
- Should we add automatic hash updating to CI? (Could use nix-update-script)
93+
- Should we submit to nixpkgs after validation? (Separate decision)
94+
- Do we want to support older Node versions in flake? (Probably no - stick to package.json requirement)

0 commit comments

Comments
 (0)