@valbuild/server 0.18.0 → 0.19.0
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/valbuild-server.cjs.dev.js +104 -13
- package/dist/valbuild-server.cjs.prod.js +104 -13
- package/dist/valbuild-server.esm.js +105 -14
- package/package.json +3 -3
- package/src/ValModuleLoader.ts +1 -1
- package/src/createFixPatch.ts +7 -12
- package/src/patch/ts/ops.test.ts +101 -0
- package/src/patch/ts/ops.ts +69 -4
- package/src/patch/ts/richtext.test.ts +74 -0
- package/src/patch/ts/richtext.ts +73 -0
- package/src/patch/validation.ts +7 -0
- package/src/patchValFile.ts +14 -5
- package/tsconfig.json +2 -1
@@ -246,6 +246,73 @@ function analyzeValModule(sourceFile) {
|
|
246
246
|
return analysis;
|
247
247
|
}
|
248
248
|
|
249
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
250
|
+
|
251
|
+
const HeaderRegEx = /h([\d])/;
|
252
|
+
function richTextToTaggedStringTemplate(source) {
|
253
|
+
const texts = [""];
|
254
|
+
const nodes = [];
|
255
|
+
let didAppendNewLines = false;
|
256
|
+
function rec(node) {
|
257
|
+
if (typeof node === "string") {
|
258
|
+
texts[texts.length - 1] += node;
|
259
|
+
} else if (node.tag) {
|
260
|
+
var _node$tag, _node$children, _node$tag2;
|
261
|
+
if ((_node$tag = node.tag) !== null && _node$tag !== void 0 && _node$tag.startsWith("h")) {
|
262
|
+
const [, depth] = node.tag.match(HeaderRegEx);
|
263
|
+
for (let i = 0; i < Number(depth); i++) {
|
264
|
+
texts[texts.length - 1] += "#";
|
265
|
+
}
|
266
|
+
texts[texts.length - 1] += " ";
|
267
|
+
} else if (node.tag === "span") {
|
268
|
+
if (node.classes.includes("bold") && !node.classes.includes("italic")) {
|
269
|
+
texts[texts.length - 1] += "**";
|
270
|
+
}
|
271
|
+
if (node.classes.includes("italic") && !node.classes.includes("bold")) {
|
272
|
+
texts[texts.length - 1] += "*";
|
273
|
+
}
|
274
|
+
if (node.classes.includes("italic") && node.classes.includes("bold")) {
|
275
|
+
texts[texts.length - 1] += "***";
|
276
|
+
}
|
277
|
+
if (node.classes.includes("line-through")) {
|
278
|
+
texts[texts.length - 1] += "~~";
|
279
|
+
}
|
280
|
+
}
|
281
|
+
(_node$children = node.children) === null || _node$children === void 0 ? void 0 : _node$children.forEach(rec);
|
282
|
+
if (node.tag === "span") {
|
283
|
+
didAppendNewLines = false;
|
284
|
+
if (node.classes.includes("line-through")) {
|
285
|
+
texts[texts.length - 1] += "~~";
|
286
|
+
}
|
287
|
+
if (node.classes.includes("italic") && node.classes.includes("bold")) {
|
288
|
+
texts[texts.length - 1] += "***";
|
289
|
+
}
|
290
|
+
if (node.classes.includes("italic") && !node.classes.includes("bold")) {
|
291
|
+
texts[texts.length - 1] += "*";
|
292
|
+
}
|
293
|
+
if (node.classes.includes("bold") && !node.classes.includes("italic")) {
|
294
|
+
texts[texts.length - 1] += "**";
|
295
|
+
}
|
296
|
+
} else if (node.tag === "p") {
|
297
|
+
didAppendNewLines = true;
|
298
|
+
texts[texts.length - 1] += "\n\n";
|
299
|
+
} else if ((_node$tag2 = node.tag) !== null && _node$tag2 !== void 0 && _node$tag2.startsWith("h")) {
|
300
|
+
didAppendNewLines = true;
|
301
|
+
texts[texts.length - 1] += "\n\n";
|
302
|
+
}
|
303
|
+
} else {
|
304
|
+
nodes.push(node);
|
305
|
+
texts.push("\n");
|
306
|
+
}
|
307
|
+
}
|
308
|
+
source.children.forEach(rec);
|
309
|
+
if (texts[texts.length - 1] && didAppendNewLines) {
|
310
|
+
// remove last \n\n
|
311
|
+
texts[texts.length - 1] = texts[texts.length - 1].slice(0, -2);
|
312
|
+
}
|
313
|
+
return [texts, nodes];
|
314
|
+
}
|
315
|
+
|
249
316
|
function isValidIdentifier(text) {
|
250
317
|
if (text.length === 0) {
|
251
318
|
return false;
|
@@ -263,8 +330,20 @@ function isValidIdentifier(text) {
|
|
263
330
|
function createPropertyAssignment(key, value) {
|
264
331
|
return ts__default["default"].factory.createPropertyAssignment(isValidIdentifier(key) ? ts__default["default"].factory.createIdentifier(key) : ts__default["default"].factory.createStringLiteral(key), toExpression(value));
|
265
332
|
}
|
266
|
-
function createValFileReference(
|
267
|
-
|
333
|
+
function createValFileReference(value) {
|
334
|
+
const args = [ts__default["default"].factory.createStringLiteral(value[core.FILE_REF_PROP])];
|
335
|
+
if (value.metadata) {
|
336
|
+
args.push(toExpression(value.metadata));
|
337
|
+
}
|
338
|
+
return ts__default["default"].factory.createCallExpression(ts__default["default"].factory.createPropertyAccessExpression(ts__default["default"].factory.createIdentifier("val"), ts__default["default"].factory.createIdentifier("file")), undefined, args);
|
339
|
+
}
|
340
|
+
function createValRichTextTaggedStringTemplate(value) {
|
341
|
+
const [[head, ...others], nodes] = richTextToTaggedStringTemplate(value);
|
342
|
+
const tag = ts__default["default"].factory.createPropertyAccessExpression(ts__default["default"].factory.createIdentifier("val"), ts__default["default"].factory.createIdentifier("richtext"));
|
343
|
+
if (nodes.length > 0) {
|
344
|
+
return ts__default["default"].factory.createTaggedTemplateExpression(tag, undefined, ts__default["default"].factory.createTemplateExpression(ts__default["default"].factory.createTemplateHead(head, head), others.map((s, i) => ts__default["default"].factory.createTemplateSpan(toExpression(nodes[i]), i < others.length - 1 ? ts__default["default"].factory.createTemplateMiddle(s, s) : ts__default["default"].factory.createTemplateTail(s, s)))));
|
345
|
+
}
|
346
|
+
return ts__default["default"].factory.createTaggedTemplateExpression(tag, undefined, ts__default["default"].factory.createNoSubstitutionTemplateLiteral(head, head));
|
268
347
|
}
|
269
348
|
function toExpression(value) {
|
270
349
|
if (typeof value === "string") {
|
@@ -280,7 +359,9 @@ function toExpression(value) {
|
|
280
359
|
return ts__default["default"].factory.createArrayLiteralExpression(value.map(toExpression));
|
281
360
|
} else if (typeof value === "object") {
|
282
361
|
if (isValFileValue(value)) {
|
283
|
-
return createValFileReference(value
|
362
|
+
return createValFileReference(value);
|
363
|
+
} else if (isValRichTextValue(value)) {
|
364
|
+
return createValRichTextTaggedStringTemplate(value);
|
284
365
|
}
|
285
366
|
return ts__default["default"].factory.createObjectLiteralExpression(Object.entries(value).map(([key, value]) => createPropertyAssignment(key, value)));
|
286
367
|
} else {
|
@@ -507,7 +588,14 @@ function removeAtPath(document, rootNode, path) {
|
|
507
588
|
return fp.pipe(getPointerFromPath(rootNode, path), fp.result.flatMap(([node, key]) => removeFromNode(document, node, key)));
|
508
589
|
}
|
509
590
|
function isValFileValue(value) {
|
510
|
-
return !!(typeof value === "object" && value &&
|
591
|
+
return !!(typeof value === "object" && value &&
|
592
|
+
// TODO: replace the below with this:
|
593
|
+
// VAL_EXTENSION in value &&
|
594
|
+
// value[VAL_EXTENSION] === "file" &&
|
595
|
+
core.FILE_REF_PROP in value && typeof value[core.FILE_REF_PROP] === "string");
|
596
|
+
}
|
597
|
+
function isValRichTextValue(value) {
|
598
|
+
return !!(typeof value === "object" && value && core.VAL_EXTENSION in value && value[core.VAL_EXTENSION] === "richtext" && "children" in value && typeof value.children === "object" && Array.isArray(value.children));
|
511
599
|
}
|
512
600
|
function addToNode(document, node, key, value) {
|
513
601
|
if (ts__default["default"].isArrayLiteralExpression(node)) {
|
@@ -691,7 +779,12 @@ const patchValFile = async (id, valConfigPath, patch$1, sourceFileHandler, runti
|
|
691
779
|
// Buffer.from(content, "base64").toString("binary")
|
692
780
|
// );
|
693
781
|
// sourceFileHandler.moveFile(tempFilePath, "." + filePath);
|
694
|
-
|
782
|
+
// TODO: ensure that directory exists
|
783
|
+
if (content.startsWith("data:/image/svg+xml")) {
|
784
|
+
sourceFileHandler.writeFile("." + filePath, convertDataUrlToBase64(content).toString("utf8"), "utf8");
|
785
|
+
} else {
|
786
|
+
sourceFileHandler.writeFile("." + filePath, convertDataUrlToBase64(content).toString("binary"), "binary");
|
787
|
+
}
|
695
788
|
}
|
696
789
|
for (const [ref, patch] of Object.entries(derefRes.value.remotePatches)) {
|
697
790
|
throw Error(`Cannot update remote ${ref} with ${JSON.stringify(patch)}: not implemented`);
|
@@ -773,7 +866,7 @@ class ValSourceFileHandler {
|
|
773
866
|
|
774
867
|
const JsFileLookupMapping = [
|
775
868
|
// NOTE: first one matching will be used
|
776
|
-
[".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js"]]];
|
869
|
+
[".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js", ".esm.js", ".mjs.js"]]];
|
777
870
|
class ValModuleLoader {
|
778
871
|
constructor(projectRoot, compilerOptions, sourceFileHandler, host = {
|
779
872
|
...ts__default["default"].sys,
|
@@ -1038,6 +1131,10 @@ const OperationJSONT = z__default["default"].discriminatedUnion("op", [z__defaul
|
|
1038
1131
|
op: z__default["default"].literal("test"),
|
1039
1132
|
path: z__default["default"].string(),
|
1040
1133
|
value: JSONValueT
|
1134
|
+
}).strict(), z__default["default"].object({
|
1135
|
+
op: z__default["default"].literal("file"),
|
1136
|
+
path: z__default["default"].string(),
|
1137
|
+
value: z__default["default"].string()
|
1041
1138
|
}).strict()]);
|
1042
1139
|
const PatchJSON = z__default["default"].array(OperationJSONT);
|
1043
1140
|
|
@@ -1772,7 +1869,7 @@ async function createFixPatch(config, apply, sourcePath, validationError) {
|
|
1772
1869
|
}
|
1773
1870
|
const localFile = path__namespace["default"].join(config.projectRoot, maybeRef);
|
1774
1871
|
const buffer = fs__default["default"].readFileSync(localFile);
|
1775
|
-
const sha256 = await getSHA256Hash(buffer);
|
1872
|
+
const sha256 = await core.Internal.getSHA256Hash(buffer);
|
1776
1873
|
const imageSize = sizeOf__default["default"](buffer);
|
1777
1874
|
return {
|
1778
1875
|
...imageSize,
|
@@ -1864,12 +1961,6 @@ async function createFixPatch(config, apply, sourcePath, validationError) {
|
|
1864
1961
|
remainingErrors
|
1865
1962
|
};
|
1866
1963
|
}
|
1867
|
-
const getSHA256Hash = async bits => {
|
1868
|
-
const hashBuffer = await crypto__default["default"].subtle.digest("SHA-256", bits);
|
1869
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
1870
|
-
const hash = hashArray.map(item => item.toString(16).padStart(2, "0")).join("");
|
1871
|
-
return hash;
|
1872
|
-
};
|
1873
1964
|
|
1874
1965
|
exports.LocalValServer = LocalValServer;
|
1875
1966
|
exports.PatchJSON = PatchJSON;
|
@@ -246,6 +246,73 @@ function analyzeValModule(sourceFile) {
|
|
246
246
|
return analysis;
|
247
247
|
}
|
248
248
|
|
249
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
250
|
+
|
251
|
+
const HeaderRegEx = /h([\d])/;
|
252
|
+
function richTextToTaggedStringTemplate(source) {
|
253
|
+
const texts = [""];
|
254
|
+
const nodes = [];
|
255
|
+
let didAppendNewLines = false;
|
256
|
+
function rec(node) {
|
257
|
+
if (typeof node === "string") {
|
258
|
+
texts[texts.length - 1] += node;
|
259
|
+
} else if (node.tag) {
|
260
|
+
var _node$tag, _node$children, _node$tag2;
|
261
|
+
if ((_node$tag = node.tag) !== null && _node$tag !== void 0 && _node$tag.startsWith("h")) {
|
262
|
+
const [, depth] = node.tag.match(HeaderRegEx);
|
263
|
+
for (let i = 0; i < Number(depth); i++) {
|
264
|
+
texts[texts.length - 1] += "#";
|
265
|
+
}
|
266
|
+
texts[texts.length - 1] += " ";
|
267
|
+
} else if (node.tag === "span") {
|
268
|
+
if (node.classes.includes("bold") && !node.classes.includes("italic")) {
|
269
|
+
texts[texts.length - 1] += "**";
|
270
|
+
}
|
271
|
+
if (node.classes.includes("italic") && !node.classes.includes("bold")) {
|
272
|
+
texts[texts.length - 1] += "*";
|
273
|
+
}
|
274
|
+
if (node.classes.includes("italic") && node.classes.includes("bold")) {
|
275
|
+
texts[texts.length - 1] += "***";
|
276
|
+
}
|
277
|
+
if (node.classes.includes("line-through")) {
|
278
|
+
texts[texts.length - 1] += "~~";
|
279
|
+
}
|
280
|
+
}
|
281
|
+
(_node$children = node.children) === null || _node$children === void 0 ? void 0 : _node$children.forEach(rec);
|
282
|
+
if (node.tag === "span") {
|
283
|
+
didAppendNewLines = false;
|
284
|
+
if (node.classes.includes("line-through")) {
|
285
|
+
texts[texts.length - 1] += "~~";
|
286
|
+
}
|
287
|
+
if (node.classes.includes("italic") && node.classes.includes("bold")) {
|
288
|
+
texts[texts.length - 1] += "***";
|
289
|
+
}
|
290
|
+
if (node.classes.includes("italic") && !node.classes.includes("bold")) {
|
291
|
+
texts[texts.length - 1] += "*";
|
292
|
+
}
|
293
|
+
if (node.classes.includes("bold") && !node.classes.includes("italic")) {
|
294
|
+
texts[texts.length - 1] += "**";
|
295
|
+
}
|
296
|
+
} else if (node.tag === "p") {
|
297
|
+
didAppendNewLines = true;
|
298
|
+
texts[texts.length - 1] += "\n\n";
|
299
|
+
} else if ((_node$tag2 = node.tag) !== null && _node$tag2 !== void 0 && _node$tag2.startsWith("h")) {
|
300
|
+
didAppendNewLines = true;
|
301
|
+
texts[texts.length - 1] += "\n\n";
|
302
|
+
}
|
303
|
+
} else {
|
304
|
+
nodes.push(node);
|
305
|
+
texts.push("\n");
|
306
|
+
}
|
307
|
+
}
|
308
|
+
source.children.forEach(rec);
|
309
|
+
if (texts[texts.length - 1] && didAppendNewLines) {
|
310
|
+
// remove last \n\n
|
311
|
+
texts[texts.length - 1] = texts[texts.length - 1].slice(0, -2);
|
312
|
+
}
|
313
|
+
return [texts, nodes];
|
314
|
+
}
|
315
|
+
|
249
316
|
function isValidIdentifier(text) {
|
250
317
|
if (text.length === 0) {
|
251
318
|
return false;
|
@@ -263,8 +330,20 @@ function isValidIdentifier(text) {
|
|
263
330
|
function createPropertyAssignment(key, value) {
|
264
331
|
return ts__default["default"].factory.createPropertyAssignment(isValidIdentifier(key) ? ts__default["default"].factory.createIdentifier(key) : ts__default["default"].factory.createStringLiteral(key), toExpression(value));
|
265
332
|
}
|
266
|
-
function createValFileReference(
|
267
|
-
|
333
|
+
function createValFileReference(value) {
|
334
|
+
const args = [ts__default["default"].factory.createStringLiteral(value[core.FILE_REF_PROP])];
|
335
|
+
if (value.metadata) {
|
336
|
+
args.push(toExpression(value.metadata));
|
337
|
+
}
|
338
|
+
return ts__default["default"].factory.createCallExpression(ts__default["default"].factory.createPropertyAccessExpression(ts__default["default"].factory.createIdentifier("val"), ts__default["default"].factory.createIdentifier("file")), undefined, args);
|
339
|
+
}
|
340
|
+
function createValRichTextTaggedStringTemplate(value) {
|
341
|
+
const [[head, ...others], nodes] = richTextToTaggedStringTemplate(value);
|
342
|
+
const tag = ts__default["default"].factory.createPropertyAccessExpression(ts__default["default"].factory.createIdentifier("val"), ts__default["default"].factory.createIdentifier("richtext"));
|
343
|
+
if (nodes.length > 0) {
|
344
|
+
return ts__default["default"].factory.createTaggedTemplateExpression(tag, undefined, ts__default["default"].factory.createTemplateExpression(ts__default["default"].factory.createTemplateHead(head, head), others.map((s, i) => ts__default["default"].factory.createTemplateSpan(toExpression(nodes[i]), i < others.length - 1 ? ts__default["default"].factory.createTemplateMiddle(s, s) : ts__default["default"].factory.createTemplateTail(s, s)))));
|
345
|
+
}
|
346
|
+
return ts__default["default"].factory.createTaggedTemplateExpression(tag, undefined, ts__default["default"].factory.createNoSubstitutionTemplateLiteral(head, head));
|
268
347
|
}
|
269
348
|
function toExpression(value) {
|
270
349
|
if (typeof value === "string") {
|
@@ -280,7 +359,9 @@ function toExpression(value) {
|
|
280
359
|
return ts__default["default"].factory.createArrayLiteralExpression(value.map(toExpression));
|
281
360
|
} else if (typeof value === "object") {
|
282
361
|
if (isValFileValue(value)) {
|
283
|
-
return createValFileReference(value
|
362
|
+
return createValFileReference(value);
|
363
|
+
} else if (isValRichTextValue(value)) {
|
364
|
+
return createValRichTextTaggedStringTemplate(value);
|
284
365
|
}
|
285
366
|
return ts__default["default"].factory.createObjectLiteralExpression(Object.entries(value).map(([key, value]) => createPropertyAssignment(key, value)));
|
286
367
|
} else {
|
@@ -507,7 +588,14 @@ function removeAtPath(document, rootNode, path) {
|
|
507
588
|
return fp.pipe(getPointerFromPath(rootNode, path), fp.result.flatMap(([node, key]) => removeFromNode(document, node, key)));
|
508
589
|
}
|
509
590
|
function isValFileValue(value) {
|
510
|
-
return !!(typeof value === "object" && value &&
|
591
|
+
return !!(typeof value === "object" && value &&
|
592
|
+
// TODO: replace the below with this:
|
593
|
+
// VAL_EXTENSION in value &&
|
594
|
+
// value[VAL_EXTENSION] === "file" &&
|
595
|
+
core.FILE_REF_PROP in value && typeof value[core.FILE_REF_PROP] === "string");
|
596
|
+
}
|
597
|
+
function isValRichTextValue(value) {
|
598
|
+
return !!(typeof value === "object" && value && core.VAL_EXTENSION in value && value[core.VAL_EXTENSION] === "richtext" && "children" in value && typeof value.children === "object" && Array.isArray(value.children));
|
511
599
|
}
|
512
600
|
function addToNode(document, node, key, value) {
|
513
601
|
if (ts__default["default"].isArrayLiteralExpression(node)) {
|
@@ -691,7 +779,12 @@ const patchValFile = async (id, valConfigPath, patch$1, sourceFileHandler, runti
|
|
691
779
|
// Buffer.from(content, "base64").toString("binary")
|
692
780
|
// );
|
693
781
|
// sourceFileHandler.moveFile(tempFilePath, "." + filePath);
|
694
|
-
|
782
|
+
// TODO: ensure that directory exists
|
783
|
+
if (content.startsWith("data:/image/svg+xml")) {
|
784
|
+
sourceFileHandler.writeFile("." + filePath, convertDataUrlToBase64(content).toString("utf8"), "utf8");
|
785
|
+
} else {
|
786
|
+
sourceFileHandler.writeFile("." + filePath, convertDataUrlToBase64(content).toString("binary"), "binary");
|
787
|
+
}
|
695
788
|
}
|
696
789
|
for (const [ref, patch] of Object.entries(derefRes.value.remotePatches)) {
|
697
790
|
throw Error(`Cannot update remote ${ref} with ${JSON.stringify(patch)}: not implemented`);
|
@@ -773,7 +866,7 @@ class ValSourceFileHandler {
|
|
773
866
|
|
774
867
|
const JsFileLookupMapping = [
|
775
868
|
// NOTE: first one matching will be used
|
776
|
-
[".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js"]]];
|
869
|
+
[".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js", ".esm.js", ".mjs.js"]]];
|
777
870
|
class ValModuleLoader {
|
778
871
|
constructor(projectRoot, compilerOptions, sourceFileHandler, host = {
|
779
872
|
...ts__default["default"].sys,
|
@@ -1038,6 +1131,10 @@ const OperationJSONT = z__default["default"].discriminatedUnion("op", [z__defaul
|
|
1038
1131
|
op: z__default["default"].literal("test"),
|
1039
1132
|
path: z__default["default"].string(),
|
1040
1133
|
value: JSONValueT
|
1134
|
+
}).strict(), z__default["default"].object({
|
1135
|
+
op: z__default["default"].literal("file"),
|
1136
|
+
path: z__default["default"].string(),
|
1137
|
+
value: z__default["default"].string()
|
1041
1138
|
}).strict()]);
|
1042
1139
|
const PatchJSON = z__default["default"].array(OperationJSONT);
|
1043
1140
|
|
@@ -1772,7 +1869,7 @@ async function createFixPatch(config, apply, sourcePath, validationError) {
|
|
1772
1869
|
}
|
1773
1870
|
const localFile = path__namespace["default"].join(config.projectRoot, maybeRef);
|
1774
1871
|
const buffer = fs__default["default"].readFileSync(localFile);
|
1775
|
-
const sha256 = await getSHA256Hash(buffer);
|
1872
|
+
const sha256 = await core.Internal.getSHA256Hash(buffer);
|
1776
1873
|
const imageSize = sizeOf__default["default"](buffer);
|
1777
1874
|
return {
|
1778
1875
|
...imageSize,
|
@@ -1864,12 +1961,6 @@ async function createFixPatch(config, apply, sourcePath, validationError) {
|
|
1864
1961
|
remainingErrors
|
1865
1962
|
};
|
1866
1963
|
}
|
1867
|
-
const getSHA256Hash = async bits => {
|
1868
|
-
const hashBuffer = await crypto__default["default"].subtle.digest("SHA-256", bits);
|
1869
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
1870
|
-
const hash = hashArray.map(item => item.toString(16).padStart(2, "0")).join("");
|
1871
|
-
return hash;
|
1872
|
-
};
|
1873
1964
|
|
1874
1965
|
exports.LocalValServer = LocalValServer;
|
1875
1966
|
exports.PatchJSON = PatchJSON;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { newQuickJSWASMModule } from 'quickjs-emscripten';
|
2
2
|
import ts from 'typescript';
|
3
3
|
import { result, pipe } from '@valbuild/core/fp';
|
4
|
-
import { FILE_REF_PROP, derefPatch, Internal, Schema } from '@valbuild/core';
|
4
|
+
import { FILE_REF_PROP, VAL_EXTENSION, derefPatch, Internal, Schema } from '@valbuild/core';
|
5
5
|
import { deepEqual, isNotRoot, PatchError, parseAndValidateArrayIndex, applyPatch, parsePatch, sourceToPatchPath } from '@valbuild/core/patch';
|
6
6
|
import * as path from 'path';
|
7
7
|
import path__default from 'path';
|
@@ -215,6 +215,73 @@ function analyzeValModule(sourceFile) {
|
|
215
215
|
return analysis;
|
216
216
|
}
|
217
217
|
|
218
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
219
|
+
|
220
|
+
const HeaderRegEx = /h([\d])/;
|
221
|
+
function richTextToTaggedStringTemplate(source) {
|
222
|
+
const texts = [""];
|
223
|
+
const nodes = [];
|
224
|
+
let didAppendNewLines = false;
|
225
|
+
function rec(node) {
|
226
|
+
if (typeof node === "string") {
|
227
|
+
texts[texts.length - 1] += node;
|
228
|
+
} else if (node.tag) {
|
229
|
+
var _node$tag, _node$children, _node$tag2;
|
230
|
+
if ((_node$tag = node.tag) !== null && _node$tag !== void 0 && _node$tag.startsWith("h")) {
|
231
|
+
const [, depth] = node.tag.match(HeaderRegEx);
|
232
|
+
for (let i = 0; i < Number(depth); i++) {
|
233
|
+
texts[texts.length - 1] += "#";
|
234
|
+
}
|
235
|
+
texts[texts.length - 1] += " ";
|
236
|
+
} else if (node.tag === "span") {
|
237
|
+
if (node.classes.includes("bold") && !node.classes.includes("italic")) {
|
238
|
+
texts[texts.length - 1] += "**";
|
239
|
+
}
|
240
|
+
if (node.classes.includes("italic") && !node.classes.includes("bold")) {
|
241
|
+
texts[texts.length - 1] += "*";
|
242
|
+
}
|
243
|
+
if (node.classes.includes("italic") && node.classes.includes("bold")) {
|
244
|
+
texts[texts.length - 1] += "***";
|
245
|
+
}
|
246
|
+
if (node.classes.includes("line-through")) {
|
247
|
+
texts[texts.length - 1] += "~~";
|
248
|
+
}
|
249
|
+
}
|
250
|
+
(_node$children = node.children) === null || _node$children === void 0 ? void 0 : _node$children.forEach(rec);
|
251
|
+
if (node.tag === "span") {
|
252
|
+
didAppendNewLines = false;
|
253
|
+
if (node.classes.includes("line-through")) {
|
254
|
+
texts[texts.length - 1] += "~~";
|
255
|
+
}
|
256
|
+
if (node.classes.includes("italic") && node.classes.includes("bold")) {
|
257
|
+
texts[texts.length - 1] += "***";
|
258
|
+
}
|
259
|
+
if (node.classes.includes("italic") && !node.classes.includes("bold")) {
|
260
|
+
texts[texts.length - 1] += "*";
|
261
|
+
}
|
262
|
+
if (node.classes.includes("bold") && !node.classes.includes("italic")) {
|
263
|
+
texts[texts.length - 1] += "**";
|
264
|
+
}
|
265
|
+
} else if (node.tag === "p") {
|
266
|
+
didAppendNewLines = true;
|
267
|
+
texts[texts.length - 1] += "\n\n";
|
268
|
+
} else if ((_node$tag2 = node.tag) !== null && _node$tag2 !== void 0 && _node$tag2.startsWith("h")) {
|
269
|
+
didAppendNewLines = true;
|
270
|
+
texts[texts.length - 1] += "\n\n";
|
271
|
+
}
|
272
|
+
} else {
|
273
|
+
nodes.push(node);
|
274
|
+
texts.push("\n");
|
275
|
+
}
|
276
|
+
}
|
277
|
+
source.children.forEach(rec);
|
278
|
+
if (texts[texts.length - 1] && didAppendNewLines) {
|
279
|
+
// remove last \n\n
|
280
|
+
texts[texts.length - 1] = texts[texts.length - 1].slice(0, -2);
|
281
|
+
}
|
282
|
+
return [texts, nodes];
|
283
|
+
}
|
284
|
+
|
218
285
|
function isValidIdentifier(text) {
|
219
286
|
if (text.length === 0) {
|
220
287
|
return false;
|
@@ -232,8 +299,20 @@ function isValidIdentifier(text) {
|
|
232
299
|
function createPropertyAssignment(key, value) {
|
233
300
|
return ts.factory.createPropertyAssignment(isValidIdentifier(key) ? ts.factory.createIdentifier(key) : ts.factory.createStringLiteral(key), toExpression(value));
|
234
301
|
}
|
235
|
-
function createValFileReference(
|
236
|
-
|
302
|
+
function createValFileReference(value) {
|
303
|
+
const args = [ts.factory.createStringLiteral(value[FILE_REF_PROP])];
|
304
|
+
if (value.metadata) {
|
305
|
+
args.push(toExpression(value.metadata));
|
306
|
+
}
|
307
|
+
return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("val"), ts.factory.createIdentifier("file")), undefined, args);
|
308
|
+
}
|
309
|
+
function createValRichTextTaggedStringTemplate(value) {
|
310
|
+
const [[head, ...others], nodes] = richTextToTaggedStringTemplate(value);
|
311
|
+
const tag = ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier("val"), ts.factory.createIdentifier("richtext"));
|
312
|
+
if (nodes.length > 0) {
|
313
|
+
return ts.factory.createTaggedTemplateExpression(tag, undefined, ts.factory.createTemplateExpression(ts.factory.createTemplateHead(head, head), others.map((s, i) => ts.factory.createTemplateSpan(toExpression(nodes[i]), i < others.length - 1 ? ts.factory.createTemplateMiddle(s, s) : ts.factory.createTemplateTail(s, s)))));
|
314
|
+
}
|
315
|
+
return ts.factory.createTaggedTemplateExpression(tag, undefined, ts.factory.createNoSubstitutionTemplateLiteral(head, head));
|
237
316
|
}
|
238
317
|
function toExpression(value) {
|
239
318
|
if (typeof value === "string") {
|
@@ -249,7 +328,9 @@ function toExpression(value) {
|
|
249
328
|
return ts.factory.createArrayLiteralExpression(value.map(toExpression));
|
250
329
|
} else if (typeof value === "object") {
|
251
330
|
if (isValFileValue(value)) {
|
252
|
-
return createValFileReference(value
|
331
|
+
return createValFileReference(value);
|
332
|
+
} else if (isValRichTextValue(value)) {
|
333
|
+
return createValRichTextTaggedStringTemplate(value);
|
253
334
|
}
|
254
335
|
return ts.factory.createObjectLiteralExpression(Object.entries(value).map(([key, value]) => createPropertyAssignment(key, value)));
|
255
336
|
} else {
|
@@ -476,7 +557,14 @@ function removeAtPath(document, rootNode, path) {
|
|
476
557
|
return pipe(getPointerFromPath(rootNode, path), result.flatMap(([node, key]) => removeFromNode(document, node, key)));
|
477
558
|
}
|
478
559
|
function isValFileValue(value) {
|
479
|
-
return !!(typeof value === "object" && value &&
|
560
|
+
return !!(typeof value === "object" && value &&
|
561
|
+
// TODO: replace the below with this:
|
562
|
+
// VAL_EXTENSION in value &&
|
563
|
+
// value[VAL_EXTENSION] === "file" &&
|
564
|
+
FILE_REF_PROP in value && typeof value[FILE_REF_PROP] === "string");
|
565
|
+
}
|
566
|
+
function isValRichTextValue(value) {
|
567
|
+
return !!(typeof value === "object" && value && VAL_EXTENSION in value && value[VAL_EXTENSION] === "richtext" && "children" in value && typeof value.children === "object" && Array.isArray(value.children));
|
480
568
|
}
|
481
569
|
function addToNode(document, node, key, value) {
|
482
570
|
if (ts.isArrayLiteralExpression(node)) {
|
@@ -660,7 +748,12 @@ const patchValFile = async (id, valConfigPath, patch, sourceFileHandler, runtime
|
|
660
748
|
// Buffer.from(content, "base64").toString("binary")
|
661
749
|
// );
|
662
750
|
// sourceFileHandler.moveFile(tempFilePath, "." + filePath);
|
663
|
-
|
751
|
+
// TODO: ensure that directory exists
|
752
|
+
if (content.startsWith("data:/image/svg+xml")) {
|
753
|
+
sourceFileHandler.writeFile("." + filePath, convertDataUrlToBase64(content).toString("utf8"), "utf8");
|
754
|
+
} else {
|
755
|
+
sourceFileHandler.writeFile("." + filePath, convertDataUrlToBase64(content).toString("binary"), "binary");
|
756
|
+
}
|
664
757
|
}
|
665
758
|
for (const [ref, patch] of Object.entries(derefRes.value.remotePatches)) {
|
666
759
|
throw Error(`Cannot update remote ${ref} with ${JSON.stringify(patch)}: not implemented`);
|
@@ -742,7 +835,7 @@ class ValSourceFileHandler {
|
|
742
835
|
|
743
836
|
const JsFileLookupMapping = [
|
744
837
|
// NOTE: first one matching will be used
|
745
|
-
[".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js"]]];
|
838
|
+
[".cjs.d.ts", [".esm.js", ".mjs.js"]], [".cjs.js", [".esm.js", ".mjs.js"]], [".cjs", [".mjs"]], [".d.ts", [".js", ".esm.js", ".mjs.js"]]];
|
746
839
|
class ValModuleLoader {
|
747
840
|
constructor(projectRoot, compilerOptions, sourceFileHandler, host = {
|
748
841
|
...ts.sys,
|
@@ -1007,6 +1100,10 @@ const OperationJSONT = z.discriminatedUnion("op", [z.object({
|
|
1007
1100
|
op: z.literal("test"),
|
1008
1101
|
path: z.string(),
|
1009
1102
|
value: JSONValueT
|
1103
|
+
}).strict(), z.object({
|
1104
|
+
op: z.literal("file"),
|
1105
|
+
path: z.string(),
|
1106
|
+
value: z.string()
|
1010
1107
|
}).strict()]);
|
1011
1108
|
const PatchJSON = z.array(OperationJSONT);
|
1012
1109
|
|
@@ -1741,7 +1838,7 @@ async function createFixPatch(config, apply, sourcePath, validationError) {
|
|
1741
1838
|
}
|
1742
1839
|
const localFile = path__default.join(config.projectRoot, maybeRef);
|
1743
1840
|
const buffer = fs.readFileSync(localFile);
|
1744
|
-
const sha256 = await getSHA256Hash(buffer);
|
1841
|
+
const sha256 = await Internal.getSHA256Hash(buffer);
|
1745
1842
|
const imageSize = sizeOf(buffer);
|
1746
1843
|
return {
|
1747
1844
|
...imageSize,
|
@@ -1833,11 +1930,5 @@ async function createFixPatch(config, apply, sourcePath, validationError) {
|
|
1833
1930
|
remainingErrors
|
1834
1931
|
};
|
1835
1932
|
}
|
1836
|
-
const getSHA256Hash = async bits => {
|
1837
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", bits);
|
1838
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
1839
|
-
const hash = hashArray.map(item => item.toString(16).padStart(2, "0")).join("");
|
1840
|
-
return hash;
|
1841
|
-
};
|
1842
1933
|
|
1843
1934
|
export { LocalValServer, PatchJSON, Service, ValFSHost, ValModuleLoader, ValSourceFileHandler, createFixPatch, createRequestHandler, createRequestListener, createService, decodeJwt, encodeJwt, formatSyntaxErrorTree, getCompilerOptions, getExpire, patchSourceFile, safeReadGit };
|
package/package.json
CHANGED
@@ -12,7 +12,7 @@
|
|
12
12
|
"./package.json": "./package.json"
|
13
13
|
},
|
14
14
|
"types": "dist/valbuild-server.cjs.d.ts",
|
15
|
-
"version": "0.
|
15
|
+
"version": "0.19.0",
|
16
16
|
"scripts": {
|
17
17
|
"typecheck": "tsc --noEmit",
|
18
18
|
"test": "jest",
|
@@ -25,8 +25,8 @@
|
|
25
25
|
"concurrently": "^7.6.0"
|
26
26
|
},
|
27
27
|
"dependencies": {
|
28
|
-
"@valbuild/core": "~0.
|
29
|
-
"@valbuild/ui": "~0.
|
28
|
+
"@valbuild/core": "~0.19.0",
|
29
|
+
"@valbuild/ui": "~0.19.0",
|
30
30
|
"express": "^4.18.2",
|
31
31
|
"image-size": "^1.0.2",
|
32
32
|
"quickjs-emscripten": "^0.21.1",
|
package/src/ValModuleLoader.ts
CHANGED
@@ -10,7 +10,7 @@ const JsFileLookupMapping: [resolvedFileExt: string, replacements: string[]][] =
|
|
10
10
|
[".cjs.d.ts", [".esm.js", ".mjs.js"]],
|
11
11
|
[".cjs.js", [".esm.js", ".mjs.js"]],
|
12
12
|
[".cjs", [".mjs"]],
|
13
|
-
[".d.ts", [".js"]],
|
13
|
+
[".d.ts", [".js", ".esm.js", ".mjs.js"]],
|
14
14
|
];
|
15
15
|
|
16
16
|
export const createModuleLoader = (
|
package/src/createFixPatch.ts
CHANGED
@@ -1,9 +1,13 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
FILE_REF_PROP,
|
3
|
+
Internal,
|
4
|
+
SourcePath,
|
5
|
+
ValidationError,
|
6
|
+
} from "@valbuild/core";
|
2
7
|
import { Patch, sourceToPatchPath } from "@valbuild/core/patch";
|
3
8
|
import sizeOf from "image-size";
|
4
9
|
import path from "path";
|
5
10
|
import fs from "fs";
|
6
|
-
import crypto from "crypto";
|
7
11
|
|
8
12
|
// TODO: find a better name? transformFixesToPatch?
|
9
13
|
export async function createFixPatch(
|
@@ -27,7 +31,7 @@ export async function createFixPatch(
|
|
27
31
|
}
|
28
32
|
const localFile = path.join(config.projectRoot, maybeRef);
|
29
33
|
const buffer = fs.readFileSync(localFile);
|
30
|
-
const sha256 = await getSHA256Hash(buffer);
|
34
|
+
const sha256 = await Internal.getSHA256Hash(buffer);
|
31
35
|
const imageSize = sizeOf(buffer);
|
32
36
|
return {
|
33
37
|
...imageSize,
|
@@ -164,12 +168,3 @@ export async function createFixPatch(
|
|
164
168
|
remainingErrors,
|
165
169
|
};
|
166
170
|
}
|
167
|
-
|
168
|
-
const getSHA256Hash = async (bits: Uint8Array) => {
|
169
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", bits);
|
170
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
171
|
-
const hash = hashArray
|
172
|
-
.map((item) => item.toString(16).padStart(2, "0"))
|
173
|
-
.join("");
|
174
|
-
return hash;
|
175
|
-
};
|
package/src/patch/ts/ops.test.ts
CHANGED
@@ -3,6 +3,7 @@ import { TSOps } from "./ops";
|
|
3
3
|
import { result, array, pipe } from "@valbuild/core/fp";
|
4
4
|
import { PatchError, JSONValue } from "@valbuild/core/patch";
|
5
5
|
import { ValSyntaxError } from "./syntax";
|
6
|
+
import { FILE_REF_PROP, VAL_EXTENSION } from "@valbuild/core";
|
6
7
|
|
7
8
|
function testSourceFile(expression: string): ts.SourceFile {
|
8
9
|
return ts.createSourceFile(
|
@@ -449,6 +450,106 @@ describe("TSOps", () => {
|
|
449
450
|
value: null,
|
450
451
|
expected: result.err(PatchError),
|
451
452
|
},
|
453
|
+
{
|
454
|
+
name: "val.richtext basic no nodes",
|
455
|
+
input: "val.richtext`Test`",
|
456
|
+
path: [],
|
457
|
+
value: {
|
458
|
+
[VAL_EXTENSION]: "richtext",
|
459
|
+
children: [{ tag: "p", children: ["Test 2"] }],
|
460
|
+
},
|
461
|
+
expected: result.ok("val.richtext `Test 2`"),
|
462
|
+
},
|
463
|
+
{
|
464
|
+
name: "val.richtext advanced no nodes",
|
465
|
+
input: "val.richtext`Test`",
|
466
|
+
path: [],
|
467
|
+
value: {
|
468
|
+
[VAL_EXTENSION]: "richtext",
|
469
|
+
children: [
|
470
|
+
{ tag: "h1", children: ["Title 1"] },
|
471
|
+
{ tag: "p", children: ["Test 2"] },
|
472
|
+
],
|
473
|
+
},
|
474
|
+
expected: result.ok(`val.richtext \`# Title 1
|
475
|
+
|
476
|
+
Test 2\``),
|
477
|
+
},
|
478
|
+
{
|
479
|
+
name: "val.richtext basic nodes",
|
480
|
+
input: "val.richtext`Test`",
|
481
|
+
path: [],
|
482
|
+
value: {
|
483
|
+
[VAL_EXTENSION]: "richtext",
|
484
|
+
children: [
|
485
|
+
{ tag: "h1", children: ["Title 1"] },
|
486
|
+
{ tag: "p", children: ["Test 2"] },
|
487
|
+
{
|
488
|
+
[VAL_EXTENSION]: "file",
|
489
|
+
[FILE_REF_PROP]: "/public/test",
|
490
|
+
metadata: { width: 100, height: 100, sha256: "123" },
|
491
|
+
},
|
492
|
+
],
|
493
|
+
},
|
494
|
+
expected: result.ok(`val.richtext \`# Title 1
|
495
|
+
|
496
|
+
Test 2
|
497
|
+
|
498
|
+
\${val.file("/public/test", { width: 100, height: 100, sha256: "123" })}\``),
|
499
|
+
},
|
500
|
+
{
|
501
|
+
name: "val.richtext advanced nodes",
|
502
|
+
input: "val.richtext`Test`",
|
503
|
+
path: [],
|
504
|
+
value: {
|
505
|
+
[VAL_EXTENSION]: "richtext",
|
506
|
+
children: [
|
507
|
+
{ tag: "h1", children: ["Title 1"] },
|
508
|
+
{
|
509
|
+
[VAL_EXTENSION]: "file",
|
510
|
+
[FILE_REF_PROP]: "/public/test1",
|
511
|
+
metadata: { width: 100, height: 100, sha256: "123" },
|
512
|
+
},
|
513
|
+
{
|
514
|
+
[VAL_EXTENSION]: "file",
|
515
|
+
[FILE_REF_PROP]: "/public/test2",
|
516
|
+
metadata: { width: 100, height: 100, sha256: "123" },
|
517
|
+
},
|
518
|
+
{ tag: "p", children: ["Test 2"] },
|
519
|
+
],
|
520
|
+
},
|
521
|
+
expected: result.ok(`val.richtext \`# Title 1
|
522
|
+
|
523
|
+
\${val.file("/public/test1", { width: 100, height: 100, sha256: "123" })}
|
524
|
+
\${val.file("/public/test2", { width: 100, height: 100, sha256: "123" })}
|
525
|
+
Test 2\``),
|
526
|
+
},
|
527
|
+
{
|
528
|
+
name: "val.richtext empty head",
|
529
|
+
input:
|
530
|
+
"{ text: val.richtext`${val.file('/public/test1', { height: 100 })}` }",
|
531
|
+
path: ["text"],
|
532
|
+
value: {
|
533
|
+
[VAL_EXTENSION]: "richtext",
|
534
|
+
children: [
|
535
|
+
{
|
536
|
+
[VAL_EXTENSION]: "file",
|
537
|
+
[FILE_REF_PROP]: "/public/test1",
|
538
|
+
metadata: { width: 100, height: 100, sha256: "123" },
|
539
|
+
},
|
540
|
+
{
|
541
|
+
[VAL_EXTENSION]: "file",
|
542
|
+
[FILE_REF_PROP]: "/public/test2",
|
543
|
+
metadata: { width: 100, height: 100, sha256: "123" },
|
544
|
+
},
|
545
|
+
{ tag: "p", children: ["Test 2"] },
|
546
|
+
],
|
547
|
+
},
|
548
|
+
expected:
|
549
|
+
result.ok(`{ text: val.richtext \`\${val.file("/public/test1", { width: 100, height: 100, sha256: "123" })}
|
550
|
+
\${val.file("/public/test2", { width: 100, height: 100, sha256: "123" })}
|
551
|
+
Test 2\` }`),
|
552
|
+
},
|
452
553
|
])("replace $name", ({ input, path, value, expected }) => {
|
453
554
|
const src = testSourceFile(input);
|
454
555
|
const ops = new TSOps(findRoot);
|
package/src/patch/ts/ops.ts
CHANGED
@@ -18,8 +18,15 @@ import {
|
|
18
18
|
JSONValue,
|
19
19
|
parseAndValidateArrayIndex,
|
20
20
|
} from "@valbuild/core/patch";
|
21
|
-
import {
|
21
|
+
import {
|
22
|
+
AnyRichTextOptions,
|
23
|
+
FILE_REF_PROP,
|
24
|
+
FileSource,
|
25
|
+
RichTextSource,
|
26
|
+
VAL_EXTENSION,
|
27
|
+
} from "@valbuild/core";
|
22
28
|
import { JsonPrimitive } from "@valbuild/core/src/Json";
|
29
|
+
import { richTextToTaggedStringTemplate } from "./richtext";
|
23
30
|
|
24
31
|
type TSOpsResult<T> = result.Result<T, PatchError | ValSyntaxErrorTree>;
|
25
32
|
|
@@ -60,14 +67,53 @@ function createPropertyAssignment(key: string, value: JSONValue) {
|
|
60
67
|
);
|
61
68
|
}
|
62
69
|
|
63
|
-
function createValFileReference(
|
70
|
+
function createValFileReference(value: FileSource) {
|
71
|
+
const args: ts.Expression[] = [
|
72
|
+
ts.factory.createStringLiteral(value[FILE_REF_PROP]),
|
73
|
+
];
|
74
|
+
if (value.metadata) {
|
75
|
+
args.push(toExpression(value.metadata));
|
76
|
+
}
|
77
|
+
|
64
78
|
return ts.factory.createCallExpression(
|
65
79
|
ts.factory.createPropertyAccessExpression(
|
66
80
|
ts.factory.createIdentifier("val"),
|
67
81
|
ts.factory.createIdentifier("file")
|
68
82
|
),
|
69
83
|
undefined,
|
70
|
-
|
84
|
+
args
|
85
|
+
);
|
86
|
+
}
|
87
|
+
|
88
|
+
function createValRichTextTaggedStringTemplate(
|
89
|
+
value: RichTextSource<AnyRichTextOptions>
|
90
|
+
): ts.Expression {
|
91
|
+
const [[head, ...others], nodes] = richTextToTaggedStringTemplate(value);
|
92
|
+
const tag = ts.factory.createPropertyAccessExpression(
|
93
|
+
ts.factory.createIdentifier("val"),
|
94
|
+
ts.factory.createIdentifier("richtext")
|
95
|
+
);
|
96
|
+
if (nodes.length > 0) {
|
97
|
+
return ts.factory.createTaggedTemplateExpression(
|
98
|
+
tag,
|
99
|
+
undefined,
|
100
|
+
ts.factory.createTemplateExpression(
|
101
|
+
ts.factory.createTemplateHead(head, head),
|
102
|
+
others.map((s, i) =>
|
103
|
+
ts.factory.createTemplateSpan(
|
104
|
+
toExpression(nodes[i]),
|
105
|
+
i < others.length - 1
|
106
|
+
? ts.factory.createTemplateMiddle(s, s)
|
107
|
+
: ts.factory.createTemplateTail(s, s)
|
108
|
+
)
|
109
|
+
)
|
110
|
+
)
|
111
|
+
);
|
112
|
+
}
|
113
|
+
return ts.factory.createTaggedTemplateExpression(
|
114
|
+
tag,
|
115
|
+
undefined,
|
116
|
+
ts.factory.createNoSubstitutionTemplateLiteral(head, head)
|
71
117
|
);
|
72
118
|
}
|
73
119
|
|
@@ -85,7 +131,9 @@ function toExpression(value: JSONValue): ts.Expression {
|
|
85
131
|
return ts.factory.createArrayLiteralExpression(value.map(toExpression));
|
86
132
|
} else if (typeof value === "object") {
|
87
133
|
if (isValFileValue(value)) {
|
88
|
-
return createValFileReference(value
|
134
|
+
return createValFileReference(value);
|
135
|
+
} else if (isValRichTextValue(value)) {
|
136
|
+
return createValRichTextTaggedStringTemplate(value);
|
89
137
|
}
|
90
138
|
return ts.factory.createObjectLiteralExpression(
|
91
139
|
Object.entries(value).map(([key, value]) =>
|
@@ -549,11 +597,28 @@ function isValFileValue(value: JSONValue): value is FileSource<{
|
|
549
597
|
return !!(
|
550
598
|
typeof value === "object" &&
|
551
599
|
value &&
|
600
|
+
// TODO: replace the below with this:
|
601
|
+
// VAL_EXTENSION in value &&
|
602
|
+
// value[VAL_EXTENSION] === "file" &&
|
552
603
|
FILE_REF_PROP in value &&
|
553
604
|
typeof value[FILE_REF_PROP] === "string"
|
554
605
|
);
|
555
606
|
}
|
556
607
|
|
608
|
+
function isValRichTextValue(
|
609
|
+
value: JSONValue
|
610
|
+
): value is RichTextSource<AnyRichTextOptions> {
|
611
|
+
return !!(
|
612
|
+
typeof value === "object" &&
|
613
|
+
value &&
|
614
|
+
VAL_EXTENSION in value &&
|
615
|
+
value[VAL_EXTENSION] === "richtext" &&
|
616
|
+
"children" in value &&
|
617
|
+
typeof value.children === "object" &&
|
618
|
+
Array.isArray(value.children)
|
619
|
+
);
|
620
|
+
}
|
621
|
+
|
557
622
|
function addToNode(
|
558
623
|
document: ts.SourceFile,
|
559
624
|
node: ts.Expression,
|
@@ -0,0 +1,74 @@
|
|
1
|
+
import { initVal } from "@valbuild/core";
|
2
|
+
import { richTextToTaggedStringTemplate } from "./richtext";
|
3
|
+
|
4
|
+
describe("patch richtext", () => {
|
5
|
+
test("basic richtext <-> markdown", () => {
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
7
|
+
const input: any = {
|
8
|
+
_type: "richtext",
|
9
|
+
children: [
|
10
|
+
{ tag: "h1", children: ["Title 1"] },
|
11
|
+
{
|
12
|
+
tag: "p",
|
13
|
+
children: [
|
14
|
+
{
|
15
|
+
tag: "span",
|
16
|
+
classes: ["bold"],
|
17
|
+
children: ["Bold text"],
|
18
|
+
},
|
19
|
+
],
|
20
|
+
},
|
21
|
+
{ _type: "file", _ref: "/public/image.png" },
|
22
|
+
{ tag: "p", children: ["Paragraph 2"] },
|
23
|
+
],
|
24
|
+
};
|
25
|
+
const r = richTextToTaggedStringTemplate(input);
|
26
|
+
expect(r).toStrictEqual([
|
27
|
+
["# Title 1\n\n**Bold text**\n\n", "\nParagraph 2"],
|
28
|
+
[{ _type: "file", _ref: "/public/image.png" }],
|
29
|
+
]);
|
30
|
+
|
31
|
+
const { val } = initVal();
|
32
|
+
const r2 = val.richtext(
|
33
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
34
|
+
// @ts-expect-error
|
35
|
+
r[0],
|
36
|
+
...r[1]
|
37
|
+
);
|
38
|
+
expect(r2).toStrictEqual(input);
|
39
|
+
});
|
40
|
+
|
41
|
+
test("richtext <-> markdown", () => {
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
43
|
+
const input: any = {
|
44
|
+
_type: "richtext",
|
45
|
+
children: [
|
46
|
+
{ tag: "h1", children: ["Title 1"] },
|
47
|
+
{ tag: "h2", children: ["Title 2"] },
|
48
|
+
{
|
49
|
+
tag: "p",
|
50
|
+
children: [
|
51
|
+
"Test 1 2 3. ",
|
52
|
+
{
|
53
|
+
tag: "span",
|
54
|
+
classes: ["bold"],
|
55
|
+
children: ["Bold text"],
|
56
|
+
},
|
57
|
+
". More text. See image below",
|
58
|
+
],
|
59
|
+
},
|
60
|
+
{ _type: "file", _ref: "/public/image.png" },
|
61
|
+
{ tag: "p", children: ["Paragraph 2"] },
|
62
|
+
],
|
63
|
+
};
|
64
|
+
const r = richTextToTaggedStringTemplate(input);
|
65
|
+
const { val } = initVal();
|
66
|
+
const r2 = val.richtext(
|
67
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
68
|
+
// @ts-expect-error
|
69
|
+
r[0],
|
70
|
+
...r[1]
|
71
|
+
);
|
72
|
+
expect(r2).toStrictEqual(input);
|
73
|
+
});
|
74
|
+
});
|
@@ -0,0 +1,73 @@
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
2
|
+
import { AnyRichTextOptions, RichTextSource } from "@valbuild/core";
|
3
|
+
|
4
|
+
const HeaderRegEx = /h([\d])/;
|
5
|
+
|
6
|
+
export function richTextToTaggedStringTemplate(
|
7
|
+
source: RichTextSource<AnyRichTextOptions>
|
8
|
+
) {
|
9
|
+
const texts: string[] = [""];
|
10
|
+
const nodes: any[] = [];
|
11
|
+
let didAppendNewLines = false;
|
12
|
+
|
13
|
+
function rec(node: any) {
|
14
|
+
if (typeof node === "string") {
|
15
|
+
texts[texts.length - 1] += node;
|
16
|
+
} else if (node.tag) {
|
17
|
+
if (node.tag?.startsWith("h")) {
|
18
|
+
const [, depth] = node.tag.match(HeaderRegEx);
|
19
|
+
for (let i = 0; i < Number(depth); i++) {
|
20
|
+
texts[texts.length - 1] += "#";
|
21
|
+
}
|
22
|
+
texts[texts.length - 1] += " ";
|
23
|
+
} else if (node.tag === "span") {
|
24
|
+
if (node.classes.includes("bold") && !node.classes.includes("italic")) {
|
25
|
+
texts[texts.length - 1] += "**";
|
26
|
+
}
|
27
|
+
if (node.classes.includes("italic") && !node.classes.includes("bold")) {
|
28
|
+
texts[texts.length - 1] += "*";
|
29
|
+
}
|
30
|
+
if (node.classes.includes("italic") && node.classes.includes("bold")) {
|
31
|
+
texts[texts.length - 1] += "***";
|
32
|
+
}
|
33
|
+
if (node.classes.includes("line-through")) {
|
34
|
+
texts[texts.length - 1] += "~~";
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
node.children?.forEach(rec);
|
39
|
+
|
40
|
+
if (node.tag === "span") {
|
41
|
+
didAppendNewLines = false;
|
42
|
+
if (node.classes.includes("line-through")) {
|
43
|
+
texts[texts.length - 1] += "~~";
|
44
|
+
}
|
45
|
+
if (node.classes.includes("italic") && node.classes.includes("bold")) {
|
46
|
+
texts[texts.length - 1] += "***";
|
47
|
+
}
|
48
|
+
if (node.classes.includes("italic") && !node.classes.includes("bold")) {
|
49
|
+
texts[texts.length - 1] += "*";
|
50
|
+
}
|
51
|
+
if (node.classes.includes("bold") && !node.classes.includes("italic")) {
|
52
|
+
texts[texts.length - 1] += "**";
|
53
|
+
}
|
54
|
+
} else if (node.tag === "p") {
|
55
|
+
didAppendNewLines = true;
|
56
|
+
texts[texts.length - 1] += "\n\n";
|
57
|
+
} else if (node.tag?.startsWith("h")) {
|
58
|
+
didAppendNewLines = true;
|
59
|
+
texts[texts.length - 1] += "\n\n";
|
60
|
+
}
|
61
|
+
} else {
|
62
|
+
nodes.push(node);
|
63
|
+
texts.push("\n");
|
64
|
+
}
|
65
|
+
}
|
66
|
+
source.children.forEach(rec);
|
67
|
+
|
68
|
+
if (texts[texts.length - 1] && didAppendNewLines) {
|
69
|
+
// remove last \n\n
|
70
|
+
texts[texts.length - 1] = texts[texts.length - 1].slice(0, -2);
|
71
|
+
}
|
72
|
+
return [texts, nodes];
|
73
|
+
}
|
package/src/patch/validation.ts
CHANGED
@@ -67,6 +67,13 @@ const OperationJSONT: z.ZodType<OperationJSONT> = z.discriminatedUnion("op", [
|
|
67
67
|
value: JSONValueT,
|
68
68
|
})
|
69
69
|
.strict(),
|
70
|
+
z
|
71
|
+
.object({
|
72
|
+
op: z.literal("file"),
|
73
|
+
path: z.string(),
|
74
|
+
value: z.string(),
|
75
|
+
})
|
76
|
+
.strict(),
|
70
77
|
]);
|
71
78
|
|
72
79
|
export const PatchJSON: z.ZodType<PatchJSONT> = z.array(OperationJSONT);
|
package/src/patchValFile.ts
CHANGED
@@ -69,11 +69,20 @@ export const patchValFile = async (
|
|
69
69
|
// Buffer.from(content, "base64").toString("binary")
|
70
70
|
// );
|
71
71
|
// sourceFileHandler.moveFile(tempFilePath, "." + filePath);
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
72
|
+
// TODO: ensure that directory exists
|
73
|
+
if (content.startsWith("data:/image/svg+xml")) {
|
74
|
+
sourceFileHandler.writeFile(
|
75
|
+
"." + filePath,
|
76
|
+
convertDataUrlToBase64(content).toString("utf8"),
|
77
|
+
"utf8"
|
78
|
+
);
|
79
|
+
} else {
|
80
|
+
sourceFileHandler.writeFile(
|
81
|
+
"." + filePath,
|
82
|
+
convertDataUrlToBase64(content).toString("binary"),
|
83
|
+
"binary"
|
84
|
+
);
|
85
|
+
}
|
77
86
|
}
|
78
87
|
|
79
88
|
for (const [ref, patch] of Object.entries(derefRes.value.remotePatches)) {
|