@contentful/experience-design-system-cli 2.32.1-dev-build-2277e1e.0 → 2.32.1-dev-build-1c6a348.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/package.json +1 -1
- package/dist/src/api/clients/abstract-api-client.d.ts +24 -0
- package/dist/src/api/clients/cma-client.d.ts +8 -0
- package/dist/src/api/clients/edsi-client.d.ts +44 -0
- package/dist/src/api/index.d.ts +10 -0
- package/dist/src/apply/api-client.d.ts +4 -68
- package/dist/src/errors/api-error.d.ts +6 -0
- package/dist/src/helpers/http.d.ts +4 -0
- package/dist/src/helpers/url.d.ts +4 -0
- package/dist/src/host-utils.d.ts +1 -4
- package/dist/src/import/orchestrator.d.ts +9 -0
- package/dist/src/index.js +237 -78
- package/dist/src/interfaces/clients/cma-client-options.d.ts +6 -0
- package/dist/src/interfaces/clients/edsi-client-options.d.ts +12 -0
- package/dist/src/interfaces/clients/poll-options.d.ts +7 -0
- package/dist/src/interfaces/responses/existing-component-entity.d.ts +8 -0
- package/dist/src/interfaces/responses/existing-entities-response.d.ts +6 -0
- package/dist/src/interfaces/responses/existing-token-entity.d.ts +6 -0
- package/dist/src/session/db.d.ts +1 -1
- package/package.json +6 -6
package/dist/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface AbstractApiClientOptions {
|
|
2
|
+
host?: string;
|
|
3
|
+
cmaToken: string;
|
|
4
|
+
spaceId: string;
|
|
5
|
+
environmentId: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Shared base for CmaApiClient + EdsiApiClient. Owns the fields, URL
|
|
9
|
+
* helpers, and the /users/me validateToken flow that both clients need.
|
|
10
|
+
* Subclasses override validateToken() when they need to add
|
|
11
|
+
* service-specific follow-ups (e.g. EdsiApiClient calls checkPreflight()
|
|
12
|
+
* after the base check).
|
|
13
|
+
*/
|
|
14
|
+
export declare abstract class AbstractApiClient {
|
|
15
|
+
protected readonly host: string;
|
|
16
|
+
protected readonly token: string;
|
|
17
|
+
protected readonly spaceId: string;
|
|
18
|
+
protected readonly environmentId: string;
|
|
19
|
+
constructor(opts: AbstractApiClientOptions);
|
|
20
|
+
protected base(): string;
|
|
21
|
+
protected buildHeaders(): Record<string, string>;
|
|
22
|
+
validateToken(): Promise<void>;
|
|
23
|
+
protected onValidateTokenResponse(_res: Response): void;
|
|
24
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AbstractApiClient } from './abstract-api-client.js';
|
|
2
|
+
import type { CmaClientOptions } from '../../interfaces/clients/cma-client-options.js';
|
|
3
|
+
import type { ExistingEntitiesResponse } from '../../interfaces/responses/existing-entities-response.js';
|
|
4
|
+
export declare class CmaApiClient extends AbstractApiClient {
|
|
5
|
+
constructor(opts: CmaClientOptions);
|
|
6
|
+
listExistingEntities(): Promise<ExistingEntitiesResponse>;
|
|
7
|
+
private listAll;
|
|
8
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ManifestPayload, ServerPreviewResponse, ApplyOperationResponse, BreakingChange } from '@contentful/experience-design-system-types';
|
|
2
|
+
import { AbstractApiClient } from './abstract-api-client.js';
|
|
3
|
+
import type { EdsiClientOptions } from '../../interfaces/clients/edsi-client-options.js';
|
|
4
|
+
import type { PollOptions } from '../../interfaces/clients/poll-options.js';
|
|
5
|
+
export declare const DEFAULT_HOST = "https://api.contentful.com";
|
|
6
|
+
export declare const PREVIEW_ERROR_PREFIX = "preview failed:";
|
|
7
|
+
export declare const APPLY_ERROR_PREFIX = "apply failed:";
|
|
8
|
+
export declare const VALIDATION_FAILED_CODE = "\"ValidationFailed\"";
|
|
9
|
+
export interface PreviewValidationError {
|
|
10
|
+
componentName: string;
|
|
11
|
+
path: string;
|
|
12
|
+
message: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Parse the JSON body of a 422 from `previewImport()` into structured
|
|
16
|
+
* per-component validation errors. Returns [] for any malformed input
|
|
17
|
+
* so callers can fall back to the generic error path without try/catch.
|
|
18
|
+
*
|
|
19
|
+
* Path shape: `manifest:components/<Name>/$slots/<key>` or
|
|
20
|
+
* `manifest:components/<Name>/$properties/<key>`. Only the component
|
|
21
|
+
* name is extracted today; `path` and `message` are kept verbatim so
|
|
22
|
+
* future surfaces (debug logging, headless retry in SP-4) can render
|
|
23
|
+
* the field-level detail.
|
|
24
|
+
*/
|
|
25
|
+
export declare function parsePreviewValidationErrors(body: string): PreviewValidationError[];
|
|
26
|
+
export declare function sanitizeBreakingChanges(raw: unknown): BreakingChange[];
|
|
27
|
+
export declare class EdsiApiClient extends AbstractApiClient {
|
|
28
|
+
private retry;
|
|
29
|
+
private lastRequestId?;
|
|
30
|
+
getLastRequestId(): string | undefined;
|
|
31
|
+
private noteRequestId;
|
|
32
|
+
protected onValidateTokenResponse(res: Response): void;
|
|
33
|
+
constructor(opts: EdsiClientOptions);
|
|
34
|
+
private headers;
|
|
35
|
+
private requestWithRetry;
|
|
36
|
+
validateToken(): Promise<void>;
|
|
37
|
+
checkPreflight(): Promise<void>;
|
|
38
|
+
previewImport(manifest: ManifestPayload, allowDeletions?: boolean): Promise<ServerPreviewResponse>;
|
|
39
|
+
applyImport(manifest: ManifestPayload, options: {
|
|
40
|
+
acknowledgeBreakingChanges: boolean;
|
|
41
|
+
allowDeletions?: boolean;
|
|
42
|
+
}): Promise<ApplyOperationResponse>;
|
|
43
|
+
pollOperation(operationId: string, opts?: PollOptions): Promise<ApplyOperationResponse>;
|
|
44
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export { CmaApiClient } from './clients/cma-client.js';
|
|
2
|
+
export { EdsiApiClient, DEFAULT_HOST, PREVIEW_ERROR_PREFIX, APPLY_ERROR_PREFIX, VALIDATION_FAILED_CODE, parsePreviewValidationErrors, sanitizeBreakingChanges, } from './clients/edsi-client.js';
|
|
3
|
+
export type { PreviewValidationError } from './clients/edsi-client.js';
|
|
4
|
+
export { ApiError } from '../errors/api-error.js';
|
|
5
|
+
export type { CmaClientOptions } from '../interfaces/clients/cma-client-options.js';
|
|
6
|
+
export type { EdsiClientOptions } from '../interfaces/clients/edsi-client-options.js';
|
|
7
|
+
export type { PollOptions } from '../interfaces/clients/poll-options.js';
|
|
8
|
+
export type { ExistingComponentEntity } from '../interfaces/responses/existing-component-entity.js';
|
|
9
|
+
export type { ExistingTokenEntity } from '../interfaces/responses/existing-token-entity.js';
|
|
10
|
+
export type { ExistingEntitiesResponse } from '../interfaces/responses/existing-entities-response.js';
|
|
@@ -1,68 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export declare const VALIDATION_FAILED_CODE = "\"ValidationFailed\"";
|
|
6
|
-
export interface ApiClientOptions {
|
|
7
|
-
host?: string;
|
|
8
|
-
cmaToken: string;
|
|
9
|
-
spaceId: string;
|
|
10
|
-
environmentId: string;
|
|
11
|
-
retry?: {
|
|
12
|
-
maxAttempts?: number;
|
|
13
|
-
initialDelayMs?: number;
|
|
14
|
-
maxDelayMs?: number;
|
|
15
|
-
sleep?: (delayMs: number) => Promise<void>;
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
export declare class ApiError extends Error {
|
|
19
|
-
readonly status: number;
|
|
20
|
-
readonly body: string;
|
|
21
|
-
readonly guidance?: string | undefined;
|
|
22
|
-
constructor(message: string, status: number, body: string, guidance?: string | undefined);
|
|
23
|
-
}
|
|
24
|
-
export interface PreviewValidationError {
|
|
25
|
-
componentName: string;
|
|
26
|
-
path: string;
|
|
27
|
-
message: string;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Parse the JSON body of a 422 from `previewImport()` into structured
|
|
31
|
-
* per-component validation errors. Returns [] for any malformed input
|
|
32
|
-
* so callers can fall back to the generic error path without try/catch.
|
|
33
|
-
*
|
|
34
|
-
* Path shape: `manifest:components/<Name>/$slots/<key>` or
|
|
35
|
-
* `manifest:components/<Name>/$properties/<key>`. Only the component
|
|
36
|
-
* name is extracted today; `path` and `message` are kept verbatim so
|
|
37
|
-
* future surfaces (debug logging, headless retry in SP-4) can render
|
|
38
|
-
* the field-level detail.
|
|
39
|
-
*/
|
|
40
|
-
export declare function parsePreviewValidationErrors(body: string): PreviewValidationError[];
|
|
41
|
-
export declare function sanitizeBreakingChanges(raw: unknown): BreakingChange[];
|
|
42
|
-
export declare class ImportApiClient {
|
|
43
|
-
private host;
|
|
44
|
-
private token;
|
|
45
|
-
private spaceId;
|
|
46
|
-
private environmentId;
|
|
47
|
-
private retry;
|
|
48
|
-
private lastRequestId?;
|
|
49
|
-
getLastRequestId(): string | undefined;
|
|
50
|
-
private noteRequestId;
|
|
51
|
-
constructor(opts: ApiClientOptions);
|
|
52
|
-
private base;
|
|
53
|
-
private headers;
|
|
54
|
-
private requestWithRetry;
|
|
55
|
-
validateToken(): Promise<void>;
|
|
56
|
-
checkPreflight(): Promise<void>;
|
|
57
|
-
previewImport(manifest: ManifestPayload, allowDeletions?: boolean): Promise<ServerPreviewResponse>;
|
|
58
|
-
applyImport(manifest: ManifestPayload, options: {
|
|
59
|
-
acknowledgeBreakingChanges: boolean;
|
|
60
|
-
allowDeletions?: boolean;
|
|
61
|
-
}): Promise<ApplyOperationResponse>;
|
|
62
|
-
pollOperation(operationId: string, opts?: {
|
|
63
|
-
intervalMs?: number;
|
|
64
|
-
maxIntervalMs?: number;
|
|
65
|
-
maxAttempts?: number;
|
|
66
|
-
onProgress?: (op: ApplyOperationResponse) => void;
|
|
67
|
-
}): Promise<ApplyOperationResponse>;
|
|
68
|
-
}
|
|
1
|
+
export { EdsiApiClient, EdsiApiClient as ImportApiClient, DEFAULT_HOST, PREVIEW_ERROR_PREFIX, APPLY_ERROR_PREFIX, VALIDATION_FAILED_CODE, parsePreviewValidationErrors, sanitizeBreakingChanges, } from '../api/clients/edsi-client.js';
|
|
2
|
+
export type { PreviewValidationError } from '../api/clients/edsi-client.js';
|
|
3
|
+
export { ApiError } from '../errors/api-error.js';
|
|
4
|
+
export type { EdsiClientOptions as ApiClientOptions } from '../interfaces/clients/edsi-client-options.js';
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const DEFAULT_API_HOST = "https://api.contentful.com";
|
|
2
|
+
export declare const DEFAULT_CONFIGURED_HOST = "api.contentful.com";
|
|
3
|
+
export declare function toConfiguredHost(host?: string): string | undefined;
|
|
4
|
+
export declare function toApiHost(host?: string): string;
|
package/dist/src/host-utils.d.ts
CHANGED
|
@@ -1,4 +1 @@
|
|
|
1
|
-
export
|
|
2
|
-
export declare const DEFAULT_CONFIGURED_HOST = "api.contentful.com";
|
|
3
|
-
export declare function toConfiguredHost(host?: string): string | undefined;
|
|
4
|
-
export declare function toApiHost(host?: string): string;
|
|
1
|
+
export { DEFAULT_API_HOST, DEFAULT_CONFIGURED_HOST, toConfiguredHost, toApiHost } from './helpers/url.js';
|
|
@@ -28,6 +28,15 @@ export interface PipelineOptions {
|
|
|
28
28
|
/** When true, auto-reject cycle participants and retry push instead of surfacing an error. */
|
|
29
29
|
autoRejectCycles?: boolean;
|
|
30
30
|
allowDeletions?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Fetch existing Components + DesignTokens from the target space+env
|
|
33
|
+
* and persist them under outDir/.existing-entities.json so downstream
|
|
34
|
+
* agent steps can consume them as context. Requires spaceId +
|
|
35
|
+
* environmentId + cmaToken; skipped with a warning otherwise. No-op
|
|
36
|
+
* for the agent flow in this PR — the file is written but nothing
|
|
37
|
+
* reads it yet.
|
|
38
|
+
*/
|
|
39
|
+
withSpaceContext?: boolean;
|
|
31
40
|
compositionMode?: CompositionMode;
|
|
32
41
|
compositionMap?: string;
|
|
33
42
|
compositionAgent?: boolean;
|
package/dist/src/index.js
CHANGED
|
@@ -12603,7 +12603,7 @@ var init_src3 = __esm({
|
|
|
12603
12603
|
}
|
|
12604
12604
|
});
|
|
12605
12605
|
|
|
12606
|
-
// packages/experience-design-system-cli/src/
|
|
12606
|
+
// packages/experience-design-system-cli/src/helpers/url.ts
|
|
12607
12607
|
function trimTrailingSlashes(value) {
|
|
12608
12608
|
return value.replace(/\/+$/, "");
|
|
12609
12609
|
}
|
|
@@ -12626,8 +12626,8 @@ function toApiHost(host) {
|
|
|
12626
12626
|
return `https://${normalized}`;
|
|
12627
12627
|
}
|
|
12628
12628
|
var DEFAULT_API_HOST, DEFAULT_CONFIGURED_HOST;
|
|
12629
|
-
var
|
|
12630
|
-
"packages/experience-design-system-cli/src/
|
|
12629
|
+
var init_url = __esm({
|
|
12630
|
+
"packages/experience-design-system-cli/src/helpers/url.ts"() {
|
|
12631
12631
|
"use strict";
|
|
12632
12632
|
DEFAULT_API_HOST = "https://api.contentful.com";
|
|
12633
12633
|
DEFAULT_CONFIGURED_HOST = "api.contentful.com";
|
|
@@ -12834,7 +12834,99 @@ var init_user_agent = __esm({
|
|
|
12834
12834
|
}
|
|
12835
12835
|
});
|
|
12836
12836
|
|
|
12837
|
-
// packages/experience-design-system-cli/src/
|
|
12837
|
+
// packages/experience-design-system-cli/src/helpers/http.ts
|
|
12838
|
+
function buildHeaders(token) {
|
|
12839
|
+
return {
|
|
12840
|
+
Authorization: `Bearer ${token}`,
|
|
12841
|
+
"Content-Type": "application/json",
|
|
12842
|
+
"X-Contentful-User-Agent": buildUserAgent()
|
|
12843
|
+
};
|
|
12844
|
+
}
|
|
12845
|
+
async function request(url, options) {
|
|
12846
|
+
const init = {
|
|
12847
|
+
method: options.method ?? "GET",
|
|
12848
|
+
headers: buildHeaders(options.token)
|
|
12849
|
+
};
|
|
12850
|
+
if (options.body) init.body = options.body;
|
|
12851
|
+
return fetch(url, init);
|
|
12852
|
+
}
|
|
12853
|
+
var init_http = __esm({
|
|
12854
|
+
"packages/experience-design-system-cli/src/helpers/http.ts"() {
|
|
12855
|
+
"use strict";
|
|
12856
|
+
init_user_agent();
|
|
12857
|
+
}
|
|
12858
|
+
});
|
|
12859
|
+
|
|
12860
|
+
// packages/experience-design-system-cli/src/errors/api-error.ts
|
|
12861
|
+
var ERROR_BODY_LOG_CAP, ApiError;
|
|
12862
|
+
var init_api_error = __esm({
|
|
12863
|
+
"packages/experience-design-system-cli/src/errors/api-error.ts"() {
|
|
12864
|
+
"use strict";
|
|
12865
|
+
ERROR_BODY_LOG_CAP = 16384;
|
|
12866
|
+
ApiError = class extends Error {
|
|
12867
|
+
constructor(message, status, body, guidance) {
|
|
12868
|
+
super(message);
|
|
12869
|
+
this.status = status;
|
|
12870
|
+
this.body = body;
|
|
12871
|
+
this.guidance = guidance;
|
|
12872
|
+
if (body) {
|
|
12873
|
+
const trimmed = body.length > ERROR_BODY_LOG_CAP ? body.slice(0, ERROR_BODY_LOG_CAP) + "\u2026" : body;
|
|
12874
|
+
this.message = `${message}
|
|
12875
|
+
${trimmed}`;
|
|
12876
|
+
}
|
|
12877
|
+
}
|
|
12878
|
+
status;
|
|
12879
|
+
body;
|
|
12880
|
+
guidance;
|
|
12881
|
+
};
|
|
12882
|
+
}
|
|
12883
|
+
});
|
|
12884
|
+
|
|
12885
|
+
// packages/experience-design-system-cli/src/api/clients/abstract-api-client.ts
|
|
12886
|
+
var AbstractApiClient;
|
|
12887
|
+
var init_abstract_api_client = __esm({
|
|
12888
|
+
"packages/experience-design-system-cli/src/api/clients/abstract-api-client.ts"() {
|
|
12889
|
+
"use strict";
|
|
12890
|
+
init_url();
|
|
12891
|
+
init_http();
|
|
12892
|
+
init_api_error();
|
|
12893
|
+
AbstractApiClient = class {
|
|
12894
|
+
host;
|
|
12895
|
+
token;
|
|
12896
|
+
spaceId;
|
|
12897
|
+
environmentId;
|
|
12898
|
+
constructor(opts) {
|
|
12899
|
+
this.host = toApiHost(opts.host);
|
|
12900
|
+
this.token = opts.cmaToken;
|
|
12901
|
+
this.spaceId = opts.spaceId;
|
|
12902
|
+
this.environmentId = opts.environmentId;
|
|
12903
|
+
}
|
|
12904
|
+
base() {
|
|
12905
|
+
return `${this.host}/spaces/${this.spaceId}/environments/${this.environmentId}`;
|
|
12906
|
+
}
|
|
12907
|
+
buildHeaders() {
|
|
12908
|
+
return buildHeaders(this.token);
|
|
12909
|
+
}
|
|
12910
|
+
async validateToken() {
|
|
12911
|
+
const url = `${this.host}/users/me`;
|
|
12912
|
+
const res = await request(url, { token: this.token });
|
|
12913
|
+
this.onValidateTokenResponse(res);
|
|
12914
|
+
if (res.status === 401) {
|
|
12915
|
+
throw new ApiError("CMA token is invalid or revoked", res.status, await res.text());
|
|
12916
|
+
}
|
|
12917
|
+
if (!res.ok) {
|
|
12918
|
+
throw new ApiError(`unexpected error validating token: ${res.status}`, res.status, await res.text());
|
|
12919
|
+
}
|
|
12920
|
+
}
|
|
12921
|
+
// Hook for subclasses to observe the raw /users/me response (e.g. capture
|
|
12922
|
+
// x-contentful-request-id). Default is a no-op.
|
|
12923
|
+
onValidateTokenResponse(_res) {
|
|
12924
|
+
}
|
|
12925
|
+
};
|
|
12926
|
+
}
|
|
12927
|
+
});
|
|
12928
|
+
|
|
12929
|
+
// packages/experience-design-system-cli/src/api/clients/edsi-client.ts
|
|
12838
12930
|
function defaultSleep(delayMs) {
|
|
12839
12931
|
return new Promise((resolve27) => setTimeout(resolve27, delayMs));
|
|
12840
12932
|
}
|
|
@@ -12917,48 +13009,20 @@ function isApsDenialBody(body) {
|
|
|
12917
13009
|
return false;
|
|
12918
13010
|
}
|
|
12919
13011
|
}
|
|
12920
|
-
|
|
12921
|
-
|
|
12922
|
-
|
|
12923
|
-
"Content-Type": "application/json",
|
|
12924
|
-
"X-Contentful-User-Agent": buildUserAgent()
|
|
12925
|
-
};
|
|
12926
|
-
const init = {
|
|
12927
|
-
method: options.method ?? "GET",
|
|
12928
|
-
headers
|
|
12929
|
-
};
|
|
12930
|
-
if (options.body) init.body = options.body;
|
|
12931
|
-
return fetch(url, init);
|
|
12932
|
-
}
|
|
12933
|
-
var PREVIEW_ERROR_PREFIX, APPLY_ERROR_PREFIX, VALIDATION_FAILED_CODE, MAX_RETRY_AFTER_MS, ERROR_BODY_LOG_CAP, ApiError, COMPONENT_PATH_PREFIX, PROPERTY_BREAKING_REASONS, SLOT_BREAKING_REASONS, ImportApiClient;
|
|
12934
|
-
var init_api_client = __esm({
|
|
12935
|
-
"packages/experience-design-system-cli/src/apply/api-client.ts"() {
|
|
13012
|
+
var PREVIEW_ERROR_PREFIX, APPLY_ERROR_PREFIX, VALIDATION_FAILED_CODE, MAX_RETRY_AFTER_MS, COMPONENT_PATH_PREFIX, PROPERTY_BREAKING_REASONS, SLOT_BREAKING_REASONS, EdsiApiClient;
|
|
13013
|
+
var init_edsi_client = __esm({
|
|
13014
|
+
"packages/experience-design-system-cli/src/api/clients/edsi-client.ts"() {
|
|
12936
13015
|
"use strict";
|
|
12937
13016
|
init_src3();
|
|
12938
|
-
|
|
13017
|
+
init_url();
|
|
12939
13018
|
init_debug_logger();
|
|
12940
|
-
|
|
13019
|
+
init_http();
|
|
13020
|
+
init_api_error();
|
|
13021
|
+
init_abstract_api_client();
|
|
12941
13022
|
PREVIEW_ERROR_PREFIX = "preview failed:";
|
|
12942
13023
|
APPLY_ERROR_PREFIX = "apply failed:";
|
|
12943
13024
|
VALIDATION_FAILED_CODE = '"ValidationFailed"';
|
|
12944
13025
|
MAX_RETRY_AFTER_MS = 6e4;
|
|
12945
|
-
ERROR_BODY_LOG_CAP = 16384;
|
|
12946
|
-
ApiError = class extends Error {
|
|
12947
|
-
constructor(message, status, body, guidance) {
|
|
12948
|
-
super(message);
|
|
12949
|
-
this.status = status;
|
|
12950
|
-
this.body = body;
|
|
12951
|
-
this.guidance = guidance;
|
|
12952
|
-
if (body) {
|
|
12953
|
-
const trimmed = body.length > ERROR_BODY_LOG_CAP ? body.slice(0, ERROR_BODY_LOG_CAP) + "\u2026" : body;
|
|
12954
|
-
this.message = `${message}
|
|
12955
|
-
${trimmed}`;
|
|
12956
|
-
}
|
|
12957
|
-
}
|
|
12958
|
-
status;
|
|
12959
|
-
body;
|
|
12960
|
-
guidance;
|
|
12961
|
-
};
|
|
12962
13026
|
COMPONENT_PATH_PREFIX = "manifest:components/";
|
|
12963
13027
|
PROPERTY_BREAKING_REASONS = /* @__PURE__ */ new Set([
|
|
12964
13028
|
"removed",
|
|
@@ -12967,11 +13031,7 @@ ${trimmed}`;
|
|
|
12967
13031
|
"validation_narrowed"
|
|
12968
13032
|
]);
|
|
12969
13033
|
SLOT_BREAKING_REASONS = /* @__PURE__ */ new Set(["slot_removed", "slot_allowed_components_narrowed"]);
|
|
12970
|
-
|
|
12971
|
-
host;
|
|
12972
|
-
token;
|
|
12973
|
-
spaceId;
|
|
12974
|
-
environmentId;
|
|
13034
|
+
EdsiApiClient = class extends AbstractApiClient {
|
|
12975
13035
|
retry;
|
|
12976
13036
|
lastRequestId;
|
|
12977
13037
|
getLastRequestId() {
|
|
@@ -12981,11 +13041,11 @@ ${trimmed}`;
|
|
|
12981
13041
|
const requestId = response.headers.get("x-contentful-request-id");
|
|
12982
13042
|
if (requestId) this.lastRequestId = requestId;
|
|
12983
13043
|
}
|
|
13044
|
+
onValidateTokenResponse(res) {
|
|
13045
|
+
this.noteRequestId(res);
|
|
13046
|
+
}
|
|
12984
13047
|
constructor(opts) {
|
|
12985
|
-
|
|
12986
|
-
this.token = opts.cmaToken;
|
|
12987
|
-
this.spaceId = opts.spaceId;
|
|
12988
|
-
this.environmentId = opts.environmentId;
|
|
13048
|
+
super(opts);
|
|
12989
13049
|
const initialDelayMs = Math.max(0, opts.retry?.initialDelayMs ?? 250);
|
|
12990
13050
|
this.retry = {
|
|
12991
13051
|
maxAttempts: Math.max(1, Math.floor(opts.retry?.maxAttempts ?? 3)),
|
|
@@ -12994,15 +13054,8 @@ ${trimmed}`;
|
|
|
12994
13054
|
sleep: opts.retry?.sleep ?? defaultSleep
|
|
12995
13055
|
};
|
|
12996
13056
|
}
|
|
12997
|
-
base() {
|
|
12998
|
-
return `${this.host}/spaces/${this.spaceId}/environments/${this.environmentId}`;
|
|
12999
|
-
}
|
|
13000
13057
|
headers() {
|
|
13001
|
-
return
|
|
13002
|
-
Authorization: `Bearer ${this.token}`,
|
|
13003
|
-
"Content-Type": "application/json",
|
|
13004
|
-
"X-Contentful-User-Agent": buildUserAgent()
|
|
13005
|
-
};
|
|
13058
|
+
return this.buildHeaders();
|
|
13006
13059
|
}
|
|
13007
13060
|
async requestWithRetry(phase, errorPrefix, makeCall, context = {}) {
|
|
13008
13061
|
const debug = getDebugLogger();
|
|
@@ -13069,15 +13122,7 @@ ${trimmed}`;
|
|
|
13069
13122
|
throw new Error("Retry attempts exhausted");
|
|
13070
13123
|
}
|
|
13071
13124
|
async validateToken() {
|
|
13072
|
-
|
|
13073
|
-
const res = await request(url, { token: this.token });
|
|
13074
|
-
this.noteRequestId(res);
|
|
13075
|
-
if (res.status === 401) {
|
|
13076
|
-
throw new ApiError("CMA token is invalid or revoked", res.status, await res.text());
|
|
13077
|
-
}
|
|
13078
|
-
if (!res.ok) {
|
|
13079
|
-
throw new ApiError(`unexpected error validating token: ${res.status}`, res.status, await res.text());
|
|
13080
|
-
}
|
|
13125
|
+
await super.validateToken();
|
|
13081
13126
|
await this.checkPreflight();
|
|
13082
13127
|
}
|
|
13083
13128
|
// Probe the sources-api preflight endpoint to catch APS-denied users up-front,
|
|
@@ -13233,6 +13278,15 @@ ${trimmed}`;
|
|
|
13233
13278
|
}
|
|
13234
13279
|
});
|
|
13235
13280
|
|
|
13281
|
+
// packages/experience-design-system-cli/src/apply/api-client.ts
|
|
13282
|
+
var init_api_client = __esm({
|
|
13283
|
+
"packages/experience-design-system-cli/src/apply/api-client.ts"() {
|
|
13284
|
+
"use strict";
|
|
13285
|
+
init_edsi_client();
|
|
13286
|
+
init_api_error();
|
|
13287
|
+
}
|
|
13288
|
+
});
|
|
13289
|
+
|
|
13236
13290
|
// packages/experience-design-system-cli/src/lib/error-parser.ts
|
|
13237
13291
|
function stripLambdaLogPrefix(body) {
|
|
13238
13292
|
let out = body.replace(LAMBDA_LOG_PREFIX_RE, "");
|
|
@@ -14221,6 +14275,14 @@ var init_strip_allowed_components = __esm({
|
|
|
14221
14275
|
}
|
|
14222
14276
|
});
|
|
14223
14277
|
|
|
14278
|
+
// packages/experience-design-system-cli/src/host-utils.ts
|
|
14279
|
+
var init_host_utils = __esm({
|
|
14280
|
+
"packages/experience-design-system-cli/src/host-utils.ts"() {
|
|
14281
|
+
"use strict";
|
|
14282
|
+
init_url();
|
|
14283
|
+
}
|
|
14284
|
+
});
|
|
14285
|
+
|
|
14224
14286
|
// packages/experience-design-system-cli/src/credentials-store.ts
|
|
14225
14287
|
var credentials_store_exports = {};
|
|
14226
14288
|
__export(credentials_store_exports, {
|
|
@@ -14836,7 +14898,7 @@ async function resolveSharedInputs(opts) {
|
|
|
14836
14898
|
if (opts.tokens) {
|
|
14837
14899
|
tokens = await readTokensFromPath("--tokens", opts.tokens);
|
|
14838
14900
|
}
|
|
14839
|
-
const client2 = new
|
|
14901
|
+
const client2 = new EdsiApiClient({
|
|
14840
14902
|
host: opts.host,
|
|
14841
14903
|
cmaToken,
|
|
14842
14904
|
spaceId,
|
|
@@ -15576,7 +15638,7 @@ function App({ sessionId: sessionId2, artifactsRoot, reviewRoot }) {
|
|
|
15576
15638
|
tokens
|
|
15577
15639
|
);
|
|
15578
15640
|
if (!manifest.componentsManifest) manifest.componentsManifest = {};
|
|
15579
|
-
const client2 = new
|
|
15641
|
+
const client2 = new EdsiApiClient({ cmaToken, spaceId, environmentId });
|
|
15580
15642
|
const preview = await client2.previewImport(manifest, true);
|
|
15581
15643
|
const annotations = {};
|
|
15582
15644
|
for (const item of preview.components.new) {
|
|
@@ -16598,7 +16660,7 @@ var init_save_path_resolver = __esm({
|
|
|
16598
16660
|
});
|
|
16599
16661
|
|
|
16600
16662
|
// packages/experience-design-system-cli/src/runs/store.ts
|
|
16601
|
-
import { readFile as readFile22, writeFile as
|
|
16663
|
+
import { readFile as readFile22, writeFile as writeFile6, mkdir as mkdir6, rename } from "node:fs/promises";
|
|
16602
16664
|
import { join as join20 } from "node:path";
|
|
16603
16665
|
import { homedir as homedir7 } from "node:os";
|
|
16604
16666
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
@@ -16661,7 +16723,7 @@ async function writeAtomic(file) {
|
|
|
16661
16723
|
await mkdir6(RUNS_DIR, { recursive: true });
|
|
16662
16724
|
const body = JSON.stringify(file, null, 2) + "\n";
|
|
16663
16725
|
const tmp = `${RUNS_PATH}.${process.pid}.${Date.now()}.tmp`;
|
|
16664
|
-
await
|
|
16726
|
+
await writeFile6(tmp, body, { mode: 384 });
|
|
16665
16727
|
await rename(tmp, RUNS_PATH);
|
|
16666
16728
|
}
|
|
16667
16729
|
async function appendRun(input) {
|
|
@@ -24143,7 +24205,7 @@ async function runLivePreview(opts) {
|
|
|
24143
24205
|
tokens = await readTokensFromPath("tokens", tokensPath);
|
|
24144
24206
|
}
|
|
24145
24207
|
const manifest = buildManifest3(components, tokens, { deleteAllComponents: opts.deleteAllComponents === true });
|
|
24146
|
-
const client2 = new
|
|
24208
|
+
const client2 = new EdsiApiClient({ host, cmaToken, spaceId, environmentId });
|
|
24147
24209
|
const startedAt = Date.now();
|
|
24148
24210
|
let timeoutHandle;
|
|
24149
24211
|
const timeoutPromise = new Promise((_resolve, reject) => {
|
|
@@ -28363,7 +28425,7 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
|
|
|
28363
28425
|
update({ step: "credentials", credentialsValidating: true, credentialsError: "" });
|
|
28364
28426
|
try {
|
|
28365
28427
|
const resolvedHost = resolveWizardHost(host2);
|
|
28366
|
-
const client2 = new
|
|
28428
|
+
const client2 = new EdsiApiClient({
|
|
28367
28429
|
cmaToken,
|
|
28368
28430
|
spaceId,
|
|
28369
28431
|
environmentId,
|
|
@@ -28437,7 +28499,7 @@ Make sure this path contains TypeScript/React/Vue component files (.tsx, .ts, .v
|
|
|
28437
28499
|
update({ step: "previewing" });
|
|
28438
28500
|
const resolvedHost = resolveWizardHost(host2);
|
|
28439
28501
|
try {
|
|
28440
|
-
const client2 = new
|
|
28502
|
+
const client2 = new EdsiApiClient({
|
|
28441
28503
|
cmaToken,
|
|
28442
28504
|
spaceId,
|
|
28443
28505
|
environmentId,
|
|
@@ -28599,7 +28661,7 @@ If using a custom --host, make sure the space exists on that host.`
|
|
|
28599
28661
|
update({ step: "pushing", pushProgress: null });
|
|
28600
28662
|
try {
|
|
28601
28663
|
const resolvedHost = resolveWizardHost(host2);
|
|
28602
|
-
const client2 = new
|
|
28664
|
+
const client2 = new EdsiApiClient({
|
|
28603
28665
|
cmaToken,
|
|
28604
28666
|
spaceId,
|
|
28605
28667
|
environmentId,
|
|
@@ -34365,14 +34427,52 @@ init_db();
|
|
|
34365
34427
|
init_command();
|
|
34366
34428
|
init_manifest();
|
|
34367
34429
|
init_api_client();
|
|
34430
|
+
import { mkdir as mkdir5, writeFile as writeFile5 } from "node:fs/promises";
|
|
34431
|
+
import { join as join18, resolve as resolve21 } from "node:path";
|
|
34432
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
34433
|
+
|
|
34434
|
+
// packages/experience-design-system-cli/src/api/clients/cma-client.ts
|
|
34435
|
+
init_http();
|
|
34436
|
+
init_api_error();
|
|
34437
|
+
init_abstract_api_client();
|
|
34438
|
+
var CMA_PAGE_LIMIT = 100;
|
|
34439
|
+
var CmaApiClient = class extends AbstractApiClient {
|
|
34440
|
+
constructor(opts) {
|
|
34441
|
+
super(opts);
|
|
34442
|
+
}
|
|
34443
|
+
async listExistingEntities() {
|
|
34444
|
+
const [components, tokens] = await Promise.all([
|
|
34445
|
+
this.listAll("component_types", "component_types"),
|
|
34446
|
+
this.listAll("design_tokens", "design_tokens")
|
|
34447
|
+
]);
|
|
34448
|
+
return { components, tokens };
|
|
34449
|
+
}
|
|
34450
|
+
async listAll(resourcePath, opName) {
|
|
34451
|
+
const items = [];
|
|
34452
|
+
let skip = 0;
|
|
34453
|
+
while (true) {
|
|
34454
|
+
const url = `${this.base()}/${resourcePath}?limit=${CMA_PAGE_LIMIT}&skip=${skip}`;
|
|
34455
|
+
const res = await request(url, { token: this.token });
|
|
34456
|
+
if (!res.ok) {
|
|
34457
|
+
throw new ApiError(`${opName} list failed: ${res.status}`, res.status, await res.text());
|
|
34458
|
+
}
|
|
34459
|
+
const page = await res.json();
|
|
34460
|
+
items.push(...page.items);
|
|
34461
|
+
const returned = page.items.length;
|
|
34462
|
+
const total = page.total ?? items.length;
|
|
34463
|
+
if (returned === 0 || items.length >= total) break;
|
|
34464
|
+
skip += returned;
|
|
34465
|
+
}
|
|
34466
|
+
return items;
|
|
34467
|
+
}
|
|
34468
|
+
};
|
|
34469
|
+
|
|
34470
|
+
// packages/experience-design-system-cli/src/import/orchestrator.ts
|
|
34368
34471
|
init_contentful_urls();
|
|
34369
34472
|
init_debug_logger();
|
|
34370
34473
|
init_analytics();
|
|
34371
34474
|
init_env();
|
|
34372
34475
|
init_cli_path();
|
|
34373
|
-
import { mkdir as mkdir5 } from "node:fs/promises";
|
|
34374
|
-
import { join as join18, resolve as resolve21 } from "node:path";
|
|
34375
|
-
import { execFile as execFile2 } from "node:child_process";
|
|
34376
34476
|
async function runStep(args, cliPath, analyticsSessionId, env = {}, streamStderr = false) {
|
|
34377
34477
|
const debug = getDebugLogger();
|
|
34378
34478
|
const startedAt = Date.now();
|
|
@@ -34480,11 +34580,66 @@ async function runPipeline(opts, progressWriter, cliPathOverride) {
|
|
|
34480
34580
|
await mkdir5(outDir, { recursive: true });
|
|
34481
34581
|
const steps = [];
|
|
34482
34582
|
let stepNum = 0;
|
|
34483
|
-
const
|
|
34583
|
+
const wantsSpaceContext = opts.withSpaceContext === true;
|
|
34584
|
+
const canFetchSpaceContext = wantsSpaceContext && !!opts.spaceId && !!opts.environmentId && !!opts.cmaToken;
|
|
34585
|
+
const totalSteps = 5 + (opts.print ? 1 : 0) + (canFetchSpaceContext ? 1 : 0);
|
|
34484
34586
|
function stepLabel(name) {
|
|
34485
34587
|
stepNum++;
|
|
34486
34588
|
return ` Step ${stepNum}/${totalSteps} ${name} `;
|
|
34487
34589
|
}
|
|
34590
|
+
if (wantsSpaceContext && !canFetchSpaceContext) {
|
|
34591
|
+
progressWriter(
|
|
34592
|
+
" Note: --with-space-context requires --space-id, --environment-id, and --cma-token; skipping context fetch."
|
|
34593
|
+
);
|
|
34594
|
+
}
|
|
34595
|
+
if (canFetchSpaceContext) {
|
|
34596
|
+
const fetchLabel = stepLabel("Fetching existing components + design tokens");
|
|
34597
|
+
const fetchStepId = createStep(db, sessionId2, "fetch existing entities", {
|
|
34598
|
+
spaceId: opts.spaceId,
|
|
34599
|
+
environmentId: opts.environmentId
|
|
34600
|
+
});
|
|
34601
|
+
const t0 = Date.now();
|
|
34602
|
+
try {
|
|
34603
|
+
const cma = new CmaApiClient({
|
|
34604
|
+
host: opts.host,
|
|
34605
|
+
cmaToken: opts.cmaToken,
|
|
34606
|
+
spaceId: opts.spaceId,
|
|
34607
|
+
environmentId: opts.environmentId
|
|
34608
|
+
});
|
|
34609
|
+
const result = await cma.listExistingEntities();
|
|
34610
|
+
const fetchDurationMs = Date.now() - t0;
|
|
34611
|
+
const existingEntitiesPath = join18(outDir, ".existing-entities.json");
|
|
34612
|
+
await writeFile5(existingEntitiesPath, JSON.stringify(result, null, 2), "utf8");
|
|
34613
|
+
updateStep(db, fetchStepId, "complete", {
|
|
34614
|
+
components: String(result.components.length),
|
|
34615
|
+
tokens: String(result.tokens.length),
|
|
34616
|
+
path: existingEntitiesPath
|
|
34617
|
+
});
|
|
34618
|
+
progressWriter(
|
|
34619
|
+
`${fetchLabel}\u2713 ${result.components.length} component${result.components.length === 1 ? "" : "s"}, ${result.tokens.length} token${result.tokens.length === 1 ? "" : "s"} (${(fetchDurationMs / 1e3).toFixed(1)}s)`
|
|
34620
|
+
);
|
|
34621
|
+
steps.push({
|
|
34622
|
+
step: "fetch existing entities",
|
|
34623
|
+
status: "complete",
|
|
34624
|
+
durationMs: fetchDurationMs,
|
|
34625
|
+
detail: { components: result.components.length, tokens: result.tokens.length }
|
|
34626
|
+
});
|
|
34627
|
+
} catch (error) {
|
|
34628
|
+
const fetchDurationMs = Date.now() - t0;
|
|
34629
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
34630
|
+
updateStep(db, fetchStepId, "failed", {}, message);
|
|
34631
|
+
progressWriter(
|
|
34632
|
+
`${fetchLabel}\u26A0 fetch failed, continuing without space context (${(fetchDurationMs / 1e3).toFixed(1)}s)`
|
|
34633
|
+
);
|
|
34634
|
+
progressWriter(` ${message.split("\n")[0]}`);
|
|
34635
|
+
steps.push({
|
|
34636
|
+
step: "fetch existing entities",
|
|
34637
|
+
status: "failed",
|
|
34638
|
+
durationMs: fetchDurationMs,
|
|
34639
|
+
error: message
|
|
34640
|
+
});
|
|
34641
|
+
}
|
|
34642
|
+
}
|
|
34488
34643
|
const analyzeLabel = stepLabel("Statically analyzing project");
|
|
34489
34644
|
const analyzeSkipped = !opts.noCache && opts.skipAnalyze;
|
|
34490
34645
|
let extractSessionId = null;
|
|
@@ -35439,7 +35594,10 @@ function registerImportCommand(program) {
|
|
|
35439
35594
|
"Override a stage prompt (repeatable). value is a file path or literal text, e.g. --prompt composition=./p.md",
|
|
35440
35595
|
(v, acc) => [...acc, v],
|
|
35441
35596
|
[]
|
|
35442
|
-
).option("--auto-reject-cycles", "Automatically reject components involved in slot cycles and retry").option(
|
|
35597
|
+
).option("--auto-reject-cycles", "Automatically reject components involved in slot cycles and retry").option(
|
|
35598
|
+
"--with-space-context",
|
|
35599
|
+
"Fetch existing Components + DesignTokens from the target space+env and persist them under <out>/.existing-entities.json for future agent-context enrichment. Requires --space-id, --environment-id, and --cma-token."
|
|
35600
|
+
).option("--auto-filter", "Force the AI auto-filter ON (overrides the credentials.json autoFilter preference)").option(
|
|
35443
35601
|
"--no-auto-filter",
|
|
35444
35602
|
"Skip the automatic AI pre-filter; jump straight to manual scope-gate (overrides the credentials.json autoFilter preference; no-op when paired with --auto-accept-scope)"
|
|
35445
35603
|
).option(
|
|
@@ -35786,6 +35944,7 @@ function registerImportCommand(program) {
|
|
|
35786
35944
|
dryRun: dryRunForward,
|
|
35787
35945
|
selectPromptPath: opts.selectPromptPath,
|
|
35788
35946
|
autoRejectCycles: opts.autoRejectCycles ?? false,
|
|
35947
|
+
withSpaceContext: opts.withSpaceContext ?? false,
|
|
35789
35948
|
allowDeletions: opts.allowDeletions ?? false,
|
|
35790
35949
|
compositionMode: headlessCompositionMode,
|
|
35791
35950
|
...opts.compositionMap ? { compositionMap: opts.compositionMap } : {},
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface EdsiClientOptions {
|
|
2
|
+
host?: string;
|
|
3
|
+
cmaToken: string;
|
|
4
|
+
spaceId: string;
|
|
5
|
+
environmentId: string;
|
|
6
|
+
retry?: {
|
|
7
|
+
maxAttempts?: number;
|
|
8
|
+
initialDelayMs?: number;
|
|
9
|
+
maxDelayMs?: number;
|
|
10
|
+
sleep?: (delayMs: number) => Promise<void>;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ExistingComponentEntity } from './existing-component-entity.js';
|
|
2
|
+
import type { ExistingTokenEntity } from './existing-token-entity.js';
|
|
3
|
+
export interface ExistingEntitiesResponse {
|
|
4
|
+
components: ExistingComponentEntity[];
|
|
5
|
+
tokens: ExistingTokenEntity[];
|
|
6
|
+
}
|
package/dist/src/session/db.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { ToolCall, TokenToolCall, ComponentSourceRef } from '@contentful/ex
|
|
|
5
5
|
import type { ComponentTypeSummary } from '@contentful/experience-design-system-types';
|
|
6
6
|
import type { SlotCycle, SlotEdge } from '../analyze/cycle-detection.js';
|
|
7
7
|
export type StepStatus = 'pending' | 'complete' | 'failed' | 'interrupted';
|
|
8
|
-
export type CommandName = 'analyze extract' | 'analyze select' | 'generate components' | 'generate tokens' | 'generate edit' | 'apply preview' | 'apply select' | 'apply push' | 'print components' | 'print tokens' | 'map tokens' | 'import';
|
|
8
|
+
export type CommandName = 'analyze extract' | 'analyze select' | 'generate components' | 'generate tokens' | 'generate edit' | 'apply preview' | 'apply select' | 'apply push' | 'print components' | 'print tokens' | 'map tokens' | 'fetch existing entities' | 'import';
|
|
9
9
|
export interface SessionRow {
|
|
10
10
|
id: string;
|
|
11
11
|
name: string | null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.32.1-dev-build-
|
|
3
|
+
"version": "2.32.1-dev-build-1c6a348.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"svelte": "^5.56.4",
|
|
40
40
|
"ts-morph": "^27.0.2",
|
|
41
41
|
"typescript": "^5.9.3",
|
|
42
|
-
"@contentful/experience-design-system-cli-v2": "2.32.1-dev-build-
|
|
43
|
-
"@contentful/experience-design-system-types": "2.32.1-dev-build-
|
|
42
|
+
"@contentful/experience-design-system-cli-v2": "2.32.1-dev-build-1c6a348.0",
|
|
43
|
+
"@contentful/experience-design-system-types": "2.32.1-dev-build-1c6a348.0"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@tsconfig/node24": "^24.0.4",
|
|
@@ -52,9 +52,9 @@
|
|
|
52
52
|
"ink-testing-library": "^4.0.0",
|
|
53
53
|
"typescript-eslint": "^8.67.0",
|
|
54
54
|
"vitest": "^4.0.16",
|
|
55
|
-
"@contentful/experience-design-system-extraction": "2.32.1-dev-build-
|
|
56
|
-
"@contentful/experience-design-system-
|
|
57
|
-
"@contentful/experience-design-system-
|
|
55
|
+
"@contentful/experience-design-system-extraction": "2.32.1-dev-build-1c6a348.0",
|
|
56
|
+
"@contentful/experience-design-system-generation": "2.32.1-dev-build-1c6a348.0",
|
|
57
|
+
"@contentful/experience-design-system-client": "2.32.1-dev-build-1c6a348.0"
|
|
58
58
|
},
|
|
59
59
|
"repository": {
|
|
60
60
|
"type": "git",
|