aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/packages/turbo-codemod/src/transforms
diff options
context:
space:
mode:
author简律纯 <hsiangnianian@outlook.com>2023-04-28 01:36:44 +0800
committer简律纯 <hsiangnianian@outlook.com>2023-04-28 01:36:44 +0800
commitdd84b9d64fb98746a230cd24233ff50a562c39c9 (patch)
treeb583261ef00b3afe72ec4d6dacb31e57779a6faf /packages/turbo-codemod/src/transforms
parent0b46fcd72ac34382387b2bcf9095233efbcc52f4 (diff)
downloadHydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.tar.gz
HydroRoll-dd84b9d64fb98746a230cd24233ff50a562c39c9.zip
Diffstat (limited to 'packages/turbo-codemod/src/transforms')
-rw-r--r--packages/turbo-codemod/src/transforms/README.md36
-rw-r--r--packages/turbo-codemod/src/transforms/add-package-manager.ts75
-rw-r--r--packages/turbo-codemod/src/transforms/create-turbo-config.ts70
-rw-r--r--packages/turbo-codemod/src/transforms/migrate-env-var-dependencies.ts181
-rw-r--r--packages/turbo-codemod/src/transforms/set-default-outputs.ts97
5 files changed, 459 insertions, 0 deletions
diff --git a/packages/turbo-codemod/src/transforms/README.md b/packages/turbo-codemod/src/transforms/README.md
new file mode 100644
index 0000000..8e4430f
--- /dev/null
+++ b/packages/turbo-codemod/src/transforms/README.md
@@ -0,0 +1,36 @@
+# `@turbo/codemod` Transformers
+
+## Adding new transformers
+
+Add new transformers using the [plopjs](https://github.com/plopjs/plop) template by running:
+
+```bash
+pnpm add-transformer
+```
+
+New Transformers will be automatically surfaced to the `transform` CLI command and used by the `migrate` CLI command when appropriate.
+
+## How it works
+
+Transformers are loaded automatically from the `src/transforms/` directory via the [`loadTransforms`](../utils/loadTransformers.ts) function.
+
+All new transformers must contain a default export that matches the [`Transformer`](../types.ts) type:
+
+```ts
+export type Transformer = {
+ name: string;
+ value: string;
+ introducedIn: string;
+ transformer: (args: TransformerArgs) => TransformerResults;
+};
+```
+
+## Writing a Transform
+
+Transforms are ran using the [TransformRunner](../runner/Runner.ts). This class is designed to make writing transforms as simple as possible by abstracting away all of the boilerplate that determines what should be logged, saved, or output as a result.
+
+To use the TransformRunner:
+
+1. Transform each file in memory (do not write it back to disk `TransformRunner` takes care of this depending on the options passed in by the user), and pass to `TransformRunner.modifyFile` method.
+2. If the transform encounters an unrecoverable error, pass it to the `TransformRunner.abortTransform` method.
+3. When all files have been modified and passed to `TransformRunner.modifyFile`, call `TransformRunner.finish` method to write the files to disk (when not running in `dry` mode) and log the results.
diff --git a/packages/turbo-codemod/src/transforms/add-package-manager.ts b/packages/turbo-codemod/src/transforms/add-package-manager.ts
new file mode 100644
index 0000000..bd6581f
--- /dev/null
+++ b/packages/turbo-codemod/src/transforms/add-package-manager.ts
@@ -0,0 +1,75 @@
+import path from "path";
+import fs from "fs-extra";
+
+import getPackageManager from "../utils/getPackageManager";
+import getPackageManagerVersion from "../utils/getPackageManagerVersion";
+import getTransformerHelpers from "../utils/getTransformerHelpers";
+import { TransformerResults } from "../runner";
+import type { TransformerArgs } from "../types";
+
+// transformer details
+const TRANSFORMER = "add-package-manager";
+const DESCRIPTION = "Set the `packageManager` key in root `package.json` file";
+const INTRODUCED_IN = "1.1.0";
+
+export function transformer({
+ root,
+ options,
+}: TransformerArgs): TransformerResults {
+ const { log, runner } = getTransformerHelpers({
+ transformer: TRANSFORMER,
+ rootPath: root,
+ options,
+ });
+
+ log.info(`Set "packageManager" key in root "package.json" file...`);
+ const packageManager = getPackageManager({ directory: root });
+ if (!packageManager) {
+ return runner.abortTransform({
+ reason: `Unable to determine package manager for ${root}`,
+ });
+ }
+
+ // handle workspaces...
+ let version = null;
+ try {
+ version = getPackageManagerVersion(packageManager, root);
+ } catch (err) {
+ return runner.abortTransform({
+ reason: `Unable to determine package manager version for ${root}`,
+ });
+ }
+ const pkgManagerString = `${packageManager}@${version}`;
+ const rootPackageJsonPath = path.join(root, "package.json");
+ const rootPackageJson = fs.readJsonSync(rootPackageJsonPath);
+ const allWorkspaces = [
+ {
+ name: "package.json",
+ path: root,
+ packageJson: {
+ ...rootPackageJson,
+ packageJsonPath: rootPackageJsonPath,
+ },
+ },
+ ];
+
+ for (const workspace of allWorkspaces) {
+ const { packageJsonPath, ...pkgJson } = workspace.packageJson;
+ const newJson = { ...pkgJson, packageManager: pkgManagerString };
+ runner.modifyFile({
+ filePath: packageJsonPath,
+ after: newJson,
+ });
+ }
+
+ return runner.finish();
+}
+
+const transformerMeta = {
+ name: `${TRANSFORMER}: ${DESCRIPTION}`,
+ value: TRANSFORMER,
+ introducedIn: INTRODUCED_IN,
+ transformer,
+};
+
+export default transformerMeta;
diff --git a/packages/turbo-codemod/src/transforms/create-turbo-config.ts b/packages/turbo-codemod/src/transforms/create-turbo-config.ts
new file mode 100644
index 0000000..0e8549a
--- /dev/null
+++ b/packages/turbo-codemod/src/transforms/create-turbo-config.ts
@@ -0,0 +1,70 @@
+import fs from "fs-extra";
+import path from "path";
+
+import { TransformerResults } from "../runner";
+import getTransformerHelpers from "../utils/getTransformerHelpers";
+import type { TransformerArgs } from "../types";
+
+// transformer details
+const TRANSFORMER = "create-turbo-config";
+const DESCRIPTION =
+ 'Create the `turbo.json` file from an existing "turbo" key in `package.json`';
+const INTRODUCED_IN = "1.1.0";
+
+export function transformer({
+ root,
+ options,
+}: TransformerArgs): TransformerResults {
+ const { log, runner } = getTransformerHelpers({
+ transformer: TRANSFORMER,
+ rootPath: root,
+ options,
+ });
+
+ log.info(`Migrating "package.json" "turbo" key to "turbo.json" file...`);
+ const turboConfigPath = path.join(root, "turbo.json");
+ const rootPackageJsonPath = path.join(root, "package.json");
+ if (!fs.existsSync(rootPackageJsonPath)) {
+ return runner.abortTransform({
+ reason: `No package.json found at ${root}. Is the path correct?`,
+ });
+ }
+
+ // read files
+ const rootPackageJson = fs.readJsonSync(rootPackageJsonPath);
+ let rootTurboJson = null;
+ try {
+ rootTurboJson = fs.readJSONSync(turboConfigPath);
+ } catch (err) {
+ rootTurboJson = null;
+ }
+
+ // modify files
+ let transformedPackageJson = rootPackageJson;
+ let transformedTurboConfig = rootTurboJson;
+ if (!rootTurboJson && rootPackageJson["turbo"]) {
+ const { turbo: turboConfig, ...remainingPkgJson } = rootPackageJson;
+ transformedTurboConfig = turboConfig;
+ transformedPackageJson = remainingPkgJson;
+ }
+
+ runner.modifyFile({
+ filePath: turboConfigPath,
+ after: transformedTurboConfig,
+ });
+ runner.modifyFile({
+ filePath: rootPackageJsonPath,
+ after: transformedPackageJson,
+ });
+
+ return runner.finish();
+}
+
+const transformerMeta = {
+ name: `${TRANSFORMER}: ${DESCRIPTION}`,
+ value: TRANSFORMER,
+ introducedIn: INTRODUCED_IN,
+ transformer,
+};
+
+export default transformerMeta;
diff --git a/packages/turbo-codemod/src/transforms/migrate-env-var-dependencies.ts b/packages/turbo-codemod/src/transforms/migrate-env-var-dependencies.ts
new file mode 100644
index 0000000..ef3a34c
--- /dev/null
+++ b/packages/turbo-codemod/src/transforms/migrate-env-var-dependencies.ts
@@ -0,0 +1,181 @@
+import fs from "fs-extra";
+import path from "path";
+import { getTurboConfigs } from "@turbo/utils";
+import type { Schema, Pipeline } from "@turbo/types";
+
+import getTransformerHelpers from "../utils/getTransformerHelpers";
+import { TransformerResults } from "../runner";
+import type { TransformerArgs } from "../types";
+
+// transformer details
+const TRANSFORMER = "migrate-env-var-dependencies";
+const DESCRIPTION =
+ 'Migrate environment variable dependencies from "dependsOn" to "env" in `turbo.json`';
+const INTRODUCED_IN = "1.5.0";
+
+export function hasLegacyEnvVarDependencies(config: Schema) {
+ const dependsOn = [
+ "extends" in config ? [] : config.globalDependencies,
+ Object.values(config.pipeline).flatMap(
+ (pipeline) => pipeline.dependsOn ?? []
+ ),
+ ].flat();
+ const envVars = dependsOn.filter((dep) => dep?.startsWith("$"));
+ return { hasKeys: !!envVars.length, envVars };
+}
+
+export function migrateDependencies({
+ env,
+ deps,
+}: {
+ env?: string[];
+ deps?: string[];
+}) {
+ const envDeps: Set<string> = new Set(env);
+ const otherDeps: string[] = [];
+ deps?.forEach((dep) => {
+ if (dep.startsWith("$")) {
+ envDeps.add(dep.slice(1));
+ } else {
+ otherDeps.push(dep);
+ }
+ });
+ if (envDeps.size) {
+ return {
+ deps: otherDeps,
+ env: Array.from(envDeps),
+ };
+ } else {
+ return { env, deps };
+ }
+}
+
+export function migratePipeline(pipeline: Pipeline) {
+ const { deps: dependsOn, env } = migrateDependencies({
+ env: pipeline.env,
+ deps: pipeline.dependsOn,
+ });
+ const migratedPipeline = { ...pipeline };
+ if (dependsOn) {
+ migratedPipeline.dependsOn = dependsOn;
+ } else {
+ delete migratedPipeline.dependsOn;
+ }
+ if (env && env.length) {
+ migratedPipeline.env = env;
+ } else {
+ delete migratedPipeline.env;
+ }
+
+ return migratedPipeline;
+}
+
+export function migrateGlobal(config: Schema) {
+ if ("extends" in config) {
+ return config;
+ }
+
+ const { deps: globalDependencies, env } = migrateDependencies({
+ env: config.globalEnv,
+ deps: config.globalDependencies,
+ });
+ const migratedConfig = { ...config };
+ if (globalDependencies && globalDependencies.length) {
+ migratedConfig.globalDependencies = globalDependencies;
+ } else {
+ delete migratedConfig.globalDependencies;
+ }
+ if (env && env.length) {
+ migratedConfig.globalEnv = env;
+ } else {
+ delete migratedConfig.globalEnv;
+ }
+ return migratedConfig;
+}
+
+export function migrateConfig(config: Schema) {
+ let migratedConfig = migrateGlobal(config);
+ Object.keys(config.pipeline).forEach((pipelineKey) => {
+ config.pipeline;
+ if (migratedConfig.pipeline && config.pipeline[pipelineKey]) {
+ const pipeline = migratedConfig.pipeline[pipelineKey];
+ migratedConfig.pipeline[pipelineKey] = {
+ ...pipeline,
+ ...migratePipeline(pipeline),
+ };
+ }
+ });
+ return migratedConfig;
+}
+
+export function transformer({
+ root,
+ options,
+}: TransformerArgs): TransformerResults {
+ const { log, runner } = getTransformerHelpers({
+ transformer: TRANSFORMER,
+ rootPath: root,
+ options,
+ });
+
+ log.info(
+ `Migrating environment variable dependencies from "globalDependencies" and "dependsOn" to "env" in "turbo.json"...`
+ );
+
+ // validate we don't have a package.json config
+ const packageJsonPath = path.join(root, "package.json");
+ let packageJSON = {};
+ try {
+ packageJSON = fs.readJSONSync(packageJsonPath);
+ } catch (e) {
+ // readJSONSync probably failed because the file doesn't exist
+ }
+
+ if ("turbo" in packageJSON) {
+ return runner.abortTransform({
+ reason:
+ '"turbo" key detected in package.json. Run `npx @turbo/codemod transform create-turbo-config` first',
+ });
+ }
+
+ // validate we have a root config
+ const turboConfigPath = path.join(root, "turbo.json");
+ if (!fs.existsSync(turboConfigPath)) {
+ return runner.abortTransform({
+ reason: `No turbo.json found at ${root}. Is the path correct?`,
+ });
+ }
+
+ let turboJson: Schema = fs.readJsonSync(turboConfigPath);
+ if (hasLegacyEnvVarDependencies(turboJson).hasKeys) {
+ turboJson = migrateConfig(turboJson);
+ }
+
+ runner.modifyFile({
+ filePath: turboConfigPath,
+ after: turboJson,
+ });
+
+ // find and migrate any workspace configs
+ const workspaceConfigs = getTurboConfigs(root);
+ workspaceConfigs.forEach((workspaceConfig) => {
+ const { config, turboConfigPath } = workspaceConfig;
+ if (hasLegacyEnvVarDependencies(config).hasKeys) {
+ runner.modifyFile({
+ filePath: turboConfigPath,
+ after: migrateConfig(config),
+ });
+ }
+ });
+
+ return runner.finish();
+}
+
+const transformerMeta = {
+ name: `${TRANSFORMER}: ${DESCRIPTION}`,
+ value: TRANSFORMER,
+ introducedIn: INTRODUCED_IN,
+ transformer,
+};
+
+export default transformerMeta;
diff --git a/packages/turbo-codemod/src/transforms/set-default-outputs.ts b/packages/turbo-codemod/src/transforms/set-default-outputs.ts
new file mode 100644
index 0000000..44f7fd1
--- /dev/null
+++ b/packages/turbo-codemod/src/transforms/set-default-outputs.ts
@@ -0,0 +1,97 @@
+import path from "path";
+import fs from "fs-extra";
+import { getTurboConfigs } from "@turbo/utils";
+import type { Schema as TurboJsonSchema } from "@turbo/types";
+
+import type { TransformerArgs } from "../types";
+import getTransformerHelpers from "../utils/getTransformerHelpers";
+import { TransformerResults } from "../runner";
+
+const DEFAULT_OUTPUTS = ["dist/**", "build/**"];
+
+// transformer details
+const TRANSFORMER = "set-default-outputs";
+const DESCRIPTION =
+ 'Add the "outputs" key with defaults where it is missing in `turbo.json`';
+const INTRODUCED_IN = "1.7.0";
+
+function migrateConfig(config: TurboJsonSchema) {
+ for (const [_, taskDef] of Object.entries(config.pipeline)) {
+ if (taskDef.cache !== false) {
+ if (!taskDef.outputs) {
+ taskDef.outputs = DEFAULT_OUTPUTS;
+ } else if (
+ Array.isArray(taskDef.outputs) &&
+ taskDef.outputs.length === 0
+ ) {
+ delete taskDef.outputs;
+ }
+ }
+ }
+
+ return config;
+}
+
+export function transformer({
+ root,
+ options,
+}: TransformerArgs): TransformerResults {
+ const { log, runner } = getTransformerHelpers({
+ transformer: TRANSFORMER,
+ rootPath: root,
+ options,
+ });
+
+ // If `turbo` key is detected in package.json, require user to run the other codemod first.
+ const packageJsonPath = path.join(root, "package.json");
+ // package.json should always exist, but if it doesn't, it would be a silly place to blow up this codemod
+ let packageJSON = {};
+
+ try {
+ packageJSON = fs.readJSONSync(packageJsonPath);
+ } catch (e) {
+ // readJSONSync probably failed because the file doesn't exist
+ }
+
+ if ("turbo" in packageJSON) {
+ return runner.abortTransform({
+ reason:
+ '"turbo" key detected in package.json. Run `npx @turbo/codemod transform create-turbo-config` first',
+ });
+ }
+
+ log.info(`Adding default \`outputs\` key into tasks if it doesn't exist`);
+ const turboConfigPath = path.join(root, "turbo.json");
+ if (!fs.existsSync(turboConfigPath)) {
+ return runner.abortTransform({
+ reason: `No turbo.json found at ${root}. Is the path correct?`,
+ });
+ }
+
+ const turboJson: TurboJsonSchema = fs.readJsonSync(turboConfigPath);
+ runner.modifyFile({
+ filePath: turboConfigPath,
+ after: migrateConfig(turboJson),
+ });
+
+ // find and migrate any workspace configs
+ const workspaceConfigs = getTurboConfigs(root);
+ workspaceConfigs.forEach((workspaceConfig) => {
+ const { config, turboConfigPath } = workspaceConfig;
+ runner.modifyFile({
+ filePath: turboConfigPath,
+ after: migrateConfig(config),
+ });
+ });
+
+ return runner.finish();
+}
+
+const transformerMeta = {
+ name: `${TRANSFORMER}: ${DESCRIPTION}`,
+ value: TRANSFORMER,
+ introducedIn: INTRODUCED_IN,
+ transformer,
+};
+
+export default transformerMeta;