@betterstart/cli 0.1.67 → 0.1.69
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 +734 -375
- package/dist/cli.js.map +1 -1
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -1753,7 +1753,18 @@ function generateFormActions(schema, cwd, actionsDir, options) {
|
|
|
1753
1753
|
const formName = schema.name;
|
|
1754
1754
|
const tableName = `${toCamelCase(formName)}Submissions`;
|
|
1755
1755
|
const pascal = toPascalCase(formName);
|
|
1756
|
+
const kebab = toKebabCase(formName);
|
|
1756
1757
|
const fields = getAllFormSchemaFields(schema);
|
|
1758
|
+
const absActionsDir = path6.join(cwd, actionsDir);
|
|
1759
|
+
const dirPath = path6.join(absActionsDir, `${kebab}-form`);
|
|
1760
|
+
const oldFilePath = path6.join(absActionsDir, `${kebab}-form.ts`);
|
|
1761
|
+
if (!options.force && (fs6.existsSync(dirPath) || fs6.existsSync(oldFilePath))) {
|
|
1762
|
+
return { files: [] };
|
|
1763
|
+
}
|
|
1764
|
+
if (options.force) {
|
|
1765
|
+
if (fs6.existsSync(oldFilePath)) fs6.unlinkSync(oldFilePath);
|
|
1766
|
+
if (fs6.existsSync(dirPath)) fs6.rmSync(dirPath, { recursive: true });
|
|
1767
|
+
}
|
|
1757
1768
|
const dataFields = fields.map((f) => {
|
|
1758
1769
|
let tsType = "string";
|
|
1759
1770
|
if (f.type === "number") tsType = "number";
|
|
@@ -1772,25 +1783,10 @@ function generateFormActions(schema, cwd, actionsDir, options) {
|
|
|
1772
1783
|
return { success: false, error: '${f.label} is required' }
|
|
1773
1784
|
}`
|
|
1774
1785
|
).join("\n ");
|
|
1775
|
-
const kebab = toKebabCase(formName);
|
|
1776
|
-
const filePath = path6.join(cwd, actionsDir, `${kebab}-form.ts`);
|
|
1777
|
-
const dir = path6.dirname(filePath);
|
|
1778
|
-
if (!fs6.existsSync(dir)) fs6.mkdirSync(dir, { recursive: true });
|
|
1779
|
-
if (fs6.existsSync(filePath) && !options.force) {
|
|
1780
|
-
return { files: [path6.relative(cwd, filePath)] };
|
|
1781
|
-
}
|
|
1782
1786
|
const mailchimpImport = schema.mailchimp ? `
|
|
1783
1787
|
import { addToMailchimpAudience } from '@cms/utils/mailchimp'` : "";
|
|
1784
1788
|
const mailchimpCall = generateMailchimpCode(schema, fields);
|
|
1785
|
-
const
|
|
1786
|
-
|
|
1787
|
-
import { desc, eq, or } from 'drizzle-orm'
|
|
1788
|
-
import db from '@cms/db'
|
|
1789
|
-
import { ${tableName} } from '@cms/db/schema'
|
|
1790
|
-
import { getFormSettings } from '@cms/actions/form-settings'
|
|
1791
|
-
import { sendWebhook } from '@cms/utils/webhook'${mailchimpImport}
|
|
1792
|
-
|
|
1793
|
-
export interface ${pascal}SubmissionData {
|
|
1789
|
+
const typesContent = `export interface ${pascal}SubmissionData {
|
|
1794
1790
|
id: number
|
|
1795
1791
|
${dataFields}
|
|
1796
1792
|
ipAddress: string | null
|
|
@@ -1822,6 +1818,13 @@ export interface Delete${pascal}SubmissionResult {
|
|
|
1822
1818
|
error?: string
|
|
1823
1819
|
count?: number
|
|
1824
1820
|
}
|
|
1821
|
+
`;
|
|
1822
|
+
const getSubmissionsContent = `'use server'
|
|
1823
|
+
|
|
1824
|
+
import { desc } from 'drizzle-orm'
|
|
1825
|
+
import db from '@cms/db'
|
|
1826
|
+
import { ${tableName} } from '@cms/db/schema'
|
|
1827
|
+
import type { ${pascal}SubmissionsResponse, ${pascal}SubmissionData } from './types'
|
|
1825
1828
|
|
|
1826
1829
|
export async function get${pascal}Submissions(): Promise<${pascal}SubmissionsResponse> {
|
|
1827
1830
|
try {
|
|
@@ -1835,6 +1838,13 @@ export async function get${pascal}Submissions(): Promise<${pascal}SubmissionsRes
|
|
|
1835
1838
|
throw new Error('Failed to fetch ${formName} submissions')
|
|
1836
1839
|
}
|
|
1837
1840
|
}
|
|
1841
|
+
`;
|
|
1842
|
+
const getSubmissionContent = `'use server'
|
|
1843
|
+
|
|
1844
|
+
import { eq } from 'drizzle-orm'
|
|
1845
|
+
import db from '@cms/db'
|
|
1846
|
+
import { ${tableName} } from '@cms/db/schema'
|
|
1847
|
+
import type { ${pascal}SubmissionData } from './types'
|
|
1838
1848
|
|
|
1839
1849
|
export async function get${pascal}Submission(id: number): Promise<${pascal}SubmissionData | null> {
|
|
1840
1850
|
try {
|
|
@@ -1849,6 +1859,14 @@ export async function get${pascal}Submission(id: number): Promise<${pascal}Submi
|
|
|
1849
1859
|
throw new Error('Failed to fetch ${formName} submission')
|
|
1850
1860
|
}
|
|
1851
1861
|
}
|
|
1862
|
+
`;
|
|
1863
|
+
const createContent = `'use server'
|
|
1864
|
+
|
|
1865
|
+
import db from '@cms/db'
|
|
1866
|
+
import { ${tableName} } from '@cms/db/schema'
|
|
1867
|
+
import { getFormSettings } from '@cms/actions/form-settings'
|
|
1868
|
+
import { sendWebhook } from '@cms/utils/webhook'${mailchimpImport}
|
|
1869
|
+
import type { Create${pascal}SubmissionInput, Create${pascal}SubmissionResult, ${pascal}SubmissionData } from './types'
|
|
1852
1870
|
|
|
1853
1871
|
export async function create${pascal}Submission(
|
|
1854
1872
|
data: Create${pascal}SubmissionInput
|
|
@@ -1899,6 +1917,13 @@ export async function create${pascal}Submission(
|
|
|
1899
1917
|
}
|
|
1900
1918
|
}
|
|
1901
1919
|
}
|
|
1920
|
+
`;
|
|
1921
|
+
const deleteContent = `'use server'
|
|
1922
|
+
|
|
1923
|
+
import { eq } from 'drizzle-orm'
|
|
1924
|
+
import db from '@cms/db'
|
|
1925
|
+
import { ${tableName} } from '@cms/db/schema'
|
|
1926
|
+
import type { Delete${pascal}SubmissionResult } from './types'
|
|
1902
1927
|
|
|
1903
1928
|
export async function delete${pascal}Submission(id: number): Promise<Delete${pascal}SubmissionResult> {
|
|
1904
1929
|
try {
|
|
@@ -1912,6 +1937,13 @@ export async function delete${pascal}Submission(id: number): Promise<Delete${pas
|
|
|
1912
1937
|
}
|
|
1913
1938
|
}
|
|
1914
1939
|
}
|
|
1940
|
+
`;
|
|
1941
|
+
const deleteBulkContent = `'use server'
|
|
1942
|
+
|
|
1943
|
+
import { eq, or } from 'drizzle-orm'
|
|
1944
|
+
import db from '@cms/db'
|
|
1945
|
+
import { ${tableName} } from '@cms/db/schema'
|
|
1946
|
+
import type { Delete${pascal}SubmissionResult } from './types'
|
|
1915
1947
|
|
|
1916
1948
|
export async function deleteBulk${pascal}Submissions(ids: number[]): Promise<Delete${pascal}SubmissionResult> {
|
|
1917
1949
|
try {
|
|
@@ -1928,6 +1960,10 @@ export async function deleteBulk${pascal}Submissions(ids: number[]): Promise<Del
|
|
|
1928
1960
|
}
|
|
1929
1961
|
}
|
|
1930
1962
|
}
|
|
1963
|
+
`;
|
|
1964
|
+
const exportCsvContent = `'use server'
|
|
1965
|
+
|
|
1966
|
+
import { get${pascal}Submissions } from './get-${kebab}-submissions'
|
|
1931
1967
|
|
|
1932
1968
|
export async function export${pascal}SubmissionsCSV(): Promise<string> {
|
|
1933
1969
|
const { submissions } = await get${pascal}Submissions()
|
|
@@ -1947,14 +1983,41 @@ export async function export${pascal}SubmissionsCSV(): Promise<string> {
|
|
|
1947
1983
|
)
|
|
1948
1984
|
return [headers, ...rows].join('\\n')
|
|
1949
1985
|
}
|
|
1986
|
+
`;
|
|
1987
|
+
const exportJsonContent = `'use server'
|
|
1988
|
+
|
|
1989
|
+
import { get${pascal}Submissions } from './get-${kebab}-submissions'
|
|
1950
1990
|
|
|
1951
1991
|
export async function export${pascal}SubmissionsJSON(): Promise<string> {
|
|
1952
1992
|
const { submissions } = await get${pascal}Submissions()
|
|
1953
1993
|
return JSON.stringify(submissions, null, 2)
|
|
1954
1994
|
}
|
|
1955
1995
|
`;
|
|
1956
|
-
|
|
1957
|
-
|
|
1996
|
+
const barrelContent = `export type { ${pascal}SubmissionData, ${pascal}SubmissionsResponse, Create${pascal}SubmissionInput, Create${pascal}SubmissionResult, Delete${pascal}SubmissionResult } from './types'
|
|
1997
|
+
export { get${pascal}Submissions } from './get-${kebab}-submissions'
|
|
1998
|
+
export { get${pascal}Submission } from './get-${kebab}-submission'
|
|
1999
|
+
export { create${pascal}Submission } from './create-${kebab}-submission'
|
|
2000
|
+
export { delete${pascal}Submission } from './delete-${kebab}-submission'
|
|
2001
|
+
export { deleteBulk${pascal}Submissions } from './delete-bulk-${kebab}-submissions'
|
|
2002
|
+
export { export${pascal}SubmissionsCSV } from './export-${kebab}-submissions-csv'
|
|
2003
|
+
export { export${pascal}SubmissionsJSON } from './export-${kebab}-submissions-json'
|
|
2004
|
+
`;
|
|
2005
|
+
const files = [
|
|
2006
|
+
{ name: "types.ts", content: typesContent },
|
|
2007
|
+
{ name: `get-${kebab}-submissions.ts`, content: getSubmissionsContent },
|
|
2008
|
+
{ name: `get-${kebab}-submission.ts`, content: getSubmissionContent },
|
|
2009
|
+
{ name: `create-${kebab}-submission.ts`, content: createContent },
|
|
2010
|
+
{ name: `delete-${kebab}-submission.ts`, content: deleteContent },
|
|
2011
|
+
{ name: `delete-bulk-${kebab}-submissions.ts`, content: deleteBulkContent },
|
|
2012
|
+
{ name: `export-${kebab}-submissions-csv.ts`, content: exportCsvContent },
|
|
2013
|
+
{ name: `export-${kebab}-submissions-json.ts`, content: exportJsonContent },
|
|
2014
|
+
{ name: "index.ts", content: barrelContent }
|
|
2015
|
+
];
|
|
2016
|
+
fs6.mkdirSync(dirPath, { recursive: true });
|
|
2017
|
+
for (const file of files) {
|
|
2018
|
+
fs6.writeFileSync(path6.join(dirPath, file.name), file.content, "utf-8");
|
|
2019
|
+
}
|
|
2020
|
+
return { files: files.map((f) => path6.relative(cwd, path6.join(dirPath, f.name))) };
|
|
1958
2021
|
}
|
|
1959
2022
|
|
|
1960
2023
|
// src/generators/form-pipeline/form-component-multistep.ts
|
|
@@ -3400,16 +3463,527 @@ ${blocks.join("\n\n")}
|
|
|
3400
3463
|
|
|
3401
3464
|
return record
|
|
3402
3465
|
}
|
|
3403
|
-
`;
|
|
3466
|
+
`;
|
|
3467
|
+
}
|
|
3468
|
+
|
|
3469
|
+
// src/generators/actions/entity-file-contents.ts
|
|
3470
|
+
function genTypesContent(ctx) {
|
|
3471
|
+
const parts = [
|
|
3472
|
+
ctx.dataInterface,
|
|
3473
|
+
ctx.displayDataInterface || null,
|
|
3474
|
+
ctx.responseInterface,
|
|
3475
|
+
ctx.filtersInterface,
|
|
3476
|
+
ctx.createInterface,
|
|
3477
|
+
`export interface Create${ctx.Singular}Result {
|
|
3478
|
+
success: boolean
|
|
3479
|
+
error?: string
|
|
3480
|
+
${ctx.camelSingular}?: ${ctx.Singular}Data
|
|
3481
|
+
}`,
|
|
3482
|
+
ctx.updateInterface,
|
|
3483
|
+
`export interface Update${ctx.Singular}Result {
|
|
3484
|
+
success: boolean
|
|
3485
|
+
error?: string
|
|
3486
|
+
${ctx.camelSingular}?: ${ctx.Singular}Data
|
|
3487
|
+
}`,
|
|
3488
|
+
`export interface Delete${ctx.Singular}Result {
|
|
3489
|
+
success: boolean
|
|
3490
|
+
error?: string
|
|
3491
|
+
}`,
|
|
3492
|
+
`export const CACHE_TAG = '${ctx.cacheTag}'`
|
|
3493
|
+
];
|
|
3494
|
+
return parts.filter(Boolean).join("\n\n") + "\n";
|
|
3495
|
+
}
|
|
3496
|
+
function genHelpersContent(ctx) {
|
|
3497
|
+
const hasSlugify = ctx.hasAutoSlug;
|
|
3498
|
+
const hasPopulate = ctx.hasListRels;
|
|
3499
|
+
if (!hasSlugify && !hasPopulate) return null;
|
|
3500
|
+
const lines = [];
|
|
3501
|
+
if (hasPopulate) {
|
|
3502
|
+
lines.push(`import db from '@cms/db'`);
|
|
3503
|
+
const tables = [...new Set(ctx.listRelTableImports)].sort();
|
|
3504
|
+
if (tables.length > 0) {
|
|
3505
|
+
lines.push(`import { ${tables.join(", ")} } from '@cms/db/schema'`);
|
|
3506
|
+
}
|
|
3507
|
+
lines.push(`import { inArray } from 'drizzle-orm'`);
|
|
3508
|
+
lines.push(`import type { ${ctx.Singular}Data } from './types'`);
|
|
3509
|
+
lines.push("");
|
|
3510
|
+
}
|
|
3511
|
+
if (hasSlugify) {
|
|
3512
|
+
lines.push(`export function slugify(text: string): string {`);
|
|
3513
|
+
lines.push(` return text.toLowerCase().trim().replace(/[^\\w\\s-]/g, '').replace(/\\s+/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '')`);
|
|
3514
|
+
lines.push(`}`);
|
|
3515
|
+
lines.push("");
|
|
3516
|
+
}
|
|
3517
|
+
if (hasPopulate) {
|
|
3518
|
+
const exportedFn = ctx.populateListRelsFn.replace("async function", "export async function").trim();
|
|
3519
|
+
lines.push(exportedFn);
|
|
3520
|
+
lines.push("");
|
|
3521
|
+
}
|
|
3522
|
+
return lines.join("\n");
|
|
3523
|
+
}
|
|
3524
|
+
function genGetPluralContent(ctx) {
|
|
3525
|
+
const dbImports = [ctx.tableVar, ...ctx.relTableImports].sort();
|
|
3526
|
+
const drizzle = ["asc"];
|
|
3527
|
+
if (ctx.hasSearch || ctx.filterableFields.length > 1) drizzle.push("and");
|
|
3528
|
+
if (ctx.hasSearch) drizzle.push("ilike", "or");
|
|
3529
|
+
if (ctx.filterableFields.some(() => true)) drizzle.push("eq");
|
|
3530
|
+
if (ctx.hasRelationships) drizzle.push("sql");
|
|
3531
|
+
const sortedDrizzle = [...new Set(drizzle)].sort();
|
|
3532
|
+
const typeImports = [`${ctx.Plural}Response`, `Get${ctx.Plural}Filters`, `${ctx.Singular}Data`];
|
|
3533
|
+
const populateImport = ctx.hasListRels ? `
|
|
3534
|
+
import { populate${ctx.Singular}ListRelationships } from './helpers'` : "";
|
|
3535
|
+
return `'use server'
|
|
3536
|
+
|
|
3537
|
+
import db from '@cms/db'
|
|
3538
|
+
import { ${dbImports.join(", ")} } from '@cms/db/schema'
|
|
3539
|
+
import { ${sortedDrizzle.join(", ")} } from 'drizzle-orm'
|
|
3540
|
+
import type { ${typeImports.sort().join(", ")} } from './types'${populateImport}
|
|
3541
|
+
|
|
3542
|
+
export async function get${ctx.Plural}(filters?: Get${ctx.Plural}Filters): Promise<${ctx.Plural}Response> {
|
|
3543
|
+
try {
|
|
3544
|
+
const conditions = []
|
|
3545
|
+
${ctx.searchBlock}${ctx.filterConditions}
|
|
3546
|
+
|
|
3547
|
+
const query = ${ctx.listSelectClause}
|
|
3548
|
+
|
|
3549
|
+
const orderedQuery = conditions.length > 0
|
|
3550
|
+
? query.where(and(...conditions)).orderBy(asc(${ctx.tableVar}.sortOrder))
|
|
3551
|
+
: query.orderBy(asc(${ctx.tableVar}.sortOrder))
|
|
3552
|
+
|
|
3553
|
+
const results = filters?.limit && filters.limit > 0
|
|
3554
|
+
? await orderedQuery.limit(filters.limit)
|
|
3555
|
+
: await orderedQuery${ctx.listResultMapping}
|
|
3556
|
+
} catch (error) {
|
|
3557
|
+
console.error('Error fetching ${ctx.plural}:', error)
|
|
3558
|
+
return { ${ctx.camelPlural}: [], total: 0 }
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
`;
|
|
3562
|
+
}
|
|
3563
|
+
function genGetByIdContent(ctx) {
|
|
3564
|
+
const dbImports = [ctx.tableVar, ...ctx.relTableImports].sort();
|
|
3565
|
+
const drizzle = ["eq"];
|
|
3566
|
+
if (ctx.hasRelationships) drizzle.push("sql");
|
|
3567
|
+
const sortedDrizzle = [...new Set(drizzle)].sort();
|
|
3568
|
+
const populateImport = ctx.hasListRels ? `
|
|
3569
|
+
import { populate${ctx.Singular}ListRelationships } from './helpers'` : "";
|
|
3570
|
+
return `'use server'
|
|
3571
|
+
|
|
3572
|
+
import db from '@cms/db'
|
|
3573
|
+
import { ${dbImports.join(", ")} } from '@cms/db/schema'
|
|
3574
|
+
import { ${sortedDrizzle.join(", ")} } from 'drizzle-orm'
|
|
3575
|
+
import type { ${ctx.Singular}Data } from './types'${populateImport}
|
|
3576
|
+
|
|
3577
|
+
export async function get${ctx.Singular}ById(id: number): Promise<${ctx.Singular}Data | null> {
|
|
3578
|
+
try {
|
|
3579
|
+
const result = await ${ctx.selectClause}.where(eq(${ctx.tableVar}.id, id)).limit(1)
|
|
3580
|
+
if (result.length === 0) return null
|
|
3581
|
+
${ctx.singleRowReturn}
|
|
3582
|
+
} catch (error) {
|
|
3583
|
+
console.error('Error fetching ${ctx.singular}:', error)
|
|
3584
|
+
return null
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3587
|
+
`;
|
|
3588
|
+
}
|
|
3589
|
+
function genGetBySlugContent(ctx) {
|
|
3590
|
+
if (!ctx.hasSlug) return null;
|
|
3591
|
+
const returnType = ctx.hasHtmlOutput ? `${ctx.Singular}DisplayData` : `${ctx.Singular}Data`;
|
|
3592
|
+
const dbImports = [ctx.tableVar, ...ctx.relTableImports].sort();
|
|
3593
|
+
const drizzle = ["eq"];
|
|
3594
|
+
if (ctx.hasRelationships) drizzle.push("sql");
|
|
3595
|
+
const sortedDrizzle = [...new Set(drizzle)].sort();
|
|
3596
|
+
const typeImport = ctx.hasHtmlOutput ? `${ctx.Singular}DisplayData` : `${ctx.Singular}Data`;
|
|
3597
|
+
const extraTypes = ctx.hasListRels && ctx.hasHtmlOutput ? `, ${ctx.Singular}Data` : "";
|
|
3598
|
+
const populateImport = ctx.hasListRels ? `
|
|
3599
|
+
import { populate${ctx.Singular}ListRelationships } from './helpers'` : "";
|
|
3600
|
+
return `'use server'
|
|
3601
|
+
|
|
3602
|
+
import db from '@cms/db'
|
|
3603
|
+
import { ${dbImports.join(", ")} } from '@cms/db/schema'
|
|
3604
|
+
import { ${sortedDrizzle.join(", ")} } from 'drizzle-orm'
|
|
3605
|
+
import type { ${typeImport}${extraTypes} } from './types'${populateImport}
|
|
3606
|
+
|
|
3607
|
+
export async function get${ctx.Singular}BySlug(slug: string): Promise<${returnType} | null> {
|
|
3608
|
+
try {
|
|
3609
|
+
const result = await ${ctx.displaySelectClause}.where(eq(${ctx.tableVar}.slug, slug)).limit(1)
|
|
3610
|
+
if (result.length === 0) return null
|
|
3611
|
+
${ctx.displaySingleRowReturn}
|
|
3612
|
+
} catch (error) {
|
|
3613
|
+
console.error('Error fetching ${ctx.singular} by slug:', error)
|
|
3614
|
+
return null
|
|
3615
|
+
}
|
|
3616
|
+
}
|
|
3617
|
+
`;
|
|
3618
|
+
}
|
|
3619
|
+
function genCreateContent(ctx) {
|
|
3620
|
+
const slugImport = ctx.hasAutoSlug ? `
|
|
3621
|
+
import { slugify } from './helpers'` : "";
|
|
3622
|
+
return `'use server'
|
|
3623
|
+
|
|
3624
|
+
import db from '@cms/db'
|
|
3625
|
+
import { ${ctx.tableVar} } from '@cms/db/schema'
|
|
3626
|
+
import { desc } from 'drizzle-orm'
|
|
3627
|
+
import { updateTag } from 'next/cache'
|
|
3628
|
+
import { CACHE_TAG } from './types'
|
|
3629
|
+
import type { Create${ctx.Singular}Input, Create${ctx.Singular}Result, ${ctx.Singular}Data } from './types'${slugImport}
|
|
3630
|
+
|
|
3631
|
+
export async function create${ctx.Singular}(input: Create${ctx.Singular}Input): Promise<Create${ctx.Singular}Result> {
|
|
3632
|
+
try {${ctx.autoSlugCreate}
|
|
3633
|
+
const maxSortOrderResult = await db
|
|
3634
|
+
.select({ maxOrder: ${ctx.tableVar}.sortOrder })
|
|
3635
|
+
.from(${ctx.tableVar})
|
|
3636
|
+
.orderBy(desc(${ctx.tableVar}.sortOrder))
|
|
3637
|
+
.limit(1)
|
|
3638
|
+
const nextSortOrder = (maxSortOrderResult[0]?.maxOrder ?? 0) + 1
|
|
3639
|
+
${ctx.htmlCreateBlock}
|
|
3640
|
+
const result = await db.insert(${ctx.tableVar}).values({
|
|
3641
|
+
${ctx.createMappings},${ctx.hasHtmlOutput ? `
|
|
3642
|
+
${ctx.htmlCreateMappings},` : ""}${ctx.hasDraft ? `
|
|
3643
|
+
published: input.published ?? false,` : ""}
|
|
3644
|
+
sortOrder: nextSortOrder,
|
|
3645
|
+
createdAt: new Date().toISOString(),
|
|
3646
|
+
updatedAt: new Date().toISOString()
|
|
3647
|
+
}).returning()
|
|
3648
|
+
|
|
3649
|
+
updateTag(CACHE_TAG)
|
|
3650
|
+
|
|
3651
|
+
return {
|
|
3652
|
+
success: true,
|
|
3653
|
+
${ctx.camelSingular}: result[0] as ${ctx.Singular}Data
|
|
3654
|
+
}
|
|
3655
|
+
} catch (error) {
|
|
3656
|
+
console.error('Error creating ${ctx.singular}:', error)
|
|
3657
|
+
throw new Error(error instanceof Error ? error.message : 'Failed to create ${ctx.singular}')
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
`;
|
|
3661
|
+
}
|
|
3662
|
+
function genUpdateContent(ctx) {
|
|
3663
|
+
const slugImport = ctx.hasAutoSlug ? `
|
|
3664
|
+
import { slugify } from './helpers'` : "";
|
|
3665
|
+
const htmlUpdateBlock = ctx.hasHtmlOutput ? `
|
|
3666
|
+
const { renderMarkdownSync } = await import('@cms/lib/markdown/render')
|
|
3667
|
+
` + ctx.htmlOutputFields.map((f) => ` if (processedData.${f.name} !== undefined) {
|
|
3668
|
+
processedData.${f.name}Html = renderMarkdownSync(String(processedData.${f.name} || ''))
|
|
3669
|
+
}`).join("\n") + "\n" : "";
|
|
3670
|
+
return `'use server'
|
|
3671
|
+
|
|
3672
|
+
import db from '@cms/db'
|
|
3673
|
+
import { ${ctx.tableVar} } from '@cms/db/schema'
|
|
3674
|
+
import { eq } from 'drizzle-orm'
|
|
3675
|
+
import { updateTag } from 'next/cache'
|
|
3676
|
+
import { CACHE_TAG } from './types'
|
|
3677
|
+
import type { Update${ctx.Singular}Input, Update${ctx.Singular}Result, ${ctx.Singular}Data } from './types'${slugImport}
|
|
3678
|
+
|
|
3679
|
+
export async function update${ctx.Singular}(input: Update${ctx.Singular}Input): Promise<Update${ctx.Singular}Result> {
|
|
3680
|
+
try {
|
|
3681
|
+
const { id, ...updateData } = input
|
|
3682
|
+
${ctx.autoSlugUpdate}
|
|
3683
|
+
const fieldMeta = [
|
|
3684
|
+
${ctx.fieldMeta}
|
|
3685
|
+
]
|
|
3686
|
+
|
|
3687
|
+
const processedData: Record<string, unknown> = {}
|
|
3688
|
+
for (const [key, value] of Object.entries(updateData)) {
|
|
3689
|
+
if (key === 'published') { processedData[key] = value; continue }
|
|
3690
|
+
const field = fieldMeta.find(f => f.name === key)
|
|
3691
|
+
if (!field) continue
|
|
3692
|
+
if (field.type === 'list') {
|
|
3693
|
+
processedData[key] = value || []
|
|
3694
|
+
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'varchar', 'text', 'select'].includes(field.type)) {
|
|
3695
|
+
processedData[key] = value && value !== '' ? value : null
|
|
3696
|
+
} else {
|
|
3697
|
+
processedData[key] = value
|
|
3698
|
+
}
|
|
3699
|
+
}
|
|
3700
|
+
${htmlUpdateBlock}
|
|
3701
|
+
const result = await db.update(${ctx.tableVar})
|
|
3702
|
+
.set({ ...processedData, updatedAt: new Date().toISOString() })
|
|
3703
|
+
.where(eq(${ctx.tableVar}.id, id))
|
|
3704
|
+
.returning()
|
|
3705
|
+
|
|
3706
|
+
if (result.length === 0) throw new Error('${ctx.Singular} not found')
|
|
3707
|
+
|
|
3708
|
+
updateTag(CACHE_TAG)
|
|
3709
|
+
|
|
3710
|
+
return {
|
|
3711
|
+
success: true,
|
|
3712
|
+
${ctx.camelSingular}: result[0] as ${ctx.Singular}Data
|
|
3713
|
+
}
|
|
3714
|
+
} catch (error) {
|
|
3715
|
+
console.error('Error updating ${ctx.singular}:', error)
|
|
3716
|
+
throw new Error(error instanceof Error ? error.message : 'Failed to update ${ctx.singular}')
|
|
3717
|
+
}
|
|
3718
|
+
}
|
|
3719
|
+
`;
|
|
3720
|
+
}
|
|
3721
|
+
function genDeleteContent(ctx) {
|
|
3722
|
+
return `'use server'
|
|
3723
|
+
|
|
3724
|
+
import db from '@cms/db'
|
|
3725
|
+
import { ${ctx.tableVar} } from '@cms/db/schema'
|
|
3726
|
+
import { eq } from 'drizzle-orm'
|
|
3727
|
+
import { updateTag } from 'next/cache'
|
|
3728
|
+
import { CACHE_TAG } from './types'
|
|
3729
|
+
import type { Delete${ctx.Singular}Result } from './types'
|
|
3730
|
+
|
|
3731
|
+
export async function delete${ctx.Singular}(id: number): Promise<Delete${ctx.Singular}Result> {
|
|
3732
|
+
try {
|
|
3733
|
+
await db.delete(${ctx.tableVar}).where(eq(${ctx.tableVar}.id, id))
|
|
3734
|
+
updateTag(CACHE_TAG)
|
|
3735
|
+
return { success: true }
|
|
3736
|
+
} catch (error) {
|
|
3737
|
+
console.error('Error deleting ${ctx.singular}:', error)
|
|
3738
|
+
return { success: false, error: error instanceof Error ? error.message : 'Failed to delete ${ctx.singular}' }
|
|
3739
|
+
}
|
|
3740
|
+
}
|
|
3741
|
+
`;
|
|
3742
|
+
}
|
|
3743
|
+
function genDeleteBulkContent(ctx) {
|
|
3744
|
+
return `'use server'
|
|
3745
|
+
|
|
3746
|
+
import db from '@cms/db'
|
|
3747
|
+
import { ${ctx.tableVar} } from '@cms/db/schema'
|
|
3748
|
+
import { inArray } from 'drizzle-orm'
|
|
3749
|
+
import { updateTag } from 'next/cache'
|
|
3750
|
+
import { CACHE_TAG } from './types'
|
|
3751
|
+
import type { Delete${ctx.Singular}Result } from './types'
|
|
3752
|
+
|
|
3753
|
+
export async function deleteBulk${ctx.Plural}(ids: number[]): Promise<Delete${ctx.Singular}Result> {
|
|
3754
|
+
try {
|
|
3755
|
+
if (ids.length === 0) return { success: false, error: 'No items selected for deletion' }
|
|
3756
|
+
await db.delete(${ctx.tableVar}).where(inArray(${ctx.tableVar}.id, ids))
|
|
3757
|
+
updateTag(CACHE_TAG)
|
|
3758
|
+
return { success: true }
|
|
3759
|
+
} catch (error) {
|
|
3760
|
+
console.error('Error deleting ${ctx.plural}:', error)
|
|
3761
|
+
return { success: false, error: error instanceof Error ? error.message : 'Failed to delete ${ctx.plural}' }
|
|
3762
|
+
}
|
|
3763
|
+
}
|
|
3764
|
+
`;
|
|
3765
|
+
}
|
|
3766
|
+
function genSortOrderContent(ctx) {
|
|
3767
|
+
return `'use server'
|
|
3768
|
+
|
|
3769
|
+
import db from '@cms/db'
|
|
3770
|
+
import { ${ctx.tableVar} } from '@cms/db/schema'
|
|
3771
|
+
import { asc, desc, eq, gt, lt } from 'drizzle-orm'
|
|
3772
|
+
import { updateTag } from 'next/cache'
|
|
3773
|
+
import { CACHE_TAG } from './types'
|
|
3774
|
+
|
|
3775
|
+
export async function update${ctx.Singular}SortOrder(
|
|
3776
|
+
id: number,
|
|
3777
|
+
direction: 'up' | 'down'
|
|
3778
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
3779
|
+
try {
|
|
3780
|
+
const current = await db.select({ id: ${ctx.tableVar}.id, sortOrder: ${ctx.tableVar}.sortOrder }).from(${ctx.tableVar}).where(eq(${ctx.tableVar}.id, id)).limit(1)
|
|
3781
|
+
if (current.length === 0) return { success: false, error: '${ctx.Singular} not found' }
|
|
3782
|
+
|
|
3783
|
+
const currentSortOrder = current[0].sortOrder
|
|
3784
|
+
const adjacent = await db
|
|
3785
|
+
.select({ id: ${ctx.tableVar}.id, sortOrder: ${ctx.tableVar}.sortOrder })
|
|
3786
|
+
.from(${ctx.tableVar})
|
|
3787
|
+
.where(direction === 'up' ? lt(${ctx.tableVar}.sortOrder, currentSortOrder) : gt(${ctx.tableVar}.sortOrder, currentSortOrder))
|
|
3788
|
+
.orderBy(direction === 'up' ? desc(${ctx.tableVar}.sortOrder) : asc(${ctx.tableVar}.sortOrder))
|
|
3789
|
+
.limit(1)
|
|
3790
|
+
|
|
3791
|
+
if (adjacent.length === 0) return { success: true }
|
|
3792
|
+
|
|
3793
|
+
await db.update(${ctx.tableVar}).set({ sortOrder: adjacent[0].sortOrder }).where(eq(${ctx.tableVar}.id, id))
|
|
3794
|
+
await db.update(${ctx.tableVar}).set({ sortOrder: currentSortOrder }).where(eq(${ctx.tableVar}.id, adjacent[0].id))
|
|
3795
|
+
updateTag(CACHE_TAG)
|
|
3796
|
+
return { success: true }
|
|
3797
|
+
} catch (error) {
|
|
3798
|
+
console.error('Error updating sort order:', error)
|
|
3799
|
+
return { success: false, error: error instanceof Error ? error.message : 'Failed to update sort order' }
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
`;
|
|
3803
|
+
}
|
|
3804
|
+
function genBulkSortOrderContent(ctx) {
|
|
3805
|
+
return `'use server'
|
|
3806
|
+
|
|
3807
|
+
import db from '@cms/db'
|
|
3808
|
+
import { ${ctx.tableVar} } from '@cms/db/schema'
|
|
3809
|
+
import { eq } from 'drizzle-orm'
|
|
3810
|
+
import { updateTag } from 'next/cache'
|
|
3811
|
+
import { CACHE_TAG } from './types'
|
|
3812
|
+
|
|
3813
|
+
export async function bulkUpdate${ctx.Plural}SortOrder(
|
|
3814
|
+
updates: Array<{ id: number; sortOrder: number }>
|
|
3815
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
3816
|
+
try {
|
|
3817
|
+
if (updates.length === 0) return { success: true }
|
|
3818
|
+
await Promise.all(updates.map((u) => db.update(${ctx.tableVar}).set({ sortOrder: u.sortOrder }).where(eq(${ctx.tableVar}.id, u.id))))
|
|
3819
|
+
updateTag(CACHE_TAG)
|
|
3820
|
+
return { success: true }
|
|
3821
|
+
} catch (error) {
|
|
3822
|
+
console.error('Error bulk updating sort order:', error)
|
|
3823
|
+
return { success: false, error: error instanceof Error ? error.message : 'Failed to bulk update sort order' }
|
|
3824
|
+
}
|
|
3825
|
+
}
|
|
3826
|
+
`;
|
|
3827
|
+
}
|
|
3828
|
+
function genM2MFiles(ctx) {
|
|
3829
|
+
const files = [];
|
|
3830
|
+
for (const f of ctx.m2mFields) {
|
|
3831
|
+
const rel = f.relationship || "";
|
|
3832
|
+
const RelPascal = toPascalCase(rel);
|
|
3833
|
+
const relSingular = singularize(rel);
|
|
3834
|
+
const junctionVar = toCamelCase(`${ctx.singular}${RelPascal}`);
|
|
3835
|
+
const entityIdCol = `${ctx.singular}Id`;
|
|
3836
|
+
const relIdCol = `${relSingular}Id`;
|
|
3837
|
+
const kebabRel = rel.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
3838
|
+
files.push({
|
|
3839
|
+
name: `get-${kebabRel}-for-${ctx.singular}.ts`,
|
|
3840
|
+
content: `'use server'
|
|
3841
|
+
|
|
3842
|
+
import db from '@cms/db'
|
|
3843
|
+
import { ${junctionVar} } from '@cms/db/schema'
|
|
3844
|
+
import { eq } from 'drizzle-orm'
|
|
3845
|
+
|
|
3846
|
+
export async function get${RelPascal}For${ctx.Singular}(${ctx.singular}Id: number): Promise<number[]> {
|
|
3847
|
+
try {
|
|
3848
|
+
const results = await db.select({ ${relIdCol}: ${junctionVar}.${relIdCol} }).from(${junctionVar}).where(eq(${junctionVar}.${entityIdCol}, ${ctx.singular}Id))
|
|
3849
|
+
return results.map(r => r.${relIdCol})
|
|
3850
|
+
} catch (error) {
|
|
3851
|
+
console.error('Error fetching ${rel} for ${ctx.singular}:', error)
|
|
3852
|
+
return []
|
|
3853
|
+
}
|
|
3854
|
+
}
|
|
3855
|
+
`
|
|
3856
|
+
});
|
|
3857
|
+
files.push({
|
|
3858
|
+
name: `set-${kebabRel}-for-${ctx.singular}.ts`,
|
|
3859
|
+
content: `'use server'
|
|
3860
|
+
|
|
3861
|
+
import db from '@cms/db'
|
|
3862
|
+
import { ${junctionVar} } from '@cms/db/schema'
|
|
3863
|
+
import { eq } from 'drizzle-orm'
|
|
3864
|
+
import { updateTag } from 'next/cache'
|
|
3865
|
+
import { CACHE_TAG } from './types'
|
|
3866
|
+
|
|
3867
|
+
export async function set${RelPascal}For${ctx.Singular}(${ctx.singular}Id: number, ${rel}Ids: number[]): Promise<{ success: boolean; error?: string }> {
|
|
3868
|
+
try {
|
|
3869
|
+
await db.delete(${junctionVar}).where(eq(${junctionVar}.${entityIdCol}, ${ctx.singular}Id))
|
|
3870
|
+
if (${rel}Ids.length > 0) {
|
|
3871
|
+
await db.insert(${junctionVar}).values(${rel}Ids.map(${relSingular}Id => ({ ${entityIdCol}: ${ctx.singular}Id, ${relIdCol}: ${relSingular}Id })))
|
|
3872
|
+
}
|
|
3873
|
+
updateTag(CACHE_TAG)
|
|
3874
|
+
return { success: true }
|
|
3875
|
+
} catch (error) {
|
|
3876
|
+
console.error('Error setting ${rel} for ${ctx.singular}:', error)
|
|
3877
|
+
return { success: false, error: error instanceof Error ? error.message : 'Failed to set ${rel}' }
|
|
3878
|
+
}
|
|
3879
|
+
}
|
|
3880
|
+
`
|
|
3881
|
+
});
|
|
3882
|
+
}
|
|
3883
|
+
return files;
|
|
3884
|
+
}
|
|
3885
|
+
function genDistinctFiles(ctx) {
|
|
3886
|
+
if (!ctx.hasFilters) return [];
|
|
3887
|
+
return ctx.filters.map((filter) => {
|
|
3888
|
+
const fieldPascal = toPascalCase(filter.field);
|
|
3889
|
+
const kebabField = filter.field.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
3890
|
+
const kebabPlural = ctx.plural.replace(/([A-Z])/g, "-$1").toLowerCase().replace(/^-/, "");
|
|
3891
|
+
return {
|
|
3892
|
+
name: `get-distinct-${kebabPlural}-${kebabField}.ts`,
|
|
3893
|
+
content: `'use server'
|
|
3894
|
+
|
|
3895
|
+
import db from '@cms/db'
|
|
3896
|
+
import { ${ctx.tableVar} } from '@cms/db/schema'
|
|
3897
|
+
import { asc, isNotNull } from 'drizzle-orm'
|
|
3898
|
+
|
|
3899
|
+
export async function getDistinct${ctx.Plural}${fieldPascal}(): Promise<string[]> {
|
|
3900
|
+
try {
|
|
3901
|
+
const results = await db
|
|
3902
|
+
.selectDistinct({ value: ${ctx.tableVar}.${filter.field} })
|
|
3903
|
+
.from(${ctx.tableVar})
|
|
3904
|
+
.where(isNotNull(${ctx.tableVar}.${filter.field}))
|
|
3905
|
+
.orderBy(asc(${ctx.tableVar}.${filter.field}))
|
|
3906
|
+
return results.map((r) => String(r.value)).filter(Boolean)
|
|
3907
|
+
} catch (error) {
|
|
3908
|
+
console.error('Error fetching distinct ${ctx.plural} ${filter.field}:', error)
|
|
3909
|
+
return []
|
|
3910
|
+
}
|
|
3911
|
+
}
|
|
3912
|
+
`
|
|
3913
|
+
};
|
|
3914
|
+
});
|
|
3915
|
+
}
|
|
3916
|
+
function genBarrelContent(files, ctx) {
|
|
3917
|
+
const lines = [];
|
|
3918
|
+
const typeNames = [
|
|
3919
|
+
`${ctx.Singular}Data`,
|
|
3920
|
+
ctx.hasHtmlOutput ? `${ctx.Singular}DisplayData` : null,
|
|
3921
|
+
`${ctx.Plural}Response`,
|
|
3922
|
+
`Get${ctx.Plural}Filters`,
|
|
3923
|
+
`Create${ctx.Singular}Input`,
|
|
3924
|
+
`Create${ctx.Singular}Result`,
|
|
3925
|
+
`Update${ctx.Singular}Input`,
|
|
3926
|
+
`Update${ctx.Singular}Result`,
|
|
3927
|
+
`Delete${ctx.Singular}Result`
|
|
3928
|
+
].filter(Boolean);
|
|
3929
|
+
lines.push(`export type { ${typeNames.join(", ")} } from './types'`);
|
|
3930
|
+
lines.push("");
|
|
3931
|
+
for (const file of files) {
|
|
3932
|
+
if (file.name === "types.ts" || file.name === "helpers.ts") continue;
|
|
3933
|
+
const exportMatches = file.content.matchAll(/export async function (\w+)/g);
|
|
3934
|
+
const fnNames = [...exportMatches].map((m) => m[1]);
|
|
3935
|
+
if (fnNames.length > 0) {
|
|
3936
|
+
const modulePath = "./" + file.name.replace(/\.ts$/, "");
|
|
3937
|
+
lines.push(`export { ${fnNames.join(", ")} } from '${modulePath}'`);
|
|
3938
|
+
}
|
|
3939
|
+
}
|
|
3940
|
+
lines.push("");
|
|
3941
|
+
return lines.join("\n");
|
|
3404
3942
|
}
|
|
3405
3943
|
|
|
3406
3944
|
// src/generators/actions/entity-actions.ts
|
|
3407
3945
|
function generateActions(schema, cwd, actionsDir, options = {}) {
|
|
3408
3946
|
const absActionsDir = path12.join(cwd, actionsDir);
|
|
3409
|
-
const
|
|
3410
|
-
|
|
3947
|
+
const dirPath = path12.join(absActionsDir, schema.name);
|
|
3948
|
+
const oldFilePath = path12.join(absActionsDir, `${schema.name}.ts`);
|
|
3949
|
+
if (!options.force && (fs12.existsSync(dirPath) || fs12.existsSync(oldFilePath))) {
|
|
3411
3950
|
return { files: [] };
|
|
3412
3951
|
}
|
|
3952
|
+
if (options.force) {
|
|
3953
|
+
if (fs12.existsSync(oldFilePath)) fs12.unlinkSync(oldFilePath);
|
|
3954
|
+
if (fs12.existsSync(dirPath)) fs12.rmSync(dirPath, { recursive: true });
|
|
3955
|
+
}
|
|
3956
|
+
const ctx = buildEntityContext(schema);
|
|
3957
|
+
const kebabSingular = toKebabCase(ctx.singular);
|
|
3958
|
+
const kebabPlural = toKebabCase(ctx.plural);
|
|
3959
|
+
const files = [];
|
|
3960
|
+
files.push({ name: "types.ts", content: genTypesContent(ctx) });
|
|
3961
|
+
const helpersContent = genHelpersContent(ctx);
|
|
3962
|
+
if (helpersContent) {
|
|
3963
|
+
files.push({ name: "helpers.ts", content: helpersContent });
|
|
3964
|
+
}
|
|
3965
|
+
files.push({ name: `get-${kebabPlural}.ts`, content: genGetPluralContent(ctx) });
|
|
3966
|
+
files.push({ name: `get-${kebabSingular}-by-id.ts`, content: genGetByIdContent(ctx) });
|
|
3967
|
+
const bySlugContent = genGetBySlugContent(ctx);
|
|
3968
|
+
if (bySlugContent) {
|
|
3969
|
+
files.push({ name: `get-${kebabSingular}-by-slug.ts`, content: bySlugContent });
|
|
3970
|
+
}
|
|
3971
|
+
files.push({ name: `create-${kebabSingular}.ts`, content: genCreateContent(ctx) });
|
|
3972
|
+
files.push({ name: `update-${kebabSingular}.ts`, content: genUpdateContent(ctx) });
|
|
3973
|
+
files.push({ name: `delete-${kebabSingular}.ts`, content: genDeleteContent(ctx) });
|
|
3974
|
+
files.push({ name: `delete-bulk-${kebabPlural}.ts`, content: genDeleteBulkContent(ctx) });
|
|
3975
|
+
files.push({ name: `update-${kebabSingular}-sort-order.ts`, content: genSortOrderContent(ctx) });
|
|
3976
|
+
files.push({ name: `bulk-update-${kebabPlural}-sort-order.ts`, content: genBulkSortOrderContent(ctx) });
|
|
3977
|
+
files.push(...genM2MFiles(ctx));
|
|
3978
|
+
files.push(...genDistinctFiles(ctx));
|
|
3979
|
+
files.push({ name: "index.ts", content: genBarrelContent(files, ctx) });
|
|
3980
|
+
fs12.mkdirSync(dirPath, { recursive: true });
|
|
3981
|
+
for (const file of files) {
|
|
3982
|
+
fs12.writeFileSync(path12.join(dirPath, file.name), file.content, "utf-8");
|
|
3983
|
+
}
|
|
3984
|
+
return { files: files.map((f) => path12.join(actionsDir, schema.name, f.name)) };
|
|
3985
|
+
}
|
|
3986
|
+
function buildEntityContext(schema) {
|
|
3413
3987
|
const singular = singularize(schema.name);
|
|
3414
3988
|
const plural = pluralize(schema.name);
|
|
3415
3989
|
const Singular = toPascalCase(singular);
|
|
@@ -3452,6 +4026,7 @@ function generateActions(schema, cwd, actionsDir, options = {}) {
|
|
|
3452
4026
|
const hasSearch = (schema.search?.fields || []).length > 0;
|
|
3453
4027
|
const searchFields = schema.search?.fields || [];
|
|
3454
4028
|
const hasFilters = (schema.filters || []).length > 0;
|
|
4029
|
+
const hasAutoSlug = schema.autoSlugify?.enabled === true;
|
|
3455
4030
|
const allDbFields = [...regularDbFields];
|
|
3456
4031
|
if (hasDraft && !hasPublished) {
|
|
3457
4032
|
allDbFields.push({ name: "published", type: "boolean", required: true });
|
|
@@ -3473,23 +4048,8 @@ function generateActions(schema, cwd, actionsDir, options = {}) {
|
|
|
3473
4048
|
const filterableFields = allDbFields.filter(
|
|
3474
4049
|
(f) => !f.primaryKey && f.name !== "createdAt" && f.name !== "updatedAt" && f.name !== "sortOrder" && ["string", "varchar", "text", "boolean", "number", "decimal"].includes(f.type)
|
|
3475
4050
|
);
|
|
3476
|
-
const
|
|
3477
|
-
|
|
3478
|
-
if (hasSearch) drizzleImports.push("ilike", "or");
|
|
3479
|
-
if (hasRelationships) drizzleImports.push("sql");
|
|
3480
|
-
if (hasFilters) drizzleImports.push("isNotNull");
|
|
3481
|
-
const sortedDrizzleImports = [...new Set(drizzleImports)].sort();
|
|
3482
|
-
const dbImports = /* @__PURE__ */ new Set([tableVar]);
|
|
3483
|
-
for (const f of relationshipFields) dbImports.add(toCamelCase(f.relationship));
|
|
3484
|
-
for (const f of m2mFields) {
|
|
3485
|
-
const junctionVar = toCamelCase(`${singular}${toPascalCase(f.relationship || "")}`);
|
|
3486
|
-
dbImports.add(junctionVar);
|
|
3487
|
-
dbImports.add(toCamelCase(f.relationship || ""));
|
|
3488
|
-
}
|
|
3489
|
-
for (const q of allListRelQueries) {
|
|
3490
|
-
dbImports.add(q.relTable);
|
|
3491
|
-
}
|
|
3492
|
-
const sortedDbImports = [...dbImports].sort();
|
|
4051
|
+
const relTableImports = relationshipFields.map((f) => toCamelCase(f.relationship));
|
|
4052
|
+
const listRelTableImports = allListRelQueries.map((q) => q.relTable);
|
|
3493
4053
|
const dataFields = allDbFields.map(
|
|
3494
4054
|
(f) => ` ${quotePropertyName(f.name)}: ${getFieldType(f, "output")}${f.required ? "" : " | null"}`
|
|
3495
4055
|
).join("\n");
|
|
@@ -3501,9 +4061,10 @@ ${htmlFieldTypes}` : ""}${m2mFieldTypes ? `
|
|
|
3501
4061
|
${m2mFieldTypes}` : ""}
|
|
3502
4062
|
}`;
|
|
3503
4063
|
const displayDbFields = allDbFields.filter((f) => !htmlOutputFields.some((h) => h.name === f.name));
|
|
3504
|
-
const displayDataFields = displayDbFields.map(
|
|
3505
|
-
|
|
3506
|
-
|
|
4064
|
+
const displayDataFields = displayDbFields.map(
|
|
4065
|
+
(f) => ` ${quotePropertyName(f.name)}: ${getFieldType(f, "output")}${f.required ? "" : " | null"}`
|
|
4066
|
+
).join("\n");
|
|
4067
|
+
const displayDataInterface = hasHtmlOutput ? `export interface ${Singular}DisplayData {
|
|
3507
4068
|
${displayDataFields}${htmlFieldTypes ? `
|
|
3508
4069
|
${htmlFieldTypes}` : ""}${m2mFieldTypes ? `
|
|
3509
4070
|
${m2mFieldTypes}` : ""}
|
|
@@ -3551,14 +4112,7 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
3551
4112
|
)
|
|
3552
4113
|
}
|
|
3553
4114
|
` : "";
|
|
3554
|
-
const resultMapping = hasRelationships ? buildResultMapping(
|
|
3555
|
-
allDbFields,
|
|
3556
|
-
relationshipFields,
|
|
3557
|
-
Plural,
|
|
3558
|
-
camelPlural,
|
|
3559
|
-
Singular,
|
|
3560
|
-
hasListRels
|
|
3561
|
-
) : hasListRels ? `
|
|
4115
|
+
const resultMapping = hasRelationships ? buildResultMapping(allDbFields, relationshipFields, Plural, camelPlural, Singular, hasListRels) : hasListRels ? `
|
|
3562
4116
|
|
|
3563
4117
|
const populated${Plural} = await Promise.all(
|
|
3564
4118
|
results.map(record => populate${Singular}ListRelationships(record as ${Singular}Data))
|
|
@@ -3572,72 +4126,26 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
3572
4126
|
${camelPlural}: results as ${Singular}Data[],
|
|
3573
4127
|
total: results.length
|
|
3574
4128
|
}`;
|
|
3575
|
-
const listResultMapping = hasHtmlOutput && hasRelationships ? buildResultMapping(
|
|
4129
|
+
const listResultMapping = hasHtmlOutput && hasRelationships ? buildResultMapping(
|
|
4130
|
+
listDbFields,
|
|
4131
|
+
relationshipFields,
|
|
4132
|
+
Plural,
|
|
4133
|
+
camelPlural,
|
|
4134
|
+
Singular,
|
|
4135
|
+
hasListRels
|
|
4136
|
+
) : resultMapping;
|
|
3576
4137
|
const fieldMeta = createFields.map((f) => `{ name: '${f.name}', type: '${f.type}', required: ${f.required ?? false} }`).join(",\n ");
|
|
3577
4138
|
const createMappings = createFields.map((f) => ` ${f.name}: ${generateFieldMapping(f)}`).join(",\n");
|
|
3578
4139
|
const htmlCreateBlock = hasHtmlOutput ? `
|
|
3579
4140
|
const { renderMarkdownSync } = await import('@cms/lib/markdown/render')
|
|
3580
4141
|
` + htmlOutputFields.map((f) => ` const ${f.name}Html = renderMarkdownSync(input.${f.name} || '')`).join("\n") + "\n" : "";
|
|
3581
4142
|
const htmlCreateMappings = htmlOutputFields.map((f) => ` ${f.name}Html`).join(",\n");
|
|
3582
|
-
const
|
|
3583
|
-
(filter) => `
|
|
3584
|
-
export async function getDistinct${Plural}${toPascalCase(filter.field)}(): Promise<string[]> {
|
|
3585
|
-
try {
|
|
3586
|
-
const results = await db
|
|
3587
|
-
.selectDistinct({ value: ${tableVar}.${filter.field} })
|
|
3588
|
-
.from(${tableVar})
|
|
3589
|
-
.where(isNotNull(${tableVar}.${filter.field}))
|
|
3590
|
-
.orderBy(asc(${tableVar}.${filter.field}))
|
|
3591
|
-
return results.map((r) => String(r.value)).filter(Boolean)
|
|
3592
|
-
} catch (error) {
|
|
3593
|
-
console.error('Error fetching distinct ${plural} ${filter.field}:', error)
|
|
3594
|
-
return []
|
|
3595
|
-
}
|
|
3596
|
-
}`
|
|
3597
|
-
).join("\n") : "";
|
|
3598
|
-
const m2mHelpers = hasM2M ? m2mFields.map((f) => {
|
|
3599
|
-
const rel = f.relationship || "";
|
|
3600
|
-
const RelPascal = toPascalCase(rel);
|
|
3601
|
-
const relSingular = singularize(rel);
|
|
3602
|
-
const junctionVar = toCamelCase(`${singular}${RelPascal}`);
|
|
3603
|
-
const entityIdCol = `${singular}Id`;
|
|
3604
|
-
const relIdCol = `${relSingular}Id`;
|
|
3605
|
-
return `
|
|
3606
|
-
export async function get${RelPascal}For${Singular}(${singular}Id: number): Promise<number[]> {
|
|
3607
|
-
try {
|
|
3608
|
-
const results = await db.select({ ${relIdCol}: ${junctionVar}.${relIdCol} }).from(${junctionVar}).where(eq(${junctionVar}.${entityIdCol}, ${singular}Id))
|
|
3609
|
-
return results.map(r => r.${relIdCol})
|
|
3610
|
-
} catch (error) {
|
|
3611
|
-
console.error('Error fetching ${rel} for ${singular}:', error)
|
|
3612
|
-
return []
|
|
3613
|
-
}
|
|
3614
|
-
}
|
|
3615
|
-
|
|
3616
|
-
export async function set${RelPascal}For${Singular}(${singular}Id: number, ${rel}Ids: number[]): Promise<{ success: boolean; error?: string }> {
|
|
3617
|
-
try {
|
|
3618
|
-
await db.delete(${junctionVar}).where(eq(${junctionVar}.${entityIdCol}, ${singular}Id))
|
|
3619
|
-
if (${rel}Ids.length > 0) {
|
|
3620
|
-
await db.insert(${junctionVar}).values(${rel}Ids.map(${relSingular}Id => ({ ${entityIdCol}: ${singular}Id, ${relIdCol}: ${relSingular}Id })))
|
|
3621
|
-
}
|
|
3622
|
-
updateTag(CACHE_TAG)
|
|
3623
|
-
return { success: true }
|
|
3624
|
-
} catch (error) {
|
|
3625
|
-
console.error('Error setting ${rel} for ${singular}:', error)
|
|
3626
|
-
return { success: false, error: error instanceof Error ? error.message : 'Failed to set ${rel}' }
|
|
3627
|
-
}
|
|
3628
|
-
}`;
|
|
3629
|
-
}).join("\n") : "";
|
|
3630
|
-
const slugifyHelper = schema.autoSlugify?.enabled ? `
|
|
3631
|
-
function slugify(text: string): string {
|
|
3632
|
-
return text.toLowerCase().trim().replace(/[^\\w\\s-]/g, '').replace(/\\s+/g, '-').replace(/-+/g, '-').replace(/^-+|-+$/g, '')
|
|
3633
|
-
}
|
|
3634
|
-
` : "";
|
|
3635
|
-
const autoSlugCreate = schema.autoSlugify?.enabled ? `
|
|
4143
|
+
const autoSlugCreate = hasAutoSlug ? `
|
|
3636
4144
|
if ((!input.${schema.autoSlugify.targetField} || input.${schema.autoSlugify.targetField} === '') && input.${schema.autoSlugify.sourceField}) {
|
|
3637
4145
|
input.${schema.autoSlugify.targetField} = slugify(input.${schema.autoSlugify.sourceField})
|
|
3638
4146
|
}
|
|
3639
4147
|
` : "";
|
|
3640
|
-
const autoSlugUpdate =
|
|
4148
|
+
const autoSlugUpdate = hasAutoSlug ? `
|
|
3641
4149
|
if (updateData.${schema.autoSlugify.sourceField}) {
|
|
3642
4150
|
if (updateData.${schema.autoSlugify.targetField} === '' || updateData.${schema.autoSlugify.targetField} === undefined) {
|
|
3643
4151
|
updateData.${schema.autoSlugify.targetField} = slugify(updateData.${schema.autoSlugify.sourceField})
|
|
@@ -3674,230 +4182,54 @@ function slugify(text: string): string {
|
|
|
3674
4182
|
}
|
|
3675
4183
|
return `return result[0] as ${displayType}`;
|
|
3676
4184
|
})() : singleRowReturn;
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
? await orderedQuery.limit(filters.limit)
|
|
3726
|
-
: await orderedQuery${listResultMapping}
|
|
3727
|
-
} catch (error) {
|
|
3728
|
-
console.error('Error fetching ${plural}:', error)
|
|
3729
|
-
return { ${camelPlural}: [], total: 0 }
|
|
3730
|
-
}
|
|
3731
|
-
}
|
|
3732
|
-
${distinctFns}
|
|
3733
|
-
|
|
3734
|
-
export async function get${Singular}ById(id: number): Promise<${Singular}Data | null> {
|
|
3735
|
-
try {
|
|
3736
|
-
const result = await ${selectClause}.where(eq(${tableVar}.id, id)).limit(1)
|
|
3737
|
-
if (result.length === 0) return null
|
|
3738
|
-
${singleRowReturn}
|
|
3739
|
-
} catch (error) {
|
|
3740
|
-
console.error('Error fetching ${singular}:', error)
|
|
3741
|
-
return null
|
|
3742
|
-
}
|
|
3743
|
-
}${hasSlug ? `
|
|
3744
|
-
|
|
3745
|
-
export async function get${Singular}BySlug(slug: string): Promise<${hasHtmlOutput ? `${Singular}DisplayData` : `${Singular}Data`} | null> {
|
|
3746
|
-
try {
|
|
3747
|
-
const result = await ${displaySelectClause}.where(eq(${tableVar}.slug, slug)).limit(1)
|
|
3748
|
-
if (result.length === 0) return null
|
|
3749
|
-
${displaySingleRowReturn}
|
|
3750
|
-
} catch (error) {
|
|
3751
|
-
console.error('Error fetching ${singular} by slug:', error)
|
|
3752
|
-
return null
|
|
3753
|
-
}
|
|
3754
|
-
}` : ""}
|
|
3755
|
-
|
|
3756
|
-
export async function create${Singular}(input: Create${Singular}Input): Promise<Create${Singular}Result> {
|
|
3757
|
-
try {${autoSlugCreate}
|
|
3758
|
-
const maxSortOrderResult = await db
|
|
3759
|
-
.select({ maxOrder: ${tableVar}.sortOrder })
|
|
3760
|
-
.from(${tableVar})
|
|
3761
|
-
.orderBy(desc(${tableVar}.sortOrder))
|
|
3762
|
-
.limit(1)
|
|
3763
|
-
const nextSortOrder = (maxSortOrderResult[0]?.maxOrder ?? 0) + 1
|
|
3764
|
-
${htmlCreateBlock}
|
|
3765
|
-
const result = await db.insert(${tableVar}).values({
|
|
3766
|
-
${createMappings},${hasHtmlOutput ? `
|
|
3767
|
-
${htmlCreateMappings},` : ""}${hasDraft ? `
|
|
3768
|
-
published: input.published ?? false,` : ""}
|
|
3769
|
-
sortOrder: nextSortOrder,
|
|
3770
|
-
createdAt: new Date().toISOString(),
|
|
3771
|
-
updatedAt: new Date().toISOString()
|
|
3772
|
-
}).returning()
|
|
3773
|
-
|
|
3774
|
-
updateTag(CACHE_TAG)
|
|
3775
|
-
|
|
3776
|
-
return {
|
|
3777
|
-
success: true,
|
|
3778
|
-
${camelSingular}: result[0] as ${Singular}Data
|
|
3779
|
-
}
|
|
3780
|
-
} catch (error) {
|
|
3781
|
-
console.error('Error creating ${singular}:', error)
|
|
3782
|
-
throw new Error(error instanceof Error ? error.message : 'Failed to create ${singular}')
|
|
3783
|
-
}
|
|
3784
|
-
}
|
|
3785
|
-
|
|
3786
|
-
export async function update${Singular}(input: Update${Singular}Input): Promise<Update${Singular}Result> {
|
|
3787
|
-
try {
|
|
3788
|
-
const { id, ...updateData } = input
|
|
3789
|
-
${autoSlugUpdate}
|
|
3790
|
-
const fieldMeta = [
|
|
3791
|
-
${fieldMeta}
|
|
3792
|
-
]
|
|
3793
|
-
|
|
3794
|
-
const processedData: Record<string, unknown> = {}
|
|
3795
|
-
for (const [key, value] of Object.entries(updateData)) {
|
|
3796
|
-
if (key === 'published') { processedData[key] = value; continue }
|
|
3797
|
-
const field = fieldMeta.find(f => f.name === key)
|
|
3798
|
-
if (!field) continue
|
|
3799
|
-
if (field.type === 'list') {
|
|
3800
|
-
processedData[key] = value || []
|
|
3801
|
-
} else if (!field.required && ['date', 'timestamp', 'time', 'string', 'varchar', 'text', 'select'].includes(field.type)) {
|
|
3802
|
-
processedData[key] = value && value !== '' ? value : null
|
|
3803
|
-
} else {
|
|
3804
|
-
processedData[key] = value
|
|
3805
|
-
}
|
|
3806
|
-
}
|
|
3807
|
-
${hasHtmlOutput ? `
|
|
3808
|
-
const { renderMarkdownSync } = await import('@cms/lib/markdown/render')
|
|
3809
|
-
` + htmlOutputFields.map((f) => ` if (processedData.${f.name} !== undefined) {
|
|
3810
|
-
processedData.${f.name}Html = renderMarkdownSync(String(processedData.${f.name} || ''))
|
|
3811
|
-
}`).join("\n") + "\n" : ""}
|
|
3812
|
-
const result = await db.update(${tableVar})
|
|
3813
|
-
.set({ ...processedData, updatedAt: new Date().toISOString() })
|
|
3814
|
-
.where(eq(${tableVar}.id, id))
|
|
3815
|
-
.returning()
|
|
3816
|
-
|
|
3817
|
-
if (result.length === 0) throw new Error('${Singular} not found')
|
|
3818
|
-
|
|
3819
|
-
updateTag(CACHE_TAG)
|
|
3820
|
-
|
|
3821
|
-
return {
|
|
3822
|
-
success: true,
|
|
3823
|
-
${camelSingular}: result[0] as ${Singular}Data
|
|
3824
|
-
}
|
|
3825
|
-
} catch (error) {
|
|
3826
|
-
console.error('Error updating ${singular}:', error)
|
|
3827
|
-
throw new Error(error instanceof Error ? error.message : 'Failed to update ${singular}')
|
|
3828
|
-
}
|
|
3829
|
-
}
|
|
3830
|
-
|
|
3831
|
-
export async function delete${Singular}(id: number): Promise<Delete${Singular}Result> {
|
|
3832
|
-
try {
|
|
3833
|
-
await db.delete(${tableVar}).where(eq(${tableVar}.id, id))
|
|
3834
|
-
updateTag(CACHE_TAG)
|
|
3835
|
-
return { success: true }
|
|
3836
|
-
} catch (error) {
|
|
3837
|
-
console.error('Error deleting ${singular}:', error)
|
|
3838
|
-
return { success: false, error: error instanceof Error ? error.message : 'Failed to delete ${singular}' }
|
|
3839
|
-
}
|
|
3840
|
-
}
|
|
3841
|
-
|
|
3842
|
-
export async function deleteBulk${Plural}(ids: number[]): Promise<Delete${Singular}Result> {
|
|
3843
|
-
try {
|
|
3844
|
-
if (ids.length === 0) return { success: false, error: 'No items selected for deletion' }
|
|
3845
|
-
await db.delete(${tableVar}).where(inArray(${tableVar}.id, ids))
|
|
3846
|
-
updateTag(CACHE_TAG)
|
|
3847
|
-
return { success: true }
|
|
3848
|
-
} catch (error) {
|
|
3849
|
-
console.error('Error deleting ${plural}:', error)
|
|
3850
|
-
return { success: false, error: error instanceof Error ? error.message : 'Failed to delete ${plural}' }
|
|
3851
|
-
}
|
|
3852
|
-
}
|
|
3853
|
-
|
|
3854
|
-
export async function update${Singular}SortOrder(
|
|
3855
|
-
id: number,
|
|
3856
|
-
direction: 'up' | 'down'
|
|
3857
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
3858
|
-
try {
|
|
3859
|
-
const current = await db.select({ id: ${tableVar}.id, sortOrder: ${tableVar}.sortOrder }).from(${tableVar}).where(eq(${tableVar}.id, id)).limit(1)
|
|
3860
|
-
if (current.length === 0) return { success: false, error: '${Singular} not found' }
|
|
3861
|
-
|
|
3862
|
-
const currentSortOrder = current[0].sortOrder
|
|
3863
|
-
const adjacent = await db
|
|
3864
|
-
.select({ id: ${tableVar}.id, sortOrder: ${tableVar}.sortOrder })
|
|
3865
|
-
.from(${tableVar})
|
|
3866
|
-
.where(direction === 'up' ? lt(${tableVar}.sortOrder, currentSortOrder) : gt(${tableVar}.sortOrder, currentSortOrder))
|
|
3867
|
-
.orderBy(direction === 'up' ? desc(${tableVar}.sortOrder) : asc(${tableVar}.sortOrder))
|
|
3868
|
-
.limit(1)
|
|
3869
|
-
|
|
3870
|
-
if (adjacent.length === 0) return { success: true }
|
|
3871
|
-
|
|
3872
|
-
await db.update(${tableVar}).set({ sortOrder: adjacent[0].sortOrder }).where(eq(${tableVar}.id, id))
|
|
3873
|
-
await db.update(${tableVar}).set({ sortOrder: currentSortOrder }).where(eq(${tableVar}.id, adjacent[0].id))
|
|
3874
|
-
updateTag(CACHE_TAG)
|
|
3875
|
-
return { success: true }
|
|
3876
|
-
} catch (error) {
|
|
3877
|
-
console.error('Error updating sort order:', error)
|
|
3878
|
-
return { success: false, error: error instanceof Error ? error.message : 'Failed to update sort order' }
|
|
3879
|
-
}
|
|
3880
|
-
}
|
|
3881
|
-
|
|
3882
|
-
export async function bulkUpdate${Plural}SortOrder(
|
|
3883
|
-
updates: Array<{ id: number; sortOrder: number }>
|
|
3884
|
-
): Promise<{ success: boolean; error?: string }> {
|
|
3885
|
-
try {
|
|
3886
|
-
if (updates.length === 0) return { success: true }
|
|
3887
|
-
await Promise.all(updates.map((u) => db.update(${tableVar}).set({ sortOrder: u.sortOrder }).where(eq(${tableVar}.id, u.id))))
|
|
3888
|
-
updateTag(CACHE_TAG)
|
|
3889
|
-
return { success: true }
|
|
3890
|
-
} catch (error) {
|
|
3891
|
-
console.error('Error bulk updating sort order:', error)
|
|
3892
|
-
return { success: false, error: error instanceof Error ? error.message : 'Failed to bulk update sort order' }
|
|
3893
|
-
}
|
|
3894
|
-
}${m2mHelpers}
|
|
3895
|
-
`;
|
|
3896
|
-
if (!fs12.existsSync(absActionsDir)) {
|
|
3897
|
-
fs12.mkdirSync(absActionsDir, { recursive: true });
|
|
3898
|
-
}
|
|
3899
|
-
fs12.writeFileSync(filePath, content, "utf-8");
|
|
3900
|
-
return { files: [path12.join(actionsDir, `${schema.name}.ts`)] };
|
|
4185
|
+
return {
|
|
4186
|
+
singular,
|
|
4187
|
+
plural,
|
|
4188
|
+
Singular,
|
|
4189
|
+
Plural,
|
|
4190
|
+
tableVar,
|
|
4191
|
+
camelSingular,
|
|
4192
|
+
camelPlural,
|
|
4193
|
+
cacheTag,
|
|
4194
|
+
hasRelationships,
|
|
4195
|
+
hasM2M,
|
|
4196
|
+
hasListRels,
|
|
4197
|
+
hasHtmlOutput,
|
|
4198
|
+
hasSlug,
|
|
4199
|
+
hasDraft,
|
|
4200
|
+
hasSearch,
|
|
4201
|
+
hasFilters,
|
|
4202
|
+
hasAutoSlug,
|
|
4203
|
+
searchFields,
|
|
4204
|
+
htmlOutputFields,
|
|
4205
|
+
m2mFields,
|
|
4206
|
+
filterableFields,
|
|
4207
|
+
relTableImports,
|
|
4208
|
+
listRelTableImports,
|
|
4209
|
+
dataInterface,
|
|
4210
|
+
displayDataInterface,
|
|
4211
|
+
responseInterface,
|
|
4212
|
+
filtersInterface,
|
|
4213
|
+
createInterface,
|
|
4214
|
+
updateInterface,
|
|
4215
|
+
selectClause,
|
|
4216
|
+
listSelectClause,
|
|
4217
|
+
displaySelectClause,
|
|
4218
|
+
filterConditions,
|
|
4219
|
+
searchBlock,
|
|
4220
|
+
resultMapping,
|
|
4221
|
+
listResultMapping,
|
|
4222
|
+
singleRowReturn,
|
|
4223
|
+
displaySingleRowReturn,
|
|
4224
|
+
fieldMeta,
|
|
4225
|
+
createMappings,
|
|
4226
|
+
htmlCreateBlock,
|
|
4227
|
+
htmlCreateMappings,
|
|
4228
|
+
autoSlugCreate,
|
|
4229
|
+
autoSlugUpdate,
|
|
4230
|
+
populateListRelsFn,
|
|
4231
|
+
filters: schema.filters || []
|
|
4232
|
+
};
|
|
3901
4233
|
}
|
|
3902
4234
|
|
|
3903
4235
|
// src/generators/actions/single-actions.ts
|
|
@@ -3905,21 +4237,27 @@ import fs13 from "fs";
|
|
|
3905
4237
|
import path13 from "path";
|
|
3906
4238
|
function generateSingleActions(schema, cwd, actionsDir, options = {}) {
|
|
3907
4239
|
const absActionsDir = path13.join(cwd, actionsDir);
|
|
3908
|
-
const
|
|
3909
|
-
|
|
4240
|
+
const dirPath = path13.join(absActionsDir, schema.name);
|
|
4241
|
+
const oldFilePath = path13.join(absActionsDir, `${schema.name}.ts`);
|
|
4242
|
+
if (!options.force && (fs13.existsSync(dirPath) || fs13.existsSync(oldFilePath))) {
|
|
3910
4243
|
return { files: [] };
|
|
3911
4244
|
}
|
|
4245
|
+
if (options.force) {
|
|
4246
|
+
if (fs13.existsSync(oldFilePath)) fs13.unlinkSync(oldFilePath);
|
|
4247
|
+
if (fs13.existsSync(dirPath)) fs13.rmSync(dirPath, { recursive: true });
|
|
4248
|
+
}
|
|
3912
4249
|
const singular = singularize(schema.name);
|
|
3913
4250
|
const Singular = toPascalCase(singular);
|
|
3914
4251
|
const tableVar = toCamelCase(schema.name);
|
|
3915
4252
|
const camelSingular = toCamelCase(singular);
|
|
4253
|
+
const kebabSingular = toKebabCase(singular);
|
|
3916
4254
|
const dbFields = flattenFields(schema.fields).filter(
|
|
3917
4255
|
(f) => !(f.type === "relationship" && f.multiple === true)
|
|
3918
4256
|
);
|
|
3919
|
-
const
|
|
4257
|
+
const htmlOutputFields = dbFields.filter(
|
|
3920
4258
|
(f) => (f.type === "richtext" || f.type === "markdown") && f.output === "html"
|
|
3921
4259
|
);
|
|
3922
|
-
const
|
|
4260
|
+
const hasHtmlOutput = htmlOutputFields.length > 0;
|
|
3923
4261
|
const allDbFields = [...dbFields];
|
|
3924
4262
|
if (!dbFields.some((f) => f.name === "createdAt")) {
|
|
3925
4263
|
allDbFields.push({ name: "createdAt", type: "timestamp", required: true });
|
|
@@ -3933,39 +4271,34 @@ function generateSingleActions(schema, cwd, actionsDir, options = {}) {
|
|
|
3933
4271
|
const dataFields = allDbFields.map(
|
|
3934
4272
|
(f) => ` ${quotePropertyName(f.name)}: ${getFieldType(f, "output")}${f.required ? "" : " | null"}`
|
|
3935
4273
|
).join("\n");
|
|
3936
|
-
const
|
|
3937
|
-
const dataInterface = `export interface ${Singular}Data {
|
|
3938
|
-
${dataFields}${singleHtmlFieldTypes ? `
|
|
3939
|
-
${singleHtmlFieldTypes}` : ""}
|
|
3940
|
-
}`;
|
|
4274
|
+
const htmlFieldTypes = htmlOutputFields.map((f) => ` ${f.name}Html: string`).join("\n");
|
|
3941
4275
|
const upsertInterfaceFields = upsertFields.map(
|
|
3942
4276
|
(f) => ` ${quotePropertyName(f.name)}${f.required ? "" : "?"}: ${getFieldType(f, "input")}`
|
|
3943
4277
|
).join("\n");
|
|
3944
|
-
const upsertInterface = `export interface Upsert${Singular}Input {
|
|
3945
|
-
${upsertInterfaceFields}
|
|
3946
|
-
}`;
|
|
3947
|
-
const upsertMappings = upsertFields.map((f) => ` ${f.name}: ${generateFieldMapping(f)}`).join(",\n");
|
|
3948
4278
|
const fieldMeta = upsertFields.map((f) => `{ name: '${f.name}', type: '${f.type}', required: ${f.required ?? false} }`).join(",\n ");
|
|
4279
|
+
const upsertMappings = upsertFields.map((f) => ` ${f.name}: ${generateFieldMapping(f)}`).join(",\n");
|
|
3949
4280
|
const cacheTag = `${schema.name}:all`;
|
|
3950
|
-
const
|
|
3951
|
-
|
|
4281
|
+
const typesContent = [
|
|
4282
|
+
`export interface ${Singular}Data {
|
|
4283
|
+
${dataFields}${htmlFieldTypes ? `
|
|
4284
|
+
${htmlFieldTypes}` : ""}
|
|
4285
|
+
}`,
|
|
4286
|
+
`export interface Upsert${Singular}Input {
|
|
4287
|
+
${upsertInterfaceFields}
|
|
4288
|
+
}`,
|
|
4289
|
+
`export interface Upsert${Singular}Result {
|
|
4290
|
+
success: boolean
|
|
4291
|
+
error?: string
|
|
4292
|
+
${camelSingular}?: ${Singular}Data
|
|
4293
|
+
}`,
|
|
4294
|
+
`export const CACHE_TAG = '${cacheTag}'`
|
|
4295
|
+
].join("\n\n") + "\n";
|
|
4296
|
+
const getContent = `'use server'
|
|
3952
4297
|
|
|
3953
4298
|
import db from '@cms/db'
|
|
3954
4299
|
import { ${tableVar} } from '@cms/db/schema'
|
|
3955
4300
|
import { eq } from 'drizzle-orm'
|
|
3956
|
-
import {
|
|
3957
|
-
|
|
3958
|
-
const CACHE_TAG = '${cacheTag}'
|
|
3959
|
-
|
|
3960
|
-
${dataInterface}
|
|
3961
|
-
|
|
3962
|
-
${upsertInterface}
|
|
3963
|
-
|
|
3964
|
-
export interface Upsert${Singular}Result {
|
|
3965
|
-
success: boolean
|
|
3966
|
-
error?: string
|
|
3967
|
-
${camelSingular}?: ${Singular}Data
|
|
3968
|
-
}
|
|
4301
|
+
import type { ${Singular}Data } from './types'
|
|
3969
4302
|
|
|
3970
4303
|
export async function get${Singular}(): Promise<${Singular}Data | null> {
|
|
3971
4304
|
try {
|
|
@@ -3977,6 +4310,20 @@ export async function get${Singular}(): Promise<${Singular}Data | null> {
|
|
|
3977
4310
|
return null
|
|
3978
4311
|
}
|
|
3979
4312
|
}
|
|
4313
|
+
`;
|
|
4314
|
+
const htmlUpsertBlock = hasHtmlOutput ? `
|
|
4315
|
+
const { renderMarkdownSync } = await import('@cms/lib/markdown/render')
|
|
4316
|
+
` + htmlOutputFields.map((f) => ` if (processedData.${f.name} !== undefined) {
|
|
4317
|
+
processedData.${f.name}Html = renderMarkdownSync(String(processedData.${f.name} || ''))
|
|
4318
|
+
}`).join("\n") + "\n" + htmlOutputFields.map((f) => ` const ${f.name}Html = renderMarkdownSync(input.${f.name} || '')`).join("\n") + "\n" : "";
|
|
4319
|
+
const htmlUpsertMappings = hasHtmlOutput ? "\n" + htmlOutputFields.map((f) => ` ${f.name}Html`).join(",\n") + "," : "";
|
|
4320
|
+
const upsertContent = `'use server'
|
|
4321
|
+
|
|
4322
|
+
import db from '@cms/db'
|
|
4323
|
+
import { ${tableVar} } from '@cms/db/schema'
|
|
4324
|
+
import { updateTag } from 'next/cache'
|
|
4325
|
+
import { CACHE_TAG } from './types'
|
|
4326
|
+
import type { Upsert${Singular}Input, Upsert${Singular}Result, ${Singular}Data } from './types'
|
|
3980
4327
|
|
|
3981
4328
|
export async function upsert${Singular}(input: Upsert${Singular}Input): Promise<Upsert${Singular}Result> {
|
|
3982
4329
|
try {
|
|
@@ -3996,18 +4343,14 @@ export async function upsert${Singular}(input: Upsert${Singular}Input): Promise<
|
|
|
3996
4343
|
processedData[key] = value
|
|
3997
4344
|
}
|
|
3998
4345
|
}
|
|
3999
|
-
${
|
|
4000
|
-
const { renderMarkdownSync } = await import('@cms/lib/markdown/render')
|
|
4001
|
-
` + singleHtmlOutputFields.map((f) => ` if (processedData.${f.name} !== undefined) {
|
|
4002
|
-
processedData.${f.name}Html = renderMarkdownSync(String(processedData.${f.name} || ''))
|
|
4003
|
-
}`).join("\n") + "\n" + singleHtmlOutputFields.map((f) => ` const ${f.name}Html = renderMarkdownSync(input.${f.name} || '')`).join("\n") + "\n" : ""}
|
|
4346
|
+
${htmlUpsertBlock}
|
|
4004
4347
|
const now = new Date().toISOString()
|
|
4005
4348
|
|
|
4006
4349
|
const result = await db
|
|
4007
4350
|
.insert(${tableVar})
|
|
4008
4351
|
.values({
|
|
4009
4352
|
id: 1,
|
|
4010
|
-
${upsertMappings},${
|
|
4353
|
+
${upsertMappings},${htmlUpsertMappings}
|
|
4011
4354
|
createdAt: now,
|
|
4012
4355
|
updatedAt: now
|
|
4013
4356
|
})
|
|
@@ -4029,11 +4372,21 @@ ${upsertMappings},${singleHasHtmlOutput ? "\n" + singleHtmlOutputFields.map((f)
|
|
|
4029
4372
|
}
|
|
4030
4373
|
}
|
|
4031
4374
|
`;
|
|
4032
|
-
|
|
4033
|
-
|
|
4375
|
+
const barrelContent = `export type { ${Singular}Data, Upsert${Singular}Input, Upsert${Singular}Result } from './types'
|
|
4376
|
+
export { get${Singular} } from './get-${kebabSingular}'
|
|
4377
|
+
export { upsert${Singular} } from './upsert-${kebabSingular}'
|
|
4378
|
+
`;
|
|
4379
|
+
const files = [
|
|
4380
|
+
{ name: "types.ts", content: typesContent },
|
|
4381
|
+
{ name: `get-${kebabSingular}.ts`, content: getContent },
|
|
4382
|
+
{ name: `upsert-${kebabSingular}.ts`, content: upsertContent },
|
|
4383
|
+
{ name: "index.ts", content: barrelContent }
|
|
4384
|
+
];
|
|
4385
|
+
fs13.mkdirSync(dirPath, { recursive: true });
|
|
4386
|
+
for (const file of files) {
|
|
4387
|
+
fs13.writeFileSync(path13.join(dirPath, file.name), file.content, "utf-8");
|
|
4034
4388
|
}
|
|
4035
|
-
|
|
4036
|
-
return { files: [path13.join(actionsDir, `${schema.name}.ts`)] };
|
|
4389
|
+
return { files: files.map((f) => path13.join(actionsDir, schema.name, f.name)) };
|
|
4037
4390
|
}
|
|
4038
4391
|
|
|
4039
4392
|
// src/generators/cache.ts
|
|
@@ -4106,7 +4459,7 @@ ${entries.join(",\n")}
|
|
|
4106
4459
|
}
|
|
4107
4460
|
function generateCachedQueries(configs) {
|
|
4108
4461
|
const actionImports = [];
|
|
4109
|
-
const
|
|
4462
|
+
const typeImportsBySchema = /* @__PURE__ */ new Map();
|
|
4110
4463
|
const fns = [];
|
|
4111
4464
|
for (const c of configs) {
|
|
4112
4465
|
if (c.isSingle) {
|
|
@@ -4120,7 +4473,9 @@ export const getCached${c.pascalSingular} = async () => {
|
|
|
4120
4473
|
continue;
|
|
4121
4474
|
}
|
|
4122
4475
|
actionImports.push(`get${c.pascalPlural}`);
|
|
4123
|
-
|
|
4476
|
+
const types = typeImportsBySchema.get(c.name) || [];
|
|
4477
|
+
types.push(`Get${c.pascalPlural}Filters`);
|
|
4478
|
+
typeImportsBySchema.set(c.name, types);
|
|
4124
4479
|
if (c.hasSlug) actionImports.push(`get${c.pascalSingular}BySlug`);
|
|
4125
4480
|
for (const n of c.nestedLookups) actionImports.push(`get${n.pascalName}BySlug`);
|
|
4126
4481
|
fns.push(`
|
|
@@ -4149,11 +4504,7 @@ export const getCached${n.pascalName}BySlug = async (${c.singularName}Slug: stri
|
|
|
4149
4504
|
}
|
|
4150
4505
|
}
|
|
4151
4506
|
actionImports.sort();
|
|
4152
|
-
|
|
4153
|
-
const typeImportStr = typeImports.length > 0 ? `${typeImports.map((t) => {
|
|
4154
|
-
const schemaName = t.replace(/^Get/, "").replace(/Filters$/, "").toLowerCase();
|
|
4155
|
-
return `import type { ${t} } from '@cms/actions/${schemaName}'`;
|
|
4156
|
-
}).join("\n")}
|
|
4507
|
+
const typeImportStr = typeImportsBySchema.size > 0 ? `${Array.from(typeImportsBySchema.entries()).map(([schemaName, types]) => `import type { ${types.sort().join(", ")} } from '@cms/actions/${schemaName}'`).join("\n")}
|
|
4157
4508
|
` : "";
|
|
4158
4509
|
const actionsBySchema = /* @__PURE__ */ new Map();
|
|
4159
4510
|
for (const c of configs) {
|
|
@@ -12200,6 +12551,7 @@ var CORE_DEPS = [
|
|
|
12200
12551
|
"input-otp",
|
|
12201
12552
|
"react-resizable-panels",
|
|
12202
12553
|
"recharts",
|
|
12554
|
+
"shadcn",
|
|
12203
12555
|
"tw-animate-css",
|
|
12204
12556
|
"usehooks-ts",
|
|
12205
12557
|
"vaul"
|
|
@@ -14942,8 +15294,15 @@ var removeCommand = new Command4("remove").alias("rm").description("Remove all g
|
|
|
14942
15294
|
isDir: true
|
|
14943
15295
|
});
|
|
14944
15296
|
}
|
|
15297
|
+
const actionsDir = path46.join(cwd, cmsDir, "lib", "actions", kebabName);
|
|
14945
15298
|
const actionsFile = path46.join(cwd, cmsDir, "lib", "actions", `${kebabName}.ts`);
|
|
14946
|
-
if (fs41.existsSync(
|
|
15299
|
+
if (fs41.existsSync(actionsDir)) {
|
|
15300
|
+
targets.push({
|
|
15301
|
+
path: actionsDir,
|
|
15302
|
+
label: `${path46.join(cmsDir, "lib", "actions", kebabName)}/`,
|
|
15303
|
+
isDir: true
|
|
15304
|
+
});
|
|
15305
|
+
} else if (fs41.existsSync(actionsFile)) {
|
|
14947
15306
|
targets.push({
|
|
14948
15307
|
path: actionsFile,
|
|
14949
15308
|
label: path46.join(cmsDir, "lib", "actions", `${kebabName}.ts`),
|