@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.
- package/README.md +37 -0
- package/dist/cli.js +585 -0
- 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
|
+
}
|