airborne-devkit 0.34.2 → 0.35.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/CHANGELOG.md CHANGED
@@ -2,6 +2,90 @@
2
2
  All notable changes to this project will be documented in this file. See [conventional commits](https://www.conventionalcommits.org/) for commit guidelines.
3
3
 
4
4
  - - -
5
+ ## airborne_cli-v0.4.0 - 2026-05-11
6
+ #### Features
7
+ - add hermes build capability - (1131f49) - Yash Rajput
8
+
9
+ - - -
10
+
11
+ ## v0.34.2 - 2026-05-11
12
+ #### Miscellaneous Chores
13
+ - **(version)** v0.34.2 [skip ci] - (da14a3e) - Airborne Bot
14
+
15
+ - - -
16
+
17
+ ## v0.34.1 - 2026-05-11
18
+ #### Miscellaneous Chores
19
+ - **(version)** v0.34.1 [skip ci] - (0bbc8f3) - Airborne Bot
20
+
21
+ - - -
22
+
23
+ ## v0.34.0 - 2026-05-11
24
+ #### Miscellaneous Chores
25
+ - **(version)** v0.34.0 [skip ci] - (0c761ce) - Airborne Bot
26
+
27
+ - - -
28
+
29
+ ## v0.33.0 - 2026-05-11
30
+ #### Miscellaneous Chores
31
+ - **(version)** v0.33.0 [skip ci] - (41d1b1c) - Airborne Bot
32
+
33
+ - - -
34
+
35
+ ## v0.32.0 - 2026-05-11
36
+ #### Miscellaneous Chores
37
+ - **(version)** v0.32.0 [skip ci] - (6fcfb70) - Airborne Bot
38
+
39
+ - - -
40
+
41
+ ## v0.31.2 - 2026-05-11
42
+ #### Miscellaneous Chores
43
+ - **(version)** v0.31.2 [skip ci] - (1776935) - Airborne Bot
44
+
45
+ - - -
46
+
47
+ ## v0.31.1 - 2026-05-11
48
+ #### Miscellaneous Chores
49
+ - **(version)** v0.31.1 [skip ci] - (fd3e886) - Airborne Bot
50
+
51
+ - - -
52
+
53
+ ## v0.31.0 - 2026-05-11
54
+ #### Miscellaneous Chores
55
+ - **(version)** v0.31.0 [skip ci] - (9f47c56) - Airborne Bot
56
+
57
+ - - -
58
+
59
+ ## v0.30.1 - 2026-05-11
60
+ #### Miscellaneous Chores
61
+ - **(version)** v0.30.1 [skip ci] - (4a81918) - Airborne Bot
62
+
63
+ - - -
64
+
65
+ ## v0.30.0 - 2026-05-11
66
+ #### Miscellaneous Chores
67
+ - **(version)** v0.30.0 [skip ci] - (e345581) - Airborne Bot
68
+
69
+ - - -
70
+
71
+ ## v0.29.2 - 2026-05-11
72
+ #### Miscellaneous Chores
73
+ - **(version)** v0.29.2 [skip ci] - (8cd1d49) - Airborne Bot
74
+
75
+ - - -
76
+
77
+ ## v0.29.1 - 2026-05-11
78
+ #### Miscellaneous Chores
79
+ - **(version)** v0.29.1 [skip ci] - (e9670ef) - Airborne Bot
80
+
81
+ - - -
82
+
83
+ ## v0.29.0 - 2026-05-11
84
+ #### Miscellaneous Chores
85
+ - **(version)** v0.29.0 [skip ci] - (4d13783) - Airborne Bot
86
+
87
+ - - -
88
+
5
89
  ## airborne_cli-v0.3.0 - 2026-03-26
6
90
  #### Features
7
91
  - add platform-specific configuration and improve file processing and update smithy-cli-generator submodule - (823cfe5) - Yash Rajput
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airborne-devkit",
3
- "version": "0.34.2",
3
+ "version": "0.35.0",
4
4
  "description": "Cli for Airborne",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -37,7 +37,7 @@ try{
37
37
  program
38
38
  .name("airborne-devkit")
39
39
  .description("Command-line interface for Airborne operations")
40
- .version("0.34.2");
40
+ .version("0.35.0");
41
41
 
42
42
  coreCli.commands.forEach((cmd, i) => {
43
43
  if (cmd._name !== "PostLogin") {
@@ -66,7 +66,8 @@ program
66
66
  -j <js-entry-file> \\
67
67
  -a <android-index-file> \\
68
68
  -i <ios-index-file> \\
69
- -e
69
+ -e \\
70
+ --hermes-enabled
70
71
 
71
72
  Parameters:
72
73
  [directoryPath] (optional) : Directory where config will be created (defaults to current directory)
@@ -78,6 +79,7 @@ program
78
79
  -a, --android-index-file <string> (optional) : Path to the Android bundle output file
79
80
  -i, --ios-index-file <string> (optional) : Path to the iOS bundle output file
80
81
  -e, --expo (optional) : Indicates if the project is using Expo
82
+ --hermes-enabled (optional) : Enable Hermes engine for the project
81
83
 
82
84
  `
83
85
  )
@@ -104,6 +106,7 @@ program
104
106
  )
105
107
  .option("-i, --ios-index-file <path>", "Path to the iOS bundle output file")
106
108
  .option("-e, --expo", "Indicates if the project is using Expo")
109
+ .option("--hermes-enabled", "Enable Hermes engine for the project")
107
110
  .addHelpText(
108
111
  "after",
109
112
  `
@@ -135,7 +138,10 @@ Examples:
135
138
  5. Create config for an Expo project:
136
139
  $ airborne-devkit create-local-airborne-config -e
137
140
 
138
- 6. Create config for Expo project with all options:
141
+ 6. Create config with Hermes enabled:
142
+ $ airborne-devkit create-local-airborne-config --hermes-enabled
143
+
144
+ 7. Create config for Expo project with all options:
139
145
  $ airborne-devkit create-local-airborne-config \\
140
146
  --android-organisation "MyCompany" \\
141
147
  --ios-organisation "MyCompany" \\
@@ -82,6 +82,7 @@ const cliToConfigMap = {
82
82
  bootTimeout: "boot_timeout",
83
83
  releaseConfigTimeout: "release_config_timeout",
84
84
  expo: "expo",
85
+ hermesEnabled: "hermes_enabled",
85
86
  };
86
87
 
87
88
  export function normalizeOptions(options = {}) {
@@ -120,6 +121,7 @@ export async function writeAirborneConfig(options) {
120
121
  try {
121
122
  const filledOptions = await fillAirborneConfigOptions(options);
122
123
  const config = {
124
+ hermes_enabled: filledOptions.hermes_enabled,
123
125
  expo: filledOptions.expo,
124
126
  js_entry_file: filledOptions.js_entry_file,
125
127
  android: {
@@ -173,6 +175,12 @@ export async function fillAirborneConfigOptions(options = {}) {
173
175
  const isExpo = getNested(result, "expo");
174
176
 
175
177
  const questions = [
178
+ {
179
+ key: "hermes_enabled",
180
+ question: "\n Enable Hermes engine? (y/n, default: no): ",
181
+ expectedType: "boolean",
182
+ defaultValue: false,
183
+ },
176
184
  {
177
185
  key: "android.organisation",
178
186
  question: "\n Please enter the Android organisation name: ",
@@ -0,0 +1,191 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { createRequire } from "module";
4
+ import { spawn } from "child_process";
5
+ import os from "os";
6
+
7
+ const require = createRequire(import.meta.url);
8
+
9
+
10
+ function isExecutable(p) {
11
+ try {
12
+ fs.accessSync(p, fs.constants.X_OK);
13
+ return true;
14
+ } catch {
15
+ return false;
16
+ }
17
+ }
18
+
19
+ function getPlatformBinaryNames() {
20
+ switch (process.platform) {
21
+ case "darwin":
22
+ return ["osx-bin/hermesc"];
23
+ case "linux":
24
+ return ["linux64-bin/hermesc", "linux-bin/hermesc"];
25
+ case "win32":
26
+ return ["win64-bin/hermesc.exe"];
27
+ default:
28
+ return [];
29
+ }
30
+ }
31
+
32
+ function resolvePackageRoot(pkgName, projectRoot) {
33
+ const nodeModulesRoot = path.join(projectRoot, "node_modules");
34
+ try {
35
+ const pkgJsonPath = require.resolve(`${pkgName}/package.json`, {
36
+ paths: [nodeModulesRoot],
37
+ });
38
+ return path.dirname(pkgJsonPath);
39
+ } catch {
40
+ return null;
41
+ }
42
+ }
43
+
44
+ function buildCandidatePaths(packageRoot) {
45
+ const bins = getPlatformBinaryNames();
46
+ const isWin = process.platform === "win32";
47
+ const hermescBin = isWin ? "hermesc.exe" : "hermesc";
48
+
49
+ return [
50
+ ...bins.map((b) => path.join(packageRoot, "hermesc", b)),
51
+ ...bins.map((b) => path.join(packageRoot, b)),
52
+ ...bins.map((b) => path.join(packageRoot, "sdks", "hermesc", b)),
53
+ path.join(
54
+ packageRoot,
55
+ "sdks",
56
+ "hermes-engine",
57
+ "destroot",
58
+ "bin",
59
+ hermescBin
60
+ ),
61
+ ].filter(Boolean);
62
+ }
63
+
64
+
65
+ const PRUNE_DIRS = new Set([
66
+ ".git", "build", "dist", ".gradle",
67
+ ".cache", ".yarn", "__tests__", "test", "docs", "example", "examples",
68
+ ]);
69
+
70
+
71
+ function findFirstExecutableInTree(startDir, fileName, maxDepth = 6) {
72
+ function walk(dir, depth) {
73
+ if (depth > maxDepth) return null;
74
+
75
+ let entries;
76
+ try {
77
+ entries = fs.readdirSync(dir, { withFileTypes: true });
78
+ } catch {
79
+ return null;
80
+ }
81
+
82
+ for (const entry of entries) {
83
+ if (entry.isDirectory()) {
84
+ if (PRUNE_DIRS.has(entry.name)) continue;
85
+ const found = walk(path.join(dir, entry.name), depth + 1);
86
+ if (found) return found;
87
+ } else if (entry.isFile() && entry.name === fileName) {
88
+ const fullPath = path.join(dir, entry.name);
89
+ if (isExecutable(fullPath)) return fullPath;
90
+ }
91
+ }
92
+
93
+ return null;
94
+ }
95
+
96
+ return walk(startDir, 0);
97
+ }
98
+
99
+
100
+ const KNOWN_PACKAGES = ["hermes-compiler", "hermes-engine", "react-native"];
101
+
102
+ export function findHermesCompiler(projectRoot) {
103
+ const nodeModulesDir = path.join(projectRoot, "node_modules");
104
+
105
+ for (const pkg of KNOWN_PACKAGES) {
106
+ const packageRoot = resolvePackageRoot(pkg, projectRoot);
107
+
108
+ if (!packageRoot) continue;
109
+
110
+ if (!packageRoot.startsWith(nodeModulesDir + path.sep)) continue;
111
+
112
+ const candidates = buildCandidatePaths(packageRoot);
113
+
114
+ for (const candidate of candidates) {
115
+ if (isExecutable(candidate)) {
116
+ return candidate;
117
+ }
118
+ }
119
+ }
120
+
121
+ const hermescBin =
122
+ process.platform === "win32" ? "hermesc.exe" : "hermesc";
123
+
124
+ return findFirstExecutableInTree(nodeModulesDir, hermescBin);
125
+ }
126
+
127
+
128
+ export async function compileHermesBundle(projectRoot, bundlePath) {
129
+ const hermescPath = findHermesCompiler(projectRoot);
130
+
131
+ if (!hermescPath) {
132
+ console.error("[Hermes] Error: Hermes compiler not found.");
133
+ console.error("[Hermes] Make sure hermes-compiler, hermes-engine, or react-native is installed.");
134
+ return;
135
+ }
136
+
137
+ const tempFile = path.join(os.tmpdir(), `${Date.now()}-${path.basename(bundlePath)}.hbc`);
138
+
139
+ try {
140
+ await runHermesCompiler(hermescPath, bundlePath, tempFile);
141
+ fs.renameSync(tempFile, bundlePath);
142
+ console.log(`[Hermes] Compiled ${bundlePath}`);
143
+ } catch (error) {
144
+ console.error(`[Hermes] Compilation failed: ${error.message}`);
145
+ if (fs.existsSync(tempFile)) {
146
+ fs.unlinkSync(tempFile);
147
+ }
148
+ throw error;
149
+ }
150
+ }
151
+
152
+ function runHermesCompiler(hermescPath, inputPath, outputPath) {
153
+ return new Promise((resolve, reject) => {
154
+ const args = ["-emit-binary", "-out", outputPath, inputPath];
155
+
156
+ const child = spawn(hermescPath, args, {
157
+ stdio: ["ignore", "pipe", "pipe"],
158
+ });
159
+
160
+ let stderr = "";
161
+ let stdout = "";
162
+ let isSettled = false;
163
+
164
+ const settle = (fn, arg) => {
165
+ if (isSettled) return;
166
+ isSettled = true;
167
+ fn(arg);
168
+ };
169
+
170
+ child.stdout.on("data", (data) => {
171
+ stdout += data.toString();
172
+ });
173
+
174
+ child.stderr.on("data", (data) => {
175
+ stderr += data.toString();
176
+ });
177
+
178
+ child.on("close", (code) => {
179
+ if (code !== 0) {
180
+ settle(reject, new Error(`hermesc exited with code ${code}: ${stderr || stdout || "(no output)"}`));
181
+ } else {
182
+ settle(resolve, undefined);
183
+ }
184
+ });
185
+
186
+ child.on("error", (err) => {
187
+ settle(reject, new Error(`Failed to spawn hermesc: ${err.message}`));
188
+ });
189
+ });
190
+ }
191
+
@@ -8,6 +8,7 @@ import fs from "fs";
8
8
  import path from "path";
9
9
  import { promptWithType } from "./prompt.js";
10
10
  import { execSync } from "child_process";
11
+ import { compileHermesBundle } from "./hermes.js";
11
12
 
12
13
  const __filename = fileURLToPath(import.meta.url);
13
14
  const __dirname = dirname(__filename);
@@ -124,6 +125,10 @@ export async function createLocalReleaseConfig(
124
125
  `React Native bundle command failed: ${bundleResult.error}`
125
126
  );
126
127
  }
128
+
129
+ if (airborneConfig.hermes_enabled) {
130
+ await compileHermesBundle(options.directory_path, `${build_folder}/${index_file_path}`);
131
+ }
127
132
 
128
133
  const baseDir = path.isAbsolute(options.directory_path)
129
134
  ? options.directory_path
@@ -268,6 +273,10 @@ export async function updateLocalReleaseConfig(
268
273
  `React Native bundle command failed: ${bundleResult.error}`
269
274
  );
270
275
  }
276
+
277
+ if (airborneConfig.hermes_enabled) {
278
+ await compileHermesBundle(options.directory_path, `${build_folder}/${index_file_path}`);
279
+ }
271
280
 
272
281
  const baseDir = path.isAbsolute(options.directory_path)
273
282
  ? options.directory_path