@examplary/cli 1.13.1 → 1.15.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/README.md +4 -0
- package/dist/commands/upload.js +37 -13
- package/dist/lib/bundle.d.ts +6 -0
- package/dist/lib/bundle.js +28 -1
- package/dist/lib/validate.d.ts +2 -0
- package/dist/lib/validate.js +30 -2
- package/dist/preview-ui/assets/icon-nodes-CxaTPv-R.js.map +1 -1
- package/dist/preview-ui/assets/index-b38eDKFI.js +3663 -0
- package/dist/preview-ui/assets/{index-CQu7xfsN.js.map → index-b38eDKFI.js.map} +1 -1
- package/dist/preview-ui/assets/index-boullcXq.css +1 -0
- package/dist/preview-ui/assets/tags-BUvo71XG.js.map +1 -1
- package/dist/preview-ui/index.html +2 -2
- package/package.json +11 -11
- package/dist/preview-ui/assets/index-CQu7xfsN.js +0 -3637
- package/dist/preview-ui/assets/index-qyJhpejV.css +0 -1
package/README.md
CHANGED
|
@@ -25,6 +25,10 @@ You'll be required to pass a `--key` argument with your API key, which you can
|
|
|
25
25
|
obtain from your Examplary account settings. You can also set the `EXAMPLARY_API_KEY`
|
|
26
26
|
environment variable to avoid passing the key every time.
|
|
27
27
|
|
|
28
|
+
A `README.md` and `preview.png` in the same directory are uploaded as the question type's
|
|
29
|
+
documentation and preview image, along with any translations named by language code, like
|
|
30
|
+
`README.nl.md` or `preview.nl.png`.
|
|
31
|
+
|
|
28
32
|
## Validating
|
|
29
33
|
|
|
30
34
|
To run all of the pre-upload checks without uploading anything, use:
|
package/dist/commands/upload.js
CHANGED
|
@@ -1,10 +1,29 @@
|
|
|
1
1
|
#! /usr/bin/env node
|
|
2
|
-
import { readFile } from "fs/promises";
|
|
2
|
+
import { readdir, readFile } from "fs/promises";
|
|
3
3
|
import { join } from "path";
|
|
4
4
|
import { cwd, exit } from "process";
|
|
5
5
|
import { setApiHost, setApiKey, uploadFile } from "../lib/api.js";
|
|
6
6
|
import { readJsonOrYamlFile, readMetaFile } from "../lib/read-meta.js";
|
|
7
7
|
import { isValid, printValidationResult, validateDefinition, } from "../lib/validate.js";
|
|
8
|
+
/**
|
|
9
|
+
* Uploads e.g. `README.md` (English) and its translations like `README.nl.md`,
|
|
10
|
+
* returning their URLs keyed by language code.
|
|
11
|
+
*/
|
|
12
|
+
const uploadLocalizedFiles = async (dir, baseName, extension, contentType) => {
|
|
13
|
+
const pattern = new RegExp(`^${baseName}(?:\\.([a-z]{2}))?\\.${extension}$`);
|
|
14
|
+
const urls = {};
|
|
15
|
+
for (const file of await readdir(dir)) {
|
|
16
|
+
const match = file.match(pattern);
|
|
17
|
+
if (!match)
|
|
18
|
+
continue;
|
|
19
|
+
const contents = await readFile(join(dir, file));
|
|
20
|
+
if (!contents.length)
|
|
21
|
+
continue;
|
|
22
|
+
urls[match[1] || "en"] = await uploadFile(file, contents, contentType);
|
|
23
|
+
console.log(`• Uploaded ${file}`);
|
|
24
|
+
}
|
|
25
|
+
return Object.keys(urls).length ? urls : undefined;
|
|
26
|
+
};
|
|
8
27
|
export const uploadCommand = async (argv) => {
|
|
9
28
|
setApiHost(argv.host);
|
|
10
29
|
setApiKey(argv.key);
|
|
@@ -38,6 +57,19 @@ export const uploadCommand = async (argv) => {
|
|
|
38
57
|
console.log(`• Uploaded component ${componentType}`);
|
|
39
58
|
}
|
|
40
59
|
definition.components = components;
|
|
60
|
+
// Upload the bundled functions
|
|
61
|
+
const functions = {};
|
|
62
|
+
for (const [functionName, output] of Object.entries(result.functions)) {
|
|
63
|
+
// Upload source map first if it exists
|
|
64
|
+
let sourceMapStatement = "";
|
|
65
|
+
if (output.sourcemap) {
|
|
66
|
+
const url = await uploadFile(`function-${functionName}.js.map`, output.sourcemap, "application/json");
|
|
67
|
+
sourceMapStatement = `\n//# sourceMappingURL=${url}`;
|
|
68
|
+
}
|
|
69
|
+
functions[functionName] = await uploadFile(`function-${functionName}.js`, output.js + sourceMapStatement, "application/javascript");
|
|
70
|
+
console.log(`• Uploaded function ${functionName}`);
|
|
71
|
+
}
|
|
72
|
+
definition.functions = functions;
|
|
41
73
|
// Upload styles if present
|
|
42
74
|
if (result.styles) {
|
|
43
75
|
definition.stylesheet = await uploadFile("styles.css", result.styles, "text/css");
|
|
@@ -49,18 +81,10 @@ export const uploadCommand = async (argv) => {
|
|
|
49
81
|
definition.icon = await uploadFile("icon.svg", result.iconContents, "image/svg+xml");
|
|
50
82
|
console.log(`• Uploaded icon`);
|
|
51
83
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
console.log(`• Uploaded README`);
|
|
57
|
-
}
|
|
58
|
-
// Upload the preview image if present
|
|
59
|
-
const previewImage = await readFile(join(dir, "preview.png")).catch(() => null);
|
|
60
|
-
if (previewImage) {
|
|
61
|
-
definition.previewImage = await uploadFile("preview.png", previewImage, "image/png");
|
|
62
|
-
console.log(`• Uploaded preview image`);
|
|
63
|
-
}
|
|
84
|
+
definition.readmeUrls = await uploadLocalizedFiles(dir, "README", "md", "text/markdown");
|
|
85
|
+
definition.readme = definition.readmeUrls?.en;
|
|
86
|
+
definition.previewImageUrls = await uploadLocalizedFiles(dir, "preview", "png", "image/png");
|
|
87
|
+
definition.previewImage = definition.previewImageUrls?.en;
|
|
64
88
|
// Upload the preview data if present, always converted to JSON
|
|
65
89
|
try {
|
|
66
90
|
const preview = await readJsonOrYamlFile(dir, "preview");
|
package/dist/lib/bundle.d.ts
CHANGED
package/dist/lib/bundle.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import { createHash } from "crypto";
|
|
1
2
|
import { build } from "esbuild";
|
|
3
|
+
const calculateHash = (compiled) => {
|
|
4
|
+
return createHash("sha256").update(compiled).digest("hex");
|
|
5
|
+
};
|
|
2
6
|
export const buildComponent = async (file) => {
|
|
3
7
|
const res = await build({
|
|
4
8
|
entryPoints: [file],
|
|
@@ -23,5 +27,28 @@ export const buildComponent = async (file) => {
|
|
|
23
27
|
sourcemap = Buffer.from(js.split(sourceMapStatement)[1].trim(), "base64").toString("utf-8");
|
|
24
28
|
js = js.split(sourceMapStatement)[0].trim();
|
|
25
29
|
}
|
|
26
|
-
|
|
30
|
+
const sha256 = calculateHash(js);
|
|
31
|
+
return { js, sourcemap, sha256 };
|
|
32
|
+
};
|
|
33
|
+
export const buildFunction = async (file) => {
|
|
34
|
+
const res = await build({
|
|
35
|
+
entryPoints: [file],
|
|
36
|
+
bundle: true,
|
|
37
|
+
write: false,
|
|
38
|
+
minify: true,
|
|
39
|
+
sourcemap: "inline",
|
|
40
|
+
platform: "neutral",
|
|
41
|
+
mainFields: ["module", "main"],
|
|
42
|
+
format: "iife",
|
|
43
|
+
globalName: "userFunction",
|
|
44
|
+
});
|
|
45
|
+
let js = res.outputFiles[0].text;
|
|
46
|
+
let sourcemap = "";
|
|
47
|
+
const sourceMapStatement = "\n//# sourceMappingURL=data:application/json;base64,";
|
|
48
|
+
if (js.includes(sourceMapStatement)) {
|
|
49
|
+
sourcemap = Buffer.from(js.split(sourceMapStatement)[1].trim(), "base64").toString("utf-8");
|
|
50
|
+
js = js.split(sourceMapStatement)[0].trim();
|
|
51
|
+
}
|
|
52
|
+
const sha256 = calculateHash(js);
|
|
53
|
+
return { js, sourcemap, sha256 };
|
|
27
54
|
};
|
package/dist/lib/validate.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export type ValidationResult = {
|
|
|
18
18
|
styles?: string;
|
|
19
19
|
/** Raw icon file contents, present only when an icon is configured. */
|
|
20
20
|
iconContents?: string;
|
|
21
|
+
/** Bundled function output, keyed by function name, ready to upload. */
|
|
22
|
+
functions: Record<string, BuiltComponent>;
|
|
21
23
|
};
|
|
22
24
|
/**
|
|
23
25
|
* Runs every check the `upload` command performs, short of actually uploading:
|
package/dist/lib/validate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFile } from "fs/promises";
|
|
2
2
|
import { resolve } from "path";
|
|
3
|
-
import { buildComponent } from "./bundle.js";
|
|
3
|
+
import { buildComponent, buildFunction } from "./bundle.js";
|
|
4
4
|
import { deepCompileJsonata } from "./jsonata.js";
|
|
5
5
|
import { runResponseProcessingTestCases, } from "./response-processing.js";
|
|
6
6
|
import { buildStyles } from "./styles.js";
|
|
@@ -20,6 +20,7 @@ export const validateDefinition = async (definition, dir) => {
|
|
|
20
20
|
const errors = [];
|
|
21
21
|
const warnings = [];
|
|
22
22
|
const bundles = {};
|
|
23
|
+
const functions = {};
|
|
23
24
|
let styles;
|
|
24
25
|
let iconContents;
|
|
25
26
|
if (!definition.id)
|
|
@@ -89,6 +90,21 @@ export const validateDefinition = async (definition, dir) => {
|
|
|
89
90
|
errors.push(`Error bundling component ${componentType} from ${componentPath}: ${error.message}`);
|
|
90
91
|
}
|
|
91
92
|
}
|
|
93
|
+
// Bundle functions to confirm they build and to capture their output
|
|
94
|
+
for (const [functionName, functionPath] of Object.entries(definition.functions || {})) {
|
|
95
|
+
const realPath = resolve(dir, functionPath);
|
|
96
|
+
try {
|
|
97
|
+
const output = await buildFunction(realPath);
|
|
98
|
+
functions[functionName] = output;
|
|
99
|
+
const sizeInKb = output.js.length / 1000;
|
|
100
|
+
if (sizeInKb > 100) {
|
|
101
|
+
errors.push(`Function ${functionName} is larger than the 100 kB limit (${sizeInKb.toFixed(2)} kB)`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
errors.push(`Error bundling function ${functionName} from ${functionPath}: ${error.message}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
92
108
|
// Build styles if any components are present
|
|
93
109
|
if (Object.keys(bundles).length) {
|
|
94
110
|
styles = (await buildStyles()) ?? undefined;
|
|
@@ -106,7 +122,15 @@ export const validateDefinition = async (definition, dir) => {
|
|
|
106
122
|
errors.push(`Icon file is empty: ${iconPath}`);
|
|
107
123
|
}
|
|
108
124
|
}
|
|
109
|
-
return {
|
|
125
|
+
return {
|
|
126
|
+
errors,
|
|
127
|
+
warnings,
|
|
128
|
+
testCaseResults,
|
|
129
|
+
bundles,
|
|
130
|
+
functions,
|
|
131
|
+
styles,
|
|
132
|
+
iconContents,
|
|
133
|
+
};
|
|
110
134
|
};
|
|
111
135
|
/** Whether the definition is safe to upload. */
|
|
112
136
|
export const isValid = (result) => result.errors.length === 0 && result.testCaseResults.every((r) => r.passed);
|
|
@@ -116,6 +140,10 @@ export const printValidationResult = (result) => {
|
|
|
116
140
|
const sizeInKb = (output.js.length / 1000).toFixed(2);
|
|
117
141
|
console.log(`• Bundled component ${componentType} (${sizeInKb} kB)`);
|
|
118
142
|
}
|
|
143
|
+
for (const [functionName, output] of Object.entries(result.functions)) {
|
|
144
|
+
const sizeInKb = (output.js.length / 1000).toFixed(2);
|
|
145
|
+
console.log(`• Bundled function ${functionName} (${sizeInKb} kB)`);
|
|
146
|
+
}
|
|
119
147
|
for (const warning of result.warnings) {
|
|
120
148
|
console.warn(` ⚠️ ${warning.message}`);
|
|
121
149
|
if (warning.file && process.env.GITHUB_ACTIONS) {
|