diff options
author | Christoph Wurst <christoph@winzerhof-wurst.at> | 2018-10-19 11:33:27 +0300 |
---|---|---|
committer | Christoph Wurst <christoph@winzerhof-wurst.at> | 2018-10-22 17:22:00 +0300 |
commit | 0bb0a8e61b1d835e5d9f8f2553c01441fd989d1a (patch) | |
tree | fbea358a1e1c178d672fb48c7d28eaabb53cb6ec /src | |
parent | 4da04fd450d7ce3a6ab6da399ddd95899c78d51b (diff) |
Migrate to Vue
Signed-off-by: Christoph Wurst <christoph@winzerhof-wurst.at>
Diffstat (limited to 'src')
-rw-r--r-- | src/components/AddDeviceDialog.vue | 193 | ||||
-rw-r--r-- | src/components/Device.vue | 112 | ||||
-rw-r--r-- | src/components/PersonalSettings.vue | 73 | ||||
-rw-r--r-- | src/main-settings.js | 46 | ||||
-rw-r--r-- | src/mixins/Nextcloud.js | 26 | ||||
-rw-r--r-- | src/services/RegistrationService.js | 47 | ||||
-rw-r--r-- | src/store.js | 45 | ||||
-rw-r--r-- | src/tests/components/Device.spec.js | 98 | ||||
-rw-r--r-- | src/tests/components/PersonalSettings.spec.js | 81 | ||||
-rw-r--r-- | src/tests/setup.js | 34 | ||||
-rw-r--r-- | src/webpack.common.js | 51 | ||||
-rw-r--r-- | src/webpack.dev.js | 7 | ||||
-rw-r--r-- | src/webpack.prod.js | 7 | ||||
-rw-r--r-- | src/webpack.test.js | 30 |
14 files changed, 850 insertions, 0 deletions
diff --git a/src/components/AddDeviceDialog.vue b/src/components/AddDeviceDialog.vue new file mode 100644 index 0000000..8a72126 --- /dev/null +++ b/src/components/AddDeviceDialog.vue @@ -0,0 +1,193 @@ +<!-- + - @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + - + - @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<template> + <div v-if="step === RegistrationSteps.READY"> + <button + v-on:click="start">{{ t('twofactor_u2f', 'Add U2F device') }} + </button> + </div> + + <div v-else-if="step === RegistrationSteps.U2F_REGISTRATION" + class="new-u2f-device"> + <span class="icon-loading-small u2f-loading"></span> + {{ t('twofactor_u2f', 'Please plug in your U2F device and press the device button to authorize.') }} + </div> + + <div v-else-if="step === RegistrationSteps.NAMING" + class="new-u2f-device"> + <span class="icon-loading-small u2f-loading"></span> + <input type="text" + :placeholder="t('twofactor_u2f', 'Name your device')" + v-model="name"> + <button v-on:click="submit">{{ t('twofactor_u2f', 'Add') }}</button> + </div> + + <div v-else-if="step === RegistrationSteps.PERSIST" + class="new-u2f-device"> + <span class="icon-loading-small u2f-loading"></span> + {{ t('twofactor_u2f', 'Adding your device …') }} + </div> + + <div v-else> + Invalid registration step. This should not have happened. + </div> +</template> + +<script> + import confirmPassword from 'nextcloud-password-confirmation' + import u2f from 'u2f-api' + + import { + startRegistration, + finishRegistration + } from '../services/RegistrationService' + + const logAndPass = (text) => (data) => { + console.debug(text) + return data + } + + const RegistrationSteps = Object.freeze({ + READY: 1, + U2F_REGISTRATION: 2, + NAMING: 3, + PERSIST: 4, + }) + + export default { + name: 'AddDeviceDialog', + props: { + httpWarning: Boolean + }, + data () { + return { + name: '', + registrationData: {}, + RegistrationSteps, + step: RegistrationSteps.READY, + } + }, + methods: { + start () { + this.step = RegistrationSteps.U2F_REGISTRATION + + return confirmPassword() + .then(this.getRegistrationData) + .then(this.register) + .then(() => this.step = RegistrationSteps.NAMING) + .catch(console.error.bind(this)) + }, + + getRegistrationData () { + return startRegistration() + .catch(err => { + console.error('Error getting u2f registration data from server', err) + throw new Error(t('twofactor_u2f', 'Server error while trying to add U2F device')) + }) + }, + + register ({req, sigs}) { + console.debug('starting u2f registration') + + return u2f.register([req], sigs) + .then(data => { + console.debug('u2f registration was successful', data) + + if (data.errorCode && data.errorCode !== 0) { + return this.rejectRegistration(data) + } + + this.registrationData = data + }) + }, + + rejectRegistration (data) { + // https://developers.yubico.com/U2F/Libraries/Client_error_codes.html + switch (data.errorCode) { + case 4: + // 4 - DEVICE_INELIGIBLE + Promise.reject(new Error(t('twofactor_u2f', 'U2F device is already registered (error code {errorCode})', { + errorCode: data.errorCode + }))); + break; + case 5: + // 5 - TIMEOUT + Promise.reject(new Error(t('twofactor_u2f', 'U2F device registration timeout reached (error code {errorCode})', { + errorCode: data.errorCode + }))); + break; + default: + // 1 - OTHER_ERROR + // 2 - BAD_REQUEST + // 3 - CONFIGURATION_UNSUPPORTED + Promise.reject(new Error(t('twofactor_u2f', 'U2F device registration failed (error code {errorCode})', { + errorCode: data.errorCode + }))); + } + }, + + submit () { + this.step = RegistrationSteps.PERSIST + + return confirmPassword() + .then(logAndPass('confirmed password')) + .then(this.saveRegistrationData) + .then(logAndPass('registration data saved')) + .then(() => this.reset()) + .then(logAndPass('app reset')) + .catch(console.error.bind(this)) + }, + + saveRegistrationData () { + const data = this.registrationData + data.name = this.name + + return finishRegistration(data) + .then(device => this.$store.commit('addDevice', device)) + .then(logAndPass('new device added to store')) + .catch(err => { + console.error('Error persisting u2f registration', err) + throw new Error(t('twofactor_u2f', 'Server error while trying to complete U2F device registration')) + }) + }, + + reset () { + this.name = '' + this.registrationData = {} + this.step = RegistrationSteps.READY + } + } + } +</script> + +<style scoped> + .u2f-loading { + display: inline-block; + vertical-align: sub; + margin-left: 2px; + margin-right: 2px; + } + + .new-u2f-device { + line-height: 300%; + } +</style>
\ No newline at end of file diff --git a/src/components/Device.vue b/src/components/Device.vue new file mode 100644 index 0000000..6344d17 --- /dev/null +++ b/src/components/Device.vue @@ -0,0 +1,112 @@ +<!-- + - @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + - + - @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<template> + <div class="u2f-device" :data-u2f-id="id"> + <span class="icon-u2f-device"></span> + {{name || t('twofactor_u2f', 'Unnamed device') }} + <span class="more"> + <a class="icon icon-more" + v-on:click.stop="togglePopover"></a> + <div class="popovermenu" + :class="{open: showPopover}" + v-click-outside="hidePopover"> + <PopoverMenu :menu="menu"/> + </div> + </span> + </div> +</template> + +<script> + import ClickOutside from 'vue-click-outside' + import confirmPassword from 'nextcloud-password-confirmation' + import {PopoverMenu} from 'nextcloud-vue' + + export default { + name: 'Device', + props: { + id: Number, + name: String, + }, + components: { + PopoverMenu + }, + directives: { + ClickOutside + }, + data () { + return { + showPopover: false, + menu: [ + { + text: t('twofactor_u2f', 'Remove'), + icon: 'icon-delete', + action: () => { + confirmPassword() + .then(() => this.$store.dispatch('removeDevice', this.id)) + } + } + ] + } + }, + methods: { + togglePopover () { + this.showPopover = !this.showPopover + }, + + hidePopover () { + this.showPopover = false + } + } + } +</script> + +<style scoped> + .u2f-device { + line-height: 300%; + display: flex; + } + + .u2f-device .more { + position: relative; + } + + .u2f-device .more .icon-more { + display: inline-block; + width: 16px; + height: 16px; + padding-left: 20px; + vertical-align: middle; + opacity: .7; + } + + .u2f-device .popovermenu { + right: -12px; + top: 42px; + } + + .icon-u2f-device { + display: inline-block; + background-size: 100%; + padding: 3px; + margin: 3px; + } +</style>
\ No newline at end of file diff --git a/src/components/PersonalSettings.vue b/src/components/PersonalSettings.vue new file mode 100644 index 0000000..559f01b --- /dev/null +++ b/src/components/PersonalSettings.vue @@ -0,0 +1,73 @@ +<!-- + - @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + - + - @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + - + - @license GNU AGPL version 3 or any later version + - + - This program is free software: you can redistribute it and/or modify + - it under the terms of the GNU Affero General Public License as + - published by the Free Software Foundation, either version 3 of the + - License, or (at your option) any later version. + - + - This program is distributed in the hope that it will be useful, + - but WITHOUT ANY WARRANTY; without even the implied warranty of + - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + - GNU Affero General Public License for more details. + - + - You should have received a copy of the GNU Affero General Public License + - along with this program. If not, see <http://www.gnu.org/licenses/>. + --> + +<template> + <div> + <p v-if="devices.length === 0">{{ t('twofactor_u2f', 'No U2F devices configured. You are not using U2F as second factor at the moment.') }}</p> + <p v-else>{{ t('twofactor_u2f', 'The following devices are configured for U2F second-factor authentication:') }}</p> + <Device v-for="device in devices" + :key="device.id" + :id="device.id" + :name="device.name" /> + + <AddDeviceDialog :httpWarning="httpWarning" /> + <p v-if="notSupported"> + {{ t('twofactor_u2f', 'Your browser does not support u2f.') }} + {{ t('twofactor_u2f', 'Chrome is the only browser that supports U2F devices. You need to install the "U2F Support Add-on" on Firefox to use U2F.') }} + </p> + <p v-if="httpWarning" + id="u2f-http-warning"> + {{ t('twofactor_u2f', 'You are accessing this site via an insecure connection. Browsers might therefore refuse the U2F authentication.') }} + </p> + </div> +</template> + +<script> + import u2f from 'u2f-api' + + import AddDeviceDialog from './AddDeviceDialog' + import Device from './Device' + + export default { + name: 'PersonalSettings', + props: { + httpWarning: Boolean + }, + components: { + AddDeviceDialog, + Device + }, + data() { + return { + notSupported: !u2f.isSupported() + } + }, + computed: { + devices() { + return this.$store.state.devices + } + } + } +</script> + +<style scoped> + +</style>
\ No newline at end of file diff --git a/src/main-settings.js b/src/main-settings.js new file mode 100644 index 0000000..9bd0c03 --- /dev/null +++ b/src/main-settings.js @@ -0,0 +1,46 @@ +/* + * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import store from './store' +import Vue from 'vue' + +import Nextcloud from './mixins/Nextcloud' + +Vue.mixin(Nextcloud) + +const initialStateElement = document.getElementById('twofactor-u2f-initial-state') +if (initialStateElement) { + const devices = JSON.parse(initialStateElement.value) + devices.sort((d1, d2) => d1.name.localeCompare(d2.name)) + store.replaceState({ + devices + }) +} + +import PersonalSettings from './components/PersonalSettings' + +const View = Vue.extend(PersonalSettings) +new View({ + propsData: { + httpWarning: document.location.protocol !== 'https:', + }, + store, +}).$mount('#twofactor-u2f-settings') diff --git a/src/mixins/Nextcloud.js b/src/mixins/Nextcloud.js new file mode 100644 index 0000000..8e35420 --- /dev/null +++ b/src/mixins/Nextcloud.js @@ -0,0 +1,26 @@ +/* + * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +export default { + methods: { + t, + } +} diff --git a/src/services/RegistrationService.js b/src/services/RegistrationService.js new file mode 100644 index 0000000..241ffcb --- /dev/null +++ b/src/services/RegistrationService.js @@ -0,0 +1,47 @@ +/* + * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import Axios from 'nextcloud-axios' +import {generateUrl} from 'nextcloud-server/dist/router' + +export function startRegistration () { + const url = generateUrl('/apps/twofactor_u2f/settings/startregister') + + return Axios.post(url) + .then(resp => resp.data) +} + +export function finishRegistration (data) { + const url = generateUrl('/apps/twofactor_u2f/settings/finishregister') + + return Axios.post(url, data) + .then(resp => resp.data) +} + +export function removeRegistration (id) { + const url = generateUrl('/apps/twofactor_u2f/settings/remove') + const data = { + id + } + + return Axios.post(url, data) + .then(resp => resp.data) +} diff --git a/src/store.js b/src/store.js new file mode 100644 index 0000000..2590776 --- /dev/null +++ b/src/store.js @@ -0,0 +1,45 @@ +import Vue from 'vue' +import Vuex from 'vuex' + +import {removeRegistration} from './services/RegistrationService' + +Vue.use(Vuex) + +export const mutations = { + addDevice (state, device) { + state.devices.push(device) + state.devices.sort((d1, d2) => d1.name.localeCompare(d2.name)) + }, + + removeDevice (state, id) { + state.devices = state.devices.filter(device => device.id !== id) + } +} + +export const actions = { + removeDevice ({state, commit}, id) { + const device = state.devices[id] + + commit('removeDevice', id) + + removeRegistration(id) + .catch(err => { + // Rollback + commit('addDevice', device) + + throw err + }) + } +} + +export const getters = {} + +export default new Vuex.Store({ + strict: process.env.NODE_ENV !== 'production', + state: { + devices: [] + }, + getters, + mutations, + actions +}) diff --git a/src/tests/components/Device.spec.js b/src/tests/components/Device.spec.js new file mode 100644 index 0000000..dad0ac9 --- /dev/null +++ b/src/tests/components/Device.spec.js @@ -0,0 +1,98 @@ +/* + * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import {shallowMount, createLocalVue} from '@vue/test-utils' +import Vuex from 'vuex' + +import Nextcloud from '../../mixins/Nextcloud' + +const localVue = createLocalVue() + +localVue.use(Vuex) +localVue.mixin(Nextcloud) + +import Device from '../../components/Device' + +describe('Device component', () => { + var actions + var store + + beforeEach(() => { + actions = {} + store = new Vuex.Store({ + state: { + devices: [] + }, + actions + }) + }) + + it('renders devices without a name', () => { + store.state.devices.push({ + id: 1, + name: undefined + }) + const device = shallowMount(Device, { + store, + localVue + }) + + expect(device.text()).to.have.string('Unnamed device') + }) + + it('has a closed popover menu by default', () => { + const device = shallowMount(Device, { + store, + localVue + }) + + expect(device.contains('.popovermenu.open')).to.be.false + }) + + it('opens popover menu on click', done => { + const device = shallowMount(Device, { + store, + localVue + }) + + device.find('.icon-more').trigger('click') + + localVue.nextTick(() => { + expect(device.vm.showPopover).to.be.true + done() + }) + }) + + it('closed popover menu on second click', done => { + const device = shallowMount(Device, { + store, + localVue + }) + device.vm.showPopover = true + + device.find('.icon-more').trigger('click') + + localVue.nextTick(() => { + expect(device.vm.showPopover).to.be.false + done() + }) + }) +})
\ No newline at end of file diff --git a/src/tests/components/PersonalSettings.spec.js b/src/tests/components/PersonalSettings.spec.js new file mode 100644 index 0000000..ef74937 --- /dev/null +++ b/src/tests/components/PersonalSettings.spec.js @@ -0,0 +1,81 @@ +/* + * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +import {shallowMount, createLocalVue} from '@vue/test-utils' +import Vuex from 'vuex' + +import Nextcloud from '../../mixins/Nextcloud' + +const localVue = createLocalVue() + +localVue.use(Vuex) +localVue.mixin(Nextcloud) + +import PersonalSettings from '../../components/PersonalSettings' + +describe('Device component', () => { + var actions + var store + + beforeEach(() => { + actions = {} + store = new Vuex.Store({ + state: { + devices: [] + }, + actions + }) + }) + + it('shows text if no devices are configured', () => { + const settings = shallowMount(PersonalSettings, { + store, + localVue + }) + + expect(settings.text()).to.contain('No U2F devices configured') + }) + + it('shows no info text if devices are configured', () => { + const settings = shallowMount(PersonalSettings, { + store, + localVue + }) + store.state.devices.push({ + id: 1, + name: 'a' + }) + + expect(settings.text()).to.not.contain('No U2F devices configured') + }) + + it('shows a HTTP warning', () => { + const settings = shallowMount(PersonalSettings, { + store, + localVue, + propsData: { + httpWarning: true + } + }) + + expect(settings.text()).to.contain('You are accessing this site via an') + }) +}) diff --git a/src/tests/setup.js b/src/tests/setup.js new file mode 100644 index 0000000..ed7cd78 --- /dev/null +++ b/src/tests/setup.js @@ -0,0 +1,34 @@ +/* + * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +require('jsdom-global')() + +const t = (app, str) => str + +require('vue').mixin({ + methods: { + t + } +}) + +global.expect = require('chai').expect +global.OC = {} +global.t = t diff --git a/src/webpack.common.js b/src/webpack.common.js new file mode 100644 index 0000000..789da78 --- /dev/null +++ b/src/webpack.common.js @@ -0,0 +1,51 @@ +const path = require('path'); +const { VueLoaderPlugin } = require('vue-loader'); + +module.exports = { + entry: path.join(__dirname, 'main-settings.js'), + output: { + path: path.resolve(__dirname, '../js'), + publicPath: '/js/', + filename: 'settings.js' + }, + module: { + rules: [ + { + test: /\.css$/, + use: ['vue-style-loader', 'css-loader'] + }, + { + test: /\.scss$/, + use: ['vue-style-loader', 'css-loader', 'sass-loader'] + }, + { + test: /\.vue$/, + loader: 'vue-loader' + }, + { + test: /\.js$/, + loader: 'babel-loader', + exclude: /node_modules/ + }, + { + test: /\.(png|jpg|gif)$/, + loader: 'file-loader', + options: { + name: '[name].[ext]?[hash]' + } + }, + { + test: /\.(svg)$/i, + use: [ + { + loader: 'url-loader' + } + ] + } + ] + }, + plugins: [new VueLoaderPlugin()], + resolve: { + extensions: ['*', '.js', '.vue'] + } +};
\ No newline at end of file diff --git a/src/webpack.dev.js b/src/webpack.dev.js new file mode 100644 index 0000000..da0084b --- /dev/null +++ b/src/webpack.dev.js @@ -0,0 +1,7 @@ +const merge = require('webpack-merge'); +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'development', + devtool: 'source-map', +}) diff --git a/src/webpack.prod.js b/src/webpack.prod.js new file mode 100644 index 0000000..8e5e2f8 --- /dev/null +++ b/src/webpack.prod.js @@ -0,0 +1,7 @@ +const merge = require('webpack-merge') +const common = require('./webpack.common.js') + +module.exports = merge(common, { + mode: 'production', + devtool: 'source-map' +}) diff --git a/src/webpack.test.js b/src/webpack.test.js new file mode 100644 index 0000000..8630396 --- /dev/null +++ b/src/webpack.test.js @@ -0,0 +1,30 @@ +/* + * @copyright 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @author 2018 Christoph Wurst <christoph@winzerhof-wurst.at> + * + * @license GNU AGPL version 3 or any later version + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +const merge = require('webpack-merge'); +const common = require('./webpack.common.js'); +const nodeExternals = require('webpack-node-externals') + +module.exports = merge(common, { + mode: 'development', + devtool: 'inline-cheap-module-source-map', + externals: [nodeExternals()] +}) |