Skip to content

Commit

Permalink
feat: move interfaces to constants
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarosabu committed Dec 17, 2024
1 parent 5081d23 commit 43f889a
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 60 deletions.
173 changes: 115 additions & 58 deletions src/commands/components/actions.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,11 @@
import { ofetch } from 'ofetch'
import { handleAPIError, handleFileSystemError, slugify } from '../../utils'
import { regionsDomain } from '../../constants'
import { join } from 'node:path'
import { join, parse } from 'node:path'
import { resolvePath, saveToFile } from '../../utils/filesystem'
import type { PullComponentsOptions } from './constants'

export interface SpaceComponent {
name: string
display_name: string
created_at: string
updated_at: string
id: number
schema: Record<string, unknown>
image?: string
preview_field?: string
is_root?: boolean
is_nestable?: boolean
preview_tmpl?: string
all_presets?: Record<string, unknown>
preset_id?: number
real_name?: string
component_group_uuid?: string
color: null
internal_tags_list: string[]
interntal_tags_ids: number[]
content_type_asset_preview?: string
}

export interface SpaceComponentGroup {
name: string
id: number
uuid: string
parent_id: number
parent_uuid: string
}

export interface ComponentsSaveOptions {
path?: string
filename?: string
separateFiles?: boolean
suffix?: string
}
import type { PullComponentsOptions, SpaceComponent, SpaceComponentGroup, SpaceComponentPreset, SpaceData } from './constants'
import { readdir, readFile } from 'node:fs/promises'

export interface SpaceComponentPreset {
id: number
name: string
preset: Record<string, unknown>
component_id: number
space_id: number
created_at: string
updated_at: string
image: string
color: string
icon: string
description: string
}

export interface SpaceData {
components: SpaceComponent[]
groups: SpaceComponentGroup[]
presets: SpaceComponentPreset[]
}
/**
* Resolves the nested folder structure based on component group hierarchy.
* @param groupUuid - The UUID of the component group.
Expand Down Expand Up @@ -167,3 +112,115 @@ export const saveComponentsToFiles = async (
handleFileSystemError('write', error as Error)
}
}

export const readComponentsFiles = async (componentsPath: string, options: { filter?: string, separateFiles?: boolean }): Promise<SpaceData> => {
const { filter, separateFiles } = options
const resolvedPath = resolvePath(componentsPath, 'components')
const regex = filter ? new RegExp(filter) : null

const spaceData: SpaceData = {
components: [],
groups: [],
presets: [],
}

try {
if (!separateFiles) {
// Read from consolidated files
const files = await readdir(resolvedPath, { recursive: !separateFiles })

// Add regex patterns to match file structures
const componentsPattern = /^components\..+\.json$/
const groupsPattern = /^groups\..+\.json$/
const presetsPattern = /^presets\..+\.json$/

for (const file of files) {
if (!file.endsWith('.json') || !componentsPattern.test(file) && !groupsPattern.test(file) && !presetsPattern.test(file)) { continue }

Check failure on line 138 in src/commands/components/actions.ts

View workflow job for this annotation

GitHub Actions / Lint (20)

Unexpected mix of '||' and '&&'. Use parentheses to clarify the intended order of operations
const { name } = parse(file)

console.log('File:', file)

try {
const content = await readFile(join(resolvedPath, file), 'utf-8')
const data = JSON.parse(content)

if (componentsPattern.test(file)) {
spaceData.components = regex
? data.filter((c: SpaceComponent) => regex.test(c.name))
: data
}
else if (groupsPattern.test(file)) {
spaceData.groups = data
}
else if (presetsPattern.test(file)) {
spaceData.presets = data
}
}
catch (error) {
// Ignore file not found errors
if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
throw error
}
}
}
return spaceData
}

// Read from separate files
const files = await readdir(resolvedPath, { recursive: true })

// First, build groups from directory structure
const groupPaths = files
.filter(file => file.includes('/'))
.map(file => parse(file).dir)
.filter((dir, index, self) => self.indexOf(dir) === index)

spaceData.groups = groupPaths.map((path, index) => ({
name: path.split('/').pop() || '',
id: index + 1,
uuid: `group-${index + 1}`,
parent_id: 0,
parent_uuid: '',
}))

// Then process files
for (const file of files) {
if (!file.endsWith('.json')) { continue }

// Skip consolidated files in separate files mode
if (/(?:components|groups|presets)\..+\.json$/.test(file)) { continue }

const { dir, name } = parse(file)
const isPreset = name.includes('.presets.')
const baseName = name.split('.')[0]

// Skip if filter is set and doesn't match the base component name
if (regex && !regex.test(baseName)) { continue }

const content = await readFile(join(resolvedPath, file), 'utf-8')
const data = JSON.parse(content)

if (isPreset) {
spaceData.presets.push(...data)
}
else {
// Regular component file
const component = Array.isArray(data) ? data[0] : data
if (dir) {
// If component is in a directory, find the corresponding group
const group = spaceData.groups.find(g => g.name === dir.split('/').pop())
if (group) {
component.component_group_uuid = group.uuid
}
}
spaceData.components.push(component)
}
}

return spaceData
}
catch (error) {
handleFileSystemError('read', error as Error)
return spaceData
}
}
80 changes: 80 additions & 0 deletions src/commands/components/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,61 @@
import type { CommandOptions } from '../../types'

export interface SpaceComponent {
name: string
display_name: string
created_at: string
updated_at: string
id: number
schema: Record<string, unknown>
image?: string
preview_field?: string
is_root?: boolean
is_nestable?: boolean
preview_tmpl?: string
all_presets?: Record<string, unknown>
preset_id?: number
real_name?: string
component_group_uuid?: string
color: null
internal_tags_list: string[]
interntal_tags_ids: number[]
content_type_asset_preview?: string
}

export interface SpaceComponentGroup {
name: string
id: number
uuid: string
parent_id: number
parent_uuid: string
}

export interface ComponentsSaveOptions {
path?: string
filename?: string
separateFiles?: boolean
suffix?: string
}

export interface SpaceComponentPreset {
id: number
name: string
preset: Record<string, unknown>
component_id: number
space_id: number
created_at: string
updated_at: string
image: string
color: string
icon: string
description: string
}

export interface SpaceData {
components: SpaceComponent[]
groups: SpaceComponentGroup[]
presets: SpaceComponentPreset[]
}
/**
* Interface representing the options for the `pull-components` command.
*/
Expand Down Expand Up @@ -33,3 +89,27 @@ export interface PullComponentsOptions extends CommandOptions {
*/
separateFiles?: boolean
}

export interface PushComponentsOptions extends CommandOptions {
/**
* The path to read the components file to.
* Defaults to `.storyblok/components`.
* @default `.storyblok/components`
*/
path?: string
/**
* The space ID.
* @required true
*/
space: string
/**
* The regex filter to apply to the components before pushing.
* @default `.*`
*/
filter?: string
/**
* Indicates whether to read each component to a separate file.
* @default false
*/
separateFiles?: boolean
}
50 changes: 48 additions & 2 deletions src/commands/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { colorPalette, commands } from '../../constants'
import { session } from '../../session'
import { getProgram } from '../../program'
import { CommandError, handleError, konsola } from '../../utils'
import { fetchComponentGroups, fetchComponentPresets, fetchComponents, saveComponentPresetsToFiles, saveComponentsToFiles } from './actions'
import type { PullComponentsOptions } from './constants'
import { fetchComponentGroups, fetchComponentPresets, fetchComponents, readComponentsFiles, saveComponentsToFiles } from './actions'
import type { PullComponentsOptions, PushComponentsOptions } from './constants'

const program = getProgram() // Get the shared singleton instance

Expand Down Expand Up @@ -73,3 +73,49 @@ componentsCommand
handleError(error as Error, verbose)
}
})

componentsCommand
.command('push')
.description(`Push your space's components schema as json`)
.option('--fi, --filter <filter>', 'Regex filter to apply to the components before pushing')
.option('--sf, --separate-files', 'Read from separate files instead of consolidated files')
.action(async (options: PushComponentsOptions) => {
konsola.title(` ${commands.COMPONENTS} `, colorPalette.COMPONENTS, 'Pushing components...')
// Global options
const verbose = program.opts().verbose
const { space, path } = componentsCommand.opts()
const { filter } = options

const { state, initializeSession } = session()
await initializeSession()

if (!state.isLoggedIn || !state.password || !state.region) {
handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`), verbose)
return
}
if (!space) {
handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`), verbose)
return
}

try {
const spaceData = await readComponentsFiles(path, {
filter,
separateFiles: options.separateFiles,
})

console.log('Components:', spaceData.components.length)
console.log('Groups:', spaceData.groups.length)
console.log('Presets:', spaceData.presets.length)

console.log('Components:', spaceData.components.map(c => c.name))

if (filter) {
console.log('Applied filter:', filter)
console.log('Filtered components:', spaceData.components.map(c => c.name))
}
}
catch (error) {
handleError(error as Error, verbose)
}
})

0 comments on commit 43f889a

Please sign in to comment.