@snutils/snu 0.2.11 → 0.2.12
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/README.md +1 -1
- package/dist/cli/doctor.d.ts +10 -0
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +25 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +11 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +41 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/server/authProbe.d.ts +29 -0
- package/dist/server/authProbe.d.ts.map +1 -0
- package/dist/server/authProbe.js +67 -0
- package/dist/server/authProbe.js.map +1 -0
- package/dist/server/bridgeIdentity.d.ts +24 -0
- package/dist/server/bridgeIdentity.d.ts.map +1 -0
- package/dist/server/bridgeIdentity.js +120 -0
- package/dist/server/bridgeIdentity.js.map +1 -0
- package/dist/server/dispatcher.d.ts +0 -1
- package/dist/server/dispatcher.d.ts.map +1 -1
- package/dist/server/dispatcher.js +85 -68
- package/dist/server/dispatcher.js.map +1 -1
- package/dist/server/helperConnection.d.ts +24 -1
- package/dist/server/helperConnection.d.ts.map +1 -1
- package/dist/server/helperConnection.js +54 -4
- package/dist/server/helperConnection.js.map +1 -1
- package/dist/server/scopeDiscovery.d.ts +37 -0
- package/dist/server/scopeDiscovery.d.ts.map +1 -0
- package/dist/server/scopeDiscovery.js +147 -0
- package/dist/server/scopeDiscovery.js.map +1 -0
- package/dist/server/wsBridge.d.ts +2 -1
- package/dist/server/wsBridge.d.ts.map +1 -1
- package/dist/server/wsBridge.js +27 -2
- package/dist/server/wsBridge.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.loadBridgeId = loadBridgeId;
|
|
37
|
+
exports.inspectBridgeId = inspectBridgeId;
|
|
38
|
+
const crypto = __importStar(require("crypto"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
/**
|
|
43
|
+
* A stable identity for "the ScriptSync bridge of this user on this machine".
|
|
44
|
+
*
|
|
45
|
+
* The SN Utils helper tab re-sends ServiceNow session tokens on its own when it
|
|
46
|
+
* reconnects, but only to a bridge the user has handed a token to before. This
|
|
47
|
+
* id is how it recognises that bridge after a restart: it is created once,
|
|
48
|
+
* kept private to the user under ~/.sn-scriptsync, and shared by the VS Code
|
|
49
|
+
* host and the standalone snu bridge so switching hosts does not reset trust.
|
|
50
|
+
*
|
|
51
|
+
* Returns an empty string when the file cannot be read or created. The helper
|
|
52
|
+
* then never refreshes automatically and a manual /token is needed, which is
|
|
53
|
+
* the behaviour from before this existed.
|
|
54
|
+
*/
|
|
55
|
+
function loadBridgeId(dir = path.join(os.homedir(), ".sn-scriptsync")) {
|
|
56
|
+
const file = path.join(dir, "bridge-id");
|
|
57
|
+
const read = () => {
|
|
58
|
+
const stat = fs.lstatSync(file);
|
|
59
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
60
|
+
return "";
|
|
61
|
+
// Existing files need the same privacy as newly created ones.
|
|
62
|
+
if (process.platform !== "win32")
|
|
63
|
+
fs.chmodSync(file, 0o600);
|
|
64
|
+
const id = fs.readFileSync(file, "utf8").trim();
|
|
65
|
+
return /^[a-f0-9]{64}$/.test(id) ? id : "";
|
|
66
|
+
};
|
|
67
|
+
try {
|
|
68
|
+
return read();
|
|
69
|
+
}
|
|
70
|
+
catch (err) {
|
|
71
|
+
if (err.code !== "ENOENT")
|
|
72
|
+
return "";
|
|
73
|
+
}
|
|
74
|
+
let temporary = "";
|
|
75
|
+
try {
|
|
76
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
77
|
+
const id = crypto.randomBytes(32).toString("hex");
|
|
78
|
+
temporary = path.join(dir, `.bridge-id-${crypto.randomBytes(16).toString("hex")}`);
|
|
79
|
+
fs.writeFileSync(temporary, id + "\n", { mode: 0o600, flag: "wx" });
|
|
80
|
+
// Publish only a complete file, without replacing another host's identity.
|
|
81
|
+
try {
|
|
82
|
+
fs.linkSync(temporary, file);
|
|
83
|
+
}
|
|
84
|
+
catch (err) {
|
|
85
|
+
if (err.code !== "EEXIST")
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
return read();
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
if (temporary) {
|
|
95
|
+
try {
|
|
96
|
+
fs.unlinkSync(temporary);
|
|
97
|
+
}
|
|
98
|
+
catch { /* best effort */ }
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Read-only look at the identity file, for diagnostics. Unlike loadBridgeId it
|
|
104
|
+
* never creates the file or changes its mode, and it never returns the id.
|
|
105
|
+
*/
|
|
106
|
+
function inspectBridgeId(dir = path.join(os.homedir(), ".sn-scriptsync")) {
|
|
107
|
+
const file = path.join(dir, "bridge-id");
|
|
108
|
+
try {
|
|
109
|
+
const stat = fs.lstatSync(file);
|
|
110
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
111
|
+
return { status: "not-a-file", private: null };
|
|
112
|
+
const isPrivate = process.platform === "win32" ? null : (stat.mode & 0o077) === 0;
|
|
113
|
+
const id = fs.readFileSync(file, "utf8").trim();
|
|
114
|
+
return { status: /^[a-f0-9]{64}$/.test(id) ? "ok" : "invalid", private: isPrivate };
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
return { status: err.code === "ENOENT" ? "missing" : "unreadable", private: null };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=bridgeIdentity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridgeIdentity.js","sourceRoot":"","sources":["../../src/server/bridgeIdentity.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/server/dispatcher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/server/dispatcher.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAA0B,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAoB,aAAa,EAAE,MAAM,aAAa,CAAC;AAiD9D,eAAO,MAAM,uBAAuB,UAInC,CAAC;AAwGF,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,GACZ;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAoBxE;AAwBD,MAAM,WAAW,2BAA2B;IAC1C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC,aAAa,GAAG;QAAE,cAAc,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;CAClE;AAED,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,EAAE,CAAqB;IAC/B,OAAO,CAAC,OAAO,CAAkB;IACjC,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,qBAAqB,CAA6B;IAC1D,OAAO,CAAC,wBAAwB,CAA6B;gBAEjD,IAAI,EAAE,2BAA2B;IAO7C,gBAAgB,IAAI,MAAM;IAI1B,mBAAmB,CAAC,YAAY,EAAE,MAAM,GAAG,GAAG;IAkBxC,sBAAsB,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;YAyDvC,yBAAyB;YAiDzB,sBAAsB;IA4BpC;;;;;;;OAOG;YACW,qBAAqB;IAmCnC,mBAAmB,IAAI,MAAM,EAAE;YAgBjB,aAAa;YAwKb,OAAO;IAoBrB,eAAe,CAAC,eAAe,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,GAAG,CAAA;KAAE;IAuF1F,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,SAAc,GAAG,IAAI;IAa/C,QAAQ,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,CAAC;CAi8B1D"}
|
|
@@ -35,6 +35,8 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.StandaloneDispatcher = exports.HELPER_CONNECT_GUIDANCE = void 0;
|
|
37
37
|
exports.resolveMappedFileName = resolveMappedFileName;
|
|
38
|
+
const authProbe_js_1 = require("./authProbe.js");
|
|
39
|
+
const scopeDiscovery_js_1 = require("./scopeDiscovery.js");
|
|
38
40
|
const fs = __importStar(require("fs"));
|
|
39
41
|
const os = __importStar(require("os"));
|
|
40
42
|
const path = __importStar(require("path"));
|
|
@@ -166,11 +168,11 @@ function resolveTableCodeFields(tableName, cwd) {
|
|
|
166
168
|
}
|
|
167
169
|
return ['script'];
|
|
168
170
|
}
|
|
169
|
-
function resolveFieldExtension(tableName, fieldName, cwd) {
|
|
171
|
+
function resolveFieldExtension(tableName, fieldName, cwd, liveType) {
|
|
170
172
|
const meta = getMetaDataRelations(cwd);
|
|
171
173
|
let fieldType = 'script';
|
|
172
174
|
try {
|
|
173
|
-
fieldType = meta?.tableFields?.[tableName]?.codeFields?.[fieldName]?.type || fieldName;
|
|
175
|
+
fieldType = liveType || meta?.tableFields?.[tableName]?.codeFields?.[fieldName]?.type || fieldName;
|
|
174
176
|
}
|
|
175
177
|
catch { }
|
|
176
178
|
let ext = FIELDTYPES[fieldType]?.extension;
|
|
@@ -475,7 +477,7 @@ class StandaloneDispatcher {
|
|
|
475
477
|
sysparm_offset: String(offset),
|
|
476
478
|
sysparm_display_value: 'false',
|
|
477
479
|
sysparm_exclude_reference_link: 'true',
|
|
478
|
-
sysparm_no_count: '
|
|
480
|
+
sysparm_no_count: 'false',
|
|
479
481
|
};
|
|
480
482
|
if (combinedQuery) {
|
|
481
483
|
queryParams.sysparm_query = combinedQuery;
|
|
@@ -499,6 +501,7 @@ class StandaloneDispatcher {
|
|
|
499
501
|
const isFolderRecordTable = FOLDERRECORDTABLES.includes(table);
|
|
500
502
|
let filesWritten = 0;
|
|
501
503
|
let skippedEmpty = 0;
|
|
504
|
+
let skippedUnreadable = 0;
|
|
502
505
|
const warnings = [];
|
|
503
506
|
const pulledRecordsList = [];
|
|
504
507
|
for (const rec of matchedRecords) {
|
|
@@ -553,7 +556,12 @@ class StandaloneDispatcher {
|
|
|
553
556
|
}
|
|
554
557
|
const recordFiles = [];
|
|
555
558
|
for (const field of codeFields) {
|
|
556
|
-
|
|
559
|
+
if (!Object.prototype.hasOwnProperty.call(rec, field)) {
|
|
560
|
+
skippedUnreadable++;
|
|
561
|
+
warnings.push(`${table}/${sysId}.${field}: field not returned; local file preserved.`);
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
const ext = resolveFieldExtension(table, field, this.cwd, opts.fieldDefinitions?.[field]?.type);
|
|
557
565
|
let targetPath;
|
|
558
566
|
try {
|
|
559
567
|
targetPath = isFolderRecordTable
|
|
@@ -603,27 +611,18 @@ class StandaloneDispatcher {
|
|
|
603
611
|
});
|
|
604
612
|
}
|
|
605
613
|
return {
|
|
614
|
+
pagination: res.pagination,
|
|
606
615
|
table,
|
|
607
616
|
matchedRecords: matchedRecords.length,
|
|
608
617
|
pulledRecords: pulledRecordsList.length,
|
|
609
618
|
filesWritten,
|
|
610
619
|
skippedEmpty,
|
|
620
|
+
skippedUnreadable,
|
|
611
621
|
warnings,
|
|
612
622
|
records: pulledRecordsList,
|
|
613
623
|
};
|
|
614
624
|
}
|
|
615
|
-
|
|
616
|
-
// resources/metaDataRelations.json that declare code fields.
|
|
617
|
-
allCodeTables() {
|
|
618
|
-
const meta = getMetaDataRelations(this.cwd);
|
|
619
|
-
const out = new Set();
|
|
620
|
-
for (const [table, def] of Object.entries(meta?.tableFields || {})) {
|
|
621
|
-
if (def?.codeFields && typeof def.codeFields === 'object')
|
|
622
|
-
out.add(table);
|
|
623
|
-
}
|
|
624
|
-
return out;
|
|
625
|
-
}
|
|
626
|
-
async restGet(inst, reqCommand, endpoint, queryParams) {
|
|
625
|
+
async restGet(inst, reqCommand, endpoint, queryParams, withPagination = false) {
|
|
627
626
|
const correlationId = crypto.randomUUID();
|
|
628
627
|
const pendingPromise = this.pending.register({ id: correlationId, command: reqCommand, timeoutMs: 70_000 });
|
|
629
628
|
this.ws.sendToBrowser({
|
|
@@ -637,9 +636,10 @@ class StandaloneDispatcher {
|
|
|
637
636
|
});
|
|
638
637
|
const res = await pendingPromise;
|
|
639
638
|
if (res?.success === false) {
|
|
640
|
-
|
|
639
|
+
const hint = res?.status ? '' : (0, authProbe_js_1.helperFailureHint)(typeof res?.error === 'string' ? res.error : '', inst.settings?.url);
|
|
640
|
+
throw Object.assign(new Error([res.error || `REST request failed: ${endpoint}`, hint].filter(Boolean).join(' ')), { code: res.code || 'E_COMMAND_FAILED' });
|
|
641
641
|
}
|
|
642
|
-
return res?.data;
|
|
642
|
+
return withPagination ? { rows: res?.data?.result, pagination: res?.pagination } : res?.data;
|
|
643
643
|
}
|
|
644
644
|
resolveInstance(requestInstance) {
|
|
645
645
|
const folders = this.listInstanceFolders();
|
|
@@ -680,8 +680,16 @@ class StandaloneDispatcher {
|
|
|
680
680
|
if (live) {
|
|
681
681
|
return { name: live.name, folder: this.cwd, settings: live };
|
|
682
682
|
}
|
|
683
|
-
|
|
683
|
+
const known = [
|
|
684
|
+
...liveInstances.map((candidate) => `${candidate.name} (${candidate.url})`),
|
|
685
|
+
...folders.map((folder) => path.basename(folder)),
|
|
686
|
+
];
|
|
687
|
+
const knownText = known.length
|
|
688
|
+
? ` Known here: ${[...new Set(known)].join(', ')}. Pass one of these as the instance, by name, hostname or origin.`
|
|
689
|
+
: ' This bridge has not received any session since it started.';
|
|
690
|
+
throw Object.assign(new Error(`No authenticated instance named "${requestInstance}" is connected.${knownText} Otherwise open that exact hostname in the browser, run /token and retry.`), {
|
|
684
691
|
code: 'E_INSTANCE_NOT_FOUND',
|
|
692
|
+
details: { requested: requestInstance, liveInstances: liveInstances.map((candidate) => ({ name: candidate.name, url: candidate.url })) },
|
|
685
693
|
});
|
|
686
694
|
}
|
|
687
695
|
if (folders.length === 1) {
|
|
@@ -792,6 +800,18 @@ class StandaloneDispatcher {
|
|
|
792
800
|
message: 'The SN Utils helper tab is not connected, so the session cannot be checked.',
|
|
793
801
|
});
|
|
794
802
|
}
|
|
803
|
+
const hasLiveSession = !!origin && this.ws.getLiveInstances().some((live) => {
|
|
804
|
+
try {
|
|
805
|
+
return new URL(live.url).origin.toLowerCase() === origin;
|
|
806
|
+
}
|
|
807
|
+
catch {
|
|
808
|
+
return false;
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
if (!inst.settings?.g_ck) {
|
|
812
|
+
const verdict = (0, authProbe_js_1.classifyProbe)({ error: 'Missing instance URL or authentication token', origin, hasLiveSession });
|
|
813
|
+
return reply({ ok: false, state: verdict.state, message: verdict.message });
|
|
814
|
+
}
|
|
795
815
|
const correlationId = crypto.randomUUID();
|
|
796
816
|
const pendingPromise = this.pending.register({ id: correlationId, command: req.command, timeoutMs: 15_000 });
|
|
797
817
|
this.ws.sendToBrowser({
|
|
@@ -805,32 +825,25 @@ class StandaloneDispatcher {
|
|
|
805
825
|
});
|
|
806
826
|
try {
|
|
807
827
|
const res = await pendingPromise;
|
|
808
|
-
const status = res?.status;
|
|
809
828
|
// 401 and 403 mean opposite things: the session is gone, versus the
|
|
810
|
-
// session authenticated and an ACL refused the row.
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
? 'AUTH_OK'
|
|
816
|
-
: 'AUTH_UNKNOWN'
|
|
817
|
-
: 'AUTH_OK';
|
|
829
|
+
// session authenticated and an ACL refused the row. Failures without
|
|
830
|
+
// a status are classified from the helper tab's own error text.
|
|
831
|
+
const verdict = res?.success === false
|
|
832
|
+
? (0, authProbe_js_1.classifyProbe)({ status: res?.status, error: typeof res?.error === 'string' ? res.error : res?.error?.message, origin, hasLiveSession })
|
|
833
|
+
: (0, authProbe_js_1.classifyProbe)({ status: 200, origin, hasLiveSession });
|
|
818
834
|
return reply({
|
|
819
|
-
ok: state === 'AUTH_OK',
|
|
820
|
-
state,
|
|
821
|
-
message:
|
|
822
|
-
? 'ServiceNow accepted the session.'
|
|
823
|
-
: state === 'AUTH_EXPIRED'
|
|
824
|
-
? 'ServiceNow rejected the session (401). Open the instance in the browser and run /token to refresh it.'
|
|
825
|
-
: 'The session check could not complete. This is not a verdict — retry before acting on it.',
|
|
835
|
+
ok: verdict.state === 'AUTH_OK',
|
|
836
|
+
state: verdict.state,
|
|
837
|
+
message: verdict.message,
|
|
826
838
|
detail: res?.error,
|
|
827
839
|
});
|
|
828
840
|
}
|
|
829
841
|
catch (err) {
|
|
842
|
+
const verdict = (0, authProbe_js_1.classifyProbe)({ status: err?.details?.status, error: err?.message || String(err), origin, hasLiveSession });
|
|
830
843
|
return reply({
|
|
831
844
|
ok: false,
|
|
832
|
-
state:
|
|
833
|
-
message:
|
|
845
|
+
state: verdict.state,
|
|
846
|
+
message: verdict.message,
|
|
834
847
|
detail: err?.message || String(err),
|
|
835
848
|
});
|
|
836
849
|
}
|
|
@@ -1427,64 +1440,66 @@ class StandaloneDispatcher {
|
|
|
1427
1440
|
}
|
|
1428
1441
|
catch { }
|
|
1429
1442
|
}
|
|
1430
|
-
|
|
1443
|
+
const fetchPage = (table, query) => this.restGet(inst, req.command, `/api/now/table/${table}`, query, true);
|
|
1444
|
+
const discovery = await (0, scopeDiscovery_js_1.readAllPages)(fetchPage, 'sys_metadata', `sys_scope=${scopeSysId}^sys_class_name!=sys_metadata_delete^sys_update_name!=NULL^ORDERBYsys_id`, 'sys_class_name');
|
|
1431
1445
|
const classCounts = new Map();
|
|
1432
|
-
const
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
sysparm_fields: 'sys_class_name',
|
|
1437
|
-
sysparm_limit: String(metaPage),
|
|
1438
|
-
sysparm_offset: String(offset),
|
|
1439
|
-
sysparm_exclude_reference_link: 'true',
|
|
1440
|
-
sysparm_no_count: 'true',
|
|
1441
|
-
});
|
|
1442
|
-
const rows = Array.isArray(data?.result) ? data.result : [];
|
|
1443
|
-
for (const r of rows) {
|
|
1444
|
-
const cls = String(typeof r.sys_class_name === 'object' ? r.sys_class_name.value : r.sys_class_name || '');
|
|
1445
|
-
if (cls)
|
|
1446
|
-
classCounts.set(cls, (classCounts.get(cls) || 0) + 1);
|
|
1447
|
-
}
|
|
1448
|
-
if (rows.length < metaPage)
|
|
1449
|
-
break;
|
|
1446
|
+
for (const row of discovery.rows) {
|
|
1447
|
+
const cls = String(typeof row.sys_class_name === 'object' ? row.sys_class_name.value : row.sys_class_name || '');
|
|
1448
|
+
if (/^[a-zA-Z0-9_]+$/.test(cls))
|
|
1449
|
+
classCounts.set(cls, (classCounts.get(cls) || 0) + 1);
|
|
1450
1450
|
}
|
|
1451
|
-
const
|
|
1452
|
-
const tables = [...classCounts.keys()].filter(
|
|
1453
|
-
const skippedTables = [...classCounts.keys()].filter(
|
|
1454
|
-
.map(
|
|
1451
|
+
const schema = await (0, scopeDiscovery_js_1.discoverScopeFields)([...classCounts.keys()], getMetaDataRelations(this.cwd)?.tableFields || {}, fetchPage);
|
|
1452
|
+
const tables = [...classCounts.keys()].filter(t => Object.keys(schema.definitions[t]?.codeFields || {}).length && (!tableFilter || tableFilter.has(t))).sort();
|
|
1453
|
+
const skippedTables = [...classCounts.keys()].filter(t => !Object.keys(schema.definitions[t]?.codeFields || {}).length).sort()
|
|
1454
|
+
.map(table => ({ table, records: classCounts.get(table) || 0, reason: schema.warnings.some(w => w.startsWith(`${table}:`)) ? 'dictionary_unavailable' : 'no_scriptable_fields' }));
|
|
1455
1455
|
const missingTables = tableFilter ? [...tableFilter].filter((t) => !classCounts.has(t)).sort() : [];
|
|
1456
1456
|
const perTable = [];
|
|
1457
|
-
const warnings = [];
|
|
1458
|
-
let
|
|
1457
|
+
const warnings = [...discovery.warnings, ...schema.warnings];
|
|
1458
|
+
let complete = discovery.complete && schema.warnings.length === 0;
|
|
1459
|
+
let totalRecords = 0, totalFiles = 0, totalSkippedEmpty = 0, totalSkippedUnreadable = 0;
|
|
1459
1460
|
for (const table of tables) {
|
|
1460
|
-
const
|
|
1461
|
+
const fieldDefinitions = schema.definitions[table].codeFields;
|
|
1462
|
+
const codeFields = Object.keys(fieldDefinitions);
|
|
1461
1463
|
const query = `sys_scope=${scopeSysId}^sys_class_name=${table}^ORDERBYsys_id`;
|
|
1462
|
-
const entry = { table, matchedRecords: 0, pulledRecords: 0, filesWritten: 0, skippedEmpty: 0, truncated: false };
|
|
1464
|
+
const entry = { table, matchedRecords: 0, pulledRecords: 0, filesWritten: 0, skippedEmpty: 0, skippedUnreadable: 0, truncated: false };
|
|
1463
1465
|
if (includeRecords)
|
|
1464
1466
|
entry.records = [];
|
|
1465
|
-
|
|
1467
|
+
let reportedTotal;
|
|
1468
|
+
for (let offset = 0;;) {
|
|
1466
1469
|
const pageLimit = Math.min(PAGE, limit - entry.matchedRecords);
|
|
1467
1470
|
if (pageLimit <= 0) {
|
|
1468
1471
|
entry.truncated = true;
|
|
1469
1472
|
break;
|
|
1470
1473
|
}
|
|
1471
|
-
const page = await this.pullTablePage(inst, req.command, { table, query, codeFields, limit: pageLimit, offset });
|
|
1474
|
+
const page = await this.pullTablePage(inst, req.command, { table, query, codeFields, fieldDefinitions, limit: pageLimit, offset });
|
|
1475
|
+
if (page.pagination?.totalCount && /^\d+$/.test(page.pagination.totalCount))
|
|
1476
|
+
reportedTotal = Number(page.pagination.totalCount);
|
|
1472
1477
|
entry.matchedRecords += page.matchedRecords;
|
|
1473
1478
|
entry.pulledRecords += page.pulledRecords;
|
|
1474
1479
|
entry.filesWritten += page.filesWritten;
|
|
1475
1480
|
entry.skippedEmpty += page.skippedEmpty;
|
|
1481
|
+
entry.skippedUnreadable += page.skippedUnreadable;
|
|
1476
1482
|
if (includeRecords)
|
|
1477
1483
|
entry.records.push(...page.records);
|
|
1478
1484
|
for (const w of page.warnings)
|
|
1479
1485
|
warnings.push(`${table}: ${w}`);
|
|
1480
|
-
|
|
1486
|
+
const paging = (0, scopeDiscovery_js_1.pageState)(page.pagination, offset, pageLimit, page.matchedRecords);
|
|
1487
|
+
if (paging.nextOffset === undefined) {
|
|
1488
|
+
complete = complete && paging.complete;
|
|
1489
|
+
if (paging.warning)
|
|
1490
|
+
warnings.push(`${table}: ${paging.warning}`);
|
|
1481
1491
|
break;
|
|
1492
|
+
}
|
|
1493
|
+
offset = paging.nextOffset;
|
|
1482
1494
|
}
|
|
1495
|
+
if (!entry.truncated && reportedTotal !== undefined && reportedTotal > entry.matchedRecords)
|
|
1496
|
+
warnings.push(`${table}: ${reportedTotal - entry.matchedRecords} matching records were not returned; access controls or changing records may account for the difference.`);
|
|
1483
1497
|
if (entry.truncated)
|
|
1484
1498
|
warnings.push(`${table}: stopped at the per-table limit of ${limit} records; raise "limit" or pull the rest with pull_records.`);
|
|
1485
1499
|
totalRecords += entry.pulledRecords;
|
|
1486
1500
|
totalFiles += entry.filesWritten;
|
|
1487
1501
|
totalSkippedEmpty += entry.skippedEmpty;
|
|
1502
|
+
totalSkippedUnreadable += entry.skippedUnreadable;
|
|
1488
1503
|
perTable.push(entry);
|
|
1489
1504
|
}
|
|
1490
1505
|
for (const t of missingTables)
|
|
@@ -1495,10 +1510,12 @@ class StandaloneDispatcher {
|
|
|
1495
1510
|
status: 'success',
|
|
1496
1511
|
timestamp: Date.now(),
|
|
1497
1512
|
result: {
|
|
1513
|
+
complete: complete && !perTable.some(t => t.truncated) && warnings.length === 0,
|
|
1514
|
+
metadataRecords: discovery.rows.length,
|
|
1498
1515
|
scope: { name: scopeName, sys_id: scopeSysId },
|
|
1499
1516
|
folder: path.join(inst.name, scopeName).replace(/\\/g, '/'),
|
|
1500
1517
|
tables: perTable,
|
|
1501
|
-
totals: { tables: perTable.length, records: totalRecords, filesWritten: totalFiles, skippedEmpty: totalSkippedEmpty },
|
|
1518
|
+
totals: { tables: perTable.length, records: totalRecords, filesWritten: totalFiles, skippedEmpty: totalSkippedEmpty, skippedUnreadable: totalSkippedUnreadable },
|
|
1502
1519
|
skippedTables,
|
|
1503
1520
|
warnings,
|
|
1504
1521
|
},
|