@bipopush/cli 0.2.0 → 0.3.0-beta.12

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/dist/project.js DELETED
@@ -1,177 +0,0 @@
1
- import { execFileSync } from "node:child_process";
2
- import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
3
- import path from "node:path";
4
- export const PROJECT_FILE = "bipopush.json";
5
- function readJson(file) {
6
- try {
7
- return JSON.parse(readFileSync(file, "utf8"));
8
- }
9
- catch {
10
- return null;
11
- }
12
- }
13
- export function loadProjectConfig(root) {
14
- return readJson(path.join(root, PROJECT_FILE));
15
- }
16
- export function saveProjectConfig(root, config) {
17
- writeFileSync(path.join(root, PROJECT_FILE), `${JSON.stringify(config, null, 2)}\n`);
18
- }
19
- function pbxSetting(pbx, key) {
20
- const values = [...pbx.matchAll(new RegExp(`${key} = "?([^";]+)"?;`, "g"))].map((m) => m[1].trim());
21
- return values.find((v) => !v.includes("$(")) ?? values[0] ?? null;
22
- }
23
- function plistValue(file, keyPath) {
24
- try {
25
- return execFileSync("plutil", ["-extract", keyPath, "raw", "-o", "-", file], { stdio: ["ignore", "pipe", "ignore"] })
26
- .toString()
27
- .trim();
28
- }
29
- catch {
30
- return null;
31
- }
32
- }
33
- function readIos(root) {
34
- const iosDir = path.join(root, "ios");
35
- const empty = { info: { id: null, version: null, scheme: null }, plist: null };
36
- if (!existsSync(iosDir))
37
- return empty;
38
- const proj = readdirSync(iosDir).find((f) => f.endsWith(".xcodeproj"));
39
- if (!proj)
40
- return empty;
41
- const pbx = readFileSync(path.join(iosDir, proj, "project.pbxproj"), "utf8");
42
- const productName = proj.replace(/\.xcodeproj$/, "");
43
- let id = pbxSetting(pbx, "PRODUCT_BUNDLE_IDENTIFIER");
44
- if (id?.includes("$(PRODUCT_NAME"))
45
- id = id.replace(/\$\(PRODUCT_NAME[^)]*\)/, productName.replace(/[^A-Za-z0-9-]/g, "-"));
46
- const plistRel = pbxSetting(pbx, "INFOPLIST_FILE");
47
- const plist = plistRel ? path.join(iosDir, plistRel.replace("$(SRCROOT)/", "")) : null;
48
- const scheme = plist ? plistValue(plist, "CFBundleURLTypes.0.CFBundleURLSchemes.0") : null;
49
- // The version the app reports at runtime is CFBundleShortVersionString: a literal in Expo projects,
50
- // "$(MARKETING_VERSION)" in bare React Native projects.
51
- const plistVersion = plist ? plistValue(plist, "CFBundleShortVersionString") : null;
52
- const version = plistVersion && !plistVersion.includes("$(") ? plistVersion : pbxSetting(pbx, "MARKETING_VERSION");
53
- return { info: { id, version, scheme }, plist: plist && existsSync(plist) ? plist : null };
54
- }
55
- function androidManifestFile(root) {
56
- const file = path.join(root, "android", "app", "src", "main", "AndroidManifest.xml");
57
- return existsSync(file) ? file : null;
58
- }
59
- function readAndroid(root) {
60
- const gradleFile = ["build.gradle", "build.gradle.kts"].map((f) => path.join(root, "android", "app", f)).find(existsSync);
61
- if (!gradleFile)
62
- return { id: null, version: null, scheme: null };
63
- const gradle = readFileSync(gradleFile, "utf8");
64
- const id = gradle.match(/applicationId\s*=?\s*["']([^"']+)["']/)?.[1] ?? null;
65
- const version = gradle.match(/versionName\s*=?\s*["']([^"']+)["']/)?.[1] ?? null;
66
- const manifestFile = path.join(root, "android", "app", "src", "main", "AndroidManifest.xml");
67
- const manifest = existsSync(manifestFile) ? readFileSync(manifestFile, "utf8") : "";
68
- const scheme = manifest.match(/<data[^>]*android:scheme="([^"]+)"/)?.[1] ?? null;
69
- return { id, version, scheme };
70
- }
71
- /** Detects Expo vs bare React Native and reads native identifiers, versions and URL scheme. */
72
- export function detectProject(root) {
73
- const pkg = readJson(path.join(root, "package.json"));
74
- if (!pkg)
75
- throw new Error(`No package.json in ${root}`);
76
- if (!pkg.dependencies?.["react-native"])
77
- throw new Error("This is not a React Native project (react-native is not a dependency)");
78
- const appJson = readJson(path.join(root, "app.json"));
79
- const isExpo = Boolean(pkg.dependencies?.expo && appJson?.expo);
80
- const ios = readIos(root);
81
- const android = readAndroid(root);
82
- const entryFile = pkg.main && existsSync(path.join(root, pkg.main)) ? pkg.main : "index.js";
83
- if (isExpo && !ios.info.id) {
84
- const expo = appJson.expo;
85
- const scheme = Array.isArray(expo.scheme) ? expo.scheme[0] : (expo.scheme ?? null);
86
- return {
87
- root,
88
- type: "expo",
89
- name: expo.name ?? pkg.name ?? "app",
90
- entryFile,
91
- ios: { id: expo.ios?.bundleIdentifier ?? null, version: expo.ios?.version ?? expo.version ?? null, scheme },
92
- android: { id: expo.android?.package ?? null, version: expo.version ?? null, scheme },
93
- iosInfoPlist: null,
94
- androidManifest: null,
95
- };
96
- }
97
- return {
98
- root,
99
- type: isExpo ? "expo" : "react-native",
100
- name: appJson?.expo?.name ?? pkg.name ?? "app",
101
- entryFile,
102
- ios: ios.info,
103
- android,
104
- iosInfoPlist: ios.plist,
105
- androidManifest: isExpo ? null : androidManifestFile(root),
106
- };
107
- }
108
- /**
109
- * Writes BipoPush keys as <meta-data> of <application> in AndroidManifest.xml (the Android module reads
110
- * them like iOS reads Info.plist). Keys passed as null are removed.
111
- */
112
- export function writeAndroidManifest(file, values) {
113
- let xml = readFileSync(file, "utf8");
114
- for (const [key, value] of Object.entries(values)) {
115
- const existing = new RegExp(`\\s*<meta-data\\s+android:name="${key}"[^>]*/>`, "g");
116
- xml = xml.replace(existing, "");
117
- if (value === null)
118
- continue;
119
- xml = xml.replace(/(<application\b[^>]*>)/, (tag) => `${tag}\n <meta-data android:name="${key}" android:value="${value}" />`);
120
- }
121
- writeFileSync(file, xml);
122
- }
123
- /** Adds a VIEW intent-filter for `<scheme>://` deeplinks to the launcher activity. */
124
- export function addAndroidUrlScheme(file, scheme) {
125
- const xml = readFileSync(file, "utf8");
126
- const filter = `
127
- <intent-filter>
128
- <action android:name="android.intent.action.VIEW" />
129
- <category android:name="android.intent.category.DEFAULT" />
130
- <category android:name="android.intent.category.BROWSABLE" />
131
- <data android:scheme="${scheme}" />
132
- </intent-filter>`;
133
- const launcher = /(<category android:name="android\.intent\.category\.LAUNCHER"\s*\/>\s*<\/intent-filter>)/;
134
- if (!launcher.test(xml))
135
- throw new Error(`Could not find the launcher activity in ${file}; add the ${scheme}:// intent-filter by hand.`);
136
- writeFileSync(file, xml.replace(launcher, (launcherFilter) => `${launcherFilter}${filter}`));
137
- }
138
- /** Writes BipoPush keys into the iOS Info.plist so the native module needs no JS configuration. Keys passed as null are removed. */
139
- export function writeIosInfoPlist(plist, values) {
140
- for (const [key, value] of Object.entries(values)) {
141
- if (value !== null)
142
- execFileSync("plutil", ["-replace", key, "-string", value, plist]);
143
- else if (plistValue(plist, key) !== null)
144
- execFileSync("plutil", ["-remove", key, plist]);
145
- }
146
- }
147
- /** Registers a URL scheme (CFBundleURLTypes) so `<scheme>://` deeplinks open the app. */
148
- export function addIosUrlScheme(plist, scheme) {
149
- const run = (cmd) => execFileSync("/usr/libexec/PlistBuddy", ["-c", cmd, plist], { stdio: ["ignore", "ignore", "ignore"] });
150
- try {
151
- run("Print :CFBundleURLTypes");
152
- }
153
- catch {
154
- run("Add :CFBundleURLTypes array");
155
- }
156
- run("Add :CFBundleURLTypes:0 dict");
157
- run("Add :CFBundleURLTypes:0:CFBundleURLSchemes array");
158
- run(`Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string ${scheme}`);
159
- }
160
- /**
161
- * Expo (continuous native generation): configure the BipoPush config plugin in app.json instead of
162
- * editing generated native files. Also sets the URL scheme when one is given and none exists.
163
- */
164
- export function writeExpoConfig(root, plugin, scheme) {
165
- const file = path.join(root, "app.json");
166
- const json = readJson(file);
167
- if (!json?.expo)
168
- throw new Error("app.json has no expo section");
169
- const expo = json.expo;
170
- const plugins = Array.isArray(expo.plugins) ? expo.plugins : [];
171
- const name = "@bipopush/react-native";
172
- const rest = plugins.filter((p) => (Array.isArray(p) ? p[0] : p) !== name);
173
- expo.plugins = [...rest, [name, plugin]];
174
- if (scheme && !expo.scheme)
175
- expo.scheme = scheme;
176
- writeFileSync(file, `${JSON.stringify(json, null, 2)}\n`);
177
- }