Skip to content

Commit

Permalink
feat: separate-files option
Browse files Browse the repository at this point in the history
  • Loading branch information
alvarosabu committed Nov 7, 2024
1 parent ecbc003 commit a9074a1
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 25 deletions.
33 changes: 33 additions & 0 deletions src/commands/pull-components/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,5 +126,38 @@ describe('pull components actions', () => {
const files = vol.readdirSync('/path/to/components2')
expect(files).toEqual(['custom.json'])
})

it('should save components to separate files', async () => {
vol.fromJSON({
'/path/to/components3': null,
})

const components = [{
name: 'component-name',
display_name: 'Component Name',
created_at: '2021-08-09T12:00:00Z',
updated_at: '2021-08-09T12:00:00Z',
id: 12345,
schema: { type: 'object' },
color: null,
internal_tags_list: ['tag'],
interntal_tags_ids: [1],
}, {
name: 'component-name-2',
display_name: 'Component Name 2',
created_at: '2021-08-09T12:00:00Z',
updated_at: '2021-08-09T12:00:00Z',
id: 12346,
schema: { type: 'object' },
color: null,
internal_tags_list: ['tag'],
interntal_tags_ids: [1],
}]

await saveComponentsToFiles('12345', components, { path: '/path/to/components3', separateFiles: true })

const files = vol.readdirSync('/path/to/components3')
expect(files).toEqual(['component-name-2.12345.json', 'component-name.12345.json'])
})
})
})
36 changes: 17 additions & 19 deletions src/commands/pull-components/actions.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { ofetch } from 'ofetch'
import { handleAPIError, handleFileSystemError } from '../../utils'
import { regionsDomain } from '../../constants'
import { access, constants, mkdir, writeFile } from 'node:fs/promises'
import { join, resolve } from 'node:path'
import { saveToFile } from '../../utils/filesystem'

export interface SpaceComponent {
name: string
Expand All @@ -29,6 +29,7 @@ export interface SpaceComponent {
export interface ComponentsSaveOptions {
path?: string
filename?: string
separateFiles?: boolean
}

export const pullComponents = async (space: string, token: string, region: string): Promise<SpaceComponent[] | undefined> => {
Expand All @@ -51,28 +52,25 @@ export const saveComponentsToFiles = async (space: string, components: SpaceComp
try {
const data = JSON.stringify(components, null, 2)
const resolvedPath = path ? resolve(process.cwd(), path) : process.cwd()
const filePath = join(resolvedPath, filename ? `${filename}.json` : `components.${space}.json`)

// Check if the path exists, and create it if it doesn't
try {
await access(resolvedPath, constants.F_OK)
}
catch {
try {
await mkdir(resolvedPath, { recursive: true })
}
catch (mkdirError) {
handleFileSystemError('mkdir', mkdirError as Error)
return // Exit early if the directory creation fails
if (options.separateFiles) {
for (const component of components) {
try {
const filePath = join(resolvedPath, `${component.name}.${space}.json`)
await saveToFile(filePath, JSON.stringify(component, null, 2))
}
catch (error) {
handleFileSystemError('write', error as Error)
}
}
return
}

try {
await writeFile(filePath, data, { mode: 0o600 })
}
catch (writeError) {
handleFileSystemError('write', writeError as Error)
}
// Default to saving all components to a single file
const filePath = join(resolvedPath, filename ? `${filename}.json` : `components.${space}.json`)

// Check if the path exists, and create it if it doesn't
await saveToFile(filePath, data)
}
catch (error) {
handleFileSystemError('write', error as Error)
Expand Down
74 changes: 70 additions & 4 deletions src/commands/pull-components/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('pullComponents', () => {
})

describe('default mode', () => {
it('should prompt tge yser if the operation was sucessfull', async () => {
it('should prompt the user if the operation was sucessfull', async () => {
const mockResponse = [{
name: 'component-name',
display_name: 'Component Name',
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('pullComponents', () => {
expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', mockResponse, {

})
expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`components.12345.json`)}`)
expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(`components.12345.json`)}`)
})

it('should throw an error if the user is not logged in', async () => {
Expand Down Expand Up @@ -151,7 +151,7 @@ describe('pullComponents', () => {
await pullComponentsCommand.parseAsync(['node', 'test', '--space', '12345', '--path', '/path/to/components'])
expect(pullComponents).toHaveBeenCalledWith('12345', 'valid-token', 'eu')
expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', mockResponse, { path: '/path/to/components' })
expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`/path/to/components/components.12345.json`)}`)
expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(`/path/to/components/components.12345.json`)}`)
})
})

Expand Down Expand Up @@ -180,7 +180,73 @@ describe('pullComponents', () => {
await pullComponentsCommand.parseAsync(['node', 'test', '--space', '12345', '--filename', 'custom'])
expect(pullComponents).toHaveBeenCalledWith('12345', 'valid-token', 'eu')
expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', mockResponse, { filename: 'custom' })
expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`custom.json`)}`)
expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(`custom.json`)}`)
})
})

describe('--separate-files option', () => {
it('should save each component in a separate file', async () => {
const mockResponse = [{
name: 'component-name',
display_name: 'Component Name',
created_at: '2021-08-09T12:00:00Z',
updated_at: '2021-08-09T12:00:00Z',
id: 12345,
schema: { type: 'object' },
color: null,
internal_tags_list: ['tag'],
interntal_tags_ids: [1],
}, {
name: 'component-name-2',
display_name: 'Component Name 2',
created_at: '2021-08-09T12:00:00Z',
updated_at: '2021-08-09T12:00:00Z',
id: 12346,
schema: { type: 'object' },
color: null,
internal_tags_list: ['tag'],
interntal_tags_ids: [1],
}]

session().state = {
isLoggedIn: true,
password: 'valid-token',
region: 'eu',
}

vi.mocked(pullComponents).mockResolvedValue(mockResponse)

await pullComponentsCommand.parseAsync(['node', 'test', '--space', '12345', '--separate-files'])
expect(pullComponents).toHaveBeenCalledWith('12345', 'valid-token', 'eu')
expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', mockResponse, { separateFiles: true })
expect(konsola.ok).toHaveBeenCalledWith(`Components downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(`./`)}`)
})

it('should warn the user if the --filename is used along', async () => {
const mockResponse = [{
name: 'component-name',
display_name: 'Component Name',
created_at: '2021-08-09T12:00:00Z',
updated_at: '2021-08-09T12:00:00Z',
id: 12345,
schema: { type: 'object' },
color: null,
internal_tags_list: ['tag'],
interntal_tags_ids: [1],
}]

session().state = {
isLoggedIn: true,
password: 'valid-token',
region: 'eu',
}

vi.mocked(pullComponents).mockResolvedValue(mockResponse)

await pullComponentsCommand.parseAsync(['node', 'test', '--space', '12345', '--separate-files', '--filename', 'custom'])
expect(pullComponents).toHaveBeenCalledWith('12345', 'valid-token', 'eu')
expect(saveComponentsToFiles).toHaveBeenCalledWith('12345', mockResponse, { separateFiles: true, filename: 'custom' })
expect(konsola.warn).toHaveBeenCalledWith(`The --filename option is ignored when using --separate-files`)
})
})
})
14 changes: 12 additions & 2 deletions src/commands/pull-components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ export const pullComponentsCommand = program
.option('-s, --space <space>', 'space ID')
.option('-p, --path <path>', 'path to save the file')
.option('-f, --filename <filename>', 'custom name to be used in file(s) name instead of space id')
.option('--sf, --separate-files [value]', 'Argument to create a single file for each component')
.action(async (options) => {
konsola.title(` ${commands.PULL_COMPONENTS} `, colorPalette.PULL_COMPONENTS, 'Pulling components...')
// Global options
const verbose = program.opts().verbose
// Command options
const { space, path, filename } = options
const { space, path, filename, separateFiles } = options

const { state, initializeSession } = session()
await initializeSession()
Expand All @@ -42,9 +43,18 @@ export const pullComponentsCommand = program
await saveComponentsToFiles(space, components, {
path,
filename,
separateFiles,
})
const msgFilename = filename ? `${filename}.json` : `components.${space}.json`
konsola.ok(`Components downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(path ? `${path}/${msgFilename}` : `${msgFilename}`)}`)

if (separateFiles) {
if (filename) {
konsola.warn(`The --filename option is ignored when using --separate-files`)
}
konsola.ok(`Components downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(path ? `${path}` : './')}`)
return
}
konsola.ok(`Components downloaded successfully in ${chalk.hex(colorPalette.PRIMARY)(path ? `${path}/${msgFilename}` : `${msgFilename}`)}`)
}
catch (error) {
handleError(error as Error, verbose)
Expand Down
27 changes: 27 additions & 0 deletions src/utils/filesystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { parse } from 'node:path'
import { access, constants, mkdir, writeFile } from 'node:fs/promises'
import { handleFileSystemError } from './error/filesystem-error'

export const saveToFile = async (filePath: string, data: string) => {
// Check if the path exists, and create it if it doesn't
const resolvedPath = parse(filePath).dir
try {
await access(resolvedPath, constants.F_OK)
}
catch {
try {
await mkdir(resolvedPath, { recursive: true })
}
catch (mkdirError) {
handleFileSystemError('mkdir', mkdirError as Error)
return // Exit early if the directory creation fails
}
}

try {
await writeFile(filePath, data, { mode: 0o600 })
}
catch (writeError) {
handleFileSystemError('write', writeError as Error)
}
}

0 comments on commit a9074a1

Please sign in to comment.