@smartreviq/cli 0.3.1 → 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/LICENSE.md ADDED
@@ -0,0 +1,16 @@
1
+ # License
2
+
3
+ Copyright (c) 2026 SmartRevIQ. All rights reserved.
4
+
5
+ This software is a component of the SmartRevIQ service. It is published so
6
+ that SmartRevIQ customers can manage the configuration of their own
7
+ SmartRevIQ environments from their own repositories and pipelines.
8
+
9
+ Use is permitted only by parties holding a current subscription to the
10
+ SmartRevIQ service, and is governed by the terms of the agreement under
11
+ which that subscription is held.
12
+
13
+ No other rights are granted. This software may not be redistributed,
14
+ sublicensed, sold, or incorporated into another product or service, and may
15
+ not be reverse engineered except to the extent that such a restriction is
16
+ unenforceable under applicable law.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # sriq
2
2
 
3
- Edit, test and ship SmartRevIQ process-engine scripts from VS Code or WebStorm.
3
+ Edit, test and ship SmartRevIQ process-engine scripts from your editor and CI.
4
4
 
5
5
  No IDE plugin. The CLI writes ordinary project files — `jsconfig.json`, type
6
6
  declarations, launch configurations — and both editors read them through the
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@smartreviq/cli",
3
- "version": "0.3.1",
4
- "description": "Edit, test and ship SmartRevIQ process-engine scripts from VS Code or WebStorm.",
3
+ "version": "0.3.3",
4
+ "description": "Edit, test and ship SmartRevIQ process-engine scripts from your editor and CI.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "sriq": "bin/sriq.mjs"
@@ -21,12 +21,7 @@
21
21
  "acorn": "^8.19.0",
22
22
  "typescript": "^5.9.3"
23
23
  },
24
- "license": "UNLICENSED",
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/SmartRevIQ/smartreviq-backend.git",
28
- "directory": "tools/sriq"
29
- },
24
+ "license": "SEE LICENSE IN LICENSE.md",
30
25
  "publishConfig": {
31
26
  "access": "public"
32
27
  },
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))}`);
@@ -824,7 +854,7 @@ async function cmdRun(argv) {
824
854
 
825
855
  if (!safeMode) log(yellow('! running with writes ENABLED (--unsafe)'));
826
856
 
827
- if (flags.local) return runLocally({ abs, api, config, flags, safeMode });
857
+ if (flags.local) return runLocally({ abs, api, root, target, flags, safeMode });
828
858
 
829
859
  let result;
830
860
  if (flags.doc) {
@@ -844,7 +874,18 @@ async function cmdRun(argv) {
844
874
  * every api call forwarded to the environment. See runner.mjs for the fidelity
845
875
  * caveat this prints.
846
876
  */
847
- async function runLocally({ abs, api, config, flags, safeMode }) {
877
+ /**
878
+ * `--local` was dead on arrival. Three names this function uses were never
879
+ * passed to it — `config`, which it does not actually need, plus `root` and
880
+ * `target`, which it does — so every invocation threw `config is not defined`
881
+ * before reaching the runner, and the next two were waiting behind it.
882
+ *
883
+ * The runner's own tests pass and always did: they exercise runner.mjs
884
+ * directly, never the handful of lines of CLI wiring between the flag and it.
885
+ * A test that spawns the command is the only thing that would have caught
886
+ * this, which is why one now exists.
887
+ */
888
+ async function runLocally({ abs, api, root, target, flags, safeMode }) {
848
889
  const { createRemoteApi, installGlobals, runScriptFile } = await import('./runner.mjs');
849
890
 
850
891
  let doc = null;
package/src/client.mjs CHANGED
@@ -25,8 +25,8 @@
25
25
  * reads as rejected credentials rather than as a misrouted request.
26
26
  *
27
27
  * Both Lambdas rewrite `/api/v1/` to `/api/` before their own routers see the
28
- * path (src-auth/index.mts, src-handler/index.mts), so the prefixed form is
29
- * correct for every endpoint listed above, on every environment.
28
+ * path, so the prefixed form is correct for every endpoint listed above, on
29
+ * every environment.
30
30
  *
31
31
  * Overridable per project through `.sriq.json` "apiPrefix", so an environment
32
32
  * that routes differently can be worked around without waiting for a release.
@@ -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
  }