Skip to content

Commit a9fc0eb

Browse files
committed
feat(cli): auto-generate / update index.ts for exports
fix #1127 Signed-off-by: Taranveer Virk <[email protected]>
1 parent 37aba50 commit a9fc0eb

File tree

6 files changed

+139
-23
lines changed

6 files changed

+139
-23
lines changed

docs/site/todo-tutorial-controller.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@ logic will live_!
2020

2121
### Create your controller
2222

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

26-
- `index.ts` (export helper)
27-
- `todo.controller.ts`
26+
```sh
27+
lb4 controller
28+
? Controller class name: todo
29+
? What kind of controller would you like to generate? Empty Controller
30+
```
2831

2932
In addition to creating the handler functions themselves, we'll also be adding
3033
decorators that setup the routing as well as the expected parameters of incoming
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './ping.controller';

packages/cli/generators/controller/index.js

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const ArtifactGenerator = require('../../lib/artifact-generator');
99
const debug = require('../../lib/debug')('controller-generator');
1010
const inspect = require('util').inspect;
1111
const path = require('path');
12+
const chalk = require('chalk');
1213
const utils = require('../../lib/utils');
1314

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

38-
this.artifactInfo.outdir = path.resolve(
39+
this.artifactInfo.outDir = path.resolve(
3940
this.artifactInfo.rootDir,
4041
'controllers',
4142
);
@@ -185,10 +186,10 @@ module.exports = class ControllerGenerator extends ArtifactGenerator {
185186
// all of the templates!
186187
if (this.shouldExit()) return false;
187188
this.artifactInfo.name = utils.toClassName(this.artifactInfo.name);
188-
this.artifactInfo.filename =
189+
this.artifactInfo.outFile =
189190
utils.kebabCase(this.artifactInfo.name) + '.controller.ts';
190191
if (debug.enabled) {
191-
debug(`Artifact filename set to: ${this.artifactInfo.filename}`);
192+
debug(`Artifact output filename set to: ${this.artifactInfo.outFile}`);
192193
}
193194
// renames the file
194195
let template = 'controller-template.ts.ejs';
@@ -204,7 +205,7 @@ module.exports = class ControllerGenerator extends ArtifactGenerator {
204205
debug(`Using template at: ${source}`);
205206
}
206207
const dest = this.destinationPath(
207-
path.join(this.artifactInfo.outdir, this.artifactInfo.filename),
208+
path.join(this.artifactInfo.outDir, this.artifactInfo.outFile),
208209
);
209210

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

224-
end() {
225-
super.end();
226-
if (this.shouldExit()) return false;
227-
// logs a message if there is no file conflict
228-
if (
229-
this.conflicter.generationStatus[this.artifactInfo.filename] !== 'skip' &&
230-
this.conflicter.generationStatus[this.artifactInfo.filename] !==
231-
'identical'
232-
) {
233-
this.log();
234-
this.log(
235-
'Controller %s is now created in src/controllers/',
236-
this.artifactInfo.name,
237-
);
238-
}
225+
async end() {
226+
await super.end();
239227
}
240228
};

packages/cli/lib/artifact-generator.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
const BaseGenerator = require('./base-generator');
88
const debug = require('./debug')('artifact-generator');
99
const utils = require('./utils');
10+
const updateIndex = require('./update-index');
11+
const path = require('path');
12+
const chalk = require('chalk');
1013
const StatusConflicter = utils.StatusConflicter;
1114

1215
module.exports = class ArtifactGenerator extends BaseGenerator {
@@ -29,6 +32,10 @@ module.exports = class ArtifactGenerator extends BaseGenerator {
2932
}
3033
this.artifactInfo.name = this.args[0];
3134
this.artifactInfo.defaultName = 'new';
35+
this.artifactInfo.relPath = path.relative(
36+
this.destinationPath(),
37+
this.artifactInfo.outDir,
38+
);
3239
this.conflicter = new StatusConflicter(
3340
this.env.adapter,
3441
this.options.force,
@@ -102,4 +109,51 @@ module.exports = class ArtifactGenerator extends BaseGenerator {
102109
{globOptions: {dot: true}},
103110
);
104111
}
112+
113+
async end() {
114+
const success = super.end();
115+
if (!success) return false;
116+
117+
let generationStatus = true;
118+
// Check all files being generated to ensure they succeeded
119+
Object.entries(this.conflicter.generationStatus).forEach(([key, val]) => {
120+
if (val === 'skip' || val === 'identical') generationStatus = false;
121+
});
122+
123+
if (generationStatus) {
124+
/**
125+
* Update the index.ts in this.artifactInfo.outDir. Creates it if it
126+
* doesn't exist.
127+
* this.artifactInfo.outFile is what is exported from the file.
128+
*
129+
* Both those properties must be present for this to happen. Optionally,
130+
* this can be disabled even if the properties are present by setting:
131+
* this.artifactInfo.disableIndexUdpdate = true;
132+
*/
133+
if (
134+
this.artifactInfo.outDir &&
135+
this.artifactInfo.outFile &&
136+
!this.artifactInfo.disableIndexUpdate
137+
) {
138+
await updateIndex(this.artifactInfo.outDir, this.artifactInfo.outFile);
139+
// Output for users
140+
this.log(
141+
chalk.green(' update'),
142+
`${this.artifactInfo.relPath}/index.ts`,
143+
);
144+
}
145+
146+
// User Output
147+
this.log();
148+
this.log(
149+
utils.toClassName(this.artifactInfo.type),
150+
chalk.yellow(this.artifactInfo.name),
151+
'is now created in',
152+
`${this.artifactInfo.relPath}/`,
153+
);
154+
this.log();
155+
}
156+
157+
return false;
158+
}
105159
};

packages/cli/lib/update-index.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
2+
// Node module: @loopback/cli
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
const path = require('path');
7+
const util = require('util');
8+
const fs = require('fs');
9+
const appendFileAsync = util.promisify(fs.appendFile);
10+
11+
/**
12+
*
13+
* @param {String} dir The directory in which index.ts is to be updated/created
14+
* @param {*} file The new file to be exported from index.ts
15+
*/
16+
module.exports = async function(dir, file) {
17+
const indexFile = path.join(dir, 'index.ts');
18+
if (!file.endsWith('.ts')) {
19+
throw new Error(`${file} must be a TypeScript (.ts) file`);
20+
}
21+
const content = `export * from './${file.slice(0, -3)}';\n`;
22+
await appendFileAsync(indexFile, content);
23+
};
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright IBM Corp. 2017,2018. All Rights Reserved.
2+
// Node module: @loopback/cli
3+
// This file is licensed under the MIT License.
4+
// License text available at https://opensource.org/licenses/MIT
5+
6+
const updateIndex = require('../../lib/update-index');
7+
const assert = require('yeoman-assert');
8+
const path = require('path');
9+
const util = require('util');
10+
const fs = require('fs');
11+
const writeFileAsync = util.promisify(fs.writeFile);
12+
13+
const testlab = require('@loopback/testlab');
14+
const expect = testlab.expect;
15+
const TestSandbox = testlab.TestSandbox;
16+
17+
// Test Sandbox
18+
const SANDBOX_PATH = path.resolve(__dirname, '.sandbox');
19+
const sandbox = new TestSandbox(SANDBOX_PATH);
20+
const expectedFile = path.join(SANDBOX_PATH, 'index.ts');
21+
22+
describe('update-index unit tests', () => {
23+
beforeEach('reset sandbox', () => sandbox.reset());
24+
25+
it('creates index.ts when not present', async () => {
26+
await updateIndex(SANDBOX_PATH, 'test.ts');
27+
assert.file(expectedFile);
28+
assert.fileContent(expectedFile, /export \* from '.\/test';/);
29+
});
30+
31+
it('appends to existing index.ts when present', async () => {
32+
await writeFileAsync(
33+
path.join(SANDBOX_PATH, 'index.ts'),
34+
`export * from './first';\n`,
35+
);
36+
await updateIndex(SANDBOX_PATH, 'test.ts');
37+
assert.file(expectedFile);
38+
assert.fileContent(expectedFile, /export \* from '.\/first'/);
39+
assert.fileContent(expectedFile, /export \* from '.\/test'/);
40+
});
41+
42+
it('throws an error when given a non-ts file', async () => {
43+
expect(updateIndex(SANDBOX_PATH, 'test.js')).to.be.rejectedWith(
44+
/test.js must be a TypeScript \(.ts\) file/,
45+
);
46+
});
47+
});

0 commit comments

Comments
 (0)