From 7ace6c33fb3f5b7fa4e5d031f80250b4028df14c Mon Sep 17 00:00:00 2001 From: Tommy Nguyen <4123478+tido64@users.noreply.github.com> Date: Tue, 15 Oct 2024 11:16:24 +0200 Subject: [PATCH] fix(align-deps): add profile for 0.76 --- .changeset/forty-shrimps-play.md | 5 + .changeset/wise-pillows-peel.md | 5 + packages/align-deps/package.json | 1 + .../align-deps/scripts/update-profile.mjs | 143 ++++++++++-------- .../src/presets/microsoft/react-native.ts | 2 + .../microsoft/react-native/profile-0.76.ts | 103 +++++++++++++ packages/tools-shell/README.md | 1 + packages/tools-shell/package.json | 5 + packages/tools-shell/src/index.ts | 1 + packages/tools-shell/src/untar.ts | 29 ++++ yarn.lock | 3 +- 11 files changed, 236 insertions(+), 62 deletions(-) create mode 100644 .changeset/forty-shrimps-play.md create mode 100644 .changeset/wise-pillows-peel.md create mode 100644 packages/align-deps/src/presets/microsoft/react-native/profile-0.76.ts create mode 100644 packages/tools-shell/src/untar.ts diff --git a/.changeset/forty-shrimps-play.md b/.changeset/forty-shrimps-play.md new file mode 100644 index 000000000..f27b1403d --- /dev/null +++ b/.changeset/forty-shrimps-play.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/tools-shell": patch +--- + +Added function for extracting tar files diff --git a/.changeset/wise-pillows-peel.md b/.changeset/wise-pillows-peel.md new file mode 100644 index 000000000..f4521f9f1 --- /dev/null +++ b/.changeset/wise-pillows-peel.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/align-deps": patch +--- + +Added profile for 0.76 diff --git a/packages/align-deps/package.json b/packages/align-deps/package.json index 98a204aac..16814daef 100644 --- a/packages/align-deps/package.json +++ b/packages/align-deps/package.json @@ -40,6 +40,7 @@ "@rnx-kit/scripts": "*", "@rnx-kit/tools-language": "*", "@rnx-kit/tools-node": "*", + "@rnx-kit/tools-shell": "*", "@rnx-kit/tools-workspaces": "*", "@rnx-kit/tsconfig": "*", "@types/jest": "^29.2.1", diff --git a/packages/align-deps/scripts/update-profile.mjs b/packages/align-deps/scripts/update-profile.mjs index 5dbbf97de..1a41c8cc8 100755 --- a/packages/align-deps/scripts/update-profile.mjs +++ b/packages/align-deps/scripts/update-profile.mjs @@ -1,9 +1,11 @@ #!/usr/bin/env node // @ts-check +import { untar } from "@rnx-kit/tools-shell"; import * as fs from "node:fs"; +import * as https from "node:https"; import * as path from "node:path"; -import { URL } from "node:url"; +import { URL, fileURLToPath } from "node:url"; import packageJson from "package-json"; import semverCoerce from "semver/functions/coerce.js"; import semverCompare from "semver/functions/compare.js"; @@ -19,6 +21,7 @@ import semverCompare from "semver/functions/compare.js"; * latest: string; * modified: string; * homepage?: string; + * tarball: string; * dependencies?: Record; * peerDependencies?: Record; * }} PackageInfo @@ -59,6 +62,7 @@ async function fetchPackageInfo(pkg, targetVersion = "latest") { const { version: latest, homepage, + dist: { tarball }, dependencies, peerDependencies, time, @@ -76,6 +80,7 @@ async function fetchPackageInfo(pkg, targetVersion = "latest") { homepage, dependencies: Optional(dependencies), peerDependencies: Optional(peerDependencies), + tarball, }; } @@ -294,93 +299,115 @@ export const profile: Profile = { * @returns {Promise} */ async function getCurrentMetroVersion(dependencies) { - const metroVersionDependencyChains = [ - // 0.73+ - ["@react-native/community-cli-plugin"], - // 0.65 - 0.72 - ["@react-native-community/cli", "@react-native-community/cli-plugin-metro"], - ]; - - for (const chain of metroVersionDependencyChains) { - const deps = await chain.reduce( - (p, packageName) => - p.then(async (dependencies) => { - if (!dependencies) { - return undefined; - } + const chain = ["react-native", "@react-native/community-cli-plugin"]; + const deps = await chain.reduce( + (p, packageName) => + p.then(async (dependencies) => { + if (!dependencies) { + return undefined; + } - try { + try { + const packageInfo = await packageJson(packageName, { + version: getPackageVersion(packageName, dependencies), + fullMetadata: true, + }); + return Optional(packageInfo.dependencies); + } catch (e) { + if (e.code === "ETARGET" || e.name === "VersionNotFoundError") { + // Some packages, such as `@react-native-community/cli`, are still + // in alpha or beta while react-native is in pre-release. Try + // again with the `next` tag. const packageInfo = await packageJson(packageName, { - version: getPackageVersion(packageName, dependencies), + version: "next", fullMetadata: true, }); return Optional(packageInfo.dependencies); - } catch (e) { - if (e.code === "ETARGET" || e.name === "VersionNotFoundError") { - // Some packages, such as `@react-native-community/cli`, are still - // in alpha or beta while react-native is in pre-release. Try - // again with the `next` tag. - const packageInfo = await packageJson(packageName, { - version: "next", - fullMetadata: true, - }); - return Optional(packageInfo.dependencies); - } else { - return undefined; - } + } else { + return undefined; } - }), - Promise.resolve(Optional(dependencies)) - ); + } + }), + Promise.resolve(Optional(dependencies)) + ); - if (deps) { - return getPackageVersion("metro", deps); - } + if (!deps) { + throw new Error("Failed to get 'metro' version"); } - throw new Error("Failed to get 'metro' version"); + return getPackageVersion("metro", deps); } /** * Fetches package versions for specified react-native version. * @param {string} preset * @param {string} targetVersion - * @param {Profile} latestProfile * @returns {Promise} */ -async function makeProfile(preset, targetVersion, latestProfile) { - const reactNativeInfo = await fetchPackageInfo( - latestProfile["core"], - `^${targetVersion}.0-0` - ); - if (!reactNativeInfo) { - throw new Error(`Failed to get manifest of 'react-native@${targetVersion}`); +async function makeProfile(preset, targetVersion) { + const templatePkg = { + name: "@react-native-community/template", + version: "0.0.0", + }; + const template = await fetchPackageInfo(templatePkg, `^${targetVersion}.0-0`); + if (!template) { + throw new Error( + `Failed to get manifest of '${templatePkg.name}@${targetVersion}` + ); } - const { dependencies, peerDependencies } = reactNativeInfo; + const { tarball } = template; + const templateDir = await new Promise((resolve, reject) => { + https + .get(tarball, (res) => { + const tmpUrl = new URL("../node_modules/.tmp", import.meta.url); + fs.mkdirSync(tmpUrl, { recursive: true }); + + const tmpDir = fileURLToPath(tmpUrl); + const dest = path.join(fileURLToPath(tmpUrl), path.basename(tarball)); + const fh = fs.createWriteStream(dest); + res.pipe(fh); + fh.on("finish", () => { + fh.close(); + untar(dest); + resolve(path.join(tmpDir, "package")); + }); + }) + .on("error", (err) => reject(err)); + }); + + const manifestPath = path.join(templateDir, "template", "package.json"); + const manifest = JSON.parse( + fs.readFileSync(manifestPath, { encoding: "utf-8" }) + ); + + const { dependencies, devDependencies } = manifest; if (!dependencies) { throw new Error( - `Failed to get dependencies of 'react-native@${targetVersion}` + `Failed to get dependencies of '${templatePkg.name}@${targetVersion}` ); } - if (!peerDependencies) { + if (!devDependencies) { throw new Error( - `Failed to get peer dependencies of 'react-native@${targetVersion}` + `Failed to get dev dependencies of '${templatePkg.name}@${targetVersion}` ); } return generateFromTemplate({ preset, targetVersion, - reactVersion: getPackageVersion("react", peerDependencies), - cliVersion: getPackageVersion("@react-native-community/cli", dependencies), + reactVersion: getPackageVersion("react", dependencies), + cliVersion: getPackageVersion( + "@react-native-community/cli", + devDependencies + ), cliAndroidVersion: getPackageVersion( "@react-native-community/cli-platform-android", - dependencies + devDependencies ), cliIOSVersion: getPackageVersion( "@react-native-community/cli-platform-ios", - dependencies + devDependencies ), metroVersion: await getCurrentMetroVersion(dependencies), }); @@ -409,8 +436,6 @@ async function main({ .reverse() ); - const latestProfile = preset[allVersions[0]]; - if (targetVersion) { if (!force && preset[targetVersion]) { console.error( @@ -420,11 +445,7 @@ async function main({ } try { - const newProfile = await makeProfile( - presetName, - targetVersion, - latestProfile - ); + const newProfile = await makeProfile(presetName, targetVersion); if (newProfile) { const [dst, presetFile] = getProfilePath(presetName, targetVersion); fs.writeFile(dst, newProfile, () => { @@ -505,7 +526,7 @@ async function main({ /** @type {[string, TableRow][]} */ const delta = []; await Promise.all( - Object.entries(latestProfile) + Object.entries(preset[allVersions[0]]) .filter(([capability]) => !ignoredCapabilities.includes(capability)) .map(async ([capability, pkg]) => { await fetchPackageInfo(pkg).then((info) => { diff --git a/packages/align-deps/src/presets/microsoft/react-native.ts b/packages/align-deps/src/presets/microsoft/react-native.ts index 093af5358..768a3f05c 100644 --- a/packages/align-deps/src/presets/microsoft/react-native.ts +++ b/packages/align-deps/src/presets/microsoft/react-native.ts @@ -14,6 +14,7 @@ import { profile as profile_0_72 } from "./react-native/profile-0.72"; import { profile as profile_0_73 } from "./react-native/profile-0.73"; import { profile as profile_0_74 } from "./react-native/profile-0.74"; import { profile as profile_0_75 } from "./react-native/profile-0.75"; +import { profile as profile_0_76 } from "./react-native/profile-0.76"; // Also export this by name for scripts to work around a bug where this module // is wrapped twice, i.e. `{ default: { default: preset } }`, when imported as @@ -34,4 +35,5 @@ export const preset: Readonly = { "0.73": profile_0_73, "0.74": profile_0_74, "0.75": profile_0_75, + "0.76": profile_0_76, }; diff --git a/packages/align-deps/src/presets/microsoft/react-native/profile-0.76.ts b/packages/align-deps/src/presets/microsoft/react-native/profile-0.76.ts new file mode 100644 index 000000000..e35599155 --- /dev/null +++ b/packages/align-deps/src/presets/microsoft/react-native/profile-0.76.ts @@ -0,0 +1,103 @@ +import type { Package, Profile } from "../../../types"; +import { profile as profile_0_75 } from "./profile-0.75"; + +const reactNative: Package = { + name: "react-native", + version: "^0.76.0", + capabilities: ["react", "core/metro-config", "community/cli"], +}; + +export const profile: Profile = { + ...profile_0_75, + react: { + name: "react", + version: "18.3.1", + }, + "react-dom": { + name: "react-dom", + version: "^18.3.1", + capabilities: ["react"], + }, + "react-test-renderer": { + name: "react-test-renderer", + version: "18.3.1", + capabilities: ["react"], + devOnly: true, + }, + + core: reactNative, + "core-android": reactNative, + "core-ios": reactNative, + "core-macos": { + name: "react-native-macos", + version: "^0.76.0", + capabilities: ["react"], + }, + "core-visionos": { + name: "@callstack/react-native-visionos", + version: "^0.76.0", + capabilities: ["react"], + }, + "core-windows": { + name: "react-native-windows", + version: "^0.76.0", + capabilities: ["core"], + }, + "core/metro-config": { + name: "@react-native/metro-config", + version: "^0.76.0", + devOnly: true, + }, + + "babel-preset-react-native": { + name: "@react-native/babel-preset", + version: "^0.76.0", + devOnly: true, + }, + "community/cli": { + name: "@react-native-community/cli", + version: "^15.0.0", + capabilities: ["community/cli-android", "community/cli-ios"], + devOnly: true, + }, + "community/cli-android": { + name: "@react-native-community/cli-platform-android", + version: "^15.0.0", + devOnly: true, + }, + "community/cli-ios": { + name: "@react-native-community/cli-platform-ios", + version: "^15.0.0", + devOnly: true, + }, + metro: { + name: "metro", + version: "^0.81.0", + devOnly: true, + }, + "metro-config": { + name: "metro-config", + version: "^0.81.0", + devOnly: true, + }, + "metro-core": { + name: "metro-core", + version: "^0.81.0", + devOnly: true, + }, + "metro-react-native-babel-transformer": { + name: "@react-native/metro-babel-transformer", + version: "^0.76.0", + devOnly: true, + }, + "metro-resolver": { + name: "metro-resolver", + version: "^0.81.0", + devOnly: true, + }, + "metro-runtime": { + name: "metro-runtime", + version: "^0.81.0", + devOnly: true, + }, +}; diff --git a/packages/tools-shell/README.md b/packages/tools-shell/README.md index dd0bddd76..ce108466f 100644 --- a/packages/tools-shell/README.md +++ b/packages/tools-shell/README.md @@ -29,5 +29,6 @@ import * as commandTools from "@rnx-kit/tools-shell/command"; | command | `ensureInstalled(check, message)` | Throws if the provided command fails. | | command | `makeCommand(command, userOptions)` | Creates an async function for calling the specified command. | | command | `makeCommandSync(command)` | Creates a synchronous function for calling the specified command. | +| untar | `untar(archive)` | Invokes `tar xf`. | diff --git a/packages/tools-shell/package.json b/packages/tools-shell/package.json index 759b87f3c..c0f4ef628 100644 --- a/packages/tools-shell/package.json +++ b/packages/tools-shell/package.json @@ -30,6 +30,11 @@ "typescript": "./src/command.ts", "default": "./lib/command.js" }, + "./untar": { + "types": "./lib/untar.d.ts", + "typescript": "./src/untar.ts", + "default": "./lib/untar.js" + }, "./package.json": "./package.json" }, "repository": { diff --git a/packages/tools-shell/src/index.ts b/packages/tools-shell/src/index.ts index c6b85e312..d667650f4 100644 --- a/packages/tools-shell/src/index.ts +++ b/packages/tools-shell/src/index.ts @@ -5,3 +5,4 @@ export { makeCommand, makeCommandSync, } from "./command.js"; +export { untar } from "./untar.js"; diff --git a/packages/tools-shell/src/untar.ts b/packages/tools-shell/src/untar.ts new file mode 100644 index 000000000..27c26f988 --- /dev/null +++ b/packages/tools-shell/src/untar.ts @@ -0,0 +1,29 @@ +import type { SpawnSyncReturns } from "node:child_process"; +import { spawnSync } from "node:child_process"; +import * as path from "node:path"; + +/** + * Invokes `tar xf`. + */ +export function untar(archive: string): SpawnSyncReturns { + const args = ["xf", archive]; + const options = { cwd: path.dirname(archive) }; + const result = spawnSync("tar", args, options); + + // If we run `tar` from Git Bash with a Windows path, it will fail with: + // + // tar: Cannot connect to C: resolve failed + // + // GNU Tar assumes archives with a colon in the file name are on another + // machine. See also + // https://www.gnu.org/software/tar/manual/html_section/file.html. + if ( + process.platform === "win32" && + result.stderr.toString().includes("tar: Cannot connect to") + ) { + args.push("--force-local"); + return spawnSync("tar", args, options); + } + + return result; +} diff --git a/yarn.lock b/yarn.lock index 22f411c8f..d19f8abaf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3593,6 +3593,7 @@ __metadata: "@rnx-kit/scripts": "npm:*" "@rnx-kit/tools-language": "npm:*" "@rnx-kit/tools-node": "npm:*" + "@rnx-kit/tools-shell": "npm:*" "@rnx-kit/tools-workspaces": "npm:*" "@rnx-kit/tsconfig": "npm:*" "@types/jest": "npm:^29.2.1" @@ -4572,7 +4573,7 @@ __metadata: languageName: unknown linkType: soft -"@rnx-kit/tools-shell@npm:^0.2.0, @rnx-kit/tools-shell@workspace:packages/tools-shell": +"@rnx-kit/tools-shell@npm:*, @rnx-kit/tools-shell@npm:^0.2.0, @rnx-kit/tools-shell@workspace:packages/tools-shell": version: 0.0.0-use.local resolution: "@rnx-kit/tools-shell@workspace:packages/tools-shell" dependencies: