sf snapshot: uncommitted changes after 43m inactivity
This commit is contained in:
parent
54bfd68b01
commit
d75ebfe7c3
44 changed files with 121 additions and 2977 deletions
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.13/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@
|
|||
| **Skills** | Skill tool registration, health, telemetry |
|
||||
| **Slash Commands** | Command boilerplate generators extension |
|
||||
| **State Machine** | State, history, persistence, reactive graph |
|
||||
| **Studio App** | Electron desktop app (renderer, main, preload) |
|
||||
| **Subagent** | Parallel/serial subagent delegation |
|
||||
| **Syntax Highlighting** | Syntect-backed ANSI code coloring |
|
||||
| **Text Processing** | Diff, truncation, HTML→MD, ANSI, JSON parse |
|
||||
|
|
@ -833,20 +832,6 @@
|
|||
|
||||
---
|
||||
|
||||
## studio/ — Electron Desktop App
|
||||
|
||||
| File | System Label(s) | Description |
|
||||
|------|-----------------|-------------|
|
||||
| studio/electron.vite.config.ts | Studio App, Build System | Electron Vite build configuration |
|
||||
| studio/src/main/index.ts | Studio App | Electron main process window creation |
|
||||
| studio/src/preload/index.ts | Studio App | Context isolation preload for IPC bridge |
|
||||
| studio/src/preload/index.d.ts | Studio App | Preload bridge type definitions |
|
||||
| studio/src/renderer/src/main.tsx | Studio App | React renderer entry point |
|
||||
| studio/src/renderer/src/App.tsx | Studio App | Main app component |
|
||||
| studio/src/renderer/src/lib/theme/tokens.ts | Studio App | Design tokens (colors, fonts, sizes) |
|
||||
|
||||
---
|
||||
|
||||
## rust-engine/ — Rust Engine
|
||||
|
||||
| File | System Label(s) | Description |
|
||||
|
|
@ -1005,7 +990,6 @@ Quick lookup: which files are part of each system?
|
|||
| **Skills** | src/resources/skills/*, sf/skill-telemetry.ts, sf/preferences-skills.ts, core/skills.ts |
|
||||
| **Slash Commands** | src/resources/extensions/slash-commands/* |
|
||||
| **State Machine** | sf/state.ts, sf/history.ts, sf/json-persistence.ts, sf/memory-store.ts, sf/reactive-graph.ts, core/agent-session.ts, web/lib/sf-workspace-store.tsx |
|
||||
| **Studio App** | studio/* |
|
||||
| **Subagent** | src/resources/extensions/subagent/*, src/resources/agents/* |
|
||||
| **Syntax Highlighting** | rust-engine/crates/engine/src/highlight.rs, packages/rust-engine/src/highlight/* |
|
||||
| **Text Processing** | rust-engine/crates/engine/src/diff.rs, html.rs, text.rs, truncate.rs, json_parse.rs, stream_process.rs |
|
||||
|
|
|
|||
2345
package-lock.json
generated
2345
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -13,8 +13,7 @@
|
|||
},
|
||||
"type": "module",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
"studio"
|
||||
"packages/*"
|
||||
],
|
||||
"bin": {
|
||||
"sf": "dist/loader.js",
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ console.log(`[bump-version] package.json: ${oldVersion} → ${newVersion}`);
|
|||
|
||||
// 2. Update all non-private workspace packages under packages/
|
||||
// These share the root version to keep the repo's source of truth coherent
|
||||
// with what ships. Private packages (studio, web) are skipped — they're not
|
||||
// published and have their own lifecycle.
|
||||
// with what ships. The web package is private and has its own lifecycle.
|
||||
const workspacePackages = [
|
||||
"daemon",
|
||||
"native",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
*/
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
|
||||
const CONTRACT_EXACT_PATHS = new Set([
|
||||
"src/resources/extensions/sf/workflow-templates/registry.json",
|
||||
|
|
@ -27,7 +27,7 @@ function trackedJsonFiles() {
|
|||
return out
|
||||
.split("\n")
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
.filter((line) => line && existsSync(line));
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`failed to list tracked JSON files: ${message}`);
|
||||
|
|
@ -84,6 +84,15 @@ export function checkJsonPolicy(paths, readText) {
|
|||
try {
|
||||
parsed = JSON.parse(readText(path));
|
||||
} catch (error) {
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
"code" in error &&
|
||||
error.code === "ENOENT"
|
||||
) {
|
||||
filesParsed--;
|
||||
continue;
|
||||
}
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
failures.push(`${path}: invalid JSON (${message})`);
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,20 @@ test("check-versioned-json: parses every JSON file", () => {
|
|||
assert.equal(result.filesParsed, 2);
|
||||
});
|
||||
|
||||
test("check-versioned-json: skips deleted tracked files", () => {
|
||||
const result = checkJsonPolicy(["deleted.json", "package.json"], (path) => {
|
||||
if (path === "deleted.json") {
|
||||
const error = new Error("ENOENT");
|
||||
error.code = "ENOENT";
|
||||
throw error;
|
||||
}
|
||||
return '{"version":"1.0.0"}';
|
||||
});
|
||||
|
||||
assert.deepEqual(result.failures, []);
|
||||
assert.equal(result.filesParsed, 1);
|
||||
});
|
||||
|
||||
test("check-versioned-json: requires numeric schemaVersion for SF contracts", () => {
|
||||
const files = {
|
||||
"src/resources/extensions/sf/learning/data/unit-weights.json":
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
/**
|
||||
* Migrate ALL test files from node:test to vitest.
|
||||
*
|
||||
* Scans src/, packages/, web/, studio/, and scripts/.
|
||||
* Scans src/, packages/, web/, and scripts/.
|
||||
* Changes:
|
||||
* 1. Replace `from "node:test"` → `from 'vitest'` in all imports
|
||||
* 2. Files using mock.fn(): replace `mock.fn` → `vi.fn` and add `vi` to imports
|
||||
|
|
@ -16,7 +16,6 @@ const ROOTS = [
|
|||
join(process.cwd(), "src"),
|
||||
join(process.cwd(), "packages"),
|
||||
join(process.cwd(), "web"),
|
||||
join(process.cwd(), "studio"),
|
||||
join(process.cwd(), "scripts"),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ const RISK_TIERS = {
|
|||
"Migration",
|
||||
"Onboarding",
|
||||
"Memory Extension",
|
||||
"Studio App",
|
||||
"VS Code Extension",
|
||||
"Voice",
|
||||
"CMux",
|
||||
|
|
|
|||
|
|
@ -180,10 +180,9 @@ function readManagedResourceManifest(
|
|||
* bundled resourcesDir).
|
||||
*
|
||||
* Walks all files under `rootDir` and builds an aggregate fingerprint from
|
||||
* `${relativePath}:${mtime}:${size}` for each one. This is orders of magnitude
|
||||
* faster than full content hashing for large resource trees (1,700+ files)
|
||||
* while still reliably detecting changes during development (npm link) and
|
||||
* after SF version upgrades.
|
||||
* `${relativePath}:${sha256(contents)}` for each one. Content hashing is required
|
||||
* because same-byte-length prompt/resource edits must still invalidate the
|
||||
* installed resource cache.
|
||||
*
|
||||
* Cost is ~1-5ms even for large trees — negligible at startup.
|
||||
*
|
||||
|
|
@ -210,13 +209,11 @@ function collectFileEntries(dir: string, root: string, out: string[]): void {
|
|||
collectFileEntries(fullPath, root, out);
|
||||
} else {
|
||||
const rel = relative(root, fullPath);
|
||||
// Use mtime and size for the fingerprint instead of full content hashing (#3471).
|
||||
// This is orders of magnitude faster for large resource trees (1700+ files)
|
||||
// while still reliably detecting dev-workflow changes and upgrades.
|
||||
let fingerprint: string;
|
||||
try {
|
||||
const stats = lstatSync(fullPath);
|
||||
fingerprint = `${stats.mtimeMs}:${stats.size}`;
|
||||
fingerprint = createHash("sha256")
|
||||
.update(readFileSync(fullPath))
|
||||
.digest("hex");
|
||||
} catch {
|
||||
// Unreadable file — fall back to a stable marker so the entry still
|
||||
// contributes to the aggregate hash and future reads will re-hash.
|
||||
|
|
|
|||
|
|
@ -43,20 +43,19 @@ import { createRequire } from "node:module";
|
|||
|
||||
const require = createRequire(import.meta.url);
|
||||
const jiti = require("jiti")(__dirname, { interopDefault: true, debug: false });
|
||||
const { EVALUATE_HELPERS_SOURCE } = jiti("../evaluate-helpers.ts");
|
||||
const { EVALUATE_HELPERS_SOURCE } = jiti("../evaluate-helpers.js");
|
||||
|
||||
// 2. Intent scoring — module-private buildIntentScoringScript.
|
||||
// Extract the function from source, wrap it, and eval to get the builder.
|
||||
const intentSource = readFileSync(resolve(ROOT, "tools/intent.ts"), "utf-8");
|
||||
const intentSource = readFileSync(resolve(ROOT, "tools/intent.js"), "utf-8");
|
||||
|
||||
function extractBuildIntentScoringScript() {
|
||||
// Match the function body: starts with "function buildIntentScoringScript"
|
||||
// and returns a template literal string. We extract up to the matching closing brace.
|
||||
const startMarker =
|
||||
"function buildIntentScoringScript(intent: string, scope?: string): string {";
|
||||
const startMarker = "function buildIntentScoringScript(intent, scope) {";
|
||||
const startIdx = intentSource.indexOf(startMarker);
|
||||
if (startIdx === -1)
|
||||
throw new Error("Could not find buildIntentScoringScript in intent.ts");
|
||||
throw new Error("Could not find buildIntentScoringScript in intent.js");
|
||||
|
||||
// Walk from start, counting braces to find the end
|
||||
let depth = 0;
|
||||
|
|
@ -86,14 +85,13 @@ function extractBuildIntentScoringScript() {
|
|||
const buildIntentScoringScript = extractBuildIntentScoringScript();
|
||||
|
||||
// 3. Form analysis — module-private buildFormAnalysisScript.
|
||||
const formsSource = readFileSync(resolve(ROOT, "tools/forms.ts"), "utf-8");
|
||||
const formsSource = readFileSync(resolve(ROOT, "tools/forms.js"), "utf-8");
|
||||
|
||||
function extractBuildFormAnalysisScript() {
|
||||
const startMarker =
|
||||
"function buildFormAnalysisScript(selector?: string): string {";
|
||||
const startMarker = "function buildFormAnalysisScript(selector) {";
|
||||
const startIdx = formsSource.indexOf(startMarker);
|
||||
if (startIdx === -1)
|
||||
throw new Error("Could not find buildFormAnalysisScript in forms.ts");
|
||||
throw new Error("Could not find buildFormAnalysisScript in forms.js");
|
||||
|
||||
let depth = 0;
|
||||
let foundFirst = false;
|
||||
|
|
|
|||
|
|
@ -84,13 +84,17 @@ export function appendNotification(
|
|||
) {
|
||||
if (!_basePath) return;
|
||||
if (_suppressCount > 0) return;
|
||||
const normalizedSeverity = severity === "warn" ? "warning" : severity;
|
||||
const persistedMessage =
|
||||
message.length > 500 ? message.slice(0, 500) + "…" : message;
|
||||
if (!shouldPersistNotification(severity, metadata, persistedMessage)) return;
|
||||
if (
|
||||
!shouldPersistNotification(normalizedSeverity, metadata, persistedMessage)
|
||||
)
|
||||
return;
|
||||
// Use explicit dedupe_key when provided; fall back to message-hash based key.
|
||||
const dedupKey = metadata?.dedupe_key
|
||||
? `${_basePath}:${metadata.dedupe_key}`
|
||||
: `${_basePath}:${severity}:${source}:${persistedMessage}`;
|
||||
: `${_basePath}:${normalizedSeverity}:${source}:${persistedMessage}`;
|
||||
const now = Date.now();
|
||||
const lastSeen = _recentMessageTimestamps.get(dedupKey);
|
||||
if (lastSeen !== undefined && now - lastSeen <= DEDUP_WINDOW_MS) return;
|
||||
|
|
@ -103,7 +107,8 @@ export function appendNotification(
|
|||
if (
|
||||
hasRecentPersistedDuplicate(
|
||||
_basePath,
|
||||
metadata?.dedupe_key ?? `${severity}:${source}:${persistedMessage}`,
|
||||
metadata?.dedupe_key ??
|
||||
`${normalizedSeverity}:${source}:${persistedMessage}`,
|
||||
now,
|
||||
)
|
||||
) {
|
||||
|
|
@ -112,7 +117,7 @@ export function appendNotification(
|
|||
const entry = {
|
||||
id: randomUUID(),
|
||||
ts: new Date().toISOString(),
|
||||
severity,
|
||||
severity: normalizedSeverity,
|
||||
message: persistedMessage,
|
||||
source,
|
||||
read: false,
|
||||
|
|
|
|||
|
|
@ -23,25 +23,25 @@ test("assistant-message caps thinking block height when text content is present"
|
|||
|
||||
assert.match(
|
||||
src,
|
||||
/const hasTextContent = message\.content\.some\(\(c\) => c\.type === "text" && c\.text\.trim\(\)\.length > 0\);/,
|
||||
/const hasTextContent = message\.content\.some\(\s*\(c\) => c\.type === "text" && c\.text\.trim\(\)\.length > 0,\s*\);/,
|
||||
"assistant-message should detect text presence in mixed thinking+text messages",
|
||||
);
|
||||
|
||||
assert.match(
|
||||
src,
|
||||
/const hasToolContent = message\.content\.some\(\(c\) => c\.type === "toolCall" \|\| c\.type === "serverToolUse"\);/,
|
||||
/const hasToolContent = message\.content\.some\(\s*\(c\) => c\.type === "toolCall" \|\| c\.type === "serverToolUse",\s*\);/,
|
||||
"assistant-message should detect tool blocks in mixed turns",
|
||||
);
|
||||
|
||||
assert.match(
|
||||
src,
|
||||
/const shouldCapThinking = hasTextContent \|\| hasToolContent \|\| message\.provider === "claude-code";/,
|
||||
/const shouldCapThinking =\s*hasTextContent \|\| hasToolContent \|\| message\.provider === "claude-code";/,
|
||||
"assistant-message should derive a cap policy that also covers claude-code long reasoning traces",
|
||||
);
|
||||
|
||||
assert.match(
|
||||
src,
|
||||
/if \(shouldCapThinking\)\s*\{\s*thinkingMarkdown\.maxLines = 8;\s*\}/s,
|
||||
/if \(shouldCapThinking\) \{\s*thinkingMarkdown\.maxLines = 8;\s*\}/s,
|
||||
"assistant-message should cap visible thinking lines when the cap policy is active",
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,21 +7,15 @@ import {
|
|||
parseBundledExtensions,
|
||||
parseKnownProviders,
|
||||
parseSearchProviders,
|
||||
parseWorkflowToolNames,
|
||||
START,
|
||||
updateFeaturesContent,
|
||||
} from "../../scripts/generate-features-inventory.mjs";
|
||||
|
||||
test("features inventory generator surfaces expected workflow tool, extension, search, and provider inventories", () => {
|
||||
const workflowTools = parseWorkflowToolNames();
|
||||
const extensions = parseBundledExtensions();
|
||||
const searchProviders = parseSearchProviders();
|
||||
const knownProviders = parseKnownProviders();
|
||||
|
||||
assert.ok(workflowTools.includes("sf_plan_milestone"));
|
||||
assert.ok(workflowTools.includes("sf_replan_slice"));
|
||||
assert.ok(workflowTools.includes("sf_task_complete"));
|
||||
|
||||
assert.ok(extensions.includes("sf"));
|
||||
assert.ok(extensions.includes("search-the-web"));
|
||||
assert.ok(extensions.includes("subagent"));
|
||||
|
|
@ -54,13 +48,12 @@ test("features inventory generator injects a rendered appendix between markers",
|
|||
assert.match(
|
||||
updated,
|
||||
new RegExp(
|
||||
`${START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n\\n### Workflow Tools`,
|
||||
`${START.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\n\\n### Bundled Extensions`,
|
||||
),
|
||||
);
|
||||
assert.match(updated, /### Bundled Extensions/);
|
||||
assert.match(updated, /### Search Providers/);
|
||||
assert.match(updated, /### Known Model Providers/);
|
||||
assert.match(updated, /- `sf_plan_milestone`/);
|
||||
assert.match(updated, /- `search-the-web` — \[extension-manifest\.json]/);
|
||||
assert.match(updated, /- `brave`/);
|
||||
assert.match(updated, /- `xiaomi`/);
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ test("npm pack produces tarball with required files", async () => {
|
|||
"tarball contains pkg/package.json",
|
||||
);
|
||||
assert.ok(
|
||||
files.some((f) => f.includes("src/resources/extensions/sf/index.ts")),
|
||||
files.some((f) => f.includes("src/resources/extensions/sf/index.js")),
|
||||
"tarball contains bundled sf extension",
|
||||
);
|
||||
assert.ok(
|
||||
|
|
@ -271,7 +271,7 @@ test("tarball installs and sf binary resolves", async () => {
|
|||
"resources",
|
||||
"extensions",
|
||||
"sf",
|
||||
"index.ts",
|
||||
"index.js",
|
||||
);
|
||||
assert.ok(
|
||||
existsSync(installedSfExt),
|
||||
|
|
|
|||
|
|
@ -921,7 +921,7 @@ test("command-surface session affordances use the shared store action path", ()
|
|||
);
|
||||
assert.match(
|
||||
commandSurfaceSource,
|
||||
/void renameSessionFromSurface\(selectedNameTarget\.sessionPath, selectedNameTarget\.name\)/,
|
||||
/void renameSessionFromSurface\(\s*selectedNameTarget\.sessionPath,\s*selectedNameTarget\.name,\s*\)/,
|
||||
"command-surface rename apply button should reuse the shared session-rename store action",
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -608,7 +608,12 @@ test("fresh sf --web browser onboarding stays locked on failed validation and un
|
|||
headers: { Accept: "application/json", ...auth },
|
||||
signal: AbortSignal.timeout(10_000),
|
||||
});
|
||||
assert.equal(bootAfter.ok, true);
|
||||
const bootAfterText = await bootAfter.clone().text();
|
||||
assert.equal(
|
||||
bootAfter.ok,
|
||||
true,
|
||||
`expected boot endpoint to respond after successful retry: ${bootAfter.status} ${bootAfterText}`,
|
||||
);
|
||||
const bootAfterPayload = (await bootAfter.json()) as any;
|
||||
assert.equal(bootAfterPayload.onboarding.locked, false);
|
||||
assert.equal(bootAfterPayload.onboarding.lockReason, null);
|
||||
|
|
|
|||
|
|
@ -613,7 +613,7 @@ test("chat tool blocks normalize Claude Code tool names before choosing built-in
|
|||
|
||||
assert.match(
|
||||
source,
|
||||
/const normalizedToolName = typeof tool\.name === "string" \? tool\.name\.toLowerCase\(\) : ""/,
|
||||
/const normalizedToolName =\s*typeof tool\.name === "string" \? tool\.name\.toLowerCase\(\) : ""/,
|
||||
"chat-mode.tsx must normalize Claude Code tool names before matching built-in tool render branches",
|
||||
);
|
||||
assert.match(
|
||||
|
|
@ -951,7 +951,7 @@ test("recovery diagnostics surface stays on a dedicated route with explicit stal
|
|||
|
||||
assert.match(
|
||||
storeSource,
|
||||
/loadRecoveryDiagnostics = async/,
|
||||
/loadRecoveryDiagnostics =\s*async/,
|
||||
"sf-workspace-store.tsx must expose a recovery diagnostics loader",
|
||||
);
|
||||
assert.match(
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ test("InteractiveMode kills descendant processes on shutdown", () => {
|
|||
|
||||
test("bg-shell removes signal handlers on session_shutdown", () => {
|
||||
const src = readSource(
|
||||
"src/resources/extensions/bg-shell/bg-shell-lifecycle.ts",
|
||||
"src/resources/extensions/bg-shell/bg-shell-lifecycle.js",
|
||||
);
|
||||
assert.ok(
|
||||
src.includes('process.off("SIGTERM"') ||
|
||||
|
|
@ -157,7 +157,7 @@ test("bg-shell removes signal handlers on session_shutdown", () => {
|
|||
|
||||
test("pendingAlerts has a maximum size cap", () => {
|
||||
const src = readSource(
|
||||
"src/resources/extensions/bg-shell/process-manager.ts",
|
||||
"src/resources/extensions/bg-shell/process-manager.js",
|
||||
);
|
||||
assert.ok(
|
||||
src.includes("MAX_PENDING_ALERTS"),
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import { test } from "vitest";
|
|||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const subagentSrc = readFileSync(
|
||||
join(__dirname, "../resources/extensions/subagent/index.ts"),
|
||||
join(__dirname, "../resources/extensions/subagent/index.js"),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ test("subagent debate mode injects prior-round transcript", () => {
|
|||
);
|
||||
assert.match(
|
||||
subagentSrc,
|
||||
/const\s+transcriptEntries:\s*string\[\]\s*=\s*\[\]/,
|
||||
/const\s+transcriptEntries\s*=\s*\[\]/,
|
||||
"debate should maintain a transcript across rounds",
|
||||
);
|
||||
assert.match(
|
||||
|
|
@ -49,7 +49,7 @@ test("subagent debate mode injects prior-round transcript", () => {
|
|||
test("subagent details includes debate as a first-class mode", () => {
|
||||
assert.match(
|
||||
subagentSrc,
|
||||
/type\s+SubagentMode\s*=\s*"single"\s*\|\s*"parallel"\s*\|\s*"debate"\s*\|\s*"chain"/,
|
||||
/TaskBatchModeSchema\s*=\s*StringEnum\(\["parallel",\s*"debate"\]/,
|
||||
);
|
||||
assert.match(
|
||||
subagentSrc,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ test("update commands use the registry fetch helper instead of npm view (#3806)"
|
|||
"resources",
|
||||
"extensions",
|
||||
"sf",
|
||||
"commands-handlers.ts",
|
||||
"commands-handlers.js",
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
|
|
@ -113,7 +113,7 @@ test("commands-handlers uses resolveInstallCommand instead of hardcoded npm (#41
|
|||
"resources",
|
||||
"extensions",
|
||||
"sf",
|
||||
"commands-handlers.ts",
|
||||
"commands-handlers.js",
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ test("Windows launch points use shell-safe shims", () => {
|
|||
"resources",
|
||||
"extensions",
|
||||
"sf",
|
||||
"pre-execution-checks.ts",
|
||||
"pre-execution-checks.js",
|
||||
),
|
||||
"utf8",
|
||||
);
|
||||
|
|
@ -81,5 +81,5 @@ test("Windows launch points use shell-safe shims", () => {
|
|||
assert.match(sfClient, /shell:\s*process\.platform === "win32"/);
|
||||
assert.match(updateService, /npm\.cmd/);
|
||||
assert.match(preExecution, /npm\.cmd/);
|
||||
assert.match(validatePack, /shell:\s*process\.platform === 'win32'/);
|
||||
assert.match(validatePack, /shell:\s*process\.platform === ["']win32["']/);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,12 +58,15 @@ export function resolveSubprocessModule(
|
|||
relPath: string,
|
||||
checkExists: (path: string) => boolean = defaultExistsSync,
|
||||
): SubprocessModuleResolution {
|
||||
if (isUnderNodeModules(packageRoot)) {
|
||||
const jsRelPath = relPath.replace(/\.ts$/, ".js");
|
||||
const distPath = join(packageRoot, "dist", jsRelPath);
|
||||
if (checkExists(distPath)) {
|
||||
return { modulePath: distPath, useCompiledJs: true };
|
||||
}
|
||||
const jsRelPath = relPath.replace(/\.ts$/, ".js");
|
||||
const distPath = join(packageRoot, "dist", jsRelPath);
|
||||
if (checkExists(distPath)) {
|
||||
return { modulePath: distPath, useCompiledJs: true };
|
||||
}
|
||||
|
||||
const sourceJsPath = join(packageRoot, "src", jsRelPath);
|
||||
if (checkExists(sourceJsPath)) {
|
||||
return { modulePath: sourceJsPath, useCompiledJs: true };
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
* so nothing is lost. The SF extension reads SF_CLI_WORKTREE to know
|
||||
* when a session was launched via -w.
|
||||
*
|
||||
* Note: Extension modules are .ts files loaded via jiti (not compiled to .js).
|
||||
* Note: Extension modules are JavaScript resource files loaded via jiti.
|
||||
* We use createJiti() here because this module is compiled by tsc but imports
|
||||
* from resources/extensions/sf/ which are shipped as raw .ts (#1283).
|
||||
*/
|
||||
|
|
@ -89,11 +89,11 @@ interface ExtensionModules {
|
|||
async function loadExtensionModules(): Promise<ExtensionModules> {
|
||||
if (_ext) return _ext;
|
||||
const [wtMgr, autoWt, gitBridge, gitSvc, wt] = await Promise.all([
|
||||
jiti.import(sfExtensionPath("worktree-manager.ts"), {}) as Promise<any>,
|
||||
jiti.import(sfExtensionPath("auto-worktree.ts"), {}) as Promise<any>,
|
||||
jiti.import(sfExtensionPath("native-git-bridge.ts"), {}) as Promise<any>,
|
||||
jiti.import(sfExtensionPath("git-service.ts"), {}) as Promise<any>,
|
||||
jiti.import(sfExtensionPath("worktree.ts"), {}) as Promise<any>,
|
||||
jiti.import(sfExtensionPath("worktree-manager.js"), {}) as Promise<any>,
|
||||
jiti.import(sfExtensionPath("auto-worktree.js"), {}) as Promise<any>,
|
||||
jiti.import(sfExtensionPath("native-git-bridge.js"), {}) as Promise<any>,
|
||||
jiti.import(sfExtensionPath("git-service.js"), {}) as Promise<any>,
|
||||
jiti.import(sfExtensionPath("worktree.js"), {}) as Promise<any>,
|
||||
]);
|
||||
_ext = {
|
||||
createWorktree: wtMgr.createWorktree,
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
import { resolve } from "node:path";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { defineConfig } from "electron-vite";
|
||||
|
||||
export default defineConfig({
|
||||
main: {
|
||||
build: {
|
||||
outDir: "dist/main",
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, "src/main/index.ts"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
preload: {
|
||||
build: {
|
||||
outDir: "dist/preload",
|
||||
rollupOptions: {
|
||||
input: {
|
||||
index: resolve(__dirname, "src/preload/index.ts"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
root: resolve(__dirname, "src/renderer"),
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": resolve(__dirname, "src/renderer/src"),
|
||||
},
|
||||
},
|
||||
plugins: [tailwindcss(), react()],
|
||||
build: {
|
||||
outDir: resolve(__dirname, "dist/renderer"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
{
|
||||
"name": "@singularity-forge/studio",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"main": "dist/main/index.js",
|
||||
"scripts": {
|
||||
"dev": "electron-vite dev",
|
||||
"build": "electron-vite build",
|
||||
"preview": "electron-vite preview",
|
||||
"test": "node --test test/*.test.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@phosphor-icons/react": "^2.1.10",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-resizable-panels": "^4.7.3",
|
||||
"zustand": "^5.0.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@types/node": "^24.0.0",
|
||||
"@types/react": "^19.2.2",
|
||||
"@types/react-dom": "^19.2.2",
|
||||
"@vitejs/plugin-react": "^5.1.0",
|
||||
"electron": "^41.0.3",
|
||||
"electron-vite": "^5.0.0",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=24.15.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
import { join } from "node:path";
|
||||
import { app, BrowserWindow } from "electron";
|
||||
|
||||
const __dirname = import.meta.dirname;
|
||||
|
||||
let _mainWindow: BrowserWindow | null = null;
|
||||
|
||||
function createWindow(): BrowserWindow {
|
||||
const preload = join(__dirname, "../preload/index.mjs");
|
||||
|
||||
const window = new BrowserWindow({
|
||||
width: 1400,
|
||||
height: 900,
|
||||
minWidth: 1100,
|
||||
minHeight: 720,
|
||||
backgroundColor: "#0a0a0a",
|
||||
titleBarStyle: process.platform === "darwin" ? "hiddenInset" : "default",
|
||||
trafficLightPosition:
|
||||
process.platform === "darwin" ? { x: 16, y: 16 } : undefined,
|
||||
webPreferences: {
|
||||
preload,
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
},
|
||||
});
|
||||
|
||||
const rendererUrl = process.env.ELECTRON_RENDERER_URL;
|
||||
|
||||
if (rendererUrl) {
|
||||
void window.loadURL(rendererUrl);
|
||||
} else {
|
||||
void window.loadFile(join(__dirname, "../renderer/index.html"));
|
||||
}
|
||||
|
||||
console.log("[studio] window created");
|
||||
console.log("SF Studio ready");
|
||||
|
||||
return window;
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
_mainWindow = createWindow();
|
||||
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
_mainWindow = createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
7
studio/src/preload/index.d.ts
vendored
7
studio/src/preload/index.d.ts
vendored
|
|
@ -1,7 +0,0 @@
|
|||
import type { StudioBridge } from "./index";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
studio: StudioBridge;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import { contextBridge } from "electron";
|
||||
|
||||
export type StudioStatus = {
|
||||
connected: boolean;
|
||||
};
|
||||
|
||||
export type StudioBridge = {
|
||||
onEvent: (callback: (event: unknown) => void) => () => void;
|
||||
sendCommand: (command: string, args?: Record<string, unknown>) => void;
|
||||
spawn: () => void;
|
||||
getStatus: () => Promise<StudioStatus>;
|
||||
};
|
||||
|
||||
const studio: StudioBridge = {
|
||||
onEvent: (_callback) => () => undefined,
|
||||
sendCommand: (_command, _args) => undefined,
|
||||
spawn: () => undefined,
|
||||
getStatus: () => Promise.resolve({ connected: false }),
|
||||
};
|
||||
|
||||
console.log("[studio] preload loaded");
|
||||
contextBridge.exposeInMainWorld("studio", studio);
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>SF Studio</title>
|
||||
<link rel="preload" href="./src/assets/fonts/Inter-Regular.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="./src/assets/fonts/Inter-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="./src/assets/fonts/Inter-SemiBold.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="./src/assets/fonts/JetBrainsMono-Regular.woff2" as="font" type="font/woff2" crossorigin />
|
||||
<link rel="preload" href="./src/assets/fonts/JetBrainsMono-Medium.woff2" as="font" type="font/woff2" crossorigin />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="./src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
import { BracketsCurly, Lightning, Palette } from "@phosphor-icons/react";
|
||||
import { colors, fontSizes, fonts } from "./lib/theme/tokens";
|
||||
|
||||
const statusRows = [
|
||||
{ label: "Shell", value: "electron-vite + React 19", icon: Lightning },
|
||||
{ label: "Theme", value: colors.accent, icon: Palette },
|
||||
{ label: "Code", value: fonts.mono, icon: BracketsCurly },
|
||||
];
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<main className="min-h-screen bg-bg-primary text-text-primary">
|
||||
<div className="mx-auto flex min-h-screen max-w-6xl flex-col justify-center px-10 py-16">
|
||||
<div className="grid gap-10 lg:grid-cols-[1.2fr_0.8fr]">
|
||||
<section className="rounded-[28px] border border-border bg-[linear-gradient(180deg,rgba(255,255,255,0.04),rgba(255,255,255,0.01))] p-10 shadow-[0_24px_80px_rgba(0,0,0,0.35)] backdrop-blur-sm">
|
||||
<div className="mb-8 inline-flex items-center gap-3 rounded-full border border-[color:var(--color-accent-muted)] bg-[color:var(--color-accent-muted)] px-4 py-2 text-xs font-medium uppercase tracking-[0.28em] text-accent">
|
||||
<span className="h-2 w-2 rounded-full bg-accent shadow-[0_0_18px_rgba(212,160,78,0.7)]" />
|
||||
Studio bootstrap
|
||||
</div>
|
||||
|
||||
<h1 className="max-w-3xl text-[clamp(3.4rem,9vw,6.8rem)] font-semibold leading-[0.92] tracking-[-0.06em] text-balance text-text-primary">
|
||||
SF Studio ships with a dark shell that actually feels deliberate.
|
||||
</h1>
|
||||
|
||||
<p className="mt-6 max-w-2xl text-lg leading-8 text-text-secondary">
|
||||
Inter drives the interface, JetBrains Mono handles code surfaces,
|
||||
and the warm amber system accent keeps the palette restrained
|
||||
instead of drifting into generic app chrome.
|
||||
</p>
|
||||
|
||||
<div className="mt-10 grid gap-4 sm:grid-cols-3">
|
||||
{statusRows.map(({ label, value, icon: Icon }) => (
|
||||
<div
|
||||
key={label}
|
||||
className="rounded-2xl border border-border bg-bg-secondary/70 p-4"
|
||||
>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<span className="text-xs uppercase tracking-[0.24em] text-text-tertiary">
|
||||
{label}
|
||||
</span>
|
||||
<Icon size={18} weight="duotone" className="text-accent" />
|
||||
</div>
|
||||
<p className="text-sm font-medium text-text-primary">
|
||||
{value}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<aside className="space-y-4 rounded-[28px] border border-border bg-bg-secondary/80 p-8 shadow-[inset_0_1px_0_rgba(255,255,255,0.03)]">
|
||||
<div>
|
||||
<p className="text-xs uppercase tracking-[0.24em] text-text-tertiary">
|
||||
Typography proof
|
||||
</p>
|
||||
<p className="mt-3 text-2xl font-semibold text-text-primary">
|
||||
Inter 600 for hierarchy
|
||||
</p>
|
||||
<p className="mt-2 text-sm leading-7 text-text-secondary">
|
||||
The first task only validates the shell and token system.
|
||||
Three-column layout and primitives land in T02.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-2xl border border-[color:var(--color-accent-muted)] bg-[#120f09] p-5">
|
||||
<p className="text-xs uppercase tracking-[0.24em] text-accent/80">
|
||||
Code surface
|
||||
</p>
|
||||
<pre className="mt-4 overflow-x-auto rounded-xl border border-border bg-black/30 p-4 text-sm leading-7 text-[#f5deb3]">
|
||||
<code>{`const studio = await window.studio.getStatus();\nif (!studio.connected) {\n console.log('Renderer scaffold ready');\n}`}</code>
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<dl className="grid grid-cols-3 gap-3 text-sm">
|
||||
<div className="rounded-2xl border border-border bg-bg-primary p-4">
|
||||
<dt className="text-text-tertiary">Accent</dt>
|
||||
<dd className="mt-2 font-medium text-accent">
|
||||
{colors.accent}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-border bg-bg-primary p-4">
|
||||
<dt className="text-text-tertiary">UI font</dt>
|
||||
<dd className="mt-2 font-medium text-text-primary">
|
||||
{fontSizes.body}
|
||||
</dd>
|
||||
</div>
|
||||
<div className="rounded-2xl border border-border bg-bg-primary p-4">
|
||||
<dt className="text-text-tertiary">Mono</dt>
|
||||
<dd className="mt-2 font-mono text-[13px] text-text-primary">
|
||||
{fonts.mono}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -1,28 +0,0 @@
|
|||
export const colors = {
|
||||
bgPrimary: "#0a0a0a",
|
||||
bgSecondary: "#111111",
|
||||
bgTertiary: "#1a1a1a",
|
||||
bgHover: "#222222",
|
||||
border: "#262626",
|
||||
borderActive: "#333333",
|
||||
textPrimary: "#e5e5e5",
|
||||
textSecondary: "#a3a3a3",
|
||||
textTertiary: "#737373",
|
||||
accent: "#d4a04e",
|
||||
accentHover: "#e0b366",
|
||||
accentMuted: "rgba(212, 160, 78, 0.15)",
|
||||
} as const;
|
||||
|
||||
export const fonts = {
|
||||
sans: "'Inter', system-ui, sans-serif",
|
||||
mono: "'JetBrains Mono', ui-monospace, monospace",
|
||||
} as const;
|
||||
|
||||
export const fontSizes = {
|
||||
hero: "4.75rem",
|
||||
display: "3.5rem",
|
||||
title: "2rem",
|
||||
bodyLg: "1.125rem",
|
||||
body: "0.9375rem",
|
||||
caption: "0.75rem",
|
||||
} as const;
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./styles/index.css";
|
||||
|
||||
const rootElement = document.getElementById("root");
|
||||
|
||||
if (!rootElement) {
|
||||
throw new Error("Root element #root was not found");
|
||||
}
|
||||
|
||||
createRoot(rootElement).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
);
|
||||
|
|
@ -1,129 +0,0 @@
|
|||
@import "tailwindcss";
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../assets/fonts/Inter-Regular.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../assets/fonts/Inter-Medium.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Inter";
|
||||
src: url("../assets/fonts/Inter-SemiBold.woff2") format("woff2");
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
src: url("../assets/fonts/JetBrainsMono-Regular.woff2") format("woff2");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
src: url("../assets/fonts/JetBrainsMono-Medium.woff2") format("woff2");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
}
|
||||
|
||||
@theme {
|
||||
--color-bg-primary: #0a0a0a;
|
||||
--color-bg-secondary: #111111;
|
||||
--color-bg-tertiary: #1a1a1a;
|
||||
--color-bg-hover: #222222;
|
||||
--color-border: #262626;
|
||||
--color-border-active: #333333;
|
||||
--color-text-primary: #e5e5e5;
|
||||
--color-text-secondary: #a3a3a3;
|
||||
--color-text-tertiary: #737373;
|
||||
--color-accent: #d4a04e;
|
||||
--color-accent-hover: #e0b366;
|
||||
--color-accent-muted: rgba(212, 160, 78, 0.15);
|
||||
|
||||
--font-sans: "Inter", system-ui, sans-serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, monospace;
|
||||
|
||||
--text-hero: 4.75rem;
|
||||
--text-display: 3.5rem;
|
||||
--text-title: 2rem;
|
||||
--text-body-lg: 1.125rem;
|
||||
--text-body: 0.9375rem;
|
||||
--text-caption: 0.75rem;
|
||||
}
|
||||
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
background-color: var(--color-bg-primary);
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
background: var(--color-bg-primary);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-height: 100vh;
|
||||
background: var(--color-bg-primary);
|
||||
color: var(--color-text-primary);
|
||||
font-family: var(--font-sans);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
#root {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
code,
|
||||
pre,
|
||||
.font-mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: rgba(212, 160, 78, 0.28);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #3a3125 #111111;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
background: #111111;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb {
|
||||
border-radius: 999px;
|
||||
background: linear-gradient(180deg, #403223 0%, #2d241a 100%);
|
||||
border: 2px solid #111111;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: linear-gradient(180deg, #5b4731 0%, #3c2f21 100%);
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { test } from "vitest";
|
||||
|
||||
const cssPath = new URL(
|
||||
"../src/renderer/src/styles/index.css",
|
||||
import.meta.url,
|
||||
);
|
||||
const tokensPath = new URL(
|
||||
"../src/renderer/src/lib/theme/tokens.ts",
|
||||
import.meta.url,
|
||||
);
|
||||
|
||||
test("theme CSS defines required color tokens and font-display block", async () => {
|
||||
const css = await readFile(cssPath, "utf8");
|
||||
|
||||
for (const token of [
|
||||
"--color-bg-primary",
|
||||
"--color-bg-secondary",
|
||||
"--color-bg-tertiary",
|
||||
"--color-bg-hover",
|
||||
"--color-border",
|
||||
"--color-border-active",
|
||||
"--color-text-primary",
|
||||
"--color-text-secondary",
|
||||
"--color-text-tertiary",
|
||||
"--color-accent",
|
||||
"--color-accent-hover",
|
||||
"--color-accent-muted",
|
||||
"--font-sans",
|
||||
"--font-mono",
|
||||
]) {
|
||||
assert.match(
|
||||
css,
|
||||
new RegExp(token.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")),
|
||||
);
|
||||
}
|
||||
|
||||
const blockMatches = css.match(/font-display:\s*block;/g) ?? [];
|
||||
assert.equal(blockMatches.length, 5);
|
||||
});
|
||||
|
||||
test("token module exports key theme primitives", async () => {
|
||||
const tokensFile = await readFile(tokensPath, "utf8");
|
||||
assert.match(tokensFile, /accent: '#d4a04e'/);
|
||||
assert.match(tokensFile, /mono: "'JetBrains Mono'/);
|
||||
assert.match(tokensFile, /body: '0\.9375rem'/);
|
||||
});
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.node.json" },
|
||||
{ "path": "./tsconfig.web.json" }
|
||||
]
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["ES2022"],
|
||||
"types": ["node", "electron-vite/node"],
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": [
|
||||
"electron.vite.config.ts",
|
||||
"src/main/**/*.ts",
|
||||
"src/preload/**/*.ts",
|
||||
"src/preload/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"jsx": "react-jsx",
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/renderer/src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["src/renderer/src/**/*", "src/preload/index.d.ts"]
|
||||
}
|
||||
|
|
@ -12,12 +12,39 @@
|
|||
* npx vitest run --changed # only tests affected by recent changes
|
||||
*/
|
||||
|
||||
import { resolve } from "node:path";
|
||||
import { existsSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { defineConfig } from "vitest/config";
|
||||
|
||||
const __dirname = import.meta.dirname;
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
{
|
||||
name: "sf-resource-js-extension-resolver",
|
||||
enforce: "pre",
|
||||
resolveId(source, importer) {
|
||||
if (
|
||||
!source.endsWith(".ts") ||
|
||||
!source.includes("resources/extensions")
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
const importerPath = importer?.startsWith("file://")
|
||||
? fileURLToPath(importer)
|
||||
: importer;
|
||||
const tsPath = source.startsWith("/")
|
||||
? resolve(__dirname, source.slice(1))
|
||||
: importerPath
|
||||
? resolve(dirname(importerPath), source)
|
||||
: resolve(__dirname, source);
|
||||
const jsPath = tsPath.replace(/\.ts$/, ".js");
|
||||
if (!existsSync(tsPath) && existsSync(jsPath)) return jsPath;
|
||||
return null;
|
||||
},
|
||||
},
|
||||
],
|
||||
// ── TypeScript / module resolution ─────────────────────────────────────────
|
||||
// Vitest uses esbuild for TS transform (fast, bundled). We still set up
|
||||
// NodeNext module resolution and path aliases to match the project's tsconfig.
|
||||
|
|
@ -162,7 +189,6 @@ export default defineConfig({
|
|||
"packages/rpc-client/src/**/*.test.ts",
|
||||
"packages/native/src/**/*.test.mjs",
|
||||
"web/lib/**/*.test.ts",
|
||||
"studio/test/**/*.test.mjs",
|
||||
"scripts/*.test.mjs",
|
||||
],
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue