"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
function _export(target, all) {
    for(var name in all)Object.defineProperty(target, name, {
        enumerable: true,
        get: all[name]
    });
}
_export(exports, {
    updatePackageJSONAsync: ()=>updatePackageJSONAsync,
    updatePkgDependencies: ()=>updatePkgDependencies,
    createDependenciesMap: ()=>createDependenciesMap,
    updatePkgScripts: ()=>updatePkgScripts,
    hashForDependencyMap: ()=>hashForDependencyMap,
    createFileHash: ()=>createFileHash
});
function _config() {
    const data = require("@expo/config");
    _config = function() {
        return data;
    };
    return data;
}
function _chalk() {
    const data = /*#__PURE__*/ _interopRequireDefault(require("chalk"));
    _chalk = function() {
        return data;
    };
    return data;
}
function _crypto() {
    const data = /*#__PURE__*/ _interopRequireDefault(require("crypto"));
    _crypto = function() {
        return data;
    };
    return data;
}
function _fs() {
    const data = /*#__PURE__*/ _interopRequireDefault(require("fs"));
    _fs = function() {
        return data;
    };
    return data;
}
function _path() {
    const data = /*#__PURE__*/ _interopRequireDefault(require("path"));
    _path = function() {
        return data;
    };
    return data;
}
function _semver() {
    const data = require("semver");
    _semver = function() {
        return data;
    };
    return data;
}
const _log = /*#__PURE__*/ _interopRequireWildcard(require("../log"));
const _isModuleSymlinked = require("../utils/isModuleSymlinked");
const _ora = require("../utils/ora");
function _interopRequireDefault(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
function _getRequireWildcardCache(nodeInterop) {
    if (typeof WeakMap !== "function") return null;
    var cacheBabelInterop = new WeakMap();
    var cacheNodeInterop = new WeakMap();
    return (_getRequireWildcardCache = function(nodeInterop) {
        return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
    })(nodeInterop);
}
function _interopRequireWildcard(obj, nodeInterop) {
    if (!nodeInterop && obj && obj.__esModule) {
        return obj;
    }
    if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
        return {
            default: obj
        };
    }
    var cache = _getRequireWildcardCache(nodeInterop);
    if (cache && cache.has(obj)) {
        return cache.get(obj);
    }
    var newObj = {};
    var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
    for(var key in obj){
        if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
            var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
            if (desc && (desc.get || desc.set)) {
                Object.defineProperty(newObj, key, desc);
            } else {
                newObj[key] = obj[key];
            }
        }
    }
    newObj.default = obj;
    if (cache) {
        cache.set(obj, newObj);
    }
    return newObj;
}
async function updatePackageJSONAsync(projectRoot, { templateDirectory , templatePkg =(0, _config().getPackageJson)(templateDirectory) , pkg , skipDependencyUpdate  }) {
    const updatingPackageJsonStep = (0, _ora.logNewSection)("Updating package.json");
    const results = modifyPackageJson(projectRoot, {
        templatePkg,
        pkg,
        skipDependencyUpdate
    });
    const hasChanges = results.changedDependencies.length || results.scriptsChanged;
    // NOTE: This is effectively bundler caching and subject to breakage if the inputs don't match the mutations.
    if (hasChanges) {
        await _fs().default.promises.writeFile(_path().default.resolve(projectRoot, "package.json"), // Add new line to match the format of running yarn.
        // This prevents the `package.json` from changing when running `prebuild --no-install` multiple times.
        JSON.stringify(pkg, null, 2) + "\n");
    }
    updatingPackageJsonStep.succeed("Updated package.json" + (hasChanges ? "" : _chalk().default.dim(` | no changes`)));
    return results;
}
/**
 * Make required modifications to the `package.json` file as a JSON object.
 *
 * 1. Update `package.json` `scripts`.
 * 2. Update `package.json` `dependencies` (not `devDependencies`).
 * 3. Update `package.json` `main`.
 *
 * @param projectRoot The root directory of the project.
 * @param templatePkg Template project package.json as JSON.
 * @param pkg Current package.json as JSON.
 * @param skipDependencyUpdate Array of dependencies to skip updating.
 * @returns
 */ function modifyPackageJson(projectRoot, { templatePkg , pkg , skipDependencyUpdate  }) {
    const scriptsChanged = updatePkgScripts({
        pkg
    });
    // TODO: Move to `npx expo-doctor`
    return {
        scriptsChanged,
        ...updatePkgDependencies(projectRoot, {
            pkg,
            templatePkg,
            skipDependencyUpdate
        })
    };
}
function updatePkgDependencies(projectRoot, { pkg , templatePkg , skipDependencyUpdate =[]  }) {
    const { dependencies  } = templatePkg;
    // The default values come from the bare-minimum template's package.json.
    // Users can change this by using different templates with the `--template` flag.
    // The main reason for allowing the changing of dependencies would be to include
    // dependencies that are required for the native project to build. For example,
    // it does not need to include dependencies that are used in the JS-code only.
    const defaultDependencies = createDependenciesMap(dependencies);
    // NOTE: This is a hack to ensure this doesn't trigger an extraneous change in the `package.json`
    // it isn't required for anything in the `ios` and `android` folders.
    delete defaultDependencies["expo-status-bar"];
    // NOTE: Expo splash screen is installed by default in the template but the config plugin also lives in prebuild-config
    // so we can delete it to prevent an extraneous change in the `package.json`.
    delete defaultDependencies["expo-splash-screen"];
    const combinedDependencies = createDependenciesMap({
        ...defaultDependencies,
        ...pkg.dependencies
    });
    // These dependencies are only added, not overwritten from the project
    const requiredDependencies = [
        // TODO: This is no longer required because it's this same package.
        "expo",
        // TODO: Drop this somehow.
        "react-native", 
    ].filter((depKey)=>!!defaultDependencies[depKey]);
    const symlinkedPackages = [];
    const nonRecommendedPackages = [];
    for (const dependenciesKey of requiredDependencies){
        var ref;
        // If the local package.json defined the dependency that we want to overwrite...
        if ((ref = pkg.dependencies) == null ? void 0 : ref[dependenciesKey]) {
            // Then ensure it isn't symlinked (i.e. the user has a custom version in their yarn workspace).
            if ((0, _isModuleSymlinked.isModuleSymlinked)(projectRoot, {
                moduleId: dependenciesKey,
                isSilent: true
            })) {
                // If the package is in the project's package.json and it's symlinked, then skip overwriting it.
                symlinkedPackages.push(dependenciesKey);
                continue;
            }
            // Do not modify manually skipped dependencies
            if (skipDependencyUpdate.includes(dependenciesKey)) {
                continue;
            }
            // Warn users for outdated dependencies when prebuilding
            const hasRecommendedVersion = versionRangesIntersect(pkg.dependencies[dependenciesKey], String(defaultDependencies[dependenciesKey]));
            if (!hasRecommendedVersion) {
                nonRecommendedPackages.push(`${dependenciesKey}@${defaultDependencies[dependenciesKey]}`);
            }
        }
    }
    if (symlinkedPackages.length) {
        _log.log(`\u203A Using symlinked ${symlinkedPackages.map((pkg)=>_chalk().default.bold(pkg)).join(", ")} instead of recommended version(s).`);
    }
    if (nonRecommendedPackages.length) {
        _log.warn(`\u203A Using current versions instead of recommended ${nonRecommendedPackages.map((pkg)=>_chalk().default.bold(pkg)).join(", ")}.`);
    }
    // Only change the dependencies if the normalized hash changes, this helps to reduce meaningless changes.
    const hasNewDependencies = hashForDependencyMap(pkg.dependencies) !== hashForDependencyMap(combinedDependencies);
    // Save the dependencies
    let changedDependencies = [];
    if (hasNewDependencies) {
        changedDependencies = diffKeys(combinedDependencies, pkg.dependencies ?? {}).sort();
        // Use Object.assign to preserve the original order of dependencies, this makes it easier to see what changed in the git diff.
        pkg.dependencies = Object.assign(pkg.dependencies ?? {}, combinedDependencies);
    }
    return {
        changedDependencies
    };
}
function diffKeys(a, b) {
    return Object.keys(a).filter((key)=>a[key] !== b[key]);
}
function createDependenciesMap(dependencies) {
    if (typeof dependencies !== "object") {
        throw new Error(`Dependency map is invalid, expected object but got ${typeof dependencies}`);
    } else if (!dependencies) {
        return {};
    }
    const outputMap = {};
    for (const key of Object.keys(dependencies)){
        const value = dependencies[key];
        if (typeof value === "string") {
            outputMap[key] = value;
        } else {
            throw new Error(`Dependency for key \`${key}\` should be a \`string\`, instead got: \`{ ${key}: ${JSON.stringify(value)} }\``);
        }
    }
    return outputMap;
}
function updatePkgScripts({ pkg  }) {
    let hasChanged = false;
    if (!pkg.scripts) {
        pkg.scripts = {};
    }
    if (!pkg.scripts.android || pkg.scripts.android === "expo start --android" || pkg.scripts.android === "react-native run-android") {
        pkg.scripts.android = "expo run:android";
        hasChanged = true;
    }
    if (!pkg.scripts.ios || pkg.scripts.ios === "expo start --ios" || pkg.scripts.ios === "react-native run-ios") {
        pkg.scripts.ios = "expo run:ios";
        hasChanged = true;
    }
    return hasChanged;
}
function normalizeDependencyMap(deps) {
    return Object.keys(deps).map((dependency)=>`${dependency}@${deps[dependency]}`).sort();
}
function hashForDependencyMap(deps = {}) {
    const depsList = normalizeDependencyMap(deps);
    const depsString = depsList.join("\n");
    return createFileHash(depsString);
}
function createFileHash(contents) {
    // this doesn't need to be secure, the shorter the better.
    return _crypto().default.createHash("sha1").update(contents).digest("hex");
}
/**
 * Determine if two semver ranges are overlapping or intersecting.
 * This is a safe version of `semver.intersects` that does not throw.
 */ function versionRangesIntersect(rangeA, rangeB) {
    try {
        return (0, _semver().intersects)(rangeA, rangeB);
    } catch  {
        return false;
    }
}

//# sourceMappingURL=updatePackageJson.js.map