@choochmeque/tauri-apple-extensions 0.1.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,8 @@
1
+ export { addExtension } from "./commands/add.js";
2
+ export { findProjectRoot, findTauriConfig, findAppleProjectDir, getAppInfo, } from "./core/project-discovery.js";
3
+ export { readProjectYml, writeProjectYml } from "./core/project-yml.js";
4
+ export { updateMainAppEntitlements, createExtensionEntitlements, } from "./core/entitlements.js";
5
+ export { addUrlSchemeToInfoPlist } from "./core/info-plist.js";
6
+ export { runXcodeGen } from "./core/xcodegen.js";
7
+ export { shareExtension } from "./extensions/share.js";
8
+ export type { AppInfo, TauriConfig, Extension, TargetConfig, DependencyConfig, AddOptions, TemplateVariables, } from "./types.js";
package/dist/index.js ADDED
@@ -0,0 +1,523 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import { execSync } from 'child_process';
5
+
6
+ /**
7
+ * Simple YAML-like parsing for the values we need.
8
+ * This is not a full YAML parser, just extracts top-level key-value pairs.
9
+ */
10
+ function parseYamlSimple(content) {
11
+ const lines = content.split("\n");
12
+ const result = {};
13
+ for (const line of lines) {
14
+ const match = line.match(/^(\w+):\s*(.+)$/);
15
+ if (match) {
16
+ result[match[1]] = match[2].trim();
17
+ }
18
+ }
19
+ return result;
20
+ }
21
+
22
+ function findProjectRoot() {
23
+ let dir = process.cwd();
24
+ while (dir !== path.dirname(dir)) {
25
+ if (fs.existsSync(path.join(dir, "tauri.conf.json")) ||
26
+ fs.existsSync(path.join(dir, "src-tauri", "tauri.conf.json"))) {
27
+ return dir;
28
+ }
29
+ dir = path.dirname(dir);
30
+ }
31
+ return process.cwd();
32
+ }
33
+ function findTauriConfig(projectRoot) {
34
+ const paths = [
35
+ path.join(projectRoot, "src-tauri", "tauri.conf.json"),
36
+ path.join(projectRoot, "tauri.conf.json"),
37
+ ];
38
+ for (const p of paths) {
39
+ if (fs.existsSync(p)) {
40
+ return JSON.parse(fs.readFileSync(p, "utf8"));
41
+ }
42
+ }
43
+ throw new Error("Could not find tauri.conf.json");
44
+ }
45
+ function findAppleProjectDir(projectRoot) {
46
+ const paths = [
47
+ path.join(projectRoot, "src-tauri", "gen", "apple"),
48
+ path.join(projectRoot, "gen", "apple"),
49
+ ];
50
+ for (const p of paths) {
51
+ if (fs.existsSync(p)) {
52
+ return p;
53
+ }
54
+ }
55
+ throw new Error("Could not find iOS project directory. Run 'tauri ios init' first.");
56
+ }
57
+ function getAppInfo(tauriConfig, projectYml) {
58
+ const parsed = parseYamlSimple(projectYml);
59
+ const productName = tauriConfig.productName || tauriConfig.package?.productName || "app";
60
+ const bundleIdPrefix = parsed.bundleIdPrefix || "com.tauri";
61
+ const identifier = tauriConfig.identifier || `${bundleIdPrefix}.${productName}`;
62
+ const version = tauriConfig.version || "1.0.0";
63
+ return {
64
+ productName,
65
+ bundleIdPrefix,
66
+ identifier,
67
+ version,
68
+ };
69
+ }
70
+
71
+ function readProjectYml(appleDir) {
72
+ const projectYmlPath = path.join(appleDir, "project.yml");
73
+ if (!fs.existsSync(projectYmlPath)) {
74
+ throw new Error("project.yml not found. Run 'tauri ios init' first.");
75
+ }
76
+ return fs.readFileSync(projectYmlPath, "utf8");
77
+ }
78
+ function writeProjectYml(appleDir, content) {
79
+ const projectYmlPath = path.join(appleDir, "project.yml");
80
+ fs.writeFileSync(projectYmlPath, content);
81
+ }
82
+ function addExtensionTarget(projectYml, targetConfig) {
83
+ let modified = projectYml;
84
+ // Remove existing target if it exists (to allow re-running the script)
85
+ // Note: Don't use 'm' flag - we need $ to match only at true end of string
86
+ const existingTargetRegex = new RegExp(`\\n ${targetConfig.name}:[\\s\\S]*?(?=\\n [a-zA-Z_]|\\n[a-zA-Z]|$)`);
87
+ if (modified.match(existingTargetRegex)) {
88
+ console.log(`Removing existing ${targetConfig.name} target to recreate it...`);
89
+ modified = modified.replace(existingTargetRegex, "");
90
+ }
91
+ // Find the end of targets section and insert the new target
92
+ if (modified.includes("targets:")) {
93
+ const targetsIndex = modified.indexOf("targets:");
94
+ const afterTargets = modified.slice(targetsIndex);
95
+ const lines = afterTargets.split("\n");
96
+ let inTargets = false;
97
+ let lastTargetEnd = targetsIndex;
98
+ for (let i = 0; i < lines.length; i++) {
99
+ const line = lines[i];
100
+ if (line.match(/^targets:/)) {
101
+ inTargets = true;
102
+ continue;
103
+ }
104
+ if (inTargets) {
105
+ // Check if we've exited the targets section (new top-level key)
106
+ if (line.match(/^[a-zA-Z]/) && !line.startsWith(" ")) {
107
+ break;
108
+ }
109
+ lastTargetEnd = targetsIndex + lines.slice(0, i + 1).join("\n").length;
110
+ }
111
+ }
112
+ modified =
113
+ modified.slice(0, lastTargetEnd) +
114
+ targetConfig.yaml +
115
+ modified.slice(lastTargetEnd);
116
+ }
117
+ return modified;
118
+ }
119
+ function addDependencyToTarget(projectYml, mainTargetName, dependencyConfig) {
120
+ let modified = projectYml;
121
+ // Remove existing dependency if it exists
122
+ const existingDepRegex = new RegExp(`\\n - target: ${dependencyConfig.target}\\n embed: true\\n codeSign: true`, "g");
123
+ modified = modified.replace(existingDepRegex, "");
124
+ // Find the main target's dependencies section and add the extension
125
+ const targetRegex = new RegExp(`(${mainTargetName}:[\\s\\S]*?dependencies:)([\\s\\S]*?)(\\n\\s{4}\\w|$)`, "m");
126
+ const depMatch = modified.match(targetRegex);
127
+ if (depMatch) {
128
+ const depSection = depMatch[2];
129
+ const newDep = `\n - target: ${dependencyConfig.target}
130
+ embed: true
131
+ codeSign: true`;
132
+ if (!depSection.includes(dependencyConfig.target)) {
133
+ modified = modified.replace(targetRegex, `$1${depSection}${newDep}$3`);
134
+ }
135
+ }
136
+ return modified;
137
+ }
138
+
139
+ function updateMainAppEntitlements(appleDir, appInfo) {
140
+ const targetName = `${appInfo.productName}_iOS`;
141
+ const entitlementsPath = path.join(appleDir, targetName, `${targetName}.entitlements`);
142
+ const appGroupId = `group.${appInfo.identifier}`;
143
+ let entitlements;
144
+ if (fs.existsSync(entitlementsPath)) {
145
+ entitlements = fs.readFileSync(entitlementsPath, "utf8");
146
+ }
147
+ else {
148
+ entitlements = `<?xml version="1.0" encoding="UTF-8"?>
149
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
150
+ <plist version="1.0">
151
+ <dict>
152
+ </dict>
153
+ </plist>`;
154
+ }
155
+ // Check if app groups already configured
156
+ if (entitlements.includes("com.apple.security.application-groups")) {
157
+ if (!entitlements.includes(appGroupId)) {
158
+ // Add our group to existing array
159
+ entitlements = entitlements.replace(/(<key>com\.apple\.security\.application-groups<\/key>\s*<array>)/, `$1\n <string>${appGroupId}</string>`);
160
+ }
161
+ }
162
+ else {
163
+ // Add app groups entitlement
164
+ entitlements = entitlements.replace(/<dict>\s*<\/dict>/, `<dict>
165
+ <key>com.apple.security.application-groups</key>
166
+ <array>
167
+ <string>${appGroupId}</string>
168
+ </array>
169
+ </dict>`);
170
+ }
171
+ fs.writeFileSync(entitlementsPath, entitlements);
172
+ console.log(`Updated main app entitlements: ${entitlementsPath}`);
173
+ }
174
+ function createExtensionEntitlements(extensionDir, appGroupId) {
175
+ const entitlements = `<?xml version="1.0" encoding="UTF-8"?>
176
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
177
+ <plist version="1.0">
178
+ <dict>
179
+ <key>com.apple.security.application-groups</key>
180
+ <array>
181
+ <string>${appGroupId}</string>
182
+ </array>
183
+ </dict>
184
+ </plist>`;
185
+ const entitlementsPath = path.join(extensionDir, `${path.basename(extensionDir)}.entitlements`);
186
+ fs.writeFileSync(entitlementsPath, entitlements);
187
+ return entitlementsPath;
188
+ }
189
+
190
+ function addUrlSchemeToInfoPlist(appleDir, appInfo) {
191
+ const targetName = `${appInfo.productName}_iOS`;
192
+ const infoPlistPath = path.join(appleDir, targetName, "Info.plist");
193
+ const urlScheme = appInfo.productName.toLowerCase().replace(/[^a-z0-9]/g, "");
194
+ if (!fs.existsSync(infoPlistPath)) {
195
+ console.log(`Info.plist not found at ${infoPlistPath}, skipping URL scheme setup`);
196
+ return;
197
+ }
198
+ let infoPlist = fs.readFileSync(infoPlistPath, "utf8");
199
+ // Check if URL schemes already configured
200
+ if (infoPlist.includes("CFBundleURLSchemes")) {
201
+ if (!infoPlist.includes(urlScheme)) {
202
+ console.log(`URL scheme may need manual configuration. Add '${urlScheme}' to CFBundleURLSchemes.`);
203
+ }
204
+ return;
205
+ }
206
+ // Add URL scheme - need to insert before closing </dict></plist>
207
+ const urlSchemeEntry = ` <key>CFBundleURLTypes</key>
208
+ <array>
209
+ <dict>
210
+ <key>CFBundleURLSchemes</key>
211
+ <array>
212
+ <string>${urlScheme}</string>
213
+ </array>
214
+ <key>CFBundleURLName</key>
215
+ <string>${appInfo.identifier}</string>
216
+ </dict>
217
+ </array>
218
+ `;
219
+ // Insert before the last </dict>
220
+ infoPlist = infoPlist.replace(/(\s*)<\/dict>\s*<\/plist>/, `\n${urlSchemeEntry}$1</dict>\n</plist>`);
221
+ fs.writeFileSync(infoPlistPath, infoPlist);
222
+ console.log(`Added URL scheme '${urlScheme}' to Info.plist`);
223
+ }
224
+
225
+ function runXcodeGen(appleDir) {
226
+ try {
227
+ console.log("Running xcodegen to regenerate project...");
228
+ execSync("xcodegen generate", {
229
+ cwd: appleDir,
230
+ stdio: "inherit",
231
+ });
232
+ console.log("Xcode project regenerated successfully!");
233
+ }
234
+ catch (error) {
235
+ console.log("\nNote: xcodegen not found or failed. You may need to run it manually:");
236
+ console.log(` cd ${appleDir} && xcodegen generate`);
237
+ console.log(` Error: ${error.message}`);
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Replace template variables in content.
243
+ * Variables are in the format {{VARIABLE_NAME}}
244
+ */
245
+ function replaceTemplateVariables(content, variables) {
246
+ let result = content;
247
+ for (const [key, value] of Object.entries(variables)) {
248
+ const regex = new RegExp(`\\{\\{${key}\\}\\}`, "g");
249
+ result = result.replace(regex, value);
250
+ }
251
+ return result;
252
+ }
253
+ /**
254
+ * Copy a template file to destination, replacing variables.
255
+ */
256
+ function copyTemplateFile(src, dest, variables) {
257
+ let content = fs.readFileSync(src, "utf8");
258
+ content = replaceTemplateVariables(content, variables);
259
+ fs.writeFileSync(dest, content);
260
+ }
261
+
262
+ const shareExtension = {
263
+ type: "share",
264
+ displayName: "Share Extension",
265
+ extensionSuffix: "ShareExtension",
266
+ extensionPointIdentifier: "com.apple.share-services",
267
+ extensionName(appInfo) {
268
+ return `${appInfo.productName}-ShareExtension`;
269
+ },
270
+ createFiles(appleDir, appInfo, templatesDir) {
271
+ const extensionDir = path.join(appleDir, "ShareExtension");
272
+ const appGroupId = `group.${appInfo.identifier}`;
273
+ const urlScheme = appInfo.productName
274
+ .toLowerCase()
275
+ .replace(/[^a-z0-9]/g, "");
276
+ // Create directory
277
+ if (!fs.existsSync(extensionDir)) {
278
+ fs.mkdirSync(extensionDir, { recursive: true });
279
+ }
280
+ const variables = {
281
+ APP_GROUP_IDENTIFIER: appGroupId,
282
+ APP_URL_SCHEME: urlScheme,
283
+ VERSION: appInfo.version,
284
+ BUNDLE_IDENTIFIER: `${appInfo.identifier}.ShareExtension`,
285
+ PRODUCT_NAME: appInfo.productName,
286
+ };
287
+ // Copy ShareViewController.swift
288
+ const viewControllerSrc = path.join(templatesDir, "ShareViewController.swift");
289
+ if (fs.existsSync(viewControllerSrc)) {
290
+ copyTemplateFile(viewControllerSrc, path.join(extensionDir, "ShareViewController.swift"), variables);
291
+ }
292
+ else {
293
+ throw new Error(`Template not found: ${viewControllerSrc}`);
294
+ }
295
+ // Copy Info.plist
296
+ const possibleInfoPlists = [
297
+ path.join(templatesDir, "Info.plist"),
298
+ path.join(templatesDir, "ShareExtension-Info.plist"),
299
+ ];
300
+ let infoPlistFound = false;
301
+ for (const src of possibleInfoPlists) {
302
+ if (fs.existsSync(src)) {
303
+ copyTemplateFile(src, path.join(extensionDir, "Info.plist"), variables);
304
+ infoPlistFound = true;
305
+ break;
306
+ }
307
+ }
308
+ if (!infoPlistFound) {
309
+ // Create a default Info.plist
310
+ const defaultInfoPlist = `<?xml version="1.0" encoding="UTF-8"?>
311
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
312
+ <plist version="1.0">
313
+ <dict>
314
+ <key>CFBundleDevelopmentRegion</key>
315
+ <string>$(DEVELOPMENT_LANGUAGE)</string>
316
+ <key>CFBundleDisplayName</key>
317
+ <string>Share</string>
318
+ <key>CFBundleExecutable</key>
319
+ <string>$(EXECUTABLE_NAME)</string>
320
+ <key>CFBundleIdentifier</key>
321
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
322
+ <key>CFBundleInfoDictionaryVersion</key>
323
+ <string>6.0</string>
324
+ <key>CFBundleName</key>
325
+ <string>$(PRODUCT_NAME)</string>
326
+ <key>CFBundlePackageType</key>
327
+ <string>XPC!</string>
328
+ <key>CFBundleShortVersionString</key>
329
+ <string>${appInfo.version}</string>
330
+ <key>CFBundleVersion</key>
331
+ <string>${appInfo.version}</string>
332
+ <key>NSExtension</key>
333
+ <dict>
334
+ <key>NSExtensionAttributes</key>
335
+ <dict>
336
+ <key>NSExtensionActivationRule</key>
337
+ <dict>
338
+ <key>NSExtensionActivationSupportsFileWithMaxCount</key>
339
+ <integer>10</integer>
340
+ <key>NSExtensionActivationSupportsImageWithMaxCount</key>
341
+ <integer>10</integer>
342
+ <key>NSExtensionActivationSupportsMovieWithMaxCount</key>
343
+ <integer>10</integer>
344
+ <key>NSExtensionActivationSupportsText</key>
345
+ <true/>
346
+ <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
347
+ <integer>1</integer>
348
+ </dict>
349
+ </dict>
350
+ <key>NSExtensionPointIdentifier</key>
351
+ <string>com.apple.share-services</string>
352
+ <key>NSExtensionPrincipalClass</key>
353
+ <string>$(PRODUCT_MODULE_NAME).ShareViewController</string>
354
+ </dict>
355
+ </dict>
356
+ </plist>`;
357
+ fs.writeFileSync(path.join(extensionDir, "Info.plist"), defaultInfoPlist);
358
+ }
359
+ // Create entitlements
360
+ createExtensionEntitlements(extensionDir, appGroupId);
361
+ console.log(`Created ShareExtension files in ${extensionDir}`);
362
+ },
363
+ updateProjectYml(projectYml, appInfo) {
364
+ const extensionName = this.extensionName(appInfo);
365
+ const extensionBundleId = `${appInfo.identifier}.ShareExtension`;
366
+ const targetName = `${appInfo.productName}_iOS`;
367
+ // Create the extension target YAML
368
+ const extensionTarget = `
369
+ ${extensionName}:
370
+ type: app-extension
371
+ platform: iOS
372
+ deploymentTarget: "14.0"
373
+ sources:
374
+ - path: ShareExtension
375
+ info:
376
+ path: ShareExtension/Info.plist
377
+ properties:
378
+ CFBundleDisplayName: Share
379
+ CFBundleShortVersionString: "${appInfo.version}"
380
+ CFBundleVersion: "${appInfo.version}"
381
+ NSExtension:
382
+ NSExtensionAttributes:
383
+ NSExtensionActivationRule:
384
+ NSExtensionActivationSupportsFileWithMaxCount: 10
385
+ NSExtensionActivationSupportsImageWithMaxCount: 10
386
+ NSExtensionActivationSupportsMovieWithMaxCount: 10
387
+ NSExtensionActivationSupportsText: true
388
+ NSExtensionActivationSupportsWebURLWithMaxCount: 1
389
+ NSExtensionPointIdentifier: com.apple.share-services
390
+ NSExtensionPrincipalClass: $(PRODUCT_MODULE_NAME).ShareViewController
391
+ settings:
392
+ base:
393
+ PRODUCT_BUNDLE_IDENTIFIER: ${extensionBundleId}
394
+ SKIP_INSTALL: YES
395
+ CODE_SIGN_ENTITLEMENTS: ShareExtension/ShareExtension.entitlements
396
+ `;
397
+ // Add the target
398
+ let modified = addExtensionTarget(projectYml, {
399
+ name: extensionName,
400
+ yaml: extensionTarget,
401
+ });
402
+ // Add dependency to main target
403
+ modified = addDependencyToTarget(modified, targetName, {
404
+ target: extensionName,
405
+ });
406
+ return modified;
407
+ },
408
+ };
409
+
410
+ const __filename$1 = fileURLToPath(import.meta.url);
411
+ const __dirname$1 = path.dirname(__filename$1);
412
+ const EXTENSIONS = {
413
+ share: shareExtension,
414
+ };
415
+ function resolveTemplatesDir(options, extensionType) {
416
+ // Option 1: Explicit templates path
417
+ if (options.templates) {
418
+ const templatesPath = path.resolve(process.cwd(), options.templates);
419
+ if (!fs.existsSync(templatesPath)) {
420
+ throw new Error(`Templates directory not found: ${templatesPath}`);
421
+ }
422
+ return templatesPath;
423
+ }
424
+ // Option 2: Plugin templates
425
+ if (options.plugin) {
426
+ const pluginPath = resolvePluginPath(options.plugin);
427
+ const pluginPkg = JSON.parse(fs.readFileSync(path.join(pluginPath, "package.json"), "utf8"));
428
+ const extensionConfig = pluginPkg["tauri-apple-extension"];
429
+ if (!extensionConfig) {
430
+ throw new Error(`Plugin ${options.plugin} does not have tauri-apple-extension config in package.json`);
431
+ }
432
+ if (extensionConfig.type !== extensionType) {
433
+ throw new Error(`Plugin ${options.plugin} is for '${extensionConfig.type}' extension, not '${extensionType}'`);
434
+ }
435
+ const templatesPath = path.join(pluginPath, extensionConfig.templates);
436
+ if (!fs.existsSync(templatesPath)) {
437
+ throw new Error(`Plugin templates directory not found: ${templatesPath}`);
438
+ }
439
+ return templatesPath;
440
+ }
441
+ // Option 3: Default templates
442
+ const defaultTemplates = path.join(__dirname$1, "../../templates", extensionType);
443
+ if (!fs.existsSync(defaultTemplates)) {
444
+ throw new Error(`No templates found. Use --plugin or --templates to specify templates.`);
445
+ }
446
+ return defaultTemplates;
447
+ }
448
+ function resolvePluginPath(pluginName) {
449
+ // Try to find in node_modules
450
+ const possiblePaths = [
451
+ path.join(process.cwd(), "node_modules", pluginName),
452
+ path.join(process.cwd(), "src-tauri", "node_modules", pluginName),
453
+ ];
454
+ for (const p of possiblePaths) {
455
+ if (fs.existsSync(p)) {
456
+ return p;
457
+ }
458
+ }
459
+ throw new Error(`Plugin ${pluginName} not found in node_modules. Make sure it's installed.`);
460
+ }
461
+ async function addExtension(type, options) {
462
+ console.log(`\nTauri Apple Extensions - Add ${type}\n`);
463
+ try {
464
+ // Validate extension type
465
+ const extension = EXTENSIONS[type];
466
+ if (!extension) {
467
+ const available = Object.keys(EXTENSIONS).join(", ");
468
+ throw new Error(`Unknown extension type: ${type}. Available: ${available}`);
469
+ }
470
+ // Find project
471
+ const projectRoot = findProjectRoot();
472
+ console.log(`Project root: ${projectRoot}`);
473
+ const tauriConfig = findTauriConfig(projectRoot);
474
+ const appleDir = findAppleProjectDir(projectRoot);
475
+ console.log(`Apple project dir: ${appleDir}`);
476
+ // Get app info
477
+ let projectYml = readProjectYml(appleDir);
478
+ const appInfo = getAppInfo(tauriConfig, projectYml);
479
+ console.log(`\nApp Info:`);
480
+ console.log(` Product Name: ${appInfo.productName}`);
481
+ console.log(` Bundle ID: ${appInfo.identifier}`);
482
+ console.log(` Version: ${appInfo.version}`);
483
+ const appGroupId = `group.${appInfo.identifier}`;
484
+ const urlScheme = appInfo.productName
485
+ .toLowerCase()
486
+ .replace(/[^a-z0-9]/g, "");
487
+ console.log(` App Group: ${appGroupId}`);
488
+ console.log(` URL Scheme: ${urlScheme}`);
489
+ // Resolve templates
490
+ const templatesDir = resolveTemplatesDir(options, type);
491
+ console.log(`\nUsing templates from: ${templatesDir}`);
492
+ // Run extension setup
493
+ console.log(`\n1. Creating ${extension.displayName} files...`);
494
+ extension.createFiles(appleDir, appInfo, templatesDir);
495
+ console.log(`\n2. Updating main app entitlements...`);
496
+ updateMainAppEntitlements(appleDir, appInfo);
497
+ console.log(`\n3. Adding URL scheme to Info.plist...`);
498
+ addUrlSchemeToInfoPlist(appleDir, appInfo);
499
+ console.log(`\n4. Updating project.yml...`);
500
+ projectYml = readProjectYml(appleDir);
501
+ projectYml = extension.updateProjectYml(projectYml, appInfo);
502
+ writeProjectYml(appleDir, projectYml);
503
+ console.log(`\n5. Regenerating Xcode project...`);
504
+ runXcodeGen(appleDir);
505
+ console.log(`\n========================================`);
506
+ console.log(`${extension.displayName} setup complete!`);
507
+ console.log(`========================================\n`);
508
+ console.log(`Next steps:`);
509
+ console.log(`1. Open the Xcode project`);
510
+ console.log(`2. Select your team for both targets (main app and ${extension.extensionName(appInfo)})`);
511
+ console.log(`3. Enable 'App Groups' capability for both targets with: ${appGroupId}`);
512
+ console.log(`4. Build and run!\n`);
513
+ console.log(`IMPORTANT: You need to configure App Groups in Apple Developer Portal:`);
514
+ console.log(` - Create App Group: ${appGroupId}`);
515
+ console.log(` - Add it to both App IDs: ${appInfo.identifier} and ${appInfo.identifier}.${extension.extensionSuffix}\n`);
516
+ }
517
+ catch (error) {
518
+ console.error(`\nError: ${error.message}`);
519
+ process.exit(1);
520
+ }
521
+ }
522
+
523
+ export { addExtension, addUrlSchemeToInfoPlist, createExtensionEntitlements, findAppleProjectDir, findProjectRoot, findTauriConfig, getAppInfo, readProjectYml, runXcodeGen, shareExtension, updateMainAppEntitlements, writeProjectYml };
@@ -0,0 +1,35 @@
1
+ export interface AppInfo {
2
+ productName: string;
3
+ bundleIdPrefix: string;
4
+ identifier: string;
5
+ version: string;
6
+ }
7
+ export interface TauriConfig {
8
+ productName?: string;
9
+ identifier?: string;
10
+ version?: string;
11
+ package?: {
12
+ productName?: string;
13
+ };
14
+ }
15
+ export interface Extension {
16
+ type: string;
17
+ displayName: string;
18
+ extensionSuffix: string;
19
+ extensionPointIdentifier: string;
20
+ extensionName(appInfo: AppInfo): string;
21
+ createFiles(appleDir: string, appInfo: AppInfo, templatesDir: string): void;
22
+ updateProjectYml(projectYml: string, appInfo: AppInfo): string;
23
+ }
24
+ export interface TargetConfig {
25
+ name: string;
26
+ yaml: string;
27
+ }
28
+ export interface DependencyConfig {
29
+ target: string;
30
+ }
31
+ export interface AddOptions {
32
+ plugin?: string;
33
+ templates?: string;
34
+ }
35
+ export type TemplateVariables = Record<string, string>;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Plist manipulation utilities.
3
+ */
4
+ /**
5
+ * Check if a plist contains a specific key.
6
+ */
7
+ export declare function plistHasKey(content: string, key: string): boolean;
8
+ /**
9
+ * Add a string to an array in a plist, if not already present.
10
+ */
11
+ export declare function plistAddToArray(content: string, key: string, value: string): string;
@@ -0,0 +1,14 @@
1
+ import type { TemplateVariables } from "../types.js";
2
+ /**
3
+ * Replace template variables in content.
4
+ * Variables are in the format {{VARIABLE_NAME}}
5
+ */
6
+ export declare function replaceTemplateVariables(content: string, variables: TemplateVariables): string;
7
+ /**
8
+ * Copy a template file to destination, replacing variables.
9
+ */
10
+ export declare function copyTemplateFile(src: string, dest: string, variables: TemplateVariables): void;
11
+ /**
12
+ * Copy all files from a templates directory, replacing variables.
13
+ */
14
+ export declare function copyTemplateDir(templatesDir: string, destDir: string, variables: TemplateVariables): void;
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Simple YAML-like parsing for the values we need.
3
+ * This is not a full YAML parser, just extracts top-level key-value pairs.
4
+ */
5
+ export declare function parseYamlSimple(content: string): Record<string, string>;
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@choochmeque/tauri-apple-extensions",
3
+ "version": "0.1.0",
4
+ "description": "Add iOS extensions to Tauri apps with a single command",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "bin": {
8
+ "tauri-apple-extensions": "./dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "templates",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "rollup -c",
17
+ "prepublishOnly": "pnpm build",
18
+ "pretest": "pnpm build",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "lint": "eslint .",
22
+ "format": "prettier --write \"./**/*.{cjs,mjs,js,jsx,mts,ts,tsx,html,css,json}\"",
23
+ "format:check": "prettier --check \"./**/*.{cjs,mjs,js,jsx,mts,ts,tsx,html,css,json}\""
24
+ },
25
+ "keywords": [
26
+ "tauri",
27
+ "ios",
28
+ "apple",
29
+ "extension",
30
+ "share",
31
+ "xcode",
32
+ "xcodegen"
33
+ ],
34
+ "author": "Choochmeque",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/Choochmeque/tauri-apple-extensions.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/Choochmeque/tauri-apple-extensions/issues"
42
+ },
43
+ "homepage": "https://github.com/Choochmeque/tauri-apple-extensions#readme",
44
+ "dependencies": {
45
+ "commander": "^14.0.2"
46
+ },
47
+ "devDependencies": {
48
+ "@eslint/js": "^9.39.2",
49
+ "@rollup/plugin-node-resolve": "^16.0.3",
50
+ "@rollup/plugin-typescript": "^12.3.0",
51
+ "@types/node": "^25.0.3",
52
+ "eslint": "^9.0.0",
53
+ "prettier": "^3.0.0",
54
+ "rollup": "^4.0.0",
55
+ "tslib": "^2.8.1",
56
+ "typescript": "^5.9.3",
57
+ "typescript-eslint": "^8.51.0",
58
+ "vitest": "^4.0.16"
59
+ },
60
+ "engines": {
61
+ "node": ">=18.0.0"
62
+ },
63
+ "packageManager": "pnpm@10.16.0+sha512.8066e7b034217b700a9a4dbb3a005061d641ba130a89915213a10b3ca4919c19c037bec8066afdc559b89635fdb806b16ea673f2468fbb28aabfa13c53e3f769"
64
+ }