Skip to content
Merged
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
12 changes: 11 additions & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,17 @@ jobs:
- name: Build (cross-platform — no POSIX rm/chmod)
run: npm run build
- name: Unit tests
run: npm test
continue-on-error: true
shell: bash
run: |
set -o pipefail
npm test 2>&1 | tee win-tests.log
- name: Upload Windows test log (full output — gh logs truncate)
if: always()
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: windows-test-log
path: win-tests.log
- name: kit smoke (init / check) — gaps surface here, non-blocking
continue-on-error: true
run: |
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).

## [Unreleased]

## [1.29.1] - 2026-06-24

### Fixed

- **Tests run on native Windows (#43).** The `test` script set env via POSIX inline vars (`KIT_NON_INTERACTIVE=1 … node`), which Windows cmd/pwsh can't parse → the suite never started. Replaced with a no-dep `scripts/test.mjs` (sets env in-process, collects `dist/**/*.test.js` itself — no shell-glob — runs `node --test`). Also: `secrets-sync` used a literal `/dev/null` (→ `D:\dev\null` ENOENT on Windows) → now `os.devNull`.
- **Windows probe is diagnosable.** The `windows-latest` workflow now tees the test output to a downloadable artifact (gh's CI logs truncate it), with `pipefail` + `continue-on-error`.

### Notes

- Real `windows-latest` status after this: builds ✓, **1526/1542 unit tests pass** (was: tests couldn't start). The remaining 15 are characterized on #43 — POSIX-path/`startsWith("/")` test assumptions + chmod/`0o600` permission semantics (the latter needs a Windows-ACL decision).

## [1.29.0] - 2026-06-24

### Added
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sandstream-kit",
"version": "1.29.0",
"version": "1.29.1",
"description": "developer kit. zero LLM, local-first, multi-vault. one command from git clone to working dev environment.",
"license": "MIT",
"funding": "https://buymeacoffee.com/sandstream",
Expand Down Expand Up @@ -30,7 +30,7 @@
"build:prod": "npm run build --workspace=packages/adapter-sdk && npm run build --workspace=packages/kit-plugin-railway && npm run build --workspace=packages/kit-plugin-supabase && npm run build --workspace=packages/kit-plugin-vercel && npm run build --workspace=packages/kit-plugin-github && npm run build --workspace=packages/kit-plugin-stripe && npm run build --workspace=packages/kit-plugin-fly && npm run build --workspace=packages/kit-plugin-cloudflare && npm run build --workspace=packages/kit-plugin-snyk && npm run build --workspace=packages/kit-plugin-wiz && npm run build --workspace=packages/kit-plugin-sentry && node scripts/clean-dist.mjs && tsc -p tsconfig.prod.json && node scripts/chmod-cli.mjs",
"prepublishOnly": "npm run build:prod",
"dev": "tsx src/cli.ts",
"test": "KIT_NON_INTERACTIVE=1 KIT_BUMBLEBEE=0 KIT_NO_FAILURE_SIM=1 KIT_NO_UPDATE_CHECK=1 node --test --test-timeout=180000 --test-concurrency=2 dist/*.test.js dist/**/*.test.js",
"test": "node scripts/test.mjs",
"lint": "eslint src",
"format": "prettier --write \"**/*.ts\"",
"format:check": "prettier --check \"**/*.ts\""
Expand Down
45 changes: 45 additions & 0 deletions scripts/test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Cross-platform test runner (#43). POSIX inline env-vars (`FOO=1 node …`) don't
// work in Windows cmd/pwsh — they're parsed as a command and fail. So set the env
// here, collect the compiled test files ourselves (no shell-glob dependency, which
// also differs across shells), and invoke `node --test`. No external dep.
import { spawnSync } from "node:child_process";
import { readdirSync, existsSync } from "node:fs";
import { join } from "node:path";

const env = {
...process.env,
KIT_NON_INTERACTIVE: "1",
KIT_BUMBLEBEE: "0",
KIT_NO_FAILURE_SIM: "1",
KIT_NO_UPDATE_CHECK: "1",
};

if (!existsSync("dist")) {
console.error("dist/ not found — run `npm run build` first");
process.exit(1);
}

// Recursively collect compiled .test.js files under dist/ (deterministic; no
// shell/library glob expansion involved).
function collect(dir) {
const out = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
const p = join(dir, entry.name);
if (entry.isDirectory()) out.push(...collect(p));
else if (entry.name.endsWith(".test.js")) out.push(p);
}
return out;
}

const files = collect("dist");
if (files.length === 0) {
console.error("no dist/**/*.test.js files found");
process.exit(1);
}

const result = spawnSync(
process.execPath,
["--test", "--test-timeout=180000", "--test-concurrency=2", ...files],
{ stdio: "inherit", env },
);
process.exit(result.status ?? 1);
7 changes: 5 additions & 2 deletions src/secrets-sync.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { writeFile } from "node:fs/promises";
import { resolve } from "node:path";
import { devNull } from "node:os";
import type { SecretsConfig } from "./config.js";
import { generateSecrets } from "./secrets.js";
import { exec } from "./utils/exec.js";
Expand Down Expand Up @@ -32,8 +33,10 @@ export async function syncSecrets(
): Promise<SyncResult> {
const { target, dryRun = false, projectPath = process.cwd() } = options;

// Resolve all secrets using the existing generate logic (reads from stores)
const { results } = await generateSecrets(secrets, "/dev/null");
// Resolve all secrets using the existing generate logic (reads from stores).
// Discard the written output to the platform null device (os.devNull): a literal
// "/dev/null" becomes "D:\dev\null" on Windows → ENOENT (#43).
const { results } = await generateSecrets(secrets, devNull);

const resolved: Record<string, string> = {};
for (const r of results) {
Expand Down
Loading