Skip to content

Commit 47f31c6

Browse files
committed
Merge remote-tracking branch 'origin/main' into lg/import-slices
2 parents 7534169 + 38db98c commit 47f31c6

19 files changed

Lines changed: 224 additions & 93 deletions

File tree

packages/adapter-next/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@slicemachine/adapter-next",
3-
"version": "0.3.88",
3+
"version": "0.3.91",
44
"description": "Slice Machine adapter for Next.js.",
55
"keywords": [
66
"typescript",

packages/adapter-nuxt/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@slicemachine/adapter-nuxt",
3-
"version": "0.3.88",
3+
"version": "0.3.91",
44
"description": "Slice Machine adapter for Nuxt.",
55
"keywords": [
66
"typescript",

packages/adapter-nuxt2/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@slicemachine/adapter-nuxt2",
3-
"version": "0.3.88",
3+
"version": "0.3.91",
44
"description": "Slice Machine adapter for Nuxt 2.",
55
"keywords": [
66
"typescript",

packages/adapter-sveltekit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@slicemachine/adapter-sveltekit",
3-
"version": "0.3.88",
3+
"version": "0.3.91",
44
"description": "Slice Machine adapter for SvelteKit.",
55
"keywords": [
66
"typescript",

packages/init/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@slicemachine/init",
3-
"version": "2.10.44",
3+
"version": "2.10.47",
44
"description": "Init Prismic Slice Machine in your project",
55
"keywords": [
66
"typescript",

packages/init/test/__setup__.ts

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,29 +39,10 @@ vi.mock("log-symbols", async () => {
3939

4040
vi.mock("fs", async () => {
4141
const memfs: typeof import("memfs") = await vi.importActual("memfs");
42-
const _fs: typeof import("node:fs") = await vi.importActual("node:fs");
43-
44-
// Create escape mechanism for realpathSync to allow the Claude SDK to initialize
45-
// The SDK calls realpathSync at module load time, which needs the real filesystem
46-
const realpathSync = (
47-
path: string | Buffer,
48-
options?: { encoding?: BufferEncoding },
49-
) => {
50-
try {
51-
return _fs.realpathSync(path, options);
52-
} catch {
53-
// If real fs fails, try memfs
54-
return memfs.fs.realpathSync(path, options);
55-
}
56-
};
5742

5843
return {
5944
...memfs.fs,
60-
realpathSync,
61-
default: {
62-
...memfs.fs,
63-
realpathSync,
64-
},
45+
default: memfs.fs,
6546
};
6647
});
6748

@@ -104,7 +85,6 @@ vi.mock("@anthropic-ai/claude-agent-sdk", () => {
10485
return {
10586
createClient: vi.fn(),
10687
Agent: vi.fn(),
107-
query: vi.fn(),
10888
};
10989
});
11090

packages/manager/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@slicemachine/manager",
3-
"version": "0.26.1",
3+
"version": "0.26.4",
44
"description": "Manage all aspects of a Slice Machine project.",
55
"repository": {
66
"type": "git",

packages/manager/src/errors.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ export class UnsupportedError extends SliceMachineError {
4343
name = "SMUnsupportedError" as const;
4444
}
4545

46+
export class InferSliceAbortError extends SliceMachineError {
47+
name = "SMInferSliceAbortError" as const;
48+
}
49+
4650
type SliceMachineErrorNames =
4751
| "SMSliceMachineError"
4852
| UnauthorizedError["name"]
@@ -53,7 +57,8 @@ type SliceMachineErrorNames =
5357
| PluginError["name"]
5458
| PluginHookResultError["name"]
5559
| InvalidActiveEnvironmentError["name"]
56-
| UnsupportedError["name"];
60+
| UnsupportedError["name"]
61+
| InferSliceAbortError["name"];
5762

5863
type ShallowSliceMachineError<TName extends SliceMachineErrorNames> = Error & {
5964
name: TName;
@@ -117,3 +122,9 @@ export const isUnsupportedError = (
117122
): error is ShallowSliceMachineError<"SMUnsupportedError"> => {
118123
return isSliceMachineError(error, "SMUnsupportedError");
119124
};
125+
126+
export const isInferSliceAbortError = (
127+
error: unknown,
128+
): error is ShallowSliceMachineError<"SMInferSliceAbortError"> => {
129+
return isSliceMachineError(error, "SMInferSliceAbortError");
130+
};

packages/manager/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export {
4141
PluginHookResultError,
4242
InvalidActiveEnvironmentError,
4343
UnsupportedError,
44+
InferSliceAbortError,
4445
} from "./errors";
4546

4647
export { getEnvironmentInfo } from "./getEnvironmentInfo";

packages/manager/src/managers/customTypes/CustomTypesManager.ts

Lines changed: 97 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import fetch from "../../lib/fetch";
3232
import { OnlyHookErrors } from "../../types";
3333
import { API_ENDPOINTS } from "../../constants/API_ENDPOINTS";
3434
import { SLICE_MACHINE_USER_AGENT } from "../../constants/SLICE_MACHINE_USER_AGENT";
35-
import { UnauthorizedError } from "../../errors";
35+
import { InferSliceAbortError, UnauthorizedError } from "../../errors";
3636

3737
import { BaseManager } from "../BaseManager";
3838
import { CustomTypeFormat } from "./types";
@@ -42,13 +42,16 @@ import { randomUUID } from "node:crypto";
4242
import { join as joinPath, relative as relativePath } from "node:path";
4343
import {
4444
mkdtemp,
45-
rename,
4645
rm,
4746
writeFile,
4847
readFile,
4948
readdir,
49+
copyFile,
5050
} from "node:fs/promises";
51-
import { query as queryClaude } from "@anthropic-ai/claude-agent-sdk";
51+
import {
52+
AbortError as ClaudeAbortError,
53+
query as queryClaude,
54+
} from "@anthropic-ai/claude-agent-sdk";
5255

5356
type SliceMachineManagerReadCustomTypeLibraryReturnType = {
5457
ids: string[];
@@ -580,6 +583,8 @@ export class CustomTypesManager extends BaseManager {
580583
console.info(`inferSlice (${source}) started for request ${requestId}`);
581584
const startTime = Date.now();
582585

586+
const claudeErrors: string[] = [];
587+
583588
try {
584589
if (source === "figma") {
585590
const { libraryID } = args;
@@ -595,6 +600,7 @@ export class CustomTypesManager extends BaseManager {
595600
.parse(exp.payload);
596601

597602
let tmpDir: string | undefined;
603+
598604
try {
599605
const config = await this.project.getSliceMachineConfig();
600606

@@ -814,13 +820,23 @@ FINAL REMINDERS:
814820
- DO NOT ATTEMPT TO BUILD THE APPLICATION
815821
- START IMMEDIATELY WITH STEP 1.1 - NO PRELIMINARY ANALYSIS`;
816822

823+
void this.telemetry.track({
824+
event: "slice-generation:started",
825+
source,
826+
llmProxyUrl,
827+
});
828+
817829
const queries = queryClaude({
818830
prompt,
819831
options: {
820832
cwd,
821-
stderr: (data) => {
822-
if (!data.startsWith("Spawning Claude Code process")) {
823-
console.error("inferSlice error:" + data);
833+
stderr: (error) => {
834+
if (!error.startsWith("Spawning Claude Code process")) {
835+
claudeErrors.push(error);
836+
console.error(
837+
`inferSlice - stderr for request ${requestId}:`,
838+
error,
839+
);
824840
}
825841
},
826842
model: "claude-haiku-4-5",
@@ -850,7 +866,7 @@ FINAL REMINDERS:
850866
]),
851867
],
852868
env: {
853-
...process.env,
869+
...this.sanitizeClaudeEnv(process.env),
854870
ANTHROPIC_BASE_URL: llmProxyUrl,
855871
ANTHROPIC_CUSTOM_HEADERS:
856872
`x-prismic-token: ${authToken}\n` +
@@ -873,10 +889,25 @@ FINAL REMINDERS:
873889
for await (const query of queries) {
874890
switch (query.type) {
875891
case "result":
876-
if (query.subtype === "success") {
877-
newSliceAbsPath = query.result.match(
878-
/<new_slice_path>(.*)<\/new_slice_path>/s,
879-
)?.[1];
892+
switch (query.subtype) {
893+
case "success":
894+
if (!query.is_error) {
895+
newSliceAbsPath = query.result.match(
896+
/<new_slice_path>(.*)<\/new_slice_path>/s,
897+
)?.[1];
898+
} else {
899+
claudeErrors.push(query.result);
900+
}
901+
break;
902+
case "error_during_execution":
903+
case "error_max_budget_usd":
904+
case "error_max_turns":
905+
claudeErrors.push(...query.errors);
906+
console.error(
907+
`inferSlice - result query error for request ${requestId}}:`,
908+
query.errors,
909+
);
910+
break;
880911
}
881912
break;
882913
}
@@ -897,12 +928,21 @@ FINAL REMINDERS:
897928
);
898929
}
899930

900-
// move the screenshot image to the new slice directory
901-
await rename(
931+
// copy instead of moving because the file might be in a different volume
932+
await copyFile(
902933
tmpImageAbsPath,
903934
joinPath(newSliceAbsPath, "screenshot-default.png"),
904935
);
905936

937+
try {
938+
await rm(tmpImageAbsPath);
939+
} catch (error) {
940+
console.warn(
941+
`inferSlice - Failed to delete temporary slice screenshot at ${tmpImageAbsPath}`,
942+
error,
943+
);
944+
}
945+
906946
return InferSliceResponse.parse({ slice: JSON.parse(model) });
907947
} finally {
908948
if (tmpDir && existsSync(tmpDir)) {
@@ -930,12 +970,25 @@ FINAL REMINDERS:
930970
return InferSliceResponse.parse(json);
931971
}
932972
} catch (error) {
973+
if (
974+
error instanceof ClaudeAbortError ||
975+
(error instanceof Error && error.name === "AbortError")
976+
) {
977+
console.warn(`inferSlice (${source}) request ${requestId} was aborted`);
978+
throw new InferSliceAbortError();
979+
}
980+
933981
console.error(
934982
`inferSlice (${source}) failed for request ${requestId}`,
935983
error,
936984
);
937-
938-
throw error;
985+
throw new Error(`inferSlice encountered errors`, {
986+
cause: {
987+
error,
988+
...(claudeErrors.length > 0 ? { claudeErrors } : {}),
989+
args,
990+
},
991+
});
939992
} finally {
940993
this.inferSliceAbortControllers.delete(requestId);
941994
clearTimeout(timeoutId);
@@ -947,6 +1000,35 @@ FINAL REMINDERS:
9471000
}
9481001
}
9491002

1003+
// https://code.claude.com/docs/en/settings#environment-variables
1004+
private claudeExcludePatterns = [
1005+
"ANTHROPIC_",
1006+
"CLAUDE_",
1007+
"MCP_",
1008+
"VERTEX_",
1009+
"DISABLE_",
1010+
"BASH_",
1011+
"AWS_",
1012+
"SLASH_COMMAND_",
1013+
"NO_PROXY",
1014+
"HTTP_PROXY",
1015+
"HTTPS_PROXY",
1016+
"HTTP_MAX_REDIRECTS",
1017+
"MAX_THINKING_TOKENS",
1018+
"USE_BUILTIN_RIPGREP",
1019+
];
1020+
1021+
private sanitizeClaudeEnv(env: Record<string, string | undefined>) {
1022+
return Object.fromEntries(
1023+
Object.entries(env).filter(([key, value]) => {
1024+
return (
1025+
value !== undefined &&
1026+
!this.claudeExcludePatterns.some((pattern) => key.startsWith(pattern))
1027+
);
1028+
}),
1029+
);
1030+
}
1031+
9501032
cancelInferSlice(args: { requestId: string }): { cancelled: boolean } {
9511033
const { requestId } = args;
9521034
const abortController = this.inferSliceAbortControllers.get(requestId);

0 commit comments

Comments
 (0)