betterstart-cli 0.0.20 → 0.0.22

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.
@@ -62,7 +62,7 @@ export function DevModeIntegrateButton({
62
62
  </DrawerHeader>
63
63
 
64
64
  {loadedIntegration ? (
65
- <Tabs defaultValue="snippets" className="min-h-0 flex-1 overflow-hidden px-4 pb-4 gap-4">
65
+ <Tabs defaultValue="snippets" className="min-h-0 flex-1 overflow-hidden px-6 pb-4 gap-4">
66
66
  <TabsList className="w-full">
67
67
  <TabsTrigger value="snippets">Snippets</TabsTrigger>
68
68
  <TabsTrigger value="lifecycle-hooks">Lifecycle Hooks</TabsTrigger>
@@ -95,7 +95,7 @@ export function EditMediaDialogContent({
95
95
  ref={handleDrawerContainerRef}
96
96
  className="pointer-events-none absolute inset-3 z-20 overflow-hidden rounded-lg"
97
97
  />
98
- <div className="relative z-10 flex min-h-0 flex-1 flex-col overflow-hidden">
98
+ <div className="relative z-10 flex min-h-0 flex-1 flex-col overflow-hidden gap-3">
99
99
  <DrawerHeader>
100
100
  <DrawerTitle>Media file</DrawerTitle>
101
101
  </DrawerHeader>
@@ -103,7 +103,7 @@ export function EditMediaDialogContent({
103
103
  <form
104
104
  id={formId}
105
105
  onSubmit={form.handleSubmit(handleSave)}
106
- className="flex flex-1 flex-col gap-4 overflow-y-auto px-4 pb-4 pt-px"
106
+ className="flex flex-1 flex-col gap-4 overflow-y-auto px-6 pb-4 pt-px"
107
107
  >
108
108
  <Card>
109
109
  <CardContent className="p-0">
@@ -194,7 +194,7 @@ export function SortOrderDialog({
194
194
  onAnimationEnd={handleDrawerAnimationEnd}
195
195
  >
196
196
  <DrawerContent className="h-full w-full overflow-hidden">
197
- <div className="relative z-10 flex min-h-0 flex-1 flex-col overflow-hidden">
197
+ <div className="relative z-10 flex min-h-0 flex-1 flex-col overflow-hidden gap-3">
198
198
  <DrawerHeader>
199
199
  <DrawerTitle>{title}</DrawerTitle>
200
200
  {description ? <DrawerDescription>{description}</DrawerDescription> : null}
@@ -205,7 +205,7 @@ export function SortOrderDialog({
205
205
  </div>
206
206
  ) : (
207
207
  <ScrollArea className="min-h-0 flex-1">
208
- <div className="flex flex-col gap-1 px-4 py-1">
208
+ <div className="flex flex-col gap-1 px-6 py-1">
209
209
  <SortableContext
210
210
  items={orderedItems.map((i) => i.id)}
211
211
  strategy={verticalListSortingStrategy}
@@ -9,7 +9,7 @@ export function DragOverlayItem({ label }: { label: string }) {
9
9
  className="shrink-0 w-full justify-start gap-2 px-2 font-normal touch-none cursor-grabbing"
10
10
  >
11
11
  <GripVertical strokeWidth={1} />
12
- <span className="truncate text-sm">{label}</span>
12
+ <span className="min-w-0 truncate text-sm">{label}</span>
13
13
  </Button>
14
14
  )
15
15
  }
@@ -25,7 +25,7 @@ export function SortableItem({ id, label }: SortOrderItemData) {
25
25
  {...listeners}
26
26
  >
27
27
  <GripVertical strokeWidth={1} />
28
- <span className="truncate text-sm">{label}</span>
28
+ <span className="min-w-0 truncate text-sm">{label}</span>
29
29
  </Button>
30
30
  </div>
31
31
  )
@@ -21,7 +21,7 @@ import { useAuditLogs, useCleanupAuditLogs } from '@admin/hooks/use-audit-log'
21
21
  import { useTableUtils } from '@admin/hooks/use-table-utils'
22
22
  import { cn } from '@admin/utils/shared/cn'
23
23
  import { type ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table'
24
- import { GripVertical, LoaderCircle, Trash2 } from 'lucide-react'
24
+ import { GripVertical } from 'lucide-react'
25
25
  import { parseAsInteger, parseAsString, useQueryStates } from 'nuqs'
26
26
  import * as React from 'react'
27
27
  import { toast } from 'sonner'
@@ -341,7 +341,7 @@ export function AuditLogPageContent() {
341
341
  })
342
342
  }, [setFilters])
343
343
 
344
- const runCleanup = React.useCallback(() => {
344
+ const _runCleanup = React.useCallback(() => {
345
345
  cleanupMutation.mutate(undefined, {
346
346
  onSuccess: (result) => {
347
347
  if (!result.success) {
@@ -386,22 +386,7 @@ export function AuditLogPageContent() {
386
386
 
387
387
  return (
388
388
  <React.Fragment>
389
- <PageHeader
390
- title="Audit Log"
391
- actions={
392
- <div className="flex items-center gap-1">
393
- <Button
394
- type="button"
395
- variant="outline"
396
- disabled={cleanupMutation.isPending}
397
- onClick={runCleanup}
398
- >
399
- {cleanupMutation.isPending ? <LoaderCircle className="animate-spin" /> : <Trash2 />}
400
- Cleanup
401
- </Button>
402
- </div>
403
- }
404
- />
389
+ <PageHeader title="Audit Log" />
405
390
 
406
391
  <EntityFiltersBar
407
392
  filters={filtersConfig}
@@ -136,7 +136,7 @@ export function ContentEditor({
136
136
  <EditorContent
137
137
  editor={editor}
138
138
  role="presentation"
139
- className="simple-editor-content min-h-0 flex-1 cursor-text p-10 overflow-y-auto bg-background [&_.ProseMirror]:min-h-full"
139
+ className="simple-editor-content min-h-0 flex-1 cursor-text p-10 overflow-y-auto bg-input [&_.ProseMirror]:min-h-full"
140
140
  />
141
141
  </div>
142
142
  </>
@@ -346,7 +346,7 @@ export function SourceMode({
346
346
  highlightActiveLineGutter: false
347
347
  }}
348
348
  className={cn(
349
- 'flex min-h-0 flex-1 flex-col overflow-hidden font-mono text-sm font-normal leading-6 [&_.cm-content]:!bg-transparent [&_.cm-editor]:!bg-background [&_.cm-gutters]:!bg-background [&_.cm-scroller]:!bg-background',
349
+ 'flex min-h-0 flex-1 flex-col overflow-hidden font-mono text-sm font-normal leading-6 [&_.cm-content]:!bg-transparent [&_.cm-editor]:!bg-input [&_.cm-gutters]:!bg-input [&_.cm-scroller]:!bg-input',
350
350
  {
351
351
  'opacity-50 cursor-not-allowed': disabled
352
352
  }
@@ -17,7 +17,7 @@ function ScrollArea({
17
17
  >
18
18
  <ScrollAreaPrimitive.Viewport
19
19
  data-slot="scroll-area-viewport"
20
- className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
20
+ className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none [&>div]:block! focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
21
21
  >
22
22
  {children}
23
23
  </ScrollAreaPrimitive.Viewport>
package/dist/cli.js CHANGED
@@ -3056,7 +3056,7 @@ async function resolvePasswordEnvValue(options) {
3056
3056
  options.overwriteEnvKeys.add(options.key);
3057
3057
  return result.trim();
3058
3058
  }
3059
- async function promptResendConfig(cwd) {
3059
+ async function collectResendConfig(cwd) {
3060
3060
  const overwriteEnvKeys = /* @__PURE__ */ new Set();
3061
3061
  const apiKey = await resolvePasswordEnvValue({
3062
3062
  cwd,
@@ -3083,9 +3083,8 @@ async function promptResendConfig(cwd) {
3083
3083
  }
3084
3084
  }
3085
3085
  });
3086
- const envResult = appendEnvVars(
3087
- cwd,
3088
- [
3086
+ return {
3087
+ sections: [
3089
3088
  {
3090
3089
  header: "Email (Resend)",
3091
3090
  vars: [
@@ -3094,15 +3093,19 @@ async function promptResendConfig(cwd) {
3094
3093
  ]
3095
3094
  }
3096
3095
  ],
3097
- overwriteEnvKeys
3098
- );
3096
+ overwriteKeys: overwriteEnvKeys
3097
+ };
3098
+ }
3099
+ async function promptResendConfig(cwd) {
3100
+ const collected = await collectResendConfig(cwd);
3101
+ const envResult = appendEnvVars(cwd, collected.sections, collected.overwriteKeys);
3099
3102
  return {
3100
3103
  configured: true,
3101
3104
  addedEnvKeys: envResult.added,
3102
3105
  updatedEnvKeys: envResult.updated
3103
3106
  };
3104
3107
  }
3105
- async function promptR2Config(cwd) {
3108
+ async function collectR2Config(cwd) {
3106
3109
  const overwriteEnvKeys = /* @__PURE__ */ new Set();
3107
3110
  const accountId = await resolveTextEnvValue({
3108
3111
  cwd,
@@ -3117,7 +3120,7 @@ async function promptR2Config(cwd) {
3117
3120
  const accessKeyId = await resolveTextEnvValue({
3118
3121
  cwd,
3119
3122
  key: "BETTERSTART_R2_ACCESS_KEY_ID",
3120
- message: "R2 access key ID",
3123
+ message: "Cloudflare R2 access key ID",
3121
3124
  cancelMessage: "R2 configuration cancelled.",
3122
3125
  overwriteEnvKeys,
3123
3126
  validate(value) {
@@ -3127,7 +3130,7 @@ async function promptR2Config(cwd) {
3127
3130
  const secretAccessKey = await resolvePasswordEnvValue({
3128
3131
  cwd,
3129
3132
  key: "BETTERSTART_R2_SECRET_ACCESS_KEY",
3130
- message: "R2 secret access key",
3133
+ message: "Cloudflare R2 secret access key",
3131
3134
  cancelMessage: "R2 configuration cancelled.",
3132
3135
  overwriteEnvKeys,
3133
3136
  validate(value) {
@@ -3137,7 +3140,7 @@ async function promptR2Config(cwd) {
3137
3140
  const bucketName = await resolveTextEnvValue({
3138
3141
  cwd,
3139
3142
  key: "BETTERSTART_R2_BUCKET_NAME",
3140
- message: "R2 bucket name",
3143
+ message: "Cloudflare R2 bucket name",
3141
3144
  defaultValue: "betterstart-uploads",
3142
3145
  cancelMessage: "R2 configuration cancelled.",
3143
3146
  overwriteEnvKeys,
@@ -3148,7 +3151,7 @@ async function promptR2Config(cwd) {
3148
3151
  const publicUrl = await resolveTextEnvValue({
3149
3152
  cwd,
3150
3153
  key: "BETTERSTART_R2_PUBLIC_URL",
3151
- message: "R2 public URL",
3154
+ message: "Cloudflare R2 public URL",
3152
3155
  cancelMessage: "R2 configuration cancelled.",
3153
3156
  overwriteEnvKeys,
3154
3157
  validate(value) {
@@ -3157,9 +3160,8 @@ async function promptR2Config(cwd) {
3157
3160
  }
3158
3161
  }
3159
3162
  });
3160
- const envResult = appendEnvVars(
3161
- cwd,
3162
- [
3163
+ return {
3164
+ sections: [
3163
3165
  {
3164
3166
  header: "Storage (Cloudflare R2)",
3165
3167
  vars: [
@@ -3171,14 +3173,34 @@ async function promptR2Config(cwd) {
3171
3173
  ]
3172
3174
  }
3173
3175
  ],
3174
- overwriteEnvKeys
3175
- );
3176
+ overwriteKeys: overwriteEnvKeys
3177
+ };
3178
+ }
3179
+ async function promptR2Config(cwd) {
3180
+ const collected = await collectR2Config(cwd);
3181
+ const envResult = appendEnvVars(cwd, collected.sections, collected.overwriteKeys);
3176
3182
  return {
3177
3183
  configured: true,
3178
3184
  addedEnvKeys: envResult.added,
3179
3185
  updatedEnvKeys: envResult.updated
3180
3186
  };
3181
3187
  }
3188
+ async function collectIntegrationConfig(cwd, integrationIds) {
3189
+ const sections = [];
3190
+ const overwriteKeys = /* @__PURE__ */ new Set();
3191
+ for (const integrationId of resolveIntegrationInstallOrder(integrationIds)) {
3192
+ const definition = getIntegrationDefinition(integrationId);
3193
+ const collected = definition.configure === "resend" ? await collectResendConfig(cwd) : definition.configure === "r2" ? await collectR2Config(cwd) : void 0;
3194
+ if (!collected) {
3195
+ continue;
3196
+ }
3197
+ sections.push(...collected.sections);
3198
+ for (const key of collected.overwriteKeys) {
3199
+ overwriteKeys.add(key);
3200
+ }
3201
+ }
3202
+ return { sections, overwriteKeys };
3203
+ }
3182
3204
  async function configureIntegration(definition, cwd, interactive) {
3183
3205
  if (definition.configure === "resend") {
3184
3206
  if (interactive) {
@@ -21081,7 +21103,32 @@ function openBrowser(url) {
21081
21103
  // adapters/next/init/prompts/plugins.ts
21082
21104
  import { styleText } from "util";
21083
21105
  import * as p8 from "@clack/prompts";
21084
- async function promptPlugins() {
21106
+ async function promptPlugins(cwd) {
21107
+ const sections = [];
21108
+ const overwriteKeys = /* @__PURE__ */ new Set();
21109
+ const mergeIntegrationConfig = (collected) => {
21110
+ sections.push(...collected.sections);
21111
+ for (const key of collected.overwriteKeys) {
21112
+ overwriteKeys.add(key);
21113
+ }
21114
+ };
21115
+ const selectedPlugins = await p8.multiselect({
21116
+ message: `Select presets
21117
+ ${styleText("dim", "Press [Spacebar] to select/unselect")}`,
21118
+ options: [
21119
+ {
21120
+ value: "blog",
21121
+ label: "Blog",
21122
+ hint: "Posts"
21123
+ }
21124
+ ],
21125
+ required: false,
21126
+ initialValues: ["blog"]
21127
+ });
21128
+ if (p8.isCancel(selectedPlugins)) {
21129
+ p8.cancel("Setup cancelled.");
21130
+ process.exit(0);
21131
+ }
21085
21132
  const storage = await p8.select({
21086
21133
  message: "Choose a file storage mode",
21087
21134
  options: [
@@ -21102,22 +21149,8 @@ async function promptPlugins() {
21102
21149
  p8.cancel("Setup cancelled.");
21103
21150
  process.exit(0);
21104
21151
  }
21105
- const selectedPlugins = await p8.multiselect({
21106
- message: `Select presets
21107
- ${styleText("dim", "Press [Spacebar] to select/unselect")}`,
21108
- options: [
21109
- {
21110
- value: "blog",
21111
- label: "Blog",
21112
- hint: "Posts"
21113
- }
21114
- ],
21115
- required: false,
21116
- initialValues: ["blog"]
21117
- });
21118
- if (p8.isCancel(selectedPlugins)) {
21119
- p8.cancel("Setup cancelled.");
21120
- process.exit(0);
21152
+ if (storage === "r2") {
21153
+ mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["r2"]));
21121
21154
  }
21122
21155
  const selectedEmailProvider = await p8.select({
21123
21156
  message: `Which email provider do you want to set up?
@@ -21146,12 +21179,19 @@ ${styleText(
21146
21179
  p8.cancel("Setup cancelled.");
21147
21180
  process.exit(0);
21148
21181
  }
21149
- const selectedIntegrations = selectedEmailProvider === "resend" ? ["resend"] : [];
21150
- const integrations = storage === "r2" ? [...selectedIntegrations, "r2"] : selectedIntegrations;
21182
+ const integrations = [];
21183
+ if (selectedEmailProvider === "resend") {
21184
+ integrations.push("resend");
21185
+ mergeIntegrationConfig(await collectIntegrationConfig(cwd, ["resend"]));
21186
+ }
21187
+ if (storage === "r2") {
21188
+ integrations.push("r2");
21189
+ }
21151
21190
  return {
21152
21191
  plugins: selectedPlugins,
21153
21192
  integrations,
21154
- storage
21193
+ storage,
21194
+ integrationConfig: { sections, overwriteKeys }
21155
21195
  };
21156
21196
  }
21157
21197
 
@@ -23610,15 +23650,28 @@ async function runInitCommand(name, options) {
23610
23650
  }
23611
23651
  return [];
23612
23652
  })();
23613
- const pluginSelection = selectedPlugins.length > 0 || selectedIntegrations.length > 0 ? {
23614
- plugins: selectedPlugins,
23615
- integrations: selectedIntegrations,
23616
- storage: selectedIntegrations.includes("r2") ? "r2" : "local"
23617
- } : options.yes ? {
23618
- plugins: [],
23619
- integrations: [],
23620
- storage: "local"
23621
- } : await promptPlugins();
23653
+ let collectedIntegrationConfig = {
23654
+ sections: [],
23655
+ overwriteKeys: /* @__PURE__ */ new Set()
23656
+ };
23657
+ let pluginSelection;
23658
+ if (selectedPlugins.length > 0 || selectedIntegrations.length > 0) {
23659
+ pluginSelection = {
23660
+ plugins: selectedPlugins,
23661
+ integrations: selectedIntegrations,
23662
+ storage: selectedIntegrations.includes("r2") ? "r2" : "local"
23663
+ };
23664
+ } else if (options.yes) {
23665
+ pluginSelection = { plugins: [], integrations: [], storage: "local" };
23666
+ } else {
23667
+ const promptResult = await promptPlugins(cwd);
23668
+ collectedIntegrationConfig = promptResult.integrationConfig;
23669
+ pluginSelection = {
23670
+ plugins: promptResult.plugins,
23671
+ integrations: promptResult.integrations,
23672
+ storage: promptResult.storage
23673
+ };
23674
+ }
23622
23675
  let databaseUrl;
23623
23676
  const existingDbUrl = readExistingDbUrl(cwd);
23624
23677
  if (options.yes) {
@@ -23678,6 +23731,13 @@ async function runInitCommand(name, options) {
23678
23731
  });
23679
23732
  s.message("Environment variables");
23680
23733
  const envResult = scaffoldEnv(cwd, { databaseUrl, namespace });
23734
+ if (collectedIntegrationConfig.sections.length > 0) {
23735
+ appendEnvVars(
23736
+ cwd,
23737
+ collectedIntegrationConfig.sections,
23738
+ collectedIntegrationConfig.overwriteKeys
23739
+ );
23740
+ }
23681
23741
  const envCount = envResult.added.length + envResult.updated.length;
23682
23742
  results.push({
23683
23743
  label: "Environment variables",
@@ -23806,7 +23866,9 @@ async function runInitCommand(name, options) {
23806
23866
  config: resolvedPluginInstallResult.config,
23807
23867
  pm,
23808
23868
  integrationIds: pluginSelection.integrations,
23809
- interactive: !options.yes,
23869
+ // Secrets were already collected up front and written to .env.local,
23870
+ // so the install runs without prompting (rule: no mid-scaffold input).
23871
+ interactive: false,
23810
23872
  includeBiome: project2.linter.type === "none"
23811
23873
  });
23812
23874
  })() : Promise.resolve({