Skip to content
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
7 changes: 7 additions & 0 deletions utils/migrate/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
STAGING_API_KEY_CONTENT=
STAGING_API_KEY_ADMIN=
STAGING_API_URL=

PRODUCTION_API_KEY_CONTENT=
PRODUCTION_API_KEY_ADMIN=
PRODUCTION_API_URL=
6 changes: 6 additions & 0 deletions utils/migrate/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
*.DS_Store
tmp/*
!tmp/.keep
.env*
!.env.example
12 changes: 12 additions & 0 deletions utils/migrate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Ruby Central Ghost Migrator

The purpose of this package is to copy data from a staging Ghost environment to a production Ghost environment.

## Setup

1. The migration script reads the `slugs` key from `./config/settings.json` to round up a list of posts to port over to production. Make sure that array is up to date for your needs.
1. Create a `.env` file using the `.env.example` file as a guide. You will need to follow [Ghost's integration instructions](https://ghost.org/docs/admin-api/) to retrieve your API keys.

## How to run a migration

In this directory, run the command `npm run migrate`. Then verify that the production site has the pages you expect.
24 changes: 24 additions & 0 deletions utils/migrate/config/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"slugs": [
"about-hero-body",
"about-hero-button",
"about-history-sectionone-body",
"about-history-sectionone-button",
"about-history-sectionthree-body",
"about-history-sectiontwo-body",
"about-position-body",
"about-team-directors-header",
"about-team-header",
"about-team-staff-header",
"footer-socials",
"home-community",
"home-hero-body",
"home-hero-button-left",
"home-hero-button-right",
"home-opensource",
"home-opensource-button",
"home-pillars-button",
"home-pillars-conferences",
"home-support"
]
}
76 changes: 76 additions & 0 deletions utils/migrate/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* eslint-disable no-console */

import { readFileSync, rmSync, writeFileSync } from 'fs';
import { join } from 'path';
import 'dotenv/config';
import GhostAdminAPI from '@tryghost/admin-api';

const STAGING = 'staging';
const PRODUCTION = 'production';

const clientStaging = new GhostAdminAPI({
url: process.env.STAGING_API_URL,
key: process.env.STAGING_API_KEY_ADMIN,
version: 'v5.126.0',
});

const clientProduction = new GhostAdminAPI({
url: process.env.PRODUCTION_API_URL,
key: process.env.PRODUCTION_API_KEY_ADMIN,
version: 'v5.126.0',
});

const getPages = (target) => {
const normalizedTarget = target.toLowerCase();
const client =
normalizedTarget === STAGING ? clientStaging : clientProduction;
const dataPath = join(process.cwd(), `tmp/${normalizedTarget}.json`);
const settingsPath = join(process.cwd(), 'config/settings.json');
const settings = JSON.parse(readFileSync(settingsPath));
const targetSlugs = settings.slugs.join(',');

console.log(`Fetching data from ${normalizedTarget} ...\n\n`);

return new Promise((resolve, reject) => {
client.pages
.browse({ filter: `slug:[${targetSlugs}]`, limit: 1000 })
.then((pages) => {
const jsonString = `{ "pages": ${JSON.stringify(pages)} }`;
writeFileSync(dataPath, jsonString, { flag: 'a' });
resolve();
})
.catch((e) => reject(e));
});
};

const cleanTmpFile = (target) => {
const relativePath = `tmp/${target.toLowerCase()}.json`;

console.log(`Removing "${relativePath}"...\n\n`);
rmSync(join(process.cwd(), relativePath));
};

const moveToProduction = () => {
const stagingDataPath = join(process.cwd(), 'tmp/staging.json');
const stagingData = JSON.parse(readFileSync(stagingDataPath)).pages;

console.log(`Adding page copies from ${STAGING} to ${PRODUCTION}...\n\n`);

return new Promise((resolve) => {
stagingData.forEach((stagingPage) => {
clientProduction.pages.add({
lexical: stagingPage.lexical,
status: 'published',
title: stagingPage.title,
published_at: stagingPage.published_at,
});
resolve();
});
});
};

getPages(STAGING).then(() => {
moveToProduction().then(() => {
cleanTmpFile(STAGING);
});
});
Loading