primitive-admin 1.1.0-alpha.65 → 1.1.0-alpha.66
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 +4 -4
- package/assets/skill/skills/primitive-platform/SKILL.md +15 -7
- package/dist/bin/primitive.js +4 -3
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/apps.js +14 -5
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/env.d.ts +12 -1
- package/dist/src/commands/env.js +96 -21
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/init.d.ts +1 -1
- package/dist/src/commands/init.js +78 -25
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/sync-app-settings.d.ts +83 -55
- package/dist/src/commands/sync-app-settings.js +131 -200
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +240 -28
- package/dist/src/commands/sync.js +2022 -309
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/workflows.js +14 -3
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +143 -15
- package/dist/src/lib/app-settings-descriptor.js +315 -28
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/config-payload.js +14 -3
- package/dist/src/lib/config-payload.js.map +1 -1
- package/dist/src/lib/config-surface.d.ts +37 -1
- package/dist/src/lib/config-surface.js +118 -0
- package/dist/src/lib/config-surface.js.map +1 -1
- package/dist/src/lib/env-resolver-core.d.ts +147 -0
- package/dist/src/lib/env-resolver-core.js +265 -0
- package/dist/src/lib/env-resolver-core.js.map +1 -0
- package/dist/src/lib/env-resolver.d.ts +28 -6
- package/dist/src/lib/env-resolver.js +44 -32
- package/dist/src/lib/env-resolver.js.map +1 -1
- package/dist/src/lib/generated-config-surfaces.d.ts +33 -0
- package/dist/src/lib/generated-config-surfaces.js +151 -49
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/integration-request-config.d.ts +30 -0
- package/dist/src/lib/integration-request-config.js +145 -0
- package/dist/src/lib/integration-request-config.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +55 -0
- package/dist/src/lib/local-state.js +167 -0
- package/dist/src/lib/local-state.js.map +1 -0
- package/dist/src/lib/server-text-normalization.d.ts +51 -0
- package/dist/src/lib/server-text-normalization.js +90 -0
- package/dist/src/lib/server-text-normalization.js.map +1 -0
- package/dist/src/lib/sync-resource-types.d.ts +8 -0
- package/dist/src/lib/sync-resource-types.js +8 -0
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/template.d.ts +27 -0
- package/dist/src/lib/template.js +46 -20
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/types/index.d.ts +31 -7
- package/package.json +2 -2
|
@@ -11,19 +11,26 @@
|
|
|
11
11
|
* plus the one thing the file cannot answer — `renderAppSettings`, the
|
|
12
12
|
* server-effective view behind `primitive apps get` (#2759).
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
* `
|
|
16
|
-
*
|
|
14
|
+
* #2880 added the comparison the whole epic is about — `appSettingsFieldDiffs`,
|
|
15
|
+
* `hashAppSettingsForDiff` and `appSettingsNameExcluded` — and removed the last
|
|
16
|
+
* caller of the retired byte-hash/present-only rule (the `settings push`
|
|
17
|
+
* orchestrator #2645 had already unhooked). `app.toml` is now compared field by
|
|
18
|
+
* field against live state by both commands, over the full writable descriptor
|
|
19
|
+
* set, and applied as the whole truth.
|
|
20
|
+
*
|
|
21
|
+
* Google is a typed client map here (#2891): `googleClients` renders as
|
|
22
|
+
* `[auth.google.clients.<type>]`, and each entry's `clientSecret` holds a whole
|
|
23
|
+
* `{{secrets.KEY}}` reference, not the secret itself, so the map pulls, pushes,
|
|
24
|
+
* diffs and shows like any other setting. The secrets' values live only in the
|
|
17
25
|
* app secret store (`primitive secrets set`).
|
|
18
26
|
*/
|
|
19
27
|
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
20
28
|
import { join } from "path";
|
|
21
29
|
import { createHash } from "crypto";
|
|
22
30
|
import { parseConfigToml, stringifyConfigToml } from "../lib/config-toml.js";
|
|
23
|
-
import { APP_SETTINGS_DESCRIPTOR, APP_SETTINGS_SECTIONS,
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import { success, error, info, warn, json, result as printResult, divider, } from "../lib/output.js";
|
|
31
|
+
import { APP_SETTINGS_DESCRIPTOR, APP_SETTINGS_SECTIONS, buildAppSettingsPayload, projectAppSettingsForComparison, serializeAppSettingsToToml, } from "../lib/app-settings-descriptor.js";
|
|
32
|
+
import { canonicalJsonStringify } from "../lib/canonical-json.js";
|
|
33
|
+
import { success, error, info, json, result as printResult, divider, } from "../lib/output.js";
|
|
27
34
|
/** The app-settings file name inside a sync directory. */
|
|
28
35
|
export const APP_TOML_FILENAME = "app.toml";
|
|
29
36
|
/** Absolute path to `app.toml` inside a sync directory. */
|
|
@@ -55,37 +62,18 @@ export function writeAppSettingsToml(configDir, settings) {
|
|
|
55
62
|
writeFileSync(path, serializeAppSettings(settings));
|
|
56
63
|
return contentHashOfFile(path);
|
|
57
64
|
}
|
|
58
|
-
/**
|
|
59
|
-
* The pull-time warning for a `googleClientSecret` the server withheld, or
|
|
60
|
-
* `null` when the stored value is a reference (or unset).
|
|
61
|
-
*
|
|
62
|
-
* Reported by `config pull` wherever it writes `app.toml` — no pull said
|
|
63
|
-
* anything at all before (#2307). A withheld value is
|
|
64
|
-
* absent from the `app.toml` they just wrote AND absent from a push built out
|
|
65
|
-
* of that file, and a settings write that does not carry a `googleClientSecret`
|
|
66
|
-
* is exactly what the server rejects while the stored value is unmigrated
|
|
67
|
-
* (`validateGoogleClientSecretMigration`). So the pulled file is not pushable
|
|
68
|
-
* until the field is migrated, and pull is where the operator can still act on
|
|
69
|
-
* that.
|
|
70
|
-
*
|
|
71
|
-
* `subject` lets `config pull` name the file and match the indentation of its
|
|
72
|
-
* other per-entity lines.
|
|
73
|
-
*/
|
|
74
|
-
export function pulledGoogleClientSecretWarning(settings, options = {}) {
|
|
75
|
-
return googleClientSecretStatusWarning(settings?.googleClientSecretStatus, {
|
|
76
|
-
subject: options.subject ?? "googleClientSecret",
|
|
77
|
-
withheldFrom: "the app.toml just written",
|
|
78
|
-
blocks: "pushing this app.toml back is rejected",
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
65
|
/**
|
|
82
66
|
* Validate a parsed `app.toml` for push and return the fatal errors (a type
|
|
83
|
-
* mismatch
|
|
84
|
-
*
|
|
85
|
-
*
|
|
67
|
+
* mismatch, an unrecognized or retired key, a missing required key). An empty
|
|
68
|
+
* array means the push may proceed. Warnings (advisory notes) are intentionally
|
|
69
|
+
* not returned here — the caller surfaces those at apply time.
|
|
70
|
+
*
|
|
71
|
+
* ONE validator, run by `config push`'s preflight AND by `config diff` (#2880
|
|
72
|
+
* criterion 1): a file diff calls Synced is a file push accepts, and a file
|
|
73
|
+
* push refuses is named by diff with the identical message.
|
|
86
74
|
*/
|
|
87
75
|
export function collectAppSettingsPushErrors(tomlData, options = {}) {
|
|
88
|
-
return
|
|
76
|
+
return buildAppSettingsPayload(tomlData, options).errors;
|
|
89
77
|
}
|
|
90
78
|
// --- Shared single sync-state entry (entities.app) -------------------------
|
|
91
79
|
// These read and write the SAME `.primitive-sync.json` that
|
|
@@ -106,36 +94,6 @@ function loadSyncState(configDir) {
|
|
|
106
94
|
return null;
|
|
107
95
|
}
|
|
108
96
|
}
|
|
109
|
-
function saveSyncState(configDir, state) {
|
|
110
|
-
writeFileSync(syncStatePath(configDir), JSON.stringify(state, null, 2));
|
|
111
|
-
}
|
|
112
|
-
/**
|
|
113
|
-
* Update `entities.app.contentHash`, preserving all other entities.
|
|
114
|
-
*
|
|
115
|
-
* On a cross-app stamp (the loaded state belongs to a different app than the
|
|
116
|
-
* one we just pulled from / pushed to) this mirrors `config push` (see
|
|
117
|
-
* `sync.ts`): it rebinds the sync state to the target app with fresh entities,
|
|
118
|
-
* then records the app-settings hash. Without the rebind the stored
|
|
119
|
-
* `state.appId` would stay pointing at the source app, so every later push to
|
|
120
|
-
* that target would keep computing `isCrossAppPush` as true — it would ignore
|
|
121
|
-
* the stored hash and keep stripping `[app].name`, making it impossible to
|
|
122
|
-
* rename the target app via `app.toml`.
|
|
123
|
-
*/
|
|
124
|
-
function stampAppContentHash(configDir, appId, contentHash) {
|
|
125
|
-
const now = new Date().toISOString();
|
|
126
|
-
const existing = loadSyncState(configDir);
|
|
127
|
-
const isCrossAppStamp = !!(existing && existing.appId !== appId);
|
|
128
|
-
const state = existing && !isCrossAppStamp
|
|
129
|
-
? existing
|
|
130
|
-
: {
|
|
131
|
-
appId,
|
|
132
|
-
serverUrl: existing?.serverUrl ?? getServerUrl(),
|
|
133
|
-
lastSyncedAt: now,
|
|
134
|
-
entities: {},
|
|
135
|
-
};
|
|
136
|
-
state.entities.app = { modifiedAt: now, contentHash };
|
|
137
|
-
saveSyncState(configDir, state);
|
|
138
|
-
}
|
|
139
97
|
// --- Display helpers -------------------------------------------------------
|
|
140
98
|
function formatValue(value) {
|
|
141
99
|
if (value === undefined)
|
|
@@ -148,9 +106,18 @@ function formatValue(value) {
|
|
|
148
106
|
return JSON.stringify(value);
|
|
149
107
|
return String(value);
|
|
150
108
|
}
|
|
151
|
-
/**
|
|
109
|
+
/**
|
|
110
|
+
* Deep equality for one setting's comparable value.
|
|
111
|
+
*
|
|
112
|
+
* The SAME canonical encoding `hashAppSettingsForDiff` hashes with (#2880):
|
|
113
|
+
* `JSON.stringify` preserves object key ORDER, so reordering the
|
|
114
|
+
* `[auth.google.clients.<type>]` tables in `app.toml` reported a `Modified`
|
|
115
|
+
* field while the push gate — hashing the sorted encoding — skipped the file.
|
|
116
|
+
* A cosmetic edit has to be a change to neither command, so both read one
|
|
117
|
+
* encoding. Arrays stay order-sensitive: a reordered list is a real edit.
|
|
118
|
+
*/
|
|
152
119
|
function settingsEqual(a, b) {
|
|
153
|
-
return
|
|
120
|
+
return canonicalJsonStringify(a ?? null) === canonicalJsonStringify(b ?? null);
|
|
154
121
|
}
|
|
155
122
|
// --- Command orchestrators -------------------------------------------------
|
|
156
123
|
/**
|
|
@@ -161,15 +128,11 @@ function settingsEqual(a, b) {
|
|
|
161
128
|
* where the hand-curated block `apps get` used to print showed whichever
|
|
162
129
|
* fields somebody remembered to add.
|
|
163
130
|
*
|
|
164
|
-
* Nothing is redacted (#2256):
|
|
165
|
-
*
|
|
166
|
-
* reference
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
* cannot reach the terminal or `app.toml` through this path either — the
|
|
170
|
-
* withheld value is reported as a remediation note instead, the same way
|
|
171
|
-
* `primitive webhooks get` reports a legacy `signingSecret`. Without it the
|
|
172
|
-
* operator sees a blank `googleClientSecret` and no reason for it.
|
|
131
|
+
* Nothing is redacted (#2256, #2891): the only credential-shaped settings are
|
|
132
|
+
* the per-entry `clientSecret`s inside `googleClients`, and each is a whole
|
|
133
|
+
* `{{secrets.KEY}}` reference rather than the secret itself — so printing one
|
|
134
|
+
* tells the operator which secret is in use rather than leaking one. There is
|
|
135
|
+
* no withheld state left to explain.
|
|
173
136
|
*
|
|
174
137
|
* Takes settings rather than a client, and never exits: `apps get` fetches the
|
|
175
138
|
* app and its settings together and tolerates a settings fetch it is not
|
|
@@ -184,118 +147,100 @@ export function renderAppSettings(settings) {
|
|
|
184
147
|
printResult(` ${d.key}`, formatValue(settings[d.field]));
|
|
185
148
|
}
|
|
186
149
|
}
|
|
187
|
-
// `googleClientSecretStatus` is not a descriptor field (it is derived, and
|
|
188
|
-
// never written back), so it prints here rather than in a section.
|
|
189
|
-
const statusWarning = googleClientSecretStatusWarning(settings?.googleClientSecretStatus, {
|
|
190
|
-
subject: "googleClientSecret",
|
|
191
|
-
withheldFrom: "this output",
|
|
192
|
-
blocks: "the next settings write will be rejected",
|
|
193
|
-
});
|
|
194
|
-
if (statusWarning) {
|
|
195
|
-
divider();
|
|
196
|
-
warn(statusWarning);
|
|
197
|
-
}
|
|
198
150
|
}
|
|
199
151
|
/**
|
|
200
|
-
* Write the pulled `app.toml
|
|
152
|
+
* Write the pulled `app.toml`.
|
|
201
153
|
*
|
|
202
|
-
* The
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* `--only app` — the `settings pull` that used to share it is gone.
|
|
154
|
+
* The file carries EVERY writable setting as of #2891 — the server withholds
|
|
155
|
+
* nothing, so there is no longer a class of value the pull has to warn about
|
|
156
|
+
* being absent (#2307's warning went with the withholding layer). `config pull`
|
|
157
|
+
* calls this for `app.toml`, whether the pull is whole-tree or `--only app`.
|
|
207
158
|
*/
|
|
208
159
|
export function writePulledAppSettings(configDir, settings, options = {}) {
|
|
209
160
|
ensureDir(configDir);
|
|
210
161
|
const contentHash = writeAppSettingsToml(configDir, settings);
|
|
211
|
-
const statusWarning = pulledGoogleClientSecretWarning(settings, {
|
|
212
|
-
subject: options.subject,
|
|
213
|
-
});
|
|
214
|
-
if (statusWarning)
|
|
215
|
-
warn(statusWarning);
|
|
216
162
|
options.log?.(`Wrote ${appTomlPath(configDir)}`);
|
|
217
163
|
return { path: appTomlPath(configDir), contentHash };
|
|
218
164
|
}
|
|
219
165
|
/**
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
166
|
+
* Whether `[app].name` is out of BOTH the comparison and the payload right now
|
|
167
|
+
* (#2880 DSO-001) — the cross-app rule, shared so diff and push cannot disagree.
|
|
168
|
+
*
|
|
169
|
+
* A cross-app push (the sync state belongs to a different app) applies the file
|
|
170
|
+
* to the target with `name` excluded, so it never renames the target, and it
|
|
171
|
+
* RECORDS the file's name in `entities.app.excludedName` with the baseline
|
|
172
|
+
* hashed the same way. While that record stands and the file still states that
|
|
173
|
+
* name, the field stays excluded: the target keeps its own name and no phantom
|
|
174
|
+
* difference appears.
|
|
175
|
+
*
|
|
176
|
+
* A file name that DIFFERS from the recorded one is, by construction, a local
|
|
177
|
+
* name edit — the operator changed the line — so the exclusion lifts, the name
|
|
178
|
+
* goes into the payload, and the baseline is restamped with `name` from the
|
|
179
|
+
* server's returned record. It cannot be discovered by comparison instead:
|
|
180
|
+
* `classifyPushChange` only answers `local-edited` when the remote hash equals
|
|
181
|
+
* the baseline, and a baseline stamped without `name` matches neither side once
|
|
182
|
+
* `name` re-enters, so every post-cross-app rename would report `both-changed`.
|
|
225
183
|
*/
|
|
226
|
-
export
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
184
|
+
export function appSettingsNameExcluded(input) {
|
|
185
|
+
if (input.isCrossAppPush)
|
|
186
|
+
return true;
|
|
187
|
+
if (input.recordedExcludedName === undefined)
|
|
188
|
+
return false;
|
|
189
|
+
return input.fileName === input.recordedExcludedName;
|
|
190
|
+
}
|
|
191
|
+
/** `appSettingsNameExcluded` for a caller holding the loaded state (diff). */
|
|
192
|
+
export function appSettingsNameExcludedForState(state, appId, tomlData) {
|
|
193
|
+
return appSettingsNameExcluded({
|
|
194
|
+
isCrossAppPush: !!(state && state.appId !== appId),
|
|
195
|
+
recordedExcludedName: state?.entities?.app?.excludedName,
|
|
196
|
+
fileName: tomlData?.app?.name,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The fields the local `app.toml` and the live settings disagree on (#2880).
|
|
201
|
+
*
|
|
202
|
+
* Compared over the FULL writable descriptor set, not just the keys the file
|
|
203
|
+
* carries: `app.toml` is the whole truth, so a key the file omits is not "no
|
|
204
|
+
* opinion", it is the absent-value the push will send. Both sides run through
|
|
205
|
+
* `projectAppSettingsForComparison`, which applies the same canonicalization
|
|
206
|
+
* and the same `whenSet`-empty flattening to each — so a pulled file compares
|
|
207
|
+
* equal to the server it was pulled from, and a deleted key shows as the clear
|
|
208
|
+
* push would apply.
|
|
209
|
+
*
|
|
210
|
+
* One helper, three callers: `config diff --only app`'s per-field output, the
|
|
211
|
+
* app-settings row in the full `config diff`, and the push gate.
|
|
212
|
+
*/
|
|
213
|
+
export function appSettingsFieldDiffs(localSettings, remoteSettings, options = {}) {
|
|
214
|
+
const local = projectAppSettingsForComparison(localSettings, options);
|
|
215
|
+
const remote = projectAppSettingsForComparison(remoteSettings, options);
|
|
216
|
+
const diffs = [];
|
|
217
|
+
for (const d of APP_SETTINGS_DESCRIPTOR) {
|
|
218
|
+
if (!(d.field in local))
|
|
219
|
+
continue;
|
|
220
|
+
if (settingsEqual(local[d.field], remote[d.field]))
|
|
221
|
+
continue;
|
|
222
|
+
diffs.push({
|
|
223
|
+
field: d.field,
|
|
224
|
+
section: d.section,
|
|
225
|
+
key: d.key,
|
|
226
|
+
local: local[d.field],
|
|
227
|
+
remote: remote[d.field],
|
|
245
228
|
});
|
|
246
|
-
// Fail before any mutation on a type mismatch.
|
|
247
|
-
if (errors.length > 0) {
|
|
248
|
-
error(`Aborting push: ${errors.length} validation error(s) in ${APP_TOML_FILENAME} — no changes were applied.`);
|
|
249
|
-
for (const e of errors)
|
|
250
|
-
error(` ${e}`);
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
// Skip when nothing changed since the last sync (shared content hash).
|
|
254
|
-
const storedHash = isCrossAppPush
|
|
255
|
-
? undefined
|
|
256
|
-
: state?.entities?.app?.contentHash;
|
|
257
|
-
const changed = options.force || !storedHash || contentHashOfFile(path) !== storedHash;
|
|
258
|
-
if (!changed) {
|
|
259
|
-
if (options.json) {
|
|
260
|
-
json({ applied: false, skipped: true, reason: "unchanged" });
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
info("Skipped app settings (unchanged). Use --force to push anyway.");
|
|
264
|
-
}
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
for (const w of warnings)
|
|
268
|
-
warn(` ${w}`);
|
|
269
|
-
if (options.dryRun) {
|
|
270
|
-
if (options.json) {
|
|
271
|
-
json({ applied: false, dryRun: true, settings });
|
|
272
|
-
}
|
|
273
|
-
else {
|
|
274
|
-
info("[dry-run] Would update app settings.");
|
|
275
|
-
}
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
await client.updateAppSettings(appId, settings);
|
|
279
|
-
stampAppContentHash(configDir, appId, contentHashOfFile(path));
|
|
280
|
-
if (options.json) {
|
|
281
|
-
json({ applied: true, fields: Object.keys(settings) });
|
|
282
|
-
return;
|
|
283
|
-
}
|
|
284
|
-
success("Updated app settings.");
|
|
285
|
-
}
|
|
286
|
-
catch (err) {
|
|
287
|
-
// Surface the server's machine-readable code alongside the message when it
|
|
288
|
-
// sends one (e.g. `GOOGLE_CLIENT_SECRET_MUST_BE_SECRET_REF`, #2256) — it is
|
|
289
|
-
// the stable identifier to search for or report, where the prose is not.
|
|
290
|
-
error(err?.code ? `${err.message} (${err.code})` : err.message);
|
|
291
|
-
process.exit(1);
|
|
292
229
|
}
|
|
230
|
+
return diffs;
|
|
293
231
|
}
|
|
294
232
|
/**
|
|
295
|
-
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
|
|
233
|
+
* The comparator's hash of one side's app settings — the push gate's
|
|
234
|
+
* `localHash`/`remoteHash`, and the `entities.app.semanticHash` baseline
|
|
235
|
+
* stamped once the two agree (#2880 criterion 6).
|
|
236
|
+
*/
|
|
237
|
+
export function hashAppSettingsForDiff(settings, options = {}) {
|
|
238
|
+
return createHash("sha256")
|
|
239
|
+
.update(canonicalJsonStringify(projectAppSettingsForComparison(settings, options)))
|
|
240
|
+
.digest("hex");
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* `config diff --only app` — the per-field report, over the full writable set.
|
|
299
244
|
*/
|
|
300
245
|
export async function diffAppSettings(client, appId, configDir, options = {}) {
|
|
301
246
|
try {
|
|
@@ -314,21 +259,22 @@ export async function diffAppSettings(client, appId, configDir, options = {}) {
|
|
|
314
259
|
error(`Aborting diff: could not parse ${APP_TOML_FILENAME}: ${err?.message || String(err)}`);
|
|
315
260
|
process.exit(1);
|
|
316
261
|
}
|
|
317
|
-
// Mirror push's cross-app determination
|
|
318
|
-
//
|
|
319
|
-
//
|
|
320
|
-
//
|
|
262
|
+
// Mirror push's cross-app determination: when the sync state belongs to a
|
|
263
|
+
// different app, the push payload leaves `[app].name` out, so diff leaves it
|
|
264
|
+
// out too — otherwise a malformed local `name` would abort diff on a file
|
|
265
|
+
// push would accept and ignore, and the target's own name would report as a
|
|
266
|
+
// difference push has no intention of applying.
|
|
321
267
|
const state = loadSyncState(configDir);
|
|
322
|
-
const
|
|
323
|
-
//
|
|
324
|
-
//
|
|
325
|
-
const { settings: local, errors } =
|
|
326
|
-
|
|
268
|
+
const excludeName = appSettingsNameExcludedForState(state, appId, tomlData);
|
|
269
|
+
// ONE validator with push (#2880 criterion 1): whole-truth payload, so a
|
|
270
|
+
// missing required key is an error here exactly as it is there.
|
|
271
|
+
const { settings: local, errors } = buildAppSettingsPayload(tomlData, {
|
|
272
|
+
excludeName,
|
|
327
273
|
});
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
274
|
+
// A file with validation errors is unpushable, so diff must not present its
|
|
275
|
+
// surviving values as clean (or as a partial diff) — that would advertise
|
|
276
|
+
// changes push will never apply. `--json` is ignored on this path, exactly
|
|
277
|
+
// as push does.
|
|
332
278
|
if (errors.length > 0) {
|
|
333
279
|
error(`Aborting diff: ${errors.length} validation error(s) in ${APP_TOML_FILENAME} — this file cannot be pushed.`);
|
|
334
280
|
for (const e of errors)
|
|
@@ -336,22 +282,7 @@ export async function diffAppSettings(client, appId, configDir, options = {}) {
|
|
|
336
282
|
process.exit(1);
|
|
337
283
|
}
|
|
338
284
|
const remote = (await client.getAppSettings(appId)) ?? {};
|
|
339
|
-
const diffs =
|
|
340
|
-
for (const d of APP_SETTINGS_DESCRIPTOR) {
|
|
341
|
-
if (!(d.field in local))
|
|
342
|
-
continue;
|
|
343
|
-
const localVal = local[d.field];
|
|
344
|
-
const remoteVal = remote[d.field];
|
|
345
|
-
if (!settingsEqual(localVal, remoteVal)) {
|
|
346
|
-
diffs.push({
|
|
347
|
-
field: d.field,
|
|
348
|
-
section: d.section,
|
|
349
|
-
key: d.key,
|
|
350
|
-
local: localVal,
|
|
351
|
-
remote: remoteVal,
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
}
|
|
285
|
+
const diffs = appSettingsFieldDiffs(local, remote, { excludeName });
|
|
355
286
|
if (options.json) {
|
|
356
287
|
json({ diffs });
|
|
357
288
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-app-settings.js","sourceRoot":"","sources":["../../../src/commands/sync-app-settings.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sync-app-settings.js","sourceRoot":"","sources":["../../../src/commands/sync-app-settings.ts"],"names":[],"mappings":"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"}
|