craft-native 0.0.87 → 0.0.88

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.
@@ -80,12 +80,32 @@ export interface InitOptions {
80
80
  teamId?: string;
81
81
  output: string;
82
82
  config?: Partial<CraftConfig>;
83
+ /**
84
+ * Where the Zig runtime archives live, overriding `CRAFT_IOS_RUNTIME`.
85
+ *
86
+ * `null` means "no runtime, whatever the environment says" — the shape
87
+ * `AppConfig.craftPath` has for `CRAFT_BIN`, and the reason it exists here
88
+ * is the same: a caller that does not opt in should not be steered by an
89
+ * ambient variable. Without it this package's own tests inherited whatever
90
+ * the developer's shell exported, and went from 12 passing to 8 passing and
91
+ * 4 failing when `CRAFT_IOS_RUNTIME` happened to be set.
92
+ *
93
+ * Omitted (`undefined`) keeps the environment lookup, which is what the
94
+ * monorepo dev loop uses.
95
+ */
96
+ runtimeDir?: string | null;
83
97
  }
84
98
  export interface BuildOptions {
85
99
  htmlPath?: string;
86
100
  devServer?: string;
87
101
  output: string;
88
102
  generateProject?: boolean;
103
+ /**
104
+ * Where to re-read the Zig runtime archives from, overriding
105
+ * `CRAFT_IOS_RUNTIME`. Same meaning as `InitOptions.runtimeDir`; `null`
106
+ * leaves whatever `init` installed exactly as it is.
107
+ */
108
+ runtimeDir?: string | null;
89
109
  }
90
110
  export interface OpenOptions {
91
111
  output: string;
@@ -101,12 +121,66 @@ export declare function renderUrlTypes(config: CraftConfig): string;
101
121
  export declare function renderBackgroundModes(config: CraftConfig): string;
102
122
  export declare function renderEntitlements(config: CraftConfig): string;
103
123
  export declare function renderWatchEntitlements(config: CraftConfig): string;
124
+ /**
125
+ * The build settings that link the Zig runtime into the app target.
126
+ *
127
+ * Two things are load-bearing here:
128
+ *
129
+ * `LIBRARY_SEARCH_PATHS` is written twice, once per SDK, so `-lcraft-ios`
130
+ * resolves to the device archive for a device build and the simulator archive
131
+ * for a simulator one without the flag itself changing. Xcode applies the
132
+ * `[sdk=...]` condition; xcodegen passes these keys through verbatim.
133
+ *
134
+ * The four `-u` flags are the reason anything works at all. Nothing in the
135
+ * Swift source *references* these symbols — `CraftZigRuntime` and
136
+ * `CraftSwiftShim` both find them with `dlsym` at runtime — so a static
137
+ * archive contributes no objects for them and the linker drops the entire
138
+ * runtime as unreachable. `-u` names them as undefined so the objects are
139
+ * pulled in and the symbols end up in the binary for `dlsym` to find.
140
+ *
141
+ * `-u` rather than `-force_load`: forcing the whole archive would pull in
142
+ * every object whether reachable or not, and these four entry points already
143
+ * reach everything the bridge actually uses.
144
+ */
145
+ export declare function renderRuntimeSettings(): string;
104
146
  export declare function renderPrivacyManifest(config: CraftConfig): string;
105
147
  /** Replace the bundled web application atomically so removed assets cannot linger. */
106
148
  export declare function syncWebAssets(source: string, output: string): void;
107
149
  /**
108
150
  * Initialize a new iOS project
109
151
  */
152
+ /**
153
+ * Where the Zig runtime archives live, or null when this build has none.
154
+ *
155
+ * `CRAFT_IOS_RUNTIME` points at a directory holding the archives `zig build
156
+ * build-ios-all` produces. It is opt-in and there is deliberately no fallback
157
+ * search: a generated project with no runtime is the shipping default today,
158
+ * and it works — `CraftZigRuntime.offer` is a `dlsym` miss, every action
159
+ * answers false, and the Swift switch serves the whole surface exactly as it
160
+ * always has. Guessing at a path would turn "no runtime" into "some runtime,
161
+ * from somewhere", which is the failure mode the pantry contract exists to
162
+ * rule out.
163
+ *
164
+ * This is the same shape as `CRAFT_BIN`: an explicit override for the monorepo
165
+ * dev loop, not a lookup path. Shipping the runtime to real apps means putting
166
+ * these archives in the pantry package beside the `craft` binary, which is a
167
+ * distribution decision this function does not make.
168
+ *
169
+ * `override` is what `InitOptions.runtimeDir` passes: a path to use instead of
170
+ * the variable, or `null` to declare there is no runtime regardless of what the
171
+ * environment says. `undefined` falls through to `CRAFT_IOS_RUNTIME`.
172
+ */
173
+ export declare function resolveRuntimeDir(override?: string | null): string | null;
174
+ /**
175
+ * Copy the Zig archives into the generated project as `Runtime/<sdk>/libcraft-ios.a`.
176
+ *
177
+ * One name for both SDKs, in two directories, because that is what lets a
178
+ * single `-lcraft-ios` in OTHER_LDFLAGS work for device and simulator builds:
179
+ * Xcode picks the directory by SDK and the flag never changes.
180
+ *
181
+ * Returns true when a runtime was installed.
182
+ */
183
+ export declare function installRuntime(output: string, runtimeDir: string): Promise<boolean>;
110
184
  export declare function init(options: InitOptions): Promise<void>;
111
185
  /**
112
186
  * Build web assets and generate Xcode project
@@ -176,6 +176,14 @@ ${appGroups}</dict>
176
176
  </plist>
177
177
  `;
178
178
  }
179
+ function renderRuntimeSettings() {
180
+ return [
181
+ ' LIBRARY_SEARCH_PATHS[sdk=iphoneos*]: "$(PROJECT_DIR)/Runtime/device"',
182
+ ' LIBRARY_SEARCH_PATHS[sdk=iphonesimulator*]: "$(PROJECT_DIR)/Runtime/simulator"',
183
+ ' OTHER_LDFLAGS: "-lcraft-ios -Wl,-u,_craft_ios_handle_action -Wl,-u,_craft_ios_set_webview ' + '-Wl,-u,_craft_ios_deliver_result -Wl,-u,_craft_ios_deliver_error"'
184
+ ].join(`
185
+ `);
186
+ }
179
187
  function renderPrivacyManifest(config) {
180
188
  const privacy = config.privacy ?? {};
181
189
  const collected = privacy.collectedDataTypes ?? [];
@@ -279,6 +287,59 @@ function syncWebAssets(source, output) {
279
287
  throw new Error(`Web asset directory must contain index.html: ${source}`);
280
288
  }
281
289
  }
290
+ function resolveRuntimeDir(override) {
291
+ if (override === null)
292
+ return null;
293
+ const dir = override ?? process.env.CRAFT_IOS_RUNTIME;
294
+ if (!dir)
295
+ return null;
296
+ if (!existsSync(dir)) {
297
+ const source = override === undefined ? "CRAFT_IOS_RUNTIME points at" : "runtimeDir is";
298
+ throw new Error(`${source} ${dir}, which does not exist.`);
299
+ }
300
+ return dir;
301
+ }
302
+ var RUNTIME_ARCHIVES = {
303
+ device: ["libcraft-ios.a"],
304
+ simulator: ["libcraft-ios-simulator-arm64.a", "libcraft-ios-simulator-x64.a"]
305
+ };
306
+ async function installRuntime(output, runtimeDir) {
307
+ const resolved = Object.entries(RUNTIME_ARCHIVES).map(([sdk, archives]) => {
308
+ const present = archives.filter((a) => existsSync(join(runtimeDir, a)));
309
+ if (present.length === 0) {
310
+ throw new Error(`${runtimeDir} has none of ${archives.join(", ")}. ` + `Run \`zig build build-ios-all\` in packages/zig and point at its zig-out/lib.`);
311
+ }
312
+ return { sdk, archives, present };
313
+ });
314
+ const dest = join(output, "Runtime");
315
+ rmSync(dest, { recursive: true, force: true });
316
+ for (const { sdk, archives, present } of resolved) {
317
+ const sdkDir = join(dest, sdk);
318
+ mkdirSync(sdkDir, { recursive: true });
319
+ const target = join(sdkDir, "libcraft-ios.a");
320
+ if (present.length === 1) {
321
+ const missing = archives.filter((a) => !present.includes(a));
322
+ if (missing.length > 0) {
323
+ console.warn(` \u26A0 ${sdk}: only ${present[0]} was found; ${missing.join(", ")} is missing. ` + `The generated project will not link on the other architecture.`);
324
+ }
325
+ cpSync(join(runtimeDir, present[0]), target);
326
+ } else {
327
+ await $`lipo -create ${present.map((a) => join(runtimeDir, a))} -output ${target}`.quiet();
328
+ }
329
+ }
330
+ return true;
331
+ }
332
+ async function refreshRuntime(output, override) {
333
+ if (!existsSync(join(output, "Runtime")))
334
+ return;
335
+ const dir = resolveRuntimeDir(override);
336
+ if (!dir) {
337
+ console.log(" Keeping the Zig runtime installed at init (no runtime directory configured)");
338
+ return;
339
+ }
340
+ await installRuntime(output, dir);
341
+ console.log(" Refreshed the Zig runtime from", dir);
342
+ }
282
343
  async function init(options) {
283
344
  const { name, bundleId, teamId, output } = options;
284
345
  console.log(`
@@ -351,7 +412,14 @@ async function init(options) {
351
412
  SWIFT_VERSION: "5.0"
352
413
  SKIP_INSTALL: YES`);
353
414
  }
354
- const projectYml = projectYmlTemplate.replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{BUNDLE_ID_PREFIX\}\}/g, bundleIdPrefix).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{IOS_VERSION\}\}/g, config.iosVersion || "15.0").replace(/\{\{DEVICE_FAMILIES\}\}/g, renderDeviceFamilies(config)).replace(/\{\{TEAM_ID\}\}/g, teamId || "").replace(/\{\{NATIVE_DEPENDENCIES\}\}/g, nativeDependencies.length ? ` dependencies:
415
+ const runtimeDir = resolveRuntimeDir(options.runtimeDir);
416
+ const hasRuntime = runtimeDir ? await installRuntime(output, runtimeDir) : false;
417
+ if (hasRuntime) {
418
+ console.log(" Linked the Zig runtime from", runtimeDir);
419
+ } else {
420
+ rmSync(join(output, "Runtime"), { recursive: true, force: true });
421
+ }
422
+ const projectYml = projectYmlTemplate.replace(/\{\{CRAFT_RUNTIME_SETTINGS\}\}/g, hasRuntime ? renderRuntimeSettings() : "").replace(/\{\{APP_NAME\}\}/g, name).replace(/\{\{BUNDLE_ID\}\}/g, finalBundleId).replace(/\{\{BUNDLE_ID_PREFIX\}\}/g, bundleIdPrefix).replace(/\{\{VERSION\}\}/g, config.version || "1.0.0").replace(/\{\{BUILD_NUMBER\}\}/g, config.buildNumber || "1").replace(/\{\{IOS_VERSION\}\}/g, config.iosVersion || "15.0").replace(/\{\{DEVICE_FAMILIES\}\}/g, renderDeviceFamilies(config)).replace(/\{\{TEAM_ID\}\}/g, teamId || "").replace(/\{\{NATIVE_DEPENDENCIES\}\}/g, nativeDependencies.length ? ` dependencies:
355
423
  ${nativeDependencies.join(`
356
424
  `)}` : "").replace(/\{\{NATIVE_TARGETS\}\}/g, nativeTargets.join(`
357
425
  `));
@@ -442,6 +510,7 @@ async function build(options) {
442
510
  syncWebAssets(htmlPath, output);
443
511
  console.log(` Synced: ${htmlPath} \u2192 dist/`);
444
512
  }
513
+ await refreshRuntime(output, options.runtimeDir);
445
514
  if (!generateProject)
446
515
  return;
447
516
  try {
@@ -567,9 +636,11 @@ export {
567
636
  syncWebAssets,
568
637
  showSimulator,
569
638
  run,
639
+ resolveRuntimeDir,
570
640
  renderWatchEntitlements,
571
641
  renderUsageDescriptions,
572
642
  renderUrlTypes,
643
+ renderRuntimeSettings,
573
644
  renderPrivacyManifest,
574
645
  renderOrientations,
575
646
  renderEntitlements,
@@ -578,6 +649,7 @@ export {
578
649
  pickSimulator,
579
650
  orderSimulators,
580
651
  open,
652
+ installRuntime,
581
653
  init,
582
654
  build,
583
655
  bootSimulator