diff --git a/docs/blog.md b/docs/blog.md index 5e257f405..3eccde44a 100644 --- a/docs/blog.md +++ b/docs/blog.md @@ -31,6 +31,16 @@ or ng g @scullyio/init:markdown --name="my text" --slug="my slug id" ``` +the following table shows all vailable options: + +| option | description | default | +| -------------- | ------------------------------------------------------------------------------ | ------------------------ | +| `name` | define the name for the created module | 'blog' | +| `slug` | define the name for the `:slug` | 'id' | +| `routingScope` | set a routing scope (`Root` or `Child`) | Child | +| `sourceDir` | define a source dir name (when not used, `name` is used instead) | value from `name` option | +| `route` | define a route path before the `:slug` (when not used, `name` is used instead) | value from `name` option | + > If your markdown content will include code blocks, you may want the [code to be highlighted](utils.md). ## Generating New Blog Posts @@ -41,7 +51,15 @@ To add a new blog post, run the following command. ng g @scullyio/init:post --name="This is my post" ``` -[Check how to integrate Scully with other tools.](utils.md) +the following table shows all vailable options: + +| option | description | default | +| -------------- | ------------------------------------------------------ | --------- | +| `name` | define the name for the created post | 'blog-X' | +| `target` | define the target directory for the new post file | 'blog' | +| `metaDataFile` | use a meta data yaml template from a file for the post | undefined | + +[Check how to integrate Scully with other tools.](utils.md) --- diff --git a/schematics/scully/assets/meta-data-template.yml b/schematics/scully/assets/meta-data-template.yml new file mode 100644 index 000000000..cd31dc109 --- /dev/null +++ b/schematics/scully/assets/meta-data-template.yml @@ -0,0 +1,8 @@ +title: override-me +thumbnail: assets/images/default.jpg +author: John Doe +mail: John.Doe@example.com +keywords: + - angular + - scully +language: en diff --git a/schematics/scully/package-lock.json b/schematics/scully/package-lock.json index 4be72152d..173bb8a7d 100644 --- a/schematics/scully/package-lock.json +++ b/schematics/scully/package-lock.json @@ -101,12 +101,20 @@ "@types/jasmine": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-3.5.0.tgz", - "integrity": "sha512-kGCRI9oiCxFS6soGKlyzhMzDydfcPix9PpTkr7h11huxOxhWwP37Tg7DYBaQ18eQTNreZEuLkhpbGSqVNZPnnw==" + "integrity": "sha512-kGCRI9oiCxFS6soGKlyzhMzDydfcPix9PpTkr7h11huxOxhWwP37Tg7DYBaQ18eQTNreZEuLkhpbGSqVNZPnnw==", + "dev": true + }, + "@types/js-yaml": { + "version": "3.12.1", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.1.tgz", + "integrity": "sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==", + "dev": true }, "@types/node": { "version": "8.10.59", "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", - "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==" + "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==", + "dev": true }, "@yarnpkg/lockfile": { "version": "1.1.0", @@ -167,6 +175,14 @@ "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -376,6 +392,11 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", @@ -579,6 +600,7 @@ "version": "3.5.0", "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.5.0.tgz", "integrity": "sha512-DYypSryORqzsGoMazemIHUfMkXM7I7easFaxAvNM3Mr6Xz3Fy36TupTrAOxZWN8MVKEU5xECv22J4tUQf3uBzQ==", + "dev": true, "requires": { "glob": "^7.1.4", "jasmine-core": "~3.5.0" @@ -587,7 +609,17 @@ "jasmine-core": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.5.0.tgz", - "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==" + "integrity": "sha512-nCeAiw37MIMA9w9IXso7bRaLl+c/ef3wnxsoSAlYrzS+Ot0zTG6nU8G/cIfGkqpkjX2wNaIW9RFG0TwIFnG6bA==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } }, "json-parse-better-errors": { "version": "1.0.2", @@ -1278,6 +1310,11 @@ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, "ssri": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", @@ -1372,7 +1409,8 @@ "typescript": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.3.tgz", - "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==" + "integrity": "sha512-ACzBtm/PhXBDId6a6sDJfroT2pOWt/oOnk4/dElG5G33ZL776N3Y6/6bKZJBFpd+b05F3Ct9qDjMeJmRWtE2/g==", + "dev": true }, "unique-filename": { "version": "1.1.1", diff --git a/schematics/scully/package.json b/schematics/scully/package.json index 76ba7366f..3b7f225ec 100644 --- a/schematics/scully/package.json +++ b/schematics/scully/package.json @@ -31,10 +31,12 @@ "@angular-devkit/core": "^9.0.0-rc.5", "@angular-devkit/schematics": "^9.0.0-rc.5", "@schematics/angular": "^9.0.0-rc.5", + "js-yaml": "^3.13.1", "schematics-utilities": "^2.0.0" }, "devDependencies": { "@types/jasmine": "^3.3.9", + "@types/js-yaml": "^3.12.1", "@types/node": "^8.0.31", "jasmine": "^3.3.1", "typescript": "~3.5.3" diff --git a/schematics/scully/src/add-blog/index.ts b/schematics/scully/src/add-blog/index.ts index 9dcc340d7..b8da109f8 100644 --- a/schematics/scully/src/add-blog/index.ts +++ b/schematics/scully/src/add-blog/index.ts @@ -1,10 +1,18 @@ -import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; +import {Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; import {RunSchematicTask} from '@angular-devkit/schematics/tasks'; +import {Schema} from './schema'; +import {Schema as MarkownSchema} from '../create-markdown/schema'; -export default function(options: any): Rule { +export default function(options: Schema): Rule { return (tree: Tree, context: SchematicContext) => { - options.name = 'blog'; - options.slug = 'slug'; - context.addTask(new RunSchematicTask('create-markdown', options), []); + const makrdownOptions: MarkownSchema = { + name: 'blog', + slug: 'slug', + }; + + if (options.routingScope) { + makrdownOptions.routingScope = options.routingScope; + } + context.addTask(new RunSchematicTask('create-markdown', makrdownOptions), []); }; } diff --git a/schematics/scully/src/add-blog/schema.json b/schematics/scully/src/add-blog/schema.json index 916d0fe43..29e020dcc 100644 --- a/schematics/scully/src/add-blog/schema.json +++ b/schematics/scully/src/add-blog/schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/schema", - "id": "add-component", + "id": "add-blog", "title": "Scully add component schematic", "type": "object", "properties": { diff --git a/schematics/scully/src/add-blog/schema.ts b/schematics/scully/src/add-blog/schema.ts new file mode 100644 index 000000000..c0f5d7f30 --- /dev/null +++ b/schematics/scully/src/add-blog/schema.ts @@ -0,0 +1,6 @@ +export interface Schema { + /** + * The scope for the new routing module. + */ + routingScope?: 'Child' | 'Root'; +} diff --git a/schematics/scully/src/add-post/index.ts b/schematics/scully/src/add-post/index.ts index 1137eb0d5..728b7c27c 100644 --- a/schematics/scully/src/add-post/index.ts +++ b/schematics/scully/src/add-post/index.ts @@ -1,27 +1,54 @@ -import { Rule, SchematicContext, SchematicsException, Tree } from '@angular-devkit/schematics'; +import {Rule, SchematicContext, SchematicsException, Tree} from '@angular-devkit/schematics'; +import {strings} from '@angular-devkit/core'; +import fs = require('fs'); +import yaml = require('js-yaml'); + import {Schema} from './schema'; -import { strings } from '@angular-devkit/core'; export default function(options: Schema): Rule { return (host: Tree, context: SchematicContext) => { + const name = options.name; + const nameDasherized = options.name ? strings.dasherize(options.name) : 'blog-X'; + const targetDasherized = options.target ? strings.dasherize(options.target) : 'blog'; + const filename = `./${targetDasherized}/${nameDasherized}.md`; + + let metaData = { + title: '', + description: 'blog description', + publish: false, + }; - const name = options.name ? options.name : 'blog-X'; - const namD = options.name ? strings.dasherize(options.name) : 'blog-X'; - if (!host.exists(`./blog/${namD}.md`)) { - host.create(`./blog/${namD}.md`, - `--- -title: ${name} -description: blog description -publish: false ---- + if (options.metaDataFile) { + let metaDataContents = ''; + try { + metaDataContents = fs.readFileSync(options.metaDataFile, 'utf8'); + } catch (e) { + throw new SchematicsException(`File ${options.metaDataFile} not found`); + } + + try { + // check if yaml is valid + metaData = yaml.safeLoad(metaDataContents); + context.logger.info(`✅️ Meta Data File ${options.metaDataFile} successfully parsed`); + } catch (e) { + throw new SchematicsException(`${options.metaDataFile} contains no valid yaml`); + } + } + + // set title from option and override if alreay in metaDataFile template + metaData.title = name; + + if (!host.exists(filename)) { + const content = `--- +${yaml.safeDump(metaData)}--- # ${name} -`); - context.logger.info(`✅️Blog ${name} file created`); +`; + host.create(filename, content); + context.logger.info(`✅️ Blog ${filename} file created`); } else { // return name exist - throw new SchematicsException(`${name} exist in your blog folder`); + throw new SchematicsException(`${nameDasherized} exist in your ${targetDasherized} folder`); } }; } - diff --git a/schematics/scully/src/add-post/index_spec.ts b/schematics/scully/src/add-post/index_spec.ts new file mode 100644 index 000000000..172fed362 --- /dev/null +++ b/schematics/scully/src/add-post/index_spec.ts @@ -0,0 +1,75 @@ +import {HostTree} from '@angular-devkit/schematics'; +import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; +import {getFileContent} from '@schematics/angular/utility/test'; +import * as path from 'path'; + +import {setupProject} from '../utils/test-utils'; +import {Schema} from './schema'; + +const collectionPath = path.join(__dirname, '../collection.json'); +const META_DATA_TEMPLATE_PATH = 'assets/meta-data-template.yml'; + +describe('add-post', () => { + const schematicRunner = new SchematicTestRunner('scully-schematics', collectionPath); + const project = 'foo'; + const defaultOptions: Schema = { + name: 'Foo barBaz', + }; + let appTree: UnitTestTree; + const expectedFileName = '/blog/foo-bar-baz.md'; + + beforeEach(async () => { + appTree = new UnitTestTree(new HostTree()); + appTree = await setupProject(appTree, schematicRunner, project); + }); + + describe('when using the default options', () => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematicAsync('post', defaultOptions, appTree).toPromise(); + }); + + it('should create a new dasherized post', () => { + expect(appTree.files).toContain(expectedFileName); + const mdFileContent = getFileContent(appTree, expectedFileName); + expect(mdFileContent).toMatch(/title: Foo barBaz/g); + expect(mdFileContent).toMatch(/description: blog description/g); + expect(mdFileContent).toMatch(/publish: false/g); + }); + }); + + describe('when using a different `target`', () => { + beforeEach(async () => { + appTree = await schematicRunner + .runSchematicAsync('post', {...defaultOptions, target: 'foo/bar'}, appTree) + .toPromise(); + }); + + it('should create a new dasherized post inside the target dir', () => { + const expected = '/foo/bar/foo-bar-baz.md'; + expect(appTree.files).toContain(expected); + const mdFileContent = getFileContent(appTree, expected); + expect(mdFileContent).toMatch(/title: Foo barBaz/g); + expect(mdFileContent).toMatch(/description: blog description/g); + expect(mdFileContent).toMatch(/publish: false/g); + }); + }); + + describe('when using `metaDataFile` option', () => { + beforeEach(async () => { + appTree = await schematicRunner + .runSchematicAsync('post', {...defaultOptions, metaDataFile: META_DATA_TEMPLATE_PATH}, appTree) + .toPromise(); + }); + + it('should add the meta data but keep title from options', () => { + expect(appTree.files).toContain(expectedFileName); + const mdFileContent = getFileContent(appTree, expectedFileName); + expect(mdFileContent).toMatch(/title: Foo barBaz/g); + expect(mdFileContent).toMatch(/thumbnail: assets\/images\/default\.jpg/g); + expect(mdFileContent).toMatch(/author: John Doe/g); + expect(mdFileContent).toMatch(/mail: John.Doe@example.com/g); + expect(mdFileContent).toMatch(/keywords:\s+-\ angular\s+-\ scully/s); + expect(mdFileContent).toMatch(/language: en/g); + }); + }); +}); diff --git a/schematics/scully/src/add-post/schema.json b/schematics/scully/src/add-post/schema.json index 5f2650210..f58a3e9cf 100644 --- a/schematics/scully/src/add-post/schema.json +++ b/schematics/scully/src/add-post/schema.json @@ -7,8 +7,20 @@ "name": { "type": "string", "description": "add the title for the post", - "x-prompt": "What title do you want to use for the post?" + "x-prompt": "What title do you want to use for the post?", + "default": "blog-X" + }, + "target": { + "type": "string", + "description": "define the target directory for the new post file", + "x-prompt": "What's the target folder for this post?", + "default": "blog" + }, + "metaDataFile": { + "type": "string", + "description": "use a meta data template file that's data will be added to the post", + "default": "" } }, - "required": [] + "required": ["name"] } diff --git a/schematics/scully/src/add-post/schema.ts b/schematics/scully/src/add-post/schema.ts index 6b7dd9502..d9331720c 100644 --- a/schematics/scully/src/add-post/schema.ts +++ b/schematics/scully/src/add-post/schema.ts @@ -1,3 +1,17 @@ +/** + * Scully ng-add-blog schematic + */ export interface Schema { - name: string; + /** + * add the title for the post + */ + name: string; + /** + * define the target directory for the new post file + */ + target?: string; + /** + * use a meta data template file that's data will be added to the post + */ + metaDataFile?: string; } diff --git a/schematics/scully/src/collection.json b/schematics/scully/src/collection.json index 73c41df6c..a08a20285 100644 --- a/schematics/scully/src/collection.json +++ b/schematics/scully/src/collection.json @@ -1,5 +1,6 @@ { "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json", + "id": "scully-schematics", "schematics": { "ng-add": { "description": "Add scully to the application.", diff --git a/schematics/scully/src/create-markdown/index.ts b/schematics/scully/src/create-markdown/index.ts index cd99f6858..4dd0abbb8 100644 --- a/schematics/scully/src/create-markdown/index.ts +++ b/schematics/scully/src/create-markdown/index.ts @@ -1,55 +1,70 @@ -import { Rule, Tree, url, applyTemplates, move, chain, SchematicContext } from '@angular-devkit/schematics'; -import { strings, normalize } from '@angular-devkit/core'; +import {Rule, Tree, url, applyTemplates, move, chain, SchematicContext} from '@angular-devkit/schematics'; +import {strings, normalize} from '@angular-devkit/core'; import {Schema as MyServiceSchema} from './schema'; -import {addRouteToModule, addRouteToScullyConfig, applyWithOverwrite, getPrefix, getSrc} from '../utils/utils'; +import { + addRouteToModule, + addRouteToScullyConfig, + applyWithOverwrite, + getPrefix, + getSrc, +} from '../utils/utils'; + +const SCULLY_CONF_FILE = '/scully.config.js'; +const ANGULAR_CONF_FILE = './angular.json'; export default function(options: MyServiceSchema): Rule { return (host: Tree, context: SchematicContext) => { try { - options.name = options.name ? options.name : 'blog'; - const name = options.name; - const nameD = strings.dasherize(options.name); + const sourceDir = getSrc(host); + const name = options.name ? options.name : 'blog'; + const nameDasherized = strings.dasherize(options.name); + const targetDirName = options.sourceDir + ? strings.dasherize(options.sourceDir) // use sourceDir when provided + : strings.dasherize(options.name); // fall back to name when not provided const date = new Date(); - const fullDay = `${date.getMonth() + 1}-${date.getDate()}-${date.getFullYear()}`; - const path = `./${nameD}/${fullDay}-${nameD}.md`; + // format yyyy-mm-dd + const fullDay = date.toISOString().substring(0, 10); + const path = `${targetDirName}/${fullDay}-${nameDasherized}.md`; if (!host.exists(path)) { - host.create(path, `--- + host.create( + path, + `--- title: This is the ${name} description: ${name} description publish: false --- # Page ${name} example -`); - context.logger.info(`✅ ${fullDay}-${nameD} file created`); +` + ); + context.logger.info(`✅ ${path} file created`); } - let scullyJson; + let scullyJs; try { - scullyJson = (host.read('/scully.config.js')).toString(); + scullyJs = host.read(SCULLY_CONF_FILE).toString(); } catch (e) { // for test in schematics - // tslint:disable-next-line:no-shadowed-variable - const srcFolder = getSrc(host); - scullyJson = `exports.config = { - projectRoot: "${srcFolder}/app", + scullyJs = `exports.config = { + projectRoot: "${getSrc(host)}/app", routes: { - '/demo/:id': { - type: 'fake', - numberOfPages: 100 - }, }, };`; } - options.slug = options.slug ? options.slug : 'id'; - const newScullyJson = addRouteToScullyConfig(scullyJson, {name, slug: options.slug, type: 'contentFolder'}); - host.overwrite(`/scully.config.js`, newScullyJson); - context.logger.info('✅️ Update scully.config.js'); - const srcFolder = getSrc(host); - options.path = options.path ? options.path : strings.dasherize(`${srcFolder}/app/${name}`); + const newScullyJs = addRouteToScullyConfig(scullyJs, { + name, + slug: options.slug, + type: 'contentFolder', + sourceDir, + route: options.route, + }); + host.overwrite(SCULLY_CONF_FILE, newScullyJs); + context.logger.info(`✅️ Update ${SCULLY_CONF_FILE}`); + + const pathName = strings.dasherize(`${sourceDir}/app/${name}`); let prefix = 'app'; - if (host.exists('./angular.json')) { - prefix = getPrefix(host.read('./angular.json').toString()); + if (host.exists(ANGULAR_CONF_FILE)) { + prefix = getPrefix(host.read(ANGULAR_CONF_FILE).toString()); addRouteToModule(host, options); } @@ -59,16 +74,12 @@ publish: false dasherize: strings.dasherize, name: options.name, slug: options.slug, - prefix + prefix, }), - move(normalize(options.path as string)) + move(normalize(pathName)), ]); - return chain([ - templateSource - ]); - - } catch (e) { } + return chain([templateSource]); + } catch (e) {} }; } - diff --git a/schematics/scully/src/create-markdown/index_spec.ts b/schematics/scully/src/create-markdown/index_spec.ts new file mode 100644 index 000000000..f3f71457e --- /dev/null +++ b/schematics/scully/src/create-markdown/index_spec.ts @@ -0,0 +1,40 @@ +import {HostTree} from '@angular-devkit/schematics'; +import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; +import {getFileContent} from '@schematics/angular/utility/test'; +import * as path from 'path'; + +import {setupProject} from '../utils/test-utils'; +import {Schema} from './schema'; + +const collectionPath = path.join(__dirname, '../collection.json'); + +describe('create-markdown', () => { + const schematicRunner = new SchematicTestRunner('scully-schematics', collectionPath); + const project = 'foo'; + const defaultOptions: Schema = { + name: 'blog', + slug: 'id', + }; + let appTree: UnitTestTree; + + beforeEach(async () => { + appTree = new UnitTestTree(new HostTree()); + appTree = await setupProject(appTree, schematicRunner, project); + }); + + describe('when using the default options', () => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematicAsync('md', defaultOptions, appTree).toPromise(); + }); + + it('should create the markdown file in the default directory', () => { + const dayString = new Date().toISOString().substring(0, 10); + const expectedFileName = `/blog/${dayString}-blog.md`; + expect(appTree.files).toContain(expectedFileName); + const mdFileContent = getFileContent(appTree, expectedFileName); + expect(mdFileContent).toMatch(/title: This is the blog/g); + expect(mdFileContent).toMatch(/description: blog/g); + expect(mdFileContent).toMatch(/# Page blog example/g); + }); + }); +}); diff --git a/schematics/scully/src/create-markdown/schema.json b/schematics/scully/src/create-markdown/schema.json index 1f70cdb0d..673a5d8bd 100644 --- a/schematics/scully/src/create-markdown/schema.json +++ b/schematics/scully/src/create-markdown/schema.json @@ -4,22 +4,34 @@ "title": "scully create markdown module schematic", "type": "object", "properties": { + "name": { + "type": "string", + "description": "add the name for the module", + "x-prompt": "What name do you want to use for the module?", + "default": "blog" + }, + "slug": { + "type": "string", + "description": "add the name for the :${slug}", + "x-prompt": "What slug do you want for the markdown file?", + "default": "id" + }, "routingScope": { "enum": ["Child", "Root"], "type": "string", "description": "The scope for the new routing module.", "default": "Child" }, - "name": { + "sourceDir": { "type": "string", - "description": "add the name for the folder and module", - "x-prompt": "What name do you want to use for the folder and module?" + "description": "add the name for the source dir to store the markdown files", + "x-prompt": "Where do you want to store your markdown files?" }, - "slug": { + "route": { "type": "string", - "description": "add the name for the :${slug}", - "x-prompt": "What slug do you want for the markdown file?" + "description": "define the route where your post will be available", + "x-prompt": "Under which route do you want your files to be requested?" } }, - "required": [] + "required": ["name", "slug"] } diff --git a/schematics/scully/src/create-markdown/schema.ts b/schematics/scully/src/create-markdown/schema.ts index c62ba62a8..5687890b3 100644 --- a/schematics/scully/src/create-markdown/schema.ts +++ b/schematics/scully/src/create-markdown/schema.ts @@ -1,5 +1,22 @@ export interface Schema { + /** + * add the name for the module + */ name: string; - path?: string; - slug?: string; + /** + * add the name for the :${slug} + */ + slug: string; + /** + * The scope for the new routing module. + */ + routingScope?: 'Child' | 'Root'; + /** + * add the name for the source dir to store the markdown files + */ + sourceDir?: string; + /** + * define the route where your post will be available + */ + route?: string; } diff --git a/schematics/scully/src/ng-add/index.ts b/schematics/scully/src/ng-add/index.ts index f9405f7d1..ade4f4123 100644 --- a/schematics/scully/src/ng-add/index.ts +++ b/schematics/scully/src/ng-add/index.ts @@ -1,6 +1,4 @@ -import { - chain, Rule, SchematicContext, Tree, -} from '@angular-devkit/schematics'; +import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; import {addPackageToPackageJson} from './package-config'; import {Schema} from './schema'; import {scullyVersion, scullyComponentVersion} from './version-names'; @@ -8,94 +6,103 @@ import {addModuleImportToRootModule, getProjectFromWorkspace, getWorkspace} from import {NodePackageInstallTask, RunSchematicTask} from '@angular-devkit/schematics/tasks'; import {getSrc} from '../utils/utils'; -export default function(options: Schema): Rule { - return (host: Tree, context: SchematicContext) => { - addPackageToPackageJson(host, '@scullyio/scully', `${scullyVersion}`); - addPackageToPackageJson(host, '@scullyio/ng-lib', `${scullyComponentVersion}`); - context.logger.info('✅️ Added dependency'); -// @ts-ignore - try { - // @ts-ignore - const workspace = getWorkspace(host); - // @ts-ignore - const project = getProjectFromWorkspace(workspace, options.project); - // import the httpClient we need for the plugins - addModuleImportToRootModule(host, 'HttpClientModule', '@angular/common/http', project); - context.logger.info('✅️ Import HttpClientModule into root module'); - } catch (e) { } +export default (options: Schema): Rule => { + return chain([ + addDependencies(options), + addHttpClientModule(options), + addPolyfill(options), + injectIdleService(options), + runScullySchmeatic(options), + ]); +}; - // add new polyfills - const srcFolder = getSrc(host); - // @ts-ignore - let polyfills = host.read(`${srcFolder}/polyfills.ts`).toString(); - if (polyfills.includes('SCULLY IMPORTS')) { - context.logger.info('⚠️️ Skipping polyfills.ts'); - } else { - polyfills = `${polyfills}\n/*************************************************************************************************** - \n* SCULLY IMPORTS - \n*/ - \n// tslint:disable-next-line: align \nimport 'zone.js/dist/task-tracking';`; - host.overwrite(`${srcFolder}/polyfills.ts`, polyfills); - } +const addDependencies = (options: Schema) => (tree: Tree, context: SchematicContext) => { + addPackageToPackageJson(tree, '@scullyio/scully', `${scullyVersion}`); + addPackageToPackageJson(tree, '@scullyio/ng-lib', `${scullyComponentVersion}`); + context.logger.info('✅️ Added dependency'); +}; + +const addHttpClientModule = (options: Schema) => (tree: Tree, context: SchematicContext) => { + try { + const workspace = getWorkspace(tree); + const project = getProjectFromWorkspace(workspace); + // import the httpClient we need for the plugins + addModuleImportToRootModule(tree, 'HttpClientModule', '@angular/common/http', project); + context.logger.info('✅️ Import HttpClientModule into root module'); + } catch (e) {} +}; - try { - // inject idleService - const appComponent = host.read(`${srcFolder}/app/app.component.ts`).toString(); - if (appComponent.includes('IdleMonitorService')) { - context.logger.info(`⚠️️ Skipping ${srcFolder}/app/app.component.ts`); +const addPolyfill = (options: Schema) => (tree: Tree, context: SchematicContext) => { + let polyfills = tree.read(`${getSrc(tree)}/polyfills.ts`).toString(); + if (polyfills.includes('SCULLY IMPORTS')) { + context.logger.info('⚠️ Skipping polyfills.ts'); + } else { + polyfills = + polyfills + + `\n/*************************************************************************************************** +\n* SCULLY IMPORTS +\n*/ +\n// tslint:disable-next-line: align \nimport 'zone.js/dist/task-tracking';`; + tree.overwrite(`${getSrc(tree)}/polyfills.ts`, polyfills); + } +}; + +const injectIdleService = (options: Schema) => (tree: Tree, context: SchematicContext) => { + try { + const appComponentPath = `${getSrc(tree)}/app/app.component.ts`; + const appComponent = tree.read(appComponentPath).toString(); + if (appComponent.includes('IdleMonitorService')) { + context.logger.info(`⚠️️ Skipping ${appComponentPath}`); + } else { + const idleImport = `import {IdleMonitorService} from '@scullyio/ng-lib';`; + // add + const idImport = `${idleImport} \n ${appComponent}`; + const idle = 'private idle: IdleMonitorService'; + let output = ''; + // check if exist + if (idImport.search(/constructor/) === -1) { + // add if no exist the constructor + const add = ` \n constructor (${idle}) { } \n`; + const position = + idImport.search(/export class AppComponent {/g) + 'export class AppComponent {'.length; + output = [idImport.slice(0, position), add, idImport.slice(position)].join(''); } else { - const idleImport = 'import {IdleMonitorService, TransferStateService} from \'@scullyio/ng-lib\';'; - // add - const idImport = `${idleImport} \n ${appComponent}`; - const idle = 'private idle: IdleMonitorService, private transferState: TransferStateService'; - let output = ''; - // check if exist - if (idImport.search(/constructor/).toString() === '-1') { - // add if no exist the constructor - const add = ` \n constructor (${idle}) { } \n`; - const position = - idImport.search(/export class AppComponent {/g) + 'export class AppComponent {'.length; + const coma = haveMoreInjects(idImport); + const add = `${idle}${coma}`; + if (idImport.search(/constructor \(/) === -1) { + const position = idImport.search(/constructor\(/g) + 'constructor('.length; output = [idImport.slice(0, position), add, idImport.slice(position)].join(''); } else { - const coma = haveMoreInjects(idImport); - const add = `${idle}${coma}`; - if (idImport.search(/constructor \(/).toString() === '-1') { - const position = idImport.search(/constructor\(/g) + 'constructor('.length; - output = [idImport.slice(0, position), add, idImport.slice(position)].join(''); - } else { - const position = idImport.search(/constructor \(/g) + 'constructor ('.length; - output = [idImport.slice(0, position), add, idImport.slice(position)].join(''); - } - } - host.overwrite(`${srcFolder}/app/app.component.ts`, output); - } - - function haveMoreInjects(fullComponent: string) { - const match = '\(([^()]*(private|public)[^()]*)\)'; - // @ts-ignore - if (fullComponent.search(match).toString !== '-1') { - return ','; + const position = idImport.search(/constructor \(/g) + 'constructor ('.length; + output = [idImport.slice(0, position), add, idImport.slice(position)].join(''); } - return ''; } - - } catch (e) { - console.log('error in idle service'); + tree.overwrite(appComponentPath, output); } - const nextRules: Rule[] = []; - // tslint:disable-next-line:triple-equals - if (options.blog === true) { - // @ts-ignore - nextRules.push(context.addTask(new RunSchematicTask('blog', options), [])); + function haveMoreInjects(fullComponent: string) { + const match = '(([^()]*(private|public)[^()]*))'; + if (fullComponent.search(match) !== -1) { + return ','; + } + return ''; } - // tslint:disable-next-line:no-shadowed-variable - nextRules.push((tree: Tree, context: SchematicContext) => { - const installTaskId = context.addTask(new NodePackageInstallTask()); - context.addTask(new RunSchematicTask('scully', options), [installTaskId]); - }); + } catch (e) { + console.log('error in idle service'); + } +}; - return chain(nextRules); +const runScullySchmeatic = (options: Schema) => (tree: Tree, context: SchematicContext) => { + const nextRules: Rule[] = []; + if (options.blog === true) { + // @ts-ignore + nextRules.push(context.addTask(new RunSchematicTask('blog', options), [])); + } + // tslint:disable-next-line:no-shadowed-variable + nextRules.push((tree: Tree, context: SchematicContext) => { + const installTaskId = context.addTask(new NodePackageInstallTask()); + context.addTask(new RunSchematicTask('scully', options), [installTaskId]); + }); - }; -} + chain(nextRules); +}; diff --git a/schematics/scully/src/ng-add/index_spec.ts b/schematics/scully/src/ng-add/index_spec.ts new file mode 100644 index 000000000..bdb3287e0 --- /dev/null +++ b/schematics/scully/src/ng-add/index_spec.ts @@ -0,0 +1,63 @@ +import {HostTree} from '@angular-devkit/schematics'; +import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; +import {getFileContent} from '@schematics/angular/utility/test'; +import * as path from 'path'; + +import {setupProject} from '../utils/test-utils'; +import {Schema} from './schema'; +import {scullyVersion, scullyComponentVersion} from './version-names'; + +const collectionPath = path.join(__dirname, '../collection.json'); +const PACKAGE_JSON_PATH = '/package.json'; + +describe('ng-add schematic', () => { + const schematicRunner = new SchematicTestRunner('scully-schematics', collectionPath); + const project = 'foo'; + const defaultOptions: Schema = { + blog: true, + }; + let appTree: UnitTestTree; + + beforeEach(async () => { + appTree = new UnitTestTree(new HostTree()); + appTree = await setupProject(appTree, schematicRunner, project); + }); + + describe('when using the default options', () => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematicAsync('ng-add', defaultOptions, appTree).toPromise(); + }); + + it('should add dependencies', () => { + const packageJson = JSON.parse(getFileContent(appTree, PACKAGE_JSON_PATH)); + const {dependencies} = packageJson; + expect(appTree.files).toContain(PACKAGE_JSON_PATH); + expect(dependencies['@scullyio/ng-lib']).toEqual(scullyVersion); + expect(dependencies['@scullyio/scully']).toEqual(scullyComponentVersion); + }); + + it('should add dependencies', () => { + const packageJson = JSON.parse(getFileContent(appTree, PACKAGE_JSON_PATH)); + const {dependencies} = packageJson; + expect(appTree.files).toContain(PACKAGE_JSON_PATH); + expect(dependencies['@scullyio/ng-lib']).toEqual(scullyVersion); + expect(dependencies['@scullyio/scully']).toEqual(scullyComponentVersion); + }); + + it('should add the HttpClientModule', () => { + const appModuleContent = getFileContent(appTree, 'src/app/app.module.ts'); + expect(appModuleContent).toMatch(/import.*HttpClientModule.*from.*\@angular\/common\/http/g); + expect(appModuleContent).toMatch(/imports.*:.*\[.*HttpClientModule\s+\]/s); + }); + + it('should add the polyfill', () => { + const appModuleContent = getFileContent(appTree, 'src/polyfills.ts'); + expect(appModuleContent).toMatch(/import.*zone\.js\/dist\/task-tracking/g); + }); + + it('should inject the idle service into AppComponent', () => { + const appModuleContent = getFileContent(appTree, 'src/app/app.component.ts'); + expect(appModuleContent).toMatch(/constructor*.*.private idle: IdleMonitorService/s); + }); + }); +}); diff --git a/schematics/scully/src/ng-add/package-config.ts b/schematics/scully/src/ng-add/package-config.ts index 6493c1025..63aa9dd32 100644 --- a/schematics/scully/src/ng-add/package-config.ts +++ b/schematics/scully/src/ng-add/package-config.ts @@ -7,49 +7,41 @@ */ import {Tree} from '@angular-devkit/schematics'; +import {overwritePackageJson, getPackageJson, PackageJsonConfigPart} from '../utils/utils'; /** * Sorts the keys of the given object. * @returns A new object instance with sorted keys */ -function sortObjectByKeys(obj: object) { - // @ts-ignore - return Object.keys(obj).sort().reduce((result, key) => (result[key] = obj[key]) && result, {}); +function sortObjectByKeys(obj: PackageJsonConfigPart) { + // @ts-ignore + return Object.keys(obj) + .sort() + .reduce((result: any, key: string) => (result[key] = obj[key]) && result, {}); } /** Adds a package to the package.json in the given host tree. */ -export function addPackageToPackageJson(host: Tree, pkg: string, version: string): Tree { +export function addPackageToPackageJson(hostTree: Tree, pkg: string, version: string): Tree { + const packageJson = getPackageJson(hostTree); - if (host.exists('package.json')) { - const sourceText = host.read('package.json')!.toString('utf-8'); - const json = JSON.parse(sourceText); - - if (!json.dependencies) { - json.dependencies = {}; - } - - if (!json.dependencies[pkg]) { - json.dependencies[pkg] = version; - json.dependencies = sortObjectByKeys(json.dependencies); - } - - host.overwrite('package.json', JSON.stringify(json, null, 2)); + if (!packageJson.dependencies) { + packageJson.dependencies = {}; } - return host; + if (!packageJson.dependencies[pkg]) { + packageJson.dependencies[pkg] = version; + packageJson.dependencies = sortObjectByKeys(packageJson.dependencies); + } + return overwritePackageJson(hostTree, packageJson); } /** Gets the version of the specified package by looking at the package.json in the given tree. */ export function getPackageVersionFromPackageJson(tree: Tree, name: string): string | null { - if (!tree.exists('package.json')) { - return null; - } - - const packageJson = JSON.parse(tree.read('package.json')!.toString('utf8')); + const packageJson = getPackageJson(tree); if (packageJson.dependencies && packageJson.dependencies[name]) { return packageJson.dependencies[name]; } return null; -} \ No newline at end of file +} diff --git a/schematics/scully/src/scully/index.ts b/schematics/scully/src/scully/index.ts index 442d1efcf..671e06817 100644 --- a/schematics/scully/src/scully/index.ts +++ b/schematics/scully/src/scully/index.ts @@ -1,11 +1,10 @@ import {Rule, SchematicContext, Tree, SchematicsException} from '@angular-devkit/schematics'; +import {Schema} from './schema'; import {getSrc} from '../utils/utils'; // for now we dont have any option for use // @ts-ignore -export function scully(options: any): Rule { - +export function scully(options: Schema): Rule { return (tree: Tree, context: SchematicContext) => { - // project workspace data const workspaceConfigBuffer = tree.read('angular.json'); if (!workspaceConfigBuffer) { @@ -14,7 +13,9 @@ export function scully(options: any): Rule { // modify package json for support npm commands const content: Buffer | null = tree.read(`/package.json`); let jsonContent; - if (content) { jsonContent = JSON.parse(content.toString()); } + if (content) { + jsonContent = JSON.parse(content.toString()); + } /* tslint:disable:no-string-literal */ jsonContent.scripts['scully'] = 'scully'; /* tslint:enable:no-string-literal */ @@ -25,15 +26,16 @@ export function scully(options: any): Rule { // add config file if (!tree.exists('./scully.config.js')) { const srcFolder = getSrc(tree); - tree.create('./scully.config.js', + tree.create( + './scully.config.js', `exports.config = { projectRoot: "${srcFolder}/app", routes: { } -};`); +};` + ); } - // end return + // end return }; - } diff --git a/schematics/scully/src/scully/index_spec.ts b/schematics/scully/src/scully/index_spec.ts index fad780353..2b91b5c9d 100644 --- a/schematics/scully/src/scully/index_spec.ts +++ b/schematics/scully/src/scully/index_spec.ts @@ -1,16 +1,43 @@ -import { Tree } from '@angular-devkit/schematics'; -import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import {HostTree} from '@angular-devkit/schematics'; +import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; +import {getFileContent} from '@schematics/angular/utility/test'; import * as path from 'path'; +import {setupProject} from '../utils/test-utils'; +import {Schema} from './schema'; const collectionPath = path.join(__dirname, '../collection.json'); +const PACKAGE_JSON_PATH = '/package.json'; +const SCULLY_PATH = '/scully.config.js'; +describe('scully schematic', () => { + const schematicRunner = new SchematicTestRunner('scully-schematics', collectionPath); + const project = 'foo'; + const defaultOptions: Schema = { + project: 'foo', + }; + let appTree: UnitTestTree; -describe('scully', () => { - it('works', () => { - const runner = new SchematicTestRunner('schematics', collectionPath); - const tree = runner.runSchematic('scully', {}, Tree.empty()); + beforeEach(async () => { + appTree = new UnitTestTree(new HostTree()); + appTree = await setupProject(appTree, schematicRunner, project); + }); + + describe('when using the default options', () => { + beforeEach(async () => { + appTree = await schematicRunner.runSchematicAsync('scully', defaultOptions, appTree).toPromise(); + }); + + it('add config file', () => { + expect(appTree.files).toContain(SCULLY_PATH); + }); - expect(tree.files).toEqual([]); + it(`should modify the 'package.json'`, () => { + const packageJson = JSON.parse(getFileContent(appTree, PACKAGE_JSON_PATH)); + const {scripts} = packageJson; + expect(appTree.files).toContain(PACKAGE_JSON_PATH); + expect(scripts.scully).toEqual('scully'); + expect(scripts['scully:serve']).toEqual('scully serve'); + }); }); }); diff --git a/schematics/scully/src/scully/schema.ts b/schematics/scully/src/scully/schema.ts new file mode 100644 index 000000000..e76548202 --- /dev/null +++ b/schematics/scully/src/scully/schema.ts @@ -0,0 +1,3 @@ +export interface Schema { + project: string; +} diff --git a/schematics/scully/src/utils/test-utils.ts b/schematics/scully/src/utils/test-utils.ts new file mode 100644 index 000000000..5565b88a4 --- /dev/null +++ b/schematics/scully/src/utils/test-utils.ts @@ -0,0 +1,25 @@ +import {SchematicTestRunner, UnitTestTree} from '@angular-devkit/schematics/testing'; + +export async function setupProject(tree: UnitTestTree, schematicRunner: SchematicTestRunner, name: string) { + tree = await schematicRunner + .runExternalSchematicAsync('@schematics/angular', 'workspace', { + name: 'workspace', + version: ' ^9.0.0-rc.4', + newProjectRoot: '', + }) + .toPromise(); + + tree = await schematicRunner + .runExternalSchematicAsync( + '@schematics/angular', + 'application', + { + name, + projectRoot: '', + }, + tree + ) + .toPromise(); + + return tree; +} diff --git a/schematics/scully/src/utils/utils.ts b/schematics/scully/src/utils/utils.ts index 1e2bbcb36..738206802 100644 --- a/schematics/scully/src/utils/utils.ts +++ b/schematics/scully/src/utils/utils.ts @@ -1,37 +1,62 @@ -import {apply, forEach, mergeWith, Rule, SchematicContext, Source, Tree} from '@angular-devkit/schematics'; +import { + apply, + forEach, + mergeWith, + Rule, + SchematicContext, + Source, + Tree, + SchematicsException, +} from '@angular-devkit/schematics'; import {normalize, strings} from '@angular-devkit/core'; +import {join} from 'path'; -import { buildRelativePath } from '@schematics/angular/utility/find-module'; -import { addRouteDeclarationToModule } from '@schematics/angular/utility/ast-utils'; +import {buildRelativePath} from '@schematics/angular/utility/find-module'; +import {addRouteDeclarationToModule} from '@schematics/angular/utility/ast-utils'; import ts = require('@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript'); import {InsertChange} from '@schematics/angular/utility/change'; import {ModuleOptions} from '@schematics/angular/utility/find-module'; +const PACKAGE_JSON = 'package.json'; interface Data { name: string; type: string; slug: string; + sourceDir?: string; + route?: string; +} + +export interface PackageJson { + dependencies: PackageJsonConfigPart; + devDependencies: PackageJsonConfigPart; + scripts?: PackageJsonConfigPart; +} + +export interface PackageJsonConfigPart { + [key: string]: T; } export function addRouteToScullyConfig(scullyConfigJs: string, data: Data) { - const addRoute = `\n '/${strings.dasherize(data.name)}/:${data.slug}': { + const baseRoute = data.route ? strings.dasherize(data.route) : strings.dasherize(data.name); + const contentDirectoy = data.sourceDir ? strings.dasherize(data.sourceDir) : strings.dasherize(data.name); + const addRoute = `\n '/${baseRoute}/:${data.slug}': { type: '${data.type}', ${data.slug}: { - folder: "./${strings.dasherize(data.name)}" + folder: "./${contentDirectoy}" } },`; - let output; - if (+scullyConfigJs.search(/routes: \{/g) > 0) { - const position = +scullyConfigJs.search(/routes: \{/g) + 'routes: {'.length; - output = [scullyConfigJs.slice(0, position), addRoute, scullyConfigJs.slice(position)].join(''); - } else if (+scullyConfigJs.search(/routes:\{/g) > 0) { - const position = +scullyConfigJs.search(/routes:\{/g) + 'routes:{'.length; - output = [scullyConfigJs.slice(0, position), addRoute, scullyConfigJs.slice(position)].join(''); - } else { - console.log('Scully can\'t found the scully.config.js'); - return scullyConfigJs; - } - return output; + let output; + if (+scullyConfigJs.search(/routes: \{/g) > 0) { + const position = +scullyConfigJs.search(/routes: \{/g) + 'routes: {'.length; + output = [scullyConfigJs.slice(0, position), addRoute, scullyConfigJs.slice(position)].join(''); + } else if (+scullyConfigJs.search(/routes:\{/g) > 0) { + const position = +scullyConfigJs.search(/routes:\{/g) + 'routes:{'.length; + output = [scullyConfigJs.slice(0, position), addRoute, scullyConfigJs.slice(position)].join(''); + } else { + console.log(`Scully can't find the scully.config.js`); + return scullyConfigJs; + } + return output; } /* @@ -53,27 +78,24 @@ function needComa(fullText: string, matchs: string[]) { } */ - export function applyWithOverwrite(source: Source, rules: Rule[]): Rule { return (tree: Tree, context: SchematicContext) => { const rule = mergeWith( apply(source, [ ...rules, - forEach((fileEntry) => { + forEach(fileEntry => { if (tree.exists(fileEntry.path)) { tree.overwrite(fileEntry.path, fileEntry.content); return null; } return fileEntry; }), - - ]), + ]) ); return rule(tree, context); }; } - export function getPrefix(angularjson: string) { const angularJSON = JSON.parse(angularjson); const prefixs = []; @@ -84,7 +106,7 @@ export function getPrefix(angularjson: string) { if (prefixs.length > 1) { // TODO: ask for prefix we need return prefixs[0].prefix; - } else if (prefixs.length === 1) { + } else if (prefixs.length === 1) { return prefixs[0].prefix; } } @@ -104,28 +126,25 @@ export function addRouteToModule(host: Tree, options: any) { const addDeclaration = addRouteDeclarationToModule( ts.createSourceFile(path, sourceText, ts.ScriptTarget.Latest, true), path, - buildRoute(options, 'app.module'), + buildRoute(options, 'app.module', options.route) ) as InsertChange; const recorder = host.beginUpdate(path); recorder.insertLeft(addDeclaration.pos, addDeclaration.toAdd); host.commitUpdate(recorder); - } -function buildRoute(options: ModuleOptions, modulePath: string) { +function buildRoute(options: ModuleOptions, modulePath: string, route?: string) { const relativeModulePath = buildRelativeModulePath(options, modulePath); const moduleName = `${strings.classify(options.name)}Module`; const loadChildren = `() => import('${relativeModulePath}').then(m => m.${moduleName})`; - return `{ path: '${options.name}', loadChildren: ${loadChildren} }`; + const basePath = route ? strings.dasherize(route) : strings.dasherize(options.name); + return `{ path: '${basePath}', loadChildren: ${loadChildren} }`; } function buildRelativeModulePath(options: ModuleOptions, modulePath: string): string { // tslint:disable-next-line:no-shadowed-variable - const importModulePath = normalize(`/${options.name}/` - + strings.dasherize(options.name) - + '.module', - ); + const importModulePath = normalize(`/${options.name}/` + strings.dasherize(options.name) + '.module'); return buildRelativePath(modulePath, importModulePath); } @@ -136,3 +155,48 @@ export function getSrc(host: Tree) { const defaultProject = angularConfig.defaultProject; return angularConfig.projects[defaultProject].sourceRoot; } + +class FileNotFoundException extends Error { + constructor(fileName: string) { + const message = `File ${fileName} not found!`; + super(message); + } +} + +export const getJsonFile = (tree: Tree, path: string): T => { + const file = tree.get(path); + if (!file) { + throw new FileNotFoundException(path); + } + + try { + const content = JSON.parse(file.content.toString()); + + return content as T; + } catch (e) { + throw new SchematicsException(`File ${path} could not be parsed!`); + } +}; + +export const getFileContents = (tree: Tree, filePath: string): string => { + const buffer = tree.read(filePath) || ''; + + return buffer.toString(); +}; + +export const getPackageJson = (tree: Tree, workingDirectory: string = ''): PackageJson => { + const url = join(workingDirectory, PACKAGE_JSON); + + return getJsonFile(tree, url); +}; + +export const overwritePackageJson = ( + tree: Tree, + content: PackageJson, + workingDirectory: string = '' +): Tree => { + const url = join(workingDirectory, PACKAGE_JSON); + + tree.overwrite(url, JSON.stringify(content, null, 2)); + return tree; +};