@qkix/strapi-plugin-ferry 0.1.0
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/LICENSE +21 -0
- package/README.md +262 -0
- package/dist/_chunks/TransferPage-Bhp6vkZE.js +1 -0
- package/dist/_chunks/TransferPage-c8hdFQDQ.mjs +398 -0
- package/dist/admin/index.js +1 -0
- package/dist/admin/index.mjs +43 -0
- package/dist/admin/src/api.d.ts +114 -0
- package/dist/admin/src/components/ExportPanel.d.ts +7 -0
- package/dist/admin/src/components/ImportPanel.d.ts +15 -0
- package/dist/admin/src/components/ReportView.d.ts +6 -0
- package/dist/admin/src/index.d.ts +11 -0
- package/dist/admin/src/pages/TransferPage.d.ts +10 -0
- package/dist/admin/src/pluginId.d.ts +7 -0
- package/dist/server/index.js +6 -0
- package/dist/server/index.mjs +996 -0
- package/dist/server/src/config/index.d.ts +30 -0
- package/dist/server/src/controllers/index.d.ts +12 -0
- package/dist/server/src/controllers/transfer.d.ts +14 -0
- package/dist/server/src/index.d.ts +97 -0
- package/dist/server/src/policies/index.d.ts +5 -0
- package/dist/server/src/register.d.ts +12 -0
- package/dist/server/src/routes/admin.d.ts +12 -0
- package/dist/server/src/routes/index.d.ts +14 -0
- package/dist/server/src/services/csv.d.ts +59 -0
- package/dist/server/src/services/export.d.ts +24 -0
- package/dist/server/src/services/import.d.ts +78 -0
- package/dist/server/src/services/index.d.ts +54 -0
- package/dist/server/src/services/nested.d.ts +38 -0
- package/dist/server/src/services/parse.d.ts +25 -0
- package/dist/server/src/services/populate.d.ts +20 -0
- package/dist/server/src/services/schema.d.ts +81 -0
- package/dist/server/src/services/serialise.d.ts +14 -0
- package/dist/server/src/types.d.ts +145 -0
- package/dist/server/src/uids.d.ts +9 -0
- package/package.json +118 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import { jsxs as t, jsx as e } from "react/jsx-runtime";
|
|
2
|
+
import * as d from "react";
|
|
3
|
+
import { useFetchClient as F, Page as N, Layouts as U } from "@strapi/admin/strapi-admin";
|
|
4
|
+
import { Flex as s, Typography as i, Field as p, SingleSelect as k, SingleSelectOption as v, Box as m, Checkbox as D, Button as B, Badge as P, Loader as V } from "@strapi/design-system";
|
|
5
|
+
import { Download as A, Upload as J } from "@strapi/icons";
|
|
6
|
+
import { PLUGIN_ID as O } from "../admin/index.mjs";
|
|
7
|
+
const j = {
|
|
8
|
+
contentTypes: `/${O}/content-types`,
|
|
9
|
+
plan: (n) => `/${O}/plan/${n}`,
|
|
10
|
+
export: `/${O}/export`,
|
|
11
|
+
preview: `/${O}/import/preview`,
|
|
12
|
+
apply: `/${O}/import`
|
|
13
|
+
}, Z = {
|
|
14
|
+
format: "json",
|
|
15
|
+
status: "draft",
|
|
16
|
+
relations: !0,
|
|
17
|
+
media: !0
|
|
18
|
+
}, _ = {
|
|
19
|
+
status: "draft",
|
|
20
|
+
onExisting: "update",
|
|
21
|
+
onMissingRelation: "skip",
|
|
22
|
+
publish: !0,
|
|
23
|
+
continueOnError: !1
|
|
24
|
+
}, z = (n, h, r) => {
|
|
25
|
+
const c = URL.createObjectURL(new Blob([n], { type: r })), u = document.createElement("a");
|
|
26
|
+
u.href = c, u.download = h, document.body.appendChild(u), u.click(), u.remove(), URL.revokeObjectURL(c);
|
|
27
|
+
}, G = (n) => [
|
|
28
|
+
n.created > 0 ? `${n.created} to create` : null,
|
|
29
|
+
n.updated > 0 ? `${n.updated} to update` : null,
|
|
30
|
+
n.skipped > 0 ? `${n.skipped} skipped` : null,
|
|
31
|
+
n.errored > 0 ? `${n.errored} failed` : null
|
|
32
|
+
].filter(Boolean).join(", ") || "nothing to do", H = ({ contentTypes: n }) => {
|
|
33
|
+
const { get: h, post: r } = F(), [c, u] = d.useState(n[0]?.uid ?? ""), [g, f] = d.useState(Z), [o, b] = d.useState(null), [w, S] = d.useState(!1), [T, R] = d.useState([]), [I, E] = d.useState(null), [y, L] = d.useState(null);
|
|
34
|
+
d.useEffect(() => {
|
|
35
|
+
if (!c) return;
|
|
36
|
+
let a = !0;
|
|
37
|
+
return h(j.plan(c)).then(({ data: $ }) => a && b($)).catch(() => a && b(null)), () => {
|
|
38
|
+
a = !1;
|
|
39
|
+
};
|
|
40
|
+
}, [h, c]);
|
|
41
|
+
const l = (a) => f(($) => ({ ...$, ...a })), C = async () => {
|
|
42
|
+
S(!0), E(null), R([]), L(null);
|
|
43
|
+
try {
|
|
44
|
+
const { data: a } = await r(j.export, {
|
|
45
|
+
uid: c,
|
|
46
|
+
...g,
|
|
47
|
+
envelope: !0
|
|
48
|
+
});
|
|
49
|
+
z(a.body, a.filename, a.mime), R(a.warnings ?? []), L(`${a.filename} (${a.count} document${a.count === 1 ? "" : "s"})`);
|
|
50
|
+
} catch (a) {
|
|
51
|
+
E(a?.response?.data?.error?.message ?? "The export failed.");
|
|
52
|
+
} finally {
|
|
53
|
+
S(!1);
|
|
54
|
+
}
|
|
55
|
+
}, x = o ? [
|
|
56
|
+
`${o.scalars.length} field(s)`,
|
|
57
|
+
g.relations && o.relations.length > 0 ? `${o.relations.length} relation(s)` : null,
|
|
58
|
+
o.components.length > 0 ? `${o.components.length} component(s)` : null,
|
|
59
|
+
o.dynamicZones.length > 0 ? `${o.dynamicZones.length} dynamic zone(s)` : null,
|
|
60
|
+
g.media && o.media.length > 0 ? `${o.media.length} media field(s)` : null
|
|
61
|
+
].filter(Boolean) : [];
|
|
62
|
+
return /* @__PURE__ */ t(s, { direction: "column", gap: 4, alignItems: "stretch", children: [
|
|
63
|
+
/* @__PURE__ */ e(i, { variant: "delta", children: "Export" }),
|
|
64
|
+
/* @__PURE__ */ t(p.Root, { name: "export-uid", children: [
|
|
65
|
+
/* @__PURE__ */ e(p.Label, { children: "Content type" }),
|
|
66
|
+
/* @__PURE__ */ e(k, { value: c, onChange: (a) => u(String(a)), children: n.map((a) => /* @__PURE__ */ e(v, { value: a.uid, children: a.label }, a.uid)) })
|
|
67
|
+
] }),
|
|
68
|
+
/* @__PURE__ */ t(s, { gap: 4, wrap: "wrap", alignItems: "flex-end", children: [
|
|
69
|
+
/* @__PURE__ */ e(m, { minWidth: "160px", children: /* @__PURE__ */ t(p.Root, { name: "export-format", children: [
|
|
70
|
+
/* @__PURE__ */ e(p.Label, { children: "Format" }),
|
|
71
|
+
/* @__PURE__ */ t(
|
|
72
|
+
k,
|
|
73
|
+
{
|
|
74
|
+
value: g.format,
|
|
75
|
+
onChange: (a) => l({ format: a }),
|
|
76
|
+
children: [
|
|
77
|
+
/* @__PURE__ */ e(v, { value: "json", children: "JSON - everything" }),
|
|
78
|
+
/* @__PURE__ */ e(v, { value: "csv", children: "CSV - flat fields" })
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
] }) }),
|
|
83
|
+
/* @__PURE__ */ e(m, { minWidth: "160px", children: /* @__PURE__ */ t(p.Root, { name: "export-status", children: [
|
|
84
|
+
/* @__PURE__ */ e(p.Label, { children: "Version" }),
|
|
85
|
+
/* @__PURE__ */ t(
|
|
86
|
+
k,
|
|
87
|
+
{
|
|
88
|
+
value: g.status,
|
|
89
|
+
onChange: (a) => l({ status: a }),
|
|
90
|
+
disabled: o ? !o.draftAndPublish : !1,
|
|
91
|
+
children: [
|
|
92
|
+
/* @__PURE__ */ e(v, { value: "draft", children: "Draft" }),
|
|
93
|
+
/* @__PURE__ */ e(v, { value: "published", children: "Published" })
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
] }) })
|
|
98
|
+
] }),
|
|
99
|
+
/* @__PURE__ */ t(s, { gap: 4, wrap: "wrap", children: [
|
|
100
|
+
/* @__PURE__ */ e(
|
|
101
|
+
D,
|
|
102
|
+
{
|
|
103
|
+
checked: g.relations,
|
|
104
|
+
onCheckedChange: (a) => l({ relations: !!a }),
|
|
105
|
+
children: "Relations"
|
|
106
|
+
}
|
|
107
|
+
),
|
|
108
|
+
/* @__PURE__ */ e(
|
|
109
|
+
D,
|
|
110
|
+
{
|
|
111
|
+
checked: g.media,
|
|
112
|
+
onCheckedChange: (a) => l({ media: !!a }),
|
|
113
|
+
children: "Media references"
|
|
114
|
+
}
|
|
115
|
+
)
|
|
116
|
+
] }),
|
|
117
|
+
x.length > 0 && /* @__PURE__ */ t(i, { variant: "pi", textColor: "neutral600", children: [
|
|
118
|
+
"Carries ",
|
|
119
|
+
x.join(", "),
|
|
120
|
+
".",
|
|
121
|
+
g.format === "csv" && (o?.components.length || o?.dynamicZones.length) ? " CSV leaves components and dynamic zones behind - use JSON to keep them." : ""
|
|
122
|
+
] }),
|
|
123
|
+
/* @__PURE__ */ e(m, { children: /* @__PURE__ */ e(B, { startIcon: /* @__PURE__ */ e(A, {}), onClick: C, loading: w, disabled: !c, children: "Export" }) }),
|
|
124
|
+
y && /* @__PURE__ */ t(i, { variant: "pi", textColor: "success600", children: [
|
|
125
|
+
"Saved ",
|
|
126
|
+
y,
|
|
127
|
+
"."
|
|
128
|
+
] }),
|
|
129
|
+
T.map((a, $) => /* @__PURE__ */ e(i, { variant: "pi", textColor: "warning700", children: a }, $)),
|
|
130
|
+
I && /* @__PURE__ */ e(i, { variant: "pi", textColor: "danger600", children: I })
|
|
131
|
+
] });
|
|
132
|
+
}, W = ({ label: n, value: h, tone: r }) => /* @__PURE__ */ t(s, { direction: "column", gap: 1, alignItems: "flex-start", children: [
|
|
133
|
+
/* @__PURE__ */ e(i, { variant: "beta", textColor: h > 0 ? r : "neutral500", children: h }),
|
|
134
|
+
/* @__PURE__ */ e(i, { variant: "pi", textColor: "neutral600", children: n })
|
|
135
|
+
] }), K = ({ report: n }) => {
|
|
136
|
+
const h = n.rows.filter((r) => r.outcome === "error");
|
|
137
|
+
return /* @__PURE__ */ t(s, { direction: "column", gap: 4, alignItems: "stretch", children: [
|
|
138
|
+
/* @__PURE__ */ t(
|
|
139
|
+
m,
|
|
140
|
+
{
|
|
141
|
+
padding: 4,
|
|
142
|
+
hasRadius: !0,
|
|
143
|
+
background: "neutral0",
|
|
144
|
+
borderColor: "neutral200",
|
|
145
|
+
borderWidth: "1px",
|
|
146
|
+
borderStyle: "solid",
|
|
147
|
+
children: [
|
|
148
|
+
/* @__PURE__ */ t(s, { justifyContent: "space-between", alignItems: "center", paddingBottom: 3, children: [
|
|
149
|
+
/* @__PURE__ */ e(i, { variant: "delta", children: n.applied ? "Imported" : "Dry run - nothing written yet" }),
|
|
150
|
+
/* @__PURE__ */ e(P, { children: G(n) })
|
|
151
|
+
] }),
|
|
152
|
+
/* @__PURE__ */ t(s, { gap: 8, wrap: "wrap", children: [
|
|
153
|
+
/* @__PURE__ */ e(W, { label: "in the file", value: n.total, tone: "neutral800" }),
|
|
154
|
+
/* @__PURE__ */ e(W, { label: "to create", value: n.created, tone: "success600" }),
|
|
155
|
+
/* @__PURE__ */ e(W, { label: "to update", value: n.updated, tone: "primary600" }),
|
|
156
|
+
/* @__PURE__ */ e(W, { label: "skipped", value: n.skipped, tone: "neutral600" }),
|
|
157
|
+
/* @__PURE__ */ e(W, { label: "failed", value: n.errored, tone: "danger600" })
|
|
158
|
+
] })
|
|
159
|
+
]
|
|
160
|
+
}
|
|
161
|
+
),
|
|
162
|
+
n.warnings.map((r, c) => /* @__PURE__ */ e(
|
|
163
|
+
m,
|
|
164
|
+
{
|
|
165
|
+
padding: 3,
|
|
166
|
+
hasRadius: !0,
|
|
167
|
+
background: "warning100",
|
|
168
|
+
borderColor: "warning200",
|
|
169
|
+
borderWidth: "1px",
|
|
170
|
+
borderStyle: "solid",
|
|
171
|
+
children: /* @__PURE__ */ e(i, { variant: "pi", textColor: "warning700", children: r })
|
|
172
|
+
},
|
|
173
|
+
c
|
|
174
|
+
)),
|
|
175
|
+
n.unresolved.length > 0 && /* @__PURE__ */ t(
|
|
176
|
+
m,
|
|
177
|
+
{
|
|
178
|
+
padding: 4,
|
|
179
|
+
hasRadius: !0,
|
|
180
|
+
background: "neutral0",
|
|
181
|
+
borderColor: "warning200",
|
|
182
|
+
borderWidth: "1px",
|
|
183
|
+
borderStyle: "solid",
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ e(i, { variant: "delta", children: "Relations that point at nothing" }),
|
|
186
|
+
/* @__PURE__ */ e(m, { paddingTop: 1, paddingBottom: 3, children: /* @__PURE__ */ e(i, { variant: "pi", textColor: "neutral600", children: 'These targets are neither in the file nor in this environment. With "drop the link" the rows still import without them; with "refuse the import" nothing is written at all.' }) }),
|
|
187
|
+
/* @__PURE__ */ t(s, { direction: "column", gap: 2, alignItems: "stretch", children: [
|
|
188
|
+
n.unresolved.slice(0, 25).map((r, c) => /* @__PURE__ */ t(s, { gap: 3, alignItems: "baseline", wrap: "wrap", children: [
|
|
189
|
+
/* @__PURE__ */ t(P, { children: [
|
|
190
|
+
"row ",
|
|
191
|
+
r.row
|
|
192
|
+
] }),
|
|
193
|
+
/* @__PURE__ */ e(i, { variant: "omega", fontWeight: "bold", children: r.field }),
|
|
194
|
+
/* @__PURE__ */ e(i, { variant: "pi", textColor: "neutral600", children: r.target }),
|
|
195
|
+
/* @__PURE__ */ e(i, { variant: "pi", textColor: "danger600", children: r.keys.join(", ") })
|
|
196
|
+
] }, c)),
|
|
197
|
+
n.unresolved.length > 25 && /* @__PURE__ */ t(i, { variant: "pi", textColor: "neutral600", children: [
|
|
198
|
+
"and ",
|
|
199
|
+
n.unresolved.length - 25,
|
|
200
|
+
" more"
|
|
201
|
+
] })
|
|
202
|
+
] })
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
),
|
|
206
|
+
h.length > 0 && /* @__PURE__ */ t(
|
|
207
|
+
m,
|
|
208
|
+
{
|
|
209
|
+
padding: 4,
|
|
210
|
+
hasRadius: !0,
|
|
211
|
+
background: "neutral0",
|
|
212
|
+
borderColor: "danger200",
|
|
213
|
+
borderWidth: "1px",
|
|
214
|
+
borderStyle: "solid",
|
|
215
|
+
children: [
|
|
216
|
+
/* @__PURE__ */ e(i, { variant: "delta", textColor: "danger600", children: "Rows that failed" }),
|
|
217
|
+
/* @__PURE__ */ e(m, { paddingTop: 2, children: /* @__PURE__ */ e(s, { direction: "column", gap: 2, alignItems: "stretch", children: h.slice(0, 25).map((r) => /* @__PURE__ */ t(s, { gap: 3, alignItems: "baseline", wrap: "wrap", children: [
|
|
218
|
+
/* @__PURE__ */ t(P, { children: [
|
|
219
|
+
"row ",
|
|
220
|
+
r.row
|
|
221
|
+
] }),
|
|
222
|
+
/* @__PURE__ */ e(i, { variant: "pi", textColor: "neutral600", children: r.documentId ?? "no documentId" }),
|
|
223
|
+
/* @__PURE__ */ e(i, { variant: "pi", textColor: "danger600", children: r.message })
|
|
224
|
+
] }, r.row)) }) })
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
] });
|
|
229
|
+
}, X = ({ contentTypes: n }) => {
|
|
230
|
+
const { post: h } = F(), [r, c] = d.useState(n[0]?.uid ?? ""), [u, g] = d.useState(_), [f, o] = d.useState(null), [b, w] = d.useState(null), [S, T] = d.useState(!1), [R, I] = d.useState(null), E = d.useRef(null), y = (l) => {
|
|
231
|
+
g((C) => ({ ...C, ...l })), w(null);
|
|
232
|
+
}, L = async (l) => {
|
|
233
|
+
if (!f) return;
|
|
234
|
+
T(!0), I(null);
|
|
235
|
+
const C = new FormData();
|
|
236
|
+
C.append("file", f), C.append("uid", r), Object.entries(u).forEach(([x, a]) => C.append(x, String(a)));
|
|
237
|
+
try {
|
|
238
|
+
const { data: x } = await h(l ? j.apply : j.preview, C);
|
|
239
|
+
w(x);
|
|
240
|
+
} catch (x) {
|
|
241
|
+
I(x?.response?.data?.error?.message ?? "The file could not be read."), w(null);
|
|
242
|
+
} finally {
|
|
243
|
+
T(!1);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
return /* @__PURE__ */ t(s, { direction: "column", gap: 4, alignItems: "stretch", children: [
|
|
247
|
+
/* @__PURE__ */ e(i, { variant: "delta", children: "Import" }),
|
|
248
|
+
/* @__PURE__ */ t(p.Root, { name: "import-uid", children: [
|
|
249
|
+
/* @__PURE__ */ e(p.Label, { children: "Content type" }),
|
|
250
|
+
/* @__PURE__ */ e(
|
|
251
|
+
k,
|
|
252
|
+
{
|
|
253
|
+
value: r,
|
|
254
|
+
onChange: (l) => {
|
|
255
|
+
c(String(l)), w(null);
|
|
256
|
+
},
|
|
257
|
+
children: n.map((l) => /* @__PURE__ */ e(v, { value: l.uid, children: l.label }, l.uid))
|
|
258
|
+
}
|
|
259
|
+
)
|
|
260
|
+
] }),
|
|
261
|
+
/* @__PURE__ */ t(p.Root, { name: "import-file", children: [
|
|
262
|
+
/* @__PURE__ */ e(p.Label, { children: "File" }),
|
|
263
|
+
/* @__PURE__ */ t(s, { gap: 2, alignItems: "center", children: [
|
|
264
|
+
/* @__PURE__ */ e(B, { variant: "secondary", onClick: () => E.current?.click(), children: "Choose a file" }),
|
|
265
|
+
/* @__PURE__ */ e(i, { variant: "pi", textColor: f ? "neutral800" : "neutral500", children: f ? f.name : "JSON or CSV" })
|
|
266
|
+
] }),
|
|
267
|
+
/* @__PURE__ */ e(
|
|
268
|
+
"input",
|
|
269
|
+
{
|
|
270
|
+
ref: E,
|
|
271
|
+
type: "file",
|
|
272
|
+
accept: ".json,.csv,application/json,text/csv",
|
|
273
|
+
style: { display: "none" },
|
|
274
|
+
onChange: (l) => {
|
|
275
|
+
o(l.target.files?.[0] ?? null), w(null);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
|
+
] }),
|
|
280
|
+
/* @__PURE__ */ t(s, { gap: 4, wrap: "wrap", alignItems: "flex-end", children: [
|
|
281
|
+
/* @__PURE__ */ e(m, { minWidth: "200px", children: /* @__PURE__ */ t(p.Root, { name: "import-existing", children: [
|
|
282
|
+
/* @__PURE__ */ e(p.Label, { children: "When it is already there" }),
|
|
283
|
+
/* @__PURE__ */ t(
|
|
284
|
+
k,
|
|
285
|
+
{
|
|
286
|
+
value: u.onExisting,
|
|
287
|
+
onChange: (l) => y({ onExisting: l === "skip" ? "skip" : "update" }),
|
|
288
|
+
children: [
|
|
289
|
+
/* @__PURE__ */ e(v, { value: "update", children: "Update it" }),
|
|
290
|
+
/* @__PURE__ */ e(v, { value: "skip", children: "Leave it alone" })
|
|
291
|
+
]
|
|
292
|
+
}
|
|
293
|
+
)
|
|
294
|
+
] }) }),
|
|
295
|
+
/* @__PURE__ */ e(m, { minWidth: "240px", children: /* @__PURE__ */ t(p.Root, { name: "import-missing", children: [
|
|
296
|
+
/* @__PURE__ */ e(p.Label, { children: "When a relation points at nothing" }),
|
|
297
|
+
/* @__PURE__ */ t(
|
|
298
|
+
k,
|
|
299
|
+
{
|
|
300
|
+
value: u.onMissingRelation,
|
|
301
|
+
onChange: (l) => y({ onMissingRelation: l === "fail" ? "fail" : "skip" }),
|
|
302
|
+
children: [
|
|
303
|
+
/* @__PURE__ */ e(v, { value: "skip", children: "Drop the link, keep the row" }),
|
|
304
|
+
/* @__PURE__ */ e(v, { value: "fail", children: "Refuse the import" })
|
|
305
|
+
]
|
|
306
|
+
}
|
|
307
|
+
)
|
|
308
|
+
] }) }),
|
|
309
|
+
/* @__PURE__ */ e(m, { minWidth: "160px", children: /* @__PURE__ */ t(p.Root, { name: "import-status", children: [
|
|
310
|
+
/* @__PURE__ */ e(p.Label, { children: "Import as" }),
|
|
311
|
+
/* @__PURE__ */ t(
|
|
312
|
+
k,
|
|
313
|
+
{
|
|
314
|
+
value: u.status,
|
|
315
|
+
onChange: (l) => y({ status: l }),
|
|
316
|
+
children: [
|
|
317
|
+
/* @__PURE__ */ e(v, { value: "draft", children: "Draft" }),
|
|
318
|
+
/* @__PURE__ */ e(v, { value: "published", children: "Published" })
|
|
319
|
+
]
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
] }) })
|
|
323
|
+
] }),
|
|
324
|
+
/* @__PURE__ */ e(
|
|
325
|
+
D,
|
|
326
|
+
{
|
|
327
|
+
checked: u.continueOnError,
|
|
328
|
+
onCheckedChange: (l) => y({ continueOnError: !!l }),
|
|
329
|
+
children: "Keep going past a row that fails"
|
|
330
|
+
}
|
|
331
|
+
),
|
|
332
|
+
/* @__PURE__ */ e(i, { variant: "pi", textColor: "neutral600", children: u.continueOnError ? "Rows that fail are reported and the rest are committed." : "All or nothing: if any row fails, the whole import is rolled back and nothing changes." }),
|
|
333
|
+
/* @__PURE__ */ t(s, { gap: 2, children: [
|
|
334
|
+
/* @__PURE__ */ e(
|
|
335
|
+
B,
|
|
336
|
+
{
|
|
337
|
+
variant: "secondary",
|
|
338
|
+
startIcon: /* @__PURE__ */ e(J, {}),
|
|
339
|
+
onClick: () => L(!1),
|
|
340
|
+
loading: S,
|
|
341
|
+
disabled: !f || !r,
|
|
342
|
+
children: "Dry run"
|
|
343
|
+
}
|
|
344
|
+
),
|
|
345
|
+
/* @__PURE__ */ e(
|
|
346
|
+
B,
|
|
347
|
+
{
|
|
348
|
+
onClick: () => L(!0),
|
|
349
|
+
loading: S,
|
|
350
|
+
disabled: !f || !r || !b || b.applied,
|
|
351
|
+
children: "Import for real"
|
|
352
|
+
}
|
|
353
|
+
)
|
|
354
|
+
] }),
|
|
355
|
+
!b && f && /* @__PURE__ */ e(i, { variant: "pi", textColor: "neutral600", children: "Run the dry run first. Nothing is written until you have seen what it would do." }),
|
|
356
|
+
R && /* @__PURE__ */ e(i, { variant: "pi", textColor: "danger600", children: R }),
|
|
357
|
+
b && /* @__PURE__ */ e(K, { report: b })
|
|
358
|
+
] });
|
|
359
|
+
}, M = ({ children: n }) => /* @__PURE__ */ e(
|
|
360
|
+
m,
|
|
361
|
+
{
|
|
362
|
+
padding: 6,
|
|
363
|
+
hasRadius: !0,
|
|
364
|
+
background: "neutral0",
|
|
365
|
+
borderColor: "neutral200",
|
|
366
|
+
borderWidth: "1px",
|
|
367
|
+
borderStyle: "solid",
|
|
368
|
+
flex: "1",
|
|
369
|
+
minWidth: "380px",
|
|
370
|
+
children: n
|
|
371
|
+
}
|
|
372
|
+
), te = () => {
|
|
373
|
+
const { get: n } = F(), [h, r] = d.useState([]), [c, u] = d.useState(!0), [g, f] = d.useState(null);
|
|
374
|
+
return d.useEffect(() => {
|
|
375
|
+
let o = !0;
|
|
376
|
+
return n(j.contentTypes).then(({ data: b }) => o && r(b.contentTypes)).catch(() => o && f("Could not read the list of content types.")).finally(() => o && u(!1)), () => {
|
|
377
|
+
o = !1;
|
|
378
|
+
};
|
|
379
|
+
}, [n]), /* @__PURE__ */ t(N.Main, { children: [
|
|
380
|
+
/* @__PURE__ */ e(
|
|
381
|
+
U.Header,
|
|
382
|
+
{
|
|
383
|
+
title: "Ferry",
|
|
384
|
+
subtitle: "Move content in and out as JSON or CSV, with relations and components intact"
|
|
385
|
+
}
|
|
386
|
+
),
|
|
387
|
+
/* @__PURE__ */ e(U.Content, { children: c ? /* @__PURE__ */ e(s, { justifyContent: "center", padding: 10, children: /* @__PURE__ */ e(V, { small: !0, children: "Reading the schema" }) }) : g ? /* @__PURE__ */ e(i, { textColor: "danger600", children: g }) : h.length === 0 ? /* @__PURE__ */ t(s, { direction: "column", gap: 2, padding: 10, alignItems: "center", children: [
|
|
388
|
+
/* @__PURE__ */ e(i, { variant: "delta", children: "Nothing to carry yet" }),
|
|
389
|
+
/* @__PURE__ */ e(i, { textColor: "neutral600", children: "Create a content type, and it will show up here." })
|
|
390
|
+
] }) : /* @__PURE__ */ t(s, { gap: 6, alignItems: "flex-start", wrap: "wrap", children: [
|
|
391
|
+
/* @__PURE__ */ e(M, { children: /* @__PURE__ */ e(H, { contentTypes: h }) }),
|
|
392
|
+
/* @__PURE__ */ e(M, { children: /* @__PURE__ */ e(X, { contentTypes: h }) })
|
|
393
|
+
] }) })
|
|
394
|
+
] });
|
|
395
|
+
};
|
|
396
|
+
export {
|
|
397
|
+
te as TransferPage
|
|
398
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});const o=require("@strapi/icons"),a=(r,e,i)=>{const t=r[e];return t?typeof t=="function"?t():Promise.resolve(t):new Promise((l,s)=>{(typeof queueMicrotask=="function"?queueMicrotask:setTimeout)(s.bind(null,new Error("Unknown variable dynamic import: "+e+(e.split("/").length!==i?". Note that variables only represent file names one level deep.":""))))})},n="ferry",u={register(r){r.registerPlugin({id:n,name:n,isReady:!0}),r.addMenuLink({to:`/plugins/${n}`,icon:o.ArrowsHorizontal,intlLabel:{id:`${n}.menu.label`,defaultMessage:"Ferry"},Component:async()=>{const{TransferPage:e}=await Promise.resolve().then(()=>require("../_chunks/TransferPage-Bhp6vkZE.js"));return{default:e}},permissions:[{action:`plugin::${n}.export`,subject:null}]})},async registerTrads({locales:r}){return Promise.all(r.map(async e=>{try{const{default:i}=await a(Object.assign({}),`./translations/${e}.json`,3);return{data:i,locale:e}}catch{return{data:{},locale:e}}}))}};exports.PLUGIN_ID=n;exports.default=u;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ArrowsHorizontal as s } from "@strapi/icons";
|
|
2
|
+
const o = (n, e, i) => {
|
|
3
|
+
const t = n[e];
|
|
4
|
+
return t ? typeof t == "function" ? t() : Promise.resolve(t) : new Promise((u, a) => {
|
|
5
|
+
(typeof queueMicrotask == "function" ? queueMicrotask : setTimeout)(
|
|
6
|
+
a.bind(
|
|
7
|
+
null,
|
|
8
|
+
new Error(
|
|
9
|
+
"Unknown variable dynamic import: " + e + (e.split("/").length !== i ? ". Note that variables only represent file names one level deep." : "")
|
|
10
|
+
)
|
|
11
|
+
)
|
|
12
|
+
);
|
|
13
|
+
});
|
|
14
|
+
}, r = "ferry", c = {
|
|
15
|
+
register(n) {
|
|
16
|
+
n.registerPlugin({ id: r, name: r, isReady: !0 }), n.addMenuLink({
|
|
17
|
+
to: `/plugins/${r}`,
|
|
18
|
+
icon: s,
|
|
19
|
+
intlLabel: { id: `${r}.menu.label`, defaultMessage: "Ferry" },
|
|
20
|
+
Component: async () => {
|
|
21
|
+
const { TransferPage: e } = await import("../_chunks/TransferPage-c8hdFQDQ.mjs");
|
|
22
|
+
return { default: e };
|
|
23
|
+
},
|
|
24
|
+
permissions: [{ action: `plugin::${r}.export`, subject: null }]
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
async registerTrads({ locales: n }) {
|
|
28
|
+
return Promise.all(
|
|
29
|
+
n.map(async (e) => {
|
|
30
|
+
try {
|
|
31
|
+
const { default: i } = await o(/* @__PURE__ */ Object.assign({}), `./translations/${e}.json`, 3);
|
|
32
|
+
return { data: i, locale: e };
|
|
33
|
+
} catch {
|
|
34
|
+
return { data: {}, locale: e };
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
export {
|
|
41
|
+
r as PLUGIN_ID,
|
|
42
|
+
c as default
|
|
43
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export type Format = 'json' | 'csv';
|
|
2
|
+
export type Status = 'draft' | 'published';
|
|
3
|
+
export interface CatalogueEntry {
|
|
4
|
+
uid: string;
|
|
5
|
+
label: string;
|
|
6
|
+
kind: 'collectionType' | 'singleType';
|
|
7
|
+
}
|
|
8
|
+
export interface FieldPlan {
|
|
9
|
+
uid: string;
|
|
10
|
+
kind: 'collectionType' | 'singleType';
|
|
11
|
+
draftAndPublish: boolean;
|
|
12
|
+
localized: boolean;
|
|
13
|
+
scalars: Array<{
|
|
14
|
+
name: string;
|
|
15
|
+
type: string;
|
|
16
|
+
}>;
|
|
17
|
+
relations: Array<{
|
|
18
|
+
name: string;
|
|
19
|
+
target: string;
|
|
20
|
+
many: boolean;
|
|
21
|
+
}>;
|
|
22
|
+
components: Array<{
|
|
23
|
+
name: string;
|
|
24
|
+
component: string;
|
|
25
|
+
repeatable: boolean;
|
|
26
|
+
}>;
|
|
27
|
+
dynamicZones: Array<{
|
|
28
|
+
name: string;
|
|
29
|
+
components: string[];
|
|
30
|
+
}>;
|
|
31
|
+
media: Array<{
|
|
32
|
+
name: string;
|
|
33
|
+
many: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
skipped: Array<{
|
|
36
|
+
name: string;
|
|
37
|
+
reason: string;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
export interface RowReport {
|
|
41
|
+
row: number;
|
|
42
|
+
documentId?: string;
|
|
43
|
+
outcome: 'create' | 'update' | 'skip' | 'error';
|
|
44
|
+
message?: string;
|
|
45
|
+
}
|
|
46
|
+
export interface UnresolvedRelation {
|
|
47
|
+
row: number;
|
|
48
|
+
documentId?: string;
|
|
49
|
+
field: string;
|
|
50
|
+
target: string;
|
|
51
|
+
keys: string[];
|
|
52
|
+
}
|
|
53
|
+
export interface Report {
|
|
54
|
+
contentType: string;
|
|
55
|
+
applied: boolean;
|
|
56
|
+
total: number;
|
|
57
|
+
created: number;
|
|
58
|
+
updated: number;
|
|
59
|
+
skipped: number;
|
|
60
|
+
errored: number;
|
|
61
|
+
rows: RowReport[];
|
|
62
|
+
unresolved: UnresolvedRelation[];
|
|
63
|
+
warnings: string[];
|
|
64
|
+
}
|
|
65
|
+
export declare const routes: {
|
|
66
|
+
contentTypes: string;
|
|
67
|
+
plan: (uid: string) => string;
|
|
68
|
+
export: string;
|
|
69
|
+
preview: string;
|
|
70
|
+
apply: string;
|
|
71
|
+
};
|
|
72
|
+
export interface ExportRequest {
|
|
73
|
+
uid: string;
|
|
74
|
+
format: Format;
|
|
75
|
+
status: Status;
|
|
76
|
+
relations: boolean;
|
|
77
|
+
media: boolean;
|
|
78
|
+
documentIds?: string[];
|
|
79
|
+
}
|
|
80
|
+
export interface ImportRequest {
|
|
81
|
+
uid: string;
|
|
82
|
+
status: Status;
|
|
83
|
+
onExisting: 'update' | 'skip';
|
|
84
|
+
onMissingRelation: 'skip' | 'fail';
|
|
85
|
+
publish: boolean;
|
|
86
|
+
continueOnError: boolean;
|
|
87
|
+
}
|
|
88
|
+
export declare const DEFAULT_EXPORT: Omit<ExportRequest, 'uid'>;
|
|
89
|
+
export declare const DEFAULT_IMPORT: Omit<ImportRequest, 'uid'>;
|
|
90
|
+
/**
|
|
91
|
+
* The export, as JSON with the file inside it.
|
|
92
|
+
*
|
|
93
|
+
* Asked for with `envelope: true`. The alternative is a real download, which is
|
|
94
|
+
* what a curl user wants and what the admin panel cannot use: Strapi's fetch
|
|
95
|
+
* client parses every response as JSON whatever `responseType` it is given.
|
|
96
|
+
*/
|
|
97
|
+
export interface ExportEnvelope {
|
|
98
|
+
filename: string;
|
|
99
|
+
mime: string;
|
|
100
|
+
body: string;
|
|
101
|
+
count: number;
|
|
102
|
+
warnings: string[];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Hands the browser a file it already has.
|
|
106
|
+
*
|
|
107
|
+
* Not a link to the export route: an admin endpoint needs the session's
|
|
108
|
+
* Authorization header, and a plain navigation cannot send one - which is the
|
|
109
|
+
* single most reported fault against the plugin this replaces, where four of
|
|
110
|
+
* eleven open issues are a 401 on download.
|
|
111
|
+
*/
|
|
112
|
+
export declare const saveFile: (body: BlobPart, filename: string, mime: string) => void;
|
|
113
|
+
/** A summary line for the report, in the order that matters when scanning it. */
|
|
114
|
+
export declare const summarise: (report: Report) => string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import type { CatalogueEntry } from '../api';
|
|
3
|
+
interface Props {
|
|
4
|
+
contentTypes: CatalogueEntry[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* The import, in two deliberate steps.
|
|
8
|
+
*
|
|
9
|
+
* The file is read and resolved against the database first, and the button that
|
|
10
|
+
* writes only appears once there is a report to read. It is the same request
|
|
11
|
+
* either way, with one flag changed, so the preview cannot drift from what the
|
|
12
|
+
* import then does.
|
|
13
|
+
*/
|
|
14
|
+
export declare const ImportPanel: ({ contentTypes }: Props) => React.JSX.Element;
|
|
15
|
+
export default ImportPanel;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Both directions on one page.
|
|
4
|
+
*
|
|
5
|
+
* Not tabs: taking a copy out of one environment and putting it into another is
|
|
6
|
+
* one job done twice, and hiding half of it behind a tab makes the second half
|
|
7
|
+
* feel like a different tool.
|
|
8
|
+
*/
|
|
9
|
+
export declare const TransferPage: () => React.JSX.Element;
|
|
10
|
+
export default TransferPage;
|