-
Notifications
You must be signed in to change notification settings - Fork 22
feat: return installed app metadata from apps install #274
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,135 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package utils | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "archive/zip" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "io" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "os" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "path/filepath" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "strconv" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "strings" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "github.com/shogo82148/androidbinary/apk" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "howett.net/plist" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // AppMetadata holds identity and version information parsed from an app file | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // (.apk, .ipa, .zip, or .app). Field names are unified across platforms: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // PackageName is the Android package / iOS CFBundleIdentifier, Version is the | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Android versionName / iOS CFBundleShortVersionString (marketing version), and | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // VersionCode is the Android versionCode / iOS CFBundleVersion (build number). | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type AppMetadata struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PackageName string `json:"packageName"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Version string `json:"version,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VersionCode string `json:"versionCode,omitempty"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // ParseAppMetadata reads identity and version metadata from an app file, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // dispatching by extension. It parses the file locally and does not touch any | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // device. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func ParseAppMetadata(path string) (*AppMetadata, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lower := strings.ToLower(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| switch { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case strings.HasSuffix(lower, ".apk"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return parseApkMetadata(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case strings.HasSuffix(lower, ".ipa"), strings.HasSuffix(lower, ".zip"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return parseIpaMetadata(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case strings.HasSuffix(lower, ".app"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return parseAppDirMetadata(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| default: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("unsupported app file type: %s", path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func parseApkMetadata(path string) (*AppMetadata, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pkg, err := apk.OpenFile(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to open apk: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer func() { _ = pkg.Close() }() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| meta := &AppMetadata{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PackageName: pkg.PackageName(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| manifest := pkg.Manifest() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if versionName, err := manifest.VersionName.String(); err == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| meta.Version = versionName | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if versionCode, err := manifest.VersionCode.Int32(); err == nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| meta.VersionCode = strconv.FormatInt(int64(versionCode), 10) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return meta, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+44
to
+64
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Description: Verify androidbinary API compatibility
# Check the API exported by the library at v1.0.5
echo "=== Fetching androidbinary v1.0.5 API ==="
rg -A 3 'func.*PackageName|func.*Manifest|func.*VersionName|func.*VersionCode' \
--iglob '**/androidbinary/**/*.go' 2>/dev/null || \
echo "Note: Library not in current workspace; manual verification needed"
# Search Go package documentation
echo -e "\n=== Manual verification needed ==="
echo "Please verify the following at https://pkg.go.dev/github.com/shogo82148/androidbinary@v1.0.5/apk:"
echo " 1. apk.OpenFile(path) returns *Pkg with PackageName() method"
echo " 2. Pkg.Manifest() returns manifest with VersionName and VersionCode fields"
echo " 3. VersionName has String() method, VersionCode has Int32() method"Repository: mobile-next/mobilecli Length of output: 517 Validate non-empty APK package name
🛡️ Suggested validation meta := &AppMetadata{
PackageName: pkg.PackageName(),
}
+ if meta.PackageName == "" {
+ return nil, fmt.Errorf("APK manifest missing package name")
+ }
manifest := pkg.Manifest()📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // parseIpaMetadata reads the top-level app's Info.plist from an .ipa or .zip | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // archive. It works for both .ipa (Payload/Foo.app/Info.plist) and simulator | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // .zip (Foo.app/Info.plist) layouts. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func parseIpaMetadata(path string) (*AppMetadata, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| reader, err := zip.OpenReader(path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to open archive: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| defer func() { _ = reader.Close() }() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for _, file := range reader.File { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !isAppInfoPlist(file.Name) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rc, err := file.Open() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to open Info.plist: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data, err := io.ReadAll(rc) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _ = rc.Close() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to read Info.plist: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return decodeInfoPlist(data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("no app Info.plist found in %s", path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func parseAppDirMetadata(path string) (*AppMetadata, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| data, err := os.ReadFile(filepath.Join(path, "Info.plist")) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to read Info.plist: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return decodeInfoPlist(data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // isAppInfoPlist reports whether a zip entry name is the top-level app bundle's | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Info.plist, e.g. "Payload/Foo.app/Info.plist" or "Foo.app/Info.plist". A | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // nested bundle's plist (frameworks, plugins) has more path segments and is | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // excluded. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func isAppInfoPlist(name string) bool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = strings.TrimPrefix(name, "Payload/") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| parts := strings.Split(name, "/") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return len(parts) == 2 && strings.HasSuffix(parts[0], ".app") && parts[1] == "Info.plist" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func decodeInfoPlist(data []byte) (*AppMetadata, error) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // infoPlist mirrors the keys we extract from an iOS Info.plist. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type infoPlist struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CFBundleIdentifier string `plist:"CFBundleIdentifier"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CFBundleShortVersionString string `plist:"CFBundleShortVersionString"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CFBundleVersion string `plist:"CFBundleVersion"` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var info infoPlist | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if _, err := plist.Unmarshal(data, &info); err != nil { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return nil, fmt.Errorf("failed to parse Info.plist: %w", err) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return &AppMetadata{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PackageName: info.CFBundleIdentifier, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Version: info.CFBundleShortVersionString, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| VersionCode: info.CFBundleVersion, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }, nil | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,117 @@ | ||||||||||||||||||
| package utils | ||||||||||||||||||
|
|
||||||||||||||||||
| import ( | ||||||||||||||||||
| "archive/zip" | ||||||||||||||||||
| "os" | ||||||||||||||||||
| "path/filepath" | ||||||||||||||||||
| "testing" | ||||||||||||||||||
|
|
||||||||||||||||||
| "github.com/stretchr/testify/assert" | ||||||||||||||||||
| "github.com/stretchr/testify/require" | ||||||||||||||||||
| ) | ||||||||||||||||||
|
|
||||||||||||||||||
| // sampleInfoPlist is a minimal XML Info.plist; howett.net/plist parses both XML | ||||||||||||||||||
| // and binary plists, so XML keeps the test fixtures readable. | ||||||||||||||||||
| const sampleInfoPlist = `<?xml version="1.0" encoding="UTF-8"?> | ||||||||||||||||||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||||||||||||||||||
| <plist version="1.0"> | ||||||||||||||||||
| <dict> | ||||||||||||||||||
| <key>CFBundleIdentifier</key> | ||||||||||||||||||
| <string>com.mobilenext.playground</string> | ||||||||||||||||||
| <key>CFBundleShortVersionString</key> | ||||||||||||||||||
| <string>1.4.0</string> | ||||||||||||||||||
| <key>CFBundleVersion</key> | ||||||||||||||||||
| <string>42</string> | ||||||||||||||||||
| </dict> | ||||||||||||||||||
| </plist>` | ||||||||||||||||||
|
|
||||||||||||||||||
| // writeZip writes a zip file with the given entries to a temp file and returns its path. | ||||||||||||||||||
| func writeZip(t *testing.T, entries map[string]string) string { | ||||||||||||||||||
| t.Helper() | ||||||||||||||||||
| path := filepath.Join(t.TempDir(), "app.zip") | ||||||||||||||||||
|
|
||||||||||||||||||
| f, err := os.Create(path) | ||||||||||||||||||
| require.NoError(t, err) | ||||||||||||||||||
|
|
||||||||||||||||||
| w := zip.NewWriter(f) | ||||||||||||||||||
| for name, content := range entries { | ||||||||||||||||||
| entry, err := w.Create(name) | ||||||||||||||||||
| require.NoError(t, err) | ||||||||||||||||||
| _, err = entry.Write([]byte(content)) | ||||||||||||||||||
| require.NoError(t, err) | ||||||||||||||||||
| } | ||||||||||||||||||
| require.NoError(t, w.Close()) | ||||||||||||||||||
| require.NoError(t, f.Close()) | ||||||||||||||||||
|
|
||||||||||||||||||
| return path | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestParseAppMetadataFromIpaLayout(t *testing.T) { | ||||||||||||||||||
| ipa := writeZip(t, map[string]string{ | ||||||||||||||||||
| "Payload/Playground.app/Info.plist": sampleInfoPlist, | ||||||||||||||||||
| }) | ||||||||||||||||||
| // rename to .ipa so extension dispatch picks the iOS path | ||||||||||||||||||
| ipaPath := ipa + ".ipa" | ||||||||||||||||||
| require.NoError(t, os.Rename(ipa, ipaPath)) | ||||||||||||||||||
|
|
||||||||||||||||||
| meta, err := ParseAppMetadata(ipaPath) | ||||||||||||||||||
| require.NoError(t, err) | ||||||||||||||||||
| assert.Equal(t, "com.mobilenext.playground", meta.PackageName) | ||||||||||||||||||
| assert.Equal(t, "1.4.0", meta.Version) | ||||||||||||||||||
| assert.Equal(t, "42", meta.VersionCode) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestParseAppMetadataFromSimulatorZipLayout(t *testing.T) { | ||||||||||||||||||
| // simulator .zip has the .app at the archive root, not under Payload/ | ||||||||||||||||||
| zipPath := writeZip(t, map[string]string{ | ||||||||||||||||||
| "Playground.app/Info.plist": sampleInfoPlist, | ||||||||||||||||||
| }) | ||||||||||||||||||
|
|
||||||||||||||||||
| meta, err := ParseAppMetadata(zipPath) | ||||||||||||||||||
| require.NoError(t, err) | ||||||||||||||||||
| assert.Equal(t, "com.mobilenext.playground", meta.PackageName) | ||||||||||||||||||
| assert.Equal(t, "1.4.0", meta.Version) | ||||||||||||||||||
| assert.Equal(t, "42", meta.VersionCode) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestParseAppMetadataIgnoresNestedBundlePlists(t *testing.T) { | ||||||||||||||||||
| // a framework's Info.plist must not shadow the top-level app's | ||||||||||||||||||
| ipa := writeZip(t, map[string]string{ | ||||||||||||||||||
| "Payload/Playground.app/Frameworks/Other.framework/Info.plist": `<plist><dict><key>CFBundleIdentifier</key><string>com.other.framework</string></dict></plist>`, | ||||||||||||||||||
| "Payload/Playground.app/Info.plist": sampleInfoPlist, | ||||||||||||||||||
| }) | ||||||||||||||||||
| ipaPath := ipa + ".ipa" | ||||||||||||||||||
| require.NoError(t, os.Rename(ipa, ipaPath)) | ||||||||||||||||||
|
|
||||||||||||||||||
| meta, err := ParseAppMetadata(ipaPath) | ||||||||||||||||||
| require.NoError(t, err) | ||||||||||||||||||
| assert.Equal(t, "com.mobilenext.playground", meta.PackageName) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestParseAppMetadataFromAppDirectory(t *testing.T) { | ||||||||||||||||||
| appDir := filepath.Join(t.TempDir(), "Playground.app") | ||||||||||||||||||
| require.NoError(t, os.Mkdir(appDir, 0o750)) | ||||||||||||||||||
| require.NoError(t, os.WriteFile(filepath.Join(appDir, "Info.plist"), []byte(sampleInfoPlist), 0o600)) | ||||||||||||||||||
|
|
||||||||||||||||||
| meta, err := ParseAppMetadata(appDir) | ||||||||||||||||||
| require.NoError(t, err) | ||||||||||||||||||
| assert.Equal(t, "com.mobilenext.playground", meta.PackageName) | ||||||||||||||||||
| assert.Equal(t, "1.4.0", meta.Version) | ||||||||||||||||||
| assert.Equal(t, "42", meta.VersionCode) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestParseAppMetadataFromApk(t *testing.T) { | ||||||||||||||||||
| // sample.apk is a stripped-down fixture: just the binary AndroidManifest.xml | ||||||||||||||||||
| // plus an empty resources.arsc (the parser requires the latter to exist). | ||||||||||||||||||
| // package/version are literal manifest attributes, so no resource table is needed. | ||||||||||||||||||
| meta, err := ParseAppMetadata("testdata/sample.apk") | ||||||||||||||||||
| require.NoError(t, err) | ||||||||||||||||||
| assert.Equal(t, "com.example.helloworld", meta.PackageName) | ||||||||||||||||||
| assert.Equal(t, "1.0", meta.Version) | ||||||||||||||||||
| assert.Equal(t, "1", meta.VersionCode) | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| func TestParseAppMetadataRejectsUnknownExtension(t *testing.T) { | ||||||||||||||||||
| _, err := ParseAppMetadata("/tmp/whatever.txt") | ||||||||||||||||||
| assert.Error(t, err) | ||||||||||||||||||
| } | ||||||||||||||||||
|
Comment on lines
+114
to
+117
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use a cross-platform path for the test. The hardcoded 🔧 Proposed fix func TestParseAppMetadataRejectsUnknownExtension(t *testing.T) {
- _, err := ParseAppMetadata("/tmp/whatever.txt")
+ _, err := ParseAppMetadata("whatever.txt")
assert.Error(t, err)
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.