@betterstart/cli 0.1.31 → 0.1.33
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 +144 -47
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2723,6 +2723,10 @@ function generateActions(schema, cwd, actionsDir, options = {}) {
|
|
|
2723
2723
|
}
|
|
2724
2724
|
}
|
|
2725
2725
|
const populateListRelsFn = hasListRels ? generatePopulateListRelsFunction(allListRelQueries, Singular) : "";
|
|
2726
|
+
const htmlOutputFields = regularDbFields.filter(
|
|
2727
|
+
(f) => (f.type === "richtext" || f.type === "markdown") && f.output === "html"
|
|
2728
|
+
);
|
|
2729
|
+
const hasHtmlOutput = htmlOutputFields.length > 0;
|
|
2726
2730
|
const hasSlug = regularDbFields.some((f) => f.name === "slug");
|
|
2727
2731
|
const hasDraft = schema.actions?.draft === true;
|
|
2728
2732
|
const hasPublished = regularDbFields.some((f) => f.name === "published");
|
|
@@ -2769,8 +2773,10 @@ function generateActions(schema, cwd, actionsDir, options = {}) {
|
|
|
2769
2773
|
(f) => ` ${quotePropertyName2(f.name)}: ${getFieldType(f, "output")}${f.required ? "" : " | null"}`
|
|
2770
2774
|
).join("\n");
|
|
2771
2775
|
const m2mFieldTypes = m2mFields.map((f) => ` ${quotePropertyName2(f.name)}: number[]`).join("\n");
|
|
2776
|
+
const htmlFieldTypes = htmlOutputFields.map((f) => ` ${f.name}Html: string`).join("\n");
|
|
2772
2777
|
const dataInterface = `export interface ${Singular}Data {
|
|
2773
|
-
${dataFields}${
|
|
2778
|
+
${dataFields}${htmlFieldTypes ? `
|
|
2779
|
+
${htmlFieldTypes}` : ""}${m2mFieldTypes ? `
|
|
2774
2780
|
${m2mFieldTypes}` : ""}
|
|
2775
2781
|
}`;
|
|
2776
2782
|
const responseInterface = `export interface ${Plural}Response {
|
|
@@ -2837,6 +2843,10 @@ ${searchFields.map((f) => ` ilike(${tableVar}.${f}, searchTerm)`).join(
|
|
|
2837
2843
|
}`;
|
|
2838
2844
|
const fieldMeta = createFields.map((f) => `{ name: '${f.name}', type: '${f.type}', required: ${f.required ?? false} }`).join(",\n ");
|
|
2839
2845
|
const createMappings = createFields.map((f) => ` ${f.name}: ${generateFieldMapping(f)}`).join(",\n");
|
|
2846
|
+
const htmlCreateBlock = hasHtmlOutput ? `
|
|
2847
|
+
const { renderMarkdownSync } = await import('@cms/lib/markdown/render')
|
|
2848
|
+
` + htmlOutputFields.map((f) => ` const ${f.name}Html = renderMarkdownSync(input.${f.name} || '')`).join("\n") + "\n" : "";
|
|
2849
|
+
const htmlCreateMappings = htmlOutputFields.map((f) => ` ${f.name}Html`).join(",\n");
|
|
2840
2850
|
const distinctFns = hasFilters ? (schema.filters || []).map(
|
|
2841
2851
|
(filter) => `
|
|
2842
2852
|
export async function getDistinct${Plural}${toPascalCase5(filter.field)}(): Promise<string[]> {
|
|
@@ -3004,9 +3014,10 @@ export async function create${Singular}(input: Create${Singular}Input): Promise<
|
|
|
3004
3014
|
.orderBy(desc(${tableVar}.sortOrder))
|
|
3005
3015
|
.limit(1)
|
|
3006
3016
|
const nextSortOrder = (maxSortOrderResult[0]?.maxOrder ?? 0) + 1
|
|
3007
|
-
|
|
3017
|
+
${htmlCreateBlock}
|
|
3008
3018
|
const result = await db.insert(${tableVar}).values({
|
|
3009
|
-
${createMappings},${
|
|
3019
|
+
${createMappings},${hasHtmlOutput ? `
|
|
3020
|
+
${htmlCreateMappings},` : ""}${hasDraft ? `
|
|
3010
3021
|
published: input.published ?? false,` : ""}
|
|
3011
3022
|
sortOrder: nextSortOrder,
|
|
3012
3023
|
createdAt: new Date().toISOString(),
|
|
@@ -3046,7 +3057,11 @@ ${autoSlugUpdate}
|
|
|
3046
3057
|
processedData[key] = value
|
|
3047
3058
|
}
|
|
3048
3059
|
}
|
|
3049
|
-
|
|
3060
|
+
${hasHtmlOutput ? `
|
|
3061
|
+
const { renderMarkdownSync } = await import('@cms/lib/markdown/render')
|
|
3062
|
+
` + htmlOutputFields.map((f) => ` if (processedData.${f.name} !== undefined) {
|
|
3063
|
+
processedData.${f.name}Html = renderMarkdownSync(String(processedData.${f.name} || ''))
|
|
3064
|
+
}`).join("\n") + "\n" : ""}
|
|
3050
3065
|
const result = await db.update(${tableVar})
|
|
3051
3066
|
.set({ ...processedData, updatedAt: new Date().toISOString() })
|
|
3052
3067
|
.where(eq(${tableVar}.id, id))
|
|
@@ -3150,6 +3165,10 @@ function generateSingleActions(schema, cwd, actionsDir, options = {}) {
|
|
|
3150
3165
|
const dbFields = flattenFields(schema.fields).filter(
|
|
3151
3166
|
(f) => !(f.type === "relationship" && f.multiple === true)
|
|
3152
3167
|
);
|
|
3168
|
+
const singleHtmlOutputFields = dbFields.filter(
|
|
3169
|
+
(f) => (f.type === "richtext" || f.type === "markdown") && f.output === "html"
|
|
3170
|
+
);
|
|
3171
|
+
const singleHasHtmlOutput = singleHtmlOutputFields.length > 0;
|
|
3153
3172
|
const allDbFields = [...dbFields];
|
|
3154
3173
|
if (!dbFields.some((f) => f.name === "createdAt")) {
|
|
3155
3174
|
allDbFields.push({ name: "createdAt", type: "timestamp", required: true });
|
|
@@ -3163,8 +3182,10 @@ function generateSingleActions(schema, cwd, actionsDir, options = {}) {
|
|
|
3163
3182
|
const dataFields = allDbFields.map(
|
|
3164
3183
|
(f) => ` ${quotePropertyName2(f.name)}: ${getFieldType(f, "output")}${f.required ? "" : " | null"}`
|
|
3165
3184
|
).join("\n");
|
|
3185
|
+
const singleHtmlFieldTypes = singleHtmlOutputFields.map((f) => ` ${f.name}Html: string`).join("\n");
|
|
3166
3186
|
const dataInterface = `export interface ${Singular}Data {
|
|
3167
|
-
${dataFields}
|
|
3187
|
+
${dataFields}${singleHtmlFieldTypes ? `
|
|
3188
|
+
${singleHtmlFieldTypes}` : ""}
|
|
3168
3189
|
}`;
|
|
3169
3190
|
const upsertInterfaceFields = upsertFields.map(
|
|
3170
3191
|
(f) => ` ${quotePropertyName2(f.name)}${f.required ? "" : "?"}: ${getFieldType(f, "input")}`
|
|
@@ -3175,6 +3196,7 @@ ${upsertInterfaceFields}
|
|
|
3175
3196
|
const upsertMappings = upsertFields.map((f) => ` ${f.name}: ${generateFieldMapping(f)}`).join(",\n");
|
|
3176
3197
|
const fieldMeta = upsertFields.map((f) => `{ name: '${f.name}', type: '${f.type}', required: ${f.required ?? false} }`).join(",\n ");
|
|
3177
3198
|
const cacheTag = `${schema.name}:all`;
|
|
3199
|
+
const singleHtmlUpsertMappings = singleHtmlOutputFields.map((f) => ` ${f.name}Html`).join(",\n");
|
|
3178
3200
|
const content = `'use server'
|
|
3179
3201
|
|
|
3180
3202
|
import db from '@cms/db'
|
|
@@ -3223,14 +3245,18 @@ export async function upsert${Singular}(input: Upsert${Singular}Input): Promise<
|
|
|
3223
3245
|
processedData[key] = value
|
|
3224
3246
|
}
|
|
3225
3247
|
}
|
|
3226
|
-
|
|
3248
|
+
${singleHasHtmlOutput ? `
|
|
3249
|
+
const { renderMarkdownSync } = await import('@cms/lib/markdown/render')
|
|
3250
|
+
` + singleHtmlOutputFields.map((f) => ` if (processedData.${f.name} !== undefined) {
|
|
3251
|
+
processedData.${f.name}Html = renderMarkdownSync(String(processedData.${f.name} || ''))
|
|
3252
|
+
}`).join("\n") + "\n" + singleHtmlOutputFields.map((f) => ` const ${f.name}Html = renderMarkdownSync(input.${f.name} || '')`).join("\n") + "\n" : ""}
|
|
3227
3253
|
const now = new Date().toISOString()
|
|
3228
3254
|
|
|
3229
3255
|
const result = await db
|
|
3230
3256
|
.insert(${tableVar})
|
|
3231
3257
|
.values({
|
|
3232
3258
|
id: 1,
|
|
3233
|
-
${upsertMappings},
|
|
3259
|
+
${upsertMappings},${singleHasHtmlOutput ? "\n" + singleHtmlOutputFields.map((f) => ` ${f.name}Html`).join(",\n") + "," : ""}
|
|
3234
3260
|
createdAt: now,
|
|
3235
3261
|
updatedAt: now
|
|
3236
3262
|
})
|
|
@@ -4553,7 +4579,14 @@ function generateTableDefinition(schema, requiredImports, needsSql) {
|
|
|
4553
4579
|
const fieldDefs = dbFields.map((field) => {
|
|
4554
4580
|
const drizzleType = toDrizzleType(field, requiredImports);
|
|
4555
4581
|
const modifiers = getFieldModifiers(field, needsSql);
|
|
4556
|
-
|
|
4582
|
+
const line = ` ${field.name}: ${drizzleType}${modifiers}`;
|
|
4583
|
+
if ((field.type === "richtext" || field.type === "markdown") && field.output === "html") {
|
|
4584
|
+
const snakeName = field.name.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
4585
|
+
requiredImports.add("text");
|
|
4586
|
+
return `${line},
|
|
4587
|
+
${field.name}Html: text('${snakeName}_html')`;
|
|
4588
|
+
}
|
|
4589
|
+
return line;
|
|
4557
4590
|
}).join(",\n");
|
|
4558
4591
|
let publishedField = "";
|
|
4559
4592
|
if (hasDraftMode && !hasPublishedField) {
|
|
@@ -7550,23 +7583,28 @@ var LOCKFILE_MAP = {
|
|
|
7550
7583
|
"bun.lock": "bun"
|
|
7551
7584
|
};
|
|
7552
7585
|
function detectPackageManager(cwd) {
|
|
7553
|
-
|
|
7554
|
-
|
|
7555
|
-
|
|
7586
|
+
let dir = path20.resolve(cwd);
|
|
7587
|
+
const root = path20.parse(dir).root;
|
|
7588
|
+
while (dir !== root) {
|
|
7589
|
+
for (const [lockfile, pm] of Object.entries(LOCKFILE_MAP)) {
|
|
7590
|
+
if (fs20.existsSync(path20.join(dir, lockfile))) {
|
|
7591
|
+
return pm;
|
|
7592
|
+
}
|
|
7556
7593
|
}
|
|
7557
|
-
|
|
7558
|
-
|
|
7559
|
-
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
|
|
7564
|
-
|
|
7565
|
-
|
|
7594
|
+
const pkgPath = path20.join(dir, "package.json");
|
|
7595
|
+
if (fs20.existsSync(pkgPath)) {
|
|
7596
|
+
try {
|
|
7597
|
+
const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
|
|
7598
|
+
if (typeof pkg.packageManager === "string") {
|
|
7599
|
+
const name = pkg.packageManager.split("@")[0];
|
|
7600
|
+
if (name === "pnpm" || name === "npm" || name === "yarn" || name === "bun") {
|
|
7601
|
+
return name;
|
|
7602
|
+
}
|
|
7566
7603
|
}
|
|
7604
|
+
} catch {
|
|
7567
7605
|
}
|
|
7568
|
-
} catch {
|
|
7569
7606
|
}
|
|
7607
|
+
dir = path20.dirname(dir);
|
|
7570
7608
|
}
|
|
7571
7609
|
return "npm";
|
|
7572
7610
|
}
|
|
@@ -10580,7 +10618,6 @@ function markdownRenderTemplate() {
|
|
|
10580
10618
|
return `import { transformerNotationDiff, transformerNotationHighlight } from '@shikijs/transformers'
|
|
10581
10619
|
import { renderToString } from 'katex'
|
|
10582
10620
|
import MarkdownIt from 'markdown-it'
|
|
10583
|
-
import dollarmath from 'markdown-it-dollarmath'
|
|
10584
10621
|
import { createHighlighterCoreSync } from 'shiki/core'
|
|
10585
10622
|
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
|
|
10586
10623
|
import css from 'shiki/langs/css.mjs'
|
|
@@ -10668,6 +10705,68 @@ function headingAnchorPlugin(md: MarkdownIt) {
|
|
|
10668
10705
|
})
|
|
10669
10706
|
}
|
|
10670
10707
|
|
|
10708
|
+
// --- Inline KaTeX math plugin (replaces markdown-it-dollarmath) ---
|
|
10709
|
+
|
|
10710
|
+
function mathPlugin(md: MarkdownIt) {
|
|
10711
|
+
// Inline: $...$
|
|
10712
|
+
md.inline.ruler.after('escape', 'math_inline', (state, silent) => {
|
|
10713
|
+
if (state.src.charCodeAt(state.pos) !== 0x24) return false
|
|
10714
|
+
if (state.src.charCodeAt(state.pos + 1) === 0x24) return false
|
|
10715
|
+
const start = state.pos + 1
|
|
10716
|
+
let end = start
|
|
10717
|
+
while (end < state.posMax && state.src.charCodeAt(end) !== 0x24) {
|
|
10718
|
+
if (state.src.charCodeAt(end) === 0x5C) end++
|
|
10719
|
+
end++
|
|
10720
|
+
}
|
|
10721
|
+
if (end >= state.posMax) return false
|
|
10722
|
+
const content = state.src.slice(start, end).trim()
|
|
10723
|
+
if (!content) return false
|
|
10724
|
+
if (!silent) {
|
|
10725
|
+
const token = state.push('math_inline', 'math', 0)
|
|
10726
|
+
token.content = content
|
|
10727
|
+
token.markup = '$'
|
|
10728
|
+
}
|
|
10729
|
+
state.pos = end + 1
|
|
10730
|
+
return true
|
|
10731
|
+
})
|
|
10732
|
+
|
|
10733
|
+
md.renderer.rules.math_inline = (tokens, idx) => {
|
|
10734
|
+
return renderToString(tokens[idx].content, { displayMode: false, throwOnError: false, strict: 'ignore' })
|
|
10735
|
+
}
|
|
10736
|
+
|
|
10737
|
+
// Block: $$...$$
|
|
10738
|
+
md.block.ruler.after('blockquote', 'math_block', (state, startLine, endLine, silent) => {
|
|
10739
|
+
const startPos = state.bMarks[startLine] + state.tShift[startLine]
|
|
10740
|
+
if (state.src.charCodeAt(startPos) !== 0x24 || state.src.charCodeAt(startPos + 1) !== 0x24) return false
|
|
10741
|
+
if (silent) return true
|
|
10742
|
+
let nextLine = startLine
|
|
10743
|
+
let found = false
|
|
10744
|
+
while (nextLine < endLine) {
|
|
10745
|
+
nextLine++
|
|
10746
|
+
if (nextLine >= endLine) break
|
|
10747
|
+
const pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
|
10748
|
+
const max = state.eMarks[nextLine]
|
|
10749
|
+
const line = state.src.slice(pos, max).trim()
|
|
10750
|
+
if (line === '$$') { found = true; break }
|
|
10751
|
+
}
|
|
10752
|
+
if (!found) return false
|
|
10753
|
+
const contentLines: string[] = []
|
|
10754
|
+
for (let i = startLine + 1; i < nextLine; i++) {
|
|
10755
|
+
contentLines.push(state.src.slice(state.bMarks[i] + state.tShift[i], state.eMarks[i]))
|
|
10756
|
+
}
|
|
10757
|
+
const token = state.push('math_block', 'math', 0)
|
|
10758
|
+
token.content = contentLines.join('\\n').trim()
|
|
10759
|
+
token.markup = '$$'
|
|
10760
|
+
token.map = [startLine, nextLine + 1]
|
|
10761
|
+
state.line = nextLine + 1
|
|
10762
|
+
return true
|
|
10763
|
+
})
|
|
10764
|
+
|
|
10765
|
+
md.renderer.rules.math_block = (tokens, idx) => {
|
|
10766
|
+
return '<div class="math-display">' + renderToString(tokens[idx].content, { displayMode: true, throwOnError: false, strict: 'ignore' }) + '</div>\\n'
|
|
10767
|
+
}
|
|
10768
|
+
}
|
|
10769
|
+
|
|
10671
10770
|
// --- Markdown-it Instance ---
|
|
10672
10771
|
|
|
10673
10772
|
const md = MarkdownIt({
|
|
@@ -10688,26 +10787,7 @@ const md = MarkdownIt({
|
|
|
10688
10787
|
},
|
|
10689
10788
|
})
|
|
10690
10789
|
.use(headingAnchorPlugin)
|
|
10691
|
-
.use(
|
|
10692
|
-
allow_space: false,
|
|
10693
|
-
allow_digits: false,
|
|
10694
|
-
double_inline: true,
|
|
10695
|
-
allow_labels: true,
|
|
10696
|
-
allow_blank_lines: true,
|
|
10697
|
-
renderer(content: string, { displayMode }: { displayMode: boolean }) {
|
|
10698
|
-
return renderToString(content, {
|
|
10699
|
-
displayMode,
|
|
10700
|
-
throwOnError: false,
|
|
10701
|
-
strict: 'ignore',
|
|
10702
|
-
})
|
|
10703
|
-
},
|
|
10704
|
-
labelNormalizer(label: string) {
|
|
10705
|
-
return label.replace(/[\\s]+/g, '-')
|
|
10706
|
-
},
|
|
10707
|
-
labelRenderer(label: string) {
|
|
10708
|
-
return \`<a href="#\${label}" class="mathlabel" title="Permalink to this equation">\xB6</a>\`
|
|
10709
|
-
},
|
|
10710
|
-
})
|
|
10790
|
+
.use(mathPlugin)
|
|
10711
10791
|
|
|
10712
10792
|
export function renderMarkdownSync(src: string): string {
|
|
10713
10793
|
const trimmedContent = trimMathBlock(src)
|
|
@@ -11560,7 +11640,6 @@ var CORE_DEPS = [
|
|
|
11560
11640
|
"@shikijs/transformers",
|
|
11561
11641
|
"katex",
|
|
11562
11642
|
"markdown-it",
|
|
11563
|
-
"markdown-it-dollarmath",
|
|
11564
11643
|
// Drag-and-drop
|
|
11565
11644
|
"@dnd-kit/core",
|
|
11566
11645
|
"@dnd-kit/sortable",
|
|
@@ -11632,6 +11711,8 @@ async function installDependenciesAsync({
|
|
|
11632
11711
|
|
|
11633
11712
|
// src/init/scaffolders/env.ts
|
|
11634
11713
|
import crypto from "crypto";
|
|
11714
|
+
import { existsSync, readFileSync } from "fs";
|
|
11715
|
+
import { join } from "path";
|
|
11635
11716
|
|
|
11636
11717
|
// src/utils/env.ts
|
|
11637
11718
|
import fs27 from "fs";
|
|
@@ -11688,7 +11769,22 @@ ${lines.join("\n")}` : header + lines.join("\n");
|
|
|
11688
11769
|
}
|
|
11689
11770
|
|
|
11690
11771
|
// src/init/scaffolders/env.ts
|
|
11691
|
-
function
|
|
11772
|
+
function detectDevPort(cwd) {
|
|
11773
|
+
const pkgPath = join(cwd, "package.json");
|
|
11774
|
+
if (!existsSync(pkgPath)) return 3e3;
|
|
11775
|
+
try {
|
|
11776
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
11777
|
+
const devScript = pkg.scripts?.dev ?? "";
|
|
11778
|
+
const match = devScript.match(/--port\s+(\d+)|-p\s+(\d+)/);
|
|
11779
|
+
if (match) {
|
|
11780
|
+
const port = Number.parseInt(match[1] ?? match[2], 10);
|
|
11781
|
+
if (port > 0 && port <= 65535) return port;
|
|
11782
|
+
}
|
|
11783
|
+
} catch {
|
|
11784
|
+
}
|
|
11785
|
+
return 3e3;
|
|
11786
|
+
}
|
|
11787
|
+
function getCoreEnvSections(databaseUrl, devPort) {
|
|
11692
11788
|
const authSecret = crypto.randomBytes(32).toString("base64");
|
|
11693
11789
|
return [
|
|
11694
11790
|
{
|
|
@@ -11699,7 +11795,7 @@ function getCoreEnvSections(databaseUrl) {
|
|
|
11699
11795
|
header: "Authentication",
|
|
11700
11796
|
vars: [
|
|
11701
11797
|
{ key: "BETTERSTART_AUTH_SECRET", value: authSecret },
|
|
11702
|
-
{ key: "BETTERSTART_AUTH_URL", value:
|
|
11798
|
+
{ key: "BETTERSTART_AUTH_URL", value: `http://localhost:${devPort}` },
|
|
11703
11799
|
{ key: "BETTERSTART_AUTH_BASE_PATH", value: "/api/cms/auth" }
|
|
11704
11800
|
]
|
|
11705
11801
|
},
|
|
@@ -11725,7 +11821,8 @@ function getEmailEnvSection() {
|
|
|
11725
11821
|
};
|
|
11726
11822
|
}
|
|
11727
11823
|
function scaffoldEnv(cwd, options) {
|
|
11728
|
-
const
|
|
11824
|
+
const devPort = detectDevPort(cwd);
|
|
11825
|
+
const sections = getCoreEnvSections(options.databaseUrl, devPort);
|
|
11729
11826
|
if (options.includeEmail) {
|
|
11730
11827
|
sections.push(getEmailEnvSection());
|
|
11731
11828
|
}
|
|
@@ -13954,7 +14051,7 @@ Run manually: ${pc2.cyan("npx betterstart seed")}`,
|
|
|
13954
14051
|
"",
|
|
13955
14052
|
`Admin: ${pc2.cyan(seedEmail)}`,
|
|
13956
14053
|
`Password: ${pc2.cyan(seedPassword)}`,
|
|
13957
|
-
`CMS: ${pc2.cyan(
|
|
14054
|
+
`CMS: ${pc2.cyan(`http://localhost:${detectDevPort(cwd)}/cms/login`)}`
|
|
13958
14055
|
);
|
|
13959
14056
|
}
|
|
13960
14057
|
const nextSteps = [];
|