Skip to content

Commit

Permalink
refactor(align-deps): migrate set-version command (#1948)
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 authored Oct 21, 2022
1 parent 1a13c1f commit c9c8236
Show file tree
Hide file tree
Showing 27 changed files with 655 additions and 802 deletions.
2 changes: 2 additions & 0 deletions .changeset/mean-bats-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
2 changes: 1 addition & 1 deletion packages/align-deps/scripts/update-profile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ async function main({
force,
}) {
const { preset } = await import(`../lib/presets/${presetName}.js`);
const allVersions = /** @type {import("../src/types").ProfileVersion[]} */ (
const allVersions = /** @type {string[]} */ (
Object.keys(preset)
.sort((lhs, rhs) => semverCompare(semverCoerce(lhs), semverCoerce(rhs)))
.reverse()
Expand Down
4 changes: 1 addition & 3 deletions packages/align-deps/scripts/update-readme.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ function sortCoreFirst(lhs, rhs) {
return lhs < rhs ? -1 : 1;
}

const allVersions = /** @type {import("../src/types").ProfileVersion[]} */ (
Object.keys(preset).reverse()
);
const allVersions = /** @type {string[]} */ (Object.keys(preset).reverse());
const allCapabilities = /** @type {import("@rnx-kit/config").Capability[]} */ (
Object.keys(preset[allVersions[0]]).sort(sortCoreFirst)
);
Expand Down
22 changes: 21 additions & 1 deletion packages/align-deps/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ import {
findWorkspacePackages,
findWorkspaceRoot,
} from "@rnx-kit/tools-workspaces";
import isString from "lodash/isString";
import * as path from "path";
import { makeCheckCommand } from "./commands/check";
import { makeInitializeCommand } from "./commands/initialize";
import { makeSetVersionCommand } from "./commands/setVersion";
import { defaultConfig } from "./config";
import { printError } from "./errors";
import { printError, printInfo } from "./errors";
import type { Args, Command } from "./types";

async function getManifests(
Expand Down Expand Up @@ -85,6 +87,7 @@ async function makeCommand(args: Args): Promise<Command | undefined> {
loose,
presets,
requirements,
"set-version": setVersion,
write,
} = args;

Expand All @@ -100,6 +103,13 @@ async function makeCommand(args: Args): Promise<Command | undefined> {
return makeInitializeCommand(init, options);
}

// When `--set-version` is without a value, `setVersion` is an empty string if
// invoked directly. When invoked via `@react-native-community/cli`,
// `setVersion` is `true` instead.
if (setVersion || isString(setVersion)) {
return makeSetVersionCommand(setVersion, options);
}

return makeCheckCommand(options);
}

Expand Down Expand Up @@ -141,6 +151,10 @@ export async function cli({ packages, ...args }: Args): Promise<void> {
}, 0);

process.exitCode = errors;

if (errors > 0) {
printInfo();
}
}

if (require.main === module) {
Expand Down Expand Up @@ -178,6 +192,12 @@ if (require.main === module) {
type: "string",
requiresArg: true,
},
"set-version": {
description:
"Sets `react-native` requirements for any configured package. There is an interactive prompt if no value is provided. The value should be a comma-separated list of `react-native` versions to set, where the first number specifies the development version. Example: `0.70,0.69`",
type: "string",
conflicts: ["init", "requirements"],
},
write: {
default: false,
description: "Writes changes to the specified 'package.json'.",
Expand Down
18 changes: 7 additions & 11 deletions packages/align-deps/src/commands/check.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,17 @@ import chalk from "chalk";
import { diffLinesUnified } from "jest-diff";
import * as path from "path";
import { migrateConfig } from "../compatibility/config";
import { getConfig } from "../config";
import { modifyManifest } from "../helpers";
import { loadConfig } from "../config";
import { isError, modifyManifest } from "../helpers";
import { updatePackageManifest } from "../manifest";
import { resolve } from "../preset";
import type { Command, ErrorCode, Options } from "../types";
import { checkPackageManifestUnconfigured } from "./vigilant";

function isError(config: ReturnType<typeof getConfig>): config is ErrorCode {
return typeof config === "string";
}

export function checkPackageManifest(
manifestPath: string,
options: Options,
inputConfig = getConfig(manifestPath)
inputConfig = loadConfig(manifestPath)
): ErrorCode {
if (isError(inputConfig)) {
return inputConfig;
Expand All @@ -43,10 +39,10 @@ export function checkPackageManifest(
);
} else {
info(
"Aligning your library's dependencies according to the following profiles:"
"Aligning your library's dependencies according to the following profiles:\n" +
`\t- Development: ${Object.keys(devPreset).join(", ")}\n` +
`\t- Production: ${Object.keys(prodPreset).join(", ")}`
);
info("\t- Development:", Object.keys(devPreset).join(", "));
info("\t- Production:", Object.keys(prodPreset).join(", "));
}

const updatedManifest = updatePackageManifest(
Expand Down Expand Up @@ -90,7 +86,7 @@ export function makeCheckCommand(options: Options): Command {
}

return (manifest: string) => {
const inputConfig = getConfig(manifest);
const inputConfig = loadConfig(manifest);
const config = isError(inputConfig)
? inputConfig
: migrateConfig(inputConfig);
Expand Down
2 changes: 1 addition & 1 deletion packages/align-deps/src/commands/initialize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function initializeConfig(
const requirements = [
`react-native@${dropPatchFromVersion(targetReactNativeVersion)}`,
];
const preset = filterPreset(requirements, mergePresets(presets, projectRoot));
const preset = filterPreset(mergePresets(presets, projectRoot), requirements);

return {
...manifest,
Expand Down
168 changes: 168 additions & 0 deletions packages/align-deps/src/commands/setVersion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import type { PackageManifest } from "@rnx-kit/tools-node/package";
import isString from "lodash/isString";
import prompts from "prompts";
import semverCoerce from "semver/functions/coerce";
import { transformConfig } from "../compatibility/config";
import { defaultConfig, loadConfig } from "../config";
import { isError, keysOf, modifyManifest } from "../helpers";
import defaultPreset from "../presets/microsoft/react-native";
import type {
AlignDepsConfig,
Command,
LegacyCheckConfig,
Options,
} from "../types";
import { checkPackageManifest } from "./check";

function parseVersions(versions: string): string[] {
return versions.split(",").map((v) => {
const coerced = semverCoerce(v.trim());
if (!coerced) {
throw new Error(`'${v}' is not a valid version number`);
}

const parsedVersion = `${coerced.major}.${coerced.minor}`;
if (!(parsedVersion in defaultPreset)) {
throw new Error(
`'${parsedVersion}' is not a supported react-native version`
);
}

return parsedVersion;
});
}

function toChoice(version: string): prompts.Choice {
return { title: version, value: version };
}

async function parseInput(versions: string | number): Promise<{
supportedVersions: string[];
targetVersion: string;
} | null> {
// When `--set-version` is without a value, `versions` is an empty string if
// invoked directly. When invoked via `@react-native-community/cli`,
// `versions` is `true` instead.
if (isString(versions) && versions) {
const supportedVersions = parseVersions(versions);
const targetVersion = supportedVersions[0];
return { supportedVersions: supportedVersions.sort(), targetVersion };
}

const { supportedVersions } = await prompts({
type: "multiselect",
name: "supportedVersions",
message: "Select all supported versions of `react-native`",
choices: keysOf(defaultPreset).map(toChoice),
min: 1,
});
if (!Array.isArray(supportedVersions)) {
return null;
}

const targetVersion =
supportedVersions.length === 1
? supportedVersions[0]
: (
await prompts({
type: "select",
name: "targetVersion",
message: "Select development version of `react-native`",
choices: supportedVersions.map(toChoice),
})
).targetVersion;
if (!supportedVersions.includes(targetVersion)) {
return null;
}

return { supportedVersions, targetVersion };
}

function setRequirement(requirements: string[], versionRange: string): void {
const prefix = "react-native@";
const index = requirements.findIndex((r: string) => r.startsWith(prefix));
if (index >= 0) {
requirements[index] = prefix + versionRange;
}
}

function updateRequirements(
{ requirements }: AlignDepsConfig["alignDeps"],
prodVersion: string,
devVersion = prodVersion
): void {
if (Array.isArray(requirements)) {
setRequirement(requirements, prodVersion);
} else {
setRequirement(requirements.production, prodVersion);
setRequirement(requirements.development, devVersion);
}
}

function setVersion(
config: AlignDepsConfig | LegacyCheckConfig,
targetVersion: string,
supportedVersions: string[]
): PackageManifest {
const { kitType, manifest } = config;
const alignDeps =
"alignDeps" in config
? config.alignDeps
: transformConfig(config).alignDeps;

if (kitType === "app") {
updateRequirements(alignDeps, targetVersion);
} else {
updateRequirements(
alignDeps,
supportedVersions.join(" || "),
targetVersion
);
}

manifest["rnx-kit"] = {
...manifest["rnx-kit"],
kitType,
alignDeps: {
...alignDeps,
presets:
// The default presets were added with `loadConfig`. We need to remove
// it here to not add new fields to the config.
alignDeps.presets === defaultConfig.presets
? undefined
: alignDeps.presets,
},
};

return config.manifest;
}

export async function makeSetVersionCommand(
versions: string | number,
options: Options
): Promise<Command | undefined> {
const input = await parseInput(versions);
if (!input) {
return undefined;
}

const { supportedVersions, targetVersion } = input;
const checkOnly = { ...options, loose: false, write: false };
const write = { ...options, loose: false, write: true };

return (manifestPath: string) => {
const config = loadConfig(manifestPath);
if (isError(config)) {
return config;
}

const checkResult = checkPackageManifest(manifestPath, checkOnly, config);
if (checkResult !== "success") {
return checkResult;
}

const result = setVersion(config, targetVersion, supportedVersions);
modifyManifest(manifestPath, result);
return checkPackageManifest(manifestPath, write);
};
}
8 changes: 3 additions & 5 deletions packages/align-deps/src/commands/vigilant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ export function buildManifestProfile(
const [targetPreset, supportPreset] = (() => {
const { requirements } = alignDeps;
if (Array.isArray(requirements)) {
const preset = filterPreset(requirements, mergedPresets);
const preset = filterPreset(mergedPresets, requirements);
return [preset, preset];
}

const prodPreset = filterPreset(requirements.production, mergedPresets);
const prodPreset = filterPreset(mergedPresets, requirements.production);
return kitType === "app"
? [prodPreset, prodPreset]
: [filterPreset(requirements.development, mergedPresets), prodPreset];
: [filterPreset(mergedPresets, requirements.development), prodPreset];
})();

const unmanagedCapabilities = getAllCapabilities(targetPreset).filter(
Expand All @@ -93,8 +93,6 @@ export function buildManifestProfile(
);

return {
name: "@rnx-kit/align-deps/vigilant-preset",
version: "1.0.0",
dependencies: directDependencies,
peerDependencies,
devDependencies: directDependencies,
Expand Down
20 changes: 9 additions & 11 deletions packages/align-deps/src/compatibility/config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { KitConfig } from "@rnx-kit/config";
import { warn } from "@rnx-kit/console";
import { defaultConfig } from "../config";
import { dropPatchFromVersion } from "../helpers";
import type { AlignDepsConfig, CheckConfig } from "../types";
import type { AlignDepsConfig, LegacyCheckConfig } from "../types";

function oldConfigKeys(config: KitConfig): (keyof KitConfig)[] {
const oldKeys = [
Expand All @@ -24,25 +25,22 @@ function oldConfigKeys(config: KitConfig): (keyof KitConfig)[] {
*/
export function transformConfig({
capabilities,
customProfilesPath,
customProfiles,
kitType,
manifest,
reactNativeDevVersion,
reactNativeVersion,
}: CheckConfig): AlignDepsConfig {
}: LegacyCheckConfig): AlignDepsConfig {
const devVersion = dropPatchFromVersion(
reactNativeDevVersion || reactNativeVersion
);
const prodVersion = dropPatchFromVersion(
reactNativeVersion || reactNativeDevVersion
);
const prodVersion = dropPatchFromVersion(reactNativeVersion);
return {
kitType,
alignDeps: {
presets: [
"microsoft/react-native",
...(customProfilesPath ? [customProfilesPath] : []),
],
presets: customProfiles
? [...defaultConfig.presets, customProfiles]
: defaultConfig.presets,
requirements:
kitType === "app"
? [`react-native@${reactNativeVersion}`]
Expand All @@ -57,7 +55,7 @@ export function transformConfig({
}

export function migrateConfig(
config: AlignDepsConfig | CheckConfig
config: AlignDepsConfig | LegacyCheckConfig
): AlignDepsConfig {
if ("alignDeps" in config) {
const oldKeys = oldConfigKeys(config);
Expand Down
Loading

0 comments on commit c9c8236

Please sign in to comment.