voltra 2.0.0 → 2.1.1

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 (84) hide show
  1. package/build/cjs/config/normalize.js +93 -0
  2. package/build/cjs/config/normalize.js.map +1 -1
  3. package/build/cjs/index.js +5 -2
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/cjs/platforms/android/apply.js +11 -2
  6. package/build/cjs/platforms/android/apply.js.map +1 -1
  7. package/build/cjs/platforms/android/generated.js +152 -130
  8. package/build/cjs/platforms/android/generated.js.map +1 -1
  9. package/build/cjs/platforms/android/gradle.js +120 -0
  10. package/build/cjs/platforms/android/gradle.js.map +1 -0
  11. package/build/cjs/platforms/ios/generated.js +246 -142
  12. package/build/cjs/platforms/ios/generated.js.map +1 -1
  13. package/build/cjs/platforms/ios/xcodeTarget.js +68 -0
  14. package/build/cjs/platforms/ios/xcodeTarget.js.map +1 -1
  15. package/build/cjs/platforms/shared/widgetModule.js +151 -0
  16. package/build/cjs/platforms/shared/widgetModule.js.map +1 -0
  17. package/build/esm/config/normalize.js +93 -0
  18. package/build/esm/config/normalize.js.map +1 -1
  19. package/build/esm/index.js +1 -0
  20. package/build/esm/index.js.map +1 -1
  21. package/build/esm/platforms/android/apply.js +11 -2
  22. package/build/esm/platforms/android/apply.js.map +1 -1
  23. package/build/esm/platforms/android/generated.js +152 -97
  24. package/build/esm/platforms/android/generated.js.map +1 -1
  25. package/build/esm/platforms/android/gradle.js +112 -0
  26. package/build/esm/platforms/android/gradle.js.map +1 -0
  27. package/build/esm/platforms/ios/generated.js +246 -109
  28. package/build/esm/platforms/ios/generated.js.map +1 -1
  29. package/build/esm/platforms/ios/xcodeTarget.js +69 -1
  30. package/build/esm/platforms/ios/xcodeTarget.js.map +1 -1
  31. package/build/esm/platforms/shared/widgetModule.js +111 -0
  32. package/build/esm/platforms/shared/widgetModule.js.map +1 -0
  33. package/build/types/config/normalize.d.ts.map +1 -1
  34. package/build/types/config/types.d.ts +32 -0
  35. package/build/types/config/types.d.ts.map +1 -1
  36. package/build/types/index.d.ts +3 -1
  37. package/build/types/index.d.ts.map +1 -1
  38. package/build/types/platforms/android/apply.d.ts.map +1 -1
  39. package/build/types/platforms/android/generated.d.ts.map +1 -1
  40. package/build/types/platforms/android/gradle.d.ts +18 -0
  41. package/build/types/platforms/android/gradle.d.ts.map +1 -0
  42. package/build/types/platforms/ios/generated.d.ts.map +1 -1
  43. package/build/types/platforms/ios/xcodeTarget.d.ts.map +1 -1
  44. package/build/types/platforms/shared/widgetModule.d.ts +9 -0
  45. package/build/types/platforms/shared/widgetModule.d.ts.map +1 -0
  46. package/package.json +2 -1
  47. package/src/apply/index.ts +309 -0
  48. package/src/apply/preflight.ts +172 -0
  49. package/src/bin.ts +14 -0
  50. package/src/commands/apply.ts +114 -0
  51. package/src/config/defaults.ts +34 -0
  52. package/src/config/load.ts +100 -0
  53. package/src/config/normalize.ts +562 -0
  54. package/src/config/types.ts +317 -0
  55. package/src/cosmiconfig.d.ts +21 -0
  56. package/src/dependencies/platformPackages.ts +75 -0
  57. package/src/discovery/android.ts +233 -0
  58. package/src/discovery/ios.ts +579 -0
  59. package/src/fs/path.ts +25 -0
  60. package/src/fs/readWrite.ts +102 -0
  61. package/src/git/status.ts +205 -0
  62. package/src/index.ts +204 -0
  63. package/src/platforms/android/apply.ts +106 -0
  64. package/src/platforms/android/generated.ts +1284 -0
  65. package/src/platforms/android/gradle.ts +155 -0
  66. package/src/platforms/android/manifest.ts +379 -0
  67. package/src/platforms/ios/apply.ts +139 -0
  68. package/src/platforms/ios/deploymentTarget.ts +47 -0
  69. package/src/platforms/ios/entitlements.ts +146 -0
  70. package/src/platforms/ios/generated.ts +1233 -0
  71. package/src/platforms/ios/mainAppEntitlements.ts +11 -0
  72. package/src/platforms/ios/plist.ts +321 -0
  73. package/src/platforms/ios/podfile.ts +255 -0
  74. package/src/platforms/ios/targetName.ts +11 -0
  75. package/src/platforms/ios/xcode.ts +181 -0
  76. package/src/platforms/ios/xcodeTarget.ts +899 -0
  77. package/src/platforms/shared/widgetModule.ts +153 -0
  78. package/src/reporting/clack.ts +67 -0
  79. package/src/reporting/summary.ts +116 -0
  80. package/src/state/diff.ts +21 -0
  81. package/src/state/files.ts +52 -0
  82. package/src/state/load.ts +66 -0
  83. package/src/state/save.ts +31 -0
  84. package/src/xml2js.d.ts +23 -0
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ensureAndroidGradleWidgetBundling = ensureAndroidGradleWidgetBundling;
7
+ exports.addDynamicWidgetBundlingSnippet = addDynamicWidgetBundlingSnippet;
8
+ exports.removeDynamicWidgetBundlingSnippet = removeDynamicWidgetBundlingSnippet;
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ const path_1 = require("../../fs/path");
11
+ const readWrite_1 = require("../../fs/readWrite");
12
+ const MARKER = '// @voltra-widget-bundling';
13
+ const END_MARKER = '// @voltra-widget-bundling-end';
14
+ async function ensureAndroidGradleWidgetBundling(options) {
15
+ const { projectRoot, discovery, hasDynamicWidgets } = options;
16
+ const isKotlinDsl = discovery.buildGradlePath.endsWith('.kts');
17
+ const projectRootRelativeToAppModule = (0, path_1.normalizeRelativePath)(node_path_1.default.relative(discovery.appModuleRoot, projectRoot)) || '.';
18
+ if (isKotlinDsl) {
19
+ if (!hasDynamicWidgets) {
20
+ return {};
21
+ }
22
+ return {
23
+ warnings: ['Voltra: app/build.gradle is not Groovy - skipping Dynamic Widget release bundling wiring.'],
24
+ };
25
+ }
26
+ const previousContent = await (0, readWrite_1.readTextFile)(discovery.buildGradlePath);
27
+ const removal = removeDynamicWidgetBundlingSnippet(previousContent, projectRootRelativeToAppModule);
28
+ const nextContent = hasDynamicWidgets
29
+ ? addDynamicWidgetBundlingSnippet(removal.contents, projectRootRelativeToAppModule)
30
+ : removal.contents;
31
+ if (nextContent === previousContent) {
32
+ return removal.warning ? { warnings: [removal.warning] } : {};
33
+ }
34
+ await (0, readWrite_1.writeTextFile)(discovery.buildGradlePath, nextContent);
35
+ return {
36
+ change: {
37
+ kind: 'updated',
38
+ path: (0, path_1.toRelativePath)(projectRoot, discovery.buildGradlePath),
39
+ },
40
+ warnings: removal.warning ? [removal.warning] : undefined,
41
+ };
42
+ }
43
+ function addDynamicWidgetBundlingSnippet(contents, projectRootRelativeToAppModule) {
44
+ if (contents.includes(MARKER)) {
45
+ return contents;
46
+ }
47
+ return `${contents.trimEnd()}\n\n${createDynamicWidgetBundlingSnippet(projectRootRelativeToAppModule)}\n`;
48
+ }
49
+ function removeDynamicWidgetBundlingSnippet(contents, projectRootRelativeToAppModule) {
50
+ const range = findManagedSnippetRange(contents);
51
+ if (range) {
52
+ return {
53
+ contents: collapseRemovedRange(contents, range.start, range.end),
54
+ };
55
+ }
56
+ const legacySnippet = createLegacyDynamicWidgetBundlingSnippet(projectRootRelativeToAppModule);
57
+ const legacyIndex = contents.indexOf(legacySnippet);
58
+ if (legacyIndex >= 0) {
59
+ return {
60
+ contents: collapseRemovedRange(contents, legacyIndex, legacyIndex + legacySnippet.length),
61
+ };
62
+ }
63
+ if (!contents.includes(MARKER)) {
64
+ return { contents };
65
+ }
66
+ return {
67
+ contents,
68
+ warning: 'Voltra: found legacy Dynamic Widget Gradle marker without managed end marker. Preserved trailing Gradle content; remove old @voltra-widget-bundling block manually if no longer needed.',
69
+ };
70
+ }
71
+ function createDynamicWidgetBundlingSnippet(projectRootRelativeToAppModule) {
72
+ return `
73
+ ${MARKER}
74
+ def voltraProjectRoot = file("${projectRootRelativeToAppModule}")
75
+ def voltraWidgetAssetsDir = file("\${buildDir}/generated/voltra/assets")
76
+ android.sourceSets.getByName("main").assets.srcDir(voltraWidgetAssetsDir)
77
+
78
+ def voltraBundleWidgets = tasks.register("voltraBundleWidgets", Exec) {
79
+ description = "Bake Dynamic Widget Voltra bundles into assets (release)."
80
+ workingDir voltraProjectRoot
81
+ environment "VOLTRA_PROJECT_ROOT", voltraProjectRoot.absolutePath
82
+ environment "VOLTRA_OUT_DIR", new File(voltraWidgetAssetsDir, "voltra").absolutePath
83
+ environment "VOLTRA_BUNDLER_RESOLVER", "const m=require('module'),p=require('path');process.stdout.write(m.createRequire(p.join(process.argv[1],'package.json')).resolve('@use-voltra/metro/bundle-widgets'))"
84
+ commandLine "bash", "-c", 'BUNDLER="$(node -e "$VOLTRA_BUNDLER_RESOLVER" "$VOLTRA_PROJECT_ROOT")"; if [ -z "$BUNDLER" ]; then echo "error: Voltra could not resolve @use-voltra/metro/bundle-widgets from $VOLTRA_PROJECT_ROOT - install @use-voltra/metro so release widgets can be baked." >&2; exit 1; fi; node "$BUNDLER" --platform android --out-dir "$VOLTRA_OUT_DIR" --project-root "$VOLTRA_PROJECT_ROOT"'
85
+ outputs.upToDateWhen { false }
86
+ }
87
+
88
+ android.applicationVariants.all { variant ->
89
+ if (variant.buildType.name == "release") {
90
+ tasks.named("merge\${variant.name.capitalize()}Assets").configure { dependsOn voltraBundleWidgets }
91
+ }
92
+ }
93
+ ${END_MARKER}
94
+ `.trim();
95
+ }
96
+ function createLegacyDynamicWidgetBundlingSnippet(projectRootRelativeToAppModule) {
97
+ return createDynamicWidgetBundlingSnippet(projectRootRelativeToAppModule).replace(`\n${END_MARKER}`, '');
98
+ }
99
+ function findManagedSnippetRange(contents) {
100
+ const markerIndex = contents.indexOf(MARKER);
101
+ if (markerIndex < 0) {
102
+ return null;
103
+ }
104
+ const endMarkerIndex = contents.indexOf(END_MARKER, markerIndex);
105
+ if (endMarkerIndex < 0) {
106
+ return null;
107
+ }
108
+ const lineEndIndex = contents.indexOf('\n', endMarkerIndex);
109
+ return {
110
+ start: markerIndex,
111
+ end: lineEndIndex >= 0 ? lineEndIndex + 1 : contents.length,
112
+ };
113
+ }
114
+ function collapseRemovedRange(contents, start, end) {
115
+ const before = contents.slice(0, start).trimEnd();
116
+ const after = contents.slice(end).replace(/^\s+/, '');
117
+ const merged = [before, after].filter(Boolean).join('\n\n');
118
+ return merged ? `${merged}\n` : '';
119
+ }
120
+ //# sourceMappingURL=gradle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gradle.js","sourceRoot":"","sources":["../../../../src/platforms/android/gradle.ts"],"names":[],"mappings":";;;;;AAsBA,8EAqCC;AAED,0EAMC;AAED,gFA8BC;AAnGD,0DAA4B;AAE5B,wCAAqE;AACrE,kDAAgE;AAKhE,MAAM,MAAM,GAAG,4BAA4B,CAAA;AAC3C,MAAM,UAAU,GAAG,gCAAgC,CAAA;AAa5C,KAAK,UAAU,iCAAiC,CACrD,OAAiD;IAEjD,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,iBAAiB,EAAE,GAAG,OAAO,CAAA;IAC7D,MAAM,WAAW,GAAG,SAAS,CAAC,eAAe,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC9D,MAAM,8BAA8B,GAClC,IAAA,4BAAqB,EAAC,mBAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC,IAAI,GAAG,CAAA;IAEnF,IAAI,WAAW,EAAE,CAAC;QAChB,IAAI,CAAC,iBAAiB,EAAE,CAAC;YACvB,OAAO,EAAE,CAAA;QACX,CAAC;QAED,OAAO;YACL,QAAQ,EAAE,CAAC,2FAA2F,CAAC;SACxG,CAAA;IACH,CAAC;IAED,MAAM,eAAe,GAAG,MAAM,IAAA,wBAAY,EAAC,SAAS,CAAC,eAAe,CAAC,CAAA;IACrE,MAAM,OAAO,GAAG,kCAAkC,CAAC,eAAe,EAAE,8BAA8B,CAAC,CAAA;IACnG,MAAM,WAAW,GAAG,iBAAiB;QACnC,CAAC,CAAC,+BAA+B,CAAC,OAAO,CAAC,QAAQ,EAAE,8BAA8B,CAAC;QACnF,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAA;IAEpB,IAAI,WAAW,KAAK,eAAe,EAAE,CAAC;QACpC,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IAC/D,CAAC;IAED,MAAM,IAAA,yBAAa,EAAC,SAAS,CAAC,eAAe,EAAE,WAAW,CAAC,CAAA;IAE3D,OAAO;QACL,MAAM,EAAE;YACN,IAAI,EAAE,SAAS;YACf,IAAI,EAAE,IAAA,qBAAc,EAAC,WAAW,EAAE,SAAS,CAAC,eAAe,CAAC;SAC7D;QACD,QAAQ,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;KAC1D,CAAA;AACH,CAAC;AAED,SAAgB,+BAA+B,CAAC,QAAgB,EAAE,8BAAsC;IACtG,IAAI,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,OAAO,QAAQ,CAAA;IACjB,CAAC;IAED,OAAO,GAAG,QAAQ,CAAC,OAAO,EAAE,OAAO,kCAAkC,CAAC,8BAA8B,CAAC,IAAI,CAAA;AAC3G,CAAC;AAED,SAAgB,kCAAkC,CAChD,QAAgB,EAChB,8BAAsC;IAEtC,MAAM,KAAK,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAA;IAE/C,IAAI,KAAK,EAAE,CAAC;QACV,OAAO;YACL,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC;SACjE,CAAA;IACH,CAAC;IAED,MAAM,aAAa,GAAG,wCAAwC,CAAC,8BAA8B,CAAC,CAAA;IAC9F,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,aAAa,CAAC,CAAA;IAEnD,IAAI,WAAW,IAAI,CAAC,EAAE,CAAC;QACrB,OAAO;YACL,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE,WAAW,EAAE,WAAW,GAAG,aAAa,CAAC,MAAM,CAAC;SAC1F,CAAA;IACH,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,QAAQ,EAAE,CAAA;IACrB,CAAC;IAED,OAAO;QACL,QAAQ;QACR,OAAO,EACL,yLAAyL;KAC5L,CAAA;AACH,CAAC;AAED,SAAS,kCAAkC,CAAC,8BAAsC;IAChF,OAAO;EACP,MAAM;gCACwB,8BAA8B;;;;;;;;;;;;;;;;;;;EAmB5D,UAAU;CACX,CAAC,IAAI,EAAE,CAAA;AACR,CAAC;AAED,SAAS,wCAAwC,CAAC,8BAAsC;IACtF,OAAO,kCAAkC,CAAC,8BAA8B,CAAC,CAAC,OAAO,CAAC,KAAK,UAAU,EAAE,EAAE,EAAE,CAAC,CAAA;AAC1G,CAAC;AAED,SAAS,uBAAuB,CAAC,QAAgB;IAC/C,MAAM,WAAW,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAA;IAC5C,IAAI,WAAW,GAAG,CAAC,EAAE,CAAC;QACpB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,WAAW,CAAC,CAAA;IAChE,IAAI,cAAc,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,IAAI,CAAA;IACb,CAAC;IAED,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,cAAc,CAAC,CAAA;IAC3D,OAAO;QACL,KAAK,EAAE,WAAW;QAClB,GAAG,EAAE,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM;KAC5D,CAAA;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,QAAgB,EAAE,KAAa,EAAE,GAAW;IACxE,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,OAAO,EAAE,CAAA;IACjD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IACrD,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;IAC3D,OAAO,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,CAAA;AACpC,CAAC"}
@@ -1,37 +1,4 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
36
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
4
  };
@@ -41,21 +8,20 @@ exports.generateIOSFiles = generateIOSFiles;
41
8
  const node_fs_1 = __importDefault(require("node:fs"));
42
9
  const promises_1 = __importDefault(require("node:fs/promises"));
43
10
  const node_path_1 = __importDefault(require("node:path"));
44
- const node_vm_1 = __importDefault(require("node:vm"));
45
11
  const node_module_1 = require("node:module");
46
- const babel = __importStar(require("@babel/core"));
47
12
  const platformPackages_1 = require("../../dependencies/platformPackages");
48
13
  const readWrite_1 = require("../../fs/readWrite");
49
14
  const path_1 = require("../../fs/path");
50
15
  const summary_1 = require("../../reporting/summary");
16
+ const widgetModule_1 = require("../shared/widgetModule");
51
17
  const plist_1 = require("./plist");
52
18
  const targetName_1 = require("./targetName");
53
- const MODULE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', ''];
54
19
  const VALID_IMAGE_EXTENSIONS = new Set(['.png', '.jpg', '.jpeg']);
55
20
  const FONT_EXTENSIONS = new Set(['.ttf', '.otf', '.woff', '.woff2']);
56
21
  const MAX_IMAGE_SIZE_BYTES = 4096;
57
22
  const DEFAULT_INITIAL_STATE_LOCALE = '__default';
58
23
  const VOLTRA_WIDGET_STRINGS_BASENAME = 'VoltraWidgets.strings';
24
+ const IOS_DYNAMIC_WIDGET_MANIFEST_PATH = node_path_1.default.join('.voltra', 'manifest.ios.json');
59
25
  const IOS_WIDGET_FAMILY_MAP = {
60
26
  systemSmall: '.systemSmall',
61
27
  systemMedium: '.systemMedium',
@@ -137,11 +103,13 @@ async function generateIOSFiles(options) {
137
103
  const { projectRoot, ios, discovery } = options;
138
104
  const targetName = (0, targetName_1.resolveIOSWidgetTargetName)(ios, discovery);
139
105
  const targetPath = node_path_1.default.join(discovery.iosRoot, targetName);
106
+ const detectedWidgets = detectClientRenderedWidgets(projectRoot, ios.widgets);
140
107
  const mainAppMetadata = await readMainAppMetadata(discovery.infoPlistPath);
141
108
  const changes = [];
142
109
  const warnings = [];
143
110
  const generatedFiles = new Set();
144
- const infoPlistResult = await generateInfoPlistFile(projectRoot, targetPath, targetName, ios, mainAppMetadata);
111
+ mergeSingleResult(await writeGeneratedTextFile(projectRoot, node_path_1.default.join(projectRoot, IOS_DYNAMIC_WIDGET_MANIFEST_PATH), `${JSON.stringify(createDynamicWidgetsManifest(detectedWidgets), null, 2)}\n`), changes, generatedFiles);
112
+ const infoPlistResult = await generateInfoPlistFile(projectRoot, targetPath, targetName, ios, mainAppMetadata, detectedWidgets);
145
113
  mergeSingleResult(infoPlistResult, changes, generatedFiles);
146
114
  const entitlementsResult = await generateEntitlementsFile(projectRoot, targetPath, targetName, ios);
147
115
  mergeSingleResult(entitlementsResult, changes, generatedFiles);
@@ -149,11 +117,11 @@ async function generateIOSFiles(options) {
149
117
  mergeResult(assetResult, changes, warnings, generatedFiles);
150
118
  const fontsResult = await copyIOSFonts(projectRoot, targetPath, ios.fonts);
151
119
  mergeResult(fontsResult, changes, warnings, generatedFiles);
152
- const initialStatesResult = await generateInitialStatesSwift(projectRoot, ios.widgets);
120
+ const initialStatesResult = await generateInitialStatesSwift(projectRoot, detectedWidgets);
153
121
  mergeSingleResult(await writeGeneratedTextFile(projectRoot, node_path_1.default.join(targetPath, 'VoltraWidgetInitialStates.swift'), initialStatesResult), changes, generatedFiles);
154
- const widgetBundleResult = await writeGeneratedTextFile(projectRoot, node_path_1.default.join(targetPath, 'VoltraWidgetBundle.swift'), generateWidgetBundleSwift(ios.widgets));
122
+ const widgetBundleResult = await writeGeneratedTextFile(projectRoot, node_path_1.default.join(targetPath, 'VoltraWidgetBundle.swift'), generateWidgetBundleSwift(detectedWidgets));
155
123
  mergeSingleResult(widgetBundleResult, changes, generatedFiles);
156
- const localizedStringResults = await generateLocalizedWidgetStrings(projectRoot, targetPath, ios.widgets);
124
+ const localizedStringResults = await generateLocalizedWidgetStrings(projectRoot, targetPath, detectedWidgets);
157
125
  mergeResult(localizedStringResults, changes, warnings, generatedFiles);
158
126
  return {
159
127
  changes,
@@ -163,10 +131,11 @@ async function generateIOSFiles(options) {
163
131
  targetPath,
164
132
  };
165
133
  }
166
- async function generateInfoPlistFile(projectRoot, targetPath, targetName, ios, mainAppMetadata) {
134
+ async function generateInfoPlistFile(projectRoot, targetPath, targetName, ios, mainAppMetadata, widgets) {
167
135
  const plistPath = node_path_1.default.join(targetPath, 'Info.plist');
168
136
  const fontNames = ios.fonts.map((fontPath) => node_path_1.default.basename(fontPath)).sort();
169
- const serverWidgets = ios.widgets.filter((widget) => widget.serverUpdate);
137
+ const serverWidgets = widgets.filter((widget) => widget.serverUpdate);
138
+ const hasClientRenderedWidget = widgets.some((widget) => widget.clientRendered);
170
139
  const serverUrls = Object.fromEntries(serverWidgets.map((widget) => [widget.id, widget.serverUpdate?.url]));
171
140
  const serverIntervals = Object.fromEntries(serverWidgets.map((widget) => [widget.id, widget.serverUpdate?.intervalMinutes]));
172
141
  const serverRefresh = Object.fromEntries(serverWidgets.map((widget) => [widget.id, widget.serverUpdate?.refresh ?? false]));
@@ -190,16 +159,30 @@ async function generateInfoPlistFile(projectRoot, targetPath, targetName, ios, m
190
159
  Voltra_KeychainGroup: ios.keychainGroup,
191
160
  Voltra_WidgetServerIntervals: Object.keys(serverIntervals).length > 0 ? serverIntervals : undefined,
192
161
  Voltra_WidgetServerRefresh: Object.keys(serverRefresh).length > 0 ? serverRefresh : undefined,
193
- NSAppTransportSecurity: serverWidgets.length > 0
194
- ? {
195
- NSAllowsLocalNetworking: true,
196
- NSAllowsArbitraryLoads: false,
197
- }
198
- : undefined,
162
+ NSAppTransportSecurity: createWidgetAppTransportSecurity(hasClientRenderedWidget, serverWidgets.length > 0),
199
163
  Voltra_WidgetServerUrls: Object.keys(serverUrls).length > 0 ? serverUrls : undefined,
200
164
  }, createGeneratedFilesError);
201
165
  return writeGeneratedTextFile(projectRoot, plistPath, infoPlist);
202
166
  }
167
+ function createWidgetAppTransportSecurity(hasClientRenderedWidget, hasServerWidgets) {
168
+ if (!hasClientRenderedWidget && !hasServerWidgets) {
169
+ return undefined;
170
+ }
171
+ return {
172
+ NSAllowsLocalNetworking: true,
173
+ ...(hasClientRenderedWidget
174
+ ? {
175
+ NSExceptionDomains: {
176
+ localhost: {
177
+ NSExceptionAllowsInsecureHTTPLoads: true,
178
+ NSIncludesSubdomains: true,
179
+ },
180
+ },
181
+ }
182
+ : {}),
183
+ ...(hasServerWidgets ? { NSAllowsArbitraryLoads: false } : {}),
184
+ };
185
+ }
203
186
  async function generateEntitlementsFile(projectRoot, targetPath, targetName, ios) {
204
187
  const entitlementsPath = node_path_1.default.join(targetPath, `${targetName}.entitlements`);
205
188
  const entitlements = (0, plist_1.buildPlistXml)({
@@ -294,8 +277,12 @@ async function readMainAppMetadata(infoPlistPath) {
294
277
  };
295
278
  }
296
279
  async function generateInitialStatesSwift(projectRoot, widgets) {
297
- const prerenderableWidgets = widgets.filter((widget) => widget.initialStatePath);
298
- if (prerenderableWidgets.length === 0) {
280
+ const serverWidgets = widgets.filter((widget) => !widget.clientRendered);
281
+ const prerenderableServerWidgets = serverWidgets.filter((widget) => widget.initialStatePath);
282
+ const serverStates = await prerenderWidgetStates(projectRoot, prerenderableServerWidgets, loadIOSWidgetRenderer(projectRoot));
283
+ const clientStates = await prerenderClientRenderedWidgets(projectRoot, widgets);
284
+ const prerenderedStates = new Map([...serverStates, ...clientStates]);
285
+ if (prerenderedStates.size === 0) {
299
286
  return [
300
287
  '//',
301
288
  '// VoltraWidgetInitialStates.swift',
@@ -314,7 +301,6 @@ async function generateInitialStatesSwift(projectRoot, widgets) {
314
301
  '',
315
302
  ].join('\n');
316
303
  }
317
- const prerenderedStates = await prerenderWidgetStates(projectRoot, prerenderableWidgets, loadIOSWidgetRenderer(projectRoot));
318
304
  const widgetEntries = [...prerenderedStates.entries()]
319
305
  .map(([widgetId, localeMap]) => {
320
306
  const localeEntries = [...localeMap.entries()]
@@ -357,8 +343,10 @@ async function generateInitialStatesSwift(projectRoot, widgets) {
357
343
  }
358
344
  function generateWidgetBundleSwift(widgets) {
359
345
  const needsFoundation = widgets.some((widget) => isWidgetLocalizedMap(widget.displayName) || isWidgetLocalizedMap(widget.description));
346
+ const appIntentWidgets = widgets.filter(widgetUsesAppIntent);
360
347
  const imports = [
361
348
  needsFoundation ? 'import Foundation' : undefined,
349
+ appIntentWidgets.length > 0 ? 'import AppIntents' : undefined,
362
350
  'import SwiftUI',
363
351
  'import WidgetKit',
364
352
  'import VoltraWidget',
@@ -385,7 +373,14 @@ function generateWidgetBundleSwift(widgets) {
385
373
  ].join('\n');
386
374
  }
387
375
  const widgetStructs = widgets.map(generateWidgetStruct).join('\n\n');
388
- const widgetInstances = widgets.map((widget) => ` VoltraWidget_${widget.id}()`).join('\n');
376
+ const plainWidgets = widgets.filter((widget) => !widgetUsesAppIntent(widget));
377
+ const plainInstances = plainWidgets.map((widget) => ` VoltraWidget_${widget.id}()`).join('\n');
378
+ const appIntentInstances = appIntentWidgets.length > 0
379
+ ? ` if #available(iOS 17.0, *) {\n${appIntentWidgets
380
+ .map((widget) => ` VoltraWidget_${widget.id}()`)
381
+ .join('\n')}\n }`
382
+ : '';
383
+ const widgetInstances = [plainInstances, appIntentInstances].filter(Boolean).join('\n');
389
384
  return [
390
385
  '//',
391
386
  '// VoltraWidgetBundle.swift',
@@ -408,9 +403,32 @@ function generateWidgetBundleSwift(widgets) {
408
403
  ].join('\n');
409
404
  }
410
405
  function generateWidgetStruct(widget) {
406
+ if (widgetUsesAppIntent(widget)) {
407
+ return generateClientAppIntentWidgetCode(widget);
408
+ }
411
409
  const familiesSwift = widget.supportedFamilies.map((family) => IOS_WIDGET_FAMILY_MAP[family]).join(', ');
412
410
  const displayNameExpr = createSwiftLabelExpression(widget.id, 'displayName', widget.displayName);
413
411
  const descriptionExpr = createSwiftLabelExpression(widget.id, 'description', widget.description);
412
+ const providerAndContent = widget.clientRendered
413
+ ? [
414
+ ' provider: VoltraClientWidgetProvider(',
415
+ ' widgetId: widgetId,',
416
+ ' initialState: VoltraWidgetInitialStates.getInitialState(for: widgetId)',
417
+ ' )',
418
+ ' ) { entry in',
419
+ ' VoltraClientWidgetContentView(',
420
+ ' entry: entry,',
421
+ ' initialState: VoltraWidgetInitialStates.getInitialState(for: widgetId)',
422
+ ' )',
423
+ ].join('\n')
424
+ : [
425
+ ' provider: VoltraHomeWidgetProvider(',
426
+ ' widgetId: widgetId,',
427
+ ' initialState: VoltraWidgetInitialStates.getInitialState(for: widgetId)',
428
+ ' )',
429
+ ' ) { entry in',
430
+ ' VoltraHomeWidgetView(entry: entry)',
431
+ ].join('\n');
414
432
  return [
415
433
  `public struct VoltraWidget_${widget.id}: Widget {`,
416
434
  ` private let widgetId = ${JSON.stringify(widget.id)}`,
@@ -420,12 +438,85 @@ function generateWidgetStruct(widget) {
420
438
  ' public var body: some WidgetConfiguration {',
421
439
  ' StaticConfiguration(',
422
440
  ` kind: ${JSON.stringify(`Voltra_Widget_${widget.id}`)},`,
423
- ' provider: VoltraHomeWidgetProvider(',
424
- ' widgetId: widgetId,',
441
+ providerAndContent,
442
+ ' }',
443
+ ` .configurationDisplayName(${displayNameExpr})`,
444
+ ` .description(${descriptionExpr})`,
445
+ ` .supportedFamilies([${familiesSwift}])`,
446
+ ' .contentMarginsDisabled()',
447
+ ' }',
448
+ '}',
449
+ ].join('\n');
450
+ }
451
+ function widgetUsesAppIntent(widget) {
452
+ return widget.clientRendered && !!widget.appIntent && widget.appIntent.parameters.length > 0;
453
+ }
454
+ function generateClientAppIntentWidgetCode(widget) {
455
+ const familiesSwift = widget.supportedFamilies.map((family) => IOS_WIDGET_FAMILY_MAP[family]).join(', ');
456
+ const intentName = `VoltraWidget_${widget.id}_Intent`;
457
+ const providerName = `VoltraWidget_${widget.id}_ClientProvider`;
458
+ const intentTitle = `Configure ${widgetLabelEnglish(widget.displayName)}`;
459
+ const displayNameExpr = createSwiftLabelExpression(widget.id, 'displayName', widget.displayName);
460
+ const descriptionExpr = createSwiftLabelExpression(widget.id, 'description', widget.description);
461
+ const parameterDeclarations = widget.appIntent.parameters
462
+ .map((parameter) => ` @Parameter(title: ${JSON.stringify(parameter.title)}, default: ${swiftDefaultValue(parameter)})\n var ${parameter.name}: String`)
463
+ .join('\n\n');
464
+ const initializerParameters = widget.appIntent.parameters.map((parameter) => `${parameter.name}: String`).join(', ');
465
+ const initializerAssignments = widget.appIntent.parameters
466
+ .map((parameter) => ` self.${parameter.name} = ${parameter.name}`)
467
+ .join('\n');
468
+ const configuredDictionary = createSwiftDictionaryLiteral(widget.appIntent.parameters.map((parameter) => `${JSON.stringify(parameter.name)}: configuration.${parameter.name}`));
469
+ const defaultDictionary = createSwiftDictionaryLiteral(widget.appIntent.parameters.map((parameter) => `${JSON.stringify(parameter.name)}: ${swiftDefaultValue(parameter)}`));
470
+ return [
471
+ `@available(iOS 17.0, *)`,
472
+ `struct ${intentName}: WidgetConfigurationIntent {`,
473
+ ` static var title: LocalizedStringResource = ${JSON.stringify(intentTitle)}`,
474
+ '',
475
+ parameterDeclarations,
476
+ '',
477
+ ' init() {}',
478
+ ` init(${initializerParameters}) {`,
479
+ initializerAssignments,
480
+ ' }',
481
+ '}',
482
+ '',
483
+ `@available(iOS 17.0, *)`,
484
+ `private struct ${providerName}: AppIntentTimelineProvider {`,
485
+ ` typealias Intent = ${intentName}`,
486
+ ' typealias Entry = VoltraClientWidgetEntry',
487
+ '',
488
+ ` private let widgetId = ${JSON.stringify(widget.id)}`,
489
+ '',
490
+ ' func placeholder(in _: Context) -> VoltraClientWidgetEntry {',
491
+ ` VoltraClientWidgetEntry(date: Date(), widgetId: widgetId, bundleReady: false, configuration: ${defaultDictionary})`,
492
+ ' }',
493
+ '',
494
+ ` func snapshot(for configuration: ${intentName}, in _: Context) async -> VoltraClientWidgetEntry {`,
495
+ ` await VoltraClientWidgetProvider.loadEntry(widgetId: widgetId, configuration: ${configuredDictionary})`,
496
+ ' }',
497
+ '',
498
+ ` func timeline(for configuration: ${intentName}, in _: Context) async -> Timeline<VoltraClientWidgetEntry> {`,
499
+ ` let entry = await VoltraClientWidgetProvider.loadEntry(widgetId: widgetId, configuration: ${configuredDictionary})`,
500
+ ' return Timeline(entries: [entry], policy: .never)',
501
+ ' }',
502
+ '}',
503
+ '',
504
+ `@available(iOS 17.0, *)`,
505
+ `public struct VoltraWidget_${widget.id}: Widget {`,
506
+ ` private let widgetId = ${JSON.stringify(widget.id)}`,
507
+ '',
508
+ ' public init() {}',
509
+ '',
510
+ ' public var body: some WidgetConfiguration {',
511
+ ' AppIntentConfiguration(',
512
+ ` kind: ${JSON.stringify(`Voltra_Widget_${widget.id}`)},`,
513
+ ` intent: ${intentName}.self,`,
514
+ ` provider: ${providerName}()`,
515
+ ' ) { entry in',
516
+ ' VoltraClientWidgetContentView(',
517
+ ' entry: entry,',
425
518
  ' initialState: VoltraWidgetInitialStates.getInitialState(for: widgetId)',
426
519
  ' )',
427
- ' ) { entry in',
428
- ' VoltraHomeWidgetView(entry: entry)',
429
520
  ' }',
430
521
  ` .configurationDisplayName(${displayNameExpr})`,
431
522
  ` .description(${descriptionExpr})`,
@@ -443,6 +534,12 @@ function createSwiftLabelExpression(widgetId, field, label) {
443
534
  const defaultEnglish = widgetLabelEnglish(label);
444
535
  return `Text(LocalizedStringResource(${JSON.stringify(key)}, defaultValue: String.LocalizationValue(${JSON.stringify(defaultEnglish)}), table: ${JSON.stringify('VoltraWidgets')}))`;
445
536
  }
537
+ function swiftDefaultValue(parameter) {
538
+ return JSON.stringify(parameter.default ?? '');
539
+ }
540
+ function createSwiftDictionaryLiteral(entries) {
541
+ return entries.length > 0 ? `[${entries.join(', ')}]` : '[:]';
542
+ }
446
543
  async function prerenderWidgetStates(projectRoot, widgets, renderer) {
447
544
  const prerenderedStates = new Map();
448
545
  for (const widget of widgets) {
@@ -456,47 +553,15 @@ async function prerenderWidgetStates(projectRoot, widgets, renderer) {
456
553
  if (!(await (0, readWrite_1.pathExists)(modulePath))) {
457
554
  throw new IOSGeneratedFilesError(`Initial state file not found for widget '${widget.id}' at ${modulePath}`);
458
555
  }
459
- const widgetVariants = evaluateWidgetModule(projectRoot, modulePath);
556
+ const widgetVariants = evaluateLegacyWidgetModule(projectRoot, modulePath);
460
557
  localeStates.set(localeKey, renderer(widgetVariants));
461
558
  }
462
559
  prerenderedStates.set(widget.id, localeStates);
463
560
  }
464
561
  return prerenderedStates;
465
562
  }
466
- function evaluateWidgetModule(projectRoot, filePath) {
467
- const projectRequire = createProjectRequire(projectRoot);
468
- const moduleCache = new Map();
469
- const customRequire = (moduleSpecifier, currentDir) => {
470
- if (!isLocalModule(moduleSpecifier)) {
471
- return projectRequire(moduleSpecifier);
472
- }
473
- const resolvedModulePath = resolveModulePath(moduleSpecifier, currentDir);
474
- if (!resolvedModulePath) {
475
- throw new IOSGeneratedFilesError(`Cannot resolve module '${moduleSpecifier}' from '${currentDir}'`);
476
- }
477
- const cachedModule = moduleCache.get(resolvedModulePath);
478
- if (cachedModule !== undefined) {
479
- return cachedModule;
480
- }
481
- const transpiledCode = transpileWidgetModule(projectRoot, resolvedModulePath, projectRequire);
482
- const moduleDir = node_path_1.default.dirname(resolvedModulePath);
483
- const moduleRecord = { exports: {} };
484
- moduleCache.set(resolvedModulePath, moduleRecord.exports);
485
- const context = node_vm_1.default.createContext({
486
- __dirname: moduleDir,
487
- __filename: resolvedModulePath,
488
- console,
489
- exports: moduleRecord.exports,
490
- module: moduleRecord,
491
- process,
492
- require: (specifier) => customRequire(specifier, moduleDir),
493
- });
494
- const script = new node_vm_1.default.Script(transpiledCode, { filename: resolvedModulePath });
495
- script.runInContext(context);
496
- moduleCache.set(resolvedModulePath, moduleRecord.exports);
497
- return moduleRecord.exports;
498
- };
499
- const exports = customRequire(filePath, node_path_1.default.dirname(filePath));
563
+ function evaluateLegacyWidgetModule(projectRoot, filePath) {
564
+ const exports = (0, widgetModule_1.evaluateWidgetModuleExports)(projectRoot, filePath, createGeneratedFilesError);
500
565
  const widgetVariants = exports.default ?? exports;
501
566
  if (!widgetVariants || typeof widgetVariants !== 'object') {
502
567
  throw new IOSGeneratedFilesError(`Widget file must export widget variants: ${filePath}`);
@@ -510,68 +575,107 @@ function loadIOSWidgetRenderer(projectRoot) {
510
575
  }
511
576
  return iosPackage.renderWidgetToString;
512
577
  }
513
- function transpileWidgetModule(projectRoot, filePath, projectRequire) {
514
- const source = node_fs_1.default.readFileSync(filePath, 'utf8');
515
- const projectBabelConfigPath = resolveProjectBabelConfig(projectRoot);
516
- const result = babel.transformSync(source, {
517
- babelrc: false,
518
- configFile: projectBabelConfigPath,
519
- cwd: projectRoot,
520
- filename: filePath,
521
- presets: projectBabelConfigPath ? undefined : [resolveFallbackBabelPreset(projectRequire)],
522
- });
523
- if (!result?.code) {
524
- throw new IOSGeneratedFilesError(`Babel transpilation failed for ${filePath}`);
525
- }
526
- return result.code;
527
- }
528
- function resolveProjectBabelConfig(projectRoot) {
529
- const candidates = ['babel.config.js', 'babel.config.cjs', 'babel.config.mjs'];
530
- for (const candidate of candidates) {
531
- const candidatePath = node_path_1.default.join(projectRoot, candidate);
532
- if (node_fs_1.default.existsSync(candidatePath)) {
533
- return candidatePath;
534
- }
578
+ function loadIOSDynamicWidgetRenderer(projectRoot) {
579
+ const iosPackage = (0, platformPackages_1.requirePlatformPackage)(projectRoot, 'ios');
580
+ if (typeof iosPackage.renderVoltraVariantToJson !== 'function') {
581
+ throw new IOSGeneratedFilesError('Installed @use-voltra/ios package does not export renderVoltraVariantToJson.');
535
582
  }
536
- return undefined;
583
+ return iosPackage.renderVoltraVariantToJson;
537
584
  }
538
- function resolveFallbackBabelPreset(projectRequire) {
539
- try {
540
- return projectRequire.resolve('@react-native/babel-preset');
585
+ function createDynamicWidgetsManifest(widgets) {
586
+ return {
587
+ version: 1,
588
+ platform: 'ios',
589
+ widgets: widgets
590
+ .filter((widget) => widget.clientRendered)
591
+ .map((widget) => ({
592
+ id: widget.id,
593
+ entry: widget.entry,
594
+ })),
595
+ };
596
+ }
597
+ function detectClientRenderedWidgets(projectRoot, widgets) {
598
+ return widgets.map((widget) => detectSingleWidget(projectRoot, widget));
599
+ }
600
+ function detectSingleWidget(projectRoot, widget) {
601
+ if (!widget.entry) {
602
+ return {
603
+ ...widget,
604
+ clientRendered: false,
605
+ };
541
606
  }
542
- catch {
607
+ const clientSourcePath = node_path_1.default.join(projectRoot, widget.entry);
608
+ if (!node_fs_1.default.existsSync(clientSourcePath) || !node_fs_1.default.statSync(clientSourcePath).isFile()) {
609
+ throw createGeneratedFilesError(`[voltra] Dynamic Widget "${widget.id}" entry not found at ${widget.entry}`);
610
+ }
611
+ const widgetModule = safelyEvaluateDynamicWidget(projectRoot, widget.id, widget.entry, clientSourcePath);
612
+ const widgetFn = readDynamicWidgetExport(widget.id, widget.entry, widgetModule);
613
+ if (typeof widgetFn !== 'function') {
614
+ throw createGeneratedFilesError(`[voltra] Dynamic Widget "${widget.id}" at ${widget.entry} must default-export a function or component.`);
615
+ }
616
+ return {
617
+ ...widget,
618
+ entry: widget.entry,
619
+ clientRendered: true,
620
+ clientSourcePath,
621
+ };
622
+ }
623
+ async function prerenderClientRenderedWidgets(projectRoot, widgets) {
624
+ const clientWidgets = widgets.filter((widget) => widget.clientRendered);
625
+ if (clientWidgets.length === 0) {
626
+ return new Map();
627
+ }
628
+ const renderer = loadIOSDynamicWidgetRenderer(projectRoot);
629
+ const placeholderEnv = {
630
+ date: Date.now(),
631
+ widgetFamily: 'systemMedium',
632
+ colorScheme: 'light',
633
+ locale: 'en-US',
634
+ widgetRenderingMode: 'fullColor',
635
+ showsWidgetContainerBackground: true,
636
+ configuration: undefined,
637
+ build: widgetModule_1.DYNAMIC_WIDGET_BUILD_INFO,
638
+ };
639
+ const prerenderedStates = new Map();
640
+ for (const widget of clientWidgets) {
543
641
  try {
544
- return projectRequire.resolve('babel-preset-expo');
642
+ const widgetModule = safelyEvaluateDynamicWidget(projectRoot, widget.id, widget.entry, widget.clientSourcePath);
643
+ const widgetFn = readDynamicWidgetExport(widget.id, widget.entry, widgetModule);
644
+ const element = widgetFn({}, placeholderEnv);
645
+ prerenderedStates.set(widget.id, new Map([[DEFAULT_INITIAL_STATE_LOCALE, JSON.stringify(renderer(element))]]));
545
646
  }
546
- catch {
547
- throw new IOSGeneratedFilesError('Could not resolve a Babel preset for iOS initial state generation. Add a project babel.config.js or install @react-native/babel-preset.');
647
+ catch (error) {
648
+ if (error instanceof IOSGeneratedFilesError) {
649
+ throw error;
650
+ }
651
+ throw createGeneratedFilesError(`[voltra] Dynamic Widget "${widget.id}" failed placeholder prerender at ${widget.entry}: ${error instanceof Error ? error.message : String(error)}`);
548
652
  }
549
653
  }
654
+ return prerenderedStates;
550
655
  }
551
- function createProjectRequire(projectRoot) {
552
- return (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
553
- }
554
- function isLocalModule(moduleSpecifier) {
555
- return moduleSpecifier.startsWith('.') || moduleSpecifier.startsWith('/');
556
- }
557
- function resolveModulePath(moduleSpecifier, fromDir) {
558
- const basePath = node_path_1.default.resolve(fromDir, moduleSpecifier);
559
- for (const extension of MODULE_EXTENSIONS) {
560
- const candidate = `${basePath}${extension}`;
561
- if (node_fs_1.default.existsSync(candidate) && node_fs_1.default.statSync(candidate).isFile()) {
562
- return candidate;
563
- }
564
- }
565
- if (!node_fs_1.default.existsSync(basePath) || !node_fs_1.default.statSync(basePath).isDirectory()) {
566
- return null;
656
+ function safelyEvaluateDynamicWidget(projectRoot, widgetId, widgetEntry, clientSourcePath) {
657
+ try {
658
+ return (0, widgetModule_1.evaluateWidgetModuleExports)(projectRoot, clientSourcePath, createGeneratedFilesError);
567
659
  }
568
- for (const extension of MODULE_EXTENSIONS) {
569
- const indexCandidate = node_path_1.default.join(basePath, `index${extension}`);
570
- if (node_fs_1.default.existsSync(indexCandidate) && node_fs_1.default.statSync(indexCandidate).isFile()) {
571
- return indexCandidate;
660
+ catch (error) {
661
+ if (error instanceof IOSGeneratedFilesError) {
662
+ throw error;
572
663
  }
664
+ throw createGeneratedFilesError(`[voltra] Dynamic Widget "${widgetId}" failed to evaluate entry at ${widgetEntry}: ${error instanceof Error ? error.message : String(error)}`);
573
665
  }
574
- return null;
666
+ }
667
+ function readDynamicWidgetExport(widgetId, widgetEntry, widgetModule) {
668
+ const exports = widgetModule && typeof widgetModule === 'object'
669
+ ? widgetModule
670
+ : { default: widgetModule };
671
+ const widgetFn = exports.default ?? widgetModule;
672
+ if (typeof widgetFn !== 'function') {
673
+ throw createGeneratedFilesError(`[voltra] Dynamic Widget "${widgetId}" at ${widgetEntry} must default-export a function or component.`);
674
+ }
675
+ return widgetFn;
676
+ }
677
+ function createProjectRequire(projectRoot) {
678
+ return (0, node_module_1.createRequire)(node_path_1.default.join(projectRoot, 'package.json'));
575
679
  }
576
680
  async function resolveFontPaths(projectRoot, fonts) {
577
681
  const projectRequire = createProjectRequire(projectRoot);