@expofp/offline 3.0.0-alpha.8 → 3.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.
package/README.md ADDED
@@ -0,0 +1,31 @@
1
+ ## Offline: generating ZIPs
2
+
3
+ The `@expofp/offline` package provides a CLI (`expofp-offline`) that creates self-contained ZIP archives of floor plans. The ZIP can be opened in any browser without internet connectivity.
4
+
5
+ ### Usage
6
+
7
+ ```bash
8
+ npx @expofp/offline@<tag-or-version> <manifest-url> [options]
9
+ ```
10
+
11
+ Use a dist-tag or exact version to match the deployed floor plan (e.g. `@latest`, `@next`, `@customer1`, `@3.0.0-alpha.9`).
12
+
13
+ | Option | Description | Default |
14
+ | ----------------- | ------------------- | ------------- |
15
+ | `-o`, `--output` | Output file path | `offline.zip` |
16
+ | `-h`, `--help` | Show help message | |
17
+ | `-v`, `--version` | Show version number | |
18
+
19
+ ### Example
20
+
21
+ ```bash
22
+ npx @expofp/offline@next https://demo.expofp.com/manifest.json -o offline.zip
23
+ ```
24
+
25
+ ### Debugging
26
+
27
+ Enable debug output with the `DEBUG` env variable:
28
+
29
+ ```bash
30
+ DEBUG=efp:offline:* npx @expofp/offline https://...
31
+ ```
@@ -1,5 +1,5 @@
1
1
  import { resolve } from '@expofp/resolve';
2
- import { deepClone, makeLocalPath, niceFetch } from '@expofp/utils';
2
+ import { deepClone, fetchWithRetry, makeLocalPath } from '@expofp/utils';
3
3
  import debug from 'debug';
4
4
  import { offlinizeAssetsInPlace } from './offlinize-assets-in-place.js';
5
5
  const log = debug('efp:offline:generate-offline-data-legacy');
@@ -53,7 +53,7 @@ export async function* generateOfflineDataLegacy(manifest, options) {
53
53
  * (the function itself is Node-only).
54
54
  */
55
55
  async function execScriptInSandbox(url, signal) {
56
- const response = await niceFetch(url, { signal });
56
+ const response = await fetchWithRetry(url, { signal });
57
57
  if (!response.ok) {
58
58
  throw new Error(`Failed to fetch script (HTTP ${response.status}): ${url}`);
59
59
  }
@@ -9,20 +9,35 @@ const log = debug('efp:offline:generate-offline-data');
9
9
  export async function* generateOfflineData(inputManifest, options) {
10
10
  const manifest = deepClone(await resolve(inputManifest));
11
11
  log(`Generating offline files for expo: ${manifest.expo}`);
12
+ const pathPrefix = manifest.expo;
13
+ // this is a requirement of mobile SDK, paths in the ZIP must be prefixed with the expo name
14
+ // TODO: make sure SDK works with unprefixed paths, and remove the prefixing logic from this function
15
+ const prefixPath = (path) => `${pathPrefix}/${path}`;
12
16
  if (manifest.legacyDataUrlBase) {
13
- yield* generateOfflineDataLegacy(manifest, options);
17
+ for await (const d of generateOfflineDataLegacy(manifest, options)) {
18
+ yield { ...d, targetFilePath: prefixPath(d.targetFilePath) };
19
+ }
14
20
  }
15
21
  // transform entire manifest + assets
16
22
  const data = await offlinizeAssetsInPlace(manifest, options);
17
23
  for (const d of data) {
18
- yield d;
24
+ yield { ...d, targetFilePath: prefixPath(d.targetFilePath) };
19
25
  }
20
- // add runtime files
21
- const { entry } = yield* generateRuntimeFilesData(options.runtimeBaseUrl);
26
+ // add runtime files — manually iterate to prefix paths while capturing return value
27
+ const runtimeGen = generateRuntimeFilesData(options.runtimeBaseUrl);
28
+ let runtimeResult = await runtimeGen.next();
29
+ while (!runtimeResult.done) {
30
+ yield {
31
+ ...runtimeResult.value,
32
+ targetFilePath: prefixPath(runtimeResult.value.targetFilePath),
33
+ };
34
+ runtimeResult = await runtimeGen.next();
35
+ }
36
+ const { entry } = runtimeResult.value;
22
37
  // generate index.html
23
38
  yield {
24
39
  text: getIndexHtml(entry, manifest),
25
- targetFilePath: './index.html',
40
+ targetFilePath: prefixPath('index.html'),
26
41
  };
27
42
  }
28
43
  /** Build a complete offline ZIP archive from a manifest. */
@@ -31,13 +46,17 @@ export async function buildOfflineZip(manifest, options) {
31
46
  const files = dataToFiles(data, { signal: options.signal });
32
47
  return await buildZipArchive(files);
33
48
  }
49
+ /** Known path — file lives in `packages/floorplan/public/` and is copied to dist by Vite. */
50
+ const COMPAT_HELPER = 'compat-helper.js';
34
51
  function getIndexHtml(entry, manifest) {
52
+ // entry is "./runtime/index.js", derive the runtime base path
53
+ const runtimeBase = entry.slice(0, entry.lastIndexOf('/') + 1);
35
54
  return `\
36
55
  <!DOCTYPE html>
56
+ <script src="${runtimeBase}${COMPAT_HELPER}"></script>
37
57
  <script type="module">
38
58
  import { load } from ${JSON.stringify(entry)};
39
59
  await load(${JSON.stringify(manifest)});
40
- console.info('loaded');
41
60
  </script>
42
61
  `;
43
62
  }
@@ -1,16 +1,16 @@
1
- import { importJsonModule } from '@expofp/utils';
1
+ import { importJson } from '@expofp/utils';
2
2
  import debug from 'debug';
3
3
  const log = debug('efp:offline:generate-runtime-files-data');
4
4
  export async function* generateRuntimeFilesData(runtimeBaseUrl) {
5
5
  const bundleJsonUrl = new URL('bundle.json', runtimeBaseUrl).href;
6
- const bundleJson = await importJsonModule(bundleJsonUrl);
6
+ const bundleJson = await importJson(bundleJsonUrl);
7
7
  log(`Generating runtime files from ${bundleJsonUrl}, ${bundleJson.files.length} files found`);
8
- const basePath = './runtime/';
8
+ const basePath = 'runtime/';
9
9
  for (const file of bundleJson.files) {
10
10
  yield {
11
11
  url: new URL(file.file, runtimeBaseUrl),
12
12
  targetFilePath: `${basePath}${file.file}`,
13
13
  };
14
14
  }
15
- return { entry: `${basePath}${bundleJson.entry}` };
15
+ return { entry: `./${basePath}${bundleJson.entry}` };
16
16
  }
@@ -1,4 +1,4 @@
1
- import { makeLocalPath, niceFetch } from '@expofp/utils';
1
+ import { fetchWithRetry, makeLocalPath } from '@expofp/utils';
2
2
  import debug from 'debug';
3
3
  import { offlinizeAssetUrl } from './offlinize-asset-url.js';
4
4
  import { offlinizeCssAssetText } from './offlinize-css-asset-text.js';
@@ -66,7 +66,7 @@ export async function offlinizeAssetsInPlace(data, opts) {
66
66
  // Mark as fetched to prevent duplicates
67
67
  log(`Offlinizing $ref: ${refValue} -> ${targetFilePath}${fragment}`);
68
68
  // Fetch and recursively process the referenced document
69
- const response = await niceFetch(urlKey, { signal: opts.signal });
69
+ const response = await fetchWithRetry(urlKey, { signal: opts.signal });
70
70
  if (response.ok) {
71
71
  const referencedData = await response.json();
72
72
  processedRefs.set(urlKey, referencedData);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@expofp/offline",
3
- "version": "3.0.0-alpha.8",
3
+ "version": "3.0.0",
4
4
  "type": "module",
5
5
  "description": "CLI tool for creating offline copies of ExpoFP floor plans",
6
6
  "license": "MIT",
@@ -30,9 +30,9 @@
30
30
  "dependencies": {
31
31
  "debug": "^4.4.3",
32
32
  "tslib": "^2.3.0",
33
- "@expofp/data": "3.0.0-alpha.8",
34
- "@expofp/floorplan": "3.0.0-alpha.8",
35
- "@expofp/resolve": "3.0.0-alpha.8",
36
- "@expofp/utils": "3.0.0-alpha.8"
33
+ "@expofp/data": "3.0.0",
34
+ "@expofp/floorplan": "3.0.0",
35
+ "@expofp/resolve": "3.0.0",
36
+ "@expofp/utils": "3.0.0"
37
37
  }
38
38
  }