@svadmin/create 0.18.1 → 0.20.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/dist/index.js CHANGED
@@ -5023,10 +5023,10 @@ var require_picocolors = __commonJS(function(exports, module) {
5023
5023
  });
5024
5024
 
5025
5025
  // src/index.ts
5026
- var import_prompts = __toESM(require_prompts3(), 1);
5027
- var import_picocolors = __toESM(require_picocolors(), 1);
5028
- import fs from "node:fs";
5029
- import path from "node:path";
5026
+ var import_prompts2 = __toESM(require_prompts3(), 1);
5027
+ var import_picocolors2 = __toESM(require_picocolors(), 1);
5028
+ import fs2 from "node:fs";
5029
+ import path2 from "node:path";
5030
5030
  import { fileURLToPath } from "node:url";
5031
5031
  import { createRequire as createRequire2 } from "node:module";
5032
5032
  import { spawnSync } from "node:child_process";
@@ -5127,6 +5127,1310 @@ function createProjectPackageJson(scaffold, options) {
5127
5127
  };
5128
5128
  }
5129
5129
 
5130
+ // src/infer-command.ts
5131
+ var import_picocolors = __toESM(require_picocolors(), 1);
5132
+ var import_prompts = __toESM(require_prompts3(), 1);
5133
+ import fs from "node:fs";
5134
+ import path from "node:path";
5135
+
5136
+ // ../core/src/inferencer-openapi.ts
5137
+ function isRef(obj) {
5138
+ return obj !== null && typeof obj === "object" && "$ref" in obj && typeof obj.$ref === "string";
5139
+ }
5140
+ function resolveRef(ref, root) {
5141
+ const parts = ref.replace("#/", "").split("/");
5142
+ let current = root;
5143
+ for (const part of parts) {
5144
+ if (current === null || typeof current !== "object")
5145
+ return null;
5146
+ current = current[part];
5147
+ if (!current)
5148
+ return null;
5149
+ }
5150
+ return current;
5151
+ }
5152
+ function resolveSchema(schemaOrRef, root) {
5153
+ if (isRef(schemaOrRef)) {
5154
+ return resolveRef(schemaOrRef.$ref, root);
5155
+ }
5156
+ return schemaOrRef;
5157
+ }
5158
+ function mapOpenAPITypeToFieldType(key, schema, root) {
5159
+ const { type, format } = schema;
5160
+ if (schema.enum && schema.enum.length > 0)
5161
+ return "select";
5162
+ if (type === "array") {
5163
+ if (schema.items) {
5164
+ const itemSchema = resolveSchema(schema.items, root);
5165
+ if (itemSchema?.type === "string") {
5166
+ const lk = key.toLowerCase();
5167
+ if (lk.includes("image") || lk.includes("photo"))
5168
+ return "images";
5169
+ return "tags";
5170
+ }
5171
+ }
5172
+ return "tags";
5173
+ }
5174
+ if (type === "object")
5175
+ return "json";
5176
+ if (type === "boolean")
5177
+ return "boolean";
5178
+ if (type === "number" || type === "integer")
5179
+ return "number";
5180
+ if (type === "string") {
5181
+ if (format === "date" || format === "date-time")
5182
+ return "date";
5183
+ if (format === "email")
5184
+ return "email";
5185
+ if (format === "uri" || format === "url")
5186
+ return "url";
5187
+ if (format === "binary" || format === "byte")
5188
+ return "image";
5189
+ const lk = key.toLowerCase();
5190
+ if (lk.includes("email"))
5191
+ return "email";
5192
+ if (lk.includes("phone") || lk.includes("tel"))
5193
+ return "phone";
5194
+ if (lk.includes("url") || lk.includes("link") || lk.includes("website"))
5195
+ return "url";
5196
+ if (lk.includes("avatar") || lk.includes("image") || lk.includes("photo") || lk.includes("logo"))
5197
+ return "image";
5198
+ if (lk.includes("color") || lk.includes("colour"))
5199
+ return "color";
5200
+ if (lk.includes("description") || lk.includes("content") || lk.includes("body") || lk.includes("bio"))
5201
+ return "textarea";
5202
+ if (lk === "created_at" || lk === "updated_at" || lk.endsWith("_at") || lk.endsWith("_date"))
5203
+ return "date";
5204
+ return "text";
5205
+ }
5206
+ return "text";
5207
+ }
5208
+ function inferFromOpenAPI(spec, options = {}) {
5209
+ const { primaryKey = "id", include, exclude } = options;
5210
+ const schemas = spec.components?.schemas ?? {};
5211
+ const resources = [];
5212
+ const resourcePaths = detectResourcePaths(spec);
5213
+ for (const [schemaName, schemaObj] of Object.entries(schemas)) {
5214
+ if (include && !include.includes(schemaName))
5215
+ continue;
5216
+ if (exclude && exclude.includes(schemaName))
5217
+ continue;
5218
+ const resolved = resolveSchema(schemaObj, spec);
5219
+ if (!resolved || resolved.type !== "object" || !resolved.properties)
5220
+ continue;
5221
+ const fields = [];
5222
+ const requiredFields = new Set(resolved.required ?? []);
5223
+ for (const [propName, propSchemaOrRef] of Object.entries(resolved.properties)) {
5224
+ const propSchema = resolveSchema(propSchemaOrRef, spec);
5225
+ if (!propSchema)
5226
+ continue;
5227
+ const fieldType = mapOpenAPITypeToFieldType(propName, propSchema, spec);
5228
+ let relatedResource = null;
5229
+ if (isRef(propSchemaOrRef)) {
5230
+ const refName = propSchemaOrRef.$ref.split("/").pop() ?? "";
5231
+ relatedResource = refName.toLowerCase() + "s";
5232
+ } else if (propName.endsWith("_id") || propName.endsWith("Id")) {
5233
+ const base = propName.replace(/_id$|Id$/, "");
5234
+ relatedResource = base.endsWith("s") ? base : base + "s";
5235
+ }
5236
+ const field = {
5237
+ key: propName,
5238
+ label: humanize(propName),
5239
+ type: relatedResource ? "relation" : fieldType,
5240
+ required: requiredFields.has(propName),
5241
+ sortable: ["text", "number", "date"].includes(fieldType),
5242
+ searchable: ["text", "email"].includes(fieldType),
5243
+ showInList: propName !== primaryKey && fieldType !== "textarea" && fieldType !== "json",
5244
+ showInForm: propName !== primaryKey
5245
+ };
5246
+ if (relatedResource) {
5247
+ field.resource = relatedResource;
5248
+ field.optionLabel = "name";
5249
+ field.optionValue = "id";
5250
+ }
5251
+ if (propSchema.enum) {
5252
+ field.options = propSchema.enum.map((v) => ({
5253
+ label: String(v).charAt(0).toUpperCase() + String(v).slice(1),
5254
+ value: v
5255
+ }));
5256
+ }
5257
+ fields.push(field);
5258
+ }
5259
+ fields.sort((a, b) => {
5260
+ if (a.key === primaryKey)
5261
+ return -1;
5262
+ if (b.key === primaryKey)
5263
+ return 1;
5264
+ return 0;
5265
+ });
5266
+ const baseName = schemaName.toLowerCase();
5267
+ const resourceName = baseName.endsWith("s") ? baseName : baseName + "s";
5268
+ const pathInfo = resourcePaths.get(baseName) ?? resourcePaths.get(resourceName);
5269
+ resources.push({
5270
+ name: resourceName,
5271
+ label: schemaName,
5272
+ primaryKey,
5273
+ fields,
5274
+ canCreate: pathInfo?.hasPost ?? true,
5275
+ canEdit: pathInfo?.hasPut ?? true,
5276
+ canDelete: pathInfo?.hasDelete ?? true,
5277
+ canShow: pathInfo?.hasGet ?? true
5278
+ });
5279
+ }
5280
+ return resources;
5281
+ }
5282
+ function detectResourcePaths(spec) {
5283
+ const result = new Map;
5284
+ if (!spec.paths)
5285
+ return result;
5286
+ for (const [path, methods] of Object.entries(spec.paths)) {
5287
+ const segments = path.split("/").filter(Boolean);
5288
+ const resourceSegment = segments.find((s) => !s.startsWith("{") && s !== "api" && s !== "v1" && s !== "v2");
5289
+ if (!resourceSegment)
5290
+ continue;
5291
+ const name = resourceSegment.toLowerCase();
5292
+ const existing = result.get(name) ?? { hasGet: false, hasPost: false, hasPut: false, hasDelete: false };
5293
+ if (methods.get)
5294
+ existing.hasGet = true;
5295
+ if (methods.post)
5296
+ existing.hasPost = true;
5297
+ if (methods.put || methods.patch)
5298
+ existing.hasPut = true;
5299
+ if (methods.delete)
5300
+ existing.hasDelete = true;
5301
+ result.set(name, existing);
5302
+ }
5303
+ return result;
5304
+ }
5305
+ function humanize(key) {
5306
+ return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase());
5307
+ }
5308
+ // ../core/src/inferencer-graphql.ts
5309
+ var GRAPHQL_INTROSPECTION_QUERY = `query IntrospectionQuery {
5310
+ __schema {
5311
+ queryType { name }
5312
+ mutationType { name }
5313
+ subscriptionType { name }
5314
+ types {
5315
+ kind
5316
+ name
5317
+ description
5318
+ fields(includeDeprecated: true) {
5319
+ name
5320
+ description
5321
+ type {
5322
+ kind
5323
+ name
5324
+ ofType {
5325
+ kind
5326
+ name
5327
+ ofType {
5328
+ kind
5329
+ name
5330
+ ofType {
5331
+ kind
5332
+ name
5333
+ ofType {
5334
+ kind
5335
+ name
5336
+ }
5337
+ }
5338
+ }
5339
+ }
5340
+ }
5341
+ }
5342
+ enumValues(includeDeprecated: true) {
5343
+ name
5344
+ description
5345
+ }
5346
+ }
5347
+ }
5348
+ }`;
5349
+ function unwrapType(typeRef) {
5350
+ let current = typeRef;
5351
+ let isRequired = false;
5352
+ let isList = false;
5353
+ if (current.kind === "NON_NULL") {
5354
+ isRequired = true;
5355
+ if (current.ofType)
5356
+ current = current.ofType;
5357
+ }
5358
+ if (current.kind === "LIST") {
5359
+ isList = true;
5360
+ if (current.ofType) {
5361
+ current = current.ofType;
5362
+ if (current.kind === "NON_NULL") {
5363
+ if (current.ofType)
5364
+ current = current.ofType;
5365
+ }
5366
+ }
5367
+ }
5368
+ return {
5369
+ baseName: current.name ?? "String",
5370
+ kind: current.kind,
5371
+ isList,
5372
+ isRequired
5373
+ };
5374
+ }
5375
+ function mapGraphQLTypeToFieldType(key, typeInfo, enumMap) {
5376
+ const { baseName, isList } = typeInfo;
5377
+ const lk = key.toLowerCase();
5378
+ if (enumMap.has(baseName))
5379
+ return "select";
5380
+ if (isList) {
5381
+ if (lk.includes("image") || lk.includes("photo") || lk.includes("avatar") || lk.includes("picture")) {
5382
+ return "images";
5383
+ }
5384
+ return "tags";
5385
+ }
5386
+ switch (baseName) {
5387
+ case "Int":
5388
+ case "Float":
5389
+ case "Decimal":
5390
+ case "BigInt":
5391
+ return "number";
5392
+ case "Boolean":
5393
+ return "boolean";
5394
+ case "Date":
5395
+ case "DateTime":
5396
+ case "Time":
5397
+ case "Timestamp":
5398
+ return "date";
5399
+ case "JSON":
5400
+ case "JsonObject":
5401
+ case "JSONObject":
5402
+ case "Json":
5403
+ return "json";
5404
+ case "ID":
5405
+ return "text";
5406
+ case "String":
5407
+ default:
5408
+ break;
5409
+ }
5410
+ if (lk.includes("email"))
5411
+ return "email";
5412
+ if (lk.includes("phone") || lk.includes("tel") || lk.includes("mobile"))
5413
+ return "phone";
5414
+ if (lk.includes("url") || lk.includes("link") || lk.includes("website"))
5415
+ return "url";
5416
+ if (lk.includes("avatar") || lk.includes("image") || lk.includes("photo") || lk.includes("thumbnail") || lk.includes("logo"))
5417
+ return "image";
5418
+ if (lk.includes("color") || lk.includes("colour"))
5419
+ return "color";
5420
+ if (lk.includes("description") || lk.includes("content") || lk.includes("body") || lk.includes("bio") || lk.includes("summary"))
5421
+ return "textarea";
5422
+ if (lk === "created_at" || lk === "updated_at" || lk.endsWith("_at") || lk.endsWith("_date") || /\bdate\b/.test(lk))
5423
+ return "date";
5424
+ return "text";
5425
+ }
5426
+ function parseGraphQLSDL(sdl) {
5427
+ const types = [];
5428
+ const queryType = { name: "Query" };
5429
+ const mutationType = { name: "Mutation" };
5430
+ const cleanSDL = sdl.replace(/#[^\n\r]*/g, "");
5431
+ const enumRegex = /enum\s+([A-Za-z0-9_]+)\s*\{([^}]+)\}/g;
5432
+ let match;
5433
+ while ((match = enumRegex.exec(cleanSDL)) !== null) {
5434
+ const enumName = match[1];
5435
+ const rawValues = match[2].split(/\s+/).filter(Boolean);
5436
+ types.push({
5437
+ kind: "ENUM",
5438
+ name: enumName,
5439
+ enumValues: rawValues.map((v) => ({ name: v }))
5440
+ });
5441
+ }
5442
+ const typeRegex = /type\s+([A-Za-z0-9_]+)\s*(?:implements\s+[A-Za-z0-9_&,\s]+)?\s*\{([^}]+)\}/g;
5443
+ while ((match = typeRegex.exec(cleanSDL)) !== null) {
5444
+ const typeName = match[1];
5445
+ const fieldsBody = match[2];
5446
+ const fieldLines = fieldsBody.split(`
5447
+ `).map((l) => l.trim()).filter(Boolean);
5448
+ const fields = [];
5449
+ for (const line of fieldLines) {
5450
+ const fieldMatch = /^([A-Za-z0-9_]+)(?:\([^)]*\))?\s*:\s*([[\]A-Za-z0-9_!]+)/.exec(line);
5451
+ if (!fieldMatch)
5452
+ continue;
5453
+ const fName = fieldMatch[1];
5454
+ const rawType = fieldMatch[2];
5455
+ const isNonNullable = rawType.endsWith("!");
5456
+ const cleanType = isNonNullable ? rawType.slice(0, -1) : rawType;
5457
+ const isList = cleanType.startsWith("[") && cleanType.endsWith("]");
5458
+ const innerType = isList ? cleanType.slice(1, -1).replace(/!$/, "") : cleanType;
5459
+ let typeRef = {
5460
+ kind: "SCALAR",
5461
+ name: innerType
5462
+ };
5463
+ if (isList) {
5464
+ typeRef = {
5465
+ kind: "LIST",
5466
+ ofType: typeRef
5467
+ };
5468
+ }
5469
+ if (isNonNullable) {
5470
+ typeRef = {
5471
+ kind: "NON_NULL",
5472
+ ofType: typeRef
5473
+ };
5474
+ }
5475
+ fields.push({
5476
+ name: fName,
5477
+ type: typeRef
5478
+ });
5479
+ }
5480
+ types.push({
5481
+ kind: "OBJECT",
5482
+ name: typeName,
5483
+ fields
5484
+ });
5485
+ }
5486
+ return {
5487
+ queryType,
5488
+ mutationType,
5489
+ types
5490
+ };
5491
+ }
5492
+ function inferFromGraphQL(schemaOrIntrospection, options = {}) {
5493
+ const { primaryKey = "id", include, exclude } = options;
5494
+ let schemaBody;
5495
+ if (typeof schemaOrIntrospection === "string") {
5496
+ schemaBody = parseGraphQLSDL(schemaOrIntrospection);
5497
+ } else if (schemaOrIntrospection && typeof schemaOrIntrospection === "object" && "data" in schemaOrIntrospection && schemaOrIntrospection.data?.__schema) {
5498
+ schemaBody = schemaOrIntrospection.data.__schema;
5499
+ } else if (schemaOrIntrospection && typeof schemaOrIntrospection === "object" && "__schema" in schemaOrIntrospection && schemaOrIntrospection.__schema) {
5500
+ schemaBody = schemaOrIntrospection.__schema;
5501
+ } else if (schemaOrIntrospection && typeof schemaOrIntrospection === "object" && "types" in schemaOrIntrospection && Array.isArray(schemaOrIntrospection.types)) {
5502
+ schemaBody = schemaOrIntrospection;
5503
+ } else {
5504
+ return [];
5505
+ }
5506
+ const types = schemaBody.types ?? [];
5507
+ const queryTypeName = schemaBody.queryType?.name ?? "Query";
5508
+ const mutationTypeName = schemaBody.mutationType?.name ?? "Mutation";
5509
+ const subscriptionTypeName = schemaBody.subscriptionType?.name ?? "Subscription";
5510
+ const enumMap = new Map;
5511
+ for (const t of types) {
5512
+ if (t.kind === "ENUM" && t.name) {
5513
+ const values = (t.enumValues ?? []).map((ev) => ev.name);
5514
+ enumMap.set(t.name, values);
5515
+ }
5516
+ }
5517
+ const objectTypeMap = new Map;
5518
+ for (const t of types) {
5519
+ if (t.kind === "OBJECT" && t.name) {
5520
+ objectTypeMap.set(t.name, t);
5521
+ }
5522
+ }
5523
+ const queryFields = new Set;
5524
+ const queryType = objectTypeMap.get(queryTypeName);
5525
+ if (queryType?.fields) {
5526
+ for (const f of queryType.fields)
5527
+ queryFields.add(f.name.toLowerCase());
5528
+ }
5529
+ const mutationFields = new Set;
5530
+ const mutationType = objectTypeMap.get(mutationTypeName);
5531
+ if (mutationType?.fields) {
5532
+ for (const f of mutationType.fields)
5533
+ mutationFields.add(f.name.toLowerCase());
5534
+ }
5535
+ const resources = [];
5536
+ for (const typeObj of types) {
5537
+ const typeName = typeObj.name;
5538
+ if (!typeName)
5539
+ continue;
5540
+ if (typeName.startsWith("__") || typeName === queryTypeName || typeName === mutationTypeName || typeName === subscriptionTypeName || typeObj.kind !== "OBJECT" || !typeObj.fields || typeObj.fields.length === 0) {
5541
+ continue;
5542
+ }
5543
+ if (include && !include.includes(typeName))
5544
+ continue;
5545
+ if (exclude && exclude.includes(typeName))
5546
+ continue;
5547
+ const baseName = typeName.toLowerCase();
5548
+ const resourceName = baseName.endsWith("s") ? baseName : baseName + "s";
5549
+ const fields = [];
5550
+ for (const fieldDesc of typeObj.fields) {
5551
+ const fieldName = fieldDesc.name;
5552
+ const typeInfo = unwrapType(fieldDesc.type);
5553
+ let fieldType = mapGraphQLTypeToFieldType(fieldName, typeInfo, enumMap);
5554
+ let relatedResource = null;
5555
+ if (objectTypeMap.has(typeInfo.baseName) && typeInfo.baseName !== typeName) {
5556
+ const target = typeInfo.baseName.toLowerCase();
5557
+ relatedResource = target.endsWith("s") ? target : target + "s";
5558
+ fieldType = "relation";
5559
+ } else if (fieldName.endsWith("_id") || fieldName.endsWith("Id")) {
5560
+ const base = fieldName.replace(/_id$|Id$/, "");
5561
+ relatedResource = base.endsWith("s") ? base : base + "s";
5562
+ fieldType = "relation";
5563
+ }
5564
+ const field = {
5565
+ key: fieldName,
5566
+ label: humanize2(fieldName),
5567
+ type: relatedResource ? "relation" : fieldType,
5568
+ required: typeInfo.isRequired,
5569
+ sortable: ["text", "number", "date"].includes(fieldType),
5570
+ searchable: ["text", "email"].includes(fieldType),
5571
+ showInList: fieldName !== primaryKey && fieldType !== "textarea" && fieldType !== "json",
5572
+ showInForm: fieldName !== primaryKey
5573
+ };
5574
+ if (relatedResource) {
5575
+ field.resource = relatedResource;
5576
+ field.optionLabel = "name";
5577
+ field.optionValue = "id";
5578
+ }
5579
+ if (enumMap.has(typeInfo.baseName)) {
5580
+ const enumValues = enumMap.get(typeInfo.baseName) ?? [];
5581
+ field.options = enumValues.map((val) => ({
5582
+ label: humanize2(val),
5583
+ value: val
5584
+ }));
5585
+ }
5586
+ fields.push(field);
5587
+ }
5588
+ fields.sort((a, b) => {
5589
+ if (a.key === primaryKey)
5590
+ return -1;
5591
+ if (b.key === primaryKey)
5592
+ return 1;
5593
+ return a.key.localeCompare(b.key);
5594
+ });
5595
+ const canShow = queryFields.size === 0 || queryFields.has(baseName) || queryFields.has(resourceName) || queryFields.has(`get${baseName}`) || queryFields.has(`${baseName}byid`) || queryFields.has(`find${baseName}`);
5596
+ const canCreate = mutationFields.size === 0 || mutationFields.has(`create${baseName}`) || mutationFields.has(`insert${baseName}`) || mutationFields.has(`add${baseName}`) || mutationFields.has(`insert_${resourceName}`) || mutationFields.has(`create_${baseName}`);
5597
+ const canEdit = mutationFields.size === 0 || mutationFields.has(`update${baseName}`) || mutationFields.has(`edit${baseName}`) || mutationFields.has(`update_${resourceName}`) || mutationFields.has(`update_${baseName}`) || mutationFields.has(`save${baseName}`);
5598
+ const canDelete = mutationFields.size === 0 || mutationFields.has(`delete${baseName}`) || mutationFields.has(`remove${baseName}`) || mutationFields.has(`delete_${resourceName}`) || mutationFields.has(`delete_${baseName}`);
5599
+ resources.push({
5600
+ name: resourceName,
5601
+ label: typeName,
5602
+ primaryKey,
5603
+ fields,
5604
+ canCreate,
5605
+ canEdit,
5606
+ canDelete,
5607
+ canShow
5608
+ });
5609
+ }
5610
+ return resources;
5611
+ }
5612
+ function humanize2(key) {
5613
+ return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase());
5614
+ }
5615
+
5616
+ // ../core/src/inferencer.ts
5617
+ var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
5618
+ var URL_RE = /^https?:\/\//;
5619
+ var IMAGE_RE = /\.(png|jpe?g|gif|svg|webp|avif|ico)(\?.*)?$/i;
5620
+ var PHONE_RE = /^\+?[\d\s\-()]{7,}$/;
5621
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2})?/;
5622
+ var COLOR_RE = /^#([0-9a-fA-F]{3,8})$/;
5623
+ function inferFieldType(key, value) {
5624
+ if (value === null || value === undefined)
5625
+ return "text";
5626
+ if (typeof value === "boolean")
5627
+ return "boolean";
5628
+ if (typeof value === "number")
5629
+ return "number";
5630
+ if (Array.isArray(value)) {
5631
+ if (value.length > 0 && typeof value[0] === "string") {
5632
+ if (value.every((v) => typeof v === "string" && IMAGE_RE.test(v)))
5633
+ return "images";
5634
+ return "tags";
5635
+ }
5636
+ if (value.length > 0 && typeof value[0] === "number")
5637
+ return "tags";
5638
+ return "json";
5639
+ }
5640
+ if (typeof value === "object")
5641
+ return "json";
5642
+ if (typeof value === "string") {
5643
+ if (COLOR_RE.test(value))
5644
+ return "color";
5645
+ if (EMAIL_RE.test(value))
5646
+ return "email";
5647
+ if (IMAGE_RE.test(value))
5648
+ return "image";
5649
+ if (URL_RE.test(value))
5650
+ return "url";
5651
+ if (PHONE_RE.test(value))
5652
+ return "phone";
5653
+ if (ISO_DATE_RE.test(value))
5654
+ return "date";
5655
+ if (value.length > 200)
5656
+ return "textarea";
5657
+ const lk = key.toLowerCase();
5658
+ if (lk.includes("email"))
5659
+ return "email";
5660
+ if (lk.includes("phone") || lk.includes("tel") || lk.includes("mobile"))
5661
+ return "phone";
5662
+ if (lk.includes("url") || lk.includes("link") || lk.includes("website"))
5663
+ return "url";
5664
+ if (lk.includes("avatar") || lk.includes("image") || lk.includes("photo") || lk.includes("thumbnail") || lk.includes("logo"))
5665
+ return "image";
5666
+ if (lk.includes("color") || lk.includes("colour"))
5667
+ return "color";
5668
+ if (lk.includes("description") || lk.includes("content") || lk.includes("body") || lk.includes("bio") || lk.includes("summary"))
5669
+ return "textarea";
5670
+ if (lk === "created_at" || lk === "updated_at" || lk.endsWith("_at") || lk.endsWith("_date") || /\bdate\b/.test(lk))
5671
+ return "date";
5672
+ return "text";
5673
+ }
5674
+ return "text";
5675
+ }
5676
+ var RELATION_SUFFIXES = ["_id", "Id", "_ID"];
5677
+ function isLikelyRelation(key) {
5678
+ for (const suffix of RELATION_SUFFIXES) {
5679
+ if (key.endsWith(suffix)) {
5680
+ const resource = key.slice(0, -suffix.length);
5681
+ return resource.endsWith("s") ? resource : resource + "s";
5682
+ }
5683
+ }
5684
+ return null;
5685
+ }
5686
+ function inferResource(resourceName, sampleData, options = {}) {
5687
+ const primaryKey = options.primaryKey ?? "id";
5688
+ const label = options.label ?? capitalize(resourceName);
5689
+ if (!sampleData.length) {
5690
+ const emptyResource = { name: resourceName, label, fields: [], primaryKey };
5691
+ return {
5692
+ fields: [],
5693
+ resource: emptyResource,
5694
+ code: `// No data available to infer fields for "${resourceName}".`,
5695
+ typeboxCode: generateTypeBoxSchemaCode(emptyResource),
5696
+ componentCode: {
5697
+ list: generateListPageCode(emptyResource),
5698
+ create: generateCreatePageCode(emptyResource),
5699
+ edit: generateEditPageCode(emptyResource),
5700
+ show: generateShowPageCode(emptyResource)
5701
+ }
5702
+ };
5703
+ }
5704
+ const keySet = new Set;
5705
+ for (const row of sampleData) {
5706
+ for (const k of Object.keys(row))
5707
+ keySet.add(k);
5708
+ }
5709
+ const fields = [];
5710
+ for (const key of keySet) {
5711
+ const values = sampleData.map((r) => r[key]).filter((v) => v !== null && v !== undefined);
5712
+ const sampleValue = values[0];
5713
+ let inferredType = inferFieldType(key, sampleValue);
5714
+ const typeCounts = new Map;
5715
+ for (const v of values) {
5716
+ const t = inferFieldType(key, v);
5717
+ typeCounts.set(t, (typeCounts.get(t) ?? 0) + 1);
5718
+ }
5719
+ let maxCount = 0;
5720
+ for (const [t, count] of typeCounts) {
5721
+ if (count > maxCount) {
5722
+ maxCount = count;
5723
+ inferredType = t;
5724
+ }
5725
+ }
5726
+ const relatedResource = isLikelyRelation(key);
5727
+ const uniqueStrings = new Set(values.filter((v) => typeof v === "string"));
5728
+ const isSelect = inferredType === "text" && uniqueStrings.size > 1 && uniqueStrings.size <= 10 && values.length >= 5;
5729
+ const field = {
5730
+ key,
5731
+ label: humanize3(key),
5732
+ type: relatedResource ? "relation" : isSelect ? "select" : inferredType,
5733
+ sortable: inferredType === "text" || inferredType === "number" || inferredType === "date",
5734
+ searchable: inferredType === "text" || inferredType === "email",
5735
+ showInList: key !== primaryKey && inferredType !== "textarea" && inferredType !== "json" && inferredType !== "richtext",
5736
+ showInForm: key !== primaryKey
5737
+ };
5738
+ if (relatedResource) {
5739
+ field.resource = relatedResource;
5740
+ field.optionLabel = "name";
5741
+ field.optionValue = "id";
5742
+ }
5743
+ if (isSelect) {
5744
+ field.options = [...uniqueStrings].map((v) => ({ label: capitalize(v), value: v }));
5745
+ }
5746
+ fields.push(field);
5747
+ }
5748
+ fields.sort((a, b) => {
5749
+ if (a.key === primaryKey)
5750
+ return -1;
5751
+ if (b.key === primaryKey)
5752
+ return 1;
5753
+ return a.key.localeCompare(b.key);
5754
+ });
5755
+ const resource = {
5756
+ name: resourceName,
5757
+ label,
5758
+ primaryKey,
5759
+ fields,
5760
+ canCreate: true,
5761
+ canEdit: true,
5762
+ canDelete: true,
5763
+ canShow: true
5764
+ };
5765
+ const code = generateCode(resource);
5766
+ const typeboxCode = generateTypeBoxSchemaCode(resource);
5767
+ const componentCode = {
5768
+ list: generateListPageCode(resource),
5769
+ create: generateCreatePageCode(resource),
5770
+ edit: generateEditPageCode(resource),
5771
+ show: generateShowPageCode(resource)
5772
+ };
5773
+ return { fields, resource, code, typeboxCode, componentCode };
5774
+ }
5775
+ function generateCode(resource) {
5776
+ const esc = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
5777
+ const fieldsCode = resource.fields.map((f) => {
5778
+ const lines = [
5779
+ ` { key: '${esc(f.key)}', label: '${esc(f.label)}', type: '${f.type}'`
5780
+ ];
5781
+ if (f.sortable)
5782
+ lines.push(` sortable: true`);
5783
+ if (f.searchable)
5784
+ lines.push(` searchable: true`);
5785
+ if (f.showInList === false)
5786
+ lines.push(` showInList: false`);
5787
+ if (f.showInForm === false)
5788
+ lines.push(` showInForm: false`);
5789
+ if (f.resource) {
5790
+ lines.push(` resource: '${f.resource}'`);
5791
+ lines.push(` optionLabel: '${f.optionLabel}'`);
5792
+ lines.push(` optionValue: '${f.optionValue}'`);
5793
+ }
5794
+ if (f.options) {
5795
+ lines.push(` options: ${JSON.stringify(f.options)}`);
5796
+ }
5797
+ return lines.join(`,
5798
+ `) + " }";
5799
+ }).join(`,
5800
+ `);
5801
+ return `import type { ResourceDefinition } from '@svadmin/core';
5802
+
5803
+ export const ${resource.name}Resource: ResourceDefinition = {
5804
+ name: '${resource.name}',
5805
+ label: '${resource.label}',
5806
+ primaryKey: '${resource.primaryKey ?? "id"}',
5807
+ canCreate: true,
5808
+ canEdit: true,
5809
+ canDelete: true,
5810
+ canShow: true,
5811
+ fields: [
5812
+ ${fieldsCode}
5813
+ ],
5814
+ };
5815
+ `;
5816
+ }
5817
+ function generateListPageCode(resource) {
5818
+ return `<script lang="ts">
5819
+ import { ListPage, AutoTable } from '@svadmin/ui';
5820
+ </script>
5821
+
5822
+ <ListPage resourceName="${resource.name}">
5823
+ <AutoTable resourceName="${resource.name}" />
5824
+ </ListPage>
5825
+ `;
5826
+ }
5827
+ function generateCreatePageCode(resource) {
5828
+ return `<script lang="ts">
5829
+ import { CreatePage, AutoForm } from '@svadmin/ui';
5830
+ </script>
5831
+
5832
+ <CreatePage resourceName="${resource.name}">
5833
+ <AutoForm resourceName="${resource.name}" mode="create" />
5834
+ </CreatePage>
5835
+ `;
5836
+ }
5837
+ function generateEditPageCode(resource) {
5838
+ return `<script lang="ts">
5839
+ import { EditPage, AutoForm } from '@svadmin/ui';
5840
+
5841
+ interface Props {
5842
+ id?: string | number;
5843
+ }
5844
+
5845
+ let { id }: Props = $props();
5846
+ </script>
5847
+
5848
+ <EditPage resourceName="${resource.name}" {id}>
5849
+ <AutoForm resourceName="${resource.name}" {id} mode="edit" />
5850
+ </EditPage>
5851
+ `;
5852
+ }
5853
+ function generateShowPageCode(resource) {
5854
+ return `<script lang="ts">
5855
+ import { ShowPage, AutoForm } from '@svadmin/ui';
5856
+
5857
+ interface Props {
5858
+ id?: string | number;
5859
+ }
5860
+
5861
+ let { id }: Props = $props();
5862
+ </script>
5863
+
5864
+ <ShowPage resourceName="${resource.name}" {id}>
5865
+ <AutoForm resourceName="${resource.name}" {id} mode="show" />
5866
+ </ShowPage>
5867
+ `;
5868
+ }
5869
+ function generateResourceCode(resource) {
5870
+ return generateCode(resource);
5871
+ }
5872
+ function generateTypeBoxSchemaCode(resource) {
5873
+ const esc = (s) => s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
5874
+ const schemaProps = resource.fields.map((f) => {
5875
+ let typeDef;
5876
+ const isRequired = f.required === true;
5877
+ switch (f.type) {
5878
+ case "number":
5879
+ typeDef = "Type.Number()";
5880
+ break;
5881
+ case "boolean":
5882
+ typeDef = "Type.Boolean()";
5883
+ break;
5884
+ case "date":
5885
+ typeDef = "Type.String({ format: 'date-time' })";
5886
+ break;
5887
+ case "email":
5888
+ typeDef = "Type.String({ format: 'email' })";
5889
+ break;
5890
+ case "url":
5891
+ typeDef = "Type.String({ format: 'uri' })";
5892
+ break;
5893
+ case "phone":
5894
+ typeDef = "Type.String({ pattern: '^\\\\+?[\\\\d\\\\s\\\\-()]{7,}$' })";
5895
+ break;
5896
+ case "tags":
5897
+ typeDef = "Type.Array(Type.String())";
5898
+ break;
5899
+ case "images":
5900
+ typeDef = "Type.Array(Type.String({ format: 'uri' }))";
5901
+ break;
5902
+ case "json":
5903
+ typeDef = "Type.Record(Type.String(), Type.Unknown())";
5904
+ break;
5905
+ case "select":
5906
+ if (f.options && f.options.length > 0) {
5907
+ const literals = f.options.map((o) => `Type.Literal('${esc(String(o.value))}')`).join(", ");
5908
+ typeDef = `Type.Union([${literals}])`;
5909
+ } else {
5910
+ typeDef = "Type.String()";
5911
+ }
5912
+ break;
5913
+ case "relation":
5914
+ typeDef = "Type.Union([Type.String(), Type.Number()])";
5915
+ break;
5916
+ case "textarea":
5917
+ case "richtext":
5918
+ case "text":
5919
+ default:
5920
+ typeDef = "Type.String()";
5921
+ break;
5922
+ }
5923
+ if (!isRequired && f.key !== (resource.primaryKey ?? "id")) {
5924
+ typeDef = `Type.Optional(${typeDef})`;
5925
+ }
5926
+ return ` ${f.key}: ${typeDef},`;
5927
+ }).join(`
5928
+ `);
5929
+ const baseName = resource.name.endsWith("s") ? resource.name.slice(0, -1) : resource.name;
5930
+ const typeName = capitalize(baseName);
5931
+ return `import { Type, type Static } from '@sinclair/typebox';
5932
+
5933
+ export const ${typeName}Schema = Type.Object({
5934
+ ${schemaProps}
5935
+ });
5936
+
5937
+ export type ${typeName} = Static<typeof ${typeName}Schema>;
5938
+ `;
5939
+ }
5940
+ function capitalize(s) {
5941
+ return s.charAt(0).toUpperCase() + s.slice(1);
5942
+ }
5943
+ function humanize3(key) {
5944
+ return key.replace(/_/g, " ").replace(/([a-z])([A-Z])/g, "$1 $2").replace(/\b\w/g, (c) => c.toUpperCase());
5945
+ }
5946
+ function generateResourceBundle(resource) {
5947
+ return {
5948
+ fields: resource.fields,
5949
+ resource,
5950
+ code: generateResourceCode(resource),
5951
+ typeboxCode: generateTypeBoxSchemaCode(resource),
5952
+ componentCode: {
5953
+ list: generateListPageCode(resource),
5954
+ create: generateCreatePageCode(resource),
5955
+ edit: generateEditPageCode(resource),
5956
+ show: generateShowPageCode(resource)
5957
+ }
5958
+ };
5959
+ }
5960
+
5961
+ // src/infer-command.ts
5962
+ function parseInferArguments(args) {
5963
+ const options = {
5964
+ headers: {},
5965
+ primaryKey: "id",
5966
+ method: "GET",
5967
+ format: "all"
5968
+ };
5969
+ for (let i = 0;i < args.length; i++) {
5970
+ const arg = args[i];
5971
+ if (arg === "--write" || arg === "-w") {
5972
+ options.write = true;
5973
+ } else if (arg === "--dry-run") {
5974
+ options.write = false;
5975
+ } else if (arg === "--url" || arg === "-u") {
5976
+ options.url = args[++i];
5977
+ } else if (arg.startsWith("--url=")) {
5978
+ options.url = arg.slice(6);
5979
+ } else if (arg === "--file" || arg === "-f") {
5980
+ options.file = args[++i];
5981
+ } else if (arg.startsWith("--file=")) {
5982
+ options.file = arg.slice(7);
5983
+ } else if (arg === "--type" || arg === "-t") {
5984
+ options.type = args[++i];
5985
+ } else if (arg.startsWith("--type=")) {
5986
+ options.type = arg.slice(7);
5987
+ } else if (arg === "--resource" || arg === "-r") {
5988
+ options.resource = args[++i];
5989
+ } else if (arg.startsWith("--resource=")) {
5990
+ options.resource = arg.slice(11);
5991
+ } else if (arg === "--out-dir" || arg === "-o" || arg === "--output") {
5992
+ options.outDir = args[++i];
5993
+ } else if (arg.startsWith("--out-dir=")) {
5994
+ options.outDir = arg.slice(10);
5995
+ } else if (arg.startsWith("--output=")) {
5996
+ options.outDir = arg.slice(9);
5997
+ } else if (arg === "--primary-key" || arg === "-k") {
5998
+ options.primaryKey = args[++i];
5999
+ } else if (arg.startsWith("--primary-key=")) {
6000
+ options.primaryKey = arg.slice(14);
6001
+ } else if (arg === "--header" || arg === "-H") {
6002
+ const headerLine = args[++i] ?? "";
6003
+ const colonIndex = headerLine.indexOf(":");
6004
+ if (colonIndex > 0) {
6005
+ const key = headerLine.slice(0, colonIndex).trim();
6006
+ const value = headerLine.slice(colonIndex + 1).trim();
6007
+ if (!options.headers)
6008
+ options.headers = {};
6009
+ options.headers[key] = value;
6010
+ }
6011
+ } else if (arg.startsWith("--header=")) {
6012
+ const headerLine = arg.slice(9);
6013
+ const colonIndex = headerLine.indexOf(":");
6014
+ if (colonIndex > 0) {
6015
+ const key = headerLine.slice(0, colonIndex).trim();
6016
+ const value = headerLine.slice(colonIndex + 1).trim();
6017
+ if (!options.headers)
6018
+ options.headers = {};
6019
+ options.headers[key] = value;
6020
+ }
6021
+ } else if (arg === "--method" || arg === "-m") {
6022
+ options.method = (args[++i] ?? "GET").toUpperCase();
6023
+ } else if (arg.startsWith("--method=")) {
6024
+ options.method = arg.slice(9).toUpperCase();
6025
+ } else if (arg === "--body" || arg === "-b") {
6026
+ options.body = args[++i];
6027
+ } else if (arg.startsWith("--body=")) {
6028
+ options.body = arg.slice(7);
6029
+ } else if (arg === "--format") {
6030
+ options.format = args[++i];
6031
+ } else if (arg.startsWith("--format=")) {
6032
+ options.format = arg.slice(9);
6033
+ } else if (arg === "--help" || arg === "-h") {
6034
+ printInferHelp();
6035
+ process.exit(0);
6036
+ } else if (!arg.startsWith("-") && !options.url && !options.file) {
6037
+ if (arg.startsWith("http://") || arg.startsWith("https://")) {
6038
+ options.url = arg;
6039
+ } else if (fs.existsSync(arg)) {
6040
+ options.file = arg;
6041
+ } else {
6042
+ options.resource = arg;
6043
+ }
6044
+ }
6045
+ }
6046
+ return options;
6047
+ }
6048
+ function printInferHelp() {
6049
+ console.log(`
6050
+ ${import_picocolors.default.bold("svadmin infer")} — Automatically infer ResourceDefinitions, TypeBox Schemas, and Svelte 5 CRUD components from REST/GraphQL APIs or OpenAPI specs.
6051
+
6052
+ ${import_picocolors.default.bold("USAGE:")}
6053
+ svadmin infer [OPTIONS]
6054
+ svadmin infer <url|file> [OPTIONS]
6055
+
6056
+ ${import_picocolors.default.bold("OPTIONS:")}
6057
+ -u, --url <url> REST API endpoint, OpenAPI schema URL, or GraphQL endpoint
6058
+ -f, --file <path> Local OpenAPI JSON/YAML, GraphQL schema (.graphql/JSON), or sample JSON data file
6059
+ -t, --type <type> Explicit source type: rest | openapi | graphql | auto (default: auto)
6060
+ -r, --resource <name> Target resource name filter or explicit resource name for sample data
6061
+ -o, --out-dir <dir> Target directory for generated code (e.g. src/resources)
6062
+ -k, --primary-key <key> Primary key field name (default: id)
6063
+ -H, --header <key:value> Custom HTTP header for fetch requests (can be specified multiple times)
6064
+ -m, --method <GET|POST> HTTP method for REST fetch (default: GET)
6065
+ -b, --body <json> HTTP request body for POST requests
6066
+ -w, --write Write generated files to target directory (default is dry-run when --out-dir is set)
6067
+ --format <all|resource|typebox|components>
6068
+ Output format (default: all)
6069
+ -h, --help Show this help message
6070
+
6071
+ ${import_picocolors.default.bold("EXAMPLES:")}
6072
+ svadmin infer --url https://api.example.com/openapi.json --out-dir src/resources --write
6073
+ svadmin infer --url https://api.example.com/graphql --out-dir src/resources --write
6074
+ svadmin infer --url https://api.example.com/api/v1/posts --resource posts
6075
+ svadmin infer --file schema.graphql --out-dir src/resources --write
6076
+ svadmin infer --file sample-posts.json --resource posts --out-dir src/resources --write
6077
+ `);
6078
+ }
6079
+ async function loadSourceData(options, customFetch = fetch) {
6080
+ if (options.file) {
6081
+ const filePath = path.resolve(process.cwd(), options.file);
6082
+ if (!fs.existsSync(filePath)) {
6083
+ throw new Error(`File not found: ${filePath}`);
6084
+ }
6085
+ const content = fs.readFileSync(filePath, "utf-8");
6086
+ const ext = path.extname(filePath).toLowerCase();
6087
+ const baseName = path.basename(filePath, ext);
6088
+ if (ext === ".graphql" || ext === ".gql") {
6089
+ return {
6090
+ sourceType: "graphql",
6091
+ data: content,
6092
+ derivedResourceName: options.resource || baseName,
6093
+ sourceDescription: options.file
6094
+ };
6095
+ }
6096
+ try {
6097
+ const json = JSON.parse(content);
6098
+ if (typeof json === "object" && json !== null && (("openapi" in json) || ("swagger" in json) || ("paths" in json) && ("components" in json))) {
6099
+ return {
6100
+ sourceType: "openapi",
6101
+ data: json,
6102
+ derivedResourceName: options.resource,
6103
+ sourceDescription: options.file
6104
+ };
6105
+ }
6106
+ if (typeof json === "object" && json !== null && (("__schema" in json) || ("data" in json) && json.data && ("__schema" in json.data) || ("types" in json) && Array.isArray(json.types))) {
6107
+ return {
6108
+ sourceType: "graphql",
6109
+ data: json,
6110
+ derivedResourceName: options.resource,
6111
+ sourceDescription: options.file
6112
+ };
6113
+ }
6114
+ const records = Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : Array.isArray(json.items) ? json.items : [json];
6115
+ return {
6116
+ sourceType: "rest",
6117
+ data: records,
6118
+ derivedResourceName: options.resource || baseName,
6119
+ sourceDescription: options.file
6120
+ };
6121
+ } catch {
6122
+ if (content.includes("type ") || content.includes("enum ") || content.includes("schema ")) {
6123
+ return {
6124
+ sourceType: "graphql",
6125
+ data: content,
6126
+ derivedResourceName: options.resource || baseName,
6127
+ sourceDescription: options.file
6128
+ };
6129
+ }
6130
+ throw new Error(`Unable to parse file ${filePath}. Expected valid JSON or GraphQL SDL schema.`);
6131
+ }
6132
+ }
6133
+ if (options.url) {
6134
+ const url = options.url;
6135
+ const isGraphQL = options.type === "graphql" || url.endsWith("/graphql") || url.includes("/graphql?");
6136
+ if (isGraphQL) {
6137
+ const response2 = await customFetch(url, {
6138
+ method: "POST",
6139
+ headers: {
6140
+ "Content-Type": "application/json",
6141
+ Accept: "application/json",
6142
+ ...options.headers ?? {}
6143
+ },
6144
+ body: JSON.stringify({ query: GRAPHQL_INTROSPECTION_QUERY })
6145
+ });
6146
+ if (!response2.ok) {
6147
+ throw new Error(`GraphQL introspection request failed: HTTP ${response2.status} ${response2.statusText}`);
6148
+ }
6149
+ const json2 = await response2.json();
6150
+ return {
6151
+ sourceType: "graphql",
6152
+ data: json2,
6153
+ derivedResourceName: options.resource,
6154
+ sourceDescription: url
6155
+ };
6156
+ }
6157
+ const response = await customFetch(url, {
6158
+ method: options.method ?? "GET",
6159
+ headers: {
6160
+ Accept: "application/json",
6161
+ ...options.headers ?? {}
6162
+ },
6163
+ body: options.body
6164
+ });
6165
+ if (!response.ok) {
6166
+ throw new Error(`HTTP request failed: HTTP ${response.status} ${response.statusText}`);
6167
+ }
6168
+ const json = await response.json();
6169
+ if (typeof json === "object" && json !== null && (("openapi" in json) || ("swagger" in json) || ("paths" in json) && ("components" in json))) {
6170
+ return {
6171
+ sourceType: "openapi",
6172
+ data: json,
6173
+ derivedResourceName: options.resource,
6174
+ sourceDescription: url
6175
+ };
6176
+ }
6177
+ if (typeof json === "object" && json !== null && (("__schema" in json) || ("data" in json) && json.data?.__schema)) {
6178
+ return {
6179
+ sourceType: "graphql",
6180
+ data: json,
6181
+ derivedResourceName: options.resource,
6182
+ sourceDescription: url
6183
+ };
6184
+ }
6185
+ let derivedName = options.resource;
6186
+ if (!derivedName) {
6187
+ try {
6188
+ const parsedUrl = new URL(url);
6189
+ const segments = parsedUrl.pathname.split("/").filter(Boolean);
6190
+ derivedName = segments.pop() || "items";
6191
+ } catch {
6192
+ derivedName = "items";
6193
+ }
6194
+ }
6195
+ const records = Array.isArray(json) ? json : Array.isArray(json.data) ? json.data : Array.isArray(json.items) ? json.items : [json];
6196
+ return {
6197
+ sourceType: "rest",
6198
+ data: records,
6199
+ derivedResourceName: derivedName,
6200
+ sourceDescription: url
6201
+ };
6202
+ }
6203
+ throw new Error("Must provide either --url <url> or --file <path>");
6204
+ }
6205
+ function planGeneratedFiles(resources, bundles, format = "all") {
6206
+ const files = [];
6207
+ const exportLines = [];
6208
+ for (const resource of resources) {
6209
+ const bundle = bundles.get(resource.name);
6210
+ if (!bundle)
6211
+ continue;
6212
+ const baseName = resource.name;
6213
+ const pascalName = capitalize2(resource.name.endsWith("s") ? resource.name.slice(0, -1) : resource.name);
6214
+ if (format === "all" || format === "resource") {
6215
+ files.push({
6216
+ relativePath: `${baseName}.resource.ts`,
6217
+ content: bundle.code
6218
+ });
6219
+ exportLines.push(`export * from './${baseName}.resource.js';`);
6220
+ }
6221
+ if (format === "all" || format === "typebox") {
6222
+ files.push({
6223
+ relativePath: `${baseName}.schema.ts`,
6224
+ content: bundle.typeboxCode
6225
+ });
6226
+ exportLines.push(`export * from './${baseName}.schema.js';`);
6227
+ }
6228
+ if (format === "all" || format === "components") {
6229
+ files.push({
6230
+ relativePath: `${baseName}/ListPage.svelte`,
6231
+ content: bundle.componentCode.list
6232
+ });
6233
+ files.push({
6234
+ relativePath: `${baseName}/CreatePage.svelte`,
6235
+ content: bundle.componentCode.create
6236
+ });
6237
+ files.push({
6238
+ relativePath: `${baseName}/EditPage.svelte`,
6239
+ content: bundle.componentCode.edit
6240
+ });
6241
+ files.push({
6242
+ relativePath: `${baseName}/ShowPage.svelte`,
6243
+ content: bundle.componentCode.show
6244
+ });
6245
+ exportLines.push(`export { default as ${pascalName}ListPage } from './${baseName}/ListPage.svelte';`);
6246
+ exportLines.push(`export { default as ${pascalName}CreatePage } from './${baseName}/CreatePage.svelte';`);
6247
+ exportLines.push(`export { default as ${pascalName}EditPage } from './${baseName}/EditPage.svelte';`);
6248
+ exportLines.push(`export { default as ${pascalName}ShowPage } from './${baseName}/ShowPage.svelte';`);
6249
+ }
6250
+ }
6251
+ if (files.length > 0) {
6252
+ files.push({
6253
+ relativePath: "index.ts",
6254
+ content: `// Auto-generated by @svadmin/create infer
6255
+
6256
+ ${exportLines.join(`
6257
+ `)}
6258
+ `
6259
+ });
6260
+ }
6261
+ return files;
6262
+ }
6263
+ async function executeInfer(options, customFetch = fetch) {
6264
+ const loaded = await loadSourceData(options, customFetch);
6265
+ const primaryKey = options.primaryKey ?? "id";
6266
+ let resources;
6267
+ const bundles = new Map;
6268
+ if (loaded.sourceType === "openapi") {
6269
+ resources = inferFromOpenAPI(loaded.data, {
6270
+ primaryKey,
6271
+ include: options.resource ? [options.resource] : undefined
6272
+ });
6273
+ for (const res of resources) {
6274
+ bundles.set(res.name, generateResourceBundle(res));
6275
+ }
6276
+ } else if (loaded.sourceType === "graphql") {
6277
+ resources = inferFromGraphQL(loaded.data, {
6278
+ primaryKey,
6279
+ include: options.resource ? [options.resource] : undefined
6280
+ });
6281
+ for (const res of resources) {
6282
+ bundles.set(res.name, generateResourceBundle(res));
6283
+ }
6284
+ } else {
6285
+ const resName = options.resource || loaded.derivedResourceName || "items";
6286
+ const sampleArray = Array.isArray(loaded.data) ? loaded.data : [loaded.data];
6287
+ const inferRes = inferResource(resName, sampleArray, { primaryKey });
6288
+ resources = [inferRes.resource];
6289
+ bundles.set(resName, inferRes);
6290
+ }
6291
+ if (resources.length === 0) {
6292
+ throw new Error(`No resources could be inferred from ${loaded.sourceDescription}`);
6293
+ }
6294
+ const files = planGeneratedFiles(resources, bundles, options.format);
6295
+ let wrote = false;
6296
+ if (options.outDir && options.write) {
6297
+ const targetDir = path.resolve(process.cwd(), options.outDir);
6298
+ for (const file of files) {
6299
+ const fullPath = path.join(targetDir, file.relativePath);
6300
+ fs.mkdirSync(path.dirname(fullPath), { recursive: true });
6301
+ fs.writeFileSync(fullPath, file.content, "utf-8");
6302
+ }
6303
+ wrote = true;
6304
+ }
6305
+ return {
6306
+ resources,
6307
+ bundles,
6308
+ files,
6309
+ sourceDescription: loaded.sourceDescription,
6310
+ wrote,
6311
+ outDir: options.outDir
6312
+ };
6313
+ }
6314
+ function printInferResult(result) {
6315
+ console.log();
6316
+ console.log(import_picocolors.default.cyan(" ╔═══════════════════════════════════╗"));
6317
+ console.log(import_picocolors.default.cyan(" ║ ") + import_picocolors.default.bold("svadmin infer") + import_picocolors.default.cyan(" ║"));
6318
+ console.log(import_picocolors.default.cyan(" ║ ") + import_picocolors.default.dim("Automated Code & UI Inferencer") + import_picocolors.default.cyan(" ║"));
6319
+ console.log(import_picocolors.default.cyan(" ╚═══════════════════════════════════╝"));
6320
+ console.log();
6321
+ console.log(import_picocolors.default.bold(` Source: ${import_picocolors.default.cyan(result.sourceDescription)}`));
6322
+ console.log(import_picocolors.default.bold(` Inferred: ${import_picocolors.default.green(result.resources.length.toString())} resource(s): ${result.resources.map((r) => r.name).join(", ")}`));
6323
+ console.log();
6324
+ if (result.outDir) {
6325
+ if (result.wrote) {
6326
+ console.log(import_picocolors.default.green(` ✔ Written ${result.files.length} file(s) to ${import_picocolors.default.cyan(result.outDir)}:`));
6327
+ for (const file of result.files) {
6328
+ console.log(` ${import_picocolors.default.green("•")} ${path.join(result.outDir, file.relativePath)}`);
6329
+ }
6330
+ console.log();
6331
+ console.log(import_picocolors.default.dim(" Next step: import and register your generated resources in src/resources.ts"));
6332
+ } else {
6333
+ console.log(import_picocolors.default.yellow(` Dry run plan — ${result.files.length} file(s) planned for ${import_picocolors.default.cyan(result.outDir)}:`));
6334
+ for (const file of result.files) {
6335
+ console.log(` ${import_picocolors.default.cyan("•")} ${path.join(result.outDir, file.relativePath)}`);
6336
+ }
6337
+ console.log();
6338
+ console.log(import_picocolors.default.yellow(" Dry run only; re-run with --write to generate files to disk."));
6339
+ }
6340
+ } else {
6341
+ for (const res of result.resources) {
6342
+ const bundle = result.bundles.get(res.name);
6343
+ console.log(import_picocolors.default.bold(` Resource: ${import_picocolors.default.green(res.name)} (${res.label})`));
6344
+ console.log(` Primary Key: ${import_picocolors.default.cyan(res.primaryKey ?? "id")} | Fields: ${res.fields.length}`);
6345
+ console.log(import_picocolors.default.dim(" Fields:"));
6346
+ for (const f of res.fields) {
6347
+ const badges = [
6348
+ f.type,
6349
+ f.required ? "required" : null,
6350
+ f.resource ? `-> ${f.resource}` : null,
6351
+ f.showInList ? "list" : null,
6352
+ f.showInForm ? "form" : null
6353
+ ].filter(Boolean).join(", ");
6354
+ console.log(` ${import_picocolors.default.cyan(f.key)}: ${import_picocolors.default.dim(`(${badges})`)}`);
6355
+ }
6356
+ console.log();
6357
+ if (bundle) {
6358
+ console.log(import_picocolors.default.bold(" TypeBox Schema:"));
6359
+ console.log(import_picocolors.default.dim(" ───────────────────────────────────"));
6360
+ console.log(bundle.typeboxCode.trim());
6361
+ console.log(import_picocolors.default.dim(" ───────────────────────────────────"));
6362
+ console.log();
6363
+ }
6364
+ }
6365
+ console.log(import_picocolors.default.dim(" Tip: Pass --out-dir src/resources --write to save generated files directly."));
6366
+ }
6367
+ console.log();
6368
+ }
6369
+ async function promptInferWizard() {
6370
+ const answers = await import_prompts.default([
6371
+ {
6372
+ type: "select",
6373
+ name: "sourceType",
6374
+ message: "Select API source type:",
6375
+ choices: [
6376
+ { title: "REST API Endpoint (URL)", value: "rest-url", description: "Fetch sample JSON data from REST endpoint" },
6377
+ { title: "OpenAPI / Swagger Spec (URL or File)", value: "openapi", description: "OpenAPI 3.x / Swagger schema definition" },
6378
+ { title: "GraphQL Endpoint (URL or Schema File)", value: "graphql", description: "GraphQL introspection or SDL schema" },
6379
+ { title: "Local Sample JSON File", value: "rest-file", description: "JSON file with sample data records" }
6380
+ ],
6381
+ initial: 0
6382
+ },
6383
+ {
6384
+ type: (prev) => prev === "rest-file" || prev === "openapi" ? "text" : null,
6385
+ name: "pathOrUrl",
6386
+ message: "Enter URL or File path:",
6387
+ validate: (v) => v.trim() ? true : "URL or file path is required"
6388
+ },
6389
+ {
6390
+ type: (prev, values) => values.sourceType === "rest-url" || values.sourceType === "graphql" ? "text" : null,
6391
+ name: "endpointUrl",
6392
+ message: "Enter Endpoint URL:",
6393
+ validate: (v) => v.trim().startsWith("http") ? true : "Must be a valid HTTP(S) URL"
6394
+ },
6395
+ {
6396
+ type: "text",
6397
+ name: "outDir",
6398
+ message: "Target output directory:",
6399
+ initial: "src/resources"
6400
+ },
6401
+ {
6402
+ type: "confirm",
6403
+ name: "write",
6404
+ message: "Write generated files to disk now?",
6405
+ initial: true
6406
+ }
6407
+ ]);
6408
+ const targetPath = answers.pathOrUrl || answers.endpointUrl;
6409
+ const isUrl = targetPath?.startsWith("http://") || targetPath?.startsWith("https://");
6410
+ return {
6411
+ url: isUrl ? targetPath : undefined,
6412
+ file: !isUrl ? targetPath : undefined,
6413
+ type: answers.sourceType.startsWith("graphql") ? "graphql" : answers.sourceType.startsWith("openapi") ? "openapi" : "rest",
6414
+ outDir: answers.outDir,
6415
+ write: answers.write
6416
+ };
6417
+ }
6418
+ async function inferCommand(args) {
6419
+ let options = parseInferArguments(args);
6420
+ if (!options.url && !options.file && process.stdin.isTTY) {
6421
+ options = await promptInferWizard();
6422
+ }
6423
+ if (!options.url && !options.file) {
6424
+ printInferHelp();
6425
+ return;
6426
+ }
6427
+ const result = await executeInfer(options);
6428
+ printInferResult(result);
6429
+ }
6430
+ function capitalize2(s) {
6431
+ return s.charAt(0).toUpperCase() + s.slice(1);
6432
+ }
6433
+
5130
6434
  // src/project-maintenance.ts
5131
6435
  import {
5132
6436
  constants,
@@ -5136,10 +6440,10 @@ import {
5136
6440
  unlinkSync,
5137
6441
  writeFileSync
5138
6442
  } from "node:fs";
5139
- function parseDependencyMap(candidate, path) {
6443
+ function parseDependencyMap(candidate, path2) {
5140
6444
  if (candidate === undefined)
5141
6445
  return;
5142
- assertStringRecord(candidate, path);
6446
+ assertStringRecord(candidate, path2);
5143
6447
  return { ...candidate };
5144
6448
  }
5145
6449
  function parseMaintainedPackageJson(packageJsonCandidate) {
@@ -5415,15 +6719,15 @@ function writeProjectPackageJsonUpgrade(packagePath, scaffold, backupDate) {
5415
6719
 
5416
6720
  // src/index.ts
5417
6721
  var __filename2 = fileURLToPath(import.meta.url);
5418
- var __dirname2 = path.dirname(__filename2);
6722
+ var __dirname2 = path2.dirname(__filename2);
5419
6723
  function loadShippedScaffoldManifest() {
5420
- return loadScaffoldManifest(path.join(__dirname2, "..", "scaffold-manifest.json"));
6724
+ return loadScaffoldManifest(path2.join(__dirname2, "..", "scaffold-manifest.json"));
5421
6725
  }
5422
6726
  function projectDirectoryFromArguments(positional) {
5423
6727
  if (positional.length > 1) {
5424
6728
  throw new Error(`Expected at most one project directory, received: ${positional.join(", ")}`);
5425
6729
  }
5426
- return path.resolve(process.cwd(), positional[0] ?? ".");
6730
+ return path2.resolve(process.cwd(), positional[0] ?? ".");
5427
6731
  }
5428
6732
  function doctorProjectDirectory(args) {
5429
6733
  const unknownOption = args.find((argument) => argument.startsWith("-"));
@@ -5459,52 +6763,52 @@ function upgradeChangeMessage(change) {
5459
6763
  return `update ${change.packageName} from ${change.from ?? "missing"} to ${change.to}`;
5460
6764
  }
5461
6765
  function printDoctorIssue(issue) {
5462
- const marker = issue.kind === "drift" || issue.kind === "section" ? import_picocolors.default.yellow(" ⚠") : import_picocolors.default.red(" ✗");
6766
+ const marker = issue.kind === "drift" || issue.kind === "section" ? import_picocolors2.default.yellow(" ⚠") : import_picocolors2.default.red(" ✗");
5463
6767
  console.log(`${marker} ${doctorIssueMessage(issue)}`);
5464
- console.log(import_picocolors.default.dim(` → ${issue.action}`));
6768
+ console.log(import_picocolors2.default.dim(` → ${issue.action}`));
5465
6769
  }
5466
6770
  function printDoctorReport(report, projectDirectory) {
5467
6771
  console.log();
5468
- console.log(import_picocolors.default.bold(`svadmin doctor — ${projectDirectory}`));
6772
+ console.log(import_picocolors2.default.bold(`svadmin doctor — ${projectDirectory}`));
5469
6773
  if (report.status === "clean") {
5470
- console.log(import_picocolors.default.green(" ✔ Dependencies match the shipped svadmin scaffold."));
6774
+ console.log(import_picocolors2.default.green(" ✔ Dependencies match the shipped svadmin scaffold."));
5471
6775
  } else {
5472
6776
  for (const issue of report.issues)
5473
6777
  printDoctorIssue(issue);
5474
6778
  console.log();
5475
- console.log(import_picocolors.default.yellow(` ${report.issues.length} actionable issue(s) found.`));
6779
+ console.log(import_picocolors2.default.yellow(` ${report.issues.length} actionable issue(s) found.`));
5476
6780
  }
5477
6781
  console.log();
5478
6782
  }
5479
6783
  function doctor(args) {
5480
6784
  const projectDirectory = doctorProjectDirectory(args);
5481
- const project = readMaintainedPackageJson(path.join(projectDirectory, "package.json"));
6785
+ const project = readMaintainedPackageJson(path2.join(projectDirectory, "package.json"));
5482
6786
  const report = doctorProjectPackageJson(project, loadShippedScaffoldManifest());
5483
6787
  printDoctorReport(report, projectDirectory);
5484
6788
  process.exitCode = report.exitCode;
5485
6789
  }
5486
6790
  function printUpgradeChanges(upgradeExecution) {
5487
6791
  for (const change of upgradeExecution.plan.changes) {
5488
- console.log(` ${import_picocolors.default.cyan("•")} ${upgradeChangeMessage(change)}`);
6792
+ console.log(` ${import_picocolors2.default.cyan("•")} ${upgradeChangeMessage(change)}`);
5489
6793
  }
5490
6794
  console.log();
5491
6795
  }
5492
6796
  function printUpgradeOutcome(upgradeExecution, packagePath) {
5493
6797
  if (upgradeExecution.wrote) {
5494
- console.log(import_picocolors.default.green(" ✔ package.json updated."));
5495
- console.log(` Backup: ${import_picocolors.default.cyan(upgradeExecution.backupPath)}`);
5496
- console.log(` Restore by copying the backup over: ${import_picocolors.default.cyan(packagePath)}`);
6798
+ console.log(import_picocolors2.default.green(" ✔ package.json updated."));
6799
+ console.log(` Backup: ${import_picocolors2.default.cyan(upgradeExecution.backupPath)}`);
6800
+ console.log(` Restore by copying the backup over: ${import_picocolors2.default.cyan(packagePath)}`);
5497
6801
  } else {
5498
- console.log(import_picocolors.default.yellow(" Dry run only; package.json was not changed."));
6802
+ console.log(import_picocolors2.default.yellow(" Dry run only; package.json was not changed."));
5499
6803
  console.log(" Re-run this command with --write to apply the plan.");
5500
6804
  }
5501
6805
  console.log();
5502
6806
  }
5503
6807
  function printUpgradeExecution(upgradeExecution, projectDirectory, packagePath) {
5504
6808
  console.log();
5505
- console.log(import_picocolors.default.bold(`svadmin upgrade — ${projectDirectory}`));
6809
+ console.log(import_picocolors2.default.bold(`svadmin upgrade — ${projectDirectory}`));
5506
6810
  if (upgradeExecution.plan.changes.length === 0) {
5507
- console.log(import_picocolors.default.green(" ✔ package.json already matches the shipped scaffold."));
6811
+ console.log(import_picocolors2.default.green(" ✔ package.json already matches the shipped scaffold."));
5508
6812
  console.log();
5509
6813
  return;
5510
6814
  }
@@ -5513,60 +6817,60 @@ function printUpgradeExecution(upgradeExecution, projectDirectory, packagePath)
5513
6817
  }
5514
6818
  function upgrade(args) {
5515
6819
  const commandArguments = parseUpgradeArguments(args);
5516
- const packagePath = path.join(commandArguments.projectDirectory, "package.json");
6820
+ const packagePath = path2.join(commandArguments.projectDirectory, "package.json");
5517
6821
  const scaffoldManifest = loadShippedScaffoldManifest();
5518
6822
  const upgradeExecution = commandArguments.write ? writeProjectPackageJsonUpgrade(packagePath, scaffoldManifest, new Date) : planProjectPackageFileUpgrade(packagePath, scaffoldManifest);
5519
6823
  printUpgradeExecution(upgradeExecution, commandArguments.projectDirectory, packagePath);
5520
6824
  }
5521
6825
  var GUIDANCE_FILES = ["DESIGN.md", "AGENTS.md"];
5522
6826
  function missingGuidanceFiles(projectDirectory) {
5523
- return GUIDANCE_FILES.filter((fileName) => !fs.existsSync(path.join(projectDirectory, fileName)));
6827
+ return GUIDANCE_FILES.filter((fileName) => !fs2.existsSync(path2.join(projectDirectory, fileName)));
5524
6828
  }
5525
6829
  function printGuidancePlan(projectDirectory, missingFiles) {
5526
6830
  console.log();
5527
- console.log(import_picocolors.default.bold(`svadmin guidance — ${projectDirectory}`));
6831
+ console.log(import_picocolors2.default.bold(`svadmin guidance — ${projectDirectory}`));
5528
6832
  for (const fileName of missingFiles) {
5529
- console.log(` ${import_picocolors.default.cyan("•")} add ${fileName}`);
6833
+ console.log(` ${import_picocolors2.default.cyan("•")} add ${fileName}`);
5530
6834
  }
5531
6835
  console.log();
5532
6836
  }
5533
6837
  function installMissingGuidanceFiles(guidanceDirectory, projectDirectory, missingFiles) {
5534
6838
  for (const fileName of missingFiles) {
5535
- fs.copyFileSync(path.join(guidanceDirectory, fileName), path.join(projectDirectory, fileName));
6839
+ fs2.copyFileSync(path2.join(guidanceDirectory, fileName), path2.join(projectDirectory, fileName));
5536
6840
  }
5537
6841
  }
5538
6842
  function guidance(args) {
5539
6843
  const { projectDirectory, write } = parseUpgradeArguments(args);
5540
- const guidanceDirectory = path.join(__dirname2, "..", "guidance");
5541
- if (!fs.existsSync(projectDirectory))
6844
+ const guidanceDirectory = path2.join(__dirname2, "..", "guidance");
6845
+ if (!fs2.existsSync(projectDirectory))
5542
6846
  throw new Error(`Project directory does not exist: ${projectDirectory}`);
5543
- if (!fs.existsSync(guidanceDirectory))
6847
+ if (!fs2.existsSync(guidanceDirectory))
5544
6848
  throw new Error("Shipped svadmin guidance files are missing");
5545
6849
  const missingFiles = missingGuidanceFiles(projectDirectory);
5546
6850
  if (missingFiles.length === 0) {
5547
- console.log(import_picocolors.default.green(`
6851
+ console.log(import_picocolors2.default.green(`
5548
6852
  ✔ DESIGN.md and AGENTS.md already exist; nothing was changed.
5549
6853
  `));
5550
6854
  return;
5551
6855
  }
5552
6856
  printGuidancePlan(projectDirectory, missingFiles);
5553
6857
  if (!write) {
5554
- console.log(import_picocolors.default.yellow(` Dry run only; re-run with --write to add missing guidance files.
6858
+ console.log(import_picocolors2.default.yellow(` Dry run only; re-run with --write to add missing guidance files.
5555
6859
  `));
5556
6860
  return;
5557
6861
  }
5558
6862
  installMissingGuidanceFiles(guidanceDirectory, projectDirectory, missingFiles);
5559
- console.log(import_picocolors.default.green(` ✔ Added ${missingFiles.length} guidance file(s); existing files were preserved.`));
6863
+ console.log(import_picocolors2.default.green(` ✔ Added ${missingFiles.length} guidance file(s); existing files were preserved.`));
5560
6864
  console.log();
5561
6865
  }
5562
6866
  async function init() {
5563
6867
  console.log();
5564
- console.log(import_picocolors.default.cyan(" ╔═══════════════════════════════════╗"));
5565
- console.log(import_picocolors.default.cyan(" ║ ") + import_picocolors.default.bold("create-svadmin") + import_picocolors.default.cyan(" ║"));
5566
- console.log(import_picocolors.default.cyan(" ║ ") + import_picocolors.default.dim("Headless Admin for Svelte 5") + import_picocolors.default.cyan(" ║"));
5567
- console.log(import_picocolors.default.cyan(" ╚═══════════════════════════════════╝"));
6868
+ console.log(import_picocolors2.default.cyan(" ╔═══════════════════════════════════╗"));
6869
+ console.log(import_picocolors2.default.cyan(" ║ ") + import_picocolors2.default.bold("create-svadmin") + import_picocolors2.default.cyan(" ║"));
6870
+ console.log(import_picocolors2.default.cyan(" ║ ") + import_picocolors2.default.dim("Headless Admin for Svelte 5") + import_picocolors2.default.cyan(" ║"));
6871
+ console.log(import_picocolors2.default.cyan(" ╚═══════════════════════════════════╝"));
5568
6872
  console.log();
5569
- const response = await import_prompts.default([
6873
+ const response = await import_prompts2.default([
5570
6874
  {
5571
6875
  type: "text",
5572
6876
  name: "projectName",
@@ -5575,7 +6879,7 @@ async function init() {
5575
6879
  validate: (value) => {
5576
6880
  if (!value.trim())
5577
6881
  return "Project name is required";
5578
- if (fs.existsSync(value.trim()) && fs.readdirSync(value.trim()).length > 0) {
6882
+ if (fs2.existsSync(value.trim()) && fs2.readdirSync(value.trim()).length > 0) {
5579
6883
  return "Directory already exists and is not empty";
5580
6884
  }
5581
6885
  return true;
@@ -5613,61 +6917,61 @@ async function init() {
5613
6917
  }
5614
6918
  ]);
5615
6919
  if (!response.projectName) {
5616
- console.log(import_picocolors.default.red(`
6920
+ console.log(import_picocolors2.default.red(`
5617
6921
  Operation cancelled.
5618
6922
  `));
5619
6923
  return;
5620
6924
  }
5621
- const projectDir = path.resolve(process.cwd(), response.projectName.trim());
5622
- if (!fs.existsSync(projectDir)) {
5623
- fs.mkdirSync(projectDir, { recursive: true });
6925
+ const projectDir = path2.resolve(process.cwd(), response.projectName.trim());
6926
+ if (!fs2.existsSync(projectDir)) {
6927
+ fs2.mkdirSync(projectDir, { recursive: true });
5624
6928
  }
5625
6929
  console.log(`
5626
- ${import_picocolors.default.bold("Scaffolding")} project in ${import_picocolors.default.green(projectDir)}...
6930
+ ${import_picocolors2.default.bold("Scaffolding")} project in ${import_picocolors2.default.green(projectDir)}...
5627
6931
  `);
5628
- const templateDir = path.join(__dirname2, "..", "template");
5629
- const guidanceDir = path.join(__dirname2, "..", "guidance");
6932
+ const templateDir = path2.join(__dirname2, "..", "template");
6933
+ const guidanceDir = path2.join(__dirname2, "..", "guidance");
5630
6934
  const scaffoldManifest = loadShippedScaffoldManifest();
5631
6935
  function copyDir(src, dest) {
5632
- fs.mkdirSync(dest, { recursive: true });
5633
- const entries = fs.readdirSync(src, { withFileTypes: true });
6936
+ fs2.mkdirSync(dest, { recursive: true });
6937
+ const entries = fs2.readdirSync(src, { withFileTypes: true });
5634
6938
  for (const entry of entries) {
5635
- const srcPath = path.join(src, entry.name);
5636
- const destPath = path.join(dest, entry.name === "_gitignore" ? ".gitignore" : entry.name);
6939
+ const srcPath = path2.join(src, entry.name);
6940
+ const destPath = path2.join(dest, entry.name === "_gitignore" ? ".gitignore" : entry.name);
5637
6941
  if (entry.isDirectory()) {
5638
6942
  copyDir(srcPath, destPath);
5639
6943
  } else {
5640
- fs.copyFileSync(srcPath, destPath);
6944
+ fs2.copyFileSync(srcPath, destPath);
5641
6945
  }
5642
6946
  }
5643
6947
  }
5644
- if (fs.existsSync(templateDir)) {
6948
+ if (fs2.existsSync(templateDir)) {
5645
6949
  copyDir(templateDir, projectDir);
5646
- console.log(import_picocolors.default.green(" ✔") + " Template files copied");
6950
+ console.log(import_picocolors2.default.green(" ✔") + " Template files copied");
5647
6951
  }
5648
- if (fs.existsSync(guidanceDir)) {
6952
+ if (fs2.existsSync(guidanceDir)) {
5649
6953
  copyDir(guidanceDir, projectDir);
5650
- console.log(import_picocolors.default.green(" ✔") + " AI and design guidance copied");
6954
+ console.log(import_picocolors2.default.green(" ✔") + " AI and design guidance copied");
5651
6955
  }
5652
6956
  const packageJson = createProjectPackageJson(scaffoldManifest, {
5653
6957
  projectName: response.projectName,
5654
6958
  dataProvider: response.dataProvider,
5655
6959
  authProvider: response.authProvider
5656
6960
  });
5657
- fs.writeFileSync(path.join(projectDir, "package.json"), `${JSON.stringify(packageJson, null, 2)}
6961
+ fs2.writeFileSync(path2.join(projectDir, "package.json"), `${JSON.stringify(packageJson, null, 2)}
5658
6962
  `);
5659
- console.log(import_picocolors.default.green(" ✔") + " package.json generated");
5660
- fs.writeFileSync(path.join(projectDir, ".gitignore"), `node_modules
6963
+ console.log(import_picocolors2.default.green(" ✔") + " package.json generated");
6964
+ fs2.writeFileSync(path2.join(projectDir, ".gitignore"), `node_modules
5661
6965
  dist
5662
6966
  .svelte-kit
5663
6967
  .env
5664
6968
  .env.local
5665
6969
  *.local
5666
6970
  `);
5667
- console.log(import_picocolors.default.green(" ✔") + " .gitignore generated");
6971
+ console.log(import_picocolors2.default.green(" ✔") + " .gitignore generated");
5668
6972
  const dpLabel = response.dataProvider === "simple-rest" ? "Simple REST" : response.dataProvider === "supabase" ? "Supabase" : response.dataProvider === "graphql" ? "GraphQL" : "Custom";
5669
6973
  const authLabel = response.authProvider === "mock" ? "Mock (demo)" : response.authProvider === "jwt" ? "JWT" : response.authProvider === "supabase" ? "Supabase Auth" : "None";
5670
- fs.writeFileSync(path.join(projectDir, "README.md"), `# ${response.projectName}
6974
+ fs2.writeFileSync(path2.join(projectDir, "README.md"), `# ${response.projectName}
5671
6975
 
5672
6976
  Built with [svadmin](https://github.com/vibeunion/svadmin) — Headless Admin Framework for Svelte 5.
5673
6977
 
@@ -5685,30 +6989,30 @@ bun run dev
5685
6989
  - **Auth**: ${authLabel}
5686
6990
  - **State**: TanStack Query v6
5687
6991
  `);
5688
- console.log(import_picocolors.default.green(" ✔") + " README.md generated");
6992
+ console.log(import_picocolors2.default.green(" ✔") + " README.md generated");
5689
6993
  if (response.installDeps) {
5690
6994
  console.log(`
5691
- ${import_picocolors.default.bold("Installing dependencies...")}
6995
+ ${import_picocolors2.default.bold("Installing dependencies...")}
5692
6996
  `);
5693
6997
  const bunInstall = spawnSync("bun", ["install"], { cwd: projectDir, stdio: "inherit" });
5694
6998
  if (bunInstall.status !== 0) {
5695
6999
  const npmInstall = spawnSync("npm", ["install"], { cwd: projectDir, stdio: "inherit" });
5696
7000
  if (npmInstall.status !== 0) {
5697
- console.log(import_picocolors.default.yellow("\n ⚠ Auto-install failed. Run `bun install` or `npm install` manually."));
7001
+ console.log(import_picocolors2.default.yellow("\n ⚠ Auto-install failed. Run `bun install` or `npm install` manually."));
5698
7002
  }
5699
7003
  }
5700
7004
  }
5701
7005
  console.log();
5702
- console.log(import_picocolors.default.green(import_picocolors.default.bold(" ✔ Project ready!")));
7006
+ console.log(import_picocolors2.default.green(import_picocolors2.default.bold(" ✔ Project ready!")));
5703
7007
  console.log();
5704
7008
  console.log(" Next steps:");
5705
- console.log(` ${import_picocolors.default.cyan(`cd ${response.projectName}`)}`);
7009
+ console.log(` ${import_picocolors2.default.cyan(`cd ${response.projectName}`)}`);
5706
7010
  if (!response.installDeps) {
5707
- console.log(` ${import_picocolors.default.cyan("bun install")}`);
7011
+ console.log(` ${import_picocolors2.default.cyan("bun install")}`);
5708
7012
  }
5709
- console.log(` ${import_picocolors.default.cyan("bun run dev")}`);
7013
+ console.log(` ${import_picocolors2.default.cyan("bun run dev")}`);
5710
7014
  console.log();
5711
- console.log(` Docs: ${import_picocolors.default.blue("https://github.com/vibeunion/svadmin")}`);
7015
+ console.log(` Docs: ${import_picocolors2.default.blue("https://github.com/vibeunion/svadmin")}`);
5712
7016
  console.log();
5713
7017
  }
5714
7018
  var EJECT_COMPONENTS = [
@@ -5740,64 +7044,64 @@ var EJECT_COMPONENTS = [
5740
7044
  ];
5741
7045
  async function eject(args) {
5742
7046
  console.log();
5743
- console.log(import_picocolors.default.cyan(" svadmin eject") + import_picocolors.default.dim(" — copy internal components for deep customization"));
7047
+ console.log(import_picocolors2.default.cyan(" svadmin eject") + import_picocolors2.default.dim(" — copy internal components for deep customization"));
5744
7048
  console.log();
5745
7049
  const requested = args.filter((a) => !a.startsWith("-"));
5746
7050
  const toEject = requested.length > 0 ? requested.filter((name) => {
5747
7051
  if (!EJECT_COMPONENTS.includes(name)) {
5748
- console.log(import_picocolors.default.yellow(` ⚠ Unknown component: ${name} (skipped)`));
7052
+ console.log(import_picocolors2.default.yellow(` ⚠ Unknown component: ${name} (skipped)`));
5749
7053
  return false;
5750
7054
  }
5751
7055
  return true;
5752
7056
  }) : [...EJECT_COMPONENTS];
5753
7057
  if (toEject.length === 0) {
5754
- console.log(import_picocolors.default.red(" No valid components to eject."));
7058
+ console.log(import_picocolors2.default.red(" No valid components to eject."));
5755
7059
  console.log(` Available: ${EJECT_COMPONENTS.join(", ")}`);
5756
7060
  return;
5757
7061
  }
5758
7062
  let uiSrcDir;
5759
7063
  try {
5760
7064
  const require2 = createRequire2(import.meta.url);
5761
- const uiPkg = path.dirname(require2.resolve("@svadmin/ui/package.json"));
5762
- uiSrcDir = path.join(uiPkg, "src", "components");
7065
+ const uiPkg = path2.dirname(require2.resolve("@svadmin/ui/package.json"));
7066
+ uiSrcDir = path2.join(uiPkg, "src", "components");
5763
7067
  } catch {
5764
- const nm = path.join(process.cwd(), "node_modules", "@svadmin", "ui", "src", "components");
5765
- if (fs.existsSync(nm)) {
7068
+ const nm = path2.join(process.cwd(), "node_modules", "@svadmin", "ui", "src", "components");
7069
+ if (fs2.existsSync(nm)) {
5766
7070
  uiSrcDir = nm;
5767
7071
  } else {
5768
- console.log(import_picocolors.default.red(" ✗ Cannot find @svadmin/ui. Run `bun install` first."));
7072
+ console.log(import_picocolors2.default.red(" ✗ Cannot find @svadmin/ui. Run `bun install` first."));
5769
7073
  return;
5770
7074
  }
5771
7075
  }
5772
- const destDir = path.join(process.cwd(), "src", "components", "svadmin");
5773
- fs.mkdirSync(destDir, { recursive: true });
7076
+ const destDir = path2.join(process.cwd(), "src", "components", "svadmin");
7077
+ fs2.mkdirSync(destDir, { recursive: true });
5774
7078
  let copied = 0;
5775
7079
  for (const name of toEject) {
5776
- const srcFile = path.join(uiSrcDir, `${name}.svelte`);
5777
- const srcFileAlt = path.join(uiSrcDir, "fields", `${name}.svelte`);
5778
- const src = fs.existsSync(srcFile) ? srcFile : fs.existsSync(srcFileAlt) ? srcFileAlt : null;
7080
+ const srcFile = path2.join(uiSrcDir, `${name}.svelte`);
7081
+ const srcFileAlt = path2.join(uiSrcDir, "fields", `${name}.svelte`);
7082
+ const src = fs2.existsSync(srcFile) ? srcFile : fs2.existsSync(srcFileAlt) ? srcFileAlt : null;
5779
7083
  if (!src) {
5780
- console.log(import_picocolors.default.yellow(` ⚠ ${name}.svelte not found in @svadmin/ui (skipped)`));
7084
+ console.log(import_picocolors2.default.yellow(` ⚠ ${name}.svelte not found in @svadmin/ui (skipped)`));
5781
7085
  continue;
5782
7086
  }
5783
- let content = fs.readFileSync(src, "utf-8");
7087
+ let content = fs2.readFileSync(src, "utf-8");
5784
7088
  content = content.replace(/from\s+['"]\.\/ui\//g, "from '@svadmin/ui/components/ui/");
5785
7089
  content = content.replace(/from\s+['"]\.\/((?!ui\/)[^'"]+)['"]/g, "from './$1'");
5786
- const destFile = path.join(destDir, `${name}.svelte`);
5787
- fs.writeFileSync(destFile, content);
5788
- console.log(import_picocolors.default.green(" ✔") + ` ${name}.svelte → src/components/svadmin/`);
7090
+ const destFile = path2.join(destDir, `${name}.svelte`);
7091
+ fs2.writeFileSync(destFile, content);
7092
+ console.log(import_picocolors2.default.green(" ✔") + ` ${name}.svelte → src/components/svadmin/`);
5789
7093
  copied++;
5790
7094
  }
5791
7095
  console.log();
5792
7096
  if (copied > 0) {
5793
- console.log(import_picocolors.default.green(import_picocolors.default.bold(` ✔ Ejected ${copied} component(s)`)));
7097
+ console.log(import_picocolors2.default.green(import_picocolors2.default.bold(` ✔ Ejected ${copied} component(s)`)));
5794
7098
  console.log();
5795
7099
  console.log(" Usage: import overrides in your AdminApp and pass via `components` prop:");
5796
7100
  console.log();
5797
- console.log(import_picocolors.default.dim(' import CustomLayout from "./components/svadmin/Layout.svelte";'));
5798
- console.log(import_picocolors.default.dim(" <AdminApp components={{ Layout: CustomLayout }} ... />"));
7101
+ console.log(import_picocolors2.default.dim(' import CustomLayout from "./components/svadmin/Layout.svelte";'));
7102
+ console.log(import_picocolors2.default.dim(" <AdminApp components={{ Layout: CustomLayout }} ... />"));
5799
7103
  } else {
5800
- console.log(import_picocolors.default.yellow(" No components were ejected."));
7104
+ console.log(import_picocolors2.default.yellow(" No components were ejected."));
5801
7105
  }
5802
7106
  console.log();
5803
7107
  }
@@ -5805,7 +7109,7 @@ var [, , subcommand, ...rest] = process.argv;
5805
7109
  var runCommand = (command) => {
5806
7110
  Promise.resolve().then(command).catch((error) => {
5807
7111
  const message = error instanceof Error ? error.message : String(error);
5808
- console.error(import_picocolors.default.red(`
7112
+ console.error(import_picocolors2.default.red(`
5809
7113
  ✗ ${message}
5810
7114
  `));
5811
7115
  process.exitCode = 2;
@@ -5819,6 +7123,8 @@ if (subcommand === "eject") {
5819
7123
  runCommand(() => upgrade(rest));
5820
7124
  } else if (subcommand === "guidance") {
5821
7125
  runCommand(() => guidance(rest));
7126
+ } else if (subcommand === "infer") {
7127
+ runCommand(() => inferCommand(rest));
5822
7128
  } else {
5823
7129
  runCommand(init);
5824
7130
  }