From 07cbd6723b7ce60b28476579ecfb981304f0d7f2 Mon Sep 17 00:00:00 2001 From: Oliver Salzburg Date: Tue, 21 Nov 2023 11:59:54 +0100 Subject: [PATCH] feat(core): Settings profiles experiments These have to happen on main to have the schema published for iteration. --- .eslintrc.js => .eslintrc.cjs | 0 LICENSE.md => LICENSE | 0 .lintstagedrc.js => lint-staged.config.js | 3 +- package.json | 1 + packages/kitten-scientists/package.json | 2 + .../kitten-scientists/source/state/State.ts | 14 +++ .../source/state/StateLoader.ts | 89 +++++++++++++++++++ presets/build-fields.json | 14 +++ presets/build-huts.json | 14 +++ presets/enable-engine.json | 8 ++ presets/experiment.json | 21 ++--- .prettierrc.js => prettier.config.js | 8 +- schemas/.scripts/load-profile.js | 8 ++ schemas/draft-01/settings-profile.schema.json | 12 ++- yarn.lock | 2 + 15 files changed, 171 insertions(+), 25 deletions(-) rename .eslintrc.js => .eslintrc.cjs (100%) rename LICENSE.md => LICENSE (100%) rename .lintstagedrc.js => lint-staged.config.js (64%) create mode 100644 packages/kitten-scientists/source/state/State.ts create mode 100644 packages/kitten-scientists/source/state/StateLoader.ts create mode 100644 presets/build-fields.json create mode 100644 presets/build-huts.json create mode 100644 presets/enable-engine.json rename .prettierrc.js => prettier.config.js (51%) create mode 100644 schemas/.scripts/load-profile.js diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE diff --git a/.lintstagedrc.js b/lint-staged.config.js similarity index 64% rename from .lintstagedrc.js rename to lint-staged.config.js index c08b5db7c..b54591cb7 100644 --- a/.lintstagedrc.js +++ b/lint-staged.config.js @@ -1,4 +1,5 @@ -module.exports = { +/** @type {import("lint-staged").Config} */ +export default { "package.json": "yarn prettier-package-json --write", "*.{js,json,md,sh,ts,yml}": "prettier --write", }; diff --git a/package.json b/package.json index 6f7257756..d8698b243 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "bugs": { "url": "https://github.com/kitten-science/kitten-scientists/issues" }, + "type": "module", "scripts": { "build:all": "tsc --build", "clean:all": "rm -rf packages/*/build packages/*/tsconfig.tsbuildinfo", diff --git a/packages/kitten-scientists/package.json b/packages/kitten-scientists/package.json index 2e89ccc25..698c3a80e 100644 --- a/packages/kitten-scientists/package.json +++ b/packages/kitten-scientists/package.json @@ -35,6 +35,8 @@ "@types/dojo": "1.9.48", "@types/jquery": "3.5.28", "@types/semver": "7.5.6", + "@types/web": "0.0.119", + "json-schema-to-ts": "2.9.2", "typescript": "5.3.2", "vite": "5.0.0", "vite-plugin-userscript": "0.1.3" diff --git a/packages/kitten-scientists/source/state/State.ts b/packages/kitten-scientists/source/state/State.ts new file mode 100644 index 000000000..3f76d0e23 --- /dev/null +++ b/packages/kitten-scientists/source/state/State.ts @@ -0,0 +1,14 @@ +import { StateLoader } from "./StateLoader.js"; + +export class State { + readonly originUrl: string; + + constructor(originUrl: string) { + this.originUrl = originUrl; + } + + async resolve() { + const loader = new StateLoader(this); + return await loader.load(); + } +} diff --git a/packages/kitten-scientists/source/state/StateLoader.ts b/packages/kitten-scientists/source/state/StateLoader.ts new file mode 100644 index 000000000..9e37c5506 --- /dev/null +++ b/packages/kitten-scientists/source/state/StateLoader.ts @@ -0,0 +1,89 @@ +import { errorToRecord, unknownToError } from "@oliversalzburg/js-utils"; +import { State } from "./State.js"; + +export interface ResolverStateView { + extends?: Array; +} +export interface ReportEntry { + message: string; + context: Record | undefined; +} + +export class LoaderReport { + readonly origin: string; + #store = new Map>(); + + constructor(origin: string) { + this.origin = origin; + } + + #getStoreEntry(entry: string) { + if (!this.#store.has(entry)) { + this.#store.set(entry, new Array()); + } + return this.#store.get(entry); + } + + request(message: string, context?: Record) { + this.#getStoreEntry("anonymous")?.push({ message, context }); + } + failure(message: string, context?: Record) { + this.#getStoreEntry("anonymous")?.push({ message, context }); + } + + log() { + for (const [profileId, entry] of this.#store.entries()) { + console.log(profileId); + console.dir(entry); + } + } +} + +export class StateLoader { + readonly report: LoaderReport; + readonly state: State; + #children = new Array(); + #data: Record | undefined; + + get data() { + return this.#data; + } + + constructor(state: State, report = new LoaderReport(state.originUrl)) { + this.state = state; + this.report = report; + } + + async load(): Promise { + this.report.request(`Resolving ${this.state.originUrl}...`); + + let data; + try { + const request = await fetch(this.state.originUrl); + data = (await request.json()) as Record; + } catch (error) { + this.report.failure( + `Error while resolving ${this.state.originUrl}!`, + errorToRecord(unknownToError(error)), + ); + return this.report; + } + + // TODO: Validate again JSON schema. + this.#data = data; + await this.#resolveBases(this.#data); + return this.report; + } + + async #resolveBases(state: ResolverStateView) { + if (!Array.isArray(state.extends) || state.extends.length === 0) { + return Promise.resolve([]); + } + + for (const base of state.extends) { + this.#children.push(new State(base)); + } + + await Promise.all(this.#children.map(base => new StateLoader(base, this.report).load())); + } +} diff --git a/presets/build-fields.json b/presets/build-fields.json new file mode 100644 index 000000000..8c38b65f5 --- /dev/null +++ b/presets/build-fields.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://github.com/kitten-science/kitten-scientists/raw/main/schemas/draft-01/settings-profile.schema.json", + "v": "2.0.0-beta.8", + "bonfire": { + "enabled": true, + "trigger": 0, + "buildings": { + "hut": { + "enabled": true, + "max": -1 + } + } + } +} diff --git a/presets/build-huts.json b/presets/build-huts.json new file mode 100644 index 000000000..133149483 --- /dev/null +++ b/presets/build-huts.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://github.com/kitten-science/kitten-scientists/raw/main/schemas/draft-01/settings-profile.schema.json", + "v": "2.0.0-beta.8", + "bonfire": { + "enabled": true, + "trigger": 0, + "buildings": { + "field": { + "enabled": true, + "max": -1 + } + } + } +} diff --git a/presets/enable-engine.json b/presets/enable-engine.json new file mode 100644 index 000000000..3415080e6 --- /dev/null +++ b/presets/enable-engine.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://github.com/kitten-science/kitten-scientists/raw/main/schemas/draft-01/settings-profile.schema.json", + "v": "2.0.0-beta.8", + "engine": { + "enabled": true, + "interval": 1000 + } +} diff --git a/presets/experiment.json b/presets/experiment.json index 187fe2f59..ef05795fc 100644 --- a/presets/experiment.json +++ b/presets/experiment.json @@ -1,19 +1,10 @@ { "$schema": "https://github.com/kitten-science/kitten-scientists/raw/main/schemas/draft-01/settings-profile.schema.json", "v": "2.0.0-beta.8", - "baseline": "https://github.com/kitten-science/kitten-scientists/raw/main/baselines/absolute-zero.json", - "engine": { - "enabled": false, - "interval": 1000 - }, - "bonfire": { - "enabled": true, - "trigger": 0, - "buildings": { - "field": { - "enabled": true, - "max": -1 - } - } - } + "extends": [ + "https://github.com/kitten-science/kitten-scientists/raw/main/baselines/absolute-zero.json", + "https://github.com/kitten-science/kitten-scientists/raw/main/presets/enable-engine.json", + "https://github.com/kitten-science/kitten-scientists/raw/main/presets/build-fields.json", + "https://github.com/kitten-science/kitten-scientists/raw/main/presets/build-huts.json" + ] } diff --git a/.prettierrc.js b/prettier.config.js similarity index 51% rename from .prettierrc.js rename to prettier.config.js index 10f4057f1..73e8b5760 100644 --- a/.prettierrc.js +++ b/prettier.config.js @@ -1,10 +1,8 @@ -module.exports = { +/** @type {import("prettier").Config} */ +export default { printWidth: 100, arrowParens: "avoid", - plugins: [ - require.resolve("prettier-plugin-organize-imports"), - require.resolve("prettier-plugin-sh"), - ], + plugins: ["prettier-plugin-organize-imports"], overrides: [ { files: "*.md", diff --git a/schemas/.scripts/load-profile.js b/schemas/.scripts/load-profile.js new file mode 100644 index 000000000..9c5837606 --- /dev/null +++ b/schemas/.scripts/load-profile.js @@ -0,0 +1,8 @@ +import { State } from "../../packages/kitten-scientists/build/state/State.js"; + +const state = new State( + "https://raw.githubusercontent.com/kitten-science/kitten-scientists/main/presets/experiment.json", +); + +const report = await state.resolve(); +report.log(); diff --git a/schemas/draft-01/settings-profile.schema.json b/schemas/draft-01/settings-profile.schema.json index b26cfe004..4fb6fbc44 100644 --- a/schemas/draft-01/settings-profile.schema.json +++ b/schemas/draft-01/settings-profile.schema.json @@ -14,9 +14,13 @@ "pattern": "^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(?:-.+)$", "type": "string" }, - "baseline": { - "description": "The URL of a KS settings baseline that this profile extends.", - "type": "string" + "extends": { + "description": "The URLs of KS settings profiles to extend.", + "items": { + "pattern": "^https://", + "type": "string" + }, + "type": "array" }, "engine": { "additionalProperties": false, @@ -1861,7 +1865,7 @@ "type": "object" } }, - "required": ["$schema", "v", "baseline"], + "required": ["$schema", "v"], "minProperties": 3, "type": "object" } diff --git a/yarn.lock b/yarn.lock index 71fa66e46..21511a7ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -745,7 +745,9 @@ __metadata: "@types/dojo": "npm:1.9.48" "@types/jquery": "npm:3.5.28" "@types/semver": "npm:7.5.6" + "@types/web": "npm:0.0.119" date-fns: "npm:2.30.0" + json-schema-to-ts: "npm:2.9.2" semver: "npm:7.5.4" tslib: "npm:2.6.2" typescript: "npm:5.3.2"