Split into modules and reorganize

This commit is contained in:
Stéphan Kochen
2020-08-25 18:55:38 +02:00
parent 708e36eaf4
commit 5d3ed3c119
10 changed files with 241 additions and 225 deletions
+1
View File
@@ -21,6 +21,7 @@
"webpack-sources": "^1.4.3"
},
"scripts": {
"check": "tsc",
"build": "./build.js -p",
"build-dev": "./build.js",
"fmt": "prettier --write src"
+52
View File
@@ -0,0 +1,52 @@
import { Command } from "clipanion";
import {
Cache,
CommandContext,
Configuration,
LocatorHash,
Project,
StreamReport,
} from "@yarnpkg/core";
// Internal command that fetches a single locator.
// Used from within Nix to build the cache for the project.
export default class FetchOneCommand extends Command<CommandContext> {
@Command.String()
locatorHash: string = ``;
@Command.Path(`nixify`, `fetch-one`)
async execute() {
const configuration = await Configuration.find(
this.context.cwd,
this.context.plugins
);
const { project } = await Project.find(configuration, this.context.cwd);
const cache = await Cache.find(configuration);
const fetcher = configuration.makeFetcher();
const report = await StreamReport.start(
{ configuration, stdout: this.context.stdout },
async (report) => {
const pkg = project.originalPackages.get(
this.locatorHash as LocatorHash
);
if (!pkg) {
report.reportError(0, `Invalid locator hash`);
return;
}
await fetcher.fetch(pkg, {
checksums: project.storedChecksums,
project,
cache,
fetcher,
report,
});
}
);
return report.exitCode();
}
}
+84
View File
@@ -0,0 +1,84 @@
import { Command } from "clipanion";
import { Filename, npath, ppath, xfs } from "@yarnpkg/fslib";
import { getPnpPath } from "@yarnpkg/plugin-pnp";
import {
CommandContext,
Configuration,
Project,
StreamReport,
} from "@yarnpkg/core";
import binWrapperNodeModulesTmpl from "./tmpl/bin-wrapper-node-modules.sh.in";
import binWrapperPnpTmpl from "./tmpl/bin-wrapper-pnp.sh.in";
const supportedLinkers = [`pnp`, `node-modules`];
// Internal command that creates wrappers for binaries.
// Used inside the Nix install phase.
export default class InstallBinCommand extends Command<CommandContext> {
@Command.String()
binDir: string = ``;
@Command.Path(`nixify`, `install-bin`)
async execute() {
const configuration = await Configuration.find(
this.context.cwd,
this.context.plugins
);
const { project, workspace } = await Project.find(
configuration,
this.context.cwd
);
const report = await StreamReport.start(
{ configuration, stdout: this.context.stdout },
async (report) => {
if (!workspace || workspace.manifest.bin.size === 0) {
return;
}
let nodeLinker = configuration.get(`nodeLinker`);
if (!supportedLinkers.includes(nodeLinker)) {
nodeLinker = `node-modules`;
report.reportWarning(
0,
`The nodeLinker ${nodeLinker} is not supported - executables may have trouble finding dependencies`
);
}
const binDir = npath.toPortablePath(this.binDir);
const pnpPath = getPnpPath(project).main;
for (const [name, scriptInput] of workspace.manifest.bin) {
const binPath = ppath.join(binDir, name as Filename);
const scriptPath = ppath.join(
project.cwd,
npath.toPortablePath(scriptInput)
);
let script;
switch (nodeLinker) {
case `pnp`:
script = binWrapperPnpTmpl
.replace(`@@PNP_PATH@@`, pnpPath)
.replace(`@@SCRIPT_PATH@@`, scriptPath);
break;
case `node-modules`:
script = binWrapperNodeModulesTmpl.replace(
`@@SCRIPT_PATH@@`,
scriptPath
);
break;
default:
throw Error(`Invalid nodeLinker ${nodeLinker}`);
}
xfs.writeFileSync(binPath, script);
xfs.chmodSync(binPath, 0o755);
}
}
);
return report.exitCode();
}
}
+97
View File
@@ -0,0 +1,97 @@
import { Cache, Project, Report, structUtils } from "@yarnpkg/core";
import { Filename, PortablePath, ppath, xfs } from "@yarnpkg/fslib";
import defaultExprTmpl from "./tmpl/default.nix.in";
import projectExprTmpl from "./tmpl/yarn-project.nix.in";
// Generator function that runs after `yarn install`.
export default async (project: Project, cache: Cache, report: Report) => {
const { configuration, cwd } = project;
const yarnPathAbs = configuration.get(`yarnPath`);
const cacheFolderAbs = configuration.get(`cacheFolder`);
let yarnPath = ppath.relative(cwd, yarnPathAbs);
if (yarnPath.startsWith(`../`)) {
yarnPath = yarnPathAbs;
report.reportWarning(
0,
`The Yarn path ${yarnPathAbs} is outside the project - it may not be reachable by the Nix build`
);
}
let cacheFolder = ppath.relative(cwd, cacheFolderAbs);
if (cacheFolder.startsWith(`../`)) {
cacheFolder = cacheFolderAbs;
report.reportWarning(
0,
`The cache folder ${cacheFolderAbs} is outside the project - it may not be reachable by the Nix build`
);
}
for (const source of configuration.sources.values()) {
if (!source.startsWith(`<`)) {
const relativeSource = ppath.relative(cwd, source as PortablePath);
if (relativeSource.startsWith(`../`)) {
report.reportWarning(
0,
`The config file ${source} is outside the project - it may not be reachable by the Nix build`
);
}
}
}
// Build a list of cache entries so Nix can fetch them.
let cacheEntries = [];
const cacheFiles = new Set(xfs.readdirSync(cache.cwd));
for (const pkg of project.storedPackages.values()) {
const checksum = project.storedChecksums.get(pkg.locatorHash);
if (!checksum) continue;
const cachePath = cache.getLocatorPath(pkg, checksum);
if (!cachePath) continue;
const filename = ppath.basename(cachePath);
if (!cacheFiles.has(filename)) continue;
let name = structUtils.slugifyIdent(pkg).replace(/^@/, "_at_");
if (pkg.version) {
name += `-${pkg.version}`;
}
const sha512 = checksum.split(`/`).pop();
cacheEntries.push([
`name = ${JSON.stringify(name)};`,
`filename = ${JSON.stringify(filename)};`,
`sha512 = ${JSON.stringify(sha512)};`,
`locatorHash = ${JSON.stringify(pkg.locatorHash)};`,
]);
}
// Render the Nix expression.
const ident = project.topLevelWorkspace.manifest.name;
const projectName = ident ? structUtils.stringifyIdent(ident) : `workspace`;
const projectExpr = projectExprTmpl
.replace(`@@PROJECT_NAME@@`, JSON.stringify(projectName))
.replace(`@@YARN_PATH@@`, JSON.stringify(yarnPath))
.replace(`@@CACHE_FOLDER@@`, JSON.stringify(cacheFolder))
.replace(
`@@CACHE_ENTRIES@@`,
`[\n` +
[...cacheEntries]
.map((entry) => ` { ${entry.join(` `)} }\n`)
.join(``) +
` ]`
);
const projectExprPath = ppath.join(cwd, `yarn-project.nix` as Filename);
xfs.writeFileSync(projectExprPath, projectExpr);
// Create a wrapper if it does not exist yet.
const defaultExprPath = ppath.join(cwd, `default.nix` as Filename);
if (!xfs.existsSync(defaultExprPath)) {
xfs.writeFileSync(defaultExprPath, defaultExprTmpl);
report.reportInfo(
0,
`A minimal default.nix was created. You may want to customize it.`
);
}
};
+4 -224
View File
@@ -1,227 +1,7 @@
import { Command } from "clipanion";
import { Filename, npath, ppath, xfs } from "@yarnpkg/fslib";
import { getPnpPath } from "@yarnpkg/plugin-pnp";
import {
Cache,
CommandContext,
Configuration,
Hooks,
LocatorHash,
Plugin,
Project,
Report,
StreamReport,
structUtils,
} from "@yarnpkg/core";
import binWrapperPnpTmpl from "./bin-wrapper-pnp.sh.in";
import binWrapperNodeModulesTmpl from "./bin-wrapper-node-modules.sh.in";
import defaultExprTmpl from "./default.nix.in";
import projectExprTmpl from "./yarn-project.nix.in";
const supportedLinkers = [`pnp`, `node-modules`];
// Generator function that runs after `yarn install`.
const generate = async (project: Project, cache: Cache, report: Report) => {
const { configuration, cwd } = project;
const yarnPathAbs = configuration.get(`yarnPath`);
const cacheFolderAbs = configuration.get(`cacheFolder`);
let yarnPath = ppath.relative(cwd, yarnPathAbs);
if (yarnPath.startsWith(`../`)) {
yarnPath = yarnPathAbs;
report.reportWarning(
0,
`The Yarn path ${yarnPathAbs} is outside the project - it may not be reachable by the Nix build`
);
}
let cacheFolder = ppath.relative(cwd, cacheFolderAbs);
if (cacheFolder.startsWith(`../`)) {
cacheFolder = cacheFolderAbs;
report.reportWarning(
0,
`The cache folder ${cacheFolderAbs} is outside the project - it may not be reachable by the Nix build`
);
}
for (const source of configuration.sources.values()) {
if (!source.startsWith(`<`)) {
const relativeSource = ppath.relative(cwd, source);
if (relativeSource.startsWith(`../`)) {
report.reportWarning(
0,
`The config file ${source} is outside the project - it may not be reachable by the Nix build`
);
}
}
}
// Build a list of cache entries so Nix can fetch them.
let cacheEntries = [];
const cacheFiles = new Set(xfs.readdirSync(cache.cwd));
for (const pkg of project.storedPackages.values()) {
const checksum = project.storedChecksums.get(pkg.locatorHash);
if (!checksum) continue;
const cachePath = cache.getLocatorPath(pkg, checksum);
if (!cachePath) continue;
const filename = ppath.basename(cachePath);
if (!cacheFiles.has(filename)) continue;
let name = structUtils.slugifyIdent(pkg).replace(/^@/, "_at_");
if (pkg.version) {
name += `-${pkg.version}`;
}
const sha512 = checksum.split(`/`).pop();
cacheEntries.push([
`name = ${JSON.stringify(name)};`,
`filename = ${JSON.stringify(filename)};`,
`sha512 = ${JSON.stringify(sha512)};`,
`locatorHash = ${JSON.stringify(pkg.locatorHash)};`,
]);
}
// Render the Nix expression.
const ident = project.topLevelWorkspace.manifest.name;
const projectName = ident ? structUtils.stringifyIdent(ident) : `workspace`;
const projectExpr = projectExprTmpl
.replace(`@@PROJECT_NAME@@`, JSON.stringify(projectName))
.replace(`@@YARN_PATH@@`, JSON.stringify(yarnPath))
.replace(`@@CACHE_FOLDER@@`, JSON.stringify(cacheFolder))
.replace(
`@@CACHE_ENTRIES@@`,
`[\n` +
[...cacheEntries]
.map((entry) => ` { ${entry.join(` `)} }\n`)
.join(``) +
` ]`
);
const projectExprPath = ppath.join(cwd, `yarn-project.nix` as Filename);
xfs.writeFileSync(projectExprPath, projectExpr);
// Create a wrapper if it does not exist yet.
const defaultExprPath = ppath.join(cwd, `default.nix` as Filename);
if (!xfs.existsSync(defaultExprPath)) {
xfs.writeFileSync(defaultExprPath, defaultExprTmpl);
report.reportInfo(
0,
`A minimal default.nix was created. You may want to customize it.`
);
}
};
// Internal command that fetches a single locator.
// Used from within Nix to build the cache for the project.
class FetchOneCommand extends Command<CommandContext> {
@Command.String()
locatorHash: string = ``;
@Command.Path(`nixify`, `fetch-one`)
async execute() {
const configuration = await Configuration.find(
this.context.cwd,
this.context.plugins
);
const { project } = await Project.find(configuration, this.context.cwd);
const cache = await Cache.find(configuration);
const fetcher = configuration.makeFetcher();
const report = await StreamReport.start(
{ configuration, stdout: this.context.stdout },
async (report) => {
const pkg = project.originalPackages.get(
this.locatorHash as LocatorHash
);
if (!pkg) {
report.reportError(0, `Invalid locator hash`);
return;
}
await fetcher.fetch(pkg, {
checksums: project.storedChecksums,
project,
cache,
fetcher,
report,
});
}
);
return report.exitCode();
}
}
// Internal command that creates wrappers for binaries.
// Used inside the Nix install phase.
class InstallBinCommand extends Command<CommandContext> {
@Command.String()
binDir: string = ``;
@Command.Path(`nixify`, `install-bin`)
async execute() {
const configuration = await Configuration.find(
this.context.cwd,
this.context.plugins
);
const { project, workspace } = await Project.find(
configuration,
this.context.cwd
);
const report = await StreamReport.start(
{ configuration, stdout: this.context.stdout },
async (report) => {
if (!workspace || workspace.manifest.bin.size === 0) {
return;
}
let nodeLinker = configuration.get(`nodeLinker`);
if (!supportedLinkers.includes(nodeLinker)) {
nodeLinker = `node-modules`;
report.reportWarning(
0,
`The nodeLinker ${nodeLinker} is not supported - executables may have trouble finding dependencies`
);
}
const binDir = npath.toPortablePath(this.binDir);
const pnpPath = getPnpPath(project).main;
for (const [name, scriptInput] of workspace.manifest.bin) {
const binPath = ppath.join(binDir, name as Filename);
const scriptPath = ppath.join(
project.cwd,
npath.toPortablePath(scriptInput)
);
let script;
switch (nodeLinker) {
case `pnp`:
script = binWrapperPnpTmpl
.replace(`@@PNP_PATH@@`, pnpPath)
.replace(`@@SCRIPT_PATH@@`, scriptPath);
break;
case `node-modules`:
script = binWrapperNodeModulesTmpl.replace(
`@@SCRIPT_PATH@@`,
scriptPath
);
break;
default:
throw Error(`Invalid nodeLinker ${nodeLinker}`);
}
xfs.writeFileSync(binPath, script);
xfs.chmodSync(binPath, 0o755);
}
}
);
}
}
import FetchOneCommand from "./FetchOneCommand";
import InstallBinCommand from "./InstallBinCommand";
import generate from "./generate";
import { Hooks, Plugin } from "@yarnpkg/core";
const plugin: Plugin<Hooks> = {
commands: [FetchOneCommand, InstallBinCommand],
+3 -1
View File
@@ -4,7 +4,9 @@
"module": "commonjs",
"target": "es2018",
"lib": ["es2018"],
"strict": true
"types": ["node"],
"strict": true,
"noEmit": true
},
"include": ["src/**/*.ts"]
}