Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(migration): implemented migration strategy using token from app #10

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule:
interval: weekly
time: "10:00"
open-pull-requests-limit: 10
ignore:
- dependency-name: husky
versions:
- ">= 4.a, < 5"
- dependency-name: mkdirp
versions:
- "> 0.5.1"
- dependency-name: y18n
versions:
- 4.0.1
- dependency-name: chalk
versions:
- 4.1.0
- dependency-name: contentful-migration
versions:
- 4.0.11
- 4.0.5
- 4.0.6
- 4.0.7
- 4.0.8
- 4.0.9
- dependency-name: lodash
versions:
- 4.17.20
- 4.17.21
- dependency-name: mocha
versions:
- 8.2.1
- 8.3.0
- 8.3.1
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
strategy:
max-parallel: 1
matrix:
node_version: [10, 12, 14]
node_version: [18, 20, 22]

steps:
- uses: actions/checkout@v1
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ your-project

```

If you don't wish to have your migrations directory in the root of your project, you can configure the name and location by setting the CONTENTFUL_MIGRATIONS_DIR environment variable, e.g. `/path/to/project/foo/contentful-migrations`

> **ATTENTION**: The migrations directory should always be a folder under the project's root (or under some subdir inside the root). Placing the directory outside of the project's version control could lead to the migration code and Contentful schema becoming out-of-sync.

For more information on schema migrations technique and practice, see:

- [Evolutionary Database Design](https://martinfowler.com/articles/evodb.html)
Expand Down
2 changes: 1 addition & 1 deletion bin/commands/bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ exports.handler = async (args) => {
})
}

const migrationsDirectory = path.join('.', 'migrations')
const migrationsDirectory = process.env.CONTENTFUL_MIGRATIONS_DIR || path.join('.', 'migrations')
let writeMigrationState = false
if (contentType.length > 0) {
const answer = await asyncQuestion(chalk.bold.yellow(`⚠️ Do you want to generate initial migration state for ${contentType}? y/N: `))
Expand Down
5 changes: 4 additions & 1 deletion bin/commands/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,10 @@ exports.builder = (yargs) => {
}

exports.handler = ({ name, contentType }) => {
const migrationsDirectory = path.join('.', 'migrations', contentType)
const migrationsDirectory = process.env.CONTENTFUL_MIGRATIONS_DIR
? path.join(process.env.CONTENTFUL_MIGRATIONS_DIR, contentType)
: path.join('.', 'migrations', contentType)

const templateFile = path.join(__dirname, '..', '..', 'lib', 'template.js')

generator({
Expand Down
51 changes: 33 additions & 18 deletions bin/commands/down.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,6 @@ exports.desc =

exports.builder = (yargs) => {
yargs
.option('access-token', {
alias: 't',
describe:
'Contentful Management API access token',
demandOption: true,
default: process.env.CONTENTFUL_MANAGEMENT_ACCESS_TOKEN,
defaultDescription: 'environment var CONTENTFUL_MANAGEMENT_ACCESS_TOKEN'
})
.option('space-id', {
alias: 's',
describe: 'space id to use',
Expand All @@ -36,21 +28,36 @@ exports.builder = (yargs) => {
type: 'string',
requiresArg: true,
default: process.env.CONTENTFUL_ENV_ID || 'master',
defaultDescription: 'environment var CONTENTFUL_ENV_ID if exists, otherwise master'
defaultDescription:
'environment var CONTENTFUL_ENV_ID if exists, otherwise master'
})
.option('content-type', {
alias: 'c',
describe: 'single content type name to process',
demandOption: true
})
.option('app-id', {
type: 'string',
alias: 'a',
describe: 'App ID to use for getting management token',
default: process.env.CONTENTFUL_APP_ID,
demandOption: true
})
.option('access-token', {
alias: 't',
describe: 'CMA access token if provided overrides token fetched by APP',
defaultDescription: 'environment var CONTENTFUL_MANAGEMENT_ACCESS_TOKEN'
})
.option('dry-run', {
alias: 'd',
describe: 'only shows the planned actions, don\'t write anything to Contentful',
describe:
"only shows the planned actions, don't write anything to Contentful",
boolean: true,
default: false
})
.positional('file', {
describe: 'If specified, rollback all migrations scripts down to this one.',
describe:
'If specified, rollback all migrations scripts down to this one.',
type: 'string'
})
}
Expand All @@ -59,16 +66,18 @@ exports.handler = async (args) => {
const {
accessToken,
contentType,
appId,
dryRun,
environmentId,
file,
spaceId
} = args

const migrationsDirectory = path.join('.', 'migrations')
const migrationsDirectory =
process.env.CONTENTFUL_MIGRATIONS_DIR || path.join('.', 'migrations')

const processSet = (set) => {
const name = (file) || set.lastRun
const name = file || set.lastRun

runMigrations(set, 'down', name, (error) => {
if (error) {
Expand All @@ -83,13 +92,19 @@ exports.handler = async (args) => {

// Load in migrations
const sets = await load({
migrationsDirectory, spaceId, environmentId, accessToken, dryRun, contentTypes: [contentType]
appId,
accessToken,
migrationsDirectory,
spaceId,
environmentId,
dryRun,
contentTypes: [contentType]
})

sets.forEach(set => set
.then(processSet)
.catch((err) => {
sets.forEach((set) =>
set.then(processSet).catch((err) => {
log.error('error', err)
process.exit(1)
}))
})
)
}
2 changes: 1 addition & 1 deletion bin/commands/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ exports.builder = (yargs) => {
exports.handler = async ({
spaceId, environmentId, contentType, accessToken
}) => {
const migrationsDirectory = path.join('.', 'migrations')
const migrationsDirectory = process.env.CONTENTFUL_MIGRATIONS_DIR || path.join('.', 'migrations')

const listSet = (set) => {
// eslint-disable-next-line no-console
Expand Down
68 changes: 43 additions & 25 deletions bin/commands/up.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,10 @@ const load = require('../../lib/load')

exports.command = 'up [file]'

exports.desc =
'Migrate up to a give migration or all pending if not specified'
exports.desc = 'Migrate up to a give migration or all pending if not specified'

exports.builder = (yargs) => {
yargs
.option('access-token', {
alias: 't',
describe:
'Contentful Management API access token',
demandOption: true,
default: process.env.CONTENTFUL_MANAGEMENT_ACCESS_TOKEN,
defaultDescription: 'environment var CONTENTFUL_MANAGEMENT_ACCESS_TOKEN'
})
.option('space-id', {
alias: 's',
describe: 'space id to use',
Expand All @@ -40,38 +31,54 @@ exports.builder = (yargs) => {
type: 'string',
requiresArg: true,
default: process.env.CONTENTFUL_ENV_ID || 'master',
defaultDescription: 'environment var CONTENTFUL_ENV_ID if exists, otherwise master'
defaultDescription:
'environment var CONTENTFUL_ENV_ID if exists, otherwise master'
})
.option('content-type', {
alias: 'c',
describe: 'one or more content type names to process',
array: true,
default: []
})
.option('all', {
.option('app-id', {
type: 'string',
alias: 'a',
describe: 'processes migrations for all content types',
boolean: true
describe: 'App ID to use for getting management token',
default: process.env.CONTENTFUL_APP_ID,
demandOption: true
})
.option('dry-run', {
alias: 'd',
describe: 'only shows the planned actions, don\'t write anything to Contentful',
describe:
"only shows the planned actions, don't write anything to Contentful",
boolean: true,
default: false
})
.option('access-token', {
alias: 't',
describe: 'Optional Contentful Management API access token',
defaultDescription:
'You can provide optional CMA access token not to fetch dynamically from app'
})
.option('all', {
describe: 'processes migrations for all content types',
boolean: true,
default: false
})
.positional('file', {
describe: 'If specified, applies all pending migrations scripts up to this one.',
describe:
'If specified, applies all pending migrations scripts up to this one.',
type: 'string'
})
.check((argv) => {
if (argv.a && argv.c.length > 0) {
return 'Arguments \'content-type\' and \'all\' are mutually exclusive'
if (argv.all && argv.c.length > 0) {
return "Arguments 'content-type' and 'all' are mutually exclusive"
}
if (!argv.a && argv.c.length === 0) {
return 'At least one of \'all\' or \'content-type\' options must be specified'
if (!argv.all && argv.c.length === 0) {
return "At least one of 'all' or 'content-type' options must be specified"
}
if (argv.a && argv.file) {
return '[file] cannot be specified together with \'all\' option'
if (argv.all && argv.file) {
return "[file] cannot be specified together with 'all' option"
}
return true
})
Expand All @@ -83,13 +90,15 @@ exports.handler = async (args) => {
const {
accessToken,
contentType,
appId,
dryRun,
environmentId,
file,
spaceId
} = args

const migrationsDirectory = path.join('.', 'migrations')
const migrationsDirectory =
process.env.CONTENTFUL_MIGRATIONS_DIR || path.join('.', 'migrations')

const processSet = async (set) => {
console.log(chalk.bold.blue('Processing'), set.store.contentTypeID)
Expand All @@ -99,6 +108,7 @@ exports.handler = async (args) => {

// Load in migrations
const sets = await load({
appId,
accessToken,
contentTypes: contentType,
dryRun,
Expand All @@ -110,11 +120,19 @@ exports.handler = async (args) => {
// TODO concurrency can be an cmdline option? I set it to 1 for now to make logs more readable
pMap(sets, processSet, { concurrency: 1 })
.then(() => {
console.log(chalk.bold.yellow(`\n🎉 All content types in ${environmentId} are up-to-date`))
console.log(
chalk.bold.yellow(
`\n🎉 All content types in ${environmentId} are up-to-date`
)
)
})
.catch((err) => {
log.error('error', err)
console.log(chalk.bold.red(`\n🚨 Error applying migrations to "${environmentId}" environment! See above for error messages`))
console.log(
chalk.bold.red(
`\n🚨 Error applying migrations to "${environmentId}" environment! See above for error messages`
)
)
process.exit(1)
})
}
11 changes: 6 additions & 5 deletions bin/ctf-migrate
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

/* eslint-disable no-unused-expressions */
// noinspection BadExpressionStatementJS
require('yargs')
.usage('Manage your Contentful schema by creating incremental scripted changes\nFor more information visit https://github.com/deluan/contentful-migrate')
.commandDir('./commands')
require("yargs")
.usage(
"Telus version to manage your Contentful schema by creating incremental scripted changes\nFor more information visit https://github.com/deluan/contentful-migrate"
)
.commandDir("./commands")
.recommendCommands()
.demandCommand(1, 'Please provide a valid command from the list above')
.argv;
.demandCommand(1, "Please provide a valid command from the list above").argv;
Loading
Loading