mirror of
https://github.com/lowdefy/lowdefy.git
synced 2025-03-31 15:20:32 +08:00
Merge branch 'v4' into v4-connection-knex
This commit is contained in:
commit
f697b789d4
.gitignoreindex.js
packages
build/src
build
addDefaultPages
buildAuth
cleanBuildDirectory.jswriteApp.jswriteApp.test.jswriteConfig.jswriteConfig.test.jswriteConnections.jswriteConnections.test.jswriteGlobal.jswriteGlobal.test.jswriteMenus.jswriteMenus.test.jswritePages.jswritePages.test.jswritePluginImports
writeBlockImports.jswriteConnectionImports.jswriteIconImports.jswriteOperatorImports.jswriteStyleImports.js
writeRequests.jswriteRequests.test.jswriteTypes.jsutils
server-dev
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,7 +21,6 @@ packages/server-dev/build/**
|
||||
|
||||
.DS_Store
|
||||
|
||||
packages/build/src/test/writeFile.txt
|
||||
packages/build/build/**
|
||||
|
||||
packages/plugins/connections/connection-mongodb/mongod-*
|
||||
|
@ -62,7 +62,6 @@ test('addDefaultPages, no pages array', async () => {
|
||||
},
|
||||
id: 'home',
|
||||
properties: {
|
||||
icon: 'HomeOutlined',
|
||||
title: 'Go to home page',
|
||||
type: 'Link',
|
||||
},
|
||||
@ -109,7 +108,6 @@ test('addDefaultPages, empty pages array', async () => {
|
||||
},
|
||||
id: 'home',
|
||||
properties: {
|
||||
icon: 'HomeOutlined',
|
||||
title: 'Go to home page',
|
||||
type: 'Link',
|
||||
},
|
||||
@ -160,7 +158,6 @@ test('addDefaultPages, pages without 404 page', async () => {
|
||||
},
|
||||
id: 'home',
|
||||
properties: {
|
||||
icon: 'HomeOutlined',
|
||||
title: 'Go to home page',
|
||||
type: 'Link',
|
||||
},
|
||||
|
@ -38,6 +38,7 @@ test('buildAuth default', async () => {
|
||||
roles: {},
|
||||
},
|
||||
},
|
||||
theme: {},
|
||||
},
|
||||
pages: [
|
||||
{ id: 'a', type: 'Context', auth: { public: true } },
|
||||
@ -59,6 +60,7 @@ test('buildAuth no pages', async () => {
|
||||
roles: {},
|
||||
},
|
||||
},
|
||||
theme: {},
|
||||
},
|
||||
});
|
||||
});
|
||||
@ -89,6 +91,7 @@ test('buildAuth all protected, some public', async () => {
|
||||
roles: {},
|
||||
},
|
||||
},
|
||||
theme: {},
|
||||
},
|
||||
pages: [
|
||||
{ id: 'a', type: 'Context', auth: { public: true } },
|
||||
@ -124,6 +127,7 @@ test('buildAuth all public, some protected', async () => {
|
||||
roles: {},
|
||||
},
|
||||
},
|
||||
theme: {},
|
||||
},
|
||||
pages: [
|
||||
{ id: 'a', type: 'Context', auth: { public: false } },
|
||||
@ -159,6 +163,7 @@ test('buildAuth all public', async () => {
|
||||
roles: {},
|
||||
},
|
||||
},
|
||||
theme: {},
|
||||
},
|
||||
pages: [
|
||||
{ id: 'a', type: 'Context', auth: { public: true } },
|
||||
@ -194,6 +199,7 @@ test('buildAuth all protected', async () => {
|
||||
roles: {},
|
||||
},
|
||||
},
|
||||
theme: {},
|
||||
},
|
||||
pages: [
|
||||
{ id: 'a', type: 'Context', auth: { public: false } },
|
||||
@ -233,6 +239,7 @@ test('buildAuth roles', async () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
theme: {},
|
||||
},
|
||||
pages: [
|
||||
{ id: 'page1', type: 'Context', auth: { public: false, roles: ['role1', 'role2'] } },
|
||||
@ -288,6 +295,7 @@ test('buildAuth roles and protected pages array', async () => {
|
||||
protected: ['page1'],
|
||||
},
|
||||
},
|
||||
theme: {},
|
||||
},
|
||||
pages: [{ id: 'page1', type: 'Context', auth: { public: false, roles: ['role1'] } }],
|
||||
});
|
||||
@ -319,6 +327,7 @@ test('buildAuth roles and protected true', async () => {
|
||||
protected: true,
|
||||
},
|
||||
},
|
||||
theme: {},
|
||||
},
|
||||
pages: [{ id: 'page1', type: 'Context', auth: { public: false, roles: ['role1'] } }],
|
||||
});
|
||||
|
@ -17,7 +17,7 @@
|
||||
import { cleanDirectory } from '@lowdefy/node-utils';
|
||||
|
||||
async function cleanBuildDirectory({ context }) {
|
||||
return cleanDirectory(context.directories.build);
|
||||
await cleanDirectory(context.directories.build);
|
||||
}
|
||||
|
||||
export default cleanBuildDirectory;
|
||||
|
@ -15,10 +15,7 @@
|
||||
*/
|
||||
|
||||
async function writeApp({ components, context }) {
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'app.json',
|
||||
content: JSON.stringify(components.app || {}, null, 2),
|
||||
});
|
||||
await context.writeBuildArtifact('app.json', JSON.stringify(components.app || {}, null, 2));
|
||||
}
|
||||
|
||||
export default writeApp;
|
||||
|
@ -34,12 +34,10 @@ test('writeApp', async () => {
|
||||
await writeApp({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'app.json',
|
||||
content: `{
|
||||
'app.json',
|
||||
`{
|
||||
"key": "value"
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -49,25 +47,11 @@ test('writeApp empty config', async () => {
|
||||
app: {},
|
||||
};
|
||||
await writeApp({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'app.json',
|
||||
content: `{}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([['app.json', `{}`]]);
|
||||
});
|
||||
|
||||
test('writeApp config undefined', async () => {
|
||||
const components = {};
|
||||
await writeApp({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'app.json',
|
||||
content: `{}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([['app.json', `{}`]]);
|
||||
});
|
||||
|
@ -15,10 +15,7 @@
|
||||
*/
|
||||
|
||||
async function writeConfig({ components, context }) {
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'config.json',
|
||||
content: JSON.stringify(components.config || {}, null, 2),
|
||||
});
|
||||
await context.writeBuildArtifact('config.json', JSON.stringify(components.config || {}, null, 2));
|
||||
}
|
||||
|
||||
export default writeConfig;
|
||||
|
@ -34,12 +34,10 @@ test('writeConfig', async () => {
|
||||
await writeConfig({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'config.json',
|
||||
content: `{
|
||||
'config.json',
|
||||
`{
|
||||
"key": "value"
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -49,25 +47,11 @@ test('writeConfig empty config', async () => {
|
||||
config: {},
|
||||
};
|
||||
await writeConfig({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'config.json',
|
||||
content: `{}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([['config.json', `{}`]]);
|
||||
});
|
||||
|
||||
test('writeConfig config undefined', async () => {
|
||||
const components = {};
|
||||
await writeConfig({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'config.json',
|
||||
content: `{}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([['config.json', `{}`]]);
|
||||
});
|
||||
|
@ -22,10 +22,10 @@ async function writeConnections({ components, context }) {
|
||||
throw new Error(`Connections is not an array.`);
|
||||
}
|
||||
const writePromises = components.connections.map(async (connection) => {
|
||||
await context.writeBuildArtifact({
|
||||
filePath: `connections/${connection.connectionId}.json`,
|
||||
content: JSON.stringify(connection, null, 2),
|
||||
});
|
||||
await context.writeBuildArtifact(
|
||||
`connections/${connection.connectionId}.json`,
|
||||
JSON.stringify(connection, null, 2)
|
||||
);
|
||||
});
|
||||
return Promise.all(writePromises);
|
||||
}
|
||||
|
@ -40,16 +40,14 @@ test('writeConnections write connection', async () => {
|
||||
await writeConnections({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'connections/connection1.json',
|
||||
content: `{
|
||||
'connections/connection1.json',
|
||||
`{
|
||||
"id": "connection:connection1",
|
||||
"connectionId": "connection1",
|
||||
"properties": {
|
||||
"prop": "val"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -70,22 +68,18 @@ test('writeConnections multiple connection', async () => {
|
||||
await writeConnections({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'connections/connection1.json',
|
||||
content: `{
|
||||
'connections/connection1.json',
|
||||
`{
|
||||
"id": "connection:connection1",
|
||||
"connectionId": "connection1"
|
||||
}`,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
filePath: 'connections/connection2.json',
|
||||
content: `{
|
||||
'connections/connection2.json',
|
||||
`{
|
||||
"id": "connection:connection2",
|
||||
"connectionId": "connection2"
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
@ -23,10 +23,7 @@ async function writeGlobal({ components, context }) {
|
||||
if (!type.isObject(components.global)) {
|
||||
throw new Error('Global is not an object.');
|
||||
}
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'global.json',
|
||||
content: JSON.stringify(components.global, null, 2),
|
||||
});
|
||||
await context.writeBuildArtifact('global.json', JSON.stringify(components.global, null, 2));
|
||||
}
|
||||
|
||||
export default writeGlobal;
|
||||
|
@ -34,12 +34,10 @@ test('writeGlobal', async () => {
|
||||
await writeGlobal({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'global.json',
|
||||
content: `{
|
||||
'global.json',
|
||||
`{
|
||||
"key": "value"
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -49,27 +47,13 @@ test('writeGlobal empty global', async () => {
|
||||
global: {},
|
||||
};
|
||||
await writeGlobal({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'global.json',
|
||||
content: `{}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([['global.json', `{}`]]);
|
||||
});
|
||||
|
||||
test('writeGlobal global undefined', async () => {
|
||||
const components = {};
|
||||
await writeGlobal({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'global.json',
|
||||
content: `{}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([['global.json', `{}`]]);
|
||||
});
|
||||
|
||||
test('writeGlobal global not an object', async () => {
|
||||
|
@ -20,10 +20,7 @@ async function writeMenus({ components, context }) {
|
||||
if (!type.isArray(components.menus)) {
|
||||
throw new Error('Menus is not an array.');
|
||||
}
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'menus.json',
|
||||
content: JSON.stringify(components.menus, null, 2),
|
||||
});
|
||||
await context.writeBuildArtifact('menus.json', JSON.stringify(components.menus, null, 2));
|
||||
}
|
||||
|
||||
export default writeMenus;
|
||||
|
@ -38,16 +38,14 @@ test('writeMenus', async () => {
|
||||
await writeMenus({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'menus.json',
|
||||
content: `[
|
||||
'menus.json',
|
||||
`[
|
||||
{
|
||||
"id": "menu:default",
|
||||
"menuId": "default",
|
||||
"links": []
|
||||
}
|
||||
]`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -57,14 +55,7 @@ test('writeMenus empty menus', async () => {
|
||||
menus: [],
|
||||
};
|
||||
await writeMenus({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'menus.json',
|
||||
content: `[]`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([['menus.json', `[]`]]);
|
||||
});
|
||||
|
||||
test('writeMenus menus undefined', async () => {
|
||||
|
@ -20,10 +20,10 @@ async function writePage({ page, context }) {
|
||||
if (!type.isObject(page)) {
|
||||
throw new Error(`Page is not an object. Received ${JSON.stringify(page)}`);
|
||||
}
|
||||
await context.writeBuildArtifact({
|
||||
filePath: `pages/${page.pageId}/${page.pageId}.json`,
|
||||
content: JSON.stringify(page, null, 2),
|
||||
});
|
||||
await context.writeBuildArtifact(
|
||||
`pages/${page.pageId}/${page.pageId}.json`,
|
||||
JSON.stringify(page, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
async function writePages({ components, context }) {
|
||||
|
@ -39,15 +39,13 @@ test('writePages write page', async () => {
|
||||
await writePages({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'pages/page1/page1.json',
|
||||
content: `{
|
||||
'pages/page1/page1.json',
|
||||
`{
|
||||
"id": "page:page1",
|
||||
"pageId": "page1",
|
||||
"blockId": "page1",
|
||||
"requests": []
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -72,26 +70,22 @@ test('writePages multiple pages', async () => {
|
||||
await writePages({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'pages/page1/page1.json',
|
||||
content: `{
|
||||
'pages/page1/page1.json',
|
||||
`{
|
||||
"id": "page:page1",
|
||||
"pageId": "page1",
|
||||
"blockId": "page1",
|
||||
"requests": []
|
||||
}`,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
filePath: 'pages/page2/page2.json',
|
||||
content: `{
|
||||
'pages/page2/page2.json',
|
||||
`{
|
||||
"id": "page:page2",
|
||||
"pageId": "page2",
|
||||
"blockId": "page2",
|
||||
"requests": []
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
@ -17,13 +17,13 @@
|
||||
import generateImportFile from './generateImportFile.js';
|
||||
|
||||
async function writeBlockImports({ components, context }) {
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'plugins/blocks.js',
|
||||
content: generateImportFile({
|
||||
await context.writeBuildArtifact(
|
||||
'plugins/blocks.js',
|
||||
generateImportFile({
|
||||
types: components.types.blocks,
|
||||
importPath: 'blocks',
|
||||
}),
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default writeBlockImports;
|
||||
|
@ -17,13 +17,13 @@
|
||||
import generateImportFile from './generateImportFile.js';
|
||||
|
||||
async function writeConnectionImports({ components, context }) {
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'plugins/connections.js',
|
||||
content: generateImportFile({
|
||||
await context.writeBuildArtifact(
|
||||
'plugins/connections.js',
|
||||
generateImportFile({
|
||||
types: components.types.connections,
|
||||
importPath: 'connections',
|
||||
}),
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default writeConnectionImports;
|
||||
|
@ -28,10 +28,7 @@ export default {
|
||||
|
||||
async function writeIconImports({ components, context }) {
|
||||
const templateFn = nunjucksFunction(template);
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'plugins/icons.js',
|
||||
content: templateFn({ packages: components.icons }),
|
||||
});
|
||||
await context.writeBuildArtifact('plugins/icons.js', templateFn({ packages: components.icons }));
|
||||
}
|
||||
|
||||
export default writeIconImports;
|
||||
|
@ -18,20 +18,20 @@ import generateImportFile from './generateImportFile.js';
|
||||
|
||||
async function writeOperatorImports({ components, context }) {
|
||||
// TODO: import _not and _type for validation.
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'plugins/operatorsClient.js',
|
||||
content: generateImportFile({
|
||||
await context.writeBuildArtifact(
|
||||
'plugins/operatorsClient.js',
|
||||
generateImportFile({
|
||||
types: components.types.operators.client,
|
||||
importPath: 'operators/client',
|
||||
}),
|
||||
});
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'plugins/operatorsServer.js',
|
||||
content: generateImportFile({
|
||||
})
|
||||
);
|
||||
await context.writeBuildArtifact(
|
||||
'plugins/operatorsServer.js',
|
||||
generateImportFile({
|
||||
types: components.types.operators.server,
|
||||
importPath: 'operators/server',
|
||||
}),
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export default writeOperatorImports;
|
||||
|
@ -24,10 +24,10 @@ const template = `@import '@lowdefy/layout/style.less';
|
||||
|
||||
async function writeStyleImports({ components, context }) {
|
||||
const templateFn = nunjucksFunction(template);
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'plugins/styles.less',
|
||||
content: templateFn({ styles: components.styles }),
|
||||
});
|
||||
await context.writeBuildArtifact(
|
||||
'plugins/styles.less',
|
||||
templateFn({ styles: components.styles })
|
||||
);
|
||||
}
|
||||
|
||||
export default writeStyleImports;
|
||||
|
@ -19,10 +19,10 @@ import { type } from '@lowdefy/helpers';
|
||||
async function writeRequestsOnPage({ page, context }) {
|
||||
return Promise.all(
|
||||
page.requests.map(async (request) => {
|
||||
await context.writeBuildArtifact({
|
||||
filePath: `pages/${page.pageId}/requests/${request.requestId}.json`,
|
||||
content: JSON.stringify(request, null, 2),
|
||||
});
|
||||
await context.writeBuildArtifact(
|
||||
`pages/${page.pageId}/requests/${request.requestId}.json`,
|
||||
JSON.stringify(request, null, 2)
|
||||
);
|
||||
delete request.properties;
|
||||
delete request.type;
|
||||
delete request.connectionId;
|
||||
|
@ -49,9 +49,8 @@ test('writeRequests write request', async () => {
|
||||
await writeRequests({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'pages/page1/requests/request1.json',
|
||||
content: `{
|
||||
'pages/page1/requests/request1.json',
|
||||
`{
|
||||
"id": "request:page1:request1",
|
||||
"requestId": "request1",
|
||||
"pageId": "page1",
|
||||
@ -65,7 +64,6 @@ test('writeRequests write request', async () => {
|
||||
"key": "value"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -104,9 +102,8 @@ test('writeRequests write multiple requests on a page', async () => {
|
||||
await writeRequests({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'pages/page1/requests/request1.json',
|
||||
content: `{
|
||||
'pages/page1/requests/request1.json',
|
||||
`{
|
||||
"id": "request:page1:request1",
|
||||
"requestId": "request1",
|
||||
"pageId": "page1",
|
||||
@ -120,12 +117,10 @@ test('writeRequests write multiple requests on a page', async () => {
|
||||
"key": "value"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
filePath: 'pages/page1/requests/request2.json',
|
||||
content: `{
|
||||
'pages/page1/requests/request2.json',
|
||||
`{
|
||||
"id": "request:page1:request2",
|
||||
"requestId": "request2",
|
||||
"pageId": "page1",
|
||||
@ -139,7 +134,6 @@ test('writeRequests write multiple requests on a page', async () => {
|
||||
"key": "value"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
@ -184,9 +178,8 @@ test('writeRequests write requests on a for multiple pages', async () => {
|
||||
await writeRequests({ components, context });
|
||||
expect(mockWriteBuildArtifact.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
filePath: 'pages/page1/requests/request1.json',
|
||||
content: `{
|
||||
'pages/page1/requests/request1.json',
|
||||
`{
|
||||
"id": "request:page1:request1",
|
||||
"requestId": "request1",
|
||||
"pageId": "page1",
|
||||
@ -200,12 +193,10 @@ test('writeRequests write requests on a for multiple pages', async () => {
|
||||
"key": "value"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
filePath: 'pages/page2/requests/request1.json',
|
||||
content: `{
|
||||
'pages/page2/requests/request1.json',
|
||||
`{
|
||||
"id": "request:page2:request1",
|
||||
"requestId": "request1",
|
||||
"pageId": "page2",
|
||||
@ -219,7 +210,6 @@ test('writeRequests write requests on a for multiple pages', async () => {
|
||||
"key": "value"
|
||||
}
|
||||
}`,
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
@ -15,10 +15,7 @@
|
||||
*/
|
||||
|
||||
async function writeTypes({ components, context }) {
|
||||
await context.writeBuildArtifact({
|
||||
filePath: 'types.json',
|
||||
content: JSON.stringify(components.types, null, 2),
|
||||
});
|
||||
await context.writeBuildArtifact('types.json', JSON.stringify(components.types, null, 2));
|
||||
}
|
||||
|
||||
export default writeTypes;
|
||||
|
@ -19,8 +19,8 @@
|
||||
import { readFile } from '@lowdefy/node-utils';
|
||||
|
||||
import createCounter from './utils/createCounter.js';
|
||||
import createReadConfigFile from './utils/files/readConfigFile.js';
|
||||
import createWriteBuildArtifact from './utils/files/writeBuildArtifact.js';
|
||||
import createReadConfigFile from './utils/readConfigFile.js';
|
||||
import createWriteBuildArtifact from './utils/writeBuildArtifact.js';
|
||||
|
||||
import addDefaultPages from './build/addDefaultPages/addDefaultPages.js';
|
||||
import buildAuth from './build/buildAuth/buildAuth.js';
|
||||
|
@ -1,41 +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 createWriteBuildArtifact from './writeBuildArtifact.js';
|
||||
|
||||
const directories = { build: path.resolve(process.cwd(), 'src/test/fileSetter') };
|
||||
|
||||
test('writeFile', async () => {
|
||||
const filePath = path.resolve(directories.build, 'writeFile.txt');
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
} catch (error) {
|
||||
//pass
|
||||
}
|
||||
expect(fs.existsSync(filePath)).toBe(false);
|
||||
const writeBuildArtifact = createWriteBuildArtifact({ directories });
|
||||
|
||||
await writeBuildArtifact({
|
||||
filePath: 'writeFile.txt',
|
||||
content: 'Test fileSetter file',
|
||||
});
|
||||
const res = fs.readFileSync(filePath, 'utf8');
|
||||
expect(res).toEqual('Test fileSetter file');
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
} catch (error) {
|
||||
//pass
|
||||
}
|
||||
});
|
@ -18,8 +18,8 @@ import path from 'path';
|
||||
import { writeFile } from '@lowdefy/node-utils';
|
||||
|
||||
function createWriteBuildArtifact({ directories }) {
|
||||
async function writeBuildArtifact({ filePath, content }) {
|
||||
return writeFile(path.resolve(directories.build, filePath), content);
|
||||
async function writeBuildArtifact(filePath, content) {
|
||||
await writeFile(path.join(directories.build, filePath), content);
|
||||
}
|
||||
return writeBuildArtifact;
|
||||
}
|
33
packages/build/src/utils/writeBuildArtifact.test.js
Normal file
33
packages/build/src/utils/writeBuildArtifact.test.js
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
jest.mock('@lowdefy/node-utils', () => {
|
||||
return {
|
||||
writeFile: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const directories = { build: '/build' };
|
||||
|
||||
test('Write build artifact.', async () => {
|
||||
const nodeUtils = await import('@lowdefy/node-utils');
|
||||
nodeUtils.readFile.mockImplementation(() => Promise.resolve(null));
|
||||
const createWriteBuildArtifact = (await import('./writeBuildArtifact.js')).default;
|
||||
|
||||
const writeBuildArtifact = createWriteBuildArtifact({ directories });
|
||||
|
||||
await writeBuildArtifact('artifact.txt', 'Test artifact content');
|
||||
expect(nodeUtils.writeFile.mock.calls).toEqual([
|
||||
['/build/artifact.txt', 'Test artifact content'],
|
||||
]);
|
||||
});
|
@ -23,11 +23,12 @@ module.exports = withLess({
|
||||
}
|
||||
return config;
|
||||
},
|
||||
swcMinify: true,
|
||||
compress: false,
|
||||
outputFileTracing: false,
|
||||
poweredByHeader: false,
|
||||
// productionBrowserSourceMaps: true
|
||||
// experimental: {
|
||||
// concurrentFeatures: true,
|
||||
// },
|
||||
generateEtags: false,
|
||||
optimizeFonts: false,
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
|
@ -16,6 +16,7 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import path from 'path';
|
||||
import { createRequire } from 'module';
|
||||
import yargs from 'yargs';
|
||||
import { hideBin } from 'yargs/helpers';
|
||||
|
||||
@ -30,10 +31,16 @@ import shutdownServer from './processes/shutdownServer.mjs';
|
||||
import startWatchers from './processes/startWatchers.mjs';
|
||||
|
||||
const argv = yargs(hideBin(process.argv)).argv;
|
||||
const require = createRequire(import.meta.url);
|
||||
|
||||
async function getContext() {
|
||||
const { verbose = false } = argv;
|
||||
const context = {
|
||||
bin: {
|
||||
// TODO: The string replace is a little hacky and will fail if the location of the bin changes,
|
||||
lowdefyBuild: require.resolve('@lowdefy/build').replace('index.js', 'scripts/run.js'),
|
||||
next: require.resolve('next').replace('server/next.js', 'bin/next'),
|
||||
},
|
||||
directories: {
|
||||
build: path.resolve(process.cwd(), './build'),
|
||||
config: path.resolve(
|
||||
|
@ -16,12 +16,12 @@
|
||||
|
||||
import { spawnProcess } from '@lowdefy/node-utils';
|
||||
|
||||
function lowdefyBuild({ packageManager, directories }) {
|
||||
function lowdefyBuild({ bin, directories }) {
|
||||
return async () => {
|
||||
await spawnProcess({
|
||||
command: 'node',
|
||||
args: [bin.lowdefyBuild],
|
||||
logger: console,
|
||||
args: ['run', 'build:lowdefy'],
|
||||
command: packageManager,
|
||||
processOptions: {
|
||||
env: {
|
||||
...process.env,
|
||||
|
@ -16,14 +16,14 @@
|
||||
|
||||
import { spawnProcess } from '@lowdefy/node-utils';
|
||||
|
||||
function nextBuild({ packageManager, verbose }) {
|
||||
function nextBuild(context) {
|
||||
return async () => {
|
||||
console.log('Building app...');
|
||||
await spawnProcess({
|
||||
logger: console,
|
||||
args: ['run', 'build:next'],
|
||||
command: packageManager,
|
||||
silent: !verbose,
|
||||
command: 'node',
|
||||
args: [context.bin.next, 'build'],
|
||||
silent: !context.verbose,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -20,8 +20,8 @@ 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));
|
||||
const dotEnv = await readFile(path.join(context.directories.config, '.env'));
|
||||
context.serverEnv = dotenv.parse(dotEnv || '');
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -14,16 +14,14 @@
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import startServerProcess from './startServerProcess.mjs';
|
||||
import startNextServer from './startNextServer.mjs';
|
||||
|
||||
function shutdownServer(context) {
|
||||
return async () => {
|
||||
if (context.serverProcess) {
|
||||
console.log('Restarting server...');
|
||||
context.serverProcess.kill();
|
||||
startServerProcess(context);
|
||||
}
|
||||
function restartServer(context) {
|
||||
return () => {
|
||||
context.shutdownServer(); // Is this needed here?
|
||||
console.log('Restarting server...');
|
||||
startNextServer(context);
|
||||
};
|
||||
}
|
||||
|
||||
export default shutdownServer;
|
||||
export default restartServer;
|
||||
|
@ -15,10 +15,19 @@
|
||||
*/
|
||||
|
||||
function shutdownServer(context) {
|
||||
return async () => {
|
||||
if (context.serverProcess) {
|
||||
console.log('Shutting down server...');
|
||||
context.serverProcess.kill();
|
||||
return () => {
|
||||
if (context.nextServer) {
|
||||
// console.log(
|
||||
// `Existing server ${context.nextServer.pid}, killed: ${context.nextServer.killed}`
|
||||
// );
|
||||
if (!context.nextServer.killed) {
|
||||
console.log('Shutting down server...');
|
||||
context.nextServer.kill();
|
||||
// console.log(
|
||||
// `Killed server ${context.nextServer.pid}, killed: ${context.nextServer.killed}`
|
||||
// );
|
||||
}
|
||||
context.nextServer = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -17,10 +17,12 @@
|
||||
import spawnProcess from '../utils/spawnProcess.mjs';
|
||||
|
||||
function startServerProcess(context) {
|
||||
context.serverProcess = spawnProcess({
|
||||
context.shutdownServer();
|
||||
|
||||
const nextServer = spawnProcess({
|
||||
logger: console,
|
||||
command: context.packageManager,
|
||||
args: ['run', 'next', 'start'],
|
||||
command: 'node',
|
||||
args: [context.bin.next, 'start'],
|
||||
silent: false,
|
||||
processOptions: {
|
||||
env: {
|
||||
@ -30,12 +32,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;
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
@ -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),
|
||||
]);
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -20,6 +20,59 @@ import opener from 'opener';
|
||||
import getContext from './getContext.mjs';
|
||||
import startServer from './processes/startServer.mjs';
|
||||
|
||||
/*
|
||||
The run script does the following:
|
||||
- Run the initial Lowdefy build, install plugins, and next build and read .env
|
||||
- Start file watchers to reload config and restart server if necessary
|
||||
- Start the server
|
||||
- Open a browser window.
|
||||
|
||||
Three watchers are started:
|
||||
|
||||
## Lowdefy build watcher
|
||||
Watches:
|
||||
- <config-dir>,
|
||||
- <watch-dirs>
|
||||
- !<ignore-dirs>
|
||||
The Lowdefy build watcher watches the Lowdefy config files for changes
|
||||
and runs Lowdefy build when they change, and triggers a soft reload.
|
||||
|
||||
If lowdefy version in lowdefy.yaml
|
||||
is changed, the server warns and exits.
|
||||
|
||||
## .env watcher
|
||||
|
||||
If the .env file is changed, the new file is parsed, and the server restarted with the new env
|
||||
and the server hard reloads.
|
||||
|
||||
## Next build watcher
|
||||
|
||||
The Next build watcher watches for any files where the app should be rebuilt and restarted.
|
||||
It watches:
|
||||
- <build-dir>/plugins/**
|
||||
- <build-dir>/config.json
|
||||
- <server-dir>/package.json
|
||||
|
||||
If app theme or config changes:
|
||||
- <build-dir>/config.json changes, rebuild and restart server.
|
||||
|
||||
If new plugin type in an existing plugin package is used:
|
||||
- <build-dir>/plugins/** changes, rebuild next and restart server.
|
||||
|
||||
If new plugin type in a new plugin package is used:
|
||||
- <server-dir>/package.json changes, run npm install, rebuild next and restart server.
|
||||
|
||||
# Reload mechanism
|
||||
|
||||
The web client creates a Server Sent Events connection with the server on the /api/reload route.
|
||||
The server watches the <build-dir>/reload file, which is written every time the server should reload,
|
||||
and sends an event to the client to reload the config. The client then uses a SWR cache mutation to
|
||||
fetch the new config.
|
||||
|
||||
If the server is restarted, the event stream is closed because the original server was shut down. The client starts
|
||||
pinging the /api/ping route, until it detects a new server has started, and then reloads the window.
|
||||
*/
|
||||
|
||||
async function run() {
|
||||
const context = await getContext();
|
||||
await context.initialBuild();
|
||||
@ -30,6 +83,7 @@ async function run() {
|
||||
opener(`http://localhost:${context.port}`);
|
||||
await serverPromise;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
context.shutdownServer();
|
||||
throw error;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
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;
|
Loading…
x
Reference in New Issue
Block a user