betterstart-cli 0.0.4 → 0.0.5

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.
@@ -66,7 +66,7 @@ export function CopyableCodeBlock({ label, code, language, tabs }: CopyableCodeB
66
66
 
67
67
  {codeTabs.map((tab) => (
68
68
  <TabsContent key={tab.id} value={tab.id} className="min-h-0 overflow-auto">
69
- <CardContent className="p-0 overflow-auto">
69
+ <CardContent className="p-0 overflow-auto select-text">
70
70
  <React.Suspense fallback={<PlainCodeFallback code={tab.code} />}>
71
71
  <LazyCodeMirror code={tab.code} language={tab.language} theme={codeTheme} />
72
72
  </React.Suspense>
@@ -92,7 +92,7 @@ export function CopyableCodeBlock({ label, code, language, tabs }: CopyableCodeB
92
92
  {isCopied ? <Check /> : <Copy />}
93
93
  </Button>
94
94
  </CardHeader>
95
- <CardContent className="p-0 overflow-auto">
95
+ <CardContent className="p-0 overflow-auto select-text">
96
96
  <React.Suspense fallback={<PlainCodeFallback code={code} />}>
97
97
  <LazyCodeMirror code={code} language={language} theme={codeTheme} />
98
98
  </React.Suspense>
@@ -19,10 +19,20 @@ const compactCodeTheme = EditorView.theme({
19
19
  '&': {
20
20
  fontSize: '12px'
21
21
  },
22
+ '.cm-editor': {
23
+ userSelect: 'text'
24
+ },
22
25
  '.cm-content': {
26
+ userSelect: 'text',
23
27
  padding: '10px 0 14px'
24
28
  },
29
+ '.cm-scroller': {
30
+ userSelect: 'text',
31
+ fontFamily:
32
+ 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace'
33
+ },
25
34
  '.cm-gutters': {
35
+ userSelect: 'none',
26
36
  padding: '0 0 14px'
27
37
  },
28
38
  '.cm-content, .cm-gutters': {
@@ -33,10 +43,6 @@ const compactCodeTheme = EditorView.theme({
33
43
  },
34
44
  '.cm-line': {
35
45
  padding: '0 16px'
36
- },
37
- '.cm-scroller': {
38
- fontFamily:
39
- 'ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace'
40
46
  }
41
47
  })
42
48
 
@@ -58,8 +64,8 @@ export default function DevModeCodeMirror({ code, language, theme }: DevModeCode
58
64
  return (
59
65
  <CodeMirror
60
66
  value={code}
61
- editable={false}
62
67
  readOnly
68
+ className="select-text"
63
69
  basicSetup={codeMirrorSetup}
64
70
  extensions={extensions}
65
71
  theme={theme === 'dark' ? githubDark : githubLight}
@@ -2,7 +2,15 @@ import type { AdminMedia } from '@admin/types'
2
2
 
3
3
  export type CreateMediaInput = Omit<
4
4
  AdminMedia,
5
- 'id' | 'createdAt' | 'updatedAt' | 'createdBy' | 'updatedBy' | 'createdByUser' | 'updatedByUser'
5
+ | 'id'
6
+ | 'createdAt'
7
+ | 'updatedAt'
8
+ | 'createdAtFormatted'
9
+ | 'updatedAtFormatted'
10
+ | 'createdBy'
11
+ | 'updatedBy'
12
+ | 'createdByUser'
13
+ | 'updatedByUser'
6
14
  >
7
15
 
8
16
  export interface CreateMediaResult {
@@ -20,6 +20,8 @@ export interface AdminMedia {
20
20
  tags: string[] | null
21
21
  createdAt: string
22
22
  updatedAt: string
23
+ createdAtFormatted?: string
24
+ updatedAtFormatted?: string
23
25
  createdBy: string | null
24
26
  updatedBy: string | null
25
27
  createdByUser?: AdminUserSummary | null
package/dist/cli.js CHANGED
@@ -3892,12 +3892,17 @@ function buildResultMapping(allDbFields, relationshipFields, Plural, camelPlural
3892
3892
  ` const createdByUser = row.createdByUser as { id?: string | null; name?: string | null; email?: string | null } | null | undefined`,
3893
3893
  ` const updatedByUser = row.updatedByUser as { id?: string | null; name?: string | null; email?: string | null } | null | undefined`
3894
3894
  ].filter(Boolean).join("\n");
3895
+ const mappedTimestampFields = allDbFields.some((f) => f.name === "createdAtFormatted") ? [] : [
3896
+ ` createdAtFormatted: formatActionDate(row.createdAt)`,
3897
+ ` updatedAtFormatted: formatActionDate(row.updatedAt)`
3898
+ ];
3895
3899
  const rowMappings = allDbFields.map((f) => {
3896
3900
  if (f.type === "relationship" && !f.multiple) {
3897
3901
  return ` ${f.name}: ${f.name}?.id ? { id: ${f.name}.id, name: ${f.name}.name ?? undefined, title: ${f.name}.title ?? undefined, slug: ${f.name}.slug ?? undefined, label: ${f.name}.label ?? undefined } : null`;
3898
3902
  }
3899
3903
  return ` ${f.name}: row.${f.name}`;
3900
3904
  }).concat([
3905
+ ...mappedTimestampFields,
3901
3906
  ` createdByUser: createdByUser?.id ? { id: createdByUser.id, name: createdByUser.name ?? '', email: createdByUser.email ?? '' } : null`,
3902
3907
  ` updatedByUser: updatedByUser?.id ? { id: updatedByUser.id, name: updatedByUser.name ?? '', email: updatedByUser.email ?? '' } : null`
3903
3908
  ]).join(",\n");
@@ -3938,9 +3943,14 @@ function buildSingleRowMapping(allDbFields, _relationshipFields, typeName, htmlF
3938
3943
  return ` ${f.name}: row.${f.name}`;
3939
3944
  });
3940
3945
  const htmlMappings = htmlFields.map((f) => ` ${f.name}Html: row.${f.name}Html`);
3946
+ const singleTimestampFields = allDbFields.some((f) => f.name === "createdAtFormatted") ? [] : [
3947
+ ` createdAtFormatted: formatActionDate(row.createdAt)`,
3948
+ ` updatedAtFormatted: formatActionDate(row.updatedAt)`
3949
+ ];
3941
3950
  const mappings = [
3942
3951
  ...fieldMappings,
3943
3952
  ...htmlMappings,
3953
+ ...singleTimestampFields,
3944
3954
  ` createdByUser: row.createdByUser?.id ? { id: row.createdByUser.id, name: row.createdByUser.name, email: row.createdByUser.email } : null`,
3945
3955
  ` updatedByUser: row.updatedByUser?.id ? { id: row.updatedByUser.id, name: row.updatedByUser.name, email: row.updatedByUser.email } : null`
3946
3956
  ].join(",\n");
@@ -4172,6 +4182,7 @@ var SINGLE_ACTION_FIELD_EXCLUSIONS = /* @__PURE__ */ new Set(["id", "createdAt",
4172
4182
  var DEFAULT_ADMIN_IMPORT_ALIAS = "@admin";
4173
4183
  var SAMPLE_UUID = "00000000-0000-4000-8000-000000000001";
4174
4184
  var SINGLETON_ID = "00000000-0000-0000-0000-000000000001";
4185
+ var SAMPLE_FORMATTED_TIMESTAMP = "Jan 01, 2026";
4175
4186
  function isActionField(field, exclusions) {
4176
4187
  return !field.primaryKey && !exclusions.has(field.name) && !isAuthorshipFieldName(field.name) && !isLayoutField(field.type);
4177
4188
  }
@@ -4354,6 +4365,8 @@ function sampleAdminMedia() {
4354
4365
  tags: ["example"],
4355
4366
  createdAt: "2026-01-01T00:00:00.000Z",
4356
4367
  updatedAt: "2026-01-01T00:00:00.000Z",
4368
+ createdAtFormatted: SAMPLE_FORMATTED_TIMESTAMP,
4369
+ updatedAtFormatted: SAMPLE_FORMATTED_TIMESTAMP,
4357
4370
  createdBy: "user-id",
4358
4371
  updatedBy: "user-id"
4359
4372
  };
@@ -4392,7 +4405,7 @@ function createEntityDataObject(schema) {
4392
4405
  return field.type === "image" || field.type === "video" || field.type === "media";
4393
4406
  });
4394
4407
  const galleryFields = regularDbFields.filter((field) => field.type === "gallery");
4395
- return {
4408
+ return addFormattedTimestamps({
4396
4409
  ...Object.fromEntries(allDbFields.map((field) => [field.name, sampleSchemaOutputValue(field)])),
4397
4410
  ...Object.fromEntries(
4398
4411
  htmlOutputFields.map((field) => [
@@ -4407,7 +4420,17 @@ function createEntityDataObject(schema) {
4407
4420
  ),
4408
4421
  createdByUser: sampleAdminUserSummary(),
4409
4422
  updatedByUser: sampleAdminUserSummary()
4410
- };
4423
+ });
4424
+ }
4425
+ function addFormattedTimestamps(data) {
4426
+ const output = { ...data };
4427
+ if (typeof output.createdAt === "string") {
4428
+ output.createdAtFormatted = SAMPLE_FORMATTED_TIMESTAMP;
4429
+ }
4430
+ if (typeof output.updatedAt === "string") {
4431
+ output.updatedAtFormatted = SAMPLE_FORMATTED_TIMESTAMP;
4432
+ }
4433
+ return output;
4411
4434
  }
4412
4435
  function getSingleAllDbFields(schema) {
4413
4436
  const dbFields = flattenFields(schema.fields).filter(
@@ -4430,7 +4453,7 @@ function getSingleAllDbFields(schema) {
4430
4453
  function createSingleDataObject(schema) {
4431
4454
  const dbFields = getSingleAllDbFields(schema);
4432
4455
  const htmlOutputFields = getHtmlOutputFields(dbFields);
4433
- return {
4456
+ return addFormattedTimestamps({
4434
4457
  ...Object.fromEntries(dbFields.map((field) => [field.name, sampleSingleOutputValue(field)])),
4435
4458
  ...Object.fromEntries(
4436
4459
  htmlOutputFields.map((field) => [
@@ -4440,7 +4463,7 @@ function createSingleDataObject(schema) {
4440
4463
  ),
4441
4464
  createdByUser: sampleAdminUserSummary(),
4442
4465
  updatedByUser: sampleAdminUserSummary()
4443
- };
4466
+ });
4444
4467
  }
4445
4468
  function createFormSubmissionObject(fields) {
4446
4469
  return {
@@ -8021,6 +8044,19 @@ function genHelpersContent(ctx) {
8021
8044
  lines.push(`import type { AdminMedia } from '@admin/types'`);
8022
8045
  }
8023
8046
  lines.push(`import type { ${ctx.Singular} } from './types'`);
8047
+ lines.push("");
8048
+ lines.push(`export function formatActionDate(value: unknown): string {
8049
+ if (typeof value !== 'string') return ''
8050
+ const date = new Date(value)
8051
+ if (Number.isNaN(date.getTime())) return ''
8052
+
8053
+ return date.toLocaleDateString('en-US', {
8054
+ month: 'short',
8055
+ day: '2-digit',
8056
+ year: 'numeric',
8057
+ timeZone: 'UTC'
8058
+ })
8059
+ }`);
8024
8060
  lines.push("");
8025
8061
  if (ctx.hasListRels) {
8026
8062
  const exportedFn = ctx.populateListRelsFn.replace("async function", "export async function").trim();
@@ -8133,10 +8169,32 @@ function generateResolveMediaFunction(ctx) {
8133
8169
  }`
8134
8170
  ).join("\n");
8135
8171
  const assignSingle = singleFields.map(
8136
- (name) => ` row.${name}Media = (row.${name} && !row.${name}.startsWith('http')) ? mediaMap.get(row.${name}) ?? null : null`
8172
+ (name) => ` row.${name}Media = row.${name} && !row.${name}.startsWith('http')
8173
+ ? (() => {
8174
+ const media = mediaMap.get(row.${name})
8175
+ if (!media) return null
8176
+ return {
8177
+ ...media,
8178
+ createdAtFormatted: formatActionDate(media.createdAt),
8179
+ updatedAtFormatted: formatActionDate(media.updatedAt)
8180
+ }
8181
+ })()
8182
+ : null`
8137
8183
  ).join("\n");
8138
8184
  const assignArray = arrayFields.map(
8139
- (name) => ` row.${name}Media = Array.isArray(row.${name}) ? row.${name}.map((id: string) => mediaMap.get(id)).filter(Boolean) as AdminMedia[] : []`
8185
+ (name) => ` row.${name}Media = Array.isArray(row.${name})
8186
+ ? row.${name}
8187
+ .map((id: string) => {
8188
+ const media = mediaMap.get(id)
8189
+ if (!media) return null
8190
+ return {
8191
+ ...media,
8192
+ createdAtFormatted: formatActionDate(media.createdAt),
8193
+ updatedAtFormatted: formatActionDate(media.updatedAt)
8194
+ }
8195
+ })
8196
+ .filter(Boolean) as AdminMedia[]
8197
+ : []`
8140
8198
  ).join("\n");
8141
8199
  return `export async function resolve${ctx.Singular}MediaFields(rows: ${ctx.Singular}[]): Promise<${ctx.Singular}[]> {
8142
8200
  if (rows.length === 0) return rows
@@ -8176,7 +8234,7 @@ function genGetPluralContent(ctx) {
8176
8234
  if (ctx.hasRelationships) drizzle.push("eq");
8177
8235
  const sortedDrizzle = [...new Set(drizzle)].sort();
8178
8236
  const typeImports = [`${ctx.Plural}Page`, `${ctx.Plural}Query`, `${ctx.Singular}`];
8179
- const helperImports = [];
8237
+ const helperImports = ["formatActionDate"];
8180
8238
  if (ctx.hasListRels) helperImports.push(`populate${ctx.Singular}ListRelationships`);
8181
8239
  if (ctx.hasMediaFields) helperImports.push(`resolve${ctx.Singular}MediaFields`);
8182
8240
  const populateImport = helperImports.length > 0 ? `
@@ -8266,7 +8324,7 @@ function genGetByIdContent(ctx) {
8266
8324
  const dbImports = [ctx.tableVar, "user", ...ctx.relTableImports].sort();
8267
8325
  const drizzle = ["eq"];
8268
8326
  const sortedDrizzle = [...new Set(drizzle)].sort();
8269
- const byIdHelperImports = [];
8327
+ const byIdHelperImports = ["formatActionDate"];
8270
8328
  if (ctx.hasListRels) byIdHelperImports.push(`populate${ctx.Singular}ListRelationships`);
8271
8329
  if (ctx.hasMediaFields) byIdHelperImports.push(`resolve${ctx.Singular}MediaFields`);
8272
8330
  const populateImport = byIdHelperImports.length > 0 ? `
@@ -8304,7 +8362,7 @@ function genGetBySlugContent(ctx) {
8304
8362
  const dbImports = [ctx.tableVar, "user", ...ctx.relTableImports].sort();
8305
8363
  const drizzle = ["eq"];
8306
8364
  const sortedDrizzle = [...new Set(drizzle)].sort();
8307
- const bySlugHelperImports = [];
8365
+ const bySlugHelperImports = ["formatActionDate"];
8308
8366
  if (ctx.hasListRels) bySlugHelperImports.push(`populate${ctx.Singular}ListRelationships`);
8309
8367
  if (ctx.hasMediaFields) bySlugHelperImports.push(`resolve${ctx.Singular}MediaFields`);
8310
8368
  const populateImport = bySlugHelperImports.length > 0 ? `
@@ -9788,13 +9846,16 @@ function buildEntityContext(schema, nextMajorVersion) {
9788
9846
  const mediaTypeImport = hasMediaFields ? `
9789
9847
  import type { AdminMedia } from '@admin/types'
9790
9848
  ` : "";
9849
+ const hasTimestampFields = allDbFields.some((f) => f.name === "createdAt") && allDbFields.some((f) => f.name === "updatedAt");
9850
+ const hasFormattedTimestampFields = allDbFields.some((f) => f.name === "createdAtFormatted");
9851
+ const timestampFields = hasTimestampFields && !hasFormattedTimestampFields ? "\n createdAtFormatted: string\n updatedAtFormatted: string" : "";
9791
9852
  const dataInterface = `${mediaTypeImport}${buildAuthorshipUserType()}
9792
9853
 
9793
9854
  export interface ${Singular} {
9794
9855
  ${dataFields}${htmlFieldTypes ? `
9795
9856
  ${htmlFieldTypes}` : ""}${m2mFieldTypes ? `
9796
9857
  ${m2mFieldTypes}` : ""}${mediaCompanionTypes ? `
9797
- ${mediaCompanionTypes}` : ""}
9858
+ ${mediaCompanionTypes}` : ""}${timestampFields}
9798
9859
  ${buildAuthorshipDataFields()}
9799
9860
  }`;
9800
9861
  const responseInterface = `export interface ${Plural}Page {
@@ -10071,6 +10132,9 @@ function generateSingleActions(schema, actionsDir, options = {}) {
10071
10132
  (f) => ` ${quotePropertyName(f.name)}: ${getFieldType(f, "output")}${f.required || f.primaryKey ? "" : " | null"}`
10072
10133
  ).join("\n");
10073
10134
  const htmlFieldTypes = htmlOutputFields.map((f) => ` ${f.name}Html: string`).join("\n");
10135
+ const hasTimestampFields = allDbFields.some((f) => f.name === "createdAt") && allDbFields.some((f) => f.name === "updatedAt");
10136
+ const hasFormattedTimestampFields = allDbFields.some((f) => f.name === "createdAtFormatted");
10137
+ const timestampFields = hasTimestampFields && !hasFormattedTimestampFields ? "\n createdAtFormatted: string\n updatedAtFormatted: string" : "";
10074
10138
  const upsertInterfaceFields = upsertFields.map((f) => ` ${quotePropertyName(f.name)}?: ${getFieldType(f, "input")}`).join("\n");
10075
10139
  const fieldMeta = upsertFields.map((f) => `{ name: '${f.name}', type: '${f.type}', required: ${f.required ?? false} }`).join(",\n ");
10076
10140
  const cacheTag = `${schema.name}:all`;
@@ -10096,7 +10160,7 @@ function generateSingleActions(schema, actionsDir, options = {}) {
10096
10160
  buildAuthorshipUserType(),
10097
10161
  `export interface ${Singular} {
10098
10162
  ${dataFields}${htmlFieldTypes ? `
10099
- ${htmlFieldTypes}` : ""}
10163
+ ${htmlFieldTypes}` : ""}${timestampFields}
10100
10164
  ${buildAuthorshipDataFields()}
10101
10165
  }`,
10102
10166
  `export interface ${Singular}UpsertInput {
@@ -10138,6 +10202,19 @@ ${buildAuthorshipAliasBlock()}
10138
10202
 
10139
10203
  const SINGLETON_ID = '${singletonId}'
10140
10204
 
10205
+ function formatActionDate(value: unknown): string {
10206
+ if (typeof value !== 'string') return ''
10207
+ const date = new Date(value)
10208
+ if (Number.isNaN(date.getTime())) return ''
10209
+
10210
+ return date.toLocaleDateString('en-US', {
10211
+ month: 'short',
10212
+ day: '2-digit',
10213
+ year: 'numeric',
10214
+ timeZone: 'UTC'
10215
+ })
10216
+ }
10217
+
10141
10218
  export async function get${Singular}(): Promise<${Singular} | null> {
10142
10219
  'use cache'${cacheLifeLine}
10143
10220
  cacheTag(${cacheTagsVar}.all)