@animaapp/anima-sdk 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +6 -14
- package/dist/index.cjs +3 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +40 -10
- package/dist/index.js +625 -566
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/anima.ts +5 -11
- package/src/dataStream.ts +7 -2
- package/src/figma/figmaError.ts +7 -2
- package/src/figma/utils.ts +4 -4
- package/src/index.ts +0 -1
- package/src/settings.ts +4 -2
- package/src/types.ts +43 -36
- package/src/{utils.ts → utils/figma.ts} +1 -1
- package/src/utils/files.ts +95 -0
- package/src/utils/index.ts +2 -0
- package/src/codegenToAnimaFiles.ts +0 -13
package/package.json
CHANGED
package/src/anima.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { convertCodegenFilesToAnimaFiles } from "./codegenToAnimaFiles";
|
|
2
1
|
import { CodegenError } from "./errors";
|
|
3
2
|
import { validateSettings } from "./settings";
|
|
4
3
|
import {
|
|
@@ -10,7 +9,7 @@ import {
|
|
|
10
9
|
|
|
11
10
|
export type Auth =
|
|
12
11
|
| { token: string; teamId: string } // for Anima user, it's mandatory to have an associated team
|
|
13
|
-
| { token: string; userId?: string }; // for users from a 3rd-party
|
|
12
|
+
| { token: string; userId?: string }; // for users from a 3rd-party integrations, they may have optionally a user id
|
|
14
13
|
|
|
15
14
|
export class Anima {
|
|
16
15
|
#auth?: Auth;
|
|
@@ -85,6 +84,7 @@ export class Anima {
|
|
|
85
84
|
uiLibrary: settings.uiLibrary,
|
|
86
85
|
enableTranslation: settings.enableTranslation,
|
|
87
86
|
enableUILibraryTheming: settings.enableUILibraryTheming,
|
|
87
|
+
enableCompactStructure: settings.enableCompactStructure,
|
|
88
88
|
}),
|
|
89
89
|
});
|
|
90
90
|
|
|
@@ -188,15 +188,8 @@ export class Anima {
|
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
case "generating_code": {
|
|
191
|
-
const codegenFiles = data.payload.files as Record<
|
|
192
|
-
string,
|
|
193
|
-
{ code: string; type: "code" }
|
|
194
|
-
>;
|
|
195
|
-
|
|
196
|
-
const files = convertCodegenFilesToAnimaFiles(codegenFiles);
|
|
197
|
-
|
|
198
191
|
if (data.payload.status === "success") {
|
|
199
|
-
result.files = files;
|
|
192
|
+
result.files = data.payload.files;
|
|
200
193
|
}
|
|
201
194
|
|
|
202
195
|
typeof handler === "function"
|
|
@@ -204,7 +197,7 @@ export class Anima {
|
|
|
204
197
|
: handler.onGeneratingCode?.({
|
|
205
198
|
status: data.payload.status,
|
|
206
199
|
progress: data.payload.progress,
|
|
207
|
-
files,
|
|
200
|
+
files: data.payload.files,
|
|
208
201
|
});
|
|
209
202
|
break;
|
|
210
203
|
}
|
|
@@ -233,6 +226,7 @@ export class Anima {
|
|
|
233
226
|
reason: "No files found",
|
|
234
227
|
});
|
|
235
228
|
}
|
|
229
|
+
result.tokenUsage = data.payload.tokenUsage;
|
|
236
230
|
return result as AnimaSDKResult;
|
|
237
231
|
}
|
|
238
232
|
}
|
package/src/dataStream.ts
CHANGED
|
@@ -37,8 +37,13 @@ export const createCodegenStream = (
|
|
|
37
37
|
controller.close();
|
|
38
38
|
}
|
|
39
39
|
})
|
|
40
|
-
.then((
|
|
41
|
-
controller.enqueue({
|
|
40
|
+
.then((_result) => {
|
|
41
|
+
controller.enqueue({
|
|
42
|
+
type: "done", payload: {
|
|
43
|
+
tokenUsage: _result.tokenUsage,
|
|
44
|
+
sessionId: _result.sessionId,
|
|
45
|
+
}
|
|
46
|
+
});
|
|
42
47
|
controller.close();
|
|
43
48
|
})
|
|
44
49
|
.catch((error) => {
|
package/src/figma/figmaError.ts
CHANGED
|
@@ -71,13 +71,18 @@ export const isFigmaTokenIssue = (error: Error) => {
|
|
|
71
71
|
);
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
export
|
|
74
|
+
export type FigmaApiError = {
|
|
75
|
+
cause?: { body?: { status?: number; reason?: string } };
|
|
76
|
+
body?: { status?: number; reason?: string };
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
export const handleFigmaApiError = (error: FigmaApiError, fileKey: string) => {
|
|
75
80
|
const err = error?.cause?.body || error.body;
|
|
76
81
|
|
|
77
82
|
if (err?.status === 403) {
|
|
78
83
|
throw new FigmaTokenIssue({
|
|
79
84
|
fileKey,
|
|
80
|
-
reason: error?.cause?.body || error.body,
|
|
85
|
+
reason: (error?.cause?.body?.reason || error.body?.reason || "Access denied").toString(),
|
|
81
86
|
});
|
|
82
87
|
}
|
|
83
88
|
|
package/src/figma/utils.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { GetFileResponse, Node } from '@figma/rest-api-spec';
|
|
2
2
|
import { FigmaRestApi } from '@animaapp/http-client-figma';
|
|
3
|
-
import { handleFigmaApiError } from './figmaError';
|
|
3
|
+
import { handleFigmaApiError, type FigmaApiError } from './figmaError';
|
|
4
4
|
|
|
5
5
|
export type FigmaNode = Node;
|
|
6
6
|
export type GetFileParams = { fileKey: string; authToken?: string; figmaRestApi?: FigmaRestApi };
|
|
@@ -10,7 +10,7 @@ export type GetFilePagesParams = {
|
|
|
10
10
|
fileKey: string;
|
|
11
11
|
authToken?: string;
|
|
12
12
|
figmaRestApi?: FigmaRestApi;
|
|
13
|
-
params?: Record<string,
|
|
13
|
+
params?: Record<string, string | number | undefined>;
|
|
14
14
|
};
|
|
15
15
|
export type GetFilePagesResult = FigmaPage[] | undefined;
|
|
16
16
|
export type GetFileNodesParams = {
|
|
@@ -18,7 +18,7 @@ export type GetFileNodesParams = {
|
|
|
18
18
|
authToken?: string;
|
|
19
19
|
nodeIds: string[];
|
|
20
20
|
figmaRestApi?: FigmaRestApi;
|
|
21
|
-
params?: Record<string,
|
|
21
|
+
params?: Record<string, string | number>;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
24
|
export type GetFigmaFileResult = GetFileResponse | undefined;
|
|
@@ -68,6 +68,6 @@ export const getFileNodes = async ({
|
|
|
68
68
|
|
|
69
69
|
return data.nodes;
|
|
70
70
|
} catch (error) {
|
|
71
|
-
return handleFigmaApiError(error, fileKey);
|
|
71
|
+
return handleFigmaApiError(error as FigmaApiError, fileKey);
|
|
72
72
|
}
|
|
73
73
|
};
|
package/src/index.ts
CHANGED
package/src/settings.ts
CHANGED
|
@@ -20,6 +20,7 @@ const CodegenSettingsSchema = z
|
|
|
20
20
|
]),
|
|
21
21
|
uiLibrary: z.enum(["mui", "antd", "radix", "shadcn"]).optional(),
|
|
22
22
|
enableUILibraryTheming: z.boolean().optional(),
|
|
23
|
+
enableCompactStructure: z.boolean().optional(),
|
|
23
24
|
}),
|
|
24
25
|
z.object({
|
|
25
26
|
framework: z.literal("html"),
|
|
@@ -32,7 +33,7 @@ const CodegenSettingsSchema = z
|
|
|
32
33
|
// We don't use the z.infer method here because the types returned by zod aren't ergonic
|
|
33
34
|
export type CodegenSettings = {
|
|
34
35
|
language?: "typescript" | "javascript";
|
|
35
|
-
model?: string
|
|
36
|
+
model?: string;
|
|
36
37
|
framework: "react" | "html";
|
|
37
38
|
styling:
|
|
38
39
|
| "plain_css"
|
|
@@ -41,10 +42,11 @@ export type CodegenSettings = {
|
|
|
41
42
|
| "tailwind"
|
|
42
43
|
| "sass"
|
|
43
44
|
| "scss"
|
|
44
|
-
| "inline_styles"
|
|
45
|
+
| "inline_styles";
|
|
45
46
|
uiLibrary?: "mui" | "antd" | "radix" | "shadcn";
|
|
46
47
|
enableTranslation?: boolean;
|
|
47
48
|
enableUILibraryTheming?: boolean;
|
|
49
|
+
enableCompactStructure?: boolean;
|
|
48
50
|
};
|
|
49
51
|
|
|
50
52
|
export const validateSettings = (obj: unknown): CodegenSettings => {
|
package/src/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type BaseResult = {
|
|
|
13
13
|
sessionId: string;
|
|
14
14
|
figmaFileName: string;
|
|
15
15
|
figmaSelectedFrameName: string;
|
|
16
|
+
tokenUsage: number;
|
|
16
17
|
};
|
|
17
18
|
|
|
18
19
|
export type AnimaSDKResult = BaseResult & {
|
|
@@ -30,7 +31,7 @@ export type AssetsStorage =
|
|
|
30
31
|
|
|
31
32
|
export type GetCodeParams = {
|
|
32
33
|
fileKey: string;
|
|
33
|
-
figmaToken
|
|
34
|
+
figmaToken?: string;
|
|
34
35
|
nodesId: string[];
|
|
35
36
|
assetsStorage?: AssetsStorage;
|
|
36
37
|
settings: CodegenSettings;
|
|
@@ -39,52 +40,58 @@ export type GetCodeParams = {
|
|
|
39
40
|
export type GetCodeHandler =
|
|
40
41
|
| ((message: SSECodgenMessage) => void)
|
|
41
42
|
| {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
43
|
+
onStart?: ({ sessionId }: { sessionId: string }) => void;
|
|
44
|
+
onPreCodegen?: ({ message }: { message: string }) => void;
|
|
45
|
+
onAssetsUploaded?: () => void;
|
|
46
|
+
onAssetsList?: ({
|
|
47
|
+
assets,
|
|
48
|
+
}: {
|
|
49
|
+
assets: Array<{ name: string; url: string }>;
|
|
50
|
+
}) => void;
|
|
51
|
+
onFigmaMetadata?: ({
|
|
52
|
+
figmaFileName,
|
|
53
|
+
figmaSelectedFrameName,
|
|
54
|
+
}: {
|
|
55
|
+
figmaFileName: string;
|
|
56
|
+
figmaSelectedFrameName: string;
|
|
57
|
+
}) => void;
|
|
58
|
+
onGeneratingCode?: ({
|
|
59
|
+
status,
|
|
60
|
+
progress,
|
|
61
|
+
files,
|
|
62
|
+
}: {
|
|
63
|
+
status: "success" | "running" | "failure";
|
|
64
|
+
progress: number;
|
|
65
|
+
files: AnimaFiles;
|
|
66
|
+
}) => void;
|
|
67
|
+
onCodegenCompleted?: () => void;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export type GeneratingCodePayload = {
|
|
71
|
+
status: "success" | "running" | "failure";
|
|
72
|
+
progress: number;
|
|
73
|
+
files: AnimaFiles;
|
|
74
|
+
};
|
|
68
75
|
|
|
69
76
|
// TODO: `SSECodgenMessage` and `SSECodgenMessageErrorPayload` should be imported from `anima-public-api`
|
|
70
77
|
export type SSECodgenMessage =
|
|
71
78
|
| { type: "start"; sessionId: string }
|
|
72
79
|
| { type: "pre_codegen"; message: string }
|
|
73
80
|
| {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
| { type: "generating_code"; payload:
|
|
81
|
+
type: "figma_metadata";
|
|
82
|
+
figmaFileName: string;
|
|
83
|
+
figmaSelectedFrameName: string;
|
|
84
|
+
}
|
|
85
|
+
| { type: "generating_code"; payload: GeneratingCodePayload }
|
|
79
86
|
| { type: "codegen_completed" }
|
|
80
87
|
| { type: "assets_uploaded" }
|
|
81
88
|
| {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
89
|
+
type: "assets_list";
|
|
90
|
+
payload: { assets: Array<{ name: string; url: string }> };
|
|
91
|
+
}
|
|
85
92
|
| { type: "aborted" }
|
|
86
93
|
| { type: "error"; payload: SSECodgenMessageErrorPayload }
|
|
87
|
-
| { type: "done" };
|
|
94
|
+
| { type: "done", payload: { sessionId: string, tokenUsage: number } };
|
|
88
95
|
export type SSECodgenMessageErrorPayload = {
|
|
89
96
|
errorName: string;
|
|
90
97
|
task?: string;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { AnimaFiles } from "../types";
|
|
2
|
+
|
|
3
|
+
export const getRelatedScreenFiles = ({
|
|
4
|
+
files,
|
|
5
|
+
screenPath = "src/screens",
|
|
6
|
+
}: {
|
|
7
|
+
files: AnimaFiles;
|
|
8
|
+
screenPath?: string;
|
|
9
|
+
}) => {
|
|
10
|
+
const result: AnimaFiles = {};
|
|
11
|
+
const processed = new Set<string>();
|
|
12
|
+
|
|
13
|
+
function processFile(filePath: string) {
|
|
14
|
+
if (processed.has(filePath) || !files[filePath]) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
processed.add(filePath);
|
|
19
|
+
result[filePath] = files[filePath];
|
|
20
|
+
|
|
21
|
+
const imports = parseImports(files[filePath].content);
|
|
22
|
+
|
|
23
|
+
imports.forEach((importPath) => {
|
|
24
|
+
try {
|
|
25
|
+
const resolvedPath = resolveImportPath(filePath, importPath);
|
|
26
|
+
|
|
27
|
+
if (resolvedPath.startsWith("src/")) {
|
|
28
|
+
const importDir = resolvedPath.split("/").slice(0, -1).join("/");
|
|
29
|
+
const dirFiles = getAllFilesInDirectory(files, importDir);
|
|
30
|
+
dirFiles.forEach((file) => {
|
|
31
|
+
if (!processed.has(file)) {
|
|
32
|
+
processFile(file);
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.warn(
|
|
38
|
+
`Failed to resolve import ${importPath} in ${filePath}:`,
|
|
39
|
+
error
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Object.entries(files).forEach(([key, value]) => {
|
|
46
|
+
if (key.startsWith(screenPath)) {
|
|
47
|
+
processFile(key);
|
|
48
|
+
} else if (!key.startsWith("src/")) {
|
|
49
|
+
result[key] = value;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return result;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function parseImports(content: string): string[] {
|
|
57
|
+
const importRegex = /import.*?["']([^"']+)["']/g;
|
|
58
|
+
const exports = /export.*from\s+["']([^"']+)["']/g;
|
|
59
|
+
const imports: string[] = [];
|
|
60
|
+
let match;
|
|
61
|
+
|
|
62
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
63
|
+
imports.push(match[1]);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
while ((match = exports.exec(content)) !== null) {
|
|
67
|
+
imports.push(match[1]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return [...new Set(imports)];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function resolveImportPath(basePath: string, importPath: string): string {
|
|
74
|
+
if (!importPath.startsWith(".")) {
|
|
75
|
+
return importPath;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const baseDir = basePath.split("/").slice(0, -1);
|
|
79
|
+
const importParts = importPath.split("/");
|
|
80
|
+
const resolvedParts: string[] = [...baseDir];
|
|
81
|
+
|
|
82
|
+
for (const part of importParts) {
|
|
83
|
+
if (part === "..") {
|
|
84
|
+
resolvedParts.pop();
|
|
85
|
+
} else if (part !== ".") {
|
|
86
|
+
resolvedParts.push(part);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return resolvedParts.join("/");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function getAllFilesInDirectory(files: AnimaFiles, dirPath: string): string[] {
|
|
94
|
+
return Object.keys(files).filter((file) => file.startsWith(dirPath));
|
|
95
|
+
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { AnimaFiles } from "./types";
|
|
2
|
-
|
|
3
|
-
export const convertCodegenFilesToAnimaFiles = (
|
|
4
|
-
codegenFiles: Record<string, { code: string; type: "code" }>
|
|
5
|
-
): AnimaFiles => {
|
|
6
|
-
return Object.entries(codegenFiles).reduce(
|
|
7
|
-
(acc, [fileName, file]) => {
|
|
8
|
-
acc[fileName] = { content: file.code, isBinary: false };
|
|
9
|
-
return acc;
|
|
10
|
-
},
|
|
11
|
-
{} as AnimaFiles
|
|
12
|
-
);
|
|
13
|
-
};
|