Skip to content
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] Support PNPM workspaces #1938

Merged
merged 39 commits into from
Jul 3, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
d44931f
Move to installManager
D4N14L Jun 15, 2020
65c1c44
Add initial implementation of workspaces
D4N14L Jun 17, 2020
824c01d
Merge branch 'master' of https://github.com/D4N14L/web-build-tools in…
D4N14L Jun 17, 2020
e6d78e7
Rush update
D4N14L Jun 17, 2020
cadb3f6
Rush change
D4N14L Jun 17, 2020
7acf1ed
Lint
D4N14L Jun 17, 2020
f35d08b
Add new test files
D4N14L Jun 19, 2020
5b49dd8
Add 'rush add' support for workspace projects
D4N14L Jun 19, 2020
078d7b0
Add workspaces versioning support and tests
D4N14L Jun 19, 2020
f73c79d
Make pnpmfile shim workspace-only
D4N14L Jun 24, 2020
85885a7
Allow exact ranges to be used for workspace range conversion
D4N14L Jun 24, 2020
debf80f
Add repo-state.json to be used by workspaces to validate common versions
D4N14L Jun 24, 2020
2301d3b
Merge branch 'master' of https://github.com/D4N14L/web-build-tools in…
D4N14L Jun 24, 2020
9b738c8
Rush update
D4N14L Jun 24, 2020
e841136
Small modification to avoid work when using DeployManager
D4N14L Jun 24, 2020
48bcca5
Fix changes when workspace dependencies should be tracked
D4N14L Jun 25, 2020
b0984b7
Merge branch 'master' of https://github.com/microsoft/rushstack into …
D4N14L Jun 27, 2020
6ce1e86
Rush update
D4N14L Jun 27, 2020
d8a7b2c
PR feedback
D4N14L Jun 27, 2020
f7f7605
Updated snapshot
D4N14L Jun 29, 2020
8b43f8e
Fix local project references for RushInstallManager
D4N14L Jun 29, 2020
495699a
Add support for reverting workspace notation when going back to legac…
D4N14L Jun 29, 2020
3ea3fbd
Fix per-project shrinkwrap when encountering duplicate dev dependencies
D4N14L Jun 29, 2020
3fad71e
Use repo-state.json for shrinkwrap hash validation
D4N14L Jun 30, 2020
a886fb1
PR feedback
D4N14L Jun 30, 2020
e94bfa1
Fix RepoStateFile serialization error
D4N14L Jun 30, 2020
c83abf7
Add "workspaces" property to the last-install.flag file
D4N14L Jul 1, 2020
5e92471
Merge branch 'master' of https://github.com/microsoft/rushstack into …
D4N14L Jul 1, 2020
952b0bc
Add SpecifierType enum
D4N14L Jul 3, 2020
46a7d3d
PR feedback
D4N14L Jul 3, 2020
58eb273
Remove 'generated' comment from checked in file
D4N14L Jul 3, 2020
cb7aad3
Merge branch 'master' of https://github.com/microsoft/rushstack into …
D4N14L Jul 3, 2020
c225f18
Bump semver
D4N14L Jul 3, 2020
e6da401
rush change
D4N14L Jul 3, 2020
3d9ec44
Merge branch 'master' of https://github.com/microsoft/rushstack into …
D4N14L Jul 3, 2020
2aaf287
PR feedback
D4N14L Jul 3, 2020
5e3c3ea
Move dynamic data to pnpmfileSettings.json
D4N14L Jul 3, 2020
1771839
Upgrade semver types to match package version.
iclanton Jul 3, 2020
0e8d05a
Improve typesafety in PnpmfileShim.
iclanton Jul 3, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/rush-lib/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"npm-packlist": "~2.1.2",
"read-package-tree": "~5.1.5",
"resolve": "~1.17.0",
"semver": "~5.3.0",
"semver": "~7.3.0",
"strict-uri-encode": "~2.0.0",
"tar": "~5.0.5",
"true-case-path": "~2.2.1",
Expand All @@ -64,7 +64,7 @@
"@types/npm-packlist": "~1.1.1",
"@types/read-package-tree": "5.1.0",
"@types/resolve": "1.17.1",
"@types/semver": "5.3.33",
"@types/semver": "~7.2.0",
"@types/strict-uri-encode": "2.0.0",
"@types/tar": "4.0.3",
"@types/wordwrap": "1.0.0",
Expand Down
16 changes: 15 additions & 1 deletion apps/rush-lib/src/api/CommonVersionsConfiguration.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import * as crypto from 'crypto';
import * as path from 'path';
import {
JsonFile,
JsonSchema,
MapExtensions,
ProtectableMap,
FileSystem
FileSystem,
Sort
} from '@rushstack/node-core-library';
import { PackageNameParsers } from './PackageNameParsers';
import { JsonSchemaUrls } from '../logic/JsonSchemaUrls';
Expand Down Expand Up @@ -148,6 +150,18 @@ export class CommonVersionsConfiguration {
return this._filePath;
}

/**
* Get a sha1 hash of the preferred versions.
*/
public get preferredVersionsHash(): string {
// Sort so that the hash is stable
const preferredVersionsToHash: Map<string, string> = new Map<string, string>(
this._preferredVersions.protectedView
);
Sort.sortMapKeys(preferredVersionsToHash);
return crypto.createHash('sha1').update(JSON.stringify(preferredVersionsToHash)).digest('hex');
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Writes the "common-versions.json" file to disk, using the filename that was passed to loadFromFile().
*/
Expand Down
6 changes: 5 additions & 1 deletion apps/rush-lib/src/api/PackageJsonEditor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,11 @@ export class PackageJsonEditor {
this._onChange.bind(this)
);

if (dependencyType === DependencyType.Regular || dependencyType === DependencyType.Optional) {
if (
dependencyType === DependencyType.Regular ||
dependencyType === DependencyType.Optional ||
dependencyType === DependencyType.Peer
) {
this._dependencies.set(packageName, dependency);
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
} else {
this._devDependencies.set(packageName, dependency);
Expand Down
91 changes: 91 additions & 0 deletions apps/rush-lib/src/api/RepoStateFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
// See LICENSE in the project root for license information.

import * as path from 'path';
import { FileSystem, JsonFile, JsonSchema } from '@rushstack/node-core-library';

/**
* This interface represents the raw pnpm-workspace.YAML file
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
* Example:
* {
* "preferredVersionsHash": "..."
* }
*/
interface IRepoStateJson {
/** A hash of the CommonVersionsConfiguration.preferredVersions field */
preferredVersionsHash?: string;
}

/**
* Th
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
*/
export class RepoStateFile {
private static _jsonSchema: JsonSchema = JsonSchema.fromFile(
path.join(__dirname, '../schemas/repo-state.schema.json')
);
private _repoStateFilePath: string;
private _preferredVersionsHash: string | undefined;
private _modified: boolean = false;

private constructor(repoStateJson: IRepoStateJson | undefined, filePath: string) {
this._repoStateFilePath = filePath;

if (repoStateJson) {
this._preferredVersionsHash = repoStateJson.preferredVersionsHash;
}
}

/**
* Get the absolute file path of the repo-state.json file.
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
*/
public get filePath(): string {
return this._repoStateFilePath;
}

/**
* The hash of all preferred versions at the end of the last update.
*/
public get preferredVersionsHash(): string | undefined {
return this._preferredVersionsHash;
}

public set preferredVersionsHash(hash: string | undefined) {
if (this._preferredVersionsHash !== hash) {
this._preferredVersionsHash = hash;
this._modified = true;
}
}

/**
* Loads the repo-state.json data from the specified file path.
* If the file has not been created yet, then an empty object is returned.
*/
public static loadFromFile(jsonFilename: string): RepoStateFile {
let repoStateJson: IRepoStateJson | undefined = undefined;

if (FileSystem.exists(jsonFilename)) {
repoStateJson = JsonFile.loadAndValidate(jsonFilename, RepoStateFile._jsonSchema);
}
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
return new RepoStateFile(repoStateJson, jsonFilename);
}

/**
* Writes the "repo-state.json" file to disk, using the filename that was passed to loadFromFile().
*/
public saveIfModified(): boolean {
if (this._modified) {
JsonFile.save(this._serialize(), this._repoStateFilePath, { updateExistingFile: true });
this._modified = false;
return true;
}

return false;
}

private _serialize(): IRepoStateJson {
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
const repoStateJson: IRepoStateJson = {
preferredVersionsHash: this.preferredVersionsHash
};
return repoStateJson;
}
}
37 changes: 37 additions & 0 deletions apps/rush-lib/src/api/RushConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { YarnPackageManager } from './packageManager/YarnPackageManager';
import { PnpmPackageManager } from './packageManager/PnpmPackageManager';
import { ExperimentsConfiguration } from './ExperimentsConfiguration';
import { PackageNameParsers } from './PackageNameParsers';
import { RepoStateFile } from './RepoStateFile';

const MINIMUM_SUPPORTED_RUSH_JSON_VERSION: string = '0.0.0';
const DEFAULT_BRANCH: string = 'master';
Expand All @@ -38,6 +39,7 @@ const knownRushConfigFilenames: string[] = [
'.npmrc-publish',
RushConstants.pinnedVersionsFilename,
RushConstants.commonVersionsFilename,
RushConstants.repoStateFilename,
RushConstants.browserApprovedPackagesFilename,
RushConstants.nonbrowserApprovedPackagesFilename,
RushConstants.versionPoliciesFilename,
Expand Down Expand Up @@ -168,6 +170,10 @@ export interface IPnpmOptionsJson extends IPackageManagerOptionsJsonBase {
* {@inheritDoc PnpmOptionsConfiguration.preventManualShrinkwrapChanges}
*/
preventManualShrinkwrapChanges?: boolean;
/**
* {@inheritDoc PnpmOptionsConfiguration.useWorkspaces}
*/
useWorkspaces?: boolean;
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
}

/**
Expand Down Expand Up @@ -346,6 +352,14 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
*/
public readonly preventManualShrinkwrapChanges: boolean;

/**
* If true, then Rush will use the workspaces feature to install and link packages when invoking PNPM.
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
*
* @remarks
* The default value is false. (For now.)
*/
public readonly useWorkspaces: boolean;

/** @internal */
public constructor(json: IPnpmOptionsJson, commonTempFolder: string) {
super(json);
Expand All @@ -360,6 +374,7 @@ export class PnpmOptionsConfiguration extends PackageManagerOptionsConfiguration
this.strictPeerDependencies = !!json.strictPeerDependencies;
this.resolutionStrategy = json.resolutionStrategy || 'fewer-dependencies';
this.preventManualShrinkwrapChanges = !!json.preventManualShrinkwrapChanges;
this.useWorkspaces = !!json.useWorkspaces;
}
}

Expand Down Expand Up @@ -1425,6 +1440,28 @@ export class RushConfiguration {
return CommonVersionsConfiguration.loadFromFile(commonVersionsFilename);
}

/**
* Gets the path to the repo-state.json file for a specific variant.
* @param variant - The name of the current variant in use by the active command.
*/
public getRepoStateFilePath(variant?: string | undefined): string {
const repoStateFilename: string = path.join(
this.commonRushConfigFolder,
...(variant ? [RushConstants.rushVariantsFolderName, variant] : []),
RushConstants.repoStateFilename
);
return repoStateFilename;
}

/**
* Gets the contents from the repo-state.json file for a specific variant.
* @param variant - The name of the current variant in use by the active command.
*/
public getRepoState(variant?: string | undefined): RepoStateFile {
const repoStateFilename: string = this.getRepoStateFilePath(variant);
return RepoStateFile.loadFromFile(repoStateFilename);
}

/**
* Gets the committed shrinkwrap file name for a specific variant.
* @param variant - The name of the current variant in use by the active command.
Expand Down
8 changes: 6 additions & 2 deletions apps/rush-lib/src/cli/actions/AddAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { RushConfigurationProject } from '../../api/RushConfigurationProject';
import { BaseRushAction } from './BaseRushAction';
import { RushCommandLineParser } from '../RushCommandLineParser';
import { PackageJsonUpdater, SemVerStyle } from '../../logic/PackageJsonUpdater';
import { DependencySpecifier } from '../../logic/DependencySpecifier';

export class AddAction extends BaseRushAction {
private _allFlag: CommandLineFlagParameter;
Expand Down Expand Up @@ -128,8 +129,11 @@ export class AddAction extends BaseRushAction {
throw new Error(`The package name "${packageName}" is not valid.`);
}

if (version && version !== 'latest' && !semver.validRange(version) && !semver.valid(version)) {
throw new Error(`The SemVer specifier "${version}" is not valid.`);
if (version && version !== 'latest') {
const specifier: DependencySpecifier = new DependencySpecifier(packageName, version);
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
if (!semver.validRange(specifier.versionSpecifier) && !semver.valid(specifier.versionSpecifier)) {
throw new Error(`The SemVer specifier "${version}" is not valid.`);
}
}

const updater: PackageJsonUpdater = new PackageJsonUpdater(this.rushConfiguration, this.rushGlobalFolder);
Expand Down
5 changes: 3 additions & 2 deletions apps/rush-lib/src/cli/actions/BaseInstallAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {

import { BaseRushAction } from './BaseRushAction';
import { Event } from '../../api/EventHooks';
import { InstallManager, IInstallManagerOptions } from '../../logic/InstallManager';
import { BaseInstallManager, IInstallManagerOptions } from '../../logic/base/BaseInstallManager';
import { InstallManagerFactory } from '../../logic/InstallManagerFactory';
import { PurgeManager } from '../../logic/PurgeManager';
import { SetupChecks } from '../../logic/SetupChecks';
import { StandardScriptUpdater } from '../../logic/StandardScriptUpdater';
Expand Down Expand Up @@ -117,7 +118,7 @@ export abstract class BaseInstallAction extends BaseRushAction {

const installManagerOptions: IInstallManagerOptions = this.buildInstallOptions();

const installManager: InstallManager = new InstallManager(
const installManager: BaseInstallManager = InstallManagerFactory.getInstallManager(
this.rushConfiguration,
this.rushGlobalFolder,
purgeManager,
Expand Down
2 changes: 1 addition & 1 deletion apps/rush-lib/src/cli/actions/InstallAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// See LICENSE in the project root for license information.

import { BaseInstallAction } from './BaseInstallAction';
import { IInstallManagerOptions } from '../../logic/InstallManager';
import { IInstallManagerOptions } from '../../logic/base/BaseInstallManager';
import { RushCommandLineParser } from '../RushCommandLineParser';

export class InstallAction extends BaseInstallAction {
Expand Down
2 changes: 1 addition & 1 deletion apps/rush-lib/src/cli/actions/UpdateAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { CommandLineFlagParameter } from '@rushstack/ts-command-line';

import { BaseInstallAction } from './BaseInstallAction';
import { IInstallManagerOptions } from '../../logic/InstallManager';
import { IInstallManagerOptions } from '../../logic/base/BaseInstallManager';
import { RushCommandLineParser } from '../RushCommandLineParser';

export class UpdateAction extends BaseInstallAction {
Expand Down
10 changes: 8 additions & 2 deletions apps/rush-lib/src/cli/actions/VersionAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,16 @@ export class VersionAction extends BaseRushAction {
const newPolicyVersion: semver.SemVer = new semver.SemVer(policy.version);
if (newPolicyVersion.prerelease.length) {
// Update 1.5.0-alpha.10 to 1.5.0-beta.10
newPolicyVersion.prerelease[0] = this._prereleaseIdentifier.value;
// For example, if we are parsing "1.5.0-alpha.10" then the newPolicyVersion.prerelease array
// would contain [ "alpha", 10 ], so we would replace "alpha" with "beta"
newPolicyVersion.prerelease = [
this._prereleaseIdentifier.value,
...newPolicyVersion.prerelease.slice(1)
];
} else {
// Update 1.5.0 to 1.5.0-beta
newPolicyVersion.prerelease.push(this._prereleaseIdentifier.value);
// Since there is no length, we can just set to a new array
newPolicyVersion.prerelease = [this._prereleaseIdentifier.value];
}
newVersion = newPolicyVersion.format();
}
Expand Down
2 changes: 1 addition & 1 deletion apps/rush-lib/src/cli/scriptActions/GlobalScriptAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { BaseScriptAction, IBaseScriptActionOptions } from './BaseScriptAction';
import { Utilities } from '../../utilities/Utilities';
import { AlreadyReportedError } from '../../utilities/AlreadyReportedError';
import { FileSystem, LockFile, IPackageJson, JsonFile, PackageName } from '@rushstack/node-core-library';
import { InstallHelpers } from '../../logic/InstallHelpers';
import { InstallHelpers } from '../../logic/installManager/InstallHelpers';
import { RushConstants } from '../../logic/RushConstants';
import { LastInstallFlag } from '../../api/LastInstallFlag';

Expand Down
10 changes: 10 additions & 0 deletions apps/rush-lib/src/logic/DependencySpecifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class DependencySpecifier {
* directory - A local directory
* remote - An HTTP url to a .tar.gz, .tar or .tgz file
* alias - A package alias such as "npm:other-package@^1.2.3"
* workspace - A package specified using workspace protocol such as "workspace:^1.2.3"
*/
public readonly specifierType: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this type be an enum?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't disagree. I've left it this way because it's the way it's been with that many different types in the past, but if you'd prefer an enum I'm not opposed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make it an enum.


Expand All @@ -46,6 +47,15 @@ export class DependencySpecifier {
this.packageName = packageName;
this.versionSpecifier = versionSpecifier;

// Workspace ranges are a feature from PNPM and Yarn. Set the version specifier
// to the trimmed version range.
if (versionSpecifier.startsWith('workspace:')) {
this.specifierType = 'workspace';
this.versionSpecifier = versionSpecifier.slice(this.specifierType.length + 1).trim();
this.aliasTarget = undefined;
return;
}

const result: npmPackageArg.AliasResult = npmPackageArg.resolve(
packageName,
versionSpecifier
Expand Down
Loading