@fias/plugin-dev-harness 1.8.1 → 1.9.1

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.
@@ -607,23 +607,23 @@
607
607
  document.getElementById('pub-description').value = m.description || '';
608
608
  document.getElementById('pub-expanded-desc').value = m.expandedDescription || '';
609
609
 
610
- // Display options
610
+ // Display options. Full-screen is an independent display choice —
611
+ // it doesn't dictate pricing. A full-screen plugin can still be paid
612
+ // (e.g., a premium immersive experience), so the pricing controls
613
+ // stay visible regardless of this checkbox.
611
614
  var isFullScreen = m.isFullScreen || m.archeType === 'site';
612
615
  document.getElementById('pub-listed').checked = m.isListed !== false; // default true
613
616
  document.getElementById('pub-public').checked = m.isPublic || false;
614
617
  document.getElementById('pub-fullscreen').checked = isFullScreen;
615
618
 
616
- // Pricing — hidden when full screen mode (sites are always free)
617
- updatePricingVisibility(isFullScreen);
618
- if (!isFullScreen) {
619
- // Normalize legacy 'free'/'per_use' to the two supported values
620
- var pModel = m.pricing?.model || 'free';
621
- if (pModel === 'free' || pModel === 'included' || pModel === 'no_base_cost') pModel = 'free';
622
- else pModel = 'fixed';
623
- document.getElementById('pub-pricing-model').value = pModel;
624
- document.getElementById('pub-price').value = m.pricing?.priceCents || '';
625
- document.getElementById('pub-price-row').style.display = pModel === 'free' ? 'none' : 'block';
626
- }
619
+ // Pricing — always editable. Normalize legacy 'free'/'per_use' to
620
+ // the two supported manifest values.
621
+ var pModel = m.pricing?.model || 'free';
622
+ if (pModel === 'free' || pModel === 'included' || pModel === 'no_base_cost') pModel = 'free';
623
+ else pModel = 'fixed';
624
+ document.getElementById('pub-pricing-model').value = pModel;
625
+ document.getElementById('pub-price').value = m.pricing?.priceCents || '';
626
+ document.getElementById('pub-price-row').style.display = pModel === 'free' ? 'none' : 'block';
627
627
  updatePublicAccessAvailability();
628
628
 
629
629
  // Permissions (read-only, derived from fias-plugin.json)
@@ -634,21 +634,6 @@
634
634
  : '<span style="color:#6b7280;font-size:12px">None declared in fias-plugin.json</span>';
635
635
  }
636
636
 
637
- function updatePricingVisibility(isFullScreen) {
638
- var pricingLabel = document.getElementById('pub-pricing-model').previousElementSibling;
639
- var pricingSelect = document.getElementById('pub-pricing-model');
640
- var priceRow = document.getElementById('pub-price-row');
641
- if (isFullScreen) {
642
- pricingLabel.style.display = 'none';
643
- pricingSelect.style.display = 'none';
644
- priceRow.style.display = 'none';
645
- } else {
646
- pricingLabel.style.display = '';
647
- pricingSelect.style.display = '';
648
- priceRow.style.display = pricingSelect.value === 'free' ? 'none' : 'block';
649
- }
650
- }
651
-
652
637
  /** Disable Public Access when the plugin is paid or uses AI entities */
653
638
  function updatePublicAccessAvailability() {
654
639
  var pubPublic = document.getElementById('pub-public');
@@ -669,9 +654,10 @@
669
654
  }
670
655
  }
671
656
 
672
- // Full Screen Mode togglecontrols pricing visibility
657
+ // Full Screen Mode is purely a display choice it doesn't affect
658
+ // pricing. Only re-check Public Access availability, which still
659
+ // depends on pricing + AI declarations (not on full-screen).
673
660
  document.getElementById('pub-fullscreen').addEventListener('change', function () {
674
- updatePricingVisibility(this.checked);
675
661
  updatePublicAccessAvailability();
676
662
  });
677
663
 
@@ -693,17 +679,15 @@
693
679
  // Infer arche type: public → site (display-only), otherwise → tool
694
680
  var archeType = isPublic ? 'site' : 'tool';
695
681
 
696
- // Pricing: user picks "free" or "fixed" — these are the valid manifest values.
682
+ // Pricing: user picks "free" or "fixed" — these are the valid
683
+ // manifest values. Full-screen does not override this; paid
684
+ // full-screen plugins are a valid shape.
697
685
  var pricing;
698
- if (isFullScreen) {
699
- pricing = { model: 'free', currency: 'usd' };
686
+ var pricingChoice = document.getElementById('pub-pricing-model').value;
687
+ if (pricingChoice === 'fixed') {
688
+ pricing = { model: 'fixed', currency: 'usd', priceCents: parseInt(document.getElementById('pub-price').value, 10) || 0 };
700
689
  } else {
701
- var pricingChoice = document.getElementById('pub-pricing-model').value;
702
- if (pricingChoice === 'fixed') {
703
- pricing = { model: 'fixed', currency: 'usd', priceCents: parseInt(document.getElementById('pub-price').value, 10) || 0 };
704
- } else {
705
- pricing = { model: 'free', currency: 'usd' };
706
- }
690
+ pricing = { model: 'free', currency: 'usd' };
707
691
  }
708
692
 
709
693
  return {
@@ -810,26 +794,141 @@
810
794
  document.getElementById('pub-build-output').textContent = '';
811
795
  document.getElementById('pub-submit-progress').innerHTML = '';
812
796
 
813
- // Determine if this is a first listing or update by checking existing submissions
797
+ // The server is the single source of truth for the publish fee. We ask
798
+ // the preflight endpoint (which knows whether this plugin's arche has
799
+ // ever been listed before) and render whatever it tells us. Never
800
+ // hardcode dollar amounts here — they will drift.
814
801
  Promise.all([
815
- fetch('/api/publish/submissions').then(function (r) { return r.ok ? r.json() : { submissions: [] }; }),
802
+ fetch('/api/publish/preflight-version', {
803
+ method: 'POST',
804
+ headers: { 'Content-Type': 'application/json' },
805
+ body: JSON.stringify({ bumpKind: 'patch' }),
806
+ }).then(function (r) { return r.ok ? r.json() : null; }),
816
807
  fetch('/api/credits').then(function (r) { return r.json(); }),
817
808
  ]).then(function (results) {
818
- var subsData = results[0];
809
+ var preflight = results[0];
819
810
  var creditsData = results[1];
820
- var existingCount = (subsData.submissions || []).length;
821
- var isUpdate = existingCount > 0;
822
- var cost = isUpdate ? '100 credits ($1.00)' : '5,000 credits ($50.00)';
823
- var costType = isUpdate ? 'Update' : 'First listing';
824
811
 
825
- costEl.innerHTML = '<p>' + costType + ' cost: <strong>' + cost + '</strong></p>';
812
+ var html;
813
+ if (preflight && preflight.feeCents != null) {
814
+ var feeCents = preflight.feeCents;
815
+ var costLabel =
816
+ (feeCents).toLocaleString() + ' credits ($' + (feeCents / 100).toFixed(2) + ')';
817
+ var costType = preflight.isFirstListing ? 'First listing' : 'Update';
818
+ html = '<p>' + costType + ' cost: <strong>' + costLabel + '</strong></p>';
819
+ if (preflight.refundableCents) {
820
+ html +=
821
+ '<p style="font-size:12px;color:#a1a1aa;margin-top:4px">' +
822
+ '$' + (preflight.refundableCents / 100).toFixed(2) +
823
+ ' refunded automatically if the review rejects your submission.</p>';
824
+ }
825
+ } else {
826
+ // Older API or preflight unavailable — describe the model in words
827
+ // rather than guessing a number that might be wrong.
828
+ html = '<p>Submission cost will be confirmed by the server when you submit.</p>';
829
+ }
826
830
 
827
831
  if (creditsData.balance != null && isFinite(creditsData.balance)) {
828
- costEl.innerHTML += '<p style="margin-top:8px">Your balance: <strong>' + creditsData.balance.toFixed(2) + ' credits</strong></p>';
832
+ html +=
833
+ '<p style="margin-top:8px">Your balance: <strong>' +
834
+ creditsData.balance.toFixed(2) + ' credits</strong></p>';
829
835
  }
836
+ costEl.innerHTML = html;
830
837
  }).catch(function () {
831
- costEl.innerHTML = '<p>Submission cost: <strong>5,000 credits ($50.00)</strong> for first listing, <strong>100 credits ($1.00)</strong> for updates.</p>';
838
+ costEl.innerHTML = '<p>Submission cost will be confirmed by the server when you submit.</p>';
832
839
  });
840
+
841
+ showVersionInfo();
842
+ }
843
+
844
+ // Asks the server "what version will I publish as?" and shows a banner so
845
+ // the user is never surprised by an auto-bump. The actual write to disk
846
+ // happens server-side during /api/publish/package — this preview is read-
847
+ // only. The override link routes back to step 1 where they can edit
848
+ // pricing/about and the version field via the form.
849
+ function showVersionInfo() {
850
+ var versionEl = document.getElementById('pub-version-info');
851
+ versionEl.style.display = 'none';
852
+ versionEl.innerHTML = '';
853
+
854
+ fetch('/api/publish/preflight-version', {
855
+ method: 'POST',
856
+ headers: { 'Content-Type': 'application/json' },
857
+ body: JSON.stringify({ bumpKind: 'patch' }),
858
+ })
859
+ .then(function (r) { return r.ok ? r.json() : null; })
860
+ .then(function (data) {
861
+ if (!data || data.available === false) return;
862
+ versionEl.style.display = 'block';
863
+ var html;
864
+ if (data.wasAutoBumped) {
865
+ html =
866
+ '<p>Will publish as <strong>v' + escapeHtml(data.submittedVersion) + '</strong>' +
867
+ ' (latest published: <strong>v' + escapeHtml(data.latestPublishedVersion || '—') + '</strong>).</p>' +
868
+ '<p style="font-size:12px;color:#a1a1aa;margin-top:4px">' +
869
+ 'Auto-bumped from v' + escapeHtml(data.previousVersion) +
870
+ '. <a href="#" id="pub-version-override-link">Override</a></p>';
871
+ } else if (data.action === 'explicit' || data.previousVersion === data.submittedVersion) {
872
+ html =
873
+ '<p>Submitting as <strong>v' + escapeHtml(data.submittedVersion) + '</strong>' +
874
+ (data.latestPublishedVersion
875
+ ? ' (latest published: <strong>v' + escapeHtml(data.latestPublishedVersion) + '</strong>)'
876
+ : '') + '.</p>' +
877
+ '<p style="font-size:12px;color:#a1a1aa;margin-top:4px">' +
878
+ 'Manifest version respected. <a href="#" id="pub-version-override-link">Override</a></p>';
879
+ } else {
880
+ html =
881
+ '<p>Will publish as <strong>v' + escapeHtml(data.submittedVersion) + '</strong>.</p>' +
882
+ '<p style="font-size:12px;color:#a1a1aa;margin-top:4px">' +
883
+ '<a href="#" id="pub-version-override-link">Override</a></p>';
884
+ }
885
+ versionEl.innerHTML = html;
886
+ var override = document.getElementById('pub-version-override-link');
887
+ if (override) {
888
+ override.addEventListener('click', function (e) {
889
+ e.preventDefault();
890
+ promptVersionOverride(data);
891
+ });
892
+ }
893
+ })
894
+ .catch(function () { /* preflight is best-effort; silently skip */ });
895
+ }
896
+
897
+ function promptVersionOverride(data) {
898
+ var current = data.submittedVersion;
899
+ var input = window.prompt(
900
+ 'Enter the version to publish as (e.g., 1.2.3 or 1.2.3-beta.1):',
901
+ current,
902
+ );
903
+ if (!input || input === current) return;
904
+ fetch('/api/publish/preflight-version', {
905
+ method: 'POST',
906
+ headers: { 'Content-Type': 'application/json' },
907
+ body: JSON.stringify({ explicitVersion: input.trim() }),
908
+ })
909
+ .then(function (r) { return r.json().then(function (b) { return { ok: r.ok, body: b }; }); })
910
+ .then(function (resp) {
911
+ if (!resp.ok) {
912
+ window.alert(resp.body.error || 'Invalid version.');
913
+ return;
914
+ }
915
+ // Persist the override to fias-plugin.json so /api/publish/package
916
+ // sees it as the manifest version. The decideNextVersion logic on
917
+ // the server will then "respect" it (manifest > latest, or pre-
918
+ // release) and not re-bump.
919
+ fetch('/api/manifest', { method: 'GET' })
920
+ .then(function (r) { return r.json(); })
921
+ .then(function (manifest) {
922
+ manifest.version = input.trim();
923
+ return fetch('/api/manifest', {
924
+ method: 'PUT',
925
+ headers: { 'Content-Type': 'application/json' },
926
+ body: JSON.stringify(manifest),
927
+ });
928
+ })
929
+ .then(function () { showVersionInfo(); });
930
+ })
931
+ .catch(function () { window.alert('Could not validate version.'); });
833
932
  }
834
933
 
835
934
  function startBuildAndSubmit() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fias/plugin-dev-harness",
3
- "version": "1.8.1",
3
+ "version": "1.9.1",
4
4
  "description": "Development harness for building and testing FIAS plugin arches locally",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -15,12 +15,6 @@
15
15
  "publishConfig": {
16
16
  "access": "public"
17
17
  },
18
- "scripts": {
19
- "build": "rm -rf dist && tsc && cp -r src/server/static dist/server/static && chmod +x dist/index.js",
20
- "clean": "rm -rf dist",
21
- "watch": "tsc -w",
22
- "typecheck": "tsc --noEmit"
23
- },
24
18
  "keywords": [
25
19
  "fias",
26
20
  "plugin",
@@ -33,11 +27,19 @@
33
27
  "@fias/arche-sdk": "^1.0.0",
34
28
  "chalk": "^4.1.2",
35
29
  "commander": "^12.0.0",
36
- "express": "^4.21.0"
30
+ "express": "^4.21.0",
31
+ "semver": "^7.7.3"
37
32
  },
38
33
  "devDependencies": {
39
34
  "@types/express": "^5.0.0",
40
35
  "@types/node": "^25.5.0",
36
+ "@types/semver": "^7.7.0",
41
37
  "typescript": "^5.3.3"
38
+ },
39
+ "scripts": {
40
+ "build": "rm -rf dist && tsc && cp -r src/server/static dist/server/static && chmod +x dist/index.js",
41
+ "clean": "rm -rf dist",
42
+ "watch": "tsc -w",
43
+ "typecheck": "tsc --noEmit"
42
44
  }
43
- }
45
+ }