Skip to content

Commit c979a4d

Browse files
committed
tools: Add thegrid-designsystem-run
For executing/developing designsystems locally
1 parent 67a5314 commit c979a4d

File tree

6 files changed

+213
-3
lines changed

6 files changed

+213
-3
lines changed

bin/thegrid-designsystem-run

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/bin/env node
2+
require('coffee-script/register');
3+
require('../tools/designsystem-run').main();

blueprint/designsystem.apib

+14-3
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,21 @@ For instance, if you have an option for 'density', use something like `mysystem_
8585

8686
All changes to site configuration will trigger a rebuild of the pages on the site.
8787

88-
# Debugging
88+
# Running locally
8989

90-
There are currently no tools to run a page build job locally.
91-
There is currently no way to get the output/errors from a failed page build job.
90+
Assuming `mypage.json` is a page input datastructure, you can execute your designsystem locally using
91+
92+
thegrid-designsystem-run https://developer.thegrid.io/designsystems/original-html.js mypage.json > output.html
93+
94+
You can now open `output.html` in your browser.
95+
96+
You should have PhantomJS 2.x installed, and `phantomjs` should existing on PATH.
97+
This is then executed in the same way as on the production infrastructure.
98+
99+
100+
<!-- TODO:
101+
- tool for getting page input datastructure
102+
-->
92103

93104
# Tools
94105

jsjobs/polyfills/function-bind.js

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// https://raw.githubusercontent.com/facebook/react/master/src/test/phantomjs-shims.js
2+
(function() {
3+
4+
var Ap = Array.prototype;
5+
var slice = Ap.slice;
6+
var Fp = Function.prototype;
7+
8+
if (!Fp.bind) {
9+
// PhantomJS doesn't support Function.prototype.bind natively, so
10+
// polyfill it whenever this module is required.
11+
Fp.bind = function(context) {
12+
var func = this;
13+
var args = slice.call(arguments, 1);
14+
15+
function bound() {
16+
var invokedAsConstructor = func.prototype && (this instanceof func);
17+
return func.apply(
18+
// Ignore the context parameter when invoking the bound function
19+
// as a constructor. Note that this includes not only constructor
20+
// invocations using the new keyword but also calls to base class
21+
// constructors such as BaseClass.call(this, ...) or super(...).
22+
!invokedAsConstructor && context || this,
23+
args.concat(slice.call(arguments))
24+
);
25+
}
26+
27+
// The bound function must share the .prototype of the unbound
28+
// function so that any object created by one constructor will count
29+
// as an instance of both constructors.
30+
bound.prototype = func.prototype;
31+
32+
return bound;
33+
};
34+
}
35+
36+
})();

jsjobs/polyfills/polysolvepage.js

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
2+
3+
// JsJob compatibility wrapper script for poly
4+
window.jsJobRun = function(data, o, callback) {
5+
var isDefined = function(obj) {
6+
return typeof obj != 'undefined' && obj != null;
7+
};
8+
var setIfNotDefined = function(obj, key, val) {
9+
if (!isDefined(obj[key])) {
10+
obj[key] = val;
11+
}
12+
};
13+
14+
var options = {};
15+
if (isDefined(data.options)) {
16+
options = data.options;
17+
}
18+
19+
window.polySolvePage(data, options, function (err, result, details) {
20+
if (err) {
21+
// Inject page info to error
22+
err.site = data.site;
23+
err.path = data.path;
24+
err.style = data.style;
25+
err.job = data.job;
26+
err.options = data.options;
27+
setIfNotDefined(err, 'job', null);
28+
setIfNotDefined(err, 'options', null);
29+
return callback(err);
30+
}
31+
32+
var solved = {
33+
path: data.path,
34+
site: data.site,
35+
page: data.id,
36+
format: data.format,
37+
style: data.style,
38+
html: result,
39+
solution: details,
40+
staging: data.staging,
41+
review: data.review,
42+
job: data.job,
43+
options: data.options,
44+
gss: '',
45+
css: '',
46+
};
47+
setIfNotDefined(solved, 'staging', false);
48+
setIfNotDefined(solved, 'review', false);
49+
setIfNotDefined(solved, 'job', null);
50+
setIfNotDefined(solved, 'options', null);
51+
52+
return callback(null, solved, details);
53+
});
54+
55+
56+
};

package.json

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"bin": {
1616
"thegrid-authenticate": "./bin/thegrid-authenticate",
1717
"thegrid-designsystem-update": "./bin/thegrid-designsystem-update",
18+
"thegrid-designsystem-run": "./bin/thegrid-designsystem-run",
1819
"thegrid-designsystem-collaborators": "./bin/thegrid-designsystem-collaborators",
1920
"thegrid-share-file": "./bin/thegrid-share-file",
2021
"thegrid-share-url": "./bin/thegrid-share-url",
@@ -33,6 +34,7 @@
3334
"dependencies": {
3435
"coffee-script": "^1.7.1",
3536
"js-yaml": "^3.2.5",
37+
"jsjob": "^0.10.5",
3638
"jsonwebtoken": "^5.0.1",
3739
"minimist": "^1.2.0",
3840
"semver": "^4.3.4"

tools/designsystem-run.coffee

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
2+
jsjob = require 'jsjob'
3+
fs = require 'fs'
4+
path = require 'path'
5+
6+
readPolyfill = (name) ->
7+
p = path.join __dirname, '../jsjobs/polyfills', name
8+
return fs.readFileSync p, 'utf-8'
9+
10+
polyFillFunctionBind = readPolyfill 'function-bind.js'
11+
polyCompat = readPolyfill 'polysolvepage.js'
12+
13+
startRunner = (options, callback) ->
14+
options.scripts = [
15+
polyFillFunctionBind,
16+
polyCompat
17+
]
18+
runner = new jsjob.Runner options
19+
runner.start (err) ->
20+
return callback err, runner
21+
22+
runJob = (runner, filterUrl, data, callback) ->
23+
jobOptions = data.options
24+
jobOptions = {} if not jobOptions
25+
runner.runJob filterUrl, data, jobOptions, (err, outpage) ->
26+
if err
27+
return callback err
28+
unless outpage
29+
return callback new Error "JsJob returned falsy and no Error"
30+
return callback null, outpage
31+
32+
usageError = (msg) ->
33+
new Error "#{msg}\nUsage: thegrid-designsystem-run designsystem-url file"
34+
35+
parse = (args) ->
36+
if args.length < 2
37+
throw usageError "Wrong number of arguments, got #{args.length}, expected 2."
38+
39+
minimist = require 'minimist'
40+
parseOptions =
41+
boolean: []
42+
default:
43+
output: 'html'
44+
parsed = minimist args, parseOptions
45+
46+
parsed.designsystem = parsed._[0]
47+
parsed.file = parsed._[1]
48+
49+
return parsed
50+
51+
collectStream = (s, callback) ->
52+
data = ""
53+
s.on 'error', (err) ->
54+
return callback err
55+
s.on 'data', (d) ->
56+
data += d.toString()
57+
s.on 'end', () ->
58+
return callback null, data
59+
s.read()
60+
61+
62+
readInput = (file, callback) ->
63+
if file == '-'
64+
return collectStream process.stdin, callback
65+
else
66+
s = fs.createReadStream file, { encoding: 'utf-8' }
67+
return collectStream s, callback
68+
69+
startAndRun = (data, options, outerCallback) ->
70+
runner = null
71+
callback = (err, r, d) ->
72+
if runner
73+
runner.stop (e) ->
74+
return outerCallback err, r, d
75+
else
76+
return outerCallback err, r, d
77+
78+
startRunner options, (err, r) ->
79+
runner = r
80+
return callback err if err
81+
return runJob runner, options.designsystem, data, callback
82+
83+
main = () ->
84+
options = parse process.argv.slice(2)
85+
86+
callback = (err, results, details) ->
87+
if err
88+
console.error err
89+
process.exit 1
90+
else
91+
if options.output
92+
results = results[options.output]
93+
console.log results
94+
process.exit 0
95+
96+
readInput options.file, (err, input) ->
97+
return callback err if err
98+
return callback new Error 'Input did not contain data' if not input
99+
data = JSON.parse input
100+
return startAndRun data, options, callback
101+
102+
module.exports.main = main

0 commit comments

Comments
 (0)