scribe-cms 0.0.8 → 0.0.9

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.
Files changed (50) hide show
  1. package/README.md +5 -5
  2. package/dist/cli/index.cjs +432 -384
  3. package/dist/cli/index.cjs.map +1 -1
  4. package/dist/cli/index.js +432 -384
  5. package/dist/cli/index.js.map +1 -1
  6. package/dist/index.cjs +642 -512
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.js +642 -513
  9. package/dist/index.js.map +1 -1
  10. package/dist/runtime.cjs +287 -261
  11. package/dist/runtime.cjs.map +1 -1
  12. package/dist/runtime.js +286 -261
  13. package/dist/runtime.js.map +1 -1
  14. package/dist/src/config/resolve-config.d.ts.map +1 -1
  15. package/dist/src/core/builtin-fields.d.ts +8 -8
  16. package/dist/src/core/builtin-fields.d.ts.map +1 -1
  17. package/dist/src/core/types.d.ts +11 -4
  18. package/dist/src/core/types.d.ts.map +1 -1
  19. package/dist/src/create-project.d.ts.map +1 -1
  20. package/dist/src/export/build-static-raw-exports.d.ts.map +1 -1
  21. package/dist/src/i18n/build-url.d.ts +16 -3
  22. package/dist/src/i18n/build-url.d.ts.map +1 -1
  23. package/dist/src/i18n/resolve-document.d.ts +3 -2
  24. package/dist/src/i18n/resolve-document.d.ts.map +1 -1
  25. package/dist/src/index.d.ts +3 -2
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/loader/create-loader.d.ts.map +1 -1
  28. package/dist/src/redirects/build-json-redirects.d.ts +6 -0
  29. package/dist/src/redirects/build-json-redirects.d.ts.map +1 -0
  30. package/dist/src/redirects/build-redirects.d.ts +9 -7
  31. package/dist/src/redirects/build-redirects.d.ts.map +1 -1
  32. package/dist/src/redirects/load-type-redirects.d.ts +14 -0
  33. package/dist/src/redirects/load-type-redirects.d.ts.map +1 -0
  34. package/dist/src/redirects/redirect-schema.d.ts +31 -0
  35. package/dist/src/redirects/redirect-schema.d.ts.map +1 -0
  36. package/dist/src/runtime.d.ts +3 -1
  37. package/dist/src/runtime.d.ts.map +1 -1
  38. package/dist/src/sitemap/generate-sitemap.d.ts.map +1 -1
  39. package/dist/src/validate/validate-project.d.ts.map +1 -1
  40. package/dist/src/validate/validate-redirects.d.ts +4 -0
  41. package/dist/src/validate/validate-redirects.d.ts.map +1 -0
  42. package/dist/studio/server.cjs +90 -61
  43. package/dist/studio/server.cjs.map +1 -1
  44. package/dist/studio/server.js +90 -61
  45. package/dist/studio/server.js.map +1 -1
  46. package/package.json +1 -1
  47. package/dist/src/core/slug-aliases.d.ts +0 -27
  48. package/dist/src/core/slug-aliases.d.ts.map +0 -1
  49. package/dist/src/redirects/translation-index.d.ts +0 -4
  50. package/dist/src/redirects/translation-index.d.ts.map +0 -1
@@ -186,65 +186,93 @@ function pathSuffix(template) {
186
186
  if (idx === -1) throw new Error(`Invalid path template (missing {slug}): ${template}`);
187
187
  return template.slice(idx + SLUG_PLACEHOLDER.length);
188
188
  }
189
- function resolvePath(template, slug, locale, defaultLocale) {
190
- const relative = template.replace(SLUG_PLACEHOLDER, slug);
191
- if (locale === defaultLocale) return relative;
192
- return `/${locale}${relative}`;
189
+ function resolveDefaultLocaleRouting() {
190
+ return { strategy: "path-prefix", prefixDefaultLocale: false };
193
191
  }
194
- function extractSlugFromResolvedPath(template, resolvedPath) {
195
- const prefix = pathPrefix(template);
196
- const suffix = pathSuffix(template);
197
- if (!resolvedPath.startsWith(prefix)) return null;
198
- if (suffix && !resolvedPath.endsWith(suffix)) return null;
199
- const slugEnd = suffix ? resolvedPath.length - suffix.length : resolvedPath.length;
200
- const slug = resolvedPath.slice(prefix.length, slugEnd);
201
- return slug.length > 0 ? slug : null;
192
+ function splitPathAndSearch(pathname) {
193
+ const q = pathname.indexOf("?");
194
+ if (q === -1) return { pathname, search: "" };
195
+ return { pathname: pathname.slice(0, q), search: pathname.slice(q) };
196
+ }
197
+ function createUrlBuilder(config) {
198
+ const localeRouting = config.localeRouting ?? resolveDefaultLocaleRouting();
199
+ const defaultLocale = config.defaultLocale;
200
+ const prefixedLocales = localeRouting.strategy === "path-prefix" ? localeRouting.prefixDefaultLocale ? [...config.locales] : config.locales.filter((locale) => locale !== defaultLocale) : config.locales.filter((locale) => locale !== defaultLocale);
201
+ function applyLocaleToPath(pathname, locale) {
202
+ if (locale === defaultLocale) {
203
+ if (localeRouting.strategy === "path-prefix" && localeRouting.prefixDefaultLocale) {
204
+ return `/${defaultLocale}${pathname}`;
205
+ }
206
+ return pathname;
207
+ }
208
+ if (localeRouting.strategy === "search-param") {
209
+ const { pathname: base, search } = splitPathAndSearch(pathname);
210
+ const params = new URLSearchParams(search.startsWith("?") ? search.slice(1) : search);
211
+ params.set(localeRouting.param, locale);
212
+ const qs = params.toString();
213
+ return qs ? `${base}?${qs}` : base;
214
+ }
215
+ return `/${locale}${pathname}`;
216
+ }
217
+ function resolvePath(template, slug, locale) {
218
+ const relative = template.replace(SLUG_PLACEHOLDER, slug);
219
+ return applyLocaleToPath(relative, locale);
220
+ }
221
+ function extractSlugFromResolvedPath(template, resolvedPath) {
222
+ let pathname = resolvedPath;
223
+ if (localeRouting.strategy === "search-param") {
224
+ pathname = splitPathAndSearch(resolvedPath).pathname;
225
+ } else if (localeRouting.strategy === "path-prefix") {
226
+ for (const locale of config.locales) {
227
+ if (locale === defaultLocale && !localeRouting.prefixDefaultLocale) continue;
228
+ const prefix2 = `/${locale}`;
229
+ if (pathname === prefix2 || pathname.startsWith(`${prefix2}/`)) {
230
+ pathname = pathname.slice(prefix2.length) || "/";
231
+ break;
232
+ }
233
+ }
234
+ }
235
+ const prefix = pathPrefix(template);
236
+ const suffix = pathSuffix(template);
237
+ if (!pathname.startsWith(prefix)) return null;
238
+ if (suffix && !pathname.endsWith(suffix)) return null;
239
+ const slugEnd = suffix ? pathname.length - suffix.length : pathname.length;
240
+ const slug = pathname.slice(prefix.length, slugEnd);
241
+ return slug.length > 0 ? slug : null;
242
+ }
243
+ return {
244
+ defaultLocale,
245
+ locales: config.locales,
246
+ localeRouting,
247
+ prefixedLocales,
248
+ resolvePath,
249
+ extractSlugFromResolvedPath,
250
+ applyLocaleToPath
251
+ };
202
252
  }
203
253
 
204
254
  // src/core/builtin-fields.ts
205
255
  var SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
206
- var slugPatternSchema = z.string().regex(SLUG_PATTERN, "slug must be lowercase-kebab-case");
256
+ z.string().regex(SLUG_PATTERN, "slug must be lowercase-kebab-case");
207
257
  var isoDateSchema = z.string().regex(/^\d{4}-\d{2}-\d{2}(T[\d:.Z+-]*)?$/, "Use ISO date YYYY-MM-DD or full ISO 8601");
208
258
  var canonicalPathSchema = z.string().regex(/^\//, "canonicalPath must start with /");
209
- function extractBuiltinEnFields(data, pathTemplate, enSlug, defaultLocale) {
259
+ var DEPRECATED_REDIRECT_FIELDS = [
260
+ [
261
+ "aliases",
262
+ "aliases frontmatter is removed \u2014 add redirects to content/<type>/_redirects.json instead"
263
+ ],
264
+ [
265
+ "redirect_to",
266
+ "redirect_to frontmatter is removed \u2014 add redirects to content/<type>/_redirects.json instead"
267
+ ]
268
+ ];
269
+ function extractBuiltinEnFields(data, _pathTemplate, _enSlug, _defaultLocale) {
210
270
  const issues = [];
211
271
  const rest = { ...data };
212
- const aliasesResult = z.array(slugPatternSchema).max(20).optional().default([]).safeParse(rest.aliases ?? []);
213
- delete rest.aliases;
214
- const redirectRaw = rest.redirect_to;
215
- delete rest.redirect_to;
216
- let redirectTo;
217
- if (redirectRaw !== void 0 && redirectRaw !== null && redirectRaw !== "") {
218
- if (typeof redirectRaw !== "string") {
219
- issues.push({
220
- field: "redirect_to",
221
- message: "redirect_to must be a string path",
222
- level: "error"
223
- });
224
- } else if (!pathTemplate) {
225
- issues.push({
226
- field: "redirect_to",
227
- message: "redirect_to is not allowed on reference-only content types",
228
- level: "error"
229
- });
230
- } else if (!extractSlugFromResolvedPath(pathTemplate, redirectRaw)) {
231
- issues.push({
232
- field: "redirect_to",
233
- message: `redirect_to must match path template "${pathTemplate}" (prefix "${pathPrefix(pathTemplate)}"${pathSuffix(pathTemplate) ? `, suffix "${pathSuffix(pathTemplate)}"` : ""})`,
234
- level: "error"
235
- });
236
- } else {
237
- redirectTo = redirectRaw;
238
- }
239
- }
240
- const aliases = aliasesResult.success ? aliasesResult.data : [];
241
- if (!aliasesResult.success) {
242
- for (const issue of aliasesResult.error.issues) {
243
- issues.push({
244
- field: `aliases.${issue.path.join(".")}`,
245
- message: issue.message,
246
- level: "error"
247
- });
272
+ for (const [field, message] of DEPRECATED_REDIRECT_FIELDS) {
273
+ if (rest[field] !== void 0) {
274
+ issues.push({ field, message, level: "error" });
275
+ delete rest[field];
248
276
  }
249
277
  }
250
278
  let publishedAt;
@@ -306,8 +334,6 @@ function extractBuiltinEnFields(data, pathTemplate, enSlug, defaultLocale) {
306
334
  }
307
335
  return {
308
336
  builtin: {
309
- aliases,
310
- redirectTo,
311
337
  publishedAt,
312
338
  updatedAt,
313
339
  noindex,
@@ -317,20 +343,24 @@ function extractBuiltinEnFields(data, pathTemplate, enSlug, defaultLocale) {
317
343
  issues
318
344
  };
319
345
  }
320
- function resolveCanonicalPathname(type, doc, defaultLocale) {
346
+ function resolveCanonicalPathname(type, doc, defaultLocale, localeRouting) {
347
+ const urlBuilder = createUrlBuilder({
348
+ locales: [defaultLocale, ...doc.locale !== defaultLocale ? [doc.locale] : []],
349
+ defaultLocale,
350
+ localeRouting: localeRouting ?? { strategy: "path-prefix", prefixDefaultLocale: false }
351
+ });
321
352
  if (doc.canonicalPathOverride) {
322
- if (doc.locale === defaultLocale) return doc.canonicalPathOverride;
323
- return `/${doc.locale}${doc.canonicalPathOverride}`;
353
+ return urlBuilder.applyLocaleToPath(doc.canonicalPathOverride, doc.locale);
324
354
  }
325
355
  if (!type.path) return `/${doc.slug}`;
326
- return resolvePath(type.path, doc.slug, doc.locale, defaultLocale);
356
+ return urlBuilder.resolvePath(type.path, doc.slug, doc.locale);
327
357
  }
328
- function mergeBuiltinsIntoFrontmatter(frontmatter, doc, type, defaultLocale) {
358
+ function mergeBuiltinsIntoFrontmatter(frontmatter, doc, type, defaultLocale, localeRouting) {
329
359
  const out = { ...frontmatter };
330
360
  if (doc.publishedAt !== void 0) out.publishedAt = doc.publishedAt;
331
361
  if (doc.updatedAt !== void 0) out.updatedAt = doc.updatedAt;
332
362
  out.noindex = doc.noindex;
333
- out.canonicalPath = resolveCanonicalPathname(type, doc, defaultLocale);
363
+ out.canonicalPath = resolveCanonicalPathname(type, doc, defaultLocale, localeRouting);
334
364
  return out;
335
365
  }
336
366
  var SCHEMA_VERSION = 4;
@@ -503,7 +533,8 @@ function parseEnMdx(filePath, config, type) {
503
533
  locale: config.defaultLocale
504
534
  },
505
535
  type,
506
- config.defaultLocale
536
+ config.defaultLocale,
537
+ config.localeRouting
507
538
  );
508
539
  const crossIssues = type.crossValidate?.(result.data, {
509
540
  locale: config.defaultLocale,
@@ -517,8 +548,6 @@ function parseEnMdx(filePath, config, type) {
517
548
  slug,
518
549
  enSlug: slug,
519
550
  locale: config.defaultLocale,
520
- aliases: builtin.aliases,
521
- redirectTo: builtin.redirectTo,
522
551
  publishedAt: builtin.publishedAt,
523
552
  updatedAt: builtin.updatedAt,
524
553
  noindex: builtin.noindex,