Skip to content

Commit

Permalink
feat(builtin): support for substitutions
Browse files Browse the repository at this point in the history
  • Loading branch information
thesayyn authored and alexeagle committed Aug 31, 2020
1 parent 24f19a9 commit 8a3f9b0
Show file tree
Hide file tree
Showing 8 changed files with 138 additions and 13 deletions.
6 changes: 5 additions & 1 deletion internal/pkg_web/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ filegroup(

nodejs_binary(
name = "assembler",
data = ["assembler.js"],
data = [
"assembler.js",
"//third_party/github.com/gjtorikian/isBinaryFile",
],
entry_point = ":assembler.js",
node_modules = ":node_modules_none",
)
Expand All @@ -32,6 +35,7 @@ jasmine_node_test(
srcs = ["assembler_spec.js"],
deps = [
"assembler.js",
"//third_party/github.com/gjtorikian/isBinaryFile",
],
)

Expand Down
54 changes: 51 additions & 3 deletions internal/pkg_web/assembler.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

const fs = require('fs');
const path = require('path');
const isBinary = require('isbinaryfile').isBinaryFileSync;

/**
* Create a new directory and any necessary subdirectories
Expand All @@ -29,9 +30,47 @@ function mkdirp(p) {
}
}

function getMappingsFromVolatileFile(volatileFilePath) {
const stampFileLines = fs.readFileSync(volatileFilePath, {encoding: 'utf-8'}).trim().split('\n');
const stampMap = {};
for (const line of stampFileLines) {
const [key, value] = line.split(' ');
stampMap[key] = value;
}
return stampMap;
}

function normalizeSubstitutions(substitutionsArg, volatileFilePath) {
const substitutions = JSON.parse(substitutionsArg);
const stampMap = getMappingsFromVolatileFile(volatileFilePath);

const normalizedSubstitutions = {};

for (const occurrence in substitutions) {
let substituteWith = substitutions[occurrence];
if (substituteWith.match(/^{.*?}$/)) {
substituteWith = substituteWith.replace(/^{(.*?)}$/, '$1');
if (!stampMap[substituteWith]) {
throw new Error(`Could not find ${substituteWith} key in volatile-status file.`);
}
substituteWith = stampMap[substituteWith];
}
normalizedSubstitutions[occurrence] = substituteWith;
}
return normalizedSubstitutions;
}

function main(params) {
const outdir = params.shift();

const volatileFilePath = params.shift();

const rawSubstitutions = params.shift().replace(/^'(.*)'$/, '$1');

const normalizedSubstitutions = normalizeSubstitutions(rawSubstitutions, volatileFilePath)

const substitutions = Object.entries(normalizedSubstitutions);

const rootDirs = [];
while (params.length && params[0] !== '--assets') {
let r = params.shift();
Expand All @@ -42,6 +81,7 @@ function main(params) {
}
// Always trim the longest prefix
rootDirs.sort((a, b) => b.length - a.length);

params.shift(); // --assets

function relative(execPath) {
Expand All @@ -56,12 +96,20 @@ function main(params) {
return execPath;
}

function copy(f) {
function copy(f, substitutions) {
if (fs.statSync(f).isDirectory()) {
for (const file of fs.readdirSync(f)) {
// Change paths to posix
copy(path.join(f, file).replace(/\\/g, '/'));
copy(path.join(f, file).replace(/\\/g, '/'), substitutions);
}
} else if (!isBinary(f)) {
const dest = path.join(outdir, relative(f));
let content = fs.readFileSync(f, {encoding: 'utf-8'});
substitutions.forEach(([occurrence, replaceWith]) => {
content = content.replace(occurrence, replaceWith);
});
fs.mkdirSync(path.dirname(dest), {recursive: true});
fs.writeFileSync(dest, content);
} else {
const dest = path.join(outdir, relative(f));
mkdirp(path.dirname(dest));
Expand All @@ -75,7 +123,7 @@ function main(params) {
// copied from within bazel-bin.
// See https://github.com/bazelbuild/rules_nodejs/pull/546.
for (const f of new Set(params)) {
copy(f);
copy(f, substitutions);
}
return 0;
}
Expand Down
36 changes: 29 additions & 7 deletions internal/pkg_web/assembler_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const fs = require('fs');

describe('assembler', () => {
const outdir = 'output';
const volatilePath = 'path/to/volatile-status.txt';
let testCount = 0;
beforeEach(() => {
const now = Date.now() + String(testCount++);
Expand All @@ -14,21 +15,42 @@ describe('assembler', () => {
fs.mkdirSync('path');
fs.mkdirSync('path/to');
fs.writeFileSync('path/to/thing1.txt', 'some content', {encoding: 'utf-8'});
fs.writeFileSync(volatilePath, 'TEST_KEY 41561')
});

it('should copy files', () => {
assembler.main([outdir, '--assets', 'path/to/thing1.txt']);
expect(fs.readdirSync('output/path/to')).toContain('thing1.txt');
expect(fs.readFileSync('output/path/to/thing1.txt', {encoding: 'utf-8'})).toBe('some content');
assembler.main([outdir, volatilePath, '{}', '--assets', 'path/to/thing1.txt']);
expect(fs.readdirSync('output/path/to')).toContain('thing1.txt');
expect(fs.readFileSync('output/path/to/thing1.txt', {
encoding: 'utf-8'
})).toBe('some content');
});

it('should strip longest rootdir', () => {
assembler.main([outdir, 'path', 'path/to', '--assets', 'path/to/thing1.txt']);
expect(fs.readdirSync('output')).toContain('thing1.txt');
assembler.main(
[outdir, volatilePath, '{}', 'path', 'path/to', '--assets', 'path/to/thing1.txt']);
expect(fs.readdirSync('output')).toContain('thing1.txt');
});

it('should handle nested directories', () => {
assembler.main([outdir, 'path', '--assets', 'path/to']);
expect(fs.readdirSync('output/to')).toContain('thing1.txt');
assembler.main([outdir, volatilePath, '{}', 'path', '--assets', 'path/to']);
expect(fs.readdirSync('output/to')).toContain('thing1.txt');
});

it('should replace contents with static text', () => {
assembler.main([
outdir, volatilePath, '{"some content":"some other content"}', '--assets',
'path/to/thing1.txt'
]);
expect(fs.readFileSync('output/path/to/thing1.txt', {
encoding: 'utf-8'
})).toBe('some other content');
})


it('should replace contents with dynamic text from volatile file', () => {
assembler.main(
[outdir, volatilePath, '{"content":"{TEST_KEY}"}', '--assets', 'path/to/thing1.txt']);
expect(fs.readFileSync('output/path/to/thing1.txt', {encoding: 'utf-8'})).toBe('some 41561');
})
});
12 changes: 10 additions & 2 deletions internal/pkg_web/pkg_web.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,17 @@ _ATTRS = {
allow_files = True,
doc = """Files which should be copied into the package""",
),
"substitutions": attr.string_dict(
doc = """Key-value pairs which are replaced in all the files while building the package.""",
),
"_assembler": attr.label(
default = "@build_bazel_rules_nodejs//internal/pkg_web:assembler",
executable = True,
cfg = "host",
),
}

def move_files(output_name, files, action_factory, var, assembler, root_paths):
def move_files(output_name, substitutions, version_file, files, action_factory, var, assembler, root_paths):
"""Moves files into an output directory
Args:
Expand All @@ -48,12 +51,15 @@ def move_files(output_name, files, action_factory, var, assembler, root_paths):
www_dir = action_factory.declare_directory(output_name)
args = action_factory.args()
args.add(www_dir.path)
args.add(version_file.path)
args.add(substitutions)
args.add_all(root_paths)
args.add("--assets")
args.add_all([f.path for f in files])
args.use_param_file("%s", use_always = True)

action_factory.run(
inputs = files,
inputs = files + [version_file],
outputs = [www_dir],
executable = assembler,
arguments = [args],
Expand Down Expand Up @@ -89,6 +95,8 @@ def _impl(ctx):

package_layout = move_files(
ctx.label.name,
ctx.attr.substitutions,
ctx.version_file,
ctx.files.srcs,
ctx.actions,
ctx.var,
Expand Down
26 changes: 26 additions & 0 deletions internal/pkg_web/test3/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
load("@build_bazel_rules_nodejs//:index.bzl", "pkg_web")
load("@npm_bazel_jasmine//:index.from_src.bzl", "jasmine_node_test")

package(default_visibility = ["//visibility:public"])

pkg_web(
name = "pkg",
srcs = [
"index.html",
],
substitutions = {
"THIS_SHOULD_BE_REPLACED": "THIS_SHOULD_BE_REPLACED_WITH_THIS",
},
)

jasmine_node_test(
name = "test",
srcs = ["spec.js"],
data = [
"index_golden.html_",
":pkg",
],
tags = [
"fix-windows",
],
)
1 change: 1 addition & 0 deletions internal/pkg_web/test3/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<html>THIS_SHOULD_BE_REPLACED</html>
1 change: 1 addition & 0 deletions internal/pkg_web/test3/index_golden.html_
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<html>THIS_SHOULD_BE_REPLACED_WITH_THIS</html>
15 changes: 15 additions & 0 deletions internal/pkg_web/test3/spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const fs = require('fs');
const path = require('path');

process.chdir(path.join(process.env['TEST_SRCDIR'], 'build_bazel_rules_nodejs'));
console.error(fs.readdirSync('.'));
describe('pkg_web', () => {
it('should match the golden file', () => {
const output = 'build_bazel_rules_nodejs/internal/pkg_web/test/pkg/index.html';
const golden = 'build_bazel_rules_nodejs/internal/pkg_web/test/index_golden.html_';
const actual = fs.readFileSync(require.resolve(output), {encoding: 'utf-8'});
const expected = fs.readFileSync(require.resolve(golden), {encoding: 'utf-8'});
// make the input hermetic by replacing the cache-buster timestamp
expect(actual.replace(/\?v=\d+/g, '?v=123')).toBe(expected);
});
});

0 comments on commit 8a3f9b0

Please sign in to comment.