@norrix/cli 0.0.40 → 0.0.42
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 +1 -1
- package/README.md +10 -10
- package/dist/cli.js +13 -7
- package/dist/cli.js.map +1 -1
- package/dist/lib/amplify-config.js +3 -1
- package/dist/lib/amplify-config.js.map +1 -1
- package/dist/lib/cli-settings.js +5 -1
- package/dist/lib/cli-settings.js.map +1 -1
- package/dist/lib/commands.d.ts +4 -4
- package/dist/lib/commands.js +112 -73
- package/dist/lib/commands.js.map +1 -1
- package/dist/lib/config.js +6 -2
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/defaults.js +1 -0
- package/dist/lib/dev-defaults.js +1 -0
- package/dist/lib/fingerprinting.d.ts +11 -5
- package/dist/lib/fingerprinting.js +63 -37
- package/dist/lib/fingerprinting.js.map +1 -1
- package/dist/lib/prod-defaults.js +1 -0
- package/dist/lib/workspace.js +10 -5
- package/dist/lib/workspace.js.map +1 -1
- package/package.json +1 -1
package/dist/lib/commands.js
CHANGED
|
@@ -8,15 +8,15 @@ import { fileURLToPath } from 'url';
|
|
|
8
8
|
import archiver from 'archiver';
|
|
9
9
|
// import FormData from 'form-data';
|
|
10
10
|
import { configureAmplify, loadCliEnvFiles } from './amplify-config.js';
|
|
11
|
-
import { computeFingerprint, embedFingerprintInNativeResources, embedVersionInNativeResources, embedNxConfigurationInNativeResources } from './fingerprinting.js';
|
|
12
|
-
import { loadNorrixConfig, hasNorrixConfig, saveNorrixConfig } from './config.js';
|
|
11
|
+
import { computeFingerprint, embedFingerprintInNativeResources, embedVersionInNativeResources, embedNxConfigurationInNativeResources, } from './fingerprinting.js';
|
|
12
|
+
import { loadNorrixConfig, hasNorrixConfig, saveNorrixConfig, } from './config.js';
|
|
13
13
|
import { detectWorkspaceContext, getNxProjectDependencies, getWorkspaceDependenciesFallback, createWorkspaceManifest, logWorkspaceContext, isAtWorkspaceRoot, discoverNativeScriptApps, getWorkspaceContextForApp, detectNxBuildConfigurations, } from './workspace.js';
|
|
14
14
|
import { signIn as amplifySignIn, signOut as amplifySignOut, getCurrentUser, fetchAuthSession, } from 'aws-amplify/auth';
|
|
15
15
|
import crypto from 'crypto';
|
|
16
16
|
import { Amplify } from 'aws-amplify';
|
|
17
17
|
import { PROD_DEFAULTS } from './prod-defaults.js';
|
|
18
18
|
import { DEV_DEFAULTS } from './dev-defaults.js';
|
|
19
|
-
import { clearSelectedOrgId, getSelectedOrgId, setSelectedOrgId } from './cli-settings.js';
|
|
19
|
+
import { clearSelectedOrgId, getSelectedOrgId, setSelectedOrgId, } from './cli-settings.js';
|
|
20
20
|
let CURRENT_ENV = 'prod';
|
|
21
21
|
let CURRENT_DEFAULTS = PROD_DEFAULTS;
|
|
22
22
|
let API_URL = PROD_DEFAULTS.apiUrl;
|
|
@@ -159,7 +159,9 @@ async function ensureOrgSelected(params) {
|
|
|
159
159
|
const { organizations } = await fetchOrganizations(Boolean(params.verbose));
|
|
160
160
|
const normalizedOrgs = organizations.filter((o) => o && o.id);
|
|
161
161
|
// If forcePrompt is NOT set, use stored selection if valid
|
|
162
|
-
if (!params.forcePrompt &&
|
|
162
|
+
if (!params.forcePrompt &&
|
|
163
|
+
stored &&
|
|
164
|
+
normalizedOrgs.some((o) => o.id === stored)) {
|
|
163
165
|
setCurrentOrgId(stored);
|
|
164
166
|
return stored;
|
|
165
167
|
}
|
|
@@ -1277,7 +1279,8 @@ async function zipWorkspaceProject(projectName, workspaceCtx, isUpdate = false,
|
|
|
1277
1279
|
// 4. Add tools directory if it exists and is referenced
|
|
1278
1280
|
for (const toolPath of deps.toolPaths) {
|
|
1279
1281
|
const absoluteToolPath = path.join(workspaceCtx.workspaceRoot, toolPath);
|
|
1280
|
-
if (fs.existsSync(absoluteToolPath) &&
|
|
1282
|
+
if (fs.existsSync(absoluteToolPath) &&
|
|
1283
|
+
fs.statSync(absoluteToolPath).isDirectory()) {
|
|
1281
1284
|
console.log(`Adding tools: ${toolPath}`);
|
|
1282
1285
|
archive.directory(absoluteToolPath, toolPath, (entry) => {
|
|
1283
1286
|
if (entry.name.includes('node_modules')) {
|
|
@@ -1290,7 +1293,8 @@ async function zipWorkspaceProject(projectName, workspaceCtx, isUpdate = false,
|
|
|
1290
1293
|
// 5. Add asset paths if they exist
|
|
1291
1294
|
for (const assetPath of deps.assetPaths) {
|
|
1292
1295
|
const absoluteAssetPath = path.join(workspaceCtx.workspaceRoot, assetPath);
|
|
1293
|
-
if (fs.existsSync(absoluteAssetPath) &&
|
|
1296
|
+
if (fs.existsSync(absoluteAssetPath) &&
|
|
1297
|
+
fs.statSync(absoluteAssetPath).isDirectory()) {
|
|
1294
1298
|
if (verbose) {
|
|
1295
1299
|
console.log(`Adding assets: ${assetPath}`);
|
|
1296
1300
|
}
|
|
@@ -1302,7 +1306,7 @@ async function zipWorkspaceProject(projectName, workspaceCtx, isUpdate = false,
|
|
|
1302
1306
|
if (deps.localFileDeps && deps.localFileDeps.length > 0) {
|
|
1303
1307
|
const addedDirs = new Set();
|
|
1304
1308
|
// Filter out local deps that are subdirectories of already-added libs
|
|
1305
|
-
const filteredLocalDeps = deps.localFileDeps.filter(localDep => {
|
|
1309
|
+
const filteredLocalDeps = deps.localFileDeps.filter((localDep) => {
|
|
1306
1310
|
// Check if this local dep is inside any of the lib paths
|
|
1307
1311
|
for (const libPath of deps.libPaths) {
|
|
1308
1312
|
if (localDep.startsWith(libPath + '/') || localDep === libPath) {
|
|
@@ -1335,7 +1339,8 @@ async function zipWorkspaceProject(projectName, workspaceCtx, isUpdate = false,
|
|
|
1335
1339
|
}
|
|
1336
1340
|
else if (stat.isDirectory()) {
|
|
1337
1341
|
// Skip if this directory or a parent is already in libPaths
|
|
1338
|
-
const alreadyCovered = deps.libPaths.some(libPath => localDep.startsWith(libPath + '/') ||
|
|
1342
|
+
const alreadyCovered = deps.libPaths.some((libPath) => localDep.startsWith(libPath + '/') ||
|
|
1343
|
+
libPath.startsWith(localDep + '/'));
|
|
1339
1344
|
if (alreadyCovered) {
|
|
1340
1345
|
if (verbose) {
|
|
1341
1346
|
console.log(` - ${localDep} (skipped, overlaps with libPaths)`);
|
|
@@ -1369,7 +1374,7 @@ async function zipWorkspaceProject(projectName, workspaceCtx, isUpdate = false,
|
|
|
1369
1374
|
export async function build(cliPlatformArg, cliConfigurationArg, cliDistributionArg, verbose = false, options // string for backwards compatibility with old projectArg
|
|
1370
1375
|
) {
|
|
1371
1376
|
// Normalize options - support both new object and legacy string projectArg
|
|
1372
|
-
const opts = typeof options === 'string' ? { project: options } :
|
|
1377
|
+
const opts = typeof options === 'string' ? { project: options } : options || {};
|
|
1373
1378
|
ensureInitialized();
|
|
1374
1379
|
try {
|
|
1375
1380
|
await ensureOrgSelected({
|
|
@@ -1407,9 +1412,11 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
1407
1412
|
logWorkspaceContext(workspaceCtx, verbose);
|
|
1408
1413
|
}
|
|
1409
1414
|
// 1. Get project info - use workspaceCtx.projectName for Nx workspaces (from project.json)
|
|
1410
|
-
const projectName = workspaceCtx.projectName || await getProjectName();
|
|
1415
|
+
const projectName = workspaceCtx.projectName || (await getProjectName());
|
|
1411
1416
|
// 2. Determine platform (CLI arg preferred, then config, otherwise prompt)
|
|
1412
|
-
let platform = (cliPlatformArg ||
|
|
1417
|
+
let platform = (cliPlatformArg ||
|
|
1418
|
+
norrixConfig.defaultPlatform ||
|
|
1419
|
+
'').toLowerCase();
|
|
1413
1420
|
const validPlatforms = ['android', 'ios', 'visionos'];
|
|
1414
1421
|
spinner.stop();
|
|
1415
1422
|
if (!validPlatforms.includes(platform)) {
|
|
@@ -1428,7 +1435,9 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
1428
1435
|
}
|
|
1429
1436
|
// 2.1 Determine configuration (CLI arg preferred, then config, otherwise prompt)
|
|
1430
1437
|
const validConfigurations = ['debug', 'release'];
|
|
1431
|
-
let configuration = (cliConfigurationArg ||
|
|
1438
|
+
let configuration = (cliConfigurationArg ||
|
|
1439
|
+
norrixConfig.defaultConfiguration ||
|
|
1440
|
+
'').toLowerCase();
|
|
1432
1441
|
if (!validConfigurations.includes(configuration)) {
|
|
1433
1442
|
if (opts.nonInteractive) {
|
|
1434
1443
|
// Default to 'debug' in non-interactive mode if not specified
|
|
@@ -1448,9 +1457,11 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
1448
1457
|
configuration = answer.configuration;
|
|
1449
1458
|
}
|
|
1450
1459
|
}
|
|
1451
|
-
// 2.2 Determine
|
|
1452
|
-
let
|
|
1453
|
-
if (!
|
|
1460
|
+
// 2.2 Determine configuration for workspace builds (CLI arg preferred, then prompt if available)
|
|
1461
|
+
let configurationTarget = opts.configuration;
|
|
1462
|
+
if (!configurationTarget &&
|
|
1463
|
+
workspaceCtx.type === 'nx' &&
|
|
1464
|
+
!opts.nonInteractive) {
|
|
1454
1465
|
const nxConfigs = detectNxBuildConfigurations(workspaceCtx.appRoot);
|
|
1455
1466
|
if (nxConfigs && nxConfigs.configurations.length > 0) {
|
|
1456
1467
|
const choices = nxConfigs.configurations.map((c) => ({
|
|
@@ -1459,20 +1470,20 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
1459
1470
|
}));
|
|
1460
1471
|
// Add option to skip/use default
|
|
1461
1472
|
choices.unshift({ name: '(none - use defaults)', value: '' });
|
|
1462
|
-
const {
|
|
1473
|
+
const { chosenConfig } = await inquirer.prompt([
|
|
1463
1474
|
{
|
|
1464
1475
|
type: 'list',
|
|
1465
|
-
name: '
|
|
1466
|
-
message: '
|
|
1476
|
+
name: 'chosenConfig',
|
|
1477
|
+
message: 'Configuration (deployment target):',
|
|
1467
1478
|
choices,
|
|
1468
1479
|
default: nxConfigs.defaultConfiguration || '',
|
|
1469
1480
|
},
|
|
1470
1481
|
]);
|
|
1471
|
-
|
|
1482
|
+
configurationTarget = chosenConfig || undefined;
|
|
1472
1483
|
}
|
|
1473
1484
|
}
|
|
1474
|
-
// Store resolved
|
|
1475
|
-
opts.
|
|
1485
|
+
// Store resolved configuration back to opts for later use
|
|
1486
|
+
opts.configuration = configurationTarget;
|
|
1476
1487
|
const normalizeIosDistribution = (input) => {
|
|
1477
1488
|
const v = String(input ?? '')
|
|
1478
1489
|
.trim()
|
|
@@ -1799,11 +1810,18 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
1799
1810
|
// Resolve values from: CLI flags > config file > environment variables
|
|
1800
1811
|
const configKeystorePath = norrixConfig.android?.keystorePath;
|
|
1801
1812
|
const configKeyAlias = norrixConfig.android?.keyAlias;
|
|
1802
|
-
const resolvedKeystorePath = trimString(opts.keystorePath) ||
|
|
1813
|
+
const resolvedKeystorePath = trimString(opts.keystorePath) ||
|
|
1814
|
+
configKeystorePath ||
|
|
1815
|
+
process.env.KEYSTORE_PATH;
|
|
1803
1816
|
const resolvedKeystorePassword = trimString(opts.keystorePassword) || process.env.KEYSTORE_PASSWORD;
|
|
1804
|
-
const resolvedKeyAlias = trimString(opts.keyAlias) ||
|
|
1805
|
-
|
|
1806
|
-
|
|
1817
|
+
const resolvedKeyAlias = trimString(opts.keyAlias) ||
|
|
1818
|
+
configKeyAlias ||
|
|
1819
|
+
process.env.KEYSTORE_ALIAS;
|
|
1820
|
+
const resolvedKeyPassword = trimString(opts.keyPassword) ||
|
|
1821
|
+
process.env.KEYSTORE_ALIAS_PASSWORD ||
|
|
1822
|
+
process.env.KEY_PASSWORD;
|
|
1823
|
+
const resolvedPlayJsonPath = trimString(opts.playJsonPath) ||
|
|
1824
|
+
process.env.PLAY_SERVICE_ACCOUNT_JSON_PATH;
|
|
1807
1825
|
if (opts.nonInteractive) {
|
|
1808
1826
|
// Non-interactive mode: use CLI flags, config, and env vars only
|
|
1809
1827
|
// All values are optional - the cloud build will use org-level credentials if not provided
|
|
@@ -1979,17 +1997,17 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
1979
1997
|
}
|
|
1980
1998
|
}
|
|
1981
1999
|
}
|
|
1982
|
-
// Embed
|
|
2000
|
+
// Embed configuration in native resources (Info.plist for iOS, app.gradle for Android)
|
|
1983
2001
|
// This ensures the client SDK can match OTA updates to the correct environment (stg, prod, etc.)
|
|
1984
|
-
// Apps with the same bundle ID but different
|
|
1985
|
-
if (opts.
|
|
1986
|
-
const
|
|
2002
|
+
// Apps with the same bundle ID but different configurations will receive different OTA updates.
|
|
2003
|
+
if (opts.configuration) {
|
|
2004
|
+
const configEmbedPaths = embedNxConfigurationInNativeResources(projectRoot, opts.configuration, platform);
|
|
1987
2005
|
if (verbose) {
|
|
1988
|
-
if (
|
|
1989
|
-
console.log(`[
|
|
2006
|
+
if (configEmbedPaths.infoPlistPath) {
|
|
2007
|
+
console.log(`[configuration] Embedded '${opts.configuration}' in Info.plist: ${configEmbedPaths.infoPlistPath}`);
|
|
1990
2008
|
}
|
|
1991
|
-
if (
|
|
1992
|
-
console.log(`[
|
|
2009
|
+
if (configEmbedPaths.appGradlePath) {
|
|
2010
|
+
console.log(`[configuration] Embedded '${opts.configuration}' in app.gradle: ${configEmbedPaths.appGradlePath}`);
|
|
1993
2011
|
}
|
|
1994
2012
|
}
|
|
1995
2013
|
}
|
|
@@ -2011,7 +2029,9 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
2011
2029
|
// Returns the actual S3 key used (may differ when using presigned URLs)
|
|
2012
2030
|
const actualS3Key = await putObjectToStorage(defaultS3Key, fileBuffer, projectName, platform, 'build', verbose);
|
|
2013
2031
|
// Extract relative key (without public/ prefix if present) for the API
|
|
2014
|
-
const s3KeyRel = actualS3Key.startsWith('public/')
|
|
2032
|
+
const s3KeyRel = actualS3Key.startsWith('public/')
|
|
2033
|
+
? actualS3Key.slice(7)
|
|
2034
|
+
: actualS3Key;
|
|
2015
2035
|
spinner.text = 'Upload complete. Starting build...';
|
|
2016
2036
|
// 5. Call the Next.js API with the S3 key and build parameters
|
|
2017
2037
|
// 5. Call the Next.js API with the S3 key, build parameters, and signing data (release only)
|
|
@@ -2031,15 +2051,17 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
2031
2051
|
inferredAppId = undefined;
|
|
2032
2052
|
}
|
|
2033
2053
|
// Include workspace info for CI to properly navigate the project structure
|
|
2034
|
-
const workspaceInfo = workspaceContext.type === 'nx'
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2054
|
+
const workspaceInfo = workspaceContext.type === 'nx'
|
|
2055
|
+
? {
|
|
2056
|
+
workspaceType: workspaceContext.type,
|
|
2057
|
+
appPath: workspaceContext.relativeAppPath,
|
|
2058
|
+
projectName: workspaceContext.projectName,
|
|
2059
|
+
}
|
|
2060
|
+
: undefined;
|
|
2039
2061
|
// For standalone projects, use the project option or infer from app ID/package name
|
|
2040
2062
|
// This allows env vars to be scoped to specific standalone projects within an org
|
|
2041
2063
|
const standaloneProjectName = workspaceContext.type === 'standalone'
|
|
2042
|
-
?
|
|
2064
|
+
? opts.project || inferredAppId || projectName
|
|
2043
2065
|
: undefined;
|
|
2044
2066
|
const response = await axios.post(`${API_URL}/build`, {
|
|
2045
2067
|
projectName,
|
|
@@ -2049,19 +2071,23 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
2049
2071
|
verbose: Boolean(verbose),
|
|
2050
2072
|
version: version || '',
|
|
2051
2073
|
buildNumber: buildNumber || '',
|
|
2052
|
-
configuration,
|
|
2053
|
-
//
|
|
2054
|
-
...(opts.
|
|
2074
|
+
buildType: configuration,
|
|
2075
|
+
// Configuration/deployment target (e.g., 'prod', 'stg', 'dev')
|
|
2076
|
+
...(opts.configuration ? { configuration: opts.configuration } : {}),
|
|
2055
2077
|
...(distributionType ? { distributionType } : {}),
|
|
2056
2078
|
// Android package type override (apk or aab) - takes precedence over distributionType
|
|
2057
|
-
...(opts.androidPackageType
|
|
2079
|
+
...(opts.androidPackageType
|
|
2080
|
+
? { androidPackageType: opts.androidPackageType }
|
|
2081
|
+
: {}),
|
|
2058
2082
|
fingerprint,
|
|
2059
2083
|
// Provide the relative key (without public/) – the workflow prepends public/
|
|
2060
2084
|
s3Key: s3KeyRel,
|
|
2061
2085
|
// Workspace context for Nx monorepos
|
|
2062
2086
|
...(workspaceInfo ? { workspace: workspaceInfo } : {}),
|
|
2063
2087
|
// For standalone projects, include project name for env var scoping
|
|
2064
|
-
...(standaloneProjectName
|
|
2088
|
+
...(standaloneProjectName
|
|
2089
|
+
? { projectName: standaloneProjectName }
|
|
2090
|
+
: {}),
|
|
2065
2091
|
// Only include raw credentials if not encrypted
|
|
2066
2092
|
...(encryptedSecrets ? { encryptedSecrets } : {}),
|
|
2067
2093
|
...(!encryptedSecrets && iosCredentials ? { iosCredentials } : {}),
|
|
@@ -2092,7 +2118,8 @@ export async function build(cliPlatformArg, cliConfigurationArg, cliDistribution
|
|
|
2092
2118
|
headers: await getAuthHeaders(),
|
|
2093
2119
|
});
|
|
2094
2120
|
if (buildRes?.data?.version) {
|
|
2095
|
-
recordedVersion =
|
|
2121
|
+
recordedVersion =
|
|
2122
|
+
String(buildRes.data.version).trim() || recordedVersion;
|
|
2096
2123
|
}
|
|
2097
2124
|
if (buildRes?.data?.buildNumber) {
|
|
2098
2125
|
recordedBuildNumber =
|
|
@@ -2383,7 +2410,10 @@ export async function submit(cliPlatformArg, cliTrackArg, verbose = false, optio
|
|
|
2383
2410
|
]);
|
|
2384
2411
|
// Build iOS credentials if provided
|
|
2385
2412
|
const ascKeyPath = normalizePath(iosConfig.apiKeyPath);
|
|
2386
|
-
if (iosConfig.ascKeyId &&
|
|
2413
|
+
if (iosConfig.ascKeyId &&
|
|
2414
|
+
iosConfig.ascIssuerId &&
|
|
2415
|
+
ascKeyPath &&
|
|
2416
|
+
fs.existsSync(ascKeyPath)) {
|
|
2387
2417
|
const ascPrivateKeyContent = fs.readFileSync(ascKeyPath, 'utf8');
|
|
2388
2418
|
const ascPrivateKeyBase64 = Buffer.from(ascPrivateKeyContent).toString('base64');
|
|
2389
2419
|
credentials.iosCredentials = {
|
|
@@ -2541,7 +2571,7 @@ export async function submit(cliPlatformArg, cliTrackArg, verbose = false, optio
|
|
|
2541
2571
|
export async function update(cliPlatformArg, cliVersionArg, verbose = false, options // string for backwards compatibility with old projectArg
|
|
2542
2572
|
) {
|
|
2543
2573
|
// Normalize options - support both new object and legacy string projectArg
|
|
2544
|
-
const opts = typeof options === 'string' ? { project: options } :
|
|
2574
|
+
const opts = typeof options === 'string' ? { project: options } : options || {};
|
|
2545
2575
|
ensureInitialized();
|
|
2546
2576
|
try {
|
|
2547
2577
|
await ensureOrgSelected({
|
|
@@ -2596,9 +2626,11 @@ export async function update(cliPlatformArg, cliVersionArg, verbose = false, opt
|
|
|
2596
2626
|
platform = chosenPlatform;
|
|
2597
2627
|
spinner.start('Preparing over-the-air update...');
|
|
2598
2628
|
}
|
|
2599
|
-
// Determine
|
|
2600
|
-
let
|
|
2601
|
-
if (!
|
|
2629
|
+
// Determine configuration for workspace builds (CLI arg preferred, then prompt if available)
|
|
2630
|
+
let configurationTarget = opts.configuration;
|
|
2631
|
+
if (!configurationTarget &&
|
|
2632
|
+
workspaceCtx.type === 'nx' &&
|
|
2633
|
+
!opts.nonInteractive) {
|
|
2602
2634
|
spinner.stop();
|
|
2603
2635
|
const nxConfigs = detectNxBuildConfigurations(workspaceCtx.appRoot);
|
|
2604
2636
|
if (nxConfigs && nxConfigs.configurations.length > 0) {
|
|
@@ -2608,21 +2640,21 @@ export async function update(cliPlatformArg, cliVersionArg, verbose = false, opt
|
|
|
2608
2640
|
}));
|
|
2609
2641
|
// Add option to skip/use default
|
|
2610
2642
|
choices.unshift({ name: '(none - use defaults)', value: '' });
|
|
2611
|
-
const {
|
|
2643
|
+
const { chosenConfig } = await inquirer.prompt([
|
|
2612
2644
|
{
|
|
2613
2645
|
type: 'list',
|
|
2614
|
-
name: '
|
|
2615
|
-
message: '
|
|
2646
|
+
name: 'chosenConfig',
|
|
2647
|
+
message: 'Configuration (deployment target):',
|
|
2616
2648
|
choices,
|
|
2617
2649
|
default: nxConfigs.defaultConfiguration || '',
|
|
2618
2650
|
},
|
|
2619
2651
|
]);
|
|
2620
|
-
|
|
2652
|
+
configurationTarget = chosenConfig || undefined;
|
|
2621
2653
|
}
|
|
2622
2654
|
spinner.start('Preparing over-the-air update...');
|
|
2623
2655
|
}
|
|
2624
|
-
// Store resolved
|
|
2625
|
-
opts.
|
|
2656
|
+
// Store resolved configuration back to opts for later use
|
|
2657
|
+
opts.configuration = configurationTarget;
|
|
2626
2658
|
// Infer version from native project files (same as build)
|
|
2627
2659
|
const appleVersionInfo = platform === 'ios' || platform === 'visionos'
|
|
2628
2660
|
? getAppleVersionFromInfoPlist(platform)
|
|
@@ -2727,7 +2759,8 @@ export async function update(cliPlatformArg, cliVersionArg, verbose = false, opt
|
|
|
2727
2759
|
}
|
|
2728
2760
|
else if (opts.nonInteractive) {
|
|
2729
2761
|
// In non-interactive mode, use server suggestion or leave undefined for auto-increment
|
|
2730
|
-
buildNumber =
|
|
2762
|
+
buildNumber =
|
|
2763
|
+
serverSuggestedBuildNumber || localInferredBuildNumber || undefined;
|
|
2731
2764
|
}
|
|
2732
2765
|
else {
|
|
2733
2766
|
const buildPromptAnswers = await inquirer.prompt([
|
|
@@ -2761,7 +2794,8 @@ export async function update(cliPlatformArg, cliVersionArg, verbose = false, opt
|
|
|
2761
2794
|
default: '',
|
|
2762
2795
|
},
|
|
2763
2796
|
]);
|
|
2764
|
-
buildNumber =
|
|
2797
|
+
buildNumber =
|
|
2798
|
+
String(buildPromptAnswers.buildNumber || '').trim() || undefined;
|
|
2765
2799
|
notes = buildPromptAnswers.notes || '';
|
|
2766
2800
|
}
|
|
2767
2801
|
// Check the app directory structure before packaging
|
|
@@ -2803,7 +2837,7 @@ export async function update(cliPlatformArg, cliVersionArg, verbose = false, opt
|
|
|
2803
2837
|
};
|
|
2804
2838
|
const hashesMatch = printFingerprintComparisonDetails(`build ${latestBuild.id}`, baseFp, 'local project', localFp);
|
|
2805
2839
|
if (!hashesMatch) {
|
|
2806
|
-
console.log(
|
|
2840
|
+
console.log('These changes cannot be delivered via OTA and will require users to update through the store.');
|
|
2807
2841
|
// In non-interactive mode, proceed automatically (CI pipelines should control this via version matching)
|
|
2808
2842
|
if (opts.nonInteractive) {
|
|
2809
2843
|
console.log('Non-interactive mode: Proceeding with OTA update despite fingerprint mismatch.');
|
|
@@ -2848,7 +2882,7 @@ export async function update(cliPlatformArg, cliVersionArg, verbose = false, opt
|
|
|
2848
2882
|
spinner.start('Packaging for over-the-air update...');
|
|
2849
2883
|
// Create the update bundle (workspace-aware) - pass true to include node_modules for updates
|
|
2850
2884
|
// Use workspaceCtx.projectName for Nx workspaces (from project.json)
|
|
2851
|
-
const projectName = workspaceCtx?.projectName || await getProjectName();
|
|
2885
|
+
const projectName = workspaceCtx?.projectName || (await getProjectName());
|
|
2852
2886
|
const { zipPath, workspaceContext } = await zipProject(projectName, workspaceCtx, true, verbose);
|
|
2853
2887
|
spinner.text = 'Uploading update to Norrix cloud storage...';
|
|
2854
2888
|
const fileBuffer = fs.readFileSync(zipPath);
|
|
@@ -2863,18 +2897,22 @@ export async function update(cliPlatformArg, cliVersionArg, verbose = false, opt
|
|
|
2863
2897
|
// Upload to S3 - uses presigned URL for API key auth, or direct SDK for Cognito auth
|
|
2864
2898
|
const actualS3Key = await putObjectToStorage(defaultS3Key, fileBuffer, projectName, platform, 'update', verbose);
|
|
2865
2899
|
// Extract relative key (without public/ prefix if present) for the API
|
|
2866
|
-
const s3KeyRel = actualS3Key.startsWith('public/')
|
|
2900
|
+
const s3KeyRel = actualS3Key.startsWith('public/')
|
|
2901
|
+
? actualS3Key.slice(7)
|
|
2902
|
+
: actualS3Key;
|
|
2867
2903
|
spinner.text = 'Upload complete. Starting update...';
|
|
2868
2904
|
// Include workspace info for CI to properly navigate the project structure
|
|
2869
|
-
const workspaceInfo = workspaceContext.type === 'nx'
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2905
|
+
const workspaceInfo = workspaceContext.type === 'nx'
|
|
2906
|
+
? {
|
|
2907
|
+
workspaceType: workspaceContext.type,
|
|
2908
|
+
appPath: workspaceContext.relativeAppPath,
|
|
2909
|
+
projectName: workspaceContext.projectName,
|
|
2910
|
+
}
|
|
2911
|
+
: undefined;
|
|
2874
2912
|
// For standalone projects, use the project option or infer from app ID
|
|
2875
2913
|
// This allows env vars to be scoped to specific standalone projects within an org
|
|
2876
2914
|
const standaloneProjectName = workspaceContext.type === 'standalone'
|
|
2877
|
-
?
|
|
2915
|
+
? opts.project || appId
|
|
2878
2916
|
: undefined;
|
|
2879
2917
|
const response = await axios.post(`${API_URL}/update`, {
|
|
2880
2918
|
appId,
|
|
@@ -2883,14 +2921,16 @@ export async function update(cliPlatformArg, cliVersionArg, verbose = false, opt
|
|
|
2883
2921
|
buildNumber: buildNumber || '',
|
|
2884
2922
|
releaseNotes: notes,
|
|
2885
2923
|
fingerprint,
|
|
2886
|
-
//
|
|
2887
|
-
...(opts.
|
|
2924
|
+
// Configuration/deployment target (e.g., 'prod', 'stg', 'dev')
|
|
2925
|
+
...(opts.configuration ? { configuration: opts.configuration } : {}),
|
|
2888
2926
|
// Provide the relative key (without public/). Consumers will prepend public/
|
|
2889
2927
|
s3Key: s3KeyRel,
|
|
2890
2928
|
// Workspace context for Nx monorepos
|
|
2891
2929
|
...(workspaceInfo ? { workspace: workspaceInfo } : {}),
|
|
2892
2930
|
// For standalone projects, include project name for env var scoping
|
|
2893
|
-
...(standaloneProjectName
|
|
2931
|
+
...(standaloneProjectName
|
|
2932
|
+
? { projectName: standaloneProjectName }
|
|
2933
|
+
: {}),
|
|
2894
2934
|
}, {
|
|
2895
2935
|
headers: {
|
|
2896
2936
|
'Content-Type': 'application/json',
|
|
@@ -2907,8 +2947,7 @@ export async function update(cliPlatformArg, cliVersionArg, verbose = false, opt
|
|
|
2907
2947
|
let recordedUpdateVersion = String(version || '').trim() || undefined;
|
|
2908
2948
|
let recordedUpdateBuildNumber = (response?.data?.buildNumber
|
|
2909
2949
|
? String(response.data.buildNumber).trim()
|
|
2910
|
-
: undefined) ||
|
|
2911
|
-
(buildNumber ? String(buildNumber).trim() : undefined);
|
|
2950
|
+
: undefined) || (buildNumber ? String(buildNumber).trim() : undefined);
|
|
2912
2951
|
if (!recordedUpdateBuildNumber) {
|
|
2913
2952
|
try {
|
|
2914
2953
|
const updateRes = await axios.get(`${API_URL}/update/${updateId}`, {
|