Revert "API: generate /api/state documentation from UI types" (#32109)
This commit is contained in:
parent
cd9812ef14
commit
9c88523682
42 changed files with 3359 additions and 8732 deletions
2
.github/workflows/documentation.yml
vendored
2
.github/workflows/documentation.yml
vendored
|
|
@ -58,7 +58,7 @@ jobs:
|
|||
- name: Prepare OpenAPI spec
|
||||
run: |
|
||||
mkdir -p ./openapi-deploy
|
||||
npx --yes @redocly/cli@2 bundle ./server/openapi.yaml -o ./openapi-deploy/openapi.yaml
|
||||
cp ./server/openapi.yaml ./openapi-deploy/openapi.yaml
|
||||
|
||||
- name: Deploy OpenAPI spec to docs repo
|
||||
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
|
||||
|
|
|
|||
3
.github/workflows/openapi-validate.yml
vendored
3
.github/workflows/openapi-validate.yml
vendored
|
|
@ -8,7 +8,6 @@ on:
|
|||
pull_request:
|
||||
paths:
|
||||
- "server/openapi.yaml"
|
||||
- "server/openapi.state.yaml"
|
||||
- ".github/workflows/openapi-validate.yml"
|
||||
|
||||
jobs:
|
||||
|
|
@ -23,4 +22,4 @@ jobs:
|
|||
with:
|
||||
go-version: stable
|
||||
- name: Validate OpenAPI spec
|
||||
run: go run github.com/getkin/kin-openapi/cmd/validate@v0.133.0 -ext -- server/openapi.yaml
|
||||
run: go run github.com/getkin/kin-openapi/cmd/validate@v0.133.0 -- server/openapi.yaml
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -18,7 +18,6 @@ testdata/**/*
|
|||
!assets/js/**/*.yaml
|
||||
!package*.json
|
||||
!evcc.dist.yaml
|
||||
!server/openapi.state.yaml
|
||||
!.coderabbit.yaml
|
||||
!tests/**/*.evcc.yaml
|
||||
!tests/**/*.tpl.yaml
|
||||
|
|
|
|||
|
|
@ -38,7 +38,9 @@ import formatter from "@/mixins/formatter";
|
|||
import minuteTicker from "@/mixins/minuteTicker";
|
||||
import { optionStep, fmtEnergy } from "@/utils/energyOptions.ts";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { CURRENCY, Forecast, PlanStrategy, Vehicle } from "@/types/evcc";
|
||||
import type { CURRENCY, Vehicle } from "@/types/evcc";
|
||||
import type { PlanStrategy } from "./types";
|
||||
import type { Forecast } from "@/types/evcc.ts";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChargingPlan",
|
||||
|
|
|
|||
|
|
@ -47,17 +47,13 @@ import GenericModal from "../Helper/GenericModal.vue";
|
|||
import PlansSettings from "./PlansSettings.vue";
|
||||
import api from "@/api";
|
||||
import type {
|
||||
CURRENCY,
|
||||
Forecast,
|
||||
PlanStrategy,
|
||||
RepeatingPlan,
|
||||
SMART_COST_TYPE,
|
||||
StaticEnergyPlan,
|
||||
StaticPlan,
|
||||
StaticSocPlan,
|
||||
UiLoadpoint,
|
||||
Vehicle,
|
||||
} from "@/types/evcc";
|
||||
} from "./types";
|
||||
import type { CURRENCY, Forecast, SMART_COST_TYPE, UiLoadpoint, Vehicle } from "@/types/evcc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChargingPlanModal",
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent } from "vue";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import type { PlanStrategy } from "@/types/evcc";
|
||||
import type { PlanStrategy } from "./types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChargingPlanStrategy",
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import PlanRepeatingSettings from "./PlanRepeatingSettings.vue";
|
|||
import deepEqual from "@/utils/deepEqual";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { RepeatingPlan } from "@/types/evcc";
|
||||
import type { RepeatingPlan } from "./types";
|
||||
|
||||
const DEFAULT_WEEKDAYS = [1, 2, 3, 4, 5];
|
||||
const DEFAULT_TARGET_TIME = "07:00";
|
||||
|
|
|
|||
|
|
@ -81,18 +81,16 @@ import api from "@/api";
|
|||
import deepEqual from "@/utils/deepEqual";
|
||||
import { debounceLeading } from "@/utils/debounceLeading";
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import type { Vehicle, CURRENCY, Forecast } from "@/types/evcc";
|
||||
import type {
|
||||
CURRENCY,
|
||||
Forecast,
|
||||
StaticPlan,
|
||||
RepeatingPlan,
|
||||
PlanWrapper,
|
||||
StaticSocPlan,
|
||||
StaticEnergyPlan,
|
||||
PlanResponse,
|
||||
PlanStrategy,
|
||||
PlanWrapper,
|
||||
RepeatingPlan,
|
||||
StaticEnergyPlan,
|
||||
StaticPlan,
|
||||
StaticSocPlan,
|
||||
Vehicle,
|
||||
} from "@/types/evcc";
|
||||
} from "./types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChargingPlansSettings",
|
||||
|
|
|
|||
|
|
@ -21,7 +21,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import type { PlanWrapper, Tariff } from "@/types/evcc";
|
||||
import type { PlanWrapper } from "./types";
|
||||
import type { Tariff } from "@/types/evcc";
|
||||
|
||||
export default defineComponent({
|
||||
name: "ChargingPlanWarnings",
|
||||
|
|
|
|||
39
assets/js/components/ChargingPlans/types.d.ts
vendored
Normal file
39
assets/js/components/ChargingPlans/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import type { Rate } from "@/types/evcc";
|
||||
|
||||
export interface RepeatingPlan {
|
||||
weekdays: number[];
|
||||
time: string;
|
||||
tz: string; // timezone like "Europe/Berlin"
|
||||
soc: number;
|
||||
active: boolean;
|
||||
}
|
||||
|
||||
export interface PlanWrapper {
|
||||
planId: number;
|
||||
planTime: Date;
|
||||
duration: number;
|
||||
plan: Rate[] | null;
|
||||
power: number;
|
||||
}
|
||||
|
||||
export interface PlanResponse {
|
||||
status: number;
|
||||
data: PlanWrapper;
|
||||
}
|
||||
|
||||
export type StaticPlan = StaticSocPlan | StaticEnergyPlan;
|
||||
|
||||
export interface StaticSocPlan {
|
||||
soc: number;
|
||||
time: Date;
|
||||
}
|
||||
|
||||
export interface StaticEnergyPlan {
|
||||
energy: number;
|
||||
time: Date;
|
||||
}
|
||||
|
||||
export interface PlanStrategy {
|
||||
continuous: boolean;
|
||||
precondition: number;
|
||||
}
|
||||
|
|
@ -42,8 +42,9 @@ import "chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm";
|
|||
import { registerChartComponents, commonOptions } from "../Sessions/chartConfig";
|
||||
import formatter, { POWER_UNIT } from "@/mixins/formatter";
|
||||
import colors, { lighterColor } from "@/colors";
|
||||
import type { CURRENCY, ForecastSlot, SolarDetails, TimeseriesEntry } from "@/types/evcc";
|
||||
import type { CURRENCY } from "@/types/evcc";
|
||||
import { ForecastType, highestSlotIndexByDay } from "@/utils/forecast";
|
||||
import type { ForecastSlot, SolarDetails, TimeseriesEntry } from "./types";
|
||||
|
||||
registerChartComponents([
|
||||
BarController,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,8 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import type { CURRENCY, ForecastSlot } from "@/types/evcc";
|
||||
import type { CURRENCY } from "@/types/evcc";
|
||||
import type { ForecastSlot } from "./types";
|
||||
import { isStaticTariff } from "@/utils/forecast";
|
||||
|
||||
const MAX_HOURS = 96;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ import {
|
|||
import colors, { lighterColor } from "@/colors";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import chartMixin from "./chartMixin";
|
||||
import type { CURRENCY, ForecastSlot } from "@/types/evcc";
|
||||
import type { CURRENCY } from "@/types/evcc";
|
||||
import type { ForecastSlot } from "./types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "PriceChart",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import colors, { lighterColor } from "@/colors";
|
|||
import formatter, { POWER_UNIT } from "@/mixins/formatter";
|
||||
import chartMixin from "./chartMixin";
|
||||
import { highestSlotIndexByDay } from "@/utils/forecast";
|
||||
import type { SolarDetails, TimeseriesEntry } from "@/types/evcc";
|
||||
import type { SolarDetails, TimeseriesEntry } from "./types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SolarChart",
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import formatter, { POWER_UNIT } from "@/mixins/formatter";
|
||||
import type { SolarDetails } from "@/types/evcc";
|
||||
import type { SolarDetails } from "./types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SolarDetails",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import {
|
|||
import colors from "@/colors";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import chartMixin from "./chartMixin";
|
||||
import type { ForecastSlot } from "@/types/evcc";
|
||||
import type { ForecastSlot } from "./types";
|
||||
|
||||
export type ValueChartType = "co2" | "temperature";
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
<script lang="ts">
|
||||
import { defineComponent, type PropType } from "vue";
|
||||
import formatter from "@/mixins/formatter";
|
||||
import type { ForecastSlot } from "@/types/evcc";
|
||||
import type { ForecastSlot } from "./types";
|
||||
import type { ValueChartType } from "./ValueChart.vue";
|
||||
|
||||
const MAX_HOURS = 96;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import * as echarts from "echarts/core";
|
||||
import colors from "@/colors";
|
||||
import escapeHtml from "@/utils/escapeHtml";
|
||||
import type { ForecastSlot } from "@/types/evcc";
|
||||
import type { ForecastSlot } from "./types";
|
||||
import { BarChart, LineChart } from "echarts/charts";
|
||||
import {
|
||||
GridComponent,
|
||||
|
|
|
|||
27
assets/js/components/Forecast/types.ts
Normal file
27
assets/js/components/Forecast/types.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
export function isForecastSlot(obj?: TimeseriesEntry | ForecastSlot): obj is ForecastSlot {
|
||||
return (obj as ForecastSlot).start !== undefined;
|
||||
}
|
||||
|
||||
export interface TimeseriesEntry {
|
||||
val: number;
|
||||
ts: string;
|
||||
}
|
||||
|
||||
export interface ForecastSlot {
|
||||
start: string;
|
||||
end: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface EnergyByDay {
|
||||
energy: number;
|
||||
complete: boolean;
|
||||
}
|
||||
|
||||
export interface SolarDetails {
|
||||
scale?: number;
|
||||
today?: EnergyByDay;
|
||||
tomorrow?: EnergyByDay;
|
||||
dayAfterTomorrow?: EnergyByDay;
|
||||
timeseries?: TimeseriesEntry[];
|
||||
}
|
||||
|
|
@ -129,9 +129,9 @@ import type {
|
|||
SMART_COST_TYPE,
|
||||
BATTERY_MODE,
|
||||
LoadpointUi,
|
||||
PlanStrategy,
|
||||
LoadpointSuggestion,
|
||||
} from "@/types/evcc";
|
||||
import type { PlanStrategy } from "@/components/ChargingPlans/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Loadpoint",
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ import { defineComponent, type PropType } from "vue";
|
|||
import api from "@/api";
|
||||
import { setLoadpointLastSmartCostLimit } from "@/uiLoadpoints";
|
||||
import settings from "@/settings";
|
||||
import { type CURRENCY, type ForecastSlot, SMART_COST_TYPE } from "@/types/evcc";
|
||||
import { type CURRENCY, SMART_COST_TYPE } from "@/types/evcc";
|
||||
import { type ForecastSlot } from "../Forecast/types";
|
||||
|
||||
export default defineComponent({
|
||||
name: "SmartCostLimit",
|
||||
|
|
|
|||
|
|
@ -105,7 +105,7 @@ export default defineComponent({
|
|||
},
|
||||
vehicleOptions(): SelectOption<string>[] {
|
||||
return this.vehicles.map((v) => ({
|
||||
name: v.name ?? "",
|
||||
name: v.name,
|
||||
value: v.title,
|
||||
}));
|
||||
},
|
||||
|
|
|
|||
|
|
@ -99,9 +99,9 @@ import {
|
|||
type VehicleStatus,
|
||||
type Vehicle,
|
||||
type LoadpointUi,
|
||||
type PlanStrategy,
|
||||
type LoadpointSuggestion,
|
||||
} from "@/types/evcc";
|
||||
import type { PlanStrategy } from "@/components/ChargingPlans/types";
|
||||
import BatteryBoostButton from "../Loadpoints/BatteryBoostButton.vue";
|
||||
import type ChargingPlanModal from "../ChargingPlans/ChargingPlanModal.vue";
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
import type { ForecastSlot, TimeseriesEntry, SolarDetails } from "@/types/evcc";
|
||||
import type { ForecastSlot, TimeseriesEntry, SolarDetails } from "../components/Forecast/types";
|
||||
import deepCopy from "./deepClone";
|
||||
|
||||
export enum ForecastType {
|
||||
|
|
|
|||
28
cmd/openapi/openapi.go
Normal file
28
cmd/openapi/openapi.go
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/getkin/kin-openapi/openapi3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
doc, err := openapi3.NewLoader().LoadFromFile(os.Args[1])
|
||||
if err != nil {
|
||||
log.Fatal("failed to load OpenAPI spec:", err)
|
||||
}
|
||||
|
||||
// omit servers
|
||||
doc.Servers = nil
|
||||
|
||||
b, err := json.MarshalIndent(doc, "", " ")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(os.Args[2], b, 0o644); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,6 @@ export default [
|
|||
"no-param-reassign": "error",
|
||||
"vue/block-lang": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-empty-object-type": ["error", { allowInterfaces: "with-single-extends" }],
|
||||
},
|
||||
}
|
||||
).map((config) => ({
|
||||
|
|
|
|||
1
go.mod
1
go.mod
|
|
@ -257,6 +257,7 @@ require (
|
|||
tool (
|
||||
github.com/dmarkham/enumer
|
||||
github.com/evcc-io/evcc/cmd/implement
|
||||
github.com/evcc-io/evcc/cmd/openapi
|
||||
github.com/evcc-io/openapi-mcp/cmd/openapi-mcp
|
||||
github.com/gokrazy/tools/cmd/gok
|
||||
go.uber.org/mock/mockgen
|
||||
|
|
|
|||
412
package-lock.json
generated
412
package-lock.json
generated
|
|
@ -39,7 +39,6 @@
|
|||
"@eslint/js": "^9.39.4",
|
||||
"@jest/types": "^30.4.1",
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@redocly/openapi-core": "^2.38.0",
|
||||
"@storybook/vue3": "^10.4.4",
|
||||
"@storybook/vue3-vite": "^10.4.4",
|
||||
"@types/body-parser": "^1.19.6",
|
||||
|
|
@ -57,8 +56,6 @@
|
|||
"@vue/eslint-config-typescript": "^14.8.0",
|
||||
"@vue/test-utils": "^2.4.11",
|
||||
"@vue/tsconfig": "^0.9.1",
|
||||
"ajv": "^8.20.0",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"body-parser": "^2.2.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.4",
|
||||
|
|
@ -80,7 +77,6 @@
|
|||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"storybook": "^10.4.6",
|
||||
"terser": "^5.48.0",
|
||||
"ts-json-schema-generator": "^2.9.0",
|
||||
"tsx": "^4.22.4",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint-language-service": "^5.0.5",
|
||||
|
|
@ -90,8 +86,7 @@
|
|||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-tsc": "^3.3.5",
|
||||
"wait-on": "^9.0.10",
|
||||
"ws": "^8.21.0",
|
||||
"yaml": "^2.9.0"
|
||||
"ws": "^8.21.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=26.0.0",
|
||||
|
|
@ -2349,23 +2344,6 @@
|
|||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/ajv": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
|
||||
"integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/globals": {
|
||||
"version": "14.0.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
|
||||
|
|
@ -2379,13 +2357,6 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@eslint/js": {
|
||||
"version": "9.39.4",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
|
||||
|
|
@ -3624,88 +3595,6 @@
|
|||
"url": "https://opencollective.com/popperjs"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/ajv": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.18.3.tgz",
|
||||
"integrity": "sha512-l42u0of3hY98sN2A+M4qTX1O/KrpgGH32Hu9kP2GtHyD5Dfqq86PKFLe5dwaD8DEnNmlOlll2BAmeEtf0DaySg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/config": {
|
||||
"version": "0.50.1",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/config/-/config-0.50.1.tgz",
|
||||
"integrity": "sha512-TrdZUK50baxIjJB/y2ROOqfg35ex+GbkHPoJ40kXQXKMOjrfxfYQ9IliiaLqT5H/Y8qbajPrYQN2DLfvcMaHSw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"json-schema-to-ts": "2.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/openapi-core": {
|
||||
"version": "2.38.0",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/openapi-core/-/openapi-core-2.38.0.tgz",
|
||||
"integrity": "sha512-VvUCotkNqVpJwW0zSzxp012a6dgv7dmgeCRCNk3BZ6tpwglLG93Vq6s1Hz6bnCexYwRjWU8NBvS/JBmDewr79Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@redocly/ajv": "^8.18.1",
|
||||
"@redocly/config": "^0.50.1",
|
||||
"ajv": "npm:@redocly/ajv@8.18.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"colorette": "^1.2.0",
|
||||
"graphql": "^16.14.1",
|
||||
"js-levenshtein": "^1.1.6",
|
||||
"js-yaml": "^4.2.0",
|
||||
"picomatch": "^4.0.4",
|
||||
"pluralize": "^8.0.0",
|
||||
"yaml-ast-parser": "0.0.43"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.12.0 || >=20.19.0 <21.0.0",
|
||||
"npm": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/openapi-core/node_modules/ajv": {
|
||||
"name": "@redocly/ajv",
|
||||
"version": "8.18.1",
|
||||
"resolved": "https://registry.npmjs.org/@redocly/ajv/-/ajv-8.18.1.tgz",
|
||||
"integrity": "sha512-Ifm/pP/tul1qmAecpbVxCBluVE32rKfjf8gYXH4xI2gCv9mRWFhJMHzkPDM4TXlxwPQYIFegymlsy8lXz7optA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/@redocly/openapi-core/node_modules/picomatch": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
|
||||
"integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/@reteps/dockerfmt": {
|
||||
"version": "0.5.4",
|
||||
"resolved": "https://registry.npmjs.org/@reteps/dockerfmt/-/dockerfmt-0.5.4.tgz",
|
||||
|
|
@ -5475,40 +5364,22 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ajv": {
|
||||
"version": "8.20.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
||||
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
|
||||
"integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-uri": "^3.0.1",
|
||||
"json-schema-traverse": "^1.0.0",
|
||||
"require-from-string": "^2.0.2"
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/ajv-formats": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
|
||||
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"ajv": "^8.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"ajv": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz",
|
||||
|
|
@ -6427,13 +6298,6 @@
|
|||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz",
|
||||
"integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
|
|
@ -7697,23 +7561,6 @@
|
|||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/ajv": {
|
||||
"version": "6.15.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
|
||||
"integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-deep-equal": "^3.1.1",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"json-schema-traverse": "^0.4.1",
|
||||
"uri-js": "^4.2.2"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/epoberezkin"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/eslint-visitor-keys": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
|
||||
|
|
@ -7727,13 +7574,6 @@
|
|||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint/node_modules/json-schema-traverse": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/esm-resolve": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/esm-resolve/-/esm-resolve-1.0.11.tgz",
|
||||
|
|
@ -7953,23 +7793,6 @@
|
|||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/fast-uri": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz",
|
||||
"integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fastify"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/fastify"
|
||||
}
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.20.1",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
|
||||
|
|
@ -8378,16 +8201,6 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/graphql": {
|
||||
"version": "16.14.2",
|
||||
"resolved": "https://registry.npmjs.org/graphql/-/graphql-16.14.2.tgz",
|
||||
"integrity": "sha512-Chq1s4CY7jmh8gO2qvLIJyfCDIN+EHLFW/9iShnp1z8FjBQMoodWP1kDC36VAMXXIvAjj4ARa7ntfAV2BrjsbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/happy-dom": {
|
||||
"version": "20.10.6",
|
||||
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.10.6.tgz",
|
||||
|
|
@ -9298,16 +9111,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/js-levenshtein": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz",
|
||||
"integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-sdsl": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.3.0.tgz",
|
||||
|
|
@ -9382,25 +9185,10 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/json-schema-to-ts": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-2.7.2.tgz",
|
||||
"integrity": "sha512-R1JfqKqbBR4qE8UyBR56Ms30LL62/nlhoz+1UkfI/VE7p54Awu919FZ6ZUPG8zIa3XB65usPJgr1ONVncUGSaQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.18.3",
|
||||
"@types/json-schema": "^7.0.9",
|
||||
"ts-algebra": "^1.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
|
||||
"integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
|
@ -10213,16 +10001,6 @@
|
|||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/normalize-path": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
|
|
@ -10744,16 +10522,6 @@
|
|||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/pluralize": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz",
|
||||
"integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
|
||||
|
|
@ -11427,16 +11195,6 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-from-string": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
|
||||
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/require-main-filename": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
|
||||
|
|
@ -11813,16 +11571,6 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-stable-stringify": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
|
||||
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
|
|
@ -12765,13 +12513,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-algebra": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-1.2.2.tgz",
|
||||
"integrity": "sha512-kloPhf1hq3JbCPOTYoOWDKxebWjNb2o/LKnNfkWhxVVisFFmMJPPdJeGoGmM+iRLyoXAR61e08Pb+vUXINg8aA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
|
||||
|
|
@ -12795,130 +12536,6 @@
|
|||
"node": ">=6.10"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-json-schema-generator": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-json-schema-generator/-/ts-json-schema-generator-2.9.0.tgz",
|
||||
"integrity": "sha512-NR5ZE108uiPtBHBJNGnhwoUaUx5vWTDJzDFG9YlRoqxPU76n+5FClRh92dcGgysbe1smRmYalM9Saj97GW1J4Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15",
|
||||
"commander": "^14.0.3",
|
||||
"glob": "^13.0.6",
|
||||
"json5": "^2.2.3",
|
||||
"normalize-path": "^3.0.0",
|
||||
"safe-stable-stringify": "^2.5.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"bin": {
|
||||
"ts-json-schema-generator": "bin/ts-json-schema-generator.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=22.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-json-schema-generator/node_modules/balanced-match": {
|
||||
"version": "4.0.4",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
|
||||
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-json-schema-generator/node_modules/brace-expansion": {
|
||||
"version": "5.0.7",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
|
||||
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-json-schema-generator/node_modules/commander": {
|
||||
"version": "14.0.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
|
||||
"integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-json-schema-generator/node_modules/glob": {
|
||||
"version": "13.0.6",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
|
||||
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"minimatch": "^10.2.2",
|
||||
"minipass": "^7.1.3",
|
||||
"path-scurry": "^2.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-json-schema-generator/node_modules/lru-cache": {
|
||||
"version": "11.5.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz",
|
||||
"integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-json-schema-generator/node_modules/minimatch": {
|
||||
"version": "10.2.5",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
|
||||
"integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^5.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-json-schema-generator/node_modules/path-scurry": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
|
||||
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^11.0.0",
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18 || 20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-json-schema-generator/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||
"dev": true,
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/ts-map": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ts-map/-/ts-map-1.0.3.tgz",
|
||||
|
|
@ -14629,13 +14246,6 @@
|
|||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml-ast-parser": {
|
||||
"version": "0.0.43",
|
||||
"resolved": "https://registry.npmjs.org/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz",
|
||||
"integrity": "sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "15.4.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
|
||||
|
|
|
|||
10
package.json
10
package.json
|
|
@ -3,7 +3,7 @@
|
|||
"description": "evcc UI",
|
||||
"author": "evcc-io",
|
||||
"scripts": {
|
||||
"build": "npm run openapi && vite build",
|
||||
"build": "vite build",
|
||||
"test": "cross-env TZ=Europe/Berlin NODE_OPTIONS=--no-experimental-webstorage vitest",
|
||||
"lint": "npm run lint:prettier && npm run lint:eslint && npm run lint:tsc && npm run lint:i18n",
|
||||
"lint:prettier": "prettier assets tests **/*.{yaml,sh} i18n/*.json --write",
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
"lint:tsc": "vue-tsc --noEmit",
|
||||
"lint:i18n": "tsx i18n/check.ts",
|
||||
"dev": "vite",
|
||||
"openapi": "tsx scripts/state-schema/index.ts",
|
||||
"playwright": "playwright test --ui",
|
||||
"playwright:ci": "cross-env CI=true playwright test",
|
||||
"simulator": "vite tests/simulator",
|
||||
|
|
@ -59,7 +58,6 @@
|
|||
"@eslint/js": "^9.39.4",
|
||||
"@jest/types": "^30.4.1",
|
||||
"@playwright/test": "^1.60.0",
|
||||
"@redocly/openapi-core": "^2.38.0",
|
||||
"@storybook/vue3": "^10.4.4",
|
||||
"@storybook/vue3-vite": "^10.4.4",
|
||||
"@types/body-parser": "^1.19.6",
|
||||
|
|
@ -77,8 +75,6 @@
|
|||
"@vue/eslint-config-typescript": "^14.8.0",
|
||||
"@vue/test-utils": "^2.4.11",
|
||||
"@vue/tsconfig": "^0.9.1",
|
||||
"ajv": "^8.20.0",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"body-parser": "^2.2.2",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.4",
|
||||
|
|
@ -100,7 +96,6 @@
|
|||
"rollup-plugin-visualizer": "^7.0.1",
|
||||
"storybook": "^10.4.6",
|
||||
"terser": "^5.48.0",
|
||||
"ts-json-schema-generator": "^2.9.0",
|
||||
"tsx": "^4.22.4",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint-language-service": "^5.0.5",
|
||||
|
|
@ -110,7 +105,6 @@
|
|||
"vue-hot-reload-api": "^2.3.4",
|
||||
"vue-tsc": "^3.3.5",
|
||||
"wait-on": "^9.0.10",
|
||||
"ws": "^8.21.0",
|
||||
"yaml": "^2.9.0"
|
||||
"ws": "^8.21.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +0,0 @@
|
|||
import { buildSchemas } from "./schemas";
|
||||
import { writeStateSchemas, bundleMcpJson } from "./openapi";
|
||||
|
||||
const command = process.argv[2] ?? "generate";
|
||||
|
||||
switch (command) {
|
||||
case "generate": {
|
||||
const schemas = buildSchemas();
|
||||
await writeStateSchemas(schemas);
|
||||
await bundleMcpJson();
|
||||
console.log(
|
||||
`${Object.keys(schemas.defs).length} state schemas → openapi.state.yaml + mcp/openapi.json`
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "dump": {
|
||||
console.log(JSON.stringify(buildSchemas().defs, null, 2));
|
||||
break;
|
||||
}
|
||||
case "validate": {
|
||||
const source = process.argv[3];
|
||||
if (!source) {
|
||||
console.error("usage: npm run openapi -- validate <payload.json | url>");
|
||||
process.exit(1);
|
||||
}
|
||||
const { validate } = await import("./validate");
|
||||
if (!(await validate(source))) process.exit(1);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.error(`unknown command: ${command}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
import { readFileSync, writeFileSync } from "node:fs";
|
||||
import { parse, stringify } from "yaml";
|
||||
import prettier from "prettier";
|
||||
import { bundle, createConfig } from "@redocly/openapi-core";
|
||||
import type { StateSchemas } from "./schemas";
|
||||
|
||||
const OPENAPI_PATH = "server/openapi.yaml";
|
||||
const STATE_PATH = "server/openapi.state.yaml";
|
||||
const MCP_JSON_PATH = "server/mcp/openapi.json";
|
||||
|
||||
const HEADER = `# GENERATED FILE - DO NOT EDIT (source: assets/js/types/evcc.ts, update: npm run openapi)
|
||||
`;
|
||||
|
||||
export async function writeStateSchemas(schemas: StateSchemas): Promise<void> {
|
||||
const root = parse(readFileSync(OPENAPI_PATH, "utf8"));
|
||||
const handWritten = new Set(Object.keys(root.components?.schemas ?? {}));
|
||||
for (const name of Object.keys(schemas.defs)) {
|
||||
if (handWritten.has(name)) {
|
||||
throw new Error(`generated schema "${name}" collides with hand-written component`);
|
||||
}
|
||||
}
|
||||
|
||||
const doc =
|
||||
HEADER +
|
||||
stringify(
|
||||
{ components: { schemas: schemas.defs } },
|
||||
{ lineWidth: 0, aliasDuplicateObjects: false }
|
||||
);
|
||||
|
||||
const config = await prettier.resolveConfig(STATE_PATH);
|
||||
writeFileSync(STATE_PATH, await prettier.format(doc, { ...config, filepath: STATE_PATH }));
|
||||
}
|
||||
|
||||
// inline the multi-file spec into the single json embedded by the MCP server
|
||||
export async function bundleMcpJson(): Promise<void> {
|
||||
const { bundle: result } = await bundle({ ref: OPENAPI_PATH, config: await createConfig({}) });
|
||||
const doc = result.parsed as { servers?: unknown };
|
||||
delete doc.servers; // mcp server sets its own url
|
||||
writeFileSync(MCP_JSON_PATH, JSON.stringify(doc, null, 2) + "\n");
|
||||
}
|
||||
|
|
@ -1,221 +0,0 @@
|
|||
import { createGenerator } from "ts-json-schema-generator";
|
||||
import type { Schema } from "ts-json-schema-generator";
|
||||
|
||||
export interface StateSchemas {
|
||||
rootName: string;
|
||||
// schema per component name, root first, rest alphabetical
|
||||
defs: Record<string, Schema>;
|
||||
}
|
||||
|
||||
// enums use SCREAMING_SNAKE in the frontend, schema names follow openapi PascalCase convention
|
||||
const RENAME: Record<string, string> = {
|
||||
CHARGE_MODE: "ChargeMode",
|
||||
BATTERY_MODE: "BatteryMode",
|
||||
CURRENCY: "Currency",
|
||||
CHARGER_STATUS_REASON: "ChargerStatusReason",
|
||||
PHASE_ACTION: "PhaseAction",
|
||||
PV_ACTION: "PvAction",
|
||||
SMART_COST_TYPE: "SmartCostType",
|
||||
OCPP_STATION_STATUS: "OcppConnectionStatus",
|
||||
MODBUS_BAUDRATE: "ModbusBaudrate",
|
||||
MODBUS_COMSET: "ModbusComset",
|
||||
MODBUS_PROXY_READONLY: "ModbusProxyReadonly",
|
||||
};
|
||||
|
||||
const VALID_NAME = /^[a-zA-Z0-9._-]+$/;
|
||||
const ROOT = "State";
|
||||
|
||||
function buildRawSchema(): Schema {
|
||||
const generator = createGenerator({
|
||||
path: "assets/js/types/evcc.ts",
|
||||
tsconfig: "tsconfig.json",
|
||||
type: ROOT,
|
||||
jsDoc: "extended",
|
||||
extraTags: ["internal"],
|
||||
additionalProperties: true,
|
||||
topRef: false,
|
||||
skipTypeCheck: true,
|
||||
sortProps: false,
|
||||
});
|
||||
return generator.createSchema(ROOT);
|
||||
}
|
||||
|
||||
type AnySchema = any;
|
||||
|
||||
function walk(node: any, visit: (schema: AnySchema) => void): void {
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach((child) => walk(child, visit));
|
||||
} else if (node && typeof node === "object") {
|
||||
visit(node);
|
||||
Object.values(node).forEach((child) => walk(child, visit));
|
||||
}
|
||||
}
|
||||
|
||||
// remove properties tagged @internal, they are UI-only and not part of the API payload
|
||||
function stripInternal(schema: AnySchema): void {
|
||||
walk(schema, (node) => {
|
||||
if (!node.properties) return;
|
||||
for (const [key, prop] of Object.entries<AnySchema>(node.properties)) {
|
||||
if (prop?.internal === true) {
|
||||
delete node.properties[key];
|
||||
if (Array.isArray(node.required)) {
|
||||
node.required = node.required.filter((r: string) => r !== key);
|
||||
if (node.required.length === 0) delete node.required;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function collectRefs(schema: AnySchema): Set<string> {
|
||||
const refs = new Set<string>();
|
||||
walk(schema, (node) => {
|
||||
if (typeof node.$ref === "string") {
|
||||
refs.add(decodeURIComponent(node.$ref.replace("#/definitions/", "")));
|
||||
}
|
||||
});
|
||||
return refs;
|
||||
}
|
||||
|
||||
// drop definitions that became unreachable after stripping @internal properties
|
||||
function reachableDefs(
|
||||
root: AnySchema,
|
||||
defs: Record<string, AnySchema>
|
||||
): Record<string, AnySchema> {
|
||||
const keep: Record<string, AnySchema> = {};
|
||||
const queue = [...collectRefs(root)];
|
||||
while (queue.length > 0) {
|
||||
const name = queue.shift()!;
|
||||
if (keep[name] || !defs[name]) continue;
|
||||
keep[name] = defs[name];
|
||||
queue.push(...collectRefs(defs[name]));
|
||||
}
|
||||
return keep;
|
||||
}
|
||||
|
||||
// convert json-schema null unions to `nullable: true`, the 3.0 style used across openapi.yaml
|
||||
// (kin-openapi, which validates the spec in CI, does not support 3.1 type arrays)
|
||||
function normalizeNullables(schema: AnySchema): void {
|
||||
walk(schema, (node) => {
|
||||
if (Array.isArray(node.type) && node.type.includes("null")) {
|
||||
const rest = node.type.filter((t: string) => t !== "null");
|
||||
if (rest.length !== 1) throw new Error(`unsupported type union ${node.type}`);
|
||||
node.type = rest[0];
|
||||
node.nullable = true;
|
||||
}
|
||||
if (Array.isArray(node.anyOf) && node.anyOf.some((b: AnySchema) => b.type === "null")) {
|
||||
const rest = node.anyOf.filter((b: AnySchema) => b.type !== "null");
|
||||
delete node.anyOf;
|
||||
node.nullable = true;
|
||||
if (rest.length === 1 && !rest[0].$ref) {
|
||||
Object.assign(node, rest[0]);
|
||||
} else {
|
||||
node.allOf = rest;
|
||||
}
|
||||
}
|
||||
// openapi 3.0 has no `const`, use a single-value enum
|
||||
if ("const" in node) {
|
||||
node.enum = [node.const];
|
||||
delete node.const;
|
||||
}
|
||||
// openapi 3.0 uses a singular example
|
||||
if (Array.isArray(node.examples)) {
|
||||
node.example = node.examples[0];
|
||||
delete node.examples;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// point $refs at the final openapi component names
|
||||
function rewriteRefs(schema: AnySchema, finalNames: Map<string, string>): void {
|
||||
walk(schema, (node) => {
|
||||
if (typeof node.$ref !== "string" || node.$ref.startsWith("#/components/")) return;
|
||||
const name = decodeURIComponent(node.$ref.replace("#/definitions/", ""));
|
||||
const renamed = finalNames.get(name);
|
||||
if (!renamed) throw new Error(`unresolved $ref "${node.$ref}"`);
|
||||
node.$ref = `#/components/schemas/${renamed}`;
|
||||
});
|
||||
}
|
||||
|
||||
export function buildSchemas(): StateSchemas {
|
||||
const raw = buildRawSchema() as AnySchema;
|
||||
const { definitions = {}, $schema, ...root } = raw;
|
||||
|
||||
stripInternal(root);
|
||||
Object.values(definitions as Record<string, AnySchema>).forEach(stripInternal);
|
||||
|
||||
const defs = reachableDefs(root, definitions);
|
||||
|
||||
const finalNames = new Map<string, string>();
|
||||
for (const name of Object.keys(defs)) {
|
||||
const renamed = RENAME[name] ?? name;
|
||||
if (!VALID_NAME.test(renamed)) {
|
||||
throw new Error(`invalid component name "${renamed}", rename the type in evcc.ts`);
|
||||
}
|
||||
if ([...finalNames.values()].includes(renamed)) {
|
||||
throw new Error(`duplicate component name "${renamed}" after renaming`);
|
||||
}
|
||||
finalNames.set(name, renamed);
|
||||
}
|
||||
|
||||
rewriteRefs(root, finalNames);
|
||||
Object.values(defs).forEach((schema) => rewriteRefs(schema, finalNames));
|
||||
|
||||
normalizeNullables(root);
|
||||
Object.values(defs).forEach(normalizeNullables);
|
||||
|
||||
const result: Record<string, Schema> = { [ROOT]: root };
|
||||
for (const [name, schema] of Object.entries(defs)
|
||||
.map(([name, schema]) => [finalNames.get(name)!, schema] as const)
|
||||
.sort(([a], [b]) => a.localeCompare(b))) {
|
||||
result[name] = schema;
|
||||
}
|
||||
|
||||
const serialized = JSON.stringify(result);
|
||||
if (serialized.includes("#/definitions/")) {
|
||||
throw new Error("unrewritten $ref to #/definitions/ left in output");
|
||||
}
|
||||
|
||||
const ordered = Object.fromEntries(
|
||||
Object.entries(result).map(([name, schema]) => [name, orderKeys(schema)])
|
||||
);
|
||||
|
||||
return { rootName: ROOT, defs: ordered };
|
||||
}
|
||||
|
||||
const KEY_ORDER = [
|
||||
"$ref",
|
||||
"description",
|
||||
"type",
|
||||
"nullable",
|
||||
"enum",
|
||||
"format",
|
||||
"example",
|
||||
"items",
|
||||
"properties",
|
||||
"required",
|
||||
"additionalProperties",
|
||||
"anyOf",
|
||||
"allOf",
|
||||
];
|
||||
|
||||
// stable key order for readable yaml diffs, property order itself is preserved
|
||||
function orderKeys(node: any): any {
|
||||
if (Array.isArray(node)) return node.map(orderKeys);
|
||||
if (!node || typeof node !== "object") return node;
|
||||
const keys = Object.keys(node).sort((a, b) => {
|
||||
const ia = KEY_ORDER.indexOf(a);
|
||||
const ib = KEY_ORDER.indexOf(b);
|
||||
return (ia === -1 ? KEY_ORDER.length : ia) - (ib === -1 ? KEY_ORDER.length : ib);
|
||||
});
|
||||
return Object.fromEntries(
|
||||
keys.map((key) => [
|
||||
key,
|
||||
key === "properties" ? mapValues(node[key], orderKeys) : orderKeys(node[key]),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
function mapValues(obj: Record<string, any>, fn: (v: any) => any): Record<string, any> {
|
||||
return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, fn(v)]));
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
import { readFileSync } from "node:fs";
|
||||
import { parse } from "yaml";
|
||||
import { Ajv2020 } from "ajv/dist/2020.js";
|
||||
import addFormats from "ajv-formats";
|
||||
|
||||
const STATE_SCHEMAS_PATH = "server/openapi.state.yaml";
|
||||
|
||||
// intentionally undocumented experimental structures
|
||||
const IGNORE = new Set(["$.evopt", "$.evopt-batteries"]);
|
||||
|
||||
type AnySchema = any;
|
||||
|
||||
function resolve(schema: AnySchema, schemas: Record<string, AnySchema>): AnySchema {
|
||||
if (typeof schema?.$ref === "string") {
|
||||
return resolve(schemas[schema.$ref.split("/").pop()!], schemas);
|
||||
}
|
||||
return schema ?? {};
|
||||
}
|
||||
|
||||
// report payload keys that have no schema property, additionalProperties:true hides them from ajv
|
||||
function coverage(
|
||||
schema: AnySchema,
|
||||
data: any,
|
||||
path: string,
|
||||
schemas: Record<string, AnySchema>,
|
||||
report: Set<string>
|
||||
): void {
|
||||
const s = resolve(schema, schemas);
|
||||
if (s.anyOf) {
|
||||
if (data === null) return;
|
||||
const branch = s.anyOf.find((b: AnySchema) => resolve(b, schemas).type !== "null");
|
||||
if (branch) coverage(branch, data, path, schemas, report);
|
||||
return;
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
if (s.items) data.forEach((item) => coverage(s.items, item, `${path}[]`, schemas, report));
|
||||
return;
|
||||
}
|
||||
if (data !== null && typeof data === "object") {
|
||||
if (!s.properties) return;
|
||||
for (const key of Object.keys(data)) {
|
||||
if (s.properties[key]) {
|
||||
coverage(s.properties[key], data[key], `${path}.${key}`, schemas, report);
|
||||
} else if (!IGNORE.has(`${path}.${key}`)) {
|
||||
report.add(`${path}.${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// openapi uses `nullable: true`, json schema expects explicit null in the type
|
||||
function expandNullable(node: any): void {
|
||||
if (Array.isArray(node)) {
|
||||
node.forEach(expandNullable);
|
||||
} else if (node && typeof node === "object") {
|
||||
if (node.nullable === true) {
|
||||
delete node.nullable;
|
||||
if (typeof node.type === "string") {
|
||||
node.type = [node.type, "null"];
|
||||
} else if (Array.isArray(node.allOf)) {
|
||||
node.anyOf = [...node.allOf, { type: "null" }];
|
||||
delete node.allOf;
|
||||
}
|
||||
}
|
||||
Object.values(node).forEach(expandNullable);
|
||||
}
|
||||
}
|
||||
|
||||
// validate a /api/state payload against the State schema in server/openapi.state.yaml
|
||||
export function validateState(payload: any): { errors: string[]; undocumented: string[] } {
|
||||
const doc = parse(readFileSync(STATE_SCHEMAS_PATH, "utf8"));
|
||||
const schemas: Record<string, AnySchema> = doc.components.schemas;
|
||||
expandNullable(schemas);
|
||||
|
||||
const ajv = new Ajv2020({ strict: false, allErrors: true });
|
||||
addFormats(ajv);
|
||||
const validateFn = ajv.compile({
|
||||
$ref: "#/components/schemas/State",
|
||||
components: { schemas },
|
||||
});
|
||||
|
||||
const errors = validateFn(payload)
|
||||
? []
|
||||
: (validateFn.errors ?? []).map((err) => `${err.instancePath || "/"} ${err.message}`);
|
||||
|
||||
const undocumented = new Set<string>();
|
||||
coverage(schemas["State"], payload, "$", schemas, undocumented);
|
||||
|
||||
return { errors, undocumented: [...undocumented].sort() };
|
||||
}
|
||||
|
||||
export async function validate(source: string): Promise<boolean> {
|
||||
let payload: any;
|
||||
if (source.startsWith("http")) {
|
||||
const res = await fetch(`${source.replace(/\/$/, "")}/api/state`);
|
||||
payload = await res.json();
|
||||
} else {
|
||||
payload = JSON.parse(readFileSync(source, "utf8"));
|
||||
}
|
||||
|
||||
const { errors, undocumented } = validateState(payload);
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error(`${source}: schema violations`);
|
||||
for (const err of errors.slice(0, 30)) console.error(` ${err}`);
|
||||
}
|
||||
if (undocumented.length > 0) {
|
||||
console.log(`${source}: ${undocumented.length} undocumented keys`);
|
||||
for (const key of undocumented) console.log(` ${key}`);
|
||||
}
|
||||
if (errors.length === 0 && undocumented.length === 0) console.log(`${source}: ok`);
|
||||
|
||||
return errors.length === 0;
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -216,13 +216,13 @@ Directly controls the mode of all controllable batteries. evcc behavior like 'pr
|
|||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| batteryMode | string | Battery operation mode. |
|
||||
| batteryMode | string | Battery mode |
|
||||
|
||||
**Example call:**
|
||||
|
||||
```json
|
||||
call setExternalBatteryMode {
|
||||
"batteryMode": "unknown"
|
||||
"batteryMode": "normal"
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -384,6 +384,26 @@ call setSolarAdjusted {
|
|||
}
|
||||
```
|
||||
|
||||
## getState
|
||||
|
||||
Returns the complete state of the system. This structure is used by the UI. It can be filtered by JQ to only return a subset of the data.
|
||||
|
||||
**Tags:** general
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| jq | string | Filter the state with JQ |
|
||||
|
||||
**Example call:**
|
||||
|
||||
```json
|
||||
call getState {
|
||||
"jq": "example"
|
||||
}
|
||||
```
|
||||
|
||||
## removeGlobalSmartCostLimit
|
||||
|
||||
Convenience method to remove limit for all loadpoints at once. Value is applied to each individual loadpoint.
|
||||
|
|
@ -1146,26 +1166,6 @@ call updateSession {
|
|||
}
|
||||
```
|
||||
|
||||
## state
|
||||
|
||||
Returns the complete state of the system. This structure is used by the UI and also published via websocket and MQTT. It can be filtered by JQ to only return a subset of the data. Note: the response mirrors the internal UI state and carries no compatibility promise. Fields may change or disappear between releases.
|
||||
|
||||
**Tags:** state
|
||||
|
||||
**Arguments:**
|
||||
|
||||
| Name | Type | Description |
|
||||
|------|------|-------------|
|
||||
| jq | string | Filter the state with JQ |
|
||||
|
||||
**Example call:**
|
||||
|
||||
```json
|
||||
call state {
|
||||
"jq": "example"
|
||||
}
|
||||
```
|
||||
|
||||
## clearCache
|
||||
|
||||
Clears all cached data. This resets all cached values from tariffs, vehicle APIs, and other components that use caching.
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
package server
|
||||
|
||||
//go:generate go tool openapi-mcp --doc mcp/openapi.md mcp/openapi.json
|
||||
//go:generate go tool openapi openapi.yaml mcp/openapi.json
|
||||
//go:generate go tool openapi-mcp --doc mcp/openapi.md openapi.yaml
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -8,7 +8,6 @@ info:
|
|||
servers:
|
||||
- url: https://demo.evcc.io/api
|
||||
tags:
|
||||
- name: state
|
||||
- name: auth
|
||||
- name: battery
|
||||
- name: db
|
||||
|
|
@ -450,7 +449,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/ChargeMode"
|
||||
$ref: "#/components/schemas/Mode"
|
||||
/loadpoints/{id}/phases/{phases}:
|
||||
post:
|
||||
operationId: setLoadpointPhases
|
||||
|
|
@ -524,7 +523,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/StaticEnergyPlan"
|
||||
$ref: "#/components/schemas/StaticEnergyPlan"
|
||||
/loadpoints/{id}/plan/repeating/preview/{soc}/{weekdays}/{hourMinuteTime}/{timezone}:
|
||||
get:
|
||||
operationId: previewLoadpointRepeatingPlan
|
||||
|
|
@ -591,14 +590,14 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/PlanStrategy"
|
||||
$ref: "#/components/schemas/PlanStrategy"
|
||||
responses:
|
||||
200:
|
||||
"200":
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/PlanStrategy"
|
||||
$ref: "#/components/schemas/PlanStrategy"
|
||||
/loadpoints/{id}/priority/{priority}:
|
||||
post:
|
||||
operationId: setLoadpointPriority
|
||||
|
|
@ -946,13 +945,13 @@ paths:
|
|||
$ref: "#/components/responses/NumberResult"
|
||||
/state:
|
||||
get:
|
||||
operationId: state
|
||||
operationId: getState
|
||||
summary: System state
|
||||
description: "Returns the complete state of the system. This structure is used by the UI and also published via websocket and MQTT. It can be filtered by JQ to only return a subset of the data. Note: the response mirrors the internal UI state and carries no compatibility promise. Fields may change or disappear between releases."
|
||||
description: "Returns the complete state of the system. This structure is used by the UI. It can be filtered by JQ to only return a subset of the data."
|
||||
externalDocs:
|
||||
url: https://docs.evcc.io/integrations/rest-api
|
||||
tags:
|
||||
- state
|
||||
- general
|
||||
parameters:
|
||||
- name: jq
|
||||
in: query
|
||||
|
|
@ -972,7 +971,7 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/State"
|
||||
$ref: "#/components/schemas/State"
|
||||
/system/log:
|
||||
get:
|
||||
operationId: getSystemLogs
|
||||
|
|
@ -1165,7 +1164,7 @@ paths:
|
|||
type: object
|
||||
properties:
|
||||
mode:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/ChargeMode"
|
||||
$ref: "#/components/schemas/Mode"
|
||||
/vehicles/{name}/plan/repeating:
|
||||
post:
|
||||
operationId: updateVehicleRepeatingPlans
|
||||
|
|
@ -1184,7 +1183,7 @@ paths:
|
|||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/RepeatingPlan"
|
||||
$ref: "#/components/schemas/RepeatingPlan"
|
||||
responses:
|
||||
"200":
|
||||
description: Success
|
||||
|
|
@ -1193,7 +1192,7 @@ paths:
|
|||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/RepeatingPlan"
|
||||
$ref: "#/components/schemas/RepeatingPlan"
|
||||
/vehicles/{name}/plan/soc:
|
||||
delete:
|
||||
operationId: deleteVehicleSocPlan
|
||||
|
|
@ -1249,14 +1248,14 @@ paths:
|
|||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/PlanStrategy"
|
||||
$ref: "#/components/schemas/PlanStrategy"
|
||||
responses:
|
||||
200:
|
||||
"200":
|
||||
description: Success
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/PlanStrategy"
|
||||
$ref: "#/components/schemas/PlanStrategy"
|
||||
/db/backup:
|
||||
get:
|
||||
operationId: downloadBackup
|
||||
|
|
@ -1434,6 +1433,16 @@ paths:
|
|||
description: Invalid parameters or database offline
|
||||
components:
|
||||
schemas:
|
||||
BatteryMode:
|
||||
description: Battery mode
|
||||
type: string
|
||||
example: normal
|
||||
enum:
|
||||
- unknown
|
||||
- normal
|
||||
- hold
|
||||
- charge
|
||||
- holdcharge
|
||||
ChangePassword:
|
||||
type: object
|
||||
properties:
|
||||
|
|
@ -1571,6 +1580,14 @@ components:
|
|||
- INFO
|
||||
- DEBUG
|
||||
- TRACE
|
||||
Mode:
|
||||
description: "Charging mode."
|
||||
type: string
|
||||
enum:
|
||||
- "off"
|
||||
- "now"
|
||||
- "minpv"
|
||||
- "pv"
|
||||
Odometer:
|
||||
nullable: true
|
||||
type: "number"
|
||||
|
|
@ -1609,10 +1626,51 @@ components:
|
|||
type: integer
|
||||
example: 3600
|
||||
minimum: 0
|
||||
Rate:
|
||||
type: object
|
||||
description: A charging interval
|
||||
properties:
|
||||
start:
|
||||
description: Start
|
||||
$ref: "#/components/schemas/Timestamp"
|
||||
end:
|
||||
description: End
|
||||
$ref: "#/components/schemas/Timestamp"
|
||||
value:
|
||||
description: Cost
|
||||
type: number
|
||||
minimum: 0
|
||||
Rates:
|
||||
type: array
|
||||
items:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/Rate"
|
||||
$ref: "#/components/schemas/Rate"
|
||||
PlanStrategy:
|
||||
description: Charging plan strategy configuration
|
||||
type: object
|
||||
properties:
|
||||
continuous:
|
||||
description: "Force continuous planning"
|
||||
type: boolean
|
||||
precondition:
|
||||
description: "Precondition duration in seconds"
|
||||
type: integer
|
||||
minimum: 0
|
||||
RepeatingPlan:
|
||||
externalDocs:
|
||||
url: https://docs.evcc.io/en/features/plans#repeating-plans
|
||||
type: object
|
||||
properties:
|
||||
active:
|
||||
description: "Set plan active."
|
||||
type: boolean
|
||||
soc:
|
||||
$ref: "#/components/schemas/Soc"
|
||||
time:
|
||||
$ref: "#/components/schemas/HourMinuteTime"
|
||||
tz:
|
||||
$ref: "#/components/schemas/IANATimeZone"
|
||||
weekdays:
|
||||
$ref: "#/components/schemas/Weekdays"
|
||||
Soc:
|
||||
description: SOC in %
|
||||
type: number
|
||||
|
|
@ -1624,6 +1682,27 @@ components:
|
|||
type: number
|
||||
example: 50
|
||||
minimum: 0
|
||||
State:
|
||||
description: "The actual state structure is not documented yet. Most values should be self-explanatory. Note: While the overall structure is quite stable, details may change between releases."
|
||||
type: object
|
||||
StaticEnergyPlan:
|
||||
externalDocs:
|
||||
url: https://docs.evcc.io/en/features/plans#energy-amount-plan
|
||||
type: object
|
||||
properties:
|
||||
energy:
|
||||
$ref: "#/components/schemas/Energy"
|
||||
time:
|
||||
$ref: "#/components/schemas/Timestamp"
|
||||
StaticSocPlan:
|
||||
externalDocs:
|
||||
url: https://docs.evcc.io/en/features/plans#create-charging-plan
|
||||
type: object
|
||||
properties:
|
||||
soc:
|
||||
$ref: "#/components/schemas/Soc"
|
||||
time:
|
||||
$ref: "#/components/schemas/Timestamp"
|
||||
Timestamp:
|
||||
description: Timestamp in RFC3339 format
|
||||
type: string
|
||||
|
|
@ -1720,7 +1799,7 @@ components:
|
|||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/ChargeMode"
|
||||
$ref: "#/components/schemas/Mode"
|
||||
soc:
|
||||
name: soc
|
||||
description: SOC in %
|
||||
|
|
@ -1782,7 +1861,7 @@ components:
|
|||
in: path
|
||||
required: true
|
||||
schema:
|
||||
$ref: "./openapi.state.yaml#/components/schemas/BatteryMode"
|
||||
$ref: "#/components/schemas/BatteryMode"
|
||||
costLimit:
|
||||
name: cost
|
||||
description: Cost limit in configured currency (default EUR) or CO2 limit in g/kWh
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import (
|
|||
|
||||
func TestOpenAPIValidation(t *testing.T) {
|
||||
loader := openapi3.NewLoader()
|
||||
loader.IsExternalRefsAllowed = true
|
||||
doc, err := loader.LoadFromFile("openapi.yaml")
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, doc.Validate(loader.Context))
|
||||
|
|
|
|||
|
|
@ -1,28 +0,0 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { start, stop, baseUrl } from "./evcc";
|
||||
import { validateState } from "../scripts/state-schema/validate";
|
||||
|
||||
test.use({ baseURL: baseUrl() });
|
||||
|
||||
const CONFIG = "cmd/demo.yaml";
|
||||
|
||||
test.beforeAll(async () => {
|
||||
await start(CONFIG);
|
||||
});
|
||||
test.afterAll(async () => {
|
||||
await stop();
|
||||
});
|
||||
|
||||
test("/api/state matches the openapi State schema", async ({ request }) => {
|
||||
// loadpoint values appear over the first update cycles, poll until stable
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const res = await request.get("/api/state");
|
||||
const { errors, undocumented } = validateState(await res.json());
|
||||
return [...errors, ...undocumented];
|
||||
},
|
||||
{ timeout: 30000 }
|
||||
)
|
||||
.toEqual([]);
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "assets/js/**/*", "tests/**/*", "scripts/**/*"],
|
||||
"include": ["env.d.ts", "assets/js/**/*", "tests/**/*"],
|
||||
"compilerOptions": {
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue