diff --git a/angular.json b/angular.json index c2392a42..6530ce4e 100644 --- a/angular.json +++ b/angular.json @@ -575,6 +575,31 @@ } } } + }, + "packages-schematics": { + "root": "packages/schematics", + "sourceRoot": "packages/schematics/src", + "projectType": "library", + "prefix": "sp", + "architect": { + "build": { + "builder": "@nrwl/builders:run-commands", + "options": { + "commands": [ + { "command": "node_modules/.bin/tsc -p packages/schematics/tsconfig.lib.json" }, + { "command": "cp packages/schematics/.npmignore packages/schematics/README.md packages/schematics/package.json packages/schematics/dist" }, + { "command": "cp packages/schematics/src/collection.json packages/schematics/src/migration.json packages/schematics/dist/src" } + ] + } + }, + "test": { + "builder": "@nrwl/builders:jest", + "options": { + "jestConfig": "packages/schematics/jest.config.js", + "tsConfig": "packages/schematics/tsconfig.spec.json" + } + } + } } }, "defaultProject": "sparkles", @@ -594,6 +619,9 @@ "unitTestRunner": "jest", "e2eTestRunner": "cypress" }, + "@nrwl/schematics:component": { + "styleext": "scss" + }, "@nrwl/schematics:node-application": { "framework": "express" } diff --git a/commitlint.config.js b/commitlint.config.js index a3ed4e73..fccb0a78 100644 --- a/commitlint.config.js +++ b/commitlint.config.js @@ -12,7 +12,8 @@ module.exports = { 'reframed', 'shared', 'styles', - 'testing' + 'testing', + 'schematics' ] ], 'scope-empty': [1, 'never'], diff --git a/nx.json b/nx.json index 87b4ac73..dae3694b 100644 --- a/nx.json +++ b/nx.json @@ -47,6 +47,9 @@ }, "demos-app": { "tags": [] + }, + "packages-schematics": { + "tags": [] } }, "implicitDependencies": { diff --git a/package.json b/package.json index c098969e..84b569fb 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,9 @@ "devDependencies": { "@angular-devkit/build-angular": "~0.13.1", "@angular-devkit/build-ng-packagr": "~0.13.0", + "@angular-devkit/core": "^7.3.8", + "@angular-devkit/schematics": "^7.3.8", + "@angular-devkit/schematics-cli": "^0.13.8", "@angular/cli": "7.3.8", "@angular/compiler-cli": "7.2.13", "@angular/language-service": "7.2.13", @@ -74,6 +77,7 @@ "@ngrx/schematics": "7.4.0", "@nrwl/builders": "7.7.2", "@nrwl/schematics": "7.7.2", + "@types/fs-extra": "^5.0.5", "@types/jasmine": "~3.3.0", "@types/jasminewd2": "~2.0.3", "@types/jest": "24.0.11", diff --git a/packages/schematics/.gitignore b/packages/schematics/.gitignore new file mode 100644 index 00000000..6a314766 --- /dev/null +++ b/packages/schematics/.gitignore @@ -0,0 +1,20 @@ +# Outputs +src/**/*.js +src/**/*.js.map +src/**/*.d.ts + +# IDEs +.idea/ +jsconfig.json +.vscode/ + +# Misc +node_modules/ +npm-debug.log* +yarn-error.log* + +# Mac OSX Finder files. +**/.DS_Store +.DS_Store + +dist/ diff --git a/packages/schematics/.npmignore b/packages/schematics/.npmignore new file mode 100644 index 00000000..c55ccfc3 --- /dev/null +++ b/packages/schematics/.npmignore @@ -0,0 +1,3 @@ +# Ignores TypeScript files, but keeps definitions. +*.ts +!*.d.ts diff --git a/packages/schematics/DEVELOP.md b/packages/schematics/DEVELOP.md new file mode 100644 index 00000000..546d2c2c --- /dev/null +++ b/packages/schematics/DEVELOP.md @@ -0,0 +1,34 @@ +# Getting Started With Schematics + +This repository is a basic Schematic implementation that serves as a starting point to create and publish Schematics to NPM. + +### Testing + +To test locally, install `@angular-devkit/schematics-cli` globally and use the `schematics` command line tool. That tool acts the same as the `generate` command of the Angular CLI, but also has a debug mode. + +Check the documentation with +```bash +schematics --help +``` + +### Unit Testing + +`npm run test` will run the unit tests, using Jasmine as a runner and test framework. + +### Publishing + +To publish, simply do: + +```bash +npm run build +npm publish +``` + +That's it! + + +#### Futher Reading + +- https://brianflove.com/2018/12/11/angular-schematics-tutorial/ +- `ng update --registry http://myregistry.org`: https://github.com/angular/angular-cli/issues/10624 +- ng update command: https://github.com/angular/angular-cli/blob/master/docs/specifications/update.md#library-developers diff --git a/packages/schematics/README.md b/packages/schematics/README.md new file mode 100644 index 00000000..76a7aa13 --- /dev/null +++ b/packages/schematics/README.md @@ -0,0 +1,9 @@ +@sparkles/schematics +==================== + +```bash +$ ng add @sparkles/schematics + +``` + +https://github.com/nrwl/nx/blob/master/packages/schematics/migrations/update-7-0-0/update-7-0-0.ts diff --git a/packages/schematics/jest.config.js b/packages/schematics/jest.config.js new file mode 100644 index 00000000..7f2e61eb --- /dev/null +++ b/packages/schematics/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + name: 'packages-schematics', + preset: '../../jest.config.js', + coverageDirectory: '../../coverage/packages/schematics', + snapshotSerializers: [ + 'jest-preset-angular/AngularSnapshotSerializer.js', + 'jest-preset-angular/HTMLCommentSerializer.js' + ] +}; diff --git a/packages/schematics/package.json b/packages/schematics/package.json new file mode 100644 index 00000000..276710b5 --- /dev/null +++ b/packages/schematics/package.json @@ -0,0 +1,27 @@ +{ + "name": "@sparkles/schematics", + "version": "1.0.0-1", + "description": "Sample schematics", + "license": "MIT", + "scripts": { + "build": "../../node_modules/.bin/tsc -p tsconfig.lib.json", + "test": "npm run build && jasmine src/**/*_spec.js" + }, + "keywords": [ + "schematics" + ], + "schematics": "./src/collection.json", + "ng-update": { + "migrations": "./src/migration.json", + "packageGroup": [ + "@sparkles/components", + "@sparkles/styles", + "@sparkles/schematics" + ], + "requirements": { + "@sparkles/schematics": ">= 1" + } + }, + "dependencies": { + } +} diff --git a/packages/schematics/src/collection.json b/packages/schematics/src/collection.json new file mode 100644 index 00000000..2696072a --- /dev/null +++ b/packages/schematics/src/collection.json @@ -0,0 +1,18 @@ +{ + "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", + "extends": "@nrwl/schematics", + "schematics": { + "ng-add": { + "description": "Adds the sparkles workspace to the application", + "factory": "./collection/ng-add/index", + "aliases": ["install"], + "hidden": true + }, + "init-workspace": { + "description": "Initializes a workspace", + "factory": "./collection/init/index", + "aliases": ["init"], + "hidden": true + } + } +} diff --git a/packages/schematics/src/collection/init/index.ts b/packages/schematics/src/collection/init/index.ts new file mode 100644 index 00000000..5a92610c --- /dev/null +++ b/packages/schematics/src/collection/init/index.ts @@ -0,0 +1,15 @@ +import { Rule, externalSchematic } from '@angular-devkit/schematics'; + +export function initWorkspace(): Rule { + + return (host, context) => { + context.logger.info(`ng add @nrwl/schematics`); + + // ng add @nrwl/schematics + return externalSchematic('@nrwl/schematics', 'ng-add', {}); + }; +} + +export default function schematics(_options: any): Rule { + return initWorkspace(); +} diff --git a/packages/schematics/src/collection/ng-add/index.spec.ts b/packages/schematics/src/collection/ng-add/index.spec.ts new file mode 100644 index 00000000..7eac50e6 --- /dev/null +++ b/packages/schematics/src/collection/ng-add/index.spec.ts @@ -0,0 +1,14 @@ +import { Tree } from '@angular-devkit/schematics'; +import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; +import * as path from 'path'; + +const collectionPath = path.join(__dirname, '../collection.json'); + +describe('schematics', () => { + it('works', () => { + const runner = new SchematicTestRunner('schematics', collectionPath); + const tree = runner.runSchematic('schematics', {}, Tree.empty()); + + expect(tree.files).toEqual([]); + }); +}); diff --git a/packages/schematics/src/collection/ng-add/index.ts b/packages/schematics/src/collection/ng-add/index.ts new file mode 100644 index 00000000..586c3760 --- /dev/null +++ b/packages/schematics/src/collection/ng-add/index.ts @@ -0,0 +1,43 @@ +import { Rule, SchematicContext, Tree, externalSchematic, chain } from '@angular-devkit/schematics'; +import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks'; +import { updateJsonInTree } from '../../utils/json'; +import { VERSIONS } from '../../versions'; + +function addNxToPackageJsonDependencies(): Rule { + + return updateJsonInTree('package.json', (json, context) => { + context.logger.info(`Updating package.json...`); + if (!json['devDependencies']) { + json['devDependencies'] = {}; + } + json['devDependencies']['@nrwl/schematics'] = VERSIONS.NX; + + return json; + }); +} + +function installDependencies(): Rule { + return (host: Tree, context: SchematicContext) => { + context.addTask(new NodePackageInstallTask()); + context.logger.info(`🔍 Installing packages...`); + + return host; + }; +} + +function scheduleNxAdd(): Rule { + return (host: Tree, context: SchematicContext) => { + context.addTask(new RunSchematicTask('@sparkles/schematics', 'init', {})); + + return host; + }; +} + +// schematic function for `ng add @sparkles/schematics` +export default function schematics(_options: any): Rule { + return chain([ + addNxToPackageJsonDependencies(), + installDependencies(), + scheduleNxAdd(), + ]); +} diff --git a/packages/schematics/src/migration.json b/packages/schematics/src/migration.json new file mode 100644 index 00000000..a0337ea7 --- /dev/null +++ b/packages/schematics/src/migration.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "migration-v1": { + "version": "1", + "description": "Updates Sparkles to v1", + "factory": "./migration/index#migrateToV1" + } + } +} diff --git a/packages/schematics/src/migration/index.ts b/packages/schematics/src/migration/index.ts new file mode 100644 index 00000000..09d7c687 --- /dev/null +++ b/packages/schematics/src/migration/index.ts @@ -0,0 +1 @@ +export { migrateToV1 } from './migration-v1/migration-v1'; diff --git a/packages/schematics/src/migration/migration-v1/migration-v1.ts b/packages/schematics/src/migration/migration-v1/migration-v1.ts new file mode 100644 index 00000000..61681e01 --- /dev/null +++ b/packages/schematics/src/migration/migration-v1/migration-v1.ts @@ -0,0 +1,67 @@ +import { Rule, SchematicContext, Tree, externalSchematic, chain } from '@angular-devkit/schematics'; +import { NodePackageInstallTask, RunSchematicTask } from '@angular-devkit/schematics/tasks'; +import { readJsonInTree, updateJsonInTree } from '../../utils/json'; + +function updateOrAddNx(): Rule { + + return (tree: Tree, context: SchematicContext) => { + const packageJson = readJsonInTree(tree, 'package.json'); + + const inDevDependencies = packageJson['devDependencies'] ? packageJson['devDependencies']['@nrwl/schematics'] : null; + const inDependencies = packageJson['devDependencies'] ? packageJson['devDependencies']['@nrwl/schematics'] : null; + if (inDevDependencies || inDependencies) { + const fromVersion = inDevDependencies || inDependencies; + const toVersion = '7.7.0'; + context.logger.info(`Migrating @nrwl/schematis from already installed version=${fromVersion} to ${toVersion}`); + + return externalSchematic('@schematics/update', 'update', { + packages: ['@nrwl/schematics'], + from: fromVersion, + to: toVersion, + force: true + }) + } else { + // ng add @nrwl/schematics + return externalSchematic('@nrwl/schematics', 'ng-add', {}); + } + }; +} + +function addNxToPackageJsonDependencies(): Rule { + + return updateJsonInTree('package.json', (json, context) => { + context.logger.info(`Updating package.json...`); + if (!json['devDependencies']) { + json['devDependencies'] = {}; + } + json['devDependencies']['@nrwl/schematics'] = '7.7.0'; + + return json; + }); +} + +function installDependencies(): Rule { + return (host: Tree, context: SchematicContext) => { + context.addTask(new NodePackageInstallTask()); + context.logger.info(`🔍 Installing packages...`); + + return host; + }; +} + +function scheduleNxAdd(): Rule { + return (host: Tree, context: SchematicContext) => { + context.addTask(new RunSchematicTask('@sparkles/schematics', 'init', {})); + + return host; + }; +} + + +export function migrateToV1(_options: any): Rule { + return chain([ + addNxToPackageJsonDependencies(), + installDependencies(), + scheduleNxAdd() + ]); +} diff --git a/packages/schematics/src/utils/files.ts b/packages/schematics/src/utils/files.ts new file mode 100644 index 00000000..e8e99fe3 --- /dev/null +++ b/packages/schematics/src/utils/files.ts @@ -0,0 +1,8 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { ensureDirSync } from 'fs-extra'; + +export function writeToFile(filePath: string, str: string) { + ensureDirSync(path.dirname(filePath)); + fs.writeFileSync(filePath, str); +} diff --git a/packages/schematics/src/utils/json.ts b/packages/schematics/src/utils/json.ts new file mode 100644 index 00000000..ea44bdd6 --- /dev/null +++ b/packages/schematics/src/utils/json.ts @@ -0,0 +1,47 @@ +import { Rule, Tree, SchematicContext } from '@angular-devkit/schematics'; + +/** + * This method is specifically for reading JSON files in a Tree + * @param host The host tree + * @param path The path to the JSON file + * @returns The JSON data in the file. + */ +export function readJsonInTree(host: Tree, path: string): T { + if (!host.exists(path)) { + throw new Error(`Cannot find ${path}`); + } + // tslint:disable-next-line:no-non-null-assertion + const contents = host.read(path)!.toString('utf-8'); + try { + return JSON.parse(contents); + } catch (e) { + throw new Error(`Cannot parse ${path}: ${e.message}`); + } +} + +/** + * This method is specifically for updating JSON in a Tree + * @param path Path of JSON file in the Tree + * @param callback Manipulation of the JSON data + * @returns A rule which updates a JSON file file in a Tree + */ +export function updateJsonInTree( + path: string, + callback: (json: T, context: SchematicContext) => O +): Rule { + return (host: Tree, context: SchematicContext): Tree => { + if (!host.exists(path)) { + host.create(path, serializeJson(callback({} as T, context))); + return host; + } + host.overwrite( + path, + serializeJson(callback(readJsonInTree(host, path), context)) + ); + return host; + }; +} + +export function serializeJson(json: any): string { + return `${JSON.stringify(json, null, 2)}\n`; +} diff --git a/packages/schematics/src/versions.ts b/packages/schematics/src/versions.ts new file mode 100644 index 00000000..5d6e6645 --- /dev/null +++ b/packages/schematics/src/versions.ts @@ -0,0 +1,7 @@ +export const VERSIONS = { + NX: '7.7.0' +}; + +export const SEMVER = { + NX: `^${VERSIONS.NX}` +}; diff --git a/packages/schematics/test-setup.ts b/packages/schematics/test-setup.ts new file mode 100644 index 00000000..cb3e85e5 --- /dev/null +++ b/packages/schematics/test-setup.ts @@ -0,0 +1,2 @@ +import 'jest-preset-angular'; +//const foo = 'bar'; diff --git a/packages/schematics/tsconfig.lib.json b/packages/schematics/tsconfig.lib.json new file mode 100644 index 00000000..7135cea6 --- /dev/null +++ b/packages/schematics/tsconfig.lib.json @@ -0,0 +1,36 @@ +{ + "compilerOptions": { + "baseUrl": "tsconfig", + "lib": [ + "es2018", + "dom" + ], + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noUnusedParameters": false, + "noUnusedLocals": false, + "rootDir": "src/", + "outDir": "dist/src", + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": true, + "strictNullChecks": true, + "target": "es6", + "types": [ + "jasmine", + "node" + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "src/*/files/**/*", + "src/**/*.spec.ts" + ] +} diff --git a/packages/schematics/tsconfig.spec.json b/packages/schematics/tsconfig.spec.json new file mode 100644 index 00000000..62ee9992 --- /dev/null +++ b/packages/schematics/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.lib.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc/packages/schematis", + "module": "commonjs", + "types": ["jest", "node"] + }, + // "files": ["test-setup.ts"], + "include": ["**/*.spec.ts", "**/*.d.ts"] +} diff --git a/yarn.lock b/yarn.lock index 6dd4ef02..1f656fa1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -110,7 +110,7 @@ rxjs "6.3.3" source-map "0.7.3" -"@angular-devkit/core@7.3.8": +"@angular-devkit/core@7.3.8", "@angular-devkit/core@^7.3.8": version "7.3.8" resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-7.3.8.tgz#702b0944a69c71cce3a1492e0d62de18df22a993" integrity sha512-3X9uzaZXFpm5o2TSzhD6wEOtVU32CgeytKjD1Scxj+uMMVo48SWLlKiFh312T+smI9ko7tOT8VqxglwYkWosgg== @@ -121,6 +121,19 @@ rxjs "6.3.3" source-map "0.7.3" +"@angular-devkit/schematics-cli@^0.13.8": + version "0.13.8" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-0.13.8.tgz#8f4c314cdd7b8b7621141ad63b02e8f8c00b2e43" + integrity sha512-PnVetGOLqONNhKcUfJoCRUJU8BSpcTZpWwQS6YywyNrvyVXtnUi/dgMTQkS8fva3XC/5Ij3Mj7yrfTHaG7M7bw== + dependencies: + "@angular-devkit/core" "7.3.8" + "@angular-devkit/schematics" "7.3.8" + "@schematics/schematics" "0.13.8" + inquirer "6.2.1" + minimist "1.2.0" + rxjs "6.3.3" + symbol-observable "1.2.0" + "@angular-devkit/schematics@7.3.1": version "7.3.1" resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.3.1.tgz#7dc704005b966ea6c1ee62f380120183bb76eee6" @@ -129,7 +142,7 @@ "@angular-devkit/core" "7.3.1" rxjs "6.3.3" -"@angular-devkit/schematics@7.3.8": +"@angular-devkit/schematics@7.3.8", "@angular-devkit/schematics@^7.3.8": version "7.3.8" resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-7.3.8.tgz#70bfc7876f7924ff53ab9310a00b62f20acf2f5c" integrity sha512-mvaKoORZIaW/h0VNZ3IQWP0qThRCZRX6869FNlzV0jlW0mhn07XbiIGHCGGSCDRxS7qJ0VbuIVnKXntF+iDeWw== @@ -1368,6 +1381,14 @@ "@angular-devkit/schematics" "7.3.8" typescript "3.2.4" +"@schematics/schematics@0.13.8": + version "0.13.8" + resolved "https://registry.yarnpkg.com/@schematics/schematics/-/schematics-0.13.8.tgz#a55c8da3dcb3e5fe75b6411c2ce9f115fb590efb" + integrity sha512-7Bqw2DzCbt7EkR0IYDEUXJ6WQjE90NqSMFqK0Lylps0WswJfjUq5axJduz5LwbmrIDhWdDhXMtI4QimUBm4Qsw== + dependencies: + "@angular-devkit/core" "7.3.8" + "@angular-devkit/schematics" "7.3.8" + "@schematics/update@0.13.1": version "0.13.1" resolved "https://registry.yarnpkg.com/@schematics/update/-/update-0.13.1.tgz#481475aee18b4a9472a06512b2e4d6429af68231" @@ -1454,6 +1475,13 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/fs-extra@^5.0.5": + version "5.0.5" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.5.tgz#080d90a792f3fa2c5559eb44bd8ef840aae9104b" + integrity sha512-w7iqhDH9mN8eLClQOYTkhdYUOSpp25eXxfc6VbFOGtzxW34JcvctH2bKjj4jD4++z4R5iO5D+pg48W2e03I65A== + dependencies: + "@types/node" "*" + "@types/istanbul-lib-coverage@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.0.tgz#1eb8c033e98cf4e1a4cedcaf8bcafe8cb7591e85"