@enyo-energy/energy-app-sdk 0.0.194 → 0.0.196
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -1
- package/dist/cjs/energy-app-package-definition.d.cts +72 -0
- package/dist/cjs/energy-app.cjs +14 -0
- package/dist/cjs/energy-app.d.cts +13 -0
- package/dist/cjs/enyo-energy-app-sdk.d.cts +3 -0
- package/dist/cjs/implementations/files/define-public-file.cjs +58 -0
- package/dist/cjs/implementations/files/define-public-file.d.cts +51 -0
- package/dist/cjs/implementations/files/public-file-validators.cjs +161 -0
- package/dist/cjs/implementations/files/public-file-validators.d.cts +77 -0
- package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.cjs +28 -1
- package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.d.cts +28 -1
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-provider-validators.cjs +167 -0
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-provider-validators.d.cts +86 -0
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.cjs +77 -3
- package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.d.cts +25 -2
- package/dist/cjs/index.cjs +5 -0
- package/dist/cjs/index.d.cts +5 -0
- package/dist/cjs/packages/energy-app-onboarding-v2.cjs +2 -0
- package/dist/cjs/packages/energy-app-onboarding-v2.d.cts +128 -0
- package/dist/cjs/packages/energy-app-onboarding.d.cts +13 -6
- package/dist/cjs/types/enyo-onboarding-v2-provider.cjs +51 -0
- package/dist/cjs/types/enyo-onboarding-v2-provider.d.cts +105 -0
- package/dist/cjs/types/enyo-onboarding-v2.d.cts +27 -3
- package/dist/cjs/version.cjs +1 -1
- package/dist/cjs/version.d.cts +1 -1
- package/dist/energy-app-package-definition.d.ts +72 -0
- package/dist/energy-app.d.ts +13 -0
- package/dist/energy-app.js +14 -0
- package/dist/enyo-energy-app-sdk.d.ts +3 -0
- package/dist/implementations/files/define-public-file.d.ts +51 -0
- package/dist/implementations/files/define-public-file.js +54 -0
- package/dist/implementations/files/public-file-validators.d.ts +77 -0
- package/dist/implementations/files/public-file-validators.js +154 -0
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.d.ts +28 -1
- package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.js +28 -1
- package/dist/implementations/onboarding-v2/onboarding-v2-provider-validators.d.ts +86 -0
- package/dist/implementations/onboarding-v2/onboarding-v2-provider-validators.js +161 -0
- package/dist/implementations/onboarding-v2/onboarding-v2-validators.d.ts +25 -2
- package/dist/implementations/onboarding-v2/onboarding-v2-validators.js +77 -3
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/packages/energy-app-onboarding-v2.d.ts +128 -0
- package/dist/packages/energy-app-onboarding-v2.js +1 -0
- package/dist/packages/energy-app-onboarding.d.ts +13 -6
- package/dist/types/enyo-onboarding-v2-provider.d.ts +105 -0
- package/dist/types/enyo-onboarding-v2-provider.js +48 -0
- package/dist/types/enyo-onboarding-v2.d.ts +27 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,7 @@ The official TypeScript SDK for building Energy Apps on the enyo platform. Creat
|
|
|
32
32
|
- [NetworkAccessGuard](#networkaccessguard)
|
|
33
33
|
- [NetworkDeviceManager](#networkdevicemanager)
|
|
34
34
|
- [Startup pattern](#startup-pattern)
|
|
35
|
+
- [Package Files](#package-files)
|
|
35
36
|
- [Firmware Update Registry](#firmware-update-registry)
|
|
36
37
|
- [Declaring firmware](#declaring-firmware)
|
|
37
38
|
- [Firmware modes](#firmware-modes)
|
|
@@ -171,7 +172,8 @@ The SDK exposes several layered building blocks. Pick the one that matches the k
|
|
|
171
172
|
| Manage electricity tariffs (default tariff, price per kWh) | [`useElectricityTariff()`](#useelectricitytariff-energyappelectricitytariff) |
|
|
172
173
|
| Register a PV system (kWp, DC strings, orientation) | [`usePvSystem()`](#usepvsystem-energyapppvsystem) |
|
|
173
174
|
| Discover capabilities of the active energy manager | [`useEnergyManager()`](#useenergymanager-energyappenergymanager) |
|
|
174
|
-
|
|
|
175
|
+
| Serve your v2 onboarding guides when the host asks for them | [`useOnboardingV2()`](#useonboardingv2-energyapponboardingv2) |
|
|
176
|
+
| Drive a multi-step onboarding flow (v1, deprecated) | [`useOnboarding()`](#useonboarding-energyapponboarding) |
|
|
175
177
|
| Allocate process-local sequential IDs | [`useSequenceGenerator()`](#usesequencegenerator-energyappsequencegenerator) |
|
|
176
178
|
| Manage retries with circuit-breaker semantics | [`RetryManager`](#retry-framework) |
|
|
177
179
|
| Keep an `applianceId` cache in sync with the SDK | [`ApplianceManager`](#appliance-management) |
|
|
@@ -1333,8 +1335,36 @@ Requires the `Savings` permission.
|
|
|
1333
1335
|
|
|
1334
1336
|
### Operational Utilities
|
|
1335
1337
|
|
|
1338
|
+
#### `useOnboardingV2(): EnergyAppOnboardingV2`
|
|
1339
|
+
|
|
1340
|
+
Serve the onboarding guides your app ships. Guides are **pulled, not published**: you register one handler, and the host calls it with "give me your v2 onboarding guides". You answer with the **complete set** or with **nothing** — there is no save, update or delete, and every answer replaces the host's picture of what your app offers.
|
|
1341
|
+
|
|
1342
|
+
```typescript
|
|
1343
|
+
await energyApp.useOnboardingV2().registerOnboardingGuidesHandler(async (request) => {
|
|
1344
|
+
const result = { requestId: request.requestId, guides: buildGuides() };
|
|
1345
|
+
|
|
1346
|
+
const { ok, errors } = validateOnboardingV2GuidesResult(result, {
|
|
1347
|
+
files: packageDefinition.files,
|
|
1348
|
+
});
|
|
1349
|
+
if (!ok) {
|
|
1350
|
+
console.error('onboarding guides invalid', errors);
|
|
1351
|
+
return null; // keep whatever the host already has
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
return result;
|
|
1355
|
+
});
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
`null` and `[]` are **not** the same answer: `[]` says "I genuinely have no guides" and drops the host's cached ones, `null` says "I cannot answer right now" and leaves them alone. Use `null` for transient failures. The host stops waiting after `request.timeoutMs`, so build the guides in memory rather than fetching them.
|
|
1359
|
+
|
|
1360
|
+
Each guide must carry the `vendorId`, `modelIds` and `startVariant` it applies to — that is how the host selects one for a run, and there is no publish step left to bind them. See [ONBOARDING.md](./ONBOARDING.md#serving-guides-the-host-pulls-the-app-never-publishes) for the full v2 model.
|
|
1361
|
+
|
|
1362
|
+
Not permission-gated.
|
|
1363
|
+
|
|
1336
1364
|
#### `useOnboarding(): EnergyAppOnboarding`
|
|
1337
1365
|
|
|
1366
|
+
> **Deprecated** — the v1 model. New guides use the v2 graph model and are served through [`useOnboardingV2()`](#useonboardingv2-energyapponboardingv2).
|
|
1367
|
+
|
|
1338
1368
|
Drive a multi-step onboarding guide — start / advance / back / skip / cancel, persist responses, and observe step transitions.
|
|
1339
1369
|
|
|
1340
1370
|
```typescript
|
|
@@ -1778,6 +1808,65 @@ async function connectDevice(networkDeviceId: string) {
|
|
|
1778
1808
|
|
|
1779
1809
|
This pattern matches the wiring used by real Sungrow / Fronius energy-app packages: one `NetworkDeviceManager` per package, `ensureAccess` before every connect, `withAccessGuard` around every poll, and a single `getDevices({ accessStatus: 'granted' })` pass at startup to cover the warm-restart case.
|
|
1780
1810
|
|
|
1811
|
+
## Package Files
|
|
1812
|
+
|
|
1813
|
+
Ship images and other public assets with your package. You declare them by local path
|
|
1814
|
+
and name in the package definition, the enyo CLI uploads them during `enyo release`,
|
|
1815
|
+
and the rest of the definition refers to each upload **by name** rather than by URL —
|
|
1816
|
+
so nothing has to hard-code a URL that only exists after the release.
|
|
1817
|
+
|
|
1818
|
+
Today the consumer is the onboarding v2 image block (see ONBOARDING.md).
|
|
1819
|
+
|
|
1820
|
+
```typescript
|
|
1821
|
+
import {
|
|
1822
|
+
defineEnergyAppPackage,
|
|
1823
|
+
definePublicFile,
|
|
1824
|
+
validatePackageFiles
|
|
1825
|
+
} from '@enyo-energy/energy-app-sdk';
|
|
1826
|
+
|
|
1827
|
+
const packageDef = defineEnergyAppPackage({
|
|
1828
|
+
// ...
|
|
1829
|
+
files: [
|
|
1830
|
+
definePublicFile({
|
|
1831
|
+
name: 'dip-switches',
|
|
1832
|
+
path: './assets/onboarding/dip-switches.png',
|
|
1833
|
+
internalComment: 'Photo of the DIP block behind the front cover'
|
|
1834
|
+
}),
|
|
1835
|
+
definePublicFile({name: 'wiring', path: './assets/onboarding/wiring.jpg'})
|
|
1836
|
+
]
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
// then, in a guide:
|
|
1840
|
+
onboardingV2Block.imageFile('show-dips', 'dip-switches');
|
|
1841
|
+
```
|
|
1842
|
+
|
|
1843
|
+
| Field | Meaning |
|
|
1844
|
+
|---|---|
|
|
1845
|
+
| `name` | Kebab-case slug, unique in the package. The handle everything else refers to — treat it as an API; renaming it breaks every reference. |
|
|
1846
|
+
| `path` | Path relative to the package root. Must stay inside the package: absolute paths, `..` and URLs are rejected. |
|
|
1847
|
+
| `mimeType` | Optional; inferred from the extension when omitted. |
|
|
1848
|
+
| `internalComment` | Optional note, never shown to users. |
|
|
1849
|
+
|
|
1850
|
+
**Every entry is public.** The upload is readable by anyone with (or guessing) its URL,
|
|
1851
|
+
with no authentication in front of it — that is the point: an installer app has to show
|
|
1852
|
+
the image before the energy app runs anywhere. Declare vendor material only —
|
|
1853
|
+
installation diagrams, wiring photos, product shots — never customer data, credentials
|
|
1854
|
+
or licensed content.
|
|
1855
|
+
|
|
1856
|
+
This is unrelated to `useFiles()`, the runtime API for offering a user a file to save:
|
|
1857
|
+
those bytes are produced on demand by the running app and never become a URL.
|
|
1858
|
+
|
|
1859
|
+
Validate the declarations before releasing:
|
|
1860
|
+
|
|
1861
|
+
```typescript
|
|
1862
|
+
const {ok, errors, warnings} = validatePackageFiles(packageDef.files);
|
|
1863
|
+
// or: assertValidPackageFiles(packageDef.files) — throws PackageFilesValidationError
|
|
1864
|
+
```
|
|
1865
|
+
|
|
1866
|
+
It checks that names are unique slugs, that paths stay inside the package and carry an
|
|
1867
|
+
extension, and that any declared MIME type is well formed; two names pointing at one
|
|
1868
|
+
path is a warning.
|
|
1869
|
+
|
|
1781
1870
|
## Firmware Update Registry
|
|
1782
1871
|
|
|
1783
1872
|
Ship firmware images with your package and hand them to devices at runtime. You declare the files by local path in the package definition, the enyo CLI uploads them during `enyo release`, and the app reaches them through `energyApp.useFirmwareRegistry()`. The release tarball never carries the bytes.
|
|
@@ -261,6 +261,63 @@ export interface EnergyAppPackageCompatibilityVendor {
|
|
|
261
261
|
/** Models from this vendor that the package supports */
|
|
262
262
|
models: EnergyAppPackageCompatibilityModel[];
|
|
263
263
|
}
|
|
264
|
+
/**
|
|
265
|
+
* A file published together with an Energy App package and served publicly
|
|
266
|
+
* after upload.
|
|
267
|
+
*
|
|
268
|
+
* The file is declared here by its local {@link path}; the enyo CLI uploads it
|
|
269
|
+
* during `enyo release` and the published definition carries the resulting
|
|
270
|
+
* public URL, so the released package tarball never has to carry the bytes.
|
|
271
|
+
* Other parts of the definition refer to the upload by its {@link name} rather
|
|
272
|
+
* than by URL — an onboarding v2 image block
|
|
273
|
+
* ({@link EnyoOnboardingV2ImageBlock.file}) is the first consumer — which keeps
|
|
274
|
+
* the URL an implementation detail that enyo resolves at render time.
|
|
275
|
+
*
|
|
276
|
+
* **Everything declared here is public.** The upload is readable by anyone who
|
|
277
|
+
* has (or guesses) its URL, with no authentication in front of it: it exists so
|
|
278
|
+
* that an installer app can show an image before the energy app runs anywhere.
|
|
279
|
+
* Declare only vendor material safe to publish — installation diagrams, wiring
|
|
280
|
+
* photos, product shots. Never customer data, credentials or licensed content.
|
|
281
|
+
*
|
|
282
|
+
* Not to be confused with {@link EnergyAppFile}, the *runtime* API for offering
|
|
283
|
+
* a user a file to save: those bytes are produced on demand by the running app,
|
|
284
|
+
* are scoped to a device, and never become a URL.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* ```typescript
|
|
288
|
+
* files: [
|
|
289
|
+
* definePublicFile({
|
|
290
|
+
* name: 'dip-switches',
|
|
291
|
+
* path: './assets/onboarding/dip-switches.png',
|
|
292
|
+
* internalComment: 'Photo of the DIP block behind the front cover'
|
|
293
|
+
* })
|
|
294
|
+
* ]
|
|
295
|
+
* ```
|
|
296
|
+
*/
|
|
297
|
+
export interface EnergyAppPackagePublicFile {
|
|
298
|
+
/**
|
|
299
|
+
* Stable, app-chosen name for this file: a kebab-case slug, unique within
|
|
300
|
+
* the package. This is the handle the rest of the definition refers to, so
|
|
301
|
+
* treat it as an API — renaming it breaks every reference to the file.
|
|
302
|
+
*/
|
|
303
|
+
name: string;
|
|
304
|
+
/**
|
|
305
|
+
* Path to the file relative to the package root, e.g.
|
|
306
|
+
* `'./assets/onboarding/dip-switches.png'`. Must stay inside the package:
|
|
307
|
+
* absolute paths, `../` segments and URLs are rejected. Resolved and
|
|
308
|
+
* uploaded by the enyo CLI on release; the published definition carries a
|
|
309
|
+
* public URL instead.
|
|
310
|
+
*/
|
|
311
|
+
path: string;
|
|
312
|
+
/**
|
|
313
|
+
* Optional IANA MIME type served with the file, e.g. `image/png`. Inferred
|
|
314
|
+
* from the extension when omitted; set it explicitly only when the
|
|
315
|
+
* extension is missing or misleading.
|
|
316
|
+
*/
|
|
317
|
+
mimeType?: string;
|
|
318
|
+
/** Optional internal note explaining this file; never shown to users. */
|
|
319
|
+
internalComment?: string;
|
|
320
|
+
}
|
|
264
321
|
/**
|
|
265
322
|
* How the firmware registry decides which image a device should install next.
|
|
266
323
|
*
|
|
@@ -423,6 +480,21 @@ export interface EnergyAppPackageDefinition {
|
|
|
423
480
|
* single vendor implicitly or has no fixed compatibility surface.
|
|
424
481
|
*/
|
|
425
482
|
compatibility: EnergyAppPackageCompatibilityVendor[];
|
|
483
|
+
/**
|
|
484
|
+
* Files shipped with this package, declared as local paths, uploaded by the
|
|
485
|
+
* enyo CLI on release and served publicly from then on.
|
|
486
|
+
*
|
|
487
|
+
* Each entry is referenced elsewhere in the package by its `name` — today
|
|
488
|
+
* from an onboarding v2 image block
|
|
489
|
+
* ({@link EnyoOnboardingV2ImageBlock.file}) — so a guide never has to
|
|
490
|
+
* hard-code a URL. Validate the declarations with `validatePackageFiles()`
|
|
491
|
+
* before publishing.
|
|
492
|
+
*
|
|
493
|
+
* Every entry is publicly readable by anyone with its URL; see
|
|
494
|
+
* {@link EnergyAppPackagePublicFile} for what that rules out. Omit for
|
|
495
|
+
* packages that ship no assets.
|
|
496
|
+
*/
|
|
497
|
+
files?: EnergyAppPackagePublicFile[];
|
|
426
498
|
/**
|
|
427
499
|
* Firmware images shipped with this package, declared as local file paths
|
|
428
500
|
* and uploaded by the enyo CLI on release.
|
package/dist/cjs/energy-app.cjs
CHANGED
|
@@ -117,6 +117,20 @@ class EnergyApp {
|
|
|
117
117
|
useOnboarding() {
|
|
118
118
|
return this.energyAppSdk.useOnboarding();
|
|
119
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* Gets the Onboarding v2 API for serving this app's onboarding guides.
|
|
122
|
+
*
|
|
123
|
+
* Guides are pulled rather than published: the app registers one handler and
|
|
124
|
+
* the host calls it with "give me your v2 onboarding guides", receiving the
|
|
125
|
+
* complete current set or nothing. There is no save, update or delete —
|
|
126
|
+
* every answer replaces the host's picture of what this app offers.
|
|
127
|
+
*
|
|
128
|
+
* Available to every app — this API is not permission-gated.
|
|
129
|
+
* @returns The Onboarding v2 API instance
|
|
130
|
+
*/
|
|
131
|
+
useOnboardingV2() {
|
|
132
|
+
return this.energyAppSdk.useOnboardingV2();
|
|
133
|
+
}
|
|
120
134
|
/**
|
|
121
135
|
* Gets the Secret Manager API for retrieving secrets from the developer organization.
|
|
122
136
|
* Provides methods to fetch secrets that have been configured in the developer org's secret store.
|
|
@@ -16,6 +16,7 @@ import { EnergyAppNotification } from "./packages/energy-app-notification.cjs";
|
|
|
16
16
|
import { EnergyAppSecretManager } from "./packages/energy-app-secret-manager.cjs";
|
|
17
17
|
import { EnergyAppLocation } from "./packages/energy-app-location.cjs";
|
|
18
18
|
import { EnergyAppOnboarding } from "./packages/energy-app-onboarding.cjs";
|
|
19
|
+
import { EnergyAppOnboardingV2 } from "./packages/energy-app-onboarding-v2.cjs";
|
|
19
20
|
import { EnergyAppTimeseries } from "./packages/energy-app-timeseries.cjs";
|
|
20
21
|
import { EnyoPackageChannel } from "./enyo-package-channel.cjs";
|
|
21
22
|
import { EnergyAppEnergyManager } from "./packages/energy-app-energy-manager.cjs";
|
|
@@ -99,6 +100,18 @@ export declare class EnergyApp implements EnyoEnergyAppSdk {
|
|
|
99
100
|
useElectricityPrices(): EnergyAppEnergyPrices;
|
|
100
101
|
useNotification(): EnergyAppNotification;
|
|
101
102
|
useOnboarding(): EnergyAppOnboarding;
|
|
103
|
+
/**
|
|
104
|
+
* Gets the Onboarding v2 API for serving this app's onboarding guides.
|
|
105
|
+
*
|
|
106
|
+
* Guides are pulled rather than published: the app registers one handler and
|
|
107
|
+
* the host calls it with "give me your v2 onboarding guides", receiving the
|
|
108
|
+
* complete current set or nothing. There is no save, update or delete —
|
|
109
|
+
* every answer replaces the host's picture of what this app offers.
|
|
110
|
+
*
|
|
111
|
+
* Available to every app — this API is not permission-gated.
|
|
112
|
+
* @returns The Onboarding v2 API instance
|
|
113
|
+
*/
|
|
114
|
+
useOnboardingV2(): EnergyAppOnboardingV2;
|
|
102
115
|
/**
|
|
103
116
|
* Gets the Secret Manager API for retrieving secrets from the developer organization.
|
|
104
117
|
* Provides methods to fetch secrets that have been configured in the developer org's secret store.
|
|
@@ -15,6 +15,7 @@ import { EnergyAppNotification } from "./packages/energy-app-notification.cjs";
|
|
|
15
15
|
import { EnergyAppSecretManager } from "./packages/energy-app-secret-manager.cjs";
|
|
16
16
|
import { EnergyAppLocation } from "./packages/energy-app-location.cjs";
|
|
17
17
|
import { EnergyAppOnboarding } from "./packages/energy-app-onboarding.cjs";
|
|
18
|
+
import { EnergyAppOnboardingV2 } from "./packages/energy-app-onboarding-v2.cjs";
|
|
18
19
|
import { EnergyAppTimeseries } from "./packages/energy-app-timeseries.cjs";
|
|
19
20
|
import { EnyoPackageChannel } from "./enyo-package-channel.cjs";
|
|
20
21
|
import { EnergyAppEnergyManager } from "./packages/energy-app-energy-manager.cjs";
|
|
@@ -106,6 +107,8 @@ export interface EnyoEnergyAppSdk {
|
|
|
106
107
|
useLocation: () => EnergyAppLocation;
|
|
107
108
|
/** Get the Onboarding API */
|
|
108
109
|
useOnboarding: () => EnergyAppOnboarding;
|
|
110
|
+
/** Get the Onboarding v2 API for registering the handler the host calls to collect this app's onboarding guides */
|
|
111
|
+
useOnboardingV2: () => EnergyAppOnboardingV2;
|
|
109
112
|
/** Get the Timeseries API for querying historical energy data */
|
|
110
113
|
useTimeseries: () => EnergyAppTimeseries;
|
|
111
114
|
/** Get the Energy Manager API for retrieving energy manager info and capabilities */
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Ergonomic authoring helpers for a package's public files
|
|
4
|
+
* ({@link EnergyAppPackageDefinition.files}).
|
|
5
|
+
*
|
|
6
|
+
* `definePublicFile()` is an identity helper that type-checks a file entry
|
|
7
|
+
* where it is written — mirroring the SDK's `defineEnergyAppPackage()` and
|
|
8
|
+
* `defineFirmwareFile()` pattern. `resolvePublicFile()` performs the same
|
|
9
|
+
* name lookup enyo does when it renders a reference, so an app can unit-test
|
|
10
|
+
* that every name it uses actually resolves.
|
|
11
|
+
*
|
|
12
|
+
* Pair with `validatePackageFiles()` (`./public-file-validators.ts`) to fail
|
|
13
|
+
* fast before publishing.
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.definePublicFile = definePublicFile;
|
|
17
|
+
exports.resolvePublicFile = resolvePublicFile;
|
|
18
|
+
/**
|
|
19
|
+
* Identity helper that type-checks a public file entry at definition time
|
|
20
|
+
* (mirrors `defineEnergyAppPackage`). Prefer this over a bare object literal so
|
|
21
|
+
* mistakes surface where the entry is written.
|
|
22
|
+
*
|
|
23
|
+
* @param file - The public file entry to declare.
|
|
24
|
+
* @returns The same entry, typed as {@link EnergyAppPackagePublicFile}.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* definePublicFile({
|
|
29
|
+
* name: 'dip-switches',
|
|
30
|
+
* path: './assets/onboarding/dip-switches.png'
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
function definePublicFile(file) {
|
|
35
|
+
return file;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Looks up a declared public file by its name.
|
|
39
|
+
*
|
|
40
|
+
* This is the resolution step enyo performs for a reference such as an
|
|
41
|
+
* onboarding v2 image block's `file`. Exposed so an app can assert in its own
|
|
42
|
+
* tests that the names its guides use are declared, rather than discovering a
|
|
43
|
+
* typo as a missing image on an installer's screen.
|
|
44
|
+
*
|
|
45
|
+
* @param files - The package's declared public files, or `undefined` when the
|
|
46
|
+
* package declares none.
|
|
47
|
+
* @param name - The file name to resolve.
|
|
48
|
+
* @returns The matching entry, or `undefined` when no entry carries that name.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const file = resolvePublicFile(packageDef.files, 'dip-switches');
|
|
53
|
+
* if (!file) throw new Error('image reference does not resolve');
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function resolvePublicFile(files, name) {
|
|
57
|
+
return files?.find(file => file.name === name);
|
|
58
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ergonomic authoring helpers for a package's public files
|
|
3
|
+
* ({@link EnergyAppPackageDefinition.files}).
|
|
4
|
+
*
|
|
5
|
+
* `definePublicFile()` is an identity helper that type-checks a file entry
|
|
6
|
+
* where it is written — mirroring the SDK's `defineEnergyAppPackage()` and
|
|
7
|
+
* `defineFirmwareFile()` pattern. `resolvePublicFile()` performs the same
|
|
8
|
+
* name lookup enyo does when it renders a reference, so an app can unit-test
|
|
9
|
+
* that every name it uses actually resolves.
|
|
10
|
+
*
|
|
11
|
+
* Pair with `validatePackageFiles()` (`./public-file-validators.ts`) to fail
|
|
12
|
+
* fast before publishing.
|
|
13
|
+
*/
|
|
14
|
+
import type { EnergyAppPackagePublicFile } from '../../energy-app-package-definition.cjs';
|
|
15
|
+
/**
|
|
16
|
+
* Identity helper that type-checks a public file entry at definition time
|
|
17
|
+
* (mirrors `defineEnergyAppPackage`). Prefer this over a bare object literal so
|
|
18
|
+
* mistakes surface where the entry is written.
|
|
19
|
+
*
|
|
20
|
+
* @param file - The public file entry to declare.
|
|
21
|
+
* @returns The same entry, typed as {@link EnergyAppPackagePublicFile}.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* definePublicFile({
|
|
26
|
+
* name: 'dip-switches',
|
|
27
|
+
* path: './assets/onboarding/dip-switches.png'
|
|
28
|
+
* })
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare function definePublicFile(file: EnergyAppPackagePublicFile): EnergyAppPackagePublicFile;
|
|
32
|
+
/**
|
|
33
|
+
* Looks up a declared public file by its name.
|
|
34
|
+
*
|
|
35
|
+
* This is the resolution step enyo performs for a reference such as an
|
|
36
|
+
* onboarding v2 image block's `file`. Exposed so an app can assert in its own
|
|
37
|
+
* tests that the names its guides use are declared, rather than discovering a
|
|
38
|
+
* typo as a missing image on an installer's screen.
|
|
39
|
+
*
|
|
40
|
+
* @param files - The package's declared public files, or `undefined` when the
|
|
41
|
+
* package declares none.
|
|
42
|
+
* @param name - The file name to resolve.
|
|
43
|
+
* @returns The matching entry, or `undefined` when no entry carries that name.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const file = resolvePublicFile(packageDef.files, 'dip-switches');
|
|
48
|
+
* if (!file) throw new Error('image reference does not resolve');
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolvePublicFile(files: EnergyAppPackagePublicFile[] | undefined, name: string): EnergyAppPackagePublicFile | undefined;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Client-side validator for the public files declared in a package definition
|
|
4
|
+
* ({@link EnergyAppPackageDefinition.files}).
|
|
5
|
+
*
|
|
6
|
+
* The declarations are resolved twice after they leave the app repository: the
|
|
7
|
+
* enyo CLI reads each `path` from the package at release time, and enyo resolves
|
|
8
|
+
* each `name` when something references the upload. Both steps happen far from
|
|
9
|
+
* the author, so anything that makes them fail — a duplicate name, a path
|
|
10
|
+
* pointing outside the package — is a blocking `error` caught here instead.
|
|
11
|
+
*
|
|
12
|
+
* `warnings` are advisory: a file nothing references still uploads fine, it is
|
|
13
|
+
* just dead weight in the release.
|
|
14
|
+
*
|
|
15
|
+
* Use {@link validatePackageFiles} for the non-throwing result, or
|
|
16
|
+
* {@link assertValidPackageFiles} to throw on failure.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.PackageFilesValidationError = void 0;
|
|
20
|
+
exports.isImagePublicFile = isImagePublicFile;
|
|
21
|
+
exports.validatePackageFiles = validatePackageFiles;
|
|
22
|
+
exports.assertValidPackageFiles = assertValidPackageFiles;
|
|
23
|
+
/**
|
|
24
|
+
* Thrown by {@link assertValidPackageFiles} when a package's public file
|
|
25
|
+
* declarations fail validation. The message lists every blocking error so
|
|
26
|
+
* callers can surface them directly.
|
|
27
|
+
*/
|
|
28
|
+
class PackageFilesValidationError extends Error {
|
|
29
|
+
/** The individual blocking errors that caused the failure. */
|
|
30
|
+
errors;
|
|
31
|
+
/**
|
|
32
|
+
* @param errors - The blocking validation errors.
|
|
33
|
+
*/
|
|
34
|
+
constructor(errors) {
|
|
35
|
+
super(`Invalid package files:\n- ${errors.join('\n- ')}`);
|
|
36
|
+
this.name = 'PackageFilesValidationError';
|
|
37
|
+
this.errors = errors;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.PackageFilesValidationError = PackageFilesValidationError;
|
|
41
|
+
/** A public file `name` — the same kebab-case slug shape used for step names. */
|
|
42
|
+
const SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
43
|
+
/**
|
|
44
|
+
* A path escaping the package: absolute (`/x`, `C:\x`), URL-like (`https://x`,
|
|
45
|
+
* `data:...`), or climbing out via `..`. The CLI resolves `path` against the
|
|
46
|
+
* package root, so any of these either breaks the release or pulls in a file
|
|
47
|
+
* that was never reviewed with the package.
|
|
48
|
+
*/
|
|
49
|
+
const ESCAPING_PATH_RE = /^(?:[/\\]|[a-zA-Z][a-zA-Z\d+.-]*:)|(?:^|[/\\])\.\.(?:[/\\]|$)/;
|
|
50
|
+
/**
|
|
51
|
+
* File extensions enyo serves as images. A reference from an image block must
|
|
52
|
+
* name one of these; other extensions upload fine and are simply never usable
|
|
53
|
+
* as an image.
|
|
54
|
+
*/
|
|
55
|
+
const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp', '.gif', '.svg'];
|
|
56
|
+
/**
|
|
57
|
+
* Extracts the lower-cased extension of a path, including the leading dot.
|
|
58
|
+
*
|
|
59
|
+
* @param path - The declared file path.
|
|
60
|
+
* @returns The extension (e.g. `'.png'`), or an empty string when the last
|
|
61
|
+
* segment carries none.
|
|
62
|
+
*/
|
|
63
|
+
function extensionOf(path) {
|
|
64
|
+
const segment = path.split(/[/\\]/).pop() ?? '';
|
|
65
|
+
const dot = segment.lastIndexOf('.');
|
|
66
|
+
return dot > 0 ? segment.slice(dot).toLowerCase() : '';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Tests whether a declared file can be served as an image, i.e. whether an
|
|
70
|
+
* onboarding image block may reference it.
|
|
71
|
+
*
|
|
72
|
+
* Judged by the declared {@link EnergyAppPackagePublicFile.mimeType} when one is
|
|
73
|
+
* set, and by the path's extension otherwise — the same order the CLI uses when
|
|
74
|
+
* it picks the upload's content type.
|
|
75
|
+
*
|
|
76
|
+
* @param file - The declared public file.
|
|
77
|
+
* @returns True when the file is an image.
|
|
78
|
+
*/
|
|
79
|
+
function isImagePublicFile(file) {
|
|
80
|
+
if (file.mimeType)
|
|
81
|
+
return file.mimeType.toLowerCase().startsWith('image/');
|
|
82
|
+
return IMAGE_EXTENSIONS.includes(extensionOf(file.path));
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Structural validation of a package's public file declarations: names are
|
|
86
|
+
* unique kebab-case slugs, paths stay inside the package and look like files,
|
|
87
|
+
* and any declared MIME type is well formed.
|
|
88
|
+
*
|
|
89
|
+
* @param files - The declared public files, or `undefined` when the package
|
|
90
|
+
* declares none (trivially valid).
|
|
91
|
+
* @returns The {@link PackageFilesValidationResult}.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const {ok, errors} = validatePackageFiles(packageDef.files);
|
|
96
|
+
* if (!ok) throw new Error(errors.join('\n'));
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
function validatePackageFiles(files) {
|
|
100
|
+
const errors = [];
|
|
101
|
+
const warnings = [];
|
|
102
|
+
if (!files?.length)
|
|
103
|
+
return { ok: true, errors, warnings };
|
|
104
|
+
const names = new Set();
|
|
105
|
+
const paths = new Map();
|
|
106
|
+
for (const [index, file] of files.entries()) {
|
|
107
|
+
const at = `files[${index}] (${file.name || '?'})`;
|
|
108
|
+
if (!file.name?.trim()) {
|
|
109
|
+
errors.push(`${at}: name is required.`);
|
|
110
|
+
}
|
|
111
|
+
else if (!SLUG_RE.test(file.name)) {
|
|
112
|
+
errors.push(`${at}: name must be a kebab-case slug, e.g. "dip-switches".`);
|
|
113
|
+
}
|
|
114
|
+
else if (names.has(file.name)) {
|
|
115
|
+
errors.push(`${at}: duplicate name "${file.name}" — names must be unique within the package.`);
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
names.add(file.name);
|
|
119
|
+
}
|
|
120
|
+
const path = file.path?.trim();
|
|
121
|
+
if (!path) {
|
|
122
|
+
errors.push(`${at}: path is required.`);
|
|
123
|
+
}
|
|
124
|
+
else if (ESCAPING_PATH_RE.test(path)) {
|
|
125
|
+
errors.push(`${at}: path "${path}" must be relative to the package root — no absolute paths, "..", or URLs.`);
|
|
126
|
+
}
|
|
127
|
+
else if (!extensionOf(path)) {
|
|
128
|
+
errors.push(`${at}: path "${path}" has no file extension.`);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// The same bytes under two names upload twice and drift apart when
|
|
132
|
+
// only one reference is updated later.
|
|
133
|
+
const previous = paths.get(path);
|
|
134
|
+
if (previous) {
|
|
135
|
+
warnings.push(`${at}: path "${path}" is already declared as "${previous}".`);
|
|
136
|
+
}
|
|
137
|
+
else if (file.name) {
|
|
138
|
+
paths.set(path, file.name);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (file.mimeType !== undefined && !/^[\w.+-]+\/[\w.+-]+$/.test(file.mimeType)) {
|
|
142
|
+
errors.push(`${at}: mimeType "${file.mimeType}" is not a valid MIME type.`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { ok: errors.length === 0, errors, warnings };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Like {@link validatePackageFiles}, but throws
|
|
149
|
+
* {@link PackageFilesValidationError} when there are blocking errors. Warnings
|
|
150
|
+
* never throw; the validated declarations are returned on success for chaining.
|
|
151
|
+
*
|
|
152
|
+
* @param files - The declared public files.
|
|
153
|
+
* @returns The same declarations when they have no blocking errors.
|
|
154
|
+
* @throws {PackageFilesValidationError} When validation produces any error.
|
|
155
|
+
*/
|
|
156
|
+
function assertValidPackageFiles(files) {
|
|
157
|
+
const { ok, errors } = validatePackageFiles(files);
|
|
158
|
+
if (!ok)
|
|
159
|
+
throw new PackageFilesValidationError(errors);
|
|
160
|
+
return files;
|
|
161
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client-side validator for the public files declared in a package definition
|
|
3
|
+
* ({@link EnergyAppPackageDefinition.files}).
|
|
4
|
+
*
|
|
5
|
+
* The declarations are resolved twice after they leave the app repository: the
|
|
6
|
+
* enyo CLI reads each `path` from the package at release time, and enyo resolves
|
|
7
|
+
* each `name` when something references the upload. Both steps happen far from
|
|
8
|
+
* the author, so anything that makes them fail — a duplicate name, a path
|
|
9
|
+
* pointing outside the package — is a blocking `error` caught here instead.
|
|
10
|
+
*
|
|
11
|
+
* `warnings` are advisory: a file nothing references still uploads fine, it is
|
|
12
|
+
* just dead weight in the release.
|
|
13
|
+
*
|
|
14
|
+
* Use {@link validatePackageFiles} for the non-throwing result, or
|
|
15
|
+
* {@link assertValidPackageFiles} to throw on failure.
|
|
16
|
+
*/
|
|
17
|
+
import type { EnergyAppPackagePublicFile } from '../../energy-app-package-definition.cjs';
|
|
18
|
+
/**
|
|
19
|
+
* Thrown by {@link assertValidPackageFiles} when a package's public file
|
|
20
|
+
* declarations fail validation. The message lists every blocking error so
|
|
21
|
+
* callers can surface them directly.
|
|
22
|
+
*/
|
|
23
|
+
export declare class PackageFilesValidationError extends Error {
|
|
24
|
+
/** The individual blocking errors that caused the failure. */
|
|
25
|
+
readonly errors: string[];
|
|
26
|
+
/**
|
|
27
|
+
* @param errors - The blocking validation errors.
|
|
28
|
+
*/
|
|
29
|
+
constructor(errors: string[]);
|
|
30
|
+
}
|
|
31
|
+
/** The outcome of validating a package's public file declarations. */
|
|
32
|
+
export interface PackageFilesValidationResult {
|
|
33
|
+
/** True when there are no blocking `errors` (warnings are still allowed). */
|
|
34
|
+
ok: boolean;
|
|
35
|
+
/** Blocking problems — these must be fixed before releasing. */
|
|
36
|
+
errors: string[];
|
|
37
|
+
/** Advisory problems — allowed, but usually worth reviewing. */
|
|
38
|
+
warnings: string[];
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Tests whether a declared file can be served as an image, i.e. whether an
|
|
42
|
+
* onboarding image block may reference it.
|
|
43
|
+
*
|
|
44
|
+
* Judged by the declared {@link EnergyAppPackagePublicFile.mimeType} when one is
|
|
45
|
+
* set, and by the path's extension otherwise — the same order the CLI uses when
|
|
46
|
+
* it picks the upload's content type.
|
|
47
|
+
*
|
|
48
|
+
* @param file - The declared public file.
|
|
49
|
+
* @returns True when the file is an image.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isImagePublicFile(file: EnergyAppPackagePublicFile): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Structural validation of a package's public file declarations: names are
|
|
54
|
+
* unique kebab-case slugs, paths stay inside the package and look like files,
|
|
55
|
+
* and any declared MIME type is well formed.
|
|
56
|
+
*
|
|
57
|
+
* @param files - The declared public files, or `undefined` when the package
|
|
58
|
+
* declares none (trivially valid).
|
|
59
|
+
* @returns The {@link PackageFilesValidationResult}.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* const {ok, errors} = validatePackageFiles(packageDef.files);
|
|
64
|
+
* if (!ok) throw new Error(errors.join('\n'));
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function validatePackageFiles(files: EnergyAppPackagePublicFile[] | undefined): PackageFilesValidationResult;
|
|
68
|
+
/**
|
|
69
|
+
* Like {@link validatePackageFiles}, but throws
|
|
70
|
+
* {@link PackageFilesValidationError} when there are blocking errors. Warnings
|
|
71
|
+
* never throw; the validated declarations are returned on success for chaining.
|
|
72
|
+
*
|
|
73
|
+
* @param files - The declared public files.
|
|
74
|
+
* @returns The same declarations when they have no blocking errors.
|
|
75
|
+
* @throws {PackageFilesValidationError} When validation produces any error.
|
|
76
|
+
*/
|
|
77
|
+
export declare function assertValidPackageFiles(files: EnergyAppPackagePublicFile[] | undefined): EnergyAppPackagePublicFile[] | undefined;
|
|
@@ -67,12 +67,39 @@ exports.onboardingV2Block = {
|
|
|
67
67
|
*/
|
|
68
68
|
bullets: (id, items) => ({ id, type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Bullets, items }),
|
|
69
69
|
/**
|
|
70
|
-
* An image block.
|
|
70
|
+
* An image block addressing an externally hosted image by URL.
|
|
71
|
+
*
|
|
72
|
+
* Prefer {@link block.imageFile} for an image that lives in the app
|
|
73
|
+
* repository: nothing mirrors the URL passed here, so the guide is only as
|
|
74
|
+
* available as the host serving it.
|
|
75
|
+
*
|
|
71
76
|
* @param id - Stable block id, unique within the guide.
|
|
72
77
|
* @param url - Public asset URL.
|
|
73
78
|
* @param caption - Optional translated caption (de/en).
|
|
74
79
|
*/
|
|
75
80
|
image: (id, url, caption) => ({ id, type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Image, url, caption }),
|
|
81
|
+
/**
|
|
82
|
+
* An image block addressing a file shipped with the package.
|
|
83
|
+
*
|
|
84
|
+
* The file must be declared in the package definition's `files`
|
|
85
|
+
* ({@link EnergyAppPackagePublicFile}); the enyo CLI uploads it on release
|
|
86
|
+
* and enyo resolves the name to a public URL when the guide is rendered.
|
|
87
|
+
* Pass the package's declarations to `validateOnboardingGuideV2()` to have
|
|
88
|
+
* a mistyped name rejected before publishing.
|
|
89
|
+
*
|
|
90
|
+
* @param id - Stable block id, unique within the guide.
|
|
91
|
+
* @param file - Name of the declared package file, e.g. `'dip-switches'`.
|
|
92
|
+
* @param caption - Optional translated caption (de/en).
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* block.imageFile('dip', 'dip-switches', [
|
|
97
|
+
* {language: 'de', value: 'DIP-Schalter hinter der Frontblende'},
|
|
98
|
+
* {language: 'en', value: 'DIP switches behind the front cover'}
|
|
99
|
+
* ])
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
imageFile: (id, file, caption) => ({ id, type: enyo_onboarding_v2_js_1.EnyoOnboardingV2BlockType.Image, file, caption }),
|
|
76
103
|
/**
|
|
77
104
|
* A hint/callout block.
|
|
78
105
|
* @param id - Stable block id, unique within the guide.
|