@rebasepro/server-postgres 0.17.3-canary.gdd23447 → 0.18.0
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/LICENSE +0 -1
- package/README.md +31 -0
- package/dist/backup-argv.d.ts +19 -0
- package/dist/branch-argv.d.ts +28 -0
- package/dist/branch-prune.d.ts +77 -0
- package/dist/cli-collections-path.d.ts +31 -0
- package/dist/cli-errors.d.ts +123 -0
- package/dist/cli-flags.d.ts +101 -0
- package/dist/cli-helpers.d.ts +94 -10
- package/dist/collection-index-C7LopMyX.js +247 -0
- package/dist/collection-index-C7LopMyX.js.map +1 -0
- package/dist/databasePoolManager.d.ts +27 -0
- package/dist/{ensure-collection-policies-UCqgv_8c.js → ensure-collection-policies-CagOzEPS.js} +6 -6
- package/dist/{ensure-collection-policies-UCqgv_8c.js.map → ensure-collection-policies-CagOzEPS.js.map} +1 -1
- package/dist/{ensure-collection-tables-DgVixhX3.js → ensure-collection-tables-DbAhrjnG.js} +614 -52
- package/dist/ensure-collection-tables-DbAhrjnG.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.es.js +19307 -3059
- package/dist/index.es.js.map +1 -1
- package/dist/{rls-enforcement-DvAbL9YJ.js → rls-enforcement-ClJoTboT.js} +53 -189
- package/dist/rls-enforcement-ClJoTboT.js.map +1 -0
- package/dist/schema/column-type-drift.d.ts +79 -0
- package/dist/schema/doctor.d.ts +42 -0
- package/dist/schema/ensure-collection-tables.d.ts +90 -0
- package/dist/schema/generate-next-step.d.ts +30 -0
- package/dist/schema/generate-postgres-ddl-logic.d.ts +28 -0
- package/dist/schema/generated-schema-staleness.d.ts +37 -0
- package/dist/schema/introspect-db-queries.d.ts +34 -0
- package/dist/security/rls-enforcement.d.ts +63 -2
- package/dist/services/BranchService.d.ts +86 -1
- package/dist/services/collection-helpers.d.ts +1 -1
- package/dist/services/realtimeService.d.ts +30 -2
- package/dist/services/row-pipeline.d.ts +12 -1
- package/dist/utils/pg-error-utils.d.ts +3 -1
- package/dist/utils/pg-numeric-number-patch.d.ts +31 -0
- package/package.json +32 -27
- package/dist/backup-service-DCk7KhhL.js +0 -9044
- package/dist/backup-service-DCk7KhhL.js.map +0 -1
- package/dist/collection-index-BRUg10H5.js +0 -5690
- package/dist/collection-index-BRUg10H5.js.map +0 -1
- package/dist/connection-GOKU3Hu5.js +0 -277
- package/dist/connection-GOKU3Hu5.js.map +0 -1
- package/dist/ensure-collection-tables-DgVixhX3.js.map +0 -1
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js +0 -244
- package/dist/rls-bootstrap-sql-B5C9LoJ6.js.map +0 -1
- package/dist/rls-enforcement-DvAbL9YJ.js.map +0 -1
- package/dist/src-DiB5RP2Z.js +0 -541
- package/dist/src-DiB5RP2Z.js.map +0 -1
- package/dist/websocket-BZ4H5wUz.js +0 -8198
- package/dist/websocket-BZ4H5wUz.js.map +0 -1
- package/src/PostgresAdapter.ts +0 -91
- package/src/PostgresBackendDriver.ts +0 -1857
- package/src/PostgresBootstrapper.ts +0 -1157
- package/src/auth/ensure-tables.ts +0 -896
- package/src/auth/schema-version.ts +0 -260
- package/src/auth/services.ts +0 -1610
- package/src/backup/backup-cli.ts +0 -520
- package/src/backup/backup-cron.ts +0 -212
- package/src/backup/backup-logic.ts +0 -93
- package/src/backup/backup-service.ts +0 -485
- package/src/backup/index.ts +0 -12
- package/src/backup/pg-tools.ts +0 -403
- package/src/backup/retention.ts +0 -86
- package/src/cli-errors.ts +0 -265
- package/src/cli-helpers.ts +0 -569
- package/src/cli-output.ts +0 -43
- package/src/cli.ts +0 -1339
- package/src/collections/PostgresCollectionRegistry.ts +0 -108
- package/src/collections/buildRegistry.ts +0 -68
- package/src/collections/validate-relations.ts +0 -402
- package/src/connection.ts +0 -333
- package/src/data-transformer.ts +0 -845
- package/src/databasePoolManager.ts +0 -95
- package/src/history/HistoryService.ts +0 -254
- package/src/history/ensure-history-table.ts +0 -53
- package/src/index.ts +0 -19
- package/src/interfaces.ts +0 -60
- package/src/module-dir.ts +0 -7
- package/src/schema/atlas-argv.ts +0 -94
- package/src/schema/auth-schema.ts +0 -298
- package/src/schema/auth-users-columns.ts +0 -131
- package/src/schema/carved-out-migration.ts +0 -404
- package/src/schema/classify-change.ts +0 -436
- package/src/schema/collection-index.ts +0 -427
- package/src/schema/collections-schema-version.ts +0 -103
- package/src/schema/destructive-sql.ts +0 -94
- package/src/schema/doctor-cli.ts +0 -66
- package/src/schema/doctor-policy-checks.ts +0 -105
- package/src/schema/doctor.ts +0 -837
- package/src/schema/drizzle-ddl.ts +0 -33
- package/src/schema/dynamic-tables.test.ts +0 -302
- package/src/schema/dynamic-tables.ts +0 -293
- package/src/schema/ensure-collection-policies.ts +0 -198
- package/src/schema/ensure-collection-tables.test.ts +0 -518
- package/src/schema/ensure-collection-tables.ts +0 -1267
- package/src/schema/generate-drizzle-schema-logic.ts +0 -957
- package/src/schema/generate-drizzle-schema.ts +0 -143
- package/src/schema/generate-postgres-ddl-logic.ts +0 -1339
- package/src/schema/generate-postgres-ddl.ts +0 -180
- package/src/schema/generate-schema-commit.ts +0 -267
- package/src/schema/generated-schema-staleness.ts +0 -284
- package/src/schema/introspect-db-constraints.ts +0 -385
- package/src/schema/introspect-db-inference.ts +0 -255
- package/src/schema/introspect-db-logic.ts +0 -1387
- package/src/schema/introspect-db-naming.ts +0 -15
- package/src/schema/introspect-db-project.ts +0 -78
- package/src/schema/introspect-db-queries.ts +0 -326
- package/src/schema/introspect-db-structure.ts +0 -670
- package/src/schema/introspect-db-types.ts +0 -56
- package/src/schema/introspect-db.ts +0 -257
- package/src/schema/introspect-runtime.test.ts +0 -260
- package/src/schema/introspect-runtime.ts +0 -327
- package/src/schema/non-sql-collections.test.ts +0 -131
- package/src/schema/rls-bootstrap-sql.ts +0 -288
- package/src/schema/search-column.ts +0 -643
- package/src/schema/test-schema.ts +0 -11
- package/src/schema/vector-index.ts +0 -491
- package/src/security/anonymous-grants.test.ts +0 -73
- package/src/security/policy-drift.test.ts +0 -435
- package/src/security/policy-drift.ts +0 -473
- package/src/security/rls-enforcement.ts +0 -505
- package/src/services/BranchService.ts +0 -326
- package/src/services/FetchService.ts +0 -2115
- package/src/services/PersistService.ts +0 -557
- package/src/services/RelationService.ts +0 -978
- package/src/services/RelationWriteService.ts +0 -653
- package/src/services/cdc/CdcListener.ts +0 -103
- package/src/services/cdc/junction-tables.ts +0 -91
- package/src/services/cdc/trigger-cdc.ts +0 -173
- package/src/services/channel-bus/ChannelBus.ts +0 -44
- package/src/services/channel-bus/PostgresChannelBus.ts +0 -299
- package/src/services/channel-bus/index.ts +0 -114
- package/src/services/channel-history.ts +0 -424
- package/src/services/channel-presence.ts +0 -185
- package/src/services/collection-helpers.ts +0 -420
- package/src/services/dataService.ts +0 -256
- package/src/services/index.ts +0 -14
- package/src/services/junction-writes.ts +0 -295
- package/src/services/nested-path.ts +0 -145
- package/src/services/pg-notify-listener.ts +0 -151
- package/src/services/realtimeService.ts +0 -2545
- package/src/services/row-pipeline.ts +0 -240
- package/src/services/write-denial.ts +0 -55
- package/src/types.ts +0 -4
- package/src/utils/connection-string.ts +0 -58
- package/src/utils/drizzle-conditions.ts +0 -2743
- package/src/utils/pg-array-null-patch.ts +0 -42
- package/src/utils/pg-error-utils.ts +0 -391
- package/src/websocket.ts +0 -855
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import { humanize } from "./introspect-db-naming";
|
|
2
|
-
|
|
3
|
-
export interface InferenceResult {
|
|
4
|
-
propType?: string; // If the inference changes the base type
|
|
5
|
-
extra?: string; // String to append to the property definition
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
const ISO_8601_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
9
|
-
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
10
|
-
const CUID_REGEX = /^c[^\s-]{7,}$/i; // Basic CUID check
|
|
11
|
-
const COLOR_HEX_REGEX = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/;
|
|
12
|
-
|
|
13
|
-
export function inferPropertyFromData(
|
|
14
|
-
columnName: string,
|
|
15
|
-
pgDataType: string,
|
|
16
|
-
currentPropType: string,
|
|
17
|
-
sampleValues: unknown[],
|
|
18
|
-
isPk: boolean,
|
|
19
|
-
/**
|
|
20
|
-
* False when generating for a project without `@rebasepro/cms-types`, where
|
|
21
|
-
* `BaseProperty` declares no `admin` field and the block below would not
|
|
22
|
-
* compile. The type-level inferences (`propType`, `url`, `storage`) are
|
|
23
|
-
* unaffected — only the form-widget hints are dropped.
|
|
24
|
-
*/
|
|
25
|
-
emitAdmin = true
|
|
26
|
-
): InferenceResult {
|
|
27
|
-
const result: InferenceResult = {};
|
|
28
|
-
const extraLines: string[] = [];
|
|
29
|
-
// Collected separately and emitted as one block at the end. Pushed inline,
|
|
30
|
-
// two admin-owned options from different branches produced two `admin: {`
|
|
31
|
-
// blocks in one property — a duplicate key, which is also a compile error.
|
|
32
|
-
const adminOptions: string[] = [];
|
|
33
|
-
|
|
34
|
-
// Filter out null/undefined for analysis
|
|
35
|
-
const validValues = sampleValues.filter(v => v !== null && v !== undefined && v !== "");
|
|
36
|
-
|
|
37
|
-
if (validValues.length === 0) {
|
|
38
|
-
return result; // Not enough data
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const colNameLower = columnName.toLowerCase();
|
|
42
|
-
|
|
43
|
-
// ── Number Analysis ──────────────────────────────────────────────────
|
|
44
|
-
if (currentPropType === "number") {
|
|
45
|
-
// Boolean stored as int
|
|
46
|
-
const allZeroOrOne = validValues.every(v => v === 0 || v === 1 || v === "0" || v === "1");
|
|
47
|
-
if (allZeroOrOne) {
|
|
48
|
-
result.propType = "boolean";
|
|
49
|
-
return result; // Don't do min/max if boolean
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// Min / Max constraints
|
|
53
|
-
const numValues = validValues.map(v => Number(v)).filter(v => !isNaN(v));
|
|
54
|
-
if (numValues.length === validValues.length) {
|
|
55
|
-
const min = Math.min(...numValues);
|
|
56
|
-
const max = Math.max(...numValues);
|
|
57
|
-
// Example heuristic: percentages
|
|
58
|
-
if (min >= 0 && max <= 100 && (colNameLower.includes("percent") || colNameLower.includes("rate") || colNameLower.includes("score"))) {
|
|
59
|
-
extraLines.push(" validation: {\n min: 0,\n max: 100\n }");
|
|
60
|
-
} else if (min >= 0 && (colNameLower.includes("count") || colNameLower.includes("total") || colNameLower.includes("amount"))) {
|
|
61
|
-
extraLines.push(" validation: {\n min: 0\n }");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
// No currency heuristic. `admin.currency` was never a declared option and
|
|
66
|
-
// nothing reads it, so a column called `price` got a block of config that did
|
|
67
|
-
// nothing — invisible because this generator emits *source text*, which no
|
|
68
|
-
// typechecker ever saw. Reinstate it by adding `currency` to
|
|
69
|
-
// AdminNumberOptions first, and something that renders it.
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// ── JSON / JSONB Analysis ────────────────────────────────────────────
|
|
73
|
-
if (currentPropType === "map" || pgDataType.includes("json")) {
|
|
74
|
-
// PostGres jsonb can be object or array
|
|
75
|
-
let allArrays = true;
|
|
76
|
-
let allObjects = true;
|
|
77
|
-
|
|
78
|
-
for (const v of validValues) {
|
|
79
|
-
let parsed = v;
|
|
80
|
-
if (typeof v === "string") {
|
|
81
|
-
try { parsed = JSON.parse(v); } catch (e) { allArrays = false; allObjects = false; break; }
|
|
82
|
-
}
|
|
83
|
-
if (Array.isArray(parsed)) {
|
|
84
|
-
allObjects = false;
|
|
85
|
-
} else if (typeof parsed === "object" && parsed !== null) {
|
|
86
|
-
allArrays = false;
|
|
87
|
-
} else {
|
|
88
|
-
allArrays = false;
|
|
89
|
-
allObjects = false;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
if (allArrays && !allObjects) {
|
|
94
|
-
result.propType = "array";
|
|
95
|
-
// Infer inner type
|
|
96
|
-
let allNumbers = true;
|
|
97
|
-
let allStrings = true;
|
|
98
|
-
for (const v of validValues) {
|
|
99
|
-
const parsed = typeof v === "string" ? JSON.parse(v) : v;
|
|
100
|
-
for (const item of parsed) {
|
|
101
|
-
if (typeof item !== "number") allNumbers = false;
|
|
102
|
-
if (typeof item !== "string") allStrings = false;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
const innerType = allNumbers ? "number" : allStrings ? "string" : "map";
|
|
106
|
-
extraLines.push(` of: { name: "${humanize(columnName)} Item", type: "${innerType}" }`);
|
|
107
|
-
} else {
|
|
108
|
-
result.propType = "map";
|
|
109
|
-
|
|
110
|
-
// Infer inner schema
|
|
111
|
-
if (allObjects && validValues.length > 0) {
|
|
112
|
-
const schema: Record<string, string> = {};
|
|
113
|
-
for (const v of validValues) {
|
|
114
|
-
const parsed = typeof v === "string" ? JSON.parse(v) : v;
|
|
115
|
-
for (const [k, val] of Object.entries(parsed)) {
|
|
116
|
-
if (val === null || val === undefined) continue;
|
|
117
|
-
const type = typeof val;
|
|
118
|
-
if (type === "string" || type === "number" || type === "boolean") {
|
|
119
|
-
if (!schema[k]) schema[k] = type;
|
|
120
|
-
else if (schema[k] !== type) schema[k] = "mixed";
|
|
121
|
-
} else if (Array.isArray(val)) {
|
|
122
|
-
if (!schema[k]) schema[k] = "array";
|
|
123
|
-
else if (schema[k] !== "array") schema[k] = "mixed";
|
|
124
|
-
} else if (type === "object") {
|
|
125
|
-
if (!schema[k]) schema[k] = "map";
|
|
126
|
-
else if (schema[k] !== "map") schema[k] = "mixed";
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
const keys = Object.keys(schema).filter(k => schema[k] !== "mixed");
|
|
132
|
-
if (keys.length > 0) {
|
|
133
|
-
const props = keys.map(k => {
|
|
134
|
-
return `\n ${k}: { name: "${humanize(k)}", type: "${schema[k]}" }`;
|
|
135
|
-
}).join(",");
|
|
136
|
-
extraLines.push(` properties: {${props}\n }`);
|
|
137
|
-
} else {
|
|
138
|
-
extraLines.push(" keyValue: true");
|
|
139
|
-
}
|
|
140
|
-
} else {
|
|
141
|
-
extraLines.push(" keyValue: true");
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// ── String Analysis ──────────────────────────────────────────────────
|
|
147
|
-
if (currentPropType === "string") {
|
|
148
|
-
// Date/Time Strings
|
|
149
|
-
if (validValues.every(v => typeof v === "string" && ISO_8601_REGEX.test(v))) {
|
|
150
|
-
result.propType = "date";
|
|
151
|
-
return result;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Implicit Enums
|
|
155
|
-
const uniqueValues = new Set(validValues);
|
|
156
|
-
// Determine the maximum length among unique values
|
|
157
|
-
let maxEnumLength = 0;
|
|
158
|
-
for (const v of uniqueValues) {
|
|
159
|
-
if (typeof v === "string" && v.length > maxEnumLength) {
|
|
160
|
-
maxEnumLength = v.length;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Ensure no empty string, max length makes sense, and fewer unique values than total values (unless small total)
|
|
165
|
-
if (uniqueValues.size > 0 && uniqueValues.size <= 5 && maxEnumLength <= 50 && validValues.length > uniqueValues.size && !uniqueValues.has("")) {
|
|
166
|
-
const isLikelyId = isPk || colNameLower.endsWith("_id");
|
|
167
|
-
if (!isLikelyId) {
|
|
168
|
-
const enumEntries = Array.from(uniqueValues).map(v => `{ id: ${JSON.stringify(v)}, label: ${JSON.stringify(humanize(v as string))} }`).join(", ");
|
|
169
|
-
extraLines.push(` enum: [${enumEntries}]`);
|
|
170
|
-
result.extra = extraLines.length > 0 ? "\n" + extraLines.join(",\n") + "," : "";
|
|
171
|
-
return result; // Skip other string checks if it's an enum
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// UUID / CUID Detection
|
|
176
|
-
const allUuid = validValues.every(v => typeof v === "string" && UUID_REGEX.test(v));
|
|
177
|
-
const allCuid = validValues.every(v => typeof v === "string" && CUID_REGEX.test(v));
|
|
178
|
-
if (allUuid) {
|
|
179
|
-
if (isPk) extraLines.push(" isId: \"uuid\"");
|
|
180
|
-
} else if (allCuid) {
|
|
181
|
-
if (isPk) extraLines.push(" isId: \"cuid\"");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// No colour heuristic. `admin.color` was never a declared option and nothing
|
|
185
|
-
// reads it — the same dead branch `currency` was. This generator emits *source
|
|
186
|
-
// text*, so no typechecker ever saw either. Reinstate by adding the option and
|
|
187
|
-
// something that renders it first.
|
|
188
|
-
|
|
189
|
-
// Text Lengths, Multiline & Markdown
|
|
190
|
-
let maxLength = 0;
|
|
191
|
-
let hasNewlines = false;
|
|
192
|
-
let hasMarkdown = false;
|
|
193
|
-
for (const v of validValues) {
|
|
194
|
-
if (typeof v === "string") {
|
|
195
|
-
if (v.length > maxLength) maxLength = v.length;
|
|
196
|
-
if (v.includes("\n")) hasNewlines = true;
|
|
197
|
-
if (/^#{1,6}\s+.+/m.test(v) || /\*\*.+\*\*/.test(v) || /\[.+\]\(.+\)/.test(v) || /^\s*[-*]\s+.+/m.test(v)) {
|
|
198
|
-
hasMarkdown = true;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// `multiline` and `markdown` are *admin* property options, so they have
|
|
204
|
-
// to sit inside the `admin` block. Emitted at the top of the property —
|
|
205
|
-
// where they were — `PostgresCollectionConfig` does not declare them and
|
|
206
|
-
// the generated file does not compile.
|
|
207
|
-
if (hasMarkdown) {
|
|
208
|
-
adminOptions.push("multiline: true", "markdown: true");
|
|
209
|
-
} else if (hasNewlines || maxLength > 100) {
|
|
210
|
-
adminOptions.push("multiline: true");
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (maxLength > 0 && maxLength < 10000) { // arbitrary cap to avoid huge limits
|
|
214
|
-
// Pad the max length slightly for the constraint
|
|
215
|
-
const paddedMax = Math.ceil((maxLength * 1.5) / 10) * 10;
|
|
216
|
-
// Only add length constraint if it seems like a bounded string, not a long text
|
|
217
|
-
if (paddedMax < 255) {
|
|
218
|
-
extraLines.push(` validation: {\n max: ${paddedMax}\n }`);
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Storage & Media (Extracted from old logic)
|
|
223
|
-
const isUrl = colNameLower.endsWith("_url") || colNameLower.endsWith("_uri") || colNameLower.endsWith("_link");
|
|
224
|
-
const isMedia = colNameLower.includes("image") || colNameLower.includes("avatar") || colNameLower.includes("photo") || colNameLower.includes("logo") || colNameLower.includes("cover");
|
|
225
|
-
|
|
226
|
-
const allAbsoluteUrls = validValues.every(v => typeof v === "string" && (v.startsWith("http://") || v.startsWith("https://")));
|
|
227
|
-
if (allAbsoluteUrls) {
|
|
228
|
-
const isImage = validValues.some(v => typeof v === "string" && v.match(/\.(jpeg|jpg|gif|png|webp|svg)/i));
|
|
229
|
-
if (isImage || isMedia) {
|
|
230
|
-
extraLines.push(" url: true");
|
|
231
|
-
adminOptions.push("urlPreview: \"image\"");
|
|
232
|
-
} else {
|
|
233
|
-
extraLines.push(" url: true");
|
|
234
|
-
}
|
|
235
|
-
} else {
|
|
236
|
-
const hasFileExtension = validValues.some(v => typeof v === "string" && v.match(/\.[a-zA-Z0-9]+$/));
|
|
237
|
-
if (hasFileExtension) {
|
|
238
|
-
const firstVal = validValues[0] as string;
|
|
239
|
-
const lastSlash = firstVal.lastIndexOf("/");
|
|
240
|
-
const inferredStoragePath = lastSlash > 0 ? firstVal.substring(0, lastSlash) : "files";
|
|
241
|
-
extraLines.push(` storage: {\n storagePath: "${inferredStoragePath}"\n }`);
|
|
242
|
-
} else if (isUrl) {
|
|
243
|
-
extraLines.push(" url: true");
|
|
244
|
-
if (isMedia) adminOptions.push("urlPreview: \"image\"");
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (emitAdmin && adminOptions.length > 0) {
|
|
250
|
-
extraLines.push(` admin: {\n${adminOptions.map((o) => ` ${o}`).join(",\n")}\n }`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
result.extra = extraLines.length > 0 ? "\n" + extraLines.join(",\n") + "," : "";
|
|
254
|
-
return result;
|
|
255
|
-
}
|