Skip to content

Commit 4f3ec35

Browse files
committed
feat: add docker
1 parent 1aabe8a commit 4f3ec35

File tree

8 files changed

+105
-7
lines changed

8 files changed

+105
-7
lines changed

__tests__/utils/run-command.test.ts

+25-4
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@ beforeEach(() => {
1010
jest.clearAllMocks();
1111
});
1212

13-
const spawnMock = async <EV>(event: 'exit' | 'error' = 'exit', emitValue?: EV, delayMs = 200) => {
14-
const ee = new EventEmitter();
13+
class ChildMock extends EventEmitter {
14+
public stdout = new EventEmitter();
15+
}
1516

16-
const exitHandler = () => ee.emit(event, emitValue);
17+
const spawnMock = <EV>(event: 'exit' | 'error' = 'exit', emitValue?: EV, delayMs = 200) => {
18+
const child = new ChildMock();
19+
20+
const exitHandler = () => child.emit(event, emitValue);
1721
delay(delayMs).then(exitHandler);
1822

19-
childProcessMocked.spawn.mockReturnValueOnce(ee as ReturnType<typeof childProcess.spawn>);
23+
childProcessMocked.spawn.mockReturnValueOnce(child as ReturnType<typeof childProcess.spawn>);
24+
25+
return child;
2026
};
2127

2228
describe('spawnCommand', () => {
@@ -51,6 +57,21 @@ describe('spawnCommand', () => {
5157

5258
throw new Error(`command doesn't crashed`);
5359
});
60+
61+
test('should return stdout result', async () => {
62+
const child = spawnMock('exit');
63+
const chunks = ['Hello', 'World'].map((value) => Buffer.from(value, 'utf-8'));
64+
65+
const spawnResult = spawnCommand(command, args);
66+
67+
for (const chunk of chunks) {
68+
child.stdout.emit('data', chunk);
69+
}
70+
71+
const result = await spawnResult;
72+
73+
expect(result).toBe(chunks.join(''));
74+
});
5475
});
5576

5677
describe('runCommand', () => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { readableMultilineString } from 'src/utils/string';
2+
import { DOCKER_FILE_NAME, DOCKER_IGNORE_NAME } from '../docker.const';
3+
4+
interface GetDockerFileArgs {
5+
version: string;
6+
}
7+
8+
export const getDockerFile = ({ version }: GetDockerFileArgs) => readableMultilineString`
9+
FROM node:${version}
10+
WORKDIR /app
11+
12+
COPY package*.json ./
13+
RUN npm ci
14+
15+
COPY . .
16+
ENV NODE_ENV production
17+
RUN npm run build
18+
19+
EXPOSE 3000
20+
21+
CMD npm run start
22+
`;
23+
24+
export const dockerIgnore = readableMultilineString`
25+
node_modules
26+
dist
27+
build
28+
29+
.husky
30+
.git
31+
32+
${DOCKER_IGNORE_NAME}
33+
${DOCKER_FILE_NAME}
34+
.docker-compose.yml
35+
`;
36+
37+
export const defaultConfig = {
38+
getDockerFile,
39+
dockerIgnore,
40+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { jsCategoryState } from 'src/states/categories';
2+
import { defaultConfig } from './config/default.config';
3+
4+
export const [getConfig] = jsCategoryState.useConfigState({
5+
default: defaultConfig,
6+
});
+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export const DOCKER_FILE_NAME = 'DockerFile';
2+
export const DOCKER_IGNORE_NAME = '.dockerignore';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { addFileToRoot } from 'src/utils/fs';
2+
import { getNodeVersion } from 'src/utils/node';
3+
import { getConfig } from './docker.config';
4+
import { DOCKER_FILE_NAME, DOCKER_IGNORE_NAME } from './docker.const';
5+
6+
export const docker = async () => {
7+
const { getDockerFile, dockerIgnore } = getConfig();
8+
const version = await getNodeVersion();
9+
const dockerFile = getDockerFile({ version });
10+
11+
await addFileToRoot(DOCKER_FILE_NAME, dockerFile);
12+
await addFileToRoot(DOCKER_IGNORE_NAME, dockerIgnore);
13+
};

src/categories/js/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { testWorkflow } from './test-workflow/test-workflow.entrypoint';
1111
import { codeqlWorkflow } from './codeql-workflow/codeql-workflow.entrypoint';
1212
import { buildWorkflow } from './build-workflow/build-workflow.entrypoint';
1313
import { dependabot } from './dependabot/dependabot.entrypoint';
14+
import { docker } from './docker/docker.entrypoint';
1415

1516
// order have matter
1617
const options = {
@@ -27,6 +28,7 @@ const options = {
2728
codeqlWorkflow,
2829
buildWorkflow,
2930
dependabot,
31+
docker,
3032
};
3133

3234
export default {

src/utils/node.ts

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { runCommand, spawnCommand } from './run-command';
2+
3+
export const getNodeVersion = async () => {
4+
const versionConsoleOutput = await runCommand('node -v');
5+
6+
return versionConsoleOutput.trim().replace('v', '');
7+
};

src/utils/run-command.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import { spawn } from 'child_process';
2+
import { on } from 'events';
23

3-
export const spawnCommand = async (command: string, args: string[]) =>
4-
new Promise((res, rej) => {
4+
export const spawnCommand = async (command: string, args: string[]): Promise<string> => {
5+
return new Promise(async (res, rej) => {
56
const child = spawn(command, args);
7+
let result = Buffer.alloc(0);
68

79
child.on('error', (err) => rej(err));
8-
child.on('exit', () => res(undefined));
10+
child.on('exit', () => res(result.toString('utf-8')));
11+
12+
for await (const [chunk] of on(child.stdout, 'data')) {
13+
result = Buffer.concat([result, chunk]);
14+
}
915
});
16+
};
1017

1118
export const runCommand = async (command: string) => {
1219
const [name, ...args] = command.split(' ');

0 commit comments

Comments
 (0)