@incode-sdks/incode-integrate 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.
package/executor.js ADDED
@@ -0,0 +1,301 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ function readText(root, file) {
5
+ return fs.readFileSync(path.join(root, file), "utf8");
6
+ }
7
+
8
+ function writeText(root, file, content) {
9
+ const filePath = path.join(root, file);
10
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
11
+ fs.writeFileSync(filePath, content);
12
+ }
13
+
14
+ function formatJson(value) {
15
+ return `${JSON.stringify(value, null, 2)}\n`;
16
+ }
17
+
18
+ function escapeRegExp(value) {
19
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20
+ }
21
+
22
+ function upsertJsonDependency(content, name, version) {
23
+ const packageJson = JSON.parse(content);
24
+ packageJson.dependencies = packageJson.dependencies || {};
25
+
26
+ if (packageJson.dependencies[name] === version) {
27
+ return content;
28
+ }
29
+
30
+ packageJson.dependencies[name] = version;
31
+ return formatJson(packageJson);
32
+ }
33
+
34
+ function ensurePodSources(content, sources) {
35
+ const desiredIncodeSource = sources.find(source =>
36
+ source.includes("IncdDistributionPodspecs.git"),
37
+ );
38
+ let next = content
39
+ .split("\n")
40
+ .filter(line => {
41
+ const isIncodePodspecSource = line.includes("IncdDistributionPodspecs.git");
42
+ return !isIncodePodspecSource || line.trim() === desiredIncodeSource;
43
+ })
44
+ .join("\n");
45
+
46
+ for (const source of sources.slice().reverse()) {
47
+ if (!next.includes(source)) {
48
+ next = `${source}\n${next}`;
49
+ }
50
+ }
51
+
52
+ return next;
53
+ }
54
+
55
+ function ensurePlistStrings(content, entries) {
56
+ let next = content;
57
+
58
+ for (const [key, value] of Object.entries(entries)) {
59
+ const pattern = new RegExp(
60
+ `<key>${escapeRegExp(key)}</key>\\s*<string>[\\s\\S]*?</string>`,
61
+ "m",
62
+ );
63
+ const replacement = `<key>${key}</key>\n\t<string>${value}</string>`;
64
+
65
+ if (pattern.test(next)) {
66
+ next = next.replace(pattern, replacement);
67
+ continue;
68
+ }
69
+
70
+ next = next.replace(/\n<\/dict>/, `\n\t${replacement}\n</dict>`);
71
+ }
72
+
73
+ return next;
74
+ }
75
+
76
+ function ensureAndroidPermissions(content, permissions) {
77
+ let next = content;
78
+
79
+ for (const permission of permissions) {
80
+ const line = `<uses-permission android:name="${permission}" />`;
81
+ if (!next.includes(line)) {
82
+ next = next.replace(/\n\s*<application/, `\n ${line}\n\n <application`);
83
+ }
84
+ }
85
+
86
+ return next;
87
+ }
88
+
89
+ function createAndroidRepositoryBlock(credentialsMode) {
90
+ const credentialBlocks = {
91
+ environment: ` credentials {
92
+ username = System.getenv("GITHUB_USERNAME")
93
+ password = System.getenv("GITHUB_TOKEN")
94
+ }`,
95
+ };
96
+
97
+ return `
98
+
99
+ // Incode SDK repositories start
100
+ allprojects {
101
+ repositories {
102
+ maven { url "https://jitpack.io" }
103
+ maven {
104
+ url = uri("https://maven.pkg.github.com/Incode-Technologies-Example-Repos/android-omni-packages")
105
+ ${credentialBlocks[credentialsMode] || credentialBlocks.environment}
106
+ }
107
+ }
108
+ }
109
+ // Incode SDK repositories end
110
+ `;
111
+ }
112
+
113
+ function replaceEnclosingAllProjectsBlock(content, replacement) {
114
+ const repoIndex = content.indexOf("android-omni-packages");
115
+ if (repoIndex < 0) {
116
+ return `${content.trimEnd()}${replacement}`;
117
+ }
118
+
119
+ const start = content.lastIndexOf("allprojects", repoIndex);
120
+ if (start < 0) {
121
+ return content;
122
+ }
123
+
124
+ const openBrace = content.indexOf("{", start);
125
+ if (openBrace < 0) {
126
+ return content;
127
+ }
128
+
129
+ let depth = 0;
130
+ for (let index = openBrace; index < content.length; index += 1) {
131
+ if (content[index] === "{") {
132
+ depth += 1;
133
+ }
134
+
135
+ if (content[index] === "}") {
136
+ depth -= 1;
137
+ }
138
+
139
+ if (depth === 0) {
140
+ const before = content.slice(0, start).trimEnd();
141
+ const after = content.slice(index + 1).replace(
142
+ /^\s*\/\/ Incode SDK repositories end\s*/,
143
+ "",
144
+ );
145
+ return `${before}${replacement}${after}`;
146
+ }
147
+ }
148
+
149
+ return content;
150
+ }
151
+
152
+ function ensureAndroidRepositories(content, credentialsMode) {
153
+ const block = createAndroidRepositoryBlock(credentialsMode);
154
+ const markedPattern =
155
+ /\n*\/\/ Incode SDK repositories start[\s\S]*?\/\/ Incode SDK repositories end\n*/;
156
+
157
+ if (markedPattern.test(content)) {
158
+ return content.replace(markedPattern, block);
159
+ }
160
+
161
+ return replaceEnclosingAllProjectsBlock(content, block);
162
+ }
163
+
164
+ function ensureAndroidMultidex(content) {
165
+ let next = content;
166
+
167
+ if (!next.includes("multiDexEnabled true")) {
168
+ next = next.replace(
169
+ /(versionName\s+["'][^"']+["']\s*)/,
170
+ `$1 multiDexEnabled true\n`,
171
+ );
172
+ }
173
+
174
+ if (!next.includes("androidx.multidex:multidex") && !next.includes("com.android.support:multidex")) {
175
+ next = next.replace(
176
+ /^(\s*implementation\(["']com\.facebook\.react:react-android["']\)\s*)$/m,
177
+ `$1\n implementation("androidx.multidex:multidex:2.0.1")`,
178
+ );
179
+ }
180
+
181
+ return next;
182
+ }
183
+
184
+ function ensureAndroidDependencies(content, dependencies) {
185
+ let next = content;
186
+
187
+ for (const dependency of dependencies) {
188
+ if (next.includes(dependency.notIfIncludes || dependency.notation)) {
189
+ continue;
190
+ }
191
+
192
+ const line = ` implementation("${dependency.notation}")`;
193
+ next = next.replace(
194
+ /^(\s*implementation\(["']com\.facebook\.react:react-android["']\)\s*)$/m,
195
+ `$1\n${line}`,
196
+ );
197
+ }
198
+
199
+ return next;
200
+ }
201
+
202
+ function ensureGitignoreEntries(content, entries) {
203
+ let next = content.trimEnd();
204
+
205
+ for (const entry of entries) {
206
+ const hasEntry = next
207
+ .split("\n")
208
+ .map(line => line.trim())
209
+ .includes(entry);
210
+
211
+ if (!hasEntry) {
212
+ next = `${next}\n${entry}`;
213
+ }
214
+ }
215
+
216
+ return `${next}\n`;
217
+ }
218
+
219
+ function ensureMainApplicationMultidex(content) {
220
+ if (content.includes("MultiDexApplication()")) {
221
+ return content;
222
+ }
223
+
224
+ let next = content;
225
+
226
+ if (next.includes("import android.app.Application")) {
227
+ next = next.replace("import android.app.Application", "import androidx.multidex.MultiDexApplication");
228
+ } else if (!next.includes("import androidx.multidex.MultiDexApplication")) {
229
+ next = next.replace(/\nimport /, "\nimport androidx.multidex.MultiDexApplication\nimport ");
230
+ }
231
+
232
+ next = next.replace(": Application(), ReactApplication", ": MultiDexApplication(), ReactApplication");
233
+ return next;
234
+ }
235
+
236
+ function applyTransform(root, change, transform) {
237
+ const before = readText(root, change.file);
238
+ const after = transform(before);
239
+
240
+ if (before !== after) {
241
+ writeText(root, change.file, after);
242
+ return { changed: true, file: change.file, title: change.title };
243
+ }
244
+
245
+ return { changed: false, file: change.file, title: change.title };
246
+ }
247
+
248
+ function applyChange(root, change) {
249
+ switch (change.action) {
250
+ case "json_dependency":
251
+ return applyTransform(root, change, content =>
252
+ upsertJsonDependency(content, change.name, change.version),
253
+ );
254
+ case "pod_sources":
255
+ return applyTransform(root, change, content => ensurePodSources(content, change.sources));
256
+ case "plist_strings":
257
+ return applyTransform(root, change, content => ensurePlistStrings(content, change.entries));
258
+ case "android_permissions":
259
+ return applyTransform(root, change, content =>
260
+ ensureAndroidPermissions(content, change.permissions),
261
+ );
262
+ case "android_repositories":
263
+ return applyTransform(root, change, content =>
264
+ ensureAndroidRepositories(content, change.credentialsMode),
265
+ );
266
+ case "android_multidex":
267
+ return applyTransform(root, change, ensureAndroidMultidex);
268
+ case "android_dependencies":
269
+ return applyTransform(root, change, content =>
270
+ ensureAndroidDependencies(content, change.dependencies),
271
+ );
272
+ case "gitignore_entries":
273
+ return applyTransform(root, change, content =>
274
+ ensureGitignoreEntries(content, change.entries),
275
+ );
276
+ case "main_application_multidex":
277
+ return applyTransform(root, change, ensureMainApplicationMultidex);
278
+ case "write_file": {
279
+ const before = fs.existsSync(path.join(root, change.file))
280
+ ? readText(root, change.file)
281
+ : "";
282
+
283
+ if (before === change.content) {
284
+ return { changed: false, file: change.file, title: change.title };
285
+ }
286
+
287
+ writeText(root, change.file, change.content);
288
+ return { changed: true, file: change.file, title: change.title };
289
+ }
290
+ default:
291
+ throw new Error(`Unknown change action: ${change.action}`);
292
+ }
293
+ }
294
+
295
+ function applyPlan(plan) {
296
+ return plan.changes.map(change => applyChange(plan.root, change));
297
+ }
298
+
299
+ module.exports = {
300
+ applyPlan,
301
+ };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@incode-sdks/incode-integrate",
3
+ "version": "0.1.0",
4
+ "description": "CLI integration auditor and fixer for adding the Incode React Native SDK to client apps.",
5
+ "main": "agent.js",
6
+ "bin": {
7
+ "incode-integrate": "agent.js"
8
+ },
9
+ "files": [
10
+ "agent.js",
11
+ "executor.js",
12
+ "scanner.js",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "apply": "node agent.js --apply",
17
+ "plan": "node agent.js",
18
+ "pack:dry": "npm pack --dry-run",
19
+ "test": "node --check agent.js && node --check executor.js && node --check scanner.js && node agent.js --yes"
20
+ },
21
+ "keywords": [
22
+ "incode",
23
+ "react-native",
24
+ "identity-verification",
25
+ "sdk",
26
+ "cli"
27
+ ],
28
+ "author": "Incode",
29
+ "license": "UNLICENSED",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "engines": {
34
+ "node": ">=18"
35
+ }
36
+ }
package/scanner.js ADDED
@@ -0,0 +1,120 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+
4
+ const SDK_PACKAGE = "@incode-sdks/react-native-incode-sdk";
5
+
6
+ function readTextIfExists(filePath) {
7
+ return fs.existsSync(filePath) ? fs.readFileSync(filePath, "utf8") : "";
8
+ }
9
+
10
+ function readJsonIfExists(filePath) {
11
+ const content = readTextIfExists(filePath);
12
+ return content ? JSON.parse(content) : {};
13
+ }
14
+
15
+ function findProjectRoot(start = path.resolve(__dirname, "..")) {
16
+ let current = start;
17
+
18
+ while (current !== path.dirname(current)) {
19
+ const packageJson = path.join(current, "package.json");
20
+ const iosDir = path.join(current, "ios");
21
+ const androidDir = path.join(current, "android");
22
+
23
+ if (fs.existsSync(packageJson) && fs.existsSync(iosDir) && fs.existsSync(androidDir)) {
24
+ return current;
25
+ }
26
+
27
+ current = path.dirname(current);
28
+ }
29
+
30
+ throw new Error("Could not find a React Native app root with package.json, ios, and android.");
31
+ }
32
+
33
+ function toRelative(root, filePath) {
34
+ return path.relative(root, filePath).split(path.sep).join("/");
35
+ }
36
+
37
+ function findInfoPlist(root) {
38
+ const iosDir = path.join(root, "ios");
39
+ if (!fs.existsSync(iosDir)) {
40
+ return "";
41
+ }
42
+
43
+ for (const entry of fs.readdirSync(iosDir, { withFileTypes: true })) {
44
+ if (!entry.isDirectory() || entry.name.endsWith(".xcodeproj") || entry.name.endsWith(".xcworkspace")) {
45
+ continue;
46
+ }
47
+
48
+ const plist = path.join(iosDir, entry.name, "Info.plist");
49
+ if (fs.existsSync(plist)) {
50
+ return toRelative(root, plist);
51
+ }
52
+ }
53
+
54
+ return "";
55
+ }
56
+
57
+ function findMainApplication(root) {
58
+ const sourceRoot = path.join(root, "android", "app", "src", "main");
59
+ const stack = [sourceRoot];
60
+
61
+ while (stack.length > 0) {
62
+ const current = stack.pop();
63
+ if (!current || !fs.existsSync(current)) {
64
+ continue;
65
+ }
66
+
67
+ for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
68
+ const fullPath = path.join(current, entry.name);
69
+
70
+ if (entry.isDirectory()) {
71
+ stack.push(fullPath);
72
+ continue;
73
+ }
74
+
75
+ if (entry.name === "MainApplication.kt" || entry.name === "MainApplication.java") {
76
+ return toRelative(root, fullPath);
77
+ }
78
+ }
79
+ }
80
+
81
+ return "";
82
+ }
83
+
84
+ function scanProject(root = findProjectRoot()) {
85
+ const packagePath = path.join(root, "package.json");
86
+ const packageJson = readJsonIfExists(packagePath);
87
+ const dependencies = packageJson.dependencies || {};
88
+ const devDependencies = packageJson.devDependencies || {};
89
+ const reactNativeVersion = dependencies["react-native"] || devDependencies["react-native"] || "";
90
+ const sdkDependency = dependencies[SDK_PACKAGE] || "";
91
+ const files = {
92
+ androidAppBuildGradle: "android/app/build.gradle",
93
+ androidBuildGradle: "android/build.gradle",
94
+ androidManifest: "android/app/src/main/AndroidManifest.xml",
95
+ infoPlist: findInfoPlist(root),
96
+ incodeFlow: "src/incodeFlow.ts",
97
+ incodeRuntimeConfig: "src/incodeRuntimeConfig.ts",
98
+ mainApplication: findMainApplication(root),
99
+ packageJson: "package.json",
100
+ podfile: "ios/Podfile",
101
+ };
102
+
103
+ return {
104
+ files,
105
+ packageJson,
106
+ reactNativeVersion,
107
+ root,
108
+ sdkDependency,
109
+ sdkInstalled: Boolean(sdkDependency),
110
+ texts: Object.fromEntries(
111
+ Object.entries(files).map(([key, file]) => [key, file ? readTextIfExists(path.join(root, file)) : ""]),
112
+ ),
113
+ };
114
+ }
115
+
116
+ module.exports = {
117
+ SDK_PACKAGE,
118
+ findProjectRoot,
119
+ scanProject,
120
+ };