@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 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
@@ -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
@@ -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"]}
@@ -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 };
@@ -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,8 @@
1
+ export { applyRenameV2, needsRenameV2 } from './chunk-SSSWTBUF.js';
2
+
3
+ // src/index.ts
4
+ var PACKAGE_NAME = "@motif-js/migrate";
5
+
6
+ export { PACKAGE_NAME };
7
+ //# sourceMappingURL=index.js.map
8
+ //# sourceMappingURL=index.js.map
@@ -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
+ }