@superblocksteam/util 1.0.0 → 1.0.2
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/dist/generate-component-types.js +1 -1
- package/dist/identifiers.d.ts +8 -0
- package/dist/identifiers.js +68 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/validation.js +44 -2
- package/package.json +2 -2
- package/src/generate-component-types.ts +1 -1
- package/src/identifiers.ts +70 -0
- package/src/index.ts +1 -0
- package/src/validation.ts +46 -2
|
@@ -12,7 +12,7 @@ const indent = (str, spaces) => {
|
|
|
12
12
|
function renderInputTypeAsTS(name, type, trailingChars, indentSpaces) {
|
|
13
13
|
let { tsType } = data_types_1.dataTypeDefinions[type];
|
|
14
14
|
if (["number", "string"].includes(type)) {
|
|
15
|
-
tsType += "| null";
|
|
15
|
+
tsType += " | null";
|
|
16
16
|
}
|
|
17
17
|
return indent(`${name}: ${tsType}${trailingChars}`, indentSpaces);
|
|
18
18
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suggest an identifier based on a label, e.g. `My Component` -> `myComponent`.
|
|
3
|
+
* @param label The label to suggest an identifier for.
|
|
4
|
+
* @param capitalizeFirst Whether to capitalize the first letter of the identifier.
|
|
5
|
+
*/
|
|
6
|
+
export declare function suggestIdentifier(label: string, capitalizeFirst?: boolean): string;
|
|
7
|
+
export declare function isValidIdentifier(str: string): boolean;
|
|
8
|
+
export declare function isReservedPropertyName(str: string): boolean;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isReservedPropertyName = exports.isValidIdentifier = exports.suggestIdentifier = void 0;
|
|
4
|
+
// convert a string to upper camel case, e.g. "123 & this is my component, foo-bar" -> "123ThisIsMyComponentFooBar"
|
|
5
|
+
function wordsToUpperCamelCase(str) {
|
|
6
|
+
return str
|
|
7
|
+
.split(/[^A-Za-z0-9_]+/)
|
|
8
|
+
.filter(Boolean)
|
|
9
|
+
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
10
|
+
.join("");
|
|
11
|
+
}
|
|
12
|
+
const digits = "Zero One Two Three Four Five Six Seven Eight Nine".split(" ");
|
|
13
|
+
// convert digits to words, e.g. "123" -> "OneTwoThree"
|
|
14
|
+
function digitsToWords(str) {
|
|
15
|
+
return str.replace(/\d/g, (char) => { var _a; return (_a = digits[parseInt(char, 10)]) !== null && _a !== void 0 ? _a : char; });
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Suggest an identifier based on a label, e.g. `My Component` -> `myComponent`.
|
|
19
|
+
* @param label The label to suggest an identifier for.
|
|
20
|
+
* @param capitalizeFirst Whether to capitalize the first letter of the identifier.
|
|
21
|
+
*/
|
|
22
|
+
function suggestIdentifier(label, capitalizeFirst = false) {
|
|
23
|
+
const upperCamelCase = wordsToUpperCamelCase(label)
|
|
24
|
+
// convert any digits at the start of the string to words as these are not valid in identifiers
|
|
25
|
+
.replace(/^\d+/, digitsToWords);
|
|
26
|
+
if (capitalizeFirst) {
|
|
27
|
+
return upperCamelCase;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
return upperCamelCase[0].toLowerCase() + upperCamelCase.slice(1);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.suggestIdentifier = suggestIdentifier;
|
|
34
|
+
const IdentifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
35
|
+
function isValidIdentifier(str) {
|
|
36
|
+
return IdentifierRegex.test(str);
|
|
37
|
+
}
|
|
38
|
+
exports.isValidIdentifier = isValidIdentifier;
|
|
39
|
+
const reservedPropertyNames = new Set([
|
|
40
|
+
"ENTITY_TYPE",
|
|
41
|
+
"bindingPaths",
|
|
42
|
+
"bottomRow",
|
|
43
|
+
"dynamicBindingPathList",
|
|
44
|
+
"dynamicTriggerPathList",
|
|
45
|
+
"gridColumns",
|
|
46
|
+
"gridRows",
|
|
47
|
+
"height",
|
|
48
|
+
"isLoading",
|
|
49
|
+
"isVisible",
|
|
50
|
+
"leftColumn",
|
|
51
|
+
"parentId",
|
|
52
|
+
"rightColumn",
|
|
53
|
+
"snapColumns",
|
|
54
|
+
"snapRows",
|
|
55
|
+
"top",
|
|
56
|
+
"topRow",
|
|
57
|
+
"type",
|
|
58
|
+
"widgetId",
|
|
59
|
+
"widgetLastChange",
|
|
60
|
+
"widgetName",
|
|
61
|
+
"width",
|
|
62
|
+
]);
|
|
63
|
+
function isReservedPropertyName(str) {
|
|
64
|
+
return (reservedPropertyNames.has(str) ||
|
|
65
|
+
// also treat any property name starting with "default_" as reserved since we use this prefix for default properties
|
|
66
|
+
str.startsWith("default_"));
|
|
67
|
+
}
|
|
68
|
+
exports.isReservedPropertyName = isReservedPropertyName;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -9,3 +9,4 @@ tslib_1.__exportStar(require("./login"), exports);
|
|
|
9
9
|
tslib_1.__exportStar(require("./resource-configs"), exports);
|
|
10
10
|
tslib_1.__exportStar(require("./data-types"), exports);
|
|
11
11
|
tslib_1.__exportStar(require("./types"), exports);
|
|
12
|
+
tslib_1.__exportStar(require("./identifiers"), exports);
|
package/dist/validation.js
CHANGED
|
@@ -4,7 +4,45 @@ exports.validateCustomComponents = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const ajv_1 = tslib_1.__importDefault(require("ajv"));
|
|
6
6
|
const better_ajv_errors_1 = tslib_1.__importDefault(require("better-ajv-errors"));
|
|
7
|
+
const identifiers_1 = require("./identifiers");
|
|
7
8
|
const ajv = new ajv_1.default();
|
|
9
|
+
ajv.addKeyword({
|
|
10
|
+
keyword: "stringFormat",
|
|
11
|
+
compile: (schema) => {
|
|
12
|
+
let validateFunc;
|
|
13
|
+
// schema is the value assigned to stringFormat in the schema
|
|
14
|
+
if (schema === "identifier") {
|
|
15
|
+
validateFunc = (data) => {
|
|
16
|
+
if (typeof data != "string")
|
|
17
|
+
return false;
|
|
18
|
+
if (!(0, identifiers_1.isValidIdentifier)(data)) {
|
|
19
|
+
validateFunc.errors = [
|
|
20
|
+
{
|
|
21
|
+
keyword: "format",
|
|
22
|
+
message: "must be a valid identifier starting with a-z or underscore and containing only a-z, 0-9, and underscores",
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if ((0, identifiers_1.isReservedPropertyName)(data)) {
|
|
28
|
+
validateFunc.errors = [
|
|
29
|
+
{
|
|
30
|
+
keyword: "format",
|
|
31
|
+
message: "must not match a reserved property path (see https://docs.superblocks.com/applications/custom-components/faq#what-is-a-reserved-property)",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw new Error(`Unknown stringFormat: ${schema}`);
|
|
41
|
+
}
|
|
42
|
+
return validateFunc;
|
|
43
|
+
},
|
|
44
|
+
errors: true,
|
|
45
|
+
});
|
|
8
46
|
const componentSchema = {
|
|
9
47
|
type: "object",
|
|
10
48
|
properties: {
|
|
@@ -46,7 +84,7 @@ const componentSchema = {
|
|
|
46
84
|
definitions: {
|
|
47
85
|
Property: {
|
|
48
86
|
properties: {
|
|
49
|
-
path: {
|
|
87
|
+
path: { $ref: "#/definitions/Identifier" },
|
|
50
88
|
dataType: { enum: ["number", "boolean", "string", "any"] },
|
|
51
89
|
propertiesPanelDisplay: {
|
|
52
90
|
type: "object",
|
|
@@ -84,12 +122,16 @@ const componentSchema = {
|
|
|
84
122
|
EventHandler: {
|
|
85
123
|
properties: {
|
|
86
124
|
label: { type: "string" },
|
|
87
|
-
path: {
|
|
125
|
+
path: { $ref: "#/definitions/Identifier" },
|
|
88
126
|
},
|
|
89
127
|
type: "object",
|
|
90
128
|
additionalProperties: false,
|
|
91
129
|
required: ["label", "path"],
|
|
92
130
|
},
|
|
131
|
+
Identifier: {
|
|
132
|
+
type: "string",
|
|
133
|
+
stringFormat: "identifier",
|
|
134
|
+
},
|
|
93
135
|
},
|
|
94
136
|
};
|
|
95
137
|
const validate = ajv.compile(componentSchema);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@superblocksteam/util",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"homepage": "https://www.superblocks.com",
|
|
6
6
|
"scripts": {
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@types/mime-types": "^2.1.1",
|
|
24
24
|
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
|
25
25
|
"@typescript-eslint/parser": "^5.60.1",
|
|
26
|
-
"eslint": "^8.
|
|
26
|
+
"eslint": "^8.48.0",
|
|
27
27
|
"eslint-plugin-unicorn": "^47.0.0",
|
|
28
28
|
"prettier": "^2.8.8",
|
|
29
29
|
"typescript": "^5.1.3"
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// convert a string to upper camel case, e.g. "123 & this is my component, foo-bar" -> "123ThisIsMyComponentFooBar"
|
|
2
|
+
function wordsToUpperCamelCase(str: string) {
|
|
3
|
+
return str
|
|
4
|
+
.split(/[^A-Za-z0-9_]+/)
|
|
5
|
+
.filter(Boolean)
|
|
6
|
+
.map((word) => word[0].toUpperCase() + word.slice(1))
|
|
7
|
+
.join("");
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const digits = "Zero One Two Three Four Five Six Seven Eight Nine".split(" ");
|
|
11
|
+
|
|
12
|
+
// convert digits to words, e.g. "123" -> "OneTwoThree"
|
|
13
|
+
function digitsToWords(str: string) {
|
|
14
|
+
return str.replace(/\d/g, (char) => digits[parseInt(char, 10)] ?? char);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Suggest an identifier based on a label, e.g. `My Component` -> `myComponent`.
|
|
19
|
+
* @param label The label to suggest an identifier for.
|
|
20
|
+
* @param capitalizeFirst Whether to capitalize the first letter of the identifier.
|
|
21
|
+
*/
|
|
22
|
+
export function suggestIdentifier(label: string, capitalizeFirst = false) {
|
|
23
|
+
const upperCamelCase = wordsToUpperCamelCase(label)
|
|
24
|
+
// convert any digits at the start of the string to words as these are not valid in identifiers
|
|
25
|
+
.replace(/^\d+/, digitsToWords);
|
|
26
|
+
if (capitalizeFirst) {
|
|
27
|
+
return upperCamelCase;
|
|
28
|
+
} else {
|
|
29
|
+
return upperCamelCase[0].toLowerCase() + upperCamelCase.slice(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const IdentifierRegex = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
|
|
34
|
+
|
|
35
|
+
export function isValidIdentifier(str: string) {
|
|
36
|
+
return IdentifierRegex.test(str);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const reservedPropertyNames = new Set<string>([
|
|
40
|
+
"ENTITY_TYPE",
|
|
41
|
+
"bindingPaths",
|
|
42
|
+
"bottomRow",
|
|
43
|
+
"dynamicBindingPathList",
|
|
44
|
+
"dynamicTriggerPathList",
|
|
45
|
+
"gridColumns",
|
|
46
|
+
"gridRows",
|
|
47
|
+
"height",
|
|
48
|
+
"isLoading",
|
|
49
|
+
"isVisible",
|
|
50
|
+
"leftColumn",
|
|
51
|
+
"parentId",
|
|
52
|
+
"rightColumn",
|
|
53
|
+
"snapColumns",
|
|
54
|
+
"snapRows",
|
|
55
|
+
"top",
|
|
56
|
+
"topRow",
|
|
57
|
+
"type",
|
|
58
|
+
"widgetId",
|
|
59
|
+
"widgetLastChange",
|
|
60
|
+
"widgetName",
|
|
61
|
+
"width",
|
|
62
|
+
]);
|
|
63
|
+
|
|
64
|
+
export function isReservedPropertyName(str: string) {
|
|
65
|
+
return (
|
|
66
|
+
reservedPropertyNames.has(str) ||
|
|
67
|
+
// also treat any property name starting with "default_" as reserved since we use this prefix for default properties
|
|
68
|
+
str.startsWith("default_")
|
|
69
|
+
);
|
|
70
|
+
}
|
package/src/index.ts
CHANGED
package/src/validation.ts
CHANGED
|
@@ -1,8 +1,48 @@
|
|
|
1
1
|
import Ajv, { Schema } from "ajv";
|
|
2
|
+
import { DataValidateFunction } from "ajv/dist/types";
|
|
2
3
|
import betterAjvErrors from "better-ajv-errors";
|
|
4
|
+
import { isReservedPropertyName, isValidIdentifier } from "./identifiers";
|
|
3
5
|
|
|
4
6
|
const ajv = new Ajv();
|
|
5
7
|
|
|
8
|
+
ajv.addKeyword({
|
|
9
|
+
keyword: "stringFormat",
|
|
10
|
+
compile: (schema: any) => {
|
|
11
|
+
let validateFunc: DataValidateFunction;
|
|
12
|
+
// schema is the value assigned to stringFormat in the schema
|
|
13
|
+
if (schema === "identifier") {
|
|
14
|
+
validateFunc = (data: any) => {
|
|
15
|
+
if (typeof data != "string") return false;
|
|
16
|
+
if (!isValidIdentifier(data)) {
|
|
17
|
+
validateFunc.errors = [
|
|
18
|
+
{
|
|
19
|
+
keyword: "format",
|
|
20
|
+
message:
|
|
21
|
+
"must be a valid identifier starting with a-z or underscore and containing only a-z, 0-9, and underscores",
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (isReservedPropertyName(data)) {
|
|
27
|
+
validateFunc.errors = [
|
|
28
|
+
{
|
|
29
|
+
keyword: "format",
|
|
30
|
+
message:
|
|
31
|
+
"must not match a reserved property path (see https://docs.superblocks.com/applications/custom-components/faq#what-is-a-reserved-property)",
|
|
32
|
+
},
|
|
33
|
+
];
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return true;
|
|
37
|
+
};
|
|
38
|
+
} else {
|
|
39
|
+
throw new Error(`Unknown stringFormat: ${schema}`);
|
|
40
|
+
}
|
|
41
|
+
return validateFunc;
|
|
42
|
+
},
|
|
43
|
+
errors: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
6
46
|
const componentSchema: Schema = {
|
|
7
47
|
type: "object",
|
|
8
48
|
properties: {
|
|
@@ -44,7 +84,7 @@ const componentSchema: Schema = {
|
|
|
44
84
|
definitions: {
|
|
45
85
|
Property: {
|
|
46
86
|
properties: {
|
|
47
|
-
path: {
|
|
87
|
+
path: { $ref: "#/definitions/Identifier" },
|
|
48
88
|
dataType: { enum: ["number", "boolean", "string", "any"] },
|
|
49
89
|
propertiesPanelDisplay: {
|
|
50
90
|
type: "object",
|
|
@@ -82,12 +122,16 @@ const componentSchema: Schema = {
|
|
|
82
122
|
EventHandler: {
|
|
83
123
|
properties: {
|
|
84
124
|
label: { type: "string" },
|
|
85
|
-
path: {
|
|
125
|
+
path: { $ref: "#/definitions/Identifier" },
|
|
86
126
|
},
|
|
87
127
|
type: "object",
|
|
88
128
|
additionalProperties: false,
|
|
89
129
|
required: ["label", "path"],
|
|
90
130
|
},
|
|
131
|
+
Identifier: {
|
|
132
|
+
type: "string",
|
|
133
|
+
stringFormat: "identifier",
|
|
134
|
+
},
|
|
91
135
|
},
|
|
92
136
|
};
|
|
93
137
|
|