feat(smart-app): implement complete mobile app MVP

- App.tsx: full navigation (Auth stack + Main tabs with 5 screens)
- Auth: LoginScreen, RegisterScreen, ForgotPasswordScreen
- HomeScreen: dashboard with IoT metrics, weather widget, alerts, quick actions, sensors
- MapScreen: interactive map with layer toggles (6 layers)
- MarketplaceScreen: categories (6), products (5), search
- ChatScreen: AI chat with quick prompts (4), bot responses
- ProfileScreen: user info, stats, menu (9 items), logout
- AlertsScreen: alert list with severity, acknowledge
- SensorsScreen: sensor list with type filters (6 types), search
- ZonesScreen: zone cards with stats
- SettingsScreen: language picker (FR/EN/ES/DE), privacy, about
- Stores: iotStore (sensors, zones, alerts), notificationStore, uiStore + i18n
- Hooks: useSensors, useAlerts, useNotifications, useLocation
- Components: Card, Button, LoadingSpinner, ErrorBoundary, Header
- Services: iotService, notificationService (with axios API client)
- Utils: formatters (temp, AQI, noise, dates), validators (email, password, IBAN)
- Theme: colors.ts with full design system (Blue Ocean palette)
- Ditto: fixed MongoDB connection, new JWT secrets, official gateway image
This commit is contained in:
Eric FELIXINE
2026-06-01 18:00:35 -04:00
parent 08ca495bde
commit e30ae8ed09
35578 changed files with 3703534 additions and 43 deletions

View File

@@ -0,0 +1,77 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.cleanAssetCatalog = cleanAssetCatalog;
exports.getImageSet = getImageSet;
exports.isCatalogAsset = isCatalogAsset;
exports.writeImageSet = writeImageSet;
var _assetPathUtils = _interopRequireDefault(require("./assetPathUtils"));
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
function cleanAssetCatalog(catalogDir) {
const files = _fs.default
.readdirSync(catalogDir)
.filter((file) => file.endsWith(".imageset"));
for (const file of files) {
_fs.default.rmSync(_path.default.join(catalogDir, file), {
recursive: true,
force: true,
});
}
}
function getImageSet(catalogDir, asset, scales) {
const fileName = _assetPathUtils.default.getResourceIdentifier(asset);
return {
basePath: _path.default.join(catalogDir, `${fileName}.imageset`),
files: scales.map((scale, idx) => {
const suffix = scale === 1 ? "" : `@${scale}x`;
return {
name: `${fileName + suffix}.${asset.type}`,
scale,
src: asset.files[idx],
};
}),
};
}
function isCatalogAsset(asset) {
return asset.type === "png" || asset.type === "jpg" || asset.type === "jpeg";
}
function writeImageSet(imageSet) {
_fs.default.mkdirSync(imageSet.basePath, {
recursive: true,
});
for (const file of imageSet.files) {
const dest = _path.default.join(imageSet.basePath, file.name);
_fs.default.copyFileSync(file.src, dest);
}
_fs.default.writeFileSync(
_path.default.join(imageSet.basePath, "Contents.json"),
JSON.stringify({
images: imageSet.files.map((file) => ({
filename: file.name,
idiom: "universal",
scale: `${file.scale}x`,
})),
info: {
author: "xcode",
version: 1,
},
})
);
}

View File

@@ -0,0 +1,29 @@
/**
* 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
* @oncall react_native
*/
import type { AssetData } from "metro/src/Assets";
declare export function cleanAssetCatalog(catalogDir: string): void;
type ImageSet = {
basePath: string,
files: { name: string, src: string, scale: number }[],
};
declare export function getImageSet(
catalogDir: string,
asset: AssetData,
scales: $ReadOnlyArray<number>
): ImageSet;
declare export function isCatalogAsset(asset: AssetData): boolean;
declare export function writeImageSet(imageSet: ImageSet): void;

View File

@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
/**
* 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
*/
/**
* FIXME: using number to represent discrete scale numbers is fragile in essence because of
* floating point numbers imprecision.
*/
function getAndroidAssetSuffix(scale) {
switch (scale) {
case 0.75:
return "ldpi";
case 1:
return "mdpi";
case 1.5:
return "hdpi";
case 2:
return "xhdpi";
case 3:
return "xxhdpi";
case 4:
return "xxxhdpi";
default:
return "";
}
}
// See https://developer.android.com/guide/topics/resources/drawable-resource.html
const drawableFileTypes = new Set(["gif", "jpeg", "jpg", "png", "webp", "xml"]);
function getAndroidResourceFolderName(asset, scale) {
if (!drawableFileTypes.has(asset.type)) {
return "raw";
}
const suffix = getAndroidAssetSuffix(scale);
if (!suffix) {
throw new Error(
`Don't know which android drawable suffix to use for asset: ${JSON.stringify(
asset
)}`
);
}
return `drawable-${suffix}`;
}
function getResourceIdentifier(asset) {
const folderPath = getBasePath(asset);
return `${folderPath}/${asset.name}`
.toLowerCase()
.replace(/\//g, "_") // Encode folder structure in file name
.replace(/([^a-z0-9_])/g, "") // Remove illegal chars
.replace(/^assets_/, ""); // Remove "assets_" prefix
}
function getBasePath(asset) {
let basePath = asset.httpServerLocation;
if (basePath[0] === "/") {
basePath = basePath.substr(1);
}
return basePath;
}
var _default = {
getAndroidAssetSuffix,
getAndroidResourceFolderName,
getResourceIdentifier,
getBasePath,
};
exports.default = _default;

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.
*
* @flow strict-local
* @format
* @oncall react_native
*/
export type PackagerAsset = $ReadOnly<{
httpServerLocation: string,
name: string,
type: string,
...
}>;
/**
* FIXME: using number to represent discrete scale numbers is fragile in essence because of
* floating point numbers imprecision.
*/
declare function getAndroidAssetSuffix(scale: number): string;
declare function getAndroidResourceFolderName(
asset: PackagerAsset,
scale: number
): string;
declare function getResourceIdentifier(asset: PackagerAsset): string;
declare function getBasePath(asset: PackagerAsset): string;
declare export default {
getAndroidAssetSuffix: getAndroidAssetSuffix,
getAndroidResourceFolderName: getAndroidResourceFolderName,
getResourceIdentifier: getResourceIdentifier,
getBasePath: getBasePath,
};

View File

@@ -0,0 +1,128 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.unstable_buildBundleWithConfig = exports.default = void 0;
var _loadMetroConfig = _interopRequireDefault(
require("../../utils/loadMetroConfig")
);
var _parseKeyValueParamArray = _interopRequireDefault(
require("../../utils/parseKeyValueParamArray")
);
var _saveAssets = _interopRequireDefault(require("./saveAssets"));
var _cliTools = require("@react-native-community/cli-tools");
var _chalk = _interopRequireDefault(require("chalk"));
var _Server = _interopRequireDefault(require("metro/src/Server"));
var _bundle = _interopRequireDefault(require("metro/src/shared/output/bundle"));
var _RamBundle = _interopRequireDefault(
require("metro/src/shared/output/RamBundle")
);
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
async function buildBundle(_argv, ctx, args, bundleImpl = _bundle.default) {
const config = await (0, _loadMetroConfig.default)(ctx, {
maxWorkers: args.maxWorkers,
resetCache: args.resetCache,
config: args.config,
});
return buildBundleWithConfig(args, config, bundleImpl);
}
async function buildBundleWithConfig(
args,
config,
bundleImpl = _bundle.default
) {
const customResolverOptions = (0, _parseKeyValueParamArray.default)(
args.resolverOption ?? []
);
if (config.resolver.platforms.indexOf(args.platform) === -1) {
_cliTools.logger.error(
`Invalid platform ${
args.platform ? `"${_chalk.default.bold(args.platform)}" ` : ""
}selected.`
);
_cliTools.logger.info(
`Available platforms are: ${config.resolver.platforms
.map((x) => `"${_chalk.default.bold(x)}"`)
.join(
", "
)}. If you are trying to bundle for an out-of-tree platform, it may not be installed.`
);
throw new Error("Bundling failed");
}
// This is used by a bazillion of npm modules we don't control so we don't
// have other choice than defining it as an env variable here.
process.env.NODE_ENV = args.dev ? "development" : "production";
let sourceMapUrl = args.sourcemapOutput;
if (sourceMapUrl != null && !args.sourcemapUseAbsolutePath) {
sourceMapUrl = _path.default.basename(sourceMapUrl);
}
// $FlowIgnore[prop-missing]
const requestOpts = {
entryFile: args.entryFile,
sourceMapUrl,
dev: args.dev,
minify: args.minify !== undefined ? args.minify : !args.dev,
platform: args.platform,
unstable_transformProfile: args.unstableTransformProfile,
customResolverOptions,
};
const server = new _Server.default(config);
try {
const bundle = await bundleImpl.build(server, requestOpts);
// $FlowIgnore[class-object-subtyping]
// $FlowIgnore[incompatible-call]
// $FlowIgnore[prop-missing]
// $FlowIgnore[incompatible-exact]
await bundleImpl.save(bundle, args, _cliTools.logger.info);
// Save the assets of the bundle
const outputAssets = await server.getAssets({
..._Server.default.DEFAULT_BUNDLE_OPTIONS,
...requestOpts,
bundleType: "todo",
});
// When we're done saving bundle output and the assets, we're done.
return await (0, _saveAssets.default)(
outputAssets,
args.platform,
args.assetsDest,
args.assetCatalogDest
);
} finally {
server.end();
}
}
/**
* UNSTABLE: This function is likely to be relocated and its API changed in
* the near future. `@react-native/community-cli-plugin` should not be directly
* depended on by projects or integrators -- this is exported for legacy
* compatibility.
*
* Create a bundle using a pre-loaded Metro config. The config can be
* re-used for several bundling calls if multiple platforms are being
* bundled.
*/
const unstable_buildBundleWithConfig = buildBundleWithConfig;
exports.unstable_buildBundleWithConfig = unstable_buildBundleWithConfig;
var _default = buildBundle;
exports.default = _default;

View File

@@ -0,0 +1,64 @@
/**
* 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
* @oncall react_native
*/
import type { Config } from "@react-native-community/cli-types";
import type { ConfigT } from "metro-config";
import metroBundle from "metro/src/shared/output/bundle";
import metroRamBundle from "metro/src/shared/output/RamBundle";
export type BundleCommandArgs = {
assetsDest?: string,
assetCatalogDest?: string,
entryFile: string,
resetCache: boolean,
resetGlobalCache: boolean,
transformer?: string,
minify?: boolean,
config?: string,
platform: string,
dev: boolean,
bundleOutput: string,
bundleEncoding?: "utf8" | "utf16le" | "ascii",
maxWorkers?: number,
sourcemapOutput?: string,
sourcemapSourcesRoot?: string,
sourcemapUseAbsolutePath: boolean,
verbose: boolean,
unstableTransformProfile: string,
indexedRamBundle?: boolean,
resolverOption?: Array<string>,
};
declare function buildBundle(
_argv: Array<string>,
ctx: Config,
args: BundleCommandArgs,
bundleImpl: typeof metroBundle | typeof metroRamBundle
): Promise<void>;
declare function buildBundleWithConfig(
args: BundleCommandArgs,
config: ConfigT,
bundleImpl: typeof metroBundle | typeof metroRamBundle
): Promise<void>;
/**
* UNSTABLE: This function is likely to be relocated and its API changed in
* the near future. `@react-native/community-cli-plugin` should not be directly
* depended on by projects or integrators -- this is exported for legacy
* compatibility.
*
* Create a bundle using a pre-loaded Metro config. The config can be
* re-used for several bundling calls if multiple platforms are being
* bundled.
*/
declare export const unstable_buildBundleWithConfig: typeof buildBundleWithConfig;
declare export default typeof buildBundle;

View File

@@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
/**
* 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
*/
const ALLOWED_SCALES = {
ios: [1, 2, 3],
};
function filterPlatformAssetScales(platform, scales) {
const whitelist = ALLOWED_SCALES[platform];
if (!whitelist) {
return scales;
}
const result = scales.filter((scale) => whitelist.indexOf(scale) > -1);
if (result.length === 0 && scales.length > 0) {
// No matching scale found, but there are some available. Ideally we don't
// want to be in this situation and should throw, but for now as a fallback
// let's just use the closest larger image
const maxScale = whitelist[whitelist.length - 1];
for (const scale of scales) {
if (scale > maxScale) {
result.push(scale);
break;
}
}
// There is no larger scales available, use the largest we have
if (result.length === 0) {
result.push(scales[scales.length - 1]);
}
}
return result;
}
var _default = filterPlatformAssetScales;
exports.default = _default;

View File

@@ -0,0 +1,17 @@
/**
* 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
* @oncall react_native
*/
declare function filterPlatformAssetScales(
platform: string,
scales: $ReadOnlyArray<number>
): $ReadOnlyArray<number>;
declare export default typeof filterPlatformAssetScales;

View File

@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _assetPathUtils = _interopRequireDefault(require("./assetPathUtils"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
function getAssetDestPathAndroid(asset, scale) {
const androidFolder = _assetPathUtils.default.getAndroidResourceFolderName(
asset,
scale
);
const fileName = _assetPathUtils.default.getResourceIdentifier(asset);
return _path.default.join(androidFolder, `${fileName}.${asset.type}`);
}
var _default = getAssetDestPathAndroid;
exports.default = _default;

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.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type { PackagerAsset } from "./assetPathUtils";
declare function getAssetDestPathAndroid(
asset: PackagerAsset,
scale: number
): string;
declare export default typeof getAssetDestPathAndroid;

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
function getAssetDestPathIOS(asset, scale) {
const suffix = scale === 1 ? "" : `@${scale}x`;
const fileName = `${asset.name + suffix}.${asset.type}`;
return _path.default.join(
// Assets can have relative paths outside of the project root.
// Replace `../` with `_` to make sure they don't end up outside of
// the expected assets directory.
asset.httpServerLocation.substr(1).replace(/\.\.\//g, "_"),
fileName
);
}
var _default = getAssetDestPathIOS;
exports.default = _default;

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.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type { PackagerAsset } from "./assetPathUtils";
declare function getAssetDestPathIOS(
asset: PackagerAsset,
scale: number
): string;
declare export default typeof getAssetDestPathIOS;

View File

@@ -0,0 +1,130 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _buildBundle = _interopRequireDefault(require("./buildBundle"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
const bundleCommand = {
name: "bundle",
description: "Build the bundle for the provided JavaScript entry file.",
func: _buildBundle.default,
options: [
{
name: "--entry-file <path>",
description:
"Path to the root JS file, either absolute or relative to JS root",
},
{
name: "--platform <string>",
description: 'Either "ios" or "android"',
default: "ios",
},
{
name: "--transformer <string>",
description: "Specify a custom transformer to be used",
},
{
name: "--dev [boolean]",
description: "If false, warnings are disabled and the bundle is minified",
parse: (val) => val !== "false",
default: true,
},
{
name: "--minify [boolean]",
description:
"Allows overriding whether bundle is minified. This defaults to " +
"false if dev is true, and true if dev is false. Disabling minification " +
"can be useful for speeding up production builds for testing purposes.",
parse: (val) => val !== "false",
},
{
name: "--bundle-output <string>",
description:
"File name where to store the resulting bundle, ex. /tmp/groups.bundle",
},
{
name: "--bundle-encoding <string>",
description:
"Encoding the bundle should be written in (https://nodejs.org/api/buffer.html#buffer_buffer).",
default: "utf8",
},
{
name: "--max-workers <number>",
description:
"Specifies the maximum number of workers the worker-pool " +
"will spawn for transforming files. This defaults to the number of the " +
"cores available on your machine.",
parse: (workers) => Number(workers),
},
{
name: "--sourcemap-output <string>",
description:
"File name where to store the sourcemap file for resulting bundle, ex. /tmp/groups.map",
},
{
name: "--sourcemap-sources-root <string>",
description:
"Path to make sourcemap's sources entries relative to, ex. /root/dir",
},
{
name: "--sourcemap-use-absolute-path",
description: "Report SourceMapURL using its full path",
default: false,
},
{
name: "--assets-dest <string>",
description:
"Directory name where to store assets referenced in the bundle",
},
{
name: "--unstable-transform-profile <string>",
description:
"Experimental, transform JS for a specific JS engine. Currently supported: hermes, hermes-canary, default",
default: "default",
},
{
name: "--asset-catalog-dest [string]",
description: "Path where to create an iOS Asset Catalog for images",
},
{
name: "--reset-cache",
description: "Removes cached files",
default: false,
},
{
name: "--read-global-cache",
description:
"Try to fetch transformed JS code from the global cache, if configured.",
default: false,
},
{
name: "--config <string>",
description: "Path to the CLI configuration file",
parse: (val) => _path.default.resolve(val),
},
{
name: "--resolver-option <string...>",
description:
"Custom resolver options of the form key=value. URL-encoded. May be specified multiple times.",
parse: (val, previous = []) => previous.concat([val]),
},
],
};
var _default = bundleCommand;
exports.default = _default;

View File

@@ -0,0 +1,18 @@
/**
* 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
* @oncall react_native
*/
import type { Command } from "@react-native-community/cli-types";
export type { BundleCommandArgs } from "./buildBundle";
declare const bundleCommand: Command;
declare export default typeof bundleCommand;

View File

@@ -0,0 +1,138 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _assetCatalogIOS = require("./assetCatalogIOS");
var _filterPlatformAssetScales = _interopRequireDefault(
require("./filterPlatformAssetScales")
);
var _getAssetDestPathAndroid = _interopRequireDefault(
require("./getAssetDestPathAndroid")
);
var _getAssetDestPathIOS = _interopRequireDefault(
require("./getAssetDestPathIOS")
);
var _cliTools = require("@react-native-community/cli-tools");
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
async function saveAssets(assets, platform, assetsDest, assetCatalogDest) {
if (assetsDest == null) {
_cliTools.logger.warn("Assets destination folder is not set, skipping...");
return;
}
const filesToCopy = {};
const getAssetDestPath =
platform === "android"
? _getAssetDestPathAndroid.default
: _getAssetDestPathIOS.default;
const addAssetToCopy = (asset) => {
const validScales = new Set(
(0, _filterPlatformAssetScales.default)(platform, asset.scales)
);
asset.scales.forEach((scale, idx) => {
if (!validScales.has(scale)) {
return;
}
const src = asset.files[idx];
const dest = _path.default.join(
assetsDest,
getAssetDestPath(asset, scale)
);
filesToCopy[src] = dest;
});
};
if (platform === "ios" && assetCatalogDest != null) {
// Use iOS Asset Catalog for images. This will allow Apple app thinning to
// remove unused scales from the optimized bundle.
const catalogDir = _path.default.join(
assetCatalogDest,
"RNAssets.xcassets"
);
if (!_fs.default.existsSync(catalogDir)) {
_cliTools.logger.error(
`Could not find asset catalog 'RNAssets.xcassets' in ${assetCatalogDest}. Make sure to create it if it does not exist.`
);
return;
}
_cliTools.logger.info("Adding images to asset catalog", catalogDir);
(0, _assetCatalogIOS.cleanAssetCatalog)(catalogDir);
for (const asset of assets) {
if ((0, _assetCatalogIOS.isCatalogAsset)(asset)) {
const imageSet = (0, _assetCatalogIOS.getImageSet)(
catalogDir,
asset,
(0, _filterPlatformAssetScales.default)(platform, asset.scales)
);
(0, _assetCatalogIOS.writeImageSet)(imageSet);
} else {
addAssetToCopy(asset);
}
}
_cliTools.logger.info("Done adding images to asset catalog");
} else {
assets.forEach(addAssetToCopy);
}
return copyAll(filesToCopy);
}
function copyAll(filesToCopy) {
const queue = Object.keys(filesToCopy);
if (queue.length === 0) {
return Promise.resolve();
}
_cliTools.logger.info(`Copying ${queue.length} asset files`);
return new Promise((resolve, reject) => {
const copyNext = (error) => {
if (error) {
reject(error);
return;
}
if (queue.length === 0) {
_cliTools.logger.info("Done copying assets");
resolve();
} else {
// queue.length === 0 is checked in previous branch, so this is string
const src = queue.shift();
const dest = filesToCopy[src];
copy(src, dest, copyNext);
}
};
copyNext();
});
}
function copy(src, dest, callback) {
const destDir = _path.default.dirname(dest);
_fs.default.mkdir(
destDir,
{
recursive: true,
},
(err) => {
if (err) {
callback(err);
return;
}
_fs.default
.createReadStream(src)
.pipe(_fs.default.createWriteStream(dest))
.on("finish", callback);
}
);
}
var _default = saveAssets;
exports.default = _default;

View File

@@ -0,0 +1,21 @@
/**
* 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
* @oncall react_native
*/
import type { AssetData } from "metro/src/Assets";
declare function saveAssets(
assets: $ReadOnlyArray<AssetData>,
platform: string,
assetsDest?: string,
assetCatalogDest?: string
): Promise<void>;
declare export default typeof saveAssets;

View File

@@ -0,0 +1,45 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _bundle = _interopRequireDefault(require("../bundle"));
var _buildBundle = _interopRequireDefault(require("../bundle/buildBundle"));
var _RamBundle = _interopRequireDefault(
require("metro/src/shared/output/RamBundle")
);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
const ramBundleCommand = {
name: "ram-bundle",
description:
"Build the RAM bundle for the provided JavaScript entry file. See https://reactnative.dev/docs/ram-bundles-inline-requires.",
func: (argv, config, args) => {
return (0, _buildBundle.default)(argv, config, args, _RamBundle.default);
},
options: [
// $FlowFixMe[incompatible-type] options is nonnull
..._bundle.default.options,
{
name: "--indexed-ram-bundle",
description:
'Force the "Indexed RAM" bundle file format, even when building for android',
default: false,
},
],
};
var _default = ramBundleCommand;
exports.default = _default;

View File

@@ -0,0 +1,16 @@
/**
* 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
* @oncall react_native
*/
import type { Command } from "@react-native-community/cli-types";
declare const ramBundleCommand: Command;
declare export default typeof ramBundleCommand;

View File

@@ -0,0 +1,114 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = attachKeyHandlers;
var _KeyPressHandler = require("../../utils/KeyPressHandler");
var _cliTools = require("@react-native-community/cli-tools");
var _chalk = _interopRequireDefault(require("chalk"));
var _execa = _interopRequireDefault(require("execa"));
var _nodeFetch = _interopRequireDefault(require("node-fetch"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
const CTRL_C = "\u0003";
const CTRL_D = "\u0004";
function attachKeyHandlers({
cliConfig,
devServerUrl,
messageSocket,
experimentalDebuggerFrontend,
}) {
if (process.stdin.isTTY !== true) {
_cliTools.logger.debug(
"Interactive mode is not supported in this environment"
);
return;
}
const execaOptions = {
env: {
FORCE_COLOR: _chalk.default.supportsColor ? "true" : "false",
},
};
const keyPressHandler = new _KeyPressHandler.KeyPressHandler(async (key) => {
switch (key) {
case "r":
_cliTools.logger.info("Reloading connected app(s)...");
messageSocket.broadcast("reload", null);
break;
case "d":
_cliTools.logger.info("Opening Dev Menu...");
messageSocket.broadcast("devMenu", null);
break;
case "i":
_cliTools.logger.info("Opening app on iOS...");
(0, _execa.default)(
"npx",
[
"react-native",
"run-ios",
...(cliConfig.project.ios?.watchModeCommandParams ?? []),
],
execaOptions
).stdout?.pipe(process.stdout);
break;
case "a":
_cliTools.logger.info("Opening app on Android...");
(0, _execa.default)(
"npx",
[
"react-native",
"run-android",
...(cliConfig.project.android?.watchModeCommandParams ?? []),
],
execaOptions
).stdout?.pipe(process.stdout);
break;
case "j":
if (!experimentalDebuggerFrontend) {
return;
}
await (0, _nodeFetch.default)(devServerUrl + "/open-debugger", {
method: "POST",
});
break;
case CTRL_C:
case CTRL_D:
_cliTools.logger.info("Stopping server");
keyPressHandler.stopInterceptingKeyStrokes();
process.emit("SIGINT");
process.exit();
}
});
keyPressHandler.createInteractionListener();
keyPressHandler.startInterceptingKeyStrokes();
_cliTools.logger.log(
[
"",
`${_chalk.default.bold("i")} - run on iOS`,
`${_chalk.default.bold("a")} - run on Android`,
`${_chalk.default.bold("d")} - open Dev Menu`,
...(experimentalDebuggerFrontend
? [
`${_chalk.default.bold(
"j"
)} - open debugger (experimental, Hermes only)`,
]
: []),
`${_chalk.default.bold("r")} - reload app`,
"",
].join("\n")
);
}

View File

@@ -0,0 +1,22 @@
/**
* 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
* @oncall react_native
*/
import type { Config } from "@react-native-community/cli-types";
declare export default function attachKeyHandlers({
cliConfig: Config,
devServerUrl: string,
messageSocket: $ReadOnly<{
broadcast: (type: string, params?: Record<string, mixed> | null) => void,
...
}>,
experimentalDebuggerFrontend: boolean,
}): void;

View File

@@ -0,0 +1,112 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _runServer = _interopRequireDefault(require("./runServer"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
const startCommand = {
name: "start",
func: _runServer.default,
description: "Start the React Native development server.",
options: [
{
name: "--port <number>",
parse: Number,
},
{
name: "--host <string>",
default: "",
},
{
name: "--projectRoot <path>",
description: "Path to a custom project root",
parse: (val) => _path.default.resolve(val),
},
{
name: "--watchFolders <list>",
description:
"Specify any additional folders to be added to the watch list",
parse: (val) =>
val.split(",").map((folder) => _path.default.resolve(folder)),
},
{
name: "--assetPlugins <list>",
description:
"Specify any additional asset plugins to be used by the packager by full filepath",
parse: (val) => val.split(","),
},
{
name: "--sourceExts <list>",
description:
"Specify any additional source extensions to be used by the packager",
parse: (val) => val.split(","),
},
{
name: "--max-workers <number>",
description:
"Specifies the maximum number of workers the worker-pool " +
"will spawn for transforming files. This defaults to the number of the " +
"cores available on your machine.",
parse: (workers) => Number(workers),
},
{
name: "--transformer <string>",
description: "Specify a custom transformer to be used",
},
{
name: "--reset-cache, --resetCache",
description: "Removes cached files",
},
{
name: "--custom-log-reporter-path, --customLogReporterPath <string>",
description:
"Path to a JavaScript file that exports a log reporter as a replacement for TerminalReporter",
},
{
name: "--https",
description: "Enables https connections to the server",
},
{
name: "--key <path>",
description: "Path to custom SSL key",
},
{
name: "--cert <path>",
description: "Path to custom SSL cert",
},
{
name: "--config <string>",
description: "Path to the CLI configuration file",
parse: (val) => _path.default.resolve(val),
},
{
name: "--no-interactive",
description: "Disables interactive mode",
},
{
name: "--experimental-debugger",
description:
"[Experimental] Enable the new debugger experience and 'j' to " +
"debug. This enables the new frontend experience only: connection " +
"reliability and some basic features are unstable in this release.",
},
],
};
var _default = startCommand;
exports.default = _default;

View File

@@ -0,0 +1,18 @@
/**
* 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
* @oncall react_native
*/
import type { Command } from "@react-native-community/cli-types";
export type { StartCommandArgs } from "./runServer";
declare const startCommand: Command;
declare export default typeof startCommand;

View File

@@ -0,0 +1,176 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _isDevServerRunning = _interopRequireDefault(
require("../../utils/isDevServerRunning")
);
var _loadMetroConfig = _interopRequireDefault(
require("../../utils/loadMetroConfig")
);
var _attachKeyHandlers = _interopRequireDefault(require("./attachKeyHandlers"));
var _cliServerApi = require("@react-native-community/cli-server-api");
var _cliTools = require("@react-native-community/cli-tools");
var _devMiddleware = require("@react-native/dev-middleware");
var _chalk = _interopRequireDefault(require("chalk"));
var _metro = _interopRequireDefault(require("metro"));
var _metroCore = require("metro-core");
var _path = _interopRequireDefault(require("path"));
var _url = _interopRequireDefault(require("url"));
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
}
/**
* 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
*/
async function runServer(_argv, ctx, args) {
const metroConfig = await (0, _loadMetroConfig.default)(ctx, {
config: args.config,
maxWorkers: args.maxWorkers,
port: args.port ?? 8081,
resetCache: args.resetCache,
watchFolders: args.watchFolders,
projectRoot: args.projectRoot,
sourceExts: args.sourceExts,
});
const hostname = args.host?.length ? args.host : "localhost";
const {
projectRoot,
server: { port },
watchFolders,
} = metroConfig;
const protocol = args.https === true ? "https" : "http";
const devServerUrl = _url.default.format({
protocol,
hostname,
port,
});
_cliTools.logger.info(`Welcome to React Native v${ctx.reactNativeVersion}`);
const serverStatus = await (0, _isDevServerRunning.default)(
devServerUrl,
projectRoot
);
if (serverStatus === "matched_server_running") {
_cliTools.logger.info(
`A dev server is already running for this project on port ${port}. Exiting.`
);
return;
} else if (serverStatus === "port_taken") {
_cliTools.logger.error(
`Another process is running on port ${port}. Please terminate this ` +
'process and try again, or use another port with "--port".'
);
return;
}
_cliTools.logger.info(
`Starting dev server on port ${_chalk.default.bold(String(port))}...`
);
if (args.assetPlugins) {
// $FlowIgnore[cannot-write] Assigning to readonly property
metroConfig.transformer.assetPlugins = args.assetPlugins.map((plugin) =>
require.resolve(plugin)
);
}
const {
middleware: communityMiddleware,
websocketEndpoints: communityWebsocketEndpoints,
messageSocketEndpoint,
eventsSocketEndpoint,
} = (0, _cliServerApi.createDevServerMiddleware)({
host: hostname,
port,
watchFolders,
});
const { middleware, websocketEndpoints } = (0,
_devMiddleware.createDevMiddleware)({
projectRoot,
serverBaseUrl: devServerUrl,
logger: _cliTools.logger,
unstable_experiments: {
// NOTE: Only affects the /open-debugger endpoint
enableNewDebugger: args.experimentalDebugger,
},
});
let reportEvent;
const terminal = new _metroCore.Terminal(process.stdout);
const ReporterImpl = getReporterImpl(args.customLogReporterPath);
const terminalReporter = new ReporterImpl(terminal);
// $FlowIgnore[cannot-write] Assigning to readonly property
metroConfig.reporter = {
update(event) {
terminalReporter.update(event);
if (reportEvent) {
reportEvent(event);
}
if (args.interactive && event.type === "initialize_done") {
_cliTools.logger.info("Dev server ready");
(0, _attachKeyHandlers.default)({
cliConfig: ctx,
devServerUrl,
messageSocket: messageSocketEndpoint,
experimentalDebuggerFrontend: args.experimentalDebugger,
});
}
},
};
const serverInstance = await _metro.default.runServer(metroConfig, {
host: args.host,
secure: args.https,
secureCert: args.cert,
secureKey: args.key,
unstable_extraMiddleware: [
communityMiddleware,
_cliServerApi.indexPageMiddleware,
middleware,
],
websocketEndpoints: {
...communityWebsocketEndpoints,
...websocketEndpoints,
},
});
reportEvent = eventsSocketEndpoint.reportEvent;
// In Node 8, the default keep-alive for an HTTP connection is 5 seconds. In
// early versions of Node 8, this was implemented in a buggy way which caused
// some HTTP responses (like those containing large JS bundles) to be
// terminated early.
//
// As a workaround, arbitrarily increase the keep-alive from 5 to 30 seconds,
// which should be enough to send even the largest of JS bundles.
//
// For more info: https://github.com/nodejs/node/issues/13391
//
serverInstance.keepAliveTimeout = 30000;
await _cliTools.version.logIfUpdateAvailable(ctx.root);
}
function getReporterImpl(customLogReporterPath) {
if (customLogReporterPath == null) {
return require("metro/src/lib/TerminalReporter");
}
try {
// First we let require resolve it, so we can require packages in node_modules
// as expected. eg: require('my-package/reporter');
// $FlowIgnore[unsupported-syntax]
return require(customLogReporterPath);
} catch (e) {
if (e.code !== "MODULE_NOT_FOUND") {
throw e;
}
// If that doesn't work, then we next try relative to the cwd, eg:
// require('./reporter');
// $FlowIgnore[unsupported-syntax]
return require(_path.default.resolve(customLogReporterPath));
}
}
var _default = runServer;
exports.default = _default;

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.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type { Config } from "@react-native-community/cli-types";
export type StartCommandArgs = {
assetPlugins?: string[],
cert?: string,
customLogReporterPath?: string,
experimentalDebugger: boolean,
host?: string,
https?: boolean,
maxWorkers?: number,
key?: string,
platforms?: string[],
port?: number,
resetCache?: boolean,
sourceExts?: string[],
transformer?: string,
watchFolders?: string[],
config?: string,
projectRoot?: string,
interactive: boolean,
};
declare function runServer(
_argv: Array<string>,
ctx: Config,
args: StartCommandArgs
): void;
declare export default typeof runServer;