Skip to content

Commit 8a3f9b0

Browse files
thesayynalexeagle
authored andcommitted
feat(builtin): support for substitutions
1 parent 24f19a9 commit 8a3f9b0

8 files changed

Lines changed: 138 additions & 13 deletions

File tree

internal/pkg_web/BUILD.bazel

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@ filegroup(
2121

2222
nodejs_binary(
2323
name = "assembler",
24-
data = ["assembler.js"],
24+
data = [
25+
"assembler.js",
26+
"//third_party/github.com/gjtorikian/isBinaryFile",
27+
],
2528
entry_point = ":assembler.js",
2629
node_modules = ":node_modules_none",
2730
)
@@ -32,6 +35,7 @@ jasmine_node_test(
3235
srcs = ["assembler_spec.js"],
3336
deps = [
3437
"assembler.js",
38+
"//third_party/github.com/gjtorikian/isBinaryFile",
3539
],
3640
)
3741

internal/pkg_web/assembler.js

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
const fs = require('fs');
1919
const path = require('path');
20+
const isBinary = require('isbinaryfile').isBinaryFileSync;
2021

2122
/**
2223
* Create a new directory and any necessary subdirectories
@@ -29,9 +30,47 @@ function mkdirp(p) {
2930
}
3031
}
3132

33+
function getMappingsFromVolatileFile(volatileFilePath) {
34+
const stampFileLines = fs.readFileSync(volatileFilePath, {encoding: 'utf-8'}).trim().split('\n');
35+
const stampMap = {};
36+
for (const line of stampFileLines) {
37+
const [key, value] = line.split(' ');
38+
stampMap[key] = value;
39+
}
40+
return stampMap;
41+
}
42+
43+
function normalizeSubstitutions(substitutionsArg, volatileFilePath) {
44+
const substitutions = JSON.parse(substitutionsArg);
45+
const stampMap = getMappingsFromVolatileFile(volatileFilePath);
46+
47+
const normalizedSubstitutions = {};
48+
49+
for (const occurrence in substitutions) {
50+
let substituteWith = substitutions[occurrence];
51+
if (substituteWith.match(/^{.*?}$/)) {
52+
substituteWith = substituteWith.replace(/^{(.*?)}$/, '$1');
53+
if (!stampMap[substituteWith]) {
54+
throw new Error(`Could not find ${substituteWith} key in volatile-status file.`);
55+
}
56+
substituteWith = stampMap[substituteWith];
57+
}
58+
normalizedSubstitutions[occurrence] = substituteWith;
59+
}
60+
return normalizedSubstitutions;
61+
}
62+
3263
function main(params) {
3364
const outdir = params.shift();
3465

66+
const volatileFilePath = params.shift();
67+
68+
const rawSubstitutions = params.shift().replace(/^'(.*)'$/, '$1');
69+
70+
const normalizedSubstitutions = normalizeSubstitutions(rawSubstitutions, volatileFilePath)
71+
72+
const substitutions = Object.entries(normalizedSubstitutions);
73+
3574
const rootDirs = [];
3675
while (params.length && params[0] !== '--assets') {
3776
let r = params.shift();
@@ -42,6 +81,7 @@ function main(params) {
4281
}
4382
// Always trim the longest prefix
4483
rootDirs.sort((a, b) => b.length - a.length);
84+
4585
params.shift(); // --assets
4686

4787
function relative(execPath) {
@@ -56,12 +96,20 @@ function main(params) {
5696
return execPath;
5797
}
5898

59-
function copy(f) {
99+
function copy(f, substitutions) {
60100
if (fs.statSync(f).isDirectory()) {
61101
for (const file of fs.readdirSync(f)) {
62102
// Change paths to posix
63-
copy(path.join(f, file).replace(/\\/g, '/'));
103+
copy(path.join(f, file).replace(/\\/g, '/'), substitutions);
64104
}
105+
} else if (!isBinary(f)) {
106+
const dest = path.join(outdir, relative(f));
107+
let content = fs.readFileSync(f, {encoding: 'utf-8'});
108+
substitutions.forEach(([occurrence, replaceWith]) => {
109+
content = content.replace(occurrence, replaceWith);
110+
});
111+
fs.mkdirSync(path.dirname(dest), {recursive: true});
112+
fs.writeFileSync(dest, content);
65113
} else {
66114
const dest = path.join(outdir, relative(f));
67115
mkdirp(path.dirname(dest));
@@ -75,7 +123,7 @@ function main(params) {
75123
// copied from within bazel-bin.
76124
// See https://github.com/bazelbuild/rules_nodejs/pull/546.
77125
for (const f of new Set(params)) {
78-
copy(f);
126+
copy(f, substitutions);
79127
}
80128
return 0;
81129
}

internal/pkg_web/assembler_spec.js

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const fs = require('fs');
44

55
describe('assembler', () => {
66
const outdir = 'output';
7+
const volatilePath = 'path/to/volatile-status.txt';
78
let testCount = 0;
89
beforeEach(() => {
910
const now = Date.now() + String(testCount++);
@@ -14,21 +15,42 @@ describe('assembler', () => {
1415
fs.mkdirSync('path');
1516
fs.mkdirSync('path/to');
1617
fs.writeFileSync('path/to/thing1.txt', 'some content', {encoding: 'utf-8'});
18+
fs.writeFileSync(volatilePath, 'TEST_KEY 41561')
1719
});
1820

1921
it('should copy files', () => {
20-
assembler.main([outdir, '--assets', 'path/to/thing1.txt']);
21-
expect(fs.readdirSync('output/path/to')).toContain('thing1.txt');
22-
expect(fs.readFileSync('output/path/to/thing1.txt', {encoding: 'utf-8'})).toBe('some content');
22+
assembler.main([outdir, volatilePath, '{}', '--assets', 'path/to/thing1.txt']);
23+
expect(fs.readdirSync('output/path/to')).toContain('thing1.txt');
24+
expect(fs.readFileSync('output/path/to/thing1.txt', {
25+
encoding: 'utf-8'
26+
})).toBe('some content');
2327
});
2428

2529
it('should strip longest rootdir', () => {
26-
assembler.main([outdir, 'path', 'path/to', '--assets', 'path/to/thing1.txt']);
27-
expect(fs.readdirSync('output')).toContain('thing1.txt');
30+
assembler.main(
31+
[outdir, volatilePath, '{}', 'path', 'path/to', '--assets', 'path/to/thing1.txt']);
32+
expect(fs.readdirSync('output')).toContain('thing1.txt');
2833
});
2934

3035
it('should handle nested directories', () => {
31-
assembler.main([outdir, 'path', '--assets', 'path/to']);
32-
expect(fs.readdirSync('output/to')).toContain('thing1.txt');
36+
assembler.main([outdir, volatilePath, '{}', 'path', '--assets', 'path/to']);
37+
expect(fs.readdirSync('output/to')).toContain('thing1.txt');
3338
});
39+
40+
it('should replace contents with static text', () => {
41+
assembler.main([
42+
outdir, volatilePath, '{"some content":"some other content"}', '--assets',
43+
'path/to/thing1.txt'
44+
]);
45+
expect(fs.readFileSync('output/path/to/thing1.txt', {
46+
encoding: 'utf-8'
47+
})).toBe('some other content');
48+
})
49+
50+
51+
it('should replace contents with dynamic text from volatile file', () => {
52+
assembler.main(
53+
[outdir, volatilePath, '{"content":"{TEST_KEY}"}', '--assets', 'path/to/thing1.txt']);
54+
expect(fs.readFileSync('output/path/to/thing1.txt', {encoding: 'utf-8'})).toBe('some 41561');
55+
})
3456
});

internal/pkg_web/pkg_web.bzl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,17 @@ _ATTRS = {
2525
allow_files = True,
2626
doc = """Files which should be copied into the package""",
2727
),
28+
"substitutions": attr.string_dict(
29+
doc = """Key-value pairs which are replaced in all the files while building the package.""",
30+
),
2831
"_assembler": attr.label(
2932
default = "@build_bazel_rules_nodejs//internal/pkg_web:assembler",
3033
executable = True,
3134
cfg = "host",
3235
),
3336
}
3437

35-
def move_files(output_name, files, action_factory, var, assembler, root_paths):
38+
def move_files(output_name, substitutions, version_file, files, action_factory, var, assembler, root_paths):
3639
"""Moves files into an output directory
3740
3841
Args:
@@ -48,12 +51,15 @@ def move_files(output_name, files, action_factory, var, assembler, root_paths):
4851
www_dir = action_factory.declare_directory(output_name)
4952
args = action_factory.args()
5053
args.add(www_dir.path)
54+
args.add(version_file.path)
55+
args.add(substitutions)
5156
args.add_all(root_paths)
5257
args.add("--assets")
5358
args.add_all([f.path for f in files])
5459
args.use_param_file("%s", use_always = True)
60+
5561
action_factory.run(
56-
inputs = files,
62+
inputs = files + [version_file],
5763
outputs = [www_dir],
5864
executable = assembler,
5965
arguments = [args],
@@ -89,6 +95,8 @@ def _impl(ctx):
8995

9096
package_layout = move_files(
9197
ctx.label.name,
98+
ctx.attr.substitutions,
99+
ctx.version_file,
92100
ctx.files.srcs,
93101
ctx.actions,
94102
ctx.var,

internal/pkg_web/test3/BUILD.bazel

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_web")
2+
load("@npm_bazel_jasmine//:index.from_src.bzl", "jasmine_node_test")
3+
4+
package(default_visibility = ["//visibility:public"])
5+
6+
pkg_web(
7+
name = "pkg",
8+
srcs = [
9+
"index.html",
10+
],
11+
substitutions = {
12+
"THIS_SHOULD_BE_REPLACED": "THIS_SHOULD_BE_REPLACED_WITH_THIS",
13+
},
14+
)
15+
16+
jasmine_node_test(
17+
name = "test",
18+
srcs = ["spec.js"],
19+
data = [
20+
"index_golden.html_",
21+
":pkg",
22+
],
23+
tags = [
24+
"fix-windows",
25+
],
26+
)

internal/pkg_web/test3/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<html>THIS_SHOULD_BE_REPLACED</html>
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<html>THIS_SHOULD_BE_REPLACED_WITH_THIS</html>

internal/pkg_web/test3/spec.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
process.chdir(path.join(process.env['TEST_SRCDIR'], 'build_bazel_rules_nodejs'));
5+
console.error(fs.readdirSync('.'));
6+
describe('pkg_web', () => {
7+
it('should match the golden file', () => {
8+
const output = 'build_bazel_rules_nodejs/internal/pkg_web/test/pkg/index.html';
9+
const golden = 'build_bazel_rules_nodejs/internal/pkg_web/test/index_golden.html_';
10+
const actual = fs.readFileSync(require.resolve(output), {encoding: 'utf-8'});
11+
const expected = fs.readFileSync(require.resolve(golden), {encoding: 'utf-8'});
12+
// make the input hermetic by replacing the cache-buster timestamp
13+
expect(actual.replace(/\?v=\d+/g, '?v=123')).toBe(expected);
14+
});
15+
});

0 commit comments

Comments
 (0)