@rmdes/indiekit-endpoint-site-config 1.0.0-beta.1 → 1.0.0-beta.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.
package/index.js CHANGED
@@ -13,6 +13,7 @@ import { apiRouter } from "./lib/controllers/api.js";
13
13
  import { getSiteConfig } from "./lib/storage/get-site-config.js";
14
14
  import { getHomepageConfig } from "./lib/storage/get-homepage-config.js";
15
15
  import { maybeSeedFromEnv } from "./lib/storage/seed-from-env.js";
16
+ import { maybeBackfillIdentity } from "./lib/storage/backfill-identity.js";
16
17
 
17
18
  import { writeThemeCss } from "./lib/render/write-theme-css.js";
18
19
  import { writeCriticalCss } from "./lib/render/write-critical-css.js";
@@ -84,6 +85,7 @@ export default class SiteConfigEndpoint {
84
85
  // First-boot seed + initial file write
85
86
  try {
86
87
  await maybeSeedFromEnv(Indiekit);
88
+ await maybeBackfillIdentity(Indiekit);
87
89
  const config = await getSiteConfig(Indiekit);
88
90
  const homepage = await getHomepageConfig(Indiekit);
89
91
  await writeThemeCss(config);
@@ -0,0 +1,65 @@
1
+ /**
2
+ * One-time backfill: copy homepageConfig.identity → siteConfig.identity
3
+ * when the latter is empty/stub.
4
+ *
5
+ * The legacy @rmdes/indiekit-endpoint-homepage plugin persisted identity
6
+ * inside the homepageConfig collection. The unified plugin reads identity
7
+ * from siteConfig.identity. Without a backfill, operators upgrading from
8
+ * homepage v1.0.24 → site-config v1.0.0-beta.2 would see an empty Identity
9
+ * tab even though the data is sitting one collection over.
10
+ *
11
+ * Heuristic for "empty/stub": none of name, avatar, bio populated.
12
+ * If those three are all empty/missing, we consider the identity stub-shaped
13
+ * and backfill from homepageConfig.identity if a richer version exists.
14
+ *
15
+ * Idempotent: once siteConfig.identity has data, the function no-ops.
16
+ *
17
+ * @module storage/backfill-identity
18
+ */
19
+
20
+ import { saveSiteConfig } from "./save-site-config.js";
21
+
22
+ function isStubIdentity(identity) {
23
+ if (!identity || typeof identity !== "object") return true;
24
+ // The three load-bearing h-card fields. If none populated, treat as stub.
25
+ const name = (identity.name || "").trim();
26
+ const avatar = (identity.avatar || "").trim();
27
+ const bio = (identity.bio || "").trim();
28
+ return name === "" && avatar === "" && bio === "";
29
+ }
30
+
31
+ function hasRichIdentity(identity) {
32
+ if (!identity || typeof identity !== "object") return false;
33
+ // Source must have at least one of the three load-bearing fields
34
+ return Boolean(
35
+ (identity.name || "").trim() ||
36
+ (identity.avatar || "").trim() ||
37
+ (identity.bio || "").trim()
38
+ );
39
+ }
40
+
41
+ /**
42
+ * If siteConfig.identity is stub-shaped AND homepageConfig.identity has
43
+ * rich data, copy the rich identity over. Idempotent.
44
+ *
45
+ * @param {object} Indiekit - Indiekit application instance
46
+ * @returns {Promise<boolean>} true if backfill ran, false otherwise
47
+ */
48
+ export async function maybeBackfillIdentity(Indiekit) {
49
+ const db = Indiekit.database;
50
+ if (!db) return false;
51
+
52
+ const siteDoc = await db.collection("siteConfig").findOne({ _id: "primary" });
53
+ if (!siteDoc) return false; // seed-from-env handles greenfield install
54
+ if (!isStubIdentity(siteDoc.identity)) return false;
55
+
56
+ const homepageDoc = await db.collection("homepageConfig").findOne({ _id: "homepage" });
57
+ if (!homepageDoc || !hasRichIdentity(homepageDoc.identity)) return false;
58
+
59
+ // Copy the rich identity over. saveSiteConfig handles the deepMerge + replaceOne
60
+ // so we preserve any other siteConfig keys (branding, navigation, features).
61
+ await saveSiteConfig(Indiekit, { identity: homepageDoc.identity }, "backfill-from-homepage");
62
+
63
+ console.log("[site-config] backfilled identity from homepageConfig (one-time migration)");
64
+ return true;
65
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rmdes/indiekit-endpoint-site-config",
3
- "version": "1.0.0-beta.1",
3
+ "version": "1.0.0-beta.2",
4
4
  "type": "module",
5
5
  "description": "Site identity, branding, layout, and feature flag configuration for Indiekit",
6
6
  "main": "index.js",
@@ -224,34 +224,34 @@
224
224
  <h2>{{ __("siteConfig.identity.profile.legend") }}</h2>
225
225
  <div class="hp-field-grid">
226
226
  <div class="field">
227
- <label class="field__label" for="identity-name">{{ __("siteConfig.identity.profile.name.label") }}</label>
228
- <input class="field__input" type="text" id="identity-name" name="identity-name" value="{{ config.identity.name or '' }}">
227
+ <label class="field__label" for="name">{{ __("siteConfig.identity.profile.name.label") }}</label>
228
+ <input class="field__input" type="text" id="name" name="name" value="{{ config.identity.name or '' }}">
229
229
  <p class="field__hint">{{ __("siteConfig.identity.profile.name.hint") }}</p>
230
230
  </div>
231
231
  <div class="field">
232
- <label class="field__label" for="identity-title">{{ __("siteConfig.identity.profile.title.label") }}</label>
233
- <input class="field__input" type="text" id="identity-title" name="identity-title" value="{{ config.identity.title or '' }}">
232
+ <label class="field__label" for="title">{{ __("siteConfig.identity.profile.title.label") }}</label>
233
+ <input class="field__input" type="text" id="title" name="title" value="{{ config.identity.title or '' }}">
234
234
  <p class="field__hint">{{ __("siteConfig.identity.profile.title.hint") }}</p>
235
235
  </div>
236
236
  <div class="field field--full">
237
- <label class="field__label" for="identity-avatar">{{ __("siteConfig.identity.profile.avatar.label") }}</label>
238
- <input class="field__input" type="url" id="identity-avatar" name="identity-avatar" value="{{ config.identity.avatar or '' }}">
237
+ <label class="field__label" for="avatar">{{ __("siteConfig.identity.profile.avatar.label") }}</label>
238
+ <input class="field__input" type="url" id="avatar" name="avatar" value="{{ config.identity.avatar or '' }}">
239
239
  <p class="field__hint">{{ __("siteConfig.identity.profile.avatar.hint") }}</p>
240
240
  </div>
241
241
  <div class="field">
242
- <label class="field__label" for="identity-pronoun">{{ __("siteConfig.identity.profile.pronoun.label") }}</label>
243
- <input class="field__input" type="text" id="identity-pronoun" name="identity-pronoun" value="{{ config.identity.pronoun or '' }}">
242
+ <label class="field__label" for="pronoun">{{ __("siteConfig.identity.profile.pronoun.label") }}</label>
243
+ <input class="field__input" type="text" id="pronoun" name="pronoun" value="{{ config.identity.pronoun or '' }}">
244
244
  <p class="field__hint">{{ __("siteConfig.identity.profile.pronoun.hint") }}</p>
245
245
  </div>
246
246
  <div class="field"></div>
247
247
  <div class="field field--full">
248
- <label class="field__label" for="identity-bio">{{ __("siteConfig.identity.profile.bio.label") }}</label>
249
- <textarea class="field__input" id="identity-bio" name="identity-bio" rows="3">{{ config.identity.bio or '' }}</textarea>
248
+ <label class="field__label" for="bio">{{ __("siteConfig.identity.profile.bio.label") }}</label>
249
+ <textarea class="field__input" id="bio" name="bio" rows="3">{{ config.identity.bio or '' }}</textarea>
250
250
  <p class="field__hint">{{ __("siteConfig.identity.profile.bio.hint") }}</p>
251
251
  </div>
252
252
  <div class="field field--full">
253
- <label class="field__label" for="identity-description">{{ __("siteConfig.identity.profile.description.label") }}</label>
254
- <textarea class="field__input" id="identity-description" name="identity-description" rows="3">{{ config.identity.description or '' }}</textarea>
253
+ <label class="field__label" for="description">{{ __("siteConfig.identity.profile.description.label") }}</label>
254
+ <textarea class="field__input" id="description" name="description" rows="3">{{ config.identity.description or '' }}</textarea>
255
255
  <p class="field__hint">{{ __("siteConfig.identity.profile.description.hint") }}</p>
256
256
  </div>
257
257
  </div>
@@ -262,17 +262,17 @@
262
262
  <h2>{{ __("siteConfig.identity.location.legend") }}</h2>
263
263
  <div class="hp-field-grid">
264
264
  <div class="field">
265
- <label class="field__label" for="identity-locality">{{ __("siteConfig.identity.location.locality.label") }}</label>
266
- <input class="field__input" type="text" id="identity-locality" name="identity-locality" value="{{ config.identity.locality or '' }}">
265
+ <label class="field__label" for="locality">{{ __("siteConfig.identity.location.locality.label") }}</label>
266
+ <input class="field__input" type="text" id="locality" name="locality" value="{{ config.identity.locality or '' }}">
267
267
  <p class="field__hint">{{ __("siteConfig.identity.location.locality.hint") }}</p>
268
268
  </div>
269
269
  <div class="field">
270
- <label class="field__label" for="identity-country">{{ __("siteConfig.identity.location.country.label") }}</label>
271
- <input class="field__input" type="text" id="identity-country" name="identity-country" value="{{ config.identity.country or '' }}">
270
+ <label class="field__label" for="country">{{ __("siteConfig.identity.location.country.label") }}</label>
271
+ <input class="field__input" type="text" id="country" name="country" value="{{ config.identity.country or '' }}">
272
272
  </div>
273
273
  <div class="field">
274
- <label class="field__label" for="identity-org">{{ __("siteConfig.identity.location.org.label") }}</label>
275
- <input class="field__input" type="text" id="identity-org" name="identity-org" value="{{ config.identity.org or '' }}">
274
+ <label class="field__label" for="org">{{ __("siteConfig.identity.location.org.label") }}</label>
275
+ <input class="field__input" type="text" id="org" name="org" value="{{ config.identity.org or '' }}">
276
276
  <p class="field__hint">{{ __("siteConfig.identity.location.org.hint") }}</p>
277
277
  </div>
278
278
  </div>
@@ -283,17 +283,17 @@
283
283
  <h2>{{ __("siteConfig.identity.contact.legend") }}</h2>
284
284
  <div class="hp-field-grid">
285
285
  <div class="field field--full">
286
- <label class="field__label" for="identity-url">{{ __("siteConfig.identity.contact.url.label") }}</label>
287
- <input class="field__input" type="url" id="identity-url" name="identity-url" value="{{ config.identity.url or '' }}">
286
+ <label class="field__label" for="url">{{ __("siteConfig.identity.contact.url.label") }}</label>
287
+ <input class="field__input" type="url" id="url" name="url" value="{{ config.identity.url or '' }}">
288
288
  <p class="field__hint">{{ __("siteConfig.identity.contact.url.hint") }}</p>
289
289
  </div>
290
290
  <div class="field">
291
- <label class="field__label" for="identity-email">{{ __("siteConfig.identity.contact.email.label") }}</label>
292
- <input class="field__input" type="email" id="identity-email" name="identity-email" value="{{ config.identity.email or '' }}">
291
+ <label class="field__label" for="email">{{ __("siteConfig.identity.contact.email.label") }}</label>
292
+ <input class="field__input" type="email" id="email" name="email" value="{{ config.identity.email or '' }}">
293
293
  </div>
294
294
  <div class="field">
295
- <label class="field__label" for="identity-keyUrl">{{ __("siteConfig.identity.contact.keyUrl.label") }}</label>
296
- <input class="field__input" type="url" id="identity-keyUrl" name="identity-keyUrl" value="{{ config.identity.keyUrl or '' }}">
295
+ <label class="field__label" for="keyUrl">{{ __("siteConfig.identity.contact.keyUrl.label") }}</label>
296
+ <input class="field__input" type="url" id="keyUrl" name="keyUrl" value="{{ config.identity.keyUrl or '' }}">
297
297
  <p class="field__hint">{{ __("siteConfig.identity.contact.keyUrl.hint") }}</p>
298
298
  </div>
299
299
  </div>
@@ -303,8 +303,8 @@
303
303
  <section class="hp-section">
304
304
  <h2>{{ __("siteConfig.identity.categories.legend") }}</h2>
305
305
  <div class="field">
306
- <label class="field__label" for="identity-categories">{{ __("siteConfig.identity.categories.tags.label") }}</label>
307
- <input class="field__input" type="text" id="identity-categories" name="identity-categories" value="{{ config.identity.categories | join(', ') if config.identity.categories else '' }}">
306
+ <label class="field__label" for="categories">{{ __("siteConfig.identity.categories.tags.label") }}</label>
307
+ <input class="field__input" type="text" id="categories" name="categories" value="{{ config.identity.categories | join(', ') if config.identity.categories else '' }}">
308
308
  <p class="field__hint">{{ __("siteConfig.identity.categories.tags.hint") }}</p>
309
309
  </div>
310
310
  </section>