@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 +1 -1
- package/src/cli.mjs +46 -16
- package/src/client.mjs +8 -1
package/package.json
CHANGED
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
|
-
|
|
718
|
-
|
|
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
|
|
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
|
-
|
|
764
|
-
|
|
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
|
-
|
|
780
|
-
|
|
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
|
|
89
|
+
said ? `${said} (401 — ${method} ${fullPath})`
|
|
90
|
+
: 'Unauthorized. Your session may have expired — run `sriq login`.',
|
|
84
91
|
401, parsed
|
|
85
92
|
);
|
|
86
93
|
}
|