betterstart-cli 0.0.16 → 0.0.18
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/assets/adapters/next/plugins/blog/schemas/menus.json +1 -0
- package/dist/assets/adapters/next/plugins/blog/schemas/posts.json +132 -99
- package/dist/assets/adapters/next/plugins/portfolio/plugin.ts +17 -0
- package/dist/assets/adapters/next/plugins/portfolio/schemas/portfolio.json +128 -0
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/dev-mode-types.ts +2 -2
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode/snippets-tab.tsx +1 -0
- package/dist/assets/adapters/next/templates/init/components/shared/dev-mode-integrate.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-dialog.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-delete-drawer.tsx +1 -1
- package/dist/assets/adapters/next/templates/init/components/shared/media/media-grid-item.tsx +17 -7
- package/dist/assets/adapters/next/templates/init/data/navigation.ts +2 -1
- package/dist/assets/adapters/next/templates/init/hooks/content-editor/use-content-editor-source-mode.tsx +3 -3
- package/dist/assets/adapters/next/templates/init/hooks/use-media.ts +2 -1
- package/dist/assets/adapters/next/templates/init/pages/profile/profile-form.tsx +3 -3
- package/dist/assets/adapters/next/templates/init/types/index.ts +7 -2
- package/dist/assets/shared-assets/react-admin/custom/content-editor/media-gallery-block.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/content-editor/table-add-controls.tsx +2 -2
- package/dist/assets/shared-assets/react-admin/custom/gallery-field.tsx +219 -40
- package/dist/assets/shared-assets/react-admin/custom/media-field-empty-state.tsx +99 -0
- package/dist/assets/shared-assets/react-admin/custom/media-field.tsx +181 -0
- package/dist/assets/shared-assets/react-admin/custom/sortable-gallery-item.tsx +38 -38
- package/dist/assets/shared-assets/react-admin/dependencies.ts +1 -0
- package/dist/assets/shared-assets/react-admin/schema.json +64 -2
- package/dist/assets/shared-assets/react-admin/ui/chart.tsx +1 -1
- package/dist/assets/shared-assets/react-admin/ui/pagination.tsx +1 -1
- package/dist/cli.js +562 -151
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/dist/assets/shared-assets/react-admin/custom/gallery-thumbnail.tsx +0 -39
- package/dist/assets/shared-assets/react-admin/custom/media-gallery-field.tsx +0 -182
package/dist/cli.js
CHANGED
|
@@ -125,7 +125,7 @@ function createUninstallCommand(runtime) {
|
|
|
125
125
|
return new Command("uninstall").description("Remove all Admin files and undo modifications made by betterstart init").option("-f, --force", "Skip all confirmation prompts", false).option("--cwd <path>", "Project root path").action((options) => runtime.runUninstall(options));
|
|
126
126
|
}
|
|
127
127
|
function createUpdateCommand(runtime) {
|
|
128
|
-
return new Command("update").alias("update-component").description("Update Admin components from the latest CLI templates").argument("[components...]", "Component names to update (e.g., media-
|
|
128
|
+
return new Command("update").alias("update-component").description("Update Admin components from the latest CLI templates").argument("[components...]", "Component names to update (e.g., media-field button)").option("--list", "List all available components").option("--all", "Update all components").option("--shadcn-preset <preset>", "Apply a shadcn preset to Admin UI components").option("--only <parts>", "Apply only supported shadcn preset parts: theme").option("--cwd <path>", "Project root path").action(
|
|
129
129
|
(components, options) => runtime.runUpdate(components, options)
|
|
130
130
|
);
|
|
131
131
|
}
|
|
@@ -867,9 +867,27 @@ var blogPlugin = {
|
|
|
867
867
|
configure: "none"
|
|
868
868
|
};
|
|
869
869
|
|
|
870
|
+
// adapters/next/plugins/portfolio/plugin.ts
|
|
871
|
+
var portfolioPlugin = {
|
|
872
|
+
id: "portfolio",
|
|
873
|
+
title: "Portfolio",
|
|
874
|
+
description: "Portfolio content module",
|
|
875
|
+
kind: "content",
|
|
876
|
+
version: "1.0.0",
|
|
877
|
+
dependencies: [],
|
|
878
|
+
conflicts: [],
|
|
879
|
+
schemaFiles: ["portfolio.json"],
|
|
880
|
+
files: [],
|
|
881
|
+
packageDependencies: [],
|
|
882
|
+
devDependencies: [],
|
|
883
|
+
envSections: [],
|
|
884
|
+
configure: "none"
|
|
885
|
+
};
|
|
886
|
+
|
|
870
887
|
// adapters/next/plugin-registry.ts
|
|
871
888
|
var pluginRegistry = {
|
|
872
|
-
blog: blogPlugin
|
|
889
|
+
blog: blogPlugin,
|
|
890
|
+
portfolio: portfolioPlugin
|
|
873
891
|
};
|
|
874
892
|
function listPluginDefinitions() {
|
|
875
893
|
return Object.values(pluginRegistry);
|
|
@@ -895,6 +913,7 @@ var REACT_ADMIN_DEPS = [
|
|
|
895
913
|
"@codemirror/lang-javascript",
|
|
896
914
|
"@codemirror/lang-json",
|
|
897
915
|
"@codemirror/lang-markdown",
|
|
916
|
+
"@codemirror/state",
|
|
898
917
|
"@codemirror/view",
|
|
899
918
|
"@dnd-kit/core",
|
|
900
919
|
"@dnd-kit/sortable",
|
|
@@ -965,6 +984,18 @@ var REACT_ADMIN_DEV_DEPS = ["@tailwindcss/typography"];
|
|
|
965
984
|
// adapters/next/init/scaffolders/dependencies.ts
|
|
966
985
|
var CLI_PACKAGE_NAME = "betterstart-cli";
|
|
967
986
|
var LOCAL_PACKAGE_PREFIXES = ["workspace:", "link:", "file:", "portal:"];
|
|
987
|
+
var TIPTAP_PACKAGE_PREFIX = "@tiptap/";
|
|
988
|
+
var TIPTAP_PACKAGE_VERSION_SPEC = "^3.26.0";
|
|
989
|
+
var CODEMIRROR_PACKAGE_SPECS = {
|
|
990
|
+
"@codemirror/commands": "^6.10.3",
|
|
991
|
+
"@codemirror/lang-javascript": "^6.2.5",
|
|
992
|
+
"@codemirror/lang-json": "^6.0.2",
|
|
993
|
+
"@codemirror/lang-markdown": "^6.5.0",
|
|
994
|
+
"@codemirror/state": "^6.6.0",
|
|
995
|
+
"@codemirror/view": "^6.43.0",
|
|
996
|
+
"@uiw/codemirror-theme-github": "^4.25.10",
|
|
997
|
+
"@uiw/react-codemirror": "^4.25.10"
|
|
998
|
+
};
|
|
968
999
|
var requireFromCli = createRequire(import.meta.url);
|
|
969
1000
|
var PNPM_PACKAGE_EXTENSION_DEFINITIONS = [
|
|
970
1001
|
{
|
|
@@ -1059,6 +1090,70 @@ function hasDeclaredPackage(pkg, name) {
|
|
|
1059
1090
|
if (!pkg) return false;
|
|
1060
1091
|
return Object.hasOwn(pkg.dependencies ?? {}, name) || Object.hasOwn(pkg.devDependencies ?? {}, name) || Object.hasOwn(pkg.optionalDependencies ?? {}, name);
|
|
1061
1092
|
}
|
|
1093
|
+
function getDeclaredRuntimePackageNames(pkg) {
|
|
1094
|
+
if (!pkg) return [];
|
|
1095
|
+
return Object.keys({
|
|
1096
|
+
...pkg.dependencies ?? {},
|
|
1097
|
+
...pkg.optionalDependencies ?? {}
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
function getDeclaredDevPackageNames(pkg) {
|
|
1101
|
+
if (!pkg) return [];
|
|
1102
|
+
return Object.keys(pkg.devDependencies ?? {}).filter(
|
|
1103
|
+
(name) => !Object.hasOwn(pkg.dependencies ?? {}, name)
|
|
1104
|
+
);
|
|
1105
|
+
}
|
|
1106
|
+
function isTiptapPackageName(name) {
|
|
1107
|
+
return name.startsWith(TIPTAP_PACKAGE_PREFIX);
|
|
1108
|
+
}
|
|
1109
|
+
function getAlignedPackageSpec(name) {
|
|
1110
|
+
if (isTiptapPackageName(name)) {
|
|
1111
|
+
return `${name}@${TIPTAP_PACKAGE_VERSION_SPEC}`;
|
|
1112
|
+
}
|
|
1113
|
+
const versionSpec = CODEMIRROR_PACKAGE_SPECS[name];
|
|
1114
|
+
return versionSpec ? `${name}@${versionSpec}` : null;
|
|
1115
|
+
}
|
|
1116
|
+
function hasAlignedPackageSpec(spec) {
|
|
1117
|
+
return getAlignedPackageSpec(packageNameFromSpec(spec)) !== null;
|
|
1118
|
+
}
|
|
1119
|
+
function alignDependencySpecs(dependencies) {
|
|
1120
|
+
return dependencies.map((dependency) => {
|
|
1121
|
+
const name = packageNameFromSpec(dependency);
|
|
1122
|
+
return getAlignedPackageSpec(name) ?? dependency;
|
|
1123
|
+
});
|
|
1124
|
+
}
|
|
1125
|
+
function getAlignedDeclaredDependencies(packageNames) {
|
|
1126
|
+
return packageNames.flatMap((name) => {
|
|
1127
|
+
const alignedSpec = getAlignedPackageSpec(name);
|
|
1128
|
+
return alignedSpec ? [alignedSpec] : [];
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
function resolveDependencyInstallPlan(options) {
|
|
1132
|
+
const pkg = readProjectPackageJson(options.cwd);
|
|
1133
|
+
const dependencies = alignDependencySpecs(options.dependencies);
|
|
1134
|
+
const devDependencies = alignDependencySpecs(options.devDependencies);
|
|
1135
|
+
const needsPackageAlignment = [...options.dependencies, ...options.devDependencies].some(
|
|
1136
|
+
hasAlignedPackageSpec
|
|
1137
|
+
);
|
|
1138
|
+
if (!needsPackageAlignment) {
|
|
1139
|
+
return {
|
|
1140
|
+
...options,
|
|
1141
|
+
dependencies: unique(dependencies),
|
|
1142
|
+
devDependencies: unique(devDependencies)
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
1145
|
+
return {
|
|
1146
|
+
...options,
|
|
1147
|
+
dependencies: unique([
|
|
1148
|
+
...dependencies,
|
|
1149
|
+
...getAlignedDeclaredDependencies(getDeclaredRuntimePackageNames(pkg))
|
|
1150
|
+
]),
|
|
1151
|
+
devDependencies: unique([
|
|
1152
|
+
...devDependencies,
|
|
1153
|
+
...getAlignedDeclaredDependencies(getDeclaredDevPackageNames(pkg))
|
|
1154
|
+
])
|
|
1155
|
+
};
|
|
1156
|
+
}
|
|
1062
1157
|
function canResolveFromProject(cwd, name) {
|
|
1063
1158
|
try {
|
|
1064
1159
|
requireFromCli.resolve(name, { paths: [cwd] });
|
|
@@ -1198,21 +1293,27 @@ async function installDependenciesAsync({
|
|
|
1198
1293
|
dependencies,
|
|
1199
1294
|
devDependencies
|
|
1200
1295
|
}) {
|
|
1296
|
+
const installPlan = resolveDependencyInstallPlan({ cwd, pm, dependencies, devDependencies });
|
|
1201
1297
|
try {
|
|
1202
1298
|
if (pm === "pnpm") {
|
|
1203
|
-
ensurePnpmPackageExtensions(cwd, dependencies);
|
|
1299
|
+
ensurePnpmPackageExtensions(cwd, installPlan.dependencies);
|
|
1204
1300
|
}
|
|
1205
|
-
if (dependencies.length > 0) {
|
|
1206
|
-
await spawnAsync(pm, buildAddArgs(pm, dependencies, false), cwd);
|
|
1301
|
+
if (installPlan.dependencies.length > 0) {
|
|
1302
|
+
await spawnAsync(pm, buildAddArgs(pm, installPlan.dependencies, false), cwd);
|
|
1207
1303
|
}
|
|
1208
|
-
if (devDependencies.length > 0) {
|
|
1209
|
-
await spawnAsync(pm, buildAddArgs(pm, devDependencies, true), cwd);
|
|
1304
|
+
if (installPlan.devDependencies.length > 0) {
|
|
1305
|
+
await spawnAsync(pm, buildAddArgs(pm, installPlan.devDependencies, true), cwd);
|
|
1210
1306
|
}
|
|
1211
|
-
return {
|
|
1307
|
+
return {
|
|
1308
|
+
dependencies: installPlan.dependencies,
|
|
1309
|
+
devDeps: installPlan.devDependencies,
|
|
1310
|
+
success: true,
|
|
1311
|
+
error: null
|
|
1312
|
+
};
|
|
1212
1313
|
} catch (err) {
|
|
1213
1314
|
return {
|
|
1214
|
-
dependencies,
|
|
1215
|
-
devDeps: devDependencies,
|
|
1315
|
+
dependencies: installPlan.dependencies,
|
|
1316
|
+
devDeps: installPlan.devDependencies,
|
|
1216
1317
|
success: false,
|
|
1217
1318
|
error: err instanceof Error ? err.message : "Install failed"
|
|
1218
1319
|
};
|
|
@@ -1641,6 +1742,7 @@ import path11 from "path";
|
|
|
1641
1742
|
// core-engine/schema/constants.ts
|
|
1642
1743
|
var FIELD_TYPES = {
|
|
1643
1744
|
STRING: "string",
|
|
1745
|
+
EMAIL: "email",
|
|
1644
1746
|
NUMBER: "number",
|
|
1645
1747
|
BOOLEAN: "boolean",
|
|
1646
1748
|
TEXT: "text",
|
|
@@ -1713,6 +1815,18 @@ var FORM_FIELD_TYPES = {
|
|
|
1713
1815
|
LIST: "list",
|
|
1714
1816
|
DYNAMIC_FIELDS: "dynamicFields"
|
|
1715
1817
|
};
|
|
1818
|
+
var AUTO_FIELDS = {
|
|
1819
|
+
ID: "id",
|
|
1820
|
+
CREATED_AT: "createdAt",
|
|
1821
|
+
UPDATED_AT: "updatedAt",
|
|
1822
|
+
PUBLISHED: "published",
|
|
1823
|
+
SLUG: "slug"
|
|
1824
|
+
};
|
|
1825
|
+
var FORM_SUBMISSION_FIELDS = {
|
|
1826
|
+
SUBMITTED_AT: "submittedAt",
|
|
1827
|
+
IP_ADDRESS: "ipAddress",
|
|
1828
|
+
USER_AGENT: "userAgent"
|
|
1829
|
+
};
|
|
1716
1830
|
function isFieldType(value) {
|
|
1717
1831
|
return Object.values(FIELD_TYPES).includes(value);
|
|
1718
1832
|
}
|
|
@@ -1873,6 +1987,11 @@ function loadSchema(schemasDir, name) {
|
|
|
1873
1987
|
const parsed = parseJson(content, filePath);
|
|
1874
1988
|
const obj = parsed;
|
|
1875
1989
|
const type = obj.type;
|
|
1990
|
+
if (type === void 0) {
|
|
1991
|
+
throw new Error(
|
|
1992
|
+
`Schema "${name}" is missing required top-level "type". Expected "entity", "single", or "form".`
|
|
1993
|
+
);
|
|
1994
|
+
}
|
|
1876
1995
|
if (type === "form") {
|
|
1877
1996
|
return { type: "form", schema: parsed, filePath };
|
|
1878
1997
|
}
|
|
@@ -1913,6 +2032,14 @@ var ENTITY_SLUG_FIELD_NAME = "slug";
|
|
|
1913
2032
|
var ENTITY_SLUG_FIELD_TYPES = /* @__PURE__ */ new Set(["string", "varchar", "text"]);
|
|
1914
2033
|
var GENERATED_SCHEMA_TITLE_FIELD_TYPES = /* @__PURE__ */ new Set(["string", "varchar", "text"]);
|
|
1915
2034
|
var FORM_SCHEMA_TITLE_FIELD_TYPES = /* @__PURE__ */ new Set(["text"]);
|
|
2035
|
+
var FORM_SUBMISSION_COLUMN_NAMES = [
|
|
2036
|
+
AUTO_FIELDS.ID,
|
|
2037
|
+
FORM_SUBMISSION_FIELDS.SUBMITTED_AT,
|
|
2038
|
+
FORM_SUBMISSION_FIELDS.IP_ADDRESS,
|
|
2039
|
+
FORM_SUBMISSION_FIELDS.USER_AGENT,
|
|
2040
|
+
AUTO_FIELDS.CREATED_AT,
|
|
2041
|
+
AUTO_FIELDS.UPDATED_AT
|
|
2042
|
+
];
|
|
1916
2043
|
function isEntitySlugField(field) {
|
|
1917
2044
|
return field.name === ENTITY_SLUG_FIELD_NAME;
|
|
1918
2045
|
}
|
|
@@ -1956,52 +2083,84 @@ function createGeneratedSlugField(fields) {
|
|
|
1956
2083
|
...sourceField ? { defaultValueFrom: sourceField } : {}
|
|
1957
2084
|
};
|
|
1958
2085
|
}
|
|
1959
|
-
function
|
|
1960
|
-
|
|
1961
|
-
|
|
2086
|
+
function normalizeEntitySlugField(field, sourceField) {
|
|
2087
|
+
return {
|
|
2088
|
+
...field,
|
|
2089
|
+
required: true,
|
|
2090
|
+
...sourceField && !field.hidden ? { defaultValueFrom: sourceField } : {}
|
|
2091
|
+
};
|
|
2092
|
+
}
|
|
2093
|
+
function createGeneratedSlugFieldForTitle(fields, titleField) {
|
|
2094
|
+
const slugField = createGeneratedSlugField(fields);
|
|
2095
|
+
return titleField.slot ? { ...slugField, slot: titleField.slot } : slugField;
|
|
2096
|
+
}
|
|
2097
|
+
function orderEntityIdentityFields(fields) {
|
|
2098
|
+
const titleIndex = fields.findIndex(isGeneratedSchemaTitleField);
|
|
2099
|
+
const slugIndex = fields.findIndex(isEntitySlugField);
|
|
2100
|
+
if (titleIndex < 0 || slugIndex < 0 || titleIndex === 0 && slugIndex === 1) return fields;
|
|
2101
|
+
const titleField = fields[titleIndex];
|
|
2102
|
+
const slugField = fields[slugIndex];
|
|
2103
|
+
return [
|
|
2104
|
+
titleField,
|
|
2105
|
+
slugField,
|
|
2106
|
+
...fields.filter((_, index) => index !== titleIndex && index !== slugIndex)
|
|
2107
|
+
];
|
|
2108
|
+
}
|
|
2109
|
+
function normalizeEntityIdentityFields(fields, rootFields, state) {
|
|
2110
|
+
const normalizedFields = [];
|
|
2111
|
+
for (const field of fields) {
|
|
1962
2112
|
if (isEntitySlugField(field)) {
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
...sourceField && !field.hidden ? { defaultValueFrom: sourceField } : {}
|
|
1968
|
-
};
|
|
2113
|
+
if (state.keptAuthoredSlugField) continue;
|
|
2114
|
+
state.keptAuthoredSlugField = true;
|
|
2115
|
+
normalizedFields.push(normalizeEntitySlugField(field, state.sourceField));
|
|
2116
|
+
continue;
|
|
1969
2117
|
}
|
|
1970
2118
|
if ((field.type === "group" || field.type === "section") && field.fields) {
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
2119
|
+
normalizedFields.push({
|
|
2120
|
+
...field,
|
|
2121
|
+
fields: normalizeEntityIdentityFields(field.fields, rootFields, state)
|
|
2122
|
+
});
|
|
2123
|
+
continue;
|
|
1974
2124
|
}
|
|
1975
2125
|
if (field.type === "tabs" && field.tabs) {
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
2126
|
+
normalizedFields.push({
|
|
2127
|
+
...field,
|
|
2128
|
+
tabs: field.tabs.map((tab) => ({
|
|
2129
|
+
...tab,
|
|
2130
|
+
fields: normalizeEntityIdentityFields(tab.fields ?? [], rootFields, state)
|
|
2131
|
+
}))
|
|
1980
2132
|
});
|
|
1981
|
-
|
|
2133
|
+
continue;
|
|
1982
2134
|
}
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
2135
|
+
normalizedFields.push(field);
|
|
2136
|
+
if (!state.hasAuthoredSlugField && !state.generatedSlugInserted && isGeneratedSchemaTitleField(field)) {
|
|
2137
|
+
normalizedFields.push(createGeneratedSlugFieldForTitle(rootFields, field));
|
|
2138
|
+
state.generatedSlugInserted = true;
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
return orderEntityIdentityFields(normalizedFields);
|
|
1986
2142
|
}
|
|
1987
2143
|
function ensureEntitySlugField(schema) {
|
|
1988
2144
|
const sourceField = hasPersistedTitleField(schema.fields) ? "title" : void 0;
|
|
1989
|
-
const
|
|
1990
|
-
|
|
2145
|
+
const hasAuthoredSlugField = flattenFields(schema.fields).some(isEntitySlugField);
|
|
2146
|
+
const state = {
|
|
2147
|
+
sourceField,
|
|
2148
|
+
hasAuthoredSlugField,
|
|
2149
|
+
keptAuthoredSlugField: false,
|
|
2150
|
+
generatedSlugInserted: false
|
|
2151
|
+
};
|
|
2152
|
+
const normalizedFields = normalizeEntityIdentityFields(schema.fields, schema.fields, state);
|
|
2153
|
+
if (hasAuthoredSlugField || state.generatedSlugInserted) {
|
|
1991
2154
|
return {
|
|
1992
2155
|
...schema,
|
|
1993
|
-
fields:
|
|
2156
|
+
fields: normalizedFields
|
|
1994
2157
|
};
|
|
1995
2158
|
}
|
|
1996
|
-
const
|
|
1997
|
-
const titleIndex = schema.fields.findIndex(
|
|
1998
|
-
(field) => field.name === GENERATED_SCHEMA_TITLE_FIELD_NAME && isStringLikeSlugField(field)
|
|
1999
|
-
);
|
|
2000
|
-
const insertIndex = titleIndex >= 0 ? titleIndex + 1 : Math.min(schema.fields.length, 1);
|
|
2159
|
+
const insertIndex = Math.min(normalizedFields.length, 1);
|
|
2001
2160
|
const fields = [
|
|
2002
|
-
...
|
|
2003
|
-
|
|
2004
|
-
...
|
|
2161
|
+
...normalizedFields.slice(0, insertIndex),
|
|
2162
|
+
createGeneratedSlugField(schema.fields),
|
|
2163
|
+
...normalizedFields.slice(insertIndex)
|
|
2005
2164
|
];
|
|
2006
2165
|
return {
|
|
2007
2166
|
...schema,
|
|
@@ -2115,6 +2274,23 @@ function collectInvalidFormColumnTypeErrors(schema, errors) {
|
|
|
2115
2274
|
}
|
|
2116
2275
|
}
|
|
2117
2276
|
}
|
|
2277
|
+
function collectInvalidFormColumnReferenceErrors(schema, errors) {
|
|
2278
|
+
const fieldNames = new Set(
|
|
2279
|
+
getAllFormSchemaFields(schema).map((field) => field.name).filter((name) => typeof name === "string" && name.length > 0)
|
|
2280
|
+
);
|
|
2281
|
+
for (const columnName of FORM_SUBMISSION_COLUMN_NAMES) {
|
|
2282
|
+
fieldNames.add(columnName);
|
|
2283
|
+
}
|
|
2284
|
+
for (const column of schema.columns || []) {
|
|
2285
|
+
if (!column.accessorKey || !column.header) {
|
|
2286
|
+
errors.push(`Form column is missing required properties: ${JSON.stringify(column)}`);
|
|
2287
|
+
} else if (!fieldNames.has(column.accessorKey)) {
|
|
2288
|
+
errors.push(
|
|
2289
|
+
`Form column accessorKey "${column.accessorKey}" does not match any field or generated submission property. Available: ${Array.from(fieldNames).join(", ")}`
|
|
2290
|
+
);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2118
2294
|
function validateEntitySchema(schema) {
|
|
2119
2295
|
const errors = [];
|
|
2120
2296
|
collectSchemaSlotLayoutErrors(schema, errors);
|
|
@@ -2134,6 +2310,9 @@ function validateEntitySchema(schema) {
|
|
|
2134
2310
|
if (!Array.isArray(normalizedSchema.fields) || normalizedSchema.fields.length === 0) {
|
|
2135
2311
|
errors.push('Schema must have at least one field via "fields" or "slot"');
|
|
2136
2312
|
}
|
|
2313
|
+
if (normalizedSchema.type !== "entity") {
|
|
2314
|
+
errors.push('Entity schema must have `type: "entity"`');
|
|
2315
|
+
}
|
|
2137
2316
|
if (!Array.isArray(normalizedSchema.columns) || normalizedSchema.columns.length === 0) {
|
|
2138
2317
|
errors.push("Schema must have at least one column");
|
|
2139
2318
|
}
|
|
@@ -2196,6 +2375,9 @@ function validateSingleSchema(schema) {
|
|
|
2196
2375
|
if (!Array.isArray(normalizedSchema.fields) || normalizedSchema.fields.length === 0) {
|
|
2197
2376
|
errors.push('Schema must have at least one field via "fields" or "slot"');
|
|
2198
2377
|
}
|
|
2378
|
+
if (normalizedSchema.type !== "single") {
|
|
2379
|
+
errors.push('Single schema must have `type: "single"`');
|
|
2380
|
+
}
|
|
2199
2381
|
for (const field of normalizedSchema.fields || []) {
|
|
2200
2382
|
if (field.type === "separator") continue;
|
|
2201
2383
|
if (!field.name || !field.type) {
|
|
@@ -2255,6 +2437,7 @@ function validateFormSchema(schema) {
|
|
|
2255
2437
|
}
|
|
2256
2438
|
collectMissingFormSchemaTitleErrors(schema, errors);
|
|
2257
2439
|
collectInvalidFormColumnTypeErrors(schema, errors);
|
|
2440
|
+
collectInvalidFormColumnReferenceErrors(schema, errors);
|
|
2258
2441
|
return errors;
|
|
2259
2442
|
}
|
|
2260
2443
|
function validateLoadedSchema(loaded) {
|
|
@@ -3632,6 +3815,7 @@ function toDrizzleType(field, requiredImports) {
|
|
|
3632
3815
|
requiredImports.add("uuid");
|
|
3633
3816
|
return "uuid().defaultRandom()";
|
|
3634
3817
|
case "string":
|
|
3818
|
+
case "email":
|
|
3635
3819
|
case "varchar":
|
|
3636
3820
|
requiredImports.add("varchar");
|
|
3637
3821
|
return field.length ? `varchar({ length: ${field.length} })` : `varchar({ length: ${DRIZZLE_DEFAULTS.VARCHAR_LENGTH} })`;
|
|
@@ -3788,6 +3972,7 @@ function toTypeScriptType(field, mode = "output") {
|
|
|
3788
3972
|
case "boolean":
|
|
3789
3973
|
return "boolean";
|
|
3790
3974
|
case "string":
|
|
3975
|
+
case "email":
|
|
3791
3976
|
case "varchar":
|
|
3792
3977
|
case "text":
|
|
3793
3978
|
case "markdown":
|
|
@@ -3962,7 +4147,7 @@ function generateFieldMapping(field, source = "input") {
|
|
|
3962
4147
|
if ((field.type === "date" || field.type === "timestamp" || field.type === "time") && !field.required) {
|
|
3963
4148
|
return `${source}.${field.name} && ${source}.${field.name} !== '' ? ${source}.${field.name} : null`;
|
|
3964
4149
|
}
|
|
3965
|
-
if (!field.required && ["string", "varchar", "text", "select"].includes(field.type)) {
|
|
4150
|
+
if (!field.required && ["string", "email", "varchar", "text", "select"].includes(field.type)) {
|
|
3966
4151
|
return `${source}.${field.name} && ${source}.${field.name} !== '' ? ${source}.${field.name} : null`;
|
|
3967
4152
|
}
|
|
3968
4153
|
return `${source}.${field.name}`;
|
|
@@ -4228,6 +4413,7 @@ ${blocks.join("\n\n")}
|
|
|
4228
4413
|
// adapters/next/generators/entity-filters.ts
|
|
4229
4414
|
var FILTERABLE_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
4230
4415
|
"string",
|
|
4416
|
+
"email",
|
|
4231
4417
|
"varchar",
|
|
4232
4418
|
"text",
|
|
4233
4419
|
"boolean",
|
|
@@ -4469,6 +4655,8 @@ function sampleSchemaFieldValue(field) {
|
|
|
4469
4655
|
switch (field.type) {
|
|
4470
4656
|
case "boolean":
|
|
4471
4657
|
return true;
|
|
4658
|
+
case "email":
|
|
4659
|
+
return "developer@example.com";
|
|
4472
4660
|
case "number":
|
|
4473
4661
|
case "decimal":
|
|
4474
4662
|
return 1;
|
|
@@ -5240,21 +5428,19 @@ function buildFormDevModeSnippets(schema, actionImportPath, adminImportAlias) {
|
|
|
5240
5428
|
const fields = getAllFormSchemaFields(schema);
|
|
5241
5429
|
const submissionInput = createFormInputObject(fields);
|
|
5242
5430
|
const actionModulePath = resolveActionModulePath(actionImportPath, adminImportAlias);
|
|
5431
|
+
const formComponentModulePath = `${adminImportAlias ?? DEFAULT_ADMIN_IMPORT_ALIAS}/components/forms/${toKebabCase(schema.name)}-form`;
|
|
5243
5432
|
const submissionData = createFormSubmissionObject(fields);
|
|
5244
5433
|
const submitResponse = { success: true, submission: submissionData };
|
|
5245
5434
|
const submissionsResponse = { submissions: [submissionData], total: 1 };
|
|
5246
5435
|
const deleteResponse = { success: true };
|
|
5247
5436
|
const formLabel2 = schema.label;
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
5254
|
-
|
|
5255
|
-
payloadExample: createPayloadExample(submissionInput),
|
|
5256
|
-
responseExample: createResponseExample(submitResponse),
|
|
5257
|
-
code: `'use client'
|
|
5437
|
+
const formComponentCode = `import { ${pascal}Form } from '${formComponentModulePath}'
|
|
5438
|
+
|
|
5439
|
+
export default function ${pascal}Page() {
|
|
5440
|
+
return <${pascal}Form />
|
|
5441
|
+
}
|
|
5442
|
+
`;
|
|
5443
|
+
const submitActionCode = `'use client'
|
|
5258
5444
|
|
|
5259
5445
|
import * as React from 'react'
|
|
5260
5446
|
import {
|
|
@@ -5281,7 +5467,25 @@ export function ${pascal}SubmitButton() {
|
|
|
5281
5467
|
</button>
|
|
5282
5468
|
)
|
|
5283
5469
|
}
|
|
5284
|
-
|
|
5470
|
+
`;
|
|
5471
|
+
return [
|
|
5472
|
+
{
|
|
5473
|
+
id: "jsx",
|
|
5474
|
+
label: `${formLabel2} Component`,
|
|
5475
|
+
kind: "jsx",
|
|
5476
|
+
icon: "file-code",
|
|
5477
|
+
language: "tsx",
|
|
5478
|
+
code: formComponentCode
|
|
5479
|
+
},
|
|
5480
|
+
{
|
|
5481
|
+
id: "submit",
|
|
5482
|
+
label: `Submit ${formLabel2}`,
|
|
5483
|
+
kind: "mutation",
|
|
5484
|
+
icon: "send",
|
|
5485
|
+
language: "tsx",
|
|
5486
|
+
payloadExample: createPayloadExample(submissionInput),
|
|
5487
|
+
responseExample: createResponseExample(submitResponse),
|
|
5488
|
+
code: submitActionCode
|
|
5285
5489
|
},
|
|
5286
5490
|
{
|
|
5287
5491
|
id: "submissions",
|
|
@@ -5375,7 +5579,7 @@ function generateColumnDef(col) {
|
|
|
5375
5579
|
case "email":
|
|
5376
5580
|
cellDef = `cell: ({ row }) => {
|
|
5377
5581
|
const email = row.getValue('${col.accessorKey}') as string
|
|
5378
|
-
return
|
|
5582
|
+
return <div className="text-sm font-medium">{email || '-'}</div>
|
|
5379
5583
|
}`;
|
|
5380
5584
|
break;
|
|
5381
5585
|
case "date":
|
|
@@ -7184,7 +7388,7 @@ export { CACHE_TAG, CACHE_TAG_BY_ID } from './types'
|
|
|
7184
7388
|
// adapters/next/generators/form-pipeline/form-component-shared.ts
|
|
7185
7389
|
import fs16 from "fs";
|
|
7186
7390
|
import path20 from "path";
|
|
7187
|
-
var ADMIN_CUSTOM_COMPONENTS = /* @__PURE__ */ new Set(["media-
|
|
7391
|
+
var ADMIN_CUSTOM_COMPONENTS = /* @__PURE__ */ new Set(["media-field"]);
|
|
7188
7392
|
function resolveUiImport(cwd, componentName) {
|
|
7189
7393
|
const componentDirectory = ADMIN_CUSTOM_COMPONENTS.has(componentName) ? "components/custom" : "components/ui";
|
|
7190
7394
|
const locations = [componentDirectory, `src/${componentDirectory}`];
|
|
@@ -7265,6 +7469,26 @@ ${decls}
|
|
|
7265
7469
|
function getListFields(fields) {
|
|
7266
7470
|
return fields.filter((f) => f.name && f.type === "list" && f.fields && f.fields.length > 0);
|
|
7267
7471
|
}
|
|
7472
|
+
function hasFormDescriptionFields(fields) {
|
|
7473
|
+
return fields.some((field) => {
|
|
7474
|
+
if (field.hint) return true;
|
|
7475
|
+
if ((field.type === "group" || field.type === "list") && field.fields) {
|
|
7476
|
+
return hasFormDescriptionFields(field.fields);
|
|
7477
|
+
}
|
|
7478
|
+
return false;
|
|
7479
|
+
});
|
|
7480
|
+
}
|
|
7481
|
+
function hasSelectFields(fields) {
|
|
7482
|
+
return fields.some((field) => {
|
|
7483
|
+
if (field.type === "select" && field.options && field.options.length > 0) {
|
|
7484
|
+
return true;
|
|
7485
|
+
}
|
|
7486
|
+
if ((field.type === "group" || field.type === "list") && field.fields) {
|
|
7487
|
+
return hasSelectFields(field.fields);
|
|
7488
|
+
}
|
|
7489
|
+
return false;
|
|
7490
|
+
});
|
|
7491
|
+
}
|
|
7268
7492
|
function renderFieldsJSX(fields) {
|
|
7269
7493
|
return fields.filter((f) => !f.hidden).map((f) => {
|
|
7270
7494
|
if (f.type === "dynamicFields") return "";
|
|
@@ -7516,7 +7740,7 @@ function generateFileUploadFieldJSX(field, name, label, hintJSX, requiredStar) {
|
|
|
7516
7740
|
<FormItem${formItemProps(field.height)}>
|
|
7517
7741
|
${formLabelWithDescriptionLine(name, label, requiredStar, hintJSX, " ")}
|
|
7518
7742
|
<FormControl>
|
|
7519
|
-
<
|
|
7743
|
+
<MediaField
|
|
7520
7744
|
value={field.value}
|
|
7521
7745
|
onChange={field.onChange}
|
|
7522
7746
|
onBlur={field.onBlur}
|
|
@@ -7684,13 +7908,15 @@ ${fieldsJSX}
|
|
|
7684
7908
|
const successHandler = redirectUrl ? `window.location.href = ${JSON.stringify(redirectUrl)}` : "setSubmitted(true)";
|
|
7685
7909
|
const hasRadio = allFields.some((f) => f.type === "radio");
|
|
7686
7910
|
const hasFileUpload = allFields.some((f) => f.type === "file" || f.type === "upload");
|
|
7911
|
+
const hasFormDescriptions = hasFormDescriptionFields(allFields);
|
|
7912
|
+
const hasSelectControls = hasSelectFields(allFields);
|
|
7687
7913
|
const buttonImport = resolveUiImport(cwd, "button");
|
|
7688
7914
|
const formImport = resolveUiImport(cwd, "form");
|
|
7689
7915
|
const inputImport = resolveUiImport(cwd, "input");
|
|
7690
7916
|
const textareaImport = resolveUiImport(cwd, "textarea");
|
|
7691
|
-
const selectImport = resolveUiImport(cwd, "select");
|
|
7917
|
+
const selectImport = hasSelectControls ? resolveUiImport(cwd, "select") : "";
|
|
7692
7918
|
const radioGroupImport = resolveUiImport(cwd, "radio-group");
|
|
7693
|
-
const mediaUploadImport = resolveUiImport(cwd, "media-
|
|
7919
|
+
const mediaUploadImport = resolveUiImport(cwd, "media-field");
|
|
7694
7920
|
const content = buildComponentSource({
|
|
7695
7921
|
pascal,
|
|
7696
7922
|
kebab,
|
|
@@ -7698,6 +7924,8 @@ ${fieldsJSX}
|
|
|
7698
7924
|
hasRadio,
|
|
7699
7925
|
hasFileUpload,
|
|
7700
7926
|
hasListFields,
|
|
7927
|
+
hasFormDescriptions,
|
|
7928
|
+
hasSelectControls,
|
|
7701
7929
|
actionImportPath,
|
|
7702
7930
|
buttonImport,
|
|
7703
7931
|
formImport,
|
|
@@ -7767,17 +7995,16 @@ import { Button } from '${p15.buttonImport}'
|
|
|
7767
7995
|
import {
|
|
7768
7996
|
Form,
|
|
7769
7997
|
FormControl,
|
|
7770
|
-
FormDescription,
|
|
7771
|
-
FormField,
|
|
7998
|
+
${p15.hasFormDescriptions ? " FormDescription,\n" : ""} FormField,
|
|
7772
7999
|
FormItem,
|
|
7773
8000
|
FormLabel,
|
|
7774
8001
|
FormMessage,
|
|
7775
8002
|
} from '${p15.formImport}'
|
|
7776
8003
|
import { Input } from '${p15.inputImport}'${p15.hasFileUpload ? `
|
|
7777
|
-
import {
|
|
8004
|
+
import { MediaField } from '${p15.mediaUploadImport}'` : ""}
|
|
7778
8005
|
${p15.hasRadio ? `import { RadioGroup, RadioGroupItem } from '${p15.radioGroupImport}'
|
|
7779
8006
|
` : ""}
|
|
7780
|
-
import { Textarea } from '${p15.textareaImport}'
|
|
8007
|
+
import { Textarea } from '${p15.textareaImport}'${p15.hasSelectControls ? `
|
|
7781
8008
|
import {
|
|
7782
8009
|
Select,
|
|
7783
8010
|
SelectContent,
|
|
@@ -7785,6 +8012,7 @@ import {
|
|
|
7785
8012
|
SelectTrigger,
|
|
7786
8013
|
SelectValue,
|
|
7787
8014
|
} from '${p15.selectImport}'
|
|
8015
|
+
` : ""}
|
|
7788
8016
|
|
|
7789
8017
|
const formSchema = z.object({
|
|
7790
8018
|
${p15.zodFields}
|
|
@@ -7932,13 +8160,15 @@ ${buildFieldArrayDecls(listFields)}
|
|
|
7932
8160
|
` : "";
|
|
7933
8161
|
const hasRadio = fields.some((f) => f.type === "radio");
|
|
7934
8162
|
const hasFileUpload = fields.some((f) => f.type === "file" || f.type === "upload");
|
|
8163
|
+
const hasFormDescriptions = hasFormDescriptionFields(rawFields);
|
|
8164
|
+
const hasSelectControls = hasSelectFields(rawFields);
|
|
7935
8165
|
const buttonImport = resolveUiImport(cwd, "button");
|
|
7936
8166
|
const formImport = resolveUiImport(cwd, "form");
|
|
7937
8167
|
const inputImport = resolveUiImport(cwd, "input");
|
|
7938
8168
|
const textareaImport = resolveUiImport(cwd, "textarea");
|
|
7939
|
-
const selectImport = resolveUiImport(cwd, "select");
|
|
8169
|
+
const selectImport = hasSelectControls ? resolveUiImport(cwd, "select") : "";
|
|
7940
8170
|
const radioGroupImport = resolveUiImport(cwd, "radio-group");
|
|
7941
|
-
const mediaUploadImport = resolveUiImport(cwd, "media-
|
|
8171
|
+
const mediaUploadImport = resolveUiImport(cwd, "media-field");
|
|
7942
8172
|
const queryClientImport = hasFileUpload ? `
|
|
7943
8173
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'` : "";
|
|
7944
8174
|
const queryClientSetup = hasFileUpload ? `
|
|
@@ -7967,16 +8197,15 @@ import { Button } from '${buttonImport}'
|
|
|
7967
8197
|
import {
|
|
7968
8198
|
Form,
|
|
7969
8199
|
FormControl,
|
|
7970
|
-
FormDescription,
|
|
7971
|
-
FormField,
|
|
8200
|
+
${hasFormDescriptions ? " FormDescription,\n" : ""} FormField,
|
|
7972
8201
|
FormItem,
|
|
7973
8202
|
FormLabel,
|
|
7974
8203
|
FormMessage,
|
|
7975
8204
|
} from '${formImport}'
|
|
7976
8205
|
import { Input } from '${inputImport}'${hasFileUpload ? `
|
|
7977
|
-
import {
|
|
8206
|
+
import { MediaField } from '${mediaUploadImport}'` : ""}${hasRadio ? `
|
|
7978
8207
|
import { RadioGroup, RadioGroupItem } from '${radioGroupImport}'` : ""}
|
|
7979
|
-
import { Textarea } from '${textareaImport}'
|
|
8208
|
+
import { Textarea } from '${textareaImport}'${hasSelectControls ? `
|
|
7980
8209
|
import {
|
|
7981
8210
|
Select,
|
|
7982
8211
|
SelectContent,
|
|
@@ -7984,6 +8213,7 @@ import {
|
|
|
7984
8213
|
SelectTrigger,
|
|
7985
8214
|
SelectValue,
|
|
7986
8215
|
} from '${selectImport}'
|
|
8216
|
+
` : ""}
|
|
7987
8217
|
|
|
7988
8218
|
const formSchema = z.object({
|
|
7989
8219
|
${zodFields}
|
|
@@ -9352,7 +9582,7 @@ ${updateBeforePublishBlock}
|
|
|
9352
9582
|
(typeof value === 'number' && Number.isNaN(value))
|
|
9353
9583
|
? null
|
|
9354
9584
|
: value
|
|
9355
|
-
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
|
|
9585
|
+
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'email', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
|
|
9356
9586
|
processedData[key] = value && value !== '' ? value : null
|
|
9357
9587
|
} else {
|
|
9358
9588
|
processedData[key] = value
|
|
@@ -10593,7 +10823,7 @@ function buildEntityContext(schema, nextMajorVersion) {
|
|
|
10593
10823
|
);
|
|
10594
10824
|
const hasCreatableSelectFields = creatableSelectFields.length > 0;
|
|
10595
10825
|
const selectFields = collectReadSelectFields(allDbFields);
|
|
10596
|
-
const
|
|
10826
|
+
const hasSelectFields2 = selectFields.length > 0;
|
|
10597
10827
|
const dateRangeFilterFields = new Set(
|
|
10598
10828
|
resolvedFilters.filter((filter) => filter.type === "date-range").map((filter) => filter.field)
|
|
10599
10829
|
);
|
|
@@ -10746,7 +10976,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
10746
10976
|
Singular,
|
|
10747
10977
|
hasListRels
|
|
10748
10978
|
);
|
|
10749
|
-
const selectResultMapping =
|
|
10979
|
+
const selectResultMapping = hasSelectFields2 ? wrapWithSelectResolution(baseResultMapping, camelPlural, Singular) : baseResultMapping;
|
|
10750
10980
|
const resultMapping = hasMediaFields ? wrapWithMediaResolution(selectResultMapping, camelPlural, Singular) : selectResultMapping;
|
|
10751
10981
|
const baseListResultMapping = hasHtmlOutput ? buildResultMapping(
|
|
10752
10982
|
listDbFields,
|
|
@@ -10756,7 +10986,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
10756
10986
|
Singular,
|
|
10757
10987
|
hasListRels
|
|
10758
10988
|
) : baseResultMapping;
|
|
10759
|
-
const selectListResultMapping =
|
|
10989
|
+
const selectListResultMapping = hasSelectFields2 ? wrapWithSelectResolution(baseListResultMapping, camelPlural, Singular) : baseListResultMapping;
|
|
10760
10990
|
const listResultMapping = hasMediaFields ? wrapWithMediaResolution(selectListResultMapping, camelPlural, Singular) : selectListResultMapping;
|
|
10761
10991
|
const fieldMeta = createFields.map((f) => `{ name: '${f.name}', type: '${f.type}', required: ${f.required ?? false} }`).join(",\n ");
|
|
10762
10992
|
const createMappings = createFields.map((f) => ` ${f.name}: ${f.name === "slug" ? "resolvedSlug" : generateFieldMapping(f)}`).join(",\n");
|
|
@@ -10787,7 +11017,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
10787
11017
|
return ${singleRowMapping}`;
|
|
10788
11018
|
})();
|
|
10789
11019
|
const singleRowResolvers = [
|
|
10790
|
-
...
|
|
11020
|
+
...hasSelectFields2 ? [`resolve${Singular}SelectFields`] : [],
|
|
10791
11021
|
...hasMediaFields ? [`resolve${Singular}MediaFields`] : []
|
|
10792
11022
|
];
|
|
10793
11023
|
const singleRowReturn = singleRowResolvers.length > 0 ? wrapSingleRowReturnWithResolvers(baseSingleRowReturn, singleRowResolvers) : baseSingleRowReturn;
|
|
@@ -10821,7 +11051,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
10821
11051
|
hasDraft,
|
|
10822
11052
|
hasSearch,
|
|
10823
11053
|
hasFilters,
|
|
10824
|
-
hasSelectFields,
|
|
11054
|
+
hasSelectFields: hasSelectFields2,
|
|
10825
11055
|
hasCreatableSelectFields,
|
|
10826
11056
|
searchFields,
|
|
10827
11057
|
htmlOutputFields,
|
|
@@ -11037,7 +11267,7 @@ function generateSingleActions(schema, actionsDir, options = {}) {
|
|
|
11037
11267
|
);
|
|
11038
11268
|
const hasCreatableSelectFields = creatableSelectFields.length > 0;
|
|
11039
11269
|
const selectFields = collectReadSelectFields(dbFields);
|
|
11040
|
-
const
|
|
11270
|
+
const hasSelectFields2 = selectFields.length > 0;
|
|
11041
11271
|
const relationshipFields = dbFields.filter(
|
|
11042
11272
|
(f) => f.type === "relationship" && f.relationship && !f.multiple
|
|
11043
11273
|
);
|
|
@@ -11150,7 +11380,7 @@ ${allUpsertInterfaceFields}
|
|
|
11150
11380
|
const m2mReadBlock = hasM2M ? `
|
|
11151
11381
|
${m2mReadQueries}
|
|
11152
11382
|
${m2mAssignments}` : "";
|
|
11153
|
-
const singleRowReturn =
|
|
11383
|
+
const singleRowReturn = hasSelectFields2 ? `const row = result[0]
|
|
11154
11384
|
const singleRow = ${singleRowMapping}${m2mReadBlock}
|
|
11155
11385
|
const [resolvedSelectRow] = await resolve${Singular}SelectFields([singleRow])
|
|
11156
11386
|
return resolvedSelectRow` : `const row = result[0]
|
|
@@ -11169,7 +11399,7 @@ ${m2mAssignments}` : "";
|
|
|
11169
11399
|
...hasCreatableSelectFields ? [`${tableVar}SelectOptions`] : []
|
|
11170
11400
|
])
|
|
11171
11401
|
).sort();
|
|
11172
|
-
const selectResolutionFunctions =
|
|
11402
|
+
const selectResolutionFunctions = hasSelectFields2 ? `
|
|
11173
11403
|
${generateSingleResolveSelectFieldsFunction(Singular, selectFields, `${tableVar}SelectOptions`, hasCreatableSelectFields)}
|
|
11174
11404
|
` : "";
|
|
11175
11405
|
const getContent = `'use server'
|
|
@@ -11180,7 +11410,7 @@ import { eq } from 'drizzle-orm'
|
|
|
11180
11410
|
import { alias } from 'drizzle-orm/pg-core'
|
|
11181
11411
|
${cacheReadImport}
|
|
11182
11412
|
import { ${cacheTagsVar} } from './types'
|
|
11183
|
-
import type { ${Singular}${
|
|
11413
|
+
import type { ${Singular}${hasSelectFields2 ? ", SelectOptionValue" : ""} } from './types'
|
|
11184
11414
|
|
|
11185
11415
|
${buildAuthorshipAliasBlock()}
|
|
11186
11416
|
|
|
@@ -11279,7 +11509,7 @@ ${m2mInputDeclarations ? `${m2mInputDeclarations}
|
|
|
11279
11509
|
(typeof value === 'number' && Number.isNaN(value))
|
|
11280
11510
|
? null
|
|
11281
11511
|
: value
|
|
11282
|
-
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
|
|
11512
|
+
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'email', 'varchar', 'text', 'select', 'relationship'].includes(field.type)) {
|
|
11283
11513
|
processedData[key] = value && value !== '' ? value : null
|
|
11284
11514
|
} else {
|
|
11285
11515
|
processedData[key] = value
|
|
@@ -12639,6 +12869,8 @@ function getFormFieldType(field) {
|
|
|
12639
12869
|
switch (field.type) {
|
|
12640
12870
|
case "boolean":
|
|
12641
12871
|
return "checkbox";
|
|
12872
|
+
case "email":
|
|
12873
|
+
return "email";
|
|
12642
12874
|
case "text":
|
|
12643
12875
|
return "textarea";
|
|
12644
12876
|
case "markdown":
|
|
@@ -12670,6 +12902,10 @@ function getZodType(field) {
|
|
|
12670
12902
|
return field.required ? `z.number({ message: '${label} is required' })` : "z.number().nullable().optional()";
|
|
12671
12903
|
case "boolean":
|
|
12672
12904
|
return "z.boolean()";
|
|
12905
|
+
case "email": {
|
|
12906
|
+
const base = field.required ? `z.string().min(1, '${label} is required').email('Please enter a valid email address')` : `z.string().optional().refine((val) => !val || val.trim() === '' || z.string().email().safeParse(val).success, { message: 'Please enter a valid email address' })`;
|
|
12907
|
+
return field.length ? `${base}.max(${field.length})` : base;
|
|
12908
|
+
}
|
|
12673
12909
|
case "string":
|
|
12674
12910
|
case "varchar":
|
|
12675
12911
|
case "text":
|
|
@@ -12808,6 +13044,40 @@ function collectRelationshipFields(fields, _tabFieldNames) {
|
|
|
12808
13044
|
collect(fields);
|
|
12809
13045
|
return result;
|
|
12810
13046
|
}
|
|
13047
|
+
function getTabNestedFields(tab) {
|
|
13048
|
+
return [
|
|
13049
|
+
...tab.fields ?? [],
|
|
13050
|
+
...tab.slot?.main?.fields ?? [],
|
|
13051
|
+
...tab.slot?.sidebar?.fields ?? []
|
|
13052
|
+
];
|
|
13053
|
+
}
|
|
13054
|
+
function collectTabIconNames(fields) {
|
|
13055
|
+
const icons = /* @__PURE__ */ new Set();
|
|
13056
|
+
function collect(fieldsToCheck) {
|
|
13057
|
+
for (const field of fieldsToCheck) {
|
|
13058
|
+
if (field.fields) collect(field.fields);
|
|
13059
|
+
if (field.tabs) {
|
|
13060
|
+
for (const tab of field.tabs) {
|
|
13061
|
+
const icon = tab.icon?.trim();
|
|
13062
|
+
if (icon) icons.add(toPascalCase(icon));
|
|
13063
|
+
collect(getTabNestedFields(tab));
|
|
13064
|
+
}
|
|
13065
|
+
}
|
|
13066
|
+
}
|
|
13067
|
+
}
|
|
13068
|
+
collect(fields);
|
|
13069
|
+
return Array.from(icons);
|
|
13070
|
+
}
|
|
13071
|
+
function renderTabsTrigger(tab, indent, options = {}) {
|
|
13072
|
+
const iconName = tab.icon?.trim() ? toPascalCase(tab.icon.trim()) : null;
|
|
13073
|
+
const className = options.className ? ` className="${options.className}"` : "";
|
|
13074
|
+
if (!iconName)
|
|
13075
|
+
return `${indent}<TabsTrigger value="${tab.name}"${className}>${tab.label}</TabsTrigger>`;
|
|
13076
|
+
return `${indent}<TabsTrigger value="${tab.name}"${className}>
|
|
13077
|
+
${indent} <${iconName} className="size-4 shrink-0" />
|
|
13078
|
+
${indent} <span>${tab.label}</span>
|
|
13079
|
+
${indent}</TabsTrigger>`;
|
|
13080
|
+
}
|
|
12811
13081
|
function selectDefaultValues(f) {
|
|
12812
13082
|
if (f.default === void 0 || f.default === null || f.default === "") return [];
|
|
12813
13083
|
const values = Array.isArray(f.default) ? f.default : [f.default];
|
|
@@ -13082,9 +13352,7 @@ function buildUiImports(ctx) {
|
|
|
13082
13352
|
if (ctx.hasBoolean) uiImports.push("import { Checkbox } from '@admin/components/ui/checkbox'");
|
|
13083
13353
|
if (ctx.hasTextarea) uiImports.push("import { Textarea } from '@admin/components/ui/textarea'");
|
|
13084
13354
|
if (ctx.hasImage || ctx.hasVideo || ctx.hasMedia)
|
|
13085
|
-
uiImports.push(
|
|
13086
|
-
"import { MediaGalleryField } from '@admin/components/custom/media-gallery-field'"
|
|
13087
|
-
);
|
|
13355
|
+
uiImports.push("import { MediaField } from '@admin/components/custom/media-field'");
|
|
13088
13356
|
if (ctx.hasGallery)
|
|
13089
13357
|
uiImports.push("import { GalleryField } from '@admin/components/custom/gallery-field'");
|
|
13090
13358
|
if (ctx.hasIcon || ctx.hasIconPostfix)
|
|
@@ -13299,7 +13567,7 @@ ${indent} render={({ field: formField }) => (
|
|
|
13299
13567
|
${indent} <FormItem${formItemProps2(field)}>
|
|
13300
13568
|
${indent} ${labelWithDescription(formLabel(field, nestedLabel), nestedHint, `${indent} `)}
|
|
13301
13569
|
${indent} <FormControl>
|
|
13302
|
-
${indent} <
|
|
13570
|
+
${indent} <MediaField value={formField.value} onChange={formField.onChange} onBlur={formField.onBlur} disabled={isPending}${acceptProp} />
|
|
13303
13571
|
${indent} </FormControl>
|
|
13304
13572
|
${indent} <FormMessage />
|
|
13305
13573
|
${indent} </FormItem>
|
|
@@ -13594,10 +13862,10 @@ function generateFieldJSXCore(field, indent = " ", context = {}) {
|
|
|
13594
13862
|
if (field.type === "separator") return renderSeparatorField(field, indent);
|
|
13595
13863
|
if (field.type === "boolean") return renderBooleanField(field, indent, label, hintJSX, context);
|
|
13596
13864
|
if (field.type === "image")
|
|
13597
|
-
return
|
|
13865
|
+
return renderMediaField(field, indent, label, hintJSX, context, "image/*");
|
|
13598
13866
|
if (field.type === "video")
|
|
13599
|
-
return
|
|
13600
|
-
if (field.type === "media") return
|
|
13867
|
+
return renderMediaField(field, indent, label, hintJSX, context, "video/*");
|
|
13868
|
+
if (field.type === "media") return renderMediaField(field, indent, label, hintJSX, context);
|
|
13601
13869
|
if (field.type === "gallery") return renderGalleryField(field, indent, label, hintJSX, context);
|
|
13602
13870
|
if (field.type === "icon") return renderIconField(field, indent, label, hintJSX);
|
|
13603
13871
|
if (field.type === "date") return renderDateField(field, indent, label, hintJSX, context);
|
|
@@ -13662,7 +13930,7 @@ ${indent} </FormItem>
|
|
|
13662
13930
|
${indent} )}
|
|
13663
13931
|
${indent}/>`;
|
|
13664
13932
|
}
|
|
13665
|
-
function
|
|
13933
|
+
function renderMediaField(field, indent, label, hintJSX, context, accept) {
|
|
13666
13934
|
const acceptProp = accept ? `
|
|
13667
13935
|
${indent} accept="${accept}"` : "";
|
|
13668
13936
|
const defaultValueFromHandler = defaultValueFromSourceHandler(field, context);
|
|
@@ -13677,7 +13945,7 @@ ${indent} render={({ field: formField }) => (
|
|
|
13677
13945
|
${indent} <FormItem${formItemProps2(field)}>
|
|
13678
13946
|
${indent} ${formLabelWithDescription(field, label, hintJSX, `${indent} `)}
|
|
13679
13947
|
${indent} <FormControl>
|
|
13680
|
-
${indent} <
|
|
13948
|
+
${indent} <MediaField
|
|
13681
13949
|
${indent} value={formField.value}
|
|
13682
13950
|
${indent} onChange={${onChange}}
|
|
13683
13951
|
${indent} onBlur={formField.onBlur}
|
|
@@ -14398,6 +14666,23 @@ ${indent}/>`;
|
|
|
14398
14666
|
}
|
|
14399
14667
|
|
|
14400
14668
|
// adapters/next/generators/form/form-entity.ts
|
|
14669
|
+
function escapeRegExp(value) {
|
|
14670
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
14671
|
+
}
|
|
14672
|
+
function usesIdentifier(source, identifier) {
|
|
14673
|
+
return new RegExp(`\\b${escapeRegExp(identifier)}\\b`).test(source);
|
|
14674
|
+
}
|
|
14675
|
+
function filterNamedImportsByUsage(importDeclarations, source) {
|
|
14676
|
+
return importDeclarations.flatMap((declaration) => {
|
|
14677
|
+
const match = declaration.match(/^import\s+\{\s*([\s\S]*?)\s*\}\s+from\s+(.+)$/);
|
|
14678
|
+
if (!match) return [declaration];
|
|
14679
|
+
const importedNames = match[1].split(",").map((name) => name.trim()).filter(Boolean);
|
|
14680
|
+
const usedNames = importedNames.filter((name) => usesIdentifier(source, name));
|
|
14681
|
+
if (usedNames.length === 0) return [];
|
|
14682
|
+
if (usedNames.length === importedNames.length) return [declaration];
|
|
14683
|
+
return [`import { ${usedNames.join(", ")} } from ${match[2]}`];
|
|
14684
|
+
});
|
|
14685
|
+
}
|
|
14401
14686
|
function collectSidebarSectionHeadingFields(fields) {
|
|
14402
14687
|
const headingFields = [];
|
|
14403
14688
|
for (const field of fields) {
|
|
@@ -14558,10 +14843,25 @@ function ${slugValueHelperName}(value: unknown): string {
|
|
|
14558
14843
|
slugValueHelperName: hasSlugTitleSource ? slugValueHelperName : void 0
|
|
14559
14844
|
});
|
|
14560
14845
|
const cardFieldIndent = " ";
|
|
14846
|
+
function hasRenderableTabsField(fields, excludedField) {
|
|
14847
|
+
for (const field of fields) {
|
|
14848
|
+
if (field.hidden) continue;
|
|
14849
|
+
if (field.type === "tabs" && field !== excludedField) return true;
|
|
14850
|
+
if ((field.type === "group" || field.type === "section") && field.fields) {
|
|
14851
|
+
if (hasRenderableTabsField(field.fields, excludedField)) return true;
|
|
14852
|
+
}
|
|
14853
|
+
if (field.type === "tabs" && field.tabs) {
|
|
14854
|
+
for (const tab of field.tabs) {
|
|
14855
|
+
if (hasRenderableTabsField(tab.fields ?? [], excludedField)) return true;
|
|
14856
|
+
}
|
|
14857
|
+
}
|
|
14858
|
+
}
|
|
14859
|
+
return false;
|
|
14860
|
+
}
|
|
14561
14861
|
const renderNestedTabsField = (f, childContext = {}) => {
|
|
14562
14862
|
const indent = cardFieldIndent;
|
|
14563
14863
|
if (!f.tabs) return "";
|
|
14564
|
-
const tabsList = f.tabs.map((t) => `${indent}
|
|
14864
|
+
const tabsList = f.tabs.map((t) => renderTabsTrigger(t, `${indent} `)).join("\n");
|
|
14565
14865
|
const tabsContent = f.tabs.map((t) => {
|
|
14566
14866
|
const tabFields = (t.fields || []).map(
|
|
14567
14867
|
(tf) => generateFieldJSX2(tf, `${indent} `, {
|
|
@@ -14649,7 +14949,7 @@ ${indent} </CardHeader>`;
|
|
|
14649
14949
|
if (headingJSX) {
|
|
14650
14950
|
return `${indent}<Card className="gap-0">
|
|
14651
14951
|
${headingJSX}
|
|
14652
|
-
${indent} <CardContent className="space-y-
|
|
14952
|
+
${indent} <CardContent className="space-y-5 p-5">
|
|
14653
14953
|
${fieldsJSX}
|
|
14654
14954
|
${indent} </CardContent>
|
|
14655
14955
|
${indent}</Card>`;
|
|
@@ -14701,42 +15001,63 @@ ${cards.join("\n")}
|
|
|
14701
15001
|
).filter(Boolean).join("\n");
|
|
14702
15002
|
return [commonMainFieldsJSX, tabFieldsJSX].filter(Boolean).join("\n");
|
|
14703
15003
|
};
|
|
14704
|
-
const
|
|
15004
|
+
const renderTopLevelTabFormBody = (tab, commonMainFieldsJSX, commonSidebarEntries) => {
|
|
14705
15005
|
const tabMainFieldsJSX = renderTabMainFields(tab, commonMainFieldsJSX);
|
|
14706
15006
|
const tabSidebarEntries = [
|
|
14707
15007
|
...commonSidebarEntries,
|
|
14708
15008
|
...collectSidebarFields(tab.fields || [], tab.name)
|
|
14709
15009
|
].map((entry) => ({ ...entry, jsx: renderStaticSidebarEntry(entry) })).filter((entry) => entry.jsx.length > 0);
|
|
14710
|
-
const sidebarColumn = renderSidebarColumn(tabSidebarEntries
|
|
14711
|
-
return `
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
15010
|
+
const sidebarColumn = renderSidebarColumn(tabSidebarEntries);
|
|
15011
|
+
return ` <div className="grid grid-cols-1 ${tabSidebarEntries.length > 0 ? "lg:grid-cols-[1fr_360px] " : ""}h-full min-h-0 gap-4 items-stretch">
|
|
15012
|
+
<div className="h-full min-h-0 min-w-0">
|
|
15013
|
+
<Card className="min-h-0 min-w-0 h-full">
|
|
15014
|
+
<CardContent className="space-y-5 gap-0 h-full min-h-0">
|
|
14715
15015
|
${tabMainFieldsJSX}
|
|
14716
|
-
|
|
14717
|
-
|
|
15016
|
+
</CardContent>
|
|
15017
|
+
</Card>
|
|
15018
|
+
</div>
|
|
14718
15019
|
${sidebarColumn}
|
|
14719
|
-
|
|
14720
|
-
</TabsContent>`;
|
|
15020
|
+
</div>`;
|
|
14721
15021
|
};
|
|
14722
|
-
const
|
|
14723
|
-
|
|
15022
|
+
const topLevelTabComponentName = (tab) => `${Singular}FormTab${toPascalCase(tab.name)}`;
|
|
15023
|
+
const topLevelTabFileBaseName = (tab) => `${schema.name}-form-tab-${toKebabCase(tab.name)}`;
|
|
15024
|
+
const renderTopLevelTabComponentSpecs = (tabsField2, commonFields) => {
|
|
15025
|
+
if (!tabsField2.tabs) return [];
|
|
14724
15026
|
const commonMainFieldsJSX = commonFields.map(renderMainField).filter(Boolean).join("\n");
|
|
14725
15027
|
const commonSidebarEntries = collectSidebarFields(commonFields);
|
|
14726
|
-
|
|
14727
|
-
|
|
14728
|
-
|
|
14729
|
-
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
${tabsContent}
|
|
14733
|
-
</Tabs>`;
|
|
15028
|
+
return tabsField2.tabs.map((tab) => ({
|
|
15029
|
+
tab,
|
|
15030
|
+
componentName: topLevelTabComponentName(tab),
|
|
15031
|
+
fileBaseName: topLevelTabFileBaseName(tab),
|
|
15032
|
+
body: renderTopLevelTabFormBody(tab, commonMainFieldsJSX, commonSidebarEntries)
|
|
15033
|
+
}));
|
|
14734
15034
|
};
|
|
15035
|
+
const renderTopLevelTabComponentSwitch = (spec) => ` {tab === ${JSON.stringify(spec.tab.name)} ? <${spec.componentName} state={state} /> : null}`;
|
|
14735
15036
|
const visibleFields = allFormFields.filter((f) => !(hasDraft && f.name === "published"));
|
|
14736
15037
|
const topLevelTabsField = visibleFields.find(
|
|
14737
15038
|
(field) => field.type === "tabs" && (field.slot ?? "main") === "main" && field.tabs?.length
|
|
14738
15039
|
);
|
|
14739
15040
|
const topLevelCommonFields = topLevelTabsField ? visibleFields.filter((field) => field !== topLevelTabsField) : visibleFields;
|
|
15041
|
+
const topLevelTabComponents = topLevelTabsField ? renderTopLevelTabComponentSpecs(topLevelTabsField, topLevelCommonFields) : [];
|
|
15042
|
+
const contentUsesTabsField = topLevelTabsField ? hasRenderableTabsField(topLevelCommonFields, topLevelTabsField) || topLevelTabsField.tabs?.some(
|
|
15043
|
+
(tab) => hasRenderableTabsField(tab.fields ?? [], topLevelTabsField)
|
|
15044
|
+
) === true : hasTabsField;
|
|
15045
|
+
const topLevelTabChildFields = topLevelTabsField?.tabs?.flatMap((tab) => [
|
|
15046
|
+
...tab.fields ?? [],
|
|
15047
|
+
...tab.slot?.main?.fields ?? [],
|
|
15048
|
+
...tab.slot?.sidebar?.fields ?? []
|
|
15049
|
+
]) ?? [];
|
|
15050
|
+
const contentTabIconNames = collectTabIconNames(
|
|
15051
|
+
topLevelTabsField ? [...topLevelCommonFields, ...topLevelTabChildFields] : allFormFields
|
|
15052
|
+
);
|
|
15053
|
+
const topLevelTabIconNames = Array.from(
|
|
15054
|
+
new Set(
|
|
15055
|
+
topLevelTabsField?.tabs?.flatMap((tab) => {
|
|
15056
|
+
const icon = tab.icon?.trim();
|
|
15057
|
+
return icon ? [toPascalCase(icon)] : [];
|
|
15058
|
+
}) ?? []
|
|
15059
|
+
)
|
|
15060
|
+
);
|
|
14740
15061
|
const mainFieldsJSX = topLevelCommonFields.map(renderMainField).filter(Boolean).join("\n");
|
|
14741
15062
|
const sidebarEntries = collectSidebarFields(topLevelCommonFields).map((entry) => ({ ...entry, jsx: renderConditionalSidebarEntry(entry) })).filter((entry) => entry.jsx.length > 0);
|
|
14742
15063
|
const hasSidebarFields = sidebarEntries.length > 0;
|
|
@@ -14759,10 +15080,10 @@ ${tabsContent}
|
|
|
14759
15080
|
const sidebarCardJSX = !hasSidebarFields ? "" : shouldGuardSidebarByTab ? ` {hasVisibleSidebarFields ? (
|
|
14760
15081
|
${renderSidebarColumn(sidebarEntries, { conditionalTabs: true })}
|
|
14761
15082
|
) : null}` : renderSidebarColumn(sidebarEntries, { conditionalTabs: true });
|
|
14762
|
-
const formBodyJSX = topLevelTabsField ?
|
|
15083
|
+
const formBodyJSX = topLevelTabsField ? topLevelTabComponents.map(renderTopLevelTabComponentSwitch).join("\n") : `${formGridOpen}
|
|
14763
15084
|
<div className="h-full min-w-0">
|
|
14764
15085
|
<Card className="min-w-0 h-full">
|
|
14765
|
-
<CardContent className="space-y-
|
|
15086
|
+
<CardContent className="space-y-5 gap-0 h-full">
|
|
14766
15087
|
${mainFieldsJSX}
|
|
14767
15088
|
</CardContent>
|
|
14768
15089
|
</Card>
|
|
@@ -14850,7 +15171,7 @@ ${sidebarCardJSX}
|
|
|
14850
15171
|
hasSelectCombobox,
|
|
14851
15172
|
hasCreatableSelect,
|
|
14852
15173
|
hasMultiSelect: multiSelectFields.length > 0,
|
|
14853
|
-
hasTabsField,
|
|
15174
|
+
hasTabsField: contentUsesTabsField,
|
|
14854
15175
|
hasRelationship,
|
|
14855
15176
|
hasSimpleList,
|
|
14856
15177
|
hasNestedList,
|
|
@@ -14868,7 +15189,7 @@ ${sidebarCardJSX}
|
|
|
14868
15189
|
if (!lucideIcons.includes(icon)) lucideIcons.push(icon);
|
|
14869
15190
|
}
|
|
14870
15191
|
};
|
|
14871
|
-
addLucideIcons(...sectionHeadingIconNames);
|
|
15192
|
+
addLucideIcons(...sectionHeadingIconNames, ...contentTabIconNames);
|
|
14872
15193
|
if (hasRelationship || hasSelectCombobox) addLucideIcons("Check", "ChevronsUpDown");
|
|
14873
15194
|
if (hasCreatableSelect) addLucideIcons("Plus");
|
|
14874
15195
|
if (hasNestedList) {
|
|
@@ -15011,12 +15332,12 @@ ${sidebarCardJSX}
|
|
|
15011
15332
|
"handleErrors"
|
|
15012
15333
|
]);
|
|
15013
15334
|
const hookReturnObject = hookReturnNames.map((name) => ` ${name}`).join(",\n");
|
|
15014
|
-
const contentUsesInitialData = hasDraft ||
|
|
15015
|
-
const
|
|
15335
|
+
const contentUsesInitialData = hasDraft || !topLevelTabsField && hasSidebarFields && showMetadataWidget;
|
|
15336
|
+
const standardContentStateNames = uniqueNames([
|
|
15016
15337
|
...contentUsesInitialData ? ["initialData"] : [],
|
|
15017
15338
|
"form",
|
|
15018
15339
|
...contentUsesPendingState ? ["isPending"] : [],
|
|
15019
|
-
...
|
|
15340
|
+
...contentUsesTabsField ? ["activeTab", "setActiveTab"] : [],
|
|
15020
15341
|
...relationshipHookReturnNames,
|
|
15021
15342
|
...creatableSelectReturnNames,
|
|
15022
15343
|
...staticMultiSelectReturnNames,
|
|
@@ -15025,8 +15346,15 @@ ${sidebarCardJSX}
|
|
|
15025
15346
|
"onSubmit",
|
|
15026
15347
|
"handleErrors"
|
|
15027
15348
|
]);
|
|
15349
|
+
const topLevelTabRouterStateNames = uniqueNames([
|
|
15350
|
+
...hasDraft ? ["initialData"] : [],
|
|
15351
|
+
"form",
|
|
15352
|
+
"onSubmit",
|
|
15353
|
+
"handleErrors"
|
|
15354
|
+
]);
|
|
15355
|
+
const contentStateNames = topLevelTabsField ? topLevelTabRouterStateNames : standardContentStateNames;
|
|
15028
15356
|
const contentStateDestructure = contentStateNames.map((name) => ` ${name}`).join(",\n");
|
|
15029
|
-
const needsCn = hasRelationship || hasSelectCombobox || shouldGuardSidebarByTab
|
|
15357
|
+
const needsCn = hasRelationship || hasSelectCombobox || shouldGuardSidebarByTab;
|
|
15030
15358
|
const metadataOffsetConst = showMetadataWidget ? "const SCROLL_BORDER_OFFSET = 8;\n\n" : "";
|
|
15031
15359
|
const tabParserConst = hasTabsField ? `const TAB_VALUES = [${tabNames.map((name) => JSON.stringify(name)).join(", ")}] as const
|
|
15032
15360
|
const TAB_PARSER = parseAsStringLiteral(TAB_VALUES).withDefault(TAB_VALUES[0])
|
|
@@ -15058,6 +15386,32 @@ export type TabValue = (typeof TAB_VALUES)[number]
|
|
|
15058
15386
|
)}
|
|
15059
15387
|
|
|
15060
15388
|
` : "";
|
|
15389
|
+
const topLevelTabsClassName = showMetadataWidget ? `className={cn('min-h-[calc(100svh-56px)] w-full gap-0', {
|
|
15390
|
+
'min-h-[calc(100svh-56px-56px)]': initialData?.id != null
|
|
15391
|
+
})}` : 'className="h-[calc(100svh-56px)] min-h-0 w-full gap-0"';
|
|
15392
|
+
const topLevelTabsShell = topLevelTabsField?.tabs ? ` <Tabs value={entityForm.activeTab} onValueChange={(value) => void entityForm.setActiveTab(value as TabValue)} ${topLevelTabsClassName}>
|
|
15393
|
+
<div className="flex h-14 shrink-0 w-full bg-background border-b border-border/80 px-4 items-center">
|
|
15394
|
+
<TabsList className="bg-transparent border-none">
|
|
15395
|
+
${topLevelTabsField.tabs.map((tab) => renderTabsTrigger(tab, " ", { className: "px-3" })).join("\n")}
|
|
15396
|
+
</TabsList>
|
|
15397
|
+
</div>
|
|
15398
|
+
|
|
15399
|
+
<main className="w-full flex-1 min-h-0 p-4">
|
|
15400
|
+
${topLevelTabsField.tabs.map(
|
|
15401
|
+
(tab) => ` <TabsContent value="${tab.name}" className="h-full min-h-0 w-full flex-1">
|
|
15402
|
+
<Form {...entityForm.form}>
|
|
15403
|
+
<${contentComponentName} state={entityForm} tab="${tab.name}" />
|
|
15404
|
+
</Form>
|
|
15405
|
+
</TabsContent>`
|
|
15406
|
+
).join("\n")}
|
|
15407
|
+
</main>
|
|
15408
|
+
</Tabs>` : "";
|
|
15409
|
+
const defaultShellBody = ` <main className="w-full p-4 h-full">
|
|
15410
|
+
<Form {...entityForm.form}>
|
|
15411
|
+
<${contentComponentName} state={entityForm} />
|
|
15412
|
+
</Form>
|
|
15413
|
+
</main>`;
|
|
15414
|
+
const shellBody = topLevelTabsField ? topLevelTabsShell : defaultShellBody;
|
|
15061
15415
|
const hookContent = `'use client'
|
|
15062
15416
|
|
|
15063
15417
|
import * as React from 'react'
|
|
@@ -15177,27 +15531,63 @@ ${hookReturnObject}
|
|
|
15177
15531
|
}
|
|
15178
15532
|
}
|
|
15179
15533
|
`;
|
|
15534
|
+
const topLevelTabComponentImports = topLevelTabComponents.map((spec) => `import { ${spec.componentName} } from './${spec.fileBaseName}'`).join("\n");
|
|
15535
|
+
const contentImports = topLevelTabsField ? `import type { TabValue, ${hookName} } from '${hookImportPath}'${topLevelTabComponentImports ? `
|
|
15536
|
+
${topLevelTabComponentImports}` : ""}` : `${lucideIcons.length > 0 ? `import { ${lucideIcons.join(", ")} } from 'lucide-react'
|
|
15537
|
+
` : ""}${hasTabsField ? `import type { TabValue, ${hookName} } from '${hookImportPath}'` : `import type { ${hookName} } from '${hookImportPath}'`}${needsCn ? "\nimport { cn } from '@admin/utils/shared/cn'" : ""}
|
|
15538
|
+
${uiImports.join("\n")}`;
|
|
15539
|
+
const contentSidebarVisibilityState = topLevelTabsField ? "" : sidebarVisibilityState;
|
|
15540
|
+
const renderTopLevelTabComponentFile = (spec) => {
|
|
15541
|
+
const body = spec.body;
|
|
15542
|
+
const tabStateNames = hookReturnNames.filter((name) => usesIdentifier(body, name));
|
|
15543
|
+
const tabStateDestructure = tabStateNames.length > 0 ? ` const {
|
|
15544
|
+
${tabStateNames.map((name) => ` ${name}`).join(",\n")}
|
|
15545
|
+
} = state
|
|
15546
|
+
|
|
15547
|
+
` : "";
|
|
15548
|
+
const tabLucideIcons = lucideIcons.filter((icon) => usesIdentifier(body, icon));
|
|
15549
|
+
const tabNeedsCn = /\bcn\s*\(/.test(body);
|
|
15550
|
+
const tabImports = [
|
|
15551
|
+
tabLucideIcons.length > 0 ? `import { ${tabLucideIcons.join(", ")} } from 'lucide-react'` : "",
|
|
15552
|
+
`import type { ${hookName} } from '${hookImportPath}'`,
|
|
15553
|
+
tabNeedsCn ? "import { cn } from '@admin/utils/shared/cn'" : "",
|
|
15554
|
+
...filterNamedImportsByUsage(uiImports, body)
|
|
15555
|
+
].filter(Boolean).join("\n");
|
|
15556
|
+
return createGeneratedFile(
|
|
15557
|
+
`${pagesDir}/${schema.name}/${spec.fileBaseName}.tsx`,
|
|
15558
|
+
`'use client'
|
|
15559
|
+
|
|
15560
|
+
${tabImports}
|
|
15561
|
+
|
|
15562
|
+
interface ${spec.componentName}Props {
|
|
15563
|
+
state: ReturnType<typeof ${hookName}>
|
|
15564
|
+
}
|
|
15565
|
+
|
|
15566
|
+
export function ${spec.componentName}({ state }: ${spec.componentName}Props) {
|
|
15567
|
+
${tabStateDestructure} return (
|
|
15568
|
+
${body}
|
|
15569
|
+
)
|
|
15570
|
+
}
|
|
15571
|
+
`
|
|
15572
|
+
);
|
|
15573
|
+
};
|
|
15574
|
+
const topLevelTabComponentFiles = topLevelTabComponents.map(renderTopLevelTabComponentFile);
|
|
15180
15575
|
const content = `'use client'
|
|
15181
|
-
|
|
15182
|
-
|
|
15183
|
-
import type { TabValue, ${hookName} } from '${hookImportPath}'` : `
|
|
15184
|
-
import type { ${hookName} } from '${hookImportPath}'`}${needsCn ? "\nimport { cn } from '@admin/utils/shared/cn'" : ""}
|
|
15185
|
-
${uiImports.join("\n")}
|
|
15576
|
+
|
|
15577
|
+
${contentImports}
|
|
15186
15578
|
|
|
15187
15579
|
interface ${contentComponentName}Props {
|
|
15188
15580
|
state: ReturnType<typeof ${hookName}>
|
|
15581
|
+
${topLevelTabsField ? " tab: TabValue\n" : ""}
|
|
15189
15582
|
}
|
|
15190
15583
|
|
|
15191
|
-
export function ${contentComponentName}({ state }: ${contentComponentName}Props) {
|
|
15584
|
+
export function ${contentComponentName}({ state${topLevelTabsField ? ", tab" : ""} }: ${contentComponentName}Props) {
|
|
15192
15585
|
const {
|
|
15193
15586
|
${contentStateDestructure}
|
|
15194
15587
|
} = state
|
|
15195
|
-
${
|
|
15588
|
+
${contentSidebarVisibilityState}
|
|
15196
15589
|
return (
|
|
15197
|
-
<form id="${schema.name}-form" onSubmit={form.handleSubmit((values) => onSubmit(values${hasDraft ? ", initialData?.published ?? false" : ""}), handleErrors)} ${topLevelTabsField ?
|
|
15198
|
-
'h-[calc(100svh-56px-56px-32px)]': initialData?.id != null,
|
|
15199
|
-
'h-[calc(100svh-56px-32px)]': initialData?.id == null
|
|
15200
|
-
})}` : 'className="h-[calc(100svh-56px-32px)] min-h-0"' : 'className="space-y-6 h-full"'}>
|
|
15590
|
+
<form id="${schema.name}-form" onSubmit={form.handleSubmit((values) => onSubmit(values${hasDraft ? ", initialData?.published ?? false" : ""}), handleErrors)} ${topLevelTabsField ? 'className="h-full"' : 'className="space-y-6 h-full"'}>
|
|
15201
15591
|
${formBodyJSX}
|
|
15202
15592
|
</form>
|
|
15203
15593
|
)
|
|
@@ -15205,11 +15595,15 @@ ${formBodyJSX}
|
|
|
15205
15595
|
`;
|
|
15206
15596
|
const shellContent = `'use client'
|
|
15207
15597
|
${showMetadataWidget ? "\nimport { usePageScrollThreshold } from '@admin/hooks/use-page-scroll-threshold'" : ""}
|
|
15598
|
+
${topLevelTabIconNames.length > 0 ? `import { ${topLevelTabIconNames.join(", ")} } from 'lucide-react'
|
|
15599
|
+
` : ""}
|
|
15208
15600
|
import { Button } from '@admin/components/ui/button'
|
|
15209
15601
|
import { Form } from '@admin/components/ui/form'
|
|
15210
|
-
import { PageHeader } from '@admin/components/shared/page-header'${showMetadataWidget ? "\nimport { EntityMetadata } from '@admin/components/shared/entity-metadata'\nimport { cn } from '@admin/utils/shared/cn'" : ""}
|
|
15602
|
+
${topLevelTabsField ? "import { Tabs, TabsContent, TabsList, TabsTrigger } from '@admin/components/ui/tabs'\n" : ""}import { PageHeader } from '@admin/components/shared/page-header'${showMetadataWidget ? "\nimport { EntityMetadata } from '@admin/components/shared/entity-metadata'\nimport { cn } from '@admin/utils/shared/cn'" : ""}
|
|
15211
15603
|
import type { ${Singular} } from '@admin/actions/${actionImportPath}'
|
|
15212
15604
|
${showMetadataWidget ? `import { getVersionsBy${Singular}Id, restore${Singular}Version } from '@admin/actions/${actionImportPath}'` : ""}
|
|
15605
|
+
${topLevelTabsField ? `import type { TabValue } from '${hookImportPath}'
|
|
15606
|
+
` : ""}
|
|
15213
15607
|
import { ${hookName} } from '${hookImportPath}'
|
|
15214
15608
|
import { ${contentComponentName} } from './${schema.name}-form-content'
|
|
15215
15609
|
|
|
@@ -15257,11 +15651,7 @@ ${hasDraft ? ` <div className="flex items-center gap-1">
|
|
|
15257
15651
|
</Button>`}
|
|
15258
15652
|
}
|
|
15259
15653
|
/>
|
|
15260
|
-
${metadataStrip}
|
|
15261
|
-
<Form {...entityForm.form}>
|
|
15262
|
-
<${contentComponentName} state={entityForm} />
|
|
15263
|
-
</Form>
|
|
15264
|
-
</main>
|
|
15654
|
+
${metadataStrip}${shellBody}
|
|
15265
15655
|
</>
|
|
15266
15656
|
)
|
|
15267
15657
|
}
|
|
@@ -15270,6 +15660,7 @@ ${metadataStrip} <main className="w-full p-4 h-full">
|
|
|
15270
15660
|
files: [
|
|
15271
15661
|
createGeneratedFile(hookPath, hookContent),
|
|
15272
15662
|
createGeneratedFile(`${pagesDir}/${schema.name}/${schema.name}-form-content.tsx`, content),
|
|
15663
|
+
...topLevelTabComponentFiles,
|
|
15273
15664
|
createGeneratedFile(`${pagesDir}/${schema.name}/${schema.name}-form.tsx`, shellContent)
|
|
15274
15665
|
]
|
|
15275
15666
|
};
|
|
@@ -15398,7 +15789,7 @@ function buildGroupFieldsJSX(group2, analysis, defaultValueFromSourceNames, crea
|
|
|
15398
15789
|
const indent = " ";
|
|
15399
15790
|
return group2.fields.map((f) => {
|
|
15400
15791
|
if (f.type === "tabs" && f.tabs) {
|
|
15401
|
-
const tabsList = f.tabs.map((t) =>
|
|
15792
|
+
const tabsList = f.tabs.map((t) => renderTabsTrigger(t, " ")).join("\n");
|
|
15402
15793
|
const tabsContent = f.tabs.map((t) => {
|
|
15403
15794
|
const tabFields = (t.fields || []).map(
|
|
15404
15795
|
(tf) => generateFieldJSX2(tf, " ", {
|
|
@@ -15621,7 +16012,7 @@ ${creatableSelectHandlers ? `${creatableSelectHandlers}
|
|
|
15621
16012
|
<CardHeader>
|
|
15622
16013
|
<CardTitle>${group2.title}</CardTitle>${descriptionLine}
|
|
15623
16014
|
</CardHeader>
|
|
15624
|
-
<CardContent className="space-y-
|
|
16015
|
+
<CardContent className="space-y-5">
|
|
15625
16016
|
${fieldsJSX}
|
|
15626
16017
|
</CardContent>
|
|
15627
16018
|
<CardFooter>${group2.hint ? `
|
|
@@ -15702,6 +16093,7 @@ function generateSingleForm(schema, pagesDir, options = {}) {
|
|
|
15702
16093
|
(group2) => analyzeGroup(group2.fields).hasNestedObjectList
|
|
15703
16094
|
);
|
|
15704
16095
|
const hasSimpleList = hasList && hasSimpleListField2(allFormFields);
|
|
16096
|
+
const tabIconNames = collectTabIconNames(allFormFields);
|
|
15705
16097
|
const allRelFields = [];
|
|
15706
16098
|
for (const g of cardGroups) {
|
|
15707
16099
|
const a = analyzeGroup(g.fields);
|
|
@@ -15747,12 +16139,17 @@ function generateSingleForm(schema, pagesDir, options = {}) {
|
|
|
15747
16139
|
CardHeader,
|
|
15748
16140
|
CardTitle
|
|
15749
16141
|
} from '@admin/components/ui/card'`);
|
|
15750
|
-
const lucideIcons = [
|
|
15751
|
-
|
|
15752
|
-
|
|
16142
|
+
const lucideIcons = [];
|
|
16143
|
+
const addLucideIcons = (...icons) => {
|
|
16144
|
+
for (const icon of icons) {
|
|
16145
|
+
if (!lucideIcons.includes(icon)) lucideIcons.push(icon);
|
|
16146
|
+
}
|
|
16147
|
+
};
|
|
16148
|
+
addLucideIcons("LoaderCircle", ...tabIconNames);
|
|
16149
|
+
if (hasRelationship || hasSelectCombobox) addLucideIcons("Check", "ChevronsUpDown");
|
|
16150
|
+
if (hasCreatableSelect) addLucideIcons("Plus");
|
|
15753
16151
|
if (hasNestedList) {
|
|
15754
|
-
|
|
15755
|
-
if (!lucideIcons.includes("X")) lucideIcons.push("X");
|
|
16152
|
+
addLucideIcons("Plus", "X");
|
|
15756
16153
|
}
|
|
15757
16154
|
const relHookImports = allRelFields.map((f) => {
|
|
15758
16155
|
const relPlural = toPascalCase(pluralize(f.relationship || ""));
|
|
@@ -18529,6 +18926,7 @@ import fs24 from "fs";
|
|
|
18529
18926
|
import path29 from "path";
|
|
18530
18927
|
var ENTITY_SINGLE_ADD_FIELD_TYPES = [
|
|
18531
18928
|
"string",
|
|
18929
|
+
"email",
|
|
18532
18930
|
"varchar",
|
|
18533
18931
|
"text",
|
|
18534
18932
|
"markdown",
|
|
@@ -18631,6 +19029,11 @@ function loadAuthoredGeneratedSchema(schemasDir, name) {
|
|
|
18631
19029
|
const filePath = filePaths[0];
|
|
18632
19030
|
const parsed = parseJsonObject(fs24.readFileSync(filePath, "utf-8"), filePath);
|
|
18633
19031
|
const type = parsed.type;
|
|
19032
|
+
if (type === void 0) {
|
|
19033
|
+
throw new Error(
|
|
19034
|
+
`Schema "${name}" is missing required top-level "type". Expected "entity", "single", or "form".`
|
|
19035
|
+
);
|
|
19036
|
+
}
|
|
18634
19037
|
if (type === "form") {
|
|
18635
19038
|
return {
|
|
18636
19039
|
kind: "form",
|
|
@@ -20305,7 +20708,7 @@ async function promptEntityIntegrationOptions(schema, field) {
|
|
|
20305
20708
|
summary.push(`table column: ${field.name}`);
|
|
20306
20709
|
}
|
|
20307
20710
|
}
|
|
20308
|
-
if (["string", "varchar", "text", "markdown", "richtext"].includes(field.type)) {
|
|
20711
|
+
if (["string", "email", "varchar", "text", "markdown", "richtext"].includes(field.type)) {
|
|
20309
20712
|
const addSearch = await promptConfirm2("Add this field to search?", false);
|
|
20310
20713
|
if (addSearch) {
|
|
20311
20714
|
schema.search = schema.search ?? { fields: [] };
|
|
@@ -20365,6 +20768,8 @@ function schemaColumnTypeForField(field) {
|
|
|
20365
20768
|
return "boolean";
|
|
20366
20769
|
case "select":
|
|
20367
20770
|
return "badge";
|
|
20771
|
+
case "email":
|
|
20772
|
+
return "email";
|
|
20368
20773
|
case "string":
|
|
20369
20774
|
case "varchar":
|
|
20370
20775
|
case "text":
|
|
@@ -24801,7 +25206,7 @@ var STATIC_CUSTOM_DEPENDENCIES = {
|
|
|
24801
25206
|
"content-editor-rich-extensions",
|
|
24802
25207
|
"content-editor-editor-view-utils",
|
|
24803
25208
|
"markdown-utils",
|
|
24804
|
-
"media-
|
|
25209
|
+
"media-field",
|
|
24805
25210
|
"use-content-editor",
|
|
24806
25211
|
"use-content-editor-table-add-controls",
|
|
24807
25212
|
"use-content-editor-source-mode",
|
|
@@ -24891,7 +25296,7 @@ var STATIC_CUSTOM_DEPENDENCIES = {
|
|
|
24891
25296
|
"content-editor/use-tiptap-editor",
|
|
24892
25297
|
"tiptap-utils"
|
|
24893
25298
|
],
|
|
24894
|
-
"content-editor/media-gallery-block": ["media-
|
|
25299
|
+
"content-editor/media-gallery-block": ["media-field"],
|
|
24895
25300
|
"content-editor/mobile-toolbar-content": [
|
|
24896
25301
|
"content-editor/math-popover",
|
|
24897
25302
|
"content-editor/use-tiptap-editor",
|
|
@@ -24916,20 +25321,25 @@ var STATIC_CUSTOM_DEPENDENCIES = {
|
|
|
24916
25321
|
],
|
|
24917
25322
|
"date-range-picker": ["date-utils"],
|
|
24918
25323
|
"gallery-field": [
|
|
24919
|
-
"
|
|
25324
|
+
"card",
|
|
25325
|
+
"carousel",
|
|
25326
|
+
"media-field",
|
|
25327
|
+
"media-field-empty-state",
|
|
24920
25328
|
"media-gallery-dialog",
|
|
25329
|
+
"skeleton",
|
|
24921
25330
|
"sortable-gallery-item",
|
|
24922
25331
|
"use-media"
|
|
24923
25332
|
],
|
|
24924
25333
|
"icon-picker": ["icons-column-skeleton", "icons-data"],
|
|
24925
|
-
"
|
|
24926
|
-
|
|
25334
|
+
"media-field": [
|
|
25335
|
+
"media-field-empty-state",
|
|
24927
25336
|
"media-gallery-dialog",
|
|
24928
25337
|
"media-preview",
|
|
24929
25338
|
"media-fallback-utils",
|
|
24930
25339
|
"use-copy-to-clipboard",
|
|
24931
25340
|
"use-media"
|
|
24932
25341
|
],
|
|
25342
|
+
"media-field-empty-state": ["button", "card", "cn", "media-gallery-dialog"],
|
|
24933
25343
|
"nested-object-list-field": ["form-list-field-utils"],
|
|
24934
25344
|
"progressive-blur": ["use-page-boundary-blur-visibility"],
|
|
24935
25345
|
"upload-dropzone": ["upload-utils"]
|
|
@@ -24962,7 +25372,7 @@ var TIPTAP_TEMPLATE_DEPENDENCIES = [
|
|
|
24962
25372
|
"content-editor/source-mode",
|
|
24963
25373
|
"tiptap-utils",
|
|
24964
25374
|
"content-editor/media-gallery-block",
|
|
24965
|
-
"media-
|
|
25375
|
+
"media-field"
|
|
24966
25376
|
];
|
|
24967
25377
|
var TIPTAP_CONTENT_EDITOR_DIRECTORIES = ["tiptap-extension", "tiptap-node", "tiptap-ui"];
|
|
24968
25378
|
var CODEMIRROR_PACKAGE_DEPS = [
|
|
@@ -24970,6 +25380,7 @@ var CODEMIRROR_PACKAGE_DEPS = [
|
|
|
24970
25380
|
"@codemirror/lang-javascript",
|
|
24971
25381
|
"@codemirror/lang-json",
|
|
24972
25382
|
"@codemirror/lang-markdown",
|
|
25383
|
+
"@codemirror/state",
|
|
24973
25384
|
"@codemirror/view",
|
|
24974
25385
|
"@uiw/codemirror-theme-github",
|
|
24975
25386
|
"@uiw/react-codemirror"
|
|
@@ -25371,7 +25782,7 @@ var TEMPLATE_REGISTRY = {
|
|
|
25371
25782
|
relPath: "hooks/content-editor/use-content-editor-source-mode.tsx",
|
|
25372
25783
|
content: () => readTemplate("hooks/content-editor/use-content-editor-source-mode.tsx"),
|
|
25373
25784
|
dependencies: [
|
|
25374
|
-
"media-
|
|
25785
|
+
"media-field",
|
|
25375
25786
|
"use-admin-theme",
|
|
25376
25787
|
"markdown-utils",
|
|
25377
25788
|
"content-editor-source-media-utils",
|
|
@@ -25392,7 +25803,7 @@ var TEMPLATE_REGISTRY = {
|
|
|
25392
25803
|
"tiptap",
|
|
25393
25804
|
"tiptap-utils",
|
|
25394
25805
|
"content-editor/media-gallery-block",
|
|
25395
|
-
"media-
|
|
25806
|
+
"media-field"
|
|
25396
25807
|
]
|
|
25397
25808
|
},
|
|
25398
25809
|
"use-content-editor-media-insertion": {
|