primitive-admin 1.0.54 → 1.0.56
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/assets/skill/skills/primitive-platform/SKILL.md +230 -2
- package/dist/bin/primitive.js +3 -1
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/apps.js +54 -2
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/database-types.js +4 -4
- package/dist/src/commands/database-types.js.map +1 -1
- package/dist/src/commands/databases.js +35 -14
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +77 -0
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/guides.d.ts +15 -12
- package/dist/src/commands/guides.js +22 -15
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/metadata.js +48 -3
- package/dist/src/commands/metadata.js.map +1 -1
- package/dist/src/commands/scripts.d.ts +18 -0
- package/dist/src/commands/scripts.js +59 -5
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/settings.d.ts +15 -0
- package/dist/src/commands/settings.js +102 -0
- package/dist/src/commands/settings.js.map +1 -0
- package/dist/src/commands/sync-app-settings.d.ts +115 -0
- package/dist/src/commands/sync-app-settings.js +379 -0
- package/dist/src/commands/sync-app-settings.js.map +1 -0
- package/dist/src/commands/sync.d.ts +134 -19
- package/dist/src/commands/sync.js +1949 -600
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/workflows.js +54 -15
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +29 -1
- package/dist/src/lib/api-client.js +31 -2
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-settings-descriptor.d.ts +108 -0
- package/dist/src/lib/app-settings-descriptor.js +254 -0
- package/dist/src/lib/app-settings-descriptor.js.map +1 -0
- package/dist/src/lib/codegen-shared/generatedFiles.d.ts +36 -0
- package/dist/src/lib/codegen-shared/generatedFiles.js +67 -10
- package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -1
- package/dist/src/lib/codegen-shared/prettierStable.d.ts +262 -0
- package/dist/src/lib/codegen-shared/prettierStable.js +610 -0
- package/dist/src/lib/codegen-shared/prettierStable.js.map +1 -0
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +11 -83
- package/dist/src/lib/db-codegen/dbGenerator.js +96 -556
- package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
- package/dist/src/lib/db-codegen/dbNaming.d.ts +20 -7
- package/dist/src/lib/db-codegen/dbNaming.js +31 -13
- package/dist/src/lib/db-codegen/dbNaming.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +70 -4
- package/dist/src/lib/db-codegen/dbTemplates.js +244 -70
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +13 -22
- package/dist/src/lib/db-codegen/dbTsTypes.js +33 -28
- package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTypeIR.d.ts +146 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js +517 -0
- package/dist/src/lib/db-codegen/dbTypeIR.js.map +1 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.d.ts +112 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js +211 -0
- package/dist/src/lib/db-codegen/generated-operation-def-descriptor.js.map +1 -0
- package/dist/src/lib/generated-allowlist.js +7 -0
- package/dist/src/lib/generated-allowlist.js.map +1 -1
- package/dist/src/lib/platform-owned.d.ts +37 -0
- package/dist/src/lib/platform-owned.js +46 -0
- package/dist/src/lib/platform-owned.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbGenerator.d.ts +100 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js +682 -0
- package/dist/src/lib/swift-codegen/dbGenerator.js.map +1 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.d.ts +42 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js +100 -0
- package/dist/src/lib/swift-codegen/dbSwiftTypes.js.map +1 -0
- package/dist/src/lib/swift-codegen/generator.d.ts +85 -0
- package/dist/src/lib/swift-codegen/generator.js +266 -0
- package/dist/src/lib/swift-codegen/generator.js.map +1 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.d.ts +72 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js +644 -0
- package/dist/src/lib/swift-codegen/schemaToSwift.js.map +1 -0
- package/dist/src/lib/swift-codegen/siblingSymbols.d.ts +90 -0
- package/dist/src/lib/swift-codegen/siblingSymbols.js +152 -0
- package/dist/src/lib/swift-codegen/siblingSymbols.js.map +1 -0
- package/dist/src/lib/swift-codegen/swiftNaming.d.ts +85 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js +198 -0
- package/dist/src/lib/swift-codegen/swiftNaming.js.map +1 -0
- package/dist/src/lib/sync-resource-types.d.ts +437 -0
- package/dist/src/lib/sync-resource-types.js +781 -0
- package/dist/src/lib/sync-resource-types.js.map +1 -0
- package/dist/src/lib/template.d.ts +1 -1
- package/dist/src/lib/template.js +6 -3
- package/dist/src/lib/template.js.map +1 -1
- package/dist/src/lib/toml-metadata-config.js +18 -0
- package/dist/src/lib/toml-metadata-config.js.map +1 -1
- package/dist/src/lib/workflow-apply.js +12 -2
- package/dist/src/lib/workflow-apply.js.map +1 -1
- package/dist/src/lib/workflow-codegen/generator.d.ts +27 -5
- package/dist/src/lib/workflow-codegen/generator.js +214 -68
- package/dist/src/lib/workflow-codegen/generator.js.map +1 -1
- package/dist/src/lib/workflow-codegen/invokerIR.d.ts +83 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js +71 -0
- package/dist/src/lib/workflow-codegen/invokerIR.js.map +1 -0
- package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +17 -1
- package/dist/src/lib/workflow-codegen/schemaToTs.js +59 -38
- package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -1
- package/dist/src/lib/workflow-payload.d.ts +19 -0
- package/dist/src/lib/workflow-payload.js +21 -0
- package/dist/src/lib/workflow-payload.js.map +1 -1
- package/dist/src/lib/workflow-toml-validator.d.ts +30 -0
- package/dist/src/lib/workflow-toml-validator.js +206 -0
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared app-settings sync helpers (issue #1033, Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* The descriptor-driven `app.toml` round-trip (issue #1033, Phase 1) was born
|
|
5
|
+
* inside the big `sync pull` / `sync push` command flow. Phase 2 lifts the
|
|
6
|
+
* app-settings-specific pieces here so BOTH surfaces drive them:
|
|
7
|
+
*
|
|
8
|
+
* - `sync pull` / `sync push` call the low-level helpers
|
|
9
|
+
* (`serializeAppSettings`, `collectAppSettingsPushErrors`) — behavior
|
|
10
|
+
* unchanged from Phase 1.
|
|
11
|
+
* - the new `primitive settings show|pull|push|diff` command
|
|
12
|
+
* (`cli/src/commands/settings.ts`) calls the higher-level orchestrators
|
|
13
|
+
* (`pullAppSettings`, `pushAppSettings`, `diffAppSettings`, `showAppSettings`).
|
|
14
|
+
*
|
|
15
|
+
* Both directions share the same `app.toml`, the same field descriptor
|
|
16
|
+
* (`cli/src/lib/app-settings-descriptor.ts`), and the same single
|
|
17
|
+
* `entities.app.contentHash` sync-state entry, so `settings push` then
|
|
18
|
+
* `sync push` (or the reverse) is idempotent — the second one skips on the
|
|
19
|
+
* unchanged hash.
|
|
20
|
+
*
|
|
21
|
+
* `googleClientSecret` never leaves the server through here: pull omits it,
|
|
22
|
+
* push errors on it (pointing at `primitive apps update --google-client-secret`),
|
|
23
|
+
* and `settings show` redacts it in both the rendered and `--json` views.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
26
|
+
import { join } from "path";
|
|
27
|
+
import { createHash } from "crypto";
|
|
28
|
+
import { parseConfigToml, stringifyConfigToml } from "../lib/config-toml.js";
|
|
29
|
+
import { APP_SETTINGS_DESCRIPTOR, APP_SETTINGS_SECTIONS, serializeAppSettingsToToml, parseTomlToAppSettings, } from "../lib/app-settings-descriptor.js";
|
|
30
|
+
import { getServerUrl } from "../lib/config.js";
|
|
31
|
+
import { success, error, info, warn, json, result as printResult, divider, } from "../lib/output.js";
|
|
32
|
+
/** The app-settings file name inside a sync directory. */
|
|
33
|
+
export const APP_TOML_FILENAME = "app.toml";
|
|
34
|
+
/** Absolute path to `app.toml` inside a sync directory. */
|
|
35
|
+
export function appTomlPath(configDir) {
|
|
36
|
+
return join(configDir, APP_TOML_FILENAME);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Serialize server settings into the `app.toml` string (pull direction).
|
|
40
|
+
* Descriptor-driven; `googleClientSecret` is never emitted. This is the single
|
|
41
|
+
* serializer both `sync pull` and `settings pull` use.
|
|
42
|
+
*/
|
|
43
|
+
export function serializeAppSettings(settings) {
|
|
44
|
+
return stringifyConfigToml(serializeAppSettingsToToml(settings));
|
|
45
|
+
}
|
|
46
|
+
/** SHA-256 of a file's bytes — the per-file content hash tracked in sync state. */
|
|
47
|
+
function contentHashOfFile(filePath) {
|
|
48
|
+
return createHash("sha256").update(readFileSync(filePath)).digest("hex");
|
|
49
|
+
}
|
|
50
|
+
function ensureDir(dirPath) {
|
|
51
|
+
if (!existsSync(dirPath))
|
|
52
|
+
mkdirSync(dirPath, { recursive: true });
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Write `app.toml` from server settings and return its content hash. Shared by
|
|
56
|
+
* `sync pull` and `settings pull` so the two produce byte-identical files and
|
|
57
|
+
* the same hash.
|
|
58
|
+
*/
|
|
59
|
+
export function writeAppSettingsToml(configDir, settings) {
|
|
60
|
+
const path = appTomlPath(configDir);
|
|
61
|
+
writeFileSync(path, serializeAppSettings(settings));
|
|
62
|
+
return contentHashOfFile(path);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Validate a parsed `app.toml` for push and return the fatal errors (a
|
|
66
|
+
* hand-added secret key, a type mismatch). An empty array means the push may
|
|
67
|
+
* proceed. Warnings (unrecognized keys) are intentionally not returned here —
|
|
68
|
+
* the caller surfaces those at apply time. Shared by the `sync push` preflight
|
|
69
|
+
* and `settings push`.
|
|
70
|
+
*/
|
|
71
|
+
export function collectAppSettingsPushErrors(tomlData, options = {}) {
|
|
72
|
+
return parseTomlToAppSettings(tomlData, options).errors;
|
|
73
|
+
}
|
|
74
|
+
// --- Shared single sync-state entry (entities.app) -------------------------
|
|
75
|
+
// `settings pull/push` read and write the SAME `.primitive-sync.json` that
|
|
76
|
+
// `sync` maintains, touching only `entities.app` and preserving every other
|
|
77
|
+
// entity. Kept as small local load/save helpers (rather than importing
|
|
78
|
+
// `sync.ts`) to avoid an import cycle — `sync.ts` imports the serializer above.
|
|
79
|
+
function syncStatePath(configDir) {
|
|
80
|
+
return join(configDir, ".primitive-sync.json");
|
|
81
|
+
}
|
|
82
|
+
function loadSyncState(configDir) {
|
|
83
|
+
const p = syncStatePath(configDir);
|
|
84
|
+
if (!existsSync(p))
|
|
85
|
+
return null;
|
|
86
|
+
try {
|
|
87
|
+
return JSON.parse(readFileSync(p, "utf-8"));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function saveSyncState(configDir, state) {
|
|
94
|
+
writeFileSync(syncStatePath(configDir), JSON.stringify(state, null, 2));
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Update `entities.app.contentHash`, preserving all other entities.
|
|
98
|
+
*
|
|
99
|
+
* On a cross-app stamp (the loaded state belongs to a different app than the
|
|
100
|
+
* one we just pulled from / pushed to) this mirrors `sync push` (see
|
|
101
|
+
* `sync.ts`): it rebinds the sync state to the target app with fresh entities,
|
|
102
|
+
* then records the app-settings hash. Without the rebind the stored
|
|
103
|
+
* `state.appId` would stay pointing at the source app, so every later
|
|
104
|
+
* `settings push <target>` would keep computing `isCrossAppPush` as true — it
|
|
105
|
+
* would ignore the stored hash and keep stripping `[app].name`, making it
|
|
106
|
+
* impossible to rename the target app via `app.toml`.
|
|
107
|
+
*/
|
|
108
|
+
function stampAppContentHash(configDir, appId, contentHash) {
|
|
109
|
+
const now = new Date().toISOString();
|
|
110
|
+
const existing = loadSyncState(configDir);
|
|
111
|
+
const isCrossAppStamp = !!(existing && existing.appId !== appId);
|
|
112
|
+
const state = existing && !isCrossAppStamp
|
|
113
|
+
? existing
|
|
114
|
+
: {
|
|
115
|
+
appId,
|
|
116
|
+
serverUrl: existing?.serverUrl ?? getServerUrl(),
|
|
117
|
+
lastSyncedAt: now,
|
|
118
|
+
entities: {},
|
|
119
|
+
};
|
|
120
|
+
state.entities.app = { modifiedAt: now, contentHash };
|
|
121
|
+
saveSyncState(configDir, state);
|
|
122
|
+
}
|
|
123
|
+
// --- googleClientSecret redaction (settings show) --------------------------
|
|
124
|
+
/** Placeholder shown in `settings show` in place of any secret field value. */
|
|
125
|
+
export const REDACTED_PLACEHOLDER = "[redacted — set via: primitive apps update <appId> --google-client-secret]";
|
|
126
|
+
/**
|
|
127
|
+
* Return a copy of the server settings with every secret descriptor field
|
|
128
|
+
* (currently `googleClientSecret`) redacted when a secret is stored. Used for
|
|
129
|
+
* BOTH the rendered and `--json` outputs of `settings show` so the real secret
|
|
130
|
+
* never leaves the server through this command (codex design-review concern #2).
|
|
131
|
+
*
|
|
132
|
+
* A secret counts as stored when either:
|
|
133
|
+
* - the server sent a truthy raw value — a server older than #1603, which
|
|
134
|
+
* still returns `googleClientSecret` on GET; or
|
|
135
|
+
* - the companion `<field>Set` boolean is true — #1603 made the secret
|
|
136
|
+
* write-only, so `GET /settings` omits the raw value entirely and reports
|
|
137
|
+
* `googleClientSecretSet` instead. Without this branch a configured secret
|
|
138
|
+
* renders as "(not set)", indistinguishable from an unconfigured one.
|
|
139
|
+
*
|
|
140
|
+
* When nothing is stored the field is left untouched, so it renders as
|
|
141
|
+
* "(not set)" — which is then accurate.
|
|
142
|
+
*/
|
|
143
|
+
export function redactSecretFields(settings) {
|
|
144
|
+
const out = { ...settings };
|
|
145
|
+
for (const d of APP_SETTINGS_DESCRIPTOR) {
|
|
146
|
+
if (!d.secret)
|
|
147
|
+
continue;
|
|
148
|
+
const isSet = !!out[d.field] || out[`${d.field}Set`] === true;
|
|
149
|
+
if (isSet)
|
|
150
|
+
out[d.field] = REDACTED_PLACEHOLDER;
|
|
151
|
+
}
|
|
152
|
+
return out;
|
|
153
|
+
}
|
|
154
|
+
// --- Display helpers -------------------------------------------------------
|
|
155
|
+
function formatValue(value) {
|
|
156
|
+
if (value === undefined)
|
|
157
|
+
return "(not set)";
|
|
158
|
+
if (value === null)
|
|
159
|
+
return "(null)";
|
|
160
|
+
if (Array.isArray(value))
|
|
161
|
+
return value.length === 0 ? "[]" : `[${value.join(", ")}]`;
|
|
162
|
+
if (typeof value === "object")
|
|
163
|
+
return JSON.stringify(value);
|
|
164
|
+
return String(value);
|
|
165
|
+
}
|
|
166
|
+
/** Order-insensitive-free deep equality good enough for settings scalars/arrays. */
|
|
167
|
+
function settingsEqual(a, b) {
|
|
168
|
+
return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
169
|
+
}
|
|
170
|
+
// --- Command orchestrators (used by cli/src/commands/settings.ts) ----------
|
|
171
|
+
/**
|
|
172
|
+
* `settings show` — render the current server-effective settings grouped by
|
|
173
|
+
* `app.toml` section, with `googleClientSecret` redacted. `--json` emits the
|
|
174
|
+
* full (redacted) server response.
|
|
175
|
+
*/
|
|
176
|
+
export async function showAppSettings(client, appId, options = {}) {
|
|
177
|
+
try {
|
|
178
|
+
const settings = await client.getAppSettings(appId);
|
|
179
|
+
const redacted = redactSecretFields(settings ?? {});
|
|
180
|
+
if (options.json) {
|
|
181
|
+
json(redacted);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
for (const section of APP_SETTINGS_SECTIONS) {
|
|
185
|
+
const descriptors = APP_SETTINGS_DESCRIPTOR.filter((d) => d.section === section);
|
|
186
|
+
divider();
|
|
187
|
+
info(`[${section}]`);
|
|
188
|
+
for (const d of descriptors) {
|
|
189
|
+
printResult(` ${d.key}`, formatValue(redacted[d.field]));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
error(err.message);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* `settings pull` — fetch the server settings, write `app.toml`, and stamp the
|
|
200
|
+
* shared `entities.app.contentHash`. Same file and hash as `sync pull`.
|
|
201
|
+
*/
|
|
202
|
+
export async function pullAppSettings(client, appId, configDir, options = {}) {
|
|
203
|
+
try {
|
|
204
|
+
const settings = await client.getAppSettings(appId);
|
|
205
|
+
if (!settings) {
|
|
206
|
+
error(`No settings returned for app ${appId}.`);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
ensureDir(configDir);
|
|
210
|
+
const contentHash = writeAppSettingsToml(configDir, settings);
|
|
211
|
+
stampAppContentHash(configDir, appId, contentHash);
|
|
212
|
+
if (options.json) {
|
|
213
|
+
json({ wrote: true, path: appTomlPath(configDir), contentHash });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
success(`Wrote ${appTomlPath(configDir)}`);
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
error(err.message);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* `settings push` — read `app.toml`, validate (abort before any PUT on a secret
|
|
225
|
+
* key or a type mismatch), skip when unchanged since the last sync unless
|
|
226
|
+
* `--force`, then apply via `PUT /settings` and re-stamp the shared content
|
|
227
|
+
* hash. Cross-app pushes (state's app differs from the target) always apply and
|
|
228
|
+
* strip `name`, matching `sync push`.
|
|
229
|
+
*/
|
|
230
|
+
export async function pushAppSettings(client, appId, configDir, options = {}) {
|
|
231
|
+
try {
|
|
232
|
+
const path = appTomlPath(configDir);
|
|
233
|
+
if (!existsSync(path)) {
|
|
234
|
+
error(`No ${APP_TOML_FILENAME} found at ${path}. Run 'primitive settings pull' first.`);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
const state = loadSyncState(configDir);
|
|
238
|
+
const isCrossAppPush = !!(state && state.appId !== appId);
|
|
239
|
+
let tomlData;
|
|
240
|
+
try {
|
|
241
|
+
tomlData = parseConfigToml(readFileSync(path, "utf-8"));
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
error(`Aborting push: could not parse ${APP_TOML_FILENAME}: ${err?.message || String(err)}`);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
const { settings, warnings, errors } = parseTomlToAppSettings(tomlData, {
|
|
248
|
+
isCrossAppPush,
|
|
249
|
+
});
|
|
250
|
+
// Fail before any mutation on a secret key or a type mismatch.
|
|
251
|
+
if (errors.length > 0) {
|
|
252
|
+
error(`Aborting push: ${errors.length} validation error(s) in ${APP_TOML_FILENAME} — no changes were applied.`);
|
|
253
|
+
for (const e of errors)
|
|
254
|
+
error(` ${e}`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
// Skip when nothing changed since the last sync (shared content hash).
|
|
258
|
+
const storedHash = isCrossAppPush
|
|
259
|
+
? undefined
|
|
260
|
+
: state?.entities?.app?.contentHash;
|
|
261
|
+
const changed = options.force || !storedHash || contentHashOfFile(path) !== storedHash;
|
|
262
|
+
if (!changed) {
|
|
263
|
+
if (options.json) {
|
|
264
|
+
json({ applied: false, skipped: true, reason: "unchanged" });
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
info("Skipped app settings (unchanged). Use --force to push anyway.");
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
for (const w of warnings)
|
|
272
|
+
warn(` ${w}`);
|
|
273
|
+
if (options.dryRun) {
|
|
274
|
+
if (options.json) {
|
|
275
|
+
json({ applied: false, dryRun: true, settings });
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
info("[dry-run] Would update app settings.");
|
|
279
|
+
}
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
await client.updateAppSettings(appId, settings);
|
|
283
|
+
stampAppContentHash(configDir, appId, contentHashOfFile(path));
|
|
284
|
+
if (options.json) {
|
|
285
|
+
json({ applied: true, fields: Object.keys(settings) });
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
success("Updated app settings.");
|
|
289
|
+
}
|
|
290
|
+
catch (err) {
|
|
291
|
+
error(err.message);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* `settings diff` — report per-field differences between the local `app.toml`
|
|
297
|
+
* push payload and the current server settings. Only keys present in the TOML
|
|
298
|
+
* are compared, so a server value that equals a descriptor default (and is
|
|
299
|
+
* therefore omitted from `app.toml`) is never flagged as a spurious difference.
|
|
300
|
+
*/
|
|
301
|
+
export async function diffAppSettings(client, appId, configDir, options = {}) {
|
|
302
|
+
try {
|
|
303
|
+
const path = appTomlPath(configDir);
|
|
304
|
+
if (!existsSync(path)) {
|
|
305
|
+
error(`No ${APP_TOML_FILENAME} found at ${path}. Run 'primitive settings pull' first.`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
// Local-parse-first, mirroring `settings push`: validate the file before
|
|
309
|
+
// touching the server so an unpushable file never issues `GET /settings`.
|
|
310
|
+
let tomlData;
|
|
311
|
+
try {
|
|
312
|
+
tomlData = parseConfigToml(readFileSync(path, "utf-8"));
|
|
313
|
+
}
|
|
314
|
+
catch (err) {
|
|
315
|
+
error(`Aborting diff: could not parse ${APP_TOML_FILENAME}: ${err?.message || String(err)}`);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
// Mirror push's cross-app determination (pushAppSettings above): when the
|
|
319
|
+
// sync state belongs to a different app, the push parse strips `[app].name`
|
|
320
|
+
// before validating, so diff must strip it too — otherwise a malformed
|
|
321
|
+
// local `name` would abort diff on a file push would accept and ignore.
|
|
322
|
+
const state = loadSyncState(configDir);
|
|
323
|
+
const isCrossAppPush = !!(state && state.appId !== appId);
|
|
324
|
+
// Present-only push payload — omitted keys are absent, so they can't be
|
|
325
|
+
// flagged against a server default.
|
|
326
|
+
const { settings: local, errors } = parseTomlToAppSettings(tomlData, {
|
|
327
|
+
isCrossAppPush,
|
|
328
|
+
});
|
|
329
|
+
// Mirror the push preflight (pushAppSettings above): a file with validation
|
|
330
|
+
// errors is unpushable, so diff must not present its surviving values as
|
|
331
|
+
// clean (or as a partial diff) — that would advertise changes push will
|
|
332
|
+
// never apply. `--json` is ignored on this path, exactly as push does.
|
|
333
|
+
if (errors.length > 0) {
|
|
334
|
+
error(`Aborting diff: ${errors.length} validation error(s) in ${APP_TOML_FILENAME} — this file cannot be pushed.`);
|
|
335
|
+
for (const e of errors)
|
|
336
|
+
error(` ${e}`);
|
|
337
|
+
process.exit(1);
|
|
338
|
+
}
|
|
339
|
+
const remote = (await client.getAppSettings(appId)) ?? {};
|
|
340
|
+
const diffs = [];
|
|
341
|
+
for (const d of APP_SETTINGS_DESCRIPTOR) {
|
|
342
|
+
if (d.secret)
|
|
343
|
+
continue;
|
|
344
|
+
if (!(d.field in local))
|
|
345
|
+
continue;
|
|
346
|
+
const localVal = local[d.field];
|
|
347
|
+
const remoteVal = remote[d.field];
|
|
348
|
+
if (!settingsEqual(localVal, remoteVal)) {
|
|
349
|
+
diffs.push({
|
|
350
|
+
field: d.field,
|
|
351
|
+
section: d.section,
|
|
352
|
+
key: d.key,
|
|
353
|
+
local: localVal,
|
|
354
|
+
remote: remoteVal,
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (options.json) {
|
|
359
|
+
json({ diffs });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (diffs.length === 0) {
|
|
363
|
+
success(`No differences — ${APP_TOML_FILENAME} matches server settings.`);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
info(`${diffs.length} field(s) differ between ${APP_TOML_FILENAME} and the server:`);
|
|
367
|
+
for (const df of diffs) {
|
|
368
|
+
divider();
|
|
369
|
+
info(`[${df.section}].${df.key}`);
|
|
370
|
+
printResult(" local", formatValue(df.local));
|
|
371
|
+
printResult(" remote", formatValue(df.remote));
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch (err) {
|
|
375
|
+
error(err.message);
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
//# sourceMappingURL=sync-app-settings.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-app-settings.js","sourceRoot":"","sources":["../../../src/commands/sync-app-settings.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { ApiClient } from "../lib/api-client.js";
|
|
3
|
+
import { type PresenceOutcome } from "../lib/sync-resource-types.js";
|
|
3
4
|
/**
|
|
4
5
|
* Wrap a server-side error so the printed message identifies which entity
|
|
5
6
|
* was in flight. Used by every entity create/update/delete call site in the
|
|
@@ -66,32 +67,28 @@ export declare function hashWorkflowTomlForDiff(parsed: any): string;
|
|
|
66
67
|
* through `hashWorkflowTomlForDiff` so it lines up with the local-file hash.
|
|
67
68
|
*/
|
|
68
69
|
export declare function hashRemoteWorkflowForDiff(workflow: any, draft: any, configs: any[]): string;
|
|
70
|
+
export interface ParsedAuthSettings {
|
|
71
|
+
settings: Record<string, any>;
|
|
72
|
+
warnings: string[];
|
|
73
|
+
}
|
|
69
74
|
/**
|
|
70
75
|
* Build the [auth] block of app.toml from server settings (pull direction).
|
|
71
76
|
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* an empty list on pull is not lossy.
|
|
77
|
+
* Thin wrapper over the shared field descriptor
|
|
78
|
+
* (`cli/src/lib/app-settings-descriptor.ts`), which now owns the [auth] field
|
|
79
|
+
* set that `AUTH_BOOLEAN_KEYS`/`RECOGNIZED_AUTH_KEYS` used to hard-code. The
|
|
80
|
+
* descriptor drives all four sections in one place, so pull, push, and the
|
|
81
|
+
* unrecognized-key warning can never drift apart. `googleClientSecret` is a
|
|
82
|
+
* secret descriptor entry and is never written.
|
|
79
83
|
*/
|
|
80
84
|
export declare function serializeAuthBlock(settings: any): Record<string, any>;
|
|
81
|
-
export interface ParsedAuthSettings {
|
|
82
|
-
settings: Record<string, any>;
|
|
83
|
-
warnings: string[];
|
|
84
|
-
}
|
|
85
85
|
/**
|
|
86
86
|
* Translate the [auth] block of app.toml into app-settings fields (push
|
|
87
|
-
* direction).
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
* `appleAudiences = []` is forwarded too; the server normalizes [] -> null
|
|
93
|
-
* ("no audiences"). Keys outside RECOGNIZED_AUTH_KEYS produce a warning rather
|
|
94
|
-
* than a hard error, so a typo or a future key doesn't silently drop.
|
|
87
|
+
* direction). Kept as a focused helper over the shared descriptor; the full
|
|
88
|
+
* push path uses `parseTomlToAppSettings` for every section. Only keys present
|
|
89
|
+
* in the TOML are forwarded, so an omitted key never overwrites server state.
|
|
90
|
+
* Descriptor errors (a secret key, a type mismatch) are surfaced alongside the
|
|
91
|
+
* unrecognized-key warnings.
|
|
95
92
|
*/
|
|
96
93
|
export declare function parseAppAuthSettings(auth: Record<string, any>): ParsedAuthSettings;
|
|
97
94
|
type VarEntity = {
|
|
@@ -249,14 +246,66 @@ export declare function rewriteWorkflowSchemasToNative(rawToml: string, workflow
|
|
|
249
246
|
changed: boolean;
|
|
250
247
|
};
|
|
251
248
|
export declare function serializeWorkflow(workflow: any, draft: any, configs: any[], logger?: (message: string) => void): string;
|
|
249
|
+
export declare function serializeRuleSet(ruleSet: any): string;
|
|
252
250
|
export declare function serializeGroupTypeConfig(config: any, ruleSetIdToName: Map<string, string>): string;
|
|
251
|
+
/**
|
|
252
|
+
* Issue #1567 — the TOML-owned scalar fields of a database-type config.
|
|
253
|
+
*
|
|
254
|
+
* These are fields the `database-types/*.toml` file OWNS: the local file is the
|
|
255
|
+
* source of truth, so removing one from the TOML must clear it server-side
|
|
256
|
+
* (config-as-code), not silently preserve the stale value. `schema` is NOT in
|
|
257
|
+
* this list — it keeps its own `hasSchema` prior-state discriminator, since a
|
|
258
|
+
* schema is a large sub-tree, not a scalar.
|
|
259
|
+
*/
|
|
260
|
+
export declare const DB_TYPE_OWNED_SCALARS: readonly ["ruleSetId", "triggers", "metadataAccess", "defaultAccess", "autoPopulatedFields", "timestamps", "metadataManifest"];
|
|
261
|
+
/** The user-facing TOML key for a wire field (for push output). */
|
|
262
|
+
export declare function dbTypeFieldLabel(key: string): string;
|
|
263
|
+
/**
|
|
264
|
+
* True when a value carries real content. null/undefined, blank/whitespace
|
|
265
|
+
* strings, and empty objects/arrays all count as "no value" — the same
|
|
266
|
+
* normalization the server applies (`value || null`) when it decides whether a
|
|
267
|
+
* field is set.
|
|
268
|
+
*/
|
|
269
|
+
export declare function hasMeaningfulValue(v: any): boolean;
|
|
270
|
+
/**
|
|
271
|
+
* Issue #1567 — always-send owned db-type scalars as value-or-`null`.
|
|
272
|
+
*
|
|
273
|
+
* The server PATCH endpoint distinguishes an omitted key (preserve) from an
|
|
274
|
+
* explicit `null` (clear), so sending every owned scalar as `value ?? null`
|
|
275
|
+
* converges the server to the local TOML: a field removed from the file lands
|
|
276
|
+
* as `null` and is cleared. Matches the always-send pattern the sibling
|
|
277
|
+
* `collection-type-config` push already uses.
|
|
278
|
+
*/
|
|
279
|
+
export declare function buildOwnedScalarUpdate(typeConfig: any): Record<string, any>;
|
|
280
|
+
/**
|
|
281
|
+
* Issue #1567 — the owned scalars this push genuinely clears: the outgoing
|
|
282
|
+
* value is empty but the server currently holds a real value (a non-null →
|
|
283
|
+
* null transition). Used for the "Cleared <field>" report so removing an
|
|
284
|
+
* access gate is never silent, and to decide whether an otherwise
|
|
285
|
+
* type-field-less push still needs a type-config PATCH. Returns wire field
|
|
286
|
+
* names (map with `dbTypeFieldLabel` for output). `serverConfig` is the live
|
|
287
|
+
* GET; without it (fresh type or a failed fetch) nothing is reported cleared.
|
|
288
|
+
*/
|
|
289
|
+
export declare function ownedScalarsBeingCleared(updateData: Record<string, any>, serverConfig: any): string[];
|
|
253
290
|
export declare function parseDatabaseTypeToml(tomlData: any): {
|
|
254
291
|
typeConfig: any;
|
|
255
292
|
operations: any[];
|
|
256
293
|
subscriptions: any[];
|
|
257
294
|
};
|
|
295
|
+
export declare function parseRuleSetToml(tomlData: any): any;
|
|
258
296
|
export declare function parseGroupTypeConfigToml(tomlData: any): any;
|
|
259
297
|
export declare function parseCollectionTypeConfigToml(tomlData: any): any;
|
|
298
|
+
export declare function hashLocalRuleSetForDiff(parsedToml: any): string;
|
|
299
|
+
export declare function hashRemoteRuleSetForDiff(ruleSet: any): string;
|
|
300
|
+
export declare function hashLocalGroupTypeConfigForDiff(parsedToml: any, ruleSetNameToId: Map<string, string>): string;
|
|
301
|
+
export declare function hashRemoteGroupTypeConfigForDiff(config: any, ruleSetIdToName: Map<string, string>, ruleSetNameToId: Map<string, string>): string;
|
|
302
|
+
export declare function hashLocalCollectionTypeConfigForDiff(parsedToml: any, ruleSetNameToId: Map<string, string>): string;
|
|
303
|
+
export declare function hashRemoteCollectionTypeConfigForDiff(config: any, ruleSetIdToName: Map<string, string>, ruleSetNameToId: Map<string, string>): string;
|
|
304
|
+
export declare function hashLocalDatabaseTypeForDiff(parsedToml: any, ruleSetNameToId: Map<string, string>): string;
|
|
305
|
+
export declare function hashRemoteDatabaseTypeForDiff(typeConfig: any, operations: any[], subscriptions: any[], ruleSetIdToName: Map<string, string>, ruleSetNameToId: Map<string, string>): string;
|
|
306
|
+
export declare function hashLocalMetadataCategoryForDiff(parsedToml: any): string;
|
|
307
|
+
export declare function hashRemoteMetadataCategoryForDiff(config: any): string;
|
|
308
|
+
export declare function parseTomlFile(filePath: string): any;
|
|
260
309
|
/**
|
|
261
310
|
* Paginate through a list endpoint, collecting all items.
|
|
262
311
|
*/
|
|
@@ -267,6 +316,54 @@ export declare function fetchAll<T>(listFn: (params: {
|
|
|
267
316
|
items: T[];
|
|
268
317
|
nextCursor?: string | null;
|
|
269
318
|
}>, pageSize?: number, maxPages?: number): Promise<T[]>;
|
|
319
|
+
/**
|
|
320
|
+
* Issue #976 / #1006: shared 409 → adopt-by-key recovery for `sync push`
|
|
321
|
+
* create paths. When a CREATE hits a per-app unique-key constraint, the resource
|
|
322
|
+
* is already on the server but missing from local sync state: orphaned by a
|
|
323
|
+
* prior push that aborted before recording it, a mid-apply crash, or an
|
|
324
|
+
* out-of-band create with the same key. Rather than hard-fail forever, this
|
|
325
|
+
* helper looks the resource up by its unique key and hands the matched item
|
|
326
|
+
* back so the caller can re-issue the create as an UPDATE and converge.
|
|
327
|
+
*
|
|
328
|
+
* The conflict surfaces inconsistently across create routes, so detection is
|
|
329
|
+
* deliberately broad (see `isConflict` below):
|
|
330
|
+
* - a 409 status (webhooks, cron triggers, the standard case);
|
|
331
|
+
* - an "already exists" message — some routes surface the 409 as HTTP 400,
|
|
332
|
+
* e.g. workflows;
|
|
333
|
+
* - a "must be unique" message — routes with no explicit conflict catch let
|
|
334
|
+
* dynamo-bao's per-app unique-constraint error propagate verbatim (e.g. rule
|
|
335
|
+
* sets, whose create route has no 409 translation). This matches the
|
|
336
|
+
* server's own admin-api convention (`src/admin-api.ts`), which treats
|
|
337
|
+
* "must be unique" as a duplicate-key conflict.
|
|
338
|
+
* Any other error is NOT a conflict and re-throws unchanged (see below).
|
|
339
|
+
*
|
|
340
|
+
* Safety (load-bearing): exact-key match only. The `lookup` is app-scoped, so a
|
|
341
|
+
* key match is also an ownership match — an unrelated resource is never
|
|
342
|
+
* overwritten. The three non-adopt outcomes each surface a clear error and do
|
|
343
|
+
* NOT touch the server:
|
|
344
|
+
* - a non-409 create error is re-wrapped via `wrapEntityError` and re-thrown
|
|
345
|
+
* (no lookup is attempted);
|
|
346
|
+
* - a lookup failure throws a clear "already exists but could not be adopted
|
|
347
|
+
* (lookup failed: …)";
|
|
348
|
+
* - a 409 with no matching key re-throws the ORIGINAL create error rather
|
|
349
|
+
* than silently proceeding.
|
|
350
|
+
*
|
|
351
|
+
* Pagination is the caller's concern: `lookup` must return the COMPLETE
|
|
352
|
+
* candidate set. For cursor-based lists (webhooks, integrations, prompts,
|
|
353
|
+
* workflows) wrap the list call in `fetchAll`; the cron-triggers route already
|
|
354
|
+
* drains every page server-side (#1668) so its flat `{ items }` is complete.
|
|
355
|
+
* The UPDATE re-issue and sync-state stamping stay with the caller because they
|
|
356
|
+
* differ per entity.
|
|
357
|
+
*
|
|
358
|
+
* @returns the matched existing item (guaranteed non-null on return).
|
|
359
|
+
*/
|
|
360
|
+
export declare function adoptByKeyOnCreate409<T>(opts: {
|
|
361
|
+
err: unknown;
|
|
362
|
+
kind: string;
|
|
363
|
+
key: string;
|
|
364
|
+
lookup: () => Promise<T[]>;
|
|
365
|
+
matchKey: (item: T) => boolean;
|
|
366
|
+
}): Promise<T>;
|
|
270
367
|
/**
|
|
271
368
|
* Resolve a key-based rule set name reference to an ID.
|
|
272
369
|
* Throws if the name cannot be resolved and throwOnMissing is true.
|
|
@@ -320,6 +417,24 @@ export declare function pullScripts(client: ApiClient, appId: string, configDir:
|
|
|
320
417
|
contentHash?: string;
|
|
321
418
|
}>;
|
|
322
419
|
count: number;
|
|
420
|
+
/**
|
|
421
|
+
* Which scripts the server listed, for the #1659 prune pass. `ok: false` when
|
|
422
|
+
* the listing failed (an older server without the route, or a transient
|
|
423
|
+
* error) or when any per-script fetch failed — in either case this pull's
|
|
424
|
+
* picture of the type is incomplete and must not drive deletions. Scripts
|
|
425
|
+
* skipped for having no active config are still *present*, so their keys stay
|
|
426
|
+
* in the set and their local `.rhai` survives.
|
|
427
|
+
*/
|
|
428
|
+
presence: PresenceOutcome;
|
|
429
|
+
/**
|
|
430
|
+
* Whether the script LISTING succeeded. Distinct from `presence`: a failed
|
|
431
|
+
* per-script fetch blocks pruning but still leaves the other scripts written,
|
|
432
|
+
* so their fresh state entries must be kept. Only a failed listing means
|
|
433
|
+
* nothing was written and the prior state slot should be preserved whole.
|
|
434
|
+
*/
|
|
435
|
+
listOk: boolean;
|
|
436
|
+
/** Names the server listed, empty when the listing failed. */
|
|
437
|
+
serverKeys: string[];
|
|
323
438
|
}>;
|
|
324
439
|
export declare function registerSyncCommands(program: Command): void;
|
|
325
440
|
export {};
|