@bpmn-io/codemods 0.2.1 → 0.3.1

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/esm/README.md CHANGED
@@ -52,11 +52,12 @@ For every `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts` and `.tsx` file below the target
52
52
  - Relative and bare (`node_modules`) specifiers are both handled.
53
53
  - TypeScript / JSX sources are written with a `.js` specifier (NodeNext
54
54
  convention), even though the file on disk is `.ts`/`.tsx`.
55
+ - Relative directory imports are expanded to `<dir>/index.js` when an
56
+ `index` file exists in that directory (e.g. `./lib` → `./lib/index.js`).
55
57
  4. Specifiers that already carry an extension, and bare package roots
56
58
  (e.g. `diagram-js`, `react`), are left untouched.
57
- 5. Anything that still cannot be resolved (e.g. a directory import that would
58
- need `/index.js`, or a genuinely missing file) is **reported** for manual
59
- review — never silently changed.
59
+ 5. Anything that still cannot be resolved (e.g. a genuinely missing file) is
60
+ **reported** for manual review never silently changed.
60
61
 
61
62
  Only the source string is edited; surrounding formatting is preserved exactly.
62
63
 
@@ -74,5 +75,4 @@ By design, the mod does **not**:
74
75
 
75
76
  - rewrite `require()` calls,
76
77
  - rewrite dynamic imports with a non-literal argument (`import(name)`),
77
- - add `/index.js` for directory imports,
78
78
  - guess at unresolvable imports — these are reported instead.
@@ -72,6 +72,11 @@ export function resolveImport(specifier, fromFile, options = {}) {
72
72
  return { status: 'skip', specifier };
73
73
  }
74
74
 
75
+ // bare subpath covered by package.json exports → already valid for ESM
76
+ if (bare && isSubpathExported(specifier, fromFile)) {
77
+ return { status: 'skip', specifier };
78
+ }
79
+
75
80
  const rewritten = bare
76
81
  ? resolveBare(specifier, fromFile)
77
82
  : resolveRelative(specifier, fromFile);
@@ -86,7 +91,19 @@ export function resolveImport(specifier, fromFile, options = {}) {
86
91
  function resolveRelative(specifier, fromFile) {
87
92
  const base = path.resolve(path.dirname(fromFile), specifier);
88
93
 
89
- return resolveCandidates(base, specifier, RELATIVE_CANDIDATES);
94
+ const direct = resolveCandidates(base, specifier, RELATIVE_CANDIDATES);
95
+ if (direct) return direct;
96
+
97
+ // directory import: specifier points to a directory, try <dir>/index.*
98
+ if (isDirectory(base)) {
99
+ return resolveCandidates(
100
+ path.join(base, 'index'),
101
+ specifier + '/index',
102
+ RELATIVE_CANDIDATES
103
+ );
104
+ }
105
+
106
+ return null;
90
107
  }
91
108
 
92
109
  function resolveBare(specifier, fromFile) {
@@ -98,7 +115,20 @@ function resolveBare(specifier, fromFile) {
98
115
  return null;
99
116
  }
100
117
 
101
- return resolveCandidates(path.join(pkgDir, subpath), specifier, BARE_CANDIDATES);
118
+ const basePath = path.join(pkgDir, subpath);
119
+
120
+ const direct = resolveCandidates(basePath, specifier, BARE_CANDIDATES);
121
+ if (direct) return direct;
122
+
123
+ if (isDirectory(basePath)) {
124
+ return resolveCandidates(
125
+ path.join(basePath, 'index'),
126
+ specifier + '/index',
127
+ BARE_CANDIDATES
128
+ );
129
+ }
130
+
131
+ return null;
102
132
  }
103
133
 
104
134
  function resolveCandidates(basePath, specifier, candidates) {
@@ -113,7 +143,8 @@ function resolveCandidates(basePath, specifier, candidates) {
113
143
 
114
144
  /**
115
145
  * Locate a package directory by walking up the `node_modules` chain, starting
116
- * from the importing file's directory.
146
+ * from the importing file's directory. Also handles self-referencing: if a
147
+ * directory's own `package.json` names the package, that directory is returned.
117
148
  */
118
149
  function findPackageDir(pkg, fromDir) {
119
150
  let dir = fromDir;
@@ -125,6 +156,10 @@ function findPackageDir(pkg, fromDir) {
125
156
  return candidate;
126
157
  }
127
158
 
159
+ if (readJson(path.join(dir, 'package.json'))?.name === pkg) {
160
+ return dir;
161
+ }
162
+
128
163
  const parent = path.dirname(dir);
129
164
 
130
165
  if (parent === dir) {
@@ -189,3 +224,49 @@ function isDirectory(p) {
189
224
  return false;
190
225
  }
191
226
  }
227
+
228
+ function readJson(filePath) {
229
+ try {
230
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Returns true if the bare subpath import is declared in the package's
238
+ * `exports` field, meaning it is already valid for ESM and should not be
239
+ * rewritten.
240
+ */
241
+ function isSubpathExported(specifier, fromFile) {
242
+ const { pkg, subpath } = parseBare(specifier);
243
+ if (!subpath) return false;
244
+
245
+ const pkgDir = findPackageDir(pkg, path.dirname(fromFile));
246
+ if (!pkgDir) return false;
247
+
248
+ const exports = readJson(path.join(pkgDir, 'package.json'))?.exports;
249
+ if (!exports || typeof exports !== 'object') return false;
250
+
251
+ const key = './' + subpath;
252
+
253
+ if (key in exports) return true;
254
+
255
+ for (const [ pattern, valueTemplate ] of Object.entries(exports)) {
256
+ if (!pattern.includes('*') || typeof valueTemplate !== 'string') {
257
+ continue;
258
+ }
259
+
260
+ const escaped = pattern.replace(/[.+?^${}()|[\]\\]/g, '\\$&').replace(/\*/g, '(.*)');
261
+ const match = new RegExp('^' + escaped + '$').exec(key);
262
+
263
+ if (!match) continue;
264
+
265
+ // wildcard matched: only treat as already-valid if the resolved target
266
+ // is an actual file — a directory target still needs rewriting to index.js
267
+ const resolved = valueTemplate.replace(/\*/g, match[1]);
268
+ if (isFile(path.join(pkgDir, resolved))) return true;
269
+ }
270
+
271
+ return false;
272
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bpmn-io/codemods",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "A collection of codemods for the bpmn.io ecosystem.",
5
5
  "type": "module",
6
6
  "bin": {