Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/kitten-scientists/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"types": "./build/index.ts",
"dependencies": {
"@oliversalzburg/js-utils": "0.0.22",
"ajv": "8.12.0",
"date-fns": "2.30.0",
"semver": "7.5.4",
"tslib": "2.6.2"
Expand Down
24 changes: 24 additions & 0 deletions packages/kitten-scientists/source/UserScript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Engine, EngineState, GameLanguage, SupportedLanguage } from "./Engine.j
import { ScienceSettings } from "./settings/ScienceSettings.js";
import { SpaceSettings } from "./settings/SpaceSettings.js";
import { WorkshopSettings } from "./settings/WorkshopSettings.js";
import { State } from "./state/State.js";
import { cdebug, cerror, cinfo, cwarn } from "./tools/Log.js";
import { Game } from "./types/index.js";
import { UserInterface } from "./ui/UserInterface.js";
Expand Down Expand Up @@ -237,6 +238,29 @@ export class UserScript {
this.engine.imessage("settings.imported");
}

/**
* Import settings from a URL.
* This is an experimental feature, and only allows using profiles from
* https://kitten-science.com/ at this time.
*
* @param url - The URL of the profile to load.
*/
async importSettingsFromUrl(url: string) {
const importState = new State(url);
const settings = await importState.resolve();
settings.report.aggregate(console);

const stateIsValid = await importState.validate();
if (!stateIsValid) {
cerror("Imported state is invalid and not imported.");
return;
}

const state = importState.merge();
this.setSettings(state);
this.engine.imessage("settings.imported");
}

/**
* Copies an engine state to the clipboard.
*
Expand Down
57 changes: 56 additions & 1 deletion packages/kitten-scientists/source/state/State.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TreeNode } from "@oliversalzburg/js-utils";
import { InvalidOperationError, TreeNode } from "@oliversalzburg/js-utils";
import AjvModule, { SchemaObject } from "ajv";
import { UserScript } from "../UserScript.js";
import { StateLoader } from "./StateLoader.js";
import { StateMerger } from "./StateMerger.js";
Expand All @@ -14,6 +15,12 @@ export class State extends TreeNode<State> {

constructor(originUrl: string, parent?: State) {
super(parent);
if (!originUrl.startsWith("https://kitten-science.com/")) {
throw new InvalidOperationError(
"While state import is experimental, you can only import from 'https://kitten-science.com/'!",
);
}

this.originUrl = originUrl;
this.loader = new StateLoader(this);
}
Expand All @@ -23,6 +30,54 @@ export class State extends TreeNode<State> {
return { loader: this.loader, report };
}

async validate() {
const schemaBaselineRequest = await fetch(
"https://schema.kitten-science.com/working-draft/baseline/engine-state.schema.json",
);
const schemaProfileRequest = await fetch(
"https://schema.kitten-science.com/working-draft/settings-profile.schema.json",
);
const schemaBaseline = (await schemaBaselineRequest.json()) as SchemaObject;
const schemaProfile = (await schemaProfileRequest.json()) as SchemaObject;

// FIXME: https://github.com/ajv-validator/ajv/issues/2047
const Ajv = AjvModule.default;
const ajv = new Ajv({ allErrors: true, verbose: true });
const validateBaseline = ajv.compile(schemaBaseline);
const validateProfile = ajv.compile(schemaProfile);

const validateLoader = (loader: StateLoader) => {
const data = loader.data;
const isValidBaseline = validateBaseline(data);
const isValidProfile = validateProfile(data);
if (isValidProfile && !isValidBaseline) {
console.log(`VALID Profile: ${loader.state.originUrl})`);
return true;
}
if (isValidProfile && isValidBaseline) {
console.log(`VALID Baseline: ${loader.state.originUrl})`);
return true;
}
console.log(`INVALID: ${loader.state.originUrl})`);
return false;
};

const validateNode = (node: State) => {
let childrenValid = true;
if (0 < node.children.size) {
for (const child of node.children) {
const childValid = validateNode(child);
if (!childValid) {
childrenValid = false;
}
}
}
return validateLoader(node.loader) && childrenValid;
};

return validateNode(this);
}

merge() {
return new StateMerger(this).merge(UserScript.unknownAsEngineStateOrThrow({ v: "2.0.0" }));
}
Expand Down
3 changes: 1 addition & 2 deletions packages/kitten-scientists/source/state/StateLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,10 @@ export class StateLoader extends TreeNode<StateLoader> {
return this.report;
}

// TODO: Validate again JSON schema.
this.#data = data;
const bases = await this.#resolveBases(this.#data, cache);
if (bases.length === 0) {
this.report.log("🍁 Profile is a leaf node and should be a baseline!");
this.report.log("🍁 Profile is a leaf node.");
} else {
this.report.log("🌳 Profile is a tree node.");
}
Expand Down
2 changes: 1 addition & 1 deletion packages/kitten-scientists/source/state/StateMerger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class StateMerger {

// Clean up merged state.
(stateSubject as Record<string, string>).$schema =
"https://github.com/kitten-science/kitten-scientists/raw/main/schemas/draft-01/settings-profile.schema.json";
"https://schema.kitten-science.com/working-draft/settings-profile.schema.json";
delete stateSubject.extends;

return stateSubject;
Expand Down
2 changes: 2 additions & 0 deletions schemas/.scripts/load-profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const state = new State(
const profile = await state.resolve();
profile.report.aggregate(console);

await state.validate();

const engineState = state.merge();
writeFileSync("load-profile.result.json", JSON.stringify(engineState, undefined, 2));
console.info("Result written to 'load-profile.result.json'.");
Expand Down
36 changes: 23 additions & 13 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,7 @@ __metadata:
"@types/jquery": "npm:3.5.29"
"@types/semver": "npm:7.5.6"
"@types/web": "npm:0.0.120"
ajv: "npm:8.12.0"
date-fns: "npm:2.30.0"
semver: "npm:7.5.4"
tslib: "npm:2.6.2"
Expand Down Expand Up @@ -1295,7 +1296,16 @@ __metadata:
languageName: node
linkType: hard

"@types/node@npm:*, @types/node@npm:20.10.0":
"@types/node@npm:*":
version: 20.9.4
resolution: "@types/node@npm:20.9.4"
dependencies:
undici-types: "npm:~5.26.4"
checksum: c8b48ace4c7e17715fa901201c98275f8e5268cf5895a8d149777eb0ec6c3ef6c831ff3917e92da5453a5dbe13f230caa50b348a0601b0d50eb9e628010c0364
languageName: node
linkType: hard

"@types/node@npm:20.10.0":
version: 20.10.0
resolution: "@types/node@npm:20.10.0"
dependencies:
Expand Down Expand Up @@ -1543,27 +1553,27 @@ __metadata:
languageName: node
linkType: hard

"ajv@npm:^6.12.4":
version: 6.12.6
resolution: "ajv@npm:6.12.6"
"ajv@npm:8.12.0, ajv@npm:^8.11.0":
version: 8.12.0
resolution: "ajv@npm:8.12.0"
dependencies:
fast-deep-equal: "npm:^3.1.1"
fast-json-stable-stringify: "npm:^2.0.0"
json-schema-traverse: "npm:^0.4.1"
json-schema-traverse: "npm:^1.0.0"
require-from-string: "npm:^2.0.2"
uri-js: "npm:^4.2.2"
checksum: 41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71
checksum: ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e
languageName: node
linkType: hard

"ajv@npm:^8.11.0":
version: 8.12.0
resolution: "ajv@npm:8.12.0"
"ajv@npm:^6.12.4":
version: 6.12.6
resolution: "ajv@npm:6.12.6"
dependencies:
fast-deep-equal: "npm:^3.1.1"
json-schema-traverse: "npm:^1.0.0"
require-from-string: "npm:^2.0.2"
fast-json-stable-stringify: "npm:^2.0.0"
json-schema-traverse: "npm:^0.4.1"
uri-js: "npm:^4.2.2"
checksum: ac4f72adf727ee425e049bc9d8b31d4a57e1c90da8d28bcd23d60781b12fcd6fc3d68db5df16994c57b78b94eed7988f5a6b482fd376dc5b084125e20a0a622e
checksum: 41e23642cbe545889245b9d2a45854ebba51cda6c778ebced9649420d9205f2efb39cb43dbc41e358409223b1ea43303ae4839db682c848b891e4811da1a5a71
languageName: node
linkType: hard

Expand Down