From bcb5790308b6f4fb69dd388459c33df7420c67df Mon Sep 17 00:00:00 2001 From: Mike Cornwell Date: Thu, 2 Jan 2025 18:28:31 -0500 Subject: [PATCH] feat(update): finish out 1.0 of toolkit --- .prettierignore | 2 +- bin/build.sh | 2 +- bin/nil-toolkit.js | 6 ++ eslint.config.mjs | 2 +- package.json | 4 +- src/app/index.ts | 74 ++++++-------- src/app/libs.ts | 22 ----- src/app/types.ts | 11 +-- src/config.ts | 1 + src/index.ts | 1 - src/package/features.ts | 46 ++++++--- src/package/libs.ts | 27 ------ src/package/services.ts | 76 +-------------- src/package/types.ts | 26 +---- src/system/index.ts | 73 +++++++++++++- src/system/types.ts | 29 ++++++ src/templating/features.ts | 15 +++ src/templating/index.ts | 7 ++ src/templating/libs.ts | 44 +++++++++ src/templating/services.ts | 96 +++++++++++++++++++ .../src/APP_NAME/features.ts.handlebars | 0 .../src/APP_NAME/index.ts.handlebars | 0 .../src/APP_NAME/services.ts.handlebars | 0 .../src/APP_NAME/types.ts.handlebars | 0 .../templates/package/all/.gitignore | 0 .../templates/package/all/.prettierignore | 0 .../templates/package/all/.prettierrc | 0 .../package/all/README.md.handlebars | 0 .../package/esm/bin/shell.js.handlebars | 0 .../package/esm/config.js.handlebars | 0 .../package/typescript/eslint.config.mjs | 1 + .../typescript/package.json.handlebars | 4 +- .../src/PACKAGE_NAME/features.ts.handlebars | 4 +- .../src/PACKAGE_NAME/index.ts.handlebars | 0 .../src/PACKAGE_NAME/services.ts.handlebars | 4 +- .../src/PACKAGE_NAME/types.ts.handlebars | 0 .../package/typescript/tsconfig.json | 0 .../templates/system/all/bin/shell.mjs} | 11 +-- .../typescript/config.dev.mjs.handlebars | 18 ++++ .../system/typescript/package.json.handlebars | 67 +++++++++++++ src/templating/types.ts | 51 ++++++++++ src/toolkit/features.ts | 15 ++- src/types.ts | 1 + tsconfig.json | 1 + 44 files changed, 493 insertions(+), 248 deletions(-) delete mode 100644 src/app/libs.ts delete mode 100644 src/package/libs.ts create mode 100644 src/system/types.ts create mode 100644 src/templating/features.ts create mode 100644 src/templating/index.ts create mode 100644 src/templating/libs.ts create mode 100644 src/templating/services.ts rename src/{ => templating}/templates/app/typescript/src/APP_NAME/features.ts.handlebars (100%) rename src/{ => templating}/templates/app/typescript/src/APP_NAME/index.ts.handlebars (100%) rename src/{ => templating}/templates/app/typescript/src/APP_NAME/services.ts.handlebars (100%) rename src/{ => templating}/templates/app/typescript/src/APP_NAME/types.ts.handlebars (100%) rename src/{ => templating}/templates/package/all/.gitignore (100%) rename src/{ => templating}/templates/package/all/.prettierignore (100%) rename src/{ => templating}/templates/package/all/.prettierrc (100%) rename src/{ => templating}/templates/package/all/README.md.handlebars (100%) rename src/{ => templating}/templates/package/esm/bin/shell.js.handlebars (100%) rename src/{ => templating}/templates/package/esm/config.js.handlebars (100%) rename src/{ => templating}/templates/package/typescript/eslint.config.mjs (99%) rename src/{ => templating}/templates/package/typescript/package.json.handlebars (93%) rename src/{ => templating}/templates/package/typescript/src/PACKAGE_NAME/features.ts.handlebars (55%) rename src/{ => templating}/templates/package/typescript/src/PACKAGE_NAME/index.ts.handlebars (100%) rename src/{ => templating}/templates/package/typescript/src/PACKAGE_NAME/services.ts.handlebars (51%) rename src/{ => templating}/templates/package/typescript/src/PACKAGE_NAME/types.ts.handlebars (100%) rename src/{ => templating}/templates/package/typescript/tsconfig.json (100%) rename src/{templates/system/bin/nil-shell.js => templating/templates/system/all/bin/shell.mjs} (82%) create mode 100644 src/templating/templates/system/typescript/config.dev.mjs.handlebars create mode 100644 src/templating/templates/system/typescript/package.json.handlebars create mode 100644 src/templating/types.ts diff --git a/.prettierignore b/.prettierignore index d9f1df7..1a164c3 100755 --- a/.prettierignore +++ b/.prettierignore @@ -1,4 +1,4 @@ -src/templates +src/templating/templates node_modules/ dist/ *.html diff --git a/bin/build.sh b/bin/build.sh index bb12afc..bc08b7a 100755 --- a/bin/build.sh +++ b/bin/build.sh @@ -5,7 +5,7 @@ rm -Rf ./dist tsc -p ./tsconfig.json cp package.json ./dist cp README.md ./dist -cp -R ./src/templates ./dist +cp -R ./src/templating/templates ./dist/templating cp -R ./bin/ ./dist/ rm ./dist/bin/build.sh diff --git a/bin/nil-toolkit.js b/bin/nil-toolkit.js index c17cb5a..b217075 100755 --- a/bin/nil-toolkit.js +++ b/bin/nil-toolkit.js @@ -39,6 +39,12 @@ const _parseArguments = () => { const addSystemParser = subParsers.add_parser('create-system', { help: 'Create a new Node In Layers system.', }) + addSystemParser.add_argument('systemName', { + help: 'The name for the system.', + }) + addSystemParser.add_argument('systemType', { + help: 'typescript, esm, commonjs', + }) const args = parser.parse_args() if (!args.command) { diff --git a/eslint.config.mjs b/eslint.config.mjs index 2cca56d..7d3164d 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -22,7 +22,7 @@ export default [ { ignores: [ 'coverage/', - 'src/templates', + 'src/templating/templates', 'dist/', 'eslint.config.mjs', 'node_modules/', diff --git a/package.json b/package.json index 850af6a..65ae3ce 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@node-in-layers/toolkit", "type": "module", - "version": "1.1.0", + "version": "1.1.1", "description": "The official toolkit for creating/updating/maintaining Node In Layer based systems.", "main": "index.js", "bin": { @@ -65,7 +65,7 @@ "typescript": "5.3.3" }, "dependencies": { - "@node-in-layers/core": "^1.1.1", + "@node-in-layers/core": "^1.1.2", "argparse": "^2.0.1", "chalk": "^4.1.2", "es-main": "^1.3.0", diff --git a/src/app/index.ts b/src/app/index.ts index 422ca55..eeded38 100644 --- a/src/app/index.ts +++ b/src/app/index.ts @@ -2,20 +2,21 @@ import { fileURLToPath } from 'node:url' import path, { dirname } from 'node:path' import * as glob from 'glob' import { - FeaturesDependencies, + FeaturesContext, Config, - ServicesDependencies, + ServicesContext, } from '@node-in-layers/core/index.js' import { PackageServicesLayer, PackageType } from '../package/types.js' import { Namespace } from '../types.js' +import { applyTemplates, createValidName } from '../templating/libs.js' +import { TemplatingServicesLayer } from '../templating/types.js' import { AppServicesLayer, AppServices } from './types.js' -import { applyTemplates, createValidAppName } from './libs.js' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) const services = { - create: (deps: ServicesDependencies): AppServices => { + create: (deps: ServicesContext): AppServices => { const _getPackageJson = async (): Promise => { const wd = `${deps.constants.workingDirectory}/package.json` return (await glob.glob(wd)).find( @@ -49,59 +50,29 @@ const services = { return PackageType.typescript }) - const readTemplates = async ({ packageType }) => { - const templatePath = path.join( - __dirname, - `../templates/app/${packageType}/src/**/*` - ) - const paths = (await glob.glob(templatePath, { dot: true })).filter(p => - deps.node.fs.lstatSync(p).isFile() - ) - return paths.map(sourceLocation => { - const dirA = path.join(__dirname, `../templates/app/${packageType}`) - const relativePath = path.relative(dirA, sourceLocation) - const sourceData = deps.node.fs.readFileSync(sourceLocation, 'utf-8') - return { - relativePath, - sourceData, - } - }) - } - - const writeTemplates = (appName, templates) => { - templates.forEach(t => { - const finalLocation = path - .join(deps.constants.workingDirectory, t.relativePath) - .replaceAll('.handlebars', '') - .replaceAll('APP_NAME', appName) - const dirPath = path.dirname(finalLocation) - deps.node.fs.mkdirSync(dirPath, { recursive: true }) - deps.node.fs.writeFileSync(finalLocation, t.templatedData) - }) - } - return { isPackageRoot, doesAppAlreadyExist, getPackageName, getPackageType, - readTemplates, - writeTemplates, } }, } const features = { create: ( - deps: FeaturesDependencies + context: FeaturesContext< + Config, + PackageServicesLayer & AppServicesLayer & TemplatingServicesLayer + > ) => { const createApp = async ({ appName }: { appName: string }) => { - const ourServices = deps.services[Namespace.app] - const logger = deps.log.getLogger('nil-toolkit:createApp') + const ourServices = context.services[Namespace.app] + const logger = context.log.getLogger('nil-toolkit:createApp') - appName = createValidAppName(appName) + appName = createValidName(appName) - if (!deps.services[Namespace.app].isPackageRoot()) { + if (!context.services[Namespace.app].isPackageRoot()) { throw new Error( `Must be located in the main directory of your node-in-layers system or package. This is the same directory as the package.json.` ) @@ -119,14 +90,25 @@ const features = { const packageType = await ourServices.getPackageType() logger.info(`Package Type if ${packageType}`) logger.info('Reading Templates') - const templates = await ourServices.readTemplates({ packageType }) + const templates = await context.services[ + Namespace.templating + ].readTemplates('app', packageType) logger.info('Apply Templates') - const appliedTemplates = applyTemplates(templates, { + const data = { + nodeInLayersCoreVersion: + await context.services[ + Namespace.templating + ].getNodeInLayersCoreVersion(), packageName, appName, - }) + } + const appliedTemplates = applyTemplates(templates, data) logger.info('Writing templates') - ourServices.writeTemplates(appName, appliedTemplates) + context.services[Namespace.templating].writeTemplates( + appName, + appliedTemplates, + { ignoreNameInDir: true } + ) logger.info('Operation complete') } return { diff --git a/src/app/libs.ts b/src/app/libs.ts deleted file mode 100644 index c8d281e..0000000 --- a/src/app/libs.ts +++ /dev/null @@ -1,22 +0,0 @@ -import kebabCase from 'lodash/kebabCase.js' -import merge from 'lodash/merge.js' -import startCase from 'lodash/startCase.js' -import { FinalizedTemplate, TemplatedFile } from '../package/types.js' -import { applyTemplates as packageApplyTemplates } from '../package/libs.js' - -const applyTemplates = ( - templates: readonly TemplatedFile[], - data: { appName: string; packageName: string } -): readonly FinalizedTemplate[] => { - const appNameTitleCase = startCase(data.appName).replaceAll(' ', '') - return packageApplyTemplates( - templates, - merge(data, { - appNameTitleCase, - }) - ) -} - -const createValidAppName = kebabCase - -export { applyTemplates, createValidAppName } diff --git a/src/app/types.ts b/src/app/types.ts index b934eed..1be7b1c 100644 --- a/src/app/types.ts +++ b/src/app/types.ts @@ -1,8 +1,4 @@ -import { - PackageType, - TemplatedFile, - FinalizedTemplate, -} from '../package/types.js' +import { PackageType } from '../templating/types.js' import { Namespace } from '../types.js' type AppServices = Readonly<{ @@ -10,11 +6,6 @@ type AppServices = Readonly<{ doesAppAlreadyExist: (appName: string) => boolean getPackageName: () => Promise getPackageType: () => Promise - readTemplates: ({ packageType }) => Promise - writeTemplates: ( - appName: string, - templates: readonly FinalizedTemplate[] - ) => void }> type AppServicesLayer = Readonly<{ diff --git a/src/config.ts b/src/config.ts index 4383348..dfc8e8e 100644 --- a/src/config.ts +++ b/src/config.ts @@ -13,6 +13,7 @@ const create = async (options: { environment: 'prod', [CoreNamespace.root]: { apps: [ + await import('./templating/index.js'), await import('./package/index.js'), await import('./app/index.js'), await import('./system/index.js'), diff --git a/src/index.ts b/src/index.ts index a7e8123..e0ec51b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,5 +2,4 @@ import { Namespace } from './types.js' const name = Namespace.root -export * as nilPackage from './package/index.js' export { name } diff --git a/src/package/features.ts b/src/package/features.ts index 3bfa34d..918eda7 100644 --- a/src/package/features.ts +++ b/src/package/features.ts @@ -1,12 +1,17 @@ -import { FeaturesDependencies, Config } from '@node-in-layers/core/index.js' +import { FeaturesContext, Config } from '@node-in-layers/core/index.js' import { Namespace } from '../types.js' +import { applyTemplates, createValidName } from '../templating/libs.js' +import { TemplatingServicesLayer } from '../templating/types.js' import { PackageServicesLayer, PackageType } from './types.js' -import { applyTemplates, createValidPackageName } from './libs.js' const create = ( - dependencies: FeaturesDependencies + context: FeaturesContext< + Config, + PackageServicesLayer & TemplatingServicesLayer + > ) => { - const ourServices = dependencies.services[Namespace.package] + const ourServices = context.services[Namespace.package] + const templatingServices = context.services[Namespace.templating] const createPackage = async ({ packageName, packageType, @@ -14,24 +19,37 @@ const create = ( packageName: string packageType: PackageType }) => { - packageName = createValidPackageName(packageName) - const logger = dependencies.log.getLogger('nil-toolkit:createNewPackage') + packageName = createValidName(packageName) + const logger = context.log.getLogger('nil-toolkit:createPackage') logger.info('Creating package directory') - ourServices.createPackageDirectory(packageName) + templatingServices.createDirectory(packageName) logger.info('Reading templates for all package types') - const generalTemplates = await ourServices.readGeneralTemplates() + const generalTemplates = await templatingServices.readTemplates( + 'package', + 'all' + ) logger.info(`Reading templates for ${packageType} package types`) - const specificTemplates = await ourServices.readTemplates(packageType) + const specificTemplates = await templatingServices.readTemplates( + 'package', + packageType + ) const templates = generalTemplates.concat(specificTemplates) logger.info(`Apply templates`) - const appliedTemplates = applyTemplates(templates, { packageName }) - logger.info( - `Writing templates to ${dependencies.constants.workingDirectory}` - ) - ourServices.writeTemplates(packageName, appliedTemplates) + const data = { + nodeInLayersCoreVersion: + await context.services[ + Namespace.templating + ].getNodeInLayersCoreVersion(), + packageName, + } + const appliedTemplates = applyTemplates(templates, data) + logger.info(`Writing templates to ${context.constants.workingDirectory}`) + templatingServices.writeTemplates(packageName, appliedTemplates) if (packageType === PackageType.typescript) { logger.info(`Running NPM Install`) ourServices.executeNpm(packageName, 'install') + logger.info(`Building package`) + ourServices.executeNpm(packageName, 'run build') } logger.info(`Running NPM Prettier`) ourServices.executeNpm(packageName, 'run prettier') diff --git a/src/package/libs.ts b/src/package/libs.ts deleted file mode 100644 index de60028..0000000 --- a/src/package/libs.ts +++ /dev/null @@ -1,27 +0,0 @@ -import kebabCase from 'lodash/kebabCase.js' -import startCase from 'lodash/startCase.js' -import hb from 'handlebars' -import { FinalizedTemplate, TemplatedFile } from './types.js' - -const applyTemplates = ( - templates: readonly TemplatedFile[], - data: object & { packageName: string } -): readonly FinalizedTemplate[] => { - const templateData = { - ...data, - packageName: data.packageName, - packageNameTitleCase: startCase(data.packageName).replaceAll(' ', ''), - } - return templates.map(t => { - const template = hb.compile(t.sourceData) - const templatedData = template(templateData) - return { - relativePath: t.relativePath, - templatedData, - } - }) -} - -const createValidPackageName = kebabCase - -export { applyTemplates, createValidPackageName } diff --git a/src/package/services.ts b/src/package/services.ts index f762cfc..7d724bd 100644 --- a/src/package/services.ts +++ b/src/package/services.ts @@ -1,75 +1,7 @@ -import { fileURLToPath } from 'url' -import { dirname } from 'path' -import path from 'node:path' import exec from 'node:child_process' -import * as glob from 'glob' -import { ServicesDependencies } from '@node-in-layers/core/index.js' -import { FinalizedTemplate, PackageServices, TemplatedFile } from './types.js' - -const __filename = fileURLToPath(import.meta.url) -const __dirname = dirname(__filename) - -const create = (dependencies: ServicesDependencies): PackageServices => { - const createPackageDirectory = (packageName: string) => { - const fullPath = path.join( - dependencies.constants.workingDirectory, - packageName - ) - if (dependencies.node.fs.existsSync(fullPath)) { - throw new Error(`${fullPath} already exists. Must be a new directory.`) - } - dependencies.node.fs.mkdirSync(fullPath) - } - - const _readAllTemplateFiles = async ( - subDirectory: string - ): Promise => { - const templatePath = path.join( - __dirname, - `../templates/package/${subDirectory}/**/*` - ) - const paths = (await glob.glob(templatePath, { dot: true })).filter(p => - dependencies.node.fs.lstatSync(p).isFile() - ) - return paths.map(sourceLocation => { - const dirA = path.join(__dirname, `../templates/package/${subDirectory}`) - const relativePath = path.relative(dirA, sourceLocation) - const sourceData = dependencies.node.fs.readFileSync( - sourceLocation, - 'utf-8' - ) - return { - relativePath, - sourceData, - } - }) - } - - const readGeneralTemplates = () => { - return _readAllTemplateFiles('all') - } - - const readTemplates = _readAllTemplateFiles - - const writeTemplates = ( - packageName: string, - templates: readonly FinalizedTemplate[] - ): void => { - templates.forEach(t => { - const finalLocation = path - .join( - dependencies.constants.workingDirectory, - packageName, - t.relativePath - ) - .replaceAll('.handlebars', '') - .replaceAll('PACKAGE_NAME', packageName) - const dirPath = path.dirname(finalLocation) - dependencies.node.fs.mkdirSync(dirPath, { recursive: true }) - dependencies.node.fs.writeFileSync(finalLocation, t.templatedData) - }) - } +import { PackageServices } from './types.js' +const create = (): PackageServices => { const executeNpm = ( packageName: string, command: string, @@ -82,10 +14,6 @@ const create = (dependencies: ServicesDependencies): PackageServices => { } return { - createPackageDirectory, - readTemplates, - readGeneralTemplates, - writeTemplates, executeNpm, } } diff --git a/src/package/types.ts b/src/package/types.ts index de885fb..8775d4b 100644 --- a/src/package/types.ts +++ b/src/package/types.ts @@ -1,13 +1,7 @@ import { Namespace } from '../types.js' +import { PackageType } from '../templating/types.js' type PackageServices = Readonly<{ - createPackageDirectory: (packageName: string) => void - readGeneralTemplates: () => Promise - readTemplates: (packageType: PackageType) => Promise - writeTemplates: ( - packageName: string, - templates: readonly Required[] - ) => void executeNpm: (packageName: string, command: string, args?: string[]) => void }> @@ -15,22 +9,6 @@ type PackageServicesLayer = Readonly<{ [Namespace.package]: PackageServices }> -type TemplatedFile = { - relativePath: string - sourceData: string -} - -type FinalizedTemplate = Readonly<{ - relativePath: string - templatedData: string -}> - -enum PackageType { - typescript = 'typescript', - esm = 'esm', - commonjs = 'commonjs', -} - type PackageFeatures = Readonly<{ createPackage: ({ packageName, @@ -49,8 +27,6 @@ export { PackageServices, PackageServicesLayer, PackageType, - TemplatedFile, - FinalizedTemplate, PackageFeatures, PackageFeaturesLayer, } diff --git a/src/system/index.ts b/src/system/index.ts index 3d94456..5479cbd 100644 --- a/src/system/index.ts +++ b/src/system/index.ts @@ -1,14 +1,81 @@ +import { Config, FeaturesContext } from '@node-in-layers/core' import { Namespace } from '../types.js' +import { + PackageFeaturesLayer, + PackageServicesLayer, + PackageType, +} from '../package/types.js' +import { applyTemplates, createValidName } from '../templating/libs.js' +import { TemplatingServicesLayer } from '../templating/types.js' +import { SystemServices, SystemServicesLayer } from './types.js' const services = { - create: () => { + create: (): SystemServices => { return {} }, } const features = { - create: () => { - return {} + create: ( + context: FeaturesContext< + Config, + SystemServicesLayer & TemplatingServicesLayer & PackageServicesLayer, + PackageFeaturesLayer + > + ) => { + const templatingServices = + context.services['@node-in-layers/toolkit/templating'] + const packageServices = context.services['@node-in-layers/toolkit/package'] + + const createSystem = async ({ systemName, systemType }) => { + systemName = createValidName(systemName) + const logger = context.log.getLogger('nil-toolkit:createSystem') + logger.info('Creating package first') + await context.features['@node-in-layers/toolkit/package'].createPackage({ + packageName: systemName, + packageType: systemType, + }) + // Now we override the package with system related data. + logger.info('Overriding package data with system data') + const specificTemplates = await templatingServices.readTemplates( + 'system', + systemType + ) + const generalTemplates = await templatingServices.readTemplates( + 'system', + 'all' + ) + const templates = generalTemplates.concat(specificTemplates) + logger.info(`Apply templates`) + const data = { + nodeInLayersCoreVersion: + await context.services[ + Namespace.templating + ].getNodeInLayersCoreVersion(), + systemName, + appName: systemName, + packageName: systemName, + } + const appliedTemplates = applyTemplates(templates, data) + logger.info(`Writing templates to ${context.constants.workingDirectory}`) + templatingServices.writeTemplates(systemName, appliedTemplates) + if (systemType === PackageType.typescript) { + logger.info(`Running NPM Install`) + packageServices.executeNpm(systemName, 'install') + logger.info(`Building system`) + packageServices.executeNpm(systemName, 'run build') + } + logger.info(`Running NPM Prettier`) + packageServices.executeNpm(systemName, 'run prettier') + logger.info(`Running NPM Eslint`) + packageServices.executeNpm(systemName, 'run eslint') + logger.info(`New package complete`) + return + } + + return { + createSystem, + } }, } diff --git a/src/system/types.ts b/src/system/types.ts new file mode 100644 index 0000000..11d30c1 --- /dev/null +++ b/src/system/types.ts @@ -0,0 +1,29 @@ +import { Namespace } from '../types.js' +import { PackageType } from '../package/types.js' + +type SystemServices = Readonly + +type SystemServicesLayer = Readonly<{ + [Namespace.system]: SystemServices +}> + +type SystemFeatures = Readonly<{ + createSystem: ({ + systemName, + systemType, + }: { + systemName: string + systemType: PackageType + }) => Promise +}> + +type SystemFeaturesLayer = Readonly<{ + [Namespace.system]: SystemFeatures +}> + +export { + SystemFeatures, + SystemFeaturesLayer, + SystemServices, + SystemServicesLayer, +} diff --git a/src/templating/features.ts b/src/templating/features.ts new file mode 100644 index 0000000..1b6e583 --- /dev/null +++ b/src/templating/features.ts @@ -0,0 +1,15 @@ +import { Config, FeaturesContext } from '@node-in-layers/core/index.js' +import { TemplatingServicesLayer, TemplatingFeaturesLayer } from './types.js' + +const create = ( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + context: FeaturesContext< + Config, + TemplatingServicesLayer, + TemplatingFeaturesLayer + > +) => { + return {} +} + +export { create } diff --git a/src/templating/index.ts b/src/templating/index.ts new file mode 100644 index 0000000..885437f --- /dev/null +++ b/src/templating/index.ts @@ -0,0 +1,7 @@ +import { Namespace } from '../types.js' + +export * as services from './services.js' +export * as features from './features.js' + +const name = Namespace.templating +export { name } diff --git a/src/templating/libs.ts b/src/templating/libs.ts new file mode 100644 index 0000000..f742169 --- /dev/null +++ b/src/templating/libs.ts @@ -0,0 +1,44 @@ +import kebabCase from 'lodash/kebabCase.js' +import startCase from 'lodash/startCase.js' +import hb from 'handlebars' +import { FinalizedTemplate, TemplatedFile } from './types.js' + +const _getProperty = (key: string, value?: string) => { + if (!value) { + return {} + } + return { + [key]: value, + [`${key}TitleCase`]: startCase(value).replaceAll(' ', ''), + } +} + +const applyTemplates = ( + templates: readonly TemplatedFile[], + data: object & { + nodeInLayersCoreVersion: string + packageName?: string + appName?: string + systemName?: string + } +): readonly FinalizedTemplate[] => { + const templateData = { + ...data, + nodeInLayersCoreVersion: data.nodeInLayersCoreVersion, + ..._getProperty('packageName', data.packageName), + ..._getProperty('appName', data.appName), + ..._getProperty('systemName', data.appName), + } + return templates.map(t => { + const template = hb.compile(t.sourceData) + const templatedData = template(templateData) + return { + relativePath: t.relativePath, + templatedData, + } + }) +} + +const createValidName = kebabCase + +export { applyTemplates, createValidName } diff --git a/src/templating/services.ts b/src/templating/services.ts new file mode 100644 index 0000000..47022dd --- /dev/null +++ b/src/templating/services.ts @@ -0,0 +1,96 @@ +import { fileURLToPath } from 'url' +import path, { dirname } from 'node:path' +import * as glob from 'glob' +import { ServicesContext } from '@node-in-layers/core' +import { PackageType } from '../package/types.js' +import { TemplatingServices, TemplatedFile } from './types.js' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +const create = (context: ServicesContext): TemplatingServices => { + const _getToolkitPackageJsonPath = async (): Promise => { + const wd = path.join(__dirname, '../../package.json') + return (await glob.glob(wd)).find( + p => context.node.fs.lstatSync(p).isFile() && p.endsWith('package.json') + ) + } + + const getNodeInLayersCoreVersion = async () => { + const packageJsonPath = await _getToolkitPackageJsonPath() + if (!packageJsonPath) { + throw new Error(`Could not find nil-toolkit's package.json file.`) + } + const packageJson = JSON.parse( + context.node.fs.readFileSync(packageJsonPath, 'utf-8') + ) + return packageJson.dependencies['@node-in-layers/core'] + } + + const createDirectory = (name: string, options?: { inSrc: boolean }) => { + const parts = options?.inSrc + ? [context.constants.workingDirectory, 'src', name] + : [context.constants.workingDirectory, name] + const fullPath = path.join(...parts) + if (context.node.fs.existsSync(fullPath)) { + throw new Error(`${fullPath} already exists. Must be a new directory.`) + } + context.node.fs.mkdirSync(fullPath) + } + + const _readAllTemplateFiles = async ( + subDirectory: string, + packageType: PackageType | 'all' + ): Promise => { + const templatePath = path.join( + __dirname, + `./templates/${subDirectory}/${packageType}/**/*` + ) + const paths = (await glob.glob(templatePath, { dot: true })).filter(p => + context.node.fs.lstatSync(p).isFile() + ) + return paths.map(sourceLocation => { + const dirA = path.join( + __dirname, + `./templates/${subDirectory}/${packageType}` + ) + const relativePath = path.relative(dirA, sourceLocation) + const sourceData = context.node.fs.readFileSync(sourceLocation, 'utf-8') + return { + relativePath, + sourceData, + } + }) + } + + const readTemplates = _readAllTemplateFiles + + const writeTemplates = (name, templates, options): void => { + templates.forEach(t => { + const pathParts = [ + context.constants.workingDirectory, + ...(options?.ignoreNameInDir ? [] : [name]), + t.relativePath, + ] + const finalLocation = path + .join(...pathParts) + .replaceAll('.handlebars', '') + .replaceAll('PACKAGE_NAME', name) + .replaceAll('APP_NAME', name) + .replaceAll('SYSTEM_NAME', name) + const dirPath = path.dirname(finalLocation) + context.node.fs.mkdirSync(dirPath, { recursive: true }) + context.node.fs.writeFileSync(finalLocation, t.templatedData) + }) + } + + return { + createDirectory, + readTemplates, + writeTemplates, + getNodeInLayersCoreVersion, + } +} + +export { create } diff --git a/src/templates/app/typescript/src/APP_NAME/features.ts.handlebars b/src/templating/templates/app/typescript/src/APP_NAME/features.ts.handlebars similarity index 100% rename from src/templates/app/typescript/src/APP_NAME/features.ts.handlebars rename to src/templating/templates/app/typescript/src/APP_NAME/features.ts.handlebars diff --git a/src/templates/app/typescript/src/APP_NAME/index.ts.handlebars b/src/templating/templates/app/typescript/src/APP_NAME/index.ts.handlebars similarity index 100% rename from src/templates/app/typescript/src/APP_NAME/index.ts.handlebars rename to src/templating/templates/app/typescript/src/APP_NAME/index.ts.handlebars diff --git a/src/templates/app/typescript/src/APP_NAME/services.ts.handlebars b/src/templating/templates/app/typescript/src/APP_NAME/services.ts.handlebars similarity index 100% rename from src/templates/app/typescript/src/APP_NAME/services.ts.handlebars rename to src/templating/templates/app/typescript/src/APP_NAME/services.ts.handlebars diff --git a/src/templates/app/typescript/src/APP_NAME/types.ts.handlebars b/src/templating/templates/app/typescript/src/APP_NAME/types.ts.handlebars similarity index 100% rename from src/templates/app/typescript/src/APP_NAME/types.ts.handlebars rename to src/templating/templates/app/typescript/src/APP_NAME/types.ts.handlebars diff --git a/src/templates/package/all/.gitignore b/src/templating/templates/package/all/.gitignore similarity index 100% rename from src/templates/package/all/.gitignore rename to src/templating/templates/package/all/.gitignore diff --git a/src/templates/package/all/.prettierignore b/src/templating/templates/package/all/.prettierignore similarity index 100% rename from src/templates/package/all/.prettierignore rename to src/templating/templates/package/all/.prettierignore diff --git a/src/templates/package/all/.prettierrc b/src/templating/templates/package/all/.prettierrc similarity index 100% rename from src/templates/package/all/.prettierrc rename to src/templating/templates/package/all/.prettierrc diff --git a/src/templates/package/all/README.md.handlebars b/src/templating/templates/package/all/README.md.handlebars similarity index 100% rename from src/templates/package/all/README.md.handlebars rename to src/templating/templates/package/all/README.md.handlebars diff --git a/src/templates/package/esm/bin/shell.js.handlebars b/src/templating/templates/package/esm/bin/shell.js.handlebars similarity index 100% rename from src/templates/package/esm/bin/shell.js.handlebars rename to src/templating/templates/package/esm/bin/shell.js.handlebars diff --git a/src/templates/package/esm/config.js.handlebars b/src/templating/templates/package/esm/config.js.handlebars similarity index 100% rename from src/templates/package/esm/config.js.handlebars rename to src/templating/templates/package/esm/config.js.handlebars diff --git a/src/templates/package/typescript/eslint.config.mjs b/src/templating/templates/package/typescript/eslint.config.mjs similarity index 99% rename from src/templates/package/typescript/eslint.config.mjs rename to src/templating/templates/package/typescript/eslint.config.mjs index 729f6b7..825f96b 100644 --- a/src/templates/package/typescript/eslint.config.mjs +++ b/src/templating/templates/package/typescript/eslint.config.mjs @@ -21,6 +21,7 @@ const compat = new FlatCompat({ export default [ { ignores: [ + 'config.*.mjs', 'coverage/', 'dist/', 'eslint.config.mjs', diff --git a/src/templates/package/typescript/package.json.handlebars b/src/templating/templates/package/typescript/package.json.handlebars similarity index 93% rename from src/templates/package/typescript/package.json.handlebars rename to src/templating/templates/package/typescript/package.json.handlebars index 887d343..e4f7bd4 100644 --- a/src/templates/package/typescript/package.json.handlebars +++ b/src/templating/templates/package/typescript/package.json.handlebars @@ -2,7 +2,7 @@ "name": "{{packageName}}", "type": "module", "version": "1.0.0", - "description": "", + "description": "A Node In Layers Package, generated by the Node In Layers Toolkit.", "main": "index.js", "scripts": { "build": "rm -Rf ./dist && tsc -p ./tsconfig.json && cp package.json ./dist && cp README.md ./dist", @@ -61,6 +61,6 @@ "typescript": "5.3.3" }, "dependencies": { - "@node-in-layers/core": "^1.0.2" + "@node-in-layers/core": "{{nodeInLayersCoreVersion}}" } } diff --git a/src/templates/package/typescript/src/PACKAGE_NAME/features.ts.handlebars b/src/templating/templates/package/typescript/src/PACKAGE_NAME/features.ts.handlebars similarity index 55% rename from src/templates/package/typescript/src/PACKAGE_NAME/features.ts.handlebars rename to src/templating/templates/package/typescript/src/PACKAGE_NAME/features.ts.handlebars index 8c28111..f6f4eca 100644 --- a/src/templates/package/typescript/src/PACKAGE_NAME/features.ts.handlebars +++ b/src/templating/templates/package/typescript/src/PACKAGE_NAME/features.ts.handlebars @@ -1,4 +1,4 @@ -import { Config, FeaturesDependencies } from '@node-in-layers/core/index.js' +import { Config, FeaturesContext } from '@node-in-layers/core/index.js' import { {{packageNameTitleCase}}ServicesLayer, {{packageNameTitleCase}}FeaturesLayer @@ -7,7 +7,7 @@ import { const create = ( // eslint-disable-next-line @typescript-eslint/no-unused-vars - context: FeaturesDependencies + context: FeaturesContext ) => { return { } diff --git a/src/templates/package/typescript/src/PACKAGE_NAME/index.ts.handlebars b/src/templating/templates/package/typescript/src/PACKAGE_NAME/index.ts.handlebars similarity index 100% rename from src/templates/package/typescript/src/PACKAGE_NAME/index.ts.handlebars rename to src/templating/templates/package/typescript/src/PACKAGE_NAME/index.ts.handlebars diff --git a/src/templates/package/typescript/src/PACKAGE_NAME/services.ts.handlebars b/src/templating/templates/package/typescript/src/PACKAGE_NAME/services.ts.handlebars similarity index 51% rename from src/templates/package/typescript/src/PACKAGE_NAME/services.ts.handlebars rename to src/templating/templates/package/typescript/src/PACKAGE_NAME/services.ts.handlebars index 3cad97c..389af64 100644 --- a/src/templates/package/typescript/src/PACKAGE_NAME/services.ts.handlebars +++ b/src/templating/templates/package/typescript/src/PACKAGE_NAME/services.ts.handlebars @@ -1,9 +1,9 @@ -import { ServicesDependencies } from "@node-in-layers/core/index.js" +import { ServicesContext } from "@node-in-layers/core/index.js" import { {{packageNameTitleCase}}Services } from './types.js' // eslint-disable-next-line @typescript-eslint/no-unused-vars -const create = (context: ServicesDependencies) : {{packageNameTitleCase}}Services => { +const create = (context: ServicesContext) : {{packageNameTitleCase}}Services => { return { } } diff --git a/src/templates/package/typescript/src/PACKAGE_NAME/types.ts.handlebars b/src/templating/templates/package/typescript/src/PACKAGE_NAME/types.ts.handlebars similarity index 100% rename from src/templates/package/typescript/src/PACKAGE_NAME/types.ts.handlebars rename to src/templating/templates/package/typescript/src/PACKAGE_NAME/types.ts.handlebars diff --git a/src/templates/package/typescript/tsconfig.json b/src/templating/templates/package/typescript/tsconfig.json similarity index 100% rename from src/templates/package/typescript/tsconfig.json rename to src/templating/templates/package/typescript/tsconfig.json diff --git a/src/templates/system/bin/nil-shell.js b/src/templating/templates/system/all/bin/shell.mjs similarity index 82% rename from src/templates/system/bin/nil-shell.js rename to src/templating/templates/system/all/bin/shell.mjs index 9af25f8..4295057 100755 --- a/src/templates/system/bin/nil-shell.js +++ b/src/templating/templates/system/all/bin/shell.mjs @@ -5,7 +5,6 @@ import { ArgumentParser } from 'argparse' import repl from 'repl' import chalk from 'chalk' import merge from 'lodash/merge.js' -import fs from 'fs' import * as core from '@node-in-layers/core' const _parseArguments = () => { @@ -20,17 +19,9 @@ const _parseArguments = () => { } const _systemStartup = async (environment) => { - const workingDirectory = process.cwd() - const coreServices = core.services.create({ + return core.loadSystem({ environment, - workingDirectory, - fs, }) - return core.features.create({ - services: { - core: coreServices - } - }).loadSystem() } diff --git a/src/templating/templates/system/typescript/config.dev.mjs.handlebars b/src/templating/templates/system/typescript/config.dev.mjs.handlebars new file mode 100644 index 0000000..be7a6a7 --- /dev/null +++ b/src/templating/templates/system/typescript/config.dev.mjs.handlebars @@ -0,0 +1,18 @@ +import { CoreNamespace } from '@node-in-layers/core/index.js' + +const core = { + apps: await Promise.all([ + import((`./dist/{{systemName}}/index.js`)), + ]), + layerOrder: [ + 'services', + 'events', + 'features', + ], + logLevel: 'debug', + logFormat: 'json', +} + +export default () => ({ + [CoreNamespace.root]: core, +}) diff --git a/src/templating/templates/system/typescript/package.json.handlebars b/src/templating/templates/system/typescript/package.json.handlebars new file mode 100644 index 0000000..5817325 --- /dev/null +++ b/src/templating/templates/system/typescript/package.json.handlebars @@ -0,0 +1,67 @@ +{ + "name": "{{systemName}}", + "type": "module", + "version": "1.0.0", + "description": "A Node In Layers System, generated by the Node In Layers Toolkit.", + "main": "index.js", + "scripts": { + "build": "rm -Rf ./dist && tsc -p ./tsconfig.json && cp package.json ./dist && cp README.md ./dist", + "build:watch": "nodemon -e '*' --watch ./src --exec \"npm run build || exit 1\"", + "commit": "cz", + "dist": "npm run build && cd dist && npm publish", + "eslint": "eslint .", + "feature-tests": "./node_modules/.bin/cucumber-js -p default", + "prettier": "prettier --write .", + "test": "export TS_NODE_TRANSPILE_ONLY=true && export TS_NODE_PROJECT='./tsconfig.test.json' && mocha -r ts-node/register 'test/**/*.test.ts'", + "test:coverage": "nyc --all --reporter cobertura --reporter text --reporter lcov --reporter html npm run test" + }, + "config": { + "commitizen": { + "path": "./node_modules/cz-conventional-changelog" + } + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@cucumber/cucumber": "11.0.1", + "@eslint/compat": "^1.2.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "^9.12.0", + "@istanbuljs/nyc-config-typescript": "^1.0.2", + "@types/chai-as-promised": "^8.0.1", + "@types/json-stringify-safe": "^5.0.3", + "@types/lodash": "^4.17.13", + "@types/mocha": "^9.1.1", + "@types/node": "^22.9.0", + "@types/proxyquire": "^1.3.31", + "@types/sinon": "^17.0.3", + "@typescript-eslint/eslint-plugin": "8.13.0", + "@typescript-eslint/parser": "8.13.0", + "argparse": "^2.0.1", + "chai": "^4.2.0", + "chai-as-promised": "^7.1.1", + "cz-conventional-changelog": "^3.3.0", + "eslint": "9.14.0", + "eslint-config-prettier": "^9.1.0", + "eslint-import-resolver-typescript": "^3.6.3", + "eslint-plugin-functional": "~7.1.0", + "eslint-plugin-import": "^2.31.0", + "globals": "^15.12.0", + "handlebars": "^4.7.8", + "js-yaml": "^4.1.0", + "mocha": "^11.0.1", + "nodemon": "^3.1.7", + "nyc": "^17.1.0", + "prettier": "^3.3.3", + "proxyquire": "^2.1.3", + "sinon": "^19.0.2", + "sinon-chai": "^3.5.0", + "source-map-support": "^0.5.21", + "ts-node": "^10.4.0", + "typescript": "5.3.3" + }, + "dependencies": { + "@node-in-layers/core": "{{nodeInLayersCoreVersion}}", + "es-main": "^1.3.0" + } +} diff --git a/src/templating/types.ts b/src/templating/types.ts new file mode 100644 index 0000000..9035863 --- /dev/null +++ b/src/templating/types.ts @@ -0,0 +1,51 @@ +import { Namespace } from '../types.js' + +type TemplatedFile = { + relativePath: string + sourceData: string +} + +type FinalizedTemplate = Readonly<{ + relativePath: string + templatedData: string +}> + +enum PackageType { + typescript = 'typescript', + esm = 'esm', + commonjs = 'commonjs', +} + +type TemplatingServices = Readonly<{ + createDirectory: (name: string, options?: { inSrc: boolean }) => void + readTemplates: ( + name: string, + packageType: PackageType | 'all' + ) => Promise + writeTemplates: ( + packageName: string, + templates: readonly Required[], + options?: { ignoreNameInDir?: boolean } + ) => void + getNodeInLayersCoreVersion: () => Promise +}> + +type TemplatingServicesLayer = Readonly<{ + [Namespace.templating]: TemplatingServices +}> + +type TemplatingFeatures = Readonly + +type TemplatingFeaturesLayer = Readonly<{ + [Namespace.templating]: TemplatingFeatures +}> + +export { + TemplatingServices, + TemplatingServicesLayer, + TemplatingFeatures, + TemplatingFeaturesLayer, + TemplatedFile, + FinalizedTemplate, + PackageType, +} diff --git a/src/toolkit/features.ts b/src/toolkit/features.ts index 195059f..932254f 100644 --- a/src/toolkit/features.ts +++ b/src/toolkit/features.ts @@ -1,22 +1,27 @@ -import { FeaturesDependencies, Config } from '@node-in-layers/core/index.js' +import { FeaturesContext, Config } from '@node-in-layers/core/index.js' import { promiseWrap } from '@node-in-layers/core/utils.js' import { PackageFeaturesLayer } from '../package/types.js' import { AppFeaturesLayer } from '../app/types.js' +import { SystemFeaturesLayer } from '../system/types.js' import { Namespace } from '../types.js' const create = ( - dependencies: FeaturesDependencies< + context: FeaturesContext< Config, object, - PackageFeaturesLayer & AppFeaturesLayer + PackageFeaturesLayer & AppFeaturesLayer & SystemFeaturesLayer > ) => { + const createSystem = promiseWrap( + context.features[Namespace.system].createSystem + ) const createPackage = promiseWrap( - dependencies.features[Namespace.package].createPackage + context.features[Namespace.package].createPackage ) - const createApp = promiseWrap(dependencies.features[Namespace.app].createApp) + const createApp = promiseWrap(context.features[Namespace.app].createApp) return { + createSystem, createPackage, createApp, } diff --git a/src/types.ts b/src/types.ts index 7455e61..ebac794 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,6 +4,7 @@ enum Namespace { system = '@node-in-layers/toolkit/system', toolkit = '@node-in-layers/toolkit/toolkit', app = '@node-in-layers/toolkit/app', + templating = '@node-in-layers/toolkit/templating', } export { Namespace } diff --git a/tsconfig.json b/tsconfig.json index b92f1a2..c58237f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -19,6 +19,7 @@ "exclude": [ "src/index.d.ts", "src/templates", + "src/templating/templates", "node_modules", "dist", "features",