From f5e1d2120deaf574c1a55b5cbd3acdeb8ba030ec Mon Sep 17 00:00:00 2001 From: Jasonzyt <66063199+Jasonzyt@users.noreply.github.com> Date: Sun, 24 Aug 2025 14:29:16 +0800 Subject: [PATCH 01/10] fix: fix csv & yaml config not passed to parser --- src/utils/content/index.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/content/index.ts b/src/utils/content/index.ts index 0a7c20197..e5f921c67 100644 --- a/src/utils/content/index.ts +++ b/src/utils/content/index.ts @@ -111,7 +111,7 @@ async function _getHighlightPlugin(key: string, options: HighlighterOptions) { export async function createParser(collection: ResolvedCollection, nuxt?: Nuxt) { const nuxtOptions = nuxt?.options as unknown as { content: ModuleOptions, mdc: MDCModuleOptions } const mdcOptions = nuxtOptions?.mdc || {} - const { pathMeta = {}, markdown = {}, transformers = [] } = nuxtOptions?.content?.build || {} + const { pathMeta = {}, markdown = {}, transformers = [], csv = {}, yaml = {} } = nuxtOptions?.content?.build || {} const rehypeHighlightPlugin = markdown.highlight !== false ? await getHighlightPluginInstance(defu(markdown.highlight as HighlighterOptions, mdcOptions.highlight, { compress: true })) @@ -149,6 +149,8 @@ export async function createParser(collection: ResolvedCollection, nuxt?: Nuxt) }, highlight: undefined, }, + csv: csv, + yaml: yaml, } return async function parse(file: ContentFile) { From a268e334c28d43d777eb0e070459cd7fb3a390ed Mon Sep 17 00:00:00 2001 From: Jasonzyt <66063199+Jasonzyt@users.noreply.github.com> Date: Sun, 24 Aug 2025 20:48:44 +0800 Subject: [PATCH 02/10] fix: fix processCollectionItems for csv files --- src/module.ts | 28 +++++++++++++++++---- src/utils/content/transformers/csv/index.ts | 2 +- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/module.ts b/src/module.ts index bdf92b237..21449c711 100644 --- a/src/module.ts +++ b/src/module.ts @@ -323,9 +323,6 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio body: content, path: fullPath, }) - if (parsedContent) { - db.insertDevelopmentCache(keyInCollection, JSON.stringify(parsedContent), checksum) - } } // Add manually provided components from the content @@ -333,8 +330,29 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio usedComponents.push(...parsedContent.__metadata.components) } - const { queries, hash } = generateCollectionInsert(collection, parsedContent) - list.push([key, queries, hash]) + // Special handling for CSV files + if (parsedContent.__metadata?.rows) { + const rows = parsedContent.__metadata?.rows as Array> + // Since csv files can contain multiple rows, we can't process it as a single ParsedContent + // As for id, priority: `id` field > first column > index + for (let i = 0; i < rows.length; i++) { + const rowid = rows[i].id || rows[i][Object.keys(rows[i])[0]] || String(i) + const rowContent = { + id: parsedContent.id + '/' + rowid, + ...rows[i], + } + db.insertDevelopmentCache(parsedContent.id + '/' + rowid, JSON.stringify(parsedContent), checksum) + const { queries, hash } = generateCollectionInsert(collection, rowContent) + list.push([key, queries, hash]) + } + } + else { + if (parsedContent) { + db.insertDevelopmentCache(keyInCollection, JSON.stringify(parsedContent), checksum) + } + const { queries, hash } = generateCollectionInsert(collection, parsedContent) + list.push([key, queries, hash]) + } } catch (e: unknown) { logger.warn(`"${keyInCollection}" is ignored because parsing is failed. Error: ${e instanceof Error ? e.message : 'Unknown error'}`) diff --git a/src/utils/content/transformers/csv/index.ts b/src/utils/content/transformers/csv/index.ts index 6bc695824..dee7f0cb4 100644 --- a/src/utils/content/transformers/csv/index.ts +++ b/src/utils/content/transformers/csv/index.ts @@ -55,7 +55,7 @@ export default defineTransformer({ return { id: file.id, - body: result, + __metadata: { rows: result }, } }, }) From 2bf313027e27e646f7cc538414d06930e913ca09 Mon Sep 17 00:00:00 2001 From: Jasonzyt <66063199+Jasonzyt@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:11:49 +0800 Subject: [PATCH 03/10] fix: fix typecheck & test --- src/module.ts | 30 ++++++++++++--------- src/utils/content/transformers/csv/index.ts | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/module.ts b/src/module.ts index 21449c711..1473f9f7b 100644 --- a/src/module.ts +++ b/src/module.ts @@ -331,19 +331,25 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio } // Special handling for CSV files - if (parsedContent.__metadata?.rows) { - const rows = parsedContent.__metadata?.rows as Array> - // Since csv files can contain multiple rows, we can't process it as a single ParsedContent - // As for id, priority: `id` field > first column > index - for (let i = 0; i < rows.length; i++) { - const rowid = rows[i].id || rows[i][Object.keys(rows[i])[0]] || String(i) - const rowContent = { - id: parsedContent.id + '/' + rowid, - ...rows[i], + if (parsedContent.extension === 'csv') { + const rows = parsedContent.body as Array> + if (rows && Array.isArray(rows)) { + // Since csv files can contain multiple rows, we can't process it as a single ParsedContent + // As for id, priority: `id` field > first column > index + for (let i = 0; i < rows.length; i++) { + if (!rows[i]) { + logger.warn(`"${keyInCollection}" row ${i} is undefined and will be ignored.`) + continue + } + const rowid = rows[i]?.id || Object.values(rows[i] || {})?.at(0) || String(i) + const rowContent = { + id: parsedContent.id + '/' + rowid, + ...rows[i], + } + db.insertDevelopmentCache(parsedContent.id + '/' + rowid, JSON.stringify(parsedContent), checksum) + const { queries, hash } = generateCollectionInsert(collection, rowContent) + list.push([key, queries, hash]) } - db.insertDevelopmentCache(parsedContent.id + '/' + rowid, JSON.stringify(parsedContent), checksum) - const { queries, hash } = generateCollectionInsert(collection, rowContent) - list.push([key, queries, hash]) } } else { diff --git a/src/utils/content/transformers/csv/index.ts b/src/utils/content/transformers/csv/index.ts index dee7f0cb4..6bc695824 100644 --- a/src/utils/content/transformers/csv/index.ts +++ b/src/utils/content/transformers/csv/index.ts @@ -55,7 +55,7 @@ export default defineTransformer({ return { id: file.id, - __metadata: { rows: result }, + body: result, } }, }) From c1cefd22a3dcfc8406f1e0ab54692e8a920fb408 Mon Sep 17 00:00:00 2001 From: Jasonzyt <66063199+Jasonzyt@users.noreply.github.com> Date: Sun, 24 Aug 2025 21:24:07 +0800 Subject: [PATCH 04/10] fix: parsedContent.body -> parsedContent.meta.body --- src/module.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/module.ts b/src/module.ts index 1473f9f7b..dad72b94c 100644 --- a/src/module.ts +++ b/src/module.ts @@ -332,7 +332,7 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio // Special handling for CSV files if (parsedContent.extension === 'csv') { - const rows = parsedContent.body as Array> + const rows = (parsedContent.meta as { path: string, body: Array> })?.body if (rows && Array.isArray(rows)) { // Since csv files can contain multiple rows, we can't process it as a single ParsedContent // As for id, priority: `id` field > first column > index @@ -346,7 +346,7 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio id: parsedContent.id + '/' + rowid, ...rows[i], } - db.insertDevelopmentCache(parsedContent.id + '/' + rowid, JSON.stringify(parsedContent), checksum) + db.insertDevelopmentCache(parsedContent.id + '/' + rowid, JSON.stringify(rowContent), checksum) const { queries, hash } = generateCollectionInsert(collection, rowContent) list.push([key, queries, hash]) } From 422fee4864fe1fe9faeb21d7c82be470719ac6d2 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 12 Sep 2025 14:23:39 +0200 Subject: [PATCH 05/10] feat: csv collection --- playground/content.config.ts | 15 ++++++ playground/content/org/people.csv | 11 ++++ playground/pages/org/data.vue | 10 ++++ playground/pages/org/people.vue | 10 ++++ src/module.ts | 34 ++---------- src/utils/content/transformers/csv/index.ts | 7 +++ src/utils/schema/index.ts | 2 +- src/utils/source.ts | 59 +++++++++++++++++++++ 8 files changed, 118 insertions(+), 30 deletions(-) create mode 100644 playground/content/org/people.csv create mode 100644 playground/pages/org/data.vue create mode 100644 playground/pages/org/people.vue diff --git a/playground/content.config.ts b/playground/content.config.ts index 396b14243..a8089436e 100644 --- a/playground/content.config.ts +++ b/playground/content.config.ts @@ -66,6 +66,21 @@ const pages = defineCollection({ }) const collections = { + people: defineCollection({ + type: 'data', + source: 'org/people.csv', + schema: z.object({ + name: z.string(), + email: z.string().email(), + }), + }), + org: defineCollection({ + type: 'data', + source: 'org/**.csv', + schema: z.object({ + body: z.array(z.any()), + }), + }), hackernews, content, data, diff --git a/playground/content/org/people.csv b/playground/content/org/people.csv new file mode 100644 index 000000000..0671ad01c --- /dev/null +++ b/playground/content/org/people.csv @@ -0,0 +1,11 @@ +name,email +John Doe,john.doe@example.com +Jane Smith,jane.smith@example.com +Bob Johnson,bob.johnson@example.com +Alice Brown,alice.brown@example.com +Charlie Wilson,charlie.wilson@example.com +Diana Lee,diana.lee@example.com +Eve Davis,eve.davis@example.com +Frank Miller,frank.miller@example.com +Grace Taylor,grace.taylor@example.com +Henry Anderson,henry.anderson@example.com diff --git a/playground/pages/org/data.vue b/playground/pages/org/data.vue new file mode 100644 index 000000000..5ecec16bd --- /dev/null +++ b/playground/pages/org/data.vue @@ -0,0 +1,10 @@ + + + diff --git a/playground/pages/org/people.vue b/playground/pages/org/people.vue new file mode 100644 index 000000000..3b456a21e --- /dev/null +++ b/playground/pages/org/people.vue @@ -0,0 +1,10 @@ + + + diff --git a/src/module.ts b/src/module.ts index a320ac572..d7c1ed73f 100644 --- a/src/module.ts +++ b/src/module.ts @@ -327,6 +327,9 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio body: content, path: fullPath, }) + if (parsedContent) { + db.insertDevelopmentCache(keyInCollection, JSON.stringify(parsedContent), checksum) + } } // Add manually provided components from the content @@ -334,35 +337,8 @@ async function processCollectionItems(nuxt: Nuxt, collections: ResolvedCollectio usedComponents.push(...parsedContent.__metadata.components) } - // Special handling for CSV files - if (parsedContent.extension === 'csv') { - const rows = (parsedContent.meta as { path: string, body: Array> })?.body - if (rows && Array.isArray(rows)) { - // Since csv files can contain multiple rows, we can't process it as a single ParsedContent - // As for id, priority: `id` field > first column > index - for (let i = 0; i < rows.length; i++) { - if (!rows[i]) { - logger.warn(`"${keyInCollection}" row ${i} is undefined and will be ignored.`) - continue - } - const rowid = rows[i]?.id || Object.values(rows[i] || {})?.at(0) || String(i) - const rowContent = { - id: parsedContent.id + '/' + rowid, - ...rows[i], - } - db.insertDevelopmentCache(parsedContent.id + '/' + rowid, JSON.stringify(rowContent), checksum) - const { queries, hash } = generateCollectionInsert(collection, rowContent) - list.push([key, queries, hash]) - } - } - } - else { - if (parsedContent) { - db.insertDevelopmentCache(keyInCollection, JSON.stringify(parsedContent), checksum) - } - const { queries, hash } = generateCollectionInsert(collection, parsedContent) - list.push([key, queries, hash]) - } + const { queries, hash } = generateCollectionInsert(collection, parsedContent) + list.push([key, queries, hash]) } catch (e: unknown) { logger.warn(`"${keyInCollection}" is ignored because parsing is failed. Error: ${e instanceof Error ? e.message : 'Unknown error'}`) diff --git a/src/utils/content/transformers/csv/index.ts b/src/utils/content/transformers/csv/index.ts index 6bc695824..5dda952ef 100644 --- a/src/utils/content/transformers/csv/index.ts +++ b/src/utils/content/transformers/csv/index.ts @@ -53,6 +53,13 @@ export default defineTransformer({ }) const { result } = await stream.process(file.body) + if (Array.isArray(result) && result.length === 1) { + return { + id: file.id, + ...result[0], + } + } + return { id: file.id, body: result, diff --git a/src/utils/schema/index.ts b/src/utils/schema/index.ts index d3fe04d2f..d8654d996 100644 --- a/src/utils/schema/index.ts +++ b/src/utils/schema/index.ts @@ -106,7 +106,7 @@ export function detectSchemaVendor(schema: ContentStandardSchemaV1) { } export function replaceComponentSchemas(property: T): T { - if ((property as Draft07DefinitionProperty).type === 'array') { + if ((property as Draft07DefinitionProperty).type === 'array' && (property as Draft07DefinitionProperty).items) { (property as Draft07DefinitionProperty).items = replaceComponentSchemas((property as Draft07DefinitionProperty).items as Draft07DefinitionProperty) as Draft07DefinitionProperty } diff --git a/src/utils/source.ts b/src/utils/source.ts index 41c4ec01b..8326d3d9a 100644 --- a/src/utils/source.ts +++ b/src/utils/source.ts @@ -1,4 +1,5 @@ import { readFile } from 'node:fs/promises' +import { createReadStream } from 'node:fs' import { join, normalize } from 'pathe' import { withLeadingSlash, withoutTrailingSlash } from 'ufo' import { glob } from 'tinyglobby' @@ -19,6 +20,12 @@ export function defineLocalSource(source: CollectionSource | ResolvedCollectionS logger.warn('Collection source should not start with `./` or `../`.') source.include = source.include.replace(/^(\.\/|\.\.\/|\/)*/, '') } + + // If source is a CSV file, define a CSV source + if (source.include.endsWith('.csv') && !source.include.includes('*')) { + return defineCSVSource(source) + } + const { fixed } = parseSourceBase(source) const resolvedSource: ResolvedCollectionSource = { _resolved: true, @@ -105,6 +112,58 @@ export function defineBitbucketSource( return resolvedSource } +export function defineCSVSource(source: CollectionSource): ResolvedCollectionSource { + const { fixed } = parseSourceBase(source) + + const resolvedSource: ResolvedCollectionSource = { + _resolved: true, + prefix: withoutTrailingSlash(withLeadingSlash(fixed)), + prepare: async ({ rootDir }) => { + resolvedSource.cwd = source.cwd + ? String(normalize(source.cwd)).replace(/^~~\//, rootDir) + : join(rootDir, 'content') + }, + getKeys: async () => { + const _keys = await glob(source.include, { cwd: resolvedSource.cwd, ignore: getExcludedSourcePaths(source), dot: true, expandDirectories: false }) + .catch((): [] => []) + const keys = _keys.map(key => key.substring(fixed.length)) + if (keys.length !== 1) { + return keys + } + + return new Promise((resolve) => { + const csvKeys: string[] = [] + let count = 0 + createReadStream(join(resolvedSource.cwd, fixed, keys[0]!)) + .on('data', function (chunk) { + for (let i = 0; i < chunk.length; i += 1) + if (chunk[i] == 10) { + csvKeys.push(`${keys[0]}#l${count}`) + count += 1 + } + }) + .on('end', () => resolve(csvKeys)) + }) + }, + getItem: async (key) => { + const [csvKey, csvIndex] = key.split('#') + const fullPath = join(resolvedSource.cwd, fixed, csvKey!) + const content = await readFile(fullPath, 'utf8') + + if (key.includes('#')) { + const lines = content.split('\n') + return lines[0] + '\n' + lines[+(csvIndex || 0)]! + } + + return content + }, + ...source, + include: source.include, + cwd: '', + } + return resolvedSource +} + export function parseSourceBase(source: CollectionSource) { const [fixPart, ...rest] = source.include.includes('*') ? source.include.split('*') : ['', source.include] return { From 5e17767b991bc1764234fabeb1f33ceb1bc58d18 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 12 Sep 2025 14:42:08 +0200 Subject: [PATCH 06/10] test: update tests --- test/unit/parseContent.csv.test.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/test/unit/parseContent.csv.test.ts b/test/unit/parseContent.csv.test.ts index 948f3f63d..b827f55aa 100644 --- a/test/unit/parseContent.csv.test.ts +++ b/test/unit/parseContent.csv.test.ts @@ -83,11 +83,20 @@ describe('Parser (.csv)', async () => { expect(parsed).toHaveProperty('id') assert(parsed.id === 'content/index.csv') - expect(parsed).toHaveProperty('body') - expect(Array.isArray(parsed.body)).toBeTruthy() - const truth = await csvToJson({ output: 'json' }).fromString(csv) + // Single line CSV files maps to a single object + if (csv.split('\n').length === 2) { + const truth = (await csvToJson({ output: 'json' }).fromString(csv))[0] + Object.keys(truth).forEach((key) => { + expect(parsed[key] || (parsed.meta as Record)[key]).toBe(truth[key]) + }) + } + else { + expect(parsed).toHaveProperty('body') + expect(Array.isArray(parsed.body)).toBeTruthy() + const truth = await csvToJson({ output: 'json' }).fromString(csv) - expect(parsed.body).toMatchObject(truth) + expect(parsed.body).toMatchObject(truth) + } }) } }) From 0f6d610bde542b3aa199614d9291a82d1d8d3014 Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Fri, 12 Sep 2025 18:21:32 +0200 Subject: [PATCH 07/10] Update source.ts --- src/utils/source.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/source.ts b/src/utils/source.ts index 8326d3d9a..d6ac04d1b 100644 --- a/src/utils/source.ts +++ b/src/utils/source.ts @@ -138,7 +138,7 @@ export function defineCSVSource(source: CollectionSource): ResolvedCollectionSou .on('data', function (chunk) { for (let i = 0; i < chunk.length; i += 1) if (chunk[i] == 10) { - csvKeys.push(`${keys[0]}#l${count}`) + csvKeys.push(`${keys[0]}#${count}`) count += 1 } }) From d529a2cdd0121b5da023a57b04f6fa567b89551a Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Mon, 15 Sep 2025 11:57:18 +0200 Subject: [PATCH 08/10] fix: ignore header row --- src/utils/source.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/source.ts b/src/utils/source.ts index d6ac04d1b..801f27c07 100644 --- a/src/utils/source.ts +++ b/src/utils/source.ts @@ -138,7 +138,9 @@ export function defineCSVSource(source: CollectionSource): ResolvedCollectionSou .on('data', function (chunk) { for (let i = 0; i < chunk.length; i += 1) if (chunk[i] == 10) { - csvKeys.push(`${keys[0]}#${count}`) + if (count > 0) { // count === 0 is CSV header row and should not be included + csvKeys.push(`${keys[0]}#${count}`) + } count += 1 } }) From f790439f160260013d3ca2fa1732d0d8194baedd Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Mon, 15 Sep 2025 11:57:28 +0200 Subject: [PATCH 09/10] docs: update csv docs --- docs/content/docs/3.files/4.csv.md | 102 ++++++++++++++++++++++------- 1 file changed, 80 insertions(+), 22 deletions(-) diff --git a/docs/content/docs/3.files/4.csv.md b/docs/content/docs/3.files/4.csv.md index 7b9042dea..d2ba6ea44 100644 --- a/docs/content/docs/3.files/4.csv.md +++ b/docs/content/docs/3.files/4.csv.md @@ -10,13 +10,15 @@ import { defineCollection, defineContentConfig, z } from '@nuxt/content' export default defineContentConfig({ collections: { - authors: defineCollection({ + charts: defineCollection({ type: 'data', - source: 'authors/**.csv', + source: 'charts/**.csv', schema: z.object({ - name: z.string(), - email: z.string(), - avatar: z.string() + // Body is important in CSV files, without body field you cannot access to data array + body: z.array(z.object({ + label: z.string(), + value: z.number() + })) }) }) } @@ -29,17 +31,18 @@ export default defineContentConfig({ Create author files in `content/authors/` directory. ::code-group -```csv [users.csv] -id,name,email -1,John Doe,john@example.com -2,Jane Smith,jane@example.com -3,Alice Johnson,alice@example.com +```csv [content/charts/chart1.csv] +label,value +A,100 +B,200 +C,300 ``` -```csv [team.csv] -name,role,avatar -John Doe,Developer,https://avatars.githubusercontent.com/u/1?v=4 -Jane Smith,Designer,https://avatars.githubusercontent.com/u/2?v=4 +```csv [content/charts/chart2.csv] +label,value +Foo,123 +Bar,456 +Baz,789 ``` :: @@ -53,25 +56,25 @@ Now we can query authors: ```vue @@ -139,4 +142,59 @@ id;name;email ::note The CSV parser can be disabled by setting `csv: false` in the configuration if you don't need CSV support. -:: +:: + +## Single file source + +When you point a collection to a single CSV file (instead of a glob), Nuxt Content treats each data row as a separate item in the collection. + +- **Define the collection**: set `source` to the path of a single `.csv` file. +- **Item generation**: each data row becomes an item with the row’s fields at the top level (no `body` array). +- **IDs**: item IDs are suffixed with `#`, where `#1` is the first data row after the header. + +```ts [content.config.ts] +import { defineCollection, defineContentConfig } from '@nuxt/content' +import { z } from 'zod' + +export default defineContentConfig({ + collections: { + people: defineCollection({ + type: 'data', + source: 'org/people.csv', + schema: z.object({ + name: z.string(), + email: z.string().email() + }) + }) + } +}) +``` + +```csv [content/org/people.csv] +name,email +Alice,alice@example.com +Bob,bob@example.com +``` + +Each row produces its own item. For example, the first data row will have an ID ending with `#1` and the second with `#2`. You can query by any column: + +```ts +const { data: alice } = await useAsyncData('alice', () => + queryCollection('people') + .where('email', '=', 'alice@example.com') + .first() +) + +const { data: allPeople } = await useAsyncData('all-people', () => + queryCollection('people') + .order('name', 'ASC') + .all() +) +``` + +::note +- The header row is required and is not turned into an item. +- With a single-file source, items contain row fields at the top level (no `body`). +- If you prefer treating each CSV file as a single item containing all rows in `body`, use a glob source like `org/**.csv` instead of a single file. +::: + From a21aa8950c65de80ef6db935dadfc5a7f31b51ee Mon Sep 17 00:00:00 2001 From: Farnabaz Date: Thu, 18 Sep 2025 11:34:21 +0200 Subject: [PATCH 10/10] fix: csv parser for indexed CSVs --- src/utils/content/transformers/csv/index.ts | 9 ++++----- test/unit/parseContent.csv.test.ts | 17 ++++------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/utils/content/transformers/csv/index.ts b/src/utils/content/transformers/csv/index.ts index 5dda952ef..2815fdfc3 100644 --- a/src/utils/content/transformers/csv/index.ts +++ b/src/utils/content/transformers/csv/index.ts @@ -53,13 +53,12 @@ export default defineTransformer({ }) const { result } = await stream.process(file.body) - if (Array.isArray(result) && result.length === 1) { - return { - id: file.id, - ...result[0], - } + // If the file id includes a row index, parse the file as a single object + if (file.id.includes('#')) { + return { id: file.id, ...result[0] } } + // Otherwise, parse the file as an array return { id: file.id, body: result, diff --git a/test/unit/parseContent.csv.test.ts b/test/unit/parseContent.csv.test.ts index b827f55aa..948f3f63d 100644 --- a/test/unit/parseContent.csv.test.ts +++ b/test/unit/parseContent.csv.test.ts @@ -83,20 +83,11 @@ describe('Parser (.csv)', async () => { expect(parsed).toHaveProperty('id') assert(parsed.id === 'content/index.csv') - // Single line CSV files maps to a single object - if (csv.split('\n').length === 2) { - const truth = (await csvToJson({ output: 'json' }).fromString(csv))[0] - Object.keys(truth).forEach((key) => { - expect(parsed[key] || (parsed.meta as Record)[key]).toBe(truth[key]) - }) - } - else { - expect(parsed).toHaveProperty('body') - expect(Array.isArray(parsed.body)).toBeTruthy() - const truth = await csvToJson({ output: 'json' }).fromString(csv) + expect(parsed).toHaveProperty('body') + expect(Array.isArray(parsed.body)).toBeTruthy() + const truth = await csvToJson({ output: 'json' }).fromString(csv) - expect(parsed.body).toMatchObject(truth) - } + expect(parsed.body).toMatchObject(truth) }) } })