Skip to content

Commit

Permalink
Merge pull request #1 from Dcard/docker-build-2
Browse files Browse the repository at this point in the history
Implement docker-build plugin
  • Loading branch information
tommy351 authored May 15, 2020
2 parents 76d2dbd + 8c8e7e7 commit a464889
Show file tree
Hide file tree
Showing 17 changed files with 474 additions and 5 deletions.
21 changes: 21 additions & 0 deletions .pnp.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Yarn plugins for Yarn 2.
## Plugins

- [yarn-plugin-changed](packages/changed) - List and run a command on changed workspaces and their dependents.
- [yarn-plugin-docker-build](packages/docker-build) - Build a Docker image for a workspace.
- [yarn-plugin-tsconfig-references](packages/tsconfig-references) - Update `references` in `tsconfig.json` when adding/removing workspaces.

## License
Expand Down
8 changes: 3 additions & 5 deletions packages/changed/src/utils/getWorkspaceDependencies.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
import { Workspace } from '@yarnpkg/core';

const DEPENDENCY_TYPES = ['devDependencies', 'dependencies'];
import { Workspace, Manifest } from '@yarnpkg/core';

export default function getWorkspaceDependencies(
workspace: Workspace,
Expand All @@ -9,8 +7,8 @@ export default function getWorkspaceDependencies(
const dependencies = new Set<Workspace>();

function addDependency({ manifest }: Workspace): void {
for (const depType of DEPENDENCY_TYPES) {
for (const [, descriptor] of manifest.getForScope(depType)) {
for (const depType of Manifest.hardDependencies) {
for (const descriptor of manifest.getForScope(depType).values()) {
const dep = project.tryWorkspaceByDescriptor(descriptor);

if (dep && !dependencies.has(dep)) {
Expand Down
11 changes: 11 additions & 0 deletions packages/docker-build/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# yarn-plugin-docker-build

Build a Docker image for a workspace.

## Installation

Install the latest plugin.

```sh
yarn plugin import https://github.com/Dcard/yarn-plugins/releases/latest/download/plugin-docker-build.js
```
19 changes: 19 additions & 0 deletions packages/docker-build/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "@dcard/yarn-plugin-docker-build",
"version": "0.0.1",
"main": "src/index.ts",
"scripts": {
"build": "builder build plugin"
},
"dependencies": {
"clipanion": "^2.1.5"
},
"devDependencies": {
"@yarnpkg/builder": "^2.0.0-rc.21",
"@yarnpkg/cli": "^2.0.0-rc.33",
"@yarnpkg/core": "^2.0.0-rc.27",
"@yarnpkg/fslib": "^2.0.0-rc.20",
"@yarnpkg/plugin-pack": "^2.0.0-rc.19",
"typescript": "^3.9.2"
}
}
168 changes: 168 additions & 0 deletions packages/docker-build/src/commands/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
import { BaseCommand } from '@yarnpkg/cli';
import { Command } from 'clipanion';
import {
Configuration,
Project,
Cache,
structUtils,
StreamReport,
execUtils,
} from '@yarnpkg/core';
import getDockerFilePath from '../utils/getDockerFilePath';
import getRequiredWorkspaces from '../utils/getRequiredWorkspaces';
import copyRcFile from '../utils/copyRcFile';
import { toFilename, ppath, xfs } from '@yarnpkg/fslib';
import copyPlugins from '../utils/copyPlugins';
import copyYarnRelease from '../utils/copyYarnRelease';
import copyManifests from '../utils/copyManifests';
import copyCacheMarkedFiles from '../utils/copyCacheMarkedFiles';
import generateLockfile from '../utils/generateLockfile';
import packWorkspace from '../utils/packWorkspace';

export default class DockerBuildCommand extends BaseCommand {
@Command.String()
public workspaceName!: string;

@Command.Proxy()
public args: string[] = [];

@Command.String('-f,--file')
public dockerFilePath?: string;

public static usage = Command.Usage({
category: 'Docker-related commands',
description: 'Build a Docker image for a workspace',
details: `
This command will build a efficient Docker image which only contains production dependencies for the specified workspace.
You have to create a Dockerfile in your workspace or your project. You can also specify the path to Dockerfile using the "-f, --file" option.
Additional arguments can be passed to "docker build" directly, please check the Docker docs for more info: https://docs.docker.com/engine/reference/commandline/build/
`,
examples: [
['Build a Docker image for a workspace', 'yarn docker build @foo/bar'],
[
'Pass additional arguments to docker build command',
'yarn docker build @foo/bar -t image-tag',
],
],
});

@Command.Path('docker', 'build')
public async execute(): Promise<number> {
const configuration = await Configuration.find(
this.context.cwd,
this.context.plugins,
);
const { project } = await Project.find(configuration, this.context.cwd);

const workspace = project.getWorkspaceByIdent(
structUtils.parseIdent(this.workspaceName),
);

const requiredWorkspaces = getRequiredWorkspaces({
project,
workspaces: [workspace],
production: true,
});

const dockerFilePath = await getDockerFilePath(
workspace,
this.dockerFilePath,
);

const cache = await Cache.find(configuration);

const report = await StreamReport.start(
{
configuration,
stdout: this.context.stdout,
includeLogs: !this.context.quiet,
},
async (report) => {
await report.startTimerPromise('Resolution Step', async () => {
await project.resolveEverything({ report, cache });
});

await report.startTimerPromise('Fetch Step', async () => {
await project.fetchEverything({ report, cache });
});

await xfs.mktempPromise(async (cwd) => {
const manifestDir = ppath.join(cwd, toFilename('manifests'));
const packDir = ppath.join(cwd, toFilename('packs'));

await report.startTimerPromise('Copy files', async () => {
await copyRcFile({
destination: manifestDir,
project,
report,
});

await copyPlugins({
destination: manifestDir,
project,
report,
});

await copyYarnRelease({
destination: manifestDir,
project,
report,
});

await copyManifests({
destination: manifestDir,
workspaces: project.workspaces,
report,
});

await copyCacheMarkedFiles({
destination: manifestDir,
project,
cache,
report,
});

await generateLockfile({
destination: manifestDir,
project,
report,
});
});

for (const ws of requiredWorkspaces) {
const name = ws.manifest.name
? structUtils.stringifyIdent(ws.manifest.name)
: '';

await report.startTimerPromise(
`Pack workspace ${name}`,
async () => {
await packWorkspace({
workspace: ws,
report,
destination: packDir,
});
},
);
}

await execUtils.pipevp(
'docker',
['build', ...this.args, '-f', dockerFilePath, '.'],
{
cwd,
strict: true,
stdin: this.context.stdin,
stdout: this.context.stdout,
stderr: this.context.stderr,
},
);
});
},
);

return report.exitCode();
}
}
9 changes: 9 additions & 0 deletions packages/docker-build/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Plugin } from '@yarnpkg/core';

import build from './commands/build';

const plugin: Plugin = {
commands: [build],
};

export default plugin;
21 changes: 21 additions & 0 deletions packages/docker-build/src/utils/copyCacheMarkedFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PortablePath, xfs, ppath } from '@yarnpkg/fslib';
import { Cache, Project, Report } from '@yarnpkg/core';

export default async function copyCacheMarkedFiles({
destination,
project,
cache,
report,
}: {
destination: PortablePath;
project: Project;
cache: Cache;
report: Report;
}): Promise<void> {
for (const src of cache.markedFiles) {
const path = ppath.relative(project.cwd, src);

report.reportInfo(null, path);
await xfs.copyPromise(ppath.join(destination, path), src);
}
}
24 changes: 24 additions & 0 deletions packages/docker-build/src/utils/copyManifests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { PortablePath, ppath, xfs } from '@yarnpkg/fslib';
import { Workspace, Manifest, Report } from '@yarnpkg/core';

export default async function copyManifests({
destination,
workspaces,
report,
}: {
destination: PortablePath;
workspaces: Workspace[];
report: Report;
}): Promise<void> {
for (const ws of workspaces) {
const path = ppath.join(ws.relativeCwd, Manifest.fileName);
const dest = ppath.join(destination, path);
const data = {};

ws.manifest.exportTo(data);

report.reportInfo(null, path);
await xfs.mkdirpPromise(ppath.dirname(dest));
await xfs.writeJsonPromise(dest, data);
}
}
21 changes: 21 additions & 0 deletions packages/docker-build/src/utils/copyPlugins.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PortablePath, xfs, toFilename, ppath } from '@yarnpkg/fslib';
import { Project, Report } from '@yarnpkg/core';

export default async function copyPlugins({
destination,
project,
report,
}: {
destination: PortablePath;
project: Project;
report: Report;
}): Promise<void> {
const pluginDir = ppath.join(toFilename('.yarn'), toFilename('plugins'));

report.reportInfo(null, pluginDir);
await xfs.copyPromise(
ppath.join(destination, pluginDir),
ppath.join(project.cwd, pluginDir),
{ overwrite: true },
);
}
21 changes: 21 additions & 0 deletions packages/docker-build/src/utils/copyRcFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Project, Report } from '@yarnpkg/core';
import { PortablePath, ppath, xfs } from '@yarnpkg/fslib';

export default async function copyRcFile({
destination,
project,
report,
}: {
destination: PortablePath;
project: Project;
report: Report;
}): Promise<void> {
const filename = project.configuration.get('rcFilename');

report.reportInfo(null, filename);
await xfs.copyPromise(
ppath.join(destination, filename),
ppath.join(project.cwd, filename),
{ overwrite: true },
);
}
21 changes: 21 additions & 0 deletions packages/docker-build/src/utils/copyYarnRelease.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PortablePath, xfs, ppath } from '@yarnpkg/fslib';
import { Project, Report } from '@yarnpkg/core';

export default async function copyYarnRelease({
destination,
project,
report,
}: {
destination: PortablePath;
project: Project;
report: Report;
}): Promise<void> {
const src = project.configuration.get('yarnPath');
const path = ppath.relative(project.cwd, src);
const dest = ppath.join(destination, path);

report.reportInfo(null, path);
await xfs.copyPromise(dest, src, {
overwrite: true,
});
}
Loading

0 comments on commit a464889

Please sign in to comment.