Skip to content

Commit

Permalink
feat: Merge pull request #343 from storyblok/feature/migration-fields
Browse files Browse the repository at this point in the history
Feature Migration Fields
  • Loading branch information
onefriendaday authored Jun 29, 2020
2 parents d59ecf4 + 2f0671b commit 0bee124
Show file tree
Hide file tree
Showing 19 changed files with 3,269 additions and 1,907 deletions.
97 changes: 97 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,35 @@ Login to the Storyblok cli
$ storyblok login
```

### generate-migration

Create a migration file (with the name `change_<COMPONENT>_<FIELD>.js`) inside the `migrations` folder. Check **Migrations** section to more details

```sh
$ storyblok generate-migration --space <SPACE_ID> --component <COMPONENT_NAME> --field <FIELD>
```

#### Options

* `space`: space where the component is
* `component`: component name. It needs to be a valid component
* `field`: name of field

### run-migration

Execute a specific migration file. Check **Migrations** section to more details

```sh
$ storyblok run-migration --space <SPACE_ID> --component <COMPONENT_NAME> --field <FIELD> --dryrun
```

#### Options

* `space`: space where the component is
* `component`: component name. It needs to be a valid component
* `field`: name of field
* `dryrun`: when passed as an argument, does not perform the migration

### Help

For global help
Expand All @@ -138,6 +167,74 @@ For view the CLI version
$ storyblok -V # or --version
```

## Migrations

Migrations are a convenient way to update stories in Storyblok. This section shows how to create a migration file and execute it using the CLI.

### Creating a migration file

To create a migration file, you need to execute the `generate-migration` command:

```sh
# creating a migration file to product component to update the price
$ storyblok generate-migration --space 00000 --component product --field price
```

When you run this command, a folder called `migrations` will be created in the location where you ran the command (if this folder does not exist) and a file called `change_product_price.js` will be created inside it.

The created file will have the following content:

```js
// here, 'subtitle' is the name of the field defined when you execute the generate command
module.exports = function (block) {
// Example to change a string to boolean
// block.subtitle = !!(block.subtitle)

// Example to transfer content from other field
// block.subtitle = block.other_field
}
```

As you can see, this file takes two parameters:

* `block`: the component content from the story
* `field`: the field content from this component

Inside the migration function, you can manipulate the blok whatever you want, because the blok content will be used to update the story. This will be occurr recursively for all content in the story, so, this change will be affect the entirely content.

### Running the migration file

To run the migration function, you need to execute the `run-migration` command, as the following:

```sh
# you can use the --dryrun option to don't execute, only show the component updates
$ storyblok run-migration --space 00000 --component product --field price
```

### Example

Let's create an example to update all occurrences of the image field in product component. Let's replace the url from `//a.storyblok.com` to `//my-custom-domain.com`.

First, you need to create the migration function:

```sh
$ storyblok generate-migration --space 00000 --component product --field image
```

After, let's update the default file:

```js
module.exports = function (block) {
block.image = block.image.replace('a.storyblok.com', 'my-custom-domain.com')
}
```

Lastly, let's execute the migration file:

```sh
$ storyblok run-migration --space 00000 --component product --field image
```

## You're looking for a headstart?

Check out our guides for client side apps (VueJS, Angular, React, ...), static site (Jekyll, NuxtJs, ...), dynamic site examples (Node, PHP, Python, Laravel, ...) on our [Getting Started](https://www.storyblok.com/getting-started) page.
31 changes: 31 additions & 0 deletions __mocks__/fs-extra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const fs = jest.genMockFromModule('fs-extra')

let mockFiles = Object.create(null)

// used by pull-components.spec.js
const pathExists = jest.fn((key) => {
return !!mockFiles[key]
})

const outputFile = jest.fn((path, data, fn) => {
mockFiles[path] = data
return Promise.resolve(true)
})

const __clearMockFiles = () => {
mockFiles = Object.create(null)
}

const __setMockFiles = (mock) => {
mockFiles = mock
}

fs.pathExists = pathExists

fs.outputFile = outputFile

fs.__clearMockFiles = __clearMockFiles

fs.__setMockFiles = __setMockFiles

module.exports = fs
37 changes: 19 additions & 18 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,35 @@
"author": "Dominik Angerer <[email protected]>, Alexander Feiglstorfer <[email protected]>",
"license": "MIT",
"dependencies": {
"axios": "^0.19.0",
"chalk": "^1.1.3",
"clear": "0.0.1",
"commander": "^4.0.0",
"figlet": "^1.2.0",
"axios": "^0.19.2",
"chalk": "^4.1.0",
"clear": "0.1.0",
"commander": "^5.1.0",
"figlet": "^1.4.0",
"fs-extra": "^9.0.1",
"github-download": "^0.5.0",
"inquirer": "^3.0.1",
"minimist": "^1.2.0",
"inquirer": "^7.2.0",
"lodash": "^4.17.15",
"netrc": "0.1.4",
"opn": "^5.1.0",
"on-change": "^2.0.1",
"opn": "^6.0.0",
"p-series": "^2.1.0",
"path": "^0.12.7",
"storyblok-js-client": "^2.0.10",
"unirest": "^0.5.1",
"update-notifier": "^4.0.0"
"storyblok-js-client": "^2.5.0",
"update-notifier": "^4.1.0"
},
"engines": {
"node": ">=9.11.0"
"node": ">=10.0.0"
},
"devDependencies": {
"concat-stream": "^2.0.0",
"eslint": "^6.6.0",
"eslint-config-standard": "^14.1.0",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jest": "^23.0.2",
"eslint-plugin-node": "^10.0.0",
"eslint": "^7.2.0",
"eslint-config-standard": "^14.1.1",
"eslint-plugin-import": "^2.21.2",
"eslint-plugin-jest": "^23.13.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.1",
"jest": "^24.9.0"
"jest": "^26.0.1"
}
}
122 changes: 86 additions & 36 deletions src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,10 @@ program
}

try {
const questions = getQuestions('login', {}, api)
const { email, password } = await inquirer.prompt(questions)

await api.login(email, password)
console.log(chalk.green('✓') + ' Log in successfully! Token has been added to .netrc file.')
await api.processLogin()
process.exit(0)
} catch (e) {
console.log(chalk.red('X') + ' An error ocurred when login the user')
console.error(e)
console.log(chalk.red('X') + ' An error occurred when logging the user: ' + e.message)
process.exit(1)
}
})
Expand All @@ -69,8 +64,8 @@ program
console.log('Logged out successfully! Token has been removed from .netrc file.')
process.exit(0)
} catch (e) {
console.log(chalk.red('X') + ' An error ocurred when logout the user')
console.error(e)
console.log(chalk.red('X') + ' An error occurred when logging out the user: ' + e.message)
process.exit(1)
}
})

Expand All @@ -79,24 +74,23 @@ program
.command('pull-components')
.description("Download your space's components schema as json")
.action(async () => {
console.log(`${chalk.blue('-')} Executing push-components task`)
console.log(`${chalk.blue('-')} Executing pull-components task`)
const space = program.space
if (!space) {
console.log(chalk.red('X') + ' Please provide the space as argument --space YOUR_SPACE_ID.')
process.exit(0)
}

try {
const questions = await getQuestions('pull-components', { space }, api)

await inquirer.prompt(questions)
if (!api.isAuthorized()) {
await api.processLogin()
}

api.setSpaceId(space)
await tasks.pullComponents(api, { space })
} catch (e) {
console.log(chalk.red('X') + ' An error ocurred when execute the pull-components task')
console.error(e)
process.exit(0)
console.log(chalk.red('X') + ' An error occurred when executing the pull-components task: ' + e.message)
process.exit(1)
}
})

Expand All @@ -114,16 +108,15 @@ program
}

try {
const questions = await getQuestions('push-components', { space }, api)

await inquirer.prompt(questions)
if (!api.isAuthorized()) {
await api.processLogin()
}

api.setSpaceId(space)
await tasks.pushComponents(api, { source })
} catch (e) {
console.log(chalk.red('X') + ' An error ocurred when execute the push-components task')
console.error(e)
process.exit(0)
console.log(chalk.red('X') + ' An error occurred when executing the push-components task: ' + e.message)
process.exit(1)
}
})

Expand All @@ -145,8 +138,8 @@ program
console.log(chalk.green('✓') + ' - source/scss/components/below/_' + name + '.scss')
process.exit(0)
} catch (e) {
console.log(chalk.red('X') + ' An error ocurred execute operations to create the component')
console.error(e)
console.log(chalk.red('X') + ' An error occurred when executing operations to create the component: ' + e.message)
process.exit(1)
}
})

Expand All @@ -163,8 +156,8 @@ program

await lastStep(answers)
} catch (e) {
console.error(e)
process.exit(0)
console.error(chalk.red('X') + ' An error ocurred when execute the select command: ' + e.message)
process.exit(1)
}
})

Expand Down Expand Up @@ -194,11 +187,7 @@ program
})

if (!api.isAuthorized()) {
const questions = getQuestions('login', {}, api)
const { email, password } = await inquirer.prompt(questions)

await api.login(email, password)
console.log(chalk.green('✓') + ' Log in successfully! Token has been added to .netrc file.')
await api.processLogin()
}

const token = creds.get().token || null
Expand All @@ -211,9 +200,8 @@ program

console.log('\n' + chalk.green('✓') + ' Sync data between spaces successfully completed')
} catch (e) {
console.error(chalk.red('X') + ' An error ocurred when sync spaces')
console.error(e)
process.exit(0)
console.error(chalk.red('X') + ' An error ocurred when syncing spaces: ' + e.message)
process.exit(1)
}
})

Expand All @@ -228,8 +216,70 @@ program
const answers = await inquirer.prompt(questions)
await tasks.quickstart(api, answers, space)
} catch (e) {
console.log(chalk.red('X') + ' An error ocurred when execute quickstart operations')
console.error(e)
console.log(chalk.red('X') + ' An error ocurred when execute quickstart operations: ' + e.message)
process.exit(1)
}
})

program
.command('generate-migration')
.description('Generate a content migration file')
.requiredOption('-c, --component <COMPONENT_NAME>', 'Name of the component')
.requiredOption('-f, --field <FIELD_NAME>', 'Name of the component field')
.action(async (options) => {
const field = options.field || ''
const component = options.component || ''

const space = program.space
if (!space) {
console.log(chalk.red('X') + ' Please provide the space as argument --space YOUR_SPACE_ID.')
process.exit(1)
}

console.log(`${chalk.blue('-')} Creating the migration file in ./migrations/change_${component}_${field}.js\n`)

try {
if (!api.isAuthorized()) {
await api.processLogin()
}

api.setSpaceId(space)
await tasks.generateMigration(api, component, field)
} catch (e) {
console.log(chalk.red('X') + ' An error ocurred when generate the migration file: ' + e.message)
process.exit(1)
}
})

program
.command('run-migration')
.description('Run a migration file')
.requiredOption('-c, --component <COMPONENT_NAME>', 'Name of the component')
.requiredOption('-f, --field <FIELD_NAME>', 'Name of the component field')
.option('--dryrun', 'Do not update the story content')
.action(async (options) => {
const field = options.field || ''
const component = options.component || ''
const isDryrun = !!options.dryrun

const space = program.space
if (!space) {
console.log(chalk.red('X') + ' Please provide the space as argument --space YOUR_SPACE_ID.')
process.exit(1)
}

console.log(`${chalk.blue('-')} Processing the migration ./migrations/change_${component}_${field}.js\n`)

try {
if (!api.isAuthorized()) {
await api.processLogin()
}

api.setSpaceId(space)
await tasks.runMigration(api, component, field, { isDryrun })
} catch (e) {
console.log(chalk.red('X') + ' An error ocurred when run the migration file: ' + e.message)
process.exit(1)
}
})

Expand Down
Loading

0 comments on commit 0bee124

Please sign in to comment.