Skip to content

Commit bfe67b0

Browse files
committed
Add CI/CD pull file filter mechanism
1 parent 7db4396 commit bfe67b0

File tree

14 files changed

+212
-99
lines changed

14 files changed

+212
-99
lines changed

packages/rjs-build/src/Plugin.ts

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -62,32 +62,27 @@ export class Plugin {
6262

6363
private resolveBuildModulePath(): string {
6464
const buildConfig: TJSON = this.fetchBuildConfig();
65-
const buildModuleReferences: string[] = _config.buildModuleNames.map(
66-
(buildModuleName: string) =>
67-
join(this.pluginDirectoryPath, buildModuleName)
68-
);
69-
buildConfig[_config.buildModuleReferenceKey] &&
70-
buildModuleReferences.push(
71-
resolve(
72-
this.pluginDirectoryPath,
73-
buildConfig[_config.buildModuleReferenceKey] as string
74-
)
75-
);
65+
const buildModuleReferences: string[] = [
66+
..._config.buildModuleNames.map((reference: string) =>
67+
join(this.pluginDirectoryPath, reference)
68+
),
69+
buildConfig[_config.buildModuleReferenceKey] as string
70+
].filter((reference: string | undefined) => !!reference);
7671

7772
let buildModulePath: string;
7873
while (!buildModulePath && buildModuleReferences.length) {
7974
const buildModuleReference: string = buildModuleReferences.shift();
8075
try {
81-
buildModulePath = require.resolve(buildModuleReference);
76+
buildModulePath = require.resolve(buildModuleReference, {
77+
paths: [this.pluginDirectoryPath]
78+
});
8279
buildModulePath = !/\.json$/.test(buildModulePath)
8380
? buildModulePath
8481
: null;
8582
} catch {}
8683
}
8784

88-
if (!buildModulePath || /\.json$/.test(buildModulePath)) return null;
89-
90-
return buildModulePath;
85+
return buildModulePath ?? null;
9186
}
9287

9388
private async fetchBuildInterface(): Promise<TPluginInterfaceCallable> {

packages/rjs-handler/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
test/unit/.*.__repo

packages/rjs-handler/src/Handler.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { resolve } from "path";
1+
import { normalize, resolve } from "path";
22

33
import {
44
TAtomicSerializable,
@@ -41,13 +41,19 @@ export class Handler {
4141
>;
4242
private readonly rpcController: RPCController | null;
4343
private readonly configuredHostnames: string[];
44+
private readonly deployPaths: string[];
4445

45-
constructor(env: Partial<IHandlerEnv>, options: TJSON = {}) {
46+
constructor(
47+
env: Partial<IHandlerEnv>,
48+
options: TJSON = {},
49+
deployPaths: string[] = []
50+
) {
4651
this.env = new Options(env ?? {}, {
4752
dev: false,
4853
cwd: process.cwd()
4954
}).object;
5055
this.config = new TypeResolver(options ?? {}, GLOBAL_CONFIG_DEFAULTS);
56+
this.deployPaths = deployPaths.map((path: string) => normalize(path));
5157

5258
this.configuredHostnames = [
5359
"localhost",
@@ -223,6 +229,7 @@ export class Handler {
223229
handler = new POSTHandlerContext(
224230
request,
225231
this.config,
232+
this.deployPaths,
226233
this.env.cwd,
227234
this.env.dev
228235
);

packages/rjs-handler/src/handler-context/POSTHandlerContext.ts

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
import { dirname, basename, join, resolve } from "path";
2-
import { existsSync, readdirSync, writeFileSync, cpSync, rmSync } from "fs";
1+
import { dirname, basename, join, resolve, normalize } from "path";
2+
import {
3+
existsSync,
4+
readdirSync,
5+
writeFileSync,
6+
cpSync,
7+
rmSync,
8+
statSync,
9+
mkdirSync
10+
} from "fs";
311
import { IncomingMessage, OutgoingHttpHeaders } from "http";
412
import { request } from "https";
513
import { createHmac, timingSafeEqual } from "crypto";
@@ -37,11 +45,13 @@ export class POSTHandlerContext extends AHandlerContext {
3745

3846
private readonly cwd: string;
3947
private readonly localEnv: LocalEnv;
48+
private readonly deployPaths: string[];
4049
private readonly repo: IGitRemote | null;
4150

4251
constructor(
4352
request: Request,
4453
config: TypeResolver,
54+
deployPaths: string[],
4555
cwd: string,
4656
dev: boolean
4757
) {
@@ -82,6 +92,7 @@ export class POSTHandlerContext extends AHandlerContext {
8292

8393
this.cwd = cwd;
8494
this.localEnv = new LocalEnv(dev, cwd);
95+
this.deployPaths = deployPaths ?? [];
8596
}
8697

8798
private requestAPI(
@@ -221,11 +232,49 @@ export class POSTHandlerContext extends AHandlerContext {
221232
force: true
222233
});
223234

224-
readdirSync(dirPath).forEach((path: string) => {
225-
cpSync(join(dirPath, path), join(this.cwd, path), {
226-
force: true,
227-
recursive: true
228-
});
235+
readdirSync(dirPath, {
236+
recursive: true
237+
}).forEach((rawPath: string | Buffer) => {
238+
const path = rawPath.toString();
239+
240+
if (this.deployPaths.length) {
241+
const maxDirectoryDepth = 50;
242+
243+
let isWhitelisted = false;
244+
let i = 0;
245+
let traversalPath: string = normalize(path);
246+
do {
247+
if (
248+
this.deployPaths.includes(
249+
normalize(traversalPath)
250+
)
251+
) {
252+
isWhitelisted = true;
253+
254+
break;
255+
}
256+
257+
traversalPath = normalize(
258+
join(traversalPath, "..")
259+
);
260+
} while (
261+
traversalPath !== "." &&
262+
++i < maxDirectoryDepth
263+
);
264+
265+
if (!isWhitelisted) return;
266+
}
267+
268+
const sourcePath: string = join(dirPath, path);
269+
const targetPath: string = join(this.cwd, path);
270+
statSync(sourcePath).isDirectory()
271+
? mkdirSync(targetPath, {
272+
recursive: true
273+
})
274+
: cpSync(sourcePath, targetPath, {
275+
force: true,
276+
recursive: true
277+
});
229278
});
230279

231280
rmSync(dirPath, {

packages/rjs-handler/test/unit/_api.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ const initHandler = (appWorkingDir) => {
1616
},
1717
"www": "never",
1818
"hostnames": [ "example.org", "other.example.org" ]
19-
});
19+
}, [
20+
"README.md",
21+
"test/file.txt"
22+
]);
2023
}
2124

2225
const defaultHandler = initHandler(

packages/rjs-handler/test/unit/webhook/webhook.test.js

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
const { createHmac } = require("crypto");
2-
31
const { initHandler, requestWithHandler } = require("../_api");
42

3+
54
const SECRET = "secret";
65
const PAYLOAD = JSON.stringify({
76
foo: "bar"
87
});
98

9+
1010
const requestWithWebhookHandler = () => {
1111
return requestWithHandler(
1212
initHandler(require("path").join(__dirname, "./app")),
@@ -16,6 +16,7 @@ const requestWithWebhookHandler = () => {
1616
headers: {
1717
"User-Agent": "GitHub-Hookshot/044aadd",
1818
"X-Hub-Signature-256": `sha256=${
19+
require("crypto").
1920
createHmac("sha256", SECRET)
2021
.update(PAYLOAD)
2122
.digest("hex")
@@ -26,37 +27,55 @@ const requestWithWebhookHandler = () => {
2627
);
2728
};
2829

29-
const README_FILE_PATH = require("path").join(__dirname, "./app", "README.md");
30+
31+
const getFilePath = name => {
32+
return require("path").join(__dirname, "./app", name);
33+
}
34+
const fileExists = name => {
35+
return require("fs").existsSync(getFilePath(name));
36+
}
37+
3038
const README_OVERRIDE_DATA = "OVERRIDE";
31-
require("fs").writeFileSync(README_FILE_PATH, README_OVERRIDE_DATA);
39+
40+
require("fs").writeFileSync(getFilePath("README.md"), README_OVERRIDE_DATA);
41+
require("fs").rmSync(getFilePath("EMPTY.md"), { force: true });
42+
require("fs").rmSync(getFilePath("test/file-2.txt"), { force: true });
43+
3244

3345
new UnitTest("POST_ GitHub:/ (dummy)")
3446
.actual(async () => {
3547
const res = await requestWithWebhookHandler();
3648

37-
const fileExists = name => {
38-
return require("fs")
39-
.existsSync(require("path").join(__dirname, "./app", name));
40-
}
41-
42-
new UnitTest("POST GitHub:/ (dummy) → .env (from local)")
43-
.actual(fileExists(".env"))
44-
.expect(true);
49+
await new Promise(r => setTimeout(() => {
50+
new UnitTest("POST GitHub:/ (dummy) → .env (from local)")
51+
.actual(fileExists(".env"))
52+
.expect(true);
53+
54+
new UnitTest("POST GitHub:/ (dummy) → test/file.txt (from remote)")
55+
.actual(fileExists("test/file.txt"))
56+
.expect(true);
4557

46-
new UnitTest("POST GitHub:/ (dummy) test/file.txt (from remote)")
47-
.actual(fileExists("test/file.txt"))
48-
.expect(true);
58+
new UnitTest("POST GitHub:/ (dummy) ¬ test/file-2.txt (non-whitelisted)")
59+
.actual(fileExists("test/file-2.txt"))
60+
.expect(false);
4961

50-
new UnitTest("POST GitHub:/ (dummy) ¬ non-existing.txt")
51-
.actual(fileExists("non-existing.txt"))
52-
.expect(false);
53-
54-
setTimeout(() => {
62+
new UnitTest("POST GitHub:/ (dummy) ¬ EMPTY.md (non-whitelisted)")
63+
.actual(fileExists("EMPTY.md"))
64+
.expect(false);
65+
5566
new UnitTest("POST GitHub:/ (dummy) ¬ README.md override")
56-
.actual(require("fs").readFileSync(README_FILE_PATH).toString().trim() !== README_OVERRIDE_DATA)
67+
.actual(
68+
require("fs")
69+
.readFileSync(getFilePath("README.md"))
70+
.toString()
71+
.trim()
72+
!== README_OVERRIDE_DATA
73+
)
5774
.expect(true);
58-
}, 750); // TODO: Improve (reliability)
59-
75+
76+
r();
77+
}, 750)); // TODO: Improve reliability (wait for emit files visibility)
78+
6079
return res;
6180
})
6281
.expect({

packages/rjs-server/src/Instance.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,33 @@ import _config from "./_config.json";
1313
export function createInstance(
1414
env?: Partial<IHandlerEnv>,
1515
options?: TJSON,
16+
deployPaths?: string[],
1617
clusterSize?: IClusterConstraints
1718
): Promise<Instance> {
1819
return new Promise((resolve) => {
19-
const instance: Instance = new Instance(env, options, clusterSize).on(
20-
"online",
21-
() => resolve(instance)
22-
);
20+
const instance: Instance = new Instance(
21+
env,
22+
options,
23+
deployPaths,
24+
clusterSize
25+
).on("online", () => resolve(instance));
2326
});
2427
}
2528

2629
export class Instance extends Cluster<ISerialRequest, ISerialResponse> {
2730
constructor(
2831
env?: Partial<IHandlerEnv>,
2932
options?: TJSON,
33+
deployPaths?: string[],
3034
clusterConstraints?: IClusterConstraints
3135
) {
3236
super(
3337
{
3438
modulePath: join(__dirname, "adapter"),
3539
options: {
3640
env,
37-
options
41+
options,
42+
deployPaths
3843
}
3944
},
4045
{

packages/rjs-server/src/Server.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,16 @@ export interface IServerEnv extends IHandlerEnv {
3636
export function createServer(
3737
env: IServerEnv,
3838
options?: TJSON,
39+
deployPaths?: string[],
3940
clusterSize?: IClusterConstraints
4041
): Promise<Server> {
4142
return new Promise((resolve) => {
42-
const server: Server = new Server(env, options, clusterSize).on(
43-
"online",
44-
() => resolve(server)
45-
);
43+
const server: Server = new Server(
44+
env,
45+
options,
46+
deployPaths,
47+
clusterSize
48+
).on("online", () => resolve(server));
4649
});
4750
}
4851

@@ -57,7 +60,7 @@ export class Server extends EventEmitter {
5760
if (!param) return null;
5861
if (param instanceof Buffer) return param;
5962

60-
const potentialPath: string = resolve(param);
63+
const potentialPath: string = resolve(cwd, param);
6164
if (!existsSync(potentialPath)) return param;
6265

6366
return readFileSync(potentialPath);
@@ -90,6 +93,7 @@ export class Server extends EventEmitter {
9093
constructor(
9194
env: IServerEnv,
9295
options?: TJSON,
96+
deployPaths?: string[],
9397
clusterSize?: IClusterConstraints
9498
) {
9599
super();
@@ -102,6 +106,7 @@ export class Server extends EventEmitter {
102106
this.instance = new Instance(
103107
env,
104108
options,
109+
deployPaths,
105110
this.env.dev
106111
? {
107112
processes: 1,

packages/rjs-server/src/adapter.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,16 @@ import { ISerialRequest, ISerialResponse } from "./.shared/global.interfaces";
33

44
import { IHandlerEnv, Handler } from "@rapidjs.org/rjs-handler";
55

6-
export default function (coreOptions: { env: IHandlerEnv; options: TJSON }) {
7-
const handler: Handler = new Handler(coreOptions.env, coreOptions.options);
6+
export default function (coreOptions: {
7+
env: IHandlerEnv;
8+
options: TJSON;
9+
deployPaths: string[];
10+
}) {
11+
const handler: Handler = new Handler(
12+
coreOptions.env,
13+
coreOptions.options,
14+
coreOptions.deployPaths
15+
);
816
// TODO: Await for preretrieval done if not dev mode
917

1018
return async (sReq: ISerialRequest): Promise<ISerialResponse> => {

packages/rjs/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<p align="center">
22
<a href="https://rapidjs.org" target="_blank">
3-
<img src="https://rapidjs.org/assets/img/repo-preview.jpg" alt="https://rapidjs.org" width="830">
3+
<img src="https://rapidjs.org/assets/img/repo-preview.png" alt="https://rapidjs.org" width="830">
44
</a>
55
<h1 align="center">rJS</h1>
66
</p>

0 commit comments

Comments
 (0)