From 0931b1aa08b4848f9426990a755f5b1f93bf97ab Mon Sep 17 00:00:00 2001 From: Darsh Patel Date: Mon, 17 Mar 2025 19:17:30 -0700 Subject: [PATCH 1/3] chore: setup ci --- .github/workflows/ci.yml | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f06a5ed --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,60 @@ +name: Build and Test + +on: + pull_request: + branches: + - '**' + push: + branches: + - main + +jobs: + build-and-test: + runs-on: ubuntu-latest + permissions: + contents: write + actions: read + checks: write + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Setup pnpm + uses: pnpm/action-setup@v2 + with: + version: 8.15.5 + run_install: false + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - name: Cache pnpm dependencies + uses: actions/cache@v3 + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Typecheck + run: pnpm typecheck + + - name: Lint + run: pnpm lint + + - name: Build + run: pnpm build + + - name: Test + run: pnpm test From 76d1d085bfd28c48efb2a05fc0375ea24adc5daa Mon Sep 17 00:00:00 2001 From: Darsh Patel Date: Tue, 18 Mar 2025 13:22:57 -0700 Subject: [PATCH 2/3] chore: bump node version --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f06a5ed..8567bdd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 22 - name: Setup pnpm uses: pnpm/action-setup@v2 From 7f785e6c89e5d0080fbb96579e67068592ec41cb Mon Sep 17 00:00:00 2001 From: Darsh Patel Date: Tue, 18 Mar 2025 13:23:03 -0700 Subject: [PATCH 3/3] chore: lint --- eslint.config.mjs | 2 + src/__tests__/integration.test.ts | 46 +++++++++---------- src/core/generator.ts | 31 +++++++------ src/core/index.ts | 2 +- src/core/plugin.ts | 24 +++++----- src/index.ts | 4 +- src/types/index.ts | 14 ++++-- src/utils/__tests__/fragments.test.ts | 62 +++++++++++++------------- src/utils/__tests__/transforms.test.ts | 52 ++++++++++----------- src/utils/fragments.ts | 18 ++++---- src/utils/hashing.ts | 17 ++++--- src/utils/index.ts | 2 +- src/utils/transforms.ts | 28 ++++++------ tsup.config.ts | 22 ++++----- vitest.config.ts | 2 +- 15 files changed, 174 insertions(+), 152 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 2c0a9d9..77ca3fc 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,5 +1,6 @@ import eslint from '@eslint/js'; import tseslint from 'typescript-eslint'; +import prettierRecommended from 'eslint-plugin-prettier/recommended'; export default tseslint.config( eslint.configs.recommended, @@ -15,4 +16,5 @@ export default tseslint.config( }, }, }, + prettierRecommended, ); \ No newline at end of file diff --git a/src/__tests__/integration.test.ts b/src/__tests__/integration.test.ts index 1a3580d..721101c 100644 --- a/src/__tests__/integration.test.ts +++ b/src/__tests__/integration.test.ts @@ -9,13 +9,13 @@ describe('Persisted Query Plugin Integration Tests', () => { // Load test schema and documents const schemaPath = join(__dirname, 'fixtures/test-schema.graphql'); const documentsPath = join(__dirname, 'fixtures/test-documents.graphql'); - + const schemaDoc = readFileSync(schemaPath, 'utf8'); const documentsDoc = readFileSync(documentsPath, 'utf8'); - + const schema = buildSchema(schemaDoc); const documentNode = parse(documentsDoc); - + const documents: Types.DocumentFile[] = [ { document: documentNode, @@ -28,47 +28,47 @@ describe('Persisted Query Plugin Integration Tests', () => { const result = plugin(schema, documents, { output: 'client' }); // We know result is a string, but TypeScript sees it as Promisable const manifest = JSON.parse(result as string); - + // Use snapshot for client manifest expect(manifest).toMatchSnapshot(); - }); - + }); + test('generates valid client manifest with algorithm prefix', () => { - const result = plugin(schema, documents, { + const result = plugin(schema, documents, { output: 'client', - includeAlgorithmPrefix: true + includeAlgorithmPrefix: true, }); // We know result is a string, but TypeScript sees it as Promisable const manifest = JSON.parse(result as string); - + // Use snapshot for client manifest with algorithm prefix expect(manifest).toMatchSnapshot(); - }); - + }); + test('generates valid client manifest with custom algorithm', () => { - const result = plugin(schema, documents, { + const result = plugin(schema, documents, { output: 'client', - algorithm: 'md5' + algorithm: 'md5', }); // We know result is a string, but TypeScript sees it as Promisable const manifest = JSON.parse(result as string); - + // Use snapshot for client manifest with custom algorithm expect(manifest).toMatchSnapshot(); }); }); - + describe('Server Manifest Generation', () => { test('generates valid server manifest with default options', () => { const result = plugin(schema, documents, { output: 'server' }); // We know result is a string, but TypeScript sees it as Promisable const manifest = JSON.parse(result as string); - + // Use snapshot for server manifest expect(manifest).toMatchSnapshot(); }); }); - + describe('Error Handling', () => { test('throws error on missing operation names', () => { const docWithUnnamedOperation = parse(` @@ -76,17 +76,17 @@ describe('Persisted Query Plugin Integration Tests', () => { hello } `); - + const docs: Types.DocumentFile[] = [ { document: docWithUnnamedOperation, location: 'test.graphql', }, ]; - - expect(() => - plugin(schema, docs, { output: 'client' }) - ).toThrow('OperationDefinition missing name'); + + expect(() => plugin(schema, docs, { output: 'client' })).toThrow( + 'OperationDefinition missing name', + ); }); }); -}); \ No newline at end of file +}); diff --git a/src/core/generator.ts b/src/core/generator.ts index 6f97108..4feeb46 100644 --- a/src/core/generator.ts +++ b/src/core/generator.ts @@ -1,10 +1,10 @@ import { DocumentNode, visit } from 'graphql'; -import { +import { PluginConfig, ClientOperationListManifest, ServerOperationListManifest, PersistedQueryManifestOperation, - ProcessedOperation + ProcessedOperation, } from '../types'; import { createHash } from '../utils/hashing'; import { addTypenameToDocument } from '../utils/transforms'; @@ -13,17 +13,20 @@ import { printDefinitions } from '../utils/transforms'; /** * Process documents and generate operation hashes with their details - * + * * @param docs - Array of GraphQL document nodes * @param config - Plugin configuration * @returns Array of processed operations with their details * @throws Error if an operation is missing a name */ -function processOperations(docs: DocumentNode[], config: PluginConfig): ProcessedOperation[] { +function processOperations( + docs: DocumentNode[], + config: PluginConfig, +): ProcessedOperation[] { // Add __typename to all selection sets const processedDocs = docs.map(addTypenameToDocument); const operations: ProcessedOperation[] = []; - + const knownFragments = findFragments(processedDocs); for (const doc of processedDocs) { @@ -43,14 +46,14 @@ function processOperations(docs: DocumentNode[], config: PluginConfig): Processe const query = printDefinitions([def, ...usedFragments.values()]); const hash = createHash(query, config); - + operations.push({ name: operationName, hash, type: def.operation, query, definition: def, - fragments: Array.from(usedFragments.values()) + fragments: Array.from(usedFragments.values()), }); }, }, @@ -63,7 +66,7 @@ function processOperations(docs: DocumentNode[], config: PluginConfig): Processe /** * Generates a client-side persisted query manifest * Client manifests map operation names to their hash - * + * * @param docs - Array of GraphQL document nodes * @param config - Plugin configuration * @returns Client-side manifest object @@ -75,7 +78,7 @@ export function generateClientManifest( ): ClientOperationListManifest { const operations = processOperations(docs, config); const manifest: ClientOperationListManifest = {}; - + for (const operation of operations) { manifest[operation.name] = operation.hash; } @@ -86,7 +89,7 @@ export function generateClientManifest( /** * Generates a server-side persisted query manifest * Server manifests map query hashes to operation details - * + * * @param docs - Array of GraphQL document nodes * @param config - Plugin configuration * @returns Server-side manifest object @@ -97,22 +100,22 @@ export function generateServerManifest( config: PluginConfig, ): ServerOperationListManifest { const operations = processOperations(docs, config); - + const manifest: ServerOperationListManifest = { format: 'apollo-persisted-query-manifest', version: 1, operations: {}, }; - + for (const operation of operations) { const operationDetails: PersistedQueryManifestOperation = { type: operation.type, name: operation.name, body: operation.query, }; - + manifest.operations[operation.hash] = operationDetails; } return manifest; -} \ No newline at end of file +} diff --git a/src/core/index.ts b/src/core/index.ts index dead50b..2915294 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -1,2 +1,2 @@ export * from './generator'; -export * from './plugin'; \ No newline at end of file +export * from './plugin'; diff --git a/src/core/plugin.ts b/src/core/plugin.ts index 61e03b0..76e3bb1 100644 --- a/src/core/plugin.ts +++ b/src/core/plugin.ts @@ -4,21 +4,17 @@ import { generateClientManifest, generateServerManifest } from './generator'; /** * GraphQL CodeGen plugin for generating persisted operation manifests - * + * * @param _schema - GraphQL schema (unused) * @param documents - GraphQL documents to process * @param config - Plugin configuration * @returns JSON string representation of the generated manifest * @throws Error if no documents are provided or output format is not specified - * + * * For GraphQL over HTTP specification compliance, set `includeAlgorithmPrefix: true` * to enable the "Prefixed Document Identifier" format (e.g., `sha256:abc123...`). */ -export const plugin: PersistedQueryPlugin = ( - _schema, - documents, - config, -) => { +export const plugin: PersistedQueryPlugin = (_schema, documents, config) => { // Validate input documents if ( !documents || @@ -35,10 +31,18 @@ export const plugin: PersistedQueryPlugin = ( // Generate appropriate manifest format based on configuration if (config.output === 'client') { - return JSON.stringify(generateClientManifest(documentNodes, config), null, ' '); + return JSON.stringify( + generateClientManifest(documentNodes, config), + null, + ' ', + ); } else if (config.output === 'server') { - return JSON.stringify(generateServerManifest(documentNodes, config), null, ' '); + return JSON.stringify( + generateServerManifest(documentNodes, config), + null, + ' ', + ); } else { throw new Error("Must configure output to 'server' or 'client'"); } -}; \ No newline at end of file +}; diff --git a/src/index.ts b/src/index.ts index a36e4ab..755f400 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ /** * GraphQL CodeGen Plugin for Persisted Query Lists - * + * * The plugin can generate manifests in two formats: * - Client format: Maps operation names to query hashes * - Server format: Maps query hashes to full operation details @@ -8,4 +8,4 @@ export * from './types'; export * from './core'; -export * from './utils'; \ No newline at end of file +export * from './utils'; diff --git a/src/types/index.ts b/src/types/index.ts index dfe9fa4..f96880f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,16 +1,20 @@ -import type { FragmentDefinitionNode, OperationDefinitionNode, OperationTypeNode } from 'graphql'; +import type { + FragmentDefinitionNode, + OperationDefinitionNode, + OperationTypeNode, +} from 'graphql'; import type { PluginFunction } from '@graphql-codegen/plugin-helpers'; /** * Configuration for the persisted query generator plugin */ export interface PluginConfig { - /** + /** * Output format - 'server' generates hash->operation mapping, * 'client' generates operation->hash mapping */ output: 'server' | 'client'; - + /** * Hash algorithm to use (defaults to 'sha256') */ @@ -54,7 +58,9 @@ export type ClientOperationListManifest = Record; /** * Union type for both manifest formats */ -export type OperationListManifest = ServerOperationListManifest | ClientOperationListManifest; +export type OperationListManifest = + | ServerOperationListManifest + | ClientOperationListManifest; /** * Internal type for a GraphQL definition diff --git a/src/utils/__tests__/fragments.test.ts b/src/utils/__tests__/fragments.test.ts index 6cc54e5..ed240ea 100644 --- a/src/utils/__tests__/fragments.test.ts +++ b/src/utils/__tests__/fragments.test.ts @@ -22,27 +22,27 @@ describe('Fragment Utilities', () => { } } `); - + const fragments = findFragments([doc]); - + expect(fragments.size).toBe(2); expect(fragments.has('UserFragment')).toBe(true); expect(fragments.has('PostFragment')).toBe(true); expect(fragments.has('NonExistentFragment')).toBe(false); }); - + test('returns empty map when no fragments are found', () => { const doc = parse(` query SimpleQuery { hello } `); - + const fragments = findFragments([doc]); - + expect(fragments.size).toBe(0); }); - + test('finds fragments across multiple documents', () => { const doc1 = parse(` fragment UserFragment on User { @@ -50,22 +50,22 @@ describe('Fragment Utilities', () => { name } `); - + const doc2 = parse(` fragment PostFragment on Post { id title } `); - + const fragments = findFragments([doc1, doc2]); - + expect(fragments.size).toBe(2); expect(fragments.has('UserFragment')).toBe(true); expect(fragments.has('PostFragment')).toBe(true); }); }); - + describe('findUsedFragments', () => { test('finds directly used fragments', () => { const doc = parse(` @@ -85,20 +85,20 @@ describe('Fragment Utilities', () => { } } `); - + const fragments = findFragments([doc]); const operation = doc.definitions.find( - def => def.kind === 'OperationDefinition' + (def) => def.kind === 'OperationDefinition', ); - + // eslint-disable-next-line @typescript-eslint/no-explicit-any const usedFragments = findUsedFragments(operation as any, fragments); - + expect(usedFragments.size).toBe(1); expect(usedFragments.has('UserFragment')).toBe(true); expect(usedFragments.has('PostFragment')).toBe(false); }); - + test('finds nested fragments', () => { const doc = parse(` fragment NameFragment on User { @@ -117,20 +117,20 @@ describe('Fragment Utilities', () => { } } `); - + const fragments = findFragments([doc]); const operation = doc.definitions.find( - def => def.kind === 'OperationDefinition' + (def) => def.kind === 'OperationDefinition', ); - + // eslint-disable-next-line @typescript-eslint/no-explicit-any const usedFragments = findUsedFragments(operation as any, fragments); - + expect(usedFragments.size).toBe(2); expect(usedFragments.has('UserFragment')).toBe(true); expect(usedFragments.has('NameFragment')).toBe(true); }); - + test('throws error on unknown fragment', () => { const doc = parse(` query GetUser { @@ -139,18 +139,18 @@ describe('Fragment Utilities', () => { } } `); - + const fragments = new Map(); const operation = doc.definitions.find( - def => def.kind === 'OperationDefinition' + (def) => def.kind === 'OperationDefinition', ); - - expect(() => + + expect(() => // eslint-disable-next-line @typescript-eslint/no-explicit-any - findUsedFragments(operation as any, fragments) + findUsedFragments(operation as any, fragments), ).toThrow('Unknown fragment: UnknownFragment'); }); - + test('handles circular fragment references', () => { const doc = parse(` fragment Fragment1 on Type { @@ -167,18 +167,18 @@ describe('Fragment Utilities', () => { ...Fragment1 } `); - + const fragments = findFragments([doc]); const operation = doc.definitions.find( - def => def.kind === 'OperationDefinition' + (def) => def.kind === 'OperationDefinition', ); - + // eslint-disable-next-line @typescript-eslint/no-explicit-any const usedFragments = findUsedFragments(operation as any, fragments); - + expect(usedFragments.size).toBe(2); expect(usedFragments.has('Fragment1')).toBe(true); expect(usedFragments.has('Fragment2')).toBe(true); }); }); -}); \ No newline at end of file +}); diff --git a/src/utils/__tests__/transforms.test.ts b/src/utils/__tests__/transforms.test.ts index 709f8d1..1bba406 100644 --- a/src/utils/__tests__/transforms.test.ts +++ b/src/utils/__tests__/transforms.test.ts @@ -11,13 +11,13 @@ describe('Transform Utilities', () => { hello } `); - + const output = printDefinitions([doc.definitions[0] as Definition]); - + expect(output).toContain('query TestQuery'); expect(output).toContain('hello'); }); - + test('prints multiple definitions with spacing', () => { const doc = parse(` query TestQuery { @@ -29,18 +29,18 @@ describe('Transform Utilities', () => { name } `); - + const output = printDefinitions([ doc.definitions[0] as Definition, doc.definitions[1] as Definition, ]); - + expect(output).toContain('query TestQuery'); expect(output).toContain('fragment UserFragment on User'); expect(output.split('\n\n').length).toBe(2); // Definitions separated by 2 newlines }); }); - + describe('addTypenameToDocument', () => { test('adds __typename to a simple query', () => { const doc = parse(` @@ -51,28 +51,30 @@ describe('Transform Utilities', () => { } } `); - + const result = addTypenameToDocument(doc); const printed = printDefinitions([result]); - + expect(printed).toContain('__typename'); expect(printed).toMatch(/user\s*{\s*id\s*name\s*__typename\s*}/s); }); - + test('does not add __typename to operation definitions', () => { const doc = parse(` query TestQuery { hello } `); - + const result = addTypenameToDocument(doc); const printed = printDefinitions([result]); - + // __typename should not be at the top level - expect(printed).not.toMatch(/query TestQuery\s*{\s*hello\s*__typename\s*}/s); + expect(printed).not.toMatch( + /query TestQuery\s*{\s*hello\s*__typename\s*}/s, + ); }); - + test('does not add duplicate __typename fields', () => { const doc = parse(` query TestQuery { @@ -83,28 +85,28 @@ describe('Transform Utilities', () => { } } `); - + const result = addTypenameToDocument(doc); const printed = printDefinitions([result]); - + // Check that there's exactly one __typename const matches = printed.match(/__typename/g); expect(matches).toHaveLength(1); }); - + test('does not add __typename to empty selection sets', () => { const doc = parse(` query TestQuery { scalar } `); - + const result = addTypenameToDocument(doc); const printed = printDefinitions([result]); - + expect(printed).not.toContain('__typename'); }); - + test('handles introspection fields correctly', () => { const doc = parse(` query IntrospectionQuery { @@ -115,18 +117,18 @@ describe('Transform Utilities', () => { } } `); - + const result = addTypenameToDocument(doc); const printed = printDefinitions([result]); - - // The implementation treats introspection fields special - but still adds + + // The implementation treats introspection fields special - but still adds // __typename to selection sets inside them. // This is the current implementation behavior which we're documenting with this test. expect(printed).toContain('__typename'); - - // Verify that it doesn't add __typename at the operation level + + // Verify that it doesn't add __typename at the operation level // (already checked by another test, but double-checking here) expect(printed).not.toContain('query IntrospectionQuery { __typename'); }); }); -}); \ No newline at end of file +}); diff --git a/src/utils/fragments.ts b/src/utils/fragments.ts index 77daef6..cc21475 100644 --- a/src/utils/fragments.ts +++ b/src/utils/fragments.ts @@ -1,13 +1,13 @@ -import { - DocumentNode, - FragmentDefinitionNode, +import { + DocumentNode, + FragmentDefinitionNode, OperationDefinitionNode, - visit + visit, } from 'graphql'; /** * Extracts all fragment definitions from an array of documents - * + * * @param docs - Array of GraphQL document nodes to scan * @returns A map of fragment names to their definitions */ @@ -31,7 +31,7 @@ export function findFragments( /** * Finds all fragments used in an operation or fragment, including nested fragments - * + * * @param operation - The operation or fragment to analyze * @param knownFragments - Map of all available fragments * @param _usedFragments - Optional accumulator for recursive calls @@ -52,13 +52,13 @@ export function findUsedFragments( enter(node) { const fragmentName = node.name.value; const fragment = knownFragments.get(fragmentName); - + if (fragment) { // Skip if we've already processed this fragment to avoid circular references if (usedFragments.has(fragmentName)) { return; } - + usedFragments.set(fragmentName, fragment); // Recursively find nested fragments findUsedFragments(fragment, knownFragments, usedFragments); @@ -70,4 +70,4 @@ export function findUsedFragments( }); return usedFragments; -} \ No newline at end of file +} diff --git a/src/utils/hashing.ts b/src/utils/hashing.ts index 04de594..398d964 100644 --- a/src/utils/hashing.ts +++ b/src/utils/hashing.ts @@ -3,20 +3,23 @@ import { PluginConfig } from '../types'; /** * Creates a hash for a given string using the specified algorithm - * + * * @param content - The content string to hash * @param config - Plugin configuration - * @returns A hex string hash of the content by default, or a prefixed hash (e.g. "sha256:abc123...") + * @returns A hex string hash of the content by default, or a prefixed hash (e.g. "sha256:abc123...") * when includeAlgorithmPrefix is true for GraphQL over HTTP specification compliance */ export function createHash(content: string, config: PluginConfig): string { const algorithm = config.algorithm || 'sha256'; - - const digest = crypto.createHash(algorithm) + + const digest = crypto + .createHash(algorithm) .update(content, 'utf8') .digest() .toString('hex'); - + // Only prefix with algorithm if includeAlgorithmPrefix is true - return config.includeAlgorithmPrefix === true ? `${algorithm}:${digest}` : digest; -} \ No newline at end of file + return config.includeAlgorithmPrefix === true + ? `${algorithm}:${digest}` + : digest; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 96043cb..c5e59a7 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,3 +1,3 @@ export * from './fragments'; export * from './hashing'; -export * from './transforms'; \ No newline at end of file +export * from './transforms'; diff --git a/src/utils/transforms.ts b/src/utils/transforms.ts index 5b64d56..e70ed9a 100644 --- a/src/utils/transforms.ts +++ b/src/utils/transforms.ts @@ -1,10 +1,10 @@ -import { - DocumentNode, - FieldNode, - Kind, - OperationDefinitionNode, - print, - visit +import { + DocumentNode, + FieldNode, + Kind, + OperationDefinitionNode, + print, + visit, } from 'graphql'; import { Definition } from '../types'; @@ -21,18 +21,20 @@ const TYPENAME_FIELD: FieldNode = { /** * Prints an array of definitions as a single string - * + * * @param definitions - Array of GraphQL definitions to print * @returns Printed string representation */ -export function printDefinitions(definitions: (Definition | DocumentNode)[]): string { +export function printDefinitions( + definitions: (Definition | DocumentNode)[], +): string { return definitions.map(print).join('\n\n'); } /** * Adds __typename to all selection sets in a document * Adapted from Apollo Client - * + * * @param doc - GraphQL document node * @returns Document with __typename fields added */ @@ -61,7 +63,7 @@ export function addTypenameToDocument(doc: DocumentNode): DocumentNode { (selection as FieldNode).name.value === '__typename' ); }); - + // Skip if this is an introspection query const isIntrospection = selections.some((selection) => { return ( @@ -69,7 +71,7 @@ export function addTypenameToDocument(doc: DocumentNode): DocumentNode { (selection as FieldNode).name.value.startsWith('__') ); }); - + if (hasTypename || isIntrospection) { return; } @@ -82,4 +84,4 @@ export function addTypenameToDocument(doc: DocumentNode): DocumentNode { }, }, }); -} \ No newline at end of file +} diff --git a/tsup.config.ts b/tsup.config.ts index 454a68d..f07467b 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -1,13 +1,13 @@ -import { defineConfig } from "tsup" +import { defineConfig } from 'tsup'; export default defineConfig({ - clean: true, - dts: true, - entry: ["src/index.ts"], - format: ["esm"], - sourcemap: true, - minify: true, - target: "esnext", - outDir: "dist", - treeshake: true, -}) \ No newline at end of file + clean: true, + dts: true, + entry: ['src/index.ts'], + format: ['esm'], + sourcemap: true, + minify: true, + target: 'esnext', + outDir: 'dist', + treeshake: true, +}); diff --git a/vitest.config.ts b/vitest.config.ts index 0f570ab..78f2771 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -10,4 +10,4 @@ export default defineConfig({ exclude: ['node_modules/'], }, }, -}); \ No newline at end of file +});