OpenWhisk Knative Runtime - NodeJS (#202)

* prototyping openwhisk actions using knative

* prototyping openwhisk actions using knative

* adding bash util to strip debug statements from source files
diff --git a/knative-build/README.md b/knative-build/README.md
new file mode 100644
index 0000000..f8bc82e
--- /dev/null
+++ b/knative-build/README.md
@@ -0,0 +1,277 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+# OpenWhisk Runtimes for Knative
+
+[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
+
+This repository contains Knative Build Templates along with modified versions of their respective OpenWhisk Action Runtimes that can be used to Build and Serve Knative compatible applications (i.e., OpenWhisk Actions) on Kubernetes.
+
+## Pre-requisites
+
+The general pre-requisites are as follows:
+- [x] Kubernetes v1.13.0
+- [x] kubectl
+- [x] Knative v0.3.0
+
+Specifically, for developement and testing on Mac OS, the following components and versions were used:
+
+- [x] [Docker Desktop for Mac Docker Community Edition 2.0.1.0 2019-01-11](https://docs.docker.com/docker-for-mac/edge-release-notes/) which includes:
+    - Docker 18.09.1
+    - Kubernetes 1.13.0
+- [x] [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) (```brew install kubernetes-cli```)
+- [x] [Knative 0.3.0](https://github.com/knative/serving/releases/tag/v0.3.0) (which will install and configure istio-1.0.1 compatible resources)
+
+### Docker Desktop Minimum Configuration
+
+Under the Docker Desktop menu select "**Preferences**"->"**Advanced**" and set these values to at least these minimums:
+- [x] **CPUs: 4**
+- [x] **Memory: 8.0 GiB**
+- [x] **Swap: 1.0 GiB**
+
+Under the Docker Desktop **Kubernetes** tab, please assure that Kubernetes is **enabled** and running.
+
+### Verify Kubernetes Installation
+
+Use the following command to verify the Kubernetes **Server Version** indicates version 1.13:
+
+```bash
+$ kubectl version
+
+Client Version: version.Info{Major:"1", Minor:"13", GitVersion:"v1.13.2", GitCommit:"cff46ab41ff0bb44d8584413b598ad8360ec1def", GitTreeState:"clean", BuildDate:"2019-01-13T23:15:13Z", GoVersion:"go1.11.4", Compiler:"gc", Platform:"darwin/amd64"}
+Server Version: version.Info{Major:"1", Minor:"13", GitVersion:"v1.13.0", GitCommit:"ddf47ac13c1a9483ea035a79cd7c10005ff21a6d", GitTreeState:"clean", BuildDate:"2018-12-03T20:56:12Z", GoVersion:"go1.11.2", Compiler:"gc", Platform:"linux/amd64"}
+```
+
+The ```Server Version``` is the version for the Kubernetes service; the ```Client Version``` is for the Kubernetes CLI (i.e., ```kubectl```).
+
+#### Verify you have a Kubernetes v1.13.0 node ready
+```
+$ kubectl get nodes
+
+NAME             STATUS    ROLES     AGE       VERSION
+docker-desktop   Ready     master    4d22h     v1.13.0
+```
+
+#### Verify all Kubernetes pods are running
+
+```
+$ kubectl get pods --namespace kube-system
+```
+<details>
+    <summary>Sample output</summary>
+    
+```    
+NAME                                     READY     STATUS    RESTARTS   AGE
+coredns-86c58d9df4-ms8qs                 1/1       Running   0          4d22h
+coredns-86c58d9df4-x29vt                 1/1       Running   0          4d22h
+etcd-docker-desktop                      1/1       Running   1          4d22h
+kube-apiserver-docker-desktop            1/1       Running   1          4d22h
+kube-controller-manager-docker-desktop   1/1       Running   3          4d22h
+kube-proxy-mltsm                         1/1       Running   0          4d22h
+kube-scheduler-docker-desktop            1/1       Running   3          4d22h
+```
+</details>
+
+## Knative Install on a Kubernetes Cluster
+
+The following instructions where used to install Knative: [Knative Install on a Kubernetes Cluster](https://github.com/knative/docs/blob/master/install/Knative-with-any-k8s.md)
+
+These instructions take you through the installation of
+- [x] [Istio v1.0.1](https://github.com/istio/istio/releases) using resources specifically configured for use with Knative Serving
+- [x] [Knative v0.3.0](https://github.com/knative/serving/releases/tag/v0.3.0)
+
+## Verify Knative installation
+
+#### Verify Istio pods are running
+
+```bash
+$ kubectl get pods --namespace istio-system
+```
+<details>
+    <summary>Sample output</summary>
+    
+```    
+NAME                                       READY     STATUS      RESTARTS   AGE
+cluster-local-gateway-547467ccf6-p8n72     1/1       Running     1          4d21h
+istio-citadel-7d64db8bcf-m7gsj             1/1       Running     0          4d21h
+istio-cleanup-secrets-8lzj4                0/1       Completed   0          4d21h
+istio-egressgateway-6ddf4c8bd6-2dxhc       1/1       Running     1          4d21h
+istio-galley-7dd996474-pdd6h               1/1       Running     1          4d21h
+istio-ingressgateway-84b89d647f-cxrwx      1/1       Running     1          4d21h
+istio-pilot-86bb4fcbbd-5ns5q               2/2       Running     0          4d21h
+istio-pilot-86bb4fcbbd-vd2xr               2/2       Running     0          4d21h
+istio-pilot-86bb4fcbbd-zstrw               2/2       Running     0          4d21h
+istio-policy-5c4d9ff96b-559db              2/2       Running     1          4d21h
+istio-sidecar-injector-6977b5cf5b-94hj5    1/1       Running     0          4d21h
+istio-statsd-prom-bridge-b44b96d7b-kzkzc   1/1       Running     0          4d21h
+istio-telemetry-7676df547f-jp952           2/2       Running     1          4d21h
+knative-ingressgateway-75644679c7-c2kxj    1/1       Running     1          4d21h
+```
+</details>
+
+#### Verify your default namespace uses Istio for all services 
+
+Check the `default` namespace has the label **istio-injection** and it is set to **enabled**:
+
+```bash
+$ kubectl get namespace default -o yaml
+```
+   
+Example output:
+```    
+apiVersion: v1
+kind: Namespace
+metadata:
+  creationTimestamp: 2019-01-29T19:30:44Z
+  labels:
+    istio-injection: enabled
+  name: default
+  resourceVersion: "3928"
+  selfLink: /api/v1/namespaces/default
+  uid: 5ecb1bb0-23fc-11e9-bed6-025000000001
+spec:
+  finalizers:
+  - kubernetes
+status:
+  phase: Active
+```
+
+
+**Note**: If you do not see the **istio-injection** label, verify you issued the 'kubectl' command to set this label to the default namespace. See [Troubleshooting](#troubleshooting) section for more information.
+
+# Building and Serving OpenWhisk Runtime Build Templates
+
+All OpenWhisk Runtime Build Templates require a valid Kubernetes **Service Account** with access to a Kubernetes **Secret** that containst base64 encoded versions of your Docker Hub username and password.  This credential will be used as part of the Knative Build process to "push" your Knative application image containing your OpenWhisk Action to Docker Hub.  
+
+## Clone this repository
+
+```bash
+$ git clone https://github.com/mrutkows/openwhisk-knative-build.git
+```
+
+## Register Secrets for Docker Hub
+
+Use the following commands to generate base64 encoded values of your Docker Hub **username** and **password** required to register a new secret in Kubernetes.
+
+```
+$ export DOCKERHUB_USERNAME_PLAIN_TEXT=<your docker hub username>
+$ export DOCKERHUB_PASSWORD_PLAIN_TEXT=<your docker hub password>
+
+$ export DOCKERHUB_USERNAME_BASE64_ENCODED=`echo -n "${DOCKERHUB_USERNAME_PLAIN_TEXT}" | base64 -b 0`
+# make sure that DOCKERHUB_USERNAME_BASE64_ENCODEDE is set to something similar to abcqWeRTy2gZApB==
+
+$ export DOCKERHUB_PASSWORD_BASE64_ENCODED=`echo -n "${DOCKERHUB_PASSWORD_PLAIN_TEXT}" | base64 -b 0`
+# make sure that DOCKERHUB_PASSWORD_BASE64_ENCODED is set to something similar to t80szzToPshrpDr3sdMn==
+```
+
+On your local system, copy the file [docker-secret.yaml.tmpl](docker-secret.yaml.tmpl) to `docker-secrets.yaml` and replace the **username** and **password** values with the base64 encoded versions of your Docker Hub username and password using following `sed` command:
+
+```
+sed -e 's/${DOCKERHUB_USERNAME_BASE64_ENCODED}/'"$DOCKERHUB_USERNAME_BASE64_ENCODED"'/' -e 's/${DOCKERHUB_PASSWORD_BASE64_ENCODED}/'"$DOCKERHUB_PASSWORD_BASE64_ENCODED"'/' docker-secret.yaml.tmpl > docker-secret.yaml
+```
+
+Apply the Secret resource manifest for Docker Hub:
+
+```bash
+$ kubectl apply -f docker-secret.yaml
+secret/dockerhub-user-pass created
+```
+
+Verify Secret exists:
+
+```bash
+$ kubectl get secret
+NAME                    TYPE                                  DATA      AGE
+dockerhub-user-pass     kubernetes.io/basic-auth              2         21s
+```
+
+## Create Service Account for our Knative Builds
+
+Knative requires a valid ServiceAccount resource that will be used when building and serving OpenWhisk Serverless Actions using the OpenWhisk runtimes.  For convenience, all Knative builds for all runtimes are configured to use the same ServiceAccount as defined in [service-account.yaml](service-account.yaml).
+
+```bash
+$ kubectl apply -f service-account.yaml
+serviceaccount/openwhisk-runtime-builder created
+```
+
+Verify the Service account has 2 secrets (i.e., username and password):
+
+```
+$ kubectl get serviceaccount/openwhisk-runtime-builder
+NAME                        SECRETS   AGE
+openwhisk-runtime-builder   2         3m46s
+```
+
+## Troubleshooting
+
+### Knative and Istio Install
+
+#### PROBLEM: Kubernetes default namespace does not have "istio-injection: enabled" key-value
+
+If the `default` namespace does not have this value under the `metadata` section, you may have forgotton to issue the following command as part of the Knative setup:
+
+```bash
+$ kubectl label namespace default istio-injection=enabled
+namespace "default" labeled
+```
+
+#### PROBLEM: Kubernetes and Istio resources do not all say "created" on "apply"
+
+1. Verify that you have configured Docker Desktop to have the required CPU and Memory values recommended above.
+2. Verify that all resources installed by applying either tha Knative or Istio YAML files show **"created"** during the installation.
+- If any of your resources were NOT **created**, then we recommend uninstalling Knative and Istio and trying again until you get the **created** result for all resources WITHOUT trying to apply a second time. Below is an example of successful creation of Knative resources:
+
+<p>
+    <details>
+    <summary>Sample output: Successful Knative resources creation</summary>    
+
+```
+clusterrole "knative-build-admin" created
+serviceaccount "build-controller" created
+clusterrolebinding "build-controller-admin" created
+customresourcedefinition "builds.build.knative.dev" created
+customresourcedefinition "buildtemplates.build.knative.dev" created
+customresourcedefinition "clusterbuildtemplates.build.knative.dev" created
+customresourcedefinition "images.caching.internal.knative.dev" created
+service "build-controller" created
+service "build-webhook" created
+image "creds-init" created
+image "git-init" created
+...
+rolebinding "prometheus-system" created
+rolebinding "prometheus-system" created
+rolebinding "prometheus-system" created
+rolebinding "prometheus-system" created
+clusterrolebinding "prometheus-system" created
+service "prometheus-system-np" created
+statefulset "prometheus-system" created
+```
+</details>
+</p>
+
+#### PROBLEM: Knative build fails initializing at `Init:1/4`
+
+Check the GitHub version is set to right branch:
+
+```
+  source:
+    git:
+      url: <repo>
+      revision: <branch>
+```
diff --git a/knative-build/archive/app-deployment-with-knative-0.2.2.md b/knative-build/archive/app-deployment-with-knative-0.2.2.md
new file mode 100644
index 0000000..0a12655
--- /dev/null
+++ b/knative-build/archive/app-deployment-with-knative-0.2.2.md
@@ -0,0 +1,391 @@
+# App Deployment with Knative 0.2.2 on Docker for Desktop
+
+This guide walks us over the set of instructions we followed to get sample applications deployed on Knative.
+
+## Deploy helloworld-go App
+
+Before you begin, please verify:
+
+- [x] Docker Desktop Community Version 2.0.0.2(30215)
+- [x] Docker Desktop for Mac is set to **4 CPU**
+- [x] Docker Desktop for Mac is set to at least **8 GB Memory**
+- [x] Kubernetes Cluster is enabled and running. By default, Docker Desktop for Mac installs Kubernetes `v1.10.11`
+- [x] Kubectl is installed using `brew install kubectl`
+
+### Verify Kubernetes Installation:
+
+```bash
+$ kubectl version
+Client Version: version.Info{Major:"1", Minor:"9", GitVersion:"v1.9.0", GitCommit:"925c127ec6b946659ad0fd596fa959be43f0cc05", GitTreeState:"clean", BuildDate:"2017-12-15T21:07:38Z", GoVersion:"go1.9.2", Compiler:"gc", Platform:"darwin/amd64"}
+Server Version: version.Info{Major:"1", Minor:"10", GitVersion:"v1.10.11", GitCommit:"637c7e288581ee40ab4ca210618a89a555b6e7e9", GitTreeState:"clean", BuildDate:"2018-11-26T14:25:46Z", GoVersion:"go1.9.3", Compiler:"gc", Platform:"linux/amd64"}
+
+$ kubectl get nodes
+NAME                 STATUS    ROLES     AGE       VERSION
+docker-for-desktop   Ready     master    9m        v1.10.11
+
+$ kubectl get pods --all-namespaces
+NAMESPACE     NAME                                         READY     STATUS    RESTARTS   AGE
+docker        compose-74649b4db6-44fz2                     1/1       Running   0          6m
+docker        compose-api-65975979ff-p4tqb                 1/1       Running   0          6m
+kube-system   etcd-docker-for-desktop                      1/1       Running   0          7m
+kube-system   kube-apiserver-docker-for-desktop            1/1       Running   0          6m
+kube-system   kube-controller-manager-docker-for-desktop   1/1       Running   0          7m
+kube-system   kube-dns-86f4d74b45-fg9mz                    3/3       Running   0          8m
+kube-system   kube-proxy-j66t6                             1/1       Running   0          8m
+kube-system   kube-scheduler-docker-for-desktop            1/1       Running   0          7m
+```
+
+### Install Istio:
+
+```bash
+$ curl -L https://github.com/knative/serving/releases/download/v0.2.2/istio.yaml | sed 's/LoadBalancer/NodePort/' | kubectl apply --filename -
+ % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
+                                 Dload  Upload   Total   Spent    Left  Speed
+100   601    0   601    0     0   1747      0 --:--:-- --:--:-- --:--:--  1752
+  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0namespace "istio-system" created
+configmap "istio-galley-configuration" created
+configmap "istio-statsd-prom-bridge" created
+configmap "istio-security-custom-resources" created
+100  106k  100  106k    0     0  44524      0  0:00:02  0:00:02 --:--:-- 68984
+configmap "istio" created
+configmap "istio-sidecar-injector" created
+serviceaccount "istio-galley-service-account" created
+serviceaccount "istio-egressgateway-service-account" created
+serviceaccount "istio-ingressgateway-service-account" created
+serviceaccount "istio-mixer-service-account" created
+serviceaccount "istio-pilot-service-account" created
+serviceaccount "istio-cleanup-secrets-service-account" created
+clusterrole "istio-cleanup-secrets-istio-system" created
+clusterrolebinding "istio-cleanup-secrets-istio-system" created
+job "istio-cleanup-secrets" created
+...
+kubernetesenv "handler" created
+rule "kubeattrgenrulerule" created
+rule "tcpkubeattrgenrulerule" created
+kubernetes "attributes" created
+destinationrule "istio-policy" created
+destinationrule "istio-telemetry" created
+```
+
+Verify that the pods under `istio-system` are all **Running**.
+
+```bash
+$ kubectl get pods --namespace istio-system
+NAME                                        READY     STATUS    RESTARTS   AGE
+istio-citadel-84fb7985bf-249lh              1/1       Running   0          4m
+istio-egressgateway-bd9fb967d-cmlht         1/1       Running   0          4m
+istio-galley-655c4f9ccd-r8gvw               1/1       Running   0          4m
+istio-ingressgateway-688865c5f7-gqdhv       1/1       Running   0          4m
+istio-pilot-6cd69dc444-md6xc                2/2       Running   0          4m
+istio-policy-6b9f4697d-lzv4f                2/2       Running   0          4m
+istio-sidecar-injector-8975849b4-qr77x      1/1       Running   0          4m
+istio-statsd-prom-bridge-7f44bb5ddb-hs47f   1/1       Running   0          4m
+istio-telemetry-6b5579595f-nqn4q            2/2       Running   0          4m
+```
+
+Now, check the `default` namespace for labels:
+
+```bash
+$ kubectl get namespace default -o yaml
+apiVersion: v1
+kind: Namespace
+metadata:
+  creationTimestamp: 2019-01-17T23:32:06Z
+  name: default
+  resourceVersion: "4"
+  selfLink: /api/v1/namespaces/default
+  uid: 19f44753-1ab0-11e9-a8d0-025000000001
+spec:
+  finalizers:
+  - kubernetes
+status:
+  phase: Active
+```
+
+Notice the `default` namespace has no labels under `metadata`. Now, add the label `istio-injection=enabled`:
+
+```bash
+$ kubectl label namespace default istio-injection=enabled
+namespace "default" labeled
+```
+
+Read the `default` namespace metadata again and it should have the label we just added.
+
+```bash
+kubectl get namespace default -o yaml
+apiVersion: v1
+kind: Namespace
+metadata:
+  creationTimestamp: 2019-01-17T23:32:06Z
+  labels:
+    istio-injection: enabled
+  name: default
+  resourceVersion: "1813"
+  selfLink: /api/v1/namespaces/default
+  uid: 19f44753-1ab0-11e9-a8d0-025000000001
+spec:
+  finalizers:
+  - kubernetes
+status:
+  phase: Active
+```
+
+### Install Knative Serving:
+
+```bash
+$ curl -L https://github.com/knative/serving/releases/download/v0.2.2/release-lite.yaml | sed 's/LoadBalancer/NodePort/' | kubectl apply --filename -
+  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
+                                 Dload  Upload   Total   Spent    Left  Speed
+100   608    0   608    0     0   1489      0 --:--:-- --:--:-- --:--:--  1493
+  0  505k    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0namespace "knative-build" created
+clusterrole "knative-build-admin" created
+serviceaccount "build-controller" created
+clusterrolebinding "build-controller-admin" created
+customresourcedefinition "builds.build.knative.dev" created
+customresourcedefinition "buildtemplates.build.knative.dev" created
+customresourcedefinition "clusterbuildtemplates.build.knative.dev" created
+customresourcedefinition "images.caching.internal.knative.dev" created
+service "build-controller" created
+service "build-webhook" created
+image "creds-init" created
+image "git-init" created
+...
+rolebinding "prometheus-system" created
+rolebinding "prometheus-system" created
+rolebinding "prometheus-system" created
+rolebinding "prometheus-system" created
+clusterrolebinding "prometheus-system" created
+service "prometheus-system-np" created
+statefulset "prometheus-system" created
+```
+
+Make sure all the pods under `knative-serving` namespace are **Running**:
+
+```bash
+$ kubectl get pods --namespace knative-serving
+NAME                          READY     STATUS    RESTARTS   AGE
+activator-df78cb6f9-dxktc     2/2       Running   0          48s
+activator-df78cb6f9-fdhmd     2/2       Running   0          48s
+activator-df78cb6f9-zhdnn     2/2       Running   0          48s
+autoscaler-6fccb66768-ksjrk   2/2       Running   0          48s
+controller-56cf5965f5-x8hvk   1/1       Running   0          47s
+webhook-5dcbf967cd-nltpl      1/1       Running   0          47s
+```
+
+### Deploying helloworld-go App
+
+**Before starting to deploy any app, make sure there are no pods running in the `default` namespace where we will be deploying and creating pods for `helloworld-go` application.**
+
+```bash
+$ kubectl get pods
+No resources found.
+```
+
+You can delete all the pods in a single namespace use this command:
+
+```bash
+$ kubectl delete --all pods --namespace=default
+```
+
+Now, create a new file `helloworld-go.yaml` with:
+
+```yaml
+apiVersion: serving.knative.dev/v1alpha1 # Current version of Knative
+kind: Service
+metadata:
+  name: helloworld-go # The name of the app
+  namespace: default # The namespace the app will use
+spec:
+  runLatest:
+    configuration:
+      revisionTemplate:
+        spec:
+          container:
+            image: gcr.io/knative-samples/helloworld-go # The URL to the image of the app
+            env:
+              - name: TARGET # The environment variable printed out by the sample app
+                value: "Go Sample v1"
+```
+
+Deploy `helloworld-go` with:
+
+```bash
+$ kubectl apply -f helloworld-go.yaml
+service "helloworld-go" created
+```
+
+### Interacting with helloworld-go App
+
+```bash
+$ kubectl get routes/helloworld-go --output=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain
+NAME            DOMAIN
+helloworld-go   helloworld-go.default.example.com
+
+$ kubectl get svc knative-ingressgateway --namespace istio-system --output 'jsonpath={.spec.ports[?(@.port==80)].nodePort}'
+32380
+
+$ curl -H "Host: helloworld-go.default.example.com" http://localhost:32380
+Hello Go Sample v1!
+```
+
+Yay! We have `helloworld-go` application deployed and reachable.
+
+Now, lets try and deploy one more application. 
+
+## Source Code in a Git Repository to a Running Application
+
+### Install Kaniko Build Template
+
+```bash
+kubectl apply --filename https://raw.githubusercontent.com/knative/build-templates/master/kaniko/kaniko.yaml
+buildtemplate "kaniko" created
+```
+
+### Register Secrets for Docker Hub
+
+Use the following commands to generate base64 encoded values of Docker Hub username and password required to register a new `secret` in Kubernetes.
+
+```bash
+$ echo -n "your username" | base64 -b 0
+eW91ciB1c2VybmFtZQ==
+
+$ echo -n "your password" | base64 -b 
+0eW91ciBwYXNzd29yZA==
+``` 
+
+Create a new `secret` manifest named `docker-secret.yaml`:
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+  name: basic-user-pass
+  annotations:
+    build.knative.dev/docker-0: https://index.docker.io/v1/
+type: kubernetes.io/basic-auth
+data:
+  # Use 'echo -n "username" | base64 -b 0' to generate this string
+  username:  eW91ciB1c2VybmFtZQ==
+  # Use 'echo -n "password" | base64 -b 0' to generate this string
+  password: 0eW91ciBwYXNzd29yZA==
+```
+
+Apply this manifest:
+
+```bash
+$ kubectl apply -f docker-secret.yaml
+secret "basic-user-pass" created
+```
+
+Verify:
+
+```bash
+$ kubectl get secret
+NAME                  TYPE                                  DATA      AGE
+basic-user-pass       kubernetes.io/basic-auth              2         21s
+...
+```
+
+### Create Service Account
+
+Create a new `Service Account` manifest which is used to link the build process to the docker hub secret. Create `service-account.yaml` with:
+
+```yaml
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: build-bot
+secrets:
+  - name: basic-user-pass
+``` 
+
+Apply `service account` manifest:
+
+```bash
+kubectl apply -f service-account.yaml
+serviceaccount "build-bot" created
+```
+
+Verify:
+
+```bash
+$ kubectl get serviceaccount
+NAME        SECRETS   AGE
+build-bot   2         24s
+default     1         4d
+```
+
+### Deploying the Application
+
+**Note: We are deploying this application under `default` namespace. We had deployed `helloworld-go` under the same `default` namespace. We have noticed a limitation with Kubernetes on Docker which restricts number of pods under one single namespace. Please make sure that there are no pods exist under `default` and if they do, delete them all using `kubectl delete --all pods --namespace=default`**
+
+Create a file named `service.yaml` and copy the following defintion. Make sure to replace `{DOCKER_USERNAME}` with your own Docker Hub username:
+
+```yaml
+apiVersion: serving.knative.dev/v1alpha1
+kind: Service
+metadata:
+  name: app-from-source
+  namespace: default
+spec:
+  runLatest:
+    configuration:
+      build:
+        apiVersion: build.knative.dev/v1alpha1
+        kind: Build
+        spec:
+          serviceAccountName: build-bot
+          source:
+            git:
+              url: https://github.com/mchmarny/simple-app.git
+              revision: master
+          template:
+            name: kaniko
+            arguments:
+              - name: IMAGE
+                value: docker.io/{DOCKER_USERNAME}/app-from-source:latest
+      revisionTemplate:
+        spec:
+          container:
+            image: docker.io/{DOCKER_USERNAME}/app-from-source:latest
+            imagePullPolicy: Always
+            env:
+              - name: SIMPLE_MSG
+                value: "Hello from the sample app!"
+```
+
+Apply this manifest:
+
+```bash
+$ kubectl apply -f service.yaml
+service "app-from-source" created
+```
+
+Verify:
+
+```bash
+$ kubectl get pods
+NAME                                                    READY     STATUS    RESTARTS   AGE
+app-from-source-00001-deployment-74c9cbdd6c-c6qzv   3/3       Running   0          24s
+```
+
+### Interacting with Service
+
+```bash
+$ kubectl get routes/app-from-source --output=custom-columns=NAME:.metadata.name,DOMAIN:.status.domain
+NAME            DOMAIN
+helloworld-go   app-from-source.default.example.com
+
+$ kubectl get svc knative-ingressgateway --namespace istio-system --output 'jsonpath={.spec.ports[?(@.port==80)].nodePort}'
+32380
+
+$ curl -H "Host: app-from-source.default.example.com" http://localhost:32380
+<h1>Hello from the sample app!</h1>
+```
+
+Hurray! We could deploy applications on Knative and interact with them. Watch this space for more experiments with Knative.
+
+
+
+
+
diff --git a/knative-build/docker-secret.yaml.tmpl b/knative-build/docker-secret.yaml.tmpl
new file mode 100644
index 0000000..ea2ec2c
--- /dev/null
+++ b/knative-build/docker-secret.yaml.tmpl
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Secret
+metadata:
+    name: dockerhub-user-pass
+    annotations:
+        build.knative.dev/docker-0: https://index.docker.io/v1/
+type: kubernetes.io/basic-auth
+data:
+    # use `echo -n "username" | base64 -b 0` to generate this value
+    username: ${DOCKERHUB_USERNAME_BASE64_ENCODED}
+    # use `echo -n "password" | base64 -b 0` to generate this value
+    password: ${DOCKERHUB_PASSWORD_BASE64_ENCODED}
diff --git a/knative-build/runtimes/javascript/Dockerfile b/knative-build/runtimes/javascript/Dockerfile
new file mode 100644
index 0000000..82bac0b
--- /dev/null
+++ b/knative-build/runtimes/javascript/Dockerfile
@@ -0,0 +1,30 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+FROM node:10.15.0-stretch
+RUN apt-get update && apt-get install -y \
+    imagemagick \
+    unzip \
+    && rm -rf /var/lib/apt/lists/*
+WORKDIR /nodejsAction
+COPY . .
+# COPY the package.json to root container, so we can install npm packages a level up from user's packages, so user's packages take precedence
+COPY ./runtimes/javascript/package.json /
+RUN cd / && npm install --no-package-lock \
+    && npm cache clean --force
+EXPOSE 8080
+CMD node --expose-gc ./runtimes/javascript/app.js
diff --git a/knative-build/runtimes/javascript/README.md b/knative-build/runtimes/javascript/README.md
new file mode 100644
index 0000000..6c07128
--- /dev/null
+++ b/knative-build/runtimes/javascript/README.md
@@ -0,0 +1,310 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+# OpenWhisk NodeJS Runtime for Knative
+
+[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0)
+
+This directory is used to build and test the OpenWhisk NodeJS Action runtime for Knative.
+
+## Pre-requisites
+
+Complete the pre-requisites and Knative installation and configuration instructions in the top-level [README](../../README.md) for this repository.
+
+### Verify your Kubernetes & Knative pods are running
+
+Verify **kube-system**, **istio-system**, and **knative-xxx** pods are all **Running**
+```
+$ kubectl get pods --all-namespaces 
+```
+<details>
+    <summary>Sample output</summary>
+    
+```
+$ kubectl get pods --all-namespaces 
+NAMESPACE          NAME                                            READY   STATUS      RESTARTS   AGE
+istio-system       cluster-local-gateway-547467ccf6-p8n72          1/1     Running     1          8d
+istio-system       istio-citadel-7d64db8bcf-m7gsj                  1/1     Running     0          8d
+istio-system       istio-cleanup-secrets-8lzj4                     0/1     Completed   0          8d
+istio-system       istio-egressgateway-6ddf4c8bd6-2dxhc            1/1     Running     1          8d
+istio-system       istio-galley-7dd996474-pdd6h                    1/1     Running     1          8d
+istio-system       istio-ingressgateway-84b89d647f-cxrwx           1/1     Running     1          8d
+istio-system       istio-pilot-86bb4fcbbd-5ns5q                    2/2     Running     0          8d
+istio-system       istio-pilot-86bb4fcbbd-vd2xr                    2/2     Running     0          8d
+istio-system       istio-pilot-86bb4fcbbd-zstrw                    2/2     Running     0          8d
+istio-system       istio-policy-5c4d9ff96b-559db                   2/2     Running     1          8d
+istio-system       istio-sidecar-injector-6977b5cf5b-94hj5         1/1     Running     0          8d
+istio-system       istio-statsd-prom-bridge-b44b96d7b-kzkzc        1/1     Running     0          8d
+istio-system       istio-telemetry-7676df547f-jp952                2/2     Running     1          8d
+istio-system       knative-ingressgateway-75644679c7-c2kxj         1/1     Running     1          8d
+knative-build      build-controller-658d64d9bd-6qp2c               1/1     Running     0          8d
+knative-build      build-webhook-6bb747665f-v8nk2                  1/1     Running     1          8d
+knative-eventing   eventing-controller-cfbb757bd-czx99             1/1     Running     0          8d
+knative-eventing   in-memory-channel-controller-75d6cc4b77-6c8st   1/1     Running     1          8d
+knative-eventing   in-memory-channel-dispatcher-c89db8bb8-phlxw    2/2     Running     7          8d
+knative-eventing   webhook-5fbb8dbcc7-nhwp5                        1/1     Running     0          8d
+knative-serving    activator-69b8474d6b-58hh2                      2/2     Running     1          8d
+knative-serving    autoscaler-6579b57774-cvvzj                     2/2     Running     1          8d
+knative-serving    controller-66cd7d99df-hgswh                     1/1     Running     0          8d
+knative-serving    webhook-6d9568d-czt8m                           1/1     Running     0          8d
+knative-sources    controller-manager-0                            1/1     Running     1          8d
+kube-system        coredns-86c58d9df4-ms8qs                        1/1     Running     0          8d
+kube-system        coredns-86c58d9df4-x29vt                        1/1     Running     0          8d
+kube-system        etcd-docker-desktop                             1/1     Running     3          8d
+kube-system        kube-apiserver-docker-desktop                   1/1     Running     3          8d
+kube-system        kube-controller-manager-docker-desktop          1/1     Running     5          8d
+kube-system        kube-proxy-mltsm                                1/1     Running     0          8d
+kube-system        kube-scheduler-docker-desktop                   1/1     Running     5          8d
+```
+</details>
+
+## Intall the BuildTemplate for the NodeJS runtime
+
+```
+$ kubectl apply --filename buildtemplate.yaml 
+buildtemplate.build.knative.dev/openwhisk-nodejs-runtime created
+```
+
+#### Verify BuildTemplate
+
+```
+$ kubectl get buildtemplate
+NAME                       AGE
+openwhisk-nodejs-runtime   2h
+```
+
+or to see the full resource:
+```
+$ kubectl get buildtemplate -o yaml
+```
+
+<details>
+    <summary>Sample output</summary>
+    
+```
+apiVersion: v1
+items:
+- apiVersion: build.knative.dev/v1alpha1
+  kind: BuildTemplate
+  metadata:
+    annotations:
+      kubectl.kubernetes.io/last-applied-configuration {}
+    creationTimestamp: "2019-02-07T16:10:46Z"
+    generation: 1
+    name: openwhisk-nodejs-runtime
+    namespace: default
+    resourceVersion: "278166"
+    selfLink: /apis/build.knative.dev/v1alpha1/namespaces/default/buildtemplates/openwhisk-nodejs-runtime
+    uid: ed5bb6e0-2af2-11e9-a25d-025000000001
+  spec:
+    generation: 1
+    parameters:
+    - description: name of the image to be tagged and pushed
+      name: TARGET_IMAGE_NAME
+    - default: latest
+      description: tag the image before pushing
+      name: TARGET_IMAGE_TAG
+    - description: name of the dockerfile
+      name: DOCKERFILE
+    - default: "false"
+      description: flag to indicate debug mode should be on/off
+      name: OW_DEBUG
+    - description: name of the action
+      name: OW_ACTION_NAME
+    - description: JavaScript source code to be evaluated
+      name: OW_ACTION_CODE
+    - default: main
+      description: name of the function in the "__OW_ACTION_CODE" to call as the action
+        handler
+      name: OW_ACTION_MAIN
+    - default: "false"
+      description: flag to indicate zip function, for zip actions, "__OW_ACTION_CODE"
+        must be base64 encoded string
+      name: OW_ACTION_BINARY
+    steps:
+    - args:
+      - -c
+      - |
+        cat <<EOF >> ${DOCKERFILE}
+          ENV __OW_DEBUG "${OW_DEBUG}"
+          ENV __OW_ACTION_NAME "${OW_ACTION_NAME}"
+          ENV __OW_ACTION_CODE "${OW_ACTION_CODE}"
+          ENV __OW_ACTION_MAIN "${OW_ACTION_MAIN}"
+          ENV __OW_ACTION_BINARY "${OW_ACTION_BINARY}"
+        EOF
+      command:
+      - /busybox/sh
+      image: gcr.io/kaniko-project/executor:debug
+      name: add-ow-env-to-dockerfile
+    - args:
+      - --destination=${TARGET_IMAGE_NAME}:${TARGET_IMAGE_TAG}
+      - --dockerfile=${DOCKERFILE}
+      image: gcr.io/kaniko-project/executor:latest
+      name: build-openwhisk-nodejs-runtime
+kind: List
+metadata:
+  resourceVersion: ""
+  selfLink: ""
+```
+</details>
+    
+## Building a Knative service using the NodeJS BuildTemplate
+
+We will use the simple "helloworld" test case to demonstrate how to use Knative to Build your function into container image and then deploy it as a Service.
+
+The testcase resides within this repo. at:
+- [/runtimes/javascript/tests/helloworld/](/runtimes/javascript/tests/helloworld/)
+
+For a complete listing of testcases, please view the [README](tests/README.md) in the tests subdirectory.
+
+### Build HelloWorld 
+
+#### Configure build.yaml
+
+You will need to configure the build template to point to the Docker Hub repo. you wish the image to be "pushed" to once built.
+
+To do this, 
+- Copy [build.yaml.tmpl](tests/helloworld/build.yaml.tmpl) to `build.yaml`.
+- Replace ```${DOCKER_USERNAME}``` with your own Docker username in `build.yaml`.
+
+If you wish to run repeated tests you MAY set an environment variable and use ```sed``` to replace the ```${DOCKER_USERNAME}``` within any of the test's Kubernetes Build YAML files as follows:
+
+```
+export DOCKER_USERNAME="myusername"
+sed 's/${DOCKER_USERNAME}/'"$DOCKER_USERNAME"'/' build.yaml.tmpl > build.yaml
+```
+
+<details>
+    <summary>build.yaml.tmpl contents</summary>
+```
+apiVersion: build.knative.dev/v1alpha1
+kind: Build
+metadata:
+  name: nodejs-10-helloworld
+spec:
+  serviceAccountName: openwhisk-runtime-builder
+  source:
+    git:
+      url: "https://github.com/mrutkows/openwhisk-knative-build.git"
+      revision: "master"
+  template:
+    name: openwhisk-nodejs-runtime
+    arguments:
+      - name: TARGET_IMAGE_NAME
+        value: "docker.io/${DOCKER_USERNAME}/nodejs-10-helloworld"
+      - name: DOCKERFILE
+        value: "./runtimes/javascript/Dockerfile"
+      - name: OW_DEBUG
+        value: "true"
+      - name: OW_ACTION_NAME
+        value: "nodejs-helloworld"
+      - name: OW_ACTION_CODE
+        value: "function main() {return {payload: 'Hello World!'};}"
+```
+</details>
+
+### Deploy the runtime
+
+```bash
+kubectl apply -f build.yaml
+```
+
+This creates a pod with a NodeJS runtime and all the action metadata (action code, main function name, etc) integrated into the container image. If for any reason there is a failure creating the pod, we can troubleshoot the deployment with:
+
+#### `kubectl get pods`
+
+```
+kubectl get pods <build-pod-name> -o yaml
+```
+
+Which lists the containers and their status under `initContainerStatuses`:
+
+- build-step-credential-initializer
+- build-step-git-source-0
+- build-step-add-ow-env-to-dockerfile
+- build-step-build-openwhisk-nodejs-runtime
+
+#### `kubectl logs`
+
+Now, the logs of each of these build steps (containers) can be retrieved using:
+
+```
+kubectl logs <build-pod-name> -c <container-name>
+```
+
+For example:
+
+```
+kubectl logs nodejs-10-helloworld-pod-71d4d2 -c build-step-build-openwhisk-nodejs-runtime
+INFO[0007] Downloading base image node:10.15.0-stretch
+error building image: getting stage builder for stage 0: Get https://index.docker.io/v2/: net/http: TLS handshake timeout
+```
+
+#### `kubectl exec`
+
+And, we can get to a shell in any of these containers with:
+
+```
+kubectl exec <build-pod-name> -- env
+```
+
+
+### Run HelloWorld
+
+#### Configure service.yaml
+
+Now that you have built the OpenWhisk NodeJS runtime image with the `helloworld` function "baked" into it, you can can deploy the image as a Knative Service.  
+
+You will need to configure the Service template to point to the Docker Hub repo. where your Knative OpenWhisk runtime (with the Hello World function) will be "pulled" from.
+
+To do this, 
+- Copy [service.yaml.tmpl](tests/helloworld/service.yaml.tmpl) to `service.yaml`.
+- Replace ```${DOCKER_USERNAME}``` with your own Docker username in `service.yaml`.
+
+As described for 'build.yaml.tmpl', you MAY set an environment variable and use ```sed``` to replace the ```${DOCKER_USERNAME}``` within any of the test's Kubernetes Build YAML files as follows:
+
+```
+export DOCKER_USERNAME="myusername"
+sed 's/${DOCKER_USERNAME}/'"$DOCKER_USERNAME"'/' service.yaml.tmpl > service.yaml
+```
+
+<details>
+    <summary>service.yaml.tmpl contents</summary>
+
+```
+apiVersion: serving.knative.dev/v1alpha1
+kind: Service
+metadata:
+  name: nodejs-helloworld
+  namespace: default
+spec:
+  runLatest:
+    configuration:
+      revisionTemplate:
+        spec:
+          container:
+            image: docker.io/${DOCKER_USERNAME}/nodejs-10-helloworld
+```
+</details>
+
+### Deploy the runtime
+
+```bash
+kubectl apply -f service.yaml
+```
diff --git a/knative-build/runtimes/javascript/app.js b/knative-build/runtimes/javascript/app.js
new file mode 100644
index 0000000..61f7193
--- /dev/null
+++ b/knative-build/runtimes/javascript/app.js
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+var dbg = require('./utils/debug');
+var DEBUG = new dbg();
+DEBUG.trace("Hello World from NodeJS runtime");
+DEBUG.dumpObject(process.env, "process.env");
+
+// __OW_ALLOW_CONCURRENT: see docs/concurrency.md
+var config = {
+        'port': 8080,
+        'apiHost': process.env.__OW_API_HOST,
+        'allowConcurrent': process.env.__OW_ALLOW_CONCURRENT,
+        'requestBodyLimit': "48mb"
+};
+
+var runtime_platform = {
+    openwhisk: 'openwhisk',
+    knative: 'knative',
+};
+
+var bodyParser = require('body-parser');
+var express    = require('express');
+
+/**
+ * instantiate app as an instance of Express
+ * i.e. app starts the server
+ */
+var app = express();
+
+/**
+ * instantiate an object which handles REST calls from the Invoker
+ */
+var service = require('./src/service').getService(config);
+
+/**
+ * setup a middleware layer to restrict the request body size
+ * this middleware is called every time a request is sent to the server
+ */
+app.use(bodyParser.json({ limit: config.requestBodyLimit }));
+
+// identify the target Serverless platform
+var targetPlatform = process.env.__OW_RUNTIME_PLATFORM;
+
+// default to "openwhisk" platform initialization if not defined
+if( typeof targetPlatform === "undefined") {
+    console.error("__OW_RUNTIME_PLATFORM is undefined; defaulting to 'openwhisk' ...");
+    targetPlatform = runtime_platform.openwhisk;
+}
+
+/**
+ * Register different endpoint handlers depending on target PLATFORM and its expected behavior.
+ * In addition, register request pre-processors and/or response post-processors as needed.
+ */
+if (targetPlatform === runtime_platform.openwhisk ) {
+    app.post('/init', wrapEndpoint(service.initCode));
+    app.post('/run', wrapEndpoint(service.runCode));
+} else if (targetPlatform === runtime_platform.knative) {
+    var platformFactory = require('./platform/platform.js');
+    var platform = new platformFactory("knative", service, config);
+    app.post('/', platform.run);
+} else {
+    console.error("Environment variable '__OW_RUNTIME_PLATFORM' has an unrecognized value ("+targetPlatform+").");
+}
+
+// short-circuit any requests to invalid routes (endpoints) that we have no handlers for.
+app.use(function (req, res, next) {
+    res.status(500).json({error: "Bad request."});
+});
+
+// register a default error handler. This effectively only gets called when invalid JSON is received (JSON Parser)
+// and we do not wish the default handler to error with a 400 and send back HTML in the body of the response.
+app.use(function (err, req, res, next) {
+    console.log(err.stackTrace);
+    res.status(500).json({error: "Bad request."});
+});
+
+service.start(app);
+
+/**
+ * Wraps an endpoint written to return a Promise into an express endpoint,
+ * producing the appropriate HTTP response and closing it for all controllable
+ * failure modes.
+ *
+ * The expected signature for the promise value (both completed and failed)
+ * is { code: int, response: object }.
+ *
+ * @param ep a request=>promise function
+ * @returns an express endpoint handler
+ */
+function wrapEndpoint(ep) {
+    DEBUG.functionStart("wrapping: " + ep.name);
+    DEBUG.functionEnd("returning wrapper: " + ep.name);
+    return function (req, res) {
+        try {
+            ep(req).then(function (result) {
+                res.status(result.code).json(result.response);
+                DEBUG.dumpObject(result,"result");
+                DEBUG.dumpObject(res,"response");
+                DEBUG.functionEndSuccess("wrapper for: " + ep.name);
+            }).catch(function (error) {
+                if (typeof error.code === "number" && typeof error.response !== "undefined") {
+                    res.status(error.code).json(error.response);
+                } else {
+                    console.error("[wrapEndpoint]", "invalid errored promise", JSON.stringify(error));
+                    res.status(500).json({ error: "Internal error." });
+                }
+                DEBUG.dumpObject(error,"error");
+                DEBUG.dumpObject(res,"response");
+                DEBUG.functionEndError(error, "wrapper for: " + ep.name);
+            });
+        } catch (e) {
+            // This should not happen, as the contract for the endpoints is to
+            // never (externally) throw, and wrap failures in the promise instead,
+            // but, as they say, better safe than sorry.
+            console.error("[wrapEndpoint]", "exception caught", e.message);
+            res.status(500).json({ error: "Internal error (exception)." });
+            DEBUG.dumpObject(error,"error");
+            DEBUG.functionEndError(error, ep.name);
+        }
+    }
+}
diff --git a/knative-build/runtimes/javascript/buildtemplate.yaml b/knative-build/runtimes/javascript/buildtemplate.yaml
new file mode 100644
index 0000000..dd7c4ac
--- /dev/null
+++ b/knative-build/runtimes/javascript/buildtemplate.yaml
@@ -0,0 +1,48 @@
+apiVersion: build.knative.dev/v1alpha1
+kind: BuildTemplate
+metadata:
+  name: openwhisk-nodejs-runtime
+spec:
+  parameters:
+  - name: TARGET_IMAGE_NAME
+    description: name of the image to be tagged and pushed
+  - name: TARGET_IMAGE_TAG
+    description: tag the image before pushing
+    default: "latest"
+  - name: DOCKERFILE
+    description: name of the dockerfile
+  - name: OW_RUNTIME_DEBUG
+    description: flag to indicate debug mode should be on/off
+    default: "false"
+  - name: OW_RUNTIME_PLATFORM
+    description: flag to indicate the platform, one of ["openwhisk", "knative", ... ]
+    default: "knative"
+  - name: OW_ACTION_NAME
+    description: name of the action
+  - name: OW_ACTION_CODE
+    description: JavaScript source code to be evaluated
+  - name: OW_ACTION_MAIN
+    description: name of the function in the "__OW_ACTION_CODE" to call as the action handler
+    default: "main"
+  - name: OW_ACTION_BINARY
+    description: flag to indicate zip function, for zip actions, "__OW_ACTION_CODE" must be base64 encoded string
+    default: "false"
+  steps:
+  - name: add-ow-env-to-dockerfile
+    image: "gcr.io/kaniko-project/executor:debug"
+    command:
+    - /busybox/sh
+    args:
+    - -c
+    - |
+      cat <<EOF >> ${DOCKERFILE}
+        ENV __OW_RUNTIME_DEBUG "${OW_RUNTIME_DEBUG}"
+        ENV __OW_RUNTIME_PLATFORM "${OW_RUNTIME_PLATFORM}"
+        ENV __OW_ACTION_NAME "${OW_ACTION_NAME}"
+        ENV __OW_ACTION_CODE "${OW_ACTION_CODE}"
+        ENV __OW_ACTION_MAIN "${OW_ACTION_MAIN}"
+        ENV __OW_ACTION_BINARY "${OW_ACTION_BINARY}"
+      EOF
+  - name: build-openwhisk-nodejs-runtime
+    image: "gcr.io/kaniko-project/executor:latest"
+    args: ["--destination=${TARGET_IMAGE_NAME}:${TARGET_IMAGE_TAG}", "--dockerfile=${DOCKERFILE}"]
diff --git a/knative-build/runtimes/javascript/package.json b/knative-build/runtimes/javascript/package.json
new file mode 100644
index 0000000..bce34b9
--- /dev/null
+++ b/knative-build/runtimes/javascript/package.json
@@ -0,0 +1,16 @@
+{
+    "name": "action-nodejs-v8",
+    "version": "1.0.0",
+    "description": "Apache OpenWhisk NodeJS Runtime",
+    "repository": {
+        "type": "git",
+        "url": "git@github.com:apache/incubator-openwhisk-runtime-nodejs.git"
+    },
+    "license": "Apache-2.0",
+    "dependencies": {
+        "body-parser": "^1.18.3",
+        "express": "^4.16.4",
+        "openwhisk": "3.18.0",
+        "serialize-error": "^3.0.0"
+    }
+}
diff --git a/knative-build/runtimes/javascript/platform/platform.js b/knative-build/runtimes/javascript/platform/platform.js
new file mode 100644
index 0000000..d448b39
--- /dev/null
+++ b/knative-build/runtimes/javascript/platform/platform.js
@@ -0,0 +1,273 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var dbg = require('../utils/debug');
+var DEBUG = new dbg();
+
+const OW_ENV_PREFIX = "__OW_";
+
+/**
+ * Pre-process the incoming
+ */
+function preProcessInitData(env, initdata, valuedata, activationdata) {
+    DEBUG.functionStart();
+    try {
+        // Set defaults to use INIT data not provided on the request
+        // Look first to the process (i.e., Container's) environment variables.
+        var main = (typeof env.__OW_ACTION_MAIN === 'undefined') ? "main" : env.__OW_ACTION_MAIN;
+        // TODO: Throw error if CODE is NOT defined!
+        var code = (typeof env.__OW_ACTION_CODE === 'undefined') ? "" : env.__OW_ACTION_CODE;
+        var binary = (typeof env.__OW_ACTION_BINARY === 'undefined') ? false : env.__OW_ACTION_BINARY.toLowerCase() === "true";
+        // TODO: deault to empty?
+        var actionName = (typeof env.__OW_ACTION_NAME === 'undefined') ? "" : env.__OW_ACTION_NAME;
+        DEBUG.dumpObject(actionName, "Action name");
+        DEBUG.dumpObject(main, "Action main");
+        DEBUG.dumpObject(code, "Action code");
+        DEBUG.dumpObject(binary, "Action binary");
+
+        // Look for init data within the request (i.e., "stem cell" runtime, where code is injected by request)
+        if (typeof(initdata) !== "undefined") {
+            if (initdata.name && typeof initdata.name === 'string') {
+                // TODO: Throw error if BINARY is not 'true' or 'false'
+                actionName = initdata.name;
+            }
+            if (initdata.main && typeof initdata.main === 'string') {
+                main = initdata.main
+            }
+            if (initdata.code && typeof initdata.code === 'string') {
+                code = initdata.code
+            }
+            if (initdata.binary && typeof initdata.binary === 'boolean') {
+                // TODO: Throw error if BINARY is not 'true' or 'false'
+                binary = initdata.binary
+            }
+        }
+
+        // Move the init data to the request body under the "value" key.
+        // This will allow us to reuse the "openwhisk" /init route handler function
+        valuedata.main = main;
+        valuedata.code = code;
+        valuedata.binary = binary;
+
+        // Action name is a special case, as we have a key collision on "name" between init. data and request
+        // param. data (as they both appear within "body.value") so we must save it to its final location
+        // as the default Action name as part of the activation data
+        // NOTE: if action name is not present in the action data, we will set it regardless even if an empty string
+        if( typeof(activationdata) !== "undefined" ) {
+            if ( typeof(activationdata.action_name) === "undefined" ||
+                (typeof(activationdata.action_name) === "string" && activationdata.action_name.length == 0 )){
+                activationdata.action_name = actionName;
+            }
+        }
+
+        DEBUG.dumpObject(valuedata.main, "valuedata.main");
+        DEBUG.dumpObject(valuedata.code , "valuedata.code");
+        DEBUG.dumpObject(valuedata.binary, "valuedata.binary");
+
+    } catch(e){
+        console.error(e);
+        DEBUG.functionEndError(e.message);
+        throw("Unable to initialize the runtime: " + e.message);
+    }
+    DEBUG.functionEnd();
+}
+
+/**
+ * Pre-process the incoming http request data, moving it to where the
+ * route handlers expect it to be for an openwhisk runtime.
+ */
+function preProcessActivationData(env, activationdata) {
+    DEBUG.functionStart();
+    try {
+        // Note: we move the values here so that the "run()" handler does not have
+        // to move them again.
+        Object.keys(activationdata).forEach(
+            function (k) {
+                if (typeof activationdata[k] === 'string') {
+                    var envVariable = OW_ENV_PREFIX + k.toUpperCase();
+                    process.env[envVariable] = activationdata[k];
+                    DEBUG.dumpObject(process.env[envVariable], envVariable, "preProcessActivationData");
+                }
+            }
+        );
+    } catch(e){
+        console.error(e);
+        DEBUG.functionEndError(e.message);
+        throw("Unable to initialize the runtime: " + e.message);
+    }
+    DEBUG.functionEnd();
+}
+
+/**
+ * helper function to set env variables for HTTP Context
+ */
+function httpContextEnv (key, value) {
+    if (typeof value === 'string') {
+        process.env[key] = value
+        DEBUG.dumpObject(process.env[key], key, "HTTPContext");
+    }
+}
+
+/**
+ * Pre-process HTTP request details, send them as parameters to the action input argument
+ * __ow_method, __ow_headers, __ow_path, __ow_user, __ow_body, and __ow_query
+ */
+function preProcessHTTPContext(req) {
+    DEBUG.functionStart()
+
+    try {
+        httpContextEnv(OW_ENV_PREFIX + "METHOD", req.method)
+        httpContextEnv(OW_ENV_PREFIX + "HEADERS", JSON.stringify(req.headers))
+        httpContextEnv(OW_ENV_PREFIX + "PATH", "");
+
+        var namespace = "";
+        if (process.env[OW_ENV_PREFIX + "NAMESPACE"] !== undefined) {
+            namespace = process.env[OW_ENV_PREFIX + "NAMESPACE"];
+        }
+        httpContextEnv(OW_ENV_PREFIX + "USER", namespace);
+
+        var bodyStr = JSON.stringify(req.body)
+        var bodyBase64 = Buffer.from(bodyStr).toString("base64")
+        httpContextEnv(OW_ENV_PREFIX + "BODY", bodyBase64)
+
+        httpContextEnv(OW_ENV_PREFIX + "QUERY", JSON.stringify(req.query));
+    } catch (e) {
+        console.error(e);
+        DEBUG.functionEndError(e.message);
+        throw ("Unable to initialize the runtime: " + e.message)
+    }
+    DEBUG.functionEnd()
+}
+
+
+/**
+ * Pre-process the incoming http request data, moving it to where the
+ * route handlers expect it to be for an openwhisk runtime.
+ */
+function preProcessRequest(req){
+    DEBUG.functionStart();
+
+    try{
+        // Get or create valid references to the various data we might encounter
+        // in a request such as Init., Activation and function parameter data.
+        let body = req.body || {};
+        let valueData = body.value || {};
+        let initData = body.init || {};
+        let activationData = body.activation || {};
+        let env = process.env || {};
+
+        // process initialization (i.e., "init") data
+        preProcessInitData(env, initData, valueData, activationData);
+
+        // Fix up pointers in case we had to allocate new maps
+        req.body = body;
+        req.body.value = valueData;
+        req.body.init = initData;
+        req.body.activation = activationData;
+
+        // process per-activation (i.e, "run") data
+        preProcessActivationData(env, activationData);
+
+    } catch(e){
+        console.error(e);
+        DEBUG.functionEndError(e.message);
+        // TODO: test this error is handled properly and results in an HTTP error response
+        throw("Unable to initialize the runtime: " + e.message);
+    }
+
+    DEBUG.functionEnd();
+}
+
+function postProcessResponse(result, res) {
+    DEBUG.functionStart();
+
+    // After getting the result back from an action, update the HTTP headers,
+    // status code, and body based on its result if it includes one or more of the
+    // following as top level JSON properties: headers, statusCode, body
+    let statusCode = result.code;
+    let headers = {};
+    let body = result.response;
+
+    // statusCode: default is 200 OK if body is not empty otherwise 204 No Content
+    if (result.response.statusCode !== undefined) {
+        statusCode = result.response.statusCode;
+        delete body['statusCode'];
+    }
+
+    // the default content-type for an HTTP response is application/json
+    // this default are overwritten with the action specified headers
+    if (result.response.headers !== undefined) {
+        headers = result.response.headers;
+        delete body['headers'];
+    }
+
+    // body: a string which is either a plain text, JSON object, or a base64 encoded string for binary data (default is "")
+    // body is considered empty if it is null, "", or undefined
+    if (result.response.body !== undefined) {
+        body = result.response.body;
+        delete body['main'];
+        delete body['code'];
+        delete body['binary'];
+    }
+
+    // statusCode: set it to 204 No Content if body is empty
+    if (statusCode === 200 && body === "") {
+        statusCode = 204;
+    }
+
+    res.header(headers).status(statusCode).json(body);
+
+    DEBUG.functionEnd();
+}
+
+function PlatformFactory(id, svc, cfg) {
+
+    DEBUG.dumpObject(id, "Platform" );
+    DEBUG.dumpObject(svc, "Service" );
+    DEBUG.dumpObject(cfg, "Config" );
+
+    var service = svc;
+    //var config = cfg;  // TODO: use this to pass future config. information uniformly to any impl.
+    var isInitialized = false;
+
+    this.run = function(req, res) {
+
+        try {
+
+            preProcessRequest(req);
+            console.info("isInitialized="+isInitialized);
+
+            service.initCode(req).then(function () {
+                service.runCode(req).then(function (result) {
+                    postProcessResponse(result, res)
+                });
+            }).catch(function (error) {
+                console.error(error);
+                if (typeof error.code === "number" && typeof error.response !== "undefined") {
+                    res.status(error.code).json(error.response);
+                } else {
+                    console.error("[wrapEndpoint]", "invalid errored promise", JSON.stringify(error));
+                    res.status(500).json({ error: "Internal error." });
+                }
+            });
+        } catch (e) {
+            res.status(500).json({error: "internal error"})
+        }
+    }
+};
+
+module.exports = PlatformFactory;
diff --git a/knative-build/runtimes/javascript/runner.js b/knative-build/runtimes/javascript/runner.js
new file mode 100644
index 0000000..a5b9779
--- /dev/null
+++ b/knative-build/runtimes/javascript/runner.js
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var dbg = require('./utils/debug');
+var DEBUG = new dbg();
+
+/**
+ * Object which encapsulates a first-class function, the user code for
+ * an action.
+ *
+ * This file (runner.js) must currently live in root directory for nodeJsAction.
+ */
+var util = require('util');
+var child_process = require('child_process');
+var fs = require('fs');
+var path = require('path');
+const serializeError = require('serialize-error');
+
+function NodeActionRunner() {
+    DEBUG.functionStart();
+    // Use this ref inside closures etc.
+    var thisRunner = this;
+
+    this.userScriptMain = undefined;
+
+    // This structure is reset for every action invocation. It contains two fields:
+    //   - completed; indicating whether the action has already signaled completion
+    //   - next; a callback to be invoked with the result of the action.
+    // Note that { error: ... } results are still results.
+    var callback = {
+        completed : undefined,
+        next      : function (result) { return; }
+    };
+
+    DEBUG.dumpObject(callback,"callback");
+
+    this.init = function(message) {
+        DEBUG.functionStart("NodeActionRunner");
+        function assertMainIsFunction() {
+            DEBUG.dumpObject(thisRunner.userScriptMain,"this.Runner.userScriptMain");
+            if (typeof thisRunner.userScriptMain !== 'function') {
+                DEBUG.functionEndError("ERROR: Action entrypoint '" + message.main + "' is not a function.");
+                throw "Action entrypoint '" + message.main + "' is not a function.";
+            }
+            DEBUG.functionEnd();
+        }
+
+        // Loading the user code.
+        DEBUG.dumpObject(message.binary, "message.binary");
+        if (message.binary) {
+            // The code is a base64-encoded zip file.
+            return unzipInTmpDir(message.code).then(function (moduleDir) {
+                if(!fs.existsSync(path.join(moduleDir, 'package.json')) &&
+                    !fs.existsSync(path.join(moduleDir, 'index.js'))) {
+                    DEBUG.functionEndError("Promise.reject(): Zipped actions must contain either package.json or index.js at the root.");
+                    return Promise.reject('Zipped actions must contain either package.json or index.js at the root.')
+                }
+
+                try {
+                    // Set the executable directory to the project dir
+                    process.chdir(moduleDir);
+                    thisRunner.userScriptMain = eval('require("' + moduleDir + '").' + message.main);
+                    assertMainIsFunction();
+                    // The value 'true' has no special meaning here;
+                    // the successful state is fully reflected in the
+                    // successful resolution of the promise.
+                    DEBUG.functionEndSuccess("return true;");
+                    return true;
+                } catch (e) {
+                    DEBUG.functionEndError("Promise.reject(): " + e.message);
+                    return Promise.reject(e);
+                }
+            }).catch(function (error) {
+                DEBUG.functionEndError("Promise.reject(): " + error.message);
+                return Promise.reject(error);
+            });
+        } else {
+            // The code is a plain old JS file.
+            try {
+                thisRunner.userScriptMain = eval('(function(){' + message.code + '\nreturn ' + message.main + '})()');
+                DEBUG.dumpObject(thisRunner.userScriptMain,"thisRunner.userScriptMain");
+                assertMainIsFunction();
+                // See comment above about 'true'; it has no specific meaning.
+                DEBUG.functionEndSuccess("Promise.resolve(true)");
+                return Promise.resolve(true);
+            } catch (e) {
+                DEBUG.functionEndError("Promise.reject(): " + e.message);
+                return Promise.reject(e);
+            }
+        }
+    };
+
+    // Returns a Promise with the result of the user code invocation.
+    // The Promise is rejected iff the user code throws.
+    this.run = function(args) {
+        DEBUG.functionStart();
+        return new Promise(
+            function (resolve, reject) {
+                callback.completed = undefined;
+                callback.next = resolve;
+
+                try {
+                    DEBUG.dumpObject(args, "Calling: thisRunner.userScriptMain(args): args", "run");
+                    var result = thisRunner.userScriptMain(args);
+                    DEBUG.dumpObject(result,"Returned: thisRunner.userScriptMain(args): result", "run");
+                } catch (e) {
+                    DEBUG.functionEndError("ERROR: Promise.reject(): " + e.message);
+                    reject(e);
+                }
+
+                // Non-promises/undefined instantly resolve.
+                Promise.resolve(result).then(function (resolvedResult) {
+                    // This happens, e.g. if you just have "return;"
+                    if (typeof resolvedResult === "undefined") {
+                        resolvedResult = {};
+                    }
+                    DEBUG.functionEndSuccess("Promise.Resolve(): result=" + resolvedResult, "run");
+                    resolve(resolvedResult);
+                }).catch(function (error) {
+                    // A rejected Promise from the user code maps into a
+                    // successful promise wrapping a whisk-encoded error.
+
+                    // Special case if the user just called `reject()`.
+                    if (!error) {
+                        DEBUG.functionEndError("ERROR: reject()", "run");
+                        resolve({ error: {}});
+                    } else {
+                        DEBUG.functionEndError("ERROR: " + error.message, "run");
+                        resolve({ error: serializeError(error) });
+                    }
+                });
+            }
+        );
+    };
+
+    // Helper function to copy a base64-encoded zip file to a temporary location,
+    // decompress it into temporary directory, and return the name of that directory.
+    // Note that this makes heavy use of shell commands because:
+    //   1) Node 0.12 doesn't have many of the useful fs functions.
+    //   2) We know in which environment we're running.
+    function unzipInTmpDir(base64) {
+        DEBUG.functionStart();
+        var mkTempCmd = "mktemp -d XXXXXXXX";
+        return exec(mkTempCmd).then(function (tmpDir1) {
+            return new Promise(
+                function (resolve, reject) {
+                    var zipFile = path.join(tmpDir1, "action.zip");
+                    fs.writeFile(zipFile, base64, "base64", function (err) {
+                        if (err) {
+                            DEBUG.functionEndError("Promise.reject: " + err.message);
+                            reject("There was an error reading the action archive.");
+                        }
+                        DEBUG.functionEndSuccess("Promise.resolve: zipFile=" + zipFile);
+                        resolve(zipFile);
+                    });
+                }
+            );
+        }).then(function (zipFile) {
+            return exec(mkTempCmd).then(function (tmpDir2) {
+                return exec("unzip -qq " + zipFile + " -d " + tmpDir2).then(function (res) {
+                    DEBUG.functionEndSuccess("Promise.resolve: tmpDir2=" + tmpDir2);
+                    return path.resolve(tmpDir2);
+                }).catch(function (error) {
+                    DEBUG.functionEndError("Promise.reject: " + error.message);
+                    return Promise.reject("There was an error uncompressing the action archive.");
+                });
+            });
+        });
+    }
+
+    // Helper function to run shell commands.
+    function exec(cmd) {
+        DEBUG.functionStart();
+        return new Promise(
+            function (resolve, reject) {
+                child_process.exec(cmd, function (error, stdout, stderr) {
+                    DEBUG.dumpObject(cmd,"cmd");
+                    if (error) {
+                        DEBUG.functionEndError("Promise.reject: " + error.message);
+                        reject(stderr.trim());
+                    } else {
+                        DEBUG.functionEndSuccess("Promise.resolve");
+                        resolve(stdout.trim());
+                    }
+                });
+            }
+        );
+    }
+}
+
+module.exports = NodeActionRunner;
diff --git a/knative-build/runtimes/javascript/src/service.js b/knative-build/runtimes/javascript/src/service.js
new file mode 100644
index 0000000..75bf55a
--- /dev/null
+++ b/knative-build/runtimes/javascript/src/service.js
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var dbg = require('../utils/debug');
+var DEBUG = new dbg();
+
+var NodeActionRunner = require('../runner');
+
+function NodeActionService(cfg) {
+
+    var Status = {
+        ready: 'ready',
+        starting: 'starting',
+        running: 'running',
+        stopped: 'stopped'
+    };
+
+    var config = cfg;
+
+    // TODO: save the entire configuration for use by any of the route handlers
+    var status = Status.ready;
+    var ignoreRunStatus = config.allowConcurrent === undefined ? false : config.allowConcurrent.toLowerCase() === "true";
+    var server = undefined;
+    var userCodeRunner = undefined;
+    DEBUG.trace("Initialize: status=" + status);
+    DEBUG.trace("Initialize: ignoreRunStatus=" + ignoreRunStatus);
+
+    function setStatus(newStatus) {
+        DEBUG.functionStart("newStatus=" + newStatus + " (oldStatus=" + status + ")");
+        if (status !== Status.stopped) {
+            status = newStatus;
+        }
+        DEBUG.functionEnd("status=" + status);
+    }
+
+    /**
+     * An ad-hoc format for the endpoints returning a Promise representing,
+     * eventually, an HTTP response.
+     *
+     * The promised values (whether successful or not) have the form:
+     * { code: int, response: object }
+     *
+     */
+    function responseMessage (code, response) {
+        return { code: code, response: response };
+    }
+
+    function errorMessage (code, errorMsg) {
+        return responseMessage(code, { error: errorMsg });
+    }
+
+    /**
+     * Starts the server.
+     *
+     * @param app express app
+     */
+    this.start = function start(app) {
+        DEBUG.functionStart();
+        server = app.listen(config.port, function() {
+            var host = server.address().address;
+            var port = server.address().port;
+            DEBUG.trace("listening: host: [" + host + "], port: [" + port + "]", "Express (callback)");
+        });
+
+        // This is required as http server will auto disconnect in 2 minutes, this to not auto disconnect at all
+        server.timeout = 0;
+        DEBUG.dumpObject(server, "server");
+        DEBUG.functionEnd();
+    };
+
+    /** Returns a promise of a response to the /init invocation.
+     *
+     *  req.body = { main: String, code: String, binary: Boolean }
+     */
+    this.initCode = function initCode(req) {
+        DEBUG.functionStart("status=" + status);
+
+        if (status === Status.ready && userCodeRunner === undefined) {
+            
+            setStatus(Status.starting);
+
+            var body = req.body || {};
+            var message = body.value || {};
+            DEBUG.dumpObject(body,"body");
+            DEBUG.dumpObject(message,"message");
+
+            if (message.main && message.code && typeof message.main === 'string' && typeof message.code === 'string') {
+                return doInit(message).then(function (result) {
+                    setStatus(Status.ready);
+                    DEBUG.dumpObject(result, "result","initCode");
+                    DEBUG.functionEndSuccess("[200] { OK: true }", "initCode");
+                    return responseMessage(200, { OK: true });
+                }).catch(function (error) {
+                    setStatus(Status.stopped);
+                    var errStr = "Initialization has failed due to: " + error.stack ? String(error.stack) : error;
+                    DEBUG.functionEndError("[502] " + errStr, "initCode");
+                    return Promise.reject(errorMessage(502, errStr));
+                });
+            } else {
+                setStatus(Status.ready);
+                var msg = "Missing main/no code to execute.";
+                console.error("Internal system error:", msg);
+                DEBUG.functionEndError("[403] " + msg);
+                return Promise.reject(errorMessage(403, msg));
+            }
+        } else if (userCodeRunner !== undefined) {
+            var msg = "Cannot initialize the action more than once.";
+            console.error("Internal system error:", msg);
+            DEBUG.functionEndError("[403] " + msg);
+            return Promise.reject(errorMessage(403, msg));
+        } else {
+            var msg = "System not ready, status is " + status + ".";
+            console.error("Internal system error:", msg);
+            DEBUG.functionEndError("[403] " + msg);
+            return Promise.reject(errorMessage(403, msg));
+        }
+    };
+
+    /**
+     * Returns a promise of a response to the /exec invocation.
+     * Note that the promise is failed if and only if there was an unhandled error
+     * (the user code threw an exception, or our proxy had an internal error).
+     * Actions returning { error: ... } are modeled as a Promise successful resolution.
+     *
+     * req.body = { value: Object, meta { activationId : int } }
+     */
+    this.runCode = function runCode(req) {
+        DEBUG.functionStart("status=" + status);
+        DEBUG.dumpObject(req, "request");
+        if (status === Status.ready) {
+            if (!ignoreRunStatus) {
+                setStatus(Status.running);
+            }
+
+            return doRun(req).then(function (result) {
+                if (!ignoreRunStatus) {
+                    setStatus(Status.ready);
+                }
+                DEBUG.dumpObject(result, "result", "runCode");
+                if (typeof result !== "object") {
+                    DEBUG.functionEndError("[502] The action did not return a dictionary.","runCode");
+                    return errorMessage(502, "The action did not return a dictionary.");
+                } else {
+                    DEBUG.functionEndSuccess("[200] result: " + result, "runCode");
+                    return responseMessage(200, result);
+                }
+            }).catch(function (error) {
+                var msg = "An error has occurred: " + error;
+                setStatus(Status.stopped);
+                DEBUG.functionEndError("[502]: " + msg);
+                return Promise.reject(errorMessage(502, msg));
+            });
+        } else {
+            var msg = "System not ready, status is " + status + ".";
+            console.error("Internal system error:", msg);
+            DEBUG.functionEndError("[403] " + msg);
+            return Promise.reject(errorMessage(403, msg));
+        }
+    };
+
+    function doInit(message) {
+        userCodeRunner = new NodeActionRunner();
+
+        DEBUG.functionStart();
+        DEBUG.dumpObject(message,"message")
+        return userCodeRunner.init(message).then(function (result) {
+            // 'true' has no particular meaning here. The fact that the promise
+            // is resolved successfully in itself carries the intended message
+            // that initialization succeeded.
+            DEBUG.functionEndSuccess("userCodeRunner.init() Success");
+            return true;
+        }).catch(function (error) {
+            // emit error to activation log then flush the logs as this
+            // is the end of the activation
+            console.error('Error during initialization:', error);
+            writeMarkers();
+            DEBUG.functionEndError("userCodeRunner.init() Error: " + error);
+            return Promise.reject(error);
+        });
+    }
+
+    function doRun(req) {
+        DEBUG.functionStart();
+        DEBUG.dumpObject(req,"request");
+        var msg = req && req.body || {};
+        DEBUG.dumpObject(msg,"msg");
+        DEBUG.trace("Adding process environment variables:");
+        // Move per-activation keys to process env. vars with __OW_ (reserved) prefix)
+        Object.keys(msg).forEach(
+            function (k) {
+                if(typeof msg[k] === 'string' && k !== 'value'){
+                    var envVariable = '__OW_' + k.toUpperCase();
+                    process.env['__OW_' + k.toUpperCase()] = msg[k];
+                    DEBUG.dumpObject(process.env[envVariable], envVariable, "doRun");
+                }
+            }
+        );
+
+        return userCodeRunner.run(msg.value).then(function(result) {
+            if (typeof result !== "object") {
+                console.error('Result must be of type object but has type "' + typeof result + '":', result);
+            }
+            writeMarkers();
+            DEBUG.functionEndSuccess("userCodeRunner.run(): Success (" + result + ")", "doRun");
+            return result;
+        }).catch(function (error) {
+            console.error(error);
+            writeMarkers();
+            DEBUG.functionEndError("userCodeRunner.run(): Error:" + error, "doRun");
+            return Promise.reject(error);
+        });
+    }
+
+    function writeMarkers() {
+        console.log('XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX');
+        console.error('XXX_THE_END_OF_A_WHISK_ACTIVATION_XXX');
+    }
+}
+
+NodeActionService.getService = function(config) {
+    return new NodeActionService(config);
+};
+
+module.exports = NodeActionService;
diff --git a/knative-build/runtimes/javascript/tests/README.md b/knative-build/runtimes/javascript/tests/README.md
new file mode 100644
index 0000000..35333dc
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/README.md
@@ -0,0 +1,387 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+# Tests for OpenWhisk NodeJS Runtime using Knative
+
+## Test summary
+
+<table cellpadding="8">
+  <tbody>
+    <tr valign="top" align="left">
+      <th width="33%">Name / Description</th>
+      <th width="33%">Knative Resource Templates</th>
+      <th width="33%">Runtime Payload Data<br><sub>(e.g., Curl, VSCode, etc.)</sub></th>
+    </tr>
+    <!-- HelloWorld -->
+    <tr align="left" valign="top">
+      <td>
+        <a href="helloworld">helloworld</a>
+        <p><sub>A simple "Hello world" function with no parameters.</sub></p>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Build: <a href="helloworld/build.yaml.tmpl">build.yaml.tmpl</a></sub></li>
+          <li><sub>Service: <a href="helloworld/build.yaml.tmpl">build.yaml.tmpl</a></sub></li>
+        </ul>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Knative data: <a href="helloworld/data-init.json">data-init-run.json</a></sub></li>
+          <li><sub>OpenWhisk /init data: <a href="helloworld/data-init.json">data-init.json</a></sub></li>
+          <li><sub>OpenWhisk /run data: <a href="helloworld/data-run.json">data-run.json</a></sub></li>
+          <li><sub>Knative Payload: <a href="helloworld/payload-knative-init-run.http">payload-knative-init-run.http</a></sub></li>
+          <li><sub>OpenWhisk /init Payload: <a href="helloworld/payload-openwhisk-init.http">payload-openwhisk-init.http</a></sub></li>
+          <li><sub>OpenWhisk /run Payload: <a href="helloworld/payload-openwhisk-run.http">payload-openwhisk-run.http</a></sub></li>
+        </ul>
+      </td>
+    </tr>
+    <!-- HelloWorld with Params -->
+    <tr align="left" valign="top">
+      <td>
+        <a href="helloworldwithparams">helloworldwithparams</a>
+        <p><sub>A simple "Hello world" function with <em>NAME</em> and <em>PLACE</em> parameters passed via <em>main</em> function args.</sub></p>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Build: <a href="helloworldwithparams/build.yaml.tmpl">build.yaml.tmpl</a></sub></li>
+          <li><sub>Service: <a href="helloworldwithparams/service.yaml.tmpl">service.yaml.tmpl</a></sub></li>
+        </ul>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Knative data: <a href="helloworldwithparams/data-init.json">data-init-run.json</a></sub></li>
+          <li><sub>OpenWhisk /init data: <a href="helloworldwithparams/data-init.json">data-init.json</a></sub></li>
+          <li><sub>OpenWhisk /run data: <a href="helloworldwithparams/data-run.json">data-run.json</a></sub></li>
+          <li><sub>Knative Payload: <a href="helloworldwithparams/payload-knative-init-run.http">payload-knative-init-run.http</a></sub></li>
+          <li><sub>OpenWhisk /init Payload: <a href="helloworldwithparams/payload-openwhisk-init.http">payload-openwhisk-init.http</a></sub></li>
+          <li><sub>OpenWhisk /run Payload: <a href="helloworldwithparams/payload-openwhisk-run.http">payload-openwhisk-run.http</a></sub></li>          
+        </ul>
+      </td>
+    </tr>
+    <!-- HelloWorld with Params from Environment -->
+    <tr align="left" valign="top">
+      <td>
+        <a href="helloworldwithparamsfromenv">helloworldwithparamsfromenv</a>
+        <p><sub>A simple "Hello world" function with <em>NAME</em> and <em>PLACE</em> parameters avail. from NodeJS as process environment variables.</sub></p>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Build: <a href="helloworldwithparamsfromenv/build.yaml.tmpl">build.yaml.tmpl</a></sub></li>
+          <li><sub>Service: <a href="helloworldwithparamsfromenv/service.yaml.tmpl">service.yaml.tmpl</a></sub></li>
+        </ul>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Knative data: <a href="helloworldwithparamsfromenv/data-init.json">data-init-run.json</a></sub></li>
+          <li><sub>OpenWhisk /init data: <a href="helloworldwithparamsfromenv/data-init.json">data-init.json</a></sub></li>
+          <li><sub>OpenWhisk /run data: <a href="helloworldwithparamsfromenv/data-run.json">data-run.json</a></sub></li>
+          <li><sub>Knative Payload: <a href="helloworldwithparamsfromenv/payload-knative-init-run.http">payload-knative-init-run.http</a></sub></li>
+          <li><sub>OpenWhisk /init Payload: <a href="helloworldwithparamsfromenv/payload-openwhisk-init.http">payload-openwhisk-init.http</a></sub></li>
+          <li><sub>OpenWhisk /run Payload: <a href="helloworldwithparamsfromenv/payload-openwhisk-run.http">payload-openwhisk-run.http</a></sub></li>
+        </ul>
+      </td>  
+    </tr>
+    <!-- webactionhelloworld -->
+    <tr align="left" valign="top">
+      <td>
+        <a href="webactionhelloworld">webactionhelloworld</a>
+        <p><sub>A Web Action that takes the HTTP request's query parameters and makes them available as arguments to 
+        the <em>main</em> function. In this case, the value for the <em>name</em> query parameter is used in a 
+        Hello World function.</sub></p>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Build: <a href="webactionhelloworld/build.yaml.tmpl">build.yaml.tmpl</a></sub></li>
+          <li><sub>Service: <a href="webactionhelloworld/service.yaml.tmpl">service.yaml.tmpl</a></sub></li>
+        </ul>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Knative data: <a href="webactionhelloworld/data-init.json">data-init-run.json</a></sub></li>          
+          <li><sub>Knative Payload: <a href="webactionhelloworld/payload-knative-init-run.http">payload-knative-init-run.http</a></sub></li>
+          <li><sub>OpenWhisk /init Payload: <a href="webactionhelloworld/payload-openwhisk-init.http">payload-openwhisk-init.http</a></sub></li>
+          <li><sub>OpenWhisk /run Payload: <a href="webactionhelloworld/payload-openwhisk-run.http">payload-openwhisk-run.http</a></sub></li>
+        </ul>
+      </td>  
+    </tr>
+    <!-- webactionhttpredirect -->
+    <tr align="left" valign="top">
+      <td>
+        <a href="webactionhttpredirect">webactionhttpredirect</a>
+        <p><sub>A Web Action that shows how to perform an HTTP redirect.</sub></p>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Build: <a href="webactionhttpredirect/build.yaml.tmpl">TBD</a></sub></li>
+          <li><sub>Service: <a href="webactionhttpredirect/service.yaml.tmpl">TBD</a></sub></li>
+        </ul>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Knative data: <a href="webactionhttpredirect/data-init.json">data-init-run.json</a></sub></li>          
+          <li><sub>Knative Payload: <a href="webactionhttpredirect/payload-knative-init-run.http">payload-knative-init-run.http</a></sub></li>
+          <li><sub>OpenWhisk /init Payload: <a href="webactionhttpredirect/payload-openwhisk-init.http">payload-openwhisk-init.http</a></sub></li>
+          <li><sub>OpenWhisk /run Payload: <a href="webactionhttpredirect/payload-openwhisk-run.http">payload-openwhisk-run.http</a></sub></li>
+        </ul>
+      </td>  
+    </tr>
+    <!-- webactionjsonparams -->
+    <tr align="left" valign="top">
+      <td>
+        <a href="webactionjsonparams">webactionjsonparams</a>
+        <p><sub>A Web Action that shows how to set an HTTP response <em>Content-Type</em> and status code for a JSON payload.</sub></p>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Build: <a href="webactionjsonparams/build.yaml.tmpl">TBD</a></sub></li>
+          <li><sub>Service: <a href="webactionjsonparams/service.yaml.tmpl">TBD</a></sub></li>
+        </ul>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Knative data: <a href="webactionjsonparams/data-init.json">data-init-run.json</a></sub></li>          
+          <li><sub>Knative Payload: <a href="webactionjsonparams/payload-knative-init-run.http">payload-knative-init-run.http</a></sub></li>
+          <li><sub>OpenWhisk /init Payload: <a href="webactionjsonparams/payload-openwhisk-init.http">payload-openwhisk-init.http</a></sub></li>
+          <li><sub>OpenWhisk /run Payload: <a href="webactionjsonparams/payload-openwhisk-run.http">payload-openwhisk-run.http</a></sub></li>
+        </ul>
+      </td>  
+    </tr>
+    <!-- webactionsettingcookie -->
+    <tr align="left" valign="top">
+      <td>
+        <a href="webactionsettingcookie">webactionsettingcookie</a>
+        <p><sub>A Web Action that shows how to set the HTTP response <em>Set-Cookie</em> field using an HTML payload.</sub></p>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Build: <a href="webactionsettingcookie/build.yaml.tmpl">TBD</a></sub></li>
+          <li><sub>Service: <a href="webactionsettingcookie/service.yaml.tmpl">TBD</a></sub></li>
+        </ul>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Knative data: <a href="webactionsettingcookie/data-init.json">data-init-run.json</a></sub></li>          
+          <li><sub>Knative Payload: <a href="webactionsettingcookie/payload-knative-init-run.http">payload-knative-init-run.http</a></sub></li>
+          <li><sub>OpenWhisk /init Payload: <a href="webactionsettingcookie/payload-openwhisk-init.http">payload-openwhisk-init.http</a></sub></li>
+          <li><sub>OpenWhisk /run Payload: <a href="webactionsettingcookie/payload-openwhisk-run.http">payload-openwhisk-run.http</a></sub></li>
+        </ul>
+      </td>  
+    </tr>
+    <!-- webactionpng -->
+    <tr align="left" valign="top">
+      <td>
+        <a href="webactionpng">webactionpng</a>
+        <p><sub>A Web Action that shows how to set the HTTP response <em>Content-Type</em> to <em>image/png</em> with a base64 encoded payload.</sub></p>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Build: <a href="webactionpng/build.yaml.tmpl">TBD</a></sub></li>
+          <li><sub>Service: <a href="webactionpng/service.yaml.tmpl">TBD</a></sub></li>
+        </ul>
+      </td>
+      <td>
+        <ul>
+          <li><sub>Knative data: <a href="webactionpng/data-init.json">data-init-run.json</a></sub></li>          
+          <li><sub>Knative Payload: <a href="webactionpng/payload-knative-init-run.http">payload-knative-init-run.http</a></sub></li>
+          <li><sub>OpenWhisk /init Payload: <a href="webactionpng/payload-openwhisk-init.http">payload-openwhisk-init.http</a></sub></li>
+          <li><sub>OpenWhisk /run Payload: <a href="webactionpng/payload-openwhisk-run.http">payload-openwhisk-run.http</a></sub></li>
+        </ul>
+      </td>  
+    </tr>
+  </tbody>
+</table>   
+
+# Running the Tests
+
+This is the typical process for running each of the tests under this directory.
+
+### Pre-requisite
+
+```
+kubectl get buildtemplate
+NAME                       CREATED AT
+openwhisk-nodejs-runtime   10m
+```
+
+### Configure and Deploy Build YAML 
+
+```
+export DOCKER_USERNAME="myusername"
+sed 's/${DOCKER_USERNAME}/'"$DOCKER_USERNAME"'/' build.yaml.tmpl > build.yaml
+kubectl apply -f build.yaml
+```
+
+### Configure and Deploy Service YAML
+
+```
+export DOCKER_USERNAME="myusername"
+sed 's/${DOCKER_USERNAME}/'"$DOCKER_USERNAME"'/' service.yaml.tmpl > service.yaml
+kubectl apply -f service.yaml
+```
+
+## Running the Test on different platforms
+
+Depending on the value you set in [buildtemplate.yaml](../buildtemplate.yaml) for the ```OW_RUNTIME_PLATFORM``` parameter, you will need to invoke different endpoints to execute the test.
+
+Currently, the following platform (values) are supported:
+- openwhisk
+- knative
+
+---
+
+## Running with OW_RUNTIME_PLATFORM set to "knative"
+
+Under the Knative platform, the developer has 2 choices:
+1. Use the Knative "build" step to "bake the function" into the runtime resulting in a dedicated runtime 
+(service) container for your running a specific function.
+2. Use Knative build to create a "stem cell" runtime that allows some control plane to inject the function 
+dynamically.
+
+The test case cases under this directory presume option 2 ("stem cells") where both the both runtime 
+initialization, as well as function execution (Activation) happen sequentially.  
+
+However, as OW runtimes do not allow "re-initialization" at this time, once you send the "init data" once to the runtime you 
+cannot send it again or it will result in an error.
+
+Below are some options for invoking the endpoint (route) manually using common developer tooling 
+in conjunction with prepared data:
+
+#### Using the 'curl' command
+
+Simply send the *"init-run"* data to the base *'/'* route on the runtime (service) endpoint.
+
+If your function requires no input data on the request:
+
+```
+curl -H "Host: <hostname>" -X POST http://localhost/
+```
+
+otherwise, you can supply the request data and ```Content-Type``` on the command and pass the JSON data to your function via data file:
+
+```
+curl -H "Host: <hostname>" -d "@data-init-run.json" -H "Content-Type: application/json" http://localhost/
+```
+
+#### Using Http Clients
+
+If using an IDE such as VSCode or IntelliJ you can simply "run" the HTTP payload files named  
+*'payload-knative-init-run.http'* which both initializes the runtime with the function and 
+configuration and executes the function with the provided *"values"* data.
+
+For example, the HelloWorld with parameters payload looks like this:
+
+```
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name": "nodejs-helloworld",
+    "main": "main",
+    "binary": false,
+    "code": "function main() {return {payload: 'Hello World!'};}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-helloworld",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe",
+    "place": "TX"
+  }
+}
+```
+
+please note that the *"activation"* data is also provided, but defaulted in most cases as these would
+be provided by a control-plane which would manage pools of the runtimes and track Activations. 
+
+---
+
+## Running with OW_RUNTIME_PLATFORM set to "openwhisk"
+
+The standard OW methods used to run functions is done through calls to 2 separte endpoints. 
+In short, The control plane would:
+
+1. first, invoke the */init* route with strictly the OW "init. data" (JSON format) including the funtional
+code itself.
+2. then, invoke */run* route which executes the function (i.e., Activates the function) with caller-provided 
+parameters via OW "value data" (JSON format) along with per-activation information which would normally be 
+provided and tracked by the control plane (default/dummy key-values provided for tests).
+
+Below are some options for invoking these routes manually using common developer tooling 
+in conjunction with prepared data:
+
+### Using the 'curl' command
+
+#### Initialize the runtime
+
+Initialize the runtime with the function and other configuration data using the ```/init``` endpoint.
+
+```
+curl -H "Host: <hostname>" -d "@data-init.json" -H "Content-Type: application/json" http://localhost/init
+```
+
+#### Run the function
+
+Execute the function using the ```/run``` endpoint.
+
+with no request data:
+
+```
+curl -H "Host: <hostname>" -X POST http://localhost/run
+```
+
+or with request data and its ```Content-Type```:
+
+```
+curl -H "Host: <hostname>" -d "@data-run.json" -H "Content-Type: <content-type>" -X POST http://localhost/run
+```
+
+# Troubleshooting
+
+## Pod will not Terminate
+
+In some cases, you may need to force the pod to be deleted when the normal delete shown below does not work.
+```
+# Normal service delete
+kubectl delete -f service.yaml
+```
+
+you will see something like the following for a long period of time:
+```
+$ kubectl get pods --namespace default
+
+NAME                                                  READY   STATUS      RESTARTS   AGE
+nodejs-helloworld-00001-deployment-78c6bfbf4c-8cgtd   2/3     Terminating  0         81s
+```
+
+In this case, you can force the pod with your service to delete as follows:
+```
+kubectl delete pod nodejs-helloworld-00001-deployment-78c6bfbf4c-8cgtd --grace-period=0 --force
+```
+
+Also, be sure your service is completely deleted from the system:
+```
+kubectl delete -f service.yaml
+```
+
diff --git a/knative-build/runtimes/javascript/tests/error/payload-bad-knative-init-code-empty.http b/knative-build/runtimes/javascript/tests/error/payload-bad-knative-init-code-empty.http
new file mode 100644
index 0000000..4c172f3
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-bad-knative-init-code-empty.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-helloworld",
+    "main" : "main",
+    "binary": false,
+    "code" : ""
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/error/payload-bad-openwhisk-init-code-empty.http b/knative-build/runtimes/javascript/tests/error/payload-bad-openwhisk-init-code-empty.http
new file mode 100644
index 0000000..d121a8b
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-bad-openwhisk-init-code-empty.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-helloworld-with-params",
+    "main" : "main",
+    "binary": false,
+    "code" : ""
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/error/payload-bad-openwhisk-init-code-missing.http b/knative-build/runtimes/javascript/tests/error/payload-bad-openwhisk-init-code-missing.http
new file mode 100644
index 0000000..040c1d1
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-bad-openwhisk-init-code-missing.http
@@ -0,0 +1,12 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-helloworld",
+    "main" : "main",
+    "binary": false
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/error/payload-init-bad-json-payload.http b/knative-build/runtimes/javascript/tests/error/payload-init-bad-json-payload.http
new file mode 100644
index 0000000..06e9d28
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-init-bad-json-payload.http
@@ -0,0 +1,13 @@
+GET http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-helloworld",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() {return {payload: 'Hello World!'};}",
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/error/payload-init-run-empty-data.http b/knative-build/runtimes/javascript/tests/error/payload-init-run-empty-data.http
new file mode 100644
index 0000000..449eaf2
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-init-run-empty-data.http
@@ -0,0 +1 @@
+POST http://localhost:8080/ HTTP/1.1
\ No newline at end of file
diff --git a/knative-build/runtimes/javascript/tests/error/payload-knative-activation-action-name-empy.http b/knative-build/runtimes/javascript/tests/error/payload-knative-activation-action-name-empy.http
new file mode 100644
index 0000000..4abbf92
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-knative-activation-action-name-empy.http
@@ -0,0 +1,25 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name": "nodejs-helloworld-from-init",
+    "main": "main",
+    "binary": false,
+    "code": "function main() {return {payload: 'Hello World!  (' + process.env[\"__OW_ACTION_NAME\"] + ')'};}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe",
+    "place": "TX"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/error/payload-knative-activation-action-name-missing.http b/knative-build/runtimes/javascript/tests/error/payload-knative-activation-action-name-missing.http
new file mode 100644
index 0000000..8b47670
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-knative-activation-action-name-missing.http
@@ -0,0 +1,24 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name": "nodejs-helloworld-from-init",
+    "main": "main",
+    "binary": false,
+    "code": "function main() {return {payload: 'Hello World!  (' + process.env[\"__OW_ACTION_NAME\"] + ')'};}"
+  },
+  "activation": {
+    "namespace": "default",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe",
+    "place": "TX"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/error/payload-no-body.http b/knative-build/runtimes/javascript/tests/error/payload-no-body.http
new file mode 100644
index 0000000..db9eee8
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-no-body.http
@@ -0,0 +1,7 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/error/payload-run-get-bad-endpoint.http b/knative-build/runtimes/javascript/tests/error/payload-run-get-bad-endpoint.http
new file mode 100644
index 0000000..701d277
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-run-get-bad-endpoint.http
@@ -0,0 +1,9 @@
+GET http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+    "name" : "Bad",
+    "place" : "GET"
+}
+
+###
\ No newline at end of file
diff --git a/knative-build/runtimes/javascript/tests/error/payload-run-post-bad-endpoint.http b/knative-build/runtimes/javascript/tests/error/payload-run-post-bad-endpoint.http
new file mode 100644
index 0000000..0350866
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/error/payload-run-post-bad-endpoint.http
@@ -0,0 +1,9 @@
+POST http://localhost:8080/bad HTTP/1.1
+content-type: application/json
+
+{
+    "name" : "Bad",
+    "place" : "POST"
+}
+
+###
\ No newline at end of file
diff --git a/knative-build/runtimes/javascript/tests/helloworld/README.md b/knative-build/runtimes/javascript/tests/helloworld/README.md
new file mode 100644
index 0000000..36b4f95
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworld/README.md
@@ -0,0 +1,54 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+# Hello World Test for OpenWhisk NodeJS Runtime using Knative
+
+## Running the test using the "Curl" command
+
+Depending on the value you set in [buildtemplate.yaml](../../buildtemplate.yaml) for the ```OW_RUNTIME_PLATFORM``` parameter, you will need to invoke different endpoints to execute the test.
+
+### Running with OW_RUNTIME_PLATFORM set to "knative"
+
+#### Invoke / endpoint on the Service
+
+```
+curl -H "Host: nodejs-helloworld.default.example.com" -X POST http://localhost/
+```
+
+### Running with OW_RUNTIME_PLATFORM set to "openwhisk"
+
+#### Initialize the runtime
+
+Initialize the runtime with the function and other configuration data using the ```/init``` endpoint.
+
+```
+curl -H "Host: nodejs-helloworld.default.example.com" -d "@data-init.json" -H "Content-Type: application/json" http://localhost/init
+
+{"OK":true}
+```
+
+#### Run the function
+
+Execute the function using the ```/run``` endpoint.
+
+```
+curl -H "Host: nodejs-helloworld.default.example.com" -d "@data-run.json" -H "Content-Type: application/json" -X POST http://localhost/run
+
+{"payload":"Hello"}
+```
diff --git a/knative-build/runtimes/javascript/tests/helloworld/build.yaml.tmpl b/knative-build/runtimes/javascript/tests/helloworld/build.yaml.tmpl
new file mode 100644
index 0000000..4e9c1a7
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworld/build.yaml.tmpl
@@ -0,0 +1,23 @@
+apiVersion: build.knative.dev/v1alpha1
+kind: Build
+metadata:
+  name: nodejs-10-helloworld
+spec:
+  serviceAccountName: openwhisk-runtime-builder
+  source:
+    git:
+      url: "https://github.com/mrutkows/openwhisk-knative-build.git"
+      revision: "master"
+  template:
+    name: openwhisk-nodejs-runtime
+    arguments:
+      - name: TARGET_IMAGE_NAME
+        value: "docker.io/${DOCKER_USERNAME}/nodejs-10-helloworld"
+      - name: DOCKERFILE
+        value: "./runtimes/javascript/Dockerfile"
+      - name: OW_RUNTIME_DEBUG
+        value: "true"
+      - name: OW_ACTION_NAME
+        value: "nodejs-helloworld"
+      - name: OW_ACTION_CODE
+        value: "function main() {return {payload: 'Hello World!'};}"
diff --git a/knative-build/runtimes/javascript/tests/helloworld/data-init.json b/knative-build/runtimes/javascript/tests/helloworld/data-init.json
new file mode 100644
index 0000000..6e655e1
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworld/data-init.json
@@ -0,0 +1,8 @@
+{
+    "value": {
+        "name" : "nodejs-helloworld",
+        "main" : "main",
+        "binary": false,
+        "code" : "function main() {return {payload: 'Hello World!'};}"
+    }
+}
diff --git a/knative-build/runtimes/javascript/tests/helloworld/data-run.json b/knative-build/runtimes/javascript/tests/helloworld/data-run.json
new file mode 100644
index 0000000..c833598
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworld/data-run.json
@@ -0,0 +1,11 @@
+{ 
+    "value": {
+        
+    },
+    "namespace": "default",
+    "action_name": "nodejs-helloworld",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+}
diff --git a/knative-build/runtimes/javascript/tests/helloworld/payload-knative-init-run.http b/knative-build/runtimes/javascript/tests/helloworld/payload-knative-init-run.http
new file mode 100644
index 0000000..a48ea10
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworld/payload-knative-init-run.http
@@ -0,0 +1,25 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name": "nodejs-helloworld",
+    "main": "main",
+    "binary": false,
+    "code": "function main() {return {payload: 'Hello World!'};}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-helloworld",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe",
+    "place": "TX"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworld/payload-openwhisk-init.http b/knative-build/runtimes/javascript/tests/helloworld/payload-openwhisk-init.http
new file mode 100644
index 0000000..9d8206d
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworld/payload-openwhisk-init.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-helloworld",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() {return {payload: 'Hello World!'};}"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworld/payload-openwhisk-run.http b/knative-build/runtimes/javascript/tests/helloworld/payload-openwhisk-run.http
new file mode 100644
index 0000000..6c4c094
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworld/payload-openwhisk-run.http
@@ -0,0 +1,15 @@
+POST http://localhost:8080/run HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+  },
+  "namespace": "default",
+  "action_name": "nodejs-helloworld",
+  "api_host": "",
+  "api_key": "",
+  "activation_id": "",
+  "deadline": "4102498800000"
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworld/service.yaml.tmpl b/knative-build/runtimes/javascript/tests/helloworld/service.yaml.tmpl
new file mode 100644
index 0000000..18eb93c
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworld/service.yaml.tmpl
@@ -0,0 +1,12 @@
+apiVersion: serving.knative.dev/v1alpha1
+kind: Service
+metadata:
+  name: nodejs-helloworld
+  namespace: default
+spec:
+  runLatest:
+    configuration:
+      revisionTemplate:
+        spec:
+          container:
+            image: docker.io/${DOCKER_USERNAME}/nodejs-10-helloworld
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/README.md b/knative-build/runtimes/javascript/tests/helloworldwithparams/README.md
new file mode 100644
index 0000000..74b243b
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/README.md
@@ -0,0 +1,54 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+# Hello World with Params Test for OpenWhisk NodeJS Runtime using Knative
+
+## Running the test using the "Curl" command
+
+Depending on the value you set in [buildtemplate.yaml](../../buildtemplate.yaml) for the ```OW_RUNTIME_PLATFORM``` parameter, you will need to invoke different endpoints to execute the test.
+
+### Running with OW_RUNTIME_PLATFORM set to "knative"
+
+#### Invoke / endpoint on the Service
+
+```
+curl -H "Host: nodejs-helloworld-with-params.default.example.com" -d '{"value": {"name": "Joe", "place": "TX"}}' -H "Content-Type: application/json" http://localhost/
+```
+
+### Running with OW_RUNTIME_PLATFORM set to "openwhisk"
+
+#### Initialize the runtime
+
+Initialize the runtime with the function and other configuration data using the ```/init``` endpoint.
+
+```
+curl -H "Host: nodejs-helloworld-with-params.default.example.com" -d "@data-init.json" -H "Content-Type: application/json" http://localhost/init
+
+{"OK":true}
+```
+
+#### Run the function
+
+Execute the function using the ```/run``` endpoint.
+
+```
+curl -H "Host: nodejs-helloworld-with-params.default.example.com" -d "@data-run.json" -H "Content-Type: application/json" -X POST http://localhost/run
+
+{"payload":"Hello Joe from TX!"};
+```
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/build.yaml.tmpl b/knative-build/runtimes/javascript/tests/helloworldwithparams/build.yaml.tmpl
new file mode 100644
index 0000000..c4cf76e
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/build.yaml.tmpl
@@ -0,0 +1,23 @@
+apiVersion: build.knative.dev/v1alpha1
+kind: Build
+metadata:
+  name: nodejs-10-helloworld-with-params
+spec:
+  serviceAccountName: openwhisk-runtime-builder
+  source:
+    git:
+      url: "https://github.com/mrutkows/openwhisk-knative-build.git"
+      revision: "master"
+  template:
+    name: openwhisk-nodejs-runtime
+    arguments:
+      - name: TARGET_IMAGE_NAME
+        value: "docker.io/${DOCKER_USERNAME}/nodejs-10-helloworld-with-params"
+      - name: DOCKERFILE
+        value: "./runtimes/javascript/Dockerfile"
+      - name: OW_RUNTIME_DEBUG
+        value: "true"
+      - name: OW_ACTION_NAME
+        value: "nodejs-helloworld-with-params"
+      - name: OW_ACTION_CODE
+        value: "function main() {return {payload: 'Hello ' + params.name + ' from ' + params.place + '!'};}"
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/data-init-run.json b/knative-build/runtimes/javascript/tests/helloworldwithparams/data-init-run.json
new file mode 100644
index 0000000..5fc3c1f
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/data-init-run.json
@@ -0,0 +1,20 @@
+{
+    "init": {
+        "name" : "nodejs-helloworld-with-params",
+        "main" : "main",
+        "binary": false,
+        "code" : "function main(params) {return {payload: 'Hello ' + params.name + ' from ' + params.place +  '!'};}"
+    },
+    "activation": {
+        "namespace": "default",
+        "action_name": "nodejs-helloworld-with-params",
+        "api_host": "",
+        "api_key": "",
+        "activation_id": "",
+        "deadline": "4102498800000"
+    },
+    "value": {
+        "name" : "Joe",
+        "place" : "TX"
+    }
+}
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/data-init.json b/knative-build/runtimes/javascript/tests/helloworldwithparams/data-init.json
new file mode 100644
index 0000000..6dccc97
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/data-init.json
@@ -0,0 +1,8 @@
+{
+    "value": {
+        "name" : "nodejs-helloworld-with-params",
+        "main" : "main",
+        "binary": false,
+        "code" : "function main(params) {return {payload: 'Hello ' + params.name + ' from ' + params.place +  '!'};}"
+    }
+}
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/data-run.json b/knative-build/runtimes/javascript/tests/helloworldwithparams/data-run.json
new file mode 100644
index 0000000..e1febdc
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/data-run.json
@@ -0,0 +1,12 @@
+{
+    "value": {
+        "name" : "Joe",
+        "place" : "TX"
+    },
+    "namespace": "default",
+    "action_name": "nodejs-helloworld-with-params",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+}
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-knative-init-run.http b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-knative-init-run.http
new file mode 100644
index 0000000..7b4a421
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-knative-init-run.http
@@ -0,0 +1,25 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name" : "nodejs-helloworld-with-params",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main(params) {return {payload: 'Hello ' + params.name + ' from ' + params.place +  '!'};}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-helloworld-with-params",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name" : "Joe",
+    "place" : "TX"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-openwhisk-init.http b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-openwhisk-init.http
new file mode 100644
index 0000000..691042c
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-openwhisk-init.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-helloworld-with-params",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main(params) {return {payload: 'Hello ' + params.name + ' from ' + params.place +  '!'};}"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-openwhisk-run.http b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-openwhisk-run.http
new file mode 100644
index 0000000..30bd652
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/payload-openwhisk-run.http
@@ -0,0 +1,17 @@
+POST http://localhost:8080/run HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "Joe",
+    "place" : "TX"
+  },
+  "namespace": "default",
+  "action_name": "nodejs-helloworld-with-params",
+  "api_host": "",
+  "api_key": "",
+  "activation_id": "",
+  "deadline": "4102498800000"
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparams/service.yaml.tmpl b/knative-build/runtimes/javascript/tests/helloworldwithparams/service.yaml.tmpl
new file mode 100644
index 0000000..1259f97
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparams/service.yaml.tmpl
@@ -0,0 +1,12 @@
+apiVersion: serving.knative.dev/v1alpha1
+kind: Service
+metadata:
+  name: nodejs-helloworld-with-params
+  namespace: default
+spec:
+  runLatest:
+    configuration:
+      revisionTemplate:
+        spec:
+          container:
+            image: docker.io/${DOCKER_USERNAME}/nodejs-10-helloworld-with-params
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/README.md b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/README.md
new file mode 100644
index 0000000..9a921fc
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/README.md
@@ -0,0 +1,54 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+# Hello World with Params from Env. Test for OpenWhisk NodeJS Runtime using Knative
+
+## Running the test using the "Curl" command
+
+Depending on the value you set in [buildtemplate.yaml](../../buildtemplate.yaml) for the ```OW_RUNTIME_PLATFORM``` parameter, you will need to invoke different endpoints to execute the test.
+
+### Running with OW_RUNTIME_PLATFORM set to "knative"
+
+#### Invoke / endpoint on the Service
+
+```
+curl -H "Host: nodejs-helloworld-with-params-from-env.default.example.com" -X POST http://localhost/
+```
+
+### Running with OW_RUNTIME_PLATFORM set to "openwhisk"
+
+#### Initialize the runtime
+
+Initialize the runtime with the function and other configuration data using the ```/init``` endpoint.
+
+```
+curl -H "Host: nodejs-helloworld-with-params-from-env.default.example.com" -d "@data-init.json" -H "Content-Type: application/json" http://localhost/init
+
+{"OK":true}
+```
+
+#### Run the function
+
+Execute the function using the ```/run``` endpoint.
+
+```
+curl -H "Host: nodejs-helloworld-with-params-from-env.default.example.com" -d "@data-run.json" -H "Content-Type: application/json" -X POST http://localhost/run
+
+{"payload":"Hello Jess from OK!"};
+```
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/build.yaml.tmpl b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/build.yaml.tmpl
new file mode 100644
index 0000000..4787e55
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/build.yaml.tmpl
@@ -0,0 +1,23 @@
+apiVersion: build.knative.dev/v1alpha1
+kind: Build
+metadata:
+  name: nodejs-10-helloworld-with-params-from-env
+spec:
+  serviceAccountName: openwhisk-runtime-builder
+  source:
+    git:
+      url: "https://github.com/mrutkows/openwhisk-knative-build.git"
+      revision: "master"
+  template:
+    name: openwhisk-nodejs-runtime
+    arguments:
+      - name: TARGET_IMAGE_NAME
+        value: "docker.io/${DOCKER_USERNAME}/nodejs-10-helloworld-with-params-from-env"
+      - name: DOCKERFILE
+        value: "./runtimes/javascript/Dockerfile"
+      - name: OW_RUNTIME_DEBUG
+        value: "true"
+      - name: OW_ACTION_NAME
+        value: "nodejs-helloworld-with-params-from-env"
+      - name: OW_ACTION_CODE
+        value: "function main() {return {payload: 'Hello ' + process.env.NAME + ' from ' + process.env.PLACE + '!'};}"
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/data-init-run.json b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/data-init-run.json
new file mode 100644
index 0000000..321a5d6
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/data-init-run.json
@@ -0,0 +1,20 @@
+{
+    "init": {
+        "name" : "nodejs-helloworld-with-params-from-env",
+        "main" : "main",
+        "binary": false,
+        "code" : "function main() {return {payload: 'Hello ' + process.env.NAME + ' from ' + process.env.PLACE +  '!'};}"
+    },
+    "activation": {
+        "namespace": "default",
+        "action_name": "nodejs-helloworld-with-params-from-env",
+        "api_host": "",
+        "api_key": "",
+        "activation_id": "",
+        "deadline": "4102498800000"
+    },
+    "value": {
+        "name" : "Jess",
+        "place" : "OK"
+    }
+}
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/data-init.json b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/data-init.json
new file mode 100644
index 0000000..b35bee0
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/data-init.json
@@ -0,0 +1,8 @@
+{ 
+    "value": { 
+        "name" : "nodejs-helloworld-with-params-from-env",
+        "main" : "main", 
+        "binary": false, 
+        "code" : "function main() {return {payload: 'Hello ' + process.env.NAME + ' from ' + process.env.PLACE +  '!'};}"
+    }
+}
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/data-run.json b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/data-run.json
new file mode 100644
index 0000000..74986e4
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/data-run.json
@@ -0,0 +1,12 @@
+{
+    "value": {
+        "name" : "Jess",
+        "place" : "OK"
+    },
+    "namespace": "default",
+    "action_name": "nodejs-helloworld-with-params-from-env",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+}
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/payload-knative-init-run.http b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/payload-knative-init-run.http
new file mode 100644
index 0000000..7a02f0b
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/payload-knative-init-run.http
@@ -0,0 +1,25 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name" : "nodejs-helloworld-with-params-from-env",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() {return {payload: 'Hello ' + process.env.NAME + ' from ' + process.env.PLACE +  '!'};}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-helloworld-with-params-from-env",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name" : "Jess",
+    "place" : "OK"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/payload-openwhisk-init.http b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/payload-openwhisk-init.http
new file mode 100644
index 0000000..d9fef7b
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/payload-openwhisk-init.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-helloworld-with-params-from-env",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() {return {payload: 'Hello ' + process.env.NAME + ' from ' + process.env.PLACE +  '!'};}"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/payload-openwhisk-run.http b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/payload-openwhisk-run.http
new file mode 100644
index 0000000..21dca91
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/payload-openwhisk-run.http
@@ -0,0 +1,16 @@
+POST http://localhost:8080/run HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "Jess",
+    "place" : "OK"
+  },
+  "namespace": "default",
+  "action_name": "nodejs-helloworld-with-params-from-env",
+  "api_host": "",
+  "api_key": "",
+  "activation_id": "",
+  "deadline": "4102498800000"
+}
+###
diff --git a/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/service.yaml.tmpl b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/service.yaml.tmpl
new file mode 100644
index 0000000..1335059
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/helloworldwithparamsfromenv/service.yaml.tmpl
@@ -0,0 +1,17 @@
+apiVersion: serving.knative.dev/v1alpha1
+kind: Service
+metadata:
+  name: nodejs-helloworld-with-params-from-env
+  namespace: default
+spec:
+  runLatest:
+    configuration:
+      revisionTemplate:
+        spec:
+          container:
+            image: docker.io/${DOCKER_USERNAME}/nodejs-10-helloworld-with-params-from-env
+            env:
+            - name: NAME
+              value: Bob
+            - name: PLACE
+              value: Italy
diff --git a/knative-build/runtimes/javascript/tests/webactionhelloworld/README.md b/knative-build/runtimes/javascript/tests/webactionhelloworld/README.md
new file mode 100644
index 0000000..d4327f7
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhelloworld/README.md
@@ -0,0 +1,54 @@
+<!--
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+-->
+
+# Hello World Web Action Test for OpenWhisk NodeJS Runtime using Knative
+
+## Running the test using the "Curl" command
+
+Depending on the value you set in [buildtemplate.yaml](../../buildtemplate.yaml) for the ```OW_RUNTIME_PLATFORM``` parameter, you will need to invoke different endpoints to execute the test.
+
+### Running with OW_RUNTIME_PLATFORM set to "knative"
+
+#### Invoke / endpoint on the Service
+
+```
+curl -H "Host: nodejs-web-action-helloworld.default.example.com" -X POST http://localhost/
+<html><body><h3>hello Joe</h3></body></html>
+```
+
+### Running with OW_RUNTIME_PLATFORM set to "openwhisk"
+
+#### Initialize the runtime
+
+Initialize the runtime with the function and other configuration data using the ```/init``` endpoint.
+
+```
+curl -H "Host: nodejs-web-aciton-helloworld.default.example.com" -d "@data-init.json" -H "Content-Type: application/json" http://localhost/init
+
+{"OK":true}
+```
+
+#### Run the function
+
+Execute the function using the ```/run``` endpoint.
+
+```
+curl -H "Host: nodejs-web-action-helloworld.default.example.com" -d "@data-run.json" -H "Content-Type: application/json" -X POST http://localhost/run
+<html><body><h3>hello Joe</h3></body></html>
+```
diff --git a/knative-build/runtimes/javascript/tests/webactionhelloworld/build.yaml.tmpl b/knative-build/runtimes/javascript/tests/webactionhelloworld/build.yaml.tmpl
new file mode 100644
index 0000000..6c7176f
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhelloworld/build.yaml.tmpl
@@ -0,0 +1,24 @@
+apiVersion: build.knative.dev/v1alpha1
+kind: Build
+metadata:
+  name: nodejs-10-web-action-helloworld
+spec:
+  serviceAccountName: openwhisk-runtime-builder
+  source:
+    git:
+      url: "https://github.com/mrutkows/openwhisk-knative-build.git"
+      revision: "master"
+  template:
+    name: openwhisk-nodejs-runtime
+    arguments:
+      - name: TARGET_IMAGE_NAME
+        value: "docker.io/${DOCKER_USERNAME}/nodejs-10-web-action-helloworld"
+      - name: DOCKERFILE
+        value: "./runtimes/javascript/Dockerfile"
+      - name: OW_RUNTIME_DEBUG
+        value: "true"
+      - name: OW_ACTION_NAME
+        value: "nodejs-web-action-helloworld"
+      - name: OW_ACTION_CODE
+        "value" : "function main({name}) { var msg = 'you did not tell me who you are.'; if (name) { msg = `hello ${name}` } return {body: `<html><body><h3>${msg}</h3></body></html>`} }"
+
diff --git a/knative-build/runtimes/javascript/tests/webactionhelloworld/data-init-run.json b/knative-build/runtimes/javascript/tests/webactionhelloworld/data-init-run.json
new file mode 100644
index 0000000..93accb4
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhelloworld/data-init-run.json
@@ -0,0 +1,19 @@
+{
+  "init": {
+    "name" : "nodejs-web-action-helloworld",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main({name}) { var msg = 'you did not tell me who you are.'; if (name) { msg = `hello ${name}` } return {body: `<html><body><h3>${msg}</h3></body></html>`} }"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-helloworld",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe"
+  }
+}
diff --git a/knative-build/runtimes/javascript/tests/webactionhelloworld/data-init.json b/knative-build/runtimes/javascript/tests/webactionhelloworld/data-init.json
new file mode 100644
index 0000000..94b3869
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhelloworld/data-init.json
@@ -0,0 +1,8 @@
+{
+    "value": {
+        "name" : "nodejs-web-action-helloworld",
+        "main" : "main",
+        "binary": false,
+        "code" : "function main({name}) { var msg = 'you did not tell me who you are.'; if (name) { msg = `hello ${name}` } return {body: `<html><body><h3>${msg}</h3></body></html>`} }"
+    }
+}
diff --git a/knative-build/runtimes/javascript/tests/webactionhelloworld/data-run.json b/knative-build/runtimes/javascript/tests/webactionhelloworld/data-run.json
new file mode 100644
index 0000000..5298eb6
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhelloworld/data-run.json
@@ -0,0 +1,11 @@
+{ 
+    "value": {
+        "name": "Joe"
+    },
+    "namespace": "default",
+    "action_name": "nodejs-web-action-helloworld",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+}
diff --git a/knative-build/runtimes/javascript/tests/webactionhelloworld/payload-knative-init-run.http b/knative-build/runtimes/javascript/tests/webactionhelloworld/payload-knative-init-run.http
new file mode 100644
index 0000000..6920998
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhelloworld/payload-knative-init-run.http
@@ -0,0 +1,24 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name" : "nodejs-web-action-helloworld",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main({name}) { var msg = 'you did not tell me who you are.'; if (name) { msg = `hello ${name}` } return {body: `<html><body><h3>${msg}</h3></body></html>`} }"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-helloworld",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionhelloworld/payload-openwhisk-init.http b/knative-build/runtimes/javascript/tests/webactionhelloworld/payload-openwhisk-init.http
new file mode 100644
index 0000000..e8e680d
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhelloworld/payload-openwhisk-init.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-web-action-helloworld",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main({name}) { var msg = 'you did not tell me who you are.'; if (name) { msg = `hello ${name}` } return {body: `<html><body><h3>${msg}</h3></body></html>`} }"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionhelloworld/payload-openwhisk-run.http b/knative-build/runtimes/javascript/tests/webactionhelloworld/payload-openwhisk-run.http
new file mode 100644
index 0000000..75721d5
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhelloworld/payload-openwhisk-run.http
@@ -0,0 +1,16 @@
+POST http://localhost:8080/run HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "Joe"
+  },
+  "namespace": "default",
+  "action_name": "nodejs-web-action-helloworld",
+  "api_host": "",
+  "api_key": "",
+  "activation_id": "",
+  "deadline": "4102498800000"
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionhelloworld/service.yaml.tmpl b/knative-build/runtimes/javascript/tests/webactionhelloworld/service.yaml.tmpl
new file mode 100644
index 0000000..aa4a123
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhelloworld/service.yaml.tmpl
@@ -0,0 +1,12 @@
+apiVersion: serving.knative.dev/v1alpha1
+kind: Service
+metadata:
+  name: nodejs-web-action-helloworld
+  namespace: default
+spec:
+  runLatest:
+    configuration:
+      revisionTemplate:
+        spec:
+          container:
+            image: docker.io/${DOCKER_USERNAME}/nodejs-10-web-action-helloworld
diff --git a/knative-build/runtimes/javascript/tests/webactionhttpredirect/data-init-run.json b/knative-build/runtimes/javascript/tests/webactionhttpredirect/data-init-run.json
new file mode 100644
index 0000000..64136d2
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhttpredirect/data-init-run.json
@@ -0,0 +1,18 @@
+{
+  "init": {
+    "name" : "nodejs-web-action-to-redirect",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() { return { headers: { location: 'http://openwhisk.org' }, statusCode: 302 }}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-to-redirect",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+  }
+}
diff --git a/knative-build/runtimes/javascript/tests/webactionhttpredirect/payload-knative-init-run.http b/knative-build/runtimes/javascript/tests/webactionhttpredirect/payload-knative-init-run.http
new file mode 100644
index 0000000..c168d5c
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhttpredirect/payload-knative-init-run.http
@@ -0,0 +1,23 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name" : "nodejs-web-action-to-redirect",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() { return { headers: { location: 'http://openwhisk.org' }, statusCode: 302 }}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-to-redirect",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionhttpredirect/payload-openwhisk-init.http b/knative-build/runtimes/javascript/tests/webactionhttpredirect/payload-openwhisk-init.http
new file mode 100644
index 0000000..3ad7072
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhttpredirect/payload-openwhisk-init.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-web-action-to-redirect",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() { return { headers: { location: 'http://openwhisk.org' }, statusCode: 302 }}"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionhttpredirect/payload-openwhisk-run.http b/knative-build/runtimes/javascript/tests/webactionhttpredirect/payload-openwhisk-run.http
new file mode 100644
index 0000000..c23a82b
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionhttpredirect/payload-openwhisk-run.http
@@ -0,0 +1,15 @@
+POST http://localhost:8080/run HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+  },
+  "namespace": "default",
+  "action_name": "nodejs-web-action-to-redirect",
+  "api_host": "",
+  "api_key": "",
+  "activation_id": "",
+  "deadline": "4102498800000"
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionjsonparams/data-init-run.json b/knative-build/runtimes/javascript/tests/webactionjsonparams/data-init-run.json
new file mode 100644
index 0000000..5acbd62
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionjsonparams/data-init-run.json
@@ -0,0 +1,19 @@
+{
+  "init": {
+    "name" : "nodejs-web-action-json-params",
+    "main" : "main",
+    "binary": false,
+    "code": "function main(params) { return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: params }};"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-json-params",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe"
+  }
+}
diff --git a/knative-build/runtimes/javascript/tests/webactionjsonparams/payload-knative-init-run.http b/knative-build/runtimes/javascript/tests/webactionjsonparams/payload-knative-init-run.http
new file mode 100644
index 0000000..7edaa52
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionjsonparams/payload-knative-init-run.http
@@ -0,0 +1,24 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name" : "nodejs-web-action-json-params",
+    "main" : "main",
+    "binary": false,
+    "code": "function main(params) { return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: params }};"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-json-params",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionjsonparams/payload-openwhisk-init.http b/knative-build/runtimes/javascript/tests/webactionjsonparams/payload-openwhisk-init.http
new file mode 100644
index 0000000..c6b99c7
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionjsonparams/payload-openwhisk-init.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-web-action-json-params",
+    "main" : "main",
+    "binary": false,
+    "code": "function main(params) { return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: params }};",
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionjsonparams/payload-openwhisk-run.http b/knative-build/runtimes/javascript/tests/webactionjsonparams/payload-openwhisk-run.http
new file mode 100644
index 0000000..2182e24
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionjsonparams/payload-openwhisk-run.http
@@ -0,0 +1,16 @@
+POST http://localhost:8080/run HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "Joe"
+  },
+  "namespace": "default",
+  "action_name": "nodejs-web-action-json-params",
+  "api_host": "",
+  "api_key": "",
+  "activation_id": "",
+  "deadline": "4102498800000"
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionpng/data-init-run.json b/knative-build/runtimes/javascript/tests/webactionpng/data-init-run.json
new file mode 100644
index 0000000..d93f07b
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionpng/data-init-run.json
@@ -0,0 +1,19 @@
+{
+  "init": {
+    "name" : "nodejs-web-action-png",
+    "main" : "main",
+    "binary": false,
+    "code": "function main() {let png = 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAA/klEQVQYGWNgAAEHBxaG//+ZQMyyn581Pfas+cRQnf1LfFLjf+62smUgcUbt0FA2Zh7drf/ffMy9vLn3RurrW9e5hCU11i2azfD4zu1/DHz8TAy/foUxsXBrFzHzC7r8+M9S1vn1qxQT07dDjL9fdemrqKxlYGT6z8AIMo6hgeUfA0PUvy9fGFh5GWK3z7vNxSWt++jX99+8SoyiGQwsW38w8PJEM7x5v5SJ8f+/xv8MDAzffv9hevfkWjiXBGMpMx+j2awovjcMjFztDO8+7GF49LkbZDCDeXLTWnZO7qDfn1/+5jbw/8pjYWS4wZLztXnuEuYTk2M+MzIw/AcA36VewaD6fzsAAAAASUVORK5CYII='; return { headers: { 'Content-Type': 'image/png' }, statusCode: 200, body: png };}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-png",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe"
+  }
+}
diff --git a/knative-build/runtimes/javascript/tests/webactionpng/payload-knative-init-run.http b/knative-build/runtimes/javascript/tests/webactionpng/payload-knative-init-run.http
new file mode 100644
index 0000000..0ca3b3c
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionpng/payload-knative-init-run.http
@@ -0,0 +1,24 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name" : "nodejs-web-action-png",
+    "main" : "main",
+    "binary": false,
+    "code": "function main() {let png = 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAA/klEQVQYGWNgAAEHBxaG//+ZQMyyn581Pfas+cRQnf1LfFLjf+62smUgcUbt0FA2Zh7drf/ffMy9vLn3RurrW9e5hCU11i2azfD4zu1/DHz8TAy/foUxsXBrFzHzC7r8+M9S1vn1qxQT07dDjL9fdemrqKxlYGT6z8AIMo6hgeUfA0PUvy9fGFh5GWK3z7vNxSWt++jX99+8SoyiGQwsW38w8PJEM7x5v5SJ8f+/xv8MDAzffv9hevfkWjiXBGMpMx+j2awovjcMjFztDO8+7GF49LkbZDCDeXLTWnZO7qDfn1/+5jbw/8pjYWS4wZLztXnuEuYTk2M+MzIw/AcA36VewaD6fzsAAAAASUVORK5CYII='; return { headers: { 'Content-Type': 'image/png' }, statusCode: 200, body: png };}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-png",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+    "name": "Joe"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionpng/payload-openwhisk-init.http b/knative-build/runtimes/javascript/tests/webactionpng/payload-openwhisk-init.http
new file mode 100644
index 0000000..882002c
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionpng/payload-openwhisk-init.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "nodejs-web-action-png",
+    "main" : "main",
+    "binary": false,
+    "code": "function main() {let png = 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAGCAYAAAD68A/GAAAA/klEQVQYGWNgAAEHBxaG//+ZQMyyn581Pfas+cRQnf1LfFLjf+62smUgcUbt0FA2Zh7drf/ffMy9vLn3RurrW9e5hCU11i2azfD4zu1/DHz8TAy/foUxsXBrFzHzC7r8+M9S1vn1qxQT07dDjL9fdemrqKxlYGT6z8AIMo6hgeUfA0PUvy9fGFh5GWK3z7vNxSWt++jX99+8SoyiGQwsW38w8PJEM7x5v5SJ8f+/xv8MDAzffv9hevfkWjiXBGMpMx+j2awovjcMjFztDO8+7GF49LkbZDCDeXLTWnZO7qDfn1/+5jbw/8pjYWS4wZLztXnuEuYTk2M+MzIw/AcA36VewaD6fzsAAAAASUVORK5CYII='; return { headers: { 'Content-Type': 'image/png' }, statusCode: 200, body: png };}"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionpng/payload-openwhisk-run.http b/knative-build/runtimes/javascript/tests/webactionpng/payload-openwhisk-run.http
new file mode 100644
index 0000000..e95f889
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionpng/payload-openwhisk-run.http
@@ -0,0 +1,16 @@
+POST http://localhost:8080/run HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "name" : "Joe"
+  },
+  "namespace": "default",
+  "action_name": "nodejs-web-action-png",
+  "api_host": "",
+  "api_key": "",
+  "activation_id": "",
+  "deadline": "4102498800000"
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionsettingcookie/data-init-run.json b/knative-build/runtimes/javascript/tests/webactionsettingcookie/data-init-run.json
new file mode 100644
index 0000000..3f2beb9
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionsettingcookie/data-init-run.json
@@ -0,0 +1,18 @@
+{
+  "init": {
+    "name" : "nodejs-web-action-set-a-cookie",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() { return { headers: { 'Set-Cookie': 'UserID=Jane; Max-Age=3600; Version=', 'Content-Type': 'text/html'}, statusCode: 200, body: '<html><body><h3>hello</h3></body></html>' }}"
+},
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-set-a-cookie",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+  }
+}
diff --git a/knative-build/runtimes/javascript/tests/webactionsettingcookie/payload-knative-init-run.http b/knative-build/runtimes/javascript/tests/webactionsettingcookie/payload-knative-init-run.http
new file mode 100644
index 0000000..0531172
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionsettingcookie/payload-knative-init-run.http
@@ -0,0 +1,23 @@
+POST http://localhost:8080/ HTTP/1.1
+content-type: application/json
+
+{
+  "init": {
+    "name" : "nodejs-web-action-set-a-cookie",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() { return { headers: { 'Set-Cookie': 'UserID=Jane; Max-Age=3600; Version=', 'Content-Type': 'text/html'}, statusCode: 200, body: '<html><body><h3>hello</h3></body></html>' }}"
+  },
+  "activation": {
+    "namespace": "default",
+    "action_name": "nodejs-web-action-set-a-cookie",
+    "api_host": "",
+    "api_key": "",
+    "activation_id": "",
+    "deadline": "4102498800000"
+  },
+  "value": {
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionsettingcookie/payload-openwhisk-init.http b/knative-build/runtimes/javascript/tests/webactionsettingcookie/payload-openwhisk-init.http
new file mode 100644
index 0000000..b6e66bc
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionsettingcookie/payload-openwhisk-init.http
@@ -0,0 +1,13 @@
+POST http://localhost:8080/init HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+    "__ow_action_name" : "nodejs-web-action-set-a-cookie",
+    "main" : "main",
+    "binary": false,
+    "code" : "function main() { return { headers: { 'Set-Cookie': 'UserID=Jane; Max-Age=3600; Version=', 'Content-Type': 'text/html'}, statusCode: 200, body: '<html><body><h3>hello</h3></body></html>' }}"
+  }
+}
+
+###
diff --git a/knative-build/runtimes/javascript/tests/webactionsettingcookie/payload-openwhisk-run.http b/knative-build/runtimes/javascript/tests/webactionsettingcookie/payload-openwhisk-run.http
new file mode 100644
index 0000000..414888a
--- /dev/null
+++ b/knative-build/runtimes/javascript/tests/webactionsettingcookie/payload-openwhisk-run.http
@@ -0,0 +1,15 @@
+POST http://localhost:8080/run HTTP/1.1
+content-type: application/json
+
+{
+  "value": {
+  },
+  "namespace": "default",
+  "action_name": "nodejs-web-action-set-a-cookie",
+  "api_host": "",
+  "api_key": "",
+  "activation_id": "",
+  "deadline": "4102498800000"
+}
+
+###
diff --git a/knative-build/runtimes/javascript/utils/dbgstrip.sh b/knative-build/runtimes/javascript/utils/dbgstrip.sh
new file mode 100755
index 0000000..2ac7130
--- /dev/null
+++ b/knative-build/runtimes/javascript/utils/dbgstrip.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+USAGE="dbgstrip <SOURCE_FILE> <TARGET_FILE>"
+
+if [ $1 ]
+then
+    : # $1 was given
+    SOURCE=$1
+
+    if [ $2 ]
+    then
+        :
+        TARGET=$2
+    else
+        echo $USAGE
+        exit 2
+    fi
+
+    echo "SOURCE=[$SOURCE]"
+    echo "TARGET=[$TARGET]"
+
+    # FIRST: remove the package itself THEN: remove all lines that
+    # dereference the package (i.e., named DEBUG by convention)
+    sed '\|utils/debug|d' $SOURCE | sed '/DEBUG/d' > $TARGET
+
+else
+    echo $USAGE
+    exit 1
+fi
diff --git a/knative-build/runtimes/javascript/utils/debug.js b/knative-build/runtimes/javascript/utils/debug.js
new file mode 100644
index 0000000..b1f3262
--- /dev/null
+++ b/knative-build/runtimes/javascript/utils/debug.js
@@ -0,0 +1,371 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+const FG_RED     = "\x1b[31m";
+const FG_GREEN   = "\x1b[32m";
+const FG_YELLOW  = "\x1b[33m";
+//const FG_BLUE    = "\x1b[34m";
+const FG_MAGENTA = "\x1b[35m";
+const FG_CYAN    = "\x1b[36m";
+const FG_LTGRAY  = "\x1b[37m";
+//const FG_WHITE   = "\x1b[97m";
+
+const FG_SUCCESS = FG_GREEN;
+const FG_INFO    = FG_LTGRAY;
+const FG_WARN    = FG_YELLOW;
+const FG_ERROR   = FG_RED;
+
+let config = {
+  prefixFGColor: FG_CYAN,
+  postfixFGColor: FG_MAGENTA,
+  bodyFGColor: FG_INFO,
+  defaultFGColor: FG_INFO,
+  functionStartMarker: ">>> START: ",
+  functionEndMarker: "<<< END: "
+};
+
+/**
+ * Formats Hours, Minutes, Seconds and Milliseconds as HH:MM:SS:mm
+ */
+function _getTimeFormatted(){
+  let date = new Date();
+  let ftime =
+      date.getHours() + ":" +
+      date.getMinutes() + ":" +
+      date.getSeconds() + ":" +
+      date.getMilliseconds();
+  return ftime;
+}
+
+/**
+ * Formats and colorizes the prefix of the message consisting of the:
+ *   [moduleName] [functionName]()
+ *
+ * @param color optional (non-default) color for the prefix string
+ */
+function _formatMessagePrefix(color){
+
+  let prefixColor = config.prefixFGColor;
+
+  // If color arg is defined, use it
+  // TODO: validate color is an actual valid color string
+  if( color !== undefined) {
+    prefixColor = color;
+  }
+
+  let prefix = prefixColor + "[" + this.moduleInfo + "]["+this.functionName+"()] ";
+
+  return prefix;
+
+}
+
+/**
+ * Formats and colorizes the postfix of the message consisting of the:
+ *   [(formattedTime)]
+ *
+ * @param color optional (non-default) color for the prefix string
+ */
+function _formatMessagePostfix(color){
+
+  let postfixColor = config.postfixFGColor;
+
+  // If color arg is defined, use it
+  // TODO: validate color is an actual valid color string
+  if( color !== undefined) {
+    postfixColor = color;
+  }
+  let postfix = postfixColor + " (" + _getTimeFormatted() + ")";
+  return postfix;
+}
+
+/**
+ * Formats and colorizes the body of the message.
+ *
+ * @param message that comprises the body of the output
+ * @param color optional (non-default) color for the prefix string
+ */
+function _formatBody(message, color){
+
+  let bodyColor = config.bodyFGColor;
+
+  // If color arg is defined, use it
+  // TODO: validate color is an actual valid color string
+  if(color !== undefined)
+    bodyColor = color;
+
+  return bodyColor + message + config.bodyFGColor;
+}
+
+/**
+ * Formats the entirety of the message comprised of the message prefix + body + postfix.
+ *
+ * @param message that comprises the body of the formatted output
+ * @param label (optional) labels (identifies) code block where message was generated;
+ * typically used to identify anon. functions.
+ * @param color overrides the default message color (this does not affect prefix or postfix)
+ */
+function _formatMessage(message, color ){
+  // Reset to default color at end of formatted message
+  let fmsg =
+      _formatMessagePrefix() +
+      _formatBody(message, color) +
+      _formatMessagePostfix() +
+      config.defaultFGColor;
+  return fmsg;
+}
+
+/**
+ * Initialize the debug context including:
+ * - Calling module
+ * - Calling function (if anonymous, identify by signature)
+ *
+ * @param callerFunctionLabel (optional) label for the function (or block) to use
+ *  instead of pulling from the call stack.  Typically used to identify anon. functions
+ *  or blocks
+ */
+function _updateContext(callerFunctionLabel){
+
+  try{
+    let obj = {};
+
+    Error.stackTraceLimit = 2;
+    Error.captureStackTrace(obj, _updateContext);
+
+    let fullStackInfo = obj.stack.split(")\n");
+    let rawFunctionInfo = fullStackInfo[1];
+    let entryInfo = rawFunctionInfo.split("at ")[1];
+
+    // TODO: if there is no '(' separator, we have no function name; do not split
+    // e.g., value would look like: /openwhisk-knative-build/runtimes/javascript/src/service.js:180:19
+    if( entryInfo.indexOf(" (") !== -1) {
+      let fm = entryInfo.split(" (");
+      this.functionName = fm[0];
+      this.fullModuleInfo = fm[1];
+    } else {
+      // assume the entry has the full module name and path (function is a anonymous)
+      this.functionName = "anonymous";
+      this.fullModuleInfo = entryInfo;
+    }
+
+    if( typeof this.fullModuleInfo !== "undefined")
+      this.moduleInfo =  this.fullModuleInfo.substring( this.fullModuleInfo.lastIndexOf("/") +1);
+
+    // if explicit label provided, use it over one from stack trace...
+    if(typeof(callerFunctionLabel) !== 'undefined') {
+
+      if( this.functionName === "anonymous")
+        this.functionName = callerFunctionLabel + "." + this.functionName;
+      else
+        this.functionName = callerFunctionLabel;
+    }
+
+  } catch(e){
+    console.error("Unable to parse stack trace: " + e.message);
+  }
+
+}
+
+module.exports = class DEBUG {
+
+  constructor() {
+    // Empty constructor
+  }
+
+  /**
+   * Used to mark the start of a function block (i.e., via console.info())
+   *
+   * @param message (optional) message displayed with function start marker
+   * @param functionName (optional) name of the function; typically used to better
+   * identify anon. functions.
+   */
+  functionStart(message, functionName) {
+
+    _updateContext(functionName);
+
+    let msg = "";
+    if(message !== undefined){
+      msg = message;
+    }
+
+    let formattedMessage = _formatMessage( config.functionStartMarker + msg );
+    console.info(formattedMessage);
+  };
+
+  /**
+   * Used to mark the end of a function block (i.e., console.info())
+   *
+   * @param message (optional) message displayed with function end marker
+   * @param functionName (optional) name of the function; typically used to better
+   * identify anon. functions.
+   */
+   functionEnd(message, functionName) {
+
+    _updateContext(functionName);
+
+    let msg = "";
+    if(message !== undefined){
+      msg = message;
+    }
+
+    let formattedMessage = _formatMessage( config.functionEndMarker + msg);
+    console.info(formattedMessage);
+  };
+
+  /**
+   * Used to mark the end of a successful function block (i.e., console.info())
+   *
+   * @param message (optional) message displayed with function end marker
+   * @param functionName (optional) name of the function; typically used to better
+   * identify anon. functions.
+   */
+  functionEndSuccess(message, functionName) {
+
+    _updateContext(functionName);
+
+    let msg = "";
+    if(message !== undefined){
+      msg = message;
+    }
+
+    let formattedMessage = _formatMessage( config.functionEndMarker + msg, FG_SUCCESS );
+    console.info(formattedMessage);
+  };
+
+
+  /**
+   * Used to mark the end function block that has failed, but did not result in
+   * an error to the user.  In other words, a soft-failure that was recoverable.
+   *
+   * @param message (optional) message displayed with function end marker
+   * @param functionName (optional) name of the function; typically used to better
+   * identify anon. functions.
+   */
+  functionEndFailure(message, functionName) {
+
+    _updateContext(functionName);
+
+    let msg = "";
+    if(message !== undefined){
+      msg = message;
+    }
+
+    let formattedMessage = _formatMessage( config.functionEndMarker + msg, FG_WARN );
+    console.info(formattedMessage);
+  };
+
+  /**
+   * Used to mark the end function block that errored
+   *
+   * @param message (optional) message displayed with function end marker
+   * @param functionName (optional) name of the function; typically used to better
+   * identify anon. functions.
+   */
+  functionEndError(message, functionName) {
+
+    _updateContext(functionName);
+
+    let msg = "";
+    if(message !== undefined){
+      msg = message;
+    }
+
+    let formattedMessage = _formatMessage( config.functionEndMarker + msg, FG_ERROR );
+    console.info(formattedMessage);
+  };
+
+  /**
+   * Used to output informational message strings (i.e., console.info())
+   *
+   * @param msg message to display to console as trace information
+   * @param functionName (optional) name of the function; typically used to better
+   * identify anon. functions.
+   */
+   trace(message, functionName) {
+
+    _updateContext(functionName);
+
+    let formattedMessage = _formatMessage(message);
+    console.info(formattedMessage);
+  };
+
+  /**
+   * Used to output the type and contents of Javascript types and Objects
+   * This method attempts to display Objects as JSON strings where cyclical references
+   * do not occur; otherwise, it attempts to display a 1st level (shallow) contents
+   * of the Object.
+   *
+   * @param obj Javascript Object (or type) to dump its information to console.info()
+   * @param label optional string label to display with object dump
+   * @param functionName (optional) name of the function; typically used to better
+   * identify anon. functions.
+   */
+   dumpObject(obj, label, functionName){
+
+    _updateContext(functionName);
+
+    let otype = typeof(obj);
+
+    if( otype !== "undefined") {
+
+      try{
+        let jsonFormatted = JSON.stringify(obj,null,4);
+        let formattedMessage = _formatMessage("[" + label + " (" + otype + ")] = "+ jsonFormatted);
+        console.info(formattedMessage);
+      } catch (e) {
+
+        // try manually dumping a shallow (string-friendly) copy of the Object
+        try {
+          console.log("{");
+          Object.keys(obj).forEach(
+              function (key) {
+                if(typeof obj[key] === 'string' && typeof(obj[key].toString()) !== "undefined"){
+                  console.info("    \"" + key + "\": \"" + obj[key].toString() +"\"" );
+                }
+              }
+          );
+          console.log("}");
+
+        } catch(e2) {
+          console.error("[" + label + " (" + otype + ")] : " + e2.message);
+          let formattedMessage = _formatMessage(_ + "[" + label + " (" + otype + ")] : " +
+              e2.message);
+          console.error(formattedMessage);
+        }
+      }
+
+    } else {
+      let formattedMessage = _formatMessage( FG_YELLOW + "[" + label + " (" + otype + ")] is undefined." + FG_LTGRAY);
+      console.info(formattedMessage);
+    }
+
+  }
+
+  /**
+   * Inject an error into code with additional information around it.
+   *
+   * @param label Javascript Object (or type) to dump its information to console.info()
+   */
+  throw(label) {
+
+    _updateContext(label);
+
+    // intentionally throw an error and be clear to log this fact
+    let formattedMessage = _formatMessage(FG_RED + "[" + label + "] Intentionally throwing error." + FG_LTGRAY);
+    throw Error(formattedMessage);
+  }
+
+};
diff --git a/knative-build/samples/helloworld-nodejs/Dockerfile b/knative-build/samples/helloworld-nodejs/Dockerfile
new file mode 100644
index 0000000..91cf874
--- /dev/null
+++ b/knative-build/samples/helloworld-nodejs/Dockerfile
@@ -0,0 +1,24 @@
+# Use the official Node.js 10 image.
+# https://hub.docker.com/_/node
+FROM node:10
+
+# Create and change to the app directory.
+WORKDIR /usr/src/app
+
+# Copy application dependency manifests to the container image.
+# A wildcard is used to ensure both package.json AND package-lock.json are copied.
+# Copying this separately prevents re-running npm install on every code change.
+COPY package*.json ./
+
+# Install production dependencies.
+RUN npm install --only=production
+
+# Copy local code to the container image.
+COPY . .
+
+# Service must listen to $PORT environment variable.
+# This default value facilitates local development.
+ENV PORT 8080
+
+# Run the web service on container startup.
+CMD [ "npm", "start" ]
diff --git a/knative-build/samples/helloworld-nodejs/Readme.md b/knative-build/samples/helloworld-nodejs/Readme.md
new file mode 100644
index 0000000..ee2bafe
--- /dev/null
+++ b/knative-build/samples/helloworld-nodejs/Readme.md
@@ -0,0 +1,61 @@
+## Step 1: Install npm package `express`
+
+```bash
+$ npm install express --save
+``` 
+
+## Build Docker Image
+
+Build the container on your local machine with:
+
+```bash
+$ docker build -t {DOCKER_USERNAME}/helloworld-nodejs
+```
+
+## Push the container to docker registry
+
+```bash
+$ docker push {DOCKER_USERNAME}/helloworld-nodejs
+```
+
+## Apply Service YAML
+
+```bash
+$ kubectl apply -f service.yaml
+```
+
+## Access the service
+
+```bash
+curl -H "Host: helloworld-nodejs.default.example.com" http://localhost
+Hello Node.js Sample v1!
+```
+
+## Logging
+
+```bash
+$ kubectl get pods helloworld-nodejs-00001-deployment-69f788f64b-gtvrc -o yaml
+``` 
+
+```bash
+$ kubectl logs helloworld-nodejs-00001-deployment-69f788f64b-gtvrc --all-containers=true
+```
+
+```bash
+$ kubectl logs helloworld-nodejs-00001-deployment-69f788f64b-gtvrc -c user-container
+
+> knative-serving-helloworld-nodejs@1.0.0 start /usr/src/app
+> node app.js
+
+Hello world listening on port 8080
+Hello world received a request.
+```
+
+```bash
+$ kubectl logs helloworld-nodejs-00001-deployment-69f788f64b-gtvrc -c istio-init
+```
+
+```bash
+kubectl exec nodejs-10-action-00003-deployment-65cd48975b-kbkwj -c istio-proxy -- ls -l 
+kubectl exec nodejs-10-action-00003-deployment-65cd48975b-kbkwj -c istio-proxy -- curl localhost:8080
+```
diff --git a/knative-build/samples/helloworld-nodejs/app.js b/knative-build/samples/helloworld-nodejs/app.js
new file mode 100644
index 0000000..c63c272
--- /dev/null
+++ b/knative-build/samples/helloworld-nodejs/app.js
@@ -0,0 +1,14 @@
+const express = require("express");
+const app = express();
+
+app.get("/", (req, res) => {
+  console.log("Hello world received a request.");
+
+  const target = process.env.TARGET || "World";
+  res.send(`Hello ${target}!`);
+});
+
+const port = process.env.PORT || 8080;
+app.listen(port, () => {
+  console.log("Hello world listening on port", port);
+});
diff --git a/knative-build/samples/helloworld-nodejs/package-lock.json b/knative-build/samples/helloworld-nodejs/package-lock.json
new file mode 100644
index 0000000..06f681a
--- /dev/null
+++ b/knative-build/samples/helloworld-nodejs/package-lock.json
@@ -0,0 +1,358 @@
+{
+  "name": "helloworld-nodejs",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "accepts": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz",
+      "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=",
+      "requires": {
+        "mime-types": "2.1.21",
+        "negotiator": "0.6.1"
+      }
+    },
+    "array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+    },
+    "body-parser": {
+      "version": "1.18.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz",
+      "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=",
+      "requires": {
+        "bytes": "3.0.0",
+        "content-type": "1.0.4",
+        "debug": "2.6.9",
+        "depd": "1.1.2",
+        "http-errors": "1.6.3",
+        "iconv-lite": "0.4.23",
+        "on-finished": "2.3.0",
+        "qs": "6.5.2",
+        "raw-body": "2.3.3",
+        "type-is": "1.6.16"
+      }
+    },
+    "bytes": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+      "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg="
+    },
+    "content-disposition": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+      "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ="
+    },
+    "content-type": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+      "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
+    },
+    "cookie": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
+      "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
+    },
+    "cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+    },
+    "debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "requires": {
+        "ms": "2.0.0"
+      }
+    },
+    "depd": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+      "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak="
+    },
+    "destroy": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+      "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+    },
+    "ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+    },
+    "encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
+    },
+    "escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+    },
+    "etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
+    },
+    "express": {
+      "version": "4.16.4",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz",
+      "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==",
+      "requires": {
+        "accepts": "1.3.5",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.18.3",
+        "content-disposition": "0.5.2",
+        "content-type": "1.0.4",
+        "cookie": "0.3.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "1.1.2",
+        "encodeurl": "1.0.2",
+        "escape-html": "1.0.3",
+        "etag": "1.8.1",
+        "finalhandler": "1.1.1",
+        "fresh": "0.5.2",
+        "merge-descriptors": "1.0.1",
+        "methods": "1.1.2",
+        "on-finished": "2.3.0",
+        "parseurl": "1.3.2",
+        "path-to-regexp": "0.1.7",
+        "proxy-addr": "2.0.4",
+        "qs": "6.5.2",
+        "range-parser": "1.2.0",
+        "safe-buffer": "5.1.2",
+        "send": "0.16.2",
+        "serve-static": "1.13.2",
+        "setprototypeof": "1.1.0",
+        "statuses": "1.4.0",
+        "type-is": "1.6.16",
+        "utils-merge": "1.0.1",
+        "vary": "1.1.2"
+      }
+    },
+    "finalhandler": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz",
+      "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==",
+      "requires": {
+        "debug": "2.6.9",
+        "encodeurl": "1.0.2",
+        "escape-html": "1.0.3",
+        "on-finished": "2.3.0",
+        "parseurl": "1.3.2",
+        "statuses": "1.4.0",
+        "unpipe": "1.0.0"
+      }
+    },
+    "forwarded": {
+      "version": "0.1.2",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
+      "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ="
+    },
+    "fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
+    },
+    "http-errors": {
+      "version": "1.6.3",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz",
+      "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=",
+      "requires": {
+        "depd": "1.1.2",
+        "inherits": "2.0.3",
+        "setprototypeof": "1.1.0",
+        "statuses": "1.4.0"
+      }
+    },
+    "iconv-lite": {
+      "version": "0.4.23",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
+      "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
+      "requires": {
+        "safer-buffer": "2.1.2"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+    },
+    "ipaddr.js": {
+      "version": "1.8.0",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz",
+      "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4="
+    },
+    "media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g="
+    },
+    "merge-descriptors": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+      "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+    },
+    "methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
+    },
+    "mime": {
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
+      "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ=="
+    },
+    "mime-db": {
+      "version": "1.37.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
+      "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
+    },
+    "mime-types": {
+      "version": "2.1.21",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
+      "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
+      "requires": {
+        "mime-db": "1.37.0"
+      }
+    },
+    "ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+    },
+    "negotiator": {
+      "version": "0.6.1",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
+      "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
+    },
+    "on-finished": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+      "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+      "requires": {
+        "ee-first": "1.1.1"
+      }
+    },
+    "parseurl": {
+      "version": "1.3.2",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
+      "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
+    },
+    "path-to-regexp": {
+      "version": "0.1.7",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+      "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+    },
+    "proxy-addr": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
+      "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==",
+      "requires": {
+        "forwarded": "0.1.2",
+        "ipaddr.js": "1.8.0"
+      }
+    },
+    "qs": {
+      "version": "6.5.2",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+      "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+    },
+    "range-parser": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+      "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4="
+    },
+    "raw-body": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz",
+      "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==",
+      "requires": {
+        "bytes": "3.0.0",
+        "http-errors": "1.6.3",
+        "iconv-lite": "0.4.23",
+        "unpipe": "1.0.0"
+      }
+    },
+    "safe-buffer": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+    },
+    "safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+    },
+    "send": {
+      "version": "0.16.2",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz",
+      "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==",
+      "requires": {
+        "debug": "2.6.9",
+        "depd": "1.1.2",
+        "destroy": "1.0.4",
+        "encodeurl": "1.0.2",
+        "escape-html": "1.0.3",
+        "etag": "1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "1.6.3",
+        "mime": "1.4.1",
+        "ms": "2.0.0",
+        "on-finished": "2.3.0",
+        "range-parser": "1.2.0",
+        "statuses": "1.4.0"
+      }
+    },
+    "serve-static": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz",
+      "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==",
+      "requires": {
+        "encodeurl": "1.0.2",
+        "escape-html": "1.0.3",
+        "parseurl": "1.3.2",
+        "send": "0.16.2"
+      }
+    },
+    "setprototypeof": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
+      "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ=="
+    },
+    "statuses": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz",
+      "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew=="
+    },
+    "type-is": {
+      "version": "1.6.16",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
+      "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==",
+      "requires": {
+        "media-typer": "0.3.0",
+        "mime-types": "2.1.21"
+      }
+    },
+    "unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
+    },
+    "utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
+    },
+    "vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw="
+    }
+  }
+}
diff --git a/knative-build/samples/helloworld-nodejs/package.json b/knative-build/samples/helloworld-nodejs/package.json
new file mode 100644
index 0000000..5c96c24
--- /dev/null
+++ b/knative-build/samples/helloworld-nodejs/package.json
@@ -0,0 +1,14 @@
+{
+  "name": "knative-serving-helloworld-nodejs",
+  "version": "1.0.0",
+  "description": "",
+  "main": "app.js",
+  "scripts": {
+    "start": "node app.js"
+  },
+  "author": "",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "express": "^4.16.4"
+  }
+}
diff --git a/knative-build/samples/helloworld-nodejs/service.yaml b/knative-build/samples/helloworld-nodejs/service.yaml
new file mode 100644
index 0000000..804647d
--- /dev/null
+++ b/knative-build/samples/helloworld-nodejs/service.yaml
@@ -0,0 +1,15 @@
+apiVersion: serving.knative.dev/v1alpha1
+kind: Service
+metadata:
+  name: helloworld-nodejs
+  namespace: default
+spec:
+  runLatest:
+    configuration:
+      revisionTemplate:
+        spec:
+          container:
+            image: docker.io/{DOCKER_USERNAME}/helloworld-nodejs
+            env:
+              - name: TARGET
+                value: "Node.js Sample v1"
diff --git a/knative-build/samples/nodejs-runtime/Readme.md b/knative-build/samples/nodejs-runtime/Readme.md
new file mode 100644
index 0000000..3088dc3
--- /dev/null
+++ b/knative-build/samples/nodejs-runtime/Readme.md
@@ -0,0 +1,470 @@
+```bash
+kubectl apply -f service.yaml
+service.serving.knative.dev/nodejs-10-runtime created
+```
+
+```bash
+kubectl get pods
+NAME                                                  READY   STATUS    RESTARTS   AGE
+nodejs-10-runtime-00001-deployment-75744dbfd7-j5snf   2/3     Running   3          68s
+```
+
+```bash
+kubectl get pods nodejs-10-runtime-00001-deployment-75744dbfd7-j5snf -o yaml
+apiVersion: v1
+kind: Pod
+metadata:
+  annotations:
+    sidecar.istio.io/inject: "true"
+    sidecar.istio.io/status: '{"version":"2153b4a1c36b2db7abd8141cba9723db658c4a56673d25af8d1d18641270f3a2","initContainers":["istio-init"],"containers":["istio-proxy"],"volumes":["istio-envoy","istio-certs"],"imagePullSecrets":null}'
+    traffic.sidecar.istio.io/includeOutboundIPRanges: '*'
+  creationTimestamp: "2019-01-28T19:29:00Z"
+  generateName: nodejs-10-runtime-00001-deployment-75744dbfd7-
+  labels:
+    app: nodejs-10-runtime-00001
+    pod-template-hash: 75744dbfd7
+    serving.knative.dev/configuration: nodejs-10-runtime
+    serving.knative.dev/configurationGeneration: "1"
+    serving.knative.dev/configurationMetadataGeneration: "1"
+    serving.knative.dev/revision: nodejs-10-runtime-00001
+    serving.knative.dev/revisionUID: f4abe4d0-2332-11e9-8abf-025000000001
+    serving.knative.dev/service: nodejs-10-runtime
+  name: nodejs-10-runtime-00001-deployment-75744dbfd7-j5snf
+  namespace: default
+  ownerReferences:
+  - apiVersion: apps/v1
+    blockOwnerDeletion: true
+    controller: true
+    kind: ReplicaSet
+    name: nodejs-10-runtime-00001-deployment-75744dbfd7
+    uid: f6985495-2332-11e9-8abf-025000000001
+  resourceVersion: "69700"
+  selfLink: /api/v1/namespaces/default/pods/nodejs-10-runtime-00001-deployment-75744dbfd7-j5snf
+  uid: f69e3e29-2332-11e9-8abf-025000000001
+spec:
+  containers:
+  - env:
+    - name: TARGET
+      value: Node.js Sample v1
+    - name: PORT
+      value: "8080"
+    - name: K_REVISION
+      value: nodejs-10-runtime-00001
+    - name: K_CONFIGURATION
+      value: nodejs-10-runtime
+    - name: K_SERVICE
+      value: nodejs-10-runtime
+    image: index.docker.io/openwhisk/nodejsactionbase@sha256:c0e903ada3dcf0f3755a8dd1e4efd41a559fd1f6d18c1880e6a241df251aa8b9
+    imagePullPolicy: IfNotPresent
+    lifecycle:
+      preStop:
+        httpGet:
+          path: quitquitquit
+          port: 8022
+          scheme: HTTP
+    name: user-container
+    ports:
+    - containerPort: 8080
+      name: user-port
+      protocol: TCP
+    resources:
+      requests:
+        cpu: 400m
+    terminationMessagePath: /dev/termination-log
+    terminationMessagePolicy: FallbackToLogsOnError
+    volumeMounts:
+    - mountPath: /var/log
+      name: varlog
+    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
+      name: default-token-w8j46
+      readOnly: true
+  - env:
+    - name: SERVING_NAMESPACE
+      value: default
+    - name: SERVING_CONFIGURATION
+      value: nodejs-10-runtime
+    - name: SERVING_REVISION
+      value: nodejs-10-runtime-00001
+    - name: SERVING_AUTOSCALER
+      value: autoscaler
+    - name: SERVING_AUTOSCALER_PORT
+      value: "8080"
+    - name: CONTAINER_CONCURRENCY
+      value: "0"
+    - name: REVISION_TIMEOUT_SECONDS
+      value: "300"
+    - name: SERVING_POD
+      valueFrom:
+        fieldRef:
+          apiVersion: v1
+          fieldPath: metadata.name
+    - name: SERVING_LOGGING_CONFIG
+      value: |
+        {
+          "level": "info",
+          "development": false,
+          "outputPaths": ["stdout"],
+          "errorOutputPaths": ["stderr"],
+          "encoding": "json",
+          "encoderConfig": {
+            "timeKey": "ts",
+            "levelKey": "level",
+            "nameKey": "logger",
+            "callerKey": "caller",
+            "messageKey": "msg",
+            "stacktraceKey": "stacktrace",
+            "lineEnding": "",
+            "levelEncoder": "",
+            "timeEncoder": "iso8601",
+            "durationEncoder": "",
+            "callerEncoder": ""
+          }
+        }
+    - name: SERVING_LOGGING_LEVEL
+      value: info
+    - name: USER_PORT
+      value: "8080"
+    image: gcr.io/knative-releases/github.com/knative/serving/cmd/queue@sha256:fc49125cb29f7bb2de2c4d6bd51153ce190cb522cf42df59898147d2074885cc
+    imagePullPolicy: IfNotPresent
+    lifecycle:
+      preStop:
+        httpGet:
+          path: quitquitquit
+          port: 8022
+          scheme: HTTP
+    name: queue-proxy
+    ports:
+    - containerPort: 8012
+      name: queue-port
+      protocol: TCP
+    - containerPort: 8022
+      name: queueadm-port
+      protocol: TCP
+    - containerPort: 9090
+      name: queue-metrics
+      protocol: TCP
+    readinessProbe:
+      failureThreshold: 3
+      httpGet:
+        path: health
+        port: 8022
+        scheme: HTTP
+      periodSeconds: 1
+      successThreshold: 1
+      timeoutSeconds: 1
+    resources:
+      requests:
+        cpu: 25m
+    terminationMessagePath: /dev/termination-log
+    terminationMessagePolicy: File
+    volumeMounts:
+    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
+      name: default-token-w8j46
+      readOnly: true
+  - args:
+    - proxy
+    - sidecar
+    - --configPath
+    - /etc/istio/proxy
+    - --binaryPath
+    - /usr/local/bin/envoy
+    - --serviceCluster
+    - nodejs-10-runtime-00001
+    - --drainDuration
+    - 45s
+    - --parentShutdownDuration
+    - 1m0s
+    - --discoveryAddress
+    - istio-pilot.istio-system:15007
+    - --discoveryRefreshDelay
+    - 1s
+    - --zipkinAddress
+    - zipkin.istio-system:9411
+    - --connectTimeout
+    - 10s
+    - --statsdUdpAddress
+    - istio-statsd-prom-bridge.istio-system:9125
+    - --proxyAdminPort
+    - "15000"
+    - --controlPlaneAuthPolicy
+    - NONE
+    env:
+    - name: POD_NAME
+      valueFrom:
+        fieldRef:
+          apiVersion: v1
+          fieldPath: metadata.name
+    - name: POD_NAMESPACE
+      valueFrom:
+        fieldRef:
+          apiVersion: v1
+          fieldPath: metadata.namespace
+    - name: INSTANCE_IP
+      valueFrom:
+        fieldRef:
+          apiVersion: v1
+          fieldPath: status.podIP
+    - name: ISTIO_META_POD_NAME
+      valueFrom:
+        fieldRef:
+          apiVersion: v1
+          fieldPath: metadata.name
+    - name: ISTIO_META_INTERCEPTION_MODE
+      value: REDIRECT
+    image: docker.io/istio/proxyv2:1.0.2
+    imagePullPolicy: IfNotPresent
+    lifecycle:
+      preStop:
+        exec:
+          command:
+          - sh
+          - -c
+          - sleep 20; until curl -s localhost:15000/clusters | grep "inbound|80|"
+            | grep "rq_active" | grep "rq_active::0"; do sleep 1; done;
+    name: istio-proxy
+    resources:
+      requests:
+        cpu: 10m
+    securityContext:
+      procMount: Default
+      readOnlyRootFilesystem: true
+      runAsUser: 1337
+    terminationMessagePath: /dev/termination-log
+    terminationMessagePolicy: File
+    volumeMounts:
+    - mountPath: /etc/istio/proxy
+      name: istio-envoy
+    - mountPath: /etc/certs/
+      name: istio-certs
+      readOnly: true
+  dnsPolicy: ClusterFirst
+  enableServiceLinks: true
+  initContainers:
+  - args:
+    - -p
+    - "15001"
+    - -u
+    - "1337"
+    - -m
+    - REDIRECT
+    - -i
+    - '*'
+    - -x
+    - ""
+    - -b
+    - 8080, 8012, 8022, 9090,
+    - -d
+    - ""
+    image: docker.io/istio/proxy_init:1.0.2
+    imagePullPolicy: IfNotPresent
+    name: istio-init
+    resources: {}
+    securityContext:
+      capabilities:
+        add:
+        - NET_ADMIN
+      procMount: Default
+    terminationMessagePath: /dev/termination-log
+    terminationMessagePolicy: File
+  nodeName: docker-desktop
+  priority: 0
+  restartPolicy: Always
+  schedulerName: default-scheduler
+  securityContext: {}
+  serviceAccount: default
+  serviceAccountName: default
+  terminationGracePeriodSeconds: 300
+  tolerations:
+  - effect: NoExecute
+    key: node.kubernetes.io/not-ready
+    operator: Exists
+    tolerationSeconds: 300
+  - effect: NoExecute
+    key: node.kubernetes.io/unreachable
+    operator: Exists
+    tolerationSeconds: 300
+  volumes:
+  - emptyDir: {}
+    name: varlog
+  - name: default-token-w8j46
+    secret:
+      defaultMode: 420
+      secretName: default-token-w8j46
+  - emptyDir:
+      medium: Memory
+    name: istio-envoy
+  - name: istio-certs
+    secret:
+      defaultMode: 420
+      optional: true
+      secretName: istio.default
+status:
+  conditions:
+  - lastProbeTime: null
+    lastTransitionTime: "2019-01-28T19:29:02Z"
+    status: "True"
+    type: Initialized
+  - lastProbeTime: null
+    lastTransitionTime: "2019-01-28T19:29:26Z"
+    message: 'containers with unready status: [user-container]'
+    reason: ContainersNotReady
+    status: "False"
+    type: Ready
+  - lastProbeTime: null
+    lastTransitionTime: "2019-01-28T19:29:26Z"
+    message: 'containers with unready status: [user-container]'
+    reason: ContainersNotReady
+    status: "False"
+    type: ContainersReady
+  - lastProbeTime: null
+    lastTransitionTime: "2019-01-28T19:29:00Z"
+    status: "True"
+    type: PodScheduled
+  containerStatuses:
+  - containerID: docker://c2b5f9e6ac2f52576a4519161fb4b5a34ef302568637da94d3a227e069acce00
+    image: istio/proxyv2:1.0.2
+    imageID: docker-pullable://istio/proxyv2@sha256:54e206530ba6ca9b3820254454e01b7592e9f986d27a5640b6c03704b3b68332
+    lastState: {}
+    name: istio-proxy
+    ready: true
+    restartCount: 0
+    state:
+      running:
+        startedAt: "2019-01-28T19:29:10Z"
+  - containerID: docker://8e5238c7765aedc89522f9441fff365c0a879f2a0338be970cac5c8cf44df036
+    image: sha256:6cb5d12d6ec5e0f951a54fa344c9646fbb96287fb6b0129388b75a6342b67157
+    imageID: docker-pullable://gcr.io/knative-releases/github.com/knative/serving/cmd/queue@sha256:fc49125cb29f7bb2de2c4d6bd51153ce190cb522cf42df59898147d2074885cc
+    lastState: {}
+    name: queue-proxy
+    ready: true
+    restartCount: 0
+    state:
+      running:
+        startedAt: "2019-01-28T19:29:09Z"
+  - containerID: docker://4ded5fe0044998cb163526317c595804a66e7369a3f836a01ddeb2149b14b6a7
+    image: sha256:e3cf12050d36bb6d672fb3c15ed98883374feb747fdbe66dbcb33306d0a6a9d9
+    imageID: docker-pullable://openwhisk/nodejsactionbase@sha256:c0e903ada3dcf0f3755a8dd1e4efd41a559fd1f6d18c1880e6a241df251aa8b9
+    lastState:
+      terminated:
+        containerID: docker://17a0d9871afa5570bcfc6a68e8a25313bc97e16ccf9711ffbfadaa943e74faab
+        exitCode: 0
+        finishedAt: "2019-01-28T19:29:51Z"
+        reason: Completed
+        startedAt: "2019-01-28T19:29:51Z"
+    name: user-container
+    ready: false
+    restartCount: 4
+    state:
+      terminated:
+        containerID: docker://4ded5fe0044998cb163526317c595804a66e7369a3f836a01ddeb2149b14b6a7
+        exitCode: 0
+        finishedAt: "2019-01-28T19:30:34Z"
+        reason: Completed
+        startedAt: "2019-01-28T19:30:34Z"
+  hostIP: 192.168.65.3
+  initContainerStatuses:
+  - containerID: docker://cb386be55ca7741b0631534f95d2860917f9fc88e7f3c2e166f4450a9ee8c464
+    image: istio/proxy_init:1.0.2
+    imageID: docker-pullable://istio/proxy_init@sha256:e16a0746f46cd45a9f63c27b9e09daff5432e33a2d80c8cc0956d7d63e2f9185
+    lastState: {}
+    name: istio-init
+    ready: true
+    restartCount: 0
+    state:
+      terminated:
+        containerID: docker://cb386be55ca7741b0631534f95d2860917f9fc88e7f3c2e166f4450a9ee8c464
+        exitCode: 0
+        finishedAt: "2019-01-28T19:29:01Z"
+        reason: Completed
+        startedAt: "2019-01-28T19:29:01Z"
+  phase: Running
+  podIP: 10.1.0.119
+  qosClass: Burstable
+  startTime: "2019-01-28T19:29:00Z"
+```
+  
+```bash
+curl -H "Host: nodejs-10-runtime.default.example.com" http://localhost
+curl: (52) Empty reply from server
+```
+NodeJS 10:
+
+```bash
+kubectl exec nodejs-10-runtime-00001-deployment-5c5bf68cd5-26qhd -c user-container -- curl localhost:8080
+  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
+                                 Dload  Upload   Total   Spent    Left  Speed
+  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<title>Error</title>
+</head>
+<body>
+<pre>Cannot GET /</pre>
+</body>
+</html>
+100   139  100   139    0     0  19806      0 --:--:-- --:--:-- --:--:-- 23166
+```
+
+```bash
+kubectl exec nodejs-10-runtime-00001-deployment-5c5bf68cd5-26qhd -c user-container -- ls -1
+CHANGELOG.md
+app.js
+package.json
+runner.js
+src
+```
+
+```bash
+kubectl exec nodejs-10-runtime-00001-deployment-5c5bf68cd5-26qhd -c user-container -- ./app.js
+OCI runtime exec failed: exec failed: container_linux.go:344: starting container process caused "exec: \"./app.js\": permission denied": unknown
+command terminated with exit code 126
+```
+
+```bash
+kubectl exec nodejs-10-runtime-00001-deployment-5c5bf68cd5-26qhd -c user-container -- pwd
+/nodejsAction
+```
+
+```bash
+kubectl exec nodejs-10-runtime-00001-deployment-5c5bf68cd5-26qhd -c user-container -- ps -eaf
+UID        PID  PPID  C STIME TTY          TIME CMD
+root         1     0  0 19:51 ?        00:00:00 /bin/sh -c node --expose-gc app.js
+root         7     1  0 19:51 ?        00:00:00 node --expose-gc app.js
+root        49     0  0 19:59 ?        00:00:00 ps -eaf
+```
+
+```bash
+kubectl exec nodejs-10-runtime-00001-deployment-5c5bf68cd5-26qhd -c user-container -- env
+PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
+HOSTNAME=nodejs-10-runtime-00001-deployment-5c5bf68cd5-26qhd
+PORT=8080
+K_REVISION=nodejs-10-runtime-00001
+K_CONFIGURATION=nodejs-10-runtime
+K_SERVICE=nodejs-10-runtime
+TARGET=Node.js Sample v1
+NODEJS_10_RUNTIME_00001_SERVICE_PORT_9090_TCP=tcp://10.107.253.143:9090
+NODEJS_10_RUNTIME_00001_SERVICE_PORT_9090_TCP_PROTO=tcp
+KUBERNETES_SERVICE_HOST=10.96.0.1
+KUBERNETES_PORT=tcp://10.96.0.1:443
+KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
+KUBERNETES_PORT_443_TCP_PROTO=tcp
+NODEJS_10_RUNTIME_00001_SERVICE_SERVICE_HOST=10.107.253.143
+NODEJS_10_RUNTIME_00001_SERVICE_SERVICE_PORT_HTTP=80
+NODEJS_10_RUNTIME_00001_SERVICE_SERVICE_PORT_METRICS=9090
+NODEJS_10_RUNTIME_00001_SERVICE_PORT_80_TCP_ADDR=10.107.253.143
+NODEJS_10_RUNTIME_00001_SERVICE_PORT_9090_TCP_ADDR=10.107.253.143
+KUBERNETES_SERVICE_PORT_HTTPS=443
+NODEJS_10_RUNTIME_00001_SERVICE_SERVICE_PORT=80
+NODEJS_10_RUNTIME_00001_SERVICE_PORT=tcp://10.107.253.143:80
+NODEJS_10_RUNTIME_00001_SERVICE_PORT_80_TCP_PROTO=tcp
+NODEJS_10_RUNTIME_00001_SERVICE_PORT_80_TCP_PORT=80
+KUBERNETES_SERVICE_PORT=443
+KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
+NODEJS_10_RUNTIME_00001_SERVICE_PORT_80_TCP=tcp://10.107.253.143:80
+NODEJS_10_RUNTIME_00001_SERVICE_PORT_9090_TCP_PORT=9090
+KUBERNETES_PORT_443_TCP_PORT=443
+NODE_VERSION=10.15.0
+YARN_VERSION=1.12.3
+HOME=/root
+```
diff --git a/knative-build/samples/nodejs-runtime/service.yaml b/knative-build/samples/nodejs-runtime/service.yaml
new file mode 100644
index 0000000..4bdae23
--- /dev/null
+++ b/knative-build/samples/nodejs-runtime/service.yaml
@@ -0,0 +1,16 @@
+apiVersion: serving.knative.dev/v1alpha1
+kind: Service
+metadata:
+  name: nodejs-10-runtime
+  namespace: default
+spec:
+  runLatest:
+    configuration:
+      revisionTemplate:
+        spec:
+          container:
+#            image: docker.io/openwhisk/action-nodejs-v10
+            image: docker.io/openwhisk/nodejsactionbase
+            env:
+              - name: TARGET
+                value: "Node.js Sample v1"
diff --git a/knative-build/service-account.yaml b/knative-build/service-account.yaml
new file mode 100644
index 0000000..1f30981
--- /dev/null
+++ b/knative-build/service-account.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+    name: openwhisk-runtime-builder
+secrets:
+    - name: dockerhub-user-pass