Skip to content

Commit aeb7c46

Browse files
hanslKeen Yee Liau
authored and
Keen Yee Liau
committed
build: add support to build JSON schema through bazel
The tests verify that the output of the rule is the same as the input, and will error if not (with a call to action). The binary produces the golden output. We have to check the result of the transformation into git because internally we cannot synchronize quicktype into google3.
1 parent 737ffad commit aeb7c46

File tree

10 files changed

+498
-22
lines changed

10 files changed

+498
-22
lines changed

BUILD

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,22 @@ licenses(["notice"]) # MIT License
99
exports_files([
1010
"LICENSE",
1111
"tsconfig.json", # @external
12+
"tslint.base.json", # @external
1213
])
1314

1415
# NOTE: this will move to node_modules/BUILD in a later release
1516
# @external_begin
17+
NODE_MODULES_EXCLUDE = [
18+
# e.g. node_modules/adm-zip/test/assets/attributes_test/New folder/hidden.txt
19+
"node_modules/**/test/**",
20+
# e.g. node_modules/xpath/docs/function resolvers.md
21+
"node_modules/**/docs/**",
22+
# e.g. node_modules/puppeteer/.local-chromium/mac-536395/chrome-mac/Chromium.app/Contents/Versions/66.0.3347.0/Chromium Framework.framework/Chromium Framework
23+
"node_modules/**/.*/**",
24+
# Ignore paths with spaces.
25+
"node_modules/**/* *",
26+
]
27+
1628
filegroup(
1729
name = "node_modules",
1830
srcs = glob(
@@ -23,16 +35,46 @@ filegroup(
2335
"node_modules/**/*.json",
2436
"node_modules/**/*.d.ts",
2537
],
26-
exclude = [
27-
# e.g. node_modules/adm-zip/test/assets/attributes_test/New folder/hidden.txt
28-
"node_modules/**/test/**",
29-
# e.g. node_modules/xpath/docs/function resolvers.md
30-
"node_modules/**/docs/**",
31-
# e.g. node_modules/puppeteer/.local-chromium/mac-536395/chrome-mac/Chromium.app/Contents/Versions/66.0.3347.0/Chromium Framework.framework/Chromium Framework
32-
"node_modules/**/.*/**",
33-
# Ignore paths with spaces.
34-
"node_modules/**/* *",
35-
],
38+
exclude = NODE_MODULES_EXCLUDE,
3639
) + glob(["node_modules/.bin/*"]),
3740
)
41+
42+
# node_modules filegroup for tools/quicktype_runner, which contains quicktype-core and tslint.
43+
QUICKTYPE_TRANSITIVE_DEPENDENCIES = [
44+
"collection-utils",
45+
"core-util-is",
46+
"inherits",
47+
"isarray",
48+
"js-base64",
49+
"pako",
50+
"pluralize",
51+
"process-nextick-args",
52+
"quicktype-core",
53+
"readable-stream",
54+
"safe-buffer",
55+
"stream-json",
56+
"string-to-stream",
57+
"tiny-inflate",
58+
"unicode-properties",
59+
"unicode-trie",
60+
"urijs",
61+
"util-deprecate",
62+
"wordwrap",
63+
]
64+
65+
filegroup(
66+
name = "quicktype_node_modules",
67+
srcs = glob(
68+
[
69+
"/".join([
70+
"node_modules", "**", pkg, "**/*.js",
71+
]) for pkg in QUICKTYPE_TRANSITIVE_DEPENDENCIES
72+
] + [
73+
"/".join([
74+
"node_modules", "**", pkg, "**/*.json",
75+
]) for pkg in QUICKTYPE_TRANSITIVE_DEPENDENCIES
76+
],
77+
exclude = NODE_MODULES_EXCLUDE,
78+
)
79+
)
3880
# @external_end

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
],
6161
"dependencies": {
6262
"glob": "^7.0.3",
63+
"node-fetch": "^2.2.0",
64+
"quicktype-core": "^5.0.41",
6365
"temp": "^0.8.3",
6466
"tslint": "^5.11.0",
6567
"typescript": "~3.0.1"

packages/schematics/update/BUILD

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ ts_library(
2020
"**/*_benchmark.ts",
2121
],
2222
),
23+
data = [
24+
"collection.json",
25+
"migrate/schema.json",
26+
"update/schema.json",
27+
],
2328
deps = [
2429
"//packages/angular_devkit/core",
2530
"//packages/angular_devkit/schematics",

rules/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// things faster.
44
"extends": "../tsconfig.json",
55
"compilerOptions": {
6-
"outDir": "../dist",
6+
"outDir": "../dist/rules",
77
"baseUrl": ""
88
},
99
"exclude": [

tools/BUILD

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,24 @@
1-
# Marker file indicating this directory is a Bazel package
1+
# Copyright Google Inc. All Rights Reserved.
2+
#
3+
# Use of this source code is governed by an MIT-style license that can be
4+
# found in the LICENSE file at https://angular.io/license
5+
6+
package(default_visibility = ["//visibility:public"])
7+
8+
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary")
9+
10+
nodejs_binary(
11+
name = 'quicktype_runner',
12+
data = [
13+
":quicktype_runner.js",
14+
],
15+
node_modules = "//:quicktype_node_modules",
16+
entry_point = "angular_devkit/tools/quicktype_runner.js",
17+
templated_args = [
18+
# Needed so that node doesn't walk back to the source directory.
19+
# From there, the relative imports would point to .ts files.
20+
"--node_options=--preserve-symlinks",
21+
],
22+
install_source_map_support = False,
23+
)
24+

tools/bazel.rc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,4 @@ build --strategy=TypeScriptCompile=worker
55
# Performance: avoid stat'ing input files
66
build --watchfs
77

8-
# Do not generate the symbolic links bazel-*.
9-
build --symlink_prefix=/
8+
test --test_output=errors

tools/quicktype_runner.js

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/**
10+
* This file is pure JavaScript because we want to avoid any dependency or build step
11+
* to it. It's just simple (and zen-ier).
12+
*
13+
* This file wraps around quicktype and can do one of two things;
14+
*
15+
* `node quicktype_runner.js <in_path> <out_path>`
16+
* Reads the in path and outputs the TS file at the out_path.
17+
*
18+
* Using `-` as the out_path will output on STDOUT instead of a file.
19+
*/
20+
21+
// Imports.
22+
const fs = require('fs');
23+
const path = require('path');
24+
const qtCore = require('quicktype-core');
25+
const tempRoot = process.env['BAZEL_TMPDIR'] || require('os').tmpdir();
26+
27+
// Header to add to all files.
28+
const header = `
29+
/**
30+
* @license
31+
* Copyright Google Inc. All Rights Reserved.
32+
*
33+
* Use of this source code is governed by an MIT-style license that can be
34+
* found in the LICENSE file at https://angular.io/license
35+
*/
36+
37+
// THIS FILE IS AUTOMATICALLY GENERATED IN BAZEL. TO UPDATE THIS FILE YOU NEED TO CHANGE THE
38+
// CORRESPONDING JSON SCHEMA FILE, THEN RUN BAZEL.
39+
40+
`.replace(/^\n/m, ''); // Remove the first \n, it's in the constant because formatting is 👍.
41+
42+
// Footer to add to all files.
43+
const footer = ``;
44+
45+
/**
46+
* The simplest Node JSONSchemaStore implementation we can build which supports our custom protocol.
47+
* Supports reading from ng-cli addresses, valid URLs and files (absolute).
48+
*/
49+
class FetchingJSONSchemaStore extends qtCore.JSONSchemaStore {
50+
constructor(inPath) {
51+
super();
52+
this._inPath = inPath;
53+
}
54+
55+
async fetch(address) {
56+
const URL = require("url");
57+
const url = URL.parse(address);
58+
let content = null;
59+
if (url.protocol === 'ng-cli:') {
60+
let filePath = path.join(__dirname, '../packages/angular/cli', url.hostname, url.path);
61+
content = fs.readFileSync(filePath, 'utf-8').trim();
62+
} else if (url.hostname) {
63+
try {
64+
const fetch = require("node-fetch");
65+
const response = await fetch(address);
66+
content = response.text();
67+
} catch (e) {
68+
content = null;
69+
}
70+
}
71+
72+
if (content === null && !path.isAbsolute(address)) {
73+
const resolvedPath = path.join(path.dirname(this._inPath), address);
74+
75+
// Check relative to inPath
76+
if (fs.existsSync(resolvedPath)) {
77+
content = fs.readFileSync(resolvedPath, 'utf-8');
78+
}
79+
}
80+
81+
if (content === null && fs.existsSync(address)) {
82+
content = fs.readFileSync(address, 'utf-8').trim();
83+
}
84+
85+
if (content == null) {
86+
throw new Error(`Address ${JSON.stringify(address)} cannot be resolved.`);
87+
}
88+
89+
return qtCore.parseJSON(content, "JSON Schema", address);
90+
}
91+
}
92+
93+
94+
/**
95+
* Create the TS file from the schema, and overwrite the outPath (or log).
96+
* @param {string} inPath
97+
* @param {string} outPath
98+
*/
99+
async function main(inPath, outPath) {
100+
const content = await generate(inPath);
101+
102+
if (outPath === '-') {
103+
console.log(content);
104+
process.exit(0);
105+
}
106+
107+
const buildWorkspaceDirectory = process.env['BUILD_WORKSPACE_DIRECTORY'] || '.';
108+
outPath = path.resolve(buildWorkspaceDirectory, outPath);
109+
fs.writeFileSync(outPath, content, 'utf-8');
110+
}
111+
112+
113+
async function generate(inPath) {
114+
// Best description of how to use the API was found at
115+
// https://blog.quicktype.io/customizing-quicktype/
116+
const inputData = new qtCore.InputData();
117+
const source = { name: 'Schema', schema: fs.readFileSync(inPath, 'utf-8') };
118+
119+
await inputData.addSource('schema', source, () => {
120+
return new qtCore.JSONSchemaInput(new FetchingJSONSchemaStore(inPath));
121+
});
122+
123+
const lang = new qtCore.TypeScriptTargetLanguage();
124+
125+
const { lines } = await qtCore.quicktype({
126+
lang,
127+
inputData,
128+
alphabetizeProperties: true,
129+
src: [inPath],
130+
rendererOptions: {
131+
'just-types': true,
132+
'explicit-unions': true,
133+
}
134+
});
135+
136+
return header + lines.join('\n') + footer;
137+
}
138+
139+
// Parse arguments and run main().
140+
const argv = process.argv.slice(2);
141+
if (argv.length < 2 || argv.length > 3) {
142+
console.error('Must include 2 or 3 arguments.');
143+
process.exit(1);
144+
}
145+
146+
main(...argv)
147+
.then(() => process.exit(0))
148+
.catch(err => {
149+
console.error('An error happened:');
150+
console.error(err);
151+
process.exit(127);
152+
});

tools/ts_json_schema.bzl

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright Google Inc. All Rights Reserved.
2+
#
3+
# Use of this source code is governed by an MIT-style license that can be
4+
# found in the LICENSE file at https://angular.io/license
5+
6+
load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
7+
load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_test", "nodejs_binary")
8+
9+
10+
def _ts_json_schema_interface_impl(ctx):
11+
args = [
12+
ctx.files.src[0].path,
13+
ctx.outputs.ts.path,
14+
]
15+
16+
ctx.actions.run(
17+
inputs = ctx.files.src + ctx.files.data,
18+
executable = ctx.executable._binary,
19+
outputs = [ctx.outputs.ts],
20+
arguments = args,
21+
)
22+
23+
return [DefaultInfo()]
24+
25+
26+
_ts_json_schema_interface = rule(
27+
_ts_json_schema_interface_impl,
28+
attrs = {
29+
"src": attr.label(
30+
allow_files = FileType([
31+
".json",
32+
]),
33+
mandatory = True,
34+
),
35+
"out": attr.string(
36+
mandatory = True,
37+
),
38+
"data": attr.label_list(
39+
allow_files = FileType([
40+
".json",
41+
]),
42+
),
43+
"_binary": attr.label(
44+
default = Label("//tools:quicktype_runner"),
45+
executable = True,
46+
cfg = "host",
47+
),
48+
},
49+
outputs = {
50+
"ts": "%{out}"
51+
},
52+
)
53+
54+
55+
def ts_json_schema(name, src, data = [], **kwargs):
56+
out = src.replace(".json", ".ts")
57+
58+
_ts_json_schema_interface(
59+
name = name + ".interface",
60+
src = src,
61+
out = out,
62+
data = data,
63+
)
64+
65+
ts_library(
66+
name = name,
67+
srcs = [
68+
out,
69+
]
70+
)

tsconfig.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@
2727
"es2017"
2828
],
2929
"baseUrl": "",
30+
"rootDirs": [
31+
".",
32+
"./bazel-bin/"
33+
],
3034
"typeRoots": [
3135
"./node_modules/@types"
3236
],

0 commit comments

Comments
 (0)