@enyo-energy/energy-app-sdk 0.0.192 → 0.0.193
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/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.cjs +74 -2
- package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.d.cts +60 -4
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.cjs +142 -12
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.d.cts +10 -4
- package/dist/cjs/types/enyo-onboarding-v2.cjs +54 -7
- package/dist/cjs/types/enyo-onboarding-v2.d.cts +128 -12
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.d.ts +60 -4
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.js +75 -3
- package/dist/implementations/onboarding-v2/onboarding-v2-validators.d.ts +10 -4
- package/dist/implementations/onboarding-v2/onboarding-v2-validators.js +143 -13
- package/dist/types/enyo-onboarding-v2.d.ts +128 -12
- package/dist/types/enyo-onboarding-v2.js +53 -6
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -133,6 +133,52 @@ exports.onboardingV2Block = {
|
|
|
133
133
|
outcomes,
|
|
134
134
|
deviceSelection,
|
|
135
135
|
}),
|
|
136
|
+
/**
|
|
137
|
+
* An OCPP-connect action block: wait for the charger to dial into enyo's
|
|
138
|
+
* CSMS after the installer has entered the dynamic OCPP URL in it.
|
|
139
|
+
*
|
|
140
|
+
* A convenience wrapper over {@link onboardingV2Block.action} that pins the
|
|
141
|
+
* action kind. Nothing is searched — an OCPP wallbox is never on the LAN, so
|
|
142
|
+
* {@link EnyoOnboardingV2ActionKind.NetworkScan} is not a substitute. Pair it
|
|
143
|
+
* with an {@link onboardingV2Block.dynamic} `ocpp-url` block on the same or a
|
|
144
|
+
* preceding step.
|
|
145
|
+
*
|
|
146
|
+
* Outcome `value`s must be {@link EnyoOnboardingV2OcppConnectOutcome} members
|
|
147
|
+
* and both must be wired — the validator enforces that, since a charger that
|
|
148
|
+
* never calls home is the common case.
|
|
149
|
+
*
|
|
150
|
+
* @param id - Stable block id, unique within the guide.
|
|
151
|
+
* @param label - Translated trigger button text (de/en).
|
|
152
|
+
* @param outcomes - The `connected` / `timeout` results; each is a routing handle.
|
|
153
|
+
*/
|
|
154
|
+
ocppConnect: (id, label, outcomes) => ({
|
|
155
|
+
id,
|
|
156
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action,
|
|
157
|
+
action: enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.OcppConnect,
|
|
158
|
+
label,
|
|
159
|
+
outcomes,
|
|
160
|
+
}),
|
|
161
|
+
/**
|
|
162
|
+
* An auth block: the installer signs into the energy app's own account
|
|
163
|
+
* system (OAuth / vendor portal).
|
|
164
|
+
*
|
|
165
|
+
* Exactly one routing handle, and it means "the login succeeded". The server
|
|
166
|
+
* decides when it fires, so the installer cannot skip it; there is no failure
|
|
167
|
+
* branch to author — a failed attempt simply keeps them on the step. Route
|
|
168
|
+
* the handle with {@link onOutcomeV2}, passing `outcome.id`.
|
|
169
|
+
*
|
|
170
|
+
* @param id - Stable block id, unique within the guide.
|
|
171
|
+
* @param label - Translated sign-in button text (de/en).
|
|
172
|
+
* @param outcome - The single success handle (`{id, label}`).
|
|
173
|
+
* @param opts - Optional translated `help` naming the account that is needed.
|
|
174
|
+
*/
|
|
175
|
+
auth: (id, label, outcome, opts) => ({
|
|
176
|
+
id,
|
|
177
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Auth,
|
|
178
|
+
label,
|
|
179
|
+
outcome,
|
|
180
|
+
help: opts?.help,
|
|
181
|
+
}),
|
|
136
182
|
/**
|
|
137
183
|
* A link block: a fixed URL the installer opens or copies.
|
|
138
184
|
*
|
|
@@ -191,14 +237,40 @@ exports.onboardingV2Target = {
|
|
|
191
237
|
step: (stepId) => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Step, stepId }),
|
|
192
238
|
/** Exit: onboarding succeeded (hand back to the app). */
|
|
193
239
|
success: () => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Success }),
|
|
194
|
-
/**
|
|
195
|
-
|
|
240
|
+
/**
|
|
241
|
+
* Exit: escalate to enyo support.
|
|
242
|
+
* @param reason - Optional short internal key describing what failed, e.g.
|
|
243
|
+
* `firmware-too-old`. Never shown to the installer; it travels with the
|
|
244
|
+
* hand-off so support knows why it arrived.
|
|
245
|
+
*/
|
|
246
|
+
support: (reason) => ({
|
|
247
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Support,
|
|
248
|
+
reason,
|
|
249
|
+
}),
|
|
196
250
|
/**
|
|
197
251
|
* Exit: pause the run (resumable) with a reason.
|
|
252
|
+
*
|
|
253
|
+
* For {@link EnyoOnboardingV2PauseReason.EnyoTodo} prefer
|
|
254
|
+
* {@link onboardingV2Target.enyoTakeover} — that reason is a terminal
|
|
255
|
+
* hand-off, not a park, and `resumeStepName` does not apply to it.
|
|
256
|
+
*
|
|
198
257
|
* @param reason - Why the run is parked.
|
|
199
258
|
* @param resumeStepName - Optional step `name` to resume at.
|
|
200
259
|
*/
|
|
201
260
|
pause: (reason, resumeStepName) => ({ type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause, reason, resumeStepName }),
|
|
261
|
+
/**
|
|
262
|
+
* Exit: **enyo übernimmt** — the installer is done and enyo finishes the
|
|
263
|
+
* setup. A terminal exit alongside `success` and `support`: the app shows the
|
|
264
|
+
* takeover screen rather than returning to the cockpit.
|
|
265
|
+
*
|
|
266
|
+
* Emits the unchanged wire shape (`pause` with reason `enyo-todo`), and
|
|
267
|
+
* counts as a completing exit for {@link validateOnboardingGuideV2} — a guide
|
|
268
|
+
* that only ends here needs no `success` branch.
|
|
269
|
+
*/
|
|
270
|
+
enyoTakeover: () => ({
|
|
271
|
+
type: enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause,
|
|
272
|
+
reason: enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo,
|
|
273
|
+
}),
|
|
202
274
|
/**
|
|
203
275
|
* Jump into another start variant's flow for the same vendor/model.
|
|
204
276
|
* @param variant - The start variant to hand off to.
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
* before publishing.
|
|
11
11
|
*/
|
|
12
12
|
import type { EnyoOnboardingTranslatedContent } from '../../types/enyo-onboarding.cjs';
|
|
13
|
-
import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection } from '../../types/enyo-onboarding-v2.cjs';
|
|
14
|
-
import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2InputOutcome, EnyoOnboardingV2InputValueType,
|
|
13
|
+
import { EnyoOnboardingV2ActionKind, EnyoOnboardingV2ChoiceLayout, EnyoOnboardingV2DeviceSelection, EnyoOnboardingV2PauseReason } from '../../types/enyo-onboarding-v2.cjs';
|
|
14
|
+
import type { EnyoOnboardingV2ActionOutcome, EnyoOnboardingV2AuthOutcome, EnyoOnboardingV2Block, EnyoOnboardingV2ChoiceOption, EnyoOnboardingV2DynamicKind, EnyoOnboardingV2Guide, EnyoOnboardingV2HintVariant, EnyoOnboardingV2InputOutcome, EnyoOnboardingV2InputValueType, EnyoOnboardingV2StartVariant, EnyoOnboardingV2Target, EnyoOnboardingV2Transition } from '../../types/enyo-onboarding-v2.cjs';
|
|
15
15
|
/**
|
|
16
16
|
* Identity helper that type-checks a v2 guide literal at definition time
|
|
17
17
|
* (mirrors `defineEnergyAppPackage`). Prefer this over a bare object literal so
|
|
@@ -98,6 +98,42 @@ export declare const onboardingV2Block: {
|
|
|
98
98
|
* @param deviceSelection - Which devices to test (defaults to `Detected`).
|
|
99
99
|
*/
|
|
100
100
|
deviceTest: (id: string, label: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2ActionOutcome[], deviceSelection?: EnyoOnboardingV2DeviceSelection) => EnyoOnboardingV2Block;
|
|
101
|
+
/**
|
|
102
|
+
* An OCPP-connect action block: wait for the charger to dial into enyo's
|
|
103
|
+
* CSMS after the installer has entered the dynamic OCPP URL in it.
|
|
104
|
+
*
|
|
105
|
+
* A convenience wrapper over {@link onboardingV2Block.action} that pins the
|
|
106
|
+
* action kind. Nothing is searched — an OCPP wallbox is never on the LAN, so
|
|
107
|
+
* {@link EnyoOnboardingV2ActionKind.NetworkScan} is not a substitute. Pair it
|
|
108
|
+
* with an {@link onboardingV2Block.dynamic} `ocpp-url` block on the same or a
|
|
109
|
+
* preceding step.
|
|
110
|
+
*
|
|
111
|
+
* Outcome `value`s must be {@link EnyoOnboardingV2OcppConnectOutcome} members
|
|
112
|
+
* and both must be wired — the validator enforces that, since a charger that
|
|
113
|
+
* never calls home is the common case.
|
|
114
|
+
*
|
|
115
|
+
* @param id - Stable block id, unique within the guide.
|
|
116
|
+
* @param label - Translated trigger button text (de/en).
|
|
117
|
+
* @param outcomes - The `connected` / `timeout` results; each is a routing handle.
|
|
118
|
+
*/
|
|
119
|
+
ocppConnect: (id: string, label: EnyoOnboardingTranslatedContent[], outcomes: EnyoOnboardingV2ActionOutcome[]) => EnyoOnboardingV2Block;
|
|
120
|
+
/**
|
|
121
|
+
* An auth block: the installer signs into the energy app's own account
|
|
122
|
+
* system (OAuth / vendor portal).
|
|
123
|
+
*
|
|
124
|
+
* Exactly one routing handle, and it means "the login succeeded". The server
|
|
125
|
+
* decides when it fires, so the installer cannot skip it; there is no failure
|
|
126
|
+
* branch to author — a failed attempt simply keeps them on the step. Route
|
|
127
|
+
* the handle with {@link onOutcomeV2}, passing `outcome.id`.
|
|
128
|
+
*
|
|
129
|
+
* @param id - Stable block id, unique within the guide.
|
|
130
|
+
* @param label - Translated sign-in button text (de/en).
|
|
131
|
+
* @param outcome - The single success handle (`{id, label}`).
|
|
132
|
+
* @param opts - Optional translated `help` naming the account that is needed.
|
|
133
|
+
*/
|
|
134
|
+
auth: (id: string, label: EnyoOnboardingTranslatedContent[], outcome: EnyoOnboardingV2AuthOutcome, opts?: {
|
|
135
|
+
help?: EnyoOnboardingTranslatedContent[];
|
|
136
|
+
}) => EnyoOnboardingV2Block;
|
|
101
137
|
/**
|
|
102
138
|
* A link block: a fixed URL the installer opens or copies.
|
|
103
139
|
*
|
|
@@ -143,14 +179,34 @@ export declare const onboardingV2Target: {
|
|
|
143
179
|
step: (stepId: string) => EnyoOnboardingV2Target;
|
|
144
180
|
/** Exit: onboarding succeeded (hand back to the app). */
|
|
145
181
|
success: () => EnyoOnboardingV2Target;
|
|
146
|
-
/**
|
|
147
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Exit: escalate to enyo support.
|
|
184
|
+
* @param reason - Optional short internal key describing what failed, e.g.
|
|
185
|
+
* `firmware-too-old`. Never shown to the installer; it travels with the
|
|
186
|
+
* hand-off so support knows why it arrived.
|
|
187
|
+
*/
|
|
188
|
+
support: (reason?: string) => EnyoOnboardingV2Target;
|
|
148
189
|
/**
|
|
149
190
|
* Exit: pause the run (resumable) with a reason.
|
|
191
|
+
*
|
|
192
|
+
* For {@link EnyoOnboardingV2PauseReason.EnyoTodo} prefer
|
|
193
|
+
* {@link onboardingV2Target.enyoTakeover} — that reason is a terminal
|
|
194
|
+
* hand-off, not a park, and `resumeStepName` does not apply to it.
|
|
195
|
+
*
|
|
150
196
|
* @param reason - Why the run is parked.
|
|
151
197
|
* @param resumeStepName - Optional step `name` to resume at.
|
|
152
198
|
*/
|
|
153
199
|
pause: (reason: EnyoOnboardingV2PauseReason, resumeStepName?: string) => EnyoOnboardingV2Target;
|
|
200
|
+
/**
|
|
201
|
+
* Exit: **enyo übernimmt** — the installer is done and enyo finishes the
|
|
202
|
+
* setup. A terminal exit alongside `success` and `support`: the app shows the
|
|
203
|
+
* takeover screen rather than returning to the cockpit.
|
|
204
|
+
*
|
|
205
|
+
* Emits the unchanged wire shape (`pause` with reason `enyo-todo`), and
|
|
206
|
+
* counts as a completing exit for {@link validateOnboardingGuideV2} — a guide
|
|
207
|
+
* that only ends here needs no `success` branch.
|
|
208
|
+
*/
|
|
209
|
+
enyoTakeover: () => EnyoOnboardingV2Target;
|
|
154
210
|
/**
|
|
155
211
|
* Jump into another start variant's flow for the same vendor/model.
|
|
156
212
|
* @param variant - The start variant to hand off to.
|
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
* energy app can fail fast locally before publishing.
|
|
6
6
|
*
|
|
7
7
|
* `errors` block publishing; `warnings` are advisory (e.g. unreachable step, no
|
|
8
|
-
* path to
|
|
9
|
-
* result, or {@link assertValidOnboardingGuideV2} to throw on the
|
|
8
|
+
* path to a completing exit). Use {@link validateOnboardingGuideV2} for the
|
|
9
|
+
* non-throwing result, or {@link assertValidOnboardingGuideV2} to throw on the
|
|
10
|
+
* first failure.
|
|
11
|
+
*
|
|
12
|
+
* A run completes either through a `success` target **or** through the
|
|
13
|
+
* "enyo übernimmt" hand-off (`pause` with reason
|
|
14
|
+
* {@link EnyoOnboardingV2PauseReason.EnyoTodo}) — both count, so a guide that
|
|
15
|
+
* legitimately ends in a hand-off is not nagged about a missing success path.
|
|
10
16
|
*/
|
|
11
17
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
18
|
exports.OnboardingV2ValidationError = void 0;
|
|
@@ -34,8 +40,8 @@ exports.OnboardingV2ValidationError = OnboardingV2ValidationError;
|
|
|
34
40
|
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
35
41
|
/**
|
|
36
42
|
* Structural + plausibility validation of a v2 guide graph. `errors` block
|
|
37
|
-
* publishing; `warnings` are advisory (e.g. unreachable step, no path to
|
|
38
|
-
* success).
|
|
43
|
+
* publishing; `warnings` are advisory (e.g. unreachable step, no path to a
|
|
44
|
+
* completing exit — `success` or the `enyo-todo` hand-off).
|
|
39
45
|
*
|
|
40
46
|
* @param guide - The v2 guide to validate.
|
|
41
47
|
* @returns The {@link OnboardingV2ValidationResult}.
|
|
@@ -72,11 +78,12 @@ function validateOnboardingGuideV2(guide) {
|
|
|
72
78
|
validateActionBlocks(step, at, errors, warnings);
|
|
73
79
|
validateLinkBlocks(step, at, errors, warnings);
|
|
74
80
|
validateInputBlocks(step, at, errors, warnings);
|
|
81
|
+
validateAuthBlocks(step, at, errors, warnings);
|
|
75
82
|
}
|
|
76
83
|
if (!guide.startStepId || !stepIds.has(guide.startStepId)) {
|
|
77
84
|
errors.push('`startStepId` must reference an existing step.');
|
|
78
85
|
}
|
|
79
|
-
let
|
|
86
|
+
let hasCompletingExit = false;
|
|
80
87
|
for (const [i, step] of guide.steps.entries()) {
|
|
81
88
|
const at = `steps[${i}] (${step.name})`;
|
|
82
89
|
const required = requiredHandleKeys(step);
|
|
@@ -90,14 +97,24 @@ function validateOnboardingGuideV2(guide) {
|
|
|
90
97
|
wired.add(key);
|
|
91
98
|
const tg = t.target;
|
|
92
99
|
if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Success)
|
|
93
|
-
|
|
100
|
+
hasCompletingExit = true;
|
|
94
101
|
else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Step && !stepIds.has(tg.stepId)) {
|
|
95
102
|
errors.push(`${at}: transition targets missing step "${tg.stepId}".`);
|
|
96
103
|
}
|
|
97
|
-
else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
104
|
+
else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.Pause) {
|
|
105
|
+
// "enyo übernimmt" is a terminal hand-off, not a park: it completes
|
|
106
|
+
// the run for the installer, so it satisfies the completion check
|
|
107
|
+
// and has nothing to resume at.
|
|
108
|
+
if (tg.reason === enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo) {
|
|
109
|
+
hasCompletingExit = true;
|
|
110
|
+
if (tg.resumeStepName) {
|
|
111
|
+
warnings.push(`${at}: pause reason "${enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo}" is a hand-off to enyo, ` +
|
|
112
|
+
`not a resumable park — resumeStepName "${tg.resumeStepName}" is ignored.`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if (tg.resumeStepName && !stepNames.has(tg.resumeStepName)) {
|
|
116
|
+
errors.push(`${at}: pause resumeStepName "${tg.resumeStepName}" is unknown.`);
|
|
117
|
+
}
|
|
101
118
|
}
|
|
102
119
|
else if (tg.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2TargetType.StartVariant &&
|
|
103
120
|
tg.variant === guide.startVariant) {
|
|
@@ -117,8 +134,11 @@ function validateOnboardingGuideV2(guide) {
|
|
|
117
134
|
warnings.push(`step "${step.name}" is unreachable from the start.`);
|
|
118
135
|
}
|
|
119
136
|
}
|
|
120
|
-
if (!
|
|
121
|
-
warnings.push('No branch reaches a `success`
|
|
137
|
+
if (!hasCompletingExit) {
|
|
138
|
+
warnings.push('No branch reaches a completing exit — wire a `success` target, or a `pause` with reason ' +
|
|
139
|
+
`"${enyo_onboarding_v2_js_1.EnyoOnboardingV2PauseReason.EnyoTodo}" when enyo takes the setup over.`);
|
|
140
|
+
}
|
|
141
|
+
validateNetworkScanFlag(guide, warnings);
|
|
122
142
|
return { ok: errors.length === 0, errors, warnings };
|
|
123
143
|
}
|
|
124
144
|
/**
|
|
@@ -155,6 +175,9 @@ function validateActionBlocks(step, at, errors, warnings) {
|
|
|
155
175
|
if (block.deviceSelection) {
|
|
156
176
|
warnings.push(`${at}: block "${block.id}" sets deviceSelection but is not a device-test action; it is ignored.`);
|
|
157
177
|
}
|
|
178
|
+
if (block.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.OcppConnect) {
|
|
179
|
+
validateOcppConnectOutcomes(block, at, errors);
|
|
180
|
+
}
|
|
158
181
|
continue;
|
|
159
182
|
}
|
|
160
183
|
const values = new Set();
|
|
@@ -284,6 +307,110 @@ function validateInputBlocks(step, at, errors, warnings) {
|
|
|
284
307
|
}
|
|
285
308
|
}
|
|
286
309
|
}
|
|
310
|
+
/** Every {@link EnyoOnboardingV2OcppConnectOutcome} value. */
|
|
311
|
+
const OCPP_CONNECT_OUTCOMES = new Set(Object.values(enyo_onboarding_v2_js_1.EnyoOnboardingV2OcppConnectOutcome));
|
|
312
|
+
/**
|
|
313
|
+
* Validates the outcomes of an {@link EnyoOnboardingV2ActionKind.OcppConnect}
|
|
314
|
+
* block.
|
|
315
|
+
*
|
|
316
|
+
* The block waits for the charger to dial into our CSMS; it can only ever report
|
|
317
|
+
* that the connection arrived or that it did not, so its outcome `value`s are
|
|
318
|
+
* closed over {@link EnyoOnboardingV2OcppConnectOutcome}. Both must be wired: a
|
|
319
|
+
* charger that never calls home — wrong URL typed, no mobile coverage in the
|
|
320
|
+
* garage — is the *common* case, and a guide without a `timeout` branch strands
|
|
321
|
+
* the installer on a spinner.
|
|
322
|
+
*
|
|
323
|
+
* @param block - The ocpp-connect action block being checked.
|
|
324
|
+
* @param at - Human-readable location prefix for messages.
|
|
325
|
+
* @param errors - Collector for blocking problems.
|
|
326
|
+
*/
|
|
327
|
+
function validateOcppConnectOutcomes(block, at, errors) {
|
|
328
|
+
const values = new Set();
|
|
329
|
+
for (const outcome of block.outcomes ?? []) {
|
|
330
|
+
if (!OCPP_CONNECT_OUTCOMES.has(outcome.value)) {
|
|
331
|
+
errors.push(`${at}: ocpp-connect block "${block.id}" has outcome value "${outcome.value}", which is not an EnyoOnboardingV2OcppConnectOutcome member.`);
|
|
332
|
+
}
|
|
333
|
+
else if (values.has(outcome.value)) {
|
|
334
|
+
errors.push(`${at}: ocpp-connect block "${block.id}" wires outcome value "${outcome.value}" more than once.`);
|
|
335
|
+
}
|
|
336
|
+
values.add(outcome.value);
|
|
337
|
+
}
|
|
338
|
+
for (const required of OCPP_CONNECT_OUTCOMES) {
|
|
339
|
+
if (!values.has(required)) {
|
|
340
|
+
errors.push(`${at}: ocpp-connect block "${block.id}" has no "${required}" outcome; both results must be routed.`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Validates the auth blocks of a step.
|
|
346
|
+
*
|
|
347
|
+
* An auth block has exactly one handle, and the **server** decides when it
|
|
348
|
+
* fires — there is no failure branch to author, because a failed login keeps the
|
|
349
|
+
* installer on the step to retry. So the checks are about the handle existing and
|
|
350
|
+
* being routable at all, and about the step not pretending the login is optional:
|
|
351
|
+
* a second decision block next to it would offer a way past a gate the client is
|
|
352
|
+
* not allowed to skip.
|
|
353
|
+
*
|
|
354
|
+
* @param step - The step whose blocks are checked.
|
|
355
|
+
* @param at - Human-readable location prefix for messages.
|
|
356
|
+
* @param errors - Collector for blocking problems.
|
|
357
|
+
* @param warnings - Collector for advisory problems.
|
|
358
|
+
*/
|
|
359
|
+
function validateAuthBlocks(step, at, errors, warnings) {
|
|
360
|
+
const authBlocks = (step.blocks ?? []).filter((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Auth);
|
|
361
|
+
for (const block of authBlocks) {
|
|
362
|
+
if (!block.label?.length)
|
|
363
|
+
errors.push(`${at}: auth block "${block.id}" has no label.`);
|
|
364
|
+
if (!block.outcome?.id) {
|
|
365
|
+
errors.push(`${at}: auth block "${block.id}" has no outcome id — its success handle cannot be routed.`);
|
|
366
|
+
}
|
|
367
|
+
if (!block.outcome?.label?.length) {
|
|
368
|
+
warnings.push(`${at}: auth block "${block.id}" outcome has no label.`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
if (authBlocks.length > 1) {
|
|
372
|
+
errors.push(`${at}: more than one auth block; a step can hold at most one login.`);
|
|
373
|
+
}
|
|
374
|
+
if (authBlocks.length === 1) {
|
|
375
|
+
const others = (step.blocks ?? []).filter((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Choice ||
|
|
376
|
+
b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action ||
|
|
377
|
+
b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Input);
|
|
378
|
+
if (others.length) {
|
|
379
|
+
warnings.push(`${at}: auth block "${authBlocks[0].id}" shares the step with ${others.length} other decision block(s) — ` +
|
|
380
|
+
'those give the installer a way past a login the server gates.');
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Checks {@link EnyoOnboardingV2Guide.requiresNetworkScan} against what the guide
|
|
386
|
+
* actually does.
|
|
387
|
+
*
|
|
388
|
+
* Opting out means "don't search, start here" — right for a device that is never
|
|
389
|
+
* on the LAN (an OCPP wallbox), wrong if the guide then relies on scan results.
|
|
390
|
+
* Both mismatches are warnings, not errors: the flag describes the host's
|
|
391
|
+
* behaviour before the guide runs, and an author may have a reason.
|
|
392
|
+
*
|
|
393
|
+
* @param guide - The guide being validated.
|
|
394
|
+
* @param warnings - Collector for advisory problems.
|
|
395
|
+
*/
|
|
396
|
+
function validateNetworkScanFlag(guide, warnings) {
|
|
397
|
+
if (guide.requiresNetworkScan !== false)
|
|
398
|
+
return;
|
|
399
|
+
const blocks = guide.steps.flatMap((s) => s.blocks ?? []);
|
|
400
|
+
const scansItself = blocks.some((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action &&
|
|
401
|
+
b.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.NetworkScan);
|
|
402
|
+
if (scansItself)
|
|
403
|
+
return;
|
|
404
|
+
const dependsOnDetected = blocks.some((b) => b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Action &&
|
|
405
|
+
b.action === enyo_onboarding_v2_js_1.EnyoOnboardingV2ActionKind.DeviceTest &&
|
|
406
|
+
(b.deviceSelection ?? enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelection.Detected) !==
|
|
407
|
+
enyo_onboarding_v2_js_1.EnyoOnboardingV2DeviceSelection.Current);
|
|
408
|
+
if (dependsOnDetected) {
|
|
409
|
+
warnings.push('requiresNetworkScan is false, but a device-test block selects from detected devices — ' +
|
|
410
|
+
'nothing was scanned, so it has nothing to test. Use deviceSelection "current", or run a ' +
|
|
411
|
+
'network-scan action inside the guide.');
|
|
412
|
+
}
|
|
413
|
+
}
|
|
287
414
|
/**
|
|
288
415
|
* The set of routing-handle keys a step must wire exactly once: one per
|
|
289
416
|
* choice option / action outcome / input outcome, or the single `continue`
|
|
@@ -303,6 +430,9 @@ function requiredHandleKeys(step) {
|
|
|
303
430
|
for (const o of b.outcomes)
|
|
304
431
|
keys.add(`outcome:${b.id}:${o.id}`);
|
|
305
432
|
}
|
|
433
|
+
else if (b.type === enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Auth && b.outcome?.id) {
|
|
434
|
+
keys.add(`outcome:${b.id}:${b.outcome.id}`);
|
|
435
|
+
}
|
|
306
436
|
}
|
|
307
437
|
if (keys.size === 0)
|
|
308
438
|
keys.add('continue');
|
|
@@ -4,8 +4,14 @@
|
|
|
4
4
|
* energy app can fail fast locally before publishing.
|
|
5
5
|
*
|
|
6
6
|
* `errors` block publishing; `warnings` are advisory (e.g. unreachable step, no
|
|
7
|
-
* path to
|
|
8
|
-
* result, or {@link assertValidOnboardingGuideV2} to throw on the
|
|
7
|
+
* path to a completing exit). Use {@link validateOnboardingGuideV2} for the
|
|
8
|
+
* non-throwing result, or {@link assertValidOnboardingGuideV2} to throw on the
|
|
9
|
+
* first failure.
|
|
10
|
+
*
|
|
11
|
+
* A run completes either through a `success` target **or** through the
|
|
12
|
+
* "enyo übernimmt" hand-off (`pause` with reason
|
|
13
|
+
* {@link EnyoOnboardingV2PauseReason.EnyoTodo}) — both count, so a guide that
|
|
14
|
+
* legitimately ends in a hand-off is not nagged about a missing success path.
|
|
9
15
|
*/
|
|
10
16
|
import type { EnyoOnboardingV2Guide } from '../../types/enyo-onboarding-v2.cjs';
|
|
11
17
|
/**
|
|
@@ -31,8 +37,8 @@ export interface OnboardingV2ValidationResult {
|
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Structural + plausibility validation of a v2 guide graph. `errors` block
|
|
34
|
-
* publishing; `warnings` are advisory (e.g. unreachable step, no path to
|
|
35
|
-
* success).
|
|
40
|
+
* publishing; `warnings` are advisory (e.g. unreachable step, no path to a
|
|
41
|
+
* completing exit — `success` or the `enyo-todo` hand-off).
|
|
36
42
|
*
|
|
37
43
|
* @param guide - The v2 guide to validate.
|
|
38
44
|
* @returns The {@link OnboardingV2ValidationResult}.
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* name-string routing, v2 is a **directed graph**: an energy app defines a guide
|
|
9
9
|
* as a set of named steps connected by explicit {@link EnyoOnboardingV2Transition}s.
|
|
10
10
|
* The installer walks it, branching on choices and device checks, and leaves through
|
|
11
|
-
* one of
|
|
12
|
-
* variant's flow.
|
|
11
|
+
* one of its exits (success / enyo takeover / paused / support) — or jumps into
|
|
12
|
+
* another start variant's flow.
|
|
13
13
|
*
|
|
14
14
|
* Every author-facing string is an {@link EnyoOnboardingTranslatedContent} array
|
|
15
15
|
* (de/en), reusing the v1 translation primitive so the two models stay consistent.
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* before publishing.
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
-
exports.EnyoOnboardingV2TargetType = exports.EnyoOnboardingV2TransitionSourceKind = exports.EnyoOnboardingV2BlockType = exports.EnyoOnboardingV2ChoiceLayout = exports.EnyoOnboardingV2IconKey = exports.EnyoOnboardingV2DeviceSelection = exports.EnyoOnboardingV2InputValueType = exports.EnyoOnboardingV2ActionKind = exports.EnyoOnboardingV2DynamicKind = exports.EnyoOnboardingV2HintVariant = exports.EnyoOnboardingV2PauseReason = exports.EnyoOnboardingV2StartVariant = void 0;
|
|
25
|
+
exports.EnyoOnboardingV2TargetType = exports.EnyoOnboardingV2TransitionSourceKind = exports.EnyoOnboardingV2BlockType = exports.EnyoOnboardingV2ChoiceLayout = exports.EnyoOnboardingV2IconKey = exports.EnyoOnboardingV2DeviceSelection = exports.EnyoOnboardingV2InputValueType = exports.EnyoOnboardingV2OcppConnectOutcome = exports.EnyoOnboardingV2ActionKind = exports.EnyoOnboardingV2DynamicKind = exports.EnyoOnboardingV2HintVariant = exports.EnyoOnboardingV2PauseReason = exports.EnyoOnboardingV2StartVariant = void 0;
|
|
26
26
|
// ---------------------------------------------------------------------------
|
|
27
27
|
// Enumerable string enums
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
@@ -40,10 +40,27 @@ var EnyoOnboardingV2StartVariant;
|
|
|
40
40
|
/** Manual setup is required by the user (e.g. enter OCPP URL). */
|
|
41
41
|
EnyoOnboardingV2StartVariant["ManualSetup"] = "manual-setup";
|
|
42
42
|
})(EnyoOnboardingV2StartVariant || (exports.EnyoOnboardingV2StartVariant = EnyoOnboardingV2StartVariant = {}));
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* Why an onboarding run left the flow through a {@link EnyoOnboardingV2TargetType.Pause}
|
|
45
|
+
* target. The reason is not a footnote — it decides what the installer sees next
|
|
46
|
+
* and how (or whether) the run is picked back up.
|
|
47
|
+
*/
|
|
44
48
|
var EnyoOnboardingV2PauseReason;
|
|
45
49
|
(function (EnyoOnboardingV2PauseReason) {
|
|
46
|
-
/**
|
|
50
|
+
/**
|
|
51
|
+
* **enyo übernimmt** — a full hand-off, not a park.
|
|
52
|
+
*
|
|
53
|
+
* Despite living under `pause` on the wire (unchanged for compatibility),
|
|
54
|
+
* this is a **terminal exit alongside `success` and `support`**: the
|
|
55
|
+
* installer is done, enyo finishes the setup, and the app renders the
|
|
56
|
+
* takeover screen instead of returning to the cockpit. Nothing is scheduled
|
|
57
|
+
* for the installer to resume, so `resumeStepName` is meaningless here.
|
|
58
|
+
*
|
|
59
|
+
* A guide whose only end is this hand-off is complete and correct;
|
|
60
|
+
* {@link validateOnboardingGuideV2} treats it as a completing exit and does
|
|
61
|
+
* **not** warn about a missing `success` path. Never invent a fake success
|
|
62
|
+
* branch to silence a warning — there is none to silence.
|
|
63
|
+
*/
|
|
47
64
|
EnyoOnboardingV2PauseReason["EnyoTodo"] = "enyo-todo";
|
|
48
65
|
/** The installer was contacted and follow-up is pending. */
|
|
49
66
|
EnyoOnboardingV2PauseReason["InstallerContacted"] = "installer-contacted";
|
|
@@ -87,7 +104,32 @@ var EnyoOnboardingV2ActionKind;
|
|
|
87
104
|
* which devices are passed along.
|
|
88
105
|
*/
|
|
89
106
|
EnyoOnboardingV2ActionKind["DeviceTest"] = "device-test";
|
|
107
|
+
/**
|
|
108
|
+
* Wait for an OCPP charger to dial into enyo's CSMS.
|
|
109
|
+
*
|
|
110
|
+
* Searches nothing: an OCPP wallbox is never on the LAN to be found, so
|
|
111
|
+
* {@link NetworkScan} is not a substitute. The installer enters the dynamic
|
|
112
|
+
* OCPP URL (see {@link EnyoOnboardingV2DynamicKind.OcppUrl}) in the
|
|
113
|
+
* charger's own configuration; this block then waits for the resulting
|
|
114
|
+
* inbound connection and branches on whether it arrived.
|
|
115
|
+
*
|
|
116
|
+
* Outcome `value`s MUST be {@link EnyoOnboardingV2OcppConnectOutcome}
|
|
117
|
+
* members, and both of them must be wired — a charger that never calls home
|
|
118
|
+
* is the common case, not an edge case.
|
|
119
|
+
*/
|
|
120
|
+
EnyoOnboardingV2ActionKind["OcppConnect"] = "ocpp-connect";
|
|
90
121
|
})(EnyoOnboardingV2ActionKind || (exports.EnyoOnboardingV2ActionKind = EnyoOnboardingV2ActionKind = {}));
|
|
122
|
+
/**
|
|
123
|
+
* The possible results of an {@link EnyoOnboardingV2ActionKind.OcppConnect}
|
|
124
|
+
* block. Deliberately binary: either the charger reached our CSMS or it did not.
|
|
125
|
+
*/
|
|
126
|
+
var EnyoOnboardingV2OcppConnectOutcome;
|
|
127
|
+
(function (EnyoOnboardingV2OcppConnectOutcome) {
|
|
128
|
+
/** The charger opened an OCPP connection to enyo's CSMS. */
|
|
129
|
+
EnyoOnboardingV2OcppConnectOutcome["Connected"] = "connected";
|
|
130
|
+
/** No connection arrived within the host's waiting window. */
|
|
131
|
+
EnyoOnboardingV2OcppConnectOutcome["Timeout"] = "timeout";
|
|
132
|
+
})(EnyoOnboardingV2OcppConnectOutcome || (exports.EnyoOnboardingV2OcppConnectOutcome = EnyoOnboardingV2OcppConnectOutcome = {}));
|
|
91
133
|
/**
|
|
92
134
|
* What an {@link EnyoOnboardingV2InputBlock} asks the installer for. Drives the
|
|
93
135
|
* keyboard the app shows, the format check it applies and the seeded texts.
|
|
@@ -144,6 +186,7 @@ var EnyoOnboardingV2BlockType;
|
|
|
144
186
|
EnyoOnboardingV2BlockType["Action"] = "action";
|
|
145
187
|
EnyoOnboardingV2BlockType["Link"] = "link";
|
|
146
188
|
EnyoOnboardingV2BlockType["Input"] = "input";
|
|
189
|
+
EnyoOnboardingV2BlockType["Auth"] = "auth";
|
|
147
190
|
})(EnyoOnboardingV2BlockType || (exports.EnyoOnboardingV2BlockType = EnyoOnboardingV2BlockType = {}));
|
|
148
191
|
// ---------------------------------------------------------------------------
|
|
149
192
|
// Routing: transitions & targets
|
|
@@ -165,9 +208,13 @@ var EnyoOnboardingV2TargetType;
|
|
|
165
208
|
EnyoOnboardingV2TargetType["Step"] = "step";
|
|
166
209
|
/** Exit: onboarding succeeded (hand back to the app). */
|
|
167
210
|
EnyoOnboardingV2TargetType["Success"] = "success";
|
|
168
|
-
/** Exit: escalate to enyo support. */
|
|
211
|
+
/** Exit: escalate to enyo support (optionally with a `reason`). */
|
|
169
212
|
EnyoOnboardingV2TargetType["Support"] = "support";
|
|
170
|
-
/**
|
|
213
|
+
/**
|
|
214
|
+
* Exit: leave the flow with an {@link EnyoOnboardingV2PauseReason}. Usually
|
|
215
|
+
* a resumable park — except {@link EnyoOnboardingV2PauseReason.EnyoTodo},
|
|
216
|
+
* which is the terminal "enyo übernimmt" hand-off.
|
|
217
|
+
*/
|
|
171
218
|
EnyoOnboardingV2TargetType["Pause"] = "pause";
|
|
172
219
|
/** Jump into the flow of another start variant for the same vendor/model. */
|
|
173
220
|
EnyoOnboardingV2TargetType["StartVariant"] = "start-variant";
|