diff --git a/.gitignore b/.gitignore index ab6d6e911..f22e3dbb9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,9 +2,6 @@ bower_components node_modules -# General purpose temp directory -/temp - # Any trash directories _trash @@ -16,16 +13,14 @@ nbproject /build/dist /build/build /build/build-test-coverage +/build/test /mutations/mutantfiles # Developer artifacts /dev/* !/dev/README.md -_attic -/install/temp -/errorShots -/target +_attic # package caches wherever they may be installed bower_components @@ -33,7 +28,7 @@ node_modules package-lock.json # General purpose temp directory -/temp +/temp/files # Any trash directories _trash @@ -60,3 +55,10 @@ nbproject .DS_Store .vscode .idea + +# Docker run artifacts +# todo divert files elsewhere +kbase-ui.stdout +kbase-ui.stderr +proxier.stdout +proxier.stderr \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index 735821ef0..51addb23c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ services: # node_js support. E.g. at the moment the docs say 4.2 is the latest on the 4.X # branch, but it is actually 4.4 node_js: - - "6.9" + - "6" # These are travis encrypted var for the DOCKER_USER and DOCKER_PASS creds # used in the build/push2dockerhub.sh script @@ -17,6 +17,10 @@ env: - secure: "rEkz6iLJq6W9Cp9EgWkmxE4eL7vUsBgG1bmSN42yy8SDjgot1VUYLrO0ZVonxCqdgGvXgfZaDVu/ohtG1D03smzpIvA+yCj/81UnewzBtzqejrAbCB05qGhWd+2VUIF7b0VpMtqmpT4TK6uuTswhYTrXoivduCI8GtqI8s7Yd4WTGUUEa8y6MaEP9qa3/NfPaXTo9OgG+1Obys5fLCdcWgtM58P9H4wFNeHeUBAzLFd96BKzTnctt4shqaUA7Kbh5xriAAC/5Zplna37FqY+wBOfXo7gesQmJs2GBBMinNvq1JDoyPF6Q0iX8UEbNys2/3RxnAwnfOgVw2GEcRF+68mhVApAz/PQK3K6vhKcRG7SnSYg4Gu6gsK0VB5UlNGnQWc0wb/qOGjvbDAZ0SHViCIjSo1m+TC4taOloTHR6iKX/L0lUxaJAxOaLO6dJkdiK2AK9FsukiRPBPjGIQowk+PhIwG88C1mUeBdO68bH0kU46PCbbW9hBT11pRkPHZnEkufOPNEKqE0rfvcuGij88+qzceO878+iYNhpu/MtuyiVX3nZEL6e4DmXRQApWaqf/TAqW1HDlupGCjPKf6jTS6NAJ9m84rKJfOsFbT2Ce0K8MxsXvjmXKnilOFl93SADwKP1PwalskjmTqRVOBxLYOLelP+by9ziwpw1d++LDA=" - secure: "v3FaaIHRKGezEtemhmop7due3sHQWspnrnmOiY3kQyusfqzNmmcOIZ39wug3DRjhB88kzrJMUSLYQ5n95420Ss4w2d4mHJUidoG5tiftmXun9WKa1h1BOhk3RbvJff26ipdb+EJt8RrBEtwBANFifR5dQI1xgUkCkarS3QtvL/uCW/UcNX6J6sWkGc+9dGzrph6fM+LcWsX1cmKCVmnPdejbrhK3+YFgdb3bSWSpEMZme9pt0q9440Dg8Cb1lf8UOuVcWFmgLx87MvD1kHmOVkGxDXOxJzPv6gIB13XUH1pkShqVJEWK6DU/1A+ry7BsL6gw/YNXdmr0Cy0I3/EOXfBXrV4hKsQeU7ebvPRekkQD/49KXvA2crfkoMqufnFUTyLBq6iJDwlIqCud5+CRtlcsPtReFsGplP/x5++noW/wvs/EVSuZBoHSAJ28wAiTqMSyqO4iwLeEtxjcE/UeuR7fmJG618C7DpgyhlSXeCz9qpN2CjioqX/tisbMhJDHFrD7SkI/CaJB+FdfpO2BU+zeNbeGFSur74PRZL7PYNxIPzOaN1Tf8HfYE5WxduL+Q+yr5E5CwefWUzW0+SDk8/eTgVmINBW8rqPWIsdYE1tgPm1ST7mjATr9WvsjWeHgPAi2pQc3EYQ9yj7/m+vfoJ1bOdxXcU8F/X49fwtmegM=" # upcoming + +addons: + firefox: latest + # addons: # apt: # packages: @@ -48,5 +52,5 @@ script: make test-travis # after_script: # - tools/travis/stop-nginx.sh after_success: - - make ci-image - - IMAGE_NAME=kbase/kbase-ui ./deployment/ci/tools/push2dockerhub.sh + - make docker_image + - IMAGE_NAME=kbase/kbase-ui ./deployment/tools/push2dockerhub.sh diff --git a/Gruntfile.js b/Gruntfile.js index baaf3c898..3c4f944d4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -3,7 +3,6 @@ /** * Gruntfile for kbase-ui */ -var require = module.exports = function (grunt) { 'use strict'; @@ -66,19 +65,20 @@ module.exports = function (grunt) { }, test: { src: [ - 'build/build-test-coverage' + 'build/build-test-coverage', + 'build/test' ] }, temp: { src: [ - 'build/temp' + 'temp/files' ] }, deps: { src: [ 'node_modules', 'bower_components' ] - } + }, }, webdriver: { @@ -86,7 +86,13 @@ module.exports = function (grunt) { configFile: './test/wdio.conf.js' }, local: { - configFile: './test/wdio.conf.local.js' + configFile: './test/wdio.conf.local.js', + baseUrl: 'https://' + grunt.option('host') + '.kbase.us' + }, + // note should be called with a base of build/tests/integration-tests + integration: { + configFile: './test/wdio.conf.integration.js', + baseUrl: 'https://' + grunt.option('host') + '.kbase.us' }, sauce: { configFile: './test/wdio.conf.sauce.js' @@ -101,11 +107,18 @@ module.exports = function (grunt) { 'clean:build', 'clean:dist', 'clean:test', 'clean:deps', 'clean:temp' ]); + grunt.registerTask('clean-build', [ + 'clean:build', 'clean:dist', 'clean:test', 'clean:temp' + ]); + // Does a single, local, unit test run. // TODO: more work on the webdriver tests, don't work now. - grunt.registerTask('test', [ - 'karma:unit', - 'webdriver:local' + grunt.registerTask('unit-test', [ + 'karma:unit' + ]); + + grunt.registerTask('integration-tests', [ + 'webdriver:integration' ]); // Does a single unit test run, then sends diff --git a/Makefile b/Makefile index c109885ba..912adb1f8 100644 --- a/Makefile +++ b/Makefile @@ -23,12 +23,32 @@ DEPLOY_CFG = deploy-$(TARGET).cfg KB_TOP = /kb GRUNT = ./node_modules/.bin/grunt KARMA = ./node_modules/.bin/karma -config = dev -directory = build + +# The config used to control the build (build task) +# dev, prod +# Defaults to prod +config = prod + +# The kbase-ui build folder to use for the docker image. +# values: build, dist +# Defaults to dist +# For local development, one would use the build, since is much faster +# to create. A debug build may be available in the future. +build = dist + +# The deploy environment; used by dev-time image runners +# dev, ci, next, appdev, prod +# Defaults to dev, since it is only useful for local dev; dev is really ci. +# Causes run-image.sh to use the file in deployment/conf/$(env).env for +# "filling out" the nginx and ui config templates. +# TODO: hook into the real configs out of KBase's gitlab +env = dev + + DEV_DOCKER_CONTEXT = $(TOPDIR)/deployment/dev/docker/context CI_DOCKER_CONTEXT = $(TOPDIR)/deployment/ci/docker/context PROD_DOCKER_CONTEXT = $(TOPDIR)/deployment/prod/docker/context -PROXIER_DOCKER_CONTEXT = $(TOPDIR)/deployment/proxier/docker/context +PROXIER_DOCKER_CONTEXT = $(TOPDIR)/tools/proxier/docker/context # Standard 'all' target = just do the standard build all: @@ -59,16 +79,20 @@ preconditions: # bower install is not part of the build process, since the bower # config is not known until the parts are assembled... -install_tools: +setup-dirs: + @echo "> Setting up directories." + mkdir -p temp/files + +install-tools: @echo "> Installing build and test tools." npm install -init: preconditions install_tools +init: preconditions setup-dirs install-tools # Perform the build. Build scnearios are supported through the config option # which is passed in like "make build config=ci" -build: +build: clean-build @echo "> Building." cd mutations; node build $(config) @@ -76,102 +100,28 @@ build-ci: @echo "> Building for CI." cd mutations; node build ci -build-prod: - @echo "> Building for prod." - cd mutations; node build prod - # Build the docker image, assumes that make init and make build have been done already -dev-image: - @echo "> Building development docker image." - @echo "> Cleaning out old contents" - rm -rf $(DEV_DOCKER_CONTEXT)/contents - @echo "> Copying current build of kbase-ui into contents..." - mkdir -p $(DEV_DOCKER_CONTEXT)/contents/services/kbase-ui - # Note that we copy the "build" build, not the dist build. - cp -pr build/build/client/* $(DEV_DOCKER_CONTEXT)/contents/services/kbase-ui - @echo "> Copying kb/deployment templates..." - cp -pr $(DEV_DOCKER_CONTEXT)/../kb-deployment/* $(DEV_DOCKER_CONTEXT)/contents - @echo "> Beginning docker build..." - cd $(DEV_DOCKER_CONTEXT)/../..; bash tools/build_docker_image.sh +image: build-docker-image -dev-dist-image: - @echo "> Building development docker image." - @echo "> Cleaning out old contents" - rm -rf $(DEV_DOCKER_CONTEXT)/contents - @echo "> Copying current build of kbase-ui into contents..." - mkdir -p $(DEV_DOCKER_CONTEXT)/contents/services/kbase-ui - # Note that we copy the "build" build, not the dist build. - cp -pr build/dist/client/* $(DEV_DOCKER_CONTEXT)/contents/services/kbase-ui - @echo "> Copying kb/deployment entrypoint and config templates..." - cp -pr $(DEV_DOCKER_CONTEXT)/../kb-deployment/* $(DEV_DOCKER_CONTEXT)/contents - chmod u+x $(DEV_DOCKER_CONTEXT)/contents/bin/entrypoint.sh - @echo "> Beginning docker build..." - cd $(DEV_DOCKER_CONTEXT)/../..; bash tools/build_docker_image.sh +docker_image: build-docker-image -ci-image: - @echo "> Building CI docker image." +build-docker-image: + @echo "> Building docker image for this branch." @echo "> Cleaning out old contents" rm -rf $(CI_DOCKER_CONTEXT)/contents - @echo "> Copying current dist build of kbase-ui into contents..." + @echo "> Copying dist build of kbase-ui into contents..." mkdir -p $(CI_DOCKER_CONTEXT)/contents/services/kbase-ui - # Note that we copy the dist build for CI - cp -pr build/dist/client/* $(CI_DOCKER_CONTEXT)/contents/services/kbase-ui - @echo "> Copying kb/deployment config templates..." + cp -pr build/$(build)/client/* $(CI_DOCKER_CONTEXT)/contents/services/kbase-ui + @echo "> Copying kb/deployment templates..." cp -pr $(CI_DOCKER_CONTEXT)/../kb-deployment/* $(CI_DOCKER_CONTEXT)/contents @echo "> Beginning docker build..." - cd $(CI_DOCKER_CONTEXT)/../..; bash tools/build_docker_image.sh - -prod-image: - @echo "> Building docker image." - @echo "> Cleaning out old contents" - rm -rf $(PROD_DOCKER_CONTEXT)/contents - @echo "> Copying current dist build of kbase-ui into contents..." - mkdir -p $(PROD_DOCKER_CONTEXT)/contents/services/kbase-ui - # And of course we use the dist build for prod. - cp -pr build/dist/client/* $(PROD_DOCKER_CONTEXT)/contents/services/kbase-ui - @echo "> Copying kb/deployment config templates..." - cp -pr $(PROD_DOCKER_CONTEXT)/../kb-deployment/* $(PROD_DOCKER_CONTEXT)/contents - @echo "> Beginning docker build..." - cd $(PROD_DOCKER_CONTEXT)/../..; bash tools/build_docker_image.sh + cd $(TOPDIR)/deployment/; bash tools/build_docker_image.sh -proxier-image: - @echo "> Building docker image." - @echo "> Cleaning out old contents" - rm -rf $(PROXIER_DOCKER_CONTEXT)/contents - mkdir -p $(PROXIER_DOCKER_CONTEXT)/contents - @echo "> Copying kb/deployment config templates..." - cp -pr $(PROXIER_DOCKER_CONTEXT)/../src/* $(PROXIER_DOCKER_CONTEXT)/contents - @echo "> Beginning docker build..." - cd $(PROXIER_DOCKER_CONTEXT)/../..; bash tools/build_docker_image.sh - -# run-dev-image: -# @echo "> Running dev image." -# # @echo "> You will need to inspect the docker container for the ip address " -# # @echo "> set your /etc/hosts for ci.kbase.us accordingly." -# @echo "> To map host directories into the container, you will need to run " -# @echo "> deploymnet/dev/tools/run-image.sh with appropriate options." -# $(eval cmd = $(TOPDIR)/deployment/dev/tools/run-image.sh $(env)) -# @echo "> Issuing: $(cmd)" -# bash $(cmd) - -run-ci-image: - $(eval cmd = $(TOPDIR)/deployment/ci/tools/run-image.sh $(env)) - @echo "> Issuing: $(cmd)" - bash $(cmd) - -run-prod-image: - $(eval cmd = $(TOPDIR)/deployment/prod/tools/run-image.sh $(env)) - @echo "> Issuing: $(cmd)" - bash $(cmd) - -run-proxier-image: - $(eval cmd = $(TOPDIR)/deployment/proxier/tools/run-image.sh $(env)) - @echo "> Issuing: $(cmd)" - bash $(cmd) - -run-dev-image: - @echo "> Running image." +# The dev version of run-image also supports cli options for mapping plugins, libraries, +# and parts of ui into the image for (more) rapdi development workflow +run-image: + @echo "> Running kbase-ui image." # @echo "> You will need to inspect the docker container for the ip address " # @echo "> set your /etc/hosts for ci.kbase.us accordingly." @echo "> With options:" @@ -179,39 +129,41 @@ run-dev-image: @echo "> internal $(internal)" @echo "> libraries $(libraries)" @echo "> To map host directories into the container, you will need to run " - @echo "> deploymnet/dev/tools/run-image.sh with appropriate options." - $(eval cmd = $(TOPDIR)/deployment/dev/tools/run-image.sh $(env) $(foreach p,$(plugins),-p $(p)) $(foreach i,$(internal),-i $i) $(foreach l,$(libraries),-l $l) $(foreach s,$(services),-s $s)) + @echo "> tools/run-image.sh with appropriate options." + $(eval cmd = $(TOPDIR)/tools/run-image.sh $(env) $(foreach p,$(plugins),-p $(p)) $(foreach i,$(internal),-i $i) $(foreach l,$(libraries),-l $l) $(foreach s,$(services),-s $s)) @echo "> Issuing: $(cmd)" bash $(cmd) -start: - @echo "> Starting preview server." - @echo "> (make stop to kill it)" - cd tools/server; node server start $(config) $(directory)& +# The proxier, for local dev support -pause: - @echo "> Pausing to let the server start up." - sleep 5 - -stop: - @echo "> Stopping the preview server." - cd tools/server; node server stop $(config) - -# Run the server, and open a browser pointing to it. -preview: - @echo "> Launching default browser for preview" - cd tools/server; node server preview $(config) +proxier-image: + @echo "> Building docker image." + @echo "> Cleaning out old contents" + rm -rf $(PROXIER_DOCKER_CONTEXT)/contents + mkdir -p $(PROXIER_DOCKER_CONTEXT)/contents + @echo "> Copying proxier config templates..." + cp -pr $(PROXIER_DOCKER_CONTEXT)/../src/* $(PROXIER_DOCKER_CONTEXT)/contents + @echo "> Beginning docker build..." + cd $(PROXIER_DOCKER_CONTEXT)/../..; bash tools/build_docker_image.sh +run-proxier-image: + $(eval cmd = $(TOPDIR)/tools/proxier/tools/run-image.sh $(env)) + @echo "> Running proxier image" + @echo "> with env $(env)" + @echo "> Issuing: $(cmd)" + bash $(cmd) # Tests are managed by grunt, but this also mimics the workflow. #init build unit-tests: $(KARMA) start test/unit-tests/karma.conf.js +integration-tests: + $(GRUNT) integration-tests --host=$(host) + travis-tests: $(GRUNT) test-travis - test: unit-tests test-travis: unit-tests travis-tests @@ -224,6 +176,9 @@ clean: clean-temp: $(GRUNT) clean:temp +clean-build: + $(GRUNT) clean-build + # If you need more clean refinement, please see Gruntfile.js, in which you will # find clean tasks for each major build artifact. diff --git a/bower.json b/bower.json index a224cfcdb..00da5e0fc 100644 --- a/bower.json +++ b/bower.json @@ -16,14 +16,14 @@ "url": "git://github.com/kbase/kbase-ui" }, "dependencies": { - "ajv": "5.1.5", + "ajv": "6.2.0", "blockui": "2.70", "bluebird": "3.5.1", "bootstrap": "3.3.7", "bower-knockout-mapping": "2.6.0", "comma-separated-values": "3.6.4", - "d3-plugins-sankey": "1.1.0", "d3": "3.5.17", + "d3-plugins-sankey": "1.1.0", "datatables-bootstrap3-plugin": "eapearson/datatables-bootstrap3-plugin#1.0.1", "datatables": "1.10.16", "file-saver": "1.3.4", diff --git a/config/app/ci/menus.yml b/config/app/ci/menus.yml deleted file mode 100644 index 95f8347c4..000000000 --- a/config/app/ci/menus.yml +++ /dev/null @@ -1,55 +0,0 @@ -menus: - hamburger: - main: - - - id: narrative - auth: true - - - id: jgi-search - auth: true - developer: - - - id: simple-search - auth: true - - - id: reske-admin - auth: true - - - id: staging-browser - auth: true - help: - - - id: about - auth: false - - - id: about-services - auth: true - - - id: contact-kbase - auth: false - - - id: help - auth: false - sidebar: - main: - - - id: dashboard - auth: true - - - id: appcatalog - auth: false - - - id: search - auth: true - - - id: jobbrowser - # The label can ovveride the label from the plugin here - label: Jobs - auth: true - - - id: account - auth: true - - - id: feeds - auth: true - allow: [alpha] \ No newline at end of file diff --git a/config/app/ci/plugins.yml b/config/app/ci/plugins.yml deleted file mode 100644 index e67d5c66f..000000000 --- a/config/app/ci/plugins.yml +++ /dev/null @@ -1,215 +0,0 @@ -## Build Configuration ---- -plugins: - # Plugins which come-with kbase-ui - - - components - - - mainwindow - - - message - - - welcome - - - narrativemanager - - - about - # external: - # These are shared with production - - - name: viswidgets - globalName: kbase-ui-plugin-vis-widgets - cwd: src/plugin - version: 1.2.0 - source: - bower: {} - - - name: typeview - globalName: kbase-ui-plugin-typeview - cwd: src/plugin - version: 1.1.1 - source: - bower: {} - - - name: dataview - globalName: kbase-ui-plugin-dataview - version: 3.5.4 - cwd: src/plugin - source: - bower: {} - - - name: dashboard - globalName: kbase-ui-plugin-dashboard - version: 2.5.1 - cwd: src/plugin - source: - bower: {} - # These are just developer - # - - # name: viswidgetdemo - # globalName: kbase-ui-plugin-demo-vis-widget - # cwd: src/plugin - # version: 1.0.0 - # source: - # bower: {} - - - name: databrowser - globalName: kbase-ui-plugin-databrowser - version: 1.0.1 - cwd: src/plugin - source: - bower: {} - - - name: typebrowser - globalName: kbase-ui-plugin-typebrowser - version: 1.0.2 - cwd: src/plugin - source: - bower: {} - - - name: datawidgets - globalName: kbase-ui-plugin-datawidgets - version: 0.4.2 - cwd: src/plugin - source: - bower: {} - - - name: data-landing-pages - globalName: kbase-ui-plugin-data-landing-pages - version: 0.8.0 - cwd: src/plugin - source: - bower: {} - - - name: shockbrowser - globalName: kbase-ui-plugin-shockbrowser - version: 0.2.0 - cwd: src/plugin - source: - bower: {} - - - name: jobbrowser - globalName: kbase-ui-plugin-jobbrowser - version: 0.3.4 - cwd: src/plugin - source: - bower: {} - - - name: methodview - globalName: kbase-ui-plugin-methodview - version: 1.1.0 - cwd: src/plugin - source: - bower: {} - - - name: ontology-widgets - globalName: kbase-ui-plugin-ontology-widgets - version: 1.0.4 - cwd: src/plugin - source: - bower: {} - - - name: sdk-clients-test - globalName: kbase-ui-plugin-sdk-clients-test - version: 0.1.0 - cwd: src/plugin - source: - bower: {} - - - name: auth2-client - globalName: kbase-ui-plugin-auth2-client - version: 1.2.9 - cwd: src/plugin - source: - bower: {} - - - name: user-profile - globalName: kbase-ui-plugin-user-profile - version: 1.4.1 - cwd: src/plugin - source: - bower: {} - - - name: pavel-demo - globalName: kbase-ui-plugin-pavel-demo - version: 0.1.2 - cwd: src/plugin - source: - bower: {} - - - name: test-dynamic-table-widget - globalName: kbase-ui-plugin-test-dynamic-table-widget - version: 0.1.2 - cwd: src/plugin - source: - bower: {} - - - name: tester - globalName: kbase-ui-plugin-tester - version: 0.1.2 - cwd: src/plugin - source: - bower: {} - - - name: reske-search - globalName: kbase-ui-plugin-reske-search - version: 0.14.1 - cwd: src/plugin - source: - bower: {} - - - name: jgi-search - globalName: kbase-ui-plugin-jgi-search - version: 0.31.6 - cwd: src/plugin - source: - bower: {} - - - name: reske-admin - globalName: kbase-ui-plugin-reske-admin - version: 0.3.2 - cwd: src/plugin - source: - bower: {} - - - name: reske-genome-browser - globalName: kbase-ui-plugin-reske-genome-browser - version: 0.10.3 - cwd: src/plugin - source: - bower: {} - - - name: feeds - globalName: kbase-ui-plugin-feeds - version: 0.2.0 - cwd: src/plugin - source: - bower: {} - - - name: reske-simple-search - globalName: kbase-ui-plugin-reske-simple-search - version: 0.11.3 - cwd: src/plugin - source: - bower: {} - - - name: staging-browser - globalName: kbase-ui-plugin-staging-browser - version: 0.1.2 - cwd: src/plugin - source: - bower: {} - - - name: catalog - globalName: kbase-ui-plugin-catalog - version: 1.2.1 - cwd: src/plugin - source: - bower: {} - - - name: data-search - globalName: kbase-ui-plugin-data-search - version: 0.9.26 - cwd: src/plugin - source: - bower: {} \ No newline at end of file diff --git a/config/app/ci/services.yml b/config/app/ci/services.yml deleted file mode 100644 index a31c1b9ed..000000000 --- a/config/app/ci/services.yml +++ /dev/null @@ -1,18 +0,0 @@ -# UI SErvices ---- -ui: - services: - heartbeat: {} - widget: {} - data: {} - type: {} - userprofile: {} - analytics: {} - ko-component: {} - notification: {} - schema: {} - route: {} - session: {} - # dynamic-service: {} - menu: - configs: [menus] diff --git a/config/app/dev/menus.yml b/config/app/dev/menus.yml deleted file mode 100644 index b110b799e..000000000 --- a/config/app/dev/menus.yml +++ /dev/null @@ -1,88 +0,0 @@ -menus: - hamburger: - main: - - - id: narrative - auth: true - - - id: jgi-search - auth: true - developer: - - - id: simple-search - auth: true - - - id: simple-sample - auth: true - - - id: narrative-finder - auth: true - - - id: staging-browser - auth: true - - - id: example-gopherjs - auth: true - - - id: reske-admin - auth: true - - - id: reske-object-search - auth: true - - - id: tester - auth: true - - - id: test_dynamic_table - auth: true - - - id: paveldemo - auth: true - # - - # id: sdkclienttest - # auth: true - - - id: databrowser - auth: true - - - id: typebrowser - auth: true - - - id: shockbrowser - auth: true - help: - - - id: about - auth: false - - - id: about-services - auth: true - - - id: contact-kbase - auth: false - - - id: help - auth: false - sidebar: - main: - - - id: dashboard - auth: true - - - id: appcatalog - auth: false - - - id: search - auth: true - - - id: jobbrowser - # The label can ovveride the label from the plugin here - label: Jobs - auth: true - - - id: account - auth: true - - - id: feeds - auth: true - allow: [alpha] diff --git a/config/app/dev/plugins.yml b/config/app/dev/plugins.yml index d79bcbe84..82f5d6e18 100644 --- a/config/app/dev/plugins.yml +++ b/config/app/dev/plugins.yml @@ -33,7 +33,7 @@ plugins: - name: dataview globalName: kbase-ui-plugin-dataview - version: 3.5.4 + version: 3.7.0 cwd: src/plugin source: bower: {} @@ -118,7 +118,7 @@ plugins: - name: auth2-client globalName: kbase-ui-plugin-auth2-client - version: 1.2.9 + version: 1.2.17 cwd: src/plugin source: bower: {} @@ -160,7 +160,7 @@ plugins: - name: jgi-search globalName: kbase-ui-plugin-jgi-search - version: 0.31.6 + version: 0.31.7 cwd: src/plugin source: bower: {} @@ -212,11 +212,11 @@ plugins: version: 0.1.2 cwd: src/plugin source: - bower: {} + bower: {} - name: catalog globalName: kbase-ui-plugin-catalog - version: 1.2.1 + version: 1.2.3 cwd: src/plugin source: bower: {} @@ -230,7 +230,7 @@ plugins: - name: data-search globalName: kbase-ui-plugin-data-search - version: 0.9.26 + version: 0.10.6 cwd: src/plugin source: bower: {} @@ -241,6 +241,13 @@ plugins: cwd: src/plugin source: bower: {} + - + name: dagre-example + globalName: kbase-ui-plugin-dagre-example + version: 0.1.0 + cwd: src/plugin + source: + bower: {} # name: profile-admin # globalName: kbase-ui-plugin-profile-admin # version: 0.1.0 diff --git a/config/app/dev/services.yml b/config/app/dev/services.yml index a31c1b9ed..5330fcbf9 100644 --- a/config/app/dev/services.yml +++ b/config/app/dev/services.yml @@ -15,4 +15,98 @@ ui: session: {} # dynamic-service: {} menu: - configs: [menus] + menus: + hamburger: + sections: + main: + items: + - + id: narrative + auth: true + - + id: jgi-search + auth: true + developer: + items: + - + id: dagre-example + auth: true + - + id: simple-search + auth: true + - + id: simple-sample + auth: true + - + id: narrative-finder + auth: true + - + id: staging-browser + auth: true + - + id: example-gopherjs + auth: true + - + id: reske-admin + auth: true + - + id: reske-object-search + auth: true + - + id: tester + auth: true + - + id: test_dynamic_table + auth: true + - + id: paveldemo + auth: true + - + id: databrowser + auth: true + - + id: typebrowser + auth: true + - + id: shockbrowser + auth: true + help: + items: + - + id: about + auth: false + - + id: about-services + auth: true + - + id: contact-kbase + auth: false + - + id: help + auth: false + sidebar: + sections: + main: + items: + - + id: dashboard + auth: true + - + id: appcatalog + auth: false + - + id: search + auth: true + - + id: jobbrowser + # The label can ovveride the label from the plugin here + label: Jobs + auth: true + - + id: account + auth: true + - + id: feeds + auth: true + allow: [alpha] + diff --git a/config/app/prod/menus.yml b/config/app/prod/menus.yml deleted file mode 100644 index 9aec9d412..000000000 --- a/config/app/prod/menus.yml +++ /dev/null @@ -1,37 +0,0 @@ -menus: - hamburger: - main: - - - id: narrative - auth: true - developer: - # no developer menu in prod build. - help: - - - id: about - auth: false - - - id: contact-kbase - auth: false - - - id: help - auth: false - sidebar: - main: - - - id: dashboard - auth: true - - - id: appcatalog - auth: false - - - id: search - auth: true - - - id: jobbrowser - # The label can ovveride the label from the plugin here - label: Jobs - auth: true - - - id: account - auth: true \ No newline at end of file diff --git a/config/app/prod/plugins.yml b/config/app/prod/plugins.yml index 3d39a21eb..200ddde80 100644 --- a/config/app/prod/plugins.yml +++ b/config/app/prod/plugins.yml @@ -15,6 +15,7 @@ plugins: - about # external: + # These are shared with production - name: viswidgets globalName: kbase-ui-plugin-vis-widgets @@ -32,21 +33,43 @@ plugins: - name: dataview globalName: kbase-ui-plugin-dataview - version: 3.5.4 + version: 3.7.0 cwd: src/plugin source: bower: {} - - name: datawidgets - globalName: kbase-ui-plugin-datawidgets - version: 0.4.2 + name: dashboard + globalName: kbase-ui-plugin-dashboard + version: 2.5.1 + cwd: src/plugin + source: + bower: {} + # These are just developer + # - + # name: viswidgetdemo + # globalName: kbase-ui-plugin-demo-vis-widget + # cwd: src/plugin + # version: 1.0.0 + # source: + # bower: {} + - + name: databrowser + globalName: kbase-ui-plugin-databrowser + version: 1.0.1 cwd: src/plugin source: bower: {} - - name: dashboard - globalName: kbase-ui-plugin-dashboard - version: 2.5.1 + name: typebrowser + globalName: kbase-ui-plugin-typebrowser + version: 1.0.2 + cwd: src/plugin + source: + bower: {} + - + name: datawidgets + globalName: kbase-ui-plugin-datawidgets + version: 0.4.2 cwd: src/plugin source: bower: {} @@ -57,6 +80,20 @@ plugins: cwd: src/plugin source: bower: {} + - + name: shockbrowser + globalName: kbase-ui-plugin-shockbrowser + version: 0.2.0 + cwd: src/plugin + source: + bower: {} + - + name: jobbrowser + globalName: kbase-ui-plugin-jobbrowser + version: 0.3.4 + cwd: src/plugin + source: + bower: {} - name: methodview globalName: kbase-ui-plugin-methodview @@ -71,10 +108,17 @@ plugins: cwd: src/plugin source: bower: {} + - + name: sdk-clients-test + globalName: kbase-ui-plugin-sdk-clients-test + version: 0.1.0 + cwd: src/plugin + source: + bower: {} - name: auth2-client globalName: kbase-ui-plugin-auth2-client - version: 1.2.9 + version: 1.2.17 cwd: src/plugin source: bower: {} @@ -86,37 +130,86 @@ plugins: source: bower: {} - - name: catalog - globalName: kbase-ui-plugin-catalog - version: 1.2.1 + name: pavel-demo + globalName: kbase-ui-plugin-pavel-demo + version: 0.1.2 + cwd: src/plugin + source: + bower: {} + - + name: test-dynamic-table-widget + globalName: kbase-ui-plugin-test-dynamic-table-widget + version: 0.1.2 + cwd: src/plugin + source: + bower: {} + - + name: tester + globalName: kbase-ui-plugin-tester + version: 0.1.2 cwd: src/plugin source: - bower: {} + bower: {} + - + name: reske-search + globalName: kbase-ui-plugin-reske-search + version: 0.14.1 + cwd: src/plugin + source: + bower: {} - name: jgi-search globalName: kbase-ui-plugin-jgi-search - version: 0.31.6 + version: 0.31.7 cwd: src/plugin source: bower: {} - - name: jobbrowser - globalName: kbase-ui-plugin-jobbrowser - version: 0.3.4 + name: reske-admin + globalName: kbase-ui-plugin-reske-admin + version: 0.3.2 cwd: src/plugin source: bower: {} - - name: data-search - globalName: kbase-ui-plugin-data-search - version: 0.9.26 + name: reske-genome-browser + globalName: kbase-ui-plugin-reske-genome-browser + version: 0.10.3 cwd: src/plugin source: - bower: {} + bower: {} + - + name: feeds + globalName: kbase-ui-plugin-feeds + version: 0.2.0 + cwd: src/plugin + source: + bower: {} - name: reske-simple-search globalName: kbase-ui-plugin-reske-simple-search version: 0.11.3 cwd: src/plugin source: - bower: {} \ No newline at end of file + bower: {} + - + name: staging-browser + globalName: kbase-ui-plugin-staging-browser + version: 0.1.2 + cwd: src/plugin + source: + bower: {} + - + name: catalog + globalName: kbase-ui-plugin-catalog + version: 1.2.3 + cwd: src/plugin + source: + bower: {} + - + name: data-search + globalName: kbase-ui-plugin-data-search + version: 0.10.6 + cwd: src/plugin + source: + bower: {} \ No newline at end of file diff --git a/config/app/prod/services.yml b/config/app/prod/services.yml index a31c1b9ed..dcf7afa08 100644 --- a/config/app/prod/services.yml +++ b/config/app/prod/services.yml @@ -15,4 +15,64 @@ ui: session: {} # dynamic-service: {} menu: - configs: [menus] + menus: + hamburger: + sections: + main: + items: + - + id: narrative + auth: true + - + id: jgi-search + auth: true + developer: + items: + - + id: simple-search + auth: true + - + id: reske-admin + auth: true + - + id: staging-browser + auth: true + help: + items: + - + id: about + auth: false + - + id: about-services + auth: true + - + id: contact-kbase + auth: false + - + id: help + auth: false + sidebar: + sections: + main: + items: + - + id: dashboard + auth: true + - + id: appcatalog + auth: false + - + id: search + auth: true + - + id: jobbrowser + # The label can ovveride the label from the plugin here + label: Jobs + auth: true + - + id: account + auth: true + - + id: feeds + auth: true + allow: [alpha] diff --git a/config/app/ui.yml b/config/app/ui.yml index c5aef540d..90355cb73 100644 --- a/config/app/ui.yml +++ b/config/app/ui.yml @@ -11,7 +11,7 @@ ui: spinner: url: /images/ajax-loader.gif constants: - client_timeout: 900000, + client_timeout: 900000 # session_max_age: 5184000 # TODO: simply paths? paths: diff --git a/config/bowerInstall.yml b/config/bowerInstall.yml index f543b3fde..dfbf65b48 100644 --- a/config/bowerInstall.yml +++ b/config/bowerInstall.yml @@ -8,17 +8,12 @@ # need to list the packages here and create config for exceptions # TODO: document the format bowerFiles: - - - name: bluebird - cwd: js/browser - src: - - bluebird.js - bowerComponent: true - - - name: buffer - src: - - buffer.js - bowerComponent: true + + # - + # name: buffer + # src: + # - buffer.js + # bowerComponent: true # - # name: esprima # src: @@ -59,6 +54,12 @@ bowerFiles: - name: d3 bowerComponent: true + - + dir: d3-plugins-sankey + src: + - sankey.js + - sankey.css + bowerComponent: true - name: file-saver src: @@ -156,12 +157,7 @@ bowerFiles: - prettify.js - prettify.css bowerComponent: true - - - dir: d3-plugins-sankey - src: - - sankey.js - - sankey.css - bowerComponent: true + - name: handlebars bowerComponent: true diff --git a/config/builds/README.md b/config/build/README.md similarity index 100% rename from config/builds/README.md rename to config/build/README.md diff --git a/config/build/configs/ci.yml b/config/build/configs/ci.yml new file mode 100644 index 000000000..5b25b84c9 --- /dev/null +++ b/config/build/configs/ci.yml @@ -0,0 +1,7 @@ +## prod Build Config +## see README.md +--- +target: prod +release: false +dist: true +vfs: true diff --git a/config/builds/dev.yml b/config/build/configs/dev.yml similarity index 61% rename from config/builds/dev.yml rename to config/build/configs/dev.yml index 07885dc7f..065cbe84d 100644 --- a/config/builds/dev.yml +++ b/config/build/configs/dev.yml @@ -2,8 +2,6 @@ ## see README.md --- target: dev -tempDir: temp -mutate: true -keepBuildDir: false +release: false dist: false vfs: false diff --git a/config/builds/prod.yml b/config/build/configs/prod.yml similarity index 62% rename from config/builds/prod.yml rename to config/build/configs/prod.yml index 35aa300ec..cb076ec2f 100644 --- a/config/builds/prod.yml +++ b/config/build/configs/prod.yml @@ -2,8 +2,6 @@ ## see README.md --- target: prod -tempDir: temp -mutate: true -keepBuildDir: false +release: true dist: true vfs: true diff --git a/config/build/defaults.yml b/config/build/defaults.yml new file mode 100644 index 000000000..ebf5ddf73 --- /dev/null +++ b/config/build/defaults.yml @@ -0,0 +1,5 @@ +# Build config file +--- +tempDir: temp/files +mutate: true +keepBuildDir: false diff --git a/config/builds/ci.yml b/config/builds/ci.yml deleted file mode 100644 index 812638edd..000000000 --- a/config/builds/ci.yml +++ /dev/null @@ -1,10 +0,0 @@ -## Global Build Config -## see README.md ---- -target: ci -tempDir: temp -mutate: true -keepBuildDir: false -dist: true -vfs: true - diff --git a/config/deploy/README.md b/config/deploy/README.md new file mode 100644 index 000000000..891f8b25f --- /dev/null +++ b/config/deploy/README.md @@ -0,0 +1,9 @@ +# Configuration files + +This directory contains example configuration files for each environment, in "ini" format. The actual configurations are stored +in different source control repositories appropriate for actual deployed services. Note that files in this directory +are not actually used, so changes to these files will have no effect on ci, appdev, next or prod. + +The files in this directory can be fed to the "env_file" directive in docker and docker-compose, as well as consumed by +apps such as dockerize that can parse name=value syntax (many YAML parsers such as the common Golang yaml parser as well +as some Python yaml parsers happily consume this as well) \ No newline at end of file diff --git a/config/deploy/appdev.env b/config/deploy/appdev.env new file mode 100644 index 000000000..bf983cc59 --- /dev/null +++ b/config/deploy/appdev.env @@ -0,0 +1,27 @@ +# KBase UI CI Config + +deploy_environment=appdev +deploy_hostname=appdev.kbase.us +deploy_icon=wrench +deploy_name=App Dev + +ui_backupCookie_domain= +ui_backupCookie_enabled=false + +ui_services_analytics_google_hostname=appdev.kbase.us +ui_services_analytics_google_code=UA-74533556-1 +allow=[] + +services_narrative_url=https://appdev.kbase.us + + +# services + +# list of plugins to remove from the runtime even if specified in the build +# comma separated list +# ui_services_plugin_disabled=["data-search", "reske-simple-search"] + +# list of sidebar menu items to remove +ui_services_menu_hamburger_disabled=["simple-search", "reske-admin", "staging-browser"] + +ui_services_menu_sidebar_disabled=["search", "feeds"] diff --git a/config/deploy/appdev.yml b/config/deploy/appdev.yml deleted file mode 100644 index f84d1f8e0..000000000 --- a/config/deploy/appdev.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Deploy-Time Config for Auth2 Local Development ---- -deploy: - environment: appdev - hostname: appdev.kbase.us - icon: wrench - name: App Dev - services: - urlBase: https://appdev.kbase.us -ui: - services: - analytics: - google: - hostname: appdev.kbase.us - code: UA-74533556-1 - allow: [] - diff --git a/deployment/conf/ci.env b/config/deploy/ci.env similarity index 55% rename from deployment/conf/ci.env rename to config/deploy/ci.env index 65fe818e6..8b03a486f 100644 --- a/deployment/conf/ci.env +++ b/config/deploy/ci.env @@ -14,15 +14,13 @@ allow=["alpha", "beta"] services_narrative_url=https://ci.kbase.us -# Proxier Container +# services -# disabled -# deploy_ui_hostname= +# list of plugins to remove from the runtime even if specified in the build +# comma separated list +# ui_services_plugin_disabled=["data-search", "reske-simple-search"] -# the ip or host of the kbase-ui container. This is required -# may need to override in the run command -kbase_ui_host=kbase-ui-container +# Everything is exposed in CI! +ui_services_menu_hamburger_disabled=[] -# true if actually deployed -# probably best to just override in the run command -deployed=true +ui_services_menu_sidebar_disabled=[] diff --git a/config/deploy/ci.yml b/config/deploy/ci.yml deleted file mode 100644 index 4412fd385..000000000 --- a/config/deploy/ci.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Deploy-Time Config for Auth2 Local Development ---- -deploy: - environment: ci - hostname: ci.kbase.us - icon: flask - name: Continuous Integration - services: - urlBase: https://ci.kbase.us -ui: - services: - analytics: - google: - hostname: ci.kbase.us - code: UA-74532036-1 - allow: [alpha, beta] - diff --git a/config/deploy/dev.env b/config/deploy/dev.env new file mode 100644 index 000000000..70e069a4f --- /dev/null +++ b/config/deploy/dev.env @@ -0,0 +1,26 @@ +# KBase UI CI Config + +deploy_environment=ci +deploy_hostname=ci.kbase.us +deploy_icon=flask +deploy_name=Local Development + +ui_backupCookie_domain= +ui_backupCookie_enabled=false + +ui_services_analytics_google_hostname=ci.kbase.us +ui_services_analytics_google_code=UA-74532036-1 +allow=["alpha", "beta"] + +services_narrative_url=https://ci.kbase.us + +# services + +# list of plugins to remove from the runtime even if specified in the build +# comma separated list +# ui_services_plugin_disabled=["data-search", "reske-simple-search"] + +# Everything is exposed in dev! +ui_services_menu_hamburger_disabled=[] + +ui_services_menu_sidebar_disabled=[] diff --git a/config/deploy/dev.yml b/config/deploy/dev.yml deleted file mode 100644 index 40f7604d2..000000000 --- a/config/deploy/dev.yml +++ /dev/null @@ -1,17 +0,0 @@ -# Deploy-Time Config for Auth2 Local Development ---- -deploy: - environment: ci - hostname: ci.kbase.us - icon: flask - name: Dev - services: - urlBase: https://ci.kbase.us -ui: - services: - analytics: - google: - hostname: ci.kbase.us - code: UA-74532036-1 - allow: [alpha, beta] - diff --git a/config/deploy/next.env b/config/deploy/next.env new file mode 100644 index 000000000..cbc11578d --- /dev/null +++ b/config/deploy/next.env @@ -0,0 +1,26 @@ +# KBase UI CI Config + +deploy_environment=next +deploy_hostname=next.kbase.us +deploy_icon=bullseye +deploy_name=Next + +ui_backupCookie_domain= +ui_backupCookie_enabled=false + +ui_services_analytics_google_hostname=next.kbase.us +ui_services_analytics_google_code=UA-74530365-1 +allow=[] + +services_narrative_url=https://next.kbase.us + +# services + +# list of plugins to remove from the runtime even if specified in the build +# comma separated list +# ui_services_plugin_disabled=["data-search", "reske-simple-search"] + +# list of sidebar menu items to remove +ui_services_menu_hamburger_disabled=["simple-search", "reske-admin", "staging-browser"] + +ui_services_menu_sidebar_disabled=["feeds"] diff --git a/config/deploy/next.yml b/config/deploy/next.yml deleted file mode 100644 index c4022e46f..000000000 --- a/config/deploy/next.yml +++ /dev/null @@ -1,16 +0,0 @@ -# Deploy-Time Config for Auth2 Local Development ---- -deploy: - environment: next - hostname: next.kbase.us - icon: bullseye - name: Next - services: - urlBase: https://next.kbase.us -ui: - services: - analytics: - google: - hostname: next.kbase.us - code: UA-74530365-1 - allow: [] \ No newline at end of file diff --git a/config/deploy/prod.env b/config/deploy/prod.env new file mode 100644 index 000000000..5d714195a --- /dev/null +++ b/config/deploy/prod.env @@ -0,0 +1,26 @@ +# KBase UI CI Config + +deploy_environment=prod +deploy_hostname=kbase.us +deploy_icon=thumbs-up +deploy_name=Production + +ui_backupCookie_domain=kbase.us +ui_backupCookie_enabled=true + +ui_services_analytics_google_hostname=kbase.us +ui_services_analytics_google_code=UA-74532966-1 +allow=[] + +services_narrative_url=https://narrative.kbase.us + +# services + +# list of plugins to remove from the runtime even if specified in the build +# comma separated list +# ui_services_plugin_disabled=["data-search", "reske-simple-search"] + +# list of sidebar menu items to remove +ui_services_menu_hamburger_disabled=["simple-search", "reske-admin", "staging-browser"] + +ui_services_menu_sidebar_disabled=["feeds"] diff --git a/config/deploy/prod.yml b/config/deploy/prod.yml deleted file mode 100644 index 2c42738e8..000000000 --- a/config/deploy/prod.yml +++ /dev/null @@ -1,27 +0,0 @@ -# Deploy-Time Config for Auth2 Local Development ---- -deploy: - environment: prod - hostname: narrative.kbase.us - icon: thumbs-up - name: Production - services: - urlBase: https://kbase.us -ui: - # backupCookie: - # domain: kbase.us - # enabled: true - services: - analytics: - google: - hostname: kbase.us - code: UA-74532966-1 - session: - cookie: - backup: - domain: kbase.us - enabled: true - allow: [] -services: - narrative: - url: https://narrative.kbase.us \ No newline at end of file diff --git a/config/legacyDeployConfigs/README.md b/config/legacyDeployConfigs/README.md new file mode 100644 index 000000000..f09fb7de3 --- /dev/null +++ b/config/legacyDeployConfigs/README.md @@ -0,0 +1,9 @@ +# Legacy Deployment Configs + +This directory contains the deploy config files located at /kb/deployment/services/kbase-ui/modules/deploy/config.json for each of the KBase deployments. + +The names are changed from config.json to {env}.json where {env} is the deploy environment ci, next, appdev, prod. + +These files were generated by dockerize by the application of the corresponding environemnt file located in config/deploy/{env}.env to the template file deployment/ci/docker/kb-deployment/conf/config.json.tmpl + +At present kbase-ui deployments still rely upon mounting the json config file into the container rather than running the container with dockerize and the corresponding environment file. diff --git a/config/legacyDeployConfigs/appdev.json b/config/legacyDeployConfigs/appdev.json new file mode 100644 index 000000000..a40a4e358 --- /dev/null +++ b/config/legacyDeployConfigs/appdev.json @@ -0,0 +1,45 @@ +{ + "deploy": { + "environment": "appdev", + "hostname": "appdev.kbase.us", + "icon": "wrench", + "name": "App Dev", + "services": { + "urlBase": "https://appdev.kbase.us" + } + }, + "ui": { + "services": { + "analytics": { + "google": { + "hostname": "appdev.kbase.us", + "code": "UA-74533556-1" + } + }, + "session": { + "cookie": { + "backup": { + "domain": null, + "enabled": false + } + } + }, + "menu": { + "menus": { + "hamburger": { + "disabled": ["simple-search", "reske-admin", "staging-browser"] + }, + "sidebar": { + "disabled": ["search", "feeds"] + } + } + } + }, + "allow": [] + }, + "services": { + "narrative": { + "url": "https://appdev.kbase.us" + } + } +} \ No newline at end of file diff --git a/config/legacyDeployConfigs/ci.json b/config/legacyDeployConfigs/ci.json new file mode 100644 index 000000000..500415c36 --- /dev/null +++ b/config/legacyDeployConfigs/ci.json @@ -0,0 +1,45 @@ +{ + "deploy": { + "environment": "ci", + "hostname": "ci.kbase.us", + "icon": "flask", + "name": "Continuous Integration", + "services": { + "urlBase": "https://ci.kbase.us" + } + }, + "ui": { + "services": { + "analytics": { + "google": { + "hostname": "ci.kbase.us", + "code": "UA-74532036-1" + } + }, + "session": { + "cookie": { + "backup": { + "domain": null, + "enabled": false + } + } + }, + "menu": { + "menus": { + "hamburger": { + "disabled": [] + }, + "sidebar": { + "disabled": [] + } + } + } + }, + "allow": ["alpha", "beta"] + }, + "services": { + "narrative": { + "url": "https://ci.kbase.us" + } + } +} \ No newline at end of file diff --git a/config/legacyDeployConfigs/next.json b/config/legacyDeployConfigs/next.json new file mode 100644 index 000000000..98315b648 --- /dev/null +++ b/config/legacyDeployConfigs/next.json @@ -0,0 +1,45 @@ +{ + "deploy": { + "environment": "next", + "hostname": "next.kbase.us", + "icon": "bullseye", + "name": "Next", + "services": { + "urlBase": "https://next.kbase.us" + } + }, + "ui": { + "services": { + "analytics": { + "google": { + "hostname": "next.kbase.us", + "code": "UA-74530365-1" + } + }, + "session": { + "cookie": { + "backup": { + "domain": null, + "enabled": false + } + } + }, + "menu": { + "menus": { + "hamburger": { + "disabled": ["simple-search", "reske-admin", "staging-browser"] + }, + "sidebar": { + "disabled": ["feeds"] + } + } + } + }, + "allow": [] + }, + "services": { + "narrative": { + "url": "https://next.kbase.us" + } + } +} \ No newline at end of file diff --git a/config/legacyDeployConfigs/prod.json b/config/legacyDeployConfigs/prod.json new file mode 100644 index 000000000..72d583816 --- /dev/null +++ b/config/legacyDeployConfigs/prod.json @@ -0,0 +1,45 @@ +{ + "deploy": { + "environment": "prod", + "hostname": "kbase.us", + "icon": "thumbs-up", + "name": "Production", + "services": { + "urlBase": "https://kbase.us" + } + }, + "ui": { + "services": { + "analytics": { + "google": { + "hostname": "kbase.us", + "code": "UA-74532966-1" + } + }, + "session": { + "cookie": { + "backup": { + "domain": "kbase.us", + "enabled": true + } + } + }, + "menu": { + "menus": { + "hamburger": { + "disabled": ["simple-search", "reske-admin", "staging-browser"] + }, + "sidebar": { + "disabled": ["feeds"] + } + } + } + }, + "allow": [] + }, + "services": { + "narrative": { + "url": "https://narrative.kbase.us" + } + } +} \ No newline at end of file diff --git a/config/npmInstall.yml b/config/npmInstall.yml new file mode 100644 index 000000000..d01a8ec62 --- /dev/null +++ b/config/npmInstall.yml @@ -0,0 +1,54 @@ +# This is used to copy specific files from bower_components to the build. +# It is yucky that we have to do this, but there is no universal bower package +# format, and some bower repos are complete source hairballs. We reduce the code +# side significantly, and eliminate the possibility of serving up hairball files +# from the kbase-ui service. +# TODO: I've learned more about bower conventions since this time -- the main +# property is pretty well supported. Not universally, but better. We only really +# need to list the packages here and create config for exceptions +# TODO: document the format +npmFiles: + - + name: ajv + cwd: dist + src: + - ajv.bundle.js + - + name: bluebird + cwd: js/browser + src: + - bluebird.js + - + name: dagre + cwd: dist + - + name: lodash + # - + # name: d3 + # cwd: build + # - + # dir: d3-sankey + # cwd: build + # src: + # - d3-sankey.js + # - + # dir: d3-shape + # cwd: build + # src: + # - d3-shape.js + # - + # dir: d3-collection + # cwd: build + # src: + # - d3-collection.js + # - + # dir: d3-array + # cwd: build + # src: + # - d3-array.js + # - + # dir: d3-path + # cwd: build + # src: + # - d3-path.js + diff --git a/config/release.yml b/config/release.yml index c5eeb0181..e13d089e4 100644 --- a/config/release.yml +++ b/config/release.yml @@ -6,4 +6,4 @@ --- # Note that this file is merged into the master config in the release namespace. release: - version: 1.6.4 \ No newline at end of file + version: 1.6.5 \ No newline at end of file diff --git a/deployment/ci/docker/context/Dockerfile b/deployment/ci/docker/context/Dockerfile index bf8dd9d55..f787e4dc7 100644 --- a/deployment/ci/docker/context/Dockerfile +++ b/deployment/ci/docker/context/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine:3.7 +FROM kbase/nginx # Container designed to serve up static KBase UI contents # These ARGs values are passed in via the docker build command @@ -7,14 +7,7 @@ ARG VCS_REF ARG BRANCH=develop ARG TAG -ENV DOCKERIZE_VERSION v0.6.0 -RUN apk --update upgrade && \ - apk --no-cache add ca-certificates nginx openssl wget bash bash-completion && \ - update-ca-certificates && \ - wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - mkdir -p /kb/deployment +RUN mkdir -p /kb/deployment/services/kbase-ui COPY contents /kb/deployment @@ -28,8 +21,7 @@ LABEL org.label-schema.build-date=$BUILD_DATE \ us.kbase.vcs-tag=$TAG \ maintainer="Steve Chan sychan@lbl.gov" -ENTRYPOINT [ \ - "dockerize", \ - "-template", "/kb/deployment/conf/nginx.conf.tmpl:/etc/nginx/nginx.conf", \ - "-template", "/kb/deployment/conf/config.json.tmpl:/kb/deployment/services/kbase-ui/modules/deploy/config.json", \ - "nginx"] +ENTRYPOINT [ "/kb/deployment/bin/dockerize" ] +CMD [ "-template", "/kb/deployment/conf/nginx.conf.tmpl:/etc/nginx/nginx.conf", \ + "-template", "/kb/deployment/conf/config.json.tmpl:/kb/deployment/services/kbase-ui/modules/deploy/config.json", \ + "nginx"] diff --git a/deployment/ci/docker/kb-deployment/conf/config.json.tmpl b/deployment/ci/docker/kb-deployment/conf/config.json.tmpl index e3279ffa5..910ce0168 100644 --- a/deployment/ci/docker/kb-deployment/conf/config.json.tmpl +++ b/deployment/ci/docker/kb-deployment/conf/config.json.tmpl @@ -1,18 +1,14 @@ { "deploy": { - "environment": "{{ .Env.deploy_environment }}", - "hostname": "{{ .Env.deploy_hostname }}", + "environment": "{{ default .Env.deploy_environment "dev" }}", + "hostname": "{{ default .Env.deploy_hostname "localhost" }}", "icon": "{{ .Env.deploy_icon }}", "name": "{{ .Env.deploy_name }}", "services": { - "urlBase": "https://{{ .Env.deploy_hostname }}" + "urlBase": "https://{{ default .Env.deploy_hostname "localhost" }}" } }, - "ui": { - "backupCookie": { - "domain": {{ if .Env.ui_backupcookie_domain }}"{{.Env.ui_backupcookie_domain}}"{{else}}null{{end}}, - "enabled": {{ default .Env.ui_backupcookie_enabled "false"}} - }, + "ui": { "services": { "analytics": { "google": { @@ -27,6 +23,16 @@ "enabled": {{ default .Env.ui_backupCookie_enabled "false"}} } } + }, + "menu": { + "menus": { + "hamburger": { + "disabled": {{ default .Env.ui_services_menu_hamburger_disabled "null" }} + }, + "sidebar": { + "disabled": {{ default .Env.ui_services_menu_sidebar_disabled "null" }} + } + } } }, "allow": {{ .Env.allow }} diff --git a/deployment/ci/docker/kb-deployment/conf/nginx.conf.tmpl b/deployment/ci/docker/kb-deployment/conf/nginx.conf.tmpl index 83035bc52..c0eecc65a 100644 --- a/deployment/ci/docker/kb-deployment/conf/nginx.conf.tmpl +++ b/deployment/ci/docker/kb-deployment/conf/nginx.conf.tmpl @@ -23,14 +23,14 @@ http { ## - {{ if eq .Env.deployed "true" }} + {{ if .Env.nginx_syslog }} # Only log to these locations if deployed. This allows local testing of this image. - error_log /dev/stdout; - access_log syslog:server=10.58.0.54,facility=local2,tag=ci,severity=info combined; + error_log /dev/stdout {{ default .Env.nginx_loglevel "error" }}; + access_log syslog:server=10.58.0.54,facility=local2,tag=ci,severity=info combined; {{ else }} # But if running locally preserve the logs for debugging access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; + error_log /var/log/nginx/error.log {{ default .Env.nginx_loglevel "error" }}; {{ end }} ## @@ -45,7 +45,7 @@ http { server { # no need for root privileges - listen 80; + listen {{ default .Env.nginx_listen "80"}}; server_name {{ default .Env.nginx_server_name "localhost" }}; location / { diff --git a/deployment/ci/tools/run-image.sh b/deployment/ci/tools/run-image.sh deleted file mode 100644 index bb56db069..000000000 --- a/deployment/ci/tools/run-image.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -function usage() { - echo 'Usage: run-image.sh env type' -} - -environment=$1 - -if [ -z "$environment" ]; then - echo "ERROR: argument 1, 'environment', not provided" - usage - exit 1 -fi - -if [ ! -e "deployment/conf/${environment}.env" ]; then - echo "ERROR: environment (arg 1) does not resolve to a config file in deployment/conf/${environment}.env" - usage - exit 1 -fi - -root=$(git rev-parse --show-toplevel) -config_mount="${root}/deployment/conf" - -echo "CONFIG MOUNT: ${config_mount}" -echo "ENVIRONMENT : ${environment}" - -echo "READING OPTIONS" - -image_tag="develop" - -docker run \ - --rm \ - --env-file ${config_mount}/${environment}.env \ - --env deployed=false \ - --network=kbase-dev \ - --name=kbase-ui-container \ - kbase/kbase-ui:${image_tag} diff --git a/deployment/conf/README.md b/deployment/conf/README.md deleted file mode 100644 index d6a6cf6ef..000000000 --- a/deployment/conf/README.md +++ /dev/null @@ -1,11 +0,0 @@ -# Configuration files - -This directory contains one configuration file per kbase deployment environment, in "ini" format, as can be consumed by Python's ConfigParser class. In actual implementation the config file is consumed by a utility app "jinja2" (or similar), which may consume INI, YAML, JSON, and TOML formats. However, KBase as of this time just supports INI. - -TODO: kbase modules have a convention in which the config files are created with a section named after the module, and the entrypoint script provides just the part of the configuration inside that section ... that is it removes the module name qualifier. Thus references inside templates are not like "module.prop" but just "prop". - -This makes it error-prone to merge configs from outside the service, so I don't know how that is handled. - -I assume we should do that here, but it isn't done at the moment because I want to confirm this behavior first. - -The one caveat for this is that, since these files are also consumed by Java, and in java I believe they are treated as properties files, which don't have sections, java will not see the sections anyway. diff --git a/deployment/conf/dev.env b/deployment/conf/dev.env deleted file mode 100644 index cb32642b7..000000000 --- a/deployment/conf/dev.env +++ /dev/null @@ -1,38 +0,0 @@ -# KBase UI CI Config - -deploy_environment=ci -deploy_hostname=ci.kbase.us -deploy_icon=flask -deploy_name=Local Development - -ui_backupCookie_domain= -ui_backupCookie_enabled=false - -ui_services_analytics_google_hostname=ci.kbase.us -ui_services_analytics_google_code=UA-74532036-1 -allow=["alpha", "beta"] - -services_narrative_url=https://ci.kbase.us - -# Proxier Container - -# For when the ui operates on a different host name services -# NOTE: must comment out unused, otherwise dockerize's "default" function will pick them up -# deploy_ui_hostname= - -# the ip or host of the kbase-ui container. This is required -# may need to override in the run command -kbase_ui_host=kbase-ui-container - -# true if actually deployed -# probably best to just override in the run command -deployed=false - -#unauthenticated_main_menu=["narrative"] -#unauthenticated_developer_menu=["data-search", "simple-sample", "narrative-finder", "staging-browser", "example-gopherjs", "reske-admin", "reske-object-search", "jgi-search", "tester", "test_dynamic_table", "paveldemo", "sdkclientstest", "databrowser", "typebrowser", "shockbrowser"] -#unauthenticated_help_menu=["about", "about-services", "contact-kbase", "help"] -#unauthenticated_sidebar_menu=["dashboard", "catalog", "account"] - -#authenticated_main_menu=[] -#authenticated_developer_menu=[] -#authenticated_help_menu=["about", "contact-kbase", "help"] \ No newline at end of file diff --git a/deployment/conf/prod.env b/deployment/conf/prod.env deleted file mode 100644 index 6676c2c82..000000000 --- a/deployment/conf/prod.env +++ /dev/null @@ -1,27 +0,0 @@ -# KBase UI CI Config - -deploy_environment=prod -deploy_hostname=kbase.us -deploy_icon=thumbs-up -deploy_name=Production - -ui_backupCookie_domain=kbase.us -ui_backupCookie_enabled=true - -ui_services_analytics_google_hostname=kbase.us -ui_services_analytics_google_code=UA-74532966-1 -allow=[] - -services_narrative_url=https://narrative.kbase.us - -# For the proxier container - -deploy_ui_hostname=narrative.kbase.us - -# the ip or host of the kbase-ui container -# may need to override in the run command -kbase_ui_host=kbase-ui-container - -# true if actually deployed -# probably best to just override in the run command -deployed=true \ No newline at end of file diff --git a/deployment/dev/README.md b/deployment/dev/README.md deleted file mode 100644 index 522bea6ea..000000000 --- a/deployment/dev/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Deployment Tools and Resources - -This directory contains all of the tools and resources for a docker based deploy of kbase-ui in any of our environments (ci, next, appdev, prod) once the ui has been built. - -Note that a parallel set of tools is available in the /dev directory, since that image needs to support interactive development against multiple deployment environments. - -The deployment process is described in /docs, but briefly: - -An image is built which will contain the kbase-ui build, an nginx server, and necessary boilerplate for configuration during docker run. When the image is run the entrypoint script will have been provided with enough information to create a configuration file for the desired runtime environment. It will proceed to create the config file and then launch the nginx server. - -That is it. - -## Building image - -### tag the repo - -Before pulling down the image for building, it should have been tagged. In fact, the clone you are building from should have been pulled down by that tag. - -### build the image - -```bash -bash tools/build_docker_image.sh -``` - -## Handy Docker tools - -docker container prune -docker image prune -docker stop $(docker ps -q) -docker exec -i -t /bin/bash diff --git a/deployment/dev/docker/README.md b/deployment/dev/docker/README.md deleted file mode 100644 index a32bd021c..000000000 --- a/deployment/dev/docker/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Docker Deployment Directory - -This directory is to be copied to the docker image under /kb. -The resulting directory strucuture will be - -``` -kb - deployment - bin - entrypoint.sh - conf - deployment_templates - config.json.j2 - nginx.conf.j2 - README.md -``` - -entrypoint - the entry point script is provided in kb/deployment/bin/entrypoint.sh - -configuration templates - upon running the entrypoint script will attempt to build -the ui deployment config file. Therefore you will find no configuration file in -the kb/deployment/conf directory. Rather you will find configuration templates. - -- config.json.j2 - provides a template for kbase-ui deployment configuration -- nginx.conf.j2 - provides a simple file-serving template for nginx diff --git a/deployment/dev/docker/context/Dockerfile b/deployment/dev/docker/context/Dockerfile deleted file mode 100644 index 67a185291..000000000 --- a/deployment/dev/docker/context/Dockerfile +++ /dev/null @@ -1,44 +0,0 @@ -# This dockerfile will create an image useful for development -# What distinguishes an image as useful for development directly, or as a base -# for such an image, is that the nginx config expects to run on a hostname -# of one of our deployment environments, and it also proxies the host to -# kbase services, narrative, and dynamic service. -FROM alpine:3.7 - -# Container designed to serve up static KBase UI contents - -# These ARGs values are passed in via the docker build command -ARG BUILD_DATE -ARG VCS_REF -ARG BRANCH=develop -ARG TAG - -# Python and friends installed to get jinja2-cli, a jinja2 template cmd line tool -# used for processing the config file templates -ENV DOCKERIZE_VERSION v0.6.0 -RUN apk --update upgrade && \ - apk add --no-cache ca-certificates nginx openssl wget bash bash-completion && \ - update-ca-certificates && \ - wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - mkdir -p /kb/deployment - -RUN touch /kb/ - -COPY contents /kb/deployment - -# The BUILD_DATE value seem to bust the docker cache when the timestamp changes, move to -# the end -LABEL org.label-schema.vcs-url="https://github.com/kbase/kbase-ui.git" \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.schema-version="1.0.0-rc1" \ - us.kbase.vcs-branch=$BRANCH \ - us.kbase.vcs-tag=$TAG \ - maintainer="Steve Chan sychan@lbl.gov" - -ENTRYPOINT [ \ - "dockerize", \ - "-template", "/kb/deployment/conf/nginx.conf.tmpl:/etc/nginx/nginx.conf", \ - "-template", "/kb/deployment/conf/config.json.tmpl:/kb/deployment/services/kbase-ui/modules/deploy/config.json", \ - "nginx"] diff --git a/deployment/dev/docker/kb-deployment/conf/config.json.tmpl b/deployment/dev/docker/kb-deployment/conf/config.json.tmpl deleted file mode 100644 index 897b67a51..000000000 --- a/deployment/dev/docker/kb-deployment/conf/config.json.tmpl +++ /dev/null @@ -1,39 +0,0 @@ -{ - "deploy": { - "environment": "{{ .Env.deploy_environment }}", - "hostname": "{{ .Env.deploy_hostname }}", - "icon": "{{ .Env.deploy_icon }}", - "name": "{{ .Env.deploy_name }}", - "services": { - "urlBase": "https://{{ .Env.deploy_hostname }}" - } - }, - "ui": { - "backupCookie": { - "domain": "{{ default .Env.ui_backupcookie_domain "null"}}", - "enabled": {{ default .Env.ui_backupcookie_enabled "false"}} - }, - "services": { - "analytics": { - "google": { - "hostname": "{{ .Env.ui_services_analytics_google_hostname }}", - "code": "{{ .Env.ui_services_analytics_google_code }}" - } - }, - "session": { - "cookie": { - "backup": { - "domain": {{ if .Env.ui_backupCookie_domain }}"{{.Env.ui_backupCookie_domain}}"{{else}}null{{end}}, - "enabled": {{ default .Env.ui_backupCookie_enabled "false"}} - } - } - } - }, - "allow": {{ .Env.allow }} - }, - "services": { - "narrative": { - "url": "{{ .Env.services_narrative_url }}" - } - } -} diff --git a/deployment/dev/docker/kb-deployment/conf/nginx.conf.tmpl b/deployment/dev/docker/kb-deployment/conf/nginx.conf.tmpl deleted file mode 100644 index 75cf4ef77..000000000 --- a/deployment/dev/docker/kb-deployment/conf/nginx.conf.tmpl +++ /dev/null @@ -1,53 +0,0 @@ -daemon off; -error_log /dev/stdout info; -worker_processes auto; -pid /var/run/nginx.pid; - -events { - worker_connections 256; - # multi_accept on; -} - -http { - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # Logging Settings - ## - - - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - - ## - # Gzip settings - ## - gzip on; - gzip_vary on; - gzip_min_length 10240; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css text/xml text/javascript application/json application/x-javascript application/xml; - gzip_disable "MSIE [1-6]\."; - - server { - # no need for root privileges - listen 80; - server_name {{ default .Env.nginx_server_name "localhost" }}; - - location / { - index index.html; - ssi_silent_errors off; - allow all; - - # insert desired path here - root /kb/deployment/services/kbase-ui/; - } - } -} diff --git a/deployment/dev/tools/build_docker_image.sh b/deployment/dev/tools/build_docker_image.sh deleted file mode 100644 index 6a44984ac..000000000 --- a/deployment/dev/tools/build_docker_image.sh +++ /dev/null @@ -1,74 +0,0 @@ -#!/bin/bash -x - -# Much much longer than the original. Just wanted to get a handle -# on explicit error capture and handling. - -function get_branch() { - local branch - branch=$(git symbolic-ref --short HEAD 2>&1) - local err=$? - echo $branch - exit $err -} - -function get_commit() { - local commit - commit=$(git rev-parse --short HEAD 2>&1) - local err=$? - echo $commit - exit $err -} - -function build_image() { - local branch=$TRAVIS_BRANCH - local commit=$TRAVIS_COMMIT - local tag=$TRAVIS_TAG - local date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - local err - - echo "DATE $date" - - if [[ -z "$branch" ]]; then - branch=$(get_branch) - err=$? - if (( $err > 0 )); then - echo "No branch available: ${branch}">&2 - exit 1 - fi - fi - echo "BRANCH ${branch}" - - if [[ -z "$commit" ]]; then - commit=$(get_commit) - err=$? - if (( $err > 0 )); then - echo "No commit available: ${commit}">&2 - exit 2 - fi - fi - echo "COMMIT: $commit" - - # Not tagged as in the usual sense, but the dev tag helps us keep the - # images separate locally. - tag="dev" - - local here=`pwd` - echo "Running docker build in context ${here}/docker/context" - # TODO: don't know why can't run this in a subprocess - # NOTE: the dev build does not use the build date -- we don't want to bust the cache - docker build --build-arg VCS_REF=$commit \ - --build-arg BRANCH=$branch \ - --build-arg TAG=$tag \ - -t kbase/kbase-ui:$tag \ - ${here}/docker/context - - err=$? - if (( $err > 0 )); then - echo "Error running docker build: $err" - else - echo "Successfully build docker image. You may invoke it " - echo "with tag \"kbase/kbase-ui:${tag}\"" - fi -} - -build_image diff --git a/deployment/dev/tools/run-image.sh b/deployment/dev/tools/run-image.sh deleted file mode 100644 index ba5f1d35b..000000000 --- a/deployment/dev/tools/run-image.sh +++ /dev/null @@ -1,138 +0,0 @@ - -# runs the given docker -# docker run --mount type=bind,src=`pwd`/conf,dst=/conf 4ed624b88a0a /conf/sample-ci.ini - -# For local development, we expose both 80 and 443 -- we always run on https against the ui and services. -# -# DNS is set to google for now; leaving it alone causes it to use the host for DNS resolution, which -# has the unfortunate side effect of propagating the /etc/hosts entries as well, which messes -# up the proxying of kbase services. -# -# We mount the configuration directory in, but we could copy it. For modeling the flexibility of deployment -# reconfig, though, it should be mounted from the host environment. The configuration directory -# location is provided as arg1. This directory is mounted as /conf in the docker container. -# -# The second argument is the deployment environment - dev, ci, next, appdev, prod - which corresponds -# to the config file within that location. -# -# Then we mount whichever directories we want to be working on. -# -# TODO: this should be driven either by command line options or simply add a config section in here to -# allow declaring which plugins or bits of the ui source to mount inside. -# we use the :dev image -# we specify the configuration target as the first environment (dev, ci, next, appdev, prod). This lets -# lets us easily swap out the targeted environment without rebuilding or anything. -# NOTE: Just because the config is swapped to a different environment doesn't mean the build reflects it. -# There is a separate build for dev, ci and prod, which expose different bits of the ui. Because this -# impacts the dependencies loaded and integrated, it is not something that can be configured here. -# Well, it COULD be, I supose... -# - -# e.g. bash ./deployment/dev/tools/run-docker.sh `pwd`/deployment/conf dev - -# export config_mount=$1 - -function usage() { - echo 'Usage: run-image.sh env [-p external-plugin] [-i internal-plugin] [-l lib-module-dir:lib-name:source-path]' -} - -environment=$1 - -if [ -z "$environment" ]; then - echo "ERROR: argument 1, 'environment', not provided" - usage - exit 1 -fi - -if [ ! -e "deployment/conf/${environment}.env" ]; then - echo "ERROR: environment (arg 1) does not resolve to a config file in deployment/conf/${environment}.env" - usage - exit 1 -fi - -root=$(git rev-parse --show-toplevel) -config_mount="${root}/deployment/conf" - -echo "CONFIG MOUNT: ${config_mount}" -echo "ENVIRONMENT : ${environment}" - -echo "READING OPTIONS" - - -# Initialize our own variables: -mounts="" - - -# from: https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash -POSITIONAL=() -while [[ $# -gt 0 ]] -do -key="$1" -# -function linkSrcFile() { - # from is relative to the dev dir, the container for all repos - local from=$1 - # to is relative to this directory, but could be DEVDIR/kbase-ui - local to=$2 - - local source=$DEVDIR/kbase-ui/src/client/$from - local dest=../../build/build/client/$to - - echo "Linking source file $from" - - rm -rf $dest - ln -s $source $dest -} -case $key in - -p|--plugin) - plugin="$2" - echo "Using external plugin: ${plugin}" - mounts="$mounts --mount type=bind,src=${root}/../kbase-ui-plugin-${plugin}/src/plugin,dst=/kb/deployment/services/kbase-ui/modules/plugins/${plugin}" - shift # past argument - shift # past value - ;; - -i|--internal) - plugin="$2" - echo "Using internal plugin: ${plugin}" - mounts="$mounts --mount type=bind,src=${root}/src/client/modules/plugins/${plugin},dst=/kb/deployment/services/kbase-ui/modules/plugins/${plugin}" - shift # past argument - shift # past value - ;; - -l|--lib) - lib="$2" - # local l - read libName libPath libModule <<<$(IFS=':';echo $lib) - # IFS=':';l=($lib);unset IFS - # local libModule="${l[0]}" - # local libName="${l[1]}" - # local libPath="${l[2]}" - # e.g. kb_common:common-js:dist/kb_common - echo "Using library repo: name = $libName, path = $libPath, module = $libModule" - mounts="$mounts --mount type=bind,src=${root}/../kbase-${libName}/${libPath},dst=/kb/deployment/services/kbase-ui/modules/${libModule}" - shift - shift - ;; - -s|--service) - service="$2" - echo "Using internal services: ${service}" - mounts="$mounts --mount type=bind,src=${root}/src/client/modules/app/services/${service}.js,dst=/kb/deployment/services/kbase-ui/modules/app/services/${service}.js" - shift # past argument - shift # past value - ;; - *) # unknown option - POSITIONAL+=("$1") # save it in an array for later - shift # past argument - ;; -esac -done -set -- "${POSITIONAL[@]}" # restore positional parameters - -echo "MOUNTS: $mounts" - -docker run \ - --rm \ - --env-file ${config_mount}/${environment}.env \ - --network=kbase-dev \ - --name=kbase-ui-container \ - $mounts \ - kbase/kbase-ui:dev diff --git a/deployment/dev/tools/test.sh b/deployment/dev/tools/test.sh deleted file mode 100644 index b7b485d43..000000000 --- a/deployment/dev/tools/test.sh +++ /dev/null @@ -1,7 +0,0 @@ -function test() { - local here="$(dirname "$(dirname "$(readlink -fm "$0")")")" - echo "HERE?" - echo $here -} - -test diff --git a/deployment/prod/docker/README.md b/deployment/prod/docker/README.md deleted file mode 100644 index a32bd021c..000000000 --- a/deployment/prod/docker/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# Docker Deployment Directory - -This directory is to be copied to the docker image under /kb. -The resulting directory strucuture will be - -``` -kb - deployment - bin - entrypoint.sh - conf - deployment_templates - config.json.j2 - nginx.conf.j2 - README.md -``` - -entrypoint - the entry point script is provided in kb/deployment/bin/entrypoint.sh - -configuration templates - upon running the entrypoint script will attempt to build -the ui deployment config file. Therefore you will find no configuration file in -the kb/deployment/conf directory. Rather you will find configuration templates. - -- config.json.j2 - provides a template for kbase-ui deployment configuration -- nginx.conf.j2 - provides a simple file-serving template for nginx diff --git a/deployment/prod/docker/context/Dockerfile b/deployment/prod/docker/context/Dockerfile deleted file mode 100644 index bf8dd9d55..000000000 --- a/deployment/prod/docker/context/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -FROM alpine:3.7 -# Container designed to serve up static KBase UI contents - -# These ARGs values are passed in via the docker build command -ARG BUILD_DATE -ARG VCS_REF -ARG BRANCH=develop -ARG TAG - -ENV DOCKERIZE_VERSION v0.6.0 -RUN apk --update upgrade && \ - apk --no-cache add ca-certificates nginx openssl wget bash bash-completion && \ - update-ca-certificates && \ - wget https://github.com/jwilder/dockerize/releases/download/$DOCKERIZE_VERSION/dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - tar -C /usr/local/bin -xzvf dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - rm dockerize-alpine-linux-amd64-$DOCKERIZE_VERSION.tar.gz && \ - mkdir -p /kb/deployment - -COPY contents /kb/deployment - -# The BUILD_DATE value seem to bust the docker cache when the timestamp changes, move to -# the end -LABEL org.label-schema.build-date=$BUILD_DATE \ - org.label-schema.vcs-url="https://github.com/kbase/kbase-ui.git" \ - org.label-schema.vcs-ref=$VCS_REF \ - org.label-schema.schema-version="1.0.0-rc1" \ - us.kbase.vcs-branch=$BRANCH \ - us.kbase.vcs-tag=$TAG \ - maintainer="Steve Chan sychan@lbl.gov" - -ENTRYPOINT [ \ - "dockerize", \ - "-template", "/kb/deployment/conf/nginx.conf.tmpl:/etc/nginx/nginx.conf", \ - "-template", "/kb/deployment/conf/config.json.tmpl:/kb/deployment/services/kbase-ui/modules/deploy/config.json", \ - "nginx"] diff --git a/deployment/prod/docker/context/README.md b/deployment/prod/docker/context/README.md deleted file mode 100644 index 929f135cb..000000000 --- a/deployment/prod/docker/context/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Deployment Docker context - -This directory is a placeholder. Contents other than this directory will not be checked in. - -In the deployment process, this directory serves as the docker context. All files to be incorporated into the image are copied into this directory, and the entire contents copied to the image's /kb/deployment directory. diff --git a/deployment/prod/docker/kb-deployment/conf/config.json.tmpl b/deployment/prod/docker/kb-deployment/conf/config.json.tmpl deleted file mode 100644 index 69151f53b..000000000 --- a/deployment/prod/docker/kb-deployment/conf/config.json.tmpl +++ /dev/null @@ -1,39 +0,0 @@ -{ - "deploy": { - "environment": "{{ .Env.deploy_environment }}", - "hostname": "{{ .Env.deploy_hostname }}", - "icon": "{{ .Env.deploy_icon }}", - "name": "{{ .Env.deploy_name }}", - "services": { - "urlBase": "https://{{ .Env.deploy_hostname }}" - } - }, - "ui": { - "backupCookie": { - "domain": {{ if .Env.ui_backupcookie_domain }}"{{.Env.ui_backupcookie_domain}}"{{else}}null{{end}}, - "enabled": {{ default .Env.ui_backupcookie_enabled "false"}} - }, - "services": { - "analytics": { - "google": { - "hostname": "{{ .Env.ui_services_analytics_google_hostname }}", - "code": "{{ .Env.ui_services_analytics_google_code }}" - } - }, - "session": { - "cookie": { - "backup": { - "domain": {{ if .Env.ui_backupCookie_domain }}"{{.Env.ui_backupCookie_domain}}"{{else}}null{{end}}, - "enabled": {{ default .Env.ui_backupCookie_enabled "false"}} - } - } - } - }, - "allow": {{ .Env.allow }} - }, - "services": { - "narrative": { - "url": "{{ .Env.services_narrative_url }}" - } - } -} diff --git a/deployment/prod/docker/kb-deployment/conf/nginx.conf.tmpl b/deployment/prod/docker/kb-deployment/conf/nginx.conf.tmpl deleted file mode 100644 index 83035bc52..000000000 --- a/deployment/prod/docker/kb-deployment/conf/nginx.conf.tmpl +++ /dev/null @@ -1,60 +0,0 @@ -daemon off; -error_log /dev/stdout info; -worker_processes auto; -pid /var/run/nginx.pid; - -events { - worker_connections 256; - # multi_accept on; -} - -http { - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - include /etc/nginx/mime.types; - default_type application/octet-stream; - - ## - # Logging Settings - ## - - - {{ if eq .Env.deployed "true" }} - # Only log to these locations if deployed. This allows local testing of this image. - error_log /dev/stdout; - access_log syslog:server=10.58.0.54,facility=local2,tag=ci,severity=info combined; - {{ else }} - # But if running locally preserve the logs for debugging - access_log /var/log/nginx/access.log; - error_log /var/log/nginx/error.log; - {{ end }} - - ## - # Gzip settings - ## - gzip on; - gzip_vary on; - gzip_min_length 10240; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css text/xml text/javascript application/json application/x-javascript application/xml; - gzip_disable "MSIE [1-6]\."; - - server { - # no need for root privileges - listen 80; - server_name {{ default .Env.nginx_server_name "localhost" }}; - - location / { - index index.html; - ssi_silent_errors off; - allow all; - - # insert desired path here - root /kb/deployment/services/kbase-ui/; - } - } -} diff --git a/deployment/prod/tools/README.md b/deployment/prod/tools/README.md deleted file mode 100644 index f290c8346..000000000 --- a/deployment/prod/tools/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Deployment Tools and Resources - -This directory contains all of the tools and resources for a docker based deploy of kbase-ui in any of our environments (ci, next, appdev, prod) once the ui has been built. - -Note that a parallel set of tools is available in the /dev directory, since the image needs to support interactive development. - -The deployment process is described in /docs, but briefly: - -An image is built which will contain the kbase-ui build, an nginx server, and necessary boilerplate for configuration during docker run. When the image is run the entrypoint script will have been provided with enough information to create a configuration file for the desired runtime environment. It will proceed to create the config file and then launch the nginx server. - -That is it. - -## Building image - -### tag the repo - -Before pulling down the image for building, it should have been tagged. In fact, the clone you are building from should have been pulled down by that tag. - -The tag should be checked into the canonical repo, and the local clone based on that tag. - -For local testing of the build, you can tag locally to simulate this. - -E.g. - -git tag v1.5.0 -m "1.5.0" - -To remove the tag - -git tag --delete v1.5.0 - -### Build kbase-ui - -The ui production build will only work if it is checked out on a specific tag. - -### build the image - -```bash -bash tools/build_docker_image.sh -``` diff --git a/deployment/prod/tools/build_docker_image.sh b/deployment/prod/tools/build_docker_image.sh deleted file mode 100755 index b8518b1f6..000000000 --- a/deployment/prod/tools/build_docker_image.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/bash -x - -# Much much longer than the original. Just wanted to get a handle -# on explicit error capture and handling. - -function get_tag() { - # local error variable captures subprocess exit code in $? - # need this so that other commands don't overwrite it - # the variable holding the git command subprocess output must be - # declared local previously because local itself will reset $? - local tag - tag=$(git describe --exact-match --tags $(git rev-parse HEAD) 2>&1) - local err=$? - echo $tag - exit $err -} - -function get_branch() { - local branch - branch=$(git symbolic-ref --short HEAD 2>&1) - local err=$? - echo $branch - exit $err -} - -function get_commit() { - local commit - commit=$(git rev-parse --short HEAD 2>&1) - local err=$? - echo $commit - exit $err -} - -function build_image() { - local branch=$TRAVIS_BRANCH - local commit=$TRAVIS_COMMIT - local tag=$TRAVIS_TAG - local date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") - local err - - echo "DATE $date" - - if [[ -z "$branch" ]]; then - branch=$(get_branch) - err=$? - if (( $err > 0 )); then - echo "No branch available: ${branch}">&2 - exit 1 - fi - fi - echo "BRANCH ${branch}" - - if [[ -z "$commit" ]]; then - commit=$(get_commit) - err=$? - if (( $err > 0 )); then - echo "No commit available: ${commit}">&2 - exit 2 - fi - fi - echo "COMMIT: $commit" - - if [[ -z "$tag" ]]; then - tag=$(get_tag) - err=$? - if (( $err > 0 )); then - echo "Sorry, not on a tag, won't build: ${tag}">&2 - exit 3 - fi - fi - echo "TAG $tag" - - local image_tag="master" - - local here="$(dirname "$(dirname "$(readlink -fm "$0")")")" - local here=`pwd` - echo "Running docker build in context ${here}/docker/context" - - # TODO: don't know why can't run this in a subprocess - - # NOTE: the image is tagged "master" for production builds. In a real - # deploy the image would be pushed up to dockerhub with the master image tag. - - docker build \ - --build-arg BUILD_DATE=$date \ - --build-arg VCS_REF=$commit \ - --build-arg BRANCH=$branch \ - --build-arg TAG=$tag \ - -t kbase/kbase-ui:${image_tag} ${here}/docker/context - - err=$? - if (( $err > 0 )); then - echo "Error running docker build: $err" - else - echo "Successfully build docker image. You may invoke it " - echo "with tag \"kbase/kbase-ui:${image_tag}\"" - fi -} - -build_image diff --git a/deployment/prod/tools/push2dockerhub.sh b/deployment/prod/tools/push2dockerhub.sh deleted file mode 100755 index 67df3665b..000000000 --- a/deployment/prod/tools/push2dockerhub.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -# -# This script is intended to be run in the deploy stage of a travis build -# It checks to make sure that this is a not a PR, and that we have the secure -# environment variables available and then checks if this is either the master -# or develop branch, otherwise we don't push anything -# -# NOTE: IMAGE_NAME is expected to be passed in via the environment so that this -# script can be more general -# -# sychan@lbl.gov -# 8/31/2017 - -# Assign the tag to be used for the docker image, and pull the git commit from either -# the TRAVIS_COMMIT env var if available, or else get the short commit via git cmd - -# Note: do not use now; for one, -exit 1 - -TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi` -COMMIT=${TRAVIS_COMMIT:-`git rev-parse --short HEAD`} - -if ( [ "$TRAVIS_SECURE_ENV_VARS" == "true" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ] ); then - # $TAG was set from TRAVIS_BRANCH, which is a little wonky on pull requests, - # but it should be okay since we should never get here on a PR - if ( [ "$TAG" == "latest" ] || [ "$TAG" == "master" ] ) ; then - echo "Logging into Dockerhub as $DOCKER_USER" - docker login -u $DOCKER_USER -p $DOCKER_PASS && \ - docker tag $IMAGE_NAME:$COMMIT $IMAGE_NAME:$TAG && \ - echo "Pushing $IMAGE_NAME:$TAG" && \ - docker push $IMAGE_NAME:$TAG || \ - ( echo "Failed to login and push tagged image" && exit 1 ) - else - echo "Not pushing image for branch $TAG" - fi -else - echo "Not building image for pull requests or if secure variables unavailable" -fi diff --git a/deployment/prod/tools/run-image.sh b/deployment/prod/tools/run-image.sh deleted file mode 100644 index 6b7035133..000000000 --- a/deployment/prod/tools/run-image.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash - -function usage() { - echo 'Usage: run-image.sh env type' -} - -environment=$1 - -if [ -z "$environment" ]; then - echo "ERROR: argument 1, 'environment', not provided" - usage - exit 1 -fi - -if [ ! -e "deployment/conf/${environment}.env" ]; then - echo "ERROR: environment (arg 1) does not resolve to a config file in deployment/conf/${environment}.env" - usage - exit 1 -fi - -root=$(git rev-parse --show-toplevel) -config_mount="${root}/deployment/conf" - -echo "CONFIG MOUNT: ${config_mount}" -echo "ENVIRONMENT : ${environment}" - -echo "READING OPTIONS" - -image_tag="master" - -docker run \ - --rm \ - --env-file ${config_mount}/${environment}.env \ - --env deployed=false \ - --network=kbase-dev \ - --name=kbase-ui-container \ - kbase/kbase-ui:${image_tag} diff --git a/deployment/proxier/docker/context/README.md b/deployment/proxier/docker/context/README.md deleted file mode 100644 index 929f135cb..000000000 --- a/deployment/proxier/docker/context/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Deployment Docker context - -This directory is a placeholder. Contents other than this directory will not be checked in. - -In the deployment process, this directory serves as the docker context. All files to be incorporated into the image are copied into this directory, and the entire contents copied to the image's /kb/deployment directory. diff --git a/deployment/ci/tools/build_docker_image.sh b/deployment/tools/build_docker_image.sh similarity index 77% rename from deployment/ci/tools/build_docker_image.sh rename to deployment/tools/build_docker_image.sh index 5caf78b41..4f0b6211b 100755 --- a/deployment/ci/tools/build_docker_image.sh +++ b/deployment/tools/build_docker_image.sh @@ -60,21 +60,9 @@ function build_image() { fi echo "COMMIT: $commit" - # if [[ -z "$tag" ]]; then - # tag=$(get_tag) - # err=$? - # if (( $err > 0 )); then - # echo "Sorry, not on a tag, won't build: ${tag}">&2 - # exit 3 - # fi - # fi - # echo "TAG $tag" - - local image_tag="develop" - - local here="$(dirname "$(dirname "$(readlink -fm "$0")")")" local here=`pwd` - echo "Running docker build in context ${here}/docker/context" + local context="${here}/ci/docker/context" + echo "Running docker build in context $context" # TODO: don't know why can't run this in a subprocess @@ -85,14 +73,14 @@ function build_image() { --build-arg BUILD_DATE=$date \ --build-arg VCS_REF=$commit \ --build-arg BRANCH=$branch \ - -t kbase/kbase-ui:${image_tag} ${here}/docker/context + -t kbase/kbase-ui:${branch} ${context} err=$? if (( $err > 0 )); then echo "Error running docker build: $err" else - echo "Successfully build docker image. You may invoke it " - echo "with tag \"kbase/kbase-ui:${image_tag}\"" + echo "Successfully built docker image. You may invoke it " + echo "with tag \"kbase/kbase-ui:${branch}\"" fi } diff --git a/deployment/ci/tools/push2dockerhub.sh b/deployment/tools/push2dockerhub.sh similarity index 91% rename from deployment/ci/tools/push2dockerhub.sh rename to deployment/tools/push2dockerhub.sh index 5e6d94bc2..bcea92b36 100755 --- a/deployment/ci/tools/push2dockerhub.sh +++ b/deployment/tools/push2dockerhub.sh @@ -14,10 +14,6 @@ # Assign the tag to be used for the docker image, and pull the git commit from either # the TRAVIS_COMMIT env var if available, or else get the short commit via git cmd -# Note: do not use now; for one, -exit 1 - - TAG=`if [ "$TRAVIS_BRANCH" == "master" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi` COMMIT=${TRAVIS_COMMIT:-`git rev-parse --short HEAD`} @@ -27,7 +23,8 @@ if ( [ "$TRAVIS_SECURE_ENV_VARS" == "true" ] && [ "$TRAVIS_PULL_REQUEST" == "fal if ( [ "$TAG" == "latest" ] || [ "$TAG" == "develop" ] ) ; then echo "Logging into Dockerhub as $DOCKER_USER" docker login -u $DOCKER_USER -p $DOCKER_PASS && \ - docker tag $IMAGE_NAME:$COMMIT $IMAGE_NAME:$TAG && \ + # In this repo, the image is already tagged with the branch + # docker tag $IMAGE_NAME:$COMMIT $IMAGE_NAME:$TAG && \ echo "Pushing $IMAGE_NAME:$TAG" && \ docker push $IMAGE_NAME:$TAG || \ ( echo "Failed to login and push tagged image" && exit 1 ) diff --git a/docs/deployment/disabling-menus.md b/docs/deployment/disabling-menus.md new file mode 100644 index 000000000..667433ad2 --- /dev/null +++ b/docs/deployment/disabling-menus.md @@ -0,0 +1,59 @@ +# Disabling Menus in a Deployment + +The UI menus consist of the hamburger and the sidebar. + +The menus are defined by individual plugins. The definition includes an id, label, icon, a flag indicating whether it appears only when the session is logged in, and a flag indicating if the menu item is disabled. + +A menu item does not appear just because it is defined. The ui itself possesses a set of configuration files defining the items appearing on a menu. These files are located at + +- config/app/dev/services +- config/app/ci/services +- config/app/prod/services + +The menu settings are set on the ui/services/menu config branch, as they menu is managed by the "menu" ui service. + +The menu settings are determined at build time during the config merge phase, at which time the various config files are merged and written to ROOT/modules/config/config.json + +The deployment config resides in ROOT/modules/deploy/config.json. + +It contains a property ```ui.services.menu``` which contains a structure like this: + +```json + "menu": { + "menus": { + "hamburger": { + "disabled": [] + }, + "sidebar": { + "disabled": ["search"] + } + } + } +``` + +The menus.sidebar.disabled and menus.hamburger.disabled properties each contain an array of menu ids to disable. The menu ids are applied as a filter to each menu section of the respective menus. + +## Setting the disabled menu items + +### Environment File + +If a menu item is to be disabled in a semi-permanent or permanent mode, it should be set in the deploy config environment file. + +In the kbase-ui repo, the development config files live in deployment/conf/DEPLOY.env, where DEPLOY is one of dev, ci, next, appdev or prod. + +Two environment variables may be used to pass to the deploy config template: + +``` +ui_services_menu_sidebar_disabled=["search"] +ui_services_menu_hamburger_disabled=[] +``` + +By replacing the underscores (_) with dots, you can see that the variable name forms a path into the kbase-ui config structure. + +The value for each one is a json array containing menu ids to be disabled. + +Note that the variable values have the form of JSON arrays, but are in actuality transported into the template purely as strings. So be careful. + +### Template File + +The deploy template \ No newline at end of file diff --git a/docs/design/configuration.md b/docs/design/configuration.md new file mode 100644 index 000000000..9fbc2a179 --- /dev/null +++ b/docs/design/configuration.md @@ -0,0 +1,155 @@ +# KBase-UI Configuration + +kbase-ui is designed to be configurable in all the right places. + +Configuration is key to each phase of working with kbase-ui: + +- installing tools +- building +- deploying +- running + +The files: + +- config + - app + - dev + - plugins.yml + - services.yml + - prod + - plugins.yml + - services.yml + - ui.yml + - build + - configs + - dev.yml + - ci.yml + - prod.yml + - defaults.yml + - deploy + - appdev.env + - ci.env + - dev.env + - next.env + - prod.env + - bowerInstally.yml + - npmInstall.yml + - release.yml + - services.yml + +## Base Tools + +The core OS dependencies are not configurable. Certain system [prerequisites](../prerequisites.md) must be manually installed in order to have adequate local functionality to work with kbase-ui + +> We do hope to reduce all depencies down to a Docker, so that even the base tools would be provided by configuration, the Dockerfile. + +## Installing Tools + +Once the core system dependencies are in place, the ```system.json``` file located at the root of the kbase-ui repo provides a list of all tools required to build and test kbase-ui. There is seldom a need to alter ```system.json``` and nothing to be done for a typical working session or deployment. + +## Building + +kbase-ui relies upon a set of configuration files to control the contents of and the manner in which the app is built. + +### builds + +The `builds` directory contains the top level build configurations. Configuration-wise, it is the entrypoint to building the ui. + +Two files are consulted for a build; they are merged together to form the build config. The *defaults.yml* file contains the basic build default settings. It is rarely changed, but is useful for debugging the build itself. The second file is one of the files within `build/configs`. This latter file is used to determine certain characteristics of the build and build process. + +In practice the config files are invoked with the *build* make task using the *config* argument. For example, + +```bash +make build config=dev +``` + +builds the ui using the build/configs/dev.yml build config, and implicitly the builds/defaults.yml. + +#### defaults.yml + +The defaults.yml file supports the following settings: + +- tempDir - (string, directory) where to place the build working files; usually + +- mutate - (boolean) whether to use the immutable or mutable method of building + +- keepBuildDir - (boolean) whether to remove the working build dir (in tempDir) or not + +##### tempDir + +During the build process, kbase-ui copies all source files and dependencies, places them into their final destinations, combines configurations, and so forth. Because this process can produce a lot of disk activity and may even leave behind build artifacts (depending on other build options used), this setting allows one to specify where that temp dir is located. + +The preset value is *temp/files*, and is thus located within the repo, but this may of course be overridden and does not affect the build product. + +##### mutate + +The *mutate* flag determines whether the build tool will create a copy of the build directory at each primary build step (false) or not (true). + +The value defaults to *true*. + +One of the original features of this build tool is that in order to assist in debugging the build process, each step which creates, deletes, or changes files first makes a copy of the entire build directory. This can be especially useful when incorporating new dependencies or transformations because it enables one to inspect the file contents before and after a transformation has been applied. + +In practice the build is rarely operated in immutable mode, but it is very useful when needed. + +##### keepBuildDir + +Normally the build working directory, located in tempDir, is removed at the end of the build process. If the build fails, the directory is always left intact, for inspection. However, at times it can be helpful to inspect the build working directory to diagnose a build malfunction (in which it succeeds but does the wrong thing). Usually this would be in combination with setting *mutate* to false. + +The default value is false. + +#### builds/{build}.yml + +Alongsize the build defaults config is a build target config. This allows for controlling characteristics of the build for different build use cases. + +Located inside ```build/configs```, each file describes a set of build settings for a given build scenario. The *dev* config is best for local development, *ci* for building for the CI environment, and *prod* for the build meant for deployment in one of the production environments - next, appdev, and production itself. + +The following settings are supported: + +- target - (string) the app type to build, either dev or prod +- release - (boolean) whether the build is a release (with new version, release notes) or not +- dist - (boolean) whether to build the distributable production files (minified) +- vfs - (boolean) whether to build the virtual file system, a bundle of javascript and other files + +##### target + +The target corresponds the set of configuration files which control the contents of the built kbase-ui. The target value corresponds to the base file name located in config/app. At present two targets are supported, *dev* and *prod*. The dev ui is used for local development. It contains several features not available in the prod build, primarily to assist in local development. It also may contain highly speculative features which should not appear in any version of a production deployment. + +In the past a *ci* target supported new features which were to be deployed only in CI for review. The *prod* build only contained features which were completely ready for users. + +The new approach at KBase is to "release" features into the kbase-ui app as soon as they are minimally useful. Even if not ready for users and available in the menu, they will be present in the app code itself. + +##### release + +Determines whether the build is handled as an app release or not. An app release involves checking that the repo head is tagged, that the tag has a semver format, that the tag matches the release stamp (config/release.yml), and that release notes are available for that version. Other quality control checks may be put in place. + +The purpose of the release flag is to allow local development and CI builds to proceed at a rapid clip, yet to ensure that the released app is tied up into a nice package. + +##### dist + +The dist flag determines whether the build applies extra validation and optimization to the app. It results in a separate directory tree; the primary build product is located in build/build, the dist build is located in build/dist. + +The primary purpose of the dist build is to produce smaller files, via the process known as "minification". It can also can be considered more generally optimization because the minification tools parse the source code into AST, and rewrite the code with certain optimizations, such as inlining, variable renaming, omission of comments, and in some cases dead code eliminitation. Since all javascript code is parsed, it must be syntactically valid, so this is also a code verification step. + +Since it is a lengthy process, this code compilation stage is disabled for the *dev* build target; but enabled for *ci* and *prod*. + +##### vfs + +Another optimization for deployed builds is the AMD virtual filesystem. This is rather simple, actually just a very large map of path to function or value (string or object). When this flag is set, the build tool creates this large map, saves it to the build and modifies the apps primary "index.html" file to load it. A modified version of the module loader will preferentially load modules from the pre-loaded VFS and avoid network calls. This improves performance (at the cost of initially loading the VFS) and stability (fewer module loading errors during spotty network conditions.) + +The *vfs* flag is set to false for dev, and true for ci and prod. + +## app + +The *app* directory defines the runtime features and behavior of kbase-ui. It accomplishes this through three configuration files. + +- ui.yml - the core ui configuration across all environments +- {ui-type}/plugins.yml - defines plugins to be installed and loaded +- {ui-type}/services.yml - defines ui services to be loaded and configuration options + +Two ui-types allows different versions of the ui to be created, containing different capabilities through + +### ui.yml + +The primary ui configuration file defines constants for global ui features, as well as ui services. This file provides default behavior, and also a location to store constants to avoid hardcoding them. + +{ui-type} \ No newline at end of file diff --git a/docs/dev/building.md b/docs/dev/building.md new file mode 100644 index 000000000..267311dd2 --- /dev/null +++ b/docs/dev/building.md @@ -0,0 +1,50 @@ +# About Building + + + +Here is the usage output: + +```bash +Usage: run-image.sh env [-p external-plugin] [-i internal-plugin] [-l lib-module-dir:lib-name:source-path]' +``` + +You should see a successful startup of the docker container, with log lines printed into the console. + + + +#### env argument + +The first argument to run-image is the ```env``. This value corresponds to the corresponding deployment config file located in deployment/conf. Since we are supplying "dev", we will be using the deploy file "deployment/conf/dev.ini". + +#### -p argument + +We are using the -p argument to specify an external plugin to link into the kbase-ui image. Note that we use the plugin name, not the repo name. The tool knows how to find the plugin directory based on the directory structure (it should be a sister directory to kbase-ui) and uniform naming structure for repos (kbase-ui-plugin-PLUGINNAME). + +### Example + +```bash +bash /Users/erikpearson/work/kbase/sprints/2018Q1S1/kbase-ui/deployment/dev/tools/run-image.sh dev +CONFIG MOUNT: /Users/erikpearson/work/kbase/sprints/2018Q1S1/kbase-ui/deployment/conf +ENVIRONMENT : dev +READING OPTIONS +MOUNTS: +BusyBox v1.26.2 (2017-11-23 08:40:54 GMT) multi-call binary. + +Usage: dirname FILENAME + +Strip non-directory suffix from FILENAME +NGINX CONFIG TEMPLATE /kb/deployment/bin/../conf/deployment_templates/nginx.conf.j2 +DATA SRC /conf/dev.ini +CONFIG TEMPLATE /kb/deployment/bin/../conf/deployment_templates/config.json.j2 +2018/01/03 23:45:01 [notice] 13#13: using the "epoll" event method +2018/01/03 23:45:01 [notice] 13#13: nginx/1.12.2 +2018/01/03 23:45:01 [notice] 13#13: OS: Linux 4.9.60-linuxkit-aufs +2018/01/03 23:45:01 [notice] 13#13: getrlimit(RLIMIT_NOFILE): 1048576:1048576 +2018/01/03 23:45:01 [notice] 13#13: start worker processes +2018/01/03 23:45:01 [notice] 13#13: start worker process 14 +2018/01/03 23:45:01 [notice] 13#13: start worker process 15 +2018/01/03 23:45:01 [notice] 13#13: start worker process 16 +2018/01/03 23:45:01 [notice] 13#13: start worker process 17 +``` + +> TODO: provide a make task for this \ No newline at end of file diff --git a/docs/dev/creating-a-new-plugin.md b/docs/dev/creating-a-new-plugin.md new file mode 100644 index 000000000..8ab279243 --- /dev/null +++ b/docs/dev/creating-a-new-plugin.md @@ -0,0 +1,413 @@ +# Creating a New Plugin + +This document describes the process for creating a new kbase-ui plugin and integrating it into the kbase-ui build. It is best if you have already absorbed [Developing a Plugin](developing-a-plugin.md). + +[TOC] + +## Prerequisities + +- [Prerequisites](prerequisites.md) +- [Getting Started](getting-started.md) +- [Developing a Plugin](developing-a-plugin.md) + + + +## Setup + +After setting up a new project directory as described in the [Getting Started](getting-started.md) guide, you should put yourself in a terminal opened inside the project directory, *~/work/project*. + +## Clone Sample Plugin Repo + +First we need to clone the sample plugin repo as a copy. + +```bash +cd ~/work/project +git clone --depth=1 --branch=master https://github.com/eapearson/kbase-ui-plugin-simple-sample kbase-ui-plugin-MYPLUGIN +``` + +This will clone the sample plugin into a directory named after the new plugin name MYPLUGIN. This will not be functional git repo, since it was cloned with a depth of 1. + +> Note: We should move the simple-sample plugin into the kbase account + +### Change the name of the plugin + +The sample plugin name is *simple-sample*. This name is used in many places to namespace this plugin. In your IDE or tool of choice you can simply perform a global search-and-replace from "simple-sample" to "MYPLUGIN" (where MYPLUGIN) is the name of your new plugin.) + +Your plugin name should be short, but fully descriptive of the purpose of the plugin. In this document when we use *MYPLUGIN* we mean the name you have chosen for your plugin. + +At the time of writing the global search-and-replace will make the following changes, with additional changes you may to to make noted + +- bower.json + - changes the name property from "kbase-ui-plugin-simple-sample" to "kbase-ui-plugin-MYPLUGIN" + - changes the property repository.url to point to the github repo + +- src/plugin/config.yml + - changes the package.name property from "simple-sample" to "MYPLUGIN" + - YOU: also change the author and description if appropriate + - changes the install.widgets[0].id from simple-sample_panel to MYPLUGIN_panel + - changes the intall.routes[0].path from [simple-sample] to [MYPLUGIN] + - changes the install.routes[0].widget from simple-sample_panel to MYPLUGIN_panel + - changes the install.menu.name property form simple-sample to MYPLUGIN + - changes the install.menu.definition.path from [simple-sample] to [MYPLUGIN] + +- src/plugin/resources/css + - changes the class name plugin_simple-sample_panel to plugin_MYPLUGIN_panel + +- src/plugin/modules + - changes the class name list 'plugin_simple-sample_panel container-fluid' to 'plugin_MYPLUGIN_panel container-fluid' + +> NOTE: You will discover that there are a few odds and ends that need to be changed as well, such as the menu label, the page title, and of course the sample content. We'll cover that later. + +## Add plugin to kbase-ui config + +In order for a plugin to be made available and integrated into kbase-ui, several files in kbase-ui need to be modified. + +- build configuration file - specify the plugin id and version +- build services configuration file - specify menu items which should appear +- deployment environment file - a menu item specified above may be disabled in one or more deployments + +### plugins.yml + +The primary entry point for a plugin into *kbase-ui* is the `plugins.yml` config file. This file lists all of the plugins which will be loaded into kbase-ui. + +Since there are two build types for kbase-ui, the local developer build and the main production build, there are two plugin configuration files. This allows us to iterate quickly over new development, have the work shared via github, and the have the work reviewable without introducing the code into a shared environment just yet. + +However, in practice we introduce new plugins destined for production into both the dev and prod build configurations. We have a second barrier for code to enter production, the develop / master branch practice, in which develop is used for local develop and CI, and master for next, appdev, and production. + +On the other hand, we do not like to create much distance from the develop branch and master, in terms of deployable features. Thus, if we have new plugins introduced into the prod build in the develop branch, and they are in an unstable state, we may introduce friction and uncertaintly in efforts to merge develop into master and initiate and deployment. + +Well, back to the task at hand, in plugins.yml you will need to add a new entry to the bottom of the plugins section. + +Edit the file *~/work/project/kbase-ui/config/app/dev/plugins.yml*. + +At the bottom of the file add this (replacing MYPLUGIN with the plugin name you have chosen) + +```yaml + - + name: MYPLUGIN + globalName: kbase-ui-plugin-MYPLUGIN + version: 0.1.0 + cwd: src/plugin + source: + directory: {} +``` + +This entry uses the "directory" source. With this setting (you will notice that all others in the config file use "bower"), the kbase-ui build tool will fetch the plugin code from the local plugin directory. With the "bower" setting, which we will set later, the plugin code would be fetched from github via the bower package manager. + +### services.yml + +We'll add a menu item to the ui as well. + +Menu items appearing in the hamburger and sidebar menus are defined in plugins, but their appearance in a menu and order are defined in the ui itself. + +The menu configuration is located within the *services.yml* file. This file contains the default configuration for all kbase-ui services. A kbase-ui service is an internal process which is responsible for some aspect of the user interface. In this case the *menu* service is responsible for providing the list of menus. (An internal plugin is responsible for displaying them.) + +Edit the file *~/work/project/kbase-ui/config/app/dev/services.yml* + +Below is the current developer menu configuration: + +```yaml +menu: + menus: + hamburger: + sections: + main: + items: + - + id: narrative + auth: true + - + id: jgi-search + auth: true + developer: + items: + - + id: dagre-example + auth: true + - + id: simple-search + auth: true + - + id: simple-sample + auth: true + - + id: narrative-finder + auth: true + - + id: staging-browser + auth: true + - + id: example-gopherjs + auth: true + - + id: reske-admin + auth: true + - + id: reske-object-search + auth: true + - + id: tester + auth: true + - + id: test_dynamic_table + auth: true + - + id: paveldemo + auth: true + - + id: databrowser + auth: true + - + id: typebrowser + auth: true + - + id: shockbrowser + auth: true + help: + items: + - + id: about + auth: false + - + id: about-services + auth: true + - + id: contact-kbase + auth: false + - + id: help + auth: false + sidebar: + sections: + main: + items: + - + id: dashboard + auth: true + - + id: appcatalog + auth: false + - + id: search + auth: true + - + id: jobbrowser + # The label can ovveride the label from the plugin here + label: Jobs + auth: true + - + id: account + auth: true + - + id: feeds + auth: true + allow: [alpha] +``` + +You can see that menu items are defined for the hamburger and sidebar, and that each is divided into sections. + +The hamburger menu has three sections — main, developer, and help. Only the main and help sections currently appear in any production site, while developer appears in CI and local development. The main section contains the primary hamburger menu actions. When the sidebar menu was introduced, years after the hamburger menu, the hamburger menu was more or less relegated to secondary functions. The help menu is generally for help and reference. The developer menu is used for tools which are useful for ui developers (especially in the local dev build), and for internal users (who might be using CI.) + +The sidebar menu contains a single *main* section, and is the primary navigation to core kbase-ui features. Each of these features is provided by a plugin. + +So, back to adding the menu item. The menu item fieldsa are: + +- `id` - the internal menu id as provided by the plugin +- `auth` - true if the menu item should only appear with the ui is in a logged-in (authenticated/authorized) state; most menu items require this +- `label` - optional, may be used to override the menu label defined in the plugin; not often used, usually to relabel the menu until the plugin can be updated +- `allow` - optional, a list of enablements for which this menu item may be displayed; if missing, implies that the menu item always appears (honoring the auth flag); if provided the menu item will only appear when the ui contains the specified enablements. Useful for having menu items only appear if the ui is enabled for alpha, beta, or experimental features. + +Since each menu item generally corresponds to the entry point for a plugin, the menu id is usually simply the plugin id. + +The sample plugin we are working with was set up that way, and the search and replace you (hopefully) performed earlier would have transformed this to MYPLUGIN. + +So... + +Add the following entry to the developer menu (where new plugins typically first appear): + +```yaml +- + id: MYPLUGIN + auth: true +``` + +This is going into the local dev build, so we don't have to put any conditions on the menu item appearing. + +## Test the new plugin + +We'll take a short detour here to ensure that the plugin works, before completing the new plugin setup. + +In the kbase-ui directory, build and run the image with the new configuration. + +```bash +cd ~/work/project/kbase-ui +make build config=dev +make image build=build +make run-image env=dev +``` + +Now point your favorite web browser to [https://ci.kbase.us](https://ci.kbase.us). + +You should see the new menu item in the hamburger menu. Selecting the new menu item should show the default sample plugin content. + +Although it is tempting to start working on the plugin, let's first finish the plugin configuration. + +## Create Github Repo + +You'll start by creating a github repo under your account. After the plugin is established, or at any time you wish, this repo should be transferred to the *kbase* github account. + +- log into github.com +- select "New repository" from the "+" menu +- use the name "kbase-ui-plugin-MYPLUGIN" for the repo (where, as usual MYPLUGIN is the name you have chosen) + +## Initialize and push the plugin + +Back in your plugin's folder, convert it into a git repo and push it up to the new github repo you just created. + +```bash +cd ~/work/project/kbase-ui-plugin-MYPLUGIN +git init +git remote add origin https://github.com/YOURACCOUNT/kbase-ui-plugin-MYPLUGIN +git add . +git commit -m "Initial commit for new plugin MYPLUGIN" +git push -u origin master +``` + +## Make the initial release + +All external plugins are pulled into a kbase build by their version. The version is requested via bower, which in turn recognizes versions set up with the correct semver format as git tags in github. + +Goto your github page for the plugin: + +- Take your browser to https://github.com/YOURACCOUNT/kbase-ui-plugin-MYPLUGIN +- Select the "release" tab +- Click the "Draft a new release" button +- For "Tag version" enter "v0.1.0" +- For "Release title" enter "0.1.0" +- For "Describe this release" you may describe the initial state of the plugin, or simply leave it blank. +- For "This is a pre-relase", check the box + +> Note that all releases before 1.0.0 are assumed to be in development, so I'm not sure that the pre-release status is necessary; it only appears in the github web ui, afaik. + +## Register in Bower + +In order for the kbase-ui build tool to be able to use bower to fetch the plugin, you need to first register it in the bower registry. + +Back in your terminal: + +```bash +bower register kbase-ui-plugin-MYPLUGIN https://github.com/YOURACCOUNT/kbase-ui-plugin-MYPLUGIN +``` + +> Caveats: Bower is deprecated - we simply haven't had time yet to move to another workflow. + +> At a later time, after the repo is transfered to the kbase account, the plugin will need to be un-registered from your account and re-registered under kbase. + + +## Change the plugin from "directory" to "bower" + +As mentioned above, we initially set the plugin configuration to be installed from a directory. We will now switch that to bower. Make the plugin entry we added bofore look like the one below (we are just changing ```source.directory: {}``` to ```source.bower: {}```) + +```yaml + - + name: MYPLUGIN + globalName: kbase-ui-plugin-MYPLUGIN + version: 0.0.1 + cwd: src/plugin + source: + bower: {} +``` + +## Build and Confirm + +Now you are ready to build kbase-ui again and confirm that the plugin has been integrated. + +### Stop the container + +First, you will need to stop the currently running container that you started earlier. Remember, that is simply `Ctrl` + `C`. + +### Build and run again + +Now lets build the ui and run it again to confirm that the changes worked. + +```bash +cd ~/work/project/kbase-ui +make build config=dev +make image build=build +make run-image env=dev +``` + +Now pull up your favorite browser to [https://ci.kbase.us](https://ci.kbase.us) to confirm that the plugin loaded correctly. + +If you added a new menu item, confirm that it is in the menu, and select it. + +If the plugin is not correctly registered in bower, or misconfigured in plugins.yml, or the services.yml is incorrect, either the build or the initial load of the kbase-ui web app will fail. + +## Make a few changes + +As you will have noticed, this new plugin still looks like the sample plugin, because we haven't changed any of the sample content yet. Let's do that now. + +### Developer Workflow + +We have succeeded in having the ui built from the plugin by pulling a specific version down from it's github repo. This is critical for reproducible builds. However, during development time it we want to have our local changes reflected in the ui. + +To accomplish this we just need to run the image with options to map the plugin folder into the container (this is described in more detail in the [developing a plugin](developing-a-plugin.md) document.) + +To accomplish this: + +- stop the container +- start the container with the following commands: + +```bash +cd ~/work/project/kbase-ui +make run-image env=dev plugins=MYPLUGIN +``` + +This will run the kbase-ui image while mounting the plugin directory inside, overriding the plugin directory that was included in the build. + +If you refresh browser, your plugin should still display. + +### Menu Item + +First lets take control of the menu item's label. + +Edit the file *~work/project/kbase-ui-plugin-MYPLUGIN/src/plugin/config.yml*. + +At the bottom of the file you'll see the *menu:* section. The *label:* property controls the display label for the menu item. Change it to whatever you like. + +Now reload the browser, then click on the hamburger menu. You should see the change you just made. + +Feel free to change the icon as well. The icon value is the suffix part of a [Font Awesome](http://fontawesome.io/icons/) icon class. The sample icon is a bicycle, so Font Awesome icon class is fa-bicycle. + +### Page Title + +The page title is set via a message sent to the ui. The page title is typically set in the widget associated with the current path. These widgets are referred to as "panel widgets" since they typically control the entire content panel of the ui. + +You can find it in *~work/project/kbase-ui-plugin-MYPLUGIN/src/plugin/modules/panel.js*. + +The line looks like: + +```javascript +runtime.send('ui', 'setTitle', 'Simple Sample Plugin Title'); +``` + +Simply change the text of the string *'Simple Sample Plugin Title'* to whatever you like. Upon reload this value will be reflected in the title area of the web app, and in the browser window title. + +### Panel Content + +The panel widget's content is also set in the *panel.js* file. In the sample the content is created using the *kb_common/html* module's *tag* function. The tag function allows for creation of html via a functional style. Towards the top of the module several tags are defined (div, p, etc.). When applied, these functions generate the corresponding html. + +In the sample, a "layout" function is used to separate the content building from the point at which it is inserted into the DOM. This is not necessary, but it is generally good practice to keep invoke a simple point of entry to construct the panels layout, outside of the *attach* or *start* lifecycle method in which the layout DOM is necessarily updated. + +Please feel free at this point to modify the content and reload the browser to see your changes. + +## Next Steps + +- A separate document describes the ongoing process of [developing a plugin](developing-a-plugin.md) in more detail. + + +--- + +[Index](../index.md) - [README](../README.md) - [Release Notes](../../release-notes/index.md) - [KBase](http://kbase.us) + +--- \ No newline at end of file diff --git a/docs/dev/developing-a-plugin.md b/docs/dev/developing-a-plugin.md new file mode 100644 index 000000000..d97335219 --- /dev/null +++ b/docs/dev/developing-a-plugin.md @@ -0,0 +1,259 @@ +# Developing kbase-ui Plugins + +This guide describes a workflow for working on kbase-ui plugins, and deploying plugin and kbase-ui updates. + +Because the creation of a plugin involves a set of one-time tasks, it is described in a separate document [Creating a New Plugin](developing-new-plugin.md). + +[TOC] + +## Prerequisites + +This guide does not cover setting up kbase-ui for development -- if you have not done that yet please see the following documents: + +1. [Prerequisites](prerequisites.md) +2. [Getting Started](getting-started.md) + +## Setting Up + +Good, now that you have set up a kbase-ui development environment, it is time to integrate a plugin to work on. + +### Fork the Plugin Repo + +If you haven't yet, now is the time to fork the plugin repo at github. + +All kbase plugins have an upstream repo in the kbase github account at ```https://github.com/kbase/kbase-ui-plugin-THEPLUGIN```. + +Where ```THEPLUGIN``` is the name of the plugin. + +> Well, sometimes during initial development, a plugin may reside at your personal repo. This saves the extra pull request step which can slow down iterative development, especially when a plugin is being born. This only works well when you are the sole developer + +Note that all plugin repos are prefixed with ```kbase-ui-plugin-```. This is not a hard requirement, in that kbase-ui can use plugins named otherwise. However kbase-ui build tools assume this naming convention, and it is good practice for a few reasons: + +- it creates a uniqe name in the *github* namespace +- it createsa a unique name in the *bower* namespace (more on that later) +- it makes it easier to manage plugins in your project (they appear together in directory listings) + +When we reference the *name* of a plugin, we always refer the the string occuring after the *kbase-ui-plugin-* prefix. + +So, your task is to fork the repo you are interested in. + +- create a github account if you do not already have one +- sign in to github if you are not already and ensure you are using the correct account for kbase work +- visit the github page for the plugin + - the root page for all plugins is https://github.com/kbase +- fork the repo into your github account using the fork button + + +### Clone the Plugin Repo + +We'll clone the plugin repo using the same method used for setting up kbase-ui for development. + +First, open a terminal in the top level ```project``` directory you set up (in [Getting Started](getting-started.md)) to contain the kbase-ui repo. + +Then we'll clone the canonical plugin repo in the kbase account. + +```bash +$ cd ~/work/project +$ git clone https://github.com/kbase/kbase-ui-plugin-PLUGINNAME +``` + +Then ensure that you can't accidentally merge changes back into the kbase repo: + +```bash +$ cd kbase-ui-plugin-PLUGINNAME +$ git remote set-url --push origin no-push +``` + +> You probably don't have push access to the repo, but this is a good practice + +Then add a remote which is your fork of the repo. + +```bash +$ git remote add NICKNAME https://github.com/YOURACCOUNT/kbase-ui-plugin-PLUGINNAME +``` + +This sets up a remote named *NICKNAME* which points to your fork using the https method. + +If you want to avoid the need to enter your password every time you push changes back up to your account, you may want to set up an ssh key: + +[https://help.github.com/articles/connecting-to-github-with-ssh](). + +If you have an ssh key for your current machine established for your github account, the clone command would look like + +``` +$ git remote add NICKNAME ssh://git@github.com/YOURACCOUNT/kbase-ui-plugin-PLUGINNAME +``` + + In either event, you have now established the plugin as a sister directory to kbase-ui. Your development directory should now look like: + + ``` + project + kbase-ui + kbase-ui-plugin-PLUGINNAME + ``` + +### Build and run the image with the plugin linked + +A tool is provided to both run the kbase-ui image (container) and at the same time link the local plugin directly into the image. Once you have done this, changes made to the local plugin will be relfected immediately in kbase-ui (after a browser reload.) + +This works by mounting the plugin source directory to the corresponding location inside of the kbase-ui container. The docker-mounted directory will temporarily replace the directory which was installed when kbase-ui was built. + +```bash +cd ~/work/project/kbase-ui +make build config=dev +make image build=build +make run-image env=dev plugins=PLUGINNAME +``` + +You should see a successful startup of the docker container, with several log lines printed into the console, and at the end + +```bash +:) +``` + +#### About linking plugins into the container + +These build steps above are are covered in [Getting Started](getting-started.md) and [About Building](about-building.md). + +What is new is the *plugins* option to *make run-image*. + +This option tells the run-image task to mount the plugin directory matching the plugin name into the kbase-ui image. + +Let me explain: + +Remember, if we have set up the project directory in the pattern described above, the plugin directory indicated by PLUGINNAME is located one directory above kbase-ui, and named "kbase-ui-plugin-PLUGINNAME". + +Inside the plugin the directory structure is uniform. The directory path `kbase-ui-plugin-PLUGINNAME/src/plugin` is the root of the plugin source code. When kbase-ui is built, this directory is copied direct to `kbase-ui/build/build/client/modules/plugins/PLUGINNAME`. When the kbase-ui image is built, the plugin code lives at `/kb/deployment/services/kbase-ui/modules/plugins/PLUGINNAME` + +> See [About Building](about-building.md) for an explanation of the build process and structure. + +The important point is that the *plugins* option will tell Docker to mount your plugin repo src/plugin directory into the kbase-ui image at the location described above. + +Once this mapping has been done, any changes you make to the plugin are "live" inside the kbase-ui docker container. A simple refresh of the browser will load any changes. + +Confirm that this works by pulling up your favorite browser to [https://ci.kbase.us](https://ci.kbase.us). + +> Assuming, that is, that your /etc/hosts still points to your local kbase-ui container! + +## Develop! + +You may now make changes to the plugin, and see those changes reflected immediately upon refreshing the browser. + +This is the essential plugin workflow + +## Next Topics + +The next view sections will cover the following topics: + +- Testing +- Releasing +- Merging into kbase-ui "for reals" +- Deploying into CI +- Additional Topics + - Debugging Tips + - supported kbase-ui dependencies + - What? You need a new dependency? + + +## Testing + +We don't currently have a formal test harness for automated testing of plugins, so all of your testing will be by hand. Nevertheless, you do have the ability to manually verify that your plugin works against all kbase environments. + +> Maybe we should rather call this "verification" + +There are three basic levels of testing, each more onerous than the previous. + +- Dev mode testing - use the kbase-ui dev build, with local plugin changes mounted into the image +- Dev build testing - use the kbase-ui dev build with local plugin changes checked in and released +- Prod build testing - use the kbase-ui prod build with local plugin changes released + +### Manual Testing Process + +For each environment (dev, ci, next, appdev, prod), do the following: + +- start the kbase-ui container for that environment + + - ```bash + make run-image env=ENV + ``` + + - where ENV is each of dev, ci, next, appdev, prod + +- start the proxier for the matching environment + + - ```bash + make run-proxier-image env=ENV + ``` + + - note that this ENV must match the first one + +- Perform your tests + +These steps are identical for each of the testing modes + +### Dev Mode Testing + +This is essentially an extension of the normal edit & reload process, with the twist that you should check all top features of the plugin for regression, and check against all deployment environments. + +You should perform this testing before releasing your plugin changes. + +### Dev Build Testing + +In this test mode, you have check in, merged, and released the plugin changes (because they passed the Dev Mode Testing above). You will have updated kbase-ui with the new plugin version, and rebuilt kbase-ui (as described in Preparing for Release.) + +- push your code up to your repo +- issue a PR for the plugin +- either merge the PR yourself, or request a review and merge +- create a semver tag for the kbase plugin repo +- update the kbase-ui build files with the new semver: + - config/app/dev/plugins.yml + - config/app/prod/plugins.yml + +You should perform this testing before a kbase-ui Pull Request which integrates your updated plugin. + +If you feel confident in your changes, or they are trivial, you may skip this step and continue to Prod BuildTesting. + +### Prod Build Testing + +Finally, before issuing a PR for integrating your updated plugin into kbase-ui, you need to perform testing using the production build. This should be done sparingly, and usually just as a pre-release task, because the prod build can take several minutes. Fortunately, once the build is finished the same as above. + +In addition to the tasks for Dev Build Testing: + +- make the prod build +- make the image using the prod build + +Then perform your tests against each environment as described above. + +### Future Testing Plans + +First, there is nascent support for selenium-based integration testing in kbase-ui. Thus you can perform ad-hoc automated integration testing if you set up the tests by hand. We do not have this testing integrated into Travis or Jenkins, nor a method for integration of plugin tests into the kbase-ui testing apparatus. + +See [Testing](testing.md) for more. + +## Deploying updated plugin + +When you have completed a round of changes to the plugin and it is ready for review on CI, you will need to update the plugin repo and issue the changes into the CI environment. Typically this will involve two sets of updates -- first a PR for your plugin changes, which results in a new version; secondly a PR for the new plugin version in kbase-ui, which will result in a new release in CI. + +- commit all changes +- push changes to your fork +- issue a PR for the kbase plugin +- after the PR is accepted, a new version will have been issued +- update the plugin version in your kbase-ui plugins.yml config for both dev and ci (and prod if this is ready for release) +- rebuild kbase-ui and verify that the build works, and that the changes are present +- commit and push your kbase-ui changes (which will just be configuration changes to bump up the version for your plugin) to your kbase-ui fork +- issue a PR for this change +- the PR will be accepted and kbase-ui will be redeployed into CI + +> If the PR is in the early stages, it may still be in your personal github account; if this is so clearly you can skip the PR process for the plugin, and issue the new release version yourself. Don't worry — when you transfer the repo to the kbase account, all commits and release tags are retained. + +## Next Steps + +- [Creating a New Plugin](creating-a-new-plugin.md) + +--- + +[Index](../index.md) - [README](../README.md) - [Release Notes](../../release-notes/index.md) - [KBase](http://kbase.us) + +--- + + diff --git a/docs/dev/developing-external-plugins-misc.md b/docs/dev/developing-external-plugins-misc.md index 491bdff55..4cfe9c0c4 100644 --- a/docs/dev/developing-external-plugins-misc.md +++ b/docs/dev/developing-external-plugins-misc.md @@ -40,8 +40,9 @@ make init make build config=ci ``` + --- -[Index](index.md) - [README](../README.md) - [Release Notes](../release-notes/index.md) - [KBase](http://kbase.us) +[Index](../index.md) - [README](../README.md) - [Release Notes](../../release-notes/index.md) - [KBase](http://kbase.us) --- \ No newline at end of file diff --git a/docs/dev/developing-external-plugins.md b/docs/dev/developing-external-plugins.md deleted file mode 100644 index 73042284f..000000000 --- a/docs/dev/developing-external-plugins.md +++ /dev/null @@ -1,190 +0,0 @@ -# Developing kbase-ui Plugins - -This guide describes a workflow for developing kbase-ui plugins, and deploying plugin and kbase-ui updates. - -> Note this is for working on an *existing* plugin -- it does not cover creation of a plugin from scratch. See [new external plugin](developing-new-external-plugin.md) if you need to create a new plugin. - -## Contents -- Prerequisites -- Setting Up -- Linking into kbase-ui -- Deploying updated plugin -- Integrating into kbase-ui -- Deploying updated kbase-ui - -## Prerequisites - -This guide does not cover setting up kbase-ui for development -- if you have not done that yet please see the following documents: - -1. [Prerequisites](prerequisites.md) -2. [Getting Started](getting-started.md) - -## Setting Up - -Good, now that you have set up a kbase-ui development environment, it is time to integrate a plugin to work on. - -### Fork the Plugin Repo - -If you haven't yet, now is the time to fork the plugin repo at github. - -All kbase plugins have an upstream repo in the kbase github account at ```https://github.com/kbase/kbase-ui-plugin-THEPLUGIN```. - -Where ```THEPLUGIN``` is the name of the plugin. - -> Well,sometimes during initial development, a plugin may reside at your personal repo. This saves the extra pull request step which can slow down iterative development, especially when a plugin is being born. This only works well when you are the sole developer - -Note that all plugin repos are prefixed with ```kbase-ui-plugin-```. This is not a hard requirement, in that kbase-ui can use plugins named otherwise. However some kbase-ui build tools assume this naming convention, and it is good practice for a few reasons: - -- it creates a uniqe name in the *github* namespace -- it createsa a unique name in the *bower* namespace (more on that later) -- it makes it easier to manage plugins in your project (they appear together in directory listings) - -When we reference the *name* of a plugin, we always refer the the string occuring after the *kbase-ui-plugin-* prefix. - -So, your task is to fork the repo you are interested in. - -- create a github account if you do not already have one -- sign in to github if you are not already and ensure you are using the correct account for kbase work -- visit the github page for the plugin - - the root page for all plugins is https://github.com/kbase -- fork the repo into your github account using the fork button - - -### Clone the Plugin Repo - -We'll clone the plugin repo using the same method used for setting up kbase-ui for development. - -First, open a terminal in the top level ```dev``` directory you set up to contain the kbase-ui repo. - -Then we'll clone the canonical plugin repo in the kbase account. - -```bash -$ cd ~/work/dev -$ git clone https://github.com/kbase/kbase-ui-plugin-PLUGINNAME -``` - -Then add a remote which is your fork of the repo. - -```bash -$ cd kbase-ui-plugin-PLUGINNAME -$ git remote add NICKNAME https://github.com/YOURACCOUNT/kbase-ui-plugin-PLUGINNAME -``` - -This sets up a remote named *NICKNAME* which points to your fork using the https method. - -If you want to avoid the need to enter your password every time you push changes back up to your account, you may want to set up an ssh key: - -[https://help.github.com/articles/connecting-to-github-with-ssh/](). - -If you have an ssh key for your current machine established for your github account, the clone command would look like - -``` -$ git remote add NICKNAME ssh://git@github.com/YOURACCOUNT/kbase-ui-plugin-PLUGINNAME -``` - - In either event, you have now established the plugin as a sister directory to kbase-ui. Your development directory should now look like: - - ``` - dev - kbase-ui - kbase-ui-plugin-PLUGINNAME - ``` - -### Build and run the image with the plugin linked - -A tool is provided to both run the kbase-ui image (container) and at the same time link the local plugin directly into the image. Once you have done this, changes made to the local plugin will be relfected immediately in kbase-ui (after a browser reload.) - -This works by mounting the plugin source directory to the corresponding location inside of the kbase-ui container. The docker-mounted directory will temporarily replace the directory which was installed when kbase-ui was built. - -```bash -cd ~/work/dev/kbase-ui -make build -make dev-image -bash deployment/dev/tools/run-image.sh dev -p MYPLUGIN -``` - -The tool we are running is ```run-image.sh``. - -Here is the usage output: - -```bash -Usage: run-image.sh env [-p external-plugin] [-i internal-plugin] [-l lib-module-dir:lib-name:source-path]' -``` - -You should see a successful startup of the docker container, with log lines printed into the console. - -#### env argument - -The first argument to run-image is the ```env``. This value corresponds to the corresponding deployment config file located in deployment/conf. Since we are supplying "dev", we will be using the deploy file "deployment/conf/dev.ini". - -#### -p argument - -We are using the -p argument to specify an external plugin to link into the kbase-ui image. Note that we use the plugin name, not the repo name. The tool knows how to find the plugin directory based on the directory structure (it should be a sister directory to kbase-ui) and uniform naming structure for repos (kbase-ui-plugin-PLUGINNAME). - -### Example - -```bash -bash /Users/erikpearson/work/kbase/sprints/2018Q1S1/kbase-ui/deployment/dev/tools/run-image.sh dev -CONFIG MOUNT: /Users/erikpearson/work/kbase/sprints/2018Q1S1/kbase-ui/deployment/conf -ENVIRONMENT : dev -READING OPTIONS -MOUNTS: -BusyBox v1.26.2 (2017-11-23 08:40:54 GMT) multi-call binary. - -Usage: dirname FILENAME - -Strip non-directory suffix from FILENAME -NGINX CONFIG TEMPLATE /kb/deployment/bin/../conf/deployment_templates/nginx.conf.j2 -DATA SRC /conf/dev.ini -CONFIG TEMPLATE /kb/deployment/bin/../conf/deployment_templates/config.json.j2 -2018/01/03 23:45:01 [notice] 13#13: using the "epoll" event method -2018/01/03 23:45:01 [notice] 13#13: nginx/1.12.2 -2018/01/03 23:45:01 [notice] 13#13: OS: Linux 4.9.60-linuxkit-aufs -2018/01/03 23:45:01 [notice] 13#13: getrlimit(RLIMIT_NOFILE): 1048576:1048576 -2018/01/03 23:45:01 [notice] 13#13: start worker processes -2018/01/03 23:45:01 [notice] 13#13: start worker process 14 -2018/01/03 23:45:01 [notice] 13#13: start worker process 15 -2018/01/03 23:45:01 [notice] 13#13: start worker process 16 -2018/01/03 23:45:01 [notice] 13#13: start worker process 17 -``` - -> TODO: provide a make task for this - -Confirm that this works by pulling up your favorite browser to https://ci.kbase.us. - -## Develop! - -You may now make changes to the plugin, and see those changes reflected immediately upon refreshing the browser. - - -## More sections to be written for external plugin development - -- testing changes for all environments -- pr, merge, tagging plugin -- local testing again with new version -- pr, merge ui changes (just config) -- redeploy on CI - -## Deploying updated plugin - -When you have completed a round of changes to the plugin and it is ready for review on CI, you will need to update the plugin repo and issue the changes into the CI environment. Typically this will involve two sets of updates -- first a PR for your plugin changes, which results in a new version; secondly a PR for the new plugin version in kbase-ui, which will result in a new release in CI. - -- commit all changes -- push changes to your fork -- issue a PR for the kbase plugin -- after the PR is accepted, a new version will have been issued -- update the plugin version in your kbase-ui plugins.yml config for both dev and ci (and prod if this is ready for release) -- rebuild kbase-ui and verify that the build works, and that the changes are present -- commit and push your kbase-ui changes (which will just be configuration changes to bump up the version for your plugin) to your kbase-ui fork -- issue a PR for this change -- the PR will be accepted and kbase-ui will be redeployed into CI - -> If the PR is in the early stages, it may still be in your personal github account; if this is so clearly you can skip the PR process for the plugin, and issue the new release version yourself. - ---- - -[Index](index.md) - [README](../README.md) - [Release Notes](../release-notes/index.md) - [KBase](http://kbase.us) - ---- - - diff --git a/docs/dev/developing-internal-plugins.md b/docs/dev/developing-internal-plugins.md index a5797a04a..9394c1ebb 100644 --- a/docs/dev/developing-internal-plugins.md +++ b/docs/dev/developing-internal-plugins.md @@ -344,8 +344,9 @@ Working with the plugin is similar, except your branch starts at the tip of the Of course, your workflow may be completely different, this is just an example. + --- -[Index](index.md) - [README](../README.md) - [Release Notes](../release-notes/index.md) - [KBase](http://kbase.us) +[Index](../index.md) - [README](../README.md) - [Release Notes](../../release-notes/index.md) - [KBase](http://kbase.us) --- \ No newline at end of file diff --git a/docs/dev/developing-kbase-ui.md b/docs/dev/developing-kbase-ui.md index e391f012b..1091f118b 100644 --- a/docs/dev/developing-kbase-ui.md +++ b/docs/dev/developing-kbase-ui.md @@ -61,8 +61,9 @@ which will stop all running containers. - testing changes for all environments - pull request for changes + --- -[Index](index.md) - [README](../README.md) - [Release Notes](../release-notes/index.md) - [KBase](http://kbase.us) +[Index](../index.md) - [README](../README.md) - [Release Notes](../../release-notes/index.md) - [KBase](http://kbase.us) --- \ No newline at end of file diff --git a/docs/dev/developing-new-external-plugin.md b/docs/dev/developing-new-external-plugin.md deleted file mode 100644 index 5305e227e..000000000 --- a/docs/dev/developing-new-external-plugin.md +++ /dev/null @@ -1,322 +0,0 @@ -# Creating a New External Plugin - -## Contents -- set up your dev env - - [Prerequisites](prerequisites.md) - - [Getting Started](getting-started.md) -- Clone the sample plugin repo with new name - - Change the name of the repo in various places -- Test the new plugin -- Initialize repo -- Create gitub repo -- Push local repo to github -- Make initial release -- Register in bower -- add plugin to kbase-ui config - - plugins - - integrate into menu if necessary -- build and confirm -- enter external development workflow -- later... - - transfer repo to kbase - - unregister in bower - - register kbase repo in bower -- alternate workflows - - use directory rather than bower - -## Setup - -After setting up a new project directory as described in the [Getting Started](getting-started.md) guide, you should find yourself in a terminal opened inside the project directory. We'll refer to this as *~/work/project*. - -## Clone Sample Plugin Repo - -First we need to clone the sample plugin repo as a copy. - -```bash -cd ~/work/project -git clone --depth=1 --branch=master https://github.com/eapearson/kbase-ui-plugin-simple-sample kbase-ui-plugin-MYPLUGIN -``` - -This will clone the sample plugin into a directory named after the new plugin name MYPLUGIN. This will not be functional git repo, since it was cloned with a depth of 1. - -### Change the name of the plugin - -The sample plugin name is *simple-sample*. This name is used in many places to namespace this plugin. In your IDE or tool of choice you can simply perform a global search-and-replace from "simple-sample" to "MYPLUGIN" (where MYPLUGIN) is the name of your new plugin.) - -Your plugin name should be short, but fully descriptive of the purpose of the plugin. In this document when we use *MYPLUGIN* we mean the name you have chosen for your plugin. - -At this time, the global search-and-replace will make the following changes: - -- bower.json - - change the name property from "kbase-ui-plugin-simple-sample" to "kbase-ui-plugin-MYPLUGIN" - change the property repository.url to point to the github repo - -- src/plugin/config.yml - - change the package.name property from "simple-sample" to "MYPLUGIN" - - also change the author and description if appropriate - - change the install.widgets[0].id from simple-sample_panel to MYPLUGIN_panel - - change the intall.routes[0].path from [simple-sample] to [MYPLUGIN] - - change the install.routes[0].widget from simple-sample_panel to MYPLUGIN_panel - - change the install.menu.name property form simple-sample to MYPLUGIN - - change the install.menu.definition.path from [simple-sample] to [MYPLUGIN] - -- src/plugin/resources/css - - change the class name plugin_simple-sample_panel to plugin_MYPLUGIN_panel - -- src/plugin/modules - - change the class name list 'plugin_simple-sample_panel container-fluid' to 'plugin_MYPLUGIN_panel container-fluid' - -> NOTE: You will discover that there are a few odds and ends that need to be changed as well, such as the menu label, the page title, and of course the sample content. We'll cover that later. - -## Add plugin to kbase-ui config - -In *kbase-ui*, each deployment has a plugin configuration file to control the features included in the kbase-ui build. Thus there is one for local development, ci and production. These files are located in *config/app/ci*, *config/app/dev*, and *config/app/prod*. Each deployment has three config files: *menus.yml*, *plugins.yml*, *services.yml*. - -- *plugins.yml* - one entry per internal and external plugin to be included in the build -- *menus.yml* - hamburger menu entries -- *services.yml* - extra configuration for the internal ui services - -During development you will typically need to modify both the CI and Dev configurations for plugins.yml, and sometimes menus.yml. - -### plugins.yml - -In plugins.yml you will need to add a new entry to the bottom of the plugins section. - -Edit the file *~/work/project/kbase-ui/config/app/dev/plugins.yml*. - -At the bottom of the file add this (replacing MYPLUGIN with the plugin name you have chosen) - -```yaml - - - name: MYPLUGIN - globalName: kbase-ui-plugin-MYPLUGIN - version: 0.1.0 - cwd: src/plugin - source: - directory: {} -``` - -This entry uses the "directory" source. With this setting (you will notice that all others in the config file use "bower"), the kbase-ui build tool will fetch the plugin code from the local plugin directory. With the "bower" setting, which we will set later, the plugin code would be fetched from github. - -### menus.yml - -We'll add a menu item to the ui as well. - -Edit the file *~/work/project/kbase-ui/config/app/dev/menus.yml* - -Within the authenticated section, add the menu item MYPLUGIN at the beginning of the developer menu list. - -E.g. - -```yaml -menus: - authenticated: - main: [narrative, bulk-ui] - developer: [MYPLUGIN, staging-browser, example-gopherjs, reske-admin, reske-object-search,jgi-search, tester, test_dynamic_table, paveldemo, sdkclientstest, databrowser, typebrowser, jobbrowser, shockbrowser] - help: [about, about-services, contact-kbase, help] - unauthenticated: - main: [] - developer: [] - help: [about, contact-kbase, help] -``` - -The menu items in menus.yml are actually menu ids, even though they are often, as in this case, the same as the plugin name. The menu ids are set in *src/plugin/config.yml* in the menu section. - -## Test the new plugin - -We'll take a short detour here to ensure that the plugin works, before completing the new plugin setup. - -In the kbase-ui directory, build and run the image with the new configuration. - -```bash -cd ~/work/project/kbase-ui -make build -make dev-image -make run-dev-image -``` - -Now point your favorite web browser to [https://ci.kbase.us](https://ci.kbase.us). - -You should see the new menu item in the hamburger menu. Selecting the new menu item should show the default sample plugin content. - -Although it is tempting to start working on the plugin, let's first finish the plugin configuration. - -## Create Github Repo - -You'll start by creating a github repo under your account. After the plugin is established, or at any time you wish, this repo should be transferred to the *kbase* github account. - -- log into github.com -- select "New repository" from the "+" menu -- use the name "kbase-ui-plugin-MYPLUGIN" for the repo (where, as usual MYPLUGIN is the name you have chosen) - -## Initialize and push the plugin - -Back in your plugin's folder, convert it into a git repo and push it up to the new github repo you just created. - -```bash -cd ~/work/project/kbase-ui-plugin-MYPLUGIN -git init -git remote add origin https://github.com/YOURACCOUNT/kbase-ui-plugin-MYPLUGIN -git add . -git commit -m "Initial commit for new plugin MYPLUGIN" -git push -u origin master -``` - -## Make the initial release - -All external plugins are pulled into a kbase build by their version. The version is requested via bower, which in turn recognizes versions set up with the correct semver format as git tags in github. - -Goto your github page for the plugin: - -- Take your browser to https://github.com/YOURACCOUNT/kbase-ui-plugin-MYPLUGIN -- Select the "release" tab -- Click the "Draft a new release" button -- For "Tag version" enter "v0.0.1" -- For "Release title" enter "0.0.1" -- For "Describe this release" you may describe the initial state of the plugin, or simply leave it blank. -- For "This is a pre-relase", check the box - -> Note that all releases before 1.0.0 are assumed to be in development, so I'm not sure that the pre-release status is necessary; it only appears in the github web ui, afaik. - -## Register in Bower - -In order for the kbase-ui build tool to be able to use bower to fetch the plugin, you need to first register it in the bower registry. - -Back in your terminal: - -```bash -bower register kbase-ui-plugin-MYPLUGIN https://github.com/YOURACCOUNT/kbase-ui-plugin-MYPLUGIN -``` - -> Caveats: Bower is deprecated - we simply haven't had time yet to move to another workflow. - -> At a later time, after the repo is transfered to the kbase account, the plugin will need to be un-registered from your account and re-registered under kbase. - - -## Change the plugin from "directory" to "bower" - -As mentioned above, we initially set the plugin configuration to be installed from a directory. We will now switch that to bower. Make the plugin entry we added bofore look like the one below (we are just changing ```source.directory: {}``` to ```source.bower: {}```) - -```yaml - - - name: MYPLUGIN - globalName: kbase-ui-plugin-MYPLUGIN - version: 0.0.1 - cwd: src/plugin - source: - bower: {} -``` - -## Build and Confirm - -Now you are ready to build kbase-ui again and confirm that the plugin has been integrated. - -### Stop the container - -First, you will need to stop the currently running container that you started earlier. - -You can do this from the command line or using your favorite Docker management tool. - -From the command line, you may issue - -```bash -docker stop $(docker ps -q) -``` - -this will stop all running containers. If you are currently using docker only for this project, this is a safe thing to do. This command first generates a list of running docker containers with *docker ps -q*, then it passes this list to the *docker stop* command to stop them. - -On the Mac, I use *Kitematic*, a gui Docker management tool. I don't think it is included with Docker for Mac, but is available from the Docker for Mac menu. If you have multiple containers running, it is handy to pick the right one to stop; it is also very useful for exploring or configuring containers. - -### Build and run again - -Now lets build the ui and run it again to confirm that the changes worked. - -```bash -cd ~/work/project/kbase-ui -make build -make dev-image -make run-dev-image -``` - -Now pull up your favorite browser to [https://ci.kbase.us](https://ci.kbase.us) to confirm that the plugin loaded correctly. - -If you added a new menu item, confirm that it is in the menu, and select it. - -If not, simply type in the path to invoke the top level panel. E.g. https://ci.kbase.us#myplugin/mypath. - -If the plugin is not correctly registered in bower, or misconfigured in plugins.yml, or the menus.yml is incorrect, either the build or the initial load of the kbase-ui web app will fail. - -## Make a few changes - -As you will have noticed, this new plugin still looks like the sample plugin, because we haven't changed any of the sample content yet. Let's do that now. - -### Developer Workflow - -We have succeeded in having the ui built from the plugin by pulling a specific version down from it's github repo. This is critical for reproducible builds. However, during development time it we want to have our local changes reflected in the ui. - -To accomplish this we just need to run the image with options to map the plugin folder into the container (this is described in more detail in the [external development workflow](developing-external-plugins.md) document.) - -To accomplish this: - -- stop the container, using the command line or a docker management tool -- start the container with the following commands: - -```bash -cd ~/work/project/kbase-ui -bash deployment/dev/tools/run-image.sh dev -p MYPLUGIN -``` - -This will run the kbase-ui image while mounting the plugin directory inside, overriding the plugin directory that was included in the build. - -> Yes, I know, this should be a make task, like make build, make dev-image, etc. - -If you refresh browser, your plugin should still display. - -### Menu Item - -First lets take control of the menu item's label. - -Edit the file *~work/project/kbase-ui-plugin-MYPLUGIN/src/plugin/config.yml*. - -At the bottom of the file you'll see the *menu:* section. The *label:* property controls the display label for the menu item. Change it to whatever you like. - -Now reload the browser, then click on the hamburger menu. You should see the change you just made. - -Feel free to change the icon as well. The icon value is the suffix part of a [Font Awesome](http://fontawesome.io/icons/) icon class. The sample icon is a bicycle, so Font Awesome icon class is fa-bicycle. - -### Page Title - -The page title is set via a message sent to the ui. The page title is typically set in the widget associated with the current path. These widgets are referred to as "panel widgets" since they typically control the entire content panel of the ui. - -You can find it in *~work/project/kbase-ui-plugin-MYPLUGIN/src/plugin/modules/panel.js*. - -The line looks like: - -```javascript -runtime.send('ui', 'setTitle', 'Simple Sample Plugin Title'); -``` - -Simply change the text of the string *'Simple Sample Plugin Title'* to whatever you like. Upon reload this value will be reflected in the title area of the web app, and in the browser window title. - -### Panel Content - -The panel widget's content is also set in the *panel.js* file. In the sample the content is created using the *kb_common/html* module's *tag* function. The tag function allows for creation of html via a functional style. Towards the top of the module several tags are defined (div, p, etc.). When applied, these functions generate the corresponding html. - -In the sample, a "layout" function is used to separate the content building from the point at which it is inserted into the DOM. This is a bit of a misnomer, since this example code was take from a more complex sample plugin which used the panel to simply compose subwidgets. - -Please feel free at this point to modify the content and reload the browser to see your changes. - -## Next Steps - -- A separate document describes [external development workflow](developing-external-plugins.md) in more detail. -- Further examples of: - - subwidgets - - using jquery and kbwidget - - using knockout components - - ---- - -[Index](index.md) - [README](../README.md) - [Release Notes](../release-notes/index.md) - [KBase](http://kbase.us) - ---- \ No newline at end of file diff --git a/docs/dev/docker-tips.md b/docs/dev/docker-tips.md index 0a3ca8a12..d3a0e1273 100644 --- a/docs/dev/docker-tips.md +++ b/docs/dev/docker-tips.md @@ -1,5 +1,9 @@ # Docker Tips +### Use Kitematic + +On macOS, Docker includes a menu item for starting Kitematic. Kitematic is a gui for managing images and containers. It will simplify your life. It is especially useful for running a shell in an running container, accessing the configuration for a container, and finding images to install. + ### Stop running containers ```bash @@ -47,6 +51,6 @@ docker run -it --entrypoint /bin/bash kbase/kbase-ui:dev --- -[Index](index.md) - [README](../README.md) - [Release Notes](../release-notes/index.md) - [KBase](http://kbase.us) +[Index](../index.md) - [README](../README.md) - [Release Notes](../../release-notes/index.md) - [KBase](http://kbase.us) --- \ No newline at end of file diff --git a/docs/dev/getting-started.md b/docs/dev/getting-started.md index 6d24071ff..53261ee65 100644 --- a/docs/dev/getting-started.md +++ b/docs/dev/getting-started.md @@ -2,26 +2,30 @@ This doc describes how to get set up for kbase-ui development. -## Basic usage +[TOC] + +## Prerequisites + +- [Prerequisites](prerequisites.md) -This section describes steps to get the ui running locally, but without any special developer hooks. This is good to test that everything is in place. +## Basic usage -(1) Ensure you have prerequisites (esp. nodejs, git) installed, see [Prerequisites](prerequisites.md). +The first step for any kbase-ui effort is to set up a project directory, prepare a copy of kbase-ui, and ensure that it works. -(2) Create a working directory for your project +### Create a working directory for your project In this and all developer documentation, it is assumed that all repos are installed under a single root working directory. Developer tools support this simple directory layout. -We'll just call this directory *~/work/project*. +We'll just call this directory *~/work/project*, but you can of course place it wherever you like and name it whatever pleases you. ```bash -mkdir ~/work/project +mkdir -p ~/work/project cd ~/work/project ``` -(3) Install a local copy of kbase-ui +### Install a local copy of kbase-ui -All development workflows for kbase-ui involve pull requests to the master repository located in the kbase github account. You will therefore be pushing changes to your fork of kbase/kbase-ui. +All development workflows for kbase-ui involve starting from the tip of the develop branch of the kbase-ui repo in kbase github account. You will therefore be pushing changes to your fork of kbase/kbase-ui. If you have your own preferred method of setting up a repo for this type of workflow, please use it. Here is one setup that I use: @@ -29,55 +33,100 @@ If you have your own preferred method of setting up a repo for this type of work ```bash git clone -b develop https://github.com/kbase/kbase-ui cd kbase-ui -git remote set-url --push origin no-pushing +git remote set-url --push origin no-push git remote add NICKNAME ssh://git@github.com/YOURACCOUNT/kbase-ui git checkout -b BRANCHNAME git push NICKNAME BRANCHNAME ``` -With this flow, you are operating in the branch *BRANCHNAME*, which started at the top if the *develop* branch in the main *kbase/kbase-ui* repo. You will push it to your fork located at the github account *YOURACCOUNT*, which has been set up at *NICKNAME* in the local git configuration. +With this flow, you are operating in the branch *BRANCHNAME*, which started at the tip of the *develop* branch in the main *kbase/kbase-ui* repo. You will push it to your fork located at the github account *YOURACCOUNT*, which has been set up at remote *NICKNAME* in the local git configuration. + +> Note: this setup requires that you have generated an ssh key on your machine, and installed it in your giithub account. + +#### Install custom Docker network -(4) Install custom Docker network +In order for the proxier to access the kbase-ui container, or for more advanced workflows involving locally hosted services or service mocks, we need to establish a local custom network for Docker. It can be named anything you like, as long as it is consistent across images. We choose the name ```kbase-dev```. ```bash docker network create kbase-dev ``` -(5) Build and run it +#### Build and run kbase-ui This will fetch, prepare, build, create the image, and run a container from that image. ```bash make init -make build -make dev-image -make run-dev-image env=dev +make build config=dev +make image build=build +make run-image env=dev ``` -(6) Prepare and run the Proxier +Where: -[ TO BE DONE ] +- ```make init``` installs all of the build and testing tools and libraries not already covered by the prerequisites +- ```make build config=dev``` runs the developer build of kbase-ui (defaults to prod build) +- ```make image build=build``` builds a Docker image containing the dev build of the ui (defaults to the prod "dist" build) +- `make run-image env=dev` runs the Docker image with the dev environment (defaults to the "prod" environment) + +> Tip: +> +> On macOS and Linux/Unix you may want to use the ```aliaas``` shell command to create your own shortcuts. E.g. +> +>```bash +>alias buildui="make build config=dev;make image build=build" +>``` +> +> will make the shell command ```buildui``` available in your shell. See tools/devtools.sh + +#### Prepare and run the Proxier + +The proxier is a small Docker image which runs nginx to proxy requests for a given KBase deployment hostname (e.g. ci.kbase.us) to a local kbase-ui Docker container, with other requests routed to the canonical locations within KBase. ```bash make proxier-image make run-proxier-image env=dev ``` -(7) Bring up your browser to [https://ci.kbase.us](https://ci.kbase.us) +Where -You'll receive a security warning due to the usage of a self-signed cert inside the container. Just go through the hoops to accept it. Each browser is different; some browsers require a restart if you rebuild the image (which may create a new self-signed cert.) +- `make proxier-image` builds the proxier Docker image +- `make run-proxier-image env=dev` will start a container running this image, using the `dev` environment. The `dev` env is the default, so you may omit this. -(8) Stop it +> Note: A shortcut for `make run-proxies-image env=dev`, runui, is provided in tools/devtools.sh -When you are finished you will want to stop the container. An handy way to do this is to open a new terminal window and enter: +The *dev* env responses to ci.kbase.us. -```bash -docker stop $(docker ps -q) +#### Map ci.kbase.us to your local host + +In order for your browser to use the proxier for requests to ci.kbase.us, you need to add a line to your `/etc/hosts` (or equivalent, if on Windows). + +```hosts +127.0.0.1 ci.kbase.us +``` + +In fact, you may find it handy to add this entire section to your /etc/hosts, and comment in the environment you are currently working on: + +``` +127.0.0.1 ci.kbase.us +#127.0.0.1 appdev.kbase.us +#127.0.0.1 next.kbase.us +#127.0.0.1 kbase.us narrative.kbase.us ``` -which will stop all running containers. +#### Bring up the local kbase-ui + +Bring up your browser to [https://ci.kbase.us](https://ci.kbase.us). -You may of course use your favorite docker management tool, such as Kitematic. +You'll receive a security warning due to the usage of a self-signed ssl certificate inside the container. Just go through the hoops to accept it. Each browser is different. + +> Tip: Browsers can be VERY finicky with self signed certs, especially if you have change the self signed cert, which may happen when you rebuild the proxier image. + +#### Stop it + +When you are finished you will want to stop the container, simpley press `Ctrl`+ `C`. The container should immediately stop. + +You may also use your favorite docker management tool, such as Kitematic on macOS. ## More @@ -85,13 +134,12 @@ You may of course use your favorite docker management tool, such as Kitematic. ## Next Steps -- [Creating a new External Plugin](developing-new-external-plugin.md) -- [Developing External Plugins](developing-external-plugins.md) - +- [Developing a Plugin](developing-a-plugin.md) +- [Creating a External Plugin](creating-a-new-plugin.md) --- -[Index](index.md) - [README](../README.md) - [Release Notes](../release-notes/index.md) - [KBase](http://kbase.us) +[Index](../index.md) - [README](../README.md) - [Release Notes](../../release-notes/index.md) - [KBase](http://kbase.us) --- \ No newline at end of file diff --git a/docs/dev/prerequisites.md b/docs/dev/prerequisites.md index 5cde87270..7bff8d309 100644 --- a/docs/dev/prerequisites.md +++ b/docs/dev/prerequisites.md @@ -1,12 +1,18 @@ # Prerequisites -The developer setup provides a workflow and small set of tools to help develop kbase-ui, plugins, and associated libraries. These tools provide for javascript development on your host (main desktop) environment, and a docker image for running kbase-ui behind a local proxy. +The developer setup provides a workflow and set of tools to help develop kbase-ui, plugins, and associated libraries. The basic workflow consists of javascript development on your host (main desktop) environment, a Docker image for running kbase-ui behind nginx, and a separate Docker image for running a proxy for the kbase-ui container as well as KBase services. -The requirements are: +[TOC] + +## Basic Development Requirements + +| app | version | notes | +|-----|---------|------ | +| nodejs | 8 (LTS) | The V8 javascript system, required for building kbase-ui and running tests; we are currently on version 8. | +| git | ?? | the source revision management tool with integration into github | +| docker | ?? | the linux container manager you will use to run kbase-ui | +| make | ?? | all build tasks go through make | -- nodejs - The V8 javascript system, required for building kbase-ui and running tests -- git - the source revision management tool with integration into github -- docker - the linux container manager you will use to run kbase-ui ## Basic Development Requirements @@ -16,9 +22,16 @@ The KBase UI should build and run on any modern system: Mac OS X, Linux, Windows Procecedures for installation of system level packages depends on ... the system you use! Even within a platform there may be multiple ways to install a given package. In this document we provide instructions for installation on platforms in use at KBase using methods that we have employed and work. -# Installation +| app | version | notes | +| ------ | ------- | ------------------------------------------------------------ | +| nodejs | 8 (LTS) | The V8 javascript system, required for building kbase-ui and running tests; we are currently on version 8. | +| git | ?? | the source revision management tool with integration into github | +| docker | ?? | the linux container manager you will use to run kbase-ui | +| make | ?? | all build tasks go through make | -## All environments +## Installation + +### All environments The following tools are available on all supported platforms (Mac, Windows, Linux). Please consult the installation instructions at the respective web sites: @@ -28,8 +41,9 @@ That leaves us with nodejs and git to install through other means. ### Macintosh -There are three main ways to install these tools natively on a Mac: +There are three common sources for these tools natively on a Mac: +- Apple xCode - Native Mac installation packages - Macports - Homebrew @@ -38,6 +52,14 @@ There are three main ways to install these tools natively on a Mac: The requisite development tools are available as regualar Mac packages. This is may be the easiest way to get started. However, if you are going to be installing other Unixy tools, or have one of the following package managers installed, either Macports or Homebrew may be preferable, and are not much more difficult. Package managers also make updating your tools easy, whereas the downloadable packages will need to be periodically updated manually. +#### Apple xCode + +xCode may be installed from the Apple App Store for free, and is highly recommeded for any developer workstation. Not only does it provide some of the required tools, but some macOS developer tools require xCode be installed. + +xCode includes both git and make, which are required for building kbase-ui, as well as a compilers which may be required to install other developer tools via installers from macports, npm, and the like. + +Although xCode includes git, make and other developer tools, you may also opt to install these or similar (e.g. gmake) tools separately. + #### Native Packages Installation of native Mac packages should put you in good stead. Just follow the links and instructions therein. @@ -58,31 +80,27 @@ nodejs may be installed from the canonical home: [https://nodejs.org](https://nodejs.org) -As of this time, please use the version 6 (e.g. 6.11.2) branch of NodeJS. +As of this time, please use version 8 of NodeJS. We try to stay on the most recent LTS release, but the most important constraint is that we are using the same major version across all environments - local dev, travis, CI and next/appdev/prod. ##### 3) npm global packages -you may optionall install the global version of npm-based development tools. +It is no longer recommended to install the global version of npm-based development tools. Rather, they run out of the local kbase-ui repo. The local repo always installs these tools, since they are used by the build and testing tools. -After nodejs is installed, from the Mac Terminal enter these commands: +For convenience, you may run -``` -sudo npm install -g bower -sudo npm install -g grunt-cli -sudo npm install -g karma-cli +```bash +. tools/devtools.sh ``` -The globally installed tools are not used by the build process. The build process installs tools locally in the kbase-ui repo directory during the build process. In addition to making deployment simpler, the internally installed tools are pinned to a specific version, making builds more deterministic. +from a terminal window located at the kbase-ui repo directory to put the node executable directory into the current path. -However, having these tools available globally on your host can ease certain development tasks, such as running grunt tasks, karma tests, or bower things like registration of a plugin. +> Note: yarn? Yes, we may be switching to yarn in the near future. #### Macports ##### 1) Install Apple xCode -xCode may be installed from the Apple App Store for free. - -Let's face it, you should really have xCode installed, even if you use a Mac package manager. +As mentioned above, you should have already installed xCode — xCode is required by macports. ##### 2) Install macports @@ -95,25 +113,16 @@ Download and follow the instructions at [https://www.macports.org/install.php](h open Terminal and issue the following commands: ``` -sudo port install nodejs git +sudo port install nodejs6 npm5 git ``` -> Although xCode installs git, the one available through macports is probably more up-to-date. - - -##### optional +Note that the version of nodejs is important. We try very hard to use the same version of nodejs in local development as KBase uses in build/deployment environments and as the Travis configuration uses. There are major changes between nodejs releases. Although a newer nodejs will be probably be able to run code written for an older version, the converse is not necessarily true, and it is certainly easy to start using features enabled by a newer node version if it is available. There may also be subtle differences in the building of dependncies through npm. -You may optionally install the following tools globally. +In general we try to stay on the most recent Long Term Support (LTS) of any dependency, but sometimes it takes us a while to have time to coordinate the concurrent update of all of the places they are used. -``` -sudo npm install -g bower -sudo npm install -g grunt-cli -sudo npm install -g karma-cli -``` +> Note: Although xCode installs git, the one available through macports is probably more up-to-date. - These tools are installed globally on your host machine, in a the /opt/local/ filesystem. - > The build process does not use these tools, but they can come in handy for manually performing tasks. #### Homebrew @@ -121,15 +130,9 @@ sudo npm install -g karma-cli ### Windows -> We have not worked out a set of best-practice windows setup instructions yet, below is just an anecdote from one successful session. - -We have performed an install, from scratch (starting with no dev tools whatsoever) on Windows 10, in about 15 minutes, which includes looking for and finding the appropriate windows installers. - -You need to install the Windows packages for Git and Nodejs, and use the Git bash shell for command line stuff. Also, phantomjs is distributed as a simple binary, though we elected to install it via npm (```npm install -g phantomjs```). Experience shows that sometimes phantomjs does not play well when installed via npm, but I may be mistaken because it works fine on Windows -- although it is a little behind the official latest version (2.0.0), which actually may be a good thing since issues have been reported. On the other hand, there are js compatability issues reported for < 2.0.0 which will not be fixed.) - -Occasionally you may be prompted for an admin account authorization if you are using a standard account. Other than that, the process was surprisingly smooth. +> to be done -## Linux +### Linux #### Ubuntu @@ -137,6 +140,6 @@ Occasionally you may be prompted for an admin account authorization if you are u --- -[Index](index.md) - [README](../README.md) - [Release Notes](../release-notes/index.md) - [KBase](http://kbase.us) +[Index](../index.md) - [README](../README.md) - [Release Notes](../../release-notes/index.md) - [KBase](http://kbase.us) --- \ No newline at end of file diff --git a/docs/dev/testing.md b/docs/dev/testing.md new file mode 100644 index 000000000..de4c56179 --- /dev/null +++ b/docs/dev/testing.md @@ -0,0 +1,19 @@ +# Testing + +## Unit Testing + +```bash +make unit-tests +``` + +## Integration Testing + +```bash +make integration-tests host=HOST +``` + +where HOST is one of ci, next, appdev, narrative. + +> TODO: instead of host, use env, and have the host looked up in the deploy configs. + +> TODO: better yet, have an uber-integration test which reads all the configs, and runs the tests for each environment. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 857f4ddd8..7530d0dce 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,32 +1,42 @@ # KBase User Interface Developer Documenation -> Note: old pre-docker documentation is still present, but is being replaced. - -## Contents +## First Steps - [Prerequisites](dev/prerequisites.md) - [Getting Started](dev/getting-started.md) -- [Developing External Plugins](dev/developing-external-plugins.md) -- [Creating a New External Plugin](dev/developing-new-external-plugin.md) +## Design + +- Architecture +- Dependencies +- Process +- Coding Standards +- Testing +- Configuration + +## Plugins - - - +- [Error Handling](topics/error-handling.md) --- [Index](index.md) - [README](../README.md) - [Release Notes](../release-notes/index.md) - [KBase](http://kbase.us) ---- \ No newline at end of file +--- + diff --git a/install/README.md b/install/README.md deleted file mode 100644 index 50fdf9ddc..000000000 --- a/install/README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Install Tool for KBase UI - -This directory contains tools to deal with the kbase-ui app in the context of the system it is working on. This directory will exist in the top level of the kbase-ui repo, and will also be installed into the kbase-ui application directory upon installation. This allows the tool to be used to reconfigure kbase-ui during runtime. The main use case is to reconfigure kbase-ui services. - -The reason for this to exist is that the config files are built by applying the standard config ini files to a template, the product of which is a yaml file which is consumed by the ui upon loading in the browser. This file, *service.yml*, contains primarily service endpoints, but also other urls such as the doc site and globus. It should contain any information which might change between different deployment environments. - -The basic installation or reconfiguration process is: - -- update or create the appropriate deploy config file -- apply the config file to the template -- copy the resulting services file into the target location within the source tree. - -It is notable that the config file must be a bit deep into the source tree because it is loaded through the AMD system in the web app, which expects (and requires) any loadable code to be located within a base directory. Our base directory is */modules* and all configuration lives in the *config* subdirectory, so thus */kb/deployment/services/kbase-ui/modules/config*. - -The deployment target directory can be provided to the tool, but is otherwise defined as */kb/deployment/services/kbase-ui*. This target directory has in the past been defined in the project configuration, but this doens't work in practice. For instance, if a deploy needs to be reconfigured, will the new configuration contain the deploy directory? And if so, what if it is different from the current deploy? There would be several other things to change -- the entire dist would need to be moved, permissions set up, and the web service reconfigured to point to the new location and then restarted. Rather, we should assume that for a given installation, reconfiguring will not result in the file moving, and that the script which calls the tool can define the utlimate destination for the dist. - -## Instructions - -In an environment in which kbase-ui has been installed, there will be some directory in which the source code has been installed. Within this there is a *build/dist* directory which contains the final product of the production build process. Within this directory you will find the *install* directory, which contains the installation and reconfiguration tool, and the *config* directory which contains a subset of the original configuration from the source (just enough to reconfigure the services.) - -- If changes are required to one of the prebuilt deploy configs, do so in *dist/config/deploy*. -- From within *dist/install* run the reconfiguration app: - -``` -node reconfigure TAG -``` - -where *TAG* is one of the supported deploy targets: ci, next, prod. - -Additional deploy targets may be created simply by creating a new file within *dist/config/deploy* following the naming convention *deploy-TAG.cfg", where TAG is the deploy target tag you wish to use. - - -## All Tools - -install -: Place the dist into the location of your choice - -reconfigure -: After editing or replacing the local deploy.cfg, create a new set of kbase-ui yaml config files which depend on deploy.cfg - -verify -: Verify that the installation is unmodified from the release; reports any differences - \ No newline at end of file diff --git a/install/install.js b/install/install.js deleted file mode 100644 index 77df5b685..000000000 --- a/install/install.js +++ /dev/null @@ -1,50 +0,0 @@ -/*jslint white:true*/ - -'use strict'; - -var Promise = require('bluebird'), - yaml = require('js-yaml'), - ini = require('ini'), - fs = Promise.promisifyAll(require('fs-extra')), - underscore = require('underscore'); - -function rebuildConfig(sourcePath, deployName, deployPath) { - var fileName = 'deploy-' + deployName + '.cfg', - filePath = sourcePath.concat(['config', 'deploy', fileName]), - destPath = deployPath.concat(['client', 'modules', 'config', 'service.yml']); - - return fs.accessAsync(destPath.join('/'), fs.R_OK | fs.W_OK) - .then(function () { - return Promise.all([ - fs.readFileAsync(filePath.join('/'), 'utf8') - .then(function (contents) { - return ini.parse(contents); - }), - fs.readFileAsync(sourcePath.concat(['config', 'deploy', 'templates', 'service.yml']).join('/'), 'utf8') - ]) - }) - .spread(function (deployConfig, template) { - var compiled = underscore.template(template), - processed = compiled(deployConfig['kbase-ui']); - // return processed; - return fs.writeFileAsync(deployPath.concat(['client', 'modules', 'config', 'service.yml']).join('/'), processed); - }); -} - - -//function rebuildServiceConfig(root, target) { -// var fileName = 'deploy-' + target + '.cfg', -// filePath = root.concat(['config', 'deploy', fileName]), -// templatePath = root.concat(['config', 'deploy', 'templates', '']) -// -// return fs.copyAsync(filePath.join('/'), targetPath.concat(['deploy.cfg']).join('/')) -// .then(function () { -// return target; -// }); -//} - - -module.exports = { - // rebuildDeployConfig: rebuildDeployConfig, - rebuildConfig: rebuildConfig -}; \ No newline at end of file diff --git a/install/package.json b/install/package.json deleted file mode 100644 index 3b8b167c4..000000000 --- a/install/package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "name": "kbase-ui-install-tools", - "version": "0.0.1", - "keywords": [ - "util", - "functional", - "server", - "client", - "browser" - ], - "author": "KBase", - "license": "SEE LICENSE IN ../LICENSE.md", - "contributors": [ - "eapearson", - "briehl" - ], - "dependencies": {}, - "devDependencies": { - "bluebird": "^3.0.0", - "fs-extra": "^0.26.0", - "glob": "^5.0.15", - "ini": "^1.3.4", - "js-yaml": "^3.4.3", - "lodash": "^3.10.1", - "underscore": "^1.8.3" - } -} diff --git a/install/reconfigure.js b/install/reconfigure.js deleted file mode 100644 index e17f6d946..000000000 --- a/install/reconfigure.js +++ /dev/null @@ -1,79 +0,0 @@ -/*jslint white:true*/ - -/* - * Replace the deploy configuruation if a target is specified, and - * in any case update the build/dist config with rebuild config files. - */ - -var install = require('./install'), - Promise = require('bluebird'); - -function parseArgs() { - var args = [], options = {}; - process.argv.slice(2).forEach(function (arg) { - var parts = arg.split(/=/); - if (parts.length === 1) { - args.push(arg); - } else { - options[parts[0]] = parts[1]; - } - }); - return { - args: args, - options: options - }; -} - -var args = parseArgs(); - -var deployName = args.args[0], - deployPath = args.args[1], - sourcePath = args.options.source; - -function usage(message) { - var usageTemplate = 'node reconfigure [deploy path]'; - console.log(usageTemplate + ': ' + message); -} - -if (!deployName) { - usage('deploy name missing'); - process.exit(1); -} - -if (!deployPath) { - deployPath = '/kb/deployment/services/kbase-ui'; -} - -if (!sourcePath) { - sourcePath = '..'; -} - - -console.log('Okay, I will redeploy ' + deployName + ' to ' + deployPath + ' from ' + sourcePath); - -// - -//if (targetDir) { -// targetPath = targetDir.split('/'); -//} else { -// targetPath = ['..', 'dist']; -//} -// - install.rebuildConfig(sourcePath.split('/'), deployName, deployPath.split('/')) - .then(function () { - console.log('Config successfully rebuilt and installed'); - }) - .catch(function (err) { - console.log('ERROR'); - console.log(err); - process.exit(1); - }); -// .then(function () { -// console.log('Rebuilding and installing kbase-ui service config'); -// return install.rebuildConfig(targetPath); -// }) -// .catch(function (err) { -// console.log('ERROR'); -// console.log(err); -// process.exit(1); -// }); diff --git a/mutations/build.js b/mutations/build.js index 3ef289bb9..55df3e1bd 100644 --- a/mutations/build.js +++ b/mutations/build.js @@ -36,6 +36,11 @@ var Promise = require('bluebird'), handlebars = require('handlebars'), numeral = require('numeral'); + +// running from kbase-ui/mutations/build.js +// some binaries in kbase-ui/node-modules/.bin +const NODE_BIN = __dirname + '/../node_modules/.bin/'; + // UTILS function gitinfo(state) { @@ -62,11 +67,11 @@ function gitinfo(state) { run('git describe --exact-match --tags $(git rev-parse HEAD)') .catch(function () { // For non-prod ui we can be tolerant of a missing version, but not for prod. - if (state.buildConfig.target === 'prod') { - throw new Error('Not on a tag, cannot deploy'); + if (state.buildConfig.release) { + throw new Error('This is a release build, a semver tag is required'); } - mutant.log('Not on a tag, but that is ok since this is not a prod build'); - mutant.log('version will be unavailable'); + mutant.log('Not on a tag, but that is ok since this is not a release build'); + mutant.log('version will be unavailable in the ui'); return ''; }) ]) @@ -334,6 +339,42 @@ function bowerInstall(state) { }); } +function npm(cmd, argv, options) { + return new Promise(function (resolve, reject) { + exec(NODE_BIN + 'npm ' + cmd + argv.join(' '), options, function (err, stdout, stderr) { + if (err) { + reject(err); + } + if (stderr) { + // reject(new Error(stderr)); + resolve({ + warnings: stderr + }); + } + resolve({ + result: stdout + }); + }); + }); +} + + +function npmInstall(state) { + var base = state.environment.path.concat(['build']); + var packagePath = base.concat(['package.json']); + return mutant.loadJson(packagePath) + .then(function (packageConfig) { + delete packageConfig.devDependencies; + return mutant.saveJson(packagePath, packageConfig); + }) + .then(function () { + return npm('install', [], { + cwd: base.join('/'), + timeout: 300000 + }); + }); +} + function copyFromBower(state) { var root = state.environment.path; @@ -429,6 +470,101 @@ function copyFromBower(state) { }); } +function copyFromNpm(state) { + var root = state.environment.path; + + return mutant.loadYaml(root.concat(['config', 'npmInstall.yml'])) + .then(function (config) { + var copyJobs = []; + + config.npmFiles.forEach(function (cfg) { + /* + The top level bower directory name is usually the name of the + package (which also is often also base of the sole json file name) + but since this is not always the case, we allow the dir setting + to override this. + */ + var dir = cfg.dir || cfg.name, + sources, cwd, dest; + if (!dir) { + throw new Error('Either the name or dir property must be provided to establish the top level directory'); + } + + /* + The source defaults to the package name with .js, unless the + src property is provided, in which case it must be either a single + or set of glob-compatible strings.*/ + if (cfg.src) { + if (typeof cfg.src === 'string') { + sources = [cfg.src]; + } else { + sources = cfg.src; + } + } else if (cfg.name) { + sources = [cfg.name + '.js']; + } else { + throw new Error('Either the src or name must be provided in order to have something to copy'); + } + + /* + Finally, the cwd serves as a way to dig into a subdirectory and use it as the + basis for copying. This allows us to "bring up" files to the top level of + the destination. Since we are relative to the root of this process, we + need to jigger that here. + */ + if (cfg.cwd) { + if (typeof cfg.cwd === 'string') { + cfg.cwd = cfg.cwd.split(/,/); + } + cwd = ['build', 'node_modules', dir].concat(cfg.cwd); + } else { + cwd = ['build', 'node_modules', dir]; + } + + /* + The destination will be composed of 'node_modules' at the top + level, then the package name or dir (as specified above). + This is the core of our "thinning and flattening", which is part of the + point of this bower copy process. + In addition, if the spec includes a dest property, we will use that + */ + if (cfg.standalone) { + dest = ['build', 'client', 'modules'].concat([cfg.name]); + } else { + dest = ['build', 'client', 'modules', 'node_modules'].concat([cfg.dir || cfg.name]); + } + + sources.forEach(function (source) { + copyJobs.push({ + cwd: cwd, + src: source, + dest: dest + }); + }); + }); + + // Create and execute a set of promises to fetch and operate on the files found + // in the above spec. + return Promise.all(copyJobs.map(function (copySpec) { + return glob(copySpec.src, { + cwd: state.environment.path.concat(copySpec.cwd).join('/'), + nodir: true + }) + .then(function (matches) { + // Do the copy! + return Promise.all(matches.map(function (match) { + var fromPath = state.environment.path.concat(copySpec.cwd).concat([match]).join('/'), + toPath = state.environment.path.concat(copySpec.dest).concat([match]).join('/'); + return fs.copy(fromPath, toPath, {}); + })); + }) + .then(function () { + return state; + }); + })); + }); +} + /* * Copy plugins from the bower module installation directory into the plugins * directory. We _could_ reference plugins directly from the bower directory, @@ -438,60 +574,83 @@ function copyFromBower(state) { * * @returns {undefined} */ -function installExternalPlugins(state) { +function installPlugins(state) { // Load plugin config var root = state.environment.path, pluginConfig, pluginConfigFile = root.concat(['config', 'app', state.buildConfig.target, '/plugins.yml']).join('/'); - return Promise.all([fs.readFileAsync(pluginConfigFile, 'utf8')]) - .spread(function (pluginFile) { + return fs.readFileAsync(pluginConfigFile, 'utf8') + .then(function (pluginFile) { pluginConfig = yaml.safeLoad(pluginFile); - return pluginConfig.plugins.filter(function (plugin) { - return (typeof plugin === 'object' && plugin.internal !== true); - }); - }) - .then(function (externalPlugins) { - return [externalPlugins, Promise.all(externalPlugins.map(function (plugin) { - if (plugin.source.bower) { - var cwds = plugin.cwd || 'dist/plugin', - cwd = cwds.split('/'), - srcDir = root.concat(['build', 'bower_components', plugin.globalName]).concat(cwd), - destDir = root.concat(['build', 'client', 'modules', 'plugins', plugin.name]); - return copyFiles(srcDir, destDir, '**/*'); - } - }))]; - }) - .spread(function (externalPlugins) { - return [externalPlugins, Promise.all(externalPlugins + var plugins = pluginConfig.plugins; + return Promise.all(plugins .filter(function (plugin) { - return plugin.source.directory ? true : false; + return (typeof plugin === 'object' && plugin.source.bower); }) .map(function (plugin) { var cwds = plugin.cwd || 'dist/plugin', cwd = cwds.split('/'), - // Our actual cwd is mutations, so we need to escape one up to the - // project root. - repoRoot = (plugin.source.directory.root && plugin.source.directory.root.split('/')) || ['..', '..'], - source = repoRoot.concat([plugin.globalName]).concat(cwd), - destination = root.concat(['build', 'client', 'modules', 'plugins', plugin.name]); - return copyFiles(source, destination, '**/*'); - }))]; - }) - .spread(function (externalPlugins) { - return Promise.all(externalPlugins - .filter(function (plugin) { - return plugin.source.link ? true : false; + srcDir = root.concat(['build', 'bower_components', plugin.globalName]).concat(cwd), + destDir = root.concat(['build', 'client', 'modules', 'plugins', plugin.name]); + return copyFiles(srcDir, destDir, '**/*'); + })) + .then(function () { + return Promise.all(plugins + .filter(function (plugin) { + return (typeof plugin === 'object' && plugin.source.directory); + }) + .map(function (plugin) { + var cwds = plugin.cwd || 'dist/plugin', + cwd = cwds.split('/'), + // Our actual cwd is mutations, so we need to escape one up to the + // project root. + repoRoot = (plugin.source.directory.root && plugin.source.directory.root.split('/')) || ['..', '..'], + source = repoRoot.concat([plugin.globalName]).concat(cwd), + destination = root.concat(['build', 'client', 'modules', 'plugins', plugin.name]); + return copyFiles(source, destination, '**/*'); + })); }) - .map(function (plugin) { - var //cwds = plugin.cwd || 'dist/plugin', - // cwd = cwds.split('/'), - // Our actual cwd is mutations, so we need to escape one up to the - // project root. - // repoRoot = (plugin.source.link.root && plugin.source.link.root.split('/')) || ['..', '..'], - // source = repoRoot.concat([plugin.globalName]).concat(cwd), - destination = root.concat(['build', 'client', 'modules', 'plugins', plugin.name]); - return fs.mkdirAsync(destination.join('/')); - })); + .then(function () { + return Promise.all(plugins + .filter(function (plugin) { + return (typeof plugin === 'string'); + }) + .map(function (plugin) { + var source = root.concat(['plugins', plugin]), + destination = root.concat(['build', 'client', 'modules', 'plugins', plugin]); + // console.log('internal plugin?', plugin, root, source.join('/'), destination.join('/')); + return copyFiles(source, destination, '**/*'); + })); + }); + }) + // now move the test files into the test dir + .then(function () { + // dir list of all plugins + var pluginsPath = root.concat(['build', 'client', 'modules', 'plugins']); + return dirList(pluginsPath) + .then(function (pluginDirs) { + return Promise.each(pluginDirs, function (pluginDir) { + var testDir = pluginDir.path.concat(['test']); + return pathExists(testDir.join('/')) + .then(function (exists) { + var justDir = pluginDir.path[pluginDir.path.length - 1]; + if (!exists) { + mutant.warn('plugin without tests: ' + justDir); + } else { + mutant.success('plugin with tests! : ' + justDir); + var dest = root.concat(['test', 'integration-tests', 'specs', 'plugins', justDir]); + return fs.moveAsync(testDir.join('/'), dest.join('/')); + } + }); + }); + }); + // warning for those without tests + // filter for those with a test directory + // ensure test/plugins exists + // move test directory there + }) + .then(function () { + return state; }); } @@ -520,16 +679,31 @@ function installExternalPlugins(state) { function setupBuild(state) { var root = state.environment.path; state.steps = []; - return mutant.deleteMatchingFiles(state.environment.path.join('/'), /.*\.DS_Store$/) + return mutant.deleteMatchingFiles(root.join('/'), /.*\.DS_Store$/) .then(function () { // the client really now becomes the build! var from = root.concat(['src', 'client']), to = root.concat(['build', 'client']); return fs.moveAsync(from.join('/'), to.join('/')); }) + .then(function () { + // the client really now becomes the build! + var from = root.concat(['src', 'test']), + to = root.concat(['test']); + return fs.moveAsync(from.join('/'), to.join('/')); + }) + .then(function () { + // the client really now becomes the build! + var from = root.concat(['src', 'plugins']), + to = root.concat(['plugins']); + return fs.moveAsync(from.join('/'), to.join('/')); + }) .then(function () { return fs.moveAsync(root.concat(['bower.json']).join('/'), root.concat(['build', 'bower.json']).join('/')); }) + .then(function () { + return fs.moveAsync(root.concat(['package.json']).join('/'), root.concat(['build', 'package.json']).join('/')); + }) .then(function () { return fs.rmdirAsync(root.concat(['src']).join('/')); }) @@ -544,10 +718,23 @@ function setupBuild(state) { }); } -function fetchPackagesWithBower(state) { - return bowerInstall(state) +// function fetchPackagesWithBower(state) { +// return bowerInstall(state) +// .then(function () { +// return fs.remove(state.environment.root.concat(['build', 'bower.json']).join('/')); +// }) +// .then(function () { +// return state; +// }); +// } + +function installNpmPackages(state) { + return npmInstall(state) + .then(function () { + return fs.remove(state.environment.path.concat(['build', 'package.json']).join('/')); + }) .then(function () { - return fs.remove(state.environment.root.concat(['build', 'bower.json']).join('/')); + return copyFromNpm(state); }) .then(function () { return state; @@ -557,15 +744,11 @@ function fetchPackagesWithBower(state) { function installBowerPackages(state) { return bowerInstall(state) .then(function () { - return copyFromBower(state); + return fs.remove(state.environment.path.concat(['build', 'bower.json']).join('/')); }) .then(function () { - return state; - }); -} - -function installPlugins(state) { - return installExternalPlugins(state) + return copyFromBower(state); + }) .then(function () { return state; }); @@ -580,7 +763,7 @@ function copyUiConfig(state) { var root = state.environment.path, configSource = root.concat(['config', 'app', state.buildConfig.target]), releaseVersionConfig = root.concat(['config', 'release.yml']), - configFiles = ['menus.yml', 'services.yml'].map(function (file) { + configFiles = ['services.yml'].map(function (file) { return configSource.concat(file); }).concat([releaseVersionConfig]), configDest = root.concat(['build', 'client', 'modules', 'config']), @@ -592,7 +775,7 @@ function copyUiConfig(state) { return mutant.loadYaml(file); })) .then(function (configs) { - return mergeObjects([baseConfig].concat(configs)); + return mutant.mergeObjects([baseConfig].concat(configs)); }) .then(function (mergedConfigs) { state.mergedConfig = mergedConfigs; @@ -636,16 +819,14 @@ function getReleaseNotes(state, version) { }); } - function verifyVersion(state) { return Promise.try(function () { - mutant.log('Verifying version...'); var releaseVersion = state.mergedConfig.release.version; var gitVersion = state.buildInfo.git.version; - if (state.buildConfig.target === 'prod') { + if (state.buildConfig.release) { if (releaseVersion === gitVersion) { - mutant.log('release and git agree on ' + releaseVersion); + mutant.log('release and git agree on version ' + releaseVersion); } else { throw new Error('Release and git versions are different; release says "' + releaseVersion + '", git says "' + gitVersion + '"'); } @@ -664,39 +845,6 @@ function verifyVersion(state) { }); } -function mergeObjects(listOfObjects) { - var simpleObjectPrototype = Object.getPrototypeOf({}); - - function isSimpleObject(obj) { - return Object.getPrototypeOf(obj) === simpleObjectPrototype; - } - - function merge(obj1, obj2, keyStack) { - Object.keys(obj2).forEach(function (key) { - var obj1Value = obj1[key]; - var obj2Value = obj2[key]; - var obj1Type = typeof obj1Value; - // var obj2Type = typeof obj2Value; - if (obj1Type === 'undefined') { - obj1[key] = obj2[key]; - } else if (isSimpleObject(obj1Value) && isSimpleObject(obj2Value)) { - keyStack.push(key); - merge(obj1Value, obj2Value, keyStack); - keyStack.pop(); - } else { - console.error('UNMERGABLE', obj1Type, obj1Value); - throw new Error('Unmergable at ' + keyStack.join('.') + ':' + key); - } - }); - } - - var base = JSON.parse(JSON.stringify(listOfObjects[0])); - for (var i = 1; i < listOfObjects.length; i += 1) { - merge(base, listOfObjects[i], []); - } - return base; -} - // TODO: the deploy will be completely replaced with a deploy script. // For now, the deploy is still required for dev and ci builds to work // without the deploy script being integrated into the ci, next, appdev, and prod @@ -723,11 +871,15 @@ function makeKbConfig(state) { fs.mkdirsAsync(deployModules.join('/')) ]) .then(function () { - return fs.readFileAsync(root.concat(['config', 'deploy', 'templates', 'build-info.js.txt']).join('/'), 'utf8') + // A bit weird to do this here... + return fs.readFileAsync(root.concat(['build', 'client', 'build-info.js.txt']).join('/'), 'utf8') .then(function (template) { var dest = root.concat(['build', 'client', 'build-info.js']).join('/'); var out = handlebars.compile(template)(state.buildInfo); return fs.writeFileAsync(dest, out); + }) + .then(function () { + fs.removeAsync(root.concat(['build', 'client', 'build-info.js.txt']).join('/')); }); }) // Now merge the configs. @@ -739,7 +891,7 @@ function makeKbConfig(state) { ]; return Promise.all(configs.map(loadYaml)) .then(function (yamls) { - var merged = mergeObjects(yamls); + var merged = mutant.mergeObjects(yamls); // expand aliases for services Object.keys(merged.services).forEach(function (serviceKey) { var serviceConfig = merged.services[serviceKey]; @@ -815,10 +967,10 @@ function cleanup(state) { var root = state.environment.path; return fs.removeAsync(root.concat(['build', 'bower_components']).join('/')) .then(function () { - return fs.removeAsync(root.concat(['bower.json']).join('/')); + fs.removeAsync(root.concat(['build', 'node_modules']).join('/')); }) .then(function () { - return fs.removeAsync(root.concat(['build', 'install', 'package.json']).join('/')); + return fs.removeAsync(root.concat(['bower.json']).join('/')); }) .then(function () { return state; @@ -834,10 +986,10 @@ function makeBaseBuild(state) { return fs.moveAsync(root.concat(['config']).join('/'), root.concat(['build', 'config']).join('/')); }) .then(function () { - return fs.moveAsync(root.concat(['install']).join('/'), root.concat(['build', 'install']).join('/')); + return fs.copyAsync(root.concat(['build']).join('/'), buildPath.concat(['build']).join('/')); }) .then(function () { - return fs.copyAsync(root.concat(['build']).join('/'), buildPath.concat(['build']).join('/')); + return fs.copyAsync(root.concat(['test']).join('/'), buildPath.concat(['test']).join('/')); }) .then(function () { return state; @@ -1035,8 +1187,9 @@ function makeModuleVFS(state, whichBuild) { return fs.statAsync(match) .then(function (stat) { if (stat.size > 200000) { - mutant.warn('skipping because too big: ' + numeral(stat.size).format('0.0b')); - mutant.warn(match); + mutant.warn('omitting file from bundle because too big: ' + numeral(stat.size).format('0.0b')); + mutant.warn(' ' + match); + mutant.warn(' don\'t worry, it is stil included in the build!'); skip('toobig'); return; } @@ -1119,48 +1272,31 @@ function makeModuleVFS(state, whichBuild) { */ function main(type) { - // INPUT - var initialFilesystem = [{ - cwd: ['..'], - path: ['src'] - }, { - cwd: ['..'], - files: ['bower.json'] - }, { - cwd: ['..'], - path: ['install'] - }, { - cwd: ['..'], - path: ['release-notes'] - }]; - // Use a copy of the configs in the dev directory; this supports local hacking without worry - // of accidentally checking in temporary config changes. - return pathExists('../dev/config') - .then(function (exists) { - // ugly work around for now - var buildControlConfigPath; - if (exists) { - initialFilesystem.push({ - cwd: ['..', 'dev'], - path: ['config'] - }); - buildControlConfigPath = ['..', 'dev', 'config', 'builds', type + '.yml']; - } else { - initialFilesystem.push({ - cwd: ['..'], - path: ['config'] - }); - buildControlConfigPath = ['..', 'config', 'builds', type + '.yml']; - } - return { - initialFilesystem: initialFilesystem, - buildControlConfigPath: buildControlConfigPath - }; - }) - .then(function (config) { - mutant.log('Creating initial state with config: '); - return mutant.createInitialState(config); - }) + return Promise.try(function () { + mutant.log('Creating initial state with for build: ' + type); + var initialFilesystem = [{ + cwd: ['..'], + path: ['src'] + }, { + cwd: ['..'], + files: ['bower.json', 'package.json'] + }, { + cwd: ['..'], + path: ['release-notes'] + }, { + cwd: ['..'], + path: ['config'] + }]; + var buildControlConfigPath = ['..', 'config', 'build', 'configs', type + '.yml']; + var buildControlDefaultsPath = ['..', 'config', 'build', 'defaults.yml']; + var config = { + initialFilesystem: initialFilesystem, + buildControlConfigPath: buildControlConfigPath, + buildControlDefaultsPath: buildControlDefaultsPath + }; + return mutant.createInitialState(config); + }) + .then(function (state) { return mutant.copyState(state); }) @@ -1169,22 +1305,31 @@ function main(type) { return setupBuild(state); }) + // .then(function (state) { + // return mutant.copyState(state); + // }) + // .then(function (state) { + // mutant.log('Fetching bower packages...'); + // return fetchPackagesWithBower(state); + // }) + .then(function (state) { return mutant.copyState(state); }) .then(function (state) { - mutant.log('Fetching bower packages...'); - return fetchPackagesWithBower(state); + mutant.log('Installing bower packages...'); + return installBowerPackages(state); }) .then(function (state) { return mutant.copyState(state); }) .then(function (state) { - mutant.log('Installing bower packages...'); - return installBowerPackages(state); + mutant.log('Installing npm packages...'); + return installNpmPackages(state); }) + .then(function (state) { return mutant.copyState(state); }) diff --git a/mutations/mutant.js b/mutations/mutant.js index 2a42662e1..dc7b1d143 100644 --- a/mutations/mutant.js +++ b/mutations/mutant.js @@ -233,22 +233,66 @@ function warn(msg) { process.stdout.write('\n'); } -function createInitialState(initialConfig) { - var initialFilesystem = initialConfig.initialFilesystem, - buildControlConfigPath = initialConfig.buildControlConfigPath; - // TODO: do this better... - var app, appName; - if (process.argv[0].match(/node$/)) { - app = process.argv[1]; - } else { - app = process.argv[0]; +function success(msg) { + var line = '✔ : '+ timestamp() + ': ' + msg; + var chalked = chalk.green(line); + process.stdout.write(chalked); + process.stdout.write('\n'); +} + +function mergeObjects(listOfObjects) { + var simpleObjectPrototype = Object.getPrototypeOf({}); + + function isSimpleObject(obj) { + return Object.getPrototypeOf(obj) === simpleObjectPrototype; + } + + function merge(obj1, obj2, keyStack) { + Object.keys(obj2).forEach(function (key) { + var obj1Value = obj1[key]; + var obj2Value = obj2[key]; + var obj1Type = typeof obj1Value; + // var obj2Type = typeof obj2Value; + if (obj1Type === 'undefined') { + obj1[key] = obj2[key]; + } else if (isSimpleObject(obj1Value) && isSimpleObject(obj2Value)) { + keyStack.push(key); + merge(obj1Value, obj2Value, keyStack); + keyStack.pop(); + } else { + console.error('UNMERGABLE', obj1Type, obj1Value); + throw new Error('Unmergable at ' + keyStack.join('.') + ':' + key); + } + }); } - appName = app.split('/').pop(); - log('Creating initial state for app: ' + appName); + var base = JSON.parse(JSON.stringify(listOfObjects[0])); + for (var i = 1; i < listOfObjects.length; i += 1) { + merge(base, listOfObjects[i], []); + } + return base; +} - return loadYaml(buildControlConfigPath) - .then(function (buildConfig) { +function createInitialState(initialConfig) { + var initialFilesystem = initialConfig.initialFilesystem, + buildControlConfigPath = initialConfig.buildControlConfigPath, + buildControlDefaultsPath = initialConfig.buildControlDefaultsPath; + + // TODO: do this better... + // var app, appName; + // if (process.argv[0].match(/node$/)) { + // app = process.argv[1]; + // } else { + // app = process.argv[0]; + // } + // appName = app.split('/').pop(); + + // log('Creating initial state for app: ' + appName); + log('Creating initial state'); + + return Promise.all([loadYaml(buildControlConfigPath), loadYaml(buildControlDefaultsPath)]) + .then(function (configs) { + var buildConfig = mergeObjects(configs); var state = { environment: {}, data: {}, @@ -316,5 +360,7 @@ module.exports = { saveJson: saveJson, rtrunc: rtrunc, log: log, - warn: warn + warn: warn, + success: success, + mergeObjects: mergeObjects }; diff --git a/package.json b/package.json index f92af5d70..fd0fb1dbd 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { "name": "kbase-ui", - "version": "0.0.1", + "version": "1.6.4", "keywords": [ "util", "functional", - "server", "client", "browser" ], @@ -12,7 +11,7 @@ "license": "SEE LICENSE IN LICENSE.md", "repository": { "type": "git", - "url": "https://github.com/eapearson/kbase-ui.git" + "url": "https://github.com/kbase/kbase-ui.git" }, "contributors": [ "eapearson", @@ -21,7 +20,7 @@ "devDependencies": { "bluebird": "3.5.1", "bower": "1.8.2", - "chalk": "2.3.1", + "chalk": "2.3.2", "findit2": "2.2.3", "fs-extra": "3.0.1", "glob": "7.1.2", @@ -42,9 +41,9 @@ "grunt-webdriver": "2.0.3", "handlebars": "4.0.11", "ini": "1.3.5", - "jasmine": "3.0.0", - "jasmine-core": "2.99.1", - "js-yaml": "3.10.0", + "jasmine": "3.1.0", + "jasmine-core": "3.1.0", + "js-yaml": "3.11.0", "karma": "2.0.0", "karma-chrome-launcher": "2.2.0", "karma-cli": "1.0.1", @@ -55,19 +54,35 @@ "lodash": "4.17.5", "node-dir": "0.1.17", "node-ini": "1.0.0", + "npm": "5.7.1", "numeral": "2.0.6", "path-exists": "3.0.0", - "puppeteer": "1.0.0", + "puppeteer": "1.1.1", "requirejs": "2.3.5", "sauce-connect-launcher": "1.2.3", "selenium-standalone": "6.12.0", - "simple-git": "1.89.0", - "uglify-es": "3.3.10", + "selenium-webdriver": "4.0.0-alpha.1", + "simple-git": "1.92.0", + "uglify-es": "3.3.9", "underscore": "1.8.3", "wdio-dot-reporter": "0.0.9", "wdio-jasmine-framework": "^0.3.1", "wdio-selenium-standalone-service": "0.0.9", "wdio-sauce-service": "0.4.8", - "webdriverio": "4.10.2" + "webdriverio": "4.11.0" + }, + "dependencies": { + "ajv": "6.2.1", + "bluebird": "3.5.1", + "dagre": "0.8.2", + "lodash": "4.17.5" + }, + "nextDependencies": { + "d3": "4.13.0", + "d3-sankey": "0.7.1", + "d3-array": "1.2.1", + "d3-collection": "1.0.4", + "d3-shape": "1.2.0", + "d3-path": "1.0.5" } } diff --git a/release-notes/RELEASE_NOTES_1.6.5.md b/release-notes/RELEASE_NOTES_1.6.5.md new file mode 100644 index 000000000..8a05afc9f --- /dev/null +++ b/release-notes/RELEASE_NOTES_1.6.5.md @@ -0,0 +1,52 @@ +# KBase kbase-ui 1.6.5 Release Notes + +This release brings many small improvements and fixes including to landing pages, signup, the catalog and job viewer, and jgi search. The new, as yet unreleased, provenance widget was improved with support from the dagre library. The beta Data Search tool brings an alpha level Genome Features tab - it is still missing significant features but does work. + +Under the hood the build tools were updated as we move towards an improved, fully dockerized development workflow and deployment. A new integration testing framework was put in place to allow semi-automated integration testing for all plugins. + + +## CHANGES + +### NEW + +- dataview: added CompoundSet landing page + - https://github.com/kbase/kbase-ui/pull/607 + +### REMOVED + +n/a + +### UNRELEASED + +- dataview: updates to new provenance widget + - https://github.com/kbase/kbase-ui/pull/600 +- data-search: add genome feature search + - https://github.com/kbase/kbase-ui/pull/601 + +### IMPROVEMENTS + +- build and deploy improvements +- can disable menu items per deployment + - https://github.com/kbase/kbase-ui/pull/593 +- can use npm packages in build + - https://github.com/kbase/kbase-ui/pull/594 +- added integration test support for plugins + - https://github.com/kbase/kbase-ui/pull/602 +- auth2-client: improved signup ui (username entry more helpful) + - https://github.com/kbase/kbase-ui/pull/595 +- catalog: catalog admin adds client groups column + - https://github.com/kbase/kbase-ui/pull/608 +- jgi-search: jgi search improved and released on menu + - https://github.com/kbase/kbase-ui/pull/609 + +### FIXES + +- auth2-client: fixed organization selector in signup form + - https://github.com/kbase/kbase-ui/pull/595 +- catalog: fixed catalog and job viewer with hardcoded app version + - https://github.com/kbase/kbase-ui/pull/606 + +## Dependency Changes + +- added dagre as dependency + - https://github.com/kbase/kbase-ui/pull/600 diff --git a/release-notes/index.md b/release-notes/index.md index 55f7e9d6c..daad2209f 100644 --- a/release-notes/index.md +++ b/release-notes/index.md @@ -6,6 +6,7 @@ | Version | Date | Notes | |-----------------------------------|------------|---------------------| +| [1.6.5](RELEASE_NOTES_1.6.5.md) | 2018-03-09 | | | [1.6.4](RELEASE_NOTES_1.6.4.md) | 2018-02-27 | | | [1.6.3](RELEASE_NOTES_1.6.3.md) | 2018-02-23 | | | [1.6.2](RELEASE_NOTES_1.6.2.md) | 2018-02-21 | | diff --git a/config/deploy/templates/build-info.js.txt b/src/client/build-info.js.txt similarity index 100% rename from config/deploy/templates/build-info.js.txt rename to src/client/build-info.js.txt diff --git a/src/client/modules/app/App.js b/src/client/modules/app/App.js index 1c01b3619..9fae99926 100644 --- a/src/client/modules/app/App.js +++ b/src/client/modules/app/App.js @@ -129,19 +129,7 @@ define([ name: serviceName }; service.module = serviceName; - // if (serviceConfig.module) { - // service.module = serviceConfig.module; - // } else { - // service.module = serviceName; - // } - // var config = appConfig.getItem(['ui', 'services', serviceName], {}); - // TODO does not support paths, but we don't want to keep this mechamism anyway. - if (serviceConfig.configs) { - serviceConfig.configs.forEach(function (configName) { - serviceConfig[configName] = appConfig.getItem(configName); - }); - delete serviceConfig.configs; - } + serviceConfig.runtime = api; appServiceManager.addService(service, serviceConfig); }); diff --git a/src/client/modules/app/services/ko-component.js b/src/client/modules/app/services/ko-component.js index 59996e261..72578d3e3 100644 --- a/src/client/modules/app/services/ko-component.js +++ b/src/client/modules/app/services/ko-component.js @@ -41,6 +41,9 @@ define([ } require(modulePaths, function (result) { // The result is a component factory which takes no arguments. + if (typeof result !== 'function') { + reject(new Error('The component module is not a factory function; perhaps it shouldn\'t be mapped in config.yml: ' + modulePaths.join(','))); + } try { ko.components.register(componentConfig.name, result()); resolve(result); diff --git a/src/client/modules/app/services/menu.js b/src/client/modules/app/services/menu.js index e40398f15..9781cda45 100644 --- a/src/client/modules/app/services/menu.js +++ b/src/client/modules/app/services/menu.js @@ -1,7 +1,10 @@ define([ 'bluebird', 'kb_common/observed' -], function (Promise, observed) { +], function ( + Promise, + observed +) { 'use strict'; function factory(config) { @@ -53,10 +56,14 @@ define([ } var path; - if (typeof menuItemDef.path === 'string') { - path = [menuItemDef.path]; - } else { - path = menuItemDef.path; + if (menuItemDef.path) { + if (typeof menuItemDef.path === 'string') { + path = menuItemDef.path; + } else if (menuItemDef.path instanceof Array) { + path = menuItemDef.path.join('/'); + } else { + throw new Error('Invalid path for menu item', menuItemDef); + } } var menuItem = { // These are from the plugin's menu item definition @@ -122,12 +129,27 @@ define([ // The hamburger menu. Object.keys(config.menus).forEach(function (menu) { var menuDef = config.menus[menu]; - Object.keys(menuDef).forEach(function (section) { + // Skip a menu with no sections + if (!menuDef.sections) { + return; + } + Object.keys(menuDef.sections).forEach(function (section) { // Skip sections with no items. - if (!menuDef[section]) { + if (!menuDef.sections[section]) { return; } - menuDef[section].forEach(function (menuItem) { + if (!menuDef.sections[section].items) { + return; + } + var items = menuDef.sections[section].items; + var disabled = menuDef.disabled || []; + items.forEach(function (menuItem) { + if (menuItem.disabled) { + return; + } + if (disabled.indexOf(menuItem.id) >= 0) { + return; + } addToMenu({ menu: menu, section: section, @@ -137,7 +159,6 @@ define([ }); }); }); - } function stop() {} diff --git a/src/client/modules/app/services/schema.js b/src/client/modules/app/services/schema.js index 9754b6000..e5613a598 100644 --- a/src/client/modules/app/services/schema.js +++ b/src/client/modules/app/services/schema.js @@ -1,6 +1,6 @@ define([ 'bluebird', - 'lib/ajv', + 'ajv', 'kb_common_ts/HttpClient' ], function ( Promise, @@ -19,9 +19,9 @@ define([ } var httpClient = new HttpClient.HttpClient(); return httpClient.request({ - method: 'GET', - url: def.url - }) + method: 'GET', + url: def.url + }) .then(function (result) { var schemaJson; var schemaCompiled; diff --git a/src/client/modules/app/styles/kb-bootstrap.css b/src/client/modules/app/styles/kb-bootstrap.css index 644c72456..7130c3ebf 100644 --- a/src/client/modules/app/styles/kb-bootstrap.css +++ b/src/client/modules/app/styles/kb-bootstrap.css @@ -328,4 +328,30 @@ .bs-callout a.alert-link { font-weight: bold; -} \ No newline at end of file +} + +/* sematic borders ...*/ + +.bs-border-invisible { + border: 1px solid transparent; +} + +.bs-border-default { + border: 1px solid #000; +} + +.bs-border-success { + border: 1px solid #3c763d; +} + +.bs-border-info { + border: 1px solid #3A87AD; +} + +.bs-border-warning { + border: 1px solid #8a6d3b; +} + +.bs-border-danger { + border: 1px solid #a94442; +} diff --git a/src/client/modules/lib/ajv.js b/src/client/modules/lib/ajv.js deleted file mode 100644 index 51be6bdb6..000000000 --- a/src/client/modules/lib/ajv.js +++ /dev/null @@ -1,7769 +0,0 @@ -(function (f) { if (typeof exports === "object" && typeof module !== "undefined") { module.exports = f() } else if (typeof define === "function" && define.amd) { define([], f) } else { var g; if (typeof window !== "undefined") { g = window } else if (typeof global !== "undefined") { g = global } else if (typeof self !== "undefined") { g = self } else { g = this } - g.Ajv = f() } })(function () { - var define, module, exports; - return (function e(t, n, r) { - function s(o, u) { if (!n[o]) { if (!t[o]) { var a = typeof require == "function" && require; if (!u && a) return a(o, !0); if (i) return i(o, !0); var f = new Error("Cannot find module '" + o + "'"); throw f.code = "MODULE_NOT_FOUND", f } var l = n[o] = { exports: {} }; - t[o][0].call(l.exports, function (e) { var n = t[o][1][e]; return s(n ? n : e) }, l, l.exports, e, t, n, r) } return n[o].exports } var i = typeof require == "function" && require; for (var o = 0; o < r.length; o++) s(r[o]); return s })({ - 1: [function (require, module, exports) { - 'use strict'; - - var KEYWORDS = [ - 'multipleOf', - 'maximum', - 'exclusiveMaximum', - 'minimum', - 'exclusiveMinimum', - 'maxLength', - 'minLength', - 'pattern', - 'additionalItems', - 'maxItems', - 'minItems', - 'uniqueItems', - 'maxProperties', - 'minProperties', - 'required', - 'additionalProperties', - 'enum', - 'format', - 'const' - ]; - - module.exports = function (metaSchema, keywordsJsonPointers) { - for (var i = 0; i < keywordsJsonPointers.length; i++) { - metaSchema = JSON.parse(JSON.stringify(metaSchema)); - var segments = keywordsJsonPointers[i].split('/'); - var keywords = metaSchema; - var j; - for (j = 1; j < segments.length; j++) - keywords = keywords[segments[j]]; - - for (j = 0; j < KEYWORDS.length; j++) { - var key = KEYWORDS[j]; - var schema = keywords[key]; - if (schema) { - keywords[key] = { - anyOf: [ - schema, - { $ref: 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/$data.json#' } - ] - }; - } - } - } - - return metaSchema; - }; - - }, {}], - 2: [function (require, module, exports) { - 'use strict'; - - - var Cache = module.exports = function Cache() { - this._cache = {}; - }; - - - Cache.prototype.put = function Cache_put(key, value) { - this._cache[key] = value; - }; - - - Cache.prototype.get = function Cache_get(key) { - return this._cache[key]; - }; - - - Cache.prototype.del = function Cache_del(key) { - delete this._cache[key]; - }; - - - Cache.prototype.clear = function Cache_clear() { - this._cache = {}; - }; - - }, {}], - 3: [function (require, module, exports) { - 'use strict'; - - //all requires must be explicit because browserify won't work with dynamic requires - module.exports = { - '$ref': require('../dotjs/ref'), - allOf: require('../dotjs/allOf'), - anyOf: require('../dotjs/anyOf'), - const: require('../dotjs/const'), - contains: require('../dotjs/contains'), - dependencies: require('../dotjs/dependencies'), - 'enum': require('../dotjs/enum'), - format: require('../dotjs/format'), - items: require('../dotjs/items'), - maximum: require('../dotjs/_limit'), - minimum: require('../dotjs/_limit'), - maxItems: require('../dotjs/_limitItems'), - minItems: require('../dotjs/_limitItems'), - maxLength: require('../dotjs/_limitLength'), - minLength: require('../dotjs/_limitLength'), - maxProperties: require('../dotjs/_limitProperties'), - minProperties: require('../dotjs/_limitProperties'), - multipleOf: require('../dotjs/multipleOf'), - not: require('../dotjs/not'), - oneOf: require('../dotjs/oneOf'), - pattern: require('../dotjs/pattern'), - properties: require('../dotjs/properties'), - propertyNames: require('../dotjs/propertyNames'), - required: require('../dotjs/required'), - uniqueItems: require('../dotjs/uniqueItems'), - validate: require('../dotjs/validate') - }; - - }, { "../dotjs/_limit": 14, "../dotjs/_limitItems": 15, "../dotjs/_limitLength": 16, "../dotjs/_limitProperties": 17, "../dotjs/allOf": 18, "../dotjs/anyOf": 19, "../dotjs/const": 20, "../dotjs/contains": 21, "../dotjs/dependencies": 23, "../dotjs/enum": 24, "../dotjs/format": 25, "../dotjs/items": 26, "../dotjs/multipleOf": 27, "../dotjs/not": 28, "../dotjs/oneOf": 29, "../dotjs/pattern": 30, "../dotjs/properties": 31, "../dotjs/propertyNames": 32, "../dotjs/ref": 33, "../dotjs/required": 34, "../dotjs/uniqueItems": 35, "../dotjs/validate": 36 }], - 4: [function (require, module, exports) { - 'use strict'; - - var MissingRefError = require('./error_classes').MissingRef; - - module.exports = compileAsync; - - - /** - * Creates validating function for passed schema with asynchronous loading of missing schemas. - * `loadSchema` option should be a function that accepts schema uri and returns promise that resolves with the schema. - * @this Ajv - * @param {Object} schema schema object - * @param {Boolean} meta optional true to compile meta-schema; this parameter can be skipped - * @param {Function} callback an optional node-style callback, it is called with 2 parameters: error (or null) and validating function. - * @return {Promise} promise that resolves with a validating function. - */ - function compileAsync(schema, meta, callback) { - /* eslint no-shadow: 0 */ - /* global Promise */ - /* jshint validthis: true */ - var self = this; - if (typeof this._opts.loadSchema != 'function') - throw new Error('options.loadSchema should be a function'); - - if (typeof meta == 'function') { - callback = meta; - meta = undefined; - } - - var p = loadMetaSchemaOf(schema).then(function () { - var schemaObj = self._addSchema(schema, undefined, meta); - return schemaObj.validate || _compileAsync(schemaObj); - }); - - if (callback) { - p.then( - function (v) { callback(null, v); }, - callback - ); - } - - return p; - - - function loadMetaSchemaOf(sch) { - var $schema = sch.$schema; - return $schema && !self.getSchema($schema) ? - compileAsync.call(self, { $ref: $schema }, true) : - Promise.resolve(); - } - - - function _compileAsync(schemaObj) { - try { return self._compile(schemaObj); } catch (e) { - if (e instanceof MissingRefError) return loadMissingSchema(e); - throw e; - } - - - function loadMissingSchema(e) { - var ref = e.missingSchema; - if (added(ref)) throw new Error('Schema ' + ref + ' is loaded but ' + e.missingRef + ' cannot be resolved'); - - var schemaPromise = self._loadingSchemas[ref]; - if (!schemaPromise) { - schemaPromise = self._loadingSchemas[ref] = self._opts.loadSchema(ref); - schemaPromise.then(removePromise, removePromise); - } - - return schemaPromise.then(function (sch) { - if (!added(ref)) { - return loadMetaSchemaOf(sch).then(function () { - if (!added(ref)) self.addSchema(sch, ref, undefined, meta); - }); - } - }).then(function () { - return _compileAsync(schemaObj); - }); - - function removePromise() { - delete self._loadingSchemas[ref]; - } - - function added(ref) { - return self._refs[ref] || self._schemas[ref]; - } - } - } - } - - }, { "./error_classes": 6 }], - 5: [function (require, module, exports) { - 'use strict'; - - /*eslint complexity: 0*/ - - module.exports = function equal(a, b) { - if (a === b) return true; - - var arrA = Array.isArray(a), - arrB = Array.isArray(b), - i; - - if (arrA && arrB) { - if (a.length != b.length) return false; - for (i = 0; i < a.length; i++) - if (!equal(a[i], b[i])) return false; - return true; - } - - if (arrA != arrB) return false; - - if (a && b && typeof a === 'object' && typeof b === 'object') { - var keys = Object.keys(a); - if (keys.length !== Object.keys(b).length) return false; - - var dateA = a instanceof Date, - dateB = b instanceof Date; - if (dateA && dateB) return a.getTime() == b.getTime(); - if (dateA != dateB) return false; - - var regexpA = a instanceof RegExp, - regexpB = b instanceof RegExp; - if (regexpA && regexpB) return a.toString() == b.toString(); - if (regexpA != regexpB) return false; - - for (i = 0; i < keys.length; i++) - if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; - - for (i = 0; i < keys.length; i++) - if (!equal(a[keys[i]], b[keys[i]])) return false; - - return true; - } - - return false; - }; - - }, {}], - 6: [function (require, module, exports) { - 'use strict'; - - var resolve = require('./resolve'); - - module.exports = { - Validation: errorSubclass(ValidationError), - MissingRef: errorSubclass(MissingRefError) - }; - - - function ValidationError(errors) { - this.message = 'validation failed'; - this.errors = errors; - this.ajv = this.validation = true; - } - - - MissingRefError.message = function (baseId, ref) { - return 'can\'t resolve reference ' + ref + ' from id ' + baseId; - }; - - - function MissingRefError(baseId, ref, message) { - this.message = message || MissingRefError.message(baseId, ref); - this.missingRef = resolve.url(baseId, ref); - this.missingSchema = resolve.normalizeId(resolve.fullPath(this.missingRef)); - } - - - function errorSubclass(Subclass) { - Subclass.prototype = Object.create(Error.prototype); - Subclass.prototype.constructor = Subclass; - return Subclass; - } - - }, { "./resolve": 9 }], - 7: [function (require, module, exports) { - 'use strict'; - - var util = require('./util'); - - var DATE = /^\d\d\d\d-(\d\d)-(\d\d)$/; - var DAYS = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - var TIME = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d:\d\d)?$/i; - var HOSTNAME = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*$/i; - var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9a-f]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'()*+,;=:@\/?]|%[0-9a-f]{2})*)?$/i; - var URIREF = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@\/?]|%[0-9a-f]{2})*)?(?:\#(?:[a-z0-9\-._~!$&'"()*+,;=:@\/?]|%[0-9a-f]{2})*)?$/i; - // uri-template: https://tools.ietf.org/html/rfc6570 - var URITEMPLATE = /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#.\/;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\:[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?:\:[1-9][0-9]{0,3}|\*)?)*\})*$/i; - // For the source: https://gist.github.com/dperini/729294 - // For test cases: https://mathiasbynens.be/demo/url-regex - // @todo Delete current URL in favour of the commented out URL rule when this issue is fixed https://github.com/eslint/eslint/issues/7983. - // var URL = /^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)(?:\.(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu; - var URL = /^(?:(?:http[s\u017F]?|ftp):\/\/)(?:(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+(?::(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*)?@)?(?:(?!10(?:\.[0-9]{1,3}){3})(?!127(?:\.[0-9]{1,3}){3})(?!169\.254(?:\.[0-9]{1,3}){2})(?!192\.168(?:\.[0-9]{1,3}){2})(?!172\.(?:1[6-9]|2[0-9]|3[01])(?:\.[0-9]{1,3}){2})(?:[1-9][0-9]?|1[0-9][0-9]|2[01][0-9]|22[0-3])(?:\.(?:1?[0-9]{1,2}|2[0-4][0-9]|25[0-5])){2}(?:\.(?:[1-9][0-9]?|1[0-9][0-9]|2[0-4][0-9]|25[0-4]))|(?:(?:(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+\-?)*(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+)(?:\.(?:(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+\-?)*(?:[0-9KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])+)*(?:\.(?:(?:[KSa-z\xA1-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]){2,})))(?::[0-9]{2,5})?(?:\/(?:[\0-\x08\x0E-\x1F!-\x9F\xA1-\u167F\u1681-\u1FFF\u200B-\u2027\u202A-\u202E\u2030-\u205E\u2060-\u2FFF\u3001-\uD7FF\uE000-\uFEFE\uFF00-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF])*)?$/i; - var UUID = /^(?:urn\:uuid\:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i; - var JSON_POINTER = /^(?:\/(?:[^~\/]|~0|~1)*)*$|^\#(?:\/(?:[a-z0-9_\-\.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i; - var RELATIVE_JSON_POINTER = /^(?:0|[1-9][0-9]*)(?:\#|(?:\/(?:[^~\/]|~0|~1)*)*)$/; - - - module.exports = formats; - - function formats(mode) { - mode = mode == 'full' ? 'full' : 'fast'; - return util.copy(formats[mode]); - } - - - formats.fast = { - // date: http://tools.ietf.org/html/rfc3339#section-5.6 - date: /^\d\d\d\d-[0-1]\d-[0-3]\d$/, - // date-time: http://tools.ietf.org/html/rfc3339#section-5.6 - time: /^[0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:z|[+-]\d\d:\d\d)?$/i, - 'date-time': /^\d\d\d\d-[0-1]\d-[0-3]\d[t\s][0-2]\d:[0-5]\d:[0-5]\d(?:\.\d+)?(?:z|[+-]\d\d:\d\d)$/i, - // uri: https://github.com/mafintosh/is-my-json-valid/blob/master/formats.js - uri: /^(?:[a-z][a-z0-9+-.]*)(?:\:|\/)\/?[^\s]*$/i, - 'uri-reference': /^(?:(?:[a-z][a-z0-9+-.]*:)?\/\/)?[^\s]*$/i, - 'uri-template': URITEMPLATE, - url: URL, - // email (sources from jsen validator): - // http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363 - // http://www.w3.org/TR/html5/forms.html#valid-e-mail-address (search for 'willful violation') - email: /^[a-z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i, - hostname: HOSTNAME, - // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html - ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/, - // optimized http://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses - ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?\s*$/i, - regex: regex, - // uuid: http://tools.ietf.org/html/rfc4122 - uuid: UUID, - // JSON-pointer: https://tools.ietf.org/html/rfc6901 - // uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A - 'json-pointer': JSON_POINTER, - // relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00 - 'relative-json-pointer': RELATIVE_JSON_POINTER - }; - - - formats.full = { - date: date, - time: time, - 'date-time': date_time, - uri: uri, - 'uri-reference': URIREF, - 'uri-template': URITEMPLATE, - url: URL, - email: /^[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&''*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i, - hostname: hostname, - ipv4: /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/, - ipv6: /^\s*(?:(?:(?:[0-9a-f]{1,4}:){7}(?:[0-9a-f]{1,4}|:))|(?:(?:[0-9a-f]{1,4}:){6}(?::[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){5}(?:(?:(?::[0-9a-f]{1,4}){1,2})|:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(?:(?:[0-9a-f]{1,4}:){4}(?:(?:(?::[0-9a-f]{1,4}){1,3})|(?:(?::[0-9a-f]{1,4})?:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){3}(?:(?:(?::[0-9a-f]{1,4}){1,4})|(?:(?::[0-9a-f]{1,4}){0,2}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){2}(?:(?:(?::[0-9a-f]{1,4}){1,5})|(?:(?::[0-9a-f]{1,4}){0,3}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?:(?:[0-9a-f]{1,4}:){1}(?:(?:(?::[0-9a-f]{1,4}){1,6})|(?:(?::[0-9a-f]{1,4}){0,4}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(?::(?:(?:(?::[0-9a-f]{1,4}){1,7})|(?:(?::[0-9a-f]{1,4}){0,5}:(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(?:%.+)?\s*$/i, - regex: regex, - uuid: UUID, - 'json-pointer': JSON_POINTER, - 'relative-json-pointer': RELATIVE_JSON_POINTER - }; - - - function date(str) { - // full-date from http://tools.ietf.org/html/rfc3339#section-5.6 - var matches = str.match(DATE); - if (!matches) return false; - - var month = +matches[1]; - var day = +matches[2]; - return month >= 1 && month <= 12 && day >= 1 && day <= DAYS[month]; - } - - - function time(str, full) { - var matches = str.match(TIME); - if (!matches) return false; - - var hour = matches[1]; - var minute = matches[2]; - var second = matches[3]; - var timeZone = matches[5]; - return hour <= 23 && minute <= 59 && second <= 59 && (!full || timeZone); - } - - - var DATE_TIME_SEPARATOR = /t|\s/i; - - function date_time(str) { - // http://tools.ietf.org/html/rfc3339#section-5.6 - var dateTime = str.split(DATE_TIME_SEPARATOR); - return dateTime.length == 2 && date(dateTime[0]) && time(dateTime[1], true); - } - - - function hostname(str) { - // https://tools.ietf.org/html/rfc1034#section-3.5 - // https://tools.ietf.org/html/rfc1123#section-2 - return str.length <= 255 && HOSTNAME.test(str); - } - - - var NOT_URI_FRAGMENT = /\/|\:/; - - function uri(str) { - // http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "." - return NOT_URI_FRAGMENT.test(str) && URI.test(str); - } - - - var Z_ANCHOR = /[^\\]\\Z/; - - function regex(str) { - if (Z_ANCHOR.test(str)) return false; - try { - new RegExp(str); - return true; - } catch (e) { - return false; - } - } - - }, { "./util": 13 }], - 8: [function (require, module, exports) { - 'use strict'; - - var resolve = require('./resolve'), - util = require('./util'), - errorClasses = require('./error_classes'), - stableStringify = require('json-stable-stringify'); - - var validateGenerator = require('../dotjs/validate'); - - /** - * Functions below are used inside compiled validations function - */ - - var co = require('co'); - var ucs2length = util.ucs2length; - var equal = require('./equal'); - - // this error is thrown by async schemas to return validation errors via exception - var ValidationError = errorClasses.Validation; - - module.exports = compile; - - - /** - * Compiles schema to validation function - * @this Ajv - * @param {Object} schema schema object - * @param {Object} root object with information about the root schema for this schema - * @param {Object} localRefs the hash of local references inside the schema (created by resolve.id), used for inline resolution - * @param {String} baseId base ID for IDs in the schema - * @return {Function} validation function - */ - function compile(schema, root, localRefs, baseId) { - /* jshint validthis: true, evil: true */ - /* eslint no-shadow: 0 */ - var self = this, - opts = this._opts, - refVal = [undefined], - refs = {}, - patterns = [], - patternsHash = {}, - defaults = [], - defaultsHash = {}, - customRules = []; - - root = root || { schema: schema, refVal: refVal, refs: refs }; - - var c = checkCompiling.call(this, schema, root, baseId); - var compilation = this._compilations[c.index]; - if (c.compiling) return (compilation.callValidate = callValidate); - - var formats = this._formats; - var RULES = this.RULES; - - try { - var v = localCompile(schema, root, localRefs, baseId); - compilation.validate = v; - var cv = compilation.callValidate; - if (cv) { - cv.schema = v.schema; - cv.errors = null; - cv.refs = v.refs; - cv.refVal = v.refVal; - cv.root = v.root; - cv.$async = v.$async; - if (opts.sourceCode) cv.source = v.source; - } - return v; - } finally { - endCompiling.call(this, schema, root, baseId); - } - - function callValidate() { - var validate = compilation.validate; - var result = validate.apply(null, arguments); - callValidate.errors = validate.errors; - return result; - } - - function localCompile(_schema, _root, localRefs, baseId) { - var isRoot = !_root || (_root && _root.schema == _schema); - if (_root.schema != root.schema) - return compile.call(self, _schema, _root, localRefs, baseId); - - var $async = _schema.$async === true; - - var sourceCode = validateGenerator({ - isTop: true, - schema: _schema, - isRoot: isRoot, - baseId: baseId, - root: _root, - schemaPath: '', - errSchemaPath: '#', - errorPath: '""', - MissingRefError: errorClasses.MissingRef, - RULES: RULES, - validate: validateGenerator, - util: util, - resolve: resolve, - resolveRef: resolveRef, - usePattern: usePattern, - useDefault: useDefault, - useCustomRule: useCustomRule, - opts: opts, - formats: formats, - self: self - }); - - sourceCode = vars(refVal, refValCode) + vars(patterns, patternCode) + - vars(defaults, defaultCode) + vars(customRules, customRuleCode) + - sourceCode; - - if (opts.processCode) sourceCode = opts.processCode(sourceCode); - var validate; - try { - var makeValidate = new Function( - 'self', - 'RULES', - 'formats', - 'root', - 'refVal', - 'defaults', - 'customRules', - 'co', - 'equal', - 'ucs2length', - 'ValidationError', - sourceCode - ); - - validate = makeValidate( - self, - RULES, - formats, - root, - refVal, - defaults, - customRules, - co, - equal, - ucs2length, - ValidationError - ); - - refVal[0] = validate; - } catch (e) { - console.error('Error compiling schema, function code:', sourceCode); - throw e; - } - - validate.schema = _schema; - validate.errors = null; - validate.refs = refs; - validate.refVal = refVal; - validate.root = isRoot ? validate : _root; - if ($async) validate.$async = true; - if (opts.sourceCode === true) { - validate.source = { - code: sourceCode, - patterns: patterns, - defaults: defaults - }; - } - - return validate; - } - - function resolveRef(baseId, ref, isRoot) { - ref = resolve.url(baseId, ref); - var refIndex = refs[ref]; - var _refVal, refCode; - if (refIndex !== undefined) { - _refVal = refVal[refIndex]; - refCode = 'refVal[' + refIndex + ']'; - return resolvedRef(_refVal, refCode); - } - if (!isRoot && root.refs) { - var rootRefId = root.refs[ref]; - if (rootRefId !== undefined) { - _refVal = root.refVal[rootRefId]; - refCode = addLocalRef(ref, _refVal); - return resolvedRef(_refVal, refCode); - } - } - - refCode = addLocalRef(ref); - var v = resolve.call(self, localCompile, root, ref); - if (v === undefined) { - var localSchema = localRefs && localRefs[ref]; - if (localSchema) { - v = resolve.inlineRef(localSchema, opts.inlineRefs) ? - localSchema : - compile.call(self, localSchema, root, localRefs, baseId); - } - } - - if (v !== undefined) { - replaceLocalRef(ref, v); - return resolvedRef(v, refCode); - } - } - - function addLocalRef(ref, v) { - var refId = refVal.length; - refVal[refId] = v; - refs[ref] = refId; - return 'refVal' + refId; - } - - function replaceLocalRef(ref, v) { - var refId = refs[ref]; - refVal[refId] = v; - } - - function resolvedRef(refVal, code) { - return typeof refVal == 'object' || typeof refVal == 'boolean' ? - { code: code, schema: refVal, inline: true } : - { code: code, $async: refVal && refVal.$async }; - } - - function usePattern(regexStr) { - var index = patternsHash[regexStr]; - if (index === undefined) { - index = patternsHash[regexStr] = patterns.length; - patterns[index] = regexStr; - } - return 'pattern' + index; - } - - function useDefault(value) { - switch (typeof value) { - case 'boolean': - case 'number': - return '' + value; - case 'string': - return util.toQuotedString(value); - case 'object': - if (value === null) return 'null'; - var valueStr = stableStringify(value); - var index = defaultsHash[valueStr]; - if (index === undefined) { - index = defaultsHash[valueStr] = defaults.length; - defaults[index] = value; - } - return 'default' + index; - } - } - - function useCustomRule(rule, schema, parentSchema, it) { - var validateSchema = rule.definition.validateSchema; - if (validateSchema && self._opts.validateSchema !== false) { - var valid = validateSchema(schema); - if (!valid) { - var message = 'keyword schema is invalid: ' + self.errorsText(validateSchema.errors); - if (self._opts.validateSchema == 'log') console.error(message); - else throw new Error(message); - } - } - - var compile = rule.definition.compile, - inline = rule.definition.inline, - macro = rule.definition.macro; - - var validate; - if (compile) { - validate = compile.call(self, schema, parentSchema, it); - } else if (macro) { - validate = macro.call(self, schema, parentSchema, it); - if (opts.validateSchema !== false) self.validateSchema(validate, true); - } else if (inline) { - validate = inline.call(self, it, rule.keyword, schema, parentSchema); - } else { - validate = rule.definition.validate; - if (!validate) return; - } - - if (validate === undefined) - throw new Error('custom keyword "' + rule.keyword + '"failed to compile'); - - var index = customRules.length; - customRules[index] = validate; - - return { - code: 'customRule' + index, - validate: validate - }; - } - } - - - /** - * Checks if the schema is currently compiled - * @this Ajv - * @param {Object} schema schema to compile - * @param {Object} root root object - * @param {String} baseId base schema ID - * @return {Object} object with properties "index" (compilation index) and "compiling" (boolean) - */ - function checkCompiling(schema, root, baseId) { - /* jshint validthis: true */ - var index = compIndex.call(this, schema, root, baseId); - if (index >= 0) return { index: index, compiling: true }; - index = this._compilations.length; - this._compilations[index] = { - schema: schema, - root: root, - baseId: baseId - }; - return { index: index, compiling: false }; - } - - - /** - * Removes the schema from the currently compiled list - * @this Ajv - * @param {Object} schema schema to compile - * @param {Object} root root object - * @param {String} baseId base schema ID - */ - function endCompiling(schema, root, baseId) { - /* jshint validthis: true */ - var i = compIndex.call(this, schema, root, baseId); - if (i >= 0) this._compilations.splice(i, 1); - } - - - /** - * Index of schema compilation in the currently compiled list - * @this Ajv - * @param {Object} schema schema to compile - * @param {Object} root root object - * @param {String} baseId base schema ID - * @return {Integer} compilation index - */ - function compIndex(schema, root, baseId) { - /* jshint validthis: true */ - for (var i = 0; i < this._compilations.length; i++) { - var c = this._compilations[i]; - if (c.schema == schema && c.root == root && c.baseId == baseId) return i; - } - return -1; - } - - - function patternCode(i, patterns) { - return 'var pattern' + i + ' = new RegExp(' + util.toQuotedString(patterns[i]) + ');'; - } - - - function defaultCode(i) { - return 'var default' + i + ' = defaults[' + i + '];'; - } - - - function refValCode(i, refVal) { - return refVal[i] === undefined ? '' : 'var refVal' + i + ' = refVal[' + i + '];'; - } - - - function customRuleCode(i) { - return 'var customRule' + i + ' = customRules[' + i + '];'; - } - - - function vars(arr, statement) { - if (!arr.length) return ''; - var code = ''; - for (var i = 0; i < arr.length; i++) - code += statement(i, arr); - return code; - } - - }, { "../dotjs/validate": 36, "./equal": 5, "./error_classes": 6, "./resolve": 9, "./util": 13, "co": 41, "json-stable-stringify": 42 }], - 9: [function (require, module, exports) { - 'use strict'; - - var url = require('url'), - equal = require('./equal'), - util = require('./util'), - SchemaObject = require('./schema_obj'); - - module.exports = resolve; - - resolve.normalizeId = normalizeId; - resolve.fullPath = getFullPath; - resolve.url = resolveUrl; - resolve.ids = resolveIds; - resolve.inlineRef = inlineRef; - resolve.schema = resolveSchema; - - /** - * [resolve and compile the references ($ref)] - * @this Ajv - * @param {Function} compile reference to schema compilation funciton (localCompile) - * @param {Object} root object with information about the root schema for the current schema - * @param {String} ref reference to resolve - * @return {Object|Function} schema object (if the schema can be inlined) or validation function - */ - function resolve(compile, root, ref) { - /* jshint validthis: true */ - var refVal = this._refs[ref]; - if (typeof refVal == 'string') { - if (this._refs[refVal]) refVal = this._refs[refVal]; - else return resolve.call(this, compile, root, refVal); - } - - refVal = refVal || this._schemas[ref]; - if (refVal instanceof SchemaObject) { - return inlineRef(refVal.schema, this._opts.inlineRefs) ? - refVal.schema : - refVal.validate || this._compile(refVal); - } - - var res = resolveSchema.call(this, root, ref); - var schema, v, baseId; - if (res) { - schema = res.schema; - root = res.root; - baseId = res.baseId; - } - - if (schema instanceof SchemaObject) { - v = schema.validate || compile.call(this, schema.schema, root, undefined, baseId); - } else if (schema !== undefined) { - v = inlineRef(schema, this._opts.inlineRefs) ? - schema : - compile.call(this, schema, root, undefined, baseId); - } - - return v; - } - - - /** - * Resolve schema, its root and baseId - * @this Ajv - * @param {Object} root root object with properties schema, refVal, refs - * @param {String} ref reference to resolve - * @return {Object} object with properties schema, root, baseId - */ - function resolveSchema(root, ref) { - /* jshint validthis: true */ - var p = url.parse(ref, false, true), - refPath = _getFullPath(p), - baseId = getFullPath(this._getId(root.schema)); - if (refPath !== baseId) { - var id = normalizeId(refPath); - var refVal = this._refs[id]; - if (typeof refVal == 'string') { - return resolveRecursive.call(this, root, refVal, p); - } else if (refVal instanceof SchemaObject) { - if (!refVal.validate) this._compile(refVal); - root = refVal; - } else { - refVal = this._schemas[id]; - if (refVal instanceof SchemaObject) { - if (!refVal.validate) this._compile(refVal); - if (id == normalizeId(ref)) - return { schema: refVal, root: root, baseId: baseId }; - root = refVal; - } else { - return; - } - } - if (!root.schema) return; - baseId = getFullPath(this._getId(root.schema)); - } - return getJsonPointer.call(this, p, baseId, root.schema, root); - } - - - /* @this Ajv */ - function resolveRecursive(root, ref, parsedRef) { - /* jshint validthis: true */ - var res = resolveSchema.call(this, root, ref); - if (res) { - var schema = res.schema; - var baseId = res.baseId; - root = res.root; - var id = this._getId(schema); - if (id) baseId = resolveUrl(baseId, id); - return getJsonPointer.call(this, parsedRef, baseId, schema, root); - } - } - - - var PREVENT_SCOPE_CHANGE = util.toHash(['properties', 'patternProperties', 'enum', 'dependencies', 'definitions']); - /* @this Ajv */ - function getJsonPointer(parsedRef, baseId, schema, root) { - /* jshint validthis: true */ - parsedRef.hash = parsedRef.hash || ''; - if (parsedRef.hash.slice(0, 2) != '#/') return; - var parts = parsedRef.hash.split('/'); - - for (var i = 1; i < parts.length; i++) { - var part = parts[i]; - if (part) { - part = util.unescapeFragment(part); - schema = schema[part]; - if (schema === undefined) break; - var id; - if (!PREVENT_SCOPE_CHANGE[part]) { - id = this._getId(schema); - if (id) baseId = resolveUrl(baseId, id); - if (schema.$ref) { - var $ref = resolveUrl(baseId, schema.$ref); - var res = resolveSchema.call(this, root, $ref); - if (res) { - schema = res.schema; - root = res.root; - baseId = res.baseId; - } - } - } - } - } - if (schema !== undefined && schema !== root.schema) - return { schema: schema, root: root, baseId: baseId }; - } - - - var SIMPLE_INLINED = util.toHash([ - 'type', 'format', 'pattern', - 'maxLength', 'minLength', - 'maxProperties', 'minProperties', - 'maxItems', 'minItems', - 'maximum', 'minimum', - 'uniqueItems', 'multipleOf', - 'required', 'enum' - ]); - - function inlineRef(schema, limit) { - if (limit === false) return false; - if (limit === undefined || limit === true) return checkNoRef(schema); - else if (limit) return countKeys(schema) <= limit; - } - - - function checkNoRef(schema) { - var item; - if (Array.isArray(schema)) { - for (var i = 0; i < schema.length; i++) { - item = schema[i]; - if (typeof item == 'object' && !checkNoRef(item)) return false; - } - } else { - for (var key in schema) { - if (key == '$ref') return false; - item = schema[key]; - if (typeof item == 'object' && !checkNoRef(item)) return false; - } - } - return true; - } - - - function countKeys(schema) { - var count = 0, - item; - if (Array.isArray(schema)) { - for (var i = 0; i < schema.length; i++) { - item = schema[i]; - if (typeof item == 'object') count += countKeys(item); - if (count == Infinity) return Infinity; - } - } else { - for (var key in schema) { - if (key == '$ref') return Infinity; - if (SIMPLE_INLINED[key]) { - count++; - } else { - item = schema[key]; - if (typeof item == 'object') count += countKeys(item) + 1; - if (count == Infinity) return Infinity; - } - } - } - return count; - } - - - function getFullPath(id, normalize) { - if (normalize !== false) id = normalizeId(id); - var p = url.parse(id, false, true); - return _getFullPath(p); - } - - - function _getFullPath(p) { - var protocolSeparator = p.protocol || p.href.slice(0, 2) == '//' ? '//' : ''; - return (p.protocol || '') + protocolSeparator + (p.host || '') + (p.path || '') + '#'; - } - - - var TRAILING_SLASH_HASH = /#\/?$/; - - function normalizeId(id) { - return id ? id.replace(TRAILING_SLASH_HASH, '') : ''; - } - - - function resolveUrl(baseId, id) { - id = normalizeId(id); - return url.resolve(baseId, id); - } - - - /* @this Ajv */ - function resolveIds(schema) { - /* eslint no-shadow: 0 */ - /* jshint validthis: true */ - var id = normalizeId(this._getId(schema)); - var localRefs = {}; - _resolveIds.call(this, schema, getFullPath(id, false), id); - return localRefs; - - /* @this Ajv */ - function _resolveIds(schema, fullPath, baseId) { - /* jshint validthis: true */ - if (Array.isArray(schema)) { - for (var i = 0; i < schema.length; i++) - _resolveIds.call(this, schema[i], fullPath + '/' + i, baseId); - } else if (schema && typeof schema == 'object') { - var id = this._getId(schema); - if (typeof id == 'string') { - id = baseId = normalizeId(baseId ? url.resolve(baseId, id) : id); - - var refVal = this._refs[id]; - if (typeof refVal == 'string') refVal = this._refs[refVal]; - if (refVal && refVal.schema) { - if (!equal(schema, refVal.schema)) - throw new Error('id "' + id + '" resolves to more than one schema'); - } else if (id != normalizeId(fullPath)) { - if (id[0] == '#') { - if (localRefs[id] && !equal(schema, localRefs[id])) - throw new Error('id "' + id + '" resolves to more than one schema'); - localRefs[id] = schema; - } else { - this._refs[id] = fullPath; - } - } - } - for (var key in schema) - _resolveIds.call(this, schema[key], fullPath + '/' + util.escapeFragment(key), baseId); - } - } - } - - }, { "./equal": 5, "./schema_obj": 11, "./util": 13, "url": 50 }], - 10: [function (require, module, exports) { - 'use strict'; - - var ruleModules = require('./_rules'), - toHash = require('./util').toHash; - - module.exports = function rules() { - var RULES = [{ - type: 'number', - rules: [{ 'maximum': ['exclusiveMaximum'] }, - { 'minimum': ['exclusiveMinimum'] }, 'multipleOf', 'format' - ] - }, - { - type: 'string', - rules: ['maxLength', 'minLength', 'pattern', 'format'] - }, - { - type: 'array', - rules: ['maxItems', 'minItems', 'uniqueItems', 'contains', 'items'] - }, - { - type: 'object', - rules: ['maxProperties', 'minProperties', 'required', 'dependencies', 'propertyNames', - { 'properties': ['additionalProperties', 'patternProperties'] } - ] - }, - { rules: ['$ref', 'const', 'enum', 'not', 'anyOf', 'oneOf', 'allOf'] } - ]; - - var ALL = ['type']; - var KEYWORDS = [ - 'additionalItems', '$schema', 'id', 'title', - 'description', 'default', 'definitions' - ]; - var TYPES = ['number', 'integer', 'string', 'array', 'object', 'boolean', 'null']; - RULES.all = toHash(ALL); - RULES.types = toHash(TYPES); - - RULES.forEach(function (group) { - group.rules = group.rules.map(function (keyword) { - var implKeywords; - if (typeof keyword == 'object') { - var key = Object.keys(keyword)[0]; - implKeywords = keyword[key]; - keyword = key; - implKeywords.forEach(function (k) { - ALL.push(k); - RULES.all[k] = true; - }); - } - ALL.push(keyword); - var rule = RULES.all[keyword] = { - keyword: keyword, - code: ruleModules[keyword], - implements: implKeywords - }; - return rule; - }); - - if (group.type) RULES.types[group.type] = group; - }); - - RULES.keywords = toHash(ALL.concat(KEYWORDS)); - RULES.custom = {}; - - return RULES; - }; - - }, { "./_rules": 3, "./util": 13 }], - 11: [function (require, module, exports) { - 'use strict'; - - var util = require('./util'); - - module.exports = SchemaObject; - - function SchemaObject(obj) { - util.copy(obj, this); - } - - }, { "./util": 13 }], - 12: [function (require, module, exports) { - 'use strict'; - - // https://mathiasbynens.be/notes/javascript-encoding - // https://github.com/bestiejs/punycode.js - punycode.ucs2.decode - module.exports = function ucs2length(str) { - var length = 0, - len = str.length, - pos = 0, - value; - while (pos < len) { - length++; - value = str.charCodeAt(pos++); - if (value >= 0xD800 && value <= 0xDBFF && pos < len) { - // high surrogate, and there is a next character - value = str.charCodeAt(pos); - if ((value & 0xFC00) == 0xDC00) pos++; // low surrogate - } - } - return length; - }; - - }, {}], - 13: [function (require, module, exports) { - 'use strict'; - - - module.exports = { - copy: copy, - checkDataType: checkDataType, - checkDataTypes: checkDataTypes, - coerceToTypes: coerceToTypes, - toHash: toHash, - getProperty: getProperty, - escapeQuotes: escapeQuotes, - equal: require('./equal'), - ucs2length: require('./ucs2length'), - varOccurences: varOccurences, - varReplace: varReplace, - cleanUpCode: cleanUpCode, - finalCleanUpCode: finalCleanUpCode, - schemaHasRules: schemaHasRules, - schemaHasRulesExcept: schemaHasRulesExcept, - toQuotedString: toQuotedString, - getPathExpr: getPathExpr, - getPath: getPath, - getData: getData, - unescapeFragment: unescapeFragment, - escapeFragment: escapeFragment, - escapeJsonPointer: escapeJsonPointer - }; - - - function copy(o, to) { - to = to || {}; - for (var key in o) to[key] = o[key]; - return to; - } - - - function checkDataType(dataType, data, negate) { - var EQUAL = negate ? ' !== ' : ' === ', - AND = negate ? ' || ' : ' && ', - OK = negate ? '!' : '', - NOT = negate ? '' : '!'; - switch (dataType) { - case 'null': - return data + EQUAL + 'null'; - case 'array': - return OK + 'Array.isArray(' + data + ')'; - case 'object': - return '(' + OK + data + AND + - 'typeof ' + data + EQUAL + '"object"' + AND + - NOT + 'Array.isArray(' + data + '))'; - case 'integer': - return '(typeof ' + data + EQUAL + '"number"' + AND + - NOT + '(' + data + ' % 1)' + - AND + data + EQUAL + data + ')'; - default: - return 'typeof ' + data + EQUAL + '"' + dataType + '"'; - } - } - - - function checkDataTypes(dataTypes, data) { - switch (dataTypes.length) { - case 1: - return checkDataType(dataTypes[0], data, true); - default: - var code = ''; - var types = toHash(dataTypes); - if (types.array && types.object) { - code = types.null ? '(' : '(!' + data + ' || '; - code += 'typeof ' + data + ' !== "object")'; - delete types.null; - delete types.array; - delete types.object; - } - if (types.number) delete types.integer; - for (var t in types) - code += (code ? ' && ' : '') + checkDataType(t, data, true); - - return code; - } - } - - - var COERCE_TO_TYPES = toHash(['string', 'number', 'integer', 'boolean', 'null']); - - function coerceToTypes(optionCoerceTypes, dataTypes) { - if (Array.isArray(dataTypes)) { - var types = []; - for (var i = 0; i < dataTypes.length; i++) { - var t = dataTypes[i]; - if (COERCE_TO_TYPES[t]) types[types.length] = t; - else if (optionCoerceTypes === 'array' && t === 'array') types[types.length] = t; - } - if (types.length) return types; - } else if (COERCE_TO_TYPES[dataTypes]) { - return [dataTypes]; - } else if (optionCoerceTypes === 'array' && dataTypes === 'array') { - return ['array']; - } - } - - - function toHash(arr) { - var hash = {}; - for (var i = 0; i < arr.length; i++) hash[arr[i]] = true; - return hash; - } - - - var IDENTIFIER = /^[a-z$_][a-z$_0-9]*$/i; - var SINGLE_QUOTE = /'|\\/g; - - function getProperty(key) { - return typeof key == 'number' ? - '[' + key + ']' : - IDENTIFIER.test(key) ? - '.' + key : - "['" + escapeQuotes(key) + "']"; - } - - - function escapeQuotes(str) { - return str.replace(SINGLE_QUOTE, '\\$&') - .replace(/\n/g, '\\n') - .replace(/\r/g, '\\r') - .replace(/\f/g, '\\f') - .replace(/\t/g, '\\t'); - } - - - function varOccurences(str, dataVar) { - dataVar += '[^0-9]'; - var matches = str.match(new RegExp(dataVar, 'g')); - return matches ? matches.length : 0; - } - - - function varReplace(str, dataVar, expr) { - dataVar += '([^0-9])'; - expr = expr.replace(/\$/g, '$$$$'); - return str.replace(new RegExp(dataVar, 'g'), expr + '$1'); - } - - - var EMPTY_ELSE = /else\s*{\s*}/g, - EMPTY_IF_NO_ELSE = /if\s*\([^)]+\)\s*\{\s*\}(?!\s*else)/g, - EMPTY_IF_WITH_ELSE = /if\s*\(([^)]+)\)\s*\{\s*\}\s*else(?!\s*if)/g; - - function cleanUpCode(out) { - return out.replace(EMPTY_ELSE, '') - .replace(EMPTY_IF_NO_ELSE, '') - .replace(EMPTY_IF_WITH_ELSE, 'if (!($1))'); - } - - - var ERRORS_REGEXP = /[^v\.]errors/g, - REMOVE_ERRORS = /var errors = 0;|var vErrors = null;|validate.errors = vErrors;/g, - REMOVE_ERRORS_ASYNC = /var errors = 0;|var vErrors = null;/g, - RETURN_VALID = 'return errors === 0;', - RETURN_TRUE = 'validate.errors = null; return true;', - RETURN_ASYNC = /if \(errors === 0\) return data;\s*else throw new ValidationError\(vErrors\);/, - RETURN_DATA_ASYNC = 'return data;', - ROOTDATA_REGEXP = /[^A-Za-z_$]rootData[^A-Za-z0-9_$]/g, - REMOVE_ROOTDATA = /if \(rootData === undefined\) rootData = data;/; - - function finalCleanUpCode(out, async) { - var matches = out.match(ERRORS_REGEXP); - if (matches && matches.length == 2) { - out = async ? - out.replace(REMOVE_ERRORS_ASYNC, '') - .replace(RETURN_ASYNC, RETURN_DATA_ASYNC) : - out.replace(REMOVE_ERRORS, '') - .replace(RETURN_VALID, RETURN_TRUE); - } - - matches = out.match(ROOTDATA_REGEXP); - if (!matches || matches.length !== 3) return out; - return out.replace(REMOVE_ROOTDATA, ''); - } - - - function schemaHasRules(schema, rules) { - if (typeof schema == 'boolean') return !schema; - for (var key in schema) - if (rules[key]) return true; - } - - - function schemaHasRulesExcept(schema, rules, exceptKeyword) { - if (typeof schema == 'boolean') return !schema && exceptKeyword != 'not'; - for (var key in schema) - if (key != exceptKeyword && rules[key]) return true; - } - - - function toQuotedString(str) { - return '\'' + escapeQuotes(str) + '\''; - } - - - function getPathExpr(currentPath, expr, jsonPointers, isNumber) { - var path = jsonPointers // false by default - ? - '\'/\' + ' + expr + (isNumber ? '' : '.replace(/~/g, \'~0\').replace(/\\//g, \'~1\')') : - (isNumber ? '\'[\' + ' + expr + ' + \']\'' : '\'[\\\'\' + ' + expr + ' + \'\\\']\''); - return joinPaths(currentPath, path); - } - - - function getPath(currentPath, prop, jsonPointers) { - var path = jsonPointers // false by default - ? - toQuotedString('/' + escapeJsonPointer(prop)) : - toQuotedString(getProperty(prop)); - return joinPaths(currentPath, path); - } - - - var JSON_POINTER = /^\/(?:[^~]|~0|~1)*$/; - var RELATIVE_JSON_POINTER = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/; - - function getData($data, lvl, paths) { - var up, jsonPointer, data, matches; - if ($data === '') return 'rootData'; - if ($data[0] == '/') { - if (!JSON_POINTER.test($data)) throw new Error('Invalid JSON-pointer: ' + $data); - jsonPointer = $data; - data = 'rootData'; - } else { - matches = $data.match(RELATIVE_JSON_POINTER); - if (!matches) throw new Error('Invalid JSON-pointer: ' + $data); - up = +matches[1]; - jsonPointer = matches[2]; - if (jsonPointer == '#') { - if (up >= lvl) throw new Error('Cannot access property/index ' + up + ' levels up, current level is ' + lvl); - return paths[lvl - up]; - } - - if (up > lvl) throw new Error('Cannot access data ' + up + ' levels up, current level is ' + lvl); - data = 'data' + ((lvl - up) || ''); - if (!jsonPointer) return data; - } - - var expr = data; - var segments = jsonPointer.split('/'); - for (var i = 0; i < segments.length; i++) { - var segment = segments[i]; - if (segment) { - data += getProperty(unescapeJsonPointer(segment)); - expr += ' && ' + data; - } - } - return expr; - } - - - function joinPaths(a, b) { - if (a == '""') return b; - return (a + ' + ' + b).replace(/' \+ '/g, ''); - } - - - function unescapeFragment(str) { - return unescapeJsonPointer(decodeURIComponent(str)); - } - - - function escapeFragment(str) { - return encodeURIComponent(escapeJsonPointer(str)); - } - - - function escapeJsonPointer(str) { - return str.replace(/~/g, '~0').replace(/\//g, '~1'); - } - - - function unescapeJsonPointer(str) { - return str.replace(/~1/g, '/').replace(/~0/g, '~'); - } - - }, { "./equal": 5, "./ucs2length": 12 }], - 14: [function (require, module, exports) { - 'use strict'; - module.exports = function generate__limit(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $errorKeyword; - var $data = 'data' + ($dataLvl || ''); - var $isData = it.opts.$data && $schema && $schema.$data, - $schemaValue; - if ($isData) { - out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; - $schemaValue = 'schema' + $lvl; - } else { - $schemaValue = $schema; - } - var $isMax = $keyword == 'maximum', - $exclusiveKeyword = $isMax ? 'exclusiveMaximum' : 'exclusiveMinimum', - $schemaExcl = it.schema[$exclusiveKeyword], - $isDataExcl = it.opts.$data && $schemaExcl && $schemaExcl.$data, - $op = $isMax ? '<' : '>', - $notOp = $isMax ? '>' : '<', - $errorKeyword = undefined; - if ($isDataExcl) { - var $schemaValueExcl = it.util.getData($schemaExcl.$data, $dataLvl, it.dataPathArr), - $exclusive = 'exclusive' + $lvl, - $exclType = 'exclType' + $lvl, - $exclIsNumber = 'exclIsNumber' + $lvl, - $opExpr = 'op' + $lvl, - $opStr = '\' + ' + $opExpr + ' + \''; - out += ' var schemaExcl' + ($lvl) + ' = ' + ($schemaValueExcl) + '; '; - $schemaValueExcl = 'schemaExcl' + $lvl; - out += ' var ' + ($exclusive) + '; var ' + ($exclType) + ' = typeof ' + ($schemaValueExcl) + '; if (' + ($exclType) + ' != \'boolean\' && ' + ($exclType) + ' != \'undefined\' && ' + ($exclType) + ' != \'number\') { '; - var $errorKeyword = $exclusiveKeyword; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || '_exclusiveLimit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; - if (it.opts.messages !== false) { - out += ' , message: \'' + ($exclusiveKeyword) + ' should be boolean\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } else if ( '; - if ($isData) { - out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; - } - out += ' ' + ($exclType) + ' == \'number\' ? ( (' + ($exclusive) + ' = ' + ($schemaValue) + ' === undefined || ' + ($schemaValueExcl) + ' ' + ($op) + '= ' + ($schemaValue) + ') ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaValueExcl) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' ) : ( (' + ($exclusive) + ' = ' + ($schemaValueExcl) + ' === true) ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaValue) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' ) || ' + ($data) + ' !== ' + ($data) + ') { var op' + ($lvl) + ' = ' + ($exclusive) + ' ? \'' + ($op) + '\' : \'' + ($op) + '=\';'; - } else { - var $exclIsNumber = typeof $schemaExcl == 'number', - $opStr = $op; - if ($exclIsNumber && $isData) { - var $opExpr = '\'' + $opStr + '\''; - out += ' if ( '; - if ($isData) { - out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; - } - out += ' ( ' + ($schemaValue) + ' === undefined || ' + ($schemaExcl) + ' ' + ($op) + '= ' + ($schemaValue) + ' ? ' + ($data) + ' ' + ($notOp) + '= ' + ($schemaExcl) + ' : ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' ) || ' + ($data) + ' !== ' + ($data) + ') { '; - } else { - if ($exclIsNumber && $schema === undefined) { - $exclusive = true; - $errorKeyword = $exclusiveKeyword; - $errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword; - $schemaValue = $schemaExcl; - $notOp += '='; - } else { - if ($exclIsNumber) $schemaValue = Math[$isMax ? 'min' : 'max']($schemaExcl, $schema); - if ($schemaExcl === ($exclIsNumber ? $schemaValue : true)) { - $exclusive = true; - $errorKeyword = $exclusiveKeyword; - $errSchemaPath = it.errSchemaPath + '/' + $exclusiveKeyword; - $notOp += '='; - } else { - $exclusive = false; - $opStr += '='; - } - } - var $opExpr = '\'' + $opStr + '\''; - out += ' if ( '; - if ($isData) { - out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; - } - out += ' ' + ($data) + ' ' + ($notOp) + ' ' + ($schemaValue) + ' || ' + ($data) + ' !== ' + ($data) + ') { '; - } - } - $errorKeyword = $errorKeyword || $keyword; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || '_limit') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { comparison: ' + ($opExpr) + ', limit: ' + ($schemaValue) + ', exclusive: ' + ($exclusive) + ' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should be ' + ($opStr) + ' '; - if ($isData) { - out += '\' + ' + ($schemaValue); - } else { - out += '' + ($schemaValue) + '\''; - } - } - if (it.opts.verbose) { - out += ' , schema: '; - if ($isData) { - out += 'validate.schema' + ($schemaPath); - } else { - out += '' + ($schema); - } - out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } '; - if ($breakOnError) { - out += ' else { '; - } - return out; - } - - }, {}], - 15: [function (require, module, exports) { - 'use strict'; - module.exports = function generate__limitItems(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $errorKeyword; - var $data = 'data' + ($dataLvl || ''); - var $isData = it.opts.$data && $schema && $schema.$data, - $schemaValue; - if ($isData) { - out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; - $schemaValue = 'schema' + $lvl; - } else { - $schemaValue = $schema; - } - var $op = $keyword == 'maxItems' ? '>' : '<'; - out += 'if ( '; - if ($isData) { - out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; - } - out += ' ' + ($data) + '.length ' + ($op) + ' ' + ($schemaValue) + ') { '; - var $errorKeyword = $keyword; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || '_limitItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schemaValue) + ' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT have '; - if ($keyword == 'maxItems') { - out += 'more'; - } else { - out += 'less'; - } - out += ' than '; - if ($isData) { - out += '\' + ' + ($schemaValue) + ' + \''; - } else { - out += '' + ($schema); - } - out += ' items\' '; - } - if (it.opts.verbose) { - out += ' , schema: '; - if ($isData) { - out += 'validate.schema' + ($schemaPath); - } else { - out += '' + ($schema); - } - out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += '} '; - if ($breakOnError) { - out += ' else { '; - } - return out; - } - - }, {}], - 16: [function (require, module, exports) { - 'use strict'; - module.exports = function generate__limitLength(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $errorKeyword; - var $data = 'data' + ($dataLvl || ''); - var $isData = it.opts.$data && $schema && $schema.$data, - $schemaValue; - if ($isData) { - out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; - $schemaValue = 'schema' + $lvl; - } else { - $schemaValue = $schema; - } - var $op = $keyword == 'maxLength' ? '>' : '<'; - out += 'if ( '; - if ($isData) { - out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; - } - if (it.opts.unicode === false) { - out += ' ' + ($data) + '.length '; - } else { - out += ' ucs2length(' + ($data) + ') '; - } - out += ' ' + ($op) + ' ' + ($schemaValue) + ') { '; - var $errorKeyword = $keyword; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || '_limitLength') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schemaValue) + ' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT be '; - if ($keyword == 'maxLength') { - out += 'longer'; - } else { - out += 'shorter'; - } - out += ' than '; - if ($isData) { - out += '\' + ' + ($schemaValue) + ' + \''; - } else { - out += '' + ($schema); - } - out += ' characters\' '; - } - if (it.opts.verbose) { - out += ' , schema: '; - if ($isData) { - out += 'validate.schema' + ($schemaPath); - } else { - out += '' + ($schema); - } - out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += '} '; - if ($breakOnError) { - out += ' else { '; - } - return out; - } - - }, {}], - 17: [function (require, module, exports) { - 'use strict'; - module.exports = function generate__limitProperties(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $errorKeyword; - var $data = 'data' + ($dataLvl || ''); - var $isData = it.opts.$data && $schema && $schema.$data, - $schemaValue; - if ($isData) { - out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; - $schemaValue = 'schema' + $lvl; - } else { - $schemaValue = $schema; - } - var $op = $keyword == 'maxProperties' ? '>' : '<'; - out += 'if ( '; - if ($isData) { - out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'number\') || '; - } - out += ' Object.keys(' + ($data) + ').length ' + ($op) + ' ' + ($schemaValue) + ') { '; - var $errorKeyword = $keyword; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || '_limitProperties') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schemaValue) + ' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT have '; - if ($keyword == 'maxProperties') { - out += 'more'; - } else { - out += 'less'; - } - out += ' than '; - if ($isData) { - out += '\' + ' + ($schemaValue) + ' + \''; - } else { - out += '' + ($schema); - } - out += ' properties\' '; - } - if (it.opts.verbose) { - out += ' , schema: '; - if ($isData) { - out += 'validate.schema' + ($schemaPath); - } else { - out += '' + ($schema); - } - out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += '} '; - if ($breakOnError) { - out += ' else { '; - } - return out; - } - - }, {}], - 18: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_allOf(it, $keyword, $ruleType) { - var out = ' '; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $it = it.util.copy(it); - var $closingBraces = ''; - $it.level++; - var $nextValid = 'valid' + $it.level; - var $currentBaseId = $it.baseId, - $allSchemasEmpty = true; - var arr1 = $schema; - if (arr1) { - var $sch, $i = -1, - l1 = arr1.length - 1; - while ($i < l1) { - $sch = arr1[$i += 1]; - if (it.util.schemaHasRules($sch, it.RULES.all)) { - $allSchemasEmpty = false; - $it.schema = $sch; - $it.schemaPath = $schemaPath + '[' + $i + ']'; - $it.errSchemaPath = $errSchemaPath + '/' + $i; - out += ' ' + (it.validate($it)) + ' '; - $it.baseId = $currentBaseId; - if ($breakOnError) { - out += ' if (' + ($nextValid) + ') { '; - $closingBraces += '}'; - } - } - } - } - if ($breakOnError) { - if ($allSchemasEmpty) { - out += ' if (true) { '; - } else { - out += ' ' + ($closingBraces.slice(0, -1)) + ' '; - } - } - out = it.util.cleanUpCode(out); - return out; - } - - }, {}], - 19: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_anyOf(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $valid = 'valid' + $lvl; - var $errs = 'errs__' + $lvl; - var $it = it.util.copy(it); - var $closingBraces = ''; - $it.level++; - var $nextValid = 'valid' + $it.level; - var $noEmptySchema = $schema.every(function ($sch) { - return it.util.schemaHasRules($sch, it.RULES.all); - }); - if ($noEmptySchema) { - var $currentBaseId = $it.baseId; - out += ' var ' + ($errs) + ' = errors; var ' + ($valid) + ' = false; '; - var $wasComposite = it.compositeRule; - it.compositeRule = $it.compositeRule = true; - var arr1 = $schema; - if (arr1) { - var $sch, $i = -1, - l1 = arr1.length - 1; - while ($i < l1) { - $sch = arr1[$i += 1]; - $it.schema = $sch; - $it.schemaPath = $schemaPath + '[' + $i + ']'; - $it.errSchemaPath = $errSchemaPath + '/' + $i; - out += ' ' + (it.validate($it)) + ' '; - $it.baseId = $currentBaseId; - out += ' ' + ($valid) + ' = ' + ($valid) + ' || ' + ($nextValid) + '; if (!' + ($valid) + ') { '; - $closingBraces += '}'; - } - } - it.compositeRule = $it.compositeRule = $wasComposite; - out += ' ' + ($closingBraces) + ' if (!' + ($valid) + ') { var err = '; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('anyOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; - if (it.opts.messages !== false) { - out += ' , message: \'should match some schema in anyOf\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError(vErrors); '; - } else { - out += ' validate.errors = vErrors; return false; '; - } - } - out += ' } else { errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } '; - if (it.opts.allErrors) { - out += ' } '; - } - out = it.util.cleanUpCode(out); - } else { - if ($breakOnError) { - out += ' if (true) { '; - } - } - return out; - } - - }, {}], - 20: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_const(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $valid = 'valid' + $lvl; - var $isData = it.opts.$data && $schema && $schema.$data, - $schemaValue; - if ($isData) { - out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; - $schemaValue = 'schema' + $lvl; - } else { - $schemaValue = $schema; - } - if (!$isData) { - out += ' var schema' + ($lvl) + ' = validate.schema' + ($schemaPath) + ';'; - } - out += 'var ' + ($valid) + ' = equal(' + ($data) + ', schema' + ($lvl) + '); if (!' + ($valid) + ') { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('const') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; - if (it.opts.messages !== false) { - out += ' , message: \'should be equal to constant\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' }'; - if ($breakOnError) { - out += ' else { '; - } - return out; - } - - }, {}], - 21: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_contains(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $valid = 'valid' + $lvl; - var $errs = 'errs__' + $lvl; - var $it = it.util.copy(it); - var $closingBraces = ''; - $it.level++; - var $nextValid = 'valid' + $it.level; - var $idx = 'i' + $lvl, - $dataNxt = $it.dataLevel = it.dataLevel + 1, - $nextData = 'data' + $dataNxt, - $currentBaseId = it.baseId, - $nonEmptySchema = it.util.schemaHasRules($schema, it.RULES.all); - out += 'var ' + ($errs) + ' = errors;var ' + ($valid) + ';'; - if ($nonEmptySchema) { - var $wasComposite = it.compositeRule; - it.compositeRule = $it.compositeRule = true; - $it.schema = $schema; - $it.schemaPath = $schemaPath; - $it.errSchemaPath = $errSchemaPath; - out += ' var ' + ($nextValid) + ' = false; for (var ' + ($idx) + ' = 0; ' + ($idx) + ' < ' + ($data) + '.length; ' + ($idx) + '++) { '; - $it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true); - var $passData = $data + '[' + $idx + ']'; - $it.dataPathArr[$dataNxt] = $idx; - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; - } else { - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; - } - out += ' if (' + ($nextValid) + ') break; } '; - it.compositeRule = $it.compositeRule = $wasComposite; - out += ' ' + ($closingBraces) + ' if (!' + ($nextValid) + ') {'; - } else { - out += ' if (' + ($data) + '.length == 0) {'; - } - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('contains') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; - if (it.opts.messages !== false) { - out += ' , message: \'should contain a valid item\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } else { '; - if ($nonEmptySchema) { - out += ' errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } '; - } - if (it.opts.allErrors) { - out += ' } '; - } - out = it.util.cleanUpCode(out); - return out; - } - - }, {}], - 22: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_custom(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $errorKeyword; - var $data = 'data' + ($dataLvl || ''); - var $valid = 'valid' + $lvl; - var $errs = 'errs__' + $lvl; - var $isData = it.opts.$data && $schema && $schema.$data, - $schemaValue; - if ($isData) { - out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; - $schemaValue = 'schema' + $lvl; - } else { - $schemaValue = $schema; - } - var $rule = this, - $definition = 'definition' + $lvl, - $rDef = $rule.definition, - $closingBraces = ''; - var $compile, $inline, $macro, $ruleValidate, $validateCode; - if ($isData && $rDef.$data) { - $validateCode = 'keywordValidate' + $lvl; - var $validateSchema = $rDef.validateSchema; - out += ' var ' + ($definition) + ' = RULES.custom[\'' + ($keyword) + '\'].definition; var ' + ($validateCode) + ' = ' + ($definition) + '.validate;'; - } else { - $ruleValidate = it.useCustomRule($rule, $schema, it.schema, it); - if (!$ruleValidate) return; - $schemaValue = 'validate.schema' + $schemaPath; - $validateCode = $ruleValidate.code; - $compile = $rDef.compile; - $inline = $rDef.inline; - $macro = $rDef.macro; - } - var $ruleErrs = $validateCode + '.errors', - $i = 'i' + $lvl, - $ruleErr = 'ruleErr' + $lvl, - $asyncKeyword = $rDef.async; - if ($asyncKeyword && !it.async) throw new Error('async keyword in sync schema'); - if (!($inline || $macro)) { - out += '' + ($ruleErrs) + ' = null;'; - } - out += 'var ' + ($errs) + ' = errors;var ' + ($valid) + ';'; - if ($isData && $rDef.$data) { - $closingBraces += '}'; - out += ' if (' + ($schemaValue) + ' === undefined) { ' + ($valid) + ' = true; } else { '; - if ($validateSchema) { - $closingBraces += '}'; - out += ' ' + ($valid) + ' = ' + ($definition) + '.validateSchema(' + ($schemaValue) + '); if (' + ($valid) + ') { '; - } - } - if ($inline) { - if ($rDef.statements) { - out += ' ' + ($ruleValidate.validate) + ' '; - } else { - out += ' ' + ($valid) + ' = ' + ($ruleValidate.validate) + '; '; - } - } else if ($macro) { - var $it = it.util.copy(it); - var $closingBraces = ''; - $it.level++; - var $nextValid = 'valid' + $it.level; - $it.schema = $ruleValidate.validate; - $it.schemaPath = ''; - var $wasComposite = it.compositeRule; - it.compositeRule = $it.compositeRule = true; - var $code = it.validate($it).replace(/validate\.schema/g, $validateCode); - it.compositeRule = $it.compositeRule = $wasComposite; - out += ' ' + ($code); - } else { - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; - out += ' ' + ($validateCode) + '.call( '; - if (it.opts.passContext) { - out += 'this'; - } else { - out += 'self'; - } - if ($compile || $rDef.schema === false) { - out += ' , ' + ($data) + ' '; - } else { - out += ' , ' + ($schemaValue) + ' , ' + ($data) + ' , validate.schema' + (it.schemaPath) + ' '; - } - out += ' , (dataPath || \'\')'; - if (it.errorPath != '""') { - out += ' + ' + (it.errorPath); - } - var $parentData = $dataLvl ? 'data' + (($dataLvl - 1) || '') : 'parentData', - $parentDataProperty = $dataLvl ? it.dataPathArr[$dataLvl] : 'parentDataProperty'; - out += ' , ' + ($parentData) + ' , ' + ($parentDataProperty) + ' , rootData ) '; - var def_callRuleValidate = out; - out = $$outStack.pop(); - if ($rDef.errors === false) { - out += ' ' + ($valid) + ' = '; - if ($asyncKeyword) { - out += '' + (it.yieldAwait); - } - out += '' + (def_callRuleValidate) + '; '; - } else { - if ($asyncKeyword) { - $ruleErrs = 'customErrors' + $lvl; - out += ' var ' + ($ruleErrs) + ' = null; try { ' + ($valid) + ' = ' + (it.yieldAwait) + (def_callRuleValidate) + '; } catch (e) { ' + ($valid) + ' = false; if (e instanceof ValidationError) ' + ($ruleErrs) + ' = e.errors; else throw e; } '; - } else { - out += ' ' + ($ruleErrs) + ' = null; ' + ($valid) + ' = ' + (def_callRuleValidate) + '; '; - } - } - } - if ($rDef.modifying) { - out += ' if (' + ($parentData) + ') ' + ($data) + ' = ' + ($parentData) + '[' + ($parentDataProperty) + '];'; - } - out += '' + ($closingBraces); - if ($rDef.valid) { - if ($breakOnError) { - out += ' if (true) { '; - } - } else { - out += ' if ( '; - if ($rDef.valid === undefined) { - out += ' !'; - if ($macro) { - out += '' + ($nextValid); - } else { - out += '' + ($valid); - } - } else { - out += ' ' + (!$rDef.valid) + ' '; - } - out += ') { '; - $errorKeyword = $rule.keyword; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || 'custom') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { keyword: \'' + ($rule.keyword) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should pass "' + ($rule.keyword) + '" keyword validation\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - var def_customError = out; - out = $$outStack.pop(); - if ($inline) { - if ($rDef.errors) { - if ($rDef.errors != 'full') { - out += ' for (var ' + ($i) + '=' + ($errs) + '; ' + ($i) + '= 0) { - if ($breakOnError) { - out += ' if (true) { '; - } - return out; - } else { - throw new Error('unknown format "' + $schema + '" is used in schema at path "' + it.errSchemaPath + '"'); - } - } - var $isObject = typeof $format == 'object' && !($format instanceof RegExp) && $format.validate; - var $formatType = $isObject && $format.type || 'string'; - if ($isObject) { - var $async = $format.async === true; - $format = $format.validate; - } - if ($formatType != $ruleType) { - if ($breakOnError) { - out += ' if (true) { '; - } - return out; - } - if ($async) { - if (!it.async) throw new Error('async format in sync schema'); - var $formatRef = 'formats' + it.util.getProperty($schema) + '.validate'; - out += ' if (!(' + (it.yieldAwait) + ' ' + ($formatRef) + '(' + ($data) + '))) { '; - } else { - out += ' if (! '; - var $formatRef = 'formats' + it.util.getProperty($schema); - if ($isObject) $formatRef += '.validate'; - if (typeof $format == 'function') { - out += ' ' + ($formatRef) + '(' + ($data) + ') '; - } else { - out += ' ' + ($formatRef) + '.test(' + ($data) + ') '; - } - out += ') { '; - } - } - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('format') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { format: '; - if ($isData) { - out += '' + ($schemaValue); - } else { - out += '' + (it.util.toQuotedString($schema)); - } - out += ' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should match format "'; - if ($isData) { - out += '\' + ' + ($schemaValue) + ' + \''; - } else { - out += '' + (it.util.escapeQuotes($schema)); - } - out += '"\' '; - } - if (it.opts.verbose) { - out += ' , schema: '; - if ($isData) { - out += 'validate.schema' + ($schemaPath); - } else { - out += '' + (it.util.toQuotedString($schema)); - } - out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } '; - if ($breakOnError) { - out += ' else { '; - } - return out; - } - - }, {}], - 26: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_items(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $valid = 'valid' + $lvl; - var $errs = 'errs__' + $lvl; - var $it = it.util.copy(it); - var $closingBraces = ''; - $it.level++; - var $nextValid = 'valid' + $it.level; - var $idx = 'i' + $lvl, - $dataNxt = $it.dataLevel = it.dataLevel + 1, - $nextData = 'data' + $dataNxt, - $currentBaseId = it.baseId; - out += 'var ' + ($errs) + ' = errors;var ' + ($valid) + ';'; - if (Array.isArray($schema)) { - var $additionalItems = it.schema.additionalItems; - if ($additionalItems === false) { - out += ' ' + ($valid) + ' = ' + ($data) + '.length <= ' + ($schema.length) + '; '; - var $currErrSchemaPath = $errSchemaPath; - $errSchemaPath = it.errSchemaPath + '/additionalItems'; - out += ' if (!' + ($valid) + ') { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('additionalItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { limit: ' + ($schema.length) + ' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT have more than ' + ($schema.length) + ' items\' '; - } - if (it.opts.verbose) { - out += ' , schema: false , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } '; - $errSchemaPath = $currErrSchemaPath; - if ($breakOnError) { - $closingBraces += '}'; - out += ' else { '; - } - } - var arr1 = $schema; - if (arr1) { - var $sch, $i = -1, - l1 = arr1.length - 1; - while ($i < l1) { - $sch = arr1[$i += 1]; - if (it.util.schemaHasRules($sch, it.RULES.all)) { - out += ' ' + ($nextValid) + ' = true; if (' + ($data) + '.length > ' + ($i) + ') { '; - var $passData = $data + '[' + $i + ']'; - $it.schema = $sch; - $it.schemaPath = $schemaPath + '[' + $i + ']'; - $it.errSchemaPath = $errSchemaPath + '/' + $i; - $it.errorPath = it.util.getPathExpr(it.errorPath, $i, it.opts.jsonPointers, true); - $it.dataPathArr[$dataNxt] = $i; - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; - } else { - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; - } - out += ' } '; - if ($breakOnError) { - out += ' if (' + ($nextValid) + ') { '; - $closingBraces += '}'; - } - } - } - } - if (typeof $additionalItems == 'object' && it.util.schemaHasRules($additionalItems, it.RULES.all)) { - $it.schema = $additionalItems; - $it.schemaPath = it.schemaPath + '.additionalItems'; - $it.errSchemaPath = it.errSchemaPath + '/additionalItems'; - out += ' ' + ($nextValid) + ' = true; if (' + ($data) + '.length > ' + ($schema.length) + ') { for (var ' + ($idx) + ' = ' + ($schema.length) + '; ' + ($idx) + ' < ' + ($data) + '.length; ' + ($idx) + '++) { '; - $it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true); - var $passData = $data + '[' + $idx + ']'; - $it.dataPathArr[$dataNxt] = $idx; - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; - } else { - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; - } - if ($breakOnError) { - out += ' if (!' + ($nextValid) + ') break; '; - } - out += ' } } '; - if ($breakOnError) { - out += ' if (' + ($nextValid) + ') { '; - $closingBraces += '}'; - } - } - } else if (it.util.schemaHasRules($schema, it.RULES.all)) { - $it.schema = $schema; - $it.schemaPath = $schemaPath; - $it.errSchemaPath = $errSchemaPath; - out += ' for (var ' + ($idx) + ' = ' + (0) + '; ' + ($idx) + ' < ' + ($data) + '.length; ' + ($idx) + '++) { '; - $it.errorPath = it.util.getPathExpr(it.errorPath, $idx, it.opts.jsonPointers, true); - var $passData = $data + '[' + $idx + ']'; - $it.dataPathArr[$dataNxt] = $idx; - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; - } else { - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; - } - if ($breakOnError) { - out += ' if (!' + ($nextValid) + ') break; '; - } - out += ' }'; - } - if ($breakOnError) { - out += ' ' + ($closingBraces) + ' if (' + ($errs) + ' == errors) {'; - } - out = it.util.cleanUpCode(out); - return out; - } - - }, {}], - 27: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_multipleOf(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $isData = it.opts.$data && $schema && $schema.$data, - $schemaValue; - if ($isData) { - out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; - $schemaValue = 'schema' + $lvl; - } else { - $schemaValue = $schema; - } - out += 'var division' + ($lvl) + ';if ('; - if ($isData) { - out += ' ' + ($schemaValue) + ' !== undefined && ( typeof ' + ($schemaValue) + ' != \'number\' || '; - } - out += ' (division' + ($lvl) + ' = ' + ($data) + ' / ' + ($schemaValue) + ', '; - if (it.opts.multipleOfPrecision) { - out += ' Math.abs(Math.round(division' + ($lvl) + ') - division' + ($lvl) + ') > 1e-' + (it.opts.multipleOfPrecision) + ' '; - } else { - out += ' division' + ($lvl) + ' !== parseInt(division' + ($lvl) + ') '; - } - out += ' ) '; - if ($isData) { - out += ' ) '; - } - out += ' ) { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('multipleOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { multipleOf: ' + ($schemaValue) + ' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should be multiple of '; - if ($isData) { - out += '\' + ' + ($schemaValue); - } else { - out += '' + ($schemaValue) + '\''; - } - } - if (it.opts.verbose) { - out += ' , schema: '; - if ($isData) { - out += 'validate.schema' + ($schemaPath); - } else { - out += '' + ($schema); - } - out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += '} '; - if ($breakOnError) { - out += ' else { '; - } - return out; - } - - }, {}], - 28: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_not(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $errs = 'errs__' + $lvl; - var $it = it.util.copy(it); - $it.level++; - var $nextValid = 'valid' + $it.level; - if (it.util.schemaHasRules($schema, it.RULES.all)) { - $it.schema = $schema; - $it.schemaPath = $schemaPath; - $it.errSchemaPath = $errSchemaPath; - out += ' var ' + ($errs) + ' = errors; '; - var $wasComposite = it.compositeRule; - it.compositeRule = $it.compositeRule = true; - $it.createErrors = false; - var $allErrorsOption; - if ($it.opts.allErrors) { - $allErrorsOption = $it.opts.allErrors; - $it.opts.allErrors = false; - } - out += ' ' + (it.validate($it)) + ' '; - $it.createErrors = true; - if ($allErrorsOption) $it.opts.allErrors = $allErrorsOption; - it.compositeRule = $it.compositeRule = $wasComposite; - out += ' if (' + ($nextValid) + ') { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('not') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT be valid\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } else { errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; } '; - if (it.opts.allErrors) { - out += ' } '; - } - } else { - out += ' var err = '; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('not') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT be valid\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - if ($breakOnError) { - out += ' if (false) { '; - } - } - return out; - } - - }, {}], - 29: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_oneOf(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $valid = 'valid' + $lvl; - var $errs = 'errs__' + $lvl; - var $it = it.util.copy(it); - var $closingBraces = ''; - $it.level++; - var $nextValid = 'valid' + $it.level; - out += 'var ' + ($errs) + ' = errors;var prevValid' + ($lvl) + ' = false;var ' + ($valid) + ' = false;'; - var $currentBaseId = $it.baseId; - var $wasComposite = it.compositeRule; - it.compositeRule = $it.compositeRule = true; - var arr1 = $schema; - if (arr1) { - var $sch, $i = -1, - l1 = arr1.length - 1; - while ($i < l1) { - $sch = arr1[$i += 1]; - if (it.util.schemaHasRules($sch, it.RULES.all)) { - $it.schema = $sch; - $it.schemaPath = $schemaPath + '[' + $i + ']'; - $it.errSchemaPath = $errSchemaPath + '/' + $i; - out += ' ' + (it.validate($it)) + ' '; - $it.baseId = $currentBaseId; - } else { - out += ' var ' + ($nextValid) + ' = true; '; - } - if ($i) { - out += ' if (' + ($nextValid) + ' && prevValid' + ($lvl) + ') ' + ($valid) + ' = false; else { '; - $closingBraces += '}'; - } - out += ' if (' + ($nextValid) + ') ' + ($valid) + ' = prevValid' + ($lvl) + ' = true;'; - } - } - it.compositeRule = $it.compositeRule = $wasComposite; - out += '' + ($closingBraces) + 'if (!' + ($valid) + ') { var err = '; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('oneOf') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; - if (it.opts.messages !== false) { - out += ' , message: \'should match exactly one schema in oneOf\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError(vErrors); '; - } else { - out += ' validate.errors = vErrors; return false; '; - } - } - out += '} else { errors = ' + ($errs) + '; if (vErrors !== null) { if (' + ($errs) + ') vErrors.length = ' + ($errs) + '; else vErrors = null; }'; - if (it.opts.allErrors) { - out += ' } '; - } - return out; - } - - }, {}], - 30: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_pattern(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $isData = it.opts.$data && $schema && $schema.$data, - $schemaValue; - if ($isData) { - out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; - $schemaValue = 'schema' + $lvl; - } else { - $schemaValue = $schema; - } - var $regexp = $isData ? '(new RegExp(' + $schemaValue + '))' : it.usePattern($schema); - out += 'if ( '; - if ($isData) { - out += ' (' + ($schemaValue) + ' !== undefined && typeof ' + ($schemaValue) + ' != \'string\') || '; - } - out += ' !' + ($regexp) + '.test(' + ($data) + ') ) { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('pattern') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { pattern: '; - if ($isData) { - out += '' + ($schemaValue); - } else { - out += '' + (it.util.toQuotedString($schema)); - } - out += ' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should match pattern "'; - if ($isData) { - out += '\' + ' + ($schemaValue) + ' + \''; - } else { - out += '' + (it.util.escapeQuotes($schema)); - } - out += '"\' '; - } - if (it.opts.verbose) { - out += ' , schema: '; - if ($isData) { - out += 'validate.schema' + ($schemaPath); - } else { - out += '' + (it.util.toQuotedString($schema)); - } - out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += '} '; - if ($breakOnError) { - out += ' else { '; - } - return out; - } - - }, {}], - 31: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_properties(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $valid = 'valid' + $lvl; - var $errs = 'errs__' + $lvl; - var $it = it.util.copy(it); - var $closingBraces = ''; - $it.level++; - var $nextValid = 'valid' + $it.level; - var $key = 'key' + $lvl, - $idx = 'idx' + $lvl, - $dataNxt = $it.dataLevel = it.dataLevel + 1, - $nextData = 'data' + $dataNxt, - $dataProperties = 'dataProperties' + $lvl; - var $schemaKeys = Object.keys($schema || {}), - $pProperties = it.schema.patternProperties || {}, - $pPropertyKeys = Object.keys($pProperties), - $aProperties = it.schema.additionalProperties, - $someProperties = $schemaKeys.length || $pPropertyKeys.length, - $noAdditional = $aProperties === false, - $additionalIsSchema = typeof $aProperties == 'object' && Object.keys($aProperties).length, - $removeAdditional = it.opts.removeAdditional, - $checkAdditional = $noAdditional || $additionalIsSchema || $removeAdditional, - $ownProperties = it.opts.ownProperties, - $currentBaseId = it.baseId; - var $required = it.schema.required; - if ($required && !(it.opts.v5 && $required.$data) && $required.length < it.opts.loopRequired) var $requiredHash = it.util.toHash($required); - if (it.opts.patternGroups) { - var $pgProperties = it.schema.patternGroups || {}, - $pgPropertyKeys = Object.keys($pgProperties); - } - out += 'var ' + ($errs) + ' = errors;var ' + ($nextValid) + ' = true;'; - if ($ownProperties) { - out += ' var ' + ($dataProperties) + ' = undefined;'; - } - if ($checkAdditional) { - if ($ownProperties) { - out += ' ' + ($dataProperties) + ' = ' + ($dataProperties) + ' || Object.keys(' + ($data) + '); for (var ' + ($idx) + '=0; ' + ($idx) + '<' + ($dataProperties) + '.length; ' + ($idx) + '++) { var ' + ($key) + ' = ' + ($dataProperties) + '[' + ($idx) + ']; '; - } else { - out += ' for (var ' + ($key) + ' in ' + ($data) + ') { '; - } - if ($someProperties) { - out += ' var isAdditional' + ($lvl) + ' = !(false '; - if ($schemaKeys.length) { - if ($schemaKeys.length > 5) { - out += ' || validate.schema' + ($schemaPath) + '[' + ($key) + '] '; - } else { - var arr1 = $schemaKeys; - if (arr1) { - var $propertyKey, i1 = -1, - l1 = arr1.length - 1; - while (i1 < l1) { - $propertyKey = arr1[i1 += 1]; - out += ' || ' + ($key) + ' == ' + (it.util.toQuotedString($propertyKey)) + ' '; - } - } - } - } - if ($pPropertyKeys.length) { - var arr2 = $pPropertyKeys; - if (arr2) { - var $pProperty, $i = -1, - l2 = arr2.length - 1; - while ($i < l2) { - $pProperty = arr2[$i += 1]; - out += ' || ' + (it.usePattern($pProperty)) + '.test(' + ($key) + ') '; - } - } - } - if (it.opts.patternGroups && $pgPropertyKeys.length) { - var arr3 = $pgPropertyKeys; - if (arr3) { - var $pgProperty, $i = -1, - l3 = arr3.length - 1; - while ($i < l3) { - $pgProperty = arr3[$i += 1]; - out += ' || ' + (it.usePattern($pgProperty)) + '.test(' + ($key) + ') '; - } - } - } - out += ' ); if (isAdditional' + ($lvl) + ') { '; - } - if ($removeAdditional == 'all') { - out += ' delete ' + ($data) + '[' + ($key) + ']; '; - } else { - var $currentErrorPath = it.errorPath; - var $additionalProperty = '\' + ' + $key + ' + \''; - if (it.opts._errorDataPathProperty) { - it.errorPath = it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); - } - if ($noAdditional) { - if ($removeAdditional) { - out += ' delete ' + ($data) + '[' + ($key) + ']; '; - } else { - out += ' ' + ($nextValid) + ' = false; '; - var $currErrSchemaPath = $errSchemaPath; - $errSchemaPath = it.errSchemaPath + '/additionalProperties'; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('additionalProperties') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { additionalProperty: \'' + ($additionalProperty) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT have additional properties\' '; - } - if (it.opts.verbose) { - out += ' , schema: false , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - $errSchemaPath = $currErrSchemaPath; - if ($breakOnError) { - out += ' break; '; - } - } - } else if ($additionalIsSchema) { - if ($removeAdditional == 'failing') { - out += ' var ' + ($errs) + ' = errors; '; - var $wasComposite = it.compositeRule; - it.compositeRule = $it.compositeRule = true; - $it.schema = $aProperties; - $it.schemaPath = it.schemaPath + '.additionalProperties'; - $it.errSchemaPath = it.errSchemaPath + '/additionalProperties'; - $it.errorPath = it.opts._errorDataPathProperty ? it.errorPath : it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); - var $passData = $data + '[' + $key + ']'; - $it.dataPathArr[$dataNxt] = $key; - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; - } else { - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; - } - out += ' if (!' + ($nextValid) + ') { errors = ' + ($errs) + '; if (validate.errors !== null) { if (errors) validate.errors.length = errors; else validate.errors = null; } delete ' + ($data) + '[' + ($key) + ']; } '; - it.compositeRule = $it.compositeRule = $wasComposite; - } else { - $it.schema = $aProperties; - $it.schemaPath = it.schemaPath + '.additionalProperties'; - $it.errSchemaPath = it.errSchemaPath + '/additionalProperties'; - $it.errorPath = it.opts._errorDataPathProperty ? it.errorPath : it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); - var $passData = $data + '[' + $key + ']'; - $it.dataPathArr[$dataNxt] = $key; - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; - } else { - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; - } - if ($breakOnError) { - out += ' if (!' + ($nextValid) + ') break; '; - } - } - } - it.errorPath = $currentErrorPath; - } - if ($someProperties) { - out += ' } '; - } - out += ' } '; - if ($breakOnError) { - out += ' if (' + ($nextValid) + ') { '; - $closingBraces += '}'; - } - } - var $useDefaults = it.opts.useDefaults && !it.compositeRule; - if ($schemaKeys.length) { - var arr4 = $schemaKeys; - if (arr4) { - var $propertyKey, i4 = -1, - l4 = arr4.length - 1; - while (i4 < l4) { - $propertyKey = arr4[i4 += 1]; - var $sch = $schema[$propertyKey]; - if (it.util.schemaHasRules($sch, it.RULES.all)) { - var $prop = it.util.getProperty($propertyKey), - $passData = $data + $prop, - $hasDefault = $useDefaults && $sch.default !== undefined; - $it.schema = $sch; - $it.schemaPath = $schemaPath + $prop; - $it.errSchemaPath = $errSchemaPath + '/' + it.util.escapeFragment($propertyKey); - $it.errorPath = it.util.getPath(it.errorPath, $propertyKey, it.opts.jsonPointers); - $it.dataPathArr[$dataNxt] = it.util.toQuotedString($propertyKey); - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - $code = it.util.varReplace($code, $nextData, $passData); - var $useData = $passData; - } else { - var $useData = $nextData; - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; '; - } - if ($hasDefault) { - out += ' ' + ($code) + ' '; - } else { - if ($requiredHash && $requiredHash[$propertyKey]) { - out += ' if ( ' + ($useData) + ' === undefined '; - if ($ownProperties) { - out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; - } - out += ') { ' + ($nextValid) + ' = false; '; - var $currentErrorPath = it.errorPath, - $currErrSchemaPath = $errSchemaPath, - $missingProperty = it.util.escapeQuotes($propertyKey); - if (it.opts._errorDataPathProperty) { - it.errorPath = it.util.getPath($currentErrorPath, $propertyKey, it.opts.jsonPointers); - } - $errSchemaPath = it.errSchemaPath + '/required'; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \''; - if (it.opts._errorDataPathProperty) { - out += 'is a required property'; - } else { - out += 'should have required property \\\'' + ($missingProperty) + '\\\''; - } - out += '\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - $errSchemaPath = $currErrSchemaPath; - it.errorPath = $currentErrorPath; - out += ' } else { '; - } else { - if ($breakOnError) { - out += ' if ( ' + ($useData) + ' === undefined '; - if ($ownProperties) { - out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; - } - out += ') { ' + ($nextValid) + ' = true; } else { '; - } else { - out += ' if (' + ($useData) + ' !== undefined '; - if ($ownProperties) { - out += ' && Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; - } - out += ' ) { '; - } - } - out += ' ' + ($code) + ' } '; - } - } - if ($breakOnError) { - out += ' if (' + ($nextValid) + ') { '; - $closingBraces += '}'; - } - } - } - } - if ($pPropertyKeys.length) { - var arr5 = $pPropertyKeys; - if (arr5) { - var $pProperty, i5 = -1, - l5 = arr5.length - 1; - while (i5 < l5) { - $pProperty = arr5[i5 += 1]; - var $sch = $pProperties[$pProperty]; - if (it.util.schemaHasRules($sch, it.RULES.all)) { - $it.schema = $sch; - $it.schemaPath = it.schemaPath + '.patternProperties' + it.util.getProperty($pProperty); - $it.errSchemaPath = it.errSchemaPath + '/patternProperties/' + it.util.escapeFragment($pProperty); - if ($ownProperties) { - out += ' ' + ($dataProperties) + ' = ' + ($dataProperties) + ' || Object.keys(' + ($data) + '); for (var ' + ($idx) + '=0; ' + ($idx) + '<' + ($dataProperties) + '.length; ' + ($idx) + '++) { var ' + ($key) + ' = ' + ($dataProperties) + '[' + ($idx) + ']; '; - } else { - out += ' for (var ' + ($key) + ' in ' + ($data) + ') { '; - } - out += ' if (' + (it.usePattern($pProperty)) + '.test(' + ($key) + ')) { '; - $it.errorPath = it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); - var $passData = $data + '[' + $key + ']'; - $it.dataPathArr[$dataNxt] = $key; - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; - } else { - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; - } - if ($breakOnError) { - out += ' if (!' + ($nextValid) + ') break; '; - } - out += ' } '; - if ($breakOnError) { - out += ' else ' + ($nextValid) + ' = true; '; - } - out += ' } '; - if ($breakOnError) { - out += ' if (' + ($nextValid) + ') { '; - $closingBraces += '}'; - } - } - } - } - } - if (it.opts.patternGroups && $pgPropertyKeys.length) { - var arr6 = $pgPropertyKeys; - if (arr6) { - var $pgProperty, i6 = -1, - l6 = arr6.length - 1; - while (i6 < l6) { - $pgProperty = arr6[i6 += 1]; - var $pgSchema = $pgProperties[$pgProperty], - $sch = $pgSchema.schema; - if (it.util.schemaHasRules($sch, it.RULES.all)) { - $it.schema = $sch; - $it.schemaPath = it.schemaPath + '.patternGroups' + it.util.getProperty($pgProperty) + '.schema'; - $it.errSchemaPath = it.errSchemaPath + '/patternGroups/' + it.util.escapeFragment($pgProperty) + '/schema'; - out += ' var pgPropCount' + ($lvl) + ' = 0; '; - if ($ownProperties) { - out += ' ' + ($dataProperties) + ' = ' + ($dataProperties) + ' || Object.keys(' + ($data) + '); for (var ' + ($idx) + '=0; ' + ($idx) + '<' + ($dataProperties) + '.length; ' + ($idx) + '++) { var ' + ($key) + ' = ' + ($dataProperties) + '[' + ($idx) + ']; '; - } else { - out += ' for (var ' + ($key) + ' in ' + ($data) + ') { '; - } - out += ' if (' + (it.usePattern($pgProperty)) + '.test(' + ($key) + ')) { pgPropCount' + ($lvl) + '++; '; - $it.errorPath = it.util.getPathExpr(it.errorPath, $key, it.opts.jsonPointers); - var $passData = $data + '[' + $key + ']'; - $it.dataPathArr[$dataNxt] = $key; - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; - } else { - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; - } - if ($breakOnError) { - out += ' if (!' + ($nextValid) + ') break; '; - } - out += ' } '; - if ($breakOnError) { - out += ' else ' + ($nextValid) + ' = true; '; - } - out += ' } '; - if ($breakOnError) { - out += ' if (' + ($nextValid) + ') { '; - $closingBraces += '}'; - } - var $pgMin = $pgSchema.minimum, - $pgMax = $pgSchema.maximum; - if ($pgMin !== undefined || $pgMax !== undefined) { - out += ' var ' + ($valid) + ' = true; '; - var $currErrSchemaPath = $errSchemaPath; - if ($pgMin !== undefined) { - var $limit = $pgMin, - $reason = 'minimum', - $moreOrLess = 'less'; - out += ' ' + ($valid) + ' = pgPropCount' + ($lvl) + ' >= ' + ($pgMin) + '; '; - $errSchemaPath = it.errSchemaPath + '/patternGroups/minimum'; - out += ' if (!' + ($valid) + ') { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('patternGroups') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { reason: \'' + ($reason) + '\', limit: ' + ($limit) + ', pattern: \'' + (it.util.escapeQuotes($pgProperty)) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT have ' + ($moreOrLess) + ' than ' + ($limit) + ' properties matching pattern "' + (it.util.escapeQuotes($pgProperty)) + '"\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } '; - if ($pgMax !== undefined) { - out += ' else '; - } - } - if ($pgMax !== undefined) { - var $limit = $pgMax, - $reason = 'maximum', - $moreOrLess = 'more'; - out += ' ' + ($valid) + ' = pgPropCount' + ($lvl) + ' <= ' + ($pgMax) + '; '; - $errSchemaPath = it.errSchemaPath + '/patternGroups/maximum'; - out += ' if (!' + ($valid) + ') { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('patternGroups') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { reason: \'' + ($reason) + '\', limit: ' + ($limit) + ', pattern: \'' + (it.util.escapeQuotes($pgProperty)) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT have ' + ($moreOrLess) + ' than ' + ($limit) + ' properties matching pattern "' + (it.util.escapeQuotes($pgProperty)) + '"\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } '; - } - $errSchemaPath = $currErrSchemaPath; - if ($breakOnError) { - out += ' if (' + ($valid) + ') { '; - $closingBraces += '}'; - } - } - } - } - } - } - if ($breakOnError) { - out += ' ' + ($closingBraces) + ' if (' + ($errs) + ' == errors) {'; - } - out = it.util.cleanUpCode(out); - return out; - } - - }, {}], - 32: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_propertyNames(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $errs = 'errs__' + $lvl; - var $it = it.util.copy(it); - var $closingBraces = ''; - $it.level++; - var $nextValid = 'valid' + $it.level; - if (it.util.schemaHasRules($schema, it.RULES.all)) { - $it.schema = $schema; - $it.schemaPath = $schemaPath; - $it.errSchemaPath = $errSchemaPath; - var $key = 'key' + $lvl, - $idx = 'idx' + $lvl, - $i = 'i' + $lvl, - $invalidName = '\' + ' + $key + ' + \'', - $dataNxt = $it.dataLevel = it.dataLevel + 1, - $nextData = 'data' + $dataNxt, - $dataProperties = 'dataProperties' + $lvl, - $ownProperties = it.opts.ownProperties, - $currentBaseId = it.baseId; - out += ' var ' + ($errs) + ' = errors; '; - if ($ownProperties) { - out += ' var ' + ($dataProperties) + ' = undefined; '; - } - if ($ownProperties) { - out += ' ' + ($dataProperties) + ' = ' + ($dataProperties) + ' || Object.keys(' + ($data) + '); for (var ' + ($idx) + '=0; ' + ($idx) + '<' + ($dataProperties) + '.length; ' + ($idx) + '++) { var ' + ($key) + ' = ' + ($dataProperties) + '[' + ($idx) + ']; '; - } else { - out += ' for (var ' + ($key) + ' in ' + ($data) + ') { '; - } - out += ' var startErrs' + ($lvl) + ' = errors; '; - var $passData = $key; - var $wasComposite = it.compositeRule; - it.compositeRule = $it.compositeRule = true; - var $code = it.validate($it); - $it.baseId = $currentBaseId; - if (it.util.varOccurences($code, $nextData) < 2) { - out += ' ' + (it.util.varReplace($code, $nextData, $passData)) + ' '; - } else { - out += ' var ' + ($nextData) + ' = ' + ($passData) + '; ' + ($code) + ' '; - } - it.compositeRule = $it.compositeRule = $wasComposite; - out += ' if (!' + ($nextValid) + ') { for (var ' + ($i) + '=startErrs' + ($lvl) + '; ' + ($i) + '= it.opts.loopRequired, - $ownProperties = it.opts.ownProperties; - if ($breakOnError) { - out += ' var missing' + ($lvl) + '; '; - if ($loopRequired) { - if (!$isData) { - out += ' var ' + ($vSchema) + ' = validate.schema' + ($schemaPath) + '; '; - } - var $i = 'i' + $lvl, - $propertyPath = 'schema' + $lvl + '[' + $i + ']', - $missingProperty = '\' + ' + $propertyPath + ' + \''; - if (it.opts._errorDataPathProperty) { - it.errorPath = it.util.getPathExpr($currentErrorPath, $propertyPath, it.opts.jsonPointers); - } - out += ' var ' + ($valid) + ' = true; '; - if ($isData) { - out += ' if (schema' + ($lvl) + ' === undefined) ' + ($valid) + ' = true; else if (!Array.isArray(schema' + ($lvl) + ')) ' + ($valid) + ' = false; else {'; - } - out += ' for (var ' + ($i) + ' = 0; ' + ($i) + ' < ' + ($vSchema) + '.length; ' + ($i) + '++) { ' + ($valid) + ' = ' + ($data) + '[' + ($vSchema) + '[' + ($i) + ']] !== undefined '; - if ($ownProperties) { - out += ' && Object.prototype.hasOwnProperty.call(' + ($data) + ', ' + ($vSchema) + '[' + ($i) + ']) '; - } - out += '; if (!' + ($valid) + ') break; } '; - if ($isData) { - out += ' } '; - } - out += ' if (!' + ($valid) + ') { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \''; - if (it.opts._errorDataPathProperty) { - out += 'is a required property'; - } else { - out += 'should have required property \\\'' + ($missingProperty) + '\\\''; - } - out += '\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } else { '; - } else { - out += ' if ( '; - var arr2 = $required; - if (arr2) { - var $propertyKey, $i = -1, - l2 = arr2.length - 1; - while ($i < l2) { - $propertyKey = arr2[$i += 1]; - if ($i) { - out += ' || '; - } - var $prop = it.util.getProperty($propertyKey), - $useData = $data + $prop; - out += ' ( ( ' + ($useData) + ' === undefined '; - if ($ownProperties) { - out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; - } - out += ') && (missing' + ($lvl) + ' = ' + (it.util.toQuotedString(it.opts.jsonPointers ? $propertyKey : $prop)) + ') ) '; - } - } - out += ') { '; - var $propertyPath = 'missing' + $lvl, - $missingProperty = '\' + ' + $propertyPath + ' + \''; - if (it.opts._errorDataPathProperty) { - it.errorPath = it.opts.jsonPointers ? it.util.getPathExpr($currentErrorPath, $propertyPath, true) : $currentErrorPath + ' + ' + $propertyPath; - } - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \''; - if (it.opts._errorDataPathProperty) { - out += 'is a required property'; - } else { - out += 'should have required property \\\'' + ($missingProperty) + '\\\''; - } - out += '\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } else { '; - } - } else { - if ($loopRequired) { - if (!$isData) { - out += ' var ' + ($vSchema) + ' = validate.schema' + ($schemaPath) + '; '; - } - var $i = 'i' + $lvl, - $propertyPath = 'schema' + $lvl + '[' + $i + ']', - $missingProperty = '\' + ' + $propertyPath + ' + \''; - if (it.opts._errorDataPathProperty) { - it.errorPath = it.util.getPathExpr($currentErrorPath, $propertyPath, it.opts.jsonPointers); - } - if ($isData) { - out += ' if (' + ($vSchema) + ' && !Array.isArray(' + ($vSchema) + ')) { var err = '; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \''; - if (it.opts._errorDataPathProperty) { - out += 'is a required property'; - } else { - out += 'should have required property \\\'' + ($missingProperty) + '\\\''; - } - out += '\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } else if (' + ($vSchema) + ' !== undefined) { '; - } - out += ' for (var ' + ($i) + ' = 0; ' + ($i) + ' < ' + ($vSchema) + '.length; ' + ($i) + '++) { if (' + ($data) + '[' + ($vSchema) + '[' + ($i) + ']] === undefined '; - if ($ownProperties) { - out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', ' + ($vSchema) + '[' + ($i) + ']) '; - } - out += ') { var err = '; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \''; - if (it.opts._errorDataPathProperty) { - out += 'is a required property'; - } else { - out += 'should have required property \\\'' + ($missingProperty) + '\\\''; - } - out += '\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } } '; - if ($isData) { - out += ' } '; - } - } else { - var arr3 = $required; - if (arr3) { - var $propertyKey, i3 = -1, - l3 = arr3.length - 1; - while (i3 < l3) { - $propertyKey = arr3[i3 += 1]; - var $prop = it.util.getProperty($propertyKey), - $missingProperty = it.util.escapeQuotes($propertyKey), - $useData = $data + $prop; - if (it.opts._errorDataPathProperty) { - it.errorPath = it.util.getPath($currentErrorPath, $propertyKey, it.opts.jsonPointers); - } - out += ' if ( ' + ($useData) + ' === undefined '; - if ($ownProperties) { - out += ' || ! Object.prototype.hasOwnProperty.call(' + ($data) + ', \'' + (it.util.escapeQuotes($propertyKey)) + '\') '; - } - out += ') { var err = '; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('required') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingProperty: \'' + ($missingProperty) + '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \''; - if (it.opts._errorDataPathProperty) { - out += 'is a required property'; - } else { - out += 'should have required property \\\'' + ($missingProperty) + '\\\''; - } - out += '\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - out += '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; } '; - } - } - } - } - it.errorPath = $currentErrorPath; - } else if ($breakOnError) { - out += ' if (true) {'; - } - return out; - } - - }, {}], - 35: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_uniqueItems(it, $keyword, $ruleType) { - var out = ' '; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $data = 'data' + ($dataLvl || ''); - var $valid = 'valid' + $lvl; - var $isData = it.opts.$data && $schema && $schema.$data, - $schemaValue; - if ($isData) { - out += ' var schema' + ($lvl) + ' = ' + (it.util.getData($schema.$data, $dataLvl, it.dataPathArr)) + '; '; - $schemaValue = 'schema' + $lvl; - } else { - $schemaValue = $schema; - } - if (($schema || $isData) && it.opts.uniqueItems !== false) { - if ($isData) { - out += ' var ' + ($valid) + '; if (' + ($schemaValue) + ' === false || ' + ($schemaValue) + ' === undefined) ' + ($valid) + ' = true; else if (typeof ' + ($schemaValue) + ' != \'boolean\') ' + ($valid) + ' = false; else { '; - } - out += ' var ' + ($valid) + ' = true; if (' + ($data) + '.length > 1) { var i = ' + ($data) + '.length, j; outer: for (;i--;) { for (j = i; j--;) { if (equal(' + ($data) + '[i], ' + ($data) + '[j])) { ' + ($valid) + ' = false; break outer; } } } } '; - if ($isData) { - out += ' } '; - } - out += ' if (!' + ($valid) + ') { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ('uniqueItems') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { i: i, j: j } '; - if (it.opts.messages !== false) { - out += ' , message: \'should NOT have duplicate items (items ## \' + j + \' and \' + i + \' are identical)\' '; - } - if (it.opts.verbose) { - out += ' , schema: '; - if ($isData) { - out += 'validate.schema' + ($schemaPath); - } else { - out += '' + ($schema); - } - out += ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } '; - if ($breakOnError) { - out += ' else { '; - } - } else { - if ($breakOnError) { - out += ' if (true) { '; - } - } - return out; - } - - }, {}], - 36: [function (require, module, exports) { - 'use strict'; - module.exports = function generate_validate(it, $keyword, $ruleType) { - var out = ''; - var $async = it.schema.$async === true, - $refKeywords = it.util.schemaHasRulesExcept(it.schema, it.RULES.all, '$ref'), - $id = it.self._getId(it.schema); - if (it.isTop) { - if ($async) { - it.async = true; - var $es7 = it.opts.async == 'es7'; - it.yieldAwait = $es7 ? 'await' : 'yield'; - } - out += ' var validate = '; - if ($async) { - if ($es7) { - out += ' (async function '; - } else { - if (it.opts.async != '*') { - out += 'co.wrap'; - } - out += '(function* '; - } - } else { - out += ' (function '; - } - out += ' (data, dataPath, parentData, parentDataProperty, rootData) { \'use strict\'; '; - if ($id && (it.opts.sourceCode || it.opts.processCode)) { - out += ' ' + ('/\*# sourceURL=' + $id + ' */') + ' '; - } - } - if (typeof it.schema == 'boolean' || !($refKeywords || it.schema.$ref)) { - var $keyword = 'false schema'; - var $lvl = it.level; - var $dataLvl = it.dataLevel; - var $schema = it.schema[$keyword]; - var $schemaPath = it.schemaPath + it.util.getProperty($keyword); - var $errSchemaPath = it.errSchemaPath + '/' + $keyword; - var $breakOnError = !it.opts.allErrors; - var $errorKeyword; - var $data = 'data' + ($dataLvl || ''); - var $valid = 'valid' + $lvl; - if (it.schema === false) { - if (it.isTop) { - $breakOnError = true; - } else { - out += ' var ' + ($valid) + ' = false; '; - } - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || 'false schema') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: {} '; - if (it.opts.messages !== false) { - out += ' , message: \'boolean schema is false\' '; - } - if (it.opts.verbose) { - out += ' , schema: false , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - } else { - if (it.isTop) { - if ($async) { - out += ' return data; '; - } else { - out += ' validate.errors = null; return true; '; - } - } else { - out += ' var ' + ($valid) + ' = true; '; - } - } - if (it.isTop) { - out += ' }); return validate; '; - } - return out; - } - if (it.isTop) { - var $top = it.isTop, - $lvl = it.level = 0, - $dataLvl = it.dataLevel = 0, - $data = 'data'; - it.rootId = it.resolve.fullPath(it.self._getId(it.root.schema)); - it.baseId = it.baseId || it.rootId; - delete it.isTop; - it.dataPathArr = [undefined]; - out += ' var vErrors = null; '; - out += ' var errors = 0; '; - out += ' if (rootData === undefined) rootData = data; '; - } else { - var $lvl = it.level, - $dataLvl = it.dataLevel, - $data = 'data' + ($dataLvl || ''); - if ($id) it.baseId = it.resolve.url(it.baseId, $id); - if ($async && !it.async) throw new Error('async schema in sync schema'); - out += ' var errs_' + ($lvl) + ' = errors;'; - } - var $valid = 'valid' + $lvl, - $breakOnError = !it.opts.allErrors, - $closingBraces1 = '', - $closingBraces2 = ''; - var $errorKeyword; - var $typeSchema = it.schema.type, - $typeIsArray = Array.isArray($typeSchema); - if ($typeIsArray && $typeSchema.length == 1) { - $typeSchema = $typeSchema[0]; - $typeIsArray = false; - } - if (it.schema.$ref && $refKeywords) { - if (it.opts.extendRefs == 'fail') { - throw new Error('$ref: validation keywords used in schema at path "' + it.errSchemaPath + '" (see option extendRefs)'); - } else if (it.opts.extendRefs !== true) { - $refKeywords = false; - console.warn('$ref: keywords ignored in schema at path "' + it.errSchemaPath + '"'); - } - } - if ($typeSchema) { - if (it.opts.coerceTypes) { - var $coerceToTypes = it.util.coerceToTypes(it.opts.coerceTypes, $typeSchema); - } - var $rulesGroup = it.RULES.types[$typeSchema]; - if ($coerceToTypes || $typeIsArray || $rulesGroup === true || ($rulesGroup && !$shouldUseGroup($rulesGroup))) { - var $schemaPath = it.schemaPath + '.type', - $errSchemaPath = it.errSchemaPath + '/type'; - var $schemaPath = it.schemaPath + '.type', - $errSchemaPath = it.errSchemaPath + '/type', - $method = $typeIsArray ? 'checkDataTypes' : 'checkDataType'; - out += ' if (' + (it.util[$method]($typeSchema, $data, true)) + ') { '; - if ($coerceToTypes) { - var $dataType = 'dataType' + $lvl, - $coerced = 'coerced' + $lvl; - out += ' var ' + ($dataType) + ' = typeof ' + ($data) + '; '; - if (it.opts.coerceTypes == 'array') { - out += ' if (' + ($dataType) + ' == \'object\' && Array.isArray(' + ($data) + ')) ' + ($dataType) + ' = \'array\'; '; - } - out += ' var ' + ($coerced) + ' = undefined; '; - var $bracesCoercion = ''; - var arr1 = $coerceToTypes; - if (arr1) { - var $type, $i = -1, - l1 = arr1.length - 1; - while ($i < l1) { - $type = arr1[$i += 1]; - if ($i) { - out += ' if (' + ($coerced) + ' === undefined) { '; - $bracesCoercion += '}'; - } - if (it.opts.coerceTypes == 'array' && $type != 'array') { - out += ' if (' + ($dataType) + ' == \'array\' && ' + ($data) + '.length == 1) { ' + ($coerced) + ' = ' + ($data) + ' = ' + ($data) + '[0]; ' + ($dataType) + ' = typeof ' + ($data) + '; } '; - } - if ($type == 'string') { - out += ' if (' + ($dataType) + ' == \'number\' || ' + ($dataType) + ' == \'boolean\') ' + ($coerced) + ' = \'\' + ' + ($data) + '; else if (' + ($data) + ' === null) ' + ($coerced) + ' = \'\'; '; - } else if ($type == 'number' || $type == 'integer') { - out += ' if (' + ($dataType) + ' == \'boolean\' || ' + ($data) + ' === null || (' + ($dataType) + ' == \'string\' && ' + ($data) + ' && ' + ($data) + ' == +' + ($data) + ' '; - if ($type == 'integer') { - out += ' && !(' + ($data) + ' % 1)'; - } - out += ')) ' + ($coerced) + ' = +' + ($data) + '; '; - } else if ($type == 'boolean') { - out += ' if (' + ($data) + ' === \'false\' || ' + ($data) + ' === 0 || ' + ($data) + ' === null) ' + ($coerced) + ' = false; else if (' + ($data) + ' === \'true\' || ' + ($data) + ' === 1) ' + ($coerced) + ' = true; '; - } else if ($type == 'null') { - out += ' if (' + ($data) + ' === \'\' || ' + ($data) + ' === 0 || ' + ($data) + ' === false) ' + ($coerced) + ' = null; '; - } else if (it.opts.coerceTypes == 'array' && $type == 'array') { - out += ' if (' + ($dataType) + ' == \'string\' || ' + ($dataType) + ' == \'number\' || ' + ($dataType) + ' == \'boolean\' || ' + ($data) + ' == null) ' + ($coerced) + ' = [' + ($data) + ']; '; - } - } - } - out += ' ' + ($bracesCoercion) + ' if (' + ($coerced) + ' === undefined) { '; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || 'type') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { type: \''; - if ($typeIsArray) { - out += '' + ($typeSchema.join(",")); - } else { - out += '' + ($typeSchema); - } - out += '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should be '; - if ($typeIsArray) { - out += '' + ($typeSchema.join(",")); - } else { - out += '' + ($typeSchema); - } - out += '\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } else { '; - var $parentData = $dataLvl ? 'data' + (($dataLvl - 1) || '') : 'parentData', - $parentDataProperty = $dataLvl ? it.dataPathArr[$dataLvl] : 'parentDataProperty'; - out += ' ' + ($data) + ' = ' + ($coerced) + '; '; - if (!$dataLvl) { - out += 'if (' + ($parentData) + ' !== undefined)'; - } - out += ' ' + ($parentData) + '[' + ($parentDataProperty) + '] = ' + ($coerced) + '; } '; - } else { - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || 'type') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { type: \''; - if ($typeIsArray) { - out += '' + ($typeSchema.join(",")); - } else { - out += '' + ($typeSchema); - } - out += '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should be '; - if ($typeIsArray) { - out += '' + ($typeSchema.join(",")); - } else { - out += '' + ($typeSchema); - } - out += '\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - } - out += ' } '; - } - } - if (it.schema.$ref && !$refKeywords) { - out += ' ' + (it.RULES.all.$ref.code(it, '$ref')) + ' '; - if ($breakOnError) { - out += ' } if (errors === '; - if ($top) { - out += '0'; - } else { - out += 'errs_' + ($lvl); - } - out += ') { '; - $closingBraces2 += '}'; - } - } else { - if (it.opts.v5 && it.schema.patternGroups) { - console.warn('keyword "patternGroups" is deprecated and disabled. Use option patternGroups: true to enable.'); - } - var arr2 = it.RULES; - if (arr2) { - var $rulesGroup, i2 = -1, - l2 = arr2.length - 1; - while (i2 < l2) { - $rulesGroup = arr2[i2 += 1]; - if ($shouldUseGroup($rulesGroup)) { - if ($rulesGroup.type) { - out += ' if (' + (it.util.checkDataType($rulesGroup.type, $data)) + ') { '; - } - if (it.opts.useDefaults && !it.compositeRule) { - if ($rulesGroup.type == 'object' && it.schema.properties) { - var $schema = it.schema.properties, - $schemaKeys = Object.keys($schema); - var arr3 = $schemaKeys; - if (arr3) { - var $propertyKey, i3 = -1, - l3 = arr3.length - 1; - while (i3 < l3) { - $propertyKey = arr3[i3 += 1]; - var $sch = $schema[$propertyKey]; - if ($sch.default !== undefined) { - var $passData = $data + it.util.getProperty($propertyKey); - out += ' if (' + ($passData) + ' === undefined) ' + ($passData) + ' = '; - if (it.opts.useDefaults == 'shared') { - out += ' ' + (it.useDefault($sch.default)) + ' '; - } else { - out += ' ' + (JSON.stringify($sch.default)) + ' '; - } - out += '; '; - } - } - } - } else if ($rulesGroup.type == 'array' && Array.isArray(it.schema.items)) { - var arr4 = it.schema.items; - if (arr4) { - var $sch, $i = -1, - l4 = arr4.length - 1; - while ($i < l4) { - $sch = arr4[$i += 1]; - if ($sch.default !== undefined) { - var $passData = $data + '[' + $i + ']'; - out += ' if (' + ($passData) + ' === undefined) ' + ($passData) + ' = '; - if (it.opts.useDefaults == 'shared') { - out += ' ' + (it.useDefault($sch.default)) + ' '; - } else { - out += ' ' + (JSON.stringify($sch.default)) + ' '; - } - out += '; '; - } - } - } - } - } - var arr5 = $rulesGroup.rules; - if (arr5) { - var $rule, i5 = -1, - l5 = arr5.length - 1; - while (i5 < l5) { - $rule = arr5[i5 += 1]; - if ($shouldUseRule($rule)) { - var $code = $rule.code(it, $rule.keyword, $rulesGroup.type); - if ($code) { - out += ' ' + ($code) + ' '; - if ($breakOnError) { - $closingBraces1 += '}'; - } - } - } - } - } - if ($breakOnError) { - out += ' ' + ($closingBraces1) + ' '; - $closingBraces1 = ''; - } - if ($rulesGroup.type) { - out += ' } '; - if ($typeSchema && $typeSchema === $rulesGroup.type && !$coerceToTypes) { - out += ' else { '; - var $schemaPath = it.schemaPath + '.type', - $errSchemaPath = it.errSchemaPath + '/type'; - var $$outStack = $$outStack || []; - $$outStack.push(out); - out = ''; /* istanbul ignore else */ - if (it.createErrors !== false) { - out += ' { keyword: \'' + ($errorKeyword || 'type') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { type: \''; - if ($typeIsArray) { - out += '' + ($typeSchema.join(",")); - } else { - out += '' + ($typeSchema); - } - out += '\' } '; - if (it.opts.messages !== false) { - out += ' , message: \'should be '; - if ($typeIsArray) { - out += '' + ($typeSchema.join(",")); - } else { - out += '' + ($typeSchema); - } - out += '\' '; - } - if (it.opts.verbose) { - out += ' , schema: validate.schema' + ($schemaPath) + ' , parentSchema: validate.schema' + (it.schemaPath) + ' , data: ' + ($data) + ' '; - } - out += ' } '; - } else { - out += ' {} '; - } - var __err = out; - out = $$outStack.pop(); - if (!it.compositeRule && $breakOnError) { /* istanbul ignore if */ - if (it.async) { - out += ' throw new ValidationError([' + (__err) + ']); '; - } else { - out += ' validate.errors = [' + (__err) + ']; return false; '; - } - } else { - out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; '; - } - out += ' } '; - } - } - if ($breakOnError) { - out += ' if (errors === '; - if ($top) { - out += '0'; - } else { - out += 'errs_' + ($lvl); - } - out += ') { '; - $closingBraces2 += '}'; - } - } - } - } - } - if ($breakOnError) { - out += ' ' + ($closingBraces2) + ' '; - } - if ($top) { - if ($async) { - out += ' if (errors === 0) return data; '; - out += ' else throw new ValidationError(vErrors); '; - } else { - out += ' validate.errors = vErrors; '; - out += ' return errors === 0; '; - } - out += ' }); return validate;'; - } else { - out += ' var ' + ($valid) + ' = errors === errs_' + ($lvl) + ';'; - } - out = it.util.cleanUpCode(out); - if ($top) { - out = it.util.finalCleanUpCode(out, $async); - } - - function $shouldUseGroup($rulesGroup) { - var rules = $rulesGroup.rules; - for (var i = 0; i < rules.length; i++) - if ($shouldUseRule(rules[i])) return true; - } - - function $shouldUseRule($rule) { - return it.schema[$rule.keyword] !== undefined || ($rule.implements && $ruleImlementsSomeKeyword($rule)); - } - - function $ruleImlementsSomeKeyword($rule) { - var impl = $rule.implements; - for (var i = 0; i < impl.length; i++) - if (it.schema[impl[i]] !== undefined) return true; - } - return out; - } - - }, {}], - 37: [function (require, module, exports) { - 'use strict'; - - var IDENTIFIER = /^[a-z_$][a-z0-9_$\-]*$/i; - var customRuleCode = require('./dotjs/custom'); - - module.exports = { - add: addKeyword, - get: getKeyword, - remove: removeKeyword - }; - - /** - * Define custom keyword - * @this Ajv - * @param {String} keyword custom keyword, should be unique (including different from all standard, custom and macro keywords). - * @param {Object} definition keyword definition object with properties `type` (type(s) which the keyword applies to), `validate` or `compile`. - */ - function addKeyword(keyword, definition) { - /* jshint validthis: true */ - /* eslint no-shadow: 0 */ - var RULES = this.RULES; - - if (RULES.keywords[keyword]) - throw new Error('Keyword ' + keyword + ' is already defined'); - - if (!IDENTIFIER.test(keyword)) - throw new Error('Keyword ' + keyword + ' is not a valid identifier'); - - if (definition) { - if (definition.macro && definition.valid !== undefined) - throw new Error('"valid" option cannot be used with macro keywords'); - - var dataType = definition.type; - if (Array.isArray(dataType)) { - var i, len = dataType.length; - for (i = 0; i < len; i++) checkDataType(dataType[i]); - for (i = 0; i < len; i++) _addRule(keyword, dataType[i], definition); - } else { - if (dataType) checkDataType(dataType); - _addRule(keyword, dataType, definition); - } - - var $data = definition.$data === true && this._opts.$data; - if ($data && !definition.validate) - throw new Error('$data support: "validate" function is not defined'); - - var metaSchema = definition.metaSchema; - if (metaSchema) { - if ($data) { - metaSchema = { - anyOf: [ - metaSchema, - { '$ref': 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/$data.json#' } - ] - }; - } - definition.validateSchema = this.compile(metaSchema, true); - } - } - - RULES.keywords[keyword] = RULES.all[keyword] = true; - - - function _addRule(keyword, dataType, definition) { - var ruleGroup; - for (var i = 0; i < RULES.length; i++) { - var rg = RULES[i]; - if (rg.type == dataType) { - ruleGroup = rg; - break; - } - } - - if (!ruleGroup) { - ruleGroup = { type: dataType, rules: [] }; - RULES.push(ruleGroup); - } - - var rule = { - keyword: keyword, - definition: definition, - custom: true, - code: customRuleCode, - implements: definition.implements - }; - ruleGroup.rules.push(rule); - RULES.custom[keyword] = rule; - } - - - function checkDataType(dataType) { - if (!RULES.types[dataType]) throw new Error('Unknown type ' + dataType); - } - } - - - /** - * Get keyword - * @this Ajv - * @param {String} keyword pre-defined or custom keyword. - * @return {Object|Boolean} custom keyword definition, `true` if it is a predefined keyword, `false` otherwise. - */ - function getKeyword(keyword) { - /* jshint validthis: true */ - var rule = this.RULES.custom[keyword]; - return rule ? rule.definition : this.RULES.keywords[keyword] || false; - } - - - /** - * Remove keyword - * @this Ajv - * @param {String} keyword pre-defined or custom keyword. - */ - function removeKeyword(keyword) { - /* jshint validthis: true */ - var RULES = this.RULES; - delete RULES.keywords[keyword]; - delete RULES.all[keyword]; - delete RULES.custom[keyword]; - for (var i = 0; i < RULES.length; i++) { - var rules = RULES[i].rules; - for (var j = 0; j < rules.length; j++) { - if (rules[j].keyword == keyword) { - rules.splice(j, 1); - break; - } - } - } - } - - }, { "./dotjs/custom": 22 }], - 38: [function (require, module, exports) { - 'use strict'; - - var META_SCHEMA_ID = 'http://json-schema.org/draft-06/schema'; - - module.exports = function (ajv) { - var defaultMeta = ajv._opts.defaultMeta; - var metaSchemaRef = typeof defaultMeta == 'string' ? - { $ref: defaultMeta } : - ajv.getSchema(META_SCHEMA_ID) ? - { $ref: META_SCHEMA_ID } : - {}; - - ajv.addKeyword('patternGroups', { - // implemented in properties.jst - metaSchema: { - type: 'object', - additionalProperties: { - type: 'object', - required: ['schema'], - properties: { - maximum: { - type: 'integer', - minimum: 0 - }, - minimum: { - type: 'integer', - minimum: 0 - }, - schema: metaSchemaRef - }, - additionalProperties: false - } - } - }); - ajv.RULES.all.properties.implements.push('patternGroups'); - }; - - }, {}], - 39: [function (require, module, exports) { - module.exports = { - "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/$data.json#", - "description": "Meta-schema for $data reference (JSON-schema extension proposal)", - "type": "object", - "required": ["$data"], - "properties": { - "$data": { - "type": "string", - "anyOf": [ - { "format": "relative-json-pointer" }, - { "format": "json-pointer" } - ] - } - }, - "additionalProperties": false - } - - }, {}], - 40: [function (require, module, exports) { - module.exports = { - "$schema": "http://json-schema.org/draft-06/schema#", - "$id": "http://json-schema.org/draft-06/schema#", - "title": "Core schema meta-schema", - "definitions": { - "schemaArray": { - "type": "array", - "minItems": 1, - "items": { "$ref": "#" } - }, - "nonNegativeInteger": { - "type": "integer", - "minimum": 0 - }, - "nonNegativeIntegerDefault0": { - "allOf": [ - { "$ref": "#/definitions/nonNegativeInteger" }, - { "default": 0 } - ] - }, - "simpleTypes": { - "enum": [ - "array", - "boolean", - "integer", - "null", - "number", - "object", - "string" - ] - }, - "stringArray": { - "type": "array", - "items": { "type": "string" }, - "uniqueItems": true, - "default": [] - } - }, - "type": ["object", "boolean"], - "properties": { - "$id": { - "type": "string", - "format": "uri-reference" - }, - "$schema": { - "type": "string", - "format": "uri" - }, - "$ref": { - "type": "string", - "format": "uri-reference" - }, - "title": { - "type": "string" - }, - "description": { - "type": "string" - }, - "default": {}, - "multipleOf": { - "type": "number", - "exclusiveMinimum": 0 - }, - "maximum": { - "type": "number" - }, - "exclusiveMaximum": { - "type": "number" - }, - "minimum": { - "type": "number" - }, - "exclusiveMinimum": { - "type": "number" - }, - "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, - "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "pattern": { - "type": "string", - "format": "regex" - }, - "additionalItems": { "$ref": "#" }, - "items": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/schemaArray" } - ], - "default": {} - }, - "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, - "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "uniqueItems": { - "type": "boolean", - "default": false - }, - "contains": { "$ref": "#" }, - "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, - "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, - "required": { "$ref": "#/definitions/stringArray" }, - "additionalProperties": { "$ref": "#" }, - "definitions": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "properties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "patternProperties": { - "type": "object", - "additionalProperties": { "$ref": "#" }, - "default": {} - }, - "dependencies": { - "type": "object", - "additionalProperties": { - "anyOf": [ - { "$ref": "#" }, - { "$ref": "#/definitions/stringArray" } - ] - } - }, - "propertyNames": { "$ref": "#" }, - "const": {}, - "enum": { - "type": "array", - "minItems": 1, - "uniqueItems": true - }, - "type": { - "anyOf": [ - { "$ref": "#/definitions/simpleTypes" }, - { - "type": "array", - "items": { "$ref": "#/definitions/simpleTypes" }, - "minItems": 1, - "uniqueItems": true - } - ] - }, - "format": { "type": "string" }, - "allOf": { "$ref": "#/definitions/schemaArray" }, - "anyOf": { "$ref": "#/definitions/schemaArray" }, - "oneOf": { "$ref": "#/definitions/schemaArray" }, - "not": { "$ref": "#" } - }, - "default": {} - } - - }, {}], - 41: [function (require, module, exports) { - - /** - * slice() reference. - */ - - var slice = Array.prototype.slice; - - /** - * Expose `co`. - */ - - module.exports = co['default'] = co.co = co; - - /** - * Wrap the given generator `fn` into a - * function that returns a promise. - * This is a separate function so that - * every `co()` call doesn't create a new, - * unnecessary closure. - * - * @param {GeneratorFunction} fn - * @return {Function} - * @api public - */ - - co.wrap = function (fn) { - createPromise.__generatorFunction__ = fn; - return createPromise; - - function createPromise() { - return co.call(this, fn.apply(this, arguments)); - } - }; - - /** - * Execute the generator function or a generator - * and return a promise. - * - * @param {Function} fn - * @return {Promise} - * @api public - */ - - function co(gen) { - var ctx = this; - var args = slice.call(arguments, 1) - - // we wrap everything in a promise to avoid promise chaining, - // which leads to memory leak errors. - // see https://github.com/tj/co/issues/180 - return new Promise(function (resolve, reject) { - if (typeof gen === 'function') gen = gen.apply(ctx, args); - if (!gen || typeof gen.next !== 'function') return resolve(gen); - - onFulfilled(); - - /** - * @param {Mixed} res - * @return {Promise} - * @api private - */ - - function onFulfilled(res) { - var ret; - try { - ret = gen.next(res); - } catch (e) { - return reject(e); - } - next(ret); - } - - /** - * @param {Error} err - * @return {Promise} - * @api private - */ - - function onRejected(err) { - var ret; - try { - ret = gen.throw(err); - } catch (e) { - return reject(e); - } - next(ret); - } - - /** - * Get the next value in the generator, - * return a promise. - * - * @param {Object} ret - * @return {Promise} - * @api private - */ - - function next(ret) { - if (ret.done) return resolve(ret.value); - var value = toPromise.call(ctx, ret.value); - if (value && isPromise(value)) return value.then(onFulfilled, onRejected); - return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + - 'but the following object was passed: "' + String(ret.value) + '"')); - } - }); - } - - /** - * Convert a `yield`ed value into a promise. - * - * @param {Mixed} obj - * @return {Promise} - * @api private - */ - - function toPromise(obj) { - if (!obj) return obj; - if (isPromise(obj)) return obj; - if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); - if ('function' == typeof obj) return thunkToPromise.call(this, obj); - if (Array.isArray(obj)) return arrayToPromise.call(this, obj); - if (isObject(obj)) return objectToPromise.call(this, obj); - return obj; - } - - /** - * Convert a thunk to a promise. - * - * @param {Function} - * @return {Promise} - * @api private - */ - - function thunkToPromise(fn) { - var ctx = this; - return new Promise(function (resolve, reject) { - fn.call(ctx, function (err, res) { - if (err) return reject(err); - if (arguments.length > 2) res = slice.call(arguments, 1); - resolve(res); - }); - }); - } - - /** - * Convert an array of "yieldables" to a promise. - * Uses `Promise.all()` internally. - * - * @param {Array} obj - * @return {Promise} - * @api private - */ - - function arrayToPromise(obj) { - return Promise.all(obj.map(toPromise, this)); - } - - /** - * Convert an object of "yieldables" to a promise. - * Uses `Promise.all()` internally. - * - * @param {Object} obj - * @return {Promise} - * @api private - */ - - function objectToPromise(obj) { - var results = new obj.constructor(); - var keys = Object.keys(obj); - var promises = []; - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var promise = toPromise.call(this, obj[key]); - if (promise && isPromise(promise)) defer(promise, key); - else results[key] = obj[key]; - } - return Promise.all(promises).then(function () { - return results; - }); - - function defer(promise, key) { - // predefine the key in the result - results[key] = undefined; - promises.push(promise.then(function (res) { - results[key] = res; - })); - } - } - - /** - * Check if `obj` is a promise. - * - * @param {Object} obj - * @return {Boolean} - * @api private - */ - - function isPromise(obj) { - return 'function' == typeof obj.then; - } - - /** - * Check if `obj` is a generator. - * - * @param {Mixed} obj - * @return {Boolean} - * @api private - */ - - function isGenerator(obj) { - return 'function' == typeof obj.next && 'function' == typeof obj.throw; - } - - /** - * Check if `obj` is a generator function. - * - * @param {Mixed} obj - * @return {Boolean} - * @api private - */ - function isGeneratorFunction(obj) { - var constructor = obj.constructor; - if (!constructor) return false; - if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true; - return isGenerator(constructor.prototype); - } - - /** - * Check for plain object. - * - * @param {Mixed} val - * @return {Boolean} - * @api private - */ - - function isObject(val) { - return Object == val.constructor; - } - - }, {}], - 42: [function (require, module, exports) { - var json = typeof JSON !== 'undefined' ? JSON : require('jsonify'); - - module.exports = function (obj, opts) { - if (!opts) opts = {}; - if (typeof opts === 'function') opts = { cmp: opts }; - var space = opts.space || ''; - if (typeof space === 'number') space = Array(space + 1).join(' '); - var cycles = (typeof opts.cycles === 'boolean') ? opts.cycles : false; - var replacer = opts.replacer || function (key, value) { return value; }; - - var cmp = opts.cmp && (function (f) { - return function (node) { - return function (a, b) { - var aobj = { key: a, value: node[a] }; - var bobj = { key: b, value: node[b] }; - return f(aobj, bobj); - }; - }; - })(opts.cmp); - - var seen = []; - return (function stringify(parent, key, node, level) { - var indent = space ? ('\n' + new Array(level + 1).join(space)) : ''; - var colonSeparator = space ? ': ' : ':'; - - if (node && node.toJSON && typeof node.toJSON === 'function') { - node = node.toJSON(); - } - - node = replacer.call(parent, key, node); - - if (node === undefined) { - return; - } - if (typeof node !== 'object' || node === null) { - return json.stringify(node); - } - if (isArray(node)) { - var out = []; - for (var i = 0; i < node.length; i++) { - var item = stringify(node, i, node[i], level + 1) || json.stringify(null); - out.push(indent + space + item); - } - return '[' + out.join(',') + indent + ']'; - } else { - if (seen.indexOf(node) !== -1) { - if (cycles) return json.stringify('__cycle__'); - throw new TypeError('Converting circular structure to JSON'); - } else seen.push(node); - - var keys = objectKeys(node).sort(cmp && cmp(node)); - var out = []; - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - var value = stringify(node, key, node[key], level + 1); - - if (!value) continue; - - var keyValue = json.stringify(key) + - colonSeparator + - value;; - out.push(indent + space + keyValue); - } - seen.splice(seen.indexOf(node), 1); - return '{' + out.join(',') + indent + '}'; - } - })({ '': obj }, '', obj, 0); - }; - - var isArray = Array.isArray || function (x) { - return {}.toString.call(x) === '[object Array]'; - }; - - var objectKeys = Object.keys || function (obj) { - var has = Object.prototype.hasOwnProperty || function () { return true }; - var keys = []; - for (var key in obj) { - if (has.call(obj, key)) keys.push(key); - } - return keys; - }; - - }, { "jsonify": 43 }], - 43: [function (require, module, exports) { - exports.parse = require('./lib/parse'); - exports.stringify = require('./lib/stringify'); - - }, { "./lib/parse": 44, "./lib/stringify": 45 }], - 44: [function (require, module, exports) { - var at, // The index of the current character - ch, // The current character - escapee = { - '"': '"', - '\\': '\\', - '/': '/', - b: '\b', - f: '\f', - n: '\n', - r: '\r', - t: '\t' - }, - text, - - error = function (m) { - // Call error when something is wrong. - throw { - name: 'SyntaxError', - message: m, - at: at, - text: text - }; - }, - - next = function (c) { - // If a c parameter is provided, verify that it matches the current character. - if (c && c !== ch) { - error("Expected '" + c + "' instead of '" + ch + "'"); - } - - // Get the next character. When there are no more characters, - // return the empty string. - - ch = text.charAt(at); - at += 1; - return ch; - }, - - number = function () { - // Parse a number value. - var number, - string = ''; - - if (ch === '-') { - string = '-'; - next('-'); - } - while (ch >= '0' && ch <= '9') { - string += ch; - next(); - } - if (ch === '.') { - string += '.'; - while (next() && ch >= '0' && ch <= '9') { - string += ch; - } - } - if (ch === 'e' || ch === 'E') { - string += ch; - next(); - if (ch === '-' || ch === '+') { - string += ch; - next(); - } - while (ch >= '0' && ch <= '9') { - string += ch; - next(); - } - } - number = +string; - if (!isFinite(number)) { - error("Bad number"); - } else { - return number; - } - }, - - string = function () { - // Parse a string value. - var hex, - i, - string = '', - uffff; - - // When parsing for string values, we must look for " and \ characters. - if (ch === '"') { - while (next()) { - if (ch === '"') { - next(); - return string; - } else if (ch === '\\') { - next(); - if (ch === 'u') { - uffff = 0; - for (i = 0; i < 4; i += 1) { - hex = parseInt(next(), 16); - if (!isFinite(hex)) { - break; - } - uffff = uffff * 16 + hex; - } - string += String.fromCharCode(uffff); - } else if (typeof escapee[ch] === 'string') { - string += escapee[ch]; - } else { - break; - } - } else { - string += ch; - } - } - } - error("Bad string"); - }, - - white = function () { - - // Skip whitespace. - - while (ch && ch <= ' ') { - next(); - } - }, - - word = function () { - - // true, false, or null. - - switch (ch) { - case 't': - next('t'); - next('r'); - next('u'); - next('e'); - return true; - case 'f': - next('f'); - next('a'); - next('l'); - next('s'); - next('e'); - return false; - case 'n': - next('n'); - next('u'); - next('l'); - next('l'); - return null; - } - error("Unexpected '" + ch + "'"); - }, - - value, // Place holder for the value function. - - array = function () { - - // Parse an array value. - - var array = []; - - if (ch === '[') { - next('['); - white(); - if (ch === ']') { - next(']'); - return array; // empty array - } - while (ch) { - array.push(value()); - white(); - if (ch === ']') { - next(']'); - return array; - } - next(','); - white(); - } - } - error("Bad array"); - }, - - object = function () { - - // Parse an object value. - - var key, - object = {}; - - if (ch === '{') { - next('{'); - white(); - if (ch === '}') { - next('}'); - return object; // empty object - } - while (ch) { - key = string(); - white(); - next(':'); - if (Object.hasOwnProperty.call(object, key)) { - error('Duplicate key "' + key + '"'); - } - object[key] = value(); - white(); - if (ch === '}') { - next('}'); - return object; - } - next(','); - white(); - } - } - error("Bad object"); - }; - - value = function () { - - // Parse a JSON value. It could be an object, an array, a string, a number, - // or a word. - - white(); - switch (ch) { - case '{': - return object(); - case '[': - return array(); - case '"': - return string(); - case '-': - return number(); - default: - return ch >= '0' && ch <= '9' ? number() : word(); - } - }; - - // Return the json_parse function. It will have access to all of the above - // functions and variables. - - module.exports = function (source, reviver) { - var result; - - text = source; - at = 0; - ch = ' '; - result = value(); - white(); - if (ch) { - error("Syntax error"); - } - - // If there is a reviver function, we recursively walk the new structure, - // passing each name/value pair to the reviver function for possible - // transformation, starting with a temporary root object that holds the result - // in an empty key. If there is not a reviver function, we simply return the - // result. - - return typeof reviver === 'function' ? (function walk(holder, key) { - var k, v, value = holder[key]; - if (value && typeof value === 'object') { - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = walk(value, k); - if (v !== undefined) { - value[k] = v; - } else { - delete value[k]; - } - } - } - } - return reviver.call(holder, key, value); - }({ '': result }, '')) : result; - }; - - }, {}], - 45: [function (require, module, exports) { - var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, - gap, - indent, - meta = { // table of character substitutions - '\b': '\\b', - '\t': '\\t', - '\n': '\\n', - '\f': '\\f', - '\r': '\\r', - '"': '\\"', - '\\': '\\\\' - }, - rep; - - function quote(string) { - // If the string contains no control characters, no quote characters, and no - // backslash characters, then we can safely slap some quotes around it. - // Otherwise we must also replace the offending characters with safe escape - // sequences. - - escapable.lastIndex = 0; - return escapable.test(string) ? '"' + string.replace(escapable, function (a) { - var c = meta[a]; - return typeof c === 'string' ? c : - '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); - }) + '"' : '"' + string + '"'; - } - - function str(key, holder) { - // Produce a string from holder[key]. - var i, // The loop counter. - k, // The member key. - v, // The member value. - length, - mind = gap, - partial, - value = holder[key]; - - // If the value has a toJSON method, call it to obtain a replacement value. - if (value && typeof value === 'object' && - typeof value.toJSON === 'function') { - value = value.toJSON(key); - } - - // If we were called with a replacer function, then call the replacer to - // obtain a replacement value. - if (typeof rep === 'function') { - value = rep.call(holder, key, value); - } - - // What happens next depends on the value's type. - switch (typeof value) { - case 'string': - return quote(value); - - case 'number': - // JSON numbers must be finite. Encode non-finite numbers as null. - return isFinite(value) ? String(value) : 'null'; - - case 'boolean': - case 'null': - // If the value is a boolean or null, convert it to a string. Note: - // typeof null does not produce 'null'. The case is included here in - // the remote chance that this gets fixed someday. - return String(value); - - case 'object': - if (!value) return 'null'; - gap += indent; - partial = []; - - // Array.isArray - if (Object.prototype.toString.apply(value) === '[object Array]') { - length = value.length; - for (i = 0; i < length; i += 1) { - partial[i] = str(i, value) || 'null'; - } - - // Join all of the elements together, separated with commas, and - // wrap them in brackets. - v = partial.length === 0 ? '[]' : gap ? - '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : - '[' + partial.join(',') + ']'; - gap = mind; - return v; - } - - // If the replacer is an array, use it to select the members to be - // stringified. - if (rep && typeof rep === 'object') { - length = rep.length; - for (i = 0; i < length; i += 1) { - k = rep[i]; - if (typeof k === 'string') { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } else { - // Otherwise, iterate through all of the keys in the object. - for (k in value) { - if (Object.prototype.hasOwnProperty.call(value, k)) { - v = str(k, value); - if (v) { - partial.push(quote(k) + (gap ? ': ' : ':') + v); - } - } - } - } - - // Join all of the member texts together, separated with commas, - // and wrap them in braces. - - v = partial.length === 0 ? '{}' : gap ? - '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : - '{' + partial.join(',') + '}'; - gap = mind; - return v; - } - } - - module.exports = function (value, replacer, space) { - var i; - gap = ''; - indent = ''; - - // If the space parameter is a number, make an indent string containing that - // many spaces. - if (typeof space === 'number') { - for (i = 0; i < space; i += 1) { - indent += ' '; - } - } - // If the space parameter is a string, it will be used as the indent string. - else if (typeof space === 'string') { - indent = space; - } - - // If there is a replacer, it must be a function or an array. - // Otherwise, throw an error. - rep = replacer; - if (replacer && typeof replacer !== 'function' && - (typeof replacer !== 'object' || typeof replacer.length !== 'number')) { - throw new Error('JSON.stringify'); - } - - // Make a fake root object containing our value under the key of ''. - // Return the result of stringifying the value. - return str('', { '': value }); - }; - - }, {}], - 46: [function (require, module, exports) { - (function (global) { - /*! https://mths.be/punycode v1.4.1 by @mathias */ - ; - (function (root) { - - /** Detect free variables */ - var freeExports = typeof exports == 'object' && exports && - !exports.nodeType && exports; - var freeModule = typeof module == 'object' && module && - !module.nodeType && module; - var freeGlobal = typeof global == 'object' && global; - if ( - freeGlobal.global === freeGlobal || - freeGlobal.window === freeGlobal || - freeGlobal.self === freeGlobal - ) { - root = freeGlobal; - } - - /** - * The `punycode` object. - * @name punycode - * @type Object - */ - var punycode, - - /** Highest positive signed 32-bit float value */ - maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1 - - /** Bootstring parameters */ - base = 36, - tMin = 1, - tMax = 26, - skew = 38, - damp = 700, - initialBias = 72, - initialN = 128, // 0x80 - delimiter = '-', // '\x2D' - - /** Regular expressions */ - regexPunycode = /^xn--/, - regexNonASCII = /[^\x20-\x7E]/, // unprintable ASCII chars + non-ASCII chars - regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 separators - - /** Error messages */ - errors = { - 'overflow': 'Overflow: input needs wider integers to process', - 'not-basic': 'Illegal input >= 0x80 (not a basic code point)', - 'invalid-input': 'Invalid input' - }, - - /** Convenience shortcuts */ - baseMinusTMin = base - tMin, - floor = Math.floor, - stringFromCharCode = String.fromCharCode, - - /** Temporary variable */ - key; - - /*--------------------------------------------------------------------------*/ - - /** - * A generic error utility function. - * @private - * @param {String} type The error type. - * @returns {Error} Throws a `RangeError` with the applicable error message. - */ - function error(type) { - throw new RangeError(errors[type]); - } - - /** - * A generic `Array#map` utility function. - * @private - * @param {Array} array The array to iterate over. - * @param {Function} callback The function that gets called for every array - * item. - * @returns {Array} A new array of values returned by the callback function. - */ - function map(array, fn) { - var length = array.length; - var result = []; - while (length--) { - result[length] = fn(array[length]); - } - return result; - } - - /** - * A simple `Array#map`-like wrapper to work with domain name strings or email - * addresses. - * @private - * @param {String} domain The domain name or email address. - * @param {Function} callback The function that gets called for every - * character. - * @returns {Array} A new string of characters returned by the callback - * function. - */ - function mapDomain(string, fn) { - var parts = string.split('@'); - var result = ''; - if (parts.length > 1) { - // In email addresses, only the domain name should be punycoded. Leave - // the local part (i.e. everything up to `@`) intact. - result = parts[0] + '@'; - string = parts[1]; - } - // Avoid `split(regex)` for IE8 compatibility. See #17. - string = string.replace(regexSeparators, '\x2E'); - var labels = string.split('.'); - var encoded = map(labels, fn).join('.'); - return result + encoded; - } - - /** - * Creates an array containing the numeric code points of each Unicode - * character in the string. While JavaScript uses UCS-2 internally, - * this function will convert a pair of surrogate halves (each of which - * UCS-2 exposes as separate characters) into a single code point, - * matching UTF-16. - * @see `punycode.ucs2.encode` - * @see - * @memberOf punycode.ucs2 - * @name decode - * @param {String} string The Unicode input string (UCS-2). - * @returns {Array} The new array of code points. - */ - function ucs2decode(string) { - var output = [], - counter = 0, - length = string.length, - value, - extra; - while (counter < length) { - value = string.charCodeAt(counter++); - if (value >= 0xD800 && value <= 0xDBFF && counter < length) { - // high surrogate, and there is a next character - extra = string.charCodeAt(counter++); - if ((extra & 0xFC00) == 0xDC00) { // low surrogate - output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000); - } else { - // unmatched surrogate; only append this code unit, in case the next - // code unit is the high surrogate of a surrogate pair - output.push(value); - counter--; - } - } else { - output.push(value); - } - } - return output; - } - - /** - * Creates a string based on an array of numeric code points. - * @see `punycode.ucs2.decode` - * @memberOf punycode.ucs2 - * @name encode - * @param {Array} codePoints The array of numeric code points. - * @returns {String} The new Unicode string (UCS-2). - */ - function ucs2encode(array) { - return map(array, function (value) { - var output = ''; - if (value > 0xFFFF) { - value -= 0x10000; - output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800); - value = 0xDC00 | value & 0x3FF; - } - output += stringFromCharCode(value); - return output; - }).join(''); - } - - /** - * Converts a basic code point into a digit/integer. - * @see `digitToBasic()` - * @private - * @param {Number} codePoint The basic numeric code point value. - * @returns {Number} The numeric value of a basic code point (for use in - * representing integers) in the range `0` to `base - 1`, or `base` if - * the code point does not represent a value. - */ - function basicToDigit(codePoint) { - if (codePoint - 48 < 10) { - return codePoint - 22; - } - if (codePoint - 65 < 26) { - return codePoint - 65; - } - if (codePoint - 97 < 26) { - return codePoint - 97; - } - return base; - } - - /** - * Converts a digit/integer into a basic code point. - * @see `basicToDigit()` - * @private - * @param {Number} digit The numeric value of a basic code point. - * @returns {Number} The basic code point whose value (when used for - * representing integers) is `digit`, which needs to be in the range - * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is - * used; else, the lowercase form is used. The behavior is undefined - * if `flag` is non-zero and `digit` has no uppercase form. - */ - function digitToBasic(digit, flag) { - // 0..25 map to ASCII a..z or A..Z - // 26..35 map to ASCII 0..9 - return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5); - } - - /** - * Bias adaptation function as per section 3.4 of RFC 3492. - * https://tools.ietf.org/html/rfc3492#section-3.4 - * @private - */ - function adapt(delta, numPoints, firstTime) { - var k = 0; - delta = firstTime ? floor(delta / damp) : delta >> 1; - delta += floor(delta / numPoints); - for ( /* no initialization */ ; delta > baseMinusTMin * tMax >> 1; k += base) { - delta = floor(delta / baseMinusTMin); - } - return floor(k + (baseMinusTMin + 1) * delta / (delta + skew)); - } - - /** - * Converts a Punycode string of ASCII-only symbols to a string of Unicode - * symbols. - * @memberOf punycode - * @param {String} input The Punycode string of ASCII-only symbols. - * @returns {String} The resulting string of Unicode symbols. - */ - function decode(input) { - // Don't use UCS-2 - var output = [], - inputLength = input.length, - out, - i = 0, - n = initialN, - bias = initialBias, - basic, - j, - index, - oldi, - w, - k, - digit, - t, - /** Cached calculation results */ - baseMinusT; - - // Handle the basic code points: let `basic` be the number of input code - // points before the last delimiter, or `0` if there is none, then copy - // the first basic code points to the output. - - basic = input.lastIndexOf(delimiter); - if (basic < 0) { - basic = 0; - } - - for (j = 0; j < basic; ++j) { - // if it's not a basic code point - if (input.charCodeAt(j) >= 0x80) { - error('not-basic'); - } - output.push(input.charCodeAt(j)); - } - - // Main decoding loop: start just after the last delimiter if any basic code - // points were copied; start at the beginning otherwise. - - for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */ ) { - - // `index` is the index of the next character to be consumed. - // Decode a generalized variable-length integer into `delta`, - // which gets added to `i`. The overflow checking is easier - // if we increase `i` as we go, then subtract off its starting - // value at the end to obtain `delta`. - for (oldi = i, w = 1, k = base; /* no condition */ ; k += base) { - - if (index >= inputLength) { - error('invalid-input'); - } - - digit = basicToDigit(input.charCodeAt(index++)); - - if (digit >= base || digit > floor((maxInt - i) / w)) { - error('overflow'); - } - - i += digit * w; - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - - if (digit < t) { - break; - } - - baseMinusT = base - t; - if (w > floor(maxInt / baseMinusT)) { - error('overflow'); - } - - w *= baseMinusT; - - } - - out = output.length + 1; - bias = adapt(i - oldi, out, oldi == 0); - - // `i` was supposed to wrap around from `out` to `0`, - // incrementing `n` each time, so we'll fix that now: - if (floor(i / out) > maxInt - n) { - error('overflow'); - } - - n += floor(i / out); - i %= out; - - // Insert `n` at position `i` of the output - output.splice(i++, 0, n); - - } - - return ucs2encode(output); - } - - /** - * Converts a string of Unicode symbols (e.g. a domain name label) to a - * Punycode string of ASCII-only symbols. - * @memberOf punycode - * @param {String} input The string of Unicode symbols. - * @returns {String} The resulting Punycode string of ASCII-only symbols. - */ - function encode(input) { - var n, - delta, - handledCPCount, - basicLength, - bias, - j, - m, - q, - k, - t, - currentValue, - output = [], - /** `inputLength` will hold the number of code points in `input`. */ - inputLength, - /** Cached calculation results */ - handledCPCountPlusOne, - baseMinusT, - qMinusT; - - // Convert the input in UCS-2 to Unicode - input = ucs2decode(input); - - // Cache the length - inputLength = input.length; - - // Initialize the state - n = initialN; - delta = 0; - bias = initialBias; - - // Handle the basic code points - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue < 0x80) { - output.push(stringFromCharCode(currentValue)); - } - } - - handledCPCount = basicLength = output.length; - - // `handledCPCount` is the number of code points that have been handled; - // `basicLength` is the number of basic code points. - - // Finish the basic string - if it is not empty - with a delimiter - if (basicLength) { - output.push(delimiter); - } - - // Main encoding loop: - while (handledCPCount < inputLength) { - - // All non-basic code points < n have been handled already. Find the next - // larger one: - for (m = maxInt, j = 0; j < inputLength; ++j) { - currentValue = input[j]; - if (currentValue >= n && currentValue < m) { - m = currentValue; - } - } - - // Increase `delta` enough to advance the decoder's state to , - // but guard against overflow - handledCPCountPlusOne = handledCPCount + 1; - if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) { - error('overflow'); - } - - delta += (m - n) * handledCPCountPlusOne; - n = m; - - for (j = 0; j < inputLength; ++j) { - currentValue = input[j]; - - if (currentValue < n && ++delta > maxInt) { - error('overflow'); - } - - if (currentValue == n) { - // Represent delta as a generalized variable-length integer - for (q = delta, k = base; /* no condition */ ; k += base) { - t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias); - if (q < t) { - break; - } - qMinusT = q - t; - baseMinusT = base - t; - output.push( - stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0)) - ); - q = floor(qMinusT / baseMinusT); - } - - output.push(stringFromCharCode(digitToBasic(q, 0))); - bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength); - delta = 0; - ++handledCPCount; - } - } - - ++delta; - ++n; - - } - return output.join(''); - } - - /** - * Converts a Punycode string representing a domain name or an email address - * to Unicode. Only the Punycoded parts of the input will be converted, i.e. - * it doesn't matter if you call it on a string that has already been - * converted to Unicode. - * @memberOf punycode - * @param {String} input The Punycoded domain name or email address to - * convert to Unicode. - * @returns {String} The Unicode representation of the given Punycode - * string. - */ - function toUnicode(input) { - return mapDomain(input, function (string) { - return regexPunycode.test(string) ? - decode(string.slice(4).toLowerCase()) : - string; - }); - } - - /** - * Converts a Unicode string representing a domain name or an email address to - * Punycode. Only the non-ASCII parts of the domain name will be converted, - * i.e. it doesn't matter if you call it with a domain that's already in - * ASCII. - * @memberOf punycode - * @param {String} input The domain name or email address to convert, as a - * Unicode string. - * @returns {String} The Punycode representation of the given domain name or - * email address. - */ - function toASCII(input) { - return mapDomain(input, function (string) { - return regexNonASCII.test(string) ? - 'xn--' + encode(string) : - string; - }); - } - - /*--------------------------------------------------------------------------*/ - - /** Define the public API */ - punycode = { - /** - * A string representing the current Punycode.js version number. - * @memberOf punycode - * @type String - */ - 'version': '1.4.1', - /** - * An object of methods to convert from JavaScript's internal character - * representation (UCS-2) to Unicode code points, and back. - * @see - * @memberOf punycode - * @type Object - */ - 'ucs2': { - 'decode': ucs2decode, - 'encode': ucs2encode - }, - 'decode': decode, - 'encode': encode, - 'toASCII': toASCII, - 'toUnicode': toUnicode - }; - - /** Expose `punycode` */ - // Some AMD build optimizers, like r.js, check for specific condition patterns - // like the following: - if ( - typeof define == 'function' && - typeof define.amd == 'object' && - define.amd - ) { - define('punycode', function () { - return punycode; - }); - } else if (freeExports && freeModule) { - if (module.exports == freeExports) { - // in Node.js, io.js, or RingoJS v0.8.0+ - freeModule.exports = punycode; - } else { - // in Narwhal or RingoJS v0.7.0- - for (key in punycode) { - punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]); - } - } - } else { - // in Rhino or a web browser - root.punycode = punycode; - } - - }(this)); - - }).call(this, typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) - }, {}], - 47: [function (require, module, exports) { - // Copyright Joyent, Inc. and other Node contributors. - // - // Permission is hereby granted, free of charge, to any person obtaining a - // copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to permit - // persons to whom the Software is furnished to do so, subject to the - // following conditions: - // - // The above copyright notice and this permission notice shall be included - // in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE - // USE OR OTHER DEALINGS IN THE SOFTWARE. - - 'use strict'; - - // If obj.hasOwnProperty has been overridden, then calling - // obj.hasOwnProperty(prop) will break. - // See: https://github.com/joyent/node/issues/1707 - function hasOwnProperty(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); - } - - module.exports = function (qs, sep, eq, options) { - sep = sep || '&'; - eq = eq || '='; - var obj = {}; - - if (typeof qs !== 'string' || qs.length === 0) { - return obj; - } - - var regexp = /\+/g; - qs = qs.split(sep); - - var maxKeys = 1000; - if (options && typeof options.maxKeys === 'number') { - maxKeys = options.maxKeys; - } - - var len = qs.length; - // maxKeys <= 0 means that we should not limit keys count - if (maxKeys > 0 && len > maxKeys) { - len = maxKeys; - } - - for (var i = 0; i < len; ++i) { - var x = qs[i].replace(regexp, '%20'), - idx = x.indexOf(eq), - kstr, vstr, k, v; - - if (idx >= 0) { - kstr = x.substr(0, idx); - vstr = x.substr(idx + 1); - } else { - kstr = x; - vstr = ''; - } - - k = decodeURIComponent(kstr); - v = decodeURIComponent(vstr); - - if (!hasOwnProperty(obj, k)) { - obj[k] = v; - } else if (isArray(obj[k])) { - obj[k].push(v); - } else { - obj[k] = [obj[k], v]; - } - } - - return obj; - }; - - var isArray = Array.isArray || function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]'; - }; - - }, {}], - 48: [function (require, module, exports) { - // Copyright Joyent, Inc. and other Node contributors. - // - // Permission is hereby granted, free of charge, to any person obtaining a - // copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to permit - // persons to whom the Software is furnished to do so, subject to the - // following conditions: - // - // The above copyright notice and this permission notice shall be included - // in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE - // USE OR OTHER DEALINGS IN THE SOFTWARE. - - 'use strict'; - - var stringifyPrimitive = function (v) { - switch (typeof v) { - case 'string': - return v; - - case 'boolean': - return v ? 'true' : 'false'; - - case 'number': - return isFinite(v) ? v : ''; - - default: - return ''; - } - }; - - module.exports = function (obj, sep, eq, name) { - sep = sep || '&'; - eq = eq || '='; - if (obj === null) { - obj = undefined; - } - - if (typeof obj === 'object') { - return map(objectKeys(obj), function (k) { - var ks = encodeURIComponent(stringifyPrimitive(k)) + eq; - if (isArray(obj[k])) { - return map(obj[k], function (v) { - return ks + encodeURIComponent(stringifyPrimitive(v)); - }).join(sep); - } else { - return ks + encodeURIComponent(stringifyPrimitive(obj[k])); - } - }).join(sep); - - } - - if (!name) return ''; - return encodeURIComponent(stringifyPrimitive(name)) + eq + - encodeURIComponent(stringifyPrimitive(obj)); - }; - - var isArray = Array.isArray || function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]'; - }; - - function map(xs, f) { - if (xs.map) return xs.map(f); - var res = []; - for (var i = 0; i < xs.length; i++) { - res.push(f(xs[i], i)); - } - return res; - } - - var objectKeys = Object.keys || function (obj) { - var res = []; - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) res.push(key); - } - return res; - }; - - }, {}], - 49: [function (require, module, exports) { - 'use strict'; - - exports.decode = exports.parse = require('./decode'); - exports.encode = exports.stringify = require('./encode'); - - }, { "./decode": 47, "./encode": 48 }], - 50: [function (require, module, exports) { - // Copyright Joyent, Inc. and other Node contributors. - // - // Permission is hereby granted, free of charge, to any person obtaining a - // copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to permit - // persons to whom the Software is furnished to do so, subject to the - // following conditions: - // - // The above copyright notice and this permission notice shall be included - // in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE - // USE OR OTHER DEALINGS IN THE SOFTWARE. - - 'use strict'; - - var punycode = require('punycode'); - var util = require('./util'); - - exports.parse = urlParse; - exports.resolve = urlResolve; - exports.resolveObject = urlResolveObject; - exports.format = urlFormat; - - exports.Url = Url; - - function Url() { - this.protocol = null; - this.slashes = null; - this.auth = null; - this.host = null; - this.port = null; - this.hostname = null; - this.hash = null; - this.search = null; - this.query = null; - this.pathname = null; - this.path = null; - this.href = null; - } - - // Reference: RFC 3986, RFC 1808, RFC 2396 - - // define these here so at least they only have to be - // compiled once on the first module load. - var protocolPattern = /^([a-z0-9.+-]+:)/i, - portPattern = /:[0-9]*$/, - - // Special case for a simple path URL - simplePathPattern = /^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/, - - // RFC 2396: characters reserved for delimiting URLs. - // We actually just auto-escape these. - delims = ['<', '>', '"', '`', ' ', '\r', '\n', '\t'], - - // RFC 2396: characters not allowed for various reasons. - unwise = ['{', '}', '|', '\\', '^', '`'].concat(delims), - - // Allowed by RFCs, but cause of XSS attacks. Always escape these. - autoEscape = ['\''].concat(unwise), - // Characters that are never ever allowed in a hostname. - // Note that any invalid chars are also handled, but these - // are the ones that are *expected* to be seen, so we fast-path - // them. - nonHostChars = ['%', '/', '?', ';', '#'].concat(autoEscape), - hostEndingChars = ['/', '?', '#'], - hostnameMaxLen = 255, - hostnamePartPattern = /^[+a-z0-9A-Z_-]{0,63}$/, - hostnamePartStart = /^([+a-z0-9A-Z_-]{0,63})(.*)$/, - // protocols that can allow "unsafe" and "unwise" chars. - unsafeProtocol = { - 'javascript': true, - 'javascript:': true - }, - // protocols that never have a hostname. - hostlessProtocol = { - 'javascript': true, - 'javascript:': true - }, - // protocols that always contain a // bit. - slashedProtocol = { - 'http': true, - 'https': true, - 'ftp': true, - 'gopher': true, - 'file': true, - 'http:': true, - 'https:': true, - 'ftp:': true, - 'gopher:': true, - 'file:': true - }, - querystring = require('querystring'); - - function urlParse(url, parseQueryString, slashesDenoteHost) { - if (url && util.isObject(url) && url instanceof Url) return url; - - var u = new Url; - u.parse(url, parseQueryString, slashesDenoteHost); - return u; - } - - Url.prototype.parse = function (url, parseQueryString, slashesDenoteHost) { - if (!util.isString(url)) { - throw new TypeError("Parameter 'url' must be a string, not " + typeof url); - } - - // Copy chrome, IE, opera backslash-handling behavior. - // Back slashes before the query string get converted to forward slashes - // See: https://code.google.com/p/chromium/issues/detail?id=25916 - var queryIndex = url.indexOf('?'), - splitter = - (queryIndex !== -1 && queryIndex < url.indexOf('#')) ? '?' : '#', - uSplit = url.split(splitter), - slashRegex = /\\/g; - uSplit[0] = uSplit[0].replace(slashRegex, '/'); - url = uSplit.join(splitter); - - var rest = url; - - // trim before proceeding. - // This is to support parse stuff like " http://foo.com \n" - rest = rest.trim(); - - if (!slashesDenoteHost && url.split('#').length === 1) { - // Try fast path regexp - var simplePath = simplePathPattern.exec(rest); - if (simplePath) { - this.path = rest; - this.href = rest; - this.pathname = simplePath[1]; - if (simplePath[2]) { - this.search = simplePath[2]; - if (parseQueryString) { - this.query = querystring.parse(this.search.substr(1)); - } else { - this.query = this.search.substr(1); - } - } else if (parseQueryString) { - this.search = ''; - this.query = {}; - } - return this; - } - } - - var proto = protocolPattern.exec(rest); - if (proto) { - proto = proto[0]; - var lowerProto = proto.toLowerCase(); - this.protocol = lowerProto; - rest = rest.substr(proto.length); - } - - // figure out if it's got a host - // user@server is *always* interpreted as a hostname, and url - // resolution will treat //foo/bar as host=foo,path=bar because that's - // how the browser resolves relative URLs. - if (slashesDenoteHost || proto || rest.match(/^\/\/[^@\/]+@[^@\/]+/)) { - var slashes = rest.substr(0, 2) === '//'; - if (slashes && !(proto && hostlessProtocol[proto])) { - rest = rest.substr(2); - this.slashes = true; - } - } - - if (!hostlessProtocol[proto] && - (slashes || (proto && !slashedProtocol[proto]))) { - - // there's a hostname. - // the first instance of /, ?, ;, or # ends the host. - // - // If there is an @ in the hostname, then non-host chars *are* allowed - // to the left of the last @ sign, unless some host-ending character - // comes *before* the @-sign. - // URLs are obnoxious. - // - // ex: - // http://a@b@c/ => user:a@b host:c - // http://a@b?@c => user:a host:c path:/?@c - - // v0.12 TODO(isaacs): This is not quite how Chrome does things. - // Review our test case against browsers more comprehensively. - - // find the first instance of any hostEndingChars - var hostEnd = -1; - for (var i = 0; i < hostEndingChars.length; i++) { - var hec = rest.indexOf(hostEndingChars[i]); - if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) - hostEnd = hec; - } - - // at this point, either we have an explicit point where the - // auth portion cannot go past, or the last @ char is the decider. - var auth, atSign; - if (hostEnd === -1) { - // atSign can be anywhere. - atSign = rest.lastIndexOf('@'); - } else { - // atSign must be in auth portion. - // http://a@b/c@d => host:b auth:a path:/c@d - atSign = rest.lastIndexOf('@', hostEnd); - } - - // Now we have a portion which is definitely the auth. - // Pull that off. - if (atSign !== -1) { - auth = rest.slice(0, atSign); - rest = rest.slice(atSign + 1); - this.auth = decodeURIComponent(auth); - } - - // the host is the remaining to the left of the first non-host char - hostEnd = -1; - for (var i = 0; i < nonHostChars.length; i++) { - var hec = rest.indexOf(nonHostChars[i]); - if (hec !== -1 && (hostEnd === -1 || hec < hostEnd)) - hostEnd = hec; - } - // if we still have not hit it, then the entire thing is a host. - if (hostEnd === -1) - hostEnd = rest.length; - - this.host = rest.slice(0, hostEnd); - rest = rest.slice(hostEnd); - - // pull out port. - this.parseHost(); - - // we've indicated that there is a hostname, - // so even if it's empty, it has to be present. - this.hostname = this.hostname || ''; - - // if hostname begins with [ and ends with ] - // assume that it's an IPv6 address. - var ipv6Hostname = this.hostname[0] === '[' && - this.hostname[this.hostname.length - 1] === ']'; - - // validate a little. - if (!ipv6Hostname) { - var hostparts = this.hostname.split(/\./); - for (var i = 0, l = hostparts.length; i < l; i++) { - var part = hostparts[i]; - if (!part) continue; - if (!part.match(hostnamePartPattern)) { - var newpart = ''; - for (var j = 0, k = part.length; j < k; j++) { - if (part.charCodeAt(j) > 127) { - // we replace non-ASCII char with a temporary placeholder - // we need this to make sure size of hostname is not - // broken by replacing non-ASCII by nothing - newpart += 'x'; - } else { - newpart += part[j]; - } - } - // we test again with ASCII char only - if (!newpart.match(hostnamePartPattern)) { - var validParts = hostparts.slice(0, i); - var notHost = hostparts.slice(i + 1); - var bit = part.match(hostnamePartStart); - if (bit) { - validParts.push(bit[1]); - notHost.unshift(bit[2]); - } - if (notHost.length) { - rest = '/' + notHost.join('.') + rest; - } - this.hostname = validParts.join('.'); - break; - } - } - } - } - - if (this.hostname.length > hostnameMaxLen) { - this.hostname = ''; - } else { - // hostnames are always lower case. - this.hostname = this.hostname.toLowerCase(); - } - - if (!ipv6Hostname) { - // IDNA Support: Returns a punycoded representation of "domain". - // It only converts parts of the domain name that - // have non-ASCII characters, i.e. it doesn't matter if - // you call it with a domain that already is ASCII-only. - this.hostname = punycode.toASCII(this.hostname); - } - - var p = this.port ? ':' + this.port : ''; - var h = this.hostname || ''; - this.host = h + p; - this.href += this.host; - - // strip [ and ] from the hostname - // the host field still retains them, though - if (ipv6Hostname) { - this.hostname = this.hostname.substr(1, this.hostname.length - 2); - if (rest[0] !== '/') { - rest = '/' + rest; - } - } - } - - // now rest is set to the post-host stuff. - // chop off any delim chars. - if (!unsafeProtocol[lowerProto]) { - - // First, make 100% sure that any "autoEscape" chars get - // escaped, even if encodeURIComponent doesn't think they - // need to be. - for (var i = 0, l = autoEscape.length; i < l; i++) { - var ae = autoEscape[i]; - if (rest.indexOf(ae) === -1) - continue; - var esc = encodeURIComponent(ae); - if (esc === ae) { - esc = escape(ae); - } - rest = rest.split(ae).join(esc); - } - } - - - // chop off from the tail first. - var hash = rest.indexOf('#'); - if (hash !== -1) { - // got a fragment string. - this.hash = rest.substr(hash); - rest = rest.slice(0, hash); - } - var qm = rest.indexOf('?'); - if (qm !== -1) { - this.search = rest.substr(qm); - this.query = rest.substr(qm + 1); - if (parseQueryString) { - this.query = querystring.parse(this.query); - } - rest = rest.slice(0, qm); - } else if (parseQueryString) { - // no query string, but parseQueryString still requested - this.search = ''; - this.query = {}; - } - if (rest) this.pathname = rest; - if (slashedProtocol[lowerProto] && - this.hostname && !this.pathname) { - this.pathname = '/'; - } - - //to support http.request - if (this.pathname || this.search) { - var p = this.pathname || ''; - var s = this.search || ''; - this.path = p + s; - } - - // finally, reconstruct the href based on what has been validated. - this.href = this.format(); - return this; - }; - - // format a parsed object into a url string - function urlFormat(obj) { - // ensure it's an object, and not a string url. - // If it's an obj, this is a no-op. - // this way, you can call url_format() on strings - // to clean up potentially wonky urls. - if (util.isString(obj)) obj = urlParse(obj); - if (!(obj instanceof Url)) return Url.prototype.format.call(obj); - return obj.format(); - } - - Url.prototype.format = function () { - var auth = this.auth || ''; - if (auth) { - auth = encodeURIComponent(auth); - auth = auth.replace(/%3A/i, ':'); - auth += '@'; - } - - var protocol = this.protocol || '', - pathname = this.pathname || '', - hash = this.hash || '', - host = false, - query = ''; - - if (this.host) { - host = auth + this.host; - } else if (this.hostname) { - host = auth + (this.hostname.indexOf(':') === -1 ? - this.hostname : - '[' + this.hostname + ']'); - if (this.port) { - host += ':' + this.port; - } - } - - if (this.query && - util.isObject(this.query) && - Object.keys(this.query).length) { - query = querystring.stringify(this.query); - } - - var search = this.search || (query && ('?' + query)) || ''; - - if (protocol && protocol.substr(-1) !== ':') protocol += ':'; - - // only the slashedProtocols get the //. Not mailto:, xmpp:, etc. - // unless they had them to begin with. - if (this.slashes || - (!protocol || slashedProtocol[protocol]) && host !== false) { - host = '//' + (host || ''); - if (pathname && pathname.charAt(0) !== '/') pathname = '/' + pathname; - } else if (!host) { - host = ''; - } - - if (hash && hash.charAt(0) !== '#') hash = '#' + hash; - if (search && search.charAt(0) !== '?') search = '?' + search; - - pathname = pathname.replace(/[?#]/g, function (match) { - return encodeURIComponent(match); - }); - search = search.replace('#', '%23'); - - return protocol + host + pathname + search + hash; - }; - - function urlResolve(source, relative) { - return urlParse(source, false, true).resolve(relative); - } - - Url.prototype.resolve = function (relative) { - return this.resolveObject(urlParse(relative, false, true)).format(); - }; - - function urlResolveObject(source, relative) { - if (!source) return relative; - return urlParse(source, false, true).resolveObject(relative); - } - - Url.prototype.resolveObject = function (relative) { - if (util.isString(relative)) { - var rel = new Url(); - rel.parse(relative, false, true); - relative = rel; - } - - var result = new Url(); - var tkeys = Object.keys(this); - for (var tk = 0; tk < tkeys.length; tk++) { - var tkey = tkeys[tk]; - result[tkey] = this[tkey]; - } - - // hash is always overridden, no matter what. - // even href="" will remove it. - result.hash = relative.hash; - - // if the relative url is empty, then there's nothing left to do here. - if (relative.href === '') { - result.href = result.format(); - return result; - } - - // hrefs like //foo/bar always cut to the protocol. - if (relative.slashes && !relative.protocol) { - // take everything except the protocol from relative - var rkeys = Object.keys(relative); - for (var rk = 0; rk < rkeys.length; rk++) { - var rkey = rkeys[rk]; - if (rkey !== 'protocol') - result[rkey] = relative[rkey]; - } - - //urlParse appends trailing / to urls like http://www.example.com - if (slashedProtocol[result.protocol] && - result.hostname && !result.pathname) { - result.path = result.pathname = '/'; - } - - result.href = result.format(); - return result; - } - - if (relative.protocol && relative.protocol !== result.protocol) { - // if it's a known url protocol, then changing - // the protocol does weird things - // first, if it's not file:, then we MUST have a host, - // and if there was a path - // to begin with, then we MUST have a path. - // if it is file:, then the host is dropped, - // because that's known to be hostless. - // anything else is assumed to be absolute. - if (!slashedProtocol[relative.protocol]) { - var keys = Object.keys(relative); - for (var v = 0; v < keys.length; v++) { - var k = keys[v]; - result[k] = relative[k]; - } - result.href = result.format(); - return result; - } - - result.protocol = relative.protocol; - if (!relative.host && !hostlessProtocol[relative.protocol]) { - var relPath = (relative.pathname || '').split('/'); - while (relPath.length && !(relative.host = relPath.shift())); - if (!relative.host) relative.host = ''; - if (!relative.hostname) relative.hostname = ''; - if (relPath[0] !== '') relPath.unshift(''); - if (relPath.length < 2) relPath.unshift(''); - result.pathname = relPath.join('/'); - } else { - result.pathname = relative.pathname; - } - result.search = relative.search; - result.query = relative.query; - result.host = relative.host || ''; - result.auth = relative.auth; - result.hostname = relative.hostname || relative.host; - result.port = relative.port; - // to support http.request - if (result.pathname || result.search) { - var p = result.pathname || ''; - var s = result.search || ''; - result.path = p + s; - } - result.slashes = result.slashes || relative.slashes; - result.href = result.format(); - return result; - } - - var isSourceAbs = (result.pathname && result.pathname.charAt(0) === '/'), - isRelAbs = ( - relative.host || - relative.pathname && relative.pathname.charAt(0) === '/' - ), - mustEndAbs = (isRelAbs || isSourceAbs || - (result.host && relative.pathname)), - removeAllDots = mustEndAbs, - srcPath = result.pathname && result.pathname.split('/') || [], - relPath = relative.pathname && relative.pathname.split('/') || [], - psychotic = result.protocol && !slashedProtocol[result.protocol]; - - // if the url is a non-slashed url, then relative - // links like ../.. should be able - // to crawl up to the hostname, as well. This is strange. - // result.protocol has already been set by now. - // Later on, put the first path part into the host field. - if (psychotic) { - result.hostname = ''; - result.port = null; - if (result.host) { - if (srcPath[0] === '') srcPath[0] = result.host; - else srcPath.unshift(result.host); - } - result.host = ''; - if (relative.protocol) { - relative.hostname = null; - relative.port = null; - if (relative.host) { - if (relPath[0] === '') relPath[0] = relative.host; - else relPath.unshift(relative.host); - } - relative.host = null; - } - mustEndAbs = mustEndAbs && (relPath[0] === '' || srcPath[0] === ''); - } - - if (isRelAbs) { - // it's absolute. - result.host = (relative.host || relative.host === '') ? - relative.host : result.host; - result.hostname = (relative.hostname || relative.hostname === '') ? - relative.hostname : result.hostname; - result.search = relative.search; - result.query = relative.query; - srcPath = relPath; - // fall through to the dot-handling below. - } else if (relPath.length) { - // it's relative - // throw away the existing file, and take the new path instead. - if (!srcPath) srcPath = []; - srcPath.pop(); - srcPath = srcPath.concat(relPath); - result.search = relative.search; - result.query = relative.query; - } else if (!util.isNullOrUndefined(relative.search)) { - // just pull out the search. - // like href='?foo'. - // Put this after the other two cases because it simplifies the booleans - if (psychotic) { - result.hostname = result.host = srcPath.shift(); - //occationaly the auth can get stuck only in host - //this especially happens in cases like - //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var authInHost = result.host && result.host.indexOf('@') > 0 ? - result.host.split('@') : false; - if (authInHost) { - result.auth = authInHost.shift(); - result.host = result.hostname = authInHost.shift(); - } - } - result.search = relative.search; - result.query = relative.query; - //to support http.request - if (!util.isNull(result.pathname) || !util.isNull(result.search)) { - result.path = (result.pathname ? result.pathname : '') + - (result.search ? result.search : ''); - } - result.href = result.format(); - return result; - } - - if (!srcPath.length) { - // no path at all. easy. - // we've already handled the other stuff above. - result.pathname = null; - //to support http.request - if (result.search) { - result.path = '/' + result.search; - } else { - result.path = null; - } - result.href = result.format(); - return result; - } - - // if a url ENDs in . or .., then it must get a trailing slash. - // however, if it ends in anything else non-slashy, - // then it must NOT get a trailing slash. - var last = srcPath.slice(-1)[0]; - var hasTrailingSlash = ( - (result.host || relative.host || srcPath.length > 1) && - (last === '.' || last === '..') || last === ''); - - // strip single dots, resolve double dots to parent dir - // if the path tries to go above the root, `up` ends up > 0 - var up = 0; - for (var i = srcPath.length; i >= 0; i--) { - last = srcPath[i]; - if (last === '.') { - srcPath.splice(i, 1); - } else if (last === '..') { - srcPath.splice(i, 1); - up++; - } else if (up) { - srcPath.splice(i, 1); - up--; - } - } - - // if the path is allowed to go above the root, restore leading ..s - if (!mustEndAbs && !removeAllDots) { - for (; up--; up) { - srcPath.unshift('..'); - } - } - - if (mustEndAbs && srcPath[0] !== '' && - (!srcPath[0] || srcPath[0].charAt(0) !== '/')) { - srcPath.unshift(''); - } - - if (hasTrailingSlash && (srcPath.join('/').substr(-1) !== '/')) { - srcPath.push(''); - } - - var isAbsolute = srcPath[0] === '' || - (srcPath[0] && srcPath[0].charAt(0) === '/'); - - // put the host back - if (psychotic) { - result.hostname = result.host = isAbsolute ? '' : - srcPath.length ? srcPath.shift() : ''; - //occationaly the auth can get stuck only in host - //this especially happens in cases like - //url.resolveObject('mailto:local1@domain1', 'local2@domain2') - var authInHost = result.host && result.host.indexOf('@') > 0 ? - result.host.split('@') : false; - if (authInHost) { - result.auth = authInHost.shift(); - result.host = result.hostname = authInHost.shift(); - } - } - - mustEndAbs = mustEndAbs || (result.host && srcPath.length); - - if (mustEndAbs && !isAbsolute) { - srcPath.unshift(''); - } - - if (!srcPath.length) { - result.pathname = null; - result.path = null; - } else { - result.pathname = srcPath.join('/'); - } - - //to support request.http - if (!util.isNull(result.pathname) || !util.isNull(result.search)) { - result.path = (result.pathname ? result.pathname : '') + - (result.search ? result.search : ''); - } - result.auth = relative.auth || result.auth; - result.slashes = result.slashes || relative.slashes; - result.href = result.format(); - return result; - }; - - Url.prototype.parseHost = function () { - var host = this.host; - var port = portPattern.exec(host); - if (port) { - port = port[0]; - if (port !== ':') { - this.port = port.substr(1); - } - host = host.substr(0, host.length - port.length); - } - if (host) this.hostname = host; - }; - - }, { "./util": 51, "punycode": 46, "querystring": 49 }], - 51: [function (require, module, exports) { - 'use strict'; - - module.exports = { - isString: function (arg) { - return typeof (arg) === 'string'; - }, - isObject: function (arg) { - return typeof (arg) === 'object' && arg !== null; - }, - isNull: function (arg) { - return arg === null; - }, - isNullOrUndefined: function (arg) { - return arg == null; - } - }; - - }, {}], - "ajv": [function (require, module, exports) { - 'use strict'; - - var compileSchema = require('./compile'), - resolve = require('./compile/resolve'), - Cache = require('./cache'), - SchemaObject = require('./compile/schema_obj'), - stableStringify = require('json-stable-stringify'), - formats = require('./compile/formats'), - rules = require('./compile/rules'), - $dataMetaSchema = require('./$data'), - patternGroups = require('./patternGroups'), - util = require('./compile/util'), - co = require('co'); - - module.exports = Ajv; - - Ajv.prototype.validate = validate; - Ajv.prototype.compile = compile; - Ajv.prototype.addSchema = addSchema; - Ajv.prototype.addMetaSchema = addMetaSchema; - Ajv.prototype.validateSchema = validateSchema; - Ajv.prototype.getSchema = getSchema; - Ajv.prototype.removeSchema = removeSchema; - Ajv.prototype.addFormat = addFormat; - Ajv.prototype.errorsText = errorsText; - - Ajv.prototype._addSchema = _addSchema; - Ajv.prototype._compile = _compile; - - Ajv.prototype.compileAsync = require('./compile/async'); - var customKeyword = require('./keyword'); - Ajv.prototype.addKeyword = customKeyword.add; - Ajv.prototype.getKeyword = customKeyword.get; - Ajv.prototype.removeKeyword = customKeyword.remove; - - var errorClasses = require('./compile/error_classes'); - Ajv.ValidationError = errorClasses.Validation; - Ajv.MissingRefError = errorClasses.MissingRef; - Ajv.$dataMetaSchema = $dataMetaSchema; - - var META_SCHEMA_ID = 'http://json-schema.org/draft-06/schema'; - - var META_IGNORE_OPTIONS = ['removeAdditional', 'useDefaults', 'coerceTypes']; - var META_SUPPORT_DATA = ['/properties']; - - /** - * Creates validator instance. - * Usage: `Ajv(opts)` - * @param {Object} opts optional options - * @return {Object} ajv instance - */ - function Ajv(opts) { - if (!(this instanceof Ajv)) return new Ajv(opts); - opts = this._opts = util.copy(opts) || {}; - this._schemas = {}; - this._refs = {}; - this._fragments = {}; - this._formats = formats(opts.format); - var schemaUriFormat = this._schemaUriFormat = this._formats['uri-reference']; - this._schemaUriFormatFunc = function (str) { return schemaUriFormat.test(str); }; - - this._cache = opts.cache || new Cache; - this._loadingSchemas = {}; - this._compilations = []; - this.RULES = rules(); - this._getId = chooseGetId(opts); - - opts.loopRequired = opts.loopRequired || Infinity; - if (opts.errorDataPath == 'property') opts._errorDataPathProperty = true; - if (opts.serialize === undefined) opts.serialize = stableStringify; - this._metaOpts = getMetaSchemaOptions(this); - - if (opts.formats) addInitialFormats(this); - addDraft6MetaSchema(this); - if (typeof opts.meta == 'object') this.addMetaSchema(opts.meta); - addInitialSchemas(this); - if (opts.patternGroups) patternGroups(this); - } - - - - /** - * Validate data using schema - * Schema will be compiled and cached (using serialized JSON as key. [json-stable-stringify](https://github.com/substack/json-stable-stringify) is used to serialize. - * @this Ajv - * @param {String|Object} schemaKeyRef key, ref or schema object - * @param {Any} data to be validated - * @return {Boolean} validation result. Errors from the last validation will be available in `ajv.errors` (and also in compiled schema: `schema.errors`). - */ - function validate(schemaKeyRef, data) { - var v; - if (typeof schemaKeyRef == 'string') { - v = this.getSchema(schemaKeyRef); - if (!v) throw new Error('no schema with key or ref "' + schemaKeyRef + '"'); - } else { - var schemaObj = this._addSchema(schemaKeyRef); - v = schemaObj.validate || this._compile(schemaObj); - } - - var valid = v(data); - if (v.$async === true) - return this._opts.async == '*' ? co(valid) : valid; - this.errors = v.errors; - return valid; - } - - - /** - * Create validating function for passed schema. - * @this Ajv - * @param {Object} schema schema object - * @param {Boolean} _meta true if schema is a meta-schema. Used internally to compile meta schemas of custom keywords. - * @return {Function} validating function - */ - function compile(schema, _meta) { - var schemaObj = this._addSchema(schema, undefined, _meta); - return schemaObj.validate || this._compile(schemaObj); - } - - - /** - * Adds schema to the instance. - * @this Ajv - * @param {Object|Array} schema schema or array of schemas. If array is passed, `key` and other parameters will be ignored. - * @param {String} key Optional schema key. Can be passed to `validate` method instead of schema object or id/ref. One schema per instance can have empty `id` and `key`. - * @param {Boolean} _skipValidation true to skip schema validation. Used internally, option validateSchema should be used instead. - * @param {Boolean} _meta true if schema is a meta-schema. Used internally, addMetaSchema should be used instead. - */ - function addSchema(schema, key, _skipValidation, _meta) { - if (Array.isArray(schema)) { - for (var i = 0; i < schema.length; i++) this.addSchema(schema[i], undefined, _skipValidation, _meta); - return; - } - var id = this._getId(schema); - if (id !== undefined && typeof id != 'string') - throw new Error('schema id must be string'); - key = resolve.normalizeId(key || id); - checkUnique(this, key); - this._schemas[key] = this._addSchema(schema, _skipValidation, _meta, true); - } - - - /** - * Add schema that will be used to validate other schemas - * options in META_IGNORE_OPTIONS are alway set to false - * @this Ajv - * @param {Object} schema schema object - * @param {String} key optional schema key - * @param {Boolean} skipValidation true to skip schema validation, can be used to override validateSchema option for meta-schema - */ - function addMetaSchema(schema, key, skipValidation) { - this.addSchema(schema, key, skipValidation, true); - } - - - /** - * Validate schema - * @this Ajv - * @param {Object} schema schema to validate - * @param {Boolean} throwOrLogError pass true to throw (or log) an error if invalid - * @return {Boolean} true if schema is valid - */ - function validateSchema(schema, throwOrLogError) { - var $schema = schema.$schema; - if ($schema !== undefined && typeof $schema != 'string') - throw new Error('$schema must be a string'); - $schema = $schema || this._opts.defaultMeta || defaultMeta(this); - if (!$schema) { - console.warn('meta-schema not available'); - this.errors = null; - return true; - } - var currentUriFormat = this._formats.uri; - this._formats.uri = typeof currentUriFormat == 'function' ? - this._schemaUriFormatFunc : - this._schemaUriFormat; - var valid; - try { valid = this.validate($schema, schema); } finally { this._formats.uri = currentUriFormat; } - if (!valid && throwOrLogError) { - var message = 'schema is invalid: ' + this.errorsText(); - if (this._opts.validateSchema == 'log') console.error(message); - else throw new Error(message); - } - return valid; - } - - - function defaultMeta(self) { - var meta = self._opts.meta; - self._opts.defaultMeta = typeof meta == 'object' ? - self._getId(meta) || meta : - self.getSchema(META_SCHEMA_ID) ? - META_SCHEMA_ID : - undefined; - return self._opts.defaultMeta; - } - - - /** - * Get compiled schema from the instance by `key` or `ref`. - * @this Ajv - * @param {String} keyRef `key` that was passed to `addSchema` or full schema reference (`schema.id` or resolved id). - * @return {Function} schema validating function (with property `schema`). - */ - function getSchema(keyRef) { - var schemaObj = _getSchemaObj(this, keyRef); - switch (typeof schemaObj) { - case 'object': - return schemaObj.validate || this._compile(schemaObj); - case 'string': - return this.getSchema(schemaObj); - case 'undefined': - return _getSchemaFragment(this, keyRef); - } - } - - - function _getSchemaFragment(self, ref) { - var res = resolve.schema.call(self, { schema: {} }, ref); - if (res) { - var schema = res.schema, - root = res.root, - baseId = res.baseId; - var v = compileSchema.call(self, schema, root, undefined, baseId); - self._fragments[ref] = new SchemaObject({ - ref: ref, - fragment: true, - schema: schema, - root: root, - baseId: baseId, - validate: v - }); - return v; - } - } - - - function _getSchemaObj(self, keyRef) { - keyRef = resolve.normalizeId(keyRef); - return self._schemas[keyRef] || self._refs[keyRef] || self._fragments[keyRef]; - } - - - /** - * Remove cached schema(s). - * If no parameter is passed all schemas but meta-schemas are removed. - * If RegExp is passed all schemas with key/id matching pattern but meta-schemas are removed. - * Even if schema is referenced by other schemas it still can be removed as other schemas have local references. - * @this Ajv - * @param {String|Object|RegExp} schemaKeyRef key, ref, pattern to match key/ref or schema object - */ - function removeSchema(schemaKeyRef) { - if (schemaKeyRef instanceof RegExp) { - _removeAllSchemas(this, this._schemas, schemaKeyRef); - _removeAllSchemas(this, this._refs, schemaKeyRef); - return; - } - switch (typeof schemaKeyRef) { - case 'undefined': - _removeAllSchemas(this, this._schemas); - _removeAllSchemas(this, this._refs); - this._cache.clear(); - return; - case 'string': - var schemaObj = _getSchemaObj(this, schemaKeyRef); - if (schemaObj) this._cache.del(schemaObj.cacheKey); - delete this._schemas[schemaKeyRef]; - delete this._refs[schemaKeyRef]; - return; - case 'object': - var serialize = this._opts.serialize; - var cacheKey = serialize ? serialize(schemaKeyRef) : schemaKeyRef; - this._cache.del(cacheKey); - var id = this._getId(schemaKeyRef); - if (id) { - id = resolve.normalizeId(id); - delete this._schemas[id]; - delete this._refs[id]; - } - } - } - - - function _removeAllSchemas(self, schemas, regex) { - for (var keyRef in schemas) { - var schemaObj = schemas[keyRef]; - if (!schemaObj.meta && (!regex || regex.test(keyRef))) { - self._cache.del(schemaObj.cacheKey); - delete schemas[keyRef]; - } - } - } - - - /* @this Ajv */ - function _addSchema(schema, skipValidation, meta, shouldAddSchema) { - if (typeof schema != 'object' && typeof schema != 'boolean') - throw new Error('schema should be object or boolean'); - var serialize = this._opts.serialize; - var cacheKey = serialize ? serialize(schema) : schema; - var cached = this._cache.get(cacheKey); - if (cached) return cached; - - shouldAddSchema = shouldAddSchema || this._opts.addUsedSchema !== false; - - var id = resolve.normalizeId(this._getId(schema)); - if (id && shouldAddSchema) checkUnique(this, id); - - var willValidate = this._opts.validateSchema !== false && !skipValidation; - var recursiveMeta; - if (willValidate && !(recursiveMeta = id && id == resolve.normalizeId(schema.$schema))) - this.validateSchema(schema, true); - - var localRefs = resolve.ids.call(this, schema); - - var schemaObj = new SchemaObject({ - id: id, - schema: schema, - localRefs: localRefs, - cacheKey: cacheKey, - meta: meta - }); - - if (id[0] != '#' && shouldAddSchema) this._refs[id] = schemaObj; - this._cache.put(cacheKey, schemaObj); - - if (willValidate && recursiveMeta) this.validateSchema(schema, true); - - return schemaObj; - } - - - /* @this Ajv */ - function _compile(schemaObj, root) { - if (schemaObj.compiling) { - schemaObj.validate = callValidate; - callValidate.schema = schemaObj.schema; - callValidate.errors = null; - callValidate.root = root ? root : callValidate; - if (schemaObj.schema.$async === true) - callValidate.$async = true; - return callValidate; - } - schemaObj.compiling = true; - - var currentOpts; - if (schemaObj.meta) { - currentOpts = this._opts; - this._opts = this._metaOpts; - } - - var v; - try { v = compileSchema.call(this, schemaObj.schema, root, schemaObj.localRefs); } finally { - schemaObj.compiling = false; - if (schemaObj.meta) this._opts = currentOpts; - } - - schemaObj.validate = v; - schemaObj.refs = v.refs; - schemaObj.refVal = v.refVal; - schemaObj.root = v.root; - return v; - - - function callValidate() { - var _validate = schemaObj.validate; - var result = _validate.apply(null, arguments); - callValidate.errors = _validate.errors; - return result; - } - } - - - function chooseGetId(opts) { - switch (opts.schemaId) { - case '$id': - return _get$Id; - case 'id': - return _getId; - default: - return _get$IdOrId; - } - } - - - function _getId(schema) { - if (schema.$id) console.warn('schema $id ignored', schema.$id); - return schema.id; - } - - - function _get$Id(schema) { - if (schema.id) console.warn('schema id ignored', schema.id); - return schema.$id; - } - - - function _get$IdOrId(schema) { - if (schema.$id && schema.id && schema.$id != schema.id) - throw new Error('schema $id is different from id'); - return schema.$id || schema.id; - } - - - /** - * Convert array of error message objects to string - * @this Ajv - * @param {Array} errors optional array of validation errors, if not passed errors from the instance are used. - * @param {Object} options optional options with properties `separator` and `dataVar`. - * @return {String} human readable string with all errors descriptions - */ - function errorsText(errors, options) { - errors = errors || this.errors; - if (!errors) return 'No errors'; - options = options || {}; - var separator = options.separator === undefined ? ', ' : options.separator; - var dataVar = options.dataVar === undefined ? 'data' : options.dataVar; - - var text = ''; - for (var i = 0; i < errors.length; i++) { - var e = errors[i]; - if (e) text += dataVar + e.dataPath + ' ' + e.message + separator; - } - return text.slice(0, -separator.length); - } - - - /** - * Add custom format - * @this Ajv - * @param {String} name format name - * @param {String|RegExp|Function} format string is converted to RegExp; function should return boolean (true when valid) - */ - function addFormat(name, format) { - if (typeof format == 'string') format = new RegExp(format); - this._formats[name] = format; - } - - - function addDraft6MetaSchema(self) { - var $dataSchema; - if (self._opts.$data) { - $dataSchema = require('./refs/$data.json'); - self.addMetaSchema($dataSchema, $dataSchema.$id, true); - } - if (self._opts.meta === false) return; - var metaSchema = require('./refs/json-schema-draft-06.json'); - if (self._opts.$data) metaSchema = $dataMetaSchema(metaSchema, META_SUPPORT_DATA); - self.addMetaSchema(metaSchema, META_SCHEMA_ID, true); - self._refs['http://json-schema.org/schema'] = META_SCHEMA_ID; - } - - - function addInitialSchemas(self) { - var optsSchemas = self._opts.schemas; - if (!optsSchemas) return; - if (Array.isArray(optsSchemas)) self.addSchema(optsSchemas); - else - for (var key in optsSchemas) self.addSchema(optsSchemas[key], key); - } - - - function addInitialFormats(self) { - for (var name in self._opts.formats) { - var format = self._opts.formats[name]; - self.addFormat(name, format); - } - } - - - function checkUnique(self, id) { - if (self._schemas[id] || self._refs[id]) - throw new Error('schema with key or id "' + id + '" already exists'); - } - - - function getMetaSchemaOptions(self) { - var metaOpts = util.copy(self._opts); - for (var i = 0; i < META_IGNORE_OPTIONS.length; i++) - delete metaOpts[META_IGNORE_OPTIONS[i]]; - return metaOpts; - } - - }, { "./$data": 1, "./cache": 2, "./compile": 8, "./compile/async": 4, "./compile/error_classes": 6, "./compile/formats": 7, "./compile/resolve": 9, "./compile/rules": 10, "./compile/schema_obj": 11, "./compile/util": 13, "./keyword": 37, "./patternGroups": 38, "./refs/$data.json": 39, "./refs/json-schema-draft-06.json": 40, "co": 41, "json-stable-stringify": 42 }] - }, {}, [])("ajv") -}); diff --git a/src/client/modules/lib/knockout-plus.js b/src/client/modules/lib/knockout-plus.js index 87eea64ff..18646cb07 100644 --- a/src/client/modules/lib/knockout-plus.js +++ b/src/client/modules/lib/knockout-plus.js @@ -509,6 +509,18 @@ define([ }; } + + function pluralize(expression, singular, plural) { + return [ + '', + singular, + '', + '', + plural, + '' + ]; + } + ko.kb = {}; ko.kb.komponent = komponent; @@ -517,6 +529,7 @@ define([ // the subscription manager is a factory. // TODO: better way of integrating into knockout... ko.kb.SubscriptionManager = SubscriptionManager; + ko.kb.pluralize = pluralize; return ko; }); diff --git a/src/client/require-config.js b/src/client/require-config.js index a66353a55..f772b17d7 100644 --- a/src/client/require-config.js +++ b/src/client/require-config.js @@ -23,7 +23,8 @@ catchError: true, waitSeconds: 60, paths: { - bluebird: 'bower_components/bluebird/bluebird', + ajv: 'node_modules/ajv/ajv.bundle', + bluebird: 'node_modules/bluebird/bluebird', bootstrap_css: 'bower_components/bootstrap/css/bootstrap', bootstrap: 'bower_components/bootstrap/js/bootstrap', css: 'bower_components/require-css/css', @@ -31,11 +32,19 @@ d3_sankey_css: 'bower_components/d3-plugins-sankey/sankey', d3_sankey: 'bower_components/d3-plugins-sankey/sankey', d3: 'bower_components/d3/d3', - domReady: 'bower_components/requirejs-domready/domReady', + // d3: 'node_modules/d3/d3', + // 'd3-sankey': 'node_modules/d3-sankey/d3-sankey', + // 'd3-collection': 'node_modules/d3-collection/d3-collection', + // 'd3-shape': 'node_modules/d3-shape/d3-shape', + // 'd3-array': 'node_modules/d3-array/d3-array', + // 'd3-path': 'node_modules/d3-path/d3-path', + + dagre: 'node_modules/dagre/dagre', datatables_bootstrap_css: 'bower_components/datatables-bootstrap3-plugin/css/datatables-bootstrap3', datatables_bootstrap: 'bower_components/datatables-bootstrap3-plugin/js/datatables-bootstrap3', datatables_css: 'bower_components/datatables/css/jquery.dataTables', datatables: 'bower_components/datatables/js/jquery.dataTables', + domReady: 'bower_components/requirejs-domready/domReady', fileSaver: 'bower_components/file-saver/FileSaver', font_awesome: 'bower_components/font-awesome/css/font-awesome', 'google-code-prettify-style': 'bower_components/google-code-prettify/prettify', @@ -58,6 +67,7 @@ 'knockout-mapping': 'bower_components/bower-knockout-mapping/knockout.mapping', 'knockout-plus': 'lib/knockout-plus', 'knockout-validation': 'bower_components/knockout-validation/knockout.validation', + lodash: 'node_modules/lodash/lodash', marked: 'bower_components/marked/marked', md5: 'bower_components/spark-md5/spark-md5', moment: 'bower_components/moment/moment', @@ -70,7 +80,7 @@ text: 'bower_components/requirejs-text/text', underscore: 'bower_components/underscore/underscore', uuid: 'bower_components/pure-uuid/uuid', - yaml: 'bower_components/requirejs-yaml/yaml' + yaml: 'bower_components/requirejs-yaml/yaml' }, shim: { bootstrap: { diff --git a/src/client/modules/plugins/about/config.yml b/src/plugins/about/config.yml similarity index 70% rename from src/client/modules/plugins/about/config.yml rename to src/plugins/about/config.yml index 290833abc..274e2ae7c 100644 --- a/src/client/modules/plugins/about/config.yml +++ b/src/plugins/about/config.yml @@ -37,19 +37,16 @@ install: menu: - name: about - definition: - path: about - label: About - icon: info-circle + path: about + label: About + icon: info-circle - name: about-build - definition: - path: about/build - label: About the UI Build - icon: building-o + path: ['about', 'build'] + label: About the UI Build + icon: building-o - name: about-services - definition: - path: ['about', 'services'] - label: KBase Services Status - icon: server + path: ['about', 'services'] + label: KBase Services Status + icon: server diff --git a/src/client/modules/plugins/about/modules/about.js b/src/plugins/about/modules/about.js similarity index 97% rename from src/client/modules/plugins/about/modules/about.js rename to src/plugins/about/modules/about.js index 4186c3eb2..9851dd557 100644 --- a/src/client/modules/plugins/about/modules/about.js +++ b/src/plugins/about/modules/about.js @@ -187,7 +187,8 @@ define([ function buildLayout() { return div({ - class: 'container-fluid' + class: 'container-fluid', + dataKbasePlugin: 'about' }, [ div({ class: 'row' @@ -195,14 +196,14 @@ define([ div({ class: 'col-sm-6', - style: {} + dataKbasePanel: 'welcome' }, [ h2('The KBase Hub'), buildWelcome() ]), div({ class: 'col-sm-6', - style: {} + dataKbasePanel: 'build-info' }, [ h2('This Version'), buildVersionInfo() diff --git a/src/client/modules/plugins/about/modules/aboutBuild.js b/src/plugins/about/modules/aboutBuild.js similarity index 100% rename from src/client/modules/plugins/about/modules/aboutBuild.js rename to src/plugins/about/modules/aboutBuild.js diff --git a/src/client/modules/plugins/about/modules/aboutServices.js b/src/plugins/about/modules/aboutServices.js similarity index 100% rename from src/client/modules/plugins/about/modules/aboutServices.js rename to src/plugins/about/modules/aboutServices.js diff --git a/src/plugins/about/test/main.json b/src/plugins/about/test/main.json new file mode 100644 index 000000000..57527bbd5 --- /dev/null +++ b/src/plugins/about/test/main.json @@ -0,0 +1,42 @@ +[ + { + "description": "About panel", + "specs": [ + { + "description": "View the about panel directly", + "baseSelector": [ + { + "type": "plugin", + "value": "about" + } + ], + "tasks": [ + { + "navigate": { + "path": "about" + } + }, + { + "selector": [ + { + "type": "panel", + "value": "welcome" + } + ], + "wait": 30000, + "action": "click" + }, + { + "selector": [ + { + "type": "panel", + "value": "build-info" + } + ], + "exists": true + } + ] + } + ] + } +] \ No newline at end of file diff --git a/src/client/modules/plugins/components/config.yml b/src/plugins/components/config.yml similarity index 100% rename from src/client/modules/plugins/components/config.yml rename to src/plugins/components/config.yml diff --git a/src/client/modules/plugins/components/modules/clock.js b/src/plugins/components/modules/clock.js similarity index 100% rename from src/client/modules/plugins/components/modules/clock.js rename to src/plugins/components/modules/clock.js diff --git a/src/client/modules/plugins/components/modules/components/dialog.js b/src/plugins/components/modules/components/dialog.js similarity index 100% rename from src/client/modules/plugins/components/modules/components/dialog.js rename to src/plugins/components/modules/components/dialog.js diff --git a/src/client/modules/plugins/components/modules/components/elapsedClock.js b/src/plugins/components/modules/components/elapsedClock.js similarity index 100% rename from src/client/modules/plugins/components/modules/components/elapsedClock.js rename to src/plugins/components/modules/components/elapsedClock.js diff --git a/src/client/modules/plugins/components/modules/components/error.js b/src/plugins/components/modules/components/error.js similarity index 100% rename from src/client/modules/plugins/components/modules/components/error.js rename to src/plugins/components/modules/components/error.js diff --git a/src/client/modules/plugins/components/modules/components/help.js b/src/plugins/components/modules/components/help.js similarity index 74% rename from src/client/modules/plugins/components/modules/components/help.js rename to src/plugins/components/modules/components/help.js index e8a00a72b..2f6094cde 100644 --- a/src/client/modules/plugins/components/modules/components/help.js +++ b/src/plugins/components/modules/components/help.js @@ -17,6 +17,8 @@ define([ function viewModel(params) { var helpDb = params.helpDb; + var subscriptions = ko.kb.SubscriptionManager.make(); + var topicsIndex = {}; helpDb.topics.forEach(function (topic) { topicsIndex[topic.id] = topic; @@ -26,9 +28,9 @@ define([ var currentTopic = ko.observable(); - currentTopicId.subscribe(function () { + subscriptions.add(currentTopicId.subscribe(function () { currentTopic(topicsIndex[currentTopicId()]); - }); + })); // ACTIONS function doSelectTopic(topic) { @@ -37,12 +39,18 @@ define([ currentTopicId(params.topic || 'overview'); + function dispose() { + subscriptions.dispose(); + } + return { topics: helpDb.topics, references: helpDb.references, currentTopicId: currentTopicId, doSelectTopic: doSelectTopic, - currentTopic: currentTopic + currentTopic: currentTopic, + + dispose: dispose }; } @@ -107,6 +115,53 @@ define([ css: { marginTop: '12px' } + }, + markdown: { + css: { + + }, + inner: { + blockquote: { + fontSize: 'inherit', + marginLeft: '1em', + paddingLeft: '1em', + borderLeft: '3px silver solid' + }, + p: { + maxWidth: '50em' + }, + h1: { + marginTop: '0', + marginBottom: '0', + fontWeight: 'bold', + fontSize: '150%' + }, + h2: { + marginTop: '1em', + marginBottom: '0', + fontWeight: 'bold', + fontSize: '133%' + }, + h3: { + marginTop: '1em', + marginBottom: '0', + fontWeight: 'bold', + fontSize: '120%' + }, + h4: { + marginTop: '1em', + marginBottom: '0', + fontWeight: 'bold', + textDecoration: 'underline', + fontSize: '100%' + }, + h5: { + marginTop: '1em', + marginBottom: '0', + fontWeight: 'bold', + fontSize: '100%' + } + } } } }); @@ -115,8 +170,6 @@ define([ update: function (element, valueAccessor, allBindings, viewModel, bindingContext) { var markdown = marked(valueAccessor()); element.innerHTML = markdown; - // console.log(valueAccessor, bindingContext); - // element.innerHTML = 'hi!'; } }; @@ -167,7 +220,8 @@ define([ dataBind: { htmlMarkdown: 'content' }, - class: 'kb-help-markdown' + class: styles.classes.markdown + // class: 'kb-help-markdown ' }) // div({ // dataBind: { diff --git a/src/client/modules/plugins/components/modules/components/jsonViewer.css b/src/plugins/components/modules/components/jsonViewer.css similarity index 100% rename from src/client/modules/plugins/components/modules/components/jsonViewer.css rename to src/plugins/components/modules/components/jsonViewer.css diff --git a/src/client/modules/plugins/components/modules/components/jsonViewer.js b/src/plugins/components/modules/components/jsonViewer.js similarity index 100% rename from src/client/modules/plugins/components/modules/components/jsonViewer.js rename to src/plugins/components/modules/components/jsonViewer.js diff --git a/src/client/modules/plugins/components/modules/components/overlayPanel.js b/src/plugins/components/modules/components/overlayPanel.js similarity index 96% rename from src/client/modules/plugins/components/modules/components/overlayPanel.js rename to src/plugins/components/modules/components/overlayPanel.js index fe957e78f..bf30ba889 100644 --- a/src/client/modules/plugins/components/modules/components/overlayPanel.js +++ b/src/plugins/components/modules/components/overlayPanel.js @@ -1,10 +1,10 @@ /* overlayPanel -A generic full-height translucent panel which overlays the entire page to about 75% of the width. +A generic full-height translucent panel which overlays the entire page. It is a container component, and expects to be passed a component to render and a viewmodel to pass along. it offers a close function for the sub-component to use, in addition to invoking close -from a built-in close button (?) +from a built-in close button. */ define([ 'knockout-plus', @@ -24,6 +24,8 @@ define([ function viewModel(params) { var showPanel = ko.observable(); + var subscriptions = ko.kb.SubscriptionManager.make(); + var bus = NanoBus.make(); var openMessage = null; @@ -101,19 +103,15 @@ define([ // The viewmodel for the embedded component - params.component.subscribe(function (newValue) { + subscriptions.add(params.component.subscribe(function (newValue) { if (newValue) { bus.send('open', newValue); } else { if (showPanel()) { bus.send('close'); - // showPanel(false); - // embeddedComponentName(null); - // embeddedParams(null); - // embeddedViewModel(null); } } - }); + })); function onPanelAnimationEnd(data, ev) { if (ev.target.classList.contains(styles.classes.panelout)) { @@ -129,6 +127,10 @@ define([ } } + function dispose() { + subscriptions.dispose(); + } + return { showPanel: showPanel, panelStyle: panelStyle, @@ -140,7 +142,9 @@ define([ embeddedParams: embeddedParams, embeddedViewModel: embeddedViewModel, - onPanelAnimationEnd: onPanelAnimationEnd + onPanelAnimationEnd: onPanelAnimationEnd, + + dispose: dispose }; } diff --git a/src/client/modules/plugins/components/modules/components/overlayPanelBootstrappish.js b/src/plugins/components/modules/components/overlayPanelBootstrappish.js similarity index 96% rename from src/client/modules/plugins/components/modules/components/overlayPanelBootstrappish.js rename to src/plugins/components/modules/components/overlayPanelBootstrappish.js index 074fa3df9..9af8c5df1 100644 --- a/src/client/modules/plugins/components/modules/components/overlayPanelBootstrappish.js +++ b/src/plugins/components/modules/components/overlayPanelBootstrappish.js @@ -24,6 +24,8 @@ define([ function viewModel(params) { var showPanel = ko.observable(); + var subscriptions = ko.kb.SubscriptionManager.make(); + var bus = NanoBus.make(); var openMessage = null; @@ -101,19 +103,15 @@ define([ // The viewmodel for the embedded component - params.component.subscribe(function (newValue) { + subscriptions.add(params.component.subscribe(function (newValue) { if (newValue) { bus.send('open', newValue); } else { if (showPanel()) { bus.send('close'); - // showPanel(false); - // embeddedComponentName(null); - // embeddedParams(null); - // embeddedViewModel(null); } } - }); + })); function onPanelAnimationEnd(data, ev) { if (ev.target.classList.contains(styles.classes.panelout)) { @@ -129,6 +127,10 @@ define([ } } + function dispose() { + subscriptions.dispose(); + } + return { showPanel: showPanel, panelStyle: panelStyle, @@ -140,7 +142,9 @@ define([ embeddedParams: embeddedParams, embeddedViewModel: embeddedViewModel, - onPanelAnimationEnd: onPanelAnimationEnd + onPanelAnimationEnd: onPanelAnimationEnd, + + dispose: dispose }; } diff --git a/src/client/modules/plugins/components/modules/components/relativeClock.js b/src/plugins/components/modules/components/relativeClock.js similarity index 100% rename from src/client/modules/plugins/components/modules/components/relativeClock.js rename to src/plugins/components/modules/components/relativeClock.js diff --git a/src/client/modules/plugins/components/modules/components/table.js b/src/plugins/components/modules/components/table.js similarity index 98% rename from src/client/modules/plugins/components/modules/components/table.js rename to src/plugins/components/modules/components/table.js index 5532fb049..d7187041c 100644 --- a/src/client/modules/plugins/components/modules/components/table.js +++ b/src/plugins/components/modules/components/table.js @@ -172,6 +172,8 @@ define([ }); function viewModel(params, componentInfo) { + var subscriptions = ko.kb.SubscriptionManager.make(); + var slowLoadingThreshold = 300; var table = params.table; @@ -236,7 +238,7 @@ define([ // TODO: bind this to the table styles var rowHeight = 35; - height.subscribe(function (newValue) { + subscriptions.add(height.subscribe(function (newValue) { if (!newValue) { table.pageSize(null); } @@ -245,7 +247,7 @@ define([ var rowCount = Math.floor(newValue / rowHeight); table.pageSize(rowCount); - }); + })); // Calculate the height immediately upon component load height(calcHeight()); @@ -271,13 +273,7 @@ define([ doRowAction = null; } - // LIFECYCLE - - function dispose() { - if (resizeListener) { - window.removeEventListener('resize', resizer, false); - } - } + var isLoadingSlowly = ko.observable(false); @@ -298,21 +294,30 @@ define([ isLoadingSlowly(false); } - table.isLoading.subscribe(function (loading) { + subscriptions.add(table.isLoading.subscribe(function (loading) { if (loading) { timeLoading(); } else { cancelTimeLoading(); } - }); + })); function openLink(url) { - // console.log('open link?', cell, row); if (url) { window.open(url, '_blank'); } } + + // LIFECYCLE + + function dispose() { + if (resizeListener) { + window.removeEventListener('resize', resizer, false); + } + subscriptions.dispose(); + } + return { rows: table.rows, isLoading: table.isLoading, @@ -324,12 +329,12 @@ define([ state: table.state, doOpenUrl: doOpenUrl, doRowAction: doRowAction, - // lifecycle hooks - dispose: dispose, openLink: openLink, // thread env for useful plugin-level hooks. env: table.env, - actions: table.actions + actions: table.actions, + // lifecycle hooks + dispose: dispose }; } diff --git a/src/client/modules/plugins/components/modules/nanoBus.js b/src/plugins/components/modules/nanoBus.js similarity index 100% rename from src/client/modules/plugins/components/modules/nanoBus.js rename to src/plugins/components/modules/nanoBus.js diff --git a/src/client/modules/plugins/contact/config.yml b/src/plugins/contact/config.yml similarity index 100% rename from src/client/modules/plugins/contact/config.yml rename to src/plugins/contact/config.yml diff --git a/src/client/modules/plugins/contact/source/javascript/panel.js b/src/plugins/contact/source/javascript/panel.js similarity index 100% rename from src/client/modules/plugins/contact/source/javascript/panel.js rename to src/plugins/contact/source/javascript/panel.js diff --git a/src/client/modules/plugins/mainwindow/config.yml b/src/plugins/mainwindow/config.yml similarity index 100% rename from src/client/modules/plugins/mainwindow/config.yml rename to src/plugins/mainwindow/config.yml diff --git a/src/client/modules/plugins/mainwindow/modules/bodyWidget.js b/src/plugins/mainwindow/modules/bodyWidget.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/bodyWidget.js rename to src/plugins/mainwindow/modules/bodyWidget.js diff --git a/src/client/modules/plugins/mainwindow/modules/buttonbarWidget.css b/src/plugins/mainwindow/modules/buttonbarWidget.css similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/buttonbarWidget.css rename to src/plugins/mainwindow/modules/buttonbarWidget.css diff --git a/src/client/modules/plugins/mainwindow/modules/buttonbarWidget.js b/src/plugins/mainwindow/modules/buttonbarWidget.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/buttonbarWidget.js rename to src/plugins/mainwindow/modules/buttonbarWidget.js diff --git a/src/client/modules/plugins/mainwindow/modules/components/hamburgerMenu.js b/src/plugins/mainwindow/modules/components/hamburgerMenu.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/components/hamburgerMenu.js rename to src/plugins/mainwindow/modules/components/hamburgerMenu.js diff --git a/src/client/modules/plugins/mainwindow/modules/components/sidebarMenu.js b/src/plugins/mainwindow/modules/components/sidebarMenu.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/components/sidebarMenu.js rename to src/plugins/mainwindow/modules/components/sidebarMenu.js diff --git a/src/client/modules/plugins/mainwindow/modules/deploymentWidget.js b/src/plugins/mainwindow/modules/deploymentWidget.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/deploymentWidget.js rename to src/plugins/mainwindow/modules/deploymentWidget.js diff --git a/src/client/modules/plugins/mainwindow/modules/loginWidget.css b/src/plugins/mainwindow/modules/loginWidget.css similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/loginWidget.css rename to src/plugins/mainwindow/modules/loginWidget.css diff --git a/src/client/modules/plugins/mainwindow/modules/loginWidget.js b/src/plugins/mainwindow/modules/loginWidget.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/loginWidget.js rename to src/plugins/mainwindow/modules/loginWidget.js diff --git a/src/client/modules/plugins/mainwindow/modules/logoWidget.css b/src/plugins/mainwindow/modules/logoWidget.css similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/logoWidget.css rename to src/plugins/mainwindow/modules/logoWidget.css diff --git a/src/client/modules/plugins/mainwindow/modules/logoWidget.js b/src/plugins/mainwindow/modules/logoWidget.js similarity index 85% rename from src/client/modules/plugins/mainwindow/modules/logoWidget.js rename to src/plugins/mainwindow/modules/logoWidget.js index 632e65ccf..2e710a325 100644 --- a/src/client/modules/plugins/mainwindow/modules/logoWidget.js +++ b/src/plugins/mainwindow/modules/logoWidget.js @@ -8,8 +8,7 @@ define([ 'use strict'; var t = html.tag, a = t('a'), - img = t('img'), - div = t('div'); + img = t('img'); function factory(config) { var hostNode, container, runtime = config.runtime; @@ -21,17 +20,18 @@ define([ } function start() { - var version; - var uiTarget = runtime.config('buildInfo.target'); - if (uiTarget === 'prod') { - version = runtime.config('release.version'); - } else { - version = uiTarget; - } + // var uiTarget = runtime.config('buildInfo.target'); + // var version; + // if (uiTarget === 'prod') { + // version = runtime.config('release.version'); + // } else { + // version = uiTarget; + // } container.innerHTML = [ a({ href: runtime.config('resources.docSite.base.url'), - class: '-logo' + class: '-logo', + dataKbaseWidget: 'logo' }, img({ src: Plugin.plugin.fullPath + '/images/kbase_logo.png', width: '46px' diff --git a/src/client/modules/plugins/mainwindow/modules/mainWindow.css b/src/plugins/mainwindow/modules/mainWindow.css similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/mainWindow.css rename to src/plugins/mainwindow/modules/mainWindow.css diff --git a/src/client/modules/plugins/mainwindow/modules/mainWindow.js b/src/plugins/mainwindow/modules/mainWindow.js similarity index 98% rename from src/client/modules/plugins/mainwindow/modules/mainWindow.js rename to src/plugins/mainwindow/modules/mainWindow.js index b29dcb848..7a99bcb54 100644 --- a/src/client/modules/plugins/mainwindow/modules/mainWindow.js +++ b/src/plugins/mainwindow/modules/mainWindow.js @@ -101,6 +101,7 @@ define([ mount = node; container = document.createElement('div'); container.classList.add('plugin-mainwindow', 'widget-mainwindow', '-main'); + container.setAttribute('data-kbase-plugin', 'mainwindow'); mount.appendChild(container); } diff --git a/src/client/modules/plugins/mainwindow/modules/menuWidget.css b/src/plugins/mainwindow/modules/menuWidget.css similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/menuWidget.css rename to src/plugins/mainwindow/modules/menuWidget.css diff --git a/src/client/modules/plugins/mainwindow/modules/menuWidget.js b/src/plugins/mainwindow/modules/menuWidget.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/menuWidget.js rename to src/plugins/mainwindow/modules/menuWidget.js diff --git a/src/client/modules/plugins/mainwindow/modules/notificationWidget.1.js b/src/plugins/mainwindow/modules/notificationWidget.1.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/notificationWidget.1.js rename to src/plugins/mainwindow/modules/notificationWidget.1.js diff --git a/src/client/modules/plugins/mainwindow/modules/notificationWidget.css b/src/plugins/mainwindow/modules/notificationWidget.css similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/notificationWidget.css rename to src/plugins/mainwindow/modules/notificationWidget.css diff --git a/src/client/modules/plugins/mainwindow/modules/notificationWidget.js b/src/plugins/mainwindow/modules/notificationWidget.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/notificationWidget.js rename to src/plugins/mainwindow/modules/notificationWidget.js diff --git a/src/client/modules/plugins/mainwindow/modules/notificationWidget.multiple-types.css b/src/plugins/mainwindow/modules/notificationWidget.multiple-types.css similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/notificationWidget.multiple-types.css rename to src/plugins/mainwindow/modules/notificationWidget.multiple-types.css diff --git a/src/client/modules/plugins/mainwindow/modules/notificationWidget.multiple-types.js b/src/plugins/mainwindow/modules/notificationWidget.multiple-types.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/notificationWidget.multiple-types.js rename to src/plugins/mainwindow/modules/notificationWidget.multiple-types.js diff --git a/src/client/modules/plugins/mainwindow/modules/sidebarNav.css b/src/plugins/mainwindow/modules/sidebarNav.css similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/sidebarNav.css rename to src/plugins/mainwindow/modules/sidebarNav.css diff --git a/src/client/modules/plugins/mainwindow/modules/sidebarNav.js b/src/plugins/mainwindow/modules/sidebarNav.js similarity index 96% rename from src/client/modules/plugins/mainwindow/modules/sidebarNav.js rename to src/plugins/mainwindow/modules/sidebarNav.js index 163fa31e4..52ef714bd 100644 --- a/src/client/modules/plugins/mainwindow/modules/sidebarNav.js +++ b/src/plugins/mainwindow/modules/sidebarNav.js @@ -65,7 +65,7 @@ define([ id: button.id, label: button.label, icon: button.icon, - path: button.path.join('/'), + path: button.path, authRequired: button.authRequired ? true : false, active: ko.observable(false), allow: button.allow, @@ -111,9 +111,6 @@ define([ // TODO: rethink this!!! runtime.recv('session', 'change', function () { - // console.log('need to adjust the menu according to the new session state...'); - // render(); - // selectButton(); isAuthorized(runtime.service('session').isLoggedIn()); }); @@ -128,6 +125,7 @@ define([ // new component. container.innerHTML = div({ + dataKbaseWidget: 'sidebarNav', dataBind: { component: { name: SidebarMenuComponent.quotedName(), diff --git a/src/client/modules/plugins/mainwindow/modules/titleWidget.css b/src/plugins/mainwindow/modules/titleWidget.css similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/titleWidget.css rename to src/plugins/mainwindow/modules/titleWidget.css diff --git a/src/client/modules/plugins/mainwindow/modules/titleWidget.js b/src/plugins/mainwindow/modules/titleWidget.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/titleWidget.js rename to src/plugins/mainwindow/modules/titleWidget.js diff --git a/src/client/modules/plugins/mainwindow/modules/widgets/alert.js b/src/plugins/mainwindow/modules/widgets/alert.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/widgets/alert.js rename to src/plugins/mainwindow/modules/widgets/alert.js diff --git a/src/client/modules/plugins/mainwindow/modules/widgets/buttonBar.js b/src/plugins/mainwindow/modules/widgets/buttonBar.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/widgets/buttonBar.js rename to src/plugins/mainwindow/modules/widgets/buttonBar.js diff --git a/src/client/modules/plugins/mainwindow/modules/widgets/error.js b/src/plugins/mainwindow/modules/widgets/error.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/widgets/error.js rename to src/plugins/mainwindow/modules/widgets/error.js diff --git a/src/client/modules/plugins/mainwindow/modules/widgets/notFound.js b/src/plugins/mainwindow/modules/widgets/notFound.js similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/widgets/notFound.js rename to src/plugins/mainwindow/modules/widgets/notFound.js diff --git a/src/client/modules/plugins/mainwindow/modules/widgets/xbuttonBar.js.saved b/src/plugins/mainwindow/modules/widgets/xbuttonBar.js.saved similarity index 100% rename from src/client/modules/plugins/mainwindow/modules/widgets/xbuttonBar.js.saved rename to src/plugins/mainwindow/modules/widgets/xbuttonBar.js.saved diff --git a/src/client/modules/plugins/mainwindow/resources/images/hub3.png b/src/plugins/mainwindow/resources/images/hub3.png similarity index 100% rename from src/client/modules/plugins/mainwindow/resources/images/hub3.png rename to src/plugins/mainwindow/resources/images/hub3.png diff --git a/src/client/modules/plugins/mainwindow/resources/images/hub32.png b/src/plugins/mainwindow/resources/images/hub32.png similarity index 100% rename from src/client/modules/plugins/mainwindow/resources/images/hub32.png rename to src/plugins/mainwindow/resources/images/hub32.png diff --git a/src/client/modules/plugins/mainwindow/resources/images/kbase_logo.png b/src/plugins/mainwindow/resources/images/kbase_logo.png similarity index 100% rename from src/client/modules/plugins/mainwindow/resources/images/kbase_logo.png rename to src/plugins/mainwindow/resources/images/kbase_logo.png diff --git a/src/client/modules/plugins/mainwindow/resources/images/nouserpic.png b/src/plugins/mainwindow/resources/images/nouserpic.png similarity index 100% rename from src/client/modules/plugins/mainwindow/resources/images/nouserpic.png rename to src/plugins/mainwindow/resources/images/nouserpic.png diff --git a/src/plugins/mainwindow/test/logo.json b/src/plugins/mainwindow/test/logo.json new file mode 100644 index 000000000..b9606081c --- /dev/null +++ b/src/plugins/mainwindow/test/logo.json @@ -0,0 +1,27 @@ +[ + { + "description": "Sidebar nav", + "specs": [ + { + "description": "It should be there", + "baseSelector": [ + { + "type": "plugin", + "value": "mainwindow" + } + ], + "tasks": [ + { + "selector": [ + { + "type": "widget", + "value": "logo" + } + ], + "wait": 10000 + } + ] + } + ] + } +] \ No newline at end of file diff --git a/src/plugins/mainwindow/test/sidebarNav.json b/src/plugins/mainwindow/test/sidebarNav.json new file mode 100644 index 000000000..dc948c8a9 --- /dev/null +++ b/src/plugins/mainwindow/test/sidebarNav.json @@ -0,0 +1,27 @@ +[ + { + "description": "Sidebar nav", + "specs": [ + { + "description": "It should be there", + "baseSelector": [ + { + "type": "plugin", + "value": "mainwindow" + } + ], + "tasks": [ + { + "selector": [ + { + "type": "widget", + "value": "sidebarNav" + } + ], + "wait": 10000 + } + ] + } + ] + } +] \ No newline at end of file diff --git a/src/client/modules/plugins/message/config.yml b/src/plugins/message/config.yml similarity index 100% rename from src/client/modules/plugins/message/config.yml rename to src/plugins/message/config.yml diff --git a/src/client/modules/plugins/message/modules/panel.js b/src/plugins/message/modules/panel.js similarity index 100% rename from src/client/modules/plugins/message/modules/panel.js rename to src/plugins/message/modules/panel.js diff --git a/src/client/modules/plugins/narrativemanager/config.yml b/src/plugins/narrativemanager/config.yml similarity index 100% rename from src/client/modules/plugins/narrativemanager/config.yml rename to src/plugins/narrativemanager/config.yml diff --git a/src/client/modules/plugins/narrativemanager/modules/createNewPanel.js b/src/plugins/narrativemanager/modules/createNewPanel.js similarity index 100% rename from src/client/modules/plugins/narrativemanager/modules/createNewPanel.js rename to src/plugins/narrativemanager/modules/createNewPanel.js diff --git a/src/client/modules/plugins/narrativemanager/modules/narrativeManager.js b/src/plugins/narrativemanager/modules/narrativeManager.js similarity index 100% rename from src/client/modules/plugins/narrativemanager/modules/narrativeManager.js rename to src/plugins/narrativemanager/modules/narrativeManager.js diff --git a/src/client/modules/plugins/narrativemanager/modules/startPanel.js b/src/plugins/narrativemanager/modules/startPanel.js similarity index 100% rename from src/client/modules/plugins/narrativemanager/modules/startPanel.js rename to src/plugins/narrativemanager/modules/startPanel.js diff --git a/src/client/modules/plugins/welcome/config.yml b/src/plugins/welcome/config.yml similarity index 100% rename from src/client/modules/plugins/welcome/config.yml rename to src/plugins/welcome/config.yml diff --git a/src/client/modules/plugins/welcome/modules/deprecatedBulkUi.js b/src/plugins/welcome/modules/deprecatedBulkUi.js similarity index 100% rename from src/client/modules/plugins/welcome/modules/deprecatedBulkUi.js rename to src/plugins/welcome/modules/deprecatedBulkUi.js diff --git a/src/client/modules/plugins/welcome/modules/goodbyePanelWidget.js b/src/plugins/welcome/modules/goodbyePanelWidget.js similarity index 100% rename from src/client/modules/plugins/welcome/modules/goodbyePanelWidget.js rename to src/plugins/welcome/modules/goodbyePanelWidget.js diff --git a/src/client/modules/plugins/welcome/modules/helloPanelWidget.js b/src/plugins/welcome/modules/helloPanelWidget.js similarity index 100% rename from src/client/modules/plugins/welcome/modules/helloPanelWidget.js rename to src/plugins/welcome/modules/helloPanelWidget.js diff --git a/src/client/modules/plugins/welcome/modules/welcomePanelWidget.js b/src/plugins/welcome/modules/welcomePanelWidget.js similarity index 100% rename from src/client/modules/plugins/welcome/modules/welcomePanelWidget.js rename to src/plugins/welcome/modules/welcomePanelWidget.js diff --git a/src/test/integration-tests/specs/runner.js b/src/test/integration-tests/specs/runner.js new file mode 100644 index 000000000..6a509734c --- /dev/null +++ b/src/test/integration-tests/specs/runner.js @@ -0,0 +1,73 @@ +/*global describe, it, browser, exports */ + +var fs = require('fs'); + +function load(file) { + var contents = fs.readFileSync(__dirname + '/' + file); + return JSON.parse(contents); +} + +function buildAttribute(element) { + if (element instanceof Array) { + return element.map(function (element) { + return buildAttribute(element); + }).join(''); + } else { + return '[data-kbase-' + element.type + '="' + element.value + '"]'; + } +} +function buildSelector(path) { + return path.map(function (element) { + return buildAttribute(element); + }).join(' '); +} +function extend(path, elements) { + var newPath = path.map(function (el) { + return el; + }); + elements.forEach(function (element) { + newPath.push(element); + }); + return newPath; +} + +function runTest(test) { + describe(test.description, function () { + test.specs.forEach(function (spec) { + it(spec.description, function () { + browser.url('/'); + spec.tasks.forEach(function (task) { + if (task.selector) { + var selector = buildSelector(extend(spec.baseSelector, task.selector)); + if (task.wait) { + browser.waitForExist(selector, task.wait); + } else if (task.exists) { + browser.isExisting(selector); + } + if (task.action) { + switch (task.action) { + case 'click': + browser.click(selector); + break; + default: + throw new Error('Unknown task action ' + task.action); + } + } + } else if (task.navigate) { + console.log('navigating to', task.navigate.path); + browser.url('#' + task.navigate.path); + } + }); + }); + }); + }); +} + +function runTests(tests) { + tests.forEach(function (test) { + runTest(test); + }); +} + +exports.runTests = runTests; +exports.load = load; \ No newline at end of file diff --git a/src/test/integration-tests/specs/signinSpec.js b/src/test/integration-tests/specs/signinSpec.js new file mode 100644 index 000000000..a89d45bc3 --- /dev/null +++ b/src/test/integration-tests/specs/signinSpec.js @@ -0,0 +1,19 @@ +/* see http://webdriver.io/api */ + +'use strict'; + +var runner = require('./runner'); +var glob = require('glob'); + +var files = glob.sync('plugins/*/*.json', { + nodir: true, + cwd: __dirname +}); + +var pluginTests = files.map(function (file) { + return runner.load(file); +}); + +pluginTests.forEach(function (tests) { + return runner.runTests(tests); +}); diff --git a/temp/README.md b/temp/README.md new file mode 100644 index 000000000..356d7bc18 --- /dev/null +++ b/temp/README.md @@ -0,0 +1 @@ +# temporary files directory \ No newline at end of file diff --git a/test/selenium-firefox-test.js b/test/selenium-firefox-test.js new file mode 100644 index 000000000..c4c3d4adc --- /dev/null +++ b/test/selenium-firefox-test.js @@ -0,0 +1,47 @@ +// setup + +let webdriver = require('selenium-webdriver'), + By = webdriver.By, + until = webdriver.until; + +let firefox = require('selenium-webdriver/firefox'); + +let binary = new firefox.Binary(firefox.Channel.NIGHTLY); +binary.addArguments('-headless'); + +// let driver = new webdriver.Builder() +// .forBrowser('firefox') +// .setFirefoxOptions(new firefox.Options().setBinary(binary)) +// .build(); + +let firefoxOptions = new firefox.Options(); +firefoxOptions.setBinary(''); +firefoxOptions.headless(); + +const driver = new webdriver.Builder() + .forBrowser('firefox') + .setFirefoxOptions(firefoxOptions) + .build(); + +// the test + +driver.get('https://www.google.com'); +driver.findElement(By.name('q')).sendKeys('webdriver'); + +driver.sleep(1000).then(function() { + driver.findElement(By.name('q')).sendKeys(webdriver.Key.TAB); +}); + +driver.findElement(By.name('btnK')).click(); + +driver.sleep(2000).then(function () { + driver.getTitle().then(function (title) { + if (title === 'webdriver - Google Search') { + console.log('OK'); + } else { + console.log('BOO'); + } + }); +}); + +driver.quit(); \ No newline at end of file diff --git a/test/specs/dataviewSpec.js b/test/specs/dataviewSpec.js deleted file mode 100644 index 3043a315f..000000000 --- a/test/specs/dataviewSpec.js +++ /dev/null @@ -1,338 +0,0 @@ -/* global describe, it, expect, browser, beforeAll */ -/* -This set of tests does not require authorization... -*/ - -// TODO: use a clean-up page (an empty page, should put one in the web app), to "clear" -// the browser between tests. Otherwise a slow loading may result in getting value from -// the previous one. - -'use strict'; -// Note this is in an intermediary development state -- requiring token to be inserted -// below before runnint tests. The token will be moved to a non-checked-in -// config file later. -var token = process.env.KBASE_SESSION; -// var timeout = process.env.TEST_TIMEOUT || 30000; -var timeout = 30000; - -// console.log('Starting with ' + token + ', ' + timeout); - -var testingTable = [ - // { - // module: 'KBaseGenomes', - // type: ' Genome', - // version: '8.0', - // title: 'Dataview for Sorghum bicolor genome', - // comments: 'Eukaryotic genome view', - // objectRef: '22676/8/6', - // nodes: [ - // // title - // { - // selector: '[data-kbase-view="dataview"] [data-widget="dataview-overview"] h3', - // text: 'Transcriptome_Sbi_shoots_PEG_upregulated' - // }, - // // Publications subwidget - // { - // selector: '[data-kbase-view="dataview"] [data-panel="publications"] [data-element="title"]', - // text: 'Publications' - // }, - // { - // selector: '[data-kbase-view="dataview"] [data-panel="publications"] [name="lit-query-box"]', - // value: 'Sorghum bicolor' - // }, - // // Taxonomy subwidget - // { - // selector: '[data-kbase-view="dataview"] [data-panel="taxonomy"] [data-element="title"]', - // text: 'Taxonomy' - // }, - // { - // selector: '[data-kbase-view="dataview"] [data-panel="taxonomy"] [data-field="scientific-name"]', - // text: 'Sorghum bicolor' - // }, - // // // Assembly and annotation sub widget - // // // The title - // { - // selector: '[data-kbase-view="dataview"] [data-panel="assembly-annotation"] [data-element="title"]', - // text: 'Assembly and Annotation' - // }, - // // // The response for euks - // { - // selector: '[data-kbase-view="dataview"] [data-panel="assembly-annotation"] [data-element="body"]', - // text: 'Browsing Eukaryotic Genome Features is not supported at this time.' - // } - // ] - // }, { - // module: 'KBaseGenomes', - // type: ' Genome', - // version: '7.0', - // title: 'Dataview for Rhodobacter sphaeroides genome', - // objectRef: '22676/5/1', - // nodes: [ - // // TItle - // { - // selector: '[data-widget="dataview-overview"] h3', - // text: 'Rhodobacter_sphaeroides_2.4.1_KBase' - // }, - // // Publications subwidget - // { - // selector: '[data-kbase-view="dataview"] [data-panel="publications"] [data-element="title"]', - // text: 'Publications' - // }, - // { - // selector: '[data-kbase-view="dataview"] [data-panel="publications"] [name="lit-query-box"]', - // value: 'Rhodobacter sphaeroides 2.4.1' - // }, - // // Taxonomy subwidget - // { - // selector: '[data-kbase-view="dataview"] [data-panel="taxonomy"] [data-element="title"]', - // text: 'Taxonomy' - // }, - // { - // selector: '[data-kbase-view="dataview"] [data-panel="taxonomy"] [data-field="scientific-name"]', - // text: 'Rhodobacter sphaeroides 2.4.1' - // }, - // ] - // }, { - // module: 'KBaseGenomes', - // type: ' ContigSet', - // version: '3.0', - // title: 'Dataview for Pangenome', - // objectRef: '8020/70/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: '12319_RefSeq_contigset_legacy' - // }] - // }, { - // module: 'KBaseGenomes', - // type: ' Pangenome', - // version: '4.0', - // title: 'Dataview for Panegenome', - // objectRef: '22676/17/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'AMC_OrthoMCL' - // }] - // }, { - // module: 'KBaseBiochem', - // type: ' Media', - // version: '1.0', - // title: 'Dataview for Media', - // objectRef: '22676/6/8', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'Rsp-minimal' - // }] - // }, { - // module: 'KBaseFile', - // type: 'PairedEndLibrary', - // version: '4.0', - // title: 'Dataview for Paired End Library', - // objectRef: '15/38/4', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'rhodo.art.jgi.reads' - // }] - // }, { - // module: 'KBaseFBA', - // type: ' FBAModel', - // version: '14.0', - // title: 'Dataview for FBAModel', - // objectRef: '22676/11/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'Caldi_gapfilled_models' - // }] - // }, { - // module: 'KBaseFBA', - // type: ' FBA', - // version: '13.0', - // title: 'Dataview for FBA ', - // objectRef: '22676/12/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: '211586.9.glucose_fba' - // }] - // }, { - // module: 'KBaseGenomeAnnotations', - // type: ' Assembly', - // version: '6.0', - // title: 'Dataview for Assembly', - // objectRef: '22676/13/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'GCF_000001735.3_assembly' - // }] - // }, { - // module: 'KBaseOntology', - // type: 'OntologyDictionary', - // version: '4.1', - // title: 'Dataview for Ontology Dictionary ', - // objectRef: '22676/14/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'environment_ontology' - // }] - // }, { - // module: 'KBaseOntology', - // type: 'OntologyTranslation', - // version: '3.0', - // title: 'Dataview for Ontology Translation ', - // objectRef: '22676/15/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'sso2go' - // }] - // }, { - // module: 'KBasePhenotypes', - // type: 'PhenotypeSet', - // version: '3.0', - // title: 'Dataview for Ontology Translation ', - // objectRef: '22676/16/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'SB2B_phenotypes' - // }] - // }, { - // module: 'KBaseSets', - // type: 'ReadsSet', - // version: '1.0', - // title: 'Dataview for Reads Set ', - // objectRef: '22676/18/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'set_o_reads' - // }] - // }, { - // module: 'KBaseSearch', - // type: 'GenomeSet', - // version: '3.0', - // title: 'Dataview for Genome Set ', - // objectRef: '22676/19/1', - // nodes: [{ - // selector: '[data-widget="dataview-overview"] h3', - // text: 'Carsonella.GS' - // }] - // }, - // KBaseTrees.Tree-1.0 - { - module: 'KBaseTrees', - type: 'Tree', - version: '1.0', - title: 'Dataview for Tree ', - objectRef: '22676/20/1', - nodes: [{ - selector: '[data-widget="dataview-overview"] h3', - text: 'AMC_tree' - }] - }, - // Communities.FunctionalMatrix-3.0 - { - module: 'Communities', - type: 'FunctionalMatrix', - version: '3.0', - title: 'Dataview for Functional Matrix ', - objectRef: '22676/21/1', - nodes: [{ - selector: '[data-widget="dataview-overview"] h3', - text: 'wgs.collection.abundance_profile.norm' - }] - }, - // Communities.Metagenome-2.0 - { - module: 'Communities', - type: 'Metagenome', - version: '2.0', - title: 'Dataview for Functional Matrix ', - objectRef: '22676/22/1', - nodes: [{ - selector: '[data-widget="dataview-overview"] h3', - text: 'mgm4477902.3.wgs.metagenome' - }] - }, - // GenomeUtil.BlastOuptut-4.0 - { - module: 'GenomeUtil', - type: 'BlastOutput', - version: '4.0', - title: 'Dataview for Blast Output ', - objectRef: '22676/23/1', - nodes: [{ - selector: '[data-widget="dataview-overview"] h3', - text: 'balst_output_1' - }] - } -]; -// module: 'KBaseFeatureValues', -// type: ' ExpressionMatrix', -// version: '1.0', -// title: 'Dataview for Expression Matrix (need better data!)', -// objectRef: '22676/7/1', -// nodes: [{ -// selector: '[data-widget="dataview-overview"] h3', -// text: 'SomeFakeData' -// }] - - -describe('Dataview Specs ', function () { - beforeAll(function () { - browser.url('/'); - browser.setCookie({ - name: 'kbase_session', - value: token, - domain: 'ci.kbase.us' - }); - browser.waitForExist('[data-element="user-label"]', timeout); - }); - var lastObjectRef; - testingTable.forEach(function (test) { - test.nodes.forEach(function (node) { - it(test.title, function () { - if (lastObjectRef !== test.objectRef) { - browser.url('/#dataview/' + test.objectRef); - lastObjectRef = test.objectRef; - } - browser.waitForExist(node.selector, timeout); - browser.waitUntil(function () { - if (node.text) { - return browser.getText(node.selector) === node.text; - } else if (node.value) { - return browser.getValue(node.selector) === node.value; - } - }, timeout, 'Could not find text "' + node.text + '" on selector "' + node.selector + '"'); - }); - }); - }); - -}); - - -// var unimplementedTypes = [{ -// module: 'Empty', -// type: 'Atype', -// version: '1.0', -// objectRef: '13105/23/1', -// objectName: 'empty' -// }]; -// describe('Types with no viewers ', function () { -// beforeAll(function () { -// browser.url('/'); -// browser.setCookie({ -// name: 'kbase_session', -// value: token, -// domain: 'ci.kbase.us' -// }); -// browser.waitForExist('[data-element="user-label"]', 5000); -// }); -// var objectNameSelector = '[data-widget="dataview-overview"] h3'; -// var dataViewSelector = '[data-element="data-view"]'; -// unimplementedTypes.forEach(function (test) { -// it(test.title, function () { -// browser.url('/#dataview/' + test.objectRef); -// browser.waitForExist(dataViewSelector, 5000); -// var text = browser.getText(objectNameSelector); -// expect(text).toEqual(test.objectName); -// text = browser.getText(dataViewSelector); -// expect(text).toEqual('This widget does not have a specific visualization'); -// }); -// }); -// }); diff --git a/test/specs/rootSpec.js b/test/specs/rootSpec.js deleted file mode 100644 index 0aedf17e9..000000000 --- a/test/specs/rootSpec.js +++ /dev/null @@ -1,11 +0,0 @@ -/* global describe, it, expect, browser */ -'use strict'; -describe('Root Spec', function () { - it('Should navigate to the home page', function () { - browser.url('/'); - browser.waitForExist('.kb-widget-title', 30000); - var text = browser.getText('.kb-widget-title'); - - expect(text).toEqual('KBase Sign In'); - }); -}); diff --git a/test/unit-tests/karma.conf.js b/test/unit-tests/karma.conf.js index ac188acb5..152125488 100644 --- a/test/unit-tests/karma.conf.js +++ b/test/unit-tests/karma.conf.js @@ -29,9 +29,9 @@ module.exports = function (config) { // Our test specs { pattern: 'test/unit-tests/specs/**/*.js', included: false }, // Spot pickup the config files - { pattern: 'build/build/client/modules/config/*.yml', included: false }, - { pattern: 'build/build/client/modules/config/*.json', included: false }, - { pattern: 'build/build/client/modules/deploy/*.json', included: false }, + // { pattern: 'build/build/client/modules/config/*.yml', included: false }, + // { pattern: 'build/build/client/modules/config/*.json', included: false }, + // { pattern: 'build/build/client/modules/deploy/*.json', included: false }, 'test/unit-tests/setup.js', ], diff --git a/test/unit-tests/setup.js b/test/unit-tests/setup.js index 605e43da1..4e910c631 100644 --- a/test/unit-tests/setup.js +++ b/test/unit-tests/setup.js @@ -19,7 +19,8 @@ requirejs.config({ // NOTE: this needs to be synced from src/require-config.js // TODO: bring these in programmatically paths: { - bluebird: 'bower_components/bluebird/bluebird', + ajv: 'node_modules/ajv/ajv.bundle', + bluebird: 'node_modules/bluebird/bluebird', bootstrap_css: 'bower_components/bootstrap/css/bootstrap', bootstrap: 'bower_components/bootstrap/js/bootstrap', css: 'bower_components/require-css/css', @@ -27,11 +28,19 @@ requirejs.config({ d3_sankey_css: 'bower_components/d3-plugins-sankey/sankey', d3_sankey: 'bower_components/d3-plugins-sankey/sankey', d3: 'bower_components/d3/d3', - domReady: 'bower_components/requirejs-domready/domReady', + // d3: 'node_modules/d3/d3', + // 'd3-sankey': 'node_modules/d3-sankey/d3-sankey', + // 'd3-collection': 'node_modules/d3-collection/d3-collection', + // 'd3-shape': 'node_modules/d3-shape/d3-shape', + // 'd3-array': 'node_modules/d3-array/d3-array', + // 'd3-path': 'node_modules/d3-path/d3-path', + + dagre: 'node_modules/dagre/dagre', datatables_bootstrap_css: 'bower_components/datatables-bootstrap3-plugin/css/datatables-bootstrap3', datatables_bootstrap: 'bower_components/datatables-bootstrap3-plugin/js/datatables-bootstrap3', datatables_css: 'bower_components/datatables/css/jquery.dataTables', datatables: 'bower_components/datatables/js/jquery.dataTables', + domReady: 'bower_components/requirejs-domready/domReady', fileSaver: 'bower_components/file-saver/FileSaver', font_awesome: 'bower_components/font-awesome/css/font-awesome', 'google-code-prettify-style': 'bower_components/google-code-prettify/prettify', @@ -54,17 +63,20 @@ requirejs.config({ 'knockout-mapping': 'bower_components/bower-knockout-mapping/knockout.mapping', 'knockout-plus': 'lib/knockout-plus', 'knockout-validation': 'bower_components/knockout-validation/knockout.validation', + lodash: 'node_modules/lodash/lodash', marked: 'bower_components/marked/marked', md5: 'bower_components/spark-md5/spark-md5', moment: 'bower_components/moment/moment', numeral: 'bower_components/numeral/numeral', nunjucks: 'bower_components/nunjucks/nunjucks', plotly: 'bower_components/plotly.js/plotly', - postal: 'bower_components/postal.js/postal', + select2: 'bower_components/select2/js/select2.full', + select2_css: 'bower_components/select2/css/select2', + select2_bootstrap_theme: 'bower_components/select2-bootstrap-theme/select2-bootstrap', text: 'bower_components/requirejs-text/text', underscore: 'bower_components/underscore/underscore', uuid: 'bower_components/pure-uuid/uuid', - yaml: 'bower_components/requirejs-yaml/yaml' + yaml: 'bower_components/requirejs-yaml/yaml' }, shim: { bootstrap: { diff --git a/wdio.conf.js b/test/wdio.conf.integration.js similarity index 85% rename from wdio.conf.js rename to test/wdio.conf.integration.js index c6dc39ce2..db8f5ce56 100644 --- a/wdio.conf.js +++ b/test/wdio.conf.integration.js @@ -1,5 +1,5 @@ exports.config = { - + // // ================== // Specify Test Files @@ -10,7 +10,7 @@ exports.config = { // directory is where your package.json resides, so `wdio` will be called from there. // specs: [ - './test/specs/**/*.js' + './build/test/integration-tests/specs/**/*.js' ], // Patterns to exclude. exclude: [ @@ -38,14 +38,39 @@ exports.config = { // Sauce Labs platform configurator - a great tool to configure your capabilities: // https://docs.saucelabs.com/reference/platforms-configurator // - capabilities: [{ - // maxInstances can get overwritten per capability. So if you have an in-house Selenium - // grid with only 5 firefox instances available you can make sure that not more than - // 5 instances get started at a time. - maxInstances: 5, - // - browserName: 'firefox' - }], + capabilities: [ + // { + // browserName: 'phantomjs', + // ignoreSslErrors: true, + // 'phantomjs.binary.path': '/Volumes/KBaseWork/Work/sprints/auth2-2017/auth2/kbase-ui/node_modules/.bin/phantomjs', + // 'phantomjs.cli.args': ['--web-security=false', '--ssl-protocol=any', '--ignore-ssl-errors=true'] + // }, + { + // maxInstances can get overwritten per capability. So if you have an in-house Selenium + // grid with only 5 firefox instances available you can make sure that not more than + // 5 instances get started at a time. + maxInstances: 5, + // + browserName: 'firefox', + //platform: 'macOS 10.12', + //version: 'latest', + 'moz:firefoxOptions': { + args: ['-headless'] + }, + acceptInsecureCerts: true + }, + // Chrome + // { + // maxInstances: 5, + // browserName: 'chrome', + // //platform: 'macOS 10.12', + // //version: 'latest', + // // chromeOptions: { + // // args: ['--headless'] + // // }, + // acceptInsecureCerts: true + // } + ], // // =================== // Test Configurations @@ -68,7 +93,7 @@ exports.config = { bail: 0, // // Saves a screenshot to a given path if a command fails. - screenshotPath: './errorShots/', + screenshotPath: './temp/files/errorShots/', // // Set a base URL in order to shorten url command calls. If your url parameter starts // with "/", then the base url gets prepended. @@ -106,7 +131,8 @@ exports.config = { // Services take over a specific job you don't want to take care of. They enhance // your test setup with almost no effort. Unlike plugins, they don't add new // commands. Instead, they hook themselves up into the test process. - services: ['selenium-standalone','phantomjs'], + services: ['selenium-standalone'], + // services: ['selenium-standalone'], // // Framework you want to run your specs with. // The following are supported: Mocha, Jasmine, and Cucumber @@ -120,22 +146,22 @@ exports.config = { // The only one supported by default is 'dot' // see also: http://webdriver.io/guide/testrunner/reporters.html reporters: ['dot'], - + // // Options to be passed to Jasmine. jasmineNodeOpts: { // // Jasmine default timeout - defaultTimeoutInterval: 10000, + defaultTimeoutInterval: 30000, // // The Jasmine framework allows interception of each assertion in order to log the state of the application // or website depending on the result. For example, it is pretty handy to take a screenshot every time // an assertion fails. - expectationResultHandler: function(passed, assertion) { + expectationResultHandler: function (passed, assertion) { // do something } }, - + // // ===== // Hooks @@ -245,4 +271,4 @@ exports.config = { */ // onComplete: function(exitCode) { // } -} +}; diff --git a/testing/Gruntfile.js b/testing/Gruntfile.js new file mode 100644 index 000000000..1d37c46ef --- /dev/null +++ b/testing/Gruntfile.js @@ -0,0 +1,84 @@ +/*global module*/ + +/** + * Gruntfile for kbase-ui + */ + +module.exports = function (grunt) { + 'use strict'; + + // Load External Tasks + grunt.loadNpmTasks('grunt-karma'); + grunt.loadNpmTasks('grunt-coveralls'); + grunt.loadNpmTasks('grunt-webdriver'); + + grunt.initConfig({ + pkg: grunt.file.readJSON('package.json'), + + // Testing with Karma! + // karma: { + // unit: { + // configFile: 'test/unit-tests/karma.conf.js' + // }, + // dev: { + // // to do - add watch here + // configFile: 'test/unit-tests/karma.conf.js', + // reporters: ['progress', 'coverage'], + // coverageReporter: { + // dir: 'build/build-test-coverage/', + // reporters: [ + // { type: 'html', subdir: 'html' } + // ] + // }, + // autoWatch: true, + // singleRun: false + // } + // }, + + // // Run coveralls and send the info. + // coveralls: { + // options: { + // force: true + // }, + // 'kbase-ui': { + // src: 'build/build-test-coverage/lcov/**/*.info' + // } + // }, + + webdriver: { + // note should be called with a base of build/tests/integration-tests + integration: { + configFile: './integration-tests/wdio.conf.local.js', + baseUrl: 'https://' + grunt.option('host') + '.kbase.us' + }, + sauce: { + configFile: './test/wdio.conf.sauce.js' + }, + travis: { + configFile: './test/wdio.conf.travis.js' + } + } + }); + + + // Does a single, local, unit test run. + // TODO: more work on the webdriver tests, don't work now. + // grunt.registerTask('unit-test', [ + // 'karma:unit' + // ]); + + grunt.registerTask('integration-tests', [ + 'webdriver:integration' + ]); + + // Does a single unit test run, then sends + // the lcov results to coveralls. Intended for running + // from travis-ci. + // grunt.registerTask('test-travis', [ + // // 'karma:unit', + // // upcoming + // // 'webdriver:travis', + // 'coveralls' + // ]); + +}; diff --git a/testing/README.md b/testing/README.md new file mode 100644 index 000000000..027ed4163 --- /dev/null +++ b/testing/README.md @@ -0,0 +1 @@ +# Testing Directory \ No newline at end of file diff --git a/testing/integration-tests/runner.js b/testing/integration-tests/runner.js new file mode 100644 index 000000000..b4e604b0d --- /dev/null +++ b/testing/integration-tests/runner.js @@ -0,0 +1,68 @@ +/*global describe, it, browser, exports */ + +var fs = require('fs'); + +function load(file) { + var contents = fs.readFileSync(file); + return JSON.parse(contents); +} + +function buildAttribute(element) { + if (element instanceof Array) { + return element.map(function (element) { + return buildAttribute(element); + }).join(''); + } else { + return '[data-kbase-' + element.type + '="' + element.value + '"]'; + } +} +function buildSelector(path) { + return path.map(function (element) { + return buildAttribute(element); + }).join(' '); +} +function extend(path, elements) { + var newPath = path.map(function (el) { + return el; + }); + elements.forEach(function (element) { + newPath.push(element); + }); + return newPath; +} + +function runTest(test) { + describe(test.description, function () { + test.specs.forEach(function (spec) { + it(spec.description, function () { + browser.url('/'); + spec.tasks.forEach(function (task) { + var selector = buildSelector(extend(spec.baseSelector, task.selector)); + if (task.wait) { + browser.waitForExist(selector); + } else if (task.exists) { + browser.isExisting(selector); + } + if (task.action) { + switch (task.action) { + case 'click': + browser.click(selector); + break; + default: + throw new Error('Unknown task action ' + task.action); + } + } + }); + }); + }); + }); +} + +function runTests(tests) { + tests.forEach(function (test) { + runTest(test); + }); +} + +exports.runTests = runTests; +exports.load = load; \ No newline at end of file diff --git a/testing/integration-tests/specs/plugins/auth2-client.json b/testing/integration-tests/specs/plugins/auth2-client.json new file mode 100644 index 000000000..ec3f67506 --- /dev/null +++ b/testing/integration-tests/specs/plugins/auth2-client.json @@ -0,0 +1,138 @@ +[ + { + "description": "Signin spec", + "specs": [ + { + "description": "Click the signin button", + "baseSelector": [ + { + "type": "plugin", + "value": "auth2-client" + }, + { + "type": "component", + "value": "login-view" + } + ], + "tasks": [ + { + "selector": [ + { + "type": "button", + "value": "signin" + } + ], + "wait": 30000, + "action": "click" + }, + { + "selector": [ + [ + { + "type": "component", + "value": "signin-button" + }, + { + "type": "name", + "value": "google" + } + ] + ], + "wait": 3000 + }, + { + "selector": [ + [ + { + "type": "component", + "value": "signin-button" + }, + { + "type": "name", + "value": "globus" + } + ] + ], + "exists": true + } + ] + } + ] + }, + { + "description": "Signup spec", + "specs": [ + { + "description": "Click the new user button and get the signup view", + "baseSelector": [ + { + "type": "plugin", + "value": "auth2-client" + } + ], + "tasks": [ + { + "selector": [ + { + "type": "component", + "value": "login-view" + }, + { + "type": "button", + "value": "signup" + } + ], + "wait": 30000, + "action": "click" + }, + { + "selector": [ + { + "type": "widget", + "value": "signup" + }, + { + "type": "component", + "value": "signup-view" + }, + [ + { + "type": "component", + "value": "signin-button" + }, + { + "type": "name", + "value": "google" + } + ] + ], + "wait": 3000 + }, + { + "selector": [ + { + "type": "widget", + "value": "signup" + }, + { + "type": "component", + "value": "signup-view" + }, + [ + { + "type": "component", + "value": "signin-button" + }, + { + "type": "name", + "value": "globus" + } + ] + ], + "exists": true + } + ] + } + ] + } +] \ No newline at end of file diff --git a/testing/integration-tests/specs/signinSpec.js b/testing/integration-tests/specs/signinSpec.js new file mode 100644 index 000000000..f747c6163 --- /dev/null +++ b/testing/integration-tests/specs/signinSpec.js @@ -0,0 +1,13 @@ +/* global describe, it, browser, expect */ + +/* see http://webdriver.io/api */ + +'use strict'; + +var runner = require('../runner'); + +console.log('hmm, where am i?', process.cwd()); + +var tests = runner.load('./integration-tests/specs/plugins/auth2-client.json'); + +runner.runTests(tests); diff --git a/test/wdio.conf.js b/testing/integration-tests/wdio.conf.local.js similarity index 90% rename from test/wdio.conf.js rename to testing/integration-tests/wdio.conf.local.js index ec8d91ffb..c1f6b382c 100644 --- a/test/wdio.conf.js +++ b/testing/integration-tests/wdio.conf.local.js @@ -1,10 +1,5 @@ exports.config = { - sauceConnect: 'true', - sauceConnectOpts: { - doctor: true - }, - // // ================== // Specify Test Files @@ -15,7 +10,7 @@ exports.config = { // directory is where your package.json resides, so `wdio` will be called from there. // specs: [ - './test/specs/**/*.js' + './integration-tests/specs/**/*.js' ], // Patterns to exclude. exclude: [ @@ -44,7 +39,12 @@ exports.config = { // https://docs.saucelabs.com/reference/platforms-configurator // capabilities: [ - // Firefox + // { + // browserName: 'phantomjs', + // ignoreSslErrors: true, + // 'phantomjs.binary.path': '/Volumes/KBaseWork/Work/sprints/auth2-2017/auth2/kbase-ui/node_modules/.bin/phantomjs', + // 'phantomjs.cli.args': ['--web-security=false', '--ssl-protocol=any', '--ignore-ssl-errors=true'] + // }, { // maxInstances can get overwritten per capability. So if you have an in-house Selenium // grid with only 5 firefox instances available you can make sure that not more than @@ -52,18 +52,24 @@ exports.config = { maxInstances: 5, // browserName: 'firefox', - platform: 'macOS 10.12', - version: '39.0', + //platform: 'macOS 10.12', + //version: 'latest', + 'moz:firefoxOptions': { + args: ['-headless'] + }, acceptInsecureCerts: true }, // Chrome - { - maxInstances: 5, - browserName: 'chrome', - platform: 'macOS 10.12', - version: '59.0', - acceptInsecureCerts: true - } + // { + // maxInstances: 5, + // browserName: 'chrome', + // //platform: 'macOS 10.12', + // //version: 'latest', + // // chromeOptions: { + // // args: ['--headless'] + // // }, + // acceptInsecureCerts: true + // } ], // // =================== @@ -74,7 +80,7 @@ exports.config = { // By default WebdriverIO commands are executed in a synchronous way using // the wdio-sync package. If you still want to run your tests in an async way // e.g. using promises you can set the sync option to false. - sync: false, + sync: true, // // Level of logging verbosity: silent | verbose | command | data | result | error logLevel: 'verbose', @@ -87,7 +93,7 @@ exports.config = { bail: 0, // // Saves a screenshot to a given path if a command fails. - screenshotPath: './errorShots/', + screenshotPath: './integration-tests/errorShots/', // // Set a base URL in order to shorten url command calls. If your url parameter starts // with "/", then the base url gets prepended. @@ -125,8 +131,7 @@ exports.config = { // Services take over a specific job you don't want to take care of. They enhance // your test setup with almost no effort. Unlike plugins, they don't add new // commands. Instead, they hook themselves up into the test process. - services: ['sauce'], - // services: ['phantomjs', 'sauce'], + services: ['selenium-standalone'], // services: ['selenium-standalone'], // // Framework you want to run your specs with. @@ -147,7 +152,7 @@ exports.config = { jasmineNodeOpts: { // // Jasmine default timeout - defaultTimeoutInterval: 10000, + defaultTimeoutInterval: 30000, // // The Jasmine framework allows interception of each assertion in order to log the state of the application // or website depending on the result. For example, it is pretty handy to take a screenshot every time diff --git a/testing/package.json b/testing/package.json new file mode 100644 index 000000000..640d0398d --- /dev/null +++ b/testing/package.json @@ -0,0 +1,49 @@ +{ + "name": "kbase-ui-testing", + "version": "1.6.4", + "keywords": [ + "util", + "functional", + "client", + "browser" + ], + "author": "KBase", + "license": "SEE LICENSE IN LICENSE.md", + "repository": { + "type": "git", + "url": "https://github.com/kbase/kbase-ui.git" + }, + "contributors": [ + "eapearson", + "briehl" + ], + "devDependencies": { + "chalk": "2.3.2", + "fs-extra": "3.0.1", + "glob": "7.1.2", + "grunt": "1.0.2", + "grunt-bower-task": "0.5.0", + "grunt-cli": "1.2.0", + "grunt-coveralls": "1.0.1", + "grunt-karma": "2.0.0", + "grunt-shell": "2.1.0", + "grunt-webdriver": "2.0.3", + "jasmine": "3.1.0", + "jasmine-core": "3.1.0", + "karma": "2.0.0", + "karma-chrome-launcher": "2.2.0", + "karma-cli": "1.0.1", + "karma-coverage": "1.1.1", + "karma-jasmine": "1.1.1", + "karma-requirejs": "1.1.0", + "karma-webdriver-launcher": "1.0.5", + "sauce-connect-launcher": "1.2.3", + "selenium-standalone": "6.12.0", + "selenium-webdriver": "4.0.0-alpha.1", + "wdio-dot-reporter": "0.0.9", + "wdio-jasmine-framework": "^0.3.1", + "wdio-selenium-standalone-service": "0.0.9", + "wdio-sauce-service": "0.4.8", + "webdriverio": "4.11.0" + } +} diff --git a/testing/testx/integration-tests/specs/runner.js b/testing/testx/integration-tests/specs/runner.js new file mode 100644 index 000000000..b3d198926 --- /dev/null +++ b/testing/testx/integration-tests/specs/runner.js @@ -0,0 +1,60 @@ +/*global describe, it, browser, exports */ + +function buildAttribute(element) { + if (element instanceof Array) { + return element.map(function (element) { + return buildAttribute(element); + }).join(''); + } else { + return '[data-kbase-' + element.type + '="' + element.value + '"]'; + } +} +function buildSelector(path) { + return path.map(function (element) { + return buildAttribute(element); + }).join(' '); +} +function extend(path, elements) { + var newPath = path.map(function (el) { + return el; + }); + elements.forEach(function (element) { + newPath.push(element); + }); + return newPath; +} + +function runTest(test) { + describe(test.description, function () { + test.specs.forEach(function (spec) { + it(spec.description, function () { + browser.url('/'); + spec.tasks.forEach(function (task) { + var selector = buildSelector(extend(spec.baseSelector, task.selector)); + if (task.wait) { + browser.waitForExist(selector); + } else if (task.exists) { + browser.isExisting(selector); + } + if (task.action) { + switch (task.action) { + case 'click': + browser.click(selector); + break; + default: + throw new Error('Unknown task action ' + task.action); + } + } + }); + }); + }); + }); +} + +function runTests(tests) { + tests.forEach(function (test) { + runTest(test); + }); +} + +exports.runTests = runTests; \ No newline at end of file diff --git a/testing/testx/integration-tests/specs/signinSpec.js b/testing/testx/integration-tests/specs/signinSpec.js new file mode 100644 index 000000000..8d0b9f939 --- /dev/null +++ b/testing/testx/integration-tests/specs/signinSpec.js @@ -0,0 +1,114 @@ +/* global describe, it, browser, expect */ + +/* see http://webdriver.io/api */ + +'use strict'; + +var runner = require('./runner'); + +var signin = { + description: 'Click the signin button', + baseSelector: [ + { + type: 'plugin', + value: 'auth2-client', + }, { + type: 'component', + value: 'login-view' + } + ], + tasks: [ + { + selector: [{ + type: 'button', + value: 'signin' + }], + wait: 30000, + action: 'click' + }, { + selector: [ + [{ + type: 'component', + value: 'signin-button' + }, { + type: 'name', + value: 'google' + }]], + wait: 3000 + }, { + selector: [ + [{ + type: 'component', + value: 'signin-button' + }, { + type: 'name', + value: 'globus' + }]], + exists: true + }] +}; + +var signup = { + description: 'Click the new user button and get the signup view', + baseSelector: [{ + type: 'plugin', + value: 'auth2-client', + }], + tasks: [ + { + selector: [{ + type: 'component', + value: 'login-view' + }, { + type: 'button', + value: 'signup' + }], + wait: 30000, + action: 'click' + }, { + selector: [ + { + type: 'widget', + value: 'signup' + }, { + type: 'component', + value: 'signup-view' + }, [{ + type: 'component', + value: 'signin-button' + }, { + type: 'name', + value: 'google' + }]], + wait: 3000 + }, { + selector: [ + { + type: 'widget', + value: 'signup' + }, { + type: 'component', + value: 'signup-view' + }, [{ + type: 'component', + value: 'signin-button' + }, { + type: 'name', + value: 'globus' + }]], + exists: true + }] +}; + +var allTests = [ + { + description: 'Signin spec', + specs: [signin] + }, + { + description: 'Signup spec', + specs: [signup] + } +]; + +runner.runTests(allTests); diff --git a/test/wdio.conf.local.js b/testing/testx/wdio.conf.local.js similarity index 94% rename from test/wdio.conf.local.js rename to testing/testx/wdio.conf.local.js index 29c99c9f1..bf2e4c84b 100644 --- a/test/wdio.conf.local.js +++ b/testing/testx/wdio.conf.local.js @@ -10,7 +10,7 @@ exports.config = { // directory is where your package.json resides, so `wdio` will be called from there. // specs: [ - './test/specs/**/*.js' + './test/integration-tests/specs/**/*.js' ], // Patterns to exclude. exclude: [ @@ -45,25 +45,31 @@ exports.config = { // 'phantomjs.binary.path': '/Volumes/KBaseWork/Work/sprints/auth2-2017/auth2/kbase-ui/node_modules/.bin/phantomjs', // 'phantomjs.cli.args': ['--web-security=false', '--ssl-protocol=any', '--ignore-ssl-errors=true'] // }, - // { - // // maxInstances can get overwritten per capability. So if you have an in-house Selenium - // // grid with only 5 firefox instances available you can make sure that not more than - // // 5 instances get started at a time. - // maxInstances: 5, - // // - // browserName: 'firefox', - // //platform: 'macOS 10.12', - // //version: 'latest', - // acceptInsecureCerts: true - // }, - // Chrome { + // maxInstances can get overwritten per capability. So if you have an in-house Selenium + // grid with only 5 firefox instances available you can make sure that not more than + // 5 instances get started at a time. maxInstances: 5, - browserName: 'chrome', + // + browserName: 'firefox', //platform: 'macOS 10.12', //version: 'latest', + 'moz:firefoxOptions': { + args: ['-headless'] + }, acceptInsecureCerts: true - } + }, + // Chrome + // { + // maxInstances: 5, + // browserName: 'chrome', + // //platform: 'macOS 10.12', + // //version: 'latest', + // // chromeOptions: { + // // args: ['--headless'] + // // }, + // acceptInsecureCerts: true + // } ], // // =================== @@ -265,4 +271,4 @@ exports.config = { */ // onComplete: function(exitCode) { // } -} +}; diff --git a/tools/dev/package.json b/tools/dev/package.json deleted file mode 100644 index 305878f55..000000000 --- a/tools/dev/package.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "name": "kbase-ui-dev-tools", - "description": "Developer tools for kbase ui", - "version": "0.1.0", - "repository": { - "type": "git", - "url": "" - }, - "dependencies": { - "bluebird": "^3.1.0", - "js-yaml": "^3.4.3" - }, - "license": "SEE LICENSE IN ../LICENSE" - -} \ No newline at end of file diff --git a/tools/devtools.sh b/tools/devtools.sh new file mode 100644 index 000000000..cf4da0ed0 --- /dev/null +++ b/tools/devtools.sh @@ -0,0 +1,3 @@ +export PATH=`pwd`/node_modules/.bin:$PATH +alias buildui="make build config=dev;make image build=build" +alias runui="make run-image env=dev" \ No newline at end of file diff --git a/tools/link.sh b/tools/link.sh deleted file mode 100644 index 19c63df8d..000000000 --- a/tools/link.sh +++ /dev/null @@ -1,171 +0,0 @@ -# use within the vm in the directory /vagrant/kbase-ui/dev/tools -# see the docs - -# Set the root of the project. -# This is the directory which contains kbase-ui and any plugin repo directories. -export DEVDIR=`pwd`/../../.. -echo "Dev dir is $DEVDIR" - -# -# linkLib links a kbase support library into the source tree. -# -# usage: linkLib package module sourcePath -# -# where -# module_root is the module root directory. -# module is the canonical name for the library, and is prefixed by kbase- in global -# naming contexts such as repo names. -# sourcePath is the path to the source distribution within the repo. This path will be -# spliced into the kbase-ui build tree in the modules/$package directory. -# -function linkLib() { - local module_root=$1 - local libname=$2 - local source_path=$3 - - echo "Linking library $libname" - # echo $DEVDIR/kbase-$libname/$source_path - # echo ../../build/build/client/modules/$module_root - - rm -rf ../../build/build/client/modules/$module_root - ln -s $DEVDIR/kbase-$libname/$source_path ../../build/build/client/modules/$module_root -} - -# -# linkPlugin links an external plugin into the build tree. -# -# It enables the basic workflow for working on external plugins. -# -# usage: linkPlugin plugin -# -function linkPlugin() { - local plugin=$1 - - echo "Linking external plugin $plugin" - - rm -rf ../../build/build/client/modules/plugins/$plugin - ln -s $DEVDIR/kbase-ui-plugin-$plugin/src/plugin ../../build/build/client/modules/plugins/$plugin -} - -# -# linkInternalPlugin links a kbase-ui built-in plugin into the build. -# -# It is useful for making changes to internal plugins without rebuilding -# the source tree each time. -# -# usage: linkInternalPlugin plugin -# -function linkInternalPlugin() { - local plugin=$1 - - echo "Linking internal plugin $plugin" - - rm -rf ../../build/build/client/modules/plugins/$plugin - ln -s $DEVDIR/kbase-ui/src/client/modules/plugins/$plugin ../../build/build/client/modules/plugins/$plugin -} - -# -# linkRepoFile links an arbitrary directory or file from the -# dev directory root into the build directory -# -# It is useful for linking an arbitrary repo or bit of a repo -# into the build directory for extreme hacking. For instance, -# debugging a third party dependency. -# -# usage: linkRepoFile from to -# -function linkRepoFile() { - - # from is relative to the dev dir, the container for all repos - local from=$1 - # to is relative to this directory, but could be DEVDIR/kbase-ui - local to=$2 - - local source=$DEVDIR/$from - local dest=../../build/build/client/$to - - echo "Linking repo file $from" - - rm -rf $dest - ln -s $source $dest -} - -# -# linkSrcFile links an arbitrary kbase-ui source directory or file -# into the build directory. -# -# It is useful for hacking on bits of the ui without going through the -# build process each time. -# -# usage: linkSrcFile from to -# -function linkSrcFile() { - # from is relative to the dev dir, the container for all repos - local from=$1 - # to is relative to this directory, but could be DEVDIR/kbase-ui - local to=$2 - - local source=$DEVDIR/kbase-ui/src/client/$from - local dest=../../build/build/client/$to - - echo "Linking source file $from" - - rm -rf $dest - ln -s $source $dest -} - -# -# MODULE LIBS -# - -# Support for linking kbase libraries into the a kbase-ui build. - -# linkLib "package" "module" -# where package is the directory within modules/kb owned by this library -# module is the repo directory following the kbase- namespacing prefix - -# usage: -# linkLib module_root libname source_path -# see the function definition above for details - -# examples: -# linkLib2 'kb_service' 'service-clients-js' 'dist/kb_service' -# linkLib2 'kb_common' 'common-js' 'dist/kb_common' - -# -# EXTERNAL PLUGINS -# - -# Support for linking kbase-ui plugins into the kbase-ui build - -# An external plugin is one that is installed into kbase-ui from bower -# during the build process. None of the code, other than configuration -# for the bower package name and version, resides in kbase-ui. -# -# To link it into the build, you want to grab the src/plugin directory -# within it and point it to build/build/client/modules/plugins/PLUGIN -# The build process performs no special transformations on the plugin -# - -# usage: -# linkPlugin plugin_name - -# examples: -# linkPlugin "dataview" -# linkPlugin "data-landing-pages" -# linkPlugin "datawidgets" - -# -# INTERNAL PLUGINS -# - -# This is helpful if you are working on plugins built in to kbase-ui. What you are doing is -# linking the source plugin directory to the corresponding build plugin directory. -# - -# usage: -# linkInternalPlugin plugin_name - -# examples: -# linkInternalPlugin about -# linkInternalPlugin catalog \ No newline at end of file diff --git a/tools/private-bower/package.json b/tools/private-bower/package.json deleted file mode 100644 index 53d866edb..000000000 --- a/tools/private-bower/package.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "kbase-ui-private-bower", - "version": "0.0.1", - "keywords": [ - "util", - "functional", - "server", - "client", - "browser" - ], - "author": "KBase", - "license": "SEE LICENSE IN ../LICENSE.md", - "contributors": [ - "eapearson", - "briehl" - ], - "dependencies": {}, - "devDependencies": { - - "private-bower": "^1.1.7" - } -} diff --git a/tools/process_config.js b/tools/process_config.js deleted file mode 100644 index 39746afb4..000000000 --- a/tools/process_config.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * process_config.js - * ----------------- - * A tiny node script that will read in a set of configured service - * endpoints and inject them into the lodash-templatized config file. - * - * This also concatenates that template file with a more kbase-ui - * focused non-templated settings file. - * - * Usage: from root of this repo - * > node tools/process_config.js [ini file, default = deploy.cfg] - * - * This script expects that the given config file is in INI format, - * and contains a [kbase-ui] stanza. - * @author Bill Riehl wjriehl@lbl.gov - */ - -'use strict'; -var iniParser = require('node-ini'); -var fs = require('fs'); -var _ = require('lodash'); - -/* Getting service config - nested options. - * default = local deploy.cfg - * if one is passed in an argument, use that and move on - * if environment var KB_DEPLOYMENT_CONFIG is available (and nothing - * passed in), use that. - */ -var deployCfgFile = 'deploy.cfg'; -if (process.argv.length > 2) { - deployCfgFile = process.argv[2]; -} -else if (process.env.KB_DEPLOYMENT_CONFIG) { - deployCfgFile = process.env.KB_DEPLOYMENT_CONFIG; -} -console.log('Building configuration from ' + deployCfgFile); -var serviceTemplateFile = 'config/service-config-template.yml'; -var settingsCfg = 'config/settings.yml'; -var outFile = 'build/client/config.yml'; - -/* Admittedly, this is a little sloppy. It loads the - * ini file synchronously with the node-ini package, - * then loads the template asynchronously. Next, it uses - * lodash to populate the template, then loads the settings - * file (also async - I hate nesting these, but it's a bit - * more compact in this case), concats them, and writes them - * out to the right place. - */ -var deployCfg = iniParser.parseSync(deployCfgFile); -fs.readFile(serviceTemplateFile, 'utf8', function(err, serviceTemplate) { - if (err) return showError('Error reading service template'); - - var compiled = _.template(serviceTemplate); - var services = compiled(deployCfg['kbase-ui']); - - fs.readFile(settingsCfg, 'utf8', function(err, settings) { - if (err) return showError('Error reading UI settings file', err); - - fs.writeFile(outFile, services + '\n\n' + settings, function(err) { - if (err) return showError('Error writing compiled configuration', err); - }); - }); -}); -console.log('Done! Configuration written to ' + outFile); - -/** - * Writes an error to the console in a somewhat pretty way. - * Takes a (supposedly) human-readable string as well as the - * usual nodejs error object. - */ -function showError(str, err) { - console.log("An error occurred while processing configuration:\n"); - if (str) console.log(str); - if (err) console.log(err); -} \ No newline at end of file diff --git a/tools/proxier/conf/README.md b/tools/proxier/conf/README.md new file mode 100644 index 000000000..68fd179fb --- /dev/null +++ b/tools/proxier/conf/README.md @@ -0,0 +1,3 @@ +# Configuration files + +Configuration files for the Proxier development image. diff --git a/deployment/conf/appdev.env b/tools/proxier/conf/appdev.env similarity index 51% rename from deployment/conf/appdev.env rename to tools/proxier/conf/appdev.env index 8cc6769d7..ffe933e0d 100644 --- a/deployment/conf/appdev.env +++ b/tools/proxier/conf/appdev.env @@ -1,21 +1,6 @@ -# KBase UI CI Config +# Proxier Container -deploy_environment=appdev deploy_hostname=appdev.kbase.us -deploy_icon=wrench -deploy_name=App Dev - -ui_backupCookie_domain= -ui_backupCookie_enabled=false - -ui_services_analytics_google_hostname=appdev.kbase.us -ui_services_analytics_google_code=UA-74533556-1 -allow=[] - -services_narrative_url=https://appdev.kbase.us - - -# Proxier Container # disabled #deploy_ui_hostname= diff --git a/tools/proxier/conf/ci.env b/tools/proxier/conf/ci.env new file mode 100644 index 000000000..4937684b8 --- /dev/null +++ b/tools/proxier/conf/ci.env @@ -0,0 +1,15 @@ +# Proxier Container + +deploy_hostname=ci.kbase.us + +# disabled +#deploy_ui_hostname= + +# the ip or host of the kbase-ui container. This is required +# may need to override in the run command +kbase_ui_host=kbase-ui-container + +# true if actually deployed +# probably best to just override in the run command +deployed=true + diff --git a/tools/proxier/conf/dev.env b/tools/proxier/conf/dev.env new file mode 100644 index 000000000..4c1d78d75 --- /dev/null +++ b/tools/proxier/conf/dev.env @@ -0,0 +1,14 @@ +# Proxier Container + +deploy_hostname=ci.kbase.us + +# disabled +#deploy_ui_hostname= + +# the ip or host of the kbase-ui container. This is required +# may need to override in the run command +kbase_ui_host=kbase-ui-container + +# true if actually deployed +# probably best to just override in the run command +deployed=false diff --git a/deployment/conf/next.env b/tools/proxier/conf/next.env similarity index 50% rename from deployment/conf/next.env rename to tools/proxier/conf/next.env index 17bbc1bf6..0c2163d39 100644 --- a/deployment/conf/next.env +++ b/tools/proxier/conf/next.env @@ -1,22 +1,8 @@ -# KBase UI CI Config +# Proxier Container -deploy_environment=next deploy_hostname=next.kbase.us -deploy_icon=bullseye -deploy_name=Next - -ui_backupCookie_domain= -ui_backupCookie_enabled=false - -ui_services_analytics_google_hostname=next.kbase.us -ui_services_analytics_google_code=UA-74530365-1 -allow=[] - -services_narrative_url=https://next.kbase.us -# Proxier Container - -# disable +# disabled #deploy_ui_hostname= # the ip or host of the kbase-ui container. This is required @@ -26,3 +12,4 @@ kbase_ui_host=kbase-ui-container # true if actually deployed # probably best to just override in the run command deployed=true + diff --git a/tools/proxier/conf/prod.env b/tools/proxier/conf/prod.env new file mode 100644 index 000000000..9fa21b887 --- /dev/null +++ b/tools/proxier/conf/prod.env @@ -0,0 +1,14 @@ +# Proxier Container + +deploy_hostname=kbase.us + +# disabled +deploy_ui_hostname=narrative.kbase.us + +# the ip or host of the kbase-ui container. This is required +# may need to override in the run command +kbase_ui_host=kbase-ui-container + +# true if actually deployed +# probably best to just override in the run command +deployed=true diff --git a/deployment/proxier/docker/context/Dockerfile b/tools/proxier/docker/context/Dockerfile similarity index 100% rename from deployment/proxier/docker/context/Dockerfile rename to tools/proxier/docker/context/Dockerfile diff --git a/deployment/dev/docker/context/README.md b/tools/proxier/docker/context/README.md similarity index 100% rename from deployment/dev/docker/context/README.md rename to tools/proxier/docker/context/README.md diff --git a/deployment/proxier/docker/src/conf/cors.conf.tmpl b/tools/proxier/docker/context/contents/conf/cors.conf.tmpl similarity index 100% rename from deployment/proxier/docker/src/conf/cors.conf.tmpl rename to tools/proxier/docker/context/contents/conf/cors.conf.tmpl diff --git a/deployment/proxier/docker/src/conf/nginx.conf.tmpl b/tools/proxier/docker/context/contents/conf/nginx.conf.tmpl similarity index 94% rename from deployment/proxier/docker/src/conf/nginx.conf.tmpl rename to tools/proxier/docker/context/contents/conf/nginx.conf.tmpl index 092e4ba75..3d35b809b 100644 --- a/deployment/proxier/docker/src/conf/nginx.conf.tmpl +++ b/tools/proxier/docker/context/contents/conf/nginx.conf.tmpl @@ -121,16 +121,16 @@ http { # Needed for running narratives location /narrative { - proxy_pass https://{{ .Env.deploy_hostname }}/narrative; + # proxy_pass https://{{ .Env.deploy_hostname }}/narrative; # Uncomment the following lines for a narrative deployed # within the proxy's vm. - #proxy_pass http://localhost:8888/narrative; + proxy_pass http://narrative:8888/narrative; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_http_version 1.1; - proxy_set_header Origin http://localhost; - proxy_set_header Host localhost; + proxy_set_header Origin https://{{ .Env.kbase_ui_host }}; + proxy_set_header Host {{ .Env.kbase_ui_host }}; } diff --git a/tools/proxier/docker/src/conf/cors.conf.tmpl b/tools/proxier/docker/src/conf/cors.conf.tmpl new file mode 100644 index 000000000..d180d10b3 --- /dev/null +++ b/tools/proxier/docker/src/conf/cors.conf.tmpl @@ -0,0 +1,27 @@ +{{ if .Env.deploy_ui_hostname }} + + proxy_hide_header 'Access-Control-Max-Age,'; + proxy_hide_header 'Access-Control-Allow-Headers'; + proxy_hide_header 'Access-Control-Allow-Methods'; + proxy_hide_header 'Access-Control-Allow-Origin'; + proxy_hide_header 'Access-Control-Allow-Credentials'; + + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS'; + add_header 'Access-Control-Allow-Origin' 'https://{{ .Env.deploy_ui_hostname }}'; + add_header 'Access-Control-Allow-Credentials' 'true'; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0 always; + return 204; + } + + add_header 'Access-Control-Allow-Origin' 'https://{{ .Env.deploy_ui_hostname }}' always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization,Cookie' always; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + +{{ end }} + \ No newline at end of file diff --git a/tools/proxier/docker/src/conf/nginx.conf.tmpl b/tools/proxier/docker/src/conf/nginx.conf.tmpl new file mode 100644 index 000000000..3d35b809b --- /dev/null +++ b/tools/proxier/docker/src/conf/nginx.conf.tmpl @@ -0,0 +1,139 @@ +daemon off; +error_log /dev/stdout info; +worker_processes auto; +pid /var/run/nginx.pid; + +events { + worker_connections 256; + # multi_accept on; +} + +http { + sendfile off; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + proxy_headers_hash_bucket_size 256; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + ## + # Logging Settings + ## + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + # + # A minimal proxying configuration for running kbase-ui through a secure proxy + # against ci. + # + # It is designed to operate inside a VM which naturally routes ci.kbase.us to its + # real location, while the host has ci mapped to the vm via /etc/hosts. + # + + # Route insecure requests to secure. + server { + listen 80 default_server; + listen [::]:80 default_server; + server_name {{ default .Env.deploy_ui_hostname .Env.deploy_hostname }}; + return 301 https:/{{ default .Env.deploy_ui_hostname .Env.deploy_hostname }}$request_uri; + + } + + {{ if .Env.deploy_ui_hostname }} + + server { + listen 443 ssl; + server_name {{ .Env.deploy_ui_hostname }}; + ssl_certificate /kb/deployment/test.crt; + ssl_certificate_key /kb/deployment/test.key; + + location / { + proxy_pass http://{{ .Env.kbase_ui_host }}/; + } + + # Needed for running narratives + location /narrative { + include /etc/nginx/cors.conf; + proxy_pass https://{{ .Env.deploy_ui_hostname }}/narrative; + # Uncomment the following lines for a narrative deployed + # within the proxy's vm. + #proxy_pass http://localhost:8888/narrative; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + proxy_set_header Origin https://{{ .Env.deploy_ui_hostname }}; + proxy_set_header Host {{ .Env.deploy_ui_hostname }}; + } + } + + + {{ end }} + + server { + listen 443 ssl; + server_name {{ .Env.deploy_hostname }}; + ssl_certificate /kb/deployment/test.crt; + ssl_certificate_key /kb/deployment/test.key; + + # Proxy all service calls, including auth2, to the real CI + location /services { + # The cookie path rewriting is just for auth2 + {{ if .Env.deploy_ui_hostname }} + include /etc/nginx/cors.conf; + {{ end }} + proxy_cookie_path /login /services/auth/login; + proxy_cookie_path /link /services/auth/link; + proxy_pass https://{{ .Env.deploy_hostname }}/services; + client_max_body_size 300M; + } + + # TODO: need to pin the docker container to a host name, + # otherwise you need to inspect the container to find out the ip + # address to use below + # location /services/jgi_gateway/rpc { + # {{ if .Env.deploy_ui_hostname }} + # include /etc/nginx/cors.conf; + # {{ end }} + # include /etc/nginx/cors.conf; + # proxy_pass http://jgi_gateway:5000; + # } + + # Needed for dynamic service calls + location /dynserv { + {{ if .Env.deploy_ui_hostname }} + include /etc/nginx/cors.conf; + {{ end }} + proxy_pass https://{{ .Env.deploy_hostname }}/dynserv; + } + + # Proxy all non-services to the local kbase-ui running in the vm + + {{ if not .Env.deploy_ui_hostname }} + + location / { + proxy_pass http://{{ .Env.kbase_ui_host }}/; + } + + # Needed for running narratives + location /narrative { + # proxy_pass https://{{ .Env.deploy_hostname }}/narrative; + # Uncomment the following lines for a narrative deployed + # within the proxy's vm. + proxy_pass http://narrative:8888/narrative; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_http_version 1.1; + proxy_set_header Origin https://{{ .Env.kbase_ui_host }}; + proxy_set_header Host {{ .Env.kbase_ui_host }}; + } + + + {{ end }} + } +} diff --git a/deployment/proxier/tools/build_docker_image.sh b/tools/proxier/tools/build_docker_image.sh similarity index 82% rename from deployment/proxier/tools/build_docker_image.sh rename to tools/proxier/tools/build_docker_image.sh index 8f154c423..9f1d03b11 100644 --- a/deployment/proxier/tools/build_docker_image.sh +++ b/tools/proxier/tools/build_docker_image.sh @@ -1,8 +1,5 @@ #!/bin/bash -x -# Much much longer than the original. Just wanted to get a handle -# on explicit error capture and handling. - function get_branch() { local branch branch=$(git symbolic-ref --short HEAD 2>&1) @@ -54,8 +51,6 @@ function build_image() { local here=`pwd` echo "Running docker build in context ${here}/docker/context" - # TODO: don't know why can't run this in a subprocess - # NOTE: the dev build does not use the build date -- we don't want to bust the cache docker build --build-arg VCS_REF=$commit \ --build-arg BRANCH=$branch \ --build-arg TAG=$tag \ @@ -66,7 +61,7 @@ function build_image() { if (( $err > 0 )); then echo "Error running docker build: $err" else - echo "Successfully build docker image. You may invoke it " + echo "Successfully built docker image. You may invoke it " echo "with tag \"kbase/kbase-ui-proxier:${tag}\"" fi } diff --git a/deployment/proxier/tools/run-image.sh b/tools/proxier/tools/run-image.sh similarity index 61% rename from deployment/proxier/tools/run-image.sh rename to tools/proxier/tools/run-image.sh index 8e735963a..a6e35e79d 100644 --- a/deployment/proxier/tools/run-image.sh +++ b/tools/proxier/tools/run-image.sh @@ -10,23 +10,27 @@ if [ -z "$environment" ]; then exit 1 fi -if [ ! -e "deployment/conf/${environment}.env" ]; then - echo "ERROR: environment (arg 1) does not resolve to a config file in deployment/conf/${environment}.env" +if [ ! -e "tools/proxier/conf/${environment}.env" ]; then + echo "ERROR: environment (arg 1) does not resolve to a config file in tools/proxier/conf/${environment}.env" usage exit 1 fi root=$(git rev-parse --show-toplevel) -config_mount="${root}/deployment/conf" +config_mount="${root}/tools/proxier/conf" +image="kbase/kbase-ui-proxier:dev" echo "CONFIG MOUNT: ${config_mount}" echo "ENVIRONMENT : ${environment}" -echo "READING OPTIONS" +echo "stdout sent to proxier.stdout, stderr sent to proxier.stderr" +echo "Running proxier image ${image}" +echo ":)" docker run \ -p 80:80 -p 443:443 --dns=8.8.8.8 --rm \ --env-file=${config_mount}/${environment}.env \ --network=kbase-dev \ --name=proxier \ - kbase/kbase-ui-proxier:dev + ${image} \ + > temp/files/proxier.stdout diff --git a/tools/run-image.sh b/tools/run-image.sh new file mode 100644 index 000000000..ea7f9ab00 --- /dev/null +++ b/tools/run-image.sh @@ -0,0 +1,111 @@ + +#!/bin/bash +# +# Runs the kbase-ui image which has already been built for the current branch. +# +# This is a develop-time tool. +# + +function usage() { + echo 'Usage: run-image.sh env [-p external-plugin] [-i internal-plugin] [-s kbase-ui-service] [-l lib-module-dir:lib-name:source-path]' +} + +environment=$1 + +if [ -z "$environment" ]; then + echo "ERROR: argument 1, 'environment', not provided" + usage + exit 1 +fi + +root=$(git rev-parse --show-toplevel) +config_mount="${root}/config/deploy" +branch=$(git symbolic-ref --short HEAD 2>&1) + +if [ ! -e "${config_mount}/${environment}.env" ]; then + echo "ERROR: environment (arg 1) does not resolve to a config file in ${config_mount}/${environment}.env" + usage + exit 1 +fi + +echo "CONFIG MOUNT: ${config_mount}" +echo "ENVIRONMENT : ${environment}" +echo "BRANCH : ${branch}" + +echo "READING OPTIONS" + +# Initialize our own variables: +mounts="" + + +# from: https://stackoverflow.com/questions/192249/how-do-i-parse-command-line-arguments-in-bash +POSITIONAL=() +while [[ $# -gt 0 ]] +do +key="$1" + + +case $key in + ### External plugins, located at the same directory level as the kbase-ui repo + -p|--plugin) + plugin="$2" + echo "Using external plugin: ${plugin}" + mounts="$mounts --mount type=bind,src=${root}/../kbase-ui-plugin-${plugin}/src/plugin,dst=/kb/deployment/services/kbase-ui/modules/plugins/${plugin}" + shift # past argument + shift # past value + ;; + ### Internal plugins, located within the repo in src/plugins + -i|--internal) + plugin="$2" + echo "Using internal plugin: ${plugin}" + mounts="$mounts --mount type=bind,src=${root}/src/plugins/${plugin},dst=/kb/deployment/services/kbase-ui/modules/plugins/${plugin}" + shift # past argument + shift # past value + ;; + ### External kbase libraries, located in a sister directory to kbase-ui + -l|--lib) + lib="$2" + # local l + read libName libPath libModule <<<$(IFS=':';echo $lib) + # IFS=':';l=($lib);unset IFS + # local libModule="${l[0]}" + # local libName="${l[1]}" + # local libPath="${l[2]}" + # e.g. kb_common:common-js:dist/kb_common + echo "Using library repo: name = $libName, path = $libPath, module = $libModule" + mounts="$mounts --mount type=bind,src=${root}/../kbase-${libName}/${libPath},dst=/kb/deployment/services/kbase-ui/modules/${libModule}" + shift + shift + ;; + ### Internal ui services, located within modules/app/services + -s|--service) + service="$2" + echo "Using internal services: ${service}" + mounts="$mounts --mount type=bind,src=${root}/src/client/modules/app/services/${service}.js,dst=/kb/deployment/services/kbase-ui/modules/app/services/${service}.js" + shift # past argument + shift # past value + ;; + *) # unknown option + POSITIONAL+=("$1") # save it in an array for later + shift # past argument + ;; +esac +done +set -- "${POSITIONAL[@]}" # restore positional parameters + +echo "MOUNTS: $mounts" + +image_tag="${branch}" + +echo "stdout sent to kbase-ui.stoud, stderr sent to kbase-ui.stderr" +echo "Running kbase-ui image kbase/kbase-ui:${image_tag}" +echo ":)" + +docker run \ + --rm \ + --env-file ${config_mount}/${environment}.env \ + --name=kbase-ui-container \ + --network=kbase-dev \ + $mounts \ + kbase/kbase-ui:${image_tag} \ + > temp/files/kbase-ui.stdout \ No newline at end of file