5 |
6 |
7 |
8 |
9 | Runtime Starter
10 |
11 |
12 |
13 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | os:
3 | - windows
4 | - linux
5 | node_js:
6 | - "10"
7 | - "12"
8 | env:
9 | global:
10 | - CXX=g++-4.8
11 | - WHISK_APIHOST=https://adobeioruntime.net
12 | - WHISK_AUTH=fake-auth
13 | - WHISK_NAMESPACE=fake
14 | - TVM_URL=https://adobeioruntime.net/api/v1/web/adobeio/tvm/get-s3-upload-token
15 | addons:
16 | chrome: stable
17 | before_install:
18 | - if [ "${TRAVIS_OS_NAME}" = "windows" ]; then choco install --ignore-checksums googlechrome ; fi
19 | install:
20 | - npm version
21 | - npm ci
22 | script:
23 | - npm run build
24 | - npm test
25 | - npm run e2e
26 | - npm run clean
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ### Expected Behaviour
5 |
6 | ### Actual Behaviour
7 |
8 | ### Reproduce Scenario (including but not limited to)
9 |
10 | #### Steps to Reproduce
11 |
12 | #### Platform and Version
13 |
14 | #### Sample Code that illustrates the problem
15 |
16 | #### Logs taken while reproducing problem
17 |
--------------------------------------------------------------------------------
/web-src/src/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | import '@babel/polyfill'
13 | import ReactDOM from 'react-dom'
14 | import React from 'react'
15 | import App from './App'
16 |
17 | // Render it!
18 | ReactDOM.render(
19 |
20 | , document.getElementById('root'))
21 |
--------------------------------------------------------------------------------
/scripts/generate.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | const fs = require('fs')
13 |
14 | const config = require('./script.config')
15 | const utils = require('./script.utils')
16 | /**
17 | * Generate Config Service Urls
18 | */
19 | function generateConfig () {
20 | fs.writeFileSync(
21 | config.uiConfigFile,
22 | JSON.stringify(config.actionUrls), { encoding: 'utf-8' }
23 | )
24 | }
25 |
26 | utils.runAsScript(generateConfig)
27 |
--------------------------------------------------------------------------------
/test/ui/App.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | /* eslint-env mocha */
13 |
14 | import React from 'react'
15 | import { mount } from 'enzyme'
16 | import assert from 'assert'
17 | import App from '../../web-src/src/App'
18 |
19 | describe('', () => {
20 | it('should display h1 tag', () => {
21 | const wrapper = mount()
22 | const h1 = wrapper.find('h1')
23 | assert.strictEqual(h1.length, 1)
24 | assert.strictEqual(h1.text(), 'Hello there')
25 | })
26 | })
27 |
--------------------------------------------------------------------------------
/web-src/src/services/Errors.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | class AppError extends Error {
13 | constructor (message, status) {
14 | if (typeof message === 'object') {
15 | super(JSON.stringify(message))
16 | this.json = message
17 | } else {
18 | super(message)
19 | }
20 |
21 | this.name = this.constructor.name
22 | this.stack = Error().stack // Error.captureStackTrace(this, this.constructor)
23 |
24 | this.status = status || 500
25 | }
26 | }
27 |
28 | class RequestValidationError extends AppError {
29 | constructor (fields) {
30 | super('Request validation failed, ' + JSON.stringify(fields), 400)
31 | this.fields = fields || {}
32 | }
33 | }
34 |
35 | module.exports = { AppError, RequestValidationError }
36 |
--------------------------------------------------------------------------------
/actions/hello/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | /**
13 | * Test action
14 | * @param args
15 | * @returns {{body: string}}
16 | */
17 | function main (args) {
18 | const headers = {
19 | 'content-type': 'application/json'
20 | }
21 |
22 | let message = 'you didn\'t tell me who you are.'
23 | if (args.name) {
24 | const name = args.name.trim()
25 |
26 | if (name.startsWith('!')) {
27 | // error command
28 | return {
29 | headers: headers,
30 | statusCode: 400,
31 | body: {
32 | error: name.substring(1)
33 | }
34 | }
35 | }
36 |
37 | message = `hello ${name}!`
38 | }
39 | return {
40 | headers: headers,
41 | body: {
42 | message
43 | }
44 | }
45 | }
46 |
47 | exports.main = main
48 |
--------------------------------------------------------------------------------
/scripts/undeploy.ui.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 | Unless required by applicable law or agreed to in writing, software distributed under
7 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8 | OF ANY KIND, either express or implied. See the License for the specific language
9 | governing permissions and limitations under the License.
10 | */
11 | const aws = require('aws-sdk')
12 | const utils = require('./script.utils')
13 | const config = require('./script.config')
14 |
15 | async function undeployStaticS3 () {
16 | const creds = config.s3Creds || await utils.getTmpS3Credentials(config.tvmUrl, config.owNamespace, config.owAuth, config.credsCacheFile)
17 | const s3 = new aws.S3(creds)
18 |
19 | if (!(await utils.s3.folderExists(s3, config.s3DeploymentFolder))) {
20 | throw new Error(`Cannot undeploy UI files, S3 folder ${config.s3DeploymentFolder} does not exist.`)
21 | }
22 | console.info(`Removing static web files from S3...`)
23 | await utils.s3.emptyFolder(s3, config.s3DeploymentFolder)
24 |
25 | console.log('Succesfully undeployed UI !')
26 | }
27 |
28 | utils.runAsScript(undeployStaticS3)
29 |
--------------------------------------------------------------------------------
/scripts/build.ui.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | const Bundler = require('parcel-bundler')
13 | const fs = require('fs-extra')
14 | const path = require('path')
15 |
16 | const config = require('./script.config')
17 | const utils = require('./script.utils')
18 |
19 | async function buildUI () {
20 | // clean/create needed dirs
21 | fs.emptyDirSync(config.distUIRemoteDir)
22 |
23 | // 1. generate config
24 | require('./generate.config')
25 |
26 | // 2. build UI files
27 | const bundler = new Bundler(path.join(config.srcUIDir, 'index.html'), {
28 | cache: false,
29 | outDir: config.distUIRemoteDir,
30 | publicUrl: './',
31 | watch: false,
32 | detailedReport: true
33 | })
34 |
35 | await bundler.bundle()
36 | console.log('UI Build succeeded!')
37 | }
38 |
39 | utils.runAsScript(buildUI)
40 |
--------------------------------------------------------------------------------
/test/helpers/browser.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | import jsdom from 'jsdom'
13 | import configure from 'enzyme-adapter-react-helper'
14 | require('@babel/register')()
15 | let exposedProperties = ['window', 'navigator', 'document']
16 |
17 | const { JSDOM } = jsdom
18 | const { document } = (new JSDOM('', {
19 | url: 'http://localhost'
20 | })).window
21 | global.document = document
22 | global.window = document.defaultView
23 |
24 | Object.keys(document.defaultView).forEach((property) => {
25 | if (typeof global[property] === 'undefined') {
26 | exposedProperties.push(property)
27 | global[property] = document.defaultView[property]
28 | }
29 | })
30 |
31 | global.navigator = {
32 | userAgent: 'node.js'
33 | }
34 |
35 | // var documentRef = document
36 |
37 | configure()
38 |
--------------------------------------------------------------------------------
/scripts/install.zip.actions.dep.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | const fs = require('fs')
13 | const path = require('path')
14 | const spawn = require('cross-spawn')
15 |
16 | const config = require('./script.config')
17 | const utils = require('./script.utils')
18 |
19 | // sync
20 | function installDep () {
21 | Object.entries(config.wskManifestActions).forEach(([name, action]) => {
22 | const actionPath = path.join(config.rootDir, action.function)
23 | if (fs.statSync(actionPath).isDirectory() &&
24 | fs.readdirSync(actionPath).includes('package.json')) {
25 | // npm install
26 | const install = spawn.sync(`npm`, ['install', '--no-package-lock'], { cwd: actionPath })
27 | if (install.error) throw install.error
28 | if (install.status !== 0) throw new Error(install.stderr.toString())
29 | }
30 | })
31 | }
32 |
33 | utils.runAsScript(installDep)
34 |
--------------------------------------------------------------------------------
/web-src/src/App.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | import React from 'react'
13 | import ErrorBoundary from 'react-error-boundary'
14 | import HelloPage from './pages/Hello'
15 |
16 | export default class App extends React.Component {
17 | constructor () {
18 | super()
19 |
20 | // error handler on UI rendering failure
21 | this.onError = (e, componentStack) => {}
22 |
23 | // component to show if UI fails rendering
24 | this.fallbackComponent = ({ componentStack, error }) => (
25 |
36 | )
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/scripts/parcel.proxy.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | /**
13 | * Proxy server for client dev
14 | */
15 | const Bundler = require('parcel-bundler')
16 | const express = require('express')
17 | const ActionRunner = require('./runner')
18 | const config = require('./script.config')
19 | const path = require('path')
20 |
21 | /**
22 | * Generate Config
23 | */
24 | require('./generate.config')
25 |
26 | /**
27 | * Make sure zip actions have their dependencies installed
28 | */
29 | require('./install.zip.actions.dep')
30 |
31 | const bundler = new Bundler(path.join(config.rootDir, 'web-src', 'index.html'), {
32 | cache: false,
33 | outDir: config.distUILocalDir,
34 | contentHash: false
35 | })
36 |
37 | const app = express()
38 |
39 | app.use(express.json())
40 |
41 | /**
42 | * Actions as API
43 | */
44 | app.all(
45 | '/actions/*',
46 | ActionRunner
47 | )
48 |
49 | app.use(bundler.middleware())
50 | const port = Number(process.env.PORT || 9000)
51 | app.listen(port)
52 |
53 | console.log(` http://localhost:${port}`)
54 |
--------------------------------------------------------------------------------
/test/actions/hello.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | /* eslint-env mocha */
13 |
14 | const assert = require('assert')
15 | const action = require('../../actions/hello')
16 |
17 | describe('Action: hello', () => {
18 | describe('main', () => {
19 | it('should return content type json', () => {
20 | const response = action.main({})
21 | assert.strictEqual(response.headers['content-type'], 'application/json')
22 | })
23 |
24 | it('should return default message', () => {
25 | const response = action.main({})
26 | assert.strictEqual(response.body.message, 'you didn\'t tell me who you are.')
27 | })
28 |
29 | it('should greet with name', () => {
30 | const response = action.main({ name: 'Atreus' })
31 | assert.strictEqual(response.body.message, 'hello Atreus!')
32 | })
33 |
34 | it('should return error', () => {
35 | const response = action.main({ name: '!Atreus' })
36 | assert.strictEqual(response.statusCode, 400)
37 | assert.strictEqual(response.body.error, 'Atreus')
38 | })
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/scripts/express.e2e.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | /**
13 | * Proxy server for e2e tests
14 | */
15 | const express = require('express')
16 | const ActionRunner = require('./runner')
17 | const createTestCafe = require('testcafe')
18 | const config = require('./script.config')
19 | const testcafeConfig = require('../test/.testcaferc.json')
20 | let testcafe = null
21 |
22 | // Set up express server
23 | const app = express()
24 | app.use(express.json())
25 |
26 | // config.distUIRemoteDir is the output of build.ui.js,
27 | // this is a bit hacky way to retrieve the output..
28 | app.use(express.static(config.distUIRemoteDir))
29 | app.all(
30 | '/actions/*',
31 | ActionRunner
32 | )
33 | const port = Number(process.env.PORT || 9000)
34 | app.listen(port)
35 | console.log('Serving on port', port)
36 |
37 | /**
38 | * Run tests
39 | */
40 | createTestCafe('localhost', 1337, 1338)
41 | .then(tc => {
42 | testcafe = tc
43 | const runner = testcafe.createRunner()
44 |
45 | return runner
46 | .src(testcafeConfig.src)
47 | .browsers(testcafeConfig.browsers)
48 | .run()
49 | })
50 | .then(failedCount => {
51 | testcafe.close()
52 | process.exit(!!failedCount)
53 | })
54 |
--------------------------------------------------------------------------------
/test/e2e/welcome.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | import { Selector } from 'testcafe'
13 |
14 | /* global fixture:true, test:true */
15 |
16 | fixture`Welcome Page`
17 | .page`http://localhost:9080`
18 |
19 | /**
20 | * Check if the homepage content loads
21 | */
22 | test('Welcome Page Loads', async t => {
23 | await t
24 | // Check for h1 tag
25 | .expect(Selector('#root h1').innerText).eql('Hello there')
26 | // Check for input tag
27 | .expect(Selector('#root input[type="text"]').exists).ok()
28 | // Check for submit button
29 | .expect(Selector('#root button').exists).ok()
30 | })
31 |
32 | /**
33 | * Test submitting the form without data
34 | */
35 | test('Test greeting form: empty input', async t => {
36 | await t
37 | // Click submit button
38 | .click(Selector('#root button'))
39 | .expect(Selector('#root h1').innerText).eql('you didn\'t tell me who you are.')
40 | })
41 |
42 | test('Test greeting form: with name', async t => {
43 | await t
44 | // Input name
45 | .typeText(Selector('#root input[type="text"]'), 'Atreus', { replace: true })
46 | // Click submit button
47 | .click(Selector('#root button'))
48 | .expect(Selector('#root h1').innerText).eql('hello Atreus!')
49 | })
50 |
--------------------------------------------------------------------------------
/test/ui/Error.service.test.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | /* eslint-env mocha */
13 | import assert from 'assert'
14 |
15 | import { AppError, RequestValidationError } from '../../web-src/src/services/Errors'
16 |
17 | describe('Error Service', () => {
18 | it('should throw an error with custom format', () => {
19 | const error = new AppError('Reponse from server could not be parsed.', 500)
20 | assert.strictEqual(error.name, 'AppError')
21 | assert.strictEqual(error.message, 'Reponse from server could not be parsed.')
22 | assert.strictEqual(error.status, 500)
23 | })
24 |
25 | it('should throw an error with json', () => {
26 | const error = new AppError({ message: 'Reponse from server could not be parsed.' }, 500)
27 | assert.strictEqual(JSON.parse(error.message).message, 'Reponse from server could not be parsed.')
28 | assert.strictEqual(error.json.message, 'Reponse from server could not be parsed.')
29 | })
30 |
31 | it('should throw an error with fields', () => {
32 | const error = new RequestValidationError({ actionName: 'TestAction' })
33 | assert.strictEqual(error.fields.actionName, 'TestAction')
34 | assert.strictEqual(error.name, 'RequestValidationError')
35 | assert.strictEqual(error.status, 400)
36 | })
37 | })
38 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Description
4 |
5 |
6 |
7 | ## Related Issue
8 |
9 |
10 |
11 |
12 |
13 |
14 | ## Motivation and Context
15 |
16 |
17 |
18 | ## How Has This Been Tested?
19 |
20 |
21 |
22 |
23 |
24 | ## Screenshots (if appropriate):
25 |
26 | ## Types of changes
27 |
28 |
29 |
30 | - [ ] Bug fix (non-breaking change which fixes an issue)
31 | - [ ] New feature (non-breaking change which adds functionality)
32 | - [ ] Breaking change (fix or feature that would cause existing functionality to change)
33 |
34 | ## Checklist:
35 |
36 |
37 |
38 |
39 | - [ ] I have signed the [Adobe Open Source CLA](http://opensource.adobe.com/cla.html).
40 | - [ ] My code follows the code style of this project.
41 | - [ ] My change requires a change to the documentation.
42 | - [ ] I have updated the documentation accordingly.
43 | - [ ] I have read the **CONTRIBUTING** document.
44 | - [ ] I have added tests to cover my changes.
45 | - [ ] All new and existing tests passed.
46 |
--------------------------------------------------------------------------------
/scripts/deploy.ui.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | const path = require('path')
13 | const fs = require('fs')
14 | const aws = require('aws-sdk')
15 | const open = require('open')
16 | const utils = require('./script.utils')
17 | const config = require('./script.config')
18 |
19 | async function deployStaticS3 () {
20 | if (!fs.existsSync(config.distUIRemoteDir) || !fs.statSync(config.distUIRemoteDir).isDirectory() || !fs.readdirSync(config.distUIRemoteDir).length) {
21 | throw new Error(`./${path.relative(config.rootDir, config.distUIRemoteDir)}/ should not be empty, maybe you forgot to build your UI ?`)
22 | }
23 | console.log(`Uploading static web files to S3...`)
24 |
25 | const creds = config.s3Creds || await utils.getTmpS3Credentials(config.tvmUrl, config.owNamespace, config.owAuth, config.credsCacheFile)
26 | const s3 = new aws.S3(creds)
27 |
28 | if (await utils.s3.folderExists(s3, config.s3DeploymentFolder)) {
29 | console.info(`An already existing deployment for version ${config.version} will be overwritten`)
30 | await utils.s3.emptyFolder(s3, config.s3DeploymentFolder)
31 | }
32 | await utils.s3.uploadDir(s3, config.s3DeploymentFolder,
33 | config.distUIRemoteDir, f => console.log(` -> ${path.basename(f)}`))
34 |
35 | const url = `https://s3.amazonaws.com/${creds.params.Bucket}/${config.s3DeploymentFolder}/index.html`
36 |
37 | console.log(url)
38 | console.log('Succesfully deployed UI 🎉')
39 | open(url)
40 | }
41 |
42 | utils.runAsScript(deployStaticS3)
43 |
--------------------------------------------------------------------------------
/web-src/src/services/Action.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | import API from '../config'
13 |
14 | import { AppError, RequestValidationError } from './Errors'
15 |
16 | const Action = {}
17 |
18 | /* global fetch */
19 |
20 | /**
21 | * Invokes a web action and returns the response.
22 | * @param {string} actionName
23 | * @param {object} params
24 | * @param {bool} errorNotJsonResponse
25 | */
26 | Action.webInvoke = async function (actionName, params, errorNotJsonResponse) {
27 | if (!actionName || !API[actionName]) {
28 | throw new RequestValidationError({ actionName: actionName })
29 | }
30 |
31 | if (params && typeof params !== 'object') {
32 | throw new RequestValidationError({ params: params })
33 | }
34 |
35 | const response = await fetch(API[actionName], params && {
36 | method: 'post',
37 | headers: {
38 | 'Content-Type': 'application/json'
39 | },
40 | body: JSON.stringify(params)
41 | })
42 |
43 | let content
44 | try {
45 | content = await response.text()
46 | } catch (e) {
47 | throw new AppError('Reponse from ' + API[actionName] + ' could not be parsed.', 500)
48 | }
49 | try {
50 | content = JSON.parse(content)
51 | } catch (e) {
52 | if (errorNotJsonResponse) {
53 | throw new AppError('Response from ' + API[actionName] + ' is not a valid JSON.', 500)
54 | }
55 | }
56 |
57 | if (!response.ok) {
58 | throw new AppError(content, response.status)
59 | }
60 |
61 | return content
62 | }
63 |
64 | export default Action
65 |
--------------------------------------------------------------------------------
/web-src/src/pages/Hello.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | import React from 'react'
13 | import Action from '../services/Action'
14 | import logo from '../../resources/adobe-logo.png'
15 |
16 | export default class App extends React.Component {
17 | constructor () {
18 | super()
19 | this.state = {
20 | greeting: 'Hello there',
21 | name: '',
22 | errorMsg: ''
23 | }
24 |
25 | this.inputChange = this.inputChange.bind(this)
26 | this.hello = this.hello.bind(this)
27 | }
28 |
29 | inputChange (event) {
30 | const name = event.target.value
31 | this.setState({
32 | name
33 | })
34 | }
35 |
36 | async hello () {
37 | try {
38 | const json = await Action.webInvoke('hello', { name: this.state.name }, true)
39 | this.setState({
40 | greeting: json.message,
41 | errorMsg: ''
42 | })
43 | } catch (e) {
44 | this.setState({
45 | errorMsg: (e.status || 'Error') + ': ' + e.message
46 | })
47 | }
48 | }
49 |
50 | render () {
51 | return
52 |
53 |
54 |
55 |
{this.state.greeting}
56 |
57 |
58 |
59 |
60 | {this.state.errorMsg &&
61 |
62 |
{this.state.errorMsg}
63 |
64 | }
65 |
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/scripts/build.actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | const fs = require('fs')
13 | const path = require('path')
14 | const Bundler = require('parcel-bundler')
15 |
16 | const config = require('./script.config')
17 | const utils = require('./script.utils')
18 |
19 | async function buildActions () {
20 | // installs dependencies in zip actions,
21 | // we create the zip right after
22 | require('./install.zip.actions.dep') // sync
23 |
24 | const build = async function (name, action) {
25 | const actionPath = path.join(config.rootDir, action.function)
26 |
27 | if (fs.statSync(actionPath).isDirectory()) {
28 | // if directory zip it
29 | const outZip = path.join(config.distActionsDir, name + '.zip')
30 | await utils.zipFolder(actionPath, outZip)
31 | console.log(path.relative(config.rootDir, outZip))
32 | } else {
33 | // if not directory => package and minify to single file
34 | const bundler = new Bundler(actionPath, {
35 | outDir: config.distActionsDir,
36 | outFile: name + '.js',
37 | cache: false,
38 | watch: false,
39 | target: 'node',
40 | contentHash: false,
41 | minify: true,
42 | sourceMaps: false,
43 | bundleNodeModules: true,
44 | logLevel: 4
45 | })
46 | // promise
47 | return bundler.bundle()
48 | }
49 | }
50 |
51 | // build all sequentially
52 | for (let [name, action] of Object.entries(config.wskManifestActions)) {
53 | await build(name, action)
54 | }
55 | console.log()
56 | console.log('Action Build succeeded!')
57 | }
58 |
59 | utils.runAsScript(buildActions)
60 |
--------------------------------------------------------------------------------
/scripts/undeploy.actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 | Unless required by applicable law or agreed to in writing, software distributed under
7 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
8 | OF ANY KIND, either express or implied. See the License for the specific language
9 | governing permissions and limitations under the License.
10 | */
11 | const fs = require('fs')
12 | const spawn = require('cross-spawn')
13 | const path = require('path')
14 |
15 | const config = require('./script.config')
16 | const utils = require('./script.utils')
17 |
18 | function undeployActionsSync () {
19 | if (!fs.existsSync(config.distWskManifestFile) || !fs.statSync(config.distWskManifestFile).isFile()) {
20 | throw new Error(`Cannot undeploy actions, missing ${path.relative(config.rootDir, config.distWskManifestFile)} file (autogenerated on deploy).`)
21 | }
22 |
23 | console.log(`Undeploying actions...`)
24 |
25 | // for now this is a tmp hack so that ~/.wskprops does not interfer with WHISK_* properties defined in .env
26 | const fakeWskProps = '.fake-wskprops'
27 | fs.writeFileSync(fakeWskProps, '')
28 | process.env['WSK_CONFIG_FILE'] = fakeWskProps
29 |
30 | // aio reads env WHISK_* properties
31 | const aio = spawn.sync(
32 | `aio`,
33 | [
34 | 'runtime', 'deploy',
35 | 'undeploy',
36 | '-m', config.distWskManifestFile
37 | ],
38 | { cwd: config.rootDir }
39 | )
40 | if (aio.error) throw aio.error
41 | if (aio.status !== 0) throw new Error(aio.stderr.toString())
42 |
43 | // hack end remove fake props file
44 | fs.unlinkSync(fakeWskProps)
45 |
46 | // rm config.distWskManifestFile
47 | fs.unlinkSync(config.distWskManifestFile)
48 |
49 | Object.keys(config.wskManifestActions).forEach(an => {
50 | console.log(` -> ${config.owDeploymentPackage}/${an}`)
51 | })
52 |
53 | console.log('Succesfully undeployed actions!')
54 | }
55 |
56 | utils.runAsScript(undeployActionsSync)
57 |
--------------------------------------------------------------------------------
/.github/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Thanks for choosing to contribute!
4 |
5 | The following are a set of guidelines to follow when contributing to this project.
6 |
7 | ## Code Of Conduct
8 |
9 | This project adheres to the Adobe [code of conduct](../CODE_OF_CONDUCT.md). By participating,
10 | you are expected to uphold this code. Please report unacceptable behavior to
11 | [Grp-opensourceoffice@adobe.com](mailto:Grp-opensourceoffice@adobe.com).
12 |
13 | ## Have A Question?
14 |
15 | Start by filing an issue. The existing committers on this project work to reach
16 | consensus around project direction and issue solutions within issue threads
17 | (when appropriate).
18 |
19 | ## Contributor License Agreement
20 |
21 | All third-party contributions to this project must be accompanied by a signed contributor
22 | license agreement. This gives Adobe permission to redistribute your contributions
23 | as part of the project. [Sign our CLA](http://opensource.adobe.com/cla.html). You
24 | only need to submit an Adobe CLA one time, so if you have submitted one previously,
25 | you are good to go!
26 |
27 | ## Code Reviews
28 |
29 | All submissions should come in the form of pull requests and need to be reviewed
30 | by project committers. Read [GitHub's pull request documentation](https://help.github.com/articles/about-pull-requests/)
31 | for more information on sending pull requests.
32 |
33 | Lastly, please follow the [pull request template](PULL_REQUEST_TEMPLATE.md) when
34 | submitting a pull request!
35 |
36 | ## From Contributor To Committer
37 |
38 | We love contributions from our community! If you'd like to go a step beyond contributor
39 | and become a committer with full write access and a say in the project, you must
40 | be invited to the project. The existing committers employ an internal nomination
41 | process that must reach lazy consensus (silence is approval) before invitations
42 | are issued. If you feel you are qualified and want to get more deeply involved,
43 | feel free to reach out to existing committers to have a conversation about that.
44 |
45 | ## Security Issues
46 |
47 | Security issues shouldn't be reported on this issue tracker. Instead, [file an issue to our security experts](https://helpx.adobe.com/security/alertus.html)
48 |
--------------------------------------------------------------------------------
/scripts/runner.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | const path = require('path')
13 | const mime = require('mime')
14 |
15 | const config = require('./script.config')
16 |
17 | /**
18 | * Express Route handler for triggering actions
19 | * @param req
20 | * @param res
21 | * @param next
22 | * @returns {Promise<*>}
23 | */
24 |
25 | module.exports = async (req, res, next) => {
26 | const url = req.params[0]
27 | const parts = url.split('/')
28 | const actionName = parts[0]
29 | const requestPath = url.replace(actionName, '')
30 |
31 | let params = {
32 | __ow_body: req.body,
33 | __ow_headers: req.headers,
34 | __ow_path: requestPath,
35 | __ow_query: req.query,
36 | __ow_method: req.method.toLowerCase(),
37 | ...req.query,
38 | ...(req.is('application/json') ? req.body : {})
39 | }
40 | params['__ow_headers']['x-forwarded-for'] = '127.0.0.1'
41 |
42 | // disallow access to UI action -> avoids getting wrong path pointing to
43 | // remote action folder. This is a tmp fix which will be solved once html is
44 | // not served by action for remote
45 | let action = config.wskManifestActions[actionName]
46 | if (!action) {
47 | return res.status(404)
48 | .send({ error: '404: Action ' + actionName + ' not found' })
49 | }
50 |
51 | const actionPath = action.function
52 | const actionFunction = require(path.join(config.rootDir, actionPath)).main
53 |
54 | try {
55 | let response = await actionFunction(params)
56 | const headers = response.headers
57 | const status = response.statusCode
58 |
59 | headers['Content-Type'] = headers['Content-Type'] || headers['content-type'] || mime.getType(requestPath)
60 | return res
61 | .set(headers || {})
62 | .status(status || 200)
63 | .send(response.body)
64 | } catch (e) {
65 | return res
66 | .status(500)
67 | .send({ error: e.message })
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/scripts/deploy.actions.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | const yaml = require('js-yaml')
13 | const fs = require('fs')
14 | const path = require('path')
15 | const spawn = require('cross-spawn')
16 | const config = require('./script.config')
17 | const utils = require('./script.utils')
18 |
19 | function deployActionsSync () {
20 | if (!fs.existsSync(config.distActionsDir) || !fs.statSync(config.distActionsDir).isDirectory() || !fs.readdirSync(config.distActionsDir).length) {
21 | throw new Error(`./${path.relative(config.rootDir, config.distActionsDir)}/ should not be empty, maybe you forgot to build your actions ?`)
22 | }
23 |
24 | console.log(`Deploying actions to ${config.owApihost} ...`)
25 |
26 | // rewrite wskManifest config
27 | const wskManifestCopy = { ...config.wskManifest }
28 | const wskManifestPackage = wskManifestCopy.packages[config.wskManifestPackagePlaceholder]
29 |
30 | wskManifestPackage.version = config.version
31 |
32 | Object.entries(wskManifestPackage.actions).forEach(([name, action]) => {
33 | const actionPath = path.join(config.rootDir, action.function)
34 | // change path to built action
35 | if (fs.statSync(actionPath).isDirectory()) {
36 | action.function = path.join(path.relative(config.rootDir, config.distActionsDir), name + '.zip')
37 | } else {
38 | action.function = path.join(path.relative(config.rootDir, config.distActionsDir), name + '.js')
39 | action.main = 'module.exports.' + (action.main || 'main')
40 | }
41 | })
42 |
43 | // replace package name
44 | const wskManifestString = yaml.safeDump(wskManifestCopy).replace(config.wskManifestPackagePlaceholder, config.owDeploymentPackage)
45 |
46 | // write the new wskManifest yaml
47 | fs.writeFileSync(config.distWskManifestFile, wskManifestString)
48 |
49 | // invoke wskManifest command
50 |
51 | // for now this is a tmp hack so that ~/.wskprops does not interfer with WHISK_* properties defined in .env
52 | const fakeWskProps = '.fake-wskprops'
53 | fs.writeFileSync(fakeWskProps, '')
54 | process.env['WSK_CONFIG_FILE'] = fakeWskProps
55 | // aio reads env WHISK_* properties
56 | const aio = spawn.sync(
57 | `aio`,
58 | [
59 | 'runtime', 'deploy',
60 | '-m', config.distWskManifestFile
61 | ],
62 | { cwd: config.rootDir }
63 | )
64 | // hack end remove fake props file
65 | fs.unlinkSync(fakeWskProps)
66 |
67 | if (aio.error) throw aio.error
68 | if (aio.status !== 0) throw new Error(aio.stderr.toString())
69 |
70 | // show list of deployed actions
71 | Object.keys(config.wskManifestActions).forEach(an => {
72 | console.log(` -> ${an}: ${config.actionUrls[an]}`)
73 | })
74 |
75 | console.log('Succesfully deployed actions 🎉')
76 | }
77 |
78 | utils.runAsScript(deployActionsSync)
79 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "adobeio-cna-starter-project",
3 | "version": "0.1.0",
4 | "description": "A starter project for building a cloud native app (CNA) on top of Adobe I/O Runtime",
5 | "scripts": {
6 | "dev": "cross-env nodemon ./scripts/parcel.proxy.js",
7 | "build:ui": "cross-env NODE_ENV=production REMOTE_ACTIONS=true node scripts/build.ui.js",
8 | "build:actions": "cross-env NODE_ENV=production node scripts/build.actions.js",
9 | "build": "npm run build:ui && npm run build:actions",
10 | "deploy:ui": "cross-env NODE_ENV=production node scripts/deploy.ui.js",
11 | "deploy:actions": "cross-env NODE_ENV=production node scripts/deploy.actions.js",
12 | "deploy": "npm run deploy:actions && npm run deploy:ui",
13 | "undeploy:ui": "cross-env NODE_ENV=production node scripts/undeploy.ui.js",
14 | "undeploy:actions": "cross-env NODE_ENV=production node scripts/undeploy.actions.js",
15 | "undeploy": "npm run undeploy:actions && npm run undeploy:ui",
16 | "lint": "eslint actions test web-src scripts",
17 | "beautify": "npm run lint -- --fix",
18 | "test": "npm run lint && mocha",
19 | "e2e": "node scripts/build.ui.js && cross-env PORT=9080 NODE_ENV=development node scripts/express.e2e.js",
20 | "coverage": "nyc --reporter=html --reporter=text mocha",
21 | "clean": "git clean -Xdf --exclude='!.env*'"
22 | },
23 | "keywords": [
24 | "ecosystem:adobe-io-cna",
25 | "aio-cna:template"
26 | ],
27 | "author": "Adobe Inc",
28 | "license": "Apache-2.0",
29 | "bugs": "https://github.com/adobe/adobeio-cna-starter-project/issues",
30 | "repository": "adobe/adobeio-cna-starter-project",
31 | "homepage": "https://github.com/adobe/adobeio-cna-starter-project",
32 | "devDependencies": {
33 | "@babel/core": "^7.2.2",
34 | "@babel/plugin-transform-runtime": "^7.1.0",
35 | "@babel/polyfill": "^7.2.5",
36 | "@babel/preset-env": "^7.2.3",
37 | "@babel/preset-react": "^7.0.0",
38 | "@babel/register": "^7.0.0",
39 | "@babel/runtime": "^7.2.0",
40 | "archiver": "^3.0.0",
41 | "aws-sdk": "^2.406.0",
42 | "babel-eslint": "^10.0.1",
43 | "cross-env": "^5.2.0",
44 | "cross-spawn": "^6.0.5",
45 | "dotenv": "^6.2.0",
46 | "enzyme": "^3.8.0",
47 | "enzyme-adapter-react-16": "^1.7.1",
48 | "enzyme-adapter-react-helper": "^1.3.1",
49 | "eslint": "^5.12.0",
50 | "eslint-config-standard": "^12.0.0",
51 | "eslint-config-standard-react": "^7.0.2",
52 | "eslint-plugin-import": "^2.14.0",
53 | "eslint-plugin-node": "^8.0.0",
54 | "eslint-plugin-promise": "^4.0.1",
55 | "eslint-plugin-react": "^7.11.1",
56 | "eslint-plugin-standard": "^4.0.0",
57 | "express": "^4.16.4",
58 | "fs-extra": "^7.0.1",
59 | "http-proxy-middleware": "^0.19.0",
60 | "ignore-styles": "^5.0.1",
61 | "js-yaml": "^3.13.1",
62 | "jsdom": "^13.1.0",
63 | "mime-types": "^2.1.22",
64 | "mocha": "^5.2.0",
65 | "nodemon": "^1.18.6",
66 | "nyc": "^13.1.0",
67 | "open": "^6.2.0",
68 | "openwhisk": "^3.18.0",
69 | "parcel-bundler": "^1.10.3",
70 | "postcss-modules": "^1.4.1",
71 | "prop-types": "^15.6.2",
72 | "pubsub-js": "^1.7.0",
73 | "query-string": "^6.2.0",
74 | "react": "^16.7.0",
75 | "react-dom": "^16.6.3",
76 | "react-error-boundary": "^1.2.3",
77 | "react-router-dom": "^4.3.1",
78 | "request-promise": "^4.2.4",
79 | "testcafe": "^1.1.1"
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Adobe Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, gender identity and expression, level of experience,
9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at Grp-opensourceoffice@adobe.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [https://contributor-covenant.org/version/1/4][version]
72 |
73 | [homepage]: https://contributor-covenant.org
74 | [version]: https://contributor-covenant.org/version/1/4/
75 |
--------------------------------------------------------------------------------
/scripts/script.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | const yaml = require('js-yaml')
13 | const fs = require('fs')
14 | const path = require('path')
15 | const utils = require('./script.utils')
16 |
17 | const rootDir = path.join(__dirname, '..')
18 |
19 | const packagejson = require(path.join(rootDir, 'package.json'))
20 |
21 | // env variables
22 | require('dotenv').config({ path: path.join(rootDir, '.env') })
23 |
24 | // config
25 | const config = {}
26 | /// dotenv
27 | if (!process.env.WHISK_APIHOST) throw new Error('Missing WHISK_APIHOST env variable')
28 | if (!process.env.WHISK_AUTH) throw new Error('Missing WHISK_AUTH env variable')
29 | if (!process.env.WHISK_NAMESPACE) throw new Error('Missing WHISK_NAMESPACE env variable')
30 | process.env.WHISK_APIVERSION = process.env.WHISK_APIVERSION || 'v1'
31 |
32 | config.owApihost = process.env.WHISK_APIHOST
33 | config.owNamespace = process.env.WHISK_NAMESPACE
34 | config.owAuth = process.env.WHISK_AUTH
35 | config.owApiversion = process.env.WHISK_APIVERSION
36 | /// either tvmUrl
37 | config.tvmUrl = process.env.TVM_URL
38 | /// or long term creds
39 | config.s3Creds = (process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY && process.env.S3_BUCKET) && {
40 | accessKeyId: process.env.AWS_ACCESS_KEY_ID,
41 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
42 | params: { Bucket: process.env.S3_BUCKET }
43 | }
44 | if (!(config.tvmUrl || config.s3Creds)) throw new Error('Missing s3 credentials or TVM_URL env variable')
45 |
46 | /// env
47 | config.remoteActions = !!process.env.REMOTE_ACTIONS
48 | /// package.json
49 | config.version = packagejson.version || '0.0.1'
50 | config.name = packagejson.name || 'unnamed-cna'
51 | /// project paths
52 | config.rootDir = rootDir
53 | config.srcActionDir = path.join(config.rootDir, 'actions')
54 | config.srcUIDir = path.join(config.rootDir, 'web-src')
55 | config.srcWskManifestFile = path.join(config.rootDir, 'manifest.yml')
56 | config.distDir = path.join(config.rootDir, 'dist')
57 | config.distActionsDir = path.join(config.distDir, 'actions')
58 | config.distUIRemoteDir = path.join(config.distDir, 'ui-remote')
59 | config.distUILocalDir = path.join(config.distDir, 'ui-local')
60 | config.distWskManifestFile = path.join(config.rootDir, '.manifest-dist.yml')
61 | config.uiConfigFile = path.join(config.srcUIDir, 'src', 'config.json')
62 | /// wskManifest config
63 | config.wskManifestPackagePlaceholder = '__CNA_PACKAGE__'
64 | config.wskManifest = yaml.safeLoad(fs.readFileSync(config.srcWskManifestFile, 'utf8'))
65 | config.wskManifestPackage = config.wskManifest.packages[config.wskManifestPackagePlaceholder]
66 | config.wskManifestActions = config.wskManifestPackage.actions
67 | /// deployment
68 | config.owDeploymentPackage = `${config.name}-${config.version}`
69 | config.s3DeploymentFolder = utils.urlJoin(config.owNamespace, config.owDeploymentPackage)
70 | // credentials cache
71 | config.credsCacheFile = path.join(rootDir, '.aws.tmp.creds.json')
72 |
73 | // action urls {name: url}, if dev url is /actions/name
74 | config.actionUrls = Object.entries(config.wskManifestActions).reduce((obj, [name, action]) => {
75 | const webArg = action['web-export'] || action['web']
76 | const webUri = (webArg && webArg !== 'no' && webArg !== 'false') ? 'web' : ''
77 | obj[name] = (config.remoteActions || process.env['NODE_ENV'] === 'production')
78 | ? utils.urlJoin(config.owApihost, 'api', config.owApiversion, webUri, config.owNamespace, config.owDeploymentPackage, name)
79 | : utils.urlJoin('/actions', name) // local url if NODE_ENV!=prod and REMOTE_ACTIONS not set
80 | return obj
81 | }, {})
82 |
83 | module.exports = config
84 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | [](https://travis-ci.com/adobe/adobeio-cna-starter-project)
3 | [](https://opensource.org/licenses/Apache-2.0)
4 |
5 |
6 |
7 |
8 |
9 | # Runtime CNA Starter
10 |
11 | # DEPRECATED! PLEASE USE https://github.com/adobe/aio-cli TO MANAGE YOUR NEW APPS!
12 |
13 | A starter project for building a cloud native app (CNA) on top of Adobe I/O Runtime.
14 |
15 | ## Setup
16 |
17 | - Install the AIO CLI and runtime plugin
18 |
19 | ```bash
20 | npm install -g @adobe/aio-cli
21 | aio plugins install @adobe/aio-cli-plugin-runtime
22 | ```
23 |
24 | - `npm install`
25 |
26 | - Create a `.env` file in the project root and fill it as shown [below](#env)
27 |
28 | ## Local Dev
29 |
30 | - `npm run dev` to start your local Dev server
31 | - App will run on `localhost:9080` by default
32 | - Local dev server uses an expressJS proxy to invoke action code.
33 | - You can invoke your back-end actions defined locally via the url `localhost:9080/actions/`
34 |
35 | ## Test & Coverage
36 |
37 | - Run `npm run test` to run unit tests for ui and actions
38 | - Run `npm run e2e` to run e2e tests
39 | - Run `npm run coverage` to generate Code coverage report
40 |
41 | ## Build, Deploy & Cleanup
42 |
43 | - `npm run build` to build your ui:React code and build your actions
44 | - `npm run deploy` to deploy all actions on Runtime and static files to S3
45 | - `npm run undeploy` to undeploy the app
46 |
47 | For each of the above command you can either append `:ui` or `:actions`, for
48 | example `npm run build:ui` will only build the UI.
49 |
50 | ## Dependencies
51 |
52 | - aio runtime CLI for action deployments
53 | - expressJS for local dev
54 | - parcelJS for packaging UI App (React by default) and actions
55 | - s3 for serving static files
56 |
57 | ## Config
58 |
59 | ### `.env`
60 |
61 | ```bash
62 | WHISK_APIVERSION=v1
63 | WHISK_APIHOST=https://adobeioruntime.net
64 | WHISK_AUTH=
65 | WHISK_NAMESPACE=
66 | # either TVM URL
67 | TVM_URL=https://adobeioruntime.net/api/v1/web/adobeio/tvm/get-s3-upload-token
68 | # Or bring your own S3 credentials and bucket
69 | AWS_ACCESS_KEY_ID=
70 | AWS_SECRET_ACCESS_KEY=
71 | S3_BUCKET=
72 | ```
73 |
74 | ### S3 Credentials
75 |
76 | - Set the `TVM_URL` variable in `.env` to point to a deployed [CNA token vending
77 | machine](https://github.com/adobe/adobeio-cna-token-vending-machine). It
78 | allows you to download temporary and restricted credentials to upload your
79 | static files to S3. Credentials will be cached in `.aws.tmp.creds.json`. Users
80 | with a valid namespace for Adobe I/O Runtime can simply use
81 | `https://adobeioruntime.net/api/v1/web/adobeio/tvm/get-s3-upload-token`.
82 |
83 | - Alternatively, you can bring your own AWS credentials by defining
84 | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `S3_BUCKET` vars in your `.env`
85 | file. In that case make sure to create the bucket beforehand.
86 |
87 | ### `package.json`
88 |
89 | - We use the `name` and `version` fields for the deployment. Make sure to fill
90 | those out. Do not use illegal characters as this might break the deployment
91 | (e.g. `/`, `@`, `#`, ..).
92 |
93 | ### `manifest.yml`
94 |
95 | - List your backend actions under the `actions` field within the `__CNA_PACKAGE__`
96 | package placeholder. We will take care of replacing the package name placeholder
97 | by your project name and version.
98 | - For each action, use the `function` field to indicate the path to the action
99 | code.
100 | - More documentation for supported action fields can be found
101 | [here](https://github.com/apache/incubator-openwhisk-wskdeploy/blob/master/specification/html/spec_actions.md#actions).
102 |
103 | #### Action Dependencies
104 |
105 | - You have two options to resolve your actions' dependencies:
106 |
107 | 1. **Packaged action file**: Add your action's dependencies to the root
108 | `package.json` and install them using `npm install`. Then set the `function`
109 | field in `manifest.yml` to point to the **entry file** of your action
110 | folder. We will use `parcelJS` to package your code and dependencies into a
111 | single minified js file. The action will then be deployed as a single file.
112 | Use this method if you want to reduce the size of your actions.
113 |
114 | 2. **Zipped action folder**: In the folder containing the action code add a
115 | `package.json` with the action's dependencies. Then set the `function`
116 | field in `manifest.yml` to point to the **folder** of that action. We will
117 | install the required dependencies within that directory and zip the folder
118 | before deploying it as a zipped action. Use this method if you want to keep
119 | your action's dependencies separated.
120 |
121 | ### `REMOTE_ACTIONS`
122 |
123 | - This variable controls the configuration generation for action URLs used by the
124 | UI.
125 |
126 | - `REMOTE_ACTIONS=true npm run dev` to run the UI locally but access
127 | remotely deployed actions.
128 |
129 | ## Debugging in VS Code
130 | Below is a configuration to run VS Code deubgger to launch and debug both the UI and Actions together.
131 |
132 | ```json
133 | {
134 | "version": "0.2.0",
135 | "configurations": [
136 | {
137 | "type": "node",
138 | "request": "launch",
139 | "name": "Server",
140 | "runtimeExecutable": "${workspaceFolder}/node_modules/nodemon/bin/nodemon.js",
141 | "program": "${workspaceFolder}/scripts/parcel.proxy.js",
142 | "restart": true,
143 | "console": "integratedTerminal",
144 | "internalConsoleOptions": "neverOpen"
145 | },
146 | {
147 | "type": "chrome",
148 | "request": "launch",
149 | "name": "Client",
150 | "url": "http://localhost:9080",
151 | "webRoot": "${workspaceFolder}/we-src/src",
152 | "sourceMapPathOverrides": {
153 | "webpack:///src/*": "${webRoot}/*"
154 | }
155 | }
156 | ],
157 | "compounds": [
158 | {
159 | "name": "Server/Client",
160 | "configurations": ["Server", "Client"]
161 | }
162 | ]
163 | }
164 | ```
165 |
166 | ## Contributing
167 |
168 | Contributions are welcomed! Read the [Contributing Guide](./.github/CONTRIBUTING.md) for more information.
169 |
170 | ## Licensing
171 |
172 | This project is licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information.
173 |
--------------------------------------------------------------------------------
/scripts/script.utils.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2019 Adobe. All rights reserved.
3 | This file is licensed to you under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License. You may obtain a copy
5 | of the License at http://www.apache.org/licenses/LICENSE-2.0
6 |
7 | Unless required by applicable law or agreed to in writing, software distributed under
8 | the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9 | OF ANY KIND, either express or implied. See the License for the specific language
10 | governing permissions and limitations under the License.
11 | */
12 | const fs = require('fs-extra') // promises
13 | const path = require('path')
14 | const mime = require('mime-types')
15 | const request = require('request-promise')
16 |
17 | // s3 utils
18 | /**
19 | * Checks if s3 prefix exists
20 | * @param {} s3Client with bucket configured
21 | * @param {string} [prefix = '']
22 | */
23 | async function folderExists (s3Client, prefix = '') {
24 | const listParams = {
25 | Prefix: prefix
26 | }
27 | const listedObjects = await s3Client.listObjectsV2(listParams).promise()
28 | return listedObjects.Contents.length !== 0
29 | }
30 | /**
31 | * Deletes all files in a s3 prefix location
32 | * @param {} s3Client with bucket configured
33 | * @param {string} [prefix = '']
34 | */
35 | async function emptyFolder (s3Client, prefix = '') {
36 | const listParams = {
37 | Prefix: prefix
38 | }
39 | const listedObjects = await s3Client.listObjectsV2(listParams).promise()
40 | if (listedObjects.Contents.length === 0) return
41 | const deleteParams = {
42 | Delete: { Objects: [] }
43 | }
44 | listedObjects.Contents.forEach(({ Key }) => {
45 | deleteParams.Delete.Objects.push({ Key })
46 | })
47 | await s3Client.deleteObjects(deleteParams).promise()
48 | if (listedObjects.IsTruncated) await emptyFolder()
49 | }
50 | /**
51 | * Uploads a file to s3
52 | * @param {string} file
53 | * @param {string} [prefix = ''] - s3 prefix to upload the file to
54 | */
55 | async function uploadFile (s3Client, file, prefix = '') {
56 | const content = await fs.readFile(file)
57 | const uploadParams = {
58 | Key: urlJoin(prefix, path.basename(file)),
59 | Body: content,
60 | ACL: 'public-read',
61 | // s3 misses some mime types like for css files
62 | ContentType: mime.lookup(path.extname(file))
63 | }
64 | return s3Client.upload(uploadParams).promise()
65 | }
66 | /**
67 | * Uploads all files in a dir to s3 - flat, no recursion support
68 | * @param {} s3Client with bucket configured
69 | * @param {string} [prefix = ''] - s3 prefix to upload the dir to
70 | * @param {string} dir - directory with files to upload
71 | * @param {function} [postFileUploadCallback] - called for each uploaded file
72 | */
73 | async function uploadDir (s3Client, prefix = '', dir, postFileUploadCallback) {
74 | async function _filterFiles (files) {
75 | const bools = await Promise.all(files.map(async f => (await fs.stat(f)).isFile()))
76 | return files.filter(f => bools.shift())
77 | }
78 |
79 | const files = await _filterFiles((await fs.readdir(dir)).map(f => path.join(dir, f)))
80 |
81 | // parallel upload
82 | return Promise.all(files.map(async f => {
83 | const s3Res = await uploadFile(s3Client, f, prefix)
84 | if (postFileUploadCallback) postFileUploadCallback(f)
85 | return s3Res
86 | }))
87 | }
88 | /**
89 | * @param {string} tvmUrl
90 | * @param {string} owNamespace
91 | * @param {string} owAuth
92 | * @param {string} cacheCredsFile=''
93 | */
94 | async function getTmpS3Credentials (tvmUrl, owNamespace, owAuth, cacheCredsFile = '') {
95 | async function _cacheCredentialsToFile (cacheCredsFile, cacheKey, creds) {
96 | if (!cacheCredsFile) return null
97 |
98 | let allCreds
99 | try {
100 | allCreds = require(cacheCredsFile)
101 | } catch (e) {
102 | allCreds = {} // cache file does not exist
103 | }
104 |
105 | // need to store by namespace in case user changes namespace in config
106 | allCreds[cacheKey] = creds
107 | fs.writeFileSync(cacheCredsFile, JSON.stringify(allCreds))
108 |
109 | return true
110 | }
111 | async function _getCredentialsFromTVM (tvmUrl, owNamespace, owAuth) {
112 | return request(tvmUrl, {
113 | json: {
114 | owNamespace: owNamespace,
115 | owAuth: owAuth
116 | }
117 | })
118 | }
119 | function _getCredentialsFromCacheFile (cacheCredsFile, cacheKey) {
120 | if (!cacheCredsFile) return null
121 |
122 | let creds
123 | try {
124 | creds = require(cacheCredsFile)[cacheKey]
125 | } catch (e) {
126 | return null // cache file does not exist
127 | }
128 | if (!creds) return null // credentials for namespace do not exist
129 | if (Date.parse(creds.expiration) < (Date.now() - 60000)) return null
130 | return creds
131 | }
132 | const cacheKey = `${owNamespace}-${tvmUrl}`
133 | let creds = _getCredentialsFromCacheFile(cacheCredsFile, cacheKey)
134 | if (!creds) {
135 | creds = await _getCredentialsFromTVM(tvmUrl, owNamespace, owAuth)
136 | _cacheCredentialsToFile(cacheCredsFile, cacheKey, creds)
137 | }
138 | return creds
139 | }
140 |
141 | /**
142 | * Zip a folder using archiver
143 | * @param {String} dir
144 | * @param {String} out
145 | * @returns {Promise}
146 | */
147 | function zipFolder (dir, out) {
148 | const archive = require('archiver')('zip', { zlib: { level: 9 } })
149 | const stream = fs.createWriteStream(out)
150 |
151 | return new Promise((resolve, reject) => {
152 | archive
153 | .directory(dir, false)
154 | .on('error', err => reject(err))
155 | .pipe(stream)
156 |
157 | stream.on('close', () => resolve())
158 | archive.finalize()
159 | })
160 | }
161 |
162 | /**
163 | * Joins url path parts
164 | * @param {...string} args url parts
165 | * @returns {string}
166 | */
167 | function urlJoin (...args) {
168 | let start = ''
169 | if (args[0] && args[0].startsWith('/')) start = '/'
170 | return start + args.map(a => a && a.replace(/(^\/|\/$)/g, '')).filter(a => a).join('/')
171 | }
172 | /**
173 | * Wraps a function and makes sure to exit the process with an error code if
174 | * there is an error
175 | * @param {function} f can be sync or async
176 | * @returns {Promise}
177 | */
178 | async function runAsScript (f) {
179 | try {
180 | await f()
181 | } catch (e) {
182 | console.error(e)
183 | process.exit(1)
184 | }
185 | }
186 |
187 | module.exports = {
188 | s3: {
189 | folderExists: folderExists,
190 | emptyFolder: emptyFolder,
191 | uploadFile: uploadFile,
192 | uploadDir: uploadDir
193 | },
194 | getTmpS3Credentials: getTmpS3Credentials,
195 | zipFolder: zipFolder,
196 | urlJoin: urlJoin,
197 | runAsScript: runAsScript
198 | }
199 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2019 Adobe
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------