Merge branch 'develop' of https://github.com/misskey-dev/misskey into develop
This commit is contained in:
commit
71c230b7b7
18 changed files with 85 additions and 167 deletions
4
.github/labeler.yml
vendored
4
.github/labeler.yml
vendored
|
@ -4,5 +4,9 @@
|
||||||
'🖥️Client':
|
'🖥️Client':
|
||||||
- packages/client/**/*
|
- packages/client/**/*
|
||||||
|
|
||||||
|
'🧪Test':
|
||||||
|
- cypress/**/*
|
||||||
|
- packages/backend/test/**/*
|
||||||
|
|
||||||
'‼️ wrong locales':
|
'‼️ wrong locales':
|
||||||
- any: ['locales/*.yml', '!locales/ja-JP.yml']
|
- any: ['locales/*.yml', '!locales/ja-JP.yml']
|
||||||
|
|
4
.github/workflows/labeler.yml
vendored
4
.github/workflows/labeler.yml
vendored
|
@ -1,6 +1,8 @@
|
||||||
name: "Pull Request Labeler"
|
name: "Pull Request Labeler"
|
||||||
on:
|
on:
|
||||||
- pull_request_target
|
pull_request_target:
|
||||||
|
branches-ignore:
|
||||||
|
- 'l10n_develop'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
triage:
|
triage:
|
||||||
|
|
|
@ -26,7 +26,7 @@ You should also include the user name that made the change.
|
||||||
Your own theme color may be unset if it was in an invalid format.
|
Your own theme color may be unset if it was in an invalid format.
|
||||||
Admins should check their instance settings if in doubt.
|
Admins should check their instance settings if in doubt.
|
||||||
- Perform port diagnosis at startup only when Listen fails @mei23
|
- Perform port diagnosis at startup only when Listen fails @mei23
|
||||||
- Rate limiting is now also usable for non-authenticated users. @Johann150
|
- Rate limiting is now also usable for non-authenticated users. @Johann150 @mei23
|
||||||
Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
|
Admins should make sure the reverse proxy sets the `X-Forwarded-For` header to the original address.
|
||||||
|
|
||||||
### Bugfixes
|
### Bugfixes
|
||||||
|
@ -43,6 +43,7 @@ You should also include the user name that made the change.
|
||||||
- Server: use correct order of attachments on notes @Johann150
|
- Server: use correct order of attachments on notes @Johann150
|
||||||
- Server: prevent crash when processing certain PNGs @syuilo
|
- Server: prevent crash when processing certain PNGs @syuilo
|
||||||
- Server: Fix unable to generate video thumbnails @mei23
|
- Server: Fix unable to generate video thumbnails @mei23
|
||||||
|
- Server: Fix `Cannot find module` issue @mei23
|
||||||
|
|
||||||
## 12.110.1 (2022/04/23)
|
## 12.110.1 (2022/04/23)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,6 @@
|
||||||
describe('Before setup instance', () => {
|
describe('Before setup instance', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.window(win => {
|
cy.resetState();
|
||||||
win.indexedDB.deleteDatabase('keyval-store');
|
|
||||||
});
|
|
||||||
cy.request('POST', '/api/reset-db').as('reset');
|
|
||||||
cy.get('@reset').its('status').should('equal', 204);
|
|
||||||
cy.reload(true);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -35,18 +30,10 @@ describe('Before setup instance', () => {
|
||||||
|
|
||||||
describe('After setup instance', () => {
|
describe('After setup instance', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.window(win => {
|
cy.resetState();
|
||||||
win.indexedDB.deleteDatabase('keyval-store');
|
|
||||||
});
|
|
||||||
cy.request('POST', '/api/reset-db').as('reset');
|
|
||||||
cy.get('@reset').its('status').should('equal', 204);
|
|
||||||
cy.reload(true);
|
|
||||||
|
|
||||||
// インスタンス初期セットアップ
|
// インスタンス初期セットアップ
|
||||||
cy.request('POST', '/api/admin/accounts/create', {
|
cy.registerUser('admin', 'pass', true);
|
||||||
username: 'admin',
|
|
||||||
password: 'pass',
|
|
||||||
}).its('body').as('admin');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -76,24 +63,13 @@ describe('After setup instance', () => {
|
||||||
|
|
||||||
describe('After user signup', () => {
|
describe('After user signup', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.window(win => {
|
cy.resetState();
|
||||||
win.indexedDB.deleteDatabase('keyval-store');
|
|
||||||
});
|
|
||||||
cy.request('POST', '/api/reset-db').as('reset');
|
|
||||||
cy.get('@reset').its('status').should('equal', 204);
|
|
||||||
cy.reload(true);
|
|
||||||
|
|
||||||
// インスタンス初期セットアップ
|
// インスタンス初期セットアップ
|
||||||
cy.request('POST', '/api/admin/accounts/create', {
|
cy.registerUser('admin', 'pass', true);
|
||||||
username: 'admin',
|
|
||||||
password: 'pass',
|
|
||||||
}).its('body').as('admin');
|
|
||||||
|
|
||||||
// ユーザー作成
|
// ユーザー作成
|
||||||
cy.request('POST', '/api/signup', {
|
cy.registerUser('alice', 'alice1234');
|
||||||
username: 'alice',
|
|
||||||
password: 'alice1234',
|
|
||||||
}).its('body').as('alice');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
@ -138,34 +114,15 @@ describe('After user signup', () => {
|
||||||
|
|
||||||
describe('After user singed in', () => {
|
describe('After user singed in', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.window(win => {
|
cy.resetState();
|
||||||
win.indexedDB.deleteDatabase('keyval-store');
|
|
||||||
});
|
|
||||||
cy.request('POST', '/api/reset-db').as('reset');
|
|
||||||
cy.get('@reset').its('status').should('equal', 204);
|
|
||||||
cy.reload(true);
|
|
||||||
|
|
||||||
// インスタンス初期セットアップ
|
// インスタンス初期セットアップ
|
||||||
cy.request('POST', '/api/admin/accounts/create', {
|
cy.registerUser('admin', 'pass', true);
|
||||||
username: 'admin',
|
|
||||||
password: 'pass',
|
|
||||||
}).its('body').as('admin');
|
|
||||||
|
|
||||||
// ユーザー作成
|
// ユーザー作成
|
||||||
cy.request('POST', '/api/signup', {
|
cy.registerUser('alice', 'alice1234');
|
||||||
username: 'alice',
|
|
||||||
password: 'alice1234',
|
|
||||||
}).its('body').as('alice');
|
|
||||||
|
|
||||||
cy.visit('/');
|
cy.login('alice', 'alice1234');
|
||||||
|
|
||||||
cy.intercept('POST', '/api/signin').as('signin');
|
|
||||||
|
|
||||||
cy.get('[data-cy-signin]').click();
|
|
||||||
cy.get('[data-cy-signin-username] input').type('alice');
|
|
||||||
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
|
|
||||||
|
|
||||||
cy.wait('@signin').as('signedIn');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
|
@ -1,34 +1,15 @@
|
||||||
describe('After user signed in', () => {
|
describe('After user signed in', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.window(win => {
|
cy.resetState();
|
||||||
win.indexedDB.deleteDatabase('keyval-store');
|
|
||||||
});
|
|
||||||
cy.viewport('macbook-16');
|
cy.viewport('macbook-16');
|
||||||
cy.request('POST', '/api/reset-db').as('reset');
|
|
||||||
cy.get('@reset').its('status').should('equal', 204);
|
|
||||||
cy.reload(true);
|
|
||||||
|
|
||||||
// インスタンス初期セットアップ
|
// インスタンス初期セットアップ
|
||||||
cy.request('POST', '/api/admin/accounts/create', {
|
cy.registerUser('admin', 'pass', true);
|
||||||
username: 'admin',
|
|
||||||
password: 'pass',
|
|
||||||
}).its('body').as('admin');
|
|
||||||
|
|
||||||
// ユーザー作成
|
// ユーザー作成
|
||||||
cy.request('POST', '/api/signup', {
|
cy.registerUser('alice', 'alice1234');
|
||||||
username: 'alice',
|
|
||||||
password: 'alice1234',
|
|
||||||
}).its('body').as('alice');
|
|
||||||
|
|
||||||
cy.visit('/');
|
cy.login('alice', 'alice1234');
|
||||||
|
|
||||||
cy.intercept('POST', '/api/signin').as('signin');
|
|
||||||
|
|
||||||
cy.get('[data-cy-signin]').click();
|
|
||||||
cy.get('[data-cy-signin-username] input').type('alice');
|
|
||||||
cy.get('[data-cy-signin-password] input').type('alice1234{enter}');
|
|
||||||
|
|
||||||
cy.wait('@signin').as('signedIn');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
|
|
|
@ -23,3 +23,33 @@
|
||||||
//
|
//
|
||||||
// -- This will overwrite an existing command --
|
// -- This will overwrite an existing command --
|
||||||
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
|
|
||||||
|
Cypress.Commands.add('resetState', () => {
|
||||||
|
cy.window(win => {
|
||||||
|
win.indexedDB.deleteDatabase('keyval-store');
|
||||||
|
});
|
||||||
|
cy.request('POST', '/api/reset-db').as('reset');
|
||||||
|
cy.get('@reset').its('status').should('equal', 204);
|
||||||
|
cy.reload(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => {
|
||||||
|
const route = isAdmin ? '/api/admin/accounts/create' : '/api/signup';
|
||||||
|
|
||||||
|
cy.request('POST', route, {
|
||||||
|
username: username,
|
||||||
|
password: password,
|
||||||
|
}).its('body').as(username);
|
||||||
|
});
|
||||||
|
|
||||||
|
Cypress.Commands.add('login', (username, password) => {
|
||||||
|
cy.visit('/');
|
||||||
|
|
||||||
|
cy.intercept('POST', '/api/signin').as('signin');
|
||||||
|
|
||||||
|
cy.get('[data-cy-signin]').click();
|
||||||
|
cy.get('[data-cy-signin-username] input').type(username);
|
||||||
|
cy.get('[data-cy-signin-password] input').type(`${password}{enter}`);
|
||||||
|
|
||||||
|
cy.wait('@signin').as('signedIn');
|
||||||
|
});
|
||||||
|
|
|
@ -6,6 +6,9 @@ const urlRegex = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+/;
|
||||||
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
|
const urlRegexFull = /^https?:\/\/[\w\/:%#@$&?!()\[\]~.,=+\-]+$/;
|
||||||
|
|
||||||
export function fromHtml(html: string, hashtagNames?: string[]): string {
|
export function fromHtml(html: string, hashtagNames?: string[]): string {
|
||||||
|
// some AP servers like Pixelfed use br tags as well as newlines
|
||||||
|
html = html.replace(/<br\s?\/?>\r?\n/gi, '\n');
|
||||||
|
|
||||||
const dom = parse5.parseFragment(html);
|
const dom = parse5.parseFragment(html);
|
||||||
|
|
||||||
let text = '';
|
let text = '';
|
||||||
|
|
9
packages/backend/src/misc/get-ip-hash.ts
Normal file
9
packages/backend/src/misc/get-ip-hash.ts
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import IPCIDR from 'ip-cidr';
|
||||||
|
|
||||||
|
export function getIpHash(ip: string) {
|
||||||
|
// because a single person may control many IPv6 addresses,
|
||||||
|
// only a /64 subnet prefix of any IP will be taken into account.
|
||||||
|
// (this means for IPv4 the entire address is used)
|
||||||
|
const prefix = IPCIDR.createAddress(ip).mask(64);
|
||||||
|
return 'ip-' + BigInt('0b' + prefix).toString(36);
|
||||||
|
}
|
|
@ -305,11 +305,13 @@ export default function() {
|
||||||
systemQueue.add('resyncCharts', {
|
systemQueue.add('resyncCharts', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { cron: '0 0 * * *' },
|
repeat: { cron: '0 0 * * *' },
|
||||||
|
removeOnComplete: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
systemQueue.add('cleanCharts', {
|
systemQueue.add('cleanCharts', {
|
||||||
}, {
|
}, {
|
||||||
repeat: { cron: '0 0 * * *' },
|
repeat: { cron: '0 0 * * *' },
|
||||||
|
removeOnComplete: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
systemQueue.add('checkExpiredMutings', {
|
systemQueue.add('checkExpiredMutings', {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import endpoints, { IEndpointMeta } from './endpoints.js';
|
||||||
import { ApiError } from './error.js';
|
import { ApiError } from './error.js';
|
||||||
import { apiLogger } from './logger.js';
|
import { apiLogger } from './logger.js';
|
||||||
import { AccessToken } from '@/models/entities/access-token.js';
|
import { AccessToken } from '@/models/entities/access-token.js';
|
||||||
import IPCIDR from 'ip-cidr';
|
import { getIpHash } from '@/misc/get-ip-hash.js';
|
||||||
|
|
||||||
const accessDenied = {
|
const accessDenied = {
|
||||||
message: 'Access denied.',
|
message: 'Access denied.',
|
||||||
|
@ -33,18 +33,13 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi
|
||||||
throw new ApiError(accessDenied);
|
throw new ApiError(accessDenied);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ep.meta.requireCredential && ep.meta.limit && !isModerator) {
|
if (ep.meta.limit && !isModerator) {
|
||||||
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
|
// koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app.
|
||||||
let limitActor: string;
|
let limitActor: string;
|
||||||
if (user) {
|
if (user) {
|
||||||
limitActor = user.id;
|
limitActor = user.id;
|
||||||
} else {
|
} else {
|
||||||
// because a single person may control many IPv6 addresses,
|
limitActor = getIpHash(ctx!.ip);
|
||||||
// only a /64 subnet prefix of any IP will be taken into account.
|
|
||||||
// (this means for IPv4 the entire address is used)
|
|
||||||
const ip = IPCIDR.createAddress(ctx.ip).mask(64);
|
|
||||||
|
|
||||||
limitActor = 'ip-' + parseInt(ip, 2).toString(36);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const limit = Object.assign({}, ep.meta.limit);
|
const limit = Object.assign({}, ep.meta.limit);
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { verifyLogin, hash } from '../2fa.js';
|
||||||
import { randomBytes } from 'node:crypto';
|
import { randomBytes } from 'node:crypto';
|
||||||
import { IsNull } from 'typeorm';
|
import { IsNull } from 'typeorm';
|
||||||
import { limiter } from '../limiter.js';
|
import { limiter } from '../limiter.js';
|
||||||
|
import { getIpHash } from '@/misc/get-ip-hash.js';
|
||||||
|
|
||||||
export default async (ctx: Koa.Context) => {
|
export default async (ctx: Koa.Context) => {
|
||||||
ctx.set('Access-Control-Allow-Origin', config.url);
|
ctx.set('Access-Control-Allow-Origin', config.url);
|
||||||
|
@ -27,7 +28,7 @@ export default async (ctx: Koa.Context) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// not more than 1 attempt per second and not more than 10 attempts per hour
|
// not more than 1 attempt per second and not more than 10 attempts per hour
|
||||||
await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, ctx.ip);
|
await limiter({ key: 'signin', duration: 60 * 60 * 1000, max: 10, minInterval: 1000 }, getIpHash(ctx.ip));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
ctx.status = 429;
|
ctx.status = 429;
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
|
|
|
@ -312,7 +312,8 @@ export default async (user: { id: User['id']; username: User['username']; host:
|
||||||
endedPollNotificationQueue.add({
|
endedPollNotificationQueue.add({
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
}, {
|
}, {
|
||||||
delay
|
delay,
|
||||||
|
removeOnComplete: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ import MkSignin from '@/components/signin.vue';
|
||||||
import MkButton from '@/components/ui/button.vue';
|
import MkButton from '@/components/ui/button.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { login } from '@/account';
|
import { login } from '@/account';
|
||||||
|
import { appendQuery, query } from '@/scripts/url';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
|
@ -82,7 +83,9 @@ export default defineComponent({
|
||||||
|
|
||||||
this.state = 'accepted';
|
this.state = 'accepted';
|
||||||
if (this.callback) {
|
if (this.callback) {
|
||||||
location.href = `${this.callback}?session=${this.session}`;
|
location.href = appendQuery(this.callback, query({
|
||||||
|
session: this.session
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
deny() {
|
deny() {
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { computed, defineExpose, ref } from 'vue';
|
import { computed, defineExpose, ref } from 'vue';
|
||||||
import * as tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import FormLink from '@/components/form/link.vue';
|
import FormLink from '@/components/form/link.vue';
|
||||||
import FormSwitch from '@/components/form/switch.vue';
|
import FormSwitch from '@/components/form/switch.vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
|
|
|
@ -94,10 +94,10 @@ function onStats(connStats) {
|
||||||
inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
inPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${inPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
||||||
outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
outPolygonPoints = `${viewBoxX - (stats.length - 1)},${viewBoxY} ${outPolylinePoints} ${viewBoxX},${viewBoxY}`;
|
||||||
|
|
||||||
inHeadX = inPolylinePoints[inPolylinePoints.length - 1][0];
|
inHeadX = inPolylinePointsStats[inPolylinePointsStats.length - 1][0];
|
||||||
inHeadY = inPolylinePoints[inPolylinePoints.length - 1][1];
|
inHeadY = inPolylinePointsStats[inPolylinePointsStats.length - 1][1];
|
||||||
outHeadX = outPolylinePoints[outPolylinePoints.length - 1][0];
|
outHeadX = outPolylinePointsStats[outPolylinePointsStats.length - 1][0];
|
||||||
outHeadY = outPolylinePoints[outPolylinePoints.length - 1][1];
|
outHeadY = outPolylinePointsStats[outPolylinePointsStats.length - 1][1];
|
||||||
|
|
||||||
inRecent = connStats.net.rx;
|
inRecent = connStats.net.rx;
|
||||||
outRecent = connStats.net.tx;
|
outRecent = connStats.net.tx;
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
/**
|
|
||||||
* webpack configuration
|
|
||||||
*/
|
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const webpack = require('webpack');
|
|
||||||
|
|
||||||
class WebpackOnBuildPlugin {
|
|
||||||
constructor(callback) {
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(compiler) {
|
|
||||||
compiler.hooks.done.tap('WebpackOnBuildPlugin', this.callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isProduction = process.env.NODE_ENV === 'production';
|
|
||||||
|
|
||||||
const locales = require('../../locales');
|
|
||||||
const meta = require('../../package.json');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
target: 'webworker',
|
|
||||||
entry: {
|
|
||||||
['sw-lib']: './src/lib.ts'
|
|
||||||
},
|
|
||||||
module: {
|
|
||||||
rules: [{
|
|
||||||
test: /\.ts$/,
|
|
||||||
exclude: /node_modules/,
|
|
||||||
use: [{
|
|
||||||
loader: 'ts-loader',
|
|
||||||
options: {
|
|
||||||
happyPackMode: true,
|
|
||||||
transpileOnly: true,
|
|
||||||
configFile: __dirname + '/tsconfig.json',
|
|
||||||
}
|
|
||||||
}]
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new webpack.ProgressPlugin({}),
|
|
||||||
new webpack.DefinePlugin({
|
|
||||||
_VERSION_: JSON.stringify(meta.version),
|
|
||||||
_LANGS_: JSON.stringify(Object.entries(locales).map(([k, v]) => [k, v._lang_])),
|
|
||||||
_ENV_: JSON.stringify(process.env.NODE_ENV),
|
|
||||||
_DEV_: process.env.NODE_ENV !== 'production',
|
|
||||||
_PERF_PREFIX_: JSON.stringify('Misskey:'),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
output: {
|
|
||||||
path: __dirname + '/../../built/_sw_dist_',
|
|
||||||
filename: `[name].js`,
|
|
||||||
publicPath: `/`,
|
|
||||||
pathinfo: false,
|
|
||||||
},
|
|
||||||
resolve: {
|
|
||||||
extensions: [
|
|
||||||
'.js', '.ts', '.json'
|
|
||||||
],
|
|
||||||
alias: {
|
|
||||||
'@': __dirname + '/src/',
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resolveLoader: {
|
|
||||||
modules: ['node_modules']
|
|
||||||
},
|
|
||||||
devtool: false, //'source-map',
|
|
||||||
mode: isProduction ? 'production' : 'development'
|
|
||||||
};
|
|
|
@ -3,7 +3,7 @@ const execa = require('execa');
|
||||||
(async () => {
|
(async () => {
|
||||||
console.log('installing dependencies of packages/backend ...');
|
console.log('installing dependencies of packages/backend ...');
|
||||||
|
|
||||||
await execa('yarn', ['install'], {
|
await execa('yarn', ['--force', 'install'], {
|
||||||
cwd: __dirname + '/../packages/backend',
|
cwd: __dirname + '/../packages/backend',
|
||||||
stdout: process.stdout,
|
stdout: process.stdout,
|
||||||
stderr: process.stderr,
|
stderr: process.stderr,
|
||||||
|
|
Loading…
Reference in a new issue