diff --git a/.pnp.cjs b/.pnp.cjs index 58a97390e..1fd36ec3f 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -5700,6 +5700,8 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["@lowdefy/operators-yaml", "workspace:packages/plugins/operators/operators-yaml"], ["@next/eslint-plugin-next", "npm:12.0.4"], ["chokidar", "npm:3.5.2"], + ["dotenv", "npm:14.2.0"], + ["js-yaml", "npm:4.1.0"], ["less", "npm:4.1.2"], ["less-loader", "virtual:003bebd8b7a948d12b44e2c11a621884feb1891eea3645171e827971487f79396db9f7422bc411ccf3f90877e94ec86f5c3da70b96efb5daddb2ee3b35eae5c6#npm:10.2.0"], ["next", "virtual:003bebd8b7a948d12b44e2c11a621884feb1891eea3645171e827971487f79396db9f7422bc411ccf3f90877e94ec86f5c3da70b96efb5daddb2ee3b35eae5c6#npm:12.0.3"], @@ -10536,6 +10538,15 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], + ["dotenv", [ + ["npm:14.2.0", { + "packageLocation": "./.yarn/cache/dotenv-npm-14.2.0-b237345d26-85a0e44918.zip/node_modules/dotenv/", + "packageDependencies": [ + ["dotenv", "npm:14.2.0"] + ], + "linkType": "HARD", + }] + ]], ["duplexer", [ ["npm:0.1.2", { "packageLocation": "./.yarn/cache/duplexer-npm-0.1.2-952c810235-62ba61a830.zip/node_modules/duplexer/", diff --git a/.yarn/cache/dotenv-npm-14.2.0-b237345d26-85a0e44918.zip b/.yarn/cache/dotenv-npm-14.2.0-b237345d26-85a0e44918.zip new file mode 100644 index 000000000..2f17715b4 Binary files /dev/null and b/.yarn/cache/dotenv-npm-14.2.0-b237345d26-85a0e44918.zip differ diff --git a/packages/cli/src/commands/dev/runDevServer.js b/packages/cli/src/commands/dev/runDevServer.js index b2ee9565f..fd19dbe9a 100644 --- a/packages/cli/src/commands/dev/runDevServer.js +++ b/packages/cli/src/commands/dev/runDevServer.js @@ -17,6 +17,7 @@ import { spawnProcess } from '@lowdefy/node-utils'; async function runDevServer({ context }) { + // TODO: Pass packageManager as option await spawnProcess({ logger: context.print, args: ['run', 'start'], diff --git a/packages/server-dev/package.json b/packages/server-dev/package.json index c533341dc..f397fcd8a 100644 --- a/packages/server-dev/package.json +++ b/packages/server-dev/package.json @@ -62,6 +62,8 @@ "@lowdefy/operators-uuid": "4.0.0-alpha.6", "@lowdefy/operators-yaml": "4.0.0-alpha.6", "chokidar": "3.5.2", + "dotenv": "14.2.0", + "js-yaml": "4.1.0", "next": "12.0.3", "next-auth": "4.0.0-beta.6", "opener": "1.5.2", diff --git a/packages/server-dev/src/components/Page.js b/packages/server-dev/src/components/Page.js index 44aa76ce7..5276f284c 100644 --- a/packages/server-dev/src/components/Page.js +++ b/packages/server-dev/src/components/Page.js @@ -26,7 +26,7 @@ const LoadingBlock = () =>
Loading...
; const Page = ({ lowdefy }) => { const { data: pageConfig } = usePageConfig(lowdefy.pageId); if (!pageConfig) { - lowdefy._internal.router.push(`/404`); + lowdefy._internal.router.replace(`/404`); return ; } return ( diff --git a/packages/server-dev/src/manager/getContext.mjs b/packages/server-dev/src/manager/getContext.mjs index fee5983f1..4049afab3 100644 --- a/packages/server-dev/src/manager/getContext.mjs +++ b/packages/server-dev/src/manager/getContext.mjs @@ -19,11 +19,15 @@ import path from 'path'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; +import initialBuild from './processes/initialBuild.mjs'; +import installPlugins from './processes/installPlugins.mjs'; import lowdefyBuild from './processes/lowdefyBuild.mjs'; import nextBuild from './processes/nextBuild.mjs'; -import installPlugins from './processes/installPlugins.mjs'; -import startServerProcess from './processes/startServerProcess.mjs'; +import readDotEnv from './processes/readDotEnv.mjs'; import reloadClients from './processes/reloadClients.mjs'; +import restartServer from './processes/restartServer.mjs'; +import shutdownServer from './processes/shutdownServer.mjs'; +import startWatchers from './processes/startWatchers.mjs'; const argv = yargs(hideBin(process.argv)).argv; @@ -38,25 +42,19 @@ async function getContext() { server: process.cwd(), }, packageManager, - restartServer: () => { - if (context.serverProcess) { - console.log('Restarting server...'); - context.serverProcess.kill(); - startServerProcess(context); - } - }, - shutdownServer: () => { - if (context.serverProcess) { - console.log('Shutting down server...'); - context.serverProcess.kill(); - } - }, verbose, }; + + context.version = process.env.npm_package_version; + context.initialBuild = initialBuild(context); context.installPlugins = installPlugins(context); context.lowdefyBuild = lowdefyBuild(context); context.nextBuild = nextBuild(context); + context.readDotEnv = readDotEnv(context); context.reloadClients = reloadClients(context); + context.restartServer = restartServer(context); + context.shutdownServer = shutdownServer(context); + context.startWatchers = startWatchers(context); return context; } diff --git a/packages/server-dev/src/manager/initialBuild.mjs b/packages/server-dev/src/manager/processes/initialBuild.mjs similarity index 76% rename from packages/server-dev/src/manager/initialBuild.mjs rename to packages/server-dev/src/manager/processes/initialBuild.mjs index 8b0d69a54..58d23711e 100644 --- a/packages/server-dev/src/manager/initialBuild.mjs +++ b/packages/server-dev/src/manager/processes/initialBuild.mjs @@ -15,10 +15,13 @@ limitations under the License. */ -async function initialBuild(context) { - await context.lowdefyBuild(); - await context.installPlugins(); - await context.nextBuild(); +function initialBuild(context) { + return async () => { + await context.lowdefyBuild(); + await context.installPlugins(); + await context.nextBuild(); + await context.readDotEnv(); + }; } export default initialBuild; diff --git a/packages/server-dev/src/manager/processes/readDotEnv.mjs b/packages/server-dev/src/manager/processes/readDotEnv.mjs new file mode 100644 index 000000000..77c2e8567 --- /dev/null +++ b/packages/server-dev/src/manager/processes/readDotEnv.mjs @@ -0,0 +1,28 @@ +/* + 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. +*/ + +import path from 'path'; +import dotenv from 'dotenv'; +import { readFile } from '@lowdefy/node-utils'; + +function readDotEnv(context) { + return async () => { + const dotEnvPath = path.join(context.directories.config, '.env'); + context.serverEnv = dotenv.parse(await readFile(dotEnvPath)); + }; +} + +export default readDotEnv; diff --git a/packages/server-dev/src/manager/processes/restartServer.mjs b/packages/server-dev/src/manager/processes/restartServer.mjs new file mode 100644 index 000000000..0e69a3f5e --- /dev/null +++ b/packages/server-dev/src/manager/processes/restartServer.mjs @@ -0,0 +1,29 @@ +/* + 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. +*/ + +import startServerProcess from './startServerProcess.mjs'; + +function shutdownServer(context) { + return async () => { + if (context.serverProcess) { + console.log('Restarting server...'); + context.serverProcess.kill(); + startServerProcess(context); + } + }; +} + +export default shutdownServer; diff --git a/packages/server-dev/src/manager/processes/shutdownServer.mjs b/packages/server-dev/src/manager/processes/shutdownServer.mjs new file mode 100644 index 000000000..f3e5e0b71 --- /dev/null +++ b/packages/server-dev/src/manager/processes/shutdownServer.mjs @@ -0,0 +1,26 @@ +/* + 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. +*/ + +function shutdownServer(context) { + return async () => { + if (context.serverProcess) { + console.log('Shutting down server...'); + context.serverProcess.kill(); + } + }; +} + +export default shutdownServer; diff --git a/packages/server-dev/src/manager/processes/startServerProcess.mjs b/packages/server-dev/src/manager/processes/startServerProcess.mjs index 60f5a141a..5307c4eaa 100644 --- a/packages/server-dev/src/manager/processes/startServerProcess.mjs +++ b/packages/server-dev/src/manager/processes/startServerProcess.mjs @@ -14,7 +14,7 @@ limitations under the License. */ -import spawnProcess from '../spawnProcess.mjs'; +import spawnProcess from '../utils/spawnProcess.mjs'; function startServerProcess(context) { context.serverProcess = spawnProcess({ @@ -22,6 +22,12 @@ function startServerProcess(context) { command: context.packageManager, args: ['run', 'next', 'start'], silent: false, + processOptions: { + env: { + ...process.env, + ...context.serverEnv, + }, + }, }); context.serverProcess.on('exit', (code) => { if (code !== 0) { diff --git a/packages/server-dev/src/manager/watchers/startWatchers.mjs b/packages/server-dev/src/manager/processes/startWatchers.mjs similarity index 86% rename from packages/server-dev/src/manager/watchers/startWatchers.mjs rename to packages/server-dev/src/manager/processes/startWatchers.mjs index a90886024..f1b354c6a 100644 --- a/packages/server-dev/src/manager/watchers/startWatchers.mjs +++ b/packages/server-dev/src/manager/processes/startWatchers.mjs @@ -14,8 +14,8 @@ limitations under the License. */ -import configWatcher from './configWatcher.mjs'; -import envWatcher from './envWatcher.mjs'; +import configWatcher from '../watchers/configWatcher.mjs'; +import envWatcher from '../watchers/envWatcher.mjs'; /* Config change @@ -31,7 +31,6 @@ Watch /package.json - No need for Lowdefy build (confirm?) - Trigger hard reload - Restart server. - ---------------------------------------- .env change Watch /.env @@ -48,7 +47,6 @@ Watch /build/config.json - Next build. - Trigger hard reload - Restart server. - ---------------------------------------- New plugin or icon used. Watch /build/plugins/* @@ -57,8 +55,10 @@ Watch /build/plugins/* - Restart server. */ -async function startWatchers(context) { - await Promise.all([configWatcher(context), envWatcher(context)]); +function startWatchers(context) { + return async () => { + await Promise.all([configWatcher(context), envWatcher(context)]); + }; } export default startWatchers; diff --git a/packages/server-dev/src/manager/run.mjs b/packages/server-dev/src/manager/run.mjs index 2597e21fc..1a276df39 100644 --- a/packages/server-dev/src/manager/run.mjs +++ b/packages/server-dev/src/manager/run.mjs @@ -18,14 +18,12 @@ import { wait } from '@lowdefy/helpers'; import opener from 'opener'; import getContext from './getContext.mjs'; -import initialBuild from './initialBuild.mjs'; import startServer from './processes/startServer.mjs'; -import startWatchers from './watchers/startWatchers.mjs'; async function run() { const context = await getContext(); - await initialBuild(context); - await startWatchers(context); + await context.initialBuild(); + await context.startWatchers(); try { const serverPromise = startServer(context); await wait(800); diff --git a/packages/server-dev/src/manager/BatchChanges.mjs b/packages/server-dev/src/manager/utils/BatchChanges.mjs similarity index 98% rename from packages/server-dev/src/manager/BatchChanges.mjs rename to packages/server-dev/src/manager/utils/BatchChanges.mjs index def45d671..ec1cb4d51 100644 --- a/packages/server-dev/src/manager/BatchChanges.mjs +++ b/packages/server-dev/src/manager/utils/BatchChanges.mjs @@ -13,6 +13,7 @@ See the License for the specific language governing permissions and limitations under the License. */ +/* eslint-disable no-console */ class BatchChanges { constructor({ fn, minDelay }) { diff --git a/packages/server-dev/src/manager/utils/getLowdefyVersion.mjs b/packages/server-dev/src/manager/utils/getLowdefyVersion.mjs new file mode 100644 index 000000000..d03a3cd99 --- /dev/null +++ b/packages/server-dev/src/manager/utils/getLowdefyVersion.mjs @@ -0,0 +1,51 @@ +/* + 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. +*/ + +import path from 'path'; +import { type } from '@lowdefy/helpers'; +import { readFile } from '@lowdefy/node-utils'; +import YAML from 'js-yaml'; + +async function getLowdefyVersion(context) { + let lowdefyYaml = await readFile(path.join(context.directories.config, 'lowdefy.yaml')); + if (!lowdefyYaml) { + lowdefyYaml = await readFile(path.join(context.directories.config, 'lowdefy.yml')); + } + if (!lowdefyYaml) { + throw new Error(`Could not find "lowdefy.yaml" file.`); + } + let lowdefy; + try { + lowdefy = YAML.load(lowdefyYaml); + } catch (error) { + throw new Error(`Could not parse "lowdefy.yaml" file. Received error ${error.message}.`); + } + if (!lowdefy.lowdefy) { + throw new Error( + `No version specified in "lowdefy.yaml" file. Specify a version in the "lowdefy" field.` + ); + } + if (!type.isString(lowdefy.lowdefy)) { + throw new Error( + `Version number specified in "lowdefy.yaml" file should be a string. Received ${JSON.stringify( + lowdefy.lowdefy + )}.` + ); + } + return lowdefy.lowdefy; +} + +export default getLowdefyVersion; diff --git a/packages/server-dev/src/manager/watchers/setupWatcher.mjs b/packages/server-dev/src/manager/utils/setupWatcher.mjs similarity index 83% rename from packages/server-dev/src/manager/watchers/setupWatcher.mjs rename to packages/server-dev/src/manager/utils/setupWatcher.mjs index 4952b1997..ffb84b67e 100644 --- a/packages/server-dev/src/manager/watchers/setupWatcher.mjs +++ b/packages/server-dev/src/manager/utils/setupWatcher.mjs @@ -15,14 +15,20 @@ */ import chokidar from 'chokidar'; -import BatchChanges from '../BatchChanges.mjs'; +import BatchChanges from './BatchChanges.mjs'; -function setupWatcher({ callback, watchDotfiles = false, ignorePaths = [], watchPaths }) { +function setupWatcher({ + callback, + watchDotfiles = false, + ignorePaths = [], + watchPaths, + minDelay = 500, +}) { return new Promise((resolve) => { // const { watch = [], watchIgnore = [] } = context.options; // const resolvedWatchPaths = watch.map((pathName) => path.resolve(pathName)); - const batchChanges = new BatchChanges({ fn: callback }); + const batchChanges = new BatchChanges({ fn: callback, minDelay }); const defaultIgnorePaths = watchDotfiles ? [] : [ @@ -33,9 +39,9 @@ function setupWatcher({ callback, watchDotfiles = false, ignorePaths = [], watch persistent: true, ignoreInitial: true, }); - configWatcher.on('add', (...args) => batchChanges.newChange(args)); - configWatcher.on('change', (...args) => batchChanges.newChange(args)); - configWatcher.on('unlink', (...args) => batchChanges.newChange(args)); + configWatcher.on('add', (...args) => batchChanges.newChange(...args)); + configWatcher.on('change', (...args) => batchChanges.newChange(...args)); + configWatcher.on('unlink', (...args) => batchChanges.newChange(...args)); configWatcher.on('ready', () => resolve()); }); } diff --git a/packages/server-dev/src/manager/spawnProcess.mjs b/packages/server-dev/src/manager/utils/spawnProcess.mjs similarity index 100% rename from packages/server-dev/src/manager/spawnProcess.mjs rename to packages/server-dev/src/manager/utils/spawnProcess.mjs diff --git a/packages/server-dev/src/manager/watchers/configWatcher.mjs b/packages/server-dev/src/manager/watchers/configWatcher.mjs index f58c7e18d..18b88dc37 100644 --- a/packages/server-dev/src/manager/watchers/configWatcher.mjs +++ b/packages/server-dev/src/manager/watchers/configWatcher.mjs @@ -13,11 +13,24 @@ See the License for the specific language governing permissions and limitations under the License. */ +/* eslint-disable no-console */ -import setupWatcher from './setupWatcher.mjs'; +import getLowdefyVersion from '../utils/getLowdefyVersion.mjs'; +import setupWatcher from '../utils/setupWatcher.mjs'; async function configWatcher(context) { - const callback = async () => { + 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) { + console.warn('Lowdefy version changed. You should restart your development server.'); + process.exit(); + } + } + await context.lowdefyBuild(); context.reloadClients(); }; diff --git a/packages/server-dev/src/manager/watchers/envWatcher.mjs b/packages/server-dev/src/manager/watchers/envWatcher.mjs index bd36738d8..356b00c9b 100644 --- a/packages/server-dev/src/manager/watchers/envWatcher.mjs +++ b/packages/server-dev/src/manager/watchers/envWatcher.mjs @@ -13,13 +13,15 @@ See the License for the specific language governing permissions and limitations under the License. */ +/* eslint-disable no-console */ import path from 'path'; -import setupWatcher from './setupWatcher.mjs'; +import setupWatcher from '../utils/setupWatcher.mjs'; async function envWatcher(context) { const callback = async () => { - console.log('.env file changed, restarting server...'); + console.warn('.env file changed.'); + await context.readDotEnv(); context.restartServer(); }; return setupWatcher({ diff --git a/packages/utils/node-utils/src/readFile.js b/packages/utils/node-utils/src/readFile.js index e78741883..a5df4bf98 100644 --- a/packages/utils/node-utils/src/readFile.js +++ b/packages/utils/node-utils/src/readFile.js @@ -27,14 +27,9 @@ async function readFile(filePath) { `Could not read file, file path should be a string, received ${JSON.stringify(filePath)}.` ); } - if (filePath !== path.resolve(filePath)) { - throw new Error( - `Could not read file, file path was not resolved, received ${JSON.stringify(filePath)}.` - ); - } try { // By specifying encoding, readFile returns a string instead of a buffer. - const file = await readFilePromise(filePath, 'utf8'); + const file = await readFilePromise(path.resolve(filePath), 'utf8'); return file; } catch (error) { if (error.code === 'ENOENT' || error.code === 'EISDIR') { diff --git a/packages/utils/node-utils/src/readFile.test.js b/packages/utils/node-utils/src/readFile.test.js index 6ee6a9d6d..71f9f33cf 100644 --- a/packages/utils/node-utils/src/readFile.test.js +++ b/packages/utils/node-utils/src/readFile.test.js @@ -35,9 +35,3 @@ test('readFile error id filepath is not a string', async () => { 'Could not read file, file path should be a string, received {}.' ); }); - -test('readFile errors if path is not already resolved', async () => { - await expect(readFile('./readFile/readFile.txt')).rejects.toThrow( - 'Could not read file, file path was not resolved, received "./readFile/readFile.txt".' - ); -}); diff --git a/yarn.lock b/yarn.lock index 096212225..8e249c0ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3917,6 +3917,8 @@ __metadata: "@lowdefy/operators-yaml": 4.0.0-alpha.6 "@next/eslint-plugin-next": 12.0.4 chokidar: 3.5.2 + dotenv: 14.2.0 + js-yaml: 4.1.0 less: 4.1.2 less-loader: 10.2.0 next: 12.0.3 @@ -7945,6 +7947,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:14.2.0": + version: 14.2.0 + resolution: "dotenv@npm:14.2.0" + checksum: 85a0e44918ef49e64c278f757dab50a156b9a6ca67f708876fd81d265e575e35b67387fc681d910df99368d6c1edca66cd546edeb0f7db3b499cb876c999233e + languageName: node + linkType: hard + "duplexer@npm:^0.1.1": version: 0.1.2 resolution: "duplexer@npm:0.1.2"