From 3c4752861e202a551d5d8d697e1a8f047523d142 Mon Sep 17 00:00:00 2001 From: alvarosabu Date: Tue, 5 Nov 2024 10:48:01 +0100 Subject: [PATCH] feat: basic pull-component and save to json functionality --- .vscode/launch.json | 16 ++++- src/commands/pull-components/actions.ts | 80 +++++++++++++++++++++++++ src/commands/pull-components/index.ts | 51 ++++++++++++++++ src/constants.ts | 2 + src/index.ts | 1 + src/utils/error/api-error.ts | 1 + 6 files changed, 150 insertions(+), 1 deletion(-) create mode 100644 src/commands/pull-components/actions.ts create mode 100644 src/commands/pull-components/index.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 8f77209..7c8a0a3 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -60,7 +60,21 @@ "request": "launch", "name": "Debug Pull languages", "program": "${workspaceFolder}/dist/index.mjs", - "args": ["pull-languages", "--space", "2950182323", "--path", ".storyblok"], + "args": ["pull-languages", "--space", "295018", "--path", ".storyblok"], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/dist/**/*.js"], + "env": { + "STUB": "true" + } + }, + { + "type": "node", + "request": "launch", + "name": "Debug Pull Components", + "program": "${workspaceFolder}/dist/index.mjs", + "args": ["pull-components", "--space", "295018", "--path", ".storyblok"], "cwd": "${workspaceFolder}", "console": "integratedTerminal", "sourceMaps": true, diff --git a/src/commands/pull-components/actions.ts b/src/commands/pull-components/actions.ts new file mode 100644 index 0000000..967c95c --- /dev/null +++ b/src/commands/pull-components/actions.ts @@ -0,0 +1,80 @@ +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' + +export interface SpaceComponent { + name: string + display_name: string + created_at: string + updated_at: string + id: number + schema: Record + image?: string + preview_field?: string + is_root?: boolean + is_nestable?: boolean + preview_tmpl?: string + all_presets?: Record + 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 ComponentsSaveOptions { + path?: string + filename?: string +} + +export const pullComponents = async (space: string, token: string, region: string): Promise => { + try { + const response = await ofetch(`https://${regionsDomain[region]}/v1/spaces/${space}/components`, { + headers: { + Authorization: token, + }, + }) + return response.components + } + catch (error) { + handleAPIError('pull_components', error as Error) + } +} + +export const saveComponentsToFiles = async (space: string, components: SpaceComponent[], options: ComponentsSaveOptions) => { + const { path, filename } = options + + 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 + } + } + + try { + await writeFile(filePath, data, { mode: 0o600 }) + } + catch (writeError) { + handleFileSystemError('write', writeError as Error) + } + } + catch (error) { + handleFileSystemError('write', error as Error) + } +} diff --git a/src/commands/pull-components/index.ts b/src/commands/pull-components/index.ts new file mode 100644 index 0000000..c39dd7f --- /dev/null +++ b/src/commands/pull-components/index.ts @@ -0,0 +1,51 @@ +import chalk from 'chalk' +import { colorPalette, commands } from '../../constants' +import { session } from '../../session' +import { getProgram } from '../../program' +import { CommandError, handleError, konsola } from '../../utils' +import { pullComponents, saveComponentsToFiles } from './actions' + +const program = getProgram() // Get the shared singleton instance + +export const pullComponentsCommand = program + .command('pull-components') + .description(`Download your space's components schema as json`) + .option('-s, --space ', 'space ID') + .option('-p, --path ', 'path to save the file') + .option('-f, --filename ', 'custom name to be used in file(s) name instead of space id') + .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 { 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 components = await pullComponents(space, state.password, state.region) + + if (!components || components.length === 0) { + konsola.warn(`No components found in the space ${space}`) + return + } + await saveComponentsToFiles(space, components, { + path, + filename, + }) + konsola.ok(`Components schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(path ? `${path}/components.${space}.json` : `components.${space}.json`)}`) + } + catch (error) { + handleError(error as Error, verbose) + } + }) diff --git a/src/constants.ts b/src/constants.ts index 8380061..9986a5d 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -3,6 +3,7 @@ export const commands = { LOGOUT: 'logout', USER: 'user', PULL_LANGUAGES: 'pull-languages', + PULL_COMPONENTS: 'pull-components', } as const export const colorPalette = { @@ -10,6 +11,7 @@ export const colorPalette = { LOGIN: '#8556D3', USER: '#8BC34A', PULL_LANGUAGES: '#FFC107', + PULL_COMPONENTS: '#FF5722', } as const export interface ReadonlyArray { diff --git a/src/index.ts b/src/index.ts index 2e83379..9f53f93 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import './commands/login' import './commands/logout' import './commands/user' import './commands/pull-languages' +import './commands/pull-components' import { loginWithToken } from './commands/login/actions' diff --git a/src/utils/error/api-error.ts b/src/utils/error/api-error.ts index 122e11c..88b86c0 100644 --- a/src/utils/error/api-error.ts +++ b/src/utils/error/api-error.ts @@ -7,6 +7,7 @@ export const API_ACTIONS = { login_email_password: 'Failed to log in with email and password', get_user: 'Failed to get user', pull_languages: 'Failed to pull languages', + pull_components: 'Failed to pull components', } as const export const API_ERRORS = {