bleam 0.0.8 → 0.0.10

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 (96) hide show
  1. package/dist/cli.cjs +269 -22
  2. package/dist/cli.d.cts +32 -2
  3. package/dist/cli.d.ts +32 -2
  4. package/dist/cli.js +270 -23
  5. package/dist/platform.d.cts +2 -0
  6. package/dist/platform.d.ts +2 -0
  7. package/dist/{ui-1WepaMS4.d.cts → ui-Bg11tvlc.d.ts} +1 -1
  8. package/dist/{ui-Ce-pnUUA.d.ts → ui-Dd7SXdbg.d.cts} +7 -7
  9. package/dist/ui.d.cts +1 -1
  10. package/dist/ui.d.ts +1 -1
  11. package/dist/window.d.cts +1 -1
  12. package/dist/window.d.ts +1 -1
  13. package/package.json +5 -1
  14. package/templates/image-generation/app/index.tsx +1 -1
  15. package/templates/native/.gitattributes +1 -0
  16. package/templates/native/App.tsx +26 -0
  17. package/templates/native/app.json +12 -0
  18. package/templates/native/index.ts +8 -0
  19. package/templates/native/ios/.xcode.env +11 -0
  20. package/templates/native/ios/Bleam/AI/Flux2/Configuration/Flux2Config.swift +288 -0
  21. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryConfig.swift +233 -0
  22. package/templates/native/ios/Bleam/AI/Flux2/Configuration/MemoryOptimizationConfig.swift +157 -0
  23. package/templates/native/ios/Bleam/AI/Flux2/Configuration/ModelRegistry.swift +425 -0
  24. package/templates/native/ios/Bleam/AI/Flux2/Configuration/QuantizationConfig.swift +138 -0
  25. package/templates/native/ios/Bleam/AI/Flux2/Configuration/TransformerRepoProfile.swift +59 -0
  26. package/templates/native/ios/Bleam/AI/Flux2/Configuration/VAEConfig.swift +134 -0
  27. package/templates/native/ios/Bleam/AI/Flux2/Flux2Core.swift +55 -0
  28. package/templates/native/ios/Bleam/AI/Flux2/Loading/WeightLoader.swift +1192 -0
  29. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/FluxImageRunner.swift +1282 -0
  30. package/templates/native/ios/Bleam/AI/Flux2/Pipeline/LatentUtils.swift +191 -0
  31. package/templates/native/ios/Bleam/AI/Flux2/Scheduler/FlowMatchEulerScheduler.swift +279 -0
  32. package/templates/native/ios/Bleam/AI/Flux2/Text/KleinTextEncoder.swift +380 -0
  33. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Attention.swift +379 -0
  34. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Embeddings.swift +159 -0
  35. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FeedForward.swift +154 -0
  36. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2FusedKernels.swift +198 -0
  37. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2KVCache.swift +36 -0
  38. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Modulation.swift +193 -0
  39. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2ParallelAttention.swift +389 -0
  40. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2RoPE.swift +304 -0
  41. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2SingleBlock.swift +290 -0
  42. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2Transformer.swift +530 -0
  43. package/templates/native/ios/Bleam/AI/Flux2/Transformer/Flux2TransformerBlock.swift +362 -0
  44. package/templates/native/ios/Bleam/AI/Flux2/Utils/Flux2Debug.swift +101 -0
  45. package/templates/native/ios/Bleam/AI/Flux2/Utils/MLXCheckpoint.swift +118 -0
  46. package/templates/native/ios/Bleam/AI/Flux2/Utils/MemoryManager.swift +201 -0
  47. package/templates/native/ios/Bleam/AI/Flux2/VAE/AutoencoderKL.swift +476 -0
  48. package/templates/native/ios/Bleam/AI/Flux2/VAE/ResnetBlock.swift +316 -0
  49. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEDecoder.swift +120 -0
  50. package/templates/native/ios/Bleam/AI/Flux2/VAE/VAEEncoder.swift +136 -0
  51. package/templates/native/ios/Bleam/AppDelegate.swift +482 -0
  52. package/templates/native/ios/Bleam/Appearance.swift +62 -0
  53. package/templates/native/ios/Bleam/Bridging-Header.h +3 -0
  54. package/templates/native/ios/Bleam/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
  55. package/templates/native/ios/Bleam/Images.xcassets/Contents.json +6 -0
  56. package/templates/native/ios/Bleam/Info.plist +76 -0
  57. package/templates/native/ios/Bleam/PrivacyInfo.xcprivacy +48 -0
  58. package/templates/native/ios/Bleam/SceneDelegate.swift +58 -0
  59. package/templates/native/ios/Bleam/SplashScreen.storyboard +47 -0
  60. package/templates/native/ios/Bleam/Supporting/Expo.plist +6 -0
  61. package/templates/native/ios/Bleam/bleam.entitlements +10 -0
  62. package/templates/native/ios/Bleam.xcodeproj/project.pbxproj +1147 -0
  63. package/templates/native/ios/Bleam.xcodeproj/xcshareddata/xcschemes/Bleam.xcscheme +88 -0
  64. package/templates/native/ios/GenerationService/GenerationService.entitlements +10 -0
  65. package/templates/native/ios/GenerationService/ImageGenerationRunner.swift +591 -0
  66. package/templates/native/ios/GenerationService/Info.plist +31 -0
  67. package/templates/native/ios/GenerationService/main.swift +165 -0
  68. package/templates/native/ios/PlatformHelper/main.swift +726 -0
  69. package/templates/native/ios/Podfile +198 -0
  70. package/templates/native/ios/Podfile.lock +2284 -0
  71. package/templates/native/ios/Podfile.properties.json +5 -0
  72. package/templates/native/ios/Shared/Generation/GenerationServiceProtocol.swift +12 -0
  73. package/templates/native/ios/Shared/Generation/GenerationWorkerProtocol.swift +124 -0
  74. package/templates/native/metro.config.js +6 -0
  75. package/templates/native/modules/bleam-runtime/BleamRuntime.podspec +14 -0
  76. package/templates/native/modules/bleam-runtime/expo-module.config.json +8 -0
  77. package/templates/native/modules/bleam-runtime/ios/AIModule.swift +1258 -0
  78. package/templates/native/modules/bleam-runtime/ios/GenerationContracts.swift +135 -0
  79. package/templates/native/modules/bleam-runtime/ios/PlatformModule.swift +329 -0
  80. package/templates/native/modules/bleam-runtime/package.json +5 -0
  81. package/templates/native/package.json +39 -0
  82. package/templates/native/scripts/build/mlx-frameworks.ts +421 -0
  83. package/templates/native/scripts/start/index.ts +474 -0
  84. package/templates/native/scripts/start/known-failures.ts +20 -0
  85. package/templates/native/scripts/start/loader.ts +76 -0
  86. package/templates/native/scripts/start/native-state.ts +242 -0
  87. package/templates/native/scripts/start/package.json +4 -0
  88. package/templates/native/scripts/start/swift-packages.ts +414 -0
  89. package/templates/native/scripts/start/xcode-formatter.ts +106 -0
  90. package/templates/native/tsconfig.json +14 -0
  91. package/templates/native/yarn.lock +3749 -0
  92. package/templates/updates/README.md +272 -0
  93. package/templates/updates/src/index.ts +385 -0
  94. package/templates/updates/src/schema.ts +385 -0
  95. package/templates/updates/tsconfig.json +12 -0
  96. package/templates/updates/wrangler.jsonc +19 -0
@@ -0,0 +1,242 @@
1
+ import { createHash } from 'node:crypto'
2
+ import {
3
+ existsSync,
4
+ mkdirSync,
5
+ readdirSync,
6
+ readFileSync,
7
+ rmSync,
8
+ writeFileSync,
9
+ } from 'node:fs'
10
+ import path from 'node:path'
11
+
12
+ export type NativeChangeKind = 'none' | 'incremental' | 'structural'
13
+
14
+ type NativeFingerprint = {
15
+ structural: string
16
+ incremental: string
17
+ }
18
+
19
+ type NativeState = {
20
+ version: 1
21
+ fingerprint: NativeFingerprint
22
+ }
23
+
24
+ const STATE_FILE_NAME = '.bleam-native-state.json'
25
+
26
+ const STRUCTURAL_EXACT_FILES = new Set([
27
+ 'Podfile',
28
+ 'Podfile.lock',
29
+ 'Podfile.properties.json',
30
+ path.join('Pods', 'Manifest.lock'),
31
+ ])
32
+
33
+ const APP_SOURCE_EXTENSIONS = new Set([
34
+ '.c',
35
+ '.cc',
36
+ '.cpp',
37
+ '.entitlements',
38
+ '.h',
39
+ '.hpp',
40
+ '.m',
41
+ '.mm',
42
+ '.plist',
43
+ '.swift',
44
+ '.xcconfig',
45
+ ])
46
+
47
+ const IGNORED_SEGMENTS = new Set([
48
+ '.symlinks',
49
+ 'Headers',
50
+ 'Target Support Files',
51
+ 'Vendor',
52
+ 'build',
53
+ 'xcuserdata',
54
+ ])
55
+
56
+ function getStatePath(rootDir: string): string {
57
+ return path.join(rootDir, 'build', STATE_FILE_NAME)
58
+ }
59
+
60
+ function isIgnoredRelativePath(relativePath: string): boolean {
61
+ const segments = relativePath.split(path.sep)
62
+ return segments.some((segment) => IGNORED_SEGMENTS.has(segment))
63
+ }
64
+
65
+ function isStructuralRelativePath(relativePath: string): boolean {
66
+ if (STRUCTURAL_EXACT_FILES.has(relativePath)) {
67
+ return true
68
+ }
69
+
70
+ return (
71
+ relativePath.endsWith(
72
+ path.join('Pods', 'Pods.xcodeproj', 'project.pbxproj'),
73
+ ) ||
74
+ relativePath.endsWith(path.join('.xcodeproj', 'project.pbxproj')) ||
75
+ relativePath.endsWith(path.join('.xcworkspace', 'contents.xcworkspacedata'))
76
+ )
77
+ }
78
+
79
+ function isAppSourceRelativePath(relativePath: string): boolean {
80
+ if (relativePath.startsWith(`Pods${path.sep}`)) {
81
+ return false
82
+ }
83
+
84
+ if (isStructuralRelativePath(relativePath)) {
85
+ return false
86
+ }
87
+
88
+ if (
89
+ relativePath.includes(`${path.sep}.xcodeproj${path.sep}`) ||
90
+ relativePath.includes(`${path.sep}.xcworkspace${path.sep}`)
91
+ ) {
92
+ return false
93
+ }
94
+
95
+ return APP_SOURCE_EXTENSIONS.has(path.extname(relativePath))
96
+ }
97
+
98
+ export function classifyNativePath(
99
+ relativePath: string,
100
+ ): NativeChangeKind | null {
101
+ if (isIgnoredRelativePath(relativePath)) {
102
+ return null
103
+ }
104
+
105
+ if (isStructuralRelativePath(relativePath)) {
106
+ return 'structural'
107
+ }
108
+
109
+ if (isAppSourceRelativePath(relativePath)) {
110
+ return 'incremental'
111
+ }
112
+
113
+ return null
114
+ }
115
+
116
+ function collectTrackedFiles(
117
+ rootDir: string,
118
+ ): Record<NativeChangeKind, string[]> {
119
+ const buckets: Record<NativeChangeKind, string[]> = {
120
+ none: [],
121
+ incremental: [],
122
+ structural: [],
123
+ }
124
+
125
+ const iosDir = path.join(rootDir, 'ios')
126
+ if (!existsSync(iosDir)) {
127
+ return buckets
128
+ }
129
+
130
+ const walk = (currentDir: string) => {
131
+ for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
132
+ const fullPath = path.join(currentDir, entry.name)
133
+ const relativePath = path.relative(iosDir, fullPath)
134
+
135
+ if (isIgnoredRelativePath(relativePath)) {
136
+ continue
137
+ }
138
+
139
+ if (entry.isDirectory()) {
140
+ walk(fullPath)
141
+ continue
142
+ }
143
+
144
+ if (!entry.isFile()) {
145
+ continue
146
+ }
147
+
148
+ const kind = classifyNativePath(relativePath)
149
+ if (kind) {
150
+ buckets[kind].push(relativePath)
151
+ }
152
+ }
153
+ }
154
+
155
+ walk(iosDir)
156
+ buckets.incremental.sort()
157
+ buckets.structural.sort()
158
+ return buckets
159
+ }
160
+
161
+ function hashFiles(rootDir: string, relativePaths: string[]): string {
162
+ const hash = createHash('sha256')
163
+ const iosDir = path.join(rootDir, 'ios')
164
+
165
+ for (const relativePath of relativePaths) {
166
+ hash.update(relativePath)
167
+ hash.update('\0')
168
+ hash.update(readFileSync(path.join(iosDir, relativePath)))
169
+ hash.update('\0')
170
+ }
171
+
172
+ return hash.digest('hex')
173
+ }
174
+
175
+ export function computeNativeFingerprint(rootDir: string): NativeFingerprint {
176
+ const trackedFiles = collectTrackedFiles(rootDir)
177
+
178
+ return {
179
+ structural: hashFiles(rootDir, trackedFiles.structural),
180
+ incremental: hashFiles(rootDir, trackedFiles.incremental),
181
+ }
182
+ }
183
+
184
+ export function readNativeState(rootDir: string): NativeState | null {
185
+ const statePath = getStatePath(rootDir)
186
+ if (!existsSync(statePath)) {
187
+ return null
188
+ }
189
+
190
+ try {
191
+ const parsed = JSON.parse(readFileSync(statePath, 'utf8')) as NativeState
192
+ if (
193
+ parsed.version !== 1 ||
194
+ typeof parsed.fingerprint?.structural !== 'string' ||
195
+ typeof parsed.fingerprint?.incremental !== 'string'
196
+ ) {
197
+ return null
198
+ }
199
+
200
+ return parsed
201
+ } catch {
202
+ return null
203
+ }
204
+ }
205
+
206
+ export function writeNativeState(
207
+ rootDir: string,
208
+ fingerprint: NativeFingerprint,
209
+ ) {
210
+ const statePath = getStatePath(rootDir)
211
+ mkdirSync(path.dirname(statePath), { recursive: true })
212
+ writeFileSync(
213
+ statePath,
214
+ `${JSON.stringify({ version: 1, fingerprint }, null, 2)}\n`,
215
+ 'utf8',
216
+ )
217
+ }
218
+
219
+ export function clearNativeState(rootDir: string) {
220
+ rmSync(getStatePath(rootDir), { force: true })
221
+ }
222
+
223
+ export function getNativeChangeKind(rootDir: string): NativeChangeKind {
224
+ const previous = readNativeState(rootDir)
225
+ if (!previous) {
226
+ return 'structural'
227
+ }
228
+
229
+ const current = computeNativeFingerprint(rootDir)
230
+ if (
231
+ current.structural === previous.fingerprint.structural &&
232
+ current.incremental === previous.fingerprint.incremental
233
+ ) {
234
+ return 'none'
235
+ }
236
+
237
+ if (current.structural !== previous.fingerprint.structural) {
238
+ return 'structural'
239
+ }
240
+
241
+ return 'incremental'
242
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "private": true,
3
+ "type": "module"
4
+ }
@@ -0,0 +1,414 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import path from 'node:path'
3
+
4
+ type SwiftPackageRequirement =
5
+ | { kind: 'branch'; branch: string }
6
+ | { kind: 'exactVersion'; version: string }
7
+ | { kind: 'upToNextMajorVersion'; minimumVersion: string }
8
+
9
+ type RemoteSwiftPackageRequirement = {
10
+ identity: string
11
+ repositoryURL: string
12
+ requirement: SwiftPackageRequirement
13
+ }
14
+
15
+ type ResolvedPackagePin = {
16
+ identity: string
17
+ location: string
18
+ state: { branch?: string; revision?: string; version?: string }
19
+ }
20
+
21
+ export type ResolvePackagesPlan = {
22
+ args: string[]
23
+ reason:
24
+ | 'no-remote-dependencies'
25
+ | 'missing-package-resolved'
26
+ | 'missing-resolved-pin'
27
+ | 'requirement-mismatch'
28
+ | 'resolved-remote-packages'
29
+ }
30
+
31
+ type XcodeProjectPackageReferences = {
32
+ remote: RemoteSwiftPackageRequirement[]
33
+ localCount: number
34
+ }
35
+
36
+ type XcodeObject = {
37
+ isa?: string
38
+ repositoryURL?: string
39
+ requirement?: Record<string, unknown>
40
+ path?: string
41
+ }
42
+
43
+ const PACKAGE_RESOLVED_CANDIDATES = [
44
+ path.join('xcshareddata', 'swiftpm', 'Package.resolved'),
45
+ path.join('xcuserdata', 'swiftpm', 'Package.resolved'),
46
+ ]
47
+
48
+ const REMOTE_PACKAGE_PROGRESS_PATTERNS = [
49
+ /(?:fetching|updating|cloning)\s*(?:from\s+)?(?<target>https?:\/\/\S+|git@\S+|\S+\.git|[^\s,]+)$/i,
50
+ /checking out\s+(?<target>[^\s,]+)(?:\s|$)/i,
51
+ /working copy of package ['"](?<target>[^'"]+)['"]/i,
52
+ /package ['"](?<target>[^'"]+)['"]/i,
53
+ ]
54
+
55
+ function parseVersion(version: string): number[] | null {
56
+ const match = version.trim().match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/)
57
+ if (!match) {
58
+ return null
59
+ }
60
+
61
+ return [Number(match[1] ?? 0), Number(match[2] ?? 0), Number(match[3] ?? 0)]
62
+ }
63
+
64
+ function compareVersions(left: string, right: string): number {
65
+ const leftParts = parseVersion(left)
66
+ const rightParts = parseVersion(right)
67
+ if (!leftParts || !rightParts) {
68
+ return left.localeCompare(right)
69
+ }
70
+
71
+ for (let index = 0; index < 3; index += 1) {
72
+ const delta = leftParts[index] - rightParts[index]
73
+ if (delta !== 0) {
74
+ return delta
75
+ }
76
+ }
77
+
78
+ return 0
79
+ }
80
+
81
+ function normalizeIdentity(value: string): string {
82
+ return (
83
+ value
84
+ .trim()
85
+ .replace(/\.git$/i, '')
86
+ .replace(/\/+$/, '')
87
+ .split('/')
88
+ .pop()
89
+ ?.toLowerCase() ?? value.trim().toLowerCase()
90
+ )
91
+ }
92
+
93
+ function parseRequirement(
94
+ rawRequirement: Record<string, unknown>,
95
+ ): SwiftPackageRequirement | null {
96
+ const kind =
97
+ typeof rawRequirement.kind === 'string' ? rawRequirement.kind : null
98
+ if (!kind) {
99
+ return null
100
+ }
101
+
102
+ if (kind === 'branch' && typeof rawRequirement.branch === 'string') {
103
+ return { kind, branch: rawRequirement.branch }
104
+ }
105
+
106
+ if (kind === 'exactVersion' && typeof rawRequirement.version === 'string') {
107
+ return { kind, version: rawRequirement.version }
108
+ }
109
+
110
+ if (
111
+ kind === 'upToNextMajorVersion' &&
112
+ typeof rawRequirement.minimumVersion === 'string'
113
+ ) {
114
+ return { kind, minimumVersion: rawRequirement.minimumVersion }
115
+ }
116
+
117
+ return null
118
+ }
119
+
120
+ function collectSectionEntries(sectionBody: string): string[] {
121
+ const entries = []
122
+ let cursor = 0
123
+
124
+ while (cursor < sectionBody.length) {
125
+ const start = sectionBody.indexOf('= {', cursor)
126
+ if (start === -1) {
127
+ break
128
+ }
129
+
130
+ let depth = 0
131
+ let end = start
132
+ for (; end < sectionBody.length; end += 1) {
133
+ const char = sectionBody[end]
134
+ if (char === '{') {
135
+ depth += 1
136
+ } else if (char === '}') {
137
+ depth -= 1
138
+ if (depth === 0) {
139
+ const semicolonIndex = sectionBody.indexOf(';', end)
140
+ if (semicolonIndex === -1) {
141
+ break
142
+ }
143
+ entries.push(sectionBody.slice(start + 3, end + 1))
144
+ cursor = semicolonIndex + 1
145
+ break
146
+ }
147
+ }
148
+ }
149
+
150
+ if (end >= sectionBody.length) {
151
+ break
152
+ }
153
+ }
154
+
155
+ return entries
156
+ }
157
+
158
+ function parseXcodeObjects(projectContents: string): XcodeObject[] {
159
+ const objectsMatch = projectContents.match(
160
+ /objects = \{([\s\S]*?)\n\s*\};\n\s*rootObject =/,
161
+ )
162
+ if (!objectsMatch) {
163
+ return []
164
+ }
165
+
166
+ const objectsBlock = objectsMatch[1]
167
+ const sections = objectsBlock.split(/\/\* Begin /).slice(1)
168
+ const objects: XcodeObject[] = []
169
+
170
+ for (const section of sections) {
171
+ const endIndex = section.indexOf('/* End ')
172
+ const body = endIndex === -1 ? section : section.slice(0, endIndex)
173
+ for (const rawEntry of collectSectionEntries(body)) {
174
+ const isa = rawEntry.match(/\bisa = ([^;]+);/)?.[1]?.trim()
175
+ const repositoryURL = rawEntry
176
+ .match(/\brepositoryURL = ([^;]+);/)?.[1]
177
+ ?.trim()
178
+ const localPath = rawEntry.match(/\bpath = ([^;]+);/)?.[1]?.trim()
179
+ const requirementBody = rawEntry.match(
180
+ /\brequirement = \{([\s\S]*?)\n\s*\};/,
181
+ )
182
+
183
+ const requirement: Record<string, unknown> = {}
184
+ if (requirementBody) {
185
+ const fieldPattern = /(\w+) = ([^;]+);/g
186
+ let fieldMatch = fieldPattern.exec(requirementBody[1])
187
+ while (fieldMatch) {
188
+ requirement[fieldMatch[1]] = stripPbxString(fieldMatch[2])
189
+ fieldMatch = fieldPattern.exec(requirementBody[1])
190
+ }
191
+ }
192
+
193
+ objects.push({
194
+ isa: isa ? stripPbxString(isa) : undefined,
195
+ repositoryURL: repositoryURL
196
+ ? stripPbxString(repositoryURL)
197
+ : undefined,
198
+ path: localPath ? stripPbxString(localPath) : undefined,
199
+ requirement,
200
+ })
201
+ }
202
+ }
203
+
204
+ return objects
205
+ }
206
+
207
+ function stripPbxString(value: string): string {
208
+ return value.trim().replace(/^"|"$/g, '')
209
+ }
210
+
211
+ export function readProjectPackageReferences(
212
+ rootDir: string,
213
+ projectName: string,
214
+ ): XcodeProjectPackageReferences {
215
+ const projectPath = path.join(
216
+ rootDir,
217
+ 'ios',
218
+ `${projectName}.xcodeproj`,
219
+ 'project.pbxproj',
220
+ )
221
+ if (!existsSync(projectPath)) {
222
+ return { remote: [], localCount: 0 }
223
+ }
224
+
225
+ const contents = readFileSync(projectPath, 'utf8')
226
+ const objects = parseXcodeObjects(contents)
227
+ const remote: RemoteSwiftPackageRequirement[] = []
228
+ let localCount = 0
229
+
230
+ for (const object of objects) {
231
+ if (object.isa === 'XCLocalSwiftPackageReference' && object.path) {
232
+ localCount += 1
233
+ continue
234
+ }
235
+
236
+ if (
237
+ object.isa !== 'XCRemoteSwiftPackageReference' ||
238
+ !object.repositoryURL ||
239
+ !object.requirement
240
+ ) {
241
+ continue
242
+ }
243
+
244
+ const requirement = parseRequirement(object.requirement)
245
+ if (!requirement) {
246
+ continue
247
+ }
248
+
249
+ remote.push({
250
+ identity: normalizeIdentity(object.repositoryURL),
251
+ repositoryURL: object.repositoryURL,
252
+ requirement,
253
+ })
254
+ }
255
+
256
+ remote.sort((left, right) => left.identity.localeCompare(right.identity))
257
+ return { remote, localCount }
258
+ }
259
+
260
+ function findPackageResolvedPath(
261
+ rootDir: string,
262
+ projectName: string,
263
+ ): string | null {
264
+ const candidateRoots = [
265
+ path.join(rootDir, 'ios', `${projectName}.xcworkspace`),
266
+ path.join(
267
+ rootDir,
268
+ 'ios',
269
+ `${projectName}.xcodeproj`,
270
+ 'project.xcworkspace',
271
+ ),
272
+ ]
273
+
274
+ for (const candidateRoot of candidateRoots) {
275
+ for (const relativePath of PACKAGE_RESOLVED_CANDIDATES) {
276
+ const fullPath = path.join(candidateRoot, relativePath)
277
+ if (existsSync(fullPath)) {
278
+ return fullPath
279
+ }
280
+ }
281
+ }
282
+
283
+ return null
284
+ }
285
+
286
+ function readResolvedPackagePins(
287
+ rootDir: string,
288
+ projectName: string,
289
+ ): ResolvedPackagePin[] | null {
290
+ const packageResolvedPath = findPackageResolvedPath(rootDir, projectName)
291
+ if (!packageResolvedPath) {
292
+ return null
293
+ }
294
+
295
+ try {
296
+ const parsed = JSON.parse(readFileSync(packageResolvedPath, 'utf8')) as {
297
+ pins?: Array<{
298
+ identity?: string
299
+ location?: string
300
+ repositoryURL?: string
301
+ state?: ResolvedPackagePin['state']
302
+ }>
303
+ object?: {
304
+ pins?: Array<{
305
+ identity?: string
306
+ location?: string
307
+ repositoryURL?: string
308
+ state?: ResolvedPackagePin['state']
309
+ }>
310
+ }
311
+ }
312
+ const pins = parsed.pins ?? parsed.object?.pins ?? []
313
+ return pins
314
+ .filter((pin) => typeof pin.state === 'object' && pin.state !== null)
315
+ .map((pin) => ({
316
+ identity: normalizeIdentity(
317
+ pin.identity ?? pin.location ?? pin.repositoryURL ?? '',
318
+ ),
319
+ location: pin.location ?? pin.repositoryURL ?? '',
320
+ state: pin.state ?? {},
321
+ }))
322
+ .filter((pin) => pin.identity.length > 0)
323
+ } catch {
324
+ return null
325
+ }
326
+ }
327
+
328
+ function requirementSatisfied(
329
+ requirement: SwiftPackageRequirement,
330
+ pin: ResolvedPackagePin,
331
+ ): boolean {
332
+ if (requirement.kind === 'branch') {
333
+ return pin.state.branch === requirement.branch
334
+ }
335
+
336
+ if (requirement.kind === 'exactVersion') {
337
+ return pin.state.version === requirement.version
338
+ }
339
+
340
+ if (!pin.state.version) {
341
+ return false
342
+ }
343
+
344
+ const minimumParts = parseVersion(requirement.minimumVersion)
345
+ const versionParts = parseVersion(pin.state.version)
346
+ if (!minimumParts || !versionParts) {
347
+ return compareVersions(pin.state.version, requirement.minimumVersion) >= 0
348
+ }
349
+
350
+ return (
351
+ versionParts[0] === minimumParts[0] &&
352
+ compareVersions(pin.state.version, requirement.minimumVersion) >= 0
353
+ )
354
+ }
355
+
356
+ export function getSwiftPackageResolvePlan(
357
+ rootDir: string,
358
+ projectName: string,
359
+ ): ResolvePackagesPlan {
360
+ const references = readProjectPackageReferences(rootDir, projectName)
361
+ if (references.remote.length === 0) {
362
+ return {
363
+ args: [],
364
+ reason: 'no-remote-dependencies',
365
+ }
366
+ }
367
+
368
+ const pins = readResolvedPackagePins(rootDir, projectName)
369
+ if (!pins) {
370
+ return {
371
+ args: [],
372
+ reason: 'missing-package-resolved',
373
+ }
374
+ }
375
+
376
+ const pinsByIdentity = new Map(pins.map((pin) => [pin.identity, pin]))
377
+ for (const remote of references.remote) {
378
+ const pin = pinsByIdentity.get(remote.identity)
379
+ if (!pin) {
380
+ return {
381
+ args: [],
382
+ reason: 'missing-resolved-pin',
383
+ }
384
+ }
385
+
386
+ if (!requirementSatisfied(remote.requirement, pin)) {
387
+ return {
388
+ args: [],
389
+ reason: 'requirement-mismatch',
390
+ }
391
+ }
392
+ }
393
+
394
+ return {
395
+ args: ['-skipPackageUpdates'],
396
+ reason: 'resolved-remote-packages',
397
+ }
398
+ }
399
+
400
+ export function extractPackageProgressLabel(line: string): string | null {
401
+ for (const pattern of REMOTE_PACKAGE_PROGRESS_PATTERNS) {
402
+ const match = line.match(pattern)
403
+ const target = match?.groups?.target?.trim()
404
+ if (!target) {
405
+ continue
406
+ }
407
+
408
+ return normalizeIdentity(target)
409
+ .split(/[\s#?]/)[0]
410
+ .replace(/^["']|["']$/g, '')
411
+ }
412
+
413
+ return null
414
+ }