import { MergeStrategy, Rule, SchematicContext, Tree, apply, chain, mergeWith, strings, template, url } from '@angular-devkit/schematics'; import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks'; import { Schema } from './schema'; import { Observable } from 'rxjs'; import * as fs from 'fs'; interface PackageJson { scripts: Record; dependencies: Record; devDependencies: Record; } // You don't have to export the function as default. You can also have more than one rule factory per file. export function newProject(options: Schema): Rule { if (fs.existsSync(options.name)) { throw new Error(`A folder named "${options.name}" already exists.`); } return (tree: Tree, context: SchematicContext) => { const rule = chain([ mergeWith(apply(url('./files'), [ template({ ...options, ...strings }), ]), MergeStrategy.Default), addPackageJsonDependency(options.name, '@angular-architects/module-federation', false, '15.0.3'), addPackageJsonDependency(options.name, '@angular/animations', false, '15.1.5'), addPackageJsonDependency(options.name, '@angular/cdk', false, '15.1.5'), addPackageJsonDependency(options.name, '@angular/common', false, '15.1.5'), addPackageJsonDependency(options.name, '@angular/compiler', false, '15.1.5'), addPackageJsonDependency(options.name, '@angular/core', false, '15.1.5'), addPackageJsonDependency(options.name, '@angular/elements', false, '15.1.5'), addPackageJsonDependency(options.name, '@angular/forms', false, '15.1.5'), addPackageJsonDependency(options.name, '@angular/platform-browser', false, '15.1.5'), addPackageJsonDependency(options.name, '@angular/platform-browser-dynamic', false, '15.1.5'), addPackageJsonDependency(options.name, '@angular/router', false, '15.1.5'), addPackageJsonDependency(options.name, '@module-federation/typescript', false, "2.3.0"), addPackageJsonDependency(options.name, 'rxjs', false, '7.8.1'), addPackageJsonDependency(options.name, 'tslib', false, '2.5.0'), addPackageJsonDependency(options.name, 'zone.js', false, '0.12.0'), addPackageJsonDependency(options.name, "@unilygrouplimited/manifest-factory", true, "1.0.2"), addPackageJsonDependency(options.name, "@unilygrouplimited/sas-plugin", true, "1.0.0"), addPackageJsonDependency(options.name, '@unilygrouplimited/remote-service', false, "1.0.1"), addPackageJsonDependency(options.name, '@angular-devkit/build-angular', true, '15.1.6'), addPackageJsonDependency(options.name, '@angular-eslint/builder', true, '15.2.0'), addPackageJsonDependency(options.name, '@angular-eslint/eslint-plugin', true, '15.2.0'), addPackageJsonDependency(options.name, '@angular-eslint/eslint-plugin-template', true, '15.2.0'), addPackageJsonDependency(options.name, '@angular-eslint/schematics', true, '15.2.0'), addPackageJsonDependency(options.name, '@angular-eslint/template-parser', true, '15.2.0'), addPackageJsonDependency(options.name, '@angular/cli', true, '15.1.6'), addPackageJsonDependency(options.name, '@angular/compiler-cli', true, '15.1.5'), addPackageJsonDependency(options.name, '@types/jasmine', true, '4.3.1'), addPackageJsonDependency(options.name, '@typescript-eslint/eslint-plugin', true, '5.59.0'), addPackageJsonDependency(options.name, '@typescript-eslint/parser', true, '5.59.0'), addPackageJsonDependency(options.name, 'eslint', true, '8.38.0'), addPackageJsonDependency(options.name, 'eslint-config-standard', true, '17.0.0'), addPackageJsonDependency(options.name, 'eslint-plugin-import', true, '2.27.5'), addPackageJsonDependency(options.name, 'eslint-plugin-n', true, '15.7.0'), addPackageJsonDependency(options.name, 'eslint-plugin-promise', true, '6.1.1'), addPackageJsonDependency(options.name, 'jasmine-core', true, '4.6.0'), addPackageJsonDependency(options.name, 'karma', true, '6.4.1'), addPackageJsonDependency(options.name, 'karma-chrome-launcher', true, '3.1.1'), addPackageJsonDependency(options.name, 'karma-coverage', true, '2.2.0'), addPackageJsonDependency(options.name, 'karma-jasmine', true, '5.1.0'), addPackageJsonDependency(options.name, 'karma-jasmine-html-reporter', true, '2.0.0'), addPackageJsonDependency(options.name, 'ngx-build-plus', true, "15.0.0"), addPackageJsonDependency(options.name, 'typescript', true, "4.8.2"), addPackageJsonDependency(options.name, 'adm-zip', true, "0.5.10"), addPackageJsonDependency(options.name, 'axios', true, "1.3.4"), sortPackageJsonDependencies(options.name), installNodePackages(options.name) ]); return rule(tree, context) as Rule; }; } function addPackageJsonDependency(projectName: string, packageName: string, isDevDependency: boolean = false, version?: string): Rule { return (tree: Tree, _: SchematicContext) => { const observer = new Observable((observer) => { if (version != null) { observer.next(writePackageJsonDependency(tree, projectName, packageName, version, isDevDependency)); console.info(`Package dependency added: ${packageName} ${version}${isDevDependency ? ' (Dev)' : ''}`); observer.complete(); return observer; } fetch('https://registry.npmjs.org/' + packageName + '/latest') .then(res => res.json()) .then(data => { observer.next(writePackageJsonDependency(tree, projectName, packageName, data.version, isDevDependency)); console.info(`Package dependency added: ${packageName} ${data.version}${isDevDependency ? ' (Dev)' : ''}`); observer.complete(); }) .catch(function (err: any) { console.error(err); observer.next(writePackageJsonDependency(tree, projectName, packageName, 'latest', isDevDependency)); console.info(`Failed to fetch package info for: ${packageName}${isDevDependency ? ' (Dev) ' : ''}- Using fallback package version "latest" (Please amend the version number of the mentioned package inside ${projectName}/package.json file).`); observer.complete(); }); }); return observer; }; } function sortPackageJsonDependencies(projectName: string): Rule { return (tree: Tree, _: SchematicContext): Tree => { console.info('Sorting package.json dependencies...'); const path = `/${projectName}/package.json`; const json = getPackageJson(tree, path); json.dependencies = sortPackageJsonObjectByKeys(json.dependencies); json.devDependencies = sortPackageJsonObjectByKeys(json.devDependencies); tree.overwrite(path, JSON.stringify(json, null, 2)); return tree; }; } function installNodePackages(projectName: string): Rule { return (tree: Tree, context: SchematicContext): Tree => { console.info('Installing node packages...'); context.addTask( new NodePackageInstallTask({ workingDirectory: projectName, packageManager: 'npm', }) ); return tree; }; } // --- Utility function getPackageJson(tree: Tree, path: string): PackageJson { const file = tree.read(path); return JSON.parse(file!.toString()) as PackageJson; } function writePackageJsonDependency(tree: Tree, projectName: string, packageName: string, version: string, isDevDependency: boolean): Tree { const path = `/${projectName}/package.json`; const json = getPackageJson(tree, path); if (isDevDependency) { json.devDependencies = { ...json.devDependencies, [packageName]: version }; } else { json.dependencies = { ...json.dependencies, [packageName]: version }; } tree.overwrite(path, JSON.stringify(json, null, 2)); return tree; } function sortPackageJsonObjectByKeys(obj: Record): Record { return Object.keys(obj) .sort() .reduce((result, key) => { result[key] = obj[key]; return result; }, {} as Record); }