betterstart-cli 0.0.116 → 0.0.118

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.
Files changed (85) hide show
  1. package/dist/assets/adapters/next/templates/init/admin-globals.css +20 -0
  2. package/dist/assets/adapters/next/templates/init/components/shared/data-table/data-grid-row.tsx +33 -1
  3. package/dist/assets/adapters/next/templates/init/components/shared/media/media-gallery-dialog.tsx +3 -3
  4. package/dist/assets/adapters/next/templates/init/components/shared/media/media-preview.tsx +3 -1
  5. package/dist/assets/adapters/next/templates/init/hooks/use-copy-to-clipboard.ts +1 -3
  6. package/dist/assets/adapters/next/templates/init/hooks/use-field-error-visible.ts +25 -0
  7. package/dist/assets/adapters/next/templates/init/hooks/use-media.ts +1 -1
  8. package/dist/assets/adapters/next/templates/init/hooks/use-upload.ts +11 -5
  9. package/dist/assets/adapters/next/templates/init/lib/actions/audit/cleanup-audit-logs.ts +1 -0
  10. package/dist/assets/adapters/next/templates/init/lib/actions/audit/query-audit-logs.ts +2 -2
  11. package/dist/assets/adapters/next/templates/init/lib/actions/audit/record-audit-event.ts +1 -2
  12. package/dist/assets/adapters/next/templates/init/lib/actions/auth/client.ts +4 -1
  13. package/dist/assets/adapters/next/templates/init/lib/actions/entity-versions/get-entity-versions.ts +5 -1
  14. package/dist/assets/adapters/next/templates/init/lib/actions/forms/get-all-form-settings.ts +1 -1
  15. package/dist/assets/adapters/next/templates/init/lib/actions/media/create-media.ts +2 -1
  16. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media-bulk.ts +1 -0
  17. package/dist/assets/adapters/next/templates/init/lib/actions/media/delete-media.ts +1 -0
  18. package/dist/assets/adapters/next/templates/init/lib/actions/media/update-media.ts +2 -4
  19. package/dist/assets/adapters/next/templates/init/lib/actions/profile/change-own-password.ts +1 -0
  20. package/dist/assets/adapters/next/templates/init/lib/actions/profile/update-email.ts +1 -0
  21. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-files.ts +1 -0
  22. package/dist/assets/adapters/next/templates/init/lib/actions/upload/upload-media-from-url.ts +16 -6
  23. package/dist/assets/adapters/next/templates/init/lib/actions/users/change-user-password.ts +1 -0
  24. package/dist/assets/adapters/next/templates/init/lib/actions/users/create-user.ts +2 -1
  25. package/dist/assets/adapters/next/templates/init/lib/actions/users/delete-user.ts +1 -0
  26. package/dist/assets/adapters/next/templates/init/lib/actions/users/types.ts +1 -1
  27. package/dist/assets/adapters/next/templates/init/lib/actions/users/update-user-role.ts +1 -0
  28. package/dist/assets/adapters/next/templates/init/lib/db/core/schema.ts +12 -4
  29. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/registry.ts +4 -4
  30. package/dist/assets/adapters/next/templates/init/lib/lifecycle-hooks/runner.ts +3 -7
  31. package/dist/assets/adapters/next/templates/init/pages/container-layout.tsx +3 -0
  32. package/dist/assets/adapters/next/templates/init/pages/dashboard-page.tsx +78 -76
  33. package/dist/assets/adapters/next/templates/init/pages/forgot-password-form.tsx +5 -8
  34. package/dist/assets/adapters/next/templates/init/pages/forgot-password-page-skeleton.tsx +2 -2
  35. package/dist/assets/adapters/next/templates/init/pages/login-form.tsx +7 -9
  36. package/dist/assets/adapters/next/templates/init/pages/login-page-skeleton.tsx +2 -2
  37. package/dist/assets/adapters/next/templates/init/pages/reset-password-form.tsx +8 -16
  38. package/dist/assets/adapters/next/templates/init/pages/reset-password-page-skeleton.tsx +2 -2
  39. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-detail-row.tsx +1 -1
  40. package/dist/assets/adapters/next/templates/init/pages/settings/audit-log/audit-log-table.tsx +1 -6
  41. package/dist/assets/adapters/next/templates/init/pages/settings/forms/forms-settings-table.tsx +2 -7
  42. package/dist/assets/adapters/next/templates/init/pages/settings/webhooks/webhooks-table.tsx +1 -6
  43. package/dist/assets/adapters/next/templates/init/pages/users/users-table.tsx +1 -1
  44. package/dist/assets/adapters/next/templates/init/public/placeholder.png +0 -0
  45. package/dist/assets/adapters/next/templates/init/types/index.ts +2 -0
  46. package/dist/assets/adapters/next/templates/init/utils/audit/normalize-comparable-value.ts +4 -2
  47. package/dist/assets/adapters/next/templates/init/utils/date/format-action-date.ts +12 -0
  48. package/dist/assets/adapters/next/templates/init/utils/dev-mode/dev-mode-json-schemas.ts +66 -0
  49. package/dist/assets/adapters/next/templates/init/utils/dev-mode/parse-dev-mode-lifecycle-hooks.ts +11 -0
  50. package/dist/assets/adapters/next/templates/init/utils/dev-mode/parse-dev-mode-snippets.ts +9 -0
  51. package/dist/assets/adapters/next/templates/init/utils/editor/set-task-item-checked.ts +13 -0
  52. package/dist/assets/adapters/next/templates/init/utils/editor/update-nodes-attr.ts +2 -1
  53. package/dist/assets/adapters/next/templates/init/utils/email/email-theme.ts +13 -0
  54. package/dist/assets/adapters/next/templates/init/utils/email/render-form-submission-rows.ts +3 -2
  55. package/dist/assets/adapters/next/templates/init/utils/email/render-notification-email.ts +7 -6
  56. package/dist/assets/adapters/next/templates/init/utils/email/render-thank-you-email.ts +5 -4
  57. package/dist/assets/adapters/next/templates/init/utils/error/read-string-property.ts +4 -2
  58. package/dist/assets/adapters/next/templates/init/utils/error/recognize.ts +2 -4
  59. package/dist/assets/adapters/next/templates/init/utils/error/to-action-error.ts +0 -1
  60. package/dist/assets/adapters/next/templates/init/utils/form/clone-default-value.ts +4 -2
  61. package/dist/assets/adapters/next/templates/init/utils/shared/is-record.ts +3 -0
  62. package/dist/assets/adapters/next/templates/init/utils/table/default-get-id.ts +3 -2
  63. package/dist/assets/shared-assets/react-admin/custom/content-editor/editor-toolbar.tsx +1 -1
  64. package/dist/assets/shared-assets/react-admin/custom/content-editor/slash-command-menu.tsx +5 -3
  65. package/dist/assets/shared-assets/react-admin/custom/content-editor/source-mode.tsx +1 -1
  66. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/removable-image-node/removable-image-node.tsx +3 -1
  67. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-node/task-item-node/task-item-node-extension.tsx +6 -15
  68. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/heading-dropdown-menu/heading-dropdown-menu.tsx +1 -6
  69. package/dist/assets/shared-assets/react-admin/custom/content-editor/tiptap-ui/list-dropdown-menu/list-dropdown-menu.tsx +1 -6
  70. package/dist/assets/shared-assets/react-admin/custom/date-range-picker.tsx +9 -2
  71. package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +3 -3
  72. package/dist/assets/shared-assets/react-admin/custom/media-field.tsx +2 -2
  73. package/dist/assets/shared-assets/react-admin/custom/nested-object-list-field.tsx +3 -1
  74. package/dist/assets/shared-assets/react-admin/custom/progressive-blur.tsx +3 -1
  75. package/dist/assets/shared-assets/react-admin/ui/dropdown-menu.tsx +20 -23
  76. package/dist/assets/shared-assets/react-admin/ui/form.tsx +6 -3
  77. package/dist/assets/shared-assets/react-admin/ui/navigation-menu.tsx +1 -1
  78. package/dist/{chunk-SG4U6EQY.js → chunk-BNHFSHPE.js} +14 -1
  79. package/dist/chunk-BNHFSHPE.js.map +1 -0
  80. package/dist/cli.js +962 -633
  81. package/dist/cli.js.map +1 -1
  82. package/dist/{read-namespaced-template-D2CD4Q7U.js → read-namespaced-template-WYIGUH7S.js} +2 -2
  83. package/package.json +1 -1
  84. package/dist/chunk-SG4U6EQY.js.map +0 -1
  85. /package/dist/{read-namespaced-template-D2CD4Q7U.js.map → read-namespaced-template-WYIGUH7S.js.map} +0 -0
package/dist/cli.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ACCEPT_TERMS_URL_PATTERN,
4
+ ADMIN_CONTAINER_ROUTE_GROUP,
4
5
  BETTERSTART_ASSET_HOST,
5
6
  BETTERSTART_DIR,
6
7
  CLI_PACKAGE_NAME,
@@ -20,6 +21,7 @@ import {
20
21
  MAIN_CSS_CANDIDATES,
21
22
  PROXY_CONVENTION_FILE_NAMES,
22
23
  PROXY_FILE_NAME,
24
+ PUBLIC_ASSET_FILE_NAMES,
23
25
  R2_ENV_KEYS,
24
26
  RAILWAY_BUCKET_ENV_KEYS,
25
27
  REDACTED,
@@ -68,6 +70,7 @@ import {
68
70
  resolveCommandCwd,
69
71
  resolveProjectEnvVar,
70
72
  restoreFile,
73
+ safeCopyFile,
71
74
  safeIdentifier,
72
75
  safeWriteFile,
73
76
  singularize,
@@ -85,7 +88,7 @@ import {
85
88
  trimDotSlash,
86
89
  usesIdentifier,
87
90
  validateAdminNamespace
88
- } from "./chunk-SG4U6EQY.js";
91
+ } from "./chunk-BNHFSHPE.js";
89
92
 
90
93
  // cli.ts
91
94
  import * as p66 from "@clack/prompts";
@@ -263,6 +266,14 @@ function redirectStdoutToStderr() {
263
266
  process.stdout.write = originalWrite;
264
267
  };
265
268
  }
269
+ function writeMachineJson(payload) {
270
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}
271
+ `);
272
+ }
273
+ function writeDiagnostic(message) {
274
+ process.stderr.write(`${message}
275
+ `);
276
+ }
266
277
  function renderTableRows(rows) {
267
278
  const widths = [];
268
279
  for (const row of rows) {
@@ -1246,32 +1257,32 @@ function collectInvalidSchemaFieldTypeErrors(fields, errors) {
1246
1257
  }
1247
1258
 
1248
1259
  // core-engine/schema/schema-reader/collect-slot-area-errors.ts
1249
- function collectSlotAreaErrors(value, path113, errors) {
1260
+ function collectSlotAreaErrors(value, path114, errors) {
1250
1261
  if (!isRecord(value)) {
1251
- errors.push(`${path113} must be an object with a "fields" array.`);
1262
+ errors.push(`${path114} must be an object with a "fields" array.`);
1252
1263
  return;
1253
1264
  }
1254
1265
  const fields = value.fields;
1255
1266
  if (!Array.isArray(fields)) {
1256
- errors.push(`${path113}.fields must be an array.`);
1267
+ errors.push(`${path114}.fields must be an array.`);
1257
1268
  return;
1258
1269
  }
1259
1270
  for (const field of fields) {
1260
1271
  const name = isRecord(field) && isNonEmptyString(field.name) ? field.name : "unnamed";
1261
- walkSlot(field, `${path113}.fields.${name}`, errors);
1272
+ walkSlot(field, `${path114}.fields.${name}`, errors);
1262
1273
  }
1263
1274
  }
1264
1275
 
1265
1276
  // core-engine/schema/schema-reader/collect-slot-layout-errors.ts
1266
- function collectSlotLayoutErrors(value, path113, errors) {
1277
+ function collectSlotLayoutErrors(value, path114, errors) {
1267
1278
  if (!isRecord(value)) {
1268
- errors.push(`${path113} must be an object with "main" and/or "sidebar" field groups.`);
1279
+ errors.push(`${path114} must be an object with "main" and/or "sidebar" field groups.`);
1269
1280
  return;
1270
1281
  }
1271
1282
  const validKeys = /* @__PURE__ */ new Set(["main", "sidebar"]);
1272
1283
  for (const key of Object.keys(value)) {
1273
1284
  if (!validKeys.has(key)) {
1274
- errors.push(`${path113} has unsupported key "${key}". Expected "main" or "sidebar".`);
1285
+ errors.push(`${path114} has unsupported key "${key}". Expected "main" or "sidebar".`);
1275
1286
  }
1276
1287
  }
1277
1288
  const areas = [
@@ -1282,10 +1293,10 @@ function collectSlotLayoutErrors(value, path113, errors) {
1282
1293
  for (const [name, area] of areas) {
1283
1294
  if (area === void 0) continue;
1284
1295
  hasArea = true;
1285
- collectSlotAreaErrors(area, `${path113}.${name}`, errors);
1296
+ collectSlotAreaErrors(area, `${path114}.${name}`, errors);
1286
1297
  }
1287
1298
  if (!hasArea) {
1288
- errors.push(`${path113} must define at least one of "main" or "sidebar".`);
1299
+ errors.push(`${path114} must define at least one of "main" or "sidebar".`);
1289
1300
  }
1290
1301
  }
1291
1302
 
@@ -1933,6 +1944,7 @@ function resolveProjectPaths(config) {
1933
1944
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
1934
1945
  const adminDir = trimDotSlash(config.paths?.admin ?? "./admin");
1935
1946
  const pagesDir = trimDotSlash(config.paths?.pages ?? "./src/app/(admin)/admin/(authenticated)");
1947
+ const containerDir = path6.posix.join(pagesDir, ADMIN_CONTAINER_ROUTE_GROUP);
1936
1948
  const schemasDir = trimDotSlash(config.paths?.schemas ?? "./admin/schemas");
1937
1949
  const adminDbDir = path6.posix.join(adminDir, "lib", "db");
1938
1950
  const adminDbCoreDir = path6.posix.join(adminDbDir, "core");
@@ -1946,6 +1958,7 @@ function resolveProjectPaths(config) {
1946
1958
  return {
1947
1959
  adminDir,
1948
1960
  pagesDir,
1961
+ containerDir,
1949
1962
  schemasDir,
1950
1963
  adminDbDir,
1951
1964
  adminDbCoreDir,
@@ -3727,7 +3740,7 @@ function buildEditsForSourceFile(sourceFile, filePath, plan) {
3727
3740
  }
3728
3741
 
3729
3742
  // core-engine/snapshots/ast-substitution/collect-preview-changes.ts
3730
- function collectPreviewChanges(path113, beforeContent, afterContent) {
3743
+ function collectPreviewChanges(path114, beforeContent, afterContent) {
3731
3744
  const beforeLines = beforeContent.split("\n");
3732
3745
  const afterLines = afterContent.split("\n");
3733
3746
  const maxLength = Math.max(beforeLines.length, afterLines.length);
@@ -3739,7 +3752,7 @@ function collectPreviewChanges(path113, beforeContent, afterContent) {
3739
3752
  continue;
3740
3753
  }
3741
3754
  changes.push({
3742
- path: path113,
3755
+ path: path114,
3743
3756
  line: index + 1,
3744
3757
  before: before.trim(),
3745
3758
  after: after.trim()
@@ -4598,9 +4611,9 @@ function renderErrorMessageLineLayoutTemplate({
4598
4611
  indent,
4599
4612
  pathExpression: pathExpression2
4600
4613
  }) {
4601
- return `${indent}{getFieldErrorMessage(form.formState.errors, ${pathExpression2}) ? (
4614
+ return `${indent}{getFieldErrorMessage(form.formState, watchedValues, revealErrors, ${pathExpression2}) ? (
4602
4615
  ${indent} <p className="text-sm text-destructive">
4603
- ${indent} {getFieldErrorMessage(form.formState.errors, ${pathExpression2})}
4616
+ ${indent} {getFieldErrorMessage(form.formState, watchedValues, revealErrors, ${pathExpression2})}
4604
4617
  ${indent} </p>
4605
4618
  ${indent}) : null}`;
4606
4619
  }
@@ -5369,19 +5382,51 @@ ${innerJSX}
5369
5382
 
5370
5383
  // adapters/next/templates/renderers/generators/form-pipeline/form-component-shared/build-field-error-helper.ts
5371
5384
  function renderBuildFieldErrorHelperSegmentTemplate() {
5372
- return `function getFieldErrorMessage(errors: FieldErrors<FormValues>, path: string): string | undefined {
5373
- let current: unknown = errors
5385
+ return `function readFormPath(source: unknown, path: string): unknown {
5386
+ let current: unknown = source
5374
5387
  for (const segment of path.split('.')) {
5375
5388
  if (!current || typeof current !== 'object') {
5376
5389
  return undefined
5377
5390
  }
5378
5391
  current = (current as Record<string, unknown>)[segment]
5379
5392
  }
5380
- if (current && typeof current === 'object' && 'message' in current) {
5381
- const message = (current as { message?: unknown }).message
5382
- return typeof message === 'string' ? message : undefined
5393
+ return current
5394
+ }
5395
+
5396
+ function hasFieldValue(value: unknown): boolean {
5397
+ if (value === null || value === undefined) {
5398
+ return false
5399
+ }
5400
+ if (typeof value === 'string') {
5401
+ return value.trim().length > 0
5402
+ }
5403
+ if (Array.isArray(value)) {
5404
+ return value.length > 0
5405
+ }
5406
+ return true
5407
+ }
5408
+
5409
+ function getFieldErrorMessage(
5410
+ formState: FormState<FormValues>,
5411
+ values: unknown,
5412
+ revealErrors: boolean,
5413
+ path: string
5414
+ ): string | undefined {
5415
+ const error = readFormPath(formState.errors, path)
5416
+ const message =
5417
+ error && typeof error === 'object' && 'message' in error
5418
+ ? (error as { message?: unknown }).message
5419
+ : undefined
5420
+ if (typeof message !== 'string') {
5421
+ return undefined
5422
+ }
5423
+ if (revealErrors) {
5424
+ return message
5383
5425
  }
5384
- return undefined
5426
+ if (readFormPath(formState.touchedFields, path) !== true) {
5427
+ return undefined
5428
+ }
5429
+ return hasFieldValue(readFormPath(values, path)) ? message : undefined
5385
5430
  }`;
5386
5431
  }
5387
5432
 
@@ -5473,18 +5518,24 @@ function ${pPascal}FormInner() {
5473
5518
  defaultValues: {
5474
5519
  ${pDefaults}
5475
5520
  },
5521
+ mode: 'onBlur'
5476
5522
  })
5523
+ const watchedValues = useWatch({ control: form.control })
5524
+ const [stepAttempted, setStepAttempted] = React.useState(false)
5525
+ const revealErrors = form.formState.isSubmitted || stepAttempted
5477
5526
 
5478
5527
  ${pFieldArraySetup}${pWatchSetup}
5479
5528
  async function handleNext() {
5480
5529
  const stepFields = STEPS[currentStep].fields as (keyof FormValues)[]
5481
5530
  const isValid = await form.trigger(stepFields, { shouldFocus: true })
5531
+ setStepAttempted(!isValid)
5482
5532
  if (isValid) {
5483
5533
  setCurrentStep(currentStep + 1)
5484
5534
  }
5485
5535
  }
5486
5536
 
5487
5537
  function handleBack() {
5538
+ setStepAttempted(false)
5488
5539
  setCurrentStep(currentStep - 1)
5489
5540
  }
5490
5541
 
@@ -5655,7 +5706,7 @@ function buildMultiStepFormComponentSource(schema, options = {}) {
5655
5706
  const listFields = getListFields(allFields);
5656
5707
  const hasListFields = listFields.length > 0;
5657
5708
  const { setup: watchSetup } = buildWatchDecls(allFields);
5658
- const rhfImport = hasListFields ? `import { Controller, useFieldArray, useForm, type FieldErrors } from 'react-hook-form'` : `import { Controller, useForm, type FieldErrors } from 'react-hook-form'`;
5709
+ const rhfImport = hasListFields ? `import { Controller, useFieldArray, useForm, useWatch, type FormState } from 'react-hook-form'` : `import { Controller, useForm, useWatch, type FormState } from 'react-hook-form'`;
5659
5710
  const fieldArraySetup = hasListFields ? `
5660
5711
  ${buildFieldArrayDecls(listFields)}
5661
5712
  ` : "";
@@ -5730,7 +5781,10 @@ export function ${pascal}Form() {
5730
5781
  defaultValues: {
5731
5782
  ${defaults}
5732
5783
  },
5784
+ mode: 'onBlur'
5733
5785
  })
5786
+ const watchedValues = useWatch({ control: form.control })
5787
+ const revealErrors = form.formState.isSubmitted
5734
5788
  ${fieldArraySetup}${watchSetup}
5735
5789
  function onSubmit(values: FormValues) {
5736
5790
  startSubmitTransition(async () => {
@@ -5795,7 +5849,7 @@ function buildSingleStepFormComponentSource(schema, options = {}) {
5795
5849
  const successMessage = escapeJsx(schema.successMessage || "Form submitted successfully!");
5796
5850
  const redirectUrl = schema.redirectUrl;
5797
5851
  const successHandler = redirectUrl ? `window.location.href = ${JSON.stringify(redirectUrl)}` : "setSubmitted(true)";
5798
- const rhfImport = hasListFields ? `import { Controller, useFieldArray, useForm, type FieldErrors } from 'react-hook-form'` : `import { Controller, useForm, type FieldErrors } from 'react-hook-form'`;
5852
+ const rhfImport = hasListFields ? `import { Controller, useFieldArray, useForm, useWatch, type FormState } from 'react-hook-form'` : `import { Controller, useForm, useWatch, type FormState } from 'react-hook-form'`;
5799
5853
  const fieldArraySetup = hasListFields ? `
5800
5854
  ${buildFieldArrayDecls(listFields)}
5801
5855
  ` : "";
@@ -6061,10 +6115,10 @@ function renderCreateDevModeIntegrationArtifactImportsTemplate({
6061
6115
  }) {
6062
6116
  return `import type {
6063
6117
  DevModeIntegration,
6064
- DevModeIntegrationMetadata,
6065
- DevModeLifecycleHookExample,
6066
- DevModeSnippet
6118
+ DevModeIntegrationMetadata
6067
6119
  } from '${adminImportAlias}/components/shared/dev-mode/dev-mode-types'
6120
+ import { parseDevModeLifecycleHooks } from '${adminImportAlias}/utils/dev-mode/parse-dev-mode-lifecycle-hooks'
6121
+ import { parseDevModeSnippets } from '${adminImportAlias}/utils/dev-mode/parse-dev-mode-snippets'
6068
6122
 
6069
6123
  export const ${exportName} = {
6070
6124
  schemaName: ${stringify},
@@ -6076,12 +6130,12 @@ export async function ${loadExportName}(): Promise<DevModeIntegration> {
6076
6130
  const [${join}] = await Promise.all([
6077
6131
  ${dynamicImports}
6078
6132
  ])
6079
- const snippets = [
6133
+ const snippets = parseDevModeSnippets([
6080
6134
  ${snippetValues}
6081
- ] as DevModeSnippet[]
6082
- const lifecycleHooks = [
6135
+ ])
6136
+ const lifecycleHooks = parseDevModeLifecycleHooks([
6083
6137
  ${lifecycleHookValues}
6084
- ] as DevModeLifecycleHookExample[]
6138
+ ])
6085
6139
 
6086
6140
  return {
6087
6141
  ...${exportName},
@@ -7557,12 +7611,7 @@ export function ${pascal}SubmissionsTable<TValue>({
7557
7611
  key={row.id}
7558
7612
  data-state={row.getIsSelected() ? 'selected' : undefined}
7559
7613
  className="cursor-pointer hover:bg-muted/50"
7560
- onClick={(e) => {
7561
- const target = e.target as HTMLElement
7562
- if (!e.currentTarget.contains(target)) return
7563
- if (target.closest('button, a, input, [role="checkbox"]')) return
7564
- onViewSubmission(row.original.id)
7565
- }}
7614
+ onActivate={() => onViewSubmission(row.original.id)}
7566
7615
  >
7567
7616
  {row.getVisibleCells().map((cell) => {
7568
7617
  const meta = cell.column.columnDef.meta as Record<string, unknown> | undefined
@@ -9212,7 +9261,7 @@ function runFormPipeline(schema, cwd, config, options = {}) {
9212
9261
  },
9213
9262
  {
9214
9263
  name: "Admin pages",
9215
- run: () => generateFormAdminPages(schema, paths.pagesDir, namespacedOptions).files
9264
+ run: () => generateFormAdminPages(schema, paths.containerDir, namespacedOptions).files
9216
9265
  },
9217
9266
  {
9218
9267
  name: "Navigation",
@@ -9422,10 +9471,11 @@ import {
9422
9471
  get${ctxSingular}SlugRetryLimit,
9423
9472
  is${ctxSingular}SlugConflict,
9424
9473
  resolveUnique${ctxSingular}Slug,
9425
- ${snapshotImport}
9474
+ ${snapshotImport},
9475
+ to${ctxSingular}
9426
9476
  } from './helpers'
9427
9477
  ${lifecycleHookImport2}
9428
- import type { ${ctxSingular}CreateInput, ${ctxSingular}CreateResult, ${ctxSingular} } from './types'${markdownImport}
9478
+ import type { ${ctxSingular}CreateInput, ${ctxSingular}CreateResult } from './types'${markdownImport}
9429
9479
 
9430
9480
  export async function create${ctxSingular}(input: ${ctxSingular}CreateInput): Promise<${ctxSingular}CreateResult> {
9431
9481
  const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
@@ -9434,7 +9484,7 @@ export async function create${ctxSingular}(input: ${ctxSingular}CreateInput): Pr
9434
9484
  const actorId = actor.id
9435
9485
  const now = new Date().toISOString()
9436
9486
  const auditRequestMetadata = await getAuditRequestMetadata()
9437
- const changedFields = getTouchedFieldNames(input as unknown as Record<string, unknown>)
9487
+ const changedFields = getTouchedFieldNames(input)
9438
9488
  const lifecycleContext = ${createContext}
9439
9489
  ${beforeHook2}
9440
9490
  ${createBeforePublishBlock}
@@ -9497,7 +9547,7 @@ ${ctxCreateMappings},${ctxHasHtmlOutputContent}${ctxHasDraftContent}
9497
9547
  typeof created${ctxSingular}Id === 'string'
9498
9548
  ? await get${ctxSingular}ById(created${ctxSingular}Id)
9499
9549
  : null
9500
- const created${ctxSingular}Data = created${ctxSingular} ?? (result[0] as unknown as ${ctxSingular})
9550
+ const created${ctxSingular}Data = created${ctxSingular} ?? to${ctxSingular}(result[0])
9501
9551
  const created${ctxSingular}Context = ${createResultContext}
9502
9552
  ${afterHook2}
9503
9553
  ${createAfterPublishBlock}
@@ -9519,21 +9569,18 @@ function genCreateContent(ctx) {
9519
9569
  const kebabSingular = toKebabCase(ctx.singular);
9520
9570
  const slugSourceValue = ctx.slugSourceField ? `input.${ctx.slugSourceField}` : "undefined";
9521
9571
  const snapshotImport = ctx.hasM2M ? `build${ctx.Singular}VersionSnapshotWithRelationships` : `build${ctx.Singular}VersionSnapshot`;
9522
- const snapshotData = ctx.hasM2M ? `await build${ctx.Singular}VersionSnapshotWithRelationships(createdRow as unknown as ${ctx.Singular}, tx)` : `build${ctx.Singular}VersionSnapshot(createdRow as unknown as ${ctx.Singular})`;
9572
+ const snapshotData = ctx.hasM2M ? `await build${ctx.Singular}VersionSnapshotWithRelationships(createdRow, tx)` : `build${ctx.Singular}VersionSnapshot(createdRow)`;
9523
9573
  const markdownImport = ctx.hasHtmlOutput ? `
9524
9574
  import { renderMarkdownSync } from './markdown'` : "";
9525
- const createContext = lifecycleContext(
9526
- ctx.plural,
9527
- `,
9528
- input: input as unknown as Record<string, unknown>,
9529
- changedFields`
9530
- );
9575
+ const createContext = lifecycleContext(ctx.plural, `,
9576
+ input,
9577
+ changedFields`);
9531
9578
  const createResultContext = lifecycleContext(
9532
9579
  ctx.plural,
9533
9580
  `,
9534
9581
  entityId: created${ctx.Singular}Id,
9535
- input: input as unknown as Record<string, unknown>,
9536
- data: created${ctx.Singular}Data as unknown as Record<string, unknown>,
9582
+ input,
9583
+ data: created${ctx.Singular}Data,
9537
9584
  changedFields`
9538
9585
  );
9539
9586
  const createBeforePublishBlock = ctx.hasDraft ? `
@@ -9781,7 +9828,7 @@ export async function deleteBulk${ctxPlural}(ids: string[]): Promise<${ctxSingul
9781
9828
  return tx.delete(${ctxTableVar}).where(inArray(${ctxTableVar}.id, ids)).returning()
9782
9829
  })
9783
9830
  const deletedRowsById = new Map(
9784
- deletedRows.map((row) => [row.id, row as unknown as Record<string, unknown>])
9831
+ deletedRows.map((row) => [row.id, row])
9785
9832
  )
9786
9833
  ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.all)
9787
9834
  for (const id of ids) {
@@ -9888,7 +9935,7 @@ export async function delete${ctxSingular}(id: string): Promise<${ctxSingular}De
9888
9935
  function renderGenDeleteContentTemplate() {
9889
9936
  return `{
9890
9937
  ...lifecycleContext,
9891
- data: deletedRow as unknown as Record<string, unknown> | undefined
9938
+ data: deletedRow
9892
9939
  }`;
9893
9940
  }
9894
9941
  function renderGenDeleteContentRenderer(request) {
@@ -10050,12 +10097,13 @@ function genGetByIdContent(ctx) {
10050
10097
  const dbImports = [ctx.tableVar, "user", ...ctx.relTableImports].sort();
10051
10098
  const drizzle = ["eq"];
10052
10099
  const sortedDrizzle = [...new Set(drizzle)].sort();
10053
- const byIdHelperImports = ["formatActionDate"];
10100
+ const byIdHelperImports = [];
10054
10101
  if (ctx.hasSelectFields) byIdHelperImports.push(`resolve${ctx.Singular}SelectFields`);
10055
10102
  if (ctx.hasListRels) byIdHelperImports.push(`populate${ctx.Singular}ListRelationships`);
10056
10103
  if (ctx.hasMediaFields) byIdHelperImports.push(`resolve${ctx.Singular}MediaFields`);
10057
- const populateImport = byIdHelperImports.length > 0 ? `
10058
- import { ${byIdHelperImports.join(", ")} } from './helpers'` : "";
10104
+ const populateImport = `
10105
+ import { formatActionDate } from '@admin/utils/date/format-action-date'` + (byIdHelperImports.length > 0 ? `
10106
+ import { ${byIdHelperImports.join(", ")} } from './helpers'` : "");
10059
10107
  return renderGenGetByIdContentServerActionTemplate({
10060
10108
  join: dbImports.join(", "),
10061
10109
  joinText: sortedDrizzle.join(", "),
@@ -10122,12 +10170,13 @@ function genGetBySlugContent(ctx) {
10122
10170
  const dbImports = [ctx.tableVar, "user", ...ctx.relTableImports].sort();
10123
10171
  const drizzle = ["eq"];
10124
10172
  const sortedDrizzle = [...new Set(drizzle)].sort();
10125
- const bySlugHelperImports = ["formatActionDate"];
10173
+ const bySlugHelperImports = [];
10126
10174
  if (ctx.hasSelectFields) bySlugHelperImports.push(`resolve${ctx.Singular}SelectFields`);
10127
10175
  if (ctx.hasListRels) bySlugHelperImports.push(`populate${ctx.Singular}ListRelationships`);
10128
10176
  if (ctx.hasMediaFields) bySlugHelperImports.push(`resolve${ctx.Singular}MediaFields`);
10129
- const populateImport = bySlugHelperImports.length > 0 ? `
10130
- import { ${bySlugHelperImports.join(", ")} } from './helpers'` : "";
10177
+ const populateImport = `
10178
+ import { formatActionDate } from '@admin/utils/date/format-action-date'` + (bySlugHelperImports.length > 0 ? `
10179
+ import { ${bySlugHelperImports.join(", ")} } from './helpers'` : "");
10131
10180
  return renderGenGetBySlugContentServerActionTemplate({
10132
10181
  join: dbImports.join(", "),
10133
10182
  joinText: sortedDrizzle.join(", "),
@@ -10212,15 +10261,11 @@ ${ctxSearchBlock}${ctxFilterConditions}
10212
10261
  ? Math.max(0, rawOffset)
10213
10262
  : 0
10214
10263
  const isPaginated = typeof limit === 'number'
10215
- let results: unknown[]
10216
- if (isPaginated) {
10217
- const paginatedQuery = offset > 0
10218
- ? orderedQuery.limit(limit).offset(offset)
10219
- : orderedQuery.limit(limit)
10220
- results = await paginatedQuery
10221
- } else {
10222
- results = await orderedQuery
10223
- }
10264
+ const results = isPaginated
10265
+ ? offset > 0
10266
+ ? await orderedQuery.limit(limit).offset(offset)
10267
+ : await orderedQuery.limit(limit)
10268
+ : await orderedQuery
10224
10269
 
10225
10270
  const total = isPaginated
10226
10271
  ? Number((await db.select({ count: sql\`count(*)\` }).from(${ctxTableVar})${ctxWhere})[0]?.count ?? 0)
@@ -10245,13 +10290,14 @@ function genGetPluralContent(ctx) {
10245
10290
  drizzle.push("sql");
10246
10291
  if (ctx.hasRelationships) drizzle.push("eq");
10247
10292
  const sortedDrizzle = [...new Set(drizzle)].sort();
10248
- const typeImports = [`${ctx.Plural}Page`, `${ctx.Plural}Query`, `${ctx.Singular}`];
10249
- const helperImports = ["formatActionDate"];
10293
+ const typeImports = [`${ctx.Plural}Page`, `${ctx.Plural}Query`];
10294
+ const helperImports = [];
10250
10295
  if (ctx.hasSelectFields) helperImports.push(`resolve${ctx.Singular}SelectFields`);
10251
10296
  if (ctx.hasListRels) helperImports.push(`populate${ctx.Singular}ListRelationships`);
10252
10297
  if (ctx.hasMediaFields) helperImports.push(`resolve${ctx.Singular}MediaFields`);
10253
- const populateImport = helperImports.length > 0 ? `
10254
- import { ${helperImports.join(", ")} } from './helpers'` : "";
10298
+ const populateImport = `
10299
+ import { formatActionDate } from '@admin/utils/date/format-action-date'` + (helperImports.length > 0 ? `
10300
+ import { ${helperImports.join(", ")} } from './helpers'` : "");
10255
10301
  const dateRangeHelper = hasDateRangeFilters ? `
10256
10302
 
10257
10303
  ${renderExclusiveDateUpperBoundHelper()}` : "";
@@ -10319,7 +10365,14 @@ export async function get${ctxSingular}SelectOptions(
10319
10365
  .where(eq(${ctxSelectOptionsTableVar}.fieldName, fieldName))
10320
10366
  .orderBy(asc(${ctxSelectOptionsTableVar}.label))
10321
10367
 
10322
- return rows as ${ctxSingular}SelectOption[]
10368
+ return rows.map((row) => ({
10369
+ id: row.id,
10370
+ fieldName,
10371
+ label: row.label,
10372
+ value: row.value,
10373
+ createdAt: row.createdAt,
10374
+ updatedAt: row.updatedAt
10375
+ }))
10323
10376
  } catch (error) {
10324
10377
  console.error('Error fetching ${ctxSingularText} select options:', error)
10325
10378
  return []
@@ -10372,120 +10425,91 @@ function genGetVersionsContent(ctx) {
10372
10425
  });
10373
10426
  }
10374
10427
 
10375
- // adapters/next/templates/renderers/generators/actions/action-helpers/build-format-action-date-source.ts
10376
- function renderBuildFormatActionDateSourceDateTemplate({
10377
- optionsExportedExport
10378
- }) {
10379
- return `${optionsExportedExport}function formatActionDate(value: unknown): string {
10380
- if (typeof value !== 'string') return ''
10381
- const date = new Date(value)
10382
- if (Number.isNaN(date.getTime())) return ''
10383
-
10384
- return date.toLocaleDateString('en-US', {
10385
- month: 'short',
10386
- day: '2-digit',
10387
- year: 'numeric',
10388
- timeZone: 'UTC'
10389
- })
10390
- }`;
10391
- }
10392
-
10393
- // adapters/next/generators/actions/action-helpers/build-format-action-date-source.ts
10394
- function buildFormatActionDateSource(options) {
10395
- return renderBuildFormatActionDateSourceDateTemplate({
10396
- optionsExportedExport: options.exported ? "export " : ""
10397
- });
10398
- }
10399
-
10400
10428
  // adapters/next/templates/renderers/generators/actions/entity-file-contents/generate-resolve-media-function.ts
10401
10429
  function renderResolveMediaFunctionIdsTemplate({
10402
10430
  ctxSingular,
10403
- collectSingle,
10404
- collectArrayContent,
10405
- map,
10406
- arrayFieldsContent,
10407
- assignSingle,
10408
- assignArrayContent
10431
+ collectStatements,
10432
+ listResolverBlock,
10433
+ assignments
10409
10434
  }) {
10410
- return `export async function resolve${ctxSingular}MediaFields(rows: ${ctxSingular}[]): Promise<${ctxSingular}[]> {
10411
- if (rows.length === 0) return rows
10412
-
10435
+ return `export async function resolve${ctxSingular}MediaFields(rows: ${ctxSingular}MediaResolvable[]): Promise<${ctxSingular}[]> {
10413
10436
  const ids = new Set<string>()
10414
- ${collectSingle}${collectArrayContent}
10415
-
10416
- if (ids.size === 0) {
10417
- for (const row of rows) {
10418
- ${map}${arrayFieldsContent}
10419
- }
10420
- return rows
10437
+ for (const row of rows) {
10438
+ ${collectStatements}
10421
10439
  }
10422
10440
 
10423
- const mediaRows = await db.select().from(adminMedia).where(inArray(adminMedia.id, [...ids]))
10424
- const mediaMap = new Map<string, AdminMedia>(mediaRows.map(m => [m.id, m]))
10441
+ const mediaRows =
10442
+ ids.size === 0
10443
+ ? []
10444
+ : await db.select().from(adminMedia).where(inArray(adminMedia.id, [...ids]))
10445
+ const mediaMap = new Map<string, AdminMedia>(mediaRows.map((media) => [media.id, media]))
10425
10446
 
10426
- for (const row of rows) {
10427
- ${assignSingle}${assignArrayContent}
10428
- }
10447
+ function resolveMedia(reference: string | null): AdminMedia | null {
10448
+ if (!reference || reference.startsWith('http')) return null
10449
+ const media = mediaMap.get(reference)
10450
+ if (!media) return null
10429
10451
 
10430
- return rows
10452
+ return {
10453
+ ...media,
10454
+ createdAtFormatted: formatActionDate(media.createdAt),
10455
+ updatedAtFormatted: formatActionDate(media.updatedAt)
10456
+ }
10457
+ }
10458
+ ${listResolverBlock}
10459
+ return rows.map((row) => ({
10460
+ ...row,
10461
+ ${assignments}
10462
+ }))
10431
10463
  }`;
10432
10464
  }
10465
+ function renderResolveMediaListResolverTemplate() {
10466
+ return `
10467
+ function resolveMediaList(references: string[]): AdminMedia[] {
10468
+ return references
10469
+ .map((reference) => resolveMedia(reference))
10470
+ .filter((media): media is AdminMedia => media !== null)
10471
+ }
10472
+ `;
10473
+ }
10474
+ function renderResolveMediaFunctionRenderer(request) {
10475
+ switch (request.kind) {
10476
+ case "renderResolveMediaFunctionIdsTemplate":
10477
+ return renderResolveMediaFunctionIdsTemplate(request.props);
10478
+ case "renderResolveMediaListResolverTemplate":
10479
+ return renderResolveMediaListResolverTemplate();
10480
+ }
10481
+ throw new Error("Unsupported renderer request");
10482
+ }
10433
10483
 
10434
10484
  // adapters/next/generators/actions/entity-file-contents/generate-resolve-media-function.ts
10435
10485
  function generateResolveMediaFunction(ctx) {
10436
10486
  const singleFields = ctx.mediaFields.map((f) => f.name);
10437
10487
  const arrayFields = ctx.galleryFields.map((f) => f.name);
10438
- const collectSingle = singleFields.map(
10439
- (name) => ` for (const row of rows) {
10440
- const val = row.${name}
10441
- if (val && typeof val === 'string' && !val.startsWith('http')) ids.add(val)
10442
- }`
10443
- ).join("\n");
10444
- const collectArray = arrayFields.map(
10445
- (name) => ` for (const row of rows) {
10446
- const arr = row.${name}
10447
- if (Array.isArray(arr)) for (const id of arr) { if (id && !id.startsWith('http')) ids.add(id) }
10488
+ const collectStatements = [
10489
+ ...singleFields.map(
10490
+ (name) => ` if (row.${name} && !row.${name}.startsWith('http')) ids.add(row.${name})`
10491
+ ),
10492
+ ...arrayFields.map(
10493
+ (name) => ` for (const id of row.${name}) {
10494
+ if (id && !id.startsWith('http')) ids.add(id)
10448
10495
  }`
10449
- ).join("\n");
10450
- const assignSingle = singleFields.map(
10451
- (name) => ` row.${name}Media = row.${name} && !row.${name}.startsWith('http')
10452
- ? (() => {
10453
- const media = mediaMap.get(row.${name})
10454
- if (!media) return null
10455
- return {
10456
- ...media,
10457
- createdAtFormatted: formatActionDate(media.createdAt),
10458
- updatedAtFormatted: formatActionDate(media.updatedAt)
10459
- }
10460
- })()
10461
- : null`
10462
- ).join("\n");
10463
- const assignArray = arrayFields.map(
10464
- (name) => ` row.${name}Media = Array.isArray(row.${name})
10465
- ? row.${name}
10466
- .map((id: string) => {
10467
- const media = mediaMap.get(id)
10468
- if (!media) return null
10469
- return {
10470
- ...media,
10471
- createdAtFormatted: formatActionDate(media.createdAt),
10472
- updatedAtFormatted: formatActionDate(media.updatedAt)
10473
- }
10474
- })
10475
- .filter(Boolean) as AdminMedia[]
10476
- : []`
10477
- ).join("\n");
10478
- return renderResolveMediaFunctionIdsTemplate({
10479
- ctxSingular: ctx.Singular,
10480
- collectSingle,
10481
- collectArrayContent: collectArray ? `
10482
- ${collectArray}` : "",
10483
- map: singleFields.map((name) => ` row.${name}Media = null`).join("\n"),
10484
- arrayFieldsContent: arrayFields.length > 0 ? `
10485
- ${arrayFields.map((name) => ` row.${name}Media = []`).join("\n")}` : "",
10486
- assignSingle,
10487
- assignArrayContent: assignArray ? `
10488
- ${assignArray}` : ""
10496
+ )
10497
+ ].join("\n");
10498
+ const assignments = [
10499
+ ...singleFields.map((name) => ` ${name}Media: resolveMedia(row.${name})`),
10500
+ ...arrayFields.map((name) => ` ${name}Media: resolveMediaList(row.${name})`)
10501
+ ].join(",\n");
10502
+ return renderResolveMediaFunctionRenderer({
10503
+ kind: "renderResolveMediaFunctionIdsTemplate",
10504
+ props: {
10505
+ ctxSingular: ctx.Singular,
10506
+ collectStatements,
10507
+ listResolverBlock: arrayFields.length > 0 ? renderResolveMediaFunctionRenderer({
10508
+ kind: "renderResolveMediaListResolverTemplate",
10509
+ props: {}
10510
+ }) : "",
10511
+ assignments
10512
+ }
10489
10513
  });
10490
10514
  }
10491
10515
 
@@ -10508,12 +10532,8 @@ function renderResolveSelectFieldsSourceDynamicRowsTemplate({
10508
10532
  }
10509
10533
  `;
10510
10534
  }
10511
- function renderResolveSelectFieldsSourceStaticSelectOptionLabelsTemplate({
10512
- staticLabelMaps,
10513
- exportedExport,
10514
- singular,
10515
- dynamicRowsBlock,
10516
- assignments
10535
+ function renderSelectOptionLabelMapsTemplate({
10536
+ staticLabelMaps
10517
10537
  }) {
10518
10538
  return `const STATIC_SELECT_OPTION_LABELS = {
10519
10539
  ${staticLabelMaps}
@@ -10526,7 +10546,56 @@ function cloneSelectOptionLabelMaps(): Record<string, Record<string, string>> {
10526
10546
  { ...labels }
10527
10547
  ])
10528
10548
  )
10549
+ }`;
10529
10550
  }
10551
+ function renderResolveSelectFieldsSourceStaticSelectOptionLabelsTemplate({
10552
+ staticLabelMaps,
10553
+ exportedExport,
10554
+ singular,
10555
+ inputType,
10556
+ outputType,
10557
+ dynamicRowsBlock,
10558
+ labelDeclarations,
10559
+ assignments
10560
+ }) {
10561
+ return `${renderSelectOptionLabelMapsTemplate({ staticLabelMaps })}
10562
+
10563
+ function toSelectOptionValues(
10564
+ values: string[],
10565
+ labels: Record<string, string>
10566
+ ): SelectOptionValue[] {
10567
+ return values
10568
+ .filter((item) => item.length > 0)
10569
+ .map((item) => ({
10570
+ label: labels[item] ?? item,
10571
+ value: item
10572
+ }))
10573
+ }
10574
+
10575
+ ${exportedExport}async function resolve${singular}SelectFields(
10576
+ rows: ${inputType}[]
10577
+ ): Promise<${outputType}[]> {
10578
+ if (rows.length === 0) return []
10579
+
10580
+ const labelMaps = cloneSelectOptionLabelMaps()
10581
+ ${dynamicRowsBlock}
10582
+ ${labelDeclarations}
10583
+
10584
+ return rows.map((row) => ({
10585
+ ...row,
10586
+ ${assignments}
10587
+ }))
10588
+ }`;
10589
+ }
10590
+ function renderResolveSelectFieldsSourceNestedTemplate({
10591
+ staticLabelMaps,
10592
+ exportedExport,
10593
+ singular,
10594
+ rowType,
10595
+ dynamicRowsBlock,
10596
+ assignments
10597
+ }) {
10598
+ return `${renderSelectOptionLabelMapsTemplate({ staticLabelMaps })}
10530
10599
 
10531
10600
  function toSelectOptionValues(value: unknown, labels: Record<string, string>): SelectOptionValue[] {
10532
10601
  const values = Array.isArray(value) ? value : value ? [value] : []
@@ -10555,25 +10624,25 @@ function resolveSelectFieldPath(
10555
10624
  const value = source[fieldName]
10556
10625
  if (Array.isArray(value)) {
10557
10626
  for (const item of value) {
10558
- if (item && typeof item === 'object') {
10559
- resolveSelectFieldPath(item as Record<string, unknown>, rest, labels)
10627
+ if (isRecord(item)) {
10628
+ resolveSelectFieldPath(item, rest, labels)
10560
10629
  }
10561
10630
  }
10562
10631
  return
10563
10632
  }
10564
10633
 
10565
- if (value && typeof value === 'object') {
10566
- resolveSelectFieldPath(value as Record<string, unknown>, rest, labels)
10634
+ if (isRecord(value)) {
10635
+ resolveSelectFieldPath(value, rest, labels)
10567
10636
  }
10568
10637
  }
10569
10638
 
10570
- ${exportedExport}async function resolve${singular}SelectFields(rows: ${singular}[]): Promise<${singular}[]> {
10639
+ ${exportedExport}async function resolve${singular}SelectFields(rows: ${rowType}[]): Promise<${rowType}[]> {
10571
10640
  if (rows.length === 0) return rows
10572
10641
 
10573
10642
  const labelMaps = cloneSelectOptionLabelMaps()
10574
10643
  ${dynamicRowsBlock}
10575
10644
  for (const row of rows) {
10576
- const source = row as unknown as Record<string, unknown>
10645
+ const source: Record<string, unknown> = row
10577
10646
  ${assignments}
10578
10647
  }
10579
10648
 
@@ -10586,6 +10655,8 @@ function renderSelectFieldsResolverRenderer(request) {
10586
10655
  return renderResolveSelectFieldsSourceDynamicRowsTemplate(request.props);
10587
10656
  case "renderResolveSelectFieldsSourceStaticSelectOptionLabelsTemplate":
10588
10657
  return renderResolveSelectFieldsSourceStaticSelectOptionLabelsTemplate(request.props);
10658
+ case "renderResolveSelectFieldsSourceNestedTemplate":
10659
+ return renderResolveSelectFieldsSourceNestedTemplate(request.props);
10589
10660
  }
10590
10661
  throw new Error("Unsupported renderer request");
10591
10662
  }
@@ -10596,6 +10667,7 @@ function generateResolveSelectFieldsSource({
10596
10667
  selectFields,
10597
10668
  selectOptionsTableVar,
10598
10669
  hasCreatableSelectFields,
10670
+ hasMediaFields,
10599
10671
  exported
10600
10672
  }) {
10601
10673
  const staticLabelMaps = selectFields.map((readField) => {
@@ -10609,17 +10681,47 @@ function generateResolveSelectFieldsSource({
10609
10681
  selectOptionsTableVar
10610
10682
  }
10611
10683
  }) : "";
10612
- const assignments = selectFields.map((readField) => {
10613
- const key = readField.path.join(".");
10614
- return ` resolveSelectFieldPath(source, ${JSON.stringify(readField.path)}, labelMaps[${JSON.stringify(key)}] ?? {})`;
10615
- }).join("\n");
10684
+ const hasNestedSelectFields = selectFields.some((readField) => readField.path.length > 1);
10685
+ const resolvedType = hasMediaFields ? `${Singular}MediaResolvable` : Singular;
10686
+ if (hasNestedSelectFields) {
10687
+ const assignments2 = selectFields.map((readField) => {
10688
+ const key = readField.path.join(".");
10689
+ return ` resolveSelectFieldPath(source, ${JSON.stringify(readField.path)}, labelMaps[${JSON.stringify(key)}] ?? {})`;
10690
+ }).join("\n");
10691
+ return renderSelectFieldsResolverRenderer({
10692
+ kind: "renderResolveSelectFieldsSourceNestedTemplate",
10693
+ props: {
10694
+ staticLabelMaps,
10695
+ exportedExport: exported ? "export " : "",
10696
+ singular: Singular,
10697
+ rowType: resolvedType,
10698
+ dynamicRowsBlock,
10699
+ assignments: assignments2
10700
+ }
10701
+ });
10702
+ }
10703
+ const labelVariables = new Map(
10704
+ selectFields.map((readField) => [
10705
+ readField.path[0],
10706
+ `${toCamelCase(singularize(readField.path[0]))}Labels`
10707
+ ])
10708
+ );
10709
+ const labelDeclarations = [...labelVariables].map(
10710
+ ([fieldName, variable]) => ` const ${variable} = labelMaps[${JSON.stringify(fieldName)}] ?? {}`
10711
+ ).join("\n");
10712
+ const assignments = [...labelVariables].map(
10713
+ ([fieldName, variable]) => ` ${fieldName}: toSelectOptionValues(row.${fieldName}, ${variable})`
10714
+ ).join(",\n");
10616
10715
  return renderSelectFieldsResolverRenderer({
10617
10716
  kind: "renderResolveSelectFieldsSourceStaticSelectOptionLabelsTemplate",
10618
10717
  props: {
10619
10718
  staticLabelMaps,
10620
10719
  exportedExport: exported ? "export " : "",
10621
10720
  singular: Singular,
10721
+ inputType: `${Singular}SelectResolvable`,
10722
+ outputType: resolvedType,
10622
10723
  dynamicRowsBlock,
10724
+ labelDeclarations,
10623
10725
  assignments
10624
10726
  }
10625
10727
  });
@@ -10632,6 +10734,7 @@ function generateResolveSelectFieldsFunction(ctx) {
10632
10734
  selectFields: ctx.selectFields,
10633
10735
  selectOptionsTableVar: ctx.selectOptionsTableVar,
10634
10736
  hasCreatableSelectFields: ctx.hasCreatableSelectFields,
10737
+ hasMediaFields: ctx.hasMediaFields,
10635
10738
  exported: true
10636
10739
  });
10637
10740
  }
@@ -10664,11 +10767,10 @@ export function get${ctxSingular}SlugRetryLimit(): number {
10664
10767
  }
10665
10768
 
10666
10769
  export function is${ctxSingular}SlugConflict(error: unknown): boolean {
10667
- if (typeof error !== 'object' || error === null) return false
10668
- const record = error as Record<string, unknown>
10669
- if (record.code !== '23505') return false
10670
- const constraint = typeof record.constraint === 'string' ? record.constraint : ''
10671
- const message = typeof record.message === 'string' ? record.message : ''
10770
+ if (!isRecord(error)) return false
10771
+ if (error.code !== '23505') return false
10772
+ const constraint = typeof error.constraint === 'string' ? error.constraint : ''
10773
+ const message = typeof error.message === 'string' ? error.message : ''
10672
10774
  return constraint === SLUG_CONSTRAINT_NAME || message.includes(SLUG_CONSTRAINT_NAME)
10673
10775
  }
10674
10776
 
@@ -10717,6 +10819,59 @@ function generateSlugHelpers(ctx) {
10717
10819
  });
10718
10820
  }
10719
10821
 
10822
+ // adapters/next/templates/renderers/generators/actions/entity-file-contents/generate-to-entity-function.ts
10823
+ function renderToEntityFunctionTemplate({
10824
+ ctxSingular,
10825
+ ctxTableVar,
10826
+ fieldMappings
10827
+ }) {
10828
+ return `export function to${ctxSingular}(row: typeof ${ctxTableVar}.$inferSelect): ${ctxSingular} {
10829
+ return {
10830
+ ${fieldMappings}
10831
+ }
10832
+ }`;
10833
+ }
10834
+
10835
+ // adapters/next/generators/actions/entity-file-contents/generate-to-entity-function.ts
10836
+ function generateToEntityFunction({
10837
+ Singular,
10838
+ tableVar,
10839
+ allDbFields,
10840
+ htmlOutputFields,
10841
+ m2mFields,
10842
+ mediaFields,
10843
+ galleryFields
10844
+ }) {
10845
+ const rowExpression = (field) => {
10846
+ if (field.type === "relationship") return field.multiple ? "[]" : "null";
10847
+ if (field.type === "select") {
10848
+ return field.multiple ? `(row.${field.name} ?? []).map((value) => ({ label: value, value }))` : `row.${field.name} ? [{ label: row.${field.name}, value: row.${field.name} }] : []`;
10849
+ }
10850
+ return `row.${field.name}`;
10851
+ };
10852
+ const hasTimestampFields = allDbFields.some((f) => f.name === "createdAt") && allDbFields.some((f) => f.name === "updatedAt");
10853
+ const hasFormattedTimestampFields = allDbFields.some((f) => f.name === "createdAtFormatted");
10854
+ const timestampMappings = hasTimestampFields && !hasFormattedTimestampFields ? [
10855
+ " createdAtFormatted: formatActionDate(row.createdAt)",
10856
+ " updatedAtFormatted: formatActionDate(row.updatedAt)"
10857
+ ] : [];
10858
+ const fieldMappings = [
10859
+ ...allDbFields.map((field) => ` ${quotePropertyName(field.name)}: ${rowExpression(field)}`),
10860
+ ...htmlOutputFields.map((field) => ` ${field.name}Html: row.${field.name}Html ?? ''`),
10861
+ ...m2mFields.map((field) => ` ${quotePropertyName(field.name)}: []`),
10862
+ ...mediaFields.map((field) => ` ${quotePropertyName(`${field.name}Media`)}: null`),
10863
+ ...galleryFields.map((field) => ` ${quotePropertyName(`${field.name}Media`)}: []`),
10864
+ ...timestampMappings,
10865
+ " createdByUser: null",
10866
+ " updatedByUser: null"
10867
+ ].join(",\n");
10868
+ return renderToEntityFunctionTemplate({
10869
+ ctxSingular: Singular,
10870
+ ctxTableVar: tableVar,
10871
+ fieldMappings
10872
+ });
10873
+ }
10874
+
10720
10875
  // adapters/next/templates/renderers/generators/actions/entity-file-contents/generate-version-snapshot-helpers.ts
10721
10876
  function renderVersionSnapshotHelpersRowsTemplate({
10722
10877
  fieldName,
@@ -10769,7 +10924,7 @@ const VERSION_RESTORE_KEYS = [${restoreKeys}] as const
10769
10924
  const VERSION_M2M_KEYS = [${m2mKeys}] as const
10770
10925
 
10771
10926
  export function build${ctxSingular}VersionSnapshot(row: Partial<${ctxSingular}> | Record<string, unknown>): Record<string, unknown> {
10772
- const source = row as unknown as Record<string, unknown>
10927
+ const source: Record<string, unknown> = row
10773
10928
  const snapshot: Record<string, unknown> = {}
10774
10929
 
10775
10930
  for (const key of VERSION_SNAPSHOT_KEYS) {
@@ -10884,12 +11039,11 @@ function genHelpersContent(ctx) {
10884
11039
  if (ctx.hasCreatableSelectFields) {
10885
11040
  schemaImports.add(ctx.selectOptionsTableVar);
10886
11041
  }
10887
- if (ctx.hasSlug) {
10888
- schemaImports.add(ctx.tableVar);
10889
- }
11042
+ schemaImports.add(ctx.tableVar);
10890
11043
  if (ctx.hasM2M || ctx.hasListRels || ctx.hasMediaFields || ctx.hasCreatableSelectFields || ctx.hasSlug) {
10891
11044
  lines.push(`import db from '@admin/db'`);
10892
11045
  }
11046
+ lines.push(`import { formatActionDate } from '@admin/utils/date/format-action-date'`);
10893
11047
  const tables = [...schemaImports].sort();
10894
11048
  if (tables.length > 0) {
10895
11049
  lines.push(`import { ${tables.join(", ")} } from '@admin/db/schema'`);
@@ -10903,11 +11057,17 @@ function genHelpersContent(ctx) {
10903
11057
  if (ctx.hasSlug) {
10904
11058
  lines.push(`import { slugify } from '@admin/utils/validation/slugify'`);
10905
11059
  }
10906
- const typeImports = ctx.hasSelectFields ? `${ctx.Singular}, SelectOptionValue` : ctx.Singular;
11060
+ if (ctx.hasSlug || ctx.hasNestedSelectFields || ctx.hasListRels) {
11061
+ lines.push(`import { isRecord } from '@admin/utils/shared/is-record'`);
11062
+ }
11063
+ const typeImports = [
11064
+ ctx.Singular,
11065
+ ...ctx.hasMediaFields ? [`${ctx.Singular}MediaResolvable`] : [],
11066
+ ...ctx.hasSelectFields && !ctx.hasNestedSelectFields ? [`${ctx.Singular}SelectResolvable`] : [],
11067
+ ...ctx.hasSelectFields ? ["SelectOptionValue"] : []
11068
+ ].join(", ");
10907
11069
  lines.push(`import type { ${typeImports} } from './types'`);
10908
11070
  lines.push("");
10909
- lines.push(buildFormatActionDateSource({ exported: true }));
10910
- lines.push("");
10911
11071
  if (ctx.hasSlug) {
10912
11072
  lines.push(generateSlugHelpers(ctx));
10913
11073
  lines.push("");
@@ -10921,6 +11081,18 @@ function genHelpersContent(ctx) {
10921
11081
  lines.push(generateResolveMediaFunction(ctx));
10922
11082
  lines.push("");
10923
11083
  }
11084
+ lines.push(
11085
+ generateToEntityFunction({
11086
+ Singular: ctx.Singular,
11087
+ tableVar: ctx.tableVar,
11088
+ allDbFields: ctx.allDbFields,
11089
+ htmlOutputFields: ctx.htmlOutputFields,
11090
+ m2mFields: ctx.m2mFields,
11091
+ mediaFields: ctx.mediaFields,
11092
+ galleryFields: ctx.galleryFields
11093
+ })
11094
+ );
11095
+ lines.push("");
10924
11096
  if (ctx.hasSelectFields) {
10925
11097
  lines.push(generateResolveSelectFieldsFunction(ctx));
10926
11098
  lines.push("");
@@ -11151,10 +11323,12 @@ import { ${ctxCamelPlural}CacheTags } from './types'
11151
11323
  import { get${ctxSingular}ById } from './get-${kebabSingular}-by-id'
11152
11324
  import {
11153
11325
  build${ctxSingular}RestoreData,
11154
- ${snapshotImport}
11326
+ ${snapshotImport},
11327
+ to${ctxSingular}
11155
11328
  } from './helpers'
11156
11329
  ${lifecycleHookImport2}
11157
- import type { ${ctxSingular}UpdateResult, ${ctxSingular} } from './types'
11330
+ import { isRecord } from '@admin/utils/shared/is-record'
11331
+ import type { ${ctxSingular}UpdateResult } from './types'
11158
11332
 
11159
11333
  export async function restore${ctxSingular}Version(
11160
11334
  versionId: string
@@ -11173,7 +11347,7 @@ export async function restore${ctxSingular}Version(
11173
11347
 
11174
11348
  if (!versionRow) throw new Error('${ctxSingular} version not found')
11175
11349
 
11176
- const versionData = versionRow.data as Record<string, unknown>
11350
+ const versionData = isRecord(versionRow.data) ? versionRow.data : {}
11177
11351
  const restoreData = build${ctxSingular}RestoreData(versionData)
11178
11352
  const auditRequestMetadata = await getAuditRequestMetadata()
11179
11353
  const lifecycleContext = ${restoreContext}
@@ -11211,7 +11385,7 @@ ${restoreM2MContent}
11211
11385
 
11212
11386
  return {
11213
11387
  success: true,
11214
- ${ctxCamelSingular}: restored${ctxSingular} ?? (result[0] as unknown as ${ctxSingular})
11388
+ ${ctxCamelSingular}: restored${ctxSingular} ?? to${ctxSingular}(result[0])
11215
11389
  }
11216
11390
  } catch (error) {
11217
11391
  console.error('Error restoring ${ctxSingularText} version:', error)
@@ -11223,7 +11397,7 @@ ${restoreM2MContent}
11223
11397
  function renderGenRestoreContentTemplate({ ctxSingular }) {
11224
11398
  return `{
11225
11399
  ...lifecycleContext,
11226
- data: (restored${ctxSingular} ?? result[0]) as unknown as Record<string, unknown>
11400
+ data: restored${ctxSingular} ?? to${ctxSingular}(result[0])
11227
11401
  }`;
11228
11402
  }
11229
11403
  function renderGenRestoreContentRenderer(request) {
@@ -11289,7 +11463,7 @@ function genRestoreContent(ctx) {
11289
11463
  metadata: { restoredFromVersion: versionRow.version }`
11290
11464
  );
11291
11465
  const snapshotImport = ctx.hasM2M ? `build${ctx.Singular}VersionSnapshotWithRelationships` : `build${ctx.Singular}VersionSnapshot`;
11292
- const currentSnapshot = ctx.hasM2M ? `await build${ctx.Singular}VersionSnapshotWithRelationships(currentRow as unknown as ${ctx.Singular}, tx)` : `build${ctx.Singular}VersionSnapshot(currentRow as unknown as ${ctx.Singular})`;
11466
+ const currentSnapshot = ctx.hasM2M ? `await build${ctx.Singular}VersionSnapshotWithRelationships(currentRow, tx)` : `build${ctx.Singular}VersionSnapshot(currentRow)`;
11293
11467
  const dbImports = [
11294
11468
  "entityVersions",
11295
11469
  ctx.tableVar,
@@ -11456,10 +11630,11 @@ import {
11456
11630
  is${ctxSingular}SlugConflict,
11457
11631
  resolveUnique${ctxSingular}Slug,
11458
11632
  ${snapshotImport},
11459
- has${ctxSingular}VersionChanges
11633
+ has${ctxSingular}VersionChanges,
11634
+ to${ctxSingular}
11460
11635
  } from './helpers'
11461
11636
  ${lifecycleHookImport2}
11462
- import type { ${ctxSingular}UpdateInput, ${ctxSingular}UpdateResult, ${ctxSingular} } from './types'${markdownImport}
11637
+ import type { ${ctxSingular}UpdateInput, ${ctxSingular}UpdateResult } from './types'${markdownImport}
11463
11638
 
11464
11639
  export async function update${ctxSingular}(input: ${ctxSingular}UpdateInput): Promise<${ctxSingular}UpdateResult> {
11465
11640
  const actor = await requireRole([UserRole.ADMIN, UserRole.EDITOR])
@@ -11520,10 +11695,7 @@ ${updateBeforePublishBlock}
11520
11695
  }
11521
11696
  }
11522
11697
  ${htmlUpdateBlock}
11523
- changedFields = getChangedFieldNames(
11524
- previousRow as unknown as Record<string, unknown>,
11525
- processedData
11526
- )
11698
+ changedFields = getChangedFieldNames(previousRow, processedData)
11527
11699
  result = await db.transaction(async (tx) => {
11528
11700
  const previousSnapshot = ${previousSnapshot}
11529
11701
  const nextSnapshot = {
@@ -11563,7 +11735,7 @@ ${htmlUpdateBlock}
11563
11735
  ${ctxCacheInvalidationFn}(${ctxCamelPlural}CacheTags.byId(id))
11564
11736
  ${ctxCacheInvalidationFn}(entityVersionsCacheTags.for('${ctxPlural}', id))
11565
11737
  const updated${ctxSingular} = await get${ctxSingular}ById(id)
11566
- const updated${ctxSingular}Data = updated${ctxSingular} ?? (result[0] as unknown as ${ctxSingular})
11738
+ const updated${ctxSingular}Data = updated${ctxSingular} ?? to${ctxSingular}(result[0])
11567
11739
  const updated${ctxSingular}Context = ${updateResultContext}
11568
11740
  ${updateAfterPublishBlock}
11569
11741
  ${afterHook2}
@@ -11585,7 +11757,7 @@ function genUpdateContent(ctx) {
11585
11757
  const kebabSingular = toKebabCase(ctx.singular);
11586
11758
  const slugSourceValue = ctx.slugSourceField ? `updateData.${ctx.slugSourceField} ?? previousRow.${ctx.slugSourceField}` : "undefined";
11587
11759
  const snapshotImport = ctx.hasM2M ? `build${ctx.Singular}VersionSnapshotWithRelationships` : `build${ctx.Singular}VersionSnapshot`;
11588
- const previousSnapshot = ctx.hasM2M ? `await build${ctx.Singular}VersionSnapshotWithRelationships(previousRow as unknown as ${ctx.Singular}, tx)` : `build${ctx.Singular}VersionSnapshot(previousRow as unknown as ${ctx.Singular})`;
11760
+ const previousSnapshot = ctx.hasM2M ? `await build${ctx.Singular}VersionSnapshotWithRelationships(previousRow, tx)` : `build${ctx.Singular}VersionSnapshot(previousRow)`;
11589
11761
  const markdownImport = ctx.hasHtmlOutput ? `
11590
11762
  import { renderMarkdownSync } from './markdown'` : "";
11591
11763
  const htmlUpdateBlock = ctx.hasHtmlOutput ? `
@@ -11598,14 +11770,14 @@ import { renderMarkdownSync } from './markdown'` : "";
11598
11770
  ctx.plural,
11599
11771
  `,
11600
11772
  entityId: id,
11601
- input: updateData as unknown as Record<string, unknown>`
11773
+ input: updateData`
11602
11774
  );
11603
11775
  const updateResultContext = lifecycleContext(
11604
11776
  ctx.plural,
11605
11777
  `,
11606
11778
  entityId: id,
11607
- input: updateData as unknown as Record<string, unknown>,
11608
- data: updated${ctx.Singular}Data as unknown as Record<string, unknown>,
11779
+ input: updateData,
11780
+ data: updated${ctx.Singular}Data,
11609
11781
  changedFields`
11610
11782
  );
11611
11783
  const updateBeforePublishBlock = ctx.hasDraft ? `
@@ -12121,7 +12293,7 @@ ${selects}
12121
12293
 
12122
12294
  // adapters/next/templates/renderers/generators/actions/action-helpers/build-read-select-option-type.ts
12123
12295
  function renderBuildReadSelectOptionTypeComponentTemplate() {
12124
- return `export interface SelectOptionValue {
12296
+ return `export type SelectOptionValue = {
12125
12297
  label: string
12126
12298
  value: string
12127
12299
  }`;
@@ -12226,19 +12398,13 @@ ${[regularSelects, relSelects, ...extraSelects].filter(Boolean).join(",\n")}
12226
12398
  // adapters/next/templates/renderers/generators/actions/action-helpers/build-result-mapping.ts
12227
12399
  function renderBuildResultMappingResultTemplate({
12228
12400
  plural,
12229
- rowDeclarations,
12230
12401
  rowMappings
12231
12402
  }) {
12232
12403
  return `
12233
12404
 
12234
- const mapped${plural} = results.map((rawRow) => {
12235
- const row = rawRow as Record<string, unknown>
12236
- ${rowDeclarations}
12237
-
12238
- return {
12405
+ const mapped${plural} = results.map((row) => ({
12239
12406
  ${rowMappings}
12240
- }
12241
- })`;
12407
+ }))`;
12242
12408
  }
12243
12409
  function renderBuildResultMappingResultDetailsTemplate({
12244
12410
  mapBlock,
@@ -12249,7 +12415,7 @@ function renderBuildResultMappingResultDetailsTemplate({
12249
12415
  return `${mapBlock}
12250
12416
 
12251
12417
  const populated${plural} = await Promise.all(
12252
- mapped${plural}.map(record => populate${singular}ListRelationships(record as ${singular}))
12418
+ mapped${plural}.map((record) => populate${singular}ListRelationships(record))
12253
12419
  )
12254
12420
 
12255
12421
  return {
@@ -12260,13 +12426,12 @@ function renderBuildResultMappingResultDetailsTemplate({
12260
12426
  function renderBuildResultMappingResultDetailsBranchTemplate({
12261
12427
  mapBlock,
12262
12428
  camelPlural,
12263
- plural,
12264
- singular
12429
+ plural
12265
12430
  }) {
12266
12431
  return `${mapBlock}
12267
12432
 
12268
12433
  return {
12269
- ${camelPlural}: mapped${plural} as unknown as ${singular}[],
12434
+ ${camelPlural}: mapped${plural},
12270
12435
  total
12271
12436
  }`;
12272
12437
  }
@@ -12287,40 +12452,58 @@ function relationshipMappingExpression(field, valueExpression) {
12287
12452
  return isUserRelationshipTarget(field.relationship) ? `${valueExpression}?.id ? { id: ${valueExpression}.id, name: ${valueExpression}.name ?? undefined, email: ${valueExpression}.email ?? undefined } : null` : `${valueExpression}?.id ? { id: ${valueExpression}.id, name: ${valueExpression}.name ?? undefined, title: ${valueExpression}.title ?? undefined, slug: ${valueExpression}.slug ?? undefined, label: ${valueExpression}.label ?? undefined } : null`;
12288
12453
  }
12289
12454
 
12290
- // adapters/next/generators/actions/action-helpers/relationship-row-type.ts
12291
- function relationshipRowType(field) {
12292
- return isUserRelationshipTarget(field.relationship) ? "{ id?: string | null; name?: string | null; email?: string | null }" : "{ id?: string | null; name?: string | null; title?: string | null; slug?: string | null; label?: string | null }";
12455
+ // adapters/next/generators/actions/action-helpers/build-row-mapping-entries.ts
12456
+ function buildRowMappingEntries({
12457
+ allDbFields,
12458
+ htmlOutputFields,
12459
+ includeHtmlValues,
12460
+ m2mEntries,
12461
+ indent
12462
+ }) {
12463
+ const excludedHtmlSources = new Set(
12464
+ includeHtmlValues ? [] : htmlOutputFields.map((field) => field.name)
12465
+ );
12466
+ const valueExpression = (field) => {
12467
+ if (excludedHtmlSources.has(field.name)) return "null";
12468
+ if (field.type === "relationship" && !field.multiple) {
12469
+ return relationshipMappingExpression(field, `row.${field.name}`);
12470
+ }
12471
+ if (field.type === "select") {
12472
+ return field.multiple ? `row.${field.name} ?? []` : `row.${field.name} ? [row.${field.name}] : []`;
12473
+ }
12474
+ return `row.${field.name}`;
12475
+ };
12476
+ const timestampEntries = allDbFields.some((field) => field.name === "createdAtFormatted") ? [] : [
12477
+ `${indent}createdAtFormatted: formatActionDate(row.createdAt)`,
12478
+ `${indent}updatedAtFormatted: formatActionDate(row.updatedAt)`
12479
+ ];
12480
+ return [
12481
+ ...allDbFields.map(
12482
+ (field) => `${indent}${quotePropertyName(field.name)}: ${valueExpression(field)}`
12483
+ ),
12484
+ ...htmlOutputFields.map(
12485
+ (field) => `${indent}${field.name}Html: ${includeHtmlValues ? `row.${field.name}Html ?? ''` : "''"}`
12486
+ ),
12487
+ ...m2mEntries.map((entry) => `${indent}${entry}`),
12488
+ ...timestampEntries,
12489
+ `${indent}createdByUser: row.createdByUser?.id ? { id: row.createdByUser.id, name: row.createdByUser.name, email: row.createdByUser.email } : null`,
12490
+ `${indent}updatedByUser: row.updatedByUser?.id ? { id: row.updatedByUser.id, name: row.updatedByUser.name, email: row.updatedByUser.email } : null`
12491
+ ];
12293
12492
  }
12294
12493
 
12295
12494
  // adapters/next/generators/actions/action-helpers/build-result-mapping.ts
12296
- function buildResultMapping(allDbFields, relationshipFields, Plural, camelPlural, Singular, hasListRels = false) {
12297
- const relationshipDeclarations = relationshipFields.map(
12298
- (f) => ` const ${f.name} = row.${f.name} as ${relationshipRowType(f)} | null | undefined`
12299
- ).join("\n");
12300
- const rowDeclarations = [
12301
- relationshipDeclarations,
12302
- ` const createdByUser = row.createdByUser as { id?: string | null; name?: string | null; email?: string | null } | null | undefined`,
12303
- ` const updatedByUser = row.updatedByUser as { id?: string | null; name?: string | null; email?: string | null } | null | undefined`
12304
- ].filter(Boolean).join("\n");
12305
- const mappedTimestampFields = allDbFields.some((f) => f.name === "createdAtFormatted") ? [] : [
12306
- ` createdAtFormatted: formatActionDate(row.createdAt)`,
12307
- ` updatedAtFormatted: formatActionDate(row.updatedAt)`
12308
- ];
12309
- const rowMappings = allDbFields.map((f) => {
12310
- if (f.type === "relationship" && !f.multiple) {
12311
- return ` ${f.name}: ${relationshipMappingExpression(f, f.name)}`;
12312
- }
12313
- return ` ${f.name}: row.${f.name}`;
12314
- }).concat([
12315
- ...mappedTimestampFields,
12316
- ` createdByUser: createdByUser?.id ? { id: createdByUser.id, name: createdByUser.name ?? '', email: createdByUser.email ?? '' } : null`,
12317
- ` updatedByUser: updatedByUser?.id ? { id: updatedByUser.id, name: updatedByUser.name ?? '', email: updatedByUser.email ?? '' } : null`
12318
- ]).join(",\n");
12495
+ function buildResultMapping(allDbFields, htmlOutputFields, includeHtmlValues, m2mEntries, Plural, camelPlural, Singular, hasListRels = false) {
12496
+ const rowMappings = buildRowMappingEntries({
12497
+ allDbFields,
12498
+ htmlOutputFields,
12499
+ includeHtmlValues,
12500
+ m2mEntries,
12501
+ indent: " "
12502
+ }).join(",\n");
12319
12503
  const mapBlock = renderBuildResultMappingRenderer({
12320
12504
  kind: "renderBuildResultMappingResultTemplate",
12321
12505
  props: {
12322
12506
  plural: Plural,
12323
- rowDeclarations,
12324
12507
  rowMappings
12325
12508
  }
12326
12509
  });
@@ -12340,41 +12523,29 @@ function buildResultMapping(allDbFields, relationshipFields, Plural, camelPlural
12340
12523
  props: {
12341
12524
  mapBlock,
12342
12525
  camelPlural,
12343
- plural: Plural,
12344
- singular: Singular
12526
+ plural: Plural
12345
12527
  }
12346
12528
  });
12347
12529
  }
12348
12530
 
12349
12531
  // adapters/next/generators/actions/action-helpers/build-single-row-mapping.ts
12350
- function buildSingleRowMapping(allDbFields, _relationshipFields, typeName, htmlFields = []) {
12351
- const fieldMappings = allDbFields.map((f) => {
12352
- if (f.type === "relationship" && !f.multiple) {
12353
- return ` ${f.name}: ${relationshipMappingExpression(f, `row.${f.name}`)}`;
12354
- }
12355
- return ` ${f.name}: row.${f.name}`;
12356
- });
12357
- const htmlMappings = htmlFields.map((f) => ` ${f.name}Html: row.${f.name}Html`);
12358
- const singleTimestampFields = allDbFields.some((f) => f.name === "createdAtFormatted") ? [] : [
12359
- ` createdAtFormatted: formatActionDate(row.createdAt)`,
12360
- ` updatedAtFormatted: formatActionDate(row.updatedAt)`
12361
- ];
12362
- const mappings = [
12363
- ...fieldMappings,
12364
- ...htmlMappings,
12365
- ...singleTimestampFields,
12366
- ` createdByUser: row.createdByUser?.id ? { id: row.createdByUser.id, name: row.createdByUser.name, email: row.createdByUser.email } : null`,
12367
- ` updatedByUser: row.updatedByUser?.id ? { id: row.updatedByUser.id, name: row.updatedByUser.name, email: row.updatedByUser.email } : null`
12368
- ].join(",\n");
12532
+ function buildSingleRowMapping(allDbFields, htmlOutputFields = [], m2mEntries = []) {
12533
+ const mappings = buildRowMappingEntries({
12534
+ allDbFields,
12535
+ htmlOutputFields,
12536
+ includeHtmlValues: true,
12537
+ m2mEntries,
12538
+ indent: " "
12539
+ }).join(",\n");
12369
12540
  return `{
12370
12541
  ${mappings}
12371
- } as unknown as ${typeName}`;
12542
+ }`;
12372
12543
  }
12373
12544
 
12374
12545
  // adapters/next/generators/actions/action-helpers/collect-read-select-fields.ts
12375
- function collectReadSelectFields(fields, path113 = []) {
12546
+ function collectReadSelectFields(fields, path114 = []) {
12376
12547
  const matches = fields.flatMap((field) => {
12377
- const currentPath = [...path113, field.name];
12548
+ const currentPath = [...path114, field.name];
12378
12549
  const matches2 = field.type === "select" ? [{ field, path: currentPath }] : [];
12379
12550
  if (field.fields) {
12380
12551
  matches2.push(...collectReadSelectFields(field.fields, currentPath));
@@ -12439,21 +12610,23 @@ function renderPopulateListRelsFunctionResultTemplate({
12439
12610
  qRelFieldName,
12440
12611
  qRelTable
12441
12612
  }) {
12442
- return ` const ${v}Ids = record.${qListFieldName} && Array.isArray(record.${qListFieldName})
12443
- ? (record.${qListFieldName} as Record<string, unknown>[])
12444
- .map((item) => item.${qRelFieldName})
12445
- .filter((id) => id != null && id !== '')
12446
- .map((id) => String(id))
12613
+ return ` const ${v}Items = Array.isArray(source.${qListFieldName})
12614
+ ? source.${qListFieldName}.filter(isRecord)
12447
12615
  : []
12448
12616
 
12617
+ const ${v}Ids = ${v}Items
12618
+ .map((item) => item.${qRelFieldName})
12619
+ .filter((id) => id != null && id !== '')
12620
+ .map((id) => String(id))
12621
+
12449
12622
  const ${v}Results = ${v}Ids.length > 0
12450
12623
  ? await db.select().from(${qRelTable}).where(inArray(${qRelTable}.id, ${v}Ids))
12451
12624
  : []
12452
12625
 
12453
12626
  const ${v}Map = new Map(${v}Results.map((r) => [r.id, r]))
12454
12627
 
12455
- if (record.${qListFieldName} && Array.isArray(record.${qListFieldName})) {
12456
- record.${qListFieldName} = (record.${qListFieldName} as Record<string, unknown>[]).map((item) => {
12628
+ if (${v}Items.length > 0) {
12629
+ source.${qListFieldName} = ${v}Items.map((item) => {
12457
12630
  const relId = item.${qRelFieldName} ? String(item.${qRelFieldName}) : null
12458
12631
  return {
12459
12632
  ...item,
@@ -12467,7 +12640,10 @@ function renderPopulateListRelsFunctionContentTemplate({
12467
12640
  join
12468
12641
  }) {
12469
12642
  return `
12470
- async function populate${singular}ListRelationships(record: ${singular}): Promise<${singular}> {
12643
+ async function populate${singular}ListRelationships<Row extends Record<string, unknown>>(
12644
+ record: Row
12645
+ ): Promise<Row> {
12646
+ const source: Record<string, unknown> = record
12471
12647
  ${join}
12472
12648
 
12473
12649
  return record
@@ -12941,6 +13117,7 @@ function buildEntityContext(schema, nextMajorVersion) {
12941
13117
  const hasCreatableSelectFields = creatableSelectFields.length > 0;
12942
13118
  const selectFields = collectReadSelectFields(allDbFields);
12943
13119
  const hasSelectFields = selectFields.length > 0;
13120
+ const hasNestedSelectFields = selectFields.some((readField) => readField.path.length > 1);
12944
13121
  const dateRangeFilterFields = new Set(
12945
13122
  resolvedFilters.filter((filter) => filter.type === "date-range").map((filter) => filter.field)
12946
13123
  );
@@ -12984,19 +13161,31 @@ import type { AdminMedia } from '@admin/types'
12984
13161
  const timestampFields = hasTimestampFields && !hasFormattedTimestampFields ? "\n createdAtFormatted: string\n updatedAtFormatted: string" : "";
12985
13162
  const readSelectOptionType = buildReadSelectOptionType(selectFields);
12986
13163
  const relationshipDataTypes = buildRelationshipDataTypes(allDbFields);
13164
+ const mediaCompanionKeys = [...mediaFields, ...galleryFields].map((f) => JSON.stringify(`${f.name}Media`)).join(" | ");
13165
+ const mediaResolvableType = hasMediaFields ? `
13166
+
13167
+ export type ${Singular}MediaResolvable = Omit<${Singular}, ${mediaCompanionKeys}>` : "";
13168
+ const selectResolvableBase = hasMediaFields ? `${Singular}MediaResolvable` : Singular;
13169
+ const topLevelSelectKeys = selectFields.map((readField) => JSON.stringify(readField.path[0])).join(" | ");
13170
+ const selectResolvableFields = selectFields.map((readField) => ` ${quotePropertyName(readField.path[0])}: string[]`).join("\n");
13171
+ const selectResolvableType = hasSelectFields && !hasNestedSelectFields ? `
13172
+
13173
+ export type ${Singular}SelectResolvable = Omit<${selectResolvableBase}, ${topLevelSelectKeys}> & {
13174
+ ${selectResolvableFields}
13175
+ }` : "";
12987
13176
  const dataInterface = `${mediaTypeImport}${buildAuthorshipUserType()}${readSelectOptionType ? `
12988
13177
 
12989
13178
  ${readSelectOptionType}` : ""}${relationshipDataTypes ? `
12990
13179
 
12991
13180
  ${relationshipDataTypes}` : ""}
12992
13181
 
12993
- export interface ${Singular} {
13182
+ export type ${Singular} = {
12994
13183
  ${dataFields}${htmlFieldTypes ? `
12995
13184
  ${htmlFieldTypes}` : ""}${m2mFieldTypes ? `
12996
13185
  ${m2mFieldTypes}` : ""}${mediaCompanionTypes ? `
12997
13186
  ${mediaCompanionTypes}` : ""}${timestampFields}
12998
13187
  ${buildAuthorshipDataFields()}
12999
- }`;
13188
+ }${mediaResolvableType}${selectResolvableType}`;
13000
13189
  const responseInterface = `export interface ${Plural}Page {
13001
13190
  ${camelPlural}: ${Singular}[]
13002
13191
  total: number
@@ -13022,12 +13211,12 @@ ${filtersInterfaceFields}
13022
13211
  const createInterfaceFields = createFields.map(
13023
13212
  (f) => ` ${quotePropertyName(f.name)}${f.required && f.name !== "slug" ? "" : "?"}: ${getInputFieldType(f)}`
13024
13213
  ).join("\n");
13025
- const createInterface = `export interface ${Singular}CreateInput {
13214
+ const createInterface = `export type ${Singular}CreateInput = {
13026
13215
  ${createInterfaceFields}${hasDraft ? `
13027
13216
  published?: boolean` : ""}
13028
13217
  }`;
13029
13218
  const updateInterfaceFields = createFields.map((f) => ` ${quotePropertyName(f.name)}?: ${getInputFieldType(f)}`).join("\n");
13030
- const updateInterface = `export interface ${Singular}UpdateInput {
13219
+ const updateInterface = `export type ${Singular}UpdateInput = {
13031
13220
  id: string
13032
13221
  ${updateInterfaceFields}${hasDraft ? `
13033
13222
  published?: boolean` : ""}
@@ -13085,9 +13274,12 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
13085
13274
  )
13086
13275
  }
13087
13276
  ` : "";
13277
+ const m2mMappingEntries = m2mFields.map((f) => `${quotePropertyName(f.name)}: []`);
13088
13278
  const baseResultMapping = buildResultMapping(
13089
13279
  allDbFields,
13090
- relationshipFields,
13280
+ htmlOutputFields,
13281
+ true,
13282
+ m2mMappingEntries,
13091
13283
  Plural,
13092
13284
  camelPlural,
13093
13285
  Singular,
@@ -13096,8 +13288,10 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
13096
13288
  const selectResultMapping = hasSelectFields ? wrapWithSelectResolution(baseResultMapping, camelPlural, Singular) : baseResultMapping;
13097
13289
  const resultMapping = hasMediaFields ? wrapWithMediaResolution(selectResultMapping, camelPlural, Singular) : selectResultMapping;
13098
13290
  const baseListResultMapping = hasHtmlOutput ? buildResultMapping(
13099
- listDbFields,
13100
- relationshipFields,
13291
+ allDbFields,
13292
+ htmlOutputFields,
13293
+ false,
13294
+ m2mMappingEntries,
13101
13295
  Plural,
13102
13296
  camelPlural,
13103
13297
  Singular,
@@ -13112,12 +13306,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
13112
13306
  const htmlCreateMappings = htmlOutputFields.map((f) => ` ${f.name}Html`).join(",\n");
13113
13307
  const cacheTag = `${plural}:all`;
13114
13308
  const baseSingleRowReturn = (() => {
13115
- const singleRowMapping = buildSingleRowMapping(
13116
- allDbFields,
13117
- relationshipFields,
13118
- Singular,
13119
- htmlOutputFields
13120
- );
13309
+ const singleRowMapping = buildSingleRowMapping(allDbFields, htmlOutputFields, m2mMappingEntries);
13121
13310
  if (hasRelationships && hasListRels) {
13122
13311
  return `const row = result[0]
13123
13312
  return await populate${Singular}ListRelationships(${singleRowMapping})`;
@@ -13169,12 +13358,14 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
13169
13358
  hasSearch,
13170
13359
  hasFilters,
13171
13360
  hasSelectFields,
13361
+ hasNestedSelectFields,
13172
13362
  hasCreatableSelectFields,
13173
13363
  searchFields,
13174
13364
  htmlOutputFields,
13175
13365
  selectFields,
13176
13366
  creatableSelectFields,
13177
13367
  m2mFields,
13368
+ allDbFields,
13178
13369
  filterableFields,
13179
13370
  relTableImports,
13180
13371
  listRelTableImports,
@@ -13957,7 +14148,7 @@ function renderFirstColumnDefLayoutGenerateAuthorshipValueTemplate({
13957
14148
  aria-label="Select row"
13958
14149
  />
13959
14150
  {label ? (
13960
- <span className="text-sm font-medium truncate max-w-[300px]" title={actor?.email}>
14151
+ <span className="text-sm font-medium truncate max-w-table-cell" title={actor?.email}>
13961
14152
  {label}
13962
14153
  </span>
13963
14154
  ) : (
@@ -14068,7 +14259,7 @@ function renderEmailColumnCell({ columnAccessorKey }) {
14068
14259
  onCheckedChange={(value) => row.toggleSelected(!!value)}
14069
14260
  aria-label="Select row"
14070
14261
  />
14071
- <span className="text-sm font-medium truncate max-w-[300px]">{email}</span>
14262
+ <span className="text-sm font-medium truncate max-w-table-cell">{email}</span>
14072
14263
  </div>
14073
14264
  )
14074
14265
  }`;
@@ -14157,7 +14348,7 @@ function renderSelectLabelCell({
14157
14348
  onCheckedChange={(value) => row.toggleSelected(!!value)}
14158
14349
  aria-label="Select row"
14159
14350
  />
14160
- <span className="truncate max-w-[300px]">
14351
+ <span className="truncate max-w-table-cell">
14161
14352
  {label || 'Unavailable'}
14162
14353
  </span>
14163
14354
  </div>
@@ -14178,7 +14369,7 @@ function renderRelationshipLabelCell({
14178
14369
  onCheckedChange={(value) => row.toggleSelected(!!value)}
14179
14370
  aria-label="Select row"
14180
14371
  />
14181
- <span className="truncate max-w-[300px]">
14372
+ <span className="truncate max-w-table-cell">
14182
14373
  {label || 'Unavailable'}
14183
14374
  </span>
14184
14375
  </div>
@@ -14195,7 +14386,7 @@ function renderPlainValueCell({ columnAccessorKey }) {
14195
14386
  onCheckedChange={(value) => row.toggleSelected(!!value)}
14196
14387
  aria-label="Select row"
14197
14388
  />
14198
- <span className="truncate max-w-[300px]">
14389
+ <span className="truncate max-w-table-cell">
14199
14390
  {value === null || value === undefined ? 'Unavailable' : String(value)}
14200
14391
  </span>
14201
14392
  </div>
@@ -15581,6 +15772,119 @@ function buildDefaultValues2(flatFields, dataSource = "initialData", sourceField
15581
15772
  }).join(",\n");
15582
15773
  }
15583
15774
 
15775
+ // adapters/next/generators/form/field-jsx-nested/empty-nested-field-value.ts
15776
+ function emptyNestedFieldValue(field) {
15777
+ if ((field.type === "group" || field.type === "section") && field.fields?.length) {
15778
+ return objectValueForFields(field.fields);
15779
+ }
15780
+ if (field.type === "list" || field.type === "gallery") return "[]";
15781
+ if (field.type === "relationship" && field.multiple) return "[]";
15782
+ if (field.type === "select" && field.multiple) return "[]";
15783
+ if (field.type === "boolean") return "false";
15784
+ if (field.type === "number" || field.type === "decimal") return field.required ? "0" : "undefined";
15785
+ return "''";
15786
+ }
15787
+
15788
+ // adapters/next/generators/form/field-jsx-nested/object-entries-for-fields.ts
15789
+ function objectEntriesForFields(fields) {
15790
+ return fields.flatMap((field) => {
15791
+ const entries = [`${quotePropertyName(field.name)}: ${emptyNestedFieldValue(field)}`];
15792
+ if (field.hasIcon) {
15793
+ entries.push(`${quotePropertyName(`${field.name}Icon`)}: ''`);
15794
+ }
15795
+ return entries;
15796
+ });
15797
+ }
15798
+
15799
+ // adapters/next/generators/form/field-jsx-nested/object-value-for-fields.ts
15800
+ function objectValueForFields(fields) {
15801
+ return `{${objectEntriesForFields(fields).join(", ")}}`;
15802
+ }
15803
+
15804
+ // adapters/next/generators/form/field-jsx-nested/required-leaf-paths.ts
15805
+ function requiredLeafPaths(fields, prefix = []) {
15806
+ return fields.flatMap((field) => {
15807
+ if (field.hidden) return [];
15808
+ const path114 = [...prefix, field.name];
15809
+ if ((field.type === "group" || field.type === "section") && field.fields?.length) {
15810
+ return requiredLeafPaths(field.fields, path114);
15811
+ }
15812
+ if (field.required) return [path114.join(".")];
15813
+ return [];
15814
+ });
15815
+ }
15816
+
15817
+ // adapters/next/generators/form/field-jsx-nested/validation-trigger-expression.ts
15818
+ function validationTriggerExpression(basePath, requiredPaths) {
15819
+ if (requiredPaths.length === 0) return `\`${basePath}.\${expandedIndex}\` as never`;
15820
+ const paths = requiredPaths.map((path114) => `\`${basePath}.\${expandedIndex}.${path114}\``).join(", ");
15821
+ return `[${paths}] as never`;
15822
+ }
15823
+
15824
+ // adapters/next/templates/renderers/generators/form/form-shared/build-list-field-handlers.ts
15825
+ function renderListFieldHandlersTemplate({
15826
+ fieldName,
15827
+ pascalFieldName,
15828
+ itemName,
15829
+ validateExpandedItemExpression,
15830
+ defaultItem
15831
+ }) {
15832
+ return ` async function isExpanded${itemName}Valid() {
15833
+ if (!${fieldName}Expanded) return true
15834
+
15835
+ const expandedIndex = Number(${fieldName}Expanded.replace('item-', '')) - 1
15836
+ if (expandedIndex < 0) return true
15837
+
15838
+ return form.trigger(${validateExpandedItemExpression})
15839
+ }
15840
+
15841
+ async function add${itemName}() {
15842
+ if (!(await isExpanded${itemName}Valid())) return
15843
+
15844
+ ${fieldName}FieldArray.append(${defaultItem})
15845
+ set${pascalFieldName}Expanded(\`item-\${${fieldName}FieldArray.fields.length + 1}\`)
15846
+ }
15847
+
15848
+ async function changeExpanded${itemName}(nextValue: string[]) {
15849
+ const nextExpanded = nextValue[0] ?? ''
15850
+ const needsValidation = Boolean(${fieldName}Expanded && nextExpanded && nextExpanded !== ${fieldName}Expanded)
15851
+ if (needsValidation && !(await isExpanded${itemName}Valid())) return
15852
+
15853
+ set${pascalFieldName}Expanded(nextExpanded)
15854
+ }
15855
+
15856
+ function remove${itemName}(index: number) {
15857
+ if (isPending) return
15858
+ ${fieldName}FieldArray.remove(index)
15859
+ }`;
15860
+ }
15861
+
15862
+ // adapters/next/generators/form/form-shared/build-list-field-handlers.ts
15863
+ function buildListFieldHandlers(fields) {
15864
+ return fields.map((field) => {
15865
+ const itemName = toPascalCase(singularize(field.name));
15866
+ const pascalFieldName = toPascalCase(field.name);
15867
+ return {
15868
+ names: [
15869
+ `isExpanded${itemName}Valid`,
15870
+ `add${itemName}`,
15871
+ `changeExpanded${itemName}`,
15872
+ `remove${itemName}`
15873
+ ],
15874
+ source: renderListFieldHandlersTemplate({
15875
+ fieldName: field.name,
15876
+ pascalFieldName,
15877
+ itemName,
15878
+ validateExpandedItemExpression: validationTriggerExpression(
15879
+ field.name,
15880
+ requiredLeafPaths(field.fields ?? [])
15881
+ ),
15882
+ defaultItem: objectValueForFields(field.fields ?? [])
15883
+ })
15884
+ };
15885
+ });
15886
+ }
15887
+
15584
15888
  // adapters/next/templates/renderers/generators/form/form-shared/build-ui-imports.ts
15585
15889
  function renderBuildUiImportsImportsTemplate({ join }) {
15586
15890
  return `import {
@@ -16060,7 +16364,7 @@ function renderFormLayoutDetailsBranchTemplate() {
16060
16364
  className={cn(
16061
16365
  'grid grid-cols-1 gap-4 items-start h-full',
16062
16366
  {
16063
- 'grid-cols-[1fr_360px]': hasVisibleSidebarFields
16367
+ 'grid-cols-entity-form': hasVisibleSidebarFields
16064
16368
  }
16065
16369
  )}
16066
16370
  >`;
@@ -16109,6 +16413,7 @@ function renderFormClientModuleTemplate({
16109
16413
  specComponentName,
16110
16414
  hookName,
16111
16415
  tabStateDestructure,
16416
+ listFieldHandlers,
16112
16417
  body
16113
16418
  }) {
16114
16419
  return `'use client'
@@ -16120,7 +16425,7 @@ interface ${specComponentName}Props {
16120
16425
  }
16121
16426
 
16122
16427
  export function ${specComponentName}({ state }: ${specComponentName}Props) {
16123
- ${tabStateDestructure} return (
16428
+ ${tabStateDestructure}${listFieldHandlers} return (
16124
16429
  ${body}
16125
16430
  )
16126
16431
  }
@@ -16134,6 +16439,7 @@ function renderEntityFormContent({
16134
16439
  topLevelTabsFieldTabTab,
16135
16440
  contentStateDestructure,
16136
16441
  contentSidebarVisibilityState,
16442
+ listFieldHandlers,
16137
16443
  schemaName,
16138
16444
  hasDraftInitialData,
16139
16445
  topLevelTabsFieldClassName,
@@ -16152,7 +16458,7 @@ export function ${contentComponentName}({ state${topLevelTabsFieldTabTab} }: ${c
16152
16458
  const {
16153
16459
  ${contentStateDestructure}
16154
16460
  } = state
16155
- ${contentSidebarVisibilityState}
16461
+ ${contentSidebarVisibilityState}${listFieldHandlers}
16156
16462
  return (
16157
16463
  <Form {...form}>
16158
16464
  <form autoComplete="off" id="${schemaName}-form" onSubmit={form.handleSubmit((values) => onSubmit(values${hasDraftInitialData}), handleErrors)} ${topLevelTabsFieldClassName}>
@@ -16220,7 +16526,7 @@ function renderFormStaticOptionsTemplate({
16220
16526
  return ` const [${fieldName}Open, set${fieldPascal}Open] = React.useState(false)
16221
16527
  const [${fieldName}Search, set${fieldPascal}Search] = React.useState('')
16222
16528
  const [create${fieldPascal}OptionPending, startCreate${fieldPascal}OptionTransition] = React.useTransition()
16223
- const { data: ${fieldName}CreatedOptions = [] as ${singular}SelectOption[] } = useQuery<${singular}SelectOption[]>({
16529
+ const { data: ${fieldName}CreatedOptions = [] } = useQuery<${singular}SelectOption[]>({
16224
16530
  queryKey: ['${schemaName}', 'select-options', '${fieldName}'],
16225
16531
  queryFn: () => get${singular}SelectOptions('${fieldName}')
16226
16532
  })
@@ -16719,8 +17025,16 @@ function buildEntityFormArtifacts({
16719
17025
  hookReturnNames,
16720
17026
  contentStateDestructure,
16721
17027
  sidebarVisibilityState,
17028
+ listFieldHandlers,
16722
17029
  formBodyJSX
16723
17030
  }) {
17031
+ const handlersUsedIn = (body) => listFieldHandlers.filter((handler) => handler.names.some((name) => usesIdentifier(body, name))).map((handler) => handler.source).join("\n\n");
17032
+ const withHandlerBlock = (handlers) => handlers ? `${handlers}
17033
+
17034
+ ` : "";
17035
+ const withHandlerSection = (handlers) => handlers ? `
17036
+ ${handlers}
17037
+ ` : "";
16724
17038
  const needsCn = hasRelationship || hasSelectCombobox || shouldGuardSidebarByTab;
16725
17039
  const metadataOffsetConst = showMetadataWidget ? "const SCROLL_BORDER_OFFSET = 8;\n\n" : "";
16726
17040
  const metadataScrollState = showMetadataWidget ? " const hasScrolled = usePageScrollThreshold(SCROLL_BORDER_OFFSET)\n" : "";
@@ -16768,7 +17082,11 @@ ${topLevelTabComponentImports}` : ""}` : renderEntityFormContentRenderer({
16768
17082
  });
16769
17083
  const contentSidebarVisibilityState = topLevelTabsField ? "" : sidebarVisibilityState;
16770
17084
  const topLevelTabComponentFiles = topLevelTabComponents.map((spec) => {
16771
- const tabStateNames = hookReturnNames.filter((name) => usesIdentifier(spec.body, name));
17085
+ const tabListFieldHandlers = handlersUsedIn(spec.body);
17086
+ const tabBodyWithHandlers = `${tabListFieldHandlers}${spec.body}`;
17087
+ const tabStateNames = hookReturnNames.filter(
17088
+ (name) => usesIdentifier(tabBodyWithHandlers, name)
17089
+ );
16772
17090
  const tabStateDestructure = tabStateNames.length > 0 ? renderEntityFormContentRenderer({
16773
17091
  kind: "renderFormGeneratedValueTemplate",
16774
17092
  props: {
@@ -16792,6 +17110,7 @@ ${topLevelTabComponentImports}` : ""}` : renderEntityFormContentRenderer({
16792
17110
  specComponentName: spec.componentName,
16793
17111
  hookName,
16794
17112
  tabStateDestructure,
17113
+ listFieldHandlers: withHandlerBlock(tabListFieldHandlers),
16795
17114
  body: spec.body
16796
17115
  }
16797
17116
  })
@@ -16807,6 +17126,7 @@ ${topLevelTabComponentImports}` : ""}` : renderEntityFormContentRenderer({
16807
17126
  topLevelTabsFieldTabTab: topLevelTabsField ? ", tab" : "",
16808
17127
  contentStateDestructure,
16809
17128
  contentSidebarVisibilityState,
17129
+ listFieldHandlers: withHandlerSection(handlersUsedIn(formBodyJSX)),
16810
17130
  schemaName: schema.name,
16811
17131
  hasDraftInitialData: hasDraft ? ", initialData?.published ?? false" : "",
16812
17132
  topLevelTabsFieldClassName: topLevelTabsField ? 'className="h-full"' : 'className="space-y-6 h-full"',
@@ -16875,9 +17195,7 @@ function renderListFieldCardContentTemplate({
16875
17195
  formItemProps: formItemProps2,
16876
17196
  fieldName,
16877
17197
  toLowerCase,
16878
- validateExpandedItemExpression,
16879
- defaultItem,
16880
- pascalFieldName,
17198
+ itemName,
16881
17199
  singularLabel,
16882
17200
  fieldLabelFieldLabel,
16883
17201
  label,
@@ -16894,17 +17212,7 @@ ${indent} <p>No ${toLowerCase} added yet.</p>
16894
17212
  ${indent} <Button
16895
17213
  ${indent} type="button"
16896
17214
  ${indent} variant="outline"
16897
- ${indent} onClick={async () => {
16898
- ${indent} if (${fieldName}Expanded) {
16899
- ${indent} const expandedIndex = Number(${fieldName}Expanded.replace('item-', '')) - 1
16900
- ${indent} if (expandedIndex >= 0) {
16901
- ${indent} const isExpandedItemValid = await form.trigger(${validateExpandedItemExpression})
16902
- ${indent} if (!isExpandedItemValid) return
16903
- ${indent} }
16904
- ${indent} }
16905
- ${indent} ${fieldName}FieldArray.append(${defaultItem})
16906
- ${indent} set${pascalFieldName}Expanded(\`item-\${${fieldName}FieldArray.fields.length + 1}\`)
16907
- ${indent} }}
17215
+ ${indent} onClick={add${itemName}}
16908
17216
  ${indent} disabled={isPending}
16909
17217
  ${indent} >
16910
17218
  ${indent} <Plus className="size-3" />
@@ -16929,17 +17237,7 @@ ${indent} />
16929
17237
  ${indent} <Button
16930
17238
  ${indent} type="button"
16931
17239
  ${indent} variant="outline"
16932
- ${indent} onClick={async () => {
16933
- ${indent} if (${fieldName}Expanded) {
16934
- ${indent} const expandedIndex = Number(${fieldName}Expanded.replace('item-', '')) - 1
16935
- ${indent} if (expandedIndex >= 0) {
16936
- ${indent} const isExpandedItemValid = await form.trigger(${validateExpandedItemExpression})
16937
- ${indent} if (!isExpandedItemValid) return
16938
- ${indent} }
16939
- ${indent} }
16940
- ${indent} ${fieldName}FieldArray.append(${defaultItem})
16941
- ${indent} set${pascalFieldName}Expanded(\`item-\${${fieldName}FieldArray.fields.length + 1}\`)
16942
- ${indent} }}
17240
+ ${indent} onClick={add${itemName}}
16943
17241
  ${indent} disabled={isPending${fieldMaxItemsFieldName}}
16944
17242
  ${indent} >
16945
17243
  ${indent} <Plus className="size-3" />
@@ -16949,17 +17247,7 @@ ${indent} </div>
16949
17247
 
16950
17248
  ${indent} <Accordion
16951
17249
  ${indent} value={${fieldName}Expanded ? [${fieldName}Expanded] : []}
16952
- ${indent} onValueChange={async (nextValue) => {
16953
- ${indent} const nextExpanded = nextValue[0] ?? ''
16954
- ${indent} if (${fieldName}Expanded && nextExpanded && nextExpanded !== ${fieldName}Expanded) {
16955
- ${indent} const expandedIndex = Number(${fieldName}Expanded.replace('item-', '')) - 1
16956
- ${indent} if (expandedIndex >= 0) {
16957
- ${indent} const isExpandedItemValid = await form.trigger(${validateExpandedItemExpression})
16958
- ${indent} if (!isExpandedItemValid) return
16959
- ${indent} }
16960
- ${indent} }
16961
- ${indent} set${pascalFieldName}Expanded(nextExpanded)
16962
- ${indent} }}
17250
+ ${indent} onValueChange={changeExpanded${itemName}}
16963
17251
  ${indent} >
16964
17252
  ${indent} {${fieldName}FieldArray.fields.map((item, index) => (
16965
17253
  ${indent} <AccordionItem key={item.id} value={\`item-\${index + 1}\`}>
@@ -16973,10 +17261,7 @@ ${indent} variant="ghost"
16973
17261
  ${indent} size="icon-sm"
16974
17262
  ${indent} className="absolute right-10 top-1/2 -translate-y-1/2 shrink-0"
16975
17263
  ${indent} aria-label="Remove ${singularLabel}"
16976
- ${indent} onClick={() => {
16977
- ${indent} if (isPending) return
16978
- ${indent} ${fieldName}FieldArray.remove(index)
16979
- ${indent} }}
17264
+ ${indent} onClick={() => remove${itemName}(index)}
16980
17265
  ${indent} disabled={isPending}
16981
17266
  ${indent} >
16982
17267
  ${indent} <X />
@@ -17023,35 +17308,6 @@ function findTitlePath(fields) {
17023
17308
  return null;
17024
17309
  }
17025
17310
 
17026
- // adapters/next/generators/form/field-jsx-nested/empty-nested-field-value.ts
17027
- function emptyNestedFieldValue(field) {
17028
- if ((field.type === "group" || field.type === "section") && field.fields?.length) {
17029
- return objectValueForFields(field.fields);
17030
- }
17031
- if (field.type === "list" || field.type === "gallery") return "[]";
17032
- if (field.type === "relationship" && field.multiple) return "[]";
17033
- if (field.type === "select" && field.multiple) return "[]";
17034
- if (field.type === "boolean") return "false";
17035
- if (field.type === "number" || field.type === "decimal") return field.required ? "0" : "undefined";
17036
- return "''";
17037
- }
17038
-
17039
- // adapters/next/generators/form/field-jsx-nested/object-entries-for-fields.ts
17040
- function objectEntriesForFields(fields) {
17041
- return fields.flatMap((field) => {
17042
- const entries = [`${quotePropertyName(field.name)}: ${emptyNestedFieldValue(field)}`];
17043
- if (field.hasIcon) {
17044
- entries.push(`${quotePropertyName(`${field.name}Icon`)}: ''`);
17045
- }
17046
- return entries;
17047
- });
17048
- }
17049
-
17050
- // adapters/next/generators/form/field-jsx-nested/object-value-for-fields.ts
17051
- function objectValueForFields(fields) {
17052
- return `{${objectEntriesForFields(fields).join(", ")}}`;
17053
- }
17054
-
17055
17311
  // adapters/next/generators/form/form-shared/has-visible-label.ts
17056
17312
  function hasVisibleLabel(field) {
17057
17313
  return typeof field.label === "string" ? field.label.trim().length > 0 : Boolean(field.label);
@@ -17227,16 +17483,16 @@ function renderNestedFieldRenderer(request) {
17227
17483
  }
17228
17484
 
17229
17485
  // adapters/next/generators/form/field-jsx-nested/path-expression.ts
17230
- function pathExpression(path113) {
17231
- return `\`${path113.map((part) => typeof part === "string" ? part : `\${${part.expression}}`).join(".")}\``;
17486
+ function pathExpression(path114) {
17487
+ return `\`${path114.map((part) => typeof part === "string" ? part : `\${${part.expression}}`).join(".")}\``;
17232
17488
  }
17233
17489
 
17234
17490
  // adapters/next/generators/form/field-jsx-nested/path-name-prop.ts
17235
- function pathNameProp(path113) {
17236
- if (path113.every((part) => typeof part === "string")) {
17237
- return `"${path113.map((part) => part).join(".")}"`;
17491
+ function pathNameProp(path114) {
17492
+ if (path114.every((part) => typeof part === "string")) {
17493
+ return `"${path114.map((part) => part).join(".")}"`;
17238
17494
  }
17239
- return `{${pathExpression(path113)}}`;
17495
+ return `{${pathExpression(path114)}}`;
17240
17496
  }
17241
17497
 
17242
17498
  // adapters/next/templates/renderers/generators/form/field-jsx-nested/render-nested-object-list-field.ts
@@ -17290,21 +17546,8 @@ function renderNestedObjectListFieldRenderer(request) {
17290
17546
  throw new Error("Unsupported renderer request");
17291
17547
  }
17292
17548
 
17293
- // adapters/next/generators/form/field-jsx-nested/required-leaf-paths.ts
17294
- function requiredLeafPaths(fields, prefix = []) {
17295
- return fields.flatMap((field) => {
17296
- if (field.hidden) return [];
17297
- const path113 = [...prefix, field.name];
17298
- if ((field.type === "group" || field.type === "section") && field.fields?.length) {
17299
- return requiredLeafPaths(field.fields, path113);
17300
- }
17301
- if (field.required) return [path113.join(".")];
17302
- return [];
17303
- });
17304
- }
17305
-
17306
17549
  // adapters/next/generators/form/field-jsx-nested/render-nested-object-list-field.ts
17307
- function renderNestedObjectListField(field, indent, label, path113, depth) {
17550
+ function renderNestedObjectListField(field, indent, label, path114, depth) {
17308
17551
  const singularLabel = singularize(label);
17309
17552
  const itemIndexVar = `${safeIdentifier(field.name, "nestedList")}Index${depth}`;
17310
17553
  const titlePath = findTitlePath(field.fields ?? []);
@@ -17314,7 +17557,7 @@ function renderNestedObjectListField(field, indent, label, path113, depth) {
17314
17557
  props: {
17315
17558
  indent,
17316
17559
  itemIndexVar,
17317
- pathString: path113.map((part) => typeof part === "string" ? part : `\${${part.expression}}`).join("."),
17560
+ pathString: path114.map((part) => typeof part === "string" ? part : `\${${part.expression}}`).join("."),
17318
17561
  titlePathSuffix,
17319
17562
  singularLabel
17320
17563
  }
@@ -17323,7 +17566,7 @@ function renderNestedObjectListField(field, indent, label, path113, depth) {
17323
17566
  (child) => renderNestedField(
17324
17567
  child,
17325
17568
  `${indent} `,
17326
- [...path113, { expression: itemIndexVar }, child.name],
17569
+ [...path114, { expression: itemIndexVar }, child.name],
17327
17570
  depth + 1
17328
17571
  )
17329
17572
  ).filter(Boolean).join("\n");
@@ -17341,7 +17584,7 @@ ${indent} validatePaths={${JSON.stringify(validatePaths)}}` : "";
17341
17584
  kind: "renderNestedObjectListFieldNestedObjectListFieldTemplate",
17342
17585
  props: {
17343
17586
  indent,
17344
- pathExpression: pathExpression(path113),
17587
+ pathExpression: pathExpression(path114),
17345
17588
  stringify: JSON.stringify(label),
17346
17589
  stringifySerializedValue: JSON.stringify(singularLabel),
17347
17590
  defaultItem,
@@ -17380,10 +17623,10 @@ ${indent}/>`;
17380
17623
  }
17381
17624
 
17382
17625
  // adapters/next/generators/form/field-jsx-nested/render-text-input-field.ts
17383
- function renderTextInputField(field, indent, label, nestedHint, path113) {
17626
+ function renderTextInputField(field, indent, label, nestedHint, path114) {
17384
17627
  return renderTextInputFieldFormFieldTemplate({
17385
17628
  indent,
17386
- pathNameProp: pathNameProp(path113),
17629
+ pathNameProp: pathNameProp(path114),
17387
17630
  formItemProps: formItemProps(field),
17388
17631
  labelWithDescription: labelWithDescription(
17389
17632
  formLabel(field, label),
@@ -17395,11 +17638,11 @@ function renderTextInputField(field, indent, label, nestedHint, path113) {
17395
17638
  }
17396
17639
 
17397
17640
  // adapters/next/generators/form/field-jsx-nested/render-nested-field.ts
17398
- function renderNestedField(field, indent, path113, depth) {
17641
+ function renderNestedField(field, indent, path114, depth) {
17399
17642
  const nestedLabel = field.label || field.name;
17400
17643
  const nestedHint = field.hint ? `<FormDescription>${field.hint}</FormDescription>` : "";
17401
17644
  if ((field.type === "group" || field.type === "section") && field.fields?.length) {
17402
- const groupFields = field.fields.map((child) => renderNestedField(child, `${indent} `, [...path113, child.name], depth)).filter(Boolean).join("\n");
17645
+ const groupFields = field.fields.map((child) => renderNestedField(child, `${indent} `, [...path114, child.name], depth)).filter(Boolean).join("\n");
17403
17646
  if (!groupFields) return "";
17404
17647
  const heading = nestedLabel && nestedLabel !== field.name ? `${indent}<h3 className="text-base font-medium">${nestedLabel}</h3>
17405
17648
  ` : "";
@@ -17415,7 +17658,7 @@ function renderNestedField(field, indent, path113, depth) {
17415
17658
  });
17416
17659
  }
17417
17660
  if (field.type === "list" && field.fields?.length) {
17418
- return renderNestedObjectListField(field, indent, nestedLabel, path113, depth);
17661
+ return renderNestedObjectListField(field, indent, nestedLabel, path114, depth);
17419
17662
  }
17420
17663
  if (field.type === "list") {
17421
17664
  const hideLabelProp = field.label ? "" : `
@@ -17426,7 +17669,7 @@ ${indent} description={${JSON.stringify(field.hint)}}` : "";
17426
17669
  kind: "renderNestedFieldDynamicListFieldTemplate",
17427
17670
  props: {
17428
17671
  indent,
17429
- pathExpression: pathExpression(path113),
17672
+ pathExpression: pathExpression(path114),
17430
17673
  nestedLabel,
17431
17674
  hideLabelProp,
17432
17675
  descriptionProp,
@@ -17440,7 +17683,7 @@ ${indent} maxItems={${field.maxItems}}` : ""
17440
17683
  kind: "renderNestedFieldFormFieldTemplate",
17441
17684
  props: {
17442
17685
  indent,
17443
- pathNameProp: pathNameProp(path113),
17686
+ pathNameProp: pathNameProp(path114),
17444
17687
  formItemProps: formItemProps(field, "flex flex-row items-start space-x-3 space-y-0"),
17445
17688
  labelWithDescription: labelWithDescription(
17446
17689
  `<FormLabel>${nestedLabel}</FormLabel>`,
@@ -17456,7 +17699,7 @@ ${indent} maxItems={${field.maxItems}}` : ""
17456
17699
  kind: "renderNestedFieldFormFieldDetailsTemplate",
17457
17700
  props: {
17458
17701
  indent,
17459
- pathNameProp: pathNameProp(path113),
17702
+ pathNameProp: pathNameProp(path114),
17460
17703
  formItemProps: formItemProps(field),
17461
17704
  labelWithDescription: labelWithDescription(
17462
17705
  formLabel(field, nestedLabel),
@@ -17472,7 +17715,7 @@ ${indent} maxItems={${field.maxItems}}` : ""
17472
17715
  kind: "renderNestedFieldFormFieldDetailsBranchTemplate",
17473
17716
  props: {
17474
17717
  indent,
17475
- pathNameProp: pathNameProp(path113),
17718
+ pathNameProp: pathNameProp(path114),
17476
17719
  formItemProps: formItemProps(field),
17477
17720
  labelWithDescription: labelWithDescription(
17478
17721
  formLabel(field, nestedLabel),
@@ -17490,7 +17733,7 @@ ${indent} maxItems={${field.maxItems}}` : ""
17490
17733
  kind: "renderNestedFieldFormFieldDetailsBranchBodyTemplate",
17491
17734
  props: {
17492
17735
  indent,
17493
- pathNameProp: pathNameProp(path113),
17736
+ pathNameProp: pathNameProp(path114),
17494
17737
  formItemProps: formItemProps(field),
17495
17738
  labelWithDescription: labelWithDescription(
17496
17739
  formLabel(field, nestedLabel),
@@ -17503,14 +17746,7 @@ ${indent} maxItems={${field.maxItems}}` : ""
17503
17746
  }
17504
17747
  });
17505
17748
  }
17506
- return renderTextInputField(field, indent, nestedLabel, nestedHint, path113);
17507
- }
17508
-
17509
- // adapters/next/generators/form/field-jsx-nested/validation-trigger-expression.ts
17510
- function validationTriggerExpression(basePath, requiredPaths) {
17511
- if (requiredPaths.length === 0) return `\`${basePath}.\${expandedIndex}\` as never`;
17512
- const paths = requiredPaths.map((path113) => `\`${basePath}.\${expandedIndex}.${path113}\``).join(", ");
17513
- return `[${paths}] as never`;
17749
+ return renderTextInputField(field, indent, nestedLabel, nestedHint, path114);
17514
17750
  }
17515
17751
 
17516
17752
  // adapters/next/generators/form/field-jsx-nested/render-list-field.ts
@@ -17535,7 +17771,7 @@ ${indent} maxItems={${field.maxItems}}` : ""
17535
17771
  });
17536
17772
  }
17537
17773
  const singularLabel = singularize(label);
17538
- const pascalFieldName = toPascalCase(field.name);
17774
+ const itemName = toPascalCase(singularize(field.name));
17539
17775
  const titlePath = findTitlePath(field.fields);
17540
17776
  const accordionTitle = titlePath ? `{String(form.watch(\`${field.name}.\${index}.${titlePath.join(".")}\` as never) || \`${singularLabel} \${index + 1}\`)}` : `${singularLabel} {index + 1}`;
17541
17777
  const nestedFieldsJSX = field.fields.map(
@@ -17546,11 +17782,6 @@ ${indent} maxItems={${field.maxItems}}` : ""
17546
17782
  0
17547
17783
  )
17548
17784
  ).join("\n");
17549
- const defaultItem = objectValueForFields(field.fields);
17550
- const validateExpandedItemExpression = validationTriggerExpression(
17551
- field.name,
17552
- requiredLeafPaths(field.fields)
17553
- );
17554
17785
  return renderListFieldRenderer({
17555
17786
  kind: "renderListFieldCardContentTemplate",
17556
17787
  props: {
@@ -17558,9 +17789,7 @@ ${indent} maxItems={${field.maxItems}}` : ""
17558
17789
  formItemProps: formItemProps(field),
17559
17790
  fieldName: field.name,
17560
17791
  toLowerCase: label.toLowerCase(),
17561
- validateExpandedItemExpression,
17562
- defaultItem,
17563
- pascalFieldName,
17792
+ itemName,
17564
17793
  singularLabel,
17565
17794
  fieldLabelFieldLabel: field.label ? "text-foreground" : "sr-only",
17566
17795
  label,
@@ -18008,7 +18237,7 @@ function renderMarkdownField(field, indent, label, hintJSX, context) {
18008
18237
  `${indent} `
18009
18238
  ),
18010
18239
  onChange,
18011
- fillControlClass: fillControlClass(field, "min-h-[300px]"),
18240
+ fillControlClass: fillControlClass(field, "min-h-entity-editor"),
18012
18241
  toLowerCase: label.toLowerCase()
18013
18242
  }
18014
18243
  });
@@ -18456,7 +18685,7 @@ function renderRichtextField(field, indent, label, hintJSX, context) {
18456
18685
  `${indent} `
18457
18686
  ),
18458
18687
  onChange,
18459
- fillControlClass: fillControlClass(field, "min-h-[300px]"),
18688
+ fillControlClass: fillControlClass(field, "min-h-entity-editor"),
18460
18689
  toLowerCase: label.toLowerCase()
18461
18690
  }
18462
18691
  });
@@ -19374,7 +19603,7 @@ function buildEntityFormLayout({
19374
19603
  return renderEntityFormContentRenderer({
19375
19604
  kind: "renderFormCardContentGridTemplate",
19376
19605
  props: {
19377
- tabSidebarEntriesGrid: tabSidebarEntries.length > 0 ? "grid-cols-[1fr_360px]" : "grid-cols-1",
19606
+ tabSidebarEntriesGrid: tabSidebarEntries.length > 0 ? "grid-cols-entity-form" : "grid-cols-1",
19378
19607
  tabMainFieldsJsx: tabMainFieldsJSX,
19379
19608
  sidebarColumn
19380
19609
  }
@@ -19430,7 +19659,7 @@ function buildEntityFormLayout({
19430
19659
  const sidebarVisibilityState = shouldGuardSidebarByTab ? `
19431
19660
  const hasVisibleSidebarFields = ${sidebarTabNames.map((tabName) => `activeTab === ${JSON.stringify(tabName)}`).join(" || ")}
19432
19661
  ` : "";
19433
- const formGridOpen = shouldGuardSidebarByTab ? renderEntityFormContentRenderer({ kind: "renderFormLayoutDetailsBranchTemplate" }) : ` <div className="grid ${hasSidebarFields ? "grid-cols-[1fr_360px]" : "grid-cols-1"} gap-4 items-start h-full">`;
19662
+ const formGridOpen = shouldGuardSidebarByTab ? renderEntityFormContentRenderer({ kind: "renderFormLayoutDetailsBranchTemplate" }) : ` <div className="grid ${hasSidebarFields ? "grid-cols-entity-form" : "grid-cols-1"} gap-4 items-start h-full">`;
19434
19663
  const sidebarCardJSX = !hasSidebarFields ? "" : shouldGuardSidebarByTab ? renderEntityFormContentRenderer({
19435
19664
  kind: "renderFormContentSidebarColumnTemplate",
19436
19665
  props: {
@@ -19932,6 +20161,7 @@ ${fieldArrayHooks}
19932
20161
  hookReturnNames,
19933
20162
  contentStateDestructure,
19934
20163
  sidebarVisibilityState,
20164
+ listFieldHandlers: buildListFieldHandlers(listFieldsWithNested),
19935
20165
  formBodyJSX
19936
20166
  });
19937
20167
  }
@@ -21638,12 +21868,7 @@ ${hasEdit ? " const router = useRouter()\n" : ""} const [{
21638
21868
  }
21639
21869
  function renderTableRowClickHandler({ editRoutePath }) {
21640
21870
  return `
21641
- onClick={(e) => {
21642
- const target = e.target as HTMLElement
21643
- if (!e.currentTarget.contains(target)) return
21644
- if (target.closest('button, a, input, [role="checkbox"]')) return
21645
- router.push(\`${editRoutePath}/\${row.original.id}/edit\`)
21646
- }}`;
21871
+ onActivate={() => router.push(\`${editRoutePath}/\${row.original.id}/edit\`)}`;
21647
21872
  }
21648
21873
  function renderTableRenderer(request) {
21649
21874
  switch (request.kind) {
@@ -21782,37 +22007,37 @@ function runEntityPipeline(schema, cwd, config, options = {}) {
21782
22007
  },
21783
22008
  {
21784
22009
  name: "Column definitions",
21785
- run: () => generateColumns2(normalizedSchema, paths.pagesDir, namespacedOptions).files
22010
+ run: () => generateColumns2(normalizedSchema, paths.containerDir, namespacedOptions).files
21786
22011
  },
21787
22012
  {
21788
22013
  name: "Table component",
21789
- run: () => generateTable2(normalizedSchema, paths.pagesDir, namespacedOptions).files
22014
+ run: () => generateTable2(normalizedSchema, paths.containerDir, namespacedOptions).files
21790
22015
  },
21791
22016
  {
21792
22017
  name: "Page content",
21793
- run: () => generatePageContent2(normalizedSchema, paths.pagesDir, namespacedOptions).files
22018
+ run: () => generatePageContent2(normalizedSchema, paths.containerDir, namespacedOptions).files
21794
22019
  },
21795
22020
  {
21796
22021
  name: "Page (server)",
21797
- run: () => generatePage2(normalizedSchema, paths.pagesDir, namespacedOptions).files
22022
+ run: () => generatePage2(normalizedSchema, paths.containerDir, namespacedOptions).files
21798
22023
  }
21799
22024
  ];
21800
22025
  if (normalizedSchema.actions?.create || normalizedSchema.actions?.edit) {
21801
22026
  steps.push({
21802
22027
  name: "Form",
21803
- run: () => generateForm(normalizedSchema, paths.pagesDir, namespacedOptions).files
22028
+ run: () => generateForm(normalizedSchema, paths.containerDir, namespacedOptions).files
21804
22029
  });
21805
22030
  }
21806
22031
  if (normalizedSchema.actions?.create) {
21807
22032
  steps.push({
21808
22033
  name: "Create page",
21809
- run: () => generateCreatePage(normalizedSchema, paths.pagesDir, namespacedOptions).files
22034
+ run: () => generateCreatePage(normalizedSchema, paths.containerDir, namespacedOptions).files
21810
22035
  });
21811
22036
  }
21812
22037
  if (normalizedSchema.actions?.edit) {
21813
22038
  steps.push({
21814
22039
  name: "Edit page",
21815
- run: () => generateEditPage(normalizedSchema, paths.pagesDir, namespacedOptions).files
22040
+ run: () => generateEditPage(normalizedSchema, paths.containerDir, namespacedOptions).files
21816
22041
  });
21817
22042
  }
21818
22043
  steps.push({
@@ -21869,7 +22094,6 @@ function renderSingleActionsServerActionTemplate({
21869
22094
  hasSelectFieldsSelectOptionValue,
21870
22095
  authorshipAliasBlock,
21871
22096
  singletonId,
21872
- formatActionDateSource,
21873
22097
  selectResolutionFunctions,
21874
22098
  cacheLifeLine,
21875
22099
  selectClause,
@@ -21886,12 +22110,11 @@ import { alias } from 'drizzle-orm/pg-core'
21886
22110
  ${cacheReadImport}
21887
22111
  import { ${cacheTagsVar} } from './types'
21888
22112
  import type { ${singular}${hasSelectFieldsSelectOptionValue} } from './types'
22113
+ import { formatActionDate } from '@admin/utils/date/format-action-date'
21889
22114
 
21890
22115
  ${authorshipAliasBlock}
21891
22116
 
21892
22117
  const SINGLETON_ID = '${singletonId}'
21893
-
21894
- ${formatActionDateSource}
21895
22118
  ${selectResolutionFunctions}
21896
22119
  export async function get${singular}(): Promise<${singular} | null> {
21897
22120
  'use cache'${cacheLifeLine}
@@ -21956,8 +22179,9 @@ import { getChangedFieldNames } from '@admin/utils/audit/get-changed-field-names
21956
22179
  import { getTouchedFieldNames } from '@admin/utils/audit/get-touched-field-names'
21957
22180
  import { ${cacheInvalidationFn} } from 'next/cache'
21958
22181
  import { get${singular} } from './get-${kebabSingular}'
22182
+ import { to${singular} } from './to-${kebabSingular}'
21959
22183
  import { ${cacheTagsVar} } from './types'
21960
- import type { ${singular}UpsertInput, ${singular}UpsertResult, ${singular} } from './types'${markdownImport}
22184
+ import type { ${singular}UpsertInput, ${singular}UpsertResult } from './types'${markdownImport}
21961
22185
 
21962
22186
  const SINGLETON_ID = '${singletonId}'
21963
22187
 
@@ -22000,7 +22224,7 @@ ${htmlUpsertBlock}
22000
22224
  .where(eq(${tableVar}.id, SINGLETON_ID))
22001
22225
  .limit(1)
22002
22226
  const changedFields = previousRow
22003
- ? getChangedFieldNames(previousRow as unknown as Record<string, unknown>, processedData)
22227
+ ? getChangedFieldNames(previousRow, processedData)
22004
22228
  : getTouchedFieldNames(processedData)
22005
22229
 
22006
22230
  const lifecycleContext = {
@@ -22043,12 +22267,12 @@ ${m2mWriteStatementsContent}
22043
22267
  const saved${singular} = await get${singular}()
22044
22268
  runAfterHooks(previousRow ? 'afterUpdate' : 'afterCreate', {
22045
22269
  ...lifecycleContext,
22046
- data: (saved${singular} ?? result[0]) as unknown as Record<string, unknown>
22270
+ data: saved${singular} ?? to${singular}(result[0])
22047
22271
  })
22048
22272
 
22049
22273
  return {
22050
22274
  success: true,
22051
- ${camelSingular}: saved${singular} ?? (result[0] as unknown as ${singular})
22275
+ ${camelSingular}: saved${singular} ?? to${singular}(result[0])
22052
22276
  }
22053
22277
  } catch (error) {
22054
22278
  console.error('Error upserting ${singularText}:', error)
@@ -22077,6 +22301,27 @@ export { get${singular} } from './get-${kebabSingular}'
22077
22301
  export { upsert${singular} } from './upsert-${kebabSingular}'${selectOptionFunctionExports}
22078
22302
  `;
22079
22303
  }
22304
+ function renderSingleActionsSelectResolvableTypeTemplate({
22305
+ singular,
22306
+ omittedKeys,
22307
+ resolvableFields
22308
+ }) {
22309
+ return `export type ${singular}SelectResolvable = Omit<${singular}, ${omittedKeys}> & {
22310
+ ${resolvableFields}
22311
+ }`;
22312
+ }
22313
+ function renderSingleActionsToSingleTemplate({
22314
+ singular,
22315
+ tableVar,
22316
+ toEntityFunction
22317
+ }) {
22318
+ return `import type { ${singular} } from './types'
22319
+ import type { ${tableVar} } from '@admin/db/schema'
22320
+ import { formatActionDate } from '@admin/utils/date/format-action-date'
22321
+
22322
+ ${toEntityFunction}
22323
+ `;
22324
+ }
22080
22325
  function renderSingleActionsSingleActionsRenderer(request) {
22081
22326
  switch (request.kind) {
22082
22327
  case "renderSingleActionsContentTemplate":
@@ -22085,6 +22330,10 @@ function renderSingleActionsSingleActionsRenderer(request) {
22085
22330
  return renderSingleActionsRowsTemplate(request.props);
22086
22331
  case "renderSingleActionsServerActionTemplate":
22087
22332
  return renderSingleActionsServerActionTemplate(request.props);
22333
+ case "renderSingleActionsSelectResolvableTypeTemplate":
22334
+ return renderSingleActionsSelectResolvableTypeTemplate(request.props);
22335
+ case "renderSingleActionsToSingleTemplate":
22336
+ return renderSingleActionsToSingleTemplate(request.props);
22088
22337
  case "renderSingleActionsContentDetailsTemplate":
22089
22338
  return renderSingleActionsContentDetailsTemplate(request.props);
22090
22339
  case "renderSingleActionsServerActionSerializedValueTemplate":
@@ -22187,6 +22436,15 @@ function generateSingleActions(schema, actionsDir, options = {}) {
22187
22436
  selectOptionsTableVar: `${tableVar}SelectOptions`
22188
22437
  };
22189
22438
  const relationshipDataTypes = buildRelationshipDataTypes(allDbFields);
22439
+ const hasNestedSelectFields = selectFields.some((readField) => readField.path.length > 1);
22440
+ const selectResolvableType = hasSelectFields && !hasNestedSelectFields ? renderSingleActionsSingleActionsRenderer({
22441
+ kind: "renderSingleActionsSelectResolvableTypeTemplate",
22442
+ props: {
22443
+ singular: Singular,
22444
+ omittedKeys: selectFields.map((readField) => JSON.stringify(readField.path[0])).join(" | "),
22445
+ resolvableFields: selectFields.map((readField) => ` ${quotePropertyName(readField.path[0])}: string[]`).join("\n")
22446
+ }
22447
+ }) : null;
22190
22448
  const typesContent = renderSingleActionsSingleActionsRenderer({
22191
22449
  kind: "renderSingleActionsContentTemplate",
22192
22450
  props: {
@@ -22194,12 +22452,13 @@ function generateSingleActions(schema, actionsDir, options = {}) {
22194
22452
  buildAuthorshipUserType(),
22195
22453
  buildReadSelectOptionType(selectFields),
22196
22454
  relationshipDataTypes,
22197
- `export interface ${Singular} {
22455
+ `export type ${Singular} = {
22198
22456
  ${dataFields}${htmlFieldTypes ? `
22199
22457
  ${htmlFieldTypes}` : ""}${m2mFieldTypes ? `
22200
22458
  ${m2mFieldTypes}` : ""}${timestampFields}
22201
22459
  ${buildAuthorshipDataFields()}
22202
22460
  }`,
22461
+ selectResolvableType,
22203
22462
  `export interface ${Singular}UpsertInput {
22204
22463
  ${allUpsertInterfaceFields}
22205
22464
  }`,
@@ -22231,12 +22490,11 @@ ${allUpsertInterfaceFields}
22231
22490
  authorshipSelects,
22232
22491
  authorshipJoins
22233
22492
  );
22234
- const singleRowMapping = buildSingleRowMapping(
22235
- allDbFields,
22236
- relationshipFields,
22237
- `${Singular}`,
22238
- htmlOutputFields
22239
- );
22493
+ const m2mMappingEntries = m2mFields.map((field) => {
22494
+ const { relationIdCol } = getSingleM2MContext(schema.name, field);
22495
+ return `${quotePropertyName(field.name)}: ${field.name}Rows.map((relationRow) => relationRow.${relationIdCol})`;
22496
+ });
22497
+ const singleRowMapping = buildSingleRowMapping(allDbFields, htmlOutputFields, m2mMappingEntries);
22240
22498
  const m2mReadQueries = m2mFields.map((field) => {
22241
22499
  const { junctionVar, schemaIdCol, relationIdCol } = getSingleM2MContext(schema.name, field);
22242
22500
  return renderSingleActionsSingleActionsRenderer({
@@ -22249,19 +22507,13 @@ ${allUpsertInterfaceFields}
22249
22507
  }
22250
22508
  });
22251
22509
  }).join("\n");
22252
- const m2mAssignments = m2mFields.map((field) => {
22253
- const { relationIdCol } = getSingleM2MContext(schema.name, field);
22254
- return ` singleRow.${field.name} = ${field.name}Rows.map((relationRow) => relationRow.${relationIdCol})`;
22255
- }).join("\n");
22256
22510
  const m2mReadBlock = hasM2M ? `
22257
- ${m2mReadQueries}
22258
- ${m2mAssignments}` : "";
22259
- const singleRowReturn = hasSelectFields ? `const row = result[0]
22260
- const singleRow = ${singleRowMapping}${m2mReadBlock}
22511
+ ${m2mReadQueries}` : "";
22512
+ const singleRowReturn = hasSelectFields ? `const row = result[0]${m2mReadBlock}
22513
+ const singleRow = ${singleRowMapping}
22261
22514
  const [resolvedSelectRow] = await resolve${Singular}SelectFields([singleRow])
22262
- return resolvedSelectRow` : `const row = result[0]
22263
- const singleRow = ${singleRowMapping}${m2mReadBlock}
22264
- return singleRow`;
22515
+ return resolvedSelectRow` : `const row = result[0]${m2mReadBlock}
22516
+ return ${singleRowMapping}`;
22265
22517
  const relationshipTableImports = relationshipFields.filter((field) => !isUserRelationshipTarget(field.relationship)).map((field) => toCamelCase(field.relationship));
22266
22518
  const m2mJunctionImports = m2mFields.map(
22267
22519
  (field) => getSingleM2MContext(schema.name, field).junctionVar
@@ -22276,7 +22528,7 @@ ${m2mAssignments}` : "";
22276
22528
  ])
22277
22529
  ).sort();
22278
22530
  const selectResolutionFunctions = hasSelectFields ? `
22279
- ${generateResolveSelectFieldsSource({ Singular, selectFields, selectOptionsTableVar: `${tableVar}SelectOptions`, hasCreatableSelectFields, exported: false })}
22531
+ ${generateResolveSelectFieldsSource({ Singular, selectFields, selectOptionsTableVar: `${tableVar}SelectOptions`, hasCreatableSelectFields, hasMediaFields: false, exported: false })}
22280
22532
  ` : "";
22281
22533
  const getContent = renderSingleActionsSingleActionsRenderer({
22282
22534
  kind: "renderSingleActionsServerActionTemplate",
@@ -22288,7 +22540,6 @@ ${generateResolveSelectFieldsSource({ Singular, selectFields, selectOptionsTable
22288
22540
  hasSelectFieldsSelectOptionValue: hasSelectFields ? ", SelectOptionValue" : "",
22289
22541
  authorshipAliasBlock: buildAuthorshipAliasBlock(),
22290
22542
  singletonId,
22291
- formatActionDateSource: buildFormatActionDateSource({ exported: false }),
22292
22543
  selectResolutionFunctions,
22293
22544
  cacheLifeLine,
22294
22545
  selectClause,
@@ -22365,8 +22616,25 @@ ${m2mWriteStatements}
22365
22616
  selectOptionFunctionExports
22366
22617
  }
22367
22618
  });
22619
+ const toSingleContent = renderSingleActionsSingleActionsRenderer({
22620
+ kind: "renderSingleActionsToSingleTemplate",
22621
+ props: {
22622
+ singular: Singular,
22623
+ tableVar,
22624
+ toEntityFunction: generateToEntityFunction({
22625
+ Singular,
22626
+ tableVar,
22627
+ allDbFields,
22628
+ htmlOutputFields,
22629
+ m2mFields,
22630
+ mediaFields: [],
22631
+ galleryFields: []
22632
+ })
22633
+ }
22634
+ });
22368
22635
  const files = [
22369
22636
  { name: "types.ts", content: typesContent },
22637
+ { name: `to-${kebabSingular}.ts`, content: toSingleContent },
22370
22638
  ...hasHtmlOutput ? [{ name: "markdown.ts", content: genMarkdownRendererContent() }] : [],
22371
22639
  { name: `get-${kebabSingular}.ts`, content: getContent },
22372
22640
  ...hasCreatableSelectFields ? [
@@ -22700,7 +22968,7 @@ function renderBuildGroupCreatableSelectStateStaticOptionsTemplate({
22700
22968
  return ` const [${fieldName}Open, set${fieldPascal}Open] = React.useState(false)
22701
22969
  const [${fieldName}Search, set${fieldPascal}Search] = React.useState('')
22702
22970
  const [create${fieldPascal}OptionPending, startCreate${fieldPascal}OptionTransition] = React.useTransition()
22703
- const { data: ${fieldName}CreatedOptions = [] as ${singular}SelectOption[] } = useQuery<${singular}SelectOption[]>({
22971
+ const { data: ${fieldName}CreatedOptions = [] } = useQuery<${singular}SelectOption[]>({
22704
22972
  queryKey: ['${schemaName}', 'select-options', '${fieldName}'],
22705
22973
  queryFn: () => get${singular}SelectOptions('${fieldName}')
22706
22974
  })
@@ -22883,6 +23151,7 @@ function generateCardComponent(group2, schema, analysis, allFlatFields) {
22883
23151
  defaultValueFromSourceNames
22884
23152
  );
22885
23153
  const fieldArrayHooks = buildGroupFieldArrayHooks(analysis);
23154
+ const listFieldHandlers = buildListFieldHandlers(analysis.listFieldsWithNested).filter((handler) => handler.names.some((name) => usesIdentifier(fieldsJSX, name))).map((handler) => handler.source).join("\n\n");
22886
23155
  const savedValueName = `saved${Singular}`;
22887
23156
  const resetValues = buildDefaultValues2(group2.flatFields, savedValueName, allFlatFields).replace(
22888
23157
  /^ {6}/gm,
@@ -22928,9 +23197,9 @@ ${defaultValueFromSyncFunctions}
22928
23197
  fieldArrayHooksContent: fieldArrayHooks ? `
22929
23198
  ${fieldArrayHooks}
22930
23199
  ` : "",
22931
- creatableSelectHandlersContent: creatableSelectHandlers ? `${creatableSelectHandlers}
23200
+ creatableSelectHandlersContent: [creatableSelectHandlers, listFieldHandlers].filter(Boolean).map((block) => `${block}
22932
23201
 
22933
- ` : "",
23202
+ `).join(""),
22934
23203
  descriptionLine,
22935
23204
  fieldsJsx: fieldsJSX,
22936
23205
  groupHintText: group2.hint ? `
@@ -23557,11 +23826,11 @@ function runSinglePipeline(schema, cwd, config, options = {}) {
23557
23826
  },
23558
23827
  {
23559
23828
  name: "Form",
23560
- run: () => generateSingleForm(normalizedSchema, paths.pagesDir, namespacedOptions).files
23829
+ run: () => generateSingleForm(normalizedSchema, paths.containerDir, namespacedOptions).files
23561
23830
  },
23562
23831
  {
23563
23832
  name: "Single page",
23564
- run: () => generateSinglePage(normalizedSchema, paths.pagesDir, namespacedOptions).files
23833
+ run: () => generateSinglePage(normalizedSchema, paths.containerDir, namespacedOptions).files
23565
23834
  },
23566
23835
  {
23567
23836
  name: "Navigation",
@@ -24480,7 +24749,7 @@ async function maybeBuildRenamePlan(loaded, cwd, config, generatedFiles, options
24480
24749
  plan.customCells.push(
24481
24750
  buildCustomCellRenameSelection(
24482
24751
  loaded.schema.name,
24483
- paths.pagesDir,
24752
+ paths.containerDir,
24484
24753
  candidate.before,
24485
24754
  candidate.after
24486
24755
  )
@@ -29274,7 +29543,7 @@ ${validationErrors.map((error) => ` - ${error}`).join("\n")}`
29274
29543
  import * as p61 from "@clack/prompts";
29275
29544
  import { execFileSync as execFileSync3 } from "child_process";
29276
29545
  import fs79 from "fs";
29277
- import path92 from "path";
29546
+ import path93 from "path";
29278
29547
 
29279
29548
  // adapters/next/config/detect/detect-project.ts
29280
29549
  import fs52 from "fs";
@@ -29692,10 +29961,14 @@ function addStorageIntegration(integrations2, provider) {
29692
29961
 
29693
29962
  // adapters/next/init/providers/prompt-deploy-provider.ts
29694
29963
  import * as p48 from "@clack/prompts";
29695
- async function promptDeployProvider(initialValue = "none") {
29964
+ async function promptDeployProvider() {
29696
29965
  const provider = await p48.select({
29697
29966
  message: "Deploy this project",
29698
29967
  options: [
29968
+ {
29969
+ value: "none",
29970
+ label: "Not now"
29971
+ },
29699
29972
  {
29700
29973
  value: "vercel",
29701
29974
  label: "Vercel"
@@ -29703,13 +29976,9 @@ async function promptDeployProvider(initialValue = "none") {
29703
29976
  {
29704
29977
  value: "railway",
29705
29978
  label: "Railway"
29706
- },
29707
- {
29708
- value: "none",
29709
- label: "Not now"
29710
29979
  }
29711
29980
  ],
29712
- initialValue
29981
+ initialValue: "none"
29713
29982
  });
29714
29983
  if (p48.isCancel(provider)) {
29715
29984
  p48.cancel("Setup cancelled.");
@@ -31440,6 +31709,7 @@ function scaffoldComponents({ cwd, config }) {
31440
31709
  "utils/date/date-range-end-time.ts",
31441
31710
  "utils/date/date-range-input-format.ts",
31442
31711
  "utils/date/date-range-start-time.ts",
31712
+ "utils/date/format-action-date.ts",
31443
31713
  "utils/date/format-date-range-input-date.ts",
31444
31714
  "utils/date/format-date-range-param-date.ts",
31445
31715
  "utils/date/format-date-range-trigger-date.ts",
@@ -31450,6 +31720,9 @@ function scaffoldComponents({ cwd, config }) {
31450
31720
  "utils/date/parse-date-input-value.ts",
31451
31721
  "utils/date/parse-editable-date-range-date.ts",
31452
31722
  "utils/dev-mode/code-block-height.ts",
31723
+ "utils/dev-mode/dev-mode-json-schemas.ts",
31724
+ "utils/dev-mode/parse-dev-mode-lifecycle-hooks.ts",
31725
+ "utils/dev-mode/parse-dev-mode-snippets.ts",
31453
31726
  "utils/error/read-string-property.ts",
31454
31727
  "utils/error/recognize.ts",
31455
31728
  "utils/error/to-action-error.ts",
@@ -31616,6 +31889,7 @@ function scaffoldComponents({ cwd, config }) {
31616
31889
  "utils/editor/set-active-table-column-alignment.ts",
31617
31890
  "utils/editor/set-element-attribute.ts",
31618
31891
  "utils/editor/set-markdown-link-at-cursor.ts",
31892
+ "utils/editor/set-task-item-checked.ts",
31619
31893
  "utils/editor/show-table-part-preview.ts",
31620
31894
  "utils/editor/slash-commands.ts",
31621
31895
  "utils/editor/slash-menu-state.ts",
@@ -31630,6 +31904,7 @@ function scaffoldComponents({ cwd, config }) {
31630
31904
  "utils/editor/transform-selected-markdown-lines.ts",
31631
31905
  "utils/editor/update-nodes-attr.ts",
31632
31906
  "utils/editor/update-selected-content-editor-math.ts",
31907
+ "utils/email/email-theme.ts",
31633
31908
  "utils/email/escape-html.ts",
31634
31909
  "utils/email/form-email-delivery-input.ts",
31635
31910
  "utils/email/format-email-value.ts",
@@ -31664,6 +31939,7 @@ function scaffoldComponents({ cwd, config }) {
31664
31939
  "utils/seo/generate-article-schema.ts",
31665
31940
  "utils/seo/schema-to-json.ts",
31666
31941
  "utils/shared/cn.ts",
31942
+ "utils/shared/is-record.ts",
31667
31943
  "utils/storage/build-local-storage-key.ts",
31668
31944
  "utils/storage/sanitize-filename.ts",
31669
31945
  "utils/table/default-get-id.ts",
@@ -31768,6 +32044,7 @@ function scaffoldComponents({ cwd, config }) {
31768
32044
  write("hooks/use-users.ts", readTemplate("hooks/use-users.ts"));
31769
32045
  write("hooks/use-webhooks.ts", readTemplate("hooks/use-webhooks.ts"));
31770
32046
  write("hooks/use-failed-image-url.ts", readTemplate("hooks/use-failed-image-url.ts"));
32047
+ write("hooks/use-field-error-visible.ts", readTemplate("hooks/use-field-error-visible.ts"));
31771
32048
  write("hooks/use-media.ts", readTemplate("hooks/use-media.ts"));
31772
32049
  write("hooks/use-entity-versions.ts", readTemplate("hooks/use-entity-versions.ts"));
31773
32050
  write("hooks/use-media-filters-bar.ts", readTemplate("hooks/use-media-filters-bar.ts"));
@@ -32231,7 +32508,9 @@ function scaffoldLayout({ cwd, config }) {
32231
32508
  readTemplate("pages/reset-password-form.tsx")
32232
32509
  );
32233
32510
  write(path74.join(config.paths.pages, "page.tsx"), readTemplate("pages/dashboard-page.tsx"));
32234
- const usersDir = path74.join(config.paths.pages, "users");
32511
+ const containerDir = path74.join(config.paths.pages, ADMIN_CONTAINER_ROUTE_GROUP);
32512
+ write(path74.join(containerDir, "layout.tsx"), readTemplate("pages/container-layout.tsx"));
32513
+ const usersDir = path74.join(containerDir, "users");
32235
32514
  write(path74.join(usersDir, "page.tsx"), readTemplate("pages/users/users-page.tsx"));
32236
32515
  write(
32237
32516
  path74.join(usersDir, "users-page-skeleton.tsx"),
@@ -32339,7 +32618,7 @@ function scaffoldLayout({ cwd, config }) {
32339
32618
  path74.join(settingsWebhooksDir, "logs", "page.tsx"),
32340
32619
  readTemplate("pages/settings/webhooks/webhooks-logs-page.tsx")
32341
32620
  );
32342
- const mediaDir = path74.join(config.paths.pages, "media");
32621
+ const mediaDir = path74.join(containerDir, "media");
32343
32622
  write(path74.join(mediaDir, "page.tsx"), readTemplate("pages/media/media-page.tsx"));
32344
32623
  write(
32345
32624
  path74.join(mediaDir, "media-page-skeleton.tsx"),
@@ -33135,15 +33414,24 @@ function scaffoldProxy({ cwd, config }) {
33135
33414
  return { status: "created", path: relPath };
33136
33415
  }
33137
33416
 
33417
+ // adapters/next/init/scaffolders/public-assets.ts
33418
+ import path79 from "path";
33419
+ function scaffoldPublicAssets(cwd) {
33420
+ const sourceDir = resolveCliAssetPath("adapters", "next", "templates", "init", "public");
33421
+ return PUBLIC_ASSET_FILE_NAMES.filter(
33422
+ (fileName) => safeCopyFile(path79.join(sourceDir, fileName), path79.resolve(cwd, "public", fileName))
33423
+ ).map((fileName) => path79.join("public", fileName));
33424
+ }
33425
+
33138
33426
  // adapters/next/init/scaffolders/tailwind/scaffold-tailwind.ts
33139
33427
  import fs67 from "fs";
33140
33428
 
33141
33429
  // adapters/next/main-css.ts
33142
33430
  import fs66 from "fs";
33143
- import path79 from "path";
33431
+ import path80 from "path";
33144
33432
  function findMainCss(cwd) {
33145
33433
  for (const candidate of MAIN_CSS_CANDIDATES) {
33146
- const filePath = path79.join(cwd, candidate);
33434
+ const filePath = path80.join(cwd, candidate);
33147
33435
  if (fs66.existsSync(filePath)) return filePath;
33148
33436
  }
33149
33437
  return void 0;
@@ -33288,7 +33576,7 @@ ${renderAdminShadowInputUtility()}
33288
33576
 
33289
33577
  // adapters/next/init/scaffolders/tsconfig/scaffold-tsconfig.ts
33290
33578
  import fs68 from "fs";
33291
- import path80 from "path";
33579
+ import path81 from "path";
33292
33580
 
33293
33581
  // adapters/next/init/scaffolders/tsconfig/create-admin-path-aliases.ts
33294
33582
  function createAdminPathAliases(config) {
@@ -33312,7 +33600,7 @@ function createAdminPathAliases(config) {
33312
33600
 
33313
33601
  // adapters/next/init/scaffolders/tsconfig/scaffold-tsconfig.ts
33314
33602
  function scaffoldTsconfig(cwd, config) {
33315
- const tsconfigPath = path80.join(cwd, "tsconfig.json");
33603
+ const tsconfigPath = path81.join(cwd, "tsconfig.json");
33316
33604
  const added = [];
33317
33605
  const skipped = [];
33318
33606
  if (!fs68.existsSync(tsconfigPath)) {
@@ -33608,9 +33896,9 @@ async function ensureVercelAuth(runner, cwd, options) {
33608
33896
 
33609
33897
  // adapters/next/init/vercel/env-guard.ts
33610
33898
  import fs69 from "fs";
33611
- import path81 from "path";
33899
+ import path82 from "path";
33612
33900
  function guardEnvLocal(cwd) {
33613
- const envPath = path81.join(cwd, ".env.local");
33901
+ const envPath = path82.join(cwd, ".env.local");
33614
33902
  let original;
33615
33903
  try {
33616
33904
  original = fs69.readFileSync(envPath, "utf-8");
@@ -33686,14 +33974,14 @@ import pc9 from "picocolors";
33686
33974
 
33687
33975
  // adapters/next/init/vercel/project/create-and-link-project.ts
33688
33976
  import fs71 from "fs";
33689
- import path83 from "path";
33977
+ import path84 from "path";
33690
33978
 
33691
33979
  // adapters/next/init/vercel/project/read-linked-project-json.ts
33692
33980
  import fs70 from "fs";
33693
- import path82 from "path";
33981
+ import path83 from "path";
33694
33982
  function readLinkedProjectJson(cwd) {
33695
33983
  try {
33696
- const projectJsonPath = path82.join(cwd, ".vercel", "project.json");
33984
+ const projectJsonPath = path83.join(cwd, ".vercel", "project.json");
33697
33985
  if (!fs70.existsSync(projectJsonPath)) return void 0;
33698
33986
  const parsed = JSON.parse(fs70.readFileSync(projectJsonPath, "utf-8"));
33699
33987
  if (!isRecord(parsed)) return void 0;
@@ -33768,7 +34056,7 @@ ${add.stderr}`)) continue;
33768
34056
  break;
33769
34057
  }
33770
34058
  try {
33771
- fs71.rmSync(path83.join(cwd, ".vercel", "project.json"), { force: true });
34059
+ fs71.rmSync(path84.join(cwd, ".vercel", "project.json"), { force: true });
33772
34060
  } catch {
33773
34061
  }
33774
34062
  let link = await runVercel(
@@ -33887,10 +34175,10 @@ ${result.stderr}`
33887
34175
  // adapters/next/init/vercel/env-pull.ts
33888
34176
  import fs72 from "fs";
33889
34177
  import os from "os";
33890
- import path84 from "path";
34178
+ import path85 from "path";
33891
34179
  async function pullVercelEnvValue(runner, cwd, read, env, environment = "development") {
33892
- const tmpDir = fs72.mkdtempSync(path84.join(os.tmpdir(), "betterstart-vercel-"));
33893
- const tmpEnv = path84.join(tmpDir, ".env.pull");
34180
+ const tmpDir = fs72.mkdtempSync(path85.join(os.tmpdir(), "betterstart-vercel-"));
34181
+ const tmpEnv = path85.join(tmpDir, ".env.pull");
33894
34182
  try {
33895
34183
  const pull = await runVercel(
33896
34184
  runner,
@@ -34117,9 +34405,9 @@ ${deploy.stderr}`, DETAIL_MAX_LINES)
34117
34405
 
34118
34406
  // adapters/next/init/vercel/deploy/ensure-vercel-json-framework.ts
34119
34407
  import fs73 from "fs";
34120
- import path85 from "path";
34408
+ import path86 from "path";
34121
34409
  function ensureVercelJsonFramework(cwd) {
34122
- const vercelJsonPath = path85.join(cwd, "vercel.json");
34410
+ const vercelJsonPath = path86.join(cwd, "vercel.json");
34123
34411
  if (fs73.existsSync(vercelJsonPath)) {
34124
34412
  return "exists";
34125
34413
  }
@@ -34133,9 +34421,9 @@ function ensureVercelJsonFramework(cwd) {
34133
34421
  }
34134
34422
 
34135
34423
  // adapters/next/init/vercel/deploy/collect-deploy-env-vars.ts
34136
- import path86 from "path";
34424
+ import path87 from "path";
34137
34425
  function collectDeployEnvVars(cwd, existingProductionKeys) {
34138
- const local = parseDotenvFile(path86.join(cwd, ".env.local"));
34426
+ const local = parseDotenvFile(path87.join(cwd, ".env.local"));
34139
34427
  const vars = [];
34140
34428
  for (const [key, value] of local) {
34141
34429
  if (VERCEL_ENV_SYNC_SKIP_KEYS.has(key)) continue;
@@ -34534,7 +34822,7 @@ ${stderr}`;
34534
34822
  // adapters/next/commands/init/run-seed-script.ts
34535
34823
  import { spawn as spawn7 } from "child_process";
34536
34824
  import fs74 from "fs";
34537
- import path87 from "path";
34825
+ import path88 from "path";
34538
34826
 
34539
34827
  // adapters/next/templates/renderers/commands/seed/build-seed-script.ts
34540
34828
  function renderBuildSeedScriptEnvFileTemplate({
@@ -34634,7 +34922,7 @@ async function main() {
34634
34922
 
34635
34923
  if (CHECK_ADMIN_ONLY) {
34636
34924
  if (existingAdmin) {
34637
- console.log(\`EXISTING_ADMIN:\${JSON.stringify(existingAdmin)}\`)
34925
+ process.stdout.write(\`EXISTING_ADMIN:\${JSON.stringify(existingAdmin)}\\n\`)
34638
34926
  process.exit(3)
34639
34927
  }
34640
34928
  process.exit(0)
@@ -34645,7 +34933,7 @@ async function main() {
34645
34933
  const NAME = process.env.SEED_NAME || 'Admin'
34646
34934
 
34647
34935
  if (!EMAIL || !PASSWORD) {
34648
- console.error(' SEED_EMAIL and SEED_PASSWORD are required.')
34936
+ process.stderr.write(' SEED_EMAIL and SEED_PASSWORD are required.\\n')
34649
34937
  process.exit(1)
34650
34938
  }
34651
34939
 
@@ -34660,15 +34948,15 @@ async function main() {
34660
34948
 
34661
34949
  if (existingUser && !replaceTarget) {
34662
34950
  // Exit code 2 signals "user exists" to the CLI
34663
- console.log(\`EXISTING_USER:\${existingUser.name}\`)
34951
+ process.stdout.write(\`EXISTING_USER:\${existingUser.name}\\n\`)
34664
34952
  process.exit(2)
34665
34953
  }
34666
34954
 
34667
34955
  if (replaceTarget) {
34668
- console.log(
34956
+ process.stdout.write(
34669
34957
  OVERWRITE_MODE === 'admin'
34670
- ? '\\n Replacing existing admin user...'
34671
- : '\\n Replacing existing account...'
34958
+ ? '\\n Replacing existing admin user...\\n'
34959
+ : '\\n Replacing existing account...\\n'
34672
34960
  )
34673
34961
  await clearAuthorshipReferences(replaceTarget.id)
34674
34962
  // Remove existing account + session rows first (foreign key refs)
@@ -34676,17 +34964,17 @@ async function main() {
34676
34964
  await db.delete(schema.account).where(eq(schema.account.userId, replaceTarget.id))
34677
34965
  await db.delete(schema.user).where(eq(schema.user.id, replaceTarget.id))
34678
34966
  } else {
34679
- console.log('\\n Creating admin user...')
34967
+ process.stdout.write('\\n Creating admin user...\\n')
34680
34968
  }
34681
34969
 
34682
- console.log(\` Email: \${EMAIL}\\n\`)
34970
+ process.stdout.write(\` Email: \${EMAIL}\\n\\n\`)
34683
34971
 
34684
34972
  const result = await auth.api.signUpEmail({
34685
34973
  body: { email: EMAIL, password: PASSWORD, name: NAME },
34686
34974
  })
34687
34975
 
34688
34976
  if (!result?.user) {
34689
- console.error(' Failed to create user.')
34977
+ process.stderr.write(' Failed to create user.\\n')
34690
34978
  process.exit(1)
34691
34979
  }
34692
34980
 
@@ -34695,13 +34983,13 @@ async function main() {
34695
34983
  .set({ role: 'admin' })
34696
34984
  .where(eq(schema.user.id, result.user.id))
34697
34985
 
34698
- console.log(\` Admin user \${replaceTarget ? 'replaced' : 'created'}: \${EMAIL}\`)
34699
- console.log(' Role: admin\\n')
34986
+ process.stdout.write(\` Admin user \${replaceTarget ? 'replaced' : 'created'}: \${EMAIL}\\n\`)
34987
+ process.stdout.write(' Role: admin\\n\\n')
34700
34988
  process.exit(0)
34701
34989
  }
34702
34990
 
34703
34991
  main().catch((err) => {
34704
- console.error(' Seed failed:', err.message || err)
34992
+ process.stderr.write(\` Seed failed: \${err.message || err}\\n\`)
34705
34993
  process.exit(1)
34706
34994
  })
34707
34995
  `;
@@ -34714,8 +35002,8 @@ function buildSeedScript(authBasePath = "/api/admin/auth") {
34714
35002
 
34715
35003
  // adapters/next/commands/init/run-seed-script.ts
34716
35004
  function runSeedScript(cwd, adminDir, authBasePath, envOverrides) {
34717
- const scriptsDir = path87.join(cwd, adminDir, "scripts");
34718
- const seedPath = path87.join(scriptsDir, "seed.ts");
35005
+ const scriptsDir = path88.join(cwd, adminDir, "scripts");
35006
+ const seedPath = path88.join(scriptsDir, "seed.ts");
34719
35007
  if (!fs74.existsSync(scriptsDir)) {
34720
35008
  fs74.mkdirSync(scriptsDir, { recursive: true });
34721
35009
  }
@@ -34730,7 +35018,7 @@ function runSeedScript(cwd, adminDir, authBasePath, envOverrides) {
34730
35018
  }
34731
35019
  };
34732
35020
  return new Promise((resolve) => {
34733
- const tsxBin = path87.join(cwd, "node_modules", ".bin", "tsx");
35021
+ const tsxBin = path88.join(cwd, "node_modules", ".bin", "tsx");
34734
35022
  const child = spawn7(tsxBin, [seedPath], {
34735
35023
  cwd,
34736
35024
  stdio: "pipe",
@@ -34801,9 +35089,9 @@ function formatAdminIdentity(admin) {
34801
35089
 
34802
35090
  // adapters/next/commands/init/has-db-url.ts
34803
35091
  import fs75 from "fs";
34804
- import path88 from "path";
35092
+ import path89 from "path";
34805
35093
  function hasDbUrl(cwd) {
34806
- const envPath = path88.join(cwd, ".env.local");
35094
+ const envPath = path89.join(cwd, ".env.local");
34807
35095
  if (!fs75.existsSync(envPath)) return false;
34808
35096
  const content = fs75.readFileSync(envPath, "utf-8");
34809
35097
  for (const line of content.split("\n")) {
@@ -34868,7 +35156,7 @@ function readExistingDbUrl(cwd) {
34868
35156
 
34869
35157
  // adapters/next/commands/init/remove-existing-admin-paths.ts
34870
35158
  import fs76 from "fs";
34871
- import path89 from "path";
35159
+ import path90 from "path";
34872
35160
 
34873
35161
  // adapters/next/commands/init/normalize-namespace-for-removal.ts
34874
35162
  function normalizeNamespaceForRemoval(value) {
@@ -34896,14 +35184,14 @@ function removeExistingAdminPaths(cwd, namespaces) {
34896
35184
  const nukeFiles = [CONFIG_FILE_NAME, "ADMIN.md", "drizzle.config.ts"];
34897
35185
  let removed = 0;
34898
35186
  for (const dir of nukeDirs) {
34899
- const fullPath = path89.resolve(cwd, dir);
35187
+ const fullPath = path90.resolve(cwd, dir);
34900
35188
  if (fs76.existsSync(fullPath)) {
34901
35189
  fs76.rmSync(fullPath, { recursive: true, force: true });
34902
35190
  removed++;
34903
35191
  }
34904
35192
  }
34905
35193
  for (const file of nukeFiles) {
34906
- const fullPath = path89.resolve(cwd, file);
35194
+ const fullPath = path90.resolve(cwd, file);
34907
35195
  if (fs76.existsSync(fullPath)) {
34908
35196
  fs76.unlinkSync(fullPath);
34909
35197
  removed++;
@@ -34944,11 +35232,11 @@ function renderInitBanner() {
34944
35232
 
34945
35233
  // adapters/next/commands/init/resolve-force-init-namespaces.ts
34946
35234
  import fs78 from "fs";
34947
- import path91 from "path";
35235
+ import path92 from "path";
34948
35236
 
34949
35237
  // adapters/next/commands/init/read-existing-config-namespace.ts
34950
35238
  import fs77 from "fs";
34951
- import path90 from "path";
35239
+ import path91 from "path";
34952
35240
 
34953
35241
  // adapters/next/commands/init/read-namespace-from-config-source.ts
34954
35242
  function readNamespaceFromConfigSource(source) {
@@ -34958,7 +35246,7 @@ function readNamespaceFromConfigSource(source) {
34958
35246
 
34959
35247
  // adapters/next/commands/init/read-existing-config-namespace.ts
34960
35248
  async function readExistingConfigNamespace(cwd) {
34961
- const configPath = path90.resolve(cwd, CONFIG_FILE_NAME);
35249
+ const configPath = path91.resolve(cwd, CONFIG_FILE_NAME);
34962
35250
  if (!fs77.existsSync(configPath)) {
34963
35251
  return;
34964
35252
  }
@@ -34977,7 +35265,7 @@ async function readExistingConfigNamespace(cwd) {
34977
35265
  // adapters/next/commands/init/resolve-force-init-namespaces.ts
34978
35266
  async function resolveForceInitNamespaces(cwd, targetNamespace) {
34979
35267
  const namespaces = /* @__PURE__ */ new Set([validateAdminNamespace(targetNamespace)]);
34980
- const hasConfig = fs78.existsSync(path91.resolve(cwd, CONFIG_FILE_NAME));
35268
+ const hasConfig = fs78.existsSync(path92.resolve(cwd, CONFIG_FILE_NAME));
34981
35269
  const existingNamespace = await readExistingConfigNamespace(cwd);
34982
35270
  if (existingNamespace) {
34983
35271
  namespaces.add(existingNamespace);
@@ -35223,7 +35511,7 @@ function writeInitJson(context, payload) {
35223
35511
  context.restoreStdout?.();
35224
35512
  context.restoreStdout = void 0;
35225
35513
  context.written = true;
35226
- console.log(JSON.stringify(payload, null, 2));
35514
+ writeMachineJson(payload);
35227
35515
  }
35228
35516
 
35229
35517
  // adapters/next/commands/init/run-init-command-internal.ts
@@ -35268,7 +35556,7 @@ async function runInitCommandInternal(name, options, jsonContext) {
35268
35556
  exitInit("validation", message, "INVALID_OPTIONS");
35269
35557
  }
35270
35558
  let cwd = process.cwd();
35271
- let projectName = path92.basename(cwd);
35559
+ let projectName = path93.basename(cwd);
35272
35560
  let forceMode = Boolean(options.force);
35273
35561
  let namespace = DEFAULT_ADMIN_NAMESPACE;
35274
35562
  if (options.namespace) {
@@ -35377,7 +35665,7 @@ async function runInitCommandInternal(name, options, jsonContext) {
35377
35665
  }
35378
35666
  pm = pmChoice;
35379
35667
  }
35380
- const displayName = freshProject.projectName === "." ? path92.basename(cwd) : freshProject.projectName;
35668
+ const displayName = freshProject.projectName === "." ? path93.basename(cwd) : freshProject.projectName;
35381
35669
  projectName = displayName;
35382
35670
  const { bin, prefix } = createNextAppCommand();
35383
35671
  const cnaArgs = [
@@ -35415,10 +35703,10 @@ async function runInitCommandInternal(name, options, jsonContext) {
35415
35703
  );
35416
35704
  exitInit("project", createNextAppResult.error, "CREATE_NEXT_APP_FAILED");
35417
35705
  }
35418
- cwd = path92.resolve(cwd, freshProject.projectName);
35419
- const hasPackageJson = fs79.existsSync(path92.join(cwd, "package.json"));
35706
+ cwd = path93.resolve(cwd, freshProject.projectName);
35707
+ const hasPackageJson = fs79.existsSync(path93.join(cwd, "package.json"));
35420
35708
  const hasNextConfig = ["next.config.ts", "next.config.js", "next.config.mjs"].some(
35421
- (f) => fs79.existsSync(path92.join(cwd, f))
35709
+ (f) => fs79.existsSync(path93.join(cwd, f))
35422
35710
  );
35423
35711
  if (!hasPackageJson || !hasNextConfig) {
35424
35712
  createNextAppSpinner.stop(`Failed to create Next.js app: ${displayName}`);
@@ -35739,6 +36027,8 @@ async function runInitCommandInternal(name, options, jsonContext) {
35739
36027
  scaffoldAuth({ cwd, config });
35740
36028
  s.message("Components");
35741
36029
  scaffoldComponents({ cwd, config });
36030
+ s.message("Static assets");
36031
+ scaffoldPublicAssets(cwd);
35742
36032
  s.message("Pages & layouts");
35743
36033
  scaffoldLayout({ cwd, config });
35744
36034
  s.message("API routes");
@@ -35760,10 +36050,10 @@ async function runInitCommandInternal(name, options, jsonContext) {
35760
36050
  `Existing ${proxyResult.path} left in place \u2014 add the admin session-cookie redirect to it so logged-out visitors never render the admin routes.`
35761
36051
  );
35762
36052
  }
35763
- const drizzleConfigPath = path92.join(cwd, "drizzle.config.ts");
36053
+ const drizzleConfigPath = path93.join(cwd, "drizzle.config.ts");
35764
36054
  if (!dbFiles.includes("drizzle.config.ts") && fs79.existsSync(drizzleConfigPath)) {
35765
36055
  if (forceMode) {
35766
- const { readNamespacedTemplate } = await import("./read-namespaced-template-D2CD4Q7U.js");
36056
+ const { readNamespacedTemplate } = await import("./read-namespaced-template-WYIGUH7S.js");
35767
36057
  fs79.writeFileSync(
35768
36058
  drizzleConfigPath,
35769
36059
  readNamespacedTemplate("drizzle.config.ts", namespace),
@@ -35776,7 +36066,7 @@ async function runInitCommandInternal(name, options, jsonContext) {
35776
36066
  initialValue: true
35777
36067
  });
35778
36068
  if (!p61.isCancel(overwrite) && overwrite) {
35779
- const { readNamespacedTemplate } = await import("./read-namespaced-template-D2CD4Q7U.js");
36069
+ const { readNamespacedTemplate } = await import("./read-namespaced-template-WYIGUH7S.js");
35780
36070
  fs79.writeFileSync(
35781
36071
  drizzleConfigPath,
35782
36072
  readNamespacedTemplate("drizzle.config.ts", namespace),
@@ -36076,8 +36366,7 @@ Run manually: ${pc18.cyan(pmExecCommand(pm, `betterstart seed`))}`,
36076
36366
  } else if (options.yes) {
36077
36367
  deployProvider = "none";
36078
36368
  } else {
36079
- const initialDeployProvider = railwaySessionPromise ? "railway" : readLinkedProjectId(cwd) ? "vercel" : "none";
36080
- deployProvider = await promptDeployProvider(initialDeployProvider);
36369
+ deployProvider = await promptDeployProvider();
36081
36370
  }
36082
36371
  let deployedUrl;
36083
36372
  if (deployProvider === "vercel") {
@@ -36194,7 +36483,7 @@ async function runInitCommand(name, options) {
36194
36483
 
36195
36484
  // adapters/next/commands/list-integrations.ts
36196
36485
  import * as p62 from "@clack/prompts";
36197
- import path93 from "path";
36486
+ import path94 from "path";
36198
36487
 
36199
36488
  // adapters/next/integration-runtime/list-available-integrations.ts
36200
36489
  function listAvailableIntegrations() {
@@ -36203,13 +36492,13 @@ function listAvailableIntegrations() {
36203
36492
 
36204
36493
  // adapters/next/commands/list-integrations.ts
36205
36494
  async function runListIntegrationsCommand(options) {
36206
- const cwd = options.cwd ? path93.resolve(options.cwd) : process.cwd();
36495
+ const cwd = options.cwd ? path94.resolve(options.cwd) : process.cwd();
36207
36496
  if (options.json) {
36208
36497
  let installed2;
36209
36498
  try {
36210
36499
  installed2 = new Set((await resolveConfig(cwd)).integrations.installed);
36211
36500
  } catch (error) {
36212
- console.error(
36501
+ writeDiagnostic(
36213
36502
  `Error loading config: ${error instanceof Error ? error.message : String(error)}`
36214
36503
  );
36215
36504
  process.exit(1);
@@ -36220,7 +36509,7 @@ async function runListIntegrationsCommand(options) {
36220
36509
  kind: integration.kind,
36221
36510
  description: integration.description
36222
36511
  }));
36223
- console.log(JSON.stringify(items, null, 2));
36512
+ writeMachineJson(items);
36224
36513
  return;
36225
36514
  }
36226
36515
  const config = await resolveConfigOrExit(cwd);
@@ -36239,7 +36528,7 @@ async function runListIntegrationsCommand(options) {
36239
36528
 
36240
36529
  // adapters/next/commands/list-presets.ts
36241
36530
  import * as p63 from "@clack/prompts";
36242
- import path94 from "path";
36531
+ import path95 from "path";
36243
36532
 
36244
36533
  // adapters/next/preset-runtime/list-available-presets.ts
36245
36534
  function listAvailablePresets() {
@@ -36248,13 +36537,13 @@ function listAvailablePresets() {
36248
36537
 
36249
36538
  // adapters/next/commands/list-presets.ts
36250
36539
  async function runListPresetsCommand(options) {
36251
- const cwd = options.cwd ? path94.resolve(options.cwd) : process.cwd();
36540
+ const cwd = options.cwd ? path95.resolve(options.cwd) : process.cwd();
36252
36541
  if (options.json) {
36253
36542
  let installed2;
36254
36543
  try {
36255
36544
  installed2 = new Set((await resolveConfig(cwd)).presets.installed);
36256
36545
  } catch (error) {
36257
- console.error(
36546
+ writeDiagnostic(
36258
36547
  `Error loading config: ${error instanceof Error ? error.message : String(error)}`
36259
36548
  );
36260
36549
  process.exit(1);
@@ -36265,7 +36554,7 @@ async function runListPresetsCommand(options) {
36265
36554
  kind: preset.kind,
36266
36555
  description: preset.description
36267
36556
  }));
36268
- console.log(JSON.stringify(items, null, 2));
36557
+ writeMachineJson(items);
36269
36558
  return;
36270
36559
  }
36271
36560
  const config = await resolveConfigOrExit(cwd);
@@ -36282,7 +36571,7 @@ async function runListPresetsCommand(options) {
36282
36571
 
36283
36572
  // adapters/next/commands/update-component/get-static-asset-component-entries.ts
36284
36573
  import fs80 from "fs";
36285
- import path95 from "path";
36574
+ import path96 from "path";
36286
36575
  function getStaticAssetComponentEntries(assetDirectory) {
36287
36576
  const assetDir = resolveCliAssetPath("shared-assets", "react-admin", assetDirectory);
36288
36577
  if (!fs80.existsSync(assetDir)) return [];
@@ -36299,12 +36588,12 @@ function getStaticAssetComponentEntries(assetDirectory) {
36299
36588
  continue;
36300
36589
  }
36301
36590
  const indexFile = ["index.tsx", "index.ts"].find(
36302
- (file) => fs80.existsSync(path95.join(assetDir, entry.name, file))
36591
+ (file) => fs80.existsSync(path96.join(assetDir, entry.name, file))
36303
36592
  );
36304
36593
  if (indexFile) {
36305
36594
  components.push({
36306
36595
  name: entry.name,
36307
- file: path95.join(entry.name, indexFile)
36596
+ file: path96.join(entry.name, indexFile)
36308
36597
  });
36309
36598
  }
36310
36599
  }
@@ -36872,6 +37161,10 @@ var TEMPLATE_REGISTRY = {
36872
37161
  content: ({ cwd, config }) => adminDataTemplate(detectProjectName(cwd), config.frameworkConfig.next.namespace)
36873
37162
  },
36874
37163
  cn: { relPath: "utils/shared/cn.ts", content: () => readTemplate("utils/shared/cn.ts") },
37164
+ "shared-is-record": {
37165
+ relPath: "utils/shared/is-record.ts",
37166
+ content: () => readTemplate("utils/shared/is-record.ts")
37167
+ },
36875
37168
  "auth-can-access-admin": {
36876
37169
  relPath: "utils/auth/can-access-admin.ts",
36877
37170
  content: () => readTemplate("utils/auth/can-access-admin.ts")
@@ -36920,6 +37213,10 @@ var TEMPLATE_REGISTRY = {
36920
37213
  relPath: "utils/date/date-range-start-time.ts",
36921
37214
  content: () => readTemplate("utils/date/date-range-start-time.ts")
36922
37215
  },
37216
+ "date-format-action-date": {
37217
+ relPath: "utils/date/format-action-date.ts",
37218
+ content: () => readTemplate("utils/date/format-action-date.ts")
37219
+ },
36923
37220
  "date-format-date-range-input-date": {
36924
37221
  relPath: "utils/date/format-date-range-input-date.ts",
36925
37222
  content: () => readTemplate("utils/date/format-date-range-input-date.ts")
@@ -37656,6 +37953,10 @@ var TEMPLATE_REGISTRY = {
37656
37953
  relPath: "utils/editor/transform-selected-markdown-lines.ts",
37657
37954
  content: () => readTemplate("utils/editor/transform-selected-markdown-lines.ts")
37658
37955
  },
37956
+ "editor-set-task-item-checked": {
37957
+ relPath: "utils/editor/set-task-item-checked.ts",
37958
+ content: () => readTemplate("utils/editor/set-task-item-checked.ts")
37959
+ },
37659
37960
  "editor-update-nodes-attr": {
37660
37961
  relPath: "utils/editor/update-nodes-attr.ts",
37661
37962
  content: () => readTemplate("utils/editor/update-nodes-attr.ts")
@@ -37676,6 +37977,10 @@ var TEMPLATE_REGISTRY = {
37676
37977
  relPath: "utils/editor/table-input.ts",
37677
37978
  content: () => readTemplate("utils/editor/table-input.ts")
37678
37979
  },
37980
+ "email-email-theme": {
37981
+ relPath: "utils/email/email-theme.ts",
37982
+ content: () => readTemplate("utils/email/email-theme.ts")
37983
+ },
37679
37984
  "email-escape-html": {
37680
37985
  relPath: "utils/email/escape-html.ts",
37681
37986
  content: () => readTemplate("utils/email/escape-html.ts")
@@ -37708,6 +38013,10 @@ var TEMPLATE_REGISTRY = {
37708
38013
  relPath: "utils/email/render-thank-you-email.ts",
37709
38014
  content: () => readTemplate("utils/email/render-thank-you-email.ts")
37710
38015
  },
38016
+ "use-field-error-visible": {
38017
+ relPath: "hooks/use-field-error-visible.ts",
38018
+ content: () => readTemplate("hooks/use-field-error-visible.ts")
38019
+ },
37711
38020
  "form-clone-default-value": {
37712
38021
  relPath: "utils/form/clone-default-value.ts",
37713
38022
  content: () => readTemplate("utils/form/clone-default-value.ts")
@@ -37936,6 +38245,20 @@ var TEMPLATE_REGISTRY = {
37936
38245
  relPath: "utils/dev-mode/code-block-height.ts",
37937
38246
  content: () => readTemplate("utils/dev-mode/code-block-height.ts")
37938
38247
  },
38248
+ "dev-mode-json-schemas-utils": {
38249
+ relPath: "utils/dev-mode/dev-mode-json-schemas.ts",
38250
+ content: () => readTemplate("utils/dev-mode/dev-mode-json-schemas.ts")
38251
+ },
38252
+ "dev-mode-parse-lifecycle-hooks-utils": {
38253
+ relPath: "utils/dev-mode/parse-dev-mode-lifecycle-hooks.ts",
38254
+ content: () => readTemplate("utils/dev-mode/parse-dev-mode-lifecycle-hooks.ts"),
38255
+ dependencies: ["dev-mode-json-schemas-utils"]
38256
+ },
38257
+ "dev-mode-parse-snippets-utils": {
38258
+ relPath: "utils/dev-mode/parse-dev-mode-snippets.ts",
38259
+ content: () => readTemplate("utils/dev-mode/parse-dev-mode-snippets.ts"),
38260
+ dependencies: ["dev-mode-json-schemas-utils"]
38261
+ },
37939
38262
  "lifecycle-hooks": {
37940
38263
  relPath: "lib/lifecycle-hooks/index.ts",
37941
38264
  content: () => readTemplate("lib/lifecycle-hooks/index.ts"),
@@ -38001,6 +38324,11 @@ var TEMPLATE_REGISTRY = {
38001
38324
  base: "cwd",
38002
38325
  dependencies: ["auth-gate"]
38003
38326
  },
38327
+ "container-layout": {
38328
+ relPath: "app/(admin)/admin/(authenticated)/(container)/layout.tsx",
38329
+ content: () => readTemplate("pages/container-layout.tsx"),
38330
+ base: "cwd"
38331
+ },
38004
38332
  "auth-gate": {
38005
38333
  relPath: "app/(admin)/admin/(authenticated)/auth-gate.tsx",
38006
38334
  content: () => readTemplate("pages/auth-gate.tsx"),
@@ -39058,16 +39386,16 @@ async function listInstallableChoices(cwd) {
39058
39386
  }
39059
39387
 
39060
39388
  // adapters/next/commands/menu-choices/list-schema-choices.ts
39061
- import path96 from "path";
39389
+ import path97 from "path";
39062
39390
  async function listSchemaChoices(cwd) {
39063
39391
  const config = await resolveConfigOrExit(cwd);
39064
39392
  const paths = resolveProjectPaths(config);
39065
- return listSchemaNames(path96.join(cwd, ...paths.schemasDir.split("/")));
39393
+ return listSchemaNames(path97.join(cwd, ...paths.schemasDir.split("/")));
39066
39394
  }
39067
39395
 
39068
39396
  // adapters/next/commands/remove-schema/run-remove-schema-command.ts
39069
39397
  import * as clack2 from "@clack/prompts";
39070
- import path97 from "path";
39398
+ import path98 from "path";
39071
39399
 
39072
39400
  // core-engine/snapshots/store/delete-snapshot.ts
39073
39401
  import fs81 from "fs";
@@ -39107,7 +39435,7 @@ function resolveSchemaOwnerForRemoval(cwd, schemaName) {
39107
39435
  // adapters/next/commands/remove-schema/run-remove-schema-command.ts
39108
39436
  async function runRemoveSchemaCommand(schemaName, options) {
39109
39437
  const owner = resolveSchemaOwnerForRemoval(
39110
- options.cwd ? path97.resolve(options.cwd) : process.cwd(),
39438
+ options.cwd ? path98.resolve(options.cwd) : process.cwd(),
39111
39439
  schemaName
39112
39440
  );
39113
39441
  if (owner === "core") {
@@ -39120,7 +39448,7 @@ async function runRemoveSchemaCommand(schemaName, options) {
39120
39448
  clack2.log.error(`"${schemaName}" is owned by ${owner}. Remove the owning preset instead.`);
39121
39449
  process.exit(1);
39122
39450
  }
39123
- const cwd = options.cwd ? path97.resolve(options.cwd) : process.cwd();
39451
+ const cwd = options.cwd ? path98.resolve(options.cwd) : process.cwd();
39124
39452
  const config = await resolveConfigOrExit(cwd);
39125
39453
  const paths = resolveProjectPaths(config);
39126
39454
  const manifest = loadManifest(cwd, schemaName);
@@ -39156,7 +39484,7 @@ async function runRemoveSchemaCommand(schemaName, options) {
39156
39484
  }
39157
39485
  const loaded = (() => {
39158
39486
  try {
39159
- return loadSchema(path97.join(cwd, ...paths.schemasDir.split("/")), schemaName);
39487
+ return loadSchema(path98.join(cwd, ...paths.schemasDir.split("/")), schemaName);
39160
39488
  } catch {
39161
39489
  return null;
39162
39490
  }
@@ -39166,15 +39494,15 @@ async function runRemoveSchemaCommand(schemaName, options) {
39166
39494
  if (removePath(cwd, `${paths.adminActionsDir}/${schemaName}`)) {
39167
39495
  deletedPaths.push(`${paths.adminActionsDir}/${schemaName}`);
39168
39496
  }
39169
- if (removePath(cwd, `${paths.pagesDir}/forms/${kebabName}`)) {
39170
- deletedPaths.push(`${paths.pagesDir}/forms/${kebabName}`);
39497
+ if (removePath(cwd, `${paths.containerDir}/forms/${kebabName}`)) {
39498
+ deletedPaths.push(`${paths.containerDir}/forms/${kebabName}`);
39171
39499
  }
39172
39500
  } else {
39173
39501
  if (removePath(cwd, `${paths.adminActionsDir}/${schemaName}`)) {
39174
39502
  deletedPaths.push(`${paths.adminActionsDir}/${schemaName}`);
39175
39503
  }
39176
- if (removePath(cwd, `${paths.pagesDir}/${schemaName}`)) {
39177
- deletedPaths.push(`${paths.pagesDir}/${schemaName}`);
39504
+ if (removePath(cwd, `${paths.containerDir}/${schemaName}`)) {
39505
+ deletedPaths.push(`${paths.containerDir}/${schemaName}`);
39178
39506
  }
39179
39507
  }
39180
39508
  cleanupSchemaEmptyDirs(cwd, deletedPaths, paths);
@@ -39201,7 +39529,7 @@ Schema JSON preserved.`
39201
39529
 
39202
39530
  // adapters/next/commands/remove.ts
39203
39531
  import * as p64 from "@clack/prompts";
39204
- import path101 from "path";
39532
+ import path102 from "path";
39205
39533
 
39206
39534
  // adapters/next/init/scaffolders/dependencies/build-remove-args.ts
39207
39535
  function buildRemoveArgs(pm, deps, dev) {
@@ -39245,11 +39573,11 @@ async function removeDependenciesAsync({
39245
39573
  }
39246
39574
 
39247
39575
  // adapters/next/integration-runtime/find-blocking-integration-dependencies.ts
39248
- import path99 from "path";
39576
+ import path100 from "path";
39249
39577
 
39250
39578
  // adapters/next/integration-runtime/schema-manifest-contains-mailchimp-imports.ts
39251
39579
  import fs83 from "fs";
39252
- import path98 from "path";
39580
+ import path99 from "path";
39253
39581
  function schemaManifestContainsMailchimpImports(cwd, config, schemaName) {
39254
39582
  const manifest = loadManifest(cwd, schemaName);
39255
39583
  if (!manifest) {
@@ -39266,7 +39594,7 @@ function schemaManifestContainsMailchimpImports(cwd, config, schemaName) {
39266
39594
  ...manifest.skipped
39267
39595
  ]);
39268
39596
  return liveManifestPaths.some((filePath) => {
39269
- const fullPath = path98.join(cwd, ...filePath.split("/"));
39597
+ const fullPath = path99.join(cwd, ...filePath.split("/"));
39270
39598
  if (!fs83.existsSync(fullPath) || !fs83.statSync(fullPath).isFile()) {
39271
39599
  return false;
39272
39600
  }
@@ -39279,7 +39607,7 @@ function schemaManifestContainsMailchimpImports(cwd, config, schemaName) {
39279
39607
  function findBlockingIntegrationDependencies(cwd, config, integrationId) {
39280
39608
  const blockers = [];
39281
39609
  const paths = resolveProjectPaths(config);
39282
- const schemasDir = path99.join(cwd, ...paths.schemasDir.split("/"));
39610
+ const schemasDir = path100.join(cwd, ...paths.schemasDir.split("/"));
39283
39611
  const ownership = loadSchemaOwnershipIndex(cwd);
39284
39612
  for (const schemaName of listSchemaNames(schemasDir)) {
39285
39613
  if (ownership.schemas[schemaName] !== "user") {
@@ -39470,7 +39798,7 @@ function findBlockingSchemaDependencies(cwd, config, ownedSchemas) {
39470
39798
 
39471
39799
  // adapters/next/preset-runtime/remove-preset-schemas.ts
39472
39800
  import fs85 from "fs";
39473
- import path100 from "path";
39801
+ import path101 from "path";
39474
39802
 
39475
39803
  // preset-engine/ownership/remove-schema-owner.ts
39476
39804
  function removeSchemaOwner(cwd, schemaName) {
@@ -39511,8 +39839,8 @@ function removePresetSchemas(cwd, config, manifest) {
39511
39839
  }
39512
39840
  deleteSnapshot(cwd, schemaName);
39513
39841
  }
39514
- const schemaPath = path100.posix.join(paths.schemasDir, `${schemaName}.json`);
39515
- const formsSchemaPath = path100.posix.join(paths.schemasDir, "forms", `${schemaName}.json`);
39842
+ const schemaPath = path101.posix.join(paths.schemasDir, `${schemaName}.json`);
39843
+ const formsSchemaPath = path101.posix.join(paths.schemasDir, "forms", `${schemaName}.json`);
39516
39844
  if (removePath(cwd, schemaPath)) {
39517
39845
  deletedPaths.push(schemaPath);
39518
39846
  }
@@ -39644,7 +39972,7 @@ async function runRemoveCommand(items, options) {
39644
39972
  );
39645
39973
  process.exit(1);
39646
39974
  }
39647
- const cwd = options.cwd ? path101.resolve(options.cwd) : process.cwd();
39975
+ const cwd = options.cwd ? path102.resolve(options.cwd) : process.cwd();
39648
39976
  const config = await resolveConfigOrExit(cwd);
39649
39977
  const pm = detectPackageManager(cwd);
39650
39978
  if (!options.force) {
@@ -39697,9 +40025,9 @@ async function runRemoveCommand(items, options) {
39697
40025
  // adapters/next/commands/seed/run-seed-command.ts
39698
40026
  import * as clack3 from "@clack/prompts";
39699
40027
  import fs86 from "fs";
39700
- import path102 from "path";
40028
+ import path103 from "path";
39701
40029
  async function runSeedCommand(options) {
39702
- const cwd = options.cwd ? path102.resolve(options.cwd) : process.cwd();
40030
+ const cwd = options.cwd ? path103.resolve(options.cwd) : process.cwd();
39703
40031
  clack3.intro("BetterStart Seed");
39704
40032
  let config;
39705
40033
  try {
@@ -39775,15 +40103,15 @@ async function runSeedCommand(options) {
39775
40103
  }
39776
40104
  name = nameInput;
39777
40105
  }
39778
- const scriptsDir = path102.join(cwd, adminDir, "scripts");
39779
- const seedPath = path102.join(scriptsDir, "seed.ts");
40106
+ const scriptsDir = path103.join(cwd, adminDir, "scripts");
40107
+ const seedPath = path103.join(scriptsDir, "seed.ts");
39780
40108
  if (!fs86.existsSync(scriptsDir)) {
39781
40109
  fs86.mkdirSync(scriptsDir, { recursive: true });
39782
40110
  }
39783
40111
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
39784
40112
  fs86.writeFileSync(seedPath, buildSeedScript(`${namespace.apiPath}/auth`), "utf-8");
39785
40113
  const { execFile: execFile2 } = await import("child_process");
39786
- const tsxBin = path102.join(cwd, "node_modules", ".bin", "tsx");
40114
+ const tsxBin = path103.join(cwd, "node_modules", ".bin", "tsx");
39787
40115
  const runSeed2 = (overwrite) => new Promise((resolve, reject) => {
39788
40116
  execFile2(
39789
40117
  tsxBin,
@@ -39849,7 +40177,7 @@ async function runSeedCommand(options) {
39849
40177
  clack3.log.error(errMsg);
39850
40178
  clack3.log.info("You can run the seed script manually:");
39851
40179
  clack3.log.info(
39852
- ` SEED_EMAIL="${email}" SEED_PASSWORD="..." npx tsx ${path102.relative(cwd, seedPath)}`
40180
+ ` SEED_EMAIL="${email}" SEED_PASSWORD="..." npx tsx ${path103.relative(cwd, seedPath)}`
39853
40181
  );
39854
40182
  clack3.outro("");
39855
40183
  process.exit(1);
@@ -39866,12 +40194,12 @@ async function runSeedCommand(options) {
39866
40194
 
39867
40195
  // adapters/next/commands/uninstall/run-uninstall-command.ts
39868
40196
  import * as p65 from "@clack/prompts";
39869
- import path104 from "path";
40197
+ import path105 from "path";
39870
40198
  import pc19 from "picocolors";
39871
40199
 
39872
40200
  // adapters/next/commands/uninstall/build-uninstall-plan.ts
39873
40201
  import fs91 from "fs";
39874
- import path103 from "path";
40202
+ import path104 from "path";
39875
40203
 
39876
40204
  // adapters/next/commands/uninstall-cleaners/clean-css.ts
39877
40205
  import fs87 from "fs";
@@ -39990,13 +40318,13 @@ function isCLICreatedOxcConfig(configPath) {
39990
40318
  function buildUninstallPlan(cwd, namespaceValue) {
39991
40319
  const steps = [];
39992
40320
  const namespace = resolveAdminNamespace(namespaceValue);
39993
- const hasSrc = fs91.existsSync(path103.join(cwd, "src"));
40321
+ const hasSrc = fs91.existsSync(path104.join(cwd, "src"));
39994
40322
  const appBase = hasSrc ? "src/app" : "app";
39995
40323
  const dirs = [];
39996
- const adminDir = path103.join(cwd, namespace.segment);
39997
- const legacyAdminDir = path103.join(cwd, "admin");
39998
- const adminRouteGroup = path103.join(cwd, appBase, namespace.routeGroup);
39999
- const legacyAdminRouteGroup = path103.join(cwd, appBase, "(admin)");
40324
+ const adminDir = path104.join(cwd, namespace.segment);
40325
+ const legacyAdminDir = path104.join(cwd, "admin");
40326
+ const adminRouteGroup = path104.join(cwd, appBase, namespace.routeGroup);
40327
+ const legacyAdminRouteGroup = path104.join(cwd, appBase, "(admin)");
40000
40328
  if (fs91.existsSync(adminDir)) dirs.push(`${namespace.segment}/`);
40001
40329
  if (namespace.segment !== "admin" && fs91.existsSync(legacyAdminDir)) dirs.push("admin/");
40002
40330
  if (fs91.existsSync(adminRouteGroup)) dirs.push(`${appBase}/${namespace.routeGroup}/`);
@@ -40024,9 +40352,9 @@ function buildUninstallPlan(cwd, namespaceValue) {
40024
40352
  const configFiles = [];
40025
40353
  const configPaths = [];
40026
40354
  const candidates = [
40027
- [CONFIG_FILE_NAME, path103.join(cwd, CONFIG_FILE_NAME)],
40028
- ["drizzle.config.ts", path103.join(cwd, "drizzle.config.ts")],
40029
- ["ADMIN.md", path103.join(cwd, "ADMIN.md")]
40355
+ [CONFIG_FILE_NAME, path104.join(cwd, CONFIG_FILE_NAME)],
40356
+ ["drizzle.config.ts", path104.join(cwd, "drizzle.config.ts")],
40357
+ ["ADMIN.md", path104.join(cwd, "ADMIN.md")]
40030
40358
  ];
40031
40359
  for (const [label, fullPath] of candidates) {
40032
40360
  if (fs91.existsSync(fullPath)) {
@@ -40035,7 +40363,7 @@ function buildUninstallPlan(cwd, namespaceValue) {
40035
40363
  }
40036
40364
  }
40037
40365
  for (const fileName of [".oxfmtrc.json", ".oxlintrc.json"]) {
40038
- const configPath = path103.join(cwd, fileName);
40366
+ const configPath = path104.join(cwd, fileName);
40039
40367
  if (isCLICreatedOxcConfig(configPath)) {
40040
40368
  configFiles.push(`${fileName} (CLI-created)`);
40041
40369
  configPaths.push(configPath);
@@ -40054,7 +40382,7 @@ function buildUninstallPlan(cwd, namespaceValue) {
40054
40382
  }
40055
40383
  });
40056
40384
  }
40057
- const tsconfigPath = path103.join(cwd, "tsconfig.json");
40385
+ const tsconfigPath = path104.join(cwd, "tsconfig.json");
40058
40386
  if (fs91.existsSync(tsconfigPath)) {
40059
40387
  const content = fs91.readFileSync(tsconfigPath, "utf-8");
40060
40388
  const aliasMatches = [
@@ -40081,7 +40409,7 @@ function buildUninstallPlan(cwd, namespaceValue) {
40081
40409
  (l) => new RegExp(`^@source\\s+"[^"]*(?:admin|${namespace.segment})[^"]*";\\s*$`).test(l)
40082
40410
  );
40083
40411
  if (sourceLines.length > 0) {
40084
- const relCss = path103.relative(cwd, cssFile);
40412
+ const relCss = path104.relative(cwd, cssFile);
40085
40413
  steps.push({
40086
40414
  label: `CSS @source lines (${relCss})`,
40087
40415
  items: [`@source lines in ${relCss}`],
@@ -40093,7 +40421,7 @@ function buildUninstallPlan(cwd, namespaceValue) {
40093
40421
  });
40094
40422
  }
40095
40423
  }
40096
- const envPath = path103.join(cwd, ".env.local");
40424
+ const envPath = path104.join(cwd, ".env.local");
40097
40425
  if (fs91.existsSync(envPath)) {
40098
40426
  const envContent = fs91.readFileSync(envPath, "utf-8");
40099
40427
  const bsVars = envContent.split("\n").filter((l) => l.trim().match(/^BETTERSTART_\w+=/)).map((l) => l.split("=")[0]);
@@ -40114,7 +40442,7 @@ function buildUninstallPlan(cwd, namespaceValue) {
40114
40442
 
40115
40443
  // adapters/next/commands/uninstall/run-uninstall-command.ts
40116
40444
  async function runUninstallCommand(options) {
40117
- const cwd = options.cwd ? path104.resolve(options.cwd) : process.cwd();
40445
+ const cwd = options.cwd ? path105.resolve(options.cwd) : process.cwd();
40118
40446
  p65.intro(pc19.bgRed(pc19.white(" BetterStart Uninstall ")));
40119
40447
  let namespace = DEFAULT_ADMIN_NAMESPACE;
40120
40448
  try {
@@ -40159,7 +40487,7 @@ async function runUninstallCommand(options) {
40159
40487
  // adapters/next/commands/update-component/run-update-command.ts
40160
40488
  import * as clack8 from "@clack/prompts";
40161
40489
  import fs97 from "fs";
40162
- import path110 from "path";
40490
+ import path111 from "path";
40163
40491
 
40164
40492
  // adapters/next/integration-runtime/sync-installed-integration-manifests.ts
40165
40493
  function syncInstalledIntegrationManifests(cwd, config) {
@@ -40190,7 +40518,7 @@ function applyNamespaceToTemplateEntry(entry, config, cwd) {
40190
40518
 
40191
40519
  // adapters/next/commands/update-component/copy-namespaced-directory.ts
40192
40520
  import fs93 from "fs";
40193
- import path105 from "path";
40521
+ import path106 from "path";
40194
40522
  import fsExtra from "fs-extra";
40195
40523
 
40196
40524
  // adapters/next/commands/update-component/write-namespaced-file.ts
@@ -40208,38 +40536,38 @@ function copyNamespacedDirectory(srcDir, destDir, namespace) {
40208
40536
  const entries = fs93.readdirSync(srcDir, { withFileTypes: true });
40209
40537
  for (const entry of entries) {
40210
40538
  const namespacedName = applyAdminNamespaceToPath(entry.name, namespace);
40211
- const srcPath = path105.join(srcDir, entry.name);
40212
- const destPath = path105.join(destDir, namespacedName);
40539
+ const srcPath = path106.join(srcDir, entry.name);
40540
+ const destPath = path106.join(destDir, namespacedName);
40213
40541
  if (entry.isDirectory()) {
40214
40542
  fsExtra.ensureDirSync(destPath);
40215
40543
  copyNamespacedDirectory(srcPath, destPath, namespace);
40216
40544
  continue;
40217
40545
  }
40218
- fsExtra.ensureDirSync(path105.dirname(destPath));
40546
+ fsExtra.ensureDirSync(path106.dirname(destPath));
40219
40547
  writeNamespacedFile(srcPath, destPath, namespace);
40220
40548
  }
40221
40549
  }
40222
40550
 
40223
40551
  // adapters/next/commands/update-component/find-static-asset-file.ts
40224
40552
  import fs94 from "fs";
40225
- import path106 from "path";
40553
+ import path107 from "path";
40226
40554
  function findStaticAssetFile(assetDir, componentName) {
40227
40555
  if (!fs94.existsSync(assetDir)) return void 0;
40228
40556
  const isNestedComponentName = /[\\/]/.test(componentName);
40229
- const nestedComponentName = componentName.split(/[\\/]/).filter(Boolean).join(path106.sep);
40230
- if (isNestedComponentName && nestedComponentName && !path106.isAbsolute(componentName) && !nestedComponentName.split(path106.sep).includes("..")) {
40557
+ const nestedComponentName = componentName.split(/[\\/]/).filter(Boolean).join(path107.sep);
40558
+ if (isNestedComponentName && nestedComponentName && !path107.isAbsolute(componentName) && !nestedComponentName.split(path107.sep).includes("..")) {
40231
40559
  for (const extension of [".tsx", ".ts"]) {
40232
40560
  const relPath = `${nestedComponentName}${extension}`;
40233
- const filePath = path106.join(assetDir, relPath);
40561
+ const filePath = path107.join(assetDir, relPath);
40234
40562
  if (fs94.existsSync(filePath) && fs94.statSync(filePath).isFile()) {
40235
40563
  return relPath;
40236
40564
  }
40237
40565
  }
40238
40566
  }
40239
- if (!isNestedComponentName && !componentName.includes("..") && !path106.isAbsolute(componentName)) {
40567
+ if (!isNestedComponentName && !componentName.includes("..") && !path107.isAbsolute(componentName)) {
40240
40568
  for (const extension of [".tsx", ".ts"]) {
40241
- const relPath = path106.join(componentName, `index${extension}`);
40242
- const filePath = path106.join(assetDir, relPath);
40569
+ const relPath = path107.join(componentName, `index${extension}`);
40570
+ const filePath = path107.join(assetDir, relPath);
40243
40571
  if (fs94.existsSync(filePath) && fs94.statSync(filePath).isFile()) {
40244
40572
  return relPath;
40245
40573
  }
@@ -40288,7 +40616,7 @@ function normalizeShadcnPresetOnly(value) {
40288
40616
  import * as clack6 from "@clack/prompts";
40289
40617
  import { execFileSync as execFileSync4 } from "child_process";
40290
40618
  import fs96 from "fs";
40291
- import path109 from "path";
40619
+ import path110 from "path";
40292
40620
 
40293
40621
  // adapters/next/commands/update-component/create-admin-shadcn-components-json.ts
40294
40622
  function createAdminShadcnComponentsJson(config) {
@@ -40319,7 +40647,7 @@ function createAdminShadcnComponentsJson(config) {
40319
40647
  }
40320
40648
 
40321
40649
  // adapters/next/commands/update-component/get-host-project-files-to-restore.ts
40322
- import path107 from "path";
40650
+ import path108 from "path";
40323
40651
  function getHostProjectFilesToRestore(cwd) {
40324
40652
  const hostRelativePaths = [
40325
40653
  "app/layout.tsx",
@@ -40333,16 +40661,16 @@ function getHostProjectFilesToRestore(cwd) {
40333
40661
  "app/globals.css",
40334
40662
  "src/app/globals.css"
40335
40663
  ];
40336
- return hostRelativePaths.map((relativePath) => path107.join(cwd, relativePath));
40664
+ return hostRelativePaths.map((relativePath) => path108.join(cwd, relativePath));
40337
40665
  }
40338
40666
 
40339
40667
  // adapters/next/commands/update-component/resolve-local-shadcn-bin.ts
40340
40668
  import * as clack5 from "@clack/prompts";
40341
40669
  import fs95 from "fs";
40342
- import path108 from "path";
40670
+ import path109 from "path";
40343
40671
  function resolveLocalShadcnBin(cwd) {
40344
40672
  const binName = process.platform === "win32" ? "shadcn.cmd" : "shadcn";
40345
- const shadcnBin = path108.join(cwd, "node_modules", ".bin", binName);
40673
+ const shadcnBin = path109.join(cwd, "node_modules", ".bin", binName);
40346
40674
  if (!fs95.existsSync(shadcnBin)) {
40347
40675
  clack5.cancel(
40348
40676
  `shadcn is not installed in this project. Run 'betterstart update-deps' and try again.`
@@ -40360,13 +40688,13 @@ function runShadcnPresetUpdate({
40360
40688
  only
40361
40689
  }) {
40362
40690
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
40363
- const adminGlobalsPath = path109.join(cwd, config.paths.admin, namespace.globalsFile);
40364
- const componentsJsonPath = path109.join(cwd, "components.json");
40691
+ const adminGlobalsPath = path110.join(cwd, config.paths.admin, namespace.globalsFile);
40692
+ const componentsJsonPath = path110.join(cwd, "components.json");
40365
40693
  const shadcnBackupPath = `${componentsJsonPath}.bak`;
40366
40694
  const restoreAfterApplyPaths = [
40367
40695
  componentsJsonPath,
40368
40696
  shadcnBackupPath,
40369
- path109.join(cwd, config.paths.admin, "lib", "utils.ts"),
40697
+ path110.join(cwd, config.paths.admin, "lib", "utils.ts"),
40370
40698
  ...getHostProjectFilesToRestore(cwd)
40371
40699
  ];
40372
40700
  if (!preset) {
@@ -40375,7 +40703,7 @@ function runShadcnPresetUpdate({
40375
40703
  }
40376
40704
  if (!fs96.existsSync(adminGlobalsPath)) {
40377
40705
  clack6.cancel(
40378
- `Admin globals file not found at ${path109.relative(cwd, adminGlobalsPath)}. Run 'betterstart update admin-globals' first.`
40706
+ `Admin globals file not found at ${path110.relative(cwd, adminGlobalsPath)}. Run 'betterstart update admin-globals' first.`
40379
40707
  );
40380
40708
  process.exit(1);
40381
40709
  }
@@ -40385,7 +40713,7 @@ function runShadcnPresetUpdate({
40385
40713
  snapshot: captureFile(filePath)
40386
40714
  }));
40387
40715
  clack6.intro("BetterStart Shadcn Preset");
40388
- clack6.log.info(`Applying preset to ${path109.join(config.paths.admin, "components/ui")}`);
40716
+ clack6.log.info(`Applying preset to ${path110.join(config.paths.admin, "components/ui")}`);
40389
40717
  let failed = false;
40390
40718
  try {
40391
40719
  fs96.writeFileSync(
@@ -40441,7 +40769,7 @@ function validateShadcnPresetOptions(components, options) {
40441
40769
 
40442
40770
  // adapters/next/commands/update-component/run-update-command.ts
40443
40771
  async function runUpdateCommand(components, options) {
40444
- const cwd = options.cwd ? path110.resolve(options.cwd) : process.cwd();
40772
+ const cwd = options.cwd ? path111.resolve(options.cwd) : process.cwd();
40445
40773
  const normalizedOnly = normalizeShadcnPresetOnly(options.only);
40446
40774
  validateShadcnPresetOptions(components, options);
40447
40775
  if (options.json && !options.list) {
@@ -40471,7 +40799,7 @@ async function runUpdateCommand(components, options) {
40471
40799
  ...templateKeys.map((name) => ({ name, path: templatePath(name), kind: "template" })),
40472
40800
  { name: "tiptap", path: "components/custom/content-editor/tiptap-*/", kind: "special" }
40473
40801
  ];
40474
- console.log(JSON.stringify(items, null, 2));
40802
+ writeMachineJson(items);
40475
40803
  return;
40476
40804
  }
40477
40805
  const all = getAllComponentNames();
@@ -40502,7 +40830,7 @@ async function runUpdateCommand(components, options) {
40502
40830
  return;
40503
40831
  }
40504
40832
  const config = await resolveConfigOrExit(cwd);
40505
- const admin = path110.resolve(cwd, config.paths.admin);
40833
+ const admin = path111.resolve(cwd, config.paths.admin);
40506
40834
  if (!fs97.existsSync(admin)) {
40507
40835
  clack8.cancel(
40508
40836
  `Admin directory not found at ${config.paths.admin}. Run 'betterstart init' first.`
@@ -40557,7 +40885,7 @@ async function runUpdateCommand(components, options) {
40557
40885
  }
40558
40886
  const { relPath, content } = applyNamespaceToTemplateEntry(entry, config, cwd);
40559
40887
  const baseDir = entry.base === "cwd" ? cwd : admin;
40560
- const destPath = path110.join(baseDir, relPath);
40888
+ const destPath = path111.join(baseDir, relPath);
40561
40889
  if (entry.preserveExisting && fs97.existsSync(destPath)) {
40562
40890
  clack8.log.info(`Preserved ${relPath}`);
40563
40891
  updatedTemplateNames.add(name);
@@ -40567,7 +40895,7 @@ async function runUpdateCommand(components, options) {
40567
40895
  pendingWrites.push({
40568
40896
  displayPath: relPath,
40569
40897
  write: () => {
40570
- fsExtra2.ensureDirSync(path110.dirname(destPath));
40898
+ fsExtra2.ensureDirSync(path111.dirname(destPath));
40571
40899
  fs97.writeFileSync(destPath, content, "utf-8");
40572
40900
  clack8.log.success(`Updated ${relPath}`);
40573
40901
  }
@@ -40599,20 +40927,20 @@ async function runUpdateCommand(components, options) {
40599
40927
  }
40600
40928
  const namespace = config.frameworkConfig.next.namespace;
40601
40929
  const namespacedAssetFile = applyAdminNamespaceToPath(assetFile, namespace);
40602
- const destPath = path110.join(admin, "components", assetDirectory, namespacedAssetFile);
40930
+ const destPath = path111.join(admin, "components", assetDirectory, namespacedAssetFile);
40603
40931
  pendingWrites.push({
40604
40932
  displayPath: `components/${assetDirectory}/${namespacedAssetFile}`,
40605
40933
  write: () => {
40606
- fsExtra2.ensureDirSync(path110.dirname(destPath));
40607
- writeNamespacedFile(path110.join(assetDir, assetFile), destPath, namespace);
40934
+ fsExtra2.ensureDirSync(path111.dirname(destPath));
40935
+ writeNamespacedFile(path111.join(assetDir, assetFile), destPath, namespace);
40608
40936
  clack8.log.success(`Updated components/${assetDirectory}/${namespacedAssetFile}`);
40609
40937
  }
40610
40938
  });
40611
40939
  if (assetDirectory === "custom") {
40612
- const assetSubdir = path110.join(assetDir, name);
40940
+ const assetSubdir = path111.join(assetDir, name);
40613
40941
  if (fs97.existsSync(assetSubdir) && fs97.statSync(assetSubdir).isDirectory()) {
40614
40942
  const namespacedName = applyAdminNamespaceToPath(name, namespace);
40615
- const destSubdir = path110.join(admin, "components", assetDirectory, namespacedName);
40943
+ const destSubdir = path111.join(admin, "components", assetDirectory, namespacedName);
40616
40944
  pendingWrites.push({
40617
40945
  displayPath: `components/${assetDirectory}/${namespacedName}/ (template files)`,
40618
40946
  write: () => {
@@ -40647,19 +40975,19 @@ async function runUpdateCommand(components, options) {
40647
40975
  "content-editor"
40648
40976
  );
40649
40977
  const namespace = config.frameworkConfig.next.namespace;
40650
- const destBaseDir = path110.join(admin, "components", "custom", "content-editor");
40978
+ const destBaseDir = path111.join(admin, "components", "custom", "content-editor");
40651
40979
  if (!fs97.existsSync(srcBaseDir)) {
40652
40980
  return false;
40653
40981
  }
40654
40982
  const dirsToCopy = [];
40655
40983
  for (const directory of TIPTAP_CONTENT_EDITOR_DIRECTORIES) {
40656
- const srcDir = path110.join(srcBaseDir, directory);
40984
+ const srcDir = path111.join(srcBaseDir, directory);
40657
40985
  if (!fs97.existsSync(srcDir)) {
40658
40986
  continue;
40659
40987
  }
40660
40988
  dirsToCopy.push({
40661
40989
  srcDir,
40662
- destDir: path110.join(destBaseDir, applyAdminNamespaceToPath(directory, namespace))
40990
+ destDir: path111.join(destBaseDir, applyAdminNamespaceToPath(directory, namespace))
40663
40991
  });
40664
40992
  }
40665
40993
  if (dirsToCopy.length === 0) {
@@ -40673,7 +41001,7 @@ async function runUpdateCommand(components, options) {
40673
41001
  copyNamespacedDirectory(srcDir, destDir, namespace);
40674
41002
  }
40675
41003
  clack8.log.success("Updated components/custom/content-editor/tiptap-*/ (template files)");
40676
- removePath(admin, path110.join(admin, "components", "custom", "tiptap"));
41004
+ removePath(admin, path111.join(admin, "components", "custom", "tiptap"));
40677
41005
  }
40678
41006
  });
40679
41007
  updatedStaticNames.add(key);
@@ -40752,6 +41080,7 @@ async function runUpdateCommand(components, options) {
40752
41080
  entry.write();
40753
41081
  }
40754
41082
  }
41083
+ scaffoldPublicAssets(cwd);
40755
41084
  syncInstalledPresetManifests(cwd, config);
40756
41085
  syncInstalledIntegrationManifests(cwd, config);
40757
41086
  const projectPackageJson = readProjectPackageJson(cwd);
@@ -40770,7 +41099,7 @@ async function runUpdateCommand(components, options) {
40770
41099
 
40771
41100
  // adapters/next/commands/update-deps.ts
40772
41101
  import * as clack9 from "@clack/prompts";
40773
- import path111 from "path";
41102
+ import path112 from "path";
40774
41103
 
40775
41104
  // adapters/next/integration-runtime/get-installed-integrations.ts
40776
41105
  function getInstalledIntegrations(config) {
@@ -40784,7 +41113,7 @@ function getInstalledPresets(config) {
40784
41113
 
40785
41114
  // adapters/next/commands/update-deps.ts
40786
41115
  async function runUpdateDepsCommand(options) {
40787
- const cwd = options.cwd ? path111.resolve(options.cwd) : process.cwd();
41116
+ const cwd = options.cwd ? path112.resolve(options.cwd) : process.cwd();
40788
41117
  clack9.intro("BetterStart Update Dependencies");
40789
41118
  const pm = detectPackageManager(cwd);
40790
41119
  clack9.log.info(`Package manager: ${pm}`);
@@ -40820,16 +41149,16 @@ async function runUpdateDepsCommand(options) {
40820
41149
  // adapters/next/commands/update-styles.ts
40821
41150
  import * as clack10 from "@clack/prompts";
40822
41151
  import fs98 from "fs";
40823
- import path112 from "path";
41152
+ import path113 from "path";
40824
41153
  async function runUpdateStylesCommand(options) {
40825
- const cwd = options.cwd ? path112.resolve(options.cwd) : process.cwd();
41154
+ const cwd = options.cwd ? path113.resolve(options.cwd) : process.cwd();
40826
41155
  clack10.intro("BetterStart Update Styles");
40827
41156
  const config = await resolveConfigOrExit(cwd);
40828
41157
  const adminDir = config.paths.admin;
40829
41158
  const namespace = resolveAdminNamespace(config.frameworkConfig.next.namespace);
40830
- const targetPath = path112.join(cwd, adminDir, namespace.globalsFile);
41159
+ const targetPath = path113.join(cwd, adminDir, namespace.globalsFile);
40831
41160
  if (!fs98.existsSync(targetPath)) {
40832
- clack10.cancel(`${namespace.globalsFile} not found at ${path112.relative(cwd, targetPath)}`);
41161
+ clack10.cancel(`${namespace.globalsFile} not found at ${path113.relative(cwd, targetPath)}`);
40833
41162
  process.exit(1);
40834
41163
  }
40835
41164
  fs98.writeFileSync(
@@ -40837,7 +41166,7 @@ async function runUpdateStylesCommand(options) {
40837
41166
  applyAdminNamespaceToContent(readTemplate("admin-globals.css"), namespace.segment),
40838
41167
  "utf-8"
40839
41168
  );
40840
- clack10.log.success(`Updated ${path112.relative(cwd, targetPath)}`);
41169
+ clack10.log.success(`Updated ${path113.relative(cwd, targetPath)}`);
40841
41170
  clack10.outro("Styles updated");
40842
41171
  }
40843
41172