├── .npmrc ├── .gitattributes ├── .eslintignore ├── .release-please-manifest.json ├── test ├── e2e │ ├── fixture │ │ ├── lerna │ │ │ ├── packages │ │ │ │ ├── bar │ │ │ │ │ └── package.json │ │ │ │ └── foo │ │ │ │ │ └── package.json │ │ │ ├── lerna.json │ │ │ ├── package.json │ │ │ └── LICENSE │ │ ├── workspaces │ │ │ ├── packages │ │ │ │ ├── bar │ │ │ │ │ └── package.json │ │ │ │ └── foo │ │ │ │ │ └── package.json │ │ │ ├── package.json │ │ │ └── LICENSE │ │ └── lerna-and-workspaces │ │ │ ├── packages │ │ │ ├── bar │ │ │ │ └── package.json │ │ │ └── foo │ │ │ │ └── package.json │ │ │ ├── lerna.json │ │ │ ├── package.json │ │ │ └── LICENSE │ └── behavior.spec.js └── unexpected.d.ts ├── commitlint.config.js ├── .husky ├── pre-commit └── commit-msg ├── src ├── error.js ├── index.js ├── util.js ├── model.js ├── find-package.js ├── sync-package.js ├── sync-file.js └── cli.js ├── .mocharc.js ├── .renovaterc.json ├── .markdownlint.yml ├── .editorconfig ├── tsconfig.json ├── .eslintrc.yml ├── .github └── workflows │ ├── release.yml │ └── nodejs.yml ├── release-please-config.json ├── .gitignore ├── package.json ├── README.md ├── CHANGELOG.md └── LICENSE /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | types 3 | **/*.d.ts 4 | -------------------------------------------------------------------------------- /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "1.0.2" 3 | } -------------------------------------------------------------------------------- /test/e2e/fixture/lerna/packages/bar/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/e2e/fixture/lerna/packages/foo/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/e2e/fixture/workspaces/packages/bar/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/e2e/fixture/workspaces/packages/foo/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/e2e/fixture/lerna-and-workspaces/packages/bar/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /test/e2e/fixture/lerna-and-workspaces/packages/foo/package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']}; 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint-staged 5 | -------------------------------------------------------------------------------- /src/error.js: -------------------------------------------------------------------------------- 1 | exports.SyncMonorepoPackagesError = class SyncMonorepoPackagesError extends ( 2 | Error 3 | ) {}; 4 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run commitlint -- --edit ${1} 5 | -------------------------------------------------------------------------------- /test/e2e/fixture/lerna/lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "0.0.0" 6 | } 7 | -------------------------------------------------------------------------------- /test/e2e/fixture/lerna-and-workspaces/lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/foo" 4 | ], 5 | "version": "0.0.0" 6 | } 7 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | timeout: '2s', 5 | slow: '1s', 6 | 'forbid-only': Boolean(process.env.CI) 7 | }; 8 | -------------------------------------------------------------------------------- /test/e2e/fixture/workspaces/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "keywords": ["foo", "bar", "baz"], 5 | "workspaces": [ 6 | "packages/*" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /test/e2e/fixture/lerna/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "keywords": ["foo", "bar", "baz"], 5 | "devDependencies": { 6 | "lerna": "^4.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.renovaterc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "github>appium/appium//renovate/default" 5 | ], 6 | "semanticCommitScope": "deps" 7 | } 8 | -------------------------------------------------------------------------------- /.markdownlint.yml: -------------------------------------------------------------------------------- 1 | ul-style: 2 | style: dash 3 | ul-indent: 4 | indent: 2 5 | commands-show-output: false 6 | no-bare-urls: false 7 | line-length: false 8 | no-emphasis-as-header: false 9 | no-duplicate-header: false 10 | header-increment: false 11 | -------------------------------------------------------------------------------- /test/e2e/fixture/lerna-and-workspaces/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "keywords": ["foo", "bar", "baz"], 5 | "devDependencies": { 6 | "lerna": "^4.0.0" 7 | }, 8 | "workspaces": [ 9 | "packages/bar" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "checkJs": true, 5 | "outDir": "types", 6 | "declaration": true, 7 | "emitDeclarationOnly": true, 8 | "strict": true, 9 | "types": ["mocha"] 10 | }, 11 | "include": ["src", "test"] 12 | } 13 | -------------------------------------------------------------------------------- /test/unexpected.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'unexpected' { 2 | 3 | interface Unexpected { 4 | (subj: any, assertion: string, ...args: any[]): Promise; 5 | clone(): Unexpected; 6 | use(plugin: any): Unexpected; 7 | it(...args: any): Unexpected; 8 | } 9 | 10 | const expect: Unexpected; 11 | export = expect; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - standard 4 | - plugin:prettier/recommended 5 | parserOptions: 6 | sourceType: script 7 | rules: 8 | standard/computed-property-even-spacing: off 9 | semi: 10 | - error 11 | - always 12 | no-extra-semi: error 13 | overrides: 14 | - files: 15 | - test/**/*.spec.js 16 | env: 17 | mocha: true 18 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - main 5 | name: release-please 6 | jobs: 7 | release-please: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: google-github-actions/release-please-action@4c5670f886fe259db4d11222f7dff41c1382304d # v3 11 | with: 12 | release-type: node 13 | package-name: sync-monorepo-packages 14 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": { 3 | ".": { 4 | "changelog-path": "CHANGELOG.md", 5 | "release-type": "node", 6 | "bump-minor-pre-major": false, 7 | "bump-patch-for-minor-pre-major": false, 8 | "draft": false, 9 | "prerelease": false 10 | } 11 | }, 12 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" 13 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const {SyncMonorepoPackagesError} = require('./error'); 2 | const { 3 | DEFAULT_FIELDS, 4 | syncPackageJsons, 5 | summarizePackageChanges, 6 | } = require('./sync-package'); 7 | const {summarizeFileCopies, syncFile} = require('./sync-file'); 8 | 9 | module.exports = { 10 | DEFAULT_FIELDS, 11 | syncFile, 12 | syncPackageJsons, 13 | summarizePackageChanges, 14 | SyncMonorepoPackagesError, 15 | summarizeFileCopies, 16 | }; 17 | -------------------------------------------------------------------------------- /src/util.js: -------------------------------------------------------------------------------- 1 | const {pipe} = require('rxjs'); 2 | const {filter} = require('rxjs/operators'); 3 | 4 | /** 5 | * @template T 6 | * @template {keyof T} K 7 | * @param {T} obj 8 | * @param {...K} keys 9 | * @returns {Pick} 10 | */ 11 | exports.pick = function pick(obj, ...keys) { 12 | const ret = /** @type {any} */ ({}); 13 | keys.forEach((key) => { 14 | ret[key] = obj[key]; 15 | }); 16 | return ret; 17 | }; 18 | 19 | /** 20 | * @template T 21 | * @returns {import('rxjs').UnaryFunction, Observable>> } 22 | */ 23 | exports.filterNullish = function filterNullish() { 24 | return pipe( 25 | /** @type {import('rxjs').OperatorFunction>} */ ( 26 | filter((x) => x != null) 27 | ) 28 | ); 29 | }; 30 | 31 | /** 32 | * @template T 33 | * @typedef {import('rxjs').Observable} Observable 34 | */ 35 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [pull_request, push] 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | build: 10 | name: Node.js v${{ matrix.node_version }} 11 | runs-on: ubuntu-latest 12 | strategy: 13 | matrix: 14 | node_version: [14, 16, 18] 15 | steps: 16 | - uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 17 | - name: Use Node.js ${{ matrix.node-version }} 18 | uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v3 19 | with: 20 | node-version: ${{ matrix.node-version }} 21 | cache: 'npm' 22 | - name: Install latest npm 23 | run: npm install -g npm 24 | - name: Install dependencies 25 | uses: bahmutov/npm-install@cb39a46f27f14697fec763d60fb23ad347e2befa # tag=v1 26 | with: 27 | useRollingCache: true 28 | - name: test 29 | run: npm test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | *.d.ts 63 | .vscode 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sync-monorepo-packages", 3 | "version": "1.0.2", 4 | "description": "Synchronize files and metadata across packages in a monorepo", 5 | "keywords": [ 6 | "lerna", 7 | "monorepo", 8 | "package", 9 | "package.json", 10 | "sync", 11 | "synchronize", 12 | "copy", 13 | "metadata", 14 | "workflow", 15 | "manifest", 16 | "workspaces" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/boneskull/sync-monorepo-packages" 21 | }, 22 | "license": "Apache-2.0", 23 | "author": "Christopher Hiller (https://boneskull.com/)", 24 | "main": "src/index.js", 25 | "types": "types/src/index.d.ts", 26 | "bin": { 27 | "sync-monorepo-packages": "src/cli.js" 28 | }, 29 | "files": [ 30 | "src", 31 | "types/src/*.d.ts" 32 | ], 33 | "scripts": { 34 | "build": "tsc", 35 | "commitlint": "commitlint", 36 | "lint": "eslint .", 37 | "lint-staged": "lint-staged", 38 | "lint:md": "markdownlint README.md", 39 | "prepare": "husky install && npm run build", 40 | "prepublishOnly": "npm run build", 41 | "prerelease": "npm test", 42 | "test": "npm run test:e2e && npm run build && npm run lint && npm run lint:md", 43 | "test:e2e": "mocha test/e2e" 44 | }, 45 | "lint-staged": { 46 | "*.js": [ 47 | "eslint --fix" 48 | ], 49 | "*.{yml,md}": [ 50 | "prettier --write" 51 | ] 52 | }, 53 | "prettier": { 54 | "bracketSpacing": false, 55 | "endOfLine": "auto", 56 | "singleQuote": true 57 | }, 58 | "dependencies": { 59 | "debug": "4.3.4", 60 | "find-up": "5.0.0", 61 | "fs-extra": "11.1.1", 62 | "globby": "10.0.2", 63 | "log-symbols": "3.0.0", 64 | "pluralize": "8.0.0", 65 | "read-pkg": "5.2.0", 66 | "rfc6902": "5.0.1", 67 | "rxjs": "7.8.1", 68 | "term-size": "2.2.1", 69 | "wrap-ansi": "7.0.0", 70 | "write-pkg": "4.0.0", 71 | "yargs": "17.7.2" 72 | }, 73 | "devDependencies": { 74 | "@commitlint/cli": "17.8.1", 75 | "@commitlint/config-conventional": "17.8.1", 76 | "@types/debug": "4.1.10", 77 | "@types/fs-extra": "9.0.13", 78 | "@types/mocha": "10.0.3", 79 | "@types/node": "18.18.6", 80 | "@types/pluralize": "0.0.32", 81 | "@types/sinon": "10.0.20", 82 | "@types/wrap-ansi": "3.0.0", 83 | "@types/yargs": "17.0.29", 84 | "eslint": "8.52.0", 85 | "eslint-config-prettier": "8.10.0", 86 | "eslint-config-standard": "17.1.0", 87 | "eslint-plugin-import": "2.29.0", 88 | "eslint-plugin-n": "15.7.0", 89 | "eslint-plugin-prettier": "4.2.1", 90 | "eslint-plugin-promise": "6.1.1", 91 | "execa": "5.1.1", 92 | "husky": "8.0.3", 93 | "lint-staged": "13.3.0", 94 | "markdownlint-cli": "0.37.0", 95 | "mocha": "10.2.0", 96 | "prettier": "2.8.8", 97 | "type-fest": "3.13.1", 98 | "typescript": "4.7.4", 99 | "unexpected": "13.2.1" 100 | }, 101 | "engines": { 102 | "node": "^14.17.0 || ^16.13.0 || >=18.0.0", 103 | "npm": ">7" 104 | }, 105 | "overrides": { 106 | "type-fest": "3.13.1" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/model.js: -------------------------------------------------------------------------------- 1 | const {inspect} = require('util'); 2 | 3 | /** 4 | * Represents the result of a file-copy operation. Do not use directly; use {@linkcode createFileCopyResult} instead. 5 | */ 6 | class FileCopyResult { 7 | /** 8 | * @param {string} from - Source filepath 9 | * @param {string} to - Destination filepath 10 | * @param {FileCopyResultOpts} [opts] - Options 11 | */ 12 | constructor(from, to, {err, success} = {}) { 13 | this.from = from; 14 | this.to = to; 15 | this.err = err; 16 | this.success = success; 17 | } 18 | 19 | toString() { 20 | return this.err 21 | ? `Could not synchronize file from ${this.from} to ${this.to}: ${this.err.message}` 22 | : `Synchronized file ${this.from} to ${this.to}`; 23 | } 24 | 25 | /** 26 | * Return a clone of this object, but add an Error 27 | * @param {Error} err - Error 28 | */ 29 | withError(err) { 30 | return Object.freeze(new FileCopyResult(this.from, this.to, {err})); 31 | } 32 | 33 | /** 34 | * Return a clone of thios object, but mark as successfully copied 35 | */ 36 | withSuccess() { 37 | return Object.freeze( 38 | new FileCopyResult(this.from, this.to, {success: true}) 39 | ); 40 | } 41 | } 42 | 43 | /** 44 | * Represents the result of a `package.json` modification 45 | */ 46 | class PkgChangeResult { 47 | /** 48 | * 49 | * @param {string} pkgPath - Path to destination package.json 50 | * @param {Operation[]} patch - JSON patch 51 | * @param {PackageJson} pkg - Original package.json 52 | * @param {PackageJson} [newPkg] - Updated package.json 53 | */ 54 | constructor(pkgPath, patch, pkg, newPkg) { 55 | this.pkgPath = pkgPath; 56 | this.patch = patch; 57 | this.pkg = pkg; 58 | this.newPkg = newPkg; 59 | } 60 | 61 | toString() { 62 | return `${this.pkgPath} - Applied patch: ${inspect(this.patch, { 63 | colors: true, 64 | compact: true, 65 | breakLength: Infinity, 66 | })}`; 67 | } 68 | 69 | /** 70 | * 71 | * @param {PackageJson} newPkg 72 | * @returns {Readonly} 73 | */ 74 | withNewPackage(newPkg) { 75 | return Object.freeze( 76 | new PkgChangeResult(this.pkgPath, [...this.patch], {...this.pkg}, newPkg) 77 | ); 78 | } 79 | } 80 | 81 | /** 82 | * Creates a {@linkcode FileCopyResult} object. 83 | * @param {string} from 84 | * @param {string} to 85 | * @param {FileCopyResultOpts} [opts] 86 | * @returns {Readonly} 87 | */ 88 | exports.createFileCopyResult = (from, to, {err, success} = {}) => 89 | Object.freeze(new FileCopyResult(from, to, {err, success})); 90 | 91 | /** 92 | * Creates a {@linkcode PkgChangeResult} object. 93 | * @param {string} pkgPath 94 | * @param {Operation[]} patch 95 | * @param {PackageJson} pkg 96 | * @param {PackageJson} [newPkg] 97 | */ 98 | exports.createPkgChangeResult = (pkgPath, patch, pkg, newPkg) => 99 | Object.freeze(new PkgChangeResult(pkgPath, patch, pkg, newPkg)); 100 | 101 | /** 102 | * @typedef {import('type-fest').PackageJson} PackageJson 103 | * @typedef {import('rfc6902').Operation} Operation 104 | */ 105 | 106 | /** 107 | * @typedef FileCopyResultOpts 108 | * @property {Error} [err] 109 | * @property {boolean} [success] 110 | */ 111 | 112 | exports.FileCopyResult = FileCopyResult; 113 | exports.PkgChangeResult = PkgChangeResult; 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sync-monorepo-packages 2 | 3 | > Synchronizes `package.json` fields and arbitrary files in a monorepo 4 | 5 | ## Features 6 | 7 | - Auto-discovery of packages via `package.json` workspaces and/or `lerna.json` 8 | - Optional manual control of destination packages 9 | - Helpful defaults 10 | - Detailed "dry run" mode 11 | - Summary of operations 12 | - Sync arbitrary files (e.g. `README.md`) 13 | 14 | ## Install 15 | 16 | **Requires Node.js `^14.17.0 || ^16.13.0 || >=18.0.0`** 17 | 18 | ```shell 19 | npm install sync-monorepo-packages --save-dev 20 | ``` 21 | 22 | _or_ 23 | 24 | ```shell 25 | $ npx sync-monorepo-packages --help 26 | ``` 27 | 28 | ## Usage 29 | 30 | Here, I have pasted the output of `--help` because I am lazy: 31 | 32 | ```plain 33 | sync-monorepo-packages [file..] 34 | 35 | Synchronize files and metadata across packages in a monorepo 36 | 37 | Positionals: 38 | file One or more source files to sync [string] 39 | 40 | Options: 41 | --help Show help [boolean] 42 | --version Show version number [boolean] 43 | -D, --dry-run Do not sync; print what would have changed (implies 44 | --verbose) [boolean] 45 | -f, --field, --fields Fields in source package.json to sync [array] [default: 46 | ["keywords","author","repository","license","engines","publishConfig"]] 47 | --force Overwrite destination file(s) [boolean] 48 | -p, --packages Dirs/globs containing destination packages 49 | [array] [default: (use workspaces and/or lerna.json)] 50 | --package-json Sync package.json [boolean] [default: true] 51 | -s, --source Path to source package.json 52 | [string] [default: (closest package.json)] 53 | -v, --verbose Print change details [boolean] 54 | --summary Print summary [boolean] [default: true] 55 | -l, --lerna Path to lerna.json, if any 56 | [string] [default: (lerna.json in current dir)] 57 | 58 | Examples: 59 | sync-monorepo-packages --field keywords Sync "keywords" and "author" from 60 | --field author -s ./foo/package.json ./foo/package.json to packages found 61 | in lerna.json 62 | sync-monorepo-packages --packages ./foo Using default fields, show what 63 | --dry-run --no-summary would have synced from package.json 64 | in current dir to packages in ./foo; 65 | hide summary 66 | sync-monorepo-packages --no-package-json Sync ./README.md to each package 67 | ./README.md found in lerna.json. Do not sync 68 | anything in package.json 69 | 70 | Found a bug? Report it at https://github.com/boneskull/sync-monorepo-packages 71 | ``` 72 | 73 | ## Notes 74 | 75 | - If there are other fields which would make sense to copy as a default, please suggest! 76 | - Use at your own risk! `--dry-run` is your friend 77 | - When copying files, directories may be created relative to the dirpath of `lerna.json` or `package.json`. For example, if you want to sync `foo/bar.md` to each package, `packages/*/foo/bar.md` will be the result. This may not work properly with explicitly-specified package directories! Use from project root to be sure. 78 | - There is an API that you can use. Go for it! 79 | 80 | ## License 81 | 82 | Copyright © 2019 Christopher Hiller. Licensed Apache-2.0 83 | -------------------------------------------------------------------------------- /test/e2e/behavior.spec.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | const expect = require('unexpected'); 4 | const execa = require('execa'); 5 | const os = require('os'); 6 | const fs = require('fs-extra'); 7 | const path = require('path'); 8 | 9 | const EXECUTABLE_PATH = require.resolve('../../src/cli.js'); 10 | 11 | /** 12 | * 13 | * @param {string[]} [args] 14 | * @param {execa.NodeOptions} [opts] 15 | * @returns {Promise} 16 | */ 17 | async function run(args = [], opts = {}) { 18 | return execa.node(EXECUTABLE_PATH, args, opts); 19 | } 20 | 21 | const FIXTURES = /** @type {const} */ ({ 22 | 'with Lerna': path.join(__dirname, 'fixture', 'lerna'), 23 | 'with workspaces': path.join(__dirname, 'fixture', 'workspaces'), 24 | 'with both Lerna and workspaces': path.join( 25 | __dirname, 26 | 'fixture', 27 | 'lerna-and-workspaces' 28 | ), 29 | }); 30 | 31 | describe('sync-monorepo-packages', function () { 32 | /** @type {import('type-fest').PackageJson} */ 33 | let pkgJson; 34 | 35 | before(async function () { 36 | pkgJson = await fs.readJson( 37 | path.join(__dirname, '..', '..', 'package.json') 38 | ); 39 | }); 40 | 41 | describe('--help', function () { 42 | it('should help', async function () { 43 | this.timeout(5000); 44 | this.slow(2500); 45 | 46 | return expect(run(['--help']), 'to be fulfilled with value satisfying', { 47 | stdout: new RegExp(/** @type {string} */ (pkgJson.description)), 48 | }); 49 | }); 50 | }); 51 | 52 | for (const [title, fixturePath] of Object.entries(FIXTURES)) { 53 | describe(title, function () { 54 | /** 55 | * @type {string} 56 | */ 57 | let tempDir; 58 | 59 | beforeEach(async function () { 60 | tempDir = path.join( 61 | await fs.mkdtemp(path.join(os.tmpdir(), 'sync-monorepo-packages-')), 62 | path.basename(fixturePath) 63 | ); 64 | await fs.copy(fixturePath, tempDir, { 65 | recursive: true, 66 | }); 67 | }); 68 | 69 | afterEach(async function () { 70 | await fs.remove(tempDir); 71 | }); 72 | 73 | describe('default behavior', function () { 74 | /** @type {import('execa').ExecaReturnValue} */ 75 | let result; 76 | 77 | beforeEach(async function () { 78 | result = await run([], {cwd: tempDir}); 79 | await expect(result, 'to satisfy', { 80 | exitCode: 0, 81 | }); 82 | }); 83 | 84 | it('should report that package.json files were synced', function () { 85 | expect(result, 'to satisfy', { 86 | stdout: /synced 2 package.json files/i, 87 | }); 88 | }); 89 | 90 | it('should actually sync the package.json files', async function () { 91 | const [monorepoJson, barJson, fooJson] = await Promise.all([ 92 | fs.readJson(path.join(tempDir, 'package.json')), 93 | fs.readJson(path.join(tempDir, 'packages', 'bar', 'package.json')), 94 | fs.readJson(path.join(tempDir, 'packages', 'foo', 'package.json')), 95 | ]); 96 | expect(barJson, 'to equal', fooJson); 97 | expect(barJson, 'to have property', 'keywords'); 98 | expect(monorepoJson.keywords, 'to equal', barJson.keywords); 99 | }); 100 | }); 101 | 102 | describe('--no-package-json', function () { 103 | /** @type {import('execa').ExecaReturnValue} */ 104 | let result; 105 | 106 | beforeEach(async function () { 107 | result = await run(['--no-package-json', 'LICENSE'], { 108 | cwd: tempDir, 109 | }); 110 | await expect(result, 'to satisfy', {exitCode: 0}); 111 | }); 112 | it('should not sync package.json files', async function () { 113 | const [barJson, fooJson] = await Promise.all([ 114 | fs.readJson(path.join(tempDir, 'packages', 'bar', 'package.json')), 115 | fs.readJson(path.join(tempDir, 'packages', 'foo', 'package.json')), 116 | ]); 117 | expect({...fooJson, ...barJson}, 'to be empty'); 118 | }); 119 | it('should copy files', async function () { 120 | console.dir(result); 121 | await expect( 122 | Promise.all([ 123 | fs.stat(path.join(tempDir, 'packages', 'foo', 'LICENSE')), 124 | fs.stat(path.join(tempDir, 'packages', 'bar', 'LICENSE')), 125 | ]), 126 | 'to be fulfilled' 127 | ); 128 | }); 129 | }); 130 | }); 131 | } 132 | }); 133 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [1.0.2](https://github.com/boneskull/sync-monorepo-packages/compare/v1.0.1...v1.0.2) (2022-12-14) 6 | 7 | 8 | ### Bug Fixes 9 | 10 | * **deps:** update dependency rxjs to v7.6.0 ([1d57275](https://github.com/boneskull/sync-monorepo-packages/commit/1d57275bcfb17619ebb51cfb064a99f00e545e77)) 11 | * **workspaces:** fix sync file behavior for workspaces ([cd0aab8](https://github.com/boneskull/sync-monorepo-packages/commit/cd0aab8cca2c2719cca2ada500239579286b0bec)) 12 | 13 | ## [1.0.1](https://github.com/boneskull/sync-monorepo-packages/compare/v1.0.0...v1.0.1) (2022-11-30) 14 | 15 | 16 | ### Bug Fixes 17 | 18 | * **deps:** update dependency fs-extra to v11.1.0 ([a2b6078](https://github.com/boneskull/sync-monorepo-packages/commit/a2b6078bdb46c32fc99d9f3c371c4f1a87fdf204)) 19 | 20 | ## [1.0.0](https://github.com/boneskull/sync-monorepo-packages/compare/v0.3.5...v1.0.0) (2022-11-30) 21 | 22 | 23 | ### ⚠ BREAKING CHANGES 24 | 25 | * This **changes the default behavior** of `sync-monorepo-packages` to first inspect the `workspaces` field of `package.json` to find target packages. 26 | * npm v7 or newer required 27 | * Supported Node.js versions are now `^14.17.0 || ^16.13.0 || >=18.0.0` 28 | 29 | ### Features 30 | 31 | * add workspace support ([6df68b4](https://github.com/boneskull/sync-monorepo-packages/commit/6df68b44410bdc693c276486e84f888126bd42aa)) 32 | 33 | 34 | ### Miscellaneous Chores 35 | 36 | * drop Node.js v12 support ([5a0b59a](https://github.com/boneskull/sync-monorepo-packages/commit/5a0b59ac1bee92c8d538897d23c39ce3af8779bf)) 37 | * require npm v7 ([b28fd76](https://github.com/boneskull/sync-monorepo-packages/commit/b28fd766c1dca7f87c721e3273c1260a18173e91)) 38 | 39 | ### [0.3.5](https://github.com/boneskull/sync-monorepo-packages/compare/v0.3.4...v0.3.5) (2022-04-18) 40 | 41 | ### Bug Fixes 42 | 43 | - **util:** fix bad import ([da44c27](https://github.com/boneskull/sync-monorepo-packages/commit/da44c27bfadedcc9d66e47535435a6b91adc893e)) 44 | - update some deps per "npm audit" [security] ([3a8139f](https://github.com/boneskull/sync-monorepo-packages/commit/3a8139ff677667ced37a9d3b9366fb2a2560c1a0)) 45 | 46 | ### [0.3.4](https://github.com/boneskull/sync-monorepo-packages/compare/v0.3.3...v0.3.4) (2021-06-15) 47 | 48 | ### Bug Fixes 49 | 50 | - **build:** typescript crap ([f2682fe](https://github.com/boneskull/sync-monorepo-packages/commit/f2682fe497774576f14daebb6c055852fe93d3a1)) 51 | 52 | ### [0.3.3](https://github.com/boneskull/sync-monorepo-packages/compare/v0.3.2...v0.3.3) (2021-06-15) 53 | 54 | ### Bug Fixes 55 | 56 | - **pkg:** dep upgrades ([d73a236](https://github.com/boneskull/sync-monorepo-packages/commit/d73a23696331d328f5f10ab59022dc37febbeaeb)) 57 | - support for comma-separated fields ([e818bbf](https://github.com/boneskull/sync-monorepo-packages/commit/e818bbfca226b502a3d01148c4dcd8b89b751604)) 58 | 59 | ### [0.3.2](https://github.com/boneskull/sync-monorepo-packages/compare/v0.3.1...v0.3.2) (2020-01-30) 60 | 61 | ### Bug Fixes 62 | 63 | - fix file copying problems and observable issues ([7b3042d](https://github.com/boneskull/sync-monorepo-packages/commit/7b3042d54bb150cdb8954d5dfd2ed51cb49b4201)) 64 | 65 | ### [0.3.1](https://github.com/boneskull/sync-monorepo-packages/compare/v0.3.0...v0.3.1) (2019-12-03) 66 | 67 | ### Bug Fixes 68 | 69 | - vuln updates ([1913aa2](https://github.com/boneskull/sync-monorepo-packages/commit/1913aa2da41ba6b72448f73da85b0fd4c515ea35)) 70 | 71 | ## [0.3.0](https://github.com/boneskull/sync-monorepo-packages/compare/v0.2.0...v0.3.0) (2019-11-02) 72 | 73 | ### Features 74 | 75 | - **pkg:** provide typescript definitions ([889b6bf](https://github.com/boneskull/sync-monorepo-packages/commit/889b6bf82baa45dc3b71a89b04c3ef1148bb594a)) 76 | 77 | ## [0.2.0](https://github.com/boneskull/sync-monorepo-packages/compare/v0.1.1...v0.2.0) (2019-10-30) 78 | 79 | ### Features 80 | 81 | - improved efficiency, many fixes and output enhancements ([9b47fbe](https://github.com/boneskull/sync-monorepo-packages/commit/9b47fbe29e5d0223ff84f657300aa4bbafa737cd)) 82 | 83 | ### [0.1.1](https://github.com/boneskull/sync-monorepo-packages/compare/v0.1.0...v0.1.1) (2019-10-29) 84 | 85 | ### Features 86 | 87 | - add ability to sync files ([df4263c](https://github.com/boneskull/sync-monorepo-packages/commit/df4263cf697ac178eb38bfe352f51da3a5516379)) 88 | 89 | ### Bug Fixes 90 | 91 | - many bugs ([9208755](https://github.com/boneskull/sync-monorepo-packages/commit/920875509c4b316b51c9682d6eafa815c399037b)) 92 | 93 | ## [0.1.0](https://github.com/boneskull/sync-monorepo-packages/compare/v0.0.3...v0.1.0) (2019-10-23) 94 | 95 | ### Features 96 | 97 | - add publishConfig to defaults; fix application problems ([41b664f](https://github.com/boneskull/sync-monorepo-packages/commit/41b664f554a61b79f645e203772b9b1d765b601a)) 98 | 99 | ### Bug Fixes 100 | 101 | - turn off dry-run on everything ([3e7af3a](https://github.com/boneskull/sync-monorepo-packages/commit/3e7af3ac2bd4716c287108d8aaa44d292bacb84f)) 102 | 103 | ### [0.0.3](https://github.com/boneskull/sync-monorepo-packages/compare/v0.0.2...v0.0.3) (2019-10-22) 104 | 105 | ### Bug Fixes 106 | 107 | - attempt to fix changelog issues upon release ([bec9038](https://github.com/boneskull/sync-monorepo-packages/commit/bec903859b0a7291468813d0c76987018600e5e3)) 108 | 109 | ### [0.0.2](https://github.com/boneskull/sync-monorepo-packages/compare/v0.0.1...v0.0.2) (2019-10-22) 110 | 111 | ### Bug Fixes 112 | 113 | - remove cruft from package ([2c3da9f](https://github.com/boneskull/sync-monorepo-packages/commit/2c3da9f1085b338c3199e5cc5c98923cb293f2b2)) 114 | 115 | ### 0.0.1 (2019-10-22) 116 | 117 | ### Features 118 | 119 | - initial commit ([85dd44c](https://github.com/boneskull/sync-monorepo-packages/commit/85dd44ce3cbf7ac40f82400a89ad3b45295b9e7d)) 120 | -------------------------------------------------------------------------------- /src/find-package.js: -------------------------------------------------------------------------------- 1 | const findUp = require('find-up'); 2 | const glob = require('globby'); 3 | const {defer, from, iif, of} = require('rxjs'); 4 | const { 5 | filter, 6 | map, 7 | mergeAll, 8 | mergeMap, 9 | tap, 10 | distinct, 11 | mergeWith, 12 | } = require('rxjs/operators'); 13 | const debug = require('debug')('sync-monorepo-packages:find-package'); 14 | const fs = require('fs-extra'); 15 | const path = require('path'); 16 | 17 | const PACKAGE_JSON = 'package.json'; 18 | const LERNA_JSON = 'lerna.json'; 19 | 20 | /** 21 | * @param {string} cwd 22 | * @returns {Observable} 23 | */ 24 | function findWorkspaces(cwd = process.cwd()) { 25 | debug('Finding workspaces from %s', cwd); 26 | return from(findUp(PACKAGE_JSON, {cwd})).pipe( 27 | filter(Boolean), 28 | mergeMap((pkgPath) => from(fs.readJSON(pkgPath))), 29 | map( 30 | /** 31 | */ (pkg) => pkg.workspaces ?? [] 32 | ), 33 | tap((value) => { 34 | debug('Found workspaces in %s: %s', PACKAGE_JSON, value); 35 | }) 36 | ); 37 | } 38 | 39 | /** 40 | * Finds a Lerna config file (lerna.json) 41 | * @param {FindLernaConfigOptions} [opts] 42 | * @returns {Observable} 43 | */ 44 | function findLernaConfig({cwd = process.cwd(), lernaJsonPath} = {}) { 45 | return iif( 46 | () => findLernaConfig.cache.has(`${cwd}:${lernaJsonPath}`), 47 | defer(() => of(findLernaConfig.cache.get(`${cwd}:${lernaJsonPath}`))), 48 | iif( 49 | () => Boolean(lernaJsonPath), 50 | of(/** @type {string} */ (lernaJsonPath)), 51 | from(findUp(LERNA_JSON, {cwd})) 52 | ).pipe( 53 | filter(Boolean), 54 | mergeMap((lernaConfigPath) => 55 | from(fs.readJSON(lernaConfigPath)).pipe( 56 | map( 57 | /** 58 | * @param {LernaJson} lernaConfig 59 | */ (lernaConfig) => ({ 60 | lernaConfig, 61 | lernaRoot: path.dirname(lernaConfigPath), 62 | }) 63 | ) 64 | ) 65 | ), 66 | tap((lernaInfo) => { 67 | debug( 68 | 'caching LernaInfo w/ key "%s:%s": %O', 69 | cwd, 70 | lernaJsonPath, 71 | lernaInfo 72 | ); 73 | findLernaConfig.cache.set(`${cwd}:${lernaJsonPath}`, lernaInfo); 74 | }) 75 | ) 76 | ); 77 | } 78 | findLernaConfig.cache = new Map(); 79 | 80 | /** 81 | * Finds one or more directories specified by `globs` 82 | * @param {string[]} globs - One ore more dirs or globs to dirs 83 | * @param {FindByGlobsOptions} [opts] 84 | */ 85 | function findDirectoriesByGlobs(globs, {cwd = process.cwd()} = {}) { 86 | return from( 87 | glob(globs, { 88 | cwd, 89 | onlyDirectories: true, 90 | expandDirectories: false, 91 | }) 92 | ).pipe(mergeAll()); 93 | } 94 | 95 | /** 96 | * Finds package.json files within one or more directories specified by `globs` 97 | * @param {string[]} globs - One ore more dirs or globs to dirs 98 | * @param {FindByGlobsOptions} [opts] 99 | */ 100 | function findPackageJsonsByGlobs(globs, {cwd = process.cwd()} = {}) { 101 | return findDirectoriesByGlobs(globs, {cwd}).pipe( 102 | mergeMap((dir) => from(glob(path.join(dir, PACKAGE_JSON)))), 103 | mergeAll(), 104 | tap((pkgPath) => { 105 | debug('Found package.json at %s', PACKAGE_JSON, pkgPath); 106 | }) 107 | ); 108 | } 109 | 110 | /** 111 | * Finds package.json files within packages as defined in a `lerna.json` file 112 | * @param {FindPackageJsonsFromLernaConfig} [opts] - Current working directory and path to lerna.json 113 | */ 114 | function findPackageJsonsFromLernaConfig({ 115 | cwd = process.cwd(), 116 | lernaJsonPath = '', 117 | sourcePkgPath = '', 118 | } = {}) { 119 | return findLernaConfig({lernaJsonPath, cwd}).pipe( 120 | filter(({lernaConfig}) => Boolean(lernaConfig.packages?.length)), 121 | mergeMap(({lernaRoot, lernaConfig}) => 122 | findPackageJsonsByGlobs(lernaConfig.packages, {cwd: lernaRoot}) 123 | ), 124 | filter((pkgPath) => pkgPath !== sourcePkgPath) 125 | ); 126 | } 127 | 128 | /** 129 | * Returns an Observable of paths to `package.json` files 130 | * @param {FindPackageJsonsOptions} opts 131 | */ 132 | function findPackageJsons({ 133 | packages: packageDirs = [], 134 | cwd = process.cwd(), 135 | lernaJsonPath = '', 136 | sourcePkgPath = '', 137 | } = {}) { 138 | return iif( 139 | () => Boolean(packageDirs.length), 140 | findPackageJsonsByGlobs(packageDirs, {cwd}), 141 | findWorkspaces(sourcePkgPath || cwd).pipe( 142 | mergeMap((workspaces) => findPackageJsonsByGlobs(workspaces, {cwd})), 143 | mergeWith( 144 | findPackageJsonsFromLernaConfig({cwd, lernaJsonPath, sourcePkgPath}) 145 | ), 146 | distinct() 147 | ) 148 | ); 149 | } 150 | 151 | exports.findWorkspaces = findWorkspaces; 152 | exports.findLernaConfig = findLernaConfig; 153 | exports.findDirectoriesByGlobs = findDirectoriesByGlobs; 154 | exports.findPackageJsonsByGlobs = findPackageJsonsByGlobs; 155 | exports.findPackageJsons = findPackageJsons; 156 | exports.PACKAGE_JSON = PACKAGE_JSON; 157 | 158 | /** 159 | * @typedef FindPackageJsonsFromLernaConfig 160 | * @property {string[]} [packageDirs] 161 | * @property {string} [cwd] 162 | * @property {string} [lernaJsonPath] 163 | * @property {string} [sourcePkgPath] 164 | */ 165 | 166 | /** 167 | * @typedef LernaInfo 168 | * @property {string} lernaRoot 169 | * @property {LernaJson} lernaConfig 170 | */ 171 | 172 | /** 173 | * @typedef FindLernaConfigOptions 174 | * @property {string} [cwd] 175 | * @property {string} [lernaJsonPath] 176 | */ 177 | 178 | /** 179 | * @typedef FindByGlobsOptions 180 | * @property {string} [cwd] 181 | */ 182 | 183 | /** 184 | * @template T 185 | * @typedef {import('rxjs').Observable} Observable 186 | */ 187 | 188 | /** 189 | * @typedef LernaJson 190 | * @property {string[]} packages - Where Lerna finds packages 191 | */ 192 | 193 | /** 194 | * @typedef FindPackageJsonsOptions 195 | * @property {string[]} [packages] 196 | * @property {string} [cwd] 197 | * @property {string} [lernaJsonPath] 198 | * @property {string} [sourcePkgPath] 199 | */ 200 | -------------------------------------------------------------------------------- /src/sync-package.js: -------------------------------------------------------------------------------- 1 | const pluralize = require('pluralize'); 2 | const {defer, from, iif, of} = require('rxjs'); 3 | const {applyPatch, createPatch} = require('rfc6902'); 4 | const {pick, filterNullish} = require('./util'); 5 | const {count, share, map, mapTo, mergeMap, tap} = require('rxjs/operators'); 6 | const findUp = require('find-up'); 7 | const path = require('path'); 8 | const readPkg = require('read-pkg'); 9 | const writePkg = require('write-pkg'); 10 | const debug = require('debug')('sync-monorepo-packages:sync-package'); 11 | const {findPackageJsons, PACKAGE_JSON} = require('./find-package'); 12 | const {createPkgChangeResult} = require('./model'); 13 | 14 | /** 15 | * These are the default fields which this program will sync from the 16 | * monorepo root `package.json` to its sub-packages. 17 | */ 18 | exports.DEFAULT_FIELDS = Object.freeze( 19 | /** @type {const} */ ([ 20 | 'keywords', 21 | 'author', 22 | 'repository', 23 | 'license', 24 | 'engines', 25 | 'publishConfig', 26 | ]) 27 | ); 28 | 29 | /** 30 | * Reads a `package.json` 31 | * @returns {OperatorFunction>} 32 | */ 33 | function readPackageJson() { 34 | return (pkgJsonPath$) => 35 | pkgJsonPath$.pipe( 36 | mergeMap((pkgJsonPath) => 37 | from(readPkg({cwd: path.dirname(pkgJsonPath), normalize: false})).pipe( 38 | map((pkg) => 39 | Object.freeze({ 40 | pkgPath: pkgJsonPath, 41 | pkg, 42 | }) 43 | ) 44 | ) 45 | ) 46 | ); 47 | } 48 | 49 | /** 50 | * Finds any fields in a source Observable of {@link PackageJson} objects 51 | * not matching the corresponding field in the `sourcePkg$` Observable. 52 | * @param {Observable} sourcePkg$ 53 | * @param {(keyof PackageJson)[]} fields 54 | * @returns {OperatorFunction} 55 | */ 56 | function findChanges(sourcePkg$, fields) { 57 | return (pkgInfo$) => 58 | pkgInfo$.pipe( 59 | mergeMap((pkgInfo) => { 60 | const {pkg, pkgPath} = pkgInfo; 61 | // only compare the interesting fields! 62 | const pkgFields = pick(pkg, ...fields); 63 | return sourcePkg$.pipe( 64 | map((sourcePkg) => { 65 | const srcPkgProps = pick(sourcePkg, ...fields); 66 | const patch = createPatch(pkgFields, srcPkgProps); 67 | if (patch.length) { 68 | return createPkgChangeResult(pkgPath, patch, pkg); 69 | } 70 | }) 71 | ); 72 | }), 73 | filterNullish() 74 | ); 75 | } 76 | 77 | /** 78 | * Applies changes to a package.json 79 | * @todo this is "not idiomatic"; somebody fix this 80 | * @returns {MonoTypeOperatorFunction} 81 | */ 82 | function applyChanges(dryRun = false) { 83 | return (observable) => 84 | observable.pipe( 85 | map((pkgChange) => { 86 | const newPkg = {...pkgChange.pkg}; 87 | // NOTE: applyPatch _mutates_ newPkg 88 | applyPatch(newPkg, pkgChange.patch); 89 | return pkgChange.withNewPackage(newPkg); 90 | }), 91 | mergeMap((pkgChange) => 92 | iif( 93 | () => dryRun, 94 | of(pkgChange), 95 | defer(() => 96 | from( 97 | writePkg( 98 | pkgChange.pkgPath, 99 | /** 100 | * @type {import('type-fest').JsonObject} 101 | */ (pkgChange.newPkg), 102 | {normalize: false} 103 | ) 104 | ) 105 | ).pipe(mapTo(pkgChange)) 106 | ) 107 | ) 108 | ); 109 | } 110 | 111 | /** 112 | * Inputs changes and outputs summaries of what happened 113 | * @returns {OperatorFunction,Summary>} 114 | */ 115 | exports.summarizePackageChanges = () => (pkgChange$) => 116 | pkgChange$.pipe( 117 | filterNullish(), 118 | count(), 119 | map((count) => { 120 | if (count) { 121 | return { 122 | success: `Synced ${count} package.json ${pluralize('file', count)}`, 123 | }; 124 | } 125 | return {noop: `No package.json changes needed; everything up-to-date!`}; 126 | }) 127 | ); 128 | 129 | /** 130 | * Strip 'package.json' from a path to get the dirname; to be handed 131 | * to `read-pkg` 132 | * @param {string} pkgPath - User-supplied package path to normalize 133 | */ 134 | function normalizePkgPath(pkgPath) { 135 | return path.basename(pkgPath) === PACKAGE_JSON 136 | ? path.dirname(pkgPath) 137 | : pkgPath; 138 | } 139 | 140 | /** 141 | * Given a source package.json and a list of package directories, sync fields from source to destination(s) 142 | * @param {Partial} [opts] 143 | */ 144 | exports.syncPackageJsons = ({ 145 | sourcePkgPath = '', 146 | packages = [], 147 | dryRun = false, 148 | fields = [], 149 | lerna: lernaJsonPath = '', 150 | } = {}) => { 151 | if (sourcePkgPath && path.basename(sourcePkgPath) !== PACKAGE_JSON) { 152 | sourcePkgPath = path.join(sourcePkgPath, PACKAGE_JSON); 153 | } 154 | 155 | const sourcePkg$ = iif( 156 | () => Boolean(sourcePkgPath), 157 | of(sourcePkgPath), 158 | defer(() => 159 | from(findUp(PACKAGE_JSON)).pipe( 160 | tap((pkgJsonPath) => { 161 | debug('found source package.json at %s', pkgJsonPath); 162 | }), 163 | filterNullish() 164 | ) 165 | ) 166 | ).pipe( 167 | mergeMap((sourcePkgPath) => 168 | from( 169 | readPkg({ 170 | cwd: normalizePkgPath(sourcePkgPath), 171 | normalize: false, 172 | }) 173 | ) 174 | ), 175 | share() 176 | ); 177 | 178 | // get changes 179 | const changes$ = findPackageJsons({ 180 | lernaJsonPath, 181 | packages, 182 | sourcePkgPath, 183 | }).pipe(readPackageJson(), findChanges(sourcePkg$, fields)); 184 | 185 | // decide if we should apply them 186 | return changes$.pipe(applyChanges(dryRun)); 187 | }; 188 | 189 | /** 190 | * @template T 191 | * @typedef {import('rxjs').MonoTypeOperatorFunction} MonoTypeOperatorFunction 192 | */ 193 | 194 | /** 195 | * @template T 196 | * @typedef {import('rxjs').Observable} Observable 197 | */ 198 | 199 | /** 200 | * @typedef {import('type-fest').PackageJson} PackageJson 201 | */ 202 | 203 | /** 204 | * @template T,U 205 | * @typedef {import('rxjs').OperatorFunction} OperatorFunction 206 | */ 207 | 208 | /** 209 | * @typedef SyncPackageJsonsOptions 210 | * @property {string} sourcePkgPath - Path to source package.json 211 | * @property {string[]} packages - Where to find packages; otherwise use Lerna 212 | * @property {boolean} dryRun - If `true`, print changes and exit 213 | * @property {(keyof PackageJson)[]} fields - Fields to copy 214 | * @property {string} lerna - Path to lerna.json 215 | */ 216 | 217 | /** 218 | * @typedef PackageInfo 219 | * @property {import('type-fest').PackageJson} pkg - Package json for package 220 | * @property {string} pkgPath - Path to package 221 | */ 222 | 223 | /** 224 | * @typedef {import('./sync-file').Summary} Summary 225 | */ 226 | -------------------------------------------------------------------------------- /src/sync-file.js: -------------------------------------------------------------------------------- 1 | const pluralize = require('pluralize'); 2 | const {defer, merge, from, iif, of} = require('rxjs'); 3 | 4 | const fs = require('fs-extra'); 5 | const { 6 | catchError, 7 | defaultIfEmpty, 8 | concatMap, 9 | throwIfEmpty, 10 | filter, 11 | reduce, 12 | toArray, 13 | share, 14 | map, 15 | mergeAll, 16 | mergeMap, 17 | mergeWith, 18 | } = require('rxjs/operators'); 19 | const path = require('path'); 20 | const glob = require('globby'); 21 | const debug = require('debug')('sync-monorepo-packages:sync-file'); 22 | const {createFileCopyResult} = require('./model'); 23 | const {SyncMonorepoPackagesError} = require('./error'); 24 | const { 25 | findLernaConfig, 26 | findDirectoriesByGlobs, 27 | findWorkspaces, 28 | } = require('./find-package'); 29 | 30 | /** 31 | * For dry-run mode, if a file were to be copied, but force is 32 | * false, we should throw. 33 | * @param {FileCopyResult} copyInfo 34 | * @param {boolean} [force] 35 | */ 36 | function dryRunTestFile(copyInfo, force = false) { 37 | return iif( 38 | () => force, 39 | of(copyInfo), 40 | defer(() => from(fs.stat(copyInfo.to))).pipe( 41 | map(() => { 42 | /** 43 | * @type {any} 44 | */ 45 | const err = new Error(); 46 | err.code = 'EEXIST'; 47 | throw err; 48 | }), 49 | catchError((err) => { 50 | if (err.code === 'ENOENT') { 51 | return of(copyInfo); 52 | } 53 | throw err; 54 | }) 55 | ) 56 | ); 57 | } 58 | 59 | /** 60 | * Provide a summary of the file copies made 61 | * @returns {OperatorFunction,Summary>} 62 | */ 63 | exports.summarizeFileCopies = () => (copyInfo$) => { 64 | /** 65 | * @returns {OperatorFunction,{totalCopies: number, allSources: Set}>} 66 | */ 67 | const summary = () => (copyInfo$) => 68 | copyInfo$.pipe( 69 | reduce( 70 | ({totalCopies, allSources}, {from}) => ({ 71 | totalCopies: totalCopies + 1, 72 | allSources: allSources.add(from), 73 | }), 74 | {totalCopies: 0, allSources: new Set()} 75 | ), 76 | filter(({totalCopies}) => totalCopies > 0) 77 | ); 78 | 79 | copyInfo$ = copyInfo$.pipe(share()); 80 | const success$ = copyInfo$.pipe( 81 | filter((copyInfo) => Boolean(copyInfo.success)), 82 | summary(), 83 | map(({totalCopies, allSources}) => ({ 84 | success: `Copied ${allSources.size} ${pluralize( 85 | 'file', 86 | allSources.size 87 | )} to ${totalCopies} ${pluralize('package', totalCopies)}`, 88 | })) 89 | ); 90 | 91 | const fail$ = copyInfo$.pipe( 92 | filter((copyInfo) => Boolean(copyInfo.err)), 93 | summary(), 94 | map(({totalCopies, allSources}) => ({ 95 | fail: `Failed to copy ${allSources.size} ${pluralize( 96 | 'file', 97 | allSources.size 98 | )} to ${totalCopies} ${pluralize( 99 | 'package', 100 | totalCopies 101 | )}; use --verbose for details`, 102 | })) 103 | ); 104 | return merge(success$, fail$).pipe( 105 | defaultIfEmpty( 106 | /** 107 | * @type {Summary} 108 | */ 109 | ({noop: 'No files copied.'}) 110 | ) 111 | ); 112 | }; 113 | 114 | /** 115 | * Synchronize source file(s) to packages 116 | * @param {string[]} [files] - Source file(s) 117 | * @param {Partial} [opts] 118 | */ 119 | exports.syncFile = ( 120 | files = [], 121 | { 122 | packages = [], 123 | dryRun = false, 124 | lerna: lernaJsonPath = '', 125 | force = false, 126 | cwd = process.cwd(), 127 | } = {} 128 | ) => { 129 | debug( 130 | 'syncFile called with force: %s, packages: %O, files: %O', 131 | force, 132 | packages, 133 | files 134 | ); 135 | const file$ = from(files).pipe( 136 | throwIfEmpty(() => new SyncMonorepoPackagesError('No files to sync!')), 137 | mergeMap((file) => 138 | from(glob(file)).pipe( 139 | mergeAll(), 140 | throwIfEmpty( 141 | () => 142 | new SyncMonorepoPackagesError( 143 | `Could not find any files matching glob "${file}"` 144 | ) 145 | ) 146 | ) 147 | ) 148 | ); 149 | 150 | const packageDirs$ = iif( 151 | () => Boolean(packages.length), 152 | from(packages), 153 | findLernaConfig({lernaJsonPath}).pipe( 154 | filter(({lernaConfig}) => Boolean(lernaConfig.packages)), 155 | mergeMap(({lernaConfig, lernaRoot: cwd}) => 156 | findDirectoriesByGlobs(lernaConfig.packages, {cwd}) 157 | ), 158 | mergeWith( 159 | findWorkspaces(cwd).pipe( 160 | mergeMap((workspaces) => findDirectoriesByGlobs(workspaces, {cwd})) 161 | ) 162 | ) 163 | ) 164 | ).pipe( 165 | toArray(), 166 | map((packageDirs) => ({cwd, packageDirs})) 167 | ); 168 | 169 | return file$.pipe( 170 | mergeMap((srcFilePath) => 171 | packageDirs$.pipe( 172 | mergeMap(({packageDirs, cwd}) => 173 | // - we might not be at the package root 174 | // - we don't know where the packages are relative to us 175 | // - we don't know where we are relative to srcFilePath 176 | // - to that end, we need to compute the destination relative to 177 | // `cwd` (the variable) and also relative to our actual cwd. 178 | // - display relative paths to the user for brevity 179 | // (we can change this later) 180 | packageDirs.map((packageDir) => 181 | createFileCopyResult( 182 | srcFilePath, 183 | path.relative( 184 | process.cwd(), 185 | path.join( 186 | path.resolve(cwd, packageDir), 187 | path.relative(path.resolve(process.cwd(), cwd), srcFilePath) 188 | ) 189 | ) 190 | ) 191 | ) 192 | ), 193 | concatMap((copyInfo) => { 194 | debug( 195 | 'attempting to copy %s to %s (overwrite: %s)', 196 | copyInfo.from, 197 | copyInfo.to, 198 | force 199 | ); 200 | return iif( 201 | () => dryRun, 202 | dryRunTestFile(copyInfo, force), 203 | defer(() => 204 | from(fs.copy(copyInfo.from, copyInfo.to, {overwrite: force})) 205 | ).pipe(map(() => copyInfo)) 206 | ).pipe( 207 | map((copyInfo) => copyInfo.withSuccess()), 208 | catchError((err) => { 209 | if (err.code === 'EEXIST') { 210 | return of( 211 | copyInfo.withError( 212 | new SyncMonorepoPackagesError( 213 | `Refusing to overwrite existing file ${copyInfo.to}; use --force to overwrite` 214 | ) 215 | ) 216 | ); 217 | } 218 | throw err; 219 | }) 220 | ); 221 | }) 222 | ) 223 | ) 224 | ); 225 | }; 226 | 227 | /** 228 | * @template T,U 229 | * @typedef {import('rxjs').OperatorFunction} OperatorFunction 230 | */ 231 | 232 | /** 233 | * @typedef {import('./model').FileCopyResult} FileCopyResult 234 | */ 235 | 236 | /** 237 | * @typedef Summary 238 | * @property {string} [fail] - Failure message 239 | * @property {string} [success] - Success message 240 | * @property {string} [noop] - No-op message 241 | */ 242 | 243 | /** 244 | * @typedef SyncFileOptions 245 | * @property {string} cwd 246 | * @property {string} lerna 247 | * @property {boolean} dryRun 248 | * @property {string[]} packages 249 | * @property {boolean} force 250 | */ 251 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const {iif, concat, EMPTY} = require('rxjs'); 3 | const {info, warning, success, error} = require('log-symbols'); 4 | const wrapAnsi = require('wrap-ansi'); 5 | const {share, tap, map} = require('rxjs/operators'); 6 | const { 7 | syncPackageJsons, 8 | syncFile, 9 | SyncMonorepoPackagesError, 10 | summarizePackageChanges, 11 | summarizeFileCopies, 12 | DEFAULT_FIELDS, 13 | } = require('./index'); 14 | 15 | const debug = require('debug')('sync-monorepo-packages:cli'); 16 | const {columns} = require('term-size')(); 17 | 18 | const yargs = require('yargs'); 19 | 20 | /** 21 | * Returns a "dry run warning" string 22 | * @returns {string} 23 | */ 24 | function obnoxiousDryRunWarning() { 25 | return `${warning}${warning}${warning} DRY RUN ${warning}${warning}${warning}`; 26 | } 27 | 28 | /** 29 | * Wraps a line to terminal size 30 | * @param {string} value Value to wrap 31 | */ 32 | function wrapLine(value) { 33 | return wrapAnsi(value, columns, {trim: false, wordWrap: true}); 34 | } 35 | 36 | /** 37 | * Writes a string to the terminal nicely. `value` is coerced to `string` 38 | * @param {any} value 39 | */ 40 | function writeOut(value) { 41 | console.log(wrapLine(String(value))); 42 | } 43 | 44 | /** 45 | * Write an error to the terminal nicely 46 | * @param {Error|string} err - Error 47 | */ 48 | function writeError(err) { 49 | console.error(); 50 | console.error( 51 | typeof err === 'string' || err instanceof SyncMonorepoPackagesError 52 | ? wrapLine(`${error} ${err}`) 53 | : err 54 | ); 55 | } 56 | 57 | function main() { 58 | yargs 59 | .scriptName('sync-monorepo-packages') 60 | .usage( 61 | '$0 [file..]', 62 | 'Synchronize files and metadata across packages in a monorepo', 63 | (yargs) => 64 | yargs 65 | .positional('file', { 66 | description: 'One or more source files to sync', 67 | normalize: true, 68 | type: 'string', 69 | array: true, 70 | coerce: 71 | /** 72 | * @param {string[]} v 73 | * @returns {string[]} 74 | */ 75 | (v) => v.filter(Boolean), 76 | }) 77 | .options({ 78 | 'dry-run': { 79 | description: 80 | 'Do not sync; print what would have changed (implies --verbose)', 81 | type: 'boolean', 82 | alias: 'D', 83 | }, 84 | field: { 85 | default: DEFAULT_FIELDS, 86 | description: 'Fields in source package.json to sync', 87 | nargs: 1, 88 | type: 'string', 89 | array: true, 90 | alias: ['f', 'fields'], 91 | coerce: 92 | /** 93 | * 94 | * @param {string[]} fields 95 | * @returns {string[]} 96 | */ 97 | (fields) => 98 | fields.flatMap((field) => field.split(',')).filter(Boolean), 99 | }, 100 | force: { 101 | description: `Overwrite destination file(s)`, 102 | type: 'boolean', 103 | }, 104 | packages: { 105 | defaultDescription: '(use workspaces and/or lerna.json)', 106 | description: 'Dirs/globs containing destination packages', 107 | nargs: 1, 108 | normalizePath: true, 109 | type: 'string', 110 | array: true, 111 | alias: ['p'], 112 | }, 113 | 'package-json': { 114 | description: 'Sync package.json', 115 | type: 'boolean', 116 | default: true, 117 | }, 118 | source: { 119 | alias: 's', 120 | defaultDescription: '(closest package.json)', 121 | description: 'Path to source package.json', 122 | nargs: 1, 123 | normalizePath: true, 124 | type: 'string', 125 | }, 126 | verbose: { 127 | description: 'Print change details', 128 | type: 'boolean', 129 | alias: 'v', 130 | }, 131 | summary: { 132 | description: 'Print summary', 133 | type: 'boolean', 134 | default: true, 135 | }, 136 | lerna: { 137 | description: 'Path to lerna.json, if any', 138 | defaultDescription: '(lerna.json in current dir)', 139 | nargs: 1, 140 | normalizePath: true, 141 | type: 'string', 142 | alias: 'l', 143 | }, 144 | }) 145 | .example( 146 | '$0 --field keywords --field author -s ./foo/package.json', 147 | 'Sync "keywords" and "author" from ./foo/package.json to packages found in lerna.json' 148 | ) 149 | .example( 150 | '$0 --packages ./foo --dry-run --no-summary', 151 | 'Using default fields, show what would have synced from package.json in current dir to packages in ./foo; hide summary' 152 | ) 153 | .example( 154 | '$0 --no-package-json ./README.md', 155 | 'Sync ./README.md to each package found in lerna.json. Do not sync anything in package.json' 156 | ), 157 | (argv) => { 158 | debug('argv: %O', argv); 159 | 160 | const {dryRun} = argv; 161 | 162 | // don't look at package.json if user passes --no-package-json 163 | const packageChange$ = iif( 164 | () => argv.packageJson, 165 | syncPackageJsons(argv).pipe( 166 | tap((result) => { 167 | if (dryRun || argv.verbose) { 168 | writeOut(result); 169 | } 170 | }), 171 | summarizePackageChanges() 172 | ), 173 | EMPTY 174 | ); 175 | 176 | const copyInfo$ = iif( 177 | () => Boolean(argv.file?.length), 178 | syncFile(argv.file, argv).pipe( 179 | tap((result) => { 180 | if (dryRun || argv.verbose) { 181 | writeOut( 182 | result.err 183 | ? `${error} ${result.err.message}` 184 | : `${info} ${result}` 185 | ); 186 | } 187 | }), 188 | share(), 189 | summarizeFileCopies() 190 | ), 191 | EMPTY 192 | ); 193 | 194 | if (dryRun) { 195 | writeOut(obnoxiousDryRunWarning()); 196 | } 197 | 198 | concat(packageChange$, copyInfo$) 199 | .pipe( 200 | map((summary) => { 201 | if (summary.success) { 202 | return `${success} ${summary.success}`; 203 | } 204 | if (summary.fail) { 205 | return `${error} ${summary.fail}`; 206 | } 207 | return `${info} ${summary.noop}`; 208 | }) 209 | ) 210 | .subscribe({ 211 | next: (result) => { 212 | if (argv.summary) { 213 | writeOut(result); 214 | } 215 | }, 216 | complete: () => { 217 | if (dryRun) { 218 | writeOut(obnoxiousDryRunWarning()); 219 | } 220 | }, 221 | error: (err) => { 222 | yargs.showHelp(); 223 | writeError(err); 224 | process.exitCode = 1; 225 | }, 226 | }); 227 | } 228 | ) 229 | .help() 230 | .version() 231 | .epilog( 232 | 'Found a bug? Report it at https://github.com/boneskull/sync-monorepo-packages' 233 | ) 234 | .parseSync(); 235 | } 236 | 237 | if (require.main === module) { 238 | main(); 239 | } 240 | 241 | /** 242 | * @template T,U 243 | * @typedef {import('rxjs').OperatorFunction} OperatorFunction 244 | */ 245 | 246 | /** 247 | * @template T 248 | * @typedef {import('rxjs').Observable} Observable 249 | */ 250 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/e2e/fixture/lerna/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/e2e/fixture/workspaces/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test/e2e/fixture/lerna-and-workspaces/LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------