@livx.cc/appwrap 0.61.3 → 0.61.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/cli.ts +38 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@livx.cc/appwrap",
3
- "version": "0.61.3",
3
+ "version": "0.61.4",
4
4
  "description": "Wrap any PWA into a native app with native capabilities (appwrap runtime + @livx.cc/native-kit).",
5
5
  "license": "MIT",
6
6
  "author": "Elya Livshitz",
package/src/cli.ts CHANGED
@@ -423,13 +423,45 @@ export function replaceImportSpecifier(src: string, oldSpec: string, newSpec: st
423
423
  return src.replace(new RegExp(`(['"])${esc}\\1`, 'g'), `$1${newSpec}$1`);
424
424
  }
425
425
 
426
+ /**
427
+ * Pick the staged filename for one pack source file, GUARANTEEING no two files share it.
428
+ *
429
+ * Staged names used to be `pack-<label>-<basename>.ts` with no collision check, while the handler
430
+ * entry is named after the MODULE. So a pack module named `widget` that imported a helper whose
431
+ * basename was also `widget` (e.g. `./kit/widget`) produced two files with one name: the helper was
432
+ * written, the handler overwrote it, and the handler's rewritten import then pointed at itself. The
433
+ * build failed with `TS2303: Circular definition of import alias` inside a generated file that exists
434
+ * in no repo — no error named the pack, the helper, or the collision. Blank's widget pack hit exactly
435
+ * this and shipped nothing to TestFlight while every local check stayed green.
436
+ *
437
+ * On a clash the pack-RELATIVE path is used instead (`kit/widget` → `kit_widget`), which is stable
438
+ * across staging order rather than dependent on it; a further clash falls back to a counter. Pure and
439
+ * exported for tests.
440
+ */
441
+ export function claimStagedName(label: string, name: string, absFile: string, packDir: string, claimed: Map<string, string>): string {
442
+ const mk = (n: string) => `pack-${label}-${n}.ts`;
443
+ const free = (n: string) => {
444
+ const owner = claimed.get(n);
445
+ return owner === undefined || owner === absFile;
446
+ };
447
+ let dest = mk(name);
448
+ if (!free(dest)) {
449
+ const rel = absFile.startsWith(packDir + '/') ? absFile.slice(packDir.length + 1) : basename(absFile);
450
+ dest = mk(rel.replace(/\.(tsx?|jsx?)$/, '').replace(/[^a-zA-Z0-9_-]/g, '_'));
451
+ for (let i = 2; !free(dest); i++) dest = mk(`${name}-${i}`);
452
+ console.warn(` ⚠ pack "${label}": staged name collision on "${mk(name)}" — using "${dest}" for ${absFile}`);
453
+ }
454
+ claimed.set(dest, absFile);
455
+ return dest;
456
+ }
457
+
426
458
  /** Stage a pack source file into the shell, namespaced `pack-<label>-<name>.ts` (never colliding with a
427
459
  * built-in file — keeps built-in staged filenames stable, preserving the byte-compare gate). Rewrites
428
460
  * (a) shell-API imports → relative `./<x>`, and (b) pack-LOCAL relative imports → their namespaced
429
461
  * staged sibling, staging each RECURSIVELY — so a handler's own helper modules (e.g. billing's
430
462
  * billing-offer.ts) are staged too. `staged` memoizes by abs path (dedup + cycle guard). `entryName`
431
463
  * overrides the staged basename for the handler entry (so the barrel imports it by module identity). */
432
- function stagePackFile(shell: string, label: string, packDir: string, absFile: string, staged: Map<string, string>, entryName?: string): string {
464
+ function stagePackFile(shell: string, label: string, packDir: string, absFile: string, staged: Map<string, string>, claimed: Map<string, string>, entryName?: string): string {
433
465
  const cached = staged.get(absFile);
434
466
  if (cached) return cached;
435
467
  if (!existsSync(absFile)) {
@@ -437,7 +469,7 @@ function stagePackFile(shell: string, label: string, packDir: string, absFile: s
437
469
  process.exit(1);
438
470
  }
439
471
  const name = entryName ?? basename(absFile).replace(/\.(tsx?|jsx?)$/, '');
440
- const destName = `pack-${label}-${name}.ts`;
472
+ const destName = claimStagedName(label, name, absFile, packDir, claimed);
441
473
  const spec = `./${destName.replace(/\.ts$/, '')}`;
442
474
  staged.set(absFile, spec); // set BEFORE recursing so an import cycle terminates
443
475
 
@@ -447,7 +479,7 @@ function stagePackFile(shell: string, label: string, packDir: string, absFile: s
447
479
  if (!imp.path.startsWith('.')) continue; // only pack-LOCAL relative imports need staging
448
480
  const depAbs = resolvePackImport(fromDir, imp.path);
449
481
  if (!depAbs || !depAbs.startsWith(packDir + '/')) continue; // must stay inside the pack dir
450
- const depSpec = stagePackFile(shell, label, packDir, depAbs, staged);
482
+ const depSpec = stagePackFile(shell, label, packDir, depAbs, staged, claimed);
451
483
  src = replaceImportSpecifier(src, imp.path, depSpec);
452
484
  }
453
485
  writeFileSync(join(shell, destName), src);
@@ -463,7 +495,9 @@ function stagePackHandler(shell: string, h: NativeReqs['packHandlers'][number]):
463
495
  process.exit(1);
464
496
  }
465
497
  const label = h.source.replace(/[^a-zA-Z0-9_-]/g, '_');
466
- const spec = stagePackFile(shell, label, h.packDir, srcFile, new Map(), h.name);
498
+ // The handler is staged FIRST, so it CLAIMS `pack-<label>-<moduleName>.ts` before any
499
+ // dependency can be assigned the same filename (see claimStagedName).
500
+ const spec = stagePackFile(shell, label, h.packDir, srcFile, new Map(), new Map(), h.name);
467
501
  console.log(` pack ← ${h.source} (${h.name} handler)`);
468
502
  return spec;
469
503
  }