primitive-admin 1.1.0-alpha.75 → 1.1.0-alpha.76
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/dist/bin/primitive.js +98 -10
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/functions.js +53 -18
- package/dist/src/commands/functions.js.map +1 -1
- package/dist/src/commands/prompts.js +54 -39
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/sync.d.ts +39 -0
- package/dist/src/commands/sync.js +163 -90
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/workflows.js +61 -30
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +16 -1
- package/dist/src/lib/api-client.js +16 -3
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/block-selector.d.ts +58 -0
- package/dist/src/lib/block-selector.js +92 -0
- package/dist/src/lib/block-selector.js.map +1 -0
- package/dist/src/lib/config-object-descriptor.js +1 -3
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/function-collect.d.ts +29 -7
- package/dist/src/lib/function-collect.js +135 -11
- package/dist/src/lib/function-collect.js.map +1 -1
- package/dist/src/lib/function-grants-preflight.d.ts +34 -88
- package/dist/src/lib/function-grants-preflight.js +32 -145
- package/dist/src/lib/function-grants-preflight.js.map +1 -1
- package/dist/src/lib/function-sync.d.ts +3 -14
- package/dist/src/lib/function-sync.js +10 -49
- package/dist/src/lib/function-sync.js.map +1 -1
- package/dist/src/lib/function-triggers.d.ts +6 -0
- package/dist/src/lib/function-triggers.js +63 -4
- package/dist/src/lib/function-triggers.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +188 -236
- package/dist/src/lib/generated-config-surfaces.js +355 -453
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +1 -1
- package/dist/src/lib/generated-sdk-types.js +1 -1
- package/dist/src/lib/generated-sdk-types.js.map +1 -1
- package/dist/src/lib/toml-database-config.js +19 -2
- package/dist/src/lib/toml-database-config.js.map +1 -1
- package/package.json +1 -1
|
@@ -35,11 +35,11 @@ import { buildPayloadFromToml, projectEntityToPayload, projectRecordToToml, toml
|
|
|
35
35
|
import { CONFIG_SURFACES, isEmptyForEmit, } from "../lib/generated-config-surfaces.js";
|
|
36
36
|
// #3179 — the function's two halves: the header payloads and the envelope
|
|
37
37
|
// `config push` sends, and the bytes `config pull` writes back.
|
|
38
|
-
import { applyFunctionPull, buildFunctionConfigPush, buildFunctionCreatePayload, buildFunctionUpdatePayload,
|
|
38
|
+
import { applyFunctionPull, buildFunctionConfigPush, buildFunctionCreatePayload, buildFunctionUpdatePayload, functionDiffEntity, planFunctionPull, serializeFunction, } from "../lib/function-sync.js";
|
|
39
39
|
import { FunctionBuildError } from "../lib/function-bundle.js";
|
|
40
|
-
// #3182 — the local half of
|
|
40
|
+
// #3182 — the local half of capability validation. The grammar is the vendored
|
|
41
41
|
// module the server also imports; this is where an author's own file is read.
|
|
42
|
-
import { configObjectKeysFromTree,
|
|
42
|
+
import { configObjectKeysFromTree, functionGrantErrors, mergeKnownGrantTargets, } from "../lib/function-grants-preflight.js";
|
|
43
43
|
// #3182 — the typed handle's generated declarations and the compiler wiring
|
|
44
44
|
// that makes an editor load them.
|
|
45
45
|
import { functionTypeWiringProblems, staleFunctionTypeArtifacts, writeFunctionTypeArtifacts, } from "../lib/function-db-types.js";
|
|
@@ -3209,6 +3209,114 @@ export function diffProjectedRecords(local, remote) {
|
|
|
3209
3209
|
}
|
|
3210
3210
|
return diffs;
|
|
3211
3211
|
}
|
|
3212
|
+
/**
|
|
3213
|
+
* The paths at which two projected records disagree, named the way the FILE
|
|
3214
|
+
* spells them (#3267) — what a `config diff` Modified row prints.
|
|
3215
|
+
*
|
|
3216
|
+
* `diffProjectedRecords` answers at the granularity of the projection, and
|
|
3217
|
+
* for a database type that is too coarse to act on: the 27 operations arrive
|
|
3218
|
+
* as one `operations` extra, the whole `[models.*]` tree as one `schema`
|
|
3219
|
+
* string. A row that read Modified for a reason the operator could not see —
|
|
3220
|
+
* an empty params set spelled two ways, an operation block the file declares
|
|
3221
|
+
* twice — was undiagnosable from the CLI. So this walks INTO each differing
|
|
3222
|
+
* field: a keyed child collection (`spec.childKeys`) is compared row by row
|
|
3223
|
+
* and named `operations["getInstitution"].params`, with a row only one side
|
|
3224
|
+
* has or that the file repeats named as such; a TOML-string field
|
|
3225
|
+
* (`spec.tomlFields`) is parsed on both sides and named by the path inside it
|
|
3226
|
+
* (`schema (models.Institution.fields.name.required)`); everything else is
|
|
3227
|
+
* walked as a value to its first differing leaf.
|
|
3228
|
+
*
|
|
3229
|
+
* Paths come back in projected-key order, keyed rows in key order, so the
|
|
3230
|
+
* list is stable across runs, and every leaf is its own entry — a schema
|
|
3231
|
+
* that differs in seven model fields is seven paths, so the caller's "+N
|
|
3232
|
+
* more" cap counts them the way it counts operations. A value the walk
|
|
3233
|
+
* cannot descend into (an array of a different length, a scalar against an
|
|
3234
|
+
* object) is named at the level it stopped.
|
|
3235
|
+
*/
|
|
3236
|
+
export function describeProjectedDifferences(spec, local, remote) {
|
|
3237
|
+
const paths = [];
|
|
3238
|
+
for (const { field, local: l, server: r } of diffProjectedRecords(local, remote)) {
|
|
3239
|
+
const childKey = spec.childKeys?.[field];
|
|
3240
|
+
if (childKey && Array.isArray(l) && Array.isArray(r)) {
|
|
3241
|
+
paths.push(...keyedChildDifferences(field, childKey, l, r));
|
|
3242
|
+
continue;
|
|
3243
|
+
}
|
|
3244
|
+
if (spec.tomlFields?.includes(field) && typeof l === "string" && typeof r === "string") {
|
|
3245
|
+
let inner;
|
|
3246
|
+
try {
|
|
3247
|
+
inner = valueDifferences("", parseConfigToml(l), parseConfigToml(r));
|
|
3248
|
+
}
|
|
3249
|
+
catch {
|
|
3250
|
+
// A side that does not parse is named as the whole field below.
|
|
3251
|
+
}
|
|
3252
|
+
if (inner) {
|
|
3253
|
+
if (inner.length === 0) {
|
|
3254
|
+
paths.push(field);
|
|
3255
|
+
}
|
|
3256
|
+
else {
|
|
3257
|
+
paths.push(...inner.map((path) => `${field} (${path})`));
|
|
3258
|
+
}
|
|
3259
|
+
continue;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
paths.push(...valueDifferences(field, l, r));
|
|
3263
|
+
}
|
|
3264
|
+
return paths;
|
|
3265
|
+
}
|
|
3266
|
+
/** The leaf paths under `path` at which two values differ (see above). */
|
|
3267
|
+
function valueDifferences(path, a, b) {
|
|
3268
|
+
if (canonicalJsonStringify(a) === canonicalJsonStringify(b))
|
|
3269
|
+
return [];
|
|
3270
|
+
const join = (key) => (path ? `${path}.${key}` : key);
|
|
3271
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
3272
|
+
const keys = [...new Set([...Object.keys(a), ...Object.keys(b)])].sort();
|
|
3273
|
+
return keys.flatMap((key) => valueDifferences(join(key), a[key], b[key]));
|
|
3274
|
+
}
|
|
3275
|
+
if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
|
|
3276
|
+
return a.flatMap((item, i) => valueDifferences(`${path}[${i}]`, item, b[i]));
|
|
3277
|
+
}
|
|
3278
|
+
return [path || "(document)"];
|
|
3279
|
+
}
|
|
3280
|
+
/**
|
|
3281
|
+
* A keyed child collection's differences, row by row. A key the file repeats
|
|
3282
|
+
* is named as the cause rather than compared: the server keys rows by it, so
|
|
3283
|
+
* only one of the blocks landed and no push can make the two sides agree
|
|
3284
|
+
* while the file says two things under one name.
|
|
3285
|
+
*/
|
|
3286
|
+
function keyedChildDifferences(field, key, local, remote) {
|
|
3287
|
+
const label = (k) => `${field}[${JSON.stringify(k)}]`;
|
|
3288
|
+
const localByKey = new Map();
|
|
3289
|
+
for (const row of local) {
|
|
3290
|
+
const k = String(row?.[key] ?? "");
|
|
3291
|
+
localByKey.set(k, [...(localByKey.get(k) ?? []), row]);
|
|
3292
|
+
}
|
|
3293
|
+
const remoteByKey = new Map(remote.map((row) => [String(row?.[key] ?? ""), row]));
|
|
3294
|
+
const keys = [...new Set([...localByKey.keys(), ...remoteByKey.keys()])].sort();
|
|
3295
|
+
const paths = [];
|
|
3296
|
+
for (const k of keys) {
|
|
3297
|
+
const rows = localByKey.get(k);
|
|
3298
|
+
const remoteRow = remoteByKey.get(k);
|
|
3299
|
+
if (rows && rows.length > 1) {
|
|
3300
|
+
paths.push(`${label(k)} (declared ${rows.length} times locally)`);
|
|
3301
|
+
}
|
|
3302
|
+
else if (!rows) {
|
|
3303
|
+
paths.push(`${label(k)} (server only)`);
|
|
3304
|
+
}
|
|
3305
|
+
else if (remoteRow === undefined) {
|
|
3306
|
+
paths.push(`${label(k)} (local only)`);
|
|
3307
|
+
}
|
|
3308
|
+
else {
|
|
3309
|
+
paths.push(...valueDifferences(label(k), rows[0], remoteRow));
|
|
3310
|
+
}
|
|
3311
|
+
}
|
|
3312
|
+
return paths;
|
|
3313
|
+
}
|
|
3314
|
+
function isPlainObject(value) {
|
|
3315
|
+
return (value !== null &&
|
|
3316
|
+
typeof value === "object" &&
|
|
3317
|
+
!Array.isArray(value) &&
|
|
3318
|
+
typeof value.toJSON !== "function");
|
|
3319
|
+
}
|
|
3212
3320
|
/**
|
|
3213
3321
|
* The differing fields the definition says an UPDATE does not accept (#2731 B8).
|
|
3214
3322
|
*
|
|
@@ -3415,7 +3523,6 @@ const CONFIG_DIFF_SPECS = {
|
|
|
3415
3523
|
capabilities: Array.isArray(entity.capabilities)
|
|
3416
3524
|
? [...entity.capabilities].map((c) => String(c)).sort()
|
|
3417
3525
|
: [],
|
|
3418
|
-
unscopedReads: entity.unscopedReads === true,
|
|
3419
3526
|
}),
|
|
3420
3527
|
describe: (entity) => `function "${entity?.functionKey}"`,
|
|
3421
3528
|
},
|
|
@@ -3692,6 +3799,8 @@ const CONFIG_DIFF_SPECS = {
|
|
|
3692
3799
|
subscriptions: byKey(entity.__subscriptions, "subscriptionKey"),
|
|
3693
3800
|
_unresolvedRuleSetName: entity._unresolvedRuleSetName,
|
|
3694
3801
|
}),
|
|
3802
|
+
childKeys: { operations: "name", subscriptions: "subscriptionKey" },
|
|
3803
|
+
tomlFields: ["schema"],
|
|
3695
3804
|
ruleSetRef: true,
|
|
3696
3805
|
describe: (entity) => `database type "${entity?.databaseType}"`,
|
|
3697
3806
|
},
|
|
@@ -3786,9 +3895,11 @@ export function compareLocalToRemote(spec, localParsed, remoteRecord, maps = {
|
|
|
3786
3895
|
const localHash = computeExpandedContentHash(pair.local);
|
|
3787
3896
|
if (remoteHash === localHash)
|
|
3788
3897
|
return { status: "exists" };
|
|
3898
|
+
// #3267 — WHICH field, ahead of the remedy, so a row that differs for a
|
|
3899
|
+
// reason the operator cannot see in the file is diagnosable from the CLI.
|
|
3789
3900
|
return {
|
|
3790
3901
|
status: "modified",
|
|
3791
|
-
hint: "run `config pull` to rewrite the local TOML to match running state",
|
|
3902
|
+
hint: namedDifferences(describeProjectedDifferences(spec, pair.local, pair.remote)) + "run `config pull` to rewrite the local TOML to match running state",
|
|
3792
3903
|
};
|
|
3793
3904
|
}
|
|
3794
3905
|
catch {
|
|
@@ -3797,6 +3908,24 @@ export function compareLocalToRemote(spec, localParsed, remoteRecord, maps = {
|
|
|
3797
3908
|
return { status: "exists", hint: "content not compared (comparison failed)" };
|
|
3798
3909
|
}
|
|
3799
3910
|
}
|
|
3911
|
+
/**
|
|
3912
|
+
* How many differing paths a Modified row names before saying "+N more"
|
|
3913
|
+
* (#3267). Enough to see the shape of a difference at a glance; the row is
|
|
3914
|
+
* one line of a whole-tree diff, not the place to print 27 operations.
|
|
3915
|
+
*/
|
|
3916
|
+
const NAMED_DIFFERENCE_LIMIT = 5;
|
|
3917
|
+
/**
|
|
3918
|
+
* The `differs in …; ` prefix of a Modified row's hint (#3267), or nothing
|
|
3919
|
+
* when the walk could name no path — the hash still differs, so the row is
|
|
3920
|
+
* still Modified, but the hint then says only what it always said.
|
|
3921
|
+
*/
|
|
3922
|
+
function namedDifferences(paths) {
|
|
3923
|
+
if (paths.length === 0)
|
|
3924
|
+
return "";
|
|
3925
|
+
const shown = paths.slice(0, NAMED_DIFFERENCE_LIMIT).join(", ");
|
|
3926
|
+
const more = paths.length - NAMED_DIFFERENCE_LIMIT;
|
|
3927
|
+
return `differs in ${shown}${more > 0 ? ` (+${more} more)` : ""}; `;
|
|
3928
|
+
}
|
|
3800
3929
|
/** Hash a LOCAL config file's definition-projected field set (#2644). */
|
|
3801
3930
|
export function hashLocalConfigForDiff(spec, parsedToml, maps, extra) {
|
|
3802
3931
|
return computeExpandedContentHash(projectLocalConfig(spec, parsedToml, maps, extra));
|
|
@@ -8310,52 +8439,35 @@ export function registerConfigSyncCommands(sync) {
|
|
|
8310
8439
|
preflightValidationErrors.push(` vars.toml: ${err?.message || String(err)}`);
|
|
8311
8440
|
}
|
|
8312
8441
|
}
|
|
8313
|
-
// #3182 — a function's
|
|
8314
|
-
//
|
|
8315
|
-
//
|
|
8316
|
-
//
|
|
8317
|
-
//
|
|
8318
|
-
//
|
|
8319
|
-
//
|
|
8320
|
-
//
|
|
8442
|
+
// #3182 — a function's capabilities, checked against the author's own
|
|
8443
|
+
// file before anything is applied. The grammar is purely local (and
|
|
8444
|
+
// since #3279 it keeps three kinds: `integration:<key>`,
|
|
8445
|
+
// `secret:<NAME>` and the high-blast opt-ins — a retired grant string
|
|
8446
|
+
// is refused here with the retirement); whether an `integration:`
|
|
8447
|
+
// names an integration that will exist is answered against the
|
|
8448
|
+
// tree's own `integrations/` (applied BEFORE functions) merged with
|
|
8449
|
+
// the integrations the server already has. If the live listing cannot
|
|
8450
|
+
// be fetched and the tree declares none, the target check is skipped
|
|
8451
|
+
// and the server — which is authoritative — has the last word.
|
|
8321
8452
|
const preflightFunctionsDir = join(configDir, "functions");
|
|
8322
8453
|
if (existsSync(preflightFunctionsDir)) {
|
|
8323
8454
|
const functionFiles = selectFiles("function", preflightFunctionsDir, readdirSync(preflightFunctionsDir).filter((f) => f.endsWith(".toml")));
|
|
8324
8455
|
if (functionFiles.length > 0) {
|
|
8325
|
-
const treeTypes = databaseTypesFromTree(configDir);
|
|
8326
|
-
let knownTypes = null;
|
|
8327
|
-
try {
|
|
8328
|
-
const live = await client.listDatabaseTypeConfigs(resolvedAppId);
|
|
8329
|
-
knownTypes = mergeKnownDatabaseTypes(databaseTypesFromServer(Array.isArray(live) ? live : []), treeTypes);
|
|
8330
|
-
}
|
|
8331
|
-
catch {
|
|
8332
|
-
knownTypes = treeTypes.size > 0 ? treeTypes : null;
|
|
8333
|
-
}
|
|
8334
|
-
// #3183 — the same question for the two grant families that name
|
|
8335
|
-
// config-tree objects. `secret:` and `var:` are absent on purpose
|
|
8336
|
-
// (D3183-002): their values are provisioned per environment, out
|
|
8337
|
-
// of band, so a tree can never answer for them.
|
|
8338
8456
|
const treeTargets = configObjectKeysFromTree(configDir);
|
|
8339
8457
|
let knownTargets = null;
|
|
8340
8458
|
try {
|
|
8341
|
-
const
|
|
8342
|
-
|
|
8343
|
-
|
|
8344
|
-
]);
|
|
8459
|
+
const integrations = await client.listIntegrations(resolvedAppId, {
|
|
8460
|
+
status: "active",
|
|
8461
|
+
});
|
|
8345
8462
|
knownTargets = mergeKnownGrantTargets({
|
|
8346
8463
|
integrations: new Set((integrations?.items ?? [])
|
|
8347
8464
|
.map((row) => String(row?.integrationKey ?? ""))
|
|
8348
8465
|
.filter(Boolean)),
|
|
8349
|
-
prompts: new Set((prompts?.items ?? [])
|
|
8350
|
-
.map((row) => String(row?.promptKey ?? ""))
|
|
8351
|
-
.filter(Boolean)),
|
|
8352
8466
|
}, treeTargets);
|
|
8353
8467
|
}
|
|
8354
8468
|
catch {
|
|
8355
8469
|
knownTargets =
|
|
8356
|
-
treeTargets.integrations.size > 0
|
|
8357
|
-
? treeTargets
|
|
8358
|
-
: null;
|
|
8470
|
+
treeTargets.integrations.size > 0 ? treeTargets : null;
|
|
8359
8471
|
}
|
|
8360
8472
|
for (const file of functionFiles) {
|
|
8361
8473
|
const filePath = join(preflightFunctionsDir, file);
|
|
@@ -8367,37 +8479,10 @@ export function registerConfigSyncCommands(sync) {
|
|
|
8367
8479
|
preflightValidationErrors.push(` ${filePath}: ${err?.message || String(err)}`);
|
|
8368
8480
|
continue;
|
|
8369
8481
|
}
|
|
8370
|
-
// #3187 — a read grant declared without `unscopedReads = true`
|
|
8371
|
-
// is accepted when the tree's registered reads are all bound to
|
|
8372
|
-
// `$caller`, and only the MANIFEST says whether they are. So the
|
|
8373
|
-
// functions whose decision depends on it are built and collected
|
|
8374
|
-
// here, before anything is applied; the result is memoized
|
|
8375
|
-
// against the built bundle, so the push below does not run the
|
|
8376
|
-
// tree's module scope a second time (CR3187-001).
|
|
8377
|
-
let manifest = null;
|
|
8378
|
-
if (functionNeedsManifest(tomlData)) {
|
|
8379
|
-
try {
|
|
8380
|
-
manifest = await collectManifestForPreflight({
|
|
8381
|
-
configDir,
|
|
8382
|
-
// The same key the push loop derives, so a refusal names
|
|
8383
|
-
// the function the way the rest of the run does.
|
|
8384
|
-
functionKey: tomlData.function?.key || basename(file, ".toml"),
|
|
8385
|
-
tomlData,
|
|
8386
|
-
});
|
|
8387
|
-
}
|
|
8388
|
-
catch (err) {
|
|
8389
|
-
// Fail-closed and early: the push would refuse this tree for
|
|
8390
|
-
// the same reason, naming the same error.
|
|
8391
|
-
preflightValidationErrors.push(` ${filePath}: ${err?.message || String(err)}`);
|
|
8392
|
-
continue;
|
|
8393
|
-
}
|
|
8394
|
-
}
|
|
8395
8482
|
for (const message of functionGrantErrors({
|
|
8396
8483
|
filePath,
|
|
8397
8484
|
tomlData,
|
|
8398
|
-
knownTypes,
|
|
8399
8485
|
knownTargets,
|
|
8400
|
-
manifest,
|
|
8401
8486
|
})) {
|
|
8402
8487
|
preflightValidationErrors.push(` ${message}`);
|
|
8403
8488
|
}
|
|
@@ -12838,7 +12923,7 @@ export function registerConfigSyncCommands(sync) {
|
|
|
12838
12923
|
//
|
|
12839
12924
|
// A function pushes on TWO axes and they are deliberately independent:
|
|
12840
12925
|
//
|
|
12841
|
-
// - the HEADER (`[function]`'s gate,
|
|
12926
|
+
// - the HEADER (`[function]`'s gate, schemas, plus the three
|
|
12842
12927
|
// version-carried keys, all compared through the shared gate), and
|
|
12843
12928
|
// - the CODE — the authored TOML bytes, every source file and the
|
|
12844
12929
|
// built bundle, sent as one immutable config VERSION.
|
|
@@ -15422,7 +15507,7 @@ export function registerConfigSyncCommands(sync) {
|
|
|
15422
15507
|
// below). Content comparison is a preview of `config pull` (pull would
|
|
15423
15508
|
// rewrite the local TOML to match running state), exactly like the
|
|
15424
15509
|
// workflow/transform blocks.
|
|
15425
|
-
const compareConfigType = (typeLabel, slot, localParsed, remoteEntities,
|
|
15510
|
+
const compareConfigType = (typeLabel, slot, localParsed, remoteEntities, compare, contentComparable) => {
|
|
15426
15511
|
for (const [key, parsed] of localParsed) {
|
|
15427
15512
|
if (!remoteEntities.has(key)) {
|
|
15428
15513
|
differences.push({ type: typeLabel, key, status: localOnlyStatus(slot, key) });
|
|
@@ -15437,16 +15522,10 @@ export function registerConfigSyncCommands(sync) {
|
|
|
15437
15522
|
hint = "content not compared (rule sets unavailable)";
|
|
15438
15523
|
}
|
|
15439
15524
|
else {
|
|
15440
|
-
|
|
15441
|
-
|
|
15442
|
-
|
|
15443
|
-
|
|
15444
|
-
}
|
|
15445
|
-
}
|
|
15446
|
-
catch {
|
|
15447
|
-
// A malformed local file must not abort the whole diff.
|
|
15448
|
-
hint = "content not compared (comparison failed)";
|
|
15449
|
-
}
|
|
15525
|
+
// The shared verdict names the differing field (#3267) and
|
|
15526
|
+
// degrades a malformed local file to "not compared" rather than
|
|
15527
|
+
// aborting the whole diff.
|
|
15528
|
+
({ status, hint } = compare(parsed, remoteEntities.get(key)));
|
|
15450
15529
|
}
|
|
15451
15530
|
differences.push({ type: typeLabel, key, status, hint });
|
|
15452
15531
|
}
|
|
@@ -15465,7 +15544,7 @@ export function registerConfigSyncCommands(sync) {
|
|
|
15465
15544
|
};
|
|
15466
15545
|
const compareByDefinition = (label, slot, localParsed, remoteEntities, contentComparable) => {
|
|
15467
15546
|
const spec = configDiffSpec(label);
|
|
15468
|
-
compareConfigType(label, slot, localParsed, remoteEntities, (p) =>
|
|
15547
|
+
compareConfigType(label, slot, localParsed, remoteEntities, (p, e) => compareLocalToRemote(spec, p, e, diffMaps), contentComparable);
|
|
15469
15548
|
};
|
|
15470
15549
|
compareByDefinition("rule-set", "ruleSets", localRuleSetsParsed, remoteRuleSets, true);
|
|
15471
15550
|
compareByDefinition("group-type-config", "groupTypeConfigs", localGroupTypeConfigsParsed, remoteGroupTypeConfigs, ruleSetsOk);
|
|
@@ -15497,20 +15576,14 @@ export function registerConfigSyncCommands(sync) {
|
|
|
15497
15576
|
hint = "content not compared (fetch failed)";
|
|
15498
15577
|
}
|
|
15499
15578
|
else {
|
|
15500
|
-
|
|
15501
|
-
|
|
15502
|
-
|
|
15503
|
-
|
|
15504
|
-
|
|
15505
|
-
|
|
15506
|
-
|
|
15507
|
-
|
|
15508
|
-
hint = "run `config pull` to rewrite the local TOML to match running state";
|
|
15509
|
-
}
|
|
15510
|
-
}
|
|
15511
|
-
catch {
|
|
15512
|
-
hint = "content not compared (comparison failed)";
|
|
15513
|
-
}
|
|
15579
|
+
const operations = Array.isArray(opsOutcome.value) ? opsOutcome.value : [];
|
|
15580
|
+
const subscriptions = Array.isArray(subsOutcome.value) ? subsOutcome.value : [];
|
|
15581
|
+
// The shared verdict (#3267): a Modified row names the
|
|
15582
|
+
// operation, subscription, model field or `[type]` scalar it
|
|
15583
|
+
// differs in, and a comparison that throws degrades this one
|
|
15584
|
+
// row to "not compared". The ops/subscriptions are the remote
|
|
15585
|
+
// side's extra only — the file already carries its own.
|
|
15586
|
+
({ status, hint } = compareLocalToRemote(configDiffSpec("database-type-config"), parsed, remote, diffMaps, { operations, subscriptions }));
|
|
15514
15587
|
}
|
|
15515
15588
|
}
|
|
15516
15589
|
differences.push({ type: "database-type-config", key, status, hint });
|