diff --git a/package.json b/package.json index 20a1c96a..40ab9d2c 100644 --- a/package.json +++ b/package.json @@ -4,17 +4,19 @@ "scripts": { "ng": "ng", "start": "ng serve", + "build-pkg": "ng build ngx-apexcharts", + "build-schematics": "tsc -p ./projects/ngx-apexcharts/schematics/tsconfig.json --outDir dist/ngx-apexcharts/schematics", "build": "ng build", "test": "ng test", "lint": "ng lint ngx-apexcharts && ng lint ngx-apexcharts-demo", "e2e": "ng e2e", - "package": "ng build ngx-apexcharts", + "package": "yarn build-pkg && yarn build-schematics", "copyfile": "copyfiles ./LICENSE ./dist/ngx-apexcharts ", "release": "cd ./projects/ngx-apexcharts && standard-version", "release:patch": "cd ./projects/ngx-apexcharts && standard-version --release-as patch", "release:minor": "cd ./projects/ngx-apexcharts && standard-version --release-as minor", "release:major": "cd ./projects/ngx-apexcharts && standard-version --release-as major", - "publish": "ng build ngx-apexcharts && copyfiles ./LICENSE ./dist/ngx-apexcharts && npm publish dist/ngx-apexcharts", + "publish": "yarn package && copyfiles ./LICENSE ./dist/ngx-apexcharts && npm publish dist/ngx-apexcharts/", "prepare": "is-ci || husky install" }, "private": true, @@ -27,6 +29,7 @@ "@angular/platform-browser": "^16.0.3", "@angular/platform-browser-dynamic": "^16.0.3", "@angular/router": "^16.0.3", + "@schematics/angular": "^16.0.4", "apexcharts": "^3.36.3", "rxjs": "~6.5.5", "tslib": "^2.0.0", @@ -53,11 +56,11 @@ "eslint": "^8.39.0", "husky": "^7.0.0", "is-ci": "^3.0.1", - "karma-coverage": "^2.1.0", "jasmine-core": "~3.6.0", "jasmine-spec-reporter": "~5.0.0", "karma": "~6.3.11", "karma-chrome-launcher": "~3.1.0", + "karma-coverage": "^2.1.0", "karma-jasmine": "~4.0.0", "karma-jasmine-html-reporter": "~1.5.0", "lint-staged": "^13.0.3", @@ -67,4 +70,4 @@ "ts-node": "~8.10.1", "typescript": "~5.0.4" } -} \ No newline at end of file +} diff --git a/projects/ngx-apexcharts/package.json b/projects/ngx-apexcharts/package.json index c4e90d20..e958c7c2 100644 --- a/projects/ngx-apexcharts/package.json +++ b/projects/ngx-apexcharts/package.json @@ -29,5 +29,6 @@ "repository": { "type": "git", "url": "https://github.com/damingerdai/ngx-apexcharts.git" - } + }, + "schematics": "./schematics/collection.json" } diff --git a/projects/ngx-apexcharts/schematics/collection.json b/projects/ngx-apexcharts/schematics/collection.json new file mode 100644 index 00000000..da7f3cfb --- /dev/null +++ b/projects/ngx-apexcharts/schematics/collection.json @@ -0,0 +1,11 @@ +{ + "$schema": "./node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "ng-add": { + "description": "Adds ngx-apexcharts to the application", + "factory": "./ng-add/index", + "schema": "./ng-add/schema.json", + "aliases": ["ngx-apexcharts-shell", "install"] + } + } +} diff --git a/projects/ngx-apexcharts/schematics/ng-add/index.ts b/projects/ngx-apexcharts/schematics/ng-add/index.ts new file mode 100644 index 00000000..a5c04dc5 --- /dev/null +++ b/projects/ngx-apexcharts/schematics/ng-add/index.ts @@ -0,0 +1,128 @@ +import { Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; +import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; +import { getAppModulePath } from '@schematics/angular/utility/ng-ast-utils'; +import { ProjectDefinition, WorkspaceDefinition, getWorkspace } from '@schematics/angular/utility/workspace'; +import { SchematicsException } from '@angular-devkit/schematics'; +import { addSymbolToNgModuleMetadata, insertImport } from '@schematics/angular/utility/ast-utils'; +import { ProjectType } from '@schematics/angular/utility/workspace-models'; +import { InsertChange } from '@schematics/angular/utility/change'; + +import * as ts from '@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript'; + +export default function(options: NgxApexchartNgAddSchema): Rule { + return async (_host: Tree, _context: SchematicContext) => { + const workspace = await getWorkspace(_host); + const project = getProjectFromWorkspace(workspace, options.project); + + if (!project) { + throw new Error(`can not find ${options.project} angular project`); + } + if (project.extensions.projectType === ProjectType.Application) { + addNgxPendoModule(project as ProjectDefinition, _host); + } + addPackageToPackageJson(_host, 'ngx-apexcharts', '~0.2.0'); + addPackageToPackageJson(_host, 'apexcharts', '~3.36.3'); + _context.logger.log('info', '✅️ Added "ngx-apexcharts"'); + _context.addTask(new NodePackageInstallTask()); + }; +} + +function addNgxPendoModule(project: ProjectDefinition, _host: Tree): void { + if (!project) { + return; + } + const appModulePath = getAppModulePath(_host, getProjectMainFile(project)); + const sourceFile = readIntoSourceFile(_host, appModulePath); + const importPath = 'ngx-apexcharts'; + const recorder = _host.beginUpdate(appModulePath); + const moduleName = 'NgxApexchartsModule'; + const importChange = insertImport(sourceFile, appModulePath, moduleName, importPath); + if (importChange instanceof InsertChange) { + recorder.insertLeft(importChange.pos, importChange.toAdd); + } + const ngModuleName = 'NgxApexchartsModule'; + const ngModuleChanges = addSymbolToNgModuleMetadata(sourceFile, appModulePath, 'imports', ngModuleName, null); + for (const change of ngModuleChanges) { + if (change instanceof InsertChange) { + recorder.insertLeft(change.pos, change.toAdd); + } + } + _host.commitUpdate(recorder); +} + + +function readIntoSourceFile(host: Tree, modulePath: string): ts.SourceFile { + const text = host.read(modulePath); + if (text === null) { + throw new SchematicsException(`File ${modulePath} does noot exist`); + } + + const sourceText = text.toString('utf-8'); + return ts.createSourceFile(modulePath, sourceText, ts.ScriptTarget.Latest, true); +} + +function addPackageToPackageJson(host: Tree, pkg: string, version: string): Tree { + 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)); + } + + return host; +} + +function sortObjectByKeys(obj: any): any { + return Object.keys(obj) + .sort() + .reduce((result: any, key: any) => (result[key] = obj[key]) && result, {}); +} + +// eslint-disable-next-line +function getProjectTargetOptions(project: ProjectDefinition, buildTarget: string) { + if (project?.targets?.get(buildTarget)?.options) { + return project!.targets!.get(buildTarget)!.options; + } + + throw new Error(`Cannot determine project target configuration for: ${buildTarget}.`); +} + +function getProjectMainFile(project: ProjectDefinition): string { + const buildOptions = getProjectTargetOptions(project, 'build'); + if (!buildOptions || !buildOptions.main) { + throw new SchematicsException(`Could not find the project main file inside of the ` + + `workspace config (${project.sourceRoot})`); + } + + return buildOptions.main.toString(); +} + +export function getProjectFromWorkspace( + workspace: WorkspaceDefinition, + projectName: string | undefined, +): ProjectDefinition { + if (!projectName) { + // TODO(crisbeto): some schematics APIs have the project name as optional so for now it's + // simpler to allow undefined and checking it at runtime. Eventually we should clean this up. + throw new SchematicsException('Project name is required.'); + } + + const project = workspace.projects.get(projectName); + + if (!project) { + throw new SchematicsException(`Could not find project in workspace: ${projectName}`); + } + + return project; +} diff --git a/projects/ngx-apexcharts/schematics/ng-add/schema.d.ts b/projects/ngx-apexcharts/schematics/ng-add/schema.d.ts new file mode 100644 index 00000000..482e4f94 --- /dev/null +++ b/projects/ngx-apexcharts/schematics/ng-add/schema.d.ts @@ -0,0 +1,10 @@ +/** + * Ngx apexchart ng-add schematic + * Generate a file of JavaScript + */ +declare interface NgxApexchartNgAddSchema { + /** + * Name of the project. + */ + project?: string; +} diff --git a/projects/ngx-apexcharts/schematics/ng-add/schema.json b/projects/ngx-apexcharts/schematics/ng-add/schema.json new file mode 100644 index 00000000..84314b94 --- /dev/null +++ b/projects/ngx-apexcharts/schematics/ng-add/schema.json @@ -0,0 +1,16 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "ngx-apexcharts-ng-add-schema", + "title": "Ngx Apexcharts ng-add schematic", + "description": "Generate a file of JavaScript", + "type": "object", + "properties": { + "project": { + "type": "string", + "description": "Name of the project.", + "$default": { + "$source": "projectName" + } + } + } +} diff --git a/projects/ngx-apexcharts/schematics/tsconfig.json b/projects/ngx-apexcharts/schematics/tsconfig.json new file mode 100644 index 00000000..6f373274 --- /dev/null +++ b/projects/ngx-apexcharts/schematics/tsconfig.json @@ -0,0 +1,33 @@ +{ + "compilerOptions": { + "baseUrl": "tsconfig", + "lib": [ + "es2018", + "dom" + ], + "declaration": true, + "module": "commonjs", + "moduleResolution": "node", + "noEmitOnError": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitThis": true, + "noUnusedParameters": true, + "noUnusedLocals": false, + "rootDir": "./", + "outDir": "dist", + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": false, + "strictNullChecks": true, + "target": "es6", + "types": [ + "jasmine", + "node" + ] + }, + "include": [ + "./**/*", + "**.json" + ] +} diff --git a/yarn.lock b/yarn.lock index c7523676..9f0d1997 100644 --- a/yarn.lock +++ b/yarn.lock @@ -109,6 +109,17 @@ rxjs "7.8.1" source-map "0.7.4" +"@angular-devkit/core@16.0.4": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-16.0.4.tgz#931be431fba9f310647e73133943218b8146fdf8" + integrity sha512-RK8+W+BVQcD2RHyQcy4iKSIFO1BexVUU2htu3+A9SY7VN/szoMgHIP/sx7Pj+LIJfDrvyve57aIOv0KPWk3WOg== + dependencies: + ajv "8.12.0" + ajv-formats "2.1.1" + jsonc-parser "3.2.0" + rxjs "7.8.1" + source-map "0.7.4" + "@angular-devkit/schematics@16.0.1": version "16.0.1" resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-16.0.1.tgz#d49387e9e41c9cce98b155da51b0e193333dd178" @@ -120,6 +131,17 @@ ora "5.4.1" rxjs "7.8.1" +"@angular-devkit/schematics@16.0.4": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-16.0.4.tgz#0abe3555567baf1150f8cb182e976f251a2b6df5" + integrity sha512-ASr9bGRuTTAAgHsr/ltBBl4CTyZETmuzS/boVMNDVLvjDDFr0aY/F/FW/QRFbJsxgxM2VAJn7NpY64Rl9fQz/g== + dependencies: + "@angular-devkit/core" "16.0.4" + jsonc-parser "3.2.0" + magic-string "0.30.0" + ora "5.4.1" + rxjs "7.8.1" + "@angular-eslint/builder@16.0.2": version "16.0.2" resolved "https://registry.yarnpkg.com/@angular-eslint/builder/-/builder-16.0.2.tgz#013d8d8e509d071eeab7aaaf5198f27e96a3745e" @@ -1995,6 +2017,15 @@ "@angular-devkit/schematics" "16.0.1" jsonc-parser "3.2.0" +"@schematics/angular@^16.0.4": + version "16.0.4" + resolved "https://registry.yarnpkg.com/@schematics/angular/-/angular-16.0.4.tgz#3321a34dd0c51dd0f4f3178f9c0f694f68b7d7c8" + integrity sha512-3uu5xq136broqVKCGwYKENZYF8SO4EU15FHRV2EFY/PqU2sU5QBlsGG2Z1JYAVE3aevsk9ORkPb1tRh6yP28Rw== + dependencies: + "@angular-devkit/core" "16.0.4" + "@angular-devkit/schematics" "16.0.4" + jsonc-parser "3.2.0" + "@sigstore/protobuf-specs@^0.1.0": version "0.1.0" resolved "https://registry.yarnpkg.com/@sigstore/protobuf-specs/-/protobuf-specs-0.1.0.tgz#957cb64ea2f5ce527cc9cf02a096baeb0d2b99b4"