From f109b4fab28dda3733460d472d83680cb7286d28 Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Fri, 5 Jul 2024 14:01:52 +0100 Subject: [PATCH 01/10] customizable allowed import libs, user configuration js file (#52) - Adds the ability to define and load a js or ts configuration - Expose a way in the configuration to register interceptor methods that can be used to allow custom modules and custom exports from custom libraries to be processed - Fix the import.ts code to handle properly the configuration file interceptors - Adds cli end to end tests --- package.json | 1 + packages/ast/src/imports.ts | 827 +++++++++++++----- packages/ast/src/types/transform.ts | 27 +- packages/build-config/package.json | 2 +- packages/cli/.gitignore | 1 + packages/cli/src/cli.ts | 12 +- packages/cli/src/commands/migrate.ts | 87 +- packages/cli/src/test/cli.test.ts | 78 ++ .../cli/src/test/expected/custom-imports.js | 16 + packages/cli/src/test/expected/plain.js | 8 + packages/cli/src/test/input-files/README.md | 1 + .../src/test/input-files/custom-imports.js | 16 + packages/cli/src/test/input-files/plain.js | 8 + packages/cli/src/test/test-utils.ts | 61 ++ packages/cli/src/test/user-config.ts | 18 + packages/cli/src/user-config.ts | 42 + packages/cli/src/utils/module.ts | 11 - packages/cli/tsconfig.json | 2 +- packages/codemod-task-utils/lib.test.ts | 1 + packages/codemod-task-utils/package.json | 1 + packages/codemod-task-utils/src/task.ts | 13 +- packages/codemod-task-utils/src/worker.ts | 19 +- packages/codemod-utils/lib.test.ts | 1 + .../codemod-utils/src/agGridHelpers.test.ts | 119 ++- packages/codemod-utils/src/agGridHelpers.ts | 113 ++- packages/codemod-utils/src/angularHelpers.ts | 29 +- packages/codemod-utils/src/transform/js.ts | 3 +- packages/codemod-utils/src/transform/vue.ts | 4 +- packages/codemod-utils/src/types/transform.ts | 3 +- packages/codemods/src/test/runners/codemod.ts | 7 +- .../codemods/src/test/runners/transform.ts | 5 +- .../migrate-legacy-js-grid-constructor.ts | 44 +- .../codemods/src/versions/31.0.0/codemod.ts | 3 +- .../codemods/src/versions/31.1.0/codemod.ts | 3 +- .../codemods/src/versions/31.2.0/codemod.ts | 3 +- .../codemods/src/versions/31.3.0/codemod.ts | 3 +- .../codemods/src/versions/32.0.0/codemod.ts | 3 +- packages/codemods/src/worker.ts | 20 +- .../create-version/codemod/codemod.ts | 3 +- packages/types/lib.ts | 2 + packages/types/src/ag-grid-export-name.ts | 46 + packages/types/src/codemod.ts | 2 + packages/types/src/task.ts | 2 + packages/types/src/user-config.ts | 104 +++ packages/utils/lib.test.ts | 2 +- packages/utils/lib.ts | 2 +- packages/utils/src/module.ts | 34 + packages/utils/src/stringHelpers.test.ts | 17 - packages/utils/src/stringHelpers.ts | 4 - pnpm-lock.yaml | 142 ++- 50 files changed, 1551 insertions(+), 424 deletions(-) create mode 100644 packages/cli/src/test/cli.test.ts create mode 100644 packages/cli/src/test/expected/custom-imports.js create mode 100644 packages/cli/src/test/expected/plain.js create mode 100644 packages/cli/src/test/input-files/README.md create mode 100644 packages/cli/src/test/input-files/custom-imports.js create mode 100644 packages/cli/src/test/input-files/plain.js create mode 100644 packages/cli/src/test/test-utils.ts create mode 100644 packages/cli/src/test/user-config.ts create mode 100644 packages/cli/src/user-config.ts delete mode 100644 packages/cli/src/utils/module.ts create mode 100644 packages/types/src/ag-grid-export-name.ts create mode 100644 packages/types/src/user-config.ts create mode 100644 packages/utils/src/module.ts delete mode 100644 packages/utils/src/stringHelpers.test.ts delete mode 100644 packages/utils/src/stringHelpers.ts diff --git a/package.json b/package.json index 75aa1d77..5775c0d8 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "prettier": "3.3.2", "typedoc": "^0.26.3", "typescript": "5.5.3", + "tsx": "4.16.2", "vite": "5.3.3", "vitest": "1.6.0", "@vitest/coverage-v8": "1.6.0" diff --git a/packages/ast/src/imports.ts b/packages/ast/src/imports.ts index 0c040f4b..dcdfcf17 100644 --- a/packages/ast/src/imports.ts +++ b/packages/ast/src/imports.ts @@ -1,6 +1,21 @@ -import { type Binding, type NodePath, type Types } from './types'; +import { + AstTransformContext, + ImportMatcherResult, + TransformContext, + type Binding, + type NodePath, + type Types, +} from './types'; import { getOptionalNodeFieldValue, getStaticPropertyKey, node as t } from './node'; -import { Enum, EnumVariant, match, matchString } from '@ag-grid-devtools/utils'; +import { Enum, EnumVariant, match } from '@ag-grid-devtools/utils'; +import { + Framework, + MatchGridImportNameArgs, + ImportType, + KnownExportName, + AgGridExportName, + isAgGridExportName, +} from '@ag-grid-devtools/types'; type CallExpression = Types.CallExpression; type Expression = Types.Expression; @@ -114,77 +129,266 @@ const NamedUmdImportBindingAccessor = Enum.create< Namespaced: true, }); +export interface ImportedModuleMatcher { + /** The basic pattern or module name to match */ + importModulePattern: RegExp | string; + + /** The umd pattern or module name to match */ + importUmdPattern: RegExp | string | null; + + /** The framework type, passed as is to the user config isGridModule method. */ + framework: Framework; + + /** If true, the UserConfig callbacks will not be called. Default is false. */ + skipUserConfig?: boolean; +} + +function matchImportedSpecifier( + moduleType: ImportType, + importPath: string, + importedModuleMatcher: ImportedModuleMatcher, + importName: string, + agGridExportName: KnownExportName, + context: AstTransformContext, +): ImportMatcherResult | null { + const { + importModulePattern: pattern, + importUmdPattern: umdPattern, + framework, + skipUserConfig, + } = importedModuleMatcher; + let patternToCheck = pattern; + if (moduleType === 'umd' && umdPattern) { + patternToCheck = umdPattern; + } + + if ( + typeof patternToCheck === 'string' + ? importPath === patternToCheck + : patternToCheck.test(importPath) + ) { + if (importName == agGridExportName) { + return { fromUserConfig: null }; + } + return null; + } + + if (skipUserConfig) { + return null; + } + + if (!isAgGridExportName(agGridExportName)) { + return null; // This specifier is not an ag-grid export, no user config needed. + } + + const userConfig = context.opts.userConfig; + if (!userConfig) { + return null; + } + + const filename = context.filename; + + if (userConfig.matchGridImport) { + // Store in the cache so we don't ask the same question to the UserConfig isGridModule + const cacheKey = importPath + '\n' + framework + '\n' + moduleType + '\n' + filename; + let userConfigIsGridModuleCache = context._userConfigIsGridModuleCache; + if (!userConfigIsGridModuleCache) { + userConfigIsGridModuleCache = new Map(); + context._userConfigIsGridModuleCache = userConfigIsGridModuleCache; + } + + let moduleArgs = userConfigIsGridModuleCache.get(cacheKey); + if (moduleArgs === null) { + return null; // UserConfig has already said this is not a grid module. + } + if (moduleArgs === undefined) { + moduleArgs = { + importPath: importPath, + framework, + importType: moduleType, + sourceFilePath: filename, + }; + + if (!userConfig.matchGridImport(moduleArgs)) { + userConfigIsGridModuleCache.set(cacheKey, null); + return null; // UserConfig has said this is not a grid module. + } + userConfigIsGridModuleCache.set(cacheKey, moduleArgs); + } + + if (userConfig.matchGridImportName) { + // Store in the cache so we don't ask the same question to the UserConfig isGridModuleExport + const specifierCacheKey = cacheKey + '\n' + match + '\n' + importName; + + let userConfigIsGridModuleExportCache = context._userConfigIsGridModuleExportCache; + + let result: ImportMatcherResult | null | undefined; + + if (!userConfigIsGridModuleExportCache) { + userConfigIsGridModuleExportCache = new Map(); + context._userConfigIsGridModuleExportCache = userConfigIsGridModuleExportCache; + } else { + result = userConfigIsGridModuleExportCache.get(specifierCacheKey); + } + + if (result !== undefined) { + return result; // UserConfig has already answered this question. + } + + const fromUserConfig: MatchGridImportNameArgs = { + ...moduleArgs, + agGridExportName, + importName, + }; + + if (userConfig.matchGridImportName(fromUserConfig)) { + result = { fromUserConfig }; + } + + if ( + !result && + framework === 'vanilla' && + agGridExportName === AgGridExportName.createGrid && + userConfig.getCreateGridName + ) { + result = { fromUserConfig }; // Special case for createGrid + } + + if (!result) { + result = null; + } + + userConfigIsGridModuleExportCache.set(specifierCacheKey, result); + return result; + } + + if (importName === agGridExportName) { + return { fromUserConfig: null }; + } + + if ( + framework === 'vanilla' && + agGridExportName === AgGridExportName.createGrid && + userConfig.getCreateGridName + ) { + return { + fromUserConfig: { + ...moduleArgs, + agGridExportName, + importName, + }, + }; // Special case for the config createGridName + } + } + + if (importName === agGridExportName) { + return { fromUserConfig: null }; + } + + return null; +} + +export interface PackageNamespaceImport { + binding: NamedImportBinding; + importMatcherResult: ImportMatcherResult; +} + export function getNamedModuleImportExpression( expression: NodePath, - packageName: string | RegExp, - umdGlobalName: string | RegExp | null, - importedName: string | RegExp, -): NamedImportBinding | null { + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): PackageNamespaceImport | null { if (expression.isIdentifier()) { const binding = expression.scope.getBinding(expression.node.name); if (!binding) return null; - return getNamedModuleImportBinding(binding, packageName, umdGlobalName, importedName); + return getNamedModuleImportBinding( + binding, + importedModuleMatcher, + importedModuleSpecifierMatcher, + context, + ); } if (expression.isJSXIdentifier()) { const binding = expression.scope.getBinding(expression.node.name); if (!binding) return null; - return getNamedModuleImportBinding(binding, packageName, umdGlobalName, importedName); + return getNamedModuleImportBinding( + binding, + importedModuleMatcher, + importedModuleSpecifierMatcher, + context, + ); } if (expression.isMemberExpression()) { const object = expression.get('object'); const property = expression.get('property'); const computed = expression.node.computed; const propertyKey = getStaticPropertyKey(property.node, computed); - if (!propertyKey || !matchString(propertyKey, importedName)) return null; + if (!propertyKey) return null; const namespaceImport = getNamedPackageNamespaceImportExpression( object, - packageName, - umdGlobalName, + importedModuleMatcher, + propertyKey, + importedModuleSpecifierMatcher, + context, ); if (!namespaceImport) return null; - return match(namespaceImport, { - CommonJs: ({ require }) => - NamedImportBinding.CommonJs({ - require, - accessor: NamedCommonJsImportBindingAccessor.Namespaced({ - accessor: expression, - local: null, + return { + importMatcherResult: namespaceImport.importMatcherResult, + binding: match(namespaceImport.binding, { + CommonJs: ({ require }) => + NamedImportBinding.CommonJs({ + require, + accessor: NamedCommonJsImportBindingAccessor.Namespaced({ + accessor: expression, + local: null, + }), }), - }), - Module: ({ declaration, specifier }) => - NamedImportBinding.Module({ - declaration, - accessor: NamedModuleImportBindingAccessor.Namespaced({ - specifier, - accessor: expression, - local: null, + Module: ({ declaration, specifier }) => + NamedImportBinding.Module({ + declaration, + accessor: NamedModuleImportBindingAccessor.Namespaced({ + specifier, + accessor: expression, + local: null, + }), }), - }), - UmdGlobal: ({}) => - NamedImportBinding.UmdGlobal({ - accessor: NamedUmdImportBindingAccessor.Namespaced({ - accessor: expression, - local: null, + UmdGlobal: ({}) => + NamedImportBinding.UmdGlobal({ + accessor: NamedUmdImportBindingAccessor.Namespaced({ + accessor: expression, + local: null, + }), }), - }), - }); + }), + }; } return null; } function getNamedModuleImportBinding( binding: Binding, - packageName: string | RegExp, - umdGlobalName: string | RegExp | null, - importedName: string | RegExp, -): NamedImportBinding | null { + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): PackageNamespaceImport | null { switch (binding.kind) { case 'module': - return getNamedEsModuleImportBinding(binding, packageName, importedName); + return getNamedEsModuleImportBinding( + binding, + importedModuleMatcher, + importedModuleSpecifierMatcher, + context, + ); case 'var': case 'let': case 'const': - return getNamedUmdImportBinding(binding, packageName, umdGlobalName, importedName); + return getNamedUmdImportBinding( + binding, + importedModuleMatcher, + importedModuleSpecifierMatcher, + context, + ); case 'hoisted': case 'param': case 'local': @@ -196,9 +400,10 @@ function getNamedModuleImportBinding( function getNamedEsModuleImportBinding( binding: Binding, - packageName: string | RegExp, - importedName: string | RegExp, -): NamedImportBinding | null { + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): PackageNamespaceImport | null { const target = binding.path; if (!target.isImportSpecifier() || !target.parentPath.isImportDeclaration()) { return null; @@ -207,22 +412,35 @@ function getNamedEsModuleImportBinding( parentPath: { node: importDeclaration }, node: importSpecifier, } = target; - if (!matchModuleImportName(importDeclaration, importSpecifier, packageName, importedName)) + + const matchedResult = matchModuleImportName( + importDeclaration, + importSpecifier, + importedModuleMatcher, + importedModuleSpecifierMatcher, + context, + ); + + if (!matchedResult) { return null; - return NamedImportBinding.Module({ - declaration: target.parentPath, - accessor: NamedModuleImportBindingAccessor.Named({ - specifier: target, + } + return { + binding: NamedImportBinding.Module({ + declaration: target.parentPath, + accessor: NamedModuleImportBindingAccessor.Named({ + specifier: target, + }), }), - }); + importMatcherResult: matchedResult.importMatcherResult, + }; } function getNamedUmdImportBinding( binding: Binding, - packageName: string | RegExp, - umdGlobalName: string | RegExp | null, - importedName: string | RegExp, -): NamedImportBinding | null { + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): PackageNamespaceImport | null { const target = binding.path; if (!target.isVariableDeclarator() || !target.parentPath.isVariableDeclaration()) return null; const initializer = getOptionalNodeFieldValue(target.get('init')); @@ -231,149 +449,284 @@ function getNamedUmdImportBinding( const object = initializer.get('object'); const key = initializer.get('property'); const computed = initializer.node.computed; - const actualImportedName = getStaticPropertyKey(key.node, computed); - if (!actualImportedName || !matchString(actualImportedName, importedName)) return null; + const importedModuleSpecifier = getStaticPropertyKey(key.node, computed); + if (!importedModuleSpecifier) return null; const exportAccessor = target.get('id'); if (!exportAccessor.isIdentifier()) return null; - if (isNamedCommonJsRequireExpression(object, packageName)) { - return NamedImportBinding.CommonJs({ - require: object, - accessor: NamedCommonJsImportBindingAccessor.Namespaced({ - accessor: initializer, - local: exportAccessor, - }), - }); + + if (isCommonJsRequireExpression(object)) { + const importMatcherResult = getNamedCommonJsRequireExpression( + object, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); + if (importMatcherResult) { + return { + binding: NamedImportBinding.CommonJs({ + require: object, + accessor: NamedCommonJsImportBindingAccessor.Namespaced({ + accessor: initializer, + local: exportAccessor, + }), + }), + importMatcherResult, + }; + } } - if (umdGlobalName != null && isNamedUmdGlobalNamespaceExpression(object, umdGlobalName)) { - return NamedImportBinding.UmdGlobal({ - accessor: NamedCommonJsImportBindingAccessor.Namespaced({ - accessor: initializer, - local: exportAccessor, - }), - }); + + if (object.isIdentifier()) { + const importMatcherResult = getNamedUmdGlobalNamespaceExpression( + object, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); + if (importMatcherResult) { + return { + binding: NamedImportBinding.UmdGlobal({ + accessor: NamedCommonJsImportBindingAccessor.Namespaced({ + accessor: initializer, + local: exportAccessor, + }), + }), + importMatcherResult, + }; + } } - return null; } - if (isNamedCommonJsRequireExpression(initializer, packageName)) { + if (isCommonJsRequireExpression(initializer)) { const exportAccessors = target.get('id'); - if (!exportAccessors.isObjectPattern()) return null; - const exportAccessor = exportAccessors - .get('properties') - .filter((property): property is NodePath => property.isObjectProperty()) - .find((property) => { - const local = property.get('value'); - return local.isIdentifier() && local.node.name === binding.identifier.name; - }); - if (!exportAccessor) return null; - const local = exportAccessor.get('value'); - if (!local.isIdentifier()) return null; - const importedKey = exportAccessor.get('key'); - const computed = exportAccessor.node.computed; - const actualImportedName = getStaticPropertyKey(importedKey.node, computed); - if (!actualImportedName || !matchString(actualImportedName, importedName)) return null; - return NamedImportBinding.CommonJs({ - require: initializer, - accessor: NamedCommonJsImportBindingAccessor.Destructured({ - declaration: target.parentPath, - declarator: target, - accessors: exportAccessors, - local: local, - }), - }); + const exportAccessor = + exportAccessors.isObjectPattern() && + exportAccessors + .get('properties') + .filter((property): property is NodePath => property.isObjectProperty()) + .find((property) => { + const local = property.get('value'); + return local.isIdentifier() && local.node.name === binding.identifier.name; + }); + const local = exportAccessor && exportAccessor.get('value'); + if (local && local.isIdentifier()) { + const importedKey = exportAccessor.get('key'); + const computed = exportAccessor.node.computed; + const actualImportedName = getStaticPropertyKey(importedKey.node, computed); + if (actualImportedName) { + const importMatcherResult = getNamedCommonJsRequireExpression( + initializer, + importedModuleMatcher, + actualImportedName, + importedModuleSpecifierMatcher, + context, + ); + if (importMatcherResult) { + return { + binding: NamedImportBinding.CommonJs({ + require: initializer, + accessor: NamedCommonJsImportBindingAccessor.Destructured({ + declaration: target.parentPath, + declarator: target, + accessors: exportAccessors, + local: local, + }), + }), + importMatcherResult, + }; + } + } + } } - if (umdGlobalName && isNamedUmdGlobalNamespaceExpression(initializer, umdGlobalName)) { + + if (initializer.isIdentifier()) { const exportAccessors = target.get('id'); - if (!exportAccessors.isObjectPattern()) return null; - const exportAccessor = exportAccessors - .get('properties') - .filter((property): property is NodePath => property.isObjectProperty()) - .find((property) => { - const local = property.get('value'); - return local.isIdentifier() && local.node.name === binding.identifier.name; - }); - if (!exportAccessor) return null; - const local = exportAccessor.get('value'); - if (!local.isIdentifier()) return null; - const importedKey = exportAccessor.get('key'); - const computed = exportAccessor.node.computed; - const actualImportedName = getStaticPropertyKey(importedKey.node, computed); - if (!actualImportedName || !matchString(actualImportedName, importedName)) return null; - return NamedImportBinding.UmdGlobal({ - accessor: NamedCommonJsImportBindingAccessor.Destructured({ - declaration: target.parentPath, - declarator: target, - accessors: exportAccessors, - local: local, - }), - }); + if (exportAccessors.isObjectPattern()) { + const exportAccessor = exportAccessors + .get('properties') + .filter((property): property is NodePath => property.isObjectProperty()) + .find((property) => { + const local = property.get('value'); + return local.isIdentifier() && local.node.name === binding.identifier.name; + }); + + const local = exportAccessor && exportAccessor.get('value'); + if (local && local.isIdentifier()) { + const importedKey = exportAccessor.get('key'); + const computed = exportAccessor.node.computed; + const actualImportedName = getStaticPropertyKey(importedKey.node, computed); + const importMatcherResult = + actualImportedName && + getNamedUmdGlobalNamespaceExpression( + initializer, + importedModuleMatcher, + actualImportedName, + importedModuleSpecifierMatcher, + context, + ); + + if (importMatcherResult) { + return { + binding: NamedImportBinding.UmdGlobal({ + accessor: NamedCommonJsImportBindingAccessor.Destructured({ + declaration: target.parentPath, + declarator: target, + accessors: exportAccessors, + local: local, + }), + }), + importMatcherResult, + }; + } + } + } } + return null; } +function getNamedUmdImportBindingCommonJsSpecifier( + binding: Binding, + target: NodePath, +) { + const exportAccessors = target.get('id'); + if (!exportAccessors.isObjectPattern()) return null; + const exportAccessor = exportAccessors + .get('properties') + .filter((property): property is NodePath => property.isObjectProperty()) + .find((property) => { + const local = property.get('value'); + return local.isIdentifier() && local.node.name === binding.identifier.name; + }); + if (!exportAccessor) return null; + const local = exportAccessor.get('value'); + if (!local.isIdentifier()) return null; + const importedKey = exportAccessor.get('key'); + const computed = exportAccessor.node.computed; + return getStaticPropertyKey(importedKey.node, computed); +} + +export interface NamedPackageNamespaceImport { + binding: PackageNamespaceImportBinding; + importMatcherResult: ImportMatcherResult; +} + export function getNamedPackageNamespaceImportExpression( expression: NodePath, - packageName: string | RegExp, - umdGlobalName: string | RegExp | null, -): PackageNamespaceImportBinding | null { + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifier: string, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): NamedPackageNamespaceImport | null { if (isCommonJsRequireExpression(expression)) { - if (!matchString(expression.node.arguments[0].value, packageName)) return null; - return PackageNamespaceImportBinding.CommonJs({ - require: expression, - local: null, - }); - } - if (umdGlobalName != null && isNamedUmdGlobalNamespaceExpression(expression, umdGlobalName)) { - return PackageNamespaceImportBinding.UmdGlobal({ - local: expression, - }); + const importMatcherResult = matchImportedSpecifier( + 'cjs', + expression.node.arguments[0].value, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); + if (importMatcherResult) { + return { + importMatcherResult: importMatcherResult, + binding: PackageNamespaceImportBinding.CommonJs({ + require: expression, + local: null, + }), + }; + } } + if (expression.isIdentifier()) { + const importMatcherResult = getNamedUmdGlobalNamespaceExpression( + expression, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); + if (importMatcherResult) { + return { + binding: PackageNamespaceImportBinding.UmdGlobal({ local: expression }), + importMatcherResult, + }; + } + const binding = expression.scope.getBinding(expression.node.name); if (!binding) return null; const packageNamespaceImport = getNamedPackageNamespaceImportBinding( binding, - packageName, - umdGlobalName, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, ); if (!packageNamespaceImport) return null; - return match(packageNamespaceImport, { - CommonJs: ({ require }) => - PackageNamespaceImportBinding.CommonJs({ - require, - local: expression, - }), - Module: ({ declaration, specifier }) => - PackageNamespaceImportBinding.Module({ - declaration, - specifier, - local: expression, - }), - UmdGlobal: ({}) => PackageNamespaceImportBinding.UmdGlobal({ local: expression }), - }); + return { + binding: match(packageNamespaceImport.binding, { + CommonJs: ({ require }) => + PackageNamespaceImportBinding.CommonJs({ + require, + local: expression, + }), + Module: ({ declaration, specifier }) => + PackageNamespaceImportBinding.Module({ + declaration, + specifier, + local: expression, + }), + UmdGlobal: ({}) => PackageNamespaceImportBinding.UmdGlobal({ local: expression }), + }), + importMatcherResult: packageNamespaceImport.importMatcherResult, + }; } return null; } -function isNamedUmdGlobalNamespaceExpression( - expression: NodePath, - umdGlobalName: string | RegExp, -): expression is NodePath { - if (!expression.isIdentifier()) return false; - return matchString(expression.node.name, umdGlobalName); +function getNamedUmdGlobalNamespaceExpression( + expression: NodePath, + importedModuleMatcher: ImportedModuleMatcher, + importName: string, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): ImportMatcherResult | null { + return matchImportedSpecifier( + 'umd', + expression.node.name, + importedModuleMatcher, + importName, + importedModuleSpecifierMatcher, + context, + ); } function getNamedPackageNamespaceImportBinding( binding: Binding, - packageName: string | RegExp, - umdGlobalName: string | RegExp | null, -): PackageNamespaceImportBinding | null { + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifier: string, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): NamedPackageNamespaceImport | null { switch (binding.kind) { case 'module': - return getNamedEsModulePackageNamespaceImportBinding(binding, packageName); + return getNamedEsModulePackageNamespaceImportBinding( + binding, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); case 'var': case 'let': case 'const': - return getNamedUmdPackageNamespaceImportBinding(binding, packageName, umdGlobalName); + return getNamedUmdPackageNamespaceImportBinding( + binding, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); case 'hoisted': case 'param': case 'local': @@ -385,8 +738,11 @@ function getNamedPackageNamespaceImportBinding( function getNamedEsModulePackageNamespaceImportBinding( binding: Binding, - packageName: string | RegExp, -): PackageNamespaceImportBinding | null { + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifier: string, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): NamedPackageNamespaceImport | null { const target = binding.path; if (!target.isImportNamespaceSpecifier() || !target.parentPath.isImportDeclaration()) { return null; @@ -394,33 +750,66 @@ function getNamedEsModulePackageNamespaceImportBinding( const { parentPath: { node: importDeclaration }, } = target; - if (!matchModuleImportPackageName(importDeclaration, packageName)) return null; - return PackageNamespaceImportBinding.Module({ - declaration: target.parentPath, - specifier: target, - local: target.get('local'), - }); + const matchResult = matchModuleImportPackageName( + importDeclaration, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); + if (!matchResult) { + return null; + } + return { + binding: PackageNamespaceImportBinding.Module({ + declaration: target.parentPath, + specifier: target, + local: target.get('local'), + }), + importMatcherResult: matchResult.importMatcherResult, + }; } function getNamedUmdPackageNamespaceImportBinding( binding: Binding, - packageName: string | RegExp, - umdGlobalName: string | RegExp | null, -): PackageNamespaceImportBinding | null { + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifier: string, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): NamedPackageNamespaceImport | null { const target = binding.path; if (!target.isVariableDeclarator() || !target.parentPath.isVariableDeclaration()) return null; const initializer = getOptionalNodeFieldValue(target.get('init')); if (!initializer) return null; - if (isNamedCommonJsRequireExpression(initializer, packageName)) { - return PackageNamespaceImportBinding.CommonJs({ - require: initializer, - local: null, - }); + if (isCommonJsRequireExpression(initializer)) { + const importMatcherResult = getNamedCommonJsRequireExpression( + initializer, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); + if (importMatcherResult) { + return { + binding: PackageNamespaceImportBinding.CommonJs({ require: initializer, local: null }), + importMatcherResult, + }; + } } - if (umdGlobalName != null && isNamedUmdGlobalNamespaceExpression(initializer, umdGlobalName)) { - return PackageNamespaceImportBinding.UmdGlobal({ - local: null, - }); + if (initializer.isIdentifier()) { + const importMatcherResult = getNamedUmdGlobalNamespaceExpression( + initializer, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); + if (importMatcherResult) { + return { + binding: PackageNamespaceImportBinding.UmdGlobal({ local: null }), + importMatcherResult, + }; + } } return null; } @@ -428,40 +817,63 @@ function getNamedUmdPackageNamespaceImportBinding( export function matchModuleImportName( declaration: ImportDeclaration, specifier: ImportSpecifier, - packageName: string | RegExp, - importedName: string | RegExp, + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, ): { packageName: string; - importedName: string; + importName: string; + importMatcherResult: ImportMatcherResult; } | null { - const actualPackageName = matchModuleImportPackageName(declaration, packageName); - if (!actualPackageName) return null; const actualImportedName = getImportSpecifierImportedName(specifier); - if (!matchString(actualImportedName, importedName)) return null; - return { packageName: actualPackageName, importedName: actualImportedName }; + const matchedModuleImportPackage = matchModuleImportPackageName( + declaration, + importedModuleMatcher, + actualImportedName, + importedModuleSpecifierMatcher, + context, + ); + if (!matchedModuleImportPackage) return null; + const { actualPackageName, importMatcherResult } = matchedModuleImportPackage; + return { + packageName: actualPackageName, + importName: actualImportedName, + importMatcherResult, + }; } export function matchModuleImportPackageName( declaration: ImportDeclaration, - packageName: string | RegExp, -): string | null { + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifier: string, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): { actualPackageName: string; importMatcherResult: ImportMatcherResult } | null { const actualPackageName = declaration.source.value; - if (!matchString(actualPackageName, packageName)) return null; - return actualPackageName; + if (!actualPackageName) return null; + const importMatcherResult = matchImportedSpecifier( + 'esm', + actualPackageName, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); + if (!importMatcherResult) return null; + return { actualPackageName, importMatcherResult }; } export function findNamedModuleImport( declaration: ImportDeclaration, - pattern: string | RegExp, + importedModuleSpecifierMatcher: string, + context: AstTransformContext, ): ImportSpecifier | null { return ( declaration.specifiers .filter((node): node is ImportSpecifier => t.isImportSpecifier(node)) - .find((specifier) => { - const importedItem = getImportSpecifierImportedName(specifier); - if (!matchString(importedItem, pattern)) return null; - return true; - }) || null + .find( + (specifier) => getImportSpecifierImportedName(specifier) === importedModuleSpecifierMatcher, + ) || null ); } @@ -477,13 +889,22 @@ export function getImportSpecifierImportedName(specifier: ImportSpecifier): stri return t.isStringLiteral(specifier.imported) ? specifier.imported.value : specifier.imported.name; } -function isNamedCommonJsRequireExpression( - expression: NodePath, - packageName: string | RegExp, -): expression is NodePath { - if (!isCommonJsRequireExpression(expression)) return false; +function getNamedCommonJsRequireExpression( + expression: NodePath, + importedModuleMatcher: ImportedModuleMatcher, + importedModuleSpecifier: string, + importedModuleSpecifierMatcher: KnownExportName, + context: AstTransformContext, +): ImportMatcherResult | null { const requirePath = expression.node.arguments[0].value; - return matchString(requirePath, packageName); + return matchImportedSpecifier( + 'cjs', + requirePath, + importedModuleMatcher, + importedModuleSpecifier, + importedModuleSpecifierMatcher, + context, + ); } function isCommonJsRequireExpression( diff --git a/packages/ast/src/types/transform.ts b/packages/ast/src/types/transform.ts index 7a1164d6..f4c30171 100644 --- a/packages/ast/src/types/transform.ts +++ b/packages/ast/src/types/transform.ts @@ -1,4 +1,9 @@ -import { type FsUtils } from '@ag-grid-devtools/types'; +import type { + UserConfig, + FsUtils, + MatchGridImportArgs, + MatchGridImportNameArgs, +} from '@ag-grid-devtools/types'; import type { NodePath, PluginObj, PluginPass, Visitor } from '@babel/core'; import type * as BabelCore from '@babel/core'; @@ -12,6 +17,11 @@ export type BabelPluginWithOptions = export type AstTransform = BabelPlugin>; +export interface ImportMatcherResult { + /** Contains the arguments passed to UserConfig.matchGridImportName, if this is a customized import */ + fromUserConfig: MatchGridImportNameArgs | null; +} + export type AstTransformWithOptions< S extends object = object, T extends object = object, @@ -19,21 +29,28 @@ export type AstTransformWithOptions< export interface AstTransformContext extends FileMetadata { opts: S; + + _userConfigIsGridModuleCache?: Map; + _userConfigIsGridModuleExportCache?: Map; } export interface FileMetadata { filename: string; } -export interface AstCliContext extends FsContext { - warn(node: NodePath | null, message: string): void; - fail(node: NodePath | null, message: string): void; +export interface TransformContext { + userConfig?: UserConfig; } -export interface FsContext { +export interface FsContext extends TransformContext { fs: FsUtils; } +export interface AstCliContext extends FsContext { + warn(node: NodePath | null, message: string): void; + fail(node: NodePath | null, message: string): void; +} + export type AstTransformResult = { source: string | null; errors: Array; diff --git a/packages/build-config/package.json b/packages/build-config/package.json index 35c310ce..b2576490 100644 --- a/packages/build-config/package.json +++ b/packages/build-config/package.json @@ -32,7 +32,7 @@ "rollup-plugin-preserve-shebang": "1.0.1", "typescript": "5.5.3", "vite": "^5.3.3", - "vite-plugin-dts": "3.9.1", + "vite-plugin-dts": "3.7.3", "vite-plugin-node-polyfills": "0.22.0", "vite-plugin-static-copy": "1.0.6", "vitest": "1.6.0" diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore index 178135c2..20c0b017 100644 --- a/packages/cli/.gitignore +++ b/packages/cli/.gitignore @@ -1 +1,2 @@ +_temp/ /dist/ diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 52a511b3..a5e92fba 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -1,4 +1,4 @@ -import { Enum, match } from '@ag-grid-devtools/utils'; +import { Enum, dynamicRequire, match } from '@ag-grid-devtools/utils'; import { parseArgs as parseMigrateCommandArgs, cli as migrate, @@ -70,6 +70,16 @@ export async function cli(args: Array, cli: CliOptions): Promise { await printUsage(stdout, env); throw null; } + + // Add optional typescript support by loading tsx or ts-node + try { + dynamicRequire.require('tsx/cjs', import.meta); + } catch { + try { + dynamicRequire.require('ts-node/register', import.meta); + } catch {} + } + const task = match(options.command, { Migrate: ({ args }) => migrate(args, cli), }); diff --git a/packages/cli/src/commands/migrate.ts b/packages/cli/src/commands/migrate.ts index 355fe2ce..d96403e9 100644 --- a/packages/cli/src/commands/migrate.ts +++ b/packages/cli/src/commands/migrate.ts @@ -1,15 +1,21 @@ import codemods from '@ag-grid-devtools/codemods'; -import { composeCodemods, createCodemodTask } from '@ag-grid-devtools/codemod-task-utils'; +import { + composeCodemods, + createCodemodTask, + loadUserConfig, +} from '@ag-grid-devtools/codemod-task-utils'; import { Codemod, CodemodTaskInput, CodemodTaskWorkerResult, + TaskRunnerEnvironment, + UserConfig, type VersionManifest, } from '@ag-grid-devtools/types'; import { createFsHelpers } from '@ag-grid-devtools/worker-utils'; import { nonNull } from '@ag-grid-devtools/utils'; import { createTwoFilesPatch } from 'diff'; -import { join } from 'node:path'; +import { join, resolve as pathResolve } from 'node:path'; import { cpus } from 'node:os'; import semver from 'semver'; @@ -22,7 +28,7 @@ import { basename, extname, resolve, relative } from '../utils/path'; import { getCliCommand, getCliPackageVersion } from '../utils/pkg'; import { green, indentErrorMessage, log } from '../utils/stdio'; import { Worker, WorkerTaskQueue, type WorkerOptions } from '../utils/worker'; -import { requireDynamicModule, resolveDynamicModule } from '../utils/module'; +import { dynamicRequire } from '@ag-grid-devtools/utils'; const { versions } = codemods; @@ -70,6 +76,10 @@ export interface MigrateCommandArgs { * List of input files to operate on (defaults to all source files in the current working directory) */ input: Array; + /** + * The path of the user config to load + */ + userConfigPath?: string; } function usage(env: CliEnv): string { @@ -90,6 +100,7 @@ Options: --allow-dirty, -d Allow operating on repositories with uncommitted changes in the working tree --num-threads Number of worker threads to spawn (defaults to the number of system cores) --dry-run Show a diff output of the changes that would be made + --config= Loads a configuration file to customize the codemod behavior (advanced). Additional arguments: [...] List of input files to operate on (defaults to all source files in the current working directory) @@ -111,6 +122,7 @@ export function parseArgs(args: string[], env: CliEnv): MigrateCommandArgs { verbose: false, help: false, input: [], + userConfigPath: undefined, }; let arg; while ((arg = args.shift())) { @@ -176,6 +188,22 @@ export function parseArgs(args: string[], env: CliEnv): MigrateCommandArgs { options.numThreads = numThreads; break; } + case '--config': { + let value = args.shift()?.trim(); + if (!value) { + throw new CliArgsError(`Missing value for ${arg}`, usage(env)); + } + if (value.startsWith('require:')) { + value = value.slice('require:'.length); + if (!value) { + throw new CliArgsError(`Missing value for ${arg}`, usage(env)); + } + options.userConfigPath = value; + } else { + options.userConfigPath = pathResolve(env.cwd ?? process.cwd(), value); + } + break; + } case '--dry-run': options.dryRun = true; break; @@ -194,6 +222,7 @@ export function parseArgs(args: string[], env: CliEnv): MigrateCommandArgs { break; } } + if (options.help) return options; if (!options.from) { throw new CliArgsError(`Missing --from migration starting version`, usage(env)); @@ -224,7 +253,17 @@ async function migrate( args: Omit, options: CliOptions, ): Promise> { - const { from, to, allowUntracked, allowDirty, numThreads, dryRun, verbose, input } = args; + const { + from, + to, + allowUntracked, + allowDirty, + numThreads, + dryRun, + verbose, + userConfigPath, + input, + } = args; const { cwd, env, stdio } = options; const { stdout, stderr } = stdio; @@ -348,11 +387,12 @@ async function migrate( // Load the codemod and wrap it in a task helper const codemod = composeCodemods( codemodPaths.map((codemodPath) => - requireDynamicModule(codemodPath, import.meta), + dynamicRequire.requireDefault(codemodPath, import.meta), ), ); return executeCodemodSingleThreaded(codemod, inputFilePaths, { dryRun, + userConfigPath, onStart, onComplete, }); @@ -364,12 +404,32 @@ async function migrate( } // Create a worker pool to run the codemods in parallel - const scriptPath = resolveDynamicModule(WORKER_PATH, import.meta); + let scriptPath: string | URL = dynamicRequire.resolve(WORKER_PATH, import.meta); + + const resolvedCodemodPaths = codemodPaths.map((codemodPath) => + dynamicRequire.resolve(codemodPath, import.meta), + ); + const config: WorkerOptions = { // Pass the list of codemod paths to the worker via workerData - workerData: codemodPaths, + workerData: { + codemodPaths: resolvedCodemodPaths, + userConfigPath, + }, + env: process.env, + argv: [scriptPath], + eval: true, }; - const workers = Array.from({ length: numWorkers }, () => new Worker(scriptPath, config)); + + const workers = Array.from( + { length: numWorkers }, + () => + new Worker( + // Add optional typescript support by loading tsx or ts-node + `try { require("tsx/cjs"); } catch (_) { try { require("ts-node/register"); } catch (__) {} } require(${JSON.stringify(scriptPath)});`, + config, + ), + ); const workerPool = new WorkerTaskQueue(workers); return executeCodemodMultiThreaded(workerPool, inputFilePaths, { dryRun, @@ -514,14 +574,19 @@ function executeCodemodSingleThreaded( codemod: Codemod, inputFilePaths: string[], options: { + userConfigPath: string | undefined; dryRun: boolean; onStart?: (inputFilePath: string) => void; onComplete?: (inputFilePath: string, stats: { runningTime: number }) => void; }, ): Promise { - const { dryRun, onStart, onComplete } = options; - const task = createCodemodTask(codemod); - const runner = { fs: createFsHelpers() }; + const { dryRun, onStart, onComplete, userConfigPath } = options; + const userConfig = loadUserConfig(userConfigPath); + const runner: TaskRunnerEnvironment = { + fs: createFsHelpers(), + userConfig, + }; + const task = createCodemodTask(codemod, userConfig); // Run the codemod for each input file return Promise.all( inputFilePaths.map((inputFilePath) => { diff --git a/packages/cli/src/test/cli.test.ts b/packages/cli/src/test/cli.test.ts new file mode 100644 index 00000000..de94333a --- /dev/null +++ b/packages/cli/src/test/cli.test.ts @@ -0,0 +1,78 @@ +import { beforeAll, expect, describe, test } from 'vitest'; +import { cli } from '../cli'; +import { + TEMP_FOLDER, + loadExpectedSource, + loadTempSource, + patchDynamicRequire, + prepareTestDataFiles, +} from './test-utils'; +import { CliOptions } from '../types/cli'; + +describe('cli e2e', () => { + beforeAll(() => { + patchDynamicRequire(); + }); + + const cliOptions: CliOptions = { + cwd: TEMP_FOLDER, + env: { + cwd: TEMP_FOLDER, + }, + stdio: { + stdin: process.stdin, + stdout: process.stdout, + stderr: process.stderr, + }, + }; + + test('plain cli single threaded', async () => { + await prepareTestDataFiles(); + await cli(['migrate', '--num-threads=0', '--allow-untracked', '--from=30.0.0'], cliOptions); + expect(await loadExpectedSource('plain.js')).toEqual(await loadTempSource('plain.js')); + }, 10000); + + test('plain cli multi-threaded', async () => { + await prepareTestDataFiles(); + await cli(['migrate', '--num-threads=0', '--allow-untracked', '--from=30.0.0'], cliOptions); + expect(await loadExpectedSource('plain.js')).toEqual(await loadTempSource('plain.js')); + }, 10000); + + test('userConfig single-threaded', async () => { + await prepareTestDataFiles(); + + await cli( + [ + 'migrate', + '--num-threads=0', + '--allow-untracked', + '--from=30.0.0', + '--config=../user-config.ts', + ], + cliOptions, + ); + + expect(await loadExpectedSource('custom-imports.js')).toEqual( + await loadTempSource('custom-imports.js'), + ); + }, 10000); + + test('userConfig multi-threaded', async () => { + await prepareTestDataFiles(); + + await cli( + [ + 'migrate', + '--num-threads=2', + '--allow-untracked', + '--from=30.0.0', + '--config=../user-config.ts', + ], + cliOptions, + ); + + expect(await loadExpectedSource('custom-imports.js')).toEqual( + await loadTempSource('custom-imports.js'), + ); + }, 10000); +}); diff --git a/packages/cli/src/test/expected/custom-imports.js b/packages/cli/src/test/expected/custom-imports.js new file mode 100644 index 00000000..85a405f8 --- /dev/null +++ b/packages/cli/src/test/expected/custom-imports.js @@ -0,0 +1,16 @@ +import { myCreateGrid } from '@hello/world'; +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = myCreateGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/expected/plain.js b/packages/cli/src/test/expected/plain.js new file mode 100644 index 00000000..2fa33ba4 --- /dev/null +++ b/packages/cli/src/test/expected/plain.js @@ -0,0 +1,8 @@ +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/input-files/README.md b/packages/cli/src/test/input-files/README.md new file mode 100644 index 00000000..d9af46e7 --- /dev/null +++ b/packages/cli/src/test/input-files/README.md @@ -0,0 +1 @@ +The files in this folder will be copied inside \_files folder to be migrated diff --git a/packages/cli/src/test/input-files/custom-imports.js b/packages/cli/src/test/input-files/custom-imports.js new file mode 100644 index 00000000..164f849d --- /dev/null +++ b/packages/cli/src/test/input-files/custom-imports.js @@ -0,0 +1,16 @@ +import { MyGrid as CustomGrid } from '@hello/world'; +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new CustomGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/input-files/plain.js b/packages/cli/src/test/input-files/plain.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/input-files/plain.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/test-utils.ts b/packages/cli/src/test/test-utils.ts new file mode 100644 index 00000000..7286dc2d --- /dev/null +++ b/packages/cli/src/test/test-utils.ts @@ -0,0 +1,61 @@ +import { readFile, cp, mkdir, rmdir } from 'fs/promises'; +import { fileURLToPath } from 'url'; +import path from 'path'; +import { dynamicRequire } from '@ag-grid-devtools/utils'; +import { cli } from '../cli'; +import prettier from 'prettier'; + +export const ROOT_FOLDER = path.dirname(fileURLToPath(import.meta.url)); +export const TEMP_FOLDER = path.resolve(ROOT_FOLDER, '_temp'); +export const INPUT_FOLDER = path.resolve(ROOT_FOLDER, 'input-files'); +export const EXPECTED_FOLDER = path.resolve(ROOT_FOLDER, 'expected'); + +export async function loadExpectedSource(name: string) { + const filepath = path.resolve(EXPECTED_FOLDER, name); + return prettier.format(await readFile(filepath, 'utf-8'), { filepath }); +} + +export async function loadTempSource(name: string) { + const filepath = path.resolve(TEMP_FOLDER, name); + return prettier.format(await readFile(filepath, 'utf-8'), { filepath }); +} + +export async function prepareTestDataFiles() { + try { + await rmdir(TEMP_FOLDER, { recursive: true }); + } catch { + // already deleted + } + + await mkdir(TEMP_FOLDER, { recursive: true }); + + await cp(path.resolve(ROOT_FOLDER, INPUT_FOLDER), TEMP_FOLDER, { + recursive: true, + force: true, + filter: (src) => !src.includes('README.md'), + }); +} + +export function patchDynamicRequire() { + /** Fixes the path of an import for typescript, as we are using those with worker threads */ + const fixPath = (p: string): string => { + if (p === '@ag-grid-devtools/codemods/worker') { + return '@ag-grid-devtools/codemods/src/worker.ts'; + } + + if (p.startsWith('@ag-grid-devtools/codemods/version/')) { + p = + '@ag-grid-devtools/codemods/src/versions/' + + p.slice('@ag-grid-devtools/codemods/version/'.length) + + '/codemod.ts'; + } + + return p; + }; + + const oldRequire = dynamicRequire.require; + dynamicRequire.require = (path: string, meta: ImportMeta) => oldRequire(fixPath(path), meta); + + const oldResolve = dynamicRequire.resolve; + dynamicRequire.resolve = (path: string, meta: ImportMeta) => oldResolve(fixPath(path), meta); +} diff --git a/packages/cli/src/test/user-config.ts b/packages/cli/src/test/user-config.ts new file mode 100644 index 00000000..265f814e --- /dev/null +++ b/packages/cli/src/test/user-config.ts @@ -0,0 +1,18 @@ +import { defineUserConfig } from '../user-config'; + +module.exports = defineUserConfig({ + getCreateGridName() { + return 'myCreateGrid'; + }, + + matchGridImport({ importPath: importedModule }) { + return importedModule === '@hello/world'; + }, + + matchGridImportName({ importName: exported, agGridExportName: match }) { + if (match === 'Grid') { + return exported === 'MyGrid'; + } + return exported === match; + }, +}); diff --git a/packages/cli/src/user-config.ts b/packages/cli/src/user-config.ts new file mode 100644 index 00000000..24a896e0 --- /dev/null +++ b/packages/cli/src/user-config.ts @@ -0,0 +1,42 @@ +import type { + UserConfig, + Framework, + ImportType, + MatchGridImportArgs, + MatchGridImportNameArgs, + AgGridExportName, +} from '@ag-grid-devtools/types'; + +export { + UserConfig, + Framework, + ImportType, + MatchGridImportArgs, + MatchGridImportNameArgs, + AgGridExportName, +}; + +/** + * + * Define a user configuration for the AG Grid CLI migrate command. + * + * @example + * + * my-user-config.cjs + * + * ```js + * module.export = defineUserConfig({ + * matchGridImport({ importPath }) { + * return importPath === '@my-org/my-grid'; + * } + * }); + * + * ```sh + * ag-grid-cli migrate --config=./my-user-config.cjs + * ``` + * + * + * @param config - The user configuration to define. + * @returns The user configuration. + */ +export const defineUserConfig = (config: UserConfig): UserConfig => config; diff --git a/packages/cli/src/utils/module.ts b/packages/cli/src/utils/module.ts deleted file mode 100644 index 44eca4b4..00000000 --- a/packages/cli/src/utils/module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createRequire } from 'node:module'; - -export function requireDynamicModule(path: string, meta: ImportMeta): T { - const require = createRequire(meta.url); - return require(path); -} - -export function resolveDynamicModule(path: string, meta: ImportMeta): string { - const require = createRequire(meta.url); - return require.resolve(path); -} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json index 24d9a4ac..9f187d71 100644 --- a/packages/cli/tsconfig.json +++ b/packages/cli/tsconfig.json @@ -1,4 +1,4 @@ { "extends": "../build-config/templates/tsc/tsconfig.json", - "include": ["src", "index.ts", "*.config.mts", "package.json"] + "include": ["src", "index.ts", "user-config.ts", "*.config.mts", "package.json"] } diff --git a/packages/codemod-task-utils/lib.test.ts b/packages/codemod-task-utils/lib.test.ts index 7e314324..3c7ca2f4 100644 --- a/packages/codemod-task-utils/lib.test.ts +++ b/packages/codemod-task-utils/lib.test.ts @@ -7,5 +7,6 @@ test('module exports', () => { composeCodemods: lib.composeCodemods, createCodemodTask: lib.createCodemodTask, initCodemodTaskWorker: lib.initCodemodTaskWorker, + loadUserConfig: lib.loadUserConfig, }); }); diff --git a/packages/codemod-task-utils/package.json b/packages/codemod-task-utils/package.json index 35543365..01575f2a 100644 --- a/packages/codemod-task-utils/package.json +++ b/packages/codemod-task-utils/package.json @@ -42,6 +42,7 @@ }, "dependencies": { "@ag-grid-devtools/types": "workspace:*", + "@ag-grid-devtools/utils": "workspace:*", "@ag-grid-devtools/worker-utils": "workspace:*" }, "devDependencies": { diff --git a/packages/codemod-task-utils/src/task.ts b/packages/codemod-task-utils/src/task.ts index a4f158ac..b50876d0 100644 --- a/packages/codemod-task-utils/src/task.ts +++ b/packages/codemod-task-utils/src/task.ts @@ -1,12 +1,17 @@ import { + UserConfig, type Codemod, type CodemodTask, type CodemodTaskInput, type CodemodTaskResult, type TaskRunnerEnvironment, } from '@ag-grid-devtools/types'; +import { dynamicRequire } from '@ag-grid-devtools/utils'; -export function createCodemodTask(codemod: Codemod): CodemodTask { +export function createCodemodTask( + codemod: Codemod, + userConfig: UserConfig | undefined, +): CodemodTask { return { run(input: CodemodTaskInput, runner: TaskRunnerEnvironment): Promise { const { fs } = runner; @@ -23,7 +28,7 @@ export function createCodemodTask(codemod: Codemod): CodemodTask { source: updated, errors, warnings, - } = codemod({ path: inputFilePath, source }, { fs }); + } = codemod({ path: inputFilePath, source }, { fs, userConfig }); const isUnchanged = updated === source; const result = { source, updated: isUnchanged ? null : updated }; if (dryRun || !updated || isUnchanged) return { result, errors, warnings }; @@ -36,3 +41,7 @@ export function createCodemodTask(codemod: Codemod): CodemodTask { function isFsErrorCode(error: unknown, code: T): error is Error & { code: T } { return error instanceof Error && (error as Error & { code?: string }).code === code; } + +export function loadUserConfig(userConfigPath: string | undefined): UserConfig | undefined { + return userConfigPath ? dynamicRequire.requireDefault(userConfigPath, import.meta) : undefined; +} diff --git a/packages/codemod-task-utils/src/worker.ts b/packages/codemod-task-utils/src/worker.ts index a4d5be36..b1d4791a 100644 --- a/packages/codemod-task-utils/src/worker.ts +++ b/packages/codemod-task-utils/src/worker.ts @@ -1,4 +1,9 @@ -import { type CodemodTask, type CodemodTaskInput, type FsUtils } from '@ag-grid-devtools/types'; +import { + UserConfig, + type CodemodTask, + type CodemodTaskInput, + type FsUtils, +} from '@ag-grid-devtools/types'; import { configureWorkerTask, createFsHelpers, @@ -7,16 +12,17 @@ import { export function initCodemodTaskWorker( task: CodemodTask, - options?: { + options: { fs?: FsUtils; + userConfig: UserConfig | undefined; }, ): void { - const { fs = createFsHelpers() } = options || {}; + const { fs = createFsHelpers(), userConfig } = options; const workerTask = configureWorkerTask(task, { main: parseMainThreadTaskInput, worker: parseWorkerThreadTaskInput, }); - initTaskWorker(workerTask, { fs }); + initTaskWorker(workerTask, { fs, userConfig }); } function parseMainThreadTaskInput(env: Pick): CodemodTaskInput { @@ -34,10 +40,13 @@ function parseMainThreadTaskInput(env: Pick): Co function parseWorkerThreadTaskInput(data: unknown): CodemodTaskInput { if (!data || typeof data !== 'object') throw new Error('Invalid task input'); - const { inputFilePath, dryRun } = data as Record; + let { inputFilePath, userConfigPath, dryRun } = data as Record; if (typeof inputFilePath !== 'string') { throw new Error(`Invalid inputFilePath field value: ${JSON.stringify(inputFilePath)}`); } + if (typeof userConfigPath !== 'string' && userConfigPath !== undefined) { + throw new Error(`Invalid userConfigPath field value: ${JSON.stringify(userConfigPath)}`); + } if (typeof dryRun !== 'boolean') { throw new Error(`Invalid dryRun field value: ${JSON.stringify(dryRun)}`); } diff --git a/packages/codemod-utils/lib.test.ts b/packages/codemod-utils/lib.test.ts index a60e96d7..85588c7c 100644 --- a/packages/codemod-utils/lib.test.ts +++ b/packages/codemod-utils/lib.test.ts @@ -5,6 +5,7 @@ import * as lib from './lib'; test('module exports', () => { expect({ ...lib }).toEqual({ AG_GRID_JS_PACKAGE_NAME_PATTERN: lib.AG_GRID_JS_PACKAGE_NAME_PATTERN, + AG_GRID_JS_PACKAGE_NAME_MATCHER: lib.AG_GRID_JS_PACKAGE_NAME_MATCHER, AG_GRID_JS_UMD_GLOBAL_NAME: lib.AG_GRID_JS_UMD_GLOBAL_NAME, Angular: lib.Angular, AngularComponentTemplateDefinition: lib.AngularComponentTemplateDefinition, diff --git a/packages/codemod-utils/src/agGridHelpers.test.ts b/packages/codemod-utils/src/agGridHelpers.test.ts index 66838287..55dffb5d 100644 --- a/packages/codemod-utils/src/agGridHelpers.test.ts +++ b/packages/codemod-utils/src/agGridHelpers.test.ts @@ -23,6 +23,7 @@ import { } from './agGridHelpers'; import { getAngularTemplateRootElements } from './angularHelpers'; import { AST, VueTemplateNode } from './vueHelpers'; +import { UserConfig } from '@ag-grid-devtools/types'; type Class = Types.Class; type ClassMethod = Types.ClassMethod; @@ -1865,6 +1866,120 @@ describe(getGridApiReferences, () => { }); }); }); + + describe('userConfig', () => { + test('custom createGrid, vanilla js', () => { + const input = ast.module` + import { myCreateGrid } from 'my-custom-import/hello'; + myCreateGrid(document.body, {}); + `; + const program = getModuleRoot(input); + const statements = program.get('body'); + const finalStatement = statements[statements.length - 1]; + const reference = (finalStatement as NodePath).get('expression'); + const gridApis = getGridApiReferences( + reference, + createTransformContext('./app.js', { + fs: memfs, + userConfig: { + getCreateGridName(args) { + return args.importPath === 'my-custom-import/hello' ? 'myCreateGrid' : null; + }, + matchGridImport: (args) => args.importPath === 'my-custom-import/hello', + }, + }), + ); + const actual = + gridApis && + gridApis.map((gridApi) => + GridApiDefinition.Js.is(gridApi) ? generate(gridApi.initializer.node) : null, + ); + const expected = ['myCreateGrid(document.body, {})']; + expect(actual).toEqual(expected); + }); + + test('custom imports specifiers, vanilla js', () => { + const input = ast.module` + import { myCreateGrid } from '@my-custom-import/hello'; + myCreateGrid(document.body, {}); + `; + const program = getModuleRoot(input); + const statements = program.get('body'); + const finalStatement = statements[statements.length - 1]; + const reference = (finalStatement as NodePath).get('expression'); + const gridApis = getGridApiReferences( + reference, + createTransformContext('./app.js', { + fs: memfs, + userConfig: { + matchGridImport: (args) => args.importPath === '@my-custom-import/hello', + matchGridImportName: (args) => args.importName === 'myCreateGrid', + }, + }), + ); + const actual = + gridApis && + gridApis.map((gridApi) => + GridApiDefinition.Js.is(gridApi) ? generate(gridApi.initializer.node) : null, + ); + const expected = ['myCreateGrid(document.body, {})']; + expect(actual).toEqual(expected); + }); + + test('should allow custom imports, react', () => { + const input = ast.module` + import { MyGridReact } from 'my-custom-import/hello'; + import { useRef } from 'react'; + + function MyComponent(props) { + const gridRef = useRef(null); + const resetState = useCallback(() => { + gridRef.current.api.resetColumnState(); + }, []); + return ( + <> + + + + ); + } + `; + const program = getModuleRoot(input); + const { + refs: { gridApi }, + } = matchNode(({ gridApi }) => ast.expression`${gridApi}.resetColumnState()`, { + gridApi: p.expression(), + }).find(program)!; + const gridApis = getGridApiReferences( + gridApi, + createTransformContext('./app.jsx', { + fs: memfs, + userConfig: { + matchGridImport: (input) => input.importPath === 'my-custom-import/hello', + matchGridImportName: (input) => + input.framework === 'react' && input.importName === 'MyGridReact', + }, + }), + ); + const actual = + gridApis && + gridApis.map((gridApi) => + GridApiDefinition.React.is(gridApi) + ? { + element: generate(gridApi.element.node), + refAccessor: formatAccessorPath(gridApi.refAccessor), + } + : null, + ); + const expected = [ + { + element: '', + refAccessor: 'useRef(null).current.api', + }, + ]; + expect(actual).toEqual(expected); + }); + }); }); function formatAccessorPath(accessor: AccessorPath): string { @@ -1977,13 +2092,15 @@ function createTransformContext( filename: string, options: { fs: typeof memfs; + userConfig?: UserConfig; }, ): AstTransformContext { - const { fs } = options; + const { fs, userConfig } = options; return { filename, opts: { fs: createMockFsHelpers(fs), + userConfig, }, }; } diff --git a/packages/codemod-utils/src/agGridHelpers.ts b/packages/codemod-utils/src/agGridHelpers.ts index 0c05a1e2..2d2c0b6a 100644 --- a/packages/codemod-utils/src/agGridHelpers.ts +++ b/packages/codemod-utils/src/agGridHelpers.ts @@ -1,6 +1,8 @@ import { AccessorKey, AccessorReference, + ImportedModuleMatcher, + TransformContext, areAccessorKeysEqual, getAccessorExpressionPaths, getNamedModuleImportExpression, @@ -56,6 +58,7 @@ import { VueTemplateFormatter, } from './vueHelpers'; import { VueComponentCliContext } from './transform'; +import { AgGridExportName } from '@ag-grid-devtools/types'; type AssignmentExpression = Types.AssignmentExpression; type CallExpression = Types.CallExpression; @@ -84,27 +87,45 @@ type VDirectiveKey = AST.VDirectiveKey; type VElement = AST.VElement; type VIdentifier = AST.VIdentifier; +export const AG_GRID_JS_UMD_GLOBAL_NAME = 'agGrid'; + export const AG_GRID_JS_PACKAGE_NAME_PATTERN = /^(?:ag-grid-(?:community|enterprise)|@ag-grid-community\/core)$/; -export const AG_GRID_JS_UMD_GLOBAL_NAME = 'agGrid'; -const AG_GRID_JS_CONSTRUCTOR_EXPORT_NAME = 'createGrid'; -const AG_GRID_REACT_PACKAGE_NAME_PATTERN = /^(?:ag-grid-react|@ag-grid-community\/react)$/; -const AG_GRID_REACT_GRID_COMPONENT_NAME = 'AgGridReact'; +export const AG_GRID_JS_PACKAGE_NAME_MATCHER: ImportedModuleMatcher = { + importModulePattern: AG_GRID_JS_PACKAGE_NAME_PATTERN, + importUmdPattern: AG_GRID_JS_UMD_GLOBAL_NAME, + framework: 'vanilla', +}; + +const AG_GRID_REACT_PACKAGE_NAME_MATCHER: ImportedModuleMatcher = { + importModulePattern: /^(?:ag-grid-react|@ag-grid-community\/react)$/, + importUmdPattern: null, + framework: 'react', +}; +const AG_GRID_REACT_GRID_COMPONENT_NAME = AgGridExportName.AgGridReact; const AG_GRID_REACT_GRID_OPTIONS_PROP_NAME = 'gridOptions'; const AG_GRID_REACT_API_ACCESSOR_NAME = 'api'; const AG_GRID_REACT_COLUMN_API_ACCESSOR_NAME = 'columnApi'; -const AG_GRID_ANGULAR_PACKAGE_NAME_PATTERN = /^(?:ag-grid-angular|@ag-grid-community\/angular)$/; -const AG_GRID_ANGULAR_GRID_COMPONENT_NAME = 'AgGridAngular'; +const AG_GRID_ANGULAR_PACKAGE_NAME_MATCHER: ImportedModuleMatcher = { + importModulePattern: /^(?:ag-grid-angular|@ag-grid-community\/angular)$/, + importUmdPattern: null, + framework: 'angular', +}; +const AG_GRID_ANGULAR_GRID_COMPONENT_NAME = AgGridExportName.AgGridAngular; const AG_GRID_ANGULAR_ELEMENT_NAME = 'ag-grid-angular'; const AG_GRID_ANGULAR_GRID_OPTIONS_ATTRIBUTE_NAME = 'gridOptions'; const AG_GRID_ANGULAR_API_ACCESSOR_NAME = 'api'; const AG_GRID_ANGULAR_COLUMN_API_ACCESSOR_NAME = 'columnApi'; // FIXME: determine correct package names for vue2 vs vue3 -const AG_GRID_VUE_PACKAGE_NAME_PATTERN = /^(?:ag-grid-vue3?|@ag-grid-community\/vue)$/; -const AG_GRID_VUE_GRID_COMPONENT_NAME = 'AgGridVue'; +const AG_GRID_VUE_PACKAGE_NAME_MATCHER: ImportedModuleMatcher = { + importModulePattern: /^(?:ag-grid-vue3?|@ag-grid-community\/vue)$/, + importUmdPattern: null, + framework: 'vue', +}; +const AG_GRID_VUE_GRID_COMPONENT_NAME = AgGridExportName.AgGridVue; const AG_GRID_VUE_GRID_OPTIONS_ATTRIBUTE_NAME = 'gridOptions'; const AG_GRID_VUE_API_ACCESSOR_NAME = 'api'; const AG_GRID_VUE_COLUMN_API_ACCESSOR_NAME = 'columnApi'; @@ -257,7 +278,7 @@ export function visitGridOptionsProperties< S extends AstTransformContext, >(visitor: ObjectPropertyVisitor): AstNodeVisitor { function CallExpression(path: NodePath, context: S) { - const gridInitializer = matchJsGridApiInitializer(path); + const gridInitializer = matchJsGridApiInitializer(path, context); if (!gridInitializer) return; const { options } = gridInitializer; if (!options) return; @@ -272,7 +293,7 @@ export function visitGridOptionsProperties< OptionalCallExpression: CallExpression, // Traverse React grid elements JSXElement(path, context) { - if (!isAgGridJsxElement(path)) return; + if (!isAgGridJsxElement(path, context)) return; path .get('openingElement') .get('attributes') @@ -352,7 +373,7 @@ export function visitGridOptionsProperties< }, // Traverse Vue grid components ObjectExpression(path, context) { - const gridElementNames = matchAgGridVueComponentElementNames(path); + const gridElementNames = matchAgGridVueComponentElementNames(path, context); if (!gridElementNames) return; const template = getVueComponentTemplate(path, context); if (!template) return; @@ -593,8 +614,8 @@ export function getGridApiReferences( // bailing out if the accessor is invalid const accessorPaths = getAccessorExpressionPaths(accessor); if (!accessorPaths) return null; - const jsGridApiReferences = getJsGridApiReferences(accessorPaths); - const reactGridApiReferences = getReactGridApiReferences(accessorPaths); + const jsGridApiReferences = getJsGridApiReferences(accessorPaths, context); + const reactGridApiReferences = getReactGridApiReferences(accessorPaths, context); const angularGridApiReferences = getAngularGridApiReferences(accessorPaths, context); const vueGridApiReferences = getVueGridApiReferences(accessorPaths, context); if ( @@ -629,7 +650,7 @@ export function getColumnApiReferences( // bailing out if the accessor is invalid const accessorPaths = getAccessorExpressionPaths(accessor); if (!accessorPaths) return null; - const reactColumnApiReferences = getReactColumnApiReferences(accessorPaths); + const reactColumnApiReferences = getReactColumnApiReferences(accessorPaths, context); const angularColumnApiReferences = getAngularColumnApiReferences(accessorPaths, context); const vueColumnColumnReferences = getVueColumnApiReferences(accessorPaths, context); if (!reactColumnApiReferences && !angularColumnApiReferences && !vueColumnColumnReferences) { @@ -644,6 +665,7 @@ export function getColumnApiReferences( export function getJsGridApiReferences( accessorPaths: Array, + context: AstTransformContext, ): Array | null { // Match only accessor paths that are direct references to a grid initializer expression const targets = accessorPaths @@ -651,7 +673,7 @@ export function getJsGridApiReferences( // Ignore accessor paths that drill down within the root expression value if (path.length > 0) return null; // Attempt to parse the expression value as a grid API initializer - return matchJsGridApiInitializer(root.target); + return matchJsGridApiInitializer(root.target, context); }) .filter(nonNull); if (targets.length === 0) return null; @@ -660,16 +682,19 @@ export function getJsGridApiReferences( return deduplicatedTargets; } -function matchJsGridApiInitializer(path: NodePath): JsGridApiDefinition | null { +function matchJsGridApiInitializer( + path: NodePath, + context: AstTransformContext, +): JsGridApiDefinition | null { if (!path.isCallExpression() && !path.isOptionalCallExpression()) return null; const typedPath = path as NodePath; const callee = typedPath.get('callee'); if (!callee.isExpression()) return null; const gridApiImport = getNamedModuleImportExpression( callee, - AG_GRID_JS_PACKAGE_NAME_PATTERN, - AG_GRID_JS_UMD_GLOBAL_NAME, - AG_GRID_JS_CONSTRUCTOR_EXPORT_NAME, + AG_GRID_JS_PACKAGE_NAME_MATCHER, + AgGridExportName.createGrid, + context, ); if (!gridApiImport) return null; const { element, options } = parseJsGridApiInitializerArguments(typedPath.get('arguments')); @@ -702,19 +727,26 @@ function parseJsGridApiInitializerArguments( export function getReactGridApiReferences( accessorPaths: Array, + context: AstTransformContext, ): Array | null { - return getReactPublicApiReferences(AG_GRID_REACT_API_ACCESSOR_NAME, accessorPaths); + return getReactPublicApiReferences(AG_GRID_REACT_API_ACCESSOR_NAME, accessorPaths, context); } export function getReactColumnApiReferences( accessorPaths: Array, + context: AstTransformContext, ): Array | null { - return getReactPublicApiReferences(AG_GRID_REACT_COLUMN_API_ACCESSOR_NAME, accessorPaths); + return getReactPublicApiReferences( + AG_GRID_REACT_COLUMN_API_ACCESSOR_NAME, + accessorPaths, + context, + ); } function getReactPublicApiReferences( apiName: string, accessorPaths: Array, + context: AstTransformContext, ): Array | null { // Get a list of JSX grid elements referred to via the accessor const results = accessorPaths @@ -735,7 +767,7 @@ function getReactPublicApiReferences( .map((reference) => { const { binding } = reference; const element = getReactBoundElementRef(binding); - if (!element || !isAgGridJsxElement(element)) return null; + if (!element || !isAgGridJsxElement(element, context)) return null; return element; }) .filter(nonNull); @@ -750,15 +782,19 @@ function getReactPublicApiReferences( return results.length === 0 ? null : results; } -function isAgGridJsxElement(element: NodePath): boolean { +function isAgGridJsxElement( + element: NodePath, + context: AstTransformContext, +): boolean { const elementName = element.get('openingElement').get('name'); // FIXME: consider supporting namespaced JSX element names if (!elementName.isJSXIdentifier()) return false; + const importDeclaration = getNamedModuleImportExpression( elementName, - AG_GRID_REACT_PACKAGE_NAME_PATTERN, - null, + AG_GRID_REACT_PACKAGE_NAME_MATCHER, AG_GRID_REACT_GRID_COMPONENT_NAME, + context, ); if (!importDeclaration) return false; return true; @@ -844,9 +880,9 @@ function getAngularPublicApiReferences( if (!AccessorReference.Property.is(reference)) return null; const { target: component, accessor: property } = reference; if (!component.isClass() || !property.isClassProperty()) return null; - const namedViewChild = getAngularViewChildMetadata(property); + const namedViewChild = getAngularViewChildMetadata(property, context); if (!namedViewChild) return null; - if (isAgGridAngularComponentImportReference(namedViewChild)) { + if (isAgGridAngularComponentImportReference(namedViewChild, context)) { return { component, elementId: null, accessor }; } if (namedViewChild.isStringLiteral()) { @@ -995,12 +1031,15 @@ function getAngularPublicApiReferences( return gridReferences.length === 0 ? null : gridReferences; } -function isAgGridAngularComponentImportReference(reference: NodePath): boolean { +function isAgGridAngularComponentImportReference( + reference: NodePath, + context: AstTransformContext, +): boolean { const importDeclaration = getNamedModuleImportExpression( reference, - AG_GRID_ANGULAR_PACKAGE_NAME_PATTERN, - null, + AG_GRID_ANGULAR_PACKAGE_NAME_MATCHER, AG_GRID_ANGULAR_GRID_COMPONENT_NAME, + context, ); if (!importDeclaration) return false; return true; @@ -1092,7 +1131,7 @@ function getVuePublicApiReferences( return null; } const gridComponentElementNames = Array.from(templateComponentDeclarations) - .filter(([, value]) => isAgGridVueComponentImportReference(value)) + .filter(([, value]) => isAgGridVueComponentImportReference(value, context)) .map(([elementName]) => elementName); if (gridComponentElementNames.length === 0) return null; const template = getVueComponentTemplate(component, context); @@ -1193,11 +1232,12 @@ function updateVueComponentTemplate( function matchAgGridVueComponentElementNames( component: NodePath, + context: AstTransformContext, ): Array | null { const componentDeclarations = getVueComponentComponentDeclarations(component); if (!componentDeclarations) return null; const agGridElementNames = Array.from(componentDeclarations.entries()) - .filter(([, reference]) => isAgGridVueComponentImportReference(reference)) + .filter(([, reference]) => isAgGridVueComponentImportReference(reference, context)) .map(([elementName]) => elementName); return agGridElementNames; } @@ -1239,12 +1279,15 @@ function isVueElementBoundAttribute( return true; } -function isAgGridVueComponentImportReference(reference: NodePath): unknown { +function isAgGridVueComponentImportReference( + reference: NodePath, + context: AstTransformContext, +): boolean { const importDeclaration = getNamedModuleImportExpression( reference, - AG_GRID_VUE_PACKAGE_NAME_PATTERN, - null, + AG_GRID_VUE_PACKAGE_NAME_MATCHER, AG_GRID_VUE_GRID_COMPONENT_NAME, + context, ); if (!importDeclaration) return false; return true; diff --git a/packages/codemod-utils/src/angularHelpers.ts b/packages/codemod-utils/src/angularHelpers.ts index d51e6791..649d01ed 100644 --- a/packages/codemod-utils/src/angularHelpers.ts +++ b/packages/codemod-utils/src/angularHelpers.ts @@ -7,6 +7,8 @@ import { type FileMetadata, type NodePath, type Types, + type TransformContext, + type ImportedModuleMatcher, } from '@ag-grid-devtools/ast'; import { Enum, match } from '@ag-grid-devtools/utils'; import { @@ -29,7 +31,6 @@ import { getTemplateNodeChild, mergeSourceChunks, printTemplate, - SourceChunk, type TemplateEngine, type TemplateFormatter, type TemplateMutation, @@ -77,6 +78,14 @@ type Property = Types.Property; type TemplateLiteral = Types.TemplateLiteral; const ANGULAR_PACKAGE_NAME = '@angular/core'; + +const ANGULAR_PACKAGE_NAME_MATCHER: ImportedModuleMatcher = { + importModulePattern: ANGULAR_PACKAGE_NAME, + importUmdPattern: null, + framework: 'angular', + skipUserConfig: true, +}; + const ANGULAR_COMPONENT_DECORATOR_IMPORT_NAME = 'Component'; const ANGULAR_COMPONENT_METADATA_TEMPLATE_FIELD_NAME = 'template'; const ANGULAR_COMPONENT_METADATA_TEMPLATE_URL_FIELD_NAME = 'templateUrl'; @@ -113,7 +122,7 @@ export function parseAngularComponentTemplate( template: AngularTemplateNode; } | null { const { filename: filePath = './component.html' } = context; - const componentMetadata = getAngularComponentMetadata(component); + const componentMetadata = getAngularComponentMetadata(component, context); if (!componentMetadata) return null; const templateDefinition = getAngularComponentTemplateDefinition(componentMetadata, context); if (!templateDefinition) return null; @@ -732,11 +741,12 @@ function getAngularComponentMetadataNamedFieldValue( export function getAngularComponentMetadata( component: NodePath, + context: AstTransformContext, ): NodePath | null { const decorators = component.get('decorators'); if (!decorators || !Array.isArray(decorators)) return null; const componentDecorators = decorators - .map((decorator) => getAngularComponentDecoratorOptions(decorator)) + .map((decorator) => getAngularComponentDecoratorOptions(decorator, context)) .filter(Boolean); if (componentDecorators.length === 0) return null; const [componentDecorator] = componentDecorators; @@ -745,6 +755,7 @@ export function getAngularComponentMetadata( export function getAngularComponentDecoratorOptions( decorator: NodePath, + context: AstTransformContext, ): NodePath | null { const expression = decorator.get('expression'); if (!expression.isCallExpression()) return null; @@ -752,9 +763,9 @@ export function getAngularComponentDecoratorOptions( if (!callee.isExpression()) return null; const componentDecoratorImport = getNamedModuleImportExpression( callee, - ANGULAR_PACKAGE_NAME, - null, + ANGULAR_PACKAGE_NAME_MATCHER, ANGULAR_COMPONENT_DECORATOR_IMPORT_NAME, + context, ); if (!componentDecoratorImport) return null; const decoratorArguments = expression.get('arguments'); @@ -766,11 +777,12 @@ export function getAngularComponentDecoratorOptions( export function getAngularViewChildMetadata( property: NodePath, + context: AstTransformContext, ): NodePath | null { const decorators = property.get('decorators'); if (!decorators || !Array.isArray(decorators)) return null; const viewChildDecorators = decorators - .map((decorator) => getAngularViewChildDecoratorOptions(decorator)) + .map((decorator) => getAngularViewChildDecoratorOptions(decorator, context)) .filter(Boolean); if (viewChildDecorators.length === 0) return null; const [componentDecorator] = viewChildDecorators; @@ -779,6 +791,7 @@ export function getAngularViewChildMetadata( function getAngularViewChildDecoratorOptions( decorator: NodePath, + context: AstTransformContext, ): NodePath | null { const expression = decorator.get('expression'); if (!expression.isCallExpression()) return null; @@ -786,9 +799,9 @@ function getAngularViewChildDecoratorOptions( if (!callee.isExpression()) return null; const viewChildDecoratorImport = getNamedModuleImportExpression( callee, - ANGULAR_PACKAGE_NAME, - null, + ANGULAR_PACKAGE_NAME_MATCHER, ANGULAR_VIEW_CHILD_DECORATOR_IMPORT_NAME, + context, ); if (!viewChildDecoratorImport) return null; const viewChildArguments = expression.get('arguments'); diff --git a/packages/codemod-utils/src/transform/js.ts b/packages/codemod-utils/src/transform/js.ts index 7fe78481..9eb33e63 100644 --- a/packages/codemod-utils/src/transform/js.ts +++ b/packages/codemod-utils/src/transform/js.ts @@ -59,7 +59,7 @@ function transformJsFile( BabelTransformJsxOptions & Required>, ): AstTransformResult { - const { filename, fs } = options; + const { filename, fs, userConfig } = options; // Transform the AST const uniqueErrors = new Map(); const transformContext: AstTransformContext = { @@ -74,6 +74,7 @@ function transformJsFile( uniqueErrors.set(error.message, { error, fatal: true }); }, fs, + userConfig, }, }; const plugins = transforms.map((plugin): AstTransformWithOptions => { diff --git a/packages/codemod-utils/src/transform/vue.ts b/packages/codemod-utils/src/transform/vue.ts index 2c5ffb2c..658672cd 100644 --- a/packages/codemod-utils/src/transform/vue.ts +++ b/packages/codemod-utils/src/transform/vue.ts @@ -1,4 +1,5 @@ import { + TransformContext, type AstCliContext, type AstTransform, type AstTransformResult, @@ -19,7 +20,7 @@ import { printTemplate } from '../templateHelpers'; type VElement = AST.VElement; -export interface VueComponentCliContext { +export interface VueComponentCliContext extends TransformContext { vue?: { template: VueTemplateNode | undefined; }; @@ -55,6 +56,7 @@ export function transformVueSfcFile( }; // Expose the template to the transforms via plugin context const vueTransformOptions: VueComponentCliContext = { + userConfig: options.userConfig, vue: { template, }, diff --git a/packages/codemod-utils/src/types/transform.ts b/packages/codemod-utils/src/types/transform.ts index fd55ee3b..9cae5632 100644 --- a/packages/codemod-utils/src/types/transform.ts +++ b/packages/codemod-utils/src/types/transform.ts @@ -1,8 +1,9 @@ import { type FileMetadata } from '@ag-grid-devtools/ast'; -import { type FsUtils } from '@ag-grid-devtools/types'; +import { type UserConfig, type FsUtils } from '@ag-grid-devtools/types'; export type AstTransformOptions = FileMetadata & AstTransformCliOptions; export interface AstTransformCliOptions { fs: FsUtils; + userConfig?: UserConfig; } diff --git a/packages/codemods/src/test/runners/codemod.ts b/packages/codemods/src/test/runners/codemod.ts index 36524807..bcbe85fb 100644 --- a/packages/codemods/src/test/runners/codemod.ts +++ b/packages/codemods/src/test/runners/codemod.ts @@ -4,22 +4,23 @@ import { memfs, type ExampleVitestHelpers, } from '@ag-grid-devtools/test-utils'; -import { type Codemod } from '@ag-grid-devtools/types'; +import { UserConfig, type Codemod } from '@ag-grid-devtools/types'; export function loadCodemodExampleScenarios( scenariosPath: string, options: { codemod: Codemod; vitest: ExampleVitestHelpers; + userConfig?: UserConfig; }, ): void { - const { codemod, vitest } = options; + const { codemod, vitest, userConfig } = options; return loadAstTransformExampleScenarios(scenariosPath, { vitest, runner: (input) => { const { source, errors, warnings } = codemod( { path: input.path, source: input.source }, - { fs: createMockFsHelpers(memfs) }, + { fs: createMockFsHelpers(memfs), userConfig }, ); return { source, errors, warnings }; }, diff --git a/packages/codemods/src/test/runners/transform.ts b/packages/codemods/src/test/runners/transform.ts index 5e2274fe..9a5ef4cd 100644 --- a/packages/codemods/src/test/runners/transform.ts +++ b/packages/codemods/src/test/runners/transform.ts @@ -10,21 +10,24 @@ import { memfs, type ExampleVitestHelpers, } from '@ag-grid-devtools/test-utils'; +import { UserConfig } from '@ag-grid-devtools/types'; export function loadTransformScenarios( scenariosPath: string, options: { transforms: Array | AstTransformWithOptions>; vitest: ExampleVitestHelpers; + userConfig?: UserConfig; }, ): void { - const { transforms, vitest } = options; + const { transforms, vitest, userConfig } = options; return loadAstTransformExampleScenarios(scenariosPath, { vitest, runner: (input) => { const { source, errors, warnings } = transformFileAst(input.source, transforms, { filename: input.path, fs: createMockFsHelpers(memfs), + userConfig, }); return { source, errors, warnings }; }, diff --git a/packages/codemods/src/transforms/migrate-legacy-js-grid-constructor-v31/migrate-legacy-js-grid-constructor.ts b/packages/codemods/src/transforms/migrate-legacy-js-grid-constructor-v31/migrate-legacy-js-grid-constructor.ts index a1729ab5..a472aa5a 100644 --- a/packages/codemods/src/transforms/migrate-legacy-js-grid-constructor-v31/migrate-legacy-js-grid-constructor.ts +++ b/packages/codemods/src/transforms/migrate-legacy-js-grid-constructor-v31/migrate-legacy-js-grid-constructor.ts @@ -16,10 +16,8 @@ import { getNamedObjectLiteralStaticProperty, NodePath, } from '@ag-grid-devtools/ast'; -import { - AG_GRID_JS_PACKAGE_NAME_PATTERN, - AG_GRID_JS_UMD_GLOBAL_NAME, -} from '@ag-grid-devtools/codemod-utils'; +import { AG_GRID_JS_PACKAGE_NAME_MATCHER } from '@ag-grid-devtools/codemod-utils'; +import { AgGridExportName } from '@ag-grid-devtools/types'; import { match, nonNull } from '@ag-grid-devtools/utils'; type Expression = Types.Expression; @@ -27,10 +25,9 @@ type Identifier = Types.Identifier; type ObjectPattern = Types.ObjectPattern; type ObjectProperty = Types.ObjectProperty; -const LEGACY_GRID_API_EXPORT_NAME = 'Grid'; +const LEGACY_GRID_API_EXPORT_NAME = AgGridExportName.Grid; const GRID_API_ACCESSOR_NAME = 'api'; const COLUMN_API_ACCESSOR_NAME = 'columnApi'; -const UPDATED_CONSTRUCTOR_EXPORT_NAME = 'createGrid'; // Used to denote that this plugin has modified the module source // (this allows us to skip unused import cleanup if there are no changes to the current file) @@ -81,32 +78,38 @@ const transform: AstTransform = function migrateLegacyJsGridConst // Determine whether the constructor refers to the legacy Grid class, and bail out if not const legacyGridApiImport = getNamedModuleImportExpression( constructorClass, - AG_GRID_JS_PACKAGE_NAME_PATTERN, - AG_GRID_JS_UMD_GLOBAL_NAME, + AG_GRID_JS_PACKAGE_NAME_MATCHER, LEGACY_GRID_API_EXPORT_NAME, + state, ); if (!legacyGridApiImport) return; + const fromUserConfig = legacyGridApiImport.importMatcherResult.fromUserConfig; + const updatedConstructorExportName = + (fromUserConfig && state.opts.userConfig?.getCreateGridName?.(fromUserConfig)) || + AgGridExportName.createGrid; + // Rewrite the legacy Grid import to the new-style createGrid import - const updatedConstructorClass = match(legacyGridApiImport, { + const updatedConstructorClass = match(legacyGridApiImport.binding, { Module: ({ declaration, accessor }) => match(accessor, { Named: ({}) => { const existingConstructorImport = findNamedModuleImport( declaration.node, - UPDATED_CONSTRUCTOR_EXPORT_NAME, + updatedConstructorExportName, + state, ); if (existingConstructorImport) return existingConstructorImport.local; return insertNamedModuleImport( declaration, t.importSpecifier( - generateUniqueScopeBinding(path.scope, UPDATED_CONSTRUCTOR_EXPORT_NAME), - t.identifier(UPDATED_CONSTRUCTOR_EXPORT_NAME), + generateUniqueScopeBinding(path.scope, updatedConstructorExportName), + t.identifier(updatedConstructorExportName), ), ); }, Namespaced: ({ accessor, local }) => { - const exportedName = t.identifier(UPDATED_CONSTRUCTOR_EXPORT_NAME); + const exportedName = t.identifier(updatedConstructorExportName); const key = exportedName; const computed = false; const [updatedAccessor] = accessor.replaceWith( @@ -121,12 +124,12 @@ const transform: AstTransform = function migrateLegacyJsGridConst const existingConstructorImportReference = findNamedDestructuredPropertyLocalIdentifier( accessors, - UPDATED_CONSTRUCTOR_EXPORT_NAME, + updatedConstructorExportName, ); if (existingConstructorImportReference) { return existingConstructorImportReference.node; } - const exportedName = t.identifier(UPDATED_CONSTRUCTOR_EXPORT_NAME); + const exportedName = t.identifier(updatedConstructorExportName); const localIdentifier = generateUniqueScopeBinding(path.scope, exportedName.name); const key = exportedName; const value = localIdentifier; @@ -139,7 +142,7 @@ const transform: AstTransform = function migrateLegacyJsGridConst return localIdentifier; }, Namespaced: ({ accessor, local }) => { - const exportedName = t.identifier(UPDATED_CONSTRUCTOR_EXPORT_NAME); + const exportedName = t.identifier(updatedConstructorExportName); const key = exportedName; const computed = false; const [updatedAccessor] = accessor.replaceWith( @@ -154,12 +157,12 @@ const transform: AstTransform = function migrateLegacyJsGridConst const existingConstructorImportReference = findNamedDestructuredPropertyLocalIdentifier( accessors, - UPDATED_CONSTRUCTOR_EXPORT_NAME, + updatedConstructorExportName, ); if (existingConstructorImportReference) { return existingConstructorImportReference.node; } - const exportedName = t.identifier(UPDATED_CONSTRUCTOR_EXPORT_NAME); + const exportedName = t.identifier(updatedConstructorExportName); const localIdentifier = generateUniqueScopeBinding(path.scope, exportedName.name); const key = exportedName; const value = localIdentifier; @@ -172,7 +175,7 @@ const transform: AstTransform = function migrateLegacyJsGridConst return localIdentifier; }, Namespaced: ({ accessor, local }) => { - const exportedName = t.identifier(UPDATED_CONSTRUCTOR_EXPORT_NAME); + const exportedName = t.identifier(updatedConstructorExportName); const key = exportedName; const computed = false; const [updatedAccessor] = accessor.replaceWith( @@ -266,8 +269,9 @@ const transform: AstTransform = function migrateLegacyJsGridConst !matchModuleImportName( target.parentPath.node, target.node, - AG_GRID_JS_PACKAGE_NAME_PATTERN, + AG_GRID_JS_PACKAGE_NAME_MATCHER, LEGACY_GRID_API_EXPORT_NAME, + state, ) ) return; diff --git a/packages/codemods/src/versions/31.0.0/codemod.ts b/packages/codemods/src/versions/31.0.0/codemod.ts index 3831b88f..b723a6a5 100644 --- a/packages/codemods/src/versions/31.0.0/codemod.ts +++ b/packages/codemods/src/versions/31.0.0/codemod.ts @@ -13,10 +13,11 @@ const codemod: Codemod = function codemodV31_0_0( options: CodemodOptions, ): CodemodResult { const { path, source } = file; - const { fs } = options; + const { fs, userConfig } = options; return transformFileAst(source, transforms, { filename: path, fs, + userConfig, }); }; diff --git a/packages/codemods/src/versions/31.1.0/codemod.ts b/packages/codemods/src/versions/31.1.0/codemod.ts index ebe59c43..983ac05d 100644 --- a/packages/codemods/src/versions/31.1.0/codemod.ts +++ b/packages/codemods/src/versions/31.1.0/codemod.ts @@ -13,10 +13,11 @@ const codemod: Codemod = function codemodV31_1_0( options: CodemodOptions, ): CodemodResult { const { path, source } = file; - const { fs } = options; + const { fs, userConfig } = options; return transformFileAst(source, transforms, { filename: path, fs, + userConfig, }); }; diff --git a/packages/codemods/src/versions/31.2.0/codemod.ts b/packages/codemods/src/versions/31.2.0/codemod.ts index c825f7c4..cacd3f34 100644 --- a/packages/codemods/src/versions/31.2.0/codemod.ts +++ b/packages/codemods/src/versions/31.2.0/codemod.ts @@ -13,10 +13,11 @@ const codemod: Codemod = function codemodV31_2_0( options: CodemodOptions, ): CodemodResult { const { path, source } = file; - const { fs } = options; + const { fs, userConfig } = options; return transformFileAst(source, transforms, { filename: path, fs, + userConfig, }); }; diff --git a/packages/codemods/src/versions/31.3.0/codemod.ts b/packages/codemods/src/versions/31.3.0/codemod.ts index 3b3a0991..887e6185 100644 --- a/packages/codemods/src/versions/31.3.0/codemod.ts +++ b/packages/codemods/src/versions/31.3.0/codemod.ts @@ -13,10 +13,11 @@ const codemod: Codemod = function codemodV31_3_0( options: CodemodOptions, ): CodemodResult { const { path, source } = file; - const { fs } = options; + const { fs, userConfig } = options; return transformFileAst(source, transforms, { filename: path, fs, + userConfig, }); }; diff --git a/packages/codemods/src/versions/32.0.0/codemod.ts b/packages/codemods/src/versions/32.0.0/codemod.ts index 2f3cd098..6474ac6d 100644 --- a/packages/codemods/src/versions/32.0.0/codemod.ts +++ b/packages/codemods/src/versions/32.0.0/codemod.ts @@ -13,10 +13,11 @@ const codemod: Codemod = function codemodV32_0_0( options: CodemodOptions, ): CodemodResult { const { path, source } = file; - const { fs } = options; + const { fs, userConfig } = options; return transformFileAst(source, transforms, { filename: path, fs, + userConfig, }); }; diff --git a/packages/codemods/src/worker.ts b/packages/codemods/src/worker.ts index e3018885..5b5e2cde 100644 --- a/packages/codemods/src/worker.ts +++ b/packages/codemods/src/worker.ts @@ -1,32 +1,35 @@ -import { createRequire } from 'node:module'; import { isMainThread, workerData } from 'node:worker_threads'; import { composeCodemods, createCodemodTask, initCodemodTaskWorker, + loadUserConfig, } from '@ag-grid-devtools/codemod-task-utils'; import type { Codemod } from '@ag-grid-devtools/types'; +import { dynamicRequire } from '@ag-grid-devtools/utils'; if (isMainThread) throw new Error('This module must be run in a worker thread'); // Get the list of codemod module paths from the worker context -const codemodPaths = workerData; +const codemodPaths = workerData.codemodPaths; if (!isStringArray(codemodPaths)) throw new Error('Invalid worker data'); // Load the specified codemod modules const codemods = codemodPaths.map((codemodPath) => { - const codemod = requireDynamicModule(codemodPath, import.meta); + let codemod = dynamicRequire.requireDefault(codemodPath, import.meta); if (typeof codemod !== 'function') { throw new Error(`Invalid codemod path: ${JSON.stringify(codemodPath)}`); } return codemod; }); +const userConfig = loadUserConfig(workerData.userConfigPath); + // Combine the codemods into a single worker task -const task = createCodemodTask(composeCodemods(codemods)); +const task = createCodemodTask(composeCodemods(codemods), userConfig); // Launch the worker task -initCodemodTaskWorker(task); +initCodemodTaskWorker(task, { userConfig }); function isStringArray(value: unknown): value is Array { return isTypedArray(value, (item): item is string => typeof item === 'string'); @@ -36,10 +39,5 @@ function isTypedArray( value: unknown, predicate: (item: unknown) => item is T, ): value is Array { - return Array.isArray(value) && value.every((item) => predicate(item)); -} - -function requireDynamicModule(path: string, meta: ImportMeta): T { - const require = createRequire(meta.url); - return require(path); + return Array.isArray(value) && value.every(predicate); } diff --git a/packages/codemods/templates/create-version/codemod/codemod.ts b/packages/codemods/templates/create-version/codemod/codemod.ts index ab012109..6723653e 100644 --- a/packages/codemods/templates/create-version/codemod/codemod.ts +++ b/packages/codemods/templates/create-version/codemod/codemod.ts @@ -13,10 +13,11 @@ const codemod: Codemod = function codemodV<%= versionIdentifier %>( options: CodemodOptions, ): CodemodResult { const { path, source } = file; - const { fs } = options; + const { fs, userConfig } = options; return transformFileAst(source, transforms, { filename: path, fs, + userConfig, }); }; diff --git a/packages/types/lib.ts b/packages/types/lib.ts index 0a5ca472..d6dac903 100644 --- a/packages/types/lib.ts +++ b/packages/types/lib.ts @@ -1,3 +1,5 @@ +export * from './src/ag-grid-export-name'; +export * from './src/user-config'; export * from './src/codemod'; export * from './src/fs'; export * from './src/manifest'; diff --git a/packages/types/src/ag-grid-export-name.ts b/packages/types/src/ag-grid-export-name.ts new file mode 100644 index 00000000..6e917da8 --- /dev/null +++ b/packages/types/src/ag-grid-export-name.ts @@ -0,0 +1,46 @@ +const agGridKnownExportNames = { + // Add here more members here if you add more codemods based on new exports + + /** Old Grid constructor */ + Grid: 0, + + /** New createGrid factory function */ + createGrid: 0, + + /** ag-grid React component */ + AgGridReact: 0, + + /** ag-grid Angular component */ + AgGridAngular: 0, + + /** ag-grid Vue component */ + AgGridVue: 0, +}; + +const knownExportNames = { + /** From angular component */ + Component: 0, + + /** From angular core */ + ViewChild: 0, + + ...agGridKnownExportNames, +}; + +/** + * The list of all known names exported by ag-grid used in various codemods. + */ +export type AgGridExportName = keyof typeof agGridKnownExportNames; + +export type KnownExportName = keyof typeof knownExportNames; + +export const AgGridExportName: Readonly> = Object.keys( + knownExportNames, +).reduce((acc, key) => { + acc[key as AgGridExportName] = key as AgGridExportName; + return acc; +}, Object.create(null)); + +export const isAgGridExportName = (name: string): name is AgGridExportName => { + return name in agGridKnownExportNames; +}; diff --git a/packages/types/src/codemod.ts b/packages/types/src/codemod.ts index a75c642e..82a7556a 100644 --- a/packages/types/src/codemod.ts +++ b/packages/types/src/codemod.ts @@ -1,5 +1,6 @@ import { type FsUtils } from './fs'; import { type Task } from './task'; +import { UserConfig } from './user-config'; export interface Codemod { (file: CodemodInput, options: CodemodOptions): CodemodResult; @@ -12,6 +13,7 @@ export interface CodemodInput { export interface CodemodOptions { fs: FsUtils; + userConfig: UserConfig | undefined; } export interface CodemodResult { diff --git a/packages/types/src/task.ts b/packages/types/src/task.ts index 624c9bc1..182dc7a8 100644 --- a/packages/types/src/task.ts +++ b/packages/types/src/task.ts @@ -1,4 +1,5 @@ import { type FsUtils } from './fs'; +import { UserConfig } from './user-config'; export interface Task { run(input: I, runner: TaskRunnerEnvironment): Promise; @@ -14,4 +15,5 @@ export interface WorkerTaskConfig { export interface TaskRunnerEnvironment { fs: FsUtils; + userConfig: UserConfig | undefined; } diff --git a/packages/types/src/user-config.ts b/packages/types/src/user-config.ts new file mode 100644 index 00000000..8fd1ed54 --- /dev/null +++ b/packages/types/src/user-config.ts @@ -0,0 +1,104 @@ +import { AgGridExportName as AgGridExportName } from './ag-grid-export-name'; + +export type Framework = 'angular' | 'react' | 'vue' | 'vanilla'; + +export type ImportType = 'esm' | 'cjs' | 'umd'; + +export interface MatchGridImportArgs { + /** The path being imported, as specified in the source code. For example `import "@my-company/my-grid"` */ + importPath: string; + + /** The type of module being imported (esm, cjs, umd) */ + importType: ImportType; + + /** + * The framework being matched (vanilla, react, angular, vue). + * - "angular" for `@ag-grid-community/angular` or `ag-grid-angular` + * - "vanilla" for `@ag-grid-community/core` or `ag-grid-community` + * - "react" for `@ag-grid-community/react` or `ag-grid-react` + * - "vue" for `@ag-grid-community/vue` or `ag-grid-vue` + */ + framework: Framework; + + /** The filename of the source file being processed. For example "/my-org/my-project/myfile.tsx" */ + sourceFilePath: string; +} + +export interface MatchGridImportNameArgs extends MatchGridImportArgs { + /** The match to check, for example "AgGridReact" */ + agGridExportName: AgGridExportName; + + /** The imported symbol, for example "MyGrid" */ + importName: string; +} + +export interface UserConfig { + /** + * Custom interceptor to check if an import is a grid module. + * + * Return true to process the received module import path as an AG Grid module. + * + * Note that this interceptor will not be called for AG Grid modules, they will be processed by default. + * + * @example + * + * ```ts + * matchGridImport({ importPath }) { + * return importPath === "@my-org/my-grid"; + * } + * ``` + * + * @param args - The input to check. + * @returns true if the received input matches one custom module to process. + */ + matchGridImport?(args: MatchGridImportArgs): boolean; + + /** + * Custom interceptor to check if a module is a grid module export. + * This may be called only if matchGridImport was specified and returned `true`. + * + * This interceptor can be used to handle reexported grid symbols with a different name. + * + * For example, if AgGridReact coming from "@ag-grid-community/react" has been reexported as "MyGrid" from "@my-org/my-grid", this interceptor can be used to match it. + * + * ```ts + * matchGridImport({ importPath }) { + * return importPath === "@my-org/my-grid"; + * }, + * + * matchGridImportName: ({ agGridExportName, importName, importPath }) => { + * if (importPath === "@my-org/my-grid" && agGridExportName === "AgGridReact" && importName === "MyGrid") { + * return true; + * } + * return agGridExportName === importName; // Default matching, for example "createGrid" will be matched with "createGrid". + * } + * ``` + * + * A default implementation would be: + * + * ```ts + * matchGridImportName: ({ agGridExportName, importName }) => { + * return agGridExportName === importName; // Match the export name with the expected default match. + * } + * ``` + * + * Note that this interceptor will not be called for AG Grid modules, they will be processed by default. + * + * @param args - The input to check. + * @returns true if the export is a custom grid module export. + */ + matchGridImportName?(args: MatchGridImportNameArgs): boolean; + + /** + * In vanilla JavaScript, the Grid constructor was deprecated and replaced by the `createGrid` function. + * This interceptor will be called to replace "new MyGrid(...)" with "createMyGrid(...)". + * + * Usually, this is the usage of createGrid: + * + * Note that this interceptor will not be called for AG Grid modules, they will be processed by default. + * + * @returns The name of the function to create a grid. If this function returns null, undefined or empty string, "createGrid" will be used. + * + */ + getCreateGridName?(args: MatchGridImportNameArgs): string | null | undefined; +} diff --git a/packages/utils/lib.test.ts b/packages/utils/lib.test.ts index 44434816..764e959c 100644 --- a/packages/utils/lib.test.ts +++ b/packages/utils/lib.test.ts @@ -11,9 +11,9 @@ test('module exports', () => { instantiateEnum: lib.instantiateEnum, isEnumVariant: lib.isEnumVariant, match: lib.match, - matchString: lib.matchString, nonNull: lib.nonNull, partition: lib.partition, unreachable: lib.unreachable, + dynamicRequire: lib.dynamicRequire, }); }); diff --git a/packages/utils/lib.ts b/packages/utils/lib.ts index 182f595f..566573f2 100644 --- a/packages/utils/lib.ts +++ b/packages/utils/lib.ts @@ -1,4 +1,4 @@ export * from './src/arrayHelpers'; export * from './src/enumHelpers'; -export * from './src/stringHelpers'; export * from './src/typeHelpers'; +export * from './src/module'; diff --git a/packages/utils/src/module.ts b/packages/utils/src/module.ts new file mode 100644 index 00000000..c11db7fa --- /dev/null +++ b/packages/utils/src/module.ts @@ -0,0 +1,34 @@ +import { createRequire } from 'node:module'; + +export const dynamicRequire = { + resolve(path: string, meta: ImportMeta): string { + if (meta.url === undefined && typeof require !== undefined) { + // import.meta not available, maybe running a ts file with tsx? use default cjs require + return require.resolve(path); + } + return createRequire(meta.url).resolve(path); + }, + + require(path: string, meta: ImportMeta): T { + if (meta.url === undefined && typeof require !== undefined) { + // import.meta not available, maybe running a ts file with tsx? use default cjs require + return require(path); + } + return createRequire(meta.url)(path); + }, + + /** Like require, but supports modules with a default export transpiled to cjs */ + requireDefault(path: string, meta: ImportMeta): T { + const required = dynamicRequire.require(path, meta); + if ( + typeof required === 'object' && + required !== null && + 'default' in required && + '__esModule' in required + ) { + // this is a default export from an esm module transpiled to cjs, return the default export + return required.default; + } + return required; + }, +}; diff --git a/packages/utils/src/stringHelpers.test.ts b/packages/utils/src/stringHelpers.test.ts deleted file mode 100644 index 1b0b3827..00000000 --- a/packages/utils/src/stringHelpers.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { describe, expect, test } from 'vitest'; - -import { matchString } from './stringHelpers'; - -describe(matchString, () => { - test('matches plain string patterns', () => { - expect(matchString('foo', 'foo')).toBe(true); - expect(matchString('foo', 'f')).toBe(false); - expect(matchString('foo', 'oo')).toBe(false); - }); - - test('matches regular expression patterns', () => { - expect(matchString('foo', /foo|bar/)).toBe(true); - expect(matchString('foo', /oo/)).toBe(true); - expect(matchString('foo', /baz/)).toBe(false); - }); -}); diff --git a/packages/utils/src/stringHelpers.ts b/packages/utils/src/stringHelpers.ts deleted file mode 100644 index 5780a789..00000000 --- a/packages/utils/src/stringHelpers.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function matchString(value: string, pattern: string | RegExp): boolean { - if (typeof pattern === 'string') return value === pattern; - return pattern.test(value); -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c4082829..e014ce0f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,9 @@ importers: prettier: specifier: 3.3.2 version: 3.3.2 + tsx: + specifier: 4.16.2 + version: 4.16.2 typedoc: specifier: ^0.26.3 version: 0.26.3(typescript@5.5.3) @@ -157,8 +160,8 @@ importers: specifier: ^5.3.3 version: 5.3.3(@types/node@20.14.9) vite-plugin-dts: - specifier: 3.9.1 - version: 3.9.1(@types/node@20.14.9)(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9)) + specifier: 3.7.3 + version: 3.7.3(@types/node@20.14.9)(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9)) vite-plugin-node-polyfills: specifier: 0.22.0 version: 0.22.0(rollup@4.18.0)(vite@5.3.3(@types/node@20.14.9)) @@ -238,6 +241,9 @@ importers: '@ag-grid-devtools/types': specifier: workspace:* version: link:../types + '@ag-grid-devtools/utils': + specifier: workspace:* + version: link:../utils '@ag-grid-devtools/worker-utils': specifier: workspace:* version: link:../worker-utils @@ -1151,11 +1157,11 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@microsoft/api-extractor-model@7.28.13': - resolution: {integrity: sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==} + '@microsoft/api-extractor-model@7.28.3': + resolution: {integrity: sha512-wT/kB2oDbdZXITyDh2SQLzaWwTOFbV326fP0pUwNW00WeliARs0qjmXBWmGWardEzp2U3/axkO3Lboqun6vrig==} - '@microsoft/api-extractor@7.43.0': - resolution: {integrity: sha512-GFhTcJpB+MI6FhvXEI9b2K0snulNLWHqC/BbcJtyNYcKUiw7l3Lgis5ApsYncJ0leALX7/of4XfmXk+maT111w==} + '@microsoft/api-extractor@7.39.0': + resolution: {integrity: sha512-PuXxzadgnvp+wdeZFPonssRAj/EW4Gm4s75TXzPk09h3wJ8RS3x7typf95B4vwZRrPTQBGopdUl+/vHvlPdAcg==} hasBin: true '@microsoft/tsdoc-config@0.16.2': @@ -1347,27 +1353,19 @@ packages: cpu: [x64] os: [win32] - '@rushstack/node-core-library@4.0.2': - resolution: {integrity: sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==} + '@rushstack/node-core-library@3.62.0': + resolution: {integrity: sha512-88aJn2h8UpSvdwuDXBv1/v1heM6GnBf3RjEy6ZPP7UnzHNCqOHA2Ut+ScYUbXcqIdfew9JlTAe3g+cnX9xQ/Aw==} peerDependencies: '@types/node': '*' peerDependenciesMeta: '@types/node': optional: true - '@rushstack/rig-package@0.5.2': - resolution: {integrity: sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==} + '@rushstack/rig-package@0.5.1': + resolution: {integrity: sha512-pXRYSe29TjRw7rqxD4WS3HN/sRSbfr+tJs4a9uuaSIBAITbUggygdhuG0VrO0EO+QqH91GhYMN4S6KRtOEmGVA==} - '@rushstack/terminal@0.10.0': - resolution: {integrity: sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==} - peerDependencies: - '@types/node': '*' - peerDependenciesMeta: - '@types/node': - optional: true - - '@rushstack/ts-command-line@4.19.1': - resolution: {integrity: sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg==} + '@rushstack/ts-command-line@4.17.1': + resolution: {integrity: sha512-2jweO1O57BYP5qdBGl6apJLB+aRIn5ccIRTPDyULh0KMwVzFqWtw6IZWt1qtUoZD/pD2RNkIOosH6Cq45rIYeg==} '@shikijs/core@1.9.1': resolution: {integrity: sha512-EmUful2MQtY8KgCF1OkBtOuMcvaZEvmdubhW0UHCGXi21O9dRLeADVCj+k6ZS+de7Mz9d2qixOXJ+GLhcK3pXg==} @@ -1817,6 +1815,10 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colors@1.2.5: + resolution: {integrity: sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==} + engines: {node: '>=0.1.90'} + commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -2136,6 +2138,9 @@ packages: resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} engines: {node: '>=16'} + get-tsconfig@4.7.5: + resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2449,9 +2454,6 @@ packages: magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} - magic-string@0.30.5: resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} engines: {node: '>=12'} @@ -2504,9 +2506,6 @@ packages: minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} - minimatch@3.0.8: - resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} - minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2744,6 +2743,9 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.19.0: resolution: {integrity: sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==} @@ -2910,10 +2912,6 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -2971,6 +2969,11 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + tsx@4.16.2: + resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==} + engines: {node: '>=18.0.0'} + hasBin: true + tty-browserify@0.0.1: resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} @@ -2998,8 +3001,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.4.2: - resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} + typescript@5.3.3: + resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} engines: {node: '>=14.17'} hasBin: true @@ -3057,8 +3060,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite-plugin-dts@3.9.1: - resolution: {integrity: sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==} + vite-plugin-dts@3.7.3: + resolution: {integrity: sha512-26eTlBYdpjRLWCsTJebM8vkCieE+p9gP3raf+ecDnzzK5E3FG6VE1wcy55OkRpfWWVlVvKkYFe6uvRHYWx7Nog==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -3830,29 +3833,28 @@ snapshots: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - '@microsoft/api-extractor-model@7.28.13(@types/node@20.14.9)': + '@microsoft/api-extractor-model@7.28.3(@types/node@20.14.9)': dependencies: '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 4.0.2(@types/node@20.14.9) + '@rushstack/node-core-library': 3.62.0(@types/node@20.14.9) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.43.0(@types/node@20.14.9)': + '@microsoft/api-extractor@7.39.0(@types/node@20.14.9)': dependencies: - '@microsoft/api-extractor-model': 7.28.13(@types/node@20.14.9) + '@microsoft/api-extractor-model': 7.28.3(@types/node@20.14.9) '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 4.0.2(@types/node@20.14.9) - '@rushstack/rig-package': 0.5.2 - '@rushstack/terminal': 0.10.0(@types/node@20.14.9) - '@rushstack/ts-command-line': 4.19.1(@types/node@20.14.9) + '@rushstack/node-core-library': 3.62.0(@types/node@20.14.9) + '@rushstack/rig-package': 0.5.1 + '@rushstack/ts-command-line': 4.17.1 + colors: 1.2.5 lodash: 4.17.21 - minimatch: 3.0.8 resolve: 1.22.4 semver: 7.5.4 source-map: 0.6.1 - typescript: 5.4.2 + typescript: 5.3.3 transitivePeerDependencies: - '@types/node' @@ -3985,8 +3987,9 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.9.4': optional: true - '@rushstack/node-core-library@4.0.2(@types/node@20.14.9)': + '@rushstack/node-core-library@3.62.0(@types/node@20.14.9)': dependencies: + colors: 1.2.5 fs-extra: 7.0.1 import-lazy: 4.0.0 jju: 1.4.0 @@ -3996,26 +3999,17 @@ snapshots: optionalDependencies: '@types/node': 20.14.9 - '@rushstack/rig-package@0.5.2': + '@rushstack/rig-package@0.5.1': dependencies: resolve: 1.22.4 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.10.0(@types/node@20.14.9)': - dependencies: - '@rushstack/node-core-library': 4.0.2(@types/node@20.14.9) - supports-color: 8.1.1 - optionalDependencies: - '@types/node': 20.14.9 - - '@rushstack/ts-command-line@4.19.1(@types/node@20.14.9)': + '@rushstack/ts-command-line@4.17.1': dependencies: - '@rushstack/terminal': 0.10.0(@types/node@20.14.9) '@types/argparse': 1.0.38 argparse: 1.0.10 + colors: 1.2.5 string-argv: 0.3.2 - transitivePeerDependencies: - - '@types/node' '@shikijs/core@1.9.1': {} @@ -4569,6 +4563,8 @@ snapshots: color-name@1.1.4: {} + colors@1.2.5: {} + commander@9.5.0: optional: true @@ -5009,6 +5005,10 @@ snapshots: get-stream@8.0.1: {} + get-tsconfig@4.7.5: + dependencies: + resolve-pkg-maps: 1.0.0 + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -5302,10 +5302,6 @@ snapshots: dependencies: sourcemap-codec: 1.4.8 - magic-string@0.30.10: - dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 - magic-string@0.30.5: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -5366,10 +5362,6 @@ snapshots: minimalistic-crypto-utils@1.0.1: {} - minimatch@3.0.8: - dependencies: - brace-expansion: 1.1.11 - minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -5631,6 +5623,8 @@ snapshots: resolve-from@4.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.19.0: dependencies: is-core-module: 2.13.0 @@ -5822,10 +5816,6 @@ snapshots: dependencies: has-flag: 4.0.0 - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} synckit@0.8.8: @@ -5869,6 +5859,13 @@ snapshots: tslib@2.6.2: {} + tsx@4.16.2: + dependencies: + esbuild: 0.21.5 + get-tsconfig: 4.7.5 + optionalDependencies: + fsevents: 2.3.3 + tty-browserify@0.0.1: {} type-check@0.4.0: @@ -5899,7 +5896,7 @@ snapshots: typescript@5.2.2: {} - typescript@5.4.2: {} + typescript@5.3.3: {} typescript@5.5.3: {} @@ -5991,14 +5988,13 @@ snapshots: - supports-color - terser - vite-plugin-dts@3.9.1(@types/node@20.14.9)(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9)): + vite-plugin-dts@3.7.3(@types/node@20.14.9)(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9)): dependencies: - '@microsoft/api-extractor': 7.43.0(@types/node@20.14.9) + '@microsoft/api-extractor': 7.39.0(@types/node@20.14.9) '@rollup/pluginutils': 5.1.0(rollup@4.18.0) '@vue/language-core': 1.8.27(typescript@5.5.3) debug: 4.3.4 kolorist: 1.8.0 - magic-string: 0.30.10 typescript: 5.5.3 vue-tsc: 1.8.27(typescript@5.5.3) optionalDependencies: From 7536b0e04179b5f75bb050586f8fd8ed03273383 Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Mon, 8 Jul 2024 11:21:25 +0100 Subject: [PATCH 02/10] fix build, add link to help screen (#57) - Add link to the documentation when running migrate --help - Add tsx as a dependency so typescript support for configuration is always available - Fix the build configuration to properly emit user-config.cjs and user-config.d.ts - Update some packages - Fix user-config.ts so proper d.ts is generated correctly - the previous version was not generating a valid d.ts --- package.json | 2 +- packages/ast/src/imports.ts | 6 +- packages/build-config/package.json | 4 +- packages/cli/package.json | 15 +- packages/cli/src/cli.ts | 6 +- packages/cli/src/commands/migrate.ts | 8 +- packages/cli/src/test/cli.test.ts | 4 +- packages/cli/src/test/user-config.cts | 18 + packages/cli/src/test/user-config.ts | 18 - packages/cli/{src => }/user-config.ts | 0 packages/cli/vite.config.mts | 14 +- packages/codemod-utils/src/agGridHelpers.ts | 10 +- .../migrate-legacy-js-grid-constructor.ts | 6 +- packages/types/src/ag-grid-export-name.ts | 38 +- packages/types/src/user-config.ts | 2 +- packages/utils/src/module.ts | 2 +- pnpm-lock.yaml | 407 +++++++++--------- 17 files changed, 288 insertions(+), 272 deletions(-) create mode 100644 packages/cli/src/test/user-config.cts delete mode 100644 packages/cli/src/test/user-config.ts rename packages/cli/{src => }/user-config.ts (100%) diff --git a/package.json b/package.json index 5775c0d8..ba53a745 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@babel/traverse": "7.23.2", "@babel/types": "7.23.0", "@types/glob": "8.1.0", - "glob": "10.4.2", + "glob": "10.4.3", "prettier": "3.3.2", "typedoc": "^0.26.3", "typescript": "5.5.3", diff --git a/packages/ast/src/imports.ts b/packages/ast/src/imports.ts index dcdfcf17..aca2ed01 100644 --- a/packages/ast/src/imports.ts +++ b/packages/ast/src/imports.ts @@ -13,7 +13,7 @@ import { MatchGridImportNameArgs, ImportType, KnownExportName, - AgGridExportName, + AgGridExportNames, isAgGridExportName, } from '@ag-grid-devtools/types'; @@ -248,7 +248,7 @@ function matchImportedSpecifier( if ( !result && framework === 'vanilla' && - agGridExportName === AgGridExportName.createGrid && + agGridExportName === AgGridExportNames.createGrid && userConfig.getCreateGridName ) { result = { fromUserConfig }; // Special case for createGrid @@ -268,7 +268,7 @@ function matchImportedSpecifier( if ( framework === 'vanilla' && - agGridExportName === AgGridExportName.createGrid && + agGridExportName === AgGridExportNames.createGrid && userConfig.getCreateGridName ) { return { diff --git a/packages/build-config/package.json b/packages/build-config/package.json index b2576490..c0d70995 100644 --- a/packages/build-config/package.json +++ b/packages/build-config/package.json @@ -17,7 +17,7 @@ "lint": "eslint --ext js,cjs,mjs,ts ." }, "dependencies": { - "@types/node": "20.14.9", + "@types/node": "20.14.10", "@types/react": "18.3.3", "@typescript-eslint/eslint-plugin": "7.14.1", "@typescript-eslint/parser": "7.14.1", @@ -32,7 +32,7 @@ "rollup-plugin-preserve-shebang": "1.0.1", "typescript": "5.5.3", "vite": "^5.3.3", - "vite-plugin-dts": "3.7.3", + "vite-plugin-dts": "3.9.1", "vite-plugin-node-polyfills": "0.22.0", "vite-plugin-static-copy": "1.0.6", "vitest": "1.6.0" diff --git a/packages/cli/package.json b/packages/cli/package.json index ed1f09f3..40d5c222 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -35,11 +35,13 @@ "main": "./index.cjs", "bin": "./index.cjs" }, + "types": "./index.d.ts", "bundleDependencies": [ "@ag-grid-devtools/codemods" ], "dependencies": { - "@ag-grid-devtools/codemods": "workspace:*" + "@ag-grid-devtools/codemods": "workspace:*", + "tsx": "4.16.2" }, "devDependencies": { "@ag-grid-devtools/build-config": "workspace:*", @@ -47,13 +49,14 @@ "@ag-grid-devtools/types": "workspace:*", "@ag-grid-devtools/utils": "workspace:*", "@ag-grid-devtools/worker-utils": "workspace:*", - "@types/diff": "5.0.8", + "@types/diff": "5.2.1", "@types/graceful-fs": "4.1.9", - "@types/node": "20.11.29", - "@types/semver": "7.5.6", - "diff": "5.1.0", + "@types/node": "20.14.10", + "@types/semver": "7.5.8", + "diff": "5.2.0", "graceful-fs": "4.2.11", - "semver": "7.5.4" + "semver": "7.6.2", + "vite-plugin-dts": "3.9.1" }, "peerDependencies": { "eslint": "^8", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index a5e92fba..95f111ab 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -71,13 +71,11 @@ export async function cli(args: Array, cli: CliOptions): Promise { throw null; } - // Add optional typescript support by loading tsx or ts-node + // Add typescript support by loading tsx try { dynamicRequire.require('tsx/cjs', import.meta); } catch { - try { - dynamicRequire.require('ts-node/register', import.meta); - } catch {} + // ignore error if tsx could not be loaded } const task = match(options.command, { diff --git a/packages/cli/src/commands/migrate.ts b/packages/cli/src/commands/migrate.ts index d96403e9..041f7aba 100644 --- a/packages/cli/src/commands/migrate.ts +++ b/packages/cli/src/commands/migrate.ts @@ -86,6 +86,7 @@ function usage(env: CliEnv): string { return `Usage: ${getCliCommand()} migrate [options] [...] Upgrade project source files to ensure compatibility with a specific AG Grid version +See https://ag-grid.com/javascript-data-grid/codemods for more information Options: Required arguments: @@ -100,7 +101,8 @@ Options: --allow-dirty, -d Allow operating on repositories with uncommitted changes in the working tree --num-threads Number of worker threads to spawn (defaults to the number of system cores) --dry-run Show a diff output of the changes that would be made - --config= Loads a configuration file to customize the codemod behavior (advanced). + --config= Loads a .cjs or .cts configuration file to customize the codemod behavior. + See https://ag-grid.com/javascript-data-grid/codemods/#configuration-file Additional arguments: [...] List of input files to operate on (defaults to all source files in the current working directory) @@ -425,8 +427,8 @@ async function migrate( { length: numWorkers }, () => new Worker( - // Add optional typescript support by loading tsx or ts-node - `try { require("tsx/cjs"); } catch (_) { try { require("ts-node/register"); } catch (__) {} } require(${JSON.stringify(scriptPath)});`, + // Try to add typescript support by loading tsx and load the worker script + `try { require("tsx/cjs"); } catch (_) {} require(${JSON.stringify(scriptPath)});`, config, ), ); diff --git a/packages/cli/src/test/cli.test.ts b/packages/cli/src/test/cli.test.ts index de94333a..c4465437 100644 --- a/packages/cli/src/test/cli.test.ts +++ b/packages/cli/src/test/cli.test.ts @@ -47,7 +47,7 @@ describe('cli e2e', () => { '--num-threads=0', '--allow-untracked', '--from=30.0.0', - '--config=../user-config.ts', + '--config=../user-config.cts', ], cliOptions, ); @@ -66,7 +66,7 @@ describe('cli e2e', () => { '--num-threads=2', '--allow-untracked', '--from=30.0.0', - '--config=../user-config.ts', + '--config=../user-config.cts', ], cliOptions, ); diff --git a/packages/cli/src/test/user-config.cts b/packages/cli/src/test/user-config.cts new file mode 100644 index 00000000..dc15db89 --- /dev/null +++ b/packages/cli/src/test/user-config.cts @@ -0,0 +1,18 @@ +import { defineUserConfig } from '../../user-config'; + +module.exports = defineUserConfig({ + getCreateGridName() { + return 'myCreateGrid'; + }, + + matchGridImport({ importPath: importedModule }) { + return importedModule === '@hello/world'; + }, + + matchGridImportName({ importName, agGridExportName }) { + if (agGridExportName === 'Grid') { + return importName === 'MyGrid'; + } + return agGridExportName === agGridExportName; + }, +}); diff --git a/packages/cli/src/test/user-config.ts b/packages/cli/src/test/user-config.ts deleted file mode 100644 index 265f814e..00000000 --- a/packages/cli/src/test/user-config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { defineUserConfig } from '../user-config'; - -module.exports = defineUserConfig({ - getCreateGridName() { - return 'myCreateGrid'; - }, - - matchGridImport({ importPath: importedModule }) { - return importedModule === '@hello/world'; - }, - - matchGridImportName({ importName: exported, agGridExportName: match }) { - if (match === 'Grid') { - return exported === 'MyGrid'; - } - return exported === match; - }, -}); diff --git a/packages/cli/src/user-config.ts b/packages/cli/user-config.ts similarity index 100% rename from packages/cli/src/user-config.ts rename to packages/cli/user-config.ts diff --git a/packages/cli/vite.config.mts b/packages/cli/vite.config.mts index 7f624e6e..9ce78913 100644 --- a/packages/cli/vite.config.mts +++ b/packages/cli/vite.config.mts @@ -1,5 +1,6 @@ import { resolve } from 'path'; import { defineConfig, mergeConfig } from 'vite'; +import dts from 'vite-plugin-dts'; import base from '../build-config/templates/vite/cli.vite.config'; @@ -10,15 +11,24 @@ export default mergeConfig( defineConfig({ build: { lib: { - entry: resolve(__dirname, pkg.main), + entry: { + index: resolve(__dirname, pkg.main), + 'user-config': resolve(__dirname, 'user-config.ts'), + }, name: pkg.name, formats: ['cjs'], - fileName: 'index', }, sourcemap: false, rollupOptions: { external: ['@ag-grid-devtools/codemods'], }, }, + plugins: [ + dts({ + rollupTypes: true, + bundledPackages: ['@ag-grid-devtools/types'], + exclude: ['node_modules/**', '*.config.mts', '**/*.test.ts', 'package.json', 'index.ts'], + }), + ], }), ); diff --git a/packages/codemod-utils/src/agGridHelpers.ts b/packages/codemod-utils/src/agGridHelpers.ts index 2d2c0b6a..bfb08c71 100644 --- a/packages/codemod-utils/src/agGridHelpers.ts +++ b/packages/codemod-utils/src/agGridHelpers.ts @@ -58,7 +58,7 @@ import { VueTemplateFormatter, } from './vueHelpers'; import { VueComponentCliContext } from './transform'; -import { AgGridExportName } from '@ag-grid-devtools/types'; +import { AgGridExportNames } from '@ag-grid-devtools/types'; type AssignmentExpression = Types.AssignmentExpression; type CallExpression = Types.CallExpression; @@ -103,7 +103,7 @@ const AG_GRID_REACT_PACKAGE_NAME_MATCHER: ImportedModuleMatcher = { importUmdPattern: null, framework: 'react', }; -const AG_GRID_REACT_GRID_COMPONENT_NAME = AgGridExportName.AgGridReact; +const AG_GRID_REACT_GRID_COMPONENT_NAME = AgGridExportNames.AgGridReact; const AG_GRID_REACT_GRID_OPTIONS_PROP_NAME = 'gridOptions'; const AG_GRID_REACT_API_ACCESSOR_NAME = 'api'; const AG_GRID_REACT_COLUMN_API_ACCESSOR_NAME = 'columnApi'; @@ -113,7 +113,7 @@ const AG_GRID_ANGULAR_PACKAGE_NAME_MATCHER: ImportedModuleMatcher = { importUmdPattern: null, framework: 'angular', }; -const AG_GRID_ANGULAR_GRID_COMPONENT_NAME = AgGridExportName.AgGridAngular; +const AG_GRID_ANGULAR_GRID_COMPONENT_NAME = AgGridExportNames.AgGridAngular; const AG_GRID_ANGULAR_ELEMENT_NAME = 'ag-grid-angular'; const AG_GRID_ANGULAR_GRID_OPTIONS_ATTRIBUTE_NAME = 'gridOptions'; const AG_GRID_ANGULAR_API_ACCESSOR_NAME = 'api'; @@ -125,7 +125,7 @@ const AG_GRID_VUE_PACKAGE_NAME_MATCHER: ImportedModuleMatcher = { importUmdPattern: null, framework: 'vue', }; -const AG_GRID_VUE_GRID_COMPONENT_NAME = AgGridExportName.AgGridVue; +const AG_GRID_VUE_GRID_COMPONENT_NAME = AgGridExportNames.AgGridVue; const AG_GRID_VUE_GRID_OPTIONS_ATTRIBUTE_NAME = 'gridOptions'; const AG_GRID_VUE_API_ACCESSOR_NAME = 'api'; const AG_GRID_VUE_COLUMN_API_ACCESSOR_NAME = 'columnApi'; @@ -693,7 +693,7 @@ function matchJsGridApiInitializer( const gridApiImport = getNamedModuleImportExpression( callee, AG_GRID_JS_PACKAGE_NAME_MATCHER, - AgGridExportName.createGrid, + AgGridExportNames.createGrid, context, ); if (!gridApiImport) return null; diff --git a/packages/codemods/src/transforms/migrate-legacy-js-grid-constructor-v31/migrate-legacy-js-grid-constructor.ts b/packages/codemods/src/transforms/migrate-legacy-js-grid-constructor-v31/migrate-legacy-js-grid-constructor.ts index a472aa5a..49ce9035 100644 --- a/packages/codemods/src/transforms/migrate-legacy-js-grid-constructor-v31/migrate-legacy-js-grid-constructor.ts +++ b/packages/codemods/src/transforms/migrate-legacy-js-grid-constructor-v31/migrate-legacy-js-grid-constructor.ts @@ -17,7 +17,7 @@ import { NodePath, } from '@ag-grid-devtools/ast'; import { AG_GRID_JS_PACKAGE_NAME_MATCHER } from '@ag-grid-devtools/codemod-utils'; -import { AgGridExportName } from '@ag-grid-devtools/types'; +import { AgGridExportNames } from '@ag-grid-devtools/types'; import { match, nonNull } from '@ag-grid-devtools/utils'; type Expression = Types.Expression; @@ -25,7 +25,7 @@ type Identifier = Types.Identifier; type ObjectPattern = Types.ObjectPattern; type ObjectProperty = Types.ObjectProperty; -const LEGACY_GRID_API_EXPORT_NAME = AgGridExportName.Grid; +const LEGACY_GRID_API_EXPORT_NAME = AgGridExportNames.Grid; const GRID_API_ACCESSOR_NAME = 'api'; const COLUMN_API_ACCESSOR_NAME = 'columnApi'; @@ -87,7 +87,7 @@ const transform: AstTransform = function migrateLegacyJsGridConst const fromUserConfig = legacyGridApiImport.importMatcherResult.fromUserConfig; const updatedConstructorExportName = (fromUserConfig && state.opts.userConfig?.getCreateGridName?.(fromUserConfig)) || - AgGridExportName.createGrid; + AgGridExportNames.createGrid; // Rewrite the legacy Grid import to the new-style createGrid import const updatedConstructorClass = match(legacyGridApiImport.binding, { diff --git a/packages/types/src/ag-grid-export-name.ts b/packages/types/src/ag-grid-export-name.ts index 6e917da8..652d8c08 100644 --- a/packages/types/src/ag-grid-export-name.ts +++ b/packages/types/src/ag-grid-export-name.ts @@ -1,46 +1,46 @@ -const agGridKnownExportNames = { +enum agGridKnownExportNames { // Add here more members here if you add more codemods based on new exports /** Old Grid constructor */ - Grid: 0, + Grid, /** New createGrid factory function */ - createGrid: 0, + createGrid, /** ag-grid React component */ - AgGridReact: 0, + AgGridReact, /** ag-grid Angular component */ - AgGridAngular: 0, + AgGridAngular, /** ag-grid Vue component */ - AgGridVue: 0, -}; + AgGridVue, +} -const knownExportNames = { +enum knownOtherExportNames { /** From angular component */ - Component: 0, + Component, /** From angular core */ - ViewChild: 0, - - ...agGridKnownExportNames, -}; + ViewChild, +} /** * The list of all known names exported by ag-grid used in various codemods. */ export type AgGridExportName = keyof typeof agGridKnownExportNames; -export type KnownExportName = keyof typeof knownExportNames; +export type KnownExportName = AgGridExportName | keyof typeof knownOtherExportNames; -export const AgGridExportName: Readonly> = Object.keys( - knownExportNames, +export const AgGridExportNames: Record = Object.keys( + agGridKnownExportNames, ).reduce((acc, key) => { - acc[key as AgGridExportName] = key as AgGridExportName; + if (isNaN(Number(key))) { + acc[key] = key as AgGridExportName; + } return acc; }, Object.create(null)); -export const isAgGridExportName = (name: string): name is AgGridExportName => { - return name in agGridKnownExportNames; +export const isAgGridExportName = (name: unknown): name is AgGridExportName => { + return typeof name === 'string' && name in AgGridExportNames; }; diff --git a/packages/types/src/user-config.ts b/packages/types/src/user-config.ts index 8fd1ed54..a45b511f 100644 --- a/packages/types/src/user-config.ts +++ b/packages/types/src/user-config.ts @@ -1,4 +1,4 @@ -import { AgGridExportName as AgGridExportName } from './ag-grid-export-name'; +import type { AgGridExportName } from './ag-grid-export-name'; export type Framework = 'angular' | 'react' | 'vue' | 'vanilla'; diff --git a/packages/utils/src/module.ts b/packages/utils/src/module.ts index c11db7fa..d7a7c05b 100644 --- a/packages/utils/src/module.ts +++ b/packages/utils/src/module.ts @@ -24,7 +24,7 @@ export const dynamicRequire = { typeof required === 'object' && required !== null && 'default' in required && - '__esModule' in required + ('__esModule' in required || required[Symbol.toStringTag] === 'Module') ) { // this is a default export from an esm module transpiled to cjs, return the default export return required.default; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e014ce0f..4c173c68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -31,10 +31,10 @@ importers: version: 8.1.0 '@vitest/coverage-v8': specifier: 1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@20.14.9)) + version: 1.6.0(vitest@1.6.0(@types/node@20.14.10)) glob: - specifier: 10.4.2 - version: 10.4.2 + specifier: 10.4.3 + version: 10.4.3 prettier: specifier: 3.3.2 version: 3.3.2 @@ -49,10 +49,10 @@ importers: version: 5.5.3 vite: specifier: 5.3.3 - version: 5.3.3(@types/node@20.14.9) + version: 5.3.3(@types/node@20.14.10) vitest: specifier: 1.6.0 - version: 1.6.0(@types/node@20.14.9) + version: 1.6.0(@types/node@20.14.10) packages/ast: dependencies: @@ -103,10 +103,10 @@ importers: version: 5.2.2 vite: specifier: ^5 - version: 5.0.11(@types/node@20.14.9) + version: 5.0.11(@types/node@20.14.10) vitest: specifier: ^1 - version: 1.3.1(@types/node@20.14.9) + version: 1.3.1(@types/node@20.14.10) devDependencies: '@ag-grid-devtools/build-config': specifier: workspace:* @@ -115,8 +115,8 @@ importers: packages/build-config: dependencies: '@types/node': - specifier: 20.14.9 - version: 20.14.9 + specifier: 20.14.10 + version: 20.14.10 '@types/react': specifier: 18.3.3 version: 18.3.3 @@ -128,10 +128,10 @@ importers: version: 7.14.1(eslint@8.56.0)(typescript@5.5.3) '@vitejs/plugin-react-swc': specifier: 3.7.0 - version: 3.7.0(vite@5.3.3(@types/node@20.14.9)) + version: 3.7.0(vite@5.3.3(@types/node@20.14.10)) '@vitest/coverage-v8': specifier: 1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@20.14.9)) + version: 1.6.0(vitest@1.6.0(@types/node@20.14.10)) eslint: specifier: 8.56.0 version: 8.56.0 @@ -146,7 +146,7 @@ importers: version: 4.6.2(eslint@8.56.0) eslint-plugin-vitest: specifier: 0.4.1 - version: 0.4.1(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3)(vitest@1.6.0(@types/node@20.14.9)) + version: 0.4.1(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3)(vitest@1.6.0(@types/node@20.14.10)) prettier: specifier: 3.3.2 version: 3.3.2 @@ -158,19 +158,19 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@20.14.9) + version: 5.3.3(@types/node@20.14.10) vite-plugin-dts: - specifier: 3.7.3 - version: 3.7.3(@types/node@20.14.9)(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9)) + specifier: 3.9.1 + version: 3.9.1(@types/node@20.14.10)(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)) vite-plugin-node-polyfills: specifier: 0.22.0 - version: 0.22.0(rollup@4.18.0)(vite@5.3.3(@types/node@20.14.9)) + version: 0.22.0(rollup@4.18.0)(vite@5.3.3(@types/node@20.14.10)) vite-plugin-static-copy: specifier: 1.0.6 - version: 1.0.6(vite@5.3.3(@types/node@20.14.9)) + version: 1.0.6(vite@5.3.3(@types/node@20.14.10)) vitest: specifier: 1.6.0 - version: 1.6.0(@types/node@20.14.9) + version: 1.6.0(@types/node@20.14.10) packages/build-tools: devDependencies: @@ -186,6 +186,9 @@ importers: eslint: specifier: ^8 version: 8.48.0 + tsx: + specifier: 4.16.2 + version: 4.16.2 typedoc: specifier: ^0.26 version: 0.26.3(typescript@5.2.2) @@ -194,10 +197,10 @@ importers: version: 5.2.2 vite: specifier: ^5 - version: 5.0.11(@types/node@20.11.29) + version: 5.0.11(@types/node@20.14.10) vitest: specifier: ^1 - version: 1.3.1(@types/node@20.11.29) + version: 1.3.1(@types/node@20.14.10) devDependencies: '@ag-grid-devtools/build-config': specifier: workspace:* @@ -215,26 +218,29 @@ importers: specifier: workspace:* version: link:../worker-utils '@types/diff': - specifier: 5.0.8 - version: 5.0.8 + specifier: 5.2.1 + version: 5.2.1 '@types/graceful-fs': specifier: 4.1.9 version: 4.1.9 '@types/node': - specifier: 20.11.29 - version: 20.11.29 + specifier: 20.14.10 + version: 20.14.10 '@types/semver': - specifier: 7.5.6 - version: 7.5.6 + specifier: 7.5.8 + version: 7.5.8 diff: - specifier: 5.1.0 - version: 5.1.0 + specifier: 5.2.0 + version: 5.2.0 graceful-fs: specifier: 4.2.11 version: 4.2.11 semver: - specifier: 7.5.4 - version: 7.5.4 + specifier: 7.6.2 + version: 7.6.2 + vite-plugin-dts: + specifier: 3.9.1 + version: 3.9.1(@types/node@20.14.10)(rollup@4.18.0)(typescript@5.2.2)(vite@5.0.11(@types/node@20.14.10)) packages/codemod-task-utils: dependencies: @@ -258,10 +264,10 @@ importers: version: 5.2.2 vite: specifier: ^5 - version: 5.0.11(@types/node@20.14.9) + version: 5.0.11(@types/node@20.14.10) vitest: specifier: ^1 - version: 1.3.1(@types/node@20.14.9) + version: 1.3.1(@types/node@20.14.10) devDependencies: '@ag-grid-devtools/build-config': specifier: workspace:* @@ -304,10 +310,10 @@ importers: version: 5.2.2 vite: specifier: ^5 - version: 5.0.11(@types/node@20.14.9) + version: 5.0.11(@types/node@20.14.10) vitest: specifier: ^1 - version: 1.3.1(@types/node@20.14.9) + version: 1.3.1(@types/node@20.14.10) vue-eslint-parser: specifier: 9.3.2 version: 9.3.2(eslint@8.48.0) @@ -332,10 +338,10 @@ importers: version: 5.2.2 vite: specifier: ^5 - version: 5.0.11(@types/node@20.14.9) + version: 5.0.11(@types/node@20.14.10) vitest: specifier: ^1 - version: 1.3.1(@types/node@20.14.9) + version: 1.3.1(@types/node@20.14.10) devDependencies: '@ag-grid-devtools/ast': specifier: workspace:* @@ -372,10 +378,10 @@ importers: version: 5.5.3 vite: specifier: ^5 - version: 5.0.11(@types/node@20.14.9) + version: 5.0.11(@types/node@20.14.10) vite-plugin-node-polyfills: specifier: ^0.22 - version: 0.22.0(rollup@4.18.0)(vite@5.0.11(@types/node@20.14.9)) + version: 0.22.0(rollup@4.18.0)(vite@5.0.11(@types/node@20.14.10)) devDependencies: '@ag-grid-devtools/build-config': specifier: workspace:* @@ -457,10 +463,10 @@ importers: version: 5.2.2 vite: specifier: ^5 - version: 5.0.11(@types/node@20.14.9) + version: 5.0.11(@types/node@20.14.10) vitest: specifier: ^1 - version: 1.3.1(@types/node@20.14.9) + version: 1.3.1(@types/node@20.14.10) devDependencies: '@ag-grid-devtools/build-config': specifier: workspace:* @@ -479,7 +485,7 @@ importers: version: 5.2.2 vite: specifier: ^5 - version: 5.0.11(@types/node@20.14.9) + version: 5.0.11(@types/node@20.14.10) devDependencies: '@ag-grid-devtools/build-config': specifier: workspace:* @@ -501,10 +507,10 @@ importers: version: 5.2.2 vite: specifier: ^5 - version: 5.0.11(@types/node@20.14.9) + version: 5.0.11(@types/node@20.14.10) vitest: specifier: ^1 - version: 1.3.1(@types/node@20.14.9) + version: 1.3.1(@types/node@20.14.10) devDependencies: '@ag-grid-devtools/build-config': specifier: workspace:* @@ -535,10 +541,10 @@ importers: version: 5.2.2 vite: specifier: ^5 - version: 5.0.11(@types/node@20.14.9) + version: 5.0.11(@types/node@20.14.10) vitest: specifier: ^1 - version: 1.3.1(@types/node@20.14.9) + version: 1.3.1(@types/node@20.14.10) devDependencies: '@ag-grid-devtools/build-config': specifier: workspace:* @@ -1157,11 +1163,11 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@microsoft/api-extractor-model@7.28.3': - resolution: {integrity: sha512-wT/kB2oDbdZXITyDh2SQLzaWwTOFbV326fP0pUwNW00WeliARs0qjmXBWmGWardEzp2U3/axkO3Lboqun6vrig==} + '@microsoft/api-extractor-model@7.28.13': + resolution: {integrity: sha512-39v/JyldX4MS9uzHcdfmjjfS6cYGAoXV+io8B5a338pkHiSt+gy2eXQ0Q7cGFJ7quSa1VqqlMdlPrB6sLR/cAw==} - '@microsoft/api-extractor@7.39.0': - resolution: {integrity: sha512-PuXxzadgnvp+wdeZFPonssRAj/EW4Gm4s75TXzPk09h3wJ8RS3x7typf95B4vwZRrPTQBGopdUl+/vHvlPdAcg==} + '@microsoft/api-extractor@7.43.0': + resolution: {integrity: sha512-GFhTcJpB+MI6FhvXEI9b2K0snulNLWHqC/BbcJtyNYcKUiw7l3Lgis5ApsYncJ0leALX7/of4XfmXk+maT111w==} hasBin: true '@microsoft/tsdoc-config@0.16.2': @@ -1353,19 +1359,27 @@ packages: cpu: [x64] os: [win32] - '@rushstack/node-core-library@3.62.0': - resolution: {integrity: sha512-88aJn2h8UpSvdwuDXBv1/v1heM6GnBf3RjEy6ZPP7UnzHNCqOHA2Ut+ScYUbXcqIdfew9JlTAe3g+cnX9xQ/Aw==} + '@rushstack/node-core-library@4.0.2': + resolution: {integrity: sha512-hyES82QVpkfQMeBMteQUnrhASL/KHPhd7iJ8euduwNJG4mu2GSOKybf0rOEjOm1Wz7CwJEUm9y0yD7jg2C1bfg==} peerDependencies: '@types/node': '*' peerDependenciesMeta: '@types/node': optional: true - '@rushstack/rig-package@0.5.1': - resolution: {integrity: sha512-pXRYSe29TjRw7rqxD4WS3HN/sRSbfr+tJs4a9uuaSIBAITbUggygdhuG0VrO0EO+QqH91GhYMN4S6KRtOEmGVA==} + '@rushstack/rig-package@0.5.2': + resolution: {integrity: sha512-mUDecIJeH3yYGZs2a48k+pbhM6JYwWlgjs2Ca5f2n1G2/kgdgP9D/07oglEGf6mRyXEnazhEENeYTSNDRCwdqA==} - '@rushstack/ts-command-line@4.17.1': - resolution: {integrity: sha512-2jweO1O57BYP5qdBGl6apJLB+aRIn5ccIRTPDyULh0KMwVzFqWtw6IZWt1qtUoZD/pD2RNkIOosH6Cq45rIYeg==} + '@rushstack/terminal@0.10.0': + resolution: {integrity: sha512-UbELbXnUdc7EKwfH2sb8ChqNgapUOdqcCIdQP4NGxBpTZV2sQyeekuK3zmfQSa/MN+/7b4kBogl2wq0vpkpYGw==} + peerDependencies: + '@types/node': '*' + peerDependenciesMeta: + '@types/node': + optional: true + + '@rushstack/ts-command-line@4.19.1': + resolution: {integrity: sha512-J7H768dgcpG60d7skZ5uSSwyCZs/S2HrWP1Ds8d1qYAyaaeJmpmmLr9BVw97RjFzmQPOYnoXcKA4GkqDCkduQg==} '@shikijs/core@1.9.1': resolution: {integrity: sha512-EmUful2MQtY8KgCF1OkBtOuMcvaZEvmdubhW0UHCGXi21O9dRLeADVCj+k6ZS+de7Mz9d2qixOXJ+GLhcK3pXg==} @@ -1466,8 +1480,8 @@ packages: '@types/babel__traverse@7.20.3': resolution: {integrity: sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==} - '@types/diff@5.0.8': - resolution: {integrity: sha512-kR0gRf0wMwpxQq6ME5s+tWk9zVCfJUl98eRkD05HWWRbhPB/eu4V1IbyZAsvzC1Gn4znBJ0HN01M4DGXdBEV8Q==} + '@types/diff@5.2.1': + resolution: {integrity: sha512-uxpcuwWJGhe2AR1g8hD9F5OYGCqjqWnBUQFD8gMZsDbv8oPHzxJF6iMO6n8Tk0AdzlxoaaoQhOYlIg/PukVU8g==} '@types/estree@1.0.5': resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} @@ -1481,11 +1495,8 @@ packages: '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - '@types/node@20.11.29': - resolution: {integrity: sha512-P99thMkD/1YkCvAtOd6/zGedKNA0p2fj4ZpjCzcNiSCBWgm3cNRTBfa/qjFnsKkkojxu4vVLtWpesnZ9+ap+gA==} - - '@types/node@20.14.9': - resolution: {integrity: sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==} + '@types/node@20.14.10': + resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} '@types/node@20.5.9': resolution: {integrity: sha512-PcGNd//40kHAS3sTlzKB9C9XL4K0sTup8nbG5lC14kzEteTNuAFh9u5nA0o5TWnSG2r/JNPRXFVcHJIIeRlmqQ==} @@ -1496,8 +1507,8 @@ packages: '@types/react@18.3.3': resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} - '@types/semver@7.5.6': - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} + '@types/semver@7.5.8': + resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} '@typescript-eslint/eslint-plugin@7.14.1': resolution: {integrity: sha512-aAJd6bIf2vvQRjUG3ZkNXkmBpN+J7Wd0mfQiiVCJMu9Z5GcZZdcc0j8XwN/BM97Fl7e3SkTXODSk4VehUv7CGw==} @@ -1815,10 +1826,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - colors@1.2.5: - resolution: {integrity: sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==} - engines: {node: '>=0.1.90'} - commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -1894,8 +1901,8 @@ packages: resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - diff@5.1.0: - resolution: {integrity: sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==} + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} engines: {node: '>=0.3.1'} diffie-hellman@5.0.3: @@ -2149,9 +2156,9 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.2: - resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} - engines: {node: '>=16 || 14 >=14.18'} + glob@10.4.3: + resolution: {integrity: sha512-Q38SGlYRpVtDBPSWEylRyctn7uDeTp4NQERTLiCT1FqA9JXPYWqAVmQU6qh4r/zMM5ehxTcbaO8EjhWnvEhmyg==} + engines: {node: '>=18'} hasBin: true glob@7.2.3: @@ -2454,6 +2461,9 @@ packages: magic-string@0.25.9: resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + magic-string@0.30.10: + resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + magic-string@0.30.5: resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==} engines: {node: '>=12'} @@ -2506,6 +2516,9 @@ packages: minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + minimatch@3.0.8: + resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -2912,6 +2925,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -3001,8 +3018,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} + typescript@5.4.2: + resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} engines: {node: '>=14.17'} hasBin: true @@ -3060,8 +3077,8 @@ packages: engines: {node: ^18.0.0 || >=20.0.0} hasBin: true - vite-plugin-dts@3.7.3: - resolution: {integrity: sha512-26eTlBYdpjRLWCsTJebM8vkCieE+p9gP3raf+ecDnzzK5E3FG6VE1wcy55OkRpfWWVlVvKkYFe6uvRHYWx7Nog==} + vite-plugin-dts@3.9.1: + resolution: {integrity: sha512-rVp2KM9Ue22NGWB8dNtWEr+KekN3rIgz1tWD050QnRGlriUCmaDwa7qA5zDEjbXg5lAXhYMSBJtx3q3hQIJZSg==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -3833,28 +3850,29 @@ snapshots: '@jridgewell/resolve-uri': 3.1.1 '@jridgewell/sourcemap-codec': 1.4.15 - '@microsoft/api-extractor-model@7.28.3(@types/node@20.14.9)': + '@microsoft/api-extractor-model@7.28.13(@types/node@20.14.10)': dependencies: '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 3.62.0(@types/node@20.14.9) + '@rushstack/node-core-library': 4.0.2(@types/node@20.14.10) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.39.0(@types/node@20.14.9)': + '@microsoft/api-extractor@7.43.0(@types/node@20.14.10)': dependencies: - '@microsoft/api-extractor-model': 7.28.3(@types/node@20.14.9) + '@microsoft/api-extractor-model': 7.28.13(@types/node@20.14.10) '@microsoft/tsdoc': 0.14.2 '@microsoft/tsdoc-config': 0.16.2 - '@rushstack/node-core-library': 3.62.0(@types/node@20.14.9) - '@rushstack/rig-package': 0.5.1 - '@rushstack/ts-command-line': 4.17.1 - colors: 1.2.5 + '@rushstack/node-core-library': 4.0.2(@types/node@20.14.10) + '@rushstack/rig-package': 0.5.2 + '@rushstack/terminal': 0.10.0(@types/node@20.14.10) + '@rushstack/ts-command-line': 4.19.1(@types/node@20.14.10) lodash: 4.17.21 + minimatch: 3.0.8 resolve: 1.22.4 semver: 7.5.4 source-map: 0.6.1 - typescript: 5.3.3 + typescript: 5.4.2 transitivePeerDependencies: - '@types/node' @@ -3987,9 +4005,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.9.4': optional: true - '@rushstack/node-core-library@3.62.0(@types/node@20.14.9)': + '@rushstack/node-core-library@4.0.2(@types/node@20.14.10)': dependencies: - colors: 1.2.5 fs-extra: 7.0.1 import-lazy: 4.0.0 jju: 1.4.0 @@ -3997,19 +4014,28 @@ snapshots: semver: 7.5.4 z-schema: 5.0.5 optionalDependencies: - '@types/node': 20.14.9 + '@types/node': 20.14.10 - '@rushstack/rig-package@0.5.1': + '@rushstack/rig-package@0.5.2': dependencies: resolve: 1.22.4 strip-json-comments: 3.1.1 - '@rushstack/ts-command-line@4.17.1': + '@rushstack/terminal@0.10.0(@types/node@20.14.10)': + dependencies: + '@rushstack/node-core-library': 4.0.2(@types/node@20.14.10) + supports-color: 8.1.1 + optionalDependencies: + '@types/node': 20.14.10 + + '@rushstack/ts-command-line@4.19.1(@types/node@20.14.10)': dependencies: + '@rushstack/terminal': 0.10.0(@types/node@20.14.10) '@types/argparse': 1.0.38 argparse: 1.0.10 - colors: 1.2.5 string-argv: 0.3.2 + transitivePeerDependencies: + - '@types/node' '@shikijs/core@1.9.1': {} @@ -4098,7 +4124,7 @@ snapshots: dependencies: '@babel/types': 7.23.0 - '@types/diff@5.0.8': {} + '@types/diff@5.2.1': {} '@types/estree@1.0.5': {} @@ -4109,15 +4135,11 @@ snapshots: '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.14.9 + '@types/node': 20.14.10 '@types/minimatch@5.1.2': {} - '@types/node@20.11.29': - dependencies: - undici-types: 5.26.5 - - '@types/node@20.14.9': + '@types/node@20.14.10': dependencies: undici-types: 5.26.5 @@ -4130,7 +4152,7 @@ snapshots: '@types/prop-types': 15.7.5 csstype: 3.1.2 - '@types/semver@7.5.6': {} + '@types/semver@7.5.8': {} '@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3)': dependencies: @@ -4215,14 +4237,14 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-react-swc@3.7.0(vite@5.3.3(@types/node@20.14.9))': + '@vitejs/plugin-react-swc@3.7.0(vite@5.3.3(@types/node@20.14.10))': dependencies: '@swc/core': 1.6.6 - vite: 5.3.3(@types/node@20.14.9) + vite: 5.3.3(@types/node@20.14.10) transitivePeerDependencies: - '@swc/helpers' - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.9))': + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.10))': dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 @@ -4237,7 +4259,7 @@ snapshots: std-env: 3.7.0 strip-literal: 2.0.0 test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@20.14.9) + vitest: 1.6.0(@types/node@20.14.10) transitivePeerDependencies: - supports-color @@ -4324,6 +4346,20 @@ snapshots: '@vue/compiler-core': 3.3.4 '@vue/shared': 3.3.4 + '@vue/language-core@1.8.27(typescript@5.2.2)': + dependencies: + '@volar/language-core': 1.11.1 + '@volar/source-map': 1.11.1 + '@vue/compiler-dom': 3.3.4 + '@vue/shared': 3.3.4 + computeds: 0.0.1 + minimatch: 9.0.5 + muggle-string: 0.3.1 + path-browserify: 1.0.1 + vue-template-compiler: 2.7.14 + optionalDependencies: + typescript: 5.2.2 + '@vue/language-core@1.8.27(typescript@5.5.3)': dependencies: '@volar/language-core': 1.11.1 @@ -4563,8 +4599,6 @@ snapshots: color-name@1.1.4: {} - colors@1.2.5: {} - commander@9.5.0: optional: true @@ -4655,7 +4689,7 @@ snapshots: diff-sequences@29.6.3: {} - diff@5.1.0: {} + diff@5.2.0: {} diffie-hellman@5.0.3: dependencies: @@ -4768,13 +4802,13 @@ snapshots: dependencies: eslint: 8.56.0 - eslint-plugin-vitest@0.4.1(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3)(vitest@1.6.0(@types/node@20.14.9)): + eslint-plugin-vitest@0.4.1(@typescript-eslint/eslint-plugin@7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3)(vitest@1.6.0(@types/node@20.14.10)): dependencies: '@typescript-eslint/utils': 7.14.1(eslint@8.56.0)(typescript@5.5.3) eslint: 8.56.0 optionalDependencies: '@typescript-eslint/eslint-plugin': 7.14.1(@typescript-eslint/parser@7.14.1(eslint@8.56.0)(typescript@5.5.3))(eslint@8.56.0)(typescript@5.5.3) - vitest: 1.6.0(@types/node@20.14.9) + vitest: 1.6.0(@types/node@20.14.10) transitivePeerDependencies: - supports-color - typescript @@ -5017,7 +5051,7 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.2: + glob@10.4.3: dependencies: foreground-child: 3.1.1 jackspeak: 3.4.0 @@ -5302,6 +5336,10 @@ snapshots: dependencies: sourcemap-codec: 1.4.8 + magic-string@0.30.10: + dependencies: + '@jridgewell/sourcemap-codec': 1.4.15 + magic-string@0.30.5: dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -5314,7 +5352,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.5.4 + semver: 7.6.2 markdown-it@14.1.0: dependencies: @@ -5362,6 +5400,10 @@ snapshots: minimalistic-crypto-utils@1.0.1: {} + minimatch@3.0.8: + dependencies: + brace-expansion: 1.1.11 + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11 @@ -5816,6 +5858,10 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} synckit@0.8.8: @@ -5896,7 +5942,7 @@ snapshots: typescript@5.2.2: {} - typescript@5.3.3: {} + typescript@5.4.2: {} typescript@5.5.3: {} @@ -5937,13 +5983,13 @@ snapshots: validator@13.11.0: {} - vite-node@1.3.1(@types/node@20.11.29): + vite-node@1.3.1(@types/node@20.14.10): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.1 picocolors: 1.0.0 - vite: 5.3.3(@types/node@20.11.29) + vite: 5.3.3(@types/node@20.14.10) transitivePeerDependencies: - '@types/node' - less @@ -5954,13 +6000,13 @@ snapshots: - supports-color - terser - vite-node@1.3.1(@types/node@20.14.9): + vite-node@1.6.0(@types/node@20.14.10): dependencies: cac: 6.7.14 debug: 4.3.4 pathe: 1.1.1 - picocolors: 1.0.0 - vite: 5.3.3(@types/node@20.14.9) + picocolors: 1.0.1 + vite: 5.3.3(@types/node@20.14.10) transitivePeerDependencies: - '@types/node' - less @@ -5971,100 +6017,83 @@ snapshots: - supports-color - terser - vite-node@1.6.0(@types/node@20.14.9): + vite-plugin-dts@3.9.1(@types/node@20.14.10)(rollup@4.18.0)(typescript@5.2.2)(vite@5.0.11(@types/node@20.14.10)): dependencies: - cac: 6.7.14 + '@microsoft/api-extractor': 7.43.0(@types/node@20.14.10) + '@rollup/pluginutils': 5.1.0(rollup@4.18.0) + '@vue/language-core': 1.8.27(typescript@5.2.2) debug: 4.3.4 - pathe: 1.1.1 - picocolors: 1.0.1 - vite: 5.3.3(@types/node@20.14.9) + kolorist: 1.8.0 + magic-string: 0.30.10 + typescript: 5.2.2 + vue-tsc: 1.8.27(typescript@5.2.2) + optionalDependencies: + vite: 5.0.11(@types/node@20.14.10) transitivePeerDependencies: - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss + - rollup - supports-color - - terser - vite-plugin-dts@3.7.3(@types/node@20.14.9)(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.9)): + vite-plugin-dts@3.9.1(@types/node@20.14.10)(rollup@4.18.0)(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)): dependencies: - '@microsoft/api-extractor': 7.39.0(@types/node@20.14.9) + '@microsoft/api-extractor': 7.43.0(@types/node@20.14.10) '@rollup/pluginutils': 5.1.0(rollup@4.18.0) '@vue/language-core': 1.8.27(typescript@5.5.3) debug: 4.3.4 kolorist: 1.8.0 + magic-string: 0.30.10 typescript: 5.5.3 vue-tsc: 1.8.27(typescript@5.5.3) optionalDependencies: - vite: 5.3.3(@types/node@20.14.9) + vite: 5.3.3(@types/node@20.14.10) transitivePeerDependencies: - '@types/node' - rollup - supports-color - vite-plugin-node-polyfills@0.22.0(rollup@4.18.0)(vite@5.0.11(@types/node@20.14.9)): + vite-plugin-node-polyfills@0.22.0(rollup@4.18.0)(vite@5.0.11(@types/node@20.14.10)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.18.0) node-stdlib-browser: 1.2.0 - vite: 5.0.11(@types/node@20.14.9) + vite: 5.0.11(@types/node@20.14.10) transitivePeerDependencies: - rollup - vite-plugin-node-polyfills@0.22.0(rollup@4.18.0)(vite@5.3.3(@types/node@20.14.9)): + vite-plugin-node-polyfills@0.22.0(rollup@4.18.0)(vite@5.3.3(@types/node@20.14.10)): dependencies: '@rollup/plugin-inject': 5.0.5(rollup@4.18.0) node-stdlib-browser: 1.2.0 - vite: 5.3.3(@types/node@20.14.9) + vite: 5.3.3(@types/node@20.14.10) transitivePeerDependencies: - rollup - vite-plugin-static-copy@1.0.6(vite@5.3.3(@types/node@20.14.9)): + vite-plugin-static-copy@1.0.6(vite@5.3.3(@types/node@20.14.10)): dependencies: chokidar: 3.5.3 fast-glob: 3.3.1 fs-extra: 11.2.0 picocolors: 1.0.1 - vite: 5.3.3(@types/node@20.14.9) + vite: 5.3.3(@types/node@20.14.10) - vite@5.0.11(@types/node@20.11.29): + vite@5.0.11(@types/node@20.14.10): dependencies: esbuild: 0.19.11 postcss: 8.4.33 rollup: 4.9.4 optionalDependencies: - '@types/node': 20.11.29 - fsevents: 2.3.3 - - vite@5.0.11(@types/node@20.14.9): - dependencies: - esbuild: 0.19.11 - postcss: 8.4.33 - rollup: 4.9.4 - optionalDependencies: - '@types/node': 20.14.9 - fsevents: 2.3.3 - - vite@5.3.3(@types/node@20.11.29): - dependencies: - esbuild: 0.21.5 - postcss: 8.4.39 - rollup: 4.18.0 - optionalDependencies: - '@types/node': 20.11.29 + '@types/node': 20.14.10 fsevents: 2.3.3 - vite@5.3.3(@types/node@20.14.9): + vite@5.3.3(@types/node@20.14.10): dependencies: esbuild: 0.21.5 postcss: 8.4.39 rollup: 4.18.0 optionalDependencies: - '@types/node': 20.14.9 + '@types/node': 20.14.10 fsevents: 2.3.3 - vitest@1.3.1(@types/node@20.11.29): + vitest@1.3.1(@types/node@20.14.10): dependencies: '@vitest/expect': 1.3.1 '@vitest/runner': 1.3.1 @@ -6083,11 +6112,11 @@ snapshots: strip-literal: 2.0.0 tinybench: 2.5.1 tinypool: 0.8.2 - vite: 5.3.3(@types/node@20.11.29) - vite-node: 1.3.1(@types/node@20.11.29) + vite: 5.3.3(@types/node@20.14.10) + vite-node: 1.3.1(@types/node@20.14.10) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.11.29 + '@types/node': 20.14.10 transitivePeerDependencies: - less - lightningcss @@ -6097,40 +6126,7 @@ snapshots: - supports-color - terser - vitest@1.3.1(@types/node@20.14.9): - dependencies: - '@vitest/expect': 1.3.1 - '@vitest/runner': 1.3.1 - '@vitest/snapshot': 1.3.1 - '@vitest/spy': 1.3.1 - '@vitest/utils': 1.3.1 - acorn-walk: 8.3.2 - chai: 4.4.0 - debug: 4.3.4 - execa: 8.0.1 - local-pkg: 0.5.0 - magic-string: 0.30.5 - pathe: 1.1.1 - picocolors: 1.0.0 - std-env: 3.7.0 - strip-literal: 2.0.0 - tinybench: 2.5.1 - tinypool: 0.8.2 - vite: 5.3.3(@types/node@20.14.9) - vite-node: 1.3.1(@types/node@20.14.9) - why-is-node-running: 2.2.2 - optionalDependencies: - '@types/node': 20.14.9 - transitivePeerDependencies: - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - vitest@1.6.0(@types/node@20.14.9): + vitest@1.6.0(@types/node@20.14.10): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -6149,11 +6145,11 @@ snapshots: strip-literal: 2.0.0 tinybench: 2.5.1 tinypool: 0.8.4 - vite: 5.3.3(@types/node@20.14.9) - vite-node: 1.6.0(@types/node@20.14.9) + vite: 5.3.3(@types/node@20.14.10) + vite-node: 1.6.0(@types/node@20.14.10) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.14.9 + '@types/node': 20.14.10 transitivePeerDependencies: - less - lightningcss @@ -6174,7 +6170,7 @@ snapshots: espree: 9.6.1 esquery: 1.5.0 lodash: 4.17.21 - semver: 7.5.4 + semver: 7.6.2 transitivePeerDependencies: - supports-color @@ -6183,11 +6179,18 @@ snapshots: de-indent: 1.0.2 he: 1.2.0 + vue-tsc@1.8.27(typescript@5.2.2): + dependencies: + '@volar/typescript': 1.11.1 + '@vue/language-core': 1.8.27(typescript@5.2.2) + semver: 7.6.2 + typescript: 5.2.2 + vue-tsc@1.8.27(typescript@5.5.3): dependencies: '@volar/typescript': 1.11.1 '@vue/language-core': 1.8.27(typescript@5.5.3) - semver: 7.5.4 + semver: 7.6.2 typescript: 5.5.3 which-typed-array@1.1.13: From 388253862bf9a71889c136d6fd6f8826a9ddece1 Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Wed, 10 Jul 2024 11:53:01 +0100 Subject: [PATCH 03/10] AG-12173 Allow processing the .gitignore file (#58) After the command npx @ag-grid-devtools/cli@32.0 migrate --from=31.2.1, error message Error: Untracked input files stop the process of migration. We can make it work by processing the .gitignore file NOTE: this is a breaking change, files specified in .gitignore will NOT be processed anymore when running the command in a directory, agreed with Stephen that this is however the right behaviour - to decide if we want to alert the users or not in the release info use "glob" and "ignore" to process correctly gitignore files recursively. update e2e tests to verify gitignore files are ignored as expected --- .eslintignore | 1 + package.json | 2 +- packages/cli/.eslintignore | 1 + packages/cli/.gitignore | 2 +- packages/cli/package.json | 2 + packages/cli/src/commands/migrate.ts | 55 +++--- packages/cli/src/test/cli.test.ts | 78 -------- packages/cli/src/test/e2e/e2e-test-utils.ts | 182 ++++++++++++++++++ ...gitignore-no-allow-untracked-error.test.ts | 29 +++ .../input-files/_.gitignore | 1 + .../input-files/ignored.js | 0 .../expected/folder/file.js | 8 + .../expected/folder/subfolder/gitignored.js | 8 + .../gitignore-no-allow-untracked.test.ts | 44 +++++ .../input-files/_.gitignore | 1 + .../input-files/folder/_.gitignore | 2 + .../input-files/folder/file.js | 8 + .../folder/subfolder/gitignored.js | 8 + .../folder/subfolder/ignore-this1.js | 8 + .../folder/subfolder/ignore-this2.js | 8 + .../input-files/gitignored.js | 10 + .../input-files/gitignored/file.js | 10 + .../gitignore-simple/expected/folder/file.js | 8 + .../gitignore-simple/gitignore-simple.test.ts | 27 +++ .../gitignore-simple/input-files/_.gitignore | 1 + .../input-files/folder/file.js | 8 + .../input-files/gitignored.js | 10 + .../input-files/gitignored/file.js | 10 + .../gitignore-uncommitted-error.test.ts | 30 +++ .../input-files/_.gitignore | 1 + .../input-files/ignored.js | 0 .../input-files/uncommitted.js | 0 .../test/e2e/multi-thread/expected/file1.js | 8 + .../test/e2e/multi-thread/expected/file2.js | 8 + .../test/e2e/multi-thread/expected/file3.js | 8 + .../e2e/multi-thread/input-files/file1.js | 8 + .../e2e/multi-thread/input-files/file2.js | 8 + .../e2e/multi-thread/input-files/file3.js | 8 + .../e2e/multi-thread/multi-thread.test.ts | 17 ++ .../test/e2e/single-thread/expected/file.js | 8 + .../e2e/single-thread/input-files/file.js | 8 + .../e2e/single-thread/single-thread.test.ts | 15 ++ .../expected/custom-imports1.js | 16 ++ .../expected/custom-imports2.js | 16 ++ .../expected/custom-imports3.js | 16 ++ .../input-files/custom-imports1.js | 16 ++ .../input-files/custom-imports2.js | 16 ++ .../input-files/custom-imports3.js | 16 ++ .../user-config-single-thread.test.ts | 32 +++ .../user-config-multi-thread/user-config.cts | 18 ++ .../expected/custom-imports.js | 16 ++ .../input-files/custom-imports.js | 16 ++ .../user-config-single-thread.test.ts | 26 +++ .../user-config-single-thread/user-config.cts | 18 ++ packages/cli/src/test/input-files/_.gitignore | 1 + .../input-files/gitignored-folder/file.js | 10 + .../cli/src/test/input-files/gitignored.js | 10 + packages/cli/src/test/test-utils.ts | 61 ------ packages/cli/src/utils/exec.ts | 8 +- packages/cli/src/utils/fs.ts | 137 ++++++++----- packages/cli/src/utils/git.ts | 5 - packages/cli/src/utils/path.ts | 1 - pnpm-lock.yaml | 62 +++--- 63 files changed, 929 insertions(+), 247 deletions(-) delete mode 100644 packages/cli/src/test/cli.test.ts create mode 100644 packages/cli/src/test/e2e/e2e-test-utils.ts create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/gitignore-no-allow-untracked-error.test.ts create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/input-files/_.gitignore create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/input-files/ignored.js create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/expected/folder/file.js create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/expected/folder/subfolder/gitignored.js create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/gitignore-no-allow-untracked.test.ts create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/_.gitignore create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/_.gitignore create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/file.js create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/gitignored.js create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/ignore-this1.js create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/ignore-this2.js create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/gitignored.js create mode 100644 packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/gitignored/file.js create mode 100644 packages/cli/src/test/e2e/gitignore-simple/expected/folder/file.js create mode 100644 packages/cli/src/test/e2e/gitignore-simple/gitignore-simple.test.ts create mode 100644 packages/cli/src/test/e2e/gitignore-simple/input-files/_.gitignore create mode 100644 packages/cli/src/test/e2e/gitignore-simple/input-files/folder/file.js create mode 100644 packages/cli/src/test/e2e/gitignore-simple/input-files/gitignored.js create mode 100644 packages/cli/src/test/e2e/gitignore-simple/input-files/gitignored/file.js create mode 100644 packages/cli/src/test/e2e/gitignore-uncommitted-error/gitignore-uncommitted-error.test.ts create mode 100644 packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/_.gitignore create mode 100644 packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/ignored.js create mode 100644 packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/uncommitted.js create mode 100644 packages/cli/src/test/e2e/multi-thread/expected/file1.js create mode 100644 packages/cli/src/test/e2e/multi-thread/expected/file2.js create mode 100644 packages/cli/src/test/e2e/multi-thread/expected/file3.js create mode 100644 packages/cli/src/test/e2e/multi-thread/input-files/file1.js create mode 100644 packages/cli/src/test/e2e/multi-thread/input-files/file2.js create mode 100644 packages/cli/src/test/e2e/multi-thread/input-files/file3.js create mode 100644 packages/cli/src/test/e2e/multi-thread/multi-thread.test.ts create mode 100644 packages/cli/src/test/e2e/single-thread/expected/file.js create mode 100644 packages/cli/src/test/e2e/single-thread/input-files/file.js create mode 100644 packages/cli/src/test/e2e/single-thread/single-thread.test.ts create mode 100644 packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports1.js create mode 100644 packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports2.js create mode 100644 packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports3.js create mode 100644 packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports1.js create mode 100644 packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports2.js create mode 100644 packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports3.js create mode 100644 packages/cli/src/test/e2e/user-config-multi-thread/user-config-single-thread.test.ts create mode 100644 packages/cli/src/test/e2e/user-config-multi-thread/user-config.cts create mode 100644 packages/cli/src/test/e2e/user-config-single-thread/expected/custom-imports.js create mode 100644 packages/cli/src/test/e2e/user-config-single-thread/input-files/custom-imports.js create mode 100644 packages/cli/src/test/e2e/user-config-single-thread/user-config-single-thread.test.ts create mode 100644 packages/cli/src/test/e2e/user-config-single-thread/user-config.cts create mode 100644 packages/cli/src/test/input-files/_.gitignore create mode 100644 packages/cli/src/test/input-files/gitignored-folder/file.js create mode 100644 packages/cli/src/test/input-files/gitignored.js delete mode 100644 packages/cli/src/test/test-utils.ts delete mode 100644 packages/cli/src/utils/path.ts diff --git a/.eslintignore b/.eslintignore index 49d91ec5..b764b083 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ /coverage/ /docs/ /packages/ +_temp \ No newline at end of file diff --git a/package.json b/package.json index ba53a745..7c2c4d7d 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@babel/traverse": "7.23.2", "@babel/types": "7.23.0", "@types/glob": "8.1.0", - "glob": "10.4.3", + "glob": "11.0.0", "prettier": "3.3.2", "typedoc": "^0.26.3", "typescript": "5.5.3", diff --git a/packages/cli/.eslintignore b/packages/cli/.eslintignore index 285b83b4..f3b033a1 100644 --- a/packages/cli/.eslintignore +++ b/packages/cli/.eslintignore @@ -1,2 +1,3 @@ /dist/ /vitest.config.mts.timestamp-*.mjs +_temp \ No newline at end of file diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore index 20c0b017..50899170 100644 --- a/packages/cli/.gitignore +++ b/packages/cli/.gitignore @@ -1,2 +1,2 @@ -_temp/ +_temp /dist/ diff --git a/packages/cli/package.json b/packages/cli/package.json index 40d5c222..7f03c8c5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -54,7 +54,9 @@ "@types/node": "20.14.10", "@types/semver": "7.5.8", "diff": "5.2.0", + "glob": "11.0.0", "graceful-fs": "4.2.11", + "ignore": "5.3.1", "semver": "7.6.2", "vite-plugin-dts": "3.9.1" }, diff --git a/packages/cli/src/commands/migrate.ts b/packages/cli/src/commands/migrate.ts index 041f7aba..555b87d1 100644 --- a/packages/cli/src/commands/migrate.ts +++ b/packages/cli/src/commands/migrate.ts @@ -9,7 +9,6 @@ import { CodemodTaskInput, CodemodTaskWorkerResult, TaskRunnerEnvironment, - UserConfig, type VersionManifest, } from '@ag-grid-devtools/types'; import { createFsHelpers } from '@ag-grid-devtools/worker-utils'; @@ -22,9 +21,9 @@ import semver from 'semver'; import { type CliEnv, type CliOptions } from '../types/cli'; import { type WritableStream } from '../types/io'; import { CliArgsError, CliError } from '../utils/cli'; -import { findInDirectory } from '../utils/fs'; -import { findInGitRepository, getGitProjectRoot, getUncommittedGitFiles } from '../utils/git'; -import { basename, extname, resolve, relative } from '../utils/path'; +import { findSourceFiles, findGitRoot } from '../utils/fs'; +import { findInGitRepository, getUncommittedGitFiles } from '../utils/git'; +import { resolve, relative } from 'node:path'; import { getCliCommand, getCliPackageVersion } from '../utils/pkg'; import { green, indentErrorMessage, log } from '../utils/stdio'; import { Worker, WorkerTaskQueue, type WorkerOptions } from '../utils/worker'; @@ -105,7 +104,8 @@ Options: See https://ag-grid.com/javascript-data-grid/codemods/#configuration-file Additional arguments: - [...] List of input files to operate on (defaults to all source files in the current working directory) + [...] List of input files to operate on. + Defaults to all source files in the current working directory excluding patterns in .gitignore Other options: --verbose, -v Show additional log output @@ -266,32 +266,38 @@ async function migrate( userConfigPath, input, } = args; - const { cwd, env, stdio } = options; + let { cwd, env, stdio } = options; const { stdout, stderr } = stdio; - const gitProjectRoot = await getGitProjectRoot(cwd); + cwd = resolve(cwd); - if (!allowUntracked && !gitProjectRoot) { + const gitRoot = await findGitRoot(cwd); + + if (!allowUntracked && !gitRoot) { throw new CliError( 'No git repository found', 'To run this command outside a git repository, use the --allow-untracked option', ); } - const gitSourceFilePaths = gitProjectRoot - ? (await getGitSourceFiles(gitProjectRoot)).map((path) => resolve(gitProjectRoot, path)) + const gitSourceFilePaths = gitRoot + ? (await getGitSourceFiles(gitRoot)).map((path) => resolve(gitRoot, path)) : null; - const inputFilePaths = - input.length > 0 - ? input.map((path) => resolve(cwd, path)) - : (await getProjectSourceFiles(cwd)).map((path) => resolve(cwd, path)); + let inputFilePaths: string[]; + + if (input.length > 0) { + inputFilePaths = input.map((path) => resolve(cwd, path)); + } else { + inputFilePaths = await findSourceFiles(cwd, SOURCE_FILE_EXTENSIONS, gitRoot); + } if (!allowUntracked) { const trackedFilePaths = gitSourceFilePaths ? new Set(gitSourceFilePaths) : null; - const untrackedInputFiles = trackedFilePaths + let untrackedInputFiles = trackedFilePaths ? inputFilePaths.filter((path) => !trackedFilePaths.has(path)) : inputFilePaths; + if (untrackedInputFiles.length > 0) throw new CliError( 'Untracked input files', @@ -303,10 +309,10 @@ async function migrate( ); } - if (gitProjectRoot && !allowDirty) { + if (gitRoot && !allowDirty) { const inputFileSet = new Set(inputFilePaths); - const uncommittedInputFiles = (await getUncommittedGitFiles(gitProjectRoot)) - .map((path) => resolve(gitProjectRoot, path)) + const uncommittedInputFiles = (await getUncommittedGitFiles(gitRoot)) + .map((path) => resolve(gitRoot, path)) .filter((path) => inputFileSet.has(path)); if (uncommittedInputFiles.length > 0) { throw new CliError( @@ -628,15 +634,6 @@ function formatFileErrors(warningResults: Array<{ path: string; errors: Error[] .join('\n'); } -function getProjectSourceFiles(projectRoot: string): Promise> { - return findInDirectory( - projectRoot, - (filePath, stats) => - (stats.isDirectory() && basename(filePath) !== 'node_modules') || - (stats.isFile() && isSourceFile(filePath)), - ); -} - function getGitSourceFiles(projectRoot: string): Promise> { return findInGitRepository( SOURCE_FILE_EXTENSIONS.map((extension) => `*${extension}`), @@ -646,10 +643,6 @@ function getGitSourceFiles(projectRoot: string): Promise> { ); } -function isSourceFile(filePath: string): boolean { - return SOURCE_FILE_EXTENSIONS.includes(extname(filePath)); -} - function getMinorSemverVersion(version: string): string | null { const parsed = semver.parse(version); if (!parsed) return null; diff --git a/packages/cli/src/test/cli.test.ts b/packages/cli/src/test/cli.test.ts deleted file mode 100644 index c4465437..00000000 --- a/packages/cli/src/test/cli.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { beforeAll, expect, describe, test } from 'vitest'; -import { cli } from '../cli'; -import { - TEMP_FOLDER, - loadExpectedSource, - loadTempSource, - patchDynamicRequire, - prepareTestDataFiles, -} from './test-utils'; -import { CliOptions } from '../types/cli'; - -describe('cli e2e', () => { - beforeAll(() => { - patchDynamicRequire(); - }); - - const cliOptions: CliOptions = { - cwd: TEMP_FOLDER, - env: { - cwd: TEMP_FOLDER, - }, - stdio: { - stdin: process.stdin, - stdout: process.stdout, - stderr: process.stderr, - }, - }; - - test('plain cli single threaded', async () => { - await prepareTestDataFiles(); - await cli(['migrate', '--num-threads=0', '--allow-untracked', '--from=30.0.0'], cliOptions); - expect(await loadExpectedSource('plain.js')).toEqual(await loadTempSource('plain.js')); - }, 10000); - - test('plain cli multi-threaded', async () => { - await prepareTestDataFiles(); - await cli(['migrate', '--num-threads=0', '--allow-untracked', '--from=30.0.0'], cliOptions); - expect(await loadExpectedSource('plain.js')).toEqual(await loadTempSource('plain.js')); - }, 10000); - - test('userConfig single-threaded', async () => { - await prepareTestDataFiles(); - - await cli( - [ - 'migrate', - '--num-threads=0', - '--allow-untracked', - '--from=30.0.0', - '--config=../user-config.cts', - ], - cliOptions, - ); - - expect(await loadExpectedSource('custom-imports.js')).toEqual( - await loadTempSource('custom-imports.js'), - ); - }, 10000); - - test('userConfig multi-threaded', async () => { - await prepareTestDataFiles(); - - await cli( - [ - 'migrate', - '--num-threads=2', - '--allow-untracked', - '--from=30.0.0', - '--config=../user-config.cts', - ], - cliOptions, - ); - - expect(await loadExpectedSource('custom-imports.js')).toEqual( - await loadTempSource('custom-imports.js'), - ); - }, 10000); -}); diff --git a/packages/cli/src/test/e2e/e2e-test-utils.ts b/packages/cli/src/test/e2e/e2e-test-utils.ts new file mode 100644 index 00000000..a3bd5a64 --- /dev/null +++ b/packages/cli/src/test/e2e/e2e-test-utils.ts @@ -0,0 +1,182 @@ +import { readFile, cp, mkdir, rm, writeFile } from 'fs/promises'; +import { fileURLToPath } from 'url'; +import path from 'path'; +import { dynamicRequire } from '@ag-grid-devtools/utils'; +import prettier from 'prettier'; +import { CliOptions } from '../../types/cli'; +import { execCommand } from '../../utils/exec'; +import { glob } from 'glob'; +import { existsSync } from 'fs'; + +export class CliE2ETestEnv { + public readonly TIMEOUT = 20000; + + public readonly rootFolder: string; + + public readonly tempFolder: string; + + public readonly inputFolder: string; + + public readonly expectedFolder: string; + + public cliOptions: CliOptions; + + public constructor(public readonly metaUrl: string) { + this.rootFolder = path.resolve(path.dirname(fileURLToPath(metaUrl))); + this.tempFolder = path.resolve(this.rootFolder, '_temp'); + this.inputFolder = path.resolve(this.rootFolder, 'input-files'); + this.expectedFolder = path.resolve(this.rootFolder, 'expected'); + + this.cliOptions = { + cwd: this.tempFolder, + env: { + cwd: this.tempFolder, + }, + stdio: { + stdin: process.stdin, + stdout: process.stdout, + stderr: process.stderr, + }, + }; + } + + public async teardown() { + try { + await rm(this.tempFolder, { recursive: true }); + } catch { + // already deleted + } + } + + public async removeGitFolder() { + try { + await rm(path.join(this.tempFolder, '.git'), { recursive: true }); + } catch { + // already deleted + } + } + + public async execCommand(command: string, args: string[], options?: { cwd?: string }) { + try { + return await execCommand(command, args, { cwd: this.tempFolder, ...options }); + } catch (error) { + console.error(error); + throw error; + } + } + + public async init({ gitInit = false }: { gitInit?: boolean } = {}) { + patchDynamicRequire(); + + await this.teardown(); + + if (gitInit) { + await mkdir(this.tempFolder, { recursive: true }); + + await this.execCommand('git', ['init']); + } else { + // create a .git directory to simulate a git repository + await mkdir(path.join(this.tempFolder, '.git'), { recursive: true }); + } + + const inputPath = this.inputFolder; + + const promises: Promise[] = []; + + if (existsSync(inputPath)) { + // copy all files from the input folder to the temp folder + promises.push( + cp(inputPath, this.tempFolder, { + recursive: true, + force: true, + filter: (src) => !path.basename(src).startsWith('_'), + }), + ); + + // copy all files starting with _ removing the _ from the name + + const allFiles = glob.sync('**/_*', { cwd: inputPath, nodir: true, absolute: true }); + for (const file of allFiles) { + promises.push( + mkdir(path.dirname(file), { recursive: true }).then(() => { + const targetFolder = path.resolve( + this.tempFolder, + path.relative(inputPath, path.dirname(file)), + ); + const name = path.basename(file).slice(1); + cp(file, path.resolve(this.tempFolder, targetFolder, name), { force: true }); + }), + ); + } + } + + await Promise.all(promises); + + if (gitInit) { + await this.execCommand('git', ['add', '.']); + + await this.execCommand('git', ['config', 'user.email', 'cli-e2e@ag-grid.com']); + await this.execCommand('git', ['config', 'user.name', 'cli-e2e']); + + await this.execCommand('git', ['commit', '-m', 'Initial commit']); + } + + return this; + } + + public async loadInputSrc(name: string) { + return loadSourceFile(path.resolve(this.tempFolder, name)); + } + + public async loadExpectedSrc(name: string) { + return loadSourceFile(path.resolve(this.expectedFolder, name)); + } + + public async loadTempSrc(name: string) { + return loadSourceFile(path.resolve(this.tempFolder, name)); + } + + public async writeTempSrc(filename: string, content: string) { + await mkdir(path.dirname(path.resolve(this.tempFolder, filename)), { recursive: true }); + await writeFile(path.resolve(this.tempFolder, filename), content); + } + + public async addGitFile(filename: string) { + await this.execCommand('git', ['add', filename]); + } +} + +async function loadSourceFile(filepath: string): Promise { + return prettier.format(await readFile(filepath, 'utf-8'), { filepath }); +} + +let _dynamicRequirePatched = false; + +export function patchDynamicRequire() { + if (_dynamicRequirePatched) { + return; + } + _dynamicRequirePatched = true; + + /** Fixes the path of an import for typescript, as we are using those with worker threads */ + const fixPath = (p: string): string => { + if (p === '@ag-grid-devtools/codemods/worker') { + return '@ag-grid-devtools/codemods/src/worker.ts'; + } + + if (p.startsWith('@ag-grid-devtools/codemods/version/')) { + p = + '@ag-grid-devtools/codemods/src/versions/' + + p.slice('@ag-grid-devtools/codemods/version/'.length) + + '/codemod.ts'; + } + + return p; + }; + + const oldRequire = dynamicRequire.require; + dynamicRequire.require = (path: string, meta: ImportMeta) => oldRequire(fixPath(path), meta); + + const oldResolve = dynamicRequire.resolve; + dynamicRequire.resolve = (path: string, meta: ImportMeta) => oldResolve(fixPath(path), meta); +} diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/gitignore-no-allow-untracked-error.test.ts b/packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/gitignore-no-allow-untracked-error.test.ts new file mode 100644 index 00000000..0ecd5bcc --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/gitignore-no-allow-untracked-error.test.ts @@ -0,0 +1,29 @@ +import { expect, test } from 'vitest'; +import { cli } from '../../../cli'; +import { CliE2ETestEnv } from '../e2e-test-utils'; + +const env = new CliE2ETestEnv(import.meta.url); + +test( + 'cli e2e - gitignore no allow untracked error', + async () => { + let error: any; + try { + await env.init({ gitInit: true }); + + await env.writeTempSrc('untracked.js', '// untracked'); + + await cli(['migrate', '--from=30.0.0'], env.cliOptions); + } catch (e) { + error = e as Error; + } finally { + await env.removeGitFolder(); + } + + expect(error).instanceOf(Error); + expect(error.message).toMatch('Untracked input files'); + expect(error.info).toMatch('untracked.js'); + expect(error.info).not.toMatch('ignored.js'); + }, + env.TIMEOUT, +); diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/input-files/_.gitignore b/packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/input-files/_.gitignore new file mode 100644 index 00000000..a860e155 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/input-files/_.gitignore @@ -0,0 +1 @@ +ignored.js \ No newline at end of file diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/input-files/ignored.js b/packages/cli/src/test/e2e/gitignore-no-allow-untracked-error/input-files/ignored.js new file mode 100644 index 00000000..e69de29b diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/expected/folder/file.js b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/expected/folder/file.js new file mode 100644 index 00000000..2fa33ba4 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/expected/folder/file.js @@ -0,0 +1,8 @@ +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/expected/folder/subfolder/gitignored.js b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/expected/folder/subfolder/gitignored.js new file mode 100644 index 00000000..2fa33ba4 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/expected/folder/subfolder/gitignored.js @@ -0,0 +1,8 @@ +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/gitignore-no-allow-untracked.test.ts b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/gitignore-no-allow-untracked.test.ts new file mode 100644 index 00000000..f61a217d --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/gitignore-no-allow-untracked.test.ts @@ -0,0 +1,44 @@ +import { expect, test } from 'vitest'; +import { cli } from '../../../cli'; +import { CliE2ETestEnv } from '../e2e-test-utils'; + +const env = new CliE2ETestEnv(import.meta.url); + +test( + 'cli e2e - gitignore no allow untracked', + async () => { + try { + await env.init({ gitInit: true }); + await cli(['migrate', '--from=30.0.0'], env.cliOptions); + } finally { + await env.removeGitFolder(); + } + + // changed files + + expect(await env.loadExpectedSrc('folder/file.js')).toEqual( + await env.loadTempSrc('folder/file.js'), + ); + + expect(await env.loadExpectedSrc('folder/subfolder/gitignored.js')).toEqual( + await env.loadTempSrc('folder/subfolder/gitignored.js'), + ); + + // unchanged files + + expect(await env.loadInputSrc('gitignored.js')).toEqual(await env.loadTempSrc('gitignored.js')); + + expect(await env.loadInputSrc('gitignored/file.js')).toEqual( + await env.loadTempSrc('gitignored/file.js'), + ); + + expect(await env.loadInputSrc('folder/subfolder/ignore-this1.js')).toEqual( + await env.loadTempSrc('folder/subfolder/ignore-this1.js'), + ); + + expect(await env.loadInputSrc('folder/subfolder/ignore-this2.js')).toEqual( + await env.loadTempSrc('folder/subfolder/ignore-this2.js'), + ); + }, + env.TIMEOUT, +); diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/_.gitignore b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/_.gitignore new file mode 100644 index 00000000..a42ef604 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/_.gitignore @@ -0,0 +1 @@ +gitignored* \ No newline at end of file diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/_.gitignore b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/_.gitignore new file mode 100644 index 00000000..26229650 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/_.gitignore @@ -0,0 +1,2 @@ +ignorethis2.js +!gitignored.js \ No newline at end of file diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/file.js b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/file.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/file.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/gitignored.js b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/gitignored.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/gitignored.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/ignore-this1.js b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/ignore-this1.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/ignore-this1.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/ignore-this2.js b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/ignore-this2.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/folder/subfolder/ignore-this2.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/gitignored.js b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/gitignored.js new file mode 100644 index 00000000..e113de0f --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/gitignored.js @@ -0,0 +1,10 @@ +// This file will be ignored as is present in .gitignore + +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/gitignored/file.js b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/gitignored/file.js new file mode 100644 index 00000000..e113de0f --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-no-allow-untracked/input-files/gitignored/file.js @@ -0,0 +1,10 @@ +// This file will be ignored as is present in .gitignore + +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-simple/expected/folder/file.js b/packages/cli/src/test/e2e/gitignore-simple/expected/folder/file.js new file mode 100644 index 00000000..2fa33ba4 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-simple/expected/folder/file.js @@ -0,0 +1,8 @@ +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-simple/gitignore-simple.test.ts b/packages/cli/src/test/e2e/gitignore-simple/gitignore-simple.test.ts new file mode 100644 index 00000000..75f4b935 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-simple/gitignore-simple.test.ts @@ -0,0 +1,27 @@ +import { expect, test } from 'vitest'; +import { cli } from '../../../cli'; +import { CliE2ETestEnv } from '../e2e-test-utils'; + +const env = new CliE2ETestEnv(import.meta.url); + +test( + 'cli e2e - gitignore simple', + async () => { + await env.init(); + await cli(['migrate', '--allow-untracked', '--from=30.0.0'], env.cliOptions); + + // changed files + + expect(await env.loadExpectedSrc('folder/file.js')).toEqual( + await env.loadTempSrc('folder/file.js'), + ); + + // unchanged files + + expect(await env.loadInputSrc('gitignored.js')).toEqual(await env.loadTempSrc('gitignored.js')); + expect(await env.loadInputSrc('gitignored/file.js')).toEqual( + await env.loadTempSrc('gitignored/file.js'), + ); + }, + env.TIMEOUT, +); diff --git a/packages/cli/src/test/e2e/gitignore-simple/input-files/_.gitignore b/packages/cli/src/test/e2e/gitignore-simple/input-files/_.gitignore new file mode 100644 index 00000000..a42ef604 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-simple/input-files/_.gitignore @@ -0,0 +1 @@ +gitignored* \ No newline at end of file diff --git a/packages/cli/src/test/e2e/gitignore-simple/input-files/folder/file.js b/packages/cli/src/test/e2e/gitignore-simple/input-files/folder/file.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-simple/input-files/folder/file.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-simple/input-files/gitignored.js b/packages/cli/src/test/e2e/gitignore-simple/input-files/gitignored.js new file mode 100644 index 00000000..e113de0f --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-simple/input-files/gitignored.js @@ -0,0 +1,10 @@ +// This file will be ignored as is present in .gitignore + +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-simple/input-files/gitignored/file.js b/packages/cli/src/test/e2e/gitignore-simple/input-files/gitignored/file.js new file mode 100644 index 00000000..e113de0f --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-simple/input-files/gitignored/file.js @@ -0,0 +1,10 @@ +// This file will be ignored as is present in .gitignore + +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/gitignore-uncommitted-error/gitignore-uncommitted-error.test.ts b/packages/cli/src/test/e2e/gitignore-uncommitted-error/gitignore-uncommitted-error.test.ts new file mode 100644 index 00000000..a157973a --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-uncommitted-error/gitignore-uncommitted-error.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'vitest'; +import { cli } from '../../../cli'; +import { CliE2ETestEnv } from '../e2e-test-utils'; + +const env = new CliE2ETestEnv(import.meta.url); + +test( + 'cli e2e - gitignore uncommitted error', + async () => { + let error: any; + try { + await env.init({ gitInit: true }); + + await env.writeTempSrc('uncommitted.js', `// uncommitted`); + await env.addGitFile('uncommitted.js'); + + await cli(['migrate', '--from=30.0.0'], env.cliOptions); + } catch (e) { + error = e as Error; + } finally { + await env.removeGitFolder(); + } + + expect(error).instanceOf(Error); + expect(error.message).toMatch('Uncommitted changes'); + expect(error.info).toMatch('uncommitted.js'); + expect(error.info).not.toMatch('ignored.js'); + }, + env.TIMEOUT, +); diff --git a/packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/_.gitignore b/packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/_.gitignore new file mode 100644 index 00000000..a860e155 --- /dev/null +++ b/packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/_.gitignore @@ -0,0 +1 @@ +ignored.js \ No newline at end of file diff --git a/packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/ignored.js b/packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/ignored.js new file mode 100644 index 00000000..e69de29b diff --git a/packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/uncommitted.js b/packages/cli/src/test/e2e/gitignore-uncommitted-error/input-files/uncommitted.js new file mode 100644 index 00000000..e69de29b diff --git a/packages/cli/src/test/e2e/multi-thread/expected/file1.js b/packages/cli/src/test/e2e/multi-thread/expected/file1.js new file mode 100644 index 00000000..2fa33ba4 --- /dev/null +++ b/packages/cli/src/test/e2e/multi-thread/expected/file1.js @@ -0,0 +1,8 @@ +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/multi-thread/expected/file2.js b/packages/cli/src/test/e2e/multi-thread/expected/file2.js new file mode 100644 index 00000000..2fa33ba4 --- /dev/null +++ b/packages/cli/src/test/e2e/multi-thread/expected/file2.js @@ -0,0 +1,8 @@ +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/multi-thread/expected/file3.js b/packages/cli/src/test/e2e/multi-thread/expected/file3.js new file mode 100644 index 00000000..2fa33ba4 --- /dev/null +++ b/packages/cli/src/test/e2e/multi-thread/expected/file3.js @@ -0,0 +1,8 @@ +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/multi-thread/input-files/file1.js b/packages/cli/src/test/e2e/multi-thread/input-files/file1.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/e2e/multi-thread/input-files/file1.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/multi-thread/input-files/file2.js b/packages/cli/src/test/e2e/multi-thread/input-files/file2.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/e2e/multi-thread/input-files/file2.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/multi-thread/input-files/file3.js b/packages/cli/src/test/e2e/multi-thread/input-files/file3.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/e2e/multi-thread/input-files/file3.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/multi-thread/multi-thread.test.ts b/packages/cli/src/test/e2e/multi-thread/multi-thread.test.ts new file mode 100644 index 00000000..83a55998 --- /dev/null +++ b/packages/cli/src/test/e2e/multi-thread/multi-thread.test.ts @@ -0,0 +1,17 @@ +import { expect, test } from 'vitest'; +import { cli } from '../../../cli'; +import { CliE2ETestEnv } from '../e2e-test-utils'; + +const env = new CliE2ETestEnv(import.meta.url); + +test( + 'cli e2e - multi thread', + async () => { + await env.init(); + await cli(['migrate', '--num-threads=3', '--allow-untracked', '--from=30.0.0'], env.cliOptions); + expect(await env.loadExpectedSrc('file1.js')).toEqual(await env.loadTempSrc('file1.js')); + expect(await env.loadExpectedSrc('file2.js')).toEqual(await env.loadTempSrc('file2.js')); + expect(await env.loadExpectedSrc('file3.js')).toEqual(await env.loadTempSrc('file3.js')); + }, + env.TIMEOUT, +); diff --git a/packages/cli/src/test/e2e/single-thread/expected/file.js b/packages/cli/src/test/e2e/single-thread/expected/file.js new file mode 100644 index 00000000..2fa33ba4 --- /dev/null +++ b/packages/cli/src/test/e2e/single-thread/expected/file.js @@ -0,0 +1,8 @@ +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/single-thread/input-files/file.js b/packages/cli/src/test/e2e/single-thread/input-files/file.js new file mode 100644 index 00000000..a508c728 --- /dev/null +++ b/packages/cli/src/test/e2e/single-thread/input-files/file.js @@ -0,0 +1,8 @@ +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/single-thread/single-thread.test.ts b/packages/cli/src/test/e2e/single-thread/single-thread.test.ts new file mode 100644 index 00000000..cbda5709 --- /dev/null +++ b/packages/cli/src/test/e2e/single-thread/single-thread.test.ts @@ -0,0 +1,15 @@ +import { expect, test } from 'vitest'; +import { cli } from '../../../cli'; +import { CliE2ETestEnv } from '../e2e-test-utils'; + +const env = new CliE2ETestEnv(import.meta.url); + +test( + 'cli e2e - single thread', + async () => { + await env.init(); + await cli(['migrate', '--num-threads=0', '--allow-untracked', '--from=30.0.0'], env.cliOptions); + expect(await env.loadExpectedSrc('file.js')).toEqual(await env.loadTempSrc('file.js')); + }, + env.TIMEOUT, +); diff --git a/packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports1.js b/packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports1.js new file mode 100644 index 00000000..85a405f8 --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports1.js @@ -0,0 +1,16 @@ +import { myCreateGrid } from '@hello/world'; +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = myCreateGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports2.js b/packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports2.js new file mode 100644 index 00000000..85a405f8 --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports2.js @@ -0,0 +1,16 @@ +import { myCreateGrid } from '@hello/world'; +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = myCreateGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports3.js b/packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports3.js new file mode 100644 index 00000000..85a405f8 --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-multi-thread/expected/custom-imports3.js @@ -0,0 +1,16 @@ +import { myCreateGrid } from '@hello/world'; +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = myCreateGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports1.js b/packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports1.js new file mode 100644 index 00000000..164f849d --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports1.js @@ -0,0 +1,16 @@ +import { MyGrid as CustomGrid } from '@hello/world'; +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new CustomGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports2.js b/packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports2.js new file mode 100644 index 00000000..164f849d --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports2.js @@ -0,0 +1,16 @@ +import { MyGrid as CustomGrid } from '@hello/world'; +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new CustomGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports3.js b/packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports3.js new file mode 100644 index 00000000..164f849d --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-multi-thread/input-files/custom-imports3.js @@ -0,0 +1,16 @@ +import { MyGrid as CustomGrid } from '@hello/world'; +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new CustomGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/user-config-multi-thread/user-config-single-thread.test.ts b/packages/cli/src/test/e2e/user-config-multi-thread/user-config-single-thread.test.ts new file mode 100644 index 00000000..0ca182d4 --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-multi-thread/user-config-single-thread.test.ts @@ -0,0 +1,32 @@ +import { expect, test } from 'vitest'; +import { cli } from '../../../cli'; +import { CliE2ETestEnv } from '../e2e-test-utils'; + +const env = new CliE2ETestEnv(import.meta.url); + +test( + 'cli e2e - user config multi thread', + async () => { + await env.init(); + await cli( + [ + 'migrate', + '--num-threads=4', + '--allow-untracked', + '--from=30.0.0', + '--config=../user-config.cts', + ], + env.cliOptions, + ); + expect(await env.loadExpectedSrc('custom-imports1.js')).toEqual( + await env.loadTempSrc('custom-imports1.js'), + ); + expect(await env.loadExpectedSrc('custom-imports2.js')).toEqual( + await env.loadTempSrc('custom-imports2.js'), + ); + expect(await env.loadExpectedSrc('custom-imports2.js')).toEqual( + await env.loadTempSrc('custom-imports3.js'), + ); + }, + env.TIMEOUT, +); diff --git a/packages/cli/src/test/e2e/user-config-multi-thread/user-config.cts b/packages/cli/src/test/e2e/user-config-multi-thread/user-config.cts new file mode 100644 index 00000000..cb06f22e --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-multi-thread/user-config.cts @@ -0,0 +1,18 @@ +import { defineUserConfig } from '../../../../user-config'; + +module.exports = defineUserConfig({ + getCreateGridName() { + return 'myCreateGrid'; + }, + + matchGridImport({ importPath: importedModule }) { + return importedModule === '@hello/world'; + }, + + matchGridImportName({ importName, agGridExportName }) { + if (agGridExportName === 'Grid') { + return importName === 'MyGrid'; + } + return agGridExportName === agGridExportName; + }, +}); diff --git a/packages/cli/src/test/e2e/user-config-single-thread/expected/custom-imports.js b/packages/cli/src/test/e2e/user-config-single-thread/expected/custom-imports.js new file mode 100644 index 00000000..85a405f8 --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-single-thread/expected/custom-imports.js @@ -0,0 +1,16 @@ +import { myCreateGrid } from '@hello/world'; +import { createGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = myCreateGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + const gridApi = createGrid(document.getQuerySelector('main'), gridOptions); + gridApi.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/user-config-single-thread/input-files/custom-imports.js b/packages/cli/src/test/e2e/user-config-single-thread/input-files/custom-imports.js new file mode 100644 index 00000000..164f849d --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-single-thread/input-files/custom-imports.js @@ -0,0 +1,16 @@ +import { MyGrid as CustomGrid } from '@hello/world'; +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new CustomGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/e2e/user-config-single-thread/user-config-single-thread.test.ts b/packages/cli/src/test/e2e/user-config-single-thread/user-config-single-thread.test.ts new file mode 100644 index 00000000..041bb883 --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-single-thread/user-config-single-thread.test.ts @@ -0,0 +1,26 @@ +import { expect, test } from 'vitest'; +import { cli } from '../../../cli'; +import { CliE2ETestEnv } from '../e2e-test-utils'; + +const env = new CliE2ETestEnv(import.meta.url); + +test( + 'cli e2e - user config single thread', + async () => { + await env.init(); + await cli( + [ + 'migrate', + '--num-threads=0', + '--allow-untracked', + '--from=30.0.0', + '--config=../user-config.cts', + ], + env.cliOptions, + ); + expect(await env.loadExpectedSrc('custom-imports.js')).toEqual( + await env.loadTempSrc('custom-imports.js'), + ); + }, + env.TIMEOUT, +); diff --git a/packages/cli/src/test/e2e/user-config-single-thread/user-config.cts b/packages/cli/src/test/e2e/user-config-single-thread/user-config.cts new file mode 100644 index 00000000..cb06f22e --- /dev/null +++ b/packages/cli/src/test/e2e/user-config-single-thread/user-config.cts @@ -0,0 +1,18 @@ +import { defineUserConfig } from '../../../../user-config'; + +module.exports = defineUserConfig({ + getCreateGridName() { + return 'myCreateGrid'; + }, + + matchGridImport({ importPath: importedModule }) { + return importedModule === '@hello/world'; + }, + + matchGridImportName({ importName, agGridExportName }) { + if (agGridExportName === 'Grid') { + return importName === 'MyGrid'; + } + return agGridExportName === agGridExportName; + }, +}); diff --git a/packages/cli/src/test/input-files/_.gitignore b/packages/cli/src/test/input-files/_.gitignore new file mode 100644 index 00000000..a42ef604 --- /dev/null +++ b/packages/cli/src/test/input-files/_.gitignore @@ -0,0 +1 @@ +gitignored* \ No newline at end of file diff --git a/packages/cli/src/test/input-files/gitignored-folder/file.js b/packages/cli/src/test/input-files/gitignored-folder/file.js new file mode 100644 index 00000000..e113de0f --- /dev/null +++ b/packages/cli/src/test/input-files/gitignored-folder/file.js @@ -0,0 +1,10 @@ +// This file will be ignored as is present in .gitignore + +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/input-files/gitignored.js b/packages/cli/src/test/input-files/gitignored.js new file mode 100644 index 00000000..e113de0f --- /dev/null +++ b/packages/cli/src/test/input-files/gitignored.js @@ -0,0 +1,10 @@ +// This file will be ignored as is present in .gitignore + +import { Grid as AgGrid } from '@ag-grid-community/core'; + +(() => { + const gridOptions = { foo: 'bar' }; + gridOptions.baz = 3; + new AgGrid(document.getQuerySelector('main'), gridOptions); + gridOptions.api.sizeColumnsToFit(); +})(); diff --git a/packages/cli/src/test/test-utils.ts b/packages/cli/src/test/test-utils.ts deleted file mode 100644 index 7286dc2d..00000000 --- a/packages/cli/src/test/test-utils.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { readFile, cp, mkdir, rmdir } from 'fs/promises'; -import { fileURLToPath } from 'url'; -import path from 'path'; -import { dynamicRequire } from '@ag-grid-devtools/utils'; -import { cli } from '../cli'; -import prettier from 'prettier'; - -export const ROOT_FOLDER = path.dirname(fileURLToPath(import.meta.url)); -export const TEMP_FOLDER = path.resolve(ROOT_FOLDER, '_temp'); -export const INPUT_FOLDER = path.resolve(ROOT_FOLDER, 'input-files'); -export const EXPECTED_FOLDER = path.resolve(ROOT_FOLDER, 'expected'); - -export async function loadExpectedSource(name: string) { - const filepath = path.resolve(EXPECTED_FOLDER, name); - return prettier.format(await readFile(filepath, 'utf-8'), { filepath }); -} - -export async function loadTempSource(name: string) { - const filepath = path.resolve(TEMP_FOLDER, name); - return prettier.format(await readFile(filepath, 'utf-8'), { filepath }); -} - -export async function prepareTestDataFiles() { - try { - await rmdir(TEMP_FOLDER, { recursive: true }); - } catch { - // already deleted - } - - await mkdir(TEMP_FOLDER, { recursive: true }); - - await cp(path.resolve(ROOT_FOLDER, INPUT_FOLDER), TEMP_FOLDER, { - recursive: true, - force: true, - filter: (src) => !src.includes('README.md'), - }); -} - -export function patchDynamicRequire() { - /** Fixes the path of an import for typescript, as we are using those with worker threads */ - const fixPath = (p: string): string => { - if (p === '@ag-grid-devtools/codemods/worker') { - return '@ag-grid-devtools/codemods/src/worker.ts'; - } - - if (p.startsWith('@ag-grid-devtools/codemods/version/')) { - p = - '@ag-grid-devtools/codemods/src/versions/' + - p.slice('@ag-grid-devtools/codemods/version/'.length) + - '/codemod.ts'; - } - - return p; - }; - - const oldRequire = dynamicRequire.require; - dynamicRequire.require = (path: string, meta: ImportMeta) => oldRequire(fixPath(path), meta); - - const oldResolve = dynamicRequire.resolve; - dynamicRequire.resolve = (path: string, meta: ImportMeta) => oldResolve(fixPath(path), meta); -} diff --git a/packages/cli/src/utils/exec.ts b/packages/cli/src/utils/exec.ts index 36a88e8b..61abe514 100644 --- a/packages/cli/src/utils/exec.ts +++ b/packages/cli/src/utils/exec.ts @@ -22,7 +22,13 @@ export function execCommand( stderr: Buffer.concat(stderrChunks).toString('utf8'), }); } else { - reject(new Error(`Command "${command} ${args.join(' ')}" exited with code ${exitCode}`)); + const error = new Error( + `Command "${command} ${args.join(' ')}" exited with code ${exitCode}`, + ); + try { + (error as any).stderr = Buffer.concat(stderrChunks).toString('utf8'); + } catch {} + reject(error); } }); }); diff --git a/packages/cli/src/utils/fs.ts b/packages/cli/src/utils/fs.ts index 05fa0ee5..efa48118 100644 --- a/packages/cli/src/utils/fs.ts +++ b/packages/cli/src/utils/fs.ts @@ -1,11 +1,10 @@ -import gracefulFs, { type Stats } from 'graceful-fs'; -import { join, parse } from 'node:path'; -import { promisify } from 'node:util'; +import { resolve, join, relative, dirname, sep as pathSeparator } from 'node:path'; +import { stat } from 'fs/promises'; +import { existsSync, readFileSync } from 'fs'; +import { glob } from 'glob'; +import createIgnore, { Ignore } from 'ignore'; -export const readdir = promisify(gracefulFs.readdir); -export const readFile = promisify(gracefulFs.readFile); -export const writeFile = promisify(gracefulFs.writeFile); -export const stat = promisify(gracefulFs.stat); +const DOT_DOT_SLASH = '..' + pathSeparator; export function isFsErrorCode( error: unknown, @@ -14,46 +13,96 @@ export function isFsErrorCode( return error instanceof Error && (error as Error & { code?: string }).code === code; } -export async function findInDirectory( +export async function findGitRoot(path: string): Promise { + let current = path; + for (;;) { + try { + const gitPath = join(current, '.git'); + if (existsSync(gitPath) && (await stat(gitPath)).isDirectory()) { + return current; + } + } catch {} + const parent = dirname(current); + if (parent === current) { + return undefined; + } + current = parent; + } +} + +export async function findSourceFiles( path: string, - predicate: (path: string, stats: Stats) => boolean, + extensions: string[], + gitRoot: string | undefined, ): Promise> { - const filenames = await readdir(path); - return Promise.all( - filenames.map((filename) => - stat(join(path, filename)).then((stats) => { - const filePath = join(path, filename); - if (!predicate(filePath, stats)) return []; - if (!stats.isDirectory()) return [filename]; - return findInDirectory(filePath, predicate).then((children) => - children.map((childFilename) => join(filename, childFilename)), - ); - }), - ), - ).then((results) => results.flat()); -} + path = resolve(path); -export async function findAncestorDirectoryContaining( - cwd: string, - filename: string, - predicate: (path: string, stats: Stats) => boolean, -): Promise { - const filePath = join(cwd, filename); - const stats = await (async () => { - try { - return await stat(filePath); - } catch (error) { - if (isFsErrorCode(error, 'ENOENT')) { - return null; - } - throw error; + let files = await glob( + extensions.map((ext) => `**/*${ext}`), + { + dot: true, + cwd: path, + nodir: true, + absolute: true, + ignore: ['**/node_modules/**', '**/.git/**'], + }, + ); + + interface DirGitignore { + directory: string; + ignore: Ignore; + parent: DirGitignore | null | undefined; + } + + const ignoreCache = new Map(); + + function getParentIgnorer(directory: string): DirGitignore | null { + const parent = dirname(directory); + return parent !== directory ? getIgnorer(parent) : null; + } + + function getIgnorer(directory: string): DirGitignore | null { + let result = ignoreCache.get(directory); + if (result !== undefined) { + return result; + } + + result = null; + + if (!gitRoot || !relative(gitRoot, directory).startsWith(DOT_DOT_SLASH)) { + let content: string | undefined; + const gitignorePath = join(directory, '.gitignore'); + try { + content = readFileSync(gitignorePath, 'utf-8'); + } catch {} + + result = content + ? { directory, ignore: createIgnore().add(content), parent: undefined } + : getParentIgnorer(directory); + } + + ignoreCache.set(directory, result); + return result; + } + + function isIgnored(path: string, ignorer: DirGitignore | null) { + if (!ignorer) { + return false; + } + const testResult = ignorer.ignore.test(relative(ignorer.directory, path)); + if (testResult.ignored) { + return true; + } + if (testResult.unignored) { + return false; } - })(); - if (stats) { - if (!predicate || predicate(filePath, stats)) return cwd; - return null; + if (ignorer.parent === undefined) { + ignorer.parent = getParentIgnorer(ignorer.directory); + } + return isIgnored(path, ignorer.parent); } - const { dir: dirname, root } = parse(cwd); - if (cwd === root) return null; - return findAncestorDirectoryContaining(dirname, filename, predicate); + + files = files.filter((file) => !isIgnored(file, getIgnorer(dirname(file)))); + + return files; } diff --git a/packages/cli/src/utils/git.ts b/packages/cli/src/utils/git.ts index 742e88ef..31ea7936 100644 --- a/packages/cli/src/utils/git.ts +++ b/packages/cli/src/utils/git.ts @@ -1,13 +1,8 @@ import { nonNull } from '@ag-grid-devtools/utils'; import { execCommand } from './exec'; -import { findAncestorDirectoryContaining } from './fs'; const GIT_STATUS_LINE_REGEX = /^\s*([^ ]+)\s*(.*)$/; -export async function getGitProjectRoot(cwd: string): Promise { - return findAncestorDirectoryContaining(cwd, '.git', (path, stats) => stats.isDirectory()); -} - export async function findInGitRepository( filenamePatterns: Array, options: { diff --git a/packages/cli/src/utils/path.ts b/packages/cli/src/utils/path.ts deleted file mode 100644 index fd60a1fe..00000000 --- a/packages/cli/src/utils/path.ts +++ /dev/null @@ -1 +0,0 @@ -export { basename, extname, resolve, relative } from 'node:path'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4c173c68..dc2f06e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,8 +33,8 @@ importers: specifier: 1.6.0 version: 1.6.0(vitest@1.6.0(@types/node@20.14.10)) glob: - specifier: 10.4.3 - version: 10.4.3 + specifier: 11.0.0 + version: 11.0.0 prettier: specifier: 3.3.2 version: 3.3.2 @@ -232,9 +232,15 @@ importers: diff: specifier: 5.2.0 version: 5.2.0 + glob: + specifier: 11.0.0 + version: 11.0.0 graceful-fs: specifier: 4.2.11 version: 4.2.11 + ignore: + specifier: 5.3.1 + version: 5.3.1 semver: specifier: 7.6.2 version: 7.6.2 @@ -2156,9 +2162,9 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.3: - resolution: {integrity: sha512-Q38SGlYRpVtDBPSWEylRyctn7uDeTp4NQERTLiCT1FqA9JXPYWqAVmQU6qh4r/zMM5ehxTcbaO8EjhWnvEhmyg==} - engines: {node: '>=18'} + glob@11.0.0: + resolution: {integrity: sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==} + engines: {node: 20 || >=22} hasBin: true glob@7.2.3: @@ -2348,9 +2354,9 @@ packages: resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} engines: {node: '>=8'} - jackspeak@3.4.0: - resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} - engines: {node: '>=14'} + jackspeak@4.0.1: + resolution: {integrity: sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==} + engines: {node: 20 || >=22} jju@1.4.0: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} @@ -2444,9 +2450,9 @@ packages: loupe@2.3.7: resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} - lru-cache@10.3.0: - resolution: {integrity: sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==} - engines: {node: 14 || >=16.14} + lru-cache@11.0.0: + resolution: {integrity: sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==} + engines: {node: 20 || >=22} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -2516,6 +2522,10 @@ packages: minimalistic-crypto-utils@1.0.1: resolution: {integrity: sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==} + minimatch@10.0.1: + resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} + engines: {node: 20 || >=22} + minimatch@3.0.8: resolution: {integrity: sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==} @@ -2638,9 +2648,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} + path-scurry@2.0.0: + resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} + engines: {node: 20 || >=22} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} @@ -3765,7 +3775,7 @@ snapshots: debug: 4.3.4 espree: 9.6.1 globals: 13.21.0 - ignore: 5.2.4 + ignore: 5.3.1 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -5051,14 +5061,14 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.3: + glob@11.0.0: dependencies: foreground-child: 3.1.1 - jackspeak: 3.4.0 - minimatch: 9.0.5 + jackspeak: 4.0.1 + minimatch: 10.0.1 minipass: 7.1.2 package-json-from-dist: 1.0.0 - path-scurry: 1.11.1 + path-scurry: 2.0.0 glob@7.2.3: dependencies: @@ -5080,7 +5090,7 @@ snapshots: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.1 - ignore: 5.2.4 + ignore: 5.3.1 merge2: 1.4.1 slash: 3.0.0 @@ -5232,7 +5242,7 @@ snapshots: html-escaper: 2.0.2 istanbul-lib-report: 3.0.1 - jackspeak@3.4.0: + jackspeak@4.0.1: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: @@ -5320,7 +5330,7 @@ snapshots: dependencies: get-func-name: 2.0.2 - lru-cache@10.3.0: {} + lru-cache@11.0.0: {} lru-cache@5.1.1: dependencies: @@ -5400,6 +5410,10 @@ snapshots: minimalistic-crypto-utils@1.0.1: {} + minimatch@10.0.1: + dependencies: + brace-expansion: 2.0.1 + minimatch@3.0.8: dependencies: brace-expansion: 1.1.11 @@ -5542,9 +5556,9 @@ snapshots: path-parse@1.0.7: {} - path-scurry@1.11.1: + path-scurry@2.0.0: dependencies: - lru-cache: 10.3.0 + lru-cache: 11.0.0 minipass: 7.1.2 path-type@4.0.0: {} From 52f4b1e47763b3ddca2f2dd7d0049f890eb8dbde Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Wed, 10 Jul 2024 13:51:19 +0100 Subject: [PATCH 04/10] AG-11722 improve and fix commandline parsing (#59) Fix command line parsing The "--config=file" should not be included in the files to be processed Partial semvers should be supported, "from=30" should work the same as "from=30.0.0" All boolean flags should have an implicit "--no-flag" so they can be overridden when chaining commangs (normal cli behaviour) --to= should support "latest" and partial semver, for example --to=32.1 or --to=latest --- .gitignore | 2 + packages/cli/src/commands/migrate.ts | 66 +++++++++++++++++++++------- packages/cli/src/utils/fs.ts | 6 +++ 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/.gitignore b/.gitignore index 76f82082..eb92d2b4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ +.DS_Store /node_modules/ /packages/*/node_modules/ /packages/*/vitest.config.mts.timestamp-*.mjs /coverage/ /docs/ +**/_temp/**/* \ No newline at end of file diff --git a/packages/cli/src/commands/migrate.ts b/packages/cli/src/commands/migrate.ts index 555b87d1..d80cb07b 100644 --- a/packages/cli/src/commands/migrate.ts +++ b/packages/cli/src/commands/migrate.ts @@ -134,11 +134,42 @@ export function parseArgs(args: string[], env: CliEnv): MigrateCommandArgs { arg = firstArg; } switch (arg) { + case '--allow-untracked': + case '-u': + options.allowUntracked = true; + break; + case '--no-allow-untracked': + options.allowUntracked = false; + break; + + case '--allow-dirty': + case '-d': + options.allowDirty = true; + break; + case '--no-allow-dirty': + options.allowDirty = false; + break; + + case '--dry-run': + options.dryRun = true; + break; + case '--no-dry-run': + options.dryRun = false; + break; + + case '--verbose': + options.verbose = true; + break; + case '--no-verbose': + options.verbose = false; + break; + case '--from': { - const value = args.shift(); + let value = args.shift(); if (!value || value.startsWith('-')) { throw new CliArgsError(`Missing value for ${arg}`, usage(env)); } + value = semverCoerce(value); if (!semver.valid(value)) { throw new CliArgsError( `Invalid ${arg} migration starting version`, @@ -149,10 +180,15 @@ export function parseArgs(args: string[], env: CliEnv): MigrateCommandArgs { break; } case '--to': { - const value = args.shift(); + let value = args.shift(); if (!value || value.startsWith('-')) { throw new CliArgsError(`Missing value for ${arg}`, usage(env)); } + if (value === 'latest') { + value = LATEST_VERSION; + } else { + value = semverCoerce(value); + } if (!versions.some(({ version }) => version === value)) { throw new CliArgsError( `Invalid ${arg} migration target version`, @@ -170,14 +206,7 @@ export function parseArgs(args: string[], env: CliEnv): MigrateCommandArgs { options.to = value; break; } - case '--allow-untracked': - case '-u': - options.allowUntracked = true; - break; - case '--allow-dirty': - case '-d': - options.allowDirty = true; - break; + case '--num-threads': { const value = args.shift(); if (!value || value.startsWith('-')) { @@ -206,12 +235,7 @@ export function parseArgs(args: string[], env: CliEnv): MigrateCommandArgs { } break; } - case '--dry-run': - options.dryRun = true; - break; - case '--verbose': - options.verbose = true; - break; + case '--help': case '-h': options.help = true; @@ -251,6 +275,10 @@ function printUsage(output: WritableStream, env: CliEnv): Promise { return log(output, usage(env)); } +function semverCoerce(version: string): string { + return semver.coerce(version)?.version ?? version; +} + async function migrate( args: Omit, options: CliOptions, @@ -289,7 +317,11 @@ async function migrate( if (input.length > 0) { inputFilePaths = input.map((path) => resolve(cwd, path)); } else { - inputFilePaths = await findSourceFiles(cwd, SOURCE_FILE_EXTENSIONS, gitRoot); + const skipFiles: string[] = []; + if (userConfigPath) { + skipFiles.push(userConfigPath); + } + inputFilePaths = await findSourceFiles(cwd, SOURCE_FILE_EXTENSIONS, skipFiles, gitRoot); } if (!allowUntracked) { diff --git a/packages/cli/src/utils/fs.ts b/packages/cli/src/utils/fs.ts index efa48118..e7f59f29 100644 --- a/packages/cli/src/utils/fs.ts +++ b/packages/cli/src/utils/fs.ts @@ -33,6 +33,7 @@ export async function findGitRoot(path: string): Promise { export async function findSourceFiles( path: string, extensions: string[], + skipFiles: string[], gitRoot: string | undefined, ): Promise> { path = resolve(path); @@ -48,6 +49,11 @@ export async function findSourceFiles( }, ); + if (skipFiles.length > 0) { + const skipFilesSet = new Set(skipFiles); + files = files.filter((file) => !skipFilesSet.has(file)); + } + interface DirGitignore { directory: string; ignore: Ignore; From 766e7d3cea50d26550cf8b3176c0036d9fda5538 Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Wed, 10 Jul 2024 14:28:52 +0100 Subject: [PATCH 05/10] release-32.0.2 (#60) release-32.0.2 Updates in the migrate CLI: - support for .gitignore files - when running in a folder without passing a list of files, .gitignore will be honoured - support for configuration file - this allows to define custom matchers to apply codemods also in wrapped or re-exported AG Grid components. --- README.md | 2 ++ packages/cli/package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index de6d174e..eb4d3d68 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ This repository contains a selection of developer tools related to [AG Grid](https://github.com/ag-grid/ag-grid) development. +See also [codemods](https://www.ag-grid.com/react-data-grid/codemods/) in AG Grid documentation. + ## Repository layout This repository is organised as a monorepo containing various packages. diff --git a/packages/cli/package.json b/packages/cli/package.json index 7f03c8c5..27184018 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@ag-grid-devtools/cli", - "version": "32.0.1", + "version": "32.0.2", "license": "MIT", "description": "AG Grid developer toolkit", "author": "AG Grid ", From 5775310dedcebbcac503613443ebd06965987a3b Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Mon, 15 Jul 2024 13:59:05 +0100 Subject: [PATCH 06/10] fix dynamic require (#62) * fix dynamic require --- packages/cli/index.mjs | 1 + packages/cli/package.json | 28 +++++++++- packages/cli/src/cli.ts | 7 +-- packages/cli/src/commands/migrate.ts | 22 ++++---- packages/cli/user-config.mjs | 1 + packages/cli/vite.config.mts | 4 ++ packages/utils/package.json | 4 +- packages/utils/src/module.ts | 80 ++++++++++++++++++++++++---- pnpm-lock.yaml | 27 ++++++++++ 9 files changed, 143 insertions(+), 31 deletions(-) create mode 100644 packages/cli/index.mjs create mode 100644 packages/cli/user-config.mjs diff --git a/packages/cli/index.mjs b/packages/cli/index.mjs new file mode 100644 index 00000000..147a5877 --- /dev/null +++ b/packages/cli/index.mjs @@ -0,0 +1 @@ +export * from './index.cjs'; diff --git a/packages/cli/package.json b/packages/cli/package.json index 27184018..3fec904e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -33,7 +33,30 @@ }, "pkg": { "main": "./index.cjs", - "bin": "./index.cjs" + "bin": "./index.cjs", + "exports": { + ".": { + "import": "./index.mjs", + "require": "./index.cjs", + "default": "./index.mjs" + }, + "./index": { + "import": "./index.mjs", + "require": "./index.cjs", + "default": "./index.mjs" + }, + "./index.js": "./index.mjs", + "./index.mjs": "./index.mjs", + "./index.cjs": "./index.cjs", + "./user-config": { + "import": "./user-config.mjs", + "require": "./user-config.cjs", + "default": "./user-config.mjs" + }, + "./user-config.js": "./user-config.mjs", + "./user-config.mjs": "./user-config.mjs", + "./user-config.cjs": "./user-config.cjs" + } }, "types": "./index.d.ts", "bundleDependencies": [ @@ -58,7 +81,8 @@ "graceful-fs": "4.2.11", "ignore": "5.3.1", "semver": "7.6.2", - "vite-plugin-dts": "3.9.1" + "vite-plugin-dts": "3.9.1", + "vite-plugin-static-copy": "1.0.6" }, "peerDependencies": { "eslint": "^8", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 95f111ab..1e988ec6 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -71,12 +71,7 @@ export async function cli(args: Array, cli: CliOptions): Promise { throw null; } - // Add typescript support by loading tsx - try { - dynamicRequire.require('tsx/cjs', import.meta); - } catch { - // ignore error if tsx could not be loaded - } + dynamicRequire.initialize(); const task = match(options.command, { Migrate: ({ args }) => migrate(args, cli), diff --git a/packages/cli/src/commands/migrate.ts b/packages/cli/src/commands/migrate.ts index d80cb07b..f76d3b81 100644 --- a/packages/cli/src/commands/migrate.ts +++ b/packages/cli/src/commands/migrate.ts @@ -446,29 +446,29 @@ async function migrate( // Create a worker pool to run the codemods in parallel let scriptPath: string | URL = dynamicRequire.resolve(WORKER_PATH, import.meta); - const resolvedCodemodPaths = codemodPaths.map((codemodPath) => - dynamicRequire.resolve(codemodPath, import.meta), - ); + // This will be true if we are trying to run from the codemod repo source code using tsx or vitest + const isTsSourceCodeWorker = scriptPath.endsWith('.ts'); const config: WorkerOptions = { // Pass the list of codemod paths to the worker via workerData workerData: { - codemodPaths: resolvedCodemodPaths, + codemodPaths: isTsSourceCodeWorker + ? codemodPaths.map((codemodPath) => dynamicRequire.resolve(codemodPath, import.meta)) + : codemodPaths, userConfigPath, }, env: process.env, argv: [scriptPath], - eval: true, + eval: isTsSourceCodeWorker, }; + const workerCodeOrPath = isTsSourceCodeWorker + ? `try { require("tsx/cjs"); } catch (_) {} require(${JSON.stringify(scriptPath)});` + : scriptPath; + const workers = Array.from( { length: numWorkers }, - () => - new Worker( - // Try to add typescript support by loading tsx and load the worker script - `try { require("tsx/cjs"); } catch (_) {} require(${JSON.stringify(scriptPath)});`, - config, - ), + () => new Worker(workerCodeOrPath, config), ); const workerPool = new WorkerTaskQueue(workers); return executeCodemodMultiThreaded(workerPool, inputFilePaths, { diff --git a/packages/cli/user-config.mjs b/packages/cli/user-config.mjs new file mode 100644 index 00000000..bda6c062 --- /dev/null +++ b/packages/cli/user-config.mjs @@ -0,0 +1 @@ +export * from './user-config.cjs'; diff --git a/packages/cli/vite.config.mts b/packages/cli/vite.config.mts index 9ce78913..9b9e6334 100644 --- a/packages/cli/vite.config.mts +++ b/packages/cli/vite.config.mts @@ -1,6 +1,7 @@ import { resolve } from 'path'; import { defineConfig, mergeConfig } from 'vite'; import dts from 'vite-plugin-dts'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; import base from '../build-config/templates/vite/cli.vite.config'; @@ -29,6 +30,9 @@ export default mergeConfig( bundledPackages: ['@ag-grid-devtools/types'], exclude: ['node_modules/**', '*.config.mts', '**/*.test.ts', 'package.json', 'index.ts'], }), + viteStaticCopy({ + targets: [{ src: '*.mjs', dest: '.' }], + }), ], }), ); diff --git a/packages/utils/package.json b/packages/utils/package.json index f7bcb9f0..e285a419 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -43,7 +43,9 @@ "@ag-grid-devtools/types": "workspace:*" }, "devDependencies": { - "@ag-grid-devtools/build-config": "workspace:*" + "@ag-grid-devtools/build-config": "workspace:*", + "@types/app-module-path": "^2.2.2", + "app-module-path": "2.2.0" }, "peerDependencies": { "eslint": "^8", diff --git a/packages/utils/src/module.ts b/packages/utils/src/module.ts index d7a7c05b..0c5ad96e 100644 --- a/packages/utils/src/module.ts +++ b/packages/utils/src/module.ts @@ -1,20 +1,68 @@ -import { createRequire } from 'node:module'; +import { fileURLToPath } from 'node:url'; +import { createRequire, Module } from 'node:module'; +import { existsSync } from 'node:fs'; +import { addPath as addNodeModulePath } from 'app-module-path'; +import { dirname, resolve as pathResolve, join as pathJoin } from 'node:path'; + +let initialized = false; + +const thisDir = pathResolve( + import.meta.url + ? dirname(fileURLToPath(import.meta.url)) + : typeof __dirname !== 'undefined' + ? __dirname + : process.cwd(), +); export const dynamicRequire = { - resolve(path: string, meta: ImportMeta): string { - if (meta.url === undefined && typeof require !== undefined) { - // import.meta not available, maybe running a ts file with tsx? use default cjs require - return require.resolve(path); + initialize() { + if (initialized) { + return; + } + initialized = true; + + // Register node_modules paths + + let currentDir = thisDir; + while (currentDir) { + if (currentDir.endsWith('node_modules')) { + tryAddNodeModulePath(currentDir); + break; + } + tryAddNodeModulePath(pathJoin(currentDir, 'node_modules')); + let parentDir = dirname(currentDir); + if (parentDir === currentDir) { + break; + } + currentDir = parentDir; + } + + // Add typescript support by loading tsx + try { + dynamicRequire.require('tsx/cjs', import.meta); + } catch { + // ignore error if tsx could not be loaded + } + + // Register .cjs and .cts extensions + + const exts = (Module as any)._extensions; + if (exts && !('.cjs' in exts)) { + exts['.cjs'] = exts['.js']; + } + if (exts && !('.cts' in exts) && '.ts' in exts) { + exts['.cts'] = exts['.ts']; } - return createRequire(meta.url).resolve(path); + }, + + resolve(path: string, meta: ImportMeta): string { + dynamicRequire.initialize(); + return createRequire(meta.url || pathResolve(thisDir, 'index.js')).resolve(path); }, require(path: string, meta: ImportMeta): T { - if (meta.url === undefined && typeof require !== undefined) { - // import.meta not available, maybe running a ts file with tsx? use default cjs require - return require(path); - } - return createRequire(meta.url)(path); + dynamicRequire.initialize(); + return createRequire(meta.url || pathResolve(thisDir, 'index.js'))(path); }, /** Like require, but supports modules with a default export transpiled to cjs */ @@ -32,3 +80,13 @@ export const dynamicRequire = { return required; }, }; + +function tryAddNodeModulePath(nodeModulesPath: string) { + try { + if (existsSync(nodeModulesPath)) { + addNodeModulePath(nodeModulesPath); + } + } catch { + // ignore error + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dc2f06e2..e9814387 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -247,6 +247,9 @@ importers: vite-plugin-dts: specifier: 3.9.1 version: 3.9.1(@types/node@20.14.10)(rollup@4.18.0)(typescript@5.2.2)(vite@5.0.11(@types/node@20.14.10)) + vite-plugin-static-copy: + specifier: 1.0.6 + version: 1.0.6(vite@5.0.11(@types/node@20.14.10)) packages/codemod-task-utils: dependencies: @@ -521,6 +524,12 @@ importers: '@ag-grid-devtools/build-config': specifier: workspace:* version: link:../build-config + '@types/app-module-path': + specifier: ^2.2.2 + version: 2.2.2 + app-module-path: + specifier: 2.2.0 + version: 2.2.0 packages/worker-utils: dependencies: @@ -1468,6 +1477,9 @@ packages: '@swc/types@0.1.9': resolution: {integrity: sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg==} + '@types/app-module-path@2.2.2': + resolution: {integrity: sha512-1Mnb53+j2cSmnc+rKxVxcQvfJyPpm7DjHTedaNTXVhj5AIAZpVjb2jbRdwYpguISutIYuVFKWx+U/QDZuOJ8vg==} + '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} @@ -1688,6 +1700,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + app-module-path@2.2.0: + resolution: {integrity: sha512-gkco+qxENJV+8vFcDiiFhuoSvRXb2a/QPqpSoWhVz829VNJfOTnELbBmPmNKFxf3xdNnw4DWCkzkDaavcX/1YQ==} + arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} @@ -4103,6 +4118,8 @@ snapshots: dependencies: '@swc/counter': 0.1.3 + '@types/app-module-path@2.2.2': {} + '@types/argparse@1.0.38': {} '@types/babel__core@7.20.3': @@ -4422,6 +4439,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 + app-module-path@2.2.0: {} + arg@5.0.2: {} argparse@1.0.10: @@ -6081,6 +6100,14 @@ snapshots: transitivePeerDependencies: - rollup + vite-plugin-static-copy@1.0.6(vite@5.0.11(@types/node@20.14.10)): + dependencies: + chokidar: 3.5.3 + fast-glob: 3.3.1 + fs-extra: 11.2.0 + picocolors: 1.0.1 + vite: 5.0.11(@types/node@20.14.10) + vite-plugin-static-copy@1.0.6(vite@5.3.3(@types/node@20.14.10)): dependencies: chokidar: 3.5.3 From 8318abef3fe02cd37cbb8977d9d6e01cc8f23d5c Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Mon, 15 Jul 2024 15:18:51 +0100 Subject: [PATCH 07/10] release-32.0.3 (#63) --- packages/cli/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 3fec904e..71eb8937 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "@ag-grid-devtools/cli", - "version": "32.0.2", + "version": "32.0.3", "license": "MIT", "description": "AG Grid developer toolkit", "author": "AG Grid ", From a2d0a7ec19460496f0deb958e6272d377ecaedd9 Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Mon, 15 Jul 2024 15:23:24 +0100 Subject: [PATCH 08/10] Release 32.0.3 (#64) * release-32.0.3 --- packages/cli/vite.config.mts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/cli/vite.config.mts b/packages/cli/vite.config.mts index 9b9e6334..f7408187 100644 --- a/packages/cli/vite.config.mts +++ b/packages/cli/vite.config.mts @@ -31,7 +31,10 @@ export default mergeConfig( exclude: ['node_modules/**', '*.config.mts', '**/*.test.ts', 'package.json', 'index.ts'], }), viteStaticCopy({ - targets: [{ src: '*.mjs', dest: '.' }], + targets: [ + { src: 'index.mjs', dest: '.' }, + { src: 'user-config.mjs', dest: '.' }, + ], }), ], }), From 143aab993a817efab19b7789f29f047cfd1df3ee Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Mon, 15 Jul 2024 15:33:13 +0100 Subject: [PATCH 09/10] merge main into develop From 24ea8e7aae24fc5ff8fcc0856d98da8de2ddf9e6 Mon Sep 17 00:00:00 2001 From: Salvatore Previti Date: Mon, 15 Jul 2024 17:51:47 +0100 Subject: [PATCH 10/10] add package.json to exports --- packages/cli/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 71eb8937..adcf65ca 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -55,7 +55,8 @@ }, "./user-config.js": "./user-config.mjs", "./user-config.mjs": "./user-config.mjs", - "./user-config.cjs": "./user-config.cjs" + "./user-config.cjs": "./user-config.cjs", + "./package.json": "./package.json" } }, "types": "./index.d.ts",