@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 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}${m2mFieldTypes ? `
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},${hasDraft ? `
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
- return ` ${field.name}: ${drizzleType}${modifiers}`;
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
- for (const [lockfile, pm] of Object.entries(LOCKFILE_MAP)) {
7554
- if (fs20.existsSync(path20.join(cwd, lockfile))) {
7555
- return pm;
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
- const pkgPath = path20.join(cwd, "package.json");
7559
- if (fs20.existsSync(pkgPath)) {
7560
- try {
7561
- const pkg = JSON.parse(fs20.readFileSync(pkgPath, "utf-8"));
7562
- if (typeof pkg.packageManager === "string") {
7563
- const name = pkg.packageManager.split("@")[0];
7564
- if (name === "pnpm" || name === "npm" || name === "yarn" || name === "bun") {
7565
- return name;
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(dollarmath, {
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 getCoreEnvSections(databaseUrl) {
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: "http://localhost:3000" },
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 sections = getCoreEnvSections(options.databaseUrl);
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("http://localhost:3000/cms/login")}`
14054
+ `CMS: ${pc2.cyan(`http://localhost:${detectDevPort(cwd)}/cms/login`)}`
13958
14055
  );
13959
14056
  }
13960
14057
  const nextSteps = [];