diff --git a/.gitignore b/.gitignore index 1d7923cd..94524345 100644 --- a/.gitignore +++ b/.gitignore @@ -111,4 +111,7 @@ dist # CLI generated files components.*.json presets.*.json -storyblok-component-types.d.ts \ No newline at end of file +storyblok-component-types.d.ts + +# storyblok folder +.storyblok/ \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 17e0d8ed..bd6ed145 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -45,6 +45,17 @@ "console": "integratedTerminal", "sourceMaps": true, "outFiles": ["${workspaceFolder}/dist/**/*.js"] + }, + { + "type": "node", + "request": "launch", + "name": "Debug Pull languages", + "program": "${workspaceFolder}/dist/index.mjs", + "args": ["pull-languages", "--space", "295018", "--path", ".storyblok"], + "cwd": "${workspaceFolder}", + "console": "integratedTerminal", + "sourceMaps": true, + "outFiles": ["${workspaceFolder}/dist/**/*.js"] } ] } diff --git a/src/commands/pull-languages/actions.ts b/src/commands/pull-languages/actions.ts new file mode 100644 index 00000000..53122928 --- /dev/null +++ b/src/commands/pull-languages/actions.ts @@ -0,0 +1,64 @@ +import { access, constants, mkdir, writeFile } from 'node:fs/promises' +import { join, resolve } from 'node:path' + +import { handleAPIError, handleFileSystemError } from '../../utils' +import { ofetch } from 'ofetch' +import { regionsDomain } from '../../constants' +import { session } from '../../session' +import { apiClient } from '../../api' + +export interface SpaceInternationalizationOptions { + languages: SpaceLanguage[] + default_lang_name: string +} +export interface SpaceLanguage { + code: string + name: string +} + +export const pullLanguages = async (space: string): Promise => { + try { + const { client } = apiClient() + if (!client) { + throw new Error('Client not initialized') + } + + const { state } = session() + const response = await ofetch(`https://${regionsDomain[state.region]}/v1/spaces/${space}`, { + headers: { + Authorization: state.password, + }, + }) + return { + default_lang_name: response.space.default_lang_name, + languages: response.space.languages, + } + } + catch (error) { + handleAPIError('pull_languages', error as Error) + } +} + +export const saveLanguagesToFile = async (space: string, internationalizationOptions: SpaceInternationalizationOptions, path?: string) => { + try { + const data = JSON.stringify(internationalizationOptions, null, 2) + const filename = `languages.${space}.json` + const resolvedPath = path ? resolve(process.cwd(), path) : process.cwd() + const filePath = join(resolvedPath, filename) + + // Check if the path exists, and create it if it doesn't + try { + await access(resolvedPath, constants.F_OK) + } + catch { + await mkdir(resolvedPath, { recursive: true }) + } + + return await writeFile(filePath, data, { + mode: 0o600, + }) + } + catch (error) { + handleFileSystemError('write', error as Error) + } +} diff --git a/src/commands/pull-languages/index.ts b/src/commands/pull-languages/index.ts new file mode 100644 index 00000000..13768829 --- /dev/null +++ b/src/commands/pull-languages/index.ts @@ -0,0 +1,45 @@ +import { colorPalette, commands } from '../../constants' +import { CommandError, handleError, konsola } from '../../utils' +import { getProgram } from '../../program' +import { session } from '../../session' +import { pullLanguages, saveLanguagesToFile } from './actions' +import chalk from 'chalk' + +const program = getProgram() // Get the shared singleton instance + +export const pullLanguagesCommand = program + .command('pull-languages') + .description(`Download your space's languages schema as json`) + .option('-s, --space ', 'space ID') + .option('-p, --path ', 'path to save the file') + .action(async (options) => { + const { space, path } = options + konsola.title(` ${commands.PULL_LANGUAGES} `, colorPalette.PULL_LANGUAGES, 'Pulling languages...') + + const { state, initializeSession } = session() + await initializeSession() + + if (!state.isLoggedIn) { + handleError(new CommandError(`You are currently not logged in. Please login first to get your user info.`)) + return + } + + if (!space) { + handleError(new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`)) + return + } + + try { + const internationalization = await pullLanguages(space) + + if (!internationalization || !internationalization.languages) { + konsola.warn(`No languages found in the space ${space}`) + return + } + await saveLanguagesToFile(space, internationalization, path) + konsola.ok(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(path ? `${path}/languages.${space}.json` : `languages.${space}.json`)}`) + } + catch (error) { + handleError(error as Error, true) + } + }) diff --git a/src/constants.ts b/src/constants.ts index cc527b28..83800611 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,12 +2,14 @@ export const commands = { LOGIN: 'login', LOGOUT: 'logout', USER: 'user', + PULL_LANGUAGES: 'pull-languages', } as const export const colorPalette = { PRIMARY: '#45bfb9', LOGIN: '#8556D3', - USER: '#8BC34A', // Changed to a less saturated green color + USER: '#8BC34A', + PULL_LANGUAGES: '#FFC107', } as const export interface ReadonlyArray { diff --git a/src/index.ts b/src/index.ts index c378fe7a..2e83379e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,8 @@ import { getProgram } from './program' import './commands/login' import './commands/logout' import './commands/user' +import './commands/pull-languages' + import { loginWithToken } from './commands/login/actions' dotenv.config() // This will load variables from .env into process.env @@ -17,7 +19,6 @@ const messageText = ` ` console.log(formatHeader(` ${introText} ${messageText}`)) -program.option('-s, --space [value]', 'space ID') program.option('-v, --verbose', 'Enable verbose output') program.on('command:*', () => {