@qkix/strapi-plugin-better-blocks 0.23.0 → 0.23.2

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.
@@ -1,7 +1,7 @@
1
- const g = ({ strapi: t }) => {
2
- }, k = ({ strapi: t }) => {
3
- }, y = ({ strapi: t }) => {
4
- t.customFields.register({
1
+ const k = ({ strapi: o }) => {
2
+ }, w = ({ strapi: o }) => {
3
+ }, y = ({ strapi: o }) => {
4
+ o.customFields.register({
5
5
  name: "better-blocks",
6
6
  plugin: "better-blocks",
7
7
  type: "json",
@@ -10,7 +10,7 @@ const g = ({ strapi: t }) => {
10
10
  isResizable: !1
11
11
  }
12
12
  });
13
- }, w = {
13
+ }, C = {
14
14
  default: {
15
15
  /**
16
16
  * Defaults for the collapsible Details / Summary block.
@@ -97,32 +97,36 @@ const g = ({ strapi: t }) => {
97
97
  cache: !0,
98
98
  cacheTTL: 86400,
99
99
  // seconds (24h)
100
+ // Upper bound on cached embeds, evicted least-recently-used. Without one
101
+ // the cache keeps an entry for every post ever embedded, for the life of
102
+ // the process — expiry alone never frees anything nobody asks for again.
103
+ cacheMaxEntries: 500,
100
104
  instagram: { accessToken: void 0 },
101
105
  facebook: { accessToken: void 0 }
102
106
  }
103
107
  },
104
- validator(t = {}) {
105
- const o = t?.details?.style;
106
- if (o && o !== "github" && o !== "custom")
108
+ validator(o = {}) {
109
+ const t = o?.details?.style;
110
+ if (t && t !== "github" && t !== "custom")
107
111
  throw new Error(
108
- `[better-blocks] config.details.style must be "github" or "custom" (received "${o}")`
112
+ `[better-blocks] config.details.style must be "github" or "custom" (received "${t}")`
109
113
  );
110
114
  }
111
- }, C = {}, v = ({ strapi: t }) => ({
112
- index(o) {
113
- o.body = t.plugin("better-blocks").service("service").getWelcomeMessage();
115
+ }, v = {}, x = ({ strapi: o }) => ({
116
+ index(t) {
117
+ t.body = o.plugin("better-blocks").service("service").getWelcomeMessage();
114
118
  },
115
119
  /**
116
120
  * Exposes the plugin config (config/plugins.js merged with server defaults) to
117
121
  * the admin panel, so blocks like Details can honour admin-level settings.
118
122
  */
119
- getConfig(o) {
120
- o.body = {
121
- details: t.plugin("better-blocks").config("details", {
123
+ getConfig(t) {
124
+ t.body = {
125
+ details: o.plugin("better-blocks").config("details", {
122
126
  defaultSummary: "Click to expand",
123
127
  style: "github"
124
128
  }),
125
- button: t.plugin("better-blocks").config("button", {
129
+ button: o.plugin("better-blocks").config("button", {
126
130
  defaultStyle: {
127
131
  backgroundColor: "#4945ff",
128
132
  textColor: "#ffffff",
@@ -155,7 +159,7 @@ const g = ({ strapi: t }) => {
155
159
  }
156
160
  }),
157
161
  social: (() => {
158
- const e = t.plugin("better-blocks").config("social", {
162
+ const e = o.plugin("better-blocks").config("social", {
159
163
  enabled: !0,
160
164
  platforms: [
161
165
  "twitter",
@@ -175,35 +179,35 @@ const g = ({ strapi: t }) => {
175
179
  })()
176
180
  };
177
181
  }
178
- }), x = [
182
+ }), E = [
179
183
  "twitter",
180
184
  "instagram",
181
185
  "facebook",
182
186
  "tiktok",
183
187
  "linkedin",
184
188
  "pinterest"
185
- ], $ = ({ strapi: t }) => ({
189
+ ], $ = ({ strapi: o }) => ({
186
190
  /**
187
191
  * GET /better-blocks/oembed?url=...&platform=...
188
- * Proxies the platform oEmbed request server-side and returns the normalised
192
+ * Proxies the platform oEmbed request server-side and returns the normalized
189
193
  * payload used by the social-embed block.
190
194
  */
191
- async fetch(o) {
192
- const { url: e, platform: i } = o.query;
195
+ async fetch(t) {
196
+ const { url: e, platform: r } = t.query;
193
197
  if (!e || typeof e != "string")
194
- return o.badRequest('Missing required "url" query parameter.');
195
- const r = i && x.includes(i) ? i : void 0;
198
+ return t.badRequest('Missing required "url" query parameter.');
199
+ const n = r && E.includes(r) ? r : void 0;
196
200
  try {
197
- o.body = await t.plugin("better-blocks").service("oembed").fetch(e, r);
198
- } catch (n) {
199
- const a = n instanceof Error ? n.message : "oEmbed request failed.";
200
- return o.badRequest(a);
201
+ t.body = await o.plugin("better-blocks").service("oembed").fetch(e, n);
202
+ } catch (s) {
203
+ const c = s instanceof Error ? s.message : "oEmbed request failed.";
204
+ return t.badRequest(c);
201
205
  }
202
206
  }
203
207
  }), T = {
204
- controller: v,
208
+ controller: x,
205
209
  oembed: $
206
- }, E = {}, R = {}, L = [
210
+ }, R = {}, _ = {}, L = [
207
211
  {
208
212
  method: "GET",
209
213
  path: "/",
@@ -213,7 +217,7 @@ const g = ({ strapi: t }) => {
213
217
  policies: []
214
218
  }
215
219
  }
216
- ], _ = [
220
+ ], S = [
217
221
  {
218
222
  method: "GET",
219
223
  path: "/config",
@@ -232,20 +236,54 @@ const g = ({ strapi: t }) => {
232
236
  policies: ["admin::isAuthenticatedAdmin"]
233
237
  }
234
238
  }
235
- ], S = {
239
+ ], z = {
236
240
  "content-api": {
237
241
  type: "content-api",
238
242
  routes: L
239
243
  },
240
244
  admin: {
241
245
  type: "admin",
242
- routes: _
246
+ routes: S
243
247
  }
244
- }, U = ({ strapi: t }) => ({
248
+ }, A = ({ strapi: o }) => ({
245
249
  getWelcomeMessage() {
246
250
  return "Welcome to Strapi 🚀";
247
251
  }
248
- }), I = [
252
+ }), U = ({ max: o }) => {
253
+ if (o < 1)
254
+ throw new Error(`ttlCache: max must be at least 1 (received ${o})`);
255
+ const t = /* @__PURE__ */ new Map();
256
+ return {
257
+ get(e) {
258
+ const r = t.get(e);
259
+ if (r) {
260
+ if (r.expires <= Date.now()) {
261
+ t.delete(e);
262
+ return;
263
+ }
264
+ return t.delete(e), t.set(e, r), r.value;
265
+ }
266
+ },
267
+ set(e, r, n) {
268
+ if (t.delete(e), t.size >= o) {
269
+ const s = Date.now();
270
+ for (const [c, i] of t)
271
+ i.expires <= s && t.delete(c);
272
+ }
273
+ for (; t.size >= o; ) {
274
+ const s = t.keys().next();
275
+ if (s.done) break;
276
+ t.delete(s.value);
277
+ }
278
+ t.set(e, { expires: Date.now() + n, value: r });
279
+ },
280
+ delete: (e) => t.delete(e),
281
+ clear: () => t.clear(),
282
+ get size() {
283
+ return t.size;
284
+ }
285
+ };
286
+ }, I = [
249
287
  {
250
288
  platform: "twitter",
251
289
  re: /(?:twitter\.com|x\.com)\/[^/]+\/status(?:es)?\/\d+/i
@@ -255,28 +293,28 @@ const g = ({ strapi: t }) => {
255
293
  { platform: "facebook", re: /facebook\.com\//i },
256
294
  { platform: "linkedin", re: /linkedin\.com\//i },
257
295
  { platform: "pinterest", re: /(?:pinterest\.[a-z.]+|pin\.it)\//i }
258
- ], b = (t) => {
259
- const o = I.find(({ re: e }) => e.test(t));
260
- return o ? o.platform : null;
261
- }, A = (t, o, e) => {
262
- const i = encodeURIComponent(o);
263
- switch (t) {
296
+ ], g = (o) => {
297
+ const t = I.find(({ re: e }) => e.test(o));
298
+ return t ? t.platform : null;
299
+ }, M = (o, t, e) => {
300
+ const r = encodeURIComponent(t);
301
+ switch (o) {
264
302
  case "twitter":
265
- return `https://publish.twitter.com/oembed?url=${i}&omit_script=true&dnt=true`;
303
+ return `https://publish.twitter.com/oembed?url=${r}&omit_script=true&dnt=true`;
266
304
  case "tiktok":
267
- return `https://www.tiktok.com/oembed?url=${i}`;
305
+ return `https://www.tiktok.com/oembed?url=${r}`;
268
306
  case "pinterest":
269
- return `https://www.pinterest.com/oembed.json?url=${i}`;
307
+ return `https://www.pinterest.com/oembed.json?url=${r}`;
270
308
  case "instagram": {
271
- const r = e.instagram?.accessToken;
272
- return r ? `https://graph.facebook.com/v17.0/instagram_oembed?url=${i}&access_token=${encodeURIComponent(
273
- r
309
+ const n = e.instagram?.accessToken;
310
+ return n ? `https://graph.facebook.com/v17.0/instagram_oembed?url=${r}&access_token=${encodeURIComponent(
311
+ n
274
312
  )}&omitscript=true` : null;
275
313
  }
276
314
  case "facebook": {
277
- const r = e.facebook?.accessToken;
278
- return r ? `https://graph.facebook.com/v17.0/oembed_post?url=${i}&access_token=${encodeURIComponent(
279
- r
315
+ const n = e.facebook?.accessToken;
316
+ return n ? `https://graph.facebook.com/v17.0/oembed_post?url=${r}&access_token=${encodeURIComponent(
317
+ n
280
318
  )}&omitscript=true` : null;
281
319
  }
282
320
  case "linkedin":
@@ -284,32 +322,32 @@ const g = ({ strapi: t }) => {
284
322
  default:
285
323
  return null;
286
324
  }
287
- }, P = async (t) => {
288
- if (!/^https?:\/\/pin\.it\//i.test(t)) return t;
325
+ }, P = async (o) => {
326
+ if (!/^https?:\/\/pin\.it\//i.test(o)) return o;
289
327
  try {
290
- const e = (await fetch(t, { redirect: "follow" })).url || t;
291
- return /pinterest\.[a-z.]+\/pin\//i.test(e) ? e : t;
328
+ const e = (await fetch(o, { redirect: "follow" })).url || o;
329
+ return /pinterest\.[a-z.]+\/pin\//i.test(e) ? e : o;
292
330
  } catch {
293
- return t;
331
+ return o;
294
332
  }
295
- }, q = (t) => {
296
- const o = t.match(
333
+ }, q = (o) => {
334
+ const t = o.match(
297
335
  /(?:urn:li:)?(ugcPost|activity|share)[:-](\d{10,})/i
298
336
  );
299
- if (!o) return null;
300
- const i = `urn:li:${o[1].toLowerCase() === "ugcpost" ? "ugcPost" : o[1].toLowerCase()}:${o[2]}`, r = `https://www.linkedin.com/embed/feed/update/${encodeURIComponent(i)}`;
337
+ if (!t) return null;
338
+ const r = `urn:li:${t[1].toLowerCase() === "ugcpost" ? "ugcPost" : t[1].toLowerCase()}:${t[2]}`, n = `https://www.linkedin.com/embed/feed/update/${encodeURIComponent(r)}`;
301
339
  return {
302
340
  platform: "linkedin",
303
- url: t,
304
- html: `<iframe src="${r}" height="600" width="100%" frameborder="0" allowfullscreen title="Embedded LinkedIn post" loading="lazy"></iframe>`,
341
+ url: o,
342
+ html: `<iframe src="${n}" height="600" width="100%" frameborder="0" allowfullscreen title="Embedded LinkedIn post" loading="lazy"></iframe>`,
305
343
  providerName: "LinkedIn",
306
344
  width: 504,
307
345
  height: 600
308
346
  };
309
- }, z = (t) => t.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").trim(), M = (t, o, e) => ({
310
- platform: t,
311
- url: o,
312
- html: typeof e.html == "string" && z(e.html) || null,
347
+ }, D = (o) => o.replace(/<script\b[^>]*>[\s\S]*?<\/script>/gi, "").trim(), W = (o, t, e) => ({
348
+ platform: o,
349
+ url: t,
350
+ html: typeof e.html == "string" && D(e.html) || null,
313
351
  title: typeof e.title == "string" ? e.title : void 0,
314
352
  author: typeof e.author_name == "string" ? e.author_name : void 0,
315
353
  authorUrl: typeof e.author_url == "string" ? e.author_url : void 0,
@@ -317,77 +355,83 @@ const g = ({ strapi: t }) => {
317
355
  providerName: typeof e.provider_name == "string" ? e.provider_name : void 0,
318
356
  width: typeof e.width == "number" ? e.width : void 0,
319
357
  height: typeof e.height == "number" ? e.height : void 0
320
- }), h = /* @__PURE__ */ new Map(), W = ({ strapi: t }) => {
321
- const o = () => t.plugin("better-blocks").config("social", {});
358
+ }), j = 500, F = ({ strapi: o }) => {
359
+ const t = () => o.plugin("better-blocks").config("social", {});
360
+ let e = null;
361
+ const r = () => (e || (e = U({
362
+ max: t().cacheMaxEntries ?? j
363
+ })), e);
322
364
  return {
323
- detectPlatform: b,
365
+ detectPlatform: g,
366
+ /** Exposed for tests and for anyone who needs to drop cached embeds. */
367
+ clearCache: () => e?.clear(),
324
368
  /**
325
- * Fetch (and cache) the normalised oEmbed for `url`. `platform` may be
369
+ * Fetch (and cache) the normalized oEmbed for `url`. `platform` may be
326
370
  * passed to skip auto-detection. Throws on an unsupported/unconfigured
327
371
  * platform or a failed upstream request.
328
372
  */
329
- async fetch(e, i) {
330
- const r = o();
331
- if (r.enabled === !1)
373
+ async fetch(n, s) {
374
+ const c = t();
375
+ if (c.enabled === !1)
332
376
  throw new Error("Social embeds are disabled in plugin config.");
333
- const n = i ?? b(e);
334
- if (!n)
335
- throw new Error(`Unsupported or unrecognised social URL: ${e}`);
336
- const a = r.platforms;
337
- if (Array.isArray(a) && !a.includes(n))
377
+ const i = s ?? g(n);
378
+ if (!i)
379
+ throw new Error(`Unsupported or unrecognized social URL: ${n}`);
380
+ const u = c.platforms;
381
+ if (Array.isArray(u) && !u.includes(i))
338
382
  throw new Error(
339
- `Platform "${n}" is not enabled in plugin config.`
383
+ `Platform "${i}" is not enabled in plugin config.`
340
384
  );
341
- const f = r.cache !== !1, u = (r.cacheTTL ?? 86400) * 1e3, p = `${n}:${e}`;
342
- if (f) {
343
- const c = h.get(p);
344
- if (c && c.expires > Date.now()) return c.data;
385
+ const p = c.cache !== !1, m = (c.cacheTTL ?? 86400) * 1e3, b = `${i}:${n}`;
386
+ if (p) {
387
+ const l = r().get(b);
388
+ if (l) return l;
345
389
  }
346
- let s = null;
347
- if (n === "linkedin") {
348
- if (s = q(e), !s)
390
+ let a = null;
391
+ if (i === "linkedin") {
392
+ if (a = q(n), !a)
349
393
  throw new Error(
350
394
  "Could not derive a LinkedIn embed from this URL. Paste the post embed code instead."
351
395
  );
352
396
  } else {
353
- const c = await P(e), m = A(n, c, r);
354
- if (!m)
397
+ const l = await P(n), h = M(i, l, c);
398
+ if (!h)
355
399
  throw new Error(
356
- `oEmbed for "${n}" requires an access token. Configure config.social.${n}.accessToken, or paste the embed code instead.`
400
+ `oEmbed for "${i}" requires an access token. Configure config.social.${i}.accessToken, or paste the embed code instead.`
357
401
  );
358
- const l = await fetch(m, {
402
+ const d = await fetch(h, {
359
403
  headers: { Accept: "application/json" }
360
404
  });
361
- if (!l.ok)
405
+ if (!d.ok)
362
406
  throw new Error(
363
- `oEmbed request to ${n} failed (HTTP ${l.status}).`
407
+ `oEmbed request to ${i} failed (HTTP ${d.status}).`
364
408
  );
365
- const d = await l.json();
366
- if (typeof d.error == "string")
367
- throw new Error(`${n} could not embed this URL: ${d.error}`);
368
- if (s = M(n, c, d), !s.html)
409
+ const f = await d.json();
410
+ if (typeof f.error == "string")
411
+ throw new Error(`${i} could not embed this URL: ${f.error}`);
412
+ if (a = W(i, l, f), !a.html)
369
413
  throw new Error(
370
- `${n} returned no embed markup for this URL. Check the post is public, or paste the embed code instead.`
414
+ `${i} returned no embed markup for this URL. Check the post is public, or paste the embed code instead.`
371
415
  );
372
416
  }
373
- return f && u > 0 && h.set(p, { expires: Date.now() + u, data: s }), s;
417
+ return p && m > 0 && r().set(b, a, m), a;
374
418
  }
375
419
  };
376
- }, j = {
377
- service: U,
378
- oembed: W
379
- }, D = {
420
+ }, N = {
421
+ service: A,
422
+ oembed: F
423
+ }, G = {
380
424
  register: y,
381
- bootstrap: g,
382
- destroy: k,
383
- config: w,
425
+ bootstrap: k,
426
+ destroy: w,
427
+ config: C,
384
428
  controllers: T,
385
- routes: S,
386
- services: j,
387
- contentTypes: C,
388
- policies: R,
389
- middlewares: E
429
+ routes: z,
430
+ services: N,
431
+ contentTypes: v,
432
+ policies: _,
433
+ middlewares: R
390
434
  };
391
435
  export {
392
- D as default
436
+ G as default
393
437
  };
@@ -77,6 +77,7 @@ declare const _default: {
77
77
  platforms: string[];
78
78
  cache: boolean;
79
79
  cacheTTL: number;
80
+ cacheMaxEntries: number;
80
81
  instagram: {
81
82
  accessToken: any;
82
83
  };
@@ -4,7 +4,7 @@ declare const oembed: ({ strapi }: {
4
4
  }) => {
5
5
  /**
6
6
  * GET /better-blocks/oembed?url=...&platform=...
7
- * Proxies the platform oEmbed request server-side and returns the normalised
7
+ * Proxies the platform oEmbed request server-side and returns the normalized
8
8
  * payload used by the social-embed block.
9
9
  */
10
10
  fetch(ctx: any): Promise<any>;
@@ -51,6 +51,7 @@ declare const _default: {
51
51
  platforms: string[];
52
52
  cache: boolean;
53
53
  cacheTTL: number;
54
+ cacheMaxEntries: number;
54
55
  instagram: {
55
56
  accessToken: any;
56
57
  };
@@ -115,6 +116,7 @@ declare const _default: {
115
116
  strapi: import("@strapi/types/dist/core").Strapi;
116
117
  }) => {
117
118
  detectPlatform: (url: string) => import("./services/oembed").SocialPlatform;
119
+ clearCache: () => void;
118
120
  fetch(url: string, platform?: import("./services/oembed").SocialPlatform): Promise<import("./services/oembed").NormalisedOEmbed>;
119
121
  };
120
122
  };
@@ -8,6 +8,7 @@ declare const _default: {
8
8
  strapi: import("@strapi/types/dist/core").Strapi;
9
9
  }) => {
10
10
  detectPlatform: (url: string) => import("./oembed").SocialPlatform;
11
+ clearCache: () => void;
11
12
  fetch(url: string, platform?: import("./oembed").SocialPlatform): Promise<import("./oembed").NormalisedOEmbed>;
12
13
  };
13
14
  };
@@ -12,13 +12,56 @@ export interface NormalisedOEmbed {
12
12
  width?: number;
13
13
  height?: number;
14
14
  }
15
+ export interface SocialConfig {
16
+ enabled?: boolean;
17
+ platforms?: SocialPlatform[];
18
+ cache?: boolean;
19
+ cacheTTL?: number;
20
+ cacheMaxEntries?: number;
21
+ instagram?: {
22
+ accessToken?: string;
23
+ };
24
+ facebook?: {
25
+ accessToken?: string;
26
+ };
27
+ }
15
28
  export declare const detectPlatform: (url: string) => SocialPlatform | null;
29
+ /**
30
+ * Returns the platform oEmbed endpoint to call, or null when the platform has
31
+ * no public oEmbed API (e.g. LinkedIn, which we render via a constructed iframe).
32
+ */
33
+ export declare const buildEndpoint: (platform: SocialPlatform, url: string, config: SocialConfig) => string | null;
34
+ /**
35
+ * `pin.it` short links are 30x redirects that Pinterest's oEmbed endpoint
36
+ * rejects outright, so expand them to the canonical `/pin/<id>/` URL first.
37
+ * Returns the original URL when it isn't a short link or can't be resolved.
38
+ */
39
+ export declare const resolveShortUrl: (url: string) => Promise<string>;
40
+ /** LinkedIn has no oEmbed API but exposes a stable iframe embed by URN. */
41
+ export declare const buildLinkedInIframe: (url: string) => NormalisedOEmbed | null;
42
+ /**
43
+ * Platform oEmbed markup often ships the widget script inline (TikTok always
44
+ * does; Twitter/Instagram only when `omit_script` isn't honoured). Renderers
45
+ * inject that script themselves, and a `<script>` added through `innerHTML`
46
+ * never executes anyway — worse, its inert tag makes a renderer believe the
47
+ * widget is already loaded. Strip it so the stored html is markup only.
48
+ */
49
+ export declare const stripScripts: (html: string) => string;
50
+ export declare const normalize: (platform: SocialPlatform, url: string, raw: Record<string, unknown>) => NormalisedOEmbed;
51
+ /**
52
+ * Bounded so a long-running instance cannot accumulate one entry per social post
53
+ * ever embedded. 500 posts is far more than an editor touches in a TTL window,
54
+ * and `cacheMaxEntries` raises it for anyone who disagrees.
55
+ */
56
+ export declare const DEFAULT_CACHE_MAX_ENTRIES = 500;
16
57
  declare const service: ({ strapi }: {
17
58
  strapi: Core.Strapi;
18
59
  }) => {
19
60
  detectPlatform: (url: string) => SocialPlatform | null;
61
+ /** Exposed for tests and for anyone who needs to drop cached embeds. */
62
+ clearCache: () => void;
20
63
  /**
21
- * Fetch (and cache) the normalised oEmbed for `url`. `platform` may be
64
+ * Fetch (and cache) the normalized oEmbed for `url`. `platform` may be
22
65
  * passed to skip auto-detection. Throws on an unsupported/unconfigured
23
66
  * platform or a failed upstream request.
24
67
  */
@@ -0,0 +1,23 @@
1
+ /**
2
+ * A bounded cache with per-entry expiry.
3
+ *
4
+ * The oEmbed service used a bare `Map` keyed by URL, which meant one entry for
5
+ * every social post ever embedded, for the life of the process. Entries expired
6
+ * logically — a stale hit was discarded on read — but nothing ever removed them,
7
+ * so a long-running instance kept the memory whether or not anyone asked for
8
+ * that post again.
9
+ *
10
+ * Eviction is least-recently-used: a hit moves its entry to the end, and a write
11
+ * past `max` drops the entry at the front. `Map` iterates in insertion order and
12
+ * re-inserting moves a key to the end, which is the whole implementation.
13
+ */
14
+ export interface TtlCache<T> {
15
+ get(key: string): T | undefined;
16
+ set(key: string, value: T, ttlMs: number): void;
17
+ delete(key: string): boolean;
18
+ clear(): void;
19
+ readonly size: number;
20
+ }
21
+ export declare const createTtlCache: <T>({ max }: {
22
+ max: number;
23
+ }) => TtlCache<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qkix/strapi-plugin-better-blocks",
3
- "version": "0.23.0",
3
+ "version": "0.23.2",
4
4
  "description": "An enhanced Rich Text (Blocks) field for Strapi with inline color picker and more",
5
5
  "strapi": {
6
6
  "kind": "plugin",
@@ -54,7 +54,8 @@
54
54
  "remark-math": "^6.0.0",
55
55
  "remark-parse": "^11.0.0",
56
56
  "shiki": "^4.3.0",
57
- "unified": "^11.0.5"
57
+ "unified": "^11.0.5",
58
+ "@qkix/better-blocks-core": "^0.2.2"
58
59
  },
59
60
  "devDependencies": {
60
61
  "@strapi/sdk-plugin": "^5.3.2",
@@ -73,6 +74,7 @@
73
74
  "typescript": "^5.8.3",
74
75
  "vitest": "^3.2.4",
75
76
  "@qkix/eslint-config": "0.0.0",
77
+ "@qkix/strapi-test-harness": "0.0.0",
76
78
  "@qkix/tsconfig": "0.0.0"
77
79
  },
78
80
  "peerDependencies": {
@@ -99,17 +101,25 @@
99
101
  "author": "qkix",
100
102
  "repository": {
101
103
  "type": "git",
102
- "url": "git+https://github.com/qkix/strapi-plugin-better-blocks.git",
104
+ "url": "git+https://github.com/qkix/strapi-plugins.git",
103
105
  "directory": "packages/strapi-plugin-better-blocks"
104
106
  },
105
107
  "bugs": {
106
- "url": "https://github.com/qkix/strapi-plugin-better-blocks/issues"
108
+ "url": "https://github.com/qkix/strapi-plugins/issues"
107
109
  },
108
- "homepage": "https://github.com/qkix/strapi-plugin-better-blocks#readme",
110
+ "homepage": "https://github.com/qkix/strapi-plugins#readme",
109
111
  "nx": {
110
112
  "tags": [
111
113
  "scope:plugin"
112
- ]
114
+ ],
115
+ "targets": {
116
+ "test": {
117
+ "dependsOn": [
118
+ "build",
119
+ "^build"
120
+ ]
121
+ }
122
+ }
113
123
  },
114
124
  "publishConfig": {
115
125
  "access": "public"
@@ -126,6 +136,6 @@
126
136
  "verify": "strapi-plugin verify",
127
137
  "watch": "strapi-plugin watch",
128
138
  "watch:link": "strapi-plugin watch:link",
129
- "typecheck": "tsc -p admin/tsconfig.json && tsc -p server/tsconfig.json"
139
+ "typecheck": "tsc -p admin/tsconfig.json --noEmit && tsc -p server/tsconfig.json --noEmit"
130
140
  }
131
141
  }