Skip to content

Commit 1a3db68

Browse files
authored
Implements enable / disable (#11)
* Implements enable / disable * Adds tests * Debugs GH-only crash * Fixes CI by running the build beforehand * Fixes build
1 parent b56df30 commit 1a3db68

19 files changed

+485
-48
lines changed

.editorconfig

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# EditorConfig is awesome!
2+
3+
# Mark this as the root editorconfig file
4+
root = true
5+
6+
# Base ruleset for all files
7+
[*]
8+
trim_trailing_whitespace = true
9+
insert_final_newline = true
10+
indent_style = space
11+
indent_size = 2
12+
13+
# Override rules for markdown
14+
[*.md]
15+
# trailing whitespace is significant in markdown -> do not remove
16+
trim_trailing_whitespace = false

.github/workflows/nodejs.yml .github/workflows/ci.yml

+1
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,6 @@ jobs:
2323
node-version: ${{ matrix.node-version }}
2424

2525
- run: yarn install --immutable
26+
- run: yarn build # We need the stubs to run the tests
2627
- run: yarn eslint
2728
- run: yarn jest

.pnp.js

+15-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.vscode/settings.json

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,9 @@
55
"**/.pnp.*": true
66
},
77
"eslint.nodePath": ".yarn/sdks",
8-
"typescript.enablePromptUseWorkspaceTsdk": true
8+
"typescript.enablePromptUseWorkspaceTsdk": true,
9+
"editor.codeActionsOnSave": {
10+
"source.fixAll": true,
11+
"source.fixAll.eslint": true
12+
}
913
}
Binary file not shown.

package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"@types/node": "^13.9.2",
2121
"@types/semver": "^7.1.0",
2222
"@types/tar": "^4.0.3",
23+
"@types/which": "^1.3.2",
2324
"@typescript-eslint/eslint-plugin": "^2.0.0",
2425
"@typescript-eslint/parser": "^4.2.0",
2526
"@yarnpkg/eslint-config": "^0.1.0",
@@ -39,13 +40,15 @@
3940
"ts-node": "^8.10.2",
4041
"typescript": "^3.9.7",
4142
"webpack": "next",
42-
"webpack-cli": "^3.3.11"
43+
"webpack-cli": "^3.3.11",
44+
"which": "^2.0.2"
4345
},
4446
"scripts": {
4547
"build": "rm -rf dist && webpack && ts-node ./mkshims.ts",
4648
"corepack": "ts-node ./sources/main.ts",
4749
"prepack": "node ./.yarn/releases/*.*js build",
48-
"postpack": "rm -rf dist shims"
50+
"postpack": "rm -rf dist shims",
51+
"test": "yarn jest"
4952
},
5053
"files": [
5154
"dist",

sources/Engine.ts

+26-9
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,34 @@
1-
import {UsageError} from 'clipanion';
2-
import fs from 'fs';
3-
import path from 'path';
4-
import semver from 'semver';
1+
import {UsageError} from 'clipanion';
2+
import fs from 'fs';
3+
import path from 'path';
4+
import semver from 'semver';
55

6-
import defaultConfig from '../config.json';
6+
import defaultConfig from '../config.json';
77

8-
import * as folderUtils from './folderUtils';
9-
import * as pmmUtils from './pmmUtils';
10-
import {Config, Descriptor, Locator, SupportedPackageManagers, SupportedPackageManagerSet} from './types';
8+
import * as folderUtils from './folderUtils';
9+
import * as pmmUtils from './pmmUtils';
10+
import {SupportedPackageManagers, SupportedPackageManagerSet} from './types';
11+
import {Config, Descriptor, Locator} from './types';
1112

1213

1314
export class Engine {
14-
constructor(private config: Config = defaultConfig as Config) {
15+
constructor(public config: Config = defaultConfig as Config) {
16+
}
17+
18+
getBinariesFor(name: SupportedPackageManagers) {
19+
const binNames = new Set<string>();
20+
21+
for (const rangeDefinition of Object.values(this.config.definitions[name]!.ranges)) {
22+
const bins = Array.isArray(rangeDefinition.bin)
23+
? rangeDefinition.bin
24+
: Object.keys(rangeDefinition.bin);
25+
26+
for (const name of bins) {
27+
binNames.add(name);
28+
}
29+
}
30+
31+
return binNames;
1532
}
1633

1734
async getDefaultDescriptors() {

sources/commands/Disable.ts

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import {Command, UsageError} from 'clipanion';
2+
import fs from 'fs';
3+
import path from 'path';
4+
import which from 'which';
5+
6+
import {Context} from '../main';
7+
import {isSupportedPackageManager, SupportedPackageManagerSet} from '../types';
8+
9+
export class DisableCommand extends Command<Context> {
10+
static usage = Command.Usage({
11+
description: `Remove the Corepack shims from the install directory`,
12+
details: `
13+
When run, this command will remove the shims for the specified package managers from the install directory, or all shims if no parameters are passed.
14+
15+
By default it will locate the install directory by running the equivalent of \`which corepack\`, but this can be tweaked by explicitly passing the install directory via the \`--bin-folder\` flag.
16+
`,
17+
examples: [[
18+
`Disable all shims, removing them if they're next to the \`coreshim\` binary`,
19+
`$0 disable`,
20+
], [
21+
`Disable all shims, removing them from the specified directory`,
22+
`$0 disable --install-directory /path/to/bin`,
23+
], [
24+
`Disable the Yarn shim only`,
25+
`$0 disable yarn`,
26+
]],
27+
});
28+
29+
@Command.String(`--install-directory`)
30+
installDirectory?: string;
31+
32+
@Command.Rest()
33+
names: Array<string> = [];
34+
35+
@Command.Path(`disable`)
36+
async execute() {
37+
let installDirectory = this.installDirectory;
38+
39+
// Node always call realpath on the module it executes, so we already
40+
// lost track of how the binary got called. To find it back, we need to
41+
// iterate over the PATH variable.
42+
if (typeof installDirectory === `undefined`)
43+
installDirectory = path.dirname(await which(`corepack`));
44+
45+
if (process.platform === `win32`) {
46+
return this.executeWin32(installDirectory);
47+
} else {
48+
return this.executePosix(installDirectory);
49+
}
50+
}
51+
52+
async executePosix(installDirectory: string) {
53+
const names = this.names.length === 0
54+
? SupportedPackageManagerSet
55+
: this.names;
56+
57+
for (const name of new Set(names)) {
58+
if (!isSupportedPackageManager(name))
59+
throw new UsageError(`Invalid package manager name '${name}'`);
60+
61+
for (const binName of this.context.engine.getBinariesFor(name)) {
62+
const file = path.join(installDirectory, binName);
63+
try {
64+
await fs.promises.unlink(file);
65+
} catch (err) {
66+
if (err.code !== `ENOENT`) {
67+
throw err;
68+
}
69+
}
70+
}
71+
}
72+
}
73+
74+
async executeWin32(installDirectory: string) {
75+
throw new UsageError(`This command isn't available on Windows at this time`);
76+
}
77+
}

sources/commands/Enable.ts

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {Command, UsageError} from 'clipanion';
2+
import fs from 'fs';
3+
import path from 'path';
4+
import which from 'which';
5+
6+
import {Context} from '../main';
7+
import {isSupportedPackageManager, SupportedPackageManagerSet} from '../types';
8+
9+
export class EnableCommand extends Command<Context> {
10+
static usage = Command.Usage({
11+
description: `Add the Corepack shims to the install directories`,
12+
details: `
13+
When run, this commmand will check whether the shims for the specified package managers can be found with the correct values inside the install directory. If not, or if they don't exist, they will be created.
14+
15+
By default it will locate the install directory by running the equivalent of \`which corepack\`, but this can be tweaked by explicitly passing the install directory via the \`--bin-folder\` flag.
16+
`,
17+
examples: [[
18+
`Enable all shims, putting them next to the \`corepath\` binary`,
19+
`$0 enable`,
20+
], [
21+
`Enable all shims, putting them in the specified directory`,
22+
`$0 enable --bin-folder /path/to/folder`,
23+
], [
24+
`Enable the Yarn shim only`,
25+
`$0 enable yarn`,
26+
]],
27+
});
28+
29+
@Command.String(`--install-directory`)
30+
installDirectory?: string;
31+
32+
@Command.Rest()
33+
names: Array<string> = [];
34+
35+
@Command.Path(`enable`)
36+
async execute() {
37+
let installDirectory = this.installDirectory;
38+
39+
// Node always call realpath on the module it executes, so we already
40+
// lost track of how the binary got called. To find it back, we need to
41+
// iterate over the PATH variable.
42+
if (typeof installDirectory === `undefined`)
43+
installDirectory = path.dirname(await which(`corepack`));
44+
45+
if (process.platform === `win32`) {
46+
return this.executeWin32(installDirectory);
47+
} else {
48+
return this.executePosix(installDirectory);
49+
}
50+
}
51+
52+
async executePosix(installDirectory: string) {
53+
// We use `eval` so that Webpack doesn't statically transform it.
54+
const manifestPath = eval(`require`).resolve(`corepack/package.json`);
55+
56+
const stubFolder = path.join(path.dirname(manifestPath), `shims`);
57+
if (!fs.existsSync(stubFolder))
58+
throw new Error(`Assertion failed: The stub folder doesn't exist`);
59+
60+
const names = this.names.length === 0
61+
? SupportedPackageManagerSet
62+
: this.names;
63+
64+
for (const name of new Set(names)) {
65+
if (!isSupportedPackageManager(name))
66+
throw new UsageError(`Invalid package manager name '${name}'`);
67+
68+
for (const binName of this.context.engine.getBinariesFor(name)) {
69+
const file = path.join(installDirectory, binName);
70+
const symlink = path.relative(installDirectory, path.join(stubFolder, binName));
71+
72+
if (fs.existsSync(file)) {
73+
const currentSymlink = await fs.promises.readlink(file);
74+
if (currentSymlink !== symlink) {
75+
await fs.promises.unlink(file);
76+
} else {
77+
return;
78+
}
79+
}
80+
81+
await fs.promises.symlink(symlink, file);
82+
}
83+
}
84+
}
85+
86+
async executeWin32(target: string) {
87+
throw new UsageError(`This command isn't available on Windows at this time`);
88+
}
89+
}

sources/commands/Hydrate.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ export class HydrateCommand extends Command<Context> {
1010
static usage = Command.Usage({
1111
description: `Import a package manager into the cache`,
1212
details: `
13-
This command unpacks a package manager archive into the cache. The archive must have been generated by the \`corepack pack\` command - no other will work.
14-
`,
13+
This command unpacks a package manager archive into the cache. The archive must have been generated by the \`corepack pack\` command - no other will work.
14+
`,
1515
examples: [[
1616
`Import a package manager in the cache`,
1717
`$0 hydrate corepack-yarn-2.2.2.tgz`,

sources/commands/Prepare.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ export class PrepareCommand extends Command<Context> {
1111
static usage = Command.Usage({
1212
description: `Generate a package manager archive`,
1313
details: `
14-
This command generates an archive for the specified package manager, in a format suitable for later hydratation via the \`corepack hydrate\` command.
14+
This command generates an archive for the specified package manager, in a format suitable for later hydratation via the \`corepack hydrate\` command.
1515
16-
If run without parameter, it'll extract the package manager spec from the active project. Otherwise an explicit spec string is required, that Corepack will resolve before installing and packing.
17-
`,
16+
If run without parameter, it'll extract the package manager spec from the active project. Otherwise an explicit spec string is required, that Corepack will resolve before installing and packing.
17+
`,
1818
examples: [[
1919
`Generate an archive from the active project`,
2020
`$0 prepare`,

sources/main.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {BaseContext, Cli, Command, UsageError} from 'clipanion';
22

33
import {Engine} from './Engine';
4+
import {DisableCommand} from './commands/Disable';
5+
import {EnableCommand} from './commands/Enable';
46
import {HydrateCommand} from './commands/Hydrate';
57
import {PrepareCommand} from './commands/Prepare';
68
import * as miscUtils from './miscUtils';
@@ -69,6 +71,8 @@ export async function main(argv: Array<string>, context: CustomContext & Partial
6971

7072
cli.register(Command.Entries.Help as any);
7173

74+
cli.register(EnableCommand);
75+
cli.register(DisableCommand);
7276
cli.register(HydrateCommand);
7377
cli.register(PrepareCommand);
7478

0 commit comments

Comments
 (0)