diff --git a/.eslintignore b/.eslintignore
index 9f9b48d05b..e63bfb096f 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1 +1,2 @@
public/**/*.js
+seed/unpacked/**/*.js
diff --git a/.gitignore b/.gitignore
index c591aad2cc..857f24aa5d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -35,6 +35,7 @@ coverage
server/*.bundle.js
public/js/bundle*
+seed/unpacked
*.map
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 124f56764a..f3bf74aa8c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -409,7 +409,7 @@ nothing to commit, working directory clean
`fix/short-fix-description` or `feature/short-feature-description`. Review
the [Contribution Guidelines](#contribution-guidelines) for more detail.
-3. Edit your file(s) locally with the editor of your choice
+3. Edit your file(s) locally with the editor of your choice. To edit challenges, you may want to use `unpack` and `repack` -- see [seed/README.md](seed/README.md) for instructions.
4. Check your `git status` to see unstaged files.
diff --git a/package-lock.json b/package-lock.json
index 71b9cac704..7e784d2d6e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -186,6 +186,24 @@
}
}
},
+ "acorn-node": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.3.0.tgz",
+ "integrity": "sha512-efP54n3d1aLfjL2UMdaXa6DsswwzJeI5rqhbFvXMrKiJ6eJFpf+7R0zN7t8IC+XKn2YOAFAv6xbBNgHUkoHWLw==",
+ "dev": true,
+ "requires": {
+ "acorn": "5.5.3",
+ "xtend": "4.0.1"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "5.5.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz",
+ "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==",
+ "dev": true
+ }
+ }
+ },
"addressparser": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/addressparser/-/addressparser-1.0.1.tgz",
@@ -473,6 +491,12 @@
"integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=",
"dev": true
},
+ "array-filter": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz",
+ "integrity": "sha1-fajPLiZijtcygDWB/SH2fKzS7uw=",
+ "dev": true
+ },
"array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@@ -493,6 +517,18 @@
"es-abstract": "1.9.0"
}
},
+ "array-map": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz",
+ "integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
+ "dev": true
+ },
+ "array-reduce": {
+ "version": "0.0.0",
+ "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz",
+ "integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=",
+ "dev": true
+ },
"array-slice": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz",
@@ -575,6 +611,23 @@
"integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=",
"dev": true
},
+ "astw": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz",
+ "integrity": "sha1-e9QXhNMkk5h66yOba04cV6hzuRc=",
+ "dev": true,
+ "requires": {
+ "acorn": "4.0.13"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "4.0.13",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz",
+ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=",
+ "dev": true
+ }
+ }
+ },
"async": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz",
@@ -2122,12 +2175,43 @@
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
+ "browser-pack": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.4.tgz",
+ "integrity": "sha512-Q4Rvn7P6ObyWfc4stqLWHtG1MJ8vVtjgT24Zbu+8UTzxYuZouqZsmNRRTFVMY/Ux0eIKv1d+JWzsInTX+fdHPQ==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "1.0.3",
+ "combine-source-map": "0.8.0",
+ "defined": "1.0.0",
+ "safe-buffer": "5.1.1",
+ "through2": "2.0.3",
+ "umd": "3.0.3"
+ }
+ },
"browser-process-hrtime": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz",
"integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=",
"dev": true
},
+ "browser-resolve": {
+ "version": "1.11.2",
+ "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz",
+ "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=",
+ "dev": true,
+ "requires": {
+ "resolve": "1.1.7"
+ },
+ "dependencies": {
+ "resolve": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz",
+ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=",
+ "dev": true
+ }
+ }
+ },
"browser-sync": {
"version": "2.18.13",
"resolved": "https://registry.npmjs.org/browser-sync/-/browser-sync-2.18.13.tgz",
@@ -2339,6 +2423,132 @@
"weinre": "2.0.0-pre-I0Z7U9OV"
}
},
+ "browserify": {
+ "version": "16.1.1",
+ "resolved": "https://registry.npmjs.org/browserify/-/browserify-16.1.1.tgz",
+ "integrity": "sha512-iSH21jK0+IApV8YHOfmGt1qsGd74oflQ1Ko/28JOkWLFNBngAQfKb6WYIJ9CufH8vycqKX1sYU3y7ZrVhwevAg==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "1.0.3",
+ "assert": "1.4.1",
+ "browser-pack": "6.0.4",
+ "browser-resolve": "1.11.2",
+ "browserify-zlib": "0.2.0",
+ "buffer": "5.1.0",
+ "cached-path-relative": "1.0.1",
+ "concat-stream": "1.6.0",
+ "console-browserify": "1.1.0",
+ "constants-browserify": "1.0.0",
+ "crypto-browserify": "3.12.0",
+ "defined": "1.0.0",
+ "deps-sort": "2.0.0",
+ "domain-browser": "1.2.0",
+ "duplexer2": "0.1.4",
+ "events": "2.0.0",
+ "glob": "7.1.2",
+ "has": "1.0.1",
+ "htmlescape": "1.1.1",
+ "https-browserify": "1.0.0",
+ "inherits": "2.0.3",
+ "insert-module-globals": "7.0.2",
+ "labeled-stream-splicer": "2.0.0",
+ "mkdirp": "0.5.1",
+ "module-deps": "6.0.0",
+ "os-browserify": "0.3.0",
+ "parents": "1.0.1",
+ "path-browserify": "0.0.0",
+ "process": "0.11.10",
+ "punycode": "1.4.1",
+ "querystring-es3": "0.2.1",
+ "read-only-stream": "2.0.0",
+ "readable-stream": "2.2.7",
+ "resolve": "1.5.0",
+ "shasum": "1.0.2",
+ "shell-quote": "1.6.1",
+ "stream-browserify": "2.0.1",
+ "stream-http": "2.7.2",
+ "string_decoder": "1.0.3",
+ "subarg": "1.0.0",
+ "syntax-error": "1.4.0",
+ "through2": "2.0.3",
+ "timers-browserify": "1.4.2",
+ "tty-browserify": "0.0.1",
+ "url": "0.11.0",
+ "util": "0.10.3",
+ "vm-browserify": "0.0.4",
+ "xtend": "4.0.1"
+ },
+ "dependencies": {
+ "base64-js": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.3.tgz",
+ "integrity": "sha512-MsAhsUW1GxCdgYSO6tAfZrNapmUKk7mWx/k5mFY/A1gBtkaCaNapTg+FExCw1r9yeaZhqx/xPg43xgTFH6KL5w==",
+ "dev": true
+ },
+ "buffer": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.1.0.tgz",
+ "integrity": "sha512-YkIRgwsZwJWTnyQrsBTWefizHh+8GYj3kbL1BTiAQ/9pwpino0G7B2gp5tx/FUBqUlvtxV85KNR3mwfAtv15Yw==",
+ "dev": true,
+ "requires": {
+ "base64-js": "1.2.3",
+ "ieee754": "1.1.8"
+ }
+ },
+ "crypto-browserify": {
+ "version": "3.12.0",
+ "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz",
+ "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==",
+ "dev": true,
+ "requires": {
+ "browserify-cipher": "1.0.0",
+ "browserify-sign": "4.0.4",
+ "create-ecdh": "4.0.0",
+ "create-hash": "1.1.3",
+ "create-hmac": "1.1.6",
+ "diffie-hellman": "5.0.2",
+ "inherits": "2.0.3",
+ "pbkdf2": "3.0.14",
+ "public-encrypt": "4.0.0",
+ "randombytes": "2.0.5",
+ "randomfill": "1.0.3"
+ }
+ },
+ "domain-browser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
+ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
+ "dev": true
+ },
+ "events": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/events/-/events-2.0.0.tgz",
+ "integrity": "sha512-r/M5YkNg9zwI8QbSf7tsDWWJvO3PGwZXyG7GpFAxtMASnHL2eblFd7iHiGPtyGKKFPZ59S63NeX10Ws6WqGDcg==",
+ "dev": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "timers-browserify": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz",
+ "integrity": "sha1-ycWLV1voQHN1y14kYtrO50NZ9B0=",
+ "dev": true,
+ "requires": {
+ "process": "0.11.10"
+ }
+ },
+ "tty-browserify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz",
+ "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==",
+ "dev": true
+ }
+ }
+ },
"browserify-aes": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz",
@@ -2545,6 +2755,12 @@
}
}
},
+ "cached-path-relative": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz",
+ "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=",
+ "dev": true
+ },
"cachedir": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cachedir/-/cachedir-1.1.1.tgz",
@@ -3147,6 +3363,26 @@
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
"dev": true
},
+ "combine-source-map": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz",
+ "integrity": "sha1-pY0N8ELBhvz4IqjoAV9UUNLXmos=",
+ "dev": true,
+ "requires": {
+ "convert-source-map": "1.1.3",
+ "inline-source-map": "0.6.2",
+ "lodash.memoize": "3.0.4",
+ "source-map": "0.5.7"
+ },
+ "dependencies": {
+ "convert-source-map": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
+ "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
+ "dev": true
+ }
+ }
+ },
"combined-stream": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz",
@@ -4257,6 +4493,18 @@
"integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=",
"dev": true
},
+ "deps-sort": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz",
+ "integrity": "sha1-CRckkC6EZYJg65EHSMzNGvbiH7U=",
+ "dev": true,
+ "requires": {
+ "JSONStream": "1.0.3",
+ "shasum": "1.0.2",
+ "subarg": "1.0.0",
+ "through2": "2.0.3"
+ }
+ },
"des.js": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz",
@@ -4320,9 +4568,9 @@
"dev": true
},
"diff": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
- "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
"dev": true
},
"diffie-hellman": {
@@ -8311,6 +8559,12 @@
"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=",
"dev": true
},
+ "htmlescape": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz",
+ "integrity": "sha1-OgPtwiFLyjtmQko+eVk0lQnLA1E=",
+ "dev": true
+ },
"htmlparser2": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz",
@@ -8541,6 +8795,15 @@
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz",
"integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4="
},
+ "inline-source-map": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz",
+ "integrity": "sha1-+Tk0ccGKedFyT4Y/o4tYY3Ct4qU=",
+ "dev": true,
+ "requires": {
+ "source-map": "0.5.7"
+ }
+ },
"inline-style-prefixer": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-2.0.5.tgz",
@@ -8570,6 +8833,79 @@
"through": "2.3.8"
}
},
+ "insert-module-globals": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.2.tgz",
+ "integrity": "sha512-p3s7g96Nm62MbHRuj9ZXab0DuJNWD7qcmdUXCOQ/ZZn42DtDXfsLill7bq19lDCx3K3StypqUnuE3H2VmIJFUw==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "1.0.3",
+ "combine-source-map": "0.7.2",
+ "concat-stream": "1.5.2",
+ "is-buffer": "1.1.6",
+ "lexical-scope": "1.2.0",
+ "process": "0.11.10",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ },
+ "dependencies": {
+ "combine-source-map": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz",
+ "integrity": "sha1-CHAxKFazB6h8xKxIbzqaYq7MwJ4=",
+ "dev": true,
+ "requires": {
+ "convert-source-map": "1.1.3",
+ "inline-source-map": "0.6.2",
+ "lodash.memoize": "3.0.4",
+ "source-map": "0.5.7"
+ }
+ },
+ "concat-stream": {
+ "version": "1.5.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz",
+ "integrity": "sha1-cIl4Yk2FavQaWnQd790mHadSwmY=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "readable-stream": "2.0.6",
+ "typedarray": "0.0.6"
+ }
+ },
+ "convert-source-map": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz",
+ "integrity": "sha1-SCnId+n+SbMWHzvzZziI4gRpmGA=",
+ "dev": true
+ },
+ "process": {
+ "version": "0.11.10",
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
+ "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz",
+ "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=",
+ "dev": true,
+ "requires": {
+ "core-util-is": "1.0.2",
+ "inherits": "2.0.3",
+ "isarray": "1.0.0",
+ "process-nextick-args": "1.0.7",
+ "string_decoder": "0.10.31",
+ "util-deprecate": "1.0.2"
+ }
+ },
+ "string_decoder": {
+ "version": "0.10.31",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
+ "dev": true
+ }
+ }
+ },
"interpret": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.4.tgz",
@@ -9550,6 +9886,25 @@
"graceful-fs": "4.1.11"
}
},
+ "labeled-stream-splicer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz",
+ "integrity": "sha1-pS4dE4AkwAuGscDJH2d5GLiuClk=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "isarray": "0.0.1",
+ "stream-splicer": "2.0.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "dev": true
+ }
+ }
+ },
"latest-version": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/latest-version/-/latest-version-2.0.0.tgz",
@@ -9788,6 +10143,15 @@
"type-check": "0.3.2"
}
},
+ "lexical-scope": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz",
+ "integrity": "sha1-/Ope3HBKSzqHls3KQZw6CvryLfQ=",
+ "dev": true,
+ "requires": {
+ "astw": "2.2.0"
+ }
+ },
"libbase64": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz",
@@ -10561,6 +10925,12 @@
"integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=",
"dev": true
},
+ "lodash.memoize": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz",
+ "integrity": "sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=",
+ "dev": true
+ },
"lodash.merge": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.0.tgz",
@@ -11705,6 +12075,12 @@
"ms": "0.7.1"
}
},
+ "diff": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-1.4.0.tgz",
+ "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=",
+ "dev": true
+ },
"escape-string-regexp": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz",
@@ -11781,6 +12157,48 @@
"integrity": "sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=",
"dev": true
},
+ "module-deps": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-6.0.0.tgz",
+ "integrity": "sha512-BKsMhJJENEM4dTgqq2MDTTHXRHcNUFegoAwlG4HO4VMdUyMcJDKgfgI+MOv6tR5Iv8G3MKZFgsSiyP3ZoosRMw==",
+ "dev": true,
+ "requires": {
+ "JSONStream": "1.0.3",
+ "browser-resolve": "1.11.2",
+ "cached-path-relative": "1.0.1",
+ "concat-stream": "1.6.0",
+ "defined": "1.0.0",
+ "detective": "5.1.0",
+ "duplexer2": "0.1.4",
+ "inherits": "2.0.3",
+ "parents": "1.0.1",
+ "readable-stream": "2.2.7",
+ "resolve": "1.5.0",
+ "stream-combiner2": "1.1.1",
+ "subarg": "1.0.0",
+ "through2": "2.0.3",
+ "xtend": "4.0.1"
+ },
+ "dependencies": {
+ "detective": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/detective/-/detective-5.1.0.tgz",
+ "integrity": "sha512-TFHMqfOvxlgrfVzTEkNBSh9SvSNX/HfF4OFI2QFGCyPm02EsyILqnUeb5P6q7JZ3SFNTBL5t2sePRgrN4epUWQ==",
+ "dev": true,
+ "requires": {
+ "acorn-node": "1.3.0",
+ "defined": "1.0.0",
+ "minimist": "1.2.0"
+ }
+ },
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
"module-details-from-path": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz",
@@ -13287,6 +13705,15 @@
"readable-stream": "2.2.7"
}
},
+ "parents": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz",
+ "integrity": "sha1-/t1NK/GTp3dF/nHjcdc8MwfZx1E=",
+ "dev": true,
+ "requires": {
+ "path-platform": "0.11.15"
+ }
+ },
"parse-asn1": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz",
@@ -13534,6 +13961,12 @@
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
},
+ "path-platform": {
+ "version": "0.11.15",
+ "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz",
+ "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=",
+ "dev": true
+ },
"path-root": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz",
@@ -14383,6 +14816,15 @@
"readable-stream": "2.2.7"
}
},
+ "read-only-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz",
+ "integrity": "sha1-JyT9aoET1zdkrCiNQ4YnDB2/F/A=",
+ "dev": true,
+ "requires": {
+ "readable-stream": "2.2.7"
+ }
+ },
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
@@ -15486,6 +15928,27 @@
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.0.2.tgz",
"integrity": "sha512-zlVXeVUKvo+HEv1e2KQF/csyeMKx2oHvatQ9l6XjCUj3agvC8XGf6R9HvIPDSmp8FNPvx7b5kaEJTRi7CqxtEw=="
},
+ "shasum": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz",
+ "integrity": "sha1-5wEjENj0F/TetXEhUOVni4euVl8=",
+ "dev": true,
+ "requires": {
+ "json-stable-stringify": "0.0.1",
+ "sha.js": "2.4.9"
+ },
+ "dependencies": {
+ "json-stable-stringify": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz",
+ "integrity": "sha1-YRwj6BTbN1Un34URk9tZ3Sryf0U=",
+ "dev": true,
+ "requires": {
+ "jsonify": "0.0.0"
+ }
+ }
+ }
+ },
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@@ -15499,6 +15962,18 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
"integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
},
+ "shell-quote": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz",
+ "integrity": "sha1-9HgZSczkAmlxJ0MOo7PFR29IF2c=",
+ "dev": true,
+ "requires": {
+ "array-filter": "0.0.1",
+ "array-map": "0.0.0",
+ "array-reduce": "0.0.0",
+ "jsonify": "0.0.0"
+ }
+ },
"shelljs": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz",
@@ -16458,6 +16933,16 @@
"duplexer": "0.0.4"
}
},
+ "stream-combiner2": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz",
+ "integrity": "sha1-+02KFCDqNidk4hrUeAOXvry0HL4=",
+ "dev": true,
+ "requires": {
+ "duplexer2": "0.1.4",
+ "readable-stream": "2.2.7"
+ }
+ },
"stream-consume": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz",
@@ -16497,6 +16982,16 @@
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
"integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI="
},
+ "stream-splicer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz",
+ "integrity": "sha1-G2O+Q4oTPktnHMGTUZdgAXWRDYM=",
+ "dev": true,
+ "requires": {
+ "inherits": "2.0.3",
+ "readable-stream": "2.2.7"
+ }
+ },
"stream-throttle": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/stream-throttle/-/stream-throttle-0.1.3.tgz",
@@ -16849,6 +17344,23 @@
"integrity": "sha1-ISqQDfq1rgTmKOIbWABjK9DZ40c=",
"dev": true
},
+ "subarg": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz",
+ "integrity": "sha1-9izxdYHplrSPyWVpn1TAauJouNI=",
+ "dev": true,
+ "requires": {
+ "minimist": "1.2.0"
+ },
+ "dependencies": {
+ "minimist": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
+ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
+ "dev": true
+ }
+ }
+ },
"sum-up": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sum-up/-/sum-up-1.0.3.tgz",
@@ -16898,6 +17410,15 @@
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=",
"dev": true
},
+ "syntax-error": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz",
+ "integrity": "sha512-YPPlu67mdnHGTup2A8ff7BC2Pjq0e0Yp/IyTFN03zWO0RcK07uLcbi7C2KpGR2FvWbaB0+bfE27a+sBKebSo7w==",
+ "dev": true,
+ "requires": {
+ "acorn-node": "1.3.0"
+ }
+ },
"table": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz",
@@ -17781,6 +18302,12 @@
"integrity": "sha1-rOEWq1V80Zc4ak6I9GhTeMiy5Po=",
"dev": true
},
+ "umd": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.3.tgz",
+ "integrity": "sha512-4IcGSufhFshvLNcMCV80UnQVlZ5pMOC8mvNPForqwA4+lzYQuetTESLDQkeLmihq8bRcnpbQa48Wb8Lh16/xow==",
+ "dev": true
+ },
"unc-path-regex": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz",
diff --git a/package.json b/package.json
index 371497d009..4f698f7c40 100644
--- a/package.json
+++ b/package.json
@@ -21,6 +21,7 @@
"prelint-js": "npm run create-rev",
"pretest": "npm run create-rev && npm run lint",
"prestart-production": "gulp build -p",
+ "seed": "node seed",
"snyk-protect": "snyk protect",
"start": "babel-node server/server.js",
"start-production": "node pm2Start",
@@ -31,7 +32,9 @@
"test-js": "npm run test-js-client && npm run test-js-common && npm run test-js-server",
"test-js-client": "tape -r babel-register \"client/**/*.test.js\" | tap-spec",
"test-js-common": "tape -r babel-register \"common/**/*.test.js\" | tap-spec",
- "test-js-server": "tape -r babel-register \"server/**/*.test.js\" | tap-spec"
+ "test-js-server": "tape -r babel-register \"server/**/*.test.js\" | tap-spec",
+ "unpack": "babel-node ./seed/unpack.js",
+ "repack": "babel-node ./seed/repack.js"
},
"license": "(BSD-3-Clause AND CC-BY-SA-4.0)",
"dependencies": {
@@ -156,10 +159,12 @@
"babel-preset-stage-0": "^6.3.13",
"babel-preset-stage-3": "^6.24.1",
"browser-sync": "^2.9.12",
+ "browserify": "^16.1.1",
"chunk-manifest-webpack-plugin": "^1.1.2",
"commitizen": "^2.9.6",
"cz-freecodecamp": "^1.0.1",
"del": "^2.2.0",
+ "diff": "^3.5.0",
"eslint": "^4.10.0",
"eslint-config-freecodecamp": "^1.1.1",
"gulp": "^3.9.1",
diff --git a/seed/README.md b/seed/README.md
index 0a6aadc9c7..f038a5ab64 100644
--- a/seed/README.md
+++ b/seed/README.md
@@ -10,8 +10,17 @@ For each challenge section, there is a JSON file (fields documented below) conta
|---|---|
| `npm run test-challenges` | run all challenge tests (for each challenge JSON file, run all `tests` against all `solutions`) |
| `npm run test` | run all JS tests in the system, including client, server, lint and challenge tests |
-| `node seed` | parses all the challenge JSON files and saves them into MongoDB (code is inside [index.js](index.js)) |
+| `npm run seed`
(or `node seed`) | parses all the challenge JSON files and saves them into MongoDB (code is inside [index.js](index.js)) |
| `npm run commit` | interactive tool to help you build a good commit message |
+| `npm run unpack` | extract challenges from `seed/challenges` into `unpacked` subdirectory, one HTML page per challenge |
+| `npm run repack` | repack challenges from `unpacked` subdirectory into `seed/challenges` |
+
+### unpack and repack
+
+`npm run unpack` extracts challenges into separate files for easier viewing and editing. The files are `.gitignore`d and will *not* be checked in, and all mongo seed importing will keep using the existing system; this is essentially a tool for editing `challenge.json` files. These HTML files are self-contained and run their own tests -- open a browser JS console to see the test results.
+
+`npm run repack` gathers up the unpacked/edited HTML files into challenge-block JSON files. Use `git diff` to see the changes
+
## Links
@@ -24,7 +33,7 @@ For each challenge section, there is a JSON file (fields documented below) conta
## Challenge Template
-```json
+```
{
"id": "unique identifier (alphanumerical, mongodb id)",
"title": "Challenge Title",
diff --git a/seed/addAssertsToTapTest.js b/seed/addAssertsToTapTest.js
new file mode 100644
index 0000000000..58fb62a3e1
--- /dev/null
+++ b/seed/addAssertsToTapTest.js
@@ -0,0 +1,52 @@
+let _ = require('lodash');
+
+function createIsAssert(tapTest, isThing) {
+ const { assert } = tapTest;
+ return function() {
+ const args = [...arguments];
+ args[0] = isThing(args[0]);
+ assert.apply(tapTest, args);
+ };
+}
+
+function addAssertsToTapTest(tapTest) {
+ const assert = tapTest.assert;
+
+ assert.isArray = createIsAssert(tapTest, _.isArray);
+ assert.isBoolean = createIsAssert(tapTest, _.isBoolean);
+ assert.isString = createIsAssert(tapTest, _.isString);
+ assert.isNumber = createIsAssert(tapTest, _.isNumber);
+ assert.isUndefined = createIsAssert(tapTest, _.isUndefined);
+
+ assert.deepEqual = tapTest.deepEqual;
+ assert.equal = tapTest.equal;
+ assert.strictEqual = tapTest.equal;
+ assert.sameMembers = function sameMembers() {
+ const [ first, second, ...args] = arguments;
+ assert.apply(
+ tapTest,
+ [
+ _.difference(first, second).length === 0 &&
+ _.difference(second, first).length === 0
+ ].concat(args)
+ );
+ };
+ assert.includeMembers = function includeMembers() {
+ const [ first, second, ...args] = arguments;
+ assert.apply(tapTest,
+ [
+ _.difference(second, first).length === 0
+ ].concat(args));
+ };
+ assert.match = function match() {
+ const [value, regex, ...args] = arguments;
+ assert.apply(tapTest,
+ [
+ regex.test(value)
+ ].concat(args));
+ };
+
+ return assert;
+}
+
+module.exports = addAssertsToTapTest;
diff --git a/seed/getChallenges.js b/seed/getChallenges.js
index 7ff5f6b6d5..a8a413fc99 100644
--- a/seed/getChallenges.js
+++ b/seed/getChallenges.js
@@ -4,64 +4,54 @@ const fs = require('fs');
const path = require('path');
const hiddenFile = /(^(\.|\/\.))|(.md$)/g;
+
function getFilesFor(dir) {
- return fs.readdirSync(path.join(__dirname, '/' + dir))
+ let targetDir = path.join(__dirname, dir);
+ return fs.readdirSync(targetDir)
.filter(file => !hiddenFile.test(file))
.map(function(file) {
let superBlock;
- if (fs.statSync(path.join(__dirname, dir + '/' + file)).isFile()) {
- return { file: file };
+ if (fs.statSync(path.join(targetDir, file)).isFile()) {
+ return {file: file};
}
superBlock = file;
- return getFilesFor(dir + '/' + superBlock)
+ return getFilesFor(path.join(dir, superBlock))
.map(function(data) {
return {
- file: superBlock + '/' + data.file,
+ file: path.join(superBlock, data.file),
superBlock: superBlock
};
});
})
- .reduce(function(files, file) {
- if (!Array.isArray(file)) {
- files.push(file);
- return files;
- }
- return files.concat(file);
+ .reduce(function(files, entry) {
+ return files.concat(entry);
}, []);
}
-function getSupOrder(filePath) {
- const order = parseInt((filePath || '').split('-')[0], 10);
- // check for NaN
- if (order !== order) {
- return 0;
+function superblockInfo(filePath) {
+ let parts = (filePath || '').split('-');
+ let order = parseInt(parts[0], 10);
+ if (isNaN(order)) {
+ return {order: 0, name: filePath};
+ } else {
+ return {
+ order: order,
+ name: parts.splice(1).join('-')
+ };
}
- return order;
}
-function getSupName(filePath) {
- const order = parseInt((filePath || '').split('-')[0], 10);
- // check for NaN
- if (order !== order) {
- return filePath;
- }
-
- return (filePath || '').split('-').splice(1).join('-');
-}
-
-module.exports = function getChallenges() {
- try {
- return getFilesFor('challenges')
- .map(function(data) {
- const challengeSpec = require('./challenges/' + data.file);
- challengeSpec.fileName = data.file;
- challengeSpec.superBlock = getSupName(data.superBlock);
- challengeSpec.superOrder = getSupOrder(data.superBlock);
-
- return challengeSpec;
- });
- } catch (e) {
- console.error('error: ', e);
- return [];
+module.exports = function getChallenges(challengesDir) {
+ if (!challengesDir) {
+ challengesDir = 'challenges';
}
+ return getFilesFor(challengesDir)
+ .map(function(data) {
+ const challengeSpec = require('./' + challengesDir + '/' + data.file);
+ let superInfo = superblockInfo(data.superBlock);
+ challengeSpec.fileName = data.file;
+ challengeSpec.superBlock = superInfo.name;
+ challengeSpec.superOrder = superInfo.order;
+ return challengeSpec;
+ });
};
diff --git a/seed/mongoIds.js b/seed/mongoIds.js
new file mode 100644
index 0000000000..034a0b48cf
--- /dev/null
+++ b/seed/mongoIds.js
@@ -0,0 +1,24 @@
+import _ from 'lodash';
+import { isMongoId } from 'validator';
+
+class MongoIds {
+ constructor() {
+ this.knownIds = [];
+ }
+ check(id, title) {
+ if (!isMongoId(id)) {
+ throw new Error(`Expected a valid ObjectId for ${title}, but got ${id}`);
+ }
+ const idIndex = _.findIndex(this.knownIds, existing => id === existing);
+ if (idIndex !== -1) {
+ throw new Error(`
+ All challenges must have a unique id.
+
+ The id for ${title} is already assigned
+ `);
+ }
+ this.knownIds = [ ...this.knownIds, id ];
+ }
+}
+
+export default MongoIds;
diff --git a/seed/normalize-seed-files.js b/seed/normalize-seed-files.js
index 4be5fec3e6..a777f3ca72 100644
--- a/seed/normalize-seed-files.js
+++ b/seed/normalize-seed-files.js
@@ -52,16 +52,16 @@ function createNewTranslations(challenge) {
newTranslation = {};
newTranslation[matches[1]] = challenge[oldKey];
translations[tag] = translations[tag] ?
- Object.assign({}, translations[tag], newTranslation) :
- Object.assign({}, newTranslation);
+ ({...translations[tag], ...newTranslation}) :
+ ({...newTranslation});
return translations;
}
matches = oldKey.match(oldNameRegex);
tag = normalizeLangTag(matches[1]);
newTranslation = { title: challenge[oldKey] };
translations[tag] = translations[tag] ?
- Object.assign({}, translations[tag], newTranslation) :
- Object.assign({}, newTranslation);
+ ({...translations[tag], ...newTranslation}) :
+ ({...newTranslation});
return translations;
}, {});
}
@@ -71,11 +71,10 @@ function normalizeChallenge(challenge) {
challenge.translations = challenge.translations || {};
var hasOldTranslations = keys.some(hasOldTranslation);
if (hasOldTranslations) {
- challenge.translations = Object.assign(
- {},
- challenge.translations,
- createNewTranslations(challenge)
- );
+ challenge.translations = ({
+ ...challenge.translations,
+ ...createNewTranslations(challenge)
+ });
}
challenge.translations = sortTranslationsKeys(challenge.translations);
// remove old translations from the top level
diff --git a/seed/repack.js b/seed/repack.js
new file mode 100644
index 0000000000..9328b3e611
--- /dev/null
+++ b/seed/repack.js
@@ -0,0 +1,76 @@
+/* eslint-disable no-eval, no-process-exit */
+import fs from 'fs-extra';
+import path from 'path';
+import {UnpackedChallenge, ChallengeFile} from './unpackedChallenge';
+
+const jsdiff = require('diff');
+
+// Repack all challenges from all
+// seed/unpacked/00-foo/bar/000-id.html files
+// into
+// seed/challenges/00-foo/bar.json files
+
+let unpackedRoot = path.join(__dirname, 'unpacked');
+let seedChallengesRoot = path.join(__dirname, 'challenges');
+
+function directoriesIn(parentDir) {
+ return fs.readdirSync(parentDir)
+ .filter(entry => fs.statSync(path.join(parentDir, entry)).isDirectory());
+}
+
+let superBlocks = directoriesIn(unpackedRoot);
+console.log(superBlocks);
+
+function diffFiles(originalFilePath, changedFilePath) {
+ // todo: async
+ console.log(`diffing ${originalFilePath} and ${changedFilePath}`);
+ let original = fs.readFileSync(originalFilePath).toString();
+ let repacked = fs.readFileSync(changedFilePath).toString();
+
+ let changes = jsdiff.diffLines(original, repacked, { newlineIsToken: true });
+ changes.forEach((change) => {
+ if (change.added || change.removed) {
+ console.log(JSON.stringify(change, null, 2));
+ }
+ });
+ console.log('');
+}
+
+superBlocks.forEach(superBlock => {
+ let superBlockPath = path.join(unpackedRoot, superBlock);
+ let blocks = directoriesIn(superBlockPath);
+ blocks.forEach(blockName => {
+ let blockPath = path.join(superBlockPath, blockName);
+ let blockFilePath = path.join(blockPath, blockName + '.json');
+ let block = require(blockFilePath);
+ let index = 0;
+ block.challenges.forEach(challengeJson => {
+ let unpackedChallenge =
+ new UnpackedChallenge(blockPath, challengeJson, index);
+ let unpackedFile = unpackedChallenge.challengeFile();
+ let chunks = unpackedFile.readChunks();
+
+ Object.assign(block.challenges[ index ], chunks);
+
+ index += 1;
+ });
+
+ let outputFilePath =
+ path.join(seedChallengesRoot, superBlock, blockName + '.json');
+ // todo: async
+ fs.writeFileSync(outputFilePath, JSON.stringify(block, null, 2));
+
+ // todo: make this a command-line option instead
+ let doDiff = false;
+ if (doDiff) {
+ diffFiles(blockFilePath, outputFilePath);
+ }
+
+ });
+
+});
+
+// let challenges = getChallenges();
+// challenges.forEach(challengeBlock => {
+// console.log()
+// });
diff --git a/seed/test-challenges.js b/seed/test-challenges.js
index 1f17a2f768..a7cdf6b11b 100644
--- a/seed/test-challenges.js
+++ b/seed/test-challenges.js
@@ -1,76 +1,106 @@
-/* eslint-disable no-eval, no-process-exit */
-import _ from 'lodash';
-import { Observable } from 'rx';
+/* eslint-disable no-eval, no-process-exit, no-unused-vars */
+
+import {Observable} from 'rx';
import tape from 'tape';
-import { isMongoId } from 'validator';
import getChallenges from './getChallenges';
import { modern } from '../common/app/utils/challengeTypes';
+import MongoIds from './mongoIds';
+import addAssertsToTapTest from './addAssertsToTapTest';
-const notMongoId = id => !isMongoId(id);
+let mongoIds = new MongoIds();
-let existingIds = [];
+function evaluateTest(solution, assert,
+ react, redux, reactRedux,
+ head, tail,
+ test, tapTest) {
+
+ let code = solution;
+
+ /* NOTE: Provide dependencies for React/Redux challenges
+ * and configure testing environment
+ */
+ let React,
+ ReactDOM,
+ Redux,
+ ReduxThunk,
+ ReactRedux,
+ Enzyme,
+ document;
+
+ // Fake Deep Equal dependency
+ const DeepEqual = (a, b) =>
+ JSON.stringify(a) === JSON.stringify(b);
+
+ // Hardcode Deep Freeze dependency
+ const DeepFreeze = (o) => {
+ Object.freeze(o);
+ Object.getOwnPropertyNames(o).forEach(function(prop) {
+ if (o.hasOwnProperty(prop)
+ && o[ prop ] !== null
+ && (
+ typeof o[ prop ] === 'object' ||
+ typeof o[ prop ] === 'function'
+ )
+ && !Object.isFrozen(o[ prop ])) {
+ DeepFreeze(o[ prop ]);
+ }
+ });
+ return o;
+ };
+
+ if (react || redux || reactRedux) {
+ // Provide dependencies, just provide all of them
+ React = require('react');
+ ReactDOM = require('react-dom');
+ Redux = require('redux');
+ ReduxThunk = require('redux-thunk');
+ ReactRedux = require('react-redux');
+ Enzyme = require('enzyme');
+ const Adapter15 = require('enzyme-adapter-react-15');
+ Enzyme.configure({ adapter: new Adapter15() });
+
+ /* Transpile ALL the code
+ * (we may use JSX in head or tail or tests, too): */
+ const transform = require('babel-standalone').transform;
+ const options = { presets: [ 'es2015', 'react' ] };
+
+ head = transform(head, options).code;
+ solution = transform(solution, options).code;
+ tail = transform(tail, options).code;
+ test = transform(test, options).code;
+
+ const { JSDOM } = require('jsdom');
+ // Mock DOM document for ReactDOM.render method
+ const jsdom = new JSDOM(`
+
+
This is the unpacked version of
+ ${this.superBlockName}/${this.challengeBlockName}
+ (challenge id ${this.challenge.id}
).
Open the JavaScript console to see test results.
'); + + // text.push(`Edit this HTML file (between <!--s only!)
+ // and run npm repack ???
+ // to incorporate your changes into the challenge database.
'); + if (this.challenge.seed) { + text.push(text, this.challenge.seed.join('\n')); + } + text.push(''); + text.push(''); + + // Q: What is the difference between 'seed' and 'challengeSeed' ? + text.push(''); + text.push('
'); + if (this.challenge.challengeSeed) { + text.push(text, this.challenge.challengeSeed.join('\n')); + } + text.push(''); + text.push(''); + + text.push(''); + text.push('