mnfst 0.5.21 → 0.5.23

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.
@@ -8,11 +8,14 @@
8
8
 
9
9
  /* Auth config */
10
10
 
11
- // Load manifest if not already loaded
11
+ // Load manifest if not already loaded (loader may set __manifestLoaded / registry.manifest)
12
12
  async function ensureManifest() {
13
13
  if (window.ManifestComponentsRegistry?.manifest) {
14
14
  return window.ManifestComponentsRegistry.manifest;
15
15
  }
16
+ if (window.__manifestLoaded) {
17
+ return window.__manifestLoaded;
18
+ }
16
19
 
17
20
  try {
18
21
  const response = await fetch('/manifest.json');
@@ -47,11 +47,6 @@ async function loadHighlightJS() {
47
47
  return hljsPromise;
48
48
  }
49
49
 
50
- // Preload highlight.js as soon as script loads
51
- loadHighlightJS().catch(() => {
52
- // Silently ignore errors during preload
53
- });
54
-
55
50
  // Optional optimization: Configure utilities plugin if present
56
51
  if (window.ManifestUtilities) {
57
52
  // Tell utilities plugin to ignore code-related DOM changes and classes
@@ -798,19 +793,24 @@ function initializeCodePlugin() {
798
793
  customElements.define('x-code-group', XCodeGroupElement);
799
794
  }
800
795
 
801
- // Process existing code blocks
802
- await processExistingCodeBlocks();
803
-
804
- // Listen for markdown plugin conversions
805
- document.addEventListener('manifest:code-blocks-converted', async () => {
806
- await processExistingCodeBlocks();
807
- });
808
-
809
- // Also listen for the event on the document body for better coverage
810
- document.body.addEventListener('manifest:code-blocks-converted', async () => {
811
- await processExistingCodeBlocks();
812
- });
796
+ // Listen for markdown plugin conversions (always process when new blocks appear)
797
+ const runProcess = () => processExistingCodeBlocks();
798
+ document.addEventListener('manifest:code-blocks-converted', runProcess);
799
+ if (document.body) {
800
+ document.body.addEventListener('manifest:code-blocks-converted', runProcess);
801
+ }
813
802
 
803
+ // Defer loading highlight.js until first code block is in view (or process immediately if none to observe)
804
+ const codeTargets = document.querySelectorAll('pre > code:not(.hljs):not([data-highlighted="yes"]), x-code:not([data-highlighted="yes"])');
805
+ if (codeTargets.length === 0) {
806
+ return;
807
+ }
808
+ const io = new IntersectionObserver((entries) => {
809
+ if (!entries.some(e => e.isIntersecting)) return;
810
+ io.disconnect();
811
+ runProcess();
812
+ }, { rootMargin: '100px', threshold: 0 });
813
+ codeTargets.forEach(el => io.observe(el));
814
814
  } catch (error) {
815
815
  console.error('[Manifest] Failed to initialize code plugin:', error);
816
816
  }
@@ -6,40 +6,50 @@ window.ManifestComponentsRegistry = {
6
6
  registered: new Set(),
7
7
  preloaded: [],
8
8
  initialize() {
9
- // Load manifest.json synchronously
10
- try {
11
- const req = new XMLHttpRequest();
12
- req.open('GET', '/manifest.json?t=' + Date.now(), false);
13
- req.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
14
- req.setRequestHeader('Pragma', 'no-cache');
15
- req.setRequestHeader('Expires', '0');
16
- req.send(null);
17
- if (req.status === 200) {
18
- this.manifest = JSON.parse(req.responseText);
19
- // Register all components from manifest
20
- const allComponents = [
21
- ...(this.manifest?.preloadedComponents || []),
22
- ...(this.manifest?.components || [])
23
- ];
24
- allComponents.forEach(path => {
25
- const name = path.split('/').pop().replace('.html', '');
26
- this.registered.add(name);
27
- });
28
- this.preloaded = (this.manifest?.preloadedComponents || []).map(path => path.split('/').pop().replace('.html', ''));
29
- } else {
30
- console.warn('[Manifest] Failed to load manifest.json (HTTP', req.status + ')');
9
+ // Use loader-provided manifest if set; otherwise load synchronously (standalone)
10
+ let manifest = window.__manifestLoaded || this.manifest;
11
+ if (!manifest) {
12
+ try {
13
+ const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
14
+ const req = new XMLHttpRequest();
15
+ req.open('GET', manifestUrl + (manifestUrl.includes('?') ? '&' : '?') + 't=' + Date.now(), false);
16
+ req.setRequestHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
17
+ req.setRequestHeader('Pragma', 'no-cache');
18
+ req.setRequestHeader('Expires', '0');
19
+ req.send(null);
20
+ if (req.status === 200) {
21
+ manifest = JSON.parse(req.responseText);
22
+ } else {
23
+ console.warn('[Manifest] Failed to load manifest.json (HTTP', req.status + ')');
24
+ }
25
+ } catch (e) {
26
+ console.warn('[Manifest] Failed to load manifest.json:', e.message);
31
27
  }
32
- } catch (e) {
33
- console.warn('[Manifest] Failed to load manifest.json:', e.message);
28
+ }
29
+ if (manifest) {
30
+ this.manifest = manifest;
31
+ const allComponents = [
32
+ ...(this.manifest?.preloadedComponents || []),
33
+ ...(this.manifest?.components || [])
34
+ ];
35
+ allComponents.forEach(path => {
36
+ const name = path.split('/').pop().replace('.html', '');
37
+ this.registered.add(name);
38
+ });
39
+ this.preloaded = (this.manifest?.preloadedComponents || []).map(path => path.split('/').pop().replace('.html', ''));
34
40
  }
35
41
  }
36
42
  };
37
43
 
38
44
  // Components loader
45
+ // Uses cache for resolved content and _loading for in-flight promises so duplicate
46
+ // loadComponent(name) calls share one network request.
39
47
  window.ManifestComponentsLoader = {
40
48
  cache: {},
49
+ _loading: {},
41
50
  initialize() {
42
51
  this.cache = {};
52
+ this._loading = {};
43
53
  // Preload components listed in registry.preloaded
44
54
  const registry = window.ManifestComponentsRegistry;
45
55
  if (registry && Array.isArray(registry.preloaded)) {
@@ -54,6 +64,9 @@ window.ManifestComponentsLoader = {
54
64
  if (this.cache[name]) {
55
65
  return this.cache[name];
56
66
  }
67
+ if (this._loading[name]) {
68
+ return this._loading[name];
69
+ }
57
70
  const registry = window.ManifestComponentsRegistry;
58
71
  if (!registry || !registry.manifest) {
59
72
  console.warn('[Manifest] Manifest not loaded, cannot load component:', name);
@@ -65,19 +78,25 @@ window.ManifestComponentsLoader = {
65
78
  console.warn('[Manifest] Component', name, 'not found in manifest.');
66
79
  return null;
67
80
  }
68
- try {
69
- const response = await fetch('/' + path);
70
- if (!response.ok) {
71
- console.warn('[Manifest] HTML file not found for component', name, 'at path:', path, '(HTTP', response.status + ')');
81
+ const promise = (async () => {
82
+ try {
83
+ const response = await fetch('/' + path);
84
+ if (!response.ok) {
85
+ console.warn('[Manifest] HTML file not found for component', name, 'at path:', path, '(HTTP', response.status + ')');
86
+ return null;
87
+ }
88
+ const content = await response.text();
89
+ this.cache[name] = content;
90
+ return content;
91
+ } catch (error) {
92
+ console.warn('[Manifest] Failed to load component', name, 'from', path + ':', error.message);
72
93
  return null;
94
+ } finally {
95
+ delete this._loading[name];
73
96
  }
74
- const content = await response.text();
75
- this.cache[name] = content;
76
- return content;
77
- } catch (error) {
78
- console.warn('[Manifest] Failed to load component', name, 'from', path + ':', error.message);
79
- return null;
80
- }
97
+ })();
98
+ this._loading[name] = promise;
99
+ return promise;
81
100
  }
82
101
  };
83
102
 
@@ -716,19 +735,27 @@ function initializeComponents() {
716
735
  window.dispatchEvent(new CustomEvent('manifest:components-ready'));
717
736
  }
718
737
 
719
- // Wait for data plugin to be ready (including content pre-load) before initializing components
720
- // manifest:data-ready fires after critical data sources are loaded, so $x.content is ready when components render
738
+ // When data plugin is loaded: wait for manifest:data-ready so $x.content is ready before components render.
739
+ // When data plugin is absent: init immediately (no artificial delay).
721
740
  function waitForDataThenInitialize() {
741
+ const hasDataPlugin = typeof window.ManifestDataConfig !== 'undefined';
742
+
743
+ if (!hasDataPlugin) {
744
+ initializeComponents();
745
+ return;
746
+ }
747
+
722
748
  window.addEventListener('manifest:data-ready', () => {
723
749
  initializeComponents();
724
750
  }, { once: true });
725
751
 
726
- // Fallback: if data plugin doesn't fire event within reasonable time, initialize anyway
752
+ // Fallback: if data plugin never fires (e.g. slow network, error), initialize anyway
753
+ const fallbackMs = 5000;
727
754
  setTimeout(() => {
728
755
  if (!window.__manifestComponentsInitialized) {
729
756
  initializeComponents();
730
757
  }
731
- }, 1000);
758
+ }, fallbackMs);
732
759
  }
733
760
 
734
761
  if (document.readyState === 'loading') {
@@ -1,10 +1,13 @@
1
1
  /* Manifest Data Sources - Configuration */
2
2
 
3
- // Load manifest if not already loaded
3
+ // Load manifest if not already loaded (loader may set __manifestLoaded / registry.manifest)
4
4
  async function ensureManifest() {
5
5
  if (window.ManifestComponentsRegistry?.manifest) {
6
6
  return window.ManifestComponentsRegistry.manifest;
7
7
  }
8
+ if (window.__manifestLoaded) {
9
+ return window.__manifestLoaded;
10
+ }
8
11
 
9
12
  try {
10
13
  const response = await fetch('/manifest.json');
@@ -1148,10 +1151,38 @@ let yamlLoadingPromise = null;
1148
1151
  let papaparse = null;
1149
1152
  let csvLoadingPromise = null;
1150
1153
 
1154
+ // Collect all path-like strings from manifest.data (recursive; includes nested locale objects)
1155
+ function collectDataPaths(manifest) {
1156
+ const paths = [];
1157
+ if (!manifest?.data || typeof manifest.data !== 'object') return paths;
1158
+ function visit(val) {
1159
+ if (typeof val === 'string' && (val.startsWith('/') || /\.(yaml|yml|csv|json)$/i.test(val))) {
1160
+ paths.push(val);
1161
+ } else if (Array.isArray(val)) {
1162
+ val.forEach(visit);
1163
+ } else if (val && typeof val === 'object' && !Array.isArray(val)) {
1164
+ Object.values(val).forEach(visit);
1165
+ }
1166
+ }
1167
+ Object.values(manifest.data).forEach(visit);
1168
+ return paths;
1169
+ }
1170
+
1171
+ function manifestDataPathsInclude(manifest, extensions) {
1172
+ const paths = collectDataPaths(manifest);
1173
+ return paths.some(p => extensions.some(ext => p.toLowerCase().includes(ext)));
1174
+ }
1175
+
1151
1176
  async function loadYamlLibrary() {
1152
1177
  if (jsyaml) return jsyaml;
1153
1178
  if (yamlLoadingPromise) return yamlLoadingPromise;
1154
1179
 
1180
+ const manifest = await window.ManifestDataConfig?.ensureManifest?.();
1181
+ if (manifest && !manifestDataPathsInclude(manifest, ['.yaml', '.yml'])) {
1182
+ yamlLoadingPromise = Promise.reject(new Error('[Manifest Data] No YAML paths in manifest - skipping loader'));
1183
+ return yamlLoadingPromise;
1184
+ }
1185
+
1155
1186
  yamlLoadingPromise = new Promise((resolve, reject) => {
1156
1187
  const script = document.createElement('script');
1157
1188
  script.src = 'https://cdn.jsdelivr.net/npm/js-yaml/dist/js-yaml.min.js';
@@ -1181,6 +1212,12 @@ async function loadCSVParser() {
1181
1212
  if (papaparse) return papaparse;
1182
1213
  if (csvLoadingPromise) return csvLoadingPromise;
1183
1214
 
1215
+ const manifest = await window.ManifestDataConfig?.ensureManifest?.();
1216
+ if (manifest && !manifestDataPathsInclude(manifest, ['.csv'])) {
1217
+ csvLoadingPromise = Promise.reject(new Error('[Manifest Data] No CSV paths in manifest - skipping loader'));
1218
+ return csvLoadingPromise;
1219
+ }
1220
+
1184
1221
  csvLoadingPromise = new Promise((resolve, reject) => {
1185
1222
  const script = document.createElement('script');
1186
1223
  script.src = 'https://cdn.jsdelivr.net/npm/papaparse@latest/papaparse.min.js';
package/dist/manifest.js CHANGED
@@ -52,6 +52,33 @@
52
52
  'appwrite-presence': ['data']
53
53
  };
54
54
 
55
+ // Derive default plugin list from manifest (only load data/localization/components when manifest needs them)
56
+ function getDefaultPluginsFromManifest(manifest) {
57
+ if (!manifest || typeof manifest !== 'object') {
58
+ return AVAILABLE_PLUGINS.slice();
59
+ }
60
+ const hasData = manifest.data && typeof manifest.data === 'object' && Object.keys(manifest.data).length > 0;
61
+ const hasComponents = (manifest.components?.length > 0) || (manifest.preloadedComponents?.length > 0);
62
+ const hasLocalization = (() => {
63
+ if (!manifest.data || typeof manifest.data !== 'object') return false;
64
+ for (const collection of Object.values(manifest.data)) {
65
+ if (!collection || typeof collection !== 'object') continue;
66
+ if (typeof collection.locales === 'string') return true;
67
+ for (const key of Object.keys(collection)) {
68
+ if (['url', 'headers', 'params', 'transform', 'defaultValue', 'locales'].includes(key)) continue;
69
+ if (/^[a-zA-Z]{2}(-[a-zA-Z]{2})?$/.test(key)) return true;
70
+ }
71
+ }
72
+ return false;
73
+ })();
74
+ return AVAILABLE_PLUGINS.filter(p => {
75
+ if (p === 'data') return hasData;
76
+ if (p === 'localization') return hasLocalization;
77
+ if (p === 'components') return hasComponents;
78
+ return true;
79
+ });
80
+ }
81
+
55
82
  // Get plugin URL from CDN
56
83
  function getPluginUrl(pluginName, version = DEFAULT_VERSION) {
57
84
  const base = getBaseUrl(version);
@@ -193,12 +220,13 @@
193
220
  const version = script.getAttribute('data-version') || DEFAULT_VERSION;
194
221
 
195
222
  let pluginList = [];
223
+ const deriveFromManifest = !plugins;
196
224
 
197
225
  if (plugins) {
198
226
  // Explicit declaration - load only specified plugins (core + Appwrite)
199
227
  pluginList = plugins.split(',').map(p => p.trim()).filter(p => p);
200
228
  } else {
201
- // Default: load all available core plugins (Appwrite plugins are opt-in only)
229
+ // Default: start with all core plugins; loader will trim by manifest when manifest is available
202
230
  pluginList = AVAILABLE_PLUGINS.slice();
203
231
  }
204
232
 
@@ -213,6 +241,7 @@
213
241
 
214
242
  return {
215
243
  plugins: pluginList,
244
+ deriveFromManifest,
216
245
  tailwind,
217
246
  version
218
247
  };
@@ -268,22 +297,45 @@
268
297
  detectAppwriteFromManifest();
269
298
 
270
299
  if (config && config.plugins.length > 0) {
271
- // Load all plugins in parallel, then Alpine with defer
300
+ const MANIFEST_DEPENDENT_PLUGINS = [
301
+ 'data', 'localization', 'components',
302
+ 'appwrite-auth', 'appwrite-data', 'appwrite-presence'
303
+ ];
304
+ const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
305
+
272
306
  const loadPlugins = async () => {
273
- const pluginPromises = config.plugins.map(pluginName => {
307
+ let manifest = null;
308
+ let pluginsToLoad = config.plugins;
309
+ let manifestPromise = null;
310
+
311
+ if (config.deriveFromManifest) {
312
+ manifest = await fetch(manifestUrl).then(r => r.ok ? r.json() : null).catch(() => null);
313
+ pluginsToLoad = resolveDependencies(getDefaultPluginsFromManifest(manifest));
314
+ } else {
315
+ const needsManifest = config.plugins.some(p => MANIFEST_DEPENDENT_PLUGINS.includes(p));
316
+ if (needsManifest) {
317
+ manifestPromise = fetch(manifestUrl).then(r => r.ok ? r.json() : null).catch(() => null);
318
+ }
319
+ }
320
+
321
+ const pluginPromises = pluginsToLoad.map(pluginName => {
274
322
  return addScript(pluginName, config.version).catch(error => {
275
323
  console.warn(`[Manifest Loader] Failed to load plugin ${pluginName}:`, error);
276
324
  });
277
325
  });
278
-
279
- // Load Tailwind in parallel if requested
280
326
  if (config.tailwind) {
281
- pluginPromises.push(loadTailwind(config.version).catch(error => {
282
- console.warn(`[Manifest Loader] Failed to load Tailwind:`, error);
283
- }));
327
+ pluginPromises.push(loadTailwind(config.version).catch(() => {}));
284
328
  }
285
-
286
329
  await Promise.all(pluginPromises);
330
+ if (manifestPromise) {
331
+ manifest = await manifestPromise;
332
+ }
333
+ if (manifest && typeof window !== 'undefined') {
334
+ window.__manifestLoaded = manifest;
335
+ if (window.ManifestComponentsRegistry) {
336
+ window.ManifestComponentsRegistry.manifest = manifest;
337
+ }
338
+ }
287
339
  loadAlpine();
288
340
  };
289
341
 
@@ -137,11 +137,15 @@ function initializeLocalizationPlugin() {
137
137
  }
138
138
 
139
139
  try {
140
- const response = await fetch('/manifest.json');
141
- if (!response.ok) {
142
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
140
+ let manifest = window.__manifestLoaded || window.ManifestComponentsRegistry?.manifest;
141
+ if (!manifest) {
142
+ const manifestUrl = (document.querySelector('link[rel="manifest"]')?.getAttribute('href')) || '/manifest.json';
143
+ const response = await fetch(manifestUrl);
144
+ if (!response.ok) {
145
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
146
+ }
147
+ manifest = await response.json();
143
148
  }
144
- const manifest = await response.json();
145
149
 
146
150
  // Validate manifest structure
147
151
  if (!manifest || typeof manifest !== 'object') {
@@ -489,6 +489,7 @@ class TailwindCompiler {
489
489
  this.styleElement = document.createElement('style');
490
490
  this.styleElement.id = 'utility-styles';
491
491
  document.head.appendChild(this.styleElement);
492
+ this.setupUtilityStylesOrderObserver();
492
493
 
493
494
  // Initialize properties
494
495
  this.tailwindLink = null;
@@ -1028,6 +1029,7 @@ TailwindCompiler.prototype.generateSynchronousUtilities = function () {
1028
1029
  } else {
1029
1030
  this.styleElement.textContent = finalCss;
1030
1031
  }
1032
+ this.ensureUtilityStylesLast();
1031
1033
 
1032
1034
  // Clear critical styles once we have generated utilities
1033
1035
  if (this.criticalStyleElement && generated.trim()) {
@@ -1116,6 +1118,8 @@ TailwindCompiler.prototype.loadAndApplyCache = function () {
1116
1118
  if (cacheToUse && cacheToUse.css) {
1117
1119
  const applyCacheStart = performance.now();
1118
1120
  this.styleElement.textContent = cacheToUse.css;
1121
+ this.ensureUtilityStylesLast();
1122
+ this.scheduleEnsureUtilityStylesLast();
1119
1123
  this.lastThemeHash = cacheToUse.themeHash;
1120
1124
 
1121
1125
  // Also apply cache to critical style element
@@ -1192,6 +1196,38 @@ TailwindCompiler.prototype.cleanupCache = function () {
1192
1196
  // Helper methods
1193
1197
  // Utility functions for extracting, parsing, and processing CSS and classes
1194
1198
 
1199
+ // Ensure #utility-styles is last in head so our responsive/variant rules win over Tailwind and any later-injected styles
1200
+ TailwindCompiler.prototype.ensureUtilityStylesLast = function () {
1201
+ if (this.styleElement && this.styleElement.parentNode && document.head.lastElementChild !== this.styleElement) {
1202
+ document.head.appendChild(this.styleElement);
1203
+ }
1204
+ };
1205
+
1206
+ // When any element is added to head after ours, move #utility-styles to end. Handles CDN load order (e.g. Tailwind injecting after we run).
1207
+ TailwindCompiler.prototype.setupUtilityStylesOrderObserver = function () {
1208
+ if (!document.head || !this.styleElement) return;
1209
+ const self = this;
1210
+ const observer = new MutationObserver((mutations) => {
1211
+ for (const mutation of mutations) {
1212
+ if (mutation.type !== 'childList' || !mutation.addedNodes.length) continue;
1213
+ for (const node of mutation.addedNodes) {
1214
+ if (node.nodeType !== Node.ELEMENT_NODE || node === self.styleElement) continue;
1215
+ self.ensureUtilityStylesLast();
1216
+ break;
1217
+ }
1218
+ }
1219
+ });
1220
+ observer.observe(document.head, { childList: true, subtree: false });
1221
+ };
1222
+
1223
+ // Schedule ensureUtilityStylesLast at 0ms, 100ms, 500ms so we win when Tailwind (or other scripts) inject styles later (e.g. CDN).
1224
+ TailwindCompiler.prototype.scheduleEnsureUtilityStylesLast = function () {
1225
+ const self = this;
1226
+ [0, 100, 500].forEach((ms) => {
1227
+ setTimeout(() => self.ensureUtilityStylesLast(), ms);
1228
+ });
1229
+ };
1230
+
1195
1231
  // Discover CSS files from stylesheets and imports
1196
1232
  TailwindCompiler.prototype.discoverCssFiles = function () {
1197
1233
  try {
@@ -2981,6 +3017,8 @@ TailwindCompiler.prototype.compile = async function () {
2981
3017
  const finalCss = `@layer utilities {\n${allUtilities}\n}`;
2982
3018
 
2983
3019
  this.styleElement.textContent = finalCss;
3020
+ this.ensureUtilityStylesLast();
3021
+ this.scheduleEnsureUtilityStylesLast();
2984
3022
 
2985
3023
  // Remove critical style element entirely after compilation
2986
3024
  // Use requestAnimationFrame to ensure styles are painted before removing
@@ -2991,6 +3029,7 @@ TailwindCompiler.prototype.compile = async function () {
2991
3029
  this.criticalStyleElement.parentNode.removeChild(this.criticalStyleElement);
2992
3030
  this.criticalStyleElement = null;
2993
3031
  }
3032
+ this.ensureUtilityStylesLast();
2994
3033
  });
2995
3034
  });
2996
3035
  this.lastClassesHash = staticUsedData.classes.sort().join(',');
@@ -3060,6 +3099,8 @@ TailwindCompiler.prototype.compile = async function () {
3060
3099
  const finalCss = `@layer utilities {\n${allUtilities}\n}`;
3061
3100
 
3062
3101
  this.styleElement.textContent = finalCss;
3102
+ this.ensureUtilityStylesLast();
3103
+ this.scheduleEnsureUtilityStylesLast();
3063
3104
 
3064
3105
  // Remove critical style element entirely after compilation
3065
3106
  // Use requestAnimationFrame to ensure styles are painted before removing
@@ -3070,6 +3111,7 @@ TailwindCompiler.prototype.compile = async function () {
3070
3111
  this.criticalStyleElement.parentNode.removeChild(this.criticalStyleElement);
3071
3112
  this.criticalStyleElement = null;
3072
3113
  }
3114
+ this.ensureUtilityStylesLast();
3073
3115
  });
3074
3116
  });
3075
3117
  this.lastClassesHash = dynamicClassesHash;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mnfst",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "private": false,
5
5
  "workspaces": [
6
6
  "templates/starter"