@maccesar/titools 2.2.9 → 2.2.11
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/lib/commands/update.js
CHANGED
|
@@ -42,6 +42,49 @@ function hasAnySkillSymlink(platformSkillsDir) {
|
|
|
42
42
|
return skillList.some((skill) => existsSync(join(platformSkillsDir, skill)));
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Perform the actual update for a specific scope
|
|
47
|
+
* @param {string|undefined} baseDir - Base directory (undefined = global, path = local)
|
|
48
|
+
* @param {string} repoDir - Repository directory
|
|
49
|
+
* @param {Object} spinner - Ora spinner instance
|
|
50
|
+
* @returns {Promise<void>}
|
|
51
|
+
*/
|
|
52
|
+
async function performUpdate(baseDir, repoDir, spinner) {
|
|
53
|
+
// Detect platforms with existing symlinks (only update those)
|
|
54
|
+
const detectedPlatforms = detectPlatforms(baseDir);
|
|
55
|
+
const platformsWithSymlinks = detectedPlatforms.filter((p) =>
|
|
56
|
+
hasAnySkillSymlink(p.skillsDir)
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Install skills
|
|
60
|
+
spinner.start('Syncing skills...');
|
|
61
|
+
const skillsResult = await installSkills(repoDir, baseDir);
|
|
62
|
+
spinner.succeed(`${skillsResult.installed.length} skills updated`);
|
|
63
|
+
|
|
64
|
+
// Install agents (only if Claude Code has symlinks)
|
|
65
|
+
const claudePlatform = platformsWithSymlinks.find((p) => p.name === 'claude');
|
|
66
|
+
if (claudePlatform) {
|
|
67
|
+
spinner.start('Syncing agents...');
|
|
68
|
+
const agentsResult = await installAgents(repoDir, baseDir);
|
|
69
|
+
if (agentsResult.installed.length > 0) {
|
|
70
|
+
spinner.succeed('Platform agents updated');
|
|
71
|
+
} else {
|
|
72
|
+
spinner.info('No agents to sync');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
cleanupLegacyArtifacts(baseDir);
|
|
77
|
+
|
|
78
|
+
// Update symlinks silently for platforms that already had them
|
|
79
|
+
for (const platform of platformsWithSymlinks) {
|
|
80
|
+
await createSkillSymlinks(
|
|
81
|
+
platform.skillsDir,
|
|
82
|
+
SKILLS,
|
|
83
|
+
baseDir
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
45
88
|
/**
|
|
46
89
|
* Update command handler
|
|
47
90
|
* @param {Object} options - Command options
|
|
@@ -65,15 +108,16 @@ export async function updateCommand(options) {
|
|
|
65
108
|
const localPlatforms = detectPlatforms(projectDir);
|
|
66
109
|
const hasLocalSkills = isProject && hasSkillsAt(projectDir);
|
|
67
110
|
const hasGlobalSkills = hasSkillsAt(undefined);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
111
|
+
|
|
112
|
+
// Only show prompt if BOTH local and global skills exist
|
|
113
|
+
if (hasLocalSkills && hasGlobalSkills) {
|
|
71
114
|
try {
|
|
72
115
|
const scope = await select({
|
|
73
|
-
message: '
|
|
116
|
+
message: 'Both local and global skills detected. What do you want to update:',
|
|
74
117
|
choices: [
|
|
75
118
|
{ name: 'Global skills (user home)', value: 'global' },
|
|
76
119
|
{ name: 'Local skills (current project)', value: 'local' },
|
|
120
|
+
{ name: 'Both locations', value: 'both' },
|
|
77
121
|
],
|
|
78
122
|
theme: {
|
|
79
123
|
style: {
|
|
@@ -87,32 +131,44 @@ export async function updateCommand(options) {
|
|
|
87
131
|
process.exit(0);
|
|
88
132
|
}
|
|
89
133
|
if (scope === 'local') {
|
|
90
|
-
baseDir =
|
|
134
|
+
baseDir = projectDir;
|
|
135
|
+
} else if (scope === 'both') {
|
|
136
|
+
baseDir = 'both';
|
|
91
137
|
}
|
|
92
138
|
} catch (error) {
|
|
93
139
|
console.log('\nCancelled.');
|
|
94
140
|
process.exit(0);
|
|
95
141
|
}
|
|
142
|
+
} else if (hasLocalSkills && !hasGlobalSkills) {
|
|
143
|
+
// Only local skills exist, update local
|
|
144
|
+
baseDir = projectDir;
|
|
96
145
|
}
|
|
146
|
+
// If only global skills exist, baseDir remains undefined (global)
|
|
97
147
|
}
|
|
98
148
|
|
|
99
|
-
|
|
149
|
+
// Display mode
|
|
150
|
+
if (baseDir === 'both') {
|
|
151
|
+
console.log(chalk.cyan('Mode: Updating both global and local skills'));
|
|
152
|
+
} else if (baseDir) {
|
|
100
153
|
console.log(chalk.cyan('Mode: Local update (current project)'));
|
|
101
154
|
} else {
|
|
102
155
|
console.log(chalk.cyan('Mode: Global update (user home)'));
|
|
103
156
|
}
|
|
104
157
|
console.log('');
|
|
105
158
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
159
|
+
// Verify skills are installed
|
|
160
|
+
if (baseDir !== 'both') {
|
|
161
|
+
const skillsDir = getAgentsSkillsDir(baseDir);
|
|
162
|
+
const hasSkillsInstalled = skillsDir && SKILLS.some((skill) => existsSync(join(skillsDir, skill)));
|
|
163
|
+
if (!hasSkillsInstalled) {
|
|
164
|
+
console.log(chalk.yellow('No skills installed at this location.'));
|
|
165
|
+
console.log('Install them first with:');
|
|
166
|
+
console.log(' titools install');
|
|
167
|
+
console.log('');
|
|
168
|
+
console.log('Looked for skills in:');
|
|
169
|
+
console.log(` ${baseDir ? 'Local' : 'Global'}: ${skillsDir}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
116
172
|
}
|
|
117
173
|
|
|
118
174
|
// Check for updates
|
|
@@ -121,7 +177,7 @@ export async function updateCommand(options) {
|
|
|
121
177
|
try {
|
|
122
178
|
const hasUpdate = await checkForUpdate(PACKAGE_VERSION);
|
|
123
179
|
|
|
124
|
-
// If there's a newer version on
|
|
180
|
+
// If there's a newer version on npm, prompt user to update CLI first
|
|
125
181
|
if (hasUpdate) {
|
|
126
182
|
let latestVersion = '(newer)';
|
|
127
183
|
try {
|
|
@@ -158,38 +214,18 @@ export async function updateCommand(options) {
|
|
|
158
214
|
return;
|
|
159
215
|
}
|
|
160
216
|
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
// Install skills
|
|
168
|
-
spinner.start('Syncing skills...');
|
|
169
|
-
const skillsResult = await installSkills(repoDir, baseDir);
|
|
170
|
-
spinner.succeed(`${skillsResult.installed.length} skills updated`);
|
|
171
|
-
|
|
172
|
-
// Install agents (only if Claude Code has symlinks)
|
|
173
|
-
const claudePlatform = platformsWithSymlinks.find((p) => p.name === 'claude');
|
|
174
|
-
if (claudePlatform) {
|
|
175
|
-
spinner.start('Syncing agents...');
|
|
176
|
-
const agentsResult = await installAgents(repoDir, baseDir);
|
|
177
|
-
if (agentsResult.installed.length > 0) {
|
|
178
|
-
spinner.succeed('Platform agents updated');
|
|
179
|
-
} else {
|
|
180
|
-
spinner.info('No agents to sync');
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
cleanupLegacyArtifacts(baseDir);
|
|
217
|
+
// Perform update(s)
|
|
218
|
+
if (baseDir === 'both') {
|
|
219
|
+
// Update both global and local
|
|
220
|
+
console.log(chalk.bold('Updating global skills...'));
|
|
221
|
+
await performUpdate(undefined, repoDir, spinner);
|
|
222
|
+
console.log('');
|
|
185
223
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
baseDir
|
|
192
|
-
);
|
|
224
|
+
console.log(chalk.bold('Updating local skills...'));
|
|
225
|
+
await performUpdate(process.cwd(), repoDir, spinner);
|
|
226
|
+
} else {
|
|
227
|
+
// Update single scope
|
|
228
|
+
await performUpdate(baseDir, repoDir, spinner);
|
|
193
229
|
}
|
|
194
230
|
|
|
195
231
|
// Summary
|
package/package.json
CHANGED
|
@@ -31,18 +31,25 @@ Behavior:
|
|
|
31
31
|
|
|
32
32
|
## Workflow
|
|
33
33
|
|
|
34
|
-
1. Architecture: define structure (`lib/api`, `lib/services`, `lib/helpers`)
|
|
34
|
+
1. Architecture: define structure by technical type with flat folders (`lib/api`, `lib/services`, `lib/actions`, `lib/repositories`, `lib/helpers`)
|
|
35
35
|
2. Data strategy: choose Models (SQLite) or Collections (API)
|
|
36
36
|
3. Contracts: define I/O specs between layers
|
|
37
37
|
4. Implementation: write XML views and ES6+ controllers
|
|
38
38
|
5. Quality: testing, error handling, logging, performance
|
|
39
39
|
6. Cleanup: implement a `cleanup()` pattern for memory management
|
|
40
40
|
|
|
41
|
+
## Organization policy (low freedom)
|
|
42
|
+
|
|
43
|
+
- Use technical-type organization in `lib` (for example: `api`, `services`, `actions`, `repositories`, `helpers`, `policies`, `providers`).
|
|
44
|
+
- Keep `lib` flat and predictable: `lib/<type>/<file>.js` only.
|
|
45
|
+
- Do not recommend deep nesting like `lib/services/auth/session/login.js`.
|
|
46
|
+
- Keep UI layers aligned by screen (`controllers/`, `views/`, `styles/`) and avoid unnecessary depth.
|
|
47
|
+
|
|
41
48
|
## Quick start example
|
|
42
49
|
|
|
43
50
|
Minimal example that matches the conventions:
|
|
44
51
|
|
|
45
|
-
View (`views/
|
|
52
|
+
View (`views/userCard.xml`)
|
|
46
53
|
```xml
|
|
47
54
|
<Alloy>
|
|
48
55
|
<View id="cardContainer">
|
|
@@ -58,7 +65,7 @@ View (`views/user/card.xml`)
|
|
|
58
65
|
</Alloy>
|
|
59
66
|
```
|
|
60
67
|
|
|
61
|
-
Styles (`styles/
|
|
68
|
+
Styles (`styles/userCard.tss`)
|
|
62
69
|
```tss
|
|
63
70
|
"#cardContainer": { left: 8, right: 8, top: 8, height: Ti.UI.SIZE, borderRadius: 12, backgroundColor: '#fff' }
|
|
64
71
|
"#headerRow": { layout: 'horizontal', left: 12, right: 12, top: 12, height: Ti.UI.SIZE, width: Ti.UI.FILL }
|
|
@@ -67,7 +74,7 @@ Styles (`styles/user/card.tss`)
|
|
|
67
74
|
"#viewProfileBtn": { left: 12, right: 12, bottom: 12, height: 40, width: Ti.UI.FILL, borderRadius: 6, backgroundColor: '#2563eb', color: '#fff' }
|
|
68
75
|
```
|
|
69
76
|
|
|
70
|
-
Controller (`controllers/
|
|
77
|
+
Controller (`controllers/userCard.js`)
|
|
71
78
|
```javascript
|
|
72
79
|
const { Navigation } = require('services/navigation')
|
|
73
80
|
|
|
@@ -77,7 +84,7 @@ function init() {
|
|
|
77
84
|
}
|
|
78
85
|
|
|
79
86
|
function onViewProfile() {
|
|
80
|
-
Navigation.open('
|
|
87
|
+
Navigation.open('userProfile', { userId: $.args.user.id })
|
|
81
88
|
}
|
|
82
89
|
|
|
83
90
|
function cleanup() {
|
|
@@ -163,6 +170,7 @@ For the complete reference with examples, see [Alloy builtins and globals](refer
|
|
|
163
170
|
| Fastest way to build? | `tn <recipe>` (using TiNy CLI wrapper) |
|
|
164
171
|
| Where does API call go? | `lib/api/` |
|
|
165
172
|
| Where does business logic go? | `lib/services/` |
|
|
173
|
+
| How deep should `lib` folders be? | One level: `lib/<type>/<file>.js` |
|
|
166
174
|
| Where do I store auth tokens? | Keychain (iOS) / KeyStore (Android) via service |
|
|
167
175
|
| Models or Collections? | Collections for API data, Models for SQLite persistence |
|
|
168
176
|
| Ti.App.fireEvent or EventBus? | Always EventBus (Backbone.Events) |
|
|
@@ -4,52 +4,81 @@
|
|
|
4
4
|
|
|
5
5
|
```
|
|
6
6
|
app/
|
|
7
|
-
├── controllers/
|
|
7
|
+
├── controllers/ # View orchestrators
|
|
8
8
|
│ ├── index.js # Bootstrap only (no business logic)
|
|
9
|
-
│
|
|
10
|
-
│
|
|
9
|
+
│ ├── home.js
|
|
10
|
+
│ └── userProfile.js
|
|
11
11
|
├── models/ # OPTIONAL: For persistence with migrations
|
|
12
12
|
│ └── user.js # Model definition (ONLY if using SQLite)
|
|
13
13
|
├── views/ # XML views
|
|
14
14
|
│ ├── index.xml
|
|
15
|
-
│
|
|
16
|
-
│
|
|
15
|
+
│ ├── home.xml
|
|
16
|
+
│ └── userProfile.xml
|
|
17
17
|
├── styles/ # TSS styles (one per view + global)
|
|
18
18
|
│ ├── app.tss # Global application styles
|
|
19
19
|
│ ├── index.tss # Styles for index view
|
|
20
|
-
│
|
|
21
|
-
│
|
|
20
|
+
│ ├── home.tss
|
|
21
|
+
│ └── userProfile.tss
|
|
22
22
|
├── lib/ # Reusable logic (no UI)
|
|
23
23
|
│ ├── api/
|
|
24
|
-
│ │
|
|
24
|
+
│ │ ├── authApi.js
|
|
25
|
+
│ │ ├── userApi.js
|
|
26
|
+
│ │ └── frameApi.js
|
|
25
27
|
│ ├── services/
|
|
26
|
-
│ │ ├──
|
|
27
|
-
│ │ ├──
|
|
28
|
-
│ │ └──
|
|
29
|
-
│
|
|
30
|
-
│
|
|
31
|
-
│
|
|
28
|
+
│ │ ├── authService.js
|
|
29
|
+
│ │ ├── navigationService.js
|
|
30
|
+
│ │ └── notificationService.js
|
|
31
|
+
│ ├── actions/
|
|
32
|
+
│ │ ├── syncUserAction.js
|
|
33
|
+
│ │ └── refreshSessionAction.js
|
|
34
|
+
│ ├── repositories/
|
|
35
|
+
│ │ ├── userRepository.js
|
|
36
|
+
│ │ └── settingsRepository.js
|
|
37
|
+
│ ├── helpers/
|
|
38
|
+
│ │ ├── validator.js
|
|
39
|
+
│ │ ├── formatter.js
|
|
40
|
+
│ │ └── dateHelper.js
|
|
41
|
+
│ ├── policies/
|
|
42
|
+
│ │ ├── permissionPolicy.js
|
|
43
|
+
│ │ └── featurePolicy.js
|
|
44
|
+
│ └── providers/
|
|
45
|
+
│ ├── containerProvider.js
|
|
46
|
+
│ └── loggerProvider.js
|
|
32
47
|
├── widgets/ # Truly reusable components (used in 3+ places)
|
|
33
48
|
│ └── customButton/
|
|
34
49
|
├── config.json # Alloy configuration
|
|
35
50
|
└── alloy.js # Collections & Global services
|
|
36
51
|
```
|
|
37
52
|
|
|
53
|
+
## Organization strategy
|
|
54
|
+
|
|
55
|
+
- `lib/` uses technical-type grouping (Laravel-style naming adapted to Titanium).
|
|
56
|
+
- UI stays in Alloy MVC folders (`controllers`, `views`, `styles`).
|
|
57
|
+
- This is a hybrid approach: technical grouping for reusable logic + screen-based organization for UI files.
|
|
58
|
+
- Keep folder depth low to preserve discoverability.
|
|
59
|
+
- Use clear composed names (`authService.js`, `userRepository.js`, `authApi.js`) and keep multiple files per folder as the normal case.
|
|
60
|
+
|
|
61
|
+
### Folder depth policy (critical)
|
|
62
|
+
|
|
63
|
+
- Allowed in `lib`: `lib/<type>/<file>.js`
|
|
64
|
+
- Avoid in `lib`: `lib/<type>/<domain>/<subdomain>/<file>.js`
|
|
65
|
+
- If a folder grows too much, split by new technical type, not deep tree nesting.
|
|
66
|
+
|
|
38
67
|
## lib/ folder and module require paths
|
|
39
68
|
|
|
40
69
|
:::danger CRITICAL: lib/ Folder is FLATTENED During Build
|
|
41
70
|
When Alloy compiles, the **entire `lib/` folder is flattened to the root of Resources**. This means:
|
|
42
|
-
- `app/lib/services/
|
|
43
|
-
- `app/lib/api/
|
|
71
|
+
- `app/lib/services/authService.js` → `Resources/iphone/services/authService.js`
|
|
72
|
+
- `app/lib/api/authApi.js` → `Resources/iphone/api/authApi.js`
|
|
44
73
|
|
|
45
74
|
**Therefore, require statements should NOT include `lib/` prefix:**
|
|
46
75
|
```javascript
|
|
47
76
|
// ❌ WRONG - Will fail at runtime
|
|
48
|
-
const
|
|
77
|
+
const authApi = require('lib/api/authApi')
|
|
49
78
|
|
|
50
79
|
// ✅ CORRECT - Path relative to flattened lib/
|
|
51
|
-
const
|
|
52
|
-
const
|
|
80
|
+
const authApi = require('api/authApi')
|
|
81
|
+
const authService = require('services/authService')
|
|
53
82
|
```
|
|
54
83
|
|
|
55
84
|
**This applies to:**
|
|
@@ -62,13 +91,18 @@ const picsum = require('services/picsum')
|
|
|
62
91
|
app/
|
|
63
92
|
├── lib/
|
|
64
93
|
│ ├── services/
|
|
65
|
-
│ │ ├──
|
|
66
|
-
│ │ ├──
|
|
67
|
-
│ │ └──
|
|
68
|
-
│
|
|
69
|
-
│
|
|
94
|
+
│ │ ├── authService.js # require('services/navigationService')
|
|
95
|
+
│ │ ├── navigationService.js # require('services/notificationService')
|
|
96
|
+
│ │ └── notificationService.js
|
|
97
|
+
│ ├── api/
|
|
98
|
+
│ │ ├── authApi.js # require('services/authService')
|
|
99
|
+
│ │ ├── userApi.js
|
|
100
|
+
│ │ └── frameApi.js
|
|
101
|
+
│ └── repositories/
|
|
102
|
+
│ ├── userRepository.js
|
|
103
|
+
│ └── settingsRepository.js
|
|
70
104
|
├── controllers/
|
|
71
|
-
│ └── index.js
|
|
105
|
+
│ └── index.js # require('services/authService')
|
|
72
106
|
```
|
|
73
107
|
:::
|
|
74
108
|
|
|
@@ -145,6 +179,7 @@ api.getFrames()
|
|
|
145
179
|
- Handle UI events and delegate to services.
|
|
146
180
|
- Format data for display (simple cases).
|
|
147
181
|
- Manage view lifecycle (including cleanup).
|
|
182
|
+
- Keep `lib` modules flat and easy to locate.
|
|
148
183
|
|
|
149
184
|
**DON'T:**
|
|
150
185
|
- Use inline style attributes in XML (define in TSS files).
|
|
@@ -198,3 +198,176 @@ Installation method:
|
|
|
198
198
|
2. Open Xcode, then Window, then Devices.
|
|
199
199
|
3. Select the device, then Installed Apps, then +.
|
|
200
200
|
4. Select the IPA file.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Mac Catalyst distribution (Mac App Store)
|
|
205
|
+
|
|
206
|
+
Mac Catalyst allows you to run your iPad app on macOS. Titanium SDK 13.1.1.GA and later supports building for Mac Catalyst.
|
|
207
|
+
|
|
208
|
+
### 1. Enable Mac Catalyst for your App ID
|
|
209
|
+
|
|
210
|
+
1. Go to [Apple Developer → Identifiers](https://developer.apple.com/account/resources/identifiers/list)
|
|
211
|
+
2. Select your App ID or create a new one
|
|
212
|
+
3. Enable **Mac Catalyst** capability
|
|
213
|
+
4. Save the changes
|
|
214
|
+
|
|
215
|
+
### 2. Create Mac App Store Distribution Certificate
|
|
216
|
+
|
|
217
|
+
1. Go to [Apple Developer → Certificates](https://developer.apple.com/account/resources/certificates/list)
|
|
218
|
+
2. Click **+** to create a new certificate
|
|
219
|
+
3. Select **Mac App Store Distribution**
|
|
220
|
+
4. Upload your CSR (Certificate Signing Request)
|
|
221
|
+
5. Download and install the certificate
|
|
222
|
+
|
|
223
|
+
### 3. Mac Catalyst build targets
|
|
224
|
+
|
|
225
|
+
Titanium provides two targets for Mac Catalyst:
|
|
226
|
+
|
|
227
|
+
| Target | Description | Configuration |
|
|
228
|
+
|--------|-------------|---------------|
|
|
229
|
+
| `macos` | Development builds for testing on Mac | Debug-maccatalyst |
|
|
230
|
+
| `dist-macappstore` | Production builds for Mac App Store | Release-maccatalyst |
|
|
231
|
+
|
|
232
|
+
### 4. Build for Mac Catalyst (Development)
|
|
233
|
+
|
|
234
|
+
```bash
|
|
235
|
+
ti build -p ios -T macos
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The `.app` bundle will be created at:
|
|
239
|
+
```
|
|
240
|
+
build/iphone/build/Products/Debug-maccatalyst/AppName.app
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
For a production-ready build:
|
|
244
|
+
```bash
|
|
245
|
+
ti build -p ios -T macos --deploy-type production
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The `.app` bundle will be at:
|
|
249
|
+
```
|
|
250
|
+
build/iphone/build/Products/Release-maccatalyst/AppName.app
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### 5. Build for Mac App Store (Distribution)
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
ti build -p ios -T dist-macappstore [-R <CERTIFICATE_NAME>]
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
Example:
|
|
260
|
+
```bash
|
|
261
|
+
ti build -p ios -T dist-macappstore -R "Apple Distribution: Your Team Name (TEAM_ID)"
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
If you omit the `-R` flag, Titanium will prompt you to select a certificate.
|
|
265
|
+
|
|
266
|
+
**What happens during the build:**
|
|
267
|
+
- Uses `Release-maccatalyst` configuration
|
|
268
|
+
- Sets code signing to Manual with identity `-`
|
|
269
|
+
- Creates a `.xcarchive` for Mac App Store
|
|
270
|
+
- Installs the archive in Xcode's Organizer
|
|
271
|
+
- Destination: `generic/platform=macOS`
|
|
272
|
+
|
|
273
|
+
### 6. Upload to Mac App Store Connect
|
|
274
|
+
|
|
275
|
+
1. Open Xcode → Window → Organizer
|
|
276
|
+
2. Select your Mac Catalyst archive
|
|
277
|
+
3. Click **Validate App** to check for issues
|
|
278
|
+
4. Click **Distribute App**
|
|
279
|
+
5. Select **Mac App Store**
|
|
280
|
+
6. Follow the prompts to upload
|
|
281
|
+
|
|
282
|
+
### 7. Create app listing in App Store Connect
|
|
283
|
+
|
|
284
|
+
1. Go to [App Store Connect](https://appstoreconnect.apple.com)
|
|
285
|
+
2. **My Apps → + → New App**
|
|
286
|
+
3. Select **Mac** as platform
|
|
287
|
+
4. Enter app details:
|
|
288
|
+
- Name
|
|
289
|
+
- Primary Language
|
|
290
|
+
- Bundle ID (must match your App ID with Mac Catalyst enabled)
|
|
291
|
+
- SKU
|
|
292
|
+
5. Complete required metadata:
|
|
293
|
+
- Description
|
|
294
|
+
- Keywords
|
|
295
|
+
- Screenshots (Mac-specific sizes)
|
|
296
|
+
- Category
|
|
297
|
+
- Age rating
|
|
298
|
+
|
|
299
|
+
### 8. Mac Catalyst entitlements
|
|
300
|
+
|
|
301
|
+
Add Mac-specific entitlements in `tiapp.xml`:
|
|
302
|
+
|
|
303
|
+
```xml
|
|
304
|
+
<ios>
|
|
305
|
+
<entitlements>
|
|
306
|
+
<dict>
|
|
307
|
+
<!-- File access for saving to Downloads -->
|
|
308
|
+
<key>com.apple.security.files.user-selected.read-write</key>
|
|
309
|
+
<true/>
|
|
310
|
+
<key>com.apple.security.files.downloads.read-write</key>
|
|
311
|
+
<true/>
|
|
312
|
+
|
|
313
|
+
<!-- App sandbox (required for Mac App Store) -->
|
|
314
|
+
<key>com.apple.security.app-sandbox</key>
|
|
315
|
+
<true/>
|
|
316
|
+
|
|
317
|
+
<!-- Network access -->
|
|
318
|
+
<key>com.apple.security.network.client</key>
|
|
319
|
+
<true/>
|
|
320
|
+
|
|
321
|
+
<!-- Additional entitlements as needed -->
|
|
322
|
+
<key>com.apple.security.print</key>
|
|
323
|
+
<true/>
|
|
324
|
+
</dict>
|
|
325
|
+
</entitlements>
|
|
326
|
+
</ios>
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### 9. Common issues
|
|
330
|
+
|
|
331
|
+
**Issue**: "No suitable signing certificate found"
|
|
332
|
+
- Ensure you have a **Mac App Store Distribution Certificate** (not iOS Distribution)
|
|
333
|
+
- The certificate must be installed in your Keychain
|
|
334
|
+
|
|
335
|
+
**Issue**: Build fails with code signing errors
|
|
336
|
+
- Verify your App ID has Mac Catalyst enabled
|
|
337
|
+
- Check that the certificate matches the App ID
|
|
338
|
+
- Try cleaning the build: `ti clean -p ios`
|
|
339
|
+
|
|
340
|
+
**Issue**: App crashes on launch
|
|
341
|
+
- Verify entitlements are correctly configured
|
|
342
|
+
- Check Console.app for crash logs
|
|
343
|
+
- Ensure all required capabilities are enabled
|
|
344
|
+
|
|
345
|
+
### 10. Versioning
|
|
346
|
+
|
|
347
|
+
Update version numbers in `tiapp.xml`:
|
|
348
|
+
|
|
349
|
+
```xml
|
|
350
|
+
<ti:app xmlns:ti="http://ti.tidev.io">
|
|
351
|
+
<id>com.yourcompany.yourapp</id>
|
|
352
|
+
<name>Your App Name</name>
|
|
353
|
+
<version>1.0.0</version>
|
|
354
|
+
<publisher>Your Company</publisher>
|
|
355
|
+
...
|
|
356
|
+
</ti:app>
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
- `version`: Display version (e.g., "1.0.0")
|
|
360
|
+
- For iOS, use `pv-version-code` in `<ios>` section for build number
|
|
361
|
+
- For Mac, the build number can be set in Xcode or via `CFBundleVersion`
|
|
362
|
+
|
|
363
|
+
### 11. Testing on Mac
|
|
364
|
+
|
|
365
|
+
Before submitting to Mac App Store:
|
|
366
|
+
|
|
367
|
+
1. Build with `macos` target for testing
|
|
368
|
+
2. Run the app on different Mac architectures (Intel and Apple Silicon)
|
|
369
|
+
3. Test all features that use file system, network, and other sandboxed resources
|
|
370
|
+
4. Verify entitlements are working correctly
|
|
371
|
+
5. Test on macOS versions you plan to support
|
|
372
|
+
|
|
373
|
+
---
|
|
@@ -258,7 +258,7 @@ Omit `-V` and `-P` to be prompted.
|
|
|
258
258
|
| `-P, --pp-uuid <uuid>` | Provisioning profile UUID (for device/dist targets). |
|
|
259
259
|
| `-R, --distribution-name <name>` | iOS Distribution Certificate (for dist targets). |
|
|
260
260
|
| `--sim-focus` | Focus the iOS Simulator after launching (default: true). Use --no-sim-focus to disable. |
|
|
261
|
-
| `-T, --target <value>` | Target: simulator, device, dist-appstore, or dist-
|
|
261
|
+
| `-T, --target <value>` | Target: simulator, device, dist-appstore, dist-adhoc, macos, or dist-macappstore. |
|
|
262
262
|
| `-V, --developer-name <name>` | iOS Developer Certificate (required for device target). |
|
|
263
263
|
| `-W, --watch-device-id <udid>` | Watch simulator UDID (simulator only). |
|
|
264
264
|
| `--watch-app-name <name>` | Name of the watch app to launch (simulator only). |
|
|
@@ -394,6 +394,48 @@ ti build -p ios -T dist-appstore -R "Pseudo, Inc." -P "AAAAAAAA-0000-9999-8888-7
|
|
|
394
394
|
|
|
395
395
|
Installs the package to Xcode Organizer.
|
|
396
396
|
|
|
397
|
+
### Mac Catalyst (macOS Development)
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
ti build -p ios -T macos
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Builds a Mac Catalyst version of your iOS app for local testing. The resulting `.app` bundle is located at:
|
|
404
|
+
```
|
|
405
|
+
build/iphone/build/Products/Debug-maccatalyst/AppName.app
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
For a production build:
|
|
409
|
+
```bash
|
|
410
|
+
ti build -p ios -T macos --deploy-type production
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
The release `.app` bundle will be at:
|
|
414
|
+
```
|
|
415
|
+
build/iphone/build/Products/Release-maccatalyst/AppName.app
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Mac App Store (Mac Catalyst Distribution)
|
|
419
|
+
|
|
420
|
+
```bash
|
|
421
|
+
ti build -p ios -T dist-macappstore [-R <CERT_NAME>]
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
Example:
|
|
425
|
+
```bash
|
|
426
|
+
ti build -p ios -T dist-macappstore -R "Apple Distribution: Your Name (TEAM_ID)"
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
**Important Notes:**
|
|
430
|
+
- Requires a Mac App Store Distribution Certificate (not iOS Distribution)
|
|
431
|
+
- The build creates an archive for Mac App Store distribution
|
|
432
|
+
- The `.xcarchive` is installed in Xcode's Organizer
|
|
433
|
+
- Uses `Release-maccatalyst` configuration
|
|
434
|
+
- Destination: `generic/platform=macOS`
|
|
435
|
+
- Code signing is set to Manual with identity `-`
|
|
436
|
+
|
|
437
|
+
The target `dist-macappstore` is available in Titanium SDK 13.1.1.GA and later.
|
|
438
|
+
|
|
397
439
|
---
|
|
398
440
|
|
|
399
441
|
## SDK management
|