autoblogger 0.2.2 → 0.2.3

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/README.md CHANGED
@@ -18,8 +18,9 @@ npx autoblogger init
18
18
  The CLI automatically:
19
19
  - Detects your Next.js and Prisma setup
20
20
  - Adds required database models to your schema
21
- - Creates config, API route, and dashboard page
22
- - Patches Tailwind to include Autoblogger styles
21
+ - Creates config, API route, and dashboard page (in isolated route group)
22
+ - Adds the standalone CSS import to your globals.css
23
+ - Fixes hydration warnings for theme switching
23
24
  - Runs the database migration
24
25
 
25
26
  Visit `/writer` to start writing.
@@ -37,9 +38,10 @@ Visit `/writer` to start writing.
37
38
 
38
39
  ## Requirements
39
40
 
40
- - Next.js 14 or 15 (App Router)
41
+ - Next.js 14, 15, or 16 (App Router)
41
42
  - Prisma 5 or 6
42
43
  - Node.js 20+
44
+ - Any CSS setup (Tailwind optional — standalone CSS included)
43
45
 
44
46
  For AI features, you'll need API keys from [Anthropic](https://console.anthropic.com/) and/or [OpenAI](https://platform.openai.com/).
45
47
 
@@ -151,24 +153,54 @@ import { getSeoValues } from 'autoblogger/seo'
151
153
  import { ARTICLE_CLASSES } from 'autoblogger/styles/article'
152
154
  ```
153
155
 
154
- ## Troubleshooting
156
+ ## Styling
157
+
158
+ Autoblogger ships with standalone CSS that works with any setup — no Tailwind required.
159
+
160
+ **Using the CLI?** It automatically adds the import for you.
161
+
162
+ **Manual setup?** Add to your `globals.css`:
163
+ ```css
164
+ @import 'autoblogger/styles/standalone.css';
165
+ ```
166
+
167
+ This single import includes all styles needed for the dashboard. Works with Tailwind v3, v4, CSS Modules, vanilla CSS, or no CSS framework at all.
168
+
169
+ ### Advanced: Customizing Theme Colors
170
+
171
+ If you use Tailwind and want autoblogger to inherit your theme colors, you can use the preset instead:
155
172
 
156
- **Tailwind classes not applying?** Add to your Tailwind content config:
157
173
  ```javascript
158
- content: ['./node_modules/autoblogger/dist/**/*.{js,mjs}']
174
+ // tailwind.config.js (Tailwind v3 only)
175
+ module.exports = {
176
+ presets: [require('autoblogger/styles/preset')],
177
+ content: [
178
+ // your paths...
179
+ './node_modules/autoblogger/dist/**/*.{js,mjs}',
180
+ ],
181
+ }
159
182
  ```
160
183
 
161
- **Styles missing?** Import in `globals.css` before Tailwind directives:
184
+ Then import the base styles (without utilities):
162
185
  ```css
163
186
  @import 'autoblogger/styles/autoblogger.css';
164
187
  ```
165
188
 
189
+ ## Troubleshooting
190
+
166
191
  **AI not working?** Check your environment variables:
167
192
  ```bash
168
193
  ANTHROPIC_API_KEY="sk-ant-..."
169
194
  OPENAI_API_KEY="sk-..."
170
195
  ```
171
196
 
197
+ **Hydration warnings with theme?** The CLI automatically adds `suppressHydrationWarning` to your root layout. If you set up manually, add it to your `<html>` tag:
198
+ ```tsx
199
+ <html lang="en" suppressHydrationWarning>
200
+ ```
201
+
202
+ **Layout conflicts?** The CLI creates the writer dashboard in an isolated route group `app/(writer)/writer/` to prevent inheriting your app's navbar/footer.
203
+
172
204
  ## License
173
205
 
174
206
  MIT © [Hunter Rosenblume](https://github.com/hrosenblume)
package/dist/cli/index.js CHANGED
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/cli/init.ts
27
- var fs7 = __toESM(require("fs"));
27
+ var fs6 = __toESM(require("fs"));
28
28
  var path6 = __toESM(require("path"));
29
29
  var import_child_process2 = require("child_process");
30
30
  var import_picocolors3 = __toESM(require("picocolors"));
@@ -388,90 +388,10 @@ function writeSchema(schemaPath, content) {
388
388
  fs3.writeFileSync(schemaPath, content, "utf-8");
389
389
  }
390
390
 
391
- // src/cli/utils/tailwind-patch.ts
392
- var fs4 = __toESM(require("fs"));
393
- var AUTOBLOGGER_CONTENT_PATH = "'./node_modules/autoblogger/dist/**/*.{js,mjs}'";
394
- var AUTOBLOGGER_SOURCE_PATH = '"./node_modules/autoblogger/dist/**/*.{js,mjs}"';
395
- function patchTailwindConfig(configPath) {
396
- if (!fs4.existsSync(configPath)) {
397
- return { success: false, alreadyPatched: false };
398
- }
399
- let content = fs4.readFileSync(configPath, "utf-8");
400
- if (content.includes("autoblogger")) {
401
- return { success: true, alreadyPatched: true };
402
- }
403
- const contentArrayRegex = /(content\s*:\s*\[)([^\]]*?)(\])/s;
404
- const match = content.match(contentArrayRegex);
405
- if (match) {
406
- const [full, start, items, end] = match;
407
- const trimmedItems = items.trimEnd();
408
- const needsComma = trimmedItems.length > 0 && !trimmedItems.endsWith(",");
409
- const newItems = trimmedItems + (needsComma ? "," : "") + "\n // Autoblogger components\n " + AUTOBLOGGER_CONTENT_PATH + ",\n ";
410
- content = content.replace(full, start + newItems + end);
411
- return { success: true, alreadyPatched: false, content };
412
- }
413
- if (content.includes("export default")) {
414
- const configObjRegex = /(export\s+default\s*\{)/;
415
- if (configObjRegex.test(content)) {
416
- content = content.replace(
417
- configObjRegex,
418
- `$1
419
- content: [
420
- // Autoblogger components
421
- ${AUTOBLOGGER_CONTENT_PATH},
422
- ],`
423
- );
424
- return { success: true, alreadyPatched: false, content };
425
- }
426
- }
427
- if (content.includes("module.exports")) {
428
- const moduleExportsRegex = /(module\.exports\s*=\s*\{)/;
429
- if (moduleExportsRegex.test(content)) {
430
- content = content.replace(
431
- moduleExportsRegex,
432
- `$1
433
- content: [
434
- // Autoblogger components
435
- ${AUTOBLOGGER_CONTENT_PATH},
436
- ],`
437
- );
438
- return { success: true, alreadyPatched: false, content };
439
- }
440
- }
441
- return { success: false, alreadyPatched: false };
442
- }
443
- function patchTailwindCssConfig(cssPath) {
444
- if (!fs4.existsSync(cssPath)) {
445
- return { success: false, alreadyPatched: false, isCssConfig: true };
446
- }
447
- let content = fs4.readFileSync(cssPath, "utf-8");
448
- if (!content.includes('@import "tailwindcss"') && !content.includes("@import 'tailwindcss'")) {
449
- return { success: false, alreadyPatched: false, isCssConfig: false };
450
- }
451
- if (content.includes("autoblogger")) {
452
- return { success: true, alreadyPatched: true, isCssConfig: true };
453
- }
454
- const importRegex = /(@import\s+["']tailwindcss["'];?\s*\n)/;
455
- const match = content.match(importRegex);
456
- if (match) {
457
- content = content.replace(
458
- importRegex,
459
- `$1/* Autoblogger components */
460
- @source ${AUTOBLOGGER_SOURCE_PATH};
461
- `
462
- );
463
- return { success: true, alreadyPatched: false, content, isCssConfig: true };
464
- }
465
- return { success: false, alreadyPatched: false, isCssConfig: true };
466
- }
467
- function writeTailwindConfig(configPath, content) {
468
- fs4.writeFileSync(configPath, content, "utf-8");
469
- }
470
-
471
391
  // src/cli/utils/css-patch.ts
472
- var fs5 = __toESM(require("fs"));
392
+ var fs4 = __toESM(require("fs"));
473
393
  var path4 = __toESM(require("path"));
474
- var AUTOBLOGGER_CSS_IMPORT = "@import 'autoblogger/styles/autoblogger.css';";
394
+ var AUTOBLOGGER_CSS_IMPORT = "@import 'autoblogger/styles/standalone.css';";
475
395
  function findGlobalsCss(projectRoot) {
476
396
  const candidates = [
477
397
  "app/globals.css",
@@ -483,22 +403,22 @@ function findGlobalsCss(projectRoot) {
483
403
  ];
484
404
  for (const candidate of candidates) {
485
405
  const fullPath = path4.join(projectRoot, candidate);
486
- if (fs5.existsSync(fullPath)) {
406
+ if (fs4.existsSync(fullPath)) {
487
407
  return fullPath;
488
408
  }
489
409
  }
490
410
  return null;
491
411
  }
492
412
  function patchGlobalsCss(cssPath) {
493
- if (!fs5.existsSync(cssPath)) {
413
+ if (!fs4.existsSync(cssPath)) {
494
414
  return { success: false, alreadyPatched: false };
495
415
  }
496
- let content = fs5.readFileSync(cssPath, "utf-8");
416
+ let content = fs4.readFileSync(cssPath, "utf-8");
497
417
  if (content.includes("autoblogger")) {
498
418
  return { success: true, alreadyPatched: true, filePath: cssPath };
499
419
  }
500
420
  content = AUTOBLOGGER_CSS_IMPORT + "\n\n" + content;
501
- fs5.writeFileSync(cssPath, content, "utf-8");
421
+ fs4.writeFileSync(cssPath, content, "utf-8");
502
422
  return { success: true, alreadyPatched: false, filePath: cssPath };
503
423
  }
504
424
 
@@ -702,8 +622,18 @@ export default async function WriterPage({
702
622
  }
703
623
  `;
704
624
 
625
+ // src/cli/templates/writer-layout.ts
626
+ var WRITER_LAYOUT_TEMPLATE = `export default function WriterLayout({
627
+ children,
628
+ }: {
629
+ children: React.ReactNode
630
+ }) {
631
+ return children
632
+ }
633
+ `;
634
+
705
635
  // src/cli/import.ts
706
- var fs6 = __toESM(require("fs"));
636
+ var fs5 = __toESM(require("fs"));
707
637
  var path5 = __toESM(require("path"));
708
638
  var import_child_process = require("child_process");
709
639
  var import_picocolors2 = __toESM(require("picocolors"));
@@ -768,7 +698,7 @@ function slugify(text) {
768
698
  }
769
699
  function parseMarkdownFile(filePath) {
770
700
  try {
771
- const content = fs6.readFileSync(filePath, "utf-8");
701
+ const content = fs5.readFileSync(filePath, "utf-8");
772
702
  const { frontmatter, body } = parseFrontmatter(content);
773
703
  let title = frontmatter.title;
774
704
  if (!title) {
@@ -806,7 +736,7 @@ function parseMarkdownFile(filePath) {
806
736
  function findMarkdownFiles(dir) {
807
737
  const files = [];
808
738
  function walk(currentDir) {
809
- const entries = fs6.readdirSync(currentDir, { withFileTypes: true });
739
+ const entries = fs5.readdirSync(currentDir, { withFileTypes: true });
810
740
  for (const entry of entries) {
811
741
  const fullPath = path5.join(currentDir, entry.name);
812
742
  if (entry.isDirectory()) {
@@ -824,7 +754,7 @@ function findMarkdownFiles(dir) {
824
754
  async function importContent(dirPath, options = {}) {
825
755
  const cwd = process.cwd();
826
756
  const absolutePath = path5.isAbsolute(dirPath) ? dirPath : path5.join(cwd, dirPath);
827
- if (!fs6.existsSync(absolutePath)) {
757
+ if (!fs5.existsSync(absolutePath)) {
828
758
  throw new Error(`Directory not found: ${dirPath}`);
829
759
  }
830
760
  const files = findMarkdownFiles(absolutePath);
@@ -851,7 +781,7 @@ async function importContent(dirPath, options = {}) {
851
781
  const status = options.status || "draft";
852
782
  const importScript = generateImportScript(posts, status, options.tag);
853
783
  const scriptPath = path5.join(cwd, ".autoblogger-import.mjs");
854
- fs6.writeFileSync(scriptPath, importScript);
784
+ fs5.writeFileSync(scriptPath, importScript);
855
785
  try {
856
786
  (0, import_child_process.execSync)(`node ${scriptPath}`, {
857
787
  cwd,
@@ -859,8 +789,8 @@ async function importContent(dirPath, options = {}) {
859
789
  });
860
790
  log("check", `Imported ${posts.length} posts as ${status}`);
861
791
  } finally {
862
- if (fs6.existsSync(scriptPath)) {
863
- fs6.unlinkSync(scriptPath);
792
+ if (fs5.existsSync(scriptPath)) {
793
+ fs5.unlinkSync(scriptPath);
864
794
  }
865
795
  }
866
796
  }
@@ -956,9 +886,6 @@ async function init(options = {}) {
956
886
  if (project.hasPrisma) {
957
887
  log("check", `Found ${project.prismaSchemaPath}`);
958
888
  }
959
- if (project.hasTailwind) {
960
- log("check", `Found ${path6.basename(project.tailwindConfigPath)}`);
961
- }
962
889
  if (!project.appRouterPath) {
963
890
  console.log(import_picocolors3.default.red("Error: Could not find App Router (app/ or src/app/ directory)"));
964
891
  console.log("Autoblogger requires Next.js App Router.");
@@ -1000,10 +927,10 @@ Error: Found conflicting model names in your Prisma schema:`));
1000
927
  console.log(` - ${prismaPath} (add 11 models)`);
1001
928
  console.log(` - lib/cms.ts`);
1002
929
  console.log(` - ${project.appRouterPath}/api/cms/[...path]/route.ts`);
1003
- console.log(` - ${project.appRouterPath}/writer/[[...path]]/page.tsx`);
1004
- if (project.tailwindConfigPath) {
1005
- console.log(` - ${project.tailwindConfigPath} (add content path)`);
1006
- }
930
+ console.log(` - ${project.appRouterPath}/(writer)/writer/[[...path]]/page.tsx`);
931
+ console.log(` - ${project.appRouterPath}/(writer)/layout.tsx`);
932
+ console.log(` - globals.css (add CSS import)`);
933
+ console.log(` - ${project.appRouterPath}/layout.tsx (add suppressHydrationWarning)`);
1007
934
  if (answers.runMigration) {
1008
935
  console.log("\nWould run:");
1009
936
  console.log(" - npx prisma migrate dev --name add-autoblogger");
@@ -1019,8 +946,7 @@ Would import ${count} posts from ${answers.importPath}`);
1019
946
  }
1020
947
  const filesToBackup = [];
1021
948
  if (project.hasPrisma) filesToBackup.push("prisma/schema.prisma");
1022
- if (project.tailwindConfigPath) filesToBackup.push(path6.relative(cwd, project.tailwindConfigPath));
1023
- if (fs7.existsSync(path6.join(cwd, "lib", "cms.ts"))) filesToBackup.push("lib/cms.ts");
949
+ if (fs6.existsSync(path6.join(cwd, "lib", "cms.ts"))) filesToBackup.push("lib/cms.ts");
1024
950
  if (filesToBackup.length > 0) {
1025
951
  const backupPath = createBackup(filesToBackup, cwd);
1026
952
  log("backup", `Created backup at ${path6.relative(cwd, backupPath)}`);
@@ -1034,59 +960,44 @@ Would import ${count} posts from ${answers.importPath}`);
1034
960
  log("write", `Updated ${path6.relative(cwd, prismaPath)} (added 11 models)`);
1035
961
  const libDir = path6.join(cwd, "lib");
1036
962
  const cmsConfigPath = path6.join(libDir, "cms.ts");
1037
- if (fs7.existsSync(cmsConfigPath)) {
963
+ if (fs6.existsSync(cmsConfigPath)) {
1038
964
  log("skip", "lib/cms.ts already exists");
1039
965
  } else {
1040
- if (!fs7.existsSync(libDir)) {
1041
- fs7.mkdirSync(libDir, { recursive: true });
966
+ if (!fs6.existsSync(libDir)) {
967
+ fs6.mkdirSync(libDir, { recursive: true });
1042
968
  }
1043
- fs7.writeFileSync(cmsConfigPath, CMS_CONFIG_TEMPLATE);
969
+ fs6.writeFileSync(cmsConfigPath, CMS_CONFIG_TEMPLATE);
1044
970
  log("write", "Created lib/cms.ts");
1045
971
  }
1046
972
  const apiRoutePath = path6.join(cwd, project.appRouterPath, "api", "cms", "[...path]", "route.ts");
1047
- if (fs7.existsSync(apiRoutePath)) {
973
+ if (fs6.existsSync(apiRoutePath)) {
1048
974
  log("skip", `${project.appRouterPath}/api/cms/[...path]/route.ts already exists`);
1049
975
  } else {
1050
976
  const apiRouteDir = path6.dirname(apiRoutePath);
1051
- if (!fs7.existsSync(apiRouteDir)) {
1052
- fs7.mkdirSync(apiRouteDir, { recursive: true });
977
+ if (!fs6.existsSync(apiRouteDir)) {
978
+ fs6.mkdirSync(apiRouteDir, { recursive: true });
1053
979
  }
1054
- fs7.writeFileSync(apiRoutePath, API_ROUTE_TEMPLATE);
980
+ fs6.writeFileSync(apiRoutePath, API_ROUTE_TEMPLATE);
1055
981
  log("write", `Created ${project.appRouterPath}/api/cms/[...path]/route.ts`);
1056
982
  }
1057
- const dashboardPath = path6.join(cwd, project.appRouterPath, "writer", "[[...path]]", "page.tsx");
1058
- if (fs7.existsSync(dashboardPath)) {
1059
- log("skip", `${project.appRouterPath}/writer/[[...path]]/page.tsx already exists`);
983
+ const writerRouteGroup = path6.join(cwd, project.appRouterPath, "(writer)");
984
+ const dashboardPath = path6.join(writerRouteGroup, "writer", "[[...path]]", "page.tsx");
985
+ const writerLayoutPath = path6.join(writerRouteGroup, "layout.tsx");
986
+ if (fs6.existsSync(dashboardPath)) {
987
+ log("skip", `${project.appRouterPath}/(writer)/writer/[[...path]]/page.tsx already exists`);
1060
988
  } else {
1061
989
  const dashboardDir = path6.dirname(dashboardPath);
1062
- if (!fs7.existsSync(dashboardDir)) {
1063
- fs7.mkdirSync(dashboardDir, { recursive: true });
990
+ if (!fs6.existsSync(dashboardDir)) {
991
+ fs6.mkdirSync(dashboardDir, { recursive: true });
1064
992
  }
1065
- fs7.writeFileSync(dashboardPath, DASHBOARD_PAGE_TEMPLATE);
1066
- log("write", `Created ${project.appRouterPath}/writer/[[...path]]/page.tsx`);
993
+ fs6.writeFileSync(dashboardPath, DASHBOARD_PAGE_TEMPLATE);
994
+ log("write", `Created ${project.appRouterPath}/(writer)/writer/[[...path]]/page.tsx`);
1067
995
  }
1068
- if (project.tailwindConfigPath) {
1069
- const patchResult = patchTailwindConfig(project.tailwindConfigPath);
1070
- if (patchResult.alreadyPatched) {
1071
- log("skip", "Tailwind config already includes autoblogger");
1072
- } else if (patchResult.success && patchResult.content) {
1073
- writeTailwindConfig(project.tailwindConfigPath, patchResult.content);
1074
- log("write", `Updated ${path6.basename(project.tailwindConfigPath)}`);
1075
- } else {
1076
- log("warn", "Could not auto-patch Tailwind config. Please add manually:");
1077
- console.log(import_picocolors3.default.gray(" content: ['./node_modules/autoblogger/dist/**/*.{js,mjs}']"));
1078
- }
1079
- } else if (project.tailwindCssPath) {
1080
- const patchResult = patchTailwindCssConfig(project.tailwindCssPath);
1081
- if (patchResult.alreadyPatched) {
1082
- log("skip", "Tailwind CSS config already includes autoblogger");
1083
- } else if (patchResult.success && patchResult.content) {
1084
- writeTailwindConfig(project.tailwindCssPath, patchResult.content);
1085
- log("write", `Updated ${path6.basename(project.tailwindCssPath)} (Tailwind v4)`);
1086
- } else {
1087
- log("warn", "Could not auto-patch Tailwind v4 CSS config. Please add manually:");
1088
- console.log(import_picocolors3.default.gray(' @source "./node_modules/autoblogger/dist/**/*.{js,mjs}";'));
1089
- }
996
+ if (fs6.existsSync(writerLayoutPath)) {
997
+ log("skip", `${project.appRouterPath}/(writer)/layout.tsx already exists`);
998
+ } else {
999
+ fs6.writeFileSync(writerLayoutPath, WRITER_LAYOUT_TEMPLATE);
1000
+ log("write", `Created ${project.appRouterPath}/(writer)/layout.tsx`);
1090
1001
  }
1091
1002
  const globalsCssPath = findGlobalsCss(cwd);
1092
1003
  if (globalsCssPath) {
@@ -1098,7 +1009,26 @@ Would import ${count} posts from ${answers.importPath}`);
1098
1009
  }
1099
1010
  } else {
1100
1011
  log("warn", "Could not find globals.css. Please add manually:");
1101
- console.log(import_picocolors3.default.gray(" @import 'autoblogger/styles/autoblogger.css';"));
1012
+ console.log(import_picocolors3.default.gray(" @import 'autoblogger/styles/standalone.css';"));
1013
+ }
1014
+ const rootLayoutPath = path6.join(cwd, project.appRouterPath, "layout.tsx");
1015
+ if (fs6.existsSync(rootLayoutPath)) {
1016
+ let layoutContent = fs6.readFileSync(rootLayoutPath, "utf-8");
1017
+ if (layoutContent.includes("suppressHydrationWarning")) {
1018
+ log("skip", "Root layout already has suppressHydrationWarning");
1019
+ } else {
1020
+ const htmlTagRegex = /<html([^>]*)>/;
1021
+ const match = layoutContent.match(htmlTagRegex);
1022
+ if (match) {
1023
+ const existingAttrs = match[1];
1024
+ const newHtmlTag = `<html${existingAttrs} suppressHydrationWarning>`;
1025
+ layoutContent = layoutContent.replace(htmlTagRegex, newHtmlTag);
1026
+ fs6.writeFileSync(rootLayoutPath, layoutContent);
1027
+ log("write", `Updated ${project.appRouterPath}/layout.tsx (added suppressHydrationWarning)`);
1028
+ } else {
1029
+ log("warn", "Could not find <html> tag in root layout. Please add suppressHydrationWarning manually.");
1030
+ }
1031
+ }
1102
1032
  }
1103
1033
  if (answers.runMigration) {
1104
1034
  console.log("");
@@ -1135,7 +1065,7 @@ Would import ${count} posts from ${answers.importPath}`);
1135
1065
  console.log("");
1136
1066
  console.log(import_picocolors3.default.gray("Next steps:"));
1137
1067
  console.log(import_picocolors3.default.gray(" 1. Update lib/cms.ts with your auth configuration"));
1138
- console.log(import_picocolors3.default.gray(" 2. Add your auth check to app/writer/[[...path]]/page.tsx"));
1068
+ console.log(import_picocolors3.default.gray(" 2. Add your auth check to app/(writer)/writer/[[...path]]/page.tsx"));
1139
1069
  console.log(import_picocolors3.default.gray(" 3. Set ANTHROPIC_API_KEY and/or OPENAI_API_KEY for AI features"));
1140
1070
  console.log("");
1141
1071
  }