Merge branch 'develop' into plugins-actions

This commit is contained in:
Sandile 2022-02-15 14:36:13 +02:00
commit 3fd8256b82
23 changed files with 186 additions and 156 deletions

1
.gitignore vendored
View File

@ -18,6 +18,7 @@ packages/server/build/**
packages/server-dev/build/**
!packages/docs/lowdefy.yaml
!packages/docs/howto/**/lowdefy.yaml
packages/cli/server/**
.DS_Store

View File

@ -41,10 +41,19 @@
"lerna:publish": "lerna publish from-git",
"postversion": "yarn install",
"prettier": "prettier --config .prettierrc --write **/*.js",
"start:server-dev": "yarn workspace @lowdefy/server-dev start --package-manager yarn --config-directory ../../app",
"start": "yarn workspace @lowdefy/server build:lowdefy --config-directory ../../app && yarn && yarn workspace @lowdefy/server build:next && yarn workspace @lowdefy/server start",
"start:dev": "yarn workspace @lowdefy/server build:lowdefy --config-directory ../../app && yarn && yarn workspace @lowdefy/server dev",
"start:dev-docs": "yarn workspace @lowdefy/server build:lowdefy --config-directory ../docs && yarn && yarn workspace @lowdefy/server dev",
"start": "yarn start:server:app",
"start:cli:build:app": "yarn workspace lowdefy start build --config-directory ../../app --server-directory ../server --output-directory ../",
"start:cli:build:docs": "yarn workspace lowdefy start build --config-directory ../docs --server-directory ../server --output-directory ../",
"start:cli:dev:app": "yarn workspace lowdefy start dev --config-directory ../../app --dev-directory ../server-dev",
"start:cli:dev:docs": "yarn workspace lowdefy start dev --config-directory ../docs --dev-directory ../server-dev",
"start:cli:start:app": "yarn workspace lowdefy start start --config-directory ../../app --server-directory ../server --output-directory ../",
"start:cli:start:docs": "yarn workspace lowdefy start start --config-directory ../docs --server-directory ../server --output-directory ../",
"start:server-dev:app": "yarn workspace @lowdefy/server-dev start --package-manager yarn --config-directory ../../app",
"start:server-dev:docs": "yarn workspace @lowdefy/server-dev start --package-manager yarn --config-directory ../docs",
"start:server:app": "yarn workspace @lowdefy/server build:lowdefy --config-directory ../../app && yarn && yarn workspace @lowdefy/server build:next && yarn workspace @lowdefy/server start",
"start:server:docs": "yarn workspace @lowdefy/server build:lowdefy --config-directory ../docs && yarn && yarn workspace @lowdefy/server build:next && yarn workspace @lowdefy/server start",
"start:server:next-dev:app": "yarn workspace @lowdefy/server build:lowdefy --config-directory ../../app && yarn && yarn workspace @lowdefy/server dev",
"start:server:next-dev:docs": "yarn workspace @lowdefy/server build:lowdefy --config-directory ../docs && yarn && yarn workspace @lowdefy/server dev",
"test": "lerna run test"
},
"devDependencies": {

View File

@ -29,7 +29,12 @@ async function addDefaultPages({ components }) {
throw new Error('lowdefy.pages is not an array.');
}
const pageIds = components.pages.map((page) => page.id);
const pageIds = components.pages.map((page, index) => {
if (!type.isObject(page)) {
throw new Error(`pages[${index}] is not an object. Received ${JSON.stringify(page)}`);
}
return page.id;
});
// deep copy to avoid mutating defaultConfig
const filteredDefaultPages = defaultPages.filter(
(defaultPage) => !pageIds.includes(defaultPage.id)

View File

@ -204,6 +204,15 @@ test('addDefaultPages, pages not an array', async () => {
);
});
test('addDefaultPages, with a page not an object', async () => {
const components = {
pages: [null],
};
await expect(addDefaultPages({ components, context })).rejects.toThrow(
'pages[0] is not an object. Received null'
);
});
test('addDefaultPages, pages are copied', async () => {
const components1 = {};
const res1 = await addDefaultPages({ components: components1, context });

View File

@ -16,8 +16,12 @@
import path from 'path';
async function getUserJavascriptFunction({ context, filePath }) {
const module = await import(path.resolve(context.directories.config, filePath));
return module.default;
try {
return (await import(path.resolve(context.directories.config, filePath))).default;
} catch (error) {
context.logger.error(`Error importing ${filePath}.`);
throw Error(error);
}
}
export default getUserJavascriptFunction;

View File

@ -22,7 +22,13 @@ async function runTransformer({ context, parsedFile, refDef }) {
context,
filePath: refDef.transformer,
});
return transformerFn(parsedFile, refDef.vars);
try {
return transformerFn(parsedFile, refDef.vars);
} catch (error) {
throw Error(
`Error calling transformer "${refDef.transformer}" from "${refDef.path}": ${error.message}`
);
}
}
return parsedFile;
}

View File

@ -14,12 +14,7 @@
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
async function writePage({ page, context }) {
if (!type.isObject(page)) {
throw new Error(`Page is not an object. Received ${JSON.stringify(page)}`);
}
await context.writeBuildArtifact(
`pages/${page.pageId}/${page.pageId}.json`,
JSON.stringify(page, null, 2)
@ -27,10 +22,6 @@ async function writePage({ page, context }) {
}
async function writePages({ components, context }) {
if (type.isNone(components.pages)) return;
if (!type.isArray(components.pages)) {
throw new Error(`Pages is not an array.`);
}
const writePromises = components.pages.map((page) => writePage({ page, context }));
return Promise.all(writePromises);
}

View File

@ -99,25 +99,3 @@ test('writePages no pages', async () => {
await writePages({ components, context });
expect(mockWriteBuildArtifact.mock.calls).toEqual([]);
});
test('writePages pages undefined', async () => {
const components = {};
await writePages({ components, context });
expect(mockWriteBuildArtifact.mock.calls).toEqual([]);
});
test('writePages pages not an array', async () => {
const components = {
pages: 'pages',
};
await expect(writePages({ components, context })).rejects.toThrow('Pages is not an array.');
});
test('writePages page is not an object', async () => {
const components = {
pages: ['page'],
};
await expect(writePages({ components, context })).rejects.toThrow(
'Page is not an object. Received "page"'
);
});

View File

@ -14,8 +14,6 @@
limitations under the License.
*/
import { type } from '@lowdefy/helpers';
async function writeRequestsOnPage({ page, context }) {
return Promise.all(
page.requests.map(async (request) => {
@ -32,7 +30,6 @@ async function writeRequestsOnPage({ page, context }) {
}
async function writeRequests({ components, context }) {
if (type.isNone(components.pages)) return;
const writePromises = components.pages.map((page) => writeRequestsOnPage({ page, context }));
return Promise.all(writePromises);
}

View File

@ -224,12 +224,6 @@ test('writeRequests empty pages array', async () => {
expect(mockWriteBuildArtifact.mock.calls).toEqual([]);
});
test('writeRequests no pages array', async () => {
const components = {};
await writeRequests({ components, context });
expect(mockWriteBuildArtifact.mock.calls).toEqual([]);
});
test('writeRequests deletes request properties', async () => {
const components = {
pages: [

View File

@ -84,40 +84,34 @@ async function createContext(options) {
async function build(options) {
const context = await createContext(options);
try {
const components = await buildRefs({ context });
await testSchema({ components, context });
await validateApp({ components, context });
await validateConfig({ components, context });
await addDefaultPages({ components, context });
await buildAuth({ components, context });
await buildConnections({ components, context });
await buildPages({ components, context });
await buildMenu({ components, context });
await buildTypes({ components, context });
await buildIcons({ components, context });
await buildStyles({ components, context });
await cleanBuildDirectory({ context });
await writeApp({ components, context });
await writeConnections({ components, context });
await writeRequests({ components, context });
await writePages({ components, context });
await writeConfig({ components, context });
await writeGlobal({ components, context });
await writeMenus({ components, context });
await writeTypes({ components, context });
await writeActionImports({ components, context });
await writeBlockImports({ components, context });
await writeConnectionImports({ components, context });
await writeOperatorImports({ components, context });
await writeStyleImports({ components, context });
await writeIconImports({ components, context });
await updateServerPackageJson({ components, context });
await copyPublicFolder({ components, context });
} catch (error) {
context.logger.error(error);
throw error;
}
const components = await buildRefs({ context });
await testSchema({ components, context });
await validateApp({ components, context });
await validateConfig({ components, context });
await addDefaultPages({ components, context });
await buildAuth({ components, context });
await buildConnections({ components, context });
await buildPages({ components, context });
await buildMenu({ components, context });
await buildTypes({ components, context });
await buildIcons({ components, context });
await buildStyles({ components, context });
await cleanBuildDirectory({ context });
await writeApp({ components, context });
await writeConnections({ components, context });
await writeRequests({ components, context });
await writePages({ components, context });
await writeConfig({ components, context });
await writeGlobal({ components, context });
await writeMenus({ components, context });
await writeTypes({ components, context });
await writeBlockImports({ components, context });
await writeConnectionImports({ components, context });
await writeOperatorImports({ components, context });
await writeStyleImports({ components, context });
await writeIconImports({ components, context });
await updateServerPackageJson({ components, context });
await copyPublicFolder({ components, context });
}
export { createContext };

View File

@ -17,7 +17,7 @@
import { get, type } from '@lowdefy/helpers';
function formatArrayKey({ index, object }) {
if (!type.isNone(object.id) || !type.isNone(object.type)) {
if (type.isObject(object) && (!type.isNone(object.id) || !type.isNone(object.type))) {
const objectId = type.isNone(object.id) ? '_ERROR_MISSING_ID_' : object.id;
const objectType = type.isNone(object.type) ? '_ERROR_MISSING_TYPE_' : object.type;
return `[${index}:${objectId}:${objectType}]`;

View File

@ -94,3 +94,46 @@ should be string
- pages
- [0:1:_ERROR_MISSING_TYPE_].id`);
});
test('Additional properties as root config.', async () => {
const components = {
additional: true,
pages: [
{
id: 'page_1',
blocks: [],
},
],
};
const error = {
instancePath: '',
message: 'must NOT have additional properties',
};
const res = formatErrorMessage({ error, components });
expect(res).toEqual(`Schema Error
must NOT have additional properties
`);
});
test('Block is null.', async () => {
const components = {
additional: true,
pages: [
{
id: 'page_1',
type: 'Box',
blocks: [null],
},
],
};
const error = {
instancePath: '/pages/0/blocks/0',
message: 'Block should be an object.',
};
const res = formatErrorMessage({ error, components });
expect(res).toEqual(`Schema Error
Block should be an object.
- pages
- [0:page_1:Box].blocks
- [0]`);
});

View File

@ -14,14 +14,14 @@
limitations under the License.
*/
import getServer from './getServer.js';
import getServer from '../../utils/getServer.js';
import installServer from './installServer.js';
import runLowdefyBuild from './runLowdefyBuild.js';
import runNextBuild from './runNextBuild.js';
async function build({ context }) {
context.print.info('Starting build.');
await getServer({ context });
await getServer({ context, packageName: '@lowdefy/server' });
await installServer({ context });
await runLowdefyBuild({ context });
await installServer({ context });

View File

@ -14,13 +14,13 @@
limitations under the License.
*/
import getServer from './getServer.js';
import installServer from './installServer.js';
import runDevServer from './runDevServer.js';
import getServer from '../../utils/getServer.js';
async function dev({ context }) {
context.print.info('Starting development server.');
await getServer({ context });
await getServer({ context, packageName: '@lowdefy/server-dev' });
await installServer({ context });
context.sendTelemetry();
await runDevServer({ context });

View File

@ -1,53 +0,0 @@
/*
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 fs from 'fs';
import path from 'path';
import { cleanDirectory, readFile } from '@lowdefy/node-utils';
import fetchNpmTarball from '../../utils/fetchNpmTarball.js';
async function getServer({ context }) {
let fetchServer = false;
const serverExists = fs.existsSync(path.join(context.directories.devServer, 'package.json'));
if (!serverExists) fetchServer = true;
if (serverExists) {
const serverPackageConfig = JSON.parse(
await readFile(path.join(context.directories.devServer, 'package.json'))
);
if (serverPackageConfig.version !== context.lowdefyVersion) {
fetchServer = true;
context.print.warn(
`Removing @lowdefy/server-dev with version ${serverPackageConfig.version}`
);
await cleanDirectory(context.directories.devServer);
}
}
if (fetchServer) {
context.print.spin('Fetching @lowdefy/server-dev from npm.');
await fetchNpmTarball({
packageName: '@lowdefy/server-dev',
version: context.lowdefyVersion,
directory: context.directories.devServer,
});
context.print.log('Fetched @lowdefy/server-dev from npm.');
return;
}
}
export default getServer;

View File

@ -29,7 +29,7 @@ async function installServer({ context }) {
command: context.packageManager, // npm or yarn
args: args[context.packageManager],
processOptions: {
cwd: context.directories.devServer,
cwd: context.directories.dev,
},
silent: false,
});

View File

@ -22,7 +22,7 @@ async function runDevServer({ context }) {
args: ['run', 'start'],
command: context.packageManager, // npm or yarn
processOptions: {
cwd: context.directories.devServer,
cwd: context.directories.dev,
env: {
...process.env,
LOWDEFY_BUILD_REF_RESOLVER: context.options.refResolver,

View File

@ -55,6 +55,10 @@ program
'--ref-resolver <ref-resolver-function-path>',
'Path to a JavaScript file containing a _ref resolver function to be used as the app default _ref resolver.'
)
.option(
'--server-directory <server-directory>',
'Change the server directory. Default is "<config-directory>/.lowdefy/server".'
)
.action(runCommand({ cliVersion, handler: build }));
program
@ -83,6 +87,10 @@ program
'--watch-ignore <paths...>',
'A list of paths to files or directories that should be ignored by the file watcher. Globs are supported. Specify each path to watch separated by spaces.'
)
.option(
'--dev-directory <dev-directory>',
'Change the development server directory. Default is "<config-directory>/.lowdefy/dev".'
)
.action(runCommand({ cliVersion, handler: dev }));
program
@ -109,6 +117,10 @@ program
'The package manager to use. Options are "npm" or "yarn".'
)
.option('--port <port>', 'Change the port the server is hosted at. Default is 3000.')
.option(
'--server-directory <server-directory>',
'Change the server directory. Default is "<config-directory>/.lowdefy/server".'
)
.action(runCommand({ cliVersion, handler: start }));
program.parse(process.argv);

View File

@ -27,8 +27,10 @@ function getDirectories({ configDirectory, options }) {
config: configDirectory,
build: path.join(dotLowdefy, 'server', 'build'),
dotLowdefy,
server: path.join(dotLowdefy, 'server'),
devServer: path.join(dotLowdefy, 'dev'),
server: options.serverDirectory
? path.resolve(options.serverDirectory)
: path.join(dotLowdefy, 'server'),
dev: options.devDirectory ? path.resolve(options.devDirectory) : path.join(dotLowdefy, 'dev'),
};
}

View File

@ -25,7 +25,7 @@ test('default directories', () => {
expect(directories).toEqual({
build: '/test/config/.lowdefy/server/build',
config: '/test/config',
devServer: '/test/config/.lowdefy/dev',
dev: '/test/config/.lowdefy/dev',
dotLowdefy: '/test/config/.lowdefy',
server: '/test/config/.lowdefy/server',
});
@ -42,8 +42,42 @@ test('specify outputDirectory in options', () => {
expect(directories).toEqual({
build: '/test/out/server/build',
config: '/test/config',
devServer: '/test/out/dev',
dev: '/test/out/dev',
dotLowdefy: '/test/out',
server: '/test/out/server',
});
});
test('specify serverDirectory in options', () => {
const directories = getDirectories({
configDirectory: '/test/config',
options: {
serverDirectory: '/test/server',
},
});
expect(directories).toEqual({
build: '/test/config/.lowdefy/server/build',
config: '/test/config',
dev: '/test/config/.lowdefy/dev',
dotLowdefy: '/test/config/.lowdefy',
server: '/test/server',
});
});
test('specify devDirectory in options', () => {
const directories = getDirectories({
configDirectory: '/test/config',
options: {
devDirectory: '/test/dev',
},
});
expect(directories).toEqual({
build: '/test/config/.lowdefy/server/build',
config: '/test/config',
dev: '/test/dev',
dotLowdefy: '/test/config/.lowdefy',
server: '/test/config/.lowdefy/server',
});
});

View File

@ -17,9 +17,14 @@
import fs from 'fs';
import path from 'path';
import { cleanDirectory, readFile } from '@lowdefy/node-utils';
import fetchNpmTarball from '../../utils/fetchNpmTarball.js';
import fetchNpmTarball from './fetchNpmTarball.js';
async function getServer({ context, packageName }) {
if (context.lowdefyVersion === 'local') {
context.print.warn(`Running local ${packageName}.`);
return;
}
async function getServer({ context }) {
let fetchServer = false;
const serverExists = fs.existsSync(path.join(context.directories.server, 'package.json'));
@ -39,12 +44,11 @@ async function getServer({ context }) {
if (fetchServer) {
context.print.spin('Fetching @lowdefy/server from npm.');
await fetchNpmTarball({
packageName: '@lowdefy/server',
packageName,
version: context.lowdefyVersion,
directory: context.directories.server,
});
context.print.log('Fetched @lowdefy/server from npm.');
return;
}
}

View File

@ -57,7 +57,7 @@ test('startUp, options empty', async () => {
directories: {
build: path.resolve(process.cwd(), './.lowdefy/server/build'),
config: path.resolve(process.cwd()),
devServer: path.resolve(process.cwd(), './.lowdefy/dev'),
dev: path.resolve(process.cwd(), './.lowdefy/dev'),
dotLowdefy: path.resolve(process.cwd(), './.lowdefy'),
server: path.resolve(process.cwd(), './.lowdefy/server'),
},
@ -104,7 +104,7 @@ test('startUp, options undefined', async () => {
directories: {
build: path.resolve(process.cwd(), './.lowdefy/server/build'),
config: path.resolve(process.cwd()),
devServer: path.resolve(process.cwd(), './.lowdefy/dev'),
dev: path.resolve(process.cwd(), './.lowdefy/dev'),
dotLowdefy: path.resolve(process.cwd(), './.lowdefy'),
server: path.resolve(process.cwd(), './.lowdefy/server'),
},
@ -150,7 +150,7 @@ test('startUp, options configDirectory', async () => {
directories: {
build: path.resolve(process.cwd(), './configDirectory/.lowdefy/server/build'),
config: path.resolve(process.cwd(), './configDirectory'),
devServer: path.resolve(process.cwd(), './configDirectory/.lowdefy/dev'),
dev: path.resolve(process.cwd(), './configDirectory/.lowdefy/dev'),
dotLowdefy: path.resolve(process.cwd(), './configDirectory/.lowdefy'),
server: path.resolve(process.cwd(), './configDirectory/.lowdefy/server'),
},
@ -180,7 +180,7 @@ test('startUp, options outputDirectory', async () => {
directories: {
build: path.resolve(process.cwd(), './outputDirectory/server/build'),
config: path.resolve(process.cwd()),
devServer: path.resolve(process.cwd(), './outputDirectory/dev'),
dev: path.resolve(process.cwd(), './outputDirectory/dev'),
dotLowdefy: path.resolve(process.cwd(), './outputDirectory'),
server: path.resolve(process.cwd(), './outputDirectory/server'),
},
@ -220,7 +220,7 @@ test('startUp, options configDirectory and outputDirectory', async () => {
directories: {
build: path.resolve(process.cwd(), './outputDirectory/server/build'),
config: path.resolve(process.cwd(), './configDirectory'),
devServer: path.resolve(process.cwd(), './outputDirectory/dev'),
dev: path.resolve(process.cwd(), './outputDirectory/dev'),
dotLowdefy: path.resolve(process.cwd(), './outputDirectory'),
server: path.resolve(process.cwd(), './outputDirectory/server'),
},
@ -254,7 +254,7 @@ test('startUp, no lowdefyVersion returned', async () => {
directories: {
build: path.resolve(process.cwd(), './.lowdefy/server/build'),
config: path.resolve(process.cwd()),
devServer: path.resolve(process.cwd(), './.lowdefy/dev'),
dev: path.resolve(process.cwd(), './.lowdefy/dev'),
dotLowdefy: path.resolve(process.cwd(), './.lowdefy'),
server: path.resolve(process.cwd(), './.lowdefy/server'),
},