@examplary/apps 0.0.1 → 1.0.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 +6 -5
- package/package.json +6 -1
- package/src/app.ts +5 -9
- package/src/capabilities/Capability.ts +5 -2
- package/src/{question-type.ts → capabilities/QuestionType.ts} +24 -16
- package/src/capabilities/index.ts +1 -0
- package/src/env.ts +1 -1
- package/src/index.ts +0 -1
- package/src/types.ts +1 -1
package/README.md
CHANGED
|
@@ -31,7 +31,7 @@ app.addCapability(exportCapability);
|
|
|
31
31
|
export default app;
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
Hooks can read secrets set with `exp
|
|
34
|
+
Hooks can read secrets set with `exp set-secret NAME value`, which are never sent to the
|
|
35
35
|
dashboard. Read them inside a hook, not at the top of a module; a missing secret is
|
|
36
36
|
`undefined`:
|
|
37
37
|
|
|
@@ -52,12 +52,13 @@ Capabilities add features to the platform, each with hooks of its own:
|
|
|
52
52
|
- `ResultsDestination` — somewhere teachers publish grades to, like a gradebook.
|
|
53
53
|
- `AssessmentPrerequisite` — something a student has to meet before starting a test, like a
|
|
54
54
|
secure browser, with a settings component for the exam's settings and a player component.
|
|
55
|
+
- `QuestionType` — a question type, which workspaces that install the app can use in tests.
|
|
55
56
|
|
|
56
|
-
`
|
|
57
|
+
A `new QuestionType({...})` takes the question type's definition. Its
|
|
57
58
|
`components`, `icon`, `readme` and `previewImage` are paths relative to `app.ts`, e.g.
|
|
58
59
|
`./essay/component-assessment.tsx`. `Essay.addValidator(({ question }) => ({ valid, error }))`
|
|
59
|
-
checks questions of that type, e.g. generated ones, as a hook.
|
|
60
|
-
|
|
60
|
+
checks questions of that type, e.g. generated ones, as a hook. `Essay.addSetting({...})` adds a
|
|
61
|
+
setting after those in the definition's `settings`.
|
|
61
62
|
|
|
62
63
|
`app.addSettingsPrepareStep()`, `app.addSettingsValidateStep()` and `app.addUninstallStep()`
|
|
63
64
|
hook into saving the app's settings and removing it from a workspace.
|
|
@@ -70,6 +71,6 @@ every hook call loads.
|
|
|
70
71
|
A `component` setting renders a React component (a `.tsx` file that default-exports a
|
|
71
72
|
`FrontendSettingsFieldComponent` from `@examplary/ui`), which calls `setValue()` and `submit()`.
|
|
72
73
|
|
|
73
|
-
`npx @examplary/cli
|
|
74
|
+
`npx @examplary/cli publish` evaluates `app.ts`, bundles every component for the browser
|
|
74
75
|
and the app itself for the server, uploads the assets and publishes the result. See
|
|
75
76
|
[docs/apps.md](../../docs/apps.md) for how the platform handles apps.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@examplary/apps",
|
|
3
3
|
"packageManager": "yarn@4.8.1",
|
|
4
|
-
"version": "0.0
|
|
4
|
+
"version": "1.0.0",
|
|
5
5
|
"description": "Define Examplary apps and question types in TypeScript.",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"release": "semantic-release -e semantic-release-monorepo"
|
|
@@ -16,5 +16,10 @@
|
|
|
16
16
|
"types": "./src/index.ts",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@examplary/schemas": "*"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://examplary.ai/developers",
|
|
21
|
+
"author": {
|
|
22
|
+
"name": "Examplary AI",
|
|
23
|
+
"email": "hi@examplary.ai"
|
|
19
24
|
}
|
|
20
25
|
}
|
package/src/app.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { Capability } from "./capabilities/Capability";
|
|
2
2
|
import { resolveComponent, serializeField } from "./fields";
|
|
3
|
-
import { AppQuestionType } from "./question-type";
|
|
4
3
|
import {
|
|
5
4
|
AppDefinition,
|
|
6
5
|
FullAppDefinition,
|
|
@@ -41,7 +40,11 @@ class App {
|
|
|
41
40
|
|
|
42
41
|
addCapability(capability: Capability) {
|
|
43
42
|
const def = capability.toDefinition();
|
|
44
|
-
|
|
43
|
+
if ("questionType" in def) {
|
|
44
|
+
this.definition.questionTypes.push(def.questionType);
|
|
45
|
+
} else {
|
|
46
|
+
this.definition.capabilities.push(def.capability);
|
|
47
|
+
}
|
|
45
48
|
this.definition.assets.push(...def.assets);
|
|
46
49
|
this.definition.hooks.push(...def.hooks);
|
|
47
50
|
this.definition.components.push(...def.components);
|
|
@@ -65,13 +68,6 @@ class App {
|
|
|
65
68
|
this.definition.settings.push(serializeField(settingDef));
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
/** Adds a question type, see `defineQuestionType()`. */
|
|
69
|
-
addQuestionType(questionType: AppQuestionType) {
|
|
70
|
-
const { questionType: definition, hooks } = questionType.toDefinition();
|
|
71
|
-
this.definition.questionTypes.push(definition);
|
|
72
|
-
this.definition.hooks.push(...hooks);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
71
|
addSettingsValidateStep(
|
|
76
72
|
fn: (settings: Record<string, any>) => Promise<boolean> | boolean,
|
|
77
73
|
) {
|
|
@@ -3,10 +3,13 @@ import {
|
|
|
3
3
|
HookDefinition,
|
|
4
4
|
ComponentDefinition,
|
|
5
5
|
AssetDefinition,
|
|
6
|
+
QuestionTypeDefinition,
|
|
6
7
|
} from "../types";
|
|
7
8
|
|
|
8
|
-
export type ToCapabilityDefinition =
|
|
9
|
-
capability: CapabilityDefinition
|
|
9
|
+
export type ToCapabilityDefinition = (
|
|
10
|
+
| { capability: CapabilityDefinition }
|
|
11
|
+
| { questionType: QuestionTypeDefinition }
|
|
12
|
+
) & {
|
|
10
13
|
hooks: HookDefinition[];
|
|
11
14
|
components: ComponentDefinition[];
|
|
12
15
|
assets: AssetDefinition[];
|
|
@@ -1,14 +1,30 @@
|
|
|
1
|
-
import type { Question } from "@examplary/schemas";
|
|
1
|
+
import type { Question, SettingType } from "@examplary/schemas";
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { Capability, ToCapabilityDefinition } from "./Capability";
|
|
4
|
+
import { QuestionTypeDefinition } from "../types";
|
|
4
5
|
|
|
5
6
|
export type QuestionValidation = { valid: boolean; error?: string };
|
|
6
7
|
|
|
7
|
-
/**
|
|
8
|
-
|
|
8
|
+
/**
|
|
9
|
+
* A question type the app adds to Examplary. Its file paths are relative to
|
|
10
|
+
* `app.ts`, e.g. `./essay/component-assessment.tsx`.
|
|
11
|
+
*/
|
|
12
|
+
export class QuestionType extends Capability {
|
|
13
|
+
protected definition: QuestionTypeDefinition;
|
|
9
14
|
private functions: Record<string, Function> = {};
|
|
10
15
|
|
|
11
|
-
constructor(
|
|
16
|
+
constructor(definition: QuestionTypeDefinition) {
|
|
17
|
+
super();
|
|
18
|
+
this.definition = definition;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Adds a setting teachers fill in for a question of this type, after those in its definition. */
|
|
22
|
+
addSetting(setting: SettingType) {
|
|
23
|
+
this.definition = {
|
|
24
|
+
...this.definition,
|
|
25
|
+
settings: [...(this.definition.settings ?? []), setting],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
12
28
|
|
|
13
29
|
/**
|
|
14
30
|
* Checks a question of this type, e.g. one AI generated, which is tried
|
|
@@ -22,10 +38,7 @@ export class AppQuestionType {
|
|
|
22
38
|
this.functions["validate-question"] = fn;
|
|
23
39
|
}
|
|
24
40
|
|
|
25
|
-
toDefinition(): {
|
|
26
|
-
questionType: QuestionTypeDefinition & { hooks?: Record<string, string> };
|
|
27
|
-
hooks: HookDefinition[];
|
|
28
|
-
} {
|
|
41
|
+
toDefinition(): ToCapabilityDefinition {
|
|
29
42
|
const hookId = (name: string) =>
|
|
30
43
|
`question-types.${this.definition.id}.${name}`;
|
|
31
44
|
const names = Object.keys(this.functions);
|
|
@@ -43,13 +56,8 @@ export class AppQuestionType {
|
|
|
43
56
|
id: hookId(name),
|
|
44
57
|
fn: this.functions[name],
|
|
45
58
|
})),
|
|
59
|
+
components: [],
|
|
60
|
+
assets: [],
|
|
46
61
|
};
|
|
47
62
|
}
|
|
48
63
|
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* A question type, to add with `app.addQuestionType()`. Its file paths are
|
|
52
|
-
* relative to `app.ts`, e.g. `./essay/component-assessment.tsx`.
|
|
53
|
-
*/
|
|
54
|
-
export const defineQuestionType = (questionType: QuestionTypeDefinition) =>
|
|
55
|
-
new AppQuestionType(questionType);
|
package/src/env.ts
CHANGED
|
@@ -4,7 +4,7 @@ declare global {
|
|
|
4
4
|
}
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* Secrets set with `exp
|
|
7
|
+
* Secrets set with `exp set-secret`, available in the app's hooks. Values
|
|
8
8
|
* are read when accessed, so read them inside a hook, not at the top of a module.
|
|
9
9
|
*/
|
|
10
10
|
export const env: Record<string, string | undefined> = new Proxy(
|
package/src/index.ts
CHANGED
package/src/types.ts
CHANGED
|
@@ -88,7 +88,7 @@ export type AssetDefinition = {
|
|
|
88
88
|
type TranslatablePath = string | Record<string, string>;
|
|
89
89
|
|
|
90
90
|
/**
|
|
91
|
-
* A question type the app provides. Its `components
|
|
91
|
+
* A question type the app provides. Its `components` and `icon`,
|
|
92
92
|
* and its `readme` and `previewImage`, are paths relative to `app.ts`.
|
|
93
93
|
*/
|
|
94
94
|
export type QuestionTypeDefinition = Omit<
|