@plutocms/pluto 0.0.1-alpha.1

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/.editorconfig ADDED
@@ -0,0 +1,12 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ [*]
7
+ indent_style = space
8
+ indent_size = 2
9
+ end_of_line = lf
10
+ charset = utf-8
11
+ trim_trailing_whitespace = true
12
+ insert_final_newline = true
@@ -0,0 +1,11 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for all configuration options:
4
+ # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
5
+
6
+ version: 2
7
+ updates:
8
+ - package-ecosystem: bun # See documentation for possible values
9
+ directory: / # Location of package manifests
10
+ schedule:
11
+ interval: weekly
@@ -0,0 +1,32 @@
1
+ name: Publish to NPM
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+
8
+ jobs:
9
+ publish:
10
+ name: Publish Package
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v3
16
+
17
+ - name: Setup Bun
18
+ uses: oven-sh/setup-bun@v1
19
+ with:
20
+ bun-version: 'latest'
21
+
22
+ - name: Install dependencies
23
+ run: bun install
24
+
25
+ - name: Run build
26
+ run: bun run build
27
+
28
+ - name: Configure NPM auth
29
+ run: echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc
30
+
31
+ - name: Publish to NPM
32
+ run: bun publish --access public
package/.prettierrc ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "useTabs": false,
4
+ "singleQuote": true,
5
+ "semi": false,
6
+ "trailingComma": "es5",
7
+ "arrowParens": "always",
8
+ "endOfLine": "lf",
9
+ "plugins": ["prettier-plugin-tailwindcss"]
10
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "recommendations": [
3
+ "Vue.volar",
4
+ "EditorConfig.EditorConfig",
5
+ "esbenp.prettier-vscode",
6
+ "dbaeumer.vscode-eslint",
7
+ "bradlc.vscode-tailwindcss",
8
+ "antfu.goto-alias",
9
+ "antfu.iconify",
10
+ "streetsidesoftware.code-spell-checker",
11
+ "usernamehw.errorlens"
12
+ ],
13
+ "unwantedRecommendations": ["octref.vetur"]
14
+ }
@@ -0,0 +1,72 @@
1
+ {
2
+ "css.lint.unknownAtRules": "ignore",
3
+ "less.lint.unknownAtRules": "ignore",
4
+ "scss.lint.unknownAtRules": "ignore",
5
+ "tailwindCSS.classAttributes": ["class", ".*-class(es)?", "theme", "ui"],
6
+ "tailwindCSS.experimental.classRegex": [
7
+ ["Theme \\=([^;]*);?", "'([^']*)'"],
8
+ ["Theme \\=([^;]*);?", "\"([^\"]*)\""],
9
+ ["Theme \\=([^;]*);?", "\\`([^\\`]*)\\`"],
10
+ ["Class \\=([^;]*);?", "'([^']*)'"],
11
+ ["Classes \\=([^;]*);?", "'([^']*)'"],
12
+ ["Class \\=([^;]*);?", "\"([^\"]*)\""],
13
+ ["Classes \\=([^;]*);?", "\"([^\"]*)\""],
14
+ ["Class \\=([^;]*);?", "\\`([^\\`]*)\\`"],
15
+ ["Classes \\=([^;]*);?", "\\`([^\\`]*)\\`"],
16
+ ["tw`(.*?)`", "tw\\('(.*?)'\\)", "tw\\(\\s*('(.*?)'|\"(.*?)\")\\s*\\)"],
17
+ ["ui:\\s*{([^)]*)\\s*}", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
18
+ ],
19
+ "files.associations": {
20
+ "*.css": "tailwindcss"
21
+ },
22
+
23
+ "editor.codeActionsOnSave": {
24
+ "source.fixAll": "never",
25
+ "source.fixAll.eslint": "explicit"
26
+ },
27
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
28
+ "editor.formatOnSave": true,
29
+ "editor.gotoLocation.multipleDefinitions": "goto",
30
+ "editor.quickSuggestions": {
31
+ "strings": true
32
+ },
33
+
34
+ "cSpell.words": [
35
+ "bunx",
36
+ "defu",
37
+ "nuxt",
38
+ "nuxtjs",
39
+ "oklch",
40
+ "pinia",
41
+ "plutocms",
42
+ "tailwindcss"
43
+ ],
44
+
45
+ "eslint.useFlatConfig": true,
46
+ "eslint.validate": [
47
+ "javascript",
48
+ "javascriptreact",
49
+ "typescript",
50
+ "typescriptreact",
51
+ "vue",
52
+ "html",
53
+ "markdown",
54
+ "json",
55
+ "jsonc",
56
+ "yaml",
57
+ "toml",
58
+ "xml",
59
+ "gql",
60
+ "graphql",
61
+ "astro",
62
+ "svelte",
63
+ "css",
64
+ "less",
65
+ "scss",
66
+ "pcss",
67
+ "postcss",
68
+ "mjs"
69
+ ],
70
+
71
+ "vue.inlayHints.missingProps": true
72
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "Vue Component": {
3
+ "prefix": ["vue", "tem", "template", "setup"],
4
+ "body": [
5
+ "<script setup lang=\"ts\">",
6
+ "$1",
7
+ "</script>",
8
+ "",
9
+ "<template>",
10
+ " <div>$2</div>",
11
+ "</template>",
12
+ "",
13
+ ],
14
+ "description": "Vue Component (Script first)",
15
+ },
16
+ }
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ <img width="335" height="105" alt="image" src="https://github.com/user-attachments/assets/1cf11470-ea54-416e-a940-7221fffef1e2" />
2
+
3
+ # Pluto
4
+
5
+ > [!WARNING]
6
+ > 🚧 Work In Progress: Pluto is experimental. Breaking changes are expected. Not production ready yet.
7
+
8
+ Pluto is a modular, layer-driven Content Management System (CMS) built with Nuxt. A modern alternative to monolithic systems like WordPress. Extend functionality through Nuxt Layers: choose only what is needed (auth, storage, e-commerce, blog, database connectors, etc.).
9
+
10
+ ## Highlights
11
+
12
+ - Nuxt + Nuxt Layers architecture
13
+ - Headless-first: consume via API (_with typed routes!_) or built-in composables (like `useAuth` or `useProduct`)
14
+ - Opt-in feature layers (Supabase, Blog, Shop, DB connectors, etc.)
15
+ - Core primitives: Content Types, Taxonomies, Custom Fields, Media, Permissions
16
+ - Deploy anywhere: Node, Edge, Serverless, Static (hybrid), Docker
17
+ - Developer-focused: composables, typed schemas, minimal core
18
+ - Dashboard is owned: fork as needed or just use it as is
19
+
20
+ ## Getting Started
21
+
22
+ First, install `typescript` as a dev dependency if you haven't already. This is required for Nuxt Layers to function properly.
23
+
24
+ ```bash
25
+ npm i -D typescript
26
+ ```
27
+
28
+ Then, extend your Nuxt configuration to include the Pluto layer, setting the install option to true, so Pluto's dependencies are installed automatically.
29
+
30
+ ```ts
31
+ // nuxt.config.ts
32
+ export default defineNuxtConfig({
33
+ extends: [
34
+ ['github:plutocms/pluto', { install: true }],
35
+
36
+ // Then, add any additional layers you want to use here
37
+ ['github:plutocms/supabase', { install: true }],
38
+ ['github:plutocms/blog', { install: true }],
39
+ ['github:plutocms/shop', { install: true }],
40
+ ],
41
+ })
42
+ ```
43
+
44
+ ## Core Concepts
45
+
46
+ - Content Types: Define structured entities (e.g. Post, Product, Page)
47
+ - Fields: Primitive & custom (string, rich text, media, relation, JSON, computed)
48
+ - Taxonomies: Hierarchical or flat classification
49
+ - Media: Pluggable storage (e.g. Supabase, S3, local)
50
+ - Auth & Identity: Provided by optional layers (e.g. Supabase)
51
+ - Policies: Role / capability mapping (planned)
52
+
53
+ ## Layer Examples (Planned / In Progress)
54
+
55
+ | Layer | Purpose |
56
+ | ------------------------- | ----------------------------------------------- |
57
+ | core | Base CMS engine + schema registry |
58
+ | supabase | Auth, file storage, row-level security hooks |
59
+ | blog | Post model, categories, tags, markdown pipeline |
60
+ | shop | Product model, pricing, inventory abstraction |
61
+ | mysql / postgres / sqlite | Database connectors |
62
+ | analytics | Basic event/log capture |
63
+ | search | Index + query adapters (e.g. Meilisearch) |
64
+
65
+ > [!NOTE]
66
+ > Actual availability depends on roadmap progress.
67
+
68
+ For now, some layers are being developed (`/layers` directory) in this same repository, but will eventually be extracted into their own packages.
69
+
70
+ > [!NOTE]
71
+ > Optional layers may require environment variables; see ./env.example when added.
72
+
73
+ ## Roadmap (Subject to Change)
74
+
75
+ Check [the roadmap here](https://github.com/ojvribeiro/pluto/issues/47).
76
+
77
+ ## Contributing
78
+
79
+ - Open issues for design discussion before large PRs
80
+ - Keep layers isolated and opt-in
81
+ - Prefer typed contracts and explicit interfaces
82
+ - Add tests where possible (testing stack TBD)
83
+
84
+ ## Philosophy
85
+
86
+ - Minimal core, everything is a layer
87
+ - Transparent, hackable dashboard (no locked UI bundle)
88
+ - Declarative schemas
89
+ - Runtime flexibility: choose infra, not dictated by core
90
+
91
+ ## Why Not Just WordPress?
92
+
93
+ - Modern SSR + SPA hydration via Nuxt
94
+ - TypeScript-first developer experience
95
+ - Layer-based opt-in features instead of plugin global mutations
96
+ - Deployment portability (Edge / Serverless friendly)
97
+ - Headless usage is primary, not an afterthought
98
+ - If you're a Vue developer, you'll feel right at home.
99
+
100
+ ## Security
101
+
102
+ Do not deploy to production yet. No security audit performed. Auth / ACL model incomplete.
103
+
104
+ ## License
105
+
106
+ TBD (likely MIT). Not final.
107
+
108
+ ## Status
109
+
110
+ Experimental. Expect refactors.
111
+
112
+ ## Naming Note
113
+
114
+ "Pluto" is a working name; subject to rename if conflicts arise.
@@ -0,0 +1,41 @@
1
+ export default defineAppConfig({
2
+ ui: {
3
+ button: {
4
+ compoundVariants: [
5
+ {
6
+ size: 'xl',
7
+ class: 'px-6 py-2',
8
+ square: false,
9
+ },
10
+ ],
11
+
12
+ defaultVariants: {
13
+ color: 'primary',
14
+ variant: 'solid',
15
+ size: 'xl',
16
+ },
17
+ },
18
+
19
+ formField: {
20
+ defaultVariants: {
21
+ size: 'xl',
22
+ },
23
+ },
24
+
25
+ input: {
26
+ slots: {
27
+ root: 'w-full',
28
+ },
29
+
30
+ compoundVariants: [
31
+ {
32
+ class: 'w-full',
33
+ },
34
+ ],
35
+
36
+ defaultVariants: {
37
+ size: 'xl',
38
+ },
39
+ },
40
+ },
41
+ })
package/app/app.vue ADDED
@@ -0,0 +1,13 @@
1
+ <script setup lang="ts">
2
+ const websiteName = 'Pluto Admin'
3
+
4
+ useHead({
5
+ titleTemplate: (title) => (title ? `${title} - ${websiteName}` : websiteName),
6
+ })
7
+ </script>
8
+
9
+ <template>
10
+ <PlutoRoot>
11
+ <NuxtPage />
12
+ </PlutoRoot>
13
+ </template>
@@ -0,0 +1,12 @@
1
+ @import 'tailwindcss';
2
+ @import '@nuxt/ui';
3
+
4
+ @source '../..';
5
+ @source '../../../../../app/**';
6
+
7
+ @theme {
8
+ --color-admin-content: var(--color-slate-800);
9
+ --color-admin-sidebar: var(--color-slate-900);
10
+
11
+ --font-outfit: 'Outfit', sans-serif;
12
+ }
@@ -0,0 +1,7 @@
1
+ <script setup lang="ts"></script>
2
+
3
+ <template>
4
+ <UContainer class="flex max-w-300 flex-col gap-y-8 py-10">
5
+ <slot />
6
+ </UContainer>
7
+ </template>
@@ -0,0 +1,28 @@
1
+ <script setup>
2
+ const colorMode = useColorMode()
3
+
4
+ const isDark = computed({
5
+ get() {
6
+ return colorMode.value === 'dark'
7
+ },
8
+ set() {
9
+ colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'
10
+ },
11
+ })
12
+ </script>
13
+
14
+ <template>
15
+ <ClientOnly v-if="!colorMode?.forced">
16
+ <UButton
17
+ :icon="isDark ? 'i-lucide-moon' : 'i-lucide-sun'"
18
+ color="neutral"
19
+ variant="solid"
20
+ size="sm"
21
+ @click="isDark = !isDark"
22
+ />
23
+
24
+ <template #fallback>
25
+ <div class="size-8" />
26
+ </template>
27
+ </ClientOnly>
28
+ </template>
@@ -0,0 +1,20 @@
1
+ <script setup lang="ts">
2
+ import type { ClassNameValue } from 'tailwind-merge'
3
+ import { twMerge } from 'tailwind-merge'
4
+
5
+ const props = defineProps<{
6
+ class?: ClassNameValue
7
+ }>()
8
+
9
+ const resolvedClasses = computed(() => {
10
+ const defaultClasses = 'mx-auto max-w-full transition-all px-4 sm:px-6 w-350'
11
+
12
+ return twMerge([defaultClasses, props.class])
13
+ })
14
+ </script>
15
+
16
+ <template>
17
+ <div :class="resolvedClasses">
18
+ <slot />
19
+ </div>
20
+ </template>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts"></script>
2
+
3
+ <template>
4
+ <nav
5
+ class="light:bg-white light:border-black/10 font-outfit sticky top-0 z-50 h-10 shrink-0 border-b dark:border-white/5 dark:bg-black dark:text-white"
6
+ >
7
+ <slot />
8
+ </nav>
9
+ </template>
@@ -0,0 +1,7 @@
1
+ <script setup lang="ts">
2
+ const { actions } = useNavbarAdminActions()
3
+ </script>
4
+
5
+ <template>
6
+ <component :is="action" v-for="(action, i) in actions" :key="i" />
7
+ </template>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts"></script>
2
+
3
+ <template>
4
+ <div class="grow">
5
+ <UApp>
6
+ <NuxtRouteAnnouncer />
7
+ <NuxtLoadingIndicator />
8
+
9
+ <NuxtLayout>
10
+ <slot />
11
+ </NuxtLayout>
12
+ </UApp>
13
+ </div>
14
+ </template>
15
+
16
+ <style>
17
+ html,
18
+ body {
19
+ height: 100%;
20
+ }
21
+
22
+ #__nuxt {
23
+ height: 100%;
24
+ display: flex;
25
+ flex-direction: column;
26
+ }
27
+ </style>
@@ -0,0 +1,86 @@
1
+ <script setup lang="ts">
2
+ import type { ClassNameValue } from 'tailwind-merge'
3
+ import { twMerge } from 'tailwind-merge'
4
+
5
+ interface Props {
6
+ class?: ClassNameValue
7
+ trackClass?: ClassNameValue
8
+ thumbClass?: ClassNameValue
9
+ hideDelay?: number
10
+ }
11
+
12
+ const props = withDefaults(defineProps<Props>(), {
13
+ hideDelay: 0,
14
+ class: '',
15
+ thumbClass: '',
16
+ trackClass: '',
17
+ })
18
+
19
+ const resolvedClasses = computed(() => {
20
+ const defaultClasses = `
21
+ h-full
22
+ transition-opacity
23
+ duration-300
24
+ `
25
+
26
+ return twMerge([defaultClasses, props.class])
27
+ })
28
+
29
+ const resolvedTrackClasses = computed(() => {
30
+ const defaultClasses = `
31
+ relative
32
+ w-2
33
+ rounded-full
34
+ bg-transparent
35
+ transition-colors duration-300
36
+ hover:bg-black/10
37
+ `
38
+
39
+ return twMerge([defaultClasses, props.trackClass])
40
+ })
41
+
42
+ const resolvedThumbClasses = computed(() => {
43
+ const defaultClasses = `
44
+ rounded-full
45
+ bg-black/40
46
+ hover:bg-black/40
47
+ active:bg-black/60
48
+ `
49
+
50
+ return twMerge([defaultClasses, props.thumbClass])
51
+ })
52
+ </script>
53
+
54
+ <template>
55
+ <RekaScrollAreaRoot
56
+ :scroll-hide-delay="props.hideDelay"
57
+ class="h-full shrink-0 overflow-hidden transition-opacity duration-300"
58
+ >
59
+ <RekaScrollAreaViewport :class="resolvedClasses">
60
+ <slot />
61
+ </RekaScrollAreaViewport>
62
+
63
+ <Transition name="fade">
64
+ <RekaScrollAreaScrollbar
65
+ :class="resolvedTrackClasses"
66
+ orientation="vertical"
67
+ >
68
+ <RekaScrollAreaThumb :class="resolvedThumbClasses" />
69
+ </RekaScrollAreaScrollbar>
70
+ </Transition>
71
+
72
+ <RekaScrollAreaCorner />
73
+ </RekaScrollAreaRoot>
74
+ </template>
75
+
76
+ <style scoped>
77
+ .fade-enter-active,
78
+ .fade-leave-active {
79
+ transition: opacity 0.2s;
80
+ }
81
+
82
+ .fade-enter-from,
83
+ .fade-leave-to {
84
+ opacity: 0;
85
+ }
86
+ </style>