mirror of
https://github.com/lowdefy/lowdefy.git
synced 2025-03-19 15:01:06 +08:00
feat(build): add block meta loader
This commit is contained in:
parent
cf7c3c52c5
commit
2a483ad8e6
2
.gitignore
vendored
2
.gitignore
vendored
@ -9,4 +9,4 @@
|
||||
|
||||
packages/express/config/**
|
||||
packages/build/src/test/writeFile.txt
|
||||
packages/build/src/utils/files/writeFile.test.js
|
||||
packages/build/src/test/fetchMetaCache/cachekey.json
|
||||
|
9
.pnp.js
generated
9
.pnp.js
generated
@ -5026,6 +5026,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["@lowdefy/helpers", "workspace:packages/helpers"],
|
||||
["@lowdefy/nunjucks", "workspace:packages/nunjucks"],
|
||||
["ajv", "npm:6.12.6"],
|
||||
["axios", "npm:0.20.0"],
|
||||
["babel-jest", "virtual:4a7337632ff6e9ee5a1c45a62a9ff4cc325a9367b21424babda93e269fe01b671e885bc41bdeebafb83c81f2a8eebbf0102043354a4e58905f61c8c3387cda1e#npm:26.5.2"],
|
||||
["babel-loader", "virtual:aad21d373d6721af7549b2798c35c4ea25d83c122770abeaa0a4343ba20d55700fd9e3c5fa3289a7e6f591d09e8790df14d525c719ca4729b178b8644cca082a#npm:8.1.0"],
|
||||
["dataloader", "npm:2.0.0"],
|
||||
@ -8696,6 +8697,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) {
|
||||
["is-buffer", "npm:2.0.4"]
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}],
|
||||
["npm:0.20.0", {
|
||||
"packageLocation": "./.yarn/cache/axios-npm-0.20.0-535d9e0086-6cf2e96317.zip/node_modules/axios/",
|
||||
"packageDependencies": [
|
||||
["axios", "npm:0.20.0"],
|
||||
["follow-redirects", "npm:1.13.0"]
|
||||
],
|
||||
"linkType": "HARD",
|
||||
}]
|
||||
]],
|
||||
["axobject-query", [
|
||||
|
BIN
.yarn/cache/axios-npm-0.20.0-535d9e0086-6cf2e96317.zip
vendored
Normal file
BIN
.yarn/cache/axios-npm-0.20.0-535d9e0086-6cf2e96317.zip
vendored
Normal file
Binary file not shown.
@ -31,6 +31,7 @@
|
||||
"@lowdefy/helpers": "1.1.0",
|
||||
"@lowdefy/nunjucks": "1.0.0",
|
||||
"ajv": "6.12.6",
|
||||
"axios": "0.20.0",
|
||||
"dataloader": "2.0.0",
|
||||
"js-yaml": "3.14.0",
|
||||
"json5": "2.1.3",
|
||||
|
82
packages/build/src/build.js
Normal file
82
packages/build/src/build.js
Normal file
@ -0,0 +1,82 @@
|
||||
/* eslint-disable no-console */
|
||||
|
||||
/*
|
||||
Copyright 2020 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 createFileLoader from './loaders/fileLoader';
|
||||
import createFileSetter from './loaders/fileSetter';
|
||||
import createMetaLoader from './loaders/metaLoader';
|
||||
|
||||
import buildConnections from './build/buildConnections';
|
||||
import buildMenu from './build/buildMenu';
|
||||
import buildPages from './build/buildPages';
|
||||
import buildRefs from './build/buildRefs';
|
||||
import cleanOutputDirectory from './build/cleanOutputDirectory';
|
||||
import testSchema from './build/testSchema';
|
||||
import writeConnections from './build/writeConnections';
|
||||
import writeGlobal from './build/writeGlobal';
|
||||
import writeMenus from './build/writeMenus';
|
||||
import writePages from './build/writePages';
|
||||
import writeRequests from './build/writeRequests';
|
||||
|
||||
function createContext(bootstrapContext) {
|
||||
const { logger, cacheDirectory, configDirectory, outputDirectory } = bootstrapContext;
|
||||
if (!type.isFunction(logger.success)) {
|
||||
logger.success = logger.log;
|
||||
}
|
||||
const context = {
|
||||
logger,
|
||||
configLoader: createFileLoader({ baseDirectory: configDirectory }),
|
||||
artifactSetter: createFileSetter({ baseDirectory: outputDirectory }),
|
||||
outputDirectory,
|
||||
cacheDirectory,
|
||||
};
|
||||
return context;
|
||||
}
|
||||
|
||||
async function build() {
|
||||
const bootstrapContext = {
|
||||
logger: console,
|
||||
cacheDirectory: path.resolve(process.cwd(), '/.lowdefy/.cache'),
|
||||
configDirectory: process.cwd(),
|
||||
outputDirectory: path.resolve(process.cwd(), '/.lowdefy/build'),
|
||||
};
|
||||
const context = createContext(bootstrapContext);
|
||||
try {
|
||||
let components = await buildRefs({ context });
|
||||
await testSchema({ components, context });
|
||||
context.metaLoader = createMetaLoader({ components, context });
|
||||
await buildConnections({ components, context });
|
||||
await buildPages({ components, context });
|
||||
await buildMenu({ components, context });
|
||||
await cleanOutputDirectory({ context });
|
||||
await writeConnections({ components, context });
|
||||
await writeRequests({ components, context });
|
||||
await writePages({ components, context });
|
||||
await writeGlobal({ components, context });
|
||||
await writeMenus({ components, context });
|
||||
} catch (error) {
|
||||
context.logger.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export { createContext };
|
||||
|
||||
export default build;
|
@ -17,7 +17,6 @@
|
||||
*/
|
||||
|
||||
import { set, type } from '@lowdefy/helpers';
|
||||
const blockTypes = {};
|
||||
|
||||
/* Page and block build steps
|
||||
|
||||
@ -108,8 +107,8 @@ async function setBlockMeta(block, context) {
|
||||
)}`
|
||||
);
|
||||
}
|
||||
const { category, loading, module, scope, url, valueType } = meta;
|
||||
block.meta = { category, loading, module, scope, url };
|
||||
const { category, loading, moduleFederation, valueType } = meta;
|
||||
block.meta = { category, loading, moduleFederation };
|
||||
if (category === 'input') {
|
||||
block.meta.valueType = valueType;
|
||||
}
|
||||
|
@ -28,12 +28,14 @@ const logger = {
|
||||
const blockMetas = {
|
||||
Context: {
|
||||
category: 'context',
|
||||
scope: 'blocks',
|
||||
module: 'Context',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
loading: {
|
||||
type: 'Spinner',
|
||||
},
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'Context',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
$id: 'https://example.com/Container.json',
|
||||
@ -41,12 +43,14 @@ const blockMetas = {
|
||||
},
|
||||
Container: {
|
||||
category: 'container',
|
||||
scope: 'blocks',
|
||||
module: 'Container',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
loading: {
|
||||
type: 'Spinner',
|
||||
},
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'Container',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
$id: 'https://example.com/Container.json',
|
||||
@ -54,12 +58,14 @@ const blockMetas = {
|
||||
},
|
||||
List: {
|
||||
category: 'list',
|
||||
scope: 'blocks',
|
||||
module: 'List',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
loading: {
|
||||
type: 'Spinner',
|
||||
},
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'List',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
$id: 'https://example.com/Container.json',
|
||||
@ -67,13 +73,15 @@ const blockMetas = {
|
||||
},
|
||||
Input: {
|
||||
category: 'input',
|
||||
scope: 'blocks',
|
||||
module: 'Input',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
valueType: 'string',
|
||||
loading: {
|
||||
type: 'SkeletonInput',
|
||||
},
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'Input',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
$id: 'https://example.com/Container.json',
|
||||
@ -81,12 +89,14 @@ const blockMetas = {
|
||||
},
|
||||
Display: {
|
||||
category: 'display',
|
||||
scope: 'blocks',
|
||||
module: 'Display',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
loading: {
|
||||
type: 'Spinner',
|
||||
},
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'Display',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
schema: {
|
||||
$schema: 'http://json-schema.org/draft-07/schema#',
|
||||
$id: 'https://example.com/Container.json',
|
||||
@ -97,27 +107,33 @@ const blockMetas = {
|
||||
const outputMetas = {
|
||||
Context: {
|
||||
category: 'context',
|
||||
scope: 'blocks',
|
||||
module: 'Context',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'Context',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
loading: {
|
||||
type: 'Spinner',
|
||||
},
|
||||
},
|
||||
Container: {
|
||||
category: 'container',
|
||||
scope: 'blocks',
|
||||
module: 'Container',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'Container',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
loading: {
|
||||
type: 'Spinner',
|
||||
},
|
||||
},
|
||||
List: {
|
||||
category: 'list',
|
||||
scope: 'blocks',
|
||||
module: 'List',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'List',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
loading: {
|
||||
type: 'Spinner',
|
||||
},
|
||||
@ -125,9 +141,11 @@ const outputMetas = {
|
||||
},
|
||||
Input: {
|
||||
category: 'input',
|
||||
scope: 'blocks',
|
||||
module: 'Input',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'Input',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
valueType: 'string',
|
||||
loading: {
|
||||
type: 'SkeletonInput',
|
||||
@ -135,9 +153,11 @@ const outputMetas = {
|
||||
},
|
||||
Display: {
|
||||
category: 'display',
|
||||
scope: 'blocks',
|
||||
module: 'Display',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
moduleFederation: {
|
||||
scope: 'blocks',
|
||||
module: 'Display',
|
||||
url: 'https://example.com/remoteEntry.js',
|
||||
},
|
||||
loading: {
|
||||
type: 'Spinner',
|
||||
},
|
||||
|
@ -19,9 +19,9 @@ import path from 'path';
|
||||
import Dataloader from 'dataloader';
|
||||
import getFile from '../utils/files/getFile';
|
||||
|
||||
function createFileBatchLoader({ baseDir }) {
|
||||
function createFileBatchLoader({ baseDirectory }) {
|
||||
async function loader(keys) {
|
||||
const filePaths = keys.map((key) => path.resolve(baseDir, key));
|
||||
const filePaths = keys.map((key) => path.resolve(baseDirectory, key));
|
||||
const fetched = [];
|
||||
const promises = filePaths.map(async (filePath) => {
|
||||
const item = await getFile(filePath);
|
||||
|
@ -17,29 +17,29 @@
|
||||
import path from 'path';
|
||||
import createFileLoader from './fileLoader';
|
||||
|
||||
const baseDir = path.resolve(process.cwd(), 'src/test/fileLoader');
|
||||
const baseDirectory = path.resolve(process.cwd(), 'src/test/fileLoader');
|
||||
|
||||
test('load file', async () => {
|
||||
const fileLoader = createFileLoader({ baseDir });
|
||||
const fileLoader = createFileLoader({ baseDirectory });
|
||||
const res = await fileLoader.load('fileLoader1.txt');
|
||||
expect(res).toEqual('File loader text file 1.');
|
||||
});
|
||||
|
||||
test('load file, file does not exist', async () => {
|
||||
const fileLoader = createFileLoader({ baseDir });
|
||||
const fileLoader = createFileLoader({ baseDirectory });
|
||||
const res = await fileLoader.load('doesNotExist.txt');
|
||||
expect(res).toEqual(null);
|
||||
});
|
||||
|
||||
test('load two files', async () => {
|
||||
const fileLoader = createFileLoader({ baseDir });
|
||||
const fileLoader = createFileLoader({ baseDirectory });
|
||||
const files = ['fileLoader1.txt', 'fileLoader2.txt'];
|
||||
const res = await Promise.all(files.map((file) => fileLoader.load(file)));
|
||||
expect(res).toEqual(['File loader text file 1.', 'File loader text file 2.']);
|
||||
});
|
||||
|
||||
test('load two files, one file errors', async () => {
|
||||
const fileLoader = createFileLoader({ baseDir });
|
||||
const fileLoader = createFileLoader({ baseDirectory });
|
||||
const files = ['fileLoader1.txt', 'doesNotExist.txt'];
|
||||
const res = await Promise.all(files.map((file) => fileLoader.load(file)));
|
||||
expect(res).toEqual(['File loader text file 1.', null]);
|
||||
|
@ -18,12 +18,12 @@ import path from 'path';
|
||||
import writeFile from '../utils/files/writeFile';
|
||||
|
||||
class FileSetter {
|
||||
constructor({ baseDir }) {
|
||||
this.baseDir = baseDir;
|
||||
constructor({ baseDirectory }) {
|
||||
this.baseDirectory = baseDirectory;
|
||||
}
|
||||
|
||||
async set({ filePath, content }) {
|
||||
return writeFile({ filePath: path.resolve(this.baseDir, filePath), content });
|
||||
return writeFile({ filePath: path.resolve(this.baseDirectory, filePath), content });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -18,17 +18,17 @@ import fs from 'fs';
|
||||
import path from 'path';
|
||||
import createFileSetter from './fileSetter';
|
||||
|
||||
const baseDir = path.resolve(process.cwd(), 'src/test/fileSetter');
|
||||
const baseDirectory = path.resolve(process.cwd(), 'src/test/fileSetter');
|
||||
|
||||
test('writeFile', async () => {
|
||||
const filePath = path.resolve(baseDir, 'writeFile.txt');
|
||||
const filePath = path.resolve(baseDirectory, 'writeFile.txt');
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
} catch (error) {
|
||||
//pass
|
||||
}
|
||||
expect(fs.existsSync(filePath)).toBe(false);
|
||||
const fileSetter = createFileSetter({ baseDir });
|
||||
const fileSetter = createFileSetter({ baseDirectory });
|
||||
await fileSetter.set({
|
||||
filePath: 'writeFile.txt',
|
||||
content: 'Test fileSetter file',
|
||||
|
48
packages/build/src/loaders/metaLoader.js
Normal file
48
packages/build/src/loaders/metaLoader.js
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2020 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 { get } from '@lowdefy/helpers';
|
||||
import Dataloader from 'dataloader';
|
||||
import createGetMeta from '../utils/meta/getMeta';
|
||||
|
||||
function createMetaBatchLoader({ components, context }) {
|
||||
const { cacheDirectory } = context;
|
||||
const { types } = components;
|
||||
const getMeta = createGetMeta({ cacheDirectory, types });
|
||||
async function loader(keys) {
|
||||
const fetched = [];
|
||||
const promises = keys.map(async (key) => {
|
||||
const item = await getMeta(key);
|
||||
fetched.push(item);
|
||||
});
|
||||
await Promise.all(promises);
|
||||
const returned = keys
|
||||
.map((key) =>
|
||||
fetched.find((item) => {
|
||||
return get(item, 'type') === key;
|
||||
})
|
||||
)
|
||||
.map((obj) => obj.meta);
|
||||
return returned;
|
||||
}
|
||||
return loader;
|
||||
}
|
||||
|
||||
function createMetaLoader(options) {
|
||||
return new Dataloader(createMetaBatchLoader(options));
|
||||
}
|
||||
|
||||
export default createMetaLoader;
|
85
packages/build/src/loaders/metaLoader.test.js
Normal file
85
packages/build/src/loaders/metaLoader.test.js
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright 2020 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 createGetMeta from '../utils/meta/getMeta';
|
||||
import createMetaLoader from './metaLoader';
|
||||
|
||||
jest.mock('../utils/meta/getMeta', () => {
|
||||
const mockGetMeta = jest.fn();
|
||||
return () => mockGetMeta;
|
||||
});
|
||||
|
||||
const mockGetMeta = createGetMeta();
|
||||
|
||||
mockGetMeta.mockImplementation((type) => {
|
||||
if (type === 'Type1') {
|
||||
return {
|
||||
type: 'Type1',
|
||||
meta: {
|
||||
key: 'value1',
|
||||
},
|
||||
};
|
||||
}
|
||||
if (type === 'Type2') {
|
||||
return {
|
||||
type: 'Type2',
|
||||
meta: {
|
||||
key: 'value2',
|
||||
},
|
||||
};
|
||||
}
|
||||
throw new Error('Not found.');
|
||||
});
|
||||
|
||||
const loaderConfig = {
|
||||
components: { types: {} },
|
||||
context: { cacheDirectory: 'cacheDirectory' },
|
||||
};
|
||||
|
||||
test('load meta', async () => {
|
||||
const metaLoader = createMetaLoader(loaderConfig);
|
||||
const res = await metaLoader.load('Type1');
|
||||
expect(res).toEqual({
|
||||
key: 'value1',
|
||||
});
|
||||
});
|
||||
|
||||
test('load meta, meta does not exist', async () => {
|
||||
const metaLoader = createMetaLoader(loaderConfig);
|
||||
await expect(metaLoader.load('DoesNotExist')).rejects.toThrow('Not found.');
|
||||
});
|
||||
|
||||
test('load two files', async () => {
|
||||
const metaLoader = createMetaLoader(loaderConfig);
|
||||
const types = ['Type1', 'Type2'];
|
||||
const res = await Promise.all(types.map((type) => metaLoader.load(type)));
|
||||
expect(res).toEqual([
|
||||
{
|
||||
key: 'value1',
|
||||
},
|
||||
{
|
||||
key: 'value2',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('load two meta, one does not exist', async () => {
|
||||
const metaLoader = createMetaLoader(loaderConfig);
|
||||
const types = ['Type1', 'DoesNotExist'];
|
||||
await expect(Promise.all(types.map((type) => metaLoader.load(type)))).rejects.toThrow(
|
||||
'Not found.'
|
||||
);
|
||||
});
|
39
packages/build/src/utils/meta/createCacheKey.js
Normal file
39
packages/build/src/utils/meta/createCacheKey.js
Normal file
@ -0,0 +1,39 @@
|
||||
/*
|
||||
Copyright 2020 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 { type } from '@lowdefy/helpers';
|
||||
|
||||
function createCacheKey(location) {
|
||||
if (type.isNone(location)) {
|
||||
throw new Error('Failed to create cache key, location is undefined.');
|
||||
}
|
||||
if (!type.isString(location.url)) {
|
||||
throw new Error('Location url definition should be a string.');
|
||||
}
|
||||
return (
|
||||
location.url
|
||||
// Replace all non alphanumeric characters (or _ - ) with a _
|
||||
.replace(/~/g, '_tilde_')
|
||||
.replace(/\^/g, '_caret_')
|
||||
.replace(/\*/g, '_star_')
|
||||
.replace(/[^a-zA-Z0-9-_]/g, '_')
|
||||
.toLowerCase()
|
||||
// Replace _json at end of the file with .json
|
||||
.replace(/_json$/, '.json')
|
||||
);
|
||||
}
|
||||
|
||||
export default createCacheKey;
|
77
packages/build/src/utils/meta/createCacheKey.test.js
Normal file
77
packages/build/src/utils/meta/createCacheKey.test.js
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
Copyright 2020 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 createCacheKey from './createCacheKey';
|
||||
|
||||
test('createCacheKey converts a url to a file safe string', () => {
|
||||
const location = {
|
||||
url: 'https://unpkg.com/@lowdefy/blocks-antd@1.0.0/dist/Button.json',
|
||||
};
|
||||
expect(createCacheKey(location)).toEqual(
|
||||
'https___unpkg_com__lowdefy_blocks-antd_1_0_0_dist_button.json'
|
||||
);
|
||||
});
|
||||
|
||||
test('createCacheKey replaces semantic versioning special characters', () => {
|
||||
const location = {
|
||||
url: '^~*',
|
||||
};
|
||||
expect(createCacheKey(location)).toEqual('_caret__tilde__star_');
|
||||
});
|
||||
|
||||
test('createCacheKey semantic versioning special characters do not clash', () => {
|
||||
const locationCaret = {
|
||||
url: 'https://unpkg.com/@lowdefy/blocks-antd@^1.0.0/dist/button.json',
|
||||
};
|
||||
const locationTilde = {
|
||||
url: 'https://unpkg.com/@lowdefy/blocks-antd@~1.0.0/dist/button.json',
|
||||
};
|
||||
expect(createCacheKey(locationCaret)).toEqual(
|
||||
'https___unpkg_com__lowdefy_blocks-antd__caret_1_0_0_dist_button.json'
|
||||
);
|
||||
expect(createCacheKey(locationTilde)).toEqual(
|
||||
'https___unpkg_com__lowdefy_blocks-antd__tilde_1_0_0_dist_button.json'
|
||||
);
|
||||
});
|
||||
|
||||
test('createCacheKey converts to lowercase', () => {
|
||||
const location = {
|
||||
url: 'UPPERCASE',
|
||||
};
|
||||
expect(createCacheKey(location)).toEqual('uppercase');
|
||||
});
|
||||
|
||||
test('createCacheKey converts all non alphanumerics', () => {
|
||||
const location = {
|
||||
url: `!@#$%&()+=±§[]{};:"'\\|\`,.<>/?`,
|
||||
};
|
||||
expect(createCacheKey(location)).toEqual('_____________________________');
|
||||
});
|
||||
|
||||
test('createCacheKey only replaces _json at then end of the string', () => {
|
||||
const location = {
|
||||
url: 'ABC$jsonDEF.jsonHIJ.json',
|
||||
};
|
||||
expect(createCacheKey(location)).toEqual('abc_jsondef_jsonhij.json');
|
||||
});
|
||||
|
||||
test('createCacheKey throws if location is undefined', () => {
|
||||
expect(() => createCacheKey()).toThrow('Failed to create cache key, location is undefined.');
|
||||
});
|
||||
|
||||
test('createCacheKey throws if location url is not a string', () => {
|
||||
expect(() => createCacheKey({ url: 1 })).toThrow('Location url definition should be a string.');
|
||||
});
|
20
packages/build/src/utils/meta/defaultMetaLocations.js
Normal file
20
packages/build/src/utils/meta/defaultMetaLocations.js
Normal file
@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2020 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.
|
||||
*/
|
||||
|
||||
const defaultMetaLocations = {
|
||||
};
|
||||
|
||||
export default defaultMetaLocations;
|
30
packages/build/src/utils/meta/fetchMetaCache.js
Normal file
30
packages/build/src/utils/meta/fetchMetaCache.js
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
Copyright 2020 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 createCacheKey from './createCacheKey';
|
||||
import readFile from '../files/readFile';
|
||||
|
||||
function createFetchMetaCache({ cacheDirectory }) {
|
||||
async function fetchMetaCache(location) {
|
||||
const cacheKey = createCacheKey(location);
|
||||
const fileContent = await readFile(path.resolve(cacheDirectory, cacheKey));
|
||||
return JSON.parse(fileContent);
|
||||
}
|
||||
return fetchMetaCache;
|
||||
}
|
||||
|
||||
export default createFetchMetaCache;
|
43
packages/build/src/utils/meta/fetchMetaCache.test.js
Normal file
43
packages/build/src/utils/meta/fetchMetaCache.test.js
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
Copyright 2020 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 createFetchMetaCache from './fetchMetaCache';
|
||||
|
||||
const cacheDirectory = path.resolve(process.cwd(), 'src/test/fetchMetaCache');
|
||||
const fetchMetaCache = createFetchMetaCache({ cacheDirectory });
|
||||
|
||||
test('fetchMetaCache fetches from cache', async () => {
|
||||
const meta = await fetchMetaCache({ url: 'cachekey.json' });
|
||||
expect(meta).toEqual({
|
||||
moduleFederation: {
|
||||
remoteEntryUrl:
|
||||
'https://unpkg.com/@lowdefy/blocks-antd@1.0.0-experimental.1/dist/remoteEntry.js',
|
||||
scope: 'lowdefy_blocks_antd',
|
||||
module: 'Button',
|
||||
},
|
||||
category: 'display',
|
||||
loading: {
|
||||
type: 'SkeletonButton',
|
||||
},
|
||||
schema: {},
|
||||
});
|
||||
});
|
||||
|
||||
test('fetchMetaCache returns null if not found', async () => {
|
||||
const meta = await fetchMetaCache({ url: 'doesNotExist.json' });
|
||||
expect(meta).toEqual(null);
|
||||
});
|
31
packages/build/src/utils/meta/fetchMetaUrl.js
Normal file
31
packages/build/src/utils/meta/fetchMetaUrl.js
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2020 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 axios from 'axios';
|
||||
import { type } from '@lowdefy/helpers';
|
||||
|
||||
async function fetchMetaUrl(location) {
|
||||
if (type.isNone(location)) {
|
||||
throw new Error('Failed to fetch meta, location is undefined.');
|
||||
}
|
||||
if (!type.isString(location.url)) {
|
||||
throw new Error('Location url definition should be a string.');
|
||||
}
|
||||
const res = await axios.get(location.url);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export default fetchMetaUrl;
|
55
packages/build/src/utils/meta/fetchMetaUrl.test.js
Normal file
55
packages/build/src/utils/meta/fetchMetaUrl.test.js
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
Copyright 2020 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-next-line no-unused-vars
|
||||
import axios from 'axios';
|
||||
import fetchMetaUrl from './fetchMetaUrl';
|
||||
|
||||
jest.mock('axios', () => {
|
||||
return {
|
||||
get: (url) => {
|
||||
if (url === 'valid-url') {
|
||||
return Promise.resolve({ data: { key: 'value' } });
|
||||
}
|
||||
throw new Error('Invalid url');
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
test('fetchMetaUrl fetches from url', async () => {
|
||||
const meta = await fetchMetaUrl({
|
||||
url: 'valid-url',
|
||||
});
|
||||
expect(meta).toEqual({ key: 'value' });
|
||||
});
|
||||
|
||||
test('fetchMetaUrl request errors', async () => {
|
||||
await expect(
|
||||
fetchMetaUrl({
|
||||
url: 'invalid-url',
|
||||
})
|
||||
).rejects.toThrow('Invalid url');
|
||||
});
|
||||
|
||||
test('fetchMetaUrl throws if location is undefined', async () => {
|
||||
await expect(fetchMetaUrl()).rejects.toThrow('Failed to fetch meta, location is undefined.');
|
||||
});
|
||||
|
||||
test('fetchMetaUrl throws if location is undefined', async () => {
|
||||
await expect(fetchMetaUrl({ url: 1 })).rejects.toThrow(
|
||||
'Location url definition should be a string.'
|
||||
);
|
||||
});
|
71
packages/build/src/utils/meta/getMeta.js
Normal file
71
packages/build/src/utils/meta/getMeta.js
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
Steps to fetch meta
|
||||
- check in cache
|
||||
- in cache
|
||||
- return
|
||||
- not in cache
|
||||
- fetch url
|
||||
- write cache
|
||||
- return
|
||||
*/
|
||||
|
||||
import createFetchMetaCache from './fetchMetaCache';
|
||||
import createWriteMetaCache from './writeMetaCache';
|
||||
import defaultMetaLocations from './defaultMetaLocations';
|
||||
import fetchMetaUrl from './fetchMetaUrl';
|
||||
|
||||
function createGetMeta({ types, cacheDirectory }) {
|
||||
const metaLocations = {
|
||||
...defaultMetaLocations,
|
||||
...types,
|
||||
};
|
||||
const fetchMetaCache = createFetchMetaCache({ cacheDirectory });
|
||||
const writeMetaCache = createWriteMetaCache({ cacheDirectory });
|
||||
async function getMeta(type) {
|
||||
if (!metaLocations[type]) {
|
||||
throw new Error(
|
||||
`Type ${JSON.stringify(type)} is not defined. Specify type url in types array.`
|
||||
);
|
||||
}
|
||||
let meta;
|
||||
const location = metaLocations[type];
|
||||
meta = await fetchMetaCache(location);
|
||||
|
||||
if (meta)
|
||||
return {
|
||||
type,
|
||||
meta,
|
||||
};
|
||||
meta = await fetchMetaUrl(location);
|
||||
if (meta) {
|
||||
await writeMetaCache({ location, meta });
|
||||
return {
|
||||
type,
|
||||
meta,
|
||||
};
|
||||
}
|
||||
throw new Error(
|
||||
`Meta for type ${type} could not be found at location ${JSON.stringify(location)}.`
|
||||
);
|
||||
}
|
||||
|
||||
return getMeta;
|
||||
}
|
||||
|
||||
export default createGetMeta;
|
120
packages/build/src/utils/meta/getMeta.test.js
Normal file
120
packages/build/src/utils/meta/getMeta.test.js
Normal file
@ -0,0 +1,120 @@
|
||||
/*
|
||||
Copyright 2020 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 createGetMeta from './getMeta';
|
||||
import createFetchMetaCache from './fetchMetaCache';
|
||||
import createWriteMetaCache from './writeMetaCache';
|
||||
import fetchMetaUrl from './fetchMetaUrl';
|
||||
|
||||
jest.mock('./fetchMetaCache', () => {
|
||||
const mockFetchMetaCache = jest.fn();
|
||||
return () => mockFetchMetaCache;
|
||||
});
|
||||
jest.mock('./writeMetaCache', () => {
|
||||
const mockWriteMetaCache = jest.fn();
|
||||
return () => mockWriteMetaCache;
|
||||
});
|
||||
jest.mock('./fetchMetaUrl', () => {
|
||||
const mockFetchMetaUrl = jest.fn();
|
||||
return mockFetchMetaUrl;
|
||||
});
|
||||
|
||||
const mockFetchMetaCache = createFetchMetaCache();
|
||||
const mockWriteMetaCache = createWriteMetaCache();
|
||||
const mockFetchMetaUrl = fetchMetaUrl;
|
||||
|
||||
const types = {
|
||||
Type1: {
|
||||
url: 'type1Url',
|
||||
},
|
||||
Type2: {
|
||||
url: 'type2Url',
|
||||
},
|
||||
};
|
||||
|
||||
const getMeta = createGetMeta({ types, cacheDirectory: 'cacheDirectory' });
|
||||
|
||||
beforeEach(() => {
|
||||
mockFetchMetaCache.mockReset();
|
||||
mockWriteMetaCache.mockReset();
|
||||
mockFetchMetaUrl.mockReset();
|
||||
});
|
||||
|
||||
test('getMeta cache returns from cache', async () => {
|
||||
mockFetchMetaCache.mockImplementation((location) => {
|
||||
if (location && location.url === 'type1Url') {
|
||||
return {
|
||||
key: 'value',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const res = await getMeta('Type1');
|
||||
expect(res).toEqual({
|
||||
type: 'Type1',
|
||||
meta: {
|
||||
key: 'value',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('getMeta fetches from url and writes to cache', async () => {
|
||||
mockFetchMetaUrl.mockImplementation((location) => {
|
||||
if (location && location.url === 'type1Url') {
|
||||
return {
|
||||
key: 'value',
|
||||
};
|
||||
}
|
||||
return null;
|
||||
});
|
||||
const res = await getMeta('Type1');
|
||||
expect(res).toEqual({
|
||||
type: 'Type1',
|
||||
meta: {
|
||||
key: 'value',
|
||||
},
|
||||
});
|
||||
expect(mockWriteMetaCache.mock.calls).toEqual([
|
||||
[
|
||||
{
|
||||
location: {
|
||||
url: 'type1Url',
|
||||
},
|
||||
meta: {
|
||||
key: 'value',
|
||||
},
|
||||
},
|
||||
],
|
||||
]);
|
||||
});
|
||||
|
||||
test('getMeta type not in types', async () => {
|
||||
await expect(getMeta('Undefined')).rejects.toThrow(
|
||||
'Type "Undefined" is not defined. Specify type url in types array.'
|
||||
);
|
||||
});
|
||||
|
||||
test('getMeta undefined type', async () => {
|
||||
await expect(getMeta()).rejects.toThrow(
|
||||
'Type undefined is not defined. Specify type url in types array.'
|
||||
);
|
||||
});
|
||||
|
||||
test('getMeta meta not found in cache or url', async () => {
|
||||
await expect(getMeta('Type2')).rejects.toThrow(
|
||||
'Meta for type Type2 could not be found at location {"url":"type2Url"}.'
|
||||
);
|
||||
});
|
32
packages/build/src/utils/meta/writeMetaCache.js
Normal file
32
packages/build/src/utils/meta/writeMetaCache.js
Normal file
@ -0,0 +1,32 @@
|
||||
/*
|
||||
Copyright 2020 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 createCacheKey from './createCacheKey';
|
||||
import writeFile from '../files/writeFile';
|
||||
|
||||
function createWriteMetaCache({ cacheDirectory }) {
|
||||
async function writeMetaCache({ location, meta }) {
|
||||
const cacheKey = createCacheKey(location);
|
||||
return writeFile({
|
||||
filePath: path.resolve(cacheDirectory, cacheKey),
|
||||
content: JSON.stringify(meta, null, 2),
|
||||
});
|
||||
}
|
||||
return writeMetaCache;
|
||||
}
|
||||
|
||||
export default createWriteMetaCache;
|
47
packages/build/src/utils/meta/writeMetaCache.test.js
Normal file
47
packages/build/src/utils/meta/writeMetaCache.test.js
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2020 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 createWriteMetaCache from './writeMetaCache';
|
||||
|
||||
const cacheDirectory = path.resolve(process.cwd(), 'src/test/fetchMetaCache');
|
||||
const writeMetaCache = createWriteMetaCache({ cacheDirectory });
|
||||
|
||||
test('writeMetaCache writes to cache', async () => {
|
||||
const filePath = path.resolve(cacheDirectory, 'writemetacache.json');
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
} catch (error) {
|
||||
//pass
|
||||
}
|
||||
expect(fs.existsSync(filePath)).toBe(false);
|
||||
await writeMetaCache({
|
||||
location: { url: 'writemetacache.json' },
|
||||
meta: {
|
||||
key: 'value',
|
||||
},
|
||||
});
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
expect(content).toEqual(`{
|
||||
"key": "value"
|
||||
}`);
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
} catch (error) {
|
||||
//pass
|
||||
}
|
||||
});
|
12
yarn.lock
12
yarn.lock
@ -3734,6 +3734,7 @@ __metadata:
|
||||
"@lowdefy/helpers": 1.1.0
|
||||
"@lowdefy/nunjucks": 1.0.0
|
||||
ajv: 6.12.6
|
||||
axios: 0.20.0
|
||||
babel-jest: 26.5.2
|
||||
babel-loader: 8.1.0
|
||||
dataloader: 2.0.0
|
||||
@ -6241,6 +6242,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:0.20.0":
|
||||
version: 0.20.0
|
||||
resolution: "axios@npm:0.20.0"
|
||||
dependencies:
|
||||
follow-redirects: ^1.10.0
|
||||
checksum: 6cf2e963176a0d18fc457d7cb71cb6cf552193619b161fc61070d9a4db3a26a87ecfe760ba36e7af174c05a6a9898dc52cfeb5c0e62e19fddd9788739553ad73
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"axios@npm:^0.18.0":
|
||||
version: 0.18.1
|
||||
resolution: "axios@npm:0.18.1"
|
||||
@ -9835,7 +9845,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"follow-redirects@npm:^1.0.0":
|
||||
"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.10.0":
|
||||
version: 1.13.0
|
||||
resolution: "follow-redirects@npm:1.13.0"
|
||||
checksum: f220828d3f153da30ea616fdbe9f6676e74e4e68c51d336a751037c1d556e2de34aa5918f58951fa19bb6517c9c88b4403a0a8bdfc40d3e779d37def2ac0f2c4
|
||||
|
Loading…
x
Reference in New Issue
Block a user