Skip to content

Commit

Permalink
feat(cli): auto-generate / update index.ts for exports
Browse files Browse the repository at this point in the history
fix #1127

Signed-off-by: Taranveer Virk <[email protected]>
  • Loading branch information
virkt25 committed Jun 1, 2018
1 parent 37aba50 commit a9fc0eb
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 23 deletions.
11 changes: 7 additions & 4 deletions docs/site/todo-tutorial-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ logic will live_!

### Create your controller

So, let's create a controller to handle our Todo routes. Inside the
`src/controllers` directory create the following two files:
So, let's create a controller to handle our Todo routes. You can create an empty
Controller using the CLI as follows:

- `index.ts` (export helper)
- `todo.controller.ts`
```sh
lb4 controller
? Controller class name: todo
? What kind of controller would you like to generate? Empty Controller
```

In addition to creating the handler functions themselves, we'll also be adding
decorators that setup the routing as well as the expected parameters of incoming
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ping.controller';
26 changes: 7 additions & 19 deletions packages/cli/generators/controller/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const ArtifactGenerator = require('../../lib/artifact-generator');
const debug = require('../../lib/debug')('controller-generator');
const inspect = require('util').inspect;
const path = require('path');
const chalk = require('chalk');
const utils = require('../../lib/utils');

// Exportable constants
Expand All @@ -35,7 +36,7 @@ module.exports = class ControllerGenerator extends ArtifactGenerator {
// XXX(kjdelisle): These should be more extensible to allow custom paths
// for each artifact type.

this.artifactInfo.outdir = path.resolve(
this.artifactInfo.outDir = path.resolve(
this.artifactInfo.rootDir,
'controllers',
);
Expand Down Expand Up @@ -185,10 +186,10 @@ module.exports = class ControllerGenerator extends ArtifactGenerator {
// all of the templates!
if (this.shouldExit()) return false;
this.artifactInfo.name = utils.toClassName(this.artifactInfo.name);
this.artifactInfo.filename =
this.artifactInfo.outFile =
utils.kebabCase(this.artifactInfo.name) + '.controller.ts';
if (debug.enabled) {
debug(`Artifact filename set to: ${this.artifactInfo.filename}`);
debug(`Artifact output filename set to: ${this.artifactInfo.outFile}`);
}
// renames the file
let template = 'controller-template.ts.ejs';
Expand All @@ -204,7 +205,7 @@ module.exports = class ControllerGenerator extends ArtifactGenerator {
debug(`Using template at: ${source}`);
}
const dest = this.destinationPath(
path.join(this.artifactInfo.outdir, this.artifactInfo.filename),
path.join(this.artifactInfo.outDir, this.artifactInfo.outFile),
);

if (debug.enabled) {
Expand All @@ -221,20 +222,7 @@ module.exports = class ControllerGenerator extends ArtifactGenerator {
return;
}

end() {
super.end();
if (this.shouldExit()) return false;
// logs a message if there is no file conflict
if (
this.conflicter.generationStatus[this.artifactInfo.filename] !== 'skip' &&
this.conflicter.generationStatus[this.artifactInfo.filename] !==
'identical'
) {
this.log();
this.log(
'Controller %s is now created in src/controllers/',
this.artifactInfo.name,
);
}
async end() {
await super.end();
}
};
54 changes: 54 additions & 0 deletions packages/cli/lib/artifact-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
const BaseGenerator = require('./base-generator');
const debug = require('./debug')('artifact-generator');
const utils = require('./utils');
const updateIndex = require('./update-index');
const path = require('path');
const chalk = require('chalk');
const StatusConflicter = utils.StatusConflicter;

module.exports = class ArtifactGenerator extends BaseGenerator {
Expand All @@ -29,6 +32,10 @@ module.exports = class ArtifactGenerator extends BaseGenerator {
}
this.artifactInfo.name = this.args[0];
this.artifactInfo.defaultName = 'new';
this.artifactInfo.relPath = path.relative(
this.destinationPath(),
this.artifactInfo.outDir,
);
this.conflicter = new StatusConflicter(
this.env.adapter,
this.options.force,
Expand Down Expand Up @@ -102,4 +109,51 @@ module.exports = class ArtifactGenerator extends BaseGenerator {
{globOptions: {dot: true}},
);
}

async end() {
const success = super.end();
if (!success) return false;

let generationStatus = true;
// Check all files being generated to ensure they succeeded
Object.entries(this.conflicter.generationStatus).forEach(([key, val]) => {
if (val === 'skip' || val === 'identical') generationStatus = false;
});

if (generationStatus) {
/**
* Update the index.ts in this.artifactInfo.outDir. Creates it if it
* doesn't exist.
* this.artifactInfo.outFile is what is exported from the file.
*
* Both those properties must be present for this to happen. Optionally,
* this can be disabled even if the properties are present by setting:
* this.artifactInfo.disableIndexUdpdate = true;
*/
if (
this.artifactInfo.outDir &&
this.artifactInfo.outFile &&
!this.artifactInfo.disableIndexUpdate
) {
await updateIndex(this.artifactInfo.outDir, this.artifactInfo.outFile);
// Output for users
this.log(
chalk.green(' update'),
`${this.artifactInfo.relPath}/index.ts`,
);
}

// User Output
this.log();
this.log(
utils.toClassName(this.artifactInfo.type),
chalk.yellow(this.artifactInfo.name),
'is now created in',
`${this.artifactInfo.relPath}/`,
);
this.log();
}

return false;
}
};
23 changes: 23 additions & 0 deletions packages/cli/lib/update-index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: @loopback/cli
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

const path = require('path');
const util = require('util');
const fs = require('fs');
const appendFileAsync = util.promisify(fs.appendFile);

/**
*
* @param {String} dir The directory in which index.ts is to be updated/created
* @param {*} file The new file to be exported from index.ts
*/
module.exports = async function(dir, file) {
const indexFile = path.join(dir, 'index.ts');
if (!file.endsWith('.ts')) {
throw new Error(`${file} must be a TypeScript (.ts) file`);
}
const content = `export * from './${file.slice(0, -3)}';\n`;
await appendFileAsync(indexFile, content);
};
47 changes: 47 additions & 0 deletions packages/cli/test/unit/update-index.unit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
// Node module: @loopback/cli
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT

const updateIndex = require('../../lib/update-index');
const assert = require('yeoman-assert');
const path = require('path');
const util = require('util');
const fs = require('fs');
const writeFileAsync = util.promisify(fs.writeFile);

const testlab = require('@loopback/testlab');
const expect = testlab.expect;
const TestSandbox = testlab.TestSandbox;

// Test Sandbox
const SANDBOX_PATH = path.resolve(__dirname, '.sandbox');
const sandbox = new TestSandbox(SANDBOX_PATH);
const expectedFile = path.join(SANDBOX_PATH, 'index.ts');

describe('update-index unit tests', () => {
beforeEach('reset sandbox', () => sandbox.reset());

it('creates index.ts when not present', async () => {
await updateIndex(SANDBOX_PATH, 'test.ts');
assert.file(expectedFile);
assert.fileContent(expectedFile, /export \* from '.\/test';/);
});

it('appends to existing index.ts when present', async () => {
await writeFileAsync(
path.join(SANDBOX_PATH, 'index.ts'),
`export * from './first';\n`,
);
await updateIndex(SANDBOX_PATH, 'test.ts');
assert.file(expectedFile);
assert.fileContent(expectedFile, /export \* from '.\/first'/);
assert.fileContent(expectedFile, /export \* from '.\/test'/);
});

it('throws an error when given a non-ts file', async () => {
expect(updateIndex(SANDBOX_PATH, 'test.js')).to.be.rejectedWith(
/test.js must be a TypeScript \(.ts\) file/,
);
});
});

0 comments on commit a9fc0eb

Please sign in to comment.