feat(server-dev): Dev server plugin install and next build working.

This commit is contained in:
Sam Tolmay 2022-01-26 21:26:20 +02:00
parent a6f39fbcd0
commit cf66a6f839
No known key found for this signature in database
GPG Key ID: D004126FCD1A6DF0
9 changed files with 139 additions and 66 deletions

View File

@ -14,15 +14,13 @@
limitations under the License.
*/
import startServerProcess from './startServerProcess.mjs';
import startNextServer from './startNextServer.mjs';
function restartServer(context) {
return async () => {
if (context.serverProcess) {
console.log('Restarting server...');
context.serverProcess.kill();
startServerProcess(context);
}
return () => {
context.shutdownServer(); // Is this needed here?
console.log('Restarting server...');
startNextServer(context);
};
}

View File

@ -15,10 +15,19 @@
*/
function shutdownServer(context) {
return async () => {
return () => {
if (context.serverProcess) {
console.log('Shutting down server...');
context.serverProcess.kill();
// console.log(
// `Existing server ${context.serverProcess.pid}, killed: ${context.serverProcess.killed}`
// );
if (!context.serverProcess.killed) {
console.log('Shutting down server...');
context.serverProcess.kill();
// console.log(
// `Killed server ${context.serverProcess.pid}, killed: ${context.serverProcess.killed}`
// );
}
context.serverProcess = null;
}
};
}

View File

@ -14,13 +14,18 @@
limitations under the License.
*/
import { createRequire } from 'module';
import spawnProcess from '../utils/spawnProcess.mjs';
const require = createRequire(import.meta.url);
function startServerProcess(context) {
context.serverProcess = spawnProcess({
context.shutdownServer();
const nextCliUrl = require.resolve('next').replace('server/next.js', 'bin/next');
const nextServer = spawnProcess({
logger: console,
command: context.packageManager,
args: ['run', 'next', 'start'],
command: 'node',
args: [nextCliUrl, 'start'],
silent: false,
processOptions: {
env: {
@ -30,12 +35,14 @@ function startServerProcess(context) {
},
},
});
context.serverProcess.on('exit', (code) => {
if (code !== 0) {
context.serverProcessPromise.reject(new Error('Server error.'));
}
context.serverProcessPromise.resolve();
// console.log(`Started server ${nextServer.pid}.`);
// nextServer.on('exit', (code, signal) => {
// console.log(`nextServer exit ${nextServer.pid}, signal: ${signal}, code: ${code}`);
// });
nextServer.on('error', (error) => {
console.log(error);
});
context.nextServer = nextServer;
}
export default startServerProcess;

View File

@ -15,14 +15,14 @@
*/
/* eslint-disable no-console */
import startServerProcess from './startServerProcess.mjs';
import startNextServer from './startNextServer.mjs';
async function startServer(context) {
return new Promise((resolve, reject) => {
context.serverProcessPromise = { resolve, reject };
try {
startServerProcess(context);
startNextServer(context);
} catch (error) {
console.log(error);
reject(error);
}
});

View File

@ -14,50 +14,17 @@
limitations under the License.
*/
import configWatcher from '../watchers/configWatcher.mjs';
import envWatcher from '../watchers/envWatcher.mjs';
/*
Config change
Watch <config-dir>, <watch-dirs>, !<ignore-dirs>
- Lowdefy build
- Trigger soft reload
----------------------------------------
Install new plugin
Watch <server>/package.json
- Install Server.
- Next build.
- No need for Lowdefy build (confirm?)
- Trigger hard reload
- Restart server.
----------------------------------------
.env change
Watch <config-dir>/.env
- Trigger hard reload
- Restart server.
----------------------------------------
Lowdefy version changed
Watch <config-dir>/lowdefy.yaml
- Warn and process.exit()
----------------------------------------
Style vars/app config change
Watch <server-dir>/build/app.json
Watch <server-dir>/build/config.json
- Next build.
- Trigger hard reload
- Restart server.
----------------------------------------
New plugin or icon used.
Watch <server-dir>/build/plugins/*
- Next build. (or dynamic import?)
- Trigger hard reload
- Restart server.
*/
import lowdefyBuildWatcher from '../watchers/lowdefyBuildWatcher.mjs';
import nextBuildWatcher from '../watchers/nextBuildWatcher.mjs';
function startWatchers(context) {
return async () => {
await Promise.all([configWatcher(context), envWatcher(context)]);
await Promise.all([
envWatcher(context),
lowdefyBuildWatcher(context),
nextBuildWatcher(context),
]);
};
}

View File

@ -30,6 +30,7 @@ async function run() {
opener(`http://localhost:${context.port}`);
await serverPromise;
} catch (error) {
console.log(error);
context.shutdownServer();
throw error;
}

View File

@ -18,7 +18,7 @@
import path from 'path';
import setupWatcher from '../utils/setupWatcher.mjs';
async function envWatcher(context) {
function envWatcher(context) {
const callback = async () => {
console.warn('.env file changed.');
await context.readDotEnv();

View File

@ -18,14 +18,15 @@
import getLowdefyVersion from '../utils/getLowdefyVersion.mjs';
import setupWatcher from '../utils/setupWatcher.mjs';
async function configWatcher(context) {
function lowdefyBuildWatcher(context) {
const callback = async (filePaths) => {
const lowdefyYamlModified = filePaths
.flat()
.some((filePath) => filePath.includes('lowdefy.yaml') || filePath.includes('lowdefy.yml'));
if (lowdefyYamlModified) {
const lowdefyVersion = await getLowdefyVersion(context);
if (lowdefyVersion !== context.version || lowdefyVersion === 'local') {
if (lowdefyVersion !== context.version && lowdefyVersion !== 'local') {
context.shutdownServer();
console.warn('Lowdefy version changed. You should restart your development server.');
process.exit();
}
@ -35,7 +36,10 @@ async function configWatcher(context) {
context.reloadClients();
};
// TODO: Add ignored and watch paths
return setupWatcher({ callback, watchPaths: [context.directories.config] });
return setupWatcher({
callback,
watchPaths: [context.directories.config],
});
}
export default configWatcher;
export default lowdefyBuildWatcher;

View File

@ -0,0 +1,87 @@
/*
Copyright 2020-2021 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/* eslint-disable no-console */
import crypto from 'crypto';
import path from 'path';
import { readFile } from '@lowdefy/node-utils';
import setupWatcher from '../utils/setupWatcher.mjs';
const hashes = {};
const watchedFiles = [
'build/config.json',
'build/plugins/blocks.js',
'build/plugins/connections.js',
'build/plugins/icons.js',
'build/plugins/operatorsClient.js',
'build/plugins/operatorsServer.js',
'build/plugins/styles.less',
'package.json',
];
async function sha1(filePath) {
const content = await readFile(filePath);
return crypto
.createHash('sha1')
.update(content || '')
.digest('hex');
}
async function nextBuildWatcher(context) {
// Initialize hashes so that app does not rebuild the first time
// Lowdefy build is run.
await Promise.all(
watchedFiles.map(async (filePath) => {
const fullPath = path.resolve(context.directories.server, filePath);
hashes[fullPath] = await sha1(fullPath);
})
);
const callback = async (filePaths) => {
let install = false;
let build = false;
await Promise.all(
filePaths.flat().map(async (filePath) => {
const hash = await sha1(filePath);
if (hashes[filePath] === hash) return;
build = true;
if (filePath.endsWith('package.json')) install = true;
hashes[filePath] = hash;
})
);
if (!build) return;
context.shutdownServer();
if (install) {
await context.installPlugins();
}
await context.nextBuild();
context.restartServer();
};
return setupWatcher({
callback,
watchPaths: [
path.join(context.directories.build, 'plugins'),
path.join(context.directories.build, 'config.json'),
path.join(context.directories.server, 'package.json'),
],
});
}
export default nextBuildWatcher;