@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.
Files changed (50) hide show
  1. package/README.md +90 -1
  2. package/dist/cjs/energy-app-package-definition.d.cts +72 -0
  3. package/dist/cjs/energy-app.cjs +14 -0
  4. package/dist/cjs/energy-app.d.cts +13 -0
  5. package/dist/cjs/enyo-energy-app-sdk.d.cts +3 -0
  6. package/dist/cjs/implementations/files/define-public-file.cjs +58 -0
  7. package/dist/cjs/implementations/files/define-public-file.d.cts +51 -0
  8. package/dist/cjs/implementations/files/public-file-validators.cjs +161 -0
  9. package/dist/cjs/implementations/files/public-file-validators.d.cts +77 -0
  10. package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.cjs +28 -1
  11. package/dist/cjs/implementations/onboarding-v2/define-onboarding-guide-v2.d.cts +28 -1
  12. package/dist/cjs/implementations/onboarding-v2/onboarding-v2-provider-validators.cjs +167 -0
  13. package/dist/cjs/implementations/onboarding-v2/onboarding-v2-provider-validators.d.cts +86 -0
  14. package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.cjs +77 -3
  15. package/dist/cjs/implementations/onboarding-v2/onboarding-v2-validators.d.cts +25 -2
  16. package/dist/cjs/index.cjs +5 -0
  17. package/dist/cjs/index.d.cts +5 -0
  18. package/dist/cjs/packages/energy-app-onboarding-v2.cjs +2 -0
  19. package/dist/cjs/packages/energy-app-onboarding-v2.d.cts +128 -0
  20. package/dist/cjs/packages/energy-app-onboarding.d.cts +13 -6
  21. package/dist/cjs/types/enyo-onboarding-v2-provider.cjs +51 -0
  22. package/dist/cjs/types/enyo-onboarding-v2-provider.d.cts +105 -0
  23. package/dist/cjs/types/enyo-onboarding-v2.d.cts +27 -3
  24. package/dist/cjs/version.cjs +1 -1
  25. package/dist/cjs/version.d.cts +1 -1
  26. package/dist/energy-app-package-definition.d.ts +72 -0
  27. package/dist/energy-app.d.ts +13 -0
  28. package/dist/energy-app.js +14 -0
  29. package/dist/enyo-energy-app-sdk.d.ts +3 -0
  30. package/dist/implementations/files/define-public-file.d.ts +51 -0
  31. package/dist/implementations/files/define-public-file.js +54 -0
  32. package/dist/implementations/files/public-file-validators.d.ts +77 -0
  33. package/dist/implementations/files/public-file-validators.js +154 -0
  34. package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.d.ts +28 -1
  35. package/dist/implementations/onboarding-v2/define-onboarding-guide-v2.js +28 -1
  36. package/dist/implementations/onboarding-v2/onboarding-v2-provider-validators.d.ts +86 -0
  37. package/dist/implementations/onboarding-v2/onboarding-v2-provider-validators.js +161 -0
  38. package/dist/implementations/onboarding-v2/onboarding-v2-validators.d.ts +25 -2
  39. package/dist/implementations/onboarding-v2/onboarding-v2-validators.js +77 -3
  40. package/dist/index.d.ts +5 -0
  41. package/dist/index.js +5 -0
  42. package/dist/packages/energy-app-onboarding-v2.d.ts +128 -0
  43. package/dist/packages/energy-app-onboarding-v2.js +1 -0
  44. package/dist/packages/energy-app-onboarding.d.ts +13 -6
  45. package/dist/types/enyo-onboarding-v2-provider.d.ts +105 -0
  46. package/dist/types/enyo-onboarding-v2-provider.js +48 -0
  47. package/dist/types/enyo-onboarding-v2.d.ts +27 -3
  48. package/dist/version.d.ts +1 -1
  49. package/dist/version.js +1 -1
  50. 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
- | Drive a multi-step onboarding flow | [`useOnboarding()`](#useonboarding-energyapponboarding) |
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.
@@ -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.