@helyx/cli 0.4.41 → 0.5.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 26b4c3e: Add versioned hidden module dependencies, automatic installation lifecycle,
8
+ delegated customer health, and transitive self-host build composition for the
9
+ Nexus foundation while preserving legacy manifest and catalogue behavior. Add
10
+ closed, owner-aware composition for the shared `/nexus` command namespace and
11
+ bounded Core reads for terminology, member history and confirmed member value
12
+ changes. Add resumable, idempotent role and server bulk-award jobs.
13
+ Add bounded Nexus privacy capture, a durable hashed erasure fence and atomic
14
+ member detachment that journals any remaining balance as a privacy burn before
15
+ removing account and bulk-recipient identifiers.
16
+ Generalise the durable Achievement contribution bus as member contributions
17
+ while retaining the existing service names as compatibility aliases.
18
+ Add the private Nexus Rewards Testing package, complete initial record schema
19
+ and an atomic timezone-aware Daily Claim path backed by the shared ledger.
20
+ Add reviewed, default-off Nexus eligibility metadata to existing contribution
21
+ catalogues and atomically apply configured fixed rewards with source, role,
22
+ rate-cap, replay, balance-cap and privacy safeguards.
23
+ Add definition-owned Achievement reward amounts and live repeatable-award
24
+ high-water processing so retries credit only newly committed occurrences.
25
+ Add the private Nexus Economy Testing package and its complete initial Exchange,
26
+ item, redemption, inventory, role-grant and durable fulfilment record schema.
27
+ Add a typed atomic Economy redemption path with exact replay, finite stock,
28
+ member limits, role eligibility, collectible inventory and durable fulfilment.
29
+ Add coalesced leased role fulfilment with reviewed Discord safety, observed
30
+ delivery reconciliation, bounded retries and Core-linked automatic refunds.
31
+
32
+ ## 0.4.42
33
+
34
+ ### Patch Changes
35
+
36
+ - Add fail-open production status heartbeat configuration and reporting support.
37
+
3
38
  ## 0.4.41
4
39
 
5
40
  ### Patch Changes
@@ -39,7 +39,88 @@ export declare const selfHostModuleSchema: z.ZodObject<{
39
39
  premium: "premium";
40
40
  }>>;
41
41
  }, z.core.$strict>;
42
- export declare const selfHostCatalogueSchema: z.ZodObject<{
42
+ export declare const selfHostModuleV2Schema: z.ZodDiscriminatedUnion<[z.ZodObject<{
43
+ id: z.ZodString;
44
+ package: z.ZodString;
45
+ version: z.ZodNullable<z.ZodString>;
46
+ name: z.ZodString;
47
+ description: z.ZodString;
48
+ category: z.ZodEnum<{
49
+ Community: "Community";
50
+ Safety: "Safety";
51
+ "Support & Staff": "Support & Staff";
52
+ "Auto Tools": "Auto Tools";
53
+ Automation: "Automation";
54
+ Engagement: "Engagement";
55
+ Gaming: "Gaming";
56
+ Insights: "Insights";
57
+ Integrations: "Integrations";
58
+ }>;
59
+ availability: z.ZodEnum<{
60
+ planned: "planned";
61
+ unpublished: "unpublished";
62
+ published: "published";
63
+ }>;
64
+ releaseStatus: z.ZodEnum<{
65
+ planned: "planned";
66
+ testing: "testing";
67
+ beta: "beta";
68
+ live: "live";
69
+ }>;
70
+ tier: z.ZodOptional<z.ZodEnum<{
71
+ free: "free";
72
+ premium: "premium";
73
+ }>>;
74
+ dependencies: z.ZodArray<z.ZodObject<{
75
+ id: z.ZodString;
76
+ version: z.ZodString;
77
+ }, z.core.$strict>>;
78
+ moduleKind: z.ZodLiteral<"customer_feature">;
79
+ customerSelectable: z.ZodLiteral<true>;
80
+ countsAsInstalledModule: z.ZodLiteral<true>;
81
+ healthExposure: z.ZodLiteral<"direct">;
82
+ }, z.core.$strict>, z.ZodObject<{
83
+ id: z.ZodString;
84
+ package: z.ZodString;
85
+ version: z.ZodNullable<z.ZodString>;
86
+ name: z.ZodString;
87
+ description: z.ZodString;
88
+ category: z.ZodEnum<{
89
+ Community: "Community";
90
+ Safety: "Safety";
91
+ "Support & Staff": "Support & Staff";
92
+ "Auto Tools": "Auto Tools";
93
+ Automation: "Automation";
94
+ Engagement: "Engagement";
95
+ Gaming: "Gaming";
96
+ Insights: "Insights";
97
+ Integrations: "Integrations";
98
+ }>;
99
+ availability: z.ZodEnum<{
100
+ planned: "planned";
101
+ unpublished: "unpublished";
102
+ published: "published";
103
+ }>;
104
+ releaseStatus: z.ZodEnum<{
105
+ planned: "planned";
106
+ testing: "testing";
107
+ beta: "beta";
108
+ live: "live";
109
+ }>;
110
+ tier: z.ZodOptional<z.ZodEnum<{
111
+ free: "free";
112
+ premium: "premium";
113
+ }>>;
114
+ dependencies: z.ZodArray<z.ZodObject<{
115
+ id: z.ZodString;
116
+ version: z.ZodString;
117
+ }, z.core.$strict>>;
118
+ moduleKind: z.ZodLiteral<"internal_dependency">;
119
+ customerSelectable: z.ZodLiteral<false>;
120
+ countsAsInstalledModule: z.ZodLiteral<false>;
121
+ healthExposure: z.ZodLiteral<"delegated">;
122
+ }, z.core.$strict>], "moduleKind">;
123
+ export declare const selfHostCatalogueSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
43
124
  schemaVersion: z.ZodLiteral<1>;
44
125
  catalogueVersion: z.ZodNumber;
45
126
  core: z.ZodObject<{
@@ -92,9 +173,112 @@ export declare const selfHostCatalogueSchema: z.ZodObject<{
92
173
  premium: "premium";
93
174
  }>>;
94
175
  }, z.core.$strict>>;
95
- }, z.core.$strict>;
176
+ }, z.core.$strict>, z.ZodObject<{
177
+ schemaVersion: z.ZodLiteral<2>;
178
+ catalogueVersion: z.ZodNumber;
179
+ core: z.ZodObject<{
180
+ package: z.ZodString;
181
+ version: z.ZodString;
182
+ availability: z.ZodEnum<{
183
+ unpublished: "unpublished";
184
+ published: "published";
185
+ }>;
186
+ }, z.core.$strict>;
187
+ generator: z.ZodObject<{
188
+ package: z.ZodString;
189
+ version: z.ZodString;
190
+ availability: z.ZodEnum<{
191
+ unpublished: "unpublished";
192
+ published: "published";
193
+ }>;
194
+ command: z.ZodString;
195
+ }, z.core.$strict>;
196
+ modules: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
197
+ id: z.ZodString;
198
+ package: z.ZodString;
199
+ version: z.ZodNullable<z.ZodString>;
200
+ name: z.ZodString;
201
+ description: z.ZodString;
202
+ category: z.ZodEnum<{
203
+ Community: "Community";
204
+ Safety: "Safety";
205
+ "Support & Staff": "Support & Staff";
206
+ "Auto Tools": "Auto Tools";
207
+ Automation: "Automation";
208
+ Engagement: "Engagement";
209
+ Gaming: "Gaming";
210
+ Insights: "Insights";
211
+ Integrations: "Integrations";
212
+ }>;
213
+ availability: z.ZodEnum<{
214
+ planned: "planned";
215
+ unpublished: "unpublished";
216
+ published: "published";
217
+ }>;
218
+ releaseStatus: z.ZodEnum<{
219
+ planned: "planned";
220
+ testing: "testing";
221
+ beta: "beta";
222
+ live: "live";
223
+ }>;
224
+ tier: z.ZodOptional<z.ZodEnum<{
225
+ free: "free";
226
+ premium: "premium";
227
+ }>>;
228
+ dependencies: z.ZodArray<z.ZodObject<{
229
+ id: z.ZodString;
230
+ version: z.ZodString;
231
+ }, z.core.$strict>>;
232
+ moduleKind: z.ZodLiteral<"customer_feature">;
233
+ customerSelectable: z.ZodLiteral<true>;
234
+ countsAsInstalledModule: z.ZodLiteral<true>;
235
+ healthExposure: z.ZodLiteral<"direct">;
236
+ }, z.core.$strict>, z.ZodObject<{
237
+ id: z.ZodString;
238
+ package: z.ZodString;
239
+ version: z.ZodNullable<z.ZodString>;
240
+ name: z.ZodString;
241
+ description: z.ZodString;
242
+ category: z.ZodEnum<{
243
+ Community: "Community";
244
+ Safety: "Safety";
245
+ "Support & Staff": "Support & Staff";
246
+ "Auto Tools": "Auto Tools";
247
+ Automation: "Automation";
248
+ Engagement: "Engagement";
249
+ Gaming: "Gaming";
250
+ Insights: "Insights";
251
+ Integrations: "Integrations";
252
+ }>;
253
+ availability: z.ZodEnum<{
254
+ planned: "planned";
255
+ unpublished: "unpublished";
256
+ published: "published";
257
+ }>;
258
+ releaseStatus: z.ZodEnum<{
259
+ planned: "planned";
260
+ testing: "testing";
261
+ beta: "beta";
262
+ live: "live";
263
+ }>;
264
+ tier: z.ZodOptional<z.ZodEnum<{
265
+ free: "free";
266
+ premium: "premium";
267
+ }>>;
268
+ dependencies: z.ZodArray<z.ZodObject<{
269
+ id: z.ZodString;
270
+ version: z.ZodString;
271
+ }, z.core.$strict>>;
272
+ moduleKind: z.ZodLiteral<"internal_dependency">;
273
+ customerSelectable: z.ZodLiteral<false>;
274
+ countsAsInstalledModule: z.ZodLiteral<false>;
275
+ healthExposure: z.ZodLiteral<"delegated">;
276
+ }, z.core.$strict>], "moduleKind">>;
277
+ }, z.core.$strict>], "schemaVersion">;
96
278
  export type SelfHostModule = z.infer<typeof selfHostModuleSchema>;
279
+ export type SelfHostModuleV2 = z.infer<typeof selfHostModuleV2Schema>;
97
280
  export type SelfHostCatalogue = z.infer<typeof selfHostCatalogueSchema>;
281
+ export declare function isSelfHostModuleCustomerSelectable(module: SelfHostModule | SelfHostModuleV2): boolean;
98
282
  export declare function parseSelfHostCatalogue(input: unknown): SelfHostCatalogue;
99
283
  export declare function loadSelfHostCatalogue(path: string | URL, reader?: (path: string | URL, encoding: "utf8") => Promise<string>): Promise<SelfHostCatalogue>;
100
284
  export declare function bundledSelfHostCatalogueUrl(): URL;
@@ -24,6 +24,14 @@ const modulePackage = z
24
24
  const moduleId = z
25
25
  .string()
26
26
  .regex(/^helyx\.[a-z0-9][a-z0-9-]*$/u, "must be a Helyx module ID");
27
+ const moduleDependency = z
28
+ .object({
29
+ id: moduleId,
30
+ version: z
31
+ .string()
32
+ .refine((value) => semver.validRange(value) !== null, "must be semver"),
33
+ })
34
+ .strict();
27
35
  const availability = z.enum(["planned", "unpublished", "published"]);
28
36
  export const selfHostReleaseStatusSchema = z.enum([
29
37
  "planned",
@@ -92,7 +100,25 @@ export const selfHostModuleSchema = z
92
100
  });
93
101
  }
94
102
  });
95
- export const selfHostCatalogueSchema = z
103
+ const selfHostCustomerModuleV2Schema = selfHostModuleSchema.safeExtend({
104
+ dependencies: z.array(moduleDependency),
105
+ moduleKind: z.literal("customer_feature"),
106
+ customerSelectable: z.literal(true),
107
+ countsAsInstalledModule: z.literal(true),
108
+ healthExposure: z.literal("direct"),
109
+ });
110
+ const selfHostInternalModuleV2Schema = selfHostModuleSchema.safeExtend({
111
+ dependencies: z.array(moduleDependency),
112
+ moduleKind: z.literal("internal_dependency"),
113
+ customerSelectable: z.literal(false),
114
+ countsAsInstalledModule: z.literal(false),
115
+ healthExposure: z.literal("delegated"),
116
+ });
117
+ export const selfHostModuleV2Schema = z.discriminatedUnion("moduleKind", [
118
+ selfHostCustomerModuleV2Schema,
119
+ selfHostInternalModuleV2Schema,
120
+ ]);
121
+ const selfHostCatalogueV1Schema = z
96
122
  .object({
97
123
  schemaVersion: z.literal(1),
98
124
  catalogueVersion: z.number().int().positive(),
@@ -100,7 +126,21 @@ export const selfHostCatalogueSchema = z
100
126
  generator: generatorSchema,
101
127
  modules: z.array(selfHostModuleSchema),
102
128
  })
103
- .strict()
129
+ .strict();
130
+ const selfHostCatalogueV2Schema = z
131
+ .object({
132
+ schemaVersion: z.literal(2),
133
+ catalogueVersion: z.number().int().positive(),
134
+ core: componentSchema,
135
+ generator: generatorSchema,
136
+ modules: z.array(selfHostModuleV2Schema),
137
+ })
138
+ .strict();
139
+ export const selfHostCatalogueSchema = z
140
+ .discriminatedUnion("schemaVersion", [
141
+ selfHostCatalogueV1Schema,
142
+ selfHostCatalogueV2Schema,
143
+ ])
104
144
  .superRefine((catalogue, context) => {
105
145
  const ids = new Set();
106
146
  const packages = new Set([
@@ -125,7 +165,32 @@ export const selfHostCatalogueSchema = z
125
165
  ids.add(module.id);
126
166
  packages.add(module.package);
127
167
  }
168
+ if (catalogue.schemaVersion === 2) {
169
+ for (const [index, module] of catalogue.modules.entries()) {
170
+ const dependencyIds = new Set();
171
+ for (const dependency of module.dependencies) {
172
+ if (dependencyIds.has(dependency.id)) {
173
+ context.addIssue({
174
+ code: "custom",
175
+ path: ["modules", index, "dependencies"],
176
+ message: `duplicate dependency: ${dependency.id}`,
177
+ });
178
+ }
179
+ if (dependency.id === module.id) {
180
+ context.addIssue({
181
+ code: "custom",
182
+ path: ["modules", index, "dependencies"],
183
+ message: "a module cannot depend on itself",
184
+ });
185
+ }
186
+ dependencyIds.add(dependency.id);
187
+ }
188
+ }
189
+ }
128
190
  });
191
+ export function isSelfHostModuleCustomerSelectable(module) {
192
+ return !("customerSelectable" in module) || module.customerSelectable;
193
+ }
129
194
  export function parseSelfHostCatalogue(input) {
130
195
  return selfHostCatalogueSchema.parse(input);
131
196
  }
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
- import type { SelfHostCatalogue } from "./catalogue.js";
3
- export declare const selfHostBuildManifestSchema: z.ZodObject<{
2
+ import { type SelfHostCatalogue } from "./catalogue.js";
3
+ export declare const selfHostBuildManifestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
4
4
  schemaVersion: z.ZodLiteral<1>;
5
5
  catalogueVersion: z.ZodNumber;
6
6
  core: z.ZodObject<{
@@ -16,7 +16,31 @@ export declare const selfHostBuildManifestSchema: z.ZodObject<{
16
16
  package: z.ZodString;
17
17
  version: z.ZodString;
18
18
  }, z.core.$strict>>;
19
- }, z.core.$strict>;
19
+ }, z.core.$strict>, z.ZodObject<{
20
+ schemaVersion: z.ZodLiteral<2>;
21
+ catalogueVersion: z.ZodNumber;
22
+ core: z.ZodObject<{
23
+ package: z.ZodString;
24
+ version: z.ZodString;
25
+ }, z.core.$strict>;
26
+ generator: z.ZodObject<{
27
+ package: z.ZodString;
28
+ version: z.ZodString;
29
+ }, z.core.$strict>;
30
+ modules: z.ZodArray<z.ZodObject<{
31
+ id: z.ZodString;
32
+ package: z.ZodString;
33
+ version: z.ZodString;
34
+ moduleKind: z.ZodEnum<{
35
+ customer_feature: "customer_feature";
36
+ internal_dependency: "internal_dependency";
37
+ }>;
38
+ dependencies: z.ZodArray<z.ZodObject<{
39
+ id: z.ZodString;
40
+ version: z.ZodString;
41
+ }, z.core.$strict>>;
42
+ }, z.core.$strict>>;
43
+ }, z.core.$strict>], "schemaVersion">;
20
44
  export type SelfHostBuildManifest = z.infer<typeof selfHostBuildManifestSchema>;
21
45
  export declare function createSelfHostBuildManifest(catalogue: SelfHostCatalogue, selectedModuleIds: readonly string[]): SelfHostBuildManifest;
22
46
  export declare function parseSelfHostBuildManifest(input: unknown, catalogue: SelfHostCatalogue): SelfHostBuildManifest;
@@ -1,5 +1,7 @@
1
1
  import semver from "semver";
2
+ import { isDeepStrictEqual } from "node:util";
2
3
  import { z } from "zod";
4
+ import { isSelfHostModuleCustomerSelectable, } from "./catalogue.js";
3
5
  const exactVersion = z
4
6
  .string()
5
7
  .refine((value) => semver.valid(value) === value, "must be exact semver");
@@ -18,7 +20,7 @@ const buildModuleSchema = z
18
20
  version: exactVersion,
19
21
  })
20
22
  .strict();
21
- export const selfHostBuildManifestSchema = z
23
+ const selfHostBuildManifestV1Schema = z
22
24
  .object({
23
25
  schemaVersion: z.literal(1),
24
26
  catalogueVersion: z.number().int().positive(),
@@ -26,7 +28,34 @@ export const selfHostBuildManifestSchema = z
26
28
  generator: buildComponentSchema,
27
29
  modules: z.array(buildModuleSchema),
28
30
  })
29
- .strict()
31
+ .strict();
32
+ const buildModuleV2Schema = buildModuleSchema
33
+ .extend({
34
+ moduleKind: z.enum(["customer_feature", "internal_dependency"]),
35
+ dependencies: z.array(z
36
+ .object({
37
+ id: moduleId,
38
+ version: z
39
+ .string()
40
+ .refine((value) => semver.validRange(value) !== null, "must be semver"),
41
+ })
42
+ .strict()),
43
+ })
44
+ .strict();
45
+ const selfHostBuildManifestV2Schema = z
46
+ .object({
47
+ schemaVersion: z.literal(2),
48
+ catalogueVersion: z.number().int().positive(),
49
+ core: buildComponentSchema,
50
+ generator: buildComponentSchema,
51
+ modules: z.array(buildModuleV2Schema),
52
+ })
53
+ .strict();
54
+ export const selfHostBuildManifestSchema = z
55
+ .discriminatedUnion("schemaVersion", [
56
+ selfHostBuildManifestV1Schema,
57
+ selfHostBuildManifestV2Schema,
58
+ ])
30
59
  .superRefine((manifest, context) => {
31
60
  const ids = new Set();
32
61
  const packages = new Set();
@@ -55,6 +84,9 @@ export function createSelfHostBuildManifest(catalogue, selectedModuleIds) {
55
84
  if (new Set(selectedModuleIds).size !== selectedModuleIds.length) {
56
85
  throw new Error("Self-host selection contains duplicate module IDs");
57
86
  }
87
+ if (catalogue.schemaVersion === 2) {
88
+ return createSelfHostBuildManifestV2(catalogue, selectedModuleIds);
89
+ }
58
90
  const byId = new Map(catalogue.modules.map((module) => [module.id, module]));
59
91
  const modules = [...selectedModuleIds]
60
92
  .sort((left, right) => left.localeCompare(right, "en"))
@@ -87,8 +119,76 @@ export function createSelfHostBuildManifest(catalogue, selectedModuleIds) {
87
119
  modules,
88
120
  };
89
121
  }
122
+ function createSelfHostBuildManifestV2(catalogue, selectedModuleIds) {
123
+ const byId = new Map(catalogue.modules.map((module) => [module.id, module]));
124
+ const explicitlySelected = new Set(selectedModuleIds);
125
+ const modules = [];
126
+ const visiting = new Set();
127
+ const included = new Set();
128
+ for (const id of selectedModuleIds) {
129
+ const module = byId.get(id);
130
+ if (!module)
131
+ throw new Error(`Unknown self-host module: ${id}`);
132
+ if (!isSelfHostModuleCustomerSelectable(module))
133
+ throw new Error(`Self-host module cannot be selected directly: ${id}`);
134
+ }
135
+ const include = (id) => {
136
+ if (included.has(id))
137
+ return;
138
+ if (visiting.has(id))
139
+ throw new Error(`Self-host module dependency cycle detected at ${id}`);
140
+ const module = byId.get(id);
141
+ if (!module)
142
+ throw new Error(`Unknown self-host module: ${id}`);
143
+ assertLiveModule(module, id);
144
+ visiting.add(id);
145
+ for (const dependency of module.dependencies) {
146
+ const required = byId.get(dependency.id);
147
+ if (!required)
148
+ throw new Error(`Self-host module ${id} is missing dependency ${dependency.id}`);
149
+ if (required.version === null ||
150
+ !semver.satisfies(required.version, dependency.version)) {
151
+ throw new Error(`Self-host module ${id} requires ${dependency.id} ${dependency.version}`);
152
+ }
153
+ if (isSelfHostModuleCustomerSelectable(required) &&
154
+ !explicitlySelected.has(required.id)) {
155
+ throw new Error(`Select required module first: ${required.name}`);
156
+ }
157
+ include(required.id);
158
+ }
159
+ visiting.delete(id);
160
+ included.add(id);
161
+ modules.push({
162
+ id: module.id,
163
+ package: module.package,
164
+ version: module.version,
165
+ moduleKind: module.moduleKind,
166
+ dependencies: module.dependencies.map((dependency) => ({
167
+ ...dependency,
168
+ })),
169
+ });
170
+ };
171
+ for (const id of [...selectedModuleIds].sort((left, right) => left.localeCompare(right, "en")))
172
+ include(id);
173
+ return {
174
+ schemaVersion: 2,
175
+ catalogueVersion: catalogue.catalogueVersion,
176
+ core: {
177
+ package: catalogue.core.package,
178
+ version: catalogue.core.version,
179
+ },
180
+ generator: {
181
+ package: catalogue.generator.package,
182
+ version: catalogue.generator.version,
183
+ },
184
+ modules,
185
+ };
186
+ }
90
187
  export function parseSelfHostBuildManifest(input, catalogue) {
91
188
  const manifest = selfHostBuildManifestSchema.parse(input);
189
+ if (manifest.schemaVersion !== catalogue.schemaVersion) {
190
+ throw new Error(`Build manifest schema does not match catalogue schema ${String(catalogue.schemaVersion)}`);
191
+ }
92
192
  if (manifest.catalogueVersion !== catalogue.catalogueVersion) {
93
193
  throw new Error(`Unsupported self-host catalogue version: ${manifest.catalogueVersion}`);
94
194
  }
@@ -96,6 +196,19 @@ export function parseSelfHostBuildManifest(input, catalogue) {
96
196
  assertPublishedComponent(catalogue.generator, "Helyx generator");
97
197
  assertComponentMatches(manifest.core, catalogue.core, "runtime");
98
198
  assertComponentMatches(manifest.generator, catalogue.generator, "generator");
199
+ if (manifest.schemaVersion === 2 && catalogue.schemaVersion === 2) {
200
+ const selectedModuleIds = manifest.modules
201
+ .filter(({ moduleKind }) => moduleKind === "customer_feature")
202
+ .map(({ id }) => id);
203
+ const expected = createSelfHostBuildManifest(catalogue, selectedModuleIds);
204
+ if (!isDeepStrictEqual(manifest, expected)) {
205
+ throw new Error("Build manifest modules do not match the trusted catalogue dependency graph");
206
+ }
207
+ return expected;
208
+ }
209
+ if (manifest.schemaVersion !== 1 || catalogue.schemaVersion !== 1) {
210
+ throw new Error("Build manifest and catalogue schemas are incompatible");
211
+ }
99
212
  const byId = new Map(catalogue.modules.map((module) => [module.id, module]));
100
213
  for (const selected of manifest.modules) {
101
214
  const module = byId.get(selected.id);
@@ -116,6 +229,13 @@ export function parseSelfHostBuildManifest(input, catalogue) {
116
229
  modules: [...manifest.modules].sort((left, right) => left.id.localeCompare(right.id, "en")),
117
230
  };
118
231
  }
232
+ function assertLiveModule(module, id) {
233
+ if (module.availability !== "published" ||
234
+ module.releaseStatus !== "live" ||
235
+ module.version === null) {
236
+ throw new Error(`Self-host module is not live: ${id}`);
237
+ }
238
+ }
119
239
  function assertPublishedComponent(component, label) {
120
240
  if (component.availability !== "published") {
121
241
  throw new Error(`${label} is not published`);
@@ -1,5 +1,5 @@
1
1
  import type { BotEnvironmentInput } from "@helyx/config";
2
- import type { SelfHostCatalogue } from "./catalogue.js";
2
+ import { type SelfHostCatalogue } from "./catalogue.js";
3
3
  import { type SelfHostBuildManifest } from "./manifest.js";
4
4
  export type SelfHostCommandRunner = (arguments_: readonly string[], workingDirectory: string, signal: AbortSignal) => Promise<void>;
5
5
  export interface GenerateSelfHostProjectOptions {
@@ -2,6 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import { spawn } from "node:child_process";
3
3
  import { mkdir, readFile, readdir, rm, stat, writeFile, } from "node:fs/promises";
4
4
  import { dirname, parse, resolve } from "node:path";
5
+ import { isSelfHostModuleCustomerSelectable, } from "./catalogue.js";
5
6
  import { parseSelfHostBuildManifest, } from "./manifest.js";
6
7
  const generatedFileNames = [
7
8
  ".env.example",
@@ -102,8 +103,11 @@ function renderReadme(template, manifest, catalogue) {
102
103
  ? manifest.modules
103
104
  .map((module) => {
104
105
  const definition = modulesById.get(module.id);
106
+ if (definition && !isSelfHostModuleCustomerSelectable(definition))
107
+ return null;
105
108
  return `- ${definition?.name ?? module.id} (\`${module.package}@${module.version}\`)`;
106
109
  })
110
+ .filter((line) => line !== null)
107
111
  .join("\n")
108
112
  : "- No feature modules selected. This is a core-only installation.";
109
113
  return normalizeCrLf(template.replace("{{SELECTED_MODULES}}", selected));
@@ -376,6 +380,27 @@ export const selfHostEnvironmentContract = {
376
380
  value: "365",
377
381
  optional: true,
378
382
  },
383
+ HELYX_STATUS_ENABLED: { value: "false", optional: false },
384
+ HELYX_STATUS_URL: {
385
+ value: "https://status.helyx.gg",
386
+ optional: true,
387
+ },
388
+ HELYX_STATUS_HEARTBEAT_SECRET: {
389
+ value: "replace-with-a-base64-encoded-32-byte-key",
390
+ optional: true,
391
+ },
392
+ HELYX_STATUS_HEARTBEAT_INTERVAL_SECONDS: {
393
+ value: "60",
394
+ optional: true,
395
+ },
396
+ CF_ACCESS_CLIENT_ID: {
397
+ value: "replace-with-access-client-id",
398
+ optional: true,
399
+ },
400
+ CF_ACCESS_CLIENT_SECRET: {
401
+ value: "replace-with-access-client-secret",
402
+ optional: true,
403
+ },
379
404
  LOG_LEVEL: { value: "info", optional: false },
380
405
  NODE_ENV: { value: "production", optional: false },
381
406
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helyx/cli",
3
- "version": "0.4.41",
3
+ "version": "0.5.0",
4
4
  "description": "Command-line tools for running and generating self-hosted Helyx deployments.",
5
5
  "keywords": [
6
6
  "helyx",