buildx-cli 1.0.10 → 1.0.11
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/README.md +546 -0
- package/dist/index.cjs +14 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +14 -0
- package/dist/package.json +64 -0
- package/package.json +7 -2
- package/.github/workflows/auto-publish.yml +0 -254
- package/.github/workflows/create-pr.yml +0 -182
- package/.prettierrc +0 -8
- package/eslint.config.mjs +0 -115
- package/jest.config.cjs +0 -16
- package/rollup.config.mjs +0 -64
- package/scripts/prepare-publish.js +0 -12
- package/src/__tests__/config.test.ts +0 -102
- package/src/__tests__/schema-types-convert.test.ts +0 -147
- package/src/commands/auth/login.ts +0 -148
- package/src/commands/auth/logout.ts +0 -16
- package/src/commands/auth/status.ts +0 -52
- package/src/commands/config/clear.ts +0 -16
- package/src/commands/config/index.ts +0 -14
- package/src/commands/config/setup.ts +0 -108
- package/src/commands/config/show.ts +0 -96
- package/src/commands/functions.ts +0 -703
- package/src/commands/projects/current.ts +0 -36
- package/src/commands/projects/list.ts +0 -61
- package/src/commands/projects/set-default.ts +0 -59
- package/src/commands/sync.ts +0 -778
- package/src/config/index.ts +0 -169
- package/src/index.ts +0 -62
- package/src/services/api.ts +0 -198
- package/src/services/schema-generator.ts +0 -132
- package/src/services/schema-types-convert.ts +0 -361
- package/src/types/index.ts +0 -91
- package/src/utils/env.ts +0 -117
- package/src/utils/logger.ts +0 -29
- package/src/utils/sync.ts +0 -70
- package/test.env +0 -2
- package/tsconfig.json +0 -29
package/src/commands/sync.ts
DELETED
|
@@ -1,778 +0,0 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import chalk from "chalk";
|
|
3
|
-
import ora from "ora";
|
|
4
|
-
import fs from "fs-extra";
|
|
5
|
-
import path from "path";
|
|
6
|
-
import { configManager } from "../config/index";
|
|
7
|
-
import { apiService } from "../services/api";
|
|
8
|
-
import { buildCollectionsFromTypes, convertTypeFieldToSchemaField, mapScalarTypeToFieldType, mergeCollectionWithBase, mergeSchemaField, parseCollectionIdEnum, resolveCollectionId } from "../services/schema-types-convert";
|
|
9
|
-
import { loadEnvConfig } from "../utils/env";
|
|
10
|
-
import { matchByFilters, objectChecksum, parseFilters, readJsonIfExists, sha256, toAbsolutePath } from "../utils/sync";
|
|
11
|
-
|
|
12
|
-
type CollectionManifest = {
|
|
13
|
-
version: 1;
|
|
14
|
-
projectId: string;
|
|
15
|
-
generatedAt: string;
|
|
16
|
-
collections: Record<string, { checksum: string }>;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
const COLLECTION_EXPORT_KEYS = [
|
|
20
|
-
"collection_id",
|
|
21
|
-
"name",
|
|
22
|
-
"description",
|
|
23
|
-
"form_schema",
|
|
24
|
-
"timestamps",
|
|
25
|
-
"auditable"
|
|
26
|
-
] as const;
|
|
27
|
-
|
|
28
|
-
const FIELD_INTERNAL_KEYS = new Set(["_expanded", "inherited", "inheried"]);
|
|
29
|
-
|
|
30
|
-
function applySchemaPullOptions(command: Command): Command {
|
|
31
|
-
return command
|
|
32
|
-
.option("-p, --project-id <project-id>", "Project ID to sync schema from (uses default if not specified)")
|
|
33
|
-
.option("-t, --target-dir <path>", "Base target directory for schema artifacts")
|
|
34
|
-
.option("-o, --output <path>", "Output path for generated TypeScript types", "./buildx/generated/types.ts")
|
|
35
|
-
.option("--collections-file <path>", "Path to store editable collections-as-code JSON", "./buildx/generated/collections.json")
|
|
36
|
-
.option("-u, --api-url <url>", "Custom API base URL")
|
|
37
|
-
.option("--filter <pattern...>", "Only include collections matching wildcard pattern(s), ex: users* or order_*")
|
|
38
|
-
.option("--dry-run", "Preview sync result without writing local files")
|
|
39
|
-
.option("--skip-collections-code", "Skip writing collections-as-code JSON")
|
|
40
|
-
.option("--no-annotate-types", "Disable @bx annotations injection into pulled types.ts")
|
|
41
|
-
.option("--raw-collections", "Export raw collections payload without normalization")
|
|
42
|
-
.option("--include-buildx", "Include buildx_* collections in collections-as-code JSON")
|
|
43
|
-
.option("-f, --force", "Force overwrite existing files");
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function printDryRunFilePreview(filePath: string, content: string): void {
|
|
47
|
-
console.log(chalk.cyan(`\n--- DRY RUN: ${filePath} ---`));
|
|
48
|
-
console.log(content);
|
|
49
|
-
console.log(chalk.cyan(`--- END DRY RUN: ${filePath} ---\n`));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function ensureConfigured(): void {
|
|
53
|
-
if (!apiService.isConfigured()) {
|
|
54
|
-
console.error(chalk.red("❌ API not configured"));
|
|
55
|
-
console.log(chalk.yellow("Please configure your API endpoint and API key first:"));
|
|
56
|
-
console.log(chalk.cyan(" buildx config:setup"));
|
|
57
|
-
process.exit(1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (!configManager.isAuthenticated()) {
|
|
61
|
-
console.error(chalk.red("Error: Not authenticated"));
|
|
62
|
-
console.log(chalk.yellow("Run \"buildx auth:login\" to authenticate first"));
|
|
63
|
-
process.exit(1);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function resolveProjectId(projectId?: string): string {
|
|
68
|
-
if (projectId) return projectId;
|
|
69
|
-
|
|
70
|
-
const defaultProjectId = configManager.getDefaultProject();
|
|
71
|
-
if (defaultProjectId) {
|
|
72
|
-
console.log(chalk.blue("Using project ID:"), defaultProjectId);
|
|
73
|
-
return defaultProjectId;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const envConfig = loadEnvConfig();
|
|
77
|
-
if (envConfig.BUILDX_PROJECT_ID) {
|
|
78
|
-
console.log(chalk.blue("Using project ID from environment variable:"), envConfig.BUILDX_PROJECT_ID);
|
|
79
|
-
return envConfig.BUILDX_PROJECT_ID;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
console.error(chalk.red("Error: No project ID specified"));
|
|
83
|
-
console.log(chalk.yellow("Use --project-id option, set a default project with 'buildx projects:set-default', or set BUILDX_PROJECT_ID environment variable"));
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function findDuplicateCollectionIds(collections: any[]): string[] {
|
|
88
|
-
const seen = new Set<string>();
|
|
89
|
-
const duplicates = new Set<string>();
|
|
90
|
-
for (const collection of collections) {
|
|
91
|
-
const id = String(collection?.collection_id || "").trim();
|
|
92
|
-
if (!id) continue;
|
|
93
|
-
if (seen.has(id)) {
|
|
94
|
-
duplicates.add(id);
|
|
95
|
-
continue;
|
|
96
|
-
}
|
|
97
|
-
seen.add(id);
|
|
98
|
-
}
|
|
99
|
-
return [...duplicates];
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function filterCollections(collections: any[], filters: string[]): any[] {
|
|
103
|
-
if (!filters.length) return collections;
|
|
104
|
-
return collections.filter((collection) => matchByFilters(String(collection.collection_id || ""), filters));
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function sanitizeFieldValue(value: any): any {
|
|
108
|
-
if (Array.isArray(value)) return value.map(sanitizeFieldValue);
|
|
109
|
-
if (value && typeof value === "object") {
|
|
110
|
-
const result: Record<string, any> = {};
|
|
111
|
-
for (const [key, subValue] of Object.entries(value)) {
|
|
112
|
-
if (FIELD_INTERNAL_KEYS.has(key)) continue;
|
|
113
|
-
result[key] = sanitizeFieldValue(subValue);
|
|
114
|
-
}
|
|
115
|
-
return result;
|
|
116
|
-
}
|
|
117
|
-
return value;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function normalizeCollectionForCode(collection: any): any {
|
|
121
|
-
if (!collection || typeof collection !== "object") return null;
|
|
122
|
-
if (!collection.collection_id || typeof collection.collection_id !== "string") return null;
|
|
123
|
-
|
|
124
|
-
const result: Record<string, any> = {};
|
|
125
|
-
for (const key of COLLECTION_EXPORT_KEYS) {
|
|
126
|
-
if (collection[key] !== undefined) {
|
|
127
|
-
result[key] = key === "form_schema" ? sanitizeFieldValue(collection[key]) : collection[key];
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
if (!result.form_schema || typeof result.form_schema !== "object") {
|
|
131
|
-
result.form_schema = {};
|
|
132
|
-
}
|
|
133
|
-
return result;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
function validateCollectionForPushDetailed(collection: any): string[] {
|
|
137
|
-
const errors: string[] = [];
|
|
138
|
-
if (!collection || typeof collection !== "object" || Array.isArray(collection)) {
|
|
139
|
-
return ["Collection must be an object"];
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const collectionId = String(collection.collection_id || "");
|
|
143
|
-
if (!collectionId) {
|
|
144
|
-
errors.push("Missing collection_id");
|
|
145
|
-
} else {
|
|
146
|
-
if (collectionId.includes(" ")) {
|
|
147
|
-
errors.push(`collection_id "${collectionId}" cannot contain spaces`);
|
|
148
|
-
}
|
|
149
|
-
if (!/^[A-Za-z0-9_-]+$/.test(collectionId)) {
|
|
150
|
-
errors.push(`collection_id "${collectionId}" must match [A-Za-z0-9_-]+`);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (!collection.form_schema || typeof collection.form_schema !== "object" || Array.isArray(collection.form_schema)) {
|
|
155
|
-
errors.push(`Collection "${collectionId || "unknown"}" is missing valid form_schema object`);
|
|
156
|
-
return errors;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const formSchema = collection.form_schema as Record<string, any>;
|
|
160
|
-
for (const [fieldName, fieldSchema] of Object.entries(formSchema)) {
|
|
161
|
-
const pathPrefix = `${collectionId || "unknown"}.form_schema.${fieldName}`;
|
|
162
|
-
if (!fieldSchema || typeof fieldSchema !== "object" || Array.isArray(fieldSchema)) {
|
|
163
|
-
errors.push(`${pathPrefix} must be an object`);
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
if (!fieldSchema.type || typeof fieldSchema.type !== "string") {
|
|
167
|
-
errors.push(`${pathPrefix}.type is required and must be a string`);
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const fieldType = String(fieldSchema.type);
|
|
171
|
-
|
|
172
|
-
if (["DataObject", "DataObjects", "ReverseReference", "ReverseReferences"].includes(fieldType)) {
|
|
173
|
-
const ref = fieldSchema?.propertiesScheme?.ref;
|
|
174
|
-
if (!ref || typeof ref !== "string") {
|
|
175
|
-
errors.push(`${pathPrefix}.propertiesScheme.ref is required for ${fieldType}`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (["Choices", "MultipleChoices"].includes(fieldType)) {
|
|
180
|
-
const choices = fieldSchema?.propertiesScheme?.choices;
|
|
181
|
-
if (choices !== undefined) {
|
|
182
|
-
if (!Array.isArray(choices)) {
|
|
183
|
-
errors.push(`${pathPrefix}.propertiesScheme.choices must be an array`);
|
|
184
|
-
} else {
|
|
185
|
-
choices.forEach((choice: any, index: number) => {
|
|
186
|
-
const choicePath = `${pathPrefix}.propertiesScheme.choices[${index}]`;
|
|
187
|
-
if (!choice || typeof choice !== "object") {
|
|
188
|
-
errors.push(`${choicePath} must be an object`);
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
if (choice.value === undefined || choice.value === null || String(choice.value).trim() === "") {
|
|
192
|
-
errors.push(`${choicePath}.value is required`);
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return errors;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
type CollectionDiffEntry = {
|
|
204
|
-
type: "added" | "removed" | "changed";
|
|
205
|
-
path: string;
|
|
206
|
-
local?: any;
|
|
207
|
-
remote?: any;
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
function diffCollectionsDetailed(local: any, remote: any, basePath = ""): CollectionDiffEntry[] {
|
|
211
|
-
if (local === remote) return [];
|
|
212
|
-
|
|
213
|
-
const localIsArray = Array.isArray(local);
|
|
214
|
-
const remoteIsArray = Array.isArray(remote);
|
|
215
|
-
if (localIsArray || remoteIsArray) {
|
|
216
|
-
if (JSON.stringify(local) === JSON.stringify(remote)) return [];
|
|
217
|
-
return [{
|
|
218
|
-
type: "changed",
|
|
219
|
-
path: basePath || ".",
|
|
220
|
-
local,
|
|
221
|
-
remote
|
|
222
|
-
}];
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const localIsObject = !!local && typeof local === "object";
|
|
226
|
-
const remoteIsObject = !!remote && typeof remote === "object";
|
|
227
|
-
if (!localIsObject || !remoteIsObject) {
|
|
228
|
-
return [{
|
|
229
|
-
type: "changed",
|
|
230
|
-
path: basePath || ".",
|
|
231
|
-
local,
|
|
232
|
-
remote
|
|
233
|
-
}];
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const result: CollectionDiffEntry[] = [];
|
|
237
|
-
const keys = new Set<string>([...Object.keys(local), ...Object.keys(remote)]);
|
|
238
|
-
for (const key of [...keys].sort()) {
|
|
239
|
-
const nextPath = basePath ? `${basePath}.${key}` : key;
|
|
240
|
-
const hasLocal = Object.prototype.hasOwnProperty.call(local, key);
|
|
241
|
-
const hasRemote = Object.prototype.hasOwnProperty.call(remote, key);
|
|
242
|
-
if (hasLocal && !hasRemote) {
|
|
243
|
-
result.push({
|
|
244
|
-
type: "removed",
|
|
245
|
-
path: nextPath,
|
|
246
|
-
local: local[key]
|
|
247
|
-
});
|
|
248
|
-
continue;
|
|
249
|
-
}
|
|
250
|
-
if (!hasLocal && hasRemote) {
|
|
251
|
-
result.push({
|
|
252
|
-
type: "added",
|
|
253
|
-
path: nextPath,
|
|
254
|
-
remote: remote[key]
|
|
255
|
-
});
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
result.push(...diffCollectionsDetailed(local[key], remote[key], nextPath));
|
|
259
|
-
}
|
|
260
|
-
return result;
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function formatDiffValue(value: any): string {
|
|
264
|
-
const text = JSON.stringify(value);
|
|
265
|
-
if (text === undefined) return "undefined";
|
|
266
|
-
return text.length > 140 ? `${text.slice(0, 137)}...` : text;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function toSingleLineText(value: any): string {
|
|
270
|
-
return String(value ?? "")
|
|
271
|
-
.replace(/\s+/g, " ")
|
|
272
|
-
.trim();
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function buildBxAnnotationLine(fieldSchema: any): string | null {
|
|
276
|
-
if (!fieldSchema || typeof fieldSchema !== "object") return null;
|
|
277
|
-
const tags: string[] = [];
|
|
278
|
-
const title = toSingleLineText(fieldSchema.title);
|
|
279
|
-
const description = toSingleLineText(fieldSchema.description);
|
|
280
|
-
if (title) tags.push(`@bx.title ${title}`);
|
|
281
|
-
if (description) tags.push(`@bx.description ${description}`);
|
|
282
|
-
if (fieldSchema.required === true) tags.push("@bx.required");
|
|
283
|
-
const ref = toSingleLineText(fieldSchema?.propertiesScheme?.ref);
|
|
284
|
-
if (ref) tags.push(`@bx.ref ${ref}`);
|
|
285
|
-
const choices = fieldSchema?.propertiesScheme?.choices;
|
|
286
|
-
if (Array.isArray(choices) && choices.length > 0) {
|
|
287
|
-
const encoded = choices
|
|
288
|
-
.map((choice: any) => {
|
|
289
|
-
const value = toSingleLineText(choice?.value);
|
|
290
|
-
if (!value) return null;
|
|
291
|
-
const hasLabel = !!choice && Object.prototype.hasOwnProperty.call(choice, "label");
|
|
292
|
-
const label = hasLabel ? toSingleLineText(choice?.label) : value;
|
|
293
|
-
return `${value}:${label}`;
|
|
294
|
-
})
|
|
295
|
-
.filter(Boolean)
|
|
296
|
-
.join("|");
|
|
297
|
-
if (encoded) tags.push(`@bx.choices ${encoded}`);
|
|
298
|
-
}
|
|
299
|
-
if (!tags.length) return null;
|
|
300
|
-
return `/** ${tags.join(" ")} */`;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
function injectTypesAnnotations(typeContent: string, collections: any[]): string {
|
|
304
|
-
const enumMap = parseCollectionIdEnum(typeContent);
|
|
305
|
-
const collectionById = new Map<string, any>();
|
|
306
|
-
for (const collection of collections) {
|
|
307
|
-
const id = String(collection?.collection_id || "").trim();
|
|
308
|
-
if (!id) continue;
|
|
309
|
-
collectionById.set(id, collection);
|
|
310
|
-
}
|
|
311
|
-
if (!collectionById.size) return typeContent;
|
|
312
|
-
|
|
313
|
-
const typeRegex = /export\s+type\s+([A-Za-z0-9_]+)\s*=\s*{([\s\S]*?)}\s*;/g;
|
|
314
|
-
return typeContent.replace(typeRegex, (full, typeName, body) => {
|
|
315
|
-
const collectionId = resolveCollectionId(typeName, enumMap);
|
|
316
|
-
const collection = collectionById.get(collectionId);
|
|
317
|
-
if (!collection?.form_schema || typeof collection.form_schema !== "object") return full;
|
|
318
|
-
|
|
319
|
-
const formSchema = collection.form_schema as Record<string, any>;
|
|
320
|
-
const lines = body.split("\n");
|
|
321
|
-
const output: string[] = [];
|
|
322
|
-
for (const line of lines) {
|
|
323
|
-
const fieldMatch = line.match(/^(\s*)([A-Za-z_][A-Za-z0-9_]*)\??\s*:/);
|
|
324
|
-
if (!fieldMatch) {
|
|
325
|
-
output.push(line);
|
|
326
|
-
continue;
|
|
327
|
-
}
|
|
328
|
-
const [, indent, fieldName] = fieldMatch;
|
|
329
|
-
const fieldSchema = formSchema[fieldName];
|
|
330
|
-
const annotationLine = buildBxAnnotationLine(fieldSchema);
|
|
331
|
-
if (annotationLine) {
|
|
332
|
-
const prev = output.length > 0 ? output[output.length - 1] : "";
|
|
333
|
-
if (!/@bx\./.test(prev)) {
|
|
334
|
-
output.push(`${indent}${annotationLine}`);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
output.push(line);
|
|
338
|
-
}
|
|
339
|
-
return `export type ${typeName} = {${output.join("\n")}};`;
|
|
340
|
-
});
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async function runSchemaPull(options: any): Promise<void> {
|
|
344
|
-
ensureConfigured();
|
|
345
|
-
const projectId = resolveProjectId(options.projectId);
|
|
346
|
-
|
|
347
|
-
if (options.apiUrl) {
|
|
348
|
-
apiService.setBaseUrl(options.apiUrl);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const targetDirPath = options.targetDir ? toAbsolutePath(options.targetDir) : null;
|
|
352
|
-
const outputPath = targetDirPath
|
|
353
|
-
? path.join(targetDirPath, "types.ts")
|
|
354
|
-
: toAbsolutePath(options.output);
|
|
355
|
-
const collectionsFilePath = targetDirPath
|
|
356
|
-
? path.join(targetDirPath, "collections.json")
|
|
357
|
-
: toAbsolutePath(options.collectionsFile);
|
|
358
|
-
const manifestPath = path.join(path.dirname(collectionsFilePath), "collections.manifest.json");
|
|
359
|
-
|
|
360
|
-
const spinner = ora("Fetching TypeScript types...").start();
|
|
361
|
-
const typescriptCode = await apiService.getSchema(projectId);
|
|
362
|
-
if (!typescriptCode || typeof typescriptCode !== "string") {
|
|
363
|
-
spinner.fail("Failed to fetch TypeScript types");
|
|
364
|
-
throw new Error("Schema endpoint returned empty content");
|
|
365
|
-
}
|
|
366
|
-
spinner.succeed("TypeScript types fetched successfully");
|
|
367
|
-
let finalTypescriptCode = typescriptCode;
|
|
368
|
-
|
|
369
|
-
if (!options.skipCollectionsCode) {
|
|
370
|
-
const collectionsSpinner = ora("Fetching collections-as-code...").start();
|
|
371
|
-
const filters = parseFilters(options.filter);
|
|
372
|
-
const collections = await apiService.getCollections(projectId);
|
|
373
|
-
let editableCollections = options.rawCollections
|
|
374
|
-
? collections
|
|
375
|
-
: collections.map(normalizeCollectionForCode).filter(Boolean);
|
|
376
|
-
if (!options.includeBuildx) {
|
|
377
|
-
editableCollections = editableCollections.filter((col) => !String(col?.collection_id || "").startsWith("buildx_"));
|
|
378
|
-
}
|
|
379
|
-
editableCollections = filterCollections(editableCollections, filters);
|
|
380
|
-
editableCollections = editableCollections.sort((a, b) => String(a.collection_id).localeCompare(String(b.collection_id)));
|
|
381
|
-
if (options.annotateTypes) {
|
|
382
|
-
finalTypescriptCode = injectTypesAnnotations(finalTypescriptCode, editableCollections);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
await fs.ensureDir(path.dirname(collectionsFilePath));
|
|
386
|
-
|
|
387
|
-
const manifest: CollectionManifest = {
|
|
388
|
-
version: 1,
|
|
389
|
-
projectId,
|
|
390
|
-
generatedAt: new Date().toISOString(),
|
|
391
|
-
collections: {}
|
|
392
|
-
};
|
|
393
|
-
for (const collection of editableCollections) {
|
|
394
|
-
manifest.collections[collection.collection_id] = { checksum: objectChecksum(collection) };
|
|
395
|
-
}
|
|
396
|
-
if (options.dryRun) {
|
|
397
|
-
collectionsSpinner.succeed("Collections-as-code preview ready");
|
|
398
|
-
console.log(chalk.cyan(`~ would write collections JSON: ${collectionsFilePath}`));
|
|
399
|
-
console.log(chalk.cyan(`~ would write collections manifest: ${manifestPath}`));
|
|
400
|
-
console.log(chalk.blue("Collections count:"), editableCollections.length);
|
|
401
|
-
printDryRunFilePreview(collectionsFilePath, JSON.stringify(editableCollections, null, 2));
|
|
402
|
-
printDryRunFilePreview(manifestPath, JSON.stringify(manifest, null, 2));
|
|
403
|
-
} else {
|
|
404
|
-
await fs.writeJson(collectionsFilePath, editableCollections, { spaces: 2 });
|
|
405
|
-
await fs.writeJson(manifestPath, manifest, { spaces: 2 });
|
|
406
|
-
collectionsSpinner.succeed("Collections-as-code saved");
|
|
407
|
-
console.log(chalk.green("✓ Collections JSON:"), collectionsFilePath);
|
|
408
|
-
console.log(chalk.green("✓ Collections manifest:"), manifestPath);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
await fs.ensureDir(path.dirname(outputPath));
|
|
413
|
-
|
|
414
|
-
if ((await fs.pathExists(outputPath)) && !options.force) {
|
|
415
|
-
const existingContent = await fs.readFile(outputPath, "utf8");
|
|
416
|
-
if (sha256(existingContent) !== sha256(finalTypescriptCode)) {
|
|
417
|
-
throw new Error(`Output file already exists with local edits: ${outputPath}. Use --force to overwrite.`);
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
if (options.dryRun) {
|
|
422
|
-
console.log(chalk.cyan(`~ would write types to: ${outputPath}`));
|
|
423
|
-
console.log(chalk.blue("Types size:"), `${(finalTypescriptCode.length / 1024).toFixed(2)} KB`);
|
|
424
|
-
printDryRunFilePreview(outputPath, finalTypescriptCode);
|
|
425
|
-
} else {
|
|
426
|
-
await fs.writeFile(outputPath, finalTypescriptCode, "utf8");
|
|
427
|
-
console.log(chalk.green("✓ Types generated at:"), outputPath);
|
|
428
|
-
console.log(chalk.blue("File size:"), `${(finalTypescriptCode.length / 1024).toFixed(2)} KB`);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export const schemaSyncCommand = applySchemaPullOptions(
|
|
433
|
-
new Command("schema:sync")
|
|
434
|
-
.description("Deprecated alias of schema:pull (kept for compatibility)")
|
|
435
|
-
).action(async (options) => {
|
|
436
|
-
try {
|
|
437
|
-
console.log(chalk.yellow("schema:sync is deprecated. Use schema:pull."));
|
|
438
|
-
await runSchemaPull(options);
|
|
439
|
-
} catch (error) {
|
|
440
|
-
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
441
|
-
process.exit(1);
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
export const schemaPullCommand = applySchemaPullOptions(
|
|
446
|
-
new Command("schema:pull")
|
|
447
|
-
.description("Pull project schema/types as code into local files")
|
|
448
|
-
).action(async (options) => {
|
|
449
|
-
try {
|
|
450
|
-
await runSchemaPull(options);
|
|
451
|
-
} catch (error) {
|
|
452
|
-
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
453
|
-
process.exit(1);
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
export const schemaPushCommand = new Command("schema:push")
|
|
458
|
-
.description("Push local collections-as-code JSON back to project")
|
|
459
|
-
.option("-p, --project-id <project-id>", "Project ID to push schema to (uses default if not specified)")
|
|
460
|
-
.option("-t, --target-dir <path>", "Base target directory for schema artifacts")
|
|
461
|
-
.option("--collections-file <path>", "Path to collections-as-code JSON", "./buildx/generated/collections.json")
|
|
462
|
-
.option("-u, --api-url <url>", "Custom API base URL")
|
|
463
|
-
.option("--filter <pattern...>", "Only push collections matching wildcard pattern(s)")
|
|
464
|
-
.option("--dry-run", "Show what would change without pushing")
|
|
465
|
-
.option("--allow-create", "Allow creating new collections when collection_id does not exist on remote")
|
|
466
|
-
.option("-f, --force", "Force push even when remote drift is detected")
|
|
467
|
-
.action(async (options) => {
|
|
468
|
-
try {
|
|
469
|
-
ensureConfigured();
|
|
470
|
-
const projectId = resolveProjectId(options.projectId);
|
|
471
|
-
|
|
472
|
-
if (options.apiUrl) {
|
|
473
|
-
apiService.setBaseUrl(options.apiUrl);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
const targetDirPath = options.targetDir ? toAbsolutePath(options.targetDir) : null;
|
|
477
|
-
const collectionsFilePath = targetDirPath
|
|
478
|
-
? path.join(targetDirPath, "collections.json")
|
|
479
|
-
: toAbsolutePath(options.collectionsFile);
|
|
480
|
-
const manifestPath = path.join(path.dirname(collectionsFilePath), "collections.manifest.json");
|
|
481
|
-
if (!(await fs.pathExists(collectionsFilePath))) {
|
|
482
|
-
throw new Error(`Collections file not found: ${collectionsFilePath}`);
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
const localCollections = await fs.readJson(collectionsFilePath);
|
|
486
|
-
if (!Array.isArray(localCollections)) {
|
|
487
|
-
throw new Error("Collections file must be a JSON array");
|
|
488
|
-
}
|
|
489
|
-
const normalizedLocalCollections = localCollections.map(normalizeCollectionForCode).filter(Boolean);
|
|
490
|
-
const filters = parseFilters(options.filter);
|
|
491
|
-
const targetCollections = filterCollections(normalizedLocalCollections, filters);
|
|
492
|
-
|
|
493
|
-
const duplicateIds = findDuplicateCollectionIds(targetCollections);
|
|
494
|
-
if (duplicateIds.length > 0) {
|
|
495
|
-
throw new Error(`Duplicate collection_id is not allowed:\n- ${duplicateIds.join("\n- ")}`);
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
const validationErrors: string[] = [];
|
|
499
|
-
for (const collection of targetCollections) {
|
|
500
|
-
validationErrors.push(...validateCollectionForPushDetailed(collection));
|
|
501
|
-
}
|
|
502
|
-
if (validationErrors.length > 0) {
|
|
503
|
-
throw new Error(`Invalid collections file:\n- ${validationErrors.join("\n- ")}`);
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
const manifest = await readJsonIfExists<CollectionManifest>(manifestPath);
|
|
507
|
-
const remoteCollections = (await apiService.getCollections(projectId))
|
|
508
|
-
.map(normalizeCollectionForCode)
|
|
509
|
-
.filter(Boolean);
|
|
510
|
-
const remoteById = new Map<string, any>(remoteCollections.map((collection) => [collection.collection_id, collection]));
|
|
511
|
-
|
|
512
|
-
let pushed = 0;
|
|
513
|
-
let skipped = 0;
|
|
514
|
-
|
|
515
|
-
for (const collection of targetCollections) {
|
|
516
|
-
const collectionId = collection.collection_id;
|
|
517
|
-
const remoteCollection = remoteById.get(collectionId);
|
|
518
|
-
|
|
519
|
-
if (!remoteCollection && !options.allowCreate) {
|
|
520
|
-
console.log(chalk.yellow(`⚠ Skipped ${collectionId}: does not exist on remote (use --allow-create to create)`));
|
|
521
|
-
skipped++;
|
|
522
|
-
continue;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
const localChecksum = objectChecksum(collection);
|
|
526
|
-
const remoteChecksum = remoteCollection ? objectChecksum(remoteCollection) : null;
|
|
527
|
-
const lastPulledChecksum = manifest?.collections?.[collectionId]?.checksum;
|
|
528
|
-
const remoteDrifted = !!(lastPulledChecksum && remoteChecksum && lastPulledChecksum !== remoteChecksum);
|
|
529
|
-
|
|
530
|
-
if (remoteDrifted && !options.force) {
|
|
531
|
-
console.log(chalk.yellow(`⚠ Skipped ${collectionId}: remote changed since last pull (use --force to override)`));
|
|
532
|
-
skipped++;
|
|
533
|
-
continue;
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
if (localChecksum === remoteChecksum) {
|
|
537
|
-
console.log(chalk.gray(`- ${collectionId}: no changes`));
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
if (options.dryRun) {
|
|
542
|
-
console.log(chalk.cyan(`~ would push ${collectionId}`));
|
|
543
|
-
pushed++;
|
|
544
|
-
continue;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
await apiService.upsertCollection(projectId, collection);
|
|
548
|
-
console.log(chalk.green(`✓ pushed ${collectionId}`));
|
|
549
|
-
pushed++;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
console.log(chalk.blue(`Done. pushed=${pushed}, skipped=${skipped}`));
|
|
553
|
-
} catch (error) {
|
|
554
|
-
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
555
|
-
process.exit(1);
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
export const schemaListCommand = new Command("schema:list")
|
|
560
|
-
.description("List remote collections in project")
|
|
561
|
-
.option("-p, --project-id <project-id>", "Project ID (uses default if not specified)")
|
|
562
|
-
.option("-u, --api-url <url>", "Custom API base URL")
|
|
563
|
-
.option("--include-buildx", "Include buildx_* collections")
|
|
564
|
-
.option("--filter <pattern...>", "Filter collection ids by wildcard pattern(s)")
|
|
565
|
-
.action(async (options) => {
|
|
566
|
-
try {
|
|
567
|
-
ensureConfigured();
|
|
568
|
-
const projectId = resolveProjectId(options.projectId);
|
|
569
|
-
if (options.apiUrl) {
|
|
570
|
-
apiService.setBaseUrl(options.apiUrl);
|
|
571
|
-
}
|
|
572
|
-
const filters = parseFilters(options.filter);
|
|
573
|
-
|
|
574
|
-
let collections = await apiService.getCollections(projectId);
|
|
575
|
-
if (!options.includeBuildx) {
|
|
576
|
-
collections = collections.filter((collection) => !String(collection.collection_id || "").startsWith("buildx_"));
|
|
577
|
-
}
|
|
578
|
-
collections = filterCollections(collections, filters).sort((a, b) => String(a.collection_id).localeCompare(String(b.collection_id)));
|
|
579
|
-
|
|
580
|
-
for (const collection of collections) {
|
|
581
|
-
console.log(collection.collection_id);
|
|
582
|
-
}
|
|
583
|
-
console.log(chalk.blue(`Total: ${collections.length}`));
|
|
584
|
-
} catch (error) {
|
|
585
|
-
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
586
|
-
process.exit(1);
|
|
587
|
-
}
|
|
588
|
-
});
|
|
589
|
-
|
|
590
|
-
export const schemaDiffCommand = new Command("schema:diff")
|
|
591
|
-
.description("Diff local collections-as-code against remote project")
|
|
592
|
-
.option("-p, --project-id <project-id>", "Project ID (uses default if not specified)")
|
|
593
|
-
.option("-t, --target-dir <path>", "Base target directory for schema artifacts")
|
|
594
|
-
.option("--collections-file <path>", "Path to collections-as-code JSON", "./buildx/generated/collections.json")
|
|
595
|
-
.option("-u, --api-url <url>", "Custom API base URL")
|
|
596
|
-
.option("--include-buildx", "Include buildx_* collections")
|
|
597
|
-
.option("--filter <pattern...>", "Filter collection ids by wildcard pattern(s)")
|
|
598
|
-
.option("--details", "Show field-level diff details for changed collections")
|
|
599
|
-
.option("--details-limit <count>", "Max number of detail lines per changed collection", "30")
|
|
600
|
-
.action(async (options) => {
|
|
601
|
-
try {
|
|
602
|
-
ensureConfigured();
|
|
603
|
-
const projectId = resolveProjectId(options.projectId);
|
|
604
|
-
if (options.apiUrl) {
|
|
605
|
-
apiService.setBaseUrl(options.apiUrl);
|
|
606
|
-
}
|
|
607
|
-
const filters = parseFilters(options.filter);
|
|
608
|
-
|
|
609
|
-
const targetDirPath = options.targetDir ? toAbsolutePath(options.targetDir) : null;
|
|
610
|
-
const collectionsFilePath = targetDirPath
|
|
611
|
-
? path.join(targetDirPath, "collections.json")
|
|
612
|
-
: toAbsolutePath(options.collectionsFile);
|
|
613
|
-
if (!(await fs.pathExists(collectionsFilePath))) {
|
|
614
|
-
throw new Error(`Collections file not found: ${collectionsFilePath}`);
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
let localCollections = await fs.readJson(collectionsFilePath);
|
|
618
|
-
if (!Array.isArray(localCollections)) {
|
|
619
|
-
throw new Error("Collections file must be a JSON array");
|
|
620
|
-
}
|
|
621
|
-
let remoteCollections = await apiService.getCollections(projectId);
|
|
622
|
-
localCollections = localCollections.map(normalizeCollectionForCode).filter(Boolean);
|
|
623
|
-
remoteCollections = remoteCollections.map(normalizeCollectionForCode).filter(Boolean);
|
|
624
|
-
|
|
625
|
-
if (!options.includeBuildx) {
|
|
626
|
-
localCollections = localCollections.filter((collection: any) => !String(collection.collection_id || "").startsWith("buildx_"));
|
|
627
|
-
remoteCollections = remoteCollections.filter((collection) => !String(collection.collection_id || "").startsWith("buildx_"));
|
|
628
|
-
}
|
|
629
|
-
localCollections = filterCollections(localCollections, filters);
|
|
630
|
-
remoteCollections = filterCollections(remoteCollections, filters);
|
|
631
|
-
|
|
632
|
-
const localById = new Map<string, any>(localCollections.map((collection: any) => [collection.collection_id, collection]));
|
|
633
|
-
const remoteById = new Map<string, any>(remoteCollections.map((collection) => [collection.collection_id, collection]));
|
|
634
|
-
|
|
635
|
-
const localIds = new Set(localById.keys());
|
|
636
|
-
const remoteIds = new Set(remoteById.keys());
|
|
637
|
-
const allIds = [...new Set([...localIds, ...remoteIds])].sort((a, b) => a.localeCompare(b));
|
|
638
|
-
|
|
639
|
-
let onlyLocal = 0;
|
|
640
|
-
let onlyRemote = 0;
|
|
641
|
-
let changed = 0;
|
|
642
|
-
let same = 0;
|
|
643
|
-
|
|
644
|
-
for (const id of allIds) {
|
|
645
|
-
const local = localById.get(id);
|
|
646
|
-
const remote = remoteById.get(id);
|
|
647
|
-
if (local && !remote) {
|
|
648
|
-
console.log(chalk.yellow(`+ local-only ${id}`));
|
|
649
|
-
onlyLocal++;
|
|
650
|
-
continue;
|
|
651
|
-
}
|
|
652
|
-
if (!local && remote) {
|
|
653
|
-
console.log(chalk.yellow(`- remote-only ${id}`));
|
|
654
|
-
onlyRemote++;
|
|
655
|
-
continue;
|
|
656
|
-
}
|
|
657
|
-
if (local && remote) {
|
|
658
|
-
if (objectChecksum(local) === objectChecksum(remote)) {
|
|
659
|
-
console.log(chalk.gray(`= same ${id}`));
|
|
660
|
-
same++;
|
|
661
|
-
} else {
|
|
662
|
-
console.log(chalk.cyan(`~ changed ${id}`));
|
|
663
|
-
if (options.details) {
|
|
664
|
-
const detailsLimit = Number(options.detailsLimit) > 0 ? Number(options.detailsLimit) : 30;
|
|
665
|
-
const details = diffCollectionsDetailed(local, remote);
|
|
666
|
-
const show = details.slice(0, detailsLimit);
|
|
667
|
-
for (const item of show) {
|
|
668
|
-
if (item.type === "added") {
|
|
669
|
-
console.log(chalk.yellow(` + ${item.path} (remote-only) = ${formatDiffValue(item.remote)}`));
|
|
670
|
-
} else if (item.type === "removed") {
|
|
671
|
-
console.log(chalk.yellow(` - ${item.path} (local-only) = ${formatDiffValue(item.local)}`));
|
|
672
|
-
} else {
|
|
673
|
-
console.log(chalk.cyan(` ~ ${item.path} local=${formatDiffValue(item.local)} remote=${formatDiffValue(item.remote)}`));
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
if (details.length > show.length) {
|
|
677
|
-
console.log(chalk.gray(` ... and ${details.length - show.length} more changes`));
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
changed++;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
console.log(chalk.blue(`Summary: changed=${changed}, same=${same}, local-only=${onlyLocal}, remote-only=${onlyRemote}`));
|
|
686
|
-
} catch (error) {
|
|
687
|
-
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
688
|
-
process.exit(1);
|
|
689
|
-
}
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
export const schemaTypesConvertCommand = new Command("schema:types:convert")
|
|
694
|
-
.description("Convert local TypeScript types file into collections-as-code JSON (review before push)")
|
|
695
|
-
.option("-t, --target-dir <path>", "Base target directory for schema artifacts")
|
|
696
|
-
.option("--types-file <path>", "Path to TypeScript types file", "./buildx/generated/types.ts")
|
|
697
|
-
.option("--collections-file <path>", "Output collections JSON path", "./buildx/generated/collections.json")
|
|
698
|
-
.option("--base-collections-file <path>", "Optional base collections file to merge metadata from")
|
|
699
|
-
.option("--no-allow-lossy", "Require base collections metadata file (strict mode)")
|
|
700
|
-
.option("--filter <pattern...>", "Filter collection ids by wildcard pattern(s)")
|
|
701
|
-
.option("--dry-run", "Preview output content without writing files")
|
|
702
|
-
.action(async (options) => {
|
|
703
|
-
try {
|
|
704
|
-
const targetDirPath = options.targetDir ? toAbsolutePath(options.targetDir) : null;
|
|
705
|
-
const typesFilePath = targetDirPath
|
|
706
|
-
? path.join(targetDirPath, "types.ts")
|
|
707
|
-
: toAbsolutePath(options.typesFile);
|
|
708
|
-
const outputCollectionsFilePath = targetDirPath
|
|
709
|
-
? path.join(targetDirPath, "collections.json")
|
|
710
|
-
: toAbsolutePath(options.collectionsFile);
|
|
711
|
-
const baseCollectionsFilePath = options.baseCollectionsFile
|
|
712
|
-
? toAbsolutePath(options.baseCollectionsFile)
|
|
713
|
-
: outputCollectionsFilePath;
|
|
714
|
-
const filters = parseFilters(options.filter);
|
|
715
|
-
|
|
716
|
-
if (!(await fs.pathExists(typesFilePath))) {
|
|
717
|
-
throw new Error(`Types file not found: ${typesFilePath}`);
|
|
718
|
-
}
|
|
719
|
-
const typeContent = await fs.readFile(typesFilePath, "utf8");
|
|
720
|
-
const { collections, warnings } = buildCollectionsFromTypes(typeContent, filters);
|
|
721
|
-
|
|
722
|
-
let finalCollections = collections;
|
|
723
|
-
let hasBaseCollections = false;
|
|
724
|
-
if (baseCollectionsFilePath && await fs.pathExists(baseCollectionsFilePath)) {
|
|
725
|
-
hasBaseCollections = true;
|
|
726
|
-
const baseCollections = await fs.readJson(baseCollectionsFilePath);
|
|
727
|
-
if (Array.isArray(baseCollections)) {
|
|
728
|
-
const baseById = new Map<string, any>(baseCollections.map((collection) => [collection.collection_id, collection]));
|
|
729
|
-
finalCollections = collections.map((collection) => {
|
|
730
|
-
const base = baseById.get(collection.collection_id);
|
|
731
|
-
if (!base) return collection;
|
|
732
|
-
return normalizeCollectionForCode(mergeCollectionWithBase(base, collection));
|
|
733
|
-
}).filter(Boolean);
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
if (!hasBaseCollections && options.allowLossy === false) {
|
|
737
|
-
throw new Error([
|
|
738
|
-
`Base collections file not found: ${baseCollectionsFilePath || "(not specified)"}`,
|
|
739
|
-
"Conversion without base metadata is lossy and usually produces a large schema:diff.",
|
|
740
|
-
"Run schema:pull first, or pass --base-collections-file <path>, or remove --no-allow-lossy."
|
|
741
|
-
].join("\n"));
|
|
742
|
-
}
|
|
743
|
-
if (!hasBaseCollections) {
|
|
744
|
-
console.log(chalk.yellow("! Running lossy convert without base metadata (titles/choices/collection metadata may be dropped)"));
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
if (warnings.length) {
|
|
748
|
-
console.log(chalk.yellow(`Conversion warnings (${warnings.length}):`));
|
|
749
|
-
for (const warning of warnings) {
|
|
750
|
-
console.log(chalk.yellow(` - ${warning}`));
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
if (options.dryRun) {
|
|
755
|
-
console.log(chalk.cyan(`~ would write converted collections to: ${outputCollectionsFilePath}`));
|
|
756
|
-
printDryRunFilePreview(outputCollectionsFilePath, JSON.stringify(finalCollections, null, 2));
|
|
757
|
-
} else {
|
|
758
|
-
await fs.ensureDir(path.dirname(outputCollectionsFilePath));
|
|
759
|
-
await fs.writeJson(outputCollectionsFilePath, finalCollections, { spaces: 2 });
|
|
760
|
-
console.log(chalk.green("✓ Converted collections written to:"), outputCollectionsFilePath);
|
|
761
|
-
console.log(chalk.blue("Next step:"));
|
|
762
|
-
console.log(chalk.cyan(` buildx-cli schema:push --collections-file "${outputCollectionsFilePath}" --project-id <project-id> --dry-run`));
|
|
763
|
-
}
|
|
764
|
-
} catch (error) {
|
|
765
|
-
console.error(chalk.red("Error:"), error instanceof Error ? error.message : "Unknown error");
|
|
766
|
-
process.exit(1);
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
export const syncCommand = schemaSyncCommand;
|
|
771
|
-
|
|
772
|
-
export const __schemaTypesConvertInternals = {
|
|
773
|
-
mapScalarTypeToFieldType,
|
|
774
|
-
convertTypeFieldToSchemaField,
|
|
775
|
-
buildCollectionsFromTypes,
|
|
776
|
-
mergeSchemaField,
|
|
777
|
-
mergeCollectionWithBase
|
|
778
|
-
};
|