@suluk/cockpit 0.1.4 → 0.1.6
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 +1 -1
- package/src/cycle.ts +16 -1
- package/src/drift.ts +0 -0
- package/src/index.ts +4 -1
- package/test/cycle.test.ts +3 -3
- package/test/drift.test.ts +9 -0
- package/test/module-cycle.test.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@suluk/cockpit",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "The pure cockpit core (cycle model · builder model · codegen · deploy planning · validate/audit/preview) shared by the vscode extension and the /superadmin web admin panel. CANDIDATE tooling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/cycle.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type { OpenAPIv4Document, Request, SchemaOrRef, SecurityRequirement } fro
|
|
|
14
14
|
import { audit, coverage } from "@suluk/hono";
|
|
15
15
|
import { formSpec, tableSpec } from "@suluk/shadcn";
|
|
16
16
|
import { costAudit, costTable, formatMicroUsd } from "@suluk/cost";
|
|
17
|
+
import { readProviders } from "@suluk/builder";
|
|
17
18
|
|
|
18
19
|
export type LayerStatus = "ok" | "warn" | "error" | "info";
|
|
19
20
|
|
|
@@ -26,7 +27,7 @@ export interface CycleItem {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
export interface CycleLayer {
|
|
29
|
-
id: "data" | "contract" | "auth" | "document" | "cost" | "docs" | "state" | "ui" | "tests";
|
|
30
|
+
id: "data" | "contract" | "auth" | "document" | "cost" | "docs" | "state" | "ui" | "providers" | "tests";
|
|
30
31
|
title: string;
|
|
31
32
|
status: LayerStatus;
|
|
32
33
|
summary: string;
|
|
@@ -140,6 +141,14 @@ export function buildCycle(doc: OpenAPIv4Document, opts: { principal?: Principal
|
|
|
140
141
|
const undeclared = costFindings.filter((f) => f.code === "no-cost-model").length;
|
|
141
142
|
const costItems: CycleItem[] = declaredCosts.map((d) => ({ label: d.operation, ref: d.operation, detail: `${formatMicroUsd(d.estimateMicroUsd)} · ${d.sources.join(", ")}` }));
|
|
142
143
|
|
|
144
|
+
// ── providers: the swappable facet bindings recorded on the document (M3)
|
|
145
|
+
const providerBindings = readProviders(doc);
|
|
146
|
+
const providerItems: CycleItem[] = providerBindings.map((b) => ({
|
|
147
|
+
label: b.facet, ref: b.facet,
|
|
148
|
+
detail: `${b.title}${b.alternatives.length ? ` · ${b.alternatives.length} alternatives` : ""}`,
|
|
149
|
+
status: b.known ? undefined : "warn",
|
|
150
|
+
}));
|
|
151
|
+
|
|
143
152
|
// ── tests: doc-level contract checks
|
|
144
153
|
const checks = docChecks(doc);
|
|
145
154
|
const testsItems: CycleItem[] = checks.map((c) => ({ label: c.name, status: c.pass ? "ok" : "error", detail: c.pass ? "pass" : c.message }));
|
|
@@ -168,6 +177,12 @@ export function buildCycle(doc: OpenAPIv4Document, opts: { principal?: Principal
|
|
|
168
177
|
{ id: "docs", title: "Docs", status: "ok", summary: "Scalar · Swagger", items: docsItems },
|
|
169
178
|
{ id: "state", title: "State (Nano Stores)", status: "ok", summary: `${stateItems.length} stores`, items: stateItems },
|
|
170
179
|
{ id: "ui", title: "UI (shadcn)", status: uiItems.length ? "ok" : "info", summary: `${uiItems.length} forms/tables`, items: uiItems },
|
|
180
|
+
{
|
|
181
|
+
id: "providers", title: "Providers",
|
|
182
|
+
status: providerItems.length ? "ok" : "info",
|
|
183
|
+
summary: providerItems.length ? providerBindings.map((b) => `${b.facet}→${b.impl}`).join(" · ") : "none",
|
|
184
|
+
items: providerItems,
|
|
185
|
+
},
|
|
171
186
|
{
|
|
172
187
|
id: "tests", title: "Tests (contract checks)",
|
|
173
188
|
status: checks.every((c) => c.pass) ? "ok" : "error",
|
package/src/drift.ts
CHANGED
|
Binary file
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,7 @@ export { entityNames, generateForm, generateTable, generateStoresModule, exportV
|
|
|
11
11
|
export { deployPlan, deployMarkdown } from "./deploy";
|
|
12
12
|
export type { DeployPlan, DeployStep, DeployProvider } from "@suluk/deploy";
|
|
13
13
|
// drift (OBSERVE): compare a LOCAL contract against a DEPLOYED one — the "what's drifted in prod" view (C020).
|
|
14
|
-
export { diffContracts, canonical, type ContractDiff, type ChangedOp, type OpRef } from "./drift";
|
|
14
|
+
export { diffContracts, canonical, type ContractDiff, type ChangedOp, type OpRef, type ProviderDelta, type ProviderChange } from "./drift";
|
|
15
15
|
// cross-cut (M1): one contract refracted through every viewer — the scope-gated surface, the moat.
|
|
16
16
|
export { crossCut, documentScopes, defaultViewers, type Viewer, type ViewerView, type GatedOp, type CrossCut } from "./crosscut";
|
|
17
17
|
// cost formatting, re-exported so the extension shell can render a live /cost ledger without a direct @suluk/cost dep.
|
|
@@ -20,5 +20,8 @@ export { formatMicroUsd, summarize, type CostSummary } from "@suluk/cost";
|
|
|
20
20
|
export {
|
|
21
21
|
installModule, namespaceModule, previewInstall, gradeModule,
|
|
22
22
|
ECOMMERCE, CRM, BILLING, FIRST_PARTY_REGISTRY,
|
|
23
|
+
PROVIDER_CATALOG, providerFacets, readProviders, swapProvider,
|
|
24
|
+
parseRegistry, validateModule,
|
|
23
25
|
type SulukModule, type InstallResult, type ModuleEntry, type ModuleRegistry, type ModuleGrade, type InstallPreview,
|
|
26
|
+
type ProviderImpl, type ProviderBinding, type RegistrySource, type ParsedRegistry,
|
|
24
27
|
} from "@suluk/builder";
|
package/test/cycle.test.ts
CHANGED
|
@@ -14,8 +14,8 @@ const petstore = parseDocument(petstoreSrc);
|
|
|
14
14
|
describe("buildCycle — the cockpit spine (every layer from one hub)", () => {
|
|
15
15
|
const model = buildCycle(petstore);
|
|
16
16
|
|
|
17
|
-
test("models all
|
|
18
|
-
expect(model.layers.map((l) => l.id)).toEqual(["data", "contract", "auth", "document", "cost", "docs", "state", "ui", "tests"]);
|
|
17
|
+
test("models all ten layers (incl. cost + providers)", () => {
|
|
18
|
+
expect(model.layers.map((l) => l.id)).toEqual(["data", "contract", "auth", "document", "cost", "docs", "state", "ui", "providers", "tests"]);
|
|
19
19
|
});
|
|
20
20
|
test("the cost layer reflects declared costs (x-suluk-cost) — none on the bare petstore", () => {
|
|
21
21
|
expect(model.layers.find((l) => l.id === "cost")!.summary).toBe("no costs declared");
|
|
@@ -110,6 +110,6 @@ describe("codegen actions land real artifacts", () => {
|
|
|
110
110
|
|
|
111
111
|
describe("cycleSummary — flat status line", () => {
|
|
112
112
|
test("returns one entry per layer", () => {
|
|
113
|
-
expect(cycleSummary(buildCycle(petstore)).length).toBe(
|
|
113
|
+
expect(cycleSummary(buildCycle(petstore)).length).toBe(10);
|
|
114
114
|
});
|
|
115
115
|
});
|
package/test/drift.test.ts
CHANGED
|
@@ -139,6 +139,15 @@ paths: { "p": { requests: { g: { method: get, responses: { ok: { status: 200 } }
|
|
|
139
139
|
expect(c.changes.some((x) => x.includes("shape"))).toBe(true); // the residual is not suppressed
|
|
140
140
|
});
|
|
141
141
|
|
|
142
|
+
test("provider-slot drift (x-suluk-providers) is reported", () => {
|
|
143
|
+
const local = { openapi: "4.0.0-candidate", info: { title: "T", version: "1.0.0" }, paths: {}, "x-suluk-providers": { payments: "paddle", email: "resend" } } as unknown as Parameters<typeof diffContracts>[0];
|
|
144
|
+
const deployed = { openapi: "4.0.0-candidate", info: { title: "T", version: "1.0.0" }, paths: {}, "x-suluk-providers": { payments: "stripe" } } as unknown as Parameters<typeof diffContracts>[0];
|
|
145
|
+
const d = diffContracts(local, deployed);
|
|
146
|
+
expect(d.identical).toBe(false);
|
|
147
|
+
expect(d.providers.changed).toEqual([{ facet: "payments", from: "stripe", to: "paddle" }]);
|
|
148
|
+
expect(d.providers.added).toEqual([{ facet: "email", impl: "resend" }]);
|
|
149
|
+
expect(d.summary).toContain("providers");
|
|
150
|
+
});
|
|
142
151
|
test("canonical() is cycle-safe (does not overflow the stack)", () => {
|
|
143
152
|
const a: Record<string, unknown> = { x: 1 };
|
|
144
153
|
a.self = a;
|
|
@@ -43,4 +43,10 @@ describe("installModule → the cockpit cycle lights up", () => {
|
|
|
43
43
|
test("the document still validates against the v4 meta-schema after the merge", () => {
|
|
44
44
|
expect(after.valid).toBe(true);
|
|
45
45
|
});
|
|
46
|
+
test("the Providers layer lights up with the module's provider slot (payments→stripe)", () => {
|
|
47
|
+
const providers = layer(after, "providers");
|
|
48
|
+
expect(providers.summary).toContain("payments→stripe");
|
|
49
|
+
expect(providers.items.map((i) => i.label)).toContain("payments");
|
|
50
|
+
expect(layer(before, "providers").summary).toBe("none"); // the host had no provider slots
|
|
51
|
+
});
|
|
46
52
|
});
|