@xleddyl/nuxt-cms 0.1.41 → 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 +249 -95
- 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/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
12
|
import { typeName, blockTypeName, blocksFieldTypeName, renderGraphqlSdl } from '../dist/runtime/shared/graphql-sdl.js';
|
|
13
|
-
import {
|
|
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,80 @@ ${[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
|
+
|
|
453
527
|
function mediaTsType(field) {
|
|
454
528
|
const types = mediaTypeFilter(field.mediaType);
|
|
455
529
|
return types ? `CmsMedia<${types.map((type) => `'${type}'`).join(" | ")}>` : "CmsMedia";
|
|
@@ -522,6 +596,54 @@ function entryTs(config, name, entry) {
|
|
|
522
596
|
${lines.join("\n")}
|
|
523
597
|
}`;
|
|
524
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
|
+
}
|
|
525
647
|
function renderTypesFile(config) {
|
|
526
648
|
const parts = [
|
|
527
649
|
`export type CmsMediaType = 'image' | 'video' | 'file'`,
|
|
@@ -544,6 +666,8 @@ function renderTypesFile(config) {
|
|
|
544
666
|
}
|
|
545
667
|
parts.push(entryTs(config, name, entry));
|
|
546
668
|
}
|
|
669
|
+
for (const [name, entry] of Object.entries(config)) parts.push(autoTypeTs(config, name, entry));
|
|
670
|
+
parts.push(...entryMapsTs(config));
|
|
547
671
|
return `${parts.join("\n\n")}
|
|
548
672
|
`;
|
|
549
673
|
}
|
|
@@ -632,6 +756,104 @@ function resolveModuleOptions(options) {
|
|
|
632
756
|
}
|
|
633
757
|
};
|
|
634
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
|
+
}
|
|
635
857
|
const module$1 = defineNuxtModule({
|
|
636
858
|
meta: {
|
|
637
859
|
name: "@xleddyl/nuxt-cms",
|
|
@@ -676,11 +898,18 @@ const module$1 = defineNuxtModule({
|
|
|
676
898
|
const logger = useLogger("nuxt-cms");
|
|
677
899
|
const resolved = resolveModuleOptions(options);
|
|
678
900
|
if (!resolveCmsEnabled(options.enabled, process.env[CMS_ENABLED_ENV])) {
|
|
679
|
-
const
|
|
901
|
+
const queryStub = resolver.resolve("./runtime/app/composables/cms-query-disabled");
|
|
902
|
+
const entryStub = resolver.resolve("./runtime/app/composables/cms-entry-disabled");
|
|
680
903
|
addImports([
|
|
681
|
-
{ name: "useCms", from:
|
|
682
|
-
{ name: "$cmsQuery", from:
|
|
904
|
+
{ name: "useCms", from: queryStub },
|
|
905
|
+
{ name: "$cmsQuery", from: queryStub },
|
|
906
|
+
{ name: "useCmsSingle", from: entryStub },
|
|
907
|
+
{ name: "useCmsCollection", from: entryStub }
|
|
683
908
|
]);
|
|
909
|
+
addCmsTypeTemplates(
|
|
910
|
+
nuxt,
|
|
911
|
+
await loadCmsConfig(nuxt, resolver, resolved.configPath, resolved.i18n, logger)
|
|
912
|
+
);
|
|
684
913
|
nuxt.options.runtimeConfig.public.cms = {
|
|
685
914
|
mediaBaseUrl: resolved.media.publicBaseUrl,
|
|
686
915
|
mediaStorage: resolved.media.storage,
|
|
@@ -688,7 +917,7 @@ const module$1 = defineNuxtModule({
|
|
|
688
917
|
i18n: resolved.i18n
|
|
689
918
|
};
|
|
690
919
|
logger.info(
|
|
691
|
-
"[nuxt-cms] disabled: registering no-op
|
|
920
|
+
"[nuxt-cms] disabled: registering no-op query composables and generated types, skipping admin, server and database setup"
|
|
692
921
|
);
|
|
693
922
|
return;
|
|
694
923
|
}
|
|
@@ -713,38 +942,13 @@ const module$1 = defineNuxtModule({
|
|
|
713
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."
|
|
714
943
|
);
|
|
715
944
|
}
|
|
716
|
-
const
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
moduleCache: false,
|
|
724
|
-
alias: { "#nuxt-cms": resolver.resolve("./runtime/shared/index") }
|
|
725
|
-
});
|
|
726
|
-
cmsConfig = await jiti.import(configPath, { default: true });
|
|
727
|
-
} else {
|
|
728
|
-
logger.warn(
|
|
729
|
-
`[nuxt-cms] Config file not found: ${configPath}. Using an empty registry \u2014 create a ${resolved.configPath}.ts with defineCmsConfig().`
|
|
730
|
-
);
|
|
731
|
-
nuxt.options.alias["#cms-config"] = resolver.resolve("./runtime/shared/empty-config");
|
|
732
|
-
}
|
|
733
|
-
const configErrors = validateConfig(cmsConfig, resolved.i18n);
|
|
734
|
-
if (configErrors.length) {
|
|
735
|
-
for (const error of configErrors) logger.error(error);
|
|
736
|
-
throw new Error(
|
|
737
|
-
`[nuxt-cms] Invalid cms config (${configErrors.length} error${configErrors.length > 1 ? "s" : ""})`
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
const moduleRequire = createRequire(import.meta.url);
|
|
741
|
-
const resolveImport = (specifier) => {
|
|
742
|
-
try {
|
|
743
|
-
return fileURLToPath(import.meta.resolve(specifier)).replace(/\\/g, "/");
|
|
744
|
-
} catch {
|
|
745
|
-
return moduleRequire.resolve(specifier).replace(/\\/g, "/");
|
|
746
|
-
}
|
|
747
|
-
};
|
|
945
|
+
const cmsConfig = await loadCmsConfig(
|
|
946
|
+
nuxt,
|
|
947
|
+
resolver,
|
|
948
|
+
resolved.configPath,
|
|
949
|
+
resolved.i18n,
|
|
950
|
+
logger
|
|
951
|
+
);
|
|
748
952
|
const schemaTemplate = addTemplate({
|
|
749
953
|
filename: "cms/schema.ts",
|
|
750
954
|
write: true,
|
|
@@ -755,64 +959,14 @@ const module$1 = defineNuxtModule({
|
|
|
755
959
|
)
|
|
756
960
|
});
|
|
757
961
|
nuxt.options.alias["#cms-tables"] = schemaTemplate.dst;
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
getContents: () => renderGraphqlSdl(cmsConfig)
|
|
762
|
-
});
|
|
763
|
-
const typesTemplate = addTemplate({
|
|
764
|
-
filename: "cms/types.ts",
|
|
765
|
-
write: true,
|
|
766
|
-
getContents: () => renderTypesFile(cmsConfig)
|
|
767
|
-
});
|
|
768
|
-
nuxt.options.alias["#cms-types"] = typesTemplate.dst;
|
|
769
|
-
addTemplate({
|
|
770
|
-
filename: "cms/graphql-env.d.ts",
|
|
771
|
-
write: true,
|
|
772
|
-
getContents: () => {
|
|
773
|
-
const introspection = minifyIntrospection(
|
|
774
|
-
introspectionFromSchema(buildSchema(renderGraphqlSdl(cmsConfig)))
|
|
775
|
-
);
|
|
776
|
-
return outputIntrospectionFile(introspection, {
|
|
777
|
-
fileType: ".d.ts",
|
|
778
|
-
shouldPreprocess: true
|
|
779
|
-
}).split("import * as gqlTada from 'gql.tada';")[0];
|
|
780
|
-
}
|
|
781
|
-
});
|
|
782
|
-
const gqlTadaTypesPath = resolveImport("gql.tada").replace(/\.[mc]?js$/, "");
|
|
783
|
-
const graphqlTemplate = addTemplate({
|
|
784
|
-
filename: "cms/graphql.ts",
|
|
785
|
-
write: true,
|
|
786
|
-
getContents: () => [
|
|
787
|
-
`import type { initGraphQLTada, ResultOf, VariablesOf } from '${gqlTadaTypesPath}'`,
|
|
788
|
-
`import type { introspection } from './graphql-env'`,
|
|
789
|
-
``,
|
|
790
|
-
`export type CmsGraphql = initGraphQLTada<{`,
|
|
791
|
-
` introspection: introspection`,
|
|
792
|
-
` scalars: {`,
|
|
793
|
-
` JSON: unknown`,
|
|
794
|
-
` }`,
|
|
795
|
-
`}>`,
|
|
796
|
-
``,
|
|
797
|
-
`declare const graphql: CmsGraphql`,
|
|
798
|
-
``,
|
|
799
|
-
`// @ts-ignore: gql.tada's cache overload rejects this instantiation, but the parse overload still resolves`,
|
|
800
|
-
`export type CmsDocument<Query extends string> = ReturnType<typeof graphql<Query, []>>`,
|
|
801
|
-
``,
|
|
802
|
-
`export type CmsResult<Query extends string> = string extends Query`,
|
|
803
|
-
` ? Record<string, unknown>`,
|
|
804
|
-
` : ResultOf<CmsDocument<Query>>`,
|
|
805
|
-
``,
|
|
806
|
-
`export type CmsVariables<Query extends string> = string extends Query`,
|
|
807
|
-
` ? Record<string, unknown>`,
|
|
808
|
-
` : VariablesOf<CmsDocument<Query>>`,
|
|
809
|
-
``
|
|
810
|
-
].join("\n")
|
|
811
|
-
});
|
|
812
|
-
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");
|
|
813
965
|
addImports([
|
|
814
|
-
{ name: "useCms", from:
|
|
815
|
-
{ name: "$cmsQuery", from:
|
|
966
|
+
{ name: "useCms", from: queryComposables },
|
|
967
|
+
{ name: "$cmsQuery", from: queryComposables },
|
|
968
|
+
{ name: "useCmsSingle", from: entryComposables },
|
|
969
|
+
{ name: "useCmsCollection", from: entryComposables }
|
|
816
970
|
]);
|
|
817
971
|
const {
|
|
818
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
|
+
}
|
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)",
|