@xleddyl/nuxt-cms 0.1.40 → 0.1.42
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 +4 -3
- package/dist/module.json +1 -1
- package/dist/module.mjs +261 -102
- package/dist/runtime/app/composables/cms-entry-disabled.d.ts +19 -0
- package/dist/runtime/app/composables/cms-entry-disabled.js +16 -0
- package/dist/runtime/app/composables/cms-entry.d.ts +19 -0
- package/dist/runtime/app/composables/cms-entry.js +50 -0
- package/dist/runtime/app/composables/cms-query-disabled.d.ts +7 -2
- package/dist/runtime/app/composables/cms-query-disabled.js +7 -3
- package/dist/runtime/app/composables/cms-query.d.ts +7 -2
- package/dist/runtime/app/composables/cms-query.js +8 -3
- package/dist/runtime/server/utils/graphql.js +7 -2
- package/dist/runtime/shared/graphql-sdl.d.ts +2 -3
- package/dist/runtime/shared/graphql-sdl.js +43 -24
- package/dist/runtime/shared/index.d.ts +3 -0
- package/dist/runtime/shared/index.js +9 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,8 +57,9 @@ Then declare your content types in a `cms.config.ts` at the project root with `d
|
|
|
57
57
|
### Disabling the CMS
|
|
58
58
|
|
|
59
59
|
Keep the module in `modules[]` at all times and turn it off with the `enabled` option or the
|
|
60
|
-
`NUXT_CMS_ENABLED` env var. When disabled the module registers no-op
|
|
61
|
-
nothing else, so components can call them unconditionally and simply render
|
|
60
|
+
`NUXT_CMS_ENABLED` env var. When disabled the module registers no-op query composables and keeps the
|
|
61
|
+
generated types, and nothing else, so components can call them unconditionally and simply render
|
|
62
|
+
their empty states:
|
|
62
63
|
|
|
63
64
|
```ts
|
|
64
65
|
export default defineNuxtConfig({
|
|
@@ -97,7 +98,7 @@ Full documentation lives in [`docs/`](docs/README.md):
|
|
|
97
98
|
- [Configuration](docs/configuration.md) — every `cms.*` option and the `NUXT_CMS_*` env vars.
|
|
98
99
|
- [Database](docs/database.md) — SQLite, Postgres, libSQL/Turso and D1 drivers, migrations, studio.
|
|
99
100
|
- [Schema](docs/schema.md) — `defineCmsConfig`, entries, field types, relations, blocks, i18n.
|
|
100
|
-
- [Querying content](docs/querying.md) — GraphQL API, `useCms` / `$cmsQuery`, filters, sorting, pagination.
|
|
101
|
+
- [Querying content](docs/querying.md) — GraphQL API, `useCmsSingle` / `useCmsCollection` / `useCms` / `$cmsQuery`, filters, sorting, pagination.
|
|
101
102
|
- [Admin panel & security](docs/admin.md) — pages, authentication, sessions, admin REST API.
|
|
102
103
|
- [Media](docs/media.md) — S3-compatible storage or local mode backed by your `public/` folder, upload flow, allowed file types.
|
|
103
104
|
- [Deployment](docs/deployment.md) — host/driver matrix, migrations on serverless, horizontal scaling, Cloudflare Workers.
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -3,14 +3,14 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
4
|
import { join, isAbsolute, resolve, relative, dirname } from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
|
-
import { defineNuxtModule, createResolver, useLogger, addImports,
|
|
6
|
+
import { defineNuxtModule, createResolver, useLogger, addImports, addTemplate, addServerPlugin, addTypeTemplate, addVitePlugin, addComponentsDir, addRouteMiddleware, extendPages, addServerHandler, resolvePath } from '@nuxt/kit';
|
|
7
7
|
import tailwindcss from '@tailwindcss/vite';
|
|
8
8
|
import svgLoader from 'vite-svg-loader';
|
|
9
9
|
import { introspectionFromSchema, buildSchema } from 'graphql';
|
|
10
10
|
import { minifyIntrospection, outputIntrospectionFile } from 'gql.tada/internal';
|
|
11
11
|
import { createJiti } from 'jiti';
|
|
12
|
-
import { typeName, blockTypeName,
|
|
13
|
-
import {
|
|
12
|
+
import { typeName, blockTypeName, blocksFieldTypeName, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
|
|
13
|
+
import { isTranslatableField, isTranslatableMediaField, isMultiSelect, fieldConditions, isRequiredField, isPrivateField, mediaTypeFilter, 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
|
import { createHash } from 'node:crypto';
|
|
16
16
|
import { readFile } from 'node:fs/promises';
|
|
@@ -450,6 +450,84 @@ ${[mediaTableExpr(dialect), ...tables, ...joins].join(
|
|
|
450
450
|
`;
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
const MEDIA_SELECTION = "key url type alt folder mime size width height";
|
|
454
|
+
function blocksSelection(entryName, key, field) {
|
|
455
|
+
const parts = ["type"];
|
|
456
|
+
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
457
|
+
const fields = Object.entries(block.fields).map(
|
|
458
|
+
([blockFieldKey, blockField]) => blockField.type === "media" ? `${blockFieldKey} { ${MEDIA_SELECTION} }` : blockFieldKey
|
|
459
|
+
);
|
|
460
|
+
if (!fields.length) continue;
|
|
461
|
+
parts.push(`... on ${blockTypeName(entryName, key, blockName)} { ${fields.join(" ")} }`);
|
|
462
|
+
}
|
|
463
|
+
return parts.join(" ");
|
|
464
|
+
}
|
|
465
|
+
function entrySelection(config, name, entry, withRelations) {
|
|
466
|
+
const parts = ["id"];
|
|
467
|
+
for (const [key, field] of Object.entries(entry.fields)) {
|
|
468
|
+
if (isPrivateField(field)) continue;
|
|
469
|
+
if (field.type === "relation") {
|
|
470
|
+
const target = config[field.to];
|
|
471
|
+
if (!withRelations || !target) continue;
|
|
472
|
+
parts.push(`${key} { ${entrySelection(config, field.to, target, false)} }`);
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
if (field.type === "media") {
|
|
476
|
+
parts.push(`${key} { ${MEDIA_SELECTION} }`);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
if (field.type === "blocks") {
|
|
480
|
+
parts.push(`${key} { ${blocksSelection(name, key, field)} }`);
|
|
481
|
+
continue;
|
|
482
|
+
}
|
|
483
|
+
parts.push(key);
|
|
484
|
+
}
|
|
485
|
+
if (entry.kind === "collection") parts.push("createdAt");
|
|
486
|
+
parts.push("updatedAt");
|
|
487
|
+
return parts.join(" ");
|
|
488
|
+
}
|
|
489
|
+
function singleQuery(config, name, entry) {
|
|
490
|
+
const selection = entrySelection(config, name, entry, true);
|
|
491
|
+
return `query CmsSingle($locale: String) { ${name}(locale: $locale) { ${selection} } }`;
|
|
492
|
+
}
|
|
493
|
+
function collectionQuery(config, name, entry) {
|
|
494
|
+
const selection = entrySelection(config, name, entry, true);
|
|
495
|
+
const gqlType = typeName(name);
|
|
496
|
+
const args = [
|
|
497
|
+
"$locale: String",
|
|
498
|
+
`$filters: ${gqlType}Filters`,
|
|
499
|
+
`$sort: [${gqlType}Sort!]`,
|
|
500
|
+
"$limit: Int",
|
|
501
|
+
"$offset: Int"
|
|
502
|
+
].join(", ");
|
|
503
|
+
return `query CmsCollection(${args}) { ${name}(locale: $locale, filters: $filters, sort: $sort, limit: $limit, offset: $offset) { ${selection} } }`;
|
|
504
|
+
}
|
|
505
|
+
function renderQueriesFile(config) {
|
|
506
|
+
const singles = [];
|
|
507
|
+
const collections = [];
|
|
508
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
509
|
+
const line = ` ${JSON.stringify(name)}: ${JSON.stringify(
|
|
510
|
+
entry.kind === "single" ? singleQuery(config, name, entry) : collectionQuery(config, name, entry)
|
|
511
|
+
)},`;
|
|
512
|
+
if (entry.kind === "single") singles.push(line);
|
|
513
|
+
else collections.push(line);
|
|
514
|
+
}
|
|
515
|
+
return [
|
|
516
|
+
`export const cmsSingleQueries: Record<string, string> = {`,
|
|
517
|
+
...singles,
|
|
518
|
+
`}`,
|
|
519
|
+
``,
|
|
520
|
+
`export const cmsCollectionQueries: Record<string, string> = {`,
|
|
521
|
+
...collections,
|
|
522
|
+
`}`,
|
|
523
|
+
``
|
|
524
|
+
].join("\n");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function mediaTsType(field) {
|
|
528
|
+
const types = mediaTypeFilter(field.mediaType);
|
|
529
|
+
return types ? `CmsMedia<${types.map((type) => `'${type}'`).join(" | ")}>` : "CmsMedia";
|
|
530
|
+
}
|
|
453
531
|
function scalarTsType(field) {
|
|
454
532
|
switch (field.type) {
|
|
455
533
|
case "number":
|
|
@@ -470,13 +548,13 @@ function fieldTsType(config, entryName, key, field) {
|
|
|
470
548
|
if (field.cardinality === "many-to-many") return `${target}[]`;
|
|
471
549
|
return isRequiredField(field) && !config[field.to]?.drafts ? target : `${target} | null`;
|
|
472
550
|
}
|
|
473
|
-
if (field.type === "media") return
|
|
551
|
+
if (field.type === "media") return `${mediaTsType(field)} | null`;
|
|
474
552
|
if (field.type === "select" && field.multiple) {
|
|
475
553
|
const union = field.options.map((o) => JSON.stringify(o)).join(" | ");
|
|
476
554
|
return `(${union})[]`;
|
|
477
555
|
}
|
|
478
556
|
if (field.type === "blocks") {
|
|
479
|
-
const union =
|
|
557
|
+
const union = blocksFieldTypeName(entryName, key);
|
|
480
558
|
return isRequiredField(field) ? `${union}[]` : `${union}[] | null`;
|
|
481
559
|
}
|
|
482
560
|
if (isTranslatableField(field)) return isRequiredField(field) ? "string" : "string | null";
|
|
@@ -492,7 +570,7 @@ function blockTypesTs(entryName, key, field) {
|
|
|
492
570
|
const lines = [` __typename?: '${name}'`, ` type: '${blockName}'`];
|
|
493
571
|
for (const [blockFieldKey, blockField] of Object.entries(block.fields)) {
|
|
494
572
|
if (blockField.type === "media") {
|
|
495
|
-
lines.push(` ${blockFieldKey}:
|
|
573
|
+
lines.push(` ${blockFieldKey}: ${mediaTsType(blockField)} | null`);
|
|
496
574
|
continue;
|
|
497
575
|
}
|
|
498
576
|
const base = scalarTsType(blockField);
|
|
@@ -503,7 +581,7 @@ ${lines.join("\n")}
|
|
|
503
581
|
}`);
|
|
504
582
|
}
|
|
505
583
|
if (members.length)
|
|
506
|
-
defs.push(`export type ${
|
|
584
|
+
defs.push(`export type ${blocksFieldTypeName(entryName, key)} = ${members.join(" | ")}`);
|
|
507
585
|
return defs;
|
|
508
586
|
}
|
|
509
587
|
function entryTs(config, name, entry) {
|
|
@@ -518,12 +596,61 @@ function entryTs(config, name, entry) {
|
|
|
518
596
|
${lines.join("\n")}
|
|
519
597
|
}`;
|
|
520
598
|
}
|
|
599
|
+
function relationKeys(entry) {
|
|
600
|
+
return Object.entries(entry.fields).filter(([, field]) => field.type === "relation" && !isPrivateField(field)).map(([key]) => key);
|
|
601
|
+
}
|
|
602
|
+
function quotedKeys(keys) {
|
|
603
|
+
return keys.map((key) => `'${key}'`).join(" | ");
|
|
604
|
+
}
|
|
605
|
+
function shallowTypeTs(config, name) {
|
|
606
|
+
const target = typeName(name);
|
|
607
|
+
const entry = config[name];
|
|
608
|
+
const keys = entry ? relationKeys(entry) : [];
|
|
609
|
+
return keys.length ? `Omit<${target}, ${quotedKeys(keys)}>` : target;
|
|
610
|
+
}
|
|
611
|
+
function autoTypeTs(config, name, entry) {
|
|
612
|
+
const auto = `${typeName(name)}Auto`;
|
|
613
|
+
const keys = relationKeys(entry);
|
|
614
|
+
if (!keys.length) return `export type ${auto} = ${typeName(name)}`;
|
|
615
|
+
const lines = keys.map((key) => {
|
|
616
|
+
const field = entry.fields[key];
|
|
617
|
+
const shallow = shallowTypeTs(config, field.to);
|
|
618
|
+
if (field.cardinality === "many-to-many") return ` ${key}: ${shallow}[]`;
|
|
619
|
+
const nonNull = isRequiredField(field) && !config[field.to]?.drafts;
|
|
620
|
+
return ` ${key}: ${shallow}${nonNull ? "" : " | null"}`;
|
|
621
|
+
});
|
|
622
|
+
return `export type ${auto} = Omit<${typeName(name)}, ${quotedKeys(keys)}> & {
|
|
623
|
+
${lines.join(
|
|
624
|
+
"\n"
|
|
625
|
+
)}
|
|
626
|
+
}`;
|
|
627
|
+
}
|
|
628
|
+
function entryMapsTs(config) {
|
|
629
|
+
const singles = [];
|
|
630
|
+
const collections = [];
|
|
631
|
+
for (const [name, entry] of Object.entries(config)) {
|
|
632
|
+
const line = ` ${JSON.stringify(name)}: ${typeName(name)}Auto`;
|
|
633
|
+
if (entry.kind === "single") singles.push(line);
|
|
634
|
+
else collections.push(line);
|
|
635
|
+
}
|
|
636
|
+
return [
|
|
637
|
+
`export interface CmsSingleTypes {
|
|
638
|
+
${singles.join("\n")}
|
|
639
|
+
}`,
|
|
640
|
+
`export interface CmsCollectionTypes {
|
|
641
|
+
${collections.join("\n")}
|
|
642
|
+
}`,
|
|
643
|
+
`export type CmsSingleName = keyof CmsSingleTypes`,
|
|
644
|
+
`export type CmsCollectionName = keyof CmsCollectionTypes`
|
|
645
|
+
];
|
|
646
|
+
}
|
|
521
647
|
function renderTypesFile(config) {
|
|
522
648
|
const parts = [
|
|
523
|
-
`export
|
|
649
|
+
`export type CmsMediaType = 'image' | 'video' | 'file'`,
|
|
650
|
+
`export interface CmsMedia<T extends CmsMediaType = CmsMediaType> {
|
|
524
651
|
key: string
|
|
525
652
|
url: string | null
|
|
526
|
-
type:
|
|
653
|
+
type: T
|
|
527
654
|
alt: string | null
|
|
528
655
|
folder: string | null
|
|
529
656
|
mime: string | null
|
|
@@ -539,6 +666,8 @@ function renderTypesFile(config) {
|
|
|
539
666
|
}
|
|
540
667
|
parts.push(entryTs(config, name, entry));
|
|
541
668
|
}
|
|
669
|
+
for (const [name, entry] of Object.entries(config)) parts.push(autoTypeTs(config, name, entry));
|
|
670
|
+
parts.push(...entryMapsTs(config));
|
|
542
671
|
return `${parts.join("\n\n")}
|
|
543
672
|
`;
|
|
544
673
|
}
|
|
@@ -627,6 +756,104 @@ function resolveModuleOptions(options) {
|
|
|
627
756
|
}
|
|
628
757
|
};
|
|
629
758
|
}
|
|
759
|
+
const moduleRequire = createRequire(import.meta.url);
|
|
760
|
+
function resolveImport(specifier) {
|
|
761
|
+
try {
|
|
762
|
+
return fileURLToPath(import.meta.resolve(specifier)).replace(/\\/g, "/");
|
|
763
|
+
} catch {
|
|
764
|
+
return moduleRequire.resolve(specifier).replace(/\\/g, "/");
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async function loadCmsConfig(nuxt, resolver, configPathOption, i18n, logger) {
|
|
768
|
+
const configPath = await resolvePath(configPathOption, { cwd: nuxt.options.rootDir });
|
|
769
|
+
nuxt.options.alias["#nuxt-cms"] = resolver.resolve("./runtime/shared/index");
|
|
770
|
+
nuxt.options.watch.push(configPath);
|
|
771
|
+
let cmsConfig = {};
|
|
772
|
+
if (existsSync(configPath)) {
|
|
773
|
+
nuxt.options.alias["#cms-config"] = configPath;
|
|
774
|
+
const jiti = createJiti(import.meta.url, {
|
|
775
|
+
moduleCache: false,
|
|
776
|
+
alias: { "#nuxt-cms": resolver.resolve("./runtime/shared/index") }
|
|
777
|
+
});
|
|
778
|
+
cmsConfig = await jiti.import(configPath, { default: true });
|
|
779
|
+
} else {
|
|
780
|
+
logger.warn(
|
|
781
|
+
`[nuxt-cms] Config file not found: ${configPath}. Using an empty registry \u2014 create a ${configPathOption}.ts with defineCmsConfig().`
|
|
782
|
+
);
|
|
783
|
+
nuxt.options.alias["#cms-config"] = resolver.resolve("./runtime/shared/empty-config");
|
|
784
|
+
}
|
|
785
|
+
const configErrors = validateConfig(cmsConfig, i18n);
|
|
786
|
+
if (configErrors.length) {
|
|
787
|
+
for (const error of configErrors) logger.error(error);
|
|
788
|
+
throw new Error(
|
|
789
|
+
`[nuxt-cms] Invalid cms config (${configErrors.length} error${configErrors.length > 1 ? "s" : ""})`
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
return cmsConfig;
|
|
793
|
+
}
|
|
794
|
+
function addCmsTypeTemplates(nuxt, cmsConfig) {
|
|
795
|
+
addTemplate({
|
|
796
|
+
filename: "cms/schema.graphql",
|
|
797
|
+
write: true,
|
|
798
|
+
getContents: () => renderGraphqlSdl(cmsConfig)
|
|
799
|
+
});
|
|
800
|
+
const typesTemplate = addTemplate({
|
|
801
|
+
filename: "cms/types.ts",
|
|
802
|
+
write: true,
|
|
803
|
+
getContents: () => renderTypesFile(cmsConfig)
|
|
804
|
+
});
|
|
805
|
+
nuxt.options.alias["#cms-types"] = typesTemplate.dst;
|
|
806
|
+
const queriesTemplate = addTemplate({
|
|
807
|
+
filename: "cms/queries.ts",
|
|
808
|
+
write: true,
|
|
809
|
+
getContents: () => renderQueriesFile(cmsConfig)
|
|
810
|
+
});
|
|
811
|
+
nuxt.options.alias["#cms-queries"] = queriesTemplate.dst;
|
|
812
|
+
addTemplate({
|
|
813
|
+
filename: "cms/graphql-env.d.ts",
|
|
814
|
+
write: true,
|
|
815
|
+
getContents: () => {
|
|
816
|
+
const introspection = minifyIntrospection(
|
|
817
|
+
introspectionFromSchema(buildSchema(renderGraphqlSdl(cmsConfig)))
|
|
818
|
+
);
|
|
819
|
+
return outputIntrospectionFile(introspection, {
|
|
820
|
+
fileType: ".d.ts",
|
|
821
|
+
shouldPreprocess: true
|
|
822
|
+
}).split("import * as gqlTada from 'gql.tada';")[0];
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
const gqlTadaTypesPath = resolveImport("gql.tada").replace(/\.[mc]?js$/, "");
|
|
826
|
+
const graphqlTemplate = addTemplate({
|
|
827
|
+
filename: "cms/graphql.ts",
|
|
828
|
+
write: true,
|
|
829
|
+
getContents: () => [
|
|
830
|
+
`import type { initGraphQLTada, ResultOf, VariablesOf } from '${gqlTadaTypesPath}'`,
|
|
831
|
+
`import type { introspection } from './graphql-env'`,
|
|
832
|
+
``,
|
|
833
|
+
`export type CmsGraphql = initGraphQLTada<{`,
|
|
834
|
+
` introspection: introspection`,
|
|
835
|
+
` scalars: {`,
|
|
836
|
+
` JSON: unknown`,
|
|
837
|
+
` }`,
|
|
838
|
+
`}>`,
|
|
839
|
+
``,
|
|
840
|
+
`declare const graphql: CmsGraphql`,
|
|
841
|
+
``,
|
|
842
|
+
`// @ts-ignore: gql.tada's cache overload rejects this instantiation, but the parse overload still resolves`,
|
|
843
|
+
`export type CmsDocument<Query extends string> = ReturnType<typeof graphql<Query, []>>`,
|
|
844
|
+
``,
|
|
845
|
+
`export type CmsResult<Query extends string> = string extends Query`,
|
|
846
|
+
` ? Record<string, unknown>`,
|
|
847
|
+
` : ResultOf<CmsDocument<Query>>`,
|
|
848
|
+
``,
|
|
849
|
+
`export type CmsVariables<Query extends string> = string extends Query`,
|
|
850
|
+
` ? Record<string, unknown>`,
|
|
851
|
+
` : VariablesOf<CmsDocument<Query>>`,
|
|
852
|
+
``
|
|
853
|
+
].join("\n")
|
|
854
|
+
});
|
|
855
|
+
nuxt.options.alias["#cms-graphql"] = graphqlTemplate.dst;
|
|
856
|
+
}
|
|
630
857
|
const module$1 = defineNuxtModule({
|
|
631
858
|
meta: {
|
|
632
859
|
name: "@xleddyl/nuxt-cms",
|
|
@@ -671,11 +898,18 @@ const module$1 = defineNuxtModule({
|
|
|
671
898
|
const logger = useLogger("nuxt-cms");
|
|
672
899
|
const resolved = resolveModuleOptions(options);
|
|
673
900
|
if (!resolveCmsEnabled(options.enabled, process.env[CMS_ENABLED_ENV])) {
|
|
674
|
-
const
|
|
901
|
+
const queryStub = resolver.resolve("./runtime/app/composables/cms-query-disabled");
|
|
902
|
+
const entryStub = resolver.resolve("./runtime/app/composables/cms-entry-disabled");
|
|
675
903
|
addImports([
|
|
676
|
-
{ name: "useCms", from:
|
|
677
|
-
{ name: "$cmsQuery", from:
|
|
904
|
+
{ name: "useCms", from: queryStub },
|
|
905
|
+
{ name: "$cmsQuery", from: queryStub },
|
|
906
|
+
{ name: "useCmsSingle", from: entryStub },
|
|
907
|
+
{ name: "useCmsCollection", from: entryStub }
|
|
678
908
|
]);
|
|
909
|
+
addCmsTypeTemplates(
|
|
910
|
+
nuxt,
|
|
911
|
+
await loadCmsConfig(nuxt, resolver, resolved.configPath, resolved.i18n, logger)
|
|
912
|
+
);
|
|
679
913
|
nuxt.options.runtimeConfig.public.cms = {
|
|
680
914
|
mediaBaseUrl: resolved.media.publicBaseUrl,
|
|
681
915
|
mediaStorage: resolved.media.storage,
|
|
@@ -683,7 +917,7 @@ const module$1 = defineNuxtModule({
|
|
|
683
917
|
i18n: resolved.i18n
|
|
684
918
|
};
|
|
685
919
|
logger.info(
|
|
686
|
-
"[nuxt-cms] disabled: registering no-op
|
|
920
|
+
"[nuxt-cms] disabled: registering no-op query composables and generated types, skipping admin, server and database setup"
|
|
687
921
|
);
|
|
688
922
|
return;
|
|
689
923
|
}
|
|
@@ -708,38 +942,13 @@ const module$1 = defineNuxtModule({
|
|
|
708
942
|
"[nuxt-cms] media.storage is 's3' with credentials configured but no publicBaseUrl (media.publicBaseUrl or NUXT_PUBLIC_CMS_MEDIA_BASE_URL); uploaded media URLs will be null."
|
|
709
943
|
);
|
|
710
944
|
}
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
moduleCache: false,
|
|
719
|
-
alias: { "#nuxt-cms": resolver.resolve("./runtime/shared/index") }
|
|
720
|
-
});
|
|
721
|
-
cmsConfig = await jiti.import(configPath, { default: true });
|
|
722
|
-
} else {
|
|
723
|
-
logger.warn(
|
|
724
|
-
`[nuxt-cms] Config file not found: ${configPath}. Using an empty registry \u2014 create a ${resolved.configPath}.ts with defineCmsConfig().`
|
|
725
|
-
);
|
|
726
|
-
nuxt.options.alias["#cms-config"] = resolver.resolve("./runtime/shared/empty-config");
|
|
727
|
-
}
|
|
728
|
-
const configErrors = validateConfig(cmsConfig, resolved.i18n);
|
|
729
|
-
if (configErrors.length) {
|
|
730
|
-
for (const error of configErrors) logger.error(error);
|
|
731
|
-
throw new Error(
|
|
732
|
-
`[nuxt-cms] Invalid cms config (${configErrors.length} error${configErrors.length > 1 ? "s" : ""})`
|
|
733
|
-
);
|
|
734
|
-
}
|
|
735
|
-
const moduleRequire = createRequire(import.meta.url);
|
|
736
|
-
const resolveImport = (specifier) => {
|
|
737
|
-
try {
|
|
738
|
-
return fileURLToPath(import.meta.resolve(specifier)).replace(/\\/g, "/");
|
|
739
|
-
} catch {
|
|
740
|
-
return moduleRequire.resolve(specifier).replace(/\\/g, "/");
|
|
741
|
-
}
|
|
742
|
-
};
|
|
945
|
+
const cmsConfig = await loadCmsConfig(
|
|
946
|
+
nuxt,
|
|
947
|
+
resolver,
|
|
948
|
+
resolved.configPath,
|
|
949
|
+
resolved.i18n,
|
|
950
|
+
logger
|
|
951
|
+
);
|
|
743
952
|
const schemaTemplate = addTemplate({
|
|
744
953
|
filename: "cms/schema.ts",
|
|
745
954
|
write: true,
|
|
@@ -750,64 +959,14 @@ const module$1 = defineNuxtModule({
|
|
|
750
959
|
)
|
|
751
960
|
});
|
|
752
961
|
nuxt.options.alias["#cms-tables"] = schemaTemplate.dst;
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
getContents: () => renderGraphqlSdl(cmsConfig)
|
|
757
|
-
});
|
|
758
|
-
const typesTemplate = addTemplate({
|
|
759
|
-
filename: "cms/types.ts",
|
|
760
|
-
write: true,
|
|
761
|
-
getContents: () => renderTypesFile(cmsConfig)
|
|
762
|
-
});
|
|
763
|
-
nuxt.options.alias["#cms-types"] = typesTemplate.dst;
|
|
764
|
-
addTemplate({
|
|
765
|
-
filename: "cms/graphql-env.d.ts",
|
|
766
|
-
write: true,
|
|
767
|
-
getContents: () => {
|
|
768
|
-
const introspection = minifyIntrospection(
|
|
769
|
-
introspectionFromSchema(buildSchema(renderGraphqlSdl(cmsConfig)))
|
|
770
|
-
);
|
|
771
|
-
return outputIntrospectionFile(introspection, {
|
|
772
|
-
fileType: ".d.ts",
|
|
773
|
-
shouldPreprocess: true
|
|
774
|
-
}).split("import * as gqlTada from 'gql.tada';")[0];
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
const gqlTadaTypesPath = resolveImport("gql.tada").replace(/\.[mc]?js$/, "");
|
|
778
|
-
const graphqlTemplate = addTemplate({
|
|
779
|
-
filename: "cms/graphql.ts",
|
|
780
|
-
write: true,
|
|
781
|
-
getContents: () => [
|
|
782
|
-
`import type { initGraphQLTada, ResultOf, VariablesOf } from '${gqlTadaTypesPath}'`,
|
|
783
|
-
`import type { introspection } from './graphql-env'`,
|
|
784
|
-
``,
|
|
785
|
-
`export type CmsGraphql = initGraphQLTada<{`,
|
|
786
|
-
` introspection: introspection`,
|
|
787
|
-
` scalars: {`,
|
|
788
|
-
` JSON: unknown`,
|
|
789
|
-
` }`,
|
|
790
|
-
`}>`,
|
|
791
|
-
``,
|
|
792
|
-
`declare const graphql: CmsGraphql`,
|
|
793
|
-
``,
|
|
794
|
-
`// @ts-ignore: gql.tada's cache overload rejects this instantiation, but the parse overload still resolves`,
|
|
795
|
-
`export type CmsDocument<Query extends string> = ReturnType<typeof graphql<Query, []>>`,
|
|
796
|
-
``,
|
|
797
|
-
`export type CmsResult<Query extends string> = string extends Query`,
|
|
798
|
-
` ? Record<string, unknown>`,
|
|
799
|
-
` : ResultOf<CmsDocument<Query>>`,
|
|
800
|
-
``,
|
|
801
|
-
`export type CmsVariables<Query extends string> = string extends Query`,
|
|
802
|
-
` ? Record<string, unknown>`,
|
|
803
|
-
` : VariablesOf<CmsDocument<Query>>`,
|
|
804
|
-
``
|
|
805
|
-
].join("\n")
|
|
806
|
-
});
|
|
807
|
-
nuxt.options.alias["#cms-graphql"] = graphqlTemplate.dst;
|
|
962
|
+
addCmsTypeTemplates(nuxt, cmsConfig);
|
|
963
|
+
const queryComposables = resolver.resolve("./runtime/app/composables/cms-query");
|
|
964
|
+
const entryComposables = resolver.resolve("./runtime/app/composables/cms-entry");
|
|
808
965
|
addImports([
|
|
809
|
-
{ name: "useCms", from:
|
|
810
|
-
{ name: "$cmsQuery", from:
|
|
966
|
+
{ name: "useCms", from: queryComposables },
|
|
967
|
+
{ name: "$cmsQuery", from: queryComposables },
|
|
968
|
+
{ name: "useCmsSingle", from: entryComposables },
|
|
969
|
+
{ name: "useCmsCollection", from: entryComposables }
|
|
811
970
|
]);
|
|
812
971
|
const {
|
|
813
972
|
driver,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AsyncData } from 'nuxt/app';
|
|
2
|
+
import type { CmsCollectionName, CmsCollectionTypes, CmsSingleName, CmsSingleTypes } from '#cms-types';
|
|
3
|
+
import type { CmsAsyncDataOptions } from './cms-query-disabled.js';
|
|
4
|
+
export interface CmsSortInput<Entry> {
|
|
5
|
+
field: Extract<keyof Entry, string>;
|
|
6
|
+
direction?: 'asc' | 'desc';
|
|
7
|
+
}
|
|
8
|
+
export interface CmsSingleOptions<ResT, DefaultT> extends CmsAsyncDataOptions<ResT, DefaultT> {
|
|
9
|
+
locale?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CmsCollectionOptions<ResT, DefaultT, Entry> extends CmsAsyncDataOptions<ResT, DefaultT> {
|
|
12
|
+
locale?: string;
|
|
13
|
+
filters?: Record<string, unknown>;
|
|
14
|
+
sort?: CmsSortInput<Entry>[];
|
|
15
|
+
limit?: number;
|
|
16
|
+
offset?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function useCmsSingle<K extends CmsSingleName, DefaultT = null>(name: K, options?: CmsSingleOptions<CmsSingleTypes[K] | null, DefaultT>): AsyncData<CmsSingleTypes[K] | DefaultT | null, Error | undefined>;
|
|
19
|
+
export declare function useCmsCollection<K extends CmsCollectionName, DefaultT = CmsCollectionTypes[K][]>(name: K, options?: CmsCollectionOptions<CmsCollectionTypes[K][], DefaultT, CmsCollectionTypes[K]>): AsyncData<CmsCollectionTypes[K][] | DefaultT, Error | undefined>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { useAsyncData } from "#imports";
|
|
2
|
+
export function useCmsSingle(name, options = {}) {
|
|
3
|
+
const { locale, key, default: fallback } = options;
|
|
4
|
+
return useAsyncData(
|
|
5
|
+
key ?? `cms-single:${String(name)}:${locale ?? ""}`,
|
|
6
|
+
async () => fallback ? fallback() : null
|
|
7
|
+
);
|
|
8
|
+
}
|
|
9
|
+
export function useCmsCollection(name, options = {}) {
|
|
10
|
+
const { locale, filters, sort, limit, offset, key, default: fallback } = options;
|
|
11
|
+
const variables = { locale, filters, sort, limit, offset };
|
|
12
|
+
return useAsyncData(
|
|
13
|
+
key ?? `cms-collection:${String(name)}:${JSON.stringify(variables)}`,
|
|
14
|
+
async () => fallback ? fallback() : []
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AsyncData } from 'nuxt/app';
|
|
2
|
+
import type { CmsCollectionName, CmsCollectionTypes, CmsSingleName, CmsSingleTypes } from '#cms-types';
|
|
3
|
+
import type { CmsAsyncDataOptions } from './cms-query.js';
|
|
4
|
+
export interface CmsSortInput<Entry> {
|
|
5
|
+
field: Extract<keyof Entry, string>;
|
|
6
|
+
direction?: 'asc' | 'desc';
|
|
7
|
+
}
|
|
8
|
+
export interface CmsSingleOptions<ResT, DefaultT> extends CmsAsyncDataOptions<ResT, DefaultT> {
|
|
9
|
+
locale?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface CmsCollectionOptions<ResT, DefaultT, Entry> extends CmsAsyncDataOptions<ResT, DefaultT> {
|
|
12
|
+
locale?: string;
|
|
13
|
+
filters?: Record<string, unknown>;
|
|
14
|
+
sort?: CmsSortInput<Entry>[];
|
|
15
|
+
limit?: number;
|
|
16
|
+
offset?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function useCmsSingle<K extends CmsSingleName, DefaultT = null>(name: K, options?: CmsSingleOptions<CmsSingleTypes[K] | null, DefaultT>): AsyncData<CmsSingleTypes[K] | DefaultT | null, Error | undefined>;
|
|
19
|
+
export declare function useCmsCollection<K extends CmsCollectionName, DefaultT = CmsCollectionTypes[K][]>(name: K, options?: CmsCollectionOptions<CmsCollectionTypes[K][], DefaultT, CmsCollectionTypes[K]>): AsyncData<CmsCollectionTypes[K][] | DefaultT, Error | undefined>;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { cmsCollectionQueries, cmsSingleQueries } from "#cms-queries";
|
|
2
|
+
import { useAsyncData } from "#imports";
|
|
3
|
+
import { $cmsQuery } from "./cms-query.js";
|
|
4
|
+
function unknownEntry(name) {
|
|
5
|
+
throw new Error(`[nuxt-cms] no generated query for "${name}"; check cms.config.ts`);
|
|
6
|
+
}
|
|
7
|
+
export function useCmsSingle(name, options = {}) {
|
|
8
|
+
const { locale, key, default: fallback, ...asyncDataOptions } = options;
|
|
9
|
+
const entryName = String(name);
|
|
10
|
+
const query = cmsSingleQueries[entryName];
|
|
11
|
+
return useAsyncData(
|
|
12
|
+
key ?? `cms-single:${entryName}:${locale ?? ""}`,
|
|
13
|
+
async () => {
|
|
14
|
+
if (!query) unknownEntry(entryName);
|
|
15
|
+
const result = await $cmsQuery(query, { locale });
|
|
16
|
+
return result?.[entryName] ?? null;
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
default: fallback ?? (() => null),
|
|
20
|
+
...asyncDataOptions
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
export function useCmsCollection(name, options = {}) {
|
|
25
|
+
const {
|
|
26
|
+
locale,
|
|
27
|
+
filters,
|
|
28
|
+
sort,
|
|
29
|
+
limit,
|
|
30
|
+
offset,
|
|
31
|
+
key,
|
|
32
|
+
default: fallback,
|
|
33
|
+
...asyncDataOptions
|
|
34
|
+
} = options;
|
|
35
|
+
const entryName = String(name);
|
|
36
|
+
const query = cmsCollectionQueries[entryName];
|
|
37
|
+
const variables = { locale, filters, sort, limit, offset };
|
|
38
|
+
return useAsyncData(
|
|
39
|
+
key ?? `cms-collection:${entryName}:${JSON.stringify(variables)}`,
|
|
40
|
+
async () => {
|
|
41
|
+
if (!query) unknownEntry(entryName);
|
|
42
|
+
const result = await $cmsQuery(query, variables);
|
|
43
|
+
return result?.[entryName] ?? [];
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
default: fallback ?? (() => []),
|
|
47
|
+
...asyncDataOptions
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import type { AsyncData } from 'nuxt/app';
|
|
1
|
+
import type { AsyncData, AsyncDataOptions } from 'nuxt/app';
|
|
2
2
|
type CmsDisabledResult = Record<string, any>;
|
|
3
3
|
type CmsDisabledVariables = Record<string, any>;
|
|
4
|
+
export interface CmsAsyncDataOptions<ResT, DefaultT> extends Pick<AsyncDataOptions<ResT>, 'server' | 'lazy' | 'immediate' | 'deep' | 'dedupe' | 'watch'> {
|
|
5
|
+
key?: string;
|
|
6
|
+
default?: () => DefaultT;
|
|
7
|
+
}
|
|
8
|
+
export declare function cmsQueryKey(query: string, variables?: unknown): string;
|
|
4
9
|
export declare function $cmsQuery<const Q extends string>(query: Q, variables?: CmsDisabledVariables): Promise<CmsDisabledResult>;
|
|
5
|
-
export declare function useCms<const Q extends string>(query: Q, variables?: CmsDisabledVariables): AsyncData<CmsDisabledResult | undefined, Error | undefined>;
|
|
10
|
+
export declare function useCms<const Q extends string, DefaultT = undefined>(query: Q, variables?: CmsDisabledVariables, options?: CmsAsyncDataOptions<CmsDisabledResult, DefaultT>): AsyncData<CmsDisabledResult | DefaultT | undefined, Error | undefined>;
|
|
6
11
|
export {};
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { useAsyncData } from "#imports";
|
|
2
|
+
export function cmsQueryKey(query, variables) {
|
|
3
|
+
return `cms-gql:${query}:${JSON.stringify(variables ?? {})}`;
|
|
4
|
+
}
|
|
2
5
|
export async function $cmsQuery(query, variables) {
|
|
3
6
|
return {};
|
|
4
7
|
}
|
|
5
|
-
export function useCms(query, variables) {
|
|
8
|
+
export function useCms(query, variables, options = {}) {
|
|
9
|
+
const { key, default: defaultValue } = options;
|
|
6
10
|
return useAsyncData(
|
|
7
|
-
|
|
8
|
-
async () => null
|
|
11
|
+
key ?? cmsQueryKey(query, variables),
|
|
12
|
+
async () => defaultValue ? defaultValue() : null
|
|
9
13
|
);
|
|
10
14
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { CmsResult, CmsVariables } from '#cms-graphql';
|
|
2
|
-
import type { AsyncData } from 'nuxt/app';
|
|
2
|
+
import type { AsyncData, AsyncDataOptions } from 'nuxt/app';
|
|
3
|
+
export interface CmsAsyncDataOptions<ResT, DefaultT> extends Pick<AsyncDataOptions<ResT>, 'server' | 'lazy' | 'immediate' | 'deep' | 'dedupe' | 'watch'> {
|
|
4
|
+
key?: string;
|
|
5
|
+
default?: () => DefaultT;
|
|
6
|
+
}
|
|
7
|
+
export declare function cmsQueryKey(query: string, variables?: unknown): string;
|
|
3
8
|
export declare function $cmsQuery<const Q extends string>(query: Q, variables?: CmsVariables<Q>): Promise<CmsResult<Q>>;
|
|
4
|
-
export declare function useCms<const Q extends string>(query: Q, variables?: CmsVariables<Q>): AsyncData<CmsResult<Q> | undefined, Error | undefined>;
|
|
9
|
+
export declare function useCms<const Q extends string, DefaultT = undefined>(query: Q, variables?: CmsVariables<Q>, options?: CmsAsyncDataOptions<CmsResult<Q>, DefaultT>): AsyncData<CmsResult<Q> | DefaultT | undefined, Error | undefined>;
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { useAsyncData } from "#imports";
|
|
2
2
|
const ENDPOINT = "/api/cms/graphql";
|
|
3
|
+
export function cmsQueryKey(query, variables) {
|
|
4
|
+
return `cms-gql:${query}:${JSON.stringify(variables ?? {})}`;
|
|
5
|
+
}
|
|
3
6
|
export async function $cmsQuery(query, variables) {
|
|
4
7
|
const res = await $fetch(ENDPOINT, {
|
|
5
8
|
method: "POST",
|
|
@@ -10,9 +13,11 @@ export async function $cmsQuery(query, variables) {
|
|
|
10
13
|
}
|
|
11
14
|
return res.data;
|
|
12
15
|
}
|
|
13
|
-
export function useCms(query, variables) {
|
|
16
|
+
export function useCms(query, variables, options = {}) {
|
|
17
|
+
const { key, ...asyncDataOptions } = options;
|
|
14
18
|
return useAsyncData(
|
|
15
|
-
|
|
16
|
-
() => $cmsQuery(query, variables)
|
|
19
|
+
key ?? cmsQueryKey(query, variables),
|
|
20
|
+
() => $cmsQuery(query, variables),
|
|
21
|
+
asyncDataOptions
|
|
17
22
|
);
|
|
18
23
|
}
|
|
@@ -30,7 +30,12 @@ import {
|
|
|
30
30
|
pickTranslatedMedia,
|
|
31
31
|
translatableFieldKeys
|
|
32
32
|
} from "../../shared/index.js";
|
|
33
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
blockTypeName,
|
|
35
|
+
blocksFieldTypeName,
|
|
36
|
+
renderGraphqlSdl,
|
|
37
|
+
typeName
|
|
38
|
+
} from "../../shared/graphql-sdl.js";
|
|
34
39
|
import { useMediaIndex } from "./media-index.js";
|
|
35
40
|
import { getContentI18n, resolveTable, tableColumns } from "./registry.js";
|
|
36
41
|
const MAX_LIMIT = 100;
|
|
@@ -285,7 +290,7 @@ function entryResolvers(config, name, entry) {
|
|
|
285
290
|
}
|
|
286
291
|
function blockResolvers(name, key, field) {
|
|
287
292
|
const resolvers = {};
|
|
288
|
-
resolvers[
|
|
293
|
+
resolvers[blocksFieldTypeName(name, key)] = {
|
|
289
294
|
__resolveType: (value) => blockTypeName(name, key, String(value.type))
|
|
290
295
|
};
|
|
291
296
|
for (const [blockName, block] of Object.entries(field.blocks ?? {})) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { CmsConfig } from './index.js';
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export declare function blockTypeName(entryName: string, fieldKey: string, blockName: string): string;
|
|
2
|
+
import { blockTypeName, blocksFieldTypeName, typeName } from './index.js';
|
|
3
|
+
export { blockTypeName, blocksFieldTypeName, typeName };
|
|
5
4
|
export declare function renderGraphqlSdl(config: CmsConfig): string;
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
blockTypeName,
|
|
3
|
+
blocksFieldTypeName,
|
|
4
|
+
isPrivateField,
|
|
5
|
+
isRequiredField,
|
|
6
|
+
isTranslatableField,
|
|
7
|
+
typeName
|
|
8
|
+
} from "./index.js";
|
|
9
|
+
export { blockTypeName, blocksFieldTypeName, typeName };
|
|
11
10
|
function scalarFor(field) {
|
|
12
11
|
switch (field.type) {
|
|
13
12
|
case "number":
|
|
@@ -58,7 +57,7 @@ function fieldSdl(config, entryName, key, field) {
|
|
|
58
57
|
if (field.type === "media") return ` ${key}: CmsMedia`;
|
|
59
58
|
if (field.type === "select" && field.multiple) return ` ${key}: [String!]!`;
|
|
60
59
|
if (field.type === "blocks")
|
|
61
|
-
return ` ${key}: [${
|
|
60
|
+
return ` ${key}: [${blocksFieldTypeName(entryName, key)}!]${isRequiredField(field) ? "!" : ""}`;
|
|
62
61
|
return ` ${key}: ${scalarFor(field)}${isRequiredField(field) ? "!" : ""}`;
|
|
63
62
|
}
|
|
64
63
|
function entrySdl(config, name, entry) {
|
|
@@ -73,25 +72,44 @@ function entrySdl(config, name, entry) {
|
|
|
73
72
|
${lines.join("\n")}
|
|
74
73
|
}`;
|
|
75
74
|
}
|
|
75
|
+
function blockFieldSdl(field) {
|
|
76
|
+
if (field.type === "media") return { base: "CmsMedia", nonNull: false };
|
|
77
|
+
return { base: scalarFor(field), nonNull: !!field.required };
|
|
78
|
+
}
|
|
79
|
+
function sharedBlockFieldsSdl(field) {
|
|
80
|
+
const blocks = Object.values(field.blocks ?? {});
|
|
81
|
+
const first = blocks[0];
|
|
82
|
+
if (!first) return [];
|
|
83
|
+
const lines = [];
|
|
84
|
+
for (const key of Object.keys(first.fields)) {
|
|
85
|
+
const rendered = blocks.map((block) => block.fields[key]);
|
|
86
|
+
if (rendered.some((blockField) => !blockField)) continue;
|
|
87
|
+
const types = rendered.map((blockField) => blockFieldSdl(blockField));
|
|
88
|
+
if (types.some((type) => type.base !== types[0].base)) continue;
|
|
89
|
+
const nonNull = types.every((type) => type.nonNull);
|
|
90
|
+
lines.push(` ${key}: ${types[0].base}${nonNull ? "!" : ""}`);
|
|
91
|
+
}
|
|
92
|
+
return lines;
|
|
93
|
+
}
|
|
76
94
|
function blocksSdl(name, key, field) {
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
const blocks = Object.entries(field.blocks ?? {});
|
|
96
|
+
if (!blocks.length) return [];
|
|
97
|
+
const interfaceName = blocksFieldTypeName(name, key);
|
|
98
|
+
const shared = [" type: String!", ...sharedBlockFieldsSdl(field)];
|
|
99
|
+
const defs = [`interface ${interfaceName} {
|
|
100
|
+
${shared.join("\n")}
|
|
101
|
+
}`];
|
|
102
|
+
for (const [blockName, block] of blocks) {
|
|
82
103
|
const lines = [" type: String!"];
|
|
83
104
|
for (const [blockFieldKey, blockField] of Object.entries(block.fields)) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
lines.push(` ${blockFieldKey}: ${scalarFor(blockField)}${blockField.required ? "!" : ""}`);
|
|
105
|
+
const type = blockFieldSdl(blockField);
|
|
106
|
+
lines.push(` ${blockFieldKey}: ${type.base}${type.nonNull ? "!" : ""}`);
|
|
89
107
|
}
|
|
90
|
-
|
|
108
|
+
const gqlType = blockTypeName(name, key, blockName);
|
|
109
|
+
defs.push(`type ${gqlType} implements ${interfaceName} {
|
|
91
110
|
${lines.join("\n")}
|
|
92
111
|
}`);
|
|
93
112
|
}
|
|
94
|
-
if (members.length) defs.push(`union ${blockUnionName(name, key)} = ${members.join(" | ")}`);
|
|
95
113
|
return defs;
|
|
96
114
|
}
|
|
97
115
|
function filterSdl(name, entry) {
|
|
@@ -119,7 +137,8 @@ const COMMON_SDL = [
|
|
|
119
137
|
"input FloatFilter {\n eq: Float\n neq: Float\n gt: Float\n gte: Float\n lt: Float\n lte: Float\n in: [Float!]\n isNull: Boolean\n}",
|
|
120
138
|
"input StringFilter {\n eq: String\n neq: String\n gt: String\n gte: String\n lt: String\n lte: String\n like: String\n in: [String!]\n isNull: Boolean\n}",
|
|
121
139
|
"input BooleanFilter {\n eq: Boolean\n neq: Boolean\n isNull: Boolean\n}",
|
|
122
|
-
"
|
|
140
|
+
"enum CmsMediaType {\n image\n video\n file\n}",
|
|
141
|
+
"type CmsMedia {\n key: String!\n url: String\n type: CmsMediaType!\n alt: String\n folder: String\n mime: String\n size: Int\n width: Int\n height: Int\n}"
|
|
123
142
|
];
|
|
124
143
|
export function renderGraphqlSdl(config) {
|
|
125
144
|
const queryLines = [];
|
|
@@ -98,6 +98,9 @@ export interface CmsEntry {
|
|
|
98
98
|
table?: CmsTable;
|
|
99
99
|
}
|
|
100
100
|
export type CmsConfig = Record<string, CmsEntry>;
|
|
101
|
+
export declare function typeName(name: string): string;
|
|
102
|
+
export declare function blocksFieldTypeName(entryName: string, fieldKey: string): string;
|
|
103
|
+
export declare function blockTypeName(entryName: string, fieldKey: string, blockName: string): string;
|
|
101
104
|
interface FieldInputBase {
|
|
102
105
|
label: string;
|
|
103
106
|
required?: boolean;
|
|
@@ -184,6 +184,15 @@ export function localizeBlocks(field, value, locale, defaultLocale) {
|
|
|
184
184
|
if (!Array.isArray(value)) return value;
|
|
185
185
|
return value.map((item) => localizeBlock(field, item, locale, defaultLocale));
|
|
186
186
|
}
|
|
187
|
+
export function typeName(name) {
|
|
188
|
+
return name.replace(/(?:^|_)([a-z0-9])/gi, (_, c) => c.toUpperCase());
|
|
189
|
+
}
|
|
190
|
+
export function blocksFieldTypeName(entryName, fieldKey) {
|
|
191
|
+
return `${typeName(entryName)}${typeName(fieldKey)}Block`;
|
|
192
|
+
}
|
|
193
|
+
export function blockTypeName(entryName, fieldKey, blockName) {
|
|
194
|
+
return `${typeName(entryName)}${typeName(fieldKey)}${typeName(blockName)}`;
|
|
195
|
+
}
|
|
187
196
|
export function defineCmsConfig(config) {
|
|
188
197
|
return config;
|
|
189
198
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xleddyl/nuxt-cms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.42",
|
|
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)",
|