diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c507ac5 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +EXAMPLE_PROJECT_URL=https://framer.com/projects/Sites--aabbccddeeff +FRAMER_API_KEY=12345678-1234-1234-1234-123456789 diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index c2563d9..64a7794 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -17,3 +17,15 @@ jobs: - run: npm ci - run: npm run check + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + + - run: npm ci + - run: npm run typecheck diff --git a/README.md b/README.md new file mode 100644 index 0000000..977e28a --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# Framer Server API Examples + +This repository contains examples for the Framer Server API. Each example is a standalone project that can be run independently. + +## How to run examples + +You need to obtain a Framer project URL and API key. You can get the API key from the Framer project settings and find the project URL in the browser URL bar. + +Then, you need to set the `EXAMPLE_PROJECT_URL` and `FRAMER_API_KEY` environment variables. + +## How to connect and get a framer client + +```ts +const projectUrl = "https://framer.com/projects/Sites--aabbccddeeff"; + +const framer = await connect(projectUrl, apiKey); +// ... your code here ... +await framer.disconnect(); +``` + +Starting with Node.js v24, you can use the `using` keyword to ensure that the Framer client is closed after the block is executed. + +```ts +using framer = await connect(projectUrl, apiKey); + +// ... your code here ... +// The disconnect is automatically called when the block is exited. +``` + +You can also use the environment variable `FRAMER_API_KEY` to set the API key and omit the API key parameter. + +```ts +using framer = await connect(projectUrl); +``` diff --git a/biome.json b/biome.json index cc3f8bf..5defc99 100644 --- a/biome.json +++ b/biome.json @@ -1,40 +1,43 @@ { - "$schema": "https://biomejs.dev/schemas/2.3.9/schema.json", - "vcs": { - "enabled": true, - "clientKind": "git", - "useIgnoreFile": true - }, - "files": { - "ignoreUnknown": false - }, - "formatter": { - "enabled": true, - "formatWithErrors": true, - "indentStyle": "space", - "indentWidth": 4, - "lineWidth": 120, - "lineEnding": "lf", - "attributePosition": "auto", + "$schema": "https://biomejs.dev/schemas/2.3.9/schema.json", + "vcs": { + "enabled": true, + "clientKind": "git", + "useIgnoreFile": true + }, + "files": { + "ignoreUnknown": false + }, + "formatter": { + "enabled": true, + "formatWithErrors": true, + "indentStyle": "space", + "indentWidth": 4, + "lineWidth": 120, + "lineEnding": "lf", + "attributePosition": "auto", "bracketSpacing": true - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - }, - "javascript": { - "formatter": { - "quoteStyle": "double" - } - }, - "assist": { - "enabled": true, - "actions": { - "source": { - "organizeImports": "on" - } - } - } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "useLiteralKeys": "off" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + }, + "assist": { + "enabled": true, + "actions": { + "source": { + "organizeImports": "on" + } + } + } } diff --git a/examples/csv-importer/README.md b/examples/csv-importer/README.md new file mode 100644 index 0000000..4e332a2 --- /dev/null +++ b/examples/csv-importer/README.md @@ -0,0 +1,13 @@ +# CSV Importer + +This example shows how to import a CSV file into a Framer collection. + +How to use: + +```bash +node --env-file=../../.env src/csv-to-collection.ts + +bun run src/csv-to-collection.ts + +deno run src/csv-to-collection.ts +``` diff --git a/examples/csv-importer/data/sample-products.csv b/examples/csv-importer/data/sample-products.csv new file mode 100644 index 0000000..0748d65 --- /dev/null +++ b/examples/csv-importer/data/sample-products.csv @@ -0,0 +1,9 @@ +slug,title,description,price,inStock,category +wireless-mouse,Wireless Mouse,Ergonomic wireless mouse with precision tracking,29.99,true,Electronics +mechanical-keyboard,Mechanical Keyboard,RGB mechanical keyboard with cherry switches,89.99,true,Electronics +usb-c-cable,USB-C Cable,Fast charging USB-C cable 6ft length,12.99,false,Accessories +monitor-stand,Monitor Stand,Adjustable aluminum monitor stand,49.99,true,Furniture +desk-lamp,LED Desk Lamp,Dimmable LED lamp with USB charging port,34.99,true,Lighting +webcam-hd,HD Webcam,1080p webcam with built-in microphone,59.99,true,Electronics +mouse-pad,Large Mouse Pad,Extended gaming mouse pad with stitched edges,19.99,true,Accessories +headphone-stand,Headphone Stand,Wooden headphone stand with cable holder,24.99,false,Furniture diff --git a/examples/csv-importer/package.json b/examples/csv-importer/package.json new file mode 100644 index 0000000..abda280 --- /dev/null +++ b/examples/csv-importer/package.json @@ -0,0 +1,18 @@ +{ + "name": "csv-importer", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "framer-api": "^0.0.1-alpha.6", + "papaparse": "^5.5.3", + "typescript": "^5.9.3" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "@types/papaparse": "^5.3.15" + } +} diff --git a/examples/csv-importer/src/csv-to-collection.ts b/examples/csv-importer/src/csv-to-collection.ts new file mode 100644 index 0000000..18ff39e --- /dev/null +++ b/examples/csv-importer/src/csv-to-collection.ts @@ -0,0 +1,92 @@ +import assert from "node:assert"; +import path from "node:path"; +import { type CreateField, connect, type FieldDataEntryInput, type FieldDataInput } from "framer-api"; +import { type FieldType, loadCsv } from "./load-csv.ts"; + +// Configuration + +const projectUrl = process.env["EXAMPLE_PROJECT_URL"]; +assert(projectUrl, "EXAMPLE_PROJECT_URL environment variable is required"); + +const csvPath = process.env["CSV_PATH"] ?? path.join(import.meta.dirname, "../data/sample-products.csv"); +const collectionName = process.env["COLLECTION_NAME"] ?? "Products"; + +const { columns, rows, fieldTypes } = loadCsv(csvPath); + +assert(columns.includes("slug"), "CSV must contain a 'slug' column"); + +// The `using` keyword is used to ensure that the Framer client is closed after the block is executed. +// If you don't use the `using` keyword, you need to manually close the client using `await framer.disconnect()`. +// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using +using framer = await connect(projectUrl); + +// Find or Create Collection + +const existingCollections = await framer.getCollections(); +let collection = existingCollections.find((c) => c.name === collectionName); + +if (!collection) { + collection = await framer.createCollection(collectionName); +} + +// Add Missing Fields + +const existingFields = await collection.getFields(); +const existingFieldNames = new Set(existingFields.map((f) => f.name.toLowerCase())); + +const fieldsToCreate = columns + .filter((column) => column !== "slug" && !existingFieldNames.has(column.toLowerCase())) + .map( + (column): CreateField => ({ + type: fieldTypes.get(column) ?? "string", + name: column, + }), + ); + +if (fieldsToCreate.length > 0) { + await collection.addFields(fieldsToCreate); +} + +// Build Items & Import + +const fields = await collection.getFields(); +const fieldNameToId = new Map(fields.map((f) => [f.name.toLowerCase(), f.id])); + +const existingItems = await collection.getItems(); +const slugToExistingId = new Map(existingItems.map((item) => [item.slug, item.id])); + +const items = rows.map((row) => { + const fieldData: FieldDataInput = {}; + + for (const column of columns) { + if (column === "slug") continue; + + const fieldId = fieldNameToId.get(column.toLowerCase()); + if (!fieldId) continue; + + const value = row[column] ?? ""; + const fieldType = fieldTypes.get(column) ?? "string"; + fieldData[fieldId] = toFieldData(value, fieldType); + } + + const slug = row["slug"]; + assert(slug && slug.length > 0, "slug is required and must be non-empty"); + const existingId = slugToExistingId.get(slug); + + return { id: existingId, slug, fieldData }; +}); + +await collection.addItems(items); + +console.log(`Imported ${items.length} items`); + +function toFieldData(value: string, type: FieldType): FieldDataEntryInput { + switch (type) { + case "boolean": + return { type: "boolean" as const, value: value.toLowerCase() === "true" }; + case "number": + return { type: "number" as const, value: parseFloat(value) || 0 }; + case "string": + return { type: "string" as const, value }; + } +} diff --git a/examples/csv-importer/src/load-csv.ts b/examples/csv-importer/src/load-csv.ts new file mode 100644 index 0000000..11b2ada --- /dev/null +++ b/examples/csv-importer/src/load-csv.ts @@ -0,0 +1,52 @@ +import { readFileSync } from "node:fs"; +import Papa from "papaparse"; + +export type FieldType = "string" | "number" | "boolean"; + +export interface CsvData { + columns: string[]; + rows: Record[]; + fieldTypes: Map; +} + +export function loadCsv(path: string): CsvData { + const csvContent = readFileSync(path, "utf-8"); + const { data: rows, meta } = Papa.parse>(csvContent, { + header: true, + skipEmptyLines: true, + transformHeader: (header: string) => header.trim(), + transform: (value: string) => value.trim(), + }); + + if (!meta.fields) { + throw new Error("CSV file has no header row"); + } + + const fieldTypes = new Map(inferFieldTypes(rows, meta.fields)); + + return { columns: meta.fields, rows, fieldTypes }; +} + +function inferFieldType(values: string[]): FieldType { + const nonEmptyValues = values.filter((v) => v !== ""); + if (nonEmptyValues.length === 0) return "string"; + + const allBooleans = nonEmptyValues.every((v) => v === "true" || v === "false"); + if (allBooleans) return "boolean"; + + const allNumbers = nonEmptyValues.every((v) => !Number.isNaN(parseFloat(v)) && Number.isFinite(Number(v))); + if (allNumbers) return "number"; + + return "string"; +} + +/** + * Infer the field types from the data in the CSV file. + * Returns the column name and the inferred field type. + */ +function inferFieldTypes(rows: Record[], columns: string[]): [string, FieldType][] { + return columns.map((column) => { + const values = rows.map((row) => row[column] ?? ""); + return [column, inferFieldType(values)]; + }); +} diff --git a/examples/csv-importer/tsconfig.json b/examples/csv-importer/tsconfig.json new file mode 100644 index 0000000..8cff07e --- /dev/null +++ b/examples/csv-importer/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["**/*.ts"] +} diff --git a/package-lock.json b/package-lock.json index 083d654..801d394 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,183 +1,329 @@ { - "name": "framer-server-api-examples", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "framer-server-api-examples", - "workspaces": [ - "examples/*" - ], - "devDependencies": { - "@biomejs/biome": "2.3.9" - } - }, - "examples/csv-importer": { - "version": "0.0.1", - "extraneous": true - }, - "node_modules/@biomejs/biome": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.9.tgz", - "integrity": "sha512-js+34KpnY65I00k8P71RH0Uh2rJk4BrpxMGM5m2nBfM9XTlKE5N1URn5ydILPRyXXq4ebhKCjsvR+txS+D4z2A==", - "dev": true, - "license": "MIT OR Apache-2.0", - "bin": { - "biome": "bin/biome" - }, - "engines": { - "node": ">=14.21.3" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/biome" - }, - "optionalDependencies": { - "@biomejs/cli-darwin-arm64": "2.3.9", - "@biomejs/cli-darwin-x64": "2.3.9", - "@biomejs/cli-linux-arm64": "2.3.9", - "@biomejs/cli-linux-arm64-musl": "2.3.9", - "@biomejs/cli-linux-x64": "2.3.9", - "@biomejs/cli-linux-x64-musl": "2.3.9", - "@biomejs/cli-win32-arm64": "2.3.9", - "@biomejs/cli-win32-x64": "2.3.9" - } - }, - "node_modules/@biomejs/cli-darwin-arm64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.9.tgz", - "integrity": "sha512-hHbYYnna/WBwem5iCpssQQLtm5ey8ADuDT8N2zqosk6LVWimlEuUnPy6Mbzgu4GWVriyL5ijWd+1zphX6ll4/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-darwin-x64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.9.tgz", - "integrity": "sha512-sKMW5fpvGDmPdqCchtVH5MVlbVeSU3ad4CuKS45x8VHt3tNSC8CZ2QbxffAOKYK9v/mAeUiPC6Cx6+wtyU1q7g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.9.tgz", - "integrity": "sha512-BXBB6HbAgZI6T6QB8q6NSwIapVngqArP6K78BqkMerht7YjL6yWctqfeTnJm0qGF2bKBYFexslrbV+VTlM2E6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-arm64-musl": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.9.tgz", - "integrity": "sha512-JOHyG2nl8XDpncbMazm1uBSi1dPX9VbQDOjKcfSVXTqajD0PsgodMOKyuZ/PkBu5Lw877sWMTGKfEfpM7jE7Cw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.9.tgz", - "integrity": "sha512-PjYuv2WLmvf0WtidxAkFjlElsn0P6qcvfPijrqu1j+3GoW3XSQh3ywGu7gZ25J25zrYj3KEovUjvUZB55ATrGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-linux-x64-musl": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.9.tgz", - "integrity": "sha512-FUkb/5beCIC2trpqAbW9e095X4vamdlju80c1ExSmhfdrojLZnWkah/XfTSixKb/dQzbAjpD7vvs6rWkJ+P07Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-win32-arm64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.9.tgz", - "integrity": "sha512-w48Yh/XbYHO2cBw8B5laK3vCAEKuocX5ItGXVDAqFE7Ze2wnR00/1vkY6GXglfRDOjWHu2XtxI0WKQ52x1qxEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.3" - } - }, - "node_modules/@biomejs/cli-win32-x64": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.9.tgz", - "integrity": "sha512-90+J63VT7qImy9s3pkWL0ZX27VzVwMNCRzpLpe5yMzMYPbO1vcjL/w/Q5f/juAGMvP7a2Fd0H7IhAR6F7/i78A==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT OR Apache-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=14.21.3" - } + "name": "framer-server-api-examples", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "framer-server-api-examples", + "workspaces": [ + "examples/*" + ], + "devDependencies": { + "@biomejs/biome": "2.3.9" + } + }, + "examples/csv-importer": { + "version": "0.0.1", + "dependencies": { + "framer-api": "^0.0.1-alpha.6", + "papaparse": "^5.5.3", + "typescript": "^5.9.3" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "@types/papaparse": "^5.3.15" + } + }, + "examples/csv-importer/node_modules/framer-api": { + "version": "0.0.1-alpha.6", + "resolved": "https://registry.npmjs.org/framer-api/-/framer-api-0.0.1-alpha.6.tgz", + "integrity": "sha512-hN4HtQlJzJ14n/ZApWHc7g1bfg7LNOV3ZfrzYm4n1l67WWag2Q/FJJJF/xZ7n085JNsVzWWNt18D80JFiYi6Iw==", + "dependencies": { + "devalue": "^5.4.2", + "unenv": "^2.0.0-rc.24", + "ws": "^8.18.0" + }, + "peerDependencies": { + "react": "^18.2.0", + "typescript": "^5.9.3" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.9.tgz", + "integrity": "sha512-js+34KpnY65I00k8P71RH0Uh2rJk4BrpxMGM5m2nBfM9XTlKE5N1URn5ydILPRyXXq4ebhKCjsvR+txS+D4z2A==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.3.9", + "@biomejs/cli-darwin-x64": "2.3.9", + "@biomejs/cli-linux-arm64": "2.3.9", + "@biomejs/cli-linux-arm64-musl": "2.3.9", + "@biomejs/cli-linux-x64": "2.3.9", + "@biomejs/cli-linux-x64-musl": "2.3.9", + "@biomejs/cli-win32-arm64": "2.3.9", + "@biomejs/cli-win32-x64": "2.3.9" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.9.tgz", + "integrity": "sha512-hHbYYnna/WBwem5iCpssQQLtm5ey8ADuDT8N2zqosk6LVWimlEuUnPy6Mbzgu4GWVriyL5ijWd+1zphX6ll4/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.9.tgz", + "integrity": "sha512-sKMW5fpvGDmPdqCchtVH5MVlbVeSU3ad4CuKS45x8VHt3tNSC8CZ2QbxffAOKYK9v/mAeUiPC6Cx6+wtyU1q7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.9.tgz", + "integrity": "sha512-BXBB6HbAgZI6T6QB8q6NSwIapVngqArP6K78BqkMerht7YjL6yWctqfeTnJm0qGF2bKBYFexslrbV+VTlM2E6g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.9.tgz", + "integrity": "sha512-JOHyG2nl8XDpncbMazm1uBSi1dPX9VbQDOjKcfSVXTqajD0PsgodMOKyuZ/PkBu5Lw877sWMTGKfEfpM7jE7Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.9.tgz", + "integrity": "sha512-PjYuv2WLmvf0WtidxAkFjlElsn0P6qcvfPijrqu1j+3GoW3XSQh3ywGu7gZ25J25zrYj3KEovUjvUZB55ATrGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.9.tgz", + "integrity": "sha512-FUkb/5beCIC2trpqAbW9e095X4vamdlju80c1ExSmhfdrojLZnWkah/XfTSixKb/dQzbAjpD7vvs6rWkJ+P07Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.9.tgz", + "integrity": "sha512-w48Yh/XbYHO2cBw8B5laK3vCAEKuocX5ItGXVDAqFE7Ze2wnR00/1vkY6GXglfRDOjWHu2XtxI0WKQ52x1qxEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.9.tgz", + "integrity": "sha512-90+J63VT7qImy9s3pkWL0ZX27VzVwMNCRzpLpe5yMzMYPbO1vcjL/w/Q5f/juAGMvP7a2Fd0H7IhAR6F7/i78A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@types/node": { + "version": "22.19.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.3.tgz", + "integrity": "sha512-1N9SBnWYOJTrNZCdh/yJE+t910Y128BoyY+zBLWhL3r0TYzlTmFdXrPwHL9DyFZmlEXNQQolTZh3KHV31QDhyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/papaparse": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/csv-importer": { + "resolved": "examples/csv-importer", + "link": true + }, + "node_modules/devalue": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.1.tgz", + "integrity": "sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/papaparse": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", + "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "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" + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } } - } } diff --git a/package.json b/package.json index 173d84d..06b672a 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,16 @@ { - "name": "framer-server-api-examples", - "private": true, - "workspaces": [ - "examples/*" - ], - "devDependencies": { - "@biomejs/biome": "2.3.9" - }, - "scripts": { - "format": "biome format --write", - "lint": "biome lint --write", - "check": "biome check" - } + "name": "framer-server-api-examples", + "private": true, + "workspaces": [ + "examples/*" + ], + "devDependencies": { + "@biomejs/biome": "2.3.9" + }, + "scripts": { + "format": "biome format --write", + "lint": "biome lint --write", + "check": "biome check", + "typecheck": "npm run typecheck --workspaces" + } } diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 0000000..07b53c8 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "allowImportingTsExtensions": true, + "noEmit": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "isolatedModules": true, + + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noPropertyAccessFromIndexSignature": true + } +}