Skip to content

Commit

Permalink
feat(cli): add new command import-lb3-model (EXPERIMENTAL)
Browse files Browse the repository at this point in the history
  • Loading branch information
bajtos committed Sep 27, 2019
1 parent 1c9709a commit 2e465e6
Show file tree
Hide file tree
Showing 16 changed files with 2,355 additions and 124 deletions.
113 changes: 113 additions & 0 deletions docs/site/Importing-LB3-models.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
lang: en
title: 'Importing models from LoopBack 3 projects'
keywords: LoopBack 4.0, Migration
sidebar: lb4_sidebar
permalink: /doc/en/lb4/Importing-LB3-models.html
---

## Synopsis

To simplify migration from LoopBack 3, LoopBack 4 provides a CLI tool to import
LoopBack 3 models into your LoopBack 4 project.

{% include warning.html content="
This command is experimental and not feature-complete yet.
See the list of known limitations below.
" %}

## Overview

Import one or more models from your LB 3.x application by running
`lb4 import-lb3-models` command.

### Arguments

`lb3app`: Path to the directory containing your LoopBack 3.x application.

{% include important.html content="
The generator loads the application via `require()`, it does not
support applications that are unable to boot (throw errors at startup).
" %}

### Options

`outDir`: Directory where to write the generated source file. Default:
`src/models`

## Known limitations

{% include note.html content="
Please up-vote the tracking GitHub issue for scenarios that are important to
your project. It will help us to better prioritize which limitations to remove
first.
" %}

### Connector-specific metadata in property definitions is not imported

_The tracking GitHub issue:
[loopback-next#3810](https://github.com/strongloop/loopback-next/issues/3810)_

Workaround: Add this metadata manually to the generated file.

### Nested properties are not upgraded

_The tracking GitHub issue:
[loopback-next#3811](https://github.com/strongloop/loopback-next/issues/3811)_

When a property is defined with a complex object type, the nested property
definitions are not converted from LB3 to LB4 format.

Workaround: Fix the generated definition manually.

### Model relations are not imported

_The tracking GitHub issue:
[loopback-next#3812](https://github.com/strongloop/loopback-next/issues/3812)_

Workaround: define relational metadata & navigational properties manually.

### Models inheriting from custom base class

_The tracking GitHub issue:
[loopback-next#3813](https://github.com/strongloop/loopback-next/issues/3813)_

Models inheriting from application-specific models (including LB3 built-in
models like `User`) cannot be imported yet.

Workaround:

1. Modify your LB3 model to inherit from `Model`, `PersistedModel` or
`KeyValueModel`.

2. Import the model to LB4

3. Update the imported model to inherit for the desired application-specific
model.

### MongoDB's `ObjectID` type

The tracking GitHub issue:
[loopback-next#3814](https://github.com/strongloop/loopback-next/issues/3814).

For models attached to MongoDB datasource, the imported LB4 model contains
incorrect definition of the primary key property of `ObjectID` type.

As a workaround, you can change the property definition from:

```ts
@property({
type: ObjectID;
})
id: ObjectID;
```

to:

```ts
@property({
type: 'string',
mongodb: {dataType: 'ObjectID'}
})
id: string;
```
4 changes: 4 additions & 0 deletions docs/site/sidebars/lb4_sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,10 @@ children:
url: DataSource-generator.html
output: 'web, pdf'

- title: 'Import models from LoopBack 3'
url: Importing-LB3-models.html
output: 'web, pdf'

- title: 'Model generator'
url: Model-generator.html
output: 'web, pdf'
Expand Down
7 changes: 5 additions & 2 deletions docs/site/tables/lb4-artifact-commands.html
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
<td><a href="DataSource-generator.html">DataSource generator</a></td>
</tr>

<tr>
<td><code>lb4 import-lb3-models</code></td>
<td>Import one or more LoopBack 3 models to a LoopBack 4 application</td>
<td><a href="Importing-LB3-models.html">Importer for LoopBack 3 models</a></td>
</tr>
<tr>
<td><code>lb4 model</code></td>
<td>Add a new model to a LoopBack 4 application</td>
Expand Down Expand Up @@ -70,7 +75,5 @@
<td>Generate interceptors</td>
<td><a href="Interceptor-generator.html">Global interceptor generator</a></td>
</tr>


</tbody>
</table>
162 changes: 162 additions & 0 deletions packages/cli/generators/import-lb3-models/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
// Copyright IBM Corp. 2019. All Rights Reserved.
// Node module: @loopback/cli
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

const chalk = require('chalk');
const path = require('path');
const BaseGenerator = require('../../lib/base-generator');
const modelUtils = require('../../lib/model-discoverer');
const debug = require('../../lib/debug')('import-lb3-models');
const utils = require('../../lib/utils');
const {loadLb3App} = require('./lb3app-loader');
const {importLb3ModelDefinition} = require('./migrate-model');
const {canImportModelName} = require('./model-names');

module.exports = class Lb3ModelImporter extends BaseGenerator {
constructor(args, opts) {
super(args, opts);

this.argument('lb3app', {
type: String,
required: true,
description:
'Path to your LoopBack 3.x application. ' +
'This can be a project directory (e.g. "my-lb3-app") or ' +
'the server file (e.g. "my-lb3-app/server/server.js").',
});

this.option('outDir', {
type: String,
description: 'Directory where to write the generated source file',
default: 'src/models',
});
}

async processOptions() {
this.sourceAppDir = this.args[0];
this.artifactInfo.outDir = this.options.outDir;
this.artifactInfo.relPath = path.relative(
this.destinationPath(),
this.artifactInfo.outDir,
);
return super.setOptions();
}

async logExperimentalStatus() {
this.log(
chalk.red(`
WARNING: This command is experimental and not feature-complete yet.
Learn more at https://loopback.io/doc/en/lb4/Importing-LB3-models.html
`),
);
}

/**
* Ensure CLI is being run in a LoopBack 4 project.
*/
checkLoopBackProject() {
if (this.shouldExit()) return;
return super.checkLoopBackProject();
}

async loadTheApp() {
this.lb3app = await loadLb3App(this.sourceAppDir);
this.modelRegistry = this.lb3app.registry.modelBuilder.models;
}

async promptModels() {
const modelNames = Object.keys(this.modelRegistry).filter(
canImportModelName,
);

debug('Available LB3 models', modelNames);

const prompts = [
{
name: 'modelNames',
message: 'Select models to import:',
type: 'checkbox',
choices: modelNames,
validate: result => !!result.length,
// TODO: add a CLI flag to supply these names programmatically
},
];

const answers = await this.prompt(prompts);
debug('Models chosen:', answers.modelNames);
this.modelNames = answers.modelNames;
}

async migrateSelectedModels() {
if (this.shouldExit()) return;
this.modelFiles = [];

try {
for (const name of this.modelNames) {
await this._migrateSingleModel(name);
}
} catch (err) {
if (err.exit) {
this.exit(err.message);
} else {
throw err;
}
}
}

async _migrateSingleModel(name) {
utils.logClassCreation('model', 'models', name, this.log.bind(this));
const modelCtor = this.modelRegistry[name];
if (typeof modelCtor !== 'function') {
const availableModels = Object.keys(this.modelRegistry)
.filter(canImportModelName)
.join(', ');

this.exit(
`Unknown model name ${name}. Available models: ${availableModels}.`,
);
return;
}

const templateData = importLb3ModelDefinition(
modelCtor,
this.log.bind(this),
);
debug('LB4 model data', templateData);

const fileName = utils.getModelFileName(name);
const fullTargetPath = path.resolve(this.artifactInfo.relPath, fileName);
debug('Model %s output file', name, fullTargetPath);

this.copyTemplatedFiles(
modelUtils.MODEL_TEMPLATE_PATH,
fullTargetPath,
templateData,
);

this.modelFiles.push(fileName);
}

/**
* Iterate through all the models we have discovered and scaffold
*/
async scaffold() {
if (this.shouldExit()) return;
}

async end() {
if (this.shouldExit() || !this._isGenerationSuccessful()) {
await super.end();
return;
}

for (const f of this.modelFiles) {
await this._updateIndexFile(this.artifactInfo.outDir, f);
}

await super.end();
}
};
31 changes: 31 additions & 0 deletions packages/cli/generators/import-lb3-models/lb3app-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright IBM Corp. 2019. All Rights Reserved.
// Node module: @loopback/cli
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

'use strict';

const debug = require('../../lib/debug')('import-lb3-models');
const path = require('path');
const pEvent = require('p-event');

module.exports = {
loadLb3App,
};

// TODO: do we want to share this code with `Lb3AppBooter.loadAndBootTheApp`?
async function loadLb3App(dir) {
debug('Loading LB3 app from', dir);
const lb3App = require(path.resolve(dir));

debug(
'If your LB3 app does not boot correctly then make sure it is using loopback-boot version 3.2.1 or higher.',
);

if (lb3App.booting) {
debug(' waiting for boot process to finish');
// Wait until the legacy LB3 app boots
await pEvent(lb3App, 'booted');
}
return lb3App;
}
Loading

0 comments on commit 2e465e6

Please sign in to comment.