@analyticscli/growth-engineer 0.1.1-preview.25 → 0.1.1-preview.27
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.
|
@@ -2133,6 +2133,12 @@ function summarizeFailureReason(detail) {
|
|
|
2133
2133
|
if (/ASC Setup Admin key auth failed: .*\.p8 key could not be parsed/i.test(text)) {
|
|
2134
2134
|
return 'ASC Setup Admin key auth failed: the .p8 key could not be parsed';
|
|
2135
2135
|
}
|
|
2136
|
+
if (/ASC Reports key auth failed: .*\.p8 file permissions are too open/i.test(text)) {
|
|
2137
|
+
return 'ASC Reports key auth failed: .p8 file permissions are too open';
|
|
2138
|
+
}
|
|
2139
|
+
if (/ASC Setup Admin key auth failed: .*\.p8 file permissions are too open/i.test(text)) {
|
|
2140
|
+
return 'ASC Setup Admin key auth failed: .p8 file permissions are too open';
|
|
2141
|
+
}
|
|
2136
2142
|
if (/ASC .*\.p8 private key is invalid|invalid private key|failed to parse|sequence truncated|malformed|asn1/i.test(text)) {
|
|
2137
2143
|
return 'ASC auth failed: the .p8 key could not be parsed';
|
|
2138
2144
|
}
|
|
@@ -2178,8 +2184,14 @@ function summarizeFailureFix(connector, blockers) {
|
|
|
2178
2184
|
return 'Paste a Coolify base URL and read-only API token from Keys & Tokens / API tokens, then rerun setup.';
|
|
2179
2185
|
}
|
|
2180
2186
|
if (connector === 'asc') {
|
|
2187
|
+
if (/invalid value: ['"]?state|parameter has an invalid value: ['"]?state|--state/i.test(combined)) {
|
|
2188
|
+
return 'Update Growth Engineer and rerun ASC setup. Setup no longer uses the flaky ASC analytics request state filter.';
|
|
2189
|
+
}
|
|
2190
|
+
if (/file permissions are too open|too permissive|chmod 600/i.test(combined)) {
|
|
2191
|
+
return 'Rerun ASC setup. The wizard saves a secure local copy of AuthKey_<KEY_ID>.p8 with chmod 600 before testing.';
|
|
2192
|
+
}
|
|
2181
2193
|
if (/Reports key auth failed|Reports key/i.test(combined) && /private key|could not be parsed|failed to parse|asn1/i.test(combined)) {
|
|
2182
|
-
return 'Use the original downloaded AuthKey_<KEY_ID>.p8 file for the Reports key. The wizard bypasses old asc keychain credentials during setup.';
|
|
2194
|
+
return 'Use the original downloaded AuthKey_<KEY_ID>.p8 file for the Reports key. The wizard bypasses old asc keychain/config credentials during setup.';
|
|
2183
2195
|
}
|
|
2184
2196
|
if (/Setup Admin key auth failed|Admin key/i.test(combined) && /private key|could not be parsed|failed to parse|asn1/i.test(combined)) {
|
|
2185
2197
|
return 'Use the original downloaded AuthKey_<KEY_ID>.p8 file for the Setup Admin key. This key is temporary and should have the Admin role.';
|
|
@@ -2737,6 +2749,15 @@ function inferAscKeyIdFromPrivateKeyPath(filePath) {
|
|
|
2737
2749
|
const match = fileName.match(/^AuthKey_([A-Za-z0-9]+)\.p8$/);
|
|
2738
2750
|
return match?.[1] || '';
|
|
2739
2751
|
}
|
|
2752
|
+
async function copyAscPrivateKeyToSecurePath(sourcePath, keyId, suffix = '') {
|
|
2753
|
+
const destinationPath = resolveAscPrivateKeyPath(keyId, suffix);
|
|
2754
|
+
await fs.mkdir(path.dirname(destinationPath), { recursive: true, mode: 0o700 });
|
|
2755
|
+
if (path.resolve(sourcePath) !== path.resolve(destinationPath)) {
|
|
2756
|
+
await fs.copyFile(sourcePath, destinationPath);
|
|
2757
|
+
}
|
|
2758
|
+
await fs.chmod(destinationPath, 0o600);
|
|
2759
|
+
return destinationPath;
|
|
2760
|
+
}
|
|
2740
2761
|
function renderEnvValue(value) {
|
|
2741
2762
|
return `"${String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$')}"`;
|
|
2742
2763
|
}
|
|
@@ -3977,6 +3998,7 @@ async function guideAscConnector(rl, secrets) {
|
|
|
3977
3998
|
process.stdout.write(`${bold('Enter the Reports key now:')}\n`);
|
|
3978
3999
|
printBullets([
|
|
3979
4000
|
`${bold('.p8 path')} to Apple\'s original ${bold('AuthKey_<KEY_ID>.p8')} file. ${bold('Do not rename it')}; KEY_ID is read from the filename.`,
|
|
4001
|
+
`The wizard saves a secure local copy with ${bold('chmod 600')}.`,
|
|
3980
4002
|
`${bold('Issuer ID')} from the API keys page. Same value for both keys.`,
|
|
3981
4003
|
`${bold('Vendor Number')} from Sales and Trends > Reports.`,
|
|
3982
4004
|
]);
|
|
@@ -3987,11 +4009,13 @@ async function guideAscConnector(rl, secrets) {
|
|
|
3987
4009
|
});
|
|
3988
4010
|
let keyId = normalKeyPath.keyId;
|
|
3989
4011
|
if (normalKeyPath.privateKeyPath) {
|
|
3990
|
-
|
|
4012
|
+
const securePrivateKeyPath = await copyAscPrivateKeyToSecurePath(normalKeyPath.privateKeyPath, keyId);
|
|
4013
|
+
secrets.ASC_PRIVATE_KEY_PATH = securePrivateKeyPath;
|
|
3991
4014
|
secrets.ASC_PRIVATE_KEY = DELETE_SECRET;
|
|
3992
4015
|
secrets.ASC_PRIVATE_KEY_B64 = DELETE_SECRET;
|
|
3993
4016
|
secrets.ASC_KEY_ID = keyId;
|
|
3994
4017
|
process.stdout.write(`Inferred ASC_KEY_ID=${keyId} from ${path.basename(normalKeyPath.privateKeyPath)}.\n`);
|
|
4018
|
+
process.stdout.write(`Saved secure Reports key copy to ${securePrivateKeyPath} with chmod 600.\n`);
|
|
3995
4019
|
}
|
|
3996
4020
|
const issuerId = await ask(rl, 'ASC_ISSUER_ID (same for both keys, empty = skip)', process.env.ASC_ISSUER_ID || '');
|
|
3997
4021
|
if (issuerId.trim())
|
|
@@ -4025,7 +4049,7 @@ async function guideAscBootstrapAdminKey(rl, issuerIdDefault = '') {
|
|
|
4025
4049
|
printBullets([
|
|
4026
4050
|
`${bold('Role must be Admin')} so Apple can create the first App Analytics report request.`,
|
|
4027
4051
|
`${bold('Use original AuthKey_<KEY_ID>.p8 filename')} so KEY_ID is read automatically.`,
|
|
4028
|
-
`${bold('Not saved')} to secrets.env.
|
|
4052
|
+
`${bold('Not saved')} to secrets.env. The temporary secure copy is deleted after setup.`,
|
|
4029
4053
|
]);
|
|
4030
4054
|
const bootstrapKeyPath = await askAscPrivateKeyPathWithKeyId(rl, {
|
|
4031
4055
|
label: 'Setup Admin .p8 path (AuthKey_<KEY_ID>.p8, empty = paste)',
|
|
@@ -4038,11 +4062,11 @@ async function guideAscBootstrapAdminKey(rl, issuerIdDefault = '') {
|
|
|
4038
4062
|
bootstrapIssuerId = await ask(rl, 'ASC_ISSUER_ID (same API keys page)', process.env.ASC_ISSUER_ID || '');
|
|
4039
4063
|
}
|
|
4040
4064
|
if (bootstrapKeyPath.privateKeyPath) {
|
|
4041
|
-
|
|
4065
|
+
const secureBootstrapPath = await copyAscPrivateKeyToSecurePath(bootstrapKeyPath.privateKeyPath, bootstrapKeyId, '_bootstrap_admin');
|
|
4066
|
+
bootstrapEnv.ASC_BOOTSTRAP_PRIVATE_KEY_PATH = secureBootstrapPath;
|
|
4042
4067
|
process.stdout.write(`Inferred ASC_BOOTSTRAP_KEY_ID=${bootstrapKeyId} from ${path.basename(bootstrapKeyPath.privateKeyPath)}.\n`);
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
bootstrapEnv.ASC_BOOTSTRAP_PRIVATE_KEY_DELETE_AFTER_USE = '1';
|
|
4068
|
+
process.stdout.write(`Saved secure temporary Admin key copy to ${secureBootstrapPath} with chmod 600.\n`);
|
|
4069
|
+
bootstrapEnv.ASC_BOOTSTRAP_PRIVATE_KEY_DELETE_AFTER_USE = '1';
|
|
4046
4070
|
}
|
|
4047
4071
|
else {
|
|
4048
4072
|
bootstrapKeyId = await ask(rl, 'ASC_BOOTSTRAP_KEY_ID (from AuthKey_<KEY_ID>.p8)', '');
|