@smartreviq/cli 0.3.2 → 0.3.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@smartreviq/cli",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Edit, test and ship SmartRevIQ process-engine scripts from your editor and CI.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli.mjs CHANGED
@@ -669,6 +669,22 @@ function localScripts(root) {
669
669
  return out;
670
670
  }
671
671
 
672
+ /**
673
+ * Every script key the environment holds, as `folder/name.script`.
674
+ *
675
+ * One request. Deliberately NOT wrapped in a try/catch: if this fails, the
676
+ * caller cannot distinguish a script that is absent from one it simply could
677
+ * not read, and every answer it gives afterwards would be invented.
678
+ */
679
+ async function listRemoteKeys(api) {
680
+ const listing = await api.listScripts();
681
+ const keys = new Set();
682
+ for (const [folder, names] of Object.entries(listing ?? {})) {
683
+ for (const n of names) if (n.endsWith('.script')) keys.add(`${folder}/${n}`);
684
+ }
685
+ return keys;
686
+ }
687
+
672
688
  async function cmdPush(argv) {
673
689
  const { root, config, target, flags, positional } = projectTarget(argv);
674
690
  const api = clientFor(target);
@@ -680,6 +696,7 @@ async function cmdPush(argv) {
680
696
  log(`${yellow('→')} target: ${bold(target.env)} ${dim(target.api)}`);
681
697
  }
682
698
  const platform = platformOwned(root);
699
+ const remoteKeys = await listRemoteKeys(api);
683
700
 
684
701
  let targets = localScripts(root);
685
702
  if (positional.length) {
@@ -714,11 +731,17 @@ async function cmdPush(argv) {
714
731
  }
715
732
 
716
733
  const body = unwrap(text);
717
- let remote = null;
718
- try { remote = await api.getScript(s3Name, t.folder); } catch { /* new script */ }
734
+ const key = `${t.folder}/${s3Name}`;
735
+ if (!remoteKeys.has(key)) {
736
+ changed.push({ ...t, s3Name, body, isNew: true });
737
+ continue;
738
+ }
739
+ // Existence came from the listing; this only answers "has it changed".
740
+ // Reading a failure here as "new" is what let an expired token report a
741
+ // whole project as added — see listRemoteKeys.
742
+ const remote = await api.getScript(s3Name, t.folder);
719
743
  if (remote === body) continue;
720
-
721
- changed.push({ ...t, s3Name, body, isNew: remote === null });
744
+ changed.push({ ...t, s3Name, body, isNew: false });
722
745
  }
723
746
 
724
747
  if (blocked.length) {
@@ -756,14 +779,26 @@ async function cmdStatus(argv) {
756
779
  const api = clientFor(target);
757
780
 
758
781
  const locals = localScripts(root);
782
+
783
+ // THE LISTING DECIDES WHAT EXISTS. It used to be inferred: fetch each script
784
+ // and treat any failure as "not there". That reads a 401 as a new script, so
785
+ // an expired token — they last an hour — turned a clean status into every
786
+ // file reported as added. Observed on a 422-script project, which also
787
+ // showed how convincing the wrong answer looks.
788
+ //
789
+ // The error code cannot rescue that inference either: a script that really
790
+ // is absent comes back 500 SYSTEM_ERROR, the same as a server fault. So the
791
+ // guess is replaced by the one authoritative answer, which this command was
792
+ // already fetching for orphan detection, and a failure to get it is fatal
793
+ // rather than best-effort — without it, A and M cannot be told apart.
794
+ const remoteKeys = await listRemoteKeys(api);
759
795
  const rows = [];
760
796
  for (const t of locals) {
761
- const s3Name = toRemoteName(t.localName);
797
+ const key = `${t.folder}/${toRemoteName(t.localName)}`;
798
+ if (!remoteKeys.has(key)) { rows.push(['A', `${t.folder}/${t.localName}`]); continue; }
762
799
  const body = unwrap(readFileSync(t.absPath, 'utf8'));
763
- let remote = null;
764
- try { remote = await api.getScript(s3Name, t.folder); } catch { /* new */ }
765
- if (remote === null) rows.push(['A', `${t.folder}/${t.localName}`]);
766
- else if (remote !== body) rows.push(['M', `${t.folder}/${t.localName}`]);
800
+ const remote = await api.getScript(toRemoteName(t.localName), t.folder);
801
+ if (remote !== body) rows.push(['M', `${t.folder}/${t.localName}`]);
767
802
  }
768
803
 
769
804
  // Scripts live on the environment with no local file. `push` only ever
@@ -776,13 +811,8 @@ async function cmdStatus(argv) {
776
811
  // and the real answer is simply `sriq pull`.
777
812
  let orphans = [];
778
813
  if (locals.length) {
779
- try {
780
- const listing = await api.listScripts();
781
- const here = new Set(locals.map((t) => `${t.folder}/${toRemoteName(t.localName)}`));
782
- orphans = Object.entries(listing)
783
- .flatMap(([folder, names]) => names.map((n) => `${folder}/${n}`))
784
- .filter((k) => !here.has(k));
785
- } catch { /* best effort — the per-script diff above is the contract */ }
814
+ const here = new Set(locals.map((t) => `${t.folder}/${toRemoteName(t.localName)}`));
815
+ orphans = [...remoteKeys].filter((k) => !here.has(k));
786
816
  }
787
817
 
788
818
  log(`${bold(target.env)} ${dim(target.api + (target.apiPrefix ?? DEFAULT_API_PREFIX))}`);
package/src/client.mjs CHANGED
@@ -79,8 +79,15 @@ export class Client {
79
79
  // matters most — an SSO-only account being refused a password login
80
80
  // reads as an expired session, which it is not.
81
81
  const said = parsed?.message || parsed?.error;
82
+ // The status goes in the text even when the server said something
83
+ // useful. Without it "Invalid token" is just a sentence: it does not
84
+ // say an AUTH check failed, so it reads as a problem with the script
85
+ // or the request. A reader who sees 401 knows to re-authenticate
86
+ // without being told to — which matters, because telling them to would
87
+ // be wrong for the SSO-only account this branch exists to serve.
82
88
  throw new ApiError(
83
- said || 'Unauthorized. Your session may have expired — run `sriq login`.',
89
+ said ? `${said} (401 — ${method} ${fullPath})`
90
+ : 'Unauthorized. Your session may have expired — run `sriq login`.',
84
91
  401, parsed
85
92
  );
86
93
  }