@superblocksteam/util 0.0.14 → 0.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +51 -0
- package/dist/component-configs.d.ts +4 -1
- package/dist/component-configs.js +51 -13
- package/dist/generate-component-types.d.ts +1 -0
- package/dist/generate-component-types.js +47 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/input-types.d.ts +9 -0
- package/dist/input-types.js +26 -0
- package/dist/resource-configs.d.ts +2 -1
- package/dist/resource-configs.js +6 -2
- package/dist/validation.d.ts +4 -0
- package/dist/validation.js +73 -0
- package/package.json +14 -3
- package/src/component-configs.ts +62 -14
- package/src/generate-component-types.ts +56 -0
- package/src/index.ts +1 -0
- package/src/input-types.ts +34 -0
- package/src/resource-configs.ts +9 -3
- package/src/validation.ts +75 -0
package/.eslintrc.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"parser": "@typescript-eslint/parser",
|
|
3
|
+
"plugins": [
|
|
4
|
+
"@typescript-eslint",
|
|
5
|
+
"prettier",
|
|
6
|
+
"import",
|
|
7
|
+
"unicorn"
|
|
8
|
+
],
|
|
9
|
+
"extends": [
|
|
10
|
+
"eslint:recommended",
|
|
11
|
+
"plugin:@typescript-eslint/recommended",
|
|
12
|
+
"plugin:prettier/recommended",
|
|
13
|
+
"prettier",
|
|
14
|
+
"plugin:import/typescript"
|
|
15
|
+
],
|
|
16
|
+
"parserOptions": {
|
|
17
|
+
"ecmaVersion": 2020,
|
|
18
|
+
"sourceType": "module",
|
|
19
|
+
"project": true
|
|
20
|
+
},
|
|
21
|
+
"rules": {
|
|
22
|
+
"@typescript-eslint/explicit-module-boundary-types": "off",
|
|
23
|
+
"@typescript-eslint/no-explicit-any": "off",
|
|
24
|
+
"@typescript-eslint/no-unused-vars": "error",
|
|
25
|
+
"@typescript-eslint/no-var-requires": "off",
|
|
26
|
+
"@typescript-eslint/no-unsafe-return": "off",
|
|
27
|
+
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
28
|
+
"@typescript-eslint/no-unsafe-member-access": "off",
|
|
29
|
+
"import/order": [
|
|
30
|
+
"error",
|
|
31
|
+
{
|
|
32
|
+
"alphabetize": { "order": "asc" },
|
|
33
|
+
"groups": [
|
|
34
|
+
"builtin",
|
|
35
|
+
"external",
|
|
36
|
+
"internal",
|
|
37
|
+
"parent",
|
|
38
|
+
"sibling",
|
|
39
|
+
"index",
|
|
40
|
+
"object",
|
|
41
|
+
"type"
|
|
42
|
+
]
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"import/no-cycle": "error",
|
|
46
|
+
"no-template-curly-in-string": "off",
|
|
47
|
+
"no-useless-escape": "off",
|
|
48
|
+
"no-void": ["error", { "allowAsStatement": true }]
|
|
49
|
+
},
|
|
50
|
+
"ignorePatterns": ["public", "build", "node_modules", "dist", "tsup.config.ts"]
|
|
51
|
+
}
|
|
@@ -2,17 +2,37 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getComponentConfigs = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
|
-
const
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
6
|
const node_os_1 = tslib_1.__importDefault(require("node:os"));
|
|
7
|
+
const node_path_1 = tslib_1.__importDefault(require("node:path"));
|
|
7
8
|
const fs = tslib_1.__importStar(require("fs-extra"));
|
|
8
9
|
const constants_1 = require("./constants");
|
|
9
|
-
const
|
|
10
|
+
const generate_component_types_1 = require("./generate-component-types");
|
|
11
|
+
const validation_1 = require("./validation");
|
|
10
12
|
const compileTypeScript = (inputFilePath, outputDir) => {
|
|
11
13
|
return new Promise((resolve, reject) => {
|
|
12
14
|
try {
|
|
13
15
|
const tscPath = require.resolve("typescript/bin/tsc");
|
|
14
16
|
if (tscPath) {
|
|
15
|
-
(0, child_process_1.execFile)(tscPath, [
|
|
17
|
+
(0, child_process_1.execFile)(tscPath, [
|
|
18
|
+
node_path_1.default.resolve(inputFilePath),
|
|
19
|
+
"--outDir",
|
|
20
|
+
outputDir,
|
|
21
|
+
"--jsx",
|
|
22
|
+
"react-jsx",
|
|
23
|
+
"--target",
|
|
24
|
+
"es2019",
|
|
25
|
+
"--esModuleInterop",
|
|
26
|
+
"true",
|
|
27
|
+
"--moduleResolution",
|
|
28
|
+
"node",
|
|
29
|
+
"--module",
|
|
30
|
+
"commonjs",
|
|
31
|
+
"--strict",
|
|
32
|
+
"true",
|
|
33
|
+
"--skipLibCheck",
|
|
34
|
+
"true",
|
|
35
|
+
], (error, stdout, stderr) => {
|
|
16
36
|
if (error) {
|
|
17
37
|
// Print any Typescript output to the user, such as errors
|
|
18
38
|
console.log(stdout);
|
|
@@ -47,13 +67,14 @@ async function getFolderPaths() {
|
|
|
47
67
|
throw new Error("Could not access component directory, check your permissions");
|
|
48
68
|
}
|
|
49
69
|
}
|
|
50
|
-
async function getComponentConfigs() {
|
|
70
|
+
async function getComponentConfigs(generateTypesFiles) {
|
|
51
71
|
const folderPaths = await getFolderPaths();
|
|
72
|
+
let hasError = false;
|
|
52
73
|
const promiseConfigFiles = folderPaths.map(async (ccpath) => {
|
|
53
74
|
try {
|
|
54
|
-
const
|
|
75
|
+
const config = await fs.readJSON(`${ccpath}/config.json`);
|
|
55
76
|
console.log(`Found component in ${ccpath}`);
|
|
56
|
-
return
|
|
77
|
+
return { ccpath, config };
|
|
57
78
|
}
|
|
58
79
|
catch (e) {
|
|
59
80
|
// Noop because there is a fallback
|
|
@@ -71,9 +92,15 @@ async function getComponentConfigs() {
|
|
|
71
92
|
.slice(2)}`);
|
|
72
93
|
await compileTypeScript(fileName, outputDirectory);
|
|
73
94
|
console.log(`Typescript compiled to ${ccpath}`);
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
95
|
+
const config = (await Promise.resolve(`${outputDirectory + "/config.js"}`).then(s => tslib_1.__importStar(require(s)))).default;
|
|
96
|
+
const isValid = (0, validation_1.validateCustomComponents)(config);
|
|
97
|
+
if (!isValid.valid) {
|
|
98
|
+
// Not throwing because we don't need a stack trace here
|
|
99
|
+
console.log(`Invalid config.ts file found`);
|
|
100
|
+
console.log(isValid.message);
|
|
101
|
+
hasError = true;
|
|
102
|
+
}
|
|
103
|
+
return { ccpath, config };
|
|
77
104
|
}
|
|
78
105
|
catch (e) {
|
|
79
106
|
console.error(e);
|
|
@@ -81,12 +108,23 @@ async function getComponentConfigs() {
|
|
|
81
108
|
throw e;
|
|
82
109
|
}
|
|
83
110
|
});
|
|
84
|
-
const configFiles =
|
|
85
|
-
|
|
86
|
-
|
|
111
|
+
const configFiles = await Promise.all(promiseConfigFiles);
|
|
112
|
+
if (generateTypesFiles) {
|
|
113
|
+
for (const { config, ccpath } of configFiles) {
|
|
114
|
+
const typesFileContents = (0, generate_component_types_1.generateComponentTypesFile)(config);
|
|
115
|
+
await fs.writeFile(`${ccpath}/types.ts`, typesFileContents);
|
|
116
|
+
console.log(`Generated ${ccpath}/types.ts`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const configs = configFiles
|
|
120
|
+
.filter((component) => component.config !== null)
|
|
121
|
+
.reduce((acc, { config }) => {
|
|
87
122
|
acc[config.id] = config;
|
|
88
123
|
return acc;
|
|
89
124
|
}, {});
|
|
90
|
-
return
|
|
125
|
+
return {
|
|
126
|
+
configs,
|
|
127
|
+
hasError,
|
|
128
|
+
};
|
|
91
129
|
}
|
|
92
130
|
exports.getComponentConfigs = getComponentConfigs;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateComponentTypesFile(config: any): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateComponentTypesFile = void 0;
|
|
4
|
+
const input_types_1 = require("./input-types");
|
|
5
|
+
const indent = (str, spaces) => {
|
|
6
|
+
const spacesString = " ".repeat(spaces);
|
|
7
|
+
return str
|
|
8
|
+
.split("\n")
|
|
9
|
+
.map((line) => `${spacesString}${line}`)
|
|
10
|
+
.join("\n");
|
|
11
|
+
};
|
|
12
|
+
function renderInputTypeAsTS(name, type, trailingChars, indentSpaces) {
|
|
13
|
+
return indent(`${name}: ${input_types_1.inputTypeDefinions[type].tsType}${trailingChars}`, indentSpaces);
|
|
14
|
+
}
|
|
15
|
+
function generateComponentTypesFile(config) {
|
|
16
|
+
var _a, _b;
|
|
17
|
+
const statefulProperties = (_a = config.statefulProperties) !== null && _a !== void 0 ? _a : [];
|
|
18
|
+
const eventHandlers = (_b = config.eventHandlers) !== null && _b !== void 0 ? _b : [];
|
|
19
|
+
return `// WARNING: This file is automatically generated and managed by Superblocks CLI and should not be edited manually.
|
|
20
|
+
// Use the \`superblocks components watch\` command to regenerate this file from its source in config.ts
|
|
21
|
+
|
|
22
|
+
// All stateful properties of your component are defined here.
|
|
23
|
+
// These are the properties which are surfaced in the Superblocks properties panel
|
|
24
|
+
// and can be referenced throughout your Superblocks Application
|
|
25
|
+
export interface StatefulProperties {
|
|
26
|
+
${statefulProperties
|
|
27
|
+
.map((v) => renderInputTypeAsTS(v.path, v.inputType, ";", 2) + "\n")
|
|
28
|
+
.join("")}}
|
|
29
|
+
|
|
30
|
+
export interface Props extends StatefulProperties {
|
|
31
|
+
/**
|
|
32
|
+
* Update one (or more) of your component's stateful properties. This will cause the component to rerender,
|
|
33
|
+
* and will also notify any other components in your Application which depend on the updated properties, so they also rerender
|
|
34
|
+
* @param props An object with the properties to update along with their new values
|
|
35
|
+
*/
|
|
36
|
+
updateStatefulProperties: (props: Partial<StatefulProperties>) => void;
|
|
37
|
+
|
|
38
|
+
// Call the subsequent function(s) to trigger event(s) in Superblocks from your component.
|
|
39
|
+
// These events can be wired up to event handlers in your Superblocks App
|
|
40
|
+
${eventHandlers.length === 0
|
|
41
|
+
? " // Note: no event handlers defined\n"
|
|
42
|
+
: eventHandlers
|
|
43
|
+
.map((v) => indent(v.path, 2) + ": () => void;" + "\n")
|
|
44
|
+
.join("")}}
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
exports.generateComponentTypesFile = generateComponentTypesFile;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -5,3 +5,4 @@ tslib_1.__exportStar(require("./constants"), exports);
|
|
|
5
5
|
tslib_1.__exportStar(require("./component-configs"), exports);
|
|
6
6
|
tslib_1.__exportStar(require("./login"), exports);
|
|
7
7
|
tslib_1.__exportStar(require("./resource-configs"), exports);
|
|
8
|
+
tslib_1.__exportStar(require("./input-types"), exports);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const supportedInputTypes: readonly ["text", "number", "boolean", "js"];
|
|
2
|
+
export type InputType = (typeof supportedInputTypes)[number];
|
|
3
|
+
interface TypeInfo {
|
|
4
|
+
tsType: string;
|
|
5
|
+
prompt: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const inputTypeDefinions: Record<InputType, TypeInfo>;
|
|
8
|
+
export declare function isValidInputType(input: string): input is InputType;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isValidInputType = exports.inputTypeDefinions = exports.supportedInputTypes = void 0;
|
|
4
|
+
exports.supportedInputTypes = ["text", "number", "boolean", "js"];
|
|
5
|
+
exports.inputTypeDefinions = {
|
|
6
|
+
text: {
|
|
7
|
+
tsType: "string",
|
|
8
|
+
prompt: "text",
|
|
9
|
+
},
|
|
10
|
+
number: {
|
|
11
|
+
tsType: "number",
|
|
12
|
+
prompt: "number",
|
|
13
|
+
},
|
|
14
|
+
boolean: {
|
|
15
|
+
tsType: "boolean",
|
|
16
|
+
prompt: "boolean",
|
|
17
|
+
},
|
|
18
|
+
js: {
|
|
19
|
+
tsType: "any",
|
|
20
|
+
prompt: "any (raw js expression)",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
function isValidInputType(input) {
|
|
24
|
+
return Object.prototype.hasOwnProperty.call(exports.inputTypeDefinions, input);
|
|
25
|
+
}
|
|
26
|
+
exports.isValidInputType = isValidInputType;
|
|
@@ -4,10 +4,11 @@ export type SuperblocksMetadata = {
|
|
|
4
4
|
lastUpdated: string;
|
|
5
5
|
created: string;
|
|
6
6
|
};
|
|
7
|
+
export type SuperblocksResourceType = "APPLICATION" | "BACKEND";
|
|
7
8
|
export type VersionedResourceConfig = {
|
|
8
9
|
location: string;
|
|
9
10
|
lastUpdated: string;
|
|
10
|
-
resourceType:
|
|
11
|
+
resourceType: SuperblocksResourceType;
|
|
11
12
|
};
|
|
12
13
|
export type SuperblocksResourceConfig = {
|
|
13
14
|
id: string;
|
package/dist/resource-configs.js
CHANGED
|
@@ -70,11 +70,15 @@ async function getSuperblocksResourceConfigIfExists() {
|
|
|
70
70
|
try {
|
|
71
71
|
config = await getSuperblocksApplicationConfigJson();
|
|
72
72
|
}
|
|
73
|
-
catch {
|
|
73
|
+
catch {
|
|
74
|
+
// Noop
|
|
75
|
+
}
|
|
74
76
|
try {
|
|
75
77
|
config = await getSuperblocksBackendConfigJson();
|
|
76
78
|
}
|
|
77
|
-
catch {
|
|
79
|
+
catch {
|
|
80
|
+
// Noop
|
|
81
|
+
}
|
|
78
82
|
return config;
|
|
79
83
|
}
|
|
80
84
|
exports.getSuperblocksResourceConfigIfExists = getSuperblocksResourceConfigIfExists;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateCustomComponents = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const ajv_1 = tslib_1.__importDefault(require("ajv"));
|
|
6
|
+
const better_ajv_errors_1 = tslib_1.__importDefault(require("better-ajv-errors"));
|
|
7
|
+
const ajv = new ajv_1.default();
|
|
8
|
+
const componentSchema = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
id: { type: "string" },
|
|
12
|
+
name: { type: "string" },
|
|
13
|
+
displayName: { type: "string" },
|
|
14
|
+
componentPath: { type: "string" },
|
|
15
|
+
statefulProperties: {
|
|
16
|
+
type: "array",
|
|
17
|
+
items: {
|
|
18
|
+
$ref: "#/definitions/StatefulProperty",
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
eventHandlers: {
|
|
22
|
+
type: "array",
|
|
23
|
+
items: {
|
|
24
|
+
$ref: "#/definitions/EventHandler",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
required: [
|
|
29
|
+
"id",
|
|
30
|
+
"name",
|
|
31
|
+
"displayName",
|
|
32
|
+
"componentPath",
|
|
33
|
+
"statefulProperties",
|
|
34
|
+
"eventHandlers",
|
|
35
|
+
],
|
|
36
|
+
additionalProperties: false,
|
|
37
|
+
definitions: {
|
|
38
|
+
StatefulProperty: {
|
|
39
|
+
properties: {
|
|
40
|
+
label: { type: "string" },
|
|
41
|
+
path: { type: "string" },
|
|
42
|
+
inputType: { enum: ["text", "number", "boolean", "js"] },
|
|
43
|
+
placeholder: { type: "string" },
|
|
44
|
+
},
|
|
45
|
+
type: "object",
|
|
46
|
+
additionalProperties: false,
|
|
47
|
+
required: ["label", "path", "inputType"],
|
|
48
|
+
},
|
|
49
|
+
EventHandler: {
|
|
50
|
+
properties: {
|
|
51
|
+
label: { type: "string" },
|
|
52
|
+
path: { type: "string" },
|
|
53
|
+
},
|
|
54
|
+
type: "object",
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
required: ["label", "path"],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const validate = ajv.compile(componentSchema);
|
|
61
|
+
function validateCustomComponents(data) {
|
|
62
|
+
const valid = validate(data);
|
|
63
|
+
if (!valid) {
|
|
64
|
+
return {
|
|
65
|
+
valid: false,
|
|
66
|
+
message: (0, better_ajv_errors_1.default)(componentSchema, data, validate.errors, {
|
|
67
|
+
indent: 2,
|
|
68
|
+
}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return { valid: true };
|
|
72
|
+
}
|
|
73
|
+
exports.validateCustomComponents = validateCustomComponents;
|
package/package.json
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superblocksteam/util",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"homepage": "https://www.superblocks.com",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "tsc --watch",
|
|
8
8
|
"build": "tsc --build",
|
|
9
9
|
"clean": "npm run clean:build && rm -rf node_modules",
|
|
10
|
-
"clean:build": "tsc --build --clean && rm -rf dist tsconfig.tsbuildinfo"
|
|
10
|
+
"clean:build": "tsc --build --clean && rm -rf dist tsconfig.tsbuildinfo",
|
|
11
|
+
"lint": "eslint . --ext .ts --config .eslintrc.json",
|
|
12
|
+
"lint:fix": "eslint . --ext .ts --config .eslintrc.json --fix"
|
|
11
13
|
},
|
|
12
14
|
"license": "Superblocks Community Software License",
|
|
13
15
|
"description": "",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"ajv": "^8.12.0",
|
|
18
|
+
"better-ajv-errors": "^1.2.0"
|
|
19
|
+
},
|
|
14
20
|
"devDependencies": {
|
|
15
|
-
"typescript": "^5.
|
|
21
|
+
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
|
22
|
+
"@typescript-eslint/parser": "^5.60.1",
|
|
23
|
+
"eslint": "^8.45.0",
|
|
24
|
+
"eslint-plugin-unicorn": "^47.0.0",
|
|
25
|
+
"prettier": "^2.8.8",
|
|
26
|
+
"typescript": "^5.1.3"
|
|
16
27
|
}
|
|
17
28
|
}
|
package/src/component-configs.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
import * as fs from "fs-extra";
|
|
4
5
|
import { CUSTOM_COMPONENTS_PATH } from "./constants";
|
|
5
|
-
import {
|
|
6
|
+
import { generateComponentTypesFile } from "./generate-component-types";
|
|
7
|
+
import { validateCustomComponents } from "./validation";
|
|
6
8
|
|
|
7
9
|
const compileTypeScript = (inputFilePath: string, outputDir: string) => {
|
|
8
10
|
return new Promise((resolve, reject) => {
|
|
@@ -11,7 +13,25 @@ const compileTypeScript = (inputFilePath: string, outputDir: string) => {
|
|
|
11
13
|
if (tscPath) {
|
|
12
14
|
execFile(
|
|
13
15
|
tscPath,
|
|
14
|
-
[
|
|
16
|
+
[
|
|
17
|
+
path.resolve(inputFilePath),
|
|
18
|
+
"--outDir",
|
|
19
|
+
outputDir,
|
|
20
|
+
"--jsx",
|
|
21
|
+
"react-jsx",
|
|
22
|
+
"--target",
|
|
23
|
+
"es2019",
|
|
24
|
+
"--esModuleInterop",
|
|
25
|
+
"true",
|
|
26
|
+
"--moduleResolution",
|
|
27
|
+
"node",
|
|
28
|
+
"--module",
|
|
29
|
+
"commonjs",
|
|
30
|
+
"--strict",
|
|
31
|
+
"true",
|
|
32
|
+
"--skipLibCheck",
|
|
33
|
+
"true",
|
|
34
|
+
],
|
|
15
35
|
(error, stdout, stderr) => {
|
|
16
36
|
if (error) {
|
|
17
37
|
// Print any Typescript output to the user, such as errors
|
|
@@ -28,7 +48,11 @@ const compileTypeScript = (inputFilePath: string, outputDir: string) => {
|
|
|
28
48
|
);
|
|
29
49
|
}
|
|
30
50
|
} catch (e) {
|
|
31
|
-
reject(
|
|
51
|
+
reject(
|
|
52
|
+
new Error(
|
|
53
|
+
"Could not find TypeScript, please install it globally (npm install -g typescript)"
|
|
54
|
+
)
|
|
55
|
+
);
|
|
32
56
|
}
|
|
33
57
|
});
|
|
34
58
|
};
|
|
@@ -52,13 +76,19 @@ async function getFolderPaths() {
|
|
|
52
76
|
}
|
|
53
77
|
}
|
|
54
78
|
|
|
55
|
-
export async function getComponentConfigs(
|
|
79
|
+
export async function getComponentConfigs(
|
|
80
|
+
generateTypesFiles: boolean
|
|
81
|
+
): Promise<{
|
|
82
|
+
configs: Record<string, any>;
|
|
83
|
+
hasError: boolean;
|
|
84
|
+
}> {
|
|
56
85
|
const folderPaths = await getFolderPaths();
|
|
86
|
+
let hasError = false;
|
|
57
87
|
const promiseConfigFiles = folderPaths.map(async (ccpath) => {
|
|
58
88
|
try {
|
|
59
|
-
const
|
|
89
|
+
const config = await fs.readJSON(`${ccpath}/config.json`);
|
|
60
90
|
console.log(`Found component in ${ccpath}`);
|
|
61
|
-
return
|
|
91
|
+
return { ccpath, config };
|
|
62
92
|
} catch (e: any) {
|
|
63
93
|
// Noop because there is a fallback
|
|
64
94
|
}
|
|
@@ -78,20 +108,38 @@ export async function getComponentConfigs(): Promise<Record<string, any>> {
|
|
|
78
108
|
);
|
|
79
109
|
await compileTypeScript(fileName, outputDirectory);
|
|
80
110
|
console.log(`Typescript compiled to ${ccpath}`);
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
const config = (await import(outputDirectory + "/config.js")).default;
|
|
112
|
+
|
|
113
|
+
const isValid = validateCustomComponents(config);
|
|
114
|
+
if (!isValid.valid) {
|
|
115
|
+
// Not throwing because we don't need a stack trace here
|
|
116
|
+
console.log(`Invalid config.ts file found`);
|
|
117
|
+
console.log(isValid.message);
|
|
118
|
+
hasError = true;
|
|
119
|
+
}
|
|
120
|
+
return { ccpath, config };
|
|
84
121
|
} catch (e: any) {
|
|
85
122
|
console.error(e);
|
|
86
123
|
console.error(e.message);
|
|
87
124
|
throw e;
|
|
88
125
|
}
|
|
89
126
|
});
|
|
90
|
-
const configFiles =
|
|
91
|
-
|
|
92
|
-
|
|
127
|
+
const configFiles = await Promise.all(promiseConfigFiles);
|
|
128
|
+
if (generateTypesFiles) {
|
|
129
|
+
for (const { config, ccpath } of configFiles) {
|
|
130
|
+
const typesFileContents = generateComponentTypesFile(config);
|
|
131
|
+
await fs.writeFile(`${ccpath}/types.ts`, typesFileContents);
|
|
132
|
+
console.log(`Generated ${ccpath}/types.ts`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const configs = configFiles
|
|
136
|
+
.filter((component) => component.config !== null)
|
|
137
|
+
.reduce((acc: Record<string, any>, { config }) => {
|
|
93
138
|
acc[config.id] = config;
|
|
94
139
|
return acc;
|
|
95
140
|
}, {});
|
|
96
|
-
return
|
|
141
|
+
return {
|
|
142
|
+
configs,
|
|
143
|
+
hasError,
|
|
144
|
+
};
|
|
97
145
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { inputTypeDefinions, type InputType } from "./input-types";
|
|
2
|
+
|
|
3
|
+
const indent = (str: string, spaces: number) => {
|
|
4
|
+
const spacesString = " ".repeat(spaces);
|
|
5
|
+
|
|
6
|
+
return str
|
|
7
|
+
.split("\n")
|
|
8
|
+
.map((line) => `${spacesString}${line}`)
|
|
9
|
+
.join("\n");
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function renderInputTypeAsTS(
|
|
13
|
+
name: string,
|
|
14
|
+
type: InputType,
|
|
15
|
+
trailingChars: string,
|
|
16
|
+
indentSpaces: number
|
|
17
|
+
) {
|
|
18
|
+
return indent(
|
|
19
|
+
`${name}: ${inputTypeDefinions[type].tsType}${trailingChars}`,
|
|
20
|
+
indentSpaces
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function generateComponentTypesFile(config: any) {
|
|
25
|
+
const statefulProperties = config.statefulProperties ?? [];
|
|
26
|
+
const eventHandlers = config.eventHandlers ?? [];
|
|
27
|
+
return `// WARNING: This file is automatically generated and managed by Superblocks CLI and should not be edited manually.
|
|
28
|
+
// Use the \`superblocks components watch\` command to regenerate this file from its source in config.ts
|
|
29
|
+
|
|
30
|
+
// All stateful properties of your component are defined here.
|
|
31
|
+
// These are the properties which are surfaced in the Superblocks properties panel
|
|
32
|
+
// and can be referenced throughout your Superblocks Application
|
|
33
|
+
export interface StatefulProperties {
|
|
34
|
+
${statefulProperties
|
|
35
|
+
.map((v: any) => renderInputTypeAsTS(v.path, v.inputType, ";", 2) + "\n")
|
|
36
|
+
.join("")}}
|
|
37
|
+
|
|
38
|
+
export interface Props extends StatefulProperties {
|
|
39
|
+
/**
|
|
40
|
+
* Update one (or more) of your component's stateful properties. This will cause the component to rerender,
|
|
41
|
+
* and will also notify any other components in your Application which depend on the updated properties, so they also rerender
|
|
42
|
+
* @param props An object with the properties to update along with their new values
|
|
43
|
+
*/
|
|
44
|
+
updateStatefulProperties: (props: Partial<StatefulProperties>) => void;
|
|
45
|
+
|
|
46
|
+
// Call the subsequent function(s) to trigger event(s) in Superblocks from your component.
|
|
47
|
+
// These events can be wired up to event handlers in your Superblocks App
|
|
48
|
+
${
|
|
49
|
+
eventHandlers.length === 0
|
|
50
|
+
? " // Note: no event handlers defined\n"
|
|
51
|
+
: eventHandlers
|
|
52
|
+
.map((v: any) => indent(v.path, 2) + ": () => void;" + "\n")
|
|
53
|
+
.join("")
|
|
54
|
+
}}
|
|
55
|
+
`;
|
|
56
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const supportedInputTypes = ["text", "number", "boolean", "js"] as const;
|
|
2
|
+
|
|
3
|
+
export type InputType = (typeof supportedInputTypes)[number];
|
|
4
|
+
|
|
5
|
+
interface TypeInfo {
|
|
6
|
+
tsType: string;
|
|
7
|
+
prompt: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const inputTypeDefinions: Record<InputType, TypeInfo> = {
|
|
11
|
+
text: {
|
|
12
|
+
tsType: "string",
|
|
13
|
+
prompt: "text",
|
|
14
|
+
},
|
|
15
|
+
number: {
|
|
16
|
+
tsType: "number",
|
|
17
|
+
prompt: "number",
|
|
18
|
+
},
|
|
19
|
+
boolean: {
|
|
20
|
+
tsType: "boolean",
|
|
21
|
+
prompt: "boolean",
|
|
22
|
+
},
|
|
23
|
+
js: {
|
|
24
|
+
tsType: "any",
|
|
25
|
+
prompt: "any (raw js expression)",
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export function isValidInputType(input: string): input is InputType {
|
|
30
|
+
return Object.prototype.hasOwnProperty.call(
|
|
31
|
+
inputTypeDefinions,
|
|
32
|
+
input as InputType
|
|
33
|
+
);
|
|
34
|
+
}
|
package/src/resource-configs.ts
CHANGED
|
@@ -8,10 +8,12 @@ export type SuperblocksMetadata = {
|
|
|
8
8
|
created: string;
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
export type SuperblocksResourceType = "APPLICATION" | "BACKEND";
|
|
12
|
+
|
|
11
13
|
export type VersionedResourceConfig = {
|
|
12
14
|
location: string;
|
|
13
15
|
lastUpdated: string;
|
|
14
|
-
resourceType:
|
|
16
|
+
resourceType: SuperblocksResourceType;
|
|
15
17
|
};
|
|
16
18
|
|
|
17
19
|
export type SuperblocksResourceConfig = {
|
|
@@ -123,9 +125,13 @@ export async function getSuperblocksResourceConfigIfExists(): Promise<
|
|
|
123
125
|
let config: SuperblocksResourceConfig | undefined;
|
|
124
126
|
try {
|
|
125
127
|
config = await getSuperblocksApplicationConfigJson();
|
|
126
|
-
} catch {
|
|
128
|
+
} catch {
|
|
129
|
+
// Noop
|
|
130
|
+
}
|
|
127
131
|
try {
|
|
128
132
|
config = await getSuperblocksBackendConfigJson();
|
|
129
|
-
} catch {
|
|
133
|
+
} catch {
|
|
134
|
+
// Noop
|
|
135
|
+
}
|
|
130
136
|
return config;
|
|
131
137
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import Ajv, { Schema } from "ajv";
|
|
2
|
+
import betterAjvErrors from "better-ajv-errors";
|
|
3
|
+
|
|
4
|
+
const ajv = new Ajv();
|
|
5
|
+
|
|
6
|
+
const componentSchema: Schema = {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: {
|
|
9
|
+
id: { type: "string" },
|
|
10
|
+
name: { type: "string" },
|
|
11
|
+
displayName: { type: "string" },
|
|
12
|
+
componentPath: { type: "string" },
|
|
13
|
+
statefulProperties: {
|
|
14
|
+
type: "array",
|
|
15
|
+
items: {
|
|
16
|
+
$ref: "#/definitions/StatefulProperty",
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
eventHandlers: {
|
|
20
|
+
type: "array",
|
|
21
|
+
items: {
|
|
22
|
+
$ref: "#/definitions/EventHandler",
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: [
|
|
27
|
+
"id",
|
|
28
|
+
"name",
|
|
29
|
+
"displayName",
|
|
30
|
+
"componentPath",
|
|
31
|
+
"statefulProperties",
|
|
32
|
+
"eventHandlers",
|
|
33
|
+
],
|
|
34
|
+
additionalProperties: false,
|
|
35
|
+
definitions: {
|
|
36
|
+
StatefulProperty: {
|
|
37
|
+
properties: {
|
|
38
|
+
label: { type: "string" },
|
|
39
|
+
path: { type: "string" },
|
|
40
|
+
inputType: { enum: ["text", "number", "boolean", "js"] },
|
|
41
|
+
placeholder: { type: "string" },
|
|
42
|
+
},
|
|
43
|
+
type: "object",
|
|
44
|
+
additionalProperties: false,
|
|
45
|
+
required: ["label", "path", "inputType"],
|
|
46
|
+
},
|
|
47
|
+
EventHandler: {
|
|
48
|
+
properties: {
|
|
49
|
+
label: { type: "string" },
|
|
50
|
+
path: { type: "string" },
|
|
51
|
+
},
|
|
52
|
+
type: "object",
|
|
53
|
+
additionalProperties: false,
|
|
54
|
+
required: ["label", "path"],
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const validate = ajv.compile(componentSchema);
|
|
60
|
+
|
|
61
|
+
export function validateCustomComponents(data: any): {
|
|
62
|
+
valid: boolean;
|
|
63
|
+
message?: string;
|
|
64
|
+
} {
|
|
65
|
+
const valid = validate(data);
|
|
66
|
+
if (!valid) {
|
|
67
|
+
return {
|
|
68
|
+
valid: false,
|
|
69
|
+
message: betterAjvErrors(componentSchema, data, validate.errors as any, {
|
|
70
|
+
indent: 2,
|
|
71
|
+
}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return { valid: true };
|
|
75
|
+
}
|