Skip to content
This repository was archived by the owner on Sep 10, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/tests/*/bazel-*
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ test_typescript:
test_webpack:
(cd tests/webpack && bazel test //...)

test_polymer-cli:
(cd tests/polymer-cli && bazel test //...)

test_mocha:
(cd tests/mocha && bazel test //...)

test_all: test_helloworld test_lyrics test_express test_namespace test_typescript test_webpack test_mocha
test_all: test_helloworld test_lyrics test_express test_namespace test_typescript test_webpack test_polymer-cli test_mocha
20 changes: 13 additions & 7 deletions node/internal/node_binary.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def create_launcher(ctx, output_dir, node, manifest):
# The package path is the path
package_path = []
if ctx.label.workspace_root:
package_path.append(ctx.label.workspace_root)
package_path.append(ctx.label.workspace_root.replace("external/", "", 1))
if ctx.label.package:
package_path.append(ctx.label.package)

Expand All @@ -52,25 +52,31 @@ def create_launcher(ctx, output_dir, node, manifest):
# Namespace where node and node_modules assets have been
# built.
'TARGET_NAME="%s"' % ctx.attr.target,
# Workspace name where this rule is defined
'WORKSPACE_NAME="%s"' % ctx.workspace_name,
# Based on the way the script has been invoked, $PACKAGE_PATH
# can exist in various locations. We need to discover this
# and assign the value to $TARGET_PATH.
'TARGET_PATH=""' ,

'ENTRYPOINT="%s"' % entrypoint,

'if [[ -e "${0}_files/${NODE_EXE}" ]]; then',
' TARGET_PATH="${0}_files/"',
#' echo "Matched [standalone script] or [bazel test] context [${TARGET_PATH}]"',
'',
'elif [[ -e "${0}_files/${NODE_EXE}" ]]; then',
' TARGET_PATH="${0}_files/"',
#' echo "Matched [standalone script] or [bazel test] context [${TARGET_PATH}]"',
'',
'elif [[ -e "${0}.runfiles/__main__/${PACKAGE_PATH}/${TARGET_NAME}/${NODE_EXE}" ]]; then',
' TARGET_PATH="${0}.runfiles/__main__/${PACKAGE_PATH}/${TARGET_NAME}/"',
' TARGET_PATH="${0}.runfiles/__main__/${PACKAGE_PATH}/${TARGET_NAME}/"',
#' echo "Matched [bazel run] context [${TARGET_PATH}]"',
'',
'elif [[ -e "${0}.runfiles/${WORKSPACE_NAME}/${PACKAGE_PATH}/${TARGET_NAME}/${NODE_EXE}" ]]; then',
' TARGET_PATH="${0}.runfiles/${WORKSPACE_NAME}/${PACKAGE_PATH}/${TARGET_NAME}/"',
#' echo "Matched [bazel build] or [bazel run] context in a named workspace [${TARGET_PATH}]"',
'',
'elif [[ -e "${0}.runfiles/${PACKAGE_PATH}/${TARGET_NAME}/${NODE_EXE}" ]]; then',
' TARGET_PATH="${0}.runfiles/${PACKAGE_PATH}/${TARGET_NAME}/"',
#' echo "Matched [bazel build] context [${TARGET_PATH}]"',
'',
'elif [[ -e "${PACKAGE_PATH}/${TARGET_NAME}/${NODE_EXE}" ]]; then',
' TARGET_PATH="${PACKAGE_PATH}/${TARGET_NAME}/"',
#' echo "Matched [bazel test TESTRULE] context [${TARGET_PATH}]"',
Expand Down
56 changes: 34 additions & 22 deletions node/internal/parse_yarn_lock.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ function main() {
//
const entries = Object.keys(yarn.object).map(key => makeYarnEntry(key, yarn.object[key]));

// For all top-level folders in the node_modules directory that
// contain a package.json file...
const getModulesIn = p => fs.readdirSync(p)
.filter(f =>
fs.statSync(path.join(p, f)).isDirectory() &&
fs.existsSync(path.join(p, f, 'package.json')) &&
fs.statSync(path.join(p, f, 'package.json')).isFile());

// ... parse ithem
const modules = getModulesIn('node_modules').map(dir => parseNodeModulePackageJson(dir));

// Iterate all the modules and merge the information from yarn into
// the module
// Scan the node_modules directory and find all top-level ('foo') or scoped (@bar/baz)
// modules, i.e. folders which contain a package.json file...
const getModulesIn = p => fs.readdirSync(p).filter(f => isPackage(p, undefined, f));
const findScopes = p => fs.readdirSync(p).filter(f => f.startsWith("@") && fs.statSync(path.join(p, f)).isDirectory());
const getModulesInScope = (p, s) => fs.readdirSync(path.join(p, s)).filter(f => isPackage(p, s, f));

// ...then parse the package.json files to collect the metadata
let topLevelModuleDirs = getModulesIn('node_modules');
let scopeModuleDirs = findScopes('node_modules').map(scope => getModulesInScope('node_modules', scope).map(m => scope+'/'+m)).reduce((a, b) => a.concat(b), []);
let moduleDirs = topLevelModuleDirs.concat(scopeModuleDirs);
const modules = moduleDirs.map(dir => parseNodeModulePackageJson(dir));

// Iterate all the modules and merge the information from yarn into the module
modules.forEach(module => mergePackageJsonWithYarnEntry(entries, module));

// Didn't realize that the nodejs module ecosystem can contain
Expand Down Expand Up @@ -68,6 +68,12 @@ function main() {
print("# EOF");
}

function isPackage(p, s, f) {
let dir = s ? path.join(p, s, f) : path.join(p, f);
return fs.statSync(dir).isDirectory() &&
fs.existsSync(path.join(dir, 'package.json')) &&
fs.statSync(path.join(dir, 'package.json')).isFile()
}

/**
* Given a list of yarn entries and a target module, find an exact
Expand Down Expand Up @@ -202,9 +208,10 @@ function breakCircularDependencies(modules) {
}
});

// Each entry in the cluster must have no other outgoing
// dependencies
entry.deps = new Set();
// Each entry in the cluster must have no connections to other
// dependencies in the cluster, or on the cluster pseudo-dep
pseudo.deps.forEach(circ => entry.deps.delete(circ));
entry.deps.delete(pseudo);
});

// Store this new pseudo-module in the modules list
Expand Down Expand Up @@ -278,17 +285,22 @@ function printNodeModule(module) {
print(``);
printJson(module);
print(`node_module(`);
print(` name = "${module.name}",`);
print(` name = "${module.yarn ? module.yarn.label : module.name}",`);

// SCC pseudomodule wont have 'yarn' property
if (module.yarn) {
const url = module.yarn.url || module.url;
const sha1 = module.yarn.sha1;
const executables = module.executables;


print(` module_name = "${module.name}",`);
print(` version = "${module.version}",`);
print(` package_json = "node_modules/${module.name}/package.json",`);
print(` srcs = glob(["node_modules/${module.name}/**/*"], exclude = ["node_modules/${module.name}/package.json"]),`);
// Exclude filenames with spaces: Bazel can't cope with them (we just have to hope they aren't needed later...)
print(` srcs = glob(["node_modules/${module.name}/**/*"], exclude = [
"node_modules/${module.name}/package.json",
"**/* *",
]),`);
if (url) {
print(` url = "${url}",`);
}
Expand All @@ -303,12 +315,12 @@ function printNodeModule(module) {
}
print(` },`);
}

}

if (deps && deps.size) {
print(` deps = [`);
deps.forEach(dep => {
print(` ":${dep.name}",`);
print(` ":${dep.yarn ? dep.yarn.label : dep.name}",`);
});
print(` ],`);
}
Expand All @@ -327,7 +339,7 @@ function printNodeModuleAll(modules) {
print(` name = "_all_",`);
print(` deps = [`);
modules.forEach(module => {
print(` ":${module.name}",`);
print(` ":${module.yarn ? module.yarn.label : module.name}",`);
});
print(` ],`);
print(`)`);
Expand Down
9 changes: 9 additions & 0 deletions tests/polymer-cli/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package(default_visibility = ["//visibility:public"])

sh_test(
name = "polymer-cli_bin_test",
size = "small",
srcs = ["polymer-cli_bin_test.sh"],
data = ["@yarn_modules//:polymer-cli_polymer_bin"],
)

17 changes: 17 additions & 0 deletions tests/polymer-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Polymer CLI Example

This folder demonstrates that the generated `BUILD` file for
`@yarn_modules` contains a `node_binary` rule for the polymer-cli module
executable. It tests that the target is callable and that the help
message is output.

Polymer CLI is a fairly complex dependency that contains @-scoped dependencies
and complex cyclic dependencies.

```sh
# Should be able to run polymer-cli directly
$ bazel build @yarn_modules//:polymer-cli_polymer_bin -- --help

# Should be able to invoke polymer-cli as standalone script
$ ./bazel-bin/external/yarn_modules/polymer-cli_polymer_bin --help
```
17 changes: 17 additions & 0 deletions tests/polymer-cli/WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
workspace(name = "org_pubref_rules_node_polymer_test")

local_repository(
name = "org_pubref_rules_node",
path = "../..",
)

load("@org_pubref_rules_node//node:rules.bzl", "node_repositories", "yarn_modules")

node_repositories()

yarn_modules(
name = "yarn_modules",
deps = {
"polymer-cli": "1.5.7",
},
)
7 changes: 7 additions & 0 deletions tests/polymer-cli/polymer-cli_bin_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
set -e

if (./external/yarn_modules/polymer-cli_polymer_bin --help &) | grep -qF '/__\/ /__\/ \/__\ The multi-tool for Polymer projects'; then
echo "PASS"
else
exit 1
fi