@despia/local 1.0.4 → 1.0.6

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/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # @despia/local
2
2
 
3
- Universal build plugin to generate `despia/local.json` manifest for [Despia](https://despia.com) web-native apps. This manifest enables Despia Local Server, which runs your web app from a secure local HTTPS server on-device, providing full offline functionality with seamless updates.
3
+ Universal build plugin to generate `despia/local.json` manifest for [Despia](https://despia.com) web-native apps. This manifest enables Despia's local server, which runs your web app from a local HTTP server on-device, providing full offline functionality with seamless updates.
4
4
 
5
- **Note**: Despia Local Server is optional. Normally, the Despia runtime can run your web app directly from a URL. Despia Local Server is for developers who need extra performance and full true native offline support.
5
+ **Note**: Despia's local server is optional. Normally, the Despia runtime can run your web app directly from a URL. Despia's local server is for developers who need extra performance and full true native offline support.
6
6
 
7
- **Store Compliant**: Despia Local Server is fully compliant with Apple App Store and Google Play Store guidelines. It downloads and caches web content (HTML, CSS, JavaScript) for offline display in a WebView—identical to how web browsers work. No native code or executables are downloaded. See [Store Compliance & Security](#store-compliance--security) section for detailed compliance information.
7
+ **Store Compliant**: Despia's local server is fully compliant with Apple App Store and Google Play Store guidelines. It downloads and caches web content (HTML, CSS, JavaScript) for offline display in a WebView—identical to how web browsers work. No native code or executables are downloaded. See [Store Compliance & Security](#store-compliance--security) section for detailed compliance information.
8
8
 
9
9
  ## Features
10
10
 
@@ -15,11 +15,11 @@ Universal build plugin to generate `despia/local.json` manifest for [Despia](htt
15
15
  - **Sorted Output** - Alphabetically sorted paths for consistent builds
16
16
  - **Standalone Script** - Can be used with any build system via CLI
17
17
 
18
- ## About Despia Local Server
18
+ ## About Despia's Local Server
19
19
 
20
- **Note**: Despia Local Server is optional. Normally, the Despia runtime can run your web app directly from a URL. Despia Local Server is for developers who need extra performance and full true native offline support.
20
+ **Note**: Despia's local server is optional. Normally, the Despia runtime can run your web app directly from a URL. Despia's local server is for developers who need extra performance and full true native offline support.
21
21
 
22
- Despia Local Server enables your web app to run entirely from a secure local HTTPS server on-device, providing a native app experience with full offline functionality.
22
+ Despia's local server enables your web app to run entirely from a local HTTP server on-device, providing a native app experience with full offline functionality.
23
23
 
24
24
  ### Architecture
25
25
 
@@ -28,13 +28,13 @@ The Despia native container consists of:
28
28
  - **Native iOS/Android Container**: The native binary submitted to App Store/Play Store
29
29
  - WebView for UI rendering (WKWebView on iOS, WebView on Android)
30
30
  - JavaScript bridge for native API access (exposes native capabilities to web content via [`despia-native`](https://www.npmjs.com/package/despia-native))
31
- - Local HTTPS server infrastructure (runs on-device only)
31
+ - Local HTTP server infrastructure (runs on-device only)
32
32
  - Storage and update management systems
33
33
 
34
34
  - **Web Content Layer** (downloaded and cached):
35
35
  - HTML, CSS, JavaScript files
36
36
  - Images, fonts, and other static assets
37
- - Served from `https://localhost` after initial hydration
37
+ - Served from `http://localhost` after initial hydration
38
38
 
39
39
  **Key Architecture Principles:**
40
40
  - Web content updates (HTML/CSS/JS only)
@@ -50,14 +50,14 @@ The App Store/Play Store binary contains the native iOS/Android container (see [
50
50
  - Native code (Swift/Kotlin) for platform integration
51
51
  - WebView component for UI rendering
52
52
  - JavaScript bridge for native API access (exposes native capabilities to web content via [`despia-native`](https://www.npmjs.com/package/despia-native))
53
- - Local HTTPS server infrastructure (runs on-device only)
53
+ - Local HTTP server infrastructure (runs on-device only)
54
54
  - Storage and update management systems
55
55
  - No web app assets (HTML, CSS, JavaScript) are bundled in the binary
56
56
 
57
57
  This approach keeps initial install sizes minimal and allows for rapid iteration of web UI without requiring app store resubmissions. **Important**: All native code, the JavaScript bridge, and app functionality are fixed at submission time and cannot be changed. Only web content (HTML/CSS/JS) can be updated.
58
58
 
59
59
  **First Launch Hydration**
60
- On first app launch, Despia securely downloads your latest web build (HTML, CSS, JavaScript, images, fonts) from your repository and stores it locally on the device. The local HTTPS server then begins serving your web content from `https://localhost`, providing a stable, secure origin for your web application.
60
+ On first app launch, Despia securely downloads your latest web build (HTML, CSS, JavaScript, images, fonts) from your repository and stores it locally on the device. The local HTTP server then begins serving your web content from `http://localhost`, providing a stable origin for your web application.
61
61
 
62
62
  **Note on Compliance**: This process downloads and caches web content files only. No native code, executables, or binary files are downloaded. The JavaScript executes within the WebView's sandbox, identical to how Safari or Chrome render web pages.
63
63
 
@@ -67,7 +67,7 @@ After the initial hydration, your app operates completely offline:
67
67
  - No network connectivity required
68
68
  - Full SPA routing and navigation
69
69
  - Deep linking support
70
- - Stable HTTPS origin (`https://localhost`) for security
70
+ - Stable HTTP origin (`http://localhost`) for hybrid app flexibility
71
71
 
72
72
  If the device goes offline permanently, the app continues to function using the last hydrated version, ensuring reliability even without connectivity.
73
73
 
@@ -84,7 +84,7 @@ When connectivity is available, Despia checks for updates in the background and
84
84
 
85
85
  **Compliance Summary for App Reviewers:**
86
86
 
87
- Despia Local Server downloads and caches web content files (HTML, CSS, JavaScript, images) for offline display in a WebView. This is identical to how web browsers (Safari, Chrome) download and render web pages.
87
+ Despia's local server downloads and caches web content files (HTML, CSS, JavaScript, images) for offline display in a WebView. This is identical to how web browsers (Safari, Chrome) download and render web pages.
88
88
 
89
89
  **Key Compliance Points:**
90
90
  - No native code is downloaded or executed
@@ -133,7 +133,7 @@ See the [`despia-version-guard` documentation](https://www.npmjs.com/package/des
133
133
 
134
134
  ---
135
135
 
136
- Despia Local Server is fully compliant with both Apple App Store and Google Play Store guidelines. This section explains the compliance in detail.
136
+ Despia's local server is fully compliant with both Apple App Store and Google Play Store guidelines. This section explains the compliance in detail.
137
137
 
138
138
  #### Apple App Store Compliance
139
139
 
@@ -141,7 +141,7 @@ Despia Local Server is fully compliant with both Apple App Store and Google Play
141
141
  > "Apps must not download, install, or execute code which introduces or changes features or functionality of the app, including other apps."
142
142
 
143
143
  **Compliance Statement:**
144
- Despia Local Server complies with this guideline because:
144
+ Despia's local server complies with this guideline because:
145
145
 
146
146
  1. **No Native Code Execution**: The app does not download, install, or execute any native code (Swift, Objective-C, C++, etc.). All native code is fixed at App Store submission time and cannot be modified.
147
147
 
@@ -159,7 +159,7 @@ Despia Local Server complies with this guideline because:
159
159
 
160
160
  **Note**: This follows the same pattern as [Expo](https://expo.dev), which has been generally accepted by app stores. However, Apple's interpretation of "feature changes" under Guideline 3.3.2 is subjective, and your app could still face review challenges. It's recommended to be conservative with updates that significantly change app behavior, even when using existing native APIs.
161
161
 
162
- 4. **On-Device Execution**: All web content is served from a local HTTPS server running on-device (`localhost`). No remote code execution occurs.
162
+ 4. **On-Device Execution**: All web content is served from a local HTTP server running on-device (`localhost`). No remote code execution occurs.
163
163
 
164
164
  **This approach is explicitly permitted** under App Store guidelines, as evidenced by:
165
165
  - Safari and other browsers downloading and rendering web content
@@ -173,7 +173,7 @@ Despia Local Server complies with this guideline because:
173
173
  > "Apps must not download executable code (e.g., dex, JAR, .so files) from a source other than Google Play."
174
174
 
175
175
  **Compliance Statement:**
176
- Despia Local Server complies with this policy because:
176
+ Despia's local server complies with this policy because:
177
177
 
178
178
  1. **No Executable Code Downloads**: The app does not download any executable code (`.dex`, `.jar`, `.so`, `.apk`, or native binaries). Only web content files (HTML, CSS, JavaScript, images, fonts) are downloaded.
179
179
 
@@ -236,12 +236,87 @@ This follows the same pattern as established frameworks like [Expo](https://expo
236
236
  - Consider submitting major feature additions through the normal App Store review process when possible
237
237
 
238
238
  **Security Model:**
239
- - All web content is served from `https://localhost` (on-device only)
239
+ - All web content is served from `http://localhost` (on-device only)
240
240
  - WebView sandbox enforces same-origin policy
241
241
  - No network access to external servers after initial hydration
242
242
  - Content Security Policy (CSP) can be enforced
243
243
  - All execution happens within WebView's security boundaries
244
244
 
245
+ ### HTTP vs HTTPS for Local Apps
246
+
247
+ Despia's local server uses **HTTP** for serving local content. This design choice provides important flexibility for hybrid apps while maintaining full security through `localhost`'s inherent security guarantees.
248
+
249
+ #### Security Guarantees of localhost
250
+
251
+ `http://localhost` is a **secure context** in all modern browsers and provides inherent security guarantees:
252
+
253
+ 1. **Reserved Hostname**: `localhost` is a reserved hostname that maps exclusively to `127.0.0.1` (IPv4) or `::1` (IPv6) on the local machine. It cannot be redirected or spoofed by external attackers.
254
+
255
+ 2. **No Network Exposure**: Traffic to `localhost` never leaves the device. All communication stays within the local machine, eliminating network-based attack vectors.
256
+
257
+ 3. **Secure Context**: `http://localhost` IS a secure context in browsers, meaning all modern Web APIs work perfectly:
258
+ - Service Workers
259
+ - Web Crypto API
260
+ - Geolocation API
261
+ - MediaDevices API
262
+ - And all other secure-context APIs
263
+
264
+ 4. **Cannot Be Spoofed**: External attackers cannot redirect `localhost` to their servers. The hostname is hardcoded to point to the local machine, providing the same security guarantees as HTTPS for local-only content.
265
+
266
+ 5. **On-Device Execution**: Combined with WebView sandbox isolation, same-origin policy, and Content Security Policy, `localhost` provides a secure execution environment without requiring encryption overhead.
267
+
268
+ #### Why HTTP is the Right Choice for Hybrid Apps
269
+
270
+ 1. **Protocol Flexibility**: HTTP allows your app to load resources from both HTTPS (external APIs, CDNs) and HTTP (local server) origins without mixed content warnings. This is crucial for hybrid apps that need to:
271
+ - Load local assets from `http://localhost`
272
+ - Make API calls to `https://api.example.com`
273
+ - Embed external HTTPS resources (images, fonts, etc.)
274
+ - Work seamlessly across different network configurations
275
+
276
+ 2. **Secure Context Support**: `http://localhost` is treated as a secure context by browsers, meaning all modern Web APIs (Service Workers, Web Crypto API, Geolocation, etc.) work without requiring HTTPS.
277
+
278
+ 3. **Inherent Security**: `localhost` is a reserved hostname that can only point to the local machine (`127.0.0.1`). This means:
279
+ - Traffic never leaves the device (no network exposure)
280
+ - Cannot be spoofed or redirected by external attackers
281
+ - Provides the same security guarantees as HTTPS for local-only content
282
+
283
+ 4. **Simplified Development**: HTTP eliminates certificate management complexity for local development and testing. No need to generate, install, or manage SSL certificates for localhost.
284
+
285
+ 5. **Cross-Origin Resource Sharing (CORS)**: HTTP on localhost provides more predictable CORS behavior when mixing local and remote resources, reducing integration complexity.
286
+
287
+ 6. **Performance**: HTTP has slightly lower overhead than HTTPS, which is beneficial for local file serving where encryption isn't necessary since all content is already on-device and `localhost` provides inherent security.
288
+
289
+ 7. **Compatibility**: HTTP works consistently across all platforms and WebView implementations without requiring certificate pinning or trust store configuration.
290
+
291
+ #### When HTTPS Might Be Needed
292
+
293
+ While HTTP with `localhost` provides full security for local-only content, HTTPS might be required in specific scenarios:
294
+
295
+ 1. **Enterprise Compliance**: Some enterprise security policies require all network traffic (including local) to be encrypted, making HTTPS a requirement for certain deployment scenarios.
296
+
297
+ 2. **Certificate Validation**: HTTPS with proper certificate validation provides additional assurance that the local server hasn't been tampered with or replaced by malicious software. This can be important in high-security environments.
298
+
299
+ 3. **Security-in-Depth**: HTTPS enforces encryption even for local traffic, following security-in-depth principles. This protects against potential local network attacks or device-level interception in shared or untrusted environments.
300
+
301
+ 4. **Content Security Policy (CSP)**: HTTPS makes it easier to enforce strict CSP headers without mixed content warnings, though this is less relevant for local-only content.
302
+
303
+ 5. **Browser Security Indicators**: HTTPS provides visual security indicators (lock icon) that can increase user trust, even for local content.
304
+
305
+ **Note**: For most hybrid app scenarios, HTTP with `localhost` provides the right balance of flexibility and security. The security comes from the reserved hostname and on-device execution, not encryption. However, if your use case requires HTTPS (e.g., enterprise compliance, strict security policies), you can configure your WebView to use HTTPS for the local server.
306
+
307
+ #### Current Implementation Rationale
308
+
309
+ Despia's local server uses HTTP because the primary use case is hybrid apps that need maximum flexibility to mix local and remote resources. The security model relies on:
310
+
311
+ - **Secure Context**: `http://localhost` IS a secure context, enabling all modern Web APIs
312
+ - **Reserved Hostname**: `localhost` is a reserved hostname (`127.0.0.1`) that cannot be spoofed
313
+ - **On-Device Execution**: All traffic stays on-device (no network exposure)
314
+ - **WebView Sandbox Isolation**: WebView enforces security boundaries
315
+ - **Same-Origin Policy**: Browser enforces same-origin policy
316
+ - **Content Security Policy**: CSP can be enforced when configured
317
+
318
+ Security comes from the reserved hostname and on-device execution, not encryption. For most hybrid app scenarios, HTTP provides the right balance of flexibility and security.
319
+
245
320
  #### Comparison to Similar Technologies
246
321
 
247
322
  This approach is conceptually and technically identical to:
@@ -286,7 +361,7 @@ This is analogous to:
286
361
 
287
362
  #### Developer Responsibility
288
363
 
289
- When submitting apps using Despia Local Server:
364
+ When submitting apps using Despia's local server:
290
365
 
291
366
  1. **App Store Submission**: Clearly state that the app uses a WebView to display web content that may be cached for offline use. This is standard practice and does not require special disclosure.
292
367
 
@@ -306,7 +381,7 @@ When submitting apps using Despia Local Server:
306
381
 
307
382
  ### The `local.json` Manifest
308
383
 
309
- The `despia/local.json` manifest generated by this plugin serves as an asset inventory for Despia Local Server. It contains a complete list of all assets that need to be cached locally, enabling:
384
+ The `despia/local.json` manifest generated by this plugin serves as an asset inventory for Despia's local server. It contains a complete list of all assets that need to be cached locally, enabling:
310
385
 
311
386
  - **Complete Asset Discovery**: Ensures all files (JS, CSS, images, fonts, HTML) are properly cached
312
387
  - **Efficient Updates**: Allows Despia to determine which assets have changed between builds
@@ -568,17 +643,22 @@ All plugins accept the following options:
568
643
 
569
644
  ## Output Format
570
645
 
571
- The generated `despia/local.json` file contains a sorted JSON array of root-relative paths:
646
+ The generated `despia/local.json` file contains an object with the entry HTML path and a sorted array of asset paths:
572
647
 
573
648
  ```json
574
- [
575
- "/assets/app.abc123.css",
576
- "/assets/app.def456.js",
577
- "/assets/logo.xyz789.png",
578
- "/index.html"
579
- ]
649
+ {
650
+ "entry": "/index.html",
651
+ "assets": [
652
+ "/assets/app.abc123.css",
653
+ "/assets/app.def456.js",
654
+ "/assets/logo.xyz789.png"
655
+ ]
656
+ }
580
657
  ```
581
658
 
659
+ - **`entry`**: The entry HTML file path (e.g., `/index.html`). **Required** - Local apps always need an entry point for client-side rendering. When `skipEntryHtml` is enabled, the entry is still required in the manifest but won't be included in the `assets` array.
660
+ - **`assets`**: A sorted array of all asset paths (excluding the entry file).
661
+
582
662
  ## Examples
583
663
 
584
664
  ### React + Vite
@@ -668,8 +748,8 @@ export default defineConfig({
668
748
  2. **Scan Output Directory** - Recursively scans the build output directory for all files
669
749
  3. **Collect Asset Paths** - Collects paths from both the build tool's bundle metadata and file system
670
750
  4. **Normalize Paths** - Converts all paths to root-relative format (starting with `/`)
671
- 5. **Include Entry HTML** - Ensures the entry HTML file is always included
672
- 6. **Sort & Write** - Sorts paths alphabetically and writes to `despia/local.json`
751
+ 5. **Separate Entry from Assets** - Identifies the entry HTML file and separates it from other assets
752
+ 6. **Sort & Write** - Sorts asset paths alphabetically and writes object format `{ entry, assets }` to `despia/local.json`
673
753
 
674
754
  The generated manifest is then used by Despia during app hydration and updates to ensure all assets are properly cached for offline operation.
675
755
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@despia/local",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Universal build plugin to generate despia/local.json manifest for offline caching in Despia web-native apps. Supports Vite, Webpack, Rollup, Nuxt, SvelteKit, Astro, Remix, esbuild, Parcel, and more.",
5
5
  "type": "module",
6
6
  "main": "./src/core.js",
package/src/astro.js CHANGED
@@ -37,11 +37,12 @@ export default function despiaLocalIntegration(options = {}) {
37
37
  }
38
38
 
39
39
  try {
40
- const paths = generateManifest({
40
+ const manifest = generateManifest({
41
41
  outputDir,
42
42
  entryHtml
43
43
  });
44
- console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
44
+ const entryInfo = manifest.entry ? ` and entry: ${manifest.entry}` : '';
45
+ console.log(`✓ Generated despia/local.json with ${manifest.assets.length} assets${entryInfo}`);
45
46
  } catch (error) {
46
47
  console.error('Error generating despia/local.json:', error.message);
47
48
  }
package/src/core.js CHANGED
@@ -51,9 +51,9 @@ export function collectFiles(dir, baseDir = dir) {
51
51
  * @param {string} options.outputDir - Output directory path (where to scan for assets)
52
52
  * @param {string} options.entryHtml - Entry HTML filename (default: 'index.html')
53
53
  * @param {string[]} options.additionalPaths - Additional paths to include
54
- * @param {boolean} options.skipEntryHtml - Skip adding entry HTML to manifest (for SSR apps)
54
+ * @param {boolean} options.skipEntryHtml - Skip adding entry HTML to assets array (entry is still required in manifest)
55
55
  * @param {string} options.manifestOutputPath - Custom path for manifest file (default: outputDir/despia/local.json)
56
- * @returns {string[]} Array of all asset paths
56
+ * @returns {{entry: string, assets: string[]}} Object with entry path and assets array
57
57
  */
58
58
  export function generateManifest({ outputDir, entryHtml = 'index.html', additionalPaths = [], skipEntryHtml = false, manifestOutputPath = null }) {
59
59
  const outputPath = resolve(process.cwd(), outputDir);
@@ -77,16 +77,26 @@ export function generateManifest({ outputDir, entryHtml = 'index.html', addition
77
77
  assetPaths.add(normalizedPath);
78
78
  });
79
79
 
80
- // Ensure entry HTML is included (unless skipped for SSR apps)
80
+ // Determine entry path (always required for local apps)
81
+ const entryPath = entryHtml.startsWith('/')
82
+ ? entryHtml
83
+ : '/' + entryHtml;
84
+
85
+ // Add entry HTML to assets if not skipped (for SSR apps, entry is still required but not in assets)
81
86
  if (!skipEntryHtml) {
82
- const entryPath = entryHtml.startsWith('/')
83
- ? entryHtml
84
- : '/' + entryHtml;
85
87
  assetPaths.add(entryPath);
86
88
  }
87
89
 
88
- // Convert to sorted array
89
- const sortedPaths = Array.from(assetPaths).sort();
90
+ // Include all assets (entry is included in assets array)
91
+ const assets = Array.from(assetPaths)
92
+ .filter(path => !skipEntryHtml || path !== entryPath)
93
+ .sort();
94
+
95
+ // Create manifest object (entry is always required for local client-side apps)
96
+ const manifest = {
97
+ entry: entryPath,
98
+ assets: assets
99
+ };
90
100
 
91
101
  // Create directory for manifest if it doesn't exist
92
102
  const manifestDir = manifestOutputPath
@@ -96,9 +106,9 @@ export function generateManifest({ outputDir, entryHtml = 'index.html', addition
96
106
  mkdirSync(manifestDir, { recursive: true });
97
107
  }
98
108
 
99
- // Write formatted JSON array
100
- const jsonContent = JSON.stringify(sortedPaths, null, 2);
109
+ // Write formatted JSON object
110
+ const jsonContent = JSON.stringify(manifest, null, 2);
101
111
  writeFileSync(manifestPath, jsonContent, 'utf-8');
102
112
 
103
- return sortedPaths;
113
+ return manifest;
104
114
  }
package/src/esbuild.js CHANGED
@@ -48,12 +48,13 @@ export function despiaLocalEsbuild(options = {}) {
48
48
  }
49
49
 
50
50
  try {
51
- const paths = generateManifest({
51
+ const manifest = generateManifest({
52
52
  outputDir: outDirPath,
53
53
  entryHtml,
54
54
  additionalPaths
55
55
  });
56
- console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
56
+ const entryInfo = manifest.entry ? ` and entry: ${manifest.entry}` : '';
57
+ console.log(`✓ Generated despia/local.json with ${manifest.assets.length} assets${entryInfo}`);
57
58
  } catch (error) {
58
59
  console.error('Error generating despia/local.json:', error.message);
59
60
  }
package/src/nuxt.js CHANGED
@@ -45,11 +45,12 @@ export default function DespiaLocalModule(moduleOptions) {
45
45
 
46
46
  for (const dir of altDirs) {
47
47
  try {
48
- const paths = generateManifest({
48
+ const manifest = generateManifest({
49
49
  outputDir: dir,
50
50
  entryHtml: options.entryHtml
51
51
  });
52
- console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
52
+ const entryInfo = manifest.entry ? ` and entry: ${manifest.entry}` : '';
53
+ console.log(`✓ Generated despia/local.json with ${manifest.assets.length} assets${entryInfo}`);
53
54
  generated = true;
54
55
  break;
55
56
  } catch (e) {
package/src/parcel.js CHANGED
@@ -35,12 +35,13 @@ export default function(api) {
35
35
  }
36
36
 
37
37
  try {
38
- const paths = generateManifest({
38
+ const manifest = generateManifest({
39
39
  outputDir: api.options?.outDir || outDir,
40
40
  entryHtml,
41
41
  additionalPaths
42
42
  });
43
- console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
43
+ const entryInfo = manifest.entry ? ` and entry: ${manifest.entry}` : '';
44
+ console.log(`✓ Generated despia/local.json with ${manifest.assets.length} assets${entryInfo}`);
44
45
  } catch (error) {
45
46
  console.error('Error generating despia/local.json:', error.message);
46
47
  console.warn('💡 Tip: For Parcel projects, use the standalone CLI: "despia-local dist"');
package/src/remix.js CHANGED
@@ -27,11 +27,12 @@ export function despiaLocalRemix(options = {}) {
27
27
 
28
28
  for (const outputDir of outputDirs) {
29
29
  try {
30
- const paths = generateManifest({
30
+ const manifest = generateManifest({
31
31
  outputDir,
32
32
  entryHtml
33
33
  });
34
- console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
34
+ const entryInfo = manifest.entry ? ` and entry: ${manifest.entry}` : '';
35
+ console.log(`✓ Generated despia/local.json with ${manifest.assets.length} assets${entryInfo}`);
35
36
  return; // Success, exit
36
37
  } catch (error) {
37
38
  // Try next directory
package/src/rollup.js CHANGED
@@ -31,12 +31,13 @@ export function despiaLocal(options = {}) {
31
31
  }
32
32
 
33
33
  try {
34
- const paths = generateManifest({
34
+ const manifest = generateManifest({
35
35
  outputDir,
36
36
  entryHtml,
37
37
  additionalPaths
38
38
  });
39
- console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
39
+ const entryInfo = manifest.entry ? ` and entry: ${manifest.entry}` : '';
40
+ console.log(`✓ Generated despia/local.json with ${manifest.assets.length} assets${entryInfo}`);
40
41
  } catch (error) {
41
42
  console.error('Error generating despia/local.json:', error.message);
42
43
  }
package/src/sveltekit.js CHANGED
@@ -27,11 +27,12 @@ export function despiaLocalSvelteKit(options = {}) {
27
27
 
28
28
  for (const outputDir of outputDirs) {
29
29
  try {
30
- const paths = generateManifest({
30
+ const manifest = generateManifest({
31
31
  outputDir,
32
32
  entryHtml
33
33
  });
34
- console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
34
+ const entryInfo = manifest.entry ? ` and entry: ${manifest.entry}` : '';
35
+ console.log(`✓ Generated despia/local.json with ${manifest.assets.length} assets${entryInfo}`);
35
36
  return; // Success, exit
36
37
  } catch (error) {
37
38
  // Try next directory
package/src/vite.js CHANGED
@@ -33,12 +33,13 @@ export function despiaLocalPlugin(options = {}) {
33
33
  }
34
34
 
35
35
  try {
36
- const paths = generateManifest({
36
+ const manifest = generateManifest({
37
37
  outputDir,
38
38
  entryHtml,
39
39
  additionalPaths
40
40
  });
41
- console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
41
+ const entryInfo = manifest.entry ? ` and entry: ${manifest.entry}` : '';
42
+ console.log(`✓ Generated despia/local.json with ${manifest.assets.length} assets${entryInfo}`);
42
43
  } catch (error) {
43
44
  console.error('Error generating despia/local.json:', error.message);
44
45
  }
package/src/webpack.js CHANGED
@@ -93,8 +93,26 @@ class DespiaLocalPlugin {
93
93
  this.scanPublicDir(this.options.publicDir, this.options.publicDir, assets);
94
94
  }
95
95
 
96
- // 3. Generate sorted array (Despia format - just a JSON array)
97
- const manifest = JSON.stringify([...assets].sort(), null, 2);
96
+ // 3. Generate object format (entry is included in assets array)
97
+ // Entry is always required for local client-side apps
98
+ const entryPath = this.options.entryHtml.startsWith('/')
99
+ ? this.options.entryHtml
100
+ : '/' + this.options.entryHtml;
101
+
102
+ // Ensure entry is in assets if not skipped
103
+ if (!this.options.skipEntryHtml) {
104
+ assets.add(entryPath);
105
+ }
106
+
107
+ const assetList = Array.from(assets)
108
+ .filter(path => !this.options.skipEntryHtml || path !== entryPath)
109
+ .sort();
110
+
111
+ const manifestObj = {
112
+ entry: entryPath,
113
+ assets: assetList
114
+ };
115
+ const manifest = JSON.stringify(manifestObj, null, 2);
98
116
 
99
117
  // 4. Inject into webpack output at despia/local.json
100
118
  const manifestPath = this.options.manifestPath || 'despia/local.json';
@@ -103,7 +121,7 @@ class DespiaLocalPlugin {
103
121
  size: () => Buffer.byteLength(manifest, 'utf8')
104
122
  };
105
123
 
106
- console.log(`✓ Injected despia/local.json into build with ${assets.size} assets`);
124
+ console.log(`✓ Injected despia/local.json into build with ${assetList.length} assets and entry: ${entryPath}`);
107
125
  } else {
108
126
  // Traditional mode: Write to filesystem (for other bundlers)
109
127
  const additionalPaths = new Set();
@@ -134,13 +152,14 @@ class DespiaLocalPlugin {
134
152
 
135
153
  try {
136
154
  const outputPath = compilation.compiler.outputPath || this.options.outDir;
137
- const paths = generateManifest({
155
+ const manifest = generateManifest({
138
156
  outputDir: outputPath,
139
157
  entryHtml: this.options.entryHtml,
140
158
  additionalPaths: Array.from(additionalPaths),
141
159
  skipEntryHtml: this.options.skipEntryHtml
142
160
  });
143
- console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
161
+ const entryInfo = manifest.entry ? ` and entry: ${manifest.entry}` : '';
162
+ console.log(`✓ Generated despia/local.json with ${manifest.assets.length} assets${entryInfo}`);
144
163
  } catch (error) {
145
164
  console.error('Error generating despia/local.json:', error.message);
146
165
  }