@motif-js/migrate 2.0.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/README.md +66 -0
- package/bin/cli.js +11 -0
- package/dist/chunk-SSSWTBUF.js +16 -0
- package/dist/chunk-SSSWTBUF.js.map +1 -0
- package/dist/cli.cjs +131 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +15 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +112 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +23 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +77 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/package.json +51 -0
- package/src/cli.ts +125 -0
- package/src/index.ts +11 -0
- package/src/transforms/rename-v2.test.ts +153 -0
- package/src/transforms/rename-v2.ts +84 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# @motif-js/migrate
|
|
2
|
+
|
|
3
|
+
> Codemod toolkit for motif-js.
|
|
4
|
+
|
|
5
|
+
Rewrites v1 motif-js import specifiers to their v2 names in every file under a given path.
|
|
6
|
+
|
|
7
|
+
## Use
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
# In your project root, with v1 imports:
|
|
11
|
+
npx @motif-js/migrate rename-v2
|
|
12
|
+
|
|
13
|
+
# Or point at a subtree:
|
|
14
|
+
npx @motif-js/migrate rename-v2 src/
|
|
15
|
+
|
|
16
|
+
# Preview without writing:
|
|
17
|
+
npx @motif-js/migrate rename-v2 --dry-run
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## What it does
|
|
21
|
+
|
|
22
|
+
| Before | After |
|
|
23
|
+
| ---------------------------------- | ------------------- |
|
|
24
|
+
| `@motif-js/react-web` | `@motif-js/react` |
|
|
25
|
+
| `@motif-js/react` (aggregator) | `motif-js` |
|
|
26
|
+
| `@motif-js/react-native` | (unchanged) |
|
|
27
|
+
| `@motif-js/react/server` | (unchanged subpath) |
|
|
28
|
+
| `@motif-js/react/tanstack-virtual` | (unchanged subpath) |
|
|
29
|
+
|
|
30
|
+
Covers every form an import specifier appears in:
|
|
31
|
+
|
|
32
|
+
- `import { X } from '…'` (named, default, namespace, type-only)
|
|
33
|
+
- `import('…')` (dynamic)
|
|
34
|
+
- `require('…')` (CommonJS)
|
|
35
|
+
- `dependencies` / `peerDependencies` / etc. keys in `package.json`
|
|
36
|
+
- Code fences in `.md` / `.mdx`
|
|
37
|
+
|
|
38
|
+
## Files scanned by default
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
**/*.{ts,tsx,js,jsx,mjs,cjs,md,mdx,json}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Skips: `node_modules`, `dist`, `.next`, `.vorge`, `.turbo`, `.cache`, `build`, `out`, `coverage`, `__visual__`.
|
|
45
|
+
|
|
46
|
+
## Programmatic API
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { applyRenameV2, needsRenameV2 } from '@motif-js/migrate';
|
|
50
|
+
|
|
51
|
+
const src = `import { Box } from '@motif-js/react-web';`;
|
|
52
|
+
if (needsRenameV2(src)) {
|
|
53
|
+
const out = applyRenameV2(src);
|
|
54
|
+
// → `import { Box } from '@motif-js/react';`
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`applyRenameV2` is a string-in, string-out function. Pass any kind of file content — TypeScript, JavaScript, MDX, JSON — it matches import specifier strings, not parsed AST nodes.
|
|
59
|
+
|
|
60
|
+
## Docs
|
|
61
|
+
|
|
62
|
+
<https://usemotif.dev/migrating/v1-to-v2>
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
[MIT](../../LICENSE)
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { argv, exit } from 'node:process';
|
|
3
|
+
import { main } from '../dist/cli.js';
|
|
4
|
+
|
|
5
|
+
main(argv.slice(2)).then(
|
|
6
|
+
(code) => exit(code),
|
|
7
|
+
(err) => {
|
|
8
|
+
process.stderr.write(`${err instanceof Error ? (err.stack ?? err.message) : String(err)}\n`);
|
|
9
|
+
exit(1);
|
|
10
|
+
},
|
|
11
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// src/transforms/rename-v2.ts
|
|
2
|
+
var SOURCE_PATTERN = /@motif-js\/react-web|@motif-js\/react(?![-\w/])/g;
|
|
3
|
+
function replaceSource(match) {
|
|
4
|
+
if (match === "@motif-js/react-web") return "@motif-js/react";
|
|
5
|
+
return "motif-js";
|
|
6
|
+
}
|
|
7
|
+
function applyRenameV2(source) {
|
|
8
|
+
return source.replace(SOURCE_PATTERN, replaceSource);
|
|
9
|
+
}
|
|
10
|
+
function needsRenameV2(source) {
|
|
11
|
+
return /@motif-js\/react-web|@motif-js\/react(?![-\w/])/.test(source);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export { applyRenameV2, needsRenameV2 };
|
|
15
|
+
//# sourceMappingURL=chunk-SSSWTBUF.js.map
|
|
16
|
+
//# sourceMappingURL=chunk-SSSWTBUF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transforms/rename-v2.ts"],"names":[],"mappings":";AA8CA,IAAM,cAAA,GAAiB,kDAAA;AAEvB,SAAS,cAAc,KAAA,EAAuB;AAC5C,EAAA,IAAI,KAAA,KAAU,uBAAuB,OAAO,iBAAA;AAG5C,EAAA,OAAO,UAAA;AACT;AAgBO,SAAS,cAAc,MAAA,EAAwB;AACpD,EAAA,OAAO,MAAA,CAAO,OAAA,CAAQ,cAAA,EAAgB,aAAa,CAAA;AACrD;AAOO,SAAS,cAAc,MAAA,EAAyB;AAIrD,EAAA,OAAO,iDAAA,CAAkD,KAAK,MAAM,CAAA;AACtE","file":"chunk-SSSWTBUF.js","sourcesContent":["/**\n * `rename-v2` codemod: rewrite v1 motif-js import specifiers to their\n * v2 names.\n *\n * | v1 | v2 |\n * |--------------------------|---------------------|\n * | `@motif-js/react-web` | `@motif-js/react` |\n * | `@motif-js/react` | `motif-js` |\n * | `@motif-js/react-native` | `@motif-js/react-native` (unchanged) |\n *\n * Touches only **import specifier strings** inside source files —\n * import statements, `require()` calls, dynamic `import()`, and the\n * dependency keys of `package.json`. Comments and string literals\n * elsewhere are left alone.\n *\n * ## Sequencing trap\n *\n * The two rewrites overlap. If we naively replace `@motif-js/react` →\n * `motif-js` first, then `@motif-js/react-web` → `@motif-js/react`,\n * the first pass already destroyed every `@motif-js/react` occurrence\n * — including the `@motif-js/react` part of `@motif-js/react-web`,\n * leaving garbage like `motif-js-web`.\n *\n * The safe order is the opposite: `@motif-js/react-web` →\n * `@motif-js/react` FIRST, then `@motif-js/react` → `motif-js`. After\n * step one, every occurrence of `@motif-js/react` in the file refers\n * to the v2 DOM bindings (just renamed from react-web), and step two\n * can safely promote any *other* `@motif-js/react` references (the\n * v1 aggregator) up to `motif-js`.\n *\n * That's still wrong, because step two would clobber the\n * just-renamed `@motif-js/react` references too. So in practice we\n * use a one-pass single regex with alternation that matches whichever\n * of the two source names appears at the call site:\n *\n * /@motif-js\\/react-web|@motif-js\\/react(?![-\\w/])/g\n *\n * — for each match, decide the replacement based on the matched text.\n * react-web → @motif-js/react, bare react → motif-js. The negative\n * lookahead `(?![-\\w/])` on the bare-react branch ensures we don't\n * catch react-web / react-native / react-foo (the `-` exclusion) or\n * the `@motif-js/react/server` and `@motif-js/react/tanstack-virtual`\n * subpaths (the `/` exclusion). Those subpath exports still belong\n * to the renamed DOM bindings package and must survive untouched.\n */\n\nconst SOURCE_PATTERN = /@motif-js\\/react-web|@motif-js\\/react(?![-\\w/])/g;\n\nfunction replaceSource(match: string): string {\n if (match === '@motif-js/react-web') return '@motif-js/react';\n // The other branch of the alternation. Anchored with negative\n // lookahead so it can only be the bare aggregator name.\n return 'motif-js';\n}\n\n/**\n * Apply the rename-v2 transform to a source string. The matching is\n * applied to the **entire** text, regardless of file kind — that's\n * intentional. Import-specifier strings have a unique enough shape\n * (the leading `@motif-js/` namespace) that false positives in prose\n * are rare. For `.md` / `.mdx` / `.json` files this lets the same\n * transform handle install snippets, code fences, and the\n * `dependencies` keys of package.json without separate parsers.\n *\n * If the input is `package.json`, sub-path imports like\n * `@motif-js/react/server` survive (the lookahead spares them) — the\n * `/server` and `/tanstack-virtual` exports still belong to the\n * renamed DOM bindings package.\n */\nexport function applyRenameV2(source: string): string {\n return source.replace(SOURCE_PATTERN, replaceSource);\n}\n\n/**\n * Convenience predicate: does the source contain anything the\n * transform would change? Used to skip the write step for unaffected\n * files (saves filesystem churn + leaves mtimes alone).\n */\nexport function needsRenameV2(source: string): boolean {\n // `SOURCE_PATTERN` is `g`-flagged, so reuse via `.test()` would mutate\n // its `.lastIndex` and corrupt later calls. Build a fresh non-global\n // regex per check.\n return /@motif-js\\/react-web|@motif-js\\/react(?![-\\w/])/.test(source);\n}\n"]}
|
package/dist/cli.cjs
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var promises = require('fs/promises');
|
|
5
|
+
var path = require('path');
|
|
6
|
+
var process = require('process');
|
|
7
|
+
var fg = require('fast-glob');
|
|
8
|
+
|
|
9
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
10
|
+
|
|
11
|
+
var fg__default = /*#__PURE__*/_interopDefault(fg);
|
|
12
|
+
|
|
13
|
+
// src/transforms/rename-v2.ts
|
|
14
|
+
var SOURCE_PATTERN = /@motif-js\/react-web|@motif-js\/react(?![-\w/])/g;
|
|
15
|
+
function replaceSource(match) {
|
|
16
|
+
if (match === "@motif-js/react-web") return "@motif-js/react";
|
|
17
|
+
return "motif-js";
|
|
18
|
+
}
|
|
19
|
+
function applyRenameV2(source) {
|
|
20
|
+
return source.replace(SOURCE_PATTERN, replaceSource);
|
|
21
|
+
}
|
|
22
|
+
function needsRenameV2(source) {
|
|
23
|
+
return /@motif-js\/react-web|@motif-js\/react(?![-\w/])/.test(source);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// src/cli.ts
|
|
27
|
+
var DEFAULT_GLOBS = [
|
|
28
|
+
"**/*.{ts,tsx,js,jsx,mjs,cjs,md,mdx,json}",
|
|
29
|
+
"!**/node_modules/**",
|
|
30
|
+
"!**/dist/**",
|
|
31
|
+
"!**/.next/**",
|
|
32
|
+
"!**/.vorge/**",
|
|
33
|
+
"!**/.turbo/**",
|
|
34
|
+
"!**/.cache/**",
|
|
35
|
+
"!**/build/**",
|
|
36
|
+
"!**/out/**",
|
|
37
|
+
"!**/coverage/**",
|
|
38
|
+
"!**/__visual__/**"
|
|
39
|
+
];
|
|
40
|
+
var HELP = `motif-js-migrate \u2014 codemod toolkit for motif-js
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
motif-js-migrate rename-v2 [path] Rewrite v1 motif-js import specifiers
|
|
44
|
+
to their v2 names in every file under
|
|
45
|
+
[path] (default: cwd).
|
|
46
|
+
motif-js-migrate --help Show this message.
|
|
47
|
+
|
|
48
|
+
Flags:
|
|
49
|
+
--dry-run Print the files that would change without writing them.
|
|
50
|
+
|
|
51
|
+
Rename map:
|
|
52
|
+
@motif-js/react-web \u2192 @motif-js/react
|
|
53
|
+
@motif-js/react \u2192 motif-js
|
|
54
|
+
@motif-js/react-native \u2192 (unchanged)
|
|
55
|
+
|
|
56
|
+
Subpath imports (@motif-js/react/server, @motif-js/react/tanstack-virtual)
|
|
57
|
+
stay on @motif-js/react.
|
|
58
|
+
`;
|
|
59
|
+
function parseArgs(argv) {
|
|
60
|
+
let command = null;
|
|
61
|
+
let path = ".";
|
|
62
|
+
let dryRun = false;
|
|
63
|
+
let help = false;
|
|
64
|
+
let positionalIndex = 0;
|
|
65
|
+
for (const arg of argv) {
|
|
66
|
+
if (arg === "--help" || arg === "-h") {
|
|
67
|
+
help = true;
|
|
68
|
+
} else if (arg === "--dry-run") {
|
|
69
|
+
dryRun = true;
|
|
70
|
+
} else if (arg.startsWith("--")) {
|
|
71
|
+
process.stderr.write(`unknown flag: ${arg}
|
|
72
|
+
`);
|
|
73
|
+
process.exit(2);
|
|
74
|
+
} else if (positionalIndex === 0) {
|
|
75
|
+
command = arg;
|
|
76
|
+
positionalIndex++;
|
|
77
|
+
} else if (positionalIndex === 1) {
|
|
78
|
+
path = arg;
|
|
79
|
+
positionalIndex++;
|
|
80
|
+
} else {
|
|
81
|
+
process.stderr.write(`unexpected positional argument: ${arg}
|
|
82
|
+
`);
|
|
83
|
+
process.exit(2);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { command, path, dryRun, help };
|
|
87
|
+
}
|
|
88
|
+
async function runRenameV2(rootArg, dryRun) {
|
|
89
|
+
const root = path.resolve(rootArg);
|
|
90
|
+
const files = await fg__default.default(DEFAULT_GLOBS, { cwd: root, absolute: true, dot: false });
|
|
91
|
+
let changed = 0;
|
|
92
|
+
for (const absPath of files) {
|
|
93
|
+
const src = await promises.readFile(absPath, "utf8");
|
|
94
|
+
if (!needsRenameV2(src)) continue;
|
|
95
|
+
const out = applyRenameV2(src);
|
|
96
|
+
if (out === src) continue;
|
|
97
|
+
const rel = path.relative(root, absPath);
|
|
98
|
+
if (dryRun) {
|
|
99
|
+
process.stdout.write(`would change: ${rel}
|
|
100
|
+
`);
|
|
101
|
+
} else {
|
|
102
|
+
await promises.writeFile(absPath, out, "utf8");
|
|
103
|
+
process.stdout.write(`changed: ${rel}
|
|
104
|
+
`);
|
|
105
|
+
}
|
|
106
|
+
changed++;
|
|
107
|
+
}
|
|
108
|
+
process.stdout.write(
|
|
109
|
+
`${dryRun ? "would change" : "changed"} ${changed} file${changed === 1 ? "" : "s"}.
|
|
110
|
+
`
|
|
111
|
+
);
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
async function main(argv) {
|
|
115
|
+
const args = parseArgs(argv);
|
|
116
|
+
if (args.help || args.command === null) {
|
|
117
|
+
process.stdout.write(HELP);
|
|
118
|
+
return 0;
|
|
119
|
+
}
|
|
120
|
+
if (args.command === "rename-v2") {
|
|
121
|
+
return runRenameV2(args.path, args.dryRun);
|
|
122
|
+
}
|
|
123
|
+
process.stderr.write(`unknown command: ${args.command}
|
|
124
|
+
`);
|
|
125
|
+
process.stderr.write(HELP);
|
|
126
|
+
return 2;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
exports.main = main;
|
|
130
|
+
//# sourceMappingURL=cli.cjs.map
|
|
131
|
+
//# sourceMappingURL=cli.cjs.map
|
package/dist/cli.cjs.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transforms/rename-v2.ts","../src/cli.ts"],"names":["stderr","exit","resolve","fg","readFile","relative","stdout","writeFile"],"mappings":"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","file":"cli.cjs","sourcesContent":["/**\n * `rename-v2` codemod: rewrite v1 motif-js import specifiers to their\n * v2 names.\n *\n * | v1 | v2 |\n * |--------------------------|---------------------|\n * | `@motif-js/react-web` | `@motif-js/react` |\n * | `@motif-js/react` | `motif-js` |\n * | `@motif-js/react-native` | `@motif-js/react-native` (unchanged) |\n *\n * Touches only **import specifier strings** inside source files —\n * import statements, `require()` calls, dynamic `import()`, and the\n * dependency keys of `package.json`. Comments and string literals\n * elsewhere are left alone.\n *\n * ## Sequencing trap\n *\n * The two rewrites overlap. If we naively replace `@motif-js/react` →\n * `motif-js` first, then `@motif-js/react-web` → `@motif-js/react`,\n * the first pass already destroyed every `@motif-js/react` occurrence\n * — including the `@motif-js/react` part of `@motif-js/react-web`,\n * leaving garbage like `motif-js-web`.\n *\n * The safe order is the opposite: `@motif-js/react-web` →\n * `@motif-js/react` FIRST, then `@motif-js/react` → `motif-js`. After\n * step one, every occurrence of `@motif-js/react` in the file refers\n * to the v2 DOM bindings (just renamed from react-web), and step two\n * can safely promote any *other* `@motif-js/react` references (the\n * v1 aggregator) up to `motif-js`.\n *\n * That's still wrong, because step two would clobber the\n * just-renamed `@motif-js/react` references too. So in practice we\n * use a one-pass single regex with alternation that matches whichever\n * of the two source names appears at the call site:\n *\n * /@motif-js\\/react-web|@motif-js\\/react(?![-\\w/])/g\n *\n * — for each match, decide the replacement based on the matched text.\n * react-web → @motif-js/react, bare react → motif-js. The negative\n * lookahead `(?![-\\w/])` on the bare-react branch ensures we don't\n * catch react-web / react-native / react-foo (the `-` exclusion) or\n * the `@motif-js/react/server` and `@motif-js/react/tanstack-virtual`\n * subpaths (the `/` exclusion). Those subpath exports still belong\n * to the renamed DOM bindings package and must survive untouched.\n */\n\nconst SOURCE_PATTERN = /@motif-js\\/react-web|@motif-js\\/react(?![-\\w/])/g;\n\nfunction replaceSource(match: string): string {\n if (match === '@motif-js/react-web') return '@motif-js/react';\n // The other branch of the alternation. Anchored with negative\n // lookahead so it can only be the bare aggregator name.\n return 'motif-js';\n}\n\n/**\n * Apply the rename-v2 transform to a source string. The matching is\n * applied to the **entire** text, regardless of file kind — that's\n * intentional. Import-specifier strings have a unique enough shape\n * (the leading `@motif-js/` namespace) that false positives in prose\n * are rare. For `.md` / `.mdx` / `.json` files this lets the same\n * transform handle install snippets, code fences, and the\n * `dependencies` keys of package.json without separate parsers.\n *\n * If the input is `package.json`, sub-path imports like\n * `@motif-js/react/server` survive (the lookahead spares them) — the\n * `/server` and `/tanstack-virtual` exports still belong to the\n * renamed DOM bindings package.\n */\nexport function applyRenameV2(source: string): string {\n return source.replace(SOURCE_PATTERN, replaceSource);\n}\n\n/**\n * Convenience predicate: does the source contain anything the\n * transform would change? Used to skip the write step for unaffected\n * files (saves filesystem churn + leaves mtimes alone).\n */\nexport function needsRenameV2(source: string): boolean {\n // `SOURCE_PATTERN` is `g`-flagged, so reuse via `.test()` would mutate\n // its `.lastIndex` and corrupt later calls. Build a fresh non-global\n // regex per check.\n return /@motif-js\\/react-web|@motif-js\\/react(?![-\\w/])/.test(source);\n}\n","#!/usr/bin/env node\n/**\n * `motif-js-migrate` CLI.\n *\n * Usage:\n * motif-js-migrate rename-v2 [path] [--dry-run]\n * motif-js-migrate --help\n *\n * Default path is the current working directory. The transform walks\n * matching files via fast-glob, applies `applyRenameV2`, and writes\n * back any file whose content changed.\n */\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { exit, stderr, stdout } from 'node:process';\nimport fg from 'fast-glob';\nimport { applyRenameV2, needsRenameV2 } from './transforms/rename-v2.js';\n\ninterface ParsedArgs {\n readonly command: string | null;\n readonly path: string;\n readonly dryRun: boolean;\n readonly help: boolean;\n}\n\nconst DEFAULT_GLOBS: string[] = [\n '**/*.{ts,tsx,js,jsx,mjs,cjs,md,mdx,json}',\n '!**/node_modules/**',\n '!**/dist/**',\n '!**/.next/**',\n '!**/.vorge/**',\n '!**/.turbo/**',\n '!**/.cache/**',\n '!**/build/**',\n '!**/out/**',\n '!**/coverage/**',\n '!**/__visual__/**',\n];\n\nconst HELP = `motif-js-migrate — codemod toolkit for motif-js\n\nUsage:\n motif-js-migrate rename-v2 [path] Rewrite v1 motif-js import specifiers\n to their v2 names in every file under\n [path] (default: cwd).\n motif-js-migrate --help Show this message.\n\nFlags:\n --dry-run Print the files that would change without writing them.\n\nRename map:\n @motif-js/react-web → @motif-js/react\n @motif-js/react → motif-js\n @motif-js/react-native → (unchanged)\n\nSubpath imports (@motif-js/react/server, @motif-js/react/tanstack-virtual)\nstay on @motif-js/react.\n`;\n\nfunction parseArgs(argv: readonly string[]): ParsedArgs {\n let command: string | null = null;\n let path = '.';\n let dryRun = false;\n let help = false;\n let positionalIndex = 0;\n for (const arg of argv) {\n if (arg === '--help' || arg === '-h') {\n help = true;\n } else if (arg === '--dry-run') {\n dryRun = true;\n } else if (arg.startsWith('--')) {\n stderr.write(`unknown flag: ${arg}\\n`);\n exit(2);\n } else if (positionalIndex === 0) {\n command = arg;\n positionalIndex++;\n } else if (positionalIndex === 1) {\n path = arg;\n positionalIndex++;\n } else {\n stderr.write(`unexpected positional argument: ${arg}\\n`);\n exit(2);\n }\n }\n return { command, path, dryRun, help };\n}\n\nasync function runRenameV2(rootArg: string, dryRun: boolean): Promise<number> {\n const root = resolve(rootArg);\n const files = await fg(DEFAULT_GLOBS, { cwd: root, absolute: true, dot: false });\n let changed = 0;\n for (const absPath of files) {\n const src = await readFile(absPath, 'utf8');\n if (!needsRenameV2(src)) continue;\n const out = applyRenameV2(src);\n if (out === src) continue;\n const rel = relative(root, absPath);\n if (dryRun) {\n stdout.write(`would change: ${rel}\\n`);\n } else {\n await writeFile(absPath, out, 'utf8');\n stdout.write(`changed: ${rel}\\n`);\n }\n changed++;\n }\n stdout.write(\n `${dryRun ? 'would change' : 'changed'} ${changed} file${changed === 1 ? '' : 's'}.\\n`,\n );\n return 0;\n}\n\nexport async function main(argv: readonly string[]): Promise<number> {\n const args = parseArgs(argv);\n if (args.help || args.command === null) {\n stdout.write(HELP);\n return 0;\n }\n if (args.command === 'rename-v2') {\n return runRenameV2(args.path, args.dryRun);\n }\n stderr.write(`unknown command: ${args.command}\\n`);\n stderr.write(HELP);\n return 2;\n}\n"]}
|
package/dist/cli.d.cts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `motif-js-migrate` CLI.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* motif-js-migrate rename-v2 [path] [--dry-run]
|
|
7
|
+
* motif-js-migrate --help
|
|
8
|
+
*
|
|
9
|
+
* Default path is the current working directory. The transform walks
|
|
10
|
+
* matching files via fast-glob, applies `applyRenameV2`, and writes
|
|
11
|
+
* back any file whose content changed.
|
|
12
|
+
*/
|
|
13
|
+
declare function main(argv: readonly string[]): Promise<number>;
|
|
14
|
+
|
|
15
|
+
export { main };
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `motif-js-migrate` CLI.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* motif-js-migrate rename-v2 [path] [--dry-run]
|
|
7
|
+
* motif-js-migrate --help
|
|
8
|
+
*
|
|
9
|
+
* Default path is the current working directory. The transform walks
|
|
10
|
+
* matching files via fast-glob, applies `applyRenameV2`, and writes
|
|
11
|
+
* back any file whose content changed.
|
|
12
|
+
*/
|
|
13
|
+
declare function main(argv: readonly string[]): Promise<number>;
|
|
14
|
+
|
|
15
|
+
export { main };
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { needsRenameV2, applyRenameV2 } from './chunk-SSSWTBUF.js';
|
|
3
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
4
|
+
import { resolve, relative } from 'path';
|
|
5
|
+
import { stdout, stderr, exit } from 'process';
|
|
6
|
+
import fg from 'fast-glob';
|
|
7
|
+
|
|
8
|
+
var DEFAULT_GLOBS = [
|
|
9
|
+
"**/*.{ts,tsx,js,jsx,mjs,cjs,md,mdx,json}",
|
|
10
|
+
"!**/node_modules/**",
|
|
11
|
+
"!**/dist/**",
|
|
12
|
+
"!**/.next/**",
|
|
13
|
+
"!**/.vorge/**",
|
|
14
|
+
"!**/.turbo/**",
|
|
15
|
+
"!**/.cache/**",
|
|
16
|
+
"!**/build/**",
|
|
17
|
+
"!**/out/**",
|
|
18
|
+
"!**/coverage/**",
|
|
19
|
+
"!**/__visual__/**"
|
|
20
|
+
];
|
|
21
|
+
var HELP = `motif-js-migrate \u2014 codemod toolkit for motif-js
|
|
22
|
+
|
|
23
|
+
Usage:
|
|
24
|
+
motif-js-migrate rename-v2 [path] Rewrite v1 motif-js import specifiers
|
|
25
|
+
to their v2 names in every file under
|
|
26
|
+
[path] (default: cwd).
|
|
27
|
+
motif-js-migrate --help Show this message.
|
|
28
|
+
|
|
29
|
+
Flags:
|
|
30
|
+
--dry-run Print the files that would change without writing them.
|
|
31
|
+
|
|
32
|
+
Rename map:
|
|
33
|
+
@motif-js/react-web \u2192 @motif-js/react
|
|
34
|
+
@motif-js/react \u2192 motif-js
|
|
35
|
+
@motif-js/react-native \u2192 (unchanged)
|
|
36
|
+
|
|
37
|
+
Subpath imports (@motif-js/react/server, @motif-js/react/tanstack-virtual)
|
|
38
|
+
stay on @motif-js/react.
|
|
39
|
+
`;
|
|
40
|
+
function parseArgs(argv) {
|
|
41
|
+
let command = null;
|
|
42
|
+
let path = ".";
|
|
43
|
+
let dryRun = false;
|
|
44
|
+
let help = false;
|
|
45
|
+
let positionalIndex = 0;
|
|
46
|
+
for (const arg of argv) {
|
|
47
|
+
if (arg === "--help" || arg === "-h") {
|
|
48
|
+
help = true;
|
|
49
|
+
} else if (arg === "--dry-run") {
|
|
50
|
+
dryRun = true;
|
|
51
|
+
} else if (arg.startsWith("--")) {
|
|
52
|
+
stderr.write(`unknown flag: ${arg}
|
|
53
|
+
`);
|
|
54
|
+
exit(2);
|
|
55
|
+
} else if (positionalIndex === 0) {
|
|
56
|
+
command = arg;
|
|
57
|
+
positionalIndex++;
|
|
58
|
+
} else if (positionalIndex === 1) {
|
|
59
|
+
path = arg;
|
|
60
|
+
positionalIndex++;
|
|
61
|
+
} else {
|
|
62
|
+
stderr.write(`unexpected positional argument: ${arg}
|
|
63
|
+
`);
|
|
64
|
+
exit(2);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { command, path, dryRun, help };
|
|
68
|
+
}
|
|
69
|
+
async function runRenameV2(rootArg, dryRun) {
|
|
70
|
+
const root = resolve(rootArg);
|
|
71
|
+
const files = await fg(DEFAULT_GLOBS, { cwd: root, absolute: true, dot: false });
|
|
72
|
+
let changed = 0;
|
|
73
|
+
for (const absPath of files) {
|
|
74
|
+
const src = await readFile(absPath, "utf8");
|
|
75
|
+
if (!needsRenameV2(src)) continue;
|
|
76
|
+
const out = applyRenameV2(src);
|
|
77
|
+
if (out === src) continue;
|
|
78
|
+
const rel = relative(root, absPath);
|
|
79
|
+
if (dryRun) {
|
|
80
|
+
stdout.write(`would change: ${rel}
|
|
81
|
+
`);
|
|
82
|
+
} else {
|
|
83
|
+
await writeFile(absPath, out, "utf8");
|
|
84
|
+
stdout.write(`changed: ${rel}
|
|
85
|
+
`);
|
|
86
|
+
}
|
|
87
|
+
changed++;
|
|
88
|
+
}
|
|
89
|
+
stdout.write(
|
|
90
|
+
`${dryRun ? "would change" : "changed"} ${changed} file${changed === 1 ? "" : "s"}.
|
|
91
|
+
`
|
|
92
|
+
);
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
async function main(argv) {
|
|
96
|
+
const args = parseArgs(argv);
|
|
97
|
+
if (args.help || args.command === null) {
|
|
98
|
+
stdout.write(HELP);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
if (args.command === "rename-v2") {
|
|
102
|
+
return runRenameV2(args.path, args.dryRun);
|
|
103
|
+
}
|
|
104
|
+
stderr.write(`unknown command: ${args.command}
|
|
105
|
+
`);
|
|
106
|
+
stderr.write(HELP);
|
|
107
|
+
return 2;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export { main };
|
|
111
|
+
//# sourceMappingURL=cli.js.map
|
|
112
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts"],"names":[],"mappings":"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","file":"cli.js","sourcesContent":["#!/usr/bin/env node\n/**\n * `motif-js-migrate` CLI.\n *\n * Usage:\n * motif-js-migrate rename-v2 [path] [--dry-run]\n * motif-js-migrate --help\n *\n * Default path is the current working directory. The transform walks\n * matching files via fast-glob, applies `applyRenameV2`, and writes\n * back any file whose content changed.\n */\n\nimport { readFile, writeFile } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { exit, stderr, stdout } from 'node:process';\nimport fg from 'fast-glob';\nimport { applyRenameV2, needsRenameV2 } from './transforms/rename-v2.js';\n\ninterface ParsedArgs {\n readonly command: string | null;\n readonly path: string;\n readonly dryRun: boolean;\n readonly help: boolean;\n}\n\nconst DEFAULT_GLOBS: string[] = [\n '**/*.{ts,tsx,js,jsx,mjs,cjs,md,mdx,json}',\n '!**/node_modules/**',\n '!**/dist/**',\n '!**/.next/**',\n '!**/.vorge/**',\n '!**/.turbo/**',\n '!**/.cache/**',\n '!**/build/**',\n '!**/out/**',\n '!**/coverage/**',\n '!**/__visual__/**',\n];\n\nconst HELP = `motif-js-migrate — codemod toolkit for motif-js\n\nUsage:\n motif-js-migrate rename-v2 [path] Rewrite v1 motif-js import specifiers\n to their v2 names in every file under\n [path] (default: cwd).\n motif-js-migrate --help Show this message.\n\nFlags:\n --dry-run Print the files that would change without writing them.\n\nRename map:\n @motif-js/react-web → @motif-js/react\n @motif-js/react → motif-js\n @motif-js/react-native → (unchanged)\n\nSubpath imports (@motif-js/react/server, @motif-js/react/tanstack-virtual)\nstay on @motif-js/react.\n`;\n\nfunction parseArgs(argv: readonly string[]): ParsedArgs {\n let command: string | null = null;\n let path = '.';\n let dryRun = false;\n let help = false;\n let positionalIndex = 0;\n for (const arg of argv) {\n if (arg === '--help' || arg === '-h') {\n help = true;\n } else if (arg === '--dry-run') {\n dryRun = true;\n } else if (arg.startsWith('--')) {\n stderr.write(`unknown flag: ${arg}\\n`);\n exit(2);\n } else if (positionalIndex === 0) {\n command = arg;\n positionalIndex++;\n } else if (positionalIndex === 1) {\n path = arg;\n positionalIndex++;\n } else {\n stderr.write(`unexpected positional argument: ${arg}\\n`);\n exit(2);\n }\n }\n return { command, path, dryRun, help };\n}\n\nasync function runRenameV2(rootArg: string, dryRun: boolean): Promise<number> {\n const root = resolve(rootArg);\n const files = await fg(DEFAULT_GLOBS, { cwd: root, absolute: true, dot: false });\n let changed = 0;\n for (const absPath of files) {\n const src = await readFile(absPath, 'utf8');\n if (!needsRenameV2(src)) continue;\n const out = applyRenameV2(src);\n if (out === src) continue;\n const rel = relative(root, absPath);\n if (dryRun) {\n stdout.write(`would change: ${rel}\\n`);\n } else {\n await writeFile(absPath, out, 'utf8');\n stdout.write(`changed: ${rel}\\n`);\n }\n changed++;\n }\n stdout.write(\n `${dryRun ? 'would change' : 'changed'} ${changed} file${changed === 1 ? '' : 's'}.\\n`,\n );\n return 0;\n}\n\nexport async function main(argv: readonly string[]): Promise<number> {\n const args = parseArgs(argv);\n if (args.help || args.command === null) {\n stdout.write(HELP);\n return 0;\n }\n if (args.command === 'rename-v2') {\n return runRenameV2(args.path, args.dryRun);\n }\n stderr.write(`unknown command: ${args.command}\\n`);\n stderr.write(HELP);\n return 2;\n}\n"]}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/transforms/rename-v2.ts
|
|
4
|
+
var SOURCE_PATTERN = /@motif-js\/react-web|@motif-js\/react(?![-\w/])/g;
|
|
5
|
+
function replaceSource(match) {
|
|
6
|
+
if (match === "@motif-js/react-web") return "@motif-js/react";
|
|
7
|
+
return "motif-js";
|
|
8
|
+
}
|
|
9
|
+
function applyRenameV2(source) {
|
|
10
|
+
return source.replace(SOURCE_PATTERN, replaceSource);
|
|
11
|
+
}
|
|
12
|
+
function needsRenameV2(source) {
|
|
13
|
+
return /@motif-js\/react-web|@motif-js\/react(?![-\w/])/.test(source);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// src/index.ts
|
|
17
|
+
var PACKAGE_NAME = "@motif-js/migrate";
|
|
18
|
+
|
|
19
|
+
exports.PACKAGE_NAME = PACKAGE_NAME;
|
|
20
|
+
exports.applyRenameV2 = applyRenameV2;
|
|
21
|
+
exports.needsRenameV2 = needsRenameV2;
|
|
22
|
+
//# sourceMappingURL=index.cjs.map
|
|
23
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/transforms/rename-v2.ts","../src/index.ts"],"names":[],"mappings":";;;AA8CA,IAAM,cAAA,GAAiB,kDAAA;AAEvB,SAAS,cAAc,KAAA,EAAuB;AAC5C,EAAA,IAAI,KAAA,KAAU,uBAAuB,OAAO,iBAAA;AAG5C,EAAA,OAAO,UAAA;AACT;AAgBO,SAAS,cAAc,MAAA,EAAwB;AACpD,EAAA,OAAO,MAAA,CAAO,OAAA,CAAQ,cAAA,EAAgB,aAAa,CAAA;AACrD;AAOO,SAAS,cAAc,MAAA,EAAyB;AAIrD,EAAA,OAAO,iDAAA,CAAkD,KAAK,MAAM,CAAA;AACtE;;;AC3EO,IAAM,YAAA,GAAe","file":"index.cjs","sourcesContent":["/**\n * `rename-v2` codemod: rewrite v1 motif-js import specifiers to their\n * v2 names.\n *\n * | v1 | v2 |\n * |--------------------------|---------------------|\n * | `@motif-js/react-web` | `@motif-js/react` |\n * | `@motif-js/react` | `motif-js` |\n * | `@motif-js/react-native` | `@motif-js/react-native` (unchanged) |\n *\n * Touches only **import specifier strings** inside source files —\n * import statements, `require()` calls, dynamic `import()`, and the\n * dependency keys of `package.json`. Comments and string literals\n * elsewhere are left alone.\n *\n * ## Sequencing trap\n *\n * The two rewrites overlap. If we naively replace `@motif-js/react` →\n * `motif-js` first, then `@motif-js/react-web` → `@motif-js/react`,\n * the first pass already destroyed every `@motif-js/react` occurrence\n * — including the `@motif-js/react` part of `@motif-js/react-web`,\n * leaving garbage like `motif-js-web`.\n *\n * The safe order is the opposite: `@motif-js/react-web` →\n * `@motif-js/react` FIRST, then `@motif-js/react` → `motif-js`. After\n * step one, every occurrence of `@motif-js/react` in the file refers\n * to the v2 DOM bindings (just renamed from react-web), and step two\n * can safely promote any *other* `@motif-js/react` references (the\n * v1 aggregator) up to `motif-js`.\n *\n * That's still wrong, because step two would clobber the\n * just-renamed `@motif-js/react` references too. So in practice we\n * use a one-pass single regex with alternation that matches whichever\n * of the two source names appears at the call site:\n *\n * /@motif-js\\/react-web|@motif-js\\/react(?![-\\w/])/g\n *\n * — for each match, decide the replacement based on the matched text.\n * react-web → @motif-js/react, bare react → motif-js. The negative\n * lookahead `(?![-\\w/])` on the bare-react branch ensures we don't\n * catch react-web / react-native / react-foo (the `-` exclusion) or\n * the `@motif-js/react/server` and `@motif-js/react/tanstack-virtual`\n * subpaths (the `/` exclusion). Those subpath exports still belong\n * to the renamed DOM bindings package and must survive untouched.\n */\n\nconst SOURCE_PATTERN = /@motif-js\\/react-web|@motif-js\\/react(?![-\\w/])/g;\n\nfunction replaceSource(match: string): string {\n if (match === '@motif-js/react-web') return '@motif-js/react';\n // The other branch of the alternation. Anchored with negative\n // lookahead so it can only be the bare aggregator name.\n return 'motif-js';\n}\n\n/**\n * Apply the rename-v2 transform to a source string. The matching is\n * applied to the **entire** text, regardless of file kind — that's\n * intentional. Import-specifier strings have a unique enough shape\n * (the leading `@motif-js/` namespace) that false positives in prose\n * are rare. For `.md` / `.mdx` / `.json` files this lets the same\n * transform handle install snippets, code fences, and the\n * `dependencies` keys of package.json without separate parsers.\n *\n * If the input is `package.json`, sub-path imports like\n * `@motif-js/react/server` survive (the lookahead spares them) — the\n * `/server` and `/tanstack-virtual` exports still belong to the\n * renamed DOM bindings package.\n */\nexport function applyRenameV2(source: string): string {\n return source.replace(SOURCE_PATTERN, replaceSource);\n}\n\n/**\n * Convenience predicate: does the source contain anything the\n * transform would change? Used to skip the write step for unaffected\n * files (saves filesystem churn + leaves mtimes alone).\n */\nexport function needsRenameV2(source: string): boolean {\n // `SOURCE_PATTERN` is `g`-flagged, so reuse via `.test()` would mutate\n // its `.lastIndex` and corrupt later calls. Build a fresh non-global\n // regex per check.\n return /@motif-js\\/react-web|@motif-js\\/react(?![-\\w/])/.test(source);\n}\n","/**\n * @motif-js/migrate — codemod toolkit for motif-js.\n *\n * Programmatic API. The CLI in `./cli.ts` is the primary surface for\n * end users; this module is the importable shape for anyone running\n * the transforms from their own tooling.\n */\n\nexport const PACKAGE_NAME = '@motif-js/migrate';\n\nexport { applyRenameV2, needsRenameV2 } from './transforms/rename-v2.js';\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rename-v2` codemod: rewrite v1 motif-js import specifiers to their
|
|
3
|
+
* v2 names.
|
|
4
|
+
*
|
|
5
|
+
* | v1 | v2 |
|
|
6
|
+
* |--------------------------|---------------------|
|
|
7
|
+
* | `@motif-js/react-web` | `@motif-js/react` |
|
|
8
|
+
* | `@motif-js/react` | `motif-js` |
|
|
9
|
+
* | `@motif-js/react-native` | `@motif-js/react-native` (unchanged) |
|
|
10
|
+
*
|
|
11
|
+
* Touches only **import specifier strings** inside source files —
|
|
12
|
+
* import statements, `require()` calls, dynamic `import()`, and the
|
|
13
|
+
* dependency keys of `package.json`. Comments and string literals
|
|
14
|
+
* elsewhere are left alone.
|
|
15
|
+
*
|
|
16
|
+
* ## Sequencing trap
|
|
17
|
+
*
|
|
18
|
+
* The two rewrites overlap. If we naively replace `@motif-js/react` →
|
|
19
|
+
* `motif-js` first, then `@motif-js/react-web` → `@motif-js/react`,
|
|
20
|
+
* the first pass already destroyed every `@motif-js/react` occurrence
|
|
21
|
+
* — including the `@motif-js/react` part of `@motif-js/react-web`,
|
|
22
|
+
* leaving garbage like `motif-js-web`.
|
|
23
|
+
*
|
|
24
|
+
* The safe order is the opposite: `@motif-js/react-web` →
|
|
25
|
+
* `@motif-js/react` FIRST, then `@motif-js/react` → `motif-js`. After
|
|
26
|
+
* step one, every occurrence of `@motif-js/react` in the file refers
|
|
27
|
+
* to the v2 DOM bindings (just renamed from react-web), and step two
|
|
28
|
+
* can safely promote any *other* `@motif-js/react` references (the
|
|
29
|
+
* v1 aggregator) up to `motif-js`.
|
|
30
|
+
*
|
|
31
|
+
* That's still wrong, because step two would clobber the
|
|
32
|
+
* just-renamed `@motif-js/react` references too. So in practice we
|
|
33
|
+
* use a one-pass single regex with alternation that matches whichever
|
|
34
|
+
* of the two source names appears at the call site:
|
|
35
|
+
*
|
|
36
|
+
* /@motif-js\/react-web|@motif-js\/react(?![-\w/])/g
|
|
37
|
+
*
|
|
38
|
+
* — for each match, decide the replacement based on the matched text.
|
|
39
|
+
* react-web → @motif-js/react, bare react → motif-js. The negative
|
|
40
|
+
* lookahead `(?![-\w/])` on the bare-react branch ensures we don't
|
|
41
|
+
* catch react-web / react-native / react-foo (the `-` exclusion) or
|
|
42
|
+
* the `@motif-js/react/server` and `@motif-js/react/tanstack-virtual`
|
|
43
|
+
* subpaths (the `/` exclusion). Those subpath exports still belong
|
|
44
|
+
* to the renamed DOM bindings package and must survive untouched.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Apply the rename-v2 transform to a source string. The matching is
|
|
48
|
+
* applied to the **entire** text, regardless of file kind — that's
|
|
49
|
+
* intentional. Import-specifier strings have a unique enough shape
|
|
50
|
+
* (the leading `@motif-js/` namespace) that false positives in prose
|
|
51
|
+
* are rare. For `.md` / `.mdx` / `.json` files this lets the same
|
|
52
|
+
* transform handle install snippets, code fences, and the
|
|
53
|
+
* `dependencies` keys of package.json without separate parsers.
|
|
54
|
+
*
|
|
55
|
+
* If the input is `package.json`, sub-path imports like
|
|
56
|
+
* `@motif-js/react/server` survive (the lookahead spares them) — the
|
|
57
|
+
* `/server` and `/tanstack-virtual` exports still belong to the
|
|
58
|
+
* renamed DOM bindings package.
|
|
59
|
+
*/
|
|
60
|
+
declare function applyRenameV2(source: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Convenience predicate: does the source contain anything the
|
|
63
|
+
* transform would change? Used to skip the write step for unaffected
|
|
64
|
+
* files (saves filesystem churn + leaves mtimes alone).
|
|
65
|
+
*/
|
|
66
|
+
declare function needsRenameV2(source: string): boolean;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @motif-js/migrate — codemod toolkit for motif-js.
|
|
70
|
+
*
|
|
71
|
+
* Programmatic API. The CLI in `./cli.ts` is the primary surface for
|
|
72
|
+
* end users; this module is the importable shape for anyone running
|
|
73
|
+
* the transforms from their own tooling.
|
|
74
|
+
*/
|
|
75
|
+
declare const PACKAGE_NAME = "@motif-js/migrate";
|
|
76
|
+
|
|
77
|
+
export { PACKAGE_NAME, applyRenameV2, needsRenameV2 };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rename-v2` codemod: rewrite v1 motif-js import specifiers to their
|
|
3
|
+
* v2 names.
|
|
4
|
+
*
|
|
5
|
+
* | v1 | v2 |
|
|
6
|
+
* |--------------------------|---------------------|
|
|
7
|
+
* | `@motif-js/react-web` | `@motif-js/react` |
|
|
8
|
+
* | `@motif-js/react` | `motif-js` |
|
|
9
|
+
* | `@motif-js/react-native` | `@motif-js/react-native` (unchanged) |
|
|
10
|
+
*
|
|
11
|
+
* Touches only **import specifier strings** inside source files —
|
|
12
|
+
* import statements, `require()` calls, dynamic `import()`, and the
|
|
13
|
+
* dependency keys of `package.json`. Comments and string literals
|
|
14
|
+
* elsewhere are left alone.
|
|
15
|
+
*
|
|
16
|
+
* ## Sequencing trap
|
|
17
|
+
*
|
|
18
|
+
* The two rewrites overlap. If we naively replace `@motif-js/react` →
|
|
19
|
+
* `motif-js` first, then `@motif-js/react-web` → `@motif-js/react`,
|
|
20
|
+
* the first pass already destroyed every `@motif-js/react` occurrence
|
|
21
|
+
* — including the `@motif-js/react` part of `@motif-js/react-web`,
|
|
22
|
+
* leaving garbage like `motif-js-web`.
|
|
23
|
+
*
|
|
24
|
+
* The safe order is the opposite: `@motif-js/react-web` →
|
|
25
|
+
* `@motif-js/react` FIRST, then `@motif-js/react` → `motif-js`. After
|
|
26
|
+
* step one, every occurrence of `@motif-js/react` in the file refers
|
|
27
|
+
* to the v2 DOM bindings (just renamed from react-web), and step two
|
|
28
|
+
* can safely promote any *other* `@motif-js/react` references (the
|
|
29
|
+
* v1 aggregator) up to `motif-js`.
|
|
30
|
+
*
|
|
31
|
+
* That's still wrong, because step two would clobber the
|
|
32
|
+
* just-renamed `@motif-js/react` references too. So in practice we
|
|
33
|
+
* use a one-pass single regex with alternation that matches whichever
|
|
34
|
+
* of the two source names appears at the call site:
|
|
35
|
+
*
|
|
36
|
+
* /@motif-js\/react-web|@motif-js\/react(?![-\w/])/g
|
|
37
|
+
*
|
|
38
|
+
* — for each match, decide the replacement based on the matched text.
|
|
39
|
+
* react-web → @motif-js/react, bare react → motif-js. The negative
|
|
40
|
+
* lookahead `(?![-\w/])` on the bare-react branch ensures we don't
|
|
41
|
+
* catch react-web / react-native / react-foo (the `-` exclusion) or
|
|
42
|
+
* the `@motif-js/react/server` and `@motif-js/react/tanstack-virtual`
|
|
43
|
+
* subpaths (the `/` exclusion). Those subpath exports still belong
|
|
44
|
+
* to the renamed DOM bindings package and must survive untouched.
|
|
45
|
+
*/
|
|
46
|
+
/**
|
|
47
|
+
* Apply the rename-v2 transform to a source string. The matching is
|
|
48
|
+
* applied to the **entire** text, regardless of file kind — that's
|
|
49
|
+
* intentional. Import-specifier strings have a unique enough shape
|
|
50
|
+
* (the leading `@motif-js/` namespace) that false positives in prose
|
|
51
|
+
* are rare. For `.md` / `.mdx` / `.json` files this lets the same
|
|
52
|
+
* transform handle install snippets, code fences, and the
|
|
53
|
+
* `dependencies` keys of package.json without separate parsers.
|
|
54
|
+
*
|
|
55
|
+
* If the input is `package.json`, sub-path imports like
|
|
56
|
+
* `@motif-js/react/server` survive (the lookahead spares them) — the
|
|
57
|
+
* `/server` and `/tanstack-virtual` exports still belong to the
|
|
58
|
+
* renamed DOM bindings package.
|
|
59
|
+
*/
|
|
60
|
+
declare function applyRenameV2(source: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Convenience predicate: does the source contain anything the
|
|
63
|
+
* transform would change? Used to skip the write step for unaffected
|
|
64
|
+
* files (saves filesystem churn + leaves mtimes alone).
|
|
65
|
+
*/
|
|
66
|
+
declare function needsRenameV2(source: string): boolean;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* @motif-js/migrate — codemod toolkit for motif-js.
|
|
70
|
+
*
|
|
71
|
+
* Programmatic API. The CLI in `./cli.ts` is the primary surface for
|
|
72
|
+
* end users; this module is the importable shape for anyone running
|
|
73
|
+
* the transforms from their own tooling.
|
|
74
|
+
*/
|
|
75
|
+
declare const PACKAGE_NAME = "@motif-js/migrate";
|
|
76
|
+
|
|
77
|
+
export { PACKAGE_NAME, applyRenameV2, needsRenameV2 };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;AAQO,IAAM,YAAA,GAAe","file":"index.js","sourcesContent":["/**\n * @motif-js/migrate — codemod toolkit for motif-js.\n *\n * Programmatic API. The CLI in `./cli.ts` is the primary surface for\n * end users; this module is the importable shape for anyone running\n * the transforms from their own tooling.\n */\n\nexport const PACKAGE_NAME = '@motif-js/migrate';\n\nexport { applyRenameV2, needsRenameV2 } from './transforms/rename-v2.js';\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@motif-js/migrate",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Codemod toolkit for motif-js. Run `npx @motif-js/migrate rename-v2 <path>` to rewrite v1 import specifiers to their v2 names.",
|
|
5
|
+
"homepage": "https://github.com/foo-stack/motif-js/tree/main/packages/migrate#readme",
|
|
6
|
+
"bugs": "https://github.com/foo-stack/motif-js/issues",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/foo-stack/motif-js.git",
|
|
11
|
+
"directory": "packages/migrate"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"motif-js-migrate": "./bin/cli.js"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"bin",
|
|
18
|
+
"dist",
|
|
19
|
+
"src"
|
|
20
|
+
],
|
|
21
|
+
"type": "module",
|
|
22
|
+
"sideEffects": false,
|
|
23
|
+
"main": "./dist/index.cjs",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"require": "./dist/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run",
|
|
40
|
+
"clean": "rm -rf dist .turbo *.tsbuildinfo"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"fast-glob": "^3.3.2"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^25.6.0",
|
|
47
|
+
"tsup": "^8.5.1",
|
|
48
|
+
"typescript": "^6.0.3",
|
|
49
|
+
"vitest": "^4.1.5"
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* `motif-js-migrate` CLI.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* motif-js-migrate rename-v2 [path] [--dry-run]
|
|
7
|
+
* motif-js-migrate --help
|
|
8
|
+
*
|
|
9
|
+
* Default path is the current working directory. The transform walks
|
|
10
|
+
* matching files via fast-glob, applies `applyRenameV2`, and writes
|
|
11
|
+
* back any file whose content changed.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
15
|
+
import { relative, resolve } from 'node:path';
|
|
16
|
+
import { exit, stderr, stdout } from 'node:process';
|
|
17
|
+
import fg from 'fast-glob';
|
|
18
|
+
import { applyRenameV2, needsRenameV2 } from './transforms/rename-v2.js';
|
|
19
|
+
|
|
20
|
+
interface ParsedArgs {
|
|
21
|
+
readonly command: string | null;
|
|
22
|
+
readonly path: string;
|
|
23
|
+
readonly dryRun: boolean;
|
|
24
|
+
readonly help: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const DEFAULT_GLOBS: string[] = [
|
|
28
|
+
'**/*.{ts,tsx,js,jsx,mjs,cjs,md,mdx,json}',
|
|
29
|
+
'!**/node_modules/**',
|
|
30
|
+
'!**/dist/**',
|
|
31
|
+
'!**/.next/**',
|
|
32
|
+
'!**/.vorge/**',
|
|
33
|
+
'!**/.turbo/**',
|
|
34
|
+
'!**/.cache/**',
|
|
35
|
+
'!**/build/**',
|
|
36
|
+
'!**/out/**',
|
|
37
|
+
'!**/coverage/**',
|
|
38
|
+
'!**/__visual__/**',
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const HELP = `motif-js-migrate — codemod toolkit for motif-js
|
|
42
|
+
|
|
43
|
+
Usage:
|
|
44
|
+
motif-js-migrate rename-v2 [path] Rewrite v1 motif-js import specifiers
|
|
45
|
+
to their v2 names in every file under
|
|
46
|
+
[path] (default: cwd).
|
|
47
|
+
motif-js-migrate --help Show this message.
|
|
48
|
+
|
|
49
|
+
Flags:
|
|
50
|
+
--dry-run Print the files that would change without writing them.
|
|
51
|
+
|
|
52
|
+
Rename map:
|
|
53
|
+
@motif-js/react-web → @motif-js/react
|
|
54
|
+
@motif-js/react → motif-js
|
|
55
|
+
@motif-js/react-native → (unchanged)
|
|
56
|
+
|
|
57
|
+
Subpath imports (@motif-js/react/server, @motif-js/react/tanstack-virtual)
|
|
58
|
+
stay on @motif-js/react.
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
62
|
+
let command: string | null = null;
|
|
63
|
+
let path = '.';
|
|
64
|
+
let dryRun = false;
|
|
65
|
+
let help = false;
|
|
66
|
+
let positionalIndex = 0;
|
|
67
|
+
for (const arg of argv) {
|
|
68
|
+
if (arg === '--help' || arg === '-h') {
|
|
69
|
+
help = true;
|
|
70
|
+
} else if (arg === '--dry-run') {
|
|
71
|
+
dryRun = true;
|
|
72
|
+
} else if (arg.startsWith('--')) {
|
|
73
|
+
stderr.write(`unknown flag: ${arg}\n`);
|
|
74
|
+
exit(2);
|
|
75
|
+
} else if (positionalIndex === 0) {
|
|
76
|
+
command = arg;
|
|
77
|
+
positionalIndex++;
|
|
78
|
+
} else if (positionalIndex === 1) {
|
|
79
|
+
path = arg;
|
|
80
|
+
positionalIndex++;
|
|
81
|
+
} else {
|
|
82
|
+
stderr.write(`unexpected positional argument: ${arg}\n`);
|
|
83
|
+
exit(2);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { command, path, dryRun, help };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function runRenameV2(rootArg: string, dryRun: boolean): Promise<number> {
|
|
90
|
+
const root = resolve(rootArg);
|
|
91
|
+
const files = await fg(DEFAULT_GLOBS, { cwd: root, absolute: true, dot: false });
|
|
92
|
+
let changed = 0;
|
|
93
|
+
for (const absPath of files) {
|
|
94
|
+
const src = await readFile(absPath, 'utf8');
|
|
95
|
+
if (!needsRenameV2(src)) continue;
|
|
96
|
+
const out = applyRenameV2(src);
|
|
97
|
+
if (out === src) continue;
|
|
98
|
+
const rel = relative(root, absPath);
|
|
99
|
+
if (dryRun) {
|
|
100
|
+
stdout.write(`would change: ${rel}\n`);
|
|
101
|
+
} else {
|
|
102
|
+
await writeFile(absPath, out, 'utf8');
|
|
103
|
+
stdout.write(`changed: ${rel}\n`);
|
|
104
|
+
}
|
|
105
|
+
changed++;
|
|
106
|
+
}
|
|
107
|
+
stdout.write(
|
|
108
|
+
`${dryRun ? 'would change' : 'changed'} ${changed} file${changed === 1 ? '' : 's'}.\n`,
|
|
109
|
+
);
|
|
110
|
+
return 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function main(argv: readonly string[]): Promise<number> {
|
|
114
|
+
const args = parseArgs(argv);
|
|
115
|
+
if (args.help || args.command === null) {
|
|
116
|
+
stdout.write(HELP);
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
if (args.command === 'rename-v2') {
|
|
120
|
+
return runRenameV2(args.path, args.dryRun);
|
|
121
|
+
}
|
|
122
|
+
stderr.write(`unknown command: ${args.command}\n`);
|
|
123
|
+
stderr.write(HELP);
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @motif-js/migrate — codemod toolkit for motif-js.
|
|
3
|
+
*
|
|
4
|
+
* Programmatic API. The CLI in `./cli.ts` is the primary surface for
|
|
5
|
+
* end users; this module is the importable shape for anyone running
|
|
6
|
+
* the transforms from their own tooling.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const PACKAGE_NAME = '@motif-js/migrate';
|
|
10
|
+
|
|
11
|
+
export { applyRenameV2, needsRenameV2 } from './transforms/rename-v2.js';
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { applyRenameV2, needsRenameV2 } from './rename-v2.js';
|
|
3
|
+
|
|
4
|
+
describe('applyRenameV2', () => {
|
|
5
|
+
it('rewrites a named import from @motif-js/react-web to @motif-js/react', () => {
|
|
6
|
+
expect(applyRenameV2(`import { Box } from '@motif-js/react-web';`)).toBe(
|
|
7
|
+
`import { Box } from '@motif-js/react';`,
|
|
8
|
+
);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('rewrites a named import from @motif-js/react (v1 aggregator) to motif-js', () => {
|
|
12
|
+
expect(applyRenameV2(`import { Box } from '@motif-js/react';`)).toBe(
|
|
13
|
+
`import { Box } from 'motif-js';`,
|
|
14
|
+
);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('leaves @motif-js/react-native untouched', () => {
|
|
18
|
+
expect(applyRenameV2(`import { Box } from '@motif-js/react-native';`)).toBe(
|
|
19
|
+
`import { Box } from '@motif-js/react-native';`,
|
|
20
|
+
);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('leaves @motif-js/react/server (subpath) untouched', () => {
|
|
24
|
+
expect(applyRenameV2(`import '@motif-js/react/server';`)).toBe(
|
|
25
|
+
`import '@motif-js/react/server';`,
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('rewrites @motif-js/react-web/server to @motif-js/react/server', () => {
|
|
30
|
+
expect(applyRenameV2(`import '@motif-js/react-web/server';`)).toBe(
|
|
31
|
+
`import '@motif-js/react/server';`,
|
|
32
|
+
);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('handles both renames in the same file in one pass', () => {
|
|
36
|
+
const src = [
|
|
37
|
+
`import { Box } from '@motif-js/react-web';`,
|
|
38
|
+
`import { Portal } from '@motif-js/react';`,
|
|
39
|
+
`import '@motif-js/react-web/server';`,
|
|
40
|
+
].join('\n');
|
|
41
|
+
const expected = [
|
|
42
|
+
`import { Box } from '@motif-js/react';`,
|
|
43
|
+
`import { Portal } from 'motif-js';`,
|
|
44
|
+
`import '@motif-js/react/server';`,
|
|
45
|
+
].join('\n');
|
|
46
|
+
expect(applyRenameV2(src)).toBe(expected);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('handles double-quoted imports', () => {
|
|
50
|
+
expect(applyRenameV2(`import { Box } from "@motif-js/react-web";`)).toBe(
|
|
51
|
+
`import { Box } from "@motif-js/react";`,
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('handles dynamic import() calls', () => {
|
|
56
|
+
expect(applyRenameV2(`await import('@motif-js/react-web/server');`)).toBe(
|
|
57
|
+
`await import('@motif-js/react/server');`,
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('handles CommonJS require() calls', () => {
|
|
62
|
+
expect(applyRenameV2(`const { Box } = require('@motif-js/react-web');`)).toBe(
|
|
63
|
+
`const { Box } = require('@motif-js/react');`,
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('handles type-only imports', () => {
|
|
68
|
+
expect(applyRenameV2(`import type { BoxProps } from '@motif-js/react';`)).toBe(
|
|
69
|
+
`import type { BoxProps } from 'motif-js';`,
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('handles aliased named imports', () => {
|
|
74
|
+
expect(applyRenameV2(`import { Box as MotifBox } from '@motif-js/react-web';`)).toBe(
|
|
75
|
+
`import { Box as MotifBox } from '@motif-js/react';`,
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('handles multi-line imports', () => {
|
|
80
|
+
const src = `import {\n Box,\n Stack,\n} from '@motif-js/react-web';`;
|
|
81
|
+
const expected = `import {\n Box,\n Stack,\n} from '@motif-js/react';`;
|
|
82
|
+
expect(applyRenameV2(src)).toBe(expected);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('rewrites package.json dependency keys', () => {
|
|
86
|
+
const src = JSON.stringify(
|
|
87
|
+
{
|
|
88
|
+
dependencies: {
|
|
89
|
+
'@motif-js/react-web': 'workspace:*',
|
|
90
|
+
'@motif-js/react': 'workspace:*',
|
|
91
|
+
'@motif-js/react-native': 'workspace:*',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
null,
|
|
95
|
+
2,
|
|
96
|
+
);
|
|
97
|
+
const out = applyRenameV2(src);
|
|
98
|
+
const parsed = JSON.parse(out) as { dependencies: Record<string, string> };
|
|
99
|
+
expect(parsed.dependencies).toEqual({
|
|
100
|
+
'@motif-js/react': 'workspace:*',
|
|
101
|
+
'motif-js': 'workspace:*',
|
|
102
|
+
'@motif-js/react-native': 'workspace:*',
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('leaves unrelated packages alone', () => {
|
|
107
|
+
const src = [
|
|
108
|
+
`import { useState } from 'react';`,
|
|
109
|
+
`import { render } from 'react-dom';`,
|
|
110
|
+
`import { reactify } from 'some-other-lib';`,
|
|
111
|
+
`import './styles/react.css';`,
|
|
112
|
+
].join('\n');
|
|
113
|
+
expect(applyRenameV2(src)).toBe(src);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('matches inside markdown / mdx code fences', () => {
|
|
117
|
+
const src = [
|
|
118
|
+
'## Install',
|
|
119
|
+
'',
|
|
120
|
+
'```sh',
|
|
121
|
+
'yarn add @motif-js/react',
|
|
122
|
+
'```',
|
|
123
|
+
'',
|
|
124
|
+
'```tsx',
|
|
125
|
+
`import { Box } from '@motif-js/react';`,
|
|
126
|
+
'```',
|
|
127
|
+
].join('\n');
|
|
128
|
+
const out = applyRenameV2(src);
|
|
129
|
+
expect(out).toContain('yarn add motif-js');
|
|
130
|
+
expect(out).toContain(`import { Box } from 'motif-js';`);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('needsRenameV2', () => {
|
|
135
|
+
it('returns true for files containing v1 specifiers', () => {
|
|
136
|
+
expect(needsRenameV2(`import { Box } from '@motif-js/react-web';`)).toBe(true);
|
|
137
|
+
expect(needsRenameV2(`import { Box } from '@motif-js/react';`)).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('returns false for files with only v2 / unrelated specifiers', () => {
|
|
141
|
+
expect(needsRenameV2(`import { Box } from 'motif-js';`)).toBe(false);
|
|
142
|
+
expect(needsRenameV2(`import { Box } from '@motif-js/react-native';`)).toBe(false);
|
|
143
|
+
expect(needsRenameV2(`import { Box } from '@motif-js/react/server';`)).toBe(false);
|
|
144
|
+
expect(needsRenameV2(`import { useState } from 'react';`)).toBe(false);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('does NOT mutate its own regex (.lastIndex) across calls', () => {
|
|
148
|
+
const src = `import { Box } from '@motif-js/react-web';`;
|
|
149
|
+
expect(needsRenameV2(src)).toBe(true);
|
|
150
|
+
expect(needsRenameV2(src)).toBe(true);
|
|
151
|
+
expect(needsRenameV2(src)).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rename-v2` codemod: rewrite v1 motif-js import specifiers to their
|
|
3
|
+
* v2 names.
|
|
4
|
+
*
|
|
5
|
+
* | v1 | v2 |
|
|
6
|
+
* |--------------------------|---------------------|
|
|
7
|
+
* | `@motif-js/react-web` | `@motif-js/react` |
|
|
8
|
+
* | `@motif-js/react` | `motif-js` |
|
|
9
|
+
* | `@motif-js/react-native` | `@motif-js/react-native` (unchanged) |
|
|
10
|
+
*
|
|
11
|
+
* Touches only **import specifier strings** inside source files —
|
|
12
|
+
* import statements, `require()` calls, dynamic `import()`, and the
|
|
13
|
+
* dependency keys of `package.json`. Comments and string literals
|
|
14
|
+
* elsewhere are left alone.
|
|
15
|
+
*
|
|
16
|
+
* ## Sequencing trap
|
|
17
|
+
*
|
|
18
|
+
* The two rewrites overlap. If we naively replace `@motif-js/react` →
|
|
19
|
+
* `motif-js` first, then `@motif-js/react-web` → `@motif-js/react`,
|
|
20
|
+
* the first pass already destroyed every `@motif-js/react` occurrence
|
|
21
|
+
* — including the `@motif-js/react` part of `@motif-js/react-web`,
|
|
22
|
+
* leaving garbage like `motif-js-web`.
|
|
23
|
+
*
|
|
24
|
+
* The safe order is the opposite: `@motif-js/react-web` →
|
|
25
|
+
* `@motif-js/react` FIRST, then `@motif-js/react` → `motif-js`. After
|
|
26
|
+
* step one, every occurrence of `@motif-js/react` in the file refers
|
|
27
|
+
* to the v2 DOM bindings (just renamed from react-web), and step two
|
|
28
|
+
* can safely promote any *other* `@motif-js/react` references (the
|
|
29
|
+
* v1 aggregator) up to `motif-js`.
|
|
30
|
+
*
|
|
31
|
+
* That's still wrong, because step two would clobber the
|
|
32
|
+
* just-renamed `@motif-js/react` references too. So in practice we
|
|
33
|
+
* use a one-pass single regex with alternation that matches whichever
|
|
34
|
+
* of the two source names appears at the call site:
|
|
35
|
+
*
|
|
36
|
+
* /@motif-js\/react-web|@motif-js\/react(?![-\w/])/g
|
|
37
|
+
*
|
|
38
|
+
* — for each match, decide the replacement based on the matched text.
|
|
39
|
+
* react-web → @motif-js/react, bare react → motif-js. The negative
|
|
40
|
+
* lookahead `(?![-\w/])` on the bare-react branch ensures we don't
|
|
41
|
+
* catch react-web / react-native / react-foo (the `-` exclusion) or
|
|
42
|
+
* the `@motif-js/react/server` and `@motif-js/react/tanstack-virtual`
|
|
43
|
+
* subpaths (the `/` exclusion). Those subpath exports still belong
|
|
44
|
+
* to the renamed DOM bindings package and must survive untouched.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
const SOURCE_PATTERN = /@motif-js\/react-web|@motif-js\/react(?![-\w/])/g;
|
|
48
|
+
|
|
49
|
+
function replaceSource(match: string): string {
|
|
50
|
+
if (match === '@motif-js/react-web') return '@motif-js/react';
|
|
51
|
+
// The other branch of the alternation. Anchored with negative
|
|
52
|
+
// lookahead so it can only be the bare aggregator name.
|
|
53
|
+
return 'motif-js';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Apply the rename-v2 transform to a source string. The matching is
|
|
58
|
+
* applied to the **entire** text, regardless of file kind — that's
|
|
59
|
+
* intentional. Import-specifier strings have a unique enough shape
|
|
60
|
+
* (the leading `@motif-js/` namespace) that false positives in prose
|
|
61
|
+
* are rare. For `.md` / `.mdx` / `.json` files this lets the same
|
|
62
|
+
* transform handle install snippets, code fences, and the
|
|
63
|
+
* `dependencies` keys of package.json without separate parsers.
|
|
64
|
+
*
|
|
65
|
+
* If the input is `package.json`, sub-path imports like
|
|
66
|
+
* `@motif-js/react/server` survive (the lookahead spares them) — the
|
|
67
|
+
* `/server` and `/tanstack-virtual` exports still belong to the
|
|
68
|
+
* renamed DOM bindings package.
|
|
69
|
+
*/
|
|
70
|
+
export function applyRenameV2(source: string): string {
|
|
71
|
+
return source.replace(SOURCE_PATTERN, replaceSource);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Convenience predicate: does the source contain anything the
|
|
76
|
+
* transform would change? Used to skip the write step for unaffected
|
|
77
|
+
* files (saves filesystem churn + leaves mtimes alone).
|
|
78
|
+
*/
|
|
79
|
+
export function needsRenameV2(source: string): boolean {
|
|
80
|
+
// `SOURCE_PATTERN` is `g`-flagged, so reuse via `.test()` would mutate
|
|
81
|
+
// its `.lastIndex` and corrupt later calls. Build a fresh non-global
|
|
82
|
+
// regex per check.
|
|
83
|
+
return /@motif-js\/react-web|@motif-js\/react(?![-\w/])/.test(source);
|
|
84
|
+
}
|