Skip to content

Commit 6f3ea41

Browse files
authored
feat(slice-library): prevent fetching from a different framework (#1763)
1 parent 4bcb959 commit 6f3ea41

5 files changed

Lines changed: 97 additions & 27 deletions

File tree

packages/slice-machine/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/LibrarySlicesDialogContent.tsx

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useDebounce } from "@prismicio/editor-support/React";
1+
import { useDebounce, useStableEffect } from "@prismicio/editor-support/React";
22
import {
33
Box,
44
Button,
@@ -24,6 +24,7 @@ import { ReactNode, Suspense, useEffect, useMemo, useState } from "react";
2424
import { toast } from "react-toastify";
2525

2626
import { getState, telemetry } from "@/apiClient";
27+
import { getFrameworkFromAdapter } from "@/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/utils/getFrameworkFromAdapter";
2728
import { useOnboarding } from "@/features/onboarding/useOnboarding";
2829
import { useAutoSync } from "@/features/sync/AutoSyncProvider";
2930
import { useRepositoryInformation } from "@/hooks/useRepositoryInformation";
@@ -37,6 +38,7 @@ import { EmptyView } from "./EmptyView";
3738
import { useGitIntegration } from "./hooks/useGitIntegration";
3839
import { SliceCard } from "./SliceCard";
3940
import {
41+
AdapterSchema,
4042
CommonDialogContentProps,
4143
GitIntegration,
4244
NewSlice,
@@ -71,16 +73,16 @@ function LibrarySlicesDialogSuspenseContent(
7173

7274
const {
7375
integrations,
76+
fetchedSlices,
7477
isImportingSlices,
7578
fetchSlicesFromGithub,
76-
importedSlices,
7779
resetImportedSlices,
7880
} = useGitIntegration();
7981

8082
const smActions = useSliceMachineActions();
8183
const { syncChanges } = useAutoSync();
8284
const { completeStep: completeOnboardingStep } = useOnboarding();
83-
const prismicRepositoryInformation = useRepositoryInformation();
85+
const prismicRepositoryInformation = usePrismicRepositoryDetails();
8486

8587
useEffect(() => {
8688
if (isTabSelected) {
@@ -102,11 +104,14 @@ function LibrarySlicesDialogSuspenseContent(
102104
const onSelectRepository = (repository: RepositorySelection) => {
103105
setSelectedRepository(repository);
104106
setSelectedSlices([]);
105-
void fetchSlicesFromGithub({ repository });
107+
void fetchSlicesFromGithub({
108+
repository,
109+
targetFramework: prismicRepositoryInformation.framework,
110+
});
106111
};
107112

108113
const onSelectAll = (checked: boolean) => {
109-
setSelectedSlices(checked ? importedSlices : []);
114+
setSelectedSlices(checked ? fetchedSlices : []);
110115
};
111116

112117
const onSelect = (slice: SliceImport) => {
@@ -258,27 +263,28 @@ repositories and set a library for this project.`}
258263
icon="alert"
259264
/>
260265
);
261-
} else if (importedSlices.length === 0) {
266+
} else if (fetchedSlices.length === 0) {
262267
renderedContent = (
263268
<EmptyView
264269
title="No slices found"
265-
description="This repository doesn't contain any Slice components."
270+
description={`This repository doesn't contain any Slice components
271+
compatible with your project.`}
266272
icon="viewDay"
267273
/>
268274
);
269275
} else {
270-
const allSelected = importedSlices.every((slice) =>
276+
const allSelected = fetchedSlices.every((slice) =>
271277
selectedSlices.some((s) => s.model.id === slice.model.id),
272278
);
273-
const someSelected = importedSlices.some((slice) =>
279+
const someSelected = fetchedSlices.some((slice) =>
274280
selectedSlices.some((s) => s.model.id === slice.model.id),
275281
);
276282

277283
let selectAllLabel = "Select all slices";
278284
if (allSelected) {
279285
selectAllLabel = `Selected all slices (${selectedSlices.length})`;
280286
} else if (someSelected) {
281-
selectAllLabel = `${selectedSlices.length} of ${importedSlices.length} selected`;
287+
selectAllLabel = `${selectedSlices.length} of ${fetchedSlices.length} selected`;
282288
}
283289

284290
renderedContent = (
@@ -300,7 +306,7 @@ repositories and set a library for this project.`}
300306
gap={16}
301307
padding={{ inline: 16, bottom: 16 }}
302308
>
303-
{importedSlices.map((slice) => (
309+
{fetchedSlices.map((slice) => (
304310
<SliceCard
305311
key={slice.model.id}
306312
model={slice.model}
@@ -380,8 +386,12 @@ function RepositorySelector(props: RepositorySelectorProps) {
380386
);
381387
}, [integrations]);
382388

383-
useEffect(() => {
389+
useStableEffect(() => {
384390
if (isTabSelected && repositories.length > 0) {
391+
if (repositories.length === 1) {
392+
onSelectRepository(repositories[0]);
393+
}
394+
385395
void telemetry.track({
386396
event: "slice-library:projects-listed",
387397
repositories_count: repositories.length,
@@ -471,6 +481,20 @@ function RepositorySelector(props: RepositorySelectorProps) {
471481
);
472482
}
473483

484+
function usePrismicRepositoryDetails() {
485+
const prismicRepositoryInformation = useRepositoryInformation();
486+
const { data: framework } = useSuspenseQuery({
487+
queryKey: ["getFrameworkFromAdapterName"],
488+
queryFn: async () => {
489+
return getFrameworkFromAdapter(
490+
AdapterSchema.parse(await managerClient.project.getAdapterName()),
491+
);
492+
},
493+
});
494+
495+
return { ...prismicRepositoryInformation, framework };
496+
}
497+
474498
function ComboBoxItemContent(props: {
475499
children: ReactNode;
476500
disabled?: boolean;

packages/slice-machine/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/hooks/useGitIntegration.ts

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@ import { toast } from "react-toastify";
55
import { telemetry } from "@/apiClient";
66
import { managerClient } from "@/managerClient";
77

8-
import { RepositorySelection, SliceImport } from "../types";
8+
import { Framework, RepositorySelection, SliceImport } from "../types";
99
import {
1010
fetchSlicesFromLibraries,
1111
getDefaultBranch,
12-
getSliceLibraries,
12+
getProjectDetails,
1313
} from "../utils/github";
1414

1515
export function useGitIntegration() {
1616
const [isImportingSlices, setIsImportingSlices] = useState(false);
17-
const [importedSlices, setImportedSlices] = useState<SliceImport[]>([]);
17+
const [fetchedSlices, setFetchedSlices] = useState<SliceImport[]>([]);
1818

1919
const { data: githubIntegrations } = useSuspenseQuery({
2020
queryKey: ["getIntegrations"],
@@ -29,14 +29,15 @@ export function useGitIntegration() {
2929
});
3030

3131
const resetImportedSlices = () => {
32-
setImportedSlices([]);
32+
setFetchedSlices([]);
3333
setIsImportingSlices(false);
3434
};
3535

3636
const fetchSlicesFromGithub = async (args: {
3737
repository: RepositorySelection;
38+
targetFramework: Framework;
3839
}) => {
39-
const { repository } = args;
40+
const { repository, targetFramework } = args;
4041

4142
try {
4243
resetImportedSlices();
@@ -62,7 +63,12 @@ export function useGitIntegration() {
6263
let libraries: string[] | undefined;
6364

6465
try {
65-
libraries = await getSliceLibraries({ owner, repo, branch, token });
66+
const project = await getProjectDetails({ owner, repo, branch, token });
67+
if (project.framework !== targetFramework) {
68+
throw new GitHubImportError("Incompatible project framework.");
69+
}
70+
71+
libraries = project.libraries;
6672
} catch (error) {
6773
throw new GitHubImportError(`
6874
Failed to fetch slicemachine.config.json: ${
@@ -89,7 +95,7 @@ export function useGitIntegration() {
8995
throw new GitHubImportError("No slices were found in the libraries.");
9096
}
9197

92-
setImportedSlices(fetchedSlices);
98+
setFetchedSlices(fetchedSlices);
9399
toast.success(
94100
`Found ${fetchedSlices.length} slice(s) from ${libraries.length} library/libraries`,
95101
);
@@ -127,8 +133,8 @@ export function useGitIntegration() {
127133

128134
return {
129135
integrations: githubIntegrations.integrations ?? [],
136+
fetchedSlices: fetchedSlices ?? [],
130137
isImportingSlices,
131-
importedSlices,
132138
resetImportedSlices,
133139
fetchSlicesFromGithub,
134140
};

packages/slice-machine/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/types.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
import { SharedSliceContent } from "@prismicio/types-internal/lib/content";
22
import { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
3+
import { z } from "zod";
4+
5+
export type Framework = "next" | "nuxt" | "sveltekit";
6+
7+
export const AdapterSchema = z.enum([
8+
"@slicemachine/adapter-next",
9+
"@slicemachine/adapter-nuxt",
10+
"@slicemachine/adapter-nuxt2",
11+
"@slicemachine/adapter-sveltekit",
12+
]);
13+
14+
export type Adapter = z.infer<typeof AdapterSchema>;
315

416
export type SliceImport = {
517
image: File;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { Adapter, Framework } from "../types";
2+
3+
export function getFrameworkFromAdapter(adapter: Adapter): Framework;
4+
export function getFrameworkFromAdapter(adapter: string): Framework | undefined;
5+
export function getFrameworkFromAdapter(
6+
adapter: string,
7+
): Framework | undefined {
8+
switch (adapter) {
9+
case "@slicemachine/adapter-next":
10+
return "next";
11+
case "@slicemachine/adapter-nuxt":
12+
case "@slicemachine/adapter-nuxt2":
13+
return "nuxt";
14+
case "@slicemachine/adapter-sveltekit":
15+
return "sveltekit";
16+
default:
17+
return undefined;
18+
}
19+
}

packages/slice-machine/src/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/utils/github.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import { SharedSliceContent } from "@prismicio/types-internal/lib/content";
22
import { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
33
import { z } from "zod";
44

5-
import { SliceFile, SliceImport } from "../types";
5+
import { getFrameworkFromAdapter } from "@/features/customTypes/customTypesBuilder/ImportSlicesFromLibraryModal/utils/getFrameworkFromAdapter";
6+
7+
import { AdapterSchema, Framework, SliceFile, SliceImport } from "../types";
68
import { mapWithConcurrency } from "./mapWithConcurrency";
79

810
class GitHubRepositoryAPI {
@@ -114,7 +116,7 @@ class GitHubRepositoryAPI {
114116
.parse(data);
115117
}
116118

117-
async getSliceLibraries(branch: string) {
119+
async getSliceMachineConfigDetails(branch: string) {
118120
const data = await this.request(
119121
`/repos/${this.owner}/${this.repo}/contents/slicemachine.config.json?ref=${branch}`,
120122
);
@@ -130,8 +132,15 @@ class GitHubRepositoryAPI {
130132
const decodedContent = atob(parsed.content.replace(/\s/g, ""));
131133

132134
return z
133-
.object({ libraries: z.array(z.string()) })
134-
.parse(JSON.parse(decodedContent)).libraries;
135+
.object({
136+
libraries: z.array(z.string()),
137+
adapter: AdapterSchema,
138+
})
139+
.transform((data) => ({
140+
framework: getFrameworkFromAdapter(data.adapter),
141+
libraries: data.libraries,
142+
}))
143+
.parse(JSON.parse(decodedContent));
135144
} else {
136145
throw new Error("No content found in slicemachine.config.json");
137146
}
@@ -151,7 +160,7 @@ export const getDefaultBranch = async ({
151160
return github.getDefaultBranch();
152161
};
153162

154-
export const getSliceLibraries = async ({
163+
export const getProjectDetails = async ({
155164
owner,
156165
repo,
157166
branch,
@@ -161,9 +170,9 @@ export const getSliceLibraries = async ({
161170
repo: string;
162171
branch: string;
163172
token: string;
164-
}): Promise<string[]> => {
173+
}): Promise<{ libraries: string[]; framework: Framework }> => {
165174
const github = new GitHubRepositoryAPI({ owner, repo, token });
166-
return github.getSliceLibraries(branch);
175+
return github.getSliceMachineConfigDetails(branch);
167176
};
168177

169178
const mocksSchema = z.array(

0 commit comments

Comments
 (0)