@apostrophecms/content-upgrader
A tool to migrate your content from Apostrophe 2.x to Apostrophe 3.x. That is, it creates a new database in the A3 format, and copies over the uploaded media. This tool does not upgrade your source code. However notes on some of the relevant code changes you'll need are provided below. See also Coming from Apostrophe 2.x, especially the Breaking Changes section.
Stability
This is an alpha release. We have not done our own acceptance testing on the output yet. It may be useful to you in A2 migrating sites. Your bug reports and contributions are appreciated.
Limitations
- As stated this is a content migration tool, not a code migration tool. You will need to make code changes manually to convert an A2 project.
- Users and groups are not migrated. This is because the user roles of A3 differ in design from the permissions groups of A2 and we wish to avoid creating any security issues. You should create new accounts on the A3 project or arrive at your own migration strategy.
- A2 has a built-in
apostrophe-images
"slideshow" widget type, while A3 only has a built-in single-image@apostrophecms/image
widget type. By defaultapostrophe-images
will be upgraded to@apostrophecms/image
, with only the first image present in each. However you can use themapWidgetTypes
option, documented below, to override this mapping during the upgrade. - Since A3 does not have a built-in cropping feature yet, there is currently no accommodation for it in A3's
@apostrophecms/image
widget type. However an attempt is made to carry over the cropping data in the format which is expected to work in A3 in the near future.
Installation
This tool installs as a module inside your existing A2 project. This is necessary to gain access to information such as the schemas of your existing piece and page and widget types.
cd my-existing-a2-project
npm install @apostrophecms/content-upgrader
Configuration
Edit app.js
of your A2 project. Add the module to the modules
section:
modules: {
// ... all of your existing A2 modules here ...
'@apostrophecms/content-upgrader': {}
}
Then create lib/modules/@apostrophecms/content-upgrader/index.js
. Here you can optionally address any content transformations and set the default locale, which is important even if you have no immediate plans to localize your site in other languages.
Mapping locales
In A3, "workflow" is always present, and the default locale is en
. In A2, the default locale of the workflow module is default
if no other configuration is done.
By default, this module's mapLocale
option will do the right thing to ensure that typical A2 content is reachable after the migration to A3, but you can adjust this option if needed. here is the default setting:
// In lib/modules/@apostrophecms/content-upgrader/index.js
module.exports = {
mapLocales: {
default: 'en'
}
};
Transforming doc types
In A2, the names of piece type modules themselves and the name
option configured for the module were often different. By convention the piece type module name was plural and the name
option was singular. The name
option is what is used in the database to set the type
property of each piece.
Yes, this was confusing. That's why in A3, the name
option no longer exists, and the name of the module and the type
property in the database are always the same. Also, by convention, module names in A3 are singular.
What does this mean for us when we upgrade the content? It means that most of the time, in our A3 source code we need to change the module name to singular and remove its name
option, but we don't need to change the type
in the database, because the new module's name will likely match it.
However, if you do want to remap the type
in the database, perhaps because you're choosing a different name for your module in A3, you can do it like this:
module.exports = {
mapDocTypes: {
'old-name': 'new-name',
...
}
}
old-name
must match the name
option from the old A2 module, not the module name from A2. new-name
must match the module name in your new A3 project, which will also be used for the type
property.
Providing a transformation function
Usually just remapping the name is enough. However if you have designed a new schema for a doc type in your A3 project and you want to migrate the A2 data to that new format, you can supply a transformation function:
module.exports = {
mapDocTypes: {
// Async functions are allowed and will be awaited
'old-name': async (doc) => {
return {
...doc,
// We've decided to change the name
type: 'new-name',
// We've changed to a single string field for the address
address: `${doc.street} ${doc.city}, ${doc.state}`
};
}
}
}
Transforming widget types
Just like doc types, in A2 widget type
properties were set via the name
option of the widget module in question. This was set automatically in most cases based on the module name, with the -widgets
part removed.
In A3 it is similar, but the name
option is no longer supported, and the suffix removed from the module name is -widget
.
For most widgets this means you should not need to remap the type names. You can just rename the module from -widgets
to -widget
.
If you choose though, you can rename the types like this:
module.exports = {
mapWidgetTypes: {
'old-name': 'new-name',
...
}
}
Transformation functions and pieces-widgets
Keep in mind that **A3 does not have a direct equivalent to apostrophe-pieces-widgets
. So when you upgrade your code, you'll likely change those widget modules to extend @apostrophecms/widget-type
and just use a relationship
schema field to select pieces. The catch is moving the data. A migration function can help you do that:
module.exports = {
mapWidgetTypes: {
// Async functions are allowed and will be awaited
'old-name': async (doc) => {
return {
...doc,
// We've decided to change the name
type: 'new-name',
// The old widget extended apostrophe-pieces-widgets, which stores
// widget ids in a pieceIds property. The new widget has a
// relationship called _products, which stores them in a
// productsIds property
productsIds: doc.pieceIds
};
}
}
}
apostrophe-pieces-widgets: "most recent" and "by tag" views
The apostrophe-pieces-widgets
module also supported "all" and "by tag" displays. Since these were rarely used, they have not been included in A3. If you need to migrate the "all" functionality, we recommend using an async component to display "all" (typically most recent) pieces in a custom widget. The "by tag" functionality can be addressed in a similar way, however see the note on tags in A3.
by
property of the pieces-widget. You're not limited to mapping a 2.x widget type to just one new widget type.
Tags in A3
In A2, there is always an apostrophe-tags
module to manage tags, and every piece has an array of tag names in its tags
property. These all come from the same namespace.
In A3 there is no such type. Instead, you can create new piece types as tags for other piece types and use relationship
fields to connect your pieces to them. This avoids an explosion of poorly-curated tags and keeps them relevant to the right kind of content.
Many A2 sites don't really use tags. However if you do, keep in mind that the tags
property remains in the database after the upgrade. Although you could use a global transformation function to convert these to a relationship (see below), it may be easier to do that with a migration in your A3 code after the initial transition to A3.
Global transformation functions
Most of the time it makes sense to write a transformation function for each doc or widget type. But if you need to address something for every doc and widget, you can write a global transformation function:
module.exports = {
async transformDoc(doc) {
// This function is invoked for every doc type
return {
...doc,
// changed properties here
};
},
async transformWidget(widget) {
// This widget is invoked for every widget type
return {
...widget,
// changed properties here
};
}
}
Note that as before, transformation functions must return a doc or widget, as appropriate, unless you want it to be removed in the upgrade. You can modify the original but you must return it if you wish to keep it.
Running the upgrade
You'll need a new A3 project to copy the content to. Specify the new project folder and the MongoDB URI of the new A3 database. If you are running MongoDB locally, which is typical during development, you can specify:
mongodb://localhost:27017/your-new-database-name
shortName
setting in app.js
, for avoidance of any possible confusion. Also make sure your A3 project is not already running, for instance in another terminal window.
node app @apostrophecms/content-upgrader:upgrade --a3-db=mongodb://localhost:27017/your-new-database-name
What about the media files?
The media files themselves don't need to change in the transition to A3.
So you can manually copy the public/uploads
folder from the A2 project to the A3 project, or use rsync
. If you are using uploadfs to store your media in S3 your procedure will vary.
This tool may automatically copy the public/uploadfs
folder in a future update.
Options
--a3-db
Required. This must be the MongoDB URI of your new A3 project. It will be cleared and overwritten. Currently there is no support for merging upgrade content with an existing A3 database.
--drop
Optional. If at least one Apostrophe doc exists in the new A3 database, the task will exit with an error message unless this option is passed.
Next steps
After the content migration, you'll be ready to test out your A3 project. Existing local accounts should work properly at this point. The extent to which page and piece templates and settings work will depend on how complete your code upgrade work is.