@lupinum/ginko 0.4.0 → 0.6.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/CHANGELOG.md +161 -0
- package/README.md +2 -2
- package/dist/basics.d.mts +492 -0
- package/dist/basics.d.ts +492 -0
- package/dist/basics.mjs +416 -0
- package/dist/cli/index.mjs +59 -56
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.mjs +3 -3
- package/dist/internal/search.mjs +6 -3
- package/dist/internal.d.mts +387 -41
- package/dist/internal.d.ts +387 -41
- package/dist/internal.mjs +267 -101
- package/dist/nuxt/module.d.mts +2 -2
- package/dist/nuxt/module.d.ts +2 -2
- package/dist/nuxt/module.mjs +108 -15
- package/dist/nuxt/runtime/GinkoImage.d.mts +1 -1
- package/dist/nuxt/runtime/GinkoMarkdown.mjs +2 -2
- package/dist/nuxt/runtime/composables.d.mts +4 -3
- package/dist/nuxt/runtime/composables.mjs +19 -15
- package/dist/nuxt/runtime/fetch.d.mts +3 -1
- package/dist/nuxt/runtime/fetch.mjs +12 -6
- package/dist/nuxt/runtime/markdown.d.mts +3 -0
- package/dist/nuxt/runtime/markdown.mjs +15 -3
- package/dist/nuxt/runtime/plugin.mjs +6 -35
- package/dist/nuxt/runtime/preview-listener.d.mts +13 -0
- package/dist/nuxt/runtime/preview-listener.mjs +37 -0
- package/dist/nuxt/runtime/preview.vue +3 -1
- package/dist/nuxt/runtime/schema.d.ts +6 -0
- package/dist/nuxt/runtime/server/bundles.d.ts +4 -0
- package/dist/nuxt/runtime/server/files-md.d.ts +2 -0
- package/dist/nuxt/runtime/server/files.d.ts +2 -0
- package/dist/nuxt/runtime/server/headers.d.ts +2 -0
- package/dist/nuxt/runtime/server/items.d.mts +12 -0
- package/dist/nuxt/runtime/server/items.d.ts +12 -0
- package/dist/nuxt/runtime/server/items.mjs +17 -0
- package/dist/nuxt/runtime/server/llms-full.d.ts +2 -0
- package/dist/nuxt/runtime/server/llms.d.ts +2 -0
- package/dist/nuxt/runtime/server/markdown-trees.d.ts +2 -0
- package/dist/nuxt/runtime/server/markdown.d.ts +2 -0
- package/dist/nuxt/runtime/server/markdown.mjs +1 -1
- package/dist/nuxt/runtime/server/platform-md.d.mts +3 -0
- package/dist/nuxt/runtime/server/platform-md.d.ts +3 -0
- package/dist/nuxt/runtime/server/platform-md.mjs +71 -0
- package/dist/nuxt/runtime/server/redirects.d.ts +2 -0
- package/dist/nuxt/runtime/server/robots.d.ts +2 -0
- package/dist/nuxt/runtime/server/search.d.ts +2 -0
- package/dist/nuxt/runtime/server/site-config.d.ts +14 -0
- package/dist/nuxt/runtime/server/sitemap-source.d.ts +2 -0
- package/dist/nuxt/runtime/server/sitemap-xml.d.ts +24 -0
- package/dist/nuxt/runtime/server/sitemap.d.ts +2 -0
- package/dist/nuxt/runtime/shared.d.mts +10 -3
- package/dist/nuxt/runtime/shared.mjs +2 -2
- package/dist/shared/{ginko.BBkZW2nW.d.ts → ginko.6cI8KXZP.d.mts} +2 -2
- package/dist/shared/{ginko.CrNRBkS5.mjs → ginko.B1-DoOea.mjs} +253 -309
- package/dist/shared/ginko.BNVC-g9n.mjs +10389 -0
- package/dist/shared/{ginko.Bwan3BB-.d.mts → ginko.B_pACpVU.d.ts} +2 -2
- package/dist/shared/ginko.BaFweQcB.d.mts +91 -0
- package/dist/shared/ginko.BiDXgjLy.d.ts +91 -0
- package/dist/shared/ginko.C-oz8wwI.mjs +95 -0
- package/dist/shared/{ginko.BDF3bW0C.d.ts → ginko.CLzVaDi4.d.ts} +56 -5
- package/dist/shared/ginko.C_nq6vk7.d.mts +1622 -0
- package/dist/shared/ginko.CjWB8N7f.d.ts +128 -0
- package/dist/shared/ginko.D1PeQ3ds.mjs +36 -0
- package/dist/shared/ginko.D2oht0Kx.d.ts +1622 -0
- package/dist/shared/ginko.DCHGkQ7P.mjs +116 -0
- package/dist/shared/ginko.DIl7l-75.mjs +26 -0
- package/dist/shared/{ginko.akHewCGt.d.mts → ginko.DkIc0ZZk.d.mts} +56 -5
- package/dist/shared/{ginko.D-fNojn9.d.mts → ginko.DrkUeYFP.d.mts} +824 -10
- package/dist/shared/{ginko.D-fNojn9.d.ts → ginko.DrkUeYFP.d.ts} +824 -10
- package/dist/shared/ginko.WAfoMhv1.mjs +601 -0
- package/dist/shared/ginko.o1yIKlgj.d.mts +128 -0
- package/dist/shared/{ginko.Dht0xxE5.mjs → ginko.raCzOPYC.mjs} +1 -2
- package/dist/shared/{ginko.C90fPpMP.mjs → ginko.t-gbwzxh.mjs} +128 -101
- package/dist/shared/ginko.yKMIjJ68.mjs +84 -0
- package/dist/vue.d.mts +5 -4
- package/dist/vue.d.ts +5 -4
- package/dist/vue.mjs +1 -1
- package/i18n/ar.json +212 -0
- package/i18n/de.json +9 -1
- package/i18n/en.json +9 -1
- package/i18n/es.json +12 -4
- package/i18n/fr.json +212 -0
- package/i18n/it.json +212 -0
- package/package.json +7 -3
- package/dist/shared/ginko.BTUfZCfa.d.mts +0 -172
- package/dist/shared/ginko.C-QjDn3O.mjs +0 -4944
- package/dist/shared/ginko.CQvxhyez.mjs +0 -40
- package/dist/shared/ginko.Dc0oNcDv.d.ts +0 -172
- package/dist/shared/ginko.Mn30dmlm.mjs +0 -20
package/dist/basics.mjs
ADDED
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { f, O as studioLabel } from './shared/ginko.BNVC-g9n.mjs';
|
|
2
|
+
import { s as single, c as collection } from './shared/ginko.C-oz8wwI.mjs';
|
|
3
|
+
export { h as withoutFiles } from './shared/ginko.C-oz8wwI.mjs';
|
|
4
|
+
import { o as organizationLd, c as compact, a as articleLd, p as postalAddressLd, d as faqLd } from './shared/ginko.DCHGkQ7P.mjs';
|
|
5
|
+
import { s as sourceOf, w as weekFor, a as addDays, h as hoursOn, b as weekdayOf, c as spansOf, d as weekdayKeys, e as both } from './shared/ginko.WAfoMhv1.mjs';
|
|
6
|
+
export { f as austrianHolidays, g as calendarIn, i as calendarOf, j as closedOnClosure, k as eventJsonLd, l as events, m as germanHolidays, n as holidayOn, o as hourPlans, p as hours, q as hoursLine, r as notices, t as noticesOn, u as offerJsonLd, v as planOn, x as sessions, y as sessionsInOrder, z as singaporeHolidays, A as spansText, B as timesOf, C as todayState, D as upcomingClosures, E as upcomingHolidayClosures, F as upcomingPlans, G as weekOf } from './shared/ginko.WAfoMhv1.mjs';
|
|
7
|
+
import { l as limitOf } from './shared/ginko.D1PeQ3ds.mjs';
|
|
8
|
+
export { c as calendarOfCountry } from './shared/ginko.D1PeQ3ds.mjs';
|
|
9
|
+
import 'comark';
|
|
10
|
+
import 'comark/plugins/alert';
|
|
11
|
+
import 'comark/plugins/attributes';
|
|
12
|
+
import 'comark/plugins/components';
|
|
13
|
+
import 'comark/plugins/emoji';
|
|
14
|
+
import 'comark/plugins/footnotes';
|
|
15
|
+
import 'comark/plugins/frontmatter';
|
|
16
|
+
import 'comark/plugins/math';
|
|
17
|
+
import 'comark/plugins/mermaid';
|
|
18
|
+
import 'comark/plugins/task-list';
|
|
19
|
+
import 'comark/plugins/toc';
|
|
20
|
+
import '@lupinum/ginko-i18n/core';
|
|
21
|
+
|
|
22
|
+
function site(o = {}) {
|
|
23
|
+
const plain = { optional: true, translate: false };
|
|
24
|
+
const fields = {
|
|
25
|
+
legalName: f.text(studioLabel("Offizieller Name", "Legal name", "Nombre legal", "Raison sociale"), {
|
|
26
|
+
...plain,
|
|
27
|
+
hint: studioLabel("Wie im Register eingetragen.", "As registered.", "Tal como figura en el registro.", "Tel qu\u2019enregistr\xE9.")
|
|
28
|
+
}),
|
|
29
|
+
street: f.text(studioLabel("Stra\xDFe und Hausnummer", "Street and number", "Calle y n\xFAmero", "Rue et num\xE9ro"), plain),
|
|
30
|
+
postalCode: f.text(studioLabel("Postleitzahl", "Postcode", "C\xF3digo postal", "Code postal"), { ...plain, max: 10 }),
|
|
31
|
+
city: f.text(studioLabel("Ort", "Town or city", "Localidad", "Ville"), plain),
|
|
32
|
+
email: f.text(studioLabel("E-Mail", "Email", "Correo electr\xF3nico", "E-mail"), { optional: true, format: "email" }),
|
|
33
|
+
phone: f.text(studioLabel("Telefon", "Phone", "Tel\xE9fono", "T\xE9l\xE9phone"), {
|
|
34
|
+
optional: true,
|
|
35
|
+
format: "tel",
|
|
36
|
+
hint: studioLabel("Mit L\xE4ndervorwahl.", "With country code.", "Con prefijo internacional.", "Avec l\u2019indicatif du pays.")
|
|
37
|
+
}),
|
|
38
|
+
registryNumber: f.text(studioLabel("Registernummer", "Registration number", "N\xFAmero de registro", "Num\xE9ro d\u2019immatriculation"), plain),
|
|
39
|
+
registryCourt: f.text(studioLabel("Registergericht", "Registry", "Registro", "Greffe"), { optional: true }),
|
|
40
|
+
vatId: f.text(studioLabel("Umsatzsteuer-ID", "VAT number", "NIF-IVA", "Num\xE9ro de TVA"), plain)
|
|
41
|
+
};
|
|
42
|
+
return single({
|
|
43
|
+
label: o.label ?? studioLabel("Kontakt und Anschrift", "Contact and address", "Contacto y direcci\xF3n", "Contact et adresse"),
|
|
44
|
+
describe: studioLabel(
|
|
45
|
+
"Name, Anschrift und Kontakt, zum Beispiel f\xFCr das Impressum und die Fu\xDFzeile.",
|
|
46
|
+
"Name, address and contact details, for example for the imprint and the footer.",
|
|
47
|
+
"Nombre, direcci\xF3n y datos de contacto, por ejemplo para el aviso legal y el pie de p\xE1gina.",
|
|
48
|
+
"Nom, adresse et coordonn\xE9es, par exemple pour les mentions l\xE9gales et le pied de page."
|
|
49
|
+
),
|
|
50
|
+
...sourceOf(o),
|
|
51
|
+
fields,
|
|
52
|
+
tasks: [{
|
|
53
|
+
id: "edit",
|
|
54
|
+
kind: "edit",
|
|
55
|
+
label: studioLabel("Kontaktdaten \xE4ndern", "Change contact details", "Cambiar los datos de contacto", "Modifier les coordonn\xE9es"),
|
|
56
|
+
fields: Object.keys(fields)
|
|
57
|
+
}]
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function siteJsonLd(data, o) {
|
|
61
|
+
return organizationLd({
|
|
62
|
+
...o,
|
|
63
|
+
legalName: data.legalName,
|
|
64
|
+
email: data.email,
|
|
65
|
+
telephone: data.phone,
|
|
66
|
+
address: { street: data.street, postalCode: data.postalCode, city: data.city, country: o.country },
|
|
67
|
+
vatID: data.vatId
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const legalTypes = [
|
|
72
|
+
{ value: "privacy", label: studioLabel("Datenschutz", "Privacy policy", "Pol\xEDtica de privacidad", "Politique de confidentialit\xE9") },
|
|
73
|
+
{ value: "customer-privacy", label: studioLabel("Datenschutz f\xFCr Kunden", "Privacy policy for customers", "Privacidad para clientes", "Confidentialit\xE9 pour les clients") },
|
|
74
|
+
{ value: "imprint", label: studioLabel("Impressum", "Imprint", "Aviso legal", "Mentions l\xE9gales") },
|
|
75
|
+
{ value: "terms", label: studioLabel("AGB", "Terms and conditions", "T\xE9rminos y condiciones", "Conditions g\xE9n\xE9rales") },
|
|
76
|
+
{ value: "cookies", label: studioLabel("Cookies", "Cookie policy", "Pol\xEDtica de cookies", "Politique relative aux cookies") },
|
|
77
|
+
{ value: "accessibility", label: studioLabel("Barrierefreiheit", "Accessibility statement", "Declaraci\xF3n de accesibilidad", "D\xE9claration d\u2019accessibilit\xE9") }
|
|
78
|
+
];
|
|
79
|
+
function legal(o = {}) {
|
|
80
|
+
return collection({
|
|
81
|
+
label: o.label ?? studioLabel("Rechtliches", "Legal", "Textos legales", "Informations l\xE9gales"),
|
|
82
|
+
...sourceOf(o),
|
|
83
|
+
max: 20,
|
|
84
|
+
order: "manual",
|
|
85
|
+
...o.route ? { route: o.route } : {},
|
|
86
|
+
tasks: [{
|
|
87
|
+
id: "update",
|
|
88
|
+
kind: "edit",
|
|
89
|
+
pick: true,
|
|
90
|
+
fields: ["body", "updatedAt"],
|
|
91
|
+
prefill: { updatedAt: "@today" },
|
|
92
|
+
label: studioLabel("Rechtstext aktualisieren", "Update a legal text", "Actualizar un texto legal", "Mettre \xE0 jour un texte juridique")
|
|
93
|
+
}],
|
|
94
|
+
fields: {
|
|
95
|
+
title: f.text(studioLabel("Titel", "Title", "T\xEDtulo", "Titre")),
|
|
96
|
+
description: f.longtext(studioLabel("Beschreibung", "Description", "Descripci\xF3n", "Description"), { optional: true }),
|
|
97
|
+
slug: f.slug(studioLabel("Adresse", "Address", "Direcci\xF3n", "Adresse"), { from: "title" }),
|
|
98
|
+
legalType: f.choice(studioLabel("Art", "Kind", "Tipo", "Type"), { options: legalTypes }),
|
|
99
|
+
updatedAt: f.date(studioLabel("Stand", "Last updated", "\xDAltima actualizaci\xF3n", "Derni\xE8re mise \xE0 jour")),
|
|
100
|
+
// Never machine-translated: a legal text in another language is a legal text of its own.
|
|
101
|
+
body: f.markdown(studioLabel("Text", "Text", "Texto", "Texte"), { translate: "manual" })
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const slugField = () => f.slug(studioLabel("Adresse", "Address", "Direcci\xF3n", "Adresse"), { from: "name" });
|
|
107
|
+
function people(o = {}) {
|
|
108
|
+
return collection({
|
|
109
|
+
label: o.label ?? studioLabel("Personen", "People", "Personas", "Personnes"),
|
|
110
|
+
...sourceOf(o),
|
|
111
|
+
max: 100,
|
|
112
|
+
order: "manual",
|
|
113
|
+
...o.route ? { route: o.route } : {},
|
|
114
|
+
tasks: [
|
|
115
|
+
{ id: "add", kind: "create", label: studioLabel("Person hinzuf\xFCgen", "Add a person", "A\xF1adir una persona", "Ajouter une personne") },
|
|
116
|
+
{ id: "edit", kind: "edit", pick: true, fields: ["name", "role", "bio", "email", "phone", "photo"], label: studioLabel("Person bearbeiten", "Edit a person", "Editar una persona", "Modifier une personne") },
|
|
117
|
+
{ id: "remove", kind: "remove", label: studioLabel("Person entfernen", "Remove a person", "Eliminar una persona", "Retirer une personne") }
|
|
118
|
+
],
|
|
119
|
+
fields: {
|
|
120
|
+
// A name is the same in every language.
|
|
121
|
+
name: f.text(studioLabel("Name", "Name", "Nombre", "Nom"), { translate: false }),
|
|
122
|
+
role: f.text(studioLabel("Rolle", "Role", "Funci\xF3n", "Fonction"), { optional: true }),
|
|
123
|
+
bio: f.longtext(studioLabel("Kurzbiografie", "Short bio", "Biograf\xEDa breve", "Courte biographie"), { optional: true }),
|
|
124
|
+
email: f.text(studioLabel("E-Mail", "Email", "Correo electr\xF3nico", "E-mail"), { optional: true, format: "email", translate: false }),
|
|
125
|
+
phone: f.text(studioLabel("Telefon", "Phone", "Tel\xE9fono", "T\xE9l\xE9phone"), { optional: true, format: "tel", translate: false }),
|
|
126
|
+
photo: f.image(studioLabel("Foto", "Photo", "Foto", "Photo"), { optional: true }),
|
|
127
|
+
// Only with a route: the stored shape of people without pages does not change.
|
|
128
|
+
// `o.route` is the runtime value of `R`, so the spread matches SlugOf<R>; TypeScript cannot narrow a generic by it.
|
|
129
|
+
...o.route ? { slug: slugField() } : {}
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function personJsonLd(p) {
|
|
134
|
+
return compact({
|
|
135
|
+
"@context": "https://schema.org",
|
|
136
|
+
"@type": "Person",
|
|
137
|
+
name: p.name,
|
|
138
|
+
jobTitle: p.role,
|
|
139
|
+
description: p.bio,
|
|
140
|
+
email: p.email,
|
|
141
|
+
telephone: p.phone,
|
|
142
|
+
image: p.image,
|
|
143
|
+
url: p.url,
|
|
144
|
+
worksFor: p.worksFor ? compact({ "@type": "Organization", name: p.worksFor.name, url: p.worksFor.url }) : void 0
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function posts(o = {}) {
|
|
149
|
+
const tag = studioLabel("Schlagwort", "Tag", "Etiqueta", "Mot-cl\xE9");
|
|
150
|
+
return collection({
|
|
151
|
+
label: o.label ?? studioLabel("Beitr\xE4ge", "Posts", "Art\xEDculos", "Articles"),
|
|
152
|
+
...sourceOf(o),
|
|
153
|
+
max: o.max ?? 500,
|
|
154
|
+
order: { field: "date", dir: "desc" },
|
|
155
|
+
...o.route ? { route: o.route } : {},
|
|
156
|
+
fields: {
|
|
157
|
+
title: f.text(studioLabel("Titel", "Title", "T\xEDtulo", "Titre")),
|
|
158
|
+
description: f.longtext(studioLabel("Beschreibung", "Description", "Descripci\xF3n", "Description"), { optional: true }),
|
|
159
|
+
date: f.date(studioLabel("Datum", "Date", "Fecha", "Date")),
|
|
160
|
+
authors: f.refs(studioLabel("Autorinnen und Autoren", "Authors", "Autores", "Auteurs"), { to: o.authors ?? "authors", optional: true }),
|
|
161
|
+
image: f.image(studioLabel("Bild", "Image", "Imagen", "Image"), { optional: true }),
|
|
162
|
+
tags: f.list(studioLabel("Schlagw\xF6rter", "Tags", "Etiquetas", "Mots-cl\xE9s"), { itemLabel: tag, fields: { tag: f.text(tag, { max: 40 }) }, max: 10, optional: true }),
|
|
163
|
+
slug: f.slug(studioLabel("Adresse", "Address", "Direcci\xF3n", "Adresse"), { from: "title" }),
|
|
164
|
+
body: f.markdown(studioLabel("Text", "Text", "Texto", "Texte"), { profile: "page" })
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
function authors(o = {}) {
|
|
169
|
+
return collection({
|
|
170
|
+
label: o.label ?? studioLabel("Autorinnen und Autoren", "Authors", "Autores", "Auteurs"),
|
|
171
|
+
...sourceOf(o),
|
|
172
|
+
max: 100,
|
|
173
|
+
order: "manual",
|
|
174
|
+
fields: {
|
|
175
|
+
name: f.text(studioLabel("Name", "Name", "Nombre", "Nom")),
|
|
176
|
+
role: f.text(studioLabel("Rolle", "Role", "Funci\xF3n", "Fonction"), { optional: true }),
|
|
177
|
+
avatar: f.image(studioLabel("Bild", "Image", "Imagen", "Image"), { optional: true }),
|
|
178
|
+
/** Shown as a link on the author's name. */
|
|
179
|
+
url: f.text(studioLabel("Website", "Website", "Sitio web", "Site web"), { format: "url", optional: true })
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function changelog(o = {}) {
|
|
184
|
+
return collection({
|
|
185
|
+
label: o.label ?? studioLabel("\xC4nderungen", "Changelog", "Registro de cambios", "Journal des modifications"),
|
|
186
|
+
...sourceOf(o),
|
|
187
|
+
max: o.max ?? 500,
|
|
188
|
+
order: { field: "date", dir: "desc" },
|
|
189
|
+
// A release written ahead is the normal case: an entry dated after the build day is scheduled, `draft: true` holds it back.
|
|
190
|
+
drafts: true,
|
|
191
|
+
fields: {
|
|
192
|
+
version: f.text(studioLabel("Version", "Version", "Versi\xF3n", "Version"), { max: 40, translate: false }),
|
|
193
|
+
date: f.date(studioLabel("Datum", "Date", "Fecha", "Date")),
|
|
194
|
+
body: f.markdown(studioLabel("Text", "Text", "Texto", "Texte"), { profile: "page" })
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function releaseAnchor(version) {
|
|
199
|
+
return `v${version.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")}`;
|
|
200
|
+
}
|
|
201
|
+
function postJsonLd(p) {
|
|
202
|
+
return articleLd({
|
|
203
|
+
type: "BlogPosting",
|
|
204
|
+
title: p.title,
|
|
205
|
+
url: p.url,
|
|
206
|
+
description: p.description,
|
|
207
|
+
image: p.image,
|
|
208
|
+
published: p.date,
|
|
209
|
+
modified: p.modified,
|
|
210
|
+
author: p.authors,
|
|
211
|
+
keywords: p.tags,
|
|
212
|
+
lang: p.lang
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const employmentTypes = [
|
|
217
|
+
{ value: "fullTime", label: studioLabel("Vollzeit", "Full time", "Jornada completa", "Temps plein") },
|
|
218
|
+
{ value: "partTime", label: studioLabel("Teilzeit", "Part time", "Media jornada", "Temps partiel") },
|
|
219
|
+
{ value: "freelance", label: studioLabel("Freie Mitarbeit", "Freelance", "Aut\xF3nomo", "Ind\xE9pendant") },
|
|
220
|
+
{ value: "internship", label: studioLabel("Praktikum", "Internship", "Pr\xE1cticas", "Stage") },
|
|
221
|
+
{ value: "apprenticeship", label: studioLabel("Lehre", "Apprenticeship", "Formaci\xF3n profesional", "Apprentissage") }
|
|
222
|
+
];
|
|
223
|
+
const workPlaces = [
|
|
224
|
+
{ value: "onsite", label: studioLabel("Vor Ort", "On site", "Presencial", "Sur place") },
|
|
225
|
+
{ value: "hybrid", label: studioLabel("Hybrid", "Hybrid", "H\xEDbrido", "Hybride") },
|
|
226
|
+
{ value: "remote", label: studioLabel("Remote", "Remote", "Remoto", "\xC0 distance") }
|
|
227
|
+
];
|
|
228
|
+
function jobs(o = {}) {
|
|
229
|
+
return collection({
|
|
230
|
+
label: o.label ?? studioLabel("Stellen", "Jobs", "Empleos", "Offres d\u2019emploi"),
|
|
231
|
+
...sourceOf(o),
|
|
232
|
+
max: 30,
|
|
233
|
+
order: { field: "title", dir: "asc" },
|
|
234
|
+
...o.route ? { route: o.route } : {},
|
|
235
|
+
visible: { mode: "until", field: "validUntil" },
|
|
236
|
+
tasks: [
|
|
237
|
+
{ id: "add", kind: "create", label: studioLabel("Stelle ausschreiben", "Post a job", "Publicar una oferta", "Publier une offre") },
|
|
238
|
+
{ id: "extend", kind: "edit", pick: true, fields: ["validUntil"], label: studioLabel("Bewerbungsfrist \xE4ndern", "Change the deadline", "Cambiar el plazo", "Modifier la date limite") },
|
|
239
|
+
{ id: "remove", kind: "remove", label: studioLabel("Stelle entfernen", "Remove a job", "Eliminar una oferta", "Retirer une offre") }
|
|
240
|
+
],
|
|
241
|
+
fields: {
|
|
242
|
+
title: f.text(studioLabel("Titel", "Title", "T\xEDtulo", "Titre")),
|
|
243
|
+
description: f.longtext(studioLabel("Kurzbeschreibung", "Short description", "Descripci\xF3n breve", "Description courte")),
|
|
244
|
+
slug: f.slug(studioLabel("Adresse", "Address", "Direcci\xF3n", "Adresse"), { from: "title" }),
|
|
245
|
+
location: f.text(studioLabel("Ort", "Location", "Ubicaci\xF3n", "Lieu")),
|
|
246
|
+
employmentType: f.choice(studioLabel("Anstellung", "Employment", "Tipo de contrato", "Type de contrat"), { options: employmentTypes }),
|
|
247
|
+
remote: f.choice(studioLabel("Arbeitsort", "Work place", "Lugar de trabajo", "Lieu de travail"), { options: workPlaces, default: "onsite" }),
|
|
248
|
+
applyUrl: f.text(studioLabel("Bewerbungs-Link", "Application link", "Enlace de solicitud", "Lien de candidature"), { format: "url", optional: true }),
|
|
249
|
+
applyEmail: f.text(studioLabel("Bewerbungs-E-Mail", "Application email", "Correo de solicitud", "E-mail de candidature"), { format: "email", optional: true }),
|
|
250
|
+
validUntil: f.date(studioLabel("Ausgeschrieben bis", "Open until", "Abierta hasta", "Ouverte jusqu\u2019au"), {
|
|
251
|
+
hint: studioLabel("Am Tag danach verschwindet die Stelle von der Website.", "The job leaves the website the day after.", "La oferta desaparece del sitio web al d\xEDa siguiente.", "L\u2019offre dispara\xEEt du site le lendemain.")
|
|
252
|
+
}),
|
|
253
|
+
body: f.markdown(studioLabel("Beschreibung", "Description", "Descripci\xF3n", "Description"))
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
const SCHEMA_EMPLOYMENT = {
|
|
258
|
+
fullTime: "FULL_TIME",
|
|
259
|
+
partTime: "PART_TIME",
|
|
260
|
+
freelance: "CONTRACTOR",
|
|
261
|
+
internship: "INTERN",
|
|
262
|
+
apprenticeship: "OTHER"
|
|
263
|
+
};
|
|
264
|
+
function jobJsonLd(j) {
|
|
265
|
+
const remote = j.remote === "remote";
|
|
266
|
+
return compact({
|
|
267
|
+
"@context": "https://schema.org",
|
|
268
|
+
"@type": "JobPosting",
|
|
269
|
+
title: j.title,
|
|
270
|
+
url: j.url,
|
|
271
|
+
description: j.description,
|
|
272
|
+
datePosted: j.datePosted,
|
|
273
|
+
validThrough: j.validUntil,
|
|
274
|
+
employmentType: j.employmentType && Object.hasOwn(SCHEMA_EMPLOYMENT, j.employmentType) ? SCHEMA_EMPLOYMENT[j.employmentType] : void 0,
|
|
275
|
+
hiringOrganization: compact({ "@type": "Organization", name: j.organization.name, sameAs: j.organization.url, logo: j.organization.logo }),
|
|
276
|
+
jobLocation: j.location ? { "@type": "Place", address: postalAddressLd({ city: j.location, country: j.country }) } : void 0,
|
|
277
|
+
jobLocationType: remote ? "TELECOMMUTE" : void 0,
|
|
278
|
+
applicantLocationRequirements: remote && j.country ? { "@type": "Country", name: j.country } : void 0
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function faq(o = {}) {
|
|
283
|
+
return collection({
|
|
284
|
+
label: o.label ?? studioLabel("H\xE4ufige Fragen", "FAQ", "Preguntas frecuentes", "Questions fr\xE9quentes"),
|
|
285
|
+
...sourceOf(o),
|
|
286
|
+
max: 100,
|
|
287
|
+
order: "manual",
|
|
288
|
+
tasks: [
|
|
289
|
+
{ id: "add", kind: "create", label: studioLabel("Frage hinzuf\xFCgen", "Add a question", "A\xF1adir una pregunta", "Ajouter une question") },
|
|
290
|
+
{ id: "edit", kind: "edit", pick: true, fields: ["question", "answer"], label: studioLabel("Frage bearbeiten", "Edit a question", "Editar una pregunta", "Modifier une question") },
|
|
291
|
+
{ id: "remove", kind: "remove", label: studioLabel("Frage entfernen", "Remove a question", "Eliminar una pregunta", "Retirer une question") }
|
|
292
|
+
],
|
|
293
|
+
fields: {
|
|
294
|
+
question: f.text(studioLabel("Frage", "Question", "Pregunta", "Question")),
|
|
295
|
+
answer: f.longtext(studioLabel("Antwort", "Answer", "Respuesta", "R\xE9ponse"))
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function faqJsonLd(items) {
|
|
300
|
+
return faqLd([...items]);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function downloads(o = {}) {
|
|
304
|
+
return collection({
|
|
305
|
+
label: o.label ?? studioLabel("Downloads", "Downloads", "Descargas", "T\xE9l\xE9chargements"),
|
|
306
|
+
...sourceOf(o),
|
|
307
|
+
max: 100,
|
|
308
|
+
order: { field: "date", dir: "desc" },
|
|
309
|
+
tasks: [
|
|
310
|
+
{ id: "add", kind: "create", label: studioLabel("Datei hinzuf\xFCgen", "Add a file", "A\xF1adir un archivo", "Ajouter un fichier") },
|
|
311
|
+
{ id: "edit", kind: "edit", pick: true, fields: ["title", "summary", "date", "file", "language"], label: studioLabel("Datei bearbeiten", "Edit a file", "Editar un archivo", "Modifier un fichier") },
|
|
312
|
+
{ id: "remove", kind: "remove", label: studioLabel("Datei entfernen", "Remove a file", "Eliminar un archivo", "Retirer un fichier") }
|
|
313
|
+
],
|
|
314
|
+
fields: {
|
|
315
|
+
title: f.text(studioLabel("Titel", "Title", "T\xEDtulo", "Titre")),
|
|
316
|
+
summary: f.longtext(studioLabel("Kurzbeschreibung", "Summary", "Resumen", "R\xE9sum\xE9"), { optional: true }),
|
|
317
|
+
date: f.date(studioLabel("Datum", "Date", "Fecha", "Date")),
|
|
318
|
+
file: f.file(studioLabel("Datei", "File", "Archivo", "Fichier"), { accept: ["pdf"], maxBytes: limitOf("file") }),
|
|
319
|
+
// The language of the document (a code such as `en`): one file serves every language of the site, so the list
|
|
320
|
+
// can say "in English" on the German page. Empty: the site's main language.
|
|
321
|
+
language: f.text(studioLabel("Sprache des Dokuments", "Language of the document", "Idioma del documento", "Langue du document"), {
|
|
322
|
+
optional: true,
|
|
323
|
+
translate: false,
|
|
324
|
+
max: 35,
|
|
325
|
+
hint: studioLabel("Sprachcode, zum Beispiel de oder en.", "A language code, for example en or de.", "Un c\xF3digo de idioma, por ejemplo es o en.", "Un code de langue, par exemple fr ou en.")
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
332
|
+
const rfc822 = (day) => (/* @__PURE__ */ new Date(`${day}T00:00:00Z`)).toUTCString();
|
|
333
|
+
const tag = (name, value) => `<${name}>${escapeXml(value)}</${name}>`;
|
|
334
|
+
function rssXml(feed) {
|
|
335
|
+
const items = feed.items.map((item) => [
|
|
336
|
+
" <item>",
|
|
337
|
+
` ${tag("title", item.title)}`,
|
|
338
|
+
` ${tag("link", item.link)}`,
|
|
339
|
+
` <guid isPermaLink="true">${escapeXml(item.link)}</guid>`,
|
|
340
|
+
` ${tag("pubDate", rfc822(item.date))}`,
|
|
341
|
+
...item.description ? [` ${tag("description", item.description)}`] : [],
|
|
342
|
+
...item.authors.map((a) => ` ${tag("dc:creator", a)}`),
|
|
343
|
+
" </item>"
|
|
344
|
+
].join("\n"));
|
|
345
|
+
const newest = feed.items.map((i) => i.date).sort().at(-1);
|
|
346
|
+
return [
|
|
347
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
348
|
+
'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">',
|
|
349
|
+
" <channel>",
|
|
350
|
+
` ${tag("title", feed.title)}`,
|
|
351
|
+
` ${tag("link", feed.link)}`,
|
|
352
|
+
` ${tag("description", feed.description)}`,
|
|
353
|
+
` ${tag("language", feed.language)}`,
|
|
354
|
+
...newest ? [` ${tag("lastBuildDate", rfc822(newest))}`] : [],
|
|
355
|
+
` <atom:link href="${escapeXml(feed.self)}" rel="self" type="application/rss+xml"/>`,
|
|
356
|
+
...items,
|
|
357
|
+
" </channel>",
|
|
358
|
+
"</rss>",
|
|
359
|
+
""
|
|
360
|
+
].join("\n");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const dayNames = { mo: "Monday", tu: "Tuesday", we: "Wednesday", th: "Thursday", fr: "Friday", sa: "Saturday", su: "Sunday" };
|
|
364
|
+
const spanKey = (spans) => spans.map((s) => `${s.from}-${s.to}`).join(",");
|
|
365
|
+
function weeklySpecs(week) {
|
|
366
|
+
const groups = /* @__PURE__ */ new Map();
|
|
367
|
+
for (const key of weekdayKeys) {
|
|
368
|
+
const day = week.find((d) => d.day === key);
|
|
369
|
+
const spans = !day || day.closed ? [] : spansOf(day);
|
|
370
|
+
if (!spans.length) continue;
|
|
371
|
+
const k = spanKey(spans);
|
|
372
|
+
const group = groups.get(k) ?? { spans, days: [] };
|
|
373
|
+
group.days.push(key);
|
|
374
|
+
groups.set(k, group);
|
|
375
|
+
}
|
|
376
|
+
return [...groups.values()].flatMap((g) => g.spans.map((s) => ({ "@type": "OpeningHoursSpecification", dayOfWeek: g.days.map((d) => dayNames[d]), opens: s.from, closes: s.to })));
|
|
377
|
+
}
|
|
378
|
+
function specialSpecs(ctx, today, days, week) {
|
|
379
|
+
const runs = [];
|
|
380
|
+
for (let i = 0; i < days; i++) {
|
|
381
|
+
const date = addDays(today, i);
|
|
382
|
+
const rule = hoursOn(ctx, date);
|
|
383
|
+
const base = week.find((d) => d.day === weekdayOf(date));
|
|
384
|
+
const baseKey = !base || base.closed ? "" : spanKey(spansOf(base));
|
|
385
|
+
const key = rule.closed ? "" : spanKey(rule.times);
|
|
386
|
+
if (key === baseKey) continue;
|
|
387
|
+
const last = runs[runs.length - 1];
|
|
388
|
+
if (last && last.key === key && last.to === addDays(date, -1)) last.to = date;
|
|
389
|
+
else runs.push({ key, spans: rule.closed ? [] : rule.times, from: date, to: date });
|
|
390
|
+
}
|
|
391
|
+
return runs.flatMap((r) => (r.spans.length ? r.spans : [{ from: "00:00", to: "00:00" }]).map((s) => ({
|
|
392
|
+
"@type": "OpeningHoursSpecification",
|
|
393
|
+
validFrom: r.from,
|
|
394
|
+
validThrough: r.to,
|
|
395
|
+
opens: s.from,
|
|
396
|
+
closes: s.to
|
|
397
|
+
})));
|
|
398
|
+
}
|
|
399
|
+
function openingHoursJsonLd(ctx, today, days = 60) {
|
|
400
|
+
const { week } = weekFor(ctx, today);
|
|
401
|
+
const special = specialSpecs(ctx, today, days, week);
|
|
402
|
+
return { openingHoursSpecification: weeklySpecs(week), ...special.length ? { specialOpeningHoursSpecification: special } : {} };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function gallery(opts = {}) {
|
|
406
|
+
return single({
|
|
407
|
+
label: opts.label ?? both("Galerie", "Gallery"),
|
|
408
|
+
...sourceOf(opts),
|
|
409
|
+
fields: {
|
|
410
|
+
images: f.list(both("Bilder", "Images"), { itemLabel: both("Bild", "Image"), fields: { image: f.image(both("Bild", "Image")) }, max: 24, default: [] })
|
|
411
|
+
},
|
|
412
|
+
tasks: [{ id: "edit", label: both("Fotos bearbeiten", "Edit photos"), kind: "edit", fields: ["images"] }]
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
export { authors, changelog, downloads, faq, faqJsonLd, gallery, hoursOn, jobJsonLd, jobs, legal, legalTypes, openingHoursJsonLd, people, personJsonLd, postJsonLd, posts, releaseAnchor, rssXml, site, siteJsonLd, spansOf, weekFor, weekdayOf };
|
package/dist/cli/index.mjs
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
import { existsSync, realpathSync, readFileSync } from 'node:fs';
|
|
3
3
|
import { join, dirname, resolve } from 'node:path';
|
|
4
4
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
5
|
-
import { f as
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { C as CliError, i as isObject, d as defaultsFor, f as fieldsChanged, r as releaseData, S as SITE_BUDGET, e as entryBytes, a as fitsBudget, b as diffSchema, l as largestEntryBytes, c as largestFieldBytes, p as parseExport, g as parseReleaseState, h as pathOf, j as defaultDir } from '../shared/ginko.t-gbwzxh.mjs';
|
|
6
|
+
import { g as siteForContent } from '../shared/ginko.C-oz8wwI.mjs';
|
|
7
|
+
import { f as fileLinkIssues } from '../shared/ginko.raCzOPYC.mjs';
|
|
8
|
+
import { F as coreMessage, i as isTranslatable, g as effectiveMax } from '../shared/ginko.BNVC-g9n.mjs';
|
|
9
|
+
import { s as serializeSchema, c as checkSite, v as validate, p as platformSchema, a as checkSchema } from '../shared/ginko.B1-DoOea.mjs';
|
|
10
|
+
import { c as calendarOfCountry } from '../shared/ginko.D1PeQ3ds.mjs';
|
|
9
11
|
import { mkdir, writeFile, rm, readdir } from 'node:fs/promises';
|
|
10
12
|
import { execFile } from 'node:child_process';
|
|
13
|
+
import '../shared/ginko.yKMIjJ68.mjs';
|
|
11
14
|
import 'comark';
|
|
12
15
|
import 'comark/plugins/alert';
|
|
13
16
|
import 'comark/plugins/attributes';
|
|
@@ -20,30 +23,6 @@ import 'comark/plugins/mermaid';
|
|
|
20
23
|
import 'comark/plugins/task-list';
|
|
21
24
|
import 'comark/plugins/toc';
|
|
22
25
|
import '@lupinum/ginko-i18n/core';
|
|
23
|
-
import '../shared/ginko.CQvxhyez.mjs';
|
|
24
|
-
|
|
25
|
-
class CliError extends Error {
|
|
26
|
-
code;
|
|
27
|
-
constructor(message, code = "INVALID") {
|
|
28
|
-
super(message);
|
|
29
|
-
this.code = code;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
-
function parseReleaseState(value) {
|
|
34
|
-
if (!isObject(value)) throw new CliError("The platform did not return a valid state.", "UNAVAILABLE");
|
|
35
|
-
const { site, schema, schemaRev, contentRev, migrations, entries } = value;
|
|
36
|
-
const valid = typeof site === "string" && isObject(schema) && isObject(schema.bricks) && Array.isArray(schema.migrations) && isObject(schema.retired) && typeof schemaRev === "number" && typeof contentRev === "number" && Array.isArray(migrations) && Array.isArray(entries) && entries.every((e) => isObject(e) && typeof e.id === "string" && typeof e.brick === "string" && isObject(e.data));
|
|
37
|
-
if (!valid) throw new CliError("The platform returned an incomplete state.", "UNAVAILABLE");
|
|
38
|
-
return value;
|
|
39
|
-
}
|
|
40
|
-
function parseExport(value, locator, problem) {
|
|
41
|
-
const optionalNumber = (v) => v === void 0 || typeof v === "number";
|
|
42
|
-
const optionalString = (v) => v === void 0 || typeof v === "string";
|
|
43
|
-
const valid = isObject(value) && value.format === 1 && typeof value.site === "string" && typeof value.name === "string" && typeof value.exportedAt === "string" && isObject(value.schema) && isObject(value.schema.bricks) && typeof value.schemaRev === "number" && typeof value.contentRev === "number" && Array.isArray(value.entries) && value.entries.every((e) => isObject(e) && typeof e.id === "string" && typeof e.brick === "string" && (e.kind === "single" || e.kind === "item") && typeof e.order === "number" && isObject(e.data) && isObject(e.prints) && typeof e.rev === "number" && optionalNumber(e.deletedAt)) && Array.isArray(value.files) && value.files.every((f) => isObject(f) && typeof f.id === "string" && typeof f.name === "string" && typeof f.mime === "string" && typeof f.size === "number" && typeof f[locator] === "string" && optionalNumber(f.width) && optionalNumber(f.height) && optionalString(f.folder) && optionalString(f.sha256) && (f.kept === void 0 || typeof f.kept === "boolean") && (f.focus === void 0 || isObject(f.focus) && typeof f.focus.x === "number" && typeof f.focus.y === "number")) && (value.folders === void 0 || Array.isArray(value.folders) && value.folders.every((f) => isObject(f) && typeof f.id === "string" && typeof f.name === "string" && optionalString(f.parent)));
|
|
44
|
-
if (!valid) throw new CliError(problem, "UNAVAILABLE" );
|
|
45
|
-
return value;
|
|
46
|
-
}
|
|
47
26
|
|
|
48
27
|
async function loadConfig(path, revision = 0) {
|
|
49
28
|
const url = pathToFileURL(path).href + (revision > 0 ? `?rev=${revision}` : "");
|
|
@@ -57,13 +36,18 @@ async function loadConfig(path, revision = 0) {
|
|
|
57
36
|
if (!isObject(config) || typeof config.site !== "string" || !isObject(config.bricks)) {
|
|
58
37
|
throw new CliError(`${path} needs a default export with defineSite(\u2026).`);
|
|
59
38
|
}
|
|
60
|
-
|
|
39
|
+
try {
|
|
40
|
+
return siteForContent(config, process.env.GINKO_CONTENT);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new CliError(error.message);
|
|
43
|
+
}
|
|
61
44
|
}
|
|
62
45
|
|
|
63
46
|
function english(m) {
|
|
64
47
|
return coreMessage("en", m.key, m.params);
|
|
65
48
|
}
|
|
66
49
|
function explainRejected(rejected) {
|
|
50
|
+
if (rejected.length === 0) return [];
|
|
67
51
|
const lines = ["The schema changes in a way that is not allowed:"];
|
|
68
52
|
for (const r of rejected) {
|
|
69
53
|
lines.push(` \u2717 ${r.path}: ${english(r)}`);
|
|
@@ -109,8 +93,6 @@ async function checkFileLinks(input) {
|
|
|
109
93
|
return ok;
|
|
110
94
|
}
|
|
111
95
|
|
|
112
|
-
const BUDGET = SITE_BUDGET;
|
|
113
|
-
const byteSize = (value) => new TextEncoder().encode(JSON.stringify(value ?? null)).byteLength;
|
|
114
96
|
async function buildCandidate(state, site, schema) {
|
|
115
97
|
const problems = checkSite(site, schema);
|
|
116
98
|
const applied = new Set([...state.schema.migrations, ...state.migrations].map((m) => m.id));
|
|
@@ -175,20 +157,27 @@ async function buildCandidate(state, site, schema) {
|
|
|
175
157
|
}
|
|
176
158
|
const changedData = new Map(changed.map((c) => [c.id, c.data]));
|
|
177
159
|
const bytes = { site: 0, largest: 0 };
|
|
178
|
-
|
|
160
|
+
let siteBefore = 0;
|
|
161
|
+
const limit = `the limit is ${kib(SITE_BUDGET.entryBytes)}`;
|
|
162
|
+
for (const entry of state.entries) {
|
|
163
|
+
if (entry.deletedAt !== null) continue;
|
|
164
|
+
const before = entryBytes(entry.data);
|
|
165
|
+
const next = changedData.get(entry.id);
|
|
166
|
+
const after = next ? entryBytes(next) : before;
|
|
167
|
+
siteBefore += before;
|
|
168
|
+
bytes.site += after;
|
|
169
|
+
bytes.largest = Math.max(bytes.largest, after);
|
|
170
|
+
if (!fitsBudget(after, before, SITE_BUDGET.entryBytes)) problems.push(`Entry ${entry.id} (${entry.brick}) would be ${kib(after)} after the release; ${limit}.`);
|
|
171
|
+
}
|
|
172
|
+
for (const single of created) {
|
|
173
|
+
const size = entryBytes(single.data);
|
|
179
174
|
bytes.site += size;
|
|
180
175
|
bytes.largest = Math.max(bytes.largest, size);
|
|
181
|
-
if (size >
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const fields = (schema.bricks[brick] ?? state.schema.bricks[brick])?.fields ?? {};
|
|
185
|
-
return byteSize(data) + byteSize(await markdownTrees(fields, data));
|
|
186
|
-
};
|
|
187
|
-
for (const entry of state.entries) count(await sizeOf(entry.brick, changedData.get(entry.id) ?? entry.data), `Entry ${entry.id} (${entry.brick})`);
|
|
188
|
-
for (const single of created) count(await sizeOf(single.brick, single.data), `The new single brick \u201C${single.brick}\u201D`);
|
|
189
|
-
if (bytes.site > BUDGET.siteBytes) problems.push("The site has reached its storage budget (4 MiB).");
|
|
176
|
+
if (size > SITE_BUDGET.entryBytes) problems.push(`The new single brick \u201C${single.brick}\u201D would be ${kib(size)}; ${limit}.`);
|
|
177
|
+
}
|
|
178
|
+
if (!fitsBudget(bytes.site, siteBefore, SITE_BUDGET.siteBytes)) problems.push(`The site would use ${kib(bytes.site)} of its ${kib(SITE_BUDGET.siteBytes)} of storage.`);
|
|
190
179
|
const liveEntries = state.entries.filter((entry) => entry.deletedAt === null).length;
|
|
191
|
-
if (liveEntries + created.length >
|
|
180
|
+
if (liveEntries + created.length > SITE_BUDGET.entries) problems.push(`The site has reached its limit of ${SITE_BUDGET.entries} entries.`);
|
|
192
181
|
return {
|
|
193
182
|
schema,
|
|
194
183
|
writes: [...writes].map(([entry, fields]) => ({ entry, fields })),
|
|
@@ -246,6 +235,8 @@ async function check(input) {
|
|
|
246
235
|
for (const problem of problems) input.log(` \u2717 ${problem}`);
|
|
247
236
|
return false;
|
|
248
237
|
}
|
|
238
|
+
warnEntrySize(input.config, input.log);
|
|
239
|
+
warnNoCalendar(input.config, input.log);
|
|
249
240
|
const image = checkSeoImage(input.config, input.root, input.log);
|
|
250
241
|
const links = await checkFileLinks({ config: input.config, root: input.root, log: input.log, fetch: input.fetch }) && image;
|
|
251
242
|
if (!input.connect) {
|
|
@@ -260,6 +251,26 @@ async function check(input) {
|
|
|
260
251
|
return false;
|
|
261
252
|
}
|
|
262
253
|
}
|
|
254
|
+
function warnEntrySize(config, log) {
|
|
255
|
+
const { bricks, locales } = platformSchema(serializeSchema(config));
|
|
256
|
+
for (const [key, brick] of Object.entries(bricks)) {
|
|
257
|
+
const max = largestEntryBytes(brick.fields, locales);
|
|
258
|
+
if (max <= SITE_BUDGET.entryBytes) continue;
|
|
259
|
+
const fields = Object.entries(brick.fields).map(([name, field]) => ({ name, field, bytes: largestFieldBytes(field, locales) }));
|
|
260
|
+
const largest = fields.filter((f) => f.bytes > 0).sort((a, b) => b.bytes - a.bytes).slice(0, 3).map(({ name, field, bytes }) => {
|
|
261
|
+
if (field.type === "list") return `${name} ${field.max} rows`;
|
|
262
|
+
if (field.type !== "text" && field.type !== "longtext" && field.type !== "markdown") return `${name} ${kib(bytes)}`;
|
|
263
|
+
const languages = isTranslatable(field) && locales.length > 1 ? ` \xD7 ${locales.length} languages` : "";
|
|
264
|
+
return `${name} ${effectiveMax(field).toLocaleString("en-US")}${languages}`;
|
|
265
|
+
});
|
|
266
|
+
const uncounted = fields.filter(({ field }) => field.type === "ref" && field.multiple && field.max === void 0).map((f) => f.name);
|
|
267
|
+
log(`Warning: brick \u201C${key}\u201D can hold about ${kib(max)} per entry (largest: ${largest.join(", ")}); the entry limit is ${kib(SITE_BUDGET.entryBytes)}. Lower a max so a full entry fits.${uncounted.length ? ` Not counted: ${uncounted.join(", ")} (entries without a max).` : ""}`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function warnNoCalendar(config, log) {
|
|
271
|
+
if (config.country === void 0 || config.calendar !== void 0 || calendarOfCountry(config.country)) return;
|
|
272
|
+
log(`Warning: Ginko has no public-holiday calendar for country \u201C${config.country}\u201D, so opening hours count no public holidays (stored as holidays: 'none'). Set \`calendar\` in ginko.config.ts to your own function (year) => holidays, or to 'none' to keep it so.`);
|
|
273
|
+
}
|
|
263
274
|
function checkSeoImage(config, root, log) {
|
|
264
275
|
const image = config.seo?.image;
|
|
265
276
|
if (!image || !image.startsWith("/") || image.startsWith("//")) return true;
|
|
@@ -286,7 +297,7 @@ async function checkRelease(input) {
|
|
|
286
297
|
const candidate = result.candidate;
|
|
287
298
|
const migrations = result.schema.migrations.filter((m) => !state.migrations.some((s) => s.id === m.id) && !state.schema.migrations.some((s) => s.id === m.id));
|
|
288
299
|
if (migrations.length > 0) input.log(`New migrations: ${migrations.map((m) => m.id).join(", ")}. Writes: ${candidate.writes.length} entries.`);
|
|
289
|
-
input.log(`Size: site ${kib(candidate.bytes.site)} of
|
|
300
|
+
input.log(`Size: site ${kib(candidate.bytes.site)} of ${kib(SITE_BUDGET.siteBytes)}, largest entry ${kib(candidate.bytes.largest)} (limit ${kib(SITE_BUDGET.entryBytes)}).`);
|
|
290
301
|
if (candidate.problems.length > 0) {
|
|
291
302
|
for (const line of explainProblems(candidate.problems)) input.log(line);
|
|
292
303
|
return false;
|
|
@@ -391,24 +402,11 @@ function createPlatform(options) {
|
|
|
391
402
|
};
|
|
392
403
|
}
|
|
393
404
|
|
|
394
|
-
const EXTENSIONS = {
|
|
395
|
-
"image/jpeg": ".jpg",
|
|
396
|
-
"image/png": ".png",
|
|
397
|
-
"image/webp": ".webp",
|
|
398
|
-
"image/avif": ".avif",
|
|
399
|
-
"application/pdf": ".pdf"
|
|
400
|
-
};
|
|
401
405
|
function siteOfKey(key) {
|
|
402
406
|
const match = PUSH_KEY_FORMAT.exec(key.trim());
|
|
403
407
|
if (!match) throw new CliError("GINKO_KEY is not a valid key.", "USAGE");
|
|
404
408
|
return match[1];
|
|
405
409
|
}
|
|
406
|
-
function defaultDir(site, now) {
|
|
407
|
-
return `ginko-export-${site}-${now.toISOString().slice(0, 10)}`;
|
|
408
|
-
}
|
|
409
|
-
function pathOf(file) {
|
|
410
|
-
return `files/${file.id}${EXTENSIONS[file.mime] ?? ""}`;
|
|
411
|
-
}
|
|
412
410
|
async function requireEmpty(dir) {
|
|
413
411
|
let names;
|
|
414
412
|
try {
|
|
@@ -543,6 +541,10 @@ function parseArgs(argv) {
|
|
|
543
541
|
const value = arg === "--config" ? argv[++i] : arg.slice("--config=".length);
|
|
544
542
|
if (!value) throw new CliError("--config needs a path.", "USAGE");
|
|
545
543
|
args.config = value;
|
|
544
|
+
} else if (arg === "--site" || arg.startsWith("--site=")) {
|
|
545
|
+
const value = arg === "--site" ? argv[++i] : arg.slice("--site=".length);
|
|
546
|
+
if (!value) throw new CliError("--site needs a site slug.", "USAGE");
|
|
547
|
+
args.site = value;
|
|
546
548
|
} else if (arg.startsWith("-")) args.flags.add(arg);
|
|
547
549
|
else if (args.command === void 0) args.command = arg;
|
|
548
550
|
else if (args.folder === void 0) args.folder = arg;
|
|
@@ -568,6 +570,7 @@ async function runCli(argv, deps, cli) {
|
|
|
568
570
|
const name = `${cli.prefix}${args.command}`;
|
|
569
571
|
const flag = [...args.flags].find((f) => f !== "--json" && !command.flags?.includes(f));
|
|
570
572
|
if (flag) throw new CliError(`Unknown option \u201C${flag}\u201D for ${name}.`, "USAGE");
|
|
573
|
+
if (args.site !== void 0 && !command.site) throw new CliError(`Unknown option \u201C--site\u201D for ${name}.`, "USAGE");
|
|
571
574
|
if (args.folder !== void 0 && !command.folder) throw new CliError(`Unexpected argument \u201C${args.folder}\u201D for ${name}.`, "USAGE");
|
|
572
575
|
if (args.folder === void 0 && command.folder === "required") throw new CliError(`${name} needs a folder: ${name} <folder>.`, "USAGE");
|
|
573
576
|
return command.run(args);
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { L as LinkConfig, M as Migration, b as MigrationContext,
|
|
2
|
-
export { B as Brick, d as BrickSource,
|
|
3
|
-
export {
|
|
4
|
-
|
|
1
|
+
export { L as LinkConfig, M as Migration, b as MigrationContext, c as Redirect, R as RouteTemplate, S as SiteInput, e as collection, f as defineSite, s as single } from './shared/ginko.DkIc0ZZk.mjs';
|
|
2
|
+
export { B as Brick, d as BrickSource, Q as BrickStatus, Y as ChoiceOption, f as CollectionBrick, e as CollectionStatus, _ as ComponentDef, b as ComponentRegistry, C as Currency, a2 as DataOf, p as Field, F as Fields, a4 as FileValue, a7 as ImageValue, a8 as ItemOf, J as Json, c as Label, L as Locale, ab as MarkdownProfile, ac as MarkdownToc, M as MarkdownTree, ad as MarkdownValue, af as Money, h as SingleBrick, g as SingleStatus, T as Task, al as TocLink, ao as UnknownChoice, V as Visibility, W as WeekInfo, aw as defineComponents, aB as f, aJ as plainText, aM as startOfWeek, aQ as wordsOf } from './shared/ginko.DrkUeYFP.mjs';
|
|
3
|
+
export { N as NavNode, b as SeoImage, e as breadcrumbJsonLd } from './shared/ginko.o1yIKlgj.mjs';
|
|
4
|
+
export { C as Condition, F as FormatMoneyOptions, Q as Query, f as formatMoney, s as safeHref } from './shared/ginko.BaFweQcB.mjs';
|
|
5
|
+
import './shared/ginko.6cI8KXZP.mjs';
|
|
5
6
|
import '@lupinum/ginko-i18n/core';
|
|
6
7
|
import 'comark';
|