@contentful/experience-design-system-cli 2.26.7-dev-build-6f3b072.0 → 2.26.7-dev-build-8206e92.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.26.7-dev-build-6f3b072.0",
3
+ "version": "2.26.7-dev-build-8206e92.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -0,0 +1,13 @@
1
+ import type { CDFComponentEntry } from '@contentful/experience-design-system-types';
2
+ type ReviewJsonComponent = {
3
+ key: string;
4
+ entry: CDFComponentEntry;
5
+ };
6
+ /**
7
+ * Builds the exact JSON string rendered by the read-only review panel.
8
+ *
9
+ * The FieldEditor intentionally continues to use the unfiltered component
10
+ * definition so toggling the panel never changes what an operator can save.
11
+ */
12
+ export declare function getReviewJsonPanelValue(selected: ReviewJsonComponent | null, showHiddenProps: boolean): string;
13
+ export {};
package/dist/src/index.js CHANGED
@@ -23,87 +23,23 @@ var init_agent_names = __esm({
23
23
 
24
24
  // packages/experience-design-system-generation/dist/src/agent-runner.js
25
25
  import { spawn } from "node:child_process";
26
- function scanJsonObjects(line) {
27
- const objects = [];
28
- let i = 0;
29
- while (i < line.length) {
30
- while (i < line.length && /\s/.test(line[i]))
31
- i++;
32
- if (i >= line.length)
33
- break;
34
- if (line[i] !== "{")
35
- return { objects, trailing: line.slice(i), malformed: objects.length === 0 };
36
- let depth = 0;
37
- let inString = false;
38
- let escaped = false;
39
- let end = -1;
40
- for (let j = i; j < line.length; j++) {
41
- const ch = line[j];
42
- if (escaped) {
43
- escaped = false;
44
- continue;
45
- }
46
- if (ch === "\\") {
47
- if (inString)
48
- escaped = true;
49
- continue;
50
- }
51
- if (ch === '"') {
52
- inString = !inString;
53
- continue;
54
- }
55
- if (inString)
56
- continue;
57
- if (ch === "{")
58
- depth++;
59
- else if (ch === "}") {
60
- depth--;
61
- if (depth === 0) {
62
- end = j;
63
- break;
64
- }
65
- }
66
- }
67
- if (end === -1)
68
- return { objects, trailing: "", malformed: true };
69
- let parsed;
70
- try {
71
- parsed = JSON.parse(line.slice(i, end + 1));
72
- } catch {
73
- return { objects, trailing: "", malformed: true };
74
- }
75
- if (typeof parsed === "object" && parsed !== null)
76
- objects.push(parsed);
77
- i = end + 1;
78
- }
79
- return { objects, trailing: "", malformed: false };
80
- }
81
- function readToolCallObjects(stdout) {
82
- const objects = [];
26
+ function parseSelectToolCallLines(stdout) {
27
+ const calls = [];
83
28
  const warnings = [];
84
29
  for (const raw of stdout.split("\n")) {
85
30
  const line = raw.trim();
86
31
  if (!line.startsWith("{"))
87
32
  continue;
88
- const { objects: found, trailing, malformed } = scanJsonObjects(line);
89
- if (malformed) {
33
+ let obj;
34
+ try {
35
+ obj = JSON.parse(line);
36
+ } catch {
90
37
  warnings.push(`unparseable line: ${line.slice(0, 120)}`);
91
38
  continue;
92
39
  }
93
- if (trailing.trim()) {
94
- warnings.push(`ignored trailing content after JSON: ${trailing.trim().slice(0, 120)}`);
95
- }
96
- for (const obj of found) {
97
- if ("tool" in obj)
98
- objects.push(obj);
99
- }
100
- }
101
- return { objects, warnings };
102
- }
103
- function parseSelectToolCallLines(stdout) {
104
- const calls = [];
105
- const { objects, warnings } = readToolCallObjects(stdout);
106
- for (const rec of objects) {
40
+ if (typeof obj !== "object" || obj === null || !("tool" in obj))
41
+ continue;
42
+ const rec = obj;
107
43
  if (!VALID_SELECT_TOOL_NAMES.has(rec.tool))
108
44
  continue;
109
45
  if (typeof rec.name !== "string" || !rec.name) {
@@ -129,8 +65,21 @@ function parseSelectToolCallLines(stdout) {
129
65
  }
130
66
  function parseToolCallLines(stdout) {
131
67
  const calls = [];
132
- const { objects, warnings } = readToolCallObjects(stdout);
133
- for (const rec of objects) {
68
+ const warnings = [];
69
+ for (const raw of stdout.split("\n")) {
70
+ const line = raw.trim();
71
+ if (!line.startsWith("{"))
72
+ continue;
73
+ let obj;
74
+ try {
75
+ obj = JSON.parse(line);
76
+ } catch {
77
+ warnings.push(`unparseable line: ${line.slice(0, 120)}`);
78
+ continue;
79
+ }
80
+ if (typeof obj !== "object" || obj === null || !("tool" in obj))
81
+ continue;
82
+ const rec = obj;
134
83
  if (!VALID_TOOL_NAMES.has(rec.tool)) {
135
84
  warnings.push(`unknown tool: ${String(rec.tool)}`);
136
85
  continue;
@@ -218,8 +167,21 @@ function parseToolCallLines(stdout) {
218
167
  }
219
168
  function parseTokenToolCallLines(stdout) {
220
169
  const calls = [];
221
- const { objects, warnings } = readToolCallObjects(stdout);
222
- for (const rec of objects) {
170
+ const warnings = [];
171
+ for (const raw of stdout.split("\n")) {
172
+ const line = raw.trim();
173
+ if (!line.startsWith("{"))
174
+ continue;
175
+ let obj;
176
+ try {
177
+ obj = JSON.parse(line);
178
+ } catch {
179
+ warnings.push(`unparseable line: ${line.slice(0, 120)}`);
180
+ continue;
181
+ }
182
+ if (typeof obj !== "object" || obj === null || !("tool" in obj))
183
+ continue;
184
+ const rec = obj;
223
185
  if (!VALID_TOKEN_TOOL_NAMES.has(rec.tool))
224
186
  continue;
225
187
  if (rec.tool === "set_token") {
@@ -8592,6 +8554,66 @@ function applyDbMigrations(db) {
8592
8554
  if (!rawPropColNames.has("source_end_line")) {
8593
8555
  db.exec("ALTER TABLE raw_props ADD COLUMN source_end_line INTEGER");
8594
8556
  }
8557
+ const rawPropsDdl = db.prepare(`SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'raw_props'`).get();
8558
+ if (rawPropsDdl && !rawPropsDdl.sql.includes(`'unattached'`)) {
8559
+ db.exec("PRAGMA foreign_keys = OFF");
8560
+ try {
8561
+ db.exec("BEGIN");
8562
+ try {
8563
+ db.exec(`
8564
+ CREATE TABLE raw_props__new (
8565
+ session_id TEXT NOT NULL,
8566
+ component_id TEXT NOT NULL,
8567
+ name TEXT NOT NULL,
8568
+ type TEXT NOT NULL,
8569
+ required INTEGER NOT NULL CHECK (required IN (0, 1)),
8570
+ category TEXT CHECK (category IN ('content', 'design', 'state')),
8571
+ default_value TEXT,
8572
+ description TEXT,
8573
+ token_reference TEXT,
8574
+ position INTEGER NOT NULL,
8575
+ cdf_type TEXT,
8576
+ cdf_category TEXT CHECK (cdf_category IN ('content', 'design', 'state', 'unattached')),
8577
+ cdf_token_kind TEXT,
8578
+ rationale TEXT,
8579
+ source_start_line INTEGER,
8580
+ source_end_line INTEGER,
8581
+ PRIMARY KEY (session_id, component_id, name),
8582
+ FOREIGN KEY (session_id, component_id) REFERENCES raw_components(session_id, component_id) ON DELETE CASCADE
8583
+ );
8584
+
8585
+ INSERT INTO raw_props__new
8586
+ (session_id, component_id, name, type, required, category, default_value,
8587
+ description, token_reference, position, cdf_type, cdf_category, cdf_token_kind,
8588
+ rationale, source_start_line, source_end_line)
8589
+ SELECT
8590
+ session_id, component_id, name, type, required, category, default_value,
8591
+ description, token_reference, position, cdf_type, cdf_category, cdf_token_kind,
8592
+ rationale, source_start_line, source_end_line
8593
+ FROM raw_props;
8594
+
8595
+ DROP TABLE raw_props;
8596
+ ALTER TABLE raw_props__new RENAME TO raw_props;
8597
+ CREATE INDEX IF NOT EXISTS idx_raw_props_session ON raw_props(session_id, component_id);
8598
+
8599
+ -- Rows written by a pre-migration exclude_prop still say cdf_type = 'excluded'
8600
+ -- with cdf_category = NULL. Backfill them into the new unattached shape so an
8601
+ -- in-flight local session doesn't end up with an unprintable half-migrated row.
8602
+ UPDATE raw_props
8603
+ SET cdf_category = 'unattached',
8604
+ cdf_type = CASE WHEN type = 'boolean' THEN 'boolean' ELSE 'string' END,
8605
+ required = 0
8606
+ WHERE cdf_type = 'excluded';
8607
+ `);
8608
+ db.exec("COMMIT");
8609
+ } catch (e) {
8610
+ db.exec("ROLLBACK");
8611
+ throw e;
8612
+ }
8613
+ } finally {
8614
+ db.exec("PRAGMA foreign_keys = ON");
8615
+ }
8616
+ }
8595
8617
  if (!rawCompColNames.has("reject_reason")) {
8596
8618
  db.exec("ALTER TABLE raw_components ADD COLUMN reject_reason TEXT");
8597
8619
  }
@@ -8777,7 +8799,12 @@ function applyToolCalls(db, sessionId2, componentId, componentName, calls, incom
8777
8799
  WHERE session_id = ? AND component_id = ? AND name = ?`
8778
8800
  );
8779
8801
  const clearProp = db.prepare(
8780
- `UPDATE raw_props SET cdf_type = 'excluded', cdf_category = NULL, cdf_token_kind = NULL, rationale = ?
8802
+ `UPDATE raw_props
8803
+ SET cdf_type = CASE WHEN type = 'boolean' THEN 'boolean' ELSE 'string' END,
8804
+ cdf_category = 'unattached',
8805
+ cdf_token_kind = NULL,
8806
+ required = 0,
8807
+ rationale = ?
8781
8808
  WHERE session_id = ? AND component_id = ? AND name = ?`
8782
8809
  );
8783
8810
  const deleteAllowedValues = db.prepare(
@@ -9376,7 +9403,7 @@ function loadCDFComponents(db, sessionId2) {
9376
9403
  `SELECT component_id, name, required, default_value, description,
9377
9404
  cdf_type, cdf_category, cdf_token_kind, position
9378
9405
  FROM raw_props
9379
- WHERE session_id = ? AND cdf_type IS NOT NULL AND cdf_type != 'excluded'
9406
+ WHERE session_id = ? AND cdf_type IS NOT NULL
9380
9407
  ORDER BY component_id, position`
9381
9408
  ).all(sessionId2);
9382
9409
  const allowedValues = db.prepare(
@@ -21346,6 +21373,27 @@ var init_merge_ai_decisions = __esm({
21346
21373
  }
21347
21374
  });
21348
21375
 
21376
+ // packages/experience-design-system-cli/src/import/tui/steps/review-json-panel.ts
21377
+ function getReviewJsonPanelValue(selected, showHiddenProps) {
21378
+ if (!selected) return "";
21379
+ const entry = showHiddenProps ? selected.entry : {
21380
+ ...selected.entry,
21381
+ $properties: Object.fromEntries(
21382
+ Object.entries(selected.entry.$properties ?? {}).filter(
21383
+ ([, definition]) => !HIDDEN_PROP_CATEGORIES.has(definition.$category)
21384
+ )
21385
+ )
21386
+ };
21387
+ return JSON.stringify({ [selected.key]: entry }, null, 2);
21388
+ }
21389
+ var HIDDEN_PROP_CATEGORIES;
21390
+ var init_review_json_panel = __esm({
21391
+ "packages/experience-design-system-cli/src/import/tui/steps/review-json-panel.ts"() {
21392
+ "use strict";
21393
+ HIDDEN_PROP_CATEGORIES = /* @__PURE__ */ new Set(["state", "unattached"]);
21394
+ }
21395
+ });
21396
+
21349
21397
  // packages/experience-design-system-cli/src/analyze/cycle-view.ts
21350
21398
  function computeCycleView(components) {
21351
21399
  const filtered = components.filter((c2) => c2.status !== "rejected");
@@ -23638,6 +23686,7 @@ function GenerateReviewStep({
23638
23686
  const [showFinalize, setShowFinalize] = useState26(false);
23639
23687
  const [showQuit, setShowQuit] = useState26(false);
23640
23688
  const [showJson, setShowJson] = useState26(false);
23689
+ const [showHiddenProps, setShowHiddenProps] = useState26(false);
23641
23690
  const [draftValue, setDraftValue] = useState26("");
23642
23691
  const [saveError, setSaveError] = useState26(null);
23643
23692
  const [finalizeError, setFinalizeError] = useState26(initialFinalizeError);
@@ -24587,7 +24636,7 @@ function GenerateReviewStep({
24587
24636
  }
24588
24637
  if (!sidebarFocused && showJson) {
24589
24638
  const current = components[selectedIdx];
24590
- const currentJson = current ? JSON.stringify({ [current.key]: current.entry }, null, 2) : "";
24639
+ const currentJson = getReviewJsonPanelValue(current ?? null, showHiddenProps);
24591
24640
  const totalLines = currentJson.split("\n").length;
24592
24641
  const maxOffset = Math.max(0, totalLines - PANEL_HEIGHT);
24593
24642
  if (input === "g" && !key.ctrl) {
@@ -24724,6 +24773,12 @@ function GenerateReviewStep({
24724
24773
  pendingGRef.current = false;
24725
24774
  return;
24726
24775
  }
24776
+ if (input === "H") {
24777
+ setShowHiddenProps((prev) => !prev);
24778
+ setJsonScrollOffset(0);
24779
+ pendingGRef.current = false;
24780
+ return;
24781
+ }
24727
24782
  if (key.return) {
24728
24783
  const current = components[selectedIdx];
24729
24784
  if (!current) return;
@@ -24824,6 +24879,7 @@ function GenerateReviewStep({
24824
24879
  }
24825
24880
  const selected = components[selectedIdx] ?? null;
24826
24881
  const selectedJson = selected ? JSON.stringify({ [selected.key]: selected.entry }, null, 2) : "";
24882
+ const visibleJsonPanelValue = getReviewJsonPanelValue(selected, showHiddenProps);
24827
24883
  const isEmpty2 = (c2) => Object.keys(c2.entry.$properties).length === 0 && Object.keys(c2.entry.$slots ?? {}).length === 0;
24828
24884
  const emptyCount = components.filter(isEmpty2).length;
24829
24885
  const cycleParticipantSet = cycleView.structural;
@@ -25145,7 +25201,7 @@ function GenerateReviewStep({
25145
25201
  JsonPanel,
25146
25202
  {
25147
25203
  label: "GENERATED DEFINITION (read-only)",
25148
- value: selectedJson,
25204
+ value: visibleJsonPanelValue,
25149
25205
  scrollOffset: jsonScrollOffset,
25150
25206
  width: panelWidth,
25151
25207
  height: PANEL_HEIGHT,
@@ -25238,6 +25294,7 @@ function GenerateReviewStep({
25238
25294
  legendEntry("[P]", "component rationale", panelOpen === "component-rationale"),
25239
25295
  legendEntry("[s]", "source", panelOpen === "source"),
25240
25296
  legendEntry("[J]", showJson ? "hide JSON" : "show JSON", showJson),
25297
+ legendEntry("[H]", showHiddenProps ? "hide state/unattached" : "show state/unattached", showHiddenProps),
25241
25298
  breakingChanges.length > 0 && legendEntry("[b]", "see breaking changes", breakingPanel.isOpen),
25242
25299
  removedComponents.length > 0 && legendEntry("[d]", removedBannerCollapsed ? "show removed" : "hide removed", !removedBannerCollapsed),
25243
25300
  legendEntry("[/]", "search", searchOpen || searchQuery.length > 0),
@@ -25268,6 +25325,7 @@ var init_GenerateReviewStep = __esm({
25268
25325
  "packages/experience-design-system-cli/src/import/tui/steps/GenerateReviewStep.tsx"() {
25269
25326
  "use strict";
25270
25327
  init_theme();
25328
+ init_review_json_panel();
25271
25329
  init_GroupedSidebar();
25272
25330
  init_composite_closure();
25273
25331
  init_slot_graph();
@@ -25418,6 +25476,7 @@ function AtomicGenerateReviewStep({
25418
25476
  const [showFinalize, setShowFinalize] = useState27(false);
25419
25477
  const [showQuit, setShowQuit] = useState27(false);
25420
25478
  const [showJson, setShowJson] = useState27(false);
25479
+ const [showHiddenProps, setShowHiddenProps] = useState27(false);
25421
25480
  const [draftValue, setDraftValue] = useState27("");
25422
25481
  const [saveError, setSaveError] = useState27(null);
25423
25482
  const [finalizeError, setFinalizeError] = useState27(initialFinalizeError);
@@ -25781,7 +25840,7 @@ function AtomicGenerateReviewStep({
25781
25840
  }
25782
25841
  if (!sidebarFocused && showJson) {
25783
25842
  const current = components[selectedIdx];
25784
- const currentJson = current ? JSON.stringify({ [current.key]: current.entry }, null, 2) : "";
25843
+ const currentJson = getReviewJsonPanelValue(current ?? null, showHiddenProps);
25785
25844
  const totalLines = currentJson.split("\n").length;
25786
25845
  const maxOffset = Math.max(0, totalLines - PANEL_HEIGHT2);
25787
25846
  if (input === "g" && !key.ctrl) {
@@ -25837,6 +25896,12 @@ function AtomicGenerateReviewStep({
25837
25896
  pendingGRef.current = false;
25838
25897
  return;
25839
25898
  }
25899
+ if (input === "H") {
25900
+ setShowHiddenProps((prev) => !prev);
25901
+ setJsonScrollOffset(0);
25902
+ pendingGRef.current = false;
25903
+ return;
25904
+ }
25840
25905
  if (key.upArrow || input === "k") {
25841
25906
  setSelectedIdx((prev) => {
25842
25907
  const newIdx = Math.max(0, prev - 1);
@@ -25869,6 +25934,7 @@ function AtomicGenerateReviewStep({
25869
25934
  }
25870
25935
  const selected = components[selectedIdx] ?? null;
25871
25936
  const selectedJson = selected ? JSON.stringify({ [selected.key]: selected.entry }, null, 2) : "";
25937
+ const visibleJsonPanelValue = getReviewJsonPanelValue(selected, showHiddenProps);
25872
25938
  const isEmpty2 = (c2) => Object.keys(c2.entry.$properties).length === 0;
25873
25939
  const emptyCount = components.filter(isEmpty2).length;
25874
25940
  const sidebarItems = components.map((c2) => ({
@@ -26044,7 +26110,7 @@ function AtomicGenerateReviewStep({
26044
26110
  JsonPanel,
26045
26111
  {
26046
26112
  label: "GENERATED DEFINITION (read-only)",
26047
- value: selectedJson,
26113
+ value: visibleJsonPanelValue,
26048
26114
  scrollOffset: jsonScrollOffset,
26049
26115
  width: panelWidth,
26050
26116
  height: PANEL_HEIGHT2,
@@ -26084,7 +26150,7 @@ function AtomicGenerateReviewStep({
26084
26150
  ),
26085
26151
  saveError && /* @__PURE__ */ jsx48(Text47, { color: PALETTE.error, children: "\u2717 " + saveError }),
26086
26152
  /* @__PURE__ */ jsxs47(Text47, { dimColor: true, children: [
26087
- sidebarFocused ? " [a] accept [r] reject [A] accept all [i] prop rationale [I] component rationale [s] source [J] " + (showJson ? "hide JSON" : "show JSON") + " [^z] undo [^y] redo [^r] reload [F] finalize [e/Tab] focus panel" + (livePreview && removedComponents.length > 0 ? " [d] removed list" : "") + " [q] quit" : showJson ? " [j/k] scroll [Ctrl+u/d] half-page [gg/G] top/bottom [Tab] focus list" : " [Tab] focus list (edit fields)",
26153
+ sidebarFocused ? " [a] accept [r] reject [A] accept all [i] prop rationale [I] component rationale [s] source [J] " + (showJson ? "hide JSON" : "show JSON") + " [H] " + (showHiddenProps ? "hide state/unattached" : "show state/unattached") + " [^z] undo [^y] redo [^r] reload [F] finalize [e/Tab] focus panel" + (livePreview && removedComponents.length > 0 ? " [d] removed list" : "") + " [q] quit" : showJson ? " [j/k] scroll [Ctrl+u/d] half-page [gg/G] top/bottom [Tab] focus list" : " [Tab] focus list (edit fields)",
26088
26154
  livePreviewHook.status === "running" && /* @__PURE__ */ jsx48(Text47, { children: ` ${livePreviewSpinner} live preview` }),
26089
26155
  livePreviewHook.disabled && /* @__PURE__ */ jsx48(Text47, { children: " \xB7 live preview disabled" })
26090
26156
  ] })
@@ -26131,6 +26197,7 @@ var init_AtomicGenerateReviewStep = __esm({
26131
26197
  init_useFinalizePreview();
26132
26198
  init_scroll_offset();
26133
26199
  init_theme();
26200
+ init_review_json_panel();
26134
26201
  VISIBLE_COUNT3 = 20;
26135
26202
  PANEL_HEIGHT2 = 22;
26136
26203
  }
@@ -31385,7 +31452,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
31385
31452
  const allOk = failed.length === 0;
31386
31453
  const cachedNote = cachedResults.length > 0 ? c.dim(` (${cachedResults.length} cached)`) : "";
31387
31454
  process.stderr.write(
31388
- (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified, ${totalExcluded} excluded`) + "\n"
31455
+ (allOk ? c.green("\u2713") : c.yellow("\u26A0")) + ` ${generated.length + cachedResults.length}/${componentResults.length} components` + cachedNote + c.dim(` ${totalClassified} classified, ${totalExcluded} unattached`) + "\n"
31389
31456
  );
31390
31457
  process.stdout.write(`renamed-slots: ${totalRenamedSlots}
31391
31458
  `);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contentful/experience-design-system-cli",
3
- "version": "2.26.7-dev-build-6f3b072.0",
3
+ "version": "2.26.7-dev-build-8206e92.0",
4
4
  "description": "Contentful Experiences design system import CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -39,7 +39,7 @@
39
39
  "svelte": "^5.56.4",
40
40
  "ts-morph": "^27.0.2",
41
41
  "typescript": "^5.9.3",
42
- "@contentful/experience-design-system-types": "2.26.7-dev-build-6f3b072.0"
42
+ "@contentful/experience-design-system-types": "2.26.7-dev-build-8206e92.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@tsconfig/node24": "^24.0.4",
@@ -51,9 +51,9 @@
51
51
  "ink-testing-library": "^4.0.0",
52
52
  "typescript-eslint": "^8.67.0",
53
53
  "vitest": "^4.0.16",
54
- "@contentful/experience-design-system-client": "2.26.7-dev-build-6f3b072.0",
55
- "@contentful/experience-design-system-extraction": "2.26.7-dev-build-6f3b072.0",
56
- "@contentful/experience-design-system-generation": "2.26.7-dev-build-6f3b072.0"
54
+ "@contentful/experience-design-system-client": "2.26.7-dev-build-8206e92.0",
55
+ "@contentful/experience-design-system-extraction": "2.26.7-dev-build-8206e92.0",
56
+ "@contentful/experience-design-system-generation": "2.26.7-dev-build-8206e92.0"
57
57
  },
58
58
  "repository": {
59
59
  "type": "git",