Skip to content
Merged
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
46 changes: 27 additions & 19 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,23 +76,30 @@ then you can just run `npx bc model test --dry-run`.

## Custom Generator Overrides

You can override any of the generators by adding any of these files to a templates folder, and passing `--templates <folder>` to the cli:
- boats-rc.js
- component-index.js
- create.js
- delete.js
- index.js
- list.js
- model.js
- models.js
- pagination-model.js
- param.js
- path-index.js
- replace.js
- show.js
- update.js

Or, alternatively, exporting any of the following methods from a module (local file or node module):
You can override any of the generators by adding any files or exports to the templates option (`--templates <folder_or_lib>`).

Boats cli will try to import from the folling:

| file | export |
|---------------------|--------------------|
| boats-rc.js | getBoatsRc |
| component-index.js | getComponentIndex |
| create.js | getCreate |
| delete.js | getDelete |
| index.js | getIndex |
| list.js | getList |
| model.js | getModel |
| models.js | getModels |
| pagination-model.js | getPaginationModel |
| param.js | getParam |
| path-index.js | getPathIndex |
| replace.js | getReplace |
| show.js | getShow |
| update.js | getUpdate |

Multiple invocations of `-T, --templates` will merge / override the results of the templates, in the order supplied.

A module export might look like:
```js
exports.getBoatsRc = (opts, file) => { /* ... */ };
exports.getIndex = (opts, file) => { /* ... */ };
Expand All @@ -110,7 +117,7 @@ exports.getUpdate = (opts, file) => { /* ... */ };
exports.getReplace = (opts, file) => { /* ... */ };
```

for example, `templates/index.js` or `exports.getList`:
or overriding `path <name> --list`, a file `templates/list.js` or module with `exports.getList`:
```js
// @ts-check
const { toYaml } = require('@acrontum/boats-cli/dist/src/lib');
Expand All @@ -134,9 +141,10 @@ module.exports = (_globalOptions, file, pluralName, schemaRef, parameters) => {
});
};

// or exports.getList = (_globalOptions, file, pluralName, schemaRef, parameters) => { ... }
````

or disabling the default generator and instead creating 2 different files for models `templates/model.yml` or `exports.getModel`:
or disabling the default generator and instead creating 2 different files for models (`templates/model.yml` or `exports.getModel`):
```js
// @ts-check
const { toYaml } = require('@acrontum/boats-cli/dist/src/lib');
Expand Down
15 changes: 7 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export const cliArguments: Record<string, CliArg> = {
type: 'string',
short: 'T',
[argname]: 'TEMPLATES',
[description]: 'Folder or module containing template overrides',
[description]: 'Folder or module containing template overrides (can be invoked multiple times)',
},
force: { type: 'boolean', short: 'f', [description]: 'Overwrite existing files' },
'no-index': { type: 'boolean', short: 'I', [description]: 'Skip auto-creating index files, only models' },
Expand Down Expand Up @@ -338,7 +338,13 @@ export const cli = async (args: string[]): Promise<Record<string, GenerationTask

return {};
}

processTemplates = arg.inlineValue ? arg.value.slice(1) : arg.value;
const templates = await getTemplates(processTemplates);
if (templates === null) {
return {};
}
globalOptions.customTemplates = { ...globalOptions.customTemplates, ...templates };
} else if (arg.name === 'output' || arg.name === 'root-ref') {
if (!arg.value) {
help(1, `Parameter '--${arg.name}' requires a value`);
Expand Down Expand Up @@ -415,13 +421,6 @@ export const cli = async (args: string[]): Promise<Record<string, GenerationTask
globalOptions.output = relative('.', globalOptions.output);
}

if (processTemplates) {
const templates = await getTemplates(processTemplates);
if (templates !== null) {
globalOptions.customTemplates = templates;
}
}

if (!globalOptions['no-init']) {
tasks.push(
{ contents: () => getIndex(globalOptions, 'src/index.yml'), filename: 'src/index.yml' },
Expand Down
50 changes: 21 additions & 29 deletions test/custom-models.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('custom-models.spec.ts', async () => {
`),
);

const indexFile = await boats('test/output/custom/src/index.yml', 'test/output/custom/api.json');
const indexFile = await boats('test/output/custom/src/index.yml', 'test/output/custom/build/api.json');

assert.strictEqual(indexFile !== '', true, 'boats failed');
assert.strictEqual(await getFile(indexFile), await getFile('test/fixtures/spec/custom.json'), 'spec mismatch');
Expand Down Expand Up @@ -96,38 +96,30 @@ describe('custom-models.spec.ts', async () => {
--quiet
--output test/output/custom
-T test/fixtures/overrides/module.js
--templates test/fixtures/overrides
--templates test/fixtures/overrides/single-export/
`),
);

const files = await getAllFiles('test/output/custom/');
assert.deepStrictEqual(files, [
'test/output/custom/.boatsrc',
'test/output/custom/src/components/parameters/index.yml',
'test/output/custom/src/components/parameters/pathUserId.yml',
'test/output/custom/src/components/parameters/queryLimit.yml',
'test/output/custom/src/components/parameters/queryOffset.yml',
'test/output/custom/src/components/parameters/queryUserId.yml',
'test/output/custom/src/components/schemas/index.yml',
'test/output/custom/src/components/schemas/jwt/model.yml',
'test/output/custom/src/components/schemas/pagination/model.yml',
'test/output/custom/src/components/schemas/user/model.yml',
'test/output/custom/src/components/schemas/user/models.yml',
'test/output/custom/src/components/schemas/user/patch.yml',
'test/output/custom/src/components/schemas/user/post.yml',
'test/output/custom/src/components/schemas/user/put.yml',
'test/output/custom/src/index.yml',
'test/output/custom/src/paths/index.yml',
'test/output/custom/src/paths/users/get.yml',
'test/output/custom/src/paths/users/post.yml',
'test/output/custom/src/paths/users/{userId}/delete.yml',
'test/output/custom/src/paths/users/{userId}/get.yml',
'test/output/custom/src/paths/users/{userId}/patch.yml',
'test/output/custom/src/paths/users/{userId}/put.yml',
]);
const indexFile = await boats('test/output/custom/src/index.yml', 'test/output/custom/build/api.json');

assert.strictEqual(indexFile !== '', true, 'boats failed');
assert.strictEqual(await getFile(indexFile), await getFile('test/fixtures/spec/custom-multi.json'), 'spec mismatch');
});

await it('adds and overwrites templates when invoked multiple times', async () => {
assert.deepStrictEqual(await getAllFiles('test/output/custom/').catch(() => []), []);

for (const file of await getAllFiles('test/output/custom/')) {
assert.deepStrictEqual(await getFile(file), file.replace('test/output/custom/', ''));
}
await cli(
toArgv(`
path users/:userId -crudl --put
model jwt
model userId --type query
--quiet
--output test/output/custom
-T test/fixtures/overrides/module.js
`),
);
});

await it('outputs a meaningful error message', async () => {
Expand Down
29 changes: 29 additions & 0 deletions test/fixtures/overrides/single-export/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @ts-check

/** @type{import('../../../../').CustomTemplates['getModel']} */
module.exports = (_globalOptions, _file) => {
return `\
type: "object"
required:
- "id"
- "createdAt"
- "updatedAt"
properties:
id:
type: "string"
format: "uuid"
createdAt:
type: "string"
format: "date-time"
updatedAt:
type: "string"
format: "date-time"
name:
type: "string"
description: "Name of the thing, separated by dashes (-)"
example: "this-is-an-example"
minLength: 1
pattern: "\\\\S"
nullable: true
`;
};
18 changes: 18 additions & 0 deletions test/fixtures/overrides/single-export/models.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// @ts-check

const { toYaml } = require('../../../../dist/src/lib');

/** @type{import('../../../../').CustomTemplates['getModels']} */
module.exports = (_globalOptions, _file) => {
return toYaml({
type: 'object',
required: ['meta', 'data'],
properties: {
meta: { $ref: '#/components/schemas/Pagination' },
data: {
type: 'array',
items: { $ref: './model.yml' },
},
},
});
};
Loading