diff --git a/nx.json b/nx.json index f26b8af2e552..f32039a35ff6 100644 --- a/nx.json +++ b/nx.json @@ -2,6 +2,7 @@ "extends": "nx/presets/npm.json", "$schema": "./node_modules/nx/schemas/nx-schema.json", "nxCloudAccessToken": "ZDFmMzkyZTYtZmU5MC00MDMyLWI3NDktYjhhYWUxZWM4YTg3fHJlYWQ=", + "plugins": ["./packages/nx-infra-plugin/dist"], "namedInputs": { "metadataToolsCommonInputs": [ "{projectRoot}/**/*.ts", diff --git a/package.json b/package.json index 153a7172f5b6..270341893a3b 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "license": "MIT", "author": "Developer Express Inc.", "scripts": { + "postinstall": "npx ts-node tools/scripts/build-nx-plugin.ts || true", "devextreme:inject-descriptions-to-bundle": "dx-tools inject-descriptions --target-path ./packages/devextreme/ts/dx.all.d.ts --artifacts ./node_modules/devextreme-metadata/dist", "devextreme:inject-descriptions-to-modules": "dx-tools inject-descriptions --collapse-tags --sources ./packages/devextreme/js --artifacts ./node_modules/devextreme-metadata/dist", "devextreme:inject-descriptions": "npm run devextreme:inject-descriptions-to-bundle && npm run devextreme:inject-descriptions-to-modules", diff --git a/packages/devextreme-react/build.config.js b/packages/devextreme-react/build.config.js deleted file mode 100644 index f7ce811b5e38..000000000000 --- a/packages/devextreme-react/build.config.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - src: './src/**/*.{ts,tsx}', - testSrc: './src/**/__tests__/**/*.*', - npm: { - dist: './npm/', - package: 'package.json', - license: 'LICENSE', - readme: 'README.md' - }, - generatedComponentsDir: './src', - coreComponentsDir: './src/core', - indexFileName: './src/index.ts', - baseComponent: './core/component', - extensionComponent: './core/extension-component', - configComponent: './core/nested-option' -}; diff --git a/packages/devextreme-react/gulpfile.js b/packages/devextreme-react/gulpfile.js deleted file mode 100644 index fa897037d4e8..000000000000 --- a/packages/devextreme-react/gulpfile.js +++ /dev/null @@ -1,240 +0,0 @@ -const fs = require('fs'); -const del = require('del'); -const gulp = require('gulp'); -const shell = require('gulp-shell'); -const header = require('gulp-header'); -const ts = require('gulp-typescript'); -const config = require('./build.config'); -const path = require('path'); -const generateReactComponents = require('devextreme-internal-tools').generateReactComponents; -const { reactConfig } = require('../../tools/generators-config'); - -const GENERATE = 'generate'; -const CLEAN = 'clean'; -const GEN_RUN = 'generator.run'; -const NPM_CLEAN = 'npm.clean'; -const NPM_PACKAGE = 'npm.package'; -const NPM_LICENSE = 'npm.license'; -const NPM_BUILD_WITH_HEADERS = 'npm.license-headers'; -const NPM_README = 'npm.readme'; -const NPM_BUILD = 'npm.build'; -const NPM_BUILD_ESM = 'npm.build-esm'; -const NPM_BUILD_CJS = 'npm.build-cjs'; -const NPM_PREPARE_MODULES = 'npm.prepare-modules'; -const NPM_PACK = 'npm.pack'; - -gulp.task(CLEAN, (c) => - del([`${config.generatedComponentsDir}\\*`, `!${config.coreComponentsDir}`], c) -); - -gulp.task(GEN_RUN, (done) => { - generateReactComponents({ - metaData: JSON.parse(fs.readFileSync(require.resolve('devextreme-metadata/integration-data.json')).toString()), - components: { - baseComponent: config.baseComponent, - extensionComponent: config.extensionComponent, - configComponent: config.configComponent - }, - out: { - componentsDir: config.generatedComponentsDir, - indexFileName: config.indexFileName - }, - widgetsPackage: 'devextreme', - typeGenerationOptions: { - generateReexports: true, - generateCustomTypes: true, - }, - templatingOptions: { - quotes: 'double', - excplicitIndexInImports: true, - }, - unifiedConfig: reactConfig, - }); - - done(); -}); - -gulp.task(GENERATE, gulp.series( - CLEAN, - GEN_RUN -)); - -gulp.task(NPM_CLEAN, (c) => - del(config.npm.dist, c) -); - -gulp.task(NPM_PACKAGE, (done) => { - const pkg = require('./package.json'); - delete pkg.publishConfig; - fs.writeFileSync(path.join(config.npm.dist, 'package.json'), JSON.stringify(pkg, null, 2)) - done(); -}); - -gulp.task(NPM_LICENSE, gulp.series( - () => gulp.src(config.npm.license).pipe(gulp.dest(config.npm.dist)) -)); - -gulp.task(NPM_README, gulp.series( - () => gulp.src(config.npm.readme).pipe(gulp.dest(config.npm.dist)) -)); - -gulp.task( - NPM_BUILD_ESM, - gulp.series(() => - gulp - .src([config.src, `!${config.testSrc}`]) - .pipe(ts('tsconfig.esm.json')) - .pipe(gulp.dest(config.npm.dist + '/esm')) - ) -); - -gulp.task( - NPM_BUILD_CJS, - gulp.series(() => - gulp - .src([config.src, `!${config.testSrc}`]) - .pipe(ts('tsconfig.json')) - .pipe(gulp.dest(config.npm.dist + '/cjs')) - ) -); - -gulp.task(NPM_PREPARE_MODULES, (done) => { - const packParamsForFolders = [ - ['common'], - ['core', ['template', 'config', 'nested-option', 'component', 'extension-component']], - ['common/core'], - ['common/data'], - ['common/export'], - ]; - const modulesImportsFromIndex = fs.readFileSync( - config.npm.dist + 'esm/index.js', - 'utf8' - ); - const modulesPaths = modulesImportsFromIndex.matchAll(/from "\.\/([^;]+)";/g); - const packParamsForModules = [...modulesPaths].map(([, modulePath]) => { - const [, , moduleFilePath, moduleFileName] = - modulePath.match(/((.*)\/)?([^/]+$)/); - - return ['', [moduleFileName], moduleFilePath]; - }); - - [ ...packParamsForModules, ...packParamsForFolders].forEach( - ([folder, moduleFileNames, moduleFilePath]) => - makeModule(folder, moduleFileNames, moduleFilePath) - ); - - done(); -}); - -gulp.task(NPM_BUILD, gulp.series( - NPM_CLEAN, - gulp.parallel( - NPM_LICENSE, - NPM_README, - NPM_BUILD_ESM, - NPM_BUILD_CJS - ), - NPM_PACKAGE -)); - -gulp.task(NPM_BUILD_WITH_HEADERS, gulp.series( - NPM_BUILD, - () => { - const pkg = require('./package.json'); - const now = new Date(); - const data = { - pkg, - date: now.toDateString(), - year: now.getFullYear() - }; - - const banner = [ - '/*!', - ' * <%= pkg.name %>', - ' * Version: <%= pkg.version %>', - ' * Build date: <%= date %>', - ' *', - ' * Copyright (c) 2012 - <%= year %> Developer Express Inc. ALL RIGHTS RESERVED', - ' *', - ' * This software may be modified and distributed under the terms', - ' * of the MIT license. See the LICENSE file in the root of the project for details.', - ' *', - ' * https://github.com/DevExpress/devextreme-react', - ' */', - '\n' - ].join('\n'); - - return gulp.src(`${config.npm.dist}**/*.{ts,js}`) - .pipe(header(banner, data)) - .pipe(gulp.dest(config.npm.dist)); - } -)); - -gulp.task(NPM_PACK, gulp.series( - NPM_BUILD_WITH_HEADERS, - NPM_PREPARE_MODULES, - shell.task(['pnpm pack']) -)); - - -function makeModule(folder, moduleFileNames, moduleFilePath) { - const distFolder = path.join(__dirname, config.npm.dist); - const distModuleFolder = path.join(distFolder, folder); - const distEsmFolder = path.join(distFolder, 'esm', folder); - const moduleNames = moduleFileNames || findJsModuleFileNamesInFolder(distEsmFolder); - - try { - if (!fs.existsSync(distModuleFolder)) { - fs.mkdirSync(distModuleFolder); - } - - if (folder && fs.existsSync(path.join(distEsmFolder, 'index.js'))) { - generatePackageJsonFile(folder); - } - - moduleNames.forEach((moduleFileName) => { - fs.mkdirSync(path.join(distModuleFolder, moduleFileName)); - - generatePackageJsonFile(folder, moduleFileName, moduleFilePath); - }) - } catch (error) { - error.message = `Exception while makeModule(${folder}).\n ${error.message}`; - throw error; - } -} -function generatePackageJsonFile(folder, moduleFileName, filePath = folder) { - const moduleName = moduleFileName || ''; - const absoluteModulePath = path.join(__dirname, config.npm.dist, folder, moduleName); - const moduleFilePath = (filePath ? filePath + '/' : '') + (moduleName || 'index'); - const relativePath = path.relative( - absoluteModulePath, - path.join(__dirname, config.npm.dist, 'esm', moduleFilePath + '.js') - ); - - const relativeBase = '../'.repeat(relativePath.split('..').length - 1); - - fs.writeFileSync( - path.join(absoluteModulePath, 'package.json'), - JSON.stringify( - { - sideEffects: false, - main: `${relativeBase}cjs/${moduleFilePath}.js`, - module: `${relativeBase}esm/${moduleFilePath}.js`, - typings: `${relativeBase}cjs/${moduleFilePath}.d.ts`, - }, - null, - 2 - ) - ); -} - -function findJsModuleFileNamesInFolder(dir) { - return fs.readdirSync(dir).filter((file) => { - const filePath = path.join(dir, file); - - return !fs.statSync(filePath).isDirectory() - && file.includes('.js') - && !file.includes('index.js') - } - ).map((filePath) => path.parse(filePath).name); -} diff --git a/packages/devextreme-react/package.json b/packages/devextreme-react/package.json index 7cfc4bdac3ce..afade306aa16 100644 --- a/packages/devextreme-react/package.json +++ b/packages/devextreme-react/package.json @@ -12,10 +12,10 @@ "module": "./esm/index.js", "types": "./cjs/index.d.ts", "scripts": { - "clean": "gulp clean", - "regenerate": "pnpm run clean && gulp generate", + "clean": "nx clean devextreme-react", + "regenerate": "pnpm --workspace-root nx regenerate devextreme-react", "lint": "eslint src/core", - "pack": "gulp npm.pack", + "pack": "pnpm --workspace-root nx pack devextreme-react", "test": "jest" }, "keywords": [ @@ -69,12 +69,8 @@ "@testing-library/user-event": "14.5.2", "@types/react": "~18.0.0", "@types/react-dom": "~18.0.0", - "del": "3.0.0", "devextreme-metadata": "workspace:*", - "gulp": "4.0.2", - "gulp-header": "2.0.9", - "gulp-shell": "0.8.0", - "gulp-typescript": "5.0.1", + "devextreme-nx-infra-plugin": "workspace:*", "jest-environment-jsdom": "29.7.0", "react": "18.0.0", "react-dom": "18.0.0", @@ -92,4 +88,4 @@ "directory": "npm", "linkDirectory": true } -} \ No newline at end of file +} diff --git a/packages/devextreme-react/project.json b/packages/devextreme-react/project.json index 3cf1edcf8cc8..f1806b0c4ad0 100644 --- a/packages/devextreme-react/project.json +++ b/packages/devextreme-react/project.json @@ -4,40 +4,120 @@ "sourceRoot": "packages/devextreme-react", "projectType": "library", "targets": { - "build": { - "executor": "nx:run-script", - "dependsOn": ["^build"], + "clean": { + "executor": "devextreme-nx-infra-plugin:clean", + "options": { + "targetDirectory": "./src", + "excludePatterns": ["./src/core", "./src/common"], + "mode": "recursive" + } + }, + "regenerate": { + "executor": "devextreme-nx-infra-plugin:generate-react-components", + "dependsOn": ["devextreme-metadata:generate","clean"], "options": { - "script": "pack" + "metadataPath": "../devextreme-metadata/dist/integration-data.json", + "componentsDir": "./src", + "indexFileName": "./src/index.ts", + "baseComponent": "./core/component", + "extensionComponent": "./core/extension-component", + "configComponent": "./core/nested-option" + } + }, + "build:esm": { + "executor": "devextreme-nx-infra-plugin:build-typescript", + "dependsOn": ["npm:clean"], + "options": { + "srcPattern": "./src/**/*.{ts,tsx}", + "excludePattern": "./src/**/__tests__/**/*", + "tsconfig": "./tsconfig.esm.json", + "outDir": "./npm/esm", + "module": "esm" }, - "inputs": [ - "default" - ], - "outputs": [ - "{projectRoot}/npm" - ], - "cache": true + "outputs": ["{projectRoot}/npm/esm"], + "cache": true, + "inputs": ["default"] }, - "pack": { - "executor": "nx:run-script", + "build:cjs": { + "executor": "devextreme-nx-infra-plugin:build-typescript", + "dependsOn": ["npm:clean"], "options": { - "script": "pack" + "srcPattern": "./src/**/*.{ts,tsx}", + "excludePattern": "./src/**/__tests__/**/*", + "tsconfig": "./tsconfig.json", + "outDir": "./npm/cjs", + "module": "cjs" }, - "inputs": [ - "default" - ], - "outputs": [ - "{projectRoot}/npm" - ], - "cache": true + "outputs": ["{projectRoot}/npm/cjs"], + "cache": true, + "inputs": ["default"] }, - "regenerate": { - "executor": "nx:run-script", - "dependsOn": ["devextreme-metadata:generate"], + "npm:clean": { + "executor": "devextreme-nx-infra-plugin:clean", + "options": { + "targetDirectory": "./npm", + "mode": "simple" + } + }, + "prepare-package-json": { + "executor": "devextreme-nx-infra-plugin:prepare-package-json", + "dependsOn": ["npm:clean", "build:esm", "build:cjs"], + "options": {} + }, + "license-headers": { + "executor": "devextreme-nx-infra-plugin:add-license-headers", + "dependsOn": ["prepare-package-json"], + "options": { + "targetDirectory": "./npm", + "packageJsonPath": "./package.json" + } + }, + "npm:prepare-modules": { + "executor": "devextreme-nx-infra-plugin:prepare-submodules", + "dependsOn": ["npm:clean", "license-headers", "copy-files"], + "options": { + "distDirectory": "./npm" + } + }, + "copy-files": { + "executor": "devextreme-nx-infra-plugin:copy-files", + "dependsOn": ["prepare-package-json"], "options": { - "script": "regenerate" + "files": [ + { + "from": "./LICENSE", + "to": "./npm/LICENSE" + }, + { + "from": "./README.md", + "to": "./npm/README.md" + } + ] } }, + "pack": { + "executor": "devextreme-nx-infra-plugin:pack-npm", + "dependsOn": ["npm:prepare-modules"], + "options": { + "workingDirectory": "./npm" + }, + "outputs": ["{projectRoot}/npm/*.tgz"], + "cache": true, + "inputs": ["default"] + }, + "build": { + "executor": "devextreme-nx-infra-plugin:pack-npm", + "dependsOn": ["npm:prepare-modules", "^build"], + "options": { + "workingDirectory": "./npm" + }, + "outputs": [ + "{projectRoot}/npm", + "{projectRoot}/*.tgz" + ], + "cache": true, + "inputs": ["default"] + }, "test": { "executor": "nx:run-script", "options": { @@ -56,7 +136,6 @@ "{projectRoot}/src/**/*", "!{projectRoot}/src/**/__tests__/*", "{projectRoot}/build.config.js", - "{projectRoot}/gulpfile.js", "{projectRoot}/tsconfig*", "{workspaceRoot}/tsconfig.json" ], diff --git a/packages/nx-infra-plugin/.prettierignore b/packages/nx-infra-plugin/.prettierignore new file mode 100644 index 000000000000..ee306f539069 --- /dev/null +++ b/packages/nx-infra-plugin/.prettierignore @@ -0,0 +1,3 @@ +/dist/ +/pnpm-lock.yaml +*.json diff --git a/packages/nx-infra-plugin/.prettierrc.yaml b/packages/nx-infra-plugin/.prettierrc.yaml new file mode 100644 index 000000000000..08e20ffc0b60 --- /dev/null +++ b/packages/nx-infra-plugin/.prettierrc.yaml @@ -0,0 +1,12 @@ +arrowParens: always +bracketSameLine: true +bracketSpacing: true +endOfLine: lf +experimentalOperatorPosition: start +objectWrap: preserve +printWidth: 100 +quoteProps: preserve +semi: true +singleQuote: true +tabWidth: 2 +trailingComma: all diff --git a/packages/nx-infra-plugin/executors.json b/packages/nx-infra-plugin/executors.json new file mode 100644 index 000000000000..b429a50f06a6 --- /dev/null +++ b/packages/nx-infra-plugin/executors.json @@ -0,0 +1,44 @@ +{ + "executors": { + "generate-react-components": { + "implementation": "./src/executors/generate-react-components/executor", + "schema": "./src/executors/generate-react-components/schema.json", + "description": "Generate React components from DevExtreme metadata" + }, + "clean": { + "implementation": "./src/executors/clean/executor", + "schema": "./src/executors/clean/schema.json", + "description": "Clean directories with support for simple or recursive mode" + }, + "add-license-headers": { + "implementation": "./src/executors/add-license-headers/executor", + "schema": "./src/executors/add-license-headers/schema.json", + "description": "Add license headers to compiled files" + }, + "prepare-submodules": { + "implementation": "./src/executors/prepare-submodules/executor", + "schema": "./src/executors/prepare-submodules/schema.json", + "description": "Prepare submodule entry points with package.json files" + }, + "copy-files": { + "implementation": "./src/executors/copy-files/executor", + "schema": "./src/executors/copy-files/schema.json", + "description": "Copy files to destination" + }, + "build-typescript": { + "implementation": "./src/executors/build-typescript/executor", + "schema": "./src/executors/build-typescript/schema.json", + "description": "Build TypeScript modules (CJS or ESM) with configurable output format" + }, + "prepare-package-json": { + "implementation": "./src/executors/prepare-package-json/executor", + "schema": "./src/executors/prepare-package-json/schema.json", + "description": "Create npm distribution package.json" + }, + "pack-npm": { + "implementation": "./src/executors/pack-npm/executor", + "schema": "./src/executors/pack-npm/schema.json", + "description": "Run pnpm pack for npm distribution" + } + } +} diff --git a/packages/nx-infra-plugin/jest.config.ts b/packages/nx-infra-plugin/jest.config.ts new file mode 100644 index 000000000000..e4469f98d5f8 --- /dev/null +++ b/packages/nx-infra-plugin/jest.config.ts @@ -0,0 +1,10 @@ +export default { + displayName: 'nx-infra-plugin', + preset: '../../jest.config.base.js', + testEnvironment: 'node', + roots: ['/src'], + transform: { + '^.+\\.ts$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], +}; diff --git a/packages/nx-infra-plugin/package.json b/packages/nx-infra-plugin/package.json new file mode 100644 index 000000000000..bf1a106ada12 --- /dev/null +++ b/packages/nx-infra-plugin/package.json @@ -0,0 +1,32 @@ +{ + "name": "devextreme-nx-infra-plugin", + "version": "0.0.1", + "type": "commonjs", + "private": true, + "executors": "./dist/executors.json", + "exports": { + ".": { + "require": "./dist/index.js", + "types": "./dist/index.d.ts" + }, + "./package.json": "./package.json" + }, + "dependencies": { + "glob": "10.4.5", + "rimraf": "3.0.2" + }, + "devDependencies": { + "@types/node": "^18.0.0", + "typescript": "4.9.5", + "prettier": "catalog:tools", + "@types/jest": "29.5.12", + "ts-jest": "29.1.3" + }, + "scripts": { + "format:check": "prettier --check .", + "format": "prettier --write .", + "build": "npx ts-node ../../tools/scripts/build-nx-plugin.ts --force", + "test": "pnpm --workspace-root nx test devextreme-nx-infra-plugin", + "lint": "pnpm run format:check" + } +} diff --git a/packages/nx-infra-plugin/project.json b/packages/nx-infra-plugin/project.json new file mode 100644 index 000000000000..1c0103ee6907 --- /dev/null +++ b/packages/nx-infra-plugin/project.json @@ -0,0 +1,29 @@ +{ + "name": "devextreme-nx-infra-plugin", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/nx-infra-plugin/src", + "projectType": "library", + "tags": [], + "targets": { + "build": { + "executor": "nx:run-script", + "outputs": ["{projectRoot}/dist"], + "options": { + "script": "build" + } + }, + "lint": { + "executor": "nx:run-script", + "options": { + "script": "lint" + } + }, + "test": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], + "options": { + "jestConfig": "{projectRoot}/jest.config.ts" + } + } + } +} diff --git a/packages/nx-infra-plugin/src/executors/add-license-headers/executor.e2e.spec.ts b/packages/nx-infra-plugin/src/executors/add-license-headers/executor.e2e.spec.ts new file mode 100644 index 000000000000..ed47947b1e36 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/add-license-headers/executor.e2e.spec.ts @@ -0,0 +1,337 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import executor from './executor'; +import { AddLicenseHeadersExecutorSchema } from './schema'; +import { createTempDir, cleanupTempDir, createMockContext } from '../../utils/test-utils'; +import { writeFileText, writeJson, readFileText } from '../../utils'; + +describe('AddLicenseHeadersExecutor E2E', () => { + let tempDir: string; + let context = createMockContext(); + + beforeEach(async () => { + tempDir = createTempDir('nx-license-e2e-'); + context = createMockContext({ root: tempDir }); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const npmDir = path.join(projectDir, 'npm'); + + fs.mkdirSync(npmDir, { recursive: true }); + + await writeJson(path.join(projectDir, 'package.json'), { + name: 'test-package', + version: '1.0.0', + }); + + await writeFileText( + path.join(npmDir, 'index.js'), + `export function hello() {\n return 'Hello';\n}\n`, + ); + + await writeFileText(path.join(npmDir, 'utils.js'), `export const add = (a, b) => a + b;\n`); + + fs.mkdirSync(path.join(npmDir, 'components'), { recursive: true }); + await writeFileText( + path.join(npmDir, 'components', 'button.js'), + `export const Button = () => {};\n`, + ); + + await writeFileText( + path.join(npmDir, 'types.ts'), + `export interface Config { name: string; }\n`, + ); + }); + + afterEach(() => { + cleanupTempDir(tempDir); + }); + + describe('Basic functionality', () => { + it('should add license headers to all JS and TS files', async () => { + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + + const indexContent = await readFileText(path.join(npmDir, 'index.js')); + expect(indexContent).toMatch(/^\/\*!/); + expect(indexContent).toContain('test-package'); + expect(indexContent).toContain('Version: 1.0.0'); + expect(indexContent).toContain('Developer Express Inc.'); + + const utilsContent = await readFileText(path.join(npmDir, 'utils.js')); + expect(utilsContent).toMatch(/^\/\*!/); + + const typesContent = await readFileText(path.join(npmDir, 'types.ts')); + expect(typesContent).toMatch(/^\/\*!/); + }); + + it('should add headers to nested files', async () => { + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + const buttonContent = await readFileText(path.join(npmDir, 'components', 'button.js')); + + expect(buttonContent).toMatch(/^\/\*!/); + expect(buttonContent).toContain('test-package'); + }); + + it('should preserve original file content after header', async () => { + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + const originalContent = await readFileText(path.join(npmDir, 'index.js')); + + await executor(options, context); + + const newContent = await readFileText(path.join(npmDir, 'index.js')); + + expect(newContent).toMatch(/^\/\*!/); + + expect(newContent).toContain(originalContent.trim()); + }); + }); + + describe('Idempotence', () => { + it('should not add duplicate headers on multiple runs', async () => { + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + + const result1 = await executor(options, context); + expect(result1.success).toBe(true); + + const contentAfterFirst = await readFileText(path.join(npmDir, 'index.js')); + const headerCount1 = (contentAfterFirst.match(/\/\*!/g) || []).length; + + const result2 = await executor(options, context); + expect(result2.success).toBe(true); + + const contentAfterSecond = await readFileText(path.join(npmDir, 'index.js')); + const headerCount2 = (contentAfterSecond.match(/\/\*!/g) || []).length; + + expect(headerCount1).toBe(1); + expect(headerCount2).toBe(1); + expect(contentAfterFirst).toBe(contentAfterSecond); + }); + + it('should skip files that already have license headers', async () => { + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + + await writeFileText( + path.join(npmDir, 'with-header.js'), + `/*!\n * Existing header\n */\nexport const foo = 'bar';\n`, + ); + + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + const result = await executor(options, context); + expect(result.success).toBe(true); + + const content = await readFileText(path.join(npmDir, 'with-header.js')); + + expect(content).toMatch(/^\/\*!/); + expect(content).toContain('Existing header'); + expect(content).not.toContain('test-package'); + }); + }); + + describe('Header content validation', () => { + it('should include package name in header', async () => { + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + await executor(options, context); + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + const content = await readFileText(path.join(npmDir, 'index.js')); + + expect(content).toContain('test-package'); + }); + + it('should include version in header', async () => { + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + await executor(options, context); + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + const content = await readFileText(path.join(npmDir, 'index.js')); + + expect(content).toContain('Version: 1.0.0'); + }); + + it('should include current year in header', async () => { + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + await executor(options, context); + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + const content = await readFileText(path.join(npmDir, 'index.js')); + + const currentYear = new Date().getFullYear(); + expect(content).toContain(`2012 - ${currentYear}`); + }); + + it('should include build date in header', async () => { + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + await executor(options, context); + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + const content = await readFileText(path.join(npmDir, 'index.js')); + + expect(content).toMatch(/Build date:/); + }); + }); + + describe('Error handling', () => { + it('should fail gracefully with missing package.json', async () => { + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './nonexistent-package.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(false); + }); + + it('should fail gracefully with invalid package.json', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + + await writeFileText(path.join(projectDir, 'package.json'), 'not valid json {{{}'); + + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(false); + }); + + it('should handle empty target directory', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const emptyDir = path.join(projectDir, 'empty'); + fs.mkdirSync(emptyDir, { recursive: true }); + + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './empty', + packageJsonPath: './package.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + }); + }); + + describe('Custom paths', () => { + it('should work with custom target directory', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const customDir = path.join(projectDir, 'dist'); + fs.mkdirSync(customDir, { recursive: true }); + + await writeFileText(path.join(customDir, 'custom.js'), `export const custom = true;\n`); + + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './dist', + packageJsonPath: './package.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const content = await readFileText(path.join(customDir, 'custom.js')); + expect(content).toMatch(/^\/\*!/); + expect(content).toContain('test-package'); + }); + + it('should work with custom package.json path', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + + await writeJson(path.join(projectDir, 'custom-package.json'), { + name: 'custom-package-name', + version: '2.0.0', + }); + + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './custom-package.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const npmDir = path.join(projectDir, 'npm'); + const content = await readFileText(path.join(npmDir, 'index.js')); + + expect(content).toContain('custom-package-name'); + expect(content).toContain('Version: 2.0.0'); + }); + }); + + it('should preserve formatting and whitespace', async () => { + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + + const originalContent = `export function complex() { + if (true) { + return 'formatted'; + } +} + +export const value = 42; +`; + + await writeFileText(path.join(npmDir, 'formatted.js'), originalContent); + + const options: AddLicenseHeadersExecutorSchema = { + targetDirectory: './npm', + packageJsonPath: './package.json', + }; + + await executor(options, context); + + const newContent = await readFileText(path.join(npmDir, 'formatted.js')); + + const contentWithoutHeader = newContent.replace(/^\/\*![\s\S]*?\*\/\n\n/, ''); + + expect(contentWithoutHeader).toBe(originalContent); + }); +}); diff --git a/packages/nx-infra-plugin/src/executors/add-license-headers/executor.ts b/packages/nx-infra-plugin/src/executors/add-license-headers/executor.ts new file mode 100644 index 000000000000..bbf6db8f0bbd --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/add-license-headers/executor.ts @@ -0,0 +1,125 @@ +import { PromiseExecutor, logger } from '@nx/devkit'; +import * as path from 'path'; +import { glob } from 'glob'; +import { AddLicenseHeadersExecutorSchema } from './schema'; +import { resolveProjectPath } from '../../utils/path-resolver'; +import { logError } from '../../utils/error-handler'; +import { readJson, readFileText, writeFileText } from '../../utils/file-operations'; + +const DEFAULT_TARGET_DIR = './npm'; +const DEFAULT_PACKAGE_JSON = './package.json'; + +const GLOB_PATTERN = '**/*.{ts,js}'; + +const LICENSE_MARKER = '/*!'; +const COMMENT_END = ' */'; +const COMMENT_PREFIX = ' *'; +const NEWLINE = '\n'; +const EMPTY_LINE = ''; + +const GITHUB_URL = 'https://github.com/DevExpress/devextreme-react'; + +const COPYRIGHT_START = + ' * Copyright (c) 2012 - <%= year %> Developer Express Inc. ALL RIGHTS RESERVED'; + +const BANNER_PKG_NAME = COMMENT_PREFIX + ' ' + '<%= pkg.name %>'; +const BANNER_VERSION = COMMENT_PREFIX + ' ' + 'Version: <%= pkg.version %>'; +const BANNER_BUILD_DATE = COMMENT_PREFIX + ' ' + 'Build date: <%= date %>'; +const BANNER_LICENSE_LINE1 = + COMMENT_PREFIX + ' ' + 'This software may be modified and distributed under the terms'; +const BANNER_LICENSE_LINE2 = + COMMENT_PREFIX + + ' ' + + 'of the MIT license. See the LICENSE file in the root of the project for details.'; +const BANNER_GITHUB = COMMENT_PREFIX + ' ' + GITHUB_URL; + +const TEMPLATE_REGEX = /<%=\s*(\w+(?:\.\w+)*)\s*%>/g; + +const runExecutor: PromiseExecutor = async (options, context) => { + const absoluteProjectRoot = resolveProjectPath(context); + const targetDirectory = path.join( + absoluteProjectRoot, + options.targetDirectory || DEFAULT_TARGET_DIR, + ); + const packageJsonPath = path.join( + absoluteProjectRoot, + options.packageJsonPath || DEFAULT_PACKAGE_JSON, + ); + + let pkg; + try { + pkg = await readJson(packageJsonPath); + } catch (error) { + logError('Failed to read package.json', error); + return { success: false }; + } + + const now = new Date(); + const data = { + pkg, + date: now.toDateString(), + year: now.getFullYear(), + }; + + const bannerTemplate = [ + LICENSE_MARKER, + BANNER_PKG_NAME, + BANNER_VERSION, + BANNER_BUILD_DATE, + COMMENT_PREFIX, + COPYRIGHT_START, + COMMENT_PREFIX, + BANNER_LICENSE_LINE1, + BANNER_LICENSE_LINE2, + COMMENT_PREFIX, + BANNER_GITHUB, + COMMENT_END, + EMPTY_LINE, + ].join(NEWLINE); + + const banner = renderTemplate(bannerTemplate, data); + + try { + const pattern = path.join(targetDirectory, GLOB_PATTERN); + const files = await glob(pattern); + + logger.info(`Adding license headers to ${files.length} files...`); + + await Promise.all( + files.map(async (file) => { + const content = await readFileText(file); + + if (content.startsWith(LICENSE_MARKER)) { + return; + } + + await writeFileText(file, banner + NEWLINE + content); + }), + ); + + logger.info('License headers added successfully'); + return { success: true }; + } catch (error) { + logError('Failed to add license headers', error); + return { success: false }; + } +}; + +function renderTemplate(template: string, data: unknown): string { + return template.replace(TEMPLATE_REGEX, (_match, key) => { + const keys = key.split('.'); + let value = data; + + for (const k of keys) { + if (value && typeof value === 'object' && k in value) { + value = (value as Record)[k]; + } else { + return ''; + } + } + + return String(value); + }); +} + +export default runExecutor; diff --git a/packages/nx-infra-plugin/src/executors/add-license-headers/schema.json b/packages/nx-infra-plugin/src/executors/add-license-headers/schema.json new file mode 100644 index 000000000000..d9c56f52207b --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/add-license-headers/schema.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "properties": { + "targetDirectory": { + "type": "string", + "description": "Directory containing files to add headers to", + "default": "./npm" + }, + "packageJsonPath": { + "type": "string", + "description": "Path to package.json for version info", + "default": "./package.json" + } + }, + "required": [] +} diff --git a/packages/nx-infra-plugin/src/executors/add-license-headers/schema.ts b/packages/nx-infra-plugin/src/executors/add-license-headers/schema.ts new file mode 100644 index 000000000000..f5e916cd2dc9 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/add-license-headers/schema.ts @@ -0,0 +1,4 @@ +export interface AddLicenseHeadersExecutorSchema { + targetDirectory?: string; + packageJsonPath?: string; +} diff --git a/packages/nx-infra-plugin/src/executors/build-typescript/executor.e2e.spec.ts b/packages/nx-infra-plugin/src/executors/build-typescript/executor.e2e.spec.ts new file mode 100644 index 000000000000..b19b4a0ba8b1 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/build-typescript/executor.e2e.spec.ts @@ -0,0 +1,162 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import executor from './executor'; +import { BuildTypescriptExecutorSchema } from './schema'; +import { createTempDir, cleanupTempDir, createMockContext } from '../../utils/test-utils'; +import { writeFileText, writeJson, readFileText } from '../../utils'; + +describe('BuildTypescriptExecutor E2E', () => { + let tempDir: string; + let context = createMockContext(); + + beforeEach(async () => { + tempDir = createTempDir('nx-build-ts-e2e-'); + context = createMockContext({ root: tempDir }); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + + const srcDir = path.join(projectDir, 'src'); + fs.mkdirSync(srcDir, { recursive: true }); + + await writeFileText( + path.join(srcDir, 'index.ts'), + `export function hello(name: string): string {\n return \`Hello, \${name}!\`;\n}\n`, + ); + + await writeFileText( + path.join(srcDir, 'utils.ts'), + `export const add = (a: number, b: number): number => a + b;\n`, + ); + + fs.mkdirSync(path.join(srcDir, '__tests__'), { recursive: true }); + await writeFileText( + path.join(srcDir, '__tests__', 'index.spec.ts'), + `import { hello } from '../index';\ntest('hello', () => {});\n`, + ); + + await writeJson(path.join(projectDir, 'tsconfig.esm.json'), { + compilerOptions: { + target: 'ES2020', + module: 'ESNext', + declaration: true, + declarationMap: true, + sourceMap: true, + outDir: './npm/esm', + rootDir: './src', + strict: true, + esModuleInterop: true, + skipLibCheck: true, + }, + include: ['src/**/*'], + exclude: ['**/*.spec.ts', '**/__tests__/**'], + }); + + await writeJson(path.join(projectDir, 'tsconfig.json'), { + compilerOptions: { + target: 'ES2020', + module: 'CommonJS', + declaration: true, + declarationMap: true, + sourceMap: true, + outDir: './npm/cjs', + rootDir: './src', + strict: true, + esModuleInterop: true, + skipLibCheck: true, + }, + include: ['src/**/*'], + exclude: ['**/*.spec.ts', '**/__tests__/**'], + }); + }); + + afterEach(() => { + cleanupTempDir(tempDir); + }); + + describe('ESM build', () => { + it('should compile TypeScript to ESM successfully', async () => { + const options: BuildTypescriptExecutorSchema = { + module: 'esm', + srcPattern: './src/**/*.{ts,tsx}', + excludePattern: './src/**/__tests__/**/*', + tsconfig: './tsconfig.esm.json', + outDir: './npm/esm', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const outDir = path.join(projectDir, 'npm', 'esm'); + + expect(fs.existsSync(path.join(outDir, 'index.js'))).toBe(true); + expect(fs.existsSync(path.join(outDir, 'utils.js'))).toBe(true); + + expect(fs.existsSync(path.join(outDir, 'index.d.ts'))).toBe(true); + expect(fs.existsSync(path.join(outDir, 'utils.d.ts'))).toBe(true); + + const indexContent = await readFileText(path.join(outDir, 'index.js')); + expect(indexContent).toContain('export'); + expect(indexContent).not.toContain('module.exports'); + }, 10000); + }); + + describe('CJS build', () => { + it('should compile TypeScript to CommonJS successfully', async () => { + const options: BuildTypescriptExecutorSchema = { + module: 'cjs', + srcPattern: './src/**/*.{ts,tsx}', + excludePattern: './src/**/__tests__/**/*', + tsconfig: './tsconfig.json', + outDir: './npm/cjs', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const outDir = path.join(projectDir, 'npm', 'cjs'); + + expect(fs.existsSync(path.join(outDir, 'index.js'))).toBe(true); + expect(fs.existsSync(path.join(outDir, 'utils.js'))).toBe(true); + + const indexContent = await readFileText(path.join(outDir, 'index.js')); + expect(indexContent).toContain('exports'); + expect(indexContent).not.toContain('export function'); + }, 10000); + }); + + describe('Error handling', () => { + it('should handle missing tsconfig file', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + + fs.unlinkSync(path.join(projectDir, 'tsconfig.esm.json')); + + const options: BuildTypescriptExecutorSchema = { + module: 'esm', + tsconfig: './tsconfig.esm.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(false); + }); + + it('should handle empty source directory', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + + fs.rmSync(path.join(projectDir, 'src'), { recursive: true, force: true }); + fs.mkdirSync(path.join(projectDir, 'src')); + + const options: BuildTypescriptExecutorSchema = { + module: 'esm', + }; + + const result = await executor(options, context); + + expect(result).toHaveProperty('success'); + }); + }); +}); diff --git a/packages/nx-infra-plugin/src/executors/build-typescript/executor.ts b/packages/nx-infra-plugin/src/executors/build-typescript/executor.ts new file mode 100644 index 000000000000..128daa7fd02f --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/build-typescript/executor.ts @@ -0,0 +1,120 @@ +import { PromiseExecutor, logger } from '@nx/devkit'; +import * as ts from 'typescript'; +import * as path from 'path'; +import { glob } from 'glob'; +import { BuildTypescriptExecutorSchema } from './schema'; +import { TsConfig, CompilerOptions } from '../../utils/types'; +import { resolveProjectPath } from '../../utils/path-resolver'; +import { logError } from '../../utils/error-handler'; +import { readFileText, exists, ensureDir } from '../../utils/file-operations'; + +const MODULE_TYPE_ESM = 'esm'; +const MODULE_TYPE_CJS = 'cjs'; + +const DEFAULT_MODULE_TYPE = MODULE_TYPE_ESM; +const DEFAULT_TSCONFIG_CJS = './tsconfig.json'; +const DEFAULT_TSCONFIG_ESM = './tsconfig.esm.json'; +const DEFAULT_OUT_DIR_CJS = './npm/cjs'; +const DEFAULT_OUT_DIR_ESM = './npm/esm'; +const DEFAULT_SRC_PATTERN = './src/**/*.{ts,tsx}'; + +const ERROR_COMPILATION_FAILED = 'Compilation failed'; + +const NEWLINE_CHAR = '\n'; + +async function loadTsConfig( + tsconfigPath: string, +): Promise<{ content: TsConfig; compilerOptions: CompilerOptions }> { + if (!(await exists(tsconfigPath))) { + throw new Error(`TypeScript config file not found: ${tsconfigPath}`); + } + + const tsconfigContentRaw = await readFileText(tsconfigPath); + const content = JSON.parse(tsconfigContentRaw) as TsConfig; + return { + content, + compilerOptions: content.compilerOptions || {}, + }; +} + +function formatDiagnostics(diagnostics: ts.Diagnostic[]): string[] { + return diagnostics.map((diagnostic) => { + if (diagnostic.file) { + const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); + const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, NEWLINE_CHAR); + return `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`; + } + return ts.flattenDiagnosticMessageText(diagnostic.messageText, NEWLINE_CHAR); + }); +} + +function compile(sourceFiles: string[], compilerOptions: ts.CompilerOptions): ts.Program { + return ts.createProgram(sourceFiles, compilerOptions); +} + +const runExecutor: PromiseExecutor = async (options, context) => { + const absoluteProjectRoot = resolveProjectPath(context); + const module = options.module || DEFAULT_MODULE_TYPE; + + const defaultTsconfigPath = + module === MODULE_TYPE_CJS ? DEFAULT_TSCONFIG_CJS : DEFAULT_TSCONFIG_ESM; + const tsconfigPath = path.join(absoluteProjectRoot, options.tsconfig || defaultTsconfigPath); + + const defaultOutDir = module === MODULE_TYPE_CJS ? DEFAULT_OUT_DIR_CJS : DEFAULT_OUT_DIR_ESM; + const outDir = path.join(absoluteProjectRoot, options.outDir || defaultOutDir); + + try { + const { content: tsconfigContent, compilerOptions } = await loadTsConfig(tsconfigPath); + compilerOptions.outDir = outDir; + await ensureDir(outDir); + + const srcPattern = options.srcPattern || DEFAULT_SRC_PATTERN; + const globPattern = path.join(absoluteProjectRoot, srcPattern); + const excludePattern = options.excludePattern + ? path.join(absoluteProjectRoot, options.excludePattern) + : undefined; + + const sourceFiles = await glob(globPattern, { + absolute: true, + nodir: true, + ignore: excludePattern ? [excludePattern] : [], + }); + + logger.info(`Building ${module.toUpperCase()} for ${sourceFiles.length} source files...`); + + if (sourceFiles.length === 0) { + logger.warn(`No source files matched pattern: ${srcPattern}`); + } + + const parsedConfig = ts.parseJsonConfigFileContent( + tsconfigContent, + ts.sys, + path.dirname(tsconfigPath), + ); + + const finalCompilerOptions: ts.CompilerOptions = { + ...parsedConfig.options, + outDir: compilerOptions.outDir, + paths: {}, + }; + + const program = compile(sourceFiles, finalCompilerOptions); + const result = program.emit(); + + if (result.emitSkipped) { + logger.error(ERROR_COMPILATION_FAILED); + const diagnostics = ts.getPreEmitDiagnostics(program).concat(result.diagnostics); + + formatDiagnostics(diagnostics).forEach((msg) => logger.error(msg)); + return { success: false }; + } + + logger.info(`✓ ${module.toUpperCase()} build completed successfully`); + return { success: true }; + } catch (error) { + logError(`Failed to build ${module.toUpperCase()}`, error); + return { success: false }; + } +}; + +export default runExecutor; diff --git a/packages/nx-infra-plugin/src/executors/build-typescript/schema.json b/packages/nx-infra-plugin/src/executors/build-typescript/schema.json new file mode 100644 index 000000000000..838ad534df45 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/build-typescript/schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/schema", + "type": "object", + "title": "Build TypeScript Executor", + "description": "Compile TypeScript code to CommonJS or ESM modules", + "properties": { + "module": { + "type": "string", + "description": "Target module format. 'esm' generates ES modules with import/export statements, 'cjs' generates CommonJS with require/module.exports. This affects output file structure, TypeScript compiler module settings, and determines the default tsconfig and output directory.", + "enum": ["cjs", "esm"], + "default": "esm" + }, + "srcPattern": { + "type": "string", + "description": "Glob pattern for source files to include in compilation. Supports standard glob syntax with wildcards. Default pattern includes all .ts and .tsx files in src directory recursively.", + "default": "./src/**/*.{ts,tsx}" + }, + "excludePattern": { + "type": "string", + "description": "Glob pattern for files to exclude from compilation (e.g., test files, stories). Files matching this pattern will be ignored even if they match srcPattern. Commonly used to exclude __tests__, __mocks__, or *.spec.ts files." + }, + "tsconfig": { + "type": "string", + "description": "Path to TypeScript configuration file relative to project root. If not specified, defaults to './tsconfig.json' for CJS builds or './tsconfig.esm.json' for ESM builds. The tsconfig should contain appropriate module and target settings for the desired output format." + }, + "outDir": { + "type": "string", + "description": "Output directory path relative to project root where compiled JavaScript and declaration files will be written. If not specified, defaults to './npm/cjs' for CJS builds or './npm/esm' for ESM builds. Directory will be created if it doesn't exist." + } + }, + "required": [], + "additionalProperties": false +} diff --git a/packages/nx-infra-plugin/src/executors/build-typescript/schema.ts b/packages/nx-infra-plugin/src/executors/build-typescript/schema.ts new file mode 100644 index 000000000000..7e774878893b --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/build-typescript/schema.ts @@ -0,0 +1,7 @@ +export interface BuildTypescriptExecutorSchema { + module?: 'cjs' | 'esm'; + srcPattern?: string; + excludePattern?: string; + tsconfig?: string; + outDir?: string; +} diff --git a/packages/nx-infra-plugin/src/executors/clean/executor.e2e.spec.ts b/packages/nx-infra-plugin/src/executors/clean/executor.e2e.spec.ts new file mode 100644 index 000000000000..561689c07d6e --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/clean/executor.e2e.spec.ts @@ -0,0 +1,449 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import executor from './executor'; +import { CleanExecutorSchema } from './schema'; +import { createTempDir, cleanupTempDir, createMockContext } from '../../utils/test-utils'; +import { writeFileText } from '../../utils'; + +describe('CleanExecutor E2E', () => { + let tempDir: string; + let context = createMockContext(); + + beforeEach(() => { + tempDir = createTempDir('nx-clean-e2e-'); + context = createMockContext({ root: tempDir }); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const srcDir = path.join(projectDir, 'src'); + + fs.mkdirSync(srcDir, { recursive: true }); + }); + + afterEach(() => { + cleanupTempDir(tempDir); + }); + + describe('Simple mode', () => { + beforeEach(async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const npmDir = path.join(projectDir, 'npm'); + + fs.mkdirSync(npmDir, { recursive: true }); + + await writeFileText(path.join(npmDir, 'index.js'), 'export const foo = "bar";'); + await writeFileText(path.join(npmDir, 'package.json'), '{"name": "test"}'); + await writeFileText(path.join(npmDir, 'README.md'), '# Test'); + + fs.mkdirSync(path.join(npmDir, 'esm'), { recursive: true }); + await writeFileText(path.join(npmDir, 'esm', 'index.js'), 'export * from "./foo";'); + + fs.mkdirSync(path.join(npmDir, 'cjs'), { recursive: true }); + await writeFileText(path.join(npmDir, 'cjs', 'index.js'), 'module.exports = {};'); + + fs.mkdirSync(path.join(npmDir, 'components', 'button'), { recursive: true }); + await writeFileText( + path.join(npmDir, 'components', 'button', 'index.js'), + 'export const Button = {};', + ); + }); + + it('should delete the entire directory', async () => { + const options: CleanExecutorSchema = { + targetDirectory: './npm', + mode: 'simple', + }; + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + + expect(fs.existsSync(npmDir)).toBe(true); + expect(fs.existsSync(path.join(npmDir, 'index.js'))).toBe(true); + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(npmDir)).toBe(false); + }); + + it('should delete all files and subdirectories recursively', async () => { + const options: CleanExecutorSchema = { + targetDirectory: './npm', + mode: 'simple', + }; + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const npmDir = path.join(projectDir, 'npm'); + + expect(fs.existsSync(path.join(npmDir, 'esm', 'index.js'))).toBe(true); + expect(fs.existsSync(path.join(npmDir, 'cjs', 'index.js'))).toBe(true); + expect(fs.existsSync(path.join(npmDir, 'components', 'button', 'index.js'))).toBe(true); + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(npmDir)).toBe(false); + }); + + it('should succeed when directory does not exist', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const npmDir = path.join(projectDir, 'npm'); + + fs.rmSync(npmDir, { recursive: true, force: true }); + + const options: CleanExecutorSchema = { + targetDirectory: './npm', + mode: 'simple', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + }); + + it('should not affect other directories', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + + const srcDir = path.join(projectDir, 'src'); + const distDir = path.join(projectDir, 'dist'); + + await writeFileText(path.join(srcDir, 'index.ts'), 'export const foo = "bar";'); + + fs.mkdirSync(distDir); + await writeFileText(path.join(distDir, 'output.js'), 'compiled'); + + const options: CleanExecutorSchema = { + targetDirectory: './npm', + mode: 'simple', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(projectDir, 'npm'))).toBe(false); + + expect(fs.existsSync(path.join(srcDir, 'index.ts'))).toBe(true); + expect(fs.existsSync(path.join(distDir, 'output.js'))).toBe(true); + }); + + it('should use simple mode by default', async () => { + const options: CleanExecutorSchema = { + targetDirectory: './npm', + }; + + const npmDir = path.join(tempDir, 'packages', 'test-lib', 'npm'); + + expect(fs.existsSync(npmDir)).toBe(true); + + const result = await executor(options, context); + + expect(result.success).toBe(true); + expect(fs.existsSync(npmDir)).toBe(false); + }); + }); + + describe('Recursive mode', () => { + beforeEach(async () => { + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + await writeFileText(path.join(srcDir, 'button.tsx'), 'export const Button = () => {};'); + await writeFileText(path.join(srcDir, 'text-box.tsx'), 'export const TextBox = () => {};'); + await writeFileText(path.join(srcDir, 'index.ts'), 'export * from "./button";'); + + fs.mkdirSync(path.join(srcDir, 'core'), { recursive: true }); + await writeFileText(path.join(srcDir, 'core', 'component.tsx'), 'export class Component {}'); + await writeFileText(path.join(srcDir, 'core', 'config.tsx'), 'export class Config {}'); + + fs.mkdirSync(path.join(srcDir, 'data'), { recursive: true }); + await writeFileText(path.join(srcDir, 'data', 'grid.tsx'), 'export const Grid = () => {};'); + }); + + it('should clean all files in target directory', async () => { + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: ['./src/core'], + mode: 'recursive', + }; + + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + expect(fs.existsSync(path.join(srcDir, 'button.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'text-box.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'index.ts'))).toBe(true); + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'button.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'text-box.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'index.ts'))).toBe(false); + }); + + it('should preserve excluded directories', async () => { + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: ['./src/core'], + mode: 'recursive', + }; + + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'core'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'core', 'component.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'core', 'config.tsx'))).toBe(true); + }); + + it('should clean nested directories', async () => { + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: ['./src/core'], + mode: 'recursive', + }; + + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + expect(fs.existsSync(path.join(srcDir, 'data', 'grid.tsx'))).toBe(true); + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'data'))).toBe(false); + }); + + it('should preserve multiple excluded directories', async () => { + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + fs.mkdirSync(path.join(srcDir, 'common'), { recursive: true }); + await writeFileText(path.join(srcDir, 'common', 'utils.ts'), 'export const utils = {};'); + + fs.mkdirSync(path.join(srcDir, 'types'), { recursive: true }); + await writeFileText(path.join(srcDir, 'types', 'index.d.ts'), 'export type Foo = string;'); + + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: ['./src/core', './src/common', './src/types'], + mode: 'recursive', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'core', 'component.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'common', 'utils.ts'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'types', 'index.d.ts'))).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'button.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'data'))).toBe(false); + }); + + it('should handle nested exclude patterns', async () => { + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + fs.mkdirSync(path.join(srcDir, 'core', 'internal'), { recursive: true }); + await writeFileText( + path.join(srcDir, 'core', 'internal', 'impl.tsx'), + 'export const impl = {};', + ); + + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: ['./src/core'], + mode: 'recursive', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'core', 'internal', 'impl.tsx'))).toBe(true); + }); + + it('should clean all files when no exclude patterns specified', async () => { + const options: CleanExecutorSchema = { + targetDirectory: './src', + mode: 'recursive', + }; + + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'button.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'core'))).toBe(false); + }); + + it('should handle absolute exclude paths', async () => { + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + const absoluteCorePath = path.join(srcDir, 'core'); + + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: [absoluteCorePath], + mode: 'recursive', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'core', 'component.tsx'))).toBe(true); + }); + }); + + describe('Shallow mode', () => { + beforeEach(async () => { + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + await writeFileText(path.join(srcDir, 'button.tsx'), 'export const Button = () => {};'); + await writeFileText(path.join(srcDir, 'text-box.tsx'), 'export const TextBox = () => {};'); + await writeFileText(path.join(srcDir, 'index.ts'), 'export * from "./button";'); + + fs.mkdirSync(path.join(srcDir, 'core'), { recursive: true }); + await writeFileText(path.join(srcDir, 'core', 'component.tsx'), 'export class Component {}'); + await writeFileText(path.join(srcDir, 'core', 'config.tsx'), 'export class Config {}'); + + fs.mkdirSync(path.join(srcDir, 'common'), { recursive: true }); + await writeFileText(path.join(srcDir, 'common', 'utils.ts'), 'export const utils = {};'); + + fs.mkdirSync(path.join(srcDir, 'data'), { recursive: true }); + await writeFileText(path.join(srcDir, 'data', 'grid.tsx'), 'export const Grid = () => {};'); + }); + + it('should remove only first-level items', async () => { + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: ['./src/core', './src/common'], + mode: 'shallow', + }; + + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + expect(fs.existsSync(path.join(srcDir, 'button.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'text-box.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'index.ts'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'data'))).toBe(true); + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'button.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'text-box.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'index.ts'))).toBe(false); + + expect(fs.existsSync(path.join(srcDir, 'data'))).toBe(false); + + expect(fs.existsSync(path.join(srcDir, 'core'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'core', 'component.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'core', 'config.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'common'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'common', 'utils.ts'))).toBe(true); + }); + + it('should preserve specific files at root level', async () => { + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + const indexPath = path.join(srcDir, 'index.ts'); + + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: ['./src/core', './src/common', indexPath], + mode: 'shallow', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(indexPath)).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'button.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'text-box.tsx'))).toBe(false); + + expect(fs.existsSync(path.join(srcDir, 'core'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'common'))).toBe(true); + }); + + it('should handle non-existent directory', async () => { + const options: CleanExecutorSchema = { + targetDirectory: './nonexistent', + excludePatterns: [], + mode: 'shallow', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + }); + + it('should remove all items when no exclusions', async () => { + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + const options: CleanExecutorSchema = { + targetDirectory: './src', + mode: 'shallow', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'button.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'core'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'common'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'data'))).toBe(false); + }); + + it('should handle relative exclude paths', async () => { + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: ['./src/core', './src/common'], + mode: 'shallow', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'core', 'component.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'common', 'utils.ts'))).toBe(true); + }); + + it('should handle absolute path exclusions', async () => { + const srcDir = path.join(tempDir, 'packages', 'test-lib', 'src'); + + const coreDir = path.join(srcDir, 'core'); + const commonDir = path.join(srcDir, 'common'); + const indexFile = path.join(srcDir, 'index.ts'); + + const options: CleanExecutorSchema = { + targetDirectory: './src', + excludePatterns: [coreDir, commonDir, indexFile], + mode: 'shallow', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(coreDir)).toBe(true); + expect(fs.existsSync(commonDir)).toBe(true); + expect(fs.existsSync(indexFile)).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'button.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'text-box.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'data'))).toBe(false); + }); + }); +}); diff --git a/packages/nx-infra-plugin/src/executors/clean/executor.ts b/packages/nx-infra-plugin/src/executors/clean/executor.ts new file mode 100644 index 000000000000..b2e56debb2f8 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/clean/executor.ts @@ -0,0 +1,117 @@ +import { PromiseExecutor, logger } from '@nx/devkit'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as rimraf from 'rimraf'; +import { glob } from 'glob'; +import { CleanExecutorSchema } from './schema'; +import { resolveProjectPath } from '../../utils/path-resolver'; +import { logError } from '../../utils/error-handler'; + +const CLEAN_MODE_SIMPLE = 'simple'; +const CLEAN_MODE_SHALLOW = 'shallow'; +const CLEAN_MODE_RECURSIVE = 'recursive'; + +const DEFAULT_TARGET_DIR = './src'; +const DEFAULT_CLEAN_MODE = CLEAN_MODE_SIMPLE; + +const GLOB_ALL_FILES = '**/*'; + +function resolveExcludePaths(patterns: string[], absoluteProjectRoot: string): string[] { + return patterns.map((pattern) => + path.isAbsolute(pattern) ? pattern : path.join(absoluteProjectRoot, pattern), + ); +} + +function shouldPreservePath( + filePath: string, + excludePaths: string[], + exactMatch: boolean, +): boolean { + const normalized = path.normalize(filePath); + + return excludePaths.some((excludePath) => { + const normalizedExclude = path.normalize(excludePath); + return exactMatch ? normalized === normalizedExclude : normalized.startsWith(normalizedExclude); + }); +} + +function cleanSimple(targetDirectory: string): void { + if (fs.existsSync(targetDirectory)) { + fs.rmSync(targetDirectory, { recursive: true, force: true }); + } +} + +function cleanShallow(targetDirectory: string, absoluteExcludePaths: string[]): void { + if (!fs.existsSync(targetDirectory)) { + return; + } + + const entries = fs.readdirSync(targetDirectory, { withFileTypes: true }); + + for (const entry of entries) { + const fullPath = path.join(targetDirectory, entry.name); + + if (!shouldPreservePath(fullPath, absoluteExcludePaths, true)) { + fs.rmSync(fullPath, { recursive: true, force: true }); + } + } +} + +async function cleanRecursive( + targetDirectory: string, + absoluteExcludePaths: string[], +): Promise { + const filesToDelete = await glob(GLOB_ALL_FILES, { + cwd: targetDirectory, + dot: true, + absolute: true, + }); + + const filteredFiles = filesToDelete.filter( + (file) => !shouldPreservePath(file, absoluteExcludePaths, false), + ); + + for (const file of filteredFiles) { + rimraf.sync(file); + } +} + +const runExecutor: PromiseExecutor = async (options, context) => { + const absoluteProjectRoot = resolveProjectPath(context); + const targetDirectory = path.join( + absoluteProjectRoot, + options.targetDirectory || DEFAULT_TARGET_DIR, + ); + const mode = options.mode || DEFAULT_CLEAN_MODE; + const excludePatterns = options.excludePatterns || []; + + logger.info(`Cleaning ${targetDirectory} in ${mode} mode...`); + + if (excludePatterns.length > 0) { + logger.info(`Excluding patterns: ${excludePatterns.join(', ')}`); + } + + try { + const absoluteExcludePaths = resolveExcludePaths(excludePatterns, absoluteProjectRoot); + + switch (mode) { + case CLEAN_MODE_SIMPLE: + cleanSimple(targetDirectory); + break; + case CLEAN_MODE_SHALLOW: + cleanShallow(targetDirectory, absoluteExcludePaths); + break; + case CLEAN_MODE_RECURSIVE: + await cleanRecursive(targetDirectory, absoluteExcludePaths); + break; + } + + logger.info(`Successfully cleaned ${targetDirectory}`); + return { success: true }; + } catch (error) { + logError(`Failed to clean ${targetDirectory}`, error); + return { success: false }; + } +}; + +export default runExecutor; diff --git a/packages/nx-infra-plugin/src/executors/clean/schema.json b/packages/nx-infra-plugin/src/executors/clean/schema.json new file mode 100644 index 000000000000..fc80aabf8b13 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/clean/schema.json @@ -0,0 +1,25 @@ +{ + "type": "object", + "properties": { + "targetDirectory": { + "type": "string", + "description": "Directory to clean", + "default": "./src" + }, + "excludePatterns": { + "type": "array", + "description": "Patterns to exclude (only used with recursive mode)", + "items": { + "type": "string" + }, + "default": [] + }, + "mode": { + "type": "string", + "enum": ["simple", "recursive", "shallow"], + "description": "Cleaning mode: 'simple' removes entire directory, 'recursive' removes contents recursively with exclusions, 'shallow' removes only first-level items with exclusions", + "default": "simple" + } + }, + "required": [] +} diff --git a/packages/nx-infra-plugin/src/executors/clean/schema.ts b/packages/nx-infra-plugin/src/executors/clean/schema.ts new file mode 100644 index 000000000000..b7d600814d9b --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/clean/schema.ts @@ -0,0 +1,5 @@ +export interface CleanExecutorSchema { + targetDirectory?: string; + excludePatterns?: string[]; + mode?: 'simple' | 'recursive' | 'shallow'; +} diff --git a/packages/nx-infra-plugin/src/executors/copy-files/executor.e2e.spec.ts b/packages/nx-infra-plugin/src/executors/copy-files/executor.e2e.spec.ts new file mode 100644 index 000000000000..72a266c51719 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/copy-files/executor.e2e.spec.ts @@ -0,0 +1,108 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import executor from './executor'; +import { CopyFilesExecutorSchema } from './schema'; +import { createTempDir, cleanupTempDir, createMockContext } from '../../utils/test-utils'; +import { writeFileText, writeJson, readFileText } from '../../utils'; + +describe('CopyFilesExecutor E2E', () => { + let tempDir: string; + let context = createMockContext(); + + beforeEach(async () => { + tempDir = createTempDir('nx-copy-e2e-'); + context = createMockContext({ root: tempDir }); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + + fs.mkdirSync(projectDir, { recursive: true }); + await writeFileText(path.join(projectDir, 'README.md'), '# Test Package\n\nThis is a test.'); + await writeFileText(path.join(projectDir, 'LICENSE'), 'MIT License\n\nCopyright...'); + await writeJson(path.join(projectDir, 'package.json'), { + name: 'test-package', + version: '1.0.0', + }); + + fs.mkdirSync(path.join(projectDir, 'docs'), { recursive: true }); + await writeFileText(path.join(projectDir, 'docs', 'guide.md'), '# Guide\n\nHow to use...'); + }); + + afterEach(() => { + cleanupTempDir(tempDir); + }); + + describe('Basic functionality', () => { + it('should copy multiple files', async () => { + const options: CopyFilesExecutorSchema = { + files: [ + { from: './README.md', to: './npm/README.md' }, + { from: './LICENSE', to: './npm/LICENSE' }, + { from: './package.json', to: './npm/package.json' }, + ], + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const npmDir = path.join(projectDir, 'npm'); + + expect(fs.existsSync(path.join(npmDir, 'README.md'))).toBe(true); + expect(fs.existsSync(path.join(npmDir, 'LICENSE'))).toBe(true); + expect(fs.existsSync(path.join(npmDir, 'package.json'))).toBe(true); + }); + + it('should preserve file content', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const originalContent = await readFileText(path.join(projectDir, 'README.md')); + + const options: CopyFilesExecutorSchema = { + files: [{ from: './README.md', to: './dist/README.md' }], + }; + + await executor(options, context); + + const copiedContent = await readFileText(path.join(projectDir, 'dist', 'README.md')); + + expect(copiedContent).toBe(originalContent); + }); + + it('should create destination directories if they do not exist', async () => { + const options: CopyFilesExecutorSchema = { + files: [{ from: './README.md', to: './output/nested/deep/README.md' }], + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const destPath = path.join(projectDir, 'output', 'nested', 'deep', 'README.md'); + + expect(fs.existsSync(destPath)).toBe(true); + }); + }); + + describe('File overwriting', () => { + it('should overwrite existing destination files', async () => { + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const distDir = path.join(projectDir, 'dist'); + fs.mkdirSync(distDir); + + await writeFileText(path.join(distDir, 'README.md'), 'Old content'); + + const options: CopyFilesExecutorSchema = { + files: [{ from: './README.md', to: './dist/README.md' }], + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const content = await readFileText(path.join(distDir, 'README.md')); + expect(content).toBe('# Test Package\n\nThis is a test.'); + expect(content).not.toBe('Old content'); + }); + }); +}); diff --git a/packages/nx-infra-plugin/src/executors/copy-files/executor.ts b/packages/nx-infra-plugin/src/executors/copy-files/executor.ts new file mode 100644 index 000000000000..23077da36f1a --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/copy-files/executor.ts @@ -0,0 +1,40 @@ +import { PromiseExecutor, logger } from '@nx/devkit'; +import * as path from 'path'; +import { CopyFilesExecutorSchema } from './schema'; +import { resolveProjectPath } from '../../utils/path-resolver'; +import { logError } from '../../utils/error-handler'; +import { copyFile, exists } from '../../utils/file-operations'; + +const ERROR_FILES_MUST_BE_ARRAY = 'Files option must be an array'; +const ERROR_FAILED_TO_COPY = 'Failed to copy files'; + +const runExecutor: PromiseExecutor = async (options, context) => { + const projectRoot = resolveProjectPath(context); + + if (!options.files || !Array.isArray(options.files)) { + logger.error(ERROR_FILES_MUST_BE_ARRAY); + return { success: false }; + } + + try { + for (const { from, to } of options.files) { + const sourcePath = path.resolve(projectRoot, from); + const destPath = path.resolve(projectRoot, to); + + if (!(await exists(sourcePath))) { + logger.error(`Source file not found: ${sourcePath}`); + return { success: false }; + } + + await copyFile(sourcePath, destPath); + logger.info(`Copied ${sourcePath} -> ${destPath}`); + } + + return { success: true }; + } catch (error) { + logError(ERROR_FAILED_TO_COPY, error); + return { success: false }; + } +}; + +export default runExecutor; diff --git a/packages/nx-infra-plugin/src/executors/copy-files/schema.json b/packages/nx-infra-plugin/src/executors/copy-files/schema.json new file mode 100644 index 000000000000..7c777b705386 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/copy-files/schema.json @@ -0,0 +1,22 @@ +{ + "type": "object", + "properties": { + "files": { + "type": "array", + "description": "Files to copy (array of {from, to})", + "items": { + "type": "object", + "properties": { + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": ["from", "to"] + } + } + }, + "required": ["files"] +} diff --git a/packages/nx-infra-plugin/src/executors/copy-files/schema.ts b/packages/nx-infra-plugin/src/executors/copy-files/schema.ts new file mode 100644 index 000000000000..9d081978ac5a --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/copy-files/schema.ts @@ -0,0 +1,6 @@ +export interface CopyFilesExecutorSchema { + files: Array<{ + from: string; + to: string; + }>; +} diff --git a/packages/nx-infra-plugin/src/executors/generate-react-components/executor.e2e.spec.ts b/packages/nx-infra-plugin/src/executors/generate-react-components/executor.e2e.spec.ts new file mode 100644 index 000000000000..42a33fb8b487 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/generate-react-components/executor.e2e.spec.ts @@ -0,0 +1,288 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import executor from './executor'; +import { GenerateReactComponentsExecutorSchema } from './schema'; +import { createTempDir, cleanupTempDir, createMockContext } from '../../utils/test-utils'; +import { writeFileText, writeJson } from '../../utils'; + +describe('GenerateReactComponentsExecutor E2E', () => { + let tempDir: string; + let context = createMockContext(); + + beforeEach(async () => { + tempDir = createTempDir('nx-gen-react-e2e-'); + context = createMockContext({ + root: tempDir, + projectName: 'react-wrappers', + projectRoot: 'packages/react-wrappers', + }); + + const projectDir = path.join(tempDir, 'packages', 'react-wrappers'); + const srcDir = path.join(projectDir, 'src'); + + fs.mkdirSync(path.join(srcDir, 'core'), { recursive: true }); + await writeFileText( + path.join(srcDir, 'core', 'component.tsx'), + `export class Component

{\n constructor(public props: P) {}\n}\n`, + ); + await writeFileText( + path.join(srcDir, 'core', 'extension-component.tsx'), + `export class ExtensionComponent {}\n`, + ); + await writeFileText( + path.join(srcDir, 'core', 'nested-option.ts'), + `export class NestedOption {}\n`, + ); + + const metadataDir = path.join(tempDir, 'packages', 'metadata', 'dist'); + fs.mkdirSync(metadataDir, { recursive: true }); + await writeJson(path.join(metadataDir, 'integration-data.json'), { + Widgets: { + dxButton: { + name: 'Button', + module: 'ui/button', + properties: [ + { name: 'text', type: 'String' }, + { name: 'type', type: 'String' }, + { name: 'onClick', type: 'Function' }, + ], + }, + dxTextBox: { + name: 'TextBox', + module: 'ui/text_box', + properties: [ + { name: 'value', type: 'String' }, + { name: 'placeholder', type: 'String' }, + ], + }, + }, + }); + }); + + afterEach(() => { + cleanupTempDir(tempDir); + }); + + describe('Basic generation workflow', () => { + it('should generate component wrappers from metadata', async () => { + const mockGenerator = jest.fn(async (config) => { + const outDir = config.out.componentsDir; + const indexFile = config.out.indexFileName; + + await writeFileText( + path.join(outDir, 'button.tsx'), + `export const Button = () =>

Button
;`, + ); + await writeFileText( + path.join(outDir, 'text-box.tsx'), + `export const TextBox = () =>
TextBox
;`, + ); + + await writeFileText( + indexFile, + `export { Button } from "./button";\nexport { TextBox } from "./text-box";\n`, + ); + }); + + jest.mock( + 'devextreme-internal-tools', + () => ({ + generateReactComponents: mockGenerator, + }), + { virtual: true }, + ); + + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + componentsDir: './src', + indexFileName: './src/index.ts', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const srcDir = path.join(tempDir, 'packages', 'react-wrappers', 'src'); + + expect(fs.existsSync(path.join(srcDir, 'core', 'component.tsx'))).toBe(true); + }); + + it('should handle missing metadata file gracefully', async () => { + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: './nonexistent/metadata.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(false); + }); + }); + + describe('Cleaning behavior', () => { + it('should clean old generated files before generation', async () => { + const srcDir = path.join(tempDir, 'packages', 'react-wrappers', 'src'); + + await writeFileText(path.join(srcDir, 'old-component.tsx'), 'old content'); + await writeFileText(path.join(srcDir, 'legacy-file.tsx'), 'legacy content'); + + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + }; + + await executor(options, context); + + expect(fs.existsSync(path.join(srcDir, 'old-component.tsx'))).toBe(false); + expect(fs.existsSync(path.join(srcDir, 'legacy-file.tsx'))).toBe(false); + }); + + it('should preserve core directory during cleaning', async () => { + const srcDir = path.join(tempDir, 'packages', 'react-wrappers', 'src'); + + await writeFileText(path.join(srcDir, 'core', 'custom.tsx'), 'custom core file'); + await writeFileText(path.join(srcDir, 'temp-generated.tsx'), 'temp'); + + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + }; + + await executor(options, context); + + expect(fs.existsSync(path.join(srcDir, 'core', 'component.tsx'))).toBe(true); + expect(fs.existsSync(path.join(srcDir, 'core', 'custom.tsx'))).toBe(true); + + expect(fs.existsSync(path.join(srcDir, 'temp-generated.tsx'))).toBe(false); + }); + + it('should preserve common directory during cleaning', async () => { + const srcDir = path.join(tempDir, 'packages', 'react-wrappers', 'src'); + + fs.mkdirSync(path.join(srcDir, 'common')); + await writeFileText(path.join(srcDir, 'common', 'utils.ts'), 'utils'); + + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + }; + + await executor(options, context); + + expect(fs.existsSync(path.join(srcDir, 'common', 'utils.ts'))).toBe(true); + }); + }); + + describe('Path resolution', () => { + it('should resolve metadata path relative to project', async () => { + const projectDir = path.join(tempDir, 'packages', 'react-wrappers'); + + await writeJson(path.join(projectDir, 'metadata.json'), { Widgets: {} }); + + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: './metadata.json', + }; + + const result = await executor(options, context); + + expect(result).toHaveProperty('success'); + }); + + it('should resolve ../ paths correctly', async () => { + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + }; + + const result = await executor(options, context); + + expect(result).toHaveProperty('success'); + }); + }); + + describe('Configuration options', () => { + it('should accept custom component paths', async () => { + const srcDir = path.join(tempDir, 'packages', 'react-wrappers', 'src'); + + fs.mkdirSync(path.join(srcDir, 'templates'), { recursive: true }); + await writeFileText(path.join(srcDir, 'templates', 'base.tsx'), 'base'); + + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + baseComponent: './templates/base', + extensionComponent: './templates/extension', + configComponent: './templates/config', + }; + + const result = await executor(options, context); + + expect(result).toHaveProperty('success'); + }); + + it('should accept custom output paths', async () => { + const projectDir = path.join(tempDir, 'packages', 'react-wrappers'); + + fs.mkdirSync(path.join(projectDir, 'generated'), { recursive: true }); + + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + componentsDir: './generated', + indexFileName: './generated/index.ts', + }; + + const result = await executor(options, context); + + expect(result).toHaveProperty('success'); + }); + }); + + describe('Multiple runs', () => { + it('should handle repeated generation cleanly', async () => { + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + }; + + const result1 = await executor(options, context); + expect(result1).toHaveProperty('success'); + + const result2 = await executor(options, context); + expect(result2).toHaveProperty('success'); + + const srcDir = path.join(tempDir, 'packages', 'react-wrappers', 'src'); + + expect(fs.existsSync(path.join(srcDir, 'core', 'component.tsx'))).toBe(true); + }); + }); + + describe('Error scenarios', () => { + it('should handle corrupted metadata gracefully', async () => { + const metadataPath = path.join( + tempDir, + 'packages', + 'metadata', + 'dist', + 'integration-data.json', + ); + + await writeFileText(metadataPath, 'not valid json {{{'); + + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(false); + }); + + it('should handle missing core templates', async () => { + const srcDir = path.join(tempDir, 'packages', 'react-wrappers', 'src'); + + fs.rmSync(path.join(srcDir, 'core'), { recursive: true, force: true }); + + const options: GenerateReactComponentsExecutorSchema = { + metadataPath: '../metadata/dist/integration-data.json', + }; + + const result = await executor(options, context); + + expect(result).toHaveProperty('success'); + expect(typeof result.success).toBe('boolean'); + }); + }); +}); diff --git a/packages/nx-infra-plugin/src/executors/generate-react-components/executor.ts b/packages/nx-infra-plugin/src/executors/generate-react-components/executor.ts new file mode 100644 index 000000000000..7d5448bdf920 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/generate-react-components/executor.ts @@ -0,0 +1,288 @@ +import { PromiseExecutor, logger, ExecutorContext } from '@nx/devkit'; +import * as fs from 'fs'; +import * as path from 'path'; +import { GenerateReactComponentsExecutorSchema } from './schema'; +import { resolveProjectPath } from '../../utils/path-resolver'; +import { logError, getErrorMessage } from '../../utils/error-handler'; +import cleanExecutor from '../clean/executor'; +import { CleanExecutorSchema } from '../clean/schema'; + +const DEFAULT_COMPONENTS_DIR = './src'; +const DEFAULT_INDEX_FILE_NAME = './src/index.ts'; +const CORE_DIR = 'core'; +const COMMON_DIR = 'common'; + +const TOOLS_DIR = 'tools'; +const GENERATORS_CONFIG_FILE = 'generators-config.js'; +const METADATA_PACKAGE = 'devextreme-metadata'; +const METADATA_FILE = 'integration-data.json'; +const INTERNAL_TOOLS_PACKAGE = 'devextreme-internal-tools'; +const GENERATION_FUNCTION = 'generateReactComponents'; + +const DEFAULT_BASE_COMPONENT = './core/component'; +const DEFAULT_EXTENSION_COMPONENT = './core/extension-component'; +const DEFAULT_CONFIG_COMPONENT = './core/nested-option'; + +const WIDGETS_PACKAGE = 'devextreme'; + +const CLEAN_MODE = 'shallow'; + +const MSG_CLEANING = '🧹 Cleaning generated components'; +const MSG_CLEANED = '✓ Successfully cleaned components directory'; +const MSG_LOADING_METADATA = '📋 Loading metadata'; +const MSG_GENERATORS_CONFIG_NOT_FOUND = + '⚠️ generators-config.js not found, proceeding without unifiedConfig'; +const MSG_LOADED_REACT_CONFIG = '✓ Loaded React configuration from generators-config.js'; +const MSG_GENERATING = '⚙️ Generating React components'; +const MSG_GENERATION_COMPLETED = '✓ Component generation completed'; +const MSG_GENERATION_SUCCESS = '✨ React component generation successful!'; +const MSG_STARTING = '🔧 Starting React component generation'; +const MSG_GENERATION_FAILED = '❌ Component generation failed'; + +const ERROR_METADATA_NOT_FOUND = + 'Could not find devextreme-metadata/integration-data.json. Please ensure devextreme-metadata is installed or provide a metadataPath option.'; +const ERROR_CLEAN_FAILED = 'Failed to clean components directory'; + +const PARENT_DIR_PREFIX = '../'; +const DOT_SLASH_PREFIX = './'; + +const ENCODING_UTF8 = 'utf-8'; + +const GENERATE_REEXPORTS = 'generateReexports'; +const GENERATE_CUSTOM_TYPES = 'generateCustomTypes'; +const QUOTES_DOUBLE = 'double'; +const EXPLICIT_INDEX_IN_IMPORTS = 'excplicitIndexInImports'; + +const EXPORT_PATTERN = /export \{/g; + +async function cleanComponentsDirectory( + absoluteComponentsDir: string, + preservePaths: string[], + context: ExecutorContext, +): Promise { + logger.info(MSG_CLEANING); + + const absoluteProjectRoot = resolveProjectPath(context); + + const relativeComponentsDir = path.relative(absoluteProjectRoot, absoluteComponentsDir); + + const cleanOptions: CleanExecutorSchema = { + targetDirectory: DOT_SLASH_PREFIX + relativeComponentsDir, + excludePatterns: preservePaths.map((currentPath) => { + if (path.isAbsolute(currentPath)) { + const relative = path.relative(absoluteProjectRoot, currentPath); + return DOT_SLASH_PREFIX + relative; + } + return currentPath; + }), + mode: CLEAN_MODE, + }; + + const result = await cleanExecutor(cleanOptions, context); + + if (result.success) { + logger.info(MSG_CLEANED); + } else { + throw new Error(ERROR_CLEAN_FAILED); + } +} + +function resolveMetadataPath( + options: GenerateReactComponentsExecutorSchema, + absoluteProjectRoot: string, + workspaceRoot: string, +): string { + if (options.metadataPath) { + return resolveCustomMetadataPath(options.metadataPath, absoluteProjectRoot, workspaceRoot); + } + + return resolveDefaultMetadataPath(); +} + +function resolveCustomMetadataPath( + metadataPath: string, + absoluteProjectRoot: string, + workspaceRoot: string, +): string { + const relativeToProject = path.resolve(absoluteProjectRoot, metadataPath); + if (fs.existsSync(relativeToProject)) { + return relativeToProject; + } + + const relativeToWorkspace = path.resolve(workspaceRoot, metadataPath); + if (fs.existsSync(relativeToWorkspace)) { + return relativeToWorkspace; + } + + if (metadataPath.startsWith(PARENT_DIR_PREFIX)) { + return path.resolve(workspaceRoot, metadataPath); + } + + return relativeToProject; +} + +function resolveDefaultMetadataPath(): string { + try { + return require.resolve(`${METADATA_PACKAGE}/${METADATA_FILE}`); + } catch (error) { + throw new Error(ERROR_METADATA_NOT_FOUND); + } +} + +function loadMetadata(metadataPath: string): any { + logger.info(MSG_LOADING_METADATA); + logger.info(` Path: ${metadataPath}`); + + if (!fs.existsSync(metadataPath)) { + throw new Error(`Metadata file not found: ${metadataPath}`); + } + + const metadataContent = fs.readFileSync(metadataPath, ENCODING_UTF8); + const metaData = JSON.parse(metadataContent); + + const widgetCount = Object.keys(metaData.Widgets || {}).length; + logger.info(`✓ Loaded ${widgetCount} widget definitions`); + + return metaData; +} + +function loadReactConfig(workspaceRoot: string): any { + const generatorsConfigPath = path.join(workspaceRoot, TOOLS_DIR, GENERATORS_CONFIG_FILE); + + if (!fs.existsSync(generatorsConfigPath)) { + logger.warn(MSG_GENERATORS_CONFIG_NOT_FOUND); + return undefined; + } + + try { + const generatorsConfig = require(generatorsConfigPath); + logger.info(MSG_LOADED_REACT_CONFIG); + return generatorsConfig.reactConfig; + } catch (error) { + logger.warn(`⚠️ Could not load generators-config.js: ${getErrorMessage(error)}`); + return undefined; + } +} + +function loadGenerationFunction(): any { + try { + const internalTools = require(INTERNAL_TOOLS_PACKAGE); + return internalTools[GENERATION_FUNCTION]; + } catch (error) { + throw new Error( + `Could not load devextreme-internal-tools. Please ensure devextreme-internal-tools is installed as a dependency. Error: ${getErrorMessage( + error, + )}`, + ); + } +} + +function buildGenerationConfig( + options: GenerateReactComponentsExecutorSchema, + componentsDir: string, + indexFileName: string, + reactConfig: any, +): any { + return { + metaData: undefined, + components: { + baseComponent: options.baseComponent || DEFAULT_BASE_COMPONENT, + extensionComponent: options.extensionComponent || DEFAULT_EXTENSION_COMPONENT, + configComponent: options.configComponent || DEFAULT_CONFIG_COMPONENT, + }, + out: { + componentsDir, + indexFileName, + }, + widgetsPackage: WIDGETS_PACKAGE, + typeGenerationOptions: { + [GENERATE_REEXPORTS]: true, + [GENERATE_CUSTOM_TYPES]: true, + }, + templatingOptions: { + quotes: QUOTES_DOUBLE, + [EXPLICIT_INDEX_IN_IMPORTS]: true, + }, + unifiedConfig: reactConfig, + }; +} + +async function executeGeneration( + generateReactComponents: any, + config: any, + metaData: any, + componentsDir: string, + indexFileName: string, +): Promise { + logger.info(MSG_GENERATING); + + config.metaData = metaData; + await generateReactComponents(config); + + logger.info(MSG_GENERATION_COMPLETED); + + if (fs.existsSync(indexFileName)) { + const indexContent = fs.readFileSync(indexFileName, ENCODING_UTF8); + const exportCount = (indexContent.match(EXPORT_PATTERN) || []).length; + logger.info(` Exports: ${exportCount}`); + } + + if (fs.existsSync(componentsDir)) { + const dirCount = fs + .readdirSync(componentsDir, { withFileTypes: true }) + .filter((entry) => entry.isDirectory() && entry.name !== CORE_DIR).length; + logger.info(` Component Directories: ${dirCount}`); + } + + logger.info(MSG_GENERATION_SUCCESS); +} + +const runExecutor: PromiseExecutor = async ( + options, + context, +) => { + const absoluteProjectRoot = resolveProjectPath(context); + const workspaceRoot = context.root; + + logger.info(MSG_STARTING); + const projectRelativePath = path.relative(workspaceRoot, absoluteProjectRoot) || DOT_SLASH_PREFIX; + logger.info(` Project root: ${projectRelativePath}`); + + try { + const componentsDir = path.resolve( + absoluteProjectRoot, + options.componentsDir || DEFAULT_COMPONENTS_DIR, + ); + const indexFileName = path.resolve( + absoluteProjectRoot, + options.indexFileName || DEFAULT_INDEX_FILE_NAME, + ); + + const coreDir = path.join(componentsDir, CORE_DIR); + const commonDir = path.join(componentsDir, COMMON_DIR); + await cleanComponentsDirectory(componentsDir, [coreDir, commonDir, indexFileName], context); + + const metadataPath = resolveMetadataPath(options, absoluteProjectRoot, workspaceRoot); + const metaData = loadMetadata(metadataPath); + + const reactConfig = loadReactConfig(workspaceRoot); + + const generateReactComponents = loadGenerationFunction(); + + const config = buildGenerationConfig(options, componentsDir, indexFileName, reactConfig); + await executeGeneration( + generateReactComponents, + config, + metaData, + componentsDir, + indexFileName, + ); + + return { success: true }; + } catch (error) { + logError(MSG_GENERATION_FAILED, error); + return { success: false }; + } +}; + +export default runExecutor; diff --git a/packages/nx-infra-plugin/src/executors/generate-react-components/schema.d.ts b/packages/nx-infra-plugin/src/executors/generate-react-components/schema.d.ts new file mode 100644 index 000000000000..1811b30b598b --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/generate-react-components/schema.d.ts @@ -0,0 +1,8 @@ +export interface GenerateReactComponentsExecutorSchema { + metadataPath?: string; + componentsDir?: string; + indexFileName?: string; + baseComponent?: string; + extensionComponent?: string; + configComponent?: string; +} diff --git a/packages/nx-infra-plugin/src/executors/generate-react-components/schema.json b/packages/nx-infra-plugin/src/executors/generate-react-components/schema.json new file mode 100644 index 000000000000..5c48579dd30c --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/generate-react-components/schema.json @@ -0,0 +1,35 @@ +{ + "type": "object", + "properties": { + "metadataPath": { + "type": "string", + "description": "Path to metadata JSON file" + }, + "componentsDir": { + "type": "string", + "description": "Output directory for generated components", + "default": "./src" + }, + "indexFileName": { + "type": "string", + "description": "Index file name", + "default": "./src/index.ts" + }, + "baseComponent": { + "type": "string", + "description": "Base component path", + "default": "./core/component" + }, + "extensionComponent": { + "type": "string", + "description": "Extension component path", + "default": "./core/extension-component" + }, + "configComponent": { + "type": "string", + "description": "Config component path", + "default": "./core/nested-option" + } + }, + "required": ["metadataPath"] +} diff --git a/packages/nx-infra-plugin/src/executors/generate-react-components/schema.ts b/packages/nx-infra-plugin/src/executors/generate-react-components/schema.ts new file mode 100644 index 000000000000..ec07683905ed --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/generate-react-components/schema.ts @@ -0,0 +1,8 @@ +export interface GenerateReactComponentsExecutorSchema { + metadataPath: string; + componentsDir?: string; + indexFileName?: string; + baseComponent?: string; + extensionComponent?: string; + configComponent?: string; +} diff --git a/packages/nx-infra-plugin/src/executors/pack-npm/executor.e2e.spec.ts b/packages/nx-infra-plugin/src/executors/pack-npm/executor.e2e.spec.ts new file mode 100644 index 000000000000..0d9b1423aeba --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/pack-npm/executor.e2e.spec.ts @@ -0,0 +1,111 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { execSync } from 'child_process'; +import executor from './executor'; +import { PackNpmExecutorSchema } from './schema'; +import { createTempDir, cleanupTempDir, createMockContext } from '../../utils/test-utils'; +import { writeFileText, writeJson } from '../../utils'; + +const PACKAGE_NAME = 'test-package'; +const PACKAGE_VERSION = '1.0.0'; +const EXPECTED_TARBALL_NAME = `${PACKAGE_NAME}-${PACKAGE_VERSION}.tgz`; +const NPM_DIR_NAME = 'npm'; +const PROJECT_NAME = 'test-lib'; +const PACKAGES_DIR = 'packages'; + +describe('PackNpmExecutor E2E', () => { + let tempDir: string; + let context = createMockContext(); + let pnpmAvailable: boolean; + + beforeAll(() => { + try { + execSync('pnpm --version', { stdio: 'ignore' }); + pnpmAvailable = true; + } catch { + pnpmAvailable = false; + } + }); + + beforeEach(async () => { + if (!pnpmAvailable) { + return; + } + + tempDir = createTempDir('nx-pack-e2e-'); + + await writeFileText( + path.join(tempDir, 'pnpm-workspace.yaml'), + `packages:\n - '${PACKAGES_DIR}/*'\n`, + ); + + await writeJson(path.join(tempDir, 'package.json'), { + name: 'test-workspace', + version: '1.0.0', + private: true, + }); + + const projectDir = path.join(tempDir, PACKAGES_DIR, PROJECT_NAME); + const npmDir = path.join(projectDir, NPM_DIR_NAME); + + fs.mkdirSync(npmDir, { recursive: true }); + + await writeJson(path.join(projectDir, 'package.json'), { + name: PACKAGE_NAME, + version: PACKAGE_VERSION, + description: 'Test package for pnpm pack', + main: './npm/index.js', + }); + + await writeJson(path.join(npmDir, 'package.json'), { + name: PACKAGE_NAME, + version: PACKAGE_VERSION, + description: 'Test package for pnpm pack', + main: './index.js', + }); + + await writeFileText(path.join(npmDir, 'index.js'), 'module.exports = { test: true };'); + + context = createMockContext({ + root: tempDir, + projectName: PROJECT_NAME, + projectRoot: path.join(PACKAGES_DIR, PROJECT_NAME), + }); + }); + + afterEach(() => { + if (!pnpmAvailable || !tempDir) { + return; + } + + cleanupTempDir(tempDir); + }); + + it('should create tarball inside npm directory', async () => { + if (!pnpmAvailable) { + console.log('Skipping test: pnpm not available'); + return; + } + + const options: PackNpmExecutorSchema = { + workingDirectory: `./${NPM_DIR_NAME}`, + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const projectDir = path.join(tempDir, PACKAGES_DIR, PROJECT_NAME); + const expectedTarballPath = path.join(projectDir, EXPECTED_TARBALL_NAME); + + console.log('projectDir:', projectDir); + console.log('Files in projectDir:', fs.readdirSync(projectDir)); + console.log('Expected tarball:', expectedTarballPath); + + expect(fs.existsSync(expectedTarballPath)).toBe(true); + + const tarballStat = fs.statSync(expectedTarballPath); + expect(tarballStat.isFile()).toBe(true); + expect(tarballStat.size).toBeGreaterThan(0); + }); +}); diff --git a/packages/nx-infra-plugin/src/executors/pack-npm/executor.ts b/packages/nx-infra-plugin/src/executors/pack-npm/executor.ts new file mode 100644 index 000000000000..25b5edc84ec3 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/pack-npm/executor.ts @@ -0,0 +1,41 @@ +import { PromiseExecutor, logger } from '@nx/devkit'; +import { execSync } from 'child_process'; +import path from 'path'; +import { PackNpmExecutorSchema } from './schema'; +import { resolveProjectPath } from '../../utils/path-resolver'; +import { logError } from '../../utils/error-handler'; + +const DEFAULT_DIST_DIR = './npm'; + +const MSG_PACK_SUCCESS = 'pnpm pack completed successfully'; +const MSG_PACK_FAILED = 'Failed to run pnpm pack'; + +const runExecutor: PromiseExecutor = async (options, context) => { + const absoluteProjectRoot = resolveProjectPath(context); + const distDirectory = options.workingDirectory || DEFAULT_DIST_DIR; + const workspaceRoot = context.root; + + if (!context.projectName) { + logError(MSG_PACK_FAILED, 'Project name is not defined in context'); + return { success: false }; + } + + try { + logger.info(`Running pnpm pack from ${absoluteProjectRoot} (packaging ${distDirectory})...`); + + const projectPath = path.join(workspaceRoot, 'packages', context.projectName); + + execSync(`pnpm pack`, { + cwd: projectPath, + stdio: 'inherit', + }); + + logger.info(MSG_PACK_SUCCESS); + return { success: true }; + } catch (error) { + logError(MSG_PACK_FAILED, error); + return { success: false }; + } +}; + +export default runExecutor; diff --git a/packages/nx-infra-plugin/src/executors/pack-npm/schema.json b/packages/nx-infra-plugin/src/executors/pack-npm/schema.json new file mode 100644 index 000000000000..888e8312ae4f --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/pack-npm/schema.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "properties": { + "workingDirectory": { + "type": "string", + "description": "Working directory for pnpm pack", + "default": "./" + } + }, + "required": [] +} diff --git a/packages/nx-infra-plugin/src/executors/pack-npm/schema.ts b/packages/nx-infra-plugin/src/executors/pack-npm/schema.ts new file mode 100644 index 000000000000..4b7a44f69ae6 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/pack-npm/schema.ts @@ -0,0 +1,3 @@ +export interface PackNpmExecutorSchema { + workingDirectory?: string; +} diff --git a/packages/nx-infra-plugin/src/executors/prepare-package-json/executor.e2e.spec.ts b/packages/nx-infra-plugin/src/executors/prepare-package-json/executor.e2e.spec.ts new file mode 100644 index 000000000000..d11d2c288583 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/prepare-package-json/executor.e2e.spec.ts @@ -0,0 +1,103 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import executor from './executor'; +import { NpmPackageExecutorSchema } from './schema'; +import { createTempDir, cleanupTempDir, createMockContext } from '../../utils/test-utils'; +import { writeJson, readFileText } from '../../utils'; + +describe('PreparePackageJsonExecutor E2E', () => { + let tempDir: string; + let context = createMockContext(); + + beforeEach(async () => { + tempDir = createTempDir('nx-prepare-pkg-e2e-'); + context = createMockContext({ root: tempDir }); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + + fs.mkdirSync(projectDir, { recursive: true }); + + await writeJson(path.join(projectDir, 'package.json'), { + name: '@devexpress/test-package', + version: '1.0.0', + description: 'Test package for prepare-package-json', + main: './index.js', + module: './esm/index.js', + types: './index.d.ts', + scripts: { + build: 'tsc', + test: 'jest', + }, + publishConfig: { + access: 'public', + directory: 'npm', + registry: 'https://registry.npmjs.org/', + }, + dependencies: { + react: '^18.0.0', + }, + devDependencies: { + typescript: '^4.9.0', + jest: '^29.0.0', + }, + keywords: ['test', 'package'], + license: 'MIT', + author: 'Test Author', + }); + }); + + afterEach(() => { + cleanupTempDir(tempDir); + }); + + describe('publishConfig removal', () => { + it('should remove publishConfig from package.json', async () => { + const options: NpmPackageExecutorSchema = { + distDirectory: './npm', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const distPackageJson = path.join(projectDir, 'npm', 'package.json'); + const distPackage = JSON.parse(await readFileText(distPackageJson)); + + expect(distPackage.publishConfig).toBeUndefined(); + }); + + it('should preserve all other fields when removing publishConfig', async () => { + const options: NpmPackageExecutorSchema = { + distDirectory: './npm', + }; + + await executor(options, context); + + const projectDir = path.join(tempDir, 'packages', 'test-lib'); + const distPackageJson = path.join(projectDir, 'npm', 'package.json'); + const distPackage = JSON.parse(await readFileText(distPackageJson)); + + expect(distPackage.name).toBe('@devexpress/test-package'); + expect(distPackage.version).toBe('1.0.0'); + expect(distPackage.description).toBe('Test package for prepare-package-json'); + expect(distPackage.main).toBe('./index.js'); + expect(distPackage.module).toBe('./esm/index.js'); + expect(distPackage.types).toBe('./index.d.ts'); + expect(distPackage.scripts).toEqual({ + build: 'tsc', + test: 'jest', + }); + expect(distPackage.dependencies).toEqual({ + react: '^18.0.0', + }); + expect(distPackage.devDependencies).toEqual({ + typescript: '^4.9.0', + jest: '^29.0.0', + }); + expect(distPackage.keywords).toEqual(['test', 'package']); + expect(distPackage.license).toBe('MIT'); + expect(distPackage.author).toBe('Test Author'); + }); + }); +}); diff --git a/packages/nx-infra-plugin/src/executors/prepare-package-json/executor.ts b/packages/nx-infra-plugin/src/executors/prepare-package-json/executor.ts new file mode 100644 index 000000000000..8315a808e821 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/prepare-package-json/executor.ts @@ -0,0 +1,44 @@ +import { PromiseExecutor, logger } from '@nx/devkit'; +import * as path from 'path'; +import { NpmPackageExecutorSchema } from './schema'; +import { resolveProjectPath } from '../../utils/path-resolver'; +import { logError } from '../../utils/error-handler'; +import { ensureDir, readJson, writeJson } from '../../utils/file-operations'; + +const DEFAULT_SOURCE_PACKAGE_JSON = './package.json'; +const DEFAULT_DIST_DIR = './npm'; + +const PACKAGE_JSON_FILE = 'package.json'; +const PUBLISH_CONFIG_FIELD = 'publishConfig'; + +const JSON_INDENT = 2; + +const ERROR_PREPARE_PACKAGE_JSON = 'Failed to prepare package.json'; + +const runExecutor: PromiseExecutor = async (options, context) => { + const absoluteProjectRoot = resolveProjectPath(context); + const sourcePackageJson = path.join( + absoluteProjectRoot, + options.sourcePackageJson || DEFAULT_SOURCE_PACKAGE_JSON, + ); + const distDirectory = path.join(absoluteProjectRoot, options.distDirectory || DEFAULT_DIST_DIR); + + try { + await ensureDir(distDirectory); + + const pkg = await readJson>(sourcePackageJson); + delete pkg[PUBLISH_CONFIG_FIELD]; + + const distPackageJson = path.join(distDirectory, PACKAGE_JSON_FILE); + await writeJson(distPackageJson, pkg, JSON_INDENT); + + logger.info(`Created ${distPackageJson}`); + + return { success: true }; + } catch (error) { + logError(ERROR_PREPARE_PACKAGE_JSON, error); + return { success: false }; + } +}; + +export default runExecutor; diff --git a/packages/nx-infra-plugin/src/executors/prepare-package-json/schema.json b/packages/nx-infra-plugin/src/executors/prepare-package-json/schema.json new file mode 100644 index 000000000000..d555d19c5d7f --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/prepare-package-json/schema.json @@ -0,0 +1,16 @@ +{ + "type": "object", + "properties": { + "sourcePackageJson": { + "type": "string", + "description": "Path to source package.json", + "default": "./package.json" + }, + "distDirectory": { + "type": "string", + "description": "Distribution directory", + "default": "./npm" + } + }, + "required": [] +} diff --git a/packages/nx-infra-plugin/src/executors/prepare-package-json/schema.ts b/packages/nx-infra-plugin/src/executors/prepare-package-json/schema.ts new file mode 100644 index 000000000000..8e294d1d6300 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/prepare-package-json/schema.ts @@ -0,0 +1,4 @@ +export interface NpmPackageExecutorSchema { + sourcePackageJson?: string; + distDirectory?: string; +} diff --git a/packages/nx-infra-plugin/src/executors/prepare-submodules/executor.e2e.spec.ts b/packages/nx-infra-plugin/src/executors/prepare-submodules/executor.e2e.spec.ts new file mode 100644 index 000000000000..d6a088b184b5 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/prepare-submodules/executor.e2e.spec.ts @@ -0,0 +1,258 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import executor from './executor'; +import { PrepareSubmodulesExecutorSchema } from './schema'; +import { createTempDir, cleanupTempDir, createMockContext } from '../../utils/test-utils'; +import { writeFileText, readFileText } from '../../utils'; + +describe('PrepareSubmodulesExecutor E2E', () => { + let tempDir: string; + let context = createMockContext(); + + beforeEach(async () => { + tempDir = createTempDir('nx-prepare-submodules-e2e-'); + context = createMockContext({ + root: tempDir, + projectName: 'test-package', + projectRoot: 'packages/test-package', + }); + + const projectDir = path.join(tempDir, 'packages', 'test-package'); + const npmDir = path.join(projectDir, 'npm'); + + fs.mkdirSync(path.join(npmDir, 'esm'), { recursive: true }); + fs.mkdirSync(path.join(npmDir, 'cjs'), { recursive: true }); + + await writeFileText( + path.join(npmDir, 'esm', 'index.js'), + `export { Button } from "./button";\nexport { Grid } from "./data/grid";\nexport { Chart } from "./viz/chart";`, + ); + + await writeFileText(path.join(npmDir, 'esm', 'button.js'), 'export const Button = () => {};'); + fs.mkdirSync(path.join(npmDir, 'esm', 'data'), { recursive: true }); + await writeFileText( + path.join(npmDir, 'esm', 'data', 'grid.js'), + 'export const Grid = () => {};', + ); + fs.mkdirSync(path.join(npmDir, 'esm', 'viz'), { recursive: true }); + await writeFileText( + path.join(npmDir, 'esm', 'viz', 'chart.js'), + 'export const Chart = () => {};', + ); + + await writeFileText(path.join(npmDir, 'cjs', 'index.js'), 'module.exports = {};'); + await writeFileText(path.join(npmDir, 'cjs', 'button.js'), 'module.exports = {};'); + fs.mkdirSync(path.join(npmDir, 'cjs', 'data'), { recursive: true }); + await writeFileText(path.join(npmDir, 'cjs', 'data', 'grid.js'), 'module.exports = {};'); + fs.mkdirSync(path.join(npmDir, 'cjs', 'viz'), { recursive: true }); + await writeFileText(path.join(npmDir, 'cjs', 'viz', 'chart.js'), 'module.exports = {};'); + }); + + afterEach(() => { + cleanupTempDir(tempDir); + }); + + describe('Basic functionality', () => { + it('should generate package.json files for discovered modules', async () => { + const options: PrepareSubmodulesExecutorSchema = { + distDirectory: './npm', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const npmDir = path.join(tempDir, 'packages', 'test-package', 'npm'); + + const buttonPkgPath = path.join(npmDir, 'button', 'package.json'); + expect(fs.existsSync(buttonPkgPath)).toBe(true); + + const buttonPkg = JSON.parse(await readFileText(buttonPkgPath)); + expect(buttonPkg).toMatchObject({ + sideEffects: false, + main: expect.stringContaining('cjs/button.js'), + module: expect.stringContaining('esm/button.js'), + typings: expect.stringContaining('cjs/button.d.ts'), + }); + }); + + it('should handle nested module structures', async () => { + const options: PrepareSubmodulesExecutorSchema = { + distDirectory: './npm', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const npmDir = path.join(tempDir, 'packages', 'test-package', 'npm'); + + const gridPkgPath = path.join(npmDir, 'grid', 'package.json'); + expect(fs.existsSync(gridPkgPath)).toBe(true); + + const gridPkg = JSON.parse(await readFileText(gridPkgPath)); + expect(gridPkg.module).toContain('esm/data/grid.js'); + }); + + it('should work with custom submodule files', async () => { + const npmDir = path.join(tempDir, 'packages', 'test-package', 'npm'); + + await writeFileText(path.join(npmDir, 'esm', 'custom.js'), 'export const Custom = {};'); + await writeFileText(path.join(npmDir, 'cjs', 'custom.js'), 'module.exports = {};'); + + const esmIndex = await readFileText(path.join(npmDir, 'esm', 'index.js')); + await writeFileText( + path.join(npmDir, 'esm', 'index.js'), + esmIndex + '\nexport { Custom } from "./custom";', + ); + + const options: PrepareSubmodulesExecutorSchema = { + distDirectory: './npm', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + const customPkgPath = path.join(npmDir, 'custom', 'package.json'); + expect(fs.existsSync(customPkgPath)).toBe(true); + + const customPkg = JSON.parse(await readFileText(customPkgPath)); + expect(customPkg.module).toBe('../esm/custom.js'); + }); + + it('should handle devextreme-react-like structure', async () => { + const npmDir = path.join(tempDir, 'packages', 'test-package', 'npm'); + + fs.mkdirSync(path.join(npmDir, 'esm', 'common'), { recursive: true }); + await writeFileText( + path.join(npmDir, 'esm', 'common', 'index.js'), + 'export * from "./core";', + ); + fs.mkdirSync(path.join(npmDir, 'esm', 'common', 'core'), { recursive: true }); + await writeFileText( + path.join(npmDir, 'esm', 'common', 'core', 'index.js'), + 'export class Component {}', + ); + + fs.mkdirSync(path.join(npmDir, 'cjs', 'common'), { recursive: true }); + await writeFileText(path.join(npmDir, 'cjs', 'common', 'index.js'), 'module.exports = {};'); + fs.mkdirSync(path.join(npmDir, 'cjs', 'common', 'core'), { recursive: true }); + await writeFileText( + path.join(npmDir, 'cjs', 'common', 'core', 'index.js'), + 'module.exports = {};', + ); + + const options: PrepareSubmodulesExecutorSchema = { + distDirectory: './npm', + submoduleFolders: [['common'], ['common/core']], + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + + expect(fs.existsSync(path.join(npmDir, 'common', 'package.json'))).toBe(true); + expect(fs.existsSync(path.join(npmDir, 'common', 'core', 'package.json'))).toBe(true); + }); + }); + + describe('Package.json content validation', () => { + it('should generate correct relative paths for top-level modules', async () => { + const options: PrepareSubmodulesExecutorSchema = { + distDirectory: './npm', + }; + + await executor(options, context); + + const npmDir = path.join(tempDir, 'packages', 'test-package', 'npm'); + const buttonPkg = JSON.parse(await readFileText(path.join(npmDir, 'button', 'package.json'))); + + expect(buttonPkg.main).toBe('../cjs/button.js'); + expect(buttonPkg.module).toBe('../esm/button.js'); + expect(buttonPkg.typings).toBe('../cjs/button.d.ts'); + }); + + it('should generate correct paths for nested folder modules with index.js', async () => { + const npmDir = path.join(tempDir, 'packages', 'test-package', 'npm'); + + fs.mkdirSync(path.join(npmDir, 'esm', 'common', 'core'), { recursive: true }); + await writeFileText( + path.join(npmDir, 'esm', 'common', 'core', 'index.js'), + 'export class Component {}', + ); + fs.mkdirSync(path.join(npmDir, 'cjs', 'common', 'core'), { recursive: true }); + await writeFileText( + path.join(npmDir, 'cjs', 'common', 'core', 'index.js'), + 'module.exports = {};', + ); + + const options: PrepareSubmodulesExecutorSchema = { + distDirectory: './npm', + submoduleFolders: [['common/core']], + }; + + const result = await executor(options, context); + expect(result.success).toBe(true); + + const commonCorePkg = JSON.parse( + await readFileText(path.join(npmDir, 'common', 'core', 'package.json')), + ); + + expect(commonCorePkg.main).toBe('../../cjs/common/core/index.js'); + expect(commonCorePkg.module).toBe('../../esm/common/core/index.js'); + expect(commonCorePkg.typings).toBe('../../cjs/common/core/index.d.ts'); + + expect(commonCorePkg.main).not.toBe('../../cjs/index.js'); + expect(commonCorePkg.module).not.toBe('../../esm/index.js'); + expect(commonCorePkg.typings).not.toBe('../../cjs/index.d.ts'); + }); + }); + + describe('Error handling', () => { + it('should handle missing dist directory gracefully', async () => { + const options: PrepareSubmodulesExecutorSchema = { + distDirectory: './nonexistent', + }; + + const result = await executor(options, context); + + expect(result).toHaveProperty('success'); + expect(typeof result.success).toBe('boolean'); + }); + + it('should handle empty ESM index file', async () => { + const npmDir = path.join(tempDir, 'packages', 'test-package', 'npm'); + await writeFileText(path.join(npmDir, 'esm', 'index.js'), ''); + + const options: PrepareSubmodulesExecutorSchema = { + distDirectory: './npm', + }; + + const result = await executor(options, context); + + expect(result.success).toBe(true); + }); + }); + + describe('Integration scenarios', () => { + it('should handle multiple runs idempotently', async () => { + const options: PrepareSubmodulesExecutorSchema = { + distDirectory: './npm', + }; + + const result1 = await executor(options, context); + expect(result1.success).toBe(true); + + const npmDir = path.join(tempDir, 'packages', 'test-package', 'npm'); + const firstPkg = JSON.parse(await readFileText(path.join(npmDir, 'button', 'package.json'))); + + const result2 = await executor(options, context); + expect(result2.success).toBe(true); + + const secondPkg = JSON.parse(await readFileText(path.join(npmDir, 'button', 'package.json'))); + + expect(secondPkg).toEqual(firstPkg); + }); + }); +}); diff --git a/packages/nx-infra-plugin/src/executors/prepare-submodules/executor.ts b/packages/nx-infra-plugin/src/executors/prepare-submodules/executor.ts new file mode 100644 index 000000000000..f024153b04af --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/prepare-submodules/executor.ts @@ -0,0 +1,156 @@ +import { PromiseExecutor, logger } from '@nx/devkit'; +import * as fs from 'fs/promises'; +import * as fsSync from 'fs'; +import type { Dirent } from 'fs'; +import * as path from 'path'; +import { PrepareSubmodulesExecutorSchema } from './schema'; +import { PackParam } from '../../utils/types'; +import { resolveProjectPath } from '../../utils/path-resolver'; +import { logError, getErrorMessage } from '../../utils/error-handler'; +import { ensureDir, writeJson } from '../../utils/file-operations'; + +const DEFAULT_DIST_DIR = './npm'; +const ESM_DIR = 'esm'; +const CJS_DIR = 'cjs'; + +const ENCODING_UTF8 = 'utf8'; + +const JS_EXTENSION = '.js'; +const DTS_EXTENSION = '.d.ts'; + +const REGEX_IMPORTS = /from "\.\/([^;]+)";/g; +const REGEX_PARSE_MODULE = /((.*)\/)?([^/]+$)/; + +const MSG_PREPARING = '📦 Preparing submodules'; +const MSG_SUCCESS = '✓ Submodules prepared successfully'; +const ERROR_PREPARE_SUBMODULES = 'Failed to prepare submodules'; + +const INDEX_FILE_NAME = 'index.js'; +const PACKAGE_JSON_FILE = 'package.json'; + +const PATH_SLASH = '/'; +const RELATIVE_DIR_PREFIX = '../'; + +const DEFAULT_SUBMODULE_FOLDERS: PackParam[] = [ + ['common'], + ['core', ['template', 'config', 'nested-option', 'component', 'extension-component']], + ['common/core'], + ['common/data'], + ['common/export'], +]; + +const runExecutor: PromiseExecutor = async (options, context) => { + const absoluteProjectRoot = resolveProjectPath(context); + const distDirectory = path.join(absoluteProjectRoot, options.distDirectory || DEFAULT_DIST_DIR); + + try { + logger.info(MSG_PREPARING); + + const packParamsForFolders = options.submoduleFolders || DEFAULT_SUBMODULE_FOLDERS; + + const esmIndexPath = path.join(distDirectory, ESM_DIR, 'index.js'); + let modulesImportsFromIndex = ''; + + if (fsSync.existsSync(esmIndexPath)) { + modulesImportsFromIndex = await fs.readFile(esmIndexPath, ENCODING_UTF8); + } + + const modulesPaths = modulesImportsFromIndex.matchAll(REGEX_IMPORTS); + const packParamsForModules: PackParam[] = Array.from(modulesPaths).map(([, modulePath]) => { + const match = modulePath.match(REGEX_PARSE_MODULE) || []; + const moduleFilePath = match[2] as string | undefined; + const moduleFileName = match[3] as string | undefined; + + return ['', moduleFileName ? [moduleFileName] : undefined, moduleFilePath]; + }); + + const allModuleParams: PackParam[] = [...packParamsForModules, ...packParamsForFolders]; + + logger.info(`Processing ${allModuleParams.length} submodules...`); + + await Promise.all( + allModuleParams.map(([folder, moduleFileNames, moduleFilePath]) => + makeModule(distDirectory, folder, moduleFileNames, moduleFilePath), + ), + ); + + logger.info(MSG_SUCCESS); + return { success: true }; + } catch (error) { + logError(ERROR_PREPARE_SUBMODULES, error); + return { success: false }; + } +}; + +async function makeModule( + distFolder: string, + folder: string, + moduleFileNames?: string[], + moduleFilePath?: string, +): Promise { + const distModuleFolder = path.join(distFolder, folder); + const distEsmFolder = path.join(distFolder, ESM_DIR, folder); + const moduleNames = moduleFileNames || (await findJsModuleFileNamesInFolder(distEsmFolder)); + + try { + await ensureDir(distModuleFolder); + + if (folder && fsSync.existsSync(path.join(distEsmFolder, 'index.js'))) { + await generatePackageJsonFile(distFolder, folder, undefined, folder); + } + + await Promise.all( + moduleNames.map(async (moduleFileName) => { + const moduleDir = path.join(distModuleFolder, moduleFileName); + await ensureDir(moduleDir); + + await generatePackageJsonFile(distFolder, folder, moduleFileName, moduleFilePath || folder); + }), + ); + } catch (error) { + throw new Error(`Exception while makeModule(${folder}): ${getErrorMessage(error)}`); + } +} + +async function generatePackageJsonFile( + distFolder: string, + folder: string, + moduleFileName?: string, + filePath?: string, +): Promise { + const moduleName = moduleFileName || ''; + const absoluteModulePath = path.join(distFolder, folder, moduleName); + const moduleFilePathResolved = (filePath ? filePath + PATH_SLASH : '') + (moduleName || 'index'); + const esmFilePath = path.join(distFolder, ESM_DIR, moduleFilePathResolved + JS_EXTENSION); + const relativePath = path.relative(absoluteModulePath, esmFilePath); + + const relativeBase = RELATIVE_DIR_PREFIX.repeat(relativePath.split('..').length - 1); + + const packageJson = { + sideEffects: false, + main: `${relativeBase}${CJS_DIR}/${moduleFilePathResolved}${JS_EXTENSION}`, + module: `${relativeBase}${ESM_DIR}/${moduleFilePathResolved}${JS_EXTENSION}`, + typings: `${relativeBase}${CJS_DIR}/${moduleFilePathResolved}${DTS_EXTENSION}`, + }; + + await ensureDir(absoluteModulePath); + await writeJson(path.join(absoluteModulePath, PACKAGE_JSON_FILE), packageJson); +} + +async function findJsModuleFileNamesInFolder(dir: string): Promise { + if (!fsSync.existsSync(dir)) { + return []; + } + + const entries = await fs.readdir(dir, { withFileTypes: true }); + + return entries.filter(isJsModule).map((entry) => path.parse(entry.name).name); +} + +function isJsModule(entry: Dirent): boolean { + return ( + !entry.isDirectory() && entry.name.endsWith(JS_EXTENSION) && entry.name !== INDEX_FILE_NAME + ); +} + +export default runExecutor; diff --git a/packages/nx-infra-plugin/src/executors/prepare-submodules/schema.json b/packages/nx-infra-plugin/src/executors/prepare-submodules/schema.json new file mode 100644 index 000000000000..e84a530524f9 --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/prepare-submodules/schema.json @@ -0,0 +1,18 @@ +{ + "type": "object", + "properties": { + "distDirectory": { + "type": "string", + "description": "Distribution directory containing ESM and CJS builds. This directory will be scanned to generate submodule package.json files.", + "default": "./npm" + }, + "submoduleFolders": { + "type": "array", + "description": "Custom submodule folder configurations. Each entry is [folder, moduleFileNames?, moduleFilePath?].", + "items": { + "type": "array" + } + } + }, + "required": [] +} diff --git a/packages/nx-infra-plugin/src/executors/prepare-submodules/schema.ts b/packages/nx-infra-plugin/src/executors/prepare-submodules/schema.ts new file mode 100644 index 000000000000..3b7170e9be1a --- /dev/null +++ b/packages/nx-infra-plugin/src/executors/prepare-submodules/schema.ts @@ -0,0 +1,6 @@ +import { PackParam } from '../../utils/types'; + +export interface PrepareSubmodulesExecutorSchema { + distDirectory?: string; + submoduleFolders?: PackParam[]; +} diff --git a/packages/nx-infra-plugin/src/index.ts b/packages/nx-infra-plugin/src/index.ts new file mode 100644 index 000000000000..b38bf27b3619 --- /dev/null +++ b/packages/nx-infra-plugin/src/index.ts @@ -0,0 +1,5 @@ +export default function () { + return { + name: 'nx-infra-plugin', + }; +} diff --git a/packages/nx-infra-plugin/src/utils/error-handler.ts b/packages/nx-infra-plugin/src/utils/error-handler.ts new file mode 100644 index 000000000000..6049e107c436 --- /dev/null +++ b/packages/nx-infra-plugin/src/utils/error-handler.ts @@ -0,0 +1,17 @@ +import { logger } from '@nx/devkit'; + +export function getErrorMessage(error: unknown): string { + return error instanceof Error ? error.message : String(error); +} + +export function getErrorStack(error: unknown): string | undefined { + return error instanceof Error ? error.stack : undefined; +} + +export function logError(message: string, error: unknown): void { + logger.error(`${message}: ${getErrorMessage(error)}`); + const stack = getErrorStack(error); + if (stack) { + logger.error(stack); + } +} diff --git a/packages/nx-infra-plugin/src/utils/file-operations.ts b/packages/nx-infra-plugin/src/utils/file-operations.ts new file mode 100644 index 000000000000..ca15a61e1464 --- /dev/null +++ b/packages/nx-infra-plugin/src/utils/file-operations.ts @@ -0,0 +1,69 @@ +import * as fs from 'fs/promises'; +import * as path from 'path'; +import { glob } from 'glob'; + +const ENCODING_UTF8 = 'utf-8'; +const ERROR_CODE_EXIST = 'EEXIST'; + +export async function ensureDir(dirPath: string): Promise { + try { + await fs.mkdir(dirPath, { recursive: true }); + } catch (error: unknown) { + if (error instanceof Error && 'code' in error && error.code === ERROR_CODE_EXIST) { + return; + } + throw error; + } +} + +export async function readJson(filePath: string): Promise { + const content = await fs.readFile(filePath, ENCODING_UTF8); + return JSON.parse(content) as T; +} + +export async function writeJson( + filePath: string, + data: unknown, + spaces: number = 2, +): Promise { + const content = JSON.stringify(data, null, spaces); + await fs.writeFile(filePath, content, ENCODING_UTF8); +} + +export async function processFiles( + pattern: string, + processor: (filePath: string) => Promise, + options: { ignore?: string[] } = {}, +): Promise { + const files = await glob(pattern, { + absolute: true, + nodir: true, + ignore: options.ignore, + }); + + await Promise.all(files.map(processor)); + return files.length; +} + +export async function exists(filePath: string): Promise { + try { + await fs.access(filePath); + return true; + } catch { + return false; + } +} + +export async function copyFile(from: string, to: string): Promise { + await ensureDir(path.dirname(to)); + await fs.copyFile(from, to); +} + +export async function readFileText(filePath: string): Promise { + return fs.readFile(filePath, ENCODING_UTF8); +} + +export async function writeFileText(filePath: string, content: string): Promise { + await ensureDir(path.dirname(filePath)); + await fs.writeFile(filePath, content, ENCODING_UTF8); +} diff --git a/packages/nx-infra-plugin/src/utils/index.ts b/packages/nx-infra-plugin/src/utils/index.ts new file mode 100644 index 000000000000..81ead4bb9a44 --- /dev/null +++ b/packages/nx-infra-plugin/src/utils/index.ts @@ -0,0 +1,4 @@ +export * from './types'; +export * from './path-resolver'; +export * from './error-handler'; +export * from './file-operations'; diff --git a/packages/nx-infra-plugin/src/utils/path-resolver.ts b/packages/nx-infra-plugin/src/utils/path-resolver.ts new file mode 100644 index 000000000000..78293541c62a --- /dev/null +++ b/packages/nx-infra-plugin/src/utils/path-resolver.ts @@ -0,0 +1,32 @@ +import { ExecutorContext } from '@nx/devkit'; +import * as path from 'path'; + +const ERROR_CONFIGURATIONS_NOT_FOUND = 'Project configurations not found in executor context'; +const ERROR_PROJECT_NAME_NOT_FOUND = 'Project name not found in executor context'; +const ERROR_PROJECT_NOT_FOUND = 'Project "{0}" not found in workspace'; + +export function resolveProjectPath(context: ExecutorContext): string { + if (!context.projectsConfigurations) { + throw new Error(ERROR_CONFIGURATIONS_NOT_FOUND); + } + + if (!context.projectName) { + throw new Error(ERROR_PROJECT_NAME_NOT_FOUND); + } + + const project = context.projectsConfigurations.projects[context.projectName]; + if (!project) { + throw new Error(ERROR_PROJECT_NOT_FOUND.replace('{0}', context.projectName)); + } + + return path.resolve(context.root, project.root); +} + +export function resolveFromProject(context: ExecutorContext, relativePath: string): string { + const projectRoot = resolveProjectPath(context); + return path.join(projectRoot, relativePath); +} + +export function resolveFromWorkspace(context: ExecutorContext, relativePath: string): string { + return path.join(context.root, relativePath); +} diff --git a/packages/nx-infra-plugin/src/utils/test-utils.ts b/packages/nx-infra-plugin/src/utils/test-utils.ts new file mode 100644 index 000000000000..98694687b197 --- /dev/null +++ b/packages/nx-infra-plugin/src/utils/test-utils.ts @@ -0,0 +1,43 @@ +import { ExecutorContext } from '@nx/devkit'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; + +export function createTempDir(prefix: string): string { + return fs.mkdtempSync(path.join(os.tmpdir(), prefix)); +} + +export function cleanupTempDir(dirPath: string): void { + if (fs.existsSync(dirPath)) { + fs.rmSync(dirPath, { recursive: true, force: true }); + } +} + +export interface MockContextOptions { + root?: string; + projectName?: string; + projectRoot?: string; + isVerbose?: boolean; +} + +export function createMockContext(options: MockContextOptions = {}): ExecutorContext { + const { + root = '/tmp/test', + projectName = 'test-lib', + projectRoot = 'packages/test-lib', + isVerbose = false, + } = options; + + return { + root, + cwd: root, + isVerbose, + projectName, + projectsConfigurations: { + projects: { + [projectName]: { root: projectRoot }, + }, + version: 2, + }, + }; +} diff --git a/packages/nx-infra-plugin/src/utils/types.ts b/packages/nx-infra-plugin/src/utils/types.ts new file mode 100644 index 000000000000..c0a6c14e6e85 --- /dev/null +++ b/packages/nx-infra-plugin/src/utils/types.ts @@ -0,0 +1,51 @@ +export interface TsConfig { + compilerOptions?: CompilerOptions; + extends?: string; + include?: string[]; + exclude?: string[]; + files?: string[]; + references?: Array<{ path: string }>; +} + +export interface CompilerOptions { + target?: string; + module?: string; + lib?: string[]; + outDir?: string; + rootDir?: string; + declaration?: boolean; + declarationMap?: boolean; + sourceMap?: boolean; + strict?: boolean; + esModuleInterop?: boolean; + skipLibCheck?: boolean; + forceConsistentCasingInFileNames?: boolean; + resolveJsonModule?: boolean; + jsx?: string; + paths?: Record; + [key: string]: any; +} + +export type PackParam = [string, string[]?, string?]; + +export interface TemplateData { + pkg: { + name: string; + version: string; + [key: string]: any; + }; + date: string; + year: number; + [key: string]: any; +} + +export interface FileCopyOperation { + from: string; + to: string; +} + +export interface SubmoduleConfig { + folder: string; + moduleFileNames?: string[]; + moduleFilePath?: string; +} diff --git a/packages/nx-infra-plugin/tsconfig.json b/packages/nx-infra-plugin/tsconfig.json new file mode 100644 index 000000000000..f3ee56d7c006 --- /dev/null +++ b/packages/nx-infra-plugin/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "commonjs" + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/nx-infra-plugin/tsconfig.lib.json b/packages/nx-infra-plugin/tsconfig.lib.json new file mode 100644 index 000000000000..1a46f829ba6b --- /dev/null +++ b/packages/nx-infra-plugin/tsconfig.lib.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "baseUrl": ".", + "outDir": "./dist", + "declaration": true, + "types": ["node"], + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/packages/nx-infra-plugin/tsconfig.spec.json b/packages/nx-infra-plugin/tsconfig.spec.json new file mode 100644 index 000000000000..d8fd5f2f8c31 --- /dev/null +++ b/packages/nx-infra-plugin/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./out-tsc/spec", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5a9f3aa075e6..997661af3348 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -238,7 +238,7 @@ importers: devDependencies: '@angular-devkit/build-angular': specifier: 17.3.11 - version: 17.3.11(4ilwirpeb4hxrzllykv7l5hjey) + version: 17.3.11(qwvpz4std2ihowmewzmfizrlqa) '@angular/cli': specifier: 17.3.11 version: 17.3.11(chokidar@3.6.0) @@ -268,7 +268,7 @@ importers: dependencies: '@angular-devkit/build-angular': specifier: 17.3.11 - version: 17.3.11(yhrafk4ev6k2dly5pdp4p2egia) + version: 17.3.11(knttvfyutxltnyggdtp5kkscoe) '@angular/cli': specifier: 17.3.11 version: 17.3.11(chokidar@3.6.0) @@ -373,7 +373,7 @@ importers: version: 0.25.0 esbuild-plugin-vue3: specifier: 0.3.2 - version: 0.3.2(cheerio@1.0.0-rc.10)(sass@1.71.1) + version: 0.3.2(cheerio@1.0.0-rc.10)(sass@1.85.0) file-saver-es: specifier: 2.0.5 version: 2.0.5 @@ -629,7 +629,7 @@ importers: version: 1.1.4 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.12.8)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)) + version: 29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)) jest-environment-node: specifier: 29.7.0 version: 29.7.0 @@ -677,7 +677,7 @@ importers: version: 3.7.2 ts-node: specifier: 10.9.2 - version: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5) + version: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5) vue-tsc: specifier: ^3.0.6 version: 3.0.8(typescript@5.4.5) @@ -1696,7 +1696,7 @@ importers: version: 1.1.0 webpack: specifier: 5.94.0 - version: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15)) + version: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) webpack-stream: specifier: 7.0.0 version: 7.0.0(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))) @@ -1932,12 +1932,12 @@ importers: '@typescript-eslint/parser': specifier: 'catalog:' version: 8.23.0(eslint@9.18.0(jiti@1.21.6))(typescript@4.9.5) - del: - specifier: 3.0.0 - version: 3.0.0 devextreme-metadata: specifier: workspace:* version: link:../devextreme-metadata + devextreme-nx-infra-plugin: + specifier: workspace:* + version: link:../nx-infra-plugin eslint: specifier: 'catalog:' version: 9.18.0(jiti@1.21.6) @@ -1953,18 +1953,6 @@ importers: eslint-plugin-import: specifier: 'catalog:' version: 2.31.0(@typescript-eslint/parser@8.23.0(eslint@9.18.0(jiti@1.21.6))(typescript@4.9.5))(eslint@9.18.0(jiti@1.21.6)) - gulp: - specifier: 4.0.2 - version: 4.0.2 - gulp-header: - specifier: 2.0.9 - version: 2.0.9 - gulp-shell: - specifier: 0.8.0 - version: 0.8.0 - gulp-typescript: - specifier: 5.0.1 - version: 5.0.1(typescript@4.9.5) jest-environment-jsdom: specifier: 29.7.0 version: 29.7.0 @@ -2209,6 +2197,31 @@ importers: specifier: 'catalog:' version: 2.13.0 + packages/nx-infra-plugin: + dependencies: + glob: + specifier: 10.4.5 + version: 10.4.5 + rimraf: + specifier: 3.0.2 + version: 3.0.2 + devDependencies: + '@types/jest': + specifier: 29.5.12 + version: 29.5.12 + '@types/node': + specifier: ^18.0.0 + version: 18.19.64 + prettier: + specifier: catalog:tools + version: 3.5.3 + ts-jest: + specifier: 29.1.3 + version: 29.1.3(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)))(typescript@4.9.5) + typescript: + specifier: 4.9.5 + version: 4.9.5 + packages/testcafe-models: dependencies: testcafe: @@ -16019,7 +16032,6 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qjobs@1.2.0: @@ -19618,7 +19630,7 @@ snapshots: transitivePeerDependencies: - chokidar - '@angular-devkit/build-angular@17.3.11(4ilwirpeb4hxrzllykv7l5hjey)': + '@angular-devkit/build-angular@17.3.11(knttvfyutxltnyggdtp5kkscoe)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1703.11(chokidar@3.6.0) @@ -19636,7 +19648,7 @@ snapshots: '@babel/runtime': 7.24.0 '@discoveryjs/json-ext': 0.5.7 '@ngtools/webpack': 17.3.11(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1)) + '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.1.8(@types/node@18.19.64)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1)) ansi-colors: 4.1.3 autoprefixer: 10.4.18(postcss@8.4.35) babel-loader: 9.1.3(@babel/core@7.24.0)(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) @@ -19678,20 +19690,20 @@ snapshots: tslib: 2.6.2 typescript: 5.4.5 undici: 6.11.1 - vite: 5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1) + vite: 5.1.8(@types/node@18.19.64)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1) watchpack: 2.4.0 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) webpack-dev-middleware: 6.1.2(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) webpack-dev-server: 4.15.1(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) webpack-merge: 5.10.0 - webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))))(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) + webpack-subresource-integrity: 5.1.0(html-webpack-plugin@5.6.3(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)))(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) optionalDependencies: '@angular/platform-server': 17.3.12(@angular/animations@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@17.3.12(@angular/animations@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10))) esbuild: 0.20.1 - jest: 29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + jest: 29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)) jest-environment-jsdom: 29.7.0 karma: 6.4.4 - ng-packagr: 17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tslib@2.6.3)(typescript@5.4.5) + ng-packagr: 17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tslib@2.8.1)(typescript@5.4.5) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -19711,7 +19723,7 @@ snapshots: - utf-8-validate - webpack-cli - '@angular-devkit/build-angular@17.3.11(yhrafk4ev6k2dly5pdp4p2egia)': + '@angular-devkit/build-angular@17.3.11(qwvpz4std2ihowmewzmfizrlqa)': dependencies: '@ampproject/remapping': 2.3.0 '@angular-devkit/architect': 0.1703.11(chokidar@3.6.0) @@ -19729,7 +19741,7 @@ snapshots: '@babel/runtime': 7.24.0 '@discoveryjs/json-ext': 0.5.7 '@ngtools/webpack': 17.3.11(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(typescript@5.4.5)(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) - '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.1.8(@types/node@20.12.8)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1)) + '@vitejs/plugin-basic-ssl': 1.1.0(vite@5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1)) ansi-colors: 4.1.3 autoprefixer: 10.4.18(postcss@8.4.35) babel-loader: 9.1.3(@babel/core@7.24.0)(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) @@ -19771,9 +19783,9 @@ snapshots: tslib: 2.6.2 typescript: 5.4.5 undici: 6.11.1 - vite: 5.1.8(@types/node@20.12.8)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1) + vite: 5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1) watchpack: 2.4.0 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) webpack-dev-middleware: 6.1.2(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) webpack-dev-server: 4.15.1(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) webpack-merge: 5.10.0 @@ -19781,10 +19793,10 @@ snapshots: optionalDependencies: '@angular/platform-server': 17.3.12(@angular/animations@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10))(@angular/platform-browser@17.3.12(@angular/animations@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(@angular/common@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10))(rxjs@7.8.1))(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10))) esbuild: 0.20.1 - jest: 29.7.0(@types/node@20.12.8)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)) + jest: 29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) jest-environment-jsdom: 29.7.0 karma: 6.4.4 - ng-packagr: 17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tslib@2.8.1)(typescript@5.4.5) + ng-packagr: 17.3.0(@angular/compiler-cli@17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5))(tslib@2.6.3)(typescript@5.4.5) transitivePeerDependencies: - '@rspack/core' - '@swc/core' @@ -19895,7 +19907,7 @@ snapshots: dependencies: '@angular-devkit/architect': 0.1703.11(chokidar@3.6.0) rxjs: 7.8.1 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) webpack-dev-server: 4.15.1(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) transitivePeerDependencies: - chokidar @@ -23853,27 +23865,27 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 slash: 3.0.0 - '@jest/core@29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5))': + '@jest/core@29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(node-notifier@9.0.1) '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + jest-config: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -23896,21 +23908,21 @@ snapshots: - supports-color - ts-node - '@jest/core@29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5))': + '@jest/core@29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(node-notifier@9.0.1) '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -23932,23 +23944,22 @@ snapshots: - babel-plugin-macros - supports-color - ts-node - optional: true - '@jest/core@29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5))': + '@jest/core@29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5))': dependencies: '@jest/console': 29.7.0 '@jest/reporters': 29.7.0(node-notifier@9.0.1) '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -23970,6 +23981,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true '@jest/core@29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2))': dependencies: @@ -23978,14 +23990,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) + jest-config: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -24015,14 +24027,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.14.5)(typescript@5.9.2)) + jest-config: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.14.5)(typescript@5.9.2)) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -24050,7 +24062,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -24068,7 +24080,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.12.8 + '@types/node': 18.19.64 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -24090,7 +24102,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.12.8 + '@types/node': 18.19.64 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -24161,7 +24173,7 @@ snapshots: dependencies: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/yargs': 16.0.9 chalk: 4.1.2 @@ -24170,7 +24182,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/yargs': 17.0.32 chalk: 4.1.2 @@ -24407,7 +24419,7 @@ snapshots: dependencies: '@angular/compiler-cli': 17.3.12(@angular/compiler@17.3.12(@angular/core@17.3.12(rxjs@7.8.1)(zone.js@0.14.10)))(typescript@5.4.5) typescript: 5.4.5 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) '@ngtools/webpack@19.2.10(@angular/compiler-cli@19.2.8(@angular/compiler@19.2.8)(typescript@5.8.3))(typescript@5.8.3)(webpack@5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1))': dependencies: @@ -26964,41 +26976,41 @@ snapshots: '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/bonjour@3.5.13': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/chai@4.3.20': {} '@types/cheerio@0.22.35': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/clean-css@4.2.11': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 source-map: 0.6.1 '@types/connect-history-api-fallback@1.5.4': dependencies: '@types/express-serve-static-core': 5.0.1 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/connect@3.4.38': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/cookie@0.4.1': {} '@types/cors@2.8.17': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/cross-spawn@6.0.6': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/debug@4.1.12': dependencies: @@ -27059,14 +27071,14 @@ snapshots: '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 '@types/express-serve-static-core@5.0.1': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 @@ -27094,16 +27106,16 @@ snapshots: '@types/fs-extra@11.0.4': dependencies: '@types/jsonfile': 6.1.4 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/hast@2.3.10': dependencies: @@ -27115,7 +27127,7 @@ snapshots: '@types/http-proxy@1.17.15': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/istanbul-lib-coverage@2.0.6': {} @@ -27142,7 +27154,7 @@ snapshots: '@types/jsdom@20.0.1': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/tough-cookie': 4.0.5 parse5: 7.2.1 @@ -27152,7 +27164,7 @@ snapshots: '@types/jsonfile@6.1.4': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/lodash@4.17.13': {} @@ -27176,12 +27188,12 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 form-data: 4.0.1 '@types/node-forge@1.3.11': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/node@12.20.55': {} @@ -27266,7 +27278,7 @@ snapshots: '@types/resolve@1.17.1': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/resolve@1.20.2': {} @@ -27283,7 +27295,7 @@ snapshots: '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/serve-index@1.9.4': dependencies: @@ -27292,30 +27304,30 @@ snapshots: '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/send': 0.17.4 '@types/shelljs@0.8.15': dependencies: '@types/glob': 7.2.0 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/sizzle@2.3.9': {} '@types/sockjs@0.3.36': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/stack-utils@2.0.3': {} '@types/tar-fs@2.0.4': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/tar-stream': 3.1.3 '@types/tar-stream@3.1.3': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/tough-cookie@4.0.5': {} @@ -27333,11 +27345,11 @@ snapshots: '@types/vinyl@2.0.12': dependencies: '@types/expect': 1.20.4 - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/ws@8.5.13': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 '@types/yargs-parser@21.0.3': {} @@ -27351,7 +27363,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 optional: true '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.18.0(jiti@1.21.6))(typescript@5.4.5))(eslint@9.18.0(jiti@1.21.6))(typescript@5.4.5)': @@ -27925,13 +27937,13 @@ snapshots: '@typescript-eslint/types': 8.25.0 eslint-visitor-keys: 4.2.0 - '@vitejs/plugin-basic-ssl@1.1.0(vite@5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1))': + '@vitejs/plugin-basic-ssl@1.1.0(vite@5.1.8(@types/node@18.19.64)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1))': dependencies: - vite: 5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1) + vite: 5.1.8(@types/node@18.19.64)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1) - '@vitejs/plugin-basic-ssl@1.1.0(vite@5.1.8(@types/node@20.12.8)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1))': + '@vitejs/plugin-basic-ssl@1.1.0(vite@5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1))': dependencies: - vite: 5.1.8(@types/node@20.12.8)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1) + vite: 5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1) '@vitejs/plugin-basic-ssl@1.2.0(vite@6.2.7(@types/node@20.14.5)(jiti@1.21.6)(less@4.2.2)(lightningcss@1.28.1)(sass-embedded@1.66.0)(sass@1.85.0)(terser@5.39.0)(yaml@2.5.0))': dependencies: @@ -29083,7 +29095,7 @@ snapshots: '@babel/core': 7.24.0 find-cache-dir: 4.0.0 schema-utils: 4.3.2 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) babel-loader@9.2.1(@babel/core@7.23.9)(webpack@5.96.1(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.18.20)): dependencies: @@ -30486,7 +30498,7 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.3.2 serialize-javascript: 6.0.2 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) copy-webpack-plugin@12.0.2(webpack@5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)): dependencies: @@ -30626,13 +30638,13 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 - create-jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): + create-jest@29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + jest-config: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -30641,13 +30653,13 @@ snapshots: - supports-color - ts-node - create-jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): + create-jest@29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -30655,15 +30667,29 @@ snapshots: - babel-plugin-macros - supports-color - ts-node - optional: true - create-jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): + create-jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) + jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + create-jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -30673,13 +30699,13 @@ snapshots: - ts-node optional: true - create-jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)): + create-jest@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -30687,6 +30713,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true create-jest@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: @@ -30801,7 +30828,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.3 optionalDependencies: - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) css-loader@6.10.0(webpack@5.94.0): dependencies: @@ -31617,7 +31644,7 @@ snapshots: dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.17 - '@types/node': 20.12.8 + '@types/node': 18.19.64 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -32027,7 +32054,7 @@ snapshots: esbuild-plugin-alias@0.2.1: {} - esbuild-plugin-vue3@0.3.2(cheerio@1.0.0-rc.10)(sass@1.71.1): + esbuild-plugin-vue3@0.3.2(cheerio@1.0.0-rc.10)(sass@1.85.0): dependencies: '@vue/compiler-core': 3.5.13 '@vue/compiler-sfc': 3.4.27 @@ -32035,7 +32062,7 @@ snapshots: typescript: 4.9.5 optionalDependencies: cheerio: 1.0.0-rc.10 - sass: 1.71.1 + sass: 1.85.0 esbuild-register@3.6.0(esbuild@0.18.20): dependencies: @@ -34276,7 +34303,7 @@ snapshots: vinyl-fs: 4.0.0 optionalDependencies: '@types/eslint': 9.6.1 - '@types/node': 20.12.8 + '@types/node': 18.19.64 transitivePeerDependencies: - jiti - supports-color @@ -34813,7 +34840,7 @@ snapshots: html-void-elements@2.0.1: {} - html-webpack-plugin@5.6.3(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))): + html-webpack-plugin@5.6.3(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -34821,7 +34848,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15)) + webpack: 5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) optional: true html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)): @@ -34832,7 +34859,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) optional: true html-webpack-plugin@5.6.3(webpack@5.96.1(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.18.20)): @@ -35764,7 +35791,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 chalk: 4.1.2 co: 4.6.0 dedent: 1.5.3 @@ -35784,16 +35811,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): + jest-cli@29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + create-jest: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + jest-config: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -35805,16 +35832,16 @@ snapshots: - supports-color - ts-node - jest-cli@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): + jest-cli@29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: - '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + create-jest: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -35825,18 +35852,38 @@ snapshots: - babel-plugin-macros - supports-color - ts-node - optional: true - jest-cli@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): + jest-cli@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): dependencies: - '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) + create-jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) + jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + optionalDependencies: + node-notifier: 9.0.1 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-cli@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): + dependencies: + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -35849,16 +35896,16 @@ snapshots: - ts-node optional: true - jest-cli@29.7.0(@types/node@20.12.8)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)): + jest-cli@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: - '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)) + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)) + create-jest: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) exit: 0.1.2 import-local: 3.2.0 - jest-config: 29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)) + jest-config: 29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -35869,6 +35916,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true jest-cli@29.7.0(@types/node@20.12.8)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: @@ -35934,7 +35982,7 @@ snapshots: - ts-node optional: true - jest-config@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): + jest-config@29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -35959,13 +36007,44 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.11.17 + '@types/node': 18.19.64 + ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5) + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-config@29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): + dependencies: + '@babel/core': 7.23.9 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.23.9) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0 + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 18.19.64 ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5) transitivePeerDependencies: - babel-plugin-macros - supports-color - jest-config@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -35990,14 +36069,14 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.11.17 + '@types/node': 18.19.64 ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color optional: true - jest-config@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): + jest-config@29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -36022,14 +36101,13 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.11.17 + '@types/node': 18.19.64 ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2) transitivePeerDependencies: - babel-plugin-macros - supports-color - optional: true - jest-config@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): + jest-config@29.7.0(@types/node@18.19.64)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.14.5)(typescript@5.9.2)): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -36054,13 +36132,14 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.12.8 - ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5) + '@types/node': 18.19.64 + ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.14.5)(typescript@5.9.2) transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true - jest-config@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -36085,14 +36164,13 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.12.8 - ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5) + '@types/node': 20.11.17 + ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5) transitivePeerDependencies: - babel-plugin-macros - supports-color - optional: true - jest-config@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)): + jest-config@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -36117,13 +36195,14 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.12.8 - ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5) + '@types/node': 20.11.17 + ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5) transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true - jest-config@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): + jest-config@29.7.0(@types/node@20.11.17)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -36148,13 +36227,14 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.12.8 + '@types/node': 20.11.17 ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2) transitivePeerDependencies: - babel-plugin-macros - supports-color + optional: true - jest-config@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.14.5)(typescript@5.9.2)): + jest-config@29.7.0(@types/node@20.12.8)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: '@babel/core': 7.23.9 '@jest/test-sequencer': 29.7.0 @@ -36180,11 +36260,10 @@ snapshots: strip-json-comments: 3.1.1 optionalDependencies: '@types/node': 20.12.8 - ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.14.5)(typescript@5.9.2) + ts-node: 10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2) transitivePeerDependencies: - babel-plugin-macros - supports-color - optional: true jest-config@29.7.0(@types/node@20.14.5)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: @@ -36298,7 +36377,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.12.8 + '@types/node': 18.19.64 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -36337,12 +36416,12 @@ snapshots: jest-mock@27.5.1: dependencies: '@jest/types': 27.5.1 - '@types/node': 20.12.8 + '@types/node': 18.19.64 jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -36377,7 +36456,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -36405,7 +36484,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 chalk: 4.1.2 cjs-module-lexer: 1.4.1 collect-v8-coverage: 1.0.2 @@ -36451,7 +36530,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -36470,7 +36549,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.12.8 + '@types/node': 18.19.64 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -36479,23 +36558,23 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 20.12.8 + '@types/node': 18.19.64 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): + jest@29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)): dependencies: - '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + jest-cli: 29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5)) optionalDependencies: node-notifier: 9.0.1 transitivePeerDependencies: @@ -36504,12 +36583,12 @@ snapshots: - supports-color - ts-node - jest@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): + jest@29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: - '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + jest-cli: 29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) optionalDependencies: node-notifier: 9.0.1 transitivePeerDependencies: @@ -36517,14 +36596,27 @@ snapshots: - babel-plugin-macros - supports-color - ts-node - optional: true - jest@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): + jest@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)): dependencies: - '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) + jest-cli: 29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5)) + optionalDependencies: + node-notifier: 9.0.1 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)): + dependencies: + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5)) optionalDependencies: node-notifier: 9.0.1 transitivePeerDependencies: @@ -36534,12 +36626,12 @@ snapshots: - ts-node optional: true - jest@29.7.0(@types/node@20.12.8)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)): + jest@29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: - '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)) + '@jest/core': 29.7.0(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) '@jest/types': 29.6.3 import-local: 3.2.0 - jest-cli: 29.7.0(@types/node@20.12.8)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5)) + jest-cli: 29.7.0(@types/node@20.11.17)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) optionalDependencies: node-notifier: 9.0.1 transitivePeerDependencies: @@ -36547,6 +36639,7 @@ snapshots: - babel-plugin-macros - supports-color - ts-node + optional: true jest@29.7.0(@types/node@20.12.8)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)): dependencies: @@ -36950,7 +37043,7 @@ snapshots: dependencies: klona: 2.0.6 less: 4.2.0 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) less-loader@12.2.0(less@4.2.2)(webpack@5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)): dependencies: @@ -36997,7 +37090,7 @@ snapshots: dependencies: webpack-sources: 3.2.3 optionalDependencies: - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) license-webpack-plugin@4.0.2(webpack@5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)): dependencies: @@ -37828,7 +37921,7 @@ snapshots: dependencies: schema-utils: 4.3.2 tapable: 2.2.1 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) mini-css-extract-plugin@2.9.2(webpack@5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)): dependencies: @@ -39261,7 +39354,7 @@ snapshots: postcss: 8.4.35 semver: 7.7.2 optionalDependencies: - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) transitivePeerDependencies: - typescript @@ -40359,7 +40452,7 @@ snapshots: rollup@0.58.2: dependencies: '@types/estree': 0.0.38 - '@types/node': 20.12.8 + '@types/node': 18.19.64 rollup@4.22.4: dependencies: @@ -40592,7 +40685,7 @@ snapshots: optionalDependencies: sass: 1.71.1 sass-embedded: 1.66.0 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) sass-loader@16.0.5(sass-embedded@1.66.0)(sass@1.85.0)(webpack@5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)): dependencies: @@ -41066,7 +41159,7 @@ snapshots: dependencies: iconv-lite: 0.6.3 source-map-js: 1.2.1 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) source-map-loader@5.0.0(webpack@5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)): dependencies: @@ -41316,7 +41409,7 @@ snapshots: dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15)) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) string-width@1.0.2: dependencies: @@ -42053,49 +42146,51 @@ snapshots: '@swc/core': 1.9.2(@swc/helpers@0.5.15) esbuild: 0.18.20 - terser-webpack-plugin@5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)): + terser-webpack-plugin@5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) optionalDependencies: '@swc/core': 1.9.2(@swc/helpers@0.5.15) - esbuild: 0.25.0 + esbuild: 0.20.1 - terser-webpack-plugin@5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)(webpack@5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)): + terser-webpack-plugin@5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1) + webpack: 5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) optionalDependencies: '@swc/core': 1.9.2(@swc/helpers@0.5.15) - esbuild: 0.25.1 + esbuild: 0.25.0 + optional: true - terser-webpack-plugin@5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))): + terser-webpack-plugin@5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)(webpack@5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15)) + webpack: 5.98.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.1) optionalDependencies: '@swc/core': 1.9.2(@swc/helpers@0.5.15) + esbuild: 0.25.1 - terser-webpack-plugin@5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))): + terser-webpack-plugin@5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 4.3.2 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15)) + webpack: 5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15)) optionalDependencies: '@swc/core': 1.9.2(@swc/helpers@0.5.15) @@ -42151,7 +42246,7 @@ snapshots: schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.39.0 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15)) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) optionalDependencies: '@swc/core': 1.9.2(@swc/helpers@0.5.15) @@ -42790,7 +42885,7 @@ snapshots: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.1 + semver: 7.7.2 typescript: 4.9.5 yargs-parser: 21.1.1 optionalDependencies: @@ -42807,7 +42902,7 @@ snapshots: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.1 + semver: 7.7.2 typescript: 4.9.5 yargs-parser: 21.1.1 optionalDependencies: @@ -42824,7 +42919,7 @@ snapshots: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.1 + semver: 7.7.2 typescript: 5.9.2 yargs-parser: 21.1.1 optionalDependencies: @@ -42832,6 +42927,24 @@ snapshots: '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.26.10) + ts-jest@29.1.3(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)))(typescript@4.9.5): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + jest: 29.7.0(@types/node@18.19.64)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)) + jest-util: 29.7.0 + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + typescript: 4.9.5 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.26.10 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.26.10) + ts-jest@29.1.3(@babel/core@7.26.10)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.10))(jest@29.7.0(@types/node@20.14.5)(node-notifier@9.0.1)(ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.9.2)))(typescript@4.9.5): dependencies: bs-logger: 0.2.6 @@ -42841,7 +42954,7 @@ snapshots: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.1 + semver: 7.7.2 typescript: 4.9.5 yargs-parser: 21.1.1 optionalDependencies: @@ -42859,7 +42972,7 @@ snapshots: json5: 2.2.3 lodash.memoize: 4.1.2 make-error: 1.3.6 - semver: 7.7.1 + semver: 7.7.2 typescript: 5.9.2 yargs-parser: 21.1.1 optionalDependencies: @@ -42913,27 +43026,27 @@ snapshots: optionalDependencies: '@swc/core': 1.9.2(@swc/helpers@0.5.15) - ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5): + ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@18.19.64)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.11.17 + '@types/node': 18.19.64 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 4.9.5 + typescript: 5.4.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: '@swc/core': 1.9.2(@swc/helpers@0.5.15) - ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5): + ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@4.9.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 @@ -42947,20 +43060,20 @@ snapshots: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.4.5 + typescript: 4.9.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: '@swc/core': 1.9.2(@swc/helpers@0.5.15) - ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.12.8)(typescript@5.4.5): + ts-node@10.9.2(@swc/core@1.9.2(@swc/helpers@0.5.15))(@types/node@20.11.17)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.12.8 + '@types/node': 20.11.17 acorn: 8.15.0 acorn-walk: 8.3.4 arg: 4.1.3 @@ -43741,26 +43854,26 @@ snapshots: replace-ext: 2.0.0 teex: 1.0.1 - vite@5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1): + vite@5.1.8(@types/node@18.19.64)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1): dependencies: esbuild: 0.19.3 postcss: 8.4.38 rollup: 4.22.4 optionalDependencies: - '@types/node': 20.11.17 + '@types/node': 18.19.64 fsevents: 2.3.3 less: 4.2.0 lightningcss: 1.28.1 sass: 1.71.1 terser: 5.29.1 - vite@5.1.8(@types/node@20.12.8)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1): + vite@5.1.8(@types/node@20.11.17)(less@4.2.0)(lightningcss@1.28.1)(sass@1.71.1)(terser@5.29.1): dependencies: esbuild: 0.19.3 postcss: 8.4.38 rollup: 4.22.4 optionalDependencies: - '@types/node': 20.12.8 + '@types/node': 20.11.17 fsevents: 2.3.3 less: 4.2.0 lightningcss: 1.28.1 @@ -43974,7 +44087,7 @@ snapshots: mime-types: 2.1.35 range-parser: 1.2.1 schema-utils: 4.3.2 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) webpack-dev-middleware@6.1.2(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)): dependencies: @@ -43984,7 +44097,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.3.2 optionalDependencies: - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) webpack-dev-middleware@6.1.3(webpack@5.96.1(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.18.20)): dependencies: @@ -44063,7 +44176,7 @@ snapshots: webpack-dev-middleware: 5.3.4(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) ws: 8.18.0 optionalDependencies: - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) transitivePeerDependencies: - bufferutil - debug @@ -44221,19 +44334,19 @@ snapshots: supports-color: 8.1.1 through: 2.3.8 vinyl: 2.2.1 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15)) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) - webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))))(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)): + webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)))(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)): dependencies: typed-assert: 1.0.9 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) optionalDependencies: - html-webpack-plugin: 5.6.3(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))) + html-webpack-plugin: 5.6.3(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)) webpack-subresource-integrity@5.1.0(html-webpack-plugin@5.6.3(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)))(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)): dependencies: typed-assert: 1.0.9 - webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0) + webpack: 5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1) optionalDependencies: html-webpack-plugin: 5.6.3(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) @@ -44280,17 +44393,19 @@ snapshots: - esbuild - uglify-js - webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15)): + webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0): dependencies: - '@types/estree': 1.0.6 + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 '@webassemblyjs/ast': 1.14.1 '@webassemblyjs/wasm-edit': 1.14.1 '@webassemblyjs/wasm-parser': 1.14.1 - acorn: 8.14.0 - acorn-import-attributes: 1.9.5(acorn@8.14.0) - browserslist: 4.24.4 + acorn: 8.15.0 + acorn-import-phases: 1.0.4(acorn@8.15.0) + browserslist: 4.25.3 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.1 + enhanced-resolve: 5.18.3 es-module-lexer: 1.5.4 eslint-scope: 5.1.1 events: 3.3.0 @@ -44300,17 +44415,18 @@ snapshots: loader-runner: 4.3.0 mime-types: 2.1.35 neo-async: 2.6.2 - schema-utils: 3.3.0 + schema-utils: 4.3.2 tapable: 2.2.1 - terser-webpack-plugin: 5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))) + terser-webpack-plugin: 5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)(webpack@5.101.3(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)) watchpack: 2.4.2 - webpack-sources: 3.2.3 + webpack-sources: 3.3.3 transitivePeerDependencies: - '@swc/core' - esbuild - uglify-js + optional: true - webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0): + webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1): dependencies: '@types/estree': 1.0.6 '@webassemblyjs/ast': 1.14.1 @@ -44332,7 +44448,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.25.0)(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) + terser-webpack-plugin: 5.3.14(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)(webpack@5.94.0(@swc/core@1.9.2(@swc/helpers@0.5.15))(esbuild@0.20.1)) watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: diff --git a/tools/scripts/build-nx-plugin.ts b/tools/scripts/build-nx-plugin.ts new file mode 100644 index 000000000000..82bbbd7f8ed7 --- /dev/null +++ b/tools/scripts/build-nx-plugin.ts @@ -0,0 +1,211 @@ +import { execSync } from 'child_process'; +import * as fs from 'fs'; +import * as path from 'path'; + +const PLUGIN_DIR_NAME = 'nx-infra-plugin'; +const DIST_DIR_NAME = 'dist'; +const SRC_DIR_NAME = 'src'; +const TEMP_TSCONFIG_NAME = 'tsconfig.bootstrap.json'; +const TSCONFIG_LIB_NAME = 'tsconfig.lib.json'; +const EXECUTORS_JSON_NAME = 'executors.json'; +const JSON_EXTENSION = '.json'; +const TSCONFIG_PREFIX = 'tsconfig'; + +interface PathConfig { + pluginDir: string; + distDir: string; + srcDir: string; + tsconfig: string; +} + +interface TsConfig { + extends?: string; + compilerOptions?: Record; + include?: string[]; + exclude?: string[]; +} + +interface CompilationResult { + success: boolean; + error?: string; +} + +interface AssetCopyResult { + filesCopied: number; +} + +const buildPathConfig = (rootDir: string): PathConfig => { + const pluginDir = path.join(rootDir, '../../packages', PLUGIN_DIR_NAME); + return { + pluginDir, + distDir: path.join(pluginDir, DIST_DIR_NAME), + srcDir: path.join(pluginDir, SRC_DIR_NAME), + tsconfig: path.join(pluginDir, TSCONFIG_LIB_NAME) + }; +}; + +const checkDistExists = (distPath: string): boolean => { + if (!fs.existsSync(distPath)) return false; + const files = fs.readdirSync(distPath); + return files.length > 0; +}; + +const readTsConfig = (configPath: string): TsConfig => { + const content = fs.readFileSync(configPath, 'utf8'); + return JSON.parse(content); +}; + +const createBootstrapConfig = (original: TsConfig): TsConfig => ({ + ...original, + compilerOptions: { + ...original.compilerOptions, + rootDir: undefined, + outDir: `./${DIST_DIR_NAME}` + } +}); + +const writeTsConfig = (configPath: string, config: TsConfig): void => { + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); +}; + +const compileTypeScript = (pluginDir: string, configPath: string): CompilationResult => { + try { + execSync( + `npx tsc -p ${configPath}`, + { + cwd: pluginDir, + stdio: 'inherit', + env: { ...process.env, NODE_ENV: 'production' } + } + ); + return { success: true }; + } catch (error) { + return { + success: false, + error: (error as Error).message + }; + } +}; + +const isJsonAsset = (filename: string): boolean => + filename.endsWith(JSON_EXTENSION) && !filename.includes(TSCONFIG_PREFIX); + +const copyJsonAssets = (srcDir: string, destDir: string): AssetCopyResult => { + if (!fs.existsSync(srcDir)) { + return { filesCopied: 0 }; + } + + let filesCopied = 0; + const entries = fs.readdirSync(srcDir, { withFileTypes: true }); + + for (const entry of entries) { + const srcPath = path.join(srcDir, entry.name); + const destPath = path.join(destDir, entry.name); + + if (entry.isDirectory()) { + if (!fs.existsSync(destPath)) { + fs.mkdirSync(destPath, { recursive: true }); + } + const result = copyJsonAssets(srcPath, destPath); + filesCopied += result.filesCopied; + } else if (isJsonAsset(entry.name)) { + fs.copyFileSync(srcPath, destPath); + filesCopied++; + } + } + + return { filesCopied }; +}; + +const updateExecutorPaths = (config: Record): Record => { + const executors = config.executors as Record; + + const updated = Object.entries(executors).reduce((acc, [key, value]) => { + acc[key] = { + ...value, + implementation: value.implementation.replace('./src/', './'), + schema: value.schema.replace('./src/', './') + }; + return acc; + }, {} as Record); + + return { ...config, executors: updated }; +}; + +const copyExecutorsJson = (pluginDir: string, distDir: string): boolean => { + const sourcePath = path.join(pluginDir, EXECUTORS_JSON_NAME); + if (!fs.existsSync(sourcePath)) return false; + + const content = fs.readFileSync(sourcePath, 'utf8'); + const config = JSON.parse(content); + const updatedConfig = updateExecutorPaths(config); + + const destPath = path.join(distDir, EXECUTORS_JSON_NAME); + fs.writeFileSync(destPath, JSON.stringify(updatedConfig, null, 2)); + return true; +}; + +const cleanupTempConfig = (configPath: string): void => { + if (fs.existsSync(configPath)) { + fs.unlinkSync(configPath); + } +}; + +const shouldSkipBuild = (distPath: string, forceRebuild: boolean): boolean => { + if (forceRebuild) return false; + return checkDistExists(distPath); +}; + +const buildPlugin = (paths: PathConfig, forceRebuild = false): void => { + if (shouldSkipBuild(paths.distDir, forceRebuild)) { + console.log('✓ Plugin already built, skipping...'); + process.exit(0); + } + + console.log(' Compiling TypeScript...'); + + const tempConfigPath = path.join(paths.pluginDir, TEMP_TSCONFIG_NAME); + const originalConfig = readTsConfig(paths.tsconfig); + const bootstrapConfig = createBootstrapConfig(originalConfig); + + writeTsConfig(tempConfigPath, bootstrapConfig); + + try { + const result = compileTypeScript(paths.pluginDir, tempConfigPath); + if (!result.success) { + throw new Error(result.error); + } + + console.log(' Copying assets...'); + copyJsonAssets(paths.srcDir, paths.distDir); + + copyExecutorsJson(paths.pluginDir, paths.distDir); + + console.log('✓ Plugin built successfully!'); + } finally { + cleanupTempConfig(tempConfigPath); + } +}; + +const parseArgs = (): { forceRebuild: boolean } => { + const args = process.argv.slice(2); + return { + forceRebuild: args.includes('--force') || args.includes('-f') + }; +}; + +const main = (): void => { + console.log('🔨 Building nx-infra-plugin...'); + + try { + const { forceRebuild } = parseArgs(); + const paths = buildPathConfig(__dirname); + buildPlugin(paths, forceRebuild); + } catch (error) { + console.error('⚠ Failed to build plugin:', (error as Error).message); + console.error(' The plugin will be built on first use by NX'); + process.exit(0); + } +}; + +main();