@wairon/cli 5.0.2-dev.1 → 5.0.2-dev.11
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/dist/cli/index.js +816 -80
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +548 -47
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +1 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -65,7 +65,7 @@ var init_defaults = __esm({
|
|
|
65
65
|
copilot: ".github/prompts",
|
|
66
66
|
codex: ".codex/agents"
|
|
67
67
|
};
|
|
68
|
-
WAIRON_VERSION = "5.0.2-dev.
|
|
68
|
+
WAIRON_VERSION = "5.0.2-dev.11";
|
|
69
69
|
GITHUB_REPO = "SYW-Apps/Waffle-AIron";
|
|
70
70
|
ARCHITECT_AGENT_ID = "agent-architect";
|
|
71
71
|
ARCHITECT_TEMPLATE_ID = "architect";
|
|
@@ -703,7 +703,7 @@ var init_template = __esm({
|
|
|
703
703
|
});
|
|
704
704
|
|
|
705
705
|
// src/models/specs.ts
|
|
706
|
-
var import_zod6, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, PatternRefSchema, EventBindingSchema, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, MethodSignatureSchema, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, GroupSpecSchema;
|
|
706
|
+
var import_zod6, SpecIdSchema, SpecStatusSchema, BoundaryItemSchema, RequirementItemSchema, DatabaseSpecSchema, DiagramConfigSchema, SURFACE_AUDIENCES, SurfaceAudienceSchema, SystemPublicInterfaceSchema, SystemSpecSchema, PublicInterfaceTypeSchema, PublicInterfaceSchema, TrustedLinkSchema, LintAllowSchema, LintConfigSchema, ExtDataSchema, LifecycleEntrypointSchema, SubsystemSpecSchema, ComponentTypeSchema, PATTERN_TYPES, PortalTypeSchema, DispatchBindingSchema, DurabilitySchema, PatternRefSchema, EventBindingSchema, ExternalLinkTypeSchema, ExternalLinkSchema, PortalAuthSchemeSchema, PortalAuthSchema, ComponentSpecSchema, HttpMethodSchema, TransportSchema, EndpointSchema, SEMANTIC_GUARANTEES, GuaranteeSchema, MethodParamSchema, MethodSignatureSchema, InterfaceSpecSchema, NarrativeStepTypeSchema, LoopKindSchema, SwitchCaseSchema, CatchClauseSchema, ParallelBranchSchema, NarrativeStepSchema, NarrativeDetailSchema, ConformanceTierSchema, MethodImplementationSchema, ImplementationSpecSchema, TypeKindSchema, TypeFieldSchema, TypeMethodSchema, InvariantSchema, TypeSpecSchema, SurfaceOriginSchema, SurfaceTypeDefSchema, SurfaceContractEntrySchema, SurfaceSnapshotSchema, NamedOpenApiSpecSchema, GroupSpecSchema;
|
|
707
707
|
var init_specs = __esm({
|
|
708
708
|
"src/models/specs.ts"() {
|
|
709
709
|
"use strict";
|
|
@@ -907,6 +907,28 @@ var init_specs = __esm({
|
|
|
907
907
|
event: import_zod6.z.string().optional(),
|
|
908
908
|
description: import_zod6.z.string().optional()
|
|
909
909
|
});
|
|
910
|
+
ExternalLinkTypeSchema = import_zod6.z.enum(["implementation", "informative"]);
|
|
911
|
+
ExternalLinkSchema = import_zod6.z.object({
|
|
912
|
+
url: import_zod6.z.string(),
|
|
913
|
+
/** Defaults to 'informative' so an untyped link never silently satisfies the source requirement. */
|
|
914
|
+
type: ExternalLinkTypeSchema.default("informative"),
|
|
915
|
+
label: import_zod6.z.string().optional()
|
|
916
|
+
});
|
|
917
|
+
PortalAuthSchemeSchema = import_zod6.z.enum(["none", "apiKey", "bearer", "basic", "oauth2", "openIdConnect", "custom"]);
|
|
918
|
+
PortalAuthSchema = import_zod6.z.object({
|
|
919
|
+
scheme: PortalAuthSchemeSchema,
|
|
920
|
+
in: import_zod6.z.enum(["header", "query", "cookie"]).optional(),
|
|
921
|
+
name: import_zod6.z.string().optional(),
|
|
922
|
+
bearerFormat: import_zod6.z.string().optional(),
|
|
923
|
+
authorizationUrl: import_zod6.z.string().optional(),
|
|
924
|
+
tokenUrl: import_zod6.z.string().optional(),
|
|
925
|
+
refreshUrl: import_zod6.z.string().optional(),
|
|
926
|
+
scopes: import_zod6.z.array(import_zod6.z.object({ name: import_zod6.z.string(), description: import_zod6.z.string() })).optional(),
|
|
927
|
+
flow: import_zod6.z.enum(["authorizationCode", "clientCredentials", "implicit", "password"]).optional(),
|
|
928
|
+
openIdConnectUrl: import_zod6.z.string().optional(),
|
|
929
|
+
description: import_zod6.z.string().optional(),
|
|
930
|
+
example: import_zod6.z.string().optional()
|
|
931
|
+
});
|
|
910
932
|
ComponentSpecSchema = import_zod6.z.object({
|
|
911
933
|
id: SpecIdSchema,
|
|
912
934
|
name: import_zod6.z.string(),
|
|
@@ -920,6 +942,10 @@ var init_specs = __esm({
|
|
|
920
942
|
dependsOn: import_zod6.z.array(import_zod6.z.string()).default([]),
|
|
921
943
|
portalType: PortalTypeSchema.optional(),
|
|
922
944
|
basePath: import_zod6.z.string().optional(),
|
|
945
|
+
/** Portal-only: the API's authentication scheme (see PortalAuthSchema) — projected
|
|
946
|
+
* into the generated OpenAPI's securitySchemes/security. Portals with different auth
|
|
947
|
+
* must be separate components (one auth per portal ⇒ one OpenAPI spec per portal). */
|
|
948
|
+
auth: PortalAuthSchema.optional(),
|
|
923
949
|
/** Portal-only: capability → component.method dispatch table (see DispatchBindingSchema). */
|
|
924
950
|
dispatch: import_zod6.z.array(DispatchBindingSchema).optional(),
|
|
925
951
|
/** Store-only: whether held state survives restart (see DurabilitySchema). */
|
|
@@ -932,6 +958,11 @@ var init_specs = __esm({
|
|
|
932
958
|
patterns: import_zod6.z.array(PatternRefSchema).optional(),
|
|
933
959
|
/** Optional component variant — a declared, base-anchored specialization of this component's stereotype (resolved against the variant registry; UNKNOWN_VARIANT / VARIANT_BASE_MISMATCH). */
|
|
934
960
|
variant: import_zod6.z.string().optional(),
|
|
961
|
+
/** Opaque external references (see ExternalLinkSchema) — documented URLs wairon does
|
|
962
|
+
* not fetch or validate. An `implementation` link is the external source-of-record and
|
|
963
|
+
* satisfies the source requirement for a source-less implementation (suppresses
|
|
964
|
+
* MISSING_SOURCE_PATH); `informative` links are context only. */
|
|
965
|
+
externalLinks: import_zod6.z.array(ExternalLinkSchema).optional(),
|
|
935
966
|
/** Per-spec lint suppressions (see LintConfigSchema). */
|
|
936
967
|
lint: LintConfigSchema.optional(),
|
|
937
968
|
/** Opaque pack/tool extension data (see ExtDataSchema) — preserved verbatim. */
|
|
@@ -1064,6 +1095,18 @@ var init_specs = __esm({
|
|
|
1064
1095
|
// Required if type is 'call', references Method name on target interface
|
|
1065
1096
|
capability: import_zod6.z.string().optional(),
|
|
1066
1097
|
// Required if type is 'dispatch': the capability routed through the target Portal's dispatch table
|
|
1098
|
+
/**
|
|
1099
|
+
* call/dispatch only: the credential this step presents to an authed callee
|
|
1100
|
+
* Portal, and WHERE it is loaded from (`from`). Two forms: an OPAQUE source
|
|
1101
|
+
* (`env:API_KEY`, a config key, `vault:path`, a free note) — a design note
|
|
1102
|
+
* wairon never resolves; or a MODELED reference `component:<id>` pointing at
|
|
1103
|
+
* the Adapter/Store that provides the secret — validated to resolve, be an
|
|
1104
|
+
* Adapter/Store, and be wired to the presenter (a checked graph edge). The
|
|
1105
|
+
* actual secret is never stored here. Absence on a call into a Portal whose
|
|
1106
|
+
* `auth ≠ none` warns (PORTAL_AUTH_UNMET), so credential loading is never
|
|
1107
|
+
* overlooked.
|
|
1108
|
+
*/
|
|
1109
|
+
auth: import_zod6.z.object({ from: import_zod6.z.string(), note: import_zod6.z.string().optional() }).optional(),
|
|
1067
1110
|
assertsGuarantees: import_zod6.z.array(GuaranteeSchema).optional(),
|
|
1068
1111
|
/**
|
|
1069
1112
|
* Declared entity invariants this step upholds, as "<type-id>.<invariant-id>"
|
|
@@ -1280,7 +1323,12 @@ var init_specs = __esm({
|
|
|
1280
1323
|
dispatch: import_zod6.z.array(DispatchBindingSchema).optional(),
|
|
1281
1324
|
details: import_zod6.z.string().default(""),
|
|
1282
1325
|
version: import_zod6.z.string().optional(),
|
|
1283
|
-
stability: import_zod6.z.string().optional()
|
|
1326
|
+
stability: import_zod6.z.string().optional(),
|
|
1327
|
+
/** Projected copy of the backing Portal's auth (see PortalAuthSchema) — the codec
|
|
1328
|
+
* emits it as OpenAPI securitySchemes/security. */
|
|
1329
|
+
auth: PortalAuthSchema.optional(),
|
|
1330
|
+
/** The backing Portal's basePath — becomes the per-portal OpenAPI `servers` url. */
|
|
1331
|
+
basePath: import_zod6.z.string().optional()
|
|
1284
1332
|
});
|
|
1285
1333
|
SurfaceSnapshotSchema = import_zod6.z.object({
|
|
1286
1334
|
/** Producing project/system name — the snapshot's resolution identity. */
|
|
@@ -1295,6 +1343,11 @@ var init_specs = __esm({
|
|
|
1295
1343
|
/** Transitive type closure of every exported signature — self-contained. */
|
|
1296
1344
|
types: import_zod6.z.array(SurfaceTypeDefSchema).default([])
|
|
1297
1345
|
});
|
|
1346
|
+
NamedOpenApiSpecSchema = import_zod6.z.object({
|
|
1347
|
+
portalId: import_zod6.z.string(),
|
|
1348
|
+
name: import_zod6.z.string(),
|
|
1349
|
+
document: import_zod6.z.string()
|
|
1350
|
+
});
|
|
1298
1351
|
GroupSpecSchema = import_zod6.z.object({
|
|
1299
1352
|
kind: import_zod6.z.literal("group"),
|
|
1300
1353
|
id: SpecIdSchema,
|
|
@@ -2652,6 +2705,7 @@ function buildCanvasModel(issues = []) {
|
|
|
2652
2705
|
...ownerOf.has(comp.id) ? { owner: ownerOf.get(comp.id) } : {},
|
|
2653
2706
|
owns: comp.owns.filter((o) => componentIds.has(o)),
|
|
2654
2707
|
dependsOn: comp.dependsOn,
|
|
2708
|
+
...comp.externalLinks && comp.externalLinks.length ? { externalLinks: comp.externalLinks } : {},
|
|
2655
2709
|
interfaces: compInterfaces.map((i) => ({
|
|
2656
2710
|
id: i.id,
|
|
2657
2711
|
name: i.name,
|
|
@@ -3304,12 +3358,21 @@ var MODEL = __MODEL_JSON__;
|
|
|
3304
3358
|
var configuredLineStyle = ['bezier', 'straight', 'taxi'].indexOf(diagramConfig.lineStyle) >= 0 ? diagramConfig.lineStyle : 'bezier';
|
|
3305
3359
|
var defaultViewKind = diagramConfig.defaultView === 'types' ? 'types' : (diagramConfig.defaultView === 'databases' && showDatabaseTab ? 'databases' : 'system');
|
|
3306
3360
|
|
|
3361
|
+
// Stage J: a deep link (opts.initialRoute) seeds the initial view directly so a
|
|
3362
|
+
// refresh / shared URL renders the right scope with NO root-first flash. Parsed
|
|
3363
|
+
// by the same resolver as openRoute (hoisted below). No onViewChange fires for
|
|
3364
|
+
// this initial seed.
|
|
3365
|
+
var initialView = { kind: defaultViewKind, id: null };
|
|
3366
|
+
if (typeof opts !== 'undefined' && opts && typeof opts.initialRoute === 'string' && opts.initialRoute.length) {
|
|
3367
|
+
initialView = resolveRoute(opts.initialRoute);
|
|
3368
|
+
}
|
|
3369
|
+
|
|
3307
3370
|
var state = {
|
|
3308
|
-
view:
|
|
3309
|
-
internals: false,
|
|
3310
|
-
externals: true,
|
|
3311
|
-
dataCoupling: false,
|
|
3312
|
-
showIssues: false,
|
|
3371
|
+
view: initialView,
|
|
3372
|
+
internals: typeof saved.internals === 'boolean' ? saved.internals : false,
|
|
3373
|
+
externals: typeof saved.externals === 'boolean' ? saved.externals : true,
|
|
3374
|
+
dataCoupling: typeof saved.dataCoupling === 'boolean' ? saved.dataCoupling : false,
|
|
3375
|
+
showIssues: typeof saved.showIssues === 'boolean' ? saved.showIssues : false,
|
|
3313
3376
|
query: '',
|
|
3314
3377
|
selected: null,
|
|
3315
3378
|
selectedKind: null,
|
|
@@ -3324,6 +3387,9 @@ var MODEL = __MODEL_JSON__;
|
|
|
3324
3387
|
// Set by buildTypeElements when the ERD is degraded for performance (huge
|
|
3325
3388
|
// scopes); consumed by renderTypesNotice to explain the level-of-detail.
|
|
3326
3389
|
var typesNotice = '';
|
|
3390
|
+
// Stage J: true while openRoute is applying a URL-driven view change, so the
|
|
3391
|
+
// onViewChange callback is suppressed and we do not loop URL -> engine -> URL.
|
|
3392
|
+
var applyingRoute = false;
|
|
3327
3393
|
|
|
3328
3394
|
function viewKey() {
|
|
3329
3395
|
// 'types2' + detail level: table sizes differ per detail, and the prefix
|
|
@@ -3343,6 +3409,10 @@ var MODEL = __MODEL_JSON__;
|
|
|
3343
3409
|
lineStyle: state.lineStyle,
|
|
3344
3410
|
panelOpen: state.panelOpen,
|
|
3345
3411
|
panelWidth: state.panelWidth,
|
|
3412
|
+
internals: state.internals,
|
|
3413
|
+
externals: state.externals,
|
|
3414
|
+
dataCoupling: state.dataCoupling,
|
|
3415
|
+
showIssues: state.showIssues,
|
|
3346
3416
|
}));
|
|
3347
3417
|
} catch (e) { /* non-fatal */ }
|
|
3348
3418
|
}
|
|
@@ -4749,6 +4819,78 @@ var MODEL = __MODEL_JSON__;
|
|
|
4749
4819
|
state.typesRenderAll = false; // a fresh scope re-evaluates the LOD budget
|
|
4750
4820
|
rebuild(true);
|
|
4751
4821
|
renderPanel();
|
|
4822
|
+
notifyViewChange();
|
|
4823
|
+
}
|
|
4824
|
+
|
|
4825
|
+
// ---- Stage J: URL <-> view routing ----------------------------------------
|
|
4826
|
+
// The canvas navigation lives in the URL path (refresh-safe + shareable). A
|
|
4827
|
+
// route is the string AFTER /canvas/<project>: '' = system root; a '/'-joined
|
|
4828
|
+
// NAMESPACE (e.g. 'a/b') that resolves against the model to a subsystem or a
|
|
4829
|
+
// component; or the 'types'/'databases' view modes with an optional scope.
|
|
4830
|
+
// A segment is the '::' namespace with '/' as separator, each part encoded.
|
|
4831
|
+
function encSeg(s) { return encodeURIComponent(String(s)); }
|
|
4832
|
+
function routeOf(view) {
|
|
4833
|
+
if (!view) return '';
|
|
4834
|
+
var k = view.kind;
|
|
4835
|
+
if (k === 'system') return '';
|
|
4836
|
+
if (k === 'types' || k === 'databases') {
|
|
4837
|
+
return view.id ? k + '/' + view.id.split('::').map(encSeg).join('/') : k;
|
|
4838
|
+
}
|
|
4839
|
+
if (k === 'subsystem') {
|
|
4840
|
+
return view.id ? view.id.split('::').map(encSeg).join('/') : '';
|
|
4841
|
+
}
|
|
4842
|
+
if (k === 'component') {
|
|
4843
|
+
// A component's full namespace path IS its id: a chained subproject carries
|
|
4844
|
+
// the subsystem prefix in the id (a::b::comp), a flat project uses the bare
|
|
4845
|
+
// id (comp). Serializing comp.id split on '::' round-trips exactly via the
|
|
4846
|
+
// compById lookup in resolveRoute. Owner-pattern nesting is deliberately NOT
|
|
4847
|
+
// encoded (ownership is a separate axis; comp.id does not embed the owner).
|
|
4848
|
+
var c = compById[view.id];
|
|
4849
|
+
var full = c ? c.id : view.id;
|
|
4850
|
+
return full.split('::').map(encSeg).join('/');
|
|
4851
|
+
}
|
|
4852
|
+
return '';
|
|
4853
|
+
}
|
|
4854
|
+
function resolveRoute(routeStr) {
|
|
4855
|
+
var parts = String(routeStr || '').split('/').filter(function (s) { return s.length > 0; }).map(decodeURIComponent);
|
|
4856
|
+
if (!parts.length) return { kind: 'system', id: null };
|
|
4857
|
+
// NOTE: a subsystem literally named 'types'/'databases' is SHADOWED by these
|
|
4858
|
+
// view-mode routes (acceptable \u2014 the modes own those first segments).
|
|
4859
|
+
if (parts[0] === 'types') {
|
|
4860
|
+
return { kind: 'types', id: parts.length > 1 ? parts.slice(1).join('::') : null };
|
|
4861
|
+
}
|
|
4862
|
+
if (parts[0] === 'databases') {
|
|
4863
|
+
if (!showDatabaseTab) return { kind: 'system', id: null };
|
|
4864
|
+
return { kind: 'databases', id: parts.length > 1 ? parts.slice(1).join('::') : null };
|
|
4865
|
+
}
|
|
4866
|
+
var joined = parts.join('::');
|
|
4867
|
+
if (subById[joined]) return { kind: 'subsystem', id: joined };
|
|
4868
|
+
if (compById[joined]) return { kind: 'component', id: joined };
|
|
4869
|
+
return { kind: 'system', id: null }; // unknown id -> fall back to the root
|
|
4870
|
+
}
|
|
4871
|
+
// Apply a route (URL -> engine) WITHOUT echoing back through onViewChange.
|
|
4872
|
+
function openRoute(routeStr) {
|
|
4873
|
+
var v = resolveRoute(routeStr);
|
|
4874
|
+
if (state.view.kind === v.kind && state.view.id === v.id) return;
|
|
4875
|
+
applyingRoute = true;
|
|
4876
|
+
try {
|
|
4877
|
+
state.view = { kind: v.kind, id: v.id };
|
|
4878
|
+
state.selected = null;
|
|
4879
|
+
state.selectedKind = null;
|
|
4880
|
+
state.typesRenderAll = false;
|
|
4881
|
+
rebuild(true);
|
|
4882
|
+
renderPanel();
|
|
4883
|
+
} finally {
|
|
4884
|
+
applyingRoute = false;
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
// Notify the embedder (engine -> URL) of the current view; suppressed while a
|
|
4888
|
+
// route is being applied so the URL is not driven in a loop.
|
|
4889
|
+
function notifyViewChange() {
|
|
4890
|
+
if (applyingRoute) return;
|
|
4891
|
+
if (typeof opts !== 'undefined' && opts && typeof opts.onViewChange === 'function') {
|
|
4892
|
+
try { opts.onViewChange(routeOf(state.view)); } catch (e) { /* ignore */ }
|
|
4893
|
+
}
|
|
4752
4894
|
}
|
|
4753
4895
|
// Explain (and offer to override) a performance-degraded ERD.
|
|
4754
4896
|
function renderTypesNotice() {
|
|
@@ -4899,6 +5041,7 @@ var MODEL = __MODEL_JSON__;
|
|
|
4899
5041
|
if (t.ghost) {
|
|
4900
5042
|
state.view = parentViewOf(t.kind, t.id);
|
|
4901
5043
|
rebuild(true);
|
|
5044
|
+
notifyViewChange();
|
|
4902
5045
|
select(t.kind, t.id, true);
|
|
4903
5046
|
return;
|
|
4904
5047
|
}
|
|
@@ -4920,10 +5063,16 @@ var MODEL = __MODEL_JSON__;
|
|
|
4920
5063
|
}
|
|
4921
5064
|
|
|
4922
5065
|
document.getElementById('search').addEventListener('input', function (ev) { state.query = ev.target.value.trim(); rebuild(false); });
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
document.getElementById('
|
|
4926
|
-
document.getElementById('
|
|
5066
|
+
// Sync each View toggle's checkbox from the (possibly persisted) state, then
|
|
5067
|
+
// persist on change so the choices survive a refresh (see persist()/saved).
|
|
5068
|
+
document.getElementById('internalsToggle').checked = state.internals;
|
|
5069
|
+
document.getElementById('internalsToggle').addEventListener('change', function (ev) { state.internals = ev.target.checked; persist(); rebuild(true); });
|
|
5070
|
+
document.getElementById('externalsToggle').checked = state.externals;
|
|
5071
|
+
document.getElementById('externalsToggle').addEventListener('change', function (ev) { state.externals = ev.target.checked; persist(); rebuild(true); });
|
|
5072
|
+
document.getElementById('dataCouplingToggle').checked = state.dataCoupling;
|
|
5073
|
+
document.getElementById('dataCouplingToggle').addEventListener('change', function (ev) { state.dataCoupling = ev.target.checked; persist(); renderLegend(); rebuild(true); });
|
|
5074
|
+
document.getElementById('issuesToggle').checked = state.showIssues;
|
|
5075
|
+
document.getElementById('issuesToggle').addEventListener('change', function (ev) { state.showIssues = ev.target.checked; persist(); rebuild(false); renderPanel(); });
|
|
4927
5076
|
document.getElementById('dragToggle').addEventListener('change', function (ev) { cy.autolock(!ev.target.checked); });
|
|
4928
5077
|
// Mode seg: Components \u21C4 Types. Entering Types keeps the current subsystem
|
|
4929
5078
|
// scope, so a subsystem's own types (plus shared ones) show scoped.
|
|
@@ -5913,10 +6062,12 @@ var MODEL = __MODEL_JSON__;
|
|
|
5913
6062
|
if (kind === 'type' && state.view.kind !== 'types' && state.view.kind !== 'databases') {
|
|
5914
6063
|
state.view = { kind: 'types', id: typesScopeFromView() };
|
|
5915
6064
|
rebuild(true);
|
|
6065
|
+
notifyViewChange();
|
|
5916
6066
|
} else if (kind === 'component' && (state.view.kind === 'types' || state.view.kind === 'databases')) {
|
|
5917
6067
|
var c = compById[id];
|
|
5918
6068
|
state.view = { kind: 'subsystem', id: c ? c.subsystem : null };
|
|
5919
6069
|
rebuild(true);
|
|
6070
|
+
notifyViewChange();
|
|
5920
6071
|
}
|
|
5921
6072
|
state.selectedKind = kind;
|
|
5922
6073
|
state.selected = id;
|
|
@@ -5975,6 +6126,14 @@ var MODEL = __MODEL_JSON__;
|
|
|
5975
6126
|
if (!exposes || !openApiAllowed()) return '';
|
|
5976
6127
|
return '<div class="openbtn"><button class="tbtn" data-openapi-tag="' + esc(tag || '') + '">\\u25A4 View OpenAPI \\u2197</button></div>';
|
|
5977
6128
|
}
|
|
6129
|
+
// "Open in Specs" \u2014 deep-links the focused spec into the hosted Specs value
|
|
6130
|
+
// editor via a host hook (opts.onOpenSpec). Hidden when no host provides it
|
|
6131
|
+
// (standalone file, shared page): those have no editor to open. kind is the
|
|
6132
|
+
// spec layer, id its qualified spec id.
|
|
6133
|
+
function openSpecButton(kind, id) {
|
|
6134
|
+
if (typeof opts === 'undefined' || !opts || typeof opts.onOpenSpec !== 'function') return '';
|
|
6135
|
+
return '<div class="openbtn"><button class="tbtn" data-openspec-kind="' + kind + '" data-openspec-id="' + esc(id) + '">\\u270E Open in Specs \\u2197</button></div>';
|
|
6136
|
+
}
|
|
5978
6137
|
|
|
5979
6138
|
function renderPanel() {
|
|
5980
6139
|
var head = '', body = '';
|
|
@@ -5997,7 +6156,8 @@ var MODEL = __MODEL_JSON__;
|
|
|
5997
6156
|
+ (scopeFocus ? staticChip('current view') : '')
|
|
5998
6157
|
+ chip(c.subsystem, 'subsystem', c.subsystem)
|
|
5999
6158
|
+ (scopeFocus ? '' : openViewButton('component', c.id, c.owns.length > 0))
|
|
6000
|
-
+ openApiButton(componentExposesApi(c), c.
|
|
6159
|
+
+ openApiButton(componentExposesApi(c), c.id)
|
|
6160
|
+
+ openSpecButton('component', c.id);
|
|
6001
6161
|
|
|
6002
6162
|
var linkedTypes = MODEL.types.filter(function (t) { return t.componentClass === c.id; });
|
|
6003
6163
|
if (linkedTypes.length) {
|
|
@@ -6007,6 +6167,14 @@ var MODEL = __MODEL_JSON__;
|
|
|
6007
6167
|
}
|
|
6008
6168
|
body += '<p class="desc">' + esc(c.description) + '</p>';
|
|
6009
6169
|
|
|
6170
|
+
if (c.externalLinks && c.externalLinks.length) {
|
|
6171
|
+
body += section('External links', c.externalLinks.length, c.externalLinks.map(function (l) {
|
|
6172
|
+
var label = l.label || l.url;
|
|
6173
|
+
var tag = l.type === 'implementation' ? staticChip('source') : '';
|
|
6174
|
+
return '<div class="method">' + tag + '<a href="' + esc(l.url) + '" target="_blank" rel="noopener" style="color:var(--accent);word-break:break-all">' + esc(label) + ' \\u2197</a></div>';
|
|
6175
|
+
}).join(''), true);
|
|
6176
|
+
}
|
|
6177
|
+
|
|
6010
6178
|
var depInner = (c.dependsOn.length ? c.dependsOn.map(function (d) { return chip(d, 'component', d); }).join('') : '<span class="desc">none</span>')
|
|
6011
6179
|
+ (c.owns.length ? '<div style="margin-top:8px"><b style="font-size:11px">Owns:</b><br>' + c.owns.map(function (d) { return chip(d, 'component', d); }).join('') + '</div>' : '');
|
|
6012
6180
|
body += section('Dependencies', c.dependsOn.length + c.owns.length, depInner, true);
|
|
@@ -6081,7 +6249,8 @@ var MODEL = __MODEL_JSON__;
|
|
|
6081
6249
|
MODEL.types.forEach(function (t2) { if (t2.id === focusId) ty = t2; });
|
|
6082
6250
|
if (ty) {
|
|
6083
6251
|
head = '<h2>' + esc(ty.name) + '</h2>' + staticChip('\\u00AB' + ty.kind + '\\u00BB')
|
|
6084
|
-
+ (ty.subsystem ? chip(ty.subsystem, 'subsystem', ty.subsystem) : staticChip('system-level shared'))
|
|
6252
|
+
+ (ty.subsystem ? chip(ty.subsystem, 'subsystem', ty.subsystem) : staticChip('system-level shared'))
|
|
6253
|
+
+ openSpecButton('type', ty.id);
|
|
6085
6254
|
|
|
6086
6255
|
if (ty.componentClass) {
|
|
6087
6256
|
head += '<div style="margin-top:6px"><b style="font-size:11px">Class Component:</b> ' + chip(ty.componentClass, 'component', ty.componentClass) + '</div>';
|
|
@@ -6157,7 +6326,8 @@ var MODEL = __MODEL_JSON__;
|
|
|
6157
6326
|
+ (s.status ? staticChip(s.status) : '')
|
|
6158
6327
|
+ (scopeFocus ? staticChip('current view') : '')
|
|
6159
6328
|
+ (scopeFocus ? '' : openViewButton('subsystem', s.id, subKids > 0))
|
|
6160
|
-
+ openApiButton(subsystemExposesApi(s.id), '')
|
|
6329
|
+
+ openApiButton(subsystemExposesApi(s.id), '')
|
|
6330
|
+
+ openSpecButton('subsystem', s.id);
|
|
6161
6331
|
body += '<p class="desc">' + esc(s.description) + '</p>';
|
|
6162
6332
|
if (s.trustedLinks.length) {
|
|
6163
6333
|
body += section('Trusted links (fast lanes)', s.trustedLinks.length, s.trustedLinks.map(function (t2) {
|
|
@@ -6215,6 +6385,16 @@ var MODEL = __MODEL_JSON__;
|
|
|
6215
6385
|
});
|
|
6216
6386
|
})(oapis[oi]);
|
|
6217
6387
|
}
|
|
6388
|
+
var ospecs = panel.querySelectorAll('[data-openspec-kind]');
|
|
6389
|
+
for (var si = 0; si < ospecs.length; si++) {
|
|
6390
|
+
(function (b) {
|
|
6391
|
+
b.addEventListener('click', function () {
|
|
6392
|
+
if (typeof opts !== 'undefined' && opts && typeof opts.onOpenSpec === 'function') {
|
|
6393
|
+
opts.onOpenSpec(b.getAttribute('data-openspec-kind'), b.getAttribute('data-openspec-id') || '');
|
|
6394
|
+
}
|
|
6395
|
+
});
|
|
6396
|
+
})(ospecs[si]);
|
|
6397
|
+
}
|
|
6218
6398
|
var flows = panel.querySelectorAll('[data-flow-comp]');
|
|
6219
6399
|
for (var j = 0; j < flows.length; j++) {
|
|
6220
6400
|
(function (b) {
|
|
@@ -6227,6 +6407,23 @@ var MODEL = __MODEL_JSON__;
|
|
|
6227
6407
|
}
|
|
6228
6408
|
|
|
6229
6409
|
renderPanel();
|
|
6410
|
+
// Stage G: a deep link may focus a component and/or open a method's narrative
|
|
6411
|
+
// modal once the seeded view + DOM + cy graph exist. The host parses the URL hash
|
|
6412
|
+
// into opts.initialSelect / opts.initialFlow \u2014 both carry the component id, so this
|
|
6413
|
+
// works whether the seeded view is the component itself or its parent subsystem
|
|
6414
|
+
// (a leaf component has no meaningful "inside", so Specs deep-links open the parent
|
|
6415
|
+
// and focus the component here).
|
|
6416
|
+
(function () {
|
|
6417
|
+
if (typeof opts === 'undefined' || !opts) return;
|
|
6418
|
+
var f = opts.initialFlow, s = opts.initialSelect;
|
|
6419
|
+
var focusComp = (f && f.comp) || (s && s.comp);
|
|
6420
|
+
if (focusComp && compById[focusComp]) {
|
|
6421
|
+
try { select('component', focusComp, true); } catch (e) { /* ignore */ }
|
|
6422
|
+
}
|
|
6423
|
+
if (f && f.comp && f.method && compById[f.comp]) {
|
|
6424
|
+
try { openFlow(f.comp, f.method, f.mode === 'steps' ? 'steps' : 'flow'); } catch (e) { /* ignore */ }
|
|
6425
|
+
}
|
|
6426
|
+
})();
|
|
6230
6427
|
})();
|
|
6231
6428
|
</script>
|
|
6232
6429
|
</body>
|
|
@@ -6662,6 +6859,16 @@ var init_type_references = __esm({
|
|
|
6662
6859
|
}
|
|
6663
6860
|
});
|
|
6664
6861
|
|
|
6862
|
+
// src/utils/filenames.ts
|
|
6863
|
+
function safeFilenamePart(value) {
|
|
6864
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
6865
|
+
}
|
|
6866
|
+
var init_filenames = __esm({
|
|
6867
|
+
"src/utils/filenames.ts"() {
|
|
6868
|
+
"use strict";
|
|
6869
|
+
}
|
|
6870
|
+
});
|
|
6871
|
+
|
|
6665
6872
|
// src/core/statehash.ts
|
|
6666
6873
|
function computeStateId() {
|
|
6667
6874
|
const tree = {
|
|
@@ -6764,11 +6971,63 @@ function operationFor(method2, closureIds) {
|
|
|
6764
6971
|
}
|
|
6765
6972
|
return op;
|
|
6766
6973
|
}
|
|
6767
|
-
function
|
|
6768
|
-
|
|
6769
|
-
const
|
|
6974
|
+
function securitySchemeObject(auth) {
|
|
6975
|
+
if (auth.scheme === "none") return null;
|
|
6976
|
+
const desc = auth.description ? { description: auth.description } : {};
|
|
6977
|
+
switch (auth.scheme) {
|
|
6978
|
+
case "apiKey":
|
|
6979
|
+
return { type: "apiKey", in: auth.in ?? "header", name: auth.name ?? "X-API-Key", ...desc };
|
|
6980
|
+
case "bearer":
|
|
6981
|
+
return { type: "http", scheme: "bearer", ...auth.bearerFormat ? { bearerFormat: auth.bearerFormat } : {}, ...desc };
|
|
6982
|
+
case "basic":
|
|
6983
|
+
return { type: "http", scheme: "basic", ...desc };
|
|
6984
|
+
case "oauth2": {
|
|
6985
|
+
const flow = { scopes: Object.fromEntries((auth.scopes ?? []).map((s) => [s.name, s.description])) };
|
|
6986
|
+
if (auth.authorizationUrl) flow.authorizationUrl = auth.authorizationUrl;
|
|
6987
|
+
if (auth.tokenUrl) flow.tokenUrl = auth.tokenUrl;
|
|
6988
|
+
if (auth.refreshUrl) flow.refreshUrl = auth.refreshUrl;
|
|
6989
|
+
return { type: "oauth2", flows: { [auth.flow ?? "authorizationCode"]: flow }, ...desc };
|
|
6990
|
+
}
|
|
6991
|
+
case "openIdConnect":
|
|
6992
|
+
return { type: "openIdConnect", openIdConnectUrl: auth.openIdConnectUrl ?? "", ...desc };
|
|
6993
|
+
case "custom":
|
|
6994
|
+
return { type: "apiKey", in: auth.in ?? "header", name: auth.name ?? "Authorization", description: auth.description ?? auth.example ?? "Custom authentication scheme." };
|
|
6995
|
+
default:
|
|
6996
|
+
return null;
|
|
6997
|
+
}
|
|
6998
|
+
}
|
|
6999
|
+
function schemeBaseName(scheme) {
|
|
7000
|
+
return { apiKey: "ApiKeyAuth", bearer: "BearerAuth", basic: "BasicAuth", oauth2: "OAuth2", openIdConnect: "OpenIdConnect", custom: "CustomAuth" }[scheme] ?? "Auth";
|
|
7001
|
+
}
|
|
7002
|
+
function buildSecurity(entries) {
|
|
7003
|
+
const schemes = {};
|
|
7004
|
+
const nameByContent = /* @__PURE__ */ new Map();
|
|
7005
|
+
const securityByEntry = /* @__PURE__ */ new Map();
|
|
7006
|
+
for (const entry of entries) {
|
|
7007
|
+
if (!entry.auth) continue;
|
|
7008
|
+
const obj = securitySchemeObject(entry.auth);
|
|
7009
|
+
if (!obj) continue;
|
|
7010
|
+
const content = JSON.stringify(obj);
|
|
7011
|
+
let name = nameByContent.get(content);
|
|
7012
|
+
if (!name) {
|
|
7013
|
+
name = schemeBaseName(entry.auth.scheme);
|
|
7014
|
+
for (let n = 2; schemes[name]; n++) name = schemeBaseName(entry.auth.scheme) + n;
|
|
7015
|
+
schemes[name] = obj;
|
|
7016
|
+
nameByContent.set(content, name);
|
|
7017
|
+
}
|
|
7018
|
+
const scopeNames = entry.auth.scheme === "oauth2" ? (entry.auth.scopes ?? []).map((s) => s.name) : [];
|
|
7019
|
+
securityByEntry.set(entry.id, { [name]: scopeNames });
|
|
7020
|
+
}
|
|
7021
|
+
return { schemes, securityByEntry };
|
|
7022
|
+
}
|
|
7023
|
+
function httpEntriesOf(snapshot) {
|
|
7024
|
+
return snapshot.interfaces.filter((e) => e.type === "REST" || e.methods.some((m) => m.endpoint?.transport === "HTTP"));
|
|
7025
|
+
}
|
|
7026
|
+
function renderDoc(snapshot, entries, closureIds, opts = {}) {
|
|
7027
|
+
const { schemes, securityByEntry } = buildSecurity(entries);
|
|
6770
7028
|
const paths = {};
|
|
6771
|
-
for (const entry of
|
|
7029
|
+
for (const entry of entries) {
|
|
7030
|
+
const security = securityByEntry.get(entry.id);
|
|
6772
7031
|
for (const method2 of entry.methods) {
|
|
6773
7032
|
const endpoint = method2.endpoint;
|
|
6774
7033
|
if (!endpoint || endpoint.transport !== "HTTP") continue;
|
|
@@ -6776,7 +7035,8 @@ function toOpenApi(snapshot) {
|
|
|
6776
7035
|
paths[p] = paths[p] ?? {};
|
|
6777
7036
|
paths[p][endpoint.method.toLowerCase()] = {
|
|
6778
7037
|
tags: [entry.id],
|
|
6779
|
-
...operationFor(method2, closureIds)
|
|
7038
|
+
...operationFor(method2, closureIds),
|
|
7039
|
+
...security ? { security: [security] } : {}
|
|
6780
7040
|
};
|
|
6781
7041
|
}
|
|
6782
7042
|
}
|
|
@@ -6789,19 +7049,41 @@ function toOpenApi(snapshot) {
|
|
|
6789
7049
|
required: t.fields.filter((f) => !f.optional).map((f) => f.name)
|
|
6790
7050
|
};
|
|
6791
7051
|
}
|
|
6792
|
-
const
|
|
7052
|
+
const components = {};
|
|
7053
|
+
if (Object.keys(schemas).length) components.schemas = schemas;
|
|
7054
|
+
if (Object.keys(schemes).length) components.securitySchemes = schemes;
|
|
7055
|
+
const basePaths = [...new Set(entries.map((e) => e.basePath).filter((b) => !!b))];
|
|
7056
|
+
const servers = opts.servers && basePaths.length === 1 ? [{ url: basePaths[0] }] : void 0;
|
|
7057
|
+
return {
|
|
6793
7058
|
openapi: "3.1.0",
|
|
6794
7059
|
info: {
|
|
6795
|
-
title: snapshot.projectName,
|
|
7060
|
+
title: opts.title ?? snapshot.projectName,
|
|
6796
7061
|
version: snapshot.version ?? "0.0.0",
|
|
6797
7062
|
...snapshot.stateId ? { "x-wairon-state-id": snapshot.stateId } : {},
|
|
6798
7063
|
"x-wairon-origin": snapshot.origin,
|
|
6799
7064
|
"x-wairon-generated-at": snapshot.generatedAt
|
|
6800
7065
|
},
|
|
7066
|
+
...servers ? { servers } : {},
|
|
6801
7067
|
paths,
|
|
6802
|
-
...Object.keys(
|
|
7068
|
+
...Object.keys(components).length ? { components } : {}
|
|
6803
7069
|
};
|
|
6804
|
-
|
|
7070
|
+
}
|
|
7071
|
+
function toOpenApiSet(snapshot) {
|
|
7072
|
+
const closureIds = new Set(snapshot.types.map((t) => t.id));
|
|
7073
|
+
const byPortal = /* @__PURE__ */ new Map();
|
|
7074
|
+
const order = [];
|
|
7075
|
+
for (const entry of httpEntriesOf(snapshot)) {
|
|
7076
|
+
if (!byPortal.has(entry.component)) {
|
|
7077
|
+
byPortal.set(entry.component, []);
|
|
7078
|
+
order.push(entry.component);
|
|
7079
|
+
}
|
|
7080
|
+
byPortal.get(entry.component).push(entry);
|
|
7081
|
+
}
|
|
7082
|
+
return order.map((portalId) => {
|
|
7083
|
+
const entries = byPortal.get(portalId);
|
|
7084
|
+
const name = entries[0]?.name ?? portalId;
|
|
7085
|
+
return { portalId, name, document: JSON.stringify(renderDoc(snapshot, entries, closureIds, { title: name, servers: true }), null, 2) };
|
|
7086
|
+
});
|
|
6805
7087
|
}
|
|
6806
7088
|
function isOpenApiDocument(body) {
|
|
6807
7089
|
try {
|
|
@@ -6823,6 +7105,39 @@ function typeRefFromSchema(schema) {
|
|
|
6823
7105
|
if (typeof t === "string" && t !== "object") return t;
|
|
6824
7106
|
return "json";
|
|
6825
7107
|
}
|
|
7108
|
+
function authFromSecurityScheme(scheme) {
|
|
7109
|
+
const desc = typeof scheme.description === "string" ? { description: scheme.description } : {};
|
|
7110
|
+
if (scheme.type === "apiKey") {
|
|
7111
|
+
return {
|
|
7112
|
+
scheme: "apiKey",
|
|
7113
|
+
...scheme.in === "header" || scheme.in === "query" || scheme.in === "cookie" ? { in: scheme.in } : {},
|
|
7114
|
+
...typeof scheme.name === "string" ? { name: scheme.name } : {},
|
|
7115
|
+
...desc
|
|
7116
|
+
};
|
|
7117
|
+
}
|
|
7118
|
+
if (scheme.type === "http") {
|
|
7119
|
+
if (scheme.scheme === "bearer") return { scheme: "bearer", ...typeof scheme.bearerFormat === "string" ? { bearerFormat: scheme.bearerFormat } : {}, ...desc };
|
|
7120
|
+
if (scheme.scheme === "basic") return { scheme: "basic", ...desc };
|
|
7121
|
+
}
|
|
7122
|
+
if (scheme.type === "oauth2") {
|
|
7123
|
+
const flows = scheme.flows ?? {};
|
|
7124
|
+
const flowKey = Object.keys(flows)[0];
|
|
7125
|
+
const f = flows[flowKey] ?? {};
|
|
7126
|
+
return {
|
|
7127
|
+
scheme: "oauth2",
|
|
7128
|
+
...flowKey === "authorizationCode" || flowKey === "clientCredentials" || flowKey === "implicit" || flowKey === "password" ? { flow: flowKey } : {},
|
|
7129
|
+
...typeof f.authorizationUrl === "string" ? { authorizationUrl: f.authorizationUrl } : {},
|
|
7130
|
+
...typeof f.tokenUrl === "string" ? { tokenUrl: f.tokenUrl } : {},
|
|
7131
|
+
...typeof f.refreshUrl === "string" ? { refreshUrl: f.refreshUrl } : {},
|
|
7132
|
+
scopes: Object.entries(f.scopes ?? {}).map(([name, description]) => ({ name, description })),
|
|
7133
|
+
...desc
|
|
7134
|
+
};
|
|
7135
|
+
}
|
|
7136
|
+
if (scheme.type === "openIdConnect") {
|
|
7137
|
+
return { scheme: "openIdConnect", openIdConnectUrl: typeof scheme.openIdConnectUrl === "string" ? scheme.openIdConnectUrl : "", ...desc };
|
|
7138
|
+
}
|
|
7139
|
+
return void 0;
|
|
7140
|
+
}
|
|
6826
7141
|
function fromOpenApi(document, projectName) {
|
|
6827
7142
|
let parsed;
|
|
6828
7143
|
try {
|
|
@@ -6900,6 +7215,9 @@ function fromOpenApi(document, projectName) {
|
|
|
6900
7215
|
}))
|
|
6901
7216
|
});
|
|
6902
7217
|
}
|
|
7218
|
+
const securitySchemes = parsed.components?.securitySchemes ?? {};
|
|
7219
|
+
const firstScheme = Object.values(securitySchemes)[0];
|
|
7220
|
+
const importedAuth = firstScheme ? authFromSecurityScheme(firstScheme) : void 0;
|
|
6903
7221
|
const entry = {
|
|
6904
7222
|
id: `${projectName}-api`,
|
|
6905
7223
|
name: typeof info.title === "string" ? info.title : projectName,
|
|
@@ -6908,7 +7226,8 @@ function fromOpenApi(document, projectName) {
|
|
|
6908
7226
|
component: `${projectName}-api`,
|
|
6909
7227
|
methods,
|
|
6910
7228
|
details: typeof info.description === "string" ? info.description : `Imported OpenAPI surface of ${projectName}.`,
|
|
6911
|
-
...typeof info.version === "string" ? { version: info.version } : {}
|
|
7229
|
+
...typeof info.version === "string" ? { version: info.version } : {},
|
|
7230
|
+
...importedAuth ? { auth: importedAuth } : {}
|
|
6912
7231
|
};
|
|
6913
7232
|
return SurfaceSnapshotSchema.parse({
|
|
6914
7233
|
projectName,
|
|
@@ -7023,6 +7342,10 @@ function projectOwnSurface(maxAudience) {
|
|
|
7023
7342
|
component: comp.id,
|
|
7024
7343
|
methods,
|
|
7025
7344
|
...comp.dispatch && comp.dispatch.length ? { dispatch: comp.dispatch } : {},
|
|
7345
|
+
// Project the backing Portal's auth + basePath so the codec can emit
|
|
7346
|
+
// OpenAPI security + per-portal servers self-contained from the snapshot.
|
|
7347
|
+
...comp.auth && comp.auth.scheme !== "none" ? { auth: comp.auth } : {},
|
|
7348
|
+
...comp.basePath ? { basePath: comp.basePath } : {},
|
|
7026
7349
|
details: raw.details ?? "",
|
|
7027
7350
|
...raw.version ? { version: raw.version } : {},
|
|
7028
7351
|
...raw.stability ? { stability: raw.stability } : {}
|
|
@@ -7066,20 +7389,52 @@ function saveSnapshot(snapshot, rootDir = getProjectRoot()) {
|
|
|
7066
7389
|
function loadSurfaceSnapshots() {
|
|
7067
7390
|
return listSnapshots();
|
|
7068
7391
|
}
|
|
7069
|
-
function
|
|
7392
|
+
function selectPortalSpec(renderedSet, portalId) {
|
|
7393
|
+
const hit = renderedSet.find((spec) => spec.portalId === portalId);
|
|
7394
|
+
if (!hit) {
|
|
7395
|
+
const known = renderedSet.map((s) => s.portalId).join(", ");
|
|
7396
|
+
throw new Error(`Unknown portal "${portalId}" \u2014 this surface renders: ${known || "(no portals)"}.`);
|
|
7397
|
+
}
|
|
7398
|
+
return [hit];
|
|
7399
|
+
}
|
|
7400
|
+
function perPortalPath(resolvedOut, portalId) {
|
|
7401
|
+
const ext = path10.extname(resolvedOut);
|
|
7402
|
+
const stem2 = ext ? resolvedOut.slice(0, -ext.length) : resolvedOut;
|
|
7403
|
+
return `${stem2}.${safeFilenamePart(portalId)}${ext}`;
|
|
7404
|
+
}
|
|
7405
|
+
function writeSurfaceFile(outPath, snapshot, renderedSet) {
|
|
7406
|
+
const resolved = path10.resolve(outPath);
|
|
7407
|
+
fs9.mkdirSync(path10.dirname(resolved), { recursive: true });
|
|
7408
|
+
if (!renderedSet || renderedSet.length === 0) {
|
|
7409
|
+
writeYamlFile(resolved, snapshot);
|
|
7410
|
+
return [resolved];
|
|
7411
|
+
}
|
|
7412
|
+
if (renderedSet.length === 1) {
|
|
7413
|
+
fs9.writeFileSync(resolved, renderedSet[0].document);
|
|
7414
|
+
return [resolved];
|
|
7415
|
+
}
|
|
7416
|
+
return renderedSet.map((spec) => {
|
|
7417
|
+
const target = perPortalPath(resolved, spec.portalId);
|
|
7418
|
+
fs9.writeFileSync(target, spec.document);
|
|
7419
|
+
return target;
|
|
7420
|
+
});
|
|
7421
|
+
}
|
|
7422
|
+
function exportResult(snapshot, renderedSet, writtenPaths) {
|
|
7423
|
+
const rendered = renderedSet?.length === 1 ? renderedSet[0].document : void 0;
|
|
7424
|
+
return {
|
|
7425
|
+
snapshot,
|
|
7426
|
+
...rendered !== void 0 ? { rendered } : {},
|
|
7427
|
+
...renderedSet ? { renderedSet } : {},
|
|
7428
|
+
...writtenPaths.length === 1 ? { writtenTo: writtenPaths[0] } : {},
|
|
7429
|
+
...writtenPaths.length ? { writtenPaths } : {}
|
|
7430
|
+
};
|
|
7431
|
+
}
|
|
7432
|
+
function exportSurface(maxAudience, format, outPath, portalId) {
|
|
7070
7433
|
const snapshot = projectOwnSurface(maxAudience);
|
|
7071
|
-
|
|
7072
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
if (rendered !== void 0) {
|
|
7076
|
-
fs9.writeFileSync(path10.resolve(outPath), rendered);
|
|
7077
|
-
} else {
|
|
7078
|
-
writeYamlFile(path10.resolve(outPath), snapshot);
|
|
7079
|
-
}
|
|
7080
|
-
writtenTo = path10.resolve(outPath);
|
|
7081
|
-
}
|
|
7082
|
-
return { snapshot, ...rendered !== void 0 ? { rendered } : {}, ...writtenTo ? { writtenTo } : {} };
|
|
7434
|
+
let renderedSet = format === "openapi" ? toOpenApiSet(snapshot) : void 0;
|
|
7435
|
+
if (renderedSet && portalId) renderedSet = selectPortalSpec(renderedSet, portalId);
|
|
7436
|
+
const writtenPaths = outPath ? writeSurfaceFile(outPath, snapshot, renderedSet) : [];
|
|
7437
|
+
return exportResult(snapshot, renderedSet, writtenPaths);
|
|
7083
7438
|
}
|
|
7084
7439
|
function importSurface(sourcePath, origin) {
|
|
7085
7440
|
const resolved = path10.resolve(sourcePath);
|
|
@@ -7145,6 +7500,7 @@ var init_surfaces = __esm({
|
|
|
7145
7500
|
path10 = __toESM(require("path"));
|
|
7146
7501
|
init_fs();
|
|
7147
7502
|
init_yaml();
|
|
7503
|
+
init_filenames();
|
|
7148
7504
|
init_models();
|
|
7149
7505
|
init_specs2();
|
|
7150
7506
|
init_statehash();
|
|
@@ -8339,13 +8695,16 @@ var init_conformance = __esm({
|
|
|
8339
8695
|
if (isInChainedSubproject(component.subsystem, ctx)) continue;
|
|
8340
8696
|
const draft = ctx.isImplementationDraft(impl);
|
|
8341
8697
|
if (!impl.sourcePath) {
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
|
|
8345
|
-
|
|
8346
|
-
|
|
8347
|
-
|
|
8348
|
-
|
|
8698
|
+
const hasExternalSource = (component.externalLinks ?? []).some((l) => l.type === "implementation");
|
|
8699
|
+
if (!hasExternalSource) {
|
|
8700
|
+
ctx.addIssue(
|
|
8701
|
+
"warning",
|
|
8702
|
+
"MISSING_SOURCE_PATH",
|
|
8703
|
+
`Implementation "${impl.id}" declares no sourcePath \u2014 its contract "${impl.contract}" cannot be structurally checked against code.`,
|
|
8704
|
+
impl.id,
|
|
8705
|
+
draft
|
|
8706
|
+
);
|
|
8707
|
+
}
|
|
8349
8708
|
continue;
|
|
8350
8709
|
}
|
|
8351
8710
|
const lookup = lookups.get(normalizeSourcePath(impl.sourcePath));
|
|
@@ -8734,13 +9093,14 @@ var init_portals = __esm({
|
|
|
8734
9093
|
};
|
|
8735
9094
|
portalsRule = {
|
|
8736
9095
|
name: "portal-endpoints",
|
|
8737
|
-
description: "A Portal declares its portalType and binds every interface method to a concrete endpoint of the matching transport. Non-Portal components carry no portalType, basePath, or
|
|
9096
|
+
description: "A Portal declares its portalType and binds every interface method to a concrete endpoint of the matching transport. Non-Portal components carry no portalType, basePath, endpoints, or auth (auth is inbound transport auth \u2014 a Gateway carries it on the Portal it owns).",
|
|
8738
9097
|
codes: [
|
|
8739
9098
|
{ code: "MISSING_PORTAL_TYPE", defaultSeverity: "error", summary: "Portal without a portalType" },
|
|
8740
9099
|
{ code: "MISSING_ENDPOINT", defaultSeverity: "error", summary: "Portal method without a wire endpoint binding" },
|
|
8741
9100
|
{ code: "ENDPOINT_TRANSPORT_MISMATCH", defaultSeverity: "error", summary: "Endpoint transport does not match the Portal portalType" },
|
|
8742
9101
|
{ code: "UNEXPECTED_PORTAL_FIELD", defaultSeverity: "error", summary: "Non-Portal component with portalType/basePath" },
|
|
8743
|
-
{ code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" }
|
|
9102
|
+
{ code: "ARCHITECTURE_VIOLATION_NON_PORTAL_ENDPOINT", defaultSeverity: "error", summary: "Non-Portal component method declaring an endpoint" },
|
|
9103
|
+
{ code: "AUTH_ON_NON_PORTAL", defaultSeverity: "warning", summary: "Non-Portal component declaring auth (auth is inbound transport auth, only meaningful on a Portal)" }
|
|
8744
9104
|
],
|
|
8745
9105
|
check(ctx) {
|
|
8746
9106
|
for (const comp of ctx.components) {
|
|
@@ -8792,6 +9152,15 @@ var init_portals = __esm({
|
|
|
8792
9152
|
isDraftCtx
|
|
8793
9153
|
);
|
|
8794
9154
|
}
|
|
9155
|
+
if (comp.auth !== void 0) {
|
|
9156
|
+
ctx.addIssue(
|
|
9157
|
+
"warning",
|
|
9158
|
+
"AUTH_ON_NON_PORTAL",
|
|
9159
|
+
`Component "${comp.id}" is a ${comp.componentType}, not a Portal, but declares "auth". Auth is inbound transport auth and is only meaningful on a Portal (a Gateway carries it on the Portal it owns). Move it to the exposed Portal, or remove it.`,
|
|
9160
|
+
comp.id,
|
|
9161
|
+
isDraftCtx
|
|
9162
|
+
);
|
|
9163
|
+
}
|
|
8795
9164
|
const compInterfaces = ctx.interfaces.filter((i) => i.component === comp.id);
|
|
8796
9165
|
for (const intf of compInterfaces) {
|
|
8797
9166
|
for (const m of intf.methods) {
|
|
@@ -11875,6 +12244,95 @@ var init_lint_allows = __esm({
|
|
|
11875
12244
|
}
|
|
11876
12245
|
});
|
|
11877
12246
|
|
|
12247
|
+
// src/core/rules/portal-call-auth.ts
|
|
12248
|
+
var COMPONENT_REF_PREFIX, portalCallAuthRule;
|
|
12249
|
+
var init_portal_call_auth = __esm({
|
|
12250
|
+
"src/core/rules/portal-call-auth.ts"() {
|
|
12251
|
+
"use strict";
|
|
12252
|
+
COMPONENT_REF_PREFIX = "component:";
|
|
12253
|
+
portalCallAuthRule = {
|
|
12254
|
+
name: "portal-call-auth",
|
|
12255
|
+
description: "Hardens authenticated cross-service calls. An OUTBOUND narrative `call` into ANOTHER component's Portal whose auth is not `none` must (a) be made by an Adapter \u2014 the only block that does external I/O \u2014 and (b) declare the credential source it presents via the step's `auth.from`. Absence of a source warns PORTAL_AUTH_UNMET; a non-Adapter presenter warns AUTH_PRESENTER_NOT_ADAPTER. When `auth.from` is a modeled reference (`component:<id>`) it must resolve to an Adapter/Store the presenter is wired to (UNKNOWN_AUTH_SOURCE / AUTH_SOURCE_NOT_PROVIDER / AUTH_SOURCE_UNWIRED). The actual secret is never stored in the spec. Dispatch steps (a portal's OWN inbound routing) and self-calls are not cross-service calls and are excluded.",
|
|
12256
|
+
codes: [
|
|
12257
|
+
{ code: "PORTAL_AUTH_UNMET", defaultSeverity: "warning", summary: "A narrative call into another component's authed Portal does not declare where its credential loads from" },
|
|
12258
|
+
{ code: "AUTH_PRESENTER_NOT_ADAPTER", defaultSeverity: "warning", summary: "A non-Adapter component authenticates an outbound call to a portal (external I/O must go through an Adapter)" },
|
|
12259
|
+
{ code: "UNKNOWN_AUTH_SOURCE", defaultSeverity: "warning", summary: "auth.from references a component: source that does not exist" },
|
|
12260
|
+
{ code: "AUTH_SOURCE_NOT_PROVIDER", defaultSeverity: "warning", summary: "auth.from references a component that is not an Adapter or Store" },
|
|
12261
|
+
{ code: "AUTH_SOURCE_UNWIRED", defaultSeverity: "warning", summary: "The presenter declares a component: credential source it does not depend on or own" }
|
|
12262
|
+
],
|
|
12263
|
+
check(ctx) {
|
|
12264
|
+
for (const impl of ctx.implementations) {
|
|
12265
|
+
const ownComponent = ctx.interfaceMap.get(impl.contract)?.component;
|
|
12266
|
+
const presenter = ownComponent ? ctx.componentMap.get(ownComponent) : void 0;
|
|
12267
|
+
const draft = ctx.isImplementationDraft(impl);
|
|
12268
|
+
for (const method2 of impl.methods ?? []) {
|
|
12269
|
+
for (const step of method2.narrative ?? []) {
|
|
12270
|
+
if (step.type !== "call") continue;
|
|
12271
|
+
if (!step.targetComponent || step.targetComponent === ownComponent) continue;
|
|
12272
|
+
const target = ctx.componentMap.get(step.targetComponent);
|
|
12273
|
+
if (!target || target.componentType !== "Portal") continue;
|
|
12274
|
+
if (!target.auth || target.auth.scheme === "none") continue;
|
|
12275
|
+
if (presenter && presenter.componentType !== "Adapter") {
|
|
12276
|
+
ctx.addIssue(
|
|
12277
|
+
"warning",
|
|
12278
|
+
"AUTH_PRESENTER_NOT_ADAPTER",
|
|
12279
|
+
`Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": "${presenter.id}" (${presenter.componentType}) authenticates an outbound call to portal "${target.id}". An authenticated cross-service call is external I/O and must be made by an Adapter (the only block that does external I/O) \u2014 route it through a client Adapter.`,
|
|
12280
|
+
impl.id,
|
|
12281
|
+
draft
|
|
12282
|
+
);
|
|
12283
|
+
}
|
|
12284
|
+
const from = step.auth?.from;
|
|
12285
|
+
if (!from) {
|
|
12286
|
+
ctx.addIssue(
|
|
12287
|
+
"warning",
|
|
12288
|
+
"PORTAL_AUTH_UNMET",
|
|
12289
|
+
`Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}" calls the authenticated portal "${target.id}" (auth scheme: ${target.auth.scheme}) but does not declare where its credential is loaded from. Add the credential source (the step's auth.from \u2014 a secret-store component, env var, config key, or vault ref), or drop the portal's auth if it needs none.`,
|
|
12290
|
+
impl.id,
|
|
12291
|
+
draft
|
|
12292
|
+
);
|
|
12293
|
+
continue;
|
|
12294
|
+
}
|
|
12295
|
+
if (from.startsWith(COMPONENT_REF_PREFIX)) {
|
|
12296
|
+
const srcId = from.slice(COMPONENT_REF_PREFIX.length);
|
|
12297
|
+
const src = ctx.componentMap.get(srcId);
|
|
12298
|
+
if (!src) {
|
|
12299
|
+
ctx.addIssue(
|
|
12300
|
+
"warning",
|
|
12301
|
+
"UNKNOWN_AUTH_SOURCE",
|
|
12302
|
+
`Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": auth.from references component "${srcId}", which does not exist.`,
|
|
12303
|
+
impl.id,
|
|
12304
|
+
draft
|
|
12305
|
+
);
|
|
12306
|
+
} else {
|
|
12307
|
+
if (src.componentType !== "Adapter" && src.componentType !== "Store") {
|
|
12308
|
+
ctx.addIssue(
|
|
12309
|
+
"warning",
|
|
12310
|
+
"AUTH_SOURCE_NOT_PROVIDER",
|
|
12311
|
+
`Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": auth.from references "${srcId}" (${src.componentType}); a credential source must be an Adapter (loads the secret via external I/O) or a Store (holds it).`,
|
|
12312
|
+
impl.id,
|
|
12313
|
+
draft
|
|
12314
|
+
);
|
|
12315
|
+
}
|
|
12316
|
+
const wired = (presenter?.dependsOn ?? []).includes(srcId) || (presenter?.owns ?? []).includes(srcId);
|
|
12317
|
+
if (presenter && !wired) {
|
|
12318
|
+
ctx.addIssue(
|
|
12319
|
+
"warning",
|
|
12320
|
+
"AUTH_SOURCE_UNWIRED",
|
|
12321
|
+
`Narrative step ${step.stepNumber} of "${method2.name}" in "${impl.id}": "${presenter.id}" loads its credential from "${srcId}" but neither depends on nor owns it \u2014 declare the dependsOn edge so the credential wiring is real.`,
|
|
12322
|
+
impl.id,
|
|
12323
|
+
draft
|
|
12324
|
+
);
|
|
12325
|
+
}
|
|
12326
|
+
}
|
|
12327
|
+
}
|
|
12328
|
+
}
|
|
12329
|
+
}
|
|
12330
|
+
}
|
|
12331
|
+
}
|
|
12332
|
+
};
|
|
12333
|
+
}
|
|
12334
|
+
});
|
|
12335
|
+
|
|
11878
12336
|
// src/core/rules/index.ts
|
|
11879
12337
|
function makeScopeFilter(opts) {
|
|
11880
12338
|
const { components, interfaces, implementations, types, scopeSubsystem } = opts;
|
|
@@ -12120,6 +12578,7 @@ var init_rules = __esm({
|
|
|
12120
12578
|
init_hidden_state();
|
|
12121
12579
|
init_dependency_conformance();
|
|
12122
12580
|
init_lint_allows();
|
|
12581
|
+
init_portal_call_auth();
|
|
12123
12582
|
init_source_analysis();
|
|
12124
12583
|
init_types();
|
|
12125
12584
|
init_type_analysis();
|
|
@@ -12141,6 +12600,9 @@ var init_rules = __esm({
|
|
|
12141
12600
|
narrativeAntipatternsRule,
|
|
12142
12601
|
narrativeDetailRule,
|
|
12143
12602
|
portalsRule,
|
|
12603
|
+
// Cross-call auth: a narrative call into an authed Portal must name its
|
|
12604
|
+
// credential source (rides with the portal family).
|
|
12605
|
+
portalCallAuthRule,
|
|
12144
12606
|
stereotypeDepsRule,
|
|
12145
12607
|
patternsRule,
|
|
12146
12608
|
// Facade shape rides with pattern ownership: same §7 doctrine, narrative side.
|
|
@@ -12203,6 +12665,7 @@ var init_rules = __esm({
|
|
|
12203
12665
|
// L4 expectations: implementations and their code linkage.
|
|
12204
12666
|
MISSING_IMPLEMENTATION_METHOD: "implementations",
|
|
12205
12667
|
MISSING_SOURCE_PATH: "implementations",
|
|
12668
|
+
PORTAL_AUTH_UNMET: "implementations",
|
|
12206
12669
|
MISSING_SOURCE_FILE: "implementations",
|
|
12207
12670
|
SOURCE_PATH_ESCAPES_ROOT: "implementations",
|
|
12208
12671
|
UNREALIZED_METHOD: "implementations",
|
|
@@ -12608,6 +13071,28 @@ function buildGraphModel(level) {
|
|
|
12608
13071
|
...t.subsystem ? { parentId: t.subsystem } : {}
|
|
12609
13072
|
});
|
|
12610
13073
|
}
|
|
13074
|
+
const componentByInterface = /* @__PURE__ */ new Map();
|
|
13075
|
+
for (const c of model.components) {
|
|
13076
|
+
for (const intf of c.interfaces) componentByInterface.set(intf.id, c.id);
|
|
13077
|
+
}
|
|
13078
|
+
for (const impl of loadImplementationSpecs()) {
|
|
13079
|
+
const componentId = componentByInterface.get(impl.contract);
|
|
13080
|
+
if (!componentId) continue;
|
|
13081
|
+
nodes.push({
|
|
13082
|
+
id: impl.id,
|
|
13083
|
+
label: impl.name,
|
|
13084
|
+
kind: "implementation",
|
|
13085
|
+
level: 4,
|
|
13086
|
+
parentId: componentId,
|
|
13087
|
+
...impl.status ? { status: impl.status } : {}
|
|
13088
|
+
});
|
|
13089
|
+
candidates.push({ from: componentId, to: impl.id, edgeKind: "owns" });
|
|
13090
|
+
}
|
|
13091
|
+
for (const c of model.components) {
|
|
13092
|
+
for (const memberId of c.owns) {
|
|
13093
|
+
candidates.push({ from: c.id, to: memberId, edgeKind: "owns" });
|
|
13094
|
+
}
|
|
13095
|
+
}
|
|
12611
13096
|
for (const e of model.edges) {
|
|
12612
13097
|
candidates.push({ from: e.from, to: e.to, edgeKind: "depends_on" });
|
|
12613
13098
|
}
|
|
@@ -16744,6 +17229,7 @@ NOTICE:
|
|
|
16744
17229
|
type: import_zod9.z.enum(["local", "call", "dispatch", "branch", "switch", "loop", "try", "parallel", "jump", "return", "throw"]),
|
|
16745
17230
|
targetComponent: import_zod9.z.string().optional().describe("call/dispatch: L2 component id (for dispatch, the Portal routed through)"),
|
|
16746
17231
|
targetMethod: import_zod9.z.string().optional().describe("call: method name on the target"),
|
|
17232
|
+
auth: import_zod9.z.object({ from: import_zod9.z.string(), note: import_zod9.z.string().optional() }).optional().describe("call/dispatch: the credential this step presents to an AUTHED callee Portal and WHERE it loads from (`from`). Opaque form (env:API_KEY, a config key, vault:path) = a design note wairon never resolves; modeled form `component:<id>` references the Adapter/Store that provides the secret and is validated (must resolve, be an Adapter/Store, and be wired to the presenter). Absence on a call into a Portal whose auth \u2260 none warns (PORTAL_AUTH_UNMET). The authenticated call itself should be made by an Adapter (AUTH_PRESENTER_NOT_ADAPTER)."),
|
|
16747
17233
|
detach: import_zod9.z.boolean().optional().describe("call/dispatch: fire-and-forget \u2014 issue the call and continue without awaiting the result (no later step consumes it)"),
|
|
16748
17234
|
capability: import_zod9.z.string().optional().describe("dispatch: the capability routed through the target Portal's dispatch table (validated against it \u2014 UNSERVED_CAPABILITY)"),
|
|
16749
17235
|
assertsGuarantees: import_zod9.z.array(import_zod9.z.string().min(1)).optional().describe("Semantic guarantees this step relies on \u2014 each must be declared in the called method's L3 guarantees (NARRATIVE_SEMANTIC_UNBACKED otherwise). Builtin tokens: idempotent | atomic | transactional | exactly-once; extension packs may declare more (any other token is UNKNOWN_GUARANTEE)"),
|
|
@@ -25456,6 +25942,7 @@ init_validation();
|
|
|
25456
25942
|
init_diagram();
|
|
25457
25943
|
init_loader();
|
|
25458
25944
|
init_extensions();
|
|
25945
|
+
init_types();
|
|
25459
25946
|
init_server();
|
|
25460
25947
|
|
|
25461
25948
|
// src/git/config.ts
|
|
@@ -25976,7 +26463,11 @@ var hostCore = {
|
|
|
25976
26463
|
checkDeclarativePack: (raw) => {
|
|
25977
26464
|
const result = DeclarativePackSchema.safeParse(raw);
|
|
25978
26465
|
return result.success ? null : result.error.issues[0]?.message ?? "shape mismatch";
|
|
25979
|
-
}
|
|
26466
|
+
},
|
|
26467
|
+
/** The ids of wairon's built-in architectural profiles, read from the core rules
|
|
26468
|
+
* registry's built-in profile set (BUILTIN_PROFILES) — a pure, side-effect-free
|
|
26469
|
+
* read of a bundled constant. */
|
|
26470
|
+
builtinProfileIds: () => [...BUILTIN_PROFILES]
|
|
25980
26471
|
};
|
|
25981
26472
|
function validateProjectAsComplete() {
|
|
25982
26473
|
const config = loadProjectConfig();
|
|
@@ -26385,7 +26876,12 @@ function probe(loadRef, baseRoot, scope, displayRef) {
|
|
|
26385
26876
|
ref: displayRef,
|
|
26386
26877
|
profiles: Object.keys(loaded.profiles).length,
|
|
26387
26878
|
languages: Object.keys(loaded.languages).length,
|
|
26388
|
-
rules: loaded.rules.length
|
|
26879
|
+
rules: loaded.rules.length,
|
|
26880
|
+
// Structured id lists paralleling the counts above — a UI can present a
|
|
26881
|
+
// pack's profiles/languages/rules as choices instead of a bare number.
|
|
26882
|
+
profileIds: Object.keys(loaded.profiles),
|
|
26883
|
+
languageIds: Object.keys(loaded.languages),
|
|
26884
|
+
ruleIds: loaded.rules.map((r) => r.name)
|
|
26389
26885
|
};
|
|
26390
26886
|
}
|
|
26391
26887
|
function writeFileAtomic(file, content) {
|
|
@@ -26415,6 +26911,31 @@ function storeListGlobalPacks() {
|
|
|
26415
26911
|
);
|
|
26416
26912
|
return [...instance, ...image];
|
|
26417
26913
|
}
|
|
26914
|
+
function storeListAvailableProfiles() {
|
|
26915
|
+
const out = [];
|
|
26916
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26917
|
+
const emit = (id, source, family) => {
|
|
26918
|
+
const key = JSON.stringify([id, source]);
|
|
26919
|
+
if (seen.has(key)) return;
|
|
26920
|
+
seen.add(key);
|
|
26921
|
+
out.push(family ? { id, source, family } : { id, source });
|
|
26922
|
+
};
|
|
26923
|
+
for (const id of hostCore.builtinProfileIds()) emit(id, "builtin");
|
|
26924
|
+
const scanTier = (dir) => {
|
|
26925
|
+
for (const full of hostCore.discoverPacks(dir)) {
|
|
26926
|
+
try {
|
|
26927
|
+
const loaded = hostCore.loadExtensionPacks([{ ref: full, scope: "global" }], path45.dirname(full));
|
|
26928
|
+
if (loaded.errors.length) continue;
|
|
26929
|
+
const source = loaded.packNames[0] ?? path45.basename(full);
|
|
26930
|
+
for (const [id, def] of Object.entries(loaded.profiles)) emit(id, source, def.family);
|
|
26931
|
+
} catch {
|
|
26932
|
+
}
|
|
26933
|
+
}
|
|
26934
|
+
};
|
|
26935
|
+
scanTier(hostCore.globalPacksDir());
|
|
26936
|
+
scanTier(imagePacksDir());
|
|
26937
|
+
return out;
|
|
26938
|
+
}
|
|
26418
26939
|
function readPackContent(full) {
|
|
26419
26940
|
const st = fs36.statSync(full);
|
|
26420
26941
|
if (st.isFile()) return fs36.readFileSync(full, "utf8");
|
|
@@ -26601,6 +27122,23 @@ function installProjectPack(cfg, credential, project2, name, content) {
|
|
|
26601
27122
|
requireCap(cfg, credential, "project:admin", "project", project2, "Forbidden \u2014 installing a project pack requires project:admin over the project");
|
|
26602
27123
|
return executeApprovedInstallProjectPack(cfg, project2, name, content);
|
|
26603
27124
|
}
|
|
27125
|
+
function listAvailableProfiles(cfg, credential) {
|
|
27126
|
+
requirePrincipal2(cfg, credential);
|
|
27127
|
+
return storeListAvailableProfiles();
|
|
27128
|
+
}
|
|
27129
|
+
function listAdoptableProjectPacks(cfg, credential, project2) {
|
|
27130
|
+
requireCap(cfg, credential, "project:read", "project", project2, "Forbidden \u2014 listing adoptable packs requires project:read over the project");
|
|
27131
|
+
return storeListGlobalPacks();
|
|
27132
|
+
}
|
|
27133
|
+
function adoptProjectPack(cfg, credential, project2, name) {
|
|
27134
|
+
requireCap(cfg, credential, "project:admin", "project", project2, "Forbidden \u2014 adopting a project pack requires project:admin over the project");
|
|
27135
|
+
const resolution = executeApprovedResolveGlobalPacks([name]);
|
|
27136
|
+
if (resolution.resolved.length === 0) {
|
|
27137
|
+
throw new Error(`no such server-global pack "${name}" \u2014 cannot adopt a pack the instance does not carry`);
|
|
27138
|
+
}
|
|
27139
|
+
const resolved = resolution.resolved[0];
|
|
27140
|
+
return executeApprovedInstallProjectPack(cfg, project2, resolved.name, resolved.content);
|
|
27141
|
+
}
|
|
26604
27142
|
function executeApprovedListProjectPacks(cfg, project2) {
|
|
26605
27143
|
return runWithProjectRoot(boundProject2(cfg, project2), () => storeListProjectPacks());
|
|
26606
27144
|
}
|
|
@@ -27521,6 +28059,7 @@ function removeIdentityProviderRecord(dataDir, id) {
|
|
|
27521
28059
|
}
|
|
27522
28060
|
var PROJECT_CREATE_CAPABILITY = "project:create";
|
|
27523
28061
|
var PROJECT_WRITE_CAPABILITY = "project:write";
|
|
28062
|
+
var PROJECT_READ_CAPABILITY = "project:read";
|
|
27524
28063
|
var POLICY_MANAGE_CAPABILITY = "project:admin";
|
|
27525
28064
|
function carriesInstancePermission(cfg, principal, capability) {
|
|
27526
28065
|
return authorize(cfg.dataDir, principal, capability, "instance", "").value === "yes";
|
|
@@ -27585,6 +28124,19 @@ function recordProjectProfileSelection(root, selection) {
|
|
|
27585
28124
|
writeYamlFile(AI_PATHS.projectConfig(), raw);
|
|
27586
28125
|
});
|
|
27587
28126
|
}
|
|
28127
|
+
function readProjectType(root) {
|
|
28128
|
+
return runWithProjectRoot(root, () => {
|
|
28129
|
+
const raw = readYamlFile(AI_PATHS.projectConfig());
|
|
28130
|
+
return raw?.projectType ?? "backend";
|
|
28131
|
+
});
|
|
28132
|
+
}
|
|
28133
|
+
function writeProjectType(root, projectType) {
|
|
28134
|
+
runWithProjectRoot(root, () => {
|
|
28135
|
+
const raw = readYamlFile(AI_PATHS.projectConfig()) ?? {};
|
|
28136
|
+
raw["projectType"] = projectType;
|
|
28137
|
+
writeYamlFile(AI_PATHS.projectConfig(), raw);
|
|
28138
|
+
});
|
|
28139
|
+
}
|
|
27588
28140
|
function requestPackNames(request) {
|
|
27589
28141
|
const sel = request.profileSelection;
|
|
27590
28142
|
if (!sel) return [];
|
|
@@ -27779,6 +28331,32 @@ function reconcileProjectPolicy(cfg, credential, projectId) {
|
|
|
27779
28331
|
);
|
|
27780
28332
|
return result;
|
|
27781
28333
|
}
|
|
28334
|
+
function getProjectConfig(cfg, credential, projectId) {
|
|
28335
|
+
const principal = requirePrincipal4(cfg, credential);
|
|
28336
|
+
if (authorize(cfg.dataDir, principal, PROJECT_READ_CAPABILITY, "project", projectId).value !== "yes") {
|
|
28337
|
+
throw new ForbiddenError(
|
|
28338
|
+
"reading a project's configuration requires project:read over the project"
|
|
28339
|
+
);
|
|
28340
|
+
}
|
|
28341
|
+
const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
|
|
28342
|
+
if (!root) throw new Error(`Unknown project "${projectId}".`);
|
|
28343
|
+
const projectType = readProjectType(root);
|
|
28344
|
+
const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
|
|
28345
|
+
return { projectType, locked };
|
|
28346
|
+
}
|
|
28347
|
+
function setProjectType(cfg, credential, projectId, projectType) {
|
|
28348
|
+
const principal = requirePrincipal4(cfg, credential);
|
|
28349
|
+
if (authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY, "project", projectId).value !== "yes") {
|
|
28350
|
+
throw new ForbiddenError(
|
|
28351
|
+
"changing a project's configuration requires project:write over the project"
|
|
28352
|
+
);
|
|
28353
|
+
}
|
|
28354
|
+
const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
|
|
28355
|
+
if (!root) throw new Error(`Unknown project "${projectId}".`);
|
|
28356
|
+
writeProjectType(root, projectType);
|
|
28357
|
+
const locked = runWithProjectRoot(root, () => hostCore.readLockRecord() !== null);
|
|
28358
|
+
return { projectType, locked };
|
|
28359
|
+
}
|
|
27782
28360
|
function getPackPolicy(cfg, credential) {
|
|
27783
28361
|
requirePrincipal4(cfg, credential);
|
|
27784
28362
|
return getPackPolicyRecord(cfg.dataDir) ?? PERMISSIVE_DEFAULT_POLICY;
|
|
@@ -27842,7 +28420,7 @@ function handlePolicyRequest(cfg, credential, req, res, body, url) {
|
|
|
27842
28420
|
|
|
27843
28421
|
// src/server/identity.ts
|
|
27844
28422
|
var PROJECT_ADMIN_CAPABILITY2 = "project:admin";
|
|
27845
|
-
var
|
|
28423
|
+
var PROJECT_READ_CAPABILITY2 = "project:read";
|
|
27846
28424
|
var PROJECT_WRITE_CAPABILITY2 = "project:write";
|
|
27847
28425
|
function requireInstanceProjectAdmin(cfg, principal, what) {
|
|
27848
28426
|
if (authorize(cfg.dataDir, principal, PROJECT_ADMIN_CAPABILITY2, "instance", "").value !== "yes") {
|
|
@@ -27988,7 +28566,7 @@ function revokeToken(cfg, credential, tokenId) {
|
|
|
27988
28566
|
}
|
|
27989
28567
|
function mintSelfToken(cfg, credential, projectId, write) {
|
|
27990
28568
|
const principal = requirePrincipal5(cfg, credential);
|
|
27991
|
-
if (authorize(cfg.dataDir, principal,
|
|
28569
|
+
if (authorize(cfg.dataDir, principal, PROJECT_READ_CAPABILITY2, "project", projectId).value !== "yes") {
|
|
27992
28570
|
throw new ForbiddenError("caller lacks project:read on the requested project");
|
|
27993
28571
|
}
|
|
27994
28572
|
if (write && authorize(cfg.dataDir, principal, PROJECT_WRITE_CAPABILITY2, "project", projectId).value !== "yes") {
|
|
@@ -28633,8 +29211,9 @@ function audienceDistance(resolution, targetProjectId) {
|
|
|
28633
29211
|
|
|
28634
29212
|
// src/server/landscape.ts
|
|
28635
29213
|
var yamlLib = __toESM(require("js-yaml"));
|
|
29214
|
+
init_filenames();
|
|
28636
29215
|
var PROJECT_ADMIN_CAPABILITY3 = "project:admin";
|
|
28637
|
-
var
|
|
29216
|
+
var PROJECT_READ_CAPABILITY3 = "project:read";
|
|
28638
29217
|
var PROJECT_WRITE_CAPABILITY3 = "project:write";
|
|
28639
29218
|
function requirePrincipal6(cfg, credential) {
|
|
28640
29219
|
const principal = authenticateCredential(cfg.dataDir, credential);
|
|
@@ -28648,7 +29227,7 @@ function readView(cfg, principal) {
|
|
|
28648
29227
|
if (isInstanceAdmin(principal)) {
|
|
28649
29228
|
return { all: true, projectIds: /* @__PURE__ */ new Set(), unitIds: /* @__PURE__ */ new Set(), contextUnitIds: /* @__PURE__ */ new Set() };
|
|
28650
29229
|
}
|
|
28651
|
-
const read = visibleScopes(cfg.dataDir, principal,
|
|
29230
|
+
const read = visibleScopes(cfg.dataDir, principal, PROJECT_READ_CAPABILITY3);
|
|
28652
29231
|
const admin = visibleScopes(cfg.dataDir, principal, PROJECT_ADMIN_CAPABILITY3);
|
|
28653
29232
|
const unitIds = /* @__PURE__ */ new Set([...actionableUnitIds(read), ...actionableUnitIds(admin)]);
|
|
28654
29233
|
const contextUnitIds = new Set(
|
|
@@ -28663,13 +29242,13 @@ function readView(cfg, principal) {
|
|
|
28663
29242
|
}
|
|
28664
29243
|
function requireLandscapeReach(cfg, principal, view, what) {
|
|
28665
29244
|
if (view.all || view.projectIds.size > 0 || view.unitIds.size > 0) return;
|
|
28666
|
-
if (permitsCap(cfg, principal,
|
|
29245
|
+
if (permitsCap(cfg, principal, PROJECT_READ_CAPABILITY3, "instance", "") || permitsCap(cfg, principal, PROJECT_ADMIN_CAPABILITY3, "instance", "")) {
|
|
28667
29246
|
return;
|
|
28668
29247
|
}
|
|
28669
29248
|
throw new ForbiddenError(`${what} requires project:read reach`);
|
|
28670
29249
|
}
|
|
28671
29250
|
function requireObserverScope(cfg, principal, observerProjectId) {
|
|
28672
|
-
const covered = permitsCap(cfg, principal,
|
|
29251
|
+
const covered = permitsCap(cfg, principal, PROJECT_READ_CAPABILITY3, "project", observerProjectId) || permitsCap(cfg, principal, PROJECT_WRITE_CAPABILITY3, "project", observerProjectId);
|
|
28673
29252
|
if (!covered) {
|
|
28674
29253
|
throw new ForbiddenError("caller lacks scope over the current project");
|
|
28675
29254
|
}
|
|
@@ -29119,7 +29698,7 @@ function getProjectSurfaceForMcp(cfg, credential, currentProjectId, targetProjec
|
|
|
29119
29698
|
const result = runWithProjectRoot(record2.rootPath, () => hostSurfaces.exportBoundSurface(maxAudience, "native"));
|
|
29120
29699
|
return { ...result.snapshot, origin: "exchanged" };
|
|
29121
29700
|
}
|
|
29122
|
-
function exportProjectSurface(cfg, credential, projectId, format, maxAudience) {
|
|
29701
|
+
function exportProjectSurface(cfg, credential, projectId, format, maxAudience, portalId) {
|
|
29123
29702
|
const principal = requirePrincipal6(cfg, credential);
|
|
29124
29703
|
if (!permitsCap(cfg, principal, PROJECT_ADMIN_CAPABILITY3, "project", projectId)) {
|
|
29125
29704
|
throw new ForbiddenError(
|
|
@@ -29133,16 +29712,41 @@ function exportProjectSurface(cfg, credential, projectId, format, maxAudience) {
|
|
|
29133
29712
|
if (!root) throw new Error(`Unknown project "${projectId}".`);
|
|
29134
29713
|
const result = runWithProjectRoot(root, () => hostSurfaces.exportBoundSurface(maxAudience, format));
|
|
29135
29714
|
if (format === "openapi") {
|
|
29715
|
+
const specs = result.renderedSet ?? [];
|
|
29716
|
+
if (portalId) {
|
|
29717
|
+
const hit = specs.find((s) => s.portalId === portalId);
|
|
29718
|
+
if (!hit) {
|
|
29719
|
+
throw new Error(
|
|
29720
|
+
`Unknown portal "${portalId}" in project "${projectId}" (published: ${specs.map((s) => s.portalId).join(", ") || "none"}).`
|
|
29721
|
+
);
|
|
29722
|
+
}
|
|
29723
|
+
return {
|
|
29724
|
+
body: hit.document,
|
|
29725
|
+
contentType: "application/json",
|
|
29726
|
+
filename: `${safeFilenamePart(projectId)}-${safeFilenamePart(portalId)}-surface.openapi.json`
|
|
29727
|
+
};
|
|
29728
|
+
}
|
|
29729
|
+
if (specs.length === 1) {
|
|
29730
|
+
return {
|
|
29731
|
+
body: specs[0].document,
|
|
29732
|
+
contentType: "application/json",
|
|
29733
|
+
filename: `${safeFilenamePart(projectId)}-surface.openapi.json`
|
|
29734
|
+
};
|
|
29735
|
+
}
|
|
29136
29736
|
return {
|
|
29137
|
-
body:
|
|
29737
|
+
body: JSON.stringify(
|
|
29738
|
+
{ openapiIndex: true, specs: specs.map((s) => ({ portalId: s.portalId, name: s.name })) },
|
|
29739
|
+
null,
|
|
29740
|
+
2
|
|
29741
|
+
),
|
|
29138
29742
|
contentType: "application/json",
|
|
29139
|
-
filename: `${projectId}-surface.openapi.json`
|
|
29743
|
+
filename: `${safeFilenamePart(projectId)}-surface.openapi.index.json`
|
|
29140
29744
|
};
|
|
29141
29745
|
}
|
|
29142
29746
|
return {
|
|
29143
29747
|
body: yamlLib.dump(result.snapshot),
|
|
29144
29748
|
contentType: "application/yaml",
|
|
29145
|
-
filename: `${projectId}-surface.yaml`
|
|
29749
|
+
filename: `${safeFilenamePart(projectId)}-surface.yaml`
|
|
29146
29750
|
};
|
|
29147
29751
|
}
|
|
29148
29752
|
function removeRelation(cfg, credential, id) {
|
|
@@ -29186,7 +29790,8 @@ function handleLandscapeRequest(cfg, credential, req, res, body, url) {
|
|
|
29186
29790
|
credential,
|
|
29187
29791
|
parts[2],
|
|
29188
29792
|
url.searchParams.get("format") ?? "native",
|
|
29189
|
-
url.searchParams.get("audience") ?? "instance"
|
|
29793
|
+
url.searchParams.get("audience") ?? "instance",
|
|
29794
|
+
url.searchParams.get("spec") ?? void 0
|
|
29190
29795
|
);
|
|
29191
29796
|
res.writeHead(200, {
|
|
29192
29797
|
"content-type": artifact.contentType,
|
|
@@ -30706,6 +31311,15 @@ function installGlobalPackArchive2(cfg, credential, archive, name) {
|
|
|
30706
31311
|
function installProjectPackArchive2(cfg, credential, project2, archive, name) {
|
|
30707
31312
|
return installProjectPackArchive(cfg, credential, project2, archive, name);
|
|
30708
31313
|
}
|
|
31314
|
+
function listAvailableProfiles2(cfg, credential) {
|
|
31315
|
+
return listAvailableProfiles(cfg, credential);
|
|
31316
|
+
}
|
|
31317
|
+
function listAdoptableProjectPacks2(cfg, credential, project2) {
|
|
31318
|
+
return listAdoptableProjectPacks(cfg, credential, project2);
|
|
31319
|
+
}
|
|
31320
|
+
function adoptProjectPack2(cfg, credential, project2, name) {
|
|
31321
|
+
return adoptProjectPack(cfg, credential, project2, name);
|
|
31322
|
+
}
|
|
30709
31323
|
function getPackPolicy2(cfg, credential) {
|
|
30710
31324
|
return getPackPolicy(cfg, credential);
|
|
30711
31325
|
}
|
|
@@ -30718,6 +31332,12 @@ function evaluateProjectPolicy2(cfg, credential, projectId) {
|
|
|
30718
31332
|
function reconcileProjectPolicy2(cfg, credential, projectId) {
|
|
30719
31333
|
return reconcileProjectPolicy(cfg, credential, projectId);
|
|
30720
31334
|
}
|
|
31335
|
+
function getProjectConfig2(cfg, credential, projectId) {
|
|
31336
|
+
return getProjectConfig(cfg, credential, projectId);
|
|
31337
|
+
}
|
|
31338
|
+
function setProjectType2(cfg, credential, projectId, projectType) {
|
|
31339
|
+
return setProjectType(cfg, credential, projectId, projectType);
|
|
31340
|
+
}
|
|
30721
31341
|
function listProducers2(cfg, credential, project2) {
|
|
30722
31342
|
return listProducers(cfg, credential, project2);
|
|
30723
31343
|
}
|
|
@@ -31246,6 +31866,26 @@ var ShareSnapshotRegistry = class {
|
|
|
31246
31866
|
return stored;
|
|
31247
31867
|
}
|
|
31248
31868
|
};
|
|
31869
|
+
function openApiIndexDocument(specs) {
|
|
31870
|
+
return JSON.stringify(
|
|
31871
|
+
{ openapiIndex: true, specs: specs.map((s) => ({ portalId: s.portalId, name: s.name })) },
|
|
31872
|
+
null,
|
|
31873
|
+
2
|
|
31874
|
+
);
|
|
31875
|
+
}
|
|
31876
|
+
function selectCapturedOpenApi(snap, portalId) {
|
|
31877
|
+
const specs = snap.openapiSet;
|
|
31878
|
+
if (!specs || specs.length === 0) {
|
|
31879
|
+
if (portalId) return null;
|
|
31880
|
+
return snap.openapi ?? null;
|
|
31881
|
+
}
|
|
31882
|
+
if (portalId) {
|
|
31883
|
+
const hit = specs.find((s) => s.portalId === portalId);
|
|
31884
|
+
return hit ? hit.document : null;
|
|
31885
|
+
}
|
|
31886
|
+
if (specs.length === 1) return specs[0].document;
|
|
31887
|
+
return openApiIndexDocument(specs);
|
|
31888
|
+
}
|
|
31249
31889
|
var ShareSnapshotIndex = class {
|
|
31250
31890
|
constructor(store) {
|
|
31251
31891
|
this.store = store;
|
|
@@ -31253,12 +31893,12 @@ var ShareSnapshotIndex = class {
|
|
|
31253
31893
|
get(snapshotId) {
|
|
31254
31894
|
return this.store.read(snapshotId);
|
|
31255
31895
|
}
|
|
31256
|
-
getArtifact(snapshotId, kind) {
|
|
31896
|
+
getArtifact(snapshotId, kind, portalId) {
|
|
31257
31897
|
const snap = this.store.read(snapshotId);
|
|
31258
31898
|
if (!snap) return null;
|
|
31259
31899
|
if (kind === "canvas") return snap.canvasModel ?? null;
|
|
31260
31900
|
if (kind === "html") return snap.html ?? null;
|
|
31261
|
-
if (kind === "openapi") return snap
|
|
31901
|
+
if (kind === "openapi") return selectCapturedOpenApi(snap, portalId);
|
|
31262
31902
|
return null;
|
|
31263
31903
|
}
|
|
31264
31904
|
};
|
|
@@ -31268,8 +31908,8 @@ function putSnapshot(dataDir, snapshot) {
|
|
|
31268
31908
|
function getSnapshot2(dataDir, snapshotId) {
|
|
31269
31909
|
return new ShareSnapshotIndex(new ShareSnapshotStore(dataDir)).get(snapshotId);
|
|
31270
31910
|
}
|
|
31271
|
-
function getSnapshotArtifact(dataDir, snapshotId, kind) {
|
|
31272
|
-
return new ShareSnapshotIndex(new ShareSnapshotStore(dataDir)).getArtifact(snapshotId, kind);
|
|
31911
|
+
function getSnapshotArtifact(dataDir, snapshotId, kind, portalId) {
|
|
31912
|
+
return new ShareSnapshotIndex(new ShareSnapshotStore(dataDir)).getArtifact(snapshotId, kind, portalId);
|
|
31273
31913
|
}
|
|
31274
31914
|
function captureSnapshot(dataDir, principal, projectId, view, artifacts) {
|
|
31275
31915
|
const root = resolveProjectRoot(dataDir, principal, projectId);
|
|
@@ -31288,7 +31928,11 @@ function captureSnapshot(dataDir, principal, projectId, view, artifacts) {
|
|
|
31288
31928
|
}
|
|
31289
31929
|
if (artifacts.includes("openapi")) {
|
|
31290
31930
|
const result = hostSurfaces.exportBoundSurface("project", "openapi");
|
|
31291
|
-
|
|
31931
|
+
const specs = result.renderedSet ?? [];
|
|
31932
|
+
if (specs.length) {
|
|
31933
|
+
snapshot.openapiSet = specs;
|
|
31934
|
+
if (specs.length === 1) snapshot.openapi = specs[0].document;
|
|
31935
|
+
}
|
|
31292
31936
|
}
|
|
31293
31937
|
return snapshot;
|
|
31294
31938
|
});
|
|
@@ -31839,7 +32483,7 @@ function getWebProjectCanvasModel(cfg, sessionId, projectId) {
|
|
|
31839
32483
|
}
|
|
31840
32484
|
return runWithProjectRoot(root, () => hostCore.buildCanvasDataModel());
|
|
31841
32485
|
}
|
|
31842
|
-
function getWebProjectOpenApi(cfg, sessionId, projectId) {
|
|
32486
|
+
function getWebProjectOpenApi(cfg, sessionId, projectId, portalId) {
|
|
31843
32487
|
const principal = authenticateSession(cfg.dataDir, sessionId);
|
|
31844
32488
|
if (!principal.authenticated) throw new UnauthenticatedError();
|
|
31845
32489
|
if (!listProjects2(cfg, sessionId).some((r) => r.id === projectId)) {
|
|
@@ -31848,7 +32492,19 @@ function getWebProjectOpenApi(cfg, sessionId, projectId) {
|
|
|
31848
32492
|
const root = resolveProjectRoot(cfg.dataDir, principal, projectId);
|
|
31849
32493
|
if (!root) throw new ForbiddenError("project not authorized or unknown");
|
|
31850
32494
|
const result = runWithProjectRoot(root, () => hostSurfaces.exportBoundSurface("project", "openapi"));
|
|
31851
|
-
|
|
32495
|
+
const specs = result.renderedSet ?? (result.rendered ? [{ portalId: "", name: projectId, document: result.rendered }] : []);
|
|
32496
|
+
if (specs.length === 0) return openApiIndexPage(projectId, []);
|
|
32497
|
+
if (portalId) {
|
|
32498
|
+
const sel = specs.find((s) => s.portalId === portalId);
|
|
32499
|
+
return sel ? swaggerUiPage(sel.document, sel.name) : openApiIndexPage(projectId, specs);
|
|
32500
|
+
}
|
|
32501
|
+
if (specs.length === 1) return swaggerUiPage(specs[0].document, specs[0].name);
|
|
32502
|
+
return openApiIndexPage(projectId, specs);
|
|
32503
|
+
}
|
|
32504
|
+
function openApiIndexPage(projectId, specs) {
|
|
32505
|
+
const esc2 = (s) => s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
32506
|
+
const items = specs.map((s) => `<li><a href="/web/openapi?projectId=${encodeURIComponent(projectId)}&spec=${encodeURIComponent(s.portalId)}">${esc2(s.name)}</a> <code>${esc2(s.portalId)}</code></li>`).join("");
|
|
32507
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>${esc2(projectId)} \u2014 API specs</title><style>body{font-family:system-ui,sans-serif;max-width:680px;margin:48px auto;padding:0 20px;color:#e6e6e6;background:#161616}h1{font-size:20px}a{color:#6ea8fe;text-decoration:none}a:hover{text-decoration:underline}li{margin:10px 0}code{color:#8a94a6;font-size:12px;margin-left:8px}</style></head><body><h1>${esc2(projectId)} \u2014 API specs</h1>` + (specs.length ? `<p>This project exposes ${specs.length} separate public APIs, each with its own OpenAPI document and auth:</p><ul>${items}</ul>` : `<p>This project publishes no HTTP API \u2014 no public portal exposes one.</p>`) + `</body></html>`;
|
|
31852
32508
|
}
|
|
31853
32509
|
function reshapeLandscapeGraph(model, level) {
|
|
31854
32510
|
const unitNodeIds = new Set(model.nodes.filter((n) => n.nodeKind === "orgUnit").map((n) => n.id));
|
|
@@ -33999,6 +34655,15 @@ function opsInstallGlobalPackArchive(cfg, sessionId, req, body, res) {
|
|
|
33999
34655
|
function opsInstallProjectPackArchive(cfg, sessionId, req, url, body, res) {
|
|
34000
34656
|
sendJson(res, 200, installProjectPackArchive2(cfg, sessionId, q(url, "projectId") ?? "", archiveBody(body), packNameOverride(req)));
|
|
34001
34657
|
}
|
|
34658
|
+
function opsListAvailableProfiles(cfg, sessionId, res) {
|
|
34659
|
+
sendJson(res, 200, { profiles: listAvailableProfiles2(cfg, sessionId) });
|
|
34660
|
+
}
|
|
34661
|
+
function opsListAdoptableProjectPacks(cfg, sessionId, url, res) {
|
|
34662
|
+
sendJson(res, 200, { packs: listAdoptableProjectPacks2(cfg, sessionId, q(url, "projectId") ?? "") });
|
|
34663
|
+
}
|
|
34664
|
+
function opsAdoptProjectPack(cfg, sessionId, body, res) {
|
|
34665
|
+
sendJson(res, 200, adoptProjectPack2(cfg, sessionId, String(body?.projectId ?? ""), String(body?.name ?? "")));
|
|
34666
|
+
}
|
|
34002
34667
|
function opsGetPackPolicy(cfg, sessionId, res) {
|
|
34003
34668
|
sendJson(res, 200, getPackPolicy2(cfg, sessionId));
|
|
34004
34669
|
}
|
|
@@ -34011,6 +34676,12 @@ function opsPolicyEvaluate(cfg, sessionId, url, res) {
|
|
|
34011
34676
|
function opsPolicyReconcile(cfg, sessionId, body, res) {
|
|
34012
34677
|
sendJson(res, 200, reconcileProjectPolicy2(cfg, sessionId, String(body?.projectId ?? "")));
|
|
34013
34678
|
}
|
|
34679
|
+
function opsGetProjectConfig(cfg, sessionId, url, res) {
|
|
34680
|
+
sendJson(res, 200, getProjectConfig2(cfg, sessionId, q(url, "projectId") ?? ""));
|
|
34681
|
+
}
|
|
34682
|
+
function opsSetProjectConfig(cfg, sessionId, body, res) {
|
|
34683
|
+
sendJson(res, 200, setProjectType2(cfg, sessionId, String(body?.projectId ?? ""), String(body?.projectType ?? "")));
|
|
34684
|
+
}
|
|
34014
34685
|
function opsListProducers(cfg, sessionId, url, res) {
|
|
34015
34686
|
sendJson(res, 200, { producers: listProducers2(cfg, sessionId, q(url, "projectId") ?? "") });
|
|
34016
34687
|
}
|
|
@@ -34195,7 +34866,8 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
|
|
|
34195
34866
|
}
|
|
34196
34867
|
if (req.method === "GET" && parts.length === 2 && parts[1] === "openapi") {
|
|
34197
34868
|
const projectId = url.searchParams.get("projectId") ?? "";
|
|
34198
|
-
const
|
|
34869
|
+
const spec = url.searchParams.get("spec") ?? void 0;
|
|
34870
|
+
const html = getWebProjectOpenApi(cfg, sessionId, projectId, spec);
|
|
34199
34871
|
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
34200
34872
|
res.end(html);
|
|
34201
34873
|
return;
|
|
@@ -34237,6 +34909,18 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
|
|
|
34237
34909
|
if (req.method === "POST" && parts.length === 4 && parts[2] === "packs" && parts[3] === "remove") {
|
|
34238
34910
|
return opsRemoveProjectPack(cfg, sessionId, body, res);
|
|
34239
34911
|
}
|
|
34912
|
+
if (req.method === "GET" && parts.length === 4 && parts[2] === "packs" && parts[3] === "adoptable") {
|
|
34913
|
+
return opsListAdoptableProjectPacks(cfg, sessionId, url, res);
|
|
34914
|
+
}
|
|
34915
|
+
if (req.method === "POST" && parts.length === 4 && parts[2] === "packs" && parts[3] === "adopt") {
|
|
34916
|
+
return opsAdoptProjectPack(cfg, sessionId, body, res);
|
|
34917
|
+
}
|
|
34918
|
+
if (req.method === "GET" && parts.length === 3 && parts[2] === "config") {
|
|
34919
|
+
return opsGetProjectConfig(cfg, sessionId, url, res);
|
|
34920
|
+
}
|
|
34921
|
+
if (req.method === "POST" && parts.length === 3 && parts[2] === "config") {
|
|
34922
|
+
return opsSetProjectConfig(cfg, sessionId, body, res);
|
|
34923
|
+
}
|
|
34240
34924
|
if (req.method === "GET" && parts.length === 3 && parts[2] === "policy") {
|
|
34241
34925
|
return opsPolicyEvaluate(cfg, sessionId, url, res);
|
|
34242
34926
|
}
|
|
@@ -34350,6 +35034,9 @@ async function handleWebRequest(cfg, req, res, body, url, ctx) {
|
|
|
34350
35034
|
if (req.method === "POST" && parts.length === 4 && parts[2] === "packs" && parts[3] === "remove") {
|
|
34351
35035
|
return opsRemoveGlobalPack(cfg, sessionId, body, res);
|
|
34352
35036
|
}
|
|
35037
|
+
if (req.method === "GET" && parts.length === 3 && parts[2] === "profiles") {
|
|
35038
|
+
return opsListAvailableProfiles(cfg, sessionId, res);
|
|
35039
|
+
}
|
|
34353
35040
|
if (req.method === "GET" && parts.length === 3 && parts[2] === "policy") {
|
|
34354
35041
|
return opsGetPackPolicy(cfg, sessionId, res);
|
|
34355
35042
|
}
|
|
@@ -34943,7 +35630,7 @@ var CONTENT_TYPE = {
|
|
|
34943
35630
|
openapi: "application/json",
|
|
34944
35631
|
canvas: "application/json"
|
|
34945
35632
|
};
|
|
34946
|
-
function downloadArtifact(cfg, token, kind, meta) {
|
|
35633
|
+
function downloadArtifact(cfg, token, kind, meta, portalId) {
|
|
34947
35634
|
const link = linkByTokenHash(cfg.dataDir, hashToken(token));
|
|
34948
35635
|
const check = usable(link);
|
|
34949
35636
|
if ("outcome" in check) {
|
|
@@ -34955,7 +35642,7 @@ function downloadArtifact(cfg, token, kind, meta) {
|
|
|
34955
35642
|
record(cfg.dataDir, check.link.id, meta, "denied-download");
|
|
34956
35643
|
return { found: false, outcome: "denied-download" };
|
|
34957
35644
|
}
|
|
34958
|
-
const content = getSnapshotArtifact(cfg.dataDir, check.link.snapshotId, kind);
|
|
35645
|
+
const content = getSnapshotArtifact(cfg.dataDir, check.link.snapshotId, kind, portalId);
|
|
34959
35646
|
if (content === null) {
|
|
34960
35647
|
record(cfg.dataDir, check.link.id, meta, "not-found");
|
|
34961
35648
|
return { found: false, outcome: "not-found" };
|
|
@@ -34965,6 +35652,35 @@ function downloadArtifact(cfg, token, kind, meta) {
|
|
|
34965
35652
|
}
|
|
34966
35653
|
|
|
34967
35654
|
// src/server/sharehttp.ts
|
|
35655
|
+
init_filenames();
|
|
35656
|
+
function specParam(req) {
|
|
35657
|
+
const url = req.url ?? "";
|
|
35658
|
+
const q2 = url.indexOf("?");
|
|
35659
|
+
if (q2 < 0) return void 0;
|
|
35660
|
+
return new URLSearchParams(url.slice(q2 + 1)).get("spec") || void 0;
|
|
35661
|
+
}
|
|
35662
|
+
function asOpenApiIndex(payload) {
|
|
35663
|
+
try {
|
|
35664
|
+
const parsed = JSON.parse(payload);
|
|
35665
|
+
return parsed && parsed.openapiIndex === true && Array.isArray(parsed.specs) ? parsed : null;
|
|
35666
|
+
} catch {
|
|
35667
|
+
return null;
|
|
35668
|
+
}
|
|
35669
|
+
}
|
|
35670
|
+
function escapeHtml2(s) {
|
|
35671
|
+
return s.replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ })[c]);
|
|
35672
|
+
}
|
|
35673
|
+
function sharedOpenApiIndexPage(index) {
|
|
35674
|
+
const items = index.specs.map(
|
|
35675
|
+
(s) => `<li><a href="?spec=${encodeURIComponent(s.portalId)}">${escapeHtml2(s.name)}</a><code>${escapeHtml2(s.portalId)}</code></li>`
|
|
35676
|
+
).join("");
|
|
35677
|
+
return `<!doctype html><html><head><meta charset="utf-8"><title>Shared APIs</title><meta name="viewport" content="width=device-width, initial-scale=1"><style>body{font:15px/1.6 system-ui,sans-serif;max-width:680px;margin:48px auto;padding:0 20px;background:#0b1120;color:#e8e8f0}h1{font-size:20px}a{color:#6ea8fe;text-decoration:none}a:hover{text-decoration:underline}li{margin:10px 0}code{color:#9a9aab;font-size:12px;margin-left:8px}p{color:#9a9aab}</style></head><body><h1>Shared APIs</h1><p>This share exposes ${index.specs.length} separate APIs, each with its own OpenAPI document and auth:</p><ul>${items}</ul></body></html>`;
|
|
35678
|
+
}
|
|
35679
|
+
function downloadFilename(kind, portalId, payload) {
|
|
35680
|
+
if (kind !== "openapi") return `shared-canvas.${safeFilenamePart(kind)}`;
|
|
35681
|
+
if (portalId) return `shared-canvas.${safeFilenamePart(portalId)}.openapi.json`;
|
|
35682
|
+
return asOpenApiIndex(payload) ? "shared-canvas.openapi.index.json" : "shared-canvas.openapi.json";
|
|
35683
|
+
}
|
|
34968
35684
|
function shareRequestMeta(req) {
|
|
34969
35685
|
const fwd = req.headers["x-forwarded-for"];
|
|
34970
35686
|
const ip = (Array.isArray(fwd) ? fwd[0] : fwd)?.split(",")[0].trim() || req.socket?.remoteAddress || "unknown";
|
|
@@ -35016,14 +35732,16 @@ function serveSharedModel(cfg, token, req, res) {
|
|
|
35016
35732
|
);
|
|
35017
35733
|
}
|
|
35018
35734
|
function serveSharedOpenApi(cfg, token, req, res) {
|
|
35019
|
-
const result = downloadArtifact(cfg, token, "openapi", shareRequestMeta(req));
|
|
35735
|
+
const result = downloadArtifact(cfg, token, "openapi", shareRequestMeta(req), specParam(req));
|
|
35020
35736
|
if (!result.found || result.content === void 0) return notFoundPage(res);
|
|
35737
|
+
const index = asOpenApiIndex(result.content);
|
|
35021
35738
|
harden(res, void 0, "text/html; charset=utf-8");
|
|
35022
35739
|
res.statusCode = 200;
|
|
35023
|
-
res.end(swaggerUiPage(result.content, "Shared API"));
|
|
35740
|
+
res.end(index ? sharedOpenApiIndexPage(index) : swaggerUiPage(result.content, "Shared API"));
|
|
35024
35741
|
}
|
|
35025
35742
|
function serveSharedDownload(cfg, token, kind, req, res) {
|
|
35026
|
-
const
|
|
35743
|
+
const portalId = specParam(req);
|
|
35744
|
+
const result = downloadArtifact(cfg, token, kind, shareRequestMeta(req), portalId);
|
|
35027
35745
|
if (!result.found || result.content === void 0) {
|
|
35028
35746
|
if (result.outcome === "denied-download") {
|
|
35029
35747
|
harden(res, void 0, "text/plain");
|
|
@@ -35033,9 +35751,11 @@ function serveSharedDownload(cfg, token, kind, req, res) {
|
|
|
35033
35751
|
}
|
|
35034
35752
|
return notFoundPage(res);
|
|
35035
35753
|
}
|
|
35036
|
-
const ext = kind === "html" ? "html" : kind === "openapi" ? "openapi.json" : kind;
|
|
35037
35754
|
harden(res, void 0, result.contentType ?? "application/octet-stream");
|
|
35038
|
-
res.setHeader(
|
|
35755
|
+
res.setHeader(
|
|
35756
|
+
"content-disposition",
|
|
35757
|
+
`attachment; filename="${downloadFilename(kind, portalId, result.content)}"`
|
|
35758
|
+
);
|
|
35039
35759
|
res.statusCode = 200;
|
|
35040
35760
|
res.end(result.content);
|
|
35041
35761
|
}
|
|
@@ -35151,6 +35871,7 @@ var WEB_MUTATION_PATHS = /* @__PURE__ */ new Set([
|
|
|
35151
35871
|
"/web/projects/packs",
|
|
35152
35872
|
"/web/projects/packs/upload",
|
|
35153
35873
|
"/web/projects/packs/remove",
|
|
35874
|
+
"/web/projects/packs/adopt",
|
|
35154
35875
|
"/web/projects/policy/reconcile",
|
|
35155
35876
|
"/web/projects/producers",
|
|
35156
35877
|
"/web/projects/producers/remove",
|
|
@@ -36644,15 +37365,29 @@ async function runSurface(action, options = {}) {
|
|
|
36644
37365
|
if (format !== "native" && format !== "openapi") {
|
|
36645
37366
|
throw new WaironError(`Unknown format "${format}" (supported: native, openapi).`);
|
|
36646
37367
|
}
|
|
36647
|
-
|
|
37368
|
+
if (options.portal && format !== "openapi") {
|
|
37369
|
+
throw new WaironError("`--portal` selects one OpenAPI document and only applies to `--format openapi`.");
|
|
37370
|
+
}
|
|
37371
|
+
const result = exportSurface(audience, format, options.out, options.portal);
|
|
36648
37372
|
logger.success(
|
|
36649
37373
|
`Projected surface of "${result.snapshot.projectName}": ${result.snapshot.interfaces.length} interface(s), ${result.snapshot.types.length} type(s) at audience \u2265 ${audience}.`
|
|
36650
37374
|
);
|
|
36651
|
-
|
|
36652
|
-
|
|
37375
|
+
const written = result.writtenPaths ?? (result.writtenTo ? [result.writtenTo] : []);
|
|
37376
|
+
if (written.length === 1) {
|
|
37377
|
+
logger.info(`Written to ${written[0]}`);
|
|
37378
|
+
} else if (written.length > 1) {
|
|
37379
|
+
logger.info(`Written ${written.length} document(s) \u2014 one per portal:`);
|
|
37380
|
+
for (const p of written) logger.info(` ${p}`);
|
|
36653
37381
|
} else if (result.rendered) {
|
|
36654
37382
|
process.stdout.write(`${result.rendered}
|
|
36655
37383
|
`);
|
|
37384
|
+
} else if (result.renderedSet && result.renderedSet.length > 1) {
|
|
37385
|
+
logger.info(
|
|
37386
|
+
`This project publishes ${result.renderedSet.length} portals \u2014 pick one with \`--portal <id>\` (or use --out to write them all):`
|
|
37387
|
+
);
|
|
37388
|
+
for (const spec of result.renderedSet) {
|
|
37389
|
+
logger.info(` ${import_chalk19.default.cyan(spec.portalId)} \u2014 ${spec.name}`);
|
|
37390
|
+
}
|
|
36656
37391
|
} else {
|
|
36657
37392
|
for (const entry of result.snapshot.interfaces) {
|
|
36658
37393
|
logger.info(` ${import_chalk19.default.cyan(entry.id)} (${entry.type}, ${entry.audience}) \u2014 ${entry.methods.length} method(s)`);
|
|
@@ -36921,13 +37656,14 @@ mcpCmd.command("status").description("Show whether the wairon MCP server is regi
|
|
|
36921
37656
|
program.command("produce <target>").description("project the local project's specs to a producer target (notion | miro)").option("--page <id>", "parent page/board id in the target").option("--token <token>", "integration token (else env, else interactive prompt)").action(async (target, opts) => {
|
|
36922
37657
|
await runProduce(target, { page: opts.page, token: opts.token });
|
|
36923
37658
|
});
|
|
36924
|
-
program.command("surface <action>").description("public surface exchange: export | import | list | generate-children").option("--audience <level>", "export ceiling: project | department | instance | partner | external (default instance)").option("--format <fmt>", "export format: native | openapi (default native)").option("--out <path>", "export output path (else print)").option("--source <path>", "import: the surface document (native snapshot YAML or OpenAPI)").option("--origin <origin>", "import provenance: exchanged | authored (default authored)").action(async (action, opts) => {
|
|
37659
|
+
program.command("surface <action>").description("public surface exchange: export | import | list | generate-children").option("--audience <level>", "export ceiling: project | department | instance | partner | external (default instance)").option("--format <fmt>", "export format: native | openapi (default native)").option("--out <path>", "export output path (else print)").option("--portal <id>", "export: select one portal's OpenAPI spec (a multi-portal project renders one document per portal)").option("--source <path>", "import: the surface document (native snapshot YAML or OpenAPI)").option("--origin <origin>", "import provenance: exchanged | authored (default authored)").action(async (action, opts) => {
|
|
36925
37660
|
await runSurface(action, {
|
|
36926
37661
|
audience: opts.audience,
|
|
36927
37662
|
format: opts.format,
|
|
36928
37663
|
out: opts.out,
|
|
36929
37664
|
source: opts.source,
|
|
36930
|
-
origin: opts.origin
|
|
37665
|
+
origin: opts.origin,
|
|
37666
|
+
portal: opts.portal
|
|
36931
37667
|
});
|
|
36932
37668
|
});
|
|
36933
37669
|
program.command("serve").description("Run the wairon hosting server: HTTP MCP for many isolated projects + admin API").option("--host <host>", "data-plane bind host (default 0.0.0.0)").option("--port <port>", "data-plane port (default 8080)").option("--admin-host <host>", "admin-plane bind host (default 127.0.0.1)").option("--admin-port <port>", "admin-plane port (default 8081)").option("--data-dir <path>", "data root holding projects/ and auth/ (default WAIRON_DATA_DIR or ~/.wairon/data)").option("--no-auth", "disable data-plane auth (trusted networks only)").action(async (opts) => {
|