@gridstorm/codemod 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +37 -0
  2. package/dist/cli.js +585 -0
  3. package/package.json +59 -0
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # @gridstorm/codemod
2
+
3
+ CLI tool to automatically migrate your codebase from AG Grid to GridStorm.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g @gridstorm/codemod
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Migrate a directory
15
+ gridstorm-codemod ./src
16
+
17
+ # Dry run (preview changes without writing)
18
+ gridstorm-codemod ./src --dry-run
19
+
20
+ # Target specific file patterns
21
+ gridstorm-codemod ./src --include "**/*.tsx"
22
+ ```
23
+
24
+ ## What It Transforms
25
+
26
+ - AG Grid import paths to GridStorm equivalents
27
+ - Component props and configuration options
28
+ - Column definition API differences
29
+ - Event handler signatures
30
+
31
+ ## Documentation
32
+
33
+ [Migration Guide](https://grid-data-analytics-explorer.vercel.app//docs/migration) | [Codemod Reference](https://grid-data-analytics-explorer.vercel.app//docs/codemod)
34
+
35
+ ## License
36
+
37
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,585 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import { Command } from "commander";
5
+ import { glob } from "glob";
6
+ import chalk from "chalk";
7
+ import fs from "fs";
8
+ import path from "path";
9
+
10
+ // src/transforms/ag-grid-to-gridstorm.ts
11
+ import jscodeshift from "jscodeshift";
12
+
13
+ // src/transforms/import-map.ts
14
+ var PACKAGE_MAP = {
15
+ // AG Grid community packages
16
+ "ag-grid-react": "@gridstorm/react",
17
+ "ag-grid-community": "@gridstorm/core",
18
+ "ag-grid-enterprise": "@gridstorm/core",
19
+ // AG Grid scoped packages
20
+ "@ag-grid-community/react": "@gridstorm/react",
21
+ "@ag-grid-community/core": "@gridstorm/core",
22
+ "@ag-grid-community/client-side-row-model": "@gridstorm/core",
23
+ "@ag-grid-community/csv-export": "@gridstorm/core",
24
+ "@ag-grid-community/infinite-row-model": "@gridstorm/core",
25
+ "@ag-grid-community/styles": "@gridstorm/theme-default",
26
+ // AG Grid enterprise scoped packages
27
+ "@ag-grid-enterprise/all-modules": "@gridstorm/core",
28
+ "@ag-grid-enterprise/core": "@gridstorm/core",
29
+ "@ag-grid-enterprise/row-grouping": "@gridstorm/plugin-grouping",
30
+ "@ag-grid-enterprise/range-selection": "@gridstorm/plugin-selection",
31
+ "@ag-grid-enterprise/clipboard": "@gridstorm/plugin-clipboard",
32
+ "@ag-grid-enterprise/rich-select": "@gridstorm/plugin-editing",
33
+ "@ag-grid-enterprise/side-bar": "@gridstorm/core",
34
+ "@ag-grid-enterprise/status-bar": "@gridstorm/core",
35
+ "@ag-grid-enterprise/master-detail": "@gridstorm/core",
36
+ "@ag-grid-enterprise/excel-export": "@gridstorm/core",
37
+ "@ag-grid-enterprise/charts": "@gridstorm/core",
38
+ "@ag-grid-enterprise/set-filter": "@gridstorm/plugin-filtering",
39
+ "@ag-grid-enterprise/multi-filter": "@gridstorm/plugin-filtering",
40
+ "@ag-grid-enterprise/column-tool-panel": "@gridstorm/core",
41
+ "@ag-grid-enterprise/filter-tool-panel": "@gridstorm/plugin-filtering",
42
+ "@ag-grid-enterprise/menu": "@gridstorm/plugin-context-menu",
43
+ "@ag-grid-enterprise/server-side-row-model": "@gridstorm/core",
44
+ "@ag-grid-enterprise/viewport-row-model": "@gridstorm/core"
45
+ };
46
+ var NAMED_EXPORT_MAP = {
47
+ // Component renames
48
+ "AgGridReact": { name: "GridStorm", from: "@gridstorm/react" },
49
+ "AgGridColumn": { name: "GridStorm", from: "@gridstorm/react" },
50
+ // Type renames — core types
51
+ "ColDef": { name: "ColumnDef", from: "@gridstorm/core" },
52
+ "ColGroupDef": { name: "ColumnDef", from: "@gridstorm/core" },
53
+ "GridApi": { name: "GridApi", from: "@gridstorm/core" },
54
+ "ColumnApi": { name: "GridApi", from: "@gridstorm/core" },
55
+ "GridReadyEvent": { name: "GridReadyEvent", from: "@gridstorm/core" },
56
+ "GridOptions": { name: "GridConfig", from: "@gridstorm/core" },
57
+ "ICellRendererParams": { name: "CellRendererProps", from: "@gridstorm/core" },
58
+ "ICellRendererComp": { name: "CellRendererFn", from: "@gridstorm/core" },
59
+ "ICellEditorParams": { name: "CellEditorParams", from: "@gridstorm/core" },
60
+ "ValueGetterParams": { name: "ValueGetterParams", from: "@gridstorm/core" },
61
+ "ValueSetterParams": { name: "ValueSetterParams", from: "@gridstorm/core" },
62
+ "ValueFormatterParams": { name: "ValueFormatterParams", from: "@gridstorm/core" },
63
+ "ValueParserParams": { name: "ValueParserParams", from: "@gridstorm/core" },
64
+ "SortDirection": { name: "SortDirection", from: "@gridstorm/core" },
65
+ "RowNode": { name: "RowNode", from: "@gridstorm/core" },
66
+ "IRowNode": { name: "RowNode", from: "@gridstorm/core" },
67
+ "Column": { name: "ColumnState", from: "@gridstorm/core" },
68
+ // Selection types
69
+ "SelectionChangedEvent": { name: "SelectionState", from: "@gridstorm/core" },
70
+ "RowSelectedEvent": { name: "SelectionState", from: "@gridstorm/core" },
71
+ // Filter types
72
+ "IFilterParams": { name: "FilterModel", from: "@gridstorm/core" },
73
+ "FilterModel": { name: "FilterModel", from: "@gridstorm/core" },
74
+ // Sort types
75
+ "SortModelItem": { name: "SortModelItem", from: "@gridstorm/core" },
76
+ // Module to Plugin renames (these are value imports, not just types)
77
+ "ClientSideRowModelModule": { name: "/* ClientSideRowModelModule is built into GridStorm core */", from: "@gridstorm/core" },
78
+ "AllCommunityModules": { name: "/* AllCommunityModules \u2014 install individual @gridstorm plugins instead */", from: "@gridstorm/core" },
79
+ "ModuleRegistry": { name: "/* ModuleRegistry \u2014 GridStorm uses plugins prop, not module registry */", from: "@gridstorm/core" }
80
+ };
81
+ var CSS_IMPORTS_TO_REMOVE = [
82
+ "ag-grid-community/styles/ag-grid.css",
83
+ "ag-grid-community/styles/ag-theme-alpine.css",
84
+ "ag-grid-community/styles/ag-theme-alpine-dark.css",
85
+ "ag-grid-community/styles/ag-theme-balham.css",
86
+ "ag-grid-community/styles/ag-theme-balham-dark.css",
87
+ "ag-grid-community/styles/ag-theme-material.css",
88
+ "ag-grid-community/dist/styles/ag-grid.css",
89
+ "ag-grid-community/dist/styles/ag-theme-alpine.css",
90
+ "ag-grid-community/dist/styles/ag-theme-balham.css",
91
+ "ag-grid-community/dist/styles/ag-theme-material.css",
92
+ "@ag-grid-community/styles/ag-grid.css",
93
+ "@ag-grid-community/styles/ag-theme-alpine.css",
94
+ "@ag-grid-community/styles/ag-theme-balham.css",
95
+ "@ag-grid-community/styles/ag-theme-material.css",
96
+ "@ag-grid-community/core/dist/styles/ag-grid.css",
97
+ "@ag-grid-community/core/dist/styles/ag-theme-alpine.css",
98
+ "@ag-grid-community/core/dist/styles/ag-theme-balham.css",
99
+ "@ag-grid-community/core/dist/styles/ag-theme-material.css"
100
+ ];
101
+ var GRIDSTORM_THEME_IMPORT = "@gridstorm/theme-default/dist/tokens.css";
102
+
103
+ // src/transforms/prop-map.ts
104
+ var PROP_MAP = {
105
+ // Core prop renames
106
+ "columnDefs": "columns",
107
+ "modules": "plugins"
108
+ // These are the same but listed for completeness / documentation
109
+ // 'rowData': 'rowData',
110
+ // 'defaultColDef': 'defaultColDef',
111
+ // 'getRowId': 'getRowId',
112
+ // 'onGridReady': 'onGridReady',
113
+ // 'rowHeight': 'rowHeight',
114
+ // 'headerHeight': 'headerHeight',
115
+ // 'rowSelection': 'rowSelection',
116
+ // 'pagination': 'pagination',
117
+ // 'paginationPageSize': 'paginationPageSize',
118
+ // 'animateRows': 'animateRows',
119
+ };
120
+ var THEME_CLASS_MAP = {
121
+ "ag-theme-alpine": "light",
122
+ "ag-theme-alpine-dark": "dark",
123
+ "ag-theme-alpine-auto-dark": "dark",
124
+ "ag-theme-balham": "light",
125
+ "ag-theme-balham-dark": "dark",
126
+ "ag-theme-balham-auto-dark": "dark",
127
+ "ag-theme-material": "light",
128
+ "ag-theme-material-dark": "dark",
129
+ "ag-theme-quartz": "light",
130
+ "ag-theme-quartz-dark": "dark",
131
+ "ag-theme-quartz-auto-dark": "dark"
132
+ };
133
+ var MODULE_TO_PLUGIN_MAP = {
134
+ "ClientSideRowModelModule": {
135
+ plugin: null,
136
+ package: null,
137
+ note: "Built into @gridstorm/core \u2014 remove this module"
138
+ },
139
+ "InfiniteRowModelModule": {
140
+ plugin: null,
141
+ package: null,
142
+ note: "Coming soon in GridStorm \u2014 not yet available"
143
+ },
144
+ "CsvExportModule": {
145
+ plugin: null,
146
+ package: null,
147
+ note: "Coming soon in GridStorm \u2014 not yet available"
148
+ },
149
+ "RowGroupingModule": {
150
+ plugin: "GroupingPlugin",
151
+ package: "@gridstorm/plugin-grouping",
152
+ note: "Free in GridStorm (no enterprise license needed)"
153
+ },
154
+ "RangeSelectionModule": {
155
+ plugin: "SelectionPlugin",
156
+ package: "@gridstorm/plugin-selection",
157
+ note: "Provides row and range selection"
158
+ },
159
+ "ClipboardModule": {
160
+ plugin: "ClipboardPlugin",
161
+ package: "@gridstorm/plugin-clipboard",
162
+ note: "Copy/paste support"
163
+ },
164
+ "RichSelectModule": {
165
+ plugin: "EditingPlugin",
166
+ package: "@gridstorm/plugin-editing",
167
+ note: "Cell editing with rich editors"
168
+ },
169
+ "SetFilterModule": {
170
+ plugin: "FilteringPlugin",
171
+ package: "@gridstorm/plugin-filtering",
172
+ note: "Includes set filter and other filter types"
173
+ },
174
+ "MultiFilterModule": {
175
+ plugin: "FilteringPlugin",
176
+ package: "@gridstorm/plugin-filtering",
177
+ note: "Multi-filter is part of FilteringPlugin"
178
+ },
179
+ "MenuModule": {
180
+ plugin: "ContextMenuPlugin",
181
+ package: "@gridstorm/plugin-context-menu",
182
+ note: "Context menu support"
183
+ },
184
+ "ColumnsToolPanelModule": {
185
+ plugin: null,
186
+ package: null,
187
+ note: "Coming soon in GridStorm \u2014 not yet available"
188
+ },
189
+ "FiltersToolPanelModule": {
190
+ plugin: "FilteringPlugin",
191
+ package: "@gridstorm/plugin-filtering",
192
+ note: "Filter panel is part of FilteringPlugin"
193
+ },
194
+ "SideBarModule": {
195
+ plugin: null,
196
+ package: null,
197
+ note: "Coming soon in GridStorm \u2014 not yet available"
198
+ },
199
+ "StatusBarModule": {
200
+ plugin: null,
201
+ package: null,
202
+ note: "Coming soon in GridStorm \u2014 not yet available"
203
+ },
204
+ "MasterDetailModule": {
205
+ plugin: null,
206
+ package: null,
207
+ note: "Coming soon in GridStorm \u2014 not yet available"
208
+ },
209
+ "ExcelExportModule": {
210
+ plugin: null,
211
+ package: null,
212
+ note: "Coming soon in GridStorm \u2014 not yet available"
213
+ },
214
+ "ServerSideRowModelModule": {
215
+ plugin: null,
216
+ package: null,
217
+ note: "Coming soon in GridStorm \u2014 not yet available"
218
+ },
219
+ "ViewportRowModelModule": {
220
+ plugin: null,
221
+ package: null,
222
+ note: "Coming soon in GridStorm \u2014 not yet available"
223
+ },
224
+ "SparklinesModule": {
225
+ plugin: null,
226
+ package: null,
227
+ note: "Coming soon in GridStorm \u2014 not yet available"
228
+ },
229
+ "GridChartsModule": {
230
+ plugin: null,
231
+ package: null,
232
+ note: "Coming soon in GridStorm \u2014 not yet available"
233
+ },
234
+ "AllCommunityModules": {
235
+ plugin: null,
236
+ package: null,
237
+ note: "Install individual @gridstorm/plugin-* packages instead"
238
+ },
239
+ "AllEnterpriseModules": {
240
+ plugin: null,
241
+ package: null,
242
+ note: "Install individual @gridstorm/plugin-* packages instead"
243
+ }
244
+ };
245
+
246
+ // src/transforms/ag-grid-to-gridstorm.ts
247
+ function transformAgGridToGridStorm(source, filePath) {
248
+ const changes = [];
249
+ const j = jscodeshift.withParser(detectParser(filePath));
250
+ let root;
251
+ try {
252
+ root = j(source);
253
+ } catch (_err) {
254
+ return { code: source, changes: [] };
255
+ }
256
+ rewriteImports(j, root, changes);
257
+ replaceCssImports(j, root, changes);
258
+ renameJsxComponents(j, root, changes);
259
+ renameJsxProps(j, root, changes);
260
+ renameTypeReferences(j, root, changes);
261
+ convertThemeClasses(j, root, changes);
262
+ addModuleToPluginComments(j, root, changes);
263
+ renameColDefAnnotations(j, root, changes);
264
+ const output = root.toSource({ quote: "single" });
265
+ return { code: output, changes };
266
+ }
267
+ function detectParser(filePath) {
268
+ if (filePath.endsWith(".tsx")) return "tsx";
269
+ if (filePath.endsWith(".ts")) return "ts";
270
+ if (filePath.endsWith(".jsx")) return "babel";
271
+ return "babel";
272
+ }
273
+ function rewriteImports(j, root, changes) {
274
+ root.find(j.ImportDeclaration).forEach((path2) => {
275
+ const sourceValue = path2.node.source.value;
276
+ if (PACKAGE_MAP[sourceValue]) {
277
+ const newPackage = PACKAGE_MAP[sourceValue];
278
+ changes.push(`Import: "${sourceValue}" \u2192 "${newPackage}"`);
279
+ path2.node.source = j.literal(newPackage);
280
+ }
281
+ const specifiers = path2.node.specifiers;
282
+ if (!specifiers) return;
283
+ for (const specifier of specifiers) {
284
+ if (specifier.type === "ImportSpecifier" && specifier.imported) {
285
+ const importedName = specifier.imported.type === "Identifier" ? specifier.imported.name : void 0;
286
+ if (importedName && NAMED_EXPORT_MAP[importedName]) {
287
+ const mapping = NAMED_EXPORT_MAP[importedName];
288
+ const newName = mapping.name;
289
+ if (newName.startsWith("/*")) {
290
+ continue;
291
+ }
292
+ if (newName !== importedName) {
293
+ changes.push(`Import specifier: "${importedName}" \u2192 "${newName}"`);
294
+ if (specifier.local && specifier.local.name === importedName) {
295
+ specifier.local = j.identifier(newName);
296
+ }
297
+ specifier.imported = j.identifier(newName);
298
+ }
299
+ if (mapping.from) {
300
+ path2.node.source = j.literal(mapping.from);
301
+ }
302
+ }
303
+ }
304
+ }
305
+ });
306
+ }
307
+ function replaceCssImports(j, root, changes) {
308
+ let removedCss = false;
309
+ root.find(j.ImportDeclaration).forEach((path2) => {
310
+ const sourceValue = path2.node.source.value;
311
+ if (CSS_IMPORTS_TO_REMOVE.includes(sourceValue)) {
312
+ changes.push(`Removed CSS import: "${sourceValue}"`);
313
+ j(path2).remove();
314
+ removedCss = true;
315
+ }
316
+ });
317
+ if (removedCss) {
318
+ const hasThemeImport = root.find(j.ImportDeclaration).filter((path2) => path2.node.source.value === GRIDSTORM_THEME_IMPORT).length > 0;
319
+ if (!hasThemeImport) {
320
+ const themeImport = j.importDeclaration([], j.literal(GRIDSTORM_THEME_IMPORT));
321
+ const body = root.find(j.Program).get("body");
322
+ const imports = root.find(j.ImportDeclaration);
323
+ if (imports.length > 0) {
324
+ const lastImport = imports.at(-1);
325
+ lastImport.insertAfter(themeImport);
326
+ } else {
327
+ body.unshift(themeImport);
328
+ }
329
+ changes.push(`Added CSS import: "${GRIDSTORM_THEME_IMPORT}"`);
330
+ }
331
+ }
332
+ }
333
+ function renameJsxComponents(j, root, changes) {
334
+ root.find(j.JSXIdentifier, { name: "AgGridReact" }).forEach((path2) => {
335
+ changes.push("Component: <AgGridReact> \u2192 <GridStorm>");
336
+ path2.node.name = "GridStorm";
337
+ });
338
+ root.find(j.JSXIdentifier, { name: "AgGridColumn" }).forEach((path2) => {
339
+ changes.push("Component: <AgGridColumn> \u2192 removed (use columns prop)");
340
+ path2.node.name = "GridStorm";
341
+ });
342
+ root.find(j.Identifier, { name: "AgGridReact" }).forEach((path2) => {
343
+ if (path2.parent.node.type === "ImportSpecifier" || path2.parent.node.type === "ImportDefaultSpecifier") {
344
+ return;
345
+ }
346
+ path2.node.name = "GridStorm";
347
+ });
348
+ }
349
+ function renameJsxProps(j, root, changes) {
350
+ root.find(j.JSXAttribute).forEach((path2) => {
351
+ const attrName = path2.node.name;
352
+ if (attrName.type !== "JSXIdentifier") return;
353
+ const propName = attrName.name;
354
+ if (PROP_MAP[propName]) {
355
+ const newPropName = PROP_MAP[propName];
356
+ changes.push(`Prop: "${propName}" \u2192 "${newPropName}"`);
357
+ attrName.name = newPropName;
358
+ }
359
+ });
360
+ }
361
+ function renameTypeReferences(j, root, changes) {
362
+ try {
363
+ root.find(j.TSTypeReference).forEach((path2) => {
364
+ const typeName = path2.node.typeName;
365
+ if (typeName.type === "Identifier" && NAMED_EXPORT_MAP[typeName.name]) {
366
+ const mapping = NAMED_EXPORT_MAP[typeName.name];
367
+ if (!mapping.name.startsWith("/*") && mapping.name !== typeName.name) {
368
+ changes.push(`Type: "${typeName.name}" \u2192 "${mapping.name}"`);
369
+ typeName.name = mapping.name;
370
+ }
371
+ }
372
+ });
373
+ } catch (_err) {
374
+ }
375
+ }
376
+ function convertThemeClasses(j, root, changes) {
377
+ root.find(j.JSXAttribute, { name: { name: "className" } }).forEach((path2) => {
378
+ const value = path2.node.value;
379
+ if (!value) return;
380
+ if (value.type === "StringLiteral" || value.type === "Literal" && typeof value.value === "string") {
381
+ const classValue = value.value;
382
+ for (const [agClass, gsTheme] of Object.entries(THEME_CLASS_MAP)) {
383
+ if (classValue.includes(agClass)) {
384
+ const remainingClasses = classValue.replace(agClass, "").trim().replace(/\s+/g, " ");
385
+ changes.push(`Theme: className="${agClass}" \u2192 data-theme="${gsTheme}"`);
386
+ if (remainingClasses) {
387
+ value.value = remainingClasses;
388
+ const parentAttrs = path2.parent.node.attributes;
389
+ if (parentAttrs && Array.isArray(parentAttrs)) {
390
+ parentAttrs.push(
391
+ j.jsxAttribute(
392
+ j.jsxIdentifier("data-theme"),
393
+ j.literal(gsTheme)
394
+ )
395
+ );
396
+ }
397
+ } else {
398
+ path2.node.name = j.jsxIdentifier("data-theme");
399
+ value.value = gsTheme;
400
+ }
401
+ break;
402
+ }
403
+ }
404
+ }
405
+ if (value.type === "JSXExpressionContainer") {
406
+ const expr = value.expression;
407
+ if (expr.type === "TemplateLiteral") {
408
+ for (const quasi of expr.quasis) {
409
+ const raw = quasi.value.raw;
410
+ for (const [agClass, gsTheme] of Object.entries(THEME_CLASS_MAP)) {
411
+ if (raw.includes(agClass)) {
412
+ changes.push(`Theme: template className containing "${agClass}" \u2014 manual conversion needed to data-theme="${gsTheme}"`);
413
+ quasi.value.raw = raw.replace(agClass, `/* TODO: use data-theme="${gsTheme}" instead */`);
414
+ quasi.value.cooked = quasi.value.raw;
415
+ break;
416
+ }
417
+ }
418
+ }
419
+ }
420
+ }
421
+ });
422
+ }
423
+ function addModuleToPluginComments(j, root, changes) {
424
+ for (const [moduleName, pluginInfo] of Object.entries(MODULE_TO_PLUGIN_MAP)) {
425
+ root.find(j.Identifier, { name: moduleName }).forEach((path2) => {
426
+ if (path2.parent.node.type === "ImportSpecifier" || path2.parent.node.type === "ImportDefaultSpecifier") {
427
+ return;
428
+ }
429
+ if (pluginInfo.plugin) {
430
+ changes.push(
431
+ `Module \u2192 Plugin: ${moduleName} \u2192 ${pluginInfo.plugin}() from "${pluginInfo.package}" (${pluginInfo.note})`
432
+ );
433
+ } else {
434
+ changes.push(
435
+ `Module removal: ${moduleName} \u2014 ${pluginInfo.note}`
436
+ );
437
+ }
438
+ if (!path2.node.comments) {
439
+ path2.node.comments = [];
440
+ }
441
+ path2.node.comments.push(
442
+ j.commentLine(
443
+ ` TODO [gridstorm-codemod]: ${pluginInfo.plugin ? `Replace with ${pluginInfo.plugin}() from "${pluginInfo.package}"` : pluginInfo.note}`,
444
+ false,
445
+ true
446
+ )
447
+ );
448
+ });
449
+ }
450
+ }
451
+ function renameColDefAnnotations(j, root, changes) {
452
+ try {
453
+ root.find(j.Identifier, { name: "ColDef" }).forEach((path2) => {
454
+ if (path2.parent.node.type === "ImportSpecifier" || path2.parent.node.type === "ImportDefaultSpecifier") {
455
+ return;
456
+ }
457
+ if (path2.parent.node.type === "TSTypeReference" || path2.parent.node.type === "TSQualifiedName") {
458
+ return;
459
+ }
460
+ path2.node.name = "ColumnDef";
461
+ changes.push('Type reference: "ColDef" \u2192 "ColumnDef"');
462
+ });
463
+ } catch (_err) {
464
+ }
465
+ try {
466
+ root.find(j.Identifier, { name: "ColGroupDef" }).forEach((path2) => {
467
+ if (path2.parent.node.type === "ImportSpecifier" || path2.parent.node.type === "ImportDefaultSpecifier") {
468
+ return;
469
+ }
470
+ path2.node.name = "ColumnDef";
471
+ changes.push('Type reference: "ColGroupDef" \u2192 "ColumnDef"');
472
+ });
473
+ } catch (_err) {
474
+ }
475
+ }
476
+
477
+ // src/cli.ts
478
+ var program = new Command();
479
+ program.name("gridstorm-codemod").description("Codemod CLI to migrate from AG Grid to GridStorm").version("0.1.0").argument("<path>", "Path to source directory or file to transform").option("--from <framework>", "Source framework", "ag-grid").option("--dry-run", "Show changes without writing files", false).option("--verbose", "Show detailed transformation logs", false).action(async (targetPath, options) => {
480
+ const resolvedPath = path.resolve(targetPath);
481
+ if (!fs.existsSync(resolvedPath)) {
482
+ console.error(chalk.red(`Error: Path "${resolvedPath}" does not exist.`));
483
+ process.exit(1);
484
+ }
485
+ if (options.from !== "ag-grid") {
486
+ console.error(chalk.red(`Error: Unsupported source framework "${options.from}". Only "ag-grid" is supported.`));
487
+ process.exit(1);
488
+ }
489
+ console.log(chalk.blue("\n GridStorm Codemod"));
490
+ console.log(chalk.blue(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n"));
491
+ console.log(` Source: ${chalk.cyan(options.from)}`);
492
+ console.log(` Path: ${chalk.cyan(resolvedPath)}`);
493
+ console.log(` Dry run: ${options.dryRun ? chalk.yellow("yes") : "no"}`);
494
+ console.log("");
495
+ const files = await findSourceFiles(resolvedPath);
496
+ if (files.length === 0) {
497
+ console.log(chalk.yellow(" No source files found."));
498
+ process.exit(0);
499
+ }
500
+ console.log(` Found ${chalk.cyan(String(files.length))} source file(s) to scan.
501
+ `);
502
+ let totalChanged = 0;
503
+ let totalChanges = 0;
504
+ for (const filePath of files) {
505
+ const result = processFile(filePath, options);
506
+ if (result.changed) {
507
+ totalChanged++;
508
+ totalChanges += result.changes.length;
509
+ console.log(` ${chalk.green("MODIFIED")} ${path.relative(resolvedPath, filePath)}`);
510
+ if (options.verbose) {
511
+ for (const change of result.changes) {
512
+ console.log(` ${chalk.gray(">")} ${change}`);
513
+ }
514
+ console.log("");
515
+ }
516
+ if (!options.dryRun) {
517
+ fs.writeFileSync(filePath, result.transformed, "utf-8");
518
+ }
519
+ } else if (options.verbose) {
520
+ console.log(` ${chalk.gray("SKIPPED")} ${path.relative(resolvedPath, filePath)}`);
521
+ }
522
+ }
523
+ console.log("");
524
+ console.log(chalk.blue(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
525
+ console.log(` Files modified: ${chalk.cyan(String(totalChanged))} / ${files.length}`);
526
+ console.log(` Total changes: ${chalk.cyan(String(totalChanges))}`);
527
+ if (options.dryRun) {
528
+ console.log(chalk.yellow("\n Dry run mode \u2014 no files were written."));
529
+ console.log(chalk.yellow(" Run without --dry-run to apply changes.\n"));
530
+ } else {
531
+ console.log(chalk.green("\n Migration complete.\n"));
532
+ console.log(" Next steps:");
533
+ console.log(" 1. Review the changes and fix any manual migration items");
534
+ console.log(" 2. Install GridStorm packages: npm install @gridstorm/core @gridstorm/react");
535
+ console.log(" 3. Install plugins you need (e.g., @gridstorm/plugin-sorting)");
536
+ console.log(" 4. Remove AG Grid packages: npm uninstall ag-grid-community ag-grid-react");
537
+ console.log(" 5. Run your test suite to verify the migration");
538
+ console.log(` 6. See the full migration guide at ${chalk.cyan("https://gridstorm.dev/guides/migration-from-ag-grid")}
539
+ `);
540
+ }
541
+ });
542
+ async function findSourceFiles(targetPath) {
543
+ const stat = fs.statSync(targetPath);
544
+ if (stat.isFile()) {
545
+ return [targetPath];
546
+ }
547
+ const pattern = "**/*.{ts,tsx,js,jsx,mjs,cjs}";
548
+ const files = await glob(pattern, {
549
+ cwd: targetPath,
550
+ absolute: true,
551
+ ignore: [
552
+ "**/node_modules/**",
553
+ "**/dist/**",
554
+ "**/build/**",
555
+ "**/.next/**",
556
+ "**/.nuxt/**",
557
+ "**/coverage/**"
558
+ ]
559
+ });
560
+ return files.sort();
561
+ }
562
+ function processFile(filePath, options) {
563
+ const original = fs.readFileSync(filePath, "utf-8");
564
+ if (!hasAgGridReferences(original)) {
565
+ return {
566
+ filePath,
567
+ changed: false,
568
+ original,
569
+ transformed: original,
570
+ changes: []
571
+ };
572
+ }
573
+ const { code: transformed, changes } = transformAgGridToGridStorm(original, filePath);
574
+ return {
575
+ filePath,
576
+ changed: transformed !== original,
577
+ original,
578
+ transformed,
579
+ changes
580
+ };
581
+ }
582
+ function hasAgGridReferences(source) {
583
+ return source.includes("ag-grid") || source.includes("AgGridReact") || source.includes("@ag-grid") || source.includes("ag-theme-");
584
+ }
585
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@gridstorm/codemod",
3
+ "version": "0.1.2",
4
+ "description": "Codemod CLI to migrate from AG Grid to GridStorm",
5
+ "type": "module",
6
+ "bin": {
7
+ "gridstorm-codemod": "./dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsup",
11
+ "dev": "tsup --watch"
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "LICENSE.md"
16
+ ],
17
+ "sideEffects": false,
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "license": "MIT",
22
+ "keywords": [
23
+ "gridstorm",
24
+ "ag-grid",
25
+ "codemod",
26
+ "migration"
27
+ ],
28
+ "dependencies": {
29
+ "jscodeshift": "^17.0.0",
30
+ "glob": "^11.0.0",
31
+ "chalk": "^5.3.0",
32
+ "commander": "^12.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "typescript": "^5.5.0",
36
+ "tsup": "^8.5.0",
37
+ "@types/jscodeshift": "^0.12.0"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "https://github.com/007krcs/grid-data.git",
42
+ "directory": "packages/codemod"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/007krcs/grid-data/issues"
46
+ },
47
+ "homepage": "https://grid-data-analytics-explorer.vercel.app/",
48
+ "author": {
49
+ "name": "GridStorm",
50
+ "url": "https://grid-data-analytics-explorer.vercel.app/"
51
+ },
52
+ "funding": {
53
+ "type": "github",
54
+ "url": "https://github.com/sponsors/gridstorm"
55
+ },
56
+ "engines": {
57
+ "node": ">=18.0.0"
58
+ }
59
+ }