Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(dep-check): introduce the new configuration schema #1911

Merged
merged 10 commits into from
Oct 13, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/tame-needles-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
20 changes: 15 additions & 5 deletions packages/align-deps/scripts/update-profile.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { isMetaPackage } from "../lib/capabilities.js";
* name: string;
* version: string;
* latest: string;
* homepage: string;
* homepage?: string;
* dependencies?: Record<string, string>;
* peerDependencies?: Record<string, string>;
* }} PackageInfo
Expand Down Expand Up @@ -62,7 +62,13 @@ function getPackageVersion(packageName, dependencies) {
if (!packageVersion) {
throw new Error(`Failed to get '${packageName}' version`);
}
return semverCoerce(packageVersion).version;

const coercedVersion = semverCoerce(packageVersion);
if (!coercedVersion) {
throw new Error(`Failed to coerce version: ${packageVersion}`);
}

return coercedVersion.version;
}

/**
Expand Down Expand Up @@ -103,6 +109,10 @@ function generateFromTemplate({
metroVersion,
}) {
const nextVersionCoerced = semverCoerce(targetVersion);
if (!nextVersionCoerced) {
throw new Error(`Failed to coerce version: ${targetVersion}`);
}

const currentVersion = `${nextVersionCoerced.major}.${
nextVersionCoerced.minor - 1
}`;
Expand All @@ -115,7 +125,7 @@ function generateFromTemplate({
const currentVersionVarName = `${nextVersionCoerced.major}_${
nextVersionCoerced.minor - 1
}`;
return `import type { Profile, Package } from "../../types";
return `import type { Package, Profile } from "../../../types";
import profile_${currentVersionVarName} from "./profile-${currentVersion}";

const reactNative: Package = {
Expand Down Expand Up @@ -271,11 +281,11 @@ async function makeProfile(preset, targetVersion, latestProfile) {
* @param {{ preset?: string; targetVersion?: string; force?: boolean; }} options
*/
async function main({
preset: presetName = "microsoft",
preset: presetName = "microsoft/react-native",
targetVersion = "",
force,
}) {
const { preset } = await import(`../lib/presets/${presetName}/index.js`);
const { preset } = await import(`../lib/presets/${presetName}.js`);
const allVersions = /** @type {import("../src/types").ProfileVersion[]} */ (
Object.keys(preset)
.sort((lhs, rhs) => semverCompare(semverCoerce(lhs), semverCoerce(rhs)))
Expand Down
2 changes: 1 addition & 1 deletion packages/align-deps/scripts/update-readme.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

const fs = require("fs");
const markdownTable = require("markdown-table");
const { preset } = require("../lib/presets/microsoft");
const { preset } = require("../lib/presets/microsoft/react-native");

const README = "README.md";
const TOKEN_START = "<!-- @rnx-kit/align-deps/capabilities start -->";
Expand Down
2 changes: 1 addition & 1 deletion packages/align-deps/src/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export function resolveCapabilities(

if (unresolvedCapabilities.size > 0) {
const message = Array.from(unresolvedCapabilities).reduce(
(lines, capability) => (lines += `\n ${capability}`),
(lines, capability) => (lines += `\n\t${capability}`),
"The following capabilities could not be resolved for one or more profiles:"
);

Expand Down
217 changes: 187 additions & 30 deletions packages/align-deps/src/check.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,56 @@
import type { KitConfig } from "@rnx-kit/config";
import { getKitCapabilities, getKitConfig } from "@rnx-kit/config";
import { error, info, warn } from "@rnx-kit/console";
import { isPackageManifest, readPackage } from "@rnx-kit/tools-node/package";
import chalk from "chalk";
import { diffLinesUnified } from "jest-diff";
import path from "path";
import { getRequirements } from "./dependencies";
import { migrateConfig } from "./compatibility/config";
import { gatherRequirements, getRequirements } from "./dependencies";
import { findBadPackages } from "./findBadPackages";
import { modifyManifest } from "./helpers";
import { updatePackageManifest } from "./manifest";
import { getProfilesFor, resolveCustomProfiles } from "./profiles";
import type { CheckConfig, CheckOptions, Command } from "./types";
import { filterPreset, mergePresets } from "./preset";
import { resolveCustomProfiles } from "./profiles";
import type {
AlignDepsConfig,
CheckConfig,
CheckOptions,
Command,
ErrorCode,
Options,
Preset,
} from "./types";

export function containsValidPresets(config: KitConfig["alignDeps"]): boolean {
const presets = config?.presets;
return !presets || (Array.isArray(presets) && presets.length > 0);
}

export function containsValidRequirements(
config: KitConfig["alignDeps"]
): boolean {
const requirements = config?.requirements;
if (requirements) {
if (Array.isArray(requirements)) {
return requirements.length > 0;
} else if (typeof requirements === "object") {
return (
Array.isArray(requirements.production) &&
requirements.production.length > 0
);
}
}
return false;
}

function ensurePreset(preset: Preset, requirements: string[]): void {
if (Object.keys(preset).length === 0) {
throw new Error(
`No profiles could satisfy requirements: ${requirements.join(", ")}`
);
}
}

export function getCheckConfig(
manifestPath: string,
Expand Down Expand Up @@ -87,33 +128,159 @@ export function getCheckConfig(
};
}

export function checkPackageManifest(
manifestPath: string,
options: CheckOptions
): number {
const result = options.config || getCheckConfig(manifestPath, options);
if (typeof result === "number") {
return result;
export function getConfig(
manifestPath: string
): AlignDepsConfig | CheckConfig | ErrorCode {
const manifest = readPackage(manifestPath);
if (!isPackageManifest(manifest)) {
return "invalid-manifest";
}

const badPackages = findBadPackages(manifest);
if (badPackages) {
warn(
`Known bad packages are found in '${manifest.name}':\n` +
badPackages
.map((pkg) => `\t${pkg.name}@${pkg.version}: ${pkg.reason}`)
.join("\n")
);
}

const projectRoot = path.dirname(manifestPath);
const kitConfig = getKitConfig({ cwd: projectRoot });
if (!kitConfig) {
return "not-configured";
}

const { kitType = "library", alignDeps, ...config } = kitConfig;
if (alignDeps) {
const errors = [];
if (!containsValidPresets(alignDeps)) {
errors.push(`${manifestPath}: 'alignDeps.presets' cannot be empty`);
}
if (!containsValidRequirements(alignDeps)) {
errors.push(`${manifestPath}: 'alignDeps.requirements' cannot be empty`);
}
if (errors.length > 0) {
for (const e of errors) {
error(e);
}
return "invalid-configuration";
}
return {
kitType,
alignDeps: {
presets: ["microsoft/react-native"],
requirements: [],
capabilities: [],
...alignDeps,
},
...config,
manifest,
};
}

const {
capabilities,
customProfiles,
reactNativeDevVersion,
reactNativeVersion,
} = getKitCapabilities(config);

return {
kitType,
reactNativeVersion,
reactNativeDevVersion,
...(config.reactNativeDevVersion ? { reactNativeDevVersion } : undefined),
capabilities,
customProfilesPath,
customProfiles,
manifest,
} = result;
} as CheckConfig;
}

function resolve(
{ kitType, alignDeps, manifest }: AlignDepsConfig,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
{ kitType, alignDeps, manifest }: AlignDepsConfig,
{ kitType, alignDeps: { capabilities, presets, requirements }, manifest }: AlignDepsConfig,

Can you do nested destructuring like this? More of a curiosity than a suggestion...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you can. It affects legibility so I don't do it as much.

projectRoot: string,
options: CheckOptions
) {
const { capabilities, presets, requirements } = alignDeps;

const prodRequirements = Array.isArray(requirements)
? requirements
: requirements.production;
const mergedPreset = mergePresets(presets, projectRoot);
const initialProdPreset = filterPreset(prodRequirements, mergedPreset);
ensurePreset(initialProdPreset, prodRequirements);

const devPreset = (() => {
if (kitType === "app") {
// Preset for development is unused when the package is an app.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sidenote: we need to make sure this is clear in the docs - I can see people not be aware

return {};
} else if (Array.isArray(requirements)) {
return initialProdPreset;
} else {
const devRequirements = requirements.development;
const devPreset = filterPreset(devRequirements, mergedPreset);
ensurePreset(devPreset, devRequirements);
return devPreset;
}
})();

if (kitType === "app") {
const { preset: prodMergedPreset, capabilities: mergedCapabilities } =
gatherRequirements(
projectRoot,
manifest,
initialProdPreset,
capabilities,
options
);
return {
devPreset,
prodPreset: prodMergedPreset,
capabilities: mergedCapabilities,
};
}

return { devPreset, prodPreset: initialProdPreset, capabilities };
}

export function checkPackageManifest(
manifestPath: string,
options: CheckOptions,
inputConfig = getConfig(manifestPath)
): ErrorCode {
if (typeof inputConfig === "string") {
return inputConfig;
}

const config = migrateConfig(inputConfig);
const { devPreset, prodPreset, capabilities } = resolve(
config,
path.dirname(manifestPath),
options
);
if (capabilities.length === 0) {
return options.uncheckedReturnCode || 0;
return "success";
}

const { kitType, manifest } = config;

if (kitType === "app") {
info(
"Aligning dependencies according to the following profiles:",
Object.keys(prodPreset).join(", ")
);
} else {
info("Aligning dependencies according to the following profiles:");
info("\t- Development:", Object.keys(devPreset).join(", "));
info("\t- Production:", Object.keys(prodPreset).join(", "));
}

const updatedManifest = updatePackageManifest(
manifest,
capabilities,
getProfilesFor(reactNativeVersion, customProfilesPath),
getProfilesFor(reactNativeDevVersion, customProfilesPath),
Object.values(prodPreset),
Object.values(devPreset),
kitType
);

Expand All @@ -136,23 +303,13 @@ export function checkPackageManifest(
}
);
console.log(diff);

error(
"Changes are needed to satisfy all requirements. Re-run with `--write` to have dep-check apply them."
);

const url = chalk.bold("https://aka.ms/dep-check");
info(`Visit ${url} for more information about dep-check.`);

return 1;
return "unsatisfied";
}
}

return 0;
return "success";
}

export function makeCheckCommand(options: CheckOptions): Command {
return (manifest: string) => {
return checkPackageManifest(manifest, options);
};
export function makeCheckCommand(options: Options): Command {
return (manifest: string) => checkPackageManifest(manifest, options);
}
Loading