first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
declare const $$EXPORT_DEFAULT_DECLARATION$$: { revision: "dev" };
declare type $$EXPORT_DEFAULT_DECLARATION$$ =
typeof $$EXPORT_DEFAULT_DECLARATION$$;
export default $$EXPORT_DEFAULT_DECLARATION$$;

View File

@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _default = (exports.default = {
revision: "dev",
});

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
declare export default { revision: "dev" };

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* %s
* @flow strict-local
* @format
*/
'use strict';
module.exports = {
default: {
revision: %s,
},
__esModule: true,
};

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/

View File

@@ -0,0 +1,162 @@
"use strict";
var _SettingsStore = _interopRequireDefault(require("./SettingsStore.js"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const path = require("path");
const util = require("util");
const { BrowserWindow, Menu, app, shell, ipcMain } = require("electron");
const appSettings = new _SettingsStore.default();
const windowMetadata = new WeakMap();
function handleLaunchArgs(argv) {
const {
values: { frontendUrl, windowKey },
} = util.parseArgs({
options: {
frontendUrl: {
type: "string",
},
windowKey: {
type: "string",
},
},
args: argv,
});
let frontendWindow = BrowserWindow.getAllWindows().find((window) => {
const metadata = windowMetadata.get(window);
if (!metadata) {
return false;
}
return metadata.windowKey === windowKey;
});
if (frontendWindow) {
if (frontendWindow.isVisible()) {
frontendWindow.flashFrame(true);
setTimeout(() => {
frontendWindow.flashFrame(false);
}, 1000);
}
} else {
frontendWindow = new BrowserWindow({
...(getSavedWindowPosition(windowKey) ?? {
width: 1200,
height: 600,
}),
webPreferences: {
partition: "persist:react-native-devtools",
preload: require.resolve("./preload.js"),
},
icon: path.join(__dirname, "resources", "icon.png"),
});
frontendWindow.setMenuBarVisibility(false);
setupWindowResizeListeners(frontendWindow, windowKey);
}
frontendWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return {
action: "deny",
};
});
frontendWindow.loadURL(frontendUrl);
windowMetadata.set(frontendWindow, {
windowKey,
});
if (process.platform === "darwin") {
app.focus({
steal: true,
});
}
frontendWindow.focus();
}
function configureAppMenu() {
const template = [
...(process.platform === "darwin"
? [
{
role: "appMenu",
},
]
: []),
{
role: "fileMenu",
},
{
role: "editMenu",
},
{
role: "viewMenu",
},
{
role: "windowMenu",
},
{
role: "help",
submenu: [
{
label: "React Native Website",
click: () => shell.openExternal("https://reactnative.dev"),
},
{
label: "Release Notes",
click: () =>
shell.openExternal(
"https://github.com/facebook/react-native/releases",
),
},
],
},
];
const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu);
}
function getSavedWindowPosition(windowKey) {
return appSettings.get("windowArrangements", {})[windowKey];
}
function saveWindowPosition(windowKey, position) {
const windowArrangements = appSettings.get("windowArrangements", {});
windowArrangements[windowKey] = position;
appSettings.set("windowArrangements", windowArrangements);
}
function setupWindowResizeListeners(browserWindow, windowKey) {
const savePosition = () => {
if (!browserWindow.isDestroyed()) {
const [x, y] = browserWindow.getPosition();
const [width, height] = browserWindow.getSize();
saveWindowPosition(windowKey, {
x,
y,
width,
height,
});
}
};
browserWindow.on("moved", savePosition);
browserWindow.on("resized", savePosition);
browserWindow.on("closed", savePosition);
}
app.whenReady().then(() => {
handleLaunchArgs(process.argv.slice(app.isPackaged ? 1 : 2));
configureAppMenu();
app.on(
"second-instance",
(event, electronArgv, workingDirectory, additionalData) => {
handleLaunchArgs(additionalData.argv);
},
);
});
app.on("window-all-closed", function () {
app.quit();
});
ipcMain.on("bringToFront", (event, title) => {
const webContents = event.sender;
const win = BrowserWindow.fromWebContents(webContents);
if (win) {
win.focus();
}
if (process.platform === "darwin") {
app.focus({
steal: true,
});
}
});

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
* @oncall react_native
*/
type Options = Readonly<{ name?: string; defaults?: Object }>;
/**
* A data persistence layer for storing application settings, modelled after
* [`electron-store`](https://www.npmjs.com/package/electron-store).
*
* Values are saved in a `config.json` file in `app.getPath('userData')`.
*
* Compatibility:
* - Maintains API and file format compatibility with `electron-store@8.2.0`.
* - Supports the Electron main process only.
*/
declare class SettingsStore {
path: string;
constructor(options?: Options);
get(key: string, defaultValue?: any): any;
set(key: string, value: any): void;
has(key: string): boolean;
reset(...keys: Array<string>): void;
delete(key: string): void;
clear(): void;
get store(): { [$$Key$$: string]: unknown };
set store(value: unknown);
_deserialize: (value: string) => unknown;
_serialize: (value: unknown) => string;
_ensureDirectory(): void;
_write(value: unknown): void;
}
export default SettingsStore;

View File

@@ -0,0 +1,95 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
const { app } = require("electron");
const fs = require("fs");
const path = require("path");
class SettingsStore {
#defaultValues = {};
constructor(options = {}) {
options = {
name: "config",
...options,
};
this.#defaultValues = {
...this.#defaultValues,
...options.defaults,
};
this.path = path.resolve(
app.getPath("userData"),
`${options.name ?? "config"}.json`,
);
}
get(key, defaultValue) {
const store = this.store;
return store[key] !== undefined ? store[key] : defaultValue;
}
set(key, value) {
const { store } = this;
if (typeof key === "object") {
for (const [k, v] of Object.entries(key)) {
store[k] = v;
}
} else {
store[key] = value;
}
this.store = store;
}
has(key) {
return key in this.store;
}
reset(...keys) {
for (const key of keys) {
if (this.#defaultValues[key] != null) {
this.set(key, this.#defaultValues[key]);
}
}
}
delete(key) {
const { store } = this;
delete store[key];
this.store = store;
}
clear() {
this.store = {};
for (const key of Object.keys(this.#defaultValues)) {
this.reset(key);
}
}
get store() {
try {
const data = fs.readFileSync(this.path, "utf8");
const deserializedData = this._deserialize(data);
return {
...deserializedData,
};
} catch (error) {
if (error?.code === "ENOENT") {
this._ensureDirectory();
return {};
}
throw error;
}
}
set store(value) {
this._ensureDirectory();
this._write(value);
}
_deserialize = (value) => JSON.parse(value);
_serialize = (value) => JSON.stringify(value, undefined, "\t") ?? "";
_ensureDirectory() {
fs.mkdirSync(path.dirname(this.path), {
recursive: true,
});
}
_write(value) {
const data = this._serialize(value);
fs.writeFileSync(this.path, data, {
mode: 0o666,
});
}
}
exports.default = SettingsStore;

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @oncall react_native
*/
type Options = $ReadOnly<{
name?: string,
defaults?: Object,
}>;
/**
* A data persistence layer for storing application settings, modelled after
* [`electron-store`](https://www.npmjs.com/package/electron-store).
*
* Values are saved in a `config.json` file in `app.getPath('userData')`.
*
* Compatibility:
* - Maintains API and file format compatibility with `electron-store@8.2.0`.
* - Supports the Electron main process only.
*/
declare export default class SettingsStore {
path: string;
constructor(options?: Options): void;
get(key: string, defaultValue?: any): any;
set(key: string, value: any): void;
has(key: string): boolean;
reset(...keys: Array<string>): void;
delete(key: string): void;
clear(): void;
get store(): { [string]: mixed };
set store(value: mixed): void;
_deserialize: (value: string) => mixed;
_serialize: (value: mixed) => string;
_ensureDirectory(): void;
_write(value: mixed): void;
}

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/

View File

@@ -0,0 +1,34 @@
"use strict";
var _BuildInfo = _interopRequireDefault(require("./BuildInfo"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const pkg = require("../../package.json");
const util = require("util");
const { app } = require("electron");
app.setName(pkg.productName ?? pkg.name);
app.setVersion(pkg.version + "-" + _BuildInfo.default.revision);
const {
values: { version = false },
} = util.parseArgs({
options: {
version: {
type: "boolean",
},
},
args: process.argv.slice(app.isPackaged ? 1 : 2),
strict: false,
});
if (version) {
console.log(`${pkg.name} v${app.getVersion()}`);
app.exit(0);
}
const gotTheLock = app.requestSingleInstanceLock({
argv: process.argv.slice(app.isPackaged ? 1 : 2),
});
if (!gotTheLock) {
app.quit();
} else {
require("./MainInstanceEntryPoint.js");
}

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/

View File

@@ -0,0 +1,31 @@
"use strict";
const { contextBridge, ipcRenderer } = require("electron");
contextBridge.executeInMainWorld({
func: (ipcDevTools) => {
let didDecorateInspectorFrontendHostInstance = false;
globalThis.reactNativeDecorateInspectorFrontendHostInstance = (
InspectorFrontendHostInstance,
) => {
didDecorateInspectorFrontendHostInstance = true;
InspectorFrontendHostInstance.bringToFront = () => {
ipcDevTools.bringToFront();
};
};
document.addEventListener("DOMContentLoaded", () => {
if (!didDecorateInspectorFrontendHostInstance) {
console.error(
"reactNativeDecorateInspectorFrontendHostInstance was not called at startup. " +
"This version of the DevTools frontend may not be compatible with @react-native/debugger-shell.",
);
}
});
},
args: [
{
bringToFront() {
ipcRenderer.send("bringToFront");
},
},
],
});

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/

View File

@@ -0,0 +1,7 @@
"use strict";
if ("electron" in process.versions) {
module.exports = require("./electron");
} else {
module.exports = require("./node");
}

View File

@@ -0,0 +1,9 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/

View File

@@ -0,0 +1,46 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
type DebuggerShellFlavor = "prebuilt" | "dev";
declare function unstable_spawnDebuggerShellWithArgs(
args: string[],
$$PARAM_1$$?: Readonly<{
mode?: "syncThenExit" | "detached";
flavor?: DebuggerShellFlavor;
prebuiltBinaryPath?: string;
}>,
): Promise<void>;
export type DebuggerShellPreparationResult = Readonly<{
code:
| "success"
| "not_implemented"
| "likely_offline"
| "platform_not_supported"
| "possible_corruption"
| "unexpected_error";
humanReadableMessage?: string;
verboseInfo?: string;
}>;
/**
* Attempts to prepare the debugger shell for use and returns a coded result
* that can be used to advise the user on how to proceed in case of failure.
* In particular, this function will attempt to download and extract an
* appropriate binary for the "prebuilt" flavor.
*
* This function should be called early during dev server startup, in parallel
* with other initialization steps, so that the debugger shell is ready to use
* instantly when the user tries to open it (and conversely, the user is
* informed ASAP if it is not ready to use).
*/
declare function unstable_prepareDebuggerShell(
flavor: DebuggerShellFlavor,
$$PARAM_1$$?: { prebuiltBinaryPath?: string },
): Promise<DebuggerShellPreparationResult>;
export { unstable_spawnDebuggerShellWithArgs, unstable_prepareDebuggerShell };

View File

@@ -0,0 +1,120 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.unstable_prepareDebuggerShell = unstable_prepareDebuggerShell;
exports.unstable_spawnDebuggerShellWithArgs =
unstable_spawnDebuggerShellWithArgs;
var _LaunchUtils = require("./private/LaunchUtils");
const { spawn } = require("cross-spawn");
const path = require("path");
const DEVTOOLS_BINARY_DOTSLASH_FILE = path.join(
__dirname,
"../../bin/react-native-devtools",
);
async function unstable_spawnDebuggerShellWithArgs(
args,
{ mode = "detached", flavor = "prebuilt", prebuiltBinaryPath } = {},
) {
const [binaryPath, baseArgs] = getShellBinaryAndArgs(
flavor,
prebuiltBinaryPath,
);
return new Promise((resolve, reject) => {
const child = spawn(binaryPath, [...baseArgs, ...args], {
stdio: "inherit",
windowsHide: true,
detached: mode === "detached",
});
if (mode === "detached") {
child.on("spawn", () => {
resolve();
});
child.on("close", (code) => {
if (code !== 0) {
reject(
new Error(
`Failed to open debugger shell: exited with code ${code}`,
),
);
}
});
child.unref();
} else if (mode === "syncThenExit") {
child.on("close", function (code, signal) {
if (code === null) {
console.error("Debugger shell exited with signal", signal);
process.exit(1);
}
process.exit(code);
});
const handleTerminationSignal = function (signal) {
process.on(signal, function signalHandler() {
if (!child.killed) {
child.kill(signal);
}
});
};
handleTerminationSignal("SIGINT");
handleTerminationSignal("SIGTERM");
}
});
}
async function unstable_prepareDebuggerShell(
flavor,
{ prebuiltBinaryPath } = {},
) {
try {
switch (flavor) {
case "prebuilt":
const prebuiltResult = await (0,
_LaunchUtils.prepareDebuggerShellFromDotSlashFile)(
prebuiltBinaryPath ?? DEVTOOLS_BINARY_DOTSLASH_FILE,
);
if (prebuiltResult.code !== "success") {
return prebuiltResult;
}
break;
case "dev":
break;
default:
throw new Error(`Unknown flavor: ${flavor}`);
}
const [binaryPath, baseArgs] = getShellBinaryAndArgs(
flavor,
prebuiltBinaryPath,
);
const { code, stderr } = await (0, _LaunchUtils.spawnAndGetStderr)(
binaryPath,
[...baseArgs, "--version"],
);
if (code !== 0) {
return {
code: "unexpected_error",
verboseInfo: stderr,
};
}
return {
code: "success",
};
} catch (e) {
return {
code: "unexpected_error",
verboseInfo: e.message,
};
}
}
function getShellBinaryAndArgs(flavor, prebuiltBinaryPath) {
switch (flavor) {
case "prebuilt":
return [
require("fb-dotslash"),
[prebuiltBinaryPath ?? DEVTOOLS_BINARY_DOTSLASH_FILE],
];
case "dev":
return [require("electron"), [require.resolve("../electron")]];
default:
throw new Error(`Unknown flavor: ${flavor}`);
}
}

View File

@@ -0,0 +1,56 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
// The 'prebuilt' flavor will use the prebuilt shell binary (and the JavaScript embedded in it).
// The 'dev' flavor will use a stock Electron binary and run the shell code from the `electron/` directory.
type DebuggerShellFlavor = "prebuilt" | "dev";
declare function unstable_spawnDebuggerShellWithArgs(
args: string[],
$$PARAM_1$$?: $ReadOnly<{
// In 'syncAndExit' mode, the current process will block until the spawned process exits, and then it will exit
// with the same exit code as the spawned process.
// In 'detached' mode, the spawned process will be detached from the current process and the current process will
// continue to run normally.
mode?: "syncThenExit" | "detached",
flavor?: DebuggerShellFlavor,
prebuiltBinaryPath?: string,
}>,
): Promise<void>;
export type DebuggerShellPreparationResult = $ReadOnly<{
code:
| "success"
| "not_implemented"
| "likely_offline"
| "platform_not_supported"
| "possible_corruption"
| "unexpected_error",
humanReadableMessage?: string,
verboseInfo?: string,
}>;
/**
* Attempts to prepare the debugger shell for use and returns a coded result
* that can be used to advise the user on how to proceed in case of failure.
* In particular, this function will attempt to download and extract an
* appropriate binary for the "prebuilt" flavor.
*
* This function should be called early during dev server startup, in parallel
* with other initialization steps, so that the debugger shell is ready to use
* instantly when the user tries to open it (and conversely, the user is
* informed ASAP if it is not ready to use).
*/
declare function unstable_prepareDebuggerShell(
flavor: DebuggerShellFlavor,
$$PARAM_1$$?: { prebuiltBinaryPath?: string },
): Promise<DebuggerShellPreparationResult>;
export { unstable_spawnDebuggerShellWithArgs, unstable_prepareDebuggerShell };

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*
* @format
*/
import type { DebuggerShellPreparationResult } from "../";
declare function spawnAndGetStderr(
command: string,
args: string[],
): Promise<{ code: number; stderr: string }>;
declare function prepareDebuggerShellFromDotSlashFile(
filePath: string,
): Promise<DebuggerShellPreparationResult>;
export { spawnAndGetStderr, prepareDebuggerShellFromDotSlashFile };

View File

@@ -0,0 +1,84 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.prepareDebuggerShellFromDotSlashFile =
prepareDebuggerShellFromDotSlashFile;
exports.spawnAndGetStderr = spawnAndGetStderr;
const { spawn } = require("cross-spawn");
async function spawnAndGetStderr(command, args) {
return new Promise((resolve, reject) => {
const child = spawn(command, args, {
stdio: ["ignore", "ignore", "pipe"],
encoding: "utf8",
windowsHide: true,
});
let stderr = "";
child.stderr.on("data", (data) => {
stderr += data;
});
child.on("error", (error) => {
reject(error);
});
child.on("close", (code, signal) => {
resolve({
code,
stderr,
});
});
});
}
async function prepareDebuggerShellFromDotSlashFile(filePath) {
const { code, stderr } = await spawnAndGetStderr(require("fb-dotslash"), [
"--",
"fetch",
filePath,
]);
if (code === 0) {
return {
code: "success",
};
}
if (
stderr.includes("dotslash error") &&
stderr.includes("no providers succeeded")
) {
if (stderr.includes("failed to verify artifact")) {
return {
code: "possible_corruption",
humanReadableMessage:
"Failed to verify the latest version of React Native DevTools. " +
"Using a fallback version instead. ",
verboseInfo: stderr,
};
}
return {
code: "likely_offline",
humanReadableMessage:
"Failed to download the latest version of React Native DevTools. " +
"Using a fallback version instead. " +
"Connect to the internet or check your network settings.",
verboseInfo: stderr,
};
}
if (
stderr.includes("dotslash error") &&
stderr.includes("platform not supported")
) {
return {
code: "platform_not_supported",
humanReadableMessage:
"The latest version of React Native DevTools is not supported on this platform. " +
"Using a fallback version instead.",
verboseInfo: stderr,
};
}
return {
code: "unexpected_error",
humanReadableMessage:
"An unexpected error occured while installing the latest version of React Native DevTools. " +
"Using a fallback version instead.",
verboseInfo: stderr,
};
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type { DebuggerShellPreparationResult } from "../";
declare function spawnAndGetStderr(
command: string,
args: string[],
): Promise<{
code: number,
stderr: string,
}>;
declare function prepareDebuggerShellFromDotSlashFile(
filePath: string,
): Promise<DebuggerShellPreparationResult>;
export { spawnAndGetStderr, prepareDebuggerShellFromDotSlashFile };