From 527c17003df154a48bb74348f1c80c4480687f89 Mon Sep 17 00:00:00 2001 From: Vitali Korezki Date: Thu, 15 Jan 2026 17:51:43 -0800 Subject: [PATCH 1/4] chore: update package structure and add package-lock.json --- package-lock.json | 85 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 15 +++++---- 2 files changed, 93 insertions(+), 7 deletions(-) create mode 100644 package-lock.json diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..69ef411 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,85 @@ +{ + "name": "vapi-gitops", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "vapi-gitops", + "version": "1.0.0", + "dependencies": { + "yaml": "^2.7.0" + }, + "devDependencies": { + "@types/bun": "latest", + "typescript": "^5.0.0" + } + }, + "node_modules/@types/bun": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.6.tgz", + "integrity": "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bun-types": "1.3.6" + } + }, + "node_modules/@types/node": { + "version": "25.0.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", + "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/bun-types": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.6.tgz", + "integrity": "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + } + } +} diff --git a/package.json b/package.json index c216f96..f483cbf 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,19 @@ "name": "vapi-gitops", "version": "1.0.0", "description": "GitOps management for Vapi resources (Assistants, Structured Outputs, Tools)", - "module": "apply.ts", + "module": "src/index.ts", "type": "module", "private": true, "scripts": { - "apply:dev": "bun run apply.ts dev", - "apply:prod": "bun run apply.ts prod" + "apply:dev": "bun run src/apply.ts dev", + "apply:prod": "bun run src/apply.ts prod", + "pull:dev": "bun run src/pull.ts dev", + "pull:prod": "bun run src/pull.ts prod", + "build": "tsc --noEmit" }, "devDependencies": { - "@types/bun": "latest" - }, - "peerDependencies": { - "typescript": "^5" + "@types/bun": "latest", + "typescript": "^5.0.0" }, "dependencies": { "yaml": "^2.7.0" From 4a6a118d2d9268d0ccacfe8df839098a503dcb78 Mon Sep 17 00:00:00 2001 From: Vitali Korezki Date: Thu, 15 Jan 2026 17:51:49 -0800 Subject: [PATCH 2/4] refactor: remove main apply engine implementation from apply.ts --- apply.ts | 101 ------------------------------------------------------- 1 file changed, 101 deletions(-) delete mode 100644 apply.ts diff --git a/apply.ts b/apply.ts deleted file mode 100644 index 39064f8..0000000 --- a/apply.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { VAPI_ENV, VAPI_BASE_URL } from "./src/config.ts"; -import { loadState, saveState } from "./src/state.ts"; -import { loadResources } from "./src/resources.ts"; -import { - applyTool, - applyStructuredOutput, - applyAssistant, - updateToolAssistantRefs, - updateStructuredOutputAssistantRefs, -} from "./src/apply.ts"; -import { deleteOrphanedResources } from "./src/delete.ts"; - -// ───────────────────────────────────────────────────────────────────────────── -// Main Apply Engine -// ───────────────────────────────────────────────────────────────────────────── - -async function apply(): Promise { - console.log("═══════════════════════════════════════════════════════════════"); - console.log(`🚀 Vapi GitOps Apply - Environment: ${VAPI_ENV}`); - console.log(` API: ${VAPI_BASE_URL}`); - console.log("═══════════════════════════════════════════════════════════════\n"); - - // Load current state - const state = loadState(); - - // Load all resources - console.log("\n📂 Loading resources...\n"); - const tools = await loadResources>("tools"); - const structuredOutputs = await loadResources>("structuredOutputs"); - const assistants = await loadResources>("assistants"); - - // Delete orphaned resources first (checks for orphan references, then deletes) - console.log("\n🗑️ Checking for deleted resources...\n"); - await deleteOrphanedResources({ tools, structuredOutputs, assistants }, state); - - // Apply in dependency order: tools → structured outputs → assistants - console.log("\n🔧 Applying tools...\n"); - for (const tool of tools) { - try { - const uuid = await applyTool(tool, state); - state.tools[tool.resourceId] = uuid; - } catch (error) { - console.error(` ❌ Failed to apply tool ${tool.resourceId}:`, error); - throw error; - } - } - - console.log("\n📊 Applying structured outputs...\n"); - for (const output of structuredOutputs) { - try { - const uuid = await applyStructuredOutput(output, state); - state.structuredOutputs[output.resourceId] = uuid; - } catch (error) { - console.error( - ` ❌ Failed to apply structured output ${output.resourceId}:`, - error - ); - throw error; - } - } - - console.log("\n🤖 Applying assistants...\n"); - for (const assistant of assistants) { - try { - const uuid = await applyAssistant(assistant, state); - state.assistants[assistant.resourceId] = uuid; - } catch (error) { - console.error( - ` ❌ Failed to apply assistant ${assistant.resourceId}:`, - error - ); - throw error; - } - } - - // Second pass: Link resources to assistants (now that assistants exist) - console.log("\n🔗 Linking tools to assistant destinations...\n"); - await updateToolAssistantRefs(tools, state); - - console.log("\n🔗 Linking structured outputs to assistants...\n"); - await updateStructuredOutputAssistantRefs(structuredOutputs, state); - - // Save updated state - await saveState(state); - - console.log("\n═══════════════════════════════════════════════════════════════"); - console.log("✅ Apply complete!"); - console.log("═══════════════════════════════════════════════════════════════\n"); - - // Summary - console.log("📋 Summary:"); - console.log(` Tools: ${Object.keys(state.tools).length}`); - console.log(` Structured Outputs: ${Object.keys(state.structuredOutputs).length}`); - console.log(` Assistants: ${Object.keys(state.assistants).length}`); -} - -// Run the apply engine -apply().catch((error) => { - console.error("\n❌ Apply failed:", error); - process.exit(1); -}); From d1e3888afc5eee8f5e81a3e57a4c622977172f36 Mon Sep 17 00:00:00 2001 From: Vitali Korezki Date: Thu, 15 Jan 2026 17:52:21 -0800 Subject: [PATCH 3/4] feat: implement main pull engine and resource management in pull.ts --- src/apply.ts | 95 ++++++++++++++++- src/index.ts | 4 +- src/pull.ts | 296 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 392 insertions(+), 3 deletions(-) create mode 100644 src/pull.ts diff --git a/src/apply.ts b/src/apply.ts index a8be51b..e02dba8 100644 --- a/src/apply.ts +++ b/src/apply.ts @@ -1,6 +1,9 @@ import { vapiRequest } from "./api.ts"; -import { removeExcludedKeys } from "./config.ts"; +import { VAPI_ENV, VAPI_BASE_URL, removeExcludedKeys } from "./config.ts"; +import { loadState, saveState } from "./state.ts"; +import { loadResources } from "./resources.ts"; import { resolveReferences, resolveAssistantIds } from "./resolver.ts"; +import { deleteOrphanedResources } from "./delete.ts"; import type { ResourceFile, StateFile } from "./types.ts"; // ───────────────────────────────────────────────────────────────────────────── @@ -180,3 +183,93 @@ export async function updateStructuredOutputAssistantRefs( } } +// ───────────────────────────────────────────────────────────────────────────── +// Main Apply Engine +// ───────────────────────────────────────────────────────────────────────────── + +async function main(): Promise { + console.log("═══════════════════════════════════════════════════════════════"); + console.log(`🚀 Vapi GitOps Apply - Environment: ${VAPI_ENV}`); + console.log(` API: ${VAPI_BASE_URL}`); + console.log("═══════════════════════════════════════════════════════════════\n"); + + // Load current state + const state = loadState(); + + // Load all resources + console.log("\n📂 Loading resources...\n"); + const tools = await loadResources>("tools"); + const structuredOutputs = await loadResources>("structuredOutputs"); + const assistants = await loadResources>("assistants"); + + // Delete orphaned resources first (checks for orphan references, then deletes) + console.log("\n🗑️ Checking for deleted resources...\n"); + await deleteOrphanedResources({ tools, structuredOutputs, assistants }, state); + + // Apply in dependency order: tools → structured outputs → assistants + console.log("\n🔧 Applying tools...\n"); + for (const tool of tools) { + try { + const uuid = await applyTool(tool, state); + state.tools[tool.resourceId] = uuid; + } catch (error) { + console.error(` ❌ Failed to apply tool ${tool.resourceId}:`, error); + throw error; + } + } + + console.log("\n📊 Applying structured outputs...\n"); + for (const output of structuredOutputs) { + try { + const uuid = await applyStructuredOutput(output, state); + state.structuredOutputs[output.resourceId] = uuid; + } catch (error) { + console.error( + ` ❌ Failed to apply structured output ${output.resourceId}:`, + error + ); + throw error; + } + } + + console.log("\n🤖 Applying assistants...\n"); + for (const assistant of assistants) { + try { + const uuid = await applyAssistant(assistant, state); + state.assistants[assistant.resourceId] = uuid; + } catch (error) { + console.error( + ` ❌ Failed to apply assistant ${assistant.resourceId}:`, + error + ); + throw error; + } + } + + // Second pass: Link resources to assistants (now that assistants exist) + console.log("\n🔗 Linking tools to assistant destinations...\n"); + await updateToolAssistantRefs(tools, state); + + console.log("\n🔗 Linking structured outputs to assistants...\n"); + await updateStructuredOutputAssistantRefs(structuredOutputs, state); + + // Save updated state + await saveState(state); + + console.log("\n═══════════════════════════════════════════════════════════════"); + console.log("✅ Apply complete!"); + console.log("═══════════════════════════════════════════════════════════════\n"); + + // Summary + console.log("📋 Summary:"); + console.log(` Tools: ${Object.keys(state.tools).length}`); + console.log(` Structured Outputs: ${Object.keys(state.structuredOutputs).length}`); + console.log(` Assistants: ${Object.keys(state.assistants).length}`); +} + +// Run the apply engine +main().catch((error) => { + console.error("\n❌ Apply failed:", error); + process.exit(1); +}); + diff --git a/src/index.ts b/src/index.ts index 19aa651..bd71185 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ -// Re-export all modules for easy importing +// Re-export shared modules for easy importing +// Note: apply.ts and pull.ts are entry points with main() functions, not re-exported here export * from "./types.ts"; export * from "./config.ts"; export * from "./api.ts"; export * from "./state.ts"; export * from "./resources.ts"; export * from "./resolver.ts"; -export * from "./apply.ts"; export * from "./delete.ts"; diff --git a/src/pull.ts b/src/pull.ts new file mode 100644 index 0000000..7e671a2 --- /dev/null +++ b/src/pull.ts @@ -0,0 +1,296 @@ +import { mkdir, writeFile } from "fs/promises"; +import { join, dirname } from "path"; +import { stringify } from "yaml"; +import { VAPI_ENV, VAPI_BASE_URL, VAPI_TOKEN, RESOURCES_DIR } from "./config.ts"; +import { loadState, saveState } from "./state.ts"; +import type { StateFile, ResourceType } from "./types.ts"; + +// ───────────────────────────────────────────────────────────────────────────── +// Types +// ───────────────────────────────────────────────────────────────────────────── + +interface VapiResource { + id: string; + name?: string; + [key: string]: unknown; +} + +// Fields to remove from resources before saving (server-managed fields) +const EXCLUDED_FIELDS = [ + "id", + "orgId", + "createdAt", + "updatedAt", + "analyticsMetadata", + "isDeleted", +]; + +// ───────────────────────────────────────────────────────────────────────────── +// API Functions +// ───────────────────────────────────────────────────────────────────────────── + +export async function fetchAllResources(resourceType: ResourceType): Promise { + const endpoint = resourceType === "structuredOutputs" + ? "/structured-output" + : `/${resourceType.replace(/s$/, "")}`; + + const url = `${VAPI_BASE_URL}${endpoint}`; + + const response = await fetch(url, { + method: "GET", + headers: { + Authorization: `Bearer ${VAPI_TOKEN}`, + }, + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`API GET ${endpoint} failed (${response.status}): ${errorText}`); + } + + return response.json() as Promise; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Naming & Slug Generation +// ───────────────────────────────────────────────────────────────────────────── + +function slugify(name: string): string { + return name + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/^-+|-+$/g, "") + .replace(/-+/g, "-"); +} + +function generateResourceId(resource: VapiResource, existingIds: Set): string { + // Use name if available, otherwise use type + short id + const baseName = resource.name + ? slugify(resource.name) + : `resource-${resource.id.slice(0, 8)}`; + + let resourceId = baseName; + let counter = 1; + + // Ensure uniqueness + while (existingIds.has(resourceId)) { + resourceId = `${baseName}-${counter}`; + counter++; + } + + return resourceId; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Resource Processing +// ───────────────────────────────────────────────────────────────────────────── + +function cleanResource(resource: VapiResource): Record { + const cleaned: Record = {}; + + for (const [key, value] of Object.entries(resource)) { + if (!EXCLUDED_FIELDS.includes(key) && value !== null && value !== undefined) { + cleaned[key] = value; + } + } + + return cleaned; +} + +function buildReverseMap(state: StateFile, resourceType: ResourceType): Map { + // uuid -> resourceId + const map = new Map(); + const stateSection = state[resourceType]; + + for (const [resourceId, uuid] of Object.entries(stateSection)) { + map.set(uuid, resourceId); + } + + return map; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Reference Resolution (UUID -> resourceId) +// ───────────────────────────────────────────────────────────────────────────── + +function resolveReferencesToResourceIds( + resource: Record, + state: StateFile +): Record { + const toolsMap = buildReverseMap(state, "tools"); + const assistantsMap = buildReverseMap(state, "assistants"); + const structuredOutputsMap = buildReverseMap(state, "structuredOutputs"); + + const resolved = { ...resource }; + + // Resolve toolIds in model + if (resolved.model && typeof resolved.model === "object") { + const model = { ...(resolved.model as Record) }; + if (Array.isArray(model.toolIds)) { + model.toolIds = model.toolIds.map((uuid: string) => + toolsMap.get(uuid) ?? uuid + ); + } + resolved.model = model; + } + + // Resolve structuredOutputIds in artifactPlan + if (resolved.artifactPlan && typeof resolved.artifactPlan === "object") { + const artifactPlan = { ...(resolved.artifactPlan as Record) }; + if (Array.isArray(artifactPlan.structuredOutputIds)) { + artifactPlan.structuredOutputIds = artifactPlan.structuredOutputIds.map((uuid: string) => + structuredOutputsMap.get(uuid) ?? uuid + ); + } + resolved.artifactPlan = artifactPlan; + } + + // Resolve assistant_ids in structured outputs + if (Array.isArray(resolved.assistant_ids)) { + resolved.assistant_ids = (resolved.assistant_ids as string[]).map((uuid: string) => + assistantsMap.get(uuid) ?? uuid + ); + } + + // Resolve assistantId in tool destinations (handoff tools) + if (Array.isArray(resolved.destinations)) { + resolved.destinations = (resolved.destinations as Record[]).map((dest) => { + if (typeof dest.assistantId === "string") { + return { + ...dest, + assistantId: assistantsMap.get(dest.assistantId) ?? dest.assistantId, + }; + } + return dest; + }); + } + + return resolved; +} + +// ───────────────────────────────────────────────────────────────────────────── +// File Writing +// ───────────────────────────────────────────────────────────────────────────── + +async function writeResourceFile( + resourceType: ResourceType, + resourceId: string, + data: Record +): Promise { + const dir = join(RESOURCES_DIR, resourceType); + const filePath = join(dir, `${resourceId}.yml`); + + // Ensure directory exists + await mkdir(dirname(filePath), { recursive: true }); + + // Convert to YAML and write + const yamlContent = stringify(data, { + lineWidth: 0, // Don't wrap lines + defaultStringType: "PLAIN", + defaultKeyType: "PLAIN", + }); + + await writeFile(filePath, yamlContent); + + return filePath; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Pull Functions +// ───────────────────────────────────────────────────────────────────────────── + +export interface PullStats { + created: number; + updated: number; +} + +export async function pullResourceType( + resourceType: ResourceType, + state: StateFile +): Promise { + console.log(`\n📥 Pulling ${resourceType}...`); + + const resources = await fetchAllResources(resourceType); + console.log(` Found ${resources.length} ${resourceType} in Vapi`); + + const reverseMap = buildReverseMap(state, resourceType); + const existingIds = new Set(Object.keys(state[resourceType])); + const newStateSection: Record = {}; + + let created = 0; + let updated = 0; + + for (const resource of resources) { + // Check if we already have this resource in state (by UUID) + let resourceId = reverseMap.get(resource.id); + const isNew = !resourceId; + + if (!resourceId) { + // Generate new resource ID + resourceId = generateResourceId(resource, existingIds); + existingIds.add(resourceId); + created++; + } else { + updated++; + } + + // Clean and resolve references + const cleaned = cleanResource(resource); + const resolved = resolveReferencesToResourceIds(cleaned, state); + + // Write to file + const filePath = await writeResourceFile(resourceType, resourceId, resolved); + console.log(` ${isNew ? "✨" : "📝"} ${resourceId} -> ${filePath}`); + + // Update state + newStateSection[resourceId] = resource.id; + } + + // Update state with new mappings + state[resourceType] = newStateSection; + + return { created, updated }; +} + +// ───────────────────────────────────────────────────────────────────────────── +// Main Pull Engine +// ───────────────────────────────────────────────────────────────────────────── + +async function main(): Promise { + console.log("═══════════════════════════════════════════════════════════════"); + console.log(`🔄 Vapi GitOps Pull - Environment: ${VAPI_ENV}`); + console.log(` API: ${VAPI_BASE_URL}`); + console.log("═══════════════════════════════════════════════════════════════"); + + const state = loadState(); + + const stats: Record = { + tools: { created: 0, updated: 0 }, + structuredOutputs: { created: 0, updated: 0 }, + assistants: { created: 0, updated: 0 }, + }; + + // Pull in dependency order (tools first, then structured outputs, then assistants) + stats.tools = await pullResourceType("tools", state); + stats.structuredOutputs = await pullResourceType("structuredOutputs", state); + stats.assistants = await pullResourceType("assistants", state); + + // Save updated state + await saveState(state); + + // Summary + console.log("\n═══════════════════════════════════════════════════════════════"); + console.log("✅ Pull complete!"); + console.log("═══════════════════════════════════════════════════════════════\n"); + + console.log("📋 Summary:"); + for (const [type, { created, updated }] of Object.entries(stats)) { + console.log(` ${type}: ${created} new, ${updated} existing`); + } +} + +// Run the pull engine +main().catch((error) => { + console.error("\n❌ Pull failed:", error); + process.exit(1); +}); From 3b66efbee224a9dfdf3ca6b2087ecab3667a312d Mon Sep 17 00:00:00 2001 From: Vitali Korezki Date: Thu, 15 Jan 2026 18:03:14 -0800 Subject: [PATCH 4/4] using npm as primary package manager --- README.md | 61 +++-- bun.lock | 30 --- package-lock.json | 561 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 12 +- src/config.ts | 9 +- tsconfig.json | 17 +- 6 files changed, 602 insertions(+), 88 deletions(-) delete mode 100644 bun.lock diff --git a/README.md b/README.md index c477914..5a77053 100644 --- a/README.md +++ b/README.md @@ -37,24 +37,45 @@ Manage Vapi resources (Assistants, Structured Outputs, and Tools) via Git using ### Prerequisites -- [Bun](https://bun.sh) installed +- Node.js installed - Vapi API token ### Installation ```bash -cd gitops -bun install +cd vapi-gitops +npm install ``` +This installs all dependencies including Bun locally (no global install needed). + ### Setup Environment ```bash # Create your .env file with your Vapi token echo "VAPI_TOKEN=your-token-here" > .env.dev +``` + +### Commands + +| Command | Description | +|---------|-------------| +| `npm run build` | Type-check the codebase | +| `npm run pull:dev` | Pull resources from Vapi to local YAML files | +| `npm run pull:prod` | Pull resources from prod | +| `npm run apply:dev` | Push local YAML files to Vapi (dev) | +| `npm run apply:prod` | Push local YAML files to Vapi (prod) | + +### Basic Workflow + +```bash +# Pull existing resources from Vapi +npm run pull:dev + +# Make changes to YAML files... -# Run the apply script -bun run apply:dev +# Push changes back to Vapi +npm run apply:dev ``` --- @@ -92,7 +113,7 @@ server: **Step 3:** Apply the changes ```bash -bun run apply:dev +npm run apply:dev ``` The tool will be created and its UUID saved to `.vapi-state.dev.json`. @@ -127,7 +148,7 @@ firstMessage: Hello! How can I help you? **Step 3:** Apply the changes ```bash -bun run apply:dev +npm run apply:dev ``` The apply engine will: @@ -184,7 +205,7 @@ artifactPlan: **Step 3:** Apply ```bash -bun run apply:dev +npm run apply:dev ``` --- @@ -217,7 +238,7 @@ rm resources/tools/my-tool-to-delete.yml **Step 3:** Apply ```bash -bun run apply:dev +npm run apply:dev ``` The apply engine will: @@ -255,7 +276,7 @@ mv resources/tools/old-tool-name.yml resources/tools/new-tool-name.yml **Step 3:** Apply ```bash -bun run apply:dev +npm run apply:dev ``` This will: @@ -270,13 +291,13 @@ This will: **Step 1:** Test in dev first ```bash -bun run apply:dev +npm run apply:dev ``` **Step 2:** Verify everything works, then apply to prod ```bash -bun run apply:prod +npm run apply:prod ``` Each environment has its own: @@ -306,9 +327,9 @@ Edit `package.json`: ```json { "scripts": { - "apply:dev": "bun run apply.ts dev", - "apply:staging": "bun run apply.ts staging", - "apply:prod": "bun run apply.ts prod" + "apply:dev": "tsx src/apply.ts dev", + "apply:staging": "tsx src/apply.ts staging", + "apply:prod": "tsx src/apply.ts prod" } } ``` @@ -328,7 +349,7 @@ echo '{"assistants":{},"structuredOutputs":{},"tools":{}}' > .vapi-state.staging **Step 5:** Apply to the new environment ```bash -bun run apply:staging +npm run apply:staging ``` This creates all resources in the staging Vapi account and populates `.vapi-state.staging.json` with the new UUIDs. @@ -392,7 +413,7 @@ assistant_ids: **Step 4:** Apply ```bash -bun run apply:dev +npm run apply:dev ``` The state file will track both: @@ -435,16 +456,16 @@ The apply engine strips everything after `##` when resolving references. ## Project Structure ``` -/gitops -├── apply.ts # Main entry point +vapi-gitops/ ├── src/ +│ ├── apply.ts # Apply entry point & functions +│ ├── pull.ts # Pull entry point & functions │ ├── types.ts # TypeScript interfaces │ ├── config.ts # Environment & configuration │ ├── api.ts # Vapi HTTP client │ ├── state.ts # State file management │ ├── resources.ts # Resource loading │ ├── resolver.ts # Reference resolution -│ ├── apply.ts # Apply functions │ └── delete.ts # Deletion & orphan checks ├── resources/ │ ├── assistants/ # Assistant YAML files diff --git a/bun.lock b/bun.lock deleted file mode 100644 index b542419..0000000 --- a/bun.lock +++ /dev/null @@ -1,30 +0,0 @@ -{ - "lockfileVersion": 1, - "workspaces": { - "": { - "name": "vapi-gitops", - "dependencies": { - "yaml": "^2.7.0", - }, - "devDependencies": { - "@types/bun": "latest", - }, - "peerDependencies": { - "typescript": "^5", - }, - }, - }, - "packages": { - "@types/bun": ["@types/bun@1.3.3", "", { "dependencies": { "bun-types": "1.3.3" } }, "sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g=="], - - "@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="], - - "bun-types": ["bun-types@1.3.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], - - "yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="], - } -} diff --git a/package-lock.json b/package-lock.json index 69ef411..52d9d77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,38 +11,561 @@ "yaml": "^2.7.0" }, "devDependencies": { - "@types/bun": "latest", + "@types/node": "^22.0.0", + "tsx": "^4.0.0", "typescript": "^5.0.0" } }, - "node_modules/@types/bun": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.3.6.tgz", - "integrity": "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "bun-types": "1.3.6" + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, "node_modules/@types/node": { - "version": "25.0.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", - "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~6.21.0" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, - "node_modules/bun-types": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.3.6.tgz", - "integrity": "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" } }, "node_modules/typescript": { @@ -60,9 +583,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index f483cbf..7830874 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,18 @@ "name": "vapi-gitops", "version": "1.0.0", "description": "GitOps management for Vapi resources (Assistants, Structured Outputs, Tools)", - "module": "src/index.ts", "type": "module", "private": true, "scripts": { - "apply:dev": "bun run src/apply.ts dev", - "apply:prod": "bun run src/apply.ts prod", - "pull:dev": "bun run src/pull.ts dev", - "pull:prod": "bun run src/pull.ts prod", + "apply:dev": "tsx src/apply.ts dev", + "apply:prod": "tsx src/apply.ts prod", + "pull:dev": "tsx src/pull.ts dev", + "pull:prod": "tsx src/pull.ts prod", "build": "tsc --noEmit" }, "devDependencies": { - "@types/bun": "latest", + "@types/node": "^22.0.0", + "tsx": "^4.0.0", "typescript": "^5.0.0" }, "dependencies": { diff --git a/src/config.ts b/src/config.ts index ccfecc1..79129f9 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,5 +1,6 @@ import { existsSync, readFileSync } from "fs"; -import { join, basename } from "path"; +import { join, basename, dirname } from "path"; +import { fileURLToPath } from "url"; import type { Environment, ResourceType } from "./types.ts"; import { VALID_ENVIRONMENTS } from "./types.ts"; @@ -12,8 +13,7 @@ function parseEnvironment(): Environment { if (!envArg) { console.error("❌ Environment argument is required"); - console.error(" Usage: bun run apply.ts "); - console.error(" Or use: bun run apply:dev | apply:staging | apply:prod"); + console.error(" Usage: npm run apply:dev | apply:prod"); process.exit(1); } @@ -73,7 +73,8 @@ function loadEnvFile(env: string, baseDir: string): void { // ───────────────────────────────────────────────────────────────────────────── // Base directory for the gitops project -export const BASE_DIR = join(import.meta.dir, ".."); +const __dirname = dirname(fileURLToPath(import.meta.url)); +export const BASE_DIR = join(__dirname, ".."); // Parse environment and load env files export const VAPI_ENV = parseEnvironment(); diff --git a/tsconfig.json b/tsconfig.json index ab0f0b0..754f639 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,17 @@ { "compilerOptions": { - // Environment setup & latest features - "lib": ["esnext"], + // Environment setup + "lib": ["ESNext"], "target": "ESNext", "module": "ESNext", "moduleDetection": "force", - "jsx": "react-jsx", - "allowJs": true, - // Bundler mode + // Module resolution for tsx "moduleResolution": "bundler", "allowImportingTsExtensions": true, "verbatimModuleSyntax": true, "noEmit": true, + "esModuleInterop": true, // Best practices "strict": true, @@ -20,9 +19,9 @@ "noFallthroughCasesInSwitch": true, "noUncheckedIndexedAccess": true, - // Some stricter flags (disabled by default) + // Stricter flags (disabled) "noUnusedLocals": false, - "noUnusedParameters": false, - "noPropertyAccessFromIndexSignature": false - } + "noUnusedParameters": false + }, + "include": ["src/**/*"] }