Skip to content

Commit a464889

Browse files
authored
Merge pull request #1 from Dcard/docker-build-2
Implement docker-build plugin
2 parents 76d2dbd + 8c8e7e7 commit a464889

17 files changed

+474
-5
lines changed

.pnp.js

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Yarn plugins for Yarn 2.
77
## Plugins
88

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

1213
## License

packages/changed/src/utils/getWorkspaceDependencies.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
import { Workspace } from '@yarnpkg/core';
2-
3-
const DEPENDENCY_TYPES = ['devDependencies', 'dependencies'];
1+
import { Workspace, Manifest } from '@yarnpkg/core';
42

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

119
function addDependency({ manifest }: Workspace): void {
12-
for (const depType of DEPENDENCY_TYPES) {
13-
for (const [, descriptor] of manifest.getForScope(depType)) {
10+
for (const depType of Manifest.hardDependencies) {
11+
for (const descriptor of manifest.getForScope(depType).values()) {
1412
const dep = project.tryWorkspaceByDescriptor(descriptor);
1513

1614
if (dep && !dependencies.has(dep)) {

packages/docker-build/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# yarn-plugin-docker-build
2+
3+
Build a Docker image for a workspace.
4+
5+
## Installation
6+
7+
Install the latest plugin.
8+
9+
```sh
10+
yarn plugin import https://github.com/Dcard/yarn-plugins/releases/latest/download/plugin-docker-build.js
11+
```

packages/docker-build/package.json

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "@dcard/yarn-plugin-docker-build",
3+
"version": "0.0.1",
4+
"main": "src/index.ts",
5+
"scripts": {
6+
"build": "builder build plugin"
7+
},
8+
"dependencies": {
9+
"clipanion": "^2.1.5"
10+
},
11+
"devDependencies": {
12+
"@yarnpkg/builder": "^2.0.0-rc.21",
13+
"@yarnpkg/cli": "^2.0.0-rc.33",
14+
"@yarnpkg/core": "^2.0.0-rc.27",
15+
"@yarnpkg/fslib": "^2.0.0-rc.20",
16+
"@yarnpkg/plugin-pack": "^2.0.0-rc.19",
17+
"typescript": "^3.9.2"
18+
}
19+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
import { BaseCommand } from '@yarnpkg/cli';
2+
import { Command } from 'clipanion';
3+
import {
4+
Configuration,
5+
Project,
6+
Cache,
7+
structUtils,
8+
StreamReport,
9+
execUtils,
10+
} from '@yarnpkg/core';
11+
import getDockerFilePath from '../utils/getDockerFilePath';
12+
import getRequiredWorkspaces from '../utils/getRequiredWorkspaces';
13+
import copyRcFile from '../utils/copyRcFile';
14+
import { toFilename, ppath, xfs } from '@yarnpkg/fslib';
15+
import copyPlugins from '../utils/copyPlugins';
16+
import copyYarnRelease from '../utils/copyYarnRelease';
17+
import copyManifests from '../utils/copyManifests';
18+
import copyCacheMarkedFiles from '../utils/copyCacheMarkedFiles';
19+
import generateLockfile from '../utils/generateLockfile';
20+
import packWorkspace from '../utils/packWorkspace';
21+
22+
export default class DockerBuildCommand extends BaseCommand {
23+
@Command.String()
24+
public workspaceName!: string;
25+
26+
@Command.Proxy()
27+
public args: string[] = [];
28+
29+
@Command.String('-f,--file')
30+
public dockerFilePath?: string;
31+
32+
public static usage = Command.Usage({
33+
category: 'Docker-related commands',
34+
description: 'Build a Docker image for a workspace',
35+
details: `
36+
This command will build a efficient Docker image which only contains production dependencies for the specified workspace.
37+
38+
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.
39+
40+
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/
41+
`,
42+
examples: [
43+
['Build a Docker image for a workspace', 'yarn docker build @foo/bar'],
44+
[
45+
'Pass additional arguments to docker build command',
46+
'yarn docker build @foo/bar -t image-tag',
47+
],
48+
],
49+
});
50+
51+
@Command.Path('docker', 'build')
52+
public async execute(): Promise<number> {
53+
const configuration = await Configuration.find(
54+
this.context.cwd,
55+
this.context.plugins,
56+
);
57+
const { project } = await Project.find(configuration, this.context.cwd);
58+
59+
const workspace = project.getWorkspaceByIdent(
60+
structUtils.parseIdent(this.workspaceName),
61+
);
62+
63+
const requiredWorkspaces = getRequiredWorkspaces({
64+
project,
65+
workspaces: [workspace],
66+
production: true,
67+
});
68+
69+
const dockerFilePath = await getDockerFilePath(
70+
workspace,
71+
this.dockerFilePath,
72+
);
73+
74+
const cache = await Cache.find(configuration);
75+
76+
const report = await StreamReport.start(
77+
{
78+
configuration,
79+
stdout: this.context.stdout,
80+
includeLogs: !this.context.quiet,
81+
},
82+
async (report) => {
83+
await report.startTimerPromise('Resolution Step', async () => {
84+
await project.resolveEverything({ report, cache });
85+
});
86+
87+
await report.startTimerPromise('Fetch Step', async () => {
88+
await project.fetchEverything({ report, cache });
89+
});
90+
91+
await xfs.mktempPromise(async (cwd) => {
92+
const manifestDir = ppath.join(cwd, toFilename('manifests'));
93+
const packDir = ppath.join(cwd, toFilename('packs'));
94+
95+
await report.startTimerPromise('Copy files', async () => {
96+
await copyRcFile({
97+
destination: manifestDir,
98+
project,
99+
report,
100+
});
101+
102+
await copyPlugins({
103+
destination: manifestDir,
104+
project,
105+
report,
106+
});
107+
108+
await copyYarnRelease({
109+
destination: manifestDir,
110+
project,
111+
report,
112+
});
113+
114+
await copyManifests({
115+
destination: manifestDir,
116+
workspaces: project.workspaces,
117+
report,
118+
});
119+
120+
await copyCacheMarkedFiles({
121+
destination: manifestDir,
122+
project,
123+
cache,
124+
report,
125+
});
126+
127+
await generateLockfile({
128+
destination: manifestDir,
129+
project,
130+
report,
131+
});
132+
});
133+
134+
for (const ws of requiredWorkspaces) {
135+
const name = ws.manifest.name
136+
? structUtils.stringifyIdent(ws.manifest.name)
137+
: '';
138+
139+
await report.startTimerPromise(
140+
`Pack workspace ${name}`,
141+
async () => {
142+
await packWorkspace({
143+
workspace: ws,
144+
report,
145+
destination: packDir,
146+
});
147+
},
148+
);
149+
}
150+
151+
await execUtils.pipevp(
152+
'docker',
153+
['build', ...this.args, '-f', dockerFilePath, '.'],
154+
{
155+
cwd,
156+
strict: true,
157+
stdin: this.context.stdin,
158+
stdout: this.context.stdout,
159+
stderr: this.context.stderr,
160+
},
161+
);
162+
});
163+
},
164+
);
165+
166+
return report.exitCode();
167+
}
168+
}

packages/docker-build/src/index.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Plugin } from '@yarnpkg/core';
2+
3+
import build from './commands/build';
4+
5+
const plugin: Plugin = {
6+
commands: [build],
7+
};
8+
9+
export default plugin;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { PortablePath, xfs, ppath } from '@yarnpkg/fslib';
2+
import { Cache, Project, Report } from '@yarnpkg/core';
3+
4+
export default async function copyCacheMarkedFiles({
5+
destination,
6+
project,
7+
cache,
8+
report,
9+
}: {
10+
destination: PortablePath;
11+
project: Project;
12+
cache: Cache;
13+
report: Report;
14+
}): Promise<void> {
15+
for (const src of cache.markedFiles) {
16+
const path = ppath.relative(project.cwd, src);
17+
18+
report.reportInfo(null, path);
19+
await xfs.copyPromise(ppath.join(destination, path), src);
20+
}
21+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { PortablePath, ppath, xfs } from '@yarnpkg/fslib';
2+
import { Workspace, Manifest, Report } from '@yarnpkg/core';
3+
4+
export default async function copyManifests({
5+
destination,
6+
workspaces,
7+
report,
8+
}: {
9+
destination: PortablePath;
10+
workspaces: Workspace[];
11+
report: Report;
12+
}): Promise<void> {
13+
for (const ws of workspaces) {
14+
const path = ppath.join(ws.relativeCwd, Manifest.fileName);
15+
const dest = ppath.join(destination, path);
16+
const data = {};
17+
18+
ws.manifest.exportTo(data);
19+
20+
report.reportInfo(null, path);
21+
await xfs.mkdirpPromise(ppath.dirname(dest));
22+
await xfs.writeJsonPromise(dest, data);
23+
}
24+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { PortablePath, xfs, toFilename, ppath } from '@yarnpkg/fslib';
2+
import { Project, Report } from '@yarnpkg/core';
3+
4+
export default async function copyPlugins({
5+
destination,
6+
project,
7+
report,
8+
}: {
9+
destination: PortablePath;
10+
project: Project;
11+
report: Report;
12+
}): Promise<void> {
13+
const pluginDir = ppath.join(toFilename('.yarn'), toFilename('plugins'));
14+
15+
report.reportInfo(null, pluginDir);
16+
await xfs.copyPromise(
17+
ppath.join(destination, pluginDir),
18+
ppath.join(project.cwd, pluginDir),
19+
{ overwrite: true },
20+
);
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { Project, Report } from '@yarnpkg/core';
2+
import { PortablePath, ppath, xfs } from '@yarnpkg/fslib';
3+
4+
export default async function copyRcFile({
5+
destination,
6+
project,
7+
report,
8+
}: {
9+
destination: PortablePath;
10+
project: Project;
11+
report: Report;
12+
}): Promise<void> {
13+
const filename = project.configuration.get('rcFilename');
14+
15+
report.reportInfo(null, filename);
16+
await xfs.copyPromise(
17+
ppath.join(destination, filename),
18+
ppath.join(project.cwd, filename),
19+
{ overwrite: true },
20+
);
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { PortablePath, xfs, ppath } from '@yarnpkg/fslib';
2+
import { Project, Report } from '@yarnpkg/core';
3+
4+
export default async function copyYarnRelease({
5+
destination,
6+
project,
7+
report,
8+
}: {
9+
destination: PortablePath;
10+
project: Project;
11+
report: Report;
12+
}): Promise<void> {
13+
const src = project.configuration.get('yarnPath');
14+
const path = ppath.relative(project.cwd, src);
15+
const dest = ppath.join(destination, path);
16+
17+
report.reportInfo(null, path);
18+
await xfs.copyPromise(dest, src, {
19+
overwrite: true,
20+
});
21+
}

0 commit comments

Comments
 (0)