Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/app/src/composables/useContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
import type { useGit } from './useGit'
import type { useDraftMedias } from './useDraftMedias'
import { useRoute, useRouter } from 'vue-router'
import { findDescendantsFileItemsFromFsPath, generateIdFromFsPath } from '../utils/tree'
import { findDescendantsFileItemsFromFsPath } from '../utils/tree'
import { generateIdFromFsPath } from '../utils/collection'
import { joinURL } from 'ufo'
import { upperFirst } from 'scule'
import { generateStemFromFsPath } from '../utils/media'
Expand Down Expand Up @@ -106,7 +107,7 @@
const { fsPath } = params
const gitkeepFsPath = joinURL(fsPath, '.gitkeep')
const gitKeepMedia: MediaItem = {
id: generateIdFromFsPath(gitkeepFsPath, TreeRootId.Media),

Check failure on line 110 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'import("/home/runner/work/studio/studio/src/app/src/types/tree").TreeRootId' is not assignable to parameter of type 'CollectionInfo'.

Check failure on line 110 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'import("/home/runner/work/studio/studio/src/app/src/types/tree").TreeRootId' is not assignable to parameter of type 'CollectionInfo'.
fsPath: gitkeepFsPath,
stem: generateStemFromFsPath(gitkeepFsPath),
extension: '',
Expand All @@ -128,7 +129,7 @@
[StudioItemActionId.UploadMedia]: async ({ parentFsPath, files }: UploadMediaParams) => {
// Remove .gitkeep draft in folder if exists
const gitkeepFsPath = parentFsPath === '/' ? '.gitkeep' : joinURL(parentFsPath, '.gitkeep')
const gitkeepId = generateIdFromFsPath(gitkeepFsPath, TreeRootId.Media)

Check failure on line 132 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'import("/home/runner/work/studio/studio/src/app/src/types/tree").TreeRootId' is not assignable to parameter of type 'CollectionInfo'.

Check failure on line 132 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'import("/home/runner/work/studio/studio/src/app/src/types/tree").TreeRootId' is not assignable to parameter of type 'CollectionInfo'.
const gitkeepDraft = await activeTree.value.draft.get(gitkeepId)
if (gitkeepDraft) {
await activeTree.value.draft.remove([gitkeepId], { rerender: false })
Expand All @@ -141,7 +142,7 @@
[StudioItemActionId.RevertItem]: async (item: TreeItem) => {
// Get collections from document item or use default media collection
for (const collection of item.collections) {
const id = generateIdFromFsPath(item.fsPath, collection)

Check failure on line 145 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.

Check failure on line 145 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.
await activeTree.value.draft.revert(id)
}
},
Expand All @@ -150,7 +151,7 @@

// Revert file
if (item.type === 'file') {
const id = generateIdFromFsPath(item.fsPath, item.collections[0])

Check failure on line 154 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.

Check failure on line 154 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.
await activeTree.value.draft.rename([{ id, newFsPath }])
return
}
Expand All @@ -160,7 +161,7 @@
if (descendants.length > 0) {
const itemsToRename = descendants.map((descendant) => {
return {
id: generateIdFromFsPath(descendant.fsPath, descendant.collections[0]),

Check failure on line 164 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.

Check failure on line 164 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.
newFsPath: descendant.fsPath.replace(item.fsPath, newFsPath),
}
})
Expand All @@ -171,7 +172,7 @@
[StudioItemActionId.DeleteItem]: async (item: TreeItem) => {
// Delete file
if (item.type === 'file') {
const id = generateIdFromFsPath(item.fsPath, item.collections![0])

Check failure on line 175 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.

Check failure on line 175 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.
await activeTree.value.draft.remove([id])
return
}
Expand All @@ -180,7 +181,7 @@
const descendants = findDescendantsFileItemsFromFsPath(activeTree.value.root.value, item.fsPath)
if (descendants.length > 0) {
const ids: string[] = descendants.map((descendant) => {
return generateIdFromFsPath(descendant.fsPath, descendant.collections![0])

Check failure on line 184 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.

Check failure on line 184 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.
})
await activeTree.value.draft.remove(ids)
}
Expand All @@ -188,7 +189,7 @@
[StudioItemActionId.DuplicateItem]: async (item: TreeItem) => {
// Duplicate file
if (item.type === 'file') {
const id = generateIdFromFsPath(item.fsPath, item.collections![0])

Check failure on line 192 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.

Check failure on line 192 in src/app/src/composables/useContext.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.
const draftItem = await activeTree.value.draft.duplicate(id)
await activeTree.value.selectItemByFsPath(draftItem!.id)
return
Expand Down
3 changes: 2 additions & 1 deletion src/app/src/composables/useTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import { ref, computed } from 'vue'
import type { useDraftDocuments } from './useDraftDocuments'
import type { useDraftMedias } from './useDraftMedias'
import { buildTree, findItemFromFsPath, findItemFromRoute, findParentFromFsPath, generateIdFromFsPath } from '../utils/tree'
import { buildTree, findItemFromFsPath, findItemFromRoute, findParentFromFsPath } from '../utils/tree'
import { generateIdFromFsPath } from '../utils/collection'
import type { RouteLocationNormalized } from 'vue-router'
import { useHooks } from './useHooks'
import { useStudioState } from './useStudioState'
Expand Down Expand Up @@ -53,7 +54,7 @@
setLocation(type, currentItem.value.fsPath)

if (item?.type === 'file') {
await draft.selectById(generateIdFromFsPath(item.fsPath, item.collections![0]))

Check failure on line 57 in src/app/src/composables/useTree.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.

Check failure on line 57 in src/app/src/composables/useTree.ts

View workflow job for this annotation

GitHub Actions / build (ubuntu-latest, 22)

Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.

if (
!preferences.value.syncEditorAndRoute
Expand Down
1 change: 1 addition & 0 deletions src/app/src/shared.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { generateContentFromDocument, generateDocumentFromContent, removeReservedKeysFromDocument } from './utils/content'
export { generateIdFromFsPath, parseSourceBase } from './utils/collection'
18 changes: 18 additions & 0 deletions src/app/src/utils/collection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { CollectionSource, CollectionInfo } from '@nuxt/content'
import { join } from 'pathe'

export function parseSourceBase(source: CollectionSource) {
const [fixPart, ...rest] = source.include.includes('*') ? source.include.split('*') : ['', source.include]
return {
fixed: fixPart || '',
dynamic: '*' + rest.join('*'),
}
}

export function generateIdFromFsPath(path: string, collectionInfo: CollectionInfo) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function signature mismatch: generateIdFromFsPath now expects a CollectionInfo object, but all call sites throughout the codebase are passing collection name strings instead, which will cause TypeScript compilation errors.

View Details
📝 Patch Details
diff --git a/src/app/src/utils/collection.ts b/src/app/src/utils/collection.ts
index 2974184..3b377c9 100644
--- a/src/app/src/utils/collection.ts
+++ b/src/app/src/utils/collection.ts
@@ -9,7 +9,37 @@ export function parseSourceBase(source: CollectionSource) {
   }
 }
 
-export function generateIdFromFsPath(path: string, collectionInfo: CollectionInfo) {
+// Helper function to create basic CollectionInfo for standard collections
+function createStandardCollectionInfo(name: string): CollectionInfo {
+  return {
+    name,
+    pascalName: name.charAt(0).toUpperCase() + name.slice(1),
+    tableName: `_content_${name}`,
+    source: [{
+      _resolved: true,
+      prefix: '/',
+      cwd: '',
+      include: '**',
+    }],
+    type: 'page' as const,
+    fields: {},
+    schema: {
+      $schema: 'http://json-schema.org/draft-07/schema#',
+      $ref: `#/definitions/${name}`,
+      definitions: {},
+    },
+    tableDefinition: '',
+  }
+}
+
+// Function overloads for backward compatibility
+export function generateIdFromFsPath(path: string, collection: CollectionInfo): string
+export function generateIdFromFsPath(path: string, collection: string): string
+export function generateIdFromFsPath(path: string, collection: CollectionInfo | string): string {
+  const collectionInfo = typeof collection === 'string' 
+    ? createStandardCollectionInfo(collection)
+    : collection
+    
   const { fixed } = parseSourceBase(collectionInfo.source[0]!)
 
   const pathWithoutFixed = path.substring(fixed.length)

Analysis

Function signature mismatch causes TypeScript compilation errors in generateIdFromFsPath

What fails: generateIdFromFsPath function expects CollectionInfo object but call sites pass collection name strings, causing TypeScript errors in useContext.ts (8 calls) and useTree.ts (1 call)

How to reproduce:

pnpm exec vue-tsc -p src/app/tsconfig.app.json --noEmit

Result: TypeScript compilation errors:

src/app/src/composables/useContext.ts(110,49): error TS2345: Argument of type 'TreeRootId' is not assignable to parameter of type 'CollectionInfo'.
src/app/src/composables/useContext.ts(132,61): error TS2345: Argument of type 'TreeRootId' is not assignable to parameter of type 'CollectionInfo'. 
src/app/src/composables/useContext.ts(145,54): error TS2345: Argument of type 'string' is not assignable to parameter of type 'CollectionInfo'.

Expected: TypeScript compilation should pass without errors as the function should accept collection name strings for backward compatibility with app-side code that uses TreeRootId.Media, TreeRootId.Content, and item.collections[0] string values.

const { fixed } = parseSourceBase(collectionInfo.source[0]!)

const pathWithoutFixed = path.substring(fixed.length)

return join(collectionInfo.name, collectionInfo.source[0]?.prefix || '', pathWithoutFixed)
}
5 changes: 0 additions & 5 deletions src/app/src/utils/tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import type { BaseItem } from '../types/item'
import { isEqual } from './database'
import { studioFlags } from '../composables/useStudio'
import { getFileExtension, parseName } from './file'
import { joinURL } from 'ufo'

export const COLOR_STATUS_MAP: { [key in TreeStatus]?: string } = {
[TreeStatus.Created]: 'green',
Expand Down Expand Up @@ -173,10 +172,6 @@ TreeItem[] {
return tree
}

export function generateIdFromFsPath(fsPath: string, collectionName: string): string {
return joinURL(collectionName, fsPath)
}

export function getTreeStatus(modified?: BaseItem, original?: BaseItem): TreeStatus {
if (studioFlags.dev) {
return TreeStatus.Opened
Expand Down
72 changes: 72 additions & 0 deletions src/app/test/mocks/collection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { CollectionInfo } from '@nuxt/content'

export const collections: Record<string, CollectionInfo> = {
landing: {
name: 'landing',
pascalName: 'Landing',
tableName: '_content_landing',
source: [
{
_resolved: true,
prefix: '/',
cwd: '/Users/larbish/Documents/nuxt/modules/studio/playground/content',
include: 'index.md',
},
],
type: 'page',
fields: {
id: 'string',
title: 'string',
body: 'json',
description: 'string',
extension: 'string',
meta: 'json',
navigation: 'json',
path: 'string',
seo: 'json',
stem: 'string',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$ref: '#/definitions/docs',
definitions: {},
},
tableDefinition: '',
},
docs: {
name: 'docs',
pascalName: 'Docs',
tableName: '_content_docs',
source: [
{
_resolved: true,
prefix: '/',
cwd: '/Users/larbish/Documents/nuxt/modules/studio/playground/content',
include: '**',
exclude: [
'index.md',
],
},
],
type: 'page',
fields: {
id: 'string',
title: 'string',
body: 'json',
description: 'string',
extension: 'string',
links: 'json',
meta: 'json',
navigation: 'json',
path: 'string',
seo: 'json',
stem: 'string',
},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$ref: '#/definitions/__SCHEMA__',
definitions: {},
},
tableDefinition: 'CREATE TABLE IF NOT EXISTS',
},
}
81 changes: 81 additions & 0 deletions src/app/test/unit/utils/collection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { describe, it, expect } from 'vitest'
import { generateIdFromFsPath } from '../../../src/utils/collection'
import type { CollectionInfo } from '@nuxt/content'
import { collections } from '../../mocks/collection'

describe('generateIdFromFsPath', () => {
it('should generate id for single file with no prefix', () => {
const path = 'index.md'
const result = generateIdFromFsPath(path, collections.landing!)
expect(result).toBe('landing/index.md')
})

it('should generate id for nested file with global pattern', () => {
const path = '1.getting-started/2.introduction.md'
const result = generateIdFromFsPath(path, collections.docs!)
expect(result).toBe('docs/1.getting-started/2.introduction.md')
})

it('should handle deeply nested paths', () => {
const path = '2.essentials/1.nested/3.components.md'
const result = generateIdFromFsPath(path, collections.docs!)
expect(result).toBe('docs/2.essentials/1.nested/3.components.md')
})

it('should handle collection with custom prefix', () => {
const customCollection: CollectionInfo = {
name: 'docs_en',
pascalName: 'DocsEn',
tableName: '_content_docs_en',
source: [
{
_resolved: true,
prefix: '/en',
cwd: '',
include: 'en/**/*',
exclude: ['en/index.md'],
},
],
type: 'page',
fields: {},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$ref: '#/definitions/docs_en',
definitions: {},
},
tableDefinition: '',
}

const path = 'en/1.getting-started/2.introduction.md'
const result = generateIdFromFsPath(path, customCollection)
expect(result).toBe('docs_en/en/1.getting-started/2.introduction.md')
})

it('should handle empty prefix correctly', () => {
const customCollection: CollectionInfo = {
name: 'pages',
pascalName: 'Pages',
tableName: '_content_pages',
source: [
{
_resolved: true,
prefix: '',
cwd: '',
include: 'content/**/*.md',
},
],
type: 'page',
fields: {},
schema: {
$schema: 'http://json-schema.org/draft-07/schema#',
$ref: '#/definitions/pages',
definitions: {},
},
tableDefinition: '',
}

const path = 'content/about.md'
const result = generateIdFromFsPath(path, customCollection)
expect(result).toBe('pages/about.md')
})
})
3 changes: 2 additions & 1 deletion src/app/test/unit/utils/tree.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest'
import { buildTree, findParentFromFsPath, findItemFromRoute, findItemFromFsPath, findDescendantsFileItemsFromFsPath, getTreeStatus, generateIdFromFsPath } from '../../../src/utils/tree'
import { buildTree, findParentFromFsPath, findItemFromRoute, findItemFromFsPath, findDescendantsFileItemsFromFsPath, getTreeStatus } from '../../../src/utils/tree'
import { tree } from '../../../test/mocks/tree'
import type { TreeItem } from '../../../src/types/tree'
import { dbItemsList, languagePrefixedDbItemsList, nestedDbItemsList } from '../../../test/mocks/database'
Expand All @@ -9,6 +9,7 @@ import { DraftStatus, TreeStatus, TreeRootId } from '../../../src/types'
import type { RouteLocationNormalized } from 'vue-router'
import type { DatabaseItem } from '../../../src/types/database'
import { joinURL, withLeadingSlash } from 'ufo'
import { generateIdFromFsPath } from '../../../src/utils/collection'

describe('buildTree of documents with one level of depth', () => {
// Result based on dbItemsList mock
Expand Down
4 changes: 2 additions & 2 deletions src/module/src/runtime/host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { ref } from 'vue'
import { ensure } from './utils/ensure'
import type { CollectionItemBase, CollectionSource, DatabaseAdapter } from '@nuxt/content'
import type { ContentDatabaseAdapter } from '../types/content'
import { getCollectionByFilePath, generateIdFromFsPath, createCollectionDocument, generateRecordDeletion, generateRecordInsert, getCollectionInfo, normalizeDocument } from './utils/collection'
import { getCollectionByFilePath, createCollectionDocument, generateRecordDeletion, generateRecordInsert, getCollectionInfo, normalizeDocument } from './utils/collection'
import { kebabCase } from 'scule'
import type { StudioHost, StudioUser, DatabaseItem, MediaItem, Repository } from 'nuxt-studio/app'
import type { RouteLocationNormalized, Router } from 'vue-router'
import { generateDocumentFromContent } from 'nuxt-studio/app/utils'
import { generateDocumentFromContent, generateIdFromFsPath } from 'nuxt-studio/app/utils'
// @ts-expect-error queryCollection is not defined in .nuxt/imports.d.ts
import { clearError, getAppManifest, queryCollection, queryCollectionItemSurroundings, queryCollectionNavigation, queryCollectionSearchSections } from '#imports'
import { collections } from '#content/preview'
Expand Down
51 changes: 29 additions & 22 deletions src/module/src/runtime/utils/collection.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { CollectionInfo, CollectionSource, Draft07, CollectionItemBase, PageCollectionItemBase, ResolvedCollectionSource, Draft07DefinitionProperty } from '@nuxt/content'
import type { CollectionInfo, Draft07, CollectionItemBase, PageCollectionItemBase, ResolvedCollectionSource, Draft07DefinitionProperty } from '@nuxt/content'
import { hash } from 'ohash'
import { pathMetaTransform } from './path-meta'
import { minimatch } from 'minimatch'
import { join, dirname, parse } from 'pathe'
import type { DatabaseItem } from 'nuxt-studio/app'
import { withoutLeadingSlash } from 'ufo'
import { parseSourceBase } from 'nuxt-studio/app/utils'
import { withLeadingSlash, withoutLeadingSlash } from 'ufo'

export const getCollectionByFilePath = (path: string, collections: Record<string, CollectionInfo>): CollectionInfo | undefined => {
let matchedSource: ResolvedCollectionSource | undefined
Expand Down Expand Up @@ -34,15 +35,6 @@ export function generateStemFromFsPath(path: string) {
return withoutLeadingSlash(join(dirname(path), parse(path).name))
}

// TODO handle several sources case
export function generateIdFromFsPath(path: string, collectionInfo: CollectionInfo) {
const { fixed } = parseSourceBase(collectionInfo.source[0]!)

const pathWithoutFixed = path.substring(fixed.length)

return join(collectionInfo.name, collectionInfo.source[0]?.prefix || '', pathWithoutFixed)
}

export function getOrderedSchemaKeys(schema: Draft07) {
const shape = Object.values(schema.definitions)[0]?.properties || {}
const keys = new Set([
Expand All @@ -67,7 +59,24 @@ export function getCollectionSource(id: string, collection: CollectionInfo) {
const path = rest.join('/')

const matchedSource = collection.source.find((source) => {
const include = minimatch(path, source.include, { dot: true })
// Id is built like this: {collection.name}/{source.prefix}/{name}
// In some cases the prefix is different from the fsPath (source.include fixed part)
// In this case we need to remove the prefix from the path and add the fixed part of the source.include to get the fsPath

let fsPath = path

// First we need to ensure the path is starting with the source prefix in order to be sure the collection is the correct one
if (source.prefix && withLeadingSlash(path).startsWith(source.prefix)) {
const [fixPart] = source.include.includes('*') ? source.include.split('*') : ['', source.include]
const fixed = (fixPart || '').replace(/^\//, '')

const prefix = (source.prefix || '').replace(/^\//, '')
const pathWithoutPrefix = path.replace(prefix || '', '')

fsPath = join(fixed, pathWithoutPrefix)
}

const include = minimatch(fsPath, source.include, { dot: true })
const exclude = source.exclude?.some(exclude => minimatch(path, exclude))

return include && !exclude
Expand All @@ -81,9 +90,15 @@ export function generateFsPathFromId(id: string, source: CollectionInfo['source'
const path = rest.join('/')

const { fixed } = parseSourceBase(source)
const normalizedFixed = fixed.replace(/\/$/, '')

// If path already starts with the fixed part, return as is
if (normalizedFixed && path.startsWith(normalizedFixed)) {
return path
}

const pathWithoutFixed = path.substring(fixed.length)
return join(fixed, pathWithoutFixed)
// Otherwise, join fixed part with path
return join(fixed, path)
}

export function getCollectionInfo(id: string, collections: Record<string, CollectionInfo>) {
Expand All @@ -99,14 +114,6 @@ export function getCollectionInfo(id: string, collections: Record<string, Collec
}
}

export function parseSourceBase(source: CollectionSource) {
const [fixPart, ...rest] = source.include.includes('*') ? source.include.split('*') : ['', source.include]
return {
fixed: fixPart || '',
dynamic: '*' + rest.join('*'),
}
}

export function createCollectionDocument(collection: CollectionInfo, id: string, document: CollectionItemBase) {
const parsedContent = [
pathMetaTransform,
Expand Down
Loading
Loading