@despia/local 1.0.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.
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Standalone script to generate despia/local.json manifest
5
+ * Can be used with any build system by running after build completes
6
+ *
7
+ * Usage:
8
+ * node generate-offline-manifest.js [outputDir] [entryHtml]
9
+ *
10
+ * Examples:
11
+ * node generate-offline-manifest.js
12
+ * node generate-offline-manifest.js dist
13
+ * node generate-offline-manifest.js dist index.html
14
+ */
15
+
16
+ import { generateManifest } from './src/core.js';
17
+ import { resolve } from 'path';
18
+
19
+ // Get command line arguments
20
+ const outputDir = process.argv[2] || 'dist';
21
+ const entryHtml = process.argv[3] || 'index.html';
22
+
23
+ try {
24
+ console.log(`Scanning ${resolve(process.cwd(), outputDir)} for assets...`);
25
+ const paths = generateManifest({ outputDir, entryHtml });
26
+
27
+ console.log(`✓ Generated despia/local.json`);
28
+ console.log(`✓ Included ${paths.length} assets`);
29
+ console.log(`✓ Entry HTML: /${entryHtml}`);
30
+ } catch (error) {
31
+ console.error(`Error: ${error.message}`);
32
+ console.error('Please run this script after your build completes.');
33
+ process.exit(1);
34
+ }
package/package.json ADDED
@@ -0,0 +1,95 @@
1
+ {
2
+ "name": "@despia/local",
3
+ "version": "1.0.0",
4
+ "description": "Universal build plugin to generate despia/local.json manifest for offline caching in Despia web-native apps. Supports Vite, Webpack, Rollup, Next.js, Nuxt, SvelteKit, Astro, Remix, esbuild, Parcel, and more.",
5
+ "type": "module",
6
+ "main": "./src/core.js",
7
+ "bin": {
8
+ "despia-local": "generate-offline-manifest.js"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./src/core.js"
13
+ },
14
+ "./vite": {
15
+ "import": "./src/vite.js"
16
+ },
17
+ "./webpack": {
18
+ "import": "./src/webpack.js"
19
+ },
20
+ "./rollup": {
21
+ "import": "./src/rollup.js"
22
+ },
23
+ "./next": {
24
+ "import": "./src/next.js"
25
+ },
26
+ "./nuxt": {
27
+ "import": "./src/nuxt.js"
28
+ },
29
+ "./sveltekit": {
30
+ "import": "./src/sveltekit.js"
31
+ },
32
+ "./astro": {
33
+ "import": "./src/astro.js"
34
+ },
35
+ "./remix": {
36
+ "import": "./src/remix.js"
37
+ },
38
+ "./esbuild": {
39
+ "import": "./src/esbuild.js"
40
+ },
41
+ "./parcel": {
42
+ "import": "./src/parcel.js"
43
+ },
44
+ "./turbopack": {
45
+ "import": "./src/turbopack.js"
46
+ },
47
+ "./cli": {
48
+ "import": "./generate-offline-manifest.js"
49
+ }
50
+ },
51
+ "files": [
52
+ "src",
53
+ "generate-offline-manifest.js",
54
+ "README.md",
55
+ "LICENSE"
56
+ ],
57
+ "scripts": {
58
+ "generate-offline": "node generate-offline-manifest.js",
59
+ "test": "echo \"Error: no test specified\" && exit 1"
60
+ },
61
+ "keywords": [
62
+ "despia",
63
+ "local",
64
+ "manifest",
65
+ "build",
66
+ "plugin",
67
+ "vite",
68
+ "webpack",
69
+ "rollup",
70
+ "nextjs",
71
+ "nuxt",
72
+ "sveltekit",
73
+ "astro",
74
+ "remix",
75
+ "esbuild",
76
+ "parcel",
77
+ "turbopack",
78
+ "bundler",
79
+ "pwa",
80
+ "caching"
81
+ ],
82
+ "author": "Despia",
83
+ "license": "MIT",
84
+ "repository": {
85
+ "type": "git",
86
+ "url": "git+https://github.com/despia-native/local.git"
87
+ },
88
+ "bugs": {
89
+ "url": "https://github.com/despia-native/local/issues"
90
+ },
91
+ "homepage": "https://github.com/despia-native/local#readme",
92
+ "engines": {
93
+ "node": ">=14.0.0"
94
+ }
95
+ }
package/src/astro.js ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Astro integration for generating despia/local.json manifest
3
+ *
4
+ * Usage in astro.config.mjs:
5
+ * import { defineConfig } from 'astro/config';
6
+ * import despiaOffline from '@despia/local/astro';
7
+ *
8
+ * export default defineConfig({
9
+ * integrations: [
10
+ * despiaOffline({ entryHtml: 'index.html' })
11
+ * ]
12
+ * });
13
+ */
14
+
15
+ import { generateManifest } from './core.js';
16
+ import { fileURLToPath } from 'url';
17
+
18
+ export default function despiaOfflineIntegration(options = {}) {
19
+ const { entryHtml = 'index.html', outDir = 'dist' } = options;
20
+
21
+ return {
22
+ name: 'despia-offline',
23
+ hooks: {
24
+ 'astro:build:done': async ({ dir }) => {
25
+ // Astro provides dir as a URL object, convert to path
26
+ let outputDir = outDir;
27
+ if (dir) {
28
+ // Handle both URL and string paths
29
+ if (typeof dir === 'string') {
30
+ outputDir = dir;
31
+ } else if (dir.pathname) {
32
+ outputDir = dir.pathname;
33
+ } else if (dir.href) {
34
+ // Convert file:// URL to path
35
+ outputDir = fileURLToPath(dir);
36
+ }
37
+ }
38
+
39
+ try {
40
+ const paths = generateManifest({
41
+ outputDir,
42
+ entryHtml
43
+ });
44
+ console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
45
+ } catch (error) {
46
+ console.error('Error generating despia/local.json:', error.message);
47
+ }
48
+ }
49
+ }
50
+ };
51
+ }
package/src/core.js ADDED
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Core utility functions for generating despia/local.json manifest
3
+ * Shared across all build tool plugins
4
+ */
5
+
6
+ import { readdirSync, statSync, writeFileSync, mkdirSync, existsSync } from 'fs';
7
+ import { join, relative, resolve } from 'path';
8
+
9
+ /**
10
+ * Recursively collect all files in a directory
11
+ * @param {string} dir - Directory to scan
12
+ * @param {string} baseDir - Base directory for relative paths
13
+ * @returns {string[]} Array of root-relative paths
14
+ */
15
+ export function collectFiles(dir, baseDir = dir) {
16
+ const files = [];
17
+
18
+ try {
19
+ const entries = readdirSync(dir);
20
+
21
+ for (const entry of entries) {
22
+ // Skip the despia directory itself to avoid recursion
23
+ if (entry === 'despia') {
24
+ continue;
25
+ }
26
+
27
+ const fullPath = join(dir, entry);
28
+ const stat = statSync(fullPath);
29
+
30
+ if (stat.isDirectory()) {
31
+ // Recursively scan subdirectories
32
+ files.push(...collectFiles(fullPath, baseDir));
33
+ } else if (stat.isFile()) {
34
+ // Get relative path from base directory
35
+ const relativePath = relative(baseDir, fullPath);
36
+ // Convert to root-relative path (starting with /)
37
+ const rootRelativePath = '/' + relativePath.replace(/\\/g, '/');
38
+ files.push(rootRelativePath);
39
+ }
40
+ }
41
+ } catch (error) {
42
+ // Silently skip directories that can't be read
43
+ }
44
+
45
+ return files;
46
+ }
47
+
48
+ /**
49
+ * Generate the offline manifest file
50
+ * @param {Object} options
51
+ * @param {string} options.outputDir - Output directory path
52
+ * @param {string} options.entryHtml - Entry HTML filename (default: 'index.html')
53
+ * @param {string[]} options.additionalPaths - Additional paths to include
54
+ * @returns {string[]} Array of all asset paths
55
+ */
56
+ export function generateManifest({ outputDir, entryHtml = 'index.html', additionalPaths = [] }) {
57
+ const outputPath = resolve(process.cwd(), outputDir);
58
+ const manifestPath = join(outputPath, 'despia', 'local.json');
59
+
60
+ // Check if output directory exists
61
+ if (!existsSync(outputPath)) {
62
+ throw new Error(`Output directory "${outputPath}" does not exist.`);
63
+ }
64
+
65
+ // Collect all files from the output directory
66
+ const assetPaths = new Set(collectFiles(outputPath, outputPath));
67
+
68
+ // Add any additional paths provided
69
+ additionalPaths.forEach(path => {
70
+ const normalizedPath = path.startsWith('/') ? path : '/' + path.replace(/\\/g, '/');
71
+ assetPaths.add(normalizedPath);
72
+ });
73
+
74
+ // Ensure entry HTML is included
75
+ const entryPath = entryHtml.startsWith('/')
76
+ ? entryHtml
77
+ : '/' + entryHtml;
78
+ assetPaths.add(entryPath);
79
+
80
+ // Convert to sorted array
81
+ const sortedPaths = Array.from(assetPaths).sort();
82
+
83
+ // Create despia directory if it doesn't exist
84
+ const despiaDir = join(outputPath, 'despia');
85
+ if (!existsSync(despiaDir)) {
86
+ mkdirSync(despiaDir, { recursive: true });
87
+ }
88
+
89
+ // Write formatted JSON array
90
+ const jsonContent = JSON.stringify(sortedPaths, null, 2);
91
+ writeFileSync(manifestPath, jsonContent, 'utf-8');
92
+
93
+ return sortedPaths;
94
+ }
package/src/esbuild.js ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * esbuild plugin for generating despia/local.json manifest
3
+ *
4
+ * Usage:
5
+ * import { build } from 'esbuild';
6
+ * import { despiaOfflineEsbuild } from '@despia/local/esbuild';
7
+ *
8
+ * await build({
9
+ * plugins: [despiaOfflineEsbuild({ outDir: 'dist' })]
10
+ * });
11
+ */
12
+
13
+ import { generateManifest } from './core.js';
14
+ import { relative } from 'path';
15
+
16
+ export function despiaOfflineEsbuild(options = {}) {
17
+ const { outDir = 'dist', entryHtml = 'index.html' } = options;
18
+
19
+ return {
20
+ name: 'despia-offline',
21
+ setup(build) {
22
+ build.onEnd(async (result) => {
23
+ if (result.errors.length > 0) {
24
+ return; // Don't generate manifest if build failed
25
+ }
26
+
27
+ const additionalPaths = [];
28
+ const actualOutDir = build.initialOptions.outdir || outDir;
29
+ const outDirPath = typeof actualOutDir === 'string' ? actualOutDir : outDir;
30
+
31
+ // Collect output files from esbuild result
32
+ if (result.outputFiles) {
33
+ for (const file of result.outputFiles) {
34
+ const filePath = file.path;
35
+ // Get relative path from output directory
36
+ try {
37
+ const relativePath = relative(outDirPath, filePath);
38
+ const rootRelativePath = '/' + relativePath.replace(/\\/g, '/');
39
+ additionalPaths.push(rootRelativePath);
40
+ } catch (e) {
41
+ // If relative path calculation fails, use filename
42
+ const filename = filePath.split(/[/\\]/).pop();
43
+ if (filename) {
44
+ additionalPaths.push('/' + filename);
45
+ }
46
+ }
47
+ }
48
+ }
49
+
50
+ try {
51
+ const paths = generateManifest({
52
+ outputDir: outDirPath,
53
+ entryHtml,
54
+ additionalPaths
55
+ });
56
+ console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
57
+ } catch (error) {
58
+ console.error('Error generating despia/local.json:', error.message);
59
+ }
60
+ });
61
+ }
62
+ };
63
+ }
package/src/index.js ADDED
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Main entry point for @despia/local
3
+ * Re-exports core functionality and all plugins
4
+ */
5
+
6
+ export { generateManifest, collectFiles } from './core.js';
7
+ export { despiaOfflinePlugin } from './vite.js';
8
+ export { default as DespiaOfflinePlugin } from './webpack.js';
9
+ export { despiaOffline } from './rollup.js';
10
+ export { withDespiaOffline } from './next.js';
11
+ export { default as DespiaOfflineModule } from './nuxt.js';
12
+ export { despiaOfflineSvelteKit } from './sveltekit.js';
13
+ export { default as despiaOfflineIntegration } from './astro.js';
14
+ export { despiaOfflineRemix } from './remix.js';
15
+ export { despiaOfflineEsbuild } from './esbuild.js';
16
+ export { default as DespiaOfflineParcel } from './parcel.js';
package/src/next.js ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Next.js integration for generating despia/local.json manifest
3
+ *
4
+ * Usage in next.config.js:
5
+ * const withDespiaOffline = require('@despia/local/next');
6
+ * module.exports = withDespiaOffline({
7
+ * entryHtml: 'index.html',
8
+ * outDir: '.next' // or 'out' for static export
9
+ * })({
10
+ * // your next config
11
+ * });
12
+ *
13
+ * Or use the webpack plugin approach:
14
+ * const DespiaOfflinePlugin = require('@despia/local/webpack');
15
+ * module.exports = {
16
+ * webpack: (config) => {
17
+ * config.plugins.push(new DespiaOfflinePlugin({ outDir: '.next' }));
18
+ * return config;
19
+ * }
20
+ * };
21
+ */
22
+
23
+ import { generateManifest } from './core.js';
24
+ import DespiaOfflinePlugin from './webpack.js';
25
+
26
+ export function withDespiaOffline(pluginOptions = {}) {
27
+ const offlineConfig = {
28
+ outDir: pluginOptions.outDir || '.next',
29
+ entryHtml: pluginOptions.entryHtml || 'index.html',
30
+ ...pluginOptions
31
+ };
32
+
33
+ return (nextConfig = {}) => {
34
+ const existingWebpack = nextConfig.webpack;
35
+
36
+ return {
37
+ ...nextConfig,
38
+ webpack: (config, options) => {
39
+ // Add Despia Offline plugin
40
+ config.plugins.push(
41
+ new DespiaOfflinePlugin({
42
+ outDir: offlineConfig.outDir,
43
+ entryHtml: offlineConfig.entryHtml
44
+ })
45
+ );
46
+
47
+ // Call existing webpack config if present
48
+ if (typeof existingWebpack === 'function') {
49
+ return existingWebpack(config, options);
50
+ }
51
+
52
+ return config;
53
+ }
54
+ };
55
+ };
56
+ }
57
+
58
+ // Also export as CommonJS for Next.js compatibility
59
+ export default withDespiaOffline;
package/src/nuxt.js ADDED
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Nuxt module for generating despia/local.json manifest
3
+ *
4
+ * Usage in nuxt.config.js:
5
+ * export default {
6
+ * modules: ['@despia/local/nuxt'],
7
+ * despiaOffline: {
8
+ * entryHtml: 'index.html'
9
+ * }
10
+ * }
11
+ *
12
+ * Or use as a Nuxt module in modules/ directory:
13
+ * // modules/despia-offline.js
14
+ * import DespiaOfflineModule from '@despia/local/nuxt';
15
+ * export default DespiaOfflineModule;
16
+ */
17
+
18
+ import { generateManifest } from './core.js';
19
+
20
+ export default function DespiaOfflineModule(moduleOptions) {
21
+ const options = {
22
+ entryHtml: 'index.html',
23
+ ...moduleOptions,
24
+ ...(this.options?.despiaOffline || {})
25
+ };
26
+
27
+ // Hook into Nuxt build completion
28
+ this.nuxt.hook('build:done', async (builder) => {
29
+ // Determine output directory based on Nuxt mode
30
+ let outputDir;
31
+
32
+ if (this.options.generate) {
33
+ // Static site generation
34
+ outputDir = '.output/public';
35
+ } else if (this.options.target === 'static') {
36
+ outputDir = 'dist';
37
+ } else {
38
+ // SSR mode - assets are in .nuxt/dist/client
39
+ outputDir = '.nuxt/dist/client';
40
+ }
41
+
42
+ // Try to generate manifest
43
+ const altDirs = [outputDir, '.output/public', 'dist', '.nuxt/dist'];
44
+ let generated = false;
45
+
46
+ for (const dir of altDirs) {
47
+ try {
48
+ const paths = generateManifest({
49
+ outputDir: dir,
50
+ entryHtml: options.entryHtml
51
+ });
52
+ console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
53
+ generated = true;
54
+ break;
55
+ } catch (e) {
56
+ // Continue to next directory
57
+ }
58
+ }
59
+
60
+ if (!generated) {
61
+ console.warn('⚠ Could not find Nuxt build output directory');
62
+ }
63
+ });
64
+ }
package/src/parcel.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Parcel plugin for generating despia/local.json manifest
3
+ *
4
+ * Note: Parcel 2.x uses a different plugin system. For Parcel projects,
5
+ * we recommend using the standalone CLI script instead:
6
+ *
7
+ * "scripts": {
8
+ * "build": "parcel build",
9
+ * "postbuild": "despia-offline dist"
10
+ * }
11
+ *
12
+ * For Parcel 1.x, you can use this plugin, but the API may vary.
13
+ */
14
+
15
+ import { generateManifest } from './core.js';
16
+
17
+ // Parcel 2.x plugin format
18
+ export default function(api) {
19
+ const { outDir = 'dist', entryHtml = 'index.html' } = api.options || {};
20
+
21
+ return {
22
+ name: 'despia-offline',
23
+ async bundleEnd({ bundleGraph }) {
24
+ const additionalPaths = [];
25
+
26
+ // Collect all bundles
27
+ if (bundleGraph && bundleGraph.getBundles) {
28
+ bundleGraph.getBundles().forEach(bundle => {
29
+ const filePath = bundle.filePath || bundle.name;
30
+ if (filePath) {
31
+ const rootRelativePath = '/' + filePath.replace(/\\/g, '/');
32
+ additionalPaths.push(rootRelativePath);
33
+ }
34
+ });
35
+ }
36
+
37
+ try {
38
+ const paths = generateManifest({
39
+ outputDir: api.options?.outDir || outDir,
40
+ entryHtml,
41
+ additionalPaths
42
+ });
43
+ console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
44
+ } catch (error) {
45
+ console.error('Error generating despia/local.json:', error.message);
46
+ console.warn('💡 Tip: For Parcel projects, use the standalone CLI: "despia-offline dist"');
47
+ }
48
+ }
49
+ };
50
+ }
package/src/remix.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Remix integration for generating despia/local.json manifest
3
+ *
4
+ * Usage in remix.config.js or vite.config.js:
5
+ * import { remix } from '@remix-run/dev';
6
+ * import { despiaOfflineRemix } from '@despia/local/remix';
7
+ *
8
+ * export default {
9
+ * plugins: [
10
+ * remix(),
11
+ * despiaOfflineRemix({ entryHtml: 'index.html' })
12
+ * ]
13
+ * }
14
+ */
15
+
16
+ import { generateManifest } from './core.js';
17
+
18
+ export function despiaOfflineRemix(options = {}) {
19
+ const { entryHtml = 'index.html', outDir = 'build/client' } = options;
20
+
21
+ return {
22
+ name: 'despia-offline-remix',
23
+ apply: 'build',
24
+ buildEnd() {
25
+ // Remix outputs to build/client for client assets
26
+ const outputDirs = ['build/client', 'build', 'public/build'];
27
+
28
+ for (const outputDir of outputDirs) {
29
+ try {
30
+ const paths = generateManifest({
31
+ outputDir,
32
+ entryHtml
33
+ });
34
+ console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
35
+ return; // Success, exit
36
+ } catch (error) {
37
+ // Try next directory
38
+ continue;
39
+ }
40
+ }
41
+
42
+ console.warn('⚠ Could not find Remix build output directory');
43
+ }
44
+ };
45
+ }
package/src/rollup.js ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Rollup plugin for generating despia/local.json manifest
3
+ */
4
+
5
+ import { generateManifest } from './core.js';
6
+
7
+ /**
8
+ * Rollup plugin to generate despia/local.json manifest
9
+ * @param {Object} options
10
+ * @param {string} options.outDir - Output directory (default: 'dist')
11
+ * @param {string} options.entryHtml - Entry HTML file (default: 'index.html')
12
+ */
13
+ export function despiaOffline(options = {}) {
14
+ const { outDir = 'dist', entryHtml = 'index.html' } = options;
15
+
16
+ return {
17
+ name: 'despia-offline',
18
+ writeBundle(outputOptions, bundle) {
19
+ const outputDir = outputOptions.dir || outDir;
20
+ const additionalPaths = [];
21
+
22
+ // Collect paths from bundle
23
+ for (const [fileName, chunk] of Object.entries(bundle)) {
24
+ if (chunk.type === 'asset' || chunk.type === 'chunk') {
25
+ const filePath = chunk.fileName || fileName;
26
+ const rootRelativePath = filePath.startsWith('/')
27
+ ? filePath
28
+ : '/' + filePath.replace(/\\/g, '/');
29
+ additionalPaths.push(rootRelativePath);
30
+ }
31
+ }
32
+
33
+ try {
34
+ const paths = generateManifest({
35
+ outputDir,
36
+ entryHtml,
37
+ additionalPaths
38
+ });
39
+ console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
40
+ } catch (error) {
41
+ console.error('Error generating despia/local.json:', error.message);
42
+ }
43
+ }
44
+ };
45
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * SvelteKit adapter for generating despia/local.json manifest
3
+ *
4
+ * Usage in vite.config.js:
5
+ * import { sveltekit } from '@sveltejs/kit/vite';
6
+ * import { despiaOfflineSvelteKit } from '@despia/local/sveltekit';
7
+ *
8
+ * export default {
9
+ * plugins: [
10
+ * sveltekit(),
11
+ * despiaOfflineSvelteKit({ entryHtml: 'index.html' })
12
+ * ]
13
+ * }
14
+ */
15
+
16
+ import { generateManifest } from './core.js';
17
+
18
+ export function despiaOfflineSvelteKit(options = {}) {
19
+ const { entryHtml = 'index.html' } = options;
20
+
21
+ return {
22
+ name: 'despia-offline-sveltekit',
23
+ apply: 'build',
24
+ buildEnd() {
25
+ // SvelteKit outputs to build directory
26
+ const outputDirs = ['build', '.svelte-kit', 'dist'];
27
+
28
+ for (const outputDir of outputDirs) {
29
+ try {
30
+ const paths = generateManifest({
31
+ outputDir,
32
+ entryHtml
33
+ });
34
+ console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
35
+ return; // Success, exit
36
+ } catch (error) {
37
+ // Try next directory
38
+ continue;
39
+ }
40
+ }
41
+
42
+ console.warn('⚠ Could not find SvelteKit build output directory');
43
+ }
44
+ };
45
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Turbopack plugin for generating despia/local.json manifest
3
+ * Note: Turbopack is Next.js's new bundler. Use the Next.js integration instead.
4
+ * This is a placeholder for future Turbopack-specific optimizations.
5
+ */
6
+
7
+ import { generateManifest } from './core.js';
8
+
9
+ export function despiaOfflineTurbopack(options = {}) {
10
+ const { entryHtml = 'index.html', outDir = '.next' } = options;
11
+
12
+ // Turbopack is used by Next.js, so we recommend using the Next.js integration
13
+ // This is kept for potential future Turbopack-specific features
14
+ console.warn('⚠ Turbopack: Use @despia/local/next instead for Next.js projects');
15
+
16
+ return {
17
+ name: 'despia-offline-turbopack',
18
+ // Implementation would go here when Turbopack plugin API is stable
19
+ };
20
+ }