mnfst 0.5.30 → 0.5.32

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.
@@ -18,7 +18,8 @@ async function ensureManifest() {
18
18
  }
19
19
 
20
20
  try {
21
- const response = await fetch('/manifest.json');
21
+ const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
22
+ const response = await fetch(manifestUrl);
22
23
  return await response.json();
23
24
  } catch (error) {
24
25
  return null;
@@ -221,6 +222,16 @@ function initializeAuthStore() {
221
222
  _updatingTeam: null, // Team ID being updated (null when not updating)
222
223
  _deletingTeam: null, // Team ID being deleted (null when not deleting)
223
224
  _creatingTeam: false, // Boolean flag for team creation
225
+ // Stub team convenience methods (replaced by teams.convenience.js; prevent "is not a function" before init)
226
+ isCreatingTeam() { return this._creatingTeam === true; },
227
+ isUpdatingTeam() { return false; },
228
+ isDeletingTeam() { return false; },
229
+ isInvitingMember() { return false; },
230
+ isUpdatingMember() { return false; },
231
+ isDeletingMember() { return false; },
232
+ isUpdatingRole() { return false; },
233
+ isDeletingRole() { return false; },
234
+ isCreatingRole() { return false; },
224
235
  // Member operation-specific loading states
225
236
  _updatingMember: null, // Membership ID being updated (null when not updating)
226
237
  _deletingMember: null, // Membership ID being deleted (null when not deleting)
@@ -1,5 +1,51 @@
1
1
  /* Manifest Components */
2
2
 
3
+ // Base URL for manifest-relative paths (e.g. "../" when viewing dist/index.html). Used by component loader, data loaders, localization.
4
+ window.getManifestBase = function getManifestBase() {
5
+ const href = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
6
+ const lastSlash = href.lastIndexOf('/');
7
+ return lastSlash >= 0 ? href.slice(0, lastSlash + 1) : '/';
8
+ };
9
+
10
+ // Absolute pathname prefix for the app root (e.g. "/src/dist"). Used by router for links and route matching.
11
+ // Prerender injects <meta name="manifest:router-base" content="/path"> from manifest.prerender.routerBase or root+output. If present, use it; else fall back to depth or manifest link.
12
+ window.getManifestBasePath = function getManifestBasePath() {
13
+ const baseMeta = document.querySelector('meta[name="manifest:router-base"]');
14
+ const content = baseMeta?.getAttribute('content');
15
+ if (content != null && content !== '') {
16
+ const base = '/' + String(content).replace(/^\/+|\/+$/g, '').trim();
17
+ return base || '';
18
+ }
19
+ const meta = document.querySelector('meta[name="manifest:router-base-depth"]');
20
+ const depth = meta ? parseInt(meta.getAttribute('content'), 10) : NaN;
21
+ if (!Number.isNaN(depth) && depth >= 0) {
22
+ const pathname = (window.location.pathname || '/').replace(/\/$/, '') || '/';
23
+ const segments = pathname.split('/').filter(Boolean);
24
+ if (depth === 0) {
25
+ try {
26
+ const link = document.querySelector('link[rel="manifest"]');
27
+ const href = (link?.getAttribute('href')) || '/manifest.json';
28
+ const url = new URL(href, window.location.href);
29
+ const basePath = url.pathname.replace(/\/[^/]*$/, '') || '/';
30
+ return basePath === '/' ? '' : basePath;
31
+ } catch {
32
+ return '';
33
+ }
34
+ }
35
+ const keep = Math.max(0, segments.length - depth);
36
+ return keep === 0 ? '' : '/' + segments.slice(0, keep).join('/');
37
+ }
38
+ try {
39
+ const link = document.querySelector('link[rel="manifest"]');
40
+ const href = (link?.getAttribute('href')) || '/manifest.json';
41
+ const url = new URL(href, window.location.href);
42
+ const pathname = url.pathname.replace(/\/[^/]*$/, '') || '/';
43
+ return pathname === '/' ? '' : pathname;
44
+ } catch {
45
+ return '';
46
+ }
47
+ };
48
+
3
49
  // Components registry
4
50
  window.ManifestComponentsRegistry = {
5
51
  manifest: null,
@@ -78,9 +124,11 @@ window.ManifestComponentsLoader = {
78
124
  console.warn('[Manifest] Component', name, 'not found in manifest.');
79
125
  return null;
80
126
  }
127
+ const base = (typeof window.getManifestBase === 'function' ? window.getManifestBase() : '') || '/';
128
+ const url = path.startsWith('/') || path.startsWith('http') ? path : base + path;
81
129
  const promise = (async () => {
82
130
  try {
83
- const response = await fetch('/' + path);
131
+ const response = await fetch(url);
84
132
  if (!response.ok) {
85
133
  console.warn('[Manifest] HTML file not found for component', name, 'at path:', path, '(HTTP', response.status + ')');
86
134
  return null;
@@ -10,7 +10,8 @@ async function ensureManifest() {
10
10
  }
11
11
 
12
12
  try {
13
- const response = await fetch('/manifest.json');
13
+ const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
14
+ const response = await fetch(manifestUrl);
14
15
  return await response.json();
15
16
  } catch (error) {
16
17
  console.error('[Manifest Data] Failed to load manifest:', error);
@@ -1561,30 +1562,41 @@ function parseCSVToNestedObject(csvText, options = {}) {
1561
1562
  }
1562
1563
  }
1563
1564
 
1565
+ // Load a local file (JSON, YAML, CSV). Resolves manifest-relative paths when viewing from dist.
1566
+ function resolveDataPath(filePath) {
1567
+ if (!filePath || typeof filePath !== 'string' || filePath.startsWith('http')) {
1568
+ return filePath;
1569
+ }
1570
+ const base = typeof window.getManifestBase === 'function' ? window.getManifestBase() : '';
1571
+ const pathOnly = filePath.startsWith('/') ? filePath.slice(1) : filePath;
1572
+ return base ? base + pathOnly : filePath;
1573
+ }
1574
+
1564
1575
  // Load a local file (JSON, YAML, CSV)
1565
1576
  async function loadLocalFile(filePath, options = {}) {
1566
- const response = await fetch(filePath);
1577
+ const resolved = resolveDataPath(filePath);
1578
+ const response = await fetch(resolved);
1567
1579
 
1568
1580
  // Check if file exists
1569
1581
  if (!response.ok) {
1570
- throw new Error(`[Manifest Data] File not found: ${filePath} (${response.status})`);
1582
+ throw new Error(`[Manifest Data] File not found: ${resolved} (${response.status})`);
1571
1583
  }
1572
1584
 
1573
1585
  const contentType = response.headers.get('content-type');
1574
1586
 
1575
1587
  // Handle CSV files
1576
- if (filePath.endsWith('.csv') || contentType?.includes('text/csv')) {
1588
+ if (resolved.endsWith('.csv') || contentType?.includes('text/csv')) {
1577
1589
  const text = await response.text();
1578
1590
  const csvParser = await loadCSVParser();
1579
1591
  // Pass currentLocale if provided in options
1580
1592
  return parseCSVToNestedObject(text, { currentLocale: options.currentLocale });
1581
1593
  }
1582
1594
  // Handle JSON files
1583
- else if (contentType?.includes('application/json') || filePath.endsWith('.json')) {
1595
+ else if (contentType?.includes('application/json') || resolved.endsWith('.json')) {
1584
1596
  return await response.json();
1585
1597
  }
1586
1598
  // Handle YAML files
1587
- else if (contentType?.includes('text/yaml') || filePath.endsWith('.yaml') || filePath.endsWith('.yml')) {
1599
+ else if (contentType?.includes('text/yaml') || resolved.endsWith('.yaml') || resolved.endsWith('.yml')) {
1588
1600
  const text = await response.text();
1589
1601
  const yamlLib = await loadYamlLibrary();
1590
1602
  return yamlLib.load(text);
@@ -1612,6 +1624,7 @@ window.ManifestDataLoaders = {
1612
1624
  loadCSVParser,
1613
1625
  deepMergeWithFallback,
1614
1626
  parseCSVToNestedObject,
1627
+ resolveDataPath,
1615
1628
  loadLocalFile
1616
1629
  };
1617
1630
 
@@ -4868,18 +4881,24 @@ if (!window.ManifestDataRouteProxyUpdateQueue) {
4868
4881
 
4869
4882
  // Debounce all updates
4870
4883
  this.timeout = setTimeout(() => {
4871
- const proxiesToUpdate = Array.from(this.pending);
4872
- this.pending.clear();
4873
-
4874
- // Update all pending proxies
4875
- proxiesToUpdate.forEach(updateFn => {
4876
- try {
4877
- updateFn();
4878
- } catch (error) {
4879
- console.error('[Manifest Data] Error updating route proxy:', error);
4880
- }
4881
- });
4884
+ this.flushSync();
4882
4885
  }, 0);
4886
+ },
4887
+ // Run all pending proxy updates immediately (e.g. on route change so content updates without refresh)
4888
+ flushSync() {
4889
+ if (this.timeout) {
4890
+ clearTimeout(this.timeout);
4891
+ this.timeout = null;
4892
+ }
4893
+ const proxiesToUpdate = Array.from(this.pending);
4894
+ this.pending.clear();
4895
+ proxiesToUpdate.forEach(updateFn => {
4896
+ try {
4897
+ updateFn();
4898
+ } catch (error) {
4899
+ console.error('[Manifest Data] Error updating route proxy:', error);
4900
+ }
4901
+ });
4883
4902
  }
4884
4903
  };
4885
4904
  }
@@ -11538,8 +11557,14 @@ function setupUrlChangeListeners() {
11538
11557
 
11539
11558
  // Listen to router's route change event (primary integration point)
11540
11559
  window.addEventListener('manifest:route-change', (event) => {
11541
- const newUrl = event.detail?.to || window.location.pathname;
11560
+ const newUrl = event.detail?.to ?? window.ManifestRoutingNavigation?.getCurrentRoute?.() ?? window.location.pathname;
11542
11561
  updateCurrentUrl(newUrl);
11562
+ // Flush route proxies after other listeners have queued their updates, so $x.*.$route('path') content updates without refresh
11563
+ setTimeout(() => {
11564
+ if (window.ManifestDataRouteProxyUpdateQueue?.flushSync) {
11565
+ window.ManifestDataRouteProxyUpdateQueue.flushSync();
11566
+ }
11567
+ }, 0);
11543
11568
  });
11544
11569
 
11545
11570
  // Also listen for popstate (browser back/forward)
@@ -6,7 +6,7 @@ function initializeDropdownPlugin() {
6
6
  function ensureAlpineContext() {
7
7
  const body = document.body;
8
8
  if (!body.hasAttribute('x-data')) {
9
- body.setAttribute('x-data', '{}');
9
+ body.setAttribute('x-data', '{ tab: \'local-data\' }');
10
10
  }
11
11
  }
12
12
 
@@ -161,7 +161,10 @@ function initializeLocalizationPlugin() {
161
161
  // Check for single-file multi-locale CSV (e.g., {"locales": "/path/to/file.csv"})
162
162
  if (collection.locales && typeof collection.locales === 'string' && collection.locales.endsWith('.csv')) {
163
163
  try {
164
- const csvResponse = await fetch(collection.locales);
164
+ const base = typeof window.getManifestBase === 'function' ? window.getManifestBase() : '';
165
+ const localesPath = collection.locales.startsWith('/') ? collection.locales.slice(1) : collection.locales;
166
+ const localesUrl = (collection.locales.startsWith('http')) ? collection.locales : (base + localesPath);
167
+ const csvResponse = await fetch(localesUrl);
165
168
  if (csvResponse.ok) {
166
169
  const csvText = await csvResponse.text();
167
170
  // Parse CSV header to get locale columns
@@ -195,7 +198,10 @@ function initializeLocalizationPlugin() {
195
198
  } else if (typeof collection === 'string' && collection.endsWith('.csv')) {
196
199
  // Simple CSV file path - check if it has locale columns
197
200
  try {
198
- const csvResponse = await fetch(collection);
201
+ const base = typeof window.getManifestBase === 'function' ? window.getManifestBase() : '';
202
+ const csvPath = collection.startsWith('/') ? collection.slice(1) : collection;
203
+ const csvUrl = collection.startsWith('http') ? collection : (base + csvPath);
204
+ const csvResponse = await fetch(csvUrl);
199
205
  if (csvResponse.ok) {
200
206
  const csvText = await csvResponse.text();
201
207
  const lines = csvText.split('\n').filter(line => line.trim());
@@ -466,12 +466,12 @@ async function initializeMarkdownPlugin() {
466
466
  // If it's a file path, fetch the content (with caching)
467
467
  if (isFilePath) {
468
468
  try {
469
- // Ensure the path is absolute from project root
469
+ // Resolve path: relative paths are relative to manifest base (project root), not document root
470
470
  let resolvedPath = pathOrContent;
471
-
472
- // If it's a relative path (doesn't start with /), make it absolute from root
473
471
  if (!pathOrContent.startsWith('/')) {
474
- resolvedPath = '/' + pathOrContent;
472
+ const base = (typeof window.getManifestBase === 'function' ? window.getManifestBase() : '') || '';
473
+ const basePath = base.replace(/\/$/, '') || '';
474
+ resolvedPath = (basePath ? basePath + '/' : '/') + pathOrContent;
475
475
  }
476
476
 
477
477
  // Check cache first
@@ -159,13 +159,35 @@ window.ManifestRoutingPosition = {
159
159
 
160
160
  // Router navigation
161
161
 
162
- // Current route state
162
+ // Current route state (logical path, e.g. /gadget; not full pathname when app is in a subpath)
163
163
  let currentRoute = '/';
164
164
  let isInternalNavigation = false;
165
165
 
166
+ function getBasePath() {
167
+ return (typeof window.getManifestBasePath === 'function' ? window.getManifestBasePath() : '') || '';
168
+ }
169
+
170
+ function pathnameToLogical(pathname) {
171
+ const base = getBasePath();
172
+ if (!base) {
173
+ const p = (pathname || '/').replace(/\/$/, '') || '/';
174
+ if (p === '/' || p === '/index.html' || p === '/index') return '/';
175
+ return p.startsWith('/') ? p : '/' + p;
176
+ }
177
+ if (pathname === base || pathname === base + '/') return '/';
178
+ if (pathname.startsWith(base + '/')) {
179
+ let logical = pathname.slice(base.length) || '/';
180
+ if (logical === '/index.html' || logical === '/index') logical = '/';
181
+ return logical;
182
+ }
183
+ if (pathname === base + '/index.html' || pathname === base + '/index') return '/';
184
+ return pathname;
185
+ }
186
+
166
187
  // Handle route changes
167
188
  async function handleRouteChange() {
168
- const newRoute = window.location.pathname;
189
+ const pathname = window.location.pathname;
190
+ const newRoute = pathnameToLogical(pathname);
169
191
  if (newRoute === currentRoute) return;
170
192
 
171
193
  currentRoute = newRoute;
@@ -215,6 +237,36 @@ async function handleRouteChange() {
215
237
  }));
216
238
  }
217
239
 
240
+ // Resolve internal link to absolute pathname for pushState. Relative hrefs (e.g. "gadget") are resolved against the app base, not the current URL, so we never get additive paths like /src/dist/widget/gadget/widget/...
241
+ function resolveHref(href) {
242
+ if (!href || href.startsWith('#') || href.startsWith('mailto:') || href.startsWith('tel:')) return href;
243
+ try {
244
+ const base = getBasePath();
245
+ const baseUrl = base ? (window.location.origin + base + '/') : (window.location.origin + '/');
246
+ const url = new URL(href, baseUrl);
247
+ if (url.origin !== window.location.origin) return href;
248
+ let path = url.pathname.replace(/\/$/, '') || '/';
249
+ if (!base) return path.startsWith('/') ? path : '/' + path;
250
+ if (path === base || path.startsWith(base + '/')) return path;
251
+ // path may be above base (e.g. /gadget when base is /src/dist) or unrelated; take only the route part and put under base so we never stack.
252
+ if (path.startsWith('/')) {
253
+ const pathSegs = path.split('/').filter(Boolean);
254
+ const baseSegs = base.split('/').filter(Boolean);
255
+ let i = 0;
256
+ while (i < baseSegs.length && i < pathSegs.length && baseSegs[i] === pathSegs[i]) i++;
257
+ const routeSegs = pathSegs.slice(i);
258
+ if (routeSegs.length) return base + '/' + routeSegs.join('/');
259
+ }
260
+ const out = base + (path.startsWith('/') ? path : '/' + path);
261
+ return out.startsWith('/') ? out : '/' + out;
262
+ } catch {
263
+ const base = getBasePath();
264
+ const safe = (href || '').trim();
265
+ if (!safe) return base || '/';
266
+ return base ? (base + (safe.startsWith('/') ? safe : '/' + safe)) : (safe.startsWith('/') ? safe : '/' + safe);
267
+ }
268
+ }
269
+
218
270
  // Intercept link clicks to prevent page reloads
219
271
  function interceptLinkClicks() {
220
272
  // Use capture phase to intercept before other handlers
@@ -247,6 +299,7 @@ function interceptLinkClicks() {
247
299
  // Handle links with both route and anchor (e.g., /page#section)
248
300
  if (href.includes('#')) {
249
301
  const [path, hash] = href.split('#');
302
+ const fullHref = resolveHref(path) + (hash ? '#' + hash : '');
250
303
 
251
304
  event.preventDefault();
252
305
  event.stopPropagation();
@@ -255,8 +308,8 @@ function interceptLinkClicks() {
255
308
  // Set flag to prevent recursive calls
256
309
  isInternalNavigation = true;
257
310
 
258
- // Update URL without page reload
259
- history.pushState(null, '', href);
311
+ // Update URL without page reload (use base path when app is in a subpath)
312
+ history.pushState(null, '', fullHref);
260
313
 
261
314
  // Handle route change (but don't scroll to top since there's an anchor)
262
315
  handleRouteChange();
@@ -282,8 +335,9 @@ function interceptLinkClicks() {
282
335
  // Set flag to prevent recursive calls
283
336
  isInternalNavigation = true;
284
337
 
285
- // Update URL without page reload
286
- history.pushState(null, '', href);
338
+ // Update URL without page reload (use base path when app is in a subpath, e.g. /src/dist/gadget)
339
+ const fullHref = resolveHref(href);
340
+ history.pushState(null, '', fullHref);
287
341
 
288
342
  // Handle route change
289
343
  handleRouteChange();
@@ -296,8 +350,8 @@ function interceptLinkClicks() {
296
350
 
297
351
  // Initialize navigation
298
352
  function initializeNavigation() {
299
- // Set initial route
300
- currentRoute = window.location.pathname;
353
+ // Set initial route (logical path for matching)
354
+ currentRoute = pathnameToLogical(window.location.pathname);
301
355
 
302
356
  // Intercept link clicks
303
357
  interceptLinkClicks();
@@ -323,7 +377,10 @@ if (document.readyState === 'loading') {
323
377
  // Export navigation interface
324
378
  window.ManifestRoutingNavigation = {
325
379
  initialize: initializeNavigation,
326
- getCurrentRoute: () => currentRoute
380
+ getCurrentRoute: () => currentRoute,
381
+ getBasePath,
382
+ resolveHref,
383
+ pathnameToLogical
327
384
  };
328
385
 
329
386
  // Router visibility
@@ -456,8 +513,8 @@ function initializeVisibility() {
456
513
  // Add x-cloak to route elements to prevent flash
457
514
  addXCloakToRouteElements();
458
515
 
459
- // Process initial visibility
460
- const currentPath = window.location.pathname;
516
+ // Process initial visibility (use logical path when app is in a subpath)
517
+ const currentPath = window.ManifestRoutingNavigation?.getCurrentRoute() ?? window.location.pathname;
461
518
  const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/|\/$/g, '');
462
519
  processRouteVisibility(normalizedPath);
463
520
 
@@ -471,7 +528,7 @@ function initializeVisibility() {
471
528
  // Add x-cloak to any new route elements
472
529
  addXCloakToRouteElements();
473
530
 
474
- const currentPath = window.location.pathname;
531
+ const currentPath = window.ManifestRoutingNavigation?.getCurrentRoute() ?? window.location.pathname;
475
532
  const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/|\/$/g, '');
476
533
  processRouteVisibility(normalizedPath);
477
534
  });
@@ -687,7 +744,7 @@ function initializeHeadContent() {
687
744
  function processHeadContentAfterComponentsReady() {
688
745
  // Process initial head content after a longer delay to let components settle
689
746
  setTimeout(() => {
690
- const currentPath = window.location.pathname;
747
+ const currentPath = window.ManifestRoutingNavigation?.getCurrentRoute() ?? window.location.pathname;
691
748
  const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/|\/$/g, '');
692
749
 
693
750
  // Debug: Check if about component exists
@@ -706,7 +763,7 @@ function initializeHeadContent() {
706
763
 
707
764
  // Function to process head content immediately (for projects without components)
708
765
  function processHeadContentImmediately() {
709
- const currentPath = window.location.pathname;
766
+ const currentPath = window.ManifestRoutingNavigation?.getCurrentRoute() ?? window.location.pathname;
710
767
  const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/|\/$/g, '');
711
768
  processAllHeadContent(normalizedPath);
712
769
  }
@@ -740,7 +797,7 @@ function initializeHeadContent() {
740
797
  // Wait a bit for components to settle after route change
741
798
  setTimeout(() => {
742
799
  // Process head content immediately to catch components before they're reverted
743
- const currentPath = window.location.pathname;
800
+ const currentPath = window.ManifestRoutingNavigation?.getCurrentRoute() ?? window.location.pathname;
744
801
  const normalizedPath = currentPath === '/' ? '/' : currentPath.replace(/^\/|\/$/g, '');
745
802
 
746
803
  // Debug: Check if about component exists
@@ -1126,9 +1183,9 @@ function initializeRouterMagic() {
1126
1183
  return;
1127
1184
  }
1128
1185
 
1129
- // Create a reactive object for route data
1186
+ // Create a reactive object for route data (use logical path when app is in a subpath)
1130
1187
  const route = Alpine.reactive({
1131
- current: window.location.pathname,
1188
+ current: window.ManifestRoutingNavigation?.getCurrentRoute() || window.location.pathname,
1132
1189
  segments: [],
1133
1190
  params: {},
1134
1191
  matches: null
@@ -1136,7 +1193,7 @@ function initializeRouterMagic() {
1136
1193
 
1137
1194
  // Update route when route changes
1138
1195
  const updateRoute = () => {
1139
- const currentRoute = window.ManifestRoutingNavigation?.getCurrentRoute() || window.location.pathname;
1196
+ const currentRoute = window.ManifestRoutingNavigation?.getCurrentRoute() ?? window.location.pathname;
1140
1197
 
1141
1198
  // Strip localization codes and other injected segments to get the logical route
1142
1199
  let logicalRoute = currentRoute;
@@ -294,16 +294,11 @@ function ensureTooltipPluginInitialized() {
294
294
 
295
295
  tooltipPluginInitialized = true;
296
296
  initializeTooltipPlugin();
297
-
298
- // If elements with x-tooltip already exist, process them
299
- if (window.Alpine && typeof window.Alpine.initTree === 'function') {
300
- const existingTooltipElements = document.querySelectorAll('[x-tooltip]');
301
- existingTooltipElements.forEach(el => {
302
- if (!el.__x) {
303
- window.Alpine.initTree(el);
304
- }
305
- });
306
- }
297
+ // Do not call Alpine.initTree() on [x-tooltip] elements here. That initializes
298
+ // them in isolation and breaks scope (e.g. "tab is not defined"). Alpine will
299
+ // process the full tree from the root, so [x-tooltip] elements get the correct
300
+ // scope. Dynamically loaded components are already initialized by the component
301
+ // processor with initTree on the swapped-in root.
307
302
  }
308
303
 
309
304
  // Expose on window for loader to call if needed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.30",
3
+ "version": "0.5.32",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter"
@@ -19,6 +19,9 @@
19
19
  "start:src": "cd src && browser-sync start --config bs-config.js",
20
20
  "start:docs": "cd docs && browser-sync start --config bs-config.js",
21
21
  "start:starter": "cd templates/starter && browser-sync start --config bs-config.js --port 3001",
22
+ "prerender": "node src/prerender.mjs --root src",
23
+ "prerender:docs": "node src/prerender.mjs --root docs",
24
+ "prerender:starter": "node src/prerender.mjs --root templates/starter",
22
25
  "publish:starter": "cd packages/create-starter && npm publish --auth-type=web",
23
26
  "prepublishOnly": "npm run build",
24
27
  "test": "echo 'No tests configured'",
@@ -30,8 +33,8 @@
30
33
  "@rollup/plugin-terser": "^0.4.4",
31
34
  "browser-sync": "^3.0.3",
32
35
  "cssnano": "^7.1.1",
33
- "fs-extra": "^11.2.0",
34
- "prompts": "^2.4.2",
36
+ "glob": "^11.0.2",
37
+ "puppeteer": "^24.15.0",
35
38
  "rimraf": "^5.0.0",
36
39
  "rollup": "^4.24.2"
37
40
  },
@@ -55,9 +58,5 @@
55
58
  },
56
59
  "bugs": {
57
60
  "url": "https://github.com/andrewmatlock/manifest/issues"
58
- },
59
- "dependencies": {
60
- "commander": "^14.0.1",
61
- "glob": "^11.0.2"
62
61
  }
63
62
  }