Skip to content

Commit

Permalink
feat!: .storyblok directory encapsulation as default path
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Generated files will no longer be saved on the root of the project by default, they will be encapsulated inside of a `.storyblok` folder.
  • Loading branch information
alvarosabu committed Nov 13, 2024
1 parent 4e52b6b commit c3b09d9
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 26 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,23 @@ If you prefer not to install the package globally you can use `npx`:
npx storyblok <command>
```

## Breaking Changes ⚠️

### `.storyblok` directory as default

All the commands that generate files will now use the `.storyblok` directory as the default directory to interact with those files. This aims to encapsulate all Storyblok CLI operations instead of filling them on the root. Users would be able to customize the directory by using the `--path` flag.

Example:

```bash
storyblok pull-languages --space=12345
```

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.
## Setup

First clone the repository and install the dependencies:
Expand Down
27 changes: 4 additions & 23 deletions src/commands/pull-languages/actions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { access, constants, mkdir, writeFile } from 'node:fs/promises'
import { join, resolve } from 'node:path'
import { join } from 'node:path'

import { handleAPIError, handleFileSystemError } from '../../utils'
import { ofetch } from 'ofetch'
import { regionsDomain } from '../../constants'
import { resolvePath, saveToFile } from '../../utils/filesystem'

export interface SpaceInternationalizationOptions {
languages: SpaceLanguage[]
Expand Down Expand Up @@ -35,29 +35,10 @@ export const saveLanguagesToFile = async (space: string, internationalizationOpt
try {
const data = JSON.stringify(internationalizationOptions, null, 2)
const filename = `languages.${space}.json`
const resolvedPath = path ? resolve(process.cwd(), path) : process.cwd()
const resolvedPath = resolvePath(path, 'languages')
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 {
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)
}
await saveToFile(filePath, data)
}
catch (error) {
handleFileSystemError('write', error as Error)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/pull-languages/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe('pullLanguages', () => {
await pullLanguagesCommand.parseAsync(['node', 'test', '--space', '12345'])
expect(pullLanguages).toHaveBeenCalledWith('12345', 'valid-token', 'eu')
expect(saveLanguagesToFile).toHaveBeenCalledWith('12345', mockResponse, undefined)
expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`languages.12345.json`)}`)
expect(konsola.ok).toHaveBeenCalledWith(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(`.storyblok/languages/languages.12345.json`)}`)
})

it('should throw an error if the user is not logged in', async () => {
Expand Down
4 changes: 2 additions & 2 deletions src/commands/pull-languages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const pullLanguagesCommand = program
.command('pull-languages')
.description(`Download your space's languages schema as json`)
.option('-s, --space <space>', 'space ID')
.option('-p, --path <path>', 'path to save the file')
.option('-p, --path <path>', 'path to save the file. Default is .storyblok/languages')
.action(async (options) => {
konsola.title(` ${commands.PULL_LANGUAGES} `, colorPalette.PULL_LANGUAGES, 'Pulling languages...')
// Global options
Expand Down Expand Up @@ -39,7 +39,7 @@ export const pullLanguagesCommand = program
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`)}`)
konsola.ok(`Languages schema downloaded successfully at ${chalk.hex(colorPalette.PRIMARY)(path ? `${path}/languages.${space}.json` : `.storyblok/languages/languages.${space}.json`)}`)
}
catch (error) {
handleError(error as Error, verbose)
Expand Down
51 changes: 51 additions & 0 deletions src/utils/filesystem.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { vol } from 'memfs'
import { resolvePath, saveToFile } from './filesystem'
import { resolve } from 'node:path'

// tell vitest to use fs mock from __mocks__ folder
// this can be done in a setup file if fs should always be mocked
vi.mock('node:fs')
vi.mock('node:fs/promises')

beforeEach(() => {
vi.clearAllMocks()
// reset the state of in-memory fs
vol.reset()
})

describe('filesystem utils', async () => {
describe('saveToFile', async () => {
it('should save the data to the file', async () => {
const filePath = '/path/to/file.txt'
const data = 'Hello, World!'

await saveToFile(filePath, data)

const content = vol.readFileSync(filePath, 'utf8')
expect(content).toBe(data)
})

it('should create the directory if it does not exist', async () => {
const filePath = '/path/to/new/file.txt'
const data = 'Hello, World!'

await saveToFile(filePath, data)

const content = vol.readFileSync(filePath, 'utf8')
expect(content).toBe(data)
})
})

describe('resolvePath', async () => {
it('should resolve the path correctly', async () => {
const path = '/path/to/file'
const folder = 'folder'

const resolvedPath = resolvePath(path, folder)
expect(resolvedPath).toBe(resolve(process.cwd(), path))

const resolvedPathWithoutPath = resolvePath(undefined, folder)
expect(resolvedPathWithoutPath).toBe(resolve(process.cwd(), '.storyblok/folder'))
})
})
})
29 changes: 29 additions & 0 deletions src/utils/filesystem.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { parse, resolve } 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)
}
}

export const resolvePath = (path: string | undefined, folder: string) => path ? resolve(process.cwd(), path) : resolve(resolve(process.cwd(), '.storyblok'), folder)

0 comments on commit c3b09d9

Please sign in to comment.