cocoapods-lynx-extension 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.
- checksums.yaml +7 -0
- data/lib/cocoapods-lynx-extension.rb +5 -0
- data/lib/cocoapods_plugin.rb +21 -0
- data/lib/lynx/extension/autolink.rb +260 -0
- metadata +59 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e469114efbb082e6d6f41b033c27cad07a9040c034f5cd32ec7361225eb8a293
|
|
4
|
+
data.tar.gz: daa9d4933feb84294aa4efb38c60005d8105eb3551f0608718caddb53561ff93
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3f75e2b6cea85991218a8dea036ec1fcb5911162cfc7dbc85bd699063d1ecab663976534e7a9f8bc833a777468550b5b4d3907f95ef258a39fed8fa76ddacd0f
|
|
7
|
+
data.tar.gz: 5b263cc73806034713c757d1bd8fb5f2ad45335813ecbf78a210ff53c703ee6f8c0e8f8afe4c522d502109b888dd0df3973f95dd19cc71b3b83a25d07581ed8f
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Copyright 2026 The Lynx Authors. All rights reserved.
|
|
2
|
+
# Licensed under the Apache License Version 2.0 that can be found in the
|
|
3
|
+
# LICENSE file in the root directory of this source tree.
|
|
4
|
+
|
|
5
|
+
require 'cocoapods-lynx-extension'
|
|
6
|
+
|
|
7
|
+
module Pod
|
|
8
|
+
class Podfile
|
|
9
|
+
module LynxExtensionDSL
|
|
10
|
+
def use_lynx_extension!(options = {})
|
|
11
|
+
Lynx::Extension::Autolink.install!(self, options)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
include LynxExtensionDSL
|
|
16
|
+
|
|
17
|
+
module DSL
|
|
18
|
+
include LynxExtensionDSL
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Copyright 2026 The Lynx Authors. All rights reserved.
|
|
2
|
+
# Licensed under the Apache License Version 2.0 that can be found in the
|
|
3
|
+
# LICENSE file in the root directory of this source tree.
|
|
4
|
+
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
module Lynx
|
|
9
|
+
module Extension
|
|
10
|
+
ExtensionInfo = Struct.new(:npm_name, :package_dir, :manifest_file, :source_dir, :podspec_path)
|
|
11
|
+
ComponentInfo = Struct.new(:kind, :name, :class_name)
|
|
12
|
+
|
|
13
|
+
class Autolink
|
|
14
|
+
REGISTRY_CLASS_NAME = 'LynxGeneratedExtensionRegistry'
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def install!(podfile, options = {})
|
|
18
|
+
start_dir = File.expand_path(options[:root] || Dir.pwd)
|
|
19
|
+
output_dir = File.expand_path(options[:output_dir] || 'generated/lynx-extension', start_dir)
|
|
20
|
+
extensions = scan(start_dir)
|
|
21
|
+
extensions.each do |extension|
|
|
22
|
+
pod_name = pod_name_from_podspec(extension.podspec_path)
|
|
23
|
+
podfile.pod pod_name, :path => File.dirname(extension.podspec_path)
|
|
24
|
+
end
|
|
25
|
+
generate_registry(output_dir, extensions)
|
|
26
|
+
podfile.pod 'LynxExtensionRegistry', :path => output_dir
|
|
27
|
+
extensions
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def scan(start_dir)
|
|
31
|
+
node_modules_dirs(start_dir).flat_map do |node_modules|
|
|
32
|
+
manifest_files(node_modules).map { |manifest| parse_manifest(manifest) }.compact
|
|
33
|
+
end.sort_by(&:npm_name)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def generate_registry(output_dir, extensions)
|
|
37
|
+
FileUtils.mkdir_p(output_dir)
|
|
38
|
+
components = extensions.flat_map { |extension| scan_components(extension.source_dir) }
|
|
39
|
+
File.write(File.join(output_dir, "#{REGISTRY_CLASS_NAME}.h"), header_source)
|
|
40
|
+
File.write(File.join(output_dir, "#{REGISTRY_CLASS_NAME}.m"),
|
|
41
|
+
implementation_source(components))
|
|
42
|
+
File.write(File.join(output_dir, 'LynxExtensionRegistry.podspec'), podspec_source)
|
|
43
|
+
components
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def scan_components(source_dir)
|
|
47
|
+
source_files(source_dir).flat_map { |file| scan_source_file(file) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def node_modules_dirs(start_dir)
|
|
53
|
+
dirs = []
|
|
54
|
+
current = File.expand_path(start_dir)
|
|
55
|
+
6.times do
|
|
56
|
+
candidate = File.join(current, 'node_modules')
|
|
57
|
+
dirs << candidate if File.directory?(candidate)
|
|
58
|
+
parent = File.dirname(current)
|
|
59
|
+
break if parent == current
|
|
60
|
+
current = parent
|
|
61
|
+
end
|
|
62
|
+
dirs.uniq
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def manifest_files(node_modules)
|
|
66
|
+
manifests = []
|
|
67
|
+
Dir.children(node_modules).sort.each do |name|
|
|
68
|
+
next if name.start_with?('.')
|
|
69
|
+
path = File.join(node_modules, name)
|
|
70
|
+
next unless File.directory?(path)
|
|
71
|
+
if name.start_with?('@')
|
|
72
|
+
Dir.children(path).sort.each do |scoped_name|
|
|
73
|
+
add_manifest(manifests, File.join(path, scoped_name))
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
add_manifest(manifests, path)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
manifests
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def add_manifest(manifests, package_dir)
|
|
83
|
+
manifest = File.join(package_dir, 'lynx.ext.json')
|
|
84
|
+
manifests << manifest if File.file?(manifest)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def parse_manifest(manifest_file)
|
|
88
|
+
json = JSON.parse(File.read(manifest_file))
|
|
89
|
+
ios = json.dig('platforms', 'ios')
|
|
90
|
+
return nil if ios.nil?
|
|
91
|
+
raise "Invalid ios platform entry in #{manifest_file}" unless ios.is_a?(Hash)
|
|
92
|
+
|
|
93
|
+
package_dir = File.dirname(manifest_file)
|
|
94
|
+
package_realpath = File.realpath(package_dir)
|
|
95
|
+
source_dir_name = ios['sourceDir'] || 'ios'
|
|
96
|
+
source_dir = resolve_package_path(package_realpath, source_dir_name, manifest_file, 'sourceDir')
|
|
97
|
+
raise "iOS sourceDir '#{source_dir_name}' does not exist for #{manifest_file}" unless
|
|
98
|
+
File.directory?(source_dir)
|
|
99
|
+
|
|
100
|
+
podspec_path = if ios['podspecPath']
|
|
101
|
+
resolve_package_path(package_realpath, ios['podspecPath'], manifest_file, 'podspecPath')
|
|
102
|
+
else
|
|
103
|
+
path = podspec_files(source_dir, package_realpath).first
|
|
104
|
+
validate_package_path(package_realpath, path, manifest_file, 'podspecPath') if path
|
|
105
|
+
end
|
|
106
|
+
raise "No iOS podspec found for #{manifest_file}" unless
|
|
107
|
+
podspec_path && File.file?(podspec_path)
|
|
108
|
+
|
|
109
|
+
npm_name = File.basename(package_dir)
|
|
110
|
+
parent_name = File.basename(File.dirname(package_dir))
|
|
111
|
+
npm_name = "#{parent_name}/#{npm_name}" if parent_name.start_with?('@')
|
|
112
|
+
ExtensionInfo.new(npm_name, package_dir, manifest_file, source_dir, podspec_path)
|
|
113
|
+
rescue JSON::ParserError => e
|
|
114
|
+
raise "Failed to parse #{manifest_file}: #{e.message}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def pod_name_from_podspec(podspec_path)
|
|
118
|
+
content = File.read(podspec_path)
|
|
119
|
+
match = content.match(/\.name\s*=\s*['"]([^'"]+)['"]/)
|
|
120
|
+
raise "Unable to read pod name from #{podspec_path}" unless match
|
|
121
|
+
match[1]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def resolve_package_path(package_realpath, configured_path, manifest_file, field_name)
|
|
125
|
+
path = File.expand_path(configured_path, package_realpath)
|
|
126
|
+
validate_package_path(package_realpath, path, manifest_file, field_name, configured_path)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def validate_package_path(package_realpath, path, manifest_file, field_name, configured_path = path)
|
|
130
|
+
path = File.realpath(path) if File.exist?(path)
|
|
131
|
+
return path if package_path?(package_realpath, path)
|
|
132
|
+
|
|
133
|
+
raise "iOS #{field_name} '#{configured_path}' must stay within package directory for #{manifest_file}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def package_path?(package_realpath, path)
|
|
137
|
+
path == package_realpath || path.start_with?("#{package_realpath}#{File::SEPARATOR}")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def podspec_files(source_dir, package_realpath)
|
|
141
|
+
files = []
|
|
142
|
+
dirs = [source_dir]
|
|
143
|
+
until dirs.empty?
|
|
144
|
+
dir = dirs.pop
|
|
145
|
+
Dir.children(dir).each do |name|
|
|
146
|
+
path = File.join(dir, name)
|
|
147
|
+
if File.symlink?(path)
|
|
148
|
+
files << path if name.end_with?('.podspec')
|
|
149
|
+
elsif File.directory?(path)
|
|
150
|
+
dirs << path if package_path?(package_realpath, File.realpath(path))
|
|
151
|
+
elsif File.file?(path) && name.end_with?('.podspec')
|
|
152
|
+
files << path
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
files.sort
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def source_files(source_dir)
|
|
160
|
+
Dir[File.join(source_dir, '**/*.{h,m,mm,swift}')].sort
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def scan_source_file(file)
|
|
164
|
+
content = File.read(file)
|
|
165
|
+
components = []
|
|
166
|
+
content.scan(/@implementation\s+([A-Za-z_][A-Za-z0-9_]*)(.*?)(?=@implementation|\z)/m) do
|
|
167
|
+
|class_name, body|
|
|
168
|
+
body.scan(/LYNX_LAZY_REGISTER_UI\(\s*@?"([^"]+)"\s*\)/) do |name|
|
|
169
|
+
components << ComponentInfo.new(:ui, name.first, class_name)
|
|
170
|
+
end
|
|
171
|
+
body.scan(/LYNX_LAZY_REGISTER_SHADOW_NODE\(\s*@?"([^"]+)"\s*\)/) do |name|
|
|
172
|
+
components << ComponentInfo.new(:shadow_node, name.first, class_name)
|
|
173
|
+
end
|
|
174
|
+
body.scan(/LYNX_LAZY_REGISTER_RENDERER_HOST\(\s*@?"([^"]+)"\s*\)/) do |name|
|
|
175
|
+
components << ComponentInfo.new(:renderer_host, name.first, class_name)
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
content.scan(/@?LynxAutolinkUI\(\s*@?"([^"]+)"\s*\)\s*@implementation\s+([A-Za-z_][A-Za-z0-9_]*)/) do
|
|
179
|
+
|name, class_name|
|
|
180
|
+
components << ComponentInfo.new(:ui, name, class_name)
|
|
181
|
+
end
|
|
182
|
+
content.scan(/@?LynxAutolinkNativeModule\(\s*@?"([^"]+)"\s*\)\s*@(implementation|interface)\s+([A-Za-z_][A-Za-z0-9_]*)/) do
|
|
183
|
+
|name, _declaration, class_name|
|
|
184
|
+
components << ComponentInfo.new(:native_module, name, class_name)
|
|
185
|
+
end
|
|
186
|
+
content.scan(/@?LynxAutolinkService\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/) do
|
|
187
|
+
|class_name, protocol_name|
|
|
188
|
+
components << ComponentInfo.new(:service, protocol_name, class_name)
|
|
189
|
+
end
|
|
190
|
+
content.scan(/@?LynxServiceRegister\(\s*([A-Za-z_][A-Za-z0-9_]*)\s*,\s*([A-Za-z_][A-Za-z0-9_]*)\s*\)/) do
|
|
191
|
+
|class_name, protocol_name|
|
|
192
|
+
components << ComponentInfo.new(:service, protocol_name, class_name)
|
|
193
|
+
end
|
|
194
|
+
components.uniq { |component| [component.kind, component.name, component.class_name] }
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def header_source
|
|
198
|
+
<<~HEADER
|
|
199
|
+
// Generated by cocoapods-lynx-extension. Do not edit.
|
|
200
|
+
#import <Foundation/Foundation.h>
|
|
201
|
+
|
|
202
|
+
@class LynxConfig;
|
|
203
|
+
|
|
204
|
+
@interface #{REGISTRY_CLASS_NAME} : NSObject
|
|
205
|
+
- (void)setup:(LynxConfig *)config;
|
|
206
|
+
@end
|
|
207
|
+
HEADER
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def implementation_source(components)
|
|
211
|
+
lines = components.map do |component|
|
|
212
|
+
class_expr = "NSClassFromString(@\"#{component.class_name}\")"
|
|
213
|
+
case component.kind
|
|
214
|
+
when :ui
|
|
215
|
+
" if (#{class_expr}) { [config registerUI:#{class_expr} withName:@\"#{component.name}\"]; }"
|
|
216
|
+
when :shadow_node
|
|
217
|
+
" if (#{class_expr}) { [config registerShadowNode:#{class_expr} withName:@\"#{component.name}\"]; }"
|
|
218
|
+
when :renderer_host
|
|
219
|
+
" if (#{class_expr}) { [config.componentRegistry registerRendererHost:#{class_expr} withName:@\"#{component.name}\"]; }"
|
|
220
|
+
when :native_module
|
|
221
|
+
" if (#{class_expr}) { [config registerModule:#{class_expr} withName:@\"#{component.name}\"]; }"
|
|
222
|
+
end
|
|
223
|
+
end.compact.join("\n")
|
|
224
|
+
|
|
225
|
+
<<~IMPL
|
|
226
|
+
// Generated by cocoapods-lynx-extension. Do not edit.
|
|
227
|
+
#import "#{REGISTRY_CLASS_NAME}.h"
|
|
228
|
+
#import <Lynx/LynxConfig.h>
|
|
229
|
+
|
|
230
|
+
@implementation #{REGISTRY_CLASS_NAME}
|
|
231
|
+
- (void)setup:(LynxConfig *)config {
|
|
232
|
+
if (config == nil) {
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
#{lines}
|
|
236
|
+
}
|
|
237
|
+
@end
|
|
238
|
+
IMPL
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def podspec_source
|
|
242
|
+
<<~PODSPEC
|
|
243
|
+
Pod::Spec.new do |s|
|
|
244
|
+
s.name = 'LynxExtensionRegistry'
|
|
245
|
+
s.version = '0.1.0'
|
|
246
|
+
s.summary = 'Generated Lynx extension registry.'
|
|
247
|
+
s.homepage = 'https://github.com/lynx-family/lynx'
|
|
248
|
+
s.license = 'Apache-2.0'
|
|
249
|
+
s.author = 'Lynx'
|
|
250
|
+
s.source = { :path => '.' }
|
|
251
|
+
s.source_files = '#{REGISTRY_CLASS_NAME}.{h,m}'
|
|
252
|
+
s.dependency 'Lynx'
|
|
253
|
+
s.requires_arc = true
|
|
254
|
+
end
|
|
255
|
+
PODSPEC
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: cocoapods-lynx-extension
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Lynx Authors
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-05-18 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: cocoapods
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 1.11.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 1.11.0
|
|
27
|
+
description:
|
|
28
|
+
email:
|
|
29
|
+
executables: []
|
|
30
|
+
extensions: []
|
|
31
|
+
extra_rdoc_files: []
|
|
32
|
+
files:
|
|
33
|
+
- lib/cocoapods-lynx-extension.rb
|
|
34
|
+
- lib/cocoapods_plugin.rb
|
|
35
|
+
- lib/lynx/extension/autolink.rb
|
|
36
|
+
homepage:
|
|
37
|
+
licenses:
|
|
38
|
+
- Apache-2.0
|
|
39
|
+
metadata: {}
|
|
40
|
+
post_install_message:
|
|
41
|
+
rdoc_options: []
|
|
42
|
+
require_paths:
|
|
43
|
+
- lib
|
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
45
|
+
requirements:
|
|
46
|
+
- - ">="
|
|
47
|
+
- !ruby/object:Gem::Version
|
|
48
|
+
version: '0'
|
|
49
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
54
|
+
requirements: []
|
|
55
|
+
rubygems_version: 3.3.27
|
|
56
|
+
signing_key:
|
|
57
|
+
specification_version: 4
|
|
58
|
+
summary: CocoaPods autolink plugin for Lynx native extensions.
|
|
59
|
+
test_files: []
|