diff --git a/.gitattributes b/.gitattributes index 9e76b5f84..bad6bc9df 100644 --- a/.gitattributes +++ b/.gitattributes @@ -34,3 +34,7 @@ *.xml text *.sln text eol=crlf merge=union + +shrinkwrap.yaml merge=binary +npm-shrinkwrap.json merge=binary +yarn.lock merge=binary \ No newline at end of file diff --git a/.gitignore b/.gitignore index 57e7743df..1d841ed76 100644 --- a/.gitignore +++ b/.gitignore @@ -227,4 +227,69 @@ Samples/**/*.map src/*/dist src/*/nm /nm/ -*.tgz \ No newline at end of file +*.tgz + + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# next.js build output +.next + +# Common toolchain intermediate files +temp + +# Rush files +common/temp/** +package-deps.json \ No newline at end of file diff --git a/common/config/rush/.npmrc b/common/config/rush/.npmrc new file mode 100644 index 000000000..95cdbb645 --- /dev/null +++ b/common/config/rush/.npmrc @@ -0,0 +1,12 @@ +# Rush uses this file to configure the package registry, regardless of whether the +# package manager is PNPM, NPM, or Yarn. Prior to invoking the package manager, +# Rush will always copy this file to the folder where installation is performed. +# When NPM is the package manager, Rush works around NPM's processing of +# undefined environment variables by deleting any lines that reference undefined +# environment variables. +# +# DO NOT SPECIFY AUTHENTICATION CREDENTIALS IN THIS FILE. It should only be used +# to configure registry sources. + +registry=https://registry.npmjs.org/ +always-auth=false diff --git a/common/config/rush/command-line.json b/common/config/rush/command-line.json new file mode 100644 index 000000000..b526e61d6 --- /dev/null +++ b/common/config/rush/command-line.json @@ -0,0 +1,206 @@ +/** + * This configuration file defines custom commands for the "rush" command-line. + * For full documentation, please see https://rushjs.io + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/command-line.schema.json", + /** + * Custom "commands" introduce new verbs for the command-line. To see the help for these + * example commands, try "rush --help", "rush my-bulk-command --help", or + * "rush my-global-command --help". + */ + "commands": [ + { + "commandKind": "bulk", + "name": "set-version", + "summary": "set the version in the package.json files", + "enableParallelism": true + } + // { + // /** + // * (Required) Determines the type of custom command. + // * Rush's "bulk" commands are invoked separately for each project. Rush will look in + // * each project's package.json file for a "scripts" entry whose name matches the + // * command name. By default, the command will run for every project in the repo, + // * according to the dependency graph (similar to how "rush build" works). + // * The set of projects can be restricted e.g. using the "--to" or "--from" parameters. + // */ + // "commandKind": "bulk", + // + // /** + // * (Required) The name that will be typed as part of the command line. This is also the name + // * of the "scripts" hook in the project's package.json file. + // * The name should be comprised of lower case words separated by hyphens. + // */ + // "name": "my-bulk-command", + // + // /** + // * (Required) A short summary of the custom command to be shown when printing command line + // * help, e.g. "rush --help". + // */ + // "summary": "Example bulk custom command", + // + // /** + // * A detailed description of the command to be shown when printing command line + // * help (e.g. "rush --help my-command"). + // * If omitted, the "summary" text will be shown instead. + // * + // * Whenever you introduce commands/parameters, taking a little time to write meaningful + // * documentation can make a big difference for the developer experience in your repo. + // */ + // "description": "This is an example custom command that runs separately for each project", + // + // /** + // * (Required) If true, then this command is safe to be run in parallel, i.e. executed + // * simultaneously for multiple projects. Similar to "rush build", regardless of parallelism + // * projects will not start processing until their dependencies have completed processing. + // */ + // "enableParallelism": false, + // + // /** + // * Normally Rush requires that each project's package.json has a "scripts" entry matching + // * the custom command name. To disable this check, set "ignoreMissingScript" to true; + // * projects with a missing definition will be skipped. + // */ + // "ignoreMissingScript": false + // }, + , + { + "commandKind": "global", + "name": "sync-versions", + "summary": "sync versions of sibling projects", + "shellCommand": "node common/scripts/sync-versions.js" + }, + { + "commandKind": "global", + "name": "watch", + "summary": "run npm watch on all projects", + "shellCommand": "node common/scripts/watch.js" + }, + { + "commandKind": "global", + "name": "clean", + "summary": "run npm clean on all projects", + "shellCommand": "node common/scripts/clean.js" + }, + { + "commandKind": "global", + "name": "test", + "summary": "run all npm test", + "shellCommand": "node common/scripts/test.js" + } + // + // { + // /** + // * (Required) Determines the type of custom command. + // * Rush's "global" commands are invoked once for the entire repo. + // */ + // "commandKind": "global", + // + // "name": "my-global-command", + // "summary": "Example global custom command", + // "description": "This is an example custom command that runs once for the entire repo", + // + // /** + // * A script that will be invoked using the OS shell. The working directory will be the folder + // * that contains rush.json. If custom parameters are associated with this command, their + // * values will be appended to the end of this string. + // */ + // "shellCommand": "node common/scripts/my-global-command.js" + // } + ], + /** + * Custom "parameters" introduce new parameters for specified Rush command-line commands. + * For example, you might define a "--production" parameter for the "rush build" command. + */ + "parameters": [ + // { + // /** + // * (Required) Determines the type of custom parameter. + // * A "flag" is a custom command-line parameter whose presence acts as an on/off switch. + // */ + // "parameterKind": "flag", + // + // /** + // * (Required) The long name of the parameter. It must be lower-case and use dash delimiters. + // */ + // "longName": "--my-flag", + // + // /** + // * An optional alternative short name for the parameter. It must be a dash followed by a single + // * lower-case or upper-case letter, which is case-sensitive. + // * + // * NOTE: The Rush developers recommend that automation scripts should always use the long name + // * to improve readability. The short name is only intended as a convenience for humans. + // * The alphabet letters run out quickly, and are difficult to memorize, so *only* use + // * a short name if you expect the parameter to be needed very often in everyday operations. + // */ + // "shortName": "-m", + // + // /** + // * (Required) A long description to be shown in the command-line help. + // * + // * Whenever you introduce commands/parameters, taking a little time to write meaningful + // * documentation can make a big difference for the developer experience in your repo. + // */ + // "description": "A custom flag parameter that is passed to the scripts that are invoked when building projects", + // + // /** + // * (Required) A list of custom commands and/or built-in Rush commands that this parameter may + // * be used with. The parameter will be appended to the shell command that Rush invokes. + // */ + // "associatedCommands": [ "build", "rebuild" ] + // }, + // + // { + // /** + // * (Required) Determines the type of custom parameter. + // * A "flag" is a custom command-line parameter whose presence acts as an on/off switch. + // */ + // "parameterKind": "choice", + // "longName": "--my-choice", + // "description": "A custom choice parameter for the \"my-global-command\" custom command", + // + // "associatedCommands": [ "my-global-command" ], + // + // /** + // * Normally if a parameter is omitted from the command line, it will not be passed + // * to the shell command. this value will be inserted by default. Whereas if a "defaultValue" + // * is defined, the parameter will always be passed to the shell command, and will use the + // * default value if unspecified. The value must be one of the defined alternatives. + // */ + // "defaultValue": "vanilla", + // + // /** + // * (Required) A list of alternative argument values that can be chosen for this parameter. + // */ + // "alternatives": [ + // { + // /** + // * A token that is one of the alternatives that can be used with the choice parameter, + // * e.g. "vanilla" in "--flavor vanilla". + // */ + // "name": "vanilla", + // + // /** + // * A detailed description for the alternative that can be shown in the command-line help. + // * + // * Whenever you introduce commands/parameters, taking a little time to write meaningful + // * documentation can make a big difference for the developer experience in your repo. + // */ + // "description": "Use the vanilla flavor (the default)" + // }, + // + // { + // "name": "chocolate", + // "description": "Use the chocolate flavor" + // }, + // + // { + // "name": "strawberry", + // "description": "Use the strawberry flavor" + // } + // ] + // } + ] +} diff --git a/common/config/rush/common-versions.json b/common/config/rush/common-versions.json new file mode 100644 index 000000000..5f8894ab3 --- /dev/null +++ b/common/config/rush/common-versions.json @@ -0,0 +1,43 @@ +/** + * This configuration file specifies NPM dependency version selections that affect all projects + * in a Rush repo. For full documentation, please see https://rushjs.io + */ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/common-versions.schema.json", + + /** + * A table that specifies a "preferred version" for a dependency package. The "preferred version" + * is typically used to hold an indirect dependency back to a specific version, however generally + * it can be any SemVer range specifier (e.g. "~1.2.3"), and it will narrow any (compatible) + * SemVer range specifier. See the Rush documentation for details about this feature. + */ + "preferredVersions": { + + /** + * When someone asks for "^1.0.0" make sure they get "1.2.3" when working in this repo, + * instead of the latest version. + */ + // "some-library": "1.2.3" + }, + + /** + * The "rush check" command can be used to enforce that every project in the repo must specify + * the same SemVer range for a given dependency. However, sometimes exceptions are needed. + * The allowedAlternativeVersions table allows you to list other SemVer ranges that will be + * accepted by "rush check" for a given dependency. + * + * IMPORTANT: THIS TABLE IS FOR *ADDITIONAL* VERSION RANGES THAT ARE ALTERNATIVES TO THE + * USUAL VERSION (WHICH IS INFERRED BY LOOKING AT ALL PROJECTS IN THE REPO). + * This design avoids unnecessary churn in this file. + */ + "allowedAlternativeVersions": { + + /** + * For example, allow some projects to use an older TypeScript compiler + * (in addition to whatever "usual" version is being used by other projects in the repo): + */ + // "typescript": [ + // "~2.4.0" + // ] + } +} diff --git a/common/config/rush/pnpmfile.js b/common/config/rush/pnpmfile.js new file mode 100644 index 000000000..a575eec35 --- /dev/null +++ b/common/config/rush/pnpmfile.js @@ -0,0 +1,39 @@ +"use strict"; + +/** + * When using the PNPM package manager, you can use pnpmfile.js to workaround + * dependencies that have mistakes in their package.json file. (This feature is + * functionally similar to Yarn's "resolutions".) + * + * For details, see the PNPM documentation: + * https://pnpm.js.org/docs/en/hooks.html + * + * IMPORTANT: SINCE THIS FILE CONTAINS EXECUTABLE CODE, MODIFYING IT IS LIKELY + * TO INVALIDATE ANY CACHED DEPENDENCY ANALYSIS. We recommend to run "rush update --full" + * after any modification to pnpmfile.js. + * + */ +module.exports = { + hooks: { + readPackage + } +}; + +/** + * This hook is invoked during installation before a package's dependencies + * are selected. + * The `packageJson` parameter is the deserialized package.json + * contents for the package that is about to be installed. + * The `context` parameter provides a log() function. + * The return value is the updated object. + */ +function readPackage(packageJson, context) { + + // // The karma types have a missing dependency on typings from the log4js package. + // if (packageJson.name === '@types/karma') { + // context.log('Fixed up dependencies for @types/karma'); + // packageJson.dependencies['log4js'] = '0.6.38'; + // } + + return packageJson; +} diff --git a/common/config/rush/version-policies.json b/common/config/rush/version-policies.json new file mode 100644 index 000000000..ef29e412e --- /dev/null +++ b/common/config/rush/version-policies.json @@ -0,0 +1,81 @@ +/** + * This is configuration file is used for advanced publishing configurations with Rush. + * For full documentation, please see https://rushjs.io + */ + + /** + * A list of version policy definitions. A "version policy" is a custom package versioning + * strategy that affets "rush change", "rush version", and "rush publish". The strategy applies + * to a set of projects that are specified using the "versionPolicyName" field in rush.json. + */ +[ + // { + // /** + // * (Required) Indicates the kind of version policy being defined ("lockStepVersion" or "individualVersion"). + // * + // * The "lockStepVersion" mode specifies that the projects will use "lock-step versioning". This + // * strategy is appropriate for a set of packages that act as selectable components of a + // * unified product. The entire set of packages are always published together, and always share + // * the same NPM version number. When the packages depend on other packages in the set, the + // * SemVer range is usually restricted to a single version. + // */ + // "definitionName": "lockStepVersion", + // + // /** + // * (Required) The name that will be used for the "versionPolicyName" field in rush.json. + // * This name is also used command-line parameters such as "--version-policy" + // * and "--to-version-policy". + // */ + // "policyName": "MyBigFramework", + // + // /** + // * (Required) The current version. All packages belonging to the set should have this version + // * in the current branch. When bumping versions, Rush uses this to determine the next version. + // * (The "version" field in package.json is NOT considered.) + // */ + // "version": "1.0.0", + // + // /** + // * (Required) The type of bump that will be performed when publishing the next release. + // * When creating a release branch in Git, this field should be updated according to the + // * type of release. + // * + // * Valid values are: "prerelease", "release", "minor", "patch", "major" + // */ + // "nextBump": "prerelease", + // + // /** + // * (Optional) If specified, all packages in the set share a common CHANGELOG.md file. + // * This file is stored with the specified "main" project, which must be a member of the set. + // * + // * If this field is omitted, then a separate CHANGELOG.md file will be maintained for each + // * package in the set. + // */ + // "mainProject": "my-app" + // }, + // + // { + // /** + // * (Required) Indicates the kind of version policy being defined ("lockStepVersion" or "individualVersion"). + // * + // * The "individualVersion" mode specifies that the projects will use "individual versioning". + // * This is the typical NPM model where each package has an independent version number + // * and CHANGELOG.md file. Although a single CI definition is responsible for publishing the + // * packages, they otherwise don't have any special relationship. The version bumping will + // * depend on how developers answer the "rush change" questions for each package that + // * is changed. + // */ + // "definitionName": "individualVersion", + // + // "policyName": "MyRandomLibraries", + // + // /** + // * (Optional) This can be used to enforce that all packages in the set must share a common + // * major version number, e.g. because they are from the same major release branch. + // * It can also be used to discourage people from accidentally making "MAJOR" SemVer changes + // * inappropriately. The minor/patch version parts will be bumped independently according + // * to the types of changes made to each project, according to the "rush change" command. + // */ + // "lockedMajor": 3 + // } +] diff --git a/common/scripts/clean.js b/common/scripts/clean.js new file mode 100644 index 000000000..4286e5927 --- /dev/null +++ b/common/scripts/clean.js @@ -0,0 +1 @@ +require('./for-each').npm('clean'); \ No newline at end of file diff --git a/common/scripts/for-each.js b/common/scripts/for-each.js new file mode 100644 index 000000000..9704361bc --- /dev/null +++ b/common/scripts/for-each.js @@ -0,0 +1,47 @@ +var cp = require("child_process"); +var fs = require("fs"); +var path = require("path"); + +function read(filename) { + const txt = fs.readFileSync(filename, "utf8") + .replace(/\r/gm, "") + .replace(/\n/gm, "«") + .replace(/\/\*.*?\*\//gm, "") + .replace(/«/gm, "\n") + .replace(/\s+\/\/.*/g, ""); + return JSON.parse(txt); +} + +const rush = read(`${__dirname}/../../rush.json`); +const pjs = {}; + +function forEachProject(onEach) { + // load all the projects + for (const each of rush.projects) { + const packageName = each.packageName; + const projectFolder = path.resolve(`${__dirname}/../../${each.projectFolder}`); + const project = require(`${projectFolder}/package.json`); + onEach(packageName, projectFolder, project); + } +} + +function npmForEach(cmd) { + const result = {}; + forEachProject((name, location, project) => { + if (project.scripts[cmd]) { + const proc = cp.spawn("npm", ["run", cmd], { cwd: location, shell: true, stdio: "inherit" }); + proc.on("close", (code, signal) => { + if (code !== 0) { + process.exit(code); + } + }); + result[name] = { + name, location, project, proc, + }; + } + }); + return result; +} + +module.exports.forEachProject = forEachProject; +module.exports.npm = npmForEach; diff --git a/common/scripts/install-run-rush.js b/common/scripts/install-run-rush.js new file mode 100644 index 000000000..51bdbbb23 --- /dev/null +++ b/common/scripts/install-run-rush.js @@ -0,0 +1,51 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See the @microsoft/rush package's LICENSE file for license information. +Object.defineProperty(exports, "__esModule", { value: true }); +// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. +// +// This script is intended for usage in an automated build environment where the Rush command may not have +// been preinstalled, or may have an unpredictable version. This script will automatically install the version of Rush +// specified in the rush.json configuration file (if not already installed), and then pass a command-line to it. +// An example usage would be: +// +// node common/scripts/install-run-rush.js install +// +// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ +const path = require("path"); +const fs = require("fs"); +const install_run_1 = require("./install-run"); +const PACKAGE_NAME = '@microsoft/rush'; +function getRushVersion() { + const rushJsonFolder = install_run_1.findRushJsonFolder(); + const rushJsonPath = path.join(rushJsonFolder, install_run_1.RUSH_JSON_FILENAME); + try { + const rushJsonContents = fs.readFileSync(rushJsonPath, 'utf-8'); + // Use a regular expression to parse out the rushVersion value because rush.json supports comments, + // but JSON.parse does not and we don't want to pull in more dependencies than we need to in this script. + const rushJsonMatches = rushJsonContents.match(/\"rushVersion\"\s*\:\s*\"([0-9a-zA-Z.+\-]+)\"/); + return rushJsonMatches[1]; + } + catch (e) { + throw new Error(`Unable to determine the required version of Rush from rush.json (${rushJsonFolder}). ` + + 'The \'rushVersion\' field is either not assigned in rush.json or was specified ' + + 'using an unexpected syntax.'); + } +} +function run() { + const [nodePath, /* Ex: /bin/node */ // tslint:disable-line:no-unused-variable + scriptPath, /* /repo/common/scripts/install-run-rush.js */ // tslint:disable-line:no-unused-variable + ...packageBinArgs /* [build, --to, myproject] */] = process.argv; + if (process.argv.length < 3) { + console.log('Usage: install-run-rush.js [args...]'); + console.log('Example: install-run-rush.js build --to myproject'); + process.exit(1); + } + install_run_1.runWithErrorAndStatusCode(() => { + const version = getRushVersion(); + console.log(`The rush.json configuration requests Rush version ${version}`); + return install_run_1.installAndRun(PACKAGE_NAME, version, 'rush', packageBinArgs); + }); +} +run(); +//# sourceMappingURL=install-run-rush.js.map \ No newline at end of file diff --git a/common/scripts/install-run.js b/common/scripts/install-run.js new file mode 100644 index 000000000..085391329 --- /dev/null +++ b/common/scripts/install-run.js @@ -0,0 +1,397 @@ +"use strict"; +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See the @microsoft/rush package's LICENSE file for license information. +Object.defineProperty(exports, "__esModule", { value: true }); +// THIS FILE WAS GENERATED BY A TOOL. ANY MANUAL MODIFICATIONS WILL GET OVERWRITTEN WHENEVER RUSH IS UPGRADED. +// +// This script is intended for usage in an automated build environment where a Node tool may not have +// been preinstalled, or may have an unpredictable version. This script will automatically install the specified +// version of the specified tool (if not already installed), and then pass a command-line to it. +// An example usage would be: +// +// node common/scripts/install-run.js rimraf@2.6.2 rimraf -f project1/lib +// +// For more information, see: https://rushjs.io/pages/maintainer/setup_new_repo/ +const childProcess = require("child_process"); +const fs = require("fs"); +const os = require("os"); +const path = require("path"); +exports.RUSH_JSON_FILENAME = 'rush.json'; +const INSTALLED_FLAG_FILENAME = 'installed.flag'; +const NODE_MODULES_FOLDER_NAME = 'node_modules'; +const PACKAGE_JSON_FILENAME = 'package.json'; +/** + * Parse a package specifier (in the form of name\@version) into name and version parts. + */ +function parsePackageSpecifier(rawPackageSpecifier) { + rawPackageSpecifier = (rawPackageSpecifier || '').trim(); + const separatorIndex = rawPackageSpecifier.lastIndexOf('@'); + let name; + let version = undefined; + if (separatorIndex === 0) { + // The specifier starts with a scope and doesn't have a version specified + name = rawPackageSpecifier; + } + else if (separatorIndex === -1) { + // The specifier doesn't have a version + name = rawPackageSpecifier; + } + else { + name = rawPackageSpecifier.substring(0, separatorIndex); + version = rawPackageSpecifier.substring(separatorIndex + 1); + } + if (!name) { + throw new Error(`Invalid package specifier: ${rawPackageSpecifier}`); + } + return { name, version }; +} +/** + * Resolve a package specifier to a static version + */ +function resolvePackageVersion(rushCommonFolder, { name, version }) { + if (!version) { + version = '*'; // If no version is specified, use the latest version + } + if (version.match(/^[a-zA-Z0-9\-\+\.]+$/)) { + // If the version contains only characters that we recognize to be used in static version specifiers, + // pass the version through + return version; + } + else { + // version resolves to + try { + const rushTempFolder = ensureAndJoinPath(rushCommonFolder, 'temp'); + const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush'); + syncNpmrc(sourceNpmrcFolder, rushTempFolder); + const npmPath = getNpmPath(); + // This returns something that looks like: + // @microsoft/rush@3.0.0 '3.0.0' + // @microsoft/rush@3.0.1 '3.0.1' + // ... + // @microsoft/rush@3.0.20 '3.0.20' + // + const npmVersionSpawnResult = childProcess.spawnSync(npmPath, ['view', `${name}@${version}`, 'version', '--no-update-notifier'], { + cwd: rushTempFolder, + stdio: [] + }); + if (npmVersionSpawnResult.status !== 0) { + throw new Error(`"npm view" returned error code ${npmVersionSpawnResult.status}`); + } + const npmViewVersionOutput = npmVersionSpawnResult.stdout.toString(); + const versionLines = npmViewVersionOutput.split('\n').filter((line) => !!line); + const latestVersion = versionLines[versionLines.length - 1]; + if (!latestVersion) { + throw new Error('No versions found for the specified version range.'); + } + const versionMatches = latestVersion.match(/^.+\s\'(.+)\'$/); + if (!versionMatches) { + throw new Error(`Invalid npm output ${latestVersion}`); + } + return versionMatches[1]; + } + catch (e) { + throw new Error(`Unable to resolve version ${version} of package ${name}: ${e}`); + } + } +} +let _npmPath = undefined; +/** + * Get the absolute path to the npm executable + */ +function getNpmPath() { + if (!_npmPath) { + try { + if (os.platform() === 'win32') { + // We're on Windows + const whereOutput = childProcess.execSync('where npm', { stdio: [] }).toString(); + const lines = whereOutput.split(os.EOL).filter((line) => !!line); + // take the last result, we are looking for a .cmd command + // see https://github.com/Microsoft/web-build-tools/issues/759 + _npmPath = lines[lines.length - 1]; + } + else { + // We aren't on Windows - assume we're on *NIX or Darwin + _npmPath = childProcess.execSync('which npm', { stdio: [] }).toString(); + } + } + catch (e) { + throw new Error(`Unable to determine the path to the NPM tool: ${e}`); + } + _npmPath = _npmPath.trim(); + if (!fs.existsSync(_npmPath)) { + throw new Error('The NPM executable does not exist'); + } + } + return _npmPath; +} +exports.getNpmPath = getNpmPath; +let _rushJsonFolder; +/** + * Find the absolute path to the folder containing rush.json + */ +function findRushJsonFolder() { + if (!_rushJsonFolder) { + let basePath = __dirname; + let tempPath = __dirname; + do { + const testRushJsonPath = path.join(basePath, exports.RUSH_JSON_FILENAME); + if (fs.existsSync(testRushJsonPath)) { + _rushJsonFolder = basePath; + break; + } + else { + basePath = tempPath; + } + } while (basePath !== (tempPath = path.dirname(basePath))); // Exit the loop when we hit the disk root + if (!_rushJsonFolder) { + throw new Error('Unable to find rush.json.'); + } + } + return _rushJsonFolder; +} +exports.findRushJsonFolder = findRushJsonFolder; +/** + * Create missing directories under the specified base directory, and return the resolved directory. + * + * Does not support "." or ".." path segments. + * Assumes the baseFolder exists. + */ +function ensureAndJoinPath(baseFolder, ...pathSegments) { + let joinedPath = baseFolder; + try { + for (let pathSegment of pathSegments) { + pathSegment = pathSegment.replace(/[\\\/]/g, '+'); + joinedPath = path.join(joinedPath, pathSegment); + if (!fs.existsSync(joinedPath)) { + fs.mkdirSync(joinedPath); + } + } + } + catch (e) { + throw new Error(`Error building local installation folder (${path.join(baseFolder, ...pathSegments)}): ${e}`); + } + return joinedPath; +} +/** + * As a workaround, _syncNpmrc() copies the .npmrc file to the target folder, and also trims + * unusable lines from the .npmrc file. If the source .npmrc file not exist, then _syncNpmrc() + * will delete an .npmrc that is found in the target folder. + * + * Why are we trimming the .npmrc lines? NPM allows environment variables to be specified in + * the .npmrc file to provide different authentication tokens for different registry. + * However, if the environment variable is undefined, it expands to an empty string, which + * produces a valid-looking mapping with an invalid URL that causes an error. Instead, + * we'd prefer to skip that line and continue looking in other places such as the user's + * home directory. + * + * IMPORTANT: THIS CODE SHOULD BE KEPT UP TO DATE WITH Utilities._syncNpmrc() + */ +function syncNpmrc(sourceNpmrcFolder, targetNpmrcFolder) { + const sourceNpmrcPath = path.join(sourceNpmrcFolder, '.npmrc'); + const targetNpmrcPath = path.join(targetNpmrcFolder, '.npmrc'); + try { + if (fs.existsSync(sourceNpmrcPath)) { + let npmrcFileLines = fs.readFileSync(sourceNpmrcPath).toString().split('\n'); + npmrcFileLines = npmrcFileLines.map((line) => (line || '').trim()); + const resultLines = []; + // Trim out lines that reference environment variables that aren't defined + for (const line of npmrcFileLines) { + // This finds environment variable tokens that look like "${VAR_NAME}" + const regex = /\$\{([^\}]+)\}/g; + const environmentVariables = line.match(regex); + let lineShouldBeTrimmed = false; + if (environmentVariables) { + for (const token of environmentVariables) { + // Remove the leading "${" and the trailing "}" from the token + const environmentVariableName = token.substring(2, token.length - 1); + if (!process.env[environmentVariableName]) { + lineShouldBeTrimmed = true; + break; + } + } + } + if (lineShouldBeTrimmed) { + // Example output: + // "; MISSING ENVIRONMENT VARIABLE: //my-registry.com/npm/:_authToken=${MY_AUTH_TOKEN}" + resultLines.push('; MISSING ENVIRONMENT VARIABLE: ' + line); + } + else { + resultLines.push(line); + } + } + fs.writeFileSync(targetNpmrcPath, resultLines.join(os.EOL)); + } + else if (fs.existsSync(targetNpmrcPath)) { + // If the source .npmrc doesn't exist and there is one in the target, delete the one in the target + fs.unlinkSync(targetNpmrcPath); + } + } + catch (e) { + throw new Error(`Error syncing .npmrc file: ${e}`); + } +} +/** + * Detects if the package in the specified directory is installed + */ +function isPackageAlreadyInstalled(packageInstallFolder) { + try { + const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); + if (!fs.existsSync(flagFilePath)) { + return false; + } + const fileContents = fs.readFileSync(flagFilePath).toString(); + return fileContents.trim() === process.version; + } + catch (e) { + return false; + } +} +/** + * Removes the following files and directories under the specified folder path: + * - installed.flag + * - + * - node_modules + */ +function cleanInstallFolder(rushCommonFolder, packageInstallFolder) { + try { + const flagFile = path.resolve(packageInstallFolder, INSTALLED_FLAG_FILENAME); + if (fs.existsSync(flagFile)) { + fs.unlinkSync(flagFile); + } + const packageLockFile = path.resolve(packageInstallFolder, 'package-lock.json'); + if (fs.existsSync(packageLockFile)) { + fs.unlinkSync(packageLockFile); + } + const nodeModulesFolder = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME); + if (fs.existsSync(nodeModulesFolder)) { + const rushRecyclerFolder = ensureAndJoinPath(rushCommonFolder, 'temp', 'rush-recycler', `install-run-${Date.now().toString()}`); + fs.renameSync(nodeModulesFolder, rushRecyclerFolder); + } + } + catch (e) { + throw new Error(`Error cleaning the package install folder (${packageInstallFolder}): ${e}`); + } +} +function createPackageJson(packageInstallFolder, name, version) { + try { + const packageJsonContents = { + 'name': 'ci-rush', + 'version': '0.0.0', + 'dependencies': { + [name]: version + }, + 'description': 'DON\'T WARN', + 'repository': 'DON\'T WARN', + 'license': 'MIT' + }; + const packageJsonPath = path.join(packageInstallFolder, PACKAGE_JSON_FILENAME); + fs.writeFileSync(packageJsonPath, JSON.stringify(packageJsonContents, undefined, 2)); + } + catch (e) { + throw new Error(`Unable to create package.json: ${e}`); + } +} +/** + * Run "npm install" in the package install folder. + */ +function installPackage(packageInstallFolder, name, version) { + try { + console.log(`Installing ${name}...`); + const npmPath = getNpmPath(); + const result = childProcess.spawnSync(npmPath, ['install'], { + stdio: 'inherit', + cwd: packageInstallFolder, + env: process.env + }); + if (result.status !== 0) { + throw new Error('"npm install" encountered an error'); + } + console.log(`Successfully installed ${name}@${version}`); + } + catch (e) { + throw new Error(`Unable to install package: ${e}`); + } +} +/** + * Get the ".bin" path for the package. + */ +function getBinPath(packageInstallFolder, binName) { + const binFolderPath = path.resolve(packageInstallFolder, NODE_MODULES_FOLDER_NAME, '.bin'); + const resolvedBinName = (os.platform() === 'win32') ? `${binName}.cmd` : binName; + return path.resolve(binFolderPath, resolvedBinName); +} +/** + * Write a flag file to the package's install directory, signifying that the install was successful. + */ +function writeFlagFile(packageInstallFolder) { + try { + const flagFilePath = path.join(packageInstallFolder, INSTALLED_FLAG_FILENAME); + fs.writeFileSync(flagFilePath, process.version); + } + catch (e) { + throw new Error(`Unable to create installed.flag file in ${packageInstallFolder}`); + } +} +function installAndRun(packageName, packageVersion, packageBinName, packageBinArgs) { + const rushJsonFolder = findRushJsonFolder(); + const rushCommonFolder = path.join(rushJsonFolder, 'common'); + const packageInstallFolder = ensureAndJoinPath(rushCommonFolder, 'temp', 'install-run', `${packageName}@${packageVersion}`); + if (!isPackageAlreadyInstalled(packageInstallFolder)) { + // The package isn't already installed + cleanInstallFolder(rushCommonFolder, packageInstallFolder); + const sourceNpmrcFolder = path.join(rushCommonFolder, 'config', 'rush'); + syncNpmrc(sourceNpmrcFolder, packageInstallFolder); + createPackageJson(packageInstallFolder, packageName, packageVersion); + installPackage(packageInstallFolder, packageName, packageVersion); + writeFlagFile(packageInstallFolder); + } + const statusMessage = `Invoking "${packageBinName} ${packageBinArgs.join(' ')}"`; + const statusMessageLine = new Array(statusMessage.length + 1).join('-'); + console.log(os.EOL + statusMessage + os.EOL + statusMessageLine + os.EOL); + const binPath = getBinPath(packageInstallFolder, packageBinName); + const result = childProcess.spawnSync(binPath, packageBinArgs, { + stdio: 'inherit', + cwd: process.cwd(), + env: process.env + }); + return result.status; +} +exports.installAndRun = installAndRun; +function runWithErrorAndStatusCode(fn) { + process.exitCode = 1; + try { + const exitCode = fn(); + process.exitCode = exitCode; + } + catch (e) { + console.error(os.EOL + os.EOL + e.toString() + os.EOL + os.EOL); + } +} +exports.runWithErrorAndStatusCode = runWithErrorAndStatusCode; +function run() { + const [nodePath, /* Ex: /bin/node */ // tslint:disable-line:no-unused-variable + scriptPath, /* /repo/common/scripts/install-run-rush.js */ rawPackageSpecifier, /* rimraf@^2.0.0 */ packageBinName, /* rimraf */ ...packageBinArgs /* [-f, myproject/lib] */] = process.argv; + if (path.basename(scriptPath).toLowerCase() !== 'install-run.js') { + // If install-run.js wasn't directly invoked, don't execute the rest of this function. Return control + // to the script that (presumably) imported this file + return; + } + if (process.argv.length < 4) { + console.log('Usage: install-run.js @ [args...]'); + console.log('Example: install-run.js rimraf@2.6.2 rimraf -f project1/lib'); + process.exit(1); + } + runWithErrorAndStatusCode(() => { + const rushJsonFolder = findRushJsonFolder(); + const rushCommonFolder = ensureAndJoinPath(rushJsonFolder, 'common'); + const packageSpecifier = parsePackageSpecifier(rawPackageSpecifier); + const name = packageSpecifier.name; + const version = resolvePackageVersion(rushCommonFolder, packageSpecifier); + if (packageSpecifier.version !== version) { + console.log(`Resolved to ${name}@${version}`); + } + return installAndRun(name, version, packageBinName, packageBinArgs); + }); +} +run(); +//# sourceMappingURL=install-run.js.map \ No newline at end of file diff --git a/common/scripts/sync-versions.js b/common/scripts/sync-versions.js new file mode 100644 index 000000000..ec239f742 --- /dev/null +++ b/common/scripts/sync-versions.js @@ -0,0 +1,102 @@ +var fs = require('fs'); + +function read(filename) { + const txt =fs.readFileSync(filename, 'utf8') + .replace(/\r/gm, '') + .replace(/\n/gm, '«') + .replace(/\/\*.*?\*\//gm,'') + .replace(/«/gm, '\n') + .replace(/\s+\/\/.*/g, ''); + return JSON.parse( txt); +} + +function versionToInt(ver) { + let v = ver.replace(/[^\d\.]/g,'').split('.').slice(0,3); + while( v.length < 3) { + v.unshift(0); + } + let n = 0 ; + for( let i =0; i< v.length;i++ ) { + n = n + ((2**(i*16))*parseInt(v[v.length-1 - i])) + } + return n; +} + +const rush =read(`${__dirname}/../../rush.json`); +const pjs = {}; + +// load all the projects +for( const each of rush.projects ) { + const packageName = each.packageName; + const projectFolder = each.projectFolder; + pjs[packageName] = require(`${__dirname}/../../${projectFolder}/package.json`); +} + +// verify that peer dependencies are the same version as they are building. +for( const pj of Object.getOwnPropertyNames(pjs) ){ + const each = pjs[pj]; + for( const dep in each.dependencies ) { + const ref = pjs[dep]; + if( ref ) { + each.dependencies[dep] = `^${ref.version}`; + } + } +} + +function recordDeps(dependencies) { + for( const packageName in dependencies ) { + const packageVersion = dependencies[packageName]; + if( packageList[packageName] ) { + // same version? + if( packageList[packageName] === packageVersion ) { + continue; + } + + // pick the higher one + const v = versionToInt(packageVersion); + + + if( v === 0) { + console.error(`Unparsed version ${packageName}:${packageVersion}`); + process.exit(1); + } + const v2 = versionToInt(packageList[packageName]); + if( v > v2 ) { + packageList[packageName] = packageVersion; + } + } else { + packageList[packageName] = packageVersion; + } + } +} +function fixDeps(pj,dependencies) { + for( const packageName in dependencies ) { + if( dependencies[packageName] !== packageList[packageName] ) { + console.log(`updating ${pj}:${packageName} from '${dependencies[packageName]}' to '${packageList[packageName]}'`) + dependencies[packageName] = packageList[packageName]; + } + } +} +const packageList = {}; +// now compare to see if someone has an exnternal package with different version +// than everyone else. +for( const pj of Object.getOwnPropertyNames(pjs) ){ + const each = pjs[pj]; + recordDeps(each.dependencies); + recordDeps(each.devDependencies); +} + +for( const pj of Object.getOwnPropertyNames(pjs) ){ + const each = pjs[pj]; + fixDeps(pj,each.dependencies); + fixDeps(pj,each.devDependencies); +} + +// write out the results. +for( const each of rush.projects ) { + const packageName = each.packageName; + const projectFolder = each.projectFolder; + fs.writeFileSync(`${__dirname}/../../${projectFolder}/package.json`, JSON.stringify(pjs[packageName], null, 2 )); +} + +console.log("project.json files updated"); \ No newline at end of file diff --git a/common/scripts/test.js b/common/scripts/test.js new file mode 100644 index 000000000..485fb5758 --- /dev/null +++ b/common/scripts/test.js @@ -0,0 +1 @@ +require('./for-each').npm('test'); \ No newline at end of file diff --git a/common/scripts/watch.js b/common/scripts/watch.js new file mode 100644 index 000000000..b3d2ed431 --- /dev/null +++ b/common/scripts/watch.js @@ -0,0 +1,43 @@ +var fs = require('fs'); +var cp = require('child_process'); + +function read(filename) { + const txt =fs.readFileSync(filename, 'utf8') + .replace(/\r/gm, '') + .replace(/\n/gm, '«') + .replace(/\/\*.*?\*\//gm,'') + .replace(/«/gm, '\n') + .replace(/\s+\/\/.*/g, ''); + return JSON.parse( txt); +} + +const rush =read(`${__dirname}/../../rush.json`); +const pjs = {}; + +// load all the projects +for( const each of rush.projects ) { + const packageName = each.packageName; + const projectFolder = each.projectFolder; + const project = require(`${__dirname}/../../${projectFolder}/package.json`); + + if( project.scripts.watch ) { + console.log(`npm.cmd run watch {cwd: ${__dirname}/../../${projectFolder}}`); + const proc = cp.spawn('npm.cmd', ['run','watch'],{cwd: `${__dirname}/../../${projectFolder}`,shell:true,stdio:"inherit"}); + proc.on("error", (c,s) => { + console.log(packageName); + console.error( c); + console.error( s); + }); + proc.on('exit',(c,s)=> { + console.log(packageName); + console.error( c); + console.error( s); + }); + proc.on('message',(c,s)=> { + console.log(packageName); + console.error( c); + console.error( s); + }) + } +} + diff --git a/rush.json b/rush.json new file mode 100644 index 000000000..798e370fd --- /dev/null +++ b/rush.json @@ -0,0 +1,74 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/rush/v5/rush.schema.json", + "rushVersion": "5.3.1", + "pnpmVersion": "2.15.1", + "pnpmOptions": {}, + "nodeSupportedVersionRange": ">=8.9.4 <11.0.0", + "ensureConsistentVersions": true, + "projectFolderMinDepth": 2, + "projectFolderMaxDepth": 3, + "repository": {}, + "eventHooks": { + "preRushInstall": [], + "postRushInstall": [], + "preRushBuild": [], + "postRushBuild": [] + }, + "telemetryEnabled": false, + "projects": [ + { + "packageName": "@microsoft.azure/tasks", + "projectFolder": "perks/libraries/tasks", + "reviewCategory": "production", + "shouldPublish": true + }, + { + "packageName": "@microsoft.azure/async-io", + "projectFolder": "perks/libraries/async-io", + "reviewCategory": "production", + "shouldPublish": true + }, + { + "packageName": "@microsoft.azure/eventing", + "projectFolder": "perks/libraries/eventing", + "reviewCategory": "production", + "shouldPublish": true + }, + { + "packageName": "@microsoft.azure/extension", + "projectFolder": "perks/libraries/extension", + "reviewCategory": "production", + "shouldPublish": true + }, + { + "packageName": "@microsoft.azure/datastore", + "projectFolder": "perks/libraries/datastore", + "reviewCategory": "production", + "shouldPublish": true + }, + { + "packageName": "@microsoft.azure/linq", + "projectFolder": "perks/libraries/linq", + "reviewCategory": "production", + "shouldPublish": true + }, + { + "packageName": "@microsoft.azure/openapi", + "projectFolder": "perks/libraries/openapi", + "reviewCategory": "production", + "shouldPublish": true + }, + { + "packageName": "@microsoft.azure/uri", + "projectFolder": "perks/libraries/uri", + "reviewCategory": "production", + "shouldPublish": true + }, + { + "packageName": "@microsoft.azure/oai2-to-oai3", + "projectFolder": "perks/libraries/oai2-to-oai3", + "reviewCategory": "production", + "shouldPublish": true + } + ] +}