@norrix/cli 0.0.6 → 0.0.8
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/cli.js +44 -12
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/lib/amplify-config.js +30 -0
- package/dist/lib/amplify-config.js.map +1 -1
- package/dist/lib/commands.d.ts +20 -10
- package/dist/lib/commands.js +585 -51
- package/dist/lib/commands.js.map +1 -1
- package/dist/lib/fingerprinting.d.ts +18 -0
- package/dist/lib/fingerprinting.js +241 -0
- package/dist/lib/fingerprinting.js.map +1 -0
- package/dist/lib/norrix-cli.js +1 -0
- package/package.json +5 -4
package/dist/lib/commands.js
CHANGED
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
import archiver from 'archiver';
|
|
9
9
|
// import FormData from 'form-data';
|
|
10
10
|
import { configureAmplify } from './amplify-config.js';
|
|
11
|
+
import { computeFingerprint, writeFingerprintFile } from './fingerprinting.js';
|
|
11
12
|
import { signIn as amplifySignIn, signOut as amplifySignOut, getCurrentUser, fetchAuthSession, } from 'aws-amplify/auth';
|
|
12
13
|
import crypto from 'crypto';
|
|
13
14
|
import { Amplify } from 'aws-amplify';
|
|
@@ -112,6 +113,11 @@ function getS3BucketRegionFromAmplify() {
|
|
|
112
113
|
}
|
|
113
114
|
return { bucket, region };
|
|
114
115
|
}
|
|
116
|
+
function logVerbose(message, verbose) {
|
|
117
|
+
if (verbose) {
|
|
118
|
+
console.log(message);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
115
121
|
async function uploadToS3Sdk(key, data) {
|
|
116
122
|
const { bucket, region } = getS3BucketRegionFromAmplify();
|
|
117
123
|
let credentials = undefined;
|
|
@@ -133,6 +139,198 @@ async function uploadToS3Sdk(key, data) {
|
|
|
133
139
|
const client = new S3Client({ region, credentials });
|
|
134
140
|
await client.send(new PutObjectCommand({ Bucket: bucket, Key: key, Body: data }));
|
|
135
141
|
}
|
|
142
|
+
export async function printFingerprint(cliPlatformArg, appId, verbose = false) {
|
|
143
|
+
try {
|
|
144
|
+
const projectRoot = process.cwd();
|
|
145
|
+
const platform = (cliPlatformArg || '').toLowerCase() || undefined;
|
|
146
|
+
const snapshot = computeFingerprint({
|
|
147
|
+
projectRoot,
|
|
148
|
+
platform,
|
|
149
|
+
appId,
|
|
150
|
+
});
|
|
151
|
+
writeFingerprintFile(projectRoot, snapshot);
|
|
152
|
+
console.log('Norrix fingerprint snapshot:');
|
|
153
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
ora().fail(`Failed to compute fingerprint: ${error?.message || error}`);
|
|
157
|
+
if (verbose) {
|
|
158
|
+
console.error('--- Verbose error details (fingerprint) ---');
|
|
159
|
+
console.error(error);
|
|
160
|
+
if (error?.stack) {
|
|
161
|
+
console.error(error.stack);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
async function fetchBuildFingerprint(buildId, verbose = false) {
|
|
167
|
+
try {
|
|
168
|
+
const spinner = ora(`Fetching fingerprint for build ${buildId}...`).start();
|
|
169
|
+
const response = await axios.get(`${API_URL}/build/${buildId}`, {
|
|
170
|
+
headers: await getAuthHeaders(),
|
|
171
|
+
});
|
|
172
|
+
spinner.stop();
|
|
173
|
+
const build = response.data;
|
|
174
|
+
if (!build?.fingerprintHash || !build?.fingerprintNativeJson) {
|
|
175
|
+
console.log(`Build ${buildId} does not have fingerprint data recorded (fingerprintHash/fingerprintNativeJson missing).`);
|
|
176
|
+
return undefined;
|
|
177
|
+
}
|
|
178
|
+
let nativeJson = {};
|
|
179
|
+
try {
|
|
180
|
+
nativeJson = JSON.parse(build.fingerprintNativeJson || '{}');
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
nativeJson = {};
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
hash: build.fingerprintHash,
|
|
187
|
+
nativePlugins: nativeJson.nativePlugins || {},
|
|
188
|
+
appResourcesHash: nativeJson.appResourcesHash ?? null,
|
|
189
|
+
nativeSourcesHash: nativeJson.nativeSourcesHash ?? null,
|
|
190
|
+
platform: build.platform ?? null,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
ora().fail(`Failed to fetch fingerprint for build ${buildId}: ${error?.message || error}`);
|
|
195
|
+
if (verbose) {
|
|
196
|
+
console.error('--- Verbose error details (fingerprint-compare/fetch) ---');
|
|
197
|
+
console.error(error);
|
|
198
|
+
if (error?.response) {
|
|
199
|
+
console.error('Axios response status:', error.response.status);
|
|
200
|
+
console.error('Axios response data:', error.response.data);
|
|
201
|
+
}
|
|
202
|
+
if (error?.stack) {
|
|
203
|
+
console.error(error.stack);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return undefined;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function diffPluginMaps(fromPlugins, toPlugins) {
|
|
210
|
+
const added = [];
|
|
211
|
+
const removed = [];
|
|
212
|
+
const changed = [];
|
|
213
|
+
const allNames = new Set([
|
|
214
|
+
...Object.keys(fromPlugins),
|
|
215
|
+
...Object.keys(toPlugins),
|
|
216
|
+
]);
|
|
217
|
+
for (const name of allNames) {
|
|
218
|
+
const fromV = fromPlugins[name];
|
|
219
|
+
const toV = toPlugins[name];
|
|
220
|
+
if (fromV && !toV) {
|
|
221
|
+
removed.push({ name, version: fromV });
|
|
222
|
+
}
|
|
223
|
+
else if (!fromV && toV) {
|
|
224
|
+
added.push({ name, version: toV });
|
|
225
|
+
}
|
|
226
|
+
else if (fromV && toV && fromV !== toV) {
|
|
227
|
+
changed.push({ name, from: fromV, to: toV });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return { added, removed, changed };
|
|
231
|
+
}
|
|
232
|
+
export async function compareFingerprint(fromBuildId, toArg, verbose = false) {
|
|
233
|
+
try {
|
|
234
|
+
if (!fromBuildId) {
|
|
235
|
+
throw new Error('Missing required --from <build-id> argument');
|
|
236
|
+
}
|
|
237
|
+
const fromFp = await fetchBuildFingerprint(fromBuildId, verbose);
|
|
238
|
+
if (!fromFp) {
|
|
239
|
+
ora().fail(`Build ${fromBuildId} does not have fingerprint data; cannot compare.`);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const toIsLocal = !toArg || toArg === 'local';
|
|
243
|
+
let toLabel;
|
|
244
|
+
let toFp;
|
|
245
|
+
if (toIsLocal) {
|
|
246
|
+
const projectRoot = process.cwd();
|
|
247
|
+
const platform = fromFp.platform || undefined;
|
|
248
|
+
const localSnapshot = computeFingerprint({
|
|
249
|
+
projectRoot,
|
|
250
|
+
platform: platform || undefined,
|
|
251
|
+
});
|
|
252
|
+
toFp = {
|
|
253
|
+
hash: localSnapshot.hash,
|
|
254
|
+
nativePlugins: localSnapshot.nativePlugins,
|
|
255
|
+
appResourcesHash: localSnapshot.appResourcesHash ?? null,
|
|
256
|
+
nativeSourcesHash: localSnapshot.nativeSourcesHash ?? null,
|
|
257
|
+
platform: platform || undefined,
|
|
258
|
+
};
|
|
259
|
+
toLabel = 'local project';
|
|
260
|
+
}
|
|
261
|
+
else {
|
|
262
|
+
toFp = await fetchBuildFingerprint(toArg, verbose);
|
|
263
|
+
if (!toFp) {
|
|
264
|
+
ora().fail(`Build ${toArg} does not have fingerprint data; cannot compare.`);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
toLabel = `build ${toArg}`;
|
|
268
|
+
}
|
|
269
|
+
console.log('\nFingerprint comparison');
|
|
270
|
+
console.log('----------------------');
|
|
271
|
+
console.log(`From: build ${fromBuildId}`);
|
|
272
|
+
console.log(` Platform: ${fromFp.platform || 'unknown'}`);
|
|
273
|
+
console.log(` Hash: ${fromFp.hash}`);
|
|
274
|
+
console.log(`To: ${toLabel}`);
|
|
275
|
+
console.log(` Platform: ${toFp.platform || fromFp.platform || 'unknown'}`);
|
|
276
|
+
console.log(` Hash: ${toFp.hash}`);
|
|
277
|
+
const hashesMatch = fromFp.hash === toFp.hash;
|
|
278
|
+
console.log('\nSummary');
|
|
279
|
+
if (hashesMatch) {
|
|
280
|
+
console.log('- Native fingerprints are identical (hash values match). OTA compatibility is expected.');
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
console.log('- Native fingerprints differ (hash values do not match). OTA between these may require a new store binary.');
|
|
284
|
+
}
|
|
285
|
+
const pluginDiff = diffPluginMaps(fromFp.nativePlugins || {}, toFp.nativePlugins || {});
|
|
286
|
+
console.log('\nNativeScript plugin differences');
|
|
287
|
+
if (pluginDiff.added.length === 0 &&
|
|
288
|
+
pluginDiff.removed.length === 0 &&
|
|
289
|
+
pluginDiff.changed.length === 0) {
|
|
290
|
+
console.log('- No plugin differences detected');
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
if (pluginDiff.added.length) {
|
|
294
|
+
console.log('- Added plugins:');
|
|
295
|
+
for (const p of pluginDiff.added) {
|
|
296
|
+
console.log(` + ${p.name}@${p.version}`);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (pluginDiff.removed.length) {
|
|
300
|
+
console.log('- Removed plugins:');
|
|
301
|
+
for (const p of pluginDiff.removed) {
|
|
302
|
+
console.log(` - ${p.name}@${p.version}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (pluginDiff.changed.length) {
|
|
306
|
+
console.log('- Changed plugins:');
|
|
307
|
+
for (const p of pluginDiff.changed) {
|
|
308
|
+
console.log(` ~ ${p.name}: ${p.from} -> ${p.to}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
console.log('\nApp_Resources hash');
|
|
313
|
+
console.log(`- From: ${fromFp.appResourcesHash || 'none'}`);
|
|
314
|
+
console.log(`- To: ${toFp.appResourcesHash || 'none'}`);
|
|
315
|
+
console.log('\niOS NativeSource hash');
|
|
316
|
+
console.log(`- From: ${fromFp.nativeSourcesHash || 'none'}`);
|
|
317
|
+
console.log(`- To: ${toFp.nativeSourcesHash || 'none'}`);
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
ora().fail(`Failed to compare fingerprints: ${error?.message || error}`);
|
|
321
|
+
if (verbose) {
|
|
322
|
+
console.error('--- Verbose error details (fingerprint-compare) ---');
|
|
323
|
+
console.error(error);
|
|
324
|
+
if (error?.response) {
|
|
325
|
+
console.error('Axios response status:', error.response.status);
|
|
326
|
+
console.error('Axios response data:', error.response.data);
|
|
327
|
+
}
|
|
328
|
+
if (error?.stack) {
|
|
329
|
+
console.error(error.stack);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
136
334
|
/**
|
|
137
335
|
* Get the current NativeScript project's name from package.json
|
|
138
336
|
*/
|
|
@@ -174,6 +372,99 @@ function getNativeScriptAppPath() {
|
|
|
174
372
|
}
|
|
175
373
|
return undefined;
|
|
176
374
|
}
|
|
375
|
+
/**
|
|
376
|
+
* Parse NativeScript app id from nativescript.config.(ts|js).
|
|
377
|
+
* Looks for top-level `id`, then platform-specific `ios.id` / `android.id`.
|
|
378
|
+
*/
|
|
379
|
+
function getNativeScriptAppId(platform) {
|
|
380
|
+
const normalizedPlatform = (platform || '').toLowerCase();
|
|
381
|
+
const candidates = ['nativescript.config.ts', 'nativescript.config.js'];
|
|
382
|
+
try {
|
|
383
|
+
for (const file of candidates) {
|
|
384
|
+
const full = path.join(process.cwd(), file);
|
|
385
|
+
if (!fs.existsSync(full))
|
|
386
|
+
continue;
|
|
387
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
388
|
+
// Prefer platform-specific id if platform is known
|
|
389
|
+
if (normalizedPlatform === 'ios' || normalizedPlatform === 'visionos') {
|
|
390
|
+
const m = content.match(/ios\s*:\s*{[^}]*id\s*:\s*['"`]([^'"`]+)['"`]/s);
|
|
391
|
+
if (m && m[1])
|
|
392
|
+
return m[1].trim();
|
|
393
|
+
}
|
|
394
|
+
else if (normalizedPlatform === 'android') {
|
|
395
|
+
const m = content.match(/android\s*:\s*{[^}]*id\s*:\s*['"`]([^'"`]+)['"`]/s);
|
|
396
|
+
if (m && m[1])
|
|
397
|
+
return m[1].trim();
|
|
398
|
+
}
|
|
399
|
+
// Fallback to top-level id
|
|
400
|
+
const topLevel = content.match(/id\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
401
|
+
if (topLevel && topLevel[1])
|
|
402
|
+
return topLevel[1].trim();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (_) {
|
|
406
|
+
// ignore parse errors and fall back to manual input
|
|
407
|
+
}
|
|
408
|
+
return undefined;
|
|
409
|
+
}
|
|
410
|
+
function getNativeScriptAppResourcesPath() {
|
|
411
|
+
try {
|
|
412
|
+
const candidates = ['nativescript.config.ts', 'nativescript.config.js'];
|
|
413
|
+
for (const file of candidates) {
|
|
414
|
+
const full = path.join(process.cwd(), file);
|
|
415
|
+
if (!fs.existsSync(full))
|
|
416
|
+
continue;
|
|
417
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
418
|
+
const match = content.match(/appResourcesPath\s*:\s*['"`]([^'"`]+)['"`]/);
|
|
419
|
+
if (match && match[1]) {
|
|
420
|
+
return match[1].trim();
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
catch (_) {
|
|
425
|
+
// ignore parse errors and fall back
|
|
426
|
+
}
|
|
427
|
+
return undefined;
|
|
428
|
+
}
|
|
429
|
+
function getAppleVersionFromInfoPlist(platform) {
|
|
430
|
+
const appResourcesPath = getNativeScriptAppResourcesPath();
|
|
431
|
+
if (!appResourcesPath)
|
|
432
|
+
return {};
|
|
433
|
+
const platformDir = platform.toLowerCase() === 'visionos' ? 'visionOS' : 'iOS';
|
|
434
|
+
const plistPath = path.join(process.cwd(), appResourcesPath, platformDir, 'Info.plist');
|
|
435
|
+
if (!fs.existsSync(plistPath))
|
|
436
|
+
return {};
|
|
437
|
+
try {
|
|
438
|
+
const xml = fs.readFileSync(plistPath, 'utf8');
|
|
439
|
+
const shortVersionMatch = xml.match(/<key>CFBundleShortVersionString<\/key>\s*<string>([^<]+)<\/string>/);
|
|
440
|
+
const bundleVersionMatch = xml.match(/<key>CFBundleVersion<\/key>\s*<string>([^<]+)<\/string>/);
|
|
441
|
+
const version = shortVersionMatch?.[1]?.trim();
|
|
442
|
+
const buildNumber = bundleVersionMatch?.[1]?.trim();
|
|
443
|
+
return { version, buildNumber };
|
|
444
|
+
}
|
|
445
|
+
catch (_) {
|
|
446
|
+
return {};
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
function getAndroidVersionFromAppGradle() {
|
|
450
|
+
const appResourcesPath = getNativeScriptAppResourcesPath();
|
|
451
|
+
if (!appResourcesPath)
|
|
452
|
+
return {};
|
|
453
|
+
const gradlePath = path.join(process.cwd(), appResourcesPath, 'Android', 'app.gradle');
|
|
454
|
+
if (!fs.existsSync(gradlePath))
|
|
455
|
+
return {};
|
|
456
|
+
try {
|
|
457
|
+
const content = fs.readFileSync(gradlePath, 'utf8');
|
|
458
|
+
const versionNameMatch = content.match(/versionName\s+"([^"]+)"/);
|
|
459
|
+
const versionCodeMatch = content.match(/versionCode\s+(\d+)/);
|
|
460
|
+
const version = versionNameMatch?.[1]?.trim();
|
|
461
|
+
const buildNumber = versionCodeMatch?.[1]?.trim();
|
|
462
|
+
return { version, buildNumber };
|
|
463
|
+
}
|
|
464
|
+
catch (_) {
|
|
465
|
+
return {};
|
|
466
|
+
}
|
|
467
|
+
}
|
|
177
468
|
/**
|
|
178
469
|
* Creates a zip file of the current directory (NativeScript project)
|
|
179
470
|
*/
|
|
@@ -222,9 +513,7 @@ async function zipProject(projectName, isUpdate = false) {
|
|
|
222
513
|
}
|
|
223
514
|
else {
|
|
224
515
|
console.warn('Warning: app directory not found in the project root');
|
|
225
|
-
const checked = [nsAppDir, srcDir, appDir]
|
|
226
|
-
.filter(Boolean)
|
|
227
|
-
.join(', ');
|
|
516
|
+
const checked = [nsAppDir, srcDir, appDir].filter(Boolean).join(', ');
|
|
228
517
|
console.warn(`Checked locations: ${checked}`);
|
|
229
518
|
console.log('Creating an empty app directory in the zip');
|
|
230
519
|
}
|
|
@@ -246,20 +535,27 @@ async function zipProject(projectName, isUpdate = false) {
|
|
|
246
535
|
* Build command implementation
|
|
247
536
|
* Uploads project to S3 and triggers build via the Next.js API gateway -> WarpBuild
|
|
248
537
|
*/
|
|
249
|
-
export async function build() {
|
|
538
|
+
export async function build(cliPlatformArg, verbose = false) {
|
|
250
539
|
try {
|
|
251
540
|
const spinner = ora('Preparing app for building...').start();
|
|
252
541
|
// 1. Get project info
|
|
253
542
|
const projectName = await getProjectName();
|
|
254
|
-
// 2.
|
|
543
|
+
// 2. Determine platform (CLI arg preferred, otherwise prompt)
|
|
544
|
+
let platform = (cliPlatformArg || '').toLowerCase();
|
|
545
|
+
const validPlatforms = ['android', 'ios', 'visionos'];
|
|
255
546
|
spinner.stop();
|
|
256
|
-
|
|
257
|
-
{
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
547
|
+
if (!validPlatforms.includes(platform)) {
|
|
548
|
+
const { chosenPlatform } = await inquirer.prompt([
|
|
549
|
+
{
|
|
550
|
+
type: 'list',
|
|
551
|
+
name: 'chosenPlatform',
|
|
552
|
+
message: 'Select target platform:',
|
|
553
|
+
choices: validPlatforms,
|
|
554
|
+
},
|
|
555
|
+
]);
|
|
556
|
+
platform = chosenPlatform;
|
|
557
|
+
}
|
|
558
|
+
const { configuration } = await inquirer.prompt([
|
|
263
559
|
{
|
|
264
560
|
type: 'list',
|
|
265
561
|
name: 'configuration',
|
|
@@ -268,32 +564,52 @@ export async function build() {
|
|
|
268
564
|
default: 'debug',
|
|
269
565
|
},
|
|
270
566
|
]);
|
|
567
|
+
const appleVersionInfo = platform === 'ios' || platform === 'visionos'
|
|
568
|
+
? getAppleVersionFromInfoPlist(platform)
|
|
569
|
+
: {};
|
|
570
|
+
const inferredIosOrVisionVersion = appleVersionInfo.version;
|
|
571
|
+
const androidVersionInfo = platform === 'android' ? getAndroidVersionFromAppGradle() : {};
|
|
572
|
+
const inferredAndroidVersion = androidVersionInfo.version;
|
|
573
|
+
const inferredVersion = inferredIosOrVisionVersion || inferredAndroidVersion || '';
|
|
574
|
+
if (inferredVersion) {
|
|
575
|
+
logVerbose(`Auto-detected app version from project files: ${inferredVersion}`, verbose);
|
|
576
|
+
}
|
|
271
577
|
const { version } = await inquirer.prompt([
|
|
272
578
|
{
|
|
273
579
|
type: 'input',
|
|
274
580
|
name: 'version',
|
|
275
|
-
message:
|
|
276
|
-
|
|
581
|
+
message: inferredVersion
|
|
582
|
+
? `App version (${inferredVersion}, enter to accept):`
|
|
583
|
+
: 'App version:',
|
|
584
|
+
default: inferredVersion,
|
|
277
585
|
validate: (input) => {
|
|
278
586
|
const val = String(input).trim();
|
|
279
|
-
if (val === '')
|
|
280
|
-
return
|
|
587
|
+
if (val === '') {
|
|
588
|
+
return 'Version is required';
|
|
589
|
+
}
|
|
281
590
|
// Strict SemVer: MAJOR.MINOR.PATCH with optional -prerelease and +build metadata
|
|
282
591
|
const semverRe = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/;
|
|
283
592
|
return semverRe.test(val)
|
|
284
593
|
? true
|
|
285
|
-
: 'Enter a valid semver (e.g., 2.0.0 or 2.0.0-beta.1)
|
|
594
|
+
: 'Enter a valid semver (e.g., 2.0.0 or 2.0.0-beta.1)';
|
|
286
595
|
},
|
|
287
596
|
},
|
|
288
597
|
]);
|
|
289
598
|
let buildNumber;
|
|
290
599
|
if (platform === 'android') {
|
|
600
|
+
const inferredAndroid = androidVersionInfo;
|
|
601
|
+
const inferredVersionCode = inferredAndroid.buildNumber;
|
|
602
|
+
if (inferredVersionCode) {
|
|
603
|
+
logVerbose(`Auto-detected Android versionCode from app.gradle: ${inferredVersionCode}`, verbose);
|
|
604
|
+
}
|
|
291
605
|
const { versionCode } = await inquirer.prompt([
|
|
292
606
|
{
|
|
293
607
|
type: 'input',
|
|
294
608
|
name: 'versionCode',
|
|
295
|
-
message:
|
|
296
|
-
|
|
609
|
+
message: inferredVersionCode
|
|
610
|
+
? `App build number, aka versionCode (${inferredVersionCode} - enter to auto increment):`
|
|
611
|
+
: 'App build number, aka versionCode (enter to auto increment):',
|
|
612
|
+
default: inferredVersionCode || '',
|
|
297
613
|
validate: (input) => {
|
|
298
614
|
const val = String(input).trim();
|
|
299
615
|
if (val === '')
|
|
@@ -434,6 +750,13 @@ export async function build() {
|
|
|
434
750
|
}
|
|
435
751
|
spinner.start('Creating project archive...');
|
|
436
752
|
}
|
|
753
|
+
// Compute a native-layer fingerprint before archiving
|
|
754
|
+
const projectRoot = process.cwd();
|
|
755
|
+
const fingerprint = computeFingerprint({
|
|
756
|
+
projectRoot,
|
|
757
|
+
platform,
|
|
758
|
+
});
|
|
759
|
+
writeFingerprintFile(projectRoot, fingerprint);
|
|
437
760
|
spinner.start('Creating project archive...');
|
|
438
761
|
// 3. Zip the project
|
|
439
762
|
const zipPath = await zipProject(projectName, false);
|
|
@@ -460,6 +783,7 @@ export async function build() {
|
|
|
460
783
|
version: version || '',
|
|
461
784
|
buildNumber: buildNumber || '',
|
|
462
785
|
configuration,
|
|
786
|
+
fingerprint,
|
|
463
787
|
// Provide the relative key (without public/) – the workflow prepends public/
|
|
464
788
|
s3Key: s3KeyRel,
|
|
465
789
|
// Only include raw credentials if not encrypted
|
|
@@ -483,14 +807,25 @@ export async function build() {
|
|
|
483
807
|
console.log(` You can check the status with: norrix build-status ${buildId}`);
|
|
484
808
|
}
|
|
485
809
|
catch (error) {
|
|
486
|
-
ora().fail(`Build failed: ${error
|
|
810
|
+
ora().fail(`Build failed: ${error?.message || error}`);
|
|
811
|
+
if (verbose) {
|
|
812
|
+
console.error('--- Verbose error details (build) ---');
|
|
813
|
+
console.error(error);
|
|
814
|
+
if (error?.response) {
|
|
815
|
+
console.error('Axios response status:', error.response.status);
|
|
816
|
+
console.error('Axios response data:', error.response.data);
|
|
817
|
+
}
|
|
818
|
+
if (error?.stack) {
|
|
819
|
+
console.error(error.stack);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
487
822
|
}
|
|
488
823
|
}
|
|
489
824
|
/**
|
|
490
825
|
* Submit command implementation
|
|
491
826
|
* Submits the built app to app stores via the Next.js API gateway
|
|
492
827
|
*/
|
|
493
|
-
export async function submit() {
|
|
828
|
+
export async function submit(cliPlatformArg, verbose = false) {
|
|
494
829
|
try {
|
|
495
830
|
const spinner = ora('Preparing app for submission...').start();
|
|
496
831
|
// 1. Get available builds to show in prompt
|
|
@@ -505,11 +840,13 @@ export async function submit() {
|
|
|
505
840
|
// Filter for successful builds
|
|
506
841
|
availableBuilds = buildsResponse.data.builds
|
|
507
842
|
.filter((build) => build.status === 'success')
|
|
508
|
-
.map((build) =>
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
843
|
+
.map((build) => {
|
|
844
|
+
return {
|
|
845
|
+
name: `${build.projectName} (${build.platform} - ${build.version} (${build.buildNumber}) - ${build.id})`,
|
|
846
|
+
value: build.id,
|
|
847
|
+
platform: build.platform,
|
|
848
|
+
};
|
|
849
|
+
});
|
|
513
850
|
}
|
|
514
851
|
}
|
|
515
852
|
catch (error) {
|
|
@@ -541,20 +878,24 @@ export async function submit() {
|
|
|
541
878
|
]);
|
|
542
879
|
buildId = buildIdAnswer.buildId;
|
|
543
880
|
}
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
if (
|
|
549
|
-
|
|
881
|
+
// Determine platform, preferring CLI arg, then build metadata, then prompt
|
|
882
|
+
let platform = (cliPlatformArg || '').toLowerCase();
|
|
883
|
+
const validPlatforms = ['android', 'ios', 'visionos'];
|
|
884
|
+
// Fallback to platform from selected build if CLI arg missing/invalid
|
|
885
|
+
if (!validPlatforms.includes(platform)) {
|
|
886
|
+
const selectedBuild = availableBuilds.find((build) => build.value === buildId);
|
|
887
|
+
if (selectedBuild && validPlatforms.includes(selectedBuild.platform)) {
|
|
888
|
+
platform = selectedBuild.platform;
|
|
889
|
+
}
|
|
550
890
|
}
|
|
551
|
-
|
|
891
|
+
// If still unknown, prompt the user
|
|
892
|
+
if (!validPlatforms.includes(platform)) {
|
|
552
893
|
const platformAnswer = await inquirer.prompt([
|
|
553
894
|
{
|
|
554
895
|
type: 'list',
|
|
555
896
|
name: 'platform',
|
|
556
897
|
message: 'Select target platform:',
|
|
557
|
-
choices:
|
|
898
|
+
choices: validPlatforms,
|
|
558
899
|
},
|
|
559
900
|
]);
|
|
560
901
|
platform = platformAnswer.platform;
|
|
@@ -656,16 +997,51 @@ export async function submit() {
|
|
|
656
997
|
console.log(` You can check the status with: norrix submit-status ${submitId}`);
|
|
657
998
|
}
|
|
658
999
|
catch (error) {
|
|
659
|
-
ora().fail(`Submission failed: ${error
|
|
1000
|
+
ora().fail(`Submission failed: ${error?.message || error}`);
|
|
1001
|
+
if (verbose) {
|
|
1002
|
+
console.error('--- Verbose error details (submit) ---');
|
|
1003
|
+
console.error(error);
|
|
1004
|
+
if (error?.response) {
|
|
1005
|
+
console.error('Axios response status:', error.response.status);
|
|
1006
|
+
console.error('Axios response data:', error.response.data);
|
|
1007
|
+
}
|
|
1008
|
+
if (error?.stack) {
|
|
1009
|
+
console.error(error.stack);
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
660
1012
|
}
|
|
661
1013
|
}
|
|
662
1014
|
/**
|
|
663
1015
|
* Update command implementation
|
|
664
1016
|
* Publishes over-the-air updates to deployed apps via the Next.js API gateway
|
|
665
1017
|
*/
|
|
666
|
-
export async function update() {
|
|
1018
|
+
export async function update(cliPlatformArg, verbose = false) {
|
|
667
1019
|
try {
|
|
668
1020
|
const spinner = ora('Preparing over-the-air update...').start();
|
|
1021
|
+
// Normalize and/or ask for platform first (CLI arg takes precedence if valid)
|
|
1022
|
+
let platform = (cliPlatformArg || '').toLowerCase();
|
|
1023
|
+
const validPlatforms = ['android', 'ios', 'visionos'];
|
|
1024
|
+
if (!validPlatforms.includes(platform)) {
|
|
1025
|
+
spinner.stop();
|
|
1026
|
+
const { chosenPlatform } = await inquirer.prompt([
|
|
1027
|
+
{
|
|
1028
|
+
type: 'list',
|
|
1029
|
+
name: 'chosenPlatform',
|
|
1030
|
+
message: 'Which platform?',
|
|
1031
|
+
choices: validPlatforms,
|
|
1032
|
+
},
|
|
1033
|
+
]);
|
|
1034
|
+
platform = chosenPlatform;
|
|
1035
|
+
spinner.start('Preparing over-the-air update...');
|
|
1036
|
+
}
|
|
1037
|
+
// Attempt to infer app ID from NativeScript config based on platform
|
|
1038
|
+
let inferredAppId;
|
|
1039
|
+
try {
|
|
1040
|
+
inferredAppId = getNativeScriptAppId(platform);
|
|
1041
|
+
}
|
|
1042
|
+
catch (_) {
|
|
1043
|
+
// ignore, will fall back to prompt without default
|
|
1044
|
+
}
|
|
669
1045
|
// Ask for app ID and other details
|
|
670
1046
|
spinner.stop();
|
|
671
1047
|
const { appId, version, notes } = await inquirer.prompt([
|
|
@@ -673,6 +1049,7 @@ export async function update() {
|
|
|
673
1049
|
type: 'input',
|
|
674
1050
|
name: 'appId',
|
|
675
1051
|
message: 'Enter the App ID to update:',
|
|
1052
|
+
default: inferredAppId || '',
|
|
676
1053
|
validate: (input) => input.length > 0 || 'App ID is required',
|
|
677
1054
|
},
|
|
678
1055
|
{
|
|
@@ -701,6 +1078,15 @@ export async function update() {
|
|
|
701
1078
|
spinner.warn('Warning: app directory not found in the project. The update may be incomplete.');
|
|
702
1079
|
spinner.warn('Expected app at either:\n- ' + appDir + '\n- ' + srcAppDir);
|
|
703
1080
|
}
|
|
1081
|
+
// Compute fingerprint for the current app state so the backend can
|
|
1082
|
+
// compare it with the binary fingerprint and decide if OTA is allowed.
|
|
1083
|
+
const projectRoot = process.cwd();
|
|
1084
|
+
const fingerprint = computeFingerprint({
|
|
1085
|
+
projectRoot,
|
|
1086
|
+
platform,
|
|
1087
|
+
appId,
|
|
1088
|
+
});
|
|
1089
|
+
writeFingerprintFile(projectRoot, fingerprint);
|
|
704
1090
|
spinner.start('Packaging JavaScript bundle for over-the-air update...');
|
|
705
1091
|
// Create the update bundle - pass true to include node_modules for updates
|
|
706
1092
|
const projectName = await getProjectName();
|
|
@@ -714,8 +1100,10 @@ export async function update() {
|
|
|
714
1100
|
spinner.text = 'Upload complete. Starting update...';
|
|
715
1101
|
const response = await axios.post(`${API_URL}/update`, {
|
|
716
1102
|
appId,
|
|
1103
|
+
platform,
|
|
717
1104
|
version,
|
|
718
1105
|
releaseNotes: notes,
|
|
1106
|
+
fingerprint,
|
|
719
1107
|
// Provide the relative key (without public/). Consumers will prepend public/
|
|
720
1108
|
s3Key: s3KeyRel,
|
|
721
1109
|
}, {
|
|
@@ -733,14 +1121,25 @@ export async function update() {
|
|
|
733
1121
|
console.log(` You can check the status with: norrix update-status ${updateId}`);
|
|
734
1122
|
}
|
|
735
1123
|
catch (error) {
|
|
736
|
-
ora().fail(`Update failed: ${error
|
|
1124
|
+
ora().fail(`Update failed: ${error?.message || error}`);
|
|
1125
|
+
if (verbose) {
|
|
1126
|
+
console.error('--- Verbose error details (update) ---');
|
|
1127
|
+
console.error(error);
|
|
1128
|
+
if (error?.response) {
|
|
1129
|
+
console.error('Axios response status:', error.response.status);
|
|
1130
|
+
console.error('Axios response data:', error.response.data);
|
|
1131
|
+
}
|
|
1132
|
+
if (error?.stack) {
|
|
1133
|
+
console.error(error.stack);
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
737
1136
|
}
|
|
738
1137
|
}
|
|
739
1138
|
/**
|
|
740
1139
|
* Build Status command implementation
|
|
741
1140
|
* Checks the status of a build via the Next.js API gateway
|
|
742
1141
|
*/
|
|
743
|
-
export async function buildStatus(buildId) {
|
|
1142
|
+
export async function buildStatus(buildId, verbose = false) {
|
|
744
1143
|
try {
|
|
745
1144
|
const spinner = ora(`Checking status of build ${buildId}...`).start();
|
|
746
1145
|
const response = await axios.get(`${API_URL}/build/${buildId}`, {
|
|
@@ -759,14 +1158,25 @@ export async function buildStatus(buildId) {
|
|
|
759
1158
|
}
|
|
760
1159
|
}
|
|
761
1160
|
catch (error) {
|
|
762
|
-
ora().fail(`Failed to check build status: ${error
|
|
1161
|
+
ora().fail(`Failed to check build status: ${error?.message || error}`);
|
|
1162
|
+
if (verbose) {
|
|
1163
|
+
console.error('--- Verbose error details (build-status) ---');
|
|
1164
|
+
console.error(error);
|
|
1165
|
+
if (error?.response) {
|
|
1166
|
+
console.error('Axios response status:', error.response.status);
|
|
1167
|
+
console.error('Axios response data:', error.response.data);
|
|
1168
|
+
}
|
|
1169
|
+
if (error?.stack) {
|
|
1170
|
+
console.error(error.stack);
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
763
1173
|
}
|
|
764
1174
|
}
|
|
765
1175
|
/**
|
|
766
1176
|
* Submit Status command implementation
|
|
767
1177
|
* Checks the status of a submission via the Next.js API gateway
|
|
768
1178
|
*/
|
|
769
|
-
export async function submitStatus(submitId) {
|
|
1179
|
+
export async function submitStatus(submitId, verbose = false) {
|
|
770
1180
|
try {
|
|
771
1181
|
const spinner = ora(`Checking status of submission ${submitId}...`).start();
|
|
772
1182
|
const response = await axios.get(`${API_URL}/submit/${submitId}`, {
|
|
@@ -781,14 +1191,25 @@ export async function submitStatus(submitId) {
|
|
|
781
1191
|
console.log(`Message: ${response.data.message}`);
|
|
782
1192
|
}
|
|
783
1193
|
catch (error) {
|
|
784
|
-
ora().fail(`Failed to check submission status: ${error
|
|
1194
|
+
ora().fail(`Failed to check submission status: ${error?.message || error}`);
|
|
1195
|
+
if (verbose) {
|
|
1196
|
+
console.error('--- Verbose error details (submit-status) ---');
|
|
1197
|
+
console.error(error);
|
|
1198
|
+
if (error?.response) {
|
|
1199
|
+
console.error('Axios response status:', error.response.status);
|
|
1200
|
+
console.error('Axios response data:', error.response.data);
|
|
1201
|
+
}
|
|
1202
|
+
if (error?.stack) {
|
|
1203
|
+
console.error(error.stack);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
785
1206
|
}
|
|
786
1207
|
}
|
|
787
1208
|
/**
|
|
788
1209
|
* Update Status command implementation
|
|
789
1210
|
* Checks the status of an update via the Next.js API gateway
|
|
790
1211
|
*/
|
|
791
|
-
export async function updateStatus(updateId) {
|
|
1212
|
+
export async function updateStatus(updateId, verbose = false) {
|
|
792
1213
|
try {
|
|
793
1214
|
const spinner = ora(`Checking status of update ${updateId}...`).start();
|
|
794
1215
|
const response = await axios.get(`${API_URL}/update/${updateId}`, {
|
|
@@ -803,13 +1224,24 @@ export async function updateStatus(updateId) {
|
|
|
803
1224
|
console.log(`Message: ${response.data.message}`);
|
|
804
1225
|
}
|
|
805
1226
|
catch (error) {
|
|
806
|
-
ora().fail(`Failed to check update status: ${error
|
|
1227
|
+
ora().fail(`Failed to check update status: ${error?.message || error}`);
|
|
1228
|
+
if (verbose) {
|
|
1229
|
+
console.error('--- Verbose error details (update-status) ---');
|
|
1230
|
+
console.error(error);
|
|
1231
|
+
if (error?.response) {
|
|
1232
|
+
console.error('Axios response status:', error.response.status);
|
|
1233
|
+
console.error('Axios response data:', error.response.data);
|
|
1234
|
+
}
|
|
1235
|
+
if (error?.stack) {
|
|
1236
|
+
console.error(error.stack);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
807
1239
|
}
|
|
808
1240
|
}
|
|
809
1241
|
/**
|
|
810
1242
|
* Sign-In command implementation (email / password via Cognito)
|
|
811
1243
|
*/
|
|
812
|
-
export async function signIn() {
|
|
1244
|
+
export async function signIn(verbose = false) {
|
|
813
1245
|
try {
|
|
814
1246
|
const answers = await inquirer.prompt([
|
|
815
1247
|
{
|
|
@@ -839,26 +1271,48 @@ export async function signIn() {
|
|
|
839
1271
|
spinner.succeed(`Signed in as ${answers.email}`);
|
|
840
1272
|
}
|
|
841
1273
|
catch (error) {
|
|
842
|
-
ora().fail(`Sign-in failed: ${error
|
|
1274
|
+
ora().fail(`Sign-in failed: ${error?.message || error}`);
|
|
1275
|
+
if (verbose) {
|
|
1276
|
+
console.error('--- Verbose error details (sign-in) ---');
|
|
1277
|
+
console.error(error);
|
|
1278
|
+
if (error?.response) {
|
|
1279
|
+
console.error('Axios response status:', error.response.status);
|
|
1280
|
+
console.error('Axios response data:', error.response.data);
|
|
1281
|
+
}
|
|
1282
|
+
if (error?.stack) {
|
|
1283
|
+
console.error(error.stack);
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
843
1286
|
}
|
|
844
1287
|
}
|
|
845
1288
|
/**
|
|
846
1289
|
* Sign-Out command implementation
|
|
847
1290
|
*/
|
|
848
|
-
export async function signOut() {
|
|
1291
|
+
export async function signOut(verbose = false) {
|
|
849
1292
|
try {
|
|
850
1293
|
const spinner = ora('Signing out...').start();
|
|
851
1294
|
await amplifySignOut();
|
|
852
1295
|
spinner.succeed('Signed out successfully');
|
|
853
1296
|
}
|
|
854
1297
|
catch (error) {
|
|
855
|
-
ora().fail(`Sign-out failed: ${error
|
|
1298
|
+
ora().fail(`Sign-out failed: ${error?.message || error}`);
|
|
1299
|
+
if (verbose) {
|
|
1300
|
+
console.error('--- Verbose error details (sign-out) ---');
|
|
1301
|
+
console.error(error);
|
|
1302
|
+
if (error?.response) {
|
|
1303
|
+
console.error('Axios response status:', error.response.status);
|
|
1304
|
+
console.error('Axios response data:', error.response.data);
|
|
1305
|
+
}
|
|
1306
|
+
if (error?.stack) {
|
|
1307
|
+
console.error(error.stack);
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
856
1310
|
}
|
|
857
1311
|
}
|
|
858
1312
|
/**
|
|
859
1313
|
* Upload a file to S3 via Amplify Storage
|
|
860
1314
|
*/
|
|
861
|
-
export async function uploadFile(filePath, options) {
|
|
1315
|
+
export async function uploadFile(filePath, options, verbose = false) {
|
|
862
1316
|
try {
|
|
863
1317
|
const resolvedPath = path.resolve(process.cwd(), filePath);
|
|
864
1318
|
if (!fs.existsSync(resolvedPath)) {
|
|
@@ -871,13 +1325,24 @@ export async function uploadFile(filePath, options) {
|
|
|
871
1325
|
spinner.succeed(`Uploaded ${key} successfully`);
|
|
872
1326
|
}
|
|
873
1327
|
catch (error) {
|
|
874
|
-
ora().fail(`Upload failed: ${error
|
|
1328
|
+
ora().fail(`Upload failed: ${error?.message || error}`);
|
|
1329
|
+
if (verbose) {
|
|
1330
|
+
console.error('--- Verbose error details (upload) ---');
|
|
1331
|
+
console.error(error);
|
|
1332
|
+
if (error?.response) {
|
|
1333
|
+
console.error('Axios response status:', error.response.status);
|
|
1334
|
+
console.error('Axios response data:', error.response.data);
|
|
1335
|
+
}
|
|
1336
|
+
if (error?.stack) {
|
|
1337
|
+
console.error(error.stack);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
875
1340
|
}
|
|
876
1341
|
}
|
|
877
1342
|
/**
|
|
878
1343
|
* Current User command implementation
|
|
879
1344
|
*/
|
|
880
|
-
export async function currentUser() {
|
|
1345
|
+
export async function currentUser(verbose = false) {
|
|
881
1346
|
try {
|
|
882
1347
|
const spinner = ora('Fetching current user...').start();
|
|
883
1348
|
let user;
|
|
@@ -911,7 +1376,76 @@ export async function currentUser() {
|
|
|
911
1376
|
console.log('⚠️ No user is currently signed in.');
|
|
912
1377
|
return;
|
|
913
1378
|
}
|
|
914
|
-
ora().fail(`Failed to fetch current user: ${error
|
|
1379
|
+
ora().fail(`Failed to fetch current user: ${error?.message || error}`);
|
|
1380
|
+
if (verbose) {
|
|
1381
|
+
console.error('--- Verbose error details (current-user) ---');
|
|
1382
|
+
console.error(error);
|
|
1383
|
+
if (error?.response) {
|
|
1384
|
+
console.error('Axios response status:', error.response.status);
|
|
1385
|
+
console.error('Axios response data:', error.response.data);
|
|
1386
|
+
}
|
|
1387
|
+
if (error?.stack) {
|
|
1388
|
+
console.error(error.stack);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Open billing checkout for the current organization
|
|
1395
|
+
*/
|
|
1396
|
+
export async function billingCheckout(priceId, verbose = false) {
|
|
1397
|
+
try {
|
|
1398
|
+
if (!priceId) {
|
|
1399
|
+
const a = await inquirer.prompt([
|
|
1400
|
+
{ type: 'input', name: 'priceId', message: 'Stripe Price ID:' },
|
|
1401
|
+
]);
|
|
1402
|
+
priceId = a.priceId;
|
|
1403
|
+
}
|
|
1404
|
+
const spinner = ora('Creating checkout session...').start();
|
|
1405
|
+
const res = await axios.post(`${API_URL}/billing/checkout`, { priceId }, { headers: await getAuthHeaders() });
|
|
1406
|
+
spinner.succeed('Checkout session created');
|
|
1407
|
+
console.log('Open this URL in your browser to complete purchase:');
|
|
1408
|
+
console.log(res.data.url);
|
|
1409
|
+
}
|
|
1410
|
+
catch (error) {
|
|
1411
|
+
ora().fail(`Checkout failed: ${error?.message || error}`);
|
|
1412
|
+
if (verbose) {
|
|
1413
|
+
console.error('--- Verbose error details (billing-checkout) ---');
|
|
1414
|
+
console.error(error);
|
|
1415
|
+
if (error?.response) {
|
|
1416
|
+
console.error('Axios response status:', error.response.status);
|
|
1417
|
+
console.error('Axios response data:', error.response.data);
|
|
1418
|
+
}
|
|
1419
|
+
if (error?.stack) {
|
|
1420
|
+
console.error(error.stack);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Open Stripe billing portal for the current organization
|
|
1427
|
+
*/
|
|
1428
|
+
export async function billingPortal(verbose = false) {
|
|
1429
|
+
try {
|
|
1430
|
+
const spinner = ora('Creating billing portal session...').start();
|
|
1431
|
+
const res = await axios.post(`${API_URL}/billing/portal`, {}, { headers: await getAuthHeaders() });
|
|
1432
|
+
spinner.succeed('Portal session created');
|
|
1433
|
+
console.log('Open this URL in your browser to manage billing:');
|
|
1434
|
+
console.log(res.data.url);
|
|
1435
|
+
}
|
|
1436
|
+
catch (error) {
|
|
1437
|
+
ora().fail(`Portal failed: ${error?.message || error}`);
|
|
1438
|
+
if (verbose) {
|
|
1439
|
+
console.error('--- Verbose error details (billing-portal) ---');
|
|
1440
|
+
console.error(error);
|
|
1441
|
+
if (error?.response) {
|
|
1442
|
+
console.error('Axios response status:', error.response.status);
|
|
1443
|
+
console.error('Axios response data:', error.response.data);
|
|
1444
|
+
}
|
|
1445
|
+
if (error?.stack) {
|
|
1446
|
+
console.error(error.stack);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
915
1449
|
}
|
|
916
1450
|
}
|
|
917
1451
|
//# sourceMappingURL=commands.js.map
|