opacacms 0.3.0 → 0.3.2

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.
@@ -1,20 +1,20 @@
1
1
  import {
2
2
  require_picocolors
3
- } from "./chunk-rwqwsanx.js";
3
+ } from "./chunk-f3dg5dq4.js";
4
4
  import {
5
5
  Gt,
6
6
  R,
7
7
  Vt,
8
8
  Wt,
9
9
  be
10
- } from "./chunk-ec4jhybj.js";
10
+ } from "./chunk-gzdfc1ct.js";
11
11
  import {
12
12
  loadConfig,
13
13
  resolveConfigPath
14
- } from "./chunk-majsbncm.js";
14
+ } from "./chunk-jvv72110.js";
15
15
  import {
16
16
  defineCommand
17
- } from "./chunk-1qm0m8r8.js";
17
+ } from "./chunk-71wwx9vj.js";
18
18
  import {
19
19
  __toESM
20
20
  } from "./chunk-6bywt602.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  require_picocolors
3
- } from "./chunk-rwqwsanx.js";
3
+ } from "./chunk-f3dg5dq4.js";
4
4
  import {
5
5
  Ct,
6
6
  Gt,
@@ -9,10 +9,10 @@ import {
9
9
  Wt,
10
10
  Zt,
11
11
  be
12
- } from "./chunk-ec4jhybj.js";
12
+ } from "./chunk-gzdfc1ct.js";
13
13
  import {
14
14
  defineCommand
15
- } from "./chunk-1qm0m8r8.js";
15
+ } from "./chunk-71wwx9vj.js";
16
16
  import {
17
17
  logger
18
18
  } from "./chunk-jq1drsen.js";
@@ -0,0 +1,326 @@
1
+ import {
2
+ require_picocolors
3
+ } from "./chunk-f3dg5dq4.js";
4
+ import {
5
+ Ct,
6
+ Gt,
7
+ Jt,
8
+ Nt,
9
+ Rt,
10
+ Vt,
11
+ Wt,
12
+ be
13
+ } from "./chunk-gzdfc1ct.js";
14
+ import {
15
+ defineCommand
16
+ } from "./chunk-71wwx9vj.js";
17
+ import {
18
+ __toESM
19
+ } from "./chunk-6bywt602.js";
20
+
21
+ // src/cli/commands/init.ts
22
+ import fs from "node:fs";
23
+ import { resolve } from "node:path";
24
+ var import_picocolors = __toESM(require_picocolors(), 1);
25
+ function detectFramework(pkgJsonPath) {
26
+ if (!fs.existsSync(pkgJsonPath))
27
+ return "unknown";
28
+ try {
29
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
30
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
31
+ if (deps.next)
32
+ return "nextjs";
33
+ if (deps.nuxt)
34
+ return "nuxt";
35
+ if (deps.vue && deps.vite)
36
+ return "vue";
37
+ if (deps.react && deps.vite)
38
+ return "react";
39
+ if (deps["@angular/core"])
40
+ return "angular";
41
+ if (deps.vite)
42
+ return "vite";
43
+ if (deps.wrangler || deps["@cloudflare/workers-types"])
44
+ return "cloudflare-workers";
45
+ return "unknown";
46
+ } catch (error) {
47
+ return "unknown";
48
+ }
49
+ }
50
+ var init_default = defineCommand({
51
+ meta: {
52
+ name: "init",
53
+ description: "Initialize OpacaCMS in an existing project"
54
+ },
55
+ args: {
56
+ target: {
57
+ type: "positional",
58
+ description: "Target directory (defaults to current directory)",
59
+ required: false
60
+ }
61
+ },
62
+ async run({ args }) {
63
+ console.log();
64
+ Wt(import_picocolors.default.bgBlue(import_picocolors.default.white(" Welcome to OpacaCMS Setup! ")));
65
+ const targetDir = args.target ? resolve(process.cwd(), args.target) : process.cwd();
66
+ const pkgJsonPath = resolve(targetDir, "package.json");
67
+ let framework = detectFramework(pkgJsonPath);
68
+ if (framework !== "unknown") {
69
+ const confirmFramework = await Rt({
70
+ message: `Detected ${import_picocolors.default.cyan(framework)}. Is this correct?`,
71
+ initialValue: true
72
+ });
73
+ if (Ct(confirmFramework)) {
74
+ Nt("Operation cancelled.");
75
+ process.exit(0);
76
+ }
77
+ if (!confirmFramework) {
78
+ framework = "unknown";
79
+ }
80
+ }
81
+ if (framework === "unknown") {
82
+ const selected = await Jt({
83
+ message: "Which framework are you using?",
84
+ options: [
85
+ { value: "nextjs", label: "Next.js" },
86
+ { value: "nuxt", label: "Nuxt" },
87
+ { value: "cloudflare-workers", label: "Cloudflare Workers" },
88
+ { value: "vue", label: "Vue / Vite" },
89
+ { value: "react", label: "React / Vite" },
90
+ { value: "angular", label: "Angular" },
91
+ { value: "unknown", label: "Other / Node.js" }
92
+ ]
93
+ });
94
+ if (Ct(selected)) {
95
+ Nt("Operation cancelled.");
96
+ process.exit(0);
97
+ }
98
+ framework = selected;
99
+ }
100
+ const dbType = await Jt({
101
+ message: "Which database would you like to use?",
102
+ options: [
103
+ { value: "sqlite", label: "SQLite (Local/Bun)" },
104
+ { value: "d1", label: "Cloudflare D1" },
105
+ { value: "postgres", label: "PostgreSQL" }
106
+ ],
107
+ initialValue: framework === "cloudflare-workers" ? "d1" : "sqlite"
108
+ });
109
+ if (Ct(dbType)) {
110
+ Nt("Operation cancelled.");
111
+ process.exit(0);
112
+ }
113
+ const useAuth = await Rt({
114
+ message: "Enable Authentication (Better Auth)?",
115
+ initialValue: true
116
+ });
117
+ if (Ct(useAuth)) {
118
+ Nt("Operation cancelled.");
119
+ process.exit(0);
120
+ }
121
+ const s = be();
122
+ s.start(`Generating boilerplate for ${framework}...`);
123
+ let dbImport = "";
124
+ let dbInit = "";
125
+ if (dbType === "sqlite") {
126
+ dbImport = `import { createSQLiteAdapter } from 'opacacms/db/sqlite';`;
127
+ dbInit = `createSQLiteAdapter('local.db')`;
128
+ } else if (dbType === "d1") {
129
+ dbImport = `import { createD1Adapter } from 'opacacms/db/d1';`;
130
+ dbInit = `createD1Adapter(env.DB)`;
131
+ } else if (dbType === "postgres") {
132
+ dbImport = `import { createPostgresAdapter } from 'opacacms/db/postgres';`;
133
+ dbInit = `createPostgresAdapter(process.env.DATABASE_URL || '')`;
134
+ }
135
+ let authConfig = "";
136
+ if (useAuth) {
137
+ authConfig = `
138
+ auth: {
139
+ strategies: { emailPassword: true },
140
+ features: {
141
+ apiKeys: { enabled: true }
142
+ }
143
+ },`;
144
+ }
145
+ const srcDir = fs.existsSync(resolve(targetDir, "src")) ? "src/" : "";
146
+ const nextPathPrefix = framework === "nextjs" ? `${srcDir}app/(opaca)/` : "";
147
+ const cfPathPrefix = framework === "cloudflare-workers" ? "src/" : "";
148
+ const pathPrefix = framework === "nextjs" ? nextPathPrefix : cfPathPrefix;
149
+ const configCode = `import { defineConfig } from 'opacacms/config';
150
+ ${dbImport}
151
+ import { posts } from './${pathPrefix}opaca/collections/posts';
152
+ import { siteSettings } from './${pathPrefix}opaca/globals/site-settings';
153
+
154
+ ${dbType === "d1" ? `const getConfig = (env: any, request: Request) => defineConfig({
155
+ appName: "My OpacaCMS App",
156
+ serverURL: new URL(request.url).origin,
157
+ secret: env.OPACA_SECRET,
158
+ db: ${dbInit},
159
+ runMigrationsOnStartup: true,${authConfig}
160
+ collections: [posts],
161
+ globals: [siteSettings]
162
+ });
163
+
164
+ export default getConfig;` : `export default defineConfig({
165
+ appName: "My OpacaCMS App",
166
+ serverURL: "http://localhost:3000",
167
+ secret: process.env.OPACA_SECRET || "my-super-secret-key-change-me",
168
+ db: ${dbInit},
169
+ runMigrationsOnStartup: true,${authConfig}
170
+ collections: [posts],
171
+ globals: [siteSettings]
172
+ });`}
173
+ `;
174
+ const postsCode = `import { defineCollection, z } from 'opacacms/schema';
175
+
176
+ export const posts = defineCollection({
177
+ slug: 'posts',
178
+ schema: z.object({
179
+ id: z.text().default(() => crypto.randomUUID()),
180
+ title: z.text().required(),
181
+ content: z.richText(),
182
+ })
183
+ });
184
+ `;
185
+ const globalsCode = `import { defineGlobal, z } from 'opacacms/schema';
186
+
187
+ export const siteSettings = defineGlobal({
188
+ slug: 'site-settings',
189
+ schema: z.object({
190
+ siteName: z.text().default("My Awesome Site"),
191
+ description: z.text().optional(),
192
+ })
193
+ });
194
+ `;
195
+ const createDir = (path) => {
196
+ fs.mkdirSync(resolve(targetDir, path), { recursive: true });
197
+ };
198
+ const writeFile = (path, content) => {
199
+ fs.writeFileSync(resolve(targetDir, path), content);
200
+ };
201
+ writeFile("opaca.config.ts", configCode);
202
+ if (framework === "nextjs") {
203
+ const baseDir = `${srcDir}app/(opaca)`;
204
+ createDir(`${baseDir}/opaca/collections`);
205
+ createDir(`${baseDir}/opaca/globals`);
206
+ createDir(`${baseDir}/api/opaca/[[...slug]]`);
207
+ createDir(`${baseDir}/admin/[[...slug]]`);
208
+ writeFile(`${baseDir}/opaca/collections/posts.ts`, postsCode);
209
+ writeFile(`${baseDir}/opaca/globals/site-settings.ts`, globalsCode);
210
+ const importSteps = srcDir ? "../../../../../../" : "../../../../../";
211
+ const routeCode = `import { createNextHandler } from 'opacacms/runtimes/next';
212
+ import config from '${importSteps}opaca.config';
213
+
214
+ const handler = createNextHandler(config);
215
+
216
+ export const GET = handler.GET;
217
+ export const POST = handler.POST;
218
+ export const PUT = handler.PUT;
219
+ export const PATCH = handler.PATCH;
220
+ export const DELETE = handler.DELETE;
221
+ export const OPTIONS = handler.OPTIONS;
222
+ `;
223
+ writeFile(`${baseDir}/api/opaca/[[...slug]]/route.ts`, routeCode);
224
+ const pageCode = `'use client';
225
+
226
+ import { useEffect, useState } from 'react';
227
+
228
+ declare module 'react' {
229
+ namespace JSX {
230
+ interface IntrinsicElements {
231
+ 'opaca-admin': {
232
+ 'server-url'?: string;
233
+ };
234
+ }
235
+ }
236
+ }
237
+
238
+ export default function Page() {
239
+ const [loaded, setLoaded] = useState(false);
240
+
241
+ useEffect(() => {
242
+ import('opacacms/admin/wc')
243
+ .then(() => setLoaded(true))
244
+ .catch((err) => console.error('Failed to load Opaca Web Component', err));
245
+ }, []);
246
+
247
+ if (!loaded) {
248
+ return (
249
+ <div style={{ padding: '2rem', color: '#71717a' }}>
250
+ Loading Admin Interface...
251
+ </div>
252
+ );
253
+ }
254
+
255
+ // Ensure this points to where your Next.js app is hosted
256
+ const serverUrl =
257
+ typeof window !== 'undefined'
258
+ ? window.location.origin
259
+ : 'http://localhost:3000';
260
+
261
+ return <opaca-admin server-url={serverUrl} />;
262
+ }
263
+ `;
264
+ writeFile(`${baseDir}/admin/[[...slug]]/page.tsx`, pageCode);
265
+ } else if (framework === "cloudflare-workers") {
266
+ createDir("src/opaca/collections");
267
+ createDir("src/opaca/globals");
268
+ writeFile("src/opaca/collections/posts.ts", postsCode);
269
+ writeFile("src/opaca/globals/site-settings.ts", globalsCode);
270
+ const indexCode = `import { createCloudflareWorkersHandler } from "opacacms/runtimes/cloudflare-workers";
271
+ import config from "../opaca.config";
272
+
273
+ export default createCloudflareWorkersHandler(config, { enableAdmin: true });
274
+ `;
275
+ writeFile("src/index.ts", indexCode);
276
+ } else if (framework === "nuxt") {
277
+ createDir("opaca/collections");
278
+ createDir("opaca/globals");
279
+ createDir("server/api/opaca");
280
+ writeFile("opaca/collections/posts.ts", postsCode);
281
+ writeFile("opaca/globals/site-settings.ts", globalsCode);
282
+ const routeCode = `import { createAPIRouter } from 'opacacms/server/router';
283
+ import config from '../../../opaca.config';
284
+
285
+ // OpacaCMS API Router (You may need to adapt this to native Nuxt handler or Hono integration)
286
+ export default defineEventHandler(async (event) => {
287
+ // Placeholder for OpacaCMS Nuxt integration
288
+ });
289
+ `;
290
+ writeFile("server/api/opaca/[...slug].ts", routeCode);
291
+ } else {
292
+ createDir("opaca/collections");
293
+ createDir("opaca/globals");
294
+ writeFile("opaca/collections/posts.ts", postsCode);
295
+ writeFile("opaca/globals/site-settings.ts", globalsCode);
296
+ const serverCode = `import { createBunHandler } from 'opacacms/runtimes/bun';
297
+ import config from './opaca.config';
298
+
299
+ const { app, init } = createBunHandler(config, { port: 3001 });
300
+
301
+ await init(); // Connects DB, runs migrations, starts server
302
+ console.log('\uD83D\uDE80 OpacaCMS Backend Listening on http://localhost:3001');
303
+ `;
304
+ writeFile("server.ts", serverCode);
305
+ }
306
+ if (fs.existsSync(pkgJsonPath)) {
307
+ try {
308
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
309
+ if (!pkg.dependencies)
310
+ pkg.dependencies = {};
311
+ if (!pkg.dependencies.opacacms) {
312
+ pkg.dependencies.opacacms = "latest";
313
+ fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2));
314
+ }
315
+ } catch (e) {}
316
+ }
317
+ s.stop(`Scaffolding complete!`);
318
+ Vt(`1. Check the newly created \`opaca.config.ts\` and \`opaca\` folder.
319
+ 2. Run \`npm install\` (or your preferred package manager) to install dependencies.
320
+ 3. Start your dev server!`, "Next Steps");
321
+ Gt(`Build something awesome! \uD83C\uDF88`);
322
+ }
323
+ });
324
+ export {
325
+ init_default as default
326
+ };
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  require_picocolors
3
- } from "./chunk-rwqwsanx.js";
3
+ } from "./chunk-f3dg5dq4.js";
4
4
  import {
5
5
  Gt,
6
6
  R,
7
7
  Vt,
8
8
  Wt
9
- } from "./chunk-ec4jhybj.js";
9
+ } from "./chunk-gzdfc1ct.js";
10
10
  import {
11
11
  defineCommand
12
- } from "./chunk-1qm0m8r8.js";
12
+ } from "./chunk-71wwx9vj.js";
13
13
  import {
14
14
  __toESM
15
15
  } from "./chunk-6bywt602.js";
@@ -1,13 +1,8 @@
1
1
  declare const _default: import("citty").CommandDef<{
2
2
  readonly target: {
3
3
  readonly type: "positional";
4
- readonly description: "Target directory name";
4
+ readonly description: "Target directory (defaults to current directory)";
5
5
  readonly required: false;
6
6
  };
7
- readonly example: {
8
- readonly type: "string";
9
- readonly alias: "e";
10
- readonly description: "Use an example template";
11
- };
12
7
  }>;
13
8
  export default _default;
package/dist/cli/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  loadConfig,
4
4
  resolveConfigPath
5
- } from "../chunk-majsbncm.js";
6
- import"../chunk-1qm0m8r8.js";
5
+ } from "../chunk-jvv72110.js";
6
+ import"../chunk-71wwx9vj.js";
7
7
  import"../chunk-6bywt602.js";
8
8
  export {
9
9
  resolveConfigPath,
@@ -3,7 +3,7 @@ import {
3
3
  } from "../chunk-a3qae86h.js";
4
4
  import {
5
5
  createAPIRouter
6
- } from "../chunk-z3ffn2b7.js";
6
+ } from "../chunk-941zxavt.js";
7
7
  import"../chunk-m24yqkeq.js";
8
8
  import"../chunk-5b8r0v8c.js";
9
9
  import"../chunk-m5ems3hh.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createAPIRouter
3
- } from "../chunk-z3ffn2b7.js";
3
+ } from "../chunk-941zxavt.js";
4
4
  import"../chunk-m24yqkeq.js";
5
5
  import"../chunk-5b8r0v8c.js";
6
6
  import"../chunk-m5ems3hh.js";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  createAPIRouter
3
- } from "../chunk-z3ffn2b7.js";
3
+ } from "../chunk-941zxavt.js";
4
4
  import"../chunk-m24yqkeq.js";
5
5
  import"../chunk-5b8r0v8c.js";
6
6
  import"../chunk-m5ems3hh.js";
@@ -3,7 +3,7 @@ import {
3
3
  } from "../chunk-a3qae86h.js";
4
4
  import {
5
5
  createAPIRouter
6
- } from "../chunk-z3ffn2b7.js";
6
+ } from "../chunk-941zxavt.js";
7
7
  import"../chunk-m24yqkeq.js";
8
8
  import"../chunk-5b8r0v8c.js";
9
9
  import"../chunk-m5ems3hh.js";
package/dist/server.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  hydrateDoc,
8
8
  parsePopulate,
9
9
  populateDoc
10
- } from "./chunk-z3ffn2b7.js";
10
+ } from "./chunk-941zxavt.js";
11
11
  import {
12
12
  defineConfig
13
13
  } from "./chunk-1bd7fz7n.js";
package/package.json CHANGED
@@ -1,6 +1,29 @@
1
1
  {
2
2
  "name": "opacacms",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
+ "license": "MIT",
5
+ "description": "OpacaCMS: A lightweight, type-safe, and developer-first Headless CMS for the edge and beyond.",
6
+ "keywords": [
7
+ "cms",
8
+ "headless-cms",
9
+ "typescript",
10
+ "hono",
11
+ "sqlite",
12
+ "d1",
13
+ "cloudflare-workers",
14
+ "admin-panel",
15
+ "edge"
16
+ ],
17
+ "author": "fhorray",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/fhorray/opacacms.git",
21
+ "directory": "packages/opacacms"
22
+ },
23
+ "bugs": {
24
+ "url": "https://github.com/fhorray/opacacms/issues"
25
+ },
26
+ "homepage": "https://opaca.francy.dev",
4
27
  "scripts": {
5
28
  "build": "bun run ../../scripts/build.ts && tsc --emitDeclarationOnly",
6
29
  "build:publish": "bun run ../../scripts/build.ts --publish && tsc --emitDeclarationOnly",
@@ -123,6 +146,7 @@
123
146
  "@hono-rate-limiter/cloudflare": "^0.2.2",
124
147
  "@hono/bun-transpiler": "^0.2.1",
125
148
  "@hono/esbuild-transpiler": "^0.1.4",
149
+ "@hono/graphql-server": "^0.7.0",
126
150
  "@hono/node-server": "^1.19.11",
127
151
  "@lexical/code": "^0.41.0",
128
152
  "@lexical/history": "^0.41.0",
@@ -139,6 +163,8 @@
139
163
  "@nanostores/react": "^1.0.0",
140
164
  "@nanostores/router": "^1.0.0",
141
165
  "@r2wc/react-to-web-component": "^2.1.1",
166
+ "@radix-ui/react-accordion": "^1.2.12",
167
+ "@radix-ui/react-tooltip": "^1.2.8",
142
168
  "@scalar/hono-api-reference": "^0.10.4",
143
169
  "@tanstack/react-form": "^1.28.5",
144
170
  "@testing-library/dom": "^10.4.1",
@@ -147,12 +173,19 @@
147
173
  "@types/bun": "latest",
148
174
  "@types/react": "^19.2.14",
149
175
  "@types/react-dom": "^19.2.3",
176
+ "better-auth": "^1.5.5",
150
177
  "better-sqlite3": "^12.6.2",
151
178
  "bun-plugin-scss": "^1.0.0",
179
+ "class-variance-authority": "^0.7.1",
152
180
  "dotenv": "^17.3.1",
153
181
  "esbuild": "^0.27.4",
154
182
  "esbuild-wasm": "^0.27.4",
183
+ "graphql": "^16.13.1",
155
184
  "happy-dom": "^20.8.4",
185
+ "hono": "^4.12.7",
186
+ "hono-rate-limiter": "^0.5.3",
187
+ "ky": "^1.14.3",
188
+ "kysely": "^0.28.11",
156
189
  "kysely-bun-sqlite": "^0.4.0",
157
190
  "kysely-d1": "^0.4.0",
158
191
  "kysely-postgres-js": "^3.0.0",
@@ -160,26 +193,30 @@
160
193
  "lucide-react": "^0.577.0",
161
194
  "nanostores": "^1.1.1",
162
195
  "postgres": "^3.4.8",
163
- "sass": "^1.98.0"
196
+ "radix-ui": "^1.4.3",
197
+ "sass": "^1.98.0",
198
+ "zod": "^4.3.6",
199
+ "zod-to-json-schema": "^3.25.1"
164
200
  },
165
201
  "peerDependencies": {
202
+ "hono": "^4.0.0",
166
203
  "react": "^19.2.4",
167
204
  "react-dom": "^19.2.4",
168
- "typescript": "^5.9.3"
205
+ "typescript": "^5.9.3",
206
+ "zod": "^4.3.6"
169
207
  },
170
- "dependencies": {
171
- "@hono/graphql-server": "^0.7.0",
172
- "better-auth": "^1.5.5",
173
- "class-variance-authority": "^0.7.1",
174
- "graphql": "^16.13.1",
175
- "hono": "^4.12.7",
176
- "hono-rate-limiter": "^0.5.3",
177
- "ky": "^1.14.3",
178
- "kysely": "^0.28.11",
179
- "radix-ui": "^1.4.3",
180
- "zod": "^4.3.6",
181
- "zod-to-json-schema": "^3.25.1"
208
+ "peerDependenciesMeta": {
209
+ "@hono/graphql-server": {
210
+ "optional": true
211
+ },
212
+ "graphql": {
213
+ "optional": true
214
+ },
215
+ "hono-rate-limiter": {
216
+ "optional": true
217
+ }
182
218
  },
219
+ "dependencies": {},
183
220
  "files": [
184
221
  "dist"
185
222
  ]