master
js 92 lines 3.73 KB
Raw
1
2 var promise = require('promise');
3
4 function apps() {
5 var ret = new promise(function (res, rej) { this._res = res; this._rej = rej; });
6
7 function runSh(script) {
8 try {
9 var child = require('child_process').execFile('/bin/sh', ['sh']);
10 child.stdout.str = '';
11 child.stdout.on('data', function (d) { child.stdout.str += d; });
12 child.stdin.write(script + '\nexit\n');
13 child.waitExit();
14 return child.stdout.str.trim();
15 } catch (ex) { return ''; }
16 }
17
18 // Single shell process: PlistBuddy handles both XML and binary plists.
19 // Outputs tab-separated: name, version, publisher, date, scope — one app per line.
20 var script = [
21 'PB=/usr/libexec/PlistBuddy',
22 'process_app() {',
23 ' app="$1" scope="$2"',
24 ' plist="$app/Contents/Info.plist"',
25 ' [ -f "$plist" ] || return',
26 ' name=$(basename "$app" .app)',
27 ' version=$("$PB" -c "Print :CFBundleShortVersionString" "$plist" 2>/dev/null)',
28 ' publisher=$("$PB" -c "Print :NSHumanReadableCopyright" "$plist" 2>/dev/null)',
29 ' if [ -z "$publisher" ]; then',
30 ' bid=$("$PB" -c "Print :CFBundleIdentifier" "$plist" 2>/dev/null)',
31 ' publisher=$(echo "$bid" | awk -F. \'{if(NF>=2){s=$2; print toupper(substr(s,1,1)) substr(s,2)}}\')',
32 ' fi',
33 ' date=$(stat -f "%Sm" -t "%Y-%m-%d" "$app" 2>/dev/null)',
34 ' arch=""',
35 ' exe=$("$PB" -c "Print :CFBundleExecutable" "$plist" 2>/dev/null)',
36 ' if [ -n "$exe" ]; then',
37 ' lipoout=$(lipo -info "$app/Contents/MacOS/$exe" 2>/dev/null)',
38 ' if echo "$lipoout" | grep -q "x86_64" && echo "$lipoout" | grep -q "arm64"; then',
39 ' arch="universal"',
40 ' elif echo "$lipoout" | grep -q "arm64"; then',
41 ' arch="arm64"',
42 ' elif echo "$lipoout" | grep -q "x86_64"; then',
43 ' arch="x86_64"',
44 ' fi',
45 ' fi',
46 ' printf "%s\\t%s\\t%s\\t%s\\t%s\\t%s\\n" \\',
47 ' "$(printf "%s" "$name" | tr "\\t\\n" " ")" \\',
48 ' "$(printf "%s" "$version" | tr "\\t\\n" " ")" \\',
49 ' "$(printf "%s" "$publisher" | tr "\\t\\n" " ")" \\',
50 ' "$date" "$arch" "$scope"',
51 '}',
52 'for sysdir in /Applications /System/Applications /System/Applications/Utilities; do',
53 ' [ -d "$sysdir" ] || continue',
54 ' find "$sysdir" -maxdepth 1 -name "*.app" -type d 2>/dev/null | while IFS= read -r app; do',
55 ' process_app "$app" system',
56 ' done',
57 'done',
58 'for userdir in /Users/*/; do',
59 ' user=$(basename "$userdir")',
60 ' [ "$user" = Shared ] && continue',
61 ' case "$user" in .*) continue;; esac',
62 ' appsdir="${userdir}Applications"',
63 ' [ -d "$appsdir" ] || continue',
64 ' find "$appsdir" -maxdepth 1 -name "*.app" -type d 2>/dev/null | while IFS= read -r app; do',
65 ' process_app "$app" user',
66 ' done',
67 'done'
68 ].join('\n');
69
70 try {
71 var out = runSh(script);
72 var results = [];
73 if (out) {
74 out.split('\n').forEach(function (line) {
75 var p = line.split('\t');
76 if (p.length < 6 || !p[0]) return;
77 results.push({ name: p[0], version: p[1] || '', publisher: p[2] || '', date: p[3] || '', arch: p[4] || '', location: p[5] || 'system' });
78 });
79 }
80 ret._res(results);
81 } catch (e) {
82 ret._rej(e);
83 }
84 return (ret);
85 }
86
87 if (process.platform == 'darwin') {
88 module.exports = { apps: apps };
89 } else {
90 var not_supported = function () { throw (process.platform + ' not supported'); };
91 module.exports = { apps: not_supported };
92 }