pressship 0.2.1 → 0.2.2
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/assets/web/app.js +868 -85
- package/assets/web/index.html +15 -6
- package/assets/web/style.css +462 -0
- package/dist/plugin/info.d.ts +1 -0
- package/dist/plugin/info.js +23 -0
- package/dist/plugin/info.js.map +1 -1
- package/dist/web/plugin-check-state.d.ts +1 -0
- package/dist/web/plugin-check-state.js +3 -0
- package/dist/web/plugin-check-state.js.map +1 -1
- package/dist/web/server.d.ts +2 -0
- package/dist/web/server.js +55 -10
- package/dist/web/server.js.map +1 -1
- package/package.json +1 -1
package/assets/web/app.js
CHANGED
|
@@ -219,6 +219,7 @@ function createInitialStudioRelease() {
|
|
|
219
219
|
newTagError: "",
|
|
220
220
|
customVersionDraft: "",
|
|
221
221
|
customVersionError: "",
|
|
222
|
+
skipReadmeValidation: false,
|
|
222
223
|
bumpInFlight: null,
|
|
223
224
|
bumpSuccess: null,
|
|
224
225
|
bumpError: "",
|
|
@@ -412,6 +413,8 @@ function onStudioResizerKeydown(event) {
|
|
|
412
413
|
|
|
413
414
|
const state = {
|
|
414
415
|
bootstrap: null,
|
|
416
|
+
pluginCheckSummaries: {},
|
|
417
|
+
latestWordPressVersion: "",
|
|
415
418
|
remote: [],
|
|
416
419
|
remoteUsername: "",
|
|
417
420
|
remoteLoading: true,
|
|
@@ -642,6 +645,12 @@ document.addEventListener("input", (event) => {
|
|
|
642
645
|
});
|
|
643
646
|
|
|
644
647
|
document.addEventListener("change", (event) => {
|
|
648
|
+
if (event.target?.id === "studio-skip-readme-validation") {
|
|
649
|
+
state.studio.release.skipReadmeValidation = Boolean(event.target.checked);
|
|
650
|
+
updateStudioSidebar();
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
|
|
645
654
|
if (event.target?.id !== "studio-plugin-picker") {
|
|
646
655
|
return;
|
|
647
656
|
}
|
|
@@ -921,6 +930,8 @@ async function boot() {
|
|
|
921
930
|
state.settings = state.bootstrap.settings ?? null;
|
|
922
931
|
state.playgrounds = state.bootstrap.playgrounds ?? [];
|
|
923
932
|
state.aiAssistance.harnesses = state.bootstrap.aiHarnesses ?? [];
|
|
933
|
+
state.pluginCheckSummaries = state.bootstrap.pluginCheckSummaries ?? {};
|
|
934
|
+
state.latestWordPressVersion = state.bootstrap.latestWordPressVersion ?? "";
|
|
924
935
|
const initialRoute = parseLocationRoute();
|
|
925
936
|
primeInitialRouteState(initialRoute);
|
|
926
937
|
renderAccount();
|
|
@@ -1991,7 +2002,7 @@ function normalizeStudioPlaygroundVersion(value) {
|
|
|
1991
2002
|
|
|
1992
2003
|
async function runStudioCheck() {
|
|
1993
2004
|
if (state.studio.scope !== "local" || !state.studio.id) {
|
|
1994
|
-
notice("
|
|
2005
|
+
notice("Verify is available for local plugins.", "warning");
|
|
1995
2006
|
return;
|
|
1996
2007
|
}
|
|
1997
2008
|
|
|
@@ -2008,13 +2019,22 @@ async function runStudioCheck() {
|
|
|
2008
2019
|
state.studio.checkRanAt = null;
|
|
2009
2020
|
state.studio.terminalOpen = true;
|
|
2010
2021
|
captureStudioEditorValue();
|
|
2011
|
-
appendStudioTerminal(
|
|
2022
|
+
appendStudioTerminal(
|
|
2023
|
+
state.studio.release.skipReadmeValidation
|
|
2024
|
+
? "Running verify with readme validator skipped…"
|
|
2025
|
+
: "Running verify with readme validation and Plugin Check…",
|
|
2026
|
+
"status"
|
|
2027
|
+
);
|
|
2012
2028
|
applyStudioCheckMarkers();
|
|
2013
2029
|
renderStudio();
|
|
2014
2030
|
remountStudioEditorIfNeeded();
|
|
2015
2031
|
updateStudioControls();
|
|
2016
2032
|
|
|
2017
|
-
const job = await createJob({
|
|
2033
|
+
const job = await createJob({
|
|
2034
|
+
type: "check",
|
|
2035
|
+
localId: state.studio.id,
|
|
2036
|
+
skipReadmeValidator: Boolean(state.studio.release.skipReadmeValidation)
|
|
2037
|
+
});
|
|
2018
2038
|
state.studio.checkJobId = job.id;
|
|
2019
2039
|
updateStudioControls();
|
|
2020
2040
|
}
|
|
@@ -2344,9 +2364,9 @@ function renderStudio() {
|
|
|
2344
2364
|
<span class="dashicons dashicons-saved" aria-hidden="true"></span>
|
|
2345
2365
|
<span>Save</span>
|
|
2346
2366
|
</button>
|
|
2347
|
-
<button class="studio-action-button studio-compact-button" type="button" data-action="studio-check" id="studio-check-button" aria-label="Run
|
|
2367
|
+
<button class="studio-action-button studio-compact-button" type="button" data-action="studio-check" id="studio-check-button" aria-label="Run Verify" title="Run Verify" disabled>
|
|
2348
2368
|
<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>
|
|
2349
|
-
<span>
|
|
2369
|
+
<span>Verify</span>
|
|
2350
2370
|
</button>
|
|
2351
2371
|
${renderStudioPackageSizeButton()}
|
|
2352
2372
|
${renderStudioThemeToggle()}
|
|
@@ -2478,7 +2498,7 @@ function renderStudioStatusBar() {
|
|
|
2478
2498
|
const file = state.studio.selectedFile?.path ?? "No file selected";
|
|
2479
2499
|
const check = state.studio.checkSummary
|
|
2480
2500
|
? `${state.studio.checkSummary.error || 0} errors, ${state.studio.checkSummary.warning || 0} warnings`
|
|
2481
|
-
: "
|
|
2501
|
+
: "Verify idle";
|
|
2482
2502
|
const assistant = selectedStudioAiAssistant();
|
|
2483
2503
|
return `
|
|
2484
2504
|
<footer class="studio-statusbar" aria-label="Studio status">
|
|
@@ -3658,10 +3678,10 @@ function renderStudioTerminal() {
|
|
|
3658
3678
|
function renderStudioCheckNotes() {
|
|
3659
3679
|
if (state.studio.checking) {
|
|
3660
3680
|
return `
|
|
3661
|
-
<aside class="studio-check-notes" aria-label="
|
|
3681
|
+
<aside class="studio-check-notes" aria-label="Validation notes">
|
|
3662
3682
|
<div class="studio-check-note studio-check-note-status">
|
|
3663
3683
|
<span class="dashicons dashicons-update" aria-hidden="true"></span>
|
|
3664
|
-
<span>
|
|
3684
|
+
<span>Verify is running…</span>
|
|
3665
3685
|
</div>
|
|
3666
3686
|
</aside>
|
|
3667
3687
|
`;
|
|
@@ -3675,19 +3695,19 @@ function renderStudioCheckNotes() {
|
|
|
3675
3695
|
if (!findings.length) {
|
|
3676
3696
|
const total = state.studio.checkSummary.total ?? 0;
|
|
3677
3697
|
return `
|
|
3678
|
-
<aside class="studio-check-notes" aria-label="
|
|
3698
|
+
<aside class="studio-check-notes" aria-label="Validation notes">
|
|
3679
3699
|
<div class="studio-check-note studio-check-note-${total ? "info" : "success"}">
|
|
3680
3700
|
<span class="dashicons ${total ? "dashicons-info-outline" : "dashicons-yes-alt"}" aria-hidden="true"></span>
|
|
3681
|
-
<span>${escapeHtml(total ? `${formatCheckCounts(state.studio.checkSummary)} in other files.` : "
|
|
3701
|
+
<span>${escapeHtml(total ? `${formatCheckCounts(state.studio.checkSummary)} in other files.` : "Verify reported no findings.")}</span>
|
|
3682
3702
|
</div>
|
|
3683
3703
|
</aside>
|
|
3684
3704
|
`;
|
|
3685
3705
|
}
|
|
3686
3706
|
|
|
3687
3707
|
return `
|
|
3688
|
-
<aside class="studio-check-notes" aria-label="
|
|
3708
|
+
<aside class="studio-check-notes" aria-label="Validation notes">
|
|
3689
3709
|
<header>
|
|
3690
|
-
<strong>
|
|
3710
|
+
<strong>Validation</strong>
|
|
3691
3711
|
<span>${escapeHtml(formatCheckCounts(studioCheckCountsForPath(state.studio.selectedFile?.path)))}</span>
|
|
3692
3712
|
</header>
|
|
3693
3713
|
<div class="studio-check-note-list">
|
|
@@ -3959,7 +3979,7 @@ function studioCliCommandForJob(input) {
|
|
|
3959
3979
|
return studioCliCommand([
|
|
3960
3980
|
"verify",
|
|
3961
3981
|
localPluginCliTarget(input.localId),
|
|
3962
|
-
"--skip-readme-validator",
|
|
3982
|
+
...(input.skipReadmeValidator ? ["--skip-readme-validator"] : []),
|
|
3963
3983
|
...studioCliIgnoreFlags(input.localId)
|
|
3964
3984
|
]);
|
|
3965
3985
|
case "dry-run-publish":
|
|
@@ -4040,6 +4060,18 @@ function applyStudioCheckState(checkState) {
|
|
|
4040
4060
|
state.studio.checkFindings = checkState.findings ?? [];
|
|
4041
4061
|
state.studio.checkSummary = checkState.summary ?? null;
|
|
4042
4062
|
state.studio.checkRanAt = checkState.checkedAt ?? null;
|
|
4063
|
+
|
|
4064
|
+
if (state.studio.id && state.studio.scope === "local" && checkState.summary) {
|
|
4065
|
+
state.pluginCheckSummaries[state.studio.id] = {
|
|
4066
|
+
slug: checkState.slug ?? state.studio.plugin?.slug,
|
|
4067
|
+
name: checkState.name ?? state.studio.plugin?.name,
|
|
4068
|
+
checkedAt: checkState.checkedAt,
|
|
4069
|
+
skipped: checkState.skipped ?? false,
|
|
4070
|
+
available: checkState.available ?? true,
|
|
4071
|
+
summary: checkState.summary
|
|
4072
|
+
};
|
|
4073
|
+
renderDashboard();
|
|
4074
|
+
}
|
|
4043
4075
|
}
|
|
4044
4076
|
|
|
4045
4077
|
function applyStudioIgnoreState(ignoreState) {
|
|
@@ -4693,11 +4725,11 @@ function updateStudioControls() {
|
|
|
4693
4725
|
}
|
|
4694
4726
|
if (studioCheck) {
|
|
4695
4727
|
studioCheck.disabled = !canCheck;
|
|
4696
|
-
studioCheck.setAttribute("aria-label", state.studio.checkSummary ? "Re-run
|
|
4697
|
-
studioCheck.title = state.studio.checkSummary ? "Re-run
|
|
4728
|
+
studioCheck.setAttribute("aria-label", state.studio.checkSummary ? "Re-run Verify" : "Run Verify");
|
|
4729
|
+
studioCheck.title = state.studio.checkSummary ? "Re-run Verify" : "Run Verify";
|
|
4698
4730
|
studioCheck.innerHTML = state.studio.checking
|
|
4699
|
-
? `<span class="dashicons dashicons-update" aria-hidden="true"></span><span>
|
|
4700
|
-
: `<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span><span>${state.studio.checkSummary ? "Re-
|
|
4731
|
+
? `<span class="dashicons dashicons-update" aria-hidden="true"></span><span>Verifying</span>`
|
|
4732
|
+
: `<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span><span>${state.studio.checkSummary ? "Re-verify" : "Verify"}</span>`;
|
|
4701
4733
|
}
|
|
4702
4734
|
if (studioSave) {
|
|
4703
4735
|
studioSave.disabled = !canSaveStudioFile();
|
|
@@ -5329,13 +5361,17 @@ function renderDashboard() {
|
|
|
5329
5361
|
<div id="dashboard-widgets" class="metabox-holder columns-2">
|
|
5330
5362
|
<div id="postbox-container-1" class="postbox-container">
|
|
5331
5363
|
<div class="meta-box-sortables">
|
|
5364
|
+
${renderDashboardOnboardingCard()}
|
|
5332
5365
|
${renderDashboardLocalWidget()}
|
|
5333
|
-
${
|
|
5366
|
+
${renderDashboardReleaseReadinessWidget()}
|
|
5367
|
+
${renderDashboardActivityWidget()}
|
|
5334
5368
|
</div>
|
|
5335
5369
|
</div>
|
|
5336
5370
|
<div id="postbox-container-2" class="postbox-container">
|
|
5337
5371
|
<div class="meta-box-sortables">
|
|
5338
5372
|
${renderDashboardAtGlanceWidget()}
|
|
5373
|
+
${renderDashboardPluginCheckWidget()}
|
|
5374
|
+
${renderDashboardCompatibilityWidget()}
|
|
5339
5375
|
${renderDashboardPlaygroundsCard()}
|
|
5340
5376
|
${renderDashboardAccountCard()}
|
|
5341
5377
|
</div>
|
|
@@ -5423,8 +5459,6 @@ function renderDashboardAtGlanceWidget() {
|
|
|
5423
5459
|
const localLabel = state.local.length === 1 ? "local plugin" : "local plugins";
|
|
5424
5460
|
const remoteLabel = state.remote.length === 1 ? "WordPress.org plugin" : "WordPress.org plugins";
|
|
5425
5461
|
const playgroundLabel = state.playgrounds.length === 1 ? "Playground running" : "Playgrounds running";
|
|
5426
|
-
const account = state.bootstrap?.account?.username;
|
|
5427
|
-
const accountLabel = account ? `Signed in as ${account}` : "WordPress.org not connected";
|
|
5428
5462
|
|
|
5429
5463
|
return renderDashboardPostbox({
|
|
5430
5464
|
id: "dashboard-at-a-glance",
|
|
@@ -5453,14 +5487,51 @@ function renderDashboardAtGlanceWidget() {
|
|
|
5453
5487
|
view: "studio"
|
|
5454
5488
|
})}
|
|
5455
5489
|
</ul>
|
|
5456
|
-
|
|
5457
|
-
<span class="dashicons ${account ? "dashicons-yes-alt" : "dashicons-warning"}" aria-hidden="true"></span>
|
|
5458
|
-
<span>${escapeHtml(accountLabel)}</span>
|
|
5459
|
-
</div>
|
|
5490
|
+
${renderDashboardReleaseStatusLine()}
|
|
5460
5491
|
`
|
|
5461
5492
|
});
|
|
5462
5493
|
}
|
|
5463
5494
|
|
|
5495
|
+
function renderDashboardReleaseStatusLine() {
|
|
5496
|
+
if (state.localLoading && !state.local.length) {
|
|
5497
|
+
return `
|
|
5498
|
+
<div class="ps-dashboard-status">
|
|
5499
|
+
<span class="dashicons dashicons-update" aria-hidden="true"></span>
|
|
5500
|
+
<span>Checking release state…</span>
|
|
5501
|
+
</div>
|
|
5502
|
+
`;
|
|
5503
|
+
}
|
|
5504
|
+
|
|
5505
|
+
const summary = dashboardReleaseSummary();
|
|
5506
|
+
let tone = "info";
|
|
5507
|
+
let icon = "dashicons-info";
|
|
5508
|
+
let text = "No local plugins to release yet.";
|
|
5509
|
+
|
|
5510
|
+
if (summary.total) {
|
|
5511
|
+
if (summary.blocked > 0) {
|
|
5512
|
+
tone = "error";
|
|
5513
|
+
icon = "dashicons-warning";
|
|
5514
|
+
text = `${summary.blocked} blocked from release · ${summary.ready} ready`;
|
|
5515
|
+
} else if (summary.behind > 0) {
|
|
5516
|
+
tone = "warning";
|
|
5517
|
+
icon = "dashicons-update";
|
|
5518
|
+
text = `${summary.behind} behind WordPress.org · ${summary.ready} ready`;
|
|
5519
|
+
} else {
|
|
5520
|
+
tone = "success";
|
|
5521
|
+
icon = "dashicons-yes-alt";
|
|
5522
|
+
text = `All ${summary.ready} plugin${summary.ready === 1 ? "" : "s"} ready to ship`;
|
|
5523
|
+
}
|
|
5524
|
+
}
|
|
5525
|
+
|
|
5526
|
+
return `
|
|
5527
|
+
<button class="ps-dashboard-status ps-dashboard-status-${escapeAttr(tone)}" type="button" data-view-button="release">
|
|
5528
|
+
<span class="dashicons ${escapeAttr(icon)}" aria-hidden="true"></span>
|
|
5529
|
+
<span>${escapeHtml(text)}</span>
|
|
5530
|
+
<span class="dashicons dashicons-arrow-right-alt2 ps-dashboard-status-arrow" aria-hidden="true"></span>
|
|
5531
|
+
</button>
|
|
5532
|
+
`;
|
|
5533
|
+
}
|
|
5534
|
+
|
|
5464
5535
|
function renderDashboardGlanceItem({ icon, value, label, view, loading }) {
|
|
5465
5536
|
return `
|
|
5466
5537
|
<li class="ps-glance-item${loading ? " is-loading" : ""}">
|
|
@@ -5609,11 +5680,94 @@ function renderDashboardPlaygroundsCard() {
|
|
|
5609
5680
|
`;
|
|
5610
5681
|
}
|
|
5611
5682
|
|
|
5683
|
+
function dashboardRemoteSummary() {
|
|
5684
|
+
let cloned = 0;
|
|
5685
|
+
let committer = 0;
|
|
5686
|
+
let contributor = 0;
|
|
5687
|
+
|
|
5688
|
+
for (const plugin of state.remote) {
|
|
5689
|
+
if (remotePluginLocalState(plugin).entry) {
|
|
5690
|
+
cloned += 1;
|
|
5691
|
+
}
|
|
5692
|
+
const roles = Array.isArray(plugin.roles) ? plugin.roles : [];
|
|
5693
|
+
if (roles.includes("committer")) {
|
|
5694
|
+
committer += 1;
|
|
5695
|
+
} else if (roles.includes("contributor")) {
|
|
5696
|
+
contributor += 1;
|
|
5697
|
+
}
|
|
5698
|
+
}
|
|
5699
|
+
|
|
5700
|
+
const total = state.remote.length;
|
|
5701
|
+
return { total, cloned, notCloned: Math.max(0, total - cloned), committer, contributor };
|
|
5702
|
+
}
|
|
5703
|
+
|
|
5612
5704
|
function renderDashboardAccountCard() {
|
|
5613
|
-
const account = state.bootstrap?.account
|
|
5705
|
+
const account = state.bootstrap?.account;
|
|
5706
|
+
const username = account?.username;
|
|
5707
|
+
const displayName = account?.displayName;
|
|
5708
|
+
const profileUrl = account?.profileUrl;
|
|
5614
5709
|
const loggedIn = Boolean(state.bootstrap?.loggedIn);
|
|
5615
5710
|
const tone = loggedIn ? "success" : "warning";
|
|
5616
5711
|
const label = loggedIn ? "Signed in" : "Not signed in";
|
|
5712
|
+
|
|
5713
|
+
if (!loggedIn) {
|
|
5714
|
+
return renderDashboardPostbox({
|
|
5715
|
+
id: "dashboard-wordpress-org",
|
|
5716
|
+
title: "WordPress.org",
|
|
5717
|
+
icon: "dashicons-admin-users",
|
|
5718
|
+
actions: badge(label, tone),
|
|
5719
|
+
body: `
|
|
5720
|
+
<div class="ps-account-body">
|
|
5721
|
+
<div class="ps-account-row">
|
|
5722
|
+
<span class="ps-account-icon" aria-hidden="true">
|
|
5723
|
+
<span class="dashicons dashicons-admin-users"></span>
|
|
5724
|
+
</span>
|
|
5725
|
+
<span class="ps-account-text">
|
|
5726
|
+
<strong>Not connected</strong>
|
|
5727
|
+
<small>Run <code>pressship login</code> in a terminal to clone, submit, and release.</small>
|
|
5728
|
+
</span>
|
|
5729
|
+
</div>
|
|
5730
|
+
<div class="ps-widget-footer">
|
|
5731
|
+
<button class="button button-ghost button-small" type="button" data-view-button="settings">
|
|
5732
|
+
Settings
|
|
5733
|
+
<span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span>
|
|
5734
|
+
</button>
|
|
5735
|
+
</div>
|
|
5736
|
+
</div>
|
|
5737
|
+
`
|
|
5738
|
+
});
|
|
5739
|
+
}
|
|
5740
|
+
|
|
5741
|
+
const summary = dashboardRemoteSummary();
|
|
5742
|
+
const remoteValue = state.remoteLoading ? "…" : String(summary.total);
|
|
5743
|
+
const clonedValue = state.remoteLoading ? "…" : String(summary.cloned);
|
|
5744
|
+
|
|
5745
|
+
const reach = dashboardReachSummary();
|
|
5746
|
+
const reachBlock = !state.remoteLoading && reach.known
|
|
5747
|
+
? `<div class="ps-account-reach">
|
|
5748
|
+
<span class="ps-account-reach-value">${escapeHtml(formatInstallCount(reach.total))}${reach.plus ? "+" : ""}</span>
|
|
5749
|
+
<span class="ps-account-reach-label">combined active installs${reach.topName ? ` · ${escapeHtml(reach.topName)} leads` : ""}</span>
|
|
5750
|
+
</div>`
|
|
5751
|
+
: "";
|
|
5752
|
+
|
|
5753
|
+
const roleParts = [];
|
|
5754
|
+
if (summary.committer) {
|
|
5755
|
+
roleParts.push(`Committer on ${summary.committer}`);
|
|
5756
|
+
}
|
|
5757
|
+
if (summary.contributor) {
|
|
5758
|
+
roleParts.push(`Contributor on ${summary.contributor}`);
|
|
5759
|
+
}
|
|
5760
|
+
const roleLine = !state.remoteLoading && roleParts.length
|
|
5761
|
+
? `<div class="ps-account-roles"><span class="dashicons dashicons-groups" aria-hidden="true"></span><span>${escapeHtml(roleParts.join(" · "))}</span></div>`
|
|
5762
|
+
: "";
|
|
5763
|
+
|
|
5764
|
+
const profileLink = profileUrl
|
|
5765
|
+
? `<a class="button button-ghost button-small" href="${escapeAttr(profileUrl)}" target="_blank" rel="noopener noreferrer">
|
|
5766
|
+
<span class="dashicons dashicons-external" aria-hidden="true"></span>
|
|
5767
|
+
View profile
|
|
5768
|
+
</a>`
|
|
5769
|
+
: "";
|
|
5770
|
+
|
|
5617
5771
|
return renderDashboardPostbox({
|
|
5618
5772
|
id: "dashboard-wordpress-org",
|
|
5619
5773
|
title: "WordPress.org",
|
|
@@ -5626,11 +5780,28 @@ function renderDashboardAccountCard() {
|
|
|
5626
5780
|
<span class="dashicons dashicons-admin-users"></span>
|
|
5627
5781
|
</span>
|
|
5628
5782
|
<span class="ps-account-text">
|
|
5629
|
-
<strong>${escapeHtml(
|
|
5630
|
-
<small>${escapeHtml(
|
|
5783
|
+
<strong>${escapeHtml(displayName || username || "WordPress.org account")}</strong>
|
|
5784
|
+
<small>${escapeHtml(username ? `@${username}` : "Used for clone, submit, and release.")}</small>
|
|
5631
5785
|
</span>
|
|
5632
5786
|
</div>
|
|
5787
|
+
${reachBlock}
|
|
5788
|
+
<ul class="ps-account-stats">
|
|
5789
|
+
<li>
|
|
5790
|
+
<button class="ps-account-stat" type="button" data-view-button="remote">
|
|
5791
|
+
<strong>${escapeHtml(remoteValue)}</strong>
|
|
5792
|
+
<span>on WordPress.org</span>
|
|
5793
|
+
</button>
|
|
5794
|
+
</li>
|
|
5795
|
+
<li>
|
|
5796
|
+
<button class="ps-account-stat" type="button" data-view-button="local">
|
|
5797
|
+
<strong>${escapeHtml(clonedValue)}</strong>
|
|
5798
|
+
<span>cloned locally</span>
|
|
5799
|
+
</button>
|
|
5800
|
+
</li>
|
|
5801
|
+
</ul>
|
|
5802
|
+
${roleLine}
|
|
5633
5803
|
<div class="ps-widget-footer">
|
|
5804
|
+
${profileLink}
|
|
5634
5805
|
<button class="button button-ghost button-small" type="button" data-view-button="settings">
|
|
5635
5806
|
Settings
|
|
5636
5807
|
<span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span>
|
|
@@ -5641,73 +5812,681 @@ function renderDashboardAccountCard() {
|
|
|
5641
5812
|
});
|
|
5642
5813
|
}
|
|
5643
5814
|
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5815
|
+
const VERSION_STATUS_META = {
|
|
5816
|
+
missing_version: { label: "Missing version", tone: "error", icon: "dashicons-warning", action: "manage-release", actionLabel: "Fix" },
|
|
5817
|
+
header_readme_mismatch: { label: "Version mismatch", tone: "error", icon: "dashicons-randomize", action: "manage-release", actionLabel: "Fix" },
|
|
5818
|
+
duplicate_tag_blocked: { label: "Tag already shipped", tone: "error", icon: "dashicons-tag", action: "manage-release", actionLabel: "Bump" },
|
|
5819
|
+
remote_newer: { label: "Behind WordPress.org", tone: "warning", icon: "dashicons-update", action: "version-state", actionLabel: "Details" },
|
|
5820
|
+
unknown_svn_state: { label: "SVN state unknown", tone: "warning", icon: "dashicons-editor-help", action: "version-state", actionLabel: "Details" },
|
|
5821
|
+
ready: { label: "Ready", tone: "success", icon: "dashicons-yes-alt", action: "manage-release", actionLabel: "Release" }
|
|
5822
|
+
};
|
|
5823
|
+
|
|
5824
|
+
const BLOCKING_STATUS_ORDER = ["missing_version", "header_readme_mismatch", "duplicate_tag_blocked"];
|
|
5825
|
+
|
|
5826
|
+
function versionStatusMeta(status) {
|
|
5827
|
+
return VERSION_STATUS_META[status] ?? { label: labelize(status), tone: "info", icon: "dashicons-info", action: "version-state", actionLabel: "Details" };
|
|
5828
|
+
}
|
|
5829
|
+
|
|
5830
|
+
function primaryVersionIssue(versionState) {
|
|
5831
|
+
const statuses = versionState?.statuses ?? [];
|
|
5832
|
+
const status =
|
|
5833
|
+
BLOCKING_STATUS_ORDER.find((candidate) => statuses.includes(candidate)) ??
|
|
5834
|
+
(statuses.includes("remote_newer") ? "remote_newer" : statuses.find((candidate) => candidate !== "ready"));
|
|
5835
|
+
if (!status) {
|
|
5836
|
+
return null;
|
|
5837
|
+
}
|
|
5838
|
+
const meta = versionStatusMeta(status);
|
|
5839
|
+
const messageIndex = statuses.indexOf(status);
|
|
5840
|
+
const message = versionState.messages?.[messageIndex] ?? versionState.messages?.[0] ?? meta.label;
|
|
5841
|
+
return { status, message, ...meta };
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5844
|
+
function dashboardReleaseSummary() {
|
|
5845
|
+
let ready = 0;
|
|
5846
|
+
let blocked = 0;
|
|
5847
|
+
let behind = 0;
|
|
5848
|
+
let unknown = 0;
|
|
5849
|
+
const attention = [];
|
|
5850
|
+
|
|
5851
|
+
for (const plugin of state.local) {
|
|
5852
|
+
const versionState = state.versionStates.get(plugin.id);
|
|
5853
|
+
if (!versionState || versionState.error || !Array.isArray(versionState.statuses)) {
|
|
5854
|
+
unknown += 1;
|
|
5855
|
+
continue;
|
|
5856
|
+
}
|
|
5857
|
+
|
|
5858
|
+
const isBehind = versionState.statuses.includes("remote_newer");
|
|
5859
|
+
if (versionState.releaseBlocked) {
|
|
5860
|
+
blocked += 1;
|
|
5861
|
+
} else if (isBehind) {
|
|
5862
|
+
behind += 1;
|
|
5863
|
+
} else {
|
|
5864
|
+
ready += 1;
|
|
5865
|
+
}
|
|
5866
|
+
|
|
5867
|
+
if (versionState.releaseBlocked || isBehind) {
|
|
5868
|
+
const issue = primaryVersionIssue(versionState);
|
|
5869
|
+
if (issue) {
|
|
5870
|
+
attention.push({ plugin, versionState, issue });
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
}
|
|
5874
|
+
|
|
5875
|
+
attention.sort((left, right) => issueWeight(right.issue) - issueWeight(left.issue));
|
|
5876
|
+
return { total: state.local.length, ready, blocked, behind, unknown, attention };
|
|
5877
|
+
}
|
|
5878
|
+
|
|
5879
|
+
function issueWeight(issue) {
|
|
5880
|
+
if (issue.tone === "error") return 2;
|
|
5881
|
+
if (issue.tone === "warning") return 1;
|
|
5882
|
+
return 0;
|
|
5883
|
+
}
|
|
5884
|
+
|
|
5885
|
+
function renderDashboardReleaseReadinessWidget() {
|
|
5886
|
+
const loading = state.localLoading && !state.local.length;
|
|
5887
|
+
const summary = dashboardReleaseSummary();
|
|
5650
5888
|
|
|
5651
5889
|
let tone = "info";
|
|
5652
|
-
let label = "
|
|
5653
|
-
if (
|
|
5654
|
-
|
|
5655
|
-
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5890
|
+
let label = "—";
|
|
5891
|
+
if (!loading) {
|
|
5892
|
+
if (!summary.total) {
|
|
5893
|
+
tone = "info";
|
|
5894
|
+
label = "No plugins";
|
|
5895
|
+
} else if (summary.blocked > 0) {
|
|
5896
|
+
tone = "error";
|
|
5897
|
+
label = `${summary.blocked} blocked`;
|
|
5898
|
+
} else if (summary.behind > 0) {
|
|
5899
|
+
tone = "warning";
|
|
5900
|
+
label = `${summary.behind} behind`;
|
|
5901
|
+
} else {
|
|
5902
|
+
tone = "success";
|
|
5903
|
+
label = "All ready";
|
|
5904
|
+
}
|
|
5905
|
+
}
|
|
5906
|
+
|
|
5907
|
+
return renderDashboardPostbox({
|
|
5908
|
+
id: "dashboard-release-readiness",
|
|
5909
|
+
title: "Release readiness",
|
|
5910
|
+
icon: "ps-icon-rocket",
|
|
5911
|
+
className: "ps-dashboard-main",
|
|
5912
|
+
actions: loading ? "" : badge(label, tone),
|
|
5913
|
+
body: renderDashboardReadinessBody(summary, loading)
|
|
5914
|
+
});
|
|
5915
|
+
}
|
|
5916
|
+
|
|
5917
|
+
function renderDashboardReadinessBody(summary, loading) {
|
|
5918
|
+
if (loading) {
|
|
5919
|
+
return `
|
|
5920
|
+
<p class="ps-widget-intro">Checking version and release state for every local plugin…</p>
|
|
5921
|
+
${renderDashboardSkeletonRows(2)}
|
|
5922
|
+
`;
|
|
5923
|
+
}
|
|
5924
|
+
|
|
5925
|
+
if (!summary.total) {
|
|
5926
|
+
return `
|
|
5927
|
+
<div class="ps-empty-card">
|
|
5928
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
5929
|
+
<strong>Nothing to release yet</strong>
|
|
5930
|
+
<p>Add a local plugin folder, then Pressship flags version mismatches and duplicate tags before you publish.</p>
|
|
5931
|
+
<div class="ps-empty-card-actions">
|
|
5932
|
+
<button class="button button-primary" type="button" data-action="choose-local-folder">
|
|
5933
|
+
<span class="dashicons dashicons-open-folder" aria-hidden="true"></span>
|
|
5934
|
+
Choose Folder
|
|
5935
|
+
</button>
|
|
5936
|
+
</div>
|
|
5937
|
+
</div>
|
|
5938
|
+
`;
|
|
5939
|
+
}
|
|
5940
|
+
|
|
5941
|
+
if (!summary.attention.length) {
|
|
5942
|
+
return `
|
|
5943
|
+
<div class="ps-readiness-clear">
|
|
5944
|
+
<span class="ps-readiness-clear-icon" aria-hidden="true">
|
|
5945
|
+
<span class="dashicons dashicons-yes-alt"></span>
|
|
5674
5946
|
</span>
|
|
5947
|
+
<div class="ps-readiness-clear-text">
|
|
5948
|
+
<strong>Everything looks release-ready</strong>
|
|
5949
|
+
<p>${escapeHtml(`All ${summary.ready} plugin${summary.ready === 1 ? "" : "s"} pass version checks. Open Release Management to ship.`)}</p>
|
|
5950
|
+
</div>
|
|
5951
|
+
<button class="button button-primary button-small" type="button" data-view-button="release">
|
|
5952
|
+
<span class="dashicons ps-icon-rocket" aria-hidden="true"></span>
|
|
5953
|
+
Release Management
|
|
5954
|
+
</button>
|
|
5955
|
+
</div>
|
|
5956
|
+
`;
|
|
5957
|
+
}
|
|
5958
|
+
|
|
5959
|
+
const visible = summary.attention.slice(0, 5);
|
|
5960
|
+
const rows = visible.map(dashboardReadinessRow).join("");
|
|
5961
|
+
const overflow = summary.attention.length > visible.length
|
|
5962
|
+
? `<div class="ps-widget-footer"><button class="button button-ghost button-small" type="button" data-view-button="release">See all ${summary.attention.length} flagged plugins<span class="dashicons dashicons-arrow-right-alt2" aria-hidden="true"></span></button></div>`
|
|
5963
|
+
: "";
|
|
5964
|
+
|
|
5965
|
+
const blockedNote = summary.blocked
|
|
5966
|
+
? `${summary.blocked} blocked from release`
|
|
5967
|
+
: `${summary.behind} behind WordPress.org`;
|
|
5968
|
+
|
|
5969
|
+
return `
|
|
5970
|
+
<p class="ps-widget-intro">${escapeHtml(`${summary.attention.length} plugin${summary.attention.length === 1 ? "" : "s"} need a fix before release — ${blockedNote}.`)}</p>
|
|
5971
|
+
<ul class="ps-attention-list">${rows}</ul>
|
|
5972
|
+
${overflow}
|
|
5973
|
+
`;
|
|
5974
|
+
}
|
|
5975
|
+
|
|
5976
|
+
function dashboardReadinessRow({ plugin, issue }) {
|
|
5977
|
+
const name = plugin.name || plugin.slug || plugin.id;
|
|
5978
|
+
return `
|
|
5979
|
+
<li class="ps-attention-row ps-attention-${escapeAttr(issue.tone)}">
|
|
5980
|
+
<span class="ps-attention-icon" aria-hidden="true">
|
|
5981
|
+
<span class="dashicons ${escapeAttr(issue.icon)}"></span>
|
|
5982
|
+
</span>
|
|
5983
|
+
<button class="ps-attention-main" type="button" data-action="studio" data-scope="local" data-id="${escapeAttr(plugin.id)}" title="Open ${escapeAttr(name)} in Studio">
|
|
5984
|
+
<span class="ps-attention-text">
|
|
5985
|
+
<strong>${escapeHtml(name)}</strong>
|
|
5986
|
+
<small>${escapeHtml(issue.message)}</small>
|
|
5987
|
+
</span>
|
|
5988
|
+
</button>
|
|
5989
|
+
<span class="ps-attention-action">
|
|
5990
|
+
${badge(issue.label, issue.tone)}
|
|
5991
|
+
<button class="button button-small" type="button" data-action="${escapeAttr(issue.action)}" data-id="${escapeAttr(plugin.id)}">${escapeHtml(issue.actionLabel)}</button>
|
|
5992
|
+
</span>
|
|
5993
|
+
</li>
|
|
5994
|
+
`;
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5997
|
+
/* ===================================================================
|
|
5998
|
+
* Dashboard: Recent activity
|
|
5999
|
+
* =================================================================== */
|
|
6000
|
+
|
|
6001
|
+
function renderDashboardActivityWidget() {
|
|
6002
|
+
const jobs = Array.from(state.jobs.values()).sort((left, right) =>
|
|
6003
|
+
String(right.createdAt ?? "").localeCompare(String(left.createdAt ?? ""))
|
|
6004
|
+
);
|
|
6005
|
+
const running = jobs.filter((job) => job.status === "running" || job.status === "queued").length;
|
|
6006
|
+
|
|
6007
|
+
const body = jobs.length
|
|
6008
|
+
? `<ul class="ps-activity-list">${jobs.slice(0, 5).map(dashboardActivityRow).join("")}</ul>`
|
|
6009
|
+
: `
|
|
6010
|
+
<div class="ps-side-empty ps-activity-empty">
|
|
6011
|
+
<span class="dashicons dashicons-clock" aria-hidden="true"></span>
|
|
6012
|
+
<span>No recent activity yet. Clone, check, dry-run, or release a plugin to see it here.</span>
|
|
6013
|
+
</div>
|
|
5675
6014
|
`;
|
|
5676
|
-
})
|
|
5677
|
-
.join("");
|
|
5678
6015
|
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
6016
|
+
return renderDashboardPostbox({
|
|
6017
|
+
id: "dashboard-activity",
|
|
6018
|
+
title: "Recent activity",
|
|
6019
|
+
icon: "dashicons-backup",
|
|
6020
|
+
className: "ps-dashboard-main",
|
|
6021
|
+
actions: running ? `<span class="ps-count-pill">${running} active</span>` : "",
|
|
6022
|
+
body
|
|
6023
|
+
});
|
|
6024
|
+
}
|
|
6025
|
+
|
|
6026
|
+
function dashboardActivityRow(job) {
|
|
6027
|
+
const tone =
|
|
6028
|
+
job.status === "failed"
|
|
6029
|
+
? "error"
|
|
6030
|
+
: job.status === "succeeded"
|
|
6031
|
+
? "success"
|
|
6032
|
+
: job.status === "cancelled"
|
|
6033
|
+
? "warning"
|
|
6034
|
+
: "info";
|
|
6035
|
+
return `
|
|
6036
|
+
<li class="ps-activity-row">
|
|
6037
|
+
<span class="ps-activity-icon ps-activity-${escapeAttr(tone)}" aria-hidden="true">
|
|
6038
|
+
<span class="dashicons ${jobIcon(job.type)}"></span>
|
|
6039
|
+
</span>
|
|
6040
|
+
<span class="ps-activity-text">
|
|
6041
|
+
<strong>${escapeHtml(job.title || jobTypeLabel(job.type))}</strong>
|
|
6042
|
+
<small>${escapeHtml(formatRelativeTime(job.createdAt))}</small>
|
|
6043
|
+
</span>
|
|
6044
|
+
${badge(job.status, tone)}
|
|
6045
|
+
</li>
|
|
6046
|
+
`;
|
|
6047
|
+
}
|
|
6048
|
+
|
|
6049
|
+
function jobTypeLabel(type) {
|
|
6050
|
+
switch (type) {
|
|
6051
|
+
case "clone":
|
|
6052
|
+
return "Clone plugin";
|
|
6053
|
+
case "play":
|
|
6054
|
+
return "Launch Playground";
|
|
6055
|
+
case "check":
|
|
6056
|
+
return "Verify plugin";
|
|
6057
|
+
case "dry-run-publish":
|
|
6058
|
+
return "Dry-run publish";
|
|
6059
|
+
case "confirm-publish":
|
|
6060
|
+
return "Publish";
|
|
6061
|
+
default:
|
|
6062
|
+
return "Task";
|
|
6063
|
+
}
|
|
6064
|
+
}
|
|
6065
|
+
|
|
6066
|
+
function formatRelativeTime(iso) {
|
|
6067
|
+
if (!iso) {
|
|
6068
|
+
return "";
|
|
6069
|
+
}
|
|
6070
|
+
const then = new Date(iso).getTime();
|
|
6071
|
+
if (!Number.isFinite(then)) {
|
|
6072
|
+
return String(iso);
|
|
6073
|
+
}
|
|
6074
|
+
const diffSeconds = Math.round((Date.now() - then) / 1000);
|
|
6075
|
+
if (diffSeconds < 45) {
|
|
6076
|
+
return "just now";
|
|
6077
|
+
}
|
|
6078
|
+
const minutes = Math.round(diffSeconds / 60);
|
|
6079
|
+
if (minutes < 60) {
|
|
6080
|
+
return `${minutes}m ago`;
|
|
6081
|
+
}
|
|
6082
|
+
const hours = Math.round(minutes / 60);
|
|
6083
|
+
if (hours < 24) {
|
|
6084
|
+
return `${hours}h ago`;
|
|
6085
|
+
}
|
|
6086
|
+
const days = Math.round(hours / 24);
|
|
6087
|
+
if (days < 7) {
|
|
6088
|
+
return `${days}d ago`;
|
|
6089
|
+
}
|
|
6090
|
+
try {
|
|
6091
|
+
return new Date(iso).toLocaleDateString([], { month: "short", day: "numeric" });
|
|
6092
|
+
} catch {
|
|
6093
|
+
return String(iso);
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
|
|
6097
|
+
/* ===================================================================
|
|
6098
|
+
* Dashboard: Validation health
|
|
6099
|
+
* =================================================================== */
|
|
6100
|
+
|
|
6101
|
+
function dashboardPluginCheckSummary() {
|
|
6102
|
+
let checked = 0;
|
|
6103
|
+
let errors = 0;
|
|
6104
|
+
let warnings = 0;
|
|
6105
|
+
let clean = 0;
|
|
6106
|
+
const flagged = [];
|
|
6107
|
+
|
|
6108
|
+
for (const plugin of state.local) {
|
|
6109
|
+
const entry = state.pluginCheckSummaries?.[plugin.id];
|
|
6110
|
+
if (!entry || !entry.summary) {
|
|
6111
|
+
continue;
|
|
6112
|
+
}
|
|
6113
|
+
checked += 1;
|
|
6114
|
+
const summary = entry.summary;
|
|
6115
|
+
if (summary.error > 0) {
|
|
6116
|
+
errors += 1;
|
|
6117
|
+
flagged.push({ plugin, entry, tone: "error" });
|
|
6118
|
+
} else if (summary.warning > 0) {
|
|
6119
|
+
warnings += 1;
|
|
6120
|
+
flagged.push({ plugin, entry, tone: "warning" });
|
|
6121
|
+
} else {
|
|
6122
|
+
clean += 1;
|
|
6123
|
+
}
|
|
6124
|
+
}
|
|
6125
|
+
|
|
6126
|
+
flagged.sort(
|
|
6127
|
+
(left, right) =>
|
|
6128
|
+
right.entry.summary.error - left.entry.summary.error ||
|
|
6129
|
+
right.entry.summary.warning - left.entry.summary.warning
|
|
6130
|
+
);
|
|
6131
|
+
|
|
6132
|
+
return { checked, errors, warnings, clean, unchecked: state.local.length - checked, flagged };
|
|
6133
|
+
}
|
|
6134
|
+
|
|
6135
|
+
function renderDashboardPluginCheckWidget() {
|
|
6136
|
+
if (!state.local.length) {
|
|
6137
|
+
return "";
|
|
6138
|
+
}
|
|
6139
|
+
|
|
6140
|
+
const summary = dashboardPluginCheckSummary();
|
|
6141
|
+
|
|
6142
|
+
let tone = "info";
|
|
6143
|
+
let label = "Not run";
|
|
6144
|
+
if (summary.checked > 0) {
|
|
6145
|
+
if (summary.errors > 0) {
|
|
6146
|
+
tone = "error";
|
|
6147
|
+
label = `${summary.errors} with errors`;
|
|
6148
|
+
} else if (summary.warnings > 0) {
|
|
6149
|
+
tone = "warning";
|
|
6150
|
+
label = `${summary.warnings} with warnings`;
|
|
6151
|
+
} else {
|
|
6152
|
+
tone = "success";
|
|
6153
|
+
label = "All clean";
|
|
6154
|
+
}
|
|
6155
|
+
}
|
|
6156
|
+
|
|
6157
|
+
let body;
|
|
6158
|
+
if (summary.checked === 0) {
|
|
6159
|
+
body = `
|
|
6160
|
+
<div class="ps-side-empty">
|
|
6161
|
+
<span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>
|
|
6162
|
+
<span>No Verify results yet. Open a plugin in Studio and run Verify.</span>
|
|
6163
|
+
</div>
|
|
6164
|
+
`;
|
|
6165
|
+
} else if (!summary.flagged.length) {
|
|
6166
|
+
body = `
|
|
6167
|
+
<div class="ps-readiness-clear">
|
|
6168
|
+
<span class="ps-readiness-clear-icon" aria-hidden="true">
|
|
6169
|
+
<span class="dashicons dashicons-yes-alt"></span>
|
|
6170
|
+
</span>
|
|
6171
|
+
<div class="ps-readiness-clear-text">
|
|
6172
|
+
<strong>No findings</strong>
|
|
6173
|
+
<p>${escapeHtml(`All ${summary.checked} checked plugin${summary.checked === 1 ? "" : "s"} passed Verify.`)}</p>
|
|
6174
|
+
</div>
|
|
6175
|
+
</div>
|
|
6176
|
+
${dashboardPluginCheckFooter(summary)}
|
|
6177
|
+
`;
|
|
6178
|
+
} else {
|
|
6179
|
+
const rows = summary.flagged.slice(0, 4).map(dashboardPluginCheckRow).join("");
|
|
6180
|
+
body = `
|
|
6181
|
+
<ul class="ps-attention-list">${rows}</ul>
|
|
6182
|
+
${dashboardPluginCheckFooter(summary)}
|
|
6183
|
+
`;
|
|
6184
|
+
}
|
|
5687
6185
|
|
|
5688
6186
|
return renderDashboardPostbox({
|
|
5689
|
-
id: "dashboard-
|
|
5690
|
-
title: "
|
|
5691
|
-
icon: "dashicons-
|
|
6187
|
+
id: "dashboard-plugin-check",
|
|
6188
|
+
title: "Validation health",
|
|
6189
|
+
icon: "dashicons-shield",
|
|
5692
6190
|
actions: badge(label, tone),
|
|
5693
|
-
body
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
6191
|
+
body
|
|
6192
|
+
});
|
|
6193
|
+
}
|
|
6194
|
+
|
|
6195
|
+
function dashboardPluginCheckFooter(summary) {
|
|
6196
|
+
return `
|
|
6197
|
+
<small class="ps-widget-meta">${escapeHtml(
|
|
6198
|
+
`${summary.checked} of ${state.local.length} checked · ${summary.clean} clean${summary.unchecked ? ` · ${summary.unchecked} not run` : ""}`
|
|
6199
|
+
)}</small>
|
|
6200
|
+
`;
|
|
6201
|
+
}
|
|
6202
|
+
|
|
6203
|
+
function dashboardPluginCheckRow({ plugin, entry, tone }) {
|
|
6204
|
+
const name = entry.name || plugin.name || plugin.slug || plugin.id;
|
|
6205
|
+
const counts = [];
|
|
6206
|
+
if (entry.summary.error) {
|
|
6207
|
+
counts.push(`${entry.summary.error} error${entry.summary.error === 1 ? "" : "s"}`);
|
|
6208
|
+
}
|
|
6209
|
+
if (entry.summary.warning) {
|
|
6210
|
+
counts.push(`${entry.summary.warning} warning${entry.summary.warning === 1 ? "" : "s"}`);
|
|
6211
|
+
}
|
|
6212
|
+
const detail = `${counts.join(" · ")}${entry.checkedAt ? ` · ${formatRelativeTime(entry.checkedAt)}` : ""}`;
|
|
6213
|
+
return `
|
|
6214
|
+
<li class="ps-attention-row ps-attention-${escapeAttr(tone)}">
|
|
6215
|
+
<span class="ps-attention-icon" aria-hidden="true">
|
|
6216
|
+
<span class="dashicons ${tone === "error" ? "dashicons-warning" : "dashicons-flag"}"></span>
|
|
6217
|
+
</span>
|
|
6218
|
+
<button class="ps-attention-main" type="button" data-action="studio" data-scope="local" data-id="${escapeAttr(plugin.id)}" title="Open ${escapeAttr(name)} in Studio">
|
|
6219
|
+
<span class="ps-attention-text">
|
|
6220
|
+
<strong>${escapeHtml(name)}</strong>
|
|
6221
|
+
<small>${escapeHtml(detail)}</small>
|
|
6222
|
+
</span>
|
|
6223
|
+
</button>
|
|
6224
|
+
<span class="ps-attention-action">
|
|
6225
|
+
<button class="button button-small" type="button" data-action="studio" data-scope="local" data-id="${escapeAttr(plugin.id)}">Open</button>
|
|
6226
|
+
</span>
|
|
6227
|
+
</li>
|
|
6228
|
+
`;
|
|
6229
|
+
}
|
|
6230
|
+
|
|
6231
|
+
/* ===================================================================
|
|
6232
|
+
* Dashboard: Compatibility watch
|
|
6233
|
+
* =================================================================== */
|
|
6234
|
+
|
|
6235
|
+
function dashboardCompatibilitySummary() {
|
|
6236
|
+
const latest = state.latestWordPressVersion || "";
|
|
6237
|
+
const latestBranch = latest ? versionBranch(latest) : "";
|
|
6238
|
+
let behind = 0;
|
|
6239
|
+
let current = 0;
|
|
6240
|
+
let unknown = 0;
|
|
6241
|
+
const outdated = [];
|
|
6242
|
+
|
|
6243
|
+
for (const plugin of state.remote) {
|
|
6244
|
+
const tested = parseTestedWith(plugin.testedWith);
|
|
6245
|
+
if (!tested) {
|
|
6246
|
+
unknown += 1;
|
|
6247
|
+
continue;
|
|
6248
|
+
}
|
|
6249
|
+
if (latestBranch && compareVersionStrings(versionBranch(tested), latestBranch) < 0) {
|
|
6250
|
+
behind += 1;
|
|
6251
|
+
outdated.push({ plugin, tested });
|
|
6252
|
+
} else {
|
|
6253
|
+
current += 1;
|
|
6254
|
+
}
|
|
6255
|
+
}
|
|
6256
|
+
|
|
6257
|
+
outdated.sort((left, right) => compareVersionStrings(left.tested, right.tested));
|
|
6258
|
+
return { latest, latestBranch, behind, current, unknown, outdated };
|
|
6259
|
+
}
|
|
6260
|
+
|
|
6261
|
+
function renderDashboardCompatibilityWidget() {
|
|
6262
|
+
if (!state.bootstrap?.loggedIn || (!state.remote.length && !state.remoteLoading)) {
|
|
6263
|
+
return "";
|
|
6264
|
+
}
|
|
6265
|
+
|
|
6266
|
+
const summary = dashboardCompatibilitySummary();
|
|
6267
|
+
const latestLabel = summary.latest ? `WP ${summary.latest}` : "WordPress";
|
|
6268
|
+
|
|
6269
|
+
let tone = "info";
|
|
6270
|
+
let label = summary.latestBranch ? "Up to date" : "Unknown";
|
|
6271
|
+
if (summary.behind > 0) {
|
|
6272
|
+
tone = "warning";
|
|
6273
|
+
label = `${summary.behind} behind`;
|
|
6274
|
+
} else if (summary.latestBranch && summary.current > 0) {
|
|
6275
|
+
tone = "success";
|
|
6276
|
+
label = "Up to date";
|
|
6277
|
+
}
|
|
6278
|
+
|
|
6279
|
+
let body;
|
|
6280
|
+
if (state.remoteLoading) {
|
|
6281
|
+
body = `<div class="ps-side-empty"><span class="dashicons dashicons-update" aria-hidden="true"></span><span>Loading WordPress.org plugins…</span></div>`;
|
|
6282
|
+
} else if (!summary.latestBranch) {
|
|
6283
|
+
body = `<div class="ps-side-empty"><span class="dashicons dashicons-editor-help" aria-hidden="true"></span><span>Could not determine the latest WordPress version right now.</span></div>`;
|
|
6284
|
+
} else if (!summary.outdated.length) {
|
|
6285
|
+
body = `
|
|
6286
|
+
<div class="ps-readiness-clear">
|
|
6287
|
+
<span class="ps-readiness-clear-icon" aria-hidden="true">
|
|
6288
|
+
<span class="dashicons dashicons-yes-alt"></span>
|
|
6289
|
+
</span>
|
|
6290
|
+
<div class="ps-readiness-clear-text">
|
|
6291
|
+
<strong>${escapeHtml(`Tested with ${latestLabel}`)}</strong>
|
|
6292
|
+
<p>${escapeHtml(`All ${summary.current} plugin${summary.current === 1 ? "" : "s"} declare compatibility with the latest WordPress.`)}</p>
|
|
5705
6293
|
</div>
|
|
5706
|
-
|
|
5707
|
-
|
|
6294
|
+
</div>
|
|
6295
|
+
`;
|
|
6296
|
+
} else {
|
|
6297
|
+
const rows = summary.outdated.slice(0, 4).map((item) => dashboardCompatibilityRow(item, summary)).join("");
|
|
6298
|
+
body = `
|
|
6299
|
+
<p class="ps-widget-intro">${escapeHtml(`Latest WordPress is ${summary.latest}. Bump "Tested up to" so users keep installing.`)}</p>
|
|
6300
|
+
<ul class="ps-attention-list">${rows}</ul>
|
|
6301
|
+
`;
|
|
6302
|
+
}
|
|
6303
|
+
|
|
6304
|
+
return renderDashboardPostbox({
|
|
6305
|
+
id: "dashboard-compatibility",
|
|
6306
|
+
title: "Compatibility watch",
|
|
6307
|
+
icon: "dashicons-wordpress",
|
|
6308
|
+
actions: badge(label, tone),
|
|
6309
|
+
body
|
|
6310
|
+
});
|
|
6311
|
+
}
|
|
6312
|
+
|
|
6313
|
+
function dashboardCompatibilityRow({ plugin, tested }, summary) {
|
|
6314
|
+
return `
|
|
6315
|
+
<li class="ps-attention-row ps-attention-warning">
|
|
6316
|
+
<span class="ps-attention-icon" aria-hidden="true">
|
|
6317
|
+
<span class="dashicons dashicons-wordpress"></span>
|
|
6318
|
+
</span>
|
|
6319
|
+
<button class="ps-attention-main" type="button" data-action="details" data-scope="remote" data-id="${escapeAttr(plugin.slug)}" title="View ${escapeAttr(plugin.name || plugin.slug)}">
|
|
6320
|
+
<span class="ps-attention-text">
|
|
6321
|
+
<strong>${escapeHtml(plugin.name || plugin.slug)}</strong>
|
|
6322
|
+
<small>${escapeHtml(`Tested up to ${tested} · WordPress is ${summary.latest}`)}</small>
|
|
6323
|
+
</span>
|
|
6324
|
+
</button>
|
|
6325
|
+
<span class="ps-attention-action">
|
|
6326
|
+
${badge(`${tested}`, "warning")}
|
|
6327
|
+
</span>
|
|
6328
|
+
</li>
|
|
6329
|
+
`;
|
|
6330
|
+
}
|
|
6331
|
+
|
|
6332
|
+
function parseTestedWith(value) {
|
|
6333
|
+
const match = String(value ?? "").match(/(\d+\.\d+(?:\.\d+)?)/);
|
|
6334
|
+
return match ? match[1] : "";
|
|
6335
|
+
}
|
|
6336
|
+
|
|
6337
|
+
function versionBranch(value) {
|
|
6338
|
+
const parts = String(value ?? "").split(".");
|
|
6339
|
+
return `${parts[0] ?? "0"}.${parts[1] ?? "0"}`;
|
|
6340
|
+
}
|
|
6341
|
+
|
|
6342
|
+
function compareVersionStrings(left, right) {
|
|
6343
|
+
const leftParts = String(left).split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0);
|
|
6344
|
+
const rightParts = String(right).split(/[.-]/).map((part) => Number.parseInt(part, 10) || 0);
|
|
6345
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
6346
|
+
const diff = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
6347
|
+
if (diff !== 0) {
|
|
6348
|
+
return diff > 0 ? 1 : -1;
|
|
6349
|
+
}
|
|
6350
|
+
}
|
|
6351
|
+
return 0;
|
|
6352
|
+
}
|
|
6353
|
+
|
|
6354
|
+
/* ===================================================================
|
|
6355
|
+
* Dashboard: WordPress.org reach
|
|
6356
|
+
* =================================================================== */
|
|
6357
|
+
|
|
6358
|
+
function dashboardReachSummary() {
|
|
6359
|
+
let total = 0;
|
|
6360
|
+
let known = false;
|
|
6361
|
+
let plus = false;
|
|
6362
|
+
let topName = "";
|
|
6363
|
+
let topCount = -1;
|
|
6364
|
+
|
|
6365
|
+
for (const plugin of state.remote) {
|
|
6366
|
+
const parsed = parseActiveInstalls(plugin.activeInstalls);
|
|
6367
|
+
if (!parsed.known) {
|
|
6368
|
+
continue;
|
|
6369
|
+
}
|
|
6370
|
+
known = true;
|
|
6371
|
+
total += parsed.count;
|
|
6372
|
+
if (parsed.plus) {
|
|
6373
|
+
plus = true;
|
|
6374
|
+
}
|
|
6375
|
+
if (parsed.count > topCount) {
|
|
6376
|
+
topCount = parsed.count;
|
|
6377
|
+
topName = plugin.name || plugin.slug || "";
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
|
|
6381
|
+
return { total, known, plus, topName: total > 0 ? topName : "" };
|
|
6382
|
+
}
|
|
6383
|
+
|
|
6384
|
+
function parseActiveInstalls(value) {
|
|
6385
|
+
const text = String(value ?? "").replace(/,/g, "");
|
|
6386
|
+
const match = text.match(/(\d+)/);
|
|
6387
|
+
if (!match) {
|
|
6388
|
+
return { count: 0, plus: false, known: false };
|
|
6389
|
+
}
|
|
6390
|
+
return { count: Number.parseInt(match[1], 10), plus: /\+/.test(text), known: true };
|
|
6391
|
+
}
|
|
6392
|
+
|
|
6393
|
+
function formatInstallCount(value) {
|
|
6394
|
+
try {
|
|
6395
|
+
return Number(value).toLocaleString();
|
|
6396
|
+
} catch {
|
|
6397
|
+
return String(value);
|
|
6398
|
+
}
|
|
6399
|
+
}
|
|
6400
|
+
|
|
6401
|
+
/* ===================================================================
|
|
6402
|
+
* Dashboard: Getting-started checklist
|
|
6403
|
+
* =================================================================== */
|
|
6404
|
+
|
|
6405
|
+
function dashboardOnboardingSteps() {
|
|
6406
|
+
const loggedIn = Boolean(state.bootstrap?.loggedIn);
|
|
6407
|
+
const hasLocal = state.local.length > 0;
|
|
6408
|
+
const ranCheck = Object.values(state.pluginCheckSummaries ?? {}).some((entry) => entry?.summary);
|
|
6409
|
+
const released =
|
|
6410
|
+
state.local.some((plugin) => state.versionStates.get(plugin.id)?.remoteVersion) ||
|
|
6411
|
+
Array.from(state.jobs.values()).some(
|
|
6412
|
+
(job) => job.type === "confirm-publish" && job.status === "succeeded"
|
|
6413
|
+
);
|
|
6414
|
+
|
|
6415
|
+
return [
|
|
6416
|
+
{
|
|
6417
|
+
id: "login",
|
|
6418
|
+
done: loggedIn,
|
|
6419
|
+
label: "Connect your WordPress.org account",
|
|
6420
|
+
hint: "Run pressship login in a terminal, then refresh.",
|
|
6421
|
+
actionAttrs: 'data-view-button="settings"',
|
|
6422
|
+
actionLabel: "Settings"
|
|
6423
|
+
},
|
|
6424
|
+
{
|
|
6425
|
+
id: "add",
|
|
6426
|
+
done: hasLocal,
|
|
6427
|
+
label: "Add a local plugin",
|
|
6428
|
+
hint: "Point Pressship at a plugin folder you're working on.",
|
|
6429
|
+
actionAttrs: 'data-action="choose-local-folder"',
|
|
6430
|
+
actionLabel: "Choose Folder"
|
|
6431
|
+
},
|
|
6432
|
+
{
|
|
6433
|
+
id: "check",
|
|
6434
|
+
done: ranCheck,
|
|
6435
|
+
label: "Run Verify on a plugin",
|
|
6436
|
+
hint: "Open a plugin in Studio and run Verify.",
|
|
6437
|
+
actionAttrs: 'data-view-button="local"',
|
|
6438
|
+
actionLabel: "Open Library"
|
|
6439
|
+
},
|
|
6440
|
+
{
|
|
6441
|
+
id: "release",
|
|
6442
|
+
done: released,
|
|
6443
|
+
label: "Prepare or ship a release",
|
|
6444
|
+
hint: "Review version state and walk the publish funnel.",
|
|
6445
|
+
actionAttrs: 'data-view-button="release"',
|
|
6446
|
+
actionLabel: "Release"
|
|
6447
|
+
}
|
|
6448
|
+
];
|
|
6449
|
+
}
|
|
6450
|
+
|
|
6451
|
+
function renderDashboardOnboardingCard() {
|
|
6452
|
+
if (state.localLoading && !state.local.length) {
|
|
6453
|
+
return "";
|
|
6454
|
+
}
|
|
6455
|
+
|
|
6456
|
+
const steps = dashboardOnboardingSteps();
|
|
6457
|
+
const done = steps.filter((step) => step.done).length;
|
|
6458
|
+
if (done === steps.length) {
|
|
6459
|
+
return "";
|
|
6460
|
+
}
|
|
6461
|
+
|
|
6462
|
+
return renderDashboardPostbox({
|
|
6463
|
+
id: "dashboard-getting-started",
|
|
6464
|
+
title: "Getting started",
|
|
6465
|
+
icon: "dashicons-flag",
|
|
6466
|
+
className: "ps-dashboard-main ps-onboarding-card",
|
|
6467
|
+
actions: `<span class="ps-count-pill">${done}/${steps.length}</span>`,
|
|
6468
|
+
body: `<ol class="ps-onboarding-list">${steps.map(dashboardOnboardingRow).join("")}</ol>`
|
|
5708
6469
|
});
|
|
5709
6470
|
}
|
|
5710
6471
|
|
|
6472
|
+
function dashboardOnboardingRow(step) {
|
|
6473
|
+
const action = step.done
|
|
6474
|
+
? ""
|
|
6475
|
+
: `<button class="button button-small" type="button" ${step.actionAttrs}>${escapeHtml(step.actionLabel)}</button>`;
|
|
6476
|
+
return `
|
|
6477
|
+
<li class="ps-onboarding-row${step.done ? " is-done" : ""}">
|
|
6478
|
+
<span class="ps-onboarding-check" aria-hidden="true">
|
|
6479
|
+
<span class="dashicons ${step.done ? "dashicons-yes-alt" : "dashicons-marker"}"></span>
|
|
6480
|
+
</span>
|
|
6481
|
+
<span class="ps-onboarding-label">
|
|
6482
|
+
<strong>${escapeHtml(step.label)}</strong>
|
|
6483
|
+
${step.hint && !step.done ? `<small>${escapeHtml(step.hint)}</small>` : ""}
|
|
6484
|
+
</span>
|
|
6485
|
+
${action}
|
|
6486
|
+
</li>
|
|
6487
|
+
`;
|
|
6488
|
+
}
|
|
6489
|
+
|
|
5711
6490
|
function renderPlaygroundsMenu() {
|
|
5712
6491
|
if (!els.playgroundsSection || !els.playgroundsMenu) {
|
|
5713
6492
|
return;
|
|
@@ -7851,13 +8630,13 @@ function renderStudioReleaseTagRow(tag) {
|
|
|
7851
8630
|
function renderStudioReleaseStepValidate(versionState, release) {
|
|
7852
8631
|
const summary = state.studio.checkSummary;
|
|
7853
8632
|
const summaryLine = summary
|
|
7854
|
-
? `
|
|
7855
|
-
: "
|
|
8633
|
+
? `Verify: ${escapeHtml(String(summary.error || 0))} errors, ${escapeHtml(String(summary.warning || 0))} warnings`
|
|
8634
|
+
: "Verify has not been run yet.";
|
|
7856
8635
|
const validationBlocked = release.dryRun?.validationBlocked;
|
|
7857
8636
|
const body = `
|
|
7858
8637
|
<ul class="ps-release-validate-list">
|
|
7859
8638
|
<li>
|
|
7860
|
-
<span><span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>Plugin Check</span>
|
|
8639
|
+
<span><span class="dashicons dashicons-yes-alt" aria-hidden="true"></span>Readme + Plugin Check</span>
|
|
7861
8640
|
<span class="ps-release-validate-state">${escapeHtml(summary ? "Ran" : "Pending")}</span>
|
|
7862
8641
|
<button class="button button-small" type="button" data-action="studio-check">${summary ? "Re-run" : "Run"}</button>
|
|
7863
8642
|
</li>
|
|
@@ -7871,6 +8650,10 @@ function renderStudioReleaseStepValidate(versionState, release) {
|
|
|
7871
8650
|
<span class="ps-release-validate-state">${escapeHtml(release.dryRun?.package?.file ? "Ready" : "Build on dry-run")}</span>
|
|
7872
8651
|
</li>
|
|
7873
8652
|
</ul>
|
|
8653
|
+
<label class="ps-release-skip-readme">
|
|
8654
|
+
<input type="checkbox" id="studio-skip-readme-validation" ${release.skipReadmeValidation ? "checked" : ""} ${state.studio.checking ? "disabled" : ""} />
|
|
8655
|
+
<span>skip readme validation</span>
|
|
8656
|
+
</label>
|
|
7874
8657
|
${validationBlocked
|
|
7875
8658
|
? `<p class="ps-release-step-error">Validation reported blocking findings. Fix them before publishing.</p>`
|
|
7876
8659
|
: ""}
|