@openparachute/hub 0.5.13-rc.13 → 0.5.13-rc.14
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/package.json
CHANGED
|
@@ -35,7 +35,8 @@ describe("validateModuleManifest", () => {
|
|
|
35
35
|
|
|
36
36
|
test("rejects missing required fields", () => {
|
|
37
37
|
expect(() => validateModuleManifest({ ...VALID, name: undefined }, "x")).toThrow(/name/);
|
|
38
|
-
|
|
38
|
+
// `kind` is NOT validated as of hub#327 — see the "kind is no longer
|
|
39
|
+
// validated" suite below for the full behavior surface.
|
|
39
40
|
expect(() => validateModuleManifest({ ...VALID, port: -1 }, "x")).toThrow(/port/);
|
|
40
41
|
expect(() => validateModuleManifest({ ...VALID, port: 99999 }, "x")).toThrow(/port/);
|
|
41
42
|
expect(() => validateModuleManifest({ ...VALID, paths: "not-array" }, "x")).toThrow(/paths/);
|
|
@@ -44,6 +45,61 @@ describe("validateModuleManifest", () => {
|
|
|
44
45
|
);
|
|
45
46
|
});
|
|
46
47
|
|
|
48
|
+
// hub#327 (hub#301 Phase A fold): the validator no longer inspects `kind`.
|
|
49
|
+
// Any value — present, absent, valid string, typo, wrong type — is
|
|
50
|
+
// accepted. The single downstream read site
|
|
51
|
+
// (`commands/upgrade.ts: target.spec?.kind === "frontend"`) handles the
|
|
52
|
+
// absent / non-"frontend" case via falsy-fallthrough into the
|
|
53
|
+
// backend-proxy default.
|
|
54
|
+
describe("kind is no longer validated (hub#327)", () => {
|
|
55
|
+
test("missing kind is accepted and produces an undefined kind", () => {
|
|
56
|
+
const { kind: _ignored, ...withoutKind } = VALID;
|
|
57
|
+
const m = validateModuleManifest(withoutKind, "x");
|
|
58
|
+
expect(m.kind).toBeUndefined();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("explicit kind: 'frontend' passes through unchanged", () => {
|
|
62
|
+
const m = validateModuleManifest({ ...VALID, kind: "frontend" }, "x");
|
|
63
|
+
expect(m.kind).toBe("frontend");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("explicit kind: 'api' passes through unchanged", () => {
|
|
67
|
+
const m = validateModuleManifest({ ...VALID, kind: "api" }, "x");
|
|
68
|
+
expect(m.kind).toBe("api");
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("explicit kind: 'tool' passes through unchanged", () => {
|
|
72
|
+
const m = validateModuleManifest({ ...VALID, kind: "tool" }, "x");
|
|
73
|
+
expect(m.kind).toBe("tool");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("invalid kind values are also accepted (validator no longer inspects)", () => {
|
|
77
|
+
// Per Aaron's direction on #327: stop validating kind. Typos,
|
|
78
|
+
// novel strings, wrong types — none of them error. The validator
|
|
79
|
+
// simply doesn't look at the field. Downstream routing branches on
|
|
80
|
+
// `kind === "frontend"` and falls through gracefully for anything
|
|
81
|
+
// else (including these), so accepting them is safe.
|
|
82
|
+
const m1 = validateModuleManifest({ ...VALID, kind: "static" }, "x");
|
|
83
|
+
expect(m1.kind).toBeUndefined();
|
|
84
|
+
const m2 = validateModuleManifest({ ...VALID, kind: "backend" }, "x");
|
|
85
|
+
expect(m2.kind).toBeUndefined();
|
|
86
|
+
const m3 = validateModuleManifest({ ...VALID, kind: null }, "x");
|
|
87
|
+
expect(m3.kind).toBeUndefined();
|
|
88
|
+
const m4 = validateModuleManifest({ ...VALID, kind: 42 }, "x");
|
|
89
|
+
expect(m4.kind).toBeUndefined();
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("routing-relevant kind: 'frontend' still survives the validator (upgrade.ts branch intact)", () => {
|
|
93
|
+
// Defensive sanity check: the one downstream branch that reads kind
|
|
94
|
+
// (commands/upgrade.ts checks `target.spec?.kind === "frontend"` to
|
|
95
|
+
// decide whether to run `bun run build`) keeps working — when a
|
|
96
|
+
// module DOES declare `kind: "frontend"`, the value reaches that
|
|
97
|
+
// branch untouched.
|
|
98
|
+
const m = validateModuleManifest({ ...VALID, kind: "frontend" }, "x");
|
|
99
|
+
expect(m.kind === "frontend").toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
47
103
|
test("rejects invalid name shape", () => {
|
|
48
104
|
expect(() => validateModuleManifest({ ...VALID, name: "Demo" }, "x")).toThrow(/name/);
|
|
49
105
|
expect(() => validateModuleManifest({ ...VALID, name: "1demo" }, "x")).toThrow(/name/);
|
package/src/module-manifest.ts
CHANGED
|
@@ -76,8 +76,17 @@ export interface ModuleManifest {
|
|
|
76
76
|
readonly displayName?: string;
|
|
77
77
|
/** One-line subtitle rendered under displayName. */
|
|
78
78
|
readonly tagline?: string;
|
|
79
|
-
/**
|
|
80
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Historically drove card vs. iframe vs. launcher in the hub. As of
|
|
81
|
+
* hub#301 Phase A's fold (#327) the validator no longer inspects `kind` —
|
|
82
|
+
* any value, or no value at all, is accepted and passes through untouched.
|
|
83
|
+
* Routing branches downstream use `=== "frontend"` style checks which
|
|
84
|
+
* treat undefined/other values as the backend-proxy default (so the
|
|
85
|
+
* routing remains correct without validator enforcement). New modules
|
|
86
|
+
* may safely omit the field; existing values are preserved for the
|
|
87
|
+
* narrow `kind === "frontend"` branch in `commands/upgrade.ts`.
|
|
88
|
+
*/
|
|
89
|
+
readonly kind?: ModuleKind;
|
|
81
90
|
/** Default loopback port. CLI warns on conflict, doesn't block. */
|
|
82
91
|
readonly port: number;
|
|
83
92
|
/** URL paths the module serves under the hub origin. */
|
|
@@ -167,11 +176,19 @@ function asOptionalString(v: unknown, where: string, field: string): string | un
|
|
|
167
176
|
return v;
|
|
168
177
|
}
|
|
169
178
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
/**
|
|
180
|
+
* Pass-through `kind` reader (hub#301 Phase A fold — #327).
|
|
181
|
+
*
|
|
182
|
+
* The validator no longer inspects `kind`. Any value, or no value, is
|
|
183
|
+
* accepted. We narrow to the canonical `ModuleKind` only when the input is
|
|
184
|
+
* one of the three known strings — otherwise we drop the field entirely so
|
|
185
|
+
* downstream `kind === "frontend"` branches fall through to the
|
|
186
|
+
* backend-proxy default. Author intent (typo, novel value, omission) is no
|
|
187
|
+
* longer surfaced from this layer; it's not the validator's job anymore.
|
|
188
|
+
*/
|
|
189
|
+
function asKind(v: unknown): ModuleKind | undefined {
|
|
190
|
+
if (v === "api" || v === "frontend" || v === "tool") return v;
|
|
191
|
+
return undefined;
|
|
175
192
|
}
|
|
176
193
|
|
|
177
194
|
function asPort(v: unknown, where: string): number {
|
|
@@ -341,9 +358,20 @@ function asDependencies(v: unknown, where: string): Record<string, ModuleDepende
|
|
|
341
358
|
/**
|
|
342
359
|
* Strict validator. Throws `ModuleManifestError` with the source path so
|
|
343
360
|
* malformed third-party modules get a clear-enough error to fix. Required
|
|
344
|
-
* fields are name, manifestName,
|
|
361
|
+
* fields are name, manifestName, port, paths, health. `kind` is no longer
|
|
362
|
+
* inspected as of hub#301 Phase A's fold (#327) — any value (or none) is
|
|
363
|
+
* accepted and passes through untouched. See `asKind` for the narrowing
|
|
364
|
+
* behavior.
|
|
365
|
+
*
|
|
366
|
+
* The optional `logger` parameter is retained for forward-compatibility
|
|
367
|
+
* with future validator soft-warnings, even though the kind soft-warning
|
|
368
|
+
* it was originally added for has been removed.
|
|
345
369
|
*/
|
|
346
|
-
export function validateModuleManifest(
|
|
370
|
+
export function validateModuleManifest(
|
|
371
|
+
raw: unknown,
|
|
372
|
+
where: string,
|
|
373
|
+
_logger: Pick<Console, "warn"> = console,
|
|
374
|
+
): ModuleManifest {
|
|
347
375
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
348
376
|
throw new ModuleManifestError(`${where}: root must be an object`);
|
|
349
377
|
}
|
|
@@ -356,7 +384,7 @@ export function validateModuleManifest(raw: unknown, where: string): ModuleManif
|
|
|
356
384
|
);
|
|
357
385
|
}
|
|
358
386
|
const manifestName = asString(m.manifestName, where, "manifestName");
|
|
359
|
-
const kind = asKind(m.kind
|
|
387
|
+
const kind = asKind(m.kind);
|
|
360
388
|
const port = asPort(m.port, where);
|
|
361
389
|
const paths = asStringArray(m.paths, where, "paths");
|
|
362
390
|
const health = asHealthPath(m.health, where);
|
|
@@ -404,7 +432,8 @@ export function validateModuleManifest(raw: unknown, where: string): ModuleManif
|
|
|
404
432
|
stripPrefix = m.stripPrefix;
|
|
405
433
|
}
|
|
406
434
|
|
|
407
|
-
const out: ModuleManifest = { name, manifestName,
|
|
435
|
+
const out: ModuleManifest = { name, manifestName, port, paths, health };
|
|
436
|
+
if (kind !== undefined) (out as { kind?: ModuleKind }).kind = kind;
|
|
408
437
|
if (displayName !== undefined) (out as { displayName?: string }).displayName = displayName;
|
|
409
438
|
if (tagline !== undefined) (out as { tagline?: string }).tagline = tagline;
|
|
410
439
|
if (startCmd !== undefined) (out as { startCmd?: readonly string[] }).startCmd = startCmd;
|
|
@@ -470,8 +499,14 @@ function asPathOrUrl(v: unknown, where: string, field: string): string | undefin
|
|
|
470
499
|
* absent (caller decides whether that's an error — first-party modules fall
|
|
471
500
|
* back to the vendored manifest; third-party hard-errors). Throws
|
|
472
501
|
* `ModuleManifestError` on parse / validation failure.
|
|
502
|
+
*
|
|
503
|
+
* The optional `logger` parameter is retained for forward-compatibility
|
|
504
|
+
* with future validator soft-warnings. Defaults to `console`.
|
|
473
505
|
*/
|
|
474
|
-
export async function readModuleManifest(
|
|
506
|
+
export async function readModuleManifest(
|
|
507
|
+
packageDir: string,
|
|
508
|
+
logger: Pick<Console, "warn"> = console,
|
|
509
|
+
): Promise<ModuleManifest | null> {
|
|
475
510
|
const path = join(packageDir, ".parachute", "module.json");
|
|
476
511
|
let buf: string;
|
|
477
512
|
try {
|
|
@@ -488,5 +523,5 @@ export async function readModuleManifest(packageDir: string): Promise<ModuleMani
|
|
|
488
523
|
`${path}: failed to parse JSON: ${err instanceof Error ? err.message : String(err)}`,
|
|
489
524
|
);
|
|
490
525
|
}
|
|
491
|
-
return validateModuleManifest(parsed, path);
|
|
526
|
+
return validateModuleManifest(parsed, path, logger);
|
|
492
527
|
}
|
package/src/service-spec.ts
CHANGED
|
@@ -174,7 +174,14 @@ export interface ServiceSpec {
|
|
|
174
174
|
* First service boot overwrites the seed with its own authoritative version.
|
|
175
175
|
*/
|
|
176
176
|
readonly seedEntry?: () => ServiceEntry;
|
|
177
|
-
|
|
177
|
+
/**
|
|
178
|
+
* Optional as of hub#327 (Phase A's fold): the validator no longer
|
|
179
|
+
* inspects `kind`, so synthesized + third-party-manifest specs may
|
|
180
|
+
* carry `undefined` here. The single read site
|
|
181
|
+
* (`commands/upgrade.ts: target.spec?.kind === "frontend"`) handles
|
|
182
|
+
* the absent case via the `=== "frontend"` falsy-fallthrough.
|
|
183
|
+
*/
|
|
184
|
+
readonly kind?: ServiceKind;
|
|
178
185
|
readonly hasAuth?: boolean;
|
|
179
186
|
readonly urlForEntry?: (entry: ServiceEntry) => string | undefined;
|
|
180
187
|
readonly postInstallFooter?: () => readonly string[];
|