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

fix: fix configure-test-app crashing on 0.75 #2190

Merged
merged 1 commit into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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: 1 addition & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ module.exports = [
{
patterns: [
{
group: ["[a-z]*", "!./*", "!node:*"],
group: ["[a-z]*", "!./*", "!./utils/*", "!node:*"],
message:
"External dependencies are not allowed in this file because it needs to be runnable before install.",
},
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
"scripts/parseargs.mjs",
"scripts/schema.mjs",
"scripts/template.mjs",
"scripts/utils/npm.mjs",
"test-app.gradle",
"test_app.rb",
"visionos",
Expand Down
26 changes: 24 additions & 2 deletions scripts/configure.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
serialize,
settingsGradle,
} from "./template.mjs";
import { downloadPackage } from "./utils/npm.mjs";

/**
* @typedef {import("./types.js").Configuration} Configuration
Expand Down Expand Up @@ -64,6 +65,25 @@ export function error(message) {
console.error(colors.red(`[!] ${message}`));
}

/**
* @param {string} targetVersion
* @returns {Promise<string | undefined>}
*/
async function findTemplateDir(targetVersion) {
if (toVersionNumber(targetVersion) < v(0, 75, 0)) {
// Let `getConfig` try to find the template inside `react-native`
return undefined;
}

const [major, minor = 0] = targetVersion.split(".");
const output = await downloadPackage(
"@react-native-community/template",
`${major}.${minor}`,
true
);
return path.join(output, "template");
}

/**
* Merges specified configurations.
* @param {Configuration} lhs
Expand Down Expand Up @@ -731,19 +751,21 @@ if (isMain(import.meta.url)) {
default: platformChoices,
},
},
({
async ({
_: { [0]: name },
flatten,
force,
init,
package: packagePath,
platforms,
}) => {
const targetVersion = getPackageVersion("react-native");
process.exitCode = configure({
name: typeof name === "string" && name ? name : getAppName(packagePath),
packagePath,
templatePath: await findTemplateDir(targetVersion),
testAppPath: fileURLToPath(new URL("..", import.meta.url)),
targetVersion: getPackageVersion("react-native"),
targetVersion,
platforms: validatePlatforms(platforms),
flatten,
force,
Expand Down
22 changes: 0 additions & 22 deletions scripts/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,6 @@ const nodefs = require("node:fs");
const path = require("node:path");
const { fileURLToPath } = require("node:url");

const npmRegistryBaseURL = "https://registry.npmjs.org/";

/**
* Fetches package metadata from the npm registry.
* @param {string} pkg
* @param {string=} distTag
*/
function fetchPackageMetadata(pkg, distTag) {
const init = {
headers: {
Accept:
"application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
},
};
const url = distTag
? npmRegistryBaseURL + pkg + "/" + distTag
: npmRegistryBaseURL + pkg;
return fetch(url, init).then((res) => res.json());
}

/**
* Finds the specified file using Node module resolution.
* @param {string} file
Expand Down Expand Up @@ -171,13 +151,11 @@ function getPackageVersion(module, startDir = process.cwd(), fs = nodefs) {
return version;
}

exports.fetchPackageMetadata = fetchPackageMetadata;
exports.findFile = findFile;
exports.findNearest = findNearest;
exports.getPackageVersion = getPackageVersion;
exports.isMain = isMain;
exports.memo = memo;
exports.npmRegistryBaseURL = npmRegistryBaseURL;
exports.readJSONFile = readJSONFile;
exports.readTextFile = readTextFile;
exports.requireTransitive = requireTransitive;
Expand Down
93 changes: 2 additions & 91 deletions scripts/init.mjs
Original file line number Diff line number Diff line change
@@ -1,74 +1,14 @@
#!/usr/bin/env node
// @ts-check
import { spawnSync } from "node:child_process";
import * as fs from "node:fs";
import * as https from "node:https";
import { createRequire } from "node:module";
import * as os from "node:os";
import * as path from "node:path";
import { fileURLToPath } from "node:url";
import prompts from "prompts";
import * as colors from "./colors.mjs";
import { configure, getDefaultPlatformPackageName } from "./configure.mjs";
import {
fetchPackageMetadata,
memo,
readJSONFile,
toVersionNumber,
v,
} from "./helpers.js";
import { memo, readJSONFile, toVersionNumber, v } from "./helpers.js";
import { parseArgs } from "./parseargs.mjs";

/**
* Invokes `tar xf`.
* @param {string} archive
*/
function untar(archive) {
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;
}

/**
* Fetches the tarball URL for the specified package and version.
* @param {string} pkg
* @param {string} version
* @returns {Promise<string>}
*/
async function fetchPackageTarballURL(pkg, version) {
const info = await fetchPackageMetadata(pkg);
const specific = info.versions[version];
if (specific) {
return specific.dist.tarball;
}

const versions = Object.keys(info.versions);
for (let i = versions.length - 1; i >= 0; --i) {
const v = versions[i];
if (v.startsWith(version)) {
return info.versions[v].dist.tarball;
}
}

throw new Error(`No match found for '${pkg}@${version}'`);
}
import { downloadPackage, fetchPackageMetadata } from "./utils/npm.mjs";

/**
* Returns the installed `react-native` manifest, if present.
Expand Down Expand Up @@ -165,35 +105,6 @@ async function getVersion(platforms) {
return target;
}

/**
* Downloads the specified npm package.
* @param {string} pkg
* @param {string} version
* @returns {Promise<string>}
*/
async function downloadPackage(pkg, version) {
const url = await fetchPackageTarballURL(pkg, version);
console.log(`Downloading ${path.basename(url)}...`);

return new Promise((resolve, reject) => {
https
.get(url, (res) => {
const tmpDir = path.join(os.tmpdir(), "react-native-test-app");
fs.mkdirSync(tmpDir, { recursive: true });

const dest = path.join(tmpDir, path.basename(url));
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));
});
}

/**
* Returns the React Native version and path to the template.
* @param {import("./types.js").Platform[]} platforms
Expand Down
3 changes: 1 addition & 2 deletions scripts/set-react-version.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import { promises as fs } from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import {
fetchPackageMetadata,
isMain,
npmRegistryBaseURL,
readJSONFile,
readTextFile,
toVersionNumber,
v,
} from "./helpers.js";
import { fetchPackageMetadata, npmRegistryBaseURL } from "./utils/npm.mjs";

/**
* @typedef {import("./types.js").Manifest} Manifest
Expand Down
17 changes: 17 additions & 0 deletions scripts/test-matrix.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,23 @@ if (platforms.length === 0) {
"ios"
);
})
.then(() => {
showBanner(`Reconfigure existing app`);
$(
PACKAGE_MANAGER,
"configure-test-app",
"-p",
"android",
"-p",
"ios",
"-p",
"macos",
"-p",
"visionos",
"-p",
"windows"
);
})
.then(() => {
showBanner(green("✔ Pass"));
});
Expand Down
114 changes: 114 additions & 0 deletions scripts/utils/npm.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// @ts-check
import { spawnSync } from "node:child_process";
import * as fs from "node:fs";
import * as https from "node:https";
import * as os from "node:os";
import * as path from "node:path";

export const npmRegistryBaseURL = "https://registry.npmjs.org/";

/**
* Invokes `tar xf`.
* @param {string} archive
*/
function untar(archive) {
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;
}

/**
* Fetches package metadata from the npm registry.
* @param {string} pkg
* @param {string=} distTag
*/
export function fetchPackageMetadata(pkg, distTag) {
const init = {
headers: {
Accept:
"application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*",
},
};
const url = distTag
? npmRegistryBaseURL + pkg + "/" + distTag
: npmRegistryBaseURL + pkg;
return fetch(url, init).then((res) => res.json());
}

/**
* Fetches the tarball URL for the specified package and version.
* @param {string} pkg
* @param {string} version
* @returns {Promise<string>}
*/
async function fetchPackageTarballURL(pkg, version) {
const info = await fetchPackageMetadata(pkg);
const specific = info.versions[version];
if (specific) {
return specific.dist.tarball;
}

const versions = Object.keys(info.versions);
for (let i = versions.length - 1; i >= 0; --i) {
const v = versions[i];
if (v.startsWith(version)) {
return info.versions[v].dist.tarball;
}
}

throw new Error(`No match found for '${pkg}@${version}'`);
}

/**
* Downloads the specified npm package.
* @param {string} pkg
* @param {string} version
* @param {boolean} useCache
* @returns {Promise<string>}
*/
export async function downloadPackage(pkg, version, useCache = false) {
const url = await fetchPackageTarballURL(pkg, version);

const tmpDir = path.join(os.tmpdir(), `react-native-test-app-${version}`);
const dest = path.join(tmpDir, path.basename(url));
const unpackedDir = path.join(tmpDir, "package");

if (useCache && fs.existsSync(dest) && fs.existsSync(unpackedDir)) {
return Promise.resolve(unpackedDir);
}

console.log(`Downloading ${path.basename(url)}...`);

return new Promise((resolve, reject) => {
https
.get(url, (res) => {
fs.mkdirSync(tmpDir, { recursive: true });

const fh = fs.createWriteStream(dest);
res.pipe(fh);
fh.on("finish", () => {
fh.close();
untar(dest);
resolve(unpackedDir);
});
})
.on("error", (err) => reject(err));
});
}
1 change: 1 addition & 0 deletions test/pack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ describe("npm pack", () => {
"scripts/parseargs.mjs",
"scripts/schema.mjs",
"scripts/template.mjs",
"scripts/utils/npm.mjs",
"test-app.gradle",
"test_app.rb",
"visionos/ReactTestApp.xcodeproj/project.pbxproj",
Expand Down
Loading