@superblocksteam/ai-service-templates 2.0.177 → 2.0.178-next.1
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/integration-map.d.ts +5 -0
- package/package.json +7 -3
- package/reify-template.d.ts +8 -0
- package/templates.d.ts +5 -0
- package/templates.js +4 -4
package/package.json
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superblocksteam/ai-service-templates",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.178-next.1",
|
|
4
4
|
"license": "Superblocks Community Software License",
|
|
5
5
|
"files": [
|
|
6
6
|
"reify-template.js",
|
|
7
|
+
"reify-template.d.ts",
|
|
7
8
|
"templates.js",
|
|
9
|
+
"templates.d.ts",
|
|
8
10
|
"integration-map.js",
|
|
11
|
+
"integration-map.d.ts",
|
|
9
12
|
"scripts"
|
|
10
13
|
],
|
|
11
14
|
"type": "module",
|
|
@@ -24,14 +27,15 @@
|
|
|
24
27
|
}
|
|
25
28
|
},
|
|
26
29
|
"devDependencies": {
|
|
27
|
-
"@types/node": "^24",
|
|
28
|
-
"vitest": "4.1.
|
|
30
|
+
"@types/node": "^24.19.2",
|
|
31
|
+
"vitest": "4.1.12"
|
|
29
32
|
},
|
|
30
33
|
"publishConfig": {
|
|
31
34
|
"registry": "https://registry.npmjs.org"
|
|
32
35
|
},
|
|
33
36
|
"scripts": {
|
|
34
37
|
"build": "node ./scripts/build.js",
|
|
38
|
+
"lint:pkg": "publint --strict && attw --pack --profile esm-only",
|
|
35
39
|
"postinstall": "node ./scripts/build.js",
|
|
36
40
|
"publish-package": "pnpm publish",
|
|
37
41
|
"test": "vitest run",
|
package/templates.d.ts
ADDED
package/templates.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
// This file is auto-generated by the build script. Do not edit directly.
|
|
2
2
|
export const templates = {
|
|
3
3
|
"api-builder-to-yaml": {
|
|
4
|
-
"bootstrap.mjs": "import {
|
|
5
|
-
"package.json": "{\n \"type\": \"module\",\n \"scripts\": {\n \"build\": \"tsc\",\n \"test\": \"pnpm build && vitest run src/superblocks-library-shim/*.test.ts\"\n },\n \"dependencies\": {\n \"@types/node\": \"^24\",\n \"@typescript-eslint/parser\": \"^8.
|
|
6
|
-
"pnpm-workspace.yaml": "packages:\n - .\n\n# Template prefetch installs this package without the repo lockfile in /tmp.\n# Keep Vitest's tinyexec transitive on the trust-policy-safe version.\noverrides:\n tinyexec: 1.
|
|
7
|
-
"shim-loader.mjs": "import path from \"path\";\nimport { pathToFileURL } from \"url\";\n\nconst overrideMap = {\n \"@superblocksteam/library\": \"./dist/superblocks-library-shim/index.js\",\n};\n\nexport
|
|
4
|
+
"bootstrap.mjs": "import { registerHooks } from \"node:module\";\n\nimport { resolve } from \"./shim-loader.mjs\";\n\nregisterHooks({ resolve });\n",
|
|
5
|
+
"package.json": "{\n \"type\": \"module\",\n \"scripts\": {\n \"build\": \"tsc\",\n \"test\": \"pnpm build && vitest run src/superblocks-library-shim/*.test.ts\"\n },\n \"dependencies\": {\n \"@types/node\": \"^24.19.2\",\n \"@typescript-eslint/parser\": \"^8.72.0\",\n \"acorn\": \"^8.19.0\",\n \"eslint\": \"^9.39.2\",\n \"prettier\": \"^3.9.10\",\n \"typescript\": \"^6.0.3\",\n \"yaml\": \"^2.9.1\"\n },\n \"devDependencies\": {\n \"vitest\": \"4.1.12\"\n }\n}\n",
|
|
6
|
+
"pnpm-workspace.yaml": "packages:\n - .\n\n# Template prefetch installs this package without the repo lockfile in /tmp.\n# Keep Vitest's tinyexec transitive on the trust-policy-safe version.\noverrides:\n tinyexec: 1.3.1\n\n# nanoid 3.x dropped npm publish provenance from 3.3.13 onward, so the\n# no-downgrade trust policy rejects 3.3.14. 3.3.15 restored provenance but is\n# still inside the release-age soak, so a fresh resolve falls back to the\n# un-provenanced 3.3.14 and the install fails. Exclude nanoid from the soak so\n# the newest provenance-bearing version (3.3.15) is picked. The no-downgrade\n# trust check stays in force, so any future provenance-less nanoid is still\n# rejected. See https://github.com/ai/nanoid/issues/599.\nminimumReleaseAgeExclude:\n - nanoid\n",
|
|
7
|
+
"shim-loader.mjs": "import path from \"path\";\nimport { pathToFileURL } from \"url\";\n\nconst overrideMap = {\n \"@superblocksteam/library\": \"./dist/superblocks-library-shim/index.js\",\n};\n\nexport function resolve(specifier, context, nextResolve) {\n if (overrideMap[specifier]) {\n const fullPath = path.resolve(overrideMap[specifier]);\n return {\n shortCircuit: true,\n url: pathToFileURL(fullPath).href,\n };\n }\n\n return nextResolve(specifier, context);\n}\n",
|
|
8
8
|
"src/do-eval-to-sdk.ts": "import fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\n// finds all JavaScript files in the specified directory,\n// imports each module, converts its JSON representation to SDK,\n// and returns a map with filenames as keys and SDK content as values.\nexport async function processSdkFiles(\n apisDir: string,\n): Promise<Record<string, string>> {\n const files = fs.readdirSync(apisDir);\n const jsFiles = files.filter(\n (file) => file.endsWith(\".js\") && file !== \"__template__.js\",\n );\n const results: Record<string, string> = {};\n\n for (const jsFile of jsFiles) {\n const filePath = path.join(apisDir, jsFile);\n const absolutePath = path.resolve(filePath);\n\n try {\n const fileUrl = new URL(`file://${absolutePath}`);\n const module = await import(fileUrl.href);\n\n const defaultExport = module.default;\n\n if (defaultExport && typeof defaultExport.toSDK === \"function\") {\n const sdkData = await defaultExport.toSDK();\n\n results[jsFile] = sdkData;\n } else {\n console.warn(`${jsFile}: Default export doesn't have a toSDK method`);\n }\n } catch (error) {\n console.error(`Error processing ${jsFile}:`, error);\n }\n }\n\n return results;\n}\n\nasync function main() {\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n const apisDir = path.resolve(__dirname, \"./to-sdk\");\n\n const results = await processSdkFiles(apisDir);\n console.log(JSON.stringify(results, null, 2));\n}\n\nmain().catch((error) => {\n console.error(\"Error:\", error);\n process.exit(1);\n});\n",
|
|
9
9
|
"src/do-eval-to-yaml.ts": "import fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nimport yaml from \"yaml\";\n\nimport { Global } from \"./superblocks-library-shim/index.js\";\n\n/**\n * Initialize global context from environment variables.\n * This allows the transformer to pass plugin version information\n * to the shim classes for version-aware output generation.\n *\n * The PLUGIN_EXECUTION_VERSIONS env var is set by ApiBuilderToYamlTransformer\n * (in to-yaml-transformer.ts) when spawning the eval process. The versions\n * originate from the SDK/UserInfo and flow through AiService -> IntegrationStore.\n */\nfunction initializeGlobalContext(): void {\n const pluginVersionsEnv = process.env.PLUGIN_EXECUTION_VERSIONS;\n if (pluginVersionsEnv) {\n try {\n Global.pluginExecutionVersions = JSON.parse(pluginVersionsEnv);\n } catch {\n console.warn(\n \"Failed to parse PLUGIN_EXECUTION_VERSIONS environment variable\",\n );\n }\n }\n}\n\n// finds all JavaScript files in the specified directory,\n// imports each module, converts its JSON representation to YAML,\n// and returns a map with filenames as keys and YAML content as values.\n\nexport async function processYamlFiles(\n apisDir: string,\n): Promise<Record<string, string>> {\n const files = fs.readdirSync(apisDir);\n const jsFiles = files.filter((file) => file.endsWith(\".js\"));\n const results: Record<string, string> = {};\n\n for (const jsFile of jsFiles) {\n const filePath = path.join(apisDir, jsFile);\n const absolutePath = path.resolve(filePath);\n\n try {\n const fileUrl = new URL(`file://${absolutePath}`);\n const module = await import(fileUrl.href);\n\n const defaultExport = module.default;\n\n if (defaultExport && typeof defaultExport.toJSON === \"function\") {\n const jsonData = await defaultExport.toJSON();\n const yamlContent = yaml.stringify(jsonData);\n results[jsFile] = yamlContent;\n } else {\n console.warn(`${jsFile}: Default export doesn't have a toJSON method`);\n }\n } catch (error) {\n console.error(`Error processing ${jsFile}:`, error);\n }\n }\n\n return results;\n}\n\nasync function main() {\n // Initialize global context before processing\n initializeGlobalContext();\n\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n const apisDir = path.resolve(__dirname, \"./to-yaml\");\n\n const results = await processYamlFiles(apisDir);\n console.log(JSON.stringify(results, null, 2));\n}\n\nmain().catch((error) => {\n console.error(\"Error:\", error);\n process.exit(1);\n});\n",
|
|
10
10
|
"src/superblocks-library-shim/ast-utils.ts": "import { Node, parse, Program } from \"acorn\";\n\n/**\n * Creates an AST from a string value.\n * If the value starts and ends with braces, wraps it in parentheses to parse as an object literal.\n */\nexport function createAst(value: string): Program {\n if (value.startsWith(\"{\") && value.endsWith(\"}\")) {\n value = `(${value})`;\n }\n\n return parse(value, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n preserveParens: false,\n });\n}\n\n/**\n * Finds entity references in a code string using AST parsing.\n * Returns an array of entity names that are referenced in the code.\n *\n * IMPORTANT: This function excludes identifiers that are declared locally\n * within the code (e.g., `const item = ...` means `item` won't be returned\n * even if it's in the entities list). This prevents duplicate declarations\n * when the result is used to create function parameters.\n */\nexport function referenced(data: string, entities: string[]): string[] {\n if (!data || entities.length === 0) {\n return [];\n }\n\n const entitySet = new Set(entities);\n\n // Try parsing strategies in order of likelihood\n const parseStrategies = data.startsWith(\"{\")\n ? [\n // For code starting with {, try as object literal first (wrapped in parens)\n () =>\n parse(`(${data})`, { ecmaVersion: \"latest\", sourceType: \"script\" }),\n // Then try as block statement (for code like { const x = ...; return x; })\n () =>\n parse(data, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n allowReturnOutsideFunction: true,\n }),\n ]\n : [\n // Parse as script (handles statements like const, return, etc.)\n () =>\n parse(data, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n allowReturnOutsideFunction: true,\n }),\n ];\n\n for (const strategy of parseStrategies) {\n try {\n const ast = strategy();\n if (ast) {\n // Use scope-aware extraction to properly handle local declarations\n const allRefs = extractExternalReferencesWithScope(ast, new Set());\n // Filter to only include entities that are referenced AND not locally declared\n return Array.from(allRefs).filter((ref) => entitySet.has(ref));\n }\n } catch {\n // Try next strategy\n }\n }\n\n return [];\n}\n\n/**\n * Collect identifiers from destructuring patterns (ObjectPattern, ArrayPattern)\n */\nexport function collectPatternIdentifiers(\n pattern: Node,\n identifiers: Set<string>,\n): void {\n if (!pattern) return;\n\n if ((pattern as any).type === \"Identifier\") {\n identifiers.add((pattern as any).name);\n } else if ((pattern as any).type === \"ObjectPattern\") {\n for (const prop of (pattern as any).properties || []) {\n if (prop.type === \"Property\") {\n collectPatternIdentifiers(prop.value, identifiers);\n } else if (prop.type === \"RestElement\") {\n collectPatternIdentifiers(prop.argument, identifiers);\n }\n }\n } else if ((pattern as any).type === \"ArrayPattern\") {\n for (const elem of (pattern as any).elements || []) {\n if (elem) {\n collectPatternIdentifiers(elem, identifiers);\n }\n }\n } else if ((pattern as any).type === \"RestElement\") {\n collectPatternIdentifiers((pattern as any).argument, identifiers);\n } else if ((pattern as any).type === \"AssignmentPattern\") {\n collectPatternIdentifiers((pattern as any).left, identifiers);\n }\n}\n\n/**\n * Collect function parameter names into a set\n */\nexport function collectFunctionParams(fn: any, params: Set<string>): void {\n for (const param of fn.params || []) {\n if (param.type === \"Identifier\") {\n params.add(param.name);\n } else if (\n param.type === \"ObjectPattern\" ||\n param.type === \"ArrayPattern\"\n ) {\n collectPatternIdentifiers(param, params);\n } else if (param.type === \"AssignmentPattern\" && param.left) {\n if (param.left.type === \"Identifier\") {\n params.add(param.left.name);\n } else if (\n param.left.type === \"ObjectPattern\" ||\n param.left.type === \"ArrayPattern\"\n ) {\n collectPatternIdentifiers(param.left, params);\n }\n }\n }\n}\n\n/**\n * Check if a node is a function node (arrow function, function expression, or function declaration)\n */\nexport function isFunctionNode(node: any): boolean {\n return (\n node.type === \"ArrowFunctionExpression\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"FunctionDeclaration\"\n );\n}\n\n/**\n * Collect variable declarations at the current scope level only.\n * Does NOT recurse into nested functions (their declarations are their own scope).\n */\nexport function collectCurrentScopeLocals(\n node: Node,\n locals: Set<string>,\n): void {\n if (!node) return;\n\n // VariableDeclarator: const foo = ...\n if (node.type === \"VariableDeclarator\") {\n const decl = node as any;\n if (decl.id?.type === \"Identifier\") {\n locals.add(decl.id.name);\n }\n if (decl.id?.type === \"ObjectPattern\" || decl.id?.type === \"ArrayPattern\") {\n collectPatternIdentifiers(decl.id, locals);\n }\n // Process the init value for nested declarations (but not function bodies)\n if (decl.init && !isFunctionNode(decl.init)) {\n collectCurrentScopeLocals(decl.init, locals);\n }\n return;\n }\n\n // FunctionDeclaration: function foo() {} - the name is local, but don't process body\n if (node.type === \"FunctionDeclaration\") {\n const fn = node as any;\n if (fn.id?.type === \"Identifier\") {\n locals.add(fn.id.name);\n }\n // Don't recurse into function body - its locals are scoped to it\n return;\n }\n\n // ArrowFunctionExpression / FunctionExpression - don't recurse, their scope is separate\n if (\n node.type === \"ArrowFunctionExpression\" ||\n node.type === \"FunctionExpression\"\n ) {\n return;\n }\n\n // CatchClause param is local to the catch block\n if (node.type === \"CatchClause\") {\n const catchClause = node as any;\n if (catchClause.param?.type === \"Identifier\") {\n locals.add(catchClause.param.name);\n }\n // Continue to process the body\n if (catchClause.body) {\n collectCurrentScopeLocals(catchClause.body, locals);\n }\n return;\n }\n\n // Recursively process child nodes (except functions)\n for (const key in node) {\n const value = (node as any)[key];\n if (value && typeof value === \"object\") {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === \"object\" && item.type) {\n collectCurrentScopeLocals(item, locals);\n }\n }\n } else if (value.type) {\n collectCurrentScopeLocals(value, locals);\n }\n }\n }\n}\n\n/**\n * Extract references from a node, handling nested functions with their own scope.\n */\nexport function extractRefsFromNode(\n node: Node,\n locals: Set<string>,\n refs: Set<string>,\n): void {\n if (!node) return;\n\n // Handle nested functions - recurse with their params added to locals\n if (\n node.type === \"ArrowFunctionExpression\" ||\n node.type === \"FunctionExpression\" ||\n node.type === \"FunctionDeclaration\"\n ) {\n const fn = node as any;\n // Create new scope with outer locals + this function's params\n const innerLocals = new Set(locals);\n collectFunctionParams(fn, innerLocals);\n // Also collect locals declared inside the function body\n if (fn.body) {\n collectCurrentScopeLocals(fn.body, innerLocals);\n }\n // Extract refs from function body with the inner scope\n if (fn.body) {\n extractRefsFromNode(fn.body, innerLocals, refs);\n }\n return;\n }\n\n // MemberExpression: only care about the root object\n if (node.type === \"MemberExpression\") {\n const memberExpr = node as any;\n extractRefsFromNode(memberExpr.object, locals, refs);\n return;\n }\n\n // Property in object literal: skip key, only process value\n if (node.type === \"Property\") {\n const prop = node as any;\n if (prop.shorthand && prop.key?.type === \"Identifier\") {\n // { foo } is shorthand for { foo: foo } - foo is a reference\n if (!locals.has(prop.key.name)) {\n refs.add(prop.key.name);\n }\n } else {\n // Process computed keys and values\n if (prop.computed) {\n extractRefsFromNode(prop.key, locals, refs);\n }\n extractRefsFromNode(prop.value, locals, refs);\n }\n return;\n }\n\n // VariableDeclarator: only process the init, not the id\n if (node.type === \"VariableDeclarator\") {\n const decl = node as any;\n if (decl.init) {\n extractRefsFromNode(decl.init, locals, refs);\n }\n return;\n }\n\n // Identifier: add if not a local\n if (node.type === \"Identifier\") {\n const name = (node as any).name;\n if (!locals.has(name)) {\n refs.add(name);\n }\n return;\n }\n\n // Recursively process child nodes\n for (const key in node) {\n const value = (node as any)[key];\n if (value && typeof value === \"object\") {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === \"object\" && item.type) {\n extractRefsFromNode(item, locals, refs);\n }\n }\n } else if (value.type) {\n extractRefsFromNode(value, locals, refs);\n }\n }\n }\n}\n\n/**\n * Extract external variable references from AST with proper scope handling.\n *\n * This function:\n * - Tracks local declarations at each scope level\n * - Properly handles nested functions (their params are local to them, not outer scope)\n * - Extracts references from nested function bodies that refer to outer scope variables\n * - Excludes object literal property keys\n * - Only extracts root object from member expressions (Step1.value -> Step1)\n */\nexport function extractExternalReferencesWithScope(\n node: Node,\n outerLocals: Set<string>,\n refs: Set<string> = new Set(),\n): Set<string> {\n if (!node) return refs;\n\n // Collect locals at current scope level (not including nested function scopes)\n const currentLocals = new Set(outerLocals);\n collectCurrentScopeLocals(node, currentLocals);\n\n // Now extract references\n extractRefsFromNode(node, currentLocals, refs);\n\n return refs;\n}\n\n/**\n * Extract variable reference identifiers from a function body using AST parsing.\n * Properly handles nested scopes - inner function params don't leak to outer scope.\n */\nexport function extractVariableReferences(node: Node): Set<string> {\n return extractExternalReferencesWithScope(node, new Set());\n}\n\n/**\n * JavaScript built-in globals that should not be treated as external variable references.\n * These are available in any JavaScript context and don't need to be destructured from parameters.\n */\nexport const JS_GLOBALS = new Set([\n // Constructors / Type converters\n \"String\",\n \"Number\",\n \"Boolean\",\n \"Object\",\n \"Array\",\n \"Function\",\n \"Symbol\",\n \"BigInt\",\n \"Date\",\n \"RegExp\",\n \"Error\",\n \"TypeError\",\n \"ReferenceError\",\n \"SyntaxError\",\n \"RangeError\",\n \"URIError\",\n \"EvalError\",\n \"Map\",\n \"Set\",\n \"WeakMap\",\n \"WeakSet\",\n \"Promise\",\n \"Proxy\",\n \"Reflect\",\n \"ArrayBuffer\",\n \"SharedArrayBuffer\",\n \"DataView\",\n \"Int8Array\",\n \"Uint8Array\",\n \"Uint8ClampedArray\",\n \"Int16Array\",\n \"Uint16Array\",\n \"Int32Array\",\n \"Uint32Array\",\n \"Float32Array\",\n \"Float64Array\",\n \"BigInt64Array\",\n \"BigUint64Array\",\n // Global functions\n \"parseInt\",\n \"parseFloat\",\n \"isNaN\",\n \"isFinite\",\n \"encodeURI\",\n \"encodeURIComponent\",\n \"decodeURI\",\n \"decodeURIComponent\",\n \"eval\",\n // Global objects\n \"Math\",\n \"JSON\",\n \"console\",\n \"Intl\",\n \"Atomics\",\n // Global values\n \"undefined\",\n \"NaN\",\n \"Infinity\",\n \"globalThis\",\n // Node.js globals (commonly used)\n \"Buffer\",\n \"process\",\n \"setTimeout\",\n \"setInterval\",\n \"setImmediate\",\n \"clearTimeout\",\n \"clearInterval\",\n \"clearImmediate\",\n \"queueMicrotask\",\n]);\n\n/**\n * Extract identifiers from a function body using AST parsing.\n * Falls back to entity-based filtering if parsing fails.\n * Filters out JavaScript built-in globals.\n */\nexport function extractReferencedIdentifiers(\n data: string,\n entities: string[],\n): string[] {\n // First try entity-based references\n const entityRefs = referenced(data, entities);\n\n // Also try to extract identifiers from the code itself using AST parsing\n // This catches parameters that aren't in the entities list (like API inputs)\n try {\n // Try parsing strategies - handle both expressions and statements with return\n const parseStrategies = data.startsWith(\"{\")\n ? [\n // For code starting with {, try as object literal first\n () =>\n parse(`(${data})`, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n }),\n // Then try as block statement\n () =>\n parse(data, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n allowReturnOutsideFunction: true,\n }),\n ]\n : [\n // For other code, try with allowReturnOutsideFunction for function bodies\n () =>\n parse(data, {\n ecmaVersion: \"latest\",\n sourceType: \"script\",\n allowReturnOutsideFunction: true,\n }),\n ];\n\n let ast: Node | null = null;\n for (const strategy of parseStrategies) {\n try {\n ast = strategy();\n if (ast) break;\n } catch {\n // Try next strategy\n }\n }\n\n if (!ast) {\n return entityRefs;\n }\n\n const astIdentifiers = Array.from(extractVariableReferences(ast));\n\n // Filter out JavaScript globals - they don't need to be destructured\n const filteredAstIdentifiers = astIdentifiers.filter(\n (id) => !JS_GLOBALS.has(id),\n );\n\n // Combine entity refs with AST-extracted identifiers (deduplicated)\n const combined = new Set([...entityRefs, ...filteredAstIdentifiers]);\n return Array.from(combined);\n } catch {\n // If parsing fails, fall back to entity-based references only\n return entityRefs;\n }\n}\n\n/**\n * Creates a function signature with destructured parameters for the SDK.\n * Example: signatureV2(\"hasMore.value\", [\"hasMore\", \"offset\"]) => \"({ hasMore }) => hasMore.value\"\n */\nexport function signatureV2(data: string, entities: string[] = []): string {\n return `({ ${extractReferencedIdentifiers(data, entities).join(\", \")} }) => ${data}`;\n}\n\n/**\n * Creates an async function signature with destructured parameters for the SDK.\n * Example: signatureV2Async(\"await fetch()\", [\"url\"]) => \"async ({ url }) => await fetch()\"\n */\nexport function signatureV2Async(\n data: string,\n entities: string[] = [],\n): string {\n return `async ({ ${extractReferencedIdentifiers(data, entities).join(\", \")} }) => ${data}`;\n}\n\n/**\n * Extracts identifier names from an AST by walking the tree.\n */\nexport function extractIdentifiers(\n node: Node,\n identifiers: Set<string> = new Set(),\n): Set<string> {\n if (!node) {\n return identifiers;\n }\n\n // we only care about the root of the expression, so we gotta unwind the subtree\n if (node.type === \"MemberExpression\") {\n const memberExpr = node as any;\n extractIdentifiers(memberExpr.object, identifiers);\n return identifiers;\n }\n\n if (node.type === \"Identifier\") {\n identifiers.add((node as any).name);\n }\n\n for (const key in node) {\n const value = (node as any)[key];\n\n if (value && typeof value === \"object\") {\n if (Array.isArray(value)) {\n for (const item of value) {\n if (item && typeof item === \"object\" && item.type) {\n extractIdentifiers(item, identifiers);\n }\n }\n } else if (value.type) {\n extractIdentifiers(value, identifiers);\n }\n }\n }\n\n return identifiers;\n}\n",
|