airborne-devkit 0.27.0 → 0.28.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,84 @@
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.3.0 - 2026-03-26
6
+ #### Features
7
+ - add platform-specific configuration and improve file processing and update smithy-cli-generator submodule - (823cfe5) - Yash Rajput
8
+
9
+ - - -
10
+
11
+ ## v0.27.0 - 2026-03-26
12
+ #### Miscellaneous Chores
13
+ - **(version)** v0.27.0 [skip ci] - (75587e4) - Airborne Bot
14
+
15
+ - - -
16
+
17
+ ## v0.26.1 - 2026-03-26
18
+ #### Miscellaneous Chores
19
+ - **(version)** v0.26.1 [skip ci] - (7b0b420) - Airborne Bot
20
+
21
+ - - -
22
+
23
+ ## v0.26.0 - 2026-03-26
24
+ #### Miscellaneous Chores
25
+ - **(version)** v0.26.0 [skip ci] - (e987bfa) - Airborne Bot
26
+
27
+ - - -
28
+
29
+ ## v0.25.0 - 2026-03-26
30
+ #### Miscellaneous Chores
31
+ - **(version)** v0.25.0 [skip ci] - (8a22156) - Airborne Bot
32
+
33
+ - - -
34
+
35
+ ## v0.24.4 - 2026-03-26
36
+ #### Miscellaneous Chores
37
+ - **(version)** v0.24.4 [skip ci] - (3398e93) - Airborne Bot
38
+
39
+ - - -
40
+
41
+ ## v0.24.3 - 2026-03-26
42
+ #### Miscellaneous Chores
43
+ - **(version)** v0.24.3 [skip ci] - (bd6d431) - Airborne Bot
44
+
45
+ - - -
46
+
47
+ ## v0.24.2 - 2026-03-26
48
+ #### Miscellaneous Chores
49
+ - **(version)** v0.24.2 [skip ci] - (3c27527) - Airborne Bot
50
+
51
+ - - -
52
+
53
+ ## v0.24.1 - 2026-03-26
54
+ #### Miscellaneous Chores
55
+ - **(version)** v0.24.1 [skip ci] - (c89ac50) - Airborne Bot
56
+
57
+ - - -
58
+
59
+ ## v0.24.0 - 2026-03-26
60
+ #### Miscellaneous Chores
61
+ - **(version)** v0.24.0 [skip ci] - (d127ca2) - Airborne Bot
62
+
63
+ - - -
64
+
65
+ ## v0.23.3 - 2026-03-26
66
+ #### Miscellaneous Chores
67
+ - **(version)** v0.23.3 [skip ci] - (fea39f7) - Airborne Bot
68
+
69
+ - - -
70
+
71
+ ## v0.23.2 - 2026-03-26
72
+ #### Miscellaneous Chores
73
+ - **(version)** v0.23.2 [skip ci] - (1a6ac61) - Airborne Bot
74
+
75
+ - - -
76
+
77
+ ## v0.23.1 - 2026-03-26
78
+ #### Miscellaneous Chores
79
+ - **(version)** v0.23.1 [skip ci] - (1644fd6) - Airborne Bot
80
+
81
+ - - -
82
+
5
83
  ## airborne_cli-v0.2.0 - 2026-02-05
6
84
  #### Features
7
85
  - add Expo project support in cli - (751a4f5) - Yash Rajput
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "airborne-devkit",
3
- "version": "0.27.0",
3
+ "version": "0.28.0",
4
4
  "description": "Cli for Airborne",
5
5
  "main": "src/index.js",
6
6
  "type": "module",
package/src/index.js CHANGED
@@ -3,7 +3,7 @@
3
3
  import { Command, InvalidOptionArgumentError } from "commander";
4
4
  import coreCli from "airborne-core-cli";
5
5
  import {
6
- readAirborneConfig,
6
+ readAndResolveAirborneConfig,
7
7
  writeAirborneConfig,
8
8
  normalizeOptions,
9
9
  formatCommand,
@@ -17,16 +17,27 @@ import {
17
17
  readReleaseConfig,
18
18
  releaseConfigExists,
19
19
  updateLocalReleaseConfig,
20
+ writeReleaseConfig,
20
21
  } from "./utils/release.js";
21
22
  import { createFiles, uploadFiles } from "./utils/file.js";
22
23
  import { createPackageFromLocalRelease } from "./utils/package.js";
23
24
  import { PostLoginAction } from "airborne-core-cli/action";
25
+ import { getBaseUrl, setBaseUrl } from "airborne-core-cli";
24
26
  const program = new Command();
25
27
 
28
+ try{
29
+ const baseUrl = await getBaseUrl();
30
+ if(!baseUrl){
31
+ await setBaseUrl("https://airborne.juspay.in");
32
+ }
33
+ }catch(err){
34
+ await setBaseUrl("https://airborne.juspay.in");
35
+ }
36
+
26
37
  program
27
38
  .name("airborne-devkit")
28
39
  .description("Command-line interface for Airborne operations")
29
- .version("0.27.0");
40
+ .version("0.28.0");
30
41
 
31
42
  coreCli.commands.forEach((cmd, i) => {
32
43
  if (cmd._name !== "PostLogin") {
@@ -48,8 +59,10 @@ program
48
59
 
49
60
  Usage 2 - With all options specified:
50
61
  $ airborne-devkit create-local-airborne-config [directoryPath] \\
51
- -o <organisation> \\
52
- -n <namespace> \\
62
+ --android-organisation <android-organisation> \\
63
+ --ios-organisation <ios-organisation> \\
64
+ --android-namespace <android-namespace> \\
65
+ --ios-namespace <ios-namespace> \\
53
66
  -j <js-entry-file> \\
54
67
  -a <android-index-file> \\
55
68
  -i <ios-index-file> \\
@@ -57,8 +70,10 @@ program
57
70
 
58
71
  Parameters:
59
72
  [directoryPath] (optional) : Directory where config will be created (defaults to current directory)
60
- -o, --organisation <string> (optional) : Organisation name of the package
61
- -n, --namespace <string> (optional) : Namespace or application name of the package
73
+ --android-organisation <string> (optional) : Organisation name for Android
74
+ --ios-organisation <string> (optional) : Organisation name for iOS
75
+ --android-namespace <string> (optional) : Namespace or application name for Android
76
+ --ios-namespace <string> (optional) : Namespace or application name for iOS
62
77
  -j, --js-entry-file <string> (optional) : Path to the JavaScript entry file
63
78
  -a, --android-index-file <string> (optional) : Path to the Android bundle output file
64
79
  -i, --ios-index-file <string> (optional) : Path to the iOS bundle output file
@@ -66,10 +81,21 @@ program
66
81
 
67
82
  `
68
83
  )
69
- .option("-o, --organisation <org>", "Organisation name of the package")
70
84
  .option(
71
- "-n, --namespace <namespace>",
72
- "Namespace or application name of the package"
85
+ "--android-organisation <org>",
86
+ "Organisation name for Android"
87
+ )
88
+ .option(
89
+ "--ios-organisation <org>",
90
+ "Organisation name for iOS"
91
+ )
92
+ .option(
93
+ "--android-namespace <namespace>",
94
+ "Namespace or application name for Android"
95
+ )
96
+ .option(
97
+ "--ios-namespace <namespace>",
98
+ "Namespace or application name for iOS"
73
99
  )
74
100
  .option("-j, --js-entry-file <path>", "Path to the JavaScript entry file")
75
101
  .option(
@@ -91,29 +117,35 @@ Examples:
91
117
 
92
118
  3. Create config with all options specified:
93
119
  $ airborne-devkit create-local-airborne-config \\
94
- -o "MyCompany" \\
95
- -n "MyApp" \\
120
+ --android-organisation "MyCompany" \\
121
+ --ios-organisation "MyCompany" \\
122
+ --android-namespace "MyApp" \\
123
+ --ios-namespace "MyApp" \\
96
124
  -j "index.js" \\
97
125
  -a "android/app/build/generated/assets/react/release/index.android.bundle" \\
98
126
  -i "ios/main.jsbundle"
99
127
 
100
128
  4. Create config in specific directory with options:
101
129
  $ airborne-devkit create-local-airborne-config ./my-rn-project \\
102
- -o "MyCompany" \\
103
- -n "MyApp"
130
+ --android-organisation "MyCompany" \\
131
+ --ios-organisation "MyCompany" \\
132
+ --android-namespace "MyApp" \\
133
+ --ios-namespace "MyApp"
104
134
 
105
135
  5. Create config for an Expo project:
106
136
  $ airborne-devkit create-local-airborne-config -e
107
137
 
108
138
  6. Create config for Expo project with all options:
109
139
  $ airborne-devkit create-local-airborne-config \\
110
- -o "MyCompany" \\
111
- -n "MyApp" \\
140
+ --android-organisation "MyCompany" \\
141
+ --ios-organisation "MyCompany" \\
142
+ --android-namespace "MyApp" \\
143
+ --ios-namespace "MyApp" \\
112
144
  -e
113
145
 
114
146
  Notes:
115
147
  - If directoryPath is not provided, current working directory will be used
116
- - If organisation or namespace and others are not provided, you'll be prompted to enter them
148
+ - If organisations, namespaces, and others are not provided, you'll be prompted to enter them
117
149
  - Command will fail if an airborne config already exists in the target directory`
118
150
  )
119
151
  .action(async (directoryPath, options) => {
@@ -245,8 +277,9 @@ Notes:
245
277
  );
246
278
  }
247
279
  const normalizedOptions = normalizeOptions(options);
248
- const airborneConfig = await readAirborneConfig(
249
- normalizedOptions.directory_path
280
+ const airborneConfig = await readAndResolveAirborneConfig(
281
+ normalizedOptions.directory_path,
282
+ normalizedOptions.platform
250
283
  );
251
284
  const releaseConfig = await releaseConfigExists(
252
285
  normalizedOptions.directory_path,
@@ -383,7 +416,10 @@ Notes:
383
416
  }
384
417
  const normalizedOptions = normalizeOptions(options);
385
418
 
386
- const config = await readAirborneConfig(normalizedOptions.directory_path);
419
+ const config = await readAndResolveAirborneConfig(
420
+ normalizedOptions.directory_path,
421
+ options.platform
422
+ );
387
423
  await updateLocalReleaseConfig(
388
424
  config,
389
425
  normalizedOptions,
@@ -492,8 +528,9 @@ Notes:
492
528
  );
493
529
  }
494
530
  const normalizedOptions = normalizeOptions(options);
495
- let airborneConfig = await readAirborneConfig(
496
- normalizedOptions.directory_path
531
+ let airborneConfig = await readAndResolveAirborneConfig(
532
+ normalizedOptions.directory_path,
533
+ normalizedOptions.platform
497
534
  );
498
535
 
499
536
  airborneConfig = { ...airborneConfig, ...normalizedOptions };
@@ -502,27 +539,81 @@ Notes:
502
539
  airborneConfig.platform,
503
540
  airborneConfig.namespace
504
541
  );
505
- const filesToUpload = releaseConfig.package.important.concat(
506
- releaseConfig.package.index
507
- );
542
+
508
543
  try {
509
544
  airborneConfig.token = await loadToken(normalizedOptions.directory_path)
510
545
  .access_token;
511
546
  } catch (err) {
512
547
  throw new Error("Please log in first");
513
548
  }
549
+
550
+ let baseUrl;
514
551
  if (!options.upload) {
515
- let baseUrl = await promptWithType(
552
+ baseUrl = await promptWithType(
516
553
  "\n Provide your base url for files: ",
517
554
  "string"
518
555
  );
519
556
  if (baseUrl[baseUrl.length - 1] !== "/") {
520
557
  baseUrl = baseUrl + "/";
521
558
  }
522
- await createFiles(filesToUpload, airborneConfig, baseUrl);
523
- } else {
524
- await uploadFiles(filesToUpload, airborneConfig);
525
559
  }
560
+
561
+ const processFiles = async (files) => {
562
+ if (!files || files.length === 0) return;
563
+ if (options.upload) {
564
+ return await uploadFiles(files, airborneConfig);
565
+ } else {
566
+ return await createFiles(files, airborneConfig, baseUrl);
567
+ }
568
+ };
569
+
570
+ const applyResults = (entries, results) => {
571
+ if (!entries || !results) return;
572
+ for (const entry of entries) {
573
+ if (results[entry.file_path]) {
574
+ entry.url = results[entry.file_path].url;
575
+ entry.checksum = results[entry.file_path].checksum;
576
+ }
577
+ }
578
+ };
579
+
580
+ console.log("Processing index file");
581
+ const indexFiles = [releaseConfig.package.index];
582
+ const indexResults = await processFiles(indexFiles);
583
+ applyResults(indexFiles, indexResults);
584
+ releaseConfig.package.index = indexFiles[0];
585
+
586
+ const importantFiles = releaseConfig.package.important || [];
587
+ if (importantFiles.length > 0) {
588
+ console.log("Processing important files");
589
+ const importantResults = await processFiles(importantFiles);
590
+ applyResults(importantFiles, importantResults);
591
+ releaseConfig.package.important = importantFiles;
592
+ }
593
+
594
+ const lazyFiles = releaseConfig.package.lazy || [];
595
+ if (lazyFiles.length > 0) {
596
+ console.log("Processing lazy files");
597
+ const lazyResults = await processFiles(lazyFiles);
598
+ applyResults(lazyFiles, lazyResults);
599
+ releaseConfig.package.lazy = lazyFiles;
600
+ }
601
+
602
+ const resourceFiles = releaseConfig.resources || [];
603
+ if (resourceFiles.length > 0) {
604
+ console.log("Processing resource files");
605
+ const resourceResults = await processFiles(resourceFiles);
606
+ applyResults(resourceFiles, resourceResults);
607
+ releaseConfig.resources = resourceFiles;
608
+ }
609
+
610
+ await writeReleaseConfig(
611
+ releaseConfig,
612
+ airborneConfig.platform,
613
+ airborneConfig.namespace,
614
+ airborneConfig.directory_path
615
+ );
616
+
526
617
  process.exit(0);
527
618
  } catch (err) {
528
619
  console.error("āŒ Failed to create remote files:", err.message);
@@ -631,8 +722,9 @@ Notes:
631
722
  );
632
723
  }
633
724
  const normalizedOptions = normalizeOptions(options);
634
- let airborneConfig = await readAirborneConfig(
635
- normalizedOptions.directory_path
725
+ let airborneConfig = await readAndResolveAirborneConfig(
726
+ normalizedOptions.directory_path,
727
+ normalizedOptions.platform
636
728
  );
637
729
 
638
730
  airborneConfig = { ...airborneConfig, ...normalizedOptions };
@@ -4,11 +4,76 @@ import { createHash } from "crypto";
4
4
  import { createReadStream } from "fs";
5
5
  import { promptWithType } from "./prompt.js";
6
6
 
7
+ function persistToConfig(configPath, platform, fields, label) {
8
+ try {
9
+ const raw = JSON.parse(fs.readFileSync(configPath, "utf8"));
10
+ if (!raw[platform]) {
11
+ raw[platform] = {};
12
+ }
13
+ for (const [key, value] of Object.entries(fields)) {
14
+ raw[platform][key] = value;
15
+ }
16
+ fs.writeFileSync(configPath, JSON.stringify(raw, null, 2), "utf8");
17
+ const keys = Object.keys(fields).join(", ");
18
+ console.log(`āœ… Updated ${label} ${keys} in ${configPath}`);
19
+ } catch (err) {
20
+ console.error(
21
+ `āš ļø Could not persist ${label} config to file:`,
22
+ err.message
23
+ );
24
+ }
25
+ }
26
+
27
+ export async function readAndResolveAirborneConfig(directoryPath, platform) {
28
+ const airborneConfig = await readAirborneConfig(directoryPath);
29
+ const configPath = path.join(directoryPath, "airborne-config.json");
30
+ const label = platform === "android" ? "Android" : "iOS";
31
+
32
+ if (!airborneConfig[platform]) {
33
+ airborneConfig[platform] = {};
34
+ }
35
+
36
+ const fieldsToPersist = {};
37
+
38
+ if (!airborneConfig[platform].organisation) {
39
+ const oldOrganisation = airborneConfig.organisation || undefined;
40
+
41
+ airborneConfig[platform].organisation = await promptWithType(
42
+ `\n Please enter the ${label} organisation name${oldOrganisation ? ` (default: ${oldOrganisation})` : ""}: `,
43
+ "string",
44
+ oldOrganisation
45
+ );
46
+ fieldsToPersist.organisation = airborneConfig[platform].organisation;
47
+ }
48
+
49
+ if (!airborneConfig[platform].namespace) {
50
+ const oldNamespace = airborneConfig.namespace || undefined;
51
+
52
+ airborneConfig[platform].namespace = await promptWithType(
53
+ `\n Please enter the ${label} namespace/application name${oldNamespace ? ` (default: ${oldNamespace})` : ""}: `,
54
+ "string",
55
+ oldNamespace
56
+ );
57
+ fieldsToPersist.namespace = airborneConfig[platform].namespace;
58
+ }
59
+
60
+ if (Object.keys(fieldsToPersist).length > 0) {
61
+ persistToConfig(configPath, platform, fieldsToPersist, label);
62
+ }
63
+
64
+ airborneConfig.namespace = airborneConfig[platform].namespace;
65
+ airborneConfig.organisation = airborneConfig[platform].organisation;
66
+
67
+ return airborneConfig;
68
+ }
69
+
7
70
  const cliToConfigMap = {
8
71
  platform: "platform",
9
72
  tag: "tag",
10
- organisation: "organisation",
11
- namespace: "namespace",
73
+ androidOrganisation: "android.organisation",
74
+ iosOrganisation: "ios.organisation",
75
+ androidNamespace: "android.namespace",
76
+ iosNamespace: "ios.namespace",
12
77
  jsEntryFile: "js_entry_file",
13
78
  androidIndex: "android.index_file_path",
14
79
  iosIndex: "ios.index_file_path",
@@ -55,14 +120,16 @@ export async function writeAirborneConfig(options) {
55
120
  try {
56
121
  const filledOptions = await fillAirborneConfigOptions(options);
57
122
  const config = {
58
- organisation: filledOptions.organisation,
59
- namespace: filledOptions.namespace,
60
123
  expo: filledOptions.expo,
61
124
  js_entry_file: filledOptions.js_entry_file,
62
125
  android: {
126
+ organisation: filledOptions.android.organisation,
127
+ namespace: filledOptions.android.namespace,
63
128
  index_file_path: filledOptions.android.index_file_path,
64
129
  },
65
130
  ios: {
131
+ organisation: filledOptions.ios.organisation,
132
+ namespace: filledOptions.ios.namespace,
66
133
  index_file_path: filledOptions.ios.index_file_path,
67
134
  },
68
135
  };
@@ -107,13 +174,23 @@ export async function fillAirborneConfigOptions(options = {}) {
107
174
 
108
175
  const questions = [
109
176
  {
110
- key: "organisation",
111
- question: "\n Please enter the organisation name: ",
177
+ key: "android.organisation",
178
+ question: "\n Please enter the Android organisation name: ",
179
+ expectedType: "string",
180
+ },
181
+ {
182
+ key: "ios.organisation",
183
+ question: "\n Please enter the iOS organisation name: ",
184
+ expectedType: "string",
185
+ },
186
+ {
187
+ key: "android.namespace",
188
+ question: "\n Please enter the Android namespace/application name: ",
112
189
  expectedType: "string",
113
190
  },
114
191
  {
115
- key: "namespace",
116
- question: "\n Please enter namespace/application name: ",
192
+ key: "ios.namespace",
193
+ question: "\n Please enter the iOS namespace/application name: ",
117
194
  expectedType: "string",
118
195
  },
119
196
  {
package/src/utils/file.js CHANGED
@@ -82,6 +82,8 @@ export const uploadFiles = async (filesToUpload, config) => {
82
82
  errors: [],
83
83
  };
84
84
 
85
+ const fileResults = {};
86
+
85
87
  try {
86
88
  for (let index = 0; index < filesToUpload.length; index++) {
87
89
  const fileObj = filesToUpload[index];
@@ -90,10 +92,10 @@ export const uploadFiles = async (filesToUpload, config) => {
90
92
  try {
91
93
  console.log(`${fileProgress} šŸ” Processing ${fileObj.file_path}...`);
92
94
 
93
- const storedChecksum = await getMappedChecksum(
95
+ const stored = await getMappedChecksumAndURL(
94
96
  config.directory_path,
95
97
  fileObj.file_path,
96
- config.tag
98
+ config
97
99
  );
98
100
 
99
101
  const baseDir = path.isAbsolute(config.directory_path)
@@ -115,10 +117,11 @@ export const uploadFiles = async (filesToUpload, config) => {
115
117
 
116
118
  const checksum = await sha256FileHex(fileFullPath);
117
119
 
118
- if (storedChecksum === checksum) {
120
+ if (stored?.checksum === checksum) {
119
121
  console.log(
120
122
  `${fileProgress} āœ… File already exists, checksum matches`
121
123
  );
124
+ fileResults[fileObj.file_path] = { url: stored.url, checksum: stored.checksum };
122
125
  results.existing++;
123
126
  continue;
124
127
  }
@@ -144,11 +147,18 @@ export const uploadFiles = async (filesToUpload, config) => {
144
147
  await createFileMapping(
145
148
  config.directory_path,
146
149
  uploadOutput.file_path,
147
- uploadOutput.id,
148
- uploadOutput.checksum,
149
- uploadOutput.tag
150
+ {
151
+ id: uploadOutput.id,
152
+ checksum: uploadOutput.checksum,
153
+ url: uploadOutput.url,
154
+ organisation: config.organisation,
155
+ namespace: config.namespace,
156
+ tag: uploadOutput.tag,
157
+ }
150
158
  );
151
159
 
160
+ fileResults[fileObj.file_path] = { url: uploadOutput.url, checksum: uploadOutput.checksum };
161
+
152
162
  // Check if this was a new upload or existing file returned
153
163
  if (uploadOutput.checksum === checksum) {
154
164
  console.log(
@@ -185,6 +195,8 @@ export const uploadFiles = async (filesToUpload, config) => {
185
195
  console.error("\nšŸ’„ Upload process failed:", err.message);
186
196
  throw err;
187
197
  }
198
+
199
+ return fileResults;
188
200
  };
189
201
 
190
202
  export async function createFiles(filesToCreate, config, prefixUrl) {
@@ -199,6 +211,8 @@ export async function createFiles(filesToCreate, config, prefixUrl) {
199
211
  errors: [],
200
212
  };
201
213
 
214
+ const fileResults = {};
215
+
202
216
  try {
203
217
  for (let index = 0; index < filesToCreate.length; index++) {
204
218
  const fileObj = filesToCreate[index];
@@ -207,10 +221,10 @@ export async function createFiles(filesToCreate, config, prefixUrl) {
207
221
  try {
208
222
  console.log(`${fileProgress} šŸ” Processing ${fileObj.file_path}...`);
209
223
 
210
- const storedChecksum = await getMappedChecksum(
224
+ const stored = await getMappedChecksumAndURL(
211
225
  config.directory_path,
212
226
  fileObj.file_path,
213
- config.tag
227
+ config
214
228
  );
215
229
 
216
230
  const baseDir = path.isAbsolute(config.directory_path)
@@ -230,14 +244,15 @@ export async function createFiles(filesToCreate, config, prefixUrl) {
230
244
  throw new Error(`File not found: ${fileFullPath}`);
231
245
  }
232
246
 
233
- if (storedChecksum) {
247
+ if (stored?.checksum) {
234
248
  console.log(`${fileProgress} šŸ” Calculating checksum...`);
235
249
  const checksum = await sha256FileHex(fileFullPath);
236
250
 
237
- if (storedChecksum === checksum) {
251
+ if (stored.checksum === checksum) {
238
252
  console.log(
239
253
  `${fileProgress} āœ… File already exists, checksum matches`
240
254
  );
255
+ fileResults[fileObj.file_path] = { url: stored.url, checksum: stored.checksum };
241
256
  results.existing++;
242
257
  continue;
243
258
  }
@@ -270,11 +285,18 @@ export async function createFiles(filesToCreate, config, prefixUrl) {
270
285
  await createFileMapping(
271
286
  config.directory_path,
272
287
  output.file_path,
273
- output.id,
274
- output.checksum,
275
- output.tag
288
+ {
289
+ id: output.id,
290
+ checksum: output.checksum,
291
+ url: output.url,
292
+ organisation: config.organisation,
293
+ namespace: config.namespace,
294
+ tag: output.tag,
295
+ }
276
296
  );
277
297
 
298
+ fileResults[fileObj.file_path] = { url: output.url, checksum: output.checksum };
299
+
278
300
  console.log(
279
301
  `${fileProgress} āœ… Successfully processed file record for ${fileObj.file_path}`
280
302
  );
@@ -304,14 +326,14 @@ export async function createFiles(filesToCreate, config, prefixUrl) {
304
326
  console.error("\nšŸ’„ File creation process failed:", err.message);
305
327
  throw err;
306
328
  }
329
+
330
+ return fileResults;
307
331
  }
308
332
 
309
333
  export async function createFileMapping(
310
334
  directory_path,
311
335
  file_path,
312
- id,
313
- checksum,
314
- tag
336
+ { id, checksum, url, organisation, namespace, tag }
315
337
  ) {
316
338
  const airborneDir = path.join(directory_path, ".airborne");
317
339
  const mappingFile = path.join(airborneDir, "mappings.json");
@@ -329,12 +351,18 @@ export async function createFileMapping(
329
351
  tag = "__default__";
330
352
  }
331
353
 
332
- if (!mappings[tag]) {
333
- mappings[tag] = {};
354
+ if (!mappings[organisation]) {
355
+ mappings[organisation] = {};
356
+ }
357
+ if (!mappings[organisation][namespace]) {
358
+ mappings[organisation][namespace] = {};
359
+ }
360
+ if (!mappings[organisation][namespace][tag]) {
361
+ mappings[organisation][namespace][tag] = {};
334
362
  }
335
363
 
336
- // Update or insert mapping with checksum
337
- mappings[tag][file_path] = { id, checksum };
364
+ // Update or insert mapping with checksum and url
365
+ mappings[organisation][namespace][tag][file_path] = { id, checksum, url };
338
366
 
339
367
  // Write updated mappings back
340
368
  await fs.promises.writeFile(
@@ -348,7 +376,7 @@ export async function createFileMapping(
348
376
  }
349
377
  }
350
378
 
351
- export async function getMappedChecksum(directory_path, file_path, tag) {
379
+ export async function getMappedChecksumAndURL(directory_path, file_path, { organisation, namespace, tag }) {
352
380
  const mappingFile = path.join(directory_path, ".airborne", "mappings.json");
353
381
  if (!tag) {
354
382
  tag = "__default__";
@@ -357,13 +385,15 @@ export async function getMappedChecksum(directory_path, file_path, tag) {
357
385
  try {
358
386
  const data = await fs.promises.readFile(mappingFile, "utf8");
359
387
  const mappings = JSON.parse(data);
360
- return mappings[tag][file_path]?.checksum || null;
388
+ const entry = mappings[organisation]?.[namespace]?.[tag]?.[file_path];
389
+ if (!entry) return null;
390
+ return { checksum: entry.checksum || null, url: entry.url || null };
361
391
  } catch (err) {
362
392
  return null;
363
393
  }
364
394
  }
365
395
 
366
- export async function readFileMapping(directory_path, file_path, tag) {
396
+ export async function readFileMapping(directory_path, file_path, { organisation, namespace, tag }) {
367
397
  const mappingFile = path.join(directory_path, ".airborne", "mappings.json");
368
398
 
369
399
  if (!tag) {
@@ -373,7 +403,7 @@ export async function readFileMapping(directory_path, file_path, tag) {
373
403
  try {
374
404
  const data = await fs.promises.readFile(mappingFile, "utf8");
375
405
  const mappings = JSON.parse(data);
376
- return mappings[tag][file_path] || null;
406
+ return mappings[organisation]?.[namespace]?.[tag]?.[file_path] || null;
377
407
  } catch (err) {
378
408
  return null;
379
409
  }
@@ -17,7 +17,7 @@ export async function createPackageFromLocalRelease(
17
17
  const indexMapping = await readFileMapping(
18
18
  airborneConfig.directory_path,
19
19
  indexFilePath,
20
- airborneConfig.tag
20
+ airborneConfig
21
21
  );
22
22
  if (!indexMapping) {
23
23
  throw new Error(`Missing upload for index file: ${indexFilePath}`);
@@ -36,7 +36,7 @@ export async function createPackageFromLocalRelease(
36
36
  const mapping = await readFileMapping(
37
37
  airborneConfig.directory_path,
38
38
  file_path,
39
- airborneConfig.tag
39
+ airborneConfig
40
40
  );
41
41
  if (!mapping) {
42
42
  throw new Error(`Missing mapping for file: ${file_path}`);
@@ -141,11 +141,13 @@ export async function createLocalReleaseConfig(
141
141
  properties: {},
142
142
  },
143
143
  package: {
144
+ name: airborneConfig.namespace,
144
145
  version: "",
145
146
  prooerties: {},
146
147
  index: {
147
148
  file_path: airborneConfig[platform].index_file_path,
148
149
  url: "",
150
+ checksum: "",
149
151
  },
150
152
  important: remotebundleContents
151
153
  .filter(
@@ -291,11 +293,13 @@ export async function updateLocalReleaseConfig(
291
293
  properties: existingReleaseConfig?.config?.properties || {},
292
294
  },
293
295
  package: {
296
+ name: airborneConfig.namespace,
294
297
  version: existingReleaseConfig?.package?.version || "",
295
298
  properties: existingReleaseConfig?.package?.properties || {},
296
299
  index: {
297
300
  file_path: airborneConfig[options.platform].index_file_path,
298
- url: existingReleaseConfig?.package?.index?.url || "",
301
+ url: "",
302
+ checksum: "",
299
303
  },
300
304
  important: remotebundleContents
301
305
  .filter(
@@ -305,10 +309,14 @@ export async function updateLocalReleaseConfig(
305
309
  .map((item) => ({
306
310
  file_path: item.path,
307
311
  url: "",
312
+ checksum: "",
308
313
  })),
309
- lazy: existingReleaseConfig?.package?.lazy || [],
314
+ lazy: [],
310
315
  },
311
- resources: existingReleaseConfig?.resources || [],
316
+ resources: (existingReleaseConfig?.resources || []).filter(
317
+ (res) =>
318
+ !remotebundleContents.some((item) => item.path === res.file_path)
319
+ ),
312
320
  };
313
321
 
314
322
  await writeReleaseConfig(