@slicemachine/adapter-next 0.3.85-beta.3 → 0.3.86-alpha.lg-page-routes.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@slicemachine/adapter-next",
3
- "version": "0.3.85-beta.3",
3
+ "version": "0.3.86-alpha.lg-page-routes.1",
4
4
  "description": "Slice Machine adapter for Next.js.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -68,7 +68,7 @@
68
68
  "dependencies": {
69
69
  "@prismicio/simulator": "^0.1.4",
70
70
  "@prismicio/types-internal": "3.11.2",
71
- "@slicemachine/plugin-kit": "0.4.83-beta.3",
71
+ "@slicemachine/plugin-kit": "0.4.84-alpha.lg-page-routes.1",
72
72
  "common-tags": "^1.8.2",
73
73
  "fp-ts": "^2.13.1",
74
74
  "io-ts": "^2.2.20",
@@ -114,5 +114,5 @@
114
114
  "publishConfig": {
115
115
  "access": "public"
116
116
  },
117
- "stableVersion": "0.3.84"
117
+ "stableVersion": "0.3.85"
118
118
  }
@@ -0,0 +1,472 @@
1
+ import { CustomType } from "@prismicio/types-internal/lib/customtypes";
2
+ import { stripIndent } from "common-tags";
3
+
4
+ import type {
5
+ CustomTypeUpdateRouteHook,
6
+ CustomTypeUpdateRouteHookData,
7
+ SliceMachineContext,
8
+ } from "@slicemachine/plugin-kit";
9
+ import {
10
+ checkHasProjectFile,
11
+ writeProjectFile,
12
+ } from "@slicemachine/plugin-kit/fs";
13
+
14
+ import { checkIsTypeScriptProject } from "../lib/checkIsTypeScriptProject";
15
+ import { getJSFileExtension } from "../lib/getJSFileExtension";
16
+
17
+ import type { PluginOptions } from "../types";
18
+ import { checkHasAppRouter } from "../lib/checkHasAppRouter";
19
+ import path from "node:path";
20
+
21
+ type Args = {
22
+ data: CustomTypeUpdateRouteHookData;
23
+ } & SliceMachineContext<PluginOptions>;
24
+
25
+ const createComponentFile = async ({ data, helpers, options }: Args) => {
26
+ const isTypeScript = await checkIsTypeScriptProject({
27
+ helpers,
28
+ options,
29
+ });
30
+ const hasSrcDirectory = await checkHasProjectFile({
31
+ filename: "src",
32
+ helpers,
33
+ });
34
+ const hasAppRouter = await checkHasAppRouter({ helpers });
35
+
36
+ const pageRoute = data.model.route ?? data.model.id;
37
+ const pageRouteLength = pageRoute.split("/").length - 1;
38
+
39
+ const contents = generatePageComponent(data.model, {
40
+ hasAppRouter,
41
+ isTypeScript,
42
+ hasSrcDirectory,
43
+ pageRouteLength,
44
+ });
45
+
46
+ const routeBase = hasAppRouter ? "app" : "pages";
47
+ const routeDirectoryPath = hasSrcDirectory
48
+ ? path.join("src", routeBase)
49
+ : routeBase;
50
+
51
+ const extension = await getJSFileExtension({
52
+ helpers,
53
+ options,
54
+ jsx: true,
55
+ });
56
+ let filePath: string;
57
+
58
+ if (hasAppRouter) {
59
+ const fileName = data.model.repeatable
60
+ ? `[uid]/page.${extension}`
61
+ : `page.${extension}`;
62
+
63
+ filePath = path.join(routeDirectoryPath, pageRoute, fileName);
64
+ } else {
65
+ const fileName = data.model.repeatable
66
+ ? `${pageRoute}/[uid].${extension}`
67
+ : `${pageRoute}.${extension}`;
68
+
69
+ filePath = path.join(routeDirectoryPath, fileName);
70
+ }
71
+
72
+ await writeProjectFile({
73
+ filename: filePath,
74
+ contents,
75
+ format: options.format,
76
+ helpers,
77
+ });
78
+ };
79
+
80
+ export const pageRouteCreate: CustomTypeUpdateRouteHook<PluginOptions> = async (
81
+ data,
82
+ context,
83
+ ) => {
84
+ await createComponentFile({ data, ...context });
85
+ };
86
+
87
+ export function generatePageComponent(
88
+ customType: CustomType,
89
+ env: {
90
+ hasAppRouter: boolean;
91
+ isTypeScript: boolean;
92
+ hasSrcDirectory: boolean;
93
+ pageRouteLength: number;
94
+ },
95
+ ): string {
96
+ if (env.hasAppRouter) {
97
+ return generateAppRouterPage(
98
+ customType,
99
+ env.isTypeScript,
100
+ env.hasSrcDirectory,
101
+ env.pageRouteLength,
102
+ );
103
+ } else {
104
+ return generatePagesRouterPage(
105
+ customType,
106
+ env.isTypeScript,
107
+ env.hasSrcDirectory,
108
+ env.pageRouteLength,
109
+ );
110
+ }
111
+ }
112
+
113
+ function generateAppRouterPage(
114
+ customType: CustomType,
115
+ isTypeScript: boolean,
116
+ hasSrcDirectory: boolean,
117
+ pageRouteLength: number,
118
+ ): string {
119
+ // Calculate import paths based on directory depth
120
+ // Single: app/[id]/page.tsx → ../../prismicio (or ../../../prismicio if src/)
121
+ // Repeatable: app/[id]/[uid]/page.tsx → ../../../prismicio (or ../../../../prismicio if src/)
122
+ const levelsUp = customType.repeatable
123
+ ? pageRouteLength + 1
124
+ : pageRouteLength;
125
+ const levelsUpWithSrc = hasSrcDirectory ? levelsUp + 1 : levelsUp;
126
+ const importPath = "../".repeat(levelsUpWithSrc) + "prismicio";
127
+ const slicesPath = "../".repeat(levelsUpWithSrc) + "slices";
128
+
129
+ if (customType.repeatable) {
130
+ if (isTypeScript) {
131
+ return stripIndent`
132
+ import { Metadata } from "next";
133
+ import { notFound } from "next/navigation";
134
+ import { asImageSrc } from "@prismicio/client";
135
+ import { SliceZone } from "@prismicio/react";
136
+
137
+ import { createClient } from "${importPath}";
138
+ import { components } from "${slicesPath}";
139
+
140
+ type Params = { uid: string };
141
+
142
+ export default async function Page({ params }: { params: Promise<Params> }) {
143
+ const { uid } = await params;
144
+ const client = createClient();
145
+ const page = await client
146
+ .getByUID("${customType.id}", uid)
147
+ .catch(() => notFound());
148
+
149
+ return <SliceZone slices={page.data.slices} components={components} />;
150
+ }
151
+
152
+ export async function generateMetadata({
153
+ params,
154
+ }: {
155
+ params: Promise<Params>;
156
+ }): Promise<Metadata> {
157
+ const { uid } = await params;
158
+ const client = createClient();
159
+ const page = await client
160
+ .getByUID("${customType.id}", uid)
161
+ .catch(() => notFound());
162
+
163
+ return {
164
+ title: page.data.meta_title,
165
+ description: page.data.meta_description,
166
+ openGraph: {
167
+ images: [{ url: asImageSrc(page.data.meta_image) ?? "" }],
168
+ },
169
+ };
170
+ }
171
+
172
+ export async function generateStaticParams() {
173
+ const client = createClient();
174
+ const pages = await client.getAllByType("${customType.id}");
175
+
176
+ return pages.map((page) => ({ uid: page.uid }));
177
+ }
178
+ `;
179
+ } else {
180
+ return stripIndent`
181
+ import { notFound } from "next/navigation";
182
+ import { asImageSrc } from "@prismicio/client";
183
+ import { SliceZone } from "@prismicio/react";
184
+
185
+ import { createClient } from "${importPath}";
186
+ import { components } from "${slicesPath}";
187
+
188
+ export default async function Page({ params }) {
189
+ const { uid } = await params;
190
+ const client = createClient();
191
+ const page = await client
192
+ .getByUID("${customType.id}", uid)
193
+ .catch(() => notFound());
194
+
195
+ return <SliceZone slices={page.data.slices} components={components} />;
196
+ }
197
+
198
+ export async function generateMetadata({ params }) {
199
+ const { uid } = await params;
200
+ const client = createClient();
201
+ const page = await client
202
+ .getByUID("${customType.id}", uid)
203
+ .catch(() => notFound());
204
+
205
+ return {
206
+ title: page.data.meta_title,
207
+ description: page.data.meta_description,
208
+ openGraph: {
209
+ images: [{ url: asImageSrc(page.data.meta_image) ?? "" }],
210
+ },
211
+ };
212
+ }
213
+
214
+ export async function generateStaticParams() {
215
+ const client = createClient();
216
+ const pages = await client.getAllByType("${customType.id}");
217
+
218
+ return pages.map((page) => ({ uid: page.uid }));
219
+ }
220
+ `;
221
+ }
222
+ } else {
223
+ // Single type
224
+ if (isTypeScript) {
225
+ return stripIndent`
226
+ import { type Metadata } from "next";
227
+ import { notFound } from "next/navigation";
228
+ import { asImageSrc } from "@prismicio/client";
229
+ import { SliceZone } from "@prismicio/react";
230
+
231
+ import { createClient } from "${importPath}";
232
+ import { components } from "${slicesPath}";
233
+
234
+ export default async function Page() {
235
+ const client = createClient();
236
+ const page = await client.getSingle("${customType.id}").catch(() => notFound());
237
+
238
+ return <SliceZone slices={page.data.slices} components={components} />;
239
+ }
240
+
241
+ export async function generateMetadata(): Promise<Metadata> {
242
+ const client = createClient();
243
+ const page = await client.getSingle("${customType.id}").catch(() => notFound());
244
+
245
+ return {
246
+ title: page.data.meta_title,
247
+ description: page.data.meta_description,
248
+ openGraph: {
249
+ images: [{ url: asImageSrc(page.data.meta_image) ?? "" }],
250
+ },
251
+ };
252
+ }
253
+ `;
254
+ } else {
255
+ return stripIndent`
256
+ import { notFound } from "next/navigation";
257
+ import { asImageSrc } from "@prismicio/client";
258
+ import { SliceZone } from "@prismicio/react";
259
+
260
+ import { createClient } from "${importPath}";
261
+ import { components } from "${slicesPath}";
262
+
263
+ export default async function Page() {
264
+ const client = createClient();
265
+ const page = await client.getSingle("${customType.id}").catch(() => notFound());
266
+
267
+ return <SliceZone slices={page.data.slices} components={components} />;
268
+ }
269
+
270
+ export async function generateMetadata() {
271
+ const client = createClient();
272
+ const page = await client.getSingle("${customType.id}").catch(() => notFound());
273
+
274
+ return {
275
+ title: page.data.meta_title,
276
+ description: page.data.meta_description,
277
+ openGraph: {
278
+ images: [{ url: asImageSrc(page.data.meta_image) ?? "" }],
279
+ },
280
+ };
281
+ }
282
+ `;
283
+ }
284
+ }
285
+ }
286
+
287
+ function generatePagesRouterPage(
288
+ customType: CustomType,
289
+ isTypeScript: boolean,
290
+ hasSrcDirectory: boolean,
291
+ pageRouteLength: number,
292
+ ): string {
293
+ // Calculate import paths based on directory depth
294
+ // Single: pages/[id].tsx → ../prismicio (or ../../prismicio if src/)
295
+ // Repeatable: pages/[id]/[uid].tsx → ../../prismicio (or ../../../prismicio if src/)
296
+ const levelsUp = customType.repeatable
297
+ ? pageRouteLength + 1
298
+ : pageRouteLength;
299
+ const levelsUpWithSrc = hasSrcDirectory ? levelsUp + 1 : levelsUp;
300
+ const importPath = "../".repeat(levelsUpWithSrc) + "prismicio";
301
+ const slicesPath = "../".repeat(levelsUpWithSrc) + "slices";
302
+
303
+ if (customType.repeatable) {
304
+ if (isTypeScript) {
305
+ return stripIndent`
306
+ import { GetStaticPropsContext, InferGetStaticPropsType } from "next";
307
+ import Head from "next/head";
308
+ import { isFilled, asLink, asImageSrc } from "@prismicio/client";
309
+ import { SliceZone } from "@prismicio/react";
310
+
311
+ import { components } from "${slicesPath}";
312
+ import { createClient } from "${importPath}";
313
+
314
+ type Params = { uid: string };
315
+
316
+ export default function Page({
317
+ page,
318
+ }: InferGetStaticPropsType<typeof getStaticProps>) {
319
+ return (
320
+ <>
321
+ <Head>
322
+ <title>{page.data.meta_title}</title>
323
+ {isFilled.keyText(page.data.meta_description) ? (
324
+ <meta name="description" content={page.data.meta_description} />
325
+ ) : null}
326
+ {isFilled.image(page.data.meta_image) ? (
327
+ <meta property="og:image" content={asImageSrc(page.data.meta_image) || ""} />
328
+ ) : null}
329
+ </Head>
330
+ <SliceZone slices={page.data.slices} components={components} />
331
+ </>
332
+ );
333
+ }
334
+
335
+ export async function getStaticProps({
336
+ params,
337
+ previewData,
338
+ }: GetStaticPropsContext<Params>) {
339
+ const client = createClient({ previewData });
340
+ const page = await client.getByUID("${customType.id}", params!.uid);
341
+
342
+ return { props: { page } };
343
+ }
344
+
345
+ export async function getStaticPaths() {
346
+ const client = createClient();
347
+ const pages = await client.getAllByType("${customType.id}");
348
+
349
+ return {
350
+ paths: pages.map((page) => asLink(page)),
351
+ fallback: false,
352
+ };
353
+ }
354
+ `;
355
+ } else {
356
+ return stripIndent`
357
+ import Head from "next/head";
358
+ import { isFilled, asLink, asImageSrc } from "@prismicio/client";
359
+ import { SliceZone } from "@prismicio/react";
360
+
361
+ import { components } from "${slicesPath}";
362
+ import { createClient } from "${importPath}";
363
+
364
+ export default function Page({ page }) {
365
+ return (
366
+ <>
367
+ <Head>
368
+ <title>{page.data.meta_title}</title>
369
+ {isFilled.keyText(page.data.meta_description) ? (
370
+ <meta name="description" content={page.data.meta_description} />
371
+ ) : null}
372
+ {isFilled.image(page.data.meta_image) ? (
373
+ <meta property="og:image" content={asImageSrc(page.data.meta_image) || ""} />
374
+ ) : null}
375
+ </Head>
376
+ <SliceZone slices={page.data.slices} components={components} />
377
+ </>
378
+ );
379
+ }
380
+
381
+ export async function getStaticProps({ params, previewData }) {
382
+ const client = createClient({ previewData });
383
+ const page = await client.getByUID("${customType.id}", params.uid);
384
+
385
+ return { props: { page } };
386
+ }
387
+
388
+ export async function getStaticPaths() {
389
+ const client = createClient();
390
+ const pages = await client.getAllByType("${customType.id}");
391
+
392
+ return {
393
+ paths: pages.map((page) => asLink(page)),
394
+ fallback: false,
395
+ };
396
+ }
397
+ `;
398
+ }
399
+ } else {
400
+ // Single type
401
+ if (isTypeScript) {
402
+ return stripIndent`
403
+ import { GetStaticPropsContext, InferGetStaticPropsType } from "next";
404
+ import Head from "next/head";
405
+ import { isFilled, asImageSrc } from "@prismicio/client";
406
+ import { SliceZone } from "@prismicio/react";
407
+
408
+ import { components } from "${slicesPath}";
409
+ import { createClient } from "${importPath}";
410
+
411
+ export default function Page({
412
+ page,
413
+ }: InferGetStaticPropsType<typeof getStaticProps>) {
414
+ return (
415
+ <>
416
+ <Head>
417
+ <title>{page.data.meta_title}</title>
418
+ {isFilled.keyText(page.data.meta_description) ? (
419
+ <meta name="description" content={page.data.meta_description} />
420
+ ) : null}
421
+ {isFilled.image(page.data.meta_image) ? (
422
+ <meta property="og:image" content={asImageSrc(page.data.meta_image) || ""} />
423
+ ) : null}
424
+ </Head>
425
+ <SliceZone slices={page.data.slices} components={components} />
426
+ </>
427
+ );
428
+ }
429
+
430
+ export async function getStaticProps({ previewData }: GetStaticPropsContext) {
431
+ const client = createClient({ previewData });
432
+ const page = await client.getSingle("${customType.id}");
433
+
434
+ return { props: { page } };
435
+ }
436
+ `;
437
+ } else {
438
+ return stripIndent`
439
+ import Head from "next/head";
440
+ import { isFilled, asImageSrc } from "@prismicio/client";
441
+ import { SliceZone } from "@prismicio/react";
442
+
443
+ import { components } from "${slicesPath}";
444
+ import { createClient } from "${importPath}";
445
+
446
+ export default function Page({ page }) {
447
+ return (
448
+ <>
449
+ <Head>
450
+ <title>{page.data.meta_title}</title>
451
+ {isFilled.keyText(page.data.meta_description) ? (
452
+ <meta name="description" content={page.data.meta_description} />
453
+ ) : null}
454
+ {isFilled.image(page.data.meta_image) ? (
455
+ <meta property="og:image" content={asImageSrc(page.data.meta_image) || ""} />
456
+ ) : null}
457
+ </Head>
458
+ <SliceZone slices={page.data.slices} components={components} />
459
+ </>
460
+ );
461
+ }
462
+
463
+ export async function getStaticProps({ previewData }) {
464
+ const client = createClient({ previewData });
465
+ const page = await client.getSingle("${customType.id}");
466
+
467
+ return { props: { page } };
468
+ }
469
+ `;
470
+ }
471
+ }
472
+ }
package/src/plugin.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  readSliceModel,
17
17
  readSliceTemplateLibrary,
18
18
  renameCustomType,
19
+ updateCustomTypeRoute,
19
20
  renameSlice,
20
21
  upsertGlobalTypeScriptTypes,
21
22
  writeCustomTypeFile,
@@ -40,6 +41,7 @@ import { documentationRead } from "./hooks/documentation-read";
40
41
  import { projectInit } from "./hooks/project-init";
41
42
  import { sliceCreate } from "./hooks/slice-create";
42
43
  import { snippetRead } from "./hooks/snippet-read";
44
+ import { pageRouteCreate } from "./hooks/page-route-create";
43
45
 
44
46
  // Slice Template Library
45
47
  import * as Hero from "./sliceTemplates/Hero";
@@ -269,6 +271,14 @@ export const plugin = defineSliceMachinePlugin<PluginOptions>({
269
271
  ...context,
270
272
  });
271
273
  });
274
+ hook("custom-type:update-route", async (data, context) => {
275
+ await updateCustomTypeRoute({
276
+ model: data.model,
277
+ ...context,
278
+ });
279
+
280
+ await pageRouteCreate(data, context);
281
+ });
272
282
  hook("custom-type:read", async (data, context) => {
273
283
  return await readCustomTypeModel({
274
284
  customTypeID: data.id,
@@ -76,6 +76,7 @@ export const mocks: SharedSliceContent[] = [
76
76
  value: [
77
77
  {
78
78
  __TYPE__: "GroupItemContent",
79
+ key: "01",
79
80
  value: [
80
81
  [
81
82
  "title",
@@ -113,6 +114,7 @@ export const mocks: SharedSliceContent[] = [
113
114
  },
114
115
  {
115
116
  __TYPE__: "GroupItemContent",
117
+ key: "02",
116
118
  value: [
117
119
  [
118
120
  "title",
@@ -150,6 +152,7 @@ export const mocks: SharedSliceContent[] = [
150
152
  },
151
153
  {
152
154
  __TYPE__: "GroupItemContent",
155
+ key: "03",
153
156
  value: [
154
157
  [
155
158
  "title",
@@ -259,6 +262,7 @@ export const mocks: SharedSliceContent[] = [
259
262
  value: [
260
263
  {
261
264
  __TYPE__: "GroupItemContent",
265
+ key: "01",
262
266
  value: [
263
267
  [
264
268
  "title",
@@ -38,6 +38,7 @@ export const mocks: SharedSliceContent[] = [
38
38
  value: [
39
39
  {
40
40
  __TYPE__: "GroupItemContent",
41
+ key: "01",
41
42
  value: [
42
43
  [
43
44
  "image",
@@ -80,6 +81,7 @@ export const mocks: SharedSliceContent[] = [
80
81
  },
81
82
  {
82
83
  __TYPE__: "GroupItemContent",
84
+ key: "02",
83
85
  value: [
84
86
  [
85
87
  "image",
@@ -123,6 +125,7 @@ export const mocks: SharedSliceContent[] = [
123
125
  },
124
126
  {
125
127
  __TYPE__: "GroupItemContent",
128
+ key: "03",
126
129
  value: [
127
130
  [
128
131
  "image",
@@ -166,6 +169,7 @@ export const mocks: SharedSliceContent[] = [
166
169
  },
167
170
  {
168
171
  __TYPE__: "GroupItemContent",
172
+ key: "04",
169
173
  value: [
170
174
  [
171
175
  "image",
@@ -209,6 +213,7 @@ export const mocks: SharedSliceContent[] = [
209
213
  },
210
214
  {
211
215
  __TYPE__: "GroupItemContent",
216
+ key: "05",
212
217
  value: [
213
218
  [
214
219
  "image",