Skip to content

Commit 9a9d4ea

Browse files
d-koppenhagenjorgeucano
authored andcommitted
Feat/schematic options (#149)
* feat(scully): pick commits for prompts picked commits from #100 * feat(scully): add options for @scullyio/init:markdown * fix(scully): fix x-prompt description * fix(scully): use yyyy-mm-dd format * feat(scully): provide options for @scullyio/init:post * fix(scully): fix filename for post * fix(scully): remove defaults from `route` and `sourceDir` * docs(scully): update options * refactor(scully): split into smaller chainable Rules * test(scully): add tests for ng-add schematic * test(scully): add more schematic tests * feat(scully): add `metaDataFile` option and tests * test(scully): add test for using `target` option * fix(scully): add correct Schema infos for add-blog * test(scully): fix test
1 parent 1708e5c commit 9a9d4ea

24 files changed

+700
-228
lines changed

docs/blog.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,16 @@ or
3131
ng g @scullyio/init:markdown --name="my text" --slug="my slug id"
3232
```
3333

34+
the following table shows all vailable options:
35+
36+
| option | description | default |
37+
| -------------- | ------------------------------------------------------------------------------ | ------------------------ |
38+
| `name` | define the name for the created module | 'blog' |
39+
| `slug` | define the name for the `:slug` | 'id' |
40+
| `routingScope` | set a routing scope (`Root` or `Child`) | Child |
41+
| `sourceDir` | define a source dir name (when not used, `name` is used instead) | value from `name` option |
42+
| `route` | define a route path before the `:slug` (when not used, `name` is used instead) | value from `name` option |
43+
3444
> If your markdown content will include code blocks, you may want the [code to be highlighted](utils.md).
3545
3646
## Generating New Blog Posts
@@ -41,7 +51,15 @@ To add a new blog post, run the following command.
4151
ng g @scullyio/init:post --name="This is my post"
4252
```
4353

44-
[Check how to integrate Scully with other tools.](utils.md)
54+
the following table shows all vailable options:
55+
56+
| option | description | default |
57+
| -------------- | ------------------------------------------------------ | --------- |
58+
| `name` | define the name for the created post | 'blog-X' |
59+
| `target` | define the target directory for the new post file | 'blog' |
60+
| `metaDataFile` | use a meta data yaml template from a file for the post | undefined |
61+
62+
[Check how to integrate Scully with other tools.](utils.md)
4563

4664
---
4765

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
title: override-me
2+
thumbnail: assets/images/default.jpg
3+
author: John Doe
4+
mail: John.Doe@example.com
5+
keywords:
6+
- angular
7+
- scully
8+
language: en

schematics/scully/package-lock.json

Lines changed: 42 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

schematics/scully/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@
3131
"@angular-devkit/core": "^9.0.0-rc.5",
3232
"@angular-devkit/schematics": "^9.0.0-rc.5",
3333
"@schematics/angular": "^9.0.0-rc.5",
34+
"js-yaml": "^3.13.1",
3435
"schematics-utilities": "^2.0.0"
3536
},
3637
"devDependencies": {
3738
"@types/jasmine": "^3.3.9",
39+
"@types/js-yaml": "^3.12.1",
3840
"@types/node": "^8.0.31",
3941
"jasmine": "^3.3.1",
4042
"typescript": "~3.5.3"
Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
1-
import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics';
1+
import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
22
import {RunSchematicTask} from '@angular-devkit/schematics/tasks';
3+
import {Schema} from './schema';
4+
import {Schema as MarkownSchema} from '../create-markdown/schema';
35

4-
export default function(options: any): Rule {
6+
export default function(options: Schema): Rule {
57
return (tree: Tree, context: SchematicContext) => {
6-
options.name = 'blog';
7-
options.slug = 'slug';
8-
context.addTask(new RunSchematicTask('create-markdown', options), []);
8+
const makrdownOptions: MarkownSchema = {
9+
name: 'blog',
10+
slug: 'slug',
11+
};
12+
13+
if (options.routingScope) {
14+
makrdownOptions.routingScope = options.routingScope;
15+
}
16+
context.addTask(new RunSchematicTask('create-markdown', makrdownOptions), []);
917
};
1018
}

schematics/scully/src/add-blog/schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "http://json-schema.org/schema",
3-
"id": "add-component",
3+
"id": "add-blog",
44
"title": "Scully add component schematic",
55
"type": "object",
66
"properties": {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface Schema {
2+
/**
3+
* The scope for the new routing module.
4+
*/
5+
routingScope?: 'Child' | 'Root';
6+
}

schematics/scully/src/add-post/index.ts

Lines changed: 42 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,54 @@
1-
import { Rule, SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics';
1+
import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics';
2+
import {strings} from '@angular-devkit/core';
3+
import fs = require('fs');
4+
import yaml = require('js-yaml');
5+
26
import {Schema} from './schema';
3-
import { strings } from '@angular-devkit/core';
47

58
export default function(options: Schema): Rule {
69
return (host: Tree, context: SchematicContext) => {
10+
const name = options.name;
11+
const nameDasherized = options.name ? strings.dasherize(options.name) : 'blog-X';
12+
const targetDasherized = options.target ? strings.dasherize(options.target) : 'blog';
13+
const filename = `./${targetDasherized}/${nameDasherized}.md`;
14+
15+
let metaData = {
16+
title: '',
17+
description: 'blog description',
18+
publish: false,
19+
};
720

8-
const name = options.name ? options.name : 'blog-X';
9-
const namD = options.name ? strings.dasherize(options.name) : 'blog-X';
10-
if (!host.exists(`./blog/${namD}.md`)) {
11-
host.create(`./blog/${namD}.md`,
12-
`---
13-
title: ${name}
14-
description: blog description
15-
publish: false
16-
---
21+
if (options.metaDataFile) {
22+
let metaDataContents = '';
23+
try {
24+
metaDataContents = fs.readFileSync(options.metaDataFile, 'utf8');
25+
} catch (e) {
26+
throw new SchematicsException(`File ${options.metaDataFile} not found`);
27+
}
28+
29+
try {
30+
// check if yaml is valid
31+
metaData = yaml.safeLoad(metaDataContents);
32+
context.logger.info(`✅️ Meta Data File ${options.metaDataFile} successfully parsed`);
33+
} catch (e) {
34+
throw new SchematicsException(`${options.metaDataFile} contains no valid yaml`);
35+
}
36+
}
37+
38+
// set title from option and override if alreay in metaDataFile template
39+
metaData.title = name;
40+
41+
if (!host.exists(filename)) {
42+
const content = `---
43+
${yaml.safeDump(metaData)}---
1744
1845
# ${name}
19-
`);
20-
context.logger.info(`✅️Blog ${name} file created`);
46+
`;
47+
host.create(filename, content);
48+
context.logger.info(`✅️ Blog ${filename} file created`);
2149
} else {
2250
// return name exist
23-
throw new SchematicsException(`${name} exist in your blog folder`);
51+
throw new SchematicsException(`${nameDasherized} exist in your ${targetDasherized} folder`);
2452
}
2553
};
2654
}
27-
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import {HostTree} from '@angular-devkit/schematics';
2+
import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing';
3+
import {getFileContent} from '@schematics/angular/utility/test';
4+
import * as path from 'path';
5+
6+
import {setupProject} from '../utils/test-utils';
7+
import {Schema} from './schema';
8+
9+
const collectionPath = path.join(__dirname, '../collection.json');
10+
const META_DATA_TEMPLATE_PATH = 'assets/meta-data-template.yml';
11+
12+
describe('add-post', () => {
13+
const schematicRunner = new SchematicTestRunner('scully-schematics', collectionPath);
14+
const project = 'foo';
15+
const defaultOptions: Schema = {
16+
name: 'Foo barBaz',
17+
};
18+
let appTree: UnitTestTree;
19+
const expectedFileName = '/blog/foo-bar-baz.md';
20+
21+
beforeEach(async () => {
22+
appTree = new UnitTestTree(new HostTree());
23+
appTree = await setupProject(appTree, schematicRunner, project);
24+
});
25+
26+
describe('when using the default options', () => {
27+
beforeEach(async () => {
28+
appTree = await schematicRunner.runSchematicAsync('post', defaultOptions, appTree).toPromise();
29+
});
30+
31+
it('should create a new dasherized post', () => {
32+
expect(appTree.files).toContain(expectedFileName);
33+
const mdFileContent = getFileContent(appTree, expectedFileName);
34+
expect(mdFileContent).toMatch(/title: Foo barBaz/g);
35+
expect(mdFileContent).toMatch(/description: blog description/g);
36+
expect(mdFileContent).toMatch(/publish: false/g);
37+
});
38+
});
39+
40+
describe('when using a different `target`', () => {
41+
beforeEach(async () => {
42+
appTree = await schematicRunner
43+
.runSchematicAsync('post', {...defaultOptions, target: 'foo/bar'}, appTree)
44+
.toPromise();
45+
});
46+
47+
it('should create a new dasherized post inside the target dir', () => {
48+
const expected = '/foo/bar/foo-bar-baz.md';
49+
expect(appTree.files).toContain(expected);
50+
const mdFileContent = getFileContent(appTree, expected);
51+
expect(mdFileContent).toMatch(/title: Foo barBaz/g);
52+
expect(mdFileContent).toMatch(/description: blog description/g);
53+
expect(mdFileContent).toMatch(/publish: false/g);
54+
});
55+
});
56+
57+
describe('when using `metaDataFile` option', () => {
58+
beforeEach(async () => {
59+
appTree = await schematicRunner
60+
.runSchematicAsync('post', {...defaultOptions, metaDataFile: META_DATA_TEMPLATE_PATH}, appTree)
61+
.toPromise();
62+
});
63+
64+
it('should add the meta data but keep title from options', () => {
65+
expect(appTree.files).toContain(expectedFileName);
66+
const mdFileContent = getFileContent(appTree, expectedFileName);
67+
expect(mdFileContent).toMatch(/title: Foo barBaz/g);
68+
expect(mdFileContent).toMatch(/thumbnail: assets\/images\/default\.jpg/g);
69+
expect(mdFileContent).toMatch(/author: John Doe/g);
70+
expect(mdFileContent).toMatch(/mail: John.Doe@example.com/g);
71+
expect(mdFileContent).toMatch(/keywords:\s+-\ angular\s+-\ scully/s);
72+
expect(mdFileContent).toMatch(/language: en/g);
73+
});
74+
});
75+
});

schematics/scully/src/add-post/schema.json

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,20 @@
77
"name": {
88
"type": "string",
99
"description": "add the title for the post",
10-
"x-prompt": "What title do you want to use for the post?"
10+
"x-prompt": "What title do you want to use for the post?",
11+
"default": "blog-X"
12+
},
13+
"target": {
14+
"type": "string",
15+
"description": "define the target directory for the new post file",
16+
"x-prompt": "What's the target folder for this post?",
17+
"default": "blog"
18+
},
19+
"metaDataFile": {
20+
"type": "string",
21+
"description": "use a meta data template file that's data will be added to the post",
22+
"default": ""
1123
}
1224
},
13-
"required": []
25+
"required": ["name"]
1426
}

0 commit comments

Comments
 (0)