Skip to content

Commit

Permalink
Merge pull request #135 from storyblok/feature/generated-file-naming-…
Browse files Browse the repository at this point in the history
…syntax

feat!: generated file naming syntax
  • Loading branch information
alvarosabu authored Dec 2, 2024
2 parents 6ee6107 + c66270c commit 16c91fc
Show file tree
Hide file tree
Showing 8 changed files with 169 additions and 24 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,37 @@ Will generate the languages in the `.storyblok/languages` directory.
> [!TIP]
> If you prefer to avoid pushing the `.storyblok` directory to your repository you can add it to your `.gitignore` file.
### Generated filename syntax conventions

The generated files will now follow a more consistent naming convention. The files will be named using the following syntax:

```
<filename>.<suffix>.<extension>
```

Where:

- `<filename>` is the name of the file. Customizable by the user with the `--filename` flag
- `<suffix>` is an optional suffix to differentiate the files. By default is going to be the `spaceId` and is customizable by the user with the `--suffix` flag
- `<extension>` is the file extension. By default is `json` (Not configurable)

Example:

```bash
storyblok pull-languages --space=12345 --filename=my-languages --suffix=dev
```

Will generate the languages in the following path `.storyblok/languages/my-languages.dev.json`

If you would like to use a timestamp as the suffix you can use:

```bash
storyblok pull-languages --space=12345 --filename=my-languages --suffix="$(date +%s)"
```

> [!WARNING]
> The `--filename` will be ignored in the case that `--separate-files` is used on the commands that supports it.
## Setup

First clone the repository and install the dependencies:
Expand Down
7 changes: 6 additions & 1 deletion src/commands/pull-languages/actions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,12 @@ describe('pull languages actions', () => {
},
],
}
await saveLanguagesToFile('12345', mockResponse, '/temp')
await saveLanguagesToFile('12345', mockResponse, {
filename: 'languages',
path: '/temp',
verbose: false,
space: '12345',
})
const content = vol.readFileSync('/temp/languages.12345.json', 'utf8')
expect(content).toBe(JSON.stringify(mockResponse, null, 2))
})
Expand Down
8 changes: 5 additions & 3 deletions src/commands/pull-languages/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { handleAPIError, handleFileSystemError } from '../../utils'
import { ofetch } from 'ofetch'
import { regionsDomain } from '../../constants'
import { resolvePath, saveToFile } from '../../utils/filesystem'
import type { PullLanguagesOptions } from './constants'

export interface SpaceInternationalizationOptions {
languages: SpaceLanguage[]
Expand Down Expand Up @@ -31,12 +32,13 @@ export const pullLanguages = async (space: string, token: string, region: string
}
}

export const saveLanguagesToFile = async (space: string, internationalizationOptions: SpaceInternationalizationOptions, path?: string) => {
export const saveLanguagesToFile = async (space: string, internationalizationOptions: SpaceInternationalizationOptions, options: PullLanguagesOptions) => {
try {
const { filename = 'languages', suffix = space, path } = options
const data = JSON.stringify(internationalizationOptions, null, 2)
const filename = `languages.${space}.json`
const name = `${filename}.${suffix}.json`
const resolvedPath = resolvePath(path, 'languages')
const filePath = join(resolvedPath, filename)
const filePath = join(resolvedPath, name)

await saveToFile(filePath, data)
}
Expand Down
30 changes: 30 additions & 0 deletions src/commands/pull-languages/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { CommandOptions } from '../../types'

/**
* Interface representing the options for the `pull-languages` command.
*/
export interface PullLanguagesOptions extends CommandOptions {
/**
* The path to save the languages file to.
* Defaults to `.storyblok/languages`.
* @default `.storyblok/languages`
*/
path?: string
/**
* The space ID.
* @required true
*/
space: string
/**
* The filename to save the file as.
* Defaults to `languages`. The file will be saved as `<filename>.<space>.json`.
* @default `languages
*/
filename?: string
/**
* The suffix to add to the filename.
* Defaults to the space ID.
* @default space
*/
suffix?: string
}
75 changes: 71 additions & 4 deletions src/commands/pull-languages/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,9 @@ describe('pullLanguages', () => {
vi.mocked(pullLanguages).mockResolvedValue(mockResponse)
await pullLanguagesCommand.parseAsync(['node', 'test', '--space', '12345'])
expect(pullLanguages).toHaveBeenCalledWith('12345', 'valid-token', 'eu')
expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, undefined)
expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, {
space: '12345',
})
expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/languages/languages.12345.json`)}`)
})

Expand All @@ -109,8 +111,6 @@ describe('pullLanguages', () => {
}

const mockError = new CommandError(`Please provide the space as argument --space YOUR_SPACE_ID.`)

console.log(pullLanguagesCommand)
await pullLanguagesCommand.parseAsync(['node', 'test'])
expect(konsola.error).toHaveBeenCalledWith(mockError, false)
})
Expand Down Expand Up @@ -157,8 +157,75 @@ describe('pullLanguages', () => {
vi.mocked(pullLanguages).mockResolvedValue(mockResponse)
await pullLanguagesCommand.parseAsync(['node', 'test', '--space', '12345', '--path', '/tmp'])
expect(pullLanguages).toHaveBeenCalledWith('12345', 'valid-token', 'eu')
expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, '/tmp')
expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, {
path: '/tmp',
space: '12345',
})
expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`/tmp/languages.12345.json`)}`)
})
})

describe('--filename option', () => {
it('should save the file with the provided filename', async () => {
const mockResponse = {
default_lang_name: 'en',
languages: [
{
code: 'ca',
name: 'Catalan',
},
{
code: 'fr',
name: 'French',
},
],
}
session().state = {
isLoggedIn: true,
password: 'valid-token',
region: 'eu',
}

vi.mocked(pullLanguages).mockResolvedValue(mockResponse)
await pullLanguagesCommand.parseAsync(['node', 'test', '--space', '12345', '--filename', 'custom-languages'])
expect(pullLanguages).toHaveBeenCalledWith('12345', 'valid-token', 'eu')
expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, {
filename: 'custom-languages',
space: '12345',
})
expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/languages/custom-languages.12345.json`)}`)
})
})

describe('--suffix option', () => {
it('should save the file with the provided suffix', async () => {
const mockResponse = {
default_lang_name: 'en',
languages: [
{
code: 'ca',
name: 'Catalan',
},
{
code: 'fr',
name: 'French',
},
],
}
session().state = {
isLoggedIn: true,
password: 'valid-token',
region: 'eu',
}

vi.mocked(pullLanguages).mockResolvedValue(mockResponse)
await pullLanguagesCommand.parseAsync(['node', 'test', '--space', '12345', '--suffix', 'custom-suffix'])
expect(pullLanguages).toHaveBeenCalledWith('12345', 'valid-token', 'eu')
expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, {
suffix: 'custom-suffix',
space: '12345',
})
expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/languages/languages.custom-suffix.json`)}`)
})
})
})
11 changes: 7 additions & 4 deletions src/commands/pull-languages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { getProgram } from '../../program'
import { session } from '../../session'
import { pullLanguages, saveLanguagesToFile } from './actions'
import chalk from 'chalk'
import type { PullLanguagesOptions } from './constants'

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

Expand All @@ -12,12 +13,14 @@ export const pullLanguagesCommand = program
.description(`Download your space's languages schema as json`)
.option('-s, --space <space>', 'space ID')
.option('-p, --path <path>', 'path to save the file. Default is .storyblok/languages')
.action(async (options) => {
.option('-f, --filename <filename>', 'filename to save the file as <filename>.<suffix>.json')
.option('--su, --suffix <suffix>', 'suffix to add to the file name (e.g. languages.<suffix>.json). By default, the space ID is used.')
.action(async (options: PullLanguagesOptions) => {
konsola.title(` ${commands.PULL_LANGUAGES} `, colorPalette.PULL_LANGUAGES, 'Pulling languages...')
// Global options
const verbose = program.opts().verbose
// Command options
const { space, path } = options
const { space, path, filename = 'languages', suffix = options.space } = options

const { state, initializeSession } = session()
await initializeSession()
Expand All @@ -38,8 +41,8 @@ export const pullLanguagesCommand = program
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` : `.storyblok/languages/languages.${space}.json`)}`)
await saveLanguagesToFile(space, internationalization, options)
konsola.ok(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(path ? `${path}/${filename}.${suffix}.json` : `.storyblok/languages/${filename}.${suffix}.json`)}`)
}
catch (error) {
handleError(error as Error, verbose)
Expand Down
9 changes: 9 additions & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Interface representing the default options for a CLI command.
*/
export interface CommandOptions {
/**
* Indicates whether verbose output is enabled.
*/
verbose: boolean
}
22 changes: 10 additions & 12 deletions src/utils/filesystem.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,23 @@
import { parse, resolve } from 'node:path'
import { access, constants, mkdir, writeFile } from 'node:fs/promises'
import { 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
// Get the directory path
const resolvedPath = parse(filePath).dir

// Ensure the directory exists
try {
await access(resolvedPath, constants.F_OK)
await mkdir(resolvedPath, { recursive: true })
}
catch {
try {
await mkdir(resolvedPath, { recursive: true })
}
catch (mkdirError) {
handleFileSystemError('mkdir', mkdirError as Error)
return // Exit early if the directory creation fails
}
catch (mkdirError) {
handleFileSystemError('mkdir', mkdirError as Error)
return // Exit early if the directory creation fails
}

// Write the file
try {
await writeFile(filePath, data, { mode: 0o600 })
await writeFile(filePath, data)
}
catch (writeError) {
handleFileSystemError('write', writeError as Error)
Expand Down

0 comments on commit 16c91fc

Please sign in to comment.