dsh-plugin-shop 0.7.0 → 0.7.1-beta.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/lib/client.js +14 -18
- package/lib/index.js +65 -13
- package/lib/typert.host.js +11 -11
- package/lib/typert.remote-client.js +10 -10
- package/lib/types/client/locales.d.ts +0 -2
- package/lib/types/host/executor.d.ts +35 -0
- package/lib/types/host/index.d.ts +0 -4
- package/lib/types/host/install.d.ts +1 -1
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -4347,7 +4347,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4347
4347
|
literal("denied"),
|
|
4348
4348
|
literal("version-mismatch"),
|
|
4349
4349
|
literal("needs-acknowledgement"),
|
|
4350
|
-
literal("git-missing"),
|
|
4351
4350
|
literal("tarball-integrity")
|
|
4352
4351
|
]),
|
|
4353
4352
|
"detail": string()
|
|
@@ -4437,7 +4436,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4437
4436
|
},
|
|
4438
4437
|
sourceLocation: {
|
|
4439
4438
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4440
|
-
"line":
|
|
4439
|
+
"line": 562,
|
|
4441
4440
|
"column": 9
|
|
4442
4441
|
}
|
|
4443
4442
|
},
|
|
@@ -4455,7 +4454,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4455
4454
|
},
|
|
4456
4455
|
sourceLocation: {
|
|
4457
4456
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4458
|
-
"line":
|
|
4457
|
+
"line": 747,
|
|
4459
4458
|
"column": 9
|
|
4460
4459
|
}
|
|
4461
4460
|
},
|
|
@@ -4483,7 +4482,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4483
4482
|
},
|
|
4484
4483
|
sourceLocation: {
|
|
4485
4484
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4486
|
-
"line":
|
|
4485
|
+
"line": 612,
|
|
4487
4486
|
"column": 9
|
|
4488
4487
|
}
|
|
4489
4488
|
},
|
|
@@ -4510,7 +4509,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4510
4509
|
},
|
|
4511
4510
|
sourceLocation: {
|
|
4512
4511
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4513
|
-
"line":
|
|
4512
|
+
"line": 736,
|
|
4514
4513
|
"column": 3
|
|
4515
4514
|
}
|
|
4516
4515
|
},
|
|
@@ -4528,7 +4527,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4528
4527
|
},
|
|
4529
4528
|
sourceLocation: {
|
|
4530
4529
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4531
|
-
"line":
|
|
4530
|
+
"line": 897,
|
|
4532
4531
|
"column": 9
|
|
4533
4532
|
}
|
|
4534
4533
|
},
|
|
@@ -4555,7 +4554,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4555
4554
|
},
|
|
4556
4555
|
sourceLocation: {
|
|
4557
4556
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4558
|
-
"line":
|
|
4557
|
+
"line": 460,
|
|
4559
4558
|
"column": 9
|
|
4560
4559
|
}
|
|
4561
4560
|
},
|
|
@@ -4583,7 +4582,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4583
4582
|
},
|
|
4584
4583
|
sourceLocation: {
|
|
4585
4584
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4586
|
-
"line":
|
|
4585
|
+
"line": 839,
|
|
4587
4586
|
"column": 9
|
|
4588
4587
|
}
|
|
4589
4588
|
},
|
|
@@ -4610,7 +4609,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4610
4609
|
},
|
|
4611
4610
|
sourceLocation: {
|
|
4612
4611
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4613
|
-
"line":
|
|
4612
|
+
"line": 957,
|
|
4614
4613
|
"column": 9
|
|
4615
4614
|
}
|
|
4616
4615
|
},
|
|
@@ -4628,7 +4627,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4628
4627
|
},
|
|
4629
4628
|
sourceLocation: {
|
|
4630
4629
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4631
|
-
"line":
|
|
4630
|
+
"line": 941,
|
|
4632
4631
|
"column": 9
|
|
4633
4632
|
}
|
|
4634
4633
|
}
|
|
@@ -4676,7 +4675,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4676
4675
|
case "not-in-catalog": return "notInCatalogCode";
|
|
4677
4676
|
case "version-mismatch": return "versionMismatchCode";
|
|
4678
4677
|
case "needs-acknowledgement": return "needsAcknowledgementCode";
|
|
4679
|
-
case "git-missing": return "gitMissingCode";
|
|
4680
4678
|
case "tarball-integrity": return "tarballIntegrityCode";
|
|
4681
4679
|
}
|
|
4682
4680
|
}
|
|
@@ -4967,7 +4965,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4967
4965
|
categoryUi: "界面",
|
|
4968
4966
|
categoryWorkflow: "工作流",
|
|
4969
4967
|
categoryIntegration: "集成",
|
|
4970
|
-
categoryTheme: "
|
|
4968
|
+
categoryTheme: "主题",
|
|
4971
4969
|
categoryOther: "其他",
|
|
4972
4970
|
empty: "目录中暂无插件。",
|
|
4973
4971
|
emptySearch: "没有匹配的插件。",
|
|
@@ -5008,7 +5006,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5008
5006
|
notInCatalogCode: "不在目录中",
|
|
5009
5007
|
versionMismatchCode: "版本不匹配",
|
|
5010
5008
|
needsAcknowledgementCode: "需要确认",
|
|
5011
|
-
gitMissingCode: "缺少 git",
|
|
5012
5009
|
tarballIntegrityCode: "压缩包完整性校验失败",
|
|
5013
5010
|
npmSource: "npm 包",
|
|
5014
5011
|
githubSource: "源码在 GitHub",
|
|
@@ -5056,7 +5053,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5056
5053
|
categoryUi: "UI",
|
|
5057
5054
|
categoryWorkflow: "Workflow",
|
|
5058
5055
|
categoryIntegration: "Integration",
|
|
5059
|
-
categoryTheme: "Themes
|
|
5056
|
+
categoryTheme: "Themes",
|
|
5060
5057
|
categoryOther: "Other",
|
|
5061
5058
|
empty: "No plugins in the catalog.",
|
|
5062
5059
|
emptySearch: "No matching plugins.",
|
|
@@ -5097,7 +5094,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5097
5094
|
notInCatalogCode: "Not in the catalog",
|
|
5098
5095
|
versionMismatchCode: "Version mismatch",
|
|
5099
5096
|
needsAcknowledgementCode: "Acknowledgement required",
|
|
5100
|
-
gitMissingCode: "git missing",
|
|
5101
5097
|
tarballIntegrityCode: "tarball integrity check failed",
|
|
5102
5098
|
npmSource: "npm package",
|
|
5103
5099
|
githubSource: "source on GitHub",
|
|
@@ -5279,7 +5275,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5279
5275
|
}
|
|
5280
5276
|
//#endregion
|
|
5281
5277
|
//#region \0dsh-plugin-shop-css:e3675a89
|
|
5282
|
-
const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (seven fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so seven brand opacities read as seven\n * shades of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.s8583e3_client.s942a79_config.s5f67aa_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` pins this. */\n\n.s255e5a_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The shelf never scrolls sideways: a card too wide for its track wraps\n * its badges rather than widening the panel. */\n overflow-x: hidden;\n}\n\n.s44a3ec_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.s3a0ece_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.se0c3b4_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s520416_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.s520416_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sf5179e_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sbb0d25_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n/* The ghost of the action line, right where the real buttons land. */\n.s96e910_skeletonActions {\n display: block;\n width: 84px;\n height: 24px;\n margin: 0 12px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s520416_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s520416_skeletonCard span { animation: none; opacity: 0.75; }\n .s520416_skeletonCard::after { content: none; }\n}\n\n.s73242a_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s73242a_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s73242a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s646509_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s745e3c_toolbarRight {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n/* The shop's own version, right of the search box: `v0.4.4`, plus the\n * update button when the version check found a newer release. */\n.s4fe61f_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.sb9b924_versionText {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa04455_updateSelfButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.sa04455_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.sa04455_updateSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The on-demand re-check next to the version number: a quiet pill; the\n * border and text carry it in the light theme where bg layers are white. */\n.s614a38_checkUpdateButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s614a38_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s614a38_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s614a38_checkUpdateButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The project's GitHub mark, right of the version row: a quiet icon link.\n * The octocat carries itself — color shift on hover, no text, no border. */\n.sa987f7_githubLink {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n border-radius: 6px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa987f7_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sa987f7_githubLink:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The self-update's expanded states (running log / done + restart /\n * failure detail) sit under the toolbar, above the category bar. */\n.sb81ba5_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s232ec4_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.s232ec4_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s1555aa_staleBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s3946f1_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.sb2c859_categoryBar {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s26d58e_categoryButton {\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s26d58e_categoryButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s5f68c4_categoryButtonOn {\n border-color: var(--dsw-alias-brand-primary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-weight: 600;\n}\n\n.s26d58e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s668774_catalogHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.sccc4d1_catalogStats {\n margin: 6px 0 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s6de1ab_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n/* Single-line rows: one full-width column, one plugin per line. */\n.sf657f5_cards {\n display: grid;\n grid-template-columns: 1fr;\n max-width: 100%;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n /* A grid item's min-width is auto by default: its content would widen the\n * 1fr track past the panel and overflow horizontally. */\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 140px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.sd7ee01_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s705cc6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s65f383_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s65f383_card { transition: none; }\n .s65f383_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.s4ce25d_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--spine-hue, #8B8E96);\n}\n\n.s65f383_card[data-category='tool'] { --spine-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'] { --spine-hue: #A78BFA; }\n.s65f383_card[data-category='ui'] { --spine-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'] { --spine-hue: #F59E0B; }\n.s65f383_card[data-category='integration'] { --spine-hue: #34D399; }\n.s65f383_card[data-category='theme'] { --spine-hue: #F472B6; }\n.s65f383_card[data-category='other'] { --spine-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.s9c8eaa_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--spine-hue, #8B8E96) 45%, transparent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--spine-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px 2px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sa97234_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name fills the row and truncates rather than wraps; the badges stay\n * intact beside it. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n flex: 1 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.s513026_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* The action line: buttons side by side; an active install/uninstall flow\n * (gate, log, notices) takes the full width below them. */\n.s9b5048_cardActions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 10px;\n}\n\n.s9b5048_cardActions > .sc4f2ac_gate,\n.s9b5048_cardActions > .s4d374c_installPanel,\n.s9b5048_cardActions > .sf53ca0_installedActions {\n flex-basis: 100%;\n}\n\n/* The entry's version, always visible on the card (the expanded detail\n * repeats it in the version row) — the same quiet mono as the toolbar. */\n.sc6ec6e_cardVersion {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The octocat marking a github-sourced entry, beside its short commit. */\n.s71f10f_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s5e82cb_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s5e82cb_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.s4bbe02_incompatibleBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sb8fbc8_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.se507db_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s10baf0_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb9dba9_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.s6eca19_incompatibleDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s9d2029_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3bdbd5_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s441702_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s441702_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sde3f0c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.se98179_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sfb4d2f_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sfb4d2f_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfb4d2f_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.sfb4d2f_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sfb4d2f_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sfb4d2f_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The publishing account, beside the npm link. Secondary label colour and a\n * middot separator carry the distinction — never a background fill, which is\n * invisible in the light theme where every bg-layer token is the same white. */\n/* Who published the entry, at the right edge of the action row. `margin-left:\n * auto` is what pushes it there; the row is a wrapping flex, so an active\n * install flow (which claims the full width) drops this to the next line\n * rather than squeezing it. */\n.s1ff66f_author {\n margin-left: auto;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sd58c6d_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.s4d374c_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.se7aaed_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.se7aaed_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb43a49_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sb43a49_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s6e45d1_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.s6e45d1_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se7aaed_installButton:focus-visible, .sb43a49_confirmButton:focus-visible, .s6e45d1_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The shelf card of a plugin that is installed and current carries this\n * non-interactive label instead of an install button. Border + text do the\n * work in the light theme, where every bg-layer token resolves to white. */\n.sb6fc74_installedLabel {\n align-self: flex-start;\n margin: 0;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An installed card's controls: the installed label or the update button,\n * plus the uninstall button. The install panel's running/failed states are\n * column layouts and sit as one item of this row. */\n.sf53ca0_installedActions {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Uninstall revokes privilege; the quiet outline keeps it from competing\n * with the primary action, and the error tint says \"removes\". */\n.s96967f_uninstallButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 55%, transparent);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s96967f_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s96967f_uninstallButton:focus-visible {\n outline: 2px solid var(--dsw-alias-state-error-primary);\n outline-offset: 1px;\n}\n\n/* The restart dsh button on a done install: quiet outline, same rank as\n * the other secondary actions. */\n.s6ac237_restartButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s6ac237_restartButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s6ac237_restartButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc4f2ac_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s3c0ace_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sc323e5_gateWarning {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s4b2373_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s52b5a5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64ab71_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s1531a8_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf20dda_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sfd3021_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s50ba35_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se75489_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* Installed section. */\n.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s5c2545_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.s877c68_outdatedVersions {\n /* Two inline labels with nothing between them in the markup ran together as\n * `installed v1.0.0latest v2.0.0`: JSX drops the whitespace-only line, and\n * the per-label class the markup asked for was never defined (singular\n * against this rule's plural). The container owns the gap, the same idiom\n * as .s513026_badges — the labels need no class of their own. */\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1f556_outdatedActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n/* The enable/disable switch. */\n.s11c019_switch {\n position: relative;\n width: 32px;\n height: 18px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n.s7580e8_switchOn {\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 55%, transparent);\n}\n\n.saab2b3_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s11c019_switch, .saab2b3_switchKnob { transition: none; }\n}\n\n/* The hot enable/disable switch on installed rows: the knob button plus the\n * §8 hot-apply / failure notices rendered beside it. */\n.sd9f9e4_switchWrap {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n";
|
|
5278
|
+
const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (seven fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so seven brand opacities read as seven\n * shades of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.s8583e3_client.s942a79_config.s5f67aa_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` pins this. */\n\n.s255e5a_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The shelf never scrolls sideways: a card too wide for its track wraps\n * its badges rather than widening the panel.\n *\n * `clip` rather than `hidden`, with a margin, because `.s232ec4_searchInput` sits\n * at exactly (0, 0) of this box — it is the first child of `.s646509_toolbar`,\n * itself the first child here, and this rule carries no padding — so its\n * focus ring paints ENTIRELY outside the panel. `hidden` clipped it at the\n * padding edge, and because the spec forbids one axis being `hidden` while\n * the other is `visible`, it silently promoted overflow-y to `auto` and\n * clipped the ring top and bottom as well; all that survived was a stray\n * arc on the right, where the 280px input stops short of the panel. `clip`\n * does not coerce the other axis, so overflow-y stays `visible`, and\n * `overflow-clip-margin` buys back the 3px the ring reaches sideways.\n * `hidden` stays as the first declaration, the fallback for engines\n * without `clip`; there the ring is clipped as before, never worse. */\n overflow-x: hidden;\n overflow-x: clip;\n overflow-clip-margin: 3px;\n}\n\n.s44a3ec_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.s3a0ece_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.se0c3b4_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s520416_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.s520416_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sf5179e_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sbb0d25_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n/* The ghost of the action line, right where the real buttons land. */\n.s96e910_skeletonActions {\n display: block;\n width: 84px;\n height: 24px;\n margin: 0 12px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s520416_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s520416_skeletonCard span { animation: none; opacity: 0.75; }\n .s520416_skeletonCard::after { content: none; }\n}\n\n.s73242a_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s73242a_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s73242a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s646509_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s745e3c_toolbarRight {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n/* The shop's own version, right of the search box: `v0.4.4`, plus the\n * update button when the version check found a newer release. */\n.s4fe61f_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.sb9b924_versionText {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa04455_updateSelfButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.sa04455_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.sa04455_updateSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The on-demand re-check next to the version number: a quiet pill; the\n * border and text carry it in the light theme where bg layers are white. */\n.s614a38_checkUpdateButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s614a38_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s614a38_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s614a38_checkUpdateButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The project's GitHub mark, right of the version row: a quiet icon link.\n * The octocat carries itself — color shift on hover, no text, no border. */\n.sa987f7_githubLink {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n border-radius: 6px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa987f7_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sa987f7_githubLink:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The self-update's expanded states (running log / done + restart /\n * failure detail) sit under the toolbar, above the category bar. */\n.sb81ba5_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s232ec4_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.s232ec4_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s1555aa_staleBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s3946f1_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.sb2c859_categoryBar {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s26d58e_categoryButton {\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s26d58e_categoryButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s5f68c4_categoryButtonOn {\n border-color: var(--dsw-alias-brand-primary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-weight: 600;\n}\n\n.s26d58e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s668774_catalogHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.sccc4d1_catalogStats {\n margin: 6px 0 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s6de1ab_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n/* Single-line rows: one full-width column, one plugin per line. */\n.sf657f5_cards {\n display: grid;\n grid-template-columns: 1fr;\n max-width: 100%;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n /* A grid item's min-width is auto by default: its content would widen the\n * 1fr track past the panel and overflow horizontally. */\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 140px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.sd7ee01_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s705cc6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s65f383_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s65f383_card { transition: none; }\n .s65f383_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.s4ce25d_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--spine-hue, #8B8E96);\n}\n\n.s65f383_card[data-category='tool'] { --spine-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'] { --spine-hue: #A78BFA; }\n.s65f383_card[data-category='ui'] { --spine-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'] { --spine-hue: #F59E0B; }\n.s65f383_card[data-category='integration'] { --spine-hue: #34D399; }\n.s65f383_card[data-category='theme'] { --spine-hue: #F472B6; }\n.s65f383_card[data-category='other'] { --spine-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.s9c8eaa_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--spine-hue, #8B8E96) 45%, transparent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--spine-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px 2px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sa97234_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name fills the row and truncates rather than wraps; the badges stay\n * intact beside it. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n flex: 1 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.s513026_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* The action line: buttons side by side; an active install/uninstall flow\n * (gate, log, notices) takes the full width below them. */\n.s9b5048_cardActions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 10px;\n}\n\n.s9b5048_cardActions > .sc4f2ac_gate,\n.s9b5048_cardActions > .s4d374c_installPanel,\n.s9b5048_cardActions > .sf53ca0_installedActions {\n flex-basis: 100%;\n}\n\n/* The entry's version, always visible on the card (the expanded detail\n * repeats it in the version row) — the same quiet mono as the toolbar. */\n.sc6ec6e_cardVersion {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The octocat marking a github-sourced entry, beside its short commit. */\n.s71f10f_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s5e82cb_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s5e82cb_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.s4bbe02_incompatibleBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sb8fbc8_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.se507db_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s10baf0_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb9dba9_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.s6eca19_incompatibleDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s9d2029_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3bdbd5_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s441702_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s441702_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sde3f0c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.se98179_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sfb4d2f_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sfb4d2f_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfb4d2f_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.sfb4d2f_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sfb4d2f_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sfb4d2f_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The publishing account, beside the npm link. Secondary label colour and a\n * middot separator carry the distinction — never a background fill, which is\n * invisible in the light theme where every bg-layer token is the same white. */\n/* Who published the entry, at the right edge of the action row. `margin-left:\n * auto` is what pushes it there; the row is a wrapping flex, so an active\n * install flow (which claims the full width) drops this to the next line\n * rather than squeezing it. */\n.s1ff66f_author {\n margin-left: auto;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sd58c6d_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.s4d374c_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.se7aaed_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.se7aaed_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb43a49_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sb43a49_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s6e45d1_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.s6e45d1_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se7aaed_installButton:focus-visible, .sb43a49_confirmButton:focus-visible, .s6e45d1_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The shelf card of a plugin that is installed and current carries this\n * non-interactive label instead of an install button. Border + text do the\n * work in the light theme, where every bg-layer token resolves to white. */\n.sb6fc74_installedLabel {\n align-self: flex-start;\n margin: 0;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An installed card's controls: the installed label or the update button,\n * plus the uninstall button. The install panel's running/failed states are\n * column layouts and sit as one item of this row. */\n.sf53ca0_installedActions {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Uninstall revokes privilege; the quiet outline keeps it from competing\n * with the primary action, and the error tint says \"removes\". */\n.s96967f_uninstallButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 55%, transparent);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s96967f_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s96967f_uninstallButton:focus-visible {\n outline: 2px solid var(--dsw-alias-state-error-primary);\n outline-offset: 1px;\n}\n\n/* The restart dsh button on a done install: quiet outline, same rank as\n * the other secondary actions. */\n.s6ac237_restartButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s6ac237_restartButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s6ac237_restartButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc4f2ac_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s3c0ace_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sc323e5_gateWarning {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s4b2373_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s52b5a5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64ab71_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s1531a8_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf20dda_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sfd3021_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s50ba35_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se75489_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* Installed section. */\n.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s5c2545_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.s877c68_outdatedVersions {\n /* Two inline labels with nothing between them in the markup ran together as\n * `installed v1.0.0latest v2.0.0`: JSX drops the whitespace-only line, and\n * the per-label class the markup asked for was never defined (singular\n * against this rule's plural). The container owns the gap, the same idiom\n * as .s513026_badges — the labels need no class of their own. */\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1f556_outdatedActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n/* The enable/disable switch. */\n.s11c019_switch {\n position: relative;\n width: 32px;\n height: 18px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n.s7580e8_switchOn {\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 55%, transparent);\n}\n\n.saab2b3_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s11c019_switch, .saab2b3_switchKnob { transition: none; }\n}\n\n/* The hot enable/disable switch on installed rows: the knob button plus the\n * §8 hot-apply / failure notices rendered beside it. */\n.sd9f9e4_switchWrap {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n";
|
|
5283
5279
|
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
|
|
5284
5280
|
const tag = document.createElement("style");
|
|
5285
5281
|
tag.dataset.pluginCss = "e3675a89";
|
|
@@ -5292,6 +5288,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5292
5288
|
"ts": "s5f67aa_ts",
|
|
5293
5289
|
"spec": "sfd9d8a_spec",
|
|
5294
5290
|
"panel": "s255e5a_panel",
|
|
5291
|
+
"searchInput": "s232ec4_searchInput",
|
|
5292
|
+
"toolbar": "s646509_toolbar",
|
|
5295
5293
|
"stateLine": "s44a3ec_stateLine",
|
|
5296
5294
|
"srOnly": "s3a0ece_srOnly",
|
|
5297
5295
|
"skeletonGrid": "se0c3b4_skeletonGrid",
|
|
@@ -5301,7 +5299,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5301
5299
|
"skeletonSummaryShort": "sbb0d25_skeletonSummaryShort",
|
|
5302
5300
|
"skeletonActions": "s96e910_skeletonActions",
|
|
5303
5301
|
"actionButton": "s73242a_actionButton",
|
|
5304
|
-
"toolbar": "s646509_toolbar",
|
|
5305
5302
|
"toolbarRight": "s745e3c_toolbarRight",
|
|
5306
5303
|
"versionBlock": "s4fe61f_versionBlock",
|
|
5307
5304
|
"versionText": "sb9b924_versionText",
|
|
@@ -5309,7 +5306,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5309
5306
|
"checkUpdateButton": "s614a38_checkUpdateButton",
|
|
5310
5307
|
"githubLink": "sa987f7_githubLink",
|
|
5311
5308
|
"selfUpdatePanel": "sb81ba5_selfUpdatePanel",
|
|
5312
|
-
"searchInput": "s232ec4_searchInput",
|
|
5313
5309
|
"staleBlock": "s1555aa_staleBlock",
|
|
5314
5310
|
"staleBadge": "s3946f1_staleBadge",
|
|
5315
5311
|
"categoryBar": "sb2c859_categoryBar",
|
package/lib/index.js
CHANGED
|
@@ -4,12 +4,12 @@ import { loadOptionalPatches, readProfileManifest, resolveProfileDir } from "@de
|
|
|
4
4
|
import { lt, minVersion, valid } from "semver";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
8
7
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
8
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
10
9
|
import { homedir } from "node:os";
|
|
11
10
|
import { z } from "zod";
|
|
12
11
|
import { gunzipSync } from "node:zlib";
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
13
|
import { JSON_SCHEMA, Type, dump, load } from "js-yaml";
|
|
14
14
|
//#region src/own-version.ts
|
|
15
15
|
/** The shop's own published version, read from the package.json that ships
|
|
@@ -167,7 +167,7 @@ function verifyIntegrity(bytes, integrity, registryUrl) {
|
|
|
167
167
|
const algorithm = dash === -1 ? "" : first.slice(0, dash);
|
|
168
168
|
const expected = dash === -1 ? "" : first.slice(dash + 1);
|
|
169
169
|
if (algorithm !== "sha512" && algorithm !== "sha256") throw new TransportError(`npm origin ${registryUrl}: unsupported dist.integrity algorithm ${JSON.stringify(algorithm)}`);
|
|
170
|
-
if (createHash(algorithm).update(bytes).digest("base64") !== expected) throw new
|
|
170
|
+
if (createHash(algorithm).update(bytes).digest("base64") !== expected) throw new TransportError(`npm origin ${registryUrl}: tarball failed dist.integrity check (${algorithm})`);
|
|
171
171
|
}
|
|
172
172
|
/** Normalise to a trailing slash so relative `URL` resolution against a
|
|
173
173
|
* registry that carries a path — every corporate registry, e.g.
|
|
@@ -739,6 +739,67 @@ function confirmBundleRemoval(profile, home, expectedName) {
|
|
|
739
739
|
return `removed but the profile manifest could not be read (${join(profileDir, "package.json")}) — re-run the uninstall`;
|
|
740
740
|
}
|
|
741
741
|
}
|
|
742
|
+
/** Lines a failed install's log tends to END on, none of which tell the user
|
|
743
|
+
* anything they can act on: dsh's own wrapper around the pnpm exit, node's
|
|
744
|
+
* version footer, pnpm's progress and update banner, and the frames and
|
|
745
|
+
* carets of a stack trace. */
|
|
746
|
+
const FAILURE_LOG_NOISE = [
|
|
747
|
+
/^dsh: pnpm failed in profile directory/,
|
|
748
|
+
/^Node\.js v/,
|
|
749
|
+
/^(?:Progress|Packages):/,
|
|
750
|
+
/^\++$/,
|
|
751
|
+
/^[╭│╰]/,
|
|
752
|
+
/^\^+$/,
|
|
753
|
+
/^\s*$/,
|
|
754
|
+
/^\s+at /,
|
|
755
|
+
/^<anonymous(?:_script)?>/
|
|
756
|
+
];
|
|
757
|
+
/**
|
|
758
|
+
* The one log line worth putting in front of the user, plus the recovery hint.
|
|
759
|
+
*
|
|
760
|
+
* The last line is the wrong choice, which is what this replaces. Measured on
|
|
761
|
+
* two real failures against the live catalog (dsh 0.1.1-rc.2, 2026-09-02),
|
|
762
|
+
* the last line was `dsh: pnpm failed in profile directory <a path the user
|
|
763
|
+
* did not choose>` and `Node.js v26.6.0` — while the line that explained the
|
|
764
|
+
* failure sat a few lines above, in the log the client already receives. A
|
|
765
|
+
* user seeing either of those cannot tell a blocked build script from a
|
|
766
|
+
* crash, which is exactly the report this fixes.
|
|
767
|
+
*
|
|
768
|
+
* Scanning from the end: a pnpm error code first, then any thrown error, then
|
|
769
|
+
* the last line that is not noise.
|
|
770
|
+
*/
|
|
771
|
+
function installFailureDetail(profile, log) {
|
|
772
|
+
const hint = `pnpm failed in the profile. Run: dsh plugin --profile ${profile} install`;
|
|
773
|
+
const usable = log.filter((line) => !FAILURE_LOG_NOISE.some((rx) => rx.test(line)));
|
|
774
|
+
const reversed = [...usable].reverse();
|
|
775
|
+
const pick = reversed.find((line) => /ERR_[A-Z][A-Z_]*/.test(line)) ?? reversed.find((line) => /(?:^|\s)\w*Error:/.test(line)) ?? usable[usable.length - 1];
|
|
776
|
+
if (pick === void 0) return `${hint}.`;
|
|
777
|
+
return `${hint} — ${pick}${/ERR_PNPM_IGNORED_BUILDS/.test(pick) ? " A dependency wants to run a build script, which pnpm blocks by default: run `pnpm approve-builds` in the profile directory to allow it, then retry." : ""}`;
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Why a spawn of the dsh CLI never started.
|
|
781
|
+
*
|
|
782
|
+
* On Windows this is the shop's own gap, not the user's setup, and saying
|
|
783
|
+
* otherwise sends them to reinstall something that already works. npm
|
|
784
|
+
* installs a CLI there as `dsh.cmd` and `dsh.ps1` shims with no `.exe`
|
|
785
|
+
* alongside them, while `spawn` without a shell goes to CreateProcess, which
|
|
786
|
+
* resolves a bare name against `.exe` only — and node has refused to spawn a
|
|
787
|
+
* `.cmd` without a shell since the 2024 batfile argument-injection fix, so a
|
|
788
|
+
* resolved absolute path to the shim fails too (EINVAL rather than ENOENT).
|
|
789
|
+
* There is no configuration that gets past this: `dshBin` can name the shim
|
|
790
|
+
* and the spawn still cannot run it. Reported from Windows 2026-09-02 as
|
|
791
|
+
* "Update failed / dsh not found on PATH" on a working install.
|
|
792
|
+
*
|
|
793
|
+
* `restart.ts` has the same gap one step further on — it runs a POSIX one
|
|
794
|
+
* liner through `sh` with `kill -0` and `sleep` — so a fixed spawn would
|
|
795
|
+
* reach a second Unix-only path. Neither is fixed here; this function only
|
|
796
|
+
* stops the report being wrong about the cause.
|
|
797
|
+
*/
|
|
798
|
+
function spawnFailureDetail(code, message, dshBin, platform) {
|
|
799
|
+
if (platform === "win32" && (code === "ENOENT" || code === "EINVAL")) return `the shop cannot start the dsh CLI on Windows yet (${dshBin}): npm installs it as a .cmd shim, which node cannot spawn without a shell. Not a problem with your install — run the update from \`dsh plugin --profile <name> add dsh-plugin-shop@<version>\` in a terminal until the shop can do it for you.`;
|
|
800
|
+
if (code === "ENOENT") return "dsh not found on PATH — install the dsh CLI to manage profile plugins";
|
|
801
|
+
return `dsh spawn failed: ${message}`;
|
|
802
|
+
}
|
|
742
803
|
/** Run one `dsh plugin --profile <profile> <verb> <target>` and track it.
|
|
743
804
|
* Never rolls back; a failure surfaces stderr verbatim plus the recovery hint
|
|
744
805
|
* (§10). The shop never passes build-script flags: `allowBuilds` stays the
|
|
@@ -806,9 +867,8 @@ function spawnPluginCli(options) {
|
|
|
806
867
|
for (const line of chunk.toString().split("\n")) if (line !== "") append(line);
|
|
807
868
|
});
|
|
808
869
|
child.on("error", (error) => {
|
|
809
|
-
const code = error.code;
|
|
810
870
|
state = "failed";
|
|
811
|
-
detail = code
|
|
871
|
+
detail = spawnFailureDetail(error.code, error.message, dshBin, process.platform);
|
|
812
872
|
onStatus?.(status());
|
|
813
873
|
resolve(status());
|
|
814
874
|
});
|
|
@@ -832,8 +892,7 @@ function spawnPluginCli(options) {
|
|
|
832
892
|
} else state = "done";
|
|
833
893
|
} else {
|
|
834
894
|
state = "failed";
|
|
835
|
-
|
|
836
|
-
detail = `pnpm failed in the profile. Run: dsh plugin --profile ${profile} install — ${lastLogLine}`;
|
|
895
|
+
detail = installFailureDetail(profile, log);
|
|
837
896
|
}
|
|
838
897
|
onStatus?.(status());
|
|
839
898
|
resolve(status());
|
|
@@ -1704,7 +1763,6 @@ let ShopGateway = (() => {
|
|
|
1704
1763
|
restartExitDelayMs;
|
|
1705
1764
|
restartParentPid;
|
|
1706
1765
|
latestVersion;
|
|
1707
|
-
hasGit;
|
|
1708
1766
|
pinFs;
|
|
1709
1767
|
allowRestart;
|
|
1710
1768
|
env;
|
|
@@ -1756,7 +1814,6 @@ let ShopGateway = (() => {
|
|
|
1756
1814
|
this.restartExitDelayMs = options.restartExitDelayMs ?? ShopGateway.RESTART_EXIT_DELAY_MS;
|
|
1757
1815
|
this.restartParentPid = options.restartParentPid ?? process.pid;
|
|
1758
1816
|
this.latestVersion = options.fetchLatestVersion ?? (() => fetchLatestVersion());
|
|
1759
|
-
this.hasGit = options.hasGit ?? (() => spawnSync("git", ["--version"], { stdio: "ignore" }).status === 0);
|
|
1760
1817
|
this.pinFs = options.pinFs ?? {
|
|
1761
1818
|
exists: (path) => existsSync(path),
|
|
1762
1819
|
read: (path) => readFileSync(path, "utf8"),
|
|
@@ -2014,11 +2071,6 @@ let ShopGateway = (() => {
|
|
|
2014
2071
|
code: "version-mismatch",
|
|
2015
2072
|
detail: `dsh-plugin-shop: ${args.name} has no installable commit`
|
|
2016
2073
|
};
|
|
2017
|
-
if (!this.hasGit()) return {
|
|
2018
|
-
ok: false,
|
|
2019
|
-
code: "git-missing",
|
|
2020
|
-
detail: "dsh-plugin-shop: git is not on PATH, which github installs require; install git and retry"
|
|
2021
|
-
};
|
|
2022
2074
|
spec = `github:${entry.repo}#${args.version}${entry.subdir !== void 0 ? `&path:${entry.subdir}` : ""}`;
|
|
2023
2075
|
} else spec = `${args.name}@${args.version}`;
|
|
2024
2076
|
const isUpdate = (readProfileManifest("dsh-plugin-shop", this.profileDirResolved()).dependencies ?? {})[args.name] !== void 0;
|
package/lib/typert.host.js
CHANGED
|
@@ -69,7 +69,7 @@ const dsh_plugin_shop_shop_installStart_result$schema = z.union([z.object({
|
|
|
69
69
|
'installId': z.string(),
|
|
70
70
|
}), z.object({
|
|
71
71
|
'ok': z.literal(false),
|
|
72
|
-
'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement"), z.literal("
|
|
72
|
+
'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement"), z.literal("tarball-integrity")]),
|
|
73
73
|
'detail': z.string(),
|
|
74
74
|
})])
|
|
75
75
|
const dsh_plugin_shop_shop_installStatus_parameter_0$schema = z.object({
|
|
@@ -154,7 +154,7 @@ export const TYPERT = {
|
|
|
154
154
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
155
155
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
156
156
|
},
|
|
157
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
157
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":562,"column":9},
|
|
158
158
|
},
|
|
159
159
|
{
|
|
160
160
|
id: 'dsh-plugin-shop#shop/installed',
|
|
@@ -169,7 +169,7 @@ export const TYPERT = {
|
|
|
169
169
|
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
170
170
|
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
171
171
|
},
|
|
172
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
172
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":747,"column":9},
|
|
173
173
|
},
|
|
174
174
|
{
|
|
175
175
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -195,7 +195,7 @@ export const TYPERT = {
|
|
|
195
195
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
196
196
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
197
197
|
},
|
|
198
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
198
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":612,"column":9},
|
|
199
199
|
},
|
|
200
200
|
{
|
|
201
201
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -220,7 +220,7 @@ export const TYPERT = {
|
|
|
220
220
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
221
221
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
222
222
|
},
|
|
223
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
223
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":736,"column":3},
|
|
224
224
|
},
|
|
225
225
|
{
|
|
226
226
|
id: 'dsh-plugin-shop#shop/restart',
|
|
@@ -235,7 +235,7 @@ export const TYPERT = {
|
|
|
235
235
|
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
236
236
|
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
237
237
|
},
|
|
238
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
238
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":897,"column":9},
|
|
239
239
|
},
|
|
240
240
|
{
|
|
241
241
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -260,7 +260,7 @@ export const TYPERT = {
|
|
|
260
260
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
261
261
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
262
262
|
},
|
|
263
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
263
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":460,"column":9},
|
|
264
264
|
},
|
|
265
265
|
{
|
|
266
266
|
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
@@ -286,7 +286,7 @@ export const TYPERT = {
|
|
|
286
286
|
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
287
287
|
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
288
288
|
},
|
|
289
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
289
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":839,"column":9},
|
|
290
290
|
},
|
|
291
291
|
{
|
|
292
292
|
id: 'dsh-plugin-shop#shop/updateStart',
|
|
@@ -311,7 +311,7 @@ export const TYPERT = {
|
|
|
311
311
|
typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
|
|
312
312
|
schema: dsh_plugin_shop_shop_updateStart_result$schema,
|
|
313
313
|
},
|
|
314
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
314
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":957,"column":9},
|
|
315
315
|
},
|
|
316
316
|
{
|
|
317
317
|
id: 'dsh-plugin-shop#shop/version',
|
|
@@ -326,7 +326,7 @@ export const TYPERT = {
|
|
|
326
326
|
typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
|
|
327
327
|
schema: dsh_plugin_shop_shop_version_result$schema,
|
|
328
328
|
},
|
|
329
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
329
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":941,"column":9},
|
|
330
330
|
},
|
|
331
331
|
],
|
|
332
332
|
model: {
|
|
@@ -436,7 +436,7 @@ export const TYPERT = {
|
|
|
436
436
|
},
|
|
437
437
|
{
|
|
438
438
|
"name": "InstallRejectionCode",
|
|
439
|
-
"declaration": "export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | '
|
|
439
|
+
"declaration": "export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | 'tarball-integrity';"
|
|
440
440
|
},
|
|
441
441
|
{
|
|
442
442
|
"name": "InstallState",
|
|
@@ -69,7 +69,7 @@ const dsh_plugin_shop_shop_installStart_result$schema = z.union([z.object({
|
|
|
69
69
|
'installId': z.string(),
|
|
70
70
|
}), z.object({
|
|
71
71
|
'ok': z.literal(false),
|
|
72
|
-
'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement"), z.literal("
|
|
72
|
+
'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement"), z.literal("tarball-integrity")]),
|
|
73
73
|
'detail': z.string(),
|
|
74
74
|
})])
|
|
75
75
|
const dsh_plugin_shop_shop_installStatus_parameter_0$schema = z.object({
|
|
@@ -151,7 +151,7 @@ export const TYPERT_REMOTE = {
|
|
|
151
151
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
152
152
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
153
153
|
},
|
|
154
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
154
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":562,"column":9},
|
|
155
155
|
},
|
|
156
156
|
{
|
|
157
157
|
id: 'dsh-plugin-shop#shop/installed',
|
|
@@ -166,7 +166,7 @@ export const TYPERT_REMOTE = {
|
|
|
166
166
|
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
167
167
|
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
168
168
|
},
|
|
169
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
169
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":747,"column":9},
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -192,7 +192,7 @@ export const TYPERT_REMOTE = {
|
|
|
192
192
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
193
193
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
194
194
|
},
|
|
195
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
195
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":612,"column":9},
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -217,7 +217,7 @@ export const TYPERT_REMOTE = {
|
|
|
217
217
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
218
218
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
219
219
|
},
|
|
220
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
220
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":736,"column":3},
|
|
221
221
|
},
|
|
222
222
|
{
|
|
223
223
|
id: 'dsh-plugin-shop#shop/restart',
|
|
@@ -232,7 +232,7 @@ export const TYPERT_REMOTE = {
|
|
|
232
232
|
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
233
233
|
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
234
234
|
},
|
|
235
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
235
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":897,"column":9},
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -257,7 +257,7 @@ export const TYPERT_REMOTE = {
|
|
|
257
257
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
258
258
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
259
259
|
},
|
|
260
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
260
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":460,"column":9},
|
|
261
261
|
},
|
|
262
262
|
{
|
|
263
263
|
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
@@ -283,7 +283,7 @@ export const TYPERT_REMOTE = {
|
|
|
283
283
|
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
284
284
|
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
285
285
|
},
|
|
286
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
286
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":839,"column":9},
|
|
287
287
|
},
|
|
288
288
|
{
|
|
289
289
|
id: 'dsh-plugin-shop#shop/updateStart',
|
|
@@ -308,7 +308,7 @@ export const TYPERT_REMOTE = {
|
|
|
308
308
|
typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
|
|
309
309
|
schema: dsh_plugin_shop_shop_updateStart_result$schema,
|
|
310
310
|
},
|
|
311
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
311
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":957,"column":9},
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
id: 'dsh-plugin-shop#shop/version',
|
|
@@ -323,7 +323,7 @@ export const TYPERT_REMOTE = {
|
|
|
323
323
|
typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
|
|
324
324
|
schema: dsh_plugin_shop_shop_version_result$schema,
|
|
325
325
|
},
|
|
326
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
326
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":941,"column":9},
|
|
327
327
|
},
|
|
328
328
|
],
|
|
329
329
|
}
|
|
@@ -56,7 +56,6 @@ export declare const zh: {
|
|
|
56
56
|
notInCatalogCode: string;
|
|
57
57
|
versionMismatchCode: string;
|
|
58
58
|
needsAcknowledgementCode: string;
|
|
59
|
-
gitMissingCode: string;
|
|
60
59
|
tarballIntegrityCode: string;
|
|
61
60
|
npmSource: string;
|
|
62
61
|
githubSource: string;
|
|
@@ -147,7 +146,6 @@ export declare const en: {
|
|
|
147
146
|
notInCatalogCode: string;
|
|
148
147
|
versionMismatchCode: string;
|
|
149
148
|
needsAcknowledgementCode: string;
|
|
150
|
-
gitMissingCode: string;
|
|
151
149
|
tarballIntegrityCode: string;
|
|
152
150
|
npmSource: string;
|
|
153
151
|
githubSource: string;
|
|
@@ -16,6 +16,41 @@ interface RunningInstall {
|
|
|
16
16
|
status: () => InstallStatus;
|
|
17
17
|
finished: Promise<InstallStatus>;
|
|
18
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* The one log line worth putting in front of the user, plus the recovery hint.
|
|
21
|
+
*
|
|
22
|
+
* The last line is the wrong choice, which is what this replaces. Measured on
|
|
23
|
+
* two real failures against the live catalog (dsh 0.1.1-rc.2, 2026-09-02),
|
|
24
|
+
* the last line was `dsh: pnpm failed in profile directory <a path the user
|
|
25
|
+
* did not choose>` and `Node.js v26.6.0` — while the line that explained the
|
|
26
|
+
* failure sat a few lines above, in the log the client already receives. A
|
|
27
|
+
* user seeing either of those cannot tell a blocked build script from a
|
|
28
|
+
* crash, which is exactly the report this fixes.
|
|
29
|
+
*
|
|
30
|
+
* Scanning from the end: a pnpm error code first, then any thrown error, then
|
|
31
|
+
* the last line that is not noise.
|
|
32
|
+
*/
|
|
33
|
+
export declare function installFailureDetail(profile: string, log: readonly string[]): string;
|
|
34
|
+
/**
|
|
35
|
+
* Why a spawn of the dsh CLI never started.
|
|
36
|
+
*
|
|
37
|
+
* On Windows this is the shop's own gap, not the user's setup, and saying
|
|
38
|
+
* otherwise sends them to reinstall something that already works. npm
|
|
39
|
+
* installs a CLI there as `dsh.cmd` and `dsh.ps1` shims with no `.exe`
|
|
40
|
+
* alongside them, while `spawn` without a shell goes to CreateProcess, which
|
|
41
|
+
* resolves a bare name against `.exe` only — and node has refused to spawn a
|
|
42
|
+
* `.cmd` without a shell since the 2024 batfile argument-injection fix, so a
|
|
43
|
+
* resolved absolute path to the shim fails too (EINVAL rather than ENOENT).
|
|
44
|
+
* There is no configuration that gets past this: `dshBin` can name the shim
|
|
45
|
+
* and the spawn still cannot run it. Reported from Windows 2026-09-02 as
|
|
46
|
+
* "Update failed / dsh not found on PATH" on a working install.
|
|
47
|
+
*
|
|
48
|
+
* `restart.ts` has the same gap one step further on — it runs a POSIX one
|
|
49
|
+
* liner through `sh` with `kill -0` and `sleep` — so a fixed spawn would
|
|
50
|
+
* reach a second Unix-only path. Neither is fixed here; this function only
|
|
51
|
+
* stops the report being wrong about the cause.
|
|
52
|
+
*/
|
|
53
|
+
export declare function spawnFailureDetail(code: string | undefined, message: string, dshBin: string, platform: NodeJS.Platform): string;
|
|
19
54
|
/**
|
|
20
55
|
* Run one `dsh plugin --profile <profile> add <spec>` and track it.
|
|
21
56
|
* When `expectedName` is given, a zero exit is confirmed against the profile
|
|
@@ -79,9 +79,6 @@ export interface ShopGatewayOptions {
|
|
|
79
79
|
/** How long the gateway waits after a successful restart response before
|
|
80
80
|
* exiting the old process; test-only shortening, production uses 2s. */
|
|
81
81
|
restartExitDelayMs?: number;
|
|
82
|
-
/** Whether `git` is on PATH — a github entry cannot install without it;
|
|
83
|
-
* test-only injection, production probes the real binary. */
|
|
84
|
-
hasGit?: () => boolean;
|
|
85
82
|
/** Test-only injection: the pins file's filesystem; production uses node:fs. */
|
|
86
83
|
pinFs?: RepoPinFs;
|
|
87
84
|
/** Test-only injection: the explicit `allowRestart` override; production
|
|
@@ -217,7 +214,6 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
217
214
|
private readonly restartExitDelayMs;
|
|
218
215
|
private readonly restartParentPid;
|
|
219
216
|
private readonly latestVersion;
|
|
220
|
-
private readonly hasGit;
|
|
221
217
|
private readonly pinFs;
|
|
222
218
|
private readonly allowRestart?;
|
|
223
219
|
private readonly env;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Install gate: the gate rejection paths of §7.2, as a pure function. */
|
|
2
2
|
import type { CatalogSnapshot } from './catalog.ts';
|
|
3
|
-
export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | '
|
|
3
|
+
export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | 'tarball-integrity';
|
|
4
4
|
export interface InstallArgs {
|
|
5
5
|
name: string;
|
|
6
6
|
version: string;
|
package/package.json
CHANGED