@examplary/cli 1.5.0 → 1.6.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 CHANGED
@@ -15,7 +15,7 @@ npx @examplary/cli preview
15
15
  ```
16
16
 
17
17
  To upload your custom question type, run the following command in a directory
18
- that contains a `question-type.json` file:
18
+ that contains a `question-type.json` or `question-type.yml` file:
19
19
 
20
20
  ```bash
21
21
  npx @examplary/cli upload
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@examplary/cli",
3
- "description": "A bundler for Examplary question types.",
3
+ "description": "Command-line tools for building and publishing Examplary question types.",
4
4
  "packageManager": "yarn@4.8.1",
5
- "version": "1.5.0",
5
+ "version": "1.6.0",
6
6
  "type": "module",
7
7
  "bin": {
8
8
  "exp": "./bin/index.js"
@@ -44,6 +44,7 @@
44
44
  "use-local-storage": "^3.0.0",
45
45
  "vite": "^8.0.0",
46
46
  "ws": "^8.18.3",
47
+ "yaml": "^2.8.2",
47
48
  "yargs": "^18.0.0"
48
49
  },
49
50
  "devDependencies": {
@@ -1,8 +1,8 @@
1
1
  #! /usr/bin/env node
2
2
  import { watch } from "fs";
3
- import { stat, readFile } from "fs/promises";
3
+ import { readFile } from "fs/promises";
4
4
  import { createServer as createHttpServer } from "http";
5
- import path, { join, resolve } from "path";
5
+ import path, { resolve } from "path";
6
6
  import { cwd, exit } from "process";
7
7
 
8
8
  import { serve } from "@hono/node-server";
@@ -16,19 +16,20 @@ import { WebSocketServer } from "ws";
16
16
  import { setApiHost, setApiKey } from "../lib/api.js";
17
17
  import { buildComponent } from "../lib/bundle.js";
18
18
  import { adaptExpressMiddleware } from "../lib/middleware.js";
19
+ import { readMetaFile } from "../lib/read-meta.js";
19
20
  import { buildStyles } from "../lib/styles.js";
20
21
 
21
22
  export const previewCommand = async (argv) => {
22
23
  setApiHost(argv.host);
23
24
  setApiKey(argv.key);
24
25
 
25
- // Let's check a question-type.json file exists
26
26
  const dir = cwd();
27
- const metaFilePath = join(dir, "question-type.json");
27
+
28
+ // Verify a question-type file exists (json or yml)
28
29
  try {
29
- await stat(metaFilePath);
30
+ await readMetaFile();
30
31
  } catch (e) {
31
- console.error(`🚫 Error reading question-type.json: ${e.message}`);
32
+ console.error(`🚫 ${e.message}`);
32
33
  exit(1);
33
34
  }
34
35
 
@@ -121,7 +122,7 @@ export const previewCommand = async (argv) => {
121
122
  app.get("/", async (c) => {
122
123
  let questionType;
123
124
  try {
124
- questionType = JSON.parse(await readFile(metaFilePath, "utf-8"));
125
+ ({ definition: questionType } = await readMetaFile());
125
126
  questionType.components = questionType.components
126
127
  ? Object.entries(questionType.components).reduce(
127
128
  (acc, [componentName, componentPath]) => ({
@@ -132,7 +133,10 @@ export const previewCommand = async (argv) => {
132
133
  )
133
134
  : questionType.components;
134
135
  } catch (e) {
135
- return c.text(`Error reading question-type.json: ${e.message}`, 500);
136
+ return c.text(
137
+ `Error reading question type definition: ${e.message}`,
138
+ 500,
139
+ );
136
140
  }
137
141
 
138
142
  let template = await readFile(resolve(root, "index.html"), "utf-8");
@@ -1,36 +1,24 @@
1
1
  #! /usr/bin/env node
2
2
  import { readFile } from "fs/promises";
3
- import { join, resolve } from "path";
3
+ import { resolve } from "path";
4
4
  import { cwd, exit } from "process";
5
5
 
6
6
  import { setApiHost, setApiKey, uploadFile } from "../lib/api.js";
7
7
  import { buildComponent } from "../lib/bundle.js";
8
8
  import { deepCompileJsonata } from "../lib/jsonata.js";
9
+ import { readMetaFile } from "../lib/read-meta.js";
9
10
  import { buildStyles } from "../lib/styles.js";
10
11
 
11
12
  export const uploadCommand = async (argv) => {
12
13
  setApiHost(argv.host);
13
14
  setApiKey(argv.key);
14
15
 
15
- // Try to read the question-type.json files in the current directory
16
16
  const dir = cwd();
17
- const metaFilePath = join(dir, "question-type.json");
18
- let metaFileContents;
19
17
  let definition = {};
20
18
  try {
21
- metaFileContents = await readFile(metaFilePath, "utf-8");
19
+ ({ definition } = await readMetaFile());
22
20
  } catch (e) {
23
- console.error(`🚫 Error reading question-type.json: ${e.message}`);
24
- exit(1);
25
- }
26
-
27
- // Read JSON
28
- try {
29
- const json = JSON.parse(metaFileContents);
30
- definition = json;
31
- if (definition.$schema) delete definition.$schema;
32
- } catch (error) {
33
- console.error(`🚫 Invalid JSON file: ${error.message}`);
21
+ console.error(`🚫 ${e.message}`);
34
22
  exit(1);
35
23
  }
36
24
  console.log(`Uploading question type: ${definition.id}...`);
@@ -0,0 +1,40 @@
1
+ import { readFile } from "fs/promises";
2
+ import { join } from "path";
3
+ import { cwd } from "process";
4
+
5
+ import yaml from "yaml";
6
+
7
+ /**
8
+ * Reads and parses the question-type definition file.
9
+ * Prefers question-type.json over question-type.yml.
10
+ */
11
+ export const readMetaFile = async () => {
12
+ const dir = cwd();
13
+ const jsonPath = join(dir, "question-type.json");
14
+ const ymlPath = join(dir, "question-type.yml");
15
+
16
+ // Try JSON first, then YAML
17
+ for (const { path, parse, format } of [
18
+ { path: jsonPath, parse: JSON.parse, format: "JSON" },
19
+ { path: ymlPath, parse: yaml.parse, format: "YAML" },
20
+ ]) {
21
+ let contents;
22
+ try {
23
+ contents = await readFile(path, "utf-8");
24
+ } catch {
25
+ continue;
26
+ }
27
+
28
+ try {
29
+ const definition = parse(contents);
30
+ if (definition.$schema) delete definition.$schema;
31
+ return { definition, path };
32
+ } catch (error) {
33
+ throw new Error(`Invalid ${format} in ${path}: ${error.message}`);
34
+ }
35
+ }
36
+
37
+ throw new Error(
38
+ "No question-type.json or question-type.yml found in the current directory",
39
+ );
40
+ };