diff --git a/package.json b/package.json deleted file mode 100644 index 7a1abb3..0000000 --- a/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "rewrite-codemods", - "version": "1.0.0", - "description": "Codemods for OpenRewrite", - "dependencies": { - "@next/codemod": "^14.0.3" - } -} diff --git a/src/main/java/org/openrewrite/codemods/ApplyCodemod.java b/src/main/java/org/openrewrite/codemods/ApplyCodemod.java index 98f7534..45155ca 100644 --- a/src/main/java/org/openrewrite/codemods/ApplyCodemod.java +++ b/src/main/java/org/openrewrite/codemods/ApplyCodemod.java @@ -27,16 +27,19 @@ import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.*; import java.util.*; +import java.util.stream.Stream; import static java.util.Collections.emptyList; @Value @EqualsAndHashCode(callSuper = true) public class ApplyCodemod extends ScanningRecipe { + private static final String NODE_MODULES_KEY = ApplyCodemod.class.getName() + ".NODE_MODULES"; + @Option(displayName = "NPM package containing the codemod", description = "The codemod's NPM package name.", example = "@next/codemod") @@ -97,19 +100,18 @@ public TreeVisitor getScanner(Accumulator acc) { @Override public Collection generate(Accumulator acc, ExecutionContext ctx) { - String template = Optional.ofNullable(codemodCommandTemplate).orElse("${codemodArgs}"); + Path nodeModules = ctx.getMessage(NODE_MODULES_KEY); + if (nodeModules == null) { + ctx.putMessage(NODE_MODULES_KEY, nodeModules = extractNodeModules()); + } + List command = new ArrayList<>(); command.add("node"); // FIXME extract from jar - Path nodeModules = Paths.get("node_modules").toAbsolutePath(); // FIXME parse `bin` from `@next/codemod/package.json` - command.add(nodeModules.resolve(npmPackage).toString()); - if (npmPackageVersion != null) { - command.add("--version"); - command.add(npmPackageVersion); - } - command.add(nodeModules.resolve("@next/codemod/bin/next-codemod.js").toString()); + command.add(nodeModules.resolve(npmPackage).resolve("bin/next-codemod.js").toString()); + String template = Optional.ofNullable(codemodCommandTemplate).orElse("${codemodArgs}"); for (String part : template.split(" ")) { part = part.trim(); int argsIdx = part.indexOf("${codemodArgs}"); @@ -149,6 +151,67 @@ public Collection generate(Accumulator acc, ExecutionConte return emptyList(); } + private Path extractNodeModules() { + try { + URI uri = Objects.requireNonNull(ApplyCodemod.class.getClassLoader().getResource("codemods")).toURI(); + if ("jar".equals(uri.getScheme())) { + try (FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap(), null)) { + Path codemodsPath = fileSystem.getPath("codemods"); + Path target = Files.createTempDirectory("codemods"); + copyNodeModules(codemodsPath, target); + return target.resolve("node_modules"); + } + } else if ("file".equals(uri.getScheme())) { + Path codemodsPath = Paths.get(uri); + Path target = Files.createTempDirectory("codemods"); + copyNodeModules(codemodsPath, target); + return target.resolve("node_modules"); + } else { + throw new IllegalArgumentException("Unsupported scheme: " + uri.getScheme()); + } + } catch (URISyntaxException | IOException | InterruptedException e) { + throw new RuntimeException(e); + } + } + + private static void copyNodeModules(Path codemodsPath, Path target) throws IOException, InterruptedException { + try (Stream stream = Files.walk(codemodsPath)) { + stream.forEach(source -> { + try { + Files.copy(source, target.resolve(codemodsPath.relativize(source)), StandardCopyOption.REPLACE_EXISTING); + } catch (Exception e) { + throw new RuntimeException(e.getMessage(), e); + } + }); + } + recreateBinSymlinks(target); + } + + /** + * The `node_modules/.bin` directory contains symlinks (corresponding to the `bin` key in the package's `package.json`) + * that point to the package's corresponding script. These must exist in order for the codemod to work properly. + *

+ * Since Gradle won't preserve relative symlinks properly (see https://github.com/gradle/gradle/issues/3982) and + * jar files cannot contain symlinks, these must be recreated manually. + */ + private static void recreateBinSymlinks(Path target) throws IOException, InterruptedException { + try (Stream files = Files.list(target.resolve("node_modules/.bin"))) { + files.forEach(f -> { + try { + Files.delete(f); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } + // FIXME instead of using `npm install` to recreate the symlinks, consider processing the package.json files + ProcessBuilder builder = new ProcessBuilder(); + builder.command("npm", "install"); + builder.directory(target.toFile()); + Process process = builder.start(); + process.waitFor(); + } + @Override public TreeVisitor getVisitor(Accumulator acc) { return new TreeVisitor() { diff --git a/package-lock.json b/src/main/resources/codemods/package-lock.json similarity index 99% rename from package-lock.json rename to src/main/resources/codemods/package-lock.json index 045a179..053d267 100644 --- a/package-lock.json +++ b/src/main/resources/codemods/package-lock.json @@ -1,12 +1,9 @@ { - "name": "rewrite-codemods", - "version": "1.0.0", + "name": "codemods", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "rewrite-codemods", - "version": "1.0.0", "dependencies": { "@next/codemod": "^14.0.3" } @@ -2660,9 +2657,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.606", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.606.tgz", - "integrity": "sha512-Zdv0XuhfyWZUsQ5Uq59d43ZmZOdoGZNWjeN4WCxxlQaP8crAWdnWcTxfHKcaJl6PW2SWpHx6DsxSx7v6KcGCuw==" + "version": "1.4.607", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.607.tgz", + "integrity": "sha512-YUlnPwE6eYxzwBnFmawA8LiLRfm70R2aJRIUv0n03uHt/cUzzYACOogmvk8M2+hVzt/kB80KJXx7d5f5JofPvQ==" }, "node_modules/emoji-regex": { "version": "8.0.0", diff --git a/src/main/resources/codemods/package.json b/src/main/resources/codemods/package.json new file mode 100644 index 0000000..50575ca --- /dev/null +++ b/src/main/resources/codemods/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@next/codemod": "^14.0.3" + } +}