-
Notifications
You must be signed in to change notification settings - Fork 607
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[rush] Introduce a "rush add" command #843
Changes from 15 commits
0f4ae68
7260861
60f2fa9
0ae6ccf
a66230d
9c6f76f
30df582
caf6983
0a8a922
f094bb8
8add64f
8177fec
bc43560
56a6ab2
ee299b1
1c8e54a
abfabf2
9d66f66
14e448f
6e62fdc
836c9a2
2909608
e0793d0
ddf3e0b
d48c7eb
2d81c46
128da2f
daf87a2
91b5592
a115ad5
2ac1044
612e9bf
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
import * as semver from 'semver'; | ||
|
||
import { | ||
IPackageJson, | ||
JsonFile | ||
} from '@microsoft/node-core-library'; | ||
|
||
export const enum DependencyType { | ||
Dependency = 'dependency', | ||
DevDependency = 'devDependency', | ||
OptionalDependency = 'optionalDependency', | ||
PeerOnly = 'peerDependency' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. normal, dev, optional, peerOnly? If it's |
||
} | ||
|
||
export class Dependency { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
PackageJsonDependency? #Resolved |
||
private _type: DependencyType; | ||
private _name: string; | ||
private _version: string | undefined; | ||
private _peerVersion: string | undefined; | ||
private _onChange: () => void; | ||
|
||
public constructor(name: string, | ||
version: string | undefined, | ||
type: DependencyType, | ||
peerVersion: string | undefined, | ||
onChange: () => void) { | ||
this._name = name; | ||
this._version = version; | ||
this._type = type; | ||
this._peerVersion = peerVersion; | ||
this._onChange = onChange; | ||
|
||
if (this._version && this._type === DependencyType.PeerOnly) { | ||
throw new Error(`Cannot specify a primary version if the dependency type is peer-only.`); | ||
} | ||
if (!this._peerVersion && this._type === DependencyType.PeerOnly) { | ||
throw new Error(`Must specify a peer version if the dependency type if peer-only.`); | ||
} | ||
} | ||
|
||
public get name(): string { | ||
return this._name; | ||
} | ||
|
||
public get version(): string | undefined { | ||
return this._version; | ||
} | ||
|
||
public setVersion(newVersion: string): void { | ||
if (!semver.valid(newVersion) && !semver.validRange(newVersion)) { | ||
throw new Error(`Cannot set version to invalid value: "${newVersion}"`); | ||
} | ||
this._version = newVersion; | ||
this._onChange(); | ||
} | ||
|
||
public get dependencyType(): DependencyType { | ||
return this._type; | ||
} | ||
|
||
public setDependencyType(newType: DependencyType): void { | ||
this._type = newType; | ||
this._onChange(); | ||
} | ||
|
||
public get peerVersion(): string | undefined { | ||
return this._peerVersion; | ||
} | ||
} | ||
|
||
export class PackageJsonEditor { | ||
private readonly _filepath: string; | ||
private readonly _data: IPackageJson; | ||
private readonly _dependencies: Map<string, Dependency>; | ||
|
||
private _onChange: () => void; | ||
private _modified: boolean; | ||
|
||
public static load(filepath: string): PackageJsonEditor { | ||
return new PackageJsonEditor(filepath, JsonFile.load(filepath)); | ||
} | ||
|
||
public static fromObject(object: IPackageJson, filename: string): PackageJsonEditor { | ||
return new PackageJsonEditor(filename, object); | ||
} | ||
|
||
public get name(): string { | ||
return this._data.name; | ||
} | ||
|
||
public get version(): string { | ||
return this._data.version; | ||
} | ||
|
||
public get filePath(): string { | ||
return this._filepath; | ||
} | ||
|
||
public getDependency(packageName: string): Dependency | undefined { | ||
return this._dependencies.get(packageName); | ||
} | ||
|
||
public forEachDependency(cb: (dependency: Dependency) => void): void { | ||
this._dependencies.forEach(cb); | ||
} | ||
|
||
public addOrUpdateDependency(packageName: string, newVersion: string, dependencyType: DependencyType): void { | ||
if (this._dependencies.has(packageName)) { | ||
const dependency: Dependency = this._dependencies.get(packageName)!; | ||
dependency.setVersion(newVersion); | ||
dependency.setDependencyType(dependencyType); | ||
} else { | ||
const dependency: Dependency | ||
= new Dependency(packageName, newVersion, dependencyType, undefined, this._onChange); | ||
this._dependencies.set(packageName, dependency); | ||
} | ||
} | ||
|
||
public saveIfModified(): boolean { | ||
if (this._modified) { | ||
JsonFile.save(this._normalize(), this._filepath); | ||
this._modified = false; | ||
return true; | ||
} | ||
return false; | ||
} | ||
|
||
private constructor(filepath: string, data: IPackageJson) { | ||
this._filepath = filepath; | ||
this._data = data; | ||
|
||
this._dependencies = new Map<string, Dependency>(); | ||
|
||
const dependencies: { [key: string]: string } = data.dependencies || {}; | ||
const devDependencies: { [key: string]: string } = data.devDependencies || {}; | ||
const optionalDependencies: { [key: string]: string } = data.optionalDependencies || {}; | ||
const peerDependencies: { [key: string]: string } = data.peerDependencies || {}; | ||
|
||
this._onChange = () => { | ||
this._modified = true; | ||
}; | ||
|
||
Object.keys(dependencies || {}).forEach((dependency: string) => { | ||
if (devDependencies[dependency]) { | ||
throw new Error(`The package "${dependency}" is listed as both a dev and a regular dependency`); | ||
} | ||
if (optionalDependencies[dependency]) { | ||
throw new Error(`The package "${dependency}" is listed as both a dev and a regular dependency`); | ||
} | ||
|
||
this._dependencies.set(dependency, new Dependency(dependency, dependencies[dependency], | ||
DependencyType.Dependency, peerDependencies[dependency], this._onChange)); | ||
}); | ||
|
||
Object.keys(devDependencies || {}).forEach((dependency: string) => { | ||
if (optionalDependencies[dependency]) { | ||
throw new Error(`The package "${dependency}" is listed as both a dev and an optional dependency`); | ||
} | ||
|
||
this._dependencies.set(dependency, new Dependency(dependency, devDependencies[dependency], | ||
DependencyType.Dependency, peerDependencies[dependency], this._onChange)); | ||
}); | ||
|
||
Object.keys(optionalDependencies || {}).forEach((dependency: string) => { | ||
this._dependencies.set(dependency, new Dependency(dependency, optionalDependencies[dependency], | ||
DependencyType.OptionalDependency, peerDependencies[dependency], this._onChange)); | ||
}); | ||
|
||
Object.keys(peerDependencies || {}).forEach((dependency: string) => { | ||
if (!this._dependencies.has(dependency)) { | ||
this._dependencies.set(dependency, new Dependency(dependency, undefined, | ||
DependencyType.PeerOnly, peerDependencies[dependency], this._onChange)); | ||
} | ||
}); | ||
} | ||
|
||
private _normalize(): IPackageJson { | ||
delete this._data.dependencies; | ||
delete this._data.devDependencies; | ||
delete this._data.peerDependencies; | ||
delete this._data.optionalDependencies; | ||
|
||
const keys: Array<string> = [...this._dependencies.keys()].sort(); | ||
|
||
for (const packageName of keys) { | ||
const dependency: Dependency = this._dependencies.get(packageName)!; | ||
|
||
if (dependency.dependencyType === DependencyType.Dependency) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
switch? #WontFix There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if (!this._data.dependencies) { | ||
this._data.dependencies = {}; | ||
} | ||
this._data.dependencies[dependency.name] = dependency.version!; | ||
} | ||
|
||
if (dependency.dependencyType === DependencyType.DevDependency) { | ||
if (!this._data.devDependencies) { | ||
this._data.devDependencies = {}; | ||
} | ||
this._data.devDependencies[dependency.name] = dependency.version!; | ||
} | ||
|
||
if (dependency.dependencyType === DependencyType.OptionalDependency) { | ||
if (!this._data.optionalDependencies) { | ||
this._data.optionalDependencies = {}; | ||
} | ||
this._data.optionalDependencies[dependency.name] = dependency.version!; | ||
} | ||
|
||
if (dependency.peerVersion) { | ||
if (!this._data.peerDependencies) { | ||
this._data.peerDependencies = {}; | ||
} | ||
this._data.peerDependencies[dependency.name] = dependency.peerVersion; | ||
} | ||
} | ||
|
||
return this._data; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,7 +4,13 @@ | |
import * as path from 'path'; | ||
import * as fs from 'fs'; | ||
import * as semver from 'semver'; | ||
import { JsonFile, JsonSchema, PackageName, FileSystem } from '@microsoft/node-core-library'; | ||
import { | ||
JsonFile, | ||
JsonSchema, | ||
Path, | ||
PackageName, | ||
FileSystem | ||
} from '@microsoft/node-core-library'; | ||
|
||
import { Rush } from '../api/Rush'; | ||
import { RushConfigurationProject, IRushConfigurationProjectJson } from './RushConfigurationProject'; | ||
|
@@ -743,6 +749,20 @@ export class RushConfiguration { | |
return this._versionPolicyConfiguration; | ||
} | ||
|
||
/** | ||
* Returns the project for which the specified path is underneath that project's folder. | ||
* If the path is not under any project's folder, returns undefined. | ||
*/ | ||
public getProjectForPath(currentFolderPath: string): RushConfigurationProject | undefined { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tryGet? #Resolved |
||
const resolvedPath: string = path.resolve(currentFolderPath); | ||
for (const project of this.projects) { | ||
if (Path.isUnder(project.projectFolder, resolvedPath)) { | ||
return project; | ||
} | ||
} | ||
return undefined; | ||
} | ||
|
||
/** | ||
* Use RushConfiguration.loadFromConfigurationFile() or Use RushConfiguration.loadFromDefaultLocation() | ||
* instead. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,6 +12,7 @@ import { | |
|
||
import { RushConfiguration } from '../api/RushConfiguration'; | ||
import { VersionPolicy, LockStepVersionPolicy } from './VersionPolicy'; | ||
import { PackageJsonEditor } from './PackageJsonEditor'; | ||
|
||
/** | ||
* This represents the JSON data object for a project entry in the rush.json configuration file. | ||
|
@@ -37,6 +38,7 @@ export class RushConfigurationProject { | |
private _projectRelativeFolder: string; | ||
private _reviewCategory: string; | ||
private _packageJson: IPackageJson; | ||
private _packageJsonEditor: PackageJsonEditor; | ||
private _tempProjectName: string; | ||
private _unscopedTempProjectName: string; | ||
private _cyclicDependencyProjects: Set<string>; | ||
|
@@ -97,6 +99,8 @@ export class RushConfigurationProject { | |
+ ` match the name "${this._packageJson.name}" from package.json`); | ||
} | ||
|
||
this._packageJsonEditor = PackageJsonEditor.load(packageJsonFilename); | ||
|
||
this._tempProjectName = tempProjectName; | ||
|
||
// The "rushProject.tempProjectName" is guaranteed to be unique name (e.g. by adding the "-2" | ||
|
@@ -172,11 +176,20 @@ export class RushConfigurationProject { | |
|
||
/** | ||
* The parsed NPM "package.json" file from projectFolder. | ||
* Will be deprecated soon in favor of packageJsonEditor | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
*/ | ||
public get packageJson(): IPackageJson { | ||
return this._packageJson; | ||
} | ||
|
||
/** | ||
* A useful wrapper around the package.json file for making modifications | ||
* @alpha | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @beta -- we don't use alpha in WBT #Resolved |
||
*/ | ||
public get packageJsonEditor(): PackageJsonEditor { | ||
return this._packageJsonEditor; | ||
} | ||
|
||
/** | ||
* The unique name for the temporary project that will be generated in the Common folder. | ||
* For example, if the project name is "@scope/MyProject", the temporary project name | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
copyright banner #Resolved