@xleddyl/nuxt-cms 0.1.31 → 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/README.md CHANGED
@@ -5,7 +5,7 @@ nuxt-cms is a Nuxt module that leverages the Nitro server to ship a lightweight
5
5
  - **Zero extra infrastructure**: the CMS runs inside your app's Nitro server; you deploy one thing.
6
6
  - **Content types in code**: a `cms.config.ts` with `defineCmsConfig()` declares collections, single documents, relations, blocks and translatable fields; database schema, migrations and TypeScript types are generated from it.
7
7
  - **Admin panel at `/cms`**: entry editing with validation, drafts, media library (S3-compatible storage, or a local mode backed directly by your `public/` folder), single-admin auth from env credentials.
8
- - **Public GraphQL API**: read-only, typed end-to-end via gql.tada, with filtering, sorting and pagination.
8
+ - **Public GraphQL API**: read-only, typed end-to-end via gql.tada, with filtering, sorting and pagination; fields marked `private` stay out of it.
9
9
  - **SQLite, Postgres or libSQL/Turso**: a local file database by default, one config line to switch (including remote SQLite over the network).
10
10
 
11
11
  ## Installation
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@xleddyl/nuxt-cms",
3
3
  "configKey": "cms",
4
- "version": "0.1.31",
4
+ "version": "0.1.32",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -10,7 +10,7 @@ import { introspectionFromSchema, buildSchema } from 'graphql';
10
10
  import { minifyIntrospection, outputIntrospectionFile } from 'gql.tada/internal';
11
11
  import { createJiti } from 'jiti';
12
12
  import { typeName, blockTypeName, blockUnionName, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
13
- import { isMultiSelect, isTranslatableField, isTranslatableMediaField, DEFAULT_MEDIA_MAX_FILE_SIZE } from '../dist/runtime/shared/index.js';
13
+ import { isMultiSelect, isTranslatableField, isTranslatableMediaField, isPrivateField, DEFAULT_MEDIA_MAX_FILE_SIZE } from '../dist/runtime/shared/index.js';
14
14
  import { scanMediaDirectory, readMediaFileMeta } from '../dist/runtime/server/utils/media-sync.js';
15
15
 
16
16
  async function collectMediaManifest(root) {
@@ -169,6 +169,8 @@ function validateConfig(config, i18n) {
169
169
  }
170
170
  if (blockField.translatable)
171
171
  errors.push(`${bfat}: translatable fields are not supported inside blocks`);
172
+ if (blockField.private)
173
+ errors.push(`${bfat}: private fields are not supported inside blocks`);
172
174
  if (blockField.type === "select" && !blockField.options?.length) {
173
175
  errors.push(`${bfat}: select requires a non-empty options array`);
174
176
  }
@@ -416,6 +418,7 @@ ${lines.join("\n")}
416
418
  function entryTs(config, name, entry) {
417
419
  const lines = [" id: string"];
418
420
  for (const [key, field] of Object.entries(entry.fields)) {
421
+ if (isPrivateField(field)) continue;
419
422
  lines.push(` ${key}: ${fieldTsType(config, name, key, field)}`);
420
423
  }
421
424
  if (entry.kind === "collection") lines.push(" createdAt: string");
@@ -440,7 +443,8 @@ function renderTypesFile(config) {
440
443
  ];
441
444
  for (const [name, entry] of Object.entries(config)) {
442
445
  for (const [key, field] of Object.entries(entry.fields)) {
443
- if (field.type === "blocks") parts.push(...blockTypesTs(name, key, field));
446
+ if (field.type === "blocks" && !isPrivateField(field))
447
+ parts.push(...blockTypesTs(name, key, field));
444
448
  }
445
449
  parts.push(entryTs(config, name, entry));
446
450
  }
@@ -21,6 +21,7 @@ import * as cmsTables from "#cms-tables";
21
21
  import { useRuntimeConfig } from "#imports";
22
22
  import {
23
23
  decodeTranslatableMedia,
24
+ isPrivateField,
24
25
  isTranslatableMediaField,
25
26
  mediaPublicUrl,
26
27
  mediaTypeFor,
@@ -257,6 +258,7 @@ function mediaObject(key, row) {
257
258
  function entryResolvers(config, name, entry) {
258
259
  const resolvers = {};
259
260
  for (const [key, field] of Object.entries(entry.fields)) {
261
+ if (isPrivateField(field)) continue;
260
262
  if (field.type === "relation" && field.cardinality === "many-to-many") {
261
263
  resolvers[key] = async (parent, _args, ctx) => {
262
264
  const rows = await loadManyToMany(ctx, name, key, field).load(parent.id);
@@ -301,7 +303,8 @@ export function buildCmsSchema() {
301
303
  const fieldLevel = entryResolvers(config, name, entry);
302
304
  if (Object.keys(fieldLevel).length) typeResolvers[gqlType] = fieldLevel;
303
305
  for (const [key, field] of Object.entries(entry.fields)) {
304
- if (field.type === "blocks") Object.assign(typeResolvers, blockResolvers(name, key, field));
306
+ if (field.type === "blocks" && !isPrivateField(field))
307
+ Object.assign(typeResolvers, blockResolvers(name, key, field));
305
308
  }
306
309
  if (entry.kind === "single") {
307
310
  queryResolvers[name] = async (_, args) => {
@@ -1,4 +1,4 @@
1
- import { isTranslatableField } from "./index.js";
1
+ import { isPrivateField, isTranslatableField } from "./index.js";
2
2
  export function typeName(name) {
3
3
  return name.replace(/(?:^|_)([a-z0-9])/gi, (_, c) => c.toUpperCase());
4
4
  }
@@ -21,7 +21,7 @@ function scalarFor(field) {
21
21
  }
22
22
  }
23
23
  function filterScalarFor(field) {
24
- if (isTranslatableField(field)) return null;
24
+ if (isPrivateField(field) || isTranslatableField(field)) return null;
25
25
  switch (field.type) {
26
26
  case "json":
27
27
  return null;
@@ -64,6 +64,7 @@ function fieldSdl(config, entryName, key, field) {
64
64
  function entrySdl(config, name, entry) {
65
65
  const lines = [" id: ID!"];
66
66
  for (const [key, field] of Object.entries(entry.fields)) {
67
+ if (isPrivateField(field)) continue;
67
68
  lines.push(fieldSdl(config, name, key, field));
68
69
  }
69
70
  if (entry.kind === "collection") lines.push(" createdAt: String!");
@@ -127,7 +128,8 @@ export function renderGraphqlSdl(config) {
127
128
  const gqlType = typeName(name);
128
129
  types.push(entrySdl(config, name, entry));
129
130
  for (const [key, field] of Object.entries(entry.fields)) {
130
- if (field.type === "blocks") types.push(...blocksSdl(name, key, field));
131
+ if (field.type === "blocks" && !isPrivateField(field))
132
+ types.push(...blocksSdl(name, key, field));
131
133
  }
132
134
  if (entry.kind === "single") {
133
135
  queryLines.push(` ${name}(locale: String): ${gqlType}`);
@@ -46,6 +46,7 @@ export interface FieldConfig {
46
46
  label: string;
47
47
  type: FieldType;
48
48
  required?: boolean;
49
+ private?: boolean;
49
50
  textarea?: boolean;
50
51
  integer?: boolean;
51
52
  translatable?: boolean;
@@ -59,6 +60,7 @@ export interface FieldConfig {
59
60
  cardinality?: 'many-to-one' | 'one-to-one' | 'many-to-many';
60
61
  onDelete?: 'set null' | 'cascade' | 'restrict';
61
62
  }
63
+ export declare function isPrivateField(field: FieldConfig): boolean;
62
64
  export declare function isTranslatableField(field: FieldConfig): boolean;
63
65
  export declare function isTranslatableMediaField(field: FieldConfig): boolean;
64
66
  export declare function decodeTranslatableMedia(value: unknown, defaultLocale: string): Record<string, string> | null;
@@ -82,6 +84,7 @@ export type CmsConfig = Record<string, CmsEntry>;
82
84
  interface FieldInputBase {
83
85
  label: string;
84
86
  required?: boolean;
87
+ private?: boolean;
85
88
  }
86
89
  export interface TextFieldInput extends FieldInputBase {
87
90
  type: 'text';
@@ -129,7 +132,7 @@ export interface RelationFieldInput extends FieldInputBase {
129
132
  cardinality?: 'many-to-one' | 'one-to-one' | 'many-to-many';
130
133
  onDelete?: 'set null' | 'cascade' | 'restrict';
131
134
  }
132
- export type BlockFieldInput = Omit<TextFieldInput, 'translatable'> | Omit<RichtextFieldInput, 'translatable'> | NumberFieldInput | BooleanFieldInput | DateFieldInput | EmailFieldInput | SelectFieldInput | JsonFieldInput | Omit<MediaFieldInput, 'translatable'>;
135
+ export type BlockFieldInput = Omit<TextFieldInput, 'translatable' | 'private'> | Omit<RichtextFieldInput, 'translatable' | 'private'> | Omit<NumberFieldInput, 'private'> | Omit<BooleanFieldInput, 'private'> | Omit<DateFieldInput, 'private'> | Omit<EmailFieldInput, 'private'> | Omit<SelectFieldInput, 'private'> | Omit<JsonFieldInput, 'private'> | Omit<MediaFieldInput, 'translatable' | 'private'>;
133
136
  export interface BlockInput {
134
137
  label: string;
135
138
  fields: Record<string, BlockFieldInput>;
@@ -61,6 +61,9 @@ export function normalizeMediaFolder(value) {
61
61
  const segments = value.split("/").map(slugify).filter(Boolean).slice(0, MEDIA_FOLDER_MAX_DEPTH);
62
62
  return segments.length ? segments.join("/") : null;
63
63
  }
64
+ export function isPrivateField(field) {
65
+ return !!field.private;
66
+ }
64
67
  export function isTranslatableField(field) {
65
68
  return !!field.translatable && (field.type === "text" || field.type === "richtext" || field.type === "media");
66
69
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xleddyl/nuxt-cms",
3
- "version": "0.1.31",
3
+ "version": "0.1.32",
4
4
  "description": "Lightweight CMS that ships with your Nuxt app: runs on the Nitro server, content types defined in code, /cms admin panel, GraphQL API, SQLite or Postgres. No external CMS needed!",
5
5
  "license": "MIT",
6
6
  "author": "Edoardo Alberti (https://github.com/xleddyl)",