@betterstart/cli 0.1.30 → 0.1.32
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 +519 -131
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-6JCWMKSY.js";
|
|
5
5
|
|
|
6
6
|
// src/cli.ts
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command9 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/commands/generate.ts
|
|
10
10
|
import path22 from "path";
|
|
@@ -901,8 +901,8 @@ function toPascalCase3(str) {
|
|
|
901
901
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
902
902
|
}
|
|
903
903
|
function toCamelCase(str) {
|
|
904
|
-
const
|
|
905
|
-
return
|
|
904
|
+
const p7 = toPascalCase3(str);
|
|
905
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
906
906
|
}
|
|
907
907
|
function toKebabCase(str) {
|
|
908
908
|
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
@@ -993,7 +993,7 @@ function generateFormAdminPages(schema, cwd, pagesDir, options) {
|
|
|
993
993
|
const adminDir = path4.join(cwd, pagesDir, "forms", kebab);
|
|
994
994
|
if (!fs4.existsSync(adminDir)) fs4.mkdirSync(adminDir, { recursive: true });
|
|
995
995
|
const files = [];
|
|
996
|
-
const rel = (
|
|
996
|
+
const rel = (p7) => path4.relative(cwd, p7);
|
|
997
997
|
const pagePath = path4.join(adminDir, "page.tsx");
|
|
998
998
|
if (!fs4.existsSync(pagePath) || options.force) {
|
|
999
999
|
fs4.writeFileSync(pagePath, generatePage(pascal, kebab), "utf-8");
|
|
@@ -1934,8 +1934,8 @@ function toPascalCase4(str) {
|
|
|
1934
1934
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
1935
1935
|
}
|
|
1936
1936
|
function toCamelCase2(str) {
|
|
1937
|
-
const
|
|
1938
|
-
return
|
|
1937
|
+
const p7 = toPascalCase4(str);
|
|
1938
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
1939
1939
|
}
|
|
1940
1940
|
function toKebabCase3(str) {
|
|
1941
1941
|
return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
|
|
@@ -2650,8 +2650,8 @@ function toPascalCase5(str) {
|
|
|
2650
2650
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
2651
2651
|
}
|
|
2652
2652
|
function toCamelCase3(str) {
|
|
2653
|
-
const
|
|
2654
|
-
return
|
|
2653
|
+
const p7 = toPascalCase5(str);
|
|
2654
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
2655
2655
|
}
|
|
2656
2656
|
function singularize2(str) {
|
|
2657
2657
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -2711,11 +2711,11 @@ function generateActions(schema, cwd, actionsDir, options = {}) {
|
|
|
2711
2711
|
const listFieldsWithRels = findListFieldsWithRelationships(dbFields);
|
|
2712
2712
|
const hasListRels = listFieldsWithRels.length > 0;
|
|
2713
2713
|
const allListRelQueries = [];
|
|
2714
|
-
for (const { field: listField, path:
|
|
2714
|
+
for (const { field: listField, path: path43 } of listFieldsWithRels) {
|
|
2715
2715
|
const rels = (listField.fields || []).filter((f) => f.type === "relationship" && f.relationship);
|
|
2716
2716
|
for (const relField of rels) {
|
|
2717
2717
|
allListRelQueries.push({
|
|
2718
|
-
fieldPath:
|
|
2718
|
+
fieldPath: path43.join("_"),
|
|
2719
2719
|
relField,
|
|
2720
2720
|
relTable: toCamelCase3(relField.relationship),
|
|
2721
2721
|
listFieldName: listField.name
|
|
@@ -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
|
})
|
|
@@ -3374,8 +3400,8 @@ function toPascalCase6(str) {
|
|
|
3374
3400
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
3375
3401
|
}
|
|
3376
3402
|
function toCamelCase4(str) {
|
|
3377
|
-
const
|
|
3378
|
-
return
|
|
3403
|
+
const p7 = toPascalCase6(str);
|
|
3404
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
3379
3405
|
}
|
|
3380
3406
|
function singularize3(str) {
|
|
3381
3407
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -4506,8 +4532,8 @@ function toPascalCase9(str) {
|
|
|
4506
4532
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
4507
4533
|
}
|
|
4508
4534
|
function toCamelCase5(str) {
|
|
4509
|
-
const
|
|
4510
|
-
return
|
|
4535
|
+
const p7 = toPascalCase9(str);
|
|
4536
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
4511
4537
|
}
|
|
4512
4538
|
function singularize6(str) {
|
|
4513
4539
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -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) {
|
|
@@ -4755,8 +4788,8 @@ function toPascalCase10(str) {
|
|
|
4755
4788
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
4756
4789
|
}
|
|
4757
4790
|
function toCamelCase6(str) {
|
|
4758
|
-
const
|
|
4759
|
-
return
|
|
4791
|
+
const p7 = toPascalCase10(str);
|
|
4792
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
4760
4793
|
}
|
|
4761
4794
|
function singularize7(str) {
|
|
4762
4795
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -5417,6 +5450,36 @@ ${indent} </FormControl>${nestedHint}
|
|
|
5417
5450
|
${indent} <FormMessage />
|
|
5418
5451
|
${indent} </FormItem>
|
|
5419
5452
|
${indent} )}
|
|
5453
|
+
${indent} />`;
|
|
5454
|
+
}
|
|
5455
|
+
if (nf.type === "video") {
|
|
5456
|
+
return `${indent} <FormField
|
|
5457
|
+
${indent} control={form.control}
|
|
5458
|
+
${indent} name={\`${field.name}.\${index}.${nf.name}\`}
|
|
5459
|
+
${indent} render={({ field: formField }) => (
|
|
5460
|
+
${indent} <FormItem>
|
|
5461
|
+
${indent} <FormLabel>${nestedLabel}</FormLabel>
|
|
5462
|
+
${indent} <FormControl>
|
|
5463
|
+
${indent} <VideoUploadField value={formField.value} onChange={formField.onChange} onBlur={formField.onBlur} disabled={isPending} maxSizeInMB={100} label="" />
|
|
5464
|
+
${indent} </FormControl>${nestedHint}
|
|
5465
|
+
${indent} <FormMessage />
|
|
5466
|
+
${indent} </FormItem>
|
|
5467
|
+
${indent} )}
|
|
5468
|
+
${indent} />`;
|
|
5469
|
+
}
|
|
5470
|
+
if (nf.type === "media") {
|
|
5471
|
+
return `${indent} <FormField
|
|
5472
|
+
${indent} control={form.control}
|
|
5473
|
+
${indent} name={\`${field.name}.\${index}.${nf.name}\`}
|
|
5474
|
+
${indent} render={({ field: formField }) => (
|
|
5475
|
+
${indent} <FormItem>
|
|
5476
|
+
${indent} <FormLabel>${nestedLabel}</FormLabel>
|
|
5477
|
+
${indent} <FormControl>
|
|
5478
|
+
${indent} <MediaUploadField value={formField.value} onChange={formField.onChange} onBlur={formField.onBlur} disabled={isPending} maxSizeInMB={100} label="" />
|
|
5479
|
+
${indent} </FormControl>${nestedHint}
|
|
5480
|
+
${indent} <FormMessage />
|
|
5481
|
+
${indent} </FormItem>
|
|
5482
|
+
${indent} )}
|
|
5420
5483
|
${indent} />`;
|
|
5421
5484
|
}
|
|
5422
5485
|
return `${indent} <FormField
|
|
@@ -6945,8 +7008,8 @@ function toPascalCase16(str) {
|
|
|
6945
7008
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
6946
7009
|
}
|
|
6947
7010
|
function toCamelCase7(str) {
|
|
6948
|
-
const
|
|
6949
|
-
return
|
|
7011
|
+
const p7 = toPascalCase16(str);
|
|
7012
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
6950
7013
|
}
|
|
6951
7014
|
function singularize12(str) {
|
|
6952
7015
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -7631,7 +7694,7 @@ function runPostGenerate(cwd, schemaName, options = {}) {
|
|
|
7631
7694
|
console.log("\n Running drizzle-kit push...");
|
|
7632
7695
|
const drizzleBin = path21.join(cwd, "node_modules", ".bin", "drizzle-kit");
|
|
7633
7696
|
try {
|
|
7634
|
-
execFileSync2(drizzleBin, ["push", "--force"], { cwd, stdio: "
|
|
7697
|
+
execFileSync2(drizzleBin, ["push", "--force"], { cwd, stdio: "inherit" });
|
|
7635
7698
|
result.dbPush = "success";
|
|
7636
7699
|
console.log(" Database schema synced");
|
|
7637
7700
|
} catch {
|
|
@@ -10550,7 +10613,6 @@ function markdownRenderTemplate() {
|
|
|
10550
10613
|
return `import { transformerNotationDiff, transformerNotationHighlight } from '@shikijs/transformers'
|
|
10551
10614
|
import { renderToString } from 'katex'
|
|
10552
10615
|
import MarkdownIt from 'markdown-it'
|
|
10553
|
-
import dollarmath from 'markdown-it-dollarmath'
|
|
10554
10616
|
import { createHighlighterCoreSync } from 'shiki/core'
|
|
10555
10617
|
import { createJavaScriptRegexEngine } from 'shiki/engine/javascript'
|
|
10556
10618
|
import css from 'shiki/langs/css.mjs'
|
|
@@ -10638,6 +10700,68 @@ function headingAnchorPlugin(md: MarkdownIt) {
|
|
|
10638
10700
|
})
|
|
10639
10701
|
}
|
|
10640
10702
|
|
|
10703
|
+
// --- Inline KaTeX math plugin (replaces markdown-it-dollarmath) ---
|
|
10704
|
+
|
|
10705
|
+
function mathPlugin(md: MarkdownIt) {
|
|
10706
|
+
// Inline: $...$
|
|
10707
|
+
md.inline.ruler.after('escape', 'math_inline', (state, silent) => {
|
|
10708
|
+
if (state.src.charCodeAt(state.pos) !== 0x24) return false
|
|
10709
|
+
if (state.src.charCodeAt(state.pos + 1) === 0x24) return false
|
|
10710
|
+
const start = state.pos + 1
|
|
10711
|
+
let end = start
|
|
10712
|
+
while (end < state.posMax && state.src.charCodeAt(end) !== 0x24) {
|
|
10713
|
+
if (state.src.charCodeAt(end) === 0x5C) end++
|
|
10714
|
+
end++
|
|
10715
|
+
}
|
|
10716
|
+
if (end >= state.posMax) return false
|
|
10717
|
+
const content = state.src.slice(start, end).trim()
|
|
10718
|
+
if (!content) return false
|
|
10719
|
+
if (!silent) {
|
|
10720
|
+
const token = state.push('math_inline', 'math', 0)
|
|
10721
|
+
token.content = content
|
|
10722
|
+
token.markup = '$'
|
|
10723
|
+
}
|
|
10724
|
+
state.pos = end + 1
|
|
10725
|
+
return true
|
|
10726
|
+
})
|
|
10727
|
+
|
|
10728
|
+
md.renderer.rules.math_inline = (tokens, idx) => {
|
|
10729
|
+
return renderToString(tokens[idx].content, { displayMode: false, throwOnError: false, strict: 'ignore' })
|
|
10730
|
+
}
|
|
10731
|
+
|
|
10732
|
+
// Block: $$...$$
|
|
10733
|
+
md.block.ruler.after('blockquote', 'math_block', (state, startLine, endLine, silent) => {
|
|
10734
|
+
const startPos = state.bMarks[startLine] + state.tShift[startLine]
|
|
10735
|
+
if (state.src.charCodeAt(startPos) !== 0x24 || state.src.charCodeAt(startPos + 1) !== 0x24) return false
|
|
10736
|
+
if (silent) return true
|
|
10737
|
+
let nextLine = startLine
|
|
10738
|
+
let found = false
|
|
10739
|
+
while (nextLine < endLine) {
|
|
10740
|
+
nextLine++
|
|
10741
|
+
if (nextLine >= endLine) break
|
|
10742
|
+
const pos = state.bMarks[nextLine] + state.tShift[nextLine]
|
|
10743
|
+
const max = state.eMarks[nextLine]
|
|
10744
|
+
const line = state.src.slice(pos, max).trim()
|
|
10745
|
+
if (line === '$$') { found = true; break }
|
|
10746
|
+
}
|
|
10747
|
+
if (!found) return false
|
|
10748
|
+
const contentLines: string[] = []
|
|
10749
|
+
for (let i = startLine + 1; i < nextLine; i++) {
|
|
10750
|
+
contentLines.push(state.src.slice(state.bMarks[i] + state.tShift[i], state.eMarks[i]))
|
|
10751
|
+
}
|
|
10752
|
+
const token = state.push('math_block', 'math', 0)
|
|
10753
|
+
token.content = contentLines.join('\\n').trim()
|
|
10754
|
+
token.markup = '$$'
|
|
10755
|
+
token.map = [startLine, nextLine + 1]
|
|
10756
|
+
state.line = nextLine + 1
|
|
10757
|
+
return true
|
|
10758
|
+
})
|
|
10759
|
+
|
|
10760
|
+
md.renderer.rules.math_block = (tokens, idx) => {
|
|
10761
|
+
return '<div class="math-display">' + renderToString(tokens[idx].content, { displayMode: true, throwOnError: false, strict: 'ignore' }) + '</div>\\n'
|
|
10762
|
+
}
|
|
10763
|
+
}
|
|
10764
|
+
|
|
10641
10765
|
// --- Markdown-it Instance ---
|
|
10642
10766
|
|
|
10643
10767
|
const md = MarkdownIt({
|
|
@@ -10658,26 +10782,7 @@ const md = MarkdownIt({
|
|
|
10658
10782
|
},
|
|
10659
10783
|
})
|
|
10660
10784
|
.use(headingAnchorPlugin)
|
|
10661
|
-
.use(
|
|
10662
|
-
allow_space: false,
|
|
10663
|
-
allow_digits: false,
|
|
10664
|
-
double_inline: true,
|
|
10665
|
-
allow_labels: true,
|
|
10666
|
-
allow_blank_lines: true,
|
|
10667
|
-
renderer(content: string, { displayMode }: { displayMode: boolean }) {
|
|
10668
|
-
return renderToString(content, {
|
|
10669
|
-
displayMode,
|
|
10670
|
-
throwOnError: false,
|
|
10671
|
-
strict: 'ignore',
|
|
10672
|
-
})
|
|
10673
|
-
},
|
|
10674
|
-
labelNormalizer(label: string) {
|
|
10675
|
-
return label.replace(/[\\s]+/g, '-')
|
|
10676
|
-
},
|
|
10677
|
-
labelRenderer(label: string) {
|
|
10678
|
-
return \`<a href="#\${label}" class="mathlabel" title="Permalink to this equation">\xB6</a>\`
|
|
10679
|
-
},
|
|
10680
|
-
})
|
|
10785
|
+
.use(mathPlugin)
|
|
10681
10786
|
|
|
10682
10787
|
export function renderMarkdownSync(src: string): string {
|
|
10683
10788
|
const trimmedContent = trimMathBlock(src)
|
|
@@ -11530,7 +11635,6 @@ var CORE_DEPS = [
|
|
|
11530
11635
|
"@shikijs/transformers",
|
|
11531
11636
|
"katex",
|
|
11532
11637
|
"markdown-it",
|
|
11533
|
-
"markdown-it-dollarmath",
|
|
11534
11638
|
// Drag-and-drop
|
|
11535
11639
|
"@dnd-kit/core",
|
|
11536
11640
|
"@dnd-kit/sortable",
|
|
@@ -13419,13 +13523,13 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13419
13523
|
clack.cancel("Cancelled.");
|
|
13420
13524
|
process.exit(0);
|
|
13421
13525
|
}
|
|
13422
|
-
const
|
|
13526
|
+
const password4 = await clack.password({
|
|
13423
13527
|
message: "Admin password",
|
|
13424
13528
|
validate: (v) => {
|
|
13425
13529
|
if (!v || v.length < 8) return "Password must be at least 8 characters";
|
|
13426
13530
|
}
|
|
13427
13531
|
});
|
|
13428
|
-
if (clack.isCancel(
|
|
13532
|
+
if (clack.isCancel(password4)) {
|
|
13429
13533
|
clack.cancel("Cancelled.");
|
|
13430
13534
|
process.exit(0);
|
|
13431
13535
|
}
|
|
@@ -13455,7 +13559,7 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13455
13559
|
env: {
|
|
13456
13560
|
...process.env,
|
|
13457
13561
|
SEED_EMAIL: email,
|
|
13458
|
-
SEED_PASSWORD:
|
|
13562
|
+
SEED_PASSWORD: password4,
|
|
13459
13563
|
SEED_NAME: name || "Admin",
|
|
13460
13564
|
...overwrite ? { SEED_OVERWRITE: "true" } : {}
|
|
13461
13565
|
}
|
|
@@ -13471,13 +13575,13 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13471
13575
|
}
|
|
13472
13576
|
);
|
|
13473
13577
|
});
|
|
13474
|
-
const
|
|
13475
|
-
|
|
13578
|
+
const spinner6 = clack.spinner();
|
|
13579
|
+
spinner6.start("Creating admin user...");
|
|
13476
13580
|
try {
|
|
13477
13581
|
const result = await runSeed2(false);
|
|
13478
13582
|
if (result.code === 2) {
|
|
13479
13583
|
const existingName = result.stdout.split("\n").find((l) => l.startsWith("EXISTING_USER:"))?.replace("EXISTING_USER:", "")?.trim() || "unknown";
|
|
13480
|
-
|
|
13584
|
+
spinner6.stop(`Account already exists for ${email}`);
|
|
13481
13585
|
const overwrite = await clack.confirm({
|
|
13482
13586
|
message: `An admin account (${existingName}) already exists with this email. Replace it?`
|
|
13483
13587
|
});
|
|
@@ -13489,14 +13593,14 @@ var seedCommand = new Command2("seed").description("Create the initial admin use
|
|
|
13489
13593
|
}
|
|
13490
13594
|
process.exit(0);
|
|
13491
13595
|
}
|
|
13492
|
-
|
|
13596
|
+
spinner6.start("Replacing admin user...");
|
|
13493
13597
|
await runSeed2(true);
|
|
13494
|
-
|
|
13598
|
+
spinner6.stop("Admin user replaced");
|
|
13495
13599
|
} else {
|
|
13496
|
-
|
|
13600
|
+
spinner6.stop("Admin user created");
|
|
13497
13601
|
}
|
|
13498
13602
|
} catch (err) {
|
|
13499
|
-
|
|
13603
|
+
spinner6.stop("Failed to create admin user");
|
|
13500
13604
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
13501
13605
|
clack.log.error(errMsg);
|
|
13502
13606
|
clack.log.info("You can run the seed script manually:");
|
|
@@ -14005,7 +14109,7 @@ function hasDbUrl(cwd) {
|
|
|
14005
14109
|
}
|
|
14006
14110
|
return false;
|
|
14007
14111
|
}
|
|
14008
|
-
function runSeed(cwd, cmsDir, email,
|
|
14112
|
+
function runSeed(cwd, cmsDir, email, password4, overwrite = false) {
|
|
14009
14113
|
const scriptsDir = path37.join(cwd, cmsDir, "scripts");
|
|
14010
14114
|
const seedPath = path37.join(scriptsDir, "seed.ts");
|
|
14011
14115
|
if (!fs32.existsSync(scriptsDir)) {
|
|
@@ -14029,7 +14133,7 @@ function runSeed(cwd, cmsDir, email, password3, overwrite = false) {
|
|
|
14029
14133
|
env: {
|
|
14030
14134
|
...process.env,
|
|
14031
14135
|
SEED_EMAIL: email,
|
|
14032
|
-
SEED_PASSWORD:
|
|
14136
|
+
SEED_PASSWORD: password4,
|
|
14033
14137
|
SEED_NAME: "Admin",
|
|
14034
14138
|
...overwrite ? { SEED_OVERWRITE: "true" } : {}
|
|
14035
14139
|
}
|
|
@@ -14116,8 +14220,8 @@ function toPascalCase17(str) {
|
|
|
14116
14220
|
return str.split(/[-_\s]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
|
|
14117
14221
|
}
|
|
14118
14222
|
function toCamelCase8(str) {
|
|
14119
|
-
const
|
|
14120
|
-
return
|
|
14223
|
+
const p7 = toPascalCase17(str);
|
|
14224
|
+
return p7.charAt(0).toLowerCase() + p7.slice(1);
|
|
14121
14225
|
}
|
|
14122
14226
|
function singularize13(str) {
|
|
14123
14227
|
if (str.endsWith("ies")) return `${str.slice(0, -3)}y`;
|
|
@@ -14329,15 +14433,298 @@ var removeCommand = new Command4("remove").alias("rm").description("Remove all g
|
|
|
14329
14433
|
console.log("");
|
|
14330
14434
|
});
|
|
14331
14435
|
|
|
14332
|
-
// src/commands/
|
|
14333
|
-
import
|
|
14436
|
+
// src/commands/setup-r2.ts
|
|
14437
|
+
import { execFileSync as execFileSync5, spawnSync } from "child_process";
|
|
14438
|
+
import fs34 from "fs";
|
|
14439
|
+
import os from "os";
|
|
14334
14440
|
import path39 from "path";
|
|
14335
14441
|
import * as p5 from "@clack/prompts";
|
|
14336
14442
|
import { Command as Command5 } from "commander";
|
|
14337
14443
|
import pc3 from "picocolors";
|
|
14444
|
+
var setupR2Command = new Command5("setup-r2").description("Create a Cloudflare R2 bucket and configure storage env vars").option("--cwd <path>", "Project root path").option("--bucket <name>", "Bucket name (skips prompt)").action(async (options) => {
|
|
14445
|
+
const cwd = options.cwd ? path39.resolve(options.cwd) : process.cwd();
|
|
14446
|
+
p5.intro(pc3.bgCyan(pc3.black(" BetterStart \u2014 R2 Storage Setup ")));
|
|
14447
|
+
const s = p5.spinner();
|
|
14448
|
+
s.start("Looking for wrangler CLI");
|
|
14449
|
+
const wrangler = findWrangler(cwd);
|
|
14450
|
+
if (!wrangler) {
|
|
14451
|
+
s.stop(`${pc3.red("\u2717")} Wrangler CLI not found`);
|
|
14452
|
+
p5.log.error(
|
|
14453
|
+
`Install it first:
|
|
14454
|
+
${pc3.cyan("npm install -g wrangler")}
|
|
14455
|
+
${pc3.dim("or")} ${pc3.cyan("npx wrangler --version")}`
|
|
14456
|
+
);
|
|
14457
|
+
process.exit(1);
|
|
14458
|
+
}
|
|
14459
|
+
s.stop(`Wrangler: ${pc3.cyan(wrangler.bin === "npx" ? "npx wrangler" : "wrangler")}`);
|
|
14460
|
+
s.start("Checking Cloudflare authentication");
|
|
14461
|
+
const whoami = runWrangler(wrangler, ["whoami"], { cwd });
|
|
14462
|
+
const whoamiOut = whoami.stdout?.toString() ?? "";
|
|
14463
|
+
if (whoami.status !== 0 || whoamiOut.includes("Not authenticated")) {
|
|
14464
|
+
s.stop(`${pc3.yellow("\u25B2")} Not logged in to Cloudflare`);
|
|
14465
|
+
const login = await p5.confirm({
|
|
14466
|
+
message: "Open browser to log in to Cloudflare?",
|
|
14467
|
+
initialValue: true
|
|
14468
|
+
});
|
|
14469
|
+
if (p5.isCancel(login) || !login) {
|
|
14470
|
+
p5.cancel("Setup cancelled. Run `wrangler login` manually first.");
|
|
14471
|
+
process.exit(0);
|
|
14472
|
+
}
|
|
14473
|
+
s.start("Waiting for Cloudflare login");
|
|
14474
|
+
const loginResult = runWrangler(wrangler, ["login"], { cwd, stdio: "inherit", timeout: 12e4 });
|
|
14475
|
+
if (loginResult.status !== 0) {
|
|
14476
|
+
s.stop(`${pc3.red("\u2717")} Login failed`);
|
|
14477
|
+
p5.cancel("Could not authenticate with Cloudflare. Run `wrangler login` manually.");
|
|
14478
|
+
process.exit(1);
|
|
14479
|
+
}
|
|
14480
|
+
s.stop(`${pc3.green("\u2713")} Logged in to Cloudflare`);
|
|
14481
|
+
} else {
|
|
14482
|
+
const emailMatch = whoamiOut.match(/associated with the email\s+(\S+)/i);
|
|
14483
|
+
const tableMatch = whoamiOut.match(/│\s*([^│]+?)\s*│\s*[a-f0-9]{32}\s*│/);
|
|
14484
|
+
const accountLabel = emailMatch?.[1] ?? tableMatch?.[1]?.trim() ?? "authenticated";
|
|
14485
|
+
s.stop(`Logged in as ${pc3.cyan(accountLabel)}`);
|
|
14486
|
+
}
|
|
14487
|
+
s.start("Fetching account ID");
|
|
14488
|
+
const accountId = extractAccountId(wrangler, cwd);
|
|
14489
|
+
if (!accountId) {
|
|
14490
|
+
s.stop(`${pc3.red("\u2717")} Could not determine account ID`);
|
|
14491
|
+
p5.log.info(
|
|
14492
|
+
`You can find it at: ${pc3.cyan("https://dash.cloudflare.com/?to=/:account/r2")}`
|
|
14493
|
+
);
|
|
14494
|
+
process.exit(1);
|
|
14495
|
+
}
|
|
14496
|
+
s.stop(`Account ID: ${pc3.dim(accountId)}`);
|
|
14497
|
+
let bucketName = options.bucket;
|
|
14498
|
+
if (!bucketName) {
|
|
14499
|
+
const result = await p5.text({
|
|
14500
|
+
message: "R2 bucket name",
|
|
14501
|
+
placeholder: "betterstart-uploads",
|
|
14502
|
+
defaultValue: "betterstart-uploads",
|
|
14503
|
+
validate: (v) => {
|
|
14504
|
+
if (!v || v.length < 3) return "Bucket name must be at least 3 characters";
|
|
14505
|
+
if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(v))
|
|
14506
|
+
return "Bucket name must be lowercase alphanumeric with hyphens, no leading/trailing hyphens";
|
|
14507
|
+
}
|
|
14508
|
+
});
|
|
14509
|
+
if (p5.isCancel(result)) {
|
|
14510
|
+
p5.cancel("Setup cancelled.");
|
|
14511
|
+
process.exit(0);
|
|
14512
|
+
}
|
|
14513
|
+
bucketName = result;
|
|
14514
|
+
}
|
|
14515
|
+
s.start(`Creating R2 bucket: ${bucketName}`);
|
|
14516
|
+
const createResult = runWrangler(wrangler, ["r2", "bucket", "create", bucketName], {
|
|
14517
|
+
cwd,
|
|
14518
|
+
timeout: 3e4
|
|
14519
|
+
});
|
|
14520
|
+
const createOut = (createResult.stdout?.toString() ?? "") + (createResult.stderr?.toString() ?? "");
|
|
14521
|
+
if (createResult.status !== 0) {
|
|
14522
|
+
if (createOut.includes("already exists") || createOut.includes("AlreadyExists")) {
|
|
14523
|
+
s.stop(`Bucket ${pc3.cyan(bucketName)} already exists \u2014 using it`);
|
|
14524
|
+
} else {
|
|
14525
|
+
s.stop("Failed to create bucket");
|
|
14526
|
+
p5.log.error(createOut.trim() || "Unknown error from wrangler");
|
|
14527
|
+
process.exit(1);
|
|
14528
|
+
}
|
|
14529
|
+
} else {
|
|
14530
|
+
s.stop(`Created bucket: ${pc3.cyan(bucketName)}`);
|
|
14531
|
+
}
|
|
14532
|
+
let publicUrl = "";
|
|
14533
|
+
const oauthToken = readWranglerToken();
|
|
14534
|
+
if (oauthToken) {
|
|
14535
|
+
s.start("Enabling public r2.dev URL");
|
|
14536
|
+
const domainResult = await enablePublicDomain(accountId, bucketName, oauthToken);
|
|
14537
|
+
if (domainResult.success && domainResult.domain) {
|
|
14538
|
+
publicUrl = `https://${domainResult.domain}`;
|
|
14539
|
+
s.stop(`Public URL: ${pc3.cyan(publicUrl)}`);
|
|
14540
|
+
} else {
|
|
14541
|
+
s.stop("Could not enable public URL automatically");
|
|
14542
|
+
p5.log.warning(domainResult.error ?? "Unknown error");
|
|
14543
|
+
p5.log.info(
|
|
14544
|
+
`You can enable it manually in the dashboard:
|
|
14545
|
+
${pc3.cyan(`https://dash.cloudflare.com/${accountId}/r2/default/buckets/${bucketName}/settings`)}`
|
|
14546
|
+
);
|
|
14547
|
+
}
|
|
14548
|
+
} else {
|
|
14549
|
+
p5.log.warning("Could not read wrangler OAuth token \u2014 skipping public URL setup");
|
|
14550
|
+
p5.log.info(
|
|
14551
|
+
`Enable it manually: ${pc3.cyan(`https://dash.cloudflare.com/${accountId}/r2/default/buckets/${bucketName}/settings`)}`
|
|
14552
|
+
);
|
|
14553
|
+
}
|
|
14554
|
+
p5.note(
|
|
14555
|
+
[
|
|
14556
|
+
`Create an R2 API token with ${pc3.bold("Object Read & Write")} permission.`,
|
|
14557
|
+
"",
|
|
14558
|
+
`Dashboard: ${pc3.cyan(`https://dash.cloudflare.com/${accountId}/r2/api-tokens`)}`,
|
|
14559
|
+
"",
|
|
14560
|
+
pc3.dim("The dashboard will give you an Access Key ID and Secret Access Key.")
|
|
14561
|
+
].join("\n"),
|
|
14562
|
+
"Create R2 API Token"
|
|
14563
|
+
);
|
|
14564
|
+
const openDashboard = await p5.confirm({
|
|
14565
|
+
message: "Open the R2 API tokens page in your browser?",
|
|
14566
|
+
initialValue: true
|
|
14567
|
+
});
|
|
14568
|
+
if (!p5.isCancel(openDashboard) && openDashboard) {
|
|
14569
|
+
const url = `https://dash.cloudflare.com/${accountId}/r2/api-tokens`;
|
|
14570
|
+
try {
|
|
14571
|
+
execFileSync5("open", [url], { stdio: "pipe", timeout: 5e3 });
|
|
14572
|
+
} catch {
|
|
14573
|
+
p5.log.warning(`Could not open browser. Visit: ${pc3.cyan(url)}`);
|
|
14574
|
+
}
|
|
14575
|
+
}
|
|
14576
|
+
const credentials = await p5.group(
|
|
14577
|
+
{
|
|
14578
|
+
accessKeyId: () => p5.text({
|
|
14579
|
+
message: "R2 Access Key ID",
|
|
14580
|
+
placeholder: "Paste from Cloudflare dashboard",
|
|
14581
|
+
validate: (v) => {
|
|
14582
|
+
if (!v || v.trim().length < 10) return "Please paste a valid Access Key ID";
|
|
14583
|
+
}
|
|
14584
|
+
}),
|
|
14585
|
+
secretAccessKey: () => p5.password({
|
|
14586
|
+
message: "R2 Secret Access Key",
|
|
14587
|
+
validate: (v) => {
|
|
14588
|
+
if (!v || v.trim().length < 10) return "Please paste a valid Secret Access Key";
|
|
14589
|
+
}
|
|
14590
|
+
})
|
|
14591
|
+
},
|
|
14592
|
+
{
|
|
14593
|
+
onCancel: () => {
|
|
14594
|
+
p5.cancel("Setup cancelled.");
|
|
14595
|
+
process.exit(0);
|
|
14596
|
+
}
|
|
14597
|
+
}
|
|
14598
|
+
);
|
|
14599
|
+
s.start("Writing environment variables");
|
|
14600
|
+
const envResult = appendEnvVars(cwd, [
|
|
14601
|
+
{
|
|
14602
|
+
header: "Storage (Cloudflare R2)",
|
|
14603
|
+
vars: [
|
|
14604
|
+
{ key: "BETTERSTART_R2_ACCOUNT_ID", value: accountId },
|
|
14605
|
+
{ key: "BETTERSTART_R2_ACCESS_KEY_ID", value: credentials.accessKeyId.trim() },
|
|
14606
|
+
{ key: "BETTERSTART_R2_SECRET_ACCESS_KEY", value: credentials.secretAccessKey.trim() },
|
|
14607
|
+
{ key: "BETTERSTART_R2_BUCKET_NAME", value: bucketName },
|
|
14608
|
+
...publicUrl ? [{ key: "BETTERSTART_R2_PUBLIC_URL", value: publicUrl }] : []
|
|
14609
|
+
]
|
|
14610
|
+
}
|
|
14611
|
+
], /* @__PURE__ */ new Set([
|
|
14612
|
+
"BETTERSTART_R2_ACCOUNT_ID",
|
|
14613
|
+
"BETTERSTART_R2_ACCESS_KEY_ID",
|
|
14614
|
+
"BETTERSTART_R2_SECRET_ACCESS_KEY",
|
|
14615
|
+
"BETTERSTART_R2_BUCKET_NAME",
|
|
14616
|
+
"BETTERSTART_R2_PUBLIC_URL"
|
|
14617
|
+
]));
|
|
14618
|
+
const totalChanged = envResult.added.length + envResult.updated.length;
|
|
14619
|
+
if (totalChanged > 0) {
|
|
14620
|
+
s.stop(`Updated .env.local ${pc3.dim(`(${envResult.added.length} added, ${envResult.updated.length} updated)`)}`);
|
|
14621
|
+
} else {
|
|
14622
|
+
s.stop("All R2 env vars already set in .env.local");
|
|
14623
|
+
}
|
|
14624
|
+
const summaryLines = [
|
|
14625
|
+
`Bucket: ${pc3.cyan(bucketName)}`,
|
|
14626
|
+
`Account: ${pc3.dim(accountId)}`,
|
|
14627
|
+
`Access Key: ${pc3.dim(credentials.accessKeyId.trim().slice(0, 8) + "...")}`
|
|
14628
|
+
];
|
|
14629
|
+
if (publicUrl) {
|
|
14630
|
+
summaryLines.push(`Public URL: ${pc3.cyan(publicUrl)}`);
|
|
14631
|
+
}
|
|
14632
|
+
summaryLines.push(`Env file: ${pc3.dim(".env.local")}`);
|
|
14633
|
+
p5.note(summaryLines.join("\n"), pc3.green("R2 storage configured"));
|
|
14634
|
+
p5.outro("Done! Your CMS can now upload files to R2.");
|
|
14635
|
+
});
|
|
14636
|
+
function findWrangler(cwd) {
|
|
14637
|
+
const localBin = path39.join(cwd, "node_modules", ".bin", "wrangler");
|
|
14638
|
+
if (fs34.existsSync(localBin)) return { bin: localBin, prefix: [] };
|
|
14639
|
+
const result = spawnSync("which", ["wrangler"], { stdio: "pipe", timeout: 5e3 });
|
|
14640
|
+
if (result.status === 0) {
|
|
14641
|
+
const found = result.stdout?.toString().trim();
|
|
14642
|
+
if (found) return { bin: found, prefix: [] };
|
|
14643
|
+
}
|
|
14644
|
+
const npxResult = spawnSync("npx", ["wrangler", "--version"], {
|
|
14645
|
+
stdio: "pipe",
|
|
14646
|
+
timeout: 15e3
|
|
14647
|
+
});
|
|
14648
|
+
if (npxResult.status === 0) return { bin: "npx", prefix: ["wrangler"] };
|
|
14649
|
+
return null;
|
|
14650
|
+
}
|
|
14651
|
+
function runWrangler(ref, args, opts) {
|
|
14652
|
+
const fullArgs = [...ref.prefix, ...args];
|
|
14653
|
+
return spawnSync(ref.bin, fullArgs, {
|
|
14654
|
+
cwd: opts.cwd,
|
|
14655
|
+
stdio: opts.stdio ?? "pipe",
|
|
14656
|
+
timeout: opts.timeout ?? 15e3
|
|
14657
|
+
});
|
|
14658
|
+
}
|
|
14659
|
+
function extractAccountId(ref, cwd) {
|
|
14660
|
+
const result = runWrangler(ref, ["whoami"], { cwd });
|
|
14661
|
+
const output = result.stdout?.toString() ?? "";
|
|
14662
|
+
const idMatch = output.match(/Account ID[:\s]+([a-f0-9]{32})/i);
|
|
14663
|
+
if (idMatch) return idMatch[1];
|
|
14664
|
+
const hexMatch = output.match(/\b([a-f0-9]{32})\b/);
|
|
14665
|
+
if (hexMatch) return hexMatch[1];
|
|
14666
|
+
return null;
|
|
14667
|
+
}
|
|
14668
|
+
function readWranglerToken() {
|
|
14669
|
+
const candidates = [
|
|
14670
|
+
path39.join(os.homedir(), "Library", "Preferences", ".wrangler", "config", "default.toml"),
|
|
14671
|
+
// macOS
|
|
14672
|
+
path39.join(os.homedir(), ".config", ".wrangler", "config", "default.toml"),
|
|
14673
|
+
// Linux
|
|
14674
|
+
path39.join(os.homedir(), ".wrangler", "config", "default.toml")
|
|
14675
|
+
// fallback
|
|
14676
|
+
];
|
|
14677
|
+
if (process.env.WRANGLER_CONFIG_PATH) {
|
|
14678
|
+
candidates.unshift(process.env.WRANGLER_CONFIG_PATH);
|
|
14679
|
+
}
|
|
14680
|
+
if (process.env.XDG_CONFIG_HOME) {
|
|
14681
|
+
candidates.unshift(
|
|
14682
|
+
path39.join(process.env.XDG_CONFIG_HOME, ".wrangler", "config", "default.toml")
|
|
14683
|
+
);
|
|
14684
|
+
}
|
|
14685
|
+
for (const configPath of candidates) {
|
|
14686
|
+
if (!fs34.existsSync(configPath)) continue;
|
|
14687
|
+
try {
|
|
14688
|
+
const content = fs34.readFileSync(configPath, "utf-8");
|
|
14689
|
+
const match = content.match(/^oauth_token\s*=\s*"([^"]+)"/m);
|
|
14690
|
+
if (match) return match[1];
|
|
14691
|
+
} catch {
|
|
14692
|
+
continue;
|
|
14693
|
+
}
|
|
14694
|
+
}
|
|
14695
|
+
return null;
|
|
14696
|
+
}
|
|
14697
|
+
async function enablePublicDomain(accountId, bucketName, token) {
|
|
14698
|
+
try {
|
|
14699
|
+
const url = `https://api.cloudflare.com/client/v4/accounts/${accountId}/r2/buckets/${bucketName}/domains/managed`;
|
|
14700
|
+
const res = await fetch(url, {
|
|
14701
|
+
method: "PUT",
|
|
14702
|
+
headers: {
|
|
14703
|
+
Authorization: `Bearer ${token}`,
|
|
14704
|
+
"Content-Type": "application/json"
|
|
14705
|
+
},
|
|
14706
|
+
body: JSON.stringify({ enabled: true })
|
|
14707
|
+
});
|
|
14708
|
+
const data = await res.json();
|
|
14709
|
+
if (data.success && data.result?.domain) {
|
|
14710
|
+
return { success: true, domain: data.result.domain };
|
|
14711
|
+
}
|
|
14712
|
+
const errMsg = data.errors?.[0]?.message ?? "API returned success=false";
|
|
14713
|
+
return { success: false, error: errMsg };
|
|
14714
|
+
} catch (err) {
|
|
14715
|
+
return { success: false, error: err instanceof Error ? err.message : "fetch failed" };
|
|
14716
|
+
}
|
|
14717
|
+
}
|
|
14718
|
+
|
|
14719
|
+
// src/commands/uninstall.ts
|
|
14720
|
+
import fs36 from "fs";
|
|
14721
|
+
import path40 from "path";
|
|
14722
|
+
import * as p6 from "@clack/prompts";
|
|
14723
|
+
import { Command as Command6 } from "commander";
|
|
14724
|
+
import pc4 from "picocolors";
|
|
14338
14725
|
|
|
14339
14726
|
// src/commands/uninstall-cleaners.ts
|
|
14340
|
-
import
|
|
14727
|
+
import fs35 from "fs";
|
|
14341
14728
|
function stripJsonComments2(input) {
|
|
14342
14729
|
let result = "";
|
|
14343
14730
|
let i = 0;
|
|
@@ -14371,8 +14758,8 @@ function stripJsonComments2(input) {
|
|
|
14371
14758
|
return result;
|
|
14372
14759
|
}
|
|
14373
14760
|
function cleanTsconfig(tsconfigPath) {
|
|
14374
|
-
if (!
|
|
14375
|
-
const raw =
|
|
14761
|
+
if (!fs35.existsSync(tsconfigPath)) return [];
|
|
14762
|
+
const raw = fs35.readFileSync(tsconfigPath, "utf-8");
|
|
14376
14763
|
const stripped = stripJsonComments2(raw).replace(/,\s*([\]}])/g, "$1");
|
|
14377
14764
|
let tsconfig;
|
|
14378
14765
|
try {
|
|
@@ -14396,13 +14783,13 @@ function cleanTsconfig(tsconfigPath) {
|
|
|
14396
14783
|
compilerOptions.paths = paths;
|
|
14397
14784
|
}
|
|
14398
14785
|
tsconfig.compilerOptions = compilerOptions;
|
|
14399
|
-
|
|
14786
|
+
fs35.writeFileSync(tsconfigPath, `${JSON.stringify(tsconfig, null, 2)}
|
|
14400
14787
|
`, "utf-8");
|
|
14401
14788
|
return removed;
|
|
14402
14789
|
}
|
|
14403
14790
|
function cleanCss(cssPath) {
|
|
14404
|
-
if (!
|
|
14405
|
-
const content =
|
|
14791
|
+
if (!fs35.existsSync(cssPath)) return [];
|
|
14792
|
+
const content = fs35.readFileSync(cssPath, "utf-8");
|
|
14406
14793
|
const lines = content.split("\n");
|
|
14407
14794
|
const sourcePattern = /^@source\s+"[^"]*cms[^"]*";\s*$/;
|
|
14408
14795
|
const removed = [];
|
|
@@ -14416,12 +14803,12 @@ function cleanCss(cssPath) {
|
|
|
14416
14803
|
}
|
|
14417
14804
|
if (removed.length === 0) return [];
|
|
14418
14805
|
const cleaned = kept.join("\n").replace(/\n{3,}/g, "\n\n");
|
|
14419
|
-
|
|
14806
|
+
fs35.writeFileSync(cssPath, cleaned, "utf-8");
|
|
14420
14807
|
return removed;
|
|
14421
14808
|
}
|
|
14422
14809
|
function cleanEnvFile(envPath) {
|
|
14423
|
-
if (!
|
|
14424
|
-
const content =
|
|
14810
|
+
if (!fs35.existsSync(envPath)) return [];
|
|
14811
|
+
const content = fs35.readFileSync(envPath, "utf-8");
|
|
14425
14812
|
const lines = content.split("\n");
|
|
14426
14813
|
const removed = [];
|
|
14427
14814
|
const kept = [];
|
|
@@ -14454,9 +14841,9 @@ function cleanEnvFile(envPath) {
|
|
|
14454
14841
|
if (removed.length === 0) return [];
|
|
14455
14842
|
const result = kept.join("\n").replace(/\n{3,}/g, "\n\n").trim();
|
|
14456
14843
|
if (result === "") {
|
|
14457
|
-
|
|
14844
|
+
fs35.unlinkSync(envPath);
|
|
14458
14845
|
} else {
|
|
14459
|
-
|
|
14846
|
+
fs35.writeFileSync(envPath, `${result}
|
|
14460
14847
|
`, "utf-8");
|
|
14461
14848
|
}
|
|
14462
14849
|
return removed;
|
|
@@ -14482,15 +14869,15 @@ function findMainCss2(cwd) {
|
|
|
14482
14869
|
"globals.css"
|
|
14483
14870
|
];
|
|
14484
14871
|
for (const candidate of candidates) {
|
|
14485
|
-
const filePath =
|
|
14486
|
-
if (
|
|
14872
|
+
const filePath = path40.join(cwd, candidate);
|
|
14873
|
+
if (fs36.existsSync(filePath)) return filePath;
|
|
14487
14874
|
}
|
|
14488
14875
|
return void 0;
|
|
14489
14876
|
}
|
|
14490
14877
|
function isCLICreatedBiome(biomePath) {
|
|
14491
|
-
if (!
|
|
14878
|
+
if (!fs36.existsSync(biomePath)) return false;
|
|
14492
14879
|
try {
|
|
14493
|
-
const content = JSON.parse(
|
|
14880
|
+
const content = JSON.parse(fs36.readFileSync(biomePath, "utf-8"));
|
|
14494
14881
|
return content.$schema?.includes("biomejs.dev") && content.formatter?.indentStyle === "space" && content.javascript?.formatter?.quoteStyle === "single" && Array.isArray(content.files?.ignore) && content.files.ignore.includes(".next");
|
|
14495
14882
|
} catch {
|
|
14496
14883
|
return false;
|
|
@@ -14498,13 +14885,13 @@ function isCLICreatedBiome(biomePath) {
|
|
|
14498
14885
|
}
|
|
14499
14886
|
function buildUninstallPlan(cwd) {
|
|
14500
14887
|
const steps = [];
|
|
14501
|
-
const hasSrc =
|
|
14888
|
+
const hasSrc = fs36.existsSync(path40.join(cwd, "src"));
|
|
14502
14889
|
const appBase = hasSrc ? "src/app" : "app";
|
|
14503
14890
|
const dirs = [];
|
|
14504
|
-
const cmsDir =
|
|
14505
|
-
const cmsRouteGroup =
|
|
14506
|
-
if (
|
|
14507
|
-
if (
|
|
14891
|
+
const cmsDir = path40.join(cwd, "cms");
|
|
14892
|
+
const cmsRouteGroup = path40.join(cwd, appBase, "(cms)");
|
|
14893
|
+
if (fs36.existsSync(cmsDir)) dirs.push("cms/");
|
|
14894
|
+
if (fs36.existsSync(cmsRouteGroup)) dirs.push(`${appBase}/(cms)/`);
|
|
14508
14895
|
if (dirs.length > 0) {
|
|
14509
14896
|
steps.push({
|
|
14510
14897
|
label: "CMS directories",
|
|
@@ -14512,25 +14899,25 @@ function buildUninstallPlan(cwd) {
|
|
|
14512
14899
|
count: dirs.length,
|
|
14513
14900
|
unit: dirs.length === 1 ? "directory" : "directories",
|
|
14514
14901
|
execute() {
|
|
14515
|
-
if (
|
|
14516
|
-
if (
|
|
14902
|
+
if (fs36.existsSync(cmsDir)) fs36.rmSync(cmsDir, { recursive: true, force: true });
|
|
14903
|
+
if (fs36.existsSync(cmsRouteGroup)) fs36.rmSync(cmsRouteGroup, { recursive: true, force: true });
|
|
14517
14904
|
}
|
|
14518
14905
|
});
|
|
14519
14906
|
}
|
|
14520
14907
|
const configFiles = [];
|
|
14521
14908
|
const configPaths = [];
|
|
14522
14909
|
const candidates = [
|
|
14523
|
-
["cms.config.ts",
|
|
14524
|
-
["drizzle.config.ts",
|
|
14525
|
-
["CMS.md",
|
|
14910
|
+
["cms.config.ts", path40.join(cwd, "cms.config.ts")],
|
|
14911
|
+
["drizzle.config.ts", path40.join(cwd, "drizzle.config.ts")],
|
|
14912
|
+
["CMS.md", path40.join(cwd, "CMS.md")]
|
|
14526
14913
|
];
|
|
14527
14914
|
for (const [label, fullPath] of candidates) {
|
|
14528
|
-
if (
|
|
14915
|
+
if (fs36.existsSync(fullPath)) {
|
|
14529
14916
|
configFiles.push(label);
|
|
14530
14917
|
configPaths.push(fullPath);
|
|
14531
14918
|
}
|
|
14532
14919
|
}
|
|
14533
|
-
const biomePath =
|
|
14920
|
+
const biomePath = path40.join(cwd, "biome.json");
|
|
14534
14921
|
if (isCLICreatedBiome(biomePath)) {
|
|
14535
14922
|
configFiles.push("biome.json (CLI-created)");
|
|
14536
14923
|
configPaths.push(biomePath);
|
|
@@ -14542,15 +14929,15 @@ function buildUninstallPlan(cwd) {
|
|
|
14542
14929
|
count: configFiles.length,
|
|
14543
14930
|
unit: configFiles.length === 1 ? "file" : "files",
|
|
14544
14931
|
execute() {
|
|
14545
|
-
for (const
|
|
14546
|
-
if (
|
|
14932
|
+
for (const p7 of configPaths) {
|
|
14933
|
+
if (fs36.existsSync(p7)) fs36.unlinkSync(p7);
|
|
14547
14934
|
}
|
|
14548
14935
|
}
|
|
14549
14936
|
});
|
|
14550
14937
|
}
|
|
14551
|
-
const tsconfigPath =
|
|
14552
|
-
if (
|
|
14553
|
-
const content =
|
|
14938
|
+
const tsconfigPath = path40.join(cwd, "tsconfig.json");
|
|
14939
|
+
if (fs36.existsSync(tsconfigPath)) {
|
|
14940
|
+
const content = fs36.readFileSync(tsconfigPath, "utf-8");
|
|
14554
14941
|
const aliasMatches = content.match(/"@cms\//g);
|
|
14555
14942
|
if (aliasMatches && aliasMatches.length > 0) {
|
|
14556
14943
|
const aliasCount = aliasMatches.length;
|
|
@@ -14567,10 +14954,10 @@ function buildUninstallPlan(cwd) {
|
|
|
14567
14954
|
}
|
|
14568
14955
|
const cssFile = findMainCss2(cwd);
|
|
14569
14956
|
if (cssFile) {
|
|
14570
|
-
const cssContent =
|
|
14957
|
+
const cssContent = fs36.readFileSync(cssFile, "utf-8");
|
|
14571
14958
|
const sourceLines = cssContent.split("\n").filter((l) => /^@source\s+"[^"]*cms[^"]*";\s*$/.test(l));
|
|
14572
14959
|
if (sourceLines.length > 0) {
|
|
14573
|
-
const relCss =
|
|
14960
|
+
const relCss = path40.relative(cwd, cssFile);
|
|
14574
14961
|
steps.push({
|
|
14575
14962
|
label: `CSS @source lines (${relCss})`,
|
|
14576
14963
|
items: [`@source lines in ${relCss}`],
|
|
@@ -14582,9 +14969,9 @@ function buildUninstallPlan(cwd) {
|
|
|
14582
14969
|
});
|
|
14583
14970
|
}
|
|
14584
14971
|
}
|
|
14585
|
-
const envPath =
|
|
14586
|
-
if (
|
|
14587
|
-
const envContent =
|
|
14972
|
+
const envPath = path40.join(cwd, ".env.local");
|
|
14973
|
+
if (fs36.existsSync(envPath)) {
|
|
14974
|
+
const envContent = fs36.readFileSync(envPath, "utf-8");
|
|
14588
14975
|
const bsVars = envContent.split("\n").filter((l) => l.trim().match(/^BETTERSTART_\w+=/)).map((l) => l.split("=")[0]);
|
|
14589
14976
|
if (bsVars.length > 0) {
|
|
14590
14977
|
steps.push({
|
|
@@ -14600,32 +14987,32 @@ function buildUninstallPlan(cwd) {
|
|
|
14600
14987
|
}
|
|
14601
14988
|
return steps;
|
|
14602
14989
|
}
|
|
14603
|
-
var uninstallCommand = new
|
|
14604
|
-
const cwd = options.cwd ?
|
|
14605
|
-
|
|
14990
|
+
var uninstallCommand = new Command6("uninstall").description("Remove all CMS files and undo modifications made by betterstart init").option("-f, --force", "Skip all confirmation prompts", false).option("--cwd <path>", "Project root path").action(async (options) => {
|
|
14991
|
+
const cwd = options.cwd ? path40.resolve(options.cwd) : process.cwd();
|
|
14992
|
+
p6.intro(pc4.bgRed(pc4.white(" BetterStart Uninstall ")));
|
|
14606
14993
|
const steps = buildUninstallPlan(cwd);
|
|
14607
14994
|
if (steps.length === 0) {
|
|
14608
|
-
|
|
14609
|
-
|
|
14995
|
+
p6.log.success(`${pc4.green("\u2713")} Nothing to remove \u2014 project is already clean.`);
|
|
14996
|
+
p6.outro("Done");
|
|
14610
14997
|
return;
|
|
14611
14998
|
}
|
|
14612
14999
|
const planLines = steps.map((step) => {
|
|
14613
15000
|
const names = step.items.join(" ");
|
|
14614
|
-
const countLabel =
|
|
14615
|
-
return `${
|
|
15001
|
+
const countLabel = pc4.dim(`${step.count} ${step.unit}`);
|
|
15002
|
+
return `${pc4.red("\xD7")} ${names} ${countLabel}`;
|
|
14616
15003
|
});
|
|
14617
|
-
|
|
15004
|
+
p6.note(planLines.join("\n"), "Uninstall plan");
|
|
14618
15005
|
if (!options.force) {
|
|
14619
|
-
const confirmed = await
|
|
15006
|
+
const confirmed = await p6.confirm({
|
|
14620
15007
|
message: "Proceed with uninstall?",
|
|
14621
15008
|
initialValue: false
|
|
14622
15009
|
});
|
|
14623
|
-
if (
|
|
14624
|
-
|
|
15010
|
+
if (p6.isCancel(confirmed) || !confirmed) {
|
|
15011
|
+
p6.cancel("Uninstall cancelled.");
|
|
14625
15012
|
process.exit(0);
|
|
14626
15013
|
}
|
|
14627
15014
|
}
|
|
14628
|
-
const s =
|
|
15015
|
+
const s = p6.spinner();
|
|
14629
15016
|
s.start(steps[0].label);
|
|
14630
15017
|
for (const step of steps) {
|
|
14631
15018
|
s.message(step.label);
|
|
@@ -14633,16 +15020,16 @@ var uninstallCommand = new Command5("uninstall").description("Remove all CMS fil
|
|
|
14633
15020
|
}
|
|
14634
15021
|
const parts = steps.map((step) => `${step.count} ${step.unit}`);
|
|
14635
15022
|
s.stop(`Removed ${parts.join(", ")}`);
|
|
14636
|
-
|
|
14637
|
-
|
|
15023
|
+
p6.note(pc4.dim("Database tables were NOT dropped \u2014 drop them manually if needed."), "Next steps");
|
|
15024
|
+
p6.outro("Uninstall complete");
|
|
14638
15025
|
});
|
|
14639
15026
|
|
|
14640
15027
|
// src/commands/update-deps.ts
|
|
14641
|
-
import
|
|
15028
|
+
import path41 from "path";
|
|
14642
15029
|
import * as clack2 from "@clack/prompts";
|
|
14643
|
-
import { Command as
|
|
14644
|
-
var updateDepsCommand = new
|
|
14645
|
-
const cwd = options.cwd ?
|
|
15030
|
+
import { Command as Command7 } from "commander";
|
|
15031
|
+
var updateDepsCommand = new Command7("update-deps").description("Install or update all CMS dependencies").option("--cwd <path>", "Project root path").action(async (options) => {
|
|
15032
|
+
const cwd = options.cwd ? path41.resolve(options.cwd) : process.cwd();
|
|
14646
15033
|
clack2.intro("BetterStart Update Dependencies");
|
|
14647
15034
|
const pm = detectPackageManager(cwd);
|
|
14648
15035
|
clack2.log.info(`Package manager: ${pm}`);
|
|
@@ -14667,32 +15054,33 @@ var updateDepsCommand = new Command6("update-deps").description("Install or upda
|
|
|
14667
15054
|
});
|
|
14668
15055
|
|
|
14669
15056
|
// src/commands/update-styles.ts
|
|
14670
|
-
import
|
|
14671
|
-
import
|
|
15057
|
+
import fs37 from "fs";
|
|
15058
|
+
import path42 from "path";
|
|
14672
15059
|
import * as clack3 from "@clack/prompts";
|
|
14673
|
-
import { Command as
|
|
14674
|
-
var updateStylesCommand = new
|
|
14675
|
-
const cwd = options.cwd ?
|
|
15060
|
+
import { Command as Command8 } from "commander";
|
|
15061
|
+
var updateStylesCommand = new Command8("update-styles").description("Replace cms-globals.css with the latest version from the CLI").option("--cwd <path>", "Project root path").action(async (options) => {
|
|
15062
|
+
const cwd = options.cwd ? path42.resolve(options.cwd) : process.cwd();
|
|
14676
15063
|
clack3.intro("BetterStart Update Styles");
|
|
14677
15064
|
const config = await resolveConfig(cwd);
|
|
14678
15065
|
const cmsDir = config.paths?.cms ?? "./cms";
|
|
14679
|
-
const targetPath =
|
|
14680
|
-
if (!
|
|
14681
|
-
clack3.cancel(`cms-globals.css not found at ${
|
|
15066
|
+
const targetPath = path42.join(cwd, cmsDir, "cms-globals.css");
|
|
15067
|
+
if (!fs37.existsSync(targetPath)) {
|
|
15068
|
+
clack3.cancel(`cms-globals.css not found at ${path42.relative(cwd, targetPath)}`);
|
|
14682
15069
|
process.exit(1);
|
|
14683
15070
|
}
|
|
14684
|
-
|
|
14685
|
-
clack3.log.success(`Updated ${
|
|
15071
|
+
fs37.writeFileSync(targetPath, cmsGlobalsCssTemplate(), "utf-8");
|
|
15072
|
+
clack3.log.success(`Updated ${path42.relative(cwd, targetPath)}`);
|
|
14686
15073
|
clack3.outro("Styles updated");
|
|
14687
15074
|
});
|
|
14688
15075
|
|
|
14689
15076
|
// src/cli.ts
|
|
14690
|
-
var program = new
|
|
15077
|
+
var program = new Command9();
|
|
14691
15078
|
program.name("betterstart").description("Scaffold a full-featured CMS into any Next.js 16 application").version("0.1.0");
|
|
14692
15079
|
program.addCommand(initCommand);
|
|
14693
15080
|
program.addCommand(generateCommand);
|
|
14694
15081
|
program.addCommand(removeCommand);
|
|
14695
15082
|
program.addCommand(seedCommand);
|
|
15083
|
+
program.addCommand(setupR2Command);
|
|
14696
15084
|
program.addCommand(uninstallCommand);
|
|
14697
15085
|
program.addCommand(updateDepsCommand);
|
|
14698
15086
|
program.addCommand(updateStylesCommand);
|