@bpmn-io/codemods 0.1.0 → 0.2.0

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/bin/codemods.js CHANGED
File without changes
package/esm/README.md CHANGED
@@ -23,11 +23,19 @@ npx @bpmn-io/codemods esm <dir|file>
23
23
 
24
24
  # preview without writing
25
25
  npx @bpmn-io/codemods esm --dry-run src
26
+
27
+ # only rewrite imports of specific packages
28
+ npx @bpmn-io/codemods esm src --package diagram-js --package bpmn-js
26
29
  ```
27
30
 
28
31
  Run it from your project root so that `node_modules` (the installed,
29
32
  already-migrated package) is reachable for resolution.
30
33
 
34
+ When one or more `--package` (`-p`) options are given, only imports of those
35
+ packages are rewritten — relative imports and imports of other packages are
36
+ left untouched (and not reported as unresolved). This is useful for migrating
37
+ one dependency at a time, e.g. when only `diagram-js` has gone "ESM only".
38
+
31
39
  The command exits with code `1` if any import could not be resolved, so it can
32
40
  be used as a CI guard.
33
41
 
@@ -36,17 +44,17 @@ be used as a CI guard.
36
44
  For every `.js`, `.jsx`, `.mjs`, `.cjs`, `.ts` and `.tsx` file below the target
37
45
  (ignoring `node_modules` and `.git`):
38
46
 
39
- 1. Each static `import` / `export ... from` source, and every dynamic
40
- `import('...')` call with a string-literal argument, is checked against ES
41
- module resolution.
42
- 2. If the specifier does not resolve as written but does once an extension is
47
+ 1. Collect static `import` / `export ... from` source, and every dynamic
48
+ `import('...')` call with a string-literal argument - filter by (optional) `package` filters
49
+ 2. Matching imports are checked against ES module resolution.
50
+ 3. If the specifier does not resolve as written but does once an extension is
43
51
  appended, the extension is added.
44
52
  - Relative and bare (`node_modules`) specifiers are both handled.
45
53
  - TypeScript / JSX sources are written with a `.js` specifier (NodeNext
46
54
  convention), even though the file on disk is `.ts`/`.tsx`.
47
- 3. Specifiers that already carry an extension, and bare package roots
55
+ 4. Specifiers that already carry an extension, and bare package roots
48
56
  (e.g. `diagram-js`, `react`), are left untouched.
49
- 4. Anything that still cannot be resolved (e.g. a directory import that would
57
+ 5. Anything that still cannot be resolved (e.g. a directory import that would
50
58
  need `/index.js`, or a genuinely missing file) is **reported** for manual
51
59
  review — never silently changed.
52
60
 
package/esm/lib/cli.js CHANGED
@@ -9,8 +9,10 @@ native ES module resolution (e.g. when migrating to a package that ships
9
9
  "ESM only", such as diagram-js).
10
10
 
11
11
  Options:
12
- -d, --dry-run Report changes without writing files
13
- -h, --help Show this help
12
+ -p, --package <name> Only rewrite imports of this package (repeatable);
13
+ when given, relative and other packages are left alone
14
+ -d, --dry-run Report changes without writing files
15
+ -h, --help Show this help
14
16
 
15
17
  Exit code is 1 when any import could not be resolved.
16
18
  `;
@@ -21,23 +23,75 @@ Exit code is 1 when any import could not be resolved.
21
23
  * @param {string[]} argv - arguments after the mod name
22
24
  */
23
25
  export function run(argv) {
24
- const dryRun = argv.includes('--dry-run') || argv.includes('-d');
25
- const help = argv.includes('--help') || argv.includes('-h');
26
+ let options;
27
+
28
+ try {
29
+ options = parseArgs(argv);
30
+ } catch (error) {
31
+ process.stderr.write(`${error.message}\n\n${HELP}`);
32
+ process.exitCode = 1;
33
+ return;
34
+ }
35
+
36
+ const { help, dryRun, packages, target } = options;
26
37
 
27
38
  if (help) {
28
39
  process.stdout.write(HELP);
29
40
  return;
30
41
  }
31
42
 
32
- const target = argv.find((arg) => !arg.startsWith('-')) || process.cwd();
33
-
34
- const report = migrate(target, { dryRun });
43
+ const report = migrate(target, { dryRun, packages });
35
44
 
36
45
  print(report, dryRun);
37
46
 
38
47
  process.exitCode = report.unresolved.length || report.errors.length ? 1 : 0;
39
48
  }
40
49
 
50
+ /**
51
+ * Parse the `esm` mod CLI arguments.
52
+ *
53
+ * @param {string[]} argv
54
+ *
55
+ * @return {{ help: boolean, dryRun: boolean, packages: string[], target: string }}
56
+ *
57
+ * @throws {Error} on invalid usage, e.g. a `--package` without a name
58
+ */
59
+ export function parseArgs(argv) {
60
+ const options = { help: false, dryRun: false, packages: [], target: null };
61
+
62
+ for (let i = 0; i < argv.length; i++) {
63
+ const arg = argv[i];
64
+
65
+ if (arg === '--help' || arg === '-h') {
66
+ options.help = true;
67
+ } else if (arg === '--dry-run' || arg === '-d') {
68
+ options.dryRun = true;
69
+ } else if (arg === '--package' || arg === '-p') {
70
+ options.packages.push(requirePackage(arg, argv[++i]));
71
+ } else if (arg.startsWith('--package=')) {
72
+ options.packages.push(requirePackage('--package', arg.slice('--package='.length)));
73
+ } else if (!arg.startsWith('-') && options.target === null) {
74
+ options.target = arg;
75
+ }
76
+ }
77
+
78
+ options.target = options.target || process.cwd();
79
+
80
+ return options;
81
+ }
82
+
83
+ /**
84
+ * Guard against a `--package` option that is missing its value or accidentally
85
+ * swallowed a following flag (e.g. `-p -d`).
86
+ */
87
+ function requirePackage(flag, value) {
88
+ if (!value || value.startsWith('-')) {
89
+ throw new Error(`Option ${flag} requires a package name, got: ${value ?? '(none)'}`);
90
+ }
91
+
92
+ return value;
93
+ }
94
+
41
95
  function print(report, dryRun) {
42
96
  const rel = (file) => path.relative(process.cwd(), file) || file;
43
97
 
@@ -22,12 +22,13 @@ const IGNORED_DIRECTORIES = new Set([ 'node_modules', '.git' ]);
22
22
  * under native ES module resolution.
23
23
  *
24
24
  * @param {string} target - directory or file to migrate
25
- * @param {{ dryRun?: boolean }} [options]
25
+ * @param {{ dryRun?: boolean, packages?: string[] }} [options] - when
26
+ * `packages` is non-empty, only imports of those packages are rewritten
26
27
  *
27
28
  * @return {Report}
28
29
  */
29
30
  export function migrate(target, options = {}) {
30
- const { dryRun = false } = options;
31
+ const { dryRun = false, packages = [] } = options;
31
32
 
32
33
  const report = {
33
34
  filesScanned: 0,
@@ -42,7 +43,7 @@ export function migrate(target, options = {}) {
42
43
  report.filesScanned++;
43
44
 
44
45
  const code = fs.readFileSync(file, 'utf8');
45
- const result = transform(code, file);
46
+ const result = transform(code, file, { packages });
46
47
 
47
48
  if (result.error) {
48
49
  report.errors.push({ file, message: result.error.message });
@@ -47,14 +47,21 @@ const BARE_CANDIDATES = [
47
47
  *
48
48
  * @param {string} specifier - the raw import source, e.g. `diagram-js/lib/util/Elements`
49
49
  * @param {string} fromFile - absolute path of the importing file
50
+ * @param {{ packages?: string[] }} [options] - if `packages` is non-empty, only
51
+ * imports of those packages are considered; everything else is skipped
50
52
  *
51
53
  * @return {{ status: ('skip'|'rewrite'|'unresolved'), specifier: string }}
52
- * `skip` if already resolvable, `rewrite` with the new specifier if an
54
+ * `skip` if already resolvable or filtered out by `options.packages`, `rewrite` with the new specifier if an
53
55
  * extension was appended, `unresolved` if no `.js`-style extension resolves.
54
56
  */
55
- export function resolveImport(specifier, fromFile) {
57
+ export function resolveImport(specifier, fromFile, options = {}) {
56
58
  const bare = isBare(specifier);
57
59
 
60
+ // when restricted to specific packages, only their (bare) imports apply
61
+ if (options.packages && options.packages.length && !targetsPackage(specifier, bare, options.packages)) {
62
+ return { status: 'skip', specifier };
63
+ }
64
+
58
65
  // bare package roots (e.g. 'react', 'diagram-js') resolve via main/exports
59
66
  if (bare && isBareRoot(specifier)) {
60
67
  return { status: 'skip', specifier };
@@ -153,6 +160,13 @@ function isBareRoot(specifier) {
153
160
  return parseBare(specifier).subpath === '';
154
161
  }
155
162
 
163
+ /**
164
+ * Whether the specifier imports from one of the given packages.
165
+ */
166
+ function targetsPackage(specifier, bare, packages) {
167
+ return bare && packages.includes(parseBare(specifier).pkg);
168
+ }
169
+
156
170
  function specifierExtension(specifier) {
157
171
  const segment = specifier.split('/').pop();
158
172
  const dot = segment.lastIndexOf('.');
@@ -13,6 +13,7 @@ import { resolveImport } from './resolve.js';
13
13
  *
14
14
  * @param {string} code - the file contents
15
15
  * @param {string} filename - absolute path of the file (used for resolution)
16
+ * @param {{ packages?: string[] }} [options] - passed through to the resolver
16
17
  *
17
18
  * @return {{
18
19
  * code: string,
@@ -22,7 +23,7 @@ import { resolveImport } from './resolve.js';
22
23
  * error: (Error|null)
23
24
  * }}
24
25
  */
25
- export function transform(code, filename) {
26
+ export function transform(code, filename, options = {}) {
26
27
  let ast;
27
28
 
28
29
  try {
@@ -40,7 +41,7 @@ export function transform(code, filename) {
40
41
  const unresolved = [];
41
42
 
42
43
  for (const source of importSources(ast)) {
43
- const result = resolveImport(source.value, filename);
44
+ const result = resolveImport(source.value, filename, options);
44
45
  const line = source.loc.start.line;
45
46
 
46
47
  if (result.status === 'rewrite') {
package/package.json CHANGED
@@ -1,11 +1,25 @@
1
1
  {
2
2
  "name": "@bpmn-io/codemods",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A collection of codemods for the bpmn.io ecosystem.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "codemods": "bin/codemods.js"
8
8
  },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/bpmn-io/diagram-js"
12
+ },
13
+ "author": {
14
+ "name": "Nico Rehwaldt",
15
+ "url": "https://github.com/nikku"
16
+ },
17
+ "contributors": [
18
+ {
19
+ "name": "bpmn.io contributors",
20
+ "url": "https://github.com/bpmn-io"
21
+ }
22
+ ],
9
23
  "scripts": {
10
24
  "all": "npm run test",
11
25
  "test": "mocha"
@@ -26,7 +40,7 @@
26
40
  "@babel/parser": "^7.24.0"
27
41
  },
28
42
  "devDependencies": {
29
- "chai": "^5.1.0",
30
- "mocha": "^10.4.0"
43
+ "chai": "^6.2.2",
44
+ "mocha": "^11.7.6"
31
45
  }
32
46
  }