@examplary/cli 1.7.0 → 1.9.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/commands/preview.d.ts +5 -0
- package/dist/commands/preview.js +174 -0
- package/dist/commands/upload.d.ts +5 -0
- package/dist/commands/upload.js +138 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +37 -0
- package/dist/lib/api.d.ts +5 -0
- package/dist/lib/api.js +47 -0
- package/dist/lib/bundle.d.ts +4 -0
- package/dist/lib/bundle.js +27 -0
- package/dist/lib/jsonata.d.ts +1 -0
- package/dist/lib/jsonata.js +26 -0
- package/dist/lib/read-meta.d.ts +16 -0
- package/dist/lib/read-meta.js +36 -0
- package/dist/lib/styles.d.ts +1 -0
- package/dist/lib/styles.js +24 -0
- package/dist/preview-ui/assets/icon-nodes-Ds7bJID4.js +2 -0
- package/dist/preview-ui/assets/icon-nodes-Ds7bJID4.js.map +1 -0
- package/dist/preview-ui/assets/index-DcEcr1Zm.css +1 -0
- package/dist/preview-ui/assets/index-NlxMWEJQ.js +3629 -0
- package/dist/preview-ui/assets/index-NlxMWEJQ.js.map +1 -0
- package/dist/preview-ui/assets/tags-DZBm0mOh.js +2 -0
- package/dist/preview-ui/assets/tags-DZBm0mOh.js.map +1 -0
- package/{src → dist}/preview-ui/index.html +2 -1
- package/package.json +23 -22
- package/bin/index.js +0 -37
- package/src/commands/preview.js +0 -220
- package/src/commands/upload.js +0 -179
- package/src/lib/api.js +0 -53
- package/src/lib/bundle.js +0 -35
- package/src/lib/jsonata.js +0 -31
- package/src/lib/middleware.js +0 -16
- package/src/lib/read-meta.js +0 -40
- package/src/lib/styles.js +0 -32
- package/src/preview-ui/App.tsx +0 -148
- package/src/preview-ui/components/json-editor.tsx +0 -27
- package/src/preview-ui/components/preview-assessment.tsx +0 -43
- package/src/preview-ui/components/preview-print.tsx +0 -38
- package/src/preview-ui/components/preview-results.tsx +0 -38
- package/src/preview-ui/components/preview-settings-area.tsx +0 -97
- package/src/preview-ui/components/question-component.tsx +0 -75
- package/src/preview-ui/index.css +0 -11
- package/src/preview-ui/lib/demo-api.ts +0 -29
- package/src/preview-ui/lib/i18n.ts +0 -15
- package/src/preview-ui/main.tsx +0 -8
|
@@ -11,10 +11,11 @@
|
|
|
11
11
|
href="https://fonts.googleapis.com/css2?family=Epunda+Sans:ital,wght@0,300..900;1,300..900&family=Epunda+Slab:ital,wght@0,300..900;1,300..900&family=Inconsolata:wght@200..900&display=swap"
|
|
12
12
|
rel="stylesheet"
|
|
13
13
|
/>
|
|
14
|
+
<script type="module" crossorigin src="/assets/index-NlxMWEJQ.js"></script>
|
|
15
|
+
<link rel="stylesheet" crossorigin href="/assets/index-DcEcr1Zm.css">
|
|
14
16
|
</head>
|
|
15
17
|
<body class="bg-app-bg font-sans">
|
|
16
18
|
<div id="root"></div>
|
|
17
|
-
<script type="module" src="/main.tsx"></script>
|
|
18
19
|
<link href="/api/stylesheet" rel="stylesheet" />
|
|
19
20
|
</body>
|
|
20
21
|
</html>
|
package/package.json
CHANGED
|
@@ -2,23 +2,22 @@
|
|
|
2
2
|
"name": "@examplary/cli",
|
|
3
3
|
"description": "Command-line tools for building and publishing Examplary question types.",
|
|
4
4
|
"packageManager": "yarn@4.8.1",
|
|
5
|
-
"version": "1.
|
|
6
|
-
"type": "module",
|
|
5
|
+
"version": "1.9.0",
|
|
7
6
|
"bin": {
|
|
8
|
-
"exp": "./
|
|
7
|
+
"exp": "./dist/index.js"
|
|
9
8
|
},
|
|
10
9
|
"scripts": {
|
|
11
|
-
"release": "semantic-release -e semantic-release-monorepo"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
"
|
|
10
|
+
"release": "semantic-release -e semantic-release-monorepo",
|
|
11
|
+
"build:preview-ui": "vite build",
|
|
12
|
+
"build:js": "tsc --project tsconfig.json",
|
|
13
|
+
"build": "yarn build:js && yarn build:preview-ui",
|
|
14
|
+
"dev": "tsx scripts/dev.ts"
|
|
15
15
|
},
|
|
16
16
|
"publishConfig": {
|
|
17
17
|
"access": "public"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
|
-
"
|
|
21
|
-
"src"
|
|
20
|
+
"dist"
|
|
22
21
|
],
|
|
23
22
|
"dependencies": {
|
|
24
23
|
"@examplary/schemas": "*",
|
|
@@ -26,30 +25,32 @@
|
|
|
26
25
|
"@hono/node-server": "^1.19.13",
|
|
27
26
|
"@tailwindcss/postcss": "^4.1.14",
|
|
28
27
|
"@tailwindcss/vite": "^4.2.1",
|
|
29
|
-
"@vitejs/plugin-react": "^6.0.1",
|
|
30
28
|
"esbuild": "^0.27.1",
|
|
31
|
-
"hono": "^4.12.
|
|
32
|
-
"i18next": "^25.3.2",
|
|
33
|
-
"i18next-browser-languagedetector": "^8.2.0",
|
|
29
|
+
"hono": "^4.12.18",
|
|
34
30
|
"jsonata": "^2.1.0",
|
|
35
31
|
"lucide-react": "^0.535.0",
|
|
36
32
|
"open": "^11.0.0",
|
|
37
|
-
"postcss": "^8.5.10",
|
|
38
|
-
"react": "^19.2.4",
|
|
39
|
-
"react-dom": "^19.2.4",
|
|
40
|
-
"react-error-boundary": "^6.0.3",
|
|
41
|
-
"react-i18next": "^16.0.0",
|
|
42
|
-
"swr": "^2.3.6",
|
|
43
33
|
"tailwindcss": "^4.2.1",
|
|
44
|
-
"use-local-storage": "^3.0.0",
|
|
45
|
-
"vite": "^8.0.5",
|
|
46
34
|
"ws": "^8.18.3",
|
|
47
35
|
"yaml": "^2.8.3",
|
|
48
36
|
"yargs": "^18.0.0"
|
|
49
37
|
},
|
|
50
38
|
"devDependencies": {
|
|
51
39
|
"@types/node": "^24.1.0",
|
|
52
|
-
"
|
|
40
|
+
"@types/yargs": "^17.0.35",
|
|
41
|
+
"@vitejs/plugin-react": "^6.0.1",
|
|
42
|
+
"i18next": "^26.2.0",
|
|
43
|
+
"i18next-browser-languagedetector": "^8.2.0",
|
|
44
|
+
"postcss": "^8.5.10",
|
|
45
|
+
"react": "^19.2.6",
|
|
46
|
+
"react-dom": "^19.2.6",
|
|
47
|
+
"react-error-boundary": "^6.0.3",
|
|
48
|
+
"react-i18next": "^17.0.8",
|
|
49
|
+
"swr": "^2.3.6",
|
|
50
|
+
"tsx": "^4.22.1",
|
|
51
|
+
"typescript": "^6.0.3",
|
|
52
|
+
"use-local-storage": "^3.0.0",
|
|
53
|
+
"vite": "^8.0.5"
|
|
53
54
|
},
|
|
54
55
|
"homepage": "https://examplary.ai/developers",
|
|
55
56
|
"author": {
|
package/bin/index.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env node
|
|
2
|
-
import yargs from "yargs";
|
|
3
|
-
import { hideBin } from "yargs/helpers";
|
|
4
|
-
|
|
5
|
-
import { previewCommand } from "../src/commands/preview.js";
|
|
6
|
-
import { uploadCommand } from "../src/commands/upload.js";
|
|
7
|
-
|
|
8
|
-
yargs()
|
|
9
|
-
.usage("examplary <cmd> [args]")
|
|
10
|
-
|
|
11
|
-
.command(
|
|
12
|
-
"upload",
|
|
13
|
-
"Bundle and upload a question type definition",
|
|
14
|
-
(yargs) => {
|
|
15
|
-
yargs
|
|
16
|
-
.option("key", {
|
|
17
|
-
type: "string",
|
|
18
|
-
description:
|
|
19
|
-
"API key for authentication (uses EXAMPLARY_API_KEY env var if not provided)",
|
|
20
|
-
default: process.env.EXAMPLARY_API_KEY || "",
|
|
21
|
-
})
|
|
22
|
-
.option("host", {
|
|
23
|
-
type: "string",
|
|
24
|
-
description: "API host to upload to",
|
|
25
|
-
default: "https://api.examplary.ai",
|
|
26
|
-
});
|
|
27
|
-
},
|
|
28
|
-
uploadCommand,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
.command("preview", "Run local development server", previewCommand)
|
|
32
|
-
|
|
33
|
-
.help()
|
|
34
|
-
.recommendCommands()
|
|
35
|
-
.demandCommand()
|
|
36
|
-
|
|
37
|
-
.parse(hideBin(process.argv));
|
package/src/commands/preview.js
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env node
|
|
2
|
-
import { watch } from "fs";
|
|
3
|
-
import { readFile } from "fs/promises";
|
|
4
|
-
import { createServer as createHttpServer } from "http";
|
|
5
|
-
import path, { resolve } from "path";
|
|
6
|
-
import { cwd, exit } from "process";
|
|
7
|
-
|
|
8
|
-
import { serve } from "@hono/node-server";
|
|
9
|
-
import tailwindcss from "@tailwindcss/vite";
|
|
10
|
-
import react from "@vitejs/plugin-react";
|
|
11
|
-
import { Hono } from "hono";
|
|
12
|
-
import open from "open";
|
|
13
|
-
import { createServer } from "vite";
|
|
14
|
-
import { WebSocketServer } from "ws";
|
|
15
|
-
|
|
16
|
-
import { setApiHost, setApiKey } from "../lib/api.js";
|
|
17
|
-
import { buildComponent } from "../lib/bundle.js";
|
|
18
|
-
import { adaptExpressMiddleware } from "../lib/middleware.js";
|
|
19
|
-
import { readMetaFile } from "../lib/read-meta.js";
|
|
20
|
-
import { buildStyles } from "../lib/styles.js";
|
|
21
|
-
|
|
22
|
-
export const previewCommand = async (argv) => {
|
|
23
|
-
setApiHost(argv.host);
|
|
24
|
-
setApiKey(argv.key);
|
|
25
|
-
|
|
26
|
-
const dir = cwd();
|
|
27
|
-
|
|
28
|
-
// Verify a question-type file exists (json or yml)
|
|
29
|
-
try {
|
|
30
|
-
await readMetaFile();
|
|
31
|
-
} catch (e) {
|
|
32
|
-
console.error(`🚫 ${e.message}`);
|
|
33
|
-
exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Let's start a Vite server to preview the question type
|
|
37
|
-
const root = resolve(import.meta.dirname, "..", "preview-ui");
|
|
38
|
-
const app = new Hono();
|
|
39
|
-
const vite = await createServer({
|
|
40
|
-
server: { middlewareMode: true },
|
|
41
|
-
mode: "development",
|
|
42
|
-
appType: "custom",
|
|
43
|
-
root,
|
|
44
|
-
plugins: [react(), tailwindcss()],
|
|
45
|
-
resolve: {
|
|
46
|
-
alias: {
|
|
47
|
-
react: path.dirname(import.meta.resolve("react")),
|
|
48
|
-
"react-dom": path.dirname(import.meta.resolve("react-dom")),
|
|
49
|
-
},
|
|
50
|
-
},
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
app.get("/api/bundle", async (c) => {
|
|
54
|
-
const path = c.req.query("path");
|
|
55
|
-
const sourcemap = c.req.query("sourcemap") === "1";
|
|
56
|
-
const componentName = c.req.query("componentName");
|
|
57
|
-
|
|
58
|
-
try {
|
|
59
|
-
const componentOutput = await buildComponent(path);
|
|
60
|
-
|
|
61
|
-
if (sourcemap) {
|
|
62
|
-
return c.body(componentOutput.sourcemap, 200, {
|
|
63
|
-
"Content-Type": "application/json",
|
|
64
|
-
});
|
|
65
|
-
} else {
|
|
66
|
-
const sizeInKb = componentOutput.length / 1000;
|
|
67
|
-
if (sizeInKb > 100) {
|
|
68
|
-
console.warn(
|
|
69
|
-
`⚠️ Component ${componentName} is larger than 100 kB! Consider optimizing it.`,
|
|
70
|
-
);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
let output = componentOutput.js;
|
|
74
|
-
if (componentOutput.sourcemap) {
|
|
75
|
-
output += `\n//# sourceMappingURL=/api/bundle?componentName=${componentName}&path=${encodeURIComponent(path || "")}&sourcemap=1`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return c.body(output, 200, {
|
|
79
|
-
"Content-Type": "application/javascript",
|
|
80
|
-
});
|
|
81
|
-
}
|
|
82
|
-
} catch (error) {
|
|
83
|
-
console.error(
|
|
84
|
-
`🚫 Error bundling component ${componentName} from ${path}: ${error.message}`,
|
|
85
|
-
);
|
|
86
|
-
return c.text(
|
|
87
|
-
`Error bundling component ${componentName}: ${error.message}`,
|
|
88
|
-
500,
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
});
|
|
92
|
-
app.get("/api/icon", async (c) => {
|
|
93
|
-
const path = c.req.query("path") || "";
|
|
94
|
-
const icon = await readFile(resolve(dir, path), "utf-8");
|
|
95
|
-
|
|
96
|
-
return c.body(icon, 200, {
|
|
97
|
-
"Content-Type": "image/svg+xml",
|
|
98
|
-
});
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
app.get("/api/stylesheet", async (c) => {
|
|
102
|
-
const componentName = c.req.query("componentName");
|
|
103
|
-
|
|
104
|
-
try {
|
|
105
|
-
const componentOutput = await buildStyles();
|
|
106
|
-
return c.body(componentOutput || "", 200, {
|
|
107
|
-
"Content-Type": "text/css",
|
|
108
|
-
});
|
|
109
|
-
} catch (error) {
|
|
110
|
-
console.error(
|
|
111
|
-
`🚫 Error building CSS for component ${componentName}: ${error.message}`,
|
|
112
|
-
);
|
|
113
|
-
return c.text(
|
|
114
|
-
`Error building CSS for component ${componentName}: ${error.message}`,
|
|
115
|
-
500,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
let wsPort; // WebSocket port will be set when server starts
|
|
121
|
-
|
|
122
|
-
app.get("/", async (c) => {
|
|
123
|
-
let questionType;
|
|
124
|
-
try {
|
|
125
|
-
({ definition: questionType } = await readMetaFile());
|
|
126
|
-
questionType.components = questionType.components
|
|
127
|
-
? Object.entries(questionType.components).reduce(
|
|
128
|
-
(acc, [componentName, componentPath]) => ({
|
|
129
|
-
...acc,
|
|
130
|
-
[componentName]: `/api/bundle?componentName=${componentName}&path=${encodeURIComponent(componentPath)}`,
|
|
131
|
-
}),
|
|
132
|
-
{},
|
|
133
|
-
)
|
|
134
|
-
: questionType.components;
|
|
135
|
-
} catch (e) {
|
|
136
|
-
return c.text(
|
|
137
|
-
`Error reading question type definition: ${e.message}`,
|
|
138
|
-
500,
|
|
139
|
-
);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
let template = await readFile(resolve(root, "index.html"), "utf-8");
|
|
143
|
-
template = await vite.transformIndexHtml("/", template);
|
|
144
|
-
|
|
145
|
-
// Inject hot reload script
|
|
146
|
-
const hotReloadScript = `
|
|
147
|
-
<script>
|
|
148
|
-
(function() {
|
|
149
|
-
const wsPort = ${wsPort || "null"};
|
|
150
|
-
if (wsPort) {
|
|
151
|
-
const ws = new WebSocket('ws://localhost:' + wsPort);
|
|
152
|
-
ws.onmessage = function(event) {
|
|
153
|
-
const data = JSON.parse(event.data);
|
|
154
|
-
if (data.type === 'reload') {
|
|
155
|
-
console.log('🔄 Reloading page due to file change...');
|
|
156
|
-
window.location.reload();
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
ws.onopen = function() {
|
|
160
|
-
console.log('🔗 Hot reload connected');
|
|
161
|
-
};
|
|
162
|
-
ws.onclose = function() {
|
|
163
|
-
console.log('🔌 Hot reload disconnected');
|
|
164
|
-
};
|
|
165
|
-
ws.onerror = function(error) {
|
|
166
|
-
console.log('❌ Hot reload error:', error);
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
})();
|
|
170
|
-
</script>
|
|
171
|
-
`;
|
|
172
|
-
|
|
173
|
-
template =
|
|
174
|
-
"<script>window._questionType = " +
|
|
175
|
-
JSON.stringify(questionType) +
|
|
176
|
-
`;</script>${template}${hotReloadScript}`;
|
|
177
|
-
return c.html(template);
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
app.use("*", adaptExpressMiddleware(vite.middlewares));
|
|
181
|
-
|
|
182
|
-
// Set up file watcher
|
|
183
|
-
const watchDir = cwd();
|
|
184
|
-
console.log(`📁 Watching ${watchDir} for changes...`);
|
|
185
|
-
|
|
186
|
-
let wss; // Will be initialized after server starts
|
|
187
|
-
|
|
188
|
-
const watcher = watch(watchDir, { recursive: true }, (filename) => {
|
|
189
|
-
if (filename && !filename.startsWith(".")) {
|
|
190
|
-
// Broadcast reload message to all connected clients
|
|
191
|
-
if (wss) {
|
|
192
|
-
wss.clients.forEach((client) => {
|
|
193
|
-
if (client.readyState === 1) {
|
|
194
|
-
// WebSocket.OPEN
|
|
195
|
-
client.send(JSON.stringify({ type: "reload" }));
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
// Clean up watcher on process exit
|
|
203
|
-
process.on("SIGINT", () => {
|
|
204
|
-
watcher.close();
|
|
205
|
-
process.exit(0);
|
|
206
|
-
});
|
|
207
|
-
|
|
208
|
-
serve({ fetch: app.fetch }, (address) => {
|
|
209
|
-
const port = address.port;
|
|
210
|
-
wsPort = port + 1234;
|
|
211
|
-
|
|
212
|
-
const wsServer = createHttpServer();
|
|
213
|
-
wss = new WebSocketServer({ server: wsServer });
|
|
214
|
-
wsServer.listen(wsPort);
|
|
215
|
-
|
|
216
|
-
const url = `http://localhost:${port}`;
|
|
217
|
-
console.log(`🚀 Preview server running at ${url}`);
|
|
218
|
-
open(url);
|
|
219
|
-
});
|
|
220
|
-
};
|
package/src/commands/upload.js
DELETED
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
#! /usr/bin/env node
|
|
2
|
-
import { readFile } from "fs/promises";
|
|
3
|
-
import { resolve } from "path";
|
|
4
|
-
import { cwd, exit } from "process";
|
|
5
|
-
|
|
6
|
-
import { setApiHost, setApiKey, uploadFile } from "../lib/api.js";
|
|
7
|
-
import { buildComponent } from "../lib/bundle.js";
|
|
8
|
-
import { deepCompileJsonata } from "../lib/jsonata.js";
|
|
9
|
-
import { readMetaFile } from "../lib/read-meta.js";
|
|
10
|
-
import { buildStyles } from "../lib/styles.js";
|
|
11
|
-
|
|
12
|
-
export const uploadCommand = async (argv) => {
|
|
13
|
-
setApiHost(argv.host);
|
|
14
|
-
setApiKey(argv.key);
|
|
15
|
-
|
|
16
|
-
const dir = cwd();
|
|
17
|
-
let definition = {};
|
|
18
|
-
try {
|
|
19
|
-
({ definition } = await readMetaFile());
|
|
20
|
-
} catch (e) {
|
|
21
|
-
console.error(`🚫 ${e.message}`);
|
|
22
|
-
exit(1);
|
|
23
|
-
}
|
|
24
|
-
console.log(`Uploading question type: ${definition.id}...`);
|
|
25
|
-
if (!definition.id) {
|
|
26
|
-
console.error(`🚫 Missing required field: id`);
|
|
27
|
-
exit(1);
|
|
28
|
-
}
|
|
29
|
-
if (!definition.name) {
|
|
30
|
-
console.error(`🚫 Missing required field: name`);
|
|
31
|
-
exit(1);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Validate JSONata expressions in the definition
|
|
35
|
-
if (definition.export?.qti?.interaction?.options) {
|
|
36
|
-
try {
|
|
37
|
-
await deepCompileJsonata(definition.export.qti.interaction.options);
|
|
38
|
-
} catch (e) {
|
|
39
|
-
console.error(
|
|
40
|
-
`🚫 Invalid JSONata expression in export.qti.interaction.options: ${e.message}`,
|
|
41
|
-
);
|
|
42
|
-
exit(1);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Bundle and upload components
|
|
47
|
-
const components = {};
|
|
48
|
-
const validComponentTypes = [
|
|
49
|
-
"settings-area",
|
|
50
|
-
"assessment",
|
|
51
|
-
"print",
|
|
52
|
-
"results",
|
|
53
|
-
];
|
|
54
|
-
for (const [componentType, componentPath] of Object.entries(
|
|
55
|
-
definition.components || {},
|
|
56
|
-
)) {
|
|
57
|
-
// Validation
|
|
58
|
-
if (!validComponentTypes.includes(componentType)) {
|
|
59
|
-
console.error(`🚫 Invalid component type specified: ${componentType}`);
|
|
60
|
-
exit(1);
|
|
61
|
-
}
|
|
62
|
-
const realPath = resolve(dir, componentPath);
|
|
63
|
-
if (!realPath) {
|
|
64
|
-
console.error(`🚫 Missing required file: ${componentPath}`);
|
|
65
|
-
exit(1);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Bundle component
|
|
69
|
-
let componentOutput = {};
|
|
70
|
-
try {
|
|
71
|
-
componentOutput = await buildComponent(realPath);
|
|
72
|
-
const sizeInKb = componentOutput.js.length / 1000;
|
|
73
|
-
const sizeHint = `(${sizeInKb.toFixed(2)} kB)`;
|
|
74
|
-
console.log(
|
|
75
|
-
`• Bundled component ${componentType} from ${componentPath} ${sizeHint}`,
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
if (sizeInKb > 100) {
|
|
79
|
-
console.warn(
|
|
80
|
-
` ⚠️ Component ${componentType} is larger than 100 kB! Consider optimizing it.`,
|
|
81
|
-
);
|
|
82
|
-
if (process.env.GITHUB_ACTIONS) {
|
|
83
|
-
console.log(
|
|
84
|
-
`::warning file=${realPath},title=Large component size::Component ${
|
|
85
|
-
componentType
|
|
86
|
-
} is larger than 100 kB ${sizeHint}! Consider optimizing it.`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
} catch (error) {
|
|
91
|
-
console.error(
|
|
92
|
-
`🚫 Error bundling component ${componentType} from ${componentPath}: ${error.message}`,
|
|
93
|
-
);
|
|
94
|
-
exit(1);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Upload source map first if it exists
|
|
98
|
-
let sourceMapStatement = "";
|
|
99
|
-
if (componentOutput.sourcemap) {
|
|
100
|
-
const url = await uploadFile(
|
|
101
|
-
`component-${componentType}.js.map`,
|
|
102
|
-
componentOutput.sourcemap,
|
|
103
|
-
"application/json",
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
sourceMapStatement = `\n//# sourceMappingURL=${url}`;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// Upload component
|
|
110
|
-
components[componentType] = await uploadFile(
|
|
111
|
-
`component-${componentType}.js`,
|
|
112
|
-
componentOutput.js + sourceMapStatement,
|
|
113
|
-
"application/javascript",
|
|
114
|
-
);
|
|
115
|
-
|
|
116
|
-
console.log(`• Uploaded component ${componentType}`);
|
|
117
|
-
}
|
|
118
|
-
definition.components = components;
|
|
119
|
-
|
|
120
|
-
// Upload styles if present
|
|
121
|
-
if (Object.keys(definition.components).length) {
|
|
122
|
-
const styles = await buildStyles();
|
|
123
|
-
if (styles) {
|
|
124
|
-
definition.stylesheet = await uploadFile(
|
|
125
|
-
"styles.css",
|
|
126
|
-
styles,
|
|
127
|
-
"text/css",
|
|
128
|
-
);
|
|
129
|
-
const sizeInKb = styles.length / 1000;
|
|
130
|
-
const sizeHint = `(${sizeInKb.toFixed(2)} kB)`;
|
|
131
|
-
console.log(`• Uploaded styles ${sizeHint}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
// Upload icon if specified
|
|
136
|
-
if (definition.icon) {
|
|
137
|
-
const iconPath = resolve(dir, definition.icon);
|
|
138
|
-
let iconContents;
|
|
139
|
-
try {
|
|
140
|
-
iconContents = await readFile(iconPath, "utf-8");
|
|
141
|
-
} catch (error) {
|
|
142
|
-
console.error(`🚫 Error reading icon file: ${error.message}`);
|
|
143
|
-
exit(1);
|
|
144
|
-
}
|
|
145
|
-
if (!iconContents) {
|
|
146
|
-
console.error(`🚫 Icon file is empty: ${iconPath}`);
|
|
147
|
-
exit(1);
|
|
148
|
-
}
|
|
149
|
-
definition.icon = await uploadFile(
|
|
150
|
-
"icon.svg",
|
|
151
|
-
iconContents,
|
|
152
|
-
"image/svg+xml",
|
|
153
|
-
);
|
|
154
|
-
console.log(`• Uploaded icon`);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Upload the main question type definition
|
|
158
|
-
const res = await fetch(`${argv.host}/question-types`, {
|
|
159
|
-
headers: {
|
|
160
|
-
Authorization: `Bearer ${argv.key}`,
|
|
161
|
-
"Content-Type": "application/json",
|
|
162
|
-
},
|
|
163
|
-
method: "POST",
|
|
164
|
-
body: JSON.stringify(definition),
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
if (!res.ok) {
|
|
168
|
-
console.error(`🚫 Uploading question type failed`);
|
|
169
|
-
try {
|
|
170
|
-
const errorResponse = await res.json();
|
|
171
|
-
console.log(errorResponse);
|
|
172
|
-
} catch (e) {
|
|
173
|
-
console.log(e);
|
|
174
|
-
}
|
|
175
|
-
exit(1);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
console.log(`• Finished uploading definition\n`);
|
|
179
|
-
};
|
package/src/lib/api.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { basename } from "path";
|
|
2
|
-
import { exit } from "process";
|
|
3
|
-
|
|
4
|
-
// These get overwritten by the CLI options
|
|
5
|
-
export let API_HOST = "https://api.examplary.ai";
|
|
6
|
-
export let API_KEY = process.env.EXAMPLARY_API_KEY || "";
|
|
7
|
-
|
|
8
|
-
export const setApiKey = (key) => {
|
|
9
|
-
if (!key) return;
|
|
10
|
-
API_KEY = key;
|
|
11
|
-
};
|
|
12
|
-
export const setApiHost = (host) => {
|
|
13
|
-
if (!host) return;
|
|
14
|
-
API_HOST = host;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const uploadFile = async (fileName, fileContents, contentType) => {
|
|
18
|
-
const params = new URLSearchParams();
|
|
19
|
-
params.append("filename", basename(fileName));
|
|
20
|
-
params.append("contentType", contentType);
|
|
21
|
-
params.append("type", "question-type");
|
|
22
|
-
|
|
23
|
-
// Request the upload URL
|
|
24
|
-
const res = await fetch(`${API_HOST}/media/upload?${params.toString()}`, {
|
|
25
|
-
headers: {
|
|
26
|
-
Authorization: `Bearer ${API_KEY}`,
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
if (!res.ok) {
|
|
31
|
-
const errorText = await res.text();
|
|
32
|
-
console.error(`🚫 Upload failed: ${errorText}`);
|
|
33
|
-
exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Actually upload the file
|
|
37
|
-
const { uploadUrl, publicUrl } = await res.json();
|
|
38
|
-
const uploadRes = await fetch(uploadUrl, {
|
|
39
|
-
method: "PUT",
|
|
40
|
-
headers: {
|
|
41
|
-
"Content-Type": contentType,
|
|
42
|
-
},
|
|
43
|
-
body: fileContents,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
if (!uploadRes.ok) {
|
|
47
|
-
const errorText = await uploadRes.text();
|
|
48
|
-
console.error(`🚫 Upload failed: ${errorText}`);
|
|
49
|
-
exit(1);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return publicUrl;
|
|
53
|
-
};
|
package/src/lib/bundle.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { build } from "esbuild";
|
|
2
|
-
|
|
3
|
-
export const buildComponent = async (file) => {
|
|
4
|
-
const res = await build({
|
|
5
|
-
entryPoints: [file],
|
|
6
|
-
bundle: true,
|
|
7
|
-
write: false,
|
|
8
|
-
minify: true,
|
|
9
|
-
sourcemap: "inline",
|
|
10
|
-
platform: "browser",
|
|
11
|
-
format: "cjs",
|
|
12
|
-
external: [
|
|
13
|
-
"@examplary/ui",
|
|
14
|
-
"react",
|
|
15
|
-
"react-dom",
|
|
16
|
-
"react/jsx-runtime",
|
|
17
|
-
"react-dom/client",
|
|
18
|
-
],
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
let js = res.outputFiles[0].text;
|
|
22
|
-
let sourcemap = "";
|
|
23
|
-
|
|
24
|
-
const sourceMapStatement =
|
|
25
|
-
"\n//# sourceMappingURL=data:application/json;base64,";
|
|
26
|
-
if (js.includes(sourceMapStatement)) {
|
|
27
|
-
sourcemap = Buffer.from(
|
|
28
|
-
js.split(sourceMapStatement)[1].trim(),
|
|
29
|
-
"base64",
|
|
30
|
-
).toString("utf-8");
|
|
31
|
-
js = js.split(sourceMapStatement)[0].trim();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return { js, sourcemap };
|
|
35
|
-
};
|
package/src/lib/jsonata.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import jsonata from "jsonata";
|
|
2
|
-
|
|
3
|
-
export const deepCompileJsonata = async (expression) => {
|
|
4
|
-
if (typeof expression === "string" && expression.startsWith("=")) {
|
|
5
|
-
try {
|
|
6
|
-
jsonata(expression.substring(1));
|
|
7
|
-
} catch (error) {
|
|
8
|
-
throw new Error(
|
|
9
|
-
`JSONata evaluation failed for "${expression}": ${error.message}`,
|
|
10
|
-
);
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
if (Array.isArray(expression)) {
|
|
15
|
-
const output = [];
|
|
16
|
-
for (const o of expression) {
|
|
17
|
-
output.push(await deepCompileJsonata(o));
|
|
18
|
-
}
|
|
19
|
-
return output;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
if (typeof expression === "object" && expression !== null) {
|
|
23
|
-
const result = {};
|
|
24
|
-
for (const [key, value] of Object.entries(expression)) {
|
|
25
|
-
result[key] = await deepCompileJsonata(value);
|
|
26
|
-
}
|
|
27
|
-
return result;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return expression;
|
|
31
|
-
};
|
package/src/lib/middleware.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { createMiddleware } from "hono/factory";
|
|
2
|
-
|
|
3
|
-
export const adaptExpressMiddleware = (middleware) =>
|
|
4
|
-
createMiddleware(async (c, next) => {
|
|
5
|
-
const req = c.env.incoming;
|
|
6
|
-
const res = c.env.outgoing;
|
|
7
|
-
|
|
8
|
-
await new Promise((resolve, reject) => {
|
|
9
|
-
middleware(req, res, (err) => {
|
|
10
|
-
if (err) reject(err);
|
|
11
|
-
else resolve(c.res);
|
|
12
|
-
});
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
await next();
|
|
16
|
-
});
|