@cat-factory/integrations 0.166.22 → 0.168.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/modules/environments/EnvironmentProvisioningService.d.ts +36 -2
- package/dist/modules/environments/EnvironmentProvisioningService.d.ts.map +1 -1
- package/dist/modules/environments/EnvironmentProvisioningService.js +56 -1
- package/dist/modules/environments/EnvironmentProvisioningService.js.map +1 -1
- package/dist/modules/environments/environmentDiagnostics.d.ts +51 -0
- package/dist/modules/environments/environmentDiagnostics.d.ts.map +1 -0
- package/dist/modules/environments/environmentDiagnostics.js +352 -0
- package/dist/modules/environments/environmentDiagnostics.js.map +1 -0
- package/dist/modules/environments/environmentRemediation.d.ts +53 -0
- package/dist/modules/environments/environmentRemediation.d.ts.map +1 -0
- package/dist/modules/environments/environmentRemediation.js +72 -0
- package/dist/modules/environments/environmentRemediation.js.map +1 -0
- package/dist/modules/environments/environments.logic.d.ts +7 -0
- package/dist/modules/environments/environments.logic.d.ts.map +1 -1
- package/dist/modules/environments/environments.logic.js +28 -0
- package/dist/modules/environments/environments.logic.js.map +1 -1
- package/dist/modules/kubernetes/KubernetesEnvironmentProvider.d.ts +25 -2
- package/dist/modules/kubernetes/KubernetesEnvironmentProvider.d.ts.map +1 -1
- package/dist/modules/kubernetes/KubernetesEnvironmentProvider.js +106 -21
- package/dist/modules/kubernetes/KubernetesEnvironmentProvider.js.map +1 -1
- package/dist/modules/kubernetes/kubernetes-diagnostics.d.ts +31 -0
- package/dist/modules/kubernetes/kubernetes-diagnostics.d.ts.map +1 -0
- package/dist/modules/kubernetes/kubernetes-diagnostics.js +350 -0
- package/dist/modules/kubernetes/kubernetes-diagnostics.js.map +1 -0
- package/dist/modules/kubernetes/kubernetes-environment.logic.d.ts +8 -1
- package/dist/modules/kubernetes/kubernetes-environment.logic.d.ts.map +1 -1
- package/dist/modules/kubernetes/kubernetes-environment.logic.js +18 -4
- package/dist/modules/kubernetes/kubernetes-environment.logic.js.map +1 -1
- package/dist/modules/kubernetes/kubernetes.logic.d.ts +42 -2
- package/dist/modules/kubernetes/kubernetes.logic.d.ts.map +1 -1
- package/dist/modules/kubernetes/kubernetes.logic.js +106 -2
- package/dist/modules/kubernetes/kubernetes.logic.js.map +1 -1
- package/dist/modules/providers/OpenRouterCatalogService.d.ts.map +1 -1
- package/dist/modules/providers/OpenRouterCatalogService.js +2 -30
- package/dist/modules/providers/OpenRouterCatalogService.js.map +1 -1
- package/dist/modules/providers/openRouterModels.d.ts +12 -0
- package/dist/modules/providers/openRouterModels.d.ts.map +1 -0
- package/dist/modules/providers/openRouterModels.js +107 -0
- package/dist/modules/providers/openRouterModels.js.map +1 -0
- package/dist/modules/serviceCatalog/BackstageCatalogClient.d.ts +75 -0
- package/dist/modules/serviceCatalog/BackstageCatalogClient.d.ts.map +1 -0
- package/dist/modules/serviceCatalog/BackstageCatalogClient.js +410 -0
- package/dist/modules/serviceCatalog/BackstageCatalogClient.js.map +1 -0
- package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.d.ts +85 -0
- package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.d.ts.map +1 -0
- package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.js +177 -0
- package/dist/modules/serviceCatalog/ServiceCatalogConnectionService.js.map +1 -0
- package/dist/modules/serviceCatalog/backstage-catalog.logic.d.ts +173 -0
- package/dist/modules/serviceCatalog/backstage-catalog.logic.d.ts.map +1 -0
- package/dist/modules/serviceCatalog/backstage-catalog.logic.js +362 -0
- package/dist/modules/serviceCatalog/backstage-catalog.logic.js.map +1 -0
- package/dist/modules/serviceCatalog/serviceCatalogAuth.d.ts +42 -0
- package/dist/modules/serviceCatalog/serviceCatalogAuth.d.ts.map +1 -0
- package/dist/modules/serviceCatalog/serviceCatalogAuth.js +201 -0
- package/dist/modules/serviceCatalog/serviceCatalogAuth.js.map +1 -0
- package/dist/modules/shared/base64.d.ts +19 -0
- package/dist/modules/shared/base64.d.ts.map +1 -0
- package/dist/modules/shared/base64.js +75 -0
- package/dist/modules/shared/base64.js.map +1 -0
- package/dist/modules/tracker/TicketTrackerService.js +1 -1
- package/dist/modules/tracker/TicketTrackerService.js.map +1 -1
- package/package.json +4 -4
- package/dist/modules/tracker/base64.d.ts +0 -2
- package/dist/modules/tracker/base64.d.ts.map +0 -1
- package/dist/modules/tracker/base64.js +0 -18
- package/dist/modules/tracker/base64.js.map +0 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { DEFAULT_MAX_CATALOG_SERVICES, DEFAULT_SERVICE_CATALOG_FILTER, } from '@cat-factory/contracts';
|
|
2
|
+
import { createOrgSecretCipher, noopLogger, orgSecretRef } from '@cat-factory/kernel';
|
|
3
|
+
import { assertSafePublicUrl } from '../shared/url-guard.js';
|
|
4
|
+
import { BackstageCatalogClient } from './BackstageCatalogClient.js';
|
|
5
|
+
import { parseServiceCatalogAuth } from './serviceCatalogAuth.js';
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// The workspace's SERVICE CATALOG connection: connect, read, probe, disconnect, and build the
|
|
8
|
+
// client an import reads through.
|
|
9
|
+
//
|
|
10
|
+
// The credential bag is sealed and opened here and only here, so nothing else in the feature
|
|
11
|
+
// (the controller, the importer, the sweeps) ever holds plaintext. The cipher goes through
|
|
12
|
+
// `createOrgSecretCipher`, which makes a mothership-mode node open the row by NAMING it over the
|
|
13
|
+
// secret delegation rather than with a key it does not have.
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* The HKDF domain the service-catalog credential is sealed under.
|
|
17
|
+
*
|
|
18
|
+
* Its own domain rather than the documents or environments one, so a key rotation or a compromise
|
|
19
|
+
* scoped to one integration cannot open another's rows. Declared HERE, once, and imported by both
|
|
20
|
+
* runtime facades and by `SEALED_SECRET_SOURCES`: the value has to be the same in all three or a
|
|
21
|
+
* mothership-delegated unseal authenticates against a different derived key, and that drift is
|
|
22
|
+
* both silent and unrecoverable, which is not a property any copied literal should carry.
|
|
23
|
+
*/
|
|
24
|
+
export const SERVICE_CATALOG_CIPHER_INFO = 'cat-factory:service-catalog';
|
|
25
|
+
export class ServiceCatalogConnectionService {
|
|
26
|
+
deps;
|
|
27
|
+
log;
|
|
28
|
+
orgSecrets;
|
|
29
|
+
constructor(deps) {
|
|
30
|
+
this.deps = deps;
|
|
31
|
+
this.log = deps.logger ?? noopLogger;
|
|
32
|
+
this.orgSecrets = createOrgSecretCipher({
|
|
33
|
+
cipher: deps.secretCipher,
|
|
34
|
+
...(deps.secretDelegate ? { delegate: deps.secretDelegate } : {}),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The connection as the management surface sees it, or null when there is none.
|
|
39
|
+
*
|
|
40
|
+
* A tombstoned row answers NULL, exactly as `resolveClient` and the importer treat one. The
|
|
41
|
+
* repository's `get` deliberately still returns it, because a re-connect reads the row to keep
|
|
42
|
+
* the prior verdict and the original `connectedAt`; the disposal of a tombstone is the caller's,
|
|
43
|
+
* and every caller but that one wants "there is no connection". Returning it here would render a
|
|
44
|
+
* disconnected workspace as connected, with an "Import now" button that 404s.
|
|
45
|
+
*/
|
|
46
|
+
async get(workspaceId) {
|
|
47
|
+
const record = await this.deps.serviceCatalogConnectionRepository.get(workspaceId);
|
|
48
|
+
return record && record.deletedAt === null ? toWire(record) : null;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Connect or replace the workspace's connection.
|
|
52
|
+
*
|
|
53
|
+
* The base URL is validated BEFORE anything is sealed, so a typo or a blocked host is refused
|
|
54
|
+
* while the operator is still looking at the form rather than becoming a stored connection that
|
|
55
|
+
* fails on every import.
|
|
56
|
+
*/
|
|
57
|
+
async connect(workspaceId, input) {
|
|
58
|
+
const baseUrl = this.assertBaseUrl(input.baseUrl);
|
|
59
|
+
const now = this.deps.clock.now();
|
|
60
|
+
const existing = await this.deps.serviceCatalogConnectionRepository.get(workspaceId);
|
|
61
|
+
const record = {
|
|
62
|
+
workspaceId,
|
|
63
|
+
provider: input.provider ?? 'backstage',
|
|
64
|
+
baseUrl,
|
|
65
|
+
authMode: input.auth.mode,
|
|
66
|
+
credentialsCipher: await this.sealAuth(workspaceId, input.auth),
|
|
67
|
+
entityFilter: normalizeFilter(input.entityFilter),
|
|
68
|
+
includeApis: input.includeApis ?? true,
|
|
69
|
+
maxServices: input.maxServices ?? DEFAULT_MAX_CATALOG_SERVICES,
|
|
70
|
+
// The prior pass's verdict is KEPT rather than cleared. Re-entering a rotated token does not
|
|
71
|
+
// change what the last import found, and blanking it would present a workspace with a
|
|
72
|
+
// truncated estate as one that has never imported.
|
|
73
|
+
lastSyncedAt: existing?.lastSyncedAt ?? null,
|
|
74
|
+
lastSyncStatus: existing?.lastSyncStatus ?? null,
|
|
75
|
+
lastSyncMessage: existing?.lastSyncMessage ?? null,
|
|
76
|
+
createdAt: existing?.createdAt ?? now,
|
|
77
|
+
updatedAt: now,
|
|
78
|
+
deletedAt: null,
|
|
79
|
+
};
|
|
80
|
+
await this.deps.serviceCatalogConnectionRepository.upsert(record);
|
|
81
|
+
this.log.info('serviceCatalog.connected', {
|
|
82
|
+
workspaceId,
|
|
83
|
+
provider: record.provider,
|
|
84
|
+
authMode: record.authMode,
|
|
85
|
+
});
|
|
86
|
+
return toWire(record);
|
|
87
|
+
}
|
|
88
|
+
/** Forget the credential. Tombstoning what it imported is the importer's half, called beside. */
|
|
89
|
+
async disconnect(workspaceId) {
|
|
90
|
+
await this.deps.serviceCatalogConnectionRepository.softDelete(workspaceId, this.deps.clock.now());
|
|
91
|
+
this.log.info('serviceCatalog.disconnected', { workspaceId });
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Probe with the SUBMITTED credentials rather than the stored ones.
|
|
95
|
+
*
|
|
96
|
+
* Which is the point of the endpoint: an operator testing a connection is testing what they just
|
|
97
|
+
* typed, and probing the stored row would answer about the credential they are replacing.
|
|
98
|
+
*/
|
|
99
|
+
async probe(input) {
|
|
100
|
+
const baseUrl = this.assertBaseUrl(input.baseUrl);
|
|
101
|
+
return this.buildClient(baseUrl, input.auth).probe();
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* The client for a workspace's stored connection, or null when it has none.
|
|
105
|
+
*
|
|
106
|
+
* Wired as the `ResolveServiceCatalogClient` port the importer depends on. It REJECTS when a row
|
|
107
|
+
* exists whose bag will not open, because that is a deployment fault with its own remedy
|
|
108
|
+
* (re-enter the credential) and is not the same fact as a workspace that never connected a
|
|
109
|
+
* portal, which the importer treats as nothing to do.
|
|
110
|
+
*/
|
|
111
|
+
resolveClient = async (workspaceId) => {
|
|
112
|
+
const record = await this.deps.serviceCatalogConnectionRepository.get(workspaceId);
|
|
113
|
+
if (!record || record.deletedAt !== null)
|
|
114
|
+
return null;
|
|
115
|
+
const auth = await this.openAuth(record);
|
|
116
|
+
return this.buildClient(record.baseUrl, auth);
|
|
117
|
+
};
|
|
118
|
+
buildClient(baseUrl, auth) {
|
|
119
|
+
return new BackstageCatalogClient({
|
|
120
|
+
baseUrl,
|
|
121
|
+
auth,
|
|
122
|
+
clock: this.deps.clock,
|
|
123
|
+
logger: this.log,
|
|
124
|
+
...(this.deps.urlPolicy ? { urlPolicy: this.deps.urlPolicy } : {}),
|
|
125
|
+
...(this.deps.fetchImpl ? { fetchImpl: this.deps.fetchImpl } : {}),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
assertBaseUrl(baseUrl) {
|
|
129
|
+
const normalized = baseUrl.trim().replace(/\/+$/, '');
|
|
130
|
+
assertSafePublicUrl(normalized, {
|
|
131
|
+
subject: 'Service catalog',
|
|
132
|
+
label: 'base URL',
|
|
133
|
+
...(this.deps.urlPolicy ? { policy: this.deps.urlPolicy } : {}),
|
|
134
|
+
});
|
|
135
|
+
return normalized;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Seal the credential half of the submitted auth.
|
|
139
|
+
*
|
|
140
|
+
* `none` seals NOTHING and stores the empty string, rather than an envelope around an empty
|
|
141
|
+
* object. An unauthenticated portal has no secret, so a deployment whose `ENCRYPTION_KEY`
|
|
142
|
+
* drifted must keep working against it: sealing a placeholder would make the one mode with no
|
|
143
|
+
* credential the one that fails on a key problem.
|
|
144
|
+
*/
|
|
145
|
+
async sealAuth(workspaceId, auth) {
|
|
146
|
+
if (auth.mode === 'none')
|
|
147
|
+
return '';
|
|
148
|
+
return this.orgSecrets.encryptFor(orgSecretRef('service_catalog_connection', workspaceId), JSON.stringify(auth));
|
|
149
|
+
}
|
|
150
|
+
async openAuth(record) {
|
|
151
|
+
if (record.authMode === 'none')
|
|
152
|
+
return { mode: 'none' };
|
|
153
|
+
const plaintext = await this.orgSecrets.decryptFor(orgSecretRef('service_catalog_connection', record.workspaceId), record.credentialsCipher);
|
|
154
|
+
return parseServiceCatalogAuth(plaintext, record.authMode);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** The stored row's non-secret half. */
|
|
158
|
+
function toWire(record) {
|
|
159
|
+
return {
|
|
160
|
+
provider: record.provider,
|
|
161
|
+
baseUrl: record.baseUrl,
|
|
162
|
+
authMode: record.authMode,
|
|
163
|
+
entityFilter: record.entityFilter,
|
|
164
|
+
includeApis: record.includeApis,
|
|
165
|
+
maxServices: record.maxServices,
|
|
166
|
+
lastSyncedAt: record.lastSyncedAt,
|
|
167
|
+
lastSyncStatus: record.lastSyncStatus,
|
|
168
|
+
lastSyncMessage: record.lastSyncMessage,
|
|
169
|
+
connectedAt: record.createdAt,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/** An empty filter falls back to the default rather than importing the whole estate. */
|
|
173
|
+
function normalizeFilter(entityFilter) {
|
|
174
|
+
const terms = (entityFilter ?? []).map((term) => term.trim()).filter(Boolean);
|
|
175
|
+
return terms.length > 0 ? terms : [...DEFAULT_SERVICE_CATALOG_FILTER];
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=ServiceCatalogConnectionService.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ServiceCatalogConnectionService.js","sourceRoot":"","sources":["../../../src/modules/serviceCatalog/ServiceCatalogConnectionService.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import type { ApiContractFormat } from '@cat-factory/contracts';
|
|
2
|
+
import type { ServiceCatalogApi, ServiceCatalogEntry } from '@cat-factory/kernel';
|
|
3
|
+
/** How long a composed description may run: the catalog's own limit for the field. */
|
|
4
|
+
export declare const MAX_DESCRIPTION_CHARS = 20000;
|
|
5
|
+
/**
|
|
6
|
+
* How large one API definition may be and still be stored.
|
|
7
|
+
*
|
|
8
|
+
* The same ceiling a direct upload is held to, deliberately: a document past it is not served to
|
|
9
|
+
* an agent in any useful form (the contract render caps far lower and says what it dropped), and
|
|
10
|
+
* letting the import store what an upload would be refused would make the two supply routes
|
|
11
|
+
* disagree about what a contract is.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MAX_DEFINITION_CHARS = 1000000;
|
|
14
|
+
/** A Backstage entity, as much of it as this adapter reads. */
|
|
15
|
+
export interface BackstageEntity {
|
|
16
|
+
kind?: string;
|
|
17
|
+
metadata?: {
|
|
18
|
+
name?: string;
|
|
19
|
+
namespace?: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
description?: string;
|
|
22
|
+
tags?: string[];
|
|
23
|
+
etag?: string;
|
|
24
|
+
annotations?: Record<string, string>;
|
|
25
|
+
links?: {
|
|
26
|
+
url?: string;
|
|
27
|
+
title?: string;
|
|
28
|
+
}[];
|
|
29
|
+
};
|
|
30
|
+
spec?: {
|
|
31
|
+
type?: string;
|
|
32
|
+
lifecycle?: string;
|
|
33
|
+
owner?: string;
|
|
34
|
+
system?: string;
|
|
35
|
+
domain?: string;
|
|
36
|
+
definition?: string;
|
|
37
|
+
providesApis?: string[];
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
/** A parsed entity reference (`component:default/orders`). */
|
|
41
|
+
export interface BackstageEntityRef {
|
|
42
|
+
kind: string;
|
|
43
|
+
namespace: string;
|
|
44
|
+
name: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Parse one of the several shapes Backstage accepts for a reference, with `defaultKind` filling
|
|
48
|
+
* the one a compact reference omits.
|
|
49
|
+
*
|
|
50
|
+
* Compact references are the norm rather than an edge case: `spec.owner: payments` and
|
|
51
|
+
* `providesApis: [orders-api]` are what a hand-written `catalog-info.yaml` contains, and the
|
|
52
|
+
* kind is implied by the FIELD they appear in. Returns null when there is no name at all, so a
|
|
53
|
+
* malformed reference is reported rather than resolved to something plausible.
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseEntityRef(raw: string, defaultKind: string): BackstageEntityRef | null;
|
|
56
|
+
/** Render a reference in the canonical `kind:namespace/name` form the API addresses entities by. */
|
|
57
|
+
export declare function formatEntityRef(ref: BackstageEntityRef): string;
|
|
58
|
+
/**
|
|
59
|
+
* The canonical reference for an entity as it came back, or null when it carries no name.
|
|
60
|
+
*
|
|
61
|
+
* Kind AND namespace are lower-cased, exactly as {@link parseEntityRef} does to the references it
|
|
62
|
+
* reads out of `providesApis` and `spec.owner`. Both halves matter: an entity declaring
|
|
63
|
+
* `namespace: Payments` would otherwise be recorded as `component:Payments/orders` while every
|
|
64
|
+
* reference the platform builds for the same entity resolves to `payments/...`, so the stored
|
|
65
|
+
* provenance would address nothing and the sync's `sourcePath` comparison would be against a value
|
|
66
|
+
* the canonical form can never produce.
|
|
67
|
+
*/
|
|
68
|
+
export declare function entityRef(entity: BackstageEntity): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* The foundational-service id an entity is imported under: its name as a lower-kebab slug,
|
|
71
|
+
* prefixed with its namespace when that is not the default one.
|
|
72
|
+
*
|
|
73
|
+
* The namespace prefix is what keeps the id unique. Backstage namespaces exist precisely so two
|
|
74
|
+
* teams can both own an `api` component, and slugging on the name alone would collapse them onto
|
|
75
|
+
* one catalog entry, silently, and in favour of whichever the portal paged first. The `default`
|
|
76
|
+
* namespace is left off because it is the one every hand-written entity lands in, and prefixing
|
|
77
|
+
* it would put `default-` in front of an organisation's entire estate.
|
|
78
|
+
*
|
|
79
|
+
* Null when nothing slug-shaped survives (a name of only punctuation), which the caller reports
|
|
80
|
+
* as a skipped entity rather than importing under a generated id nobody can look up.
|
|
81
|
+
*/
|
|
82
|
+
export declare function serviceIdForEntity(entity: BackstageEntity): string | null;
|
|
83
|
+
/**
|
|
84
|
+
* A lower-kebab slug, or the empty string when the input holds nothing slug-shaped.
|
|
85
|
+
*
|
|
86
|
+
* Capped at the 64 characters the catalog's own id rule allows, and the cap is a plain prefix by
|
|
87
|
+
* design: a truncated slug still has to be STABLE across imports, and any cleverer strategy (a
|
|
88
|
+
* hash suffix, dropping interior words) would produce a different id the day a name grows.
|
|
89
|
+
*/
|
|
90
|
+
export declare function slugify(value: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* The format an API entity's declared `spec.type` maps to, or null when the platform serves none
|
|
93
|
+
* for it.
|
|
94
|
+
*
|
|
95
|
+
* Null rather than a fallback, and that is the whole point of the function. Backstage's API type
|
|
96
|
+
* is an open vocabulary (`trpc`, `sql`, `soap` and anything an organisation invents all appear),
|
|
97
|
+
* and storing one of those AS OpenAPI would produce a contract that fails the OpenAPI parse and
|
|
98
|
+
* lists zero operations, which reads to an Architect as a fully-specified service that offers no
|
|
99
|
+
* endpoints. The importer reports a null as a skipped interface instead, which says the opposite
|
|
100
|
+
* and is true.
|
|
101
|
+
*/
|
|
102
|
+
export declare function apiContractFormatForType(type: string | undefined): ApiContractFormat | null;
|
|
103
|
+
/**
|
|
104
|
+
* Map one catalog entity onto the platform's neutral entry, with `apis` supplied separately
|
|
105
|
+
* (they are separate entities, fetched in one batch rather than one request per service).
|
|
106
|
+
*
|
|
107
|
+
* Returns null when the entity yields no usable identity, which the caller counts as skipped.
|
|
108
|
+
*/
|
|
109
|
+
export declare function toServiceCatalogEntry(entity: BackstageEntity, apis: ServiceCatalogApi[]): ServiceCatalogEntry | null;
|
|
110
|
+
/**
|
|
111
|
+
* The one-line relevance signal.
|
|
112
|
+
*
|
|
113
|
+
* Falls back to the facts the entity DOES carry rather than to a placeholder, because a catalog
|
|
114
|
+
* whose every summary reads "no description" is a catalog a reader stops reading. A component's
|
|
115
|
+
* type and owner are the two things a portal always has, and together they are a real answer to
|
|
116
|
+
* "is this the service I am looking for".
|
|
117
|
+
*/
|
|
118
|
+
export declare function entitySummary(entity: BackstageEntity): string;
|
|
119
|
+
/**
|
|
120
|
+
* The composed description: the portal's structured facts as labelled lines, then the entity's
|
|
121
|
+
* own prose.
|
|
122
|
+
*
|
|
123
|
+
* The ORDER is the load-bearing part. Ownership is what a triage reader needs first and it is
|
|
124
|
+
* also what a cap must never drop, so the labelled facts come before the free text and the
|
|
125
|
+
* truncation falls on the prose. A description that lost its owner line to a long README excerpt
|
|
126
|
+
* would be the one field this composition exists to guarantee.
|
|
127
|
+
*/
|
|
128
|
+
export declare function composeDescription(entity: BackstageEntity): string;
|
|
129
|
+
/**
|
|
130
|
+
* A readable owner: the reference's NAME, with the full reference beside it.
|
|
131
|
+
*
|
|
132
|
+
* Both halves, because they answer different questions. `payments` is what a human recognises
|
|
133
|
+
* and what an agent can put in a report; `group:default/payments` is what someone can look up in
|
|
134
|
+
* the portal. Rendering only the reference makes the estate read like machine output, and
|
|
135
|
+
* rendering only the name loses which namespace (and which kind of owner) it belongs to.
|
|
136
|
+
*/
|
|
137
|
+
export declare function ownerLabel(owner: string | undefined): string | null;
|
|
138
|
+
/**
|
|
139
|
+
* The capability tags an imported service carries: the entity's own tags plus its declared type.
|
|
140
|
+
*
|
|
141
|
+
* A RESERVED platform tag is dropped rather than imported, and that is a rule rather than a cap.
|
|
142
|
+
* `asset-storage` and `generation-context` are spellings the platform assigns meaning to (the
|
|
143
|
+
* first makes a service selectable as a binary-output step's storage target), so accepting one
|
|
144
|
+
* from an external portal would let whoever edits a `catalog-info.yaml` enrol a component into a
|
|
145
|
+
* platform capability that nobody at this deployment chose. A NEAR-MISS of a reserved tag goes
|
|
146
|
+
* the same way, since importing it would put a tag in the catalog that reads as the reserved one
|
|
147
|
+
* and behaves as nothing.
|
|
148
|
+
*/
|
|
149
|
+
export declare function entityCapabilities(entity: BackstageEntity): string[];
|
|
150
|
+
/** The API entity references one component declares, canonicalised and de-duplicated. */
|
|
151
|
+
export declare function providedApiRefs(entity: BackstageEntity): string[];
|
|
152
|
+
/**
|
|
153
|
+
* Map one API entity onto a neutral interface, or null when it carries nothing storable.
|
|
154
|
+
*
|
|
155
|
+
* The three null cases are deliberately not distinguished HERE (an unusable type, an empty
|
|
156
|
+
* definition and an oversized one all become one skipped interface), because the caller reports
|
|
157
|
+
* a count and every one of them has the same remedy at the portal: give the API entity a
|
|
158
|
+
* definition this platform can serve. What must not happen is any of them being stored anyway.
|
|
159
|
+
*/
|
|
160
|
+
export declare function toServiceCatalogApi(entity: BackstageEntity): ServiceCatalogApi | null;
|
|
161
|
+
/**
|
|
162
|
+
* The `fields` projection the LIST request asks for.
|
|
163
|
+
*
|
|
164
|
+
* Naming the fields is what keeps a large estate's listing cheap: without it Backstage returns
|
|
165
|
+
* every entity in full, relations included, and an API entity's `spec.definition` can be
|
|
166
|
+
* hundreds of kilobytes. `spec.definition` is deliberately absent here: the definitions are
|
|
167
|
+
* read in the second, batched request, for only the APIs the imported components actually
|
|
168
|
+
* declare.
|
|
169
|
+
*/
|
|
170
|
+
export declare const CATALOG_LIST_FIELDS: readonly string[];
|
|
171
|
+
/** The `fields` projection the batched API-entity request asks for: the list plus definitions. */
|
|
172
|
+
export declare const API_FETCH_FIELDS: readonly string[];
|
|
173
|
+
//# sourceMappingURL=backstage-catalog.logic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backstage-catalog.logic.d.ts","sourceRoot":"","sources":["../../../src/modules/serviceCatalog/backstage-catalog.logic.ts"],"names":[],"mappings":"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"}
|