From 5a26f7a26287198f53a157706ba291513a930112 Mon Sep 17 00:00:00 2001 From: Micah Buckley-Farlee Date: Wed, 9 Jul 2025 14:12:47 -0500 Subject: [PATCH 1/3] Re-Merge pull request #64 from FlatFilers/mbf/v2-2 Add V2 Records API --- bunfig.toml | 2 + package.json | 36 +- src/v2/records/index.ts | 612 ++++++++++++++++++++++++++++ src/v2/records/types.ts | 162 ++++++++ tests/bun/records.test.ts | 327 +++++++++++++++ tests/fixtures/v2.records.get.jsonl | 21 + yarn.lock | 166 +++++++- 7 files changed, 1307 insertions(+), 19 deletions(-) create mode 100644 bunfig.toml create mode 100644 src/v2/records/index.ts create mode 100644 src/v2/records/types.ts create mode 100644 tests/bun/records.test.ts create mode 100644 tests/fixtures/v2.records.get.jsonl diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 00000000..fad65cce --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[test] +root = "./tests/bun" diff --git a/package.json b/package.json index b98b3781..c27e6520 100644 --- a/package.json +++ b/package.json @@ -3,41 +3,41 @@ "version": "1.18.0", "private": false, "repository": "https://github.com/FlatFilers/flatfile-node", - "main": "./index.js", - "types": "./index.d.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "scripts": { "format": "prettier . --write --ignore-unknown", - "build": "tsc", - "prepack": "cp -rv dist/. .", + "build": "rimraf dist && tsc", "test": "jest" }, "dependencies": { - "url-join": "4.0.1", + "@flatfile/cross-env-config": "0.0.6", "form-data": "^4.0.0", + "form-data-encoder": "^4.0.2", "formdata-node": "^6.0.3", + "js-base64": "3.7.7", "node-fetch": "^2.7.0", + "pako": "2.0.3", "qs": "^6.13.1", "readable-stream": "^4.5.2", - "js-base64": "3.7.7", - "form-data-encoder": "^4.0.2", - "@flatfile/cross-env-config": "0.0.6", - "@types/pako": "2.0.3", - "pako": "2.0.3" + "url-join": "4.0.1" }, "devDependencies": { - "@types/url-join": "4.0.1", - "@types/qs": "^6.9.17", + "@types/jest": "^29.5.14", + "@types/node": "^18.19.70", "@types/node-fetch": "^2.6.12", + "@types/pako": "2.0.3", + "@types/qs": "^6.9.17", "@types/readable-stream": "^4.0.18", - "webpack": "^5.97.1", - "ts-loader": "^9.5.1", + "@types/url-join": "4.0.1", "jest": "^29.7.0", - "@types/jest": "^29.5.14", - "ts-jest": "^29.1.1", "jest-environment-jsdom": "^29.7.0", - "@types/node": "^18.19.70", "prettier": "^3.4.2", - "typescript": "~5.7.2" + "rimraf": "^6.0.1", + "ts-jest": "^29.1.1", + "ts-loader": "^9.5.1", + "typescript": "~5.7.2", + "webpack": "^5.97.1" }, "browser": { "fs": false, diff --git a/src/v2/records/index.ts b/src/v2/records/index.ts new file mode 100644 index 00000000..bfcb1769 --- /dev/null +++ b/src/v2/records/index.ts @@ -0,0 +1,612 @@ +import { Records as FernRecords } from "../../api/resources/records/client/Client"; +import { Flatfile } from "../.."; +import * as core from "../../core"; +import urlJoin from "url-join"; +import { + GetRecordsRequestOptions, + JsonlRecord, + WriteRecordsRequestOptions, + WriteRecordsResponse, + WriteStreamingOptions, +} from "./types"; +import * as environments from "../../environments"; +import * as errors from "../../errors"; +import * as serializers from "../../serialization"; +import { toRawResponse } from "../../core/fetcher/RawResponse"; + +interface RecordsOptions extends FernRecords.Options {} + +export class RecordsV2 { + private readonly options: RecordsOptions; + + constructor(options: RecordsOptions = {}) { + this.options = options; + } + + /** + * Retrieve records from a sheet in raw JSONL format. + * + * This method fetches all records at once and returns them as an array of + * JsonlRecord objects, which contain the raw data structure from the API + * including special fields like __k (record ID), __v (version), etc. + * + * @param sheetId - The ID of the sheet to retrieve records from + * @param options - Optional request parameters for filtering, pagination, etc. + * @param requestOptions - Optional request configuration (headers, timeout, etc.) + * @returns Promise that resolves to an array of JsonlRecord objects + * + * @example + * ```typescript + * const rawRecords = await recordsV2.getRaw('us_sh_123', { + * fields: ['firstName', 'lastName'], + * pageSize: 1000 + * }); + * rawRecords.forEach(record => { + * console.log(`Record ID: ${record.__k}`); + * console.log(`Field values:`, record); + * }); + * ``` + */ + public async getRaw( + sheetId: Flatfile.SheetId, + options: GetRecordsRequestOptions = {}, + requestOptions: FernRecords.RequestOptions, + ): Promise { + const url = await this._buildUrl(`/v2-alpha/records.jsonl`); + + // Add sheetId and options as query parameters + const queryParams = this._buildQueryParams({ ...options, sheetId }); + if (queryParams.length > 0) { + url.search = queryParams; + } + + const headers = await this._prepareHeaders(requestOptions); + const response = await this._executeRequest(url.toString(), "GET", { + headers, + requestOptions, + }); + + // Parse the JSONL response into an array of records + const text = await response.text(); + return this._parseJsonlText(text); + } + + /** + * Stream records from a sheet in raw JSONL format. + * + * This method provides an async generator that yields JsonlRecord objects + * as they are received from the server. This is the most memory-efficient + * way to process large datasets as records are yielded individually rather + * than loading all records into memory at once. + * + * @param sheetId - The ID of the sheet to retrieve records from + * @param options - Optional request parameters for filtering, pagination, etc. + * @param requestOptions - Optional request configuration (headers, timeout, etc.) + * @returns AsyncGenerator that yields JsonlRecord objects + * + * @example + * ```typescript + * for await (const rawRecord of recordsV2.getRawStreaming('us_sh_123', { + * includeTimestamps: true + * })) { + * console.log(`Record ID: ${rawRecord.__k}`); + * console.log(`Updated at: ${rawRecord.__u}`); + * // Process each record as it streams in + * } + * ``` + */ + public async *getRawStreaming( + sheetId: Flatfile.SheetId, + options: GetRecordsRequestOptions = {}, + requestOptions: FernRecords.RequestOptions = {}, + ): AsyncGenerator { + const url = await this._buildUrl(`/v2-alpha/records.jsonl`); + + // Add sheetId and options as query parameters + const queryParams = this._buildQueryParams({ ...options, sheetId }); + if (queryParams.length > 0) { + url.search = queryParams; + } + + const headers = await this._prepareHeaders(requestOptions); + const response = await this._executeRequest(url.toString(), "GET", { + headers, + requestOptions, + }); + + // Check if ReadableStream streaming is supported + if (response.body && typeof response.body.getReader === "function") { + // Use streaming approach for supported browsers + yield* this._streamJsonlResponse(response); + } else { + // Final fallback for browsers without streaming support + yield* this._fallbackJsonlResponse(response); + } + } + + /** + * Stream JSONL response using ReadableStream (modern browsers) + */ + private async *_streamJsonlResponse(response: Response): AsyncGenerator { + if (!response.body) { + throw new errors.FlatfileError({ + message: "Response body is null", + rawResponse: undefined, + }); + } + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + + try { + while (true) { + const { done, value } = await reader.read(); + + if (done) { + // Process any remaining data in buffer + if (buffer.trim()) { + try { + yield JSON.parse(buffer.trim()) as JsonlRecord; + } catch (error) { + // Skip malformed final line + console.warn("Failed to parse final JSONL line:", buffer.trim()); + } + } + break; + } + + // Decode chunk and add to buffer + buffer += decoder.decode(value, { stream: true }); + + // Process complete lines + const lines = buffer.split("\n"); + buffer = lines.pop() || ""; // Keep incomplete line in buffer + + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine) { + try { + yield JSON.parse(trimmedLine) as JsonlRecord; + } catch (error) { + // Skip malformed line but continue streaming + console.warn("Failed to parse JSONL line:", trimmedLine); + } + } + } + } + } finally { + reader.releaseLock(); + } + } + + /** + * Parse JSONL text into an array of JsonlRecord objects + */ + private _parseJsonlText(text: string): JsonlRecord[] { + const lines = text.split("\n"); + const records: JsonlRecord[] = []; + + for (const line of lines) { + const trimmedLine = line.trim(); + if (trimmedLine) { + try { + records.push(JSON.parse(trimmedLine) as JsonlRecord); + } catch (error) { + console.warn("Failed to parse JSONL line:", trimmedLine); + } + } + } + + return records; + } + + /** + * Fallback JSONL processing for browsers without streaming support + */ + private async *_fallbackJsonlResponse(response: Response): AsyncGenerator { + const text = await response.text(); + const records = this._parseJsonlText(text); + + for (const record of records) { + yield record; + } + } + + /** + * Write records to a sheet in raw JSONL format. + * + * This method takes an array of JsonlRecord objects and writes them to the specified sheet. + * Records can be inserts (no __k field) or updates (with __k field for existing record ID). + * Supports various write options like truncate, snapshot, and sheet targeting. + * + * @param records - Array of JsonlRecord objects to write + * @param options - Write configuration options + * @param requestOptions - Optional request configuration (headers, timeout, etc.) + * @returns Promise that resolves to WriteRecordsResponse with operation results + * + * @example + * ```typescript + * const records: JsonlRecord[] = [ + * { firstName: 'John', lastName: 'Doe', __s: 'us_sh_123' }, + * { __k: 'us_rc_456', firstName: 'Jane', lastName: 'Smith' } // Update existing + * ]; + * const result = await recordsV2.writeRaw(records, { + * sheetId: 'us_sh_123', + * truncate: false + * }); + * console.log(`Created: ${result.created}, Updated: ${result.updated}`); + * ``` + */ + public async writeRaw( + records: JsonlRecord[], + options: WriteRecordsRequestOptions = {}, + requestOptions: FernRecords.RequestOptions = {}, + ): Promise { + const url = await this._buildUrl(`/v2-alpha/records.jsonl`); + + // For write operations, ensure all records have the sheet ID set if provided in options + const enrichedRecords = records.map((record) => { + // Always ensure sheet ID is present when provided in options + if (options.sheetId) { + return { __s: options.sheetId, ...record }; + } + return record; + }); + + // Add options as query parameters, excluding sheetId since it's in the record body + const { sheetId: _, ...queryOptions } = options; + const queryParams = this._buildQueryParams(queryOptions, false); // Write operation + if (queryParams.length > 0) { + url.search = queryParams; + } + + // Convert records to JSONL format (one record per line) + const jsonlBody = enrichedRecords.map((record) => JSON.stringify(record)).join("\n"); + + const headers = await this._prepareHeaders(requestOptions, "application/jsonl"); + + // Add sheet ID header if provided in options and not already in records + if (options.sheetId) { + headers["X-Sheet-Id"] = options.sheetId; + } + + const response = await this._executeRequest(url.toString(), "POST", { + body: jsonlBody, + contentType: "application/jsonl", + headers, + requestOptions, + }); + + // Parse the response + const responseBody = await response.text(); + try { + return JSON.parse(responseBody) as WriteRecordsResponse; + } catch (error) { + // If response isn't JSON, return a basic success response + return { success: true }; + } + } + + /** + * Stream records to a sheet in raw JSONL format using HTTP body streaming. + * + * This method accepts an async generator/iterator of records and streams them + * directly to the server using a ReadableStream as the HTTP request body. + * This approach is memory efficient for large datasets as records are processed + * and transmitted without loading all data into memory at once. + * + * The operation is atomic - all records are sent in a single HTTP request, + * ensuring consistent write semantics. Records can be new inserts (without __k) + * or updates to existing records (with __k field containing the record ID). + * + * @param recordsStream - Async generator/iterator that yields JsonlRecord objects + * @param options - Write configuration options (sheetId, truncate, etc.) + * @param requestOptions - Optional request configuration (headers, timeout, etc.) + * @returns Promise that resolves to WriteRecordsResponse with operation results + * + * @example + * ```typescript + * async function* generateRecords() { + * for (let i = 0; i < 100000; i++) { + * yield { + * firstName: `User${i}`, + * email: `user${i}@example.com`, + * __s: 'us_sh_123' + * }; + * } + * } + * + * const result = await recordsV2.writeRawStreaming(generateRecords(), { + * sheetId: 'us_sh_123', + * truncate: false + * }); + * console.log(`Created: ${result.created}, Updated: ${result.updated}`); + * ``` + */ + public async writeRawStreaming( + recordsStream: AsyncIterable, + options: WriteStreamingOptions = {}, + requestOptions: FernRecords.RequestOptions = {}, + ): Promise { + const url = await this._buildUrl(`/v2-alpha/records.jsonl`); + + // Add options as query parameters, excluding sheetId since it's in the record body + const { sheetId, ...queryOptions } = options; + const queryParams = this._buildQueryParams(queryOptions, false); // Write operation + if (queryParams.length > 0) { + url.search = queryParams; + } + + // Create ReadableStream that converts records to JSONL + const readableStream = new ReadableStream({ + async start(controller) { + const encoder = new TextEncoder(); + try { + for await (const record of recordsStream) { + // Ensure sheet ID is set if provided in options + const enrichedRecord = sheetId && !record.__s ? { ...record, __s: sheetId } : record; + const jsonlLine = JSON.stringify(enrichedRecord) + "\n"; + controller.enqueue(encoder.encode(jsonlLine)); + } + controller.close(); + } catch (error) { + controller.error(error); + } + }, + }); + + const headers = await this._prepareHeaders(requestOptions, "application/jsonl"); + + // Execute the streaming request + const response = await this._executeRequest(url.toString(), "POST", { + body: readableStream as any, // TypeScript might complain about ReadableStream + contentType: "application/jsonl", + headers, + requestOptions, + }); + + // Parse the response + const responseBody = await response.text(); + try { + return JSON.parse(responseBody) as WriteRecordsResponse; + } catch (error) { + // If response isn't JSON, return a basic success response + return { success: true }; + } + } + + private async _prepareHeaders( + requestOptions?: FernRecords.RequestOptions, + contentType: string = "application/json", + ): Promise> { + const authHeader = await this._getAuthorizationHeader(); + const headers: Record = { + "X-Disable-Hooks": requestOptions?.xDisableHooks ?? this.options?.xDisableHooks ?? "true", + "X-Fern-Language": "JavaScript", + "X-Fern-Runtime": core.RUNTIME.type ?? "node", + "X-Fern-Runtime-Version": core.RUNTIME.version ?? "unknown", + "Content-Type": contentType, + ...requestOptions?.headers, + }; + + // Only add Authorization header if we have a token + if (authHeader) { + headers.Authorization = authHeader; + } + + return headers; + } + + /** + * Get authorization header from options + */ + private async _getAuthorizationHeader(): Promise { + const bearer = await core.Supplier.get(this.options.token); + if (bearer != null) { + return `Bearer ${bearer}`; + } + return undefined; + } + + /** + * Build full URL from path + */ + private async _buildUrl(path: string): Promise { + let baseUrl = + (await core.Supplier.get(this.options.baseUrl)) ?? + (await core.Supplier.get(this.options.environment)) ?? + environments.FlatfileEnvironment.Production; + + // Special handling for v2-alpha endpoints - remove /v1 suffix if present + if (path.startsWith("/v2-alpha") && baseUrl.endsWith("/v1")) { + baseUrl = baseUrl.slice(0, -3); + } + + return new URL(urlJoin(baseUrl, path)); + } + + /** + * Build query parameters string from options object + */ + private _buildQueryParams(params: Record, isReadOperation: boolean = true): string { + const searchParams = new URLSearchParams(); + + // Only include stream=true for read operations (GET requests) + if (isReadOperation) { + searchParams.append("stream", "true"); + } + + for (const [key, value] of Object.entries(params)) { + if (value !== undefined && value !== null) { + if (Array.isArray(value)) { + // Handle array values (like fields) + value.forEach((item) => { + if (item !== undefined && item !== null) { + searchParams.append(key, String(item)); + } + }); + } else { + searchParams.append(key, String(value)); + } + } + } + + return searchParams.toString(); + } + + /** + * Execute HTTP request with retry logic and timeout handling + */ + private async _executeRequest( + url: string, + method: string, + options: { + body?: string | Uint8Array | ReadableStream; + contentType?: string; + headers?: Record; + requestOptions?: FernRecords.RequestOptions; + } = {}, + ): Promise { + const { body, contentType = "application/json", headers: directHeaders, requestOptions } = options; + + // Prepare headers - merge direct headers with those from requestOptions + const preparedHeaders = await this._prepareHeaders(requestOptions, contentType); + const finalHeaders = { ...preparedHeaders, ...directHeaders }; + + // Setup timeout and retry configuration + const timeoutMs = requestOptions?.timeoutInSeconds != null ? requestOptions.timeoutInSeconds * 1000 : 60000; + const maxRetries = requestOptions?.maxRetries ?? 0; + + let lastError: Error | null = null; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + const controller = new AbortController(); + let timeoutId: NodeJS.Timeout | null = null; + + try { + // Setup external abort signal forwarding + if (requestOptions?.abortSignal) { + requestOptions.abortSignal.addEventListener("abort", () => controller.abort()); + } + + // Setup timeout + if (timeoutMs > 0) { + timeoutId = setTimeout(() => controller.abort(), timeoutMs); + } + + // Prepare fetch options + const fetchOptions: RequestInit = { + method, + headers: finalHeaders, + body, + signal: controller.signal, + }; + + // Add duplex option when body is a ReadableStream (only if ReadableStream is available) + if (typeof ReadableStream !== "undefined" && body instanceof ReadableStream) { + (fetchOptions as any).duplex = "half"; + } + + const response = await fetch(url, fetchOptions); + + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Handle non-success status codes by throwing appropriate errors + if (!response.ok) { + const errorBody = await this._parseErrorBody(response); + this._throwErrorForStatus(response.status, errorBody, response); + } + + return response; + } catch (error) { + if (timeoutId) { + clearTimeout(timeoutId); + } + + // Handle abort/timeout + if (error instanceof Error && error.name === "AbortError") { + throw new errors.FlatfileTimeoutError(`Timeout exceeded when calling ${method} ${url}.`); + } + + // If this is already a Flatfile error, rethrow it + if (error instanceof errors.FlatfileError || error instanceof errors.FlatfileTimeoutError) { + throw error; + } + + lastError = error as Error; + + // If we've exhausted retries, throw the last error + if (attempt === maxRetries) { + throw new errors.FlatfileError({ + message: lastError.message, + rawResponse: undefined, + }); + } + + // Wait before retry (exponential backoff) + if (attempt < maxRetries) { + await new Promise((resolve) => setTimeout(resolve, Math.pow(2, attempt) * 1000)); + } + } + } + + // This should never be reached, but just in case + throw new errors.FlatfileError({ + message: lastError?.message ?? "Unknown error occurred", + rawResponse: undefined, + }); + } + + /** + * Parse error body from response + */ + private async _parseErrorBody(response: Response): Promise { + try { + const text = await response.text(); + return text ? JSON.parse(text) : {}; + } catch { + return {}; + } + } + + /** + * Throw appropriate error based on HTTP status code + */ + private _throwErrorForStatus(status: number, errorBody: any, response: Response): never { + const rawResponse = toRawResponse(response); + + switch (status) { + case 400: + throw new Flatfile.BadRequestError( + serializers.Errors.parseOrThrow(errorBody, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }), + rawResponse, + ); + case 404: + throw new Flatfile.NotFoundError( + serializers.Errors.parseOrThrow(errorBody, { + unrecognizedObjectKeys: "passthrough", + allowUnrecognizedUnionMembers: true, + allowUnrecognizedEnumValues: true, + skipValidation: true, + breadcrumbsPrefix: ["response"], + }), + rawResponse, + ); + default: + throw new errors.FlatfileError({ + statusCode: status, + body: errorBody, + rawResponse: rawResponse, + }); + } + } +} diff --git a/src/v2/records/types.ts b/src/v2/records/types.ts new file mode 100644 index 00000000..e52a02a7 --- /dev/null +++ b/src/v2/records/types.ts @@ -0,0 +1,162 @@ +import * as Flatfile from "../../api/index"; +import { Sheet } from "../../serialization"; + +interface SheetSearchParams { + filter?: string; + filterField?: string; + searchField?: string; + searchValue?: string; + q?: string; + commitId?: string; + sinceCommitId?: string; + ids?: string; + forEvent?: string; +} + +interface SheetSortParams { + sortField?: string; + sortDirection?: "asc" | "desc"; +} + +interface PaginationParams { + pageNumber?: number; + pageSize?: number; +} + +/** + * Request options for records endpoint query parameters + */ +interface GetRecordsParams { + /** The ID of the Sheet */ + sheetId?: Flatfile.SheetId; + /** The ID of the Workbook */ + workbookId?: Flatfile.WorkbookId; + /** The slug of the Sheet */ + sheetSlug?: string; + /** Whether to include links in the response */ + includeLinks?: boolean; + /** Whether to include messages in the response */ + includeMessages?: boolean; + /** Whether to include metadata in the response */ + includeMetadata?: boolean; + /** Whether to include config in the response */ + includeConfig?: boolean; + /** Whether to include sheet in the response */ + includeSheet?: boolean; + /** Whether to include sheet slug in the response */ + includeSheetSlug?: boolean; + /** Whether to include the updated at timestamps in the response */ + includeTimestamps?: boolean; + /** Whether to exclude context from the response */ + noContext?: boolean; + /** Whether to stream the response */ + isStream?: boolean; + /** Whether to include empty cells/fields in the response */ + includeEmptyCells?: boolean; + /** Specific fields to include in the response */ + fields?: string[]; +} + +export type GetRecordsRequestOptions = GetRecordsParams & SheetSearchParams & SheetSortParams & PaginationParams; + +interface JsonlRecordSpecialParams { + /** + * Record ID + */ + __k?: string; + + /** + * Record ID (for Creation) + */ + __nk?: string; + + /** + * Record version ID + */ + __v?: string; + + /** + * Sheet ID + */ + __s?: string; + + /** + * Sheet slug + */ + __n?: string; + + /** + * Config + */ + __c?: Record; + + /** + * Metadata + */ + __m?: Record; + + /** + * Messages + */ + __i?: Record; + + /** + * Whether the record is deleted + */ + __d?: boolean; + + /** + * Whether the record is valid + */ + __e?: boolean; + + /** + * Links to other records + */ + __l?: JsonlRecord[]; + + /** + * Record-level updated timestamp (when includeTimestamps=true) + */ + __u?: string; +} + +export interface JsonlRecord extends JsonlRecordSpecialParams { + [fieldKey: string]: any; +} + +/** + * Request options for writing records + */ +interface WriteRecordsParams { + /** The ID of the Sheet */ + sheetId?: Flatfile.SheetId; + /** The ID of the Workbook */ + workbookId?: Flatfile.WorkbookId; + /** The slug of the Sheet */ + sheetSlug?: string; + /** Whether to truncate (clear) the sheet before writing */ + truncate?: boolean; + /** Whether to create a snapshot before writing */ + snapshot?: boolean; + /** Whether to suppress hooks during writing */ + silent?: boolean; + /** Event ID for context */ + for?: Flatfile.EventId; +} + +export type WriteRecordsRequestOptions = WriteRecordsParams; + +/** + * Response structure for write operations + */ +export interface WriteRecordsResponse { + success: boolean; + created?: number; + updated?: number; +} + +/** + * Options for streaming write operations + */ +export interface WriteStreamingOptions extends WriteRecordsRequestOptions {} diff --git a/tests/bun/records.test.ts b/tests/bun/records.test.ts new file mode 100644 index 00000000..f75aceb2 --- /dev/null +++ b/tests/bun/records.test.ts @@ -0,0 +1,327 @@ +import fs from "fs"; +import { join } from "path"; +import { RecordsV2 } from "../../src/v2/records"; +import { Flatfile } from "../../src"; + +// Mock the global fetch function +const mockFetch = jest.fn(); +global.fetch = mockFetch; + +// Helper to read the fixture data +const getFixtureData = (): string => { + return fs.readFileSync(join(__dirname, "../fixtures/v2.records.get.jsonl"), "utf-8"); +}; + +// Helper to parse JSONL fixture data into records array +const parseFixtureData = (): any[] => { + const data = getFixtureData(); + return data + .split("\n") + .filter((line) => line.trim()) + .map((line) => JSON.parse(line)); +}; + +describe("RecordsV2", () => { + let recordsV2: RecordsV2; + const sheetId = "dev_sh_jVnmFCKg" as Flatfile.SheetId; + const defaultRequestOptions = {}; + + beforeEach(() => { + recordsV2 = new RecordsV2({ + token: "test-token", + baseUrl: "https://api.flatfile.com/v1", + }); + mockFetch.mockClear(); + mockFetch.mockReset(); + }); + + describe("getRaw", () => { + it("should fetch and return raw JSONL records", async () => { + const fixtureData = getFixtureData(); + + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve(fixtureData), + body: null, + }); + + const result = await recordsV2.getRaw(sheetId, {}, defaultRequestOptions); + + expect(result).toHaveLength(21); + + // Check first record structure + const firstRecord = result[0]; + expect(firstRecord).toMatchObject({ + __k: "dev_rc_a5d2afda7dda4149afe51229e2674906", + __s: "dev_sh_jVnmFCKg", + __n: "contacts-pCZHI4", + firstname: "John", + lastname: "Smith [X]", + email: "john.smith@example.com", + }); + }); + + it("should handle empty response", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve(""), + body: null, + }); + + const result = await recordsV2.getRaw(sheetId, {}, defaultRequestOptions); + expect(result).toEqual([]); + }); + + it("should skip malformed JSONL lines", async () => { + const malformedData = `{"__k":"valid1","name":"John"} +invalid json line +{"__k":"valid2","name":"Jane"}`; + + // Mock console.warn to avoid test output noise + const consoleSpy = jest.spyOn(console, "warn").mockImplementation(); + + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve(malformedData), + body: null, + }); + + const result = await recordsV2.getRaw(sheetId, {}, defaultRequestOptions); + + expect(result).toHaveLength(2); + expect(result[0].__k).toBe("valid1"); + expect(result[1].__k).toBe("valid2"); + expect(consoleSpy).toHaveBeenCalledWith("Failed to parse JSONL line:", "invalid json line"); + + consoleSpy.mockRestore(); + }); + }); + + describe("getRawStreaming", () => { + it("should stream raw JSONL records with ReadableStream", async () => { + // Test ReadableStream with single record to verify core functionality + const testData = `{"__k":"stream_test_id","name":"StreamTest"}`; + const encoder = new TextEncoder(); + const chunk = encoder.encode(testData); + + const mockStream = new ReadableStream({ + start(controller) { + controller.enqueue(chunk); + controller.close(); + }, + }); + + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + body: mockStream, + }); + + const results: any[] = []; + for await (const record of recordsV2.getRawStreaming(sheetId, {}, defaultRequestOptions)) { + results.push(record); + } + + expect(results).toHaveLength(1); + expect(results[0].__k).toBe("stream_test_id"); + expect(results[0].name).toBe("StreamTest"); + }); + + it("should handle streaming without ReadableStream support", async () => { + const fixtureData = getFixtureData(); + + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve(fixtureData), + body: null, // No ReadableStream support + }); + + const results: any[] = []; + for await (const record of recordsV2.getRawStreaming(sheetId, {}, defaultRequestOptions)) { + results.push(record); + } + + expect(results).toHaveLength(21); + expect(results[0].__k).toBe("dev_rc_a5d2afda7dda4149afe51229e2674906"); + }); + }); + + describe("writeRaw", () => { + it("should write JSONL records successfully", async () => { + const mockResponse = { success: true, created: 2, updated: 0 }; + + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve(JSON.stringify(mockResponse)), + }); + + const records = [ + { firstName: "John", lastName: "Doe", __s: sheetId }, + { firstName: "Jane", lastName: "Smith", __s: sheetId }, + ]; + + const result = await recordsV2.writeRaw(records, { sheetId }, defaultRequestOptions); + + expect(mockFetch).toHaveBeenCalledTimes(1); + + const fetchCall = mockFetch.mock.calls[0]; + expect(fetchCall[0]).toContain("/v2-alpha/records.jsonl"); + expect(fetchCall[1].method).toBe("POST"); + expect(fetchCall[1].headers).toMatchObject({ + Authorization: "Bearer test-token", + "Content-Type": "application/jsonl", + "X-Disable-Hooks": "true", + "X-Fern-Language": "JavaScript", + }); + + // Check the body content by parsing the JSONL + const bodyLines = fetchCall[1].body.split("\n"); + expect(bodyLines).toHaveLength(2); + + const firstRecord = JSON.parse(bodyLines[0]); + expect(firstRecord).toMatchObject({ + __s: sheetId, + firstName: "John", + lastName: "Doe", + }); + + const secondRecord = JSON.parse(bodyLines[1]); + expect(secondRecord).toMatchObject({ + __s: sheetId, + firstName: "Jane", + lastName: "Smith", + }); + + expect(result).toEqual(mockResponse); + }); + + it("should pass query parameters correctly for writeRaw", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve(JSON.stringify({ success: true })), + }); + + const records = [{ firstName: "John", __s: sheetId }]; + const options = { + sheetId, + truncate: true, + snapshot: true, + silent: true, + for: "event_123" as Flatfile.EventId, + }; + + await recordsV2.writeRaw(records, options, defaultRequestOptions); + + const fetchCall = mockFetch.mock.calls[0]; + const url = new URL(fetchCall[0]); + + // sheetId is excluded from query parameters for write operations (it's in the record body) + expect(url.searchParams.get("sheetId")).toBe(null); + expect(url.searchParams.get("truncate")).toBe("true"); + expect(url.searchParams.get("snapshot")).toBe("true"); + expect(url.searchParams.get("silent")).toBe("true"); + expect(url.searchParams.get("for")).toBe("event_123"); + }); + + it("should handle non-JSON response for writeRaw", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve("OK"), + }); + + const records = [{ firstName: "John", __s: sheetId }]; + const result = await recordsV2.writeRaw(records, { sheetId }, defaultRequestOptions); + + expect(result).toEqual({ success: true }); + }); + + it("should handle writeRaw fetch errors", async () => { + mockFetch.mockResolvedValueOnce({ + ok: false, + status: 400, + text: () => Promise.resolve(JSON.stringify({ error: "Bad request" })), + }); + + const records = [{ firstName: "John", __s: sheetId }]; + + await expect(recordsV2.writeRaw(records, { sheetId }, defaultRequestOptions)).rejects.toThrow(); + }); + + it("should send empty body for empty records array", async () => { + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve(JSON.stringify({ success: true })), + }); + + await recordsV2.writeRaw([], { sheetId }, defaultRequestOptions); + + const fetchCall = mockFetch.mock.calls[0]; + expect(fetchCall[1].body).toBe(""); + }); + }); + + describe("writeRawStreaming", () => { + it("should stream records using body streaming", async () => { + const mockResponse = { success: true, created: 3, updated: 0 }; + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve(JSON.stringify(mockResponse)), + }); + + async function* generateRecords() { + yield { firstName: "User1", __s: sheetId }; + yield { firstName: "User2", __s: sheetId }; + yield { firstName: "User3", __s: sheetId }; + } + + const result = await recordsV2.writeRawStreaming(generateRecords(), { sheetId }, defaultRequestOptions); + + expect(mockFetch).toHaveBeenCalledTimes(1); // Single request with streaming body + expect(result).toEqual(mockResponse); + + // Check that the body is a ReadableStream-like object + const fetchCall = mockFetch.mock.calls[0]; + expect(fetchCall[1].body).toBeDefined(); + }); + + it("should handle streaming errors properly", async () => { + mockFetch.mockRejectedValueOnce(new Error("Network error")); + + async function* generateRecords() { + yield { firstName: "User1", __s: sheetId }; + } + + await expect( + recordsV2.writeRawStreaming(generateRecords(), { sheetId }, defaultRequestOptions), + ).rejects.toThrow("Network error"); + }); + + it("should handle empty async generator", async () => { + const mockResponse = { success: true, created: 0, updated: 0 }; + mockFetch.mockResolvedValueOnce({ + ok: true, + status: 200, + text: () => Promise.resolve(JSON.stringify(mockResponse)), + }); + + async function* generateRecords() { + // Empty generator + return; + } + + const result = await recordsV2.writeRawStreaming(generateRecords(), { sheetId }, defaultRequestOptions); + + expect(mockFetch).toHaveBeenCalledTimes(1); // Always makes a request even for empty stream + expect(result).toEqual(mockResponse); + }); + }); +}); diff --git a/tests/fixtures/v2.records.get.jsonl b/tests/fixtures/v2.records.get.jsonl new file mode 100644 index 00000000..be77dcdd --- /dev/null +++ b/tests/fixtures/v2.records.get.jsonl @@ -0,0 +1,21 @@ +{"__k":"dev_rc_a5d2afda7dda4149afe51229e2674906","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","firstname":"John","lastname":"Smith [X]","email":"john.smith@example.com","company":"TechCorp","home_address":"123 Main St, New York, NY, 10001, USA","billing_address":"123 Main St, New York, NY, 10001, USA","is_subscribed":"No","birth_date":"6/15/85","gender":"M","lang":"Englisch","notes":"Top client","tag1":"VIP","tag2":"Priority","contact_method_1_type":"Email","contact_method_1_value":"john.smith@example.com","contact_method_2_type":"Phone","contact_method_2_value":"(312) 555-1234"} +{"__k":"dev_rc_8b2910e275e04f45b48f05f6f1774364","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","firstname":"Emily","lastname":"Davis","email":"emily.davis@EXAMple.com","company":"HealthCare Inc","home_address":"456 Elm St, Los Angeles, CA, 90001, USA","billing_address":"456 Elm St, Los Angeles, CA, 90001, USA","is_subscribed":"Yes","birth_date":"8/23/90","gender":"F","lang":"Spanisch","notes":"Opted out of newsletters","tag1":"Prospect","contact_method_1_type":"Email","contact_method_1_value":"emily.davis@EXAMple.com","contact_method_2_type":"Phone","contact_method_2_value":"(312) 555-5678"} +{"__k":"dev_rc_2e63adcdbdca49a18d469c4bace6eec3","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","firstname":"Michael","lastname":"Johnson","email":"michael.johnson@example.com","company":"FinancePro","home_address":"789 Pine Ave, Chicago, IL, 60601, USA","billing_address":"789 Pine Ave, Chicago, IL, 60601, USA","is_subscribed":"No","birth_date":"2/11/78","gender":"M","lang":"Französisch","tag1":"High Value","contact_method_1_type":"Email","contact_method_1_value":"michael.johnson@example.com","contact_method_2_type":"Phone","contact_method_2_value":"(312) 555-2345"} +{"__k":"dev_rc_6eded17a9c134053a22a25f2ddd28590","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","firstname":"Sarah","lastname":"Brown","email":"sarah.brown@example.com","company":"EduPlus","home_address":"101 Maple Dr, Houston, TX, 77001, USA","is_subscribed":"No","birth_date":"11/4/82","gender":"F","lang":"Englisch","tag1":"Referral","tag2":"Potential","contact_method_1_type":"Email","contact_method_1_value":"sarah.brown@example.com","contact_method_2_type":"Phone","contact_method_2_value":"(312) 555-3456"} +{"__k":"dev_rc_ee47a640c52347aaa38f063b1a798444","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","firstname":"David","lastname":"Wilson [X]","email":"david.wilson@example.com","company":"AutoGroup","home_address":"202 Cedar Ln, Phoenix, AZ, 85001, USA","is_subscribed":"Yes","birth_date":"5/12/80","gender":"M","lang":"Deutsch","tag1":"Customer","tag2":"Feedback","contact_method_1_type":"Email","contact_method_1_value":"david.wilson@example.com","contact_method_2_type":"Mobile","contact_method_2_value":"555-4567"} +{"__k":"dev_rc_63d66e65bfbd4cb58b91e4decaeea667","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Dr.","firstname":"Olivia","lastname":"Miller","email":"olivia.miller@example.com","company":"GreenEnergy","home_address":"303 Birch Rd, Philadelphia, PA, 19101, USA","is_subscribed":"No","birth_date":"3/27/95","gender":"F","lang":"Englisch","tag1":"VIP","contact_method_1_type":"Email","contact_method_1_value":"olivia.miller@example.com","contact_method_2_type":"Mobile","contact_method_2_value":"555-5679"} +{"__k":"dev_rc_deb4e6af3ba5469daf03446468d3a9dc","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Mr.","firstname":"James","lastname":"Taylor","email":"james.taylor@example.com","company":"BuildCorp","home_address":"404 Willow St, San Diego, CA, 92101, USA","is_subscribed":"No","birth_date":"6/12/86","gender":"M","lang":"Spanisch","notes":"Interested in expansion projects","tag1":"Client","tag2":"Follow-Up","contact_method_1_type":"Email","contact_method_1_value":"james.taylor@example.com","contact_method_2_type":"Mobile","contact_method_2_value":"555-6780"} +{"__k":"dev_rc_b1ae5bfce51c449db41083e2ccea516a","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Ms.","firstname":"Sophia","lastname":"Moore","email":"sophia.moore@example.com","company":"Medica","home_address":"505 Oak Blvd, San Francisco, CA, 94101, USA","is_subscribed":"Yes","birth_date":"9/14/92","gender":"F","lang":"Französisch","notes":"Needs product demo","tag1":"Cold Lead","contact_method_1_type":"Email","contact_method_1_value":"sophia.moore@example.com","contact_method_2_type":"Mobile","contact_method_2_value":"555-7891"} +{"__k":"dev_rc_33363990118f424c9b18f0dcc83d2665","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","firstname":"L","lastname":"White","email":"liam.white@example.com","company":"CityGov","home_address":"606 Chestnut Ave, Austin, TX, 73301, USA","is_subscribed":"No","birth_date":"12/19/84","gender":"M","lang":"Englisch","tag1":"High Value","contact_method_1_type":"Email","contact_method_1_value":"liam.white@example.com","contact_method_2_type":"Mobile","contact_method_2_value":"555-8902"} +{"__k":"dev_rc_34aeec7c4078472cacaad531bc8e0af1","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Mrs.","firstname":"Emma","lastname":"Harris","email":"emma.harris@example.com","company":"TravelWorld","home_address":"707 Aspen St, Miami, FL, 33101, USA","is_subscribed":"Yes","birth_date":"11/2/93","gender":"F","lang":"Deutsch","notes":"Frequent traveler discounts","tag1":"VIP","contact_method_1_type":"Email","contact_method_1_value":"emma.harris@example.com","contact_method_2_type":"Mobile","contact_method_2_value":"555-9013"} +{"__k":"dev_rc_54c32104b6114087bafaacfa690fecb4","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Dr.","firstname":"Noah","lastname":"Lewis","email":"noah.lewis@example.com","company":"PharmaPlus","home_address":"808 Spruce Ln, Dallas, TX, 75201, USA","is_subscribed":"No","birth_date":"1/22/88","gender":"M","lang":"Englisch","notes":"Research collaborator","contact_method_1_type":"Email","contact_method_1_value":"noah.lewis@example.com","contact_method_2_type":"Mobile","contact_method_2_value":"555-0124"} +{"__k":"dev_rc_99f1b38061574f9aa79b6b82bfc83725","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Ms.","firstname":"Ava","lastname":"Clark","email":"ava.clark@example.com","company":"ShopEasy","home_address":"909 Redwood Dr, Seattle, WA, 98101, USA","is_subscribed":"No","birth_date":"10/15/90","gender":"F","lang":"Spanisch","tag1":"High Value","tag2":"Priority","contact_method_1_type":"Email","contact_method_1_value":"ava.clark@example.com","contact_method_2_type":"Phone","contact_method_2_value":"555-1235"} +{"__k":"dev_rc_694cdbdee1a7460eaba8709f1d60ba28","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Mr.","firstname":"William","lastname":"Walker","email":"william.walker@example.com","company":"RealEstate Inc","home_address":"1010 Pine Ave, Denver, CO, 80201, USA","is_subscribed":"Yes","birth_date":"4/19/75","gender":"M","lang":"Französisch","tag1":"High Value","contact_method_1_type":"Email","contact_method_1_value":"william.walker@example.com","contact_method_2_type":"Phone","contact_method_2_value":"555-2346"} +{"__k":"dev_rc_3c0a8fa7b6cc43e8a79815fcfa0a117f","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","firstname":"Mia","lastname":"Young","email":"mia.young@example.com","company":"FashionNow","home_address":"1111 Maple St, Boston, MA, 02101, USA","billing_address":"1111 Maple St, Boston, MA, 02101, USA","is_subscribed":"No","birth_date":"9/28/80","gender":"F","lang":"Englisch","tag1":"Referral","tag2":"Priority","contact_method_1_type":"Email","contact_method_1_value":"mia.young@example.com","contact_method_2_type":"Phone","contact_method_2_value":"555-3457"} +{"__k":"dev_rc_8e61af79aeb84418a7eb7808d0c632d1","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","firstname":"Benjamin","lastname":"Hall","email":"benjamin.hall@example.com","company":"InnovateTech","home_address":"1212 Cedar Rd, Atlanta, GA, 30301, USA","billing_address":"1212 Cedar Rd, Atlanta, GA, 30301, USA","is_subscribed":"No","birth_date":"9/28/82","gender":"M","lang":"Deutsch","contact_method_1_type":"Email","contact_method_1_value":"benjamin.hall@example.com","contact_method_2_type":"Phone","contact_method_2_value":"555-4568"} +{"__k":"dev_rc_4cda95427f474e1f9370e34fdb804d5e","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","firstname":"Charlotte","lastname":"King","email":"charlotte.king@example.com","company":"FoodPro","home_address":"1313 Birch Ln, Las Vegas, NV, 89101, USA","billing_address":"1313 Birch Ln, Las Vegas, NV, 89101, USA","is_subscribed":"Yes","birth_date":"11/2/93","gender":"F","lang":"Englisch","tag1":"Customer","tag2":"Priority","contact_method_1_type":"Email","contact_method_1_value":"charlotte.king@example.com","contact_method_2_type":"Phone","contact_method_2_value":"555-5679"} +{"__k":"dev_rc_c986ccb6802a4efa98fe44665636ad4c","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Mr.","firstname":"Lucas","lastname":"Wright","email":"lucas.wright@example.com","company":"NextGen","home_address":"1414 Oak Dr, San Jose, CA, 95101, USA","is_subscribed":"No","birth_date":"6/12/86","gender":"M","lang":"Spanisch","notes":"Technical advisor","tag1":"Client","tag2":"Priority","contact_method_1_type":"Email","contact_method_1_value":"lucas.wright@example.com","contact_method_2_type":"Phone","contact_method_2_value":"555-6789"} +{"__k":"dev_rc_ae8c4a157fbc4dc7bf45a6780e0472eb","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Mrs.","firstname":"Amelia","lastname":"Green","email":"amelia.green@example.com","company":"HomeLife","home_address":"1515 Willow St, Portland, OR, 97201, USA","is_subscribed":"No","birth_date":"9/14/92","gender":"F","lang":"Französisch","notes":"Home renovation interest","tag1":"Cold Lead","tag2":"Follow-Up","contact_method_1_type":"Email","contact_method_1_value":"amelia.green@example.com","contact_method_2_type":"Phone","contact_method_2_value":"555-7890"} +{"__k":"dev_rc_9ccba1eb954941cb8648561f1ef77dac","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Dr.","firstname":"Ethan","lastname":"Scott","email":"ethan.scott@example.com","company":"AgriTech","home_address":"1616 Chestnut Ave, Nashville, TN, 37201, USA","is_subscribed":"Yes","birth_date":"12/19/84","gender":"M","lang":"Englisch","contact_method_1_type":"Email","contact_method_1_value":"ethan.scott@example.com","contact_method_2_type":"Phone","contact_method_2_value":"555-8901","contact_method_3_type":"Mobile","contact_method_3_value":"555-8901"} +{"__k":"dev_rc_9fd2cdfa318a4d5b88859d33d40e65dc","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","pref":"Ms.","firstname":"Isabella","lastname":"Adams","email":"isabella.adams@example.com","company":"EventPro","home_address":"1717 Aspen St, Orlando, FL, 32801, USA","billing_address":"1717 Aspen St, Orlando, FL, 32801, USA","is_subscribed":"No","birth_date":"3/27/95","gender":"F","lang":"Spanisch","notes":"Event planning services","tag1":"VIP","tag2":"Feedback","contact_method_1_type":"Email","contact_method_1_value":"isabella.adams@example.com","contact_method_2_type":"Mobile","contact_method_2_value":"555-9012","contact_method_3_type":"Phone","contact_method_3_value":"555-9012"} +{"__k":"dev_rc_7a797327a5e74e96b6ebb822632d2092","__s":"dev_sh_jVnmFCKg","__n":"contacts-pCZHI4","is_subscribed":"No","birth_date":"3/27/95","gender":"F","lang":"Spanisch"} diff --git a/yarn.lock b/yarn.lock index a048b2fa..53681d5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -283,6 +283,30 @@ resolved "https://registry.yarnpkg.com/@flatfile/cross-env-config/-/cross-env-config-0.0.6.tgz#f32b7ba04dbb893eb9cb2445f54a4ab68f2a0c32" integrity sha512-/8GpBVxjzTlAmFni08ELQfIrfuwZ+yIsAsTcYOzazbIAMp1ShKNB/1RfaIzRGhOF5OTWbj37mAL/z/tu6+rvpQ== +"@isaacs/balanced-match@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" + integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== + +"@isaacs/brace-expansion@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" + integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== + dependencies: + "@isaacs/balanced-match" "^4.0.1" + +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -928,6 +952,11 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== +ansi-regex@^6.0.1: + version "6.1.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" + integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== + ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -940,6 +969,11 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + anymatch@^3.0.3: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1227,7 +1261,7 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" -cross-spawn@^7.0.3: +cross-spawn@^7.0.3, cross-spawn@^7.0.6: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -1315,6 +1349,11 @@ dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ejs@^3.1.10: version "3.1.10" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" @@ -1337,6 +1376,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.2.2: + version "9.2.2" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" + integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== + enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.2: version "5.18.2" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz#7903c5b32ffd4b2143eeb4b92472bd68effd5464" @@ -1530,6 +1574,14 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +foreground-child@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" + integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== + dependencies: + cross-spawn "^7.0.6" + signal-exit "^4.0.1" + form-data-encoder@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-4.1.0.tgz#497cedc94810bd5d53b99b5d4f6c152d5cbc9db2" @@ -1615,6 +1667,18 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== +glob@^11.0.0: + version "11.0.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.3.tgz#9d8087e6d72ddb3c4707b1d2778f80ea3eaefcd6" + integrity sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA== + dependencies: + foreground-child "^3.3.1" + jackspeak "^4.1.1" + minimatch "^10.0.3" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^2.0.0" + glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -1828,6 +1892,13 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jackspeak@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.1.1.tgz#96876030f450502047fc7e8c7fcf8ce8124e43ae" + integrity sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ== + dependencies: + "@isaacs/cliui" "^8.0.2" + jake@^10.8.5: version "10.9.2" resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" @@ -2321,6 +2392,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== +lru-cache@^11.0.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117" + integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2382,6 +2458,13 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +minimatch@^10.0.3: + version "10.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.3.tgz#cf7a0314a16c4d9ab73a7730a0e8e3c3502d47aa" + integrity sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw== + dependencies: + "@isaacs/brace-expansion" "^5.0.0" + minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2396,6 +2479,11 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" +minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -2490,6 +2578,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" + integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== + pako@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.3.tgz#cdf475e31b678565251406de9e759196a0ea7a43" @@ -2532,6 +2625,14 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== +path-scurry@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" + integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== + dependencies: + lru-cache "^11.0.0" + minipass "^7.1.2" + picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -2674,6 +2775,14 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +rimraf@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.0.1.tgz#ffb8ad8844dd60332ab15f52bc104bc3ed71ea4e" + integrity sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A== + dependencies: + glob "^11.0.0" + package-json-from-dist "^1.0.0" + safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -2775,6 +2884,11 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -2831,6 +2945,15 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -2840,6 +2963,15 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2847,6 +2979,13 @@ string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -2854,6 +2993,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -3156,6 +3302,15 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3165,6 +3320,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" From 86f8c4697cffc85160970c90bebb51114bcdbf6a Mon Sep 17 00:00:00 2001 From: Micah Buckley-Farlee Date: Wed, 9 Jul 2025 16:05:34 -0500 Subject: [PATCH 2/3] Update fernignore for v2 --- .fernignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.fernignore b/.fernignore index c41a27d4..8b52e246 100644 --- a/.fernignore +++ b/.fernignore @@ -6,6 +6,9 @@ cl-config.yml src/wrapper src/index.ts +src/v2 +tests/bun +tests/fixtures .github/workflows/ci.yml .github/workflows/cl-create.yml \ No newline at end of file From 9fd0bf0412fb26c5417442e560fee93030e99028 Mon Sep 17 00:00:00 2001 From: Micah Buckley-Farlee Date: Wed, 9 Jul 2025 16:12:49 -0500 Subject: [PATCH 3/3] Don't mess with package.json --- package.json | 36 +++++------ yarn.lock | 166 +-------------------------------------------------- 2 files changed, 19 insertions(+), 183 deletions(-) diff --git a/package.json b/package.json index c27e6520..b98b3781 100644 --- a/package.json +++ b/package.json @@ -3,41 +3,41 @@ "version": "1.18.0", "private": false, "repository": "https://github.com/FlatFilers/flatfile-node", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "./index.js", + "types": "./index.d.ts", "scripts": { "format": "prettier . --write --ignore-unknown", - "build": "rimraf dist && tsc", + "build": "tsc", + "prepack": "cp -rv dist/. .", "test": "jest" }, "dependencies": { - "@flatfile/cross-env-config": "0.0.6", + "url-join": "4.0.1", "form-data": "^4.0.0", - "form-data-encoder": "^4.0.2", "formdata-node": "^6.0.3", - "js-base64": "3.7.7", "node-fetch": "^2.7.0", - "pako": "2.0.3", "qs": "^6.13.1", "readable-stream": "^4.5.2", - "url-join": "4.0.1" + "js-base64": "3.7.7", + "form-data-encoder": "^4.0.2", + "@flatfile/cross-env-config": "0.0.6", + "@types/pako": "2.0.3", + "pako": "2.0.3" }, "devDependencies": { - "@types/jest": "^29.5.14", - "@types/node": "^18.19.70", - "@types/node-fetch": "^2.6.12", - "@types/pako": "2.0.3", + "@types/url-join": "4.0.1", "@types/qs": "^6.9.17", + "@types/node-fetch": "^2.6.12", "@types/readable-stream": "^4.0.18", - "@types/url-join": "4.0.1", + "webpack": "^5.97.1", + "ts-loader": "^9.5.1", "jest": "^29.7.0", + "@types/jest": "^29.5.14", + "ts-jest": "^29.1.1", "jest-environment-jsdom": "^29.7.0", + "@types/node": "^18.19.70", "prettier": "^3.4.2", - "rimraf": "^6.0.1", - "ts-jest": "^29.1.1", - "ts-loader": "^9.5.1", - "typescript": "~5.7.2", - "webpack": "^5.97.1" + "typescript": "~5.7.2" }, "browser": { "fs": false, diff --git a/yarn.lock b/yarn.lock index 53681d5f..a048b2fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -283,30 +283,6 @@ resolved "https://registry.yarnpkg.com/@flatfile/cross-env-config/-/cross-env-config-0.0.6.tgz#f32b7ba04dbb893eb9cb2445f54a4ab68f2a0c32" integrity sha512-/8GpBVxjzTlAmFni08ELQfIrfuwZ+yIsAsTcYOzazbIAMp1ShKNB/1RfaIzRGhOF5OTWbj37mAL/z/tu6+rvpQ== -"@isaacs/balanced-match@^4.0.1": - version "4.0.1" - resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29" - integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ== - -"@isaacs/brace-expansion@^5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3" - integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA== - dependencies: - "@isaacs/balanced-match" "^4.0.1" - -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -952,11 +928,6 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.1.0.tgz#95ec409c69619d6cb1b8b34f14b660ef28ebd654" - integrity sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA== - ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -969,11 +940,6 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - anymatch@^3.0.3: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" @@ -1261,7 +1227,7 @@ create-jest@^29.7.0: jest-util "^29.7.0" prompts "^2.0.1" -cross-spawn@^7.0.3, cross-spawn@^7.0.6: +cross-spawn@^7.0.3: version "7.0.6" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -1349,11 +1315,6 @@ dunder-proto@^1.0.1: es-errors "^1.3.0" gopd "^1.2.0" -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - ejs@^3.1.10: version "3.1.10" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" @@ -1376,11 +1337,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.2: version "5.18.2" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz#7903c5b32ffd4b2143eeb4b92472bd68effd5464" @@ -1574,14 +1530,6 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" -foreground-child@^3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f" - integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw== - dependencies: - cross-spawn "^7.0.6" - signal-exit "^4.0.1" - form-data-encoder@^4.0.2: version "4.1.0" resolved "https://registry.yarnpkg.com/form-data-encoder/-/form-data-encoder-4.1.0.tgz#497cedc94810bd5d53b99b5d4f6c152d5cbc9db2" @@ -1667,18 +1615,6 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@^11.0.0: - version "11.0.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.3.tgz#9d8087e6d72ddb3c4707b1d2778f80ea3eaefcd6" - integrity sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA== - dependencies: - foreground-child "^3.3.1" - jackspeak "^4.1.1" - minimatch "^10.0.3" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^2.0.0" - glob@^7.1.3, glob@^7.1.4: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" @@ -1892,13 +1828,6 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" -jackspeak@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.1.1.tgz#96876030f450502047fc7e8c7fcf8ce8124e43ae" - integrity sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - jake@^10.8.5: version "10.9.2" resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" @@ -2392,11 +2321,6 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== -lru-cache@^11.0.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117" - integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A== - lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -2458,13 +2382,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimatch@^10.0.3: - version "10.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.3.tgz#cf7a0314a16c4d9ab73a7730a0e8e3c3502d47aa" - integrity sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw== - dependencies: - "@isaacs/brace-expansion" "^5.0.0" - minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -2479,11 +2396,6 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" -minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -2578,11 +2490,6 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json-from-dist@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505" - integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw== - pako@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.3.tgz#cdf475e31b678565251406de9e759196a0ea7a43" @@ -2625,14 +2532,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580" - integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg== - dependencies: - lru-cache "^11.0.0" - minipass "^7.1.2" - picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -2775,14 +2674,6 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -rimraf@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-6.0.1.tgz#ffb8ad8844dd60332ab15f52bc104bc3ed71ea4e" - integrity sha512-9dkvaxAsk/xNXSJzMgFqqMCuFgt2+KsOFek3TMLfo8NCPfWpBmqwyNn5Y+NX56QUYfCtsyhF3ayiboEoUmJk/A== - dependencies: - glob "^11.0.0" - package-json-from-dist "^1.0.0" - safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" @@ -2884,11 +2775,6 @@ signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - sisteransi@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" @@ -2945,15 +2831,6 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -2963,15 +2840,6 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2979,13 +2847,6 @@ string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -2993,13 +2854,6 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -3302,15 +3156,6 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -3320,15 +3165,6 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"