@betterstart/cli 0.1.70 → 0.1.71

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -6969,6 +6969,180 @@ ${hasDraft ? ` <Button
6969
6969
  // src/generators/form/form-single.ts
6970
6970
  import fs21 from "fs";
6971
6971
  import path21 from "path";
6972
+ function parseCardGroups(allFormFields) {
6973
+ const groups = [];
6974
+ let currentFields = [];
6975
+ let nextTitle = "";
6976
+ let nextDescription = "";
6977
+ for (const field of allFormFields) {
6978
+ if (field.type === "break") {
6979
+ if (currentFields.length > 0) {
6980
+ groups.push(buildGroup(currentFields, nextTitle, nextDescription));
6981
+ }
6982
+ nextTitle = field.label || "";
6983
+ nextDescription = field.hint || "";
6984
+ currentFields = [];
6985
+ continue;
6986
+ }
6987
+ if (field.type === "separator") continue;
6988
+ currentFields.push(field);
6989
+ }
6990
+ if (currentFields.length > 0) {
6991
+ groups.push(buildGroup(currentFields, nextTitle, nextDescription));
6992
+ }
6993
+ return groups;
6994
+ }
6995
+ function buildGroup(fields, titleOverride, description) {
6996
+ const firstField = fields[0];
6997
+ const title = titleOverride || firstField.label || firstField.name;
6998
+ const varPrefix = toCamelCase(firstField.name);
6999
+ return {
7000
+ title,
7001
+ description,
7002
+ varPrefix,
7003
+ componentName: `${toPascalCase(firstField.name)}Card`,
7004
+ fields,
7005
+ flatFields: flattenFields(fields).filter((f) => !isLayoutField(f.type))
7006
+ };
7007
+ }
7008
+ function analyzeGroup(fields) {
7009
+ const tabFieldNames = /* @__PURE__ */ new Set();
7010
+ for (const f of fields) {
7011
+ if (f.type === "tabs" && f.tabs) {
7012
+ for (const tab of f.tabs) {
7013
+ if (tab.fields) for (const tf of tab.fields) tabFieldNames.add(tf.name);
7014
+ }
7015
+ }
7016
+ }
7017
+ const relFields = collectRelationshipFields(fields, tabFieldNames);
7018
+ const listFieldsWithNested = [];
7019
+ function collectLists(flds) {
7020
+ for (const f of flds) {
7021
+ if (f.type === "list" && f.fields && f.fields.length > 0 && !f.hidden) {
7022
+ listFieldsWithNested.push(f);
7023
+ }
7024
+ if (f.type === "group" && f.fields) collectLists(f.fields);
7025
+ if (f.type === "tabs" && f.tabs) {
7026
+ for (const tab of f.tabs) {
7027
+ if (tab.fields) collectLists(tab.fields);
7028
+ }
7029
+ }
7030
+ }
7031
+ }
7032
+ collectLists(fields);
7033
+ const hasTabsField = fields.some((f) => f.type === "tabs");
7034
+ const tabsField = fields.find((f) => f.type === "tabs");
7035
+ const firstTabName = tabsField?.tabs?.[0]?.name || "";
7036
+ return { relFields, listFieldsWithNested, hasTabsField, tabsField, firstTabName, tabFieldNames };
7037
+ }
7038
+ function buildGroupFieldsJSX(group3, analysis, skipLabel) {
7039
+ const indent = " ";
7040
+ return group3.fields.map((f) => {
7041
+ if (f.type === "tabs" && f.tabs) {
7042
+ const tabsList = f.tabs.map((t) => ` <TabsTrigger value="${t.name}">${t.label}</TabsTrigger>`).join("\n");
7043
+ const tabsContent = f.tabs.map((t) => {
7044
+ const tabFields = (t.fields || []).map((tf) => generateFieldJSX2(tf, " ")).join("\n");
7045
+ return ` <TabsContent value="${t.name}" className="space-y-6">
7046
+ ${tabFields}
7047
+ </TabsContent>`;
7048
+ }).join("\n");
7049
+ return `${indent}<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
7050
+ <TabsList>
7051
+ ${tabsList}
7052
+ </TabsList>
7053
+ ${tabsContent}
7054
+ ${indent}</Tabs>`;
7055
+ }
7056
+ if (analysis.tabFieldNames.has(f.name)) return "";
7057
+ return generateFieldJSX2(f, indent, { skipLabel });
7058
+ }).filter(Boolean).join("\n");
7059
+ }
7060
+ function buildGroupRelState(analysis) {
7061
+ return analysis.relFields.map((f) => {
7062
+ const relPlural = toPascalCase(pluralize(f.relationship || ""));
7063
+ return ` const [${f.name}Open, set${toPascalCase(f.name)}Open] = React.useState(false)
7064
+ const { data: ${f.relationship}Data } = use${relPlural}()`;
7065
+ }).filter((v, i, a) => a.indexOf(v) === i).join("\n");
7066
+ }
7067
+ function buildGroupFieldArrayHooks(analysis) {
7068
+ return analysis.listFieldsWithNested.map((field) => {
7069
+ const pascalFieldName = toPascalCase(field.name);
7070
+ return ` const [${field.name}Expanded, set${pascalFieldName}Expanded] = React.useState<string | undefined>(undefined)
7071
+ const ${field.name}FieldArray = useFieldArray({
7072
+ control: form.control,
7073
+ name: '${field.name}'
7074
+ })`;
7075
+ }).join("\n");
7076
+ }
7077
+ function generateCardComponent(group3, schema, analysis) {
7078
+ const singular = singularize(schema.name);
7079
+ const Singular = toPascalCase(singular);
7080
+ const zodFields = buildZodFields2(group3.flatFields);
7081
+ const defaultValues = buildDefaultValues2(group3.flatFields);
7082
+ const isSingleField = group3.fields.length === 1 && group3.title === (group3.fields[0].label || group3.fields[0].name);
7083
+ const fieldsJSX = buildGroupFieldsJSX(group3, analysis, isSingleField);
7084
+ const relState = buildGroupRelState(analysis);
7085
+ const fieldArrayHooks = buildGroupFieldArrayHooks(analysis);
7086
+ const needsReact = analysis.relFields.length > 0 || analysis.listFieldsWithNested.length > 0;
7087
+ const descriptionLine = group3.description ? `
7088
+ <CardDescription>${group3.description}</CardDescription>` : "";
7089
+ return `const ${group3.varPrefix}Schema = z.object({
7090
+ ${zodFields}
7091
+ })
7092
+
7093
+ function ${group3.componentName}({ initialData }: { initialData?: ${Singular}Data | null }) {
7094
+ const queryClient = useQueryClient()${analysis.hasTabsField ? `
7095
+ const [activeTab, setActiveTab] = useQueryState('tab', { defaultValue: '${analysis.firstTabName}' })` : ""}${relState ? `
7096
+ ${relState}` : ""}
7097
+
7098
+ const mutation = useMutation({
7099
+ mutationFn: (data: Upsert${Singular}Input) => upsert${Singular}(data),
7100
+ onSuccess: () => {
7101
+ toast.success('${group3.title} saved')
7102
+ queryClient.invalidateQueries({ queryKey: ['${schema.name}'] })
7103
+ },
7104
+ onError: (error: Error) => {
7105
+ toast.error(error.message || 'Failed to save')
7106
+ }
7107
+ })
7108
+
7109
+ const isPending = mutation.isPending
7110
+
7111
+ const form = useForm<z.infer<typeof ${group3.varPrefix}Schema>>({
7112
+ resolver: zodResolver(${group3.varPrefix}Schema),
7113
+ defaultValues: {
7114
+ ${defaultValues}
7115
+ }
7116
+ })
7117
+ ${fieldArrayHooks ? `
7118
+ ${fieldArrayHooks}
7119
+ ` : ""}
7120
+ return (
7121
+ <Form {...form}>
7122
+ <form onSubmit={form.handleSubmit((values) => {
7123
+ const cleaned = Object.fromEntries(
7124
+ Object.entries(values).map(([key, value]) => [key, value === undefined ? '' : value])
7125
+ )
7126
+ mutation.mutate(cleaned as Upsert${Singular}Input)
7127
+ })}>
7128
+ <Card>
7129
+ <CardHeader>
7130
+ <CardTitle>${group3.title}</CardTitle>${descriptionLine}
7131
+ </CardHeader>
7132
+ <CardContent className="space-y-6">
7133
+ ${fieldsJSX}
7134
+ </CardContent>
7135
+ <CardFooter>
7136
+ <Button type="submit" disabled={isPending} size="sm">
7137
+ {isPending ? 'Saving...' : 'Save'}
7138
+ </Button>
7139
+ </CardFooter>
7140
+ </Card>
7141
+ </form>
7142
+ </Form>
7143
+ )
7144
+ }`;
7145
+ }
6972
7146
  function generateSingleForm(schema, cwd, pagesDir, options = {}) {
6973
7147
  const entityDir = path21.join(cwd, pagesDir, schema.name);
6974
7148
  const formFilePath = path21.join(entityDir, `${schema.name}-form.tsx`);
@@ -6980,15 +7154,7 @@ function generateSingleForm(schema, cwd, pagesDir, options = {}) {
6980
7154
  const allFormFields = schema.fields.filter(
6981
7155
  (f) => !f.primaryKey && f.name !== "createdAt" && f.name !== "updatedAt"
6982
7156
  );
6983
- const flatFields = flattenFields(allFormFields);
6984
- const tabFieldNames = /* @__PURE__ */ new Set();
6985
- for (const f of schema.fields) {
6986
- if (f.type === "tabs" && f.tabs) {
6987
- for (const tab of f.tabs) {
6988
- if (tab.fields) for (const tf of tab.fields) tabFieldNames.add(tf.name);
6989
- }
6990
- }
6991
- }
7157
+ const cardGroups = parseCardGroups(allFormFields);
6992
7158
  const hasBoolean = hasFieldType(schema.fields, "boolean");
6993
7159
  const hasImage = hasFieldType(schema.fields, "image");
6994
7160
  const hasVideo = hasFieldType(schema.fields, "video");
@@ -7003,48 +7169,20 @@ function generateSingleForm(schema, cwd, pagesDir, options = {}) {
7003
7169
  const hasSeparator = hasFieldType(schema.fields, "separator");
7004
7170
  const hasRelationship = hasFieldType(schema.fields, "relationship");
7005
7171
  const hasTabsField = schema.fields.some((f) => f.type === "tabs");
7006
- const tabsField = schema.fields.find((f) => f.type === "tabs");
7007
- const firstTabName = tabsField?.tabs?.[0]?.name || "";
7008
- const mainRelFields = collectRelationshipFields(schema.fields, tabFieldNames);
7009
- const listFieldsWithNested = [];
7010
- function collectListFieldsSingle(fields) {
7011
- for (const f of fields) {
7012
- if (f.type === "list" && f.fields && f.fields.length > 0 && !f.hidden) {
7013
- listFieldsWithNested.push(f);
7014
- }
7015
- if (f.type === "group" && f.fields) collectListFieldsSingle(f.fields);
7016
- if (f.type === "tabs" && f.tabs) {
7017
- for (const tab of f.tabs) {
7018
- if (tab.fields) collectListFieldsSingle(tab.fields);
7019
- }
7020
- }
7021
- }
7022
- }
7023
- collectListFieldsSingle(allFormFields);
7172
+ const flatFields = flattenFields(allFormFields);
7024
7173
  const hasList = hasFieldType(schema.fields, "list");
7025
- const hasNestedList = listFieldsWithNested.length > 0;
7174
+ const hasNestedList = cardGroups.some((g) => analyzeGroup(g.fields).listFieldsWithNested.length > 0);
7026
7175
  const hasSimpleList = hasList && flatFields.some((f) => f.type === "list" && (!f.fields || f.fields.length === 0));
7027
- const zodFields = buildZodFields2(flatFields);
7028
- const defaultValues = buildDefaultValues2(flatFields);
7029
- const formFieldsJSX = allFormFields.map((f) => {
7030
- if (f.type === "tabs" && f.tabs) {
7031
- const tabsList = f.tabs.map((t) => ` <TabsTrigger value="${t.name}">${t.label}</TabsTrigger>`).join("\n");
7032
- const tabsContent = f.tabs.map((t) => {
7033
- const tabFields = (t.fields || []).map((tf) => generateFieldJSX2(tf, " ")).join("\n");
7034
- return ` <TabsContent value="${t.name}" className="space-y-6 p-6 rounded-2xl border bg-card">
7035
- ${tabFields}
7036
- </TabsContent>`;
7037
- }).join("\n");
7038
- return ` <Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
7039
- <TabsList>
7040
- ${tabsList}
7041
- </TabsList>
7042
- ${tabsContent}
7043
- </Tabs>`;
7176
+ const allRelFields = [];
7177
+ for (const g of cardGroups) {
7178
+ const a = analyzeGroup(g.fields);
7179
+ for (const rf of a.relFields) {
7180
+ if (!allRelFields.some((e) => e.relationship === rf.relationship)) {
7181
+ allRelFields.push(rf);
7182
+ }
7044
7183
  }
7045
- if (tabFieldNames.has(f.name)) return "";
7046
- return generateFieldJSX2(f);
7047
- }).filter(Boolean).join("\n");
7184
+ }
7185
+ const needsReact = allRelFields.length > 0 || hasNestedList;
7048
7186
  const uiImports = buildUiImports({
7049
7187
  hasBoolean,
7050
7188
  hasTextarea,
@@ -7063,30 +7201,29 @@ ${tabsContent}
7063
7201
  hasSimpleList,
7064
7202
  hasNestedList
7065
7203
  });
7204
+ uiImports.push(`import {
7205
+ Card,
7206
+ CardContent,
7207
+ CardDescription,
7208
+ CardFooter,
7209
+ CardHeader,
7210
+ CardTitle
7211
+ } from '@cms/components/ui/card'`);
7066
7212
  const lucideIcons = [];
7067
7213
  if (hasRelationship) lucideIcons.push("Check", "ChevronsUpDown");
7068
7214
  if (hasNestedList) {
7069
7215
  if (!lucideIcons.includes("Plus")) lucideIcons.push("Plus");
7070
7216
  if (!lucideIcons.includes("X")) lucideIcons.push("X");
7071
7217
  }
7072
- const relHookImports = mainRelFields.map((f) => {
7218
+ const relHookImports = allRelFields.map((f) => {
7073
7219
  const relPlural = toPascalCase(pluralize(f.relationship || ""));
7074
7220
  return `import { use${relPlural} } from '@cms/hooks/use-${f.relationship}'`;
7075
7221
  }).filter((v, i, a) => a.indexOf(v) === i).join("\n");
7076
- const relState = mainRelFields.map((f) => {
7077
- const relPlural = toPascalCase(pluralize(f.relationship || ""));
7078
- return ` const [${f.name}Open, set${toPascalCase(f.name)}Open] = React.useState(false)
7079
- const { data: ${f.relationship}Data } = use${relPlural}()`;
7080
- }).filter((v, i, a) => a.indexOf(v) === i).join("\n");
7081
- const fieldArrayHooks = listFieldsWithNested.map((field) => {
7082
- const pascalFieldName = toPascalCase(field.name);
7083
- return ` const [${field.name}Expanded, set${pascalFieldName}Expanded] = React.useState<string | undefined>(undefined)
7084
- const ${field.name}FieldArray = useFieldArray({
7085
- control: form.control,
7086
- name: '${field.name}'
7087
- })`;
7088
- }).join("\n");
7089
- const needsReact = mainRelFields.length > 0 || hasNestedList;
7222
+ const cardComponents = cardGroups.map((group3) => {
7223
+ const analysis = analyzeGroup(group3.fields);
7224
+ return generateCardComponent(group3, schema, analysis);
7225
+ });
7226
+ const cardRenders = cardGroups.map((g) => ` <${g.componentName} initialData={initialData} />`).join("\n");
7090
7227
  const content = `'use client'
7091
7228
  ${needsReact ? "\nimport * as React from 'react'" : ""}
7092
7229
  import { zodResolver } from '@hookform/resolvers/zod'
@@ -7103,75 +7240,17 @@ import type {
7103
7240
  } from '@cms/actions/${schema.name}'
7104
7241
  import { upsert${Singular} } from '@cms/actions/${schema.name}'
7105
7242
 
7106
- const formSchema = z.object({
7107
- ${zodFields}
7108
- })
7109
-
7110
- export type FormValues = z.infer<typeof formSchema>
7243
+ ${cardComponents.join("\n\n")}
7111
7244
 
7112
7245
  interface ${Singular}FormProps {
7113
7246
  initialData?: ${Singular}Data | null
7114
7247
  }
7115
7248
 
7116
7249
  export function ${Singular}Form({ initialData }: ${Singular}FormProps) {
7117
- const queryClient = useQueryClient()${hasTabsField ? `
7118
- const [activeTab, setActiveTab] = useQueryState('tab', { defaultValue: '${firstTabName}' })` : ""}${relState ? `
7119
- ${relState}` : ""}
7120
-
7121
- const upsertMutation = useMutation({
7122
- mutationFn: (data: Upsert${Singular}Input) => upsert${Singular}(data),
7123
- onSuccess: () => {
7124
- toast.success('${schema.label} saved successfully')
7125
- queryClient.invalidateQueries({ queryKey: ['${schema.name}'] })
7126
- },
7127
- onError: (error: Error) => {
7128
- toast.error(error.message || 'Failed to save ${schema.label.toLowerCase()}')
7129
- }
7130
- })
7131
-
7132
- const isPending = upsertMutation.isPending
7133
-
7134
- const form = useForm<FormValues>({
7135
- resolver: zodResolver(formSchema),
7136
- defaultValues: {
7137
- ${defaultValues}
7138
- }
7139
- })
7140
- ${fieldArrayHooks ? `
7141
- ${fieldArrayHooks}
7142
- ` : ""}
7143
- function onSubmit(values: FormValues) {
7144
- const cleanedValues = Object.fromEntries(
7145
- Object.entries(values).map(([key, value]) => [key, value === undefined ? '' : value])
7146
- ) as FormValues
7147
-
7148
- upsertMutation.mutate(cleanedValues as Upsert${Singular}Input)
7149
- }
7150
-
7151
7250
  return (
7152
- <Form {...form}>
7153
- <form id="${schema.name}-form" onSubmit={form.handleSubmit(onSubmit, (errors) => {
7154
- console.error('Form validation errors:', errors)
7155
- const firstError = Object.values(errors)[0]
7156
- if (firstError?.message) {
7157
- toast.error(String(firstError.message))
7158
- } else {
7159
- toast.error('Please fix the form errors before submitting')
7160
- }
7161
- })} className="space-y-6">
7162
- <div className="space-y-6 p-6 rounded-2xl border bg-card">
7163
- ${formFieldsJSX}
7164
- </div>
7165
-
7166
- <div className="flex items-center fixed bottom-0 md:left-[calc(var(--sidebar-width))] w-screen md:w-[calc(100svw-var(--sidebar-width)-4px)] right-0 bg-secondary border-t">
7167
- <div className="flex mx-auto py-4 w-full max-w-5xl items-center justify-end gap-2">
7168
- <Button type="submit" disabled={isPending} size="lg">
7169
- {isPending ? 'Saving...' : 'Save'}
7170
- </Button>
7171
- </div>
7172
- </div>
7173
- </form>
7174
- </Form>
7251
+ <div className="space-y-6">
7252
+ ${cardRenders}
7253
+ </div>
7175
7254
  )
7176
7255
  }
7177
7256
  `;
@@ -9998,14 +10077,12 @@ function defaultSettingsSchema() {
9998
10077
  name: "settings",
9999
10078
  type: "single",
10000
10079
  label: "Settings",
10001
- description: "General site settings",
10080
+ description: "General Settings",
10002
10081
  icon: "Settings",
10003
10082
  fields: [
10004
10083
  { name: "siteName", type: "string", label: "Site Name", default: "BetterStart" },
10005
- { name: "tagline", type: "string", label: "Tagline" },
10006
- { name: "separator1", type: "separator" },
10007
- { name: "logo", type: "image", label: "Logo" },
10008
- { name: "favicon", type: "image", label: "Favicon" }
10084
+ { name: "break1", type: "break" },
10085
+ { name: "logo", type: "image", label: "Logo" }
10009
10086
  ]
10010
10087
  },
10011
10088
  null,